1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Raydium RM67191 MIPI-DSI panel driver |
4 | * |
5 | * Copyright 2019 NXP |
6 | */ |
7 | |
8 | #include <linux/backlight.h> |
9 | #include <linux/delay.h> |
10 | #include <linux/gpio/consumer.h> |
11 | #include <linux/media-bus-format.h> |
12 | #include <linux/module.h> |
13 | #include <linux/of.h> |
14 | #include <linux/regulator/consumer.h> |
15 | |
16 | #include <video/mipi_display.h> |
17 | #include <video/of_videomode.h> |
18 | #include <video/videomode.h> |
19 | |
20 | #include <drm/drm_crtc.h> |
21 | #include <drm/drm_mipi_dsi.h> |
22 | #include <drm/drm_panel.h> |
23 | |
24 | /* Panel specific color-format bits */ |
25 | #define COL_FMT_16BPP 0x55 |
26 | #define COL_FMT_18BPP 0x66 |
27 | #define COL_FMT_24BPP 0x77 |
28 | |
29 | /* Write Manufacture Command Set Control */ |
30 | #define WRMAUCCTR 0xFE |
31 | |
32 | /* Manufacturer Command Set pages (CMD2) */ |
33 | struct cmd_set_entry { |
34 | u8 cmd; |
35 | u8 param; |
36 | }; |
37 | |
38 | /* |
39 | * There is no description in the Reference Manual about these commands. |
40 | * We received them from vendor, so just use them as is. |
41 | */ |
42 | static const struct cmd_set_entry manufacturer_cmd_set[] = { |
43 | {0xFE, 0x0B}, |
44 | {0x28, 0x40}, |
45 | {0x29, 0x4F}, |
46 | {0xFE, 0x0E}, |
47 | {0x4B, 0x00}, |
48 | {0x4C, 0x0F}, |
49 | {0x4D, 0x20}, |
50 | {0x4E, 0x40}, |
51 | {0x4F, 0x60}, |
52 | {0x50, 0xA0}, |
53 | {0x51, 0xC0}, |
54 | {0x52, 0xE0}, |
55 | {0x53, 0xFF}, |
56 | {0xFE, 0x0D}, |
57 | {0x18, 0x08}, |
58 | {0x42, 0x00}, |
59 | {0x08, 0x41}, |
60 | {0x46, 0x02}, |
61 | {0x72, 0x09}, |
62 | {0xFE, 0x0A}, |
63 | {0x24, 0x17}, |
64 | {0x04, 0x07}, |
65 | {0x1A, 0x0C}, |
66 | {0x0F, 0x44}, |
67 | {0xFE, 0x04}, |
68 | {0x00, 0x0C}, |
69 | {0x05, 0x08}, |
70 | {0x06, 0x08}, |
71 | {0x08, 0x08}, |
72 | {0x09, 0x08}, |
73 | {0x0A, 0xE6}, |
74 | {0x0B, 0x8C}, |
75 | {0x1A, 0x12}, |
76 | {0x1E, 0xE0}, |
77 | {0x29, 0x93}, |
78 | {0x2A, 0x93}, |
79 | {0x2F, 0x02}, |
80 | {0x31, 0x02}, |
81 | {0x33, 0x05}, |
82 | {0x37, 0x2D}, |
83 | {0x38, 0x2D}, |
84 | {0x3A, 0x1E}, |
85 | {0x3B, 0x1E}, |
86 | {0x3D, 0x27}, |
87 | {0x3F, 0x80}, |
88 | {0x40, 0x40}, |
89 | {0x41, 0xE0}, |
90 | {0x4F, 0x2F}, |
91 | {0x50, 0x1E}, |
92 | {0xFE, 0x06}, |
93 | {0x00, 0xCC}, |
94 | {0x05, 0x05}, |
95 | {0x07, 0xA2}, |
96 | {0x08, 0xCC}, |
97 | {0x0D, 0x03}, |
98 | {0x0F, 0xA2}, |
99 | {0x32, 0xCC}, |
100 | {0x37, 0x05}, |
101 | {0x39, 0x83}, |
102 | {0x3A, 0xCC}, |
103 | {0x41, 0x04}, |
104 | {0x43, 0x83}, |
105 | {0x44, 0xCC}, |
106 | {0x49, 0x05}, |
107 | {0x4B, 0xA2}, |
108 | {0x4C, 0xCC}, |
109 | {0x51, 0x03}, |
110 | {0x53, 0xA2}, |
111 | {0x75, 0xCC}, |
112 | {0x7A, 0x03}, |
113 | {0x7C, 0x83}, |
114 | {0x7D, 0xCC}, |
115 | {0x82, 0x02}, |
116 | {0x84, 0x83}, |
117 | {0x85, 0xEC}, |
118 | {0x86, 0x0F}, |
119 | {0x87, 0xFF}, |
120 | {0x88, 0x00}, |
121 | {0x8A, 0x02}, |
122 | {0x8C, 0xA2}, |
123 | {0x8D, 0xEA}, |
124 | {0x8E, 0x01}, |
125 | {0x8F, 0xE8}, |
126 | {0xFE, 0x06}, |
127 | {0x90, 0x0A}, |
128 | {0x92, 0x06}, |
129 | {0x93, 0xA0}, |
130 | {0x94, 0xA8}, |
131 | {0x95, 0xEC}, |
132 | {0x96, 0x0F}, |
133 | {0x97, 0xFF}, |
134 | {0x98, 0x00}, |
135 | {0x9A, 0x02}, |
136 | {0x9C, 0xA2}, |
137 | {0xAC, 0x04}, |
138 | {0xFE, 0x06}, |
139 | {0xB1, 0x12}, |
140 | {0xB2, 0x17}, |
141 | {0xB3, 0x17}, |
142 | {0xB4, 0x17}, |
143 | {0xB5, 0x17}, |
144 | {0xB6, 0x11}, |
145 | {0xB7, 0x08}, |
146 | {0xB8, 0x09}, |
147 | {0xB9, 0x06}, |
148 | {0xBA, 0x07}, |
149 | {0xBB, 0x17}, |
150 | {0xBC, 0x17}, |
151 | {0xBD, 0x17}, |
152 | {0xBE, 0x17}, |
153 | {0xBF, 0x17}, |
154 | {0xC0, 0x17}, |
155 | {0xC1, 0x17}, |
156 | {0xC2, 0x17}, |
157 | {0xC3, 0x17}, |
158 | {0xC4, 0x0F}, |
159 | {0xC5, 0x0E}, |
160 | {0xC6, 0x00}, |
161 | {0xC7, 0x01}, |
162 | {0xC8, 0x10}, |
163 | {0xFE, 0x06}, |
164 | {0x95, 0xEC}, |
165 | {0x8D, 0xEE}, |
166 | {0x44, 0xEC}, |
167 | {0x4C, 0xEC}, |
168 | {0x32, 0xEC}, |
169 | {0x3A, 0xEC}, |
170 | {0x7D, 0xEC}, |
171 | {0x75, 0xEC}, |
172 | {0x00, 0xEC}, |
173 | {0x08, 0xEC}, |
174 | {0x85, 0xEC}, |
175 | {0xA6, 0x21}, |
176 | {0xA7, 0x05}, |
177 | {0xA9, 0x06}, |
178 | {0x82, 0x06}, |
179 | {0x41, 0x06}, |
180 | {0x7A, 0x07}, |
181 | {0x37, 0x07}, |
182 | {0x05, 0x06}, |
183 | {0x49, 0x06}, |
184 | {0x0D, 0x04}, |
185 | {0x51, 0x04}, |
186 | }; |
187 | |
188 | static const u32 rad_bus_formats[] = { |
189 | MEDIA_BUS_FMT_RGB888_1X24, |
190 | MEDIA_BUS_FMT_RGB666_1X18, |
191 | MEDIA_BUS_FMT_RGB565_1X16, |
192 | }; |
193 | |
194 | static const u32 rad_bus_flags = DRM_BUS_FLAG_DE_LOW | |
195 | DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE; |
196 | |
197 | struct rad_panel { |
198 | struct drm_panel panel; |
199 | struct mipi_dsi_device *dsi; |
200 | |
201 | struct gpio_desc *reset; |
202 | struct backlight_device *backlight; |
203 | |
204 | struct regulator_bulk_data *supplies; |
205 | unsigned int num_supplies; |
206 | |
207 | bool prepared; |
208 | bool enabled; |
209 | }; |
210 | |
211 | static const struct drm_display_mode default_mode = { |
212 | .clock = 132000, |
213 | .hdisplay = 1080, |
214 | .hsync_start = 1080 + 20, |
215 | .hsync_end = 1080 + 20 + 2, |
216 | .htotal = 1080 + 20 + 2 + 34, |
217 | .vdisplay = 1920, |
218 | .vsync_start = 1920 + 10, |
219 | .vsync_end = 1920 + 10 + 2, |
220 | .vtotal = 1920 + 10 + 2 + 4, |
221 | .width_mm = 68, |
222 | .height_mm = 121, |
223 | .flags = DRM_MODE_FLAG_NHSYNC | |
224 | DRM_MODE_FLAG_NVSYNC, |
225 | }; |
226 | |
227 | static inline struct rad_panel *to_rad_panel(struct drm_panel *panel) |
228 | { |
229 | return container_of(panel, struct rad_panel, panel); |
230 | } |
231 | |
232 | static int rad_panel_push_cmd_list(struct mipi_dsi_device *dsi) |
233 | { |
234 | size_t i; |
235 | size_t count = ARRAY_SIZE(manufacturer_cmd_set); |
236 | int ret = 0; |
237 | |
238 | for (i = 0; i < count; i++) { |
239 | const struct cmd_set_entry *entry = &manufacturer_cmd_set[i]; |
240 | u8 buffer[2] = { entry->cmd, entry->param }; |
241 | |
242 | ret = mipi_dsi_generic_write(dsi, payload: &buffer, size: sizeof(buffer)); |
243 | if (ret < 0) |
244 | return ret; |
245 | } |
246 | |
247 | return ret; |
248 | }; |
249 | |
250 | static int color_format_from_dsi_format(enum mipi_dsi_pixel_format format) |
251 | { |
252 | switch (format) { |
253 | case MIPI_DSI_FMT_RGB565: |
254 | return COL_FMT_16BPP; |
255 | case MIPI_DSI_FMT_RGB666: |
256 | case MIPI_DSI_FMT_RGB666_PACKED: |
257 | return COL_FMT_18BPP; |
258 | case MIPI_DSI_FMT_RGB888: |
259 | return COL_FMT_24BPP; |
260 | default: |
261 | return COL_FMT_24BPP; /* for backward compatibility */ |
262 | } |
263 | }; |
264 | |
265 | static int rad_panel_prepare(struct drm_panel *panel) |
266 | { |
267 | struct rad_panel *rad = to_rad_panel(panel); |
268 | int ret; |
269 | |
270 | if (rad->prepared) |
271 | return 0; |
272 | |
273 | ret = regulator_bulk_enable(num_consumers: rad->num_supplies, consumers: rad->supplies); |
274 | if (ret) |
275 | return ret; |
276 | |
277 | if (rad->reset) { |
278 | gpiod_set_value_cansleep(desc: rad->reset, value: 1); |
279 | usleep_range(min: 3000, max: 5000); |
280 | gpiod_set_value_cansleep(desc: rad->reset, value: 0); |
281 | usleep_range(min: 18000, max: 20000); |
282 | } |
283 | |
284 | rad->prepared = true; |
285 | |
286 | return 0; |
287 | } |
288 | |
289 | static int rad_panel_unprepare(struct drm_panel *panel) |
290 | { |
291 | struct rad_panel *rad = to_rad_panel(panel); |
292 | int ret; |
293 | |
294 | if (!rad->prepared) |
295 | return 0; |
296 | |
297 | /* |
298 | * Right after asserting the reset, we need to release it, so that the |
299 | * touch driver can have an active connection with the touch controller |
300 | * even after the display is turned off. |
301 | */ |
302 | if (rad->reset) { |
303 | gpiod_set_value_cansleep(desc: rad->reset, value: 1); |
304 | usleep_range(min: 15000, max: 17000); |
305 | gpiod_set_value_cansleep(desc: rad->reset, value: 0); |
306 | } |
307 | |
308 | ret = regulator_bulk_disable(num_consumers: rad->num_supplies, consumers: rad->supplies); |
309 | if (ret) |
310 | return ret; |
311 | |
312 | rad->prepared = false; |
313 | |
314 | return 0; |
315 | } |
316 | |
317 | static int rad_panel_enable(struct drm_panel *panel) |
318 | { |
319 | struct rad_panel *rad = to_rad_panel(panel); |
320 | struct mipi_dsi_device *dsi = rad->dsi; |
321 | struct device *dev = &dsi->dev; |
322 | int color_format = color_format_from_dsi_format(format: dsi->format); |
323 | int ret; |
324 | |
325 | if (rad->enabled) |
326 | return 0; |
327 | |
328 | dsi->mode_flags |= MIPI_DSI_MODE_LPM; |
329 | |
330 | ret = rad_panel_push_cmd_list(dsi); |
331 | if (ret < 0) { |
332 | dev_err(dev, "Failed to send MCS (%d)\n" , ret); |
333 | goto fail; |
334 | } |
335 | |
336 | /* Select User Command Set table (CMD1) */ |
337 | ret = mipi_dsi_generic_write(dsi, payload: (u8[]){ WRMAUCCTR, 0x00 }, size: 2); |
338 | if (ret < 0) |
339 | goto fail; |
340 | |
341 | /* Software reset */ |
342 | ret = mipi_dsi_dcs_soft_reset(dsi); |
343 | if (ret < 0) { |
344 | dev_err(dev, "Failed to do Software Reset (%d)\n" , ret); |
345 | goto fail; |
346 | } |
347 | |
348 | usleep_range(min: 15000, max: 17000); |
349 | |
350 | /* Set DSI mode */ |
351 | ret = mipi_dsi_generic_write(dsi, payload: (u8[]){ 0xC2, 0x0B }, size: 2); |
352 | if (ret < 0) { |
353 | dev_err(dev, "Failed to set DSI mode (%d)\n" , ret); |
354 | goto fail; |
355 | } |
356 | /* Set tear ON */ |
357 | ret = mipi_dsi_dcs_set_tear_on(dsi, mode: MIPI_DSI_DCS_TEAR_MODE_VBLANK); |
358 | if (ret < 0) { |
359 | dev_err(dev, "Failed to set tear ON (%d)\n" , ret); |
360 | goto fail; |
361 | } |
362 | /* Set tear scanline */ |
363 | ret = mipi_dsi_dcs_set_tear_scanline(dsi, scanline: 0x380); |
364 | if (ret < 0) { |
365 | dev_err(dev, "Failed to set tear scanline (%d)\n" , ret); |
366 | goto fail; |
367 | } |
368 | /* Set pixel format */ |
369 | ret = mipi_dsi_dcs_set_pixel_format(dsi, format: color_format); |
370 | dev_dbg(dev, "Interface color format set to 0x%x\n" , color_format); |
371 | if (ret < 0) { |
372 | dev_err(dev, "Failed to set pixel format (%d)\n" , ret); |
373 | goto fail; |
374 | } |
375 | /* Exit sleep mode */ |
376 | ret = mipi_dsi_dcs_exit_sleep_mode(dsi); |
377 | if (ret < 0) { |
378 | dev_err(dev, "Failed to exit sleep mode (%d)\n" , ret); |
379 | goto fail; |
380 | } |
381 | |
382 | usleep_range(min: 5000, max: 7000); |
383 | |
384 | ret = mipi_dsi_dcs_set_display_on(dsi); |
385 | if (ret < 0) { |
386 | dev_err(dev, "Failed to set display ON (%d)\n" , ret); |
387 | goto fail; |
388 | } |
389 | |
390 | backlight_enable(bd: rad->backlight); |
391 | |
392 | rad->enabled = true; |
393 | |
394 | return 0; |
395 | |
396 | fail: |
397 | gpiod_set_value_cansleep(desc: rad->reset, value: 1); |
398 | |
399 | return ret; |
400 | } |
401 | |
402 | static int rad_panel_disable(struct drm_panel *panel) |
403 | { |
404 | struct rad_panel *rad = to_rad_panel(panel); |
405 | struct mipi_dsi_device *dsi = rad->dsi; |
406 | struct device *dev = &dsi->dev; |
407 | int ret; |
408 | |
409 | if (!rad->enabled) |
410 | return 0; |
411 | |
412 | dsi->mode_flags |= MIPI_DSI_MODE_LPM; |
413 | |
414 | backlight_disable(bd: rad->backlight); |
415 | |
416 | usleep_range(min: 10000, max: 12000); |
417 | |
418 | ret = mipi_dsi_dcs_set_display_off(dsi); |
419 | if (ret < 0) { |
420 | dev_err(dev, "Failed to set display OFF (%d)\n" , ret); |
421 | return ret; |
422 | } |
423 | |
424 | usleep_range(min: 5000, max: 10000); |
425 | |
426 | ret = mipi_dsi_dcs_enter_sleep_mode(dsi); |
427 | if (ret < 0) { |
428 | dev_err(dev, "Failed to enter sleep mode (%d)\n" , ret); |
429 | return ret; |
430 | } |
431 | |
432 | rad->enabled = false; |
433 | |
434 | return 0; |
435 | } |
436 | |
437 | static int rad_panel_get_modes(struct drm_panel *panel, |
438 | struct drm_connector *connector) |
439 | { |
440 | struct drm_display_mode *mode; |
441 | |
442 | mode = drm_mode_duplicate(dev: connector->dev, mode: &default_mode); |
443 | if (!mode) { |
444 | dev_err(panel->dev, "failed to add mode %ux%u@%u\n" , |
445 | default_mode.hdisplay, default_mode.vdisplay, |
446 | drm_mode_vrefresh(&default_mode)); |
447 | return -ENOMEM; |
448 | } |
449 | |
450 | drm_mode_set_name(mode); |
451 | mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; |
452 | drm_mode_probed_add(connector, mode); |
453 | |
454 | connector->display_info.width_mm = mode->width_mm; |
455 | connector->display_info.height_mm = mode->height_mm; |
456 | connector->display_info.bus_flags = rad_bus_flags; |
457 | |
458 | drm_display_info_set_bus_formats(info: &connector->display_info, |
459 | formats: rad_bus_formats, |
460 | ARRAY_SIZE(rad_bus_formats)); |
461 | return 1; |
462 | } |
463 | |
464 | static int rad_bl_get_brightness(struct backlight_device *bl) |
465 | { |
466 | struct mipi_dsi_device *dsi = bl_get_data(bl_dev: bl); |
467 | struct rad_panel *rad = mipi_dsi_get_drvdata(dsi); |
468 | u16 brightness; |
469 | int ret; |
470 | |
471 | if (!rad->prepared) |
472 | return 0; |
473 | |
474 | dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; |
475 | |
476 | ret = mipi_dsi_dcs_get_display_brightness(dsi, brightness: &brightness); |
477 | if (ret < 0) |
478 | return ret; |
479 | |
480 | bl->props.brightness = brightness; |
481 | |
482 | return brightness & 0xff; |
483 | } |
484 | |
485 | static int rad_bl_update_status(struct backlight_device *bl) |
486 | { |
487 | struct mipi_dsi_device *dsi = bl_get_data(bl_dev: bl); |
488 | struct rad_panel *rad = mipi_dsi_get_drvdata(dsi); |
489 | int ret = 0; |
490 | |
491 | if (!rad->prepared) |
492 | return 0; |
493 | |
494 | dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; |
495 | |
496 | ret = mipi_dsi_dcs_set_display_brightness(dsi, brightness: bl->props.brightness); |
497 | if (ret < 0) |
498 | return ret; |
499 | |
500 | return 0; |
501 | } |
502 | |
503 | static const struct backlight_ops rad_bl_ops = { |
504 | .update_status = rad_bl_update_status, |
505 | .get_brightness = rad_bl_get_brightness, |
506 | }; |
507 | |
508 | static const struct drm_panel_funcs rad_panel_funcs = { |
509 | .prepare = rad_panel_prepare, |
510 | .unprepare = rad_panel_unprepare, |
511 | .enable = rad_panel_enable, |
512 | .disable = rad_panel_disable, |
513 | .get_modes = rad_panel_get_modes, |
514 | }; |
515 | |
516 | static const char * const rad_supply_names[] = { |
517 | "v3p3" , |
518 | "v1p8" , |
519 | }; |
520 | |
521 | static int rad_init_regulators(struct rad_panel *rad) |
522 | { |
523 | struct device *dev = &rad->dsi->dev; |
524 | int i; |
525 | |
526 | rad->num_supplies = ARRAY_SIZE(rad_supply_names); |
527 | rad->supplies = devm_kcalloc(dev, n: rad->num_supplies, |
528 | size: sizeof(*rad->supplies), GFP_KERNEL); |
529 | if (!rad->supplies) |
530 | return -ENOMEM; |
531 | |
532 | for (i = 0; i < rad->num_supplies; i++) |
533 | rad->supplies[i].supply = rad_supply_names[i]; |
534 | |
535 | return devm_regulator_bulk_get(dev, num_consumers: rad->num_supplies, consumers: rad->supplies); |
536 | }; |
537 | |
538 | static int rad_panel_probe(struct mipi_dsi_device *dsi) |
539 | { |
540 | struct device *dev = &dsi->dev; |
541 | struct device_node *np = dev->of_node; |
542 | struct rad_panel *panel; |
543 | struct backlight_properties bl_props; |
544 | int ret; |
545 | u32 video_mode; |
546 | |
547 | panel = devm_kzalloc(dev: &dsi->dev, size: sizeof(*panel), GFP_KERNEL); |
548 | if (!panel) |
549 | return -ENOMEM; |
550 | |
551 | mipi_dsi_set_drvdata(dsi, data: panel); |
552 | |
553 | panel->dsi = dsi; |
554 | |
555 | dsi->format = MIPI_DSI_FMT_RGB888; |
556 | dsi->mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO; |
557 | |
558 | ret = of_property_read_u32(np, propname: "video-mode" , out_value: &video_mode); |
559 | if (!ret) { |
560 | switch (video_mode) { |
561 | case 0: |
562 | /* burst mode */ |
563 | dsi->mode_flags |= MIPI_DSI_MODE_VIDEO_BURST; |
564 | break; |
565 | case 1: |
566 | /* non-burst mode with sync event */ |
567 | break; |
568 | case 2: |
569 | /* non-burst mode with sync pulse */ |
570 | dsi->mode_flags |= MIPI_DSI_MODE_VIDEO_SYNC_PULSE; |
571 | break; |
572 | default: |
573 | dev_warn(dev, "invalid video mode %d\n" , video_mode); |
574 | break; |
575 | } |
576 | } |
577 | |
578 | ret = of_property_read_u32(np, propname: "dsi-lanes" , out_value: &dsi->lanes); |
579 | if (ret) { |
580 | dev_err(dev, "Failed to get dsi-lanes property (%d)\n" , ret); |
581 | return ret; |
582 | } |
583 | |
584 | panel->reset = devm_gpiod_get_optional(dev, con_id: "reset" , flags: GPIOD_OUT_LOW); |
585 | if (IS_ERR(ptr: panel->reset)) |
586 | return PTR_ERR(ptr: panel->reset); |
587 | |
588 | memset(&bl_props, 0, sizeof(bl_props)); |
589 | bl_props.type = BACKLIGHT_RAW; |
590 | bl_props.brightness = 255; |
591 | bl_props.max_brightness = 255; |
592 | |
593 | panel->backlight = devm_backlight_device_register(dev, name: dev_name(dev), |
594 | parent: dev, devdata: dsi, ops: &rad_bl_ops, |
595 | props: &bl_props); |
596 | if (IS_ERR(ptr: panel->backlight)) { |
597 | ret = PTR_ERR(ptr: panel->backlight); |
598 | dev_err(dev, "Failed to register backlight (%d)\n" , ret); |
599 | return ret; |
600 | } |
601 | |
602 | ret = rad_init_regulators(rad: panel); |
603 | if (ret) |
604 | return ret; |
605 | |
606 | drm_panel_init(panel: &panel->panel, dev, funcs: &rad_panel_funcs, |
607 | DRM_MODE_CONNECTOR_DSI); |
608 | dev_set_drvdata(dev, data: panel); |
609 | |
610 | drm_panel_add(panel: &panel->panel); |
611 | |
612 | ret = mipi_dsi_attach(dsi); |
613 | if (ret) |
614 | drm_panel_remove(panel: &panel->panel); |
615 | |
616 | return ret; |
617 | } |
618 | |
619 | static void rad_panel_remove(struct mipi_dsi_device *dsi) |
620 | { |
621 | struct rad_panel *rad = mipi_dsi_get_drvdata(dsi); |
622 | struct device *dev = &dsi->dev; |
623 | int ret; |
624 | |
625 | ret = mipi_dsi_detach(dsi); |
626 | if (ret) |
627 | dev_err(dev, "Failed to detach from host (%d)\n" , ret); |
628 | |
629 | drm_panel_remove(panel: &rad->panel); |
630 | } |
631 | |
632 | static void rad_panel_shutdown(struct mipi_dsi_device *dsi) |
633 | { |
634 | struct rad_panel *rad = mipi_dsi_get_drvdata(dsi); |
635 | |
636 | rad_panel_disable(panel: &rad->panel); |
637 | rad_panel_unprepare(panel: &rad->panel); |
638 | } |
639 | |
640 | static const struct of_device_id rad_of_match[] = { |
641 | { .compatible = "raydium,rm67191" , }, |
642 | { /* sentinel */ } |
643 | }; |
644 | MODULE_DEVICE_TABLE(of, rad_of_match); |
645 | |
646 | static struct mipi_dsi_driver rad_panel_driver = { |
647 | .driver = { |
648 | .name = "panel-raydium-rm67191" , |
649 | .of_match_table = rad_of_match, |
650 | }, |
651 | .probe = rad_panel_probe, |
652 | .remove = rad_panel_remove, |
653 | .shutdown = rad_panel_shutdown, |
654 | }; |
655 | module_mipi_dsi_driver(rad_panel_driver); |
656 | |
657 | MODULE_AUTHOR("Robert Chiras <robert.chiras@nxp.com>" ); |
658 | MODULE_DESCRIPTION("DRM Driver for Raydium RM67191 MIPI DSI panel" ); |
659 | MODULE_LICENSE("GPL v2" ); |
660 | |