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
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
49struct 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};
56
57static inline struct xpp055c272 *panel_to_xpp055c272(struct drm_panel *panel)
58{
59 return container_of(panel, struct xpp055c272, panel);
60}
61
62static void xpp055c272_init_sequence(struct mipi_dsi_multi_context *dsi_ctx)
63{
64 /*
65 * Init sequence was supplied by the panel vendor without much
66 * documentation.
67 */
68 mipi_dsi_dcs_write_seq_multi(dsi_ctx, XPP055C272_CMD_SETEXTC, 0xf1, 0x12, 0x83);
69 mipi_dsi_dcs_write_seq_multi(dsi_ctx, XPP055C272_CMD_SETMIPI,
70 0x33, 0x81, 0x05, 0xf9, 0x0e, 0x0e, 0x00, 0x00,
71 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x25,
72 0x00, 0x91, 0x0a, 0x00, 0x00, 0x02, 0x4f, 0x01,
73 0x00, 0x00, 0x37);
74 mipi_dsi_dcs_write_seq_multi(dsi_ctx, XPP055C272_CMD_SETPOWER_EXT, 0x25);
75 mipi_dsi_dcs_write_seq_multi(dsi_ctx, XPP055C272_CMD_SETPCR, 0x02, 0x11, 0x00);
76 mipi_dsi_dcs_write_seq_multi(dsi_ctx, XPP055C272_CMD_SETRGBIF,
77 0x0c, 0x10, 0x0a, 0x50, 0x03, 0xff, 0x00, 0x00,
78 0x00, 0x00);
79 mipi_dsi_dcs_write_seq_multi(dsi_ctx, XPP055C272_CMD_SETSCR,
80 0x73, 0x73, 0x50, 0x50, 0x00, 0x00, 0x08, 0x70,
81 0x00);
82 mipi_dsi_dcs_write_seq_multi(dsi_ctx, XPP055C272_CMD_SETVDC, 0x46);
83 mipi_dsi_dcs_write_seq_multi(dsi_ctx, XPP055C272_CMD_SETPANEL, 0x0b);
84 mipi_dsi_dcs_write_seq_multi(dsi_ctx, XPP055C272_CMD_SETCYC, 0x80);
85 mipi_dsi_dcs_write_seq_multi(dsi_ctx, XPP055C272_CMD_SETDISP, 0xc8, 0x12, 0x30);
86 mipi_dsi_dcs_write_seq_multi(dsi_ctx, XPP055C272_CMD_SETEQ,
87 0x07, 0x07, 0x0b, 0x0b, 0x03, 0x0b, 0x00, 0x00,
88 0x00, 0x00, 0xff, 0x00, 0xC0, 0x10);
89 mipi_dsi_dcs_write_seq_multi(dsi_ctx, XPP055C272_CMD_SETPOWER,
90 0x53, 0x00, 0x1e, 0x1e, 0x77, 0xe1, 0xcc, 0xdd,
91 0x67, 0x77, 0x33, 0x33);
92 mipi_dsi_dcs_write_seq_multi(dsi_ctx, XPP055C272_CMD_SETECO, 0x00, 0x00, 0xff,
93 0xff, 0x01, 0xff);
94 mipi_dsi_dcs_write_seq_multi(dsi_ctx, XPP055C272_CMD_SETBGP, 0x09, 0x09);
95 mipi_dsi_msleep(dsi_ctx, 20);
96
97 mipi_dsi_dcs_write_seq_multi(dsi_ctx, XPP055C272_CMD_SETVCOM, 0x87, 0x95);
98 mipi_dsi_dcs_write_seq_multi(dsi_ctx, XPP055C272_CMD_SETGIP1,
99 0xc2, 0x10, 0x05, 0x05, 0x10, 0x05, 0xa0, 0x12,
100 0x31, 0x23, 0x3f, 0x81, 0x0a, 0xa0, 0x37, 0x18,
101 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x80,
102 0x01, 0x00, 0x00, 0x00, 0x48, 0xf8, 0x86, 0x42,
103 0x08, 0x88, 0x88, 0x80, 0x88, 0x88, 0x88, 0x58,
104 0xf8, 0x87, 0x53, 0x18, 0x88, 0x88, 0x81, 0x88,
105 0x88, 0x88, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
106 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
107 mipi_dsi_dcs_write_seq_multi(dsi_ctx, XPP055C272_CMD_SETGIP2,
108 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
109 0x00, 0x00, 0x00, 0x00, 0x1f, 0x88, 0x81, 0x35,
110 0x78, 0x88, 0x88, 0x85, 0x88, 0x88, 0x88, 0x0f,
111 0x88, 0x80, 0x24, 0x68, 0x88, 0x88, 0x84, 0x88,
112 0x88, 0x88, 0x23, 0x10, 0x00, 0x00, 0x1c, 0x00,
113 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
114 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x05,
115 0xa0, 0x00, 0x00, 0x00, 0x00);
116 mipi_dsi_dcs_write_seq_multi(dsi_ctx, XPP055C272_CMD_SETGAMMA,
117 0x00, 0x06, 0x08, 0x2a, 0x31, 0x3f, 0x38, 0x36,
118 0x07, 0x0c, 0x0d, 0x11, 0x13, 0x12, 0x13, 0x11,
119 0x18, 0x00, 0x06, 0x08, 0x2a, 0x31, 0x3f, 0x38,
120 0x36, 0x07, 0x0c, 0x0d, 0x11, 0x13, 0x12, 0x13,
121 0x11, 0x18);
122
123 mipi_dsi_msleep(dsi_ctx, 60);
124}
125
126static int xpp055c272_unprepare(struct drm_panel *panel)
127{
128 struct xpp055c272 *ctx = panel_to_xpp055c272(panel);
129 struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
130 struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi };
131
132 mipi_dsi_dcs_set_display_off_multi(&dsi_ctx);
133 mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx);
134 if (dsi_ctx.accum_err)
135 return dsi_ctx.accum_err;
136
137 regulator_disable(ctx->iovcc);
138 regulator_disable(ctx->vci);
139
140 return 0;
141}
142
143static int xpp055c272_prepare(struct drm_panel *panel)
144{
145 struct xpp055c272 *ctx = panel_to_xpp055c272(panel);
146 struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
147 struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi };
148
149 dev_dbg(ctx->dev, "Resetting the panel\n");
150 dsi_ctx.accum_err = regulator_enable(ctx->vci);
151 if (dsi_ctx.accum_err) {
152 dev_err(ctx->dev, "Failed to enable vci supply: %d\n",
153 dsi_ctx.accum_err);
154 return dsi_ctx.accum_err;
155 }
156 dsi_ctx.accum_err = regulator_enable(ctx->iovcc);
157 if (dsi_ctx.accum_err) {
158 dev_err(ctx->dev, "Failed to enable iovcc supply: %d\n",
159 dsi_ctx.accum_err);
160 goto disable_vci;
161 }
162
163 gpiod_set_value_cansleep(ctx->reset_gpio, 1);
164 /* T6: 10us */
165 usleep_range(10, 20);
166 gpiod_set_value_cansleep(ctx->reset_gpio, 0);
167
168 /* T8: 20ms */
169 msleep(20);
170
171 xpp055c272_init_sequence(&dsi_ctx);
172 if (!dsi_ctx.accum_err)
173 dev_dbg(ctx->dev, "Panel init sequence done\n");
174
175 mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx);
176 /* T9: 120ms */
177 mipi_dsi_msleep(&dsi_ctx, 120);
178 mipi_dsi_dcs_set_display_on_multi(&dsi_ctx);
179
180 if (dsi_ctx.accum_err)
181 goto disable_iovcc;
182
183 msleep(50);
184
185 return 0;
186
187disable_iovcc:
188 regulator_disable(ctx->iovcc);
189disable_vci:
190 regulator_disable(ctx->vci);
191 return dsi_ctx.accum_err;
192}
193
194static const struct drm_display_mode default_mode = {
195 .hdisplay = 720,
196 .hsync_start = 720 + 40,
197 .hsync_end = 720 + 40 + 10,
198 .htotal = 720 + 40 + 10 + 40,
199 .vdisplay = 1280,
200 .vsync_start = 1280 + 22,
201 .vsync_end = 1280 + 22 + 4,
202 .vtotal = 1280 + 22 + 4 + 11,
203 .clock = 64000,
204 .width_mm = 68,
205 .height_mm = 121,
206};
207
208static int xpp055c272_get_modes(struct drm_panel *panel,
209 struct drm_connector *connector)
210{
211 struct xpp055c272 *ctx = panel_to_xpp055c272(panel);
212 struct drm_display_mode *mode;
213
214 mode = drm_mode_duplicate(connector->dev, &default_mode);
215 if (!mode) {
216 dev_err(ctx->dev, "Failed to add mode %ux%u@%u\n",
217 default_mode.hdisplay, default_mode.vdisplay,
218 drm_mode_vrefresh(&default_mode));
219 return -ENOMEM;
220 }
221
222 drm_mode_set_name(mode);
223
224 mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
225 connector->display_info.width_mm = mode->width_mm;
226 connector->display_info.height_mm = mode->height_mm;
227 drm_mode_probed_add(connector, mode);
228
229 return 1;
230}
231
232static const struct drm_panel_funcs xpp055c272_funcs = {
233 .unprepare = xpp055c272_unprepare,
234 .prepare = xpp055c272_prepare,
235 .get_modes = xpp055c272_get_modes,
236};
237
238static int xpp055c272_probe(struct mipi_dsi_device *dsi)
239{
240 struct device *dev = &dsi->dev;
241 struct xpp055c272 *ctx;
242 int ret;
243
244 ctx = devm_drm_panel_alloc(dev, struct xpp055c272, panel,
245 &xpp055c272_funcs, DRM_MODE_CONNECTOR_DSI);
246 if (IS_ERR(ctx))
247 return PTR_ERR(ctx);
248
249 ctx->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
250 if (IS_ERR(ctx->reset_gpio))
251 return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio),
252 "cannot get reset gpio\n");
253
254 ctx->vci = devm_regulator_get(dev, "vci");
255 if (IS_ERR(ctx->vci))
256 return dev_err_probe(dev, PTR_ERR(ctx->vci),
257 "Failed to request vci regulator\n");
258
259 ctx->iovcc = devm_regulator_get(dev, "iovcc");
260 if (IS_ERR(ctx->iovcc))
261 return dev_err_probe(dev, PTR_ERR(ctx->iovcc),
262 "Failed to request iovcc regulator\n");
263
264 mipi_dsi_set_drvdata(dsi, ctx);
265
266 ctx->dev = dev;
267
268 dsi->lanes = 4;
269 dsi->format = MIPI_DSI_FMT_RGB888;
270 dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
271 MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_NO_EOT_PACKET;
272
273 ret = drm_panel_of_backlight(&ctx->panel);
274 if (ret)
275 return ret;
276
277 drm_panel_add(&ctx->panel);
278
279 ret = mipi_dsi_attach(dsi);
280 if (ret < 0) {
281 dev_err(dev, "mipi_dsi_attach failed: %d\n", ret);
282 drm_panel_remove(&ctx->panel);
283 return ret;
284 }
285
286 return 0;
287}
288
289static void xpp055c272_remove(struct mipi_dsi_device *dsi)
290{
291 struct xpp055c272 *ctx = mipi_dsi_get_drvdata(dsi);
292 int ret;
293
294 ret = mipi_dsi_detach(dsi);
295 if (ret < 0)
296 dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret);
297
298 drm_panel_remove(&ctx->panel);
299}
300
301static const struct of_device_id xpp055c272_of_match[] = {
302 { .compatible = "xinpeng,xpp055c272" },
303 { /* sentinel */ }
304};
305MODULE_DEVICE_TABLE(of, xpp055c272_of_match);
306
307static struct mipi_dsi_driver xpp055c272_driver = {
308 .driver = {
309 .name = "panel-xinpeng-xpp055c272",
310 .of_match_table = xpp055c272_of_match,
311 },
312 .probe = xpp055c272_probe,
313 .remove = xpp055c272_remove,
314};
315module_mipi_dsi_driver(xpp055c272_driver);
316
317MODULE_AUTHOR("Heiko Stuebner <heiko.stuebner@theobroma-systems.com>");
318MODULE_DESCRIPTION("DRM driver for Xinpeng xpp055c272 MIPI DSI panel");
319MODULE_LICENSE("GPL v2");