1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Toppoly TD028TTEC1 Panel Driver |
4 | * |
5 | * Copyright (C) 2019 Texas Instruments Incorporated |
6 | * |
7 | * Based on the omapdrm-specific panel-tpo-td028ttec1 driver |
8 | * |
9 | * Copyright (C) 2008 Nokia Corporation |
10 | * Author: Tomi Valkeinen <tomi.valkeinen@ti.com> |
11 | * |
12 | * Neo 1973 code (jbt6k74.c): |
13 | * Copyright (C) 2006-2007 OpenMoko, Inc. |
14 | * Author: Harald Welte <laforge@openmoko.org> |
15 | * |
16 | * Ported and adapted from Neo 1973 U-Boot by: |
17 | * H. Nikolaus Schaller <hns@goldelico.com> |
18 | */ |
19 | |
20 | #include <linux/delay.h> |
21 | #include <linux/module.h> |
22 | #include <linux/spi/spi.h> |
23 | |
24 | #include <drm/drm_connector.h> |
25 | #include <drm/drm_modes.h> |
26 | #include <drm/drm_panel.h> |
27 | |
28 | #define JBT_COMMAND 0x000 |
29 | #define JBT_DATA 0x100 |
30 | |
31 | #define JBT_REG_SLEEP_IN 0x10 |
32 | #define JBT_REG_SLEEP_OUT 0x11 |
33 | |
34 | #define JBT_REG_DISPLAY_OFF 0x28 |
35 | #define JBT_REG_DISPLAY_ON 0x29 |
36 | |
37 | #define JBT_REG_RGB_FORMAT 0x3a |
38 | #define JBT_REG_QUAD_RATE 0x3b |
39 | |
40 | #define JBT_REG_POWER_ON_OFF 0xb0 |
41 | #define JBT_REG_BOOSTER_OP 0xb1 |
42 | #define JBT_REG_BOOSTER_MODE 0xb2 |
43 | #define JBT_REG_BOOSTER_FREQ 0xb3 |
44 | #define JBT_REG_OPAMP_SYSCLK 0xb4 |
45 | #define JBT_REG_VSC_VOLTAGE 0xb5 |
46 | #define JBT_REG_VCOM_VOLTAGE 0xb6 |
47 | #define JBT_REG_EXT_DISPL 0xb7 |
48 | #define JBT_REG_OUTPUT_CONTROL 0xb8 |
49 | #define JBT_REG_DCCLK_DCEV 0xb9 |
50 | #define JBT_REG_DISPLAY_MODE1 0xba |
51 | #define JBT_REG_DISPLAY_MODE2 0xbb |
52 | #define JBT_REG_DISPLAY_MODE 0xbc |
53 | #define JBT_REG_ASW_SLEW 0xbd |
54 | #define JBT_REG_DUMMY_DISPLAY 0xbe |
55 | #define JBT_REG_DRIVE_SYSTEM 0xbf |
56 | |
57 | #define JBT_REG_SLEEP_OUT_FR_A 0xc0 |
58 | #define JBT_REG_SLEEP_OUT_FR_B 0xc1 |
59 | #define JBT_REG_SLEEP_OUT_FR_C 0xc2 |
60 | #define JBT_REG_SLEEP_IN_LCCNT_D 0xc3 |
61 | #define JBT_REG_SLEEP_IN_LCCNT_E 0xc4 |
62 | #define JBT_REG_SLEEP_IN_LCCNT_F 0xc5 |
63 | #define JBT_REG_SLEEP_IN_LCCNT_G 0xc6 |
64 | |
65 | #define JBT_REG_GAMMA1_FINE_1 0xc7 |
66 | #define JBT_REG_GAMMA1_FINE_2 0xc8 |
67 | #define JBT_REG_GAMMA1_INCLINATION 0xc9 |
68 | #define JBT_REG_GAMMA1_BLUE_OFFSET 0xca |
69 | |
70 | #define JBT_REG_BLANK_CONTROL 0xcf |
71 | #define JBT_REG_BLANK_TH_TV 0xd0 |
72 | #define JBT_REG_CKV_ON_OFF 0xd1 |
73 | #define JBT_REG_CKV_1_2 0xd2 |
74 | #define JBT_REG_OEV_TIMING 0xd3 |
75 | #define JBT_REG_ASW_TIMING_1 0xd4 |
76 | #define JBT_REG_ASW_TIMING_2 0xd5 |
77 | |
78 | #define JBT_REG_HCLOCK_VGA 0xec |
79 | #define JBT_REG_HCLOCK_QVGA 0xed |
80 | |
81 | struct td028ttec1_panel { |
82 | struct drm_panel panel; |
83 | |
84 | struct spi_device *spi; |
85 | }; |
86 | |
87 | #define to_td028ttec1_device(p) container_of(p, struct td028ttec1_panel, panel) |
88 | |
89 | /* |
90 | * noinline_for_stack so we don't get multiple copies of tx_buf |
91 | * on the stack in case of gcc-plugin-structleak |
92 | */ |
93 | static int noinline_for_stack |
94 | jbt_ret_write_0(struct td028ttec1_panel *lcd, u8 reg, int *err) |
95 | { |
96 | struct spi_device *spi = lcd->spi; |
97 | u16 tx_buf = JBT_COMMAND | reg; |
98 | int ret; |
99 | |
100 | if (err && *err) |
101 | return *err; |
102 | |
103 | ret = spi_write(spi, buf: (u8 *)&tx_buf, len: sizeof(tx_buf)); |
104 | if (ret < 0) { |
105 | dev_err(&spi->dev, "%s: SPI write failed: %d\n" , __func__, ret); |
106 | if (err) |
107 | *err = ret; |
108 | } |
109 | |
110 | return ret; |
111 | } |
112 | |
113 | static int noinline_for_stack |
114 | jbt_reg_write_1(struct td028ttec1_panel *lcd, |
115 | u8 reg, u8 data, int *err) |
116 | { |
117 | struct spi_device *spi = lcd->spi; |
118 | u16 tx_buf[2]; |
119 | int ret; |
120 | |
121 | if (err && *err) |
122 | return *err; |
123 | |
124 | tx_buf[0] = JBT_COMMAND | reg; |
125 | tx_buf[1] = JBT_DATA | data; |
126 | |
127 | ret = spi_write(spi, buf: (u8 *)tx_buf, len: sizeof(tx_buf)); |
128 | if (ret < 0) { |
129 | dev_err(&spi->dev, "%s: SPI write failed: %d\n" , __func__, ret); |
130 | if (err) |
131 | *err = ret; |
132 | } |
133 | |
134 | return ret; |
135 | } |
136 | |
137 | static int noinline_for_stack |
138 | jbt_reg_write_2(struct td028ttec1_panel *lcd, |
139 | u8 reg, u16 data, int *err) |
140 | { |
141 | struct spi_device *spi = lcd->spi; |
142 | u16 tx_buf[3]; |
143 | int ret; |
144 | |
145 | if (err && *err) |
146 | return *err; |
147 | |
148 | tx_buf[0] = JBT_COMMAND | reg; |
149 | tx_buf[1] = JBT_DATA | (data >> 8); |
150 | tx_buf[2] = JBT_DATA | (data & 0xff); |
151 | |
152 | ret = spi_write(spi, buf: (u8 *)tx_buf, len: sizeof(tx_buf)); |
153 | if (ret < 0) { |
154 | dev_err(&spi->dev, "%s: SPI write failed: %d\n" , __func__, ret); |
155 | if (err) |
156 | *err = ret; |
157 | } |
158 | |
159 | return ret; |
160 | } |
161 | |
162 | static int td028ttec1_prepare(struct drm_panel *panel) |
163 | { |
164 | struct td028ttec1_panel *lcd = to_td028ttec1_device(panel); |
165 | unsigned int i; |
166 | int ret = 0; |
167 | |
168 | /* Three times command zero */ |
169 | for (i = 0; i < 3; ++i) { |
170 | jbt_ret_write_0(lcd, reg: 0x00, err: &ret); |
171 | usleep_range(min: 1000, max: 2000); |
172 | } |
173 | |
174 | /* deep standby out */ |
175 | jbt_reg_write_1(lcd, JBT_REG_POWER_ON_OFF, data: 0x17, err: &ret); |
176 | |
177 | /* RGB I/F on, RAM write off, QVGA through, SIGCON enable */ |
178 | jbt_reg_write_1(lcd, JBT_REG_DISPLAY_MODE, data: 0x80, err: &ret); |
179 | |
180 | /* Quad mode off */ |
181 | jbt_reg_write_1(lcd, JBT_REG_QUAD_RATE, data: 0x00, err: &ret); |
182 | |
183 | /* AVDD on, XVDD on */ |
184 | jbt_reg_write_1(lcd, JBT_REG_POWER_ON_OFF, data: 0x16, err: &ret); |
185 | |
186 | /* Output control */ |
187 | jbt_reg_write_2(lcd, JBT_REG_OUTPUT_CONTROL, data: 0xfff9, err: &ret); |
188 | |
189 | /* Sleep mode off */ |
190 | jbt_ret_write_0(lcd, JBT_REG_SLEEP_OUT, err: &ret); |
191 | |
192 | /* at this point we have like 50% grey */ |
193 | |
194 | /* initialize register set */ |
195 | jbt_reg_write_1(lcd, JBT_REG_DISPLAY_MODE1, data: 0x01, err: &ret); |
196 | jbt_reg_write_1(lcd, JBT_REG_DISPLAY_MODE2, data: 0x00, err: &ret); |
197 | jbt_reg_write_1(lcd, JBT_REG_RGB_FORMAT, data: 0x60, err: &ret); |
198 | jbt_reg_write_1(lcd, JBT_REG_DRIVE_SYSTEM, data: 0x10, err: &ret); |
199 | jbt_reg_write_1(lcd, JBT_REG_BOOSTER_OP, data: 0x56, err: &ret); |
200 | jbt_reg_write_1(lcd, JBT_REG_BOOSTER_MODE, data: 0x33, err: &ret); |
201 | jbt_reg_write_1(lcd, JBT_REG_BOOSTER_FREQ, data: 0x11, err: &ret); |
202 | jbt_reg_write_1(lcd, JBT_REG_BOOSTER_FREQ, data: 0x11, err: &ret); |
203 | jbt_reg_write_1(lcd, JBT_REG_OPAMP_SYSCLK, data: 0x02, err: &ret); |
204 | jbt_reg_write_1(lcd, JBT_REG_VSC_VOLTAGE, data: 0x2b, err: &ret); |
205 | jbt_reg_write_1(lcd, JBT_REG_VCOM_VOLTAGE, data: 0x40, err: &ret); |
206 | jbt_reg_write_1(lcd, JBT_REG_EXT_DISPL, data: 0x03, err: &ret); |
207 | jbt_reg_write_1(lcd, JBT_REG_DCCLK_DCEV, data: 0x04, err: &ret); |
208 | /* |
209 | * default of 0x02 in JBT_REG_ASW_SLEW responsible for 72Hz requirement |
210 | * to avoid red / blue flicker |
211 | */ |
212 | jbt_reg_write_1(lcd, JBT_REG_ASW_SLEW, data: 0x04, err: &ret); |
213 | jbt_reg_write_1(lcd, JBT_REG_DUMMY_DISPLAY, data: 0x00, err: &ret); |
214 | |
215 | jbt_reg_write_1(lcd, JBT_REG_SLEEP_OUT_FR_A, data: 0x11, err: &ret); |
216 | jbt_reg_write_1(lcd, JBT_REG_SLEEP_OUT_FR_B, data: 0x11, err: &ret); |
217 | jbt_reg_write_1(lcd, JBT_REG_SLEEP_OUT_FR_C, data: 0x11, err: &ret); |
218 | jbt_reg_write_2(lcd, JBT_REG_SLEEP_IN_LCCNT_D, data: 0x2040, err: &ret); |
219 | jbt_reg_write_2(lcd, JBT_REG_SLEEP_IN_LCCNT_E, data: 0x60c0, err: &ret); |
220 | jbt_reg_write_2(lcd, JBT_REG_SLEEP_IN_LCCNT_F, data: 0x1020, err: &ret); |
221 | jbt_reg_write_2(lcd, JBT_REG_SLEEP_IN_LCCNT_G, data: 0x60c0, err: &ret); |
222 | |
223 | jbt_reg_write_2(lcd, JBT_REG_GAMMA1_FINE_1, data: 0x5533, err: &ret); |
224 | jbt_reg_write_1(lcd, JBT_REG_GAMMA1_FINE_2, data: 0x00, err: &ret); |
225 | jbt_reg_write_1(lcd, JBT_REG_GAMMA1_INCLINATION, data: 0x00, err: &ret); |
226 | jbt_reg_write_1(lcd, JBT_REG_GAMMA1_BLUE_OFFSET, data: 0x00, err: &ret); |
227 | |
228 | jbt_reg_write_2(lcd, JBT_REG_HCLOCK_VGA, data: 0x1f0, err: &ret); |
229 | jbt_reg_write_1(lcd, JBT_REG_BLANK_CONTROL, data: 0x02, err: &ret); |
230 | jbt_reg_write_2(lcd, JBT_REG_BLANK_TH_TV, data: 0x0804, err: &ret); |
231 | |
232 | jbt_reg_write_1(lcd, JBT_REG_CKV_ON_OFF, data: 0x01, err: &ret); |
233 | jbt_reg_write_2(lcd, JBT_REG_CKV_1_2, data: 0x0000, err: &ret); |
234 | |
235 | jbt_reg_write_2(lcd, JBT_REG_OEV_TIMING, data: 0x0d0e, err: &ret); |
236 | jbt_reg_write_2(lcd, JBT_REG_ASW_TIMING_1, data: 0x11a4, err: &ret); |
237 | jbt_reg_write_1(lcd, JBT_REG_ASW_TIMING_2, data: 0x0e, err: &ret); |
238 | |
239 | return ret; |
240 | } |
241 | |
242 | static int td028ttec1_enable(struct drm_panel *panel) |
243 | { |
244 | struct td028ttec1_panel *lcd = to_td028ttec1_device(panel); |
245 | |
246 | return jbt_ret_write_0(lcd, JBT_REG_DISPLAY_ON, NULL); |
247 | } |
248 | |
249 | static int td028ttec1_disable(struct drm_panel *panel) |
250 | { |
251 | struct td028ttec1_panel *lcd = to_td028ttec1_device(panel); |
252 | |
253 | jbt_ret_write_0(lcd, JBT_REG_DISPLAY_OFF, NULL); |
254 | |
255 | return 0; |
256 | } |
257 | |
258 | static int td028ttec1_unprepare(struct drm_panel *panel) |
259 | { |
260 | struct td028ttec1_panel *lcd = to_td028ttec1_device(panel); |
261 | |
262 | jbt_reg_write_2(lcd, JBT_REG_OUTPUT_CONTROL, data: 0x8002, NULL); |
263 | jbt_ret_write_0(lcd, JBT_REG_SLEEP_IN, NULL); |
264 | jbt_reg_write_1(lcd, JBT_REG_POWER_ON_OFF, data: 0x00, NULL); |
265 | |
266 | return 0; |
267 | } |
268 | |
269 | static const struct drm_display_mode td028ttec1_mode = { |
270 | .clock = 22153, |
271 | .hdisplay = 480, |
272 | .hsync_start = 480 + 24, |
273 | .hsync_end = 480 + 24 + 8, |
274 | .htotal = 480 + 24 + 8 + 8, |
275 | .vdisplay = 640, |
276 | .vsync_start = 640 + 4, |
277 | .vsync_end = 640 + 4 + 2, |
278 | .vtotal = 640 + 4 + 2 + 2, |
279 | .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, |
280 | .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, |
281 | .width_mm = 43, |
282 | .height_mm = 58, |
283 | }; |
284 | |
285 | static int td028ttec1_get_modes(struct drm_panel *panel, |
286 | struct drm_connector *connector) |
287 | { |
288 | struct drm_display_mode *mode; |
289 | |
290 | mode = drm_mode_duplicate(dev: connector->dev, mode: &td028ttec1_mode); |
291 | if (!mode) |
292 | return -ENOMEM; |
293 | |
294 | drm_mode_set_name(mode); |
295 | drm_mode_probed_add(connector, mode); |
296 | |
297 | connector->display_info.width_mm = td028ttec1_mode.width_mm; |
298 | connector->display_info.height_mm = td028ttec1_mode.height_mm; |
299 | /* |
300 | * FIXME: According to the datasheet sync signals are sampled on the |
301 | * rising edge of the clock, but the code running on the OpenMoko Neo |
302 | * FreeRunner and Neo 1973 indicates sampling on the falling edge. This |
303 | * should be tested on a real device. |
304 | */ |
305 | connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH |
306 | | DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE |
307 | | DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE; |
308 | |
309 | return 1; |
310 | } |
311 | |
312 | static const struct drm_panel_funcs td028ttec1_funcs = { |
313 | .prepare = td028ttec1_prepare, |
314 | .enable = td028ttec1_enable, |
315 | .disable = td028ttec1_disable, |
316 | .unprepare = td028ttec1_unprepare, |
317 | .get_modes = td028ttec1_get_modes, |
318 | }; |
319 | |
320 | static int td028ttec1_probe(struct spi_device *spi) |
321 | { |
322 | struct td028ttec1_panel *lcd; |
323 | int ret; |
324 | |
325 | lcd = devm_kzalloc(dev: &spi->dev, size: sizeof(*lcd), GFP_KERNEL); |
326 | if (!lcd) |
327 | return -ENOMEM; |
328 | |
329 | spi_set_drvdata(spi, data: lcd); |
330 | lcd->spi = spi; |
331 | |
332 | spi->mode = SPI_MODE_3; |
333 | spi->bits_per_word = 9; |
334 | |
335 | ret = spi_setup(spi); |
336 | if (ret < 0) { |
337 | dev_err(&spi->dev, "failed to setup SPI: %d\n" , ret); |
338 | return ret; |
339 | } |
340 | |
341 | drm_panel_init(panel: &lcd->panel, dev: &lcd->spi->dev, funcs: &td028ttec1_funcs, |
342 | DRM_MODE_CONNECTOR_DPI); |
343 | |
344 | ret = drm_panel_of_backlight(panel: &lcd->panel); |
345 | if (ret) |
346 | return ret; |
347 | |
348 | drm_panel_add(panel: &lcd->panel); |
349 | |
350 | return 0; |
351 | } |
352 | |
353 | static void td028ttec1_remove(struct spi_device *spi) |
354 | { |
355 | struct td028ttec1_panel *lcd = spi_get_drvdata(spi); |
356 | |
357 | drm_panel_remove(panel: &lcd->panel); |
358 | drm_panel_disable(panel: &lcd->panel); |
359 | drm_panel_unprepare(panel: &lcd->panel); |
360 | } |
361 | |
362 | static const struct of_device_id td028ttec1_of_match[] = { |
363 | { .compatible = "tpo,td028ttec1" , }, |
364 | /* DT backward compatibility. */ |
365 | { .compatible = "toppoly,td028ttec1" , }, |
366 | { /* sentinel */ }, |
367 | }; |
368 | |
369 | MODULE_DEVICE_TABLE(of, td028ttec1_of_match); |
370 | |
371 | static const struct spi_device_id td028ttec1_ids[] = { |
372 | { "td028ttec1" , 0 }, |
373 | { /* sentinel */ } |
374 | }; |
375 | |
376 | MODULE_DEVICE_TABLE(spi, td028ttec1_ids); |
377 | |
378 | static struct spi_driver td028ttec1_driver = { |
379 | .probe = td028ttec1_probe, |
380 | .remove = td028ttec1_remove, |
381 | .id_table = td028ttec1_ids, |
382 | .driver = { |
383 | .name = "panel-tpo-td028ttec1" , |
384 | .of_match_table = td028ttec1_of_match, |
385 | }, |
386 | }; |
387 | |
388 | module_spi_driver(td028ttec1_driver); |
389 | |
390 | MODULE_AUTHOR("H. Nikolaus Schaller <hns@goldelico.com>" ); |
391 | MODULE_DESCRIPTION("Toppoly TD028TTEC1 panel driver" ); |
392 | MODULE_LICENSE("GPL" ); |
393 | |