xref: /linux/drivers/gpu/drm/panel/panel-leadtek-ltk050h3146w.c (revision 7f71507851fc7764b36a3221839607d3a45c2025)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Copyright (C) 2020 Theobroma Systems Design und Consulting GmbH
4  */
5 
6 #include <linux/delay.h>
7 #include <linux/gpio/consumer.h>
8 #include <linux/media-bus-format.h>
9 #include <linux/module.h>
10 #include <linux/of.h>
11 #include <linux/regulator/consumer.h>
12 
13 #include <video/display_timing.h>
14 #include <video/mipi_display.h>
15 
16 #include <drm/drm_mipi_dsi.h>
17 #include <drm/drm_modes.h>
18 #include <drm/drm_panel.h>
19 
20 struct ltk050h3146w_cmd {
21 	char cmd;
22 	char data;
23 };
24 
25 struct ltk050h3146w;
26 struct ltk050h3146w_desc {
27 	const unsigned long mode_flags;
28 	const struct drm_display_mode *mode;
29 	void (*init)(struct mipi_dsi_multi_context *dsi_ctx);
30 };
31 
32 struct ltk050h3146w {
33 	struct device *dev;
34 	struct drm_panel panel;
35 	struct gpio_desc *reset_gpio;
36 	struct regulator *vci;
37 	struct regulator *iovcc;
38 	const struct ltk050h3146w_desc *panel_desc;
39 };
40 
41 static const struct ltk050h3146w_cmd page1_cmds[] = {
42 	{ 0x22, 0x0A }, /* BGR SS GS */
43 	{ 0x31, 0x00 }, /* column inversion */
44 	{ 0x53, 0xA2 }, /* VCOM1 */
45 	{ 0x55, 0xA2 }, /* VCOM2 */
46 	{ 0x50, 0x81 }, /* VREG1OUT=5V */
47 	{ 0x51, 0x85 }, /* VREG2OUT=-5V */
48 	{ 0x62, 0x0D }, /* EQT Time setting */
49 /*
50  * The vendor init selected page 1 here _again_
51  * Is this supposed to be page 2?
52  */
53 	{ 0xA0, 0x00 },
54 	{ 0xA1, 0x1A },
55 	{ 0xA2, 0x28 },
56 	{ 0xA3, 0x13 },
57 	{ 0xA4, 0x16 },
58 	{ 0xA5, 0x29 },
59 	{ 0xA6, 0x1D },
60 	{ 0xA7, 0x1E },
61 	{ 0xA8, 0x84 },
62 	{ 0xA9, 0x1C },
63 	{ 0xAA, 0x28 },
64 	{ 0xAB, 0x75 },
65 	{ 0xAC, 0x1A },
66 	{ 0xAD, 0x19 },
67 	{ 0xAE, 0x4D },
68 	{ 0xAF, 0x22 },
69 	{ 0xB0, 0x28 },
70 	{ 0xB1, 0x54 },
71 	{ 0xB2, 0x66 },
72 	{ 0xB3, 0x39 },
73 	{ 0xC0, 0x00 },
74 	{ 0xC1, 0x1A },
75 	{ 0xC2, 0x28 },
76 	{ 0xC3, 0x13 },
77 	{ 0xC4, 0x16 },
78 	{ 0xC5, 0x29 },
79 	{ 0xC6, 0x1D },
80 	{ 0xC7, 0x1E },
81 	{ 0xC8, 0x84 },
82 	{ 0xC9, 0x1C },
83 	{ 0xCA, 0x28 },
84 	{ 0xCB, 0x75 },
85 	{ 0xCC, 0x1A },
86 	{ 0xCD, 0x19 },
87 	{ 0xCE, 0x4D },
88 	{ 0xCF, 0x22 },
89 	{ 0xD0, 0x28 },
90 	{ 0xD1, 0x54 },
91 	{ 0xD2, 0x66 },
92 	{ 0xD3, 0x39 },
93 };
94 
95 static const struct ltk050h3146w_cmd page3_cmds[] = {
96 	{ 0x01, 0x00 },
97 	{ 0x02, 0x00 },
98 	{ 0x03, 0x73 },
99 	{ 0x04, 0x00 },
100 	{ 0x05, 0x00 },
101 	{ 0x06, 0x0a },
102 	{ 0x07, 0x00 },
103 	{ 0x08, 0x00 },
104 	{ 0x09, 0x01 },
105 	{ 0x0a, 0x00 },
106 	{ 0x0b, 0x00 },
107 	{ 0x0c, 0x01 },
108 	{ 0x0d, 0x00 },
109 	{ 0x0e, 0x00 },
110 	{ 0x0f, 0x1d },
111 	{ 0x10, 0x1d },
112 	{ 0x11, 0x00 },
113 	{ 0x12, 0x00 },
114 	{ 0x13, 0x00 },
115 	{ 0x14, 0x00 },
116 	{ 0x15, 0x00 },
117 	{ 0x16, 0x00 },
118 	{ 0x17, 0x00 },
119 	{ 0x18, 0x00 },
120 	{ 0x19, 0x00 },
121 	{ 0x1a, 0x00 },
122 	{ 0x1b, 0x00 },
123 	{ 0x1c, 0x00 },
124 	{ 0x1d, 0x00 },
125 	{ 0x1e, 0x40 },
126 	{ 0x1f, 0x80 },
127 	{ 0x20, 0x06 },
128 	{ 0x21, 0x02 },
129 	{ 0x22, 0x00 },
130 	{ 0x23, 0x00 },
131 	{ 0x24, 0x00 },
132 	{ 0x25, 0x00 },
133 	{ 0x26, 0x00 },
134 	{ 0x27, 0x00 },
135 	{ 0x28, 0x33 },
136 	{ 0x29, 0x03 },
137 	{ 0x2a, 0x00 },
138 	{ 0x2b, 0x00 },
139 	{ 0x2c, 0x00 },
140 	{ 0x2d, 0x00 },
141 	{ 0x2e, 0x00 },
142 	{ 0x2f, 0x00 },
143 	{ 0x30, 0x00 },
144 	{ 0x31, 0x00 },
145 	{ 0x32, 0x00 },
146 	{ 0x33, 0x00 },
147 	{ 0x34, 0x04 },
148 	{ 0x35, 0x00 },
149 	{ 0x36, 0x00 },
150 	{ 0x37, 0x00 },
151 	{ 0x38, 0x3C },
152 	{ 0x39, 0x35 },
153 	{ 0x3A, 0x01 },
154 	{ 0x3B, 0x40 },
155 	{ 0x3C, 0x00 },
156 	{ 0x3D, 0x01 },
157 	{ 0x3E, 0x00 },
158 	{ 0x3F, 0x00 },
159 	{ 0x40, 0x00 },
160 	{ 0x41, 0x88 },
161 	{ 0x42, 0x00 },
162 	{ 0x43, 0x00 },
163 	{ 0x44, 0x1F },
164 	{ 0x50, 0x01 },
165 	{ 0x51, 0x23 },
166 	{ 0x52, 0x45 },
167 	{ 0x53, 0x67 },
168 	{ 0x54, 0x89 },
169 	{ 0x55, 0xab },
170 	{ 0x56, 0x01 },
171 	{ 0x57, 0x23 },
172 	{ 0x58, 0x45 },
173 	{ 0x59, 0x67 },
174 	{ 0x5a, 0x89 },
175 	{ 0x5b, 0xab },
176 	{ 0x5c, 0xcd },
177 	{ 0x5d, 0xef },
178 	{ 0x5e, 0x11 },
179 	{ 0x5f, 0x01 },
180 	{ 0x60, 0x00 },
181 	{ 0x61, 0x15 },
182 	{ 0x62, 0x14 },
183 	{ 0x63, 0x0E },
184 	{ 0x64, 0x0F },
185 	{ 0x65, 0x0C },
186 	{ 0x66, 0x0D },
187 	{ 0x67, 0x06 },
188 	{ 0x68, 0x02 },
189 	{ 0x69, 0x07 },
190 	{ 0x6a, 0x02 },
191 	{ 0x6b, 0x02 },
192 	{ 0x6c, 0x02 },
193 	{ 0x6d, 0x02 },
194 	{ 0x6e, 0x02 },
195 	{ 0x6f, 0x02 },
196 	{ 0x70, 0x02 },
197 	{ 0x71, 0x02 },
198 	{ 0x72, 0x02 },
199 	{ 0x73, 0x02 },
200 	{ 0x74, 0x02 },
201 	{ 0x75, 0x01 },
202 	{ 0x76, 0x00 },
203 	{ 0x77, 0x14 },
204 	{ 0x78, 0x15 },
205 	{ 0x79, 0x0E },
206 	{ 0x7a, 0x0F },
207 	{ 0x7b, 0x0C },
208 	{ 0x7c, 0x0D },
209 	{ 0x7d, 0x06 },
210 	{ 0x7e, 0x02 },
211 	{ 0x7f, 0x07 },
212 	{ 0x80, 0x02 },
213 	{ 0x81, 0x02 },
214 	{ 0x82, 0x02 },
215 	{ 0x83, 0x02 },
216 	{ 0x84, 0x02 },
217 	{ 0x85, 0x02 },
218 	{ 0x86, 0x02 },
219 	{ 0x87, 0x02 },
220 	{ 0x88, 0x02 },
221 	{ 0x89, 0x02 },
222 	{ 0x8A, 0x02 },
223 };
224 
225 static const struct ltk050h3146w_cmd page4_cmds[] = {
226 	{ 0x70, 0x00 },
227 	{ 0x71, 0x00 },
228 	{ 0x82, 0x0F }, /* VGH_MOD clamp level=15v */
229 	{ 0x84, 0x0F }, /* VGH clamp level 15V */
230 	{ 0x85, 0x0D }, /* VGL clamp level (-10V) */
231 	{ 0x32, 0xAC },
232 	{ 0x8C, 0x80 },
233 	{ 0x3C, 0xF5 },
234 	{ 0xB5, 0x07 }, /* GAMMA OP */
235 	{ 0x31, 0x45 }, /* SOURCE OP */
236 	{ 0x3A, 0x24 }, /* PS_EN OFF */
237 	{ 0x88, 0x33 }, /* LVD */
238 };
239 
240 static inline
241 struct ltk050h3146w *panel_to_ltk050h3146w(struct drm_panel *panel)
242 {
243 	return container_of(panel, struct ltk050h3146w, panel);
244 }
245 
246 static void ltk050h3148w_init_sequence(struct mipi_dsi_multi_context *dsi_ctx)
247 {
248 	/*
249 	 * Init sequence was supplied by the panel vendor without much
250 	 * documentation.
251 	 */
252 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb9, 0xff, 0x83, 0x94);
253 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb1, 0x50, 0x15, 0x75, 0x09, 0x32, 0x44,
254 				     0x71, 0x31, 0x55, 0x2f);
255 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xba, 0x63, 0x03, 0x68, 0x6b, 0xb2, 0xc0);
256 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xd2, 0x88);
257 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb2, 0x00, 0x80, 0x64, 0x10, 0x07);
258 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb4, 0x05, 0x70, 0x05, 0x70, 0x01, 0x70,
259 				     0x01, 0x0c, 0x86, 0x75, 0x00, 0x3f, 0x01, 0x74,
260 				     0x01, 0x74, 0x01, 0x74, 0x01, 0x0c, 0x86);
261 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xd3, 0x00, 0x00, 0x07, 0x07, 0x40, 0x1e,
262 				     0x08, 0x00, 0x32, 0x10, 0x08, 0x00, 0x08, 0x54,
263 				     0x15, 0x10, 0x05, 0x04, 0x02, 0x12, 0x10, 0x05,
264 				     0x07, 0x33, 0x34, 0x0c, 0x0c, 0x37, 0x10, 0x07,
265 				     0x17, 0x11, 0x40);
266 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xd5, 0x19, 0x19, 0x18, 0x18, 0x1b, 0x1b,
267 				     0x1a, 0x1a, 0x04, 0x05, 0x06, 0x07, 0x00, 0x01,
268 				     0x02, 0x03, 0x20, 0x21, 0x18, 0x18, 0x22, 0x23,
269 				     0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
270 				     0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
271 				     0x18, 0x18, 0x18, 0x18, 0x18, 0x18);
272 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xd6, 0x18, 0x18, 0x19, 0x19, 0x1b, 0x1b,
273 				     0x1a, 0x1a, 0x03, 0x02, 0x01, 0x00, 0x07, 0x06,
274 				     0x05, 0x04, 0x23, 0x22, 0x18, 0x18, 0x21, 0x20,
275 				     0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
276 				     0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
277 				     0x18, 0x18, 0x18, 0x18, 0x18, 0x18);
278 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xe0, 0x00, 0x03, 0x09, 0x11, 0x11, 0x14,
279 				     0x18, 0x16, 0x2e, 0x3d, 0x4d, 0x4d, 0x58, 0x6c,
280 				     0x72, 0x78, 0x88, 0x8b, 0x86, 0xa4, 0xb2, 0x58,
281 				     0x55, 0x59, 0x5b, 0x5d, 0x60, 0x64, 0x7f, 0x00,
282 				     0x03, 0x09, 0x0f, 0x11, 0x14, 0x18, 0x16, 0x2e,
283 				     0x3d, 0x4d, 0x4d, 0x58, 0x6d, 0x73, 0x78, 0x88,
284 				     0x8b, 0x87, 0xa5, 0xb2, 0x58, 0x55, 0x58, 0x5b,
285 				     0x5d, 0x61, 0x65, 0x7f);
286 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xcc, 0x0b);
287 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc0, 0x1f, 0x31);
288 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb6, 0xc4, 0xc4);
289 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xbd, 0x01);
290 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb1, 0x00);
291 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xbd, 0x00);
292 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc6, 0xef);
293 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xd4, 0x02);
294 
295 	mipi_dsi_dcs_set_tear_on_multi(dsi_ctx, 1);
296 	mipi_dsi_msleep(dsi_ctx, 60);
297 }
298 
299 static const struct drm_display_mode ltk050h3148w_mode = {
300 	.hdisplay	= 720,
301 	.hsync_start	= 720 + 12,
302 	.hsync_end	= 720 + 12 + 6,
303 	.htotal		= 720 + 12 + 6 + 24,
304 	.vdisplay	= 1280,
305 	.vsync_start	= 1280 + 9,
306 	.vsync_end	= 1280 + 9 + 2,
307 	.vtotal		= 1280 + 9 + 2 + 16,
308 	.clock		= 59756,
309 	.width_mm	= 62,
310 	.height_mm	= 110,
311 };
312 
313 static const struct ltk050h3146w_desc ltk050h3148w_data = {
314 	.mode = &ltk050h3148w_mode,
315 	.init = ltk050h3148w_init_sequence,
316 	.mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
317 		      MIPI_DSI_MODE_VIDEO_BURST,
318 };
319 
320 static void ltk050h3146w_init_sequence(struct mipi_dsi_multi_context *dsi_ctx)
321 {
322 	/*
323 	 * Init sequence was supplied by the panel vendor without much
324 	 * documentation.
325 	 */
326 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xdf, 0x93, 0x65, 0xf8);
327 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb0, 0x01, 0x03, 0x02, 0x00, 0x64, 0x06,
328 				     0x01);
329 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb2, 0x00, 0xb5);
330 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb3, 0x00, 0xb5);
331 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb7, 0x00, 0xbf, 0x00, 0x00, 0xbf, 0x00);
332 
333 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb9, 0x00, 0xc4, 0x23, 0x07);
334 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xbb, 0x02, 0x01, 0x24, 0x00, 0x28, 0x0f,
335 				     0x28, 0x04, 0xcc, 0xcc, 0xcc);
336 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xbc, 0x0f, 0x04);
337 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xbe, 0x1e, 0xf2);
338 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc0, 0x26, 0x03);
339 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc1, 0x00, 0x12);
340 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc3, 0x04, 0x02, 0x02, 0x76, 0x01, 0x80,
341 				     0x80);
342 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc4, 0x24, 0x80, 0xb4, 0x81, 0x12, 0x0f,
343 				     0x16, 0x00, 0x00);
344 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc8, 0x7f, 0x72, 0x67, 0x5d, 0x5d, 0x50,
345 				     0x56, 0x41, 0x59, 0x57, 0x55, 0x70, 0x5b, 0x5f,
346 				     0x4f, 0x47, 0x38, 0x23, 0x08, 0x7f, 0x72, 0x67,
347 				     0x5d, 0x5d, 0x50, 0x56, 0x41, 0x59, 0x57, 0x55,
348 				     0x70, 0x5b, 0x5f, 0x4f, 0x47, 0x38, 0x23, 0x08);
349 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xd0, 0x1e, 0x1f, 0x57, 0x58, 0x48, 0x4a,
350 				     0x44, 0x46, 0x40, 0x1f, 0x42, 0x1f, 0x1f, 0x1f,
351 				     0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f);
352 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xd1, 0x1e, 0x1f, 0x57, 0x58, 0x49, 0x4b,
353 				     0x45, 0x47, 0x41, 0x1f, 0x43, 0x1f, 0x1f, 0x1f,
354 				     0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f);
355 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xd2, 0x1f, 0x1e, 0x17, 0x18, 0x07, 0x05,
356 				     0x0b, 0x09, 0x03, 0x1f, 0x01, 0x1f, 0x1f, 0x1f,
357 				     0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f);
358 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xd3, 0x1f, 0x1e, 0x17, 0x18, 0x06, 0x04,
359 				     0x0a, 0x08, 0x02, 0x1f, 0x00, 0x1f, 0x1f, 0x1f,
360 				     0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f);
361 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xd4, 0x00, 0x00, 0x00, 0x0c, 0x06, 0x20,
362 				     0x01, 0x02, 0x00, 0x60, 0x15, 0xb0, 0x30, 0x03,
363 				     0x04, 0x00, 0x60, 0x72, 0x0a, 0x00, 0x60, 0x08);
364 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xd5, 0x00, 0x06, 0x06, 0x00, 0x30, 0x00,
365 				     0x00, 0x00, 0x00, 0x00, 0xbc, 0x50, 0x00, 0x05,
366 				     0x21, 0x00, 0x60);
367 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xdd, 0x2c, 0xa3, 0x00);
368 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xde, 0x02);
369 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb2, 0x32, 0x1c);
370 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb7, 0x3b, 0x70, 0x00, 0x04);
371 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc1, 0x11);
372 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xbb, 0x21, 0x22, 0x23, 0x24, 0x36, 0x37);
373 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc2, 0x20, 0x38, 0x1e, 0x84);
374 	mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xde, 0x00);
375 
376 	mipi_dsi_dcs_set_tear_on_multi(dsi_ctx, 1);
377 	mipi_dsi_msleep(dsi_ctx, 60);
378 }
379 
380 static const struct drm_display_mode ltk050h3146w_mode = {
381 	.hdisplay	= 720,
382 	.hsync_start	= 720 + 42,
383 	.hsync_end	= 720 + 42 + 8,
384 	.htotal		= 720 + 42 + 8 + 42,
385 	.vdisplay	= 1280,
386 	.vsync_start	= 1280 + 12,
387 	.vsync_end	= 1280 + 12 + 4,
388 	.vtotal		= 1280 + 12 + 4 + 18,
389 	.clock		= 64018,
390 	.width_mm	= 62,
391 	.height_mm	= 110,
392 };
393 
394 static const struct ltk050h3146w_desc ltk050h3146w_data = {
395 	.mode = &ltk050h3146w_mode,
396 	.init = ltk050h3146w_init_sequence,
397 	.mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
398 		MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_NO_EOT_PACKET,
399 };
400 
401 static void ltk050h3146w_a2_select_page(struct mipi_dsi_multi_context *dsi_ctx, int page)
402 {
403 	u8 d[4] = { 0xff, 0x98, 0x81, page };
404 
405 	mipi_dsi_dcs_write_buffer_multi(dsi_ctx, d, ARRAY_SIZE(d));
406 }
407 
408 static void ltk050h3146w_a2_write_page(struct mipi_dsi_multi_context *dsi_ctx, int page,
409 				      const struct ltk050h3146w_cmd *cmds,
410 				      int num)
411 {
412 	ltk050h3146w_a2_select_page(dsi_ctx, page);
413 
414 	for (int i = 0; i < num; i++)
415 		mipi_dsi_generic_write_multi(dsi_ctx, &cmds[i],
416 					     sizeof(struct ltk050h3146w_cmd));
417 }
418 
419 static void ltk050h3146w_a2_init_sequence(struct mipi_dsi_multi_context *dsi_ctx)
420 {
421 	/*
422 	 * Init sequence was supplied by the panel vendor without much
423 	 * documentation.
424 	 */
425 	ltk050h3146w_a2_write_page(dsi_ctx, 3, page3_cmds,
426 					 ARRAY_SIZE(page3_cmds));
427 	ltk050h3146w_a2_write_page(dsi_ctx, 4, page4_cmds,
428 					 ARRAY_SIZE(page4_cmds));
429 	ltk050h3146w_a2_write_page(dsi_ctx, 1, page1_cmds,
430 					 ARRAY_SIZE(page1_cmds));
431 	ltk050h3146w_a2_select_page(dsi_ctx, 0);
432 
433 	/* vendor code called this without param, where there should be one */
434 	mipi_dsi_dcs_set_tear_on_multi(dsi_ctx, 0);
435 
436 	mipi_dsi_msleep(dsi_ctx, 60);
437 }
438 
439 static const struct drm_display_mode ltk050h3146w_a2_mode = {
440 	.hdisplay	= 720,
441 	.hsync_start	= 720 + 42,
442 	.hsync_end	= 720 + 42 + 10,
443 	.htotal		= 720 + 42 + 10 + 60,
444 	.vdisplay	= 1280,
445 	.vsync_start	= 1280 + 18,
446 	.vsync_end	= 1280 + 18 + 4,
447 	.vtotal		= 1280 + 18 + 4 + 12,
448 	.clock		= 65595,
449 	.width_mm	= 62,
450 	.height_mm	= 110,
451 };
452 
453 static const struct ltk050h3146w_desc ltk050h3146w_a2_data = {
454 	.mode = &ltk050h3146w_a2_mode,
455 	.init = ltk050h3146w_a2_init_sequence,
456 	.mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
457 		MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_NO_EOT_PACKET,
458 };
459 
460 static int ltk050h3146w_unprepare(struct drm_panel *panel)
461 {
462 	struct ltk050h3146w *ctx = panel_to_ltk050h3146w(panel);
463 	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
464 	struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi };
465 
466 	mipi_dsi_dcs_set_display_off_multi(&dsi_ctx);
467 	mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx);
468 	if (dsi_ctx.accum_err)
469 		return dsi_ctx.accum_err;
470 
471 	regulator_disable(ctx->iovcc);
472 	regulator_disable(ctx->vci);
473 
474 	return 0;
475 }
476 
477 static int ltk050h3146w_prepare(struct drm_panel *panel)
478 {
479 	struct ltk050h3146w *ctx = panel_to_ltk050h3146w(panel);
480 	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
481 	struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi };
482 
483 	dev_dbg(ctx->dev, "Resetting the panel\n");
484 	dsi_ctx.accum_err = regulator_enable(ctx->vci);
485 	if (dsi_ctx.accum_err) {
486 		dev_err(ctx->dev, "Failed to enable vci supply: %d\n", dsi_ctx.accum_err);
487 		return dsi_ctx.accum_err;
488 	}
489 	dsi_ctx.accum_err = regulator_enable(ctx->iovcc);
490 	if (dsi_ctx.accum_err) {
491 		dev_err(ctx->dev, "Failed to enable iovcc supply: %d\n", dsi_ctx.accum_err);
492 		goto disable_vci;
493 	}
494 
495 	gpiod_set_value_cansleep(ctx->reset_gpio, 1);
496 	usleep_range(5000, 6000);
497 	gpiod_set_value_cansleep(ctx->reset_gpio, 0);
498 	msleep(20);
499 
500 	ctx->panel_desc->init(&dsi_ctx);
501 	mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx);
502 	/* T9: 120ms */
503 	mipi_dsi_msleep(&dsi_ctx, 120);
504 	mipi_dsi_dcs_set_display_on_multi(&dsi_ctx);
505 	mipi_dsi_msleep(&dsi_ctx, 50);
506 
507 	if (dsi_ctx.accum_err)
508 		goto disable_iovcc;
509 
510 	return 0;
511 
512 disable_iovcc:
513 	regulator_disable(ctx->iovcc);
514 disable_vci:
515 	regulator_disable(ctx->vci);
516 	return dsi_ctx.accum_err;
517 }
518 
519 static int ltk050h3146w_get_modes(struct drm_panel *panel,
520 				  struct drm_connector *connector)
521 {
522 	struct ltk050h3146w *ctx = panel_to_ltk050h3146w(panel);
523 	struct drm_display_mode *mode;
524 
525 	mode = drm_mode_duplicate(connector->dev, ctx->panel_desc->mode);
526 	if (!mode)
527 		return -ENOMEM;
528 
529 	drm_mode_set_name(mode);
530 
531 	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
532 	connector->display_info.width_mm = mode->width_mm;
533 	connector->display_info.height_mm = mode->height_mm;
534 	drm_mode_probed_add(connector, mode);
535 
536 	return 1;
537 }
538 
539 static const struct drm_panel_funcs ltk050h3146w_funcs = {
540 	.unprepare	= ltk050h3146w_unprepare,
541 	.prepare	= ltk050h3146w_prepare,
542 	.get_modes	= ltk050h3146w_get_modes,
543 };
544 
545 static int ltk050h3146w_probe(struct mipi_dsi_device *dsi)
546 {
547 	struct device *dev = &dsi->dev;
548 	struct ltk050h3146w *ctx;
549 	int ret;
550 
551 	ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
552 	if (!ctx)
553 		return -ENOMEM;
554 
555 	ctx->panel_desc = of_device_get_match_data(dev);
556 	if (!ctx->panel_desc)
557 		return -EINVAL;
558 
559 	ctx->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
560 	if (IS_ERR(ctx->reset_gpio))
561 		return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), "cannot get reset gpio\n");
562 
563 	ctx->vci = devm_regulator_get(dev, "vci");
564 	if (IS_ERR(ctx->vci))
565 		return dev_err_probe(dev, PTR_ERR(ctx->vci), "Failed to request vci regulator\n");
566 
567 	ctx->iovcc = devm_regulator_get(dev, "iovcc");
568 	if (IS_ERR(ctx->iovcc))
569 		return dev_err_probe(dev, PTR_ERR(ctx->iovcc),
570 				     "Failed to request iovcc regulator\n");
571 
572 	mipi_dsi_set_drvdata(dsi, ctx);
573 
574 	ctx->dev = dev;
575 
576 	dsi->lanes = 4;
577 	dsi->format = MIPI_DSI_FMT_RGB888;
578 	dsi->mode_flags = ctx->panel_desc->mode_flags;
579 
580 	drm_panel_init(&ctx->panel, &dsi->dev, &ltk050h3146w_funcs,
581 		       DRM_MODE_CONNECTOR_DSI);
582 
583 	ret = drm_panel_of_backlight(&ctx->panel);
584 	if (ret)
585 		return ret;
586 
587 	drm_panel_add(&ctx->panel);
588 
589 	ret = mipi_dsi_attach(dsi);
590 	if (ret < 0) {
591 		dev_err(dev, "mipi_dsi_attach failed: %d\n", ret);
592 		drm_panel_remove(&ctx->panel);
593 		return ret;
594 	}
595 
596 	return 0;
597 }
598 
599 static void ltk050h3146w_remove(struct mipi_dsi_device *dsi)
600 {
601 	struct ltk050h3146w *ctx = mipi_dsi_get_drvdata(dsi);
602 	int ret;
603 
604 	ret = mipi_dsi_detach(dsi);
605 	if (ret < 0)
606 		dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret);
607 
608 	drm_panel_remove(&ctx->panel);
609 }
610 
611 static const struct of_device_id ltk050h3146w_of_match[] = {
612 	{
613 		.compatible = "leadtek,ltk050h3146w",
614 		.data = &ltk050h3146w_data,
615 	},
616 	{
617 		.compatible = "leadtek,ltk050h3146w-a2",
618 		.data = &ltk050h3146w_a2_data,
619 	},
620 	{
621 		.compatible = "leadtek,ltk050h3148w",
622 		.data = &ltk050h3148w_data,
623 	},
624 	{ /* sentinel */ }
625 };
626 MODULE_DEVICE_TABLE(of, ltk050h3146w_of_match);
627 
628 static struct mipi_dsi_driver ltk050h3146w_driver = {
629 	.driver = {
630 		.name = "panel-leadtek-ltk050h3146w",
631 		.of_match_table = ltk050h3146w_of_match,
632 	},
633 	.probe	= ltk050h3146w_probe,
634 	.remove = ltk050h3146w_remove,
635 };
636 module_mipi_dsi_driver(ltk050h3146w_driver);
637 
638 MODULE_AUTHOR("Heiko Stuebner <heiko.stuebner@theobroma-systems.com>");
639 MODULE_DESCRIPTION("DRM driver for Leadtek LTK050H3146W MIPI DSI panel");
640 MODULE_LICENSE("GPL v2");
641