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