Merge tag 'v5.15.57' into rpi-5.15.y
[platform/kernel/linux-rpi.git] / drivers / gpu / drm / panel / panel-tdo-y17p.c
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * TDO Y17P TFT LCD drm_panel driver.
4  *
5  * SPI configured DPI display controller
6  * Copyright (C) 2022 Raspberry Pi Ltd
7  *
8  * Derived from drivers/drm/gpu/panel/panel-sitronix-st7789v.c
9  * Copyright (C) 2017 Free Electrons
10  */
11
12 #include <drm/drm_modes.h>
13 #include <drm/drm_panel.h>
14
15 #include <linux/bitops.h>
16 #include <linux/gpio/consumer.h>
17 #include <linux/media-bus-format.h>
18 #include <linux/module.h>
19 #include <linux/of_device.h>
20 #include <linux/regmap.h>
21 #include <linux/regulator/consumer.h>
22 #include <linux/spi/spi.h>
23
24 #include <video/mipi_display.h>
25 #include <video/of_videomode.h>
26 #include <video/videomode.h>
27
28 struct tdo_y17p {
29         struct drm_panel panel;
30         struct spi_device *spi;
31         struct gpio_desc *reset;
32         struct regulator *power;
33         u32 bus_format;
34 };
35
36 static const u16 panel_init[] = {
37         0x0ff, 0x1ff, 0x198, 0x106, 0x104, 0x101, 0x008, 0x110,
38         0x021, 0x109, 0x030, 0x102, 0x031, 0x100, 0x040, 0x110,
39         0x041, 0x155, 0x042, 0x102, 0x043, 0x109, 0x044, 0x107,
40         0x050, 0x178, 0x051, 0x178, 0x052, 0x100, 0x053, 0x16d,
41         0x060, 0x107, 0x061, 0x100, 0x062, 0x108, 0x063, 0x100,
42         0x0a0, 0x100, 0x0a1, 0x107, 0x0a2, 0x10c, 0x0a3, 0x10b,
43         0x0a4, 0x103, 0x0a5, 0x107, 0x0a6, 0x106, 0x0a7, 0x104,
44         0x0a8, 0x108, 0x0a9, 0x10c, 0x0aa, 0x113, 0x0ab, 0x106,
45         0x0ac, 0x10d, 0x0ad, 0x119, 0x0ae, 0x110, 0x0af, 0x100,
46         0x0c0, 0x100, 0x0c1, 0x107, 0x0c2, 0x10c, 0x0c3, 0x10b,
47         0x0c4, 0x103, 0x0c5, 0x107, 0x0c6, 0x107, 0x0c7, 0x104,
48         0x0c8, 0x108, 0x0c9, 0x10c, 0x0ca, 0x113, 0x0cb, 0x106,
49         0x0cc, 0x10d, 0x0cd, 0x118, 0x0ce, 0x110, 0x0cf, 0x100,
50         0x0ff, 0x1ff, 0x198, 0x106, 0x104, 0x106, 0x000, 0x120,
51         0x001, 0x10a, 0x002, 0x100, 0x003, 0x100, 0x004, 0x101,
52         0x005, 0x101, 0x006, 0x198, 0x007, 0x106, 0x008, 0x101,
53         0x009, 0x180, 0x00a, 0x100, 0x00b, 0x100, 0x00c, 0x101,
54         0x00d, 0x101, 0x00e, 0x100, 0x00f, 0x100, 0x010, 0x1f0,
55         0x011, 0x1f4, 0x012, 0x101, 0x013, 0x100, 0x014, 0x100,
56         0x015, 0x1c0, 0x016, 0x108, 0x017, 0x100, 0x018, 0x100,
57         0x019, 0x100, 0x01a, 0x100, 0x01b, 0x100, 0x01c, 0x100,
58         0x01d, 0x100, 0x020, 0x101, 0x021, 0x123, 0x022, 0x145,
59         0x023, 0x167, 0x024, 0x101, 0x025, 0x123, 0x026, 0x145,
60         0x027, 0x167, 0x030, 0x111, 0x031, 0x111, 0x032, 0x100,
61         0x033, 0x1ee, 0x034, 0x1ff, 0x035, 0x1bb, 0x036, 0x1aa,
62         0x037, 0x1dd, 0x038, 0x1cc, 0x039, 0x166, 0x03a, 0x177,
63         0x03b, 0x122, 0x03c, 0x122, 0x03d, 0x122, 0x03e, 0x122,
64         0x03f, 0x122, 0x040, 0x122, 0x052, 0x110, 0x053, 0x110,
65         0x0ff, 0x1ff, 0x198, 0x106, 0x104, 0x107, 0x018, 0x11d,
66         0x017, 0x122, 0x002, 0x177, 0x026, 0x1b2, 0x0e1, 0x179,
67         0x0ff, 0x1ff, 0x198, 0x106, 0x104, 0x100, 0x03a, 0x160,
68         0x035, 0x100, 0x011, 0x100,
69 };
70
71 #define NUM_INIT_REGS ARRAY_SIZE(panel_init)
72
73 static inline struct tdo_y17p *panel_to_tdo_y17p(struct drm_panel *panel)
74 {
75         return container_of(panel, struct tdo_y17p, panel);
76 }
77
78 static int tdo_y17p_write_msg(struct tdo_y17p *ctx, const u16 *msg, unsigned int len)
79 {
80         struct spi_transfer xfer = { };
81         struct spi_message spi;
82
83         spi_message_init(&spi);
84
85         xfer.tx_buf = msg;
86         xfer.bits_per_word = 9;
87         xfer.len = sizeof(u16) * len;
88
89         spi_message_add_tail(&xfer, &spi);
90         return spi_sync(ctx->spi, &spi);
91 }
92
93 static const struct drm_display_mode tdo_y17pe_720x720_mode = {
94         .clock = 36720,
95         .hdisplay = 720,
96         .hsync_start = 720 + 20,
97         .hsync_end = 720 + 20 + 20,
98         .htotal = 720 + 20 + 20 + 40,
99         .vdisplay = 720,
100         .vsync_start = 720 + 15,
101         .vsync_end = 720 + 15 + 15,
102         .vtotal = 720 + 15 + 15 + 15,
103         .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC,
104 };
105
106 static int tdo_y17p_get_modes(struct drm_panel *panel,
107                               struct drm_connector *connector)
108 {
109         struct tdo_y17p *ctx = panel_to_tdo_y17p(panel);
110         struct drm_display_mode *mode;
111
112         mode = drm_mode_duplicate(connector->dev, &tdo_y17pe_720x720_mode);
113         if (!mode) {
114                 dev_err(panel->dev, "failed to add mode %ux%ux@%u\n",
115                         tdo_y17pe_720x720_mode.hdisplay,
116                         tdo_y17pe_720x720_mode.vdisplay,
117                         drm_mode_vrefresh(&tdo_y17pe_720x720_mode));
118                 return -ENOMEM;
119         }
120
121         drm_mode_set_name(mode);
122
123         mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
124         drm_mode_probed_add(connector, mode);
125
126         connector->display_info.width_mm = 72;
127         connector->display_info.height_mm = 72;
128         drm_display_info_set_bus_formats(&connector->display_info,
129                                          &ctx->bus_format, 1);
130         connector->display_info.bus_flags = 0;
131
132         return 1;
133 }
134
135 static int tdo_y17p_prepare(struct drm_panel *panel)
136 {
137         struct tdo_y17p *ctx = panel_to_tdo_y17p(panel);
138         int ret;
139
140         ret = regulator_enable(ctx->power);
141         if (ret)
142                 return ret;
143
144         ret = tdo_y17p_write_msg(ctx, panel_init, NUM_INIT_REGS);
145
146         msleep(120);
147
148         return ret;
149 }
150
151 static int tdo_y17p_enable(struct drm_panel *panel)
152 {
153         struct tdo_y17p *ctx = panel_to_tdo_y17p(panel);
154         const u16 msg[] = { MIPI_DCS_SET_DISPLAY_ON };
155         int ret;
156
157         ret = tdo_y17p_write_msg(ctx, msg, ARRAY_SIZE(msg));
158
159         return ret;
160 }
161
162 static int tdo_y17p_disable(struct drm_panel *panel)
163 {
164         struct tdo_y17p *ctx = panel_to_tdo_y17p(panel);
165         const u16 msg[] = { MIPI_DCS_SET_DISPLAY_OFF };
166         int ret;
167
168         ret = tdo_y17p_write_msg(ctx, msg, ARRAY_SIZE(msg));
169
170         return ret;
171 }
172
173 static int tdo_y17p_unprepare(struct drm_panel *panel)
174 {
175         struct tdo_y17p *ctx = panel_to_tdo_y17p(panel);
176         const u16 msg[] = { MIPI_DCS_ENTER_SLEEP_MODE };
177         int ret;
178
179         ret = tdo_y17p_write_msg(ctx, msg, ARRAY_SIZE(msg));
180
181         return ret;
182 }
183
184 static const struct drm_panel_funcs tdo_y17p_drm_funcs = {
185         .disable        = tdo_y17p_disable,
186         .enable         = tdo_y17p_enable,
187         .get_modes      = tdo_y17p_get_modes,
188         .prepare        = tdo_y17p_prepare,
189         .unprepare      = tdo_y17p_unprepare,
190 };
191
192 static const struct of_device_id tdo_y17p_of_match[] = {
193         {       .compatible = "tdo,tl040hds20ct",
194                 .data = (void *)MEDIA_BUS_FMT_BGR888_1X24,
195         }, {
196                 .compatible = "pimoroni,hyperpixel4square",
197                 .data = (void *)MEDIA_BUS_FMT_BGR666_1X24_CPADHI,
198         }, {
199                 .compatible = "tdo,y17p",
200                 .data = (void *)MEDIA_BUS_FMT_BGR888_1X24,
201         }, {
202                 /* sentinel */
203         }
204 };
205 MODULE_DEVICE_TABLE(of, tdo_y17p_of_match);
206
207 static int tdo_y17p_probe(struct spi_device *spi)
208 {
209         const struct of_device_id *id;
210         struct tdo_y17p *ctx;
211         int ret;
212
213         ctx = devm_kzalloc(&spi->dev, sizeof(*ctx), GFP_KERNEL);
214         if (!ctx)
215                 return -ENOMEM;
216
217         id = of_match_node(tdo_y17p_of_match, spi->dev.of_node);
218         if (!id)
219                 return -ENODEV;
220
221         ctx->bus_format = (u32)(uintptr_t)id->data;
222
223         spi_set_drvdata(spi, ctx);
224         ctx->spi = spi;
225
226         drm_panel_init(&ctx->panel, &spi->dev, &tdo_y17p_drm_funcs,
227                        DRM_MODE_CONNECTOR_DPI);
228
229         ctx->power = devm_regulator_get(&spi->dev, "power");
230         if (IS_ERR(ctx->power))
231                 return PTR_ERR(ctx->power);
232
233         ctx->reset = devm_gpiod_get_optional(&spi->dev, "reset", GPIOD_OUT_LOW);
234         if (IS_ERR(ctx->reset)) {
235                 dev_err(&spi->dev, "Couldn't get our reset line\n");
236                 return PTR_ERR(ctx->reset);
237         }
238
239         ret = drm_panel_of_backlight(&ctx->panel);
240         if (ret)
241                 return ret;
242
243         drm_panel_add(&ctx->panel);
244
245         return 0;
246 }
247
248 static int tdo_y17p_remove(struct spi_device *spi)
249 {
250         struct tdo_y17p *ctx = spi_get_drvdata(spi);
251
252         drm_panel_remove(&ctx->panel);
253
254         return 0;
255 }
256
257 static const struct spi_device_id tdo_y17p_ids[] = {
258         { "tl040hds20ct", 0 },
259         { "hyperpixel4square", 0 },
260         { "y17p", 0 },
261         { /* sentinel */ }
262 };
263
264 MODULE_DEVICE_TABLE(spi, tdo_y17p_ids);
265
266 static struct spi_driver tdo_y17p_driver = {
267         .probe = tdo_y17p_probe,
268         .remove = tdo_y17p_remove,
269         .driver = {
270                 .name = "tdo_y17p",
271                 .of_match_table = tdo_y17p_of_match,
272         },
273         .id_table = tdo_y17p_ids,
274 };
275 module_spi_driver(tdo_y17p_driver);
276
277 MODULE_AUTHOR("Dave Stevenson <dave.stevenson@raspberrypi.com>");
278 MODULE_DESCRIPTION("TDO Y17P LCD panel driver");
279 MODULE_LICENSE("GPL v2");