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