1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Samsung S6D7AA0 MIPI-DSI TFT LCD controller drm_panel driver. |
4 | * |
5 | * Copyright (C) 2022 Artur Weber <aweber.kernel@gmail.com> |
6 | */ |
7 | |
8 | #include <linux/backlight.h> |
9 | #include <linux/delay.h> |
10 | #include <linux/gpio/consumer.h> |
11 | #include <linux/module.h> |
12 | #include <linux/regulator/consumer.h> |
13 | #include <linux/of.h> |
14 | |
15 | #include <video/mipi_display.h> |
16 | #include <drm/drm_mipi_dsi.h> |
17 | #include <drm/drm_modes.h> |
18 | #include <drm/drm_panel.h> |
19 | |
20 | /* Manufacturer command set */ |
21 | #define MCS_BL_CTL 0xc3 |
22 | #define MCS_OTP_RELOAD 0xd0 |
23 | #define MCS_PASSWD1 0xf0 |
24 | #define MCS_PASSWD2 0xf1 |
25 | #define MCS_PASSWD3 0xfc |
26 | |
27 | struct s6d7aa0 { |
28 | struct drm_panel panel; |
29 | struct mipi_dsi_device *dsi; |
30 | struct gpio_desc *reset_gpio; |
31 | struct regulator_bulk_data supplies[2]; |
32 | const struct s6d7aa0_panel_desc *desc; |
33 | }; |
34 | |
35 | struct s6d7aa0_panel_desc { |
36 | unsigned int panel_type; |
37 | int (*init_func)(struct s6d7aa0 *ctx); |
38 | int (*off_func)(struct s6d7aa0 *ctx); |
39 | const struct drm_display_mode *drm_mode; |
40 | unsigned long mode_flags; |
41 | u32 bus_flags; |
42 | bool has_backlight; |
43 | bool use_passwd3; |
44 | }; |
45 | |
46 | enum s6d7aa0_panels { |
47 | S6D7AA0_PANEL_LSL080AL02, |
48 | S6D7AA0_PANEL_LSL080AL03, |
49 | S6D7AA0_PANEL_LTL101AT01, |
50 | }; |
51 | |
52 | static inline struct s6d7aa0 *panel_to_s6d7aa0(struct drm_panel *panel) |
53 | { |
54 | return container_of(panel, struct s6d7aa0, panel); |
55 | } |
56 | |
57 | static void s6d7aa0_reset(struct s6d7aa0 *ctx) |
58 | { |
59 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 1); |
60 | msleep(msecs: 50); |
61 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 0); |
62 | msleep(msecs: 50); |
63 | } |
64 | |
65 | static int s6d7aa0_lock(struct s6d7aa0 *ctx, bool lock) |
66 | { |
67 | struct mipi_dsi_device *dsi = ctx->dsi; |
68 | |
69 | if (lock) { |
70 | mipi_dsi_dcs_write_seq(dsi, MCS_PASSWD1, 0xa5, 0xa5); |
71 | mipi_dsi_dcs_write_seq(dsi, MCS_PASSWD2, 0xa5, 0xa5); |
72 | if (ctx->desc->use_passwd3) |
73 | mipi_dsi_dcs_write_seq(dsi, MCS_PASSWD3, 0x5a, 0x5a); |
74 | } else { |
75 | mipi_dsi_dcs_write_seq(dsi, MCS_PASSWD1, 0x5a, 0x5a); |
76 | mipi_dsi_dcs_write_seq(dsi, MCS_PASSWD2, 0x5a, 0x5a); |
77 | if (ctx->desc->use_passwd3) |
78 | mipi_dsi_dcs_write_seq(dsi, MCS_PASSWD3, 0xa5, 0xa5); |
79 | } |
80 | |
81 | return 0; |
82 | } |
83 | |
84 | static int s6d7aa0_on(struct s6d7aa0 *ctx) |
85 | { |
86 | struct mipi_dsi_device *dsi = ctx->dsi; |
87 | struct device *dev = &dsi->dev; |
88 | int ret; |
89 | |
90 | ret = ctx->desc->init_func(ctx); |
91 | if (ret < 0) { |
92 | dev_err(dev, "Failed to initialize panel: %d\n" , ret); |
93 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 1); |
94 | return ret; |
95 | } |
96 | |
97 | ret = mipi_dsi_dcs_set_display_on(dsi); |
98 | if (ret < 0) { |
99 | dev_err(dev, "Failed to set display on: %d\n" , ret); |
100 | return ret; |
101 | } |
102 | |
103 | return 0; |
104 | } |
105 | |
106 | static int s6d7aa0_off(struct s6d7aa0 *ctx) |
107 | { |
108 | struct mipi_dsi_device *dsi = ctx->dsi; |
109 | struct device *dev = &dsi->dev; |
110 | int ret; |
111 | |
112 | ret = ctx->desc->off_func(ctx); |
113 | if (ret < 0) { |
114 | dev_err(dev, "Panel-specific off function failed: %d\n" , ret); |
115 | return ret; |
116 | } |
117 | |
118 | ret = mipi_dsi_dcs_set_display_off(dsi); |
119 | if (ret < 0) { |
120 | dev_err(dev, "Failed to set display off: %d\n" , ret); |
121 | return ret; |
122 | } |
123 | msleep(msecs: 64); |
124 | |
125 | ret = mipi_dsi_dcs_enter_sleep_mode(dsi); |
126 | if (ret < 0) { |
127 | dev_err(dev, "Failed to enter sleep mode: %d\n" , ret); |
128 | return ret; |
129 | } |
130 | msleep(msecs: 120); |
131 | |
132 | return 0; |
133 | } |
134 | |
135 | static int s6d7aa0_prepare(struct drm_panel *panel) |
136 | { |
137 | struct s6d7aa0 *ctx = panel_to_s6d7aa0(panel); |
138 | struct device *dev = &ctx->dsi->dev; |
139 | int ret; |
140 | |
141 | ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), consumers: ctx->supplies); |
142 | if (ret < 0) { |
143 | dev_err(dev, "Failed to enable regulators: %d\n" , ret); |
144 | return ret; |
145 | } |
146 | |
147 | s6d7aa0_reset(ctx); |
148 | |
149 | ret = s6d7aa0_on(ctx); |
150 | if (ret < 0) { |
151 | dev_err(dev, "Failed to initialize panel: %d\n" , ret); |
152 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 1); |
153 | return ret; |
154 | } |
155 | |
156 | return 0; |
157 | } |
158 | |
159 | static int s6d7aa0_disable(struct drm_panel *panel) |
160 | { |
161 | struct s6d7aa0 *ctx = panel_to_s6d7aa0(panel); |
162 | struct device *dev = &ctx->dsi->dev; |
163 | int ret; |
164 | |
165 | ret = s6d7aa0_off(ctx); |
166 | if (ret < 0) |
167 | dev_err(dev, "Failed to un-initialize panel: %d\n" , ret); |
168 | |
169 | return 0; |
170 | } |
171 | |
172 | static int s6d7aa0_unprepare(struct drm_panel *panel) |
173 | { |
174 | struct s6d7aa0 *ctx = panel_to_s6d7aa0(panel); |
175 | |
176 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 1); |
177 | regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), consumers: ctx->supplies); |
178 | |
179 | return 0; |
180 | } |
181 | |
182 | /* Backlight control code */ |
183 | |
184 | static int s6d7aa0_bl_update_status(struct backlight_device *bl) |
185 | { |
186 | struct mipi_dsi_device *dsi = bl_get_data(bl_dev: bl); |
187 | u16 brightness = backlight_get_brightness(bd: bl); |
188 | int ret; |
189 | |
190 | ret = mipi_dsi_dcs_set_display_brightness(dsi, brightness); |
191 | if (ret < 0) |
192 | return ret; |
193 | |
194 | return 0; |
195 | } |
196 | |
197 | static int s6d7aa0_bl_get_brightness(struct backlight_device *bl) |
198 | { |
199 | struct mipi_dsi_device *dsi = bl_get_data(bl_dev: bl); |
200 | u16 brightness; |
201 | int ret; |
202 | |
203 | ret = mipi_dsi_dcs_get_display_brightness(dsi, brightness: &brightness); |
204 | if (ret < 0) |
205 | return ret; |
206 | |
207 | return brightness & 0xff; |
208 | } |
209 | |
210 | static const struct backlight_ops s6d7aa0_bl_ops = { |
211 | .update_status = s6d7aa0_bl_update_status, |
212 | .get_brightness = s6d7aa0_bl_get_brightness, |
213 | }; |
214 | |
215 | static struct backlight_device * |
216 | s6d7aa0_create_backlight(struct mipi_dsi_device *dsi) |
217 | { |
218 | struct device *dev = &dsi->dev; |
219 | const struct backlight_properties props = { |
220 | .type = BACKLIGHT_RAW, |
221 | .brightness = 255, |
222 | .max_brightness = 255, |
223 | }; |
224 | |
225 | return devm_backlight_device_register(dev, name: dev_name(dev), parent: dev, devdata: dsi, |
226 | ops: &s6d7aa0_bl_ops, props: &props); |
227 | } |
228 | |
229 | /* Initialization code and structures for LSL080AL02 panel */ |
230 | |
231 | static int s6d7aa0_lsl080al02_init(struct s6d7aa0 *ctx) |
232 | { |
233 | struct mipi_dsi_device *dsi = ctx->dsi; |
234 | struct device *dev = &dsi->dev; |
235 | int ret; |
236 | |
237 | usleep_range(min: 20000, max: 25000); |
238 | |
239 | ret = s6d7aa0_lock(ctx, lock: false); |
240 | if (ret < 0) { |
241 | dev_err(dev, "Failed to unlock registers: %d\n" , ret); |
242 | return ret; |
243 | } |
244 | |
245 | mipi_dsi_dcs_write_seq(dsi, MCS_OTP_RELOAD, 0x00, 0x10); |
246 | usleep_range(min: 1000, max: 1500); |
247 | |
248 | /* SEQ_B6_PARAM_8_R01 */ |
249 | mipi_dsi_dcs_write_seq(dsi, 0xb6, 0x10); |
250 | |
251 | /* BL_CTL_ON */ |
252 | mipi_dsi_dcs_write_seq(dsi, MCS_BL_CTL, 0x40, 0x00, 0x28); |
253 | |
254 | usleep_range(min: 5000, max: 6000); |
255 | |
256 | mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_ADDRESS_MODE, 0x04); |
257 | |
258 | ret = mipi_dsi_dcs_exit_sleep_mode(dsi); |
259 | if (ret < 0) { |
260 | dev_err(dev, "Failed to exit sleep mode: %d\n" , ret); |
261 | return ret; |
262 | } |
263 | |
264 | msleep(msecs: 120); |
265 | mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_ADDRESS_MODE, 0x00); |
266 | |
267 | ret = s6d7aa0_lock(ctx, lock: true); |
268 | if (ret < 0) { |
269 | dev_err(dev, "Failed to lock registers: %d\n" , ret); |
270 | return ret; |
271 | } |
272 | |
273 | ret = mipi_dsi_dcs_set_display_on(dsi); |
274 | if (ret < 0) { |
275 | dev_err(dev, "Failed to set display on: %d\n" , ret); |
276 | return ret; |
277 | } |
278 | |
279 | return 0; |
280 | } |
281 | |
282 | static int s6d7aa0_lsl080al02_off(struct s6d7aa0 *ctx) |
283 | { |
284 | struct mipi_dsi_device *dsi = ctx->dsi; |
285 | |
286 | /* BL_CTL_OFF */ |
287 | mipi_dsi_dcs_write_seq(dsi, MCS_BL_CTL, 0x40, 0x00, 0x20); |
288 | |
289 | return 0; |
290 | } |
291 | |
292 | static const struct drm_display_mode s6d7aa0_lsl080al02_mode = { |
293 | .clock = (800 + 16 + 4 + 140) * (1280 + 8 + 4 + 4) * 60 / 1000, |
294 | .hdisplay = 800, |
295 | .hsync_start = 800 + 16, |
296 | .hsync_end = 800 + 16 + 4, |
297 | .htotal = 800 + 16 + 4 + 140, |
298 | .vdisplay = 1280, |
299 | .vsync_start = 1280 + 8, |
300 | .vsync_end = 1280 + 8 + 4, |
301 | .vtotal = 1280 + 8 + 4 + 4, |
302 | .width_mm = 108, |
303 | .height_mm = 173, |
304 | }; |
305 | |
306 | static const struct s6d7aa0_panel_desc s6d7aa0_lsl080al02_desc = { |
307 | .panel_type = S6D7AA0_PANEL_LSL080AL02, |
308 | .init_func = s6d7aa0_lsl080al02_init, |
309 | .off_func = s6d7aa0_lsl080al02_off, |
310 | .drm_mode = &s6d7aa0_lsl080al02_mode, |
311 | .mode_flags = MIPI_DSI_MODE_VSYNC_FLUSH | MIPI_DSI_MODE_VIDEO_NO_HFP, |
312 | .bus_flags = 0, |
313 | |
314 | .has_backlight = false, |
315 | .use_passwd3 = false, |
316 | }; |
317 | |
318 | /* Initialization code and structures for LSL080AL03 panel */ |
319 | |
320 | static int s6d7aa0_lsl080al03_init(struct s6d7aa0 *ctx) |
321 | { |
322 | struct mipi_dsi_device *dsi = ctx->dsi; |
323 | struct device *dev = &dsi->dev; |
324 | int ret; |
325 | |
326 | usleep_range(min: 20000, max: 25000); |
327 | |
328 | ret = s6d7aa0_lock(ctx, lock: false); |
329 | if (ret < 0) { |
330 | dev_err(dev, "Failed to unlock registers: %d\n" , ret); |
331 | return ret; |
332 | } |
333 | |
334 | if (ctx->desc->panel_type == S6D7AA0_PANEL_LSL080AL03) { |
335 | mipi_dsi_dcs_write_seq(dsi, MCS_BL_CTL, 0xc7, 0x00, 0x29); |
336 | mipi_dsi_dcs_write_seq(dsi, 0xbc, 0x01, 0x4e, 0xa0); |
337 | mipi_dsi_dcs_write_seq(dsi, 0xfd, 0x16, 0x10, 0x11, 0x23, |
338 | 0x09); |
339 | mipi_dsi_dcs_write_seq(dsi, 0xfe, 0x00, 0x02, 0x03, 0x21, |
340 | 0x80, 0x78); |
341 | } else if (ctx->desc->panel_type == S6D7AA0_PANEL_LTL101AT01) { |
342 | mipi_dsi_dcs_write_seq(dsi, MCS_BL_CTL, 0x40, 0x00, 0x08); |
343 | mipi_dsi_dcs_write_seq(dsi, 0xbc, 0x01, 0x4e, 0x0b); |
344 | mipi_dsi_dcs_write_seq(dsi, 0xfd, 0x16, 0x10, 0x11, 0x23, |
345 | 0x09); |
346 | mipi_dsi_dcs_write_seq(dsi, 0xfe, 0x00, 0x02, 0x03, 0x21, |
347 | 0x80, 0x68); |
348 | } |
349 | |
350 | mipi_dsi_dcs_write_seq(dsi, 0xb3, 0x51); |
351 | mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x24); |
352 | mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x02, 0x08, 0x08); |
353 | |
354 | usleep_range(min: 10000, max: 11000); |
355 | |
356 | mipi_dsi_dcs_write_seq(dsi, 0xc0, 0x80, 0x80, 0x30); |
357 | mipi_dsi_dcs_write_seq(dsi, 0xcd, |
358 | 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, |
359 | 0x2e, 0x2e, 0x2e, 0x2e, 0x2e); |
360 | mipi_dsi_dcs_write_seq(dsi, 0xce, |
361 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
362 | 0x00, 0x00, 0x00, 0x00, 0x00); |
363 | mipi_dsi_dcs_write_seq(dsi, 0xc1, 0x03); |
364 | |
365 | ret = mipi_dsi_dcs_exit_sleep_mode(dsi); |
366 | if (ret < 0) { |
367 | dev_err(dev, "Failed to exit sleep mode: %d\n" , ret); |
368 | return ret; |
369 | } |
370 | |
371 | ret = s6d7aa0_lock(ctx, lock: true); |
372 | if (ret < 0) { |
373 | dev_err(dev, "Failed to lock registers: %d\n" , ret); |
374 | return ret; |
375 | } |
376 | |
377 | ret = mipi_dsi_dcs_set_display_on(dsi); |
378 | if (ret < 0) { |
379 | dev_err(dev, "Failed to set display on: %d\n" , ret); |
380 | return ret; |
381 | } |
382 | |
383 | return 0; |
384 | } |
385 | |
386 | static int s6d7aa0_lsl080al03_off(struct s6d7aa0 *ctx) |
387 | { |
388 | struct mipi_dsi_device *dsi = ctx->dsi; |
389 | |
390 | mipi_dsi_dcs_write_seq(dsi, 0x22, 0x00); |
391 | |
392 | return 0; |
393 | } |
394 | |
395 | static const struct drm_display_mode s6d7aa0_lsl080al03_mode = { |
396 | .clock = (768 + 18 + 16 + 126) * (1024 + 8 + 2 + 6) * 60 / 1000, |
397 | .hdisplay = 768, |
398 | .hsync_start = 768 + 18, |
399 | .hsync_end = 768 + 18 + 16, |
400 | .htotal = 768 + 18 + 16 + 126, |
401 | .vdisplay = 1024, |
402 | .vsync_start = 1024 + 8, |
403 | .vsync_end = 1024 + 8 + 2, |
404 | .vtotal = 1024 + 8 + 2 + 6, |
405 | .width_mm = 122, |
406 | .height_mm = 163, |
407 | }; |
408 | |
409 | static const struct s6d7aa0_panel_desc s6d7aa0_lsl080al03_desc = { |
410 | .panel_type = S6D7AA0_PANEL_LSL080AL03, |
411 | .init_func = s6d7aa0_lsl080al03_init, |
412 | .off_func = s6d7aa0_lsl080al03_off, |
413 | .drm_mode = &s6d7aa0_lsl080al03_mode, |
414 | .mode_flags = MIPI_DSI_MODE_NO_EOT_PACKET, |
415 | .bus_flags = 0, |
416 | |
417 | .has_backlight = true, |
418 | .use_passwd3 = true, |
419 | }; |
420 | |
421 | /* Initialization structures for LTL101AT01 panel */ |
422 | |
423 | static const struct drm_display_mode s6d7aa0_ltl101at01_mode = { |
424 | .clock = (768 + 96 + 16 + 184) * (1024 + 8 + 2 + 6) * 60 / 1000, |
425 | .hdisplay = 768, |
426 | .hsync_start = 768 + 96, |
427 | .hsync_end = 768 + 96 + 16, |
428 | .htotal = 768 + 96 + 16 + 184, |
429 | .vdisplay = 1024, |
430 | .vsync_start = 1024 + 8, |
431 | .vsync_end = 1024 + 8 + 2, |
432 | .vtotal = 1024 + 8 + 2 + 6, |
433 | .width_mm = 148, |
434 | .height_mm = 197, |
435 | }; |
436 | |
437 | static const struct s6d7aa0_panel_desc s6d7aa0_ltl101at01_desc = { |
438 | .panel_type = S6D7AA0_PANEL_LTL101AT01, |
439 | .init_func = s6d7aa0_lsl080al03_init, /* Similar init to LSL080AL03 */ |
440 | .off_func = s6d7aa0_lsl080al03_off, |
441 | .drm_mode = &s6d7aa0_ltl101at01_mode, |
442 | .mode_flags = MIPI_DSI_MODE_NO_EOT_PACKET, |
443 | .bus_flags = 0, |
444 | |
445 | .has_backlight = true, |
446 | .use_passwd3 = true, |
447 | }; |
448 | |
449 | static int s6d7aa0_get_modes(struct drm_panel *panel, |
450 | struct drm_connector *connector) |
451 | { |
452 | struct drm_display_mode *mode; |
453 | struct s6d7aa0 *ctx; |
454 | |
455 | ctx = container_of(panel, struct s6d7aa0, panel); |
456 | if (!ctx) |
457 | return -EINVAL; |
458 | |
459 | mode = drm_mode_duplicate(dev: connector->dev, mode: ctx->desc->drm_mode); |
460 | if (!mode) |
461 | return -ENOMEM; |
462 | |
463 | drm_mode_set_name(mode); |
464 | |
465 | mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; |
466 | connector->display_info.width_mm = mode->width_mm; |
467 | connector->display_info.height_mm = mode->height_mm; |
468 | connector->display_info.bus_flags = ctx->desc->bus_flags; |
469 | drm_mode_probed_add(connector, mode); |
470 | |
471 | return 1; |
472 | } |
473 | |
474 | static const struct drm_panel_funcs s6d7aa0_panel_funcs = { |
475 | .disable = s6d7aa0_disable, |
476 | .prepare = s6d7aa0_prepare, |
477 | .unprepare = s6d7aa0_unprepare, |
478 | .get_modes = s6d7aa0_get_modes, |
479 | }; |
480 | |
481 | static int s6d7aa0_probe(struct mipi_dsi_device *dsi) |
482 | { |
483 | struct device *dev = &dsi->dev; |
484 | struct s6d7aa0 *ctx; |
485 | int ret; |
486 | |
487 | ctx = devm_kzalloc(dev, size: sizeof(*ctx), GFP_KERNEL); |
488 | if (!ctx) |
489 | return -ENOMEM; |
490 | |
491 | ctx->desc = of_device_get_match_data(dev); |
492 | if (!ctx->desc) |
493 | return -ENODEV; |
494 | |
495 | ctx->supplies[0].supply = "power" ; |
496 | ctx->supplies[1].supply = "vmipi" ; |
497 | ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), |
498 | consumers: ctx->supplies); |
499 | if (ret < 0) |
500 | return dev_err_probe(dev, err: ret, fmt: "Failed to get regulators\n" ); |
501 | |
502 | ctx->reset_gpio = devm_gpiod_get(dev, con_id: "reset" , flags: GPIOD_OUT_HIGH); |
503 | if (IS_ERR(ptr: ctx->reset_gpio)) |
504 | return dev_err_probe(dev, err: PTR_ERR(ptr: ctx->reset_gpio), |
505 | fmt: "Failed to get reset-gpios\n" ); |
506 | |
507 | ctx->dsi = dsi; |
508 | mipi_dsi_set_drvdata(dsi, data: ctx); |
509 | |
510 | dsi->lanes = 4; |
511 | dsi->format = MIPI_DSI_FMT_RGB888; |
512 | dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
513 | | ctx->desc->mode_flags; |
514 | |
515 | drm_panel_init(panel: &ctx->panel, dev, funcs: &s6d7aa0_panel_funcs, |
516 | DRM_MODE_CONNECTOR_DSI); |
517 | ctx->panel.prepare_prev_first = true; |
518 | |
519 | ret = drm_panel_of_backlight(panel: &ctx->panel); |
520 | if (ret) |
521 | return dev_err_probe(dev, err: ret, fmt: "Failed to get backlight\n" ); |
522 | |
523 | /* Use DSI-based backlight as fallback if available */ |
524 | if (ctx->desc->has_backlight && !ctx->panel.backlight) { |
525 | ctx->panel.backlight = s6d7aa0_create_backlight(dsi); |
526 | if (IS_ERR(ptr: ctx->panel.backlight)) |
527 | return dev_err_probe(dev, err: PTR_ERR(ptr: ctx->panel.backlight), |
528 | fmt: "Failed to create backlight\n" ); |
529 | } |
530 | |
531 | drm_panel_add(panel: &ctx->panel); |
532 | |
533 | ret = mipi_dsi_attach(dsi); |
534 | if (ret < 0) { |
535 | dev_err(dev, "Failed to attach to DSI host: %d\n" , ret); |
536 | drm_panel_remove(panel: &ctx->panel); |
537 | return ret; |
538 | } |
539 | |
540 | return 0; |
541 | } |
542 | |
543 | static void s6d7aa0_remove(struct mipi_dsi_device *dsi) |
544 | { |
545 | struct s6d7aa0 *ctx = mipi_dsi_get_drvdata(dsi); |
546 | int ret; |
547 | |
548 | ret = mipi_dsi_detach(dsi); |
549 | if (ret < 0) |
550 | dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n" , ret); |
551 | |
552 | drm_panel_remove(panel: &ctx->panel); |
553 | } |
554 | |
555 | static const struct of_device_id s6d7aa0_of_match[] = { |
556 | { |
557 | .compatible = "samsung,lsl080al02" , |
558 | .data = &s6d7aa0_lsl080al02_desc |
559 | }, |
560 | { |
561 | .compatible = "samsung,lsl080al03" , |
562 | .data = &s6d7aa0_lsl080al03_desc |
563 | }, |
564 | { |
565 | .compatible = "samsung,ltl101at01" , |
566 | .data = &s6d7aa0_ltl101at01_desc |
567 | }, |
568 | { /* sentinel */ } |
569 | }; |
570 | MODULE_DEVICE_TABLE(of, s6d7aa0_of_match); |
571 | |
572 | static struct mipi_dsi_driver s6d7aa0_driver = { |
573 | .probe = s6d7aa0_probe, |
574 | .remove = s6d7aa0_remove, |
575 | .driver = { |
576 | .name = "panel-samsung-s6d7aa0" , |
577 | .of_match_table = s6d7aa0_of_match, |
578 | }, |
579 | }; |
580 | module_mipi_dsi_driver(s6d7aa0_driver); |
581 | |
582 | MODULE_AUTHOR("Artur Weber <aweber.kernel@gmail.com>" ); |
583 | MODULE_DESCRIPTION("Samsung S6D7AA0 MIPI-DSI LCD controller driver" ); |
584 | MODULE_LICENSE("GPL" ); |
585 | |