1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * DRM driver for Ilitek ILI9225 panels |
4 | * |
5 | * Copyright 2017 David Lechner <david@lechnology.com> |
6 | * |
7 | * Some code copied from mipi-dbi.c |
8 | * Copyright 2016 Noralf Trønnes |
9 | */ |
10 | |
11 | #include <linux/delay.h> |
12 | #include <linux/dma-buf.h> |
13 | #include <linux/gpio/consumer.h> |
14 | #include <linux/module.h> |
15 | #include <linux/property.h> |
16 | #include <linux/spi/spi.h> |
17 | #include <video/mipi_display.h> |
18 | |
19 | #include <drm/drm_atomic_helper.h> |
20 | #include <drm/drm_damage_helper.h> |
21 | #include <drm/drm_drv.h> |
22 | #include <drm/drm_fb_dma_helper.h> |
23 | #include <drm/drm_fbdev_generic.h> |
24 | #include <drm/drm_fourcc.h> |
25 | #include <drm/drm_framebuffer.h> |
26 | #include <drm/drm_gem_atomic_helper.h> |
27 | #include <drm/drm_gem_dma_helper.h> |
28 | #include <drm/drm_gem_framebuffer_helper.h> |
29 | #include <drm/drm_managed.h> |
30 | #include <drm/drm_mipi_dbi.h> |
31 | #include <drm/drm_rect.h> |
32 | |
33 | #define ILI9225_DRIVER_READ_CODE 0x00 |
34 | #define ILI9225_DRIVER_OUTPUT_CONTROL 0x01 |
35 | #define ILI9225_LCD_AC_DRIVING_CONTROL 0x02 |
36 | #define ILI9225_ENTRY_MODE 0x03 |
37 | #define ILI9225_DISPLAY_CONTROL_1 0x07 |
38 | #define ILI9225_BLANK_PERIOD_CONTROL_1 0x08 |
39 | #define ILI9225_FRAME_CYCLE_CONTROL 0x0b |
40 | #define ILI9225_INTERFACE_CONTROL 0x0c |
41 | #define ILI9225_OSCILLATION_CONTROL 0x0f |
42 | #define ILI9225_POWER_CONTROL_1 0x10 |
43 | #define ILI9225_POWER_CONTROL_2 0x11 |
44 | #define ILI9225_POWER_CONTROL_3 0x12 |
45 | #define ILI9225_POWER_CONTROL_4 0x13 |
46 | #define ILI9225_POWER_CONTROL_5 0x14 |
47 | #define ILI9225_VCI_RECYCLING 0x15 |
48 | #define ILI9225_RAM_ADDRESS_SET_1 0x20 |
49 | #define ILI9225_RAM_ADDRESS_SET_2 0x21 |
50 | #define ILI9225_WRITE_DATA_TO_GRAM 0x22 |
51 | #define ILI9225_SOFTWARE_RESET 0x28 |
52 | #define ILI9225_GATE_SCAN_CONTROL 0x30 |
53 | #define ILI9225_VERTICAL_SCROLL_1 0x31 |
54 | #define ILI9225_VERTICAL_SCROLL_2 0x32 |
55 | #define ILI9225_VERTICAL_SCROLL_3 0x33 |
56 | #define ILI9225_PARTIAL_DRIVING_POS_1 0x34 |
57 | #define ILI9225_PARTIAL_DRIVING_POS_2 0x35 |
58 | #define ILI9225_HORIZ_WINDOW_ADDR_1 0x36 |
59 | #define ILI9225_HORIZ_WINDOW_ADDR_2 0x37 |
60 | #define ILI9225_VERT_WINDOW_ADDR_1 0x38 |
61 | #define ILI9225_VERT_WINDOW_ADDR_2 0x39 |
62 | #define ILI9225_GAMMA_CONTROL_1 0x50 |
63 | #define ILI9225_GAMMA_CONTROL_2 0x51 |
64 | #define ILI9225_GAMMA_CONTROL_3 0x52 |
65 | #define ILI9225_GAMMA_CONTROL_4 0x53 |
66 | #define ILI9225_GAMMA_CONTROL_5 0x54 |
67 | #define ILI9225_GAMMA_CONTROL_6 0x55 |
68 | #define ILI9225_GAMMA_CONTROL_7 0x56 |
69 | #define ILI9225_GAMMA_CONTROL_8 0x57 |
70 | #define ILI9225_GAMMA_CONTROL_9 0x58 |
71 | #define ILI9225_GAMMA_CONTROL_10 0x59 |
72 | |
73 | static inline int ili9225_command(struct mipi_dbi *dbi, u8 cmd, u16 data) |
74 | { |
75 | u8 par[2] = { data >> 8, data & 0xff }; |
76 | |
77 | return mipi_dbi_command_buf(dbi, cmd, data: par, len: 2); |
78 | } |
79 | |
80 | static void ili9225_fb_dirty(struct iosys_map *src, struct drm_framebuffer *fb, |
81 | struct drm_rect *rect, struct drm_format_conv_state *fmtcnv_state) |
82 | { |
83 | struct mipi_dbi_dev *dbidev = drm_to_mipi_dbi_dev(drm: fb->dev); |
84 | unsigned int height = rect->y2 - rect->y1; |
85 | unsigned int width = rect->x2 - rect->x1; |
86 | struct mipi_dbi *dbi = &dbidev->dbi; |
87 | bool swap = dbi->swap_bytes; |
88 | u16 x_start, y_start; |
89 | u16 x1, x2, y1, y2; |
90 | int ret = 0; |
91 | bool full; |
92 | void *tr; |
93 | |
94 | full = width == fb->width && height == fb->height; |
95 | |
96 | DRM_DEBUG_KMS("Flushing [FB:%d] " DRM_RECT_FMT "\n" , fb->base.id, DRM_RECT_ARG(rect)); |
97 | |
98 | if (!dbi->dc || !full || swap || |
99 | fb->format->format == DRM_FORMAT_XRGB8888) { |
100 | tr = dbidev->tx_buf; |
101 | ret = mipi_dbi_buf_copy(dst: tr, src, fb, clip: rect, swap, fmtcnv_state); |
102 | if (ret) |
103 | goto err_msg; |
104 | } else { |
105 | tr = src->vaddr; /* TODO: Use mapping abstraction properly */ |
106 | } |
107 | |
108 | switch (dbidev->rotation) { |
109 | default: |
110 | x1 = rect->x1; |
111 | x2 = rect->x2 - 1; |
112 | y1 = rect->y1; |
113 | y2 = rect->y2 - 1; |
114 | x_start = x1; |
115 | y_start = y1; |
116 | break; |
117 | case 90: |
118 | x1 = rect->y1; |
119 | x2 = rect->y2 - 1; |
120 | y1 = fb->width - rect->x2; |
121 | y2 = fb->width - rect->x1 - 1; |
122 | x_start = x1; |
123 | y_start = y2; |
124 | break; |
125 | case 180: |
126 | x1 = fb->width - rect->x2; |
127 | x2 = fb->width - rect->x1 - 1; |
128 | y1 = fb->height - rect->y2; |
129 | y2 = fb->height - rect->y1 - 1; |
130 | x_start = x2; |
131 | y_start = y2; |
132 | break; |
133 | case 270: |
134 | x1 = fb->height - rect->y2; |
135 | x2 = fb->height - rect->y1 - 1; |
136 | y1 = rect->x1; |
137 | y2 = rect->x2 - 1; |
138 | x_start = x2; |
139 | y_start = y1; |
140 | break; |
141 | } |
142 | |
143 | ili9225_command(dbi, ILI9225_HORIZ_WINDOW_ADDR_1, data: x2); |
144 | ili9225_command(dbi, ILI9225_HORIZ_WINDOW_ADDR_2, data: x1); |
145 | ili9225_command(dbi, ILI9225_VERT_WINDOW_ADDR_1, data: y2); |
146 | ili9225_command(dbi, ILI9225_VERT_WINDOW_ADDR_2, data: y1); |
147 | |
148 | ili9225_command(dbi, ILI9225_RAM_ADDRESS_SET_1, data: x_start); |
149 | ili9225_command(dbi, ILI9225_RAM_ADDRESS_SET_2, data: y_start); |
150 | |
151 | ret = mipi_dbi_command_buf(dbi, ILI9225_WRITE_DATA_TO_GRAM, data: tr, |
152 | len: width * height * 2); |
153 | err_msg: |
154 | if (ret) |
155 | dev_err_once(fb->dev->dev, "Failed to update display %d\n" , ret); |
156 | } |
157 | |
158 | static void ili9225_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_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(state); |
163 | struct drm_framebuffer *fb = state->fb; |
164 | struct drm_rect rect; |
165 | int idx; |
166 | |
167 | if (!pipe->crtc.state->active) |
168 | return; |
169 | |
170 | if (!drm_dev_enter(dev: fb->dev, idx: &idx)) |
171 | return; |
172 | |
173 | if (drm_atomic_helper_damage_merged(old_state, state, rect: &rect)) |
174 | ili9225_fb_dirty(src: &shadow_plane_state->data[0], fb, rect: &rect, |
175 | fmtcnv_state: &shadow_plane_state->fmtcnv_state); |
176 | |
177 | drm_dev_exit(idx); |
178 | } |
179 | |
180 | static void ili9225_pipe_enable(struct drm_simple_display_pipe *pipe, |
181 | struct drm_crtc_state *crtc_state, |
182 | struct drm_plane_state *plane_state) |
183 | { |
184 | struct mipi_dbi_dev *dbidev = drm_to_mipi_dbi_dev(drm: pipe->crtc.dev); |
185 | struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(state: plane_state); |
186 | struct drm_framebuffer *fb = plane_state->fb; |
187 | struct device *dev = pipe->crtc.dev->dev; |
188 | struct mipi_dbi *dbi = &dbidev->dbi; |
189 | struct drm_rect rect = { |
190 | .x1 = 0, |
191 | .x2 = fb->width, |
192 | .y1 = 0, |
193 | .y2 = fb->height, |
194 | }; |
195 | int ret, idx; |
196 | u8 am_id; |
197 | |
198 | if (!drm_dev_enter(dev: pipe->crtc.dev, idx: &idx)) |
199 | return; |
200 | |
201 | DRM_DEBUG_KMS("\n" ); |
202 | |
203 | mipi_dbi_hw_reset(dbi); |
204 | |
205 | /* |
206 | * There don't seem to be two example init sequences that match, so |
207 | * using the one from the popular Arduino library for this display. |
208 | * https://github.com/Nkawu/TFT_22_ILI9225/blob/master/src/TFT_22_ILI9225.cpp |
209 | */ |
210 | |
211 | ret = ili9225_command(dbi, ILI9225_POWER_CONTROL_1, data: 0x0000); |
212 | if (ret) { |
213 | DRM_DEV_ERROR(dev, "Error sending command %d\n" , ret); |
214 | goto out_exit; |
215 | } |
216 | ili9225_command(dbi, ILI9225_POWER_CONTROL_2, data: 0x0000); |
217 | ili9225_command(dbi, ILI9225_POWER_CONTROL_3, data: 0x0000); |
218 | ili9225_command(dbi, ILI9225_POWER_CONTROL_4, data: 0x0000); |
219 | ili9225_command(dbi, ILI9225_POWER_CONTROL_5, data: 0x0000); |
220 | |
221 | msleep(msecs: 40); |
222 | |
223 | ili9225_command(dbi, ILI9225_POWER_CONTROL_2, data: 0x0018); |
224 | ili9225_command(dbi, ILI9225_POWER_CONTROL_3, data: 0x6121); |
225 | ili9225_command(dbi, ILI9225_POWER_CONTROL_4, data: 0x006f); |
226 | ili9225_command(dbi, ILI9225_POWER_CONTROL_5, data: 0x495f); |
227 | ili9225_command(dbi, ILI9225_POWER_CONTROL_1, data: 0x0800); |
228 | |
229 | msleep(msecs: 10); |
230 | |
231 | ili9225_command(dbi, ILI9225_POWER_CONTROL_2, data: 0x103b); |
232 | |
233 | msleep(msecs: 50); |
234 | |
235 | switch (dbidev->rotation) { |
236 | default: |
237 | am_id = 0x30; |
238 | break; |
239 | case 90: |
240 | am_id = 0x18; |
241 | break; |
242 | case 180: |
243 | am_id = 0x00; |
244 | break; |
245 | case 270: |
246 | am_id = 0x28; |
247 | break; |
248 | } |
249 | ili9225_command(dbi, ILI9225_DRIVER_OUTPUT_CONTROL, data: 0x011c); |
250 | ili9225_command(dbi, ILI9225_LCD_AC_DRIVING_CONTROL, data: 0x0100); |
251 | ili9225_command(dbi, ILI9225_ENTRY_MODE, data: 0x1000 | am_id); |
252 | ili9225_command(dbi, ILI9225_DISPLAY_CONTROL_1, data: 0x0000); |
253 | ili9225_command(dbi, ILI9225_BLANK_PERIOD_CONTROL_1, data: 0x0808); |
254 | ili9225_command(dbi, ILI9225_FRAME_CYCLE_CONTROL, data: 0x1100); |
255 | ili9225_command(dbi, ILI9225_INTERFACE_CONTROL, data: 0x0000); |
256 | ili9225_command(dbi, ILI9225_OSCILLATION_CONTROL, data: 0x0d01); |
257 | ili9225_command(dbi, ILI9225_VCI_RECYCLING, data: 0x0020); |
258 | ili9225_command(dbi, ILI9225_RAM_ADDRESS_SET_1, data: 0x0000); |
259 | ili9225_command(dbi, ILI9225_RAM_ADDRESS_SET_2, data: 0x0000); |
260 | |
261 | ili9225_command(dbi, ILI9225_GATE_SCAN_CONTROL, data: 0x0000); |
262 | ili9225_command(dbi, ILI9225_VERTICAL_SCROLL_1, data: 0x00db); |
263 | ili9225_command(dbi, ILI9225_VERTICAL_SCROLL_2, data: 0x0000); |
264 | ili9225_command(dbi, ILI9225_VERTICAL_SCROLL_3, data: 0x0000); |
265 | ili9225_command(dbi, ILI9225_PARTIAL_DRIVING_POS_1, data: 0x00db); |
266 | ili9225_command(dbi, ILI9225_PARTIAL_DRIVING_POS_2, data: 0x0000); |
267 | |
268 | ili9225_command(dbi, ILI9225_GAMMA_CONTROL_1, data: 0x0000); |
269 | ili9225_command(dbi, ILI9225_GAMMA_CONTROL_2, data: 0x0808); |
270 | ili9225_command(dbi, ILI9225_GAMMA_CONTROL_3, data: 0x080a); |
271 | ili9225_command(dbi, ILI9225_GAMMA_CONTROL_4, data: 0x000a); |
272 | ili9225_command(dbi, ILI9225_GAMMA_CONTROL_5, data: 0x0a08); |
273 | ili9225_command(dbi, ILI9225_GAMMA_CONTROL_6, data: 0x0808); |
274 | ili9225_command(dbi, ILI9225_GAMMA_CONTROL_7, data: 0x0000); |
275 | ili9225_command(dbi, ILI9225_GAMMA_CONTROL_8, data: 0x0a00); |
276 | ili9225_command(dbi, ILI9225_GAMMA_CONTROL_9, data: 0x0710); |
277 | ili9225_command(dbi, ILI9225_GAMMA_CONTROL_10, data: 0x0710); |
278 | |
279 | ili9225_command(dbi, ILI9225_DISPLAY_CONTROL_1, data: 0x0012); |
280 | |
281 | msleep(msecs: 50); |
282 | |
283 | ili9225_command(dbi, ILI9225_DISPLAY_CONTROL_1, data: 0x1017); |
284 | |
285 | ili9225_fb_dirty(src: &shadow_plane_state->data[0], fb, rect: &rect, |
286 | fmtcnv_state: &shadow_plane_state->fmtcnv_state); |
287 | |
288 | out_exit: |
289 | drm_dev_exit(idx); |
290 | } |
291 | |
292 | static void ili9225_pipe_disable(struct drm_simple_display_pipe *pipe) |
293 | { |
294 | struct mipi_dbi_dev *dbidev = drm_to_mipi_dbi_dev(drm: pipe->crtc.dev); |
295 | struct mipi_dbi *dbi = &dbidev->dbi; |
296 | |
297 | DRM_DEBUG_KMS("\n" ); |
298 | |
299 | /* |
300 | * This callback is not protected by drm_dev_enter/exit since we want to |
301 | * turn off the display on regular driver unload. It's highly unlikely |
302 | * that the underlying SPI controller is gone should this be called after |
303 | * unplug. |
304 | */ |
305 | |
306 | ili9225_command(dbi, ILI9225_DISPLAY_CONTROL_1, data: 0x0000); |
307 | msleep(msecs: 50); |
308 | ili9225_command(dbi, ILI9225_POWER_CONTROL_2, data: 0x0007); |
309 | msleep(msecs: 50); |
310 | ili9225_command(dbi, ILI9225_POWER_CONTROL_1, data: 0x0a02); |
311 | } |
312 | |
313 | static int ili9225_dbi_command(struct mipi_dbi *dbi, u8 *cmd, u8 *par, |
314 | size_t num) |
315 | { |
316 | struct spi_device *spi = dbi->spi; |
317 | unsigned int bpw = 8; |
318 | u32 speed_hz; |
319 | int ret; |
320 | |
321 | spi_bus_lock(ctlr: spi->controller); |
322 | gpiod_set_value_cansleep(desc: dbi->dc, value: 0); |
323 | speed_hz = mipi_dbi_spi_cmd_max_speed(spi, len: 1); |
324 | ret = mipi_dbi_spi_transfer(spi, speed_hz, bpw: 8, buf: cmd, len: 1); |
325 | spi_bus_unlock(ctlr: spi->controller); |
326 | if (ret || !num) |
327 | return ret; |
328 | |
329 | if (*cmd == ILI9225_WRITE_DATA_TO_GRAM && !dbi->swap_bytes) |
330 | bpw = 16; |
331 | |
332 | spi_bus_lock(ctlr: spi->controller); |
333 | gpiod_set_value_cansleep(desc: dbi->dc, value: 1); |
334 | speed_hz = mipi_dbi_spi_cmd_max_speed(spi, len: num); |
335 | ret = mipi_dbi_spi_transfer(spi, speed_hz, bpw, buf: par, len: num); |
336 | spi_bus_unlock(ctlr: spi->controller); |
337 | |
338 | return ret; |
339 | } |
340 | |
341 | static const struct drm_simple_display_pipe_funcs ili9225_pipe_funcs = { |
342 | .mode_valid = mipi_dbi_pipe_mode_valid, |
343 | .enable = ili9225_pipe_enable, |
344 | .disable = ili9225_pipe_disable, |
345 | .update = ili9225_pipe_update, |
346 | .begin_fb_access = mipi_dbi_pipe_begin_fb_access, |
347 | .end_fb_access = mipi_dbi_pipe_end_fb_access, |
348 | .reset_plane = mipi_dbi_pipe_reset_plane, |
349 | .duplicate_plane_state = mipi_dbi_pipe_duplicate_plane_state, |
350 | .destroy_plane_state = mipi_dbi_pipe_destroy_plane_state, |
351 | }; |
352 | |
353 | static const struct drm_display_mode ili9225_mode = { |
354 | DRM_SIMPLE_MODE(176, 220, 35, 44), |
355 | }; |
356 | |
357 | DEFINE_DRM_GEM_DMA_FOPS(ili9225_fops); |
358 | |
359 | static const struct drm_driver ili9225_driver = { |
360 | .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, |
361 | .fops = &ili9225_fops, |
362 | DRM_GEM_DMA_DRIVER_OPS_VMAP, |
363 | .name = "ili9225" , |
364 | .desc = "Ilitek ILI9225" , |
365 | .date = "20171106" , |
366 | .major = 1, |
367 | .minor = 0, |
368 | }; |
369 | |
370 | static const struct of_device_id ili9225_of_match[] = { |
371 | { .compatible = "vot,v220hf01a-t" }, |
372 | {}, |
373 | }; |
374 | MODULE_DEVICE_TABLE(of, ili9225_of_match); |
375 | |
376 | static const struct spi_device_id ili9225_id[] = { |
377 | { "v220hf01a-t" , 0 }, |
378 | { }, |
379 | }; |
380 | MODULE_DEVICE_TABLE(spi, ili9225_id); |
381 | |
382 | static int ili9225_probe(struct spi_device *spi) |
383 | { |
384 | struct device *dev = &spi->dev; |
385 | struct mipi_dbi_dev *dbidev; |
386 | struct drm_device *drm; |
387 | struct mipi_dbi *dbi; |
388 | struct gpio_desc *rs; |
389 | u32 rotation = 0; |
390 | int ret; |
391 | |
392 | dbidev = devm_drm_dev_alloc(dev, &ili9225_driver, |
393 | struct mipi_dbi_dev, drm); |
394 | if (IS_ERR(ptr: dbidev)) |
395 | return PTR_ERR(ptr: dbidev); |
396 | |
397 | dbi = &dbidev->dbi; |
398 | drm = &dbidev->drm; |
399 | |
400 | dbi->reset = devm_gpiod_get(dev, con_id: "reset" , flags: GPIOD_OUT_HIGH); |
401 | if (IS_ERR(ptr: dbi->reset)) |
402 | return dev_err_probe(dev, err: PTR_ERR(ptr: dbi->reset), fmt: "Failed to get GPIO 'reset'\n" ); |
403 | |
404 | rs = devm_gpiod_get(dev, con_id: "rs" , flags: GPIOD_OUT_LOW); |
405 | if (IS_ERR(ptr: rs)) |
406 | return dev_err_probe(dev, err: PTR_ERR(ptr: rs), fmt: "Failed to get GPIO 'rs'\n" ); |
407 | |
408 | device_property_read_u32(dev, propname: "rotation" , val: &rotation); |
409 | |
410 | ret = mipi_dbi_spi_init(spi, dbi, dc: rs); |
411 | if (ret) |
412 | return ret; |
413 | |
414 | /* override the command function set in mipi_dbi_spi_init() */ |
415 | dbi->command = ili9225_dbi_command; |
416 | |
417 | ret = mipi_dbi_dev_init(dbidev, funcs: &ili9225_pipe_funcs, mode: &ili9225_mode, rotation); |
418 | if (ret) |
419 | return ret; |
420 | |
421 | drm_mode_config_reset(dev: drm); |
422 | |
423 | ret = drm_dev_register(dev: drm, flags: 0); |
424 | if (ret) |
425 | return ret; |
426 | |
427 | spi_set_drvdata(spi, data: drm); |
428 | |
429 | drm_fbdev_generic_setup(dev: drm, preferred_bpp: 0); |
430 | |
431 | return 0; |
432 | } |
433 | |
434 | static void ili9225_remove(struct spi_device *spi) |
435 | { |
436 | struct drm_device *drm = spi_get_drvdata(spi); |
437 | |
438 | drm_dev_unplug(dev: drm); |
439 | drm_atomic_helper_shutdown(dev: drm); |
440 | } |
441 | |
442 | static void ili9225_shutdown(struct spi_device *spi) |
443 | { |
444 | drm_atomic_helper_shutdown(dev: spi_get_drvdata(spi)); |
445 | } |
446 | |
447 | static struct spi_driver ili9225_spi_driver = { |
448 | .driver = { |
449 | .name = "ili9225" , |
450 | .owner = THIS_MODULE, |
451 | .of_match_table = ili9225_of_match, |
452 | }, |
453 | .id_table = ili9225_id, |
454 | .probe = ili9225_probe, |
455 | .remove = ili9225_remove, |
456 | .shutdown = ili9225_shutdown, |
457 | }; |
458 | module_spi_driver(ili9225_spi_driver); |
459 | |
460 | MODULE_DESCRIPTION("Ilitek ILI9225 DRM driver" ); |
461 | MODULE_AUTHOR("David Lechner <david@lechnology.com>" ); |
462 | MODULE_LICENSE("GPL" ); |
463 | |