]>
Commit | Line | Data |
---|---|---|
eac99d4a DL |
1 | /* |
2 | * DRM driver for Sitronix ST7586 panels | |
3 | * | |
4 | * Copyright 2017 David Lechner <david@lechnology.com> | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation; either version 2 of the License, or | |
9 | * (at your option) any later version. | |
10 | */ | |
11 | ||
12 | #include <linux/delay.h> | |
13 | #include <linux/dma-buf.h> | |
14 | #include <linux/gpio/consumer.h> | |
15 | #include <linux/module.h> | |
16 | #include <linux/property.h> | |
17 | #include <linux/spi/spi.h> | |
18 | #include <video/mipi_display.h> | |
19 | ||
d0a51634 | 20 | #include <drm/drm_atomic_helper.h> |
af741381 | 21 | #include <drm/drm_damage_helper.h> |
84056e9b | 22 | #include <drm/drm_drv.h> |
3db8d37d NT |
23 | #include <drm/drm_fb_cma_helper.h> |
24 | #include <drm/drm_gem_cma_helper.h> | |
cce1a877 | 25 | #include <drm/drm_gem_framebuffer_helper.h> |
b051b345 | 26 | #include <drm/drm_rect.h> |
af741381 | 27 | #include <drm/drm_vblank.h> |
eac99d4a DL |
28 | #include <drm/tinydrm/mipi-dbi.h> |
29 | #include <drm/tinydrm/tinydrm-helpers.h> | |
30 | ||
31 | /* controller-specific commands */ | |
32 | #define ST7586_DISP_MODE_GRAY 0x38 | |
33 | #define ST7586_DISP_MODE_MONO 0x39 | |
34 | #define ST7586_ENABLE_DDRAM 0x3a | |
35 | #define ST7586_SET_DISP_DUTY 0xb0 | |
36 | #define ST7586_SET_PART_DISP 0xb4 | |
37 | #define ST7586_SET_NLINE_INV 0xb5 | |
38 | #define ST7586_SET_VOP 0xc0 | |
39 | #define ST7586_SET_BIAS_SYSTEM 0xc3 | |
40 | #define ST7586_SET_BOOST_LEVEL 0xc4 | |
41 | #define ST7586_SET_VOP_OFFSET 0xc7 | |
42 | #define ST7586_ENABLE_ANALOG 0xd0 | |
43 | #define ST7586_AUTO_READ_CTRL 0xd7 | |
44 | #define ST7586_OTP_RW_CTRL 0xe0 | |
45 | #define ST7586_OTP_CTRL_OUT 0xe1 | |
46 | #define ST7586_OTP_READ 0xe3 | |
47 | ||
48 | #define ST7586_DISP_CTRL_MX BIT(6) | |
49 | #define ST7586_DISP_CTRL_MY BIT(7) | |
50 | ||
51 | /* | |
52 | * The ST7586 controller has an unusual pixel format where 2bpp grayscale is | |
53 | * packed 3 pixels per byte with the first two pixels using 3 bits and the 3rd | |
54 | * pixel using only 2 bits. | |
55 | * | |
56 | * | D7 | D6 | D5 || | || 2bpp | | |
57 | * | (D4) | (D3) | (D2) || D1 | D0 || GRAY | | |
58 | * +------+------+------++------+------++------+ | |
59 | * | 1 | 1 | 1 || 1 | 1 || 0 0 | black | |
60 | * | 1 | 0 | 0 || 1 | 0 || 0 1 | dark gray | |
61 | * | 0 | 1 | 0 || 0 | 1 || 1 0 | light gray | |
62 | * | 0 | 0 | 0 || 0 | 0 || 1 1 | white | |
63 | */ | |
64 | ||
65 | static const u8 st7586_lookup[] = { 0x7, 0x4, 0x2, 0x0 }; | |
66 | ||
67 | static void st7586_xrgb8888_to_gray332(u8 *dst, void *vaddr, | |
68 | struct drm_framebuffer *fb, | |
b051b345 | 69 | struct drm_rect *clip) |
eac99d4a DL |
70 | { |
71 | size_t len = (clip->x2 - clip->x1) * (clip->y2 - clip->y1); | |
72 | unsigned int x, y; | |
73 | u8 *src, *buf, val; | |
74 | ||
75 | buf = kmalloc(len, GFP_KERNEL); | |
76 | if (!buf) | |
77 | return; | |
78 | ||
79 | tinydrm_xrgb8888_to_gray8(buf, vaddr, fb, clip); | |
80 | src = buf; | |
81 | ||
82 | for (y = clip->y1; y < clip->y2; y++) { | |
83 | for (x = clip->x1; x < clip->x2; x += 3) { | |
84 | val = st7586_lookup[*src++ >> 6] << 5; | |
85 | val |= st7586_lookup[*src++ >> 6] << 2; | |
86 | val |= st7586_lookup[*src++ >> 6] >> 1; | |
87 | *dst++ = val; | |
88 | } | |
89 | } | |
90 | ||
91 | kfree(buf); | |
92 | } | |
93 | ||
94 | static int st7586_buf_copy(void *dst, struct drm_framebuffer *fb, | |
b051b345 | 95 | struct drm_rect *clip) |
eac99d4a DL |
96 | { |
97 | struct drm_gem_cma_object *cma_obj = drm_fb_cma_get_gem_obj(fb, 0); | |
98 | struct dma_buf_attachment *import_attach = cma_obj->base.import_attach; | |
99 | void *src = cma_obj->vaddr; | |
100 | int ret = 0; | |
101 | ||
102 | if (import_attach) { | |
103 | ret = dma_buf_begin_cpu_access(import_attach->dmabuf, | |
104 | DMA_FROM_DEVICE); | |
105 | if (ret) | |
106 | return ret; | |
107 | } | |
108 | ||
109 | st7586_xrgb8888_to_gray332(dst, src, fb, clip); | |
110 | ||
111 | if (import_attach) | |
112 | ret = dma_buf_end_cpu_access(import_attach->dmabuf, | |
113 | DMA_FROM_DEVICE); | |
114 | ||
115 | return ret; | |
116 | } | |
117 | ||
af741381 | 118 | static void st7586_fb_dirty(struct drm_framebuffer *fb, struct drm_rect *rect) |
eac99d4a DL |
119 | { |
120 | struct tinydrm_device *tdev = fb->dev->dev_private; | |
121 | struct mipi_dbi *mipi = mipi_dbi_from_tinydrm(tdev); | |
eac99d4a DL |
122 | int start, end; |
123 | int ret = 0; | |
124 | ||
eac99d4a | 125 | if (!mipi->enabled) |
af741381 | 126 | return; |
eac99d4a DL |
127 | |
128 | /* 3 pixels per byte, so grow clip to nearest multiple of 3 */ | |
b051b345 NT |
129 | rect->x1 = rounddown(rect->x1, 3); |
130 | rect->x2 = roundup(rect->x2, 3); | |
eac99d4a | 131 | |
b051b345 | 132 | DRM_DEBUG_KMS("Flushing [FB:%d] " DRM_RECT_FMT "\n", fb->base.id, DRM_RECT_ARG(rect)); |
eac99d4a | 133 | |
b051b345 | 134 | ret = st7586_buf_copy(mipi->tx_buf, fb, rect); |
eac99d4a | 135 | if (ret) |
af741381 | 136 | goto err_msg; |
eac99d4a DL |
137 | |
138 | /* Pixels are packed 3 per byte */ | |
b051b345 NT |
139 | start = rect->x1 / 3; |
140 | end = rect->x2 / 3; | |
eac99d4a DL |
141 | |
142 | mipi_dbi_command(mipi, MIPI_DCS_SET_COLUMN_ADDRESS, | |
143 | (start >> 8) & 0xFF, start & 0xFF, | |
144 | (end >> 8) & 0xFF, (end - 1) & 0xFF); | |
145 | mipi_dbi_command(mipi, MIPI_DCS_SET_PAGE_ADDRESS, | |
b051b345 NT |
146 | (rect->y1 >> 8) & 0xFF, rect->y1 & 0xFF, |
147 | (rect->y2 >> 8) & 0xFF, (rect->y2 - 1) & 0xFF); | |
eac99d4a DL |
148 | |
149 | ret = mipi_dbi_command_buf(mipi, MIPI_DCS_WRITE_MEMORY_START, | |
150 | (u8 *)mipi->tx_buf, | |
b051b345 | 151 | (end - start) * (rect->y2 - rect->y1)); |
af741381 NT |
152 | err_msg: |
153 | if (ret) | |
154 | dev_err_once(fb->dev->dev, "Failed to update display %d\n", ret); | |
eac99d4a DL |
155 | } |
156 | ||
af741381 NT |
157 | static void st7586_pipe_update(struct drm_simple_display_pipe *pipe, |
158 | struct drm_plane_state *old_state) | |
159 | { | |
160 | struct drm_plane_state *state = pipe->plane.state; | |
161 | struct drm_crtc *crtc = &pipe->crtc; | |
162 | struct drm_rect rect; | |
163 | ||
164 | if (drm_atomic_helper_damage_merged(old_state, state, &rect)) | |
165 | st7586_fb_dirty(state->fb, &rect); | |
166 | ||
167 | if (crtc->state->event) { | |
168 | spin_lock_irq(&crtc->dev->event_lock); | |
169 | drm_crtc_send_vblank_event(crtc, crtc->state->event); | |
170 | spin_unlock_irq(&crtc->dev->event_lock); | |
171 | crtc->state->event = NULL; | |
172 | } | |
173 | } | |
eac99d4a | 174 | |
f3bbc908 | 175 | static void st7586_pipe_enable(struct drm_simple_display_pipe *pipe, |
0c9c7fd0 VS |
176 | struct drm_crtc_state *crtc_state, |
177 | struct drm_plane_state *plane_state) | |
eac99d4a DL |
178 | { |
179 | struct tinydrm_device *tdev = pipe_to_tinydrm(pipe); | |
180 | struct mipi_dbi *mipi = mipi_dbi_from_tinydrm(tdev); | |
af741381 NT |
181 | struct drm_framebuffer *fb = plane_state->fb; |
182 | struct drm_rect rect = { | |
183 | .x1 = 0, | |
184 | .x2 = fb->width, | |
185 | .y1 = 0, | |
186 | .y2 = fb->height, | |
187 | }; | |
eac99d4a DL |
188 | int ret; |
189 | u8 addr_mode; | |
190 | ||
191 | DRM_DEBUG_KMS("\n"); | |
192 | ||
070ab128 NT |
193 | ret = mipi_dbi_poweron_reset(mipi); |
194 | if (ret) | |
eac99d4a | 195 | return; |
eac99d4a | 196 | |
070ab128 | 197 | mipi_dbi_command(mipi, ST7586_AUTO_READ_CTRL, 0x9f); |
eac99d4a DL |
198 | mipi_dbi_command(mipi, ST7586_OTP_RW_CTRL, 0x00); |
199 | ||
200 | msleep(10); | |
201 | ||
202 | mipi_dbi_command(mipi, ST7586_OTP_READ); | |
203 | ||
204 | msleep(20); | |
205 | ||
206 | mipi_dbi_command(mipi, ST7586_OTP_CTRL_OUT); | |
207 | mipi_dbi_command(mipi, MIPI_DCS_EXIT_SLEEP_MODE); | |
208 | mipi_dbi_command(mipi, MIPI_DCS_SET_DISPLAY_OFF); | |
209 | ||
210 | msleep(50); | |
211 | ||
212 | mipi_dbi_command(mipi, ST7586_SET_VOP_OFFSET, 0x00); | |
213 | mipi_dbi_command(mipi, ST7586_SET_VOP, 0xe3, 0x00); | |
214 | mipi_dbi_command(mipi, ST7586_SET_BIAS_SYSTEM, 0x02); | |
215 | mipi_dbi_command(mipi, ST7586_SET_BOOST_LEVEL, 0x04); | |
216 | mipi_dbi_command(mipi, ST7586_ENABLE_ANALOG, 0x1d); | |
217 | mipi_dbi_command(mipi, ST7586_SET_NLINE_INV, 0x00); | |
218 | mipi_dbi_command(mipi, ST7586_DISP_MODE_GRAY); | |
219 | mipi_dbi_command(mipi, ST7586_ENABLE_DDRAM, 0x02); | |
220 | ||
221 | switch (mipi->rotation) { | |
222 | default: | |
223 | addr_mode = 0x00; | |
224 | break; | |
225 | case 90: | |
226 | addr_mode = ST7586_DISP_CTRL_MY; | |
227 | break; | |
228 | case 180: | |
229 | addr_mode = ST7586_DISP_CTRL_MX | ST7586_DISP_CTRL_MY; | |
230 | break; | |
231 | case 270: | |
232 | addr_mode = ST7586_DISP_CTRL_MX; | |
233 | break; | |
234 | } | |
235 | mipi_dbi_command(mipi, MIPI_DCS_SET_ADDRESS_MODE, addr_mode); | |
236 | ||
237 | mipi_dbi_command(mipi, ST7586_SET_DISP_DUTY, 0x7f); | |
238 | mipi_dbi_command(mipi, ST7586_SET_PART_DISP, 0xa0); | |
239 | mipi_dbi_command(mipi, MIPI_DCS_SET_PARTIAL_AREA, 0x00, 0x00, 0x00, 0x77); | |
240 | mipi_dbi_command(mipi, MIPI_DCS_EXIT_INVERT_MODE); | |
241 | ||
242 | msleep(100); | |
243 | ||
af741381 NT |
244 | mipi->enabled = true; |
245 | st7586_fb_dirty(fb, &rect); | |
eac99d4a | 246 | |
af741381 | 247 | mipi_dbi_command(mipi, MIPI_DCS_SET_DISPLAY_ON); |
eac99d4a DL |
248 | } |
249 | ||
250 | static void st7586_pipe_disable(struct drm_simple_display_pipe *pipe) | |
251 | { | |
252 | struct tinydrm_device *tdev = pipe_to_tinydrm(pipe); | |
253 | struct mipi_dbi *mipi = mipi_dbi_from_tinydrm(tdev); | |
254 | ||
255 | DRM_DEBUG_KMS("\n"); | |
256 | ||
257 | if (!mipi->enabled) | |
258 | return; | |
259 | ||
260 | mipi_dbi_command(mipi, MIPI_DCS_SET_DISPLAY_OFF); | |
261 | mipi->enabled = false; | |
262 | } | |
263 | ||
264 | static const u32 st7586_formats[] = { | |
265 | DRM_FORMAT_XRGB8888, | |
266 | }; | |
267 | ||
268 | static int st7586_init(struct device *dev, struct mipi_dbi *mipi, | |
269 | const struct drm_simple_display_pipe_funcs *pipe_funcs, | |
270 | struct drm_driver *driver, const struct drm_display_mode *mode, | |
271 | unsigned int rotation) | |
272 | { | |
273 | size_t bufsize = (mode->vdisplay + 2) / 3 * mode->hdisplay; | |
274 | struct tinydrm_device *tdev = &mipi->tinydrm; | |
275 | int ret; | |
276 | ||
277 | mutex_init(&mipi->cmdlock); | |
278 | ||
279 | mipi->tx_buf = devm_kmalloc(dev, bufsize, GFP_KERNEL); | |
280 | if (!mipi->tx_buf) | |
281 | return -ENOMEM; | |
282 | ||
af741381 | 283 | ret = devm_tinydrm_init(dev, tdev, driver); |
eac99d4a DL |
284 | if (ret) |
285 | return ret; | |
286 | ||
96f2a9ae | 287 | ret = tinydrm_display_pipe_init(tdev->drm, &tdev->pipe, pipe_funcs, |
eac99d4a DL |
288 | DRM_MODE_CONNECTOR_VIRTUAL, |
289 | st7586_formats, | |
290 | ARRAY_SIZE(st7586_formats), | |
291 | mode, rotation); | |
292 | if (ret) | |
293 | return ret; | |
294 | ||
af741381 NT |
295 | drm_plane_enable_fb_damage_clips(&tdev->pipe.plane); |
296 | ||
eac99d4a DL |
297 | tdev->drm->mode_config.preferred_depth = 32; |
298 | mipi->rotation = rotation; | |
299 | ||
300 | drm_mode_config_reset(tdev->drm); | |
301 | ||
302 | DRM_DEBUG_KMS("preferred_depth=%u, rotation = %u\n", | |
303 | tdev->drm->mode_config.preferred_depth, rotation); | |
304 | ||
305 | return 0; | |
306 | } | |
307 | ||
308 | static const struct drm_simple_display_pipe_funcs st7586_pipe_funcs = { | |
309 | .enable = st7586_pipe_enable, | |
310 | .disable = st7586_pipe_disable, | |
af741381 | 311 | .update = st7586_pipe_update, |
ccc3b2b3 | 312 | .prepare_fb = drm_gem_fb_simple_display_pipe_prepare_fb, |
eac99d4a DL |
313 | }; |
314 | ||
315 | static const struct drm_display_mode st7586_mode = { | |
06db4b8b | 316 | DRM_SIMPLE_MODE(178, 128, 37, 27), |
eac99d4a DL |
317 | }; |
318 | ||
319 | DEFINE_DRM_GEM_CMA_FOPS(st7586_fops); | |
320 | ||
321 | static struct drm_driver st7586_driver = { | |
322 | .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME | | |
323 | DRIVER_ATOMIC, | |
324 | .fops = &st7586_fops, | |
3db8d37d | 325 | DRM_GEM_CMA_VMAP_DRIVER_OPS, |
eac99d4a DL |
326 | .debugfs_init = mipi_dbi_debugfs_init, |
327 | .name = "st7586", | |
328 | .desc = "Sitronix ST7586", | |
329 | .date = "20170801", | |
330 | .major = 1, | |
331 | .minor = 0, | |
332 | }; | |
333 | ||
334 | static const struct of_device_id st7586_of_match[] = { | |
335 | { .compatible = "lego,ev3-lcd" }, | |
336 | {}, | |
337 | }; | |
338 | MODULE_DEVICE_TABLE(of, st7586_of_match); | |
339 | ||
340 | static const struct spi_device_id st7586_id[] = { | |
341 | { "ev3-lcd", 0 }, | |
342 | { }, | |
343 | }; | |
344 | MODULE_DEVICE_TABLE(spi, st7586_id); | |
345 | ||
346 | static int st7586_probe(struct spi_device *spi) | |
347 | { | |
348 | struct device *dev = &spi->dev; | |
eac99d4a DL |
349 | struct mipi_dbi *mipi; |
350 | struct gpio_desc *a0; | |
351 | u32 rotation = 0; | |
352 | int ret; | |
353 | ||
354 | mipi = devm_kzalloc(dev, sizeof(*mipi), GFP_KERNEL); | |
355 | if (!mipi) | |
356 | return -ENOMEM; | |
357 | ||
358 | mipi->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); | |
359 | if (IS_ERR(mipi->reset)) { | |
e43e8181 | 360 | DRM_DEV_ERROR(dev, "Failed to get gpio 'reset'\n"); |
eac99d4a DL |
361 | return PTR_ERR(mipi->reset); |
362 | } | |
363 | ||
364 | a0 = devm_gpiod_get(dev, "a0", GPIOD_OUT_LOW); | |
365 | if (IS_ERR(a0)) { | |
e43e8181 | 366 | DRM_DEV_ERROR(dev, "Failed to get gpio 'a0'\n"); |
eac99d4a DL |
367 | return PTR_ERR(a0); |
368 | } | |
369 | ||
370 | device_property_read_u32(dev, "rotation", &rotation); | |
371 | ||
372 | ret = mipi_dbi_spi_init(spi, mipi, a0); | |
373 | if (ret) | |
374 | return ret; | |
375 | ||
376 | /* Cannot read from this controller via SPI */ | |
377 | mipi->read_commands = NULL; | |
378 | ||
379 | /* | |
380 | * we are using 8-bit data, so we are not actually swapping anything, | |
381 | * but setting mipi->swap_bytes makes mipi_dbi_typec3_command() do the | |
382 | * right thing and not use 16-bit transfers (which results in swapped | |
383 | * bytes on little-endian systems and causes out of order data to be | |
384 | * sent to the display). | |
385 | */ | |
386 | mipi->swap_bytes = true; | |
387 | ||
388 | ret = st7586_init(&spi->dev, mipi, &st7586_pipe_funcs, &st7586_driver, | |
389 | &st7586_mode, rotation); | |
390 | if (ret) | |
391 | return ret; | |
392 | ||
d0a51634 | 393 | spi_set_drvdata(spi, mipi->tinydrm.drm); |
eac99d4a | 394 | |
95a0cfe9 | 395 | return devm_tinydrm_register(&mipi->tinydrm); |
eac99d4a DL |
396 | } |
397 | ||
398 | static void st7586_shutdown(struct spi_device *spi) | |
399 | { | |
d0a51634 | 400 | drm_atomic_helper_shutdown(spi_get_drvdata(spi)); |
eac99d4a DL |
401 | } |
402 | ||
403 | static struct spi_driver st7586_spi_driver = { | |
404 | .driver = { | |
405 | .name = "st7586", | |
406 | .owner = THIS_MODULE, | |
407 | .of_match_table = st7586_of_match, | |
408 | }, | |
409 | .id_table = st7586_id, | |
410 | .probe = st7586_probe, | |
411 | .shutdown = st7586_shutdown, | |
412 | }; | |
413 | module_spi_driver(st7586_spi_driver); | |
414 | ||
415 | MODULE_DESCRIPTION("Sitronix ST7586 DRM driver"); | |
416 | MODULE_AUTHOR("David Lechner <david@lechnology.com>"); | |
417 | MODULE_LICENSE("GPL"); |