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