b098df3ffafe4513cc923d8985b4984364ab5137
[openwrt/staging/linusw.git] /
1 From 7a5df2564ca1e83585326c183dedd9db662aa389 Mon Sep 17 00:00:00 2001
2 From: Dave Stevenson <dave.stevenson@raspberrypi.com>
3 Date: Wed, 5 Jan 2022 19:14:48 +0000
4 Subject: [PATCH 0345/1085] drm/panel: Add panel driver for Ilitek ILI9806E
5 panel
6
7 The Ilitek ILI9806E driver is used in the Pimoroni HyperPixel4
8 and potentially other displays. Whilst it can support multiple
9 interfaces, this driver only accounts for SPI configuration and
10 DPI video data.
11
12 Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
13 ---
14 drivers/gpu/drm/panel/Kconfig | 11 +
15 drivers/gpu/drm/panel/Makefile | 1 +
16 drivers/gpu/drm/panel/panel-ilitek-ili9806e.c | 484 ++++++++++++++++++
17 3 files changed, 496 insertions(+)
18 create mode 100644 drivers/gpu/drm/panel/panel-ilitek-ili9806e.c
19
20 --- a/drivers/gpu/drm/panel/Kconfig
21 +++ b/drivers/gpu/drm/panel/Kconfig
22 @@ -194,6 +194,17 @@ config DRM_PANEL_ILITEK_ILI9341
23 QVGA (240x320) RGB panels. support serial & parallel rgb
24 interface.
25
26 +config DRM_PANEL_ILITEK_ILI9806E
27 + tristate "Ilitek ILI9806E-based panels"
28 + depends on OF && SPI
29 + select DRM_KMS_HELPER
30 + depends on DRM_GEM_CMA_HELPER
31 + depends on BACKLIGHT_CLASS_DEVICE
32 + select DRM_MIPI_DBI
33 + help
34 + Say Y if you want to enable support for panels based on the
35 + Ilitek ILI9806e controller.
36 +
37 config DRM_PANEL_ILITEK_ILI9881C
38 tristate "Ilitek ILI9881C-based panels"
39 depends on OF
40 --- a/drivers/gpu/drm/panel/Makefile
41 +++ b/drivers/gpu/drm/panel/Makefile
42 @@ -17,6 +17,7 @@ obj-$(CONFIG_DRM_PANEL_FEIYANG_FY07024DI
43 obj-$(CONFIG_DRM_PANEL_HIMAX_HX8394) += panel-himax-hx8394.o
44 obj-$(CONFIG_DRM_PANEL_ILITEK_IL9322) += panel-ilitek-ili9322.o
45 obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9341) += panel-ilitek-ili9341.o
46 +obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9806E) += panel-ilitek-ili9806e.o
47 obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9881C) += panel-ilitek-ili9881c.o
48 obj-$(CONFIG_DRM_PANEL_INNOLUX_EJ030NA) += panel-innolux-ej030na.o
49 obj-$(CONFIG_DRM_PANEL_INNOLUX_P079ZCA) += panel-innolux-p079zca.o
50 --- /dev/null
51 +++ b/drivers/gpu/drm/panel/panel-ilitek-ili9806e.c
52 @@ -0,0 +1,484 @@
53 +// SPDX-License-Identifier: GPL-2.0-only
54 +/*
55 + * Ilitek ILI9806E TFT LCD drm_panel driver.
56 + *
57 + * Copyright (C) 2022 Raspberry Pi Ltd
58 + *
59 + * Derived from drivers/drm/gpu/panel/panel-sitronix-st7789v.c
60 + * Copyright (C) 2017 Free Electrons
61 + */
62 +
63 +#include <drm/drm_modes.h>
64 +#include <drm/drm_panel.h>
65 +
66 +#include <linux/bitops.h>
67 +#include <linux/gpio/consumer.h>
68 +#include <linux/media-bus-format.h>
69 +#include <linux/module.h>
70 +#include <linux/of_device.h>
71 +#include <linux/regmap.h>
72 +#include <linux/regulator/consumer.h>
73 +#include <linux/spi/spi.h>
74 +
75 +#include <video/mipi_display.h>
76 +#include <video/of_videomode.h>
77 +#include <video/videomode.h>
78 +
79 +struct ili9806 {
80 + struct drm_panel panel;
81 + struct spi_device *spi;
82 + struct gpio_desc *reset;
83 + struct regulator *power;
84 + u32 bus_format;
85 +};
86 +
87 +#define ILI9806_DATA BIT(8)
88 +
89 +#define ILI9806_MAX_MSG_LEN 6
90 +
91 +struct ili9806e_msg {
92 + unsigned int len;
93 + u16 msg[ILI9806_MAX_MSG_LEN];
94 +};
95 +
96 +#define ILI9806_SET_PAGE(page) \
97 + { \
98 + .len = 6, \
99 + .msg = { \
100 + 0xFF, \
101 + ILI9806_DATA | 0xFF, \
102 + ILI9806_DATA | 0x98, \
103 + ILI9806_DATA | 0x06, \
104 + ILI9806_DATA | 0x04, \
105 + ILI9806_DATA | (page) \
106 + }, \
107 + }
108 +
109 +#define ILI9806_SET_REG_PARAM(reg, data) \
110 + { \
111 + .len = 2, \
112 + .msg = { \
113 + (reg), \
114 + ILI9806_DATA | (data), \
115 + }, \
116 + }
117 +
118 +#define ILI9806_SET_REG(reg) \
119 + { \
120 + .len = 1, \
121 + .msg = { (reg) }, \
122 + }
123 +
124 +static const struct ili9806e_msg panel_init[] = {
125 + ILI9806_SET_PAGE(1),
126 +
127 + /* interface mode
128 + * SEPT_SDIO = 0 (spi interface transfer through SDA pin)
129 + * SDO_STATUS = 1 (always output, but without output tri-state)
130 + */
131 + ILI9806_SET_REG_PARAM(0x08, 0x10),
132 + /* display control
133 + * VSPL = 1 (vertical sync polarity)
134 + * HSPL = 0 (horizontal sync polarity)
135 + * DPL = 0 (PCLK polarity)
136 + * EPL = 1 (data enable polarity)
137 + */
138 + ILI9806_SET_REG_PARAM(0x21, 0x0d),
139 + /* resolution control (0x02 = 480x800) */
140 + ILI9806_SET_REG_PARAM(0x30, 0x02),
141 + /* display inversion control (0x00 = column inversion) */
142 + ILI9806_SET_REG_PARAM(0x31, 0x00),
143 + /* power control
144 + * EXB1T = 0 (internal charge pump)
145 + * EXT_CPCK_SEL = 1 (pump clock control signal = output 2 x waveform)
146 + * BT = 0 (DDVDH / DDVDL voltage = VCI x 2 / VCI x -2)
147 + */
148 + ILI9806_SET_REG_PARAM(0x40, 0x10),
149 + /* power control
150 + * DDVDH_CLP = 5.6 (DDVDH clamp leve)
151 + * DDVDL_CLP = -5.6 (DDVDL clamp leve)
152 + */
153 + ILI9806_SET_REG_PARAM(0x41, 0x55),
154 + /* power control
155 + * VGH_CP = 2DDVDH - DDVDL (step up factor for VGH)
156 + * VGL_CP = DDVDL + VCL - VCIP (step up factor for VGL)
157 + */
158 + ILI9806_SET_REG_PARAM(0x42, 0x02),
159 + /* power control
160 + * VGH_CLPEN = 0 (disable VGH clamp level)
161 + * VGH_CLP = 9 (15.0 VGH clamp level - but this is disabled so not used?)
162 + */
163 + ILI9806_SET_REG_PARAM(0x43, 0x84),
164 + /* power control
165 + * VGL_CLPEN = 0 (disable VGL clamp level)
166 + * VGL_CLP = 9 (-11.0 VGL clamp level - but this is disabled so not used?)
167 + */
168 + ILI9806_SET_REG_PARAM(0x44, 0x84),
169 +
170 + /* power control
171 + * VREG1OUT voltage for positive gamma?
172 + */
173 + ILI9806_SET_REG_PARAM(0x50, 0x78),
174 + /* power control
175 + * VREG2OUT voltage for negative gamma?
176 + */
177 + ILI9806_SET_REG_PARAM(0x51, 0x78),
178 +
179 + ILI9806_SET_REG_PARAM(0x52, 0x00),
180 + ILI9806_SET_REG_PARAM(0x53, 0x77),
181 + ILI9806_SET_REG_PARAM(0x57, 0x60),
182 + ILI9806_SET_REG_PARAM(0x60, 0x07),
183 + ILI9806_SET_REG_PARAM(0x61, 0x00),
184 + ILI9806_SET_REG_PARAM(0x62, 0x08),
185 + ILI9806_SET_REG_PARAM(0x63, 0x00),
186 + ILI9806_SET_REG_PARAM(0xA0, 0x00),
187 + ILI9806_SET_REG_PARAM(0xA1, 0x07),
188 + ILI9806_SET_REG_PARAM(0xA2, 0x0C),
189 + ILI9806_SET_REG_PARAM(0xA3, 0x0B),
190 + ILI9806_SET_REG_PARAM(0xA4, 0x03),
191 + ILI9806_SET_REG_PARAM(0xA5, 0x07),
192 + ILI9806_SET_REG_PARAM(0xA6, 0x06),
193 + ILI9806_SET_REG_PARAM(0xA7, 0x04),
194 + ILI9806_SET_REG_PARAM(0xA8, 0x08),
195 + ILI9806_SET_REG_PARAM(0xA9, 0x0C),
196 + ILI9806_SET_REG_PARAM(0xAA, 0x13),
197 + ILI9806_SET_REG_PARAM(0xAB, 0x06),
198 + ILI9806_SET_REG_PARAM(0xAC, 0x0D),
199 + ILI9806_SET_REG_PARAM(0xAD, 0x19),
200 + ILI9806_SET_REG_PARAM(0xAE, 0x10),
201 + ILI9806_SET_REG_PARAM(0xAF, 0x00),
202 + /* negative gamma control
203 + * set the gray scale voltage to adjust the gamma characteristics of the panel
204 + */
205 + ILI9806_SET_REG_PARAM(0xC0, 0x00),
206 + ILI9806_SET_REG_PARAM(0xC1, 0x07),
207 + ILI9806_SET_REG_PARAM(0xC2, 0x0C),
208 + ILI9806_SET_REG_PARAM(0xC3, 0x0B),
209 + ILI9806_SET_REG_PARAM(0xC4, 0x03),
210 + ILI9806_SET_REG_PARAM(0xC5, 0x07),
211 + ILI9806_SET_REG_PARAM(0xC6, 0x07),
212 + ILI9806_SET_REG_PARAM(0xC7, 0x04),
213 + ILI9806_SET_REG_PARAM(0xC8, 0x08),
214 + ILI9806_SET_REG_PARAM(0xC9, 0x0C),
215 + ILI9806_SET_REG_PARAM(0xCA, 0x13),
216 + ILI9806_SET_REG_PARAM(0xCB, 0x06),
217 + ILI9806_SET_REG_PARAM(0xCC, 0x0D),
218 + ILI9806_SET_REG_PARAM(0xCD, 0x18),
219 + ILI9806_SET_REG_PARAM(0xCE, 0x10),
220 + ILI9806_SET_REG_PARAM(0xCF, 0x00),
221 +
222 + ILI9806_SET_PAGE(6),
223 +
224 + ILI9806_SET_REG_PARAM(0x00, 0x20),
225 + ILI9806_SET_REG_PARAM(0x01, 0x0A),
226 + ILI9806_SET_REG_PARAM(0x02, 0x00),
227 + ILI9806_SET_REG_PARAM(0x03, 0x00),
228 + ILI9806_SET_REG_PARAM(0x04, 0x01),
229 + ILI9806_SET_REG_PARAM(0x05, 0x01),
230 + ILI9806_SET_REG_PARAM(0x06, 0x98),
231 + ILI9806_SET_REG_PARAM(0x07, 0x06),
232 + ILI9806_SET_REG_PARAM(0x08, 0x01),
233 + ILI9806_SET_REG_PARAM(0x09, 0x80),
234 + ILI9806_SET_REG_PARAM(0x0A, 0x00),
235 + ILI9806_SET_REG_PARAM(0x0B, 0x00),
236 + ILI9806_SET_REG_PARAM(0x0C, 0x01),
237 + ILI9806_SET_REG_PARAM(0x0D, 0x01),
238 + ILI9806_SET_REG_PARAM(0x0E, 0x00),
239 + ILI9806_SET_REG_PARAM(0x0F, 0x00),
240 + ILI9806_SET_REG_PARAM(0x10, 0xF0),
241 + ILI9806_SET_REG_PARAM(0x11, 0xF4),
242 + ILI9806_SET_REG_PARAM(0x12, 0x01),
243 + ILI9806_SET_REG_PARAM(0x13, 0x00),
244 + ILI9806_SET_REG_PARAM(0x14, 0x00),
245 + ILI9806_SET_REG_PARAM(0x15, 0xC0),
246 + ILI9806_SET_REG_PARAM(0x16, 0x08),
247 + ILI9806_SET_REG_PARAM(0x17, 0x00),
248 + ILI9806_SET_REG_PARAM(0x18, 0x00),
249 + ILI9806_SET_REG_PARAM(0x19, 0x00),
250 + ILI9806_SET_REG_PARAM(0x1A, 0x00),
251 + ILI9806_SET_REG_PARAM(0x1B, 0x00),
252 + ILI9806_SET_REG_PARAM(0x1C, 0x00),
253 + ILI9806_SET_REG_PARAM(0x1D, 0x00),
254 + ILI9806_SET_REG_PARAM(0x20, 0x01),
255 + ILI9806_SET_REG_PARAM(0x21, 0x23),
256 + ILI9806_SET_REG_PARAM(0x22, 0x45),
257 + ILI9806_SET_REG_PARAM(0x23, 0x67),
258 + ILI9806_SET_REG_PARAM(0x24, 0x01),
259 + ILI9806_SET_REG_PARAM(0x25, 0x23),
260 + ILI9806_SET_REG_PARAM(0x26, 0x45),
261 + ILI9806_SET_REG_PARAM(0x27, 0x67),
262 + ILI9806_SET_REG_PARAM(0x30, 0x11),
263 + ILI9806_SET_REG_PARAM(0x31, 0x11),
264 + ILI9806_SET_REG_PARAM(0x32, 0x00),
265 + ILI9806_SET_REG_PARAM(0x33, 0xEE),
266 + ILI9806_SET_REG_PARAM(0x34, 0xFF),
267 + ILI9806_SET_REG_PARAM(0x35, 0xBB),
268 + ILI9806_SET_REG_PARAM(0x36, 0xAA),
269 + ILI9806_SET_REG_PARAM(0x37, 0xDD),
270 + ILI9806_SET_REG_PARAM(0x38, 0xCC),
271 + ILI9806_SET_REG_PARAM(0x39, 0x66),
272 + ILI9806_SET_REG_PARAM(0x3A, 0x77),
273 + ILI9806_SET_REG_PARAM(0x3B, 0x22),
274 + ILI9806_SET_REG_PARAM(0x3C, 0x22),
275 + ILI9806_SET_REG_PARAM(0x3D, 0x22),
276 + ILI9806_SET_REG_PARAM(0x3E, 0x22),
277 + ILI9806_SET_REG_PARAM(0x3F, 0x22),
278 + ILI9806_SET_REG_PARAM(0x40, 0x22),
279 + /* register doesn't exist on page 6? */
280 + ILI9806_SET_REG_PARAM(0x52, 0x10),
281 + /* doesn't make sense, not valid according to datasheet */
282 + ILI9806_SET_REG_PARAM(0x53, 0x10),
283 + /* doesn't make sense, not valid according to datasheet */
284 + ILI9806_SET_REG_PARAM(0x54, 0x13),
285 +
286 + ILI9806_SET_PAGE(7),
287 +
288 + /* enable VREG */
289 + ILI9806_SET_REG_PARAM(0x18, 0x1D),
290 + /* enable VGL_REG */
291 + ILI9806_SET_REG_PARAM(0x17, 0x22),
292 + /* register doesn't exist on page 7? */
293 + ILI9806_SET_REG_PARAM(0x02, 0x77),
294 + /* register doesn't exist on page 7? */
295 + ILI9806_SET_REG_PARAM(0x26, 0xB2),
296 + /* register doesn't exist on page 7? */
297 + ILI9806_SET_REG_PARAM(0xE1, 0x79),
298 +
299 + ILI9806_SET_PAGE(0),
300 +
301 + ILI9806_SET_REG_PARAM(MIPI_DCS_SET_PIXEL_FORMAT,
302 + MIPI_DCS_PIXEL_FMT_18BIT << 4),
303 + ILI9806_SET_REG_PARAM(MIPI_DCS_SET_TEAR_ON, 0x00),
304 + ILI9806_SET_REG(MIPI_DCS_EXIT_SLEEP_MODE),
305 +};
306 +
307 +#define NUM_INIT_REGS ARRAY_SIZE(panel_init)
308 +
309 +static inline struct ili9806 *panel_to_ili9806(struct drm_panel *panel)
310 +{
311 + return container_of(panel, struct ili9806, panel);
312 +}
313 +
314 +static int ili9806_write_msg(struct ili9806 *ctx, const struct ili9806e_msg *msg)
315 +{
316 + struct spi_transfer xfer = { };
317 + struct spi_message spi;
318 + //u16 txbuf[] = { msg->, ILI9806_DATA | data };
319 +
320 + spi_message_init(&spi);
321 +
322 + xfer.tx_buf = msg->msg;
323 + xfer.bits_per_word = 9;
324 + xfer.len = sizeof(u16) * msg->len;
325 +
326 + spi_message_add_tail(&xfer, &spi);
327 + return spi_sync(ctx->spi, &spi);
328 +}
329 +
330 +static int ili9806e_write_msg_list(struct ili9806 *ctx,
331 + const struct ili9806e_msg msgs[],
332 + unsigned int num_msgs)
333 +{
334 + int ret, i;
335 +
336 + for (i = 0; i < num_msgs; i++) {
337 + ret = ili9806_write_msg(ctx, &msgs[i]);
338 + if (ret)
339 + break;
340 + }
341 +
342 + return ret;
343 +}
344 +
345 +static const struct drm_display_mode ili9806e_480x800_mode = {
346 + .clock = 32000,
347 + .hdisplay = 480,
348 + .hsync_start = 480 + 10,
349 + .hsync_end = 480 + 10 + 16,
350 + .htotal = 480 + 10 + 16 + 59,
351 + .vdisplay = 800,
352 + .vsync_start = 800 + 15,
353 + .vsync_end = 800 + 15 + 113,
354 + .vtotal = 800 + 15 + 113 + 15,
355 + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
356 +};
357 +
358 +static int ili9806_get_modes(struct drm_panel *panel,
359 + struct drm_connector *connector)
360 +{
361 + struct ili9806 *ctx = panel_to_ili9806(panel);
362 + struct drm_display_mode *mode;
363 +
364 + mode = drm_mode_duplicate(connector->dev, &ili9806e_480x800_mode);
365 + if (!mode) {
366 + dev_err(panel->dev, "failed to add mode %ux%ux@%u\n",
367 + ili9806e_480x800_mode.hdisplay,
368 + ili9806e_480x800_mode.vdisplay,
369 + drm_mode_vrefresh(&ili9806e_480x800_mode));
370 + return -ENOMEM;
371 + }
372 +
373 + drm_mode_set_name(mode);
374 +
375 + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
376 + drm_mode_probed_add(connector, mode);
377 +
378 + connector->display_info.width_mm = 61;
379 + connector->display_info.height_mm = 103;
380 + drm_display_info_set_bus_formats(&connector->display_info,
381 + &ctx->bus_format, 1);
382 + connector->display_info.bus_flags =
383 + DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE;
384 +
385 + return 1;
386 +}
387 +
388 +static int ili9806_prepare(struct drm_panel *panel)
389 +{
390 + struct ili9806 *ctx = panel_to_ili9806(panel);
391 + int ret;
392 +
393 + ret = regulator_enable(ctx->power);
394 + if (ret)
395 + return ret;
396 +
397 + ret = ili9806e_write_msg_list(ctx, panel_init, NUM_INIT_REGS);
398 +
399 + return ret;
400 +}
401 +
402 +static int ili9806_enable(struct drm_panel *panel)
403 +{
404 + struct ili9806 *ctx = panel_to_ili9806(panel);
405 + const struct ili9806e_msg msg = ILI9806_SET_REG(MIPI_DCS_SET_DISPLAY_ON);
406 + int ret;
407 +
408 + ret = ili9806_write_msg(ctx, &msg);
409 +
410 + return ret;
411 +}
412 +
413 +static int ili9806_disable(struct drm_panel *panel)
414 +{
415 + struct ili9806 *ctx = panel_to_ili9806(panel);
416 + const struct ili9806e_msg msg = ILI9806_SET_REG(MIPI_DCS_SET_DISPLAY_OFF);
417 + int ret;
418 +
419 + ret = ili9806_write_msg(ctx, &msg);
420 +
421 + return ret;
422 +}
423 +
424 +static int ili9806_unprepare(struct drm_panel *panel)
425 +{
426 + struct ili9806 *ctx = panel_to_ili9806(panel);
427 + const struct ili9806e_msg msg = ILI9806_SET_REG(MIPI_DCS_ENTER_SLEEP_MODE);
428 + int ret;
429 +
430 + ret = ili9806_write_msg(ctx, &msg);
431 +
432 + return ret;
433 +}
434 +
435 +static const struct drm_panel_funcs ili9806_drm_funcs = {
436 + .disable = ili9806_disable,
437 + .enable = ili9806_enable,
438 + .get_modes = ili9806_get_modes,
439 + .prepare = ili9806_prepare,
440 + .unprepare = ili9806_unprepare,
441 +};
442 +
443 +static const struct of_device_id ili9806_of_match[] = {
444 + { .compatible = "txw,txw397017s2",
445 + .data = (void *)MEDIA_BUS_FMT_RGB888_1X24,
446 + }, {
447 + .compatible = "pimoroni,hyperpixel4",
448 + .data = (void *)MEDIA_BUS_FMT_RGB666_1X24_CPADHI,
449 + }, {
450 + .compatible = "ilitek,ili9806e",
451 + .data = (void *)MEDIA_BUS_FMT_RGB888_1X24,
452 + }, {
453 + /* sentinel */
454 + }
455 +};
456 +MODULE_DEVICE_TABLE(of, ili9806_of_match);
457 +
458 +static int ili9806_probe(struct spi_device *spi)
459 +{
460 + const struct ili9806e_msg panel_reset[] = {
461 + ILI9806_SET_PAGE(0),
462 + ILI9806_SET_REG_PARAM(0x01, 0x00)
463 + };
464 + const struct of_device_id *id;
465 + struct ili9806 *ctx;
466 + int ret;
467 +
468 + ctx = devm_kzalloc(&spi->dev, sizeof(*ctx), GFP_KERNEL);
469 + if (!ctx)
470 + return -ENOMEM;
471 +
472 + id = of_match_node(ili9806_of_match, spi->dev.of_node);
473 + if (!id)
474 + return -ENODEV;
475 +
476 + ctx->bus_format = (u32)(uintptr_t)id->data;
477 +
478 + spi_set_drvdata(spi, ctx);
479 + ctx->spi = spi;
480 +
481 + drm_panel_init(&ctx->panel, &spi->dev, &ili9806_drm_funcs,
482 + DRM_MODE_CONNECTOR_DPI);
483 +
484 + ctx->power = devm_regulator_get(&spi->dev, "power");
485 + if (IS_ERR(ctx->power))
486 + return PTR_ERR(ctx->power);
487 +
488 + ctx->reset = devm_gpiod_get_optional(&spi->dev, "reset", GPIOD_OUT_LOW);
489 + if (IS_ERR(ctx->reset)) {
490 + dev_err(&spi->dev, "Couldn't get our reset line\n");
491 + return PTR_ERR(ctx->reset);
492 + }
493 +
494 + /* Soft reset */
495 + ili9806e_write_msg_list(ctx, panel_reset, ARRAY_SIZE(panel_reset));
496 + msleep(200);
497 +
498 + ret = drm_panel_of_backlight(&ctx->panel);
499 + if (ret)
500 + return ret;
501 +
502 + drm_panel_add(&ctx->panel);
503 +
504 + return 0;
505 +}
506 +
507 +static void ili9806_remove(struct spi_device *spi)
508 +{
509 + struct ili9806 *ctx = spi_get_drvdata(spi);
510 +
511 + drm_panel_remove(&ctx->panel);
512 +}
513 +
514 +static const struct spi_device_id ili9806_ids[] = {
515 + { "txw397017s2", 0 },
516 + { "ili9806e", 0 },
517 + { "hyperpixel4", 0 },
518 + { /* sentinel */ }
519 +};
520 +
521 +MODULE_DEVICE_TABLE(spi, ili9806_ids);
522 +
523 +static struct spi_driver ili9806_driver = {
524 + .probe = ili9806_probe,
525 + .remove = ili9806_remove,
526 + .driver = {
527 + .name = "ili9806e",
528 + .of_match_table = ili9806_of_match,
529 + },
530 + .id_table = ili9806_ids,
531 +};
532 +module_spi_driver(ili9806_driver);
533 +
534 +MODULE_AUTHOR("Dave Stevenson <dave.stevenson@raspberrypi.com>");
535 +MODULE_DESCRIPTION("ili9806 LCD panel driver");
536 +MODULE_LICENSE("GPL v2");