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
14#include <linux/backlight.h>
15#include <linux/delay.h>
16#include <linux/gpio/consumer.h>
17#include <linux/module.h>
18#include <linux/regulator/consumer.h>
19#include <linux/media-bus-format.h>
20
21#include <video/mipi_display.h>
22
23#include "panel-samsung-s6e63m0.h"
24
25#define S6E63M0_LCD_ID_VALUE_M2 0xA4
26#define S6E63M0_LCD_ID_VALUE_SM2 0xB4
27#define S6E63M0_LCD_ID_VALUE_SM2_1 0xB6
28
29#define NUM_GAMMA_LEVELS 28
30#define GAMMA_TABLE_COUNT 23
31
32#define MAX_BRIGHTNESS (NUM_GAMMA_LEVELS - 1)
33
34/* array of gamma tables for gamma value 2.2 */
35static u8 const s6e63m0_gamma_22[NUM_GAMMA_LEVELS][GAMMA_TABLE_COUNT] = {
36 /* 30 cd */
37 { MCS_PGAMMACTL, 0x02,
38 0x18, 0x08, 0x24, 0xA1, 0x51, 0x7B, 0xCE,
39 0xCB, 0xC2, 0xC7, 0xCB, 0xBC, 0xDA, 0xDD,
40 0xD3, 0x00, 0x53, 0x00, 0x52, 0x00, 0x6F, },
41 /* 40 cd */
42 { MCS_PGAMMACTL, 0x02,
43 0x18, 0x08, 0x24, 0x97, 0x58, 0x71, 0xCC,
44 0xCB, 0xC0, 0xC5, 0xC9, 0xBA, 0xD9, 0xDC,
45 0xD1, 0x00, 0x5B, 0x00, 0x5A, 0x00, 0x7A, },
46 /* 50 cd */
47 { MCS_PGAMMACTL, 0x02,
48 0x18, 0x08, 0x24, 0x96, 0x58, 0x72, 0xCB,
49 0xCA, 0xBF, 0xC6, 0xC9, 0xBA, 0xD6, 0xD9,
50 0xCD, 0x00, 0x61, 0x00, 0x61, 0x00, 0x83, },
51 /* 60 cd */
52 { MCS_PGAMMACTL, 0x02,
53 0x18, 0x08, 0x24, 0x91, 0x5E, 0x6E, 0xC9,
54 0xC9, 0xBD, 0xC4, 0xC9, 0xB8, 0xD3, 0xD7,
55 0xCA, 0x00, 0x69, 0x00, 0x67, 0x00, 0x8D, },
56 /* 70 cd */
57 { MCS_PGAMMACTL, 0x02,
58 0x18, 0x08, 0x24, 0x8E, 0x62, 0x6B, 0xC7,
59 0xC9, 0xBB, 0xC3, 0xC7, 0xB7, 0xD3, 0xD7,
60 0xCA, 0x00, 0x6E, 0x00, 0x6C, 0x00, 0x94, },
61 /* 80 cd */
62 { MCS_PGAMMACTL, 0x02,
63 0x18, 0x08, 0x24, 0x89, 0x68, 0x65, 0xC9,
64 0xC9, 0xBC, 0xC1, 0xC5, 0xB6, 0xD2, 0xD5,
65 0xC9, 0x00, 0x73, 0x00, 0x72, 0x00, 0x9A, },
66 /* 90 cd */
67 { MCS_PGAMMACTL, 0x02,
68 0x18, 0x08, 0x24, 0x89, 0x69, 0x64, 0xC7,
69 0xC8, 0xBB, 0xC0, 0xC5, 0xB4, 0xD2, 0xD5,
70 0xC9, 0x00, 0x77, 0x00, 0x76, 0x00, 0xA0, },
71 /* 100 cd */
72 { MCS_PGAMMACTL, 0x02,
73 0x18, 0x08, 0x24, 0x86, 0x69, 0x60, 0xC6,
74 0xC8, 0xBA, 0xBF, 0xC4, 0xB4, 0xD0, 0xD4,
75 0xC6, 0x00, 0x7C, 0x00, 0x7A, 0x00, 0xA7, },
76 /* 110 cd */
77 { MCS_PGAMMACTL, 0x02,
78 0x18, 0x08, 0x24, 0x86, 0x6A, 0x60, 0xC5,
79 0xC7, 0xBA, 0xBD, 0xC3, 0xB2, 0xD0, 0xD4,
80 0xC5, 0x00, 0x80, 0x00, 0x7E, 0x00, 0xAD, },
81 /* 120 cd */
82 { MCS_PGAMMACTL, 0x02,
83 0x18, 0x08, 0x24, 0x82, 0x6B, 0x5E, 0xC4,
84 0xC8, 0xB9, 0xBD, 0xC2, 0xB1, 0xCE, 0xD2,
85 0xC4, 0x00, 0x85, 0x00, 0x82, 0x00, 0xB3, },
86 /* 130 cd */
87 { MCS_PGAMMACTL, 0x02,
88 0x18, 0x08, 0x24, 0x8C, 0x6C, 0x60, 0xC3,
89 0xC7, 0xB9, 0xBC, 0xC1, 0xAF, 0xCE, 0xD2,
90 0xC3, 0x00, 0x88, 0x00, 0x86, 0x00, 0xB8, },
91 /* 140 cd */
92 { MCS_PGAMMACTL, 0x02,
93 0x18, 0x08, 0x24, 0x80, 0x6C, 0x5F, 0xC1,
94 0xC6, 0xB7, 0xBC, 0xC1, 0xAE, 0xCD, 0xD0,
95 0xC2, 0x00, 0x8C, 0x00, 0x8A, 0x00, 0xBE, },
96 /* 150 cd */
97 { MCS_PGAMMACTL, 0x02,
98 0x18, 0x08, 0x24, 0x80, 0x6E, 0x5F, 0xC1,
99 0xC6, 0xB6, 0xBC, 0xC0, 0xAE, 0xCC, 0xD0,
100 0xC2, 0x00, 0x8F, 0x00, 0x8D, 0x00, 0xC2, },
101 /* 160 cd */
102 { MCS_PGAMMACTL, 0x02,
103 0x18, 0x08, 0x24, 0x7F, 0x6E, 0x5F, 0xC0,
104 0xC6, 0xB5, 0xBA, 0xBF, 0xAD, 0xCB, 0xCF,
105 0xC0, 0x00, 0x94, 0x00, 0x91, 0x00, 0xC8, },
106 /* 170 cd */
107 { MCS_PGAMMACTL, 0x02,
108 0x18, 0x08, 0x24, 0x7C, 0x6D, 0x5C, 0xC0,
109 0xC6, 0xB4, 0xBB, 0xBE, 0xAD, 0xCA, 0xCF,
110 0xC0, 0x00, 0x96, 0x00, 0x94, 0x00, 0xCC, },
111 /* 180 cd */
112 { MCS_PGAMMACTL, 0x02,
113 0x18, 0x08, 0x24, 0x7B, 0x6D, 0x5B, 0xC0,
114 0xC5, 0xB3, 0xBA, 0xBE, 0xAD, 0xCA, 0xCE,
115 0xBF, 0x00, 0x99, 0x00, 0x97, 0x00, 0xD0, },
116 /* 190 cd */
117 { MCS_PGAMMACTL, 0x02,
118 0x18, 0x08, 0x24, 0x7A, 0x6D, 0x59, 0xC1,
119 0xC5, 0xB4, 0xB8, 0xBD, 0xAC, 0xC9, 0xCE,
120 0xBE, 0x00, 0x9D, 0x00, 0x9A, 0x00, 0xD5, },
121 /* 200 cd */
122 { MCS_PGAMMACTL, 0x02,
123 0x18, 0x08, 0x24, 0x79, 0x6D, 0x58, 0xC1,
124 0xC4, 0xB4, 0xB6, 0xBD, 0xAA, 0xCA, 0xCD,
125 0xBE, 0x00, 0x9F, 0x00, 0x9D, 0x00, 0xD9, },
126 /* 210 cd */
127 { MCS_PGAMMACTL, 0x02,
128 0x18, 0x08, 0x24, 0x79, 0x6D, 0x57, 0xC0,
129 0xC4, 0xB4, 0xB7, 0xBD, 0xAA, 0xC8, 0xCC,
130 0xBD, 0x00, 0xA2, 0x00, 0xA0, 0x00, 0xDD, },
131 /* 220 cd */
132 { MCS_PGAMMACTL, 0x02,
133 0x18, 0x08, 0x24, 0x78, 0x6F, 0x58, 0xBF,
134 0xC4, 0xB3, 0xB5, 0xBB, 0xA9, 0xC8, 0xCC,
135 0xBC, 0x00, 0xA6, 0x00, 0xA3, 0x00, 0xE2, },
136 /* 230 cd */
137 { MCS_PGAMMACTL, 0x02,
138 0x18, 0x08, 0x24, 0x75, 0x6F, 0x56, 0xBF,
139 0xC3, 0xB2, 0xB6, 0xBB, 0xA8, 0xC7, 0xCB,
140 0xBC, 0x00, 0xA8, 0x00, 0xA6, 0x00, 0xE6, },
141 /* 240 cd */
142 { MCS_PGAMMACTL, 0x02,
143 0x18, 0x08, 0x24, 0x76, 0x6F, 0x56, 0xC0,
144 0xC3, 0xB2, 0xB5, 0xBA, 0xA8, 0xC6, 0xCB,
145 0xBB, 0x00, 0xAA, 0x00, 0xA8, 0x00, 0xE9, },
146 /* 250 cd */
147 { MCS_PGAMMACTL, 0x02,
148 0x18, 0x08, 0x24, 0x74, 0x6D, 0x54, 0xBF,
149 0xC3, 0xB2, 0xB4, 0xBA, 0xA7, 0xC6, 0xCA,
150 0xBA, 0x00, 0xAD, 0x00, 0xAB, 0x00, 0xED, },
151 /* 260 cd */
152 { MCS_PGAMMACTL, 0x02,
153 0x18, 0x08, 0x24, 0x74, 0x6E, 0x54, 0xBD,
154 0xC2, 0xB0, 0xB5, 0xBA, 0xA7, 0xC5, 0xC9,
155 0xBA, 0x00, 0xB0, 0x00, 0xAE, 0x00, 0xF1, },
156 /* 270 cd */
157 { MCS_PGAMMACTL, 0x02,
158 0x18, 0x08, 0x24, 0x71, 0x6C, 0x50, 0xBD,
159 0xC3, 0xB0, 0xB4, 0xB8, 0xA6, 0xC6, 0xC9,
160 0xBB, 0x00, 0xB2, 0x00, 0xB1, 0x00, 0xF4, },
161 /* 280 cd */
162 { MCS_PGAMMACTL, 0x02,
163 0x18, 0x08, 0x24, 0x6E, 0x6C, 0x4D, 0xBE,
164 0xC3, 0xB1, 0xB3, 0xB8, 0xA5, 0xC6, 0xC8,
165 0xBB, 0x00, 0xB4, 0x00, 0xB3, 0x00, 0xF7, },
166 /* 290 cd */
167 { MCS_PGAMMACTL, 0x02,
168 0x18, 0x08, 0x24, 0x71, 0x70, 0x50, 0xBD,
169 0xC1, 0xB0, 0xB2, 0xB8, 0xA4, 0xC6, 0xC7,
170 0xBB, 0x00, 0xB6, 0x00, 0xB6, 0x00, 0xFA, },
171 /* 300 cd */
172 { MCS_PGAMMACTL, 0x02,
173 0x18, 0x08, 0x24, 0x70, 0x6E, 0x4E, 0xBC,
174 0xC0, 0xAF, 0xB3, 0xB8, 0xA5, 0xC5, 0xC7,
175 0xBB, 0x00, 0xB9, 0x00, 0xB8, 0x00, 0xFC, },
176};
177
178#define NUM_ACL_LEVELS 7
179#define ACL_TABLE_COUNT 28
180
181static u8 const s6e63m0_acl[NUM_ACL_LEVELS][ACL_TABLE_COUNT] = {
182 /* NULL ACL */
183 { MCS_BCMODE,
184 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
185 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
186 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
187 0x00, 0x00, 0x00 },
188 /* 40P ACL */
189 { MCS_BCMODE,
190 0x4D, 0x96, 0x1D, 0x00, 0x00, 0x01, 0xDF, 0x00,
191 0x00, 0x03, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00,
192 0x01, 0x06, 0x0C, 0x11, 0x16, 0x1C, 0x21, 0x26,
193 0x2B, 0x31, 0x36 },
194 /* 43P ACL */
195 { MCS_BCMODE,
196 0x4D, 0x96, 0x1D, 0x00, 0x00, 0x01, 0xDF, 0x00,
197 0x00, 0x03, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00,
198 0x01, 0x07, 0x0C, 0x12, 0x18, 0x1E, 0x23, 0x29,
199 0x2F, 0x34, 0x3A },
200 /* 45P ACL */
201 { MCS_BCMODE,
202 0x4D, 0x96, 0x1D, 0x00, 0x00, 0x01, 0xDF, 0x00,
203 0x00, 0x03, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00,
204 0x01, 0x07, 0x0D, 0x13, 0x19, 0x1F, 0x25, 0x2B,
205 0x31, 0x37, 0x3D },
206 /* 47P ACL */
207 { MCS_BCMODE,
208 0x4D, 0x96, 0x1D, 0x00, 0x00, 0x01, 0xDF, 0x00,
209 0x00, 0x03, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00,
210 0x01, 0x07, 0x0E, 0x14, 0x1B, 0x21, 0x27, 0x2E,
211 0x34, 0x3B, 0x41 },
212 /* 48P ACL */
213 { MCS_BCMODE,
214 0x4D, 0x96, 0x1D, 0x00, 0x00, 0x01, 0xDF, 0x00,
215 0x00, 0x03, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00,
216 0x01, 0x08, 0x0E, 0x15, 0x1B, 0x22, 0x29, 0x2F,
217 0x36, 0x3C, 0x43 },
218 /* 50P ACL */
219 { MCS_BCMODE,
220 0x4D, 0x96, 0x1D, 0x00, 0x00, 0x01, 0xDF, 0x00,
221 0x00, 0x03, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00,
222 0x01, 0x08, 0x0F, 0x16, 0x1D, 0x24, 0x2A, 0x31,
223 0x38, 0x3F, 0x46 },
224};
225
226/* This tells us which ACL level goes with which gamma */
227static u8 const s6e63m0_acl_per_gamma[NUM_GAMMA_LEVELS] = {
228 /* 30 - 60 cd: ACL off/NULL */
229 0, 0, 0, 0,
230 /* 70 - 250 cd: 40P ACL */
231 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
232 /* 260 - 300 cd: 50P ACL */
233 6, 6, 6, 6, 6,
234};
235
236/* The ELVSS backlight regulator has 5 levels */
237#define S6E63M0_ELVSS_LEVELS 5
238
239static u8 const s6e63m0_elvss_offsets[S6E63M0_ELVSS_LEVELS] = {
240 0x00, /* not set */
241 0x0D, /* 30 cd - 100 cd */
242 0x09, /* 110 cd - 160 cd */
243 0x07, /* 170 cd - 200 cd */
244 0x00, /* 210 cd - 300 cd */
245};
246
247/* This tells us which ELVSS level goes with which gamma */
248static u8 const s6e63m0_elvss_per_gamma[NUM_GAMMA_LEVELS] = {
249 /* 30 - 100 cd */
250 1, 1, 1, 1, 1, 1, 1, 1,
251 /* 110 - 160 cd */
252 2, 2, 2, 2, 2, 2,
253 /* 170 - 200 cd */
254 3, 3, 3, 3,
255 /* 210 - 300 cd */
256 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
257};
258
259struct s6e63m0 {
260 struct device *dev;
261 void *transport_data;
262 int (*dcs_read)(struct device *dev, void *trsp, const u8 cmd, u8 *val);
263 int (*dcs_write)(struct device *dev, void *trsp, const u8 *data, size_t len);
264 struct drm_panel panel;
265 struct backlight_device *bl_dev;
266 u8 lcd_type;
267 u8 elvss_pulse;
268 bool dsi_mode;
269
270 struct regulator_bulk_data supplies[2];
271 struct gpio_desc *reset_gpio;
272
273 /*
274 * This field is tested by functions directly accessing bus before
275 * transfer, transfer is skipped if it is set. In case of transfer
276 * failure or unexpected response the field is set to error value.
277 * Such construct allows to eliminate many checks in higher level
278 * functions.
279 */
280 int error;
281};
282
283static const struct drm_display_mode default_mode = {
284 .clock = 25628,
285 .hdisplay = 480,
286 .hsync_start = 480 + 16,
287 .hsync_end = 480 + 16 + 2,
288 .htotal = 480 + 16 + 2 + 16,
289 .vdisplay = 800,
290 .vsync_start = 800 + 28,
291 .vsync_end = 800 + 28 + 2,
292 .vtotal = 800 + 28 + 2 + 1,
293 .width_mm = 53,
294 .height_mm = 89,
295 .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC,
296};
297
298static inline struct s6e63m0 *panel_to_s6e63m0(struct drm_panel *panel)
299{
300 return container_of(panel, struct s6e63m0, panel);
301}
302
303static int s6e63m0_clear_error(struct s6e63m0 *ctx)
304{
305 int ret = ctx->error;
306
307 ctx->error = 0;
308 return ret;
309}
310
311static void s6e63m0_dcs_read(struct s6e63m0 *ctx, const u8 cmd, u8 *data)
312{
313 if (ctx->error < 0)
314 return;
315
316 ctx->error = ctx->dcs_read(ctx->dev, ctx->transport_data, cmd, data);
317}
318
319static void s6e63m0_dcs_write(struct s6e63m0 *ctx, const u8 *data, size_t len)
320{
321 if (ctx->error < 0 || len == 0)
322 return;
323
324 ctx->error = ctx->dcs_write(ctx->dev, ctx->transport_data, data, len);
325}
326
327#define s6e63m0_dcs_write_seq_static(ctx, seq ...) \
328 ({ \
329 static const u8 d[] = { seq }; \
330 s6e63m0_dcs_write(ctx, d, ARRAY_SIZE(d)); \
331 })
332
333static int s6e63m0_check_lcd_type(struct s6e63m0 *ctx)
334{
335 u8 id1, id2, id3;
336 int ret;
337
338 s6e63m0_dcs_read(ctx, MCS_READ_ID1, data: &id1);
339 s6e63m0_dcs_read(ctx, MCS_READ_ID2, data: &id2);
340 s6e63m0_dcs_read(ctx, MCS_READ_ID3, data: &id3);
341
342 ret = s6e63m0_clear_error(ctx);
343 if (ret) {
344 dev_err(ctx->dev, "error checking LCD type (%d)\n", ret);
345 ctx->lcd_type = 0x00;
346 return ret;
347 }
348
349 dev_info(ctx->dev, "MTP ID: %02x %02x %02x\n", id1, id2, id3);
350
351 /*
352 * We attempt to detect what panel is mounted on the controller.
353 * The third ID byte represents the desired ELVSS pulse for
354 * some displays.
355 */
356 switch (id2) {
357 case S6E63M0_LCD_ID_VALUE_M2:
358 dev_info(ctx->dev, "detected LCD panel AMS397GE MIPI M2\n");
359 ctx->elvss_pulse = id3;
360 break;
361 case S6E63M0_LCD_ID_VALUE_SM2:
362 case S6E63M0_LCD_ID_VALUE_SM2_1:
363 dev_info(ctx->dev, "detected LCD panel AMS397GE MIPI SM2\n");
364 ctx->elvss_pulse = id3;
365 break;
366 default:
367 dev_info(ctx->dev, "unknown LCD panel type %02x\n", id2);
368 /* Default ELVSS pulse level */
369 ctx->elvss_pulse = 0x16;
370 break;
371 }
372
373 ctx->lcd_type = id2;
374
375 return 0;
376}
377
378static void s6e63m0_init(struct s6e63m0 *ctx)
379{
380 /*
381 * We do not know why there is a difference in the DSI mode.
382 * (No datasheet.)
383 *
384 * In the vendor driver this sequence is called
385 * "SEQ_PANEL_CONDITION_SET" or "DCS_CMD_SEQ_PANEL_COND_SET".
386 */
387 if (ctx->dsi_mode)
388 s6e63m0_dcs_write_seq_static(ctx, MCS_PANELCTL,
389 0x01, 0x2c, 0x2c, 0x07, 0x07, 0x5f, 0xb3,
390 0x6d, 0x97, 0x1d, 0x3a, 0x0f, 0x00, 0x00);
391 else
392 s6e63m0_dcs_write_seq_static(ctx, MCS_PANELCTL,
393 0x01, 0x27, 0x27, 0x07, 0x07, 0x54, 0x9f,
394 0x63, 0x8f, 0x1a, 0x33, 0x0d, 0x00, 0x00);
395
396 s6e63m0_dcs_write_seq_static(ctx, MCS_DISCTL,
397 0x02, 0x03, 0x1c, 0x10, 0x10);
398 s6e63m0_dcs_write_seq_static(ctx, MCS_IFCTL,
399 0x03, 0x00, 0x00);
400
401 s6e63m0_dcs_write_seq_static(ctx, MCS_PGAMMACTL,
402 0x00, 0x18, 0x08, 0x24, 0x64, 0x56, 0x33,
403 0xb6, 0xba, 0xa8, 0xac, 0xb1, 0x9d, 0xc1,
404 0xc1, 0xb7, 0x00, 0x9c, 0x00, 0x9f, 0x00,
405 0xd6);
406 s6e63m0_dcs_write_seq_static(ctx, MCS_PGAMMACTL,
407 0x01);
408
409 s6e63m0_dcs_write_seq_static(ctx, MCS_SRCCTL,
410 0x00, 0x8e, 0x07);
411 s6e63m0_dcs_write_seq_static(ctx, MCS_PENTILE_1, 0x6c);
412
413 s6e63m0_dcs_write_seq_static(ctx, MCS_GAMMA_DELTA_Y_RED,
414 0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17,
415 0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b,
416 0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a,
417 0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23,
418 0x21, 0x20, 0x1e, 0x1e);
419
420 s6e63m0_dcs_write_seq_static(ctx, MCS_GAMMA_DELTA_X_RED,
421 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x44,
422 0x44, 0x55, 0x55, 0x66, 0x66, 0x66, 0x66,
423 0x66, 0x66);
424
425 s6e63m0_dcs_write_seq_static(ctx, MCS_GAMMA_DELTA_Y_GREEN,
426 0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17,
427 0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b,
428 0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a,
429 0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23,
430 0x21, 0x20, 0x1e, 0x1e);
431
432 s6e63m0_dcs_write_seq_static(ctx, MCS_GAMMA_DELTA_X_GREEN,
433 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x44,
434 0x44, 0x55, 0x55, 0x66, 0x66, 0x66, 0x66,
435 0x66, 0x66);
436
437 s6e63m0_dcs_write_seq_static(ctx, MCS_GAMMA_DELTA_Y_BLUE,
438 0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17,
439 0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b,
440 0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a,
441 0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23,
442 0x21, 0x20, 0x1e, 0x1e);
443
444 s6e63m0_dcs_write_seq_static(ctx, MCS_GAMMA_DELTA_X_BLUE,
445 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x44,
446 0x44, 0x55, 0x55, 0x66, 0x66, 0x66, 0x66,
447 0x66, 0x66);
448
449 s6e63m0_dcs_write_seq_static(ctx, MCS_BCMODE,
450 0x4d, 0x96, 0x1d, 0x00, 0x00, 0x01, 0xdf,
451 0x00, 0x00, 0x03, 0x1f, 0x00, 0x00, 0x00,
452 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x06,
453 0x09, 0x0d, 0x0f, 0x12, 0x15, 0x18);
454
455 s6e63m0_dcs_write_seq_static(ctx, MCS_TEMP_SWIRE,
456 0x10, 0x10, 0x0b, 0x05);
457
458 s6e63m0_dcs_write_seq_static(ctx, MCS_MIECTL1,
459 0x01);
460
461 s6e63m0_dcs_write_seq_static(ctx, MCS_ELVSS_ON,
462 0x0b);
463}
464
465static int s6e63m0_power_on(struct s6e63m0 *ctx)
466{
467 int ret;
468
469 ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), consumers: ctx->supplies);
470 if (ret < 0)
471 return ret;
472
473 msleep(msecs: 25);
474
475 /* Be sure to send a reset pulse */
476 gpiod_set_value(desc: ctx->reset_gpio, value: 1);
477 msleep(msecs: 5);
478 gpiod_set_value(desc: ctx->reset_gpio, value: 0);
479 msleep(msecs: 120);
480
481 return 0;
482}
483
484static int s6e63m0_power_off(struct s6e63m0 *ctx)
485{
486 int ret;
487
488 gpiod_set_value(desc: ctx->reset_gpio, value: 1);
489 msleep(msecs: 120);
490
491 ret = regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), consumers: ctx->supplies);
492 if (ret < 0)
493 return ret;
494
495 return 0;
496}
497
498static int s6e63m0_disable(struct drm_panel *panel)
499{
500 struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
501
502 backlight_disable(bd: ctx->bl_dev);
503
504 s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_OFF);
505 msleep(msecs: 10);
506 s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_ENTER_SLEEP_MODE);
507 msleep(msecs: 120);
508
509 return 0;
510}
511
512static int s6e63m0_unprepare(struct drm_panel *panel)
513{
514 struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
515 int ret;
516
517 s6e63m0_clear_error(ctx);
518
519 ret = s6e63m0_power_off(ctx);
520 if (ret < 0)
521 return ret;
522
523 return 0;
524}
525
526static int s6e63m0_prepare(struct drm_panel *panel)
527{
528 struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
529 int ret;
530
531 ret = s6e63m0_power_on(ctx);
532 if (ret < 0)
533 return ret;
534
535 /* Magic to unlock level 2 control of the display */
536 s6e63m0_dcs_write_seq_static(ctx, MCS_LEVEL_2_KEY, 0x5a, 0x5a);
537 /* Magic to unlock MTP reading */
538 s6e63m0_dcs_write_seq_static(ctx, MCS_MTP_KEY, 0x5a, 0x5a);
539
540 ret = s6e63m0_check_lcd_type(ctx);
541 if (ret < 0)
542 return ret;
543
544 s6e63m0_init(ctx);
545
546 ret = s6e63m0_clear_error(ctx);
547
548 if (ret < 0)
549 s6e63m0_unprepare(panel);
550
551 return ret;
552}
553
554static int s6e63m0_enable(struct drm_panel *panel)
555{
556 struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
557
558 s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_EXIT_SLEEP_MODE);
559 msleep(msecs: 120);
560 s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_ON);
561 msleep(msecs: 10);
562
563 s6e63m0_dcs_write_seq_static(ctx, MCS_ERROR_CHECK,
564 0xE7, 0x14, 0x60, 0x17, 0x0A, 0x49, 0xC3,
565 0x8F, 0x19, 0x64, 0x91, 0x84, 0x76, 0x20,
566 0x0F, 0x00);
567
568 backlight_enable(bd: ctx->bl_dev);
569
570 return 0;
571}
572
573static int s6e63m0_get_modes(struct drm_panel *panel,
574 struct drm_connector *connector)
575{
576 struct drm_display_mode *mode;
577 static const u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24;
578
579 mode = drm_mode_duplicate(dev: connector->dev, mode: &default_mode);
580 if (!mode) {
581 dev_err(panel->dev, "failed to add mode %ux%u@%u\n",
582 default_mode.hdisplay, default_mode.vdisplay,
583 drm_mode_vrefresh(&default_mode));
584 return -ENOMEM;
585 }
586
587 connector->display_info.width_mm = mode->width_mm;
588 connector->display_info.height_mm = mode->height_mm;
589 drm_display_info_set_bus_formats(info: &connector->display_info,
590 formats: &bus_format, num_formats: 1);
591 connector->display_info.bus_flags = DRM_BUS_FLAG_DE_LOW |
592 DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE;
593
594 drm_mode_set_name(mode);
595
596 mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
597 drm_mode_probed_add(connector, mode);
598
599 return 1;
600}
601
602static const struct drm_panel_funcs s6e63m0_drm_funcs = {
603 .disable = s6e63m0_disable,
604 .unprepare = s6e63m0_unprepare,
605 .prepare = s6e63m0_prepare,
606 .enable = s6e63m0_enable,
607 .get_modes = s6e63m0_get_modes,
608};
609
610static int s6e63m0_set_brightness(struct backlight_device *bd)
611{
612 struct s6e63m0 *ctx = bl_get_data(bl_dev: bd);
613 int brightness = bd->props.brightness;
614 u8 elvss_val;
615 u8 elvss_cmd_set[5];
616 int i;
617
618 /* Adjust ELVSS to candela level */
619 i = s6e63m0_elvss_per_gamma[brightness];
620 elvss_val = ctx->elvss_pulse + s6e63m0_elvss_offsets[i];
621 if (elvss_val > 0x1f)
622 elvss_val = 0x1f;
623 elvss_cmd_set[0] = MCS_TEMP_SWIRE;
624 elvss_cmd_set[1] = elvss_val;
625 elvss_cmd_set[2] = elvss_val;
626 elvss_cmd_set[3] = elvss_val;
627 elvss_cmd_set[4] = elvss_val;
628 s6e63m0_dcs_write(ctx, data: elvss_cmd_set, len: 5);
629
630 /* Update the ACL per gamma value */
631 i = s6e63m0_acl_per_gamma[brightness];
632 s6e63m0_dcs_write(ctx, data: s6e63m0_acl[i],
633 ARRAY_SIZE(s6e63m0_acl[i]));
634
635 /* Update gamma table */
636 s6e63m0_dcs_write(ctx, data: s6e63m0_gamma_22[brightness],
637 ARRAY_SIZE(s6e63m0_gamma_22[brightness]));
638 s6e63m0_dcs_write_seq_static(ctx, MCS_PGAMMACTL, 0x03);
639
640
641 return s6e63m0_clear_error(ctx);
642}
643
644static const struct backlight_ops s6e63m0_backlight_ops = {
645 .update_status = s6e63m0_set_brightness,
646};
647
648static int s6e63m0_backlight_register(struct s6e63m0 *ctx, u32 max_brightness)
649{
650 struct backlight_properties props = {
651 .type = BACKLIGHT_RAW,
652 .brightness = max_brightness,
653 .max_brightness = max_brightness,
654 };
655 struct device *dev = ctx->dev;
656 int ret = 0;
657
658 ctx->bl_dev = devm_backlight_device_register(dev, name: "panel", parent: dev, devdata: ctx,
659 ops: &s6e63m0_backlight_ops,
660 props: &props);
661 if (IS_ERR(ptr: ctx->bl_dev)) {
662 ret = PTR_ERR(ptr: ctx->bl_dev);
663 dev_err(dev, "error registering backlight device (%d)\n", ret);
664 }
665
666 return ret;
667}
668
669int s6e63m0_probe(struct device *dev, void *trsp,
670 int (*dcs_read)(struct device *dev, void *trsp, const u8 cmd, u8 *val),
671 int (*dcs_write)(struct device *dev, void *trsp, const u8 *data, size_t len),
672 bool dsi_mode)
673{
674 struct s6e63m0 *ctx;
675 u32 max_brightness;
676 int ret;
677
678 ctx = devm_kzalloc(dev, size: sizeof(struct s6e63m0), GFP_KERNEL);
679 if (!ctx)
680 return -ENOMEM;
681
682 ctx->transport_data = trsp;
683 ctx->dsi_mode = dsi_mode;
684 ctx->dcs_read = dcs_read;
685 ctx->dcs_write = dcs_write;
686 dev_set_drvdata(dev, data: ctx);
687
688 ctx->dev = dev;
689
690 ret = device_property_read_u32(dev, propname: "max-brightness", val: &max_brightness);
691 if (ret)
692 max_brightness = MAX_BRIGHTNESS;
693 if (max_brightness > MAX_BRIGHTNESS) {
694 dev_err(dev, "illegal max brightness specified\n");
695 max_brightness = MAX_BRIGHTNESS;
696 }
697
698 ctx->supplies[0].supply = "vdd3";
699 ctx->supplies[1].supply = "vci";
700 ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies),
701 consumers: ctx->supplies);
702 if (ret < 0) {
703 dev_err(dev, "failed to get regulators: %d\n", ret);
704 return ret;
705 }
706
707 ctx->reset_gpio = devm_gpiod_get(dev, con_id: "reset", flags: GPIOD_OUT_HIGH);
708 if (IS_ERR(ptr: ctx->reset_gpio)) {
709 dev_err(dev, "cannot get reset-gpios %ld\n", PTR_ERR(ctx->reset_gpio));
710 return PTR_ERR(ptr: ctx->reset_gpio);
711 }
712
713 drm_panel_init(panel: &ctx->panel, dev, funcs: &s6e63m0_drm_funcs,
714 connector_type: dsi_mode ? DRM_MODE_CONNECTOR_DSI :
715 DRM_MODE_CONNECTOR_DPI);
716
717 ret = s6e63m0_backlight_register(ctx, max_brightness);
718 if (ret < 0)
719 return ret;
720
721 drm_panel_add(panel: &ctx->panel);
722
723 return 0;
724}
725EXPORT_SYMBOL_GPL(s6e63m0_probe);
726
727void s6e63m0_remove(struct device *dev)
728{
729 struct s6e63m0 *ctx = dev_get_drvdata(dev);
730
731 drm_panel_remove(panel: &ctx->panel);
732}
733EXPORT_SYMBOL_GPL(s6e63m0_remove);
734
735MODULE_AUTHOR("Paweł Chmiel <pawel.mikolaj.chmiel@gmail.com>");
736MODULE_DESCRIPTION("s6e63m0 LCD Driver");
737MODULE_LICENSE("GPL v2");
738

source code of linux/drivers/gpu/drm/panel/panel-samsung-s6e63m0.c