1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * NV3051D MIPI-DSI panel driver for Anbernic RG353x |
4 | * Copyright (C) 2022 Chris Morgan |
5 | * |
6 | * based on |
7 | * |
8 | * Elida kd35t133 3.5" MIPI-DSI panel driver |
9 | * Copyright (C) Theobroma Systems 2020 |
10 | */ |
11 | |
12 | #include <linux/delay.h> |
13 | #include <linux/gpio/consumer.h> |
14 | #include <linux/media-bus-format.h> |
15 | #include <linux/module.h> |
16 | #include <linux/of.h> |
17 | #include <linux/regulator/consumer.h> |
18 | |
19 | #include <video/display_timing.h> |
20 | #include <video/mipi_display.h> |
21 | |
22 | #include <drm/drm_mipi_dsi.h> |
23 | #include <drm/drm_modes.h> |
24 | #include <drm/drm_panel.h> |
25 | |
26 | struct nv3051d_panel_info { |
27 | const struct drm_display_mode *display_modes; |
28 | unsigned int num_modes; |
29 | u16 width_mm, height_mm; |
30 | u32 bus_flags; |
31 | u32 mode_flags; |
32 | }; |
33 | |
34 | struct panel_nv3051d { |
35 | struct device *dev; |
36 | struct drm_panel panel; |
37 | struct gpio_desc *reset_gpio; |
38 | const struct nv3051d_panel_info *panel_info; |
39 | struct regulator *vdd; |
40 | }; |
41 | |
42 | static inline struct panel_nv3051d *panel_to_panelnv3051d(struct drm_panel *panel) |
43 | { |
44 | return container_of(panel, struct panel_nv3051d, panel); |
45 | } |
46 | |
47 | static int panel_nv3051d_init_sequence(struct panel_nv3051d *ctx) |
48 | { |
49 | struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); |
50 | |
51 | /* |
52 | * Init sequence was supplied by device vendor with no |
53 | * documentation. |
54 | */ |
55 | |
56 | mipi_dsi_dcs_write_seq(dsi, 0xFF, 0x30); |
57 | mipi_dsi_dcs_write_seq(dsi, 0xFF, 0x52); |
58 | mipi_dsi_dcs_write_seq(dsi, 0xFF, 0x01); |
59 | mipi_dsi_dcs_write_seq(dsi, 0xE3, 0x00); |
60 | mipi_dsi_dcs_write_seq(dsi, 0x03, 0x40); |
61 | mipi_dsi_dcs_write_seq(dsi, 0x04, 0x00); |
62 | mipi_dsi_dcs_write_seq(dsi, 0x05, 0x03); |
63 | mipi_dsi_dcs_write_seq(dsi, 0x24, 0x12); |
64 | mipi_dsi_dcs_write_seq(dsi, 0x25, 0x1E); |
65 | mipi_dsi_dcs_write_seq(dsi, 0x26, 0x28); |
66 | mipi_dsi_dcs_write_seq(dsi, 0x27, 0x52); |
67 | mipi_dsi_dcs_write_seq(dsi, 0x28, 0x57); |
68 | mipi_dsi_dcs_write_seq(dsi, 0x29, 0x01); |
69 | mipi_dsi_dcs_write_seq(dsi, 0x2A, 0xDF); |
70 | mipi_dsi_dcs_write_seq(dsi, 0x38, 0x9C); |
71 | mipi_dsi_dcs_write_seq(dsi, 0x39, 0xA7); |
72 | mipi_dsi_dcs_write_seq(dsi, 0x3A, 0x53); |
73 | mipi_dsi_dcs_write_seq(dsi, 0x44, 0x00); |
74 | mipi_dsi_dcs_write_seq(dsi, 0x49, 0x3C); |
75 | mipi_dsi_dcs_write_seq(dsi, 0x59, 0xFE); |
76 | mipi_dsi_dcs_write_seq(dsi, 0x5C, 0x00); |
77 | mipi_dsi_dcs_write_seq(dsi, 0x91, 0x77); |
78 | mipi_dsi_dcs_write_seq(dsi, 0x92, 0x77); |
79 | mipi_dsi_dcs_write_seq(dsi, 0xA0, 0x55); |
80 | mipi_dsi_dcs_write_seq(dsi, 0xA1, 0x50); |
81 | mipi_dsi_dcs_write_seq(dsi, 0xA4, 0x9C); |
82 | mipi_dsi_dcs_write_seq(dsi, 0xA7, 0x02); |
83 | mipi_dsi_dcs_write_seq(dsi, 0xA8, 0x01); |
84 | mipi_dsi_dcs_write_seq(dsi, 0xA9, 0x01); |
85 | mipi_dsi_dcs_write_seq(dsi, 0xAA, 0xFC); |
86 | mipi_dsi_dcs_write_seq(dsi, 0xAB, 0x28); |
87 | mipi_dsi_dcs_write_seq(dsi, 0xAC, 0x06); |
88 | mipi_dsi_dcs_write_seq(dsi, 0xAD, 0x06); |
89 | mipi_dsi_dcs_write_seq(dsi, 0xAE, 0x06); |
90 | mipi_dsi_dcs_write_seq(dsi, 0xAF, 0x03); |
91 | mipi_dsi_dcs_write_seq(dsi, 0xB0, 0x08); |
92 | mipi_dsi_dcs_write_seq(dsi, 0xB1, 0x26); |
93 | mipi_dsi_dcs_write_seq(dsi, 0xB2, 0x28); |
94 | mipi_dsi_dcs_write_seq(dsi, 0xB3, 0x28); |
95 | mipi_dsi_dcs_write_seq(dsi, 0xB4, 0x33); |
96 | mipi_dsi_dcs_write_seq(dsi, 0xB5, 0x08); |
97 | mipi_dsi_dcs_write_seq(dsi, 0xB6, 0x26); |
98 | mipi_dsi_dcs_write_seq(dsi, 0xB7, 0x08); |
99 | mipi_dsi_dcs_write_seq(dsi, 0xB8, 0x26); |
100 | mipi_dsi_dcs_write_seq(dsi, 0xFF, 0x30); |
101 | mipi_dsi_dcs_write_seq(dsi, 0xFF, 0x52); |
102 | mipi_dsi_dcs_write_seq(dsi, 0xFF, 0x02); |
103 | mipi_dsi_dcs_write_seq(dsi, 0xB1, 0x0E); |
104 | mipi_dsi_dcs_write_seq(dsi, 0xD1, 0x0E); |
105 | mipi_dsi_dcs_write_seq(dsi, 0xB4, 0x29); |
106 | mipi_dsi_dcs_write_seq(dsi, 0xD4, 0x2B); |
107 | mipi_dsi_dcs_write_seq(dsi, 0xB2, 0x0C); |
108 | mipi_dsi_dcs_write_seq(dsi, 0xD2, 0x0A); |
109 | mipi_dsi_dcs_write_seq(dsi, 0xB3, 0x28); |
110 | mipi_dsi_dcs_write_seq(dsi, 0xD3, 0x28); |
111 | mipi_dsi_dcs_write_seq(dsi, 0xB6, 0x11); |
112 | mipi_dsi_dcs_write_seq(dsi, 0xD6, 0x0D); |
113 | mipi_dsi_dcs_write_seq(dsi, 0xB7, 0x32); |
114 | mipi_dsi_dcs_write_seq(dsi, 0xD7, 0x30); |
115 | mipi_dsi_dcs_write_seq(dsi, 0xC1, 0x04); |
116 | mipi_dsi_dcs_write_seq(dsi, 0xE1, 0x06); |
117 | mipi_dsi_dcs_write_seq(dsi, 0xB8, 0x0A); |
118 | mipi_dsi_dcs_write_seq(dsi, 0xD8, 0x0A); |
119 | mipi_dsi_dcs_write_seq(dsi, 0xB9, 0x01); |
120 | mipi_dsi_dcs_write_seq(dsi, 0xD9, 0x01); |
121 | mipi_dsi_dcs_write_seq(dsi, 0xBD, 0x13); |
122 | mipi_dsi_dcs_write_seq(dsi, 0xDD, 0x13); |
123 | mipi_dsi_dcs_write_seq(dsi, 0xBC, 0x11); |
124 | mipi_dsi_dcs_write_seq(dsi, 0xDC, 0x11); |
125 | mipi_dsi_dcs_write_seq(dsi, 0xBB, 0x0F); |
126 | mipi_dsi_dcs_write_seq(dsi, 0xDB, 0x0F); |
127 | mipi_dsi_dcs_write_seq(dsi, 0xBA, 0x0F); |
128 | mipi_dsi_dcs_write_seq(dsi, 0xDA, 0x0F); |
129 | mipi_dsi_dcs_write_seq(dsi, 0xBE, 0x18); |
130 | mipi_dsi_dcs_write_seq(dsi, 0xDE, 0x18); |
131 | mipi_dsi_dcs_write_seq(dsi, 0xBF, 0x0F); |
132 | mipi_dsi_dcs_write_seq(dsi, 0xDF, 0x0F); |
133 | mipi_dsi_dcs_write_seq(dsi, 0xC0, 0x17); |
134 | mipi_dsi_dcs_write_seq(dsi, 0xE0, 0x17); |
135 | mipi_dsi_dcs_write_seq(dsi, 0xB5, 0x3B); |
136 | mipi_dsi_dcs_write_seq(dsi, 0xD5, 0x3C); |
137 | mipi_dsi_dcs_write_seq(dsi, 0xB0, 0x0B); |
138 | mipi_dsi_dcs_write_seq(dsi, 0xD0, 0x0C); |
139 | mipi_dsi_dcs_write_seq(dsi, 0xFF, 0x30); |
140 | mipi_dsi_dcs_write_seq(dsi, 0xFF, 0x52); |
141 | mipi_dsi_dcs_write_seq(dsi, 0xFF, 0x03); |
142 | mipi_dsi_dcs_write_seq(dsi, 0x00, 0x2A); |
143 | mipi_dsi_dcs_write_seq(dsi, 0x01, 0x2A); |
144 | mipi_dsi_dcs_write_seq(dsi, 0x02, 0x2A); |
145 | mipi_dsi_dcs_write_seq(dsi, 0x03, 0x2A); |
146 | mipi_dsi_dcs_write_seq(dsi, 0x04, 0x61); |
147 | mipi_dsi_dcs_write_seq(dsi, 0x05, 0x80); |
148 | mipi_dsi_dcs_write_seq(dsi, 0x06, 0xC7); |
149 | mipi_dsi_dcs_write_seq(dsi, 0x07, 0x01); |
150 | mipi_dsi_dcs_write_seq(dsi, 0x08, 0x82); |
151 | mipi_dsi_dcs_write_seq(dsi, 0x09, 0x83); |
152 | mipi_dsi_dcs_write_seq(dsi, 0x30, 0x2A); |
153 | mipi_dsi_dcs_write_seq(dsi, 0x31, 0x2A); |
154 | mipi_dsi_dcs_write_seq(dsi, 0x32, 0x2A); |
155 | mipi_dsi_dcs_write_seq(dsi, 0x33, 0x2A); |
156 | mipi_dsi_dcs_write_seq(dsi, 0x34, 0x61); |
157 | mipi_dsi_dcs_write_seq(dsi, 0x35, 0xC5); |
158 | mipi_dsi_dcs_write_seq(dsi, 0x36, 0x80); |
159 | mipi_dsi_dcs_write_seq(dsi, 0x37, 0x23); |
160 | mipi_dsi_dcs_write_seq(dsi, 0x40, 0x82); |
161 | mipi_dsi_dcs_write_seq(dsi, 0x41, 0x83); |
162 | mipi_dsi_dcs_write_seq(dsi, 0x42, 0x80); |
163 | mipi_dsi_dcs_write_seq(dsi, 0x43, 0x81); |
164 | mipi_dsi_dcs_write_seq(dsi, 0x44, 0x11); |
165 | mipi_dsi_dcs_write_seq(dsi, 0x45, 0xF2); |
166 | mipi_dsi_dcs_write_seq(dsi, 0x46, 0xF1); |
167 | mipi_dsi_dcs_write_seq(dsi, 0x47, 0x11); |
168 | mipi_dsi_dcs_write_seq(dsi, 0x48, 0xF4); |
169 | mipi_dsi_dcs_write_seq(dsi, 0x49, 0xF3); |
170 | mipi_dsi_dcs_write_seq(dsi, 0x50, 0x02); |
171 | mipi_dsi_dcs_write_seq(dsi, 0x51, 0x01); |
172 | mipi_dsi_dcs_write_seq(dsi, 0x52, 0x04); |
173 | mipi_dsi_dcs_write_seq(dsi, 0x53, 0x03); |
174 | mipi_dsi_dcs_write_seq(dsi, 0x54, 0x11); |
175 | mipi_dsi_dcs_write_seq(dsi, 0x55, 0xF6); |
176 | mipi_dsi_dcs_write_seq(dsi, 0x56, 0xF5); |
177 | mipi_dsi_dcs_write_seq(dsi, 0x57, 0x11); |
178 | mipi_dsi_dcs_write_seq(dsi, 0x58, 0xF8); |
179 | mipi_dsi_dcs_write_seq(dsi, 0x59, 0xF7); |
180 | mipi_dsi_dcs_write_seq(dsi, 0x7E, 0x02); |
181 | mipi_dsi_dcs_write_seq(dsi, 0x7F, 0x80); |
182 | mipi_dsi_dcs_write_seq(dsi, 0xE0, 0x5A); |
183 | mipi_dsi_dcs_write_seq(dsi, 0xB1, 0x00); |
184 | mipi_dsi_dcs_write_seq(dsi, 0xB4, 0x0E); |
185 | mipi_dsi_dcs_write_seq(dsi, 0xB5, 0x0F); |
186 | mipi_dsi_dcs_write_seq(dsi, 0xB6, 0x04); |
187 | mipi_dsi_dcs_write_seq(dsi, 0xB7, 0x07); |
188 | mipi_dsi_dcs_write_seq(dsi, 0xB8, 0x06); |
189 | mipi_dsi_dcs_write_seq(dsi, 0xB9, 0x05); |
190 | mipi_dsi_dcs_write_seq(dsi, 0xBA, 0x0F); |
191 | mipi_dsi_dcs_write_seq(dsi, 0xC7, 0x00); |
192 | mipi_dsi_dcs_write_seq(dsi, 0xCA, 0x0E); |
193 | mipi_dsi_dcs_write_seq(dsi, 0xCB, 0x0F); |
194 | mipi_dsi_dcs_write_seq(dsi, 0xCC, 0x04); |
195 | mipi_dsi_dcs_write_seq(dsi, 0xCD, 0x07); |
196 | mipi_dsi_dcs_write_seq(dsi, 0xCE, 0x06); |
197 | mipi_dsi_dcs_write_seq(dsi, 0xCF, 0x05); |
198 | mipi_dsi_dcs_write_seq(dsi, 0xD0, 0x0F); |
199 | mipi_dsi_dcs_write_seq(dsi, 0x81, 0x0F); |
200 | mipi_dsi_dcs_write_seq(dsi, 0x84, 0x0E); |
201 | mipi_dsi_dcs_write_seq(dsi, 0x85, 0x0F); |
202 | mipi_dsi_dcs_write_seq(dsi, 0x86, 0x07); |
203 | mipi_dsi_dcs_write_seq(dsi, 0x87, 0x04); |
204 | mipi_dsi_dcs_write_seq(dsi, 0x88, 0x05); |
205 | mipi_dsi_dcs_write_seq(dsi, 0x89, 0x06); |
206 | mipi_dsi_dcs_write_seq(dsi, 0x8A, 0x00); |
207 | mipi_dsi_dcs_write_seq(dsi, 0x97, 0x0F); |
208 | mipi_dsi_dcs_write_seq(dsi, 0x9A, 0x0E); |
209 | mipi_dsi_dcs_write_seq(dsi, 0x9B, 0x0F); |
210 | mipi_dsi_dcs_write_seq(dsi, 0x9C, 0x07); |
211 | mipi_dsi_dcs_write_seq(dsi, 0x9D, 0x04); |
212 | mipi_dsi_dcs_write_seq(dsi, 0x9E, 0x05); |
213 | mipi_dsi_dcs_write_seq(dsi, 0x9F, 0x06); |
214 | mipi_dsi_dcs_write_seq(dsi, 0xA0, 0x00); |
215 | mipi_dsi_dcs_write_seq(dsi, 0xFF, 0x30); |
216 | mipi_dsi_dcs_write_seq(dsi, 0xFF, 0x52); |
217 | mipi_dsi_dcs_write_seq(dsi, 0xFF, 0x02); |
218 | mipi_dsi_dcs_write_seq(dsi, 0x01, 0x01); |
219 | mipi_dsi_dcs_write_seq(dsi, 0x02, 0xDA); |
220 | mipi_dsi_dcs_write_seq(dsi, 0x03, 0xBA); |
221 | mipi_dsi_dcs_write_seq(dsi, 0x04, 0xA8); |
222 | mipi_dsi_dcs_write_seq(dsi, 0x05, 0x9A); |
223 | mipi_dsi_dcs_write_seq(dsi, 0x06, 0x70); |
224 | mipi_dsi_dcs_write_seq(dsi, 0x07, 0xFF); |
225 | mipi_dsi_dcs_write_seq(dsi, 0x08, 0x91); |
226 | mipi_dsi_dcs_write_seq(dsi, 0x09, 0x90); |
227 | mipi_dsi_dcs_write_seq(dsi, 0x0A, 0xFF); |
228 | mipi_dsi_dcs_write_seq(dsi, 0x0B, 0x8F); |
229 | mipi_dsi_dcs_write_seq(dsi, 0x0C, 0x60); |
230 | mipi_dsi_dcs_write_seq(dsi, 0x0D, 0x58); |
231 | mipi_dsi_dcs_write_seq(dsi, 0x0E, 0x48); |
232 | mipi_dsi_dcs_write_seq(dsi, 0x0F, 0x38); |
233 | mipi_dsi_dcs_write_seq(dsi, 0x10, 0x2B); |
234 | mipi_dsi_dcs_write_seq(dsi, 0xFF, 0x30); |
235 | mipi_dsi_dcs_write_seq(dsi, 0xFF, 0x52); |
236 | mipi_dsi_dcs_write_seq(dsi, 0xFF, 0x00); |
237 | mipi_dsi_dcs_write_seq(dsi, 0x36, 0x02); |
238 | mipi_dsi_dcs_write_seq(dsi, 0x3A, 0x70); |
239 | |
240 | dev_dbg(ctx->dev, "Panel init sequence done\n" ); |
241 | |
242 | return 0; |
243 | } |
244 | |
245 | static int panel_nv3051d_unprepare(struct drm_panel *panel) |
246 | { |
247 | struct panel_nv3051d *ctx = panel_to_panelnv3051d(panel); |
248 | struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); |
249 | int ret; |
250 | |
251 | ret = mipi_dsi_dcs_set_display_off(dsi); |
252 | if (ret < 0) |
253 | dev_err(ctx->dev, "failed to set display off: %d\n" , ret); |
254 | |
255 | msleep(msecs: 20); |
256 | |
257 | ret = mipi_dsi_dcs_enter_sleep_mode(dsi); |
258 | if (ret < 0) { |
259 | dev_err(ctx->dev, "failed to enter sleep mode: %d\n" , ret); |
260 | return ret; |
261 | } |
262 | |
263 | usleep_range(min: 10000, max: 15000); |
264 | |
265 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 1); |
266 | |
267 | regulator_disable(regulator: ctx->vdd); |
268 | |
269 | return 0; |
270 | } |
271 | |
272 | static int panel_nv3051d_prepare(struct drm_panel *panel) |
273 | { |
274 | struct panel_nv3051d *ctx = panel_to_panelnv3051d(panel); |
275 | struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); |
276 | int ret; |
277 | |
278 | dev_dbg(ctx->dev, "Resetting the panel\n" ); |
279 | ret = regulator_enable(regulator: ctx->vdd); |
280 | if (ret < 0) { |
281 | dev_err(ctx->dev, "Failed to enable vdd supply: %d\n" , ret); |
282 | return ret; |
283 | } |
284 | |
285 | usleep_range(min: 2000, max: 3000); |
286 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 1); |
287 | msleep(msecs: 150); |
288 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 0); |
289 | msleep(msecs: 20); |
290 | |
291 | ret = panel_nv3051d_init_sequence(ctx); |
292 | if (ret < 0) { |
293 | dev_err(ctx->dev, "Panel init sequence failed: %d\n" , ret); |
294 | goto disable_vdd; |
295 | } |
296 | |
297 | ret = mipi_dsi_dcs_exit_sleep_mode(dsi); |
298 | if (ret < 0) { |
299 | dev_err(ctx->dev, "Failed to exit sleep mode: %d\n" , ret); |
300 | goto disable_vdd; |
301 | } |
302 | |
303 | msleep(msecs: 200); |
304 | |
305 | ret = mipi_dsi_dcs_set_display_on(dsi); |
306 | if (ret < 0) { |
307 | dev_err(ctx->dev, "Failed to set display on: %d\n" , ret); |
308 | goto disable_vdd; |
309 | } |
310 | |
311 | usleep_range(min: 10000, max: 15000); |
312 | |
313 | return 0; |
314 | |
315 | disable_vdd: |
316 | regulator_disable(regulator: ctx->vdd); |
317 | return ret; |
318 | } |
319 | |
320 | static int panel_nv3051d_get_modes(struct drm_panel *panel, |
321 | struct drm_connector *connector) |
322 | { |
323 | struct panel_nv3051d *ctx = panel_to_panelnv3051d(panel); |
324 | const struct nv3051d_panel_info *panel_info = ctx->panel_info; |
325 | struct drm_display_mode *mode; |
326 | unsigned int i; |
327 | |
328 | for (i = 0; i < panel_info->num_modes; i++) { |
329 | mode = drm_mode_duplicate(dev: connector->dev, |
330 | mode: &panel_info->display_modes[i]); |
331 | if (!mode) |
332 | return -ENOMEM; |
333 | |
334 | drm_mode_set_name(mode); |
335 | |
336 | mode->type = DRM_MODE_TYPE_DRIVER; |
337 | if (panel_info->num_modes == 1) |
338 | mode->type |= DRM_MODE_TYPE_PREFERRED; |
339 | |
340 | drm_mode_probed_add(connector, mode); |
341 | } |
342 | |
343 | connector->display_info.bpc = 8; |
344 | connector->display_info.width_mm = panel_info->width_mm; |
345 | connector->display_info.height_mm = panel_info->height_mm; |
346 | connector->display_info.bus_flags = panel_info->bus_flags; |
347 | |
348 | return panel_info->num_modes; |
349 | } |
350 | |
351 | static const struct drm_panel_funcs panel_nv3051d_funcs = { |
352 | .unprepare = panel_nv3051d_unprepare, |
353 | .prepare = panel_nv3051d_prepare, |
354 | .get_modes = panel_nv3051d_get_modes, |
355 | }; |
356 | |
357 | static int panel_nv3051d_probe(struct mipi_dsi_device *dsi) |
358 | { |
359 | struct device *dev = &dsi->dev; |
360 | struct panel_nv3051d *ctx; |
361 | int ret; |
362 | |
363 | ctx = devm_kzalloc(dev, size: sizeof(*ctx), GFP_KERNEL); |
364 | if (!ctx) |
365 | return -ENOMEM; |
366 | |
367 | ctx->dev = dev; |
368 | |
369 | ctx->panel_info = of_device_get_match_data(dev); |
370 | if (!ctx->panel_info) |
371 | return -EINVAL; |
372 | |
373 | ctx->reset_gpio = devm_gpiod_get_optional(dev, con_id: "reset" , flags: GPIOD_OUT_HIGH); |
374 | if (IS_ERR(ptr: ctx->reset_gpio)) { |
375 | dev_err(dev, "cannot get reset gpio\n" ); |
376 | return PTR_ERR(ptr: ctx->reset_gpio); |
377 | } |
378 | |
379 | ctx->vdd = devm_regulator_get(dev, id: "vdd" ); |
380 | if (IS_ERR(ptr: ctx->vdd)) { |
381 | ret = PTR_ERR(ptr: ctx->vdd); |
382 | if (ret != -EPROBE_DEFER) |
383 | dev_err(dev, "Failed to request vdd regulator: %d\n" , ret); |
384 | return ret; |
385 | } |
386 | |
387 | mipi_dsi_set_drvdata(dsi, data: ctx); |
388 | |
389 | dsi->lanes = 4; |
390 | dsi->format = MIPI_DSI_FMT_RGB888; |
391 | dsi->mode_flags = ctx->panel_info->mode_flags; |
392 | |
393 | drm_panel_init(panel: &ctx->panel, dev: &dsi->dev, funcs: &panel_nv3051d_funcs, |
394 | DRM_MODE_CONNECTOR_DSI); |
395 | |
396 | ret = drm_panel_of_backlight(panel: &ctx->panel); |
397 | if (ret) |
398 | return ret; |
399 | |
400 | drm_panel_add(panel: &ctx->panel); |
401 | |
402 | ret = mipi_dsi_attach(dsi); |
403 | if (ret < 0) { |
404 | dev_err(dev, "mipi_dsi_attach failed: %d\n" , ret); |
405 | drm_panel_remove(panel: &ctx->panel); |
406 | return ret; |
407 | } |
408 | |
409 | return 0; |
410 | } |
411 | |
412 | static void panel_nv3051d_shutdown(struct mipi_dsi_device *dsi) |
413 | { |
414 | struct panel_nv3051d *ctx = mipi_dsi_get_drvdata(dsi); |
415 | int ret; |
416 | |
417 | ret = drm_panel_unprepare(panel: &ctx->panel); |
418 | if (ret < 0) |
419 | dev_err(&dsi->dev, "Failed to unprepare panel: %d\n" , ret); |
420 | |
421 | ret = drm_panel_disable(panel: &ctx->panel); |
422 | if (ret < 0) |
423 | dev_err(&dsi->dev, "Failed to disable panel: %d\n" , ret); |
424 | } |
425 | |
426 | static void panel_nv3051d_remove(struct mipi_dsi_device *dsi) |
427 | { |
428 | struct panel_nv3051d *ctx = mipi_dsi_get_drvdata(dsi); |
429 | int ret; |
430 | |
431 | panel_nv3051d_shutdown(dsi); |
432 | |
433 | ret = mipi_dsi_detach(dsi); |
434 | if (ret < 0) |
435 | dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n" , ret); |
436 | |
437 | drm_panel_remove(panel: &ctx->panel); |
438 | } |
439 | |
440 | static const struct drm_display_mode nv3051d_rgxx3_modes[] = { |
441 | { /* 120hz */ |
442 | .hdisplay = 640, |
443 | .hsync_start = 640 + 40, |
444 | .hsync_end = 640 + 40 + 2, |
445 | .htotal = 640 + 40 + 2 + 80, |
446 | .vdisplay = 480, |
447 | .vsync_start = 480 + 18, |
448 | .vsync_end = 480 + 18 + 2, |
449 | .vtotal = 480 + 18 + 2 + 28, |
450 | .clock = 48300, |
451 | .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, |
452 | }, |
453 | { /* 100hz */ |
454 | .hdisplay = 640, |
455 | .hsync_start = 640 + 40, |
456 | .hsync_end = 640 + 40 + 2, |
457 | .htotal = 640 + 40 + 2 + 80, |
458 | .vdisplay = 480, |
459 | .vsync_start = 480 + 18, |
460 | .vsync_end = 480 + 18 + 2, |
461 | .vtotal = 480 + 18 + 2 + 28, |
462 | .clock = 40250, |
463 | .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, |
464 | }, |
465 | { /* 60hz */ |
466 | .hdisplay = 640, |
467 | .hsync_start = 640 + 40, |
468 | .hsync_end = 640 + 40 + 2, |
469 | .htotal = 640 + 40 + 2 + 80, |
470 | .vdisplay = 480, |
471 | .vsync_start = 480 + 18, |
472 | .vsync_end = 480 + 18 + 2, |
473 | .vtotal = 480 + 18 + 2 + 28, |
474 | .clock = 24150, |
475 | .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, |
476 | }, |
477 | }; |
478 | |
479 | static const struct drm_display_mode nv3051d_rk2023_modes[] = { |
480 | { |
481 | .hdisplay = 640, |
482 | .hsync_start = 640 + 40, |
483 | .hsync_end = 640 + 40 + 2, |
484 | .htotal = 640 + 40 + 2 + 80, |
485 | .vdisplay = 480, |
486 | .vsync_start = 480 + 18, |
487 | .vsync_end = 480 + 18 + 2, |
488 | .vtotal = 480 + 18 + 2 + 4, |
489 | .clock = 24150, |
490 | .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, |
491 | }, |
492 | }; |
493 | |
494 | static const struct nv3051d_panel_info nv3051d_rg351v_info = { |
495 | .display_modes = nv3051d_rgxx3_modes, |
496 | .num_modes = ARRAY_SIZE(nv3051d_rgxx3_modes), |
497 | .width_mm = 70, |
498 | .height_mm = 57, |
499 | .bus_flags = DRM_BUS_FLAG_DE_LOW | DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE, |
500 | .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | |
501 | MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_NO_EOT_PACKET | |
502 | MIPI_DSI_CLOCK_NON_CONTINUOUS, |
503 | }; |
504 | |
505 | static const struct nv3051d_panel_info nv3051d_rg353p_info = { |
506 | .display_modes = nv3051d_rgxx3_modes, |
507 | .num_modes = ARRAY_SIZE(nv3051d_rgxx3_modes), |
508 | .width_mm = 70, |
509 | .height_mm = 57, |
510 | .bus_flags = DRM_BUS_FLAG_DE_LOW | DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE, |
511 | .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | |
512 | MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_NO_EOT_PACKET, |
513 | }; |
514 | |
515 | static const struct nv3051d_panel_info nv3051d_rk2023_info = { |
516 | .display_modes = nv3051d_rk2023_modes, |
517 | .num_modes = ARRAY_SIZE(nv3051d_rk2023_modes), |
518 | .width_mm = 70, |
519 | .height_mm = 57, |
520 | .bus_flags = DRM_BUS_FLAG_DE_LOW | DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE, |
521 | .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | |
522 | MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_NO_EOT_PACKET, |
523 | }; |
524 | |
525 | static const struct of_device_id newvision_nv3051d_of_match[] = { |
526 | { .compatible = "anbernic,rg351v-panel" , .data = &nv3051d_rg351v_info }, |
527 | { .compatible = "anbernic,rg353p-panel" , .data = &nv3051d_rg353p_info }, |
528 | { .compatible = "powkiddy,rk2023-panel" , .data = &nv3051d_rk2023_info }, |
529 | { /* sentinel */ } |
530 | }; |
531 | MODULE_DEVICE_TABLE(of, newvision_nv3051d_of_match); |
532 | |
533 | static struct mipi_dsi_driver newvision_nv3051d_driver = { |
534 | .driver = { |
535 | .name = "panel-newvision-nv3051d" , |
536 | .of_match_table = newvision_nv3051d_of_match, |
537 | }, |
538 | .probe = panel_nv3051d_probe, |
539 | .remove = panel_nv3051d_remove, |
540 | .shutdown = panel_nv3051d_shutdown, |
541 | }; |
542 | module_mipi_dsi_driver(newvision_nv3051d_driver); |
543 | |
544 | MODULE_AUTHOR("Chris Morgan <macromorgan@hotmail.com>" ); |
545 | MODULE_DESCRIPTION("DRM driver for Newvision NV3051D based MIPI DSI panels" ); |
546 | MODULE_LICENSE("GPL" ); |
547 | |