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