Linux Audio

Check our new training course

Loading...
Note: File does not exist in v4.6.
  1// SPDX-License-Identifier: GPL-2.0
  2/*
  3 * S6E63M0 AMOLED LCD drm_panel driver.
  4 *
  5 * Copyright (C) 2019 Paweł Chmiel <pawel.mikolaj.chmiel@gmail.com>
  6 * Derived from drivers/gpu/drm/panel-samsung-ld9040.c
  7 *
  8 * Andrzej Hajda <a.hajda@samsung.com>
  9 */
 10
 11#include <drm/drm_modes.h>
 12#include <drm/drm_panel.h>
 13#include <drm/drm_print.h>
 14
 15#include <linux/backlight.h>
 16#include <linux/delay.h>
 17#include <linux/gpio/consumer.h>
 18#include <linux/module.h>
 19#include <linux/regulator/consumer.h>
 20#include <linux/spi/spi.h>
 21
 22#include <video/mipi_display.h>
 23
 24/* Manufacturer Command Set */
 25#define MCS_ELVSS_ON                0xb1
 26#define MCS_MIECTL1                0xc0
 27#define MCS_BCMODE                              0xc1
 28#define MCS_DISCTL   0xf2
 29#define MCS_SRCCTL           0xf6
 30#define MCS_IFCTL                       0xf7
 31#define MCS_PANELCTL         0xF8
 32#define MCS_PGAMMACTL                   0xfa
 33
 34#define NUM_GAMMA_LEVELS             11
 35#define GAMMA_TABLE_COUNT           23
 36
 37#define DATA_MASK                                       0x100
 38
 39#define MAX_BRIGHTNESS              (NUM_GAMMA_LEVELS - 1)
 40
 41/* array of gamma tables for gamma value 2.2 */
 42static u8 const s6e63m0_gamma_22[NUM_GAMMA_LEVELS][GAMMA_TABLE_COUNT] = {
 43	{ MCS_PGAMMACTL, 0x00,
 44	  0x18, 0x08, 0x24, 0x78, 0xEC, 0x3D, 0xC8,
 45	  0xC2, 0xB6, 0xC4, 0xC7, 0xB6, 0xD5, 0xD7,
 46	  0xCC, 0x00, 0x39, 0x00, 0x36, 0x00, 0x51 },
 47	{ MCS_PGAMMACTL, 0x00,
 48	  0x18, 0x08, 0x24, 0x73, 0x4A, 0x3D, 0xC0,
 49	  0xC2, 0xB1, 0xBB, 0xBE, 0xAC, 0xCE, 0xCF,
 50	  0xC5, 0x00, 0x5D, 0x00, 0x5E, 0x00, 0x82 },
 51	{ MCS_PGAMMACTL, 0x00,
 52	  0x18, 0x08, 0x24, 0x70, 0x51, 0x3E, 0xBF,
 53	  0xC1, 0xAF, 0xB9, 0xBC, 0xAB, 0xCC, 0xCC,
 54	  0xC2, 0x00, 0x65, 0x00, 0x67, 0x00, 0x8D },
 55	{ MCS_PGAMMACTL, 0x00,
 56	  0x18, 0x08, 0x24, 0x6C, 0x54, 0x3A, 0xBC,
 57	  0xBF, 0xAC, 0xB7, 0xBB, 0xA9, 0xC9, 0xC9,
 58	  0xBE, 0x00, 0x71, 0x00, 0x73, 0x00, 0x9E },
 59	{ MCS_PGAMMACTL, 0x00,
 60	  0x18, 0x08, 0x24, 0x69, 0x54, 0x37, 0xBB,
 61	  0xBE, 0xAC, 0xB4, 0xB7, 0xA6, 0xC7, 0xC8,
 62	  0xBC, 0x00, 0x7B, 0x00, 0x7E, 0x00, 0xAB },
 63	{ MCS_PGAMMACTL, 0x00,
 64	  0x18, 0x08, 0x24, 0x66, 0x55, 0x34, 0xBA,
 65	  0xBD, 0xAB, 0xB1, 0xB5, 0xA3, 0xC5, 0xC6,
 66	  0xB9, 0x00, 0x85, 0x00, 0x88, 0x00, 0xBA },
 67	{ MCS_PGAMMACTL, 0x00,
 68	  0x18, 0x08, 0x24, 0x63, 0x53, 0x31, 0xB8,
 69	  0xBC, 0xA9, 0xB0, 0xB5, 0xA2, 0xC4, 0xC4,
 70	  0xB8, 0x00, 0x8B, 0x00, 0x8E, 0x00, 0xC2 },
 71	{ MCS_PGAMMACTL, 0x00,
 72	  0x18, 0x08, 0x24, 0x62, 0x54, 0x30, 0xB9,
 73	  0xBB, 0xA9, 0xB0, 0xB3, 0xA1, 0xC1, 0xC3,
 74	  0xB7, 0x00, 0x91, 0x00, 0x95, 0x00, 0xDA },
 75	{ MCS_PGAMMACTL, 0x00,
 76	  0x18, 0x08, 0x24, 0x66, 0x58, 0x34, 0xB6,
 77	  0xBA, 0xA7, 0xAF, 0xB3, 0xA0, 0xC1, 0xC2,
 78	  0xB7, 0x00, 0x97, 0x00, 0x9A, 0x00, 0xD1 },
 79	{ MCS_PGAMMACTL, 0x00,
 80	  0x18, 0x08, 0x24, 0x64, 0x56, 0x33, 0xB6,
 81	  0xBA, 0xA8, 0xAC, 0xB1, 0x9D, 0xC1, 0xC1,
 82	  0xB7, 0x00, 0x9C, 0x00, 0x9F, 0x00, 0xD6 },
 83	{ MCS_PGAMMACTL, 0x00,
 84	  0x18, 0x08, 0x24, 0x5f, 0x50, 0x2d, 0xB6,
 85	  0xB9, 0xA7, 0xAd, 0xB1, 0x9f, 0xbe, 0xC0,
 86	  0xB5, 0x00, 0xa0, 0x00, 0xa4, 0x00, 0xdb },
 87};
 88
 89struct s6e63m0 {
 90	struct device *dev;
 91	struct drm_panel panel;
 92	struct backlight_device *bl_dev;
 93
 94	struct regulator_bulk_data supplies[2];
 95	struct gpio_desc *reset_gpio;
 96
 97	bool prepared;
 98	bool enabled;
 99
100	/*
101	 * This field is tested by functions directly accessing bus before
102	 * transfer, transfer is skipped if it is set. In case of transfer
103	 * failure or unexpected response the field is set to error value.
104	 * Such construct allows to eliminate many checks in higher level
105	 * functions.
106	 */
107	int error;
108};
109
110static const struct drm_display_mode default_mode = {
111	.clock		= 25628,
112	.hdisplay	= 480,
113	.hsync_start	= 480 + 16,
114	.hsync_end	= 480 + 16 + 2,
115	.htotal		= 480 + 16 + 2 + 16,
116	.vdisplay	= 800,
117	.vsync_start	= 800 + 28,
118	.vsync_end	= 800 + 28 + 2,
119	.vtotal		= 800 + 28 + 2 + 1,
120	.width_mm	= 53,
121	.height_mm	= 89,
122	.flags		= DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC,
123};
124
125static inline struct s6e63m0 *panel_to_s6e63m0(struct drm_panel *panel)
126{
127	return container_of(panel, struct s6e63m0, panel);
128}
129
130static int s6e63m0_clear_error(struct s6e63m0 *ctx)
131{
132	int ret = ctx->error;
133
134	ctx->error = 0;
135	return ret;
136}
137
138static int s6e63m0_spi_write_word(struct s6e63m0 *ctx, u16 data)
139{
140	struct spi_device *spi = to_spi_device(ctx->dev);
141	struct spi_transfer xfer = {
142		.len	= 2,
143		.tx_buf = &data,
144	};
145	struct spi_message msg;
146
147	spi_message_init(&msg);
148	spi_message_add_tail(&xfer, &msg);
149
150	return spi_sync(spi, &msg);
151}
152
153static void s6e63m0_dcs_write(struct s6e63m0 *ctx, const u8 *data, size_t len)
154{
155	int ret = 0;
156
157	if (ctx->error < 0 || len == 0)
158		return;
159
160	DRM_DEV_DEBUG(ctx->dev, "writing dcs seq: %*ph\n", (int)len, data);
161	ret = s6e63m0_spi_write_word(ctx, *data);
162
163	while (!ret && --len) {
164		++data;
165		ret = s6e63m0_spi_write_word(ctx, *data | DATA_MASK);
166	}
167
168	if (ret) {
169		DRM_DEV_ERROR(ctx->dev, "error %d writing dcs seq: %*ph\n", ret,
170			      (int)len, data);
171		ctx->error = ret;
172	}
173
174	usleep_range(300, 310);
175}
176
177#define s6e63m0_dcs_write_seq_static(ctx, seq ...) \
178	({ \
179		static const u8 d[] = { seq }; \
180		s6e63m0_dcs_write(ctx, d, ARRAY_SIZE(d)); \
181	})
182
183static void s6e63m0_init(struct s6e63m0 *ctx)
184{
185	s6e63m0_dcs_write_seq_static(ctx, MCS_PANELCTL,
186				     0x01, 0x27, 0x27, 0x07, 0x07, 0x54, 0x9f,
187				     0x63, 0x86, 0x1a, 0x33, 0x0d, 0x00, 0x00);
188
189	s6e63m0_dcs_write_seq_static(ctx, MCS_DISCTL,
190				     0x02, 0x03, 0x1c, 0x10, 0x10);
191	s6e63m0_dcs_write_seq_static(ctx, MCS_IFCTL,
192				     0x03, 0x00, 0x00);
193
194	s6e63m0_dcs_write_seq_static(ctx, MCS_PGAMMACTL,
195				     0x00, 0x18, 0x08, 0x24, 0x64, 0x56, 0x33,
196				     0xb6, 0xba, 0xa8, 0xac, 0xb1, 0x9d, 0xc1,
197				     0xc1, 0xb7, 0x00, 0x9c, 0x00, 0x9f, 0x00,
198				     0xd6);
199	s6e63m0_dcs_write_seq_static(ctx, MCS_PGAMMACTL,
200				     0x01);
201
202	s6e63m0_dcs_write_seq_static(ctx, MCS_SRCCTL,
203				     0x00, 0x8c, 0x07);
204	s6e63m0_dcs_write_seq_static(ctx, 0xb3,
205				     0xc);
206
207	s6e63m0_dcs_write_seq_static(ctx, 0xb5,
208				     0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17,
209				     0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b,
210				     0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a,
211				     0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23,
212				     0x21, 0x20, 0x1e, 0x1e);
213
214	s6e63m0_dcs_write_seq_static(ctx, 0xb6,
215				     0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x44,
216				     0x44, 0x55, 0x55, 0x66, 0x66, 0x66, 0x66,
217				     0x66, 0x66);
218
219	s6e63m0_dcs_write_seq_static(ctx, 0xb7,
220				     0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17,
221				     0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b,
222				     0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a,
223				     0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23,
224				     0x21, 0x20, 0x1e, 0x1e, 0x00, 0x00, 0x11,
225				     0x22, 0x33, 0x44, 0x44, 0x44, 0x55, 0x55,
226				     0x66, 0x66, 0x66, 0x66, 0x66, 0x66);
227
228	s6e63m0_dcs_write_seq_static(ctx, 0xb9,
229				     0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17,
230				     0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b,
231				     0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a,
232				     0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23,
233				     0x21, 0x20, 0x1e, 0x1e);
234
235	s6e63m0_dcs_write_seq_static(ctx, 0xba,
236				     0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x44,
237				     0x44, 0x55, 0x55, 0x66, 0x66, 0x66, 0x66,
238				     0x66, 0x66);
239
240	s6e63m0_dcs_write_seq_static(ctx, MCS_BCMODE,
241				     0x4d, 0x96, 0x1d, 0x00, 0x00, 0x01, 0xdf,
242				     0x00, 0x00, 0x03, 0x1f, 0x00, 0x00, 0x00,
243				     0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x06,
244				     0x09, 0x0d, 0x0f, 0x12, 0x15, 0x18);
245
246	s6e63m0_dcs_write_seq_static(ctx, 0xb2,
247				     0x10, 0x10, 0x0b, 0x05);
248
249	s6e63m0_dcs_write_seq_static(ctx, MCS_MIECTL1,
250				     0x01);
251
252	s6e63m0_dcs_write_seq_static(ctx, MCS_ELVSS_ON,
253				     0x0b);
254
255	s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_EXIT_SLEEP_MODE);
256}
257
258static int s6e63m0_power_on(struct s6e63m0 *ctx)
259{
260	int ret;
261
262	ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
263	if (ret < 0)
264		return ret;
265
266	msleep(25);
267
268	gpiod_set_value(ctx->reset_gpio, 0);
269	msleep(120);
270
271	return 0;
272}
273
274static int s6e63m0_power_off(struct s6e63m0 *ctx)
275{
276	int ret;
277
278	gpiod_set_value(ctx->reset_gpio, 1);
279	msleep(120);
280
281	ret = regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
282	if (ret < 0)
283		return ret;
284
285	return 0;
286}
287
288static int s6e63m0_disable(struct drm_panel *panel)
289{
290	struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
291
292	if (!ctx->enabled)
293		return 0;
294
295	backlight_disable(ctx->bl_dev);
296
297	s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_ENTER_SLEEP_MODE);
298	msleep(200);
299
300	ctx->enabled = false;
301
302	return 0;
303}
304
305static int s6e63m0_unprepare(struct drm_panel *panel)
306{
307	struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
308	int ret;
309
310	if (!ctx->prepared)
311		return 0;
312
313	s6e63m0_clear_error(ctx);
314
315	ret = s6e63m0_power_off(ctx);
316	if (ret < 0)
317		return ret;
318
319	ctx->prepared = false;
320
321	return 0;
322}
323
324static int s6e63m0_prepare(struct drm_panel *panel)
325{
326	struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
327	int ret;
328
329	if (ctx->prepared)
330		return 0;
331
332	ret = s6e63m0_power_on(ctx);
333	if (ret < 0)
334		return ret;
335
336	s6e63m0_init(ctx);
337
338	ret = s6e63m0_clear_error(ctx);
339
340	if (ret < 0)
341		s6e63m0_unprepare(panel);
342
343	ctx->prepared = true;
344
345	return ret;
346}
347
348static int s6e63m0_enable(struct drm_panel *panel)
349{
350	struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
351
352	if (ctx->enabled)
353		return 0;
354
355	s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_ON);
356
357	backlight_enable(ctx->bl_dev);
358
359	ctx->enabled = true;
360
361	return 0;
362}
363
364static int s6e63m0_get_modes(struct drm_panel *panel,
365			     struct drm_connector *connector)
366{
367	struct drm_display_mode *mode;
368
369	mode = drm_mode_duplicate(connector->dev, &default_mode);
370	if (!mode) {
371		DRM_ERROR("failed to add mode %ux%ux@%u\n",
372			  default_mode.hdisplay, default_mode.vdisplay,
373			  drm_mode_vrefresh(&default_mode));
374		return -ENOMEM;
375	}
376
377	drm_mode_set_name(mode);
378
379	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
380	drm_mode_probed_add(connector, mode);
381
382	return 1;
383}
384
385static const struct drm_panel_funcs s6e63m0_drm_funcs = {
386	.disable	= s6e63m0_disable,
387	.unprepare	= s6e63m0_unprepare,
388	.prepare	= s6e63m0_prepare,
389	.enable		= s6e63m0_enable,
390	.get_modes	= s6e63m0_get_modes,
391};
392
393static int s6e63m0_set_brightness(struct backlight_device *bd)
394{
395	struct s6e63m0 *ctx = bl_get_data(bd);
396
397	int brightness = bd->props.brightness;
398
399	/* disable and set new gamma */
400	s6e63m0_dcs_write(ctx, s6e63m0_gamma_22[brightness],
401			  ARRAY_SIZE(s6e63m0_gamma_22[brightness]));
402
403	/* update gamma table. */
404	s6e63m0_dcs_write_seq_static(ctx, MCS_PGAMMACTL, 0x01);
405
406	return s6e63m0_clear_error(ctx);
407}
408
409static const struct backlight_ops s6e63m0_backlight_ops = {
410	.update_status	= s6e63m0_set_brightness,
411};
412
413static int s6e63m0_backlight_register(struct s6e63m0 *ctx)
414{
415	struct backlight_properties props = {
416		.type		= BACKLIGHT_RAW,
417		.brightness	= MAX_BRIGHTNESS,
418		.max_brightness = MAX_BRIGHTNESS
419	};
420	struct device *dev = ctx->dev;
421	int ret = 0;
422
423	ctx->bl_dev = devm_backlight_device_register(dev, "panel", dev, ctx,
424						     &s6e63m0_backlight_ops,
425						     &props);
426	if (IS_ERR(ctx->bl_dev)) {
427		ret = PTR_ERR(ctx->bl_dev);
428		DRM_DEV_ERROR(dev, "error registering backlight device (%d)\n",
429			      ret);
430	}
431
432	return ret;
433}
434
435static int s6e63m0_probe(struct spi_device *spi)
436{
437	struct device *dev = &spi->dev;
438	struct s6e63m0 *ctx;
439	int ret;
440
441	ctx = devm_kzalloc(dev, sizeof(struct s6e63m0), GFP_KERNEL);
442	if (!ctx)
443		return -ENOMEM;
444
445	spi_set_drvdata(spi, ctx);
446
447	ctx->dev = dev;
448	ctx->enabled = false;
449	ctx->prepared = false;
450
451	ctx->supplies[0].supply = "vdd3";
452	ctx->supplies[1].supply = "vci";
453	ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies),
454				      ctx->supplies);
455	if (ret < 0) {
456		DRM_DEV_ERROR(dev, "failed to get regulators: %d\n", ret);
457		return ret;
458	}
459
460	ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
461	if (IS_ERR(ctx->reset_gpio)) {
462		DRM_DEV_ERROR(dev, "cannot get reset-gpios %ld\n",
463			      PTR_ERR(ctx->reset_gpio));
464		return PTR_ERR(ctx->reset_gpio);
465	}
466
467	spi->bits_per_word = 9;
468	spi->mode = SPI_MODE_3;
469	ret = spi_setup(spi);
470	if (ret < 0) {
471		DRM_DEV_ERROR(dev, "spi setup failed.\n");
472		return ret;
473	}
474
475	drm_panel_init(&ctx->panel, dev, &s6e63m0_drm_funcs,
476		       DRM_MODE_CONNECTOR_DPI);
477
478	ret = s6e63m0_backlight_register(ctx);
479	if (ret < 0)
480		return ret;
481
482	return drm_panel_add(&ctx->panel);
483}
484
485static int s6e63m0_remove(struct spi_device *spi)
486{
487	struct s6e63m0 *ctx = spi_get_drvdata(spi);
488
489	drm_panel_remove(&ctx->panel);
490
491	return 0;
492}
493
494static const struct of_device_id s6e63m0_of_match[] = {
495	{ .compatible = "samsung,s6e63m0" },
496	{ /* sentinel */ }
497};
498MODULE_DEVICE_TABLE(of, s6e63m0_of_match);
499
500static struct spi_driver s6e63m0_driver = {
501	.probe			= s6e63m0_probe,
502	.remove			= s6e63m0_remove,
503	.driver			= {
504		.name		= "panel-samsung-s6e63m0",
505		.of_match_table = s6e63m0_of_match,
506	},
507};
508module_spi_driver(s6e63m0_driver);
509
510MODULE_AUTHOR("Paweł Chmiel <pawel.mikolaj.chmiel@gmail.com>");
511MODULE_DESCRIPTION("s6e63m0 LCD Driver");
512MODULE_LICENSE("GPL v2");