b2d907183628f9b832a443b9bea9b7c4d7cbc0af
[openwrt/staging/neocturne.git] /
1 From f493a1250d55c4a61d94f631757186e8beef9d90 Mon Sep 17 00:00:00 2001
2 From: Dave Stevenson <dave.stevenson@raspberrypi.com>
3 Date: Wed, 26 Jan 2022 16:02:31 +0000
4 Subject: [PATCH] drm/panel: Add panel driver for TDO Y17B based panels
5
6 The Top DisplayOptoelectronics (TDO) T17B driver chip is used
7 in the TL040HDS20CT panel (found in the Pimoroni HyperPixel4
8 Square display) and potentially other displays.
9 The driver chip supports SPI for configuration and DPI
10 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-tdo-y17p.c | 279 +++++++++++++++++++++++++
17 3 files changed, 291 insertions(+)
18 create mode 100644 drivers/gpu/drm/panel/panel-tdo-y17p.c
19
20 --- a/drivers/gpu/drm/panel/Kconfig
21 +++ b/drivers/gpu/drm/panel/Kconfig
22 @@ -562,6 +562,17 @@ config DRM_PANEL_SONY_ACX565AKM
23 Say Y here if you want to enable support for the Sony ACX565AKM
24 800x600 3.5" panel (found on the Nokia N900).
25
26 +config DRM_PANEL_TPO_Y17P
27 + tristate "TDO Y17P-based panels"
28 + depends on OF && SPI
29 + depends on DRM_KMS_HELPER
30 + depends on DRM_KMS_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 + TDO Y17P controller.
36 +
37 config DRM_PANEL_TDO_TL070WSH30
38 tristate "TDO TL070WSH30 DSI panel"
39 depends on OF
40 --- a/drivers/gpu/drm/panel/Makefile
41 +++ b/drivers/gpu/drm/panel/Makefile
42 @@ -58,6 +58,7 @@ obj-$(CONFIG_DRM_PANEL_SITRONIX_ST7789V)
43 obj-$(CONFIG_DRM_PANEL_SONY_ACX424AKP) += panel-sony-acx424akp.o
44 obj-$(CONFIG_DRM_PANEL_SONY_ACX565AKM) += panel-sony-acx565akm.o
45 obj-$(CONFIG_DRM_PANEL_TDO_TL070WSH30) += panel-tdo-tl070wsh30.o
46 +obj-$(CONFIG_DRM_PANEL_TPO_Y17P) += panel-tdo-y17p.o
47 obj-$(CONFIG_DRM_PANEL_TPO_TD028TTEC1) += panel-tpo-td028ttec1.o
48 obj-$(CONFIG_DRM_PANEL_TPO_TD043MTEA1) += panel-tpo-td043mtea1.o
49 obj-$(CONFIG_DRM_PANEL_TPO_TPG110) += panel-tpo-tpg110.o
50 --- /dev/null
51 +++ b/drivers/gpu/drm/panel/panel-tdo-y17p.c
52 @@ -0,0 +1,279 @@
53 +// SPDX-License-Identifier: GPL-2.0-only
54 +/*
55 + * TDO Y17P TFT LCD drm_panel driver.
56 + *
57 + * SPI configured DPI display controller
58 + * Copyright (C) 2022 Raspberry Pi Ltd
59 + *
60 + * Derived from drivers/drm/gpu/panel/panel-sitronix-st7789v.c
61 + * Copyright (C) 2017 Free Electrons
62 + */
63 +
64 +#include <drm/drm_modes.h>
65 +#include <drm/drm_panel.h>
66 +
67 +#include <linux/bitops.h>
68 +#include <linux/gpio/consumer.h>
69 +#include <linux/media-bus-format.h>
70 +#include <linux/module.h>
71 +#include <linux/of_device.h>
72 +#include <linux/regmap.h>
73 +#include <linux/regulator/consumer.h>
74 +#include <linux/spi/spi.h>
75 +
76 +#include <video/mipi_display.h>
77 +#include <video/of_videomode.h>
78 +#include <video/videomode.h>
79 +
80 +struct tdo_y17p {
81 + struct drm_panel panel;
82 + struct spi_device *spi;
83 + struct gpio_desc *reset;
84 + struct regulator *power;
85 + u32 bus_format;
86 +};
87 +
88 +static const u16 panel_init[] = {
89 + 0x0ff, 0x1ff, 0x198, 0x106, 0x104, 0x101, 0x008, 0x110,
90 + 0x021, 0x109, 0x030, 0x102, 0x031, 0x100, 0x040, 0x110,
91 + 0x041, 0x155, 0x042, 0x102, 0x043, 0x109, 0x044, 0x107,
92 + 0x050, 0x178, 0x051, 0x178, 0x052, 0x100, 0x053, 0x16d,
93 + 0x060, 0x107, 0x061, 0x100, 0x062, 0x108, 0x063, 0x100,
94 + 0x0a0, 0x100, 0x0a1, 0x107, 0x0a2, 0x10c, 0x0a3, 0x10b,
95 + 0x0a4, 0x103, 0x0a5, 0x107, 0x0a6, 0x106, 0x0a7, 0x104,
96 + 0x0a8, 0x108, 0x0a9, 0x10c, 0x0aa, 0x113, 0x0ab, 0x106,
97 + 0x0ac, 0x10d, 0x0ad, 0x119, 0x0ae, 0x110, 0x0af, 0x100,
98 + 0x0c0, 0x100, 0x0c1, 0x107, 0x0c2, 0x10c, 0x0c3, 0x10b,
99 + 0x0c4, 0x103, 0x0c5, 0x107, 0x0c6, 0x107, 0x0c7, 0x104,
100 + 0x0c8, 0x108, 0x0c9, 0x10c, 0x0ca, 0x113, 0x0cb, 0x106,
101 + 0x0cc, 0x10d, 0x0cd, 0x118, 0x0ce, 0x110, 0x0cf, 0x100,
102 + 0x0ff, 0x1ff, 0x198, 0x106, 0x104, 0x106, 0x000, 0x120,
103 + 0x001, 0x10a, 0x002, 0x100, 0x003, 0x100, 0x004, 0x101,
104 + 0x005, 0x101, 0x006, 0x198, 0x007, 0x106, 0x008, 0x101,
105 + 0x009, 0x180, 0x00a, 0x100, 0x00b, 0x100, 0x00c, 0x101,
106 + 0x00d, 0x101, 0x00e, 0x100, 0x00f, 0x100, 0x010, 0x1f0,
107 + 0x011, 0x1f4, 0x012, 0x101, 0x013, 0x100, 0x014, 0x100,
108 + 0x015, 0x1c0, 0x016, 0x108, 0x017, 0x100, 0x018, 0x100,
109 + 0x019, 0x100, 0x01a, 0x100, 0x01b, 0x100, 0x01c, 0x100,
110 + 0x01d, 0x100, 0x020, 0x101, 0x021, 0x123, 0x022, 0x145,
111 + 0x023, 0x167, 0x024, 0x101, 0x025, 0x123, 0x026, 0x145,
112 + 0x027, 0x167, 0x030, 0x111, 0x031, 0x111, 0x032, 0x100,
113 + 0x033, 0x1ee, 0x034, 0x1ff, 0x035, 0x1bb, 0x036, 0x1aa,
114 + 0x037, 0x1dd, 0x038, 0x1cc, 0x039, 0x166, 0x03a, 0x177,
115 + 0x03b, 0x122, 0x03c, 0x122, 0x03d, 0x122, 0x03e, 0x122,
116 + 0x03f, 0x122, 0x040, 0x122, 0x052, 0x110, 0x053, 0x110,
117 + 0x0ff, 0x1ff, 0x198, 0x106, 0x104, 0x107, 0x018, 0x11d,
118 + 0x017, 0x122, 0x002, 0x177, 0x026, 0x1b2, 0x0e1, 0x179,
119 + 0x0ff, 0x1ff, 0x198, 0x106, 0x104, 0x100, 0x03a, 0x160,
120 + 0x035, 0x100, 0x011, 0x100,
121 +};
122 +
123 +#define NUM_INIT_REGS ARRAY_SIZE(panel_init)
124 +
125 +static inline struct tdo_y17p *panel_to_tdo_y17p(struct drm_panel *panel)
126 +{
127 + return container_of(panel, struct tdo_y17p, panel);
128 +}
129 +
130 +static int tdo_y17p_write_msg(struct tdo_y17p *ctx, const u16 *msg, unsigned int len)
131 +{
132 + struct spi_transfer xfer = { };
133 + struct spi_message spi;
134 +
135 + spi_message_init(&spi);
136 +
137 + xfer.tx_buf = msg;
138 + xfer.bits_per_word = 9;
139 + xfer.len = sizeof(u16) * len;
140 +
141 + spi_message_add_tail(&xfer, &spi);
142 + return spi_sync(ctx->spi, &spi);
143 +}
144 +
145 +static const struct drm_display_mode tdo_y17pe_720x720_mode = {
146 + .clock = 36720,
147 + .hdisplay = 720,
148 + .hsync_start = 720 + 20,
149 + .hsync_end = 720 + 20 + 20,
150 + .htotal = 720 + 20 + 20 + 40,
151 + .vdisplay = 720,
152 + .vsync_start = 720 + 15,
153 + .vsync_end = 720 + 15 + 15,
154 + .vtotal = 720 + 15 + 15 + 15,
155 + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC,
156 +};
157 +
158 +static int tdo_y17p_get_modes(struct drm_panel *panel,
159 + struct drm_connector *connector)
160 +{
161 + struct tdo_y17p *ctx = panel_to_tdo_y17p(panel);
162 + struct drm_display_mode *mode;
163 +
164 + mode = drm_mode_duplicate(connector->dev, &tdo_y17pe_720x720_mode);
165 + if (!mode) {
166 + dev_err(panel->dev, "failed to add mode %ux%ux@%u\n",
167 + tdo_y17pe_720x720_mode.hdisplay,
168 + tdo_y17pe_720x720_mode.vdisplay,
169 + drm_mode_vrefresh(&tdo_y17pe_720x720_mode));
170 + return -ENOMEM;
171 + }
172 +
173 + drm_mode_set_name(mode);
174 +
175 + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
176 + drm_mode_probed_add(connector, mode);
177 +
178 + connector->display_info.width_mm = 72;
179 + connector->display_info.height_mm = 72;
180 + drm_display_info_set_bus_formats(&connector->display_info,
181 + &ctx->bus_format, 1);
182 + connector->display_info.bus_flags = 0;
183 +
184 + return 1;
185 +}
186 +
187 +static int tdo_y17p_prepare(struct drm_panel *panel)
188 +{
189 + struct tdo_y17p *ctx = panel_to_tdo_y17p(panel);
190 + int ret;
191 +
192 + ret = regulator_enable(ctx->power);
193 + if (ret)
194 + return ret;
195 +
196 + ret = tdo_y17p_write_msg(ctx, panel_init, NUM_INIT_REGS);
197 +
198 + msleep(120);
199 +
200 + return ret;
201 +}
202 +
203 +static int tdo_y17p_enable(struct drm_panel *panel)
204 +{
205 + struct tdo_y17p *ctx = panel_to_tdo_y17p(panel);
206 + const u16 msg[] = { MIPI_DCS_SET_DISPLAY_ON };
207 + int ret;
208 +
209 + ret = tdo_y17p_write_msg(ctx, msg, ARRAY_SIZE(msg));
210 +
211 + return ret;
212 +}
213 +
214 +static int tdo_y17p_disable(struct drm_panel *panel)
215 +{
216 + struct tdo_y17p *ctx = panel_to_tdo_y17p(panel);
217 + const u16 msg[] = { MIPI_DCS_SET_DISPLAY_OFF };
218 + int ret;
219 +
220 + ret = tdo_y17p_write_msg(ctx, msg, ARRAY_SIZE(msg));
221 +
222 + return ret;
223 +}
224 +
225 +static int tdo_y17p_unprepare(struct drm_panel *panel)
226 +{
227 + struct tdo_y17p *ctx = panel_to_tdo_y17p(panel);
228 + const u16 msg[] = { MIPI_DCS_ENTER_SLEEP_MODE };
229 + int ret;
230 +
231 + ret = tdo_y17p_write_msg(ctx, msg, ARRAY_SIZE(msg));
232 +
233 + return ret;
234 +}
235 +
236 +static const struct drm_panel_funcs tdo_y17p_drm_funcs = {
237 + .disable = tdo_y17p_disable,
238 + .enable = tdo_y17p_enable,
239 + .get_modes = tdo_y17p_get_modes,
240 + .prepare = tdo_y17p_prepare,
241 + .unprepare = tdo_y17p_unprepare,
242 +};
243 +
244 +static const struct of_device_id tdo_y17p_of_match[] = {
245 + { .compatible = "tdo,tl040hds20ct",
246 + .data = (void *)MEDIA_BUS_FMT_BGR888_1X24,
247 + }, {
248 + .compatible = "pimoroni,hyperpixel4square",
249 + .data = (void *)MEDIA_BUS_FMT_BGR666_1X24_CPADHI,
250 + }, {
251 + .compatible = "tdo,y17p",
252 + .data = (void *)MEDIA_BUS_FMT_BGR888_1X24,
253 + }, {
254 + /* sentinel */
255 + }
256 +};
257 +MODULE_DEVICE_TABLE(of, tdo_y17p_of_match);
258 +
259 +static int tdo_y17p_probe(struct spi_device *spi)
260 +{
261 + const struct of_device_id *id;
262 + struct tdo_y17p *ctx;
263 + int ret;
264 +
265 + ctx = devm_kzalloc(&spi->dev, sizeof(*ctx), GFP_KERNEL);
266 + if (!ctx)
267 + return -ENOMEM;
268 +
269 + id = of_match_node(tdo_y17p_of_match, spi->dev.of_node);
270 + if (!id)
271 + return -ENODEV;
272 +
273 + ctx->bus_format = (u32)id->data;
274 +
275 + spi_set_drvdata(spi, ctx);
276 + ctx->spi = spi;
277 +
278 + drm_panel_init(&ctx->panel, &spi->dev, &tdo_y17p_drm_funcs,
279 + DRM_MODE_CONNECTOR_DPI);
280 +
281 + ctx->power = devm_regulator_get(&spi->dev, "power");
282 + if (IS_ERR(ctx->power))
283 + return PTR_ERR(ctx->power);
284 +
285 + ctx->reset = devm_gpiod_get_optional(&spi->dev, "reset", GPIOD_OUT_LOW);
286 + if (IS_ERR(ctx->reset)) {
287 + dev_err(&spi->dev, "Couldn't get our reset line\n");
288 + return PTR_ERR(ctx->reset);
289 + }
290 +
291 + ret = drm_panel_of_backlight(&ctx->panel);
292 + if (ret)
293 + return ret;
294 +
295 + drm_panel_add(&ctx->panel);
296 +
297 + return 0;
298 +}
299 +
300 +static int tdo_y17p_remove(struct spi_device *spi)
301 +{
302 + struct tdo_y17p *ctx = spi_get_drvdata(spi);
303 +
304 + drm_panel_remove(&ctx->panel);
305 +
306 + return 0;
307 +}
308 +
309 +static const struct spi_device_id tdo_y17p_ids[] = {
310 + { "tl040hds20ct", 0 },
311 + { "hyperpixel4square", 0 },
312 + { "y17p", 0 },
313 + { /* sentinel */ }
314 +};
315 +
316 +MODULE_DEVICE_TABLE(spi, tdo_y17p_ids);
317 +
318 +static struct spi_driver tdo_y17p_driver = {
319 + .probe = tdo_y17p_probe,
320 + .remove = tdo_y17p_remove,
321 + .driver = {
322 + .name = "tdo_y17p",
323 + .of_match_table = tdo_y17p_of_match,
324 + },
325 + .id_table = tdo_y17p_ids,
326 +};
327 +module_spi_driver(tdo_y17p_driver);
328 +
329 +MODULE_AUTHOR("Dave Stevenson <dave.stevenson@raspberrypi.com>");
330 +MODULE_DESCRIPTION("TDO Y17P LCD panel driver");
331 +MODULE_LICENSE("GPL v2");