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