Linux Audio

Check our new training course

Loading...
Note: File does not exist in v4.17.
  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	.vrefresh	= 60,
121	.width_mm	= 53,
122	.height_mm	= 89,
123	.flags		= DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC,
124};
125
126static inline struct s6e63m0 *panel_to_s6e63m0(struct drm_panel *panel)
127{
128	return container_of(panel, struct s6e63m0, panel);
129}
130
131static int s6e63m0_clear_error(struct s6e63m0 *ctx)
132{
133	int ret = ctx->error;
134
135	ctx->error = 0;
136	return ret;
137}
138
139static int s6e63m0_spi_write_word(struct s6e63m0 *ctx, u16 data)
140{
141	struct spi_device *spi = to_spi_device(ctx->dev);
142	struct spi_transfer xfer = {
143		.len	= 2,
144		.tx_buf = &data,
145	};
146	struct spi_message msg;
147
148	spi_message_init(&msg);
149	spi_message_add_tail(&xfer, &msg);
150
151	return spi_sync(spi, &msg);
152}
153
154static void s6e63m0_dcs_write(struct s6e63m0 *ctx, const u8 *data, size_t len)
155{
156	int ret = 0;
157
158	if (ctx->error < 0 || len == 0)
159		return;
160
161	DRM_DEV_DEBUG(ctx->dev, "writing dcs seq: %*ph\n", (int)len, data);
162	ret = s6e63m0_spi_write_word(ctx, *data);
163
164	while (!ret && --len) {
165		++data;
166		ret = s6e63m0_spi_write_word(ctx, *data | DATA_MASK);
167	}
168
169	if (ret) {
170		DRM_DEV_ERROR(ctx->dev, "error %d writing dcs seq: %*ph\n", ret,
171			      (int)len, data);
172		ctx->error = ret;
173	}
174
175	usleep_range(300, 310);
176}
177
178#define s6e63m0_dcs_write_seq_static(ctx, seq ...) \
179	({ \
180		static const u8 d[] = { seq }; \
181		s6e63m0_dcs_write(ctx, d, ARRAY_SIZE(d)); \
182	})
183
184static void s6e63m0_init(struct s6e63m0 *ctx)
185{
186	s6e63m0_dcs_write_seq_static(ctx, MCS_PANELCTL,
187				     0x01, 0x27, 0x27, 0x07, 0x07, 0x54, 0x9f,
188				     0x63, 0x86, 0x1a, 0x33, 0x0d, 0x00, 0x00);
189
190	s6e63m0_dcs_write_seq_static(ctx, MCS_DISCTL,
191				     0x02, 0x03, 0x1c, 0x10, 0x10);
192	s6e63m0_dcs_write_seq_static(ctx, MCS_IFCTL,
193				     0x03, 0x00, 0x00);
194
195	s6e63m0_dcs_write_seq_static(ctx, MCS_PGAMMACTL,
196				     0x00, 0x18, 0x08, 0x24, 0x64, 0x56, 0x33,
197				     0xb6, 0xba, 0xa8, 0xac, 0xb1, 0x9d, 0xc1,
198				     0xc1, 0xb7, 0x00, 0x9c, 0x00, 0x9f, 0x00,
199				     0xd6);
200	s6e63m0_dcs_write_seq_static(ctx, MCS_PGAMMACTL,
201				     0x01);
202
203	s6e63m0_dcs_write_seq_static(ctx, MCS_SRCCTL,
204				     0x00, 0x8c, 0x07);
205	s6e63m0_dcs_write_seq_static(ctx, 0xb3,
206				     0xc);
207
208	s6e63m0_dcs_write_seq_static(ctx, 0xb5,
209				     0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17,
210				     0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b,
211				     0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a,
212				     0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23,
213				     0x21, 0x20, 0x1e, 0x1e);
214
215	s6e63m0_dcs_write_seq_static(ctx, 0xb6,
216				     0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x44,
217				     0x44, 0x55, 0x55, 0x66, 0x66, 0x66, 0x66,
218				     0x66, 0x66);
219
220	s6e63m0_dcs_write_seq_static(ctx, 0xb7,
221				     0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17,
222				     0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b,
223				     0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a,
224				     0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23,
225				     0x21, 0x20, 0x1e, 0x1e, 0x00, 0x00, 0x11,
226				     0x22, 0x33, 0x44, 0x44, 0x44, 0x55, 0x55,
227				     0x66, 0x66, 0x66, 0x66, 0x66, 0x66);
228
229	s6e63m0_dcs_write_seq_static(ctx, 0xb9,
230				     0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17,
231				     0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b,
232				     0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a,
233				     0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23,
234				     0x21, 0x20, 0x1e, 0x1e);
235
236	s6e63m0_dcs_write_seq_static(ctx, 0xba,
237				     0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x44,
238				     0x44, 0x55, 0x55, 0x66, 0x66, 0x66, 0x66,
239				     0x66, 0x66);
240
241	s6e63m0_dcs_write_seq_static(ctx, MCS_BCMODE,
242				     0x4d, 0x96, 0x1d, 0x00, 0x00, 0x01, 0xdf,
243				     0x00, 0x00, 0x03, 0x1f, 0x00, 0x00, 0x00,
244				     0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x06,
245				     0x09, 0x0d, 0x0f, 0x12, 0x15, 0x18);
246
247	s6e63m0_dcs_write_seq_static(ctx, 0xb2,
248				     0x10, 0x10, 0x0b, 0x05);
249
250	s6e63m0_dcs_write_seq_static(ctx, MCS_MIECTL1,
251				     0x01);
252
253	s6e63m0_dcs_write_seq_static(ctx, MCS_ELVSS_ON,
254				     0x0b);
255
256	s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_EXIT_SLEEP_MODE);
257}
258
259static int s6e63m0_power_on(struct s6e63m0 *ctx)
260{
261	int ret;
262
263	ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
264	if (ret < 0)
265		return ret;
266
267	msleep(25);
268
269	gpiod_set_value(ctx->reset_gpio, 0);
270	msleep(120);
271
272	return 0;
273}
274
275static int s6e63m0_power_off(struct s6e63m0 *ctx)
276{
277	int ret;
278
279	gpiod_set_value(ctx->reset_gpio, 1);
280	msleep(120);
281
282	ret = regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
283	if (ret < 0)
284		return ret;
285
286	return 0;
287}
288
289static int s6e63m0_disable(struct drm_panel *panel)
290{
291	struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
292
293	if (!ctx->enabled)
294		return 0;
295
296	backlight_disable(ctx->bl_dev);
297
298	s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_ENTER_SLEEP_MODE);
299	msleep(200);
300
301	ctx->enabled = false;
302
303	return 0;
304}
305
306static int s6e63m0_unprepare(struct drm_panel *panel)
307{
308	struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
309	int ret;
310
311	if (!ctx->prepared)
312		return 0;
313
314	s6e63m0_clear_error(ctx);
315
316	ret = s6e63m0_power_off(ctx);
317	if (ret < 0)
318		return ret;
319
320	ctx->prepared = false;
321
322	return 0;
323}
324
325static int s6e63m0_prepare(struct drm_panel *panel)
326{
327	struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
328	int ret;
329
330	if (ctx->prepared)
331		return 0;
332
333	ret = s6e63m0_power_on(ctx);
334	if (ret < 0)
335		return ret;
336
337	s6e63m0_init(ctx);
338
339	ret = s6e63m0_clear_error(ctx);
340
341	if (ret < 0)
342		s6e63m0_unprepare(panel);
343
344	ctx->prepared = true;
345
346	return ret;
347}
348
349static int s6e63m0_enable(struct drm_panel *panel)
350{
351	struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
352
353	if (ctx->enabled)
354		return 0;
355
356	s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_ON);
357
358	backlight_enable(ctx->bl_dev);
359
360	ctx->enabled = true;
361
362	return 0;
363}
364
365static int s6e63m0_get_modes(struct drm_panel *panel)
366{
367	struct drm_connector *connector = panel->connector;
368	struct drm_display_mode *mode;
369
370	mode = drm_mode_duplicate(panel->drm, &default_mode);
371	if (!mode) {
372		DRM_ERROR("failed to add mode %ux%ux@%u\n",
373			  default_mode.hdisplay, default_mode.vdisplay,
374			  default_mode.vrefresh);
375		return -ENOMEM;
376	}
377
378	drm_mode_set_name(mode);
379
380	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
381	drm_mode_probed_add(connector, mode);
382
383	return 1;
384}
385
386static const struct drm_panel_funcs s6e63m0_drm_funcs = {
387	.disable	= s6e63m0_disable,
388	.unprepare	= s6e63m0_unprepare,
389	.prepare	= s6e63m0_prepare,
390	.enable		= s6e63m0_enable,
391	.get_modes	= s6e63m0_get_modes,
392};
393
394static int s6e63m0_set_brightness(struct backlight_device *bd)
395{
396	struct s6e63m0 *ctx = bl_get_data(bd);
397
398	int brightness = bd->props.brightness;
399
400	/* disable and set new gamma */
401	s6e63m0_dcs_write(ctx, s6e63m0_gamma_22[brightness],
402			  ARRAY_SIZE(s6e63m0_gamma_22[brightness]));
403
404	/* update gamma table. */
405	s6e63m0_dcs_write_seq_static(ctx, MCS_PGAMMACTL, 0x01);
406
407	return s6e63m0_clear_error(ctx);
408}
409
410static const struct backlight_ops s6e63m0_backlight_ops = {
411	.update_status	= s6e63m0_set_brightness,
412};
413
414static int s6e63m0_backlight_register(struct s6e63m0 *ctx)
415{
416	struct backlight_properties props = {
417		.type		= BACKLIGHT_RAW,
418		.brightness	= MAX_BRIGHTNESS,
419		.max_brightness = MAX_BRIGHTNESS
420	};
421	struct device *dev = ctx->dev;
422	int ret = 0;
423
424	ctx->bl_dev = devm_backlight_device_register(dev, "panel", dev, ctx,
425						     &s6e63m0_backlight_ops,
426						     &props);
427	if (IS_ERR(ctx->bl_dev)) {
428		ret = PTR_ERR(ctx->bl_dev);
429		DRM_DEV_ERROR(dev, "error registering backlight device (%d)\n",
430			      ret);
431	}
432
433	return ret;
434}
435
436static int s6e63m0_probe(struct spi_device *spi)
437{
438	struct device *dev = &spi->dev;
439	struct s6e63m0 *ctx;
440	int ret;
441
442	ctx = devm_kzalloc(dev, sizeof(struct s6e63m0), GFP_KERNEL);
443	if (!ctx)
444		return -ENOMEM;
445
446	spi_set_drvdata(spi, ctx);
447
448	ctx->dev = dev;
449	ctx->enabled = false;
450	ctx->prepared = false;
451
452	ctx->supplies[0].supply = "vdd3";
453	ctx->supplies[1].supply = "vci";
454	ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies),
455				      ctx->supplies);
456	if (ret < 0) {
457		DRM_DEV_ERROR(dev, "failed to get regulators: %d\n", ret);
458		return ret;
459	}
460
461	ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
462	if (IS_ERR(ctx->reset_gpio)) {
463		DRM_DEV_ERROR(dev, "cannot get reset-gpios %ld\n",
464			      PTR_ERR(ctx->reset_gpio));
465		return PTR_ERR(ctx->reset_gpio);
466	}
467
468	spi->bits_per_word = 9;
469	spi->mode = SPI_MODE_3;
470	ret = spi_setup(spi);
471	if (ret < 0) {
472		DRM_DEV_ERROR(dev, "spi setup failed.\n");
473		return ret;
474	}
475
476	drm_panel_init(&ctx->panel);
477	ctx->panel.dev = dev;
478	ctx->panel.funcs = &s6e63m0_drm_funcs;
479
480	ret = s6e63m0_backlight_register(ctx);
481	if (ret < 0)
482		return ret;
483
484	return drm_panel_add(&ctx->panel);
485}
486
487static int s6e63m0_remove(struct spi_device *spi)
488{
489	struct s6e63m0 *ctx = spi_get_drvdata(spi);
490
491	drm_panel_remove(&ctx->panel);
492
493	return 0;
494}
495
496static const struct of_device_id s6e63m0_of_match[] = {
497	{ .compatible = "samsung,s6e63m0" },
498	{ /* sentinel */ }
499};
500MODULE_DEVICE_TABLE(of, s6e63m0_of_match);
501
502static struct spi_driver s6e63m0_driver = {
503	.probe			= s6e63m0_probe,
504	.remove			= s6e63m0_remove,
505	.driver			= {
506		.name		= "panel-samsung-s6e63m0",
507		.of_match_table = s6e63m0_of_match,
508	},
509};
510module_spi_driver(s6e63m0_driver);
511
512MODULE_AUTHOR("Paweł Chmiel <pawel.mikolaj.chmiel@gmail.com>");
513MODULE_DESCRIPTION("s6e63m0 LCD Driver");
514MODULE_LICENSE("GPL v2");