diff options
Diffstat (limited to 'drivers/gpu/drm/panel/panel-tpo-tpg110.c')
-rw-r--r-- | drivers/gpu/drm/panel/panel-tpo-tpg110.c | 496 |
1 files changed, 496 insertions, 0 deletions
diff --git a/drivers/gpu/drm/panel/panel-tpo-tpg110.c b/drivers/gpu/drm/panel/panel-tpo-tpg110.c new file mode 100644 index 000000000000..5a9f8f4d5d24 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-tpo-tpg110.c | |||
@@ -0,0 +1,496 @@ | |||
1 | // SPDX-License-Identifier: GPL-2.0 | ||
2 | /* | ||
3 | * Panel driver for the TPO TPG110 400CH LTPS TFT LCD Single Chip | ||
4 | * Digital Driver. | ||
5 | * | ||
6 | * This chip drives a TFT LCD, so it does not know what kind of | ||
7 | * display is actually connected to it, so the width and height of that | ||
8 | * display needs to be supplied from the machine configuration. | ||
9 | * | ||
10 | * Author: | ||
11 | * Linus Walleij <linus.walleij@linaro.org> | ||
12 | */ | ||
13 | #include <drm/drm_modes.h> | ||
14 | #include <drm/drm_panel.h> | ||
15 | #include <drm/drm_print.h> | ||
16 | |||
17 | #include <linux/backlight.h> | ||
18 | #include <linux/bitops.h> | ||
19 | #include <linux/delay.h> | ||
20 | #include <linux/gpio/consumer.h> | ||
21 | #include <linux/init.h> | ||
22 | #include <linux/kernel.h> | ||
23 | #include <linux/module.h> | ||
24 | #include <linux/platform_device.h> | ||
25 | #include <linux/spi/spi.h> | ||
26 | |||
27 | #define TPG110_TEST 0x00 | ||
28 | #define TPG110_CHIPID 0x01 | ||
29 | #define TPG110_CTRL1 0x02 | ||
30 | #define TPG110_RES_MASK GENMASK(2, 0) | ||
31 | #define TPG110_RES_800X480 0x07 | ||
32 | #define TPG110_RES_640X480 0x06 | ||
33 | #define TPG110_RES_480X272 0x05 | ||
34 | #define TPG110_RES_480X640 0x04 | ||
35 | #define TPG110_RES_480X272_D 0x01 /* Dual scan: outputs 800x480 */ | ||
36 | #define TPG110_RES_400X240_D 0x00 /* Dual scan: outputs 800x480 */ | ||
37 | #define TPG110_CTRL2 0x03 | ||
38 | #define TPG110_CTRL2_PM BIT(0) | ||
39 | #define TPG110_CTRL2_RES_PM_CTRL BIT(7) | ||
40 | |||
41 | /** | ||
42 | * struct tpg110_panel_mode - lookup struct for the supported modes | ||
43 | */ | ||
44 | struct tpg110_panel_mode { | ||
45 | /** | ||
46 | * @name: the name of this panel | ||
47 | */ | ||
48 | const char *name; | ||
49 | /** | ||
50 | * @magic: the magic value from the detection register | ||
51 | */ | ||
52 | u32 magic; | ||
53 | /** | ||
54 | * @mode: the DRM display mode for this panel | ||
55 | */ | ||
56 | struct drm_display_mode mode; | ||
57 | /** | ||
58 | * @bus_flags: the DRM bus flags for this panel e.g. inverted clock | ||
59 | */ | ||
60 | u32 bus_flags; | ||
61 | }; | ||
62 | |||
63 | /** | ||
64 | * struct tpg110 - state container for the TPG110 panel | ||
65 | */ | ||
66 | struct tpg110 { | ||
67 | /** | ||
68 | * @dev: the container device | ||
69 | */ | ||
70 | struct device *dev; | ||
71 | /** | ||
72 | * @spi: the corresponding SPI device | ||
73 | */ | ||
74 | struct spi_device *spi; | ||
75 | /** | ||
76 | * @panel: the DRM panel instance for this device | ||
77 | */ | ||
78 | struct drm_panel panel; | ||
79 | /** | ||
80 | * @backlight: backlight for this panel | ||
81 | */ | ||
82 | struct backlight_device *backlight; | ||
83 | /** | ||
84 | * @panel_type: the panel mode as detected | ||
85 | */ | ||
86 | const struct tpg110_panel_mode *panel_mode; | ||
87 | /** | ||
88 | * @width: the width of this panel in mm | ||
89 | */ | ||
90 | u32 width; | ||
91 | /** | ||
92 | * @height: the height of this panel in mm | ||
93 | */ | ||
94 | u32 height; | ||
95 | /** | ||
96 | * @grestb: reset GPIO line | ||
97 | */ | ||
98 | struct gpio_desc *grestb; | ||
99 | }; | ||
100 | |||
101 | /* | ||
102 | * TPG110 modes, these are the simple modes, the dualscan modes that | ||
103 | * take 400x240 or 480x272 in and display as 800x480 are not listed. | ||
104 | */ | ||
105 | static const struct tpg110_panel_mode tpg110_modes[] = { | ||
106 | { | ||
107 | .name = "800x480 RGB", | ||
108 | .magic = TPG110_RES_800X480, | ||
109 | .mode = { | ||
110 | .clock = 33200, | ||
111 | .hdisplay = 800, | ||
112 | .hsync_start = 800 + 40, | ||
113 | .hsync_end = 800 + 40 + 1, | ||
114 | .htotal = 800 + 40 + 1 + 216, | ||
115 | .vdisplay = 480, | ||
116 | .vsync_start = 480 + 10, | ||
117 | .vsync_end = 480 + 10 + 1, | ||
118 | .vtotal = 480 + 10 + 1 + 35, | ||
119 | .vrefresh = 60, | ||
120 | }, | ||
121 | .bus_flags = DRM_BUS_FLAG_PIXDATA_POSEDGE, | ||
122 | }, | ||
123 | { | ||
124 | .name = "640x480 RGB", | ||
125 | .magic = TPG110_RES_640X480, | ||
126 | .mode = { | ||
127 | .clock = 25200, | ||
128 | .hdisplay = 640, | ||
129 | .hsync_start = 640 + 24, | ||
130 | .hsync_end = 640 + 24 + 1, | ||
131 | .htotal = 640 + 24 + 1 + 136, | ||
132 | .vdisplay = 480, | ||
133 | .vsync_start = 480 + 18, | ||
134 | .vsync_end = 480 + 18 + 1, | ||
135 | .vtotal = 480 + 18 + 1 + 27, | ||
136 | .vrefresh = 60, | ||
137 | }, | ||
138 | .bus_flags = DRM_BUS_FLAG_PIXDATA_POSEDGE, | ||
139 | }, | ||
140 | { | ||
141 | .name = "480x272 RGB", | ||
142 | .magic = TPG110_RES_480X272, | ||
143 | .mode = { | ||
144 | .clock = 9000, | ||
145 | .hdisplay = 480, | ||
146 | .hsync_start = 480 + 2, | ||
147 | .hsync_end = 480 + 2 + 1, | ||
148 | .htotal = 480 + 2 + 1 + 43, | ||
149 | .vdisplay = 272, | ||
150 | .vsync_start = 272 + 2, | ||
151 | .vsync_end = 272 + 2 + 1, | ||
152 | .vtotal = 272 + 2 + 1 + 12, | ||
153 | .vrefresh = 60, | ||
154 | }, | ||
155 | .bus_flags = DRM_BUS_FLAG_PIXDATA_POSEDGE, | ||
156 | }, | ||
157 | { | ||
158 | .name = "480x640 RGB", | ||
159 | .magic = TPG110_RES_480X640, | ||
160 | .mode = { | ||
161 | .clock = 20500, | ||
162 | .hdisplay = 480, | ||
163 | .hsync_start = 480 + 2, | ||
164 | .hsync_end = 480 + 2 + 1, | ||
165 | .htotal = 480 + 2 + 1 + 43, | ||
166 | .vdisplay = 640, | ||
167 | .vsync_start = 640 + 4, | ||
168 | .vsync_end = 640 + 4 + 1, | ||
169 | .vtotal = 640 + 4 + 1 + 8, | ||
170 | .vrefresh = 60, | ||
171 | }, | ||
172 | .bus_flags = DRM_BUS_FLAG_PIXDATA_POSEDGE, | ||
173 | }, | ||
174 | { | ||
175 | .name = "400x240 RGB", | ||
176 | .magic = TPG110_RES_400X240_D, | ||
177 | .mode = { | ||
178 | .clock = 8300, | ||
179 | .hdisplay = 400, | ||
180 | .hsync_start = 400 + 20, | ||
181 | .hsync_end = 400 + 20 + 1, | ||
182 | .htotal = 400 + 20 + 1 + 108, | ||
183 | .vdisplay = 240, | ||
184 | .vsync_start = 240 + 2, | ||
185 | .vsync_end = 240 + 2 + 1, | ||
186 | .vtotal = 240 + 2 + 1 + 20, | ||
187 | .vrefresh = 60, | ||
188 | }, | ||
189 | .bus_flags = DRM_BUS_FLAG_PIXDATA_POSEDGE, | ||
190 | }, | ||
191 | }; | ||
192 | |||
193 | static inline struct tpg110 * | ||
194 | to_tpg110(struct drm_panel *panel) | ||
195 | { | ||
196 | return container_of(panel, struct tpg110, panel); | ||
197 | } | ||
198 | |||
199 | static u8 tpg110_readwrite_reg(struct tpg110 *tpg, bool write, | ||
200 | u8 address, u8 outval) | ||
201 | { | ||
202 | struct spi_message m; | ||
203 | struct spi_transfer t[2]; | ||
204 | u8 buf[2]; | ||
205 | int ret; | ||
206 | |||
207 | spi_message_init(&m); | ||
208 | memset(t, 0, sizeof(t)); | ||
209 | |||
210 | if (write) { | ||
211 | /* | ||
212 | * Clear address bit 0, 1 when writing, just to be sure | ||
213 | * The actual bit indicating a write here is bit 1, bit | ||
214 | * 0 is just surplus to pad it up to 8 bits. | ||
215 | */ | ||
216 | buf[0] = address << 2; | ||
217 | buf[0] &= ~0x03; | ||
218 | buf[1] = outval; | ||
219 | |||
220 | t[0].bits_per_word = 8; | ||
221 | t[0].tx_buf = &buf[0]; | ||
222 | t[0].len = 1; | ||
223 | |||
224 | t[1].tx_buf = &buf[1]; | ||
225 | t[1].len = 1; | ||
226 | t[1].bits_per_word = 8; | ||
227 | } else { | ||
228 | /* Set address bit 0 to 1 to read */ | ||
229 | buf[0] = address << 1; | ||
230 | buf[0] |= 0x01; | ||
231 | |||
232 | /* | ||
233 | * The last bit/clock is Hi-Z turnaround cycle, so we need | ||
234 | * to send only 7 bits here. The 8th bit is the high impedance | ||
235 | * turn-around cycle. | ||
236 | */ | ||
237 | t[0].bits_per_word = 7; | ||
238 | t[0].tx_buf = &buf[0]; | ||
239 | t[0].len = 1; | ||
240 | |||
241 | t[1].rx_buf = &buf[1]; | ||
242 | t[1].len = 1; | ||
243 | t[1].bits_per_word = 8; | ||
244 | } | ||
245 | |||
246 | spi_message_add_tail(&t[0], &m); | ||
247 | spi_message_add_tail(&t[1], &m); | ||
248 | ret = spi_sync(tpg->spi, &m); | ||
249 | if (ret) { | ||
250 | DRM_DEV_ERROR(tpg->dev, "SPI message error %d\n", ret); | ||
251 | return ret; | ||
252 | } | ||
253 | if (write) | ||
254 | return 0; | ||
255 | /* Read */ | ||
256 | return buf[1]; | ||
257 | } | ||
258 | |||
259 | static u8 tpg110_read_reg(struct tpg110 *tpg, u8 address) | ||
260 | { | ||
261 | return tpg110_readwrite_reg(tpg, false, address, 0); | ||
262 | } | ||
263 | |||
264 | static void tpg110_write_reg(struct tpg110 *tpg, u8 address, u8 outval) | ||
265 | { | ||
266 | tpg110_readwrite_reg(tpg, true, address, outval); | ||
267 | } | ||
268 | |||
269 | static int tpg110_startup(struct tpg110 *tpg) | ||
270 | { | ||
271 | u8 val; | ||
272 | int i; | ||
273 | |||
274 | /* De-assert the reset signal */ | ||
275 | gpiod_set_value_cansleep(tpg->grestb, 0); | ||
276 | usleep_range(1000, 2000); | ||
277 | DRM_DEV_DEBUG(tpg->dev, "de-asserted GRESTB\n"); | ||
278 | |||
279 | /* Test display communication */ | ||
280 | tpg110_write_reg(tpg, TPG110_TEST, 0x55); | ||
281 | val = tpg110_read_reg(tpg, TPG110_TEST); | ||
282 | if (val != 0x55) { | ||
283 | DRM_DEV_ERROR(tpg->dev, "failed communication test\n"); | ||
284 | return -ENODEV; | ||
285 | } | ||
286 | |||
287 | val = tpg110_read_reg(tpg, TPG110_CHIPID); | ||
288 | DRM_DEV_INFO(tpg->dev, "TPG110 chip ID: %d version: %d\n", | ||
289 | val >> 4, val & 0x0f); | ||
290 | |||
291 | /* Show display resolution */ | ||
292 | val = tpg110_read_reg(tpg, TPG110_CTRL1); | ||
293 | val &= TPG110_RES_MASK; | ||
294 | switch (val) { | ||
295 | case TPG110_RES_400X240_D: | ||
296 | DRM_DEV_INFO(tpg->dev, | ||
297 | "IN 400x240 RGB -> OUT 800x480 RGB (dual scan)\n"); | ||
298 | break; | ||
299 | case TPG110_RES_480X272_D: | ||
300 | DRM_DEV_INFO(tpg->dev, | ||
301 | "IN 480x272 RGB -> OUT 800x480 RGB (dual scan)\n"); | ||
302 | break; | ||
303 | case TPG110_RES_480X640: | ||
304 | DRM_DEV_INFO(tpg->dev, "480x640 RGB\n"); | ||
305 | break; | ||
306 | case TPG110_RES_480X272: | ||
307 | DRM_DEV_INFO(tpg->dev, "480x272 RGB\n"); | ||
308 | break; | ||
309 | case TPG110_RES_640X480: | ||
310 | DRM_DEV_INFO(tpg->dev, "640x480 RGB\n"); | ||
311 | break; | ||
312 | case TPG110_RES_800X480: | ||
313 | DRM_DEV_INFO(tpg->dev, "800x480 RGB\n"); | ||
314 | break; | ||
315 | default: | ||
316 | DRM_DEV_ERROR(tpg->dev, "ILLEGAL RESOLUTION 0x%02x\n", val); | ||
317 | break; | ||
318 | } | ||
319 | |||
320 | /* From the producer side, this is the same resolution */ | ||
321 | if (val == TPG110_RES_480X272_D) | ||
322 | val = TPG110_RES_480X272; | ||
323 | |||
324 | for (i = 0; i < ARRAY_SIZE(tpg110_modes); i++) { | ||
325 | const struct tpg110_panel_mode *pm; | ||
326 | |||
327 | pm = &tpg110_modes[i]; | ||
328 | if (pm->magic == val) { | ||
329 | tpg->panel_mode = pm; | ||
330 | break; | ||
331 | } | ||
332 | } | ||
333 | if (i == ARRAY_SIZE(tpg110_modes)) { | ||
334 | DRM_DEV_ERROR(tpg->dev, "unsupported mode (%02x) detected\n", | ||
335 | val); | ||
336 | return -ENODEV; | ||
337 | } | ||
338 | |||
339 | val = tpg110_read_reg(tpg, TPG110_CTRL2); | ||
340 | DRM_DEV_INFO(tpg->dev, "resolution and standby is controlled by %s\n", | ||
341 | (val & TPG110_CTRL2_RES_PM_CTRL) ? "software" : "hardware"); | ||
342 | /* Take control over resolution and standby */ | ||
343 | val |= TPG110_CTRL2_RES_PM_CTRL; | ||
344 | tpg110_write_reg(tpg, TPG110_CTRL2, val); | ||
345 | |||
346 | return 0; | ||
347 | } | ||
348 | |||
349 | static int tpg110_disable(struct drm_panel *panel) | ||
350 | { | ||
351 | struct tpg110 *tpg = to_tpg110(panel); | ||
352 | u8 val; | ||
353 | |||
354 | /* Put chip into standby */ | ||
355 | val = tpg110_read_reg(tpg, TPG110_CTRL2_PM); | ||
356 | val &= ~TPG110_CTRL2_PM; | ||
357 | tpg110_write_reg(tpg, TPG110_CTRL2_PM, val); | ||
358 | |||
359 | backlight_disable(tpg->backlight); | ||
360 | |||
361 | return 0; | ||
362 | } | ||
363 | |||
364 | static int tpg110_enable(struct drm_panel *panel) | ||
365 | { | ||
366 | struct tpg110 *tpg = to_tpg110(panel); | ||
367 | u8 val; | ||
368 | |||
369 | backlight_enable(tpg->backlight); | ||
370 | |||
371 | /* Take chip out of standby */ | ||
372 | val = tpg110_read_reg(tpg, TPG110_CTRL2_PM); | ||
373 | val |= TPG110_CTRL2_PM; | ||
374 | tpg110_write_reg(tpg, TPG110_CTRL2_PM, val); | ||
375 | |||
376 | return 0; | ||
377 | } | ||
378 | |||
379 | /** | ||
380 | * tpg110_get_modes() - return the appropriate mode | ||
381 | * @panel: the panel to get the mode for | ||
382 | * | ||
383 | * This currently does not present a forest of modes, instead it | ||
384 | * presents the mode that is configured for the system under use, | ||
385 | * and which is detected by reading the registers of the display. | ||
386 | */ | ||
387 | static int tpg110_get_modes(struct drm_panel *panel) | ||
388 | { | ||
389 | struct drm_connector *connector = panel->connector; | ||
390 | struct tpg110 *tpg = to_tpg110(panel); | ||
391 | struct drm_display_mode *mode; | ||
392 | |||
393 | strncpy(connector->display_info.name, tpg->panel_mode->name, | ||
394 | DRM_DISPLAY_INFO_LEN); | ||
395 | connector->display_info.width_mm = tpg->width; | ||
396 | connector->display_info.height_mm = tpg->height; | ||
397 | connector->display_info.bus_flags = tpg->panel_mode->bus_flags; | ||
398 | |||
399 | mode = drm_mode_duplicate(panel->drm, &tpg->panel_mode->mode); | ||
400 | drm_mode_set_name(mode); | ||
401 | mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; | ||
402 | |||
403 | mode->width_mm = tpg->width; | ||
404 | mode->height_mm = tpg->height; | ||
405 | |||
406 | drm_mode_probed_add(connector, mode); | ||
407 | |||
408 | return 1; | ||
409 | } | ||
410 | |||
411 | static const struct drm_panel_funcs tpg110_drm_funcs = { | ||
412 | .disable = tpg110_disable, | ||
413 | .enable = tpg110_enable, | ||
414 | .get_modes = tpg110_get_modes, | ||
415 | }; | ||
416 | |||
417 | static int tpg110_probe(struct spi_device *spi) | ||
418 | { | ||
419 | struct device *dev = &spi->dev; | ||
420 | struct device_node *np = dev->of_node; | ||
421 | struct tpg110 *tpg; | ||
422 | int ret; | ||
423 | |||
424 | tpg = devm_kzalloc(dev, sizeof(*tpg), GFP_KERNEL); | ||
425 | if (!tpg) | ||
426 | return -ENOMEM; | ||
427 | tpg->dev = dev; | ||
428 | |||
429 | /* We get the physical display dimensions from the DT */ | ||
430 | ret = of_property_read_u32(np, "width-mm", &tpg->width); | ||
431 | if (ret) | ||
432 | DRM_DEV_ERROR(dev, "no panel width specified\n"); | ||
433 | ret = of_property_read_u32(np, "height-mm", &tpg->height); | ||
434 | if (ret) | ||
435 | DRM_DEV_ERROR(dev, "no panel height specified\n"); | ||
436 | |||
437 | /* Look for some optional backlight */ | ||
438 | tpg->backlight = devm_of_find_backlight(dev); | ||
439 | if (IS_ERR(tpg->backlight)) | ||
440 | return PTR_ERR(tpg->backlight); | ||
441 | |||
442 | /* This asserts the GRESTB signal, putting the display into reset */ | ||
443 | tpg->grestb = devm_gpiod_get(dev, "grestb", GPIOD_OUT_HIGH); | ||
444 | if (IS_ERR(tpg->grestb)) { | ||
445 | DRM_DEV_ERROR(dev, "no GRESTB GPIO\n"); | ||
446 | return -ENODEV; | ||
447 | } | ||
448 | |||
449 | spi->bits_per_word = 8; | ||
450 | spi->mode |= SPI_3WIRE_HIZ; | ||
451 | ret = spi_setup(spi); | ||
452 | if (ret < 0) { | ||
453 | DRM_DEV_ERROR(dev, "spi setup failed.\n"); | ||
454 | return ret; | ||
455 | } | ||
456 | tpg->spi = spi; | ||
457 | |||
458 | ret = tpg110_startup(tpg); | ||
459 | if (ret) | ||
460 | return ret; | ||
461 | |||
462 | drm_panel_init(&tpg->panel); | ||
463 | tpg->panel.dev = dev; | ||
464 | tpg->panel.funcs = &tpg110_drm_funcs; | ||
465 | spi_set_drvdata(spi, tpg); | ||
466 | |||
467 | return drm_panel_add(&tpg->panel); | ||
468 | } | ||
469 | |||
470 | static int tpg110_remove(struct spi_device *spi) | ||
471 | { | ||
472 | struct tpg110 *tpg = spi_get_drvdata(spi); | ||
473 | |||
474 | drm_panel_remove(&tpg->panel); | ||
475 | return 0; | ||
476 | } | ||
477 | |||
478 | static const struct of_device_id tpg110_match[] = { | ||
479 | { .compatible = "tpo,tpg110", }, | ||
480 | {}, | ||
481 | }; | ||
482 | MODULE_DEVICE_TABLE(of, tpg110_match); | ||
483 | |||
484 | static struct spi_driver tpg110_driver = { | ||
485 | .probe = tpg110_probe, | ||
486 | .remove = tpg110_remove, | ||
487 | .driver = { | ||
488 | .name = "tpo-tpg110-panel", | ||
489 | .of_match_table = tpg110_match, | ||
490 | }, | ||
491 | }; | ||
492 | module_spi_driver(tpg110_driver); | ||
493 | |||
494 | MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>"); | ||
495 | MODULE_DESCRIPTION("TPO TPG110 panel driver"); | ||
496 | MODULE_LICENSE("GPL v2"); | ||