blob: d88ea8da2ec2643f832b27ce0ada2aa447d12499 [file] [log] [blame]
Guido Günther530b1962019-04-01 12:35:35 +02001// SPDX-License-Identifier: GPL-2.0
2/*
3 * Rockteck jh057n00900 5.5" MIPI-DSI panel driver
4 *
5 * Copyright (C) Purism SPC 2019
6 */
7
8#include <drm/drm_mipi_dsi.h>
9#include <drm/drm_modes.h>
10#include <drm/drm_panel.h>
11#include <drm/drm_print.h>
12#include <linux/backlight.h>
13#include <linux/debugfs.h>
14#include <linux/delay.h>
15#include <linux/gpio/consumer.h>
16#include <linux/media-bus-format.h>
17#include <linux/module.h>
18#include <video/display_timing.h>
19#include <video/mipi_display.h>
20
21#define DRV_NAME "panel-rocktech-jh057n00900"
22
23/* Manufacturer specific Commands send via DSI */
24#define ST7703_CMD_ALL_PIXEL_OFF 0x22
25#define ST7703_CMD_ALL_PIXEL_ON 0x23
26#define ST7703_CMD_SETDISP 0xB2
27#define ST7703_CMD_SETRGBIF 0xB3
28#define ST7703_CMD_SETCYC 0xB4
29#define ST7703_CMD_SETBGP 0xB5
30#define ST7703_CMD_SETVCOM 0xB6
31#define ST7703_CMD_SETOTP 0xB7
32#define ST7703_CMD_SETPOWER_EXT 0xB8
33#define ST7703_CMD_SETEXTC 0xB9
34#define ST7703_CMD_SETMIPI 0xBA
35#define ST7703_CMD_SETVDC 0xBC
36#define ST7703_CMD_SETSCR 0xC0
37#define ST7703_CMD_SETPOWER 0xC1
38#define ST7703_CMD_SETPANEL 0xCC
39#define ST7703_CMD_SETGAMMA 0xE0
40#define ST7703_CMD_SETEQ 0xE3
41#define ST7703_CMD_SETGIP1 0xE9
42#define ST7703_CMD_SETGIP2 0xEA
43
44struct jh057n {
45 struct device *dev;
46 struct drm_panel panel;
47 struct gpio_desc *reset_gpio;
48 struct backlight_device *backlight;
49 bool prepared;
50
51 struct dentry *debugfs;
52};
53
54static inline struct jh057n *panel_to_jh057n(struct drm_panel *panel)
55{
56 return container_of(panel, struct jh057n, panel);
57}
58
59#define dsi_generic_write_seq(dsi, seq...) do { \
60 static const u8 d[] = { seq }; \
61 int ret; \
62 ret = mipi_dsi_generic_write(dsi, d, ARRAY_SIZE(d)); \
63 if (ret < 0) \
64 return ret; \
65 } while (0)
66
67static int jh057n_init_sequence(struct jh057n *ctx)
68{
69 struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
70 struct device *dev = ctx->dev;
71 int ret;
72
73 /*
74 * Init sequence was supplied by the panel vendor. Most of the commands
75 * resemble the ST7703 but the number of parameters often don't match
76 * so it's likely a clone.
77 */
78 dsi_generic_write_seq(dsi, ST7703_CMD_SETEXTC,
79 0xF1, 0x12, 0x83);
80 dsi_generic_write_seq(dsi, ST7703_CMD_SETRGBIF,
81 0x10, 0x10, 0x05, 0x05, 0x03, 0xFF, 0x00, 0x00,
82 0x00, 0x00);
83 dsi_generic_write_seq(dsi, ST7703_CMD_SETSCR,
84 0x73, 0x73, 0x50, 0x50, 0x00, 0x00, 0x08, 0x70,
85 0x00);
86 dsi_generic_write_seq(dsi, ST7703_CMD_SETVDC, 0x4E);
87 dsi_generic_write_seq(dsi, ST7703_CMD_SETPANEL, 0x0B);
88 dsi_generic_write_seq(dsi, ST7703_CMD_SETCYC, 0x80);
89 dsi_generic_write_seq(dsi, ST7703_CMD_SETDISP, 0xF0, 0x12, 0x30);
90 dsi_generic_write_seq(dsi, ST7703_CMD_SETEQ,
91 0x07, 0x07, 0x0B, 0x0B, 0x03, 0x0B, 0x00, 0x00,
92 0x00, 0x00, 0xFF, 0x00, 0xC0, 0x10);
93 dsi_generic_write_seq(dsi, ST7703_CMD_SETBGP, 0x08, 0x08);
94 msleep(20);
95
96 dsi_generic_write_seq(dsi, ST7703_CMD_SETVCOM, 0x3F, 0x3F);
97 dsi_generic_write_seq(dsi, 0xBF, 0x02, 0x11, 0x00);
98 dsi_generic_write_seq(dsi, ST7703_CMD_SETGIP1,
99 0x82, 0x10, 0x06, 0x05, 0x9E, 0x0A, 0xA5, 0x12,
100 0x31, 0x23, 0x37, 0x83, 0x04, 0xBC, 0x27, 0x38,
101 0x0C, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0C, 0x00,
102 0x03, 0x00, 0x00, 0x00, 0x75, 0x75, 0x31, 0x88,
103 0x88, 0x88, 0x88, 0x88, 0x88, 0x13, 0x88, 0x64,
104 0x64, 0x20, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
105 0x02, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
106 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
107 dsi_generic_write_seq(dsi, ST7703_CMD_SETGIP2,
108 0x02, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
109 0x00, 0x00, 0x00, 0x00, 0x02, 0x46, 0x02, 0x88,
110 0x88, 0x88, 0x88, 0x88, 0x88, 0x64, 0x88, 0x13,
111 0x57, 0x13, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
112 0x75, 0x88, 0x23, 0x14, 0x00, 0x00, 0x02, 0x00,
113 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
114 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x0A,
115 0xA5, 0x00, 0x00, 0x00, 0x00);
116 dsi_generic_write_seq(dsi, ST7703_CMD_SETGAMMA,
117 0x00, 0x09, 0x0E, 0x29, 0x2D, 0x3C, 0x41, 0x37,
118 0x07, 0x0B, 0x0D, 0x10, 0x11, 0x0F, 0x10, 0x11,
119 0x18, 0x00, 0x09, 0x0E, 0x29, 0x2D, 0x3C, 0x41,
120 0x37, 0x07, 0x0B, 0x0D, 0x10, 0x11, 0x0F, 0x10,
121 0x11, 0x18);
122 msleep(20);
123
124 ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
125 if (ret < 0) {
Joe Perches2ebf4712019-04-04 08:06:09 -0700126 DRM_DEV_ERROR(dev, "Failed to exit sleep mode\n");
Guido Günther530b1962019-04-01 12:35:35 +0200127 return ret;
128 }
129 /* Panel is operational 120 msec after reset */
130 msleep(60);
131 ret = mipi_dsi_dcs_set_display_on(dsi);
132 if (ret)
133 return ret;
134
Joe Perches2ebf4712019-04-04 08:06:09 -0700135 DRM_DEV_DEBUG_DRIVER(dev, "Panel init sequence done\n");
Guido Günther530b1962019-04-01 12:35:35 +0200136 return 0;
137}
138
139static int jh057n_enable(struct drm_panel *panel)
140{
141 struct jh057n *ctx = panel_to_jh057n(panel);
142
143 return backlight_enable(ctx->backlight);
144}
145
146static int jh057n_disable(struct drm_panel *panel)
147{
148 struct jh057n *ctx = panel_to_jh057n(panel);
149
150 return backlight_disable(ctx->backlight);
151}
152
153static int jh057n_unprepare(struct drm_panel *panel)
154{
155 struct jh057n *ctx = panel_to_jh057n(panel);
156 struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
157
158 if (!ctx->prepared)
159 return 0;
160
161 mipi_dsi_dcs_set_display_off(dsi);
162 ctx->prepared = false;
163
164 return 0;
165}
166
167static int jh057n_prepare(struct drm_panel *panel)
168{
169 struct jh057n *ctx = panel_to_jh057n(panel);
170 int ret;
171
172 if (ctx->prepared)
173 return 0;
174
Joe Perches2ebf4712019-04-04 08:06:09 -0700175 DRM_DEV_DEBUG_DRIVER(ctx->dev, "Resetting the panel\n");
Guido Günther530b1962019-04-01 12:35:35 +0200176 gpiod_set_value_cansleep(ctx->reset_gpio, 1);
177 usleep_range(20, 40);
178 gpiod_set_value_cansleep(ctx->reset_gpio, 0);
179 msleep(20);
180
181 ret = jh057n_init_sequence(ctx);
182 if (ret < 0) {
Joe Perches2ebf4712019-04-04 08:06:09 -0700183 DRM_DEV_ERROR(ctx->dev, "Panel init sequence failed: %d\n",
184 ret);
Guido Günther530b1962019-04-01 12:35:35 +0200185 return ret;
186 }
187
188 ctx->prepared = true;
189
190 return 0;
191}
192
193static const struct drm_display_mode default_mode = {
194 .hdisplay = 720,
195 .hsync_start = 720 + 90,
196 .hsync_end = 720 + 90 + 20,
197 .htotal = 720 + 90 + 20 + 20,
198 .vdisplay = 1440,
199 .vsync_start = 1440 + 20,
200 .vsync_end = 1440 + 20 + 4,
201 .vtotal = 1440 + 20 + 4 + 12,
202 .vrefresh = 60,
203 .clock = 75276,
204 .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
205 .width_mm = 65,
206 .height_mm = 130,
207};
208
209static int jh057n_get_modes(struct drm_panel *panel)
210{
211 struct jh057n *ctx = panel_to_jh057n(panel);
212 struct drm_display_mode *mode;
213
214 mode = drm_mode_duplicate(panel->drm, &default_mode);
215 if (!mode) {
Joe Perches2ebf4712019-04-04 08:06:09 -0700216 DRM_DEV_ERROR(ctx->dev, "Failed to add mode %ux%u@%u\n",
Guido Günther530b1962019-04-01 12:35:35 +0200217 default_mode.hdisplay, default_mode.vdisplay,
218 default_mode.vrefresh);
219 return -ENOMEM;
220 }
221
222 drm_mode_set_name(mode);
223
224 mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
225 panel->connector->display_info.width_mm = mode->width_mm;
226 panel->connector->display_info.height_mm = mode->height_mm;
227 drm_mode_probed_add(panel->connector, mode);
228
229 return 1;
230}
231
232static const struct drm_panel_funcs jh057n_drm_funcs = {
233 .disable = jh057n_disable,
234 .unprepare = jh057n_unprepare,
235 .prepare = jh057n_prepare,
236 .enable = jh057n_enable,
237 .get_modes = jh057n_get_modes,
238};
239
240static int allpixelson_set(void *data, u64 val)
241{
242 struct jh057n *ctx = data;
243 struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
244
Joe Perches2ebf4712019-04-04 08:06:09 -0700245 DRM_DEV_DEBUG_DRIVER(ctx->dev, "Setting all pixels on\n");
Guido Günther530b1962019-04-01 12:35:35 +0200246 dsi_generic_write_seq(dsi, ST7703_CMD_ALL_PIXEL_ON);
247 msleep(val * 1000);
248 /* Reset the panel to get video back */
249 drm_panel_disable(&ctx->panel);
250 drm_panel_unprepare(&ctx->panel);
251 drm_panel_prepare(&ctx->panel);
252 drm_panel_enable(&ctx->panel);
253
254 return 0;
255}
256
257DEFINE_SIMPLE_ATTRIBUTE(allpixelson_fops, NULL,
258 allpixelson_set, "%llu\n");
259
260static int jh057n_debugfs_init(struct jh057n *ctx)
261{
262 struct dentry *f;
263
264 ctx->debugfs = debugfs_create_dir(DRV_NAME, NULL);
265 if (!ctx->debugfs)
266 return -ENOMEM;
267
268 f = debugfs_create_file("allpixelson", 0600,
269 ctx->debugfs, ctx, &allpixelson_fops);
270 if (!f)
271 return -ENOMEM;
272
273 return 0;
274}
275
276static void jh057n_debugfs_remove(struct jh057n *ctx)
277{
278 debugfs_remove_recursive(ctx->debugfs);
279 ctx->debugfs = NULL;
280}
281
282static int jh057n_probe(struct mipi_dsi_device *dsi)
283{
284 struct device *dev = &dsi->dev;
285 struct jh057n *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(dev, "reset", GPIOD_OUT_LOW);
293 if (IS_ERR(ctx->reset_gpio)) {
Joe Perches2ebf4712019-04-04 08:06:09 -0700294 DRM_DEV_ERROR(dev, "cannot get reset gpio\n");
Guido Günther530b1962019-04-01 12:35:35 +0200295 return PTR_ERR(ctx->reset_gpio);
296 }
297
298 mipi_dsi_set_drvdata(dsi, ctx);
299
300 ctx->dev = dev;
301
302 dsi->lanes = 4;
303 dsi->format = MIPI_DSI_FMT_RGB888;
304 dsi->mode_flags = MIPI_DSI_MODE_VIDEO |
305 MIPI_DSI_MODE_VIDEO_BURST | MIPI_DSI_MODE_VIDEO_SYNC_PULSE;
306
307 ctx->backlight = devm_of_find_backlight(dev);
308 if (IS_ERR(ctx->backlight))
309 return PTR_ERR(ctx->backlight);
310
311 drm_panel_init(&ctx->panel);
312 ctx->panel.dev = dev;
313 ctx->panel.funcs = &jh057n_drm_funcs;
314
315 drm_panel_add(&ctx->panel);
316
317 ret = mipi_dsi_attach(dsi);
318 if (ret < 0) {
Joe Perches2ebf4712019-04-04 08:06:09 -0700319 DRM_DEV_ERROR(dev, "mipi_dsi_attach failed. Is host ready?\n");
Guido Günther530b1962019-04-01 12:35:35 +0200320 drm_panel_remove(&ctx->panel);
321 return ret;
322 }
323
Joe Perches2ebf4712019-04-04 08:06:09 -0700324 DRM_DEV_INFO(dev, "%ux%u@%u %ubpp dsi %udl - ready\n",
Guido Günther530b1962019-04-01 12:35:35 +0200325 default_mode.hdisplay, default_mode.vdisplay,
326 default_mode.vrefresh,
327 mipi_dsi_pixel_format_to_bpp(dsi->format), dsi->lanes);
328
329 jh057n_debugfs_init(ctx);
330 return 0;
331}
332
333static void jh057n_shutdown(struct mipi_dsi_device *dsi)
334{
335 struct jh057n *ctx = mipi_dsi_get_drvdata(dsi);
336 int ret;
337
338 ret = jh057n_unprepare(&ctx->panel);
339 if (ret < 0)
340 DRM_DEV_ERROR(&dsi->dev, "Failed to unprepare panel: %d\n",
341 ret);
342
343 ret = jh057n_disable(&ctx->panel);
344 if (ret < 0)
345 DRM_DEV_ERROR(&dsi->dev, "Failed to disable panel: %d\n",
346 ret);
347}
348
349static int jh057n_remove(struct mipi_dsi_device *dsi)
350{
351 struct jh057n *ctx = mipi_dsi_get_drvdata(dsi);
352 int ret;
353
354 jh057n_shutdown(dsi);
355
356 ret = mipi_dsi_detach(dsi);
357 if (ret < 0)
358 DRM_DEV_ERROR(&dsi->dev, "Failed to detach from DSI host: %d\n",
359 ret);
360
361 drm_panel_remove(&ctx->panel);
362
363 jh057n_debugfs_remove(ctx);
364
365 return 0;
366}
367
368static const struct of_device_id jh057n_of_match[] = {
369 { .compatible = "rocktech,jh057n00900" },
370 { /* sentinel */ }
371};
372MODULE_DEVICE_TABLE(of, jh057n_of_match);
373
374static struct mipi_dsi_driver jh057n_driver = {
375 .probe = jh057n_probe,
376 .remove = jh057n_remove,
377 .shutdown = jh057n_shutdown,
378 .driver = {
379 .name = DRV_NAME,
380 .of_match_table = jh057n_of_match,
381 },
382};
383module_mipi_dsi_driver(jh057n_driver);
384
385MODULE_AUTHOR("Guido Günther <agx@sigxcpu.org>");
386MODULE_DESCRIPTION("DRM driver for Rocktech JH057N00900 MIPI DSI panel");
387MODULE_LICENSE("GPL v2");