1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Xinpeng xpp055c272 5.5" MIPI-DSI panel driver |
4 | * Copyright (C) 2019 Theobroma Systems Design und Consulting GmbH |
5 | * |
6 | * based on |
7 | * |
8 | * Rockteck jh057n00900 5.5" MIPI-DSI panel driver |
9 | * Copyright (C) Purism SPC 2019 |
10 | */ |
11 | |
12 | #include <drm/drm_mipi_dsi.h> |
13 | #include <drm/drm_modes.h> |
14 | #include <drm/drm_panel.h> |
15 | |
16 | #include <video/display_timing.h> |
17 | #include <video/mipi_display.h> |
18 | |
19 | #include <linux/delay.h> |
20 | #include <linux/gpio/consumer.h> |
21 | #include <linux/media-bus-format.h> |
22 | #include <linux/module.h> |
23 | #include <linux/of.h> |
24 | #include <linux/regulator/consumer.h> |
25 | |
26 | /* Manufacturer specific Commands send via DSI */ |
27 | #define XPP055C272_CMD_ALL_PIXEL_OFF 0x22 |
28 | #define XPP055C272_CMD_ALL_PIXEL_ON 0x23 |
29 | #define XPP055C272_CMD_SETDISP 0xb2 |
30 | #define XPP055C272_CMD_SETRGBIF 0xb3 |
31 | #define XPP055C272_CMD_SETCYC 0xb4 |
32 | #define XPP055C272_CMD_SETBGP 0xb5 |
33 | #define XPP055C272_CMD_SETVCOM 0xb6 |
34 | #define XPP055C272_CMD_SETOTP 0xb7 |
35 | #define XPP055C272_CMD_SETPOWER_EXT 0xb8 |
36 | #define XPP055C272_CMD_SETEXTC 0xb9 |
37 | #define XPP055C272_CMD_SETMIPI 0xbA |
38 | #define XPP055C272_CMD_SETVDC 0xbc |
39 | #define XPP055C272_CMD_SETPCR 0xbf |
40 | #define XPP055C272_CMD_SETSCR 0xc0 |
41 | #define XPP055C272_CMD_SETPOWER 0xc1 |
42 | #define XPP055C272_CMD_SETECO 0xc6 |
43 | #define XPP055C272_CMD_SETPANEL 0xcc |
44 | #define XPP055C272_CMD_SETGAMMA 0xe0 |
45 | #define XPP055C272_CMD_SETEQ 0xe3 |
46 | #define XPP055C272_CMD_SETGIP1 0xe9 |
47 | #define XPP055C272_CMD_SETGIP2 0xea |
48 | |
49 | struct xpp055c272 { |
50 | struct device *dev; |
51 | struct drm_panel panel; |
52 | struct gpio_desc *reset_gpio; |
53 | struct regulator *vci; |
54 | struct regulator *iovcc; |
55 | bool prepared; |
56 | }; |
57 | |
58 | static inline struct xpp055c272 *panel_to_xpp055c272(struct drm_panel *panel) |
59 | { |
60 | return container_of(panel, struct xpp055c272, panel); |
61 | } |
62 | |
63 | static int xpp055c272_init_sequence(struct xpp055c272 *ctx) |
64 | { |
65 | struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); |
66 | struct device *dev = ctx->dev; |
67 | |
68 | /* |
69 | * Init sequence was supplied by the panel vendor without much |
70 | * documentation. |
71 | */ |
72 | mipi_dsi_dcs_write_seq(dsi, XPP055C272_CMD_SETEXTC, 0xf1, 0x12, 0x83); |
73 | mipi_dsi_dcs_write_seq(dsi, XPP055C272_CMD_SETMIPI, |
74 | 0x33, 0x81, 0x05, 0xf9, 0x0e, 0x0e, 0x00, 0x00, |
75 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x25, |
76 | 0x00, 0x91, 0x0a, 0x00, 0x00, 0x02, 0x4f, 0x01, |
77 | 0x00, 0x00, 0x37); |
78 | mipi_dsi_dcs_write_seq(dsi, XPP055C272_CMD_SETPOWER_EXT, 0x25); |
79 | mipi_dsi_dcs_write_seq(dsi, XPP055C272_CMD_SETPCR, 0x02, 0x11, 0x00); |
80 | mipi_dsi_dcs_write_seq(dsi, XPP055C272_CMD_SETRGBIF, |
81 | 0x0c, 0x10, 0x0a, 0x50, 0x03, 0xff, 0x00, 0x00, |
82 | 0x00, 0x00); |
83 | mipi_dsi_dcs_write_seq(dsi, XPP055C272_CMD_SETSCR, |
84 | 0x73, 0x73, 0x50, 0x50, 0x00, 0x00, 0x08, 0x70, |
85 | 0x00); |
86 | mipi_dsi_dcs_write_seq(dsi, XPP055C272_CMD_SETVDC, 0x46); |
87 | mipi_dsi_dcs_write_seq(dsi, XPP055C272_CMD_SETPANEL, 0x0b); |
88 | mipi_dsi_dcs_write_seq(dsi, XPP055C272_CMD_SETCYC, 0x80); |
89 | mipi_dsi_dcs_write_seq(dsi, XPP055C272_CMD_SETDISP, 0xc8, 0x12, 0x30); |
90 | mipi_dsi_dcs_write_seq(dsi, XPP055C272_CMD_SETEQ, |
91 | 0x07, 0x07, 0x0B, 0x0B, 0x03, 0x0B, 0x00, 0x00, |
92 | 0x00, 0x00, 0xFF, 0x00, 0xC0, 0x10); |
93 | mipi_dsi_dcs_write_seq(dsi, XPP055C272_CMD_SETPOWER, |
94 | 0x53, 0x00, 0x1e, 0x1e, 0x77, 0xe1, 0xcc, 0xdd, |
95 | 0x67, 0x77, 0x33, 0x33); |
96 | mipi_dsi_dcs_write_seq(dsi, XPP055C272_CMD_SETECO, 0x00, 0x00, 0xff, |
97 | 0xff, 0x01, 0xff); |
98 | mipi_dsi_dcs_write_seq(dsi, XPP055C272_CMD_SETBGP, 0x09, 0x09); |
99 | msleep(msecs: 20); |
100 | |
101 | mipi_dsi_dcs_write_seq(dsi, XPP055C272_CMD_SETVCOM, 0x87, 0x95); |
102 | mipi_dsi_dcs_write_seq(dsi, XPP055C272_CMD_SETGIP1, |
103 | 0xc2, 0x10, 0x05, 0x05, 0x10, 0x05, 0xa0, 0x12, |
104 | 0x31, 0x23, 0x3f, 0x81, 0x0a, 0xa0, 0x37, 0x18, |
105 | 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x80, |
106 | 0x01, 0x00, 0x00, 0x00, 0x48, 0xf8, 0x86, 0x42, |
107 | 0x08, 0x88, 0x88, 0x80, 0x88, 0x88, 0x88, 0x58, |
108 | 0xf8, 0x87, 0x53, 0x18, 0x88, 0x88, 0x81, 0x88, |
109 | 0x88, 0x88, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, |
110 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); |
111 | mipi_dsi_dcs_write_seq(dsi, XPP055C272_CMD_SETGIP2, |
112 | 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, |
113 | 0x00, 0x00, 0x00, 0x00, 0x1f, 0x88, 0x81, 0x35, |
114 | 0x78, 0x88, 0x88, 0x85, 0x88, 0x88, 0x88, 0x0f, |
115 | 0x88, 0x80, 0x24, 0x68, 0x88, 0x88, 0x84, 0x88, |
116 | 0x88, 0x88, 0x23, 0x10, 0x00, 0x00, 0x1c, 0x00, |
117 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
118 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x05, |
119 | 0xa0, 0x00, 0x00, 0x00, 0x00); |
120 | mipi_dsi_dcs_write_seq(dsi, XPP055C272_CMD_SETGAMMA, |
121 | 0x00, 0x06, 0x08, 0x2a, 0x31, 0x3f, 0x38, 0x36, |
122 | 0x07, 0x0c, 0x0d, 0x11, 0x13, 0x12, 0x13, 0x11, |
123 | 0x18, 0x00, 0x06, 0x08, 0x2a, 0x31, 0x3f, 0x38, |
124 | 0x36, 0x07, 0x0c, 0x0d, 0x11, 0x13, 0x12, 0x13, |
125 | 0x11, 0x18); |
126 | |
127 | msleep(msecs: 60); |
128 | |
129 | dev_dbg(dev, "Panel init sequence done\n" ); |
130 | return 0; |
131 | } |
132 | |
133 | static int xpp055c272_unprepare(struct drm_panel *panel) |
134 | { |
135 | struct xpp055c272 *ctx = panel_to_xpp055c272(panel); |
136 | struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); |
137 | int ret; |
138 | |
139 | if (!ctx->prepared) |
140 | return 0; |
141 | |
142 | ret = mipi_dsi_dcs_set_display_off(dsi); |
143 | if (ret < 0) |
144 | dev_err(ctx->dev, "failed to set display off: %d\n" , ret); |
145 | |
146 | mipi_dsi_dcs_enter_sleep_mode(dsi); |
147 | if (ret < 0) { |
148 | dev_err(ctx->dev, "failed to enter sleep mode: %d\n" , ret); |
149 | return ret; |
150 | } |
151 | |
152 | regulator_disable(regulator: ctx->iovcc); |
153 | regulator_disable(regulator: ctx->vci); |
154 | |
155 | ctx->prepared = false; |
156 | |
157 | return 0; |
158 | } |
159 | |
160 | static int xpp055c272_prepare(struct drm_panel *panel) |
161 | { |
162 | struct xpp055c272 *ctx = panel_to_xpp055c272(panel); |
163 | struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); |
164 | int ret; |
165 | |
166 | if (ctx->prepared) |
167 | return 0; |
168 | |
169 | dev_dbg(ctx->dev, "Resetting the panel\n" ); |
170 | ret = regulator_enable(regulator: ctx->vci); |
171 | if (ret < 0) { |
172 | dev_err(ctx->dev, "Failed to enable vci supply: %d\n" , ret); |
173 | return ret; |
174 | } |
175 | ret = regulator_enable(regulator: ctx->iovcc); |
176 | if (ret < 0) { |
177 | dev_err(ctx->dev, "Failed to enable iovcc supply: %d\n" , ret); |
178 | goto disable_vci; |
179 | } |
180 | |
181 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 1); |
182 | /* T6: 10us */ |
183 | usleep_range(min: 10, max: 20); |
184 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 0); |
185 | |
186 | /* T8: 20ms */ |
187 | msleep(msecs: 20); |
188 | |
189 | ret = xpp055c272_init_sequence(ctx); |
190 | if (ret < 0) { |
191 | dev_err(ctx->dev, "Panel init sequence failed: %d\n" , ret); |
192 | goto disable_iovcc; |
193 | } |
194 | |
195 | ret = mipi_dsi_dcs_exit_sleep_mode(dsi); |
196 | if (ret < 0) { |
197 | dev_err(ctx->dev, "Failed to exit sleep mode: %d\n" , ret); |
198 | goto disable_iovcc; |
199 | } |
200 | |
201 | /* T9: 120ms */ |
202 | msleep(msecs: 120); |
203 | |
204 | ret = mipi_dsi_dcs_set_display_on(dsi); |
205 | if (ret < 0) { |
206 | dev_err(ctx->dev, "Failed to set display on: %d\n" , ret); |
207 | goto disable_iovcc; |
208 | } |
209 | |
210 | msleep(msecs: 50); |
211 | |
212 | ctx->prepared = true; |
213 | |
214 | return 0; |
215 | |
216 | disable_iovcc: |
217 | regulator_disable(regulator: ctx->iovcc); |
218 | disable_vci: |
219 | regulator_disable(regulator: ctx->vci); |
220 | return ret; |
221 | } |
222 | |
223 | static const struct drm_display_mode default_mode = { |
224 | .hdisplay = 720, |
225 | .hsync_start = 720 + 40, |
226 | .hsync_end = 720 + 40 + 10, |
227 | .htotal = 720 + 40 + 10 + 40, |
228 | .vdisplay = 1280, |
229 | .vsync_start = 1280 + 22, |
230 | .vsync_end = 1280 + 22 + 4, |
231 | .vtotal = 1280 + 22 + 4 + 11, |
232 | .clock = 64000, |
233 | .width_mm = 68, |
234 | .height_mm = 121, |
235 | }; |
236 | |
237 | static int xpp055c272_get_modes(struct drm_panel *panel, |
238 | struct drm_connector *connector) |
239 | { |
240 | struct xpp055c272 *ctx = panel_to_xpp055c272(panel); |
241 | struct drm_display_mode *mode; |
242 | |
243 | mode = drm_mode_duplicate(dev: connector->dev, mode: &default_mode); |
244 | if (!mode) { |
245 | dev_err(ctx->dev, "Failed to add mode %ux%u@%u\n" , |
246 | default_mode.hdisplay, default_mode.vdisplay, |
247 | drm_mode_vrefresh(&default_mode)); |
248 | return -ENOMEM; |
249 | } |
250 | |
251 | drm_mode_set_name(mode); |
252 | |
253 | mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; |
254 | connector->display_info.width_mm = mode->width_mm; |
255 | connector->display_info.height_mm = mode->height_mm; |
256 | drm_mode_probed_add(connector, mode); |
257 | |
258 | return 1; |
259 | } |
260 | |
261 | static const struct drm_panel_funcs xpp055c272_funcs = { |
262 | .unprepare = xpp055c272_unprepare, |
263 | .prepare = xpp055c272_prepare, |
264 | .get_modes = xpp055c272_get_modes, |
265 | }; |
266 | |
267 | static int xpp055c272_probe(struct mipi_dsi_device *dsi) |
268 | { |
269 | struct device *dev = &dsi->dev; |
270 | struct xpp055c272 *ctx; |
271 | int ret; |
272 | |
273 | ctx = devm_kzalloc(dev, size: sizeof(*ctx), GFP_KERNEL); |
274 | if (!ctx) |
275 | return -ENOMEM; |
276 | |
277 | ctx->reset_gpio = devm_gpiod_get_optional(dev, con_id: "reset" , flags: GPIOD_OUT_LOW); |
278 | if (IS_ERR(ptr: ctx->reset_gpio)) |
279 | return dev_err_probe(dev, err: PTR_ERR(ptr: ctx->reset_gpio), |
280 | fmt: "cannot get reset gpio\n" ); |
281 | |
282 | ctx->vci = devm_regulator_get(dev, id: "vci" ); |
283 | if (IS_ERR(ptr: ctx->vci)) |
284 | return dev_err_probe(dev, err: PTR_ERR(ptr: ctx->vci), |
285 | fmt: "Failed to request vci regulator\n" ); |
286 | |
287 | ctx->iovcc = devm_regulator_get(dev, id: "iovcc" ); |
288 | if (IS_ERR(ptr: ctx->iovcc)) |
289 | return dev_err_probe(dev, err: PTR_ERR(ptr: ctx->iovcc), |
290 | fmt: "Failed to request iovcc regulator\n" ); |
291 | |
292 | mipi_dsi_set_drvdata(dsi, data: ctx); |
293 | |
294 | ctx->dev = dev; |
295 | |
296 | dsi->lanes = 4; |
297 | dsi->format = MIPI_DSI_FMT_RGB888; |
298 | dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | |
299 | MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_NO_EOT_PACKET; |
300 | |
301 | drm_panel_init(panel: &ctx->panel, dev: &dsi->dev, funcs: &xpp055c272_funcs, |
302 | DRM_MODE_CONNECTOR_DSI); |
303 | |
304 | ret = drm_panel_of_backlight(panel: &ctx->panel); |
305 | if (ret) |
306 | return ret; |
307 | |
308 | drm_panel_add(panel: &ctx->panel); |
309 | |
310 | ret = mipi_dsi_attach(dsi); |
311 | if (ret < 0) { |
312 | dev_err(dev, "mipi_dsi_attach failed: %d\n" , ret); |
313 | drm_panel_remove(panel: &ctx->panel); |
314 | return ret; |
315 | } |
316 | |
317 | return 0; |
318 | } |
319 | |
320 | static void xpp055c272_shutdown(struct mipi_dsi_device *dsi) |
321 | { |
322 | struct xpp055c272 *ctx = mipi_dsi_get_drvdata(dsi); |
323 | int ret; |
324 | |
325 | ret = drm_panel_unprepare(panel: &ctx->panel); |
326 | if (ret < 0) |
327 | dev_err(&dsi->dev, "Failed to unprepare panel: %d\n" , ret); |
328 | |
329 | ret = drm_panel_disable(panel: &ctx->panel); |
330 | if (ret < 0) |
331 | dev_err(&dsi->dev, "Failed to disable panel: %d\n" , ret); |
332 | } |
333 | |
334 | static void xpp055c272_remove(struct mipi_dsi_device *dsi) |
335 | { |
336 | struct xpp055c272 *ctx = mipi_dsi_get_drvdata(dsi); |
337 | int ret; |
338 | |
339 | xpp055c272_shutdown(dsi); |
340 | |
341 | ret = mipi_dsi_detach(dsi); |
342 | if (ret < 0) |
343 | dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n" , ret); |
344 | |
345 | drm_panel_remove(panel: &ctx->panel); |
346 | } |
347 | |
348 | static const struct of_device_id xpp055c272_of_match[] = { |
349 | { .compatible = "xinpeng,xpp055c272" }, |
350 | { /* sentinel */ } |
351 | }; |
352 | MODULE_DEVICE_TABLE(of, xpp055c272_of_match); |
353 | |
354 | static struct mipi_dsi_driver xpp055c272_driver = { |
355 | .driver = { |
356 | .name = "panel-xinpeng-xpp055c272" , |
357 | .of_match_table = xpp055c272_of_match, |
358 | }, |
359 | .probe = xpp055c272_probe, |
360 | .remove = xpp055c272_remove, |
361 | .shutdown = xpp055c272_shutdown, |
362 | }; |
363 | module_mipi_dsi_driver(xpp055c272_driver); |
364 | |
365 | MODULE_AUTHOR("Heiko Stuebner <heiko.stuebner@theobroma-systems.com>" ); |
366 | MODULE_DESCRIPTION("DRM driver for Xinpeng xpp055c272 MIPI DSI panel" ); |
367 | MODULE_LICENSE("GPL v2" ); |
368 | |