1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Toppoly TD043MTEA1 Panel Driver |
4 | * |
5 | * Copyright (C) 2019 Texas Instruments Incorporated |
6 | * |
7 | * Based on the omapdrm-specific panel-tpo-td043mtea1 driver |
8 | * |
9 | * Author: Gražvydas Ignotas <notasas@gmail.com> |
10 | */ |
11 | |
12 | #include <linux/delay.h> |
13 | #include <linux/module.h> |
14 | #include <linux/regulator/consumer.h> |
15 | #include <linux/spi/spi.h> |
16 | |
17 | #include <drm/drm_connector.h> |
18 | #include <drm/drm_modes.h> |
19 | #include <drm/drm_panel.h> |
20 | |
21 | #define TPO_R02_MODE(x) ((x) & 7) |
22 | #define TPO_R02_MODE_800x480 7 |
23 | #define TPO_R02_NCLK_RISING BIT(3) |
24 | #define TPO_R02_HSYNC_HIGH BIT(4) |
25 | #define TPO_R02_VSYNC_HIGH BIT(5) |
26 | |
27 | #define TPO_R03_NSTANDBY BIT(0) |
28 | #define TPO_R03_EN_CP_CLK BIT(1) |
29 | #define TPO_R03_EN_VGL_PUMP BIT(2) |
30 | #define TPO_R03_EN_PWM BIT(3) |
31 | #define TPO_R03_DRIVING_CAP_100 BIT(4) |
32 | #define TPO_R03_EN_PRE_CHARGE BIT(6) |
33 | #define TPO_R03_SOFTWARE_CTL BIT(7) |
34 | |
35 | #define TPO_R04_NFLIP_H BIT(0) |
36 | #define TPO_R04_NFLIP_V BIT(1) |
37 | #define TPO_R04_CP_CLK_FREQ_1H BIT(2) |
38 | #define TPO_R04_VGL_FREQ_1H BIT(4) |
39 | |
40 | #define TPO_R03_VAL_NORMAL \ |
41 | (TPO_R03_NSTANDBY | TPO_R03_EN_CP_CLK | TPO_R03_EN_VGL_PUMP | \ |
42 | TPO_R03_EN_PWM | TPO_R03_DRIVING_CAP_100 | TPO_R03_EN_PRE_CHARGE | \ |
43 | TPO_R03_SOFTWARE_CTL) |
44 | |
45 | #define TPO_R03_VAL_STANDBY \ |
46 | (TPO_R03_DRIVING_CAP_100 | TPO_R03_EN_PRE_CHARGE | \ |
47 | TPO_R03_SOFTWARE_CTL) |
48 | |
49 | static const u16 td043mtea1_def_gamma[12] = { |
50 | 105, 315, 381, 431, 490, 537, 579, 686, 780, 837, 880, 1023 |
51 | }; |
52 | |
53 | struct td043mtea1_panel { |
54 | struct drm_panel panel; |
55 | |
56 | struct spi_device *spi; |
57 | struct regulator *vcc_reg; |
58 | struct gpio_desc *reset_gpio; |
59 | |
60 | unsigned int mode; |
61 | u16 gamma[12]; |
62 | bool vmirror; |
63 | bool powered_on; |
64 | bool spi_suspended; |
65 | bool power_on_resume; |
66 | }; |
67 | |
68 | #define to_td043mtea1_device(p) container_of(p, struct td043mtea1_panel, panel) |
69 | |
70 | /* ----------------------------------------------------------------------------- |
71 | * Hardware Access |
72 | */ |
73 | |
74 | static int td043mtea1_write(struct td043mtea1_panel *lcd, u8 addr, u8 value) |
75 | { |
76 | struct spi_message msg; |
77 | struct spi_transfer xfer; |
78 | u16 data; |
79 | int ret; |
80 | |
81 | spi_message_init(m: &msg); |
82 | |
83 | memset(&xfer, 0, sizeof(xfer)); |
84 | |
85 | data = ((u16)addr << 10) | (1 << 8) | value; |
86 | xfer.tx_buf = &data; |
87 | xfer.bits_per_word = 16; |
88 | xfer.len = 2; |
89 | spi_message_add_tail(t: &xfer, m: &msg); |
90 | |
91 | ret = spi_sync(spi: lcd->spi, message: &msg); |
92 | if (ret < 0) |
93 | dev_warn(&lcd->spi->dev, "failed to write to LCD reg (%d)\n" , |
94 | ret); |
95 | |
96 | return ret; |
97 | } |
98 | |
99 | static void td043mtea1_write_gamma(struct td043mtea1_panel *lcd) |
100 | { |
101 | const u16 *gamma = lcd->gamma; |
102 | unsigned int i; |
103 | u8 val; |
104 | |
105 | /* gamma bits [9:8] */ |
106 | for (val = i = 0; i < 4; i++) |
107 | val |= (gamma[i] & 0x300) >> ((i + 1) * 2); |
108 | td043mtea1_write(lcd, addr: 0x11, value: val); |
109 | |
110 | for (val = i = 0; i < 4; i++) |
111 | val |= (gamma[i + 4] & 0x300) >> ((i + 1) * 2); |
112 | td043mtea1_write(lcd, addr: 0x12, value: val); |
113 | |
114 | for (val = i = 0; i < 4; i++) |
115 | val |= (gamma[i + 8] & 0x300) >> ((i + 1) * 2); |
116 | td043mtea1_write(lcd, addr: 0x13, value: val); |
117 | |
118 | /* gamma bits [7:0] */ |
119 | for (i = 0; i < 12; i++) |
120 | td043mtea1_write(lcd, addr: 0x14 + i, value: gamma[i] & 0xff); |
121 | } |
122 | |
123 | static int td043mtea1_write_mirror(struct td043mtea1_panel *lcd) |
124 | { |
125 | u8 reg4 = TPO_R04_NFLIP_H | TPO_R04_NFLIP_V | |
126 | TPO_R04_CP_CLK_FREQ_1H | TPO_R04_VGL_FREQ_1H; |
127 | if (lcd->vmirror) |
128 | reg4 &= ~TPO_R04_NFLIP_V; |
129 | |
130 | return td043mtea1_write(lcd, addr: 4, value: reg4); |
131 | } |
132 | |
133 | static int td043mtea1_power_on(struct td043mtea1_panel *lcd) |
134 | { |
135 | int ret; |
136 | |
137 | if (lcd->powered_on) |
138 | return 0; |
139 | |
140 | ret = regulator_enable(regulator: lcd->vcc_reg); |
141 | if (ret < 0) |
142 | return ret; |
143 | |
144 | /* Wait for the panel to stabilize. */ |
145 | msleep(msecs: 160); |
146 | |
147 | gpiod_set_value(desc: lcd->reset_gpio, value: 0); |
148 | |
149 | td043mtea1_write(lcd, addr: 2, TPO_R02_MODE(lcd->mode) | TPO_R02_NCLK_RISING); |
150 | td043mtea1_write(lcd, addr: 3, TPO_R03_VAL_NORMAL); |
151 | td043mtea1_write(lcd, addr: 0x20, value: 0xf0); |
152 | td043mtea1_write(lcd, addr: 0x21, value: 0xf0); |
153 | td043mtea1_write_mirror(lcd); |
154 | td043mtea1_write_gamma(lcd); |
155 | |
156 | lcd->powered_on = true; |
157 | |
158 | return 0; |
159 | } |
160 | |
161 | static void td043mtea1_power_off(struct td043mtea1_panel *lcd) |
162 | { |
163 | if (!lcd->powered_on) |
164 | return; |
165 | |
166 | td043mtea1_write(lcd, addr: 3, TPO_R03_VAL_STANDBY | TPO_R03_EN_PWM); |
167 | |
168 | gpiod_set_value(desc: lcd->reset_gpio, value: 1); |
169 | |
170 | /* wait for at least 2 vsyncs before cutting off power */ |
171 | msleep(msecs: 50); |
172 | |
173 | td043mtea1_write(lcd, addr: 3, TPO_R03_VAL_STANDBY); |
174 | |
175 | regulator_disable(regulator: lcd->vcc_reg); |
176 | |
177 | lcd->powered_on = false; |
178 | } |
179 | |
180 | /* ----------------------------------------------------------------------------- |
181 | * sysfs |
182 | */ |
183 | |
184 | static ssize_t vmirror_show(struct device *dev, struct device_attribute *attr, |
185 | char *buf) |
186 | { |
187 | struct td043mtea1_panel *lcd = dev_get_drvdata(dev); |
188 | |
189 | return sysfs_emit(buf, fmt: "%d\n" , lcd->vmirror); |
190 | } |
191 | |
192 | static ssize_t vmirror_store(struct device *dev, struct device_attribute *attr, |
193 | const char *buf, size_t count) |
194 | { |
195 | struct td043mtea1_panel *lcd = dev_get_drvdata(dev); |
196 | int val; |
197 | int ret; |
198 | |
199 | ret = kstrtoint(s: buf, base: 0, res: &val); |
200 | if (ret < 0) |
201 | return ret; |
202 | |
203 | lcd->vmirror = !!val; |
204 | |
205 | ret = td043mtea1_write_mirror(lcd); |
206 | if (ret < 0) |
207 | return ret; |
208 | |
209 | return count; |
210 | } |
211 | |
212 | static ssize_t mode_show(struct device *dev, struct device_attribute *attr, |
213 | char *buf) |
214 | { |
215 | struct td043mtea1_panel *lcd = dev_get_drvdata(dev); |
216 | |
217 | return sysfs_emit(buf, fmt: "%d\n" , lcd->mode); |
218 | } |
219 | |
220 | static ssize_t mode_store(struct device *dev, struct device_attribute *attr, |
221 | const char *buf, size_t count) |
222 | { |
223 | struct td043mtea1_panel *lcd = dev_get_drvdata(dev); |
224 | long val; |
225 | int ret; |
226 | |
227 | ret = kstrtol(s: buf, base: 0, res: &val); |
228 | if (ret != 0 || val & ~7) |
229 | return -EINVAL; |
230 | |
231 | lcd->mode = val; |
232 | |
233 | val |= TPO_R02_NCLK_RISING; |
234 | td043mtea1_write(lcd, addr: 2, value: val); |
235 | |
236 | return count; |
237 | } |
238 | |
239 | static ssize_t gamma_show(struct device *dev, struct device_attribute *attr, |
240 | char *buf) |
241 | { |
242 | struct td043mtea1_panel *lcd = dev_get_drvdata(dev); |
243 | ssize_t len = 0; |
244 | unsigned int i; |
245 | int ret; |
246 | |
247 | for (i = 0; i < ARRAY_SIZE(lcd->gamma); i++) { |
248 | ret = snprintf(buf: buf + len, PAGE_SIZE - len, fmt: "%u " , |
249 | lcd->gamma[i]); |
250 | if (ret < 0) |
251 | return ret; |
252 | len += ret; |
253 | } |
254 | buf[len - 1] = '\n'; |
255 | |
256 | return len; |
257 | } |
258 | |
259 | static ssize_t gamma_store(struct device *dev, struct device_attribute *attr, |
260 | const char *buf, size_t count) |
261 | { |
262 | struct td043mtea1_panel *lcd = dev_get_drvdata(dev); |
263 | unsigned int g[12]; |
264 | unsigned int i; |
265 | int ret; |
266 | |
267 | ret = sscanf(buf, "%u %u %u %u %u %u %u %u %u %u %u %u" , |
268 | &g[0], &g[1], &g[2], &g[3], &g[4], &g[5], |
269 | &g[6], &g[7], &g[8], &g[9], &g[10], &g[11]); |
270 | if (ret != 12) |
271 | return -EINVAL; |
272 | |
273 | for (i = 0; i < 12; i++) |
274 | lcd->gamma[i] = g[i]; |
275 | |
276 | td043mtea1_write_gamma(lcd); |
277 | |
278 | return count; |
279 | } |
280 | |
281 | static DEVICE_ATTR_RW(vmirror); |
282 | static DEVICE_ATTR_RW(mode); |
283 | static DEVICE_ATTR_RW(gamma); |
284 | |
285 | static struct attribute *td043mtea1_attrs[] = { |
286 | &dev_attr_vmirror.attr, |
287 | &dev_attr_mode.attr, |
288 | &dev_attr_gamma.attr, |
289 | NULL, |
290 | }; |
291 | |
292 | static const struct attribute_group td043mtea1_attr_group = { |
293 | .attrs = td043mtea1_attrs, |
294 | }; |
295 | |
296 | /* ----------------------------------------------------------------------------- |
297 | * Panel Operations |
298 | */ |
299 | |
300 | static int td043mtea1_unprepare(struct drm_panel *panel) |
301 | { |
302 | struct td043mtea1_panel *lcd = to_td043mtea1_device(panel); |
303 | |
304 | if (!lcd->spi_suspended) |
305 | td043mtea1_power_off(lcd); |
306 | |
307 | return 0; |
308 | } |
309 | |
310 | static int td043mtea1_prepare(struct drm_panel *panel) |
311 | { |
312 | struct td043mtea1_panel *lcd = to_td043mtea1_device(panel); |
313 | int ret; |
314 | |
315 | /* |
316 | * If we are resuming from system suspend, SPI might not be enabled |
317 | * yet, so we'll program the LCD from SPI PM resume callback. |
318 | */ |
319 | if (lcd->spi_suspended) |
320 | return 0; |
321 | |
322 | ret = td043mtea1_power_on(lcd); |
323 | if (ret) { |
324 | dev_err(&lcd->spi->dev, "%s: power on failed (%d)\n" , |
325 | __func__, ret); |
326 | return ret; |
327 | } |
328 | |
329 | return 0; |
330 | } |
331 | |
332 | static const struct drm_display_mode td043mtea1_mode = { |
333 | .clock = 36000, |
334 | .hdisplay = 800, |
335 | .hsync_start = 800 + 68, |
336 | .hsync_end = 800 + 68 + 1, |
337 | .htotal = 800 + 68 + 1 + 214, |
338 | .vdisplay = 480, |
339 | .vsync_start = 480 + 39, |
340 | .vsync_end = 480 + 39 + 1, |
341 | .vtotal = 480 + 39 + 1 + 34, |
342 | .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, |
343 | .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, |
344 | .width_mm = 94, |
345 | .height_mm = 56, |
346 | }; |
347 | |
348 | static int td043mtea1_get_modes(struct drm_panel *panel, |
349 | struct drm_connector *connector) |
350 | { |
351 | struct drm_display_mode *mode; |
352 | |
353 | mode = drm_mode_duplicate(dev: connector->dev, mode: &td043mtea1_mode); |
354 | if (!mode) |
355 | return -ENOMEM; |
356 | |
357 | drm_mode_set_name(mode); |
358 | drm_mode_probed_add(connector, mode); |
359 | |
360 | connector->display_info.width_mm = td043mtea1_mode.width_mm; |
361 | connector->display_info.height_mm = td043mtea1_mode.height_mm; |
362 | /* |
363 | * FIXME: According to the datasheet sync signals are sampled on the |
364 | * rising edge of the clock, but the code running on the OMAP3 Pandora |
365 | * indicates sampling on the falling edge. This should be tested on a |
366 | * real device. |
367 | */ |
368 | connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH |
369 | | DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE |
370 | | DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE; |
371 | |
372 | return 1; |
373 | } |
374 | |
375 | static const struct drm_panel_funcs td043mtea1_funcs = { |
376 | .unprepare = td043mtea1_unprepare, |
377 | .prepare = td043mtea1_prepare, |
378 | .get_modes = td043mtea1_get_modes, |
379 | }; |
380 | |
381 | /* ----------------------------------------------------------------------------- |
382 | * Power Management, Probe and Remove |
383 | */ |
384 | |
385 | static int __maybe_unused td043mtea1_suspend(struct device *dev) |
386 | { |
387 | struct td043mtea1_panel *lcd = dev_get_drvdata(dev); |
388 | |
389 | if (lcd->powered_on) { |
390 | td043mtea1_power_off(lcd); |
391 | lcd->powered_on = true; |
392 | } |
393 | |
394 | lcd->spi_suspended = true; |
395 | |
396 | return 0; |
397 | } |
398 | |
399 | static int __maybe_unused td043mtea1_resume(struct device *dev) |
400 | { |
401 | struct td043mtea1_panel *lcd = dev_get_drvdata(dev); |
402 | int ret; |
403 | |
404 | lcd->spi_suspended = false; |
405 | |
406 | if (lcd->powered_on) { |
407 | lcd->powered_on = false; |
408 | ret = td043mtea1_power_on(lcd); |
409 | if (ret) |
410 | return ret; |
411 | } |
412 | |
413 | return 0; |
414 | } |
415 | |
416 | static SIMPLE_DEV_PM_OPS(td043mtea1_pm_ops, td043mtea1_suspend, |
417 | td043mtea1_resume); |
418 | |
419 | static int td043mtea1_probe(struct spi_device *spi) |
420 | { |
421 | struct td043mtea1_panel *lcd; |
422 | int ret; |
423 | |
424 | lcd = devm_kzalloc(dev: &spi->dev, size: sizeof(*lcd), GFP_KERNEL); |
425 | if (lcd == NULL) |
426 | return -ENOMEM; |
427 | |
428 | spi_set_drvdata(spi, data: lcd); |
429 | lcd->spi = spi; |
430 | lcd->mode = TPO_R02_MODE_800x480; |
431 | memcpy(lcd->gamma, td043mtea1_def_gamma, sizeof(lcd->gamma)); |
432 | |
433 | lcd->vcc_reg = devm_regulator_get(dev: &spi->dev, id: "vcc" ); |
434 | if (IS_ERR(ptr: lcd->vcc_reg)) |
435 | return dev_err_probe(dev: &spi->dev, err: PTR_ERR(ptr: lcd->vcc_reg), |
436 | fmt: "failed to get VCC regulator\n" ); |
437 | |
438 | lcd->reset_gpio = devm_gpiod_get(dev: &spi->dev, con_id: "reset" , flags: GPIOD_OUT_HIGH); |
439 | if (IS_ERR(ptr: lcd->reset_gpio)) |
440 | return dev_err_probe(dev: &spi->dev, err: PTR_ERR(ptr: lcd->reset_gpio), |
441 | fmt: "failed to get reset GPIO\n" ); |
442 | |
443 | spi->bits_per_word = 16; |
444 | spi->mode = SPI_MODE_0; |
445 | |
446 | ret = spi_setup(spi); |
447 | if (ret < 0) { |
448 | dev_err(&spi->dev, "failed to setup SPI: %d\n" , ret); |
449 | return ret; |
450 | } |
451 | |
452 | ret = sysfs_create_group(kobj: &spi->dev.kobj, grp: &td043mtea1_attr_group); |
453 | if (ret < 0) { |
454 | dev_err(&spi->dev, "failed to create sysfs files\n" ); |
455 | return ret; |
456 | } |
457 | |
458 | drm_panel_init(panel: &lcd->panel, dev: &lcd->spi->dev, funcs: &td043mtea1_funcs, |
459 | DRM_MODE_CONNECTOR_DPI); |
460 | |
461 | drm_panel_add(panel: &lcd->panel); |
462 | |
463 | return 0; |
464 | } |
465 | |
466 | static void td043mtea1_remove(struct spi_device *spi) |
467 | { |
468 | struct td043mtea1_panel *lcd = spi_get_drvdata(spi); |
469 | |
470 | drm_panel_remove(panel: &lcd->panel); |
471 | drm_panel_disable(panel: &lcd->panel); |
472 | drm_panel_unprepare(panel: &lcd->panel); |
473 | |
474 | sysfs_remove_group(kobj: &spi->dev.kobj, grp: &td043mtea1_attr_group); |
475 | } |
476 | |
477 | static const struct of_device_id td043mtea1_of_match[] = { |
478 | { .compatible = "tpo,td043mtea1" , }, |
479 | { /* sentinel */ }, |
480 | }; |
481 | |
482 | MODULE_DEVICE_TABLE(of, td043mtea1_of_match); |
483 | |
484 | static const struct spi_device_id td043mtea1_ids[] = { |
485 | { "td043mtea1" , 0 }, |
486 | { /* sentinel */ } |
487 | }; |
488 | |
489 | MODULE_DEVICE_TABLE(spi, td043mtea1_ids); |
490 | |
491 | static struct spi_driver td043mtea1_driver = { |
492 | .probe = td043mtea1_probe, |
493 | .remove = td043mtea1_remove, |
494 | .id_table = td043mtea1_ids, |
495 | .driver = { |
496 | .name = "panel-tpo-td043mtea1" , |
497 | .pm = &td043mtea1_pm_ops, |
498 | .of_match_table = td043mtea1_of_match, |
499 | }, |
500 | }; |
501 | |
502 | module_spi_driver(td043mtea1_driver); |
503 | |
504 | MODULE_AUTHOR("Gražvydas Ignotas <notasas@gmail.com>" ); |
505 | MODULE_DESCRIPTION("TPO TD043MTEA1 Panel Driver" ); |
506 | MODULE_LICENSE("GPL" ); |
507 | |