Linux Audio

Check our new training course

In-person Linux kernel drivers training

Jun 16-20, 2025
Register
Loading...
Note: File does not exist in v3.1.
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * MIPI-DSI based S6E63J0X03 AMOLED lcd 1.63 inch panel driver.
  4 *
  5 * Copyright (c) 2014-2017 Samsung Electronics Co., Ltd
  6 *
  7 * Inki Dae <inki.dae@samsung.com>
  8 * Hoegeun Kwon <hoegeun.kwon@samsung.com>
  9 */
 10
 11#include <linux/backlight.h>
 12#include <linux/delay.h>
 13#include <linux/gpio/consumer.h>
 14#include <linux/module.h>
 15#include <linux/regulator/consumer.h>
 16
 17#include <video/mipi_display.h>
 18
 19#include <drm/drm_mipi_dsi.h>
 20#include <drm/drm_modes.h>
 21#include <drm/drm_panel.h>
 22#include <drm/drm_print.h>
 23
 24#define MCS_LEVEL2_KEY		0xf0
 25#define MCS_MTP_KEY		0xf1
 26#define MCS_MTP_SET3		0xd4
 27
 28#define MAX_BRIGHTNESS		100
 29#define DEFAULT_BRIGHTNESS	80
 30
 31#define NUM_GAMMA_STEPS		9
 32#define GAMMA_CMD_CNT		28
 33
 34#define FIRST_COLUMN 20
 35
 36struct s6e63j0x03 {
 37	struct device *dev;
 38	struct drm_panel panel;
 39	struct backlight_device *bl_dev;
 40
 41	struct regulator_bulk_data supplies[2];
 42	struct gpio_desc *reset_gpio;
 43};
 44
 45static const struct drm_display_mode default_mode = {
 46	.clock = 4649,
 47	.hdisplay = 320,
 48	.hsync_start = 320 + 1,
 49	.hsync_end = 320 + 1 + 1,
 50	.htotal = 320 + 1 + 1 + 1,
 51	.vdisplay = 320,
 52	.vsync_start = 320 + 150,
 53	.vsync_end = 320 + 150 + 1,
 54	.vtotal = 320 + 150 + 1 + 2,
 55	.flags = 0,
 56};
 57
 58static const unsigned char gamma_tbl[NUM_GAMMA_STEPS][GAMMA_CMD_CNT] = {
 59	{	/* Gamma 10 */
 60		MCS_MTP_SET3,
 61		0x00, 0x00, 0x00, 0x7f, 0x7f, 0x7f, 0x52, 0x6b, 0x6f, 0x26,
 62		0x28, 0x2d, 0x28, 0x26, 0x27, 0x33, 0x34, 0x32, 0x36, 0x36,
 63		0x35, 0x00, 0xab, 0x00, 0xae, 0x00, 0xbf
 64	},
 65	{	/* gamma 30 */
 66		MCS_MTP_SET3,
 67		0x00, 0x00, 0x00, 0x70, 0x7f, 0x7f, 0x4e, 0x64, 0x69, 0x26,
 68		0x27, 0x2a, 0x28, 0x29, 0x27, 0x31, 0x32, 0x31, 0x35, 0x34,
 69		0x35, 0x00, 0xc4, 0x00, 0xca, 0x00, 0xdc
 70	},
 71	{	/* gamma 60 */
 72		MCS_MTP_SET3,
 73		0x00, 0x00, 0x00, 0x65, 0x7b, 0x7d, 0x5f, 0x67, 0x68, 0x2a,
 74		0x28, 0x29, 0x28, 0x2a, 0x27, 0x31, 0x2f, 0x30, 0x34, 0x33,
 75		0x34, 0x00, 0xd9, 0x00, 0xe4, 0x00, 0xf5
 76	},
 77	{	/* gamma 90 */
 78		MCS_MTP_SET3,
 79		0x00, 0x00, 0x00, 0x4d, 0x6f, 0x71, 0x67, 0x6a, 0x6c, 0x29,
 80		0x28, 0x28, 0x28, 0x29, 0x27, 0x30, 0x2e, 0x30, 0x32, 0x31,
 81		0x31, 0x00, 0xea, 0x00, 0xf6, 0x01, 0x09
 82	},
 83	{	/* gamma 120 */
 84		MCS_MTP_SET3,
 85		0x00, 0x00, 0x00, 0x3d, 0x66, 0x68, 0x69, 0x69, 0x69, 0x28,
 86		0x28, 0x27, 0x28, 0x28, 0x27, 0x30, 0x2e, 0x2f, 0x31, 0x31,
 87		0x30, 0x00, 0xf9, 0x01, 0x05, 0x01, 0x1b
 88	},
 89	{	/* gamma 150 */
 90		MCS_MTP_SET3,
 91		0x00, 0x00, 0x00, 0x31, 0x51, 0x53, 0x66, 0x66, 0x67, 0x28,
 92		0x29, 0x27, 0x28, 0x27, 0x27, 0x2e, 0x2d, 0x2e, 0x31, 0x31,
 93		0x30, 0x01, 0x04, 0x01, 0x11, 0x01, 0x29
 94	},
 95	{	/* gamma 200 */
 96		MCS_MTP_SET3,
 97		0x00, 0x00, 0x00, 0x2f, 0x4f, 0x51, 0x67, 0x65, 0x65, 0x29,
 98		0x2a, 0x28, 0x27, 0x25, 0x26, 0x2d, 0x2c, 0x2c, 0x30, 0x30,
 99		0x30, 0x01, 0x14, 0x01, 0x23, 0x01, 0x3b
100	},
101	{	/* gamma 240 */
102		MCS_MTP_SET3,
103		0x00, 0x00, 0x00, 0x2c, 0x4d, 0x50, 0x65, 0x63, 0x64, 0x2a,
104		0x2c, 0x29, 0x26, 0x24, 0x25, 0x2c, 0x2b, 0x2b, 0x30, 0x30,
105		0x30, 0x01, 0x1e, 0x01, 0x2f, 0x01, 0x47
106	},
107	{	/* gamma 300 */
108		MCS_MTP_SET3,
109		0x00, 0x00, 0x00, 0x38, 0x61, 0x64, 0x65, 0x63, 0x64, 0x28,
110		0x2a, 0x27, 0x26, 0x23, 0x25, 0x2b, 0x2b, 0x2a, 0x30, 0x2f,
111		0x30, 0x01, 0x2d, 0x01, 0x3f, 0x01, 0x57
112	}
113};
114
115static inline struct s6e63j0x03 *panel_to_s6e63j0x03(struct drm_panel *panel)
116{
117	return container_of(panel, struct s6e63j0x03, panel);
118}
119
120static inline ssize_t s6e63j0x03_dcs_write_seq(struct s6e63j0x03 *ctx,
121					const void *seq, size_t len)
122{
123	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
124
125	return mipi_dsi_dcs_write_buffer(dsi, seq, len);
126}
127
128#define s6e63j0x03_dcs_write_seq_static(ctx, seq...)			\
129	({								\
130		static const u8 d[] = { seq };				\
131		s6e63j0x03_dcs_write_seq(ctx, d, ARRAY_SIZE(d));	\
132	})
133
134static inline int s6e63j0x03_enable_lv2_command(struct s6e63j0x03 *ctx)
135{
136	return s6e63j0x03_dcs_write_seq_static(ctx, MCS_LEVEL2_KEY, 0x5a, 0x5a);
137}
138
139static inline int s6e63j0x03_apply_mtp_key(struct s6e63j0x03 *ctx, bool on)
140{
141	if (on)
142		return s6e63j0x03_dcs_write_seq_static(ctx,
143				MCS_MTP_KEY, 0x5a, 0x5a);
144
145	return s6e63j0x03_dcs_write_seq_static(ctx, MCS_MTP_KEY, 0xa5, 0xa5);
146}
147
148static int s6e63j0x03_power_on(struct s6e63j0x03 *ctx)
149{
150	int ret;
151
152	ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
153	if (ret < 0)
154		return ret;
155
156	msleep(30);
157
158	gpiod_set_value(ctx->reset_gpio, 1);
159	usleep_range(1000, 2000);
160	gpiod_set_value(ctx->reset_gpio, 0);
161	usleep_range(5000, 6000);
162
163	return 0;
164}
165
166static int s6e63j0x03_power_off(struct s6e63j0x03 *ctx)
167{
168	return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
169}
170
171static unsigned int s6e63j0x03_get_brightness_index(unsigned int brightness)
172{
173	unsigned int index;
174
175	index = brightness / (MAX_BRIGHTNESS / NUM_GAMMA_STEPS);
176
177	if (index >= NUM_GAMMA_STEPS)
178		index = NUM_GAMMA_STEPS - 1;
179
180	return index;
181}
182
183static int s6e63j0x03_update_gamma(struct s6e63j0x03 *ctx,
184					unsigned int brightness)
185{
186	struct backlight_device *bl_dev = ctx->bl_dev;
187	unsigned int index = s6e63j0x03_get_brightness_index(brightness);
188	int ret;
189
190	ret = s6e63j0x03_apply_mtp_key(ctx, true);
191	if (ret < 0)
192		return ret;
193
194	ret = s6e63j0x03_dcs_write_seq(ctx, gamma_tbl[index], GAMMA_CMD_CNT);
195	if (ret < 0)
196		return ret;
197
198	ret = s6e63j0x03_apply_mtp_key(ctx, false);
199	if (ret < 0)
200		return ret;
201
202	bl_dev->props.brightness = brightness;
203
204	return 0;
205}
206
207static int s6e63j0x03_set_brightness(struct backlight_device *bl_dev)
208{
209	struct s6e63j0x03 *ctx = bl_get_data(bl_dev);
210	unsigned int brightness = bl_dev->props.brightness;
211
212	return s6e63j0x03_update_gamma(ctx, brightness);
213}
214
215static const struct backlight_ops s6e63j0x03_bl_ops = {
216	.update_status = s6e63j0x03_set_brightness,
217};
218
219static int s6e63j0x03_disable(struct drm_panel *panel)
220{
221	struct s6e63j0x03 *ctx = panel_to_s6e63j0x03(panel);
222	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
223	int ret;
224
225	ret = mipi_dsi_dcs_set_display_off(dsi);
226	if (ret < 0)
227		return ret;
228
229	ctx->bl_dev->props.power = FB_BLANK_NORMAL;
230
231	ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
232	if (ret < 0)
233		return ret;
234
235	msleep(120);
236
237	return 0;
238}
239
240static int s6e63j0x03_unprepare(struct drm_panel *panel)
241{
242	struct s6e63j0x03 *ctx = panel_to_s6e63j0x03(panel);
243	int ret;
244
245	ret = s6e63j0x03_power_off(ctx);
246	if (ret < 0)
247		return ret;
248
249	ctx->bl_dev->props.power = FB_BLANK_POWERDOWN;
250
251	return 0;
252}
253
254static int s6e63j0x03_panel_init(struct s6e63j0x03 *ctx)
255{
256	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
257	int ret;
258
259	ret = s6e63j0x03_enable_lv2_command(ctx);
260	if (ret < 0)
261		return ret;
262
263	ret = s6e63j0x03_apply_mtp_key(ctx, true);
264	if (ret < 0)
265		return ret;
266
267	/* set porch adjustment */
268	ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xf2, 0x1c, 0x28);
269	if (ret < 0)
270		return ret;
271
272	/* set frame freq */
273	ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xb5, 0x00, 0x02, 0x00);
274	if (ret < 0)
275		return ret;
276
277	/* set caset, paset */
278	ret = mipi_dsi_dcs_set_column_address(dsi, FIRST_COLUMN,
279		default_mode.hdisplay - 1 + FIRST_COLUMN);
280	if (ret < 0)
281		return ret;
282
283	ret = mipi_dsi_dcs_set_page_address(dsi, 0, default_mode.vdisplay - 1);
284	if (ret < 0)
285		return ret;
286
287	/* set ltps timming 0, 1 */
288	ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xf8, 0x08, 0x08, 0x08, 0x17,
289		0x00, 0x2a, 0x02, 0x26, 0x00, 0x00, 0x02, 0x00, 0x00);
290	if (ret < 0)
291		return ret;
292
293	ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xf7, 0x02);
294	if (ret < 0)
295		return ret;
296
297	/* set param pos te_edge */
298	ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xb0, 0x01);
299	if (ret < 0)
300		return ret;
301
302	/* set te rising edge */
303	ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xe2, 0x0f);
304	if (ret < 0)
305		return ret;
306
307	/* set param pos default */
308	ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xb0, 0x00);
309	if (ret < 0)
310		return ret;
311
312	ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
313	if (ret < 0)
314		return ret;
315
316	ret = s6e63j0x03_apply_mtp_key(ctx, false);
317	if (ret < 0)
318		return ret;
319
320	return 0;
321}
322
323static int s6e63j0x03_prepare(struct drm_panel *panel)
324{
325	struct s6e63j0x03 *ctx = panel_to_s6e63j0x03(panel);
326	int ret;
327
328	ret = s6e63j0x03_power_on(ctx);
329	if (ret < 0)
330		return ret;
331
332	ret = s6e63j0x03_panel_init(ctx);
333	if (ret < 0)
334		goto err;
335
336	ctx->bl_dev->props.power = FB_BLANK_NORMAL;
337
338	return 0;
339
340err:
341	s6e63j0x03_power_off(ctx);
342	return ret;
343}
344
345static int s6e63j0x03_enable(struct drm_panel *panel)
346{
347	struct s6e63j0x03 *ctx = panel_to_s6e63j0x03(panel);
348	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
349	int ret;
350
351	msleep(120);
352
353	ret = s6e63j0x03_apply_mtp_key(ctx, true);
354	if (ret < 0)
355		return ret;
356
357	/* set elvss_cond */
358	ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xb1, 0x00, 0x09);
359	if (ret < 0)
360		return ret;
361
362	/* set pos */
363	ret = s6e63j0x03_dcs_write_seq_static(ctx,
364		MIPI_DCS_SET_ADDRESS_MODE, 0x40);
365	if (ret < 0)
366		return ret;
367
368	/* set default white brightness */
369	ret = mipi_dsi_dcs_set_display_brightness(dsi, 0x00ff);
370	if (ret < 0)
371		return ret;
372
373	/* set white ctrl */
374	ret = s6e63j0x03_dcs_write_seq_static(ctx,
375		MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x20);
376	if (ret < 0)
377		return ret;
378
379	/* set acl off */
380	ret = s6e63j0x03_dcs_write_seq_static(ctx,
381		MIPI_DCS_WRITE_POWER_SAVE, 0x00);
382	if (ret < 0)
383		return ret;
384
385	ret = mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
386	if (ret < 0)
387		return ret;
388
389	ret = s6e63j0x03_apply_mtp_key(ctx, false);
390	if (ret < 0)
391		return ret;
392
393	ret = mipi_dsi_dcs_set_display_on(dsi);
394	if (ret < 0)
395		return ret;
396
397	ctx->bl_dev->props.power = FB_BLANK_UNBLANK;
398
399	return 0;
400}
401
402static int s6e63j0x03_get_modes(struct drm_panel *panel,
403				struct drm_connector *connector)
404{
405	struct drm_display_mode *mode;
406
407	mode = drm_mode_duplicate(connector->dev, &default_mode);
408	if (!mode) {
409		DRM_ERROR("failed to add mode %ux%ux@%u\n",
410			default_mode.hdisplay, default_mode.vdisplay,
411			drm_mode_vrefresh(&default_mode));
412		return -ENOMEM;
413	}
414
415	drm_mode_set_name(mode);
416
417	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
418	drm_mode_probed_add(connector, mode);
419
420	connector->display_info.width_mm = 29;
421	connector->display_info.height_mm = 29;
422
423	return 1;
424}
425
426static const struct drm_panel_funcs s6e63j0x03_funcs = {
427	.disable = s6e63j0x03_disable,
428	.unprepare = s6e63j0x03_unprepare,
429	.prepare = s6e63j0x03_prepare,
430	.enable = s6e63j0x03_enable,
431	.get_modes = s6e63j0x03_get_modes,
432};
433
434static int s6e63j0x03_probe(struct mipi_dsi_device *dsi)
435{
436	struct device *dev = &dsi->dev;
437	struct s6e63j0x03 *ctx;
438	int ret;
439
440	ctx = devm_kzalloc(dev, sizeof(struct s6e63j0x03), GFP_KERNEL);
441	if (!ctx)
442		return -ENOMEM;
443
444	mipi_dsi_set_drvdata(dsi, ctx);
445
446	ctx->dev = dev;
447
448	dsi->lanes = 1;
449	dsi->format = MIPI_DSI_FMT_RGB888;
450	dsi->mode_flags = MIPI_DSI_MODE_EOT_PACKET;
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		dev_err(dev, "failed to get regulators: %d\n", ret);
458		return ret;
459	}
460
461	ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
462	if (IS_ERR(ctx->reset_gpio)) {
463		dev_err(dev, "cannot get reset-gpio: %ld\n",
464				PTR_ERR(ctx->reset_gpio));
465		return PTR_ERR(ctx->reset_gpio);
466	}
467
468	drm_panel_init(&ctx->panel, dev, &s6e63j0x03_funcs,
469		       DRM_MODE_CONNECTOR_DSI);
470
471	ctx->bl_dev = backlight_device_register("s6e63j0x03", dev, ctx,
472						&s6e63j0x03_bl_ops, NULL);
473	if (IS_ERR(ctx->bl_dev)) {
474		dev_err(dev, "failed to register backlight device\n");
475		return PTR_ERR(ctx->bl_dev);
476	}
477
478	ctx->bl_dev->props.max_brightness = MAX_BRIGHTNESS;
479	ctx->bl_dev->props.brightness = DEFAULT_BRIGHTNESS;
480	ctx->bl_dev->props.power = FB_BLANK_POWERDOWN;
481
482	ret = drm_panel_add(&ctx->panel);
483	if (ret < 0)
484		goto unregister_backlight;
485
486	ret = mipi_dsi_attach(dsi);
487	if (ret < 0)
488		goto remove_panel;
489
490	return ret;
491
492remove_panel:
493	drm_panel_remove(&ctx->panel);
494
495unregister_backlight:
496	backlight_device_unregister(ctx->bl_dev);
497
498	return ret;
499}
500
501static int s6e63j0x03_remove(struct mipi_dsi_device *dsi)
502{
503	struct s6e63j0x03 *ctx = mipi_dsi_get_drvdata(dsi);
504
505	mipi_dsi_detach(dsi);
506	drm_panel_remove(&ctx->panel);
507
508	backlight_device_unregister(ctx->bl_dev);
509
510	return 0;
511}
512
513static const struct of_device_id s6e63j0x03_of_match[] = {
514	{ .compatible = "samsung,s6e63j0x03" },
515	{ }
516};
517MODULE_DEVICE_TABLE(of, s6e63j0x03_of_match);
518
519static struct mipi_dsi_driver s6e63j0x03_driver = {
520	.probe = s6e63j0x03_probe,
521	.remove = s6e63j0x03_remove,
522	.driver = {
523		.name = "panel_samsung_s6e63j0x03",
524		.of_match_table = s6e63j0x03_of_match,
525	},
526};
527module_mipi_dsi_driver(s6e63j0x03_driver);
528
529MODULE_AUTHOR("Inki Dae <inki.dae@samsung.com>");
530MODULE_AUTHOR("Hoegeun Kwon <hoegeun.kwon@samsung.com>");
531MODULE_DESCRIPTION("MIPI-DSI based s6e63j0x03 AMOLED LCD Panel Driver");
532MODULE_LICENSE("GPL v2");