1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | // Copyright (c) 2023, Linaro Limited |
3 | |
4 | #include <linux/backlight.h> |
5 | #include <linux/delay.h> |
6 | #include <linux/gpio/consumer.h> |
7 | #include <linux/regulator/consumer.h> |
8 | #include <linux/module.h> |
9 | #include <linux/of.h> |
10 | |
11 | #include <drm/display/drm_dsc.h> |
12 | #include <drm/drm_mipi_dsi.h> |
13 | #include <drm/drm_modes.h> |
14 | #include <drm/drm_panel.h> |
15 | |
16 | #include <video/mipi_display.h> |
17 | |
18 | struct visionox_vtdr6130 { |
19 | struct drm_panel panel; |
20 | struct mipi_dsi_device *dsi; |
21 | struct gpio_desc *reset_gpio; |
22 | struct regulator_bulk_data supplies[3]; |
23 | }; |
24 | |
25 | static inline struct visionox_vtdr6130 *to_visionox_vtdr6130(struct drm_panel *panel) |
26 | { |
27 | return container_of(panel, struct visionox_vtdr6130, panel); |
28 | } |
29 | |
30 | static void visionox_vtdr6130_reset(struct visionox_vtdr6130 *ctx) |
31 | { |
32 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 0); |
33 | usleep_range(min: 10000, max: 11000); |
34 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 1); |
35 | usleep_range(min: 10000, max: 11000); |
36 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 0); |
37 | usleep_range(min: 10000, max: 11000); |
38 | } |
39 | |
40 | static int visionox_vtdr6130_on(struct visionox_vtdr6130 *ctx) |
41 | { |
42 | struct mipi_dsi_device *dsi = ctx->dsi; |
43 | struct device *dev = &dsi->dev; |
44 | int ret; |
45 | |
46 | dsi->mode_flags |= MIPI_DSI_MODE_LPM; |
47 | |
48 | ret = mipi_dsi_dcs_set_tear_on(dsi, mode: MIPI_DSI_DCS_TEAR_MODE_VBLANK); |
49 | if (ret) |
50 | return ret; |
51 | |
52 | mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x20); |
53 | mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0x00, 0x00); |
54 | mipi_dsi_dcs_write_seq(dsi, 0x59, 0x09); |
55 | mipi_dsi_dcs_write_seq(dsi, 0x6c, 0x01); |
56 | mipi_dsi_dcs_write_seq(dsi, 0x6d, 0x00); |
57 | mipi_dsi_dcs_write_seq(dsi, 0x6f, 0x01); |
58 | mipi_dsi_dcs_write_seq(dsi, 0x70, |
59 | 0x12, 0x00, 0x00, 0xab, 0x30, 0x80, 0x09, 0x60, 0x04, |
60 | 0x38, 0x00, 0x28, 0x02, 0x1c, 0x02, 0x1c, 0x02, 0x00, |
61 | 0x02, 0x0e, 0x00, 0x20, 0x03, 0xdd, 0x00, 0x07, 0x00, |
62 | 0x0c, 0x02, 0x77, 0x02, 0x8b, 0x18, 0x00, 0x10, 0xf0, |
63 | 0x07, 0x10, 0x20, 0x00, 0x06, 0x0f, 0x0f, 0x33, 0x0e, |
64 | 0x1c, 0x2a, 0x38, 0x46, 0x54, 0x62, 0x69, 0x70, 0x77, |
65 | 0x79, 0x7b, 0x7d, 0x7e, 0x02, 0x02, 0x22, 0x00, 0x2a, |
66 | 0x40, 0x2a, 0xbe, 0x3a, 0xfc, 0x3a, 0xfa, 0x3a, 0xf8, |
67 | 0x3b, 0x38, 0x3b, 0x78, 0x3b, 0xb6, 0x4b, 0xb6, 0x4b, |
68 | 0xf4, 0x4b, 0xf4, 0x6c, 0x34, 0x84, 0x74, 0x00, 0x00, |
69 | 0x00, 0x00, 0x00, 0x00); |
70 | mipi_dsi_dcs_write_seq(dsi, 0xf0, 0xaa, 0x10); |
71 | mipi_dsi_dcs_write_seq(dsi, 0xb1, |
72 | 0x01, 0x38, 0x00, 0x14, 0x00, 0x1c, 0x00, 0x01, 0x66, |
73 | 0x00, 0x14, 0x00, 0x14, 0x00, 0x01, 0x66, 0x00, 0x14, |
74 | 0x05, 0xcc, 0x00); |
75 | mipi_dsi_dcs_write_seq(dsi, 0xf0, 0xaa, 0x13); |
76 | mipi_dsi_dcs_write_seq(dsi, 0xce, |
77 | 0x09, 0x11, 0x09, 0x11, 0x08, 0xc1, 0x07, 0xfa, 0x05, |
78 | 0xa4, 0x00, 0x3c, 0x00, 0x34, 0x00, 0x24, 0x00, 0x0c, |
79 | 0x00, 0x0c, 0x04, 0x00, 0x35); |
80 | mipi_dsi_dcs_write_seq(dsi, 0xf0, 0xaa, 0x14); |
81 | mipi_dsi_dcs_write_seq(dsi, 0xb2, 0x03, 0x33); |
82 | mipi_dsi_dcs_write_seq(dsi, 0xb4, |
83 | 0x00, 0x33, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, |
84 | 0x3e, 0x00, 0x00); |
85 | mipi_dsi_dcs_write_seq(dsi, 0xb5, |
86 | 0x00, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x06, 0x01); |
87 | mipi_dsi_dcs_write_seq(dsi, 0xb9, 0x00, 0x00, 0x08, 0x09, 0x09, 0x09); |
88 | mipi_dsi_dcs_write_seq(dsi, 0xbc, |
89 | 0x10, 0x00, 0x00, 0x06, 0x11, 0x09, 0x3b, 0x09, 0x47, |
90 | 0x09, 0x47, 0x00); |
91 | mipi_dsi_dcs_write_seq(dsi, 0xbe, |
92 | 0x10, 0x10, 0x00, 0x08, 0x22, 0x09, 0x19, 0x09, 0x25, |
93 | 0x09, 0x25, 0x00); |
94 | mipi_dsi_dcs_write_seq(dsi, 0xff, 0x5a, 0x80); |
95 | mipi_dsi_dcs_write_seq(dsi, 0x65, 0x14); |
96 | mipi_dsi_dcs_write_seq(dsi, 0xfa, 0x08, 0x08, 0x08); |
97 | mipi_dsi_dcs_write_seq(dsi, 0xff, 0x5a, 0x81); |
98 | mipi_dsi_dcs_write_seq(dsi, 0x65, 0x05); |
99 | mipi_dsi_dcs_write_seq(dsi, 0xf3, 0x0f); |
100 | mipi_dsi_dcs_write_seq(dsi, 0xf0, 0xaa, 0x00); |
101 | mipi_dsi_dcs_write_seq(dsi, 0xff, 0x5a, 0x82); |
102 | mipi_dsi_dcs_write_seq(dsi, 0xf9, 0x00); |
103 | mipi_dsi_dcs_write_seq(dsi, 0xff, 0x51, 0x83); |
104 | mipi_dsi_dcs_write_seq(dsi, 0x65, 0x04); |
105 | mipi_dsi_dcs_write_seq(dsi, 0xf8, 0x00); |
106 | mipi_dsi_dcs_write_seq(dsi, 0xff, 0x5a, 0x00); |
107 | mipi_dsi_dcs_write_seq(dsi, 0x65, 0x01); |
108 | mipi_dsi_dcs_write_seq(dsi, 0xf4, 0x9a); |
109 | mipi_dsi_dcs_write_seq(dsi, 0xff, 0x5a, 0x00); |
110 | |
111 | ret = mipi_dsi_dcs_exit_sleep_mode(dsi); |
112 | if (ret < 0) { |
113 | dev_err(dev, "Failed to exit sleep mode: %d\n" , ret); |
114 | return ret; |
115 | } |
116 | msleep(msecs: 120); |
117 | |
118 | ret = mipi_dsi_dcs_set_display_on(dsi); |
119 | if (ret < 0) { |
120 | dev_err(dev, "Failed to set display on: %d\n" , ret); |
121 | return ret; |
122 | } |
123 | msleep(msecs: 20); |
124 | |
125 | return 0; |
126 | } |
127 | |
128 | static int visionox_vtdr6130_off(struct visionox_vtdr6130 *ctx) |
129 | { |
130 | struct mipi_dsi_device *dsi = ctx->dsi; |
131 | struct device *dev = &dsi->dev; |
132 | int ret; |
133 | |
134 | dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; |
135 | |
136 | ret = mipi_dsi_dcs_set_display_off(dsi); |
137 | if (ret < 0) { |
138 | dev_err(dev, "Failed to set display off: %d\n" , ret); |
139 | return ret; |
140 | } |
141 | msleep(msecs: 20); |
142 | |
143 | ret = mipi_dsi_dcs_enter_sleep_mode(dsi); |
144 | if (ret < 0) { |
145 | dev_err(dev, "Failed to enter sleep mode: %d\n" , ret); |
146 | return ret; |
147 | } |
148 | msleep(msecs: 120); |
149 | |
150 | return 0; |
151 | } |
152 | |
153 | static int visionox_vtdr6130_prepare(struct drm_panel *panel) |
154 | { |
155 | struct visionox_vtdr6130 *ctx = to_visionox_vtdr6130(panel); |
156 | struct device *dev = &ctx->dsi->dev; |
157 | int ret; |
158 | |
159 | ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), |
160 | consumers: ctx->supplies); |
161 | if (ret < 0) |
162 | return ret; |
163 | |
164 | visionox_vtdr6130_reset(ctx); |
165 | |
166 | ret = visionox_vtdr6130_on(ctx); |
167 | if (ret < 0) { |
168 | dev_err(dev, "Failed to initialize panel: %d\n" , ret); |
169 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 1); |
170 | regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), consumers: ctx->supplies); |
171 | return ret; |
172 | } |
173 | |
174 | return 0; |
175 | } |
176 | |
177 | static int visionox_vtdr6130_unprepare(struct drm_panel *panel) |
178 | { |
179 | struct visionox_vtdr6130 *ctx = to_visionox_vtdr6130(panel); |
180 | struct device *dev = &ctx->dsi->dev; |
181 | int ret; |
182 | |
183 | ret = visionox_vtdr6130_off(ctx); |
184 | if (ret < 0) |
185 | dev_err(dev, "Failed to un-initialize panel: %d\n" , ret); |
186 | |
187 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 1); |
188 | |
189 | regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), consumers: ctx->supplies); |
190 | |
191 | return 0; |
192 | } |
193 | |
194 | static const struct drm_display_mode visionox_vtdr6130_mode = { |
195 | .clock = (1080 + 20 + 2 + 20) * (2400 + 20 + 2 + 18) * 144 / 1000, |
196 | .hdisplay = 1080, |
197 | .hsync_start = 1080 + 20, |
198 | .hsync_end = 1080 + 20 + 2, |
199 | .htotal = 1080 + 20 + 2 + 20, |
200 | .vdisplay = 2400, |
201 | .vsync_start = 2400 + 20, |
202 | .vsync_end = 2400 + 20 + 2, |
203 | .vtotal = 2400 + 20 + 2 + 18, |
204 | .width_mm = 71, |
205 | .height_mm = 157, |
206 | }; |
207 | |
208 | static int visionox_vtdr6130_get_modes(struct drm_panel *panel, |
209 | struct drm_connector *connector) |
210 | { |
211 | struct drm_display_mode *mode; |
212 | |
213 | mode = drm_mode_duplicate(dev: connector->dev, mode: &visionox_vtdr6130_mode); |
214 | if (!mode) |
215 | return -ENOMEM; |
216 | |
217 | drm_mode_set_name(mode); |
218 | |
219 | mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; |
220 | connector->display_info.width_mm = mode->width_mm; |
221 | connector->display_info.height_mm = mode->height_mm; |
222 | drm_mode_probed_add(connector, mode); |
223 | |
224 | return 1; |
225 | } |
226 | |
227 | static const struct drm_panel_funcs visionox_vtdr6130_panel_funcs = { |
228 | .prepare = visionox_vtdr6130_prepare, |
229 | .unprepare = visionox_vtdr6130_unprepare, |
230 | .get_modes = visionox_vtdr6130_get_modes, |
231 | }; |
232 | |
233 | static int visionox_vtdr6130_bl_update_status(struct backlight_device *bl) |
234 | { |
235 | struct mipi_dsi_device *dsi = bl_get_data(bl_dev: bl); |
236 | u16 brightness = backlight_get_brightness(bd: bl); |
237 | |
238 | return mipi_dsi_dcs_set_display_brightness_large(dsi, brightness); |
239 | } |
240 | |
241 | static const struct backlight_ops visionox_vtdr6130_bl_ops = { |
242 | .update_status = visionox_vtdr6130_bl_update_status, |
243 | }; |
244 | |
245 | static struct backlight_device * |
246 | visionox_vtdr6130_create_backlight(struct mipi_dsi_device *dsi) |
247 | { |
248 | struct device *dev = &dsi->dev; |
249 | const struct backlight_properties props = { |
250 | .type = BACKLIGHT_RAW, |
251 | .brightness = 4095, |
252 | .max_brightness = 4095, |
253 | }; |
254 | |
255 | return devm_backlight_device_register(dev, name: dev_name(dev), parent: dev, devdata: dsi, |
256 | ops: &visionox_vtdr6130_bl_ops, props: &props); |
257 | } |
258 | |
259 | static int visionox_vtdr6130_probe(struct mipi_dsi_device *dsi) |
260 | { |
261 | struct device *dev = &dsi->dev; |
262 | struct visionox_vtdr6130 *ctx; |
263 | int ret; |
264 | |
265 | ctx = devm_kzalloc(dev, size: sizeof(*ctx), GFP_KERNEL); |
266 | if (!ctx) |
267 | return -ENOMEM; |
268 | |
269 | ctx->supplies[0].supply = "vddio" ; |
270 | ctx->supplies[1].supply = "vci" ; |
271 | ctx->supplies[2].supply = "vdd" ; |
272 | |
273 | ret = devm_regulator_bulk_get(dev: &dsi->dev, ARRAY_SIZE(ctx->supplies), |
274 | consumers: ctx->supplies); |
275 | if (ret < 0) |
276 | return ret; |
277 | |
278 | ctx->reset_gpio = devm_gpiod_get(dev, con_id: "reset" , flags: GPIOD_OUT_LOW); |
279 | if (IS_ERR(ptr: ctx->reset_gpio)) |
280 | return dev_err_probe(dev, err: PTR_ERR(ptr: ctx->reset_gpio), |
281 | fmt: "Failed to get reset-gpios\n" ); |
282 | |
283 | ctx->dsi = dsi; |
284 | mipi_dsi_set_drvdata(dsi, data: ctx); |
285 | |
286 | dsi->lanes = 4; |
287 | dsi->format = MIPI_DSI_FMT_RGB888; |
288 | dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_NO_EOT_PACKET | |
289 | MIPI_DSI_CLOCK_NON_CONTINUOUS; |
290 | ctx->panel.prepare_prev_first = true; |
291 | |
292 | drm_panel_init(panel: &ctx->panel, dev, funcs: &visionox_vtdr6130_panel_funcs, |
293 | DRM_MODE_CONNECTOR_DSI); |
294 | |
295 | ctx->panel.backlight = visionox_vtdr6130_create_backlight(dsi); |
296 | if (IS_ERR(ptr: ctx->panel.backlight)) |
297 | return dev_err_probe(dev, err: PTR_ERR(ptr: ctx->panel.backlight), |
298 | fmt: "Failed to create backlight\n" ); |
299 | |
300 | drm_panel_add(panel: &ctx->panel); |
301 | |
302 | ret = mipi_dsi_attach(dsi); |
303 | if (ret < 0) { |
304 | dev_err(dev, "Failed to attach to DSI host: %d\n" , ret); |
305 | drm_panel_remove(panel: &ctx->panel); |
306 | return ret; |
307 | } |
308 | |
309 | return 0; |
310 | } |
311 | |
312 | static void visionox_vtdr6130_remove(struct mipi_dsi_device *dsi) |
313 | { |
314 | struct visionox_vtdr6130 *ctx = mipi_dsi_get_drvdata(dsi); |
315 | int ret; |
316 | |
317 | ret = mipi_dsi_detach(dsi); |
318 | if (ret < 0) |
319 | dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n" , ret); |
320 | |
321 | drm_panel_remove(panel: &ctx->panel); |
322 | } |
323 | |
324 | static const struct of_device_id visionox_vtdr6130_of_match[] = { |
325 | { .compatible = "visionox,vtdr6130" }, |
326 | { /* sentinel */ } |
327 | }; |
328 | MODULE_DEVICE_TABLE(of, visionox_vtdr6130_of_match); |
329 | |
330 | static struct mipi_dsi_driver visionox_vtdr6130_driver = { |
331 | .probe = visionox_vtdr6130_probe, |
332 | .remove = visionox_vtdr6130_remove, |
333 | .driver = { |
334 | .name = "panel-visionox-vtdr6130" , |
335 | .of_match_table = visionox_vtdr6130_of_match, |
336 | }, |
337 | }; |
338 | module_mipi_dsi_driver(visionox_vtdr6130_driver); |
339 | |
340 | MODULE_AUTHOR("Neil Armstrong <neil.armstrong@linaro.org>" ); |
341 | MODULE_DESCRIPTION("Panel driver for the Visionox VTDR6130 AMOLED DSI panel" ); |
342 | MODULE_LICENSE("GPL" ); |
343 | |