Linux Audio

Check our new training course

Linux kernel drivers training

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