1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Asia Better Technology Ltd. Y030XX067A IPS LCD panel driver |
4 | * |
5 | * Copyright (C) 2020, Paul Cercueil <paul@crapouillou.net> |
6 | * Copyright (C) 2020, Christophe Branchereau <cbranchereau@gmail.com> |
7 | */ |
8 | |
9 | #include <linux/delay.h> |
10 | #include <linux/device.h> |
11 | #include <linux/gpio/consumer.h> |
12 | #include <linux/media-bus-format.h> |
13 | #include <linux/module.h> |
14 | #include <linux/of.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/regmap.h> |
17 | #include <linux/regulator/consumer.h> |
18 | #include <linux/spi/spi.h> |
19 | |
20 | #include <drm/drm_modes.h> |
21 | #include <drm/drm_panel.h> |
22 | |
23 | #define REG00_VBRT_CTRL(val) (val) |
24 | |
25 | #define REG01_COM_DC(val) (val) |
26 | |
27 | #define REG02_DA_CONTRAST(val) (val) |
28 | #define REG02_VESA_SEL(val) ((val) << 5) |
29 | #define REG02_COMDC_SW BIT(7) |
30 | |
31 | #define REG03_VPOSITION(val) (val) |
32 | #define REG03_BSMOUNT BIT(5) |
33 | #define REG03_COMTST BIT(6) |
34 | #define REG03_HPOSITION1 BIT(7) |
35 | |
36 | #define REG04_HPOSITION1(val) (val) |
37 | |
38 | #define REG05_CLIP BIT(0) |
39 | #define REG05_NVM_VREFRESH BIT(1) |
40 | #define REG05_SLFR BIT(2) |
41 | #define REG05_SLBRCHARGE(val) ((val) << 3) |
42 | #define REG05_PRECHARGE_LEVEL(val) ((val) << 6) |
43 | |
44 | #define REG06_TEST5 BIT(0) |
45 | #define REG06_SLDWN BIT(1) |
46 | #define REG06_SLRGT BIT(2) |
47 | #define REG06_TEST2 BIT(3) |
48 | #define REG06_XPSAVE BIT(4) |
49 | #define REG06_GAMMA_SEL(val) ((val) << 5) |
50 | #define REG06_NT BIT(7) |
51 | |
52 | #define REG07_TEST1 BIT(0) |
53 | #define REG07_HDVD_POL BIT(1) |
54 | #define REG07_CK_POL BIT(2) |
55 | #define REG07_TEST3 BIT(3) |
56 | #define REG07_TEST4 BIT(4) |
57 | #define REG07_480_LINEMASK BIT(5) |
58 | #define REG07_AMPTST(val) ((val) << 6) |
59 | |
60 | #define REG08_SLHRC(val) (val) |
61 | #define REG08_CLOCK_DIV(val) ((val) << 2) |
62 | #define REG08_PANEL(val) ((val) << 5) |
63 | |
64 | #define REG09_SUB_BRIGHT_R(val) (val) |
65 | #define REG09_NW_NB BIT(6) |
66 | #define REG09_IPCON BIT(7) |
67 | |
68 | #define REG0A_SUB_BRIGHT_B(val) (val) |
69 | #define REG0A_PAIR BIT(6) |
70 | #define REG0A_DE_SEL BIT(7) |
71 | |
72 | #define REG0B_MBK_POSITION(val) (val) |
73 | #define REG0B_HD_FREERUN BIT(4) |
74 | #define REG0B_VD_FREERUN BIT(5) |
75 | #define REG0B_YUV2BIN(val) ((val) << 6) |
76 | |
77 | #define REG0C_CONTRAST_R(val) (val) |
78 | #define REG0C_DOUBLEREAD BIT(7) |
79 | |
80 | #define REG0D_CONTRAST_G(val) (val) |
81 | #define REG0D_RGB_YUV BIT(7) |
82 | |
83 | #define REG0E_CONTRAST_B(val) (val) |
84 | #define REG0E_PIXELCOLORDRIVE BIT(7) |
85 | |
86 | #define REG0F_ASPECT BIT(0) |
87 | #define REG0F_OVERSCAN(val) ((val) << 1) |
88 | #define REG0F_FRAMEWIDTH(val) ((val) << 3) |
89 | |
90 | #define REG10_BRIGHT(val) (val) |
91 | |
92 | #define REG11_SIG_GAIN(val) (val) |
93 | #define REG11_SIGC_CNTL BIT(6) |
94 | #define REG11_SIGC_POL BIT(7) |
95 | |
96 | #define REG12_COLOR(val) (val) |
97 | #define REG12_PWCKSEL(val) ((val) << 6) |
98 | |
99 | #define REG13_4096LEVEL_CNTL(val) (val) |
100 | #define REG13_SL4096(val) ((val) << 4) |
101 | #define REG13_LIMITER_CONTROL BIT(7) |
102 | |
103 | #define REG14_PANEL_TEST(val) (val) |
104 | |
105 | #define REG15_NVM_LINK0 BIT(0) |
106 | #define REG15_NVM_LINK1 BIT(1) |
107 | #define REG15_NVM_LINK2 BIT(2) |
108 | #define REG15_NVM_LINK3 BIT(3) |
109 | #define REG15_NVM_LINK4 BIT(4) |
110 | #define REG15_NVM_LINK5 BIT(5) |
111 | #define REG15_NVM_LINK6 BIT(6) |
112 | #define REG15_NVM_LINK7 BIT(7) |
113 | |
114 | struct y030xx067a_info { |
115 | const struct drm_display_mode *display_modes; |
116 | unsigned int num_modes; |
117 | u16 width_mm, height_mm; |
118 | u32 bus_format, bus_flags; |
119 | }; |
120 | |
121 | struct y030xx067a { |
122 | struct drm_panel panel; |
123 | struct spi_device *spi; |
124 | struct regmap *map; |
125 | |
126 | const struct y030xx067a_info *panel_info; |
127 | |
128 | struct regulator *supply; |
129 | struct gpio_desc *reset_gpio; |
130 | }; |
131 | |
132 | static inline struct y030xx067a *to_y030xx067a(struct drm_panel *panel) |
133 | { |
134 | return container_of(panel, struct y030xx067a, panel); |
135 | } |
136 | |
137 | static const struct reg_sequence y030xx067a_init_sequence[] = { |
138 | { 0x00, REG00_VBRT_CTRL(0x7f) }, |
139 | { 0x01, REG01_COM_DC(0x3c) }, |
140 | { 0x02, REG02_VESA_SEL(0x3) | REG02_DA_CONTRAST(0x1f) }, |
141 | { 0x03, REG03_VPOSITION(0x0a) }, |
142 | { 0x04, REG04_HPOSITION1(0xd2) }, |
143 | { 0x05, REG05_CLIP | REG05_NVM_VREFRESH | REG05_SLBRCHARGE(0x2) }, |
144 | { 0x06, REG06_NT }, |
145 | { 0x07, 0 }, |
146 | { 0x08, REG08_PANEL(0x1) | REG08_CLOCK_DIV(0x2) }, |
147 | { 0x09, REG09_SUB_BRIGHT_R(0x20) }, |
148 | { 0x0a, REG0A_SUB_BRIGHT_B(0x20) }, |
149 | { 0x0b, REG0B_HD_FREERUN | REG0B_VD_FREERUN }, |
150 | { 0x0c, REG0C_CONTRAST_R(0x00) }, |
151 | { 0x0d, REG0D_CONTRAST_G(0x00) }, |
152 | { 0x0e, REG0E_CONTRAST_B(0x10) }, |
153 | { 0x0f, 0 }, |
154 | { 0x10, REG10_BRIGHT(0x7f) }, |
155 | { 0x11, REG11_SIGC_CNTL | REG11_SIG_GAIN(0x3f) }, |
156 | { 0x12, REG12_COLOR(0x20) | REG12_PWCKSEL(0x1) }, |
157 | { 0x13, REG13_4096LEVEL_CNTL(0x8) }, |
158 | { 0x14, 0 }, |
159 | { 0x15, 0 }, |
160 | }; |
161 | |
162 | static int y030xx067a_prepare(struct drm_panel *panel) |
163 | { |
164 | struct y030xx067a *priv = to_y030xx067a(panel); |
165 | struct device *dev = &priv->spi->dev; |
166 | int err; |
167 | |
168 | err = regulator_enable(regulator: priv->supply); |
169 | if (err) { |
170 | dev_err(dev, "Failed to enable power supply: %d\n" , err); |
171 | return err; |
172 | } |
173 | |
174 | /* Reset the chip */ |
175 | gpiod_set_value_cansleep(desc: priv->reset_gpio, value: 1); |
176 | usleep_range(min: 1000, max: 20000); |
177 | gpiod_set_value_cansleep(desc: priv->reset_gpio, value: 0); |
178 | usleep_range(min: 1000, max: 20000); |
179 | |
180 | err = regmap_multi_reg_write(map: priv->map, regs: y030xx067a_init_sequence, |
181 | ARRAY_SIZE(y030xx067a_init_sequence)); |
182 | if (err) { |
183 | dev_err(dev, "Failed to init registers: %d\n" , err); |
184 | goto err_disable_regulator; |
185 | } |
186 | |
187 | return 0; |
188 | |
189 | err_disable_regulator: |
190 | regulator_disable(regulator: priv->supply); |
191 | return err; |
192 | } |
193 | |
194 | static int y030xx067a_unprepare(struct drm_panel *panel) |
195 | { |
196 | struct y030xx067a *priv = to_y030xx067a(panel); |
197 | |
198 | gpiod_set_value_cansleep(desc: priv->reset_gpio, value: 1); |
199 | regulator_disable(regulator: priv->supply); |
200 | |
201 | return 0; |
202 | } |
203 | |
204 | static int y030xx067a_enable(struct drm_panel *panel) |
205 | { |
206 | struct y030xx067a *priv = to_y030xx067a(panel); |
207 | |
208 | regmap_set_bits(map: priv->map, reg: 0x06, REG06_XPSAVE); |
209 | |
210 | if (panel->backlight) { |
211 | /* Wait for the picture to be ready before enabling backlight */ |
212 | msleep(msecs: 120); |
213 | } |
214 | |
215 | return 0; |
216 | } |
217 | |
218 | static int y030xx067a_disable(struct drm_panel *panel) |
219 | { |
220 | struct y030xx067a *priv = to_y030xx067a(panel); |
221 | |
222 | regmap_clear_bits(map: priv->map, reg: 0x06, REG06_XPSAVE); |
223 | |
224 | return 0; |
225 | } |
226 | |
227 | static int y030xx067a_get_modes(struct drm_panel *panel, |
228 | struct drm_connector *connector) |
229 | { |
230 | struct y030xx067a *priv = to_y030xx067a(panel); |
231 | const struct y030xx067a_info *panel_info = priv->panel_info; |
232 | struct drm_display_mode *mode; |
233 | unsigned int i; |
234 | |
235 | for (i = 0; i < panel_info->num_modes; i++) { |
236 | mode = drm_mode_duplicate(dev: connector->dev, |
237 | mode: &panel_info->display_modes[i]); |
238 | if (!mode) |
239 | return -ENOMEM; |
240 | |
241 | drm_mode_set_name(mode); |
242 | |
243 | mode->type = DRM_MODE_TYPE_DRIVER; |
244 | if (panel_info->num_modes == 1) |
245 | mode->type |= DRM_MODE_TYPE_PREFERRED; |
246 | |
247 | drm_mode_probed_add(connector, mode); |
248 | } |
249 | |
250 | connector->display_info.bpc = 8; |
251 | connector->display_info.width_mm = panel_info->width_mm; |
252 | connector->display_info.height_mm = panel_info->height_mm; |
253 | |
254 | drm_display_info_set_bus_formats(info: &connector->display_info, |
255 | formats: &panel_info->bus_format, num_formats: 1); |
256 | connector->display_info.bus_flags = panel_info->bus_flags; |
257 | |
258 | return panel_info->num_modes; |
259 | } |
260 | |
261 | static const struct drm_panel_funcs y030xx067a_funcs = { |
262 | .prepare = y030xx067a_prepare, |
263 | .unprepare = y030xx067a_unprepare, |
264 | .enable = y030xx067a_enable, |
265 | .disable = y030xx067a_disable, |
266 | .get_modes = y030xx067a_get_modes, |
267 | }; |
268 | |
269 | static const struct regmap_config y030xx067a_regmap_config = { |
270 | .reg_bits = 8, |
271 | .val_bits = 8, |
272 | .max_register = 0x15, |
273 | .cache_type = REGCACHE_FLAT, |
274 | }; |
275 | |
276 | static int y030xx067a_probe(struct spi_device *spi) |
277 | { |
278 | struct device *dev = &spi->dev; |
279 | struct y030xx067a *priv; |
280 | int err; |
281 | |
282 | priv = devm_kzalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
283 | if (!priv) |
284 | return -ENOMEM; |
285 | |
286 | priv->spi = spi; |
287 | spi_set_drvdata(spi, data: priv); |
288 | |
289 | priv->map = devm_regmap_init_spi(spi, &y030xx067a_regmap_config); |
290 | if (IS_ERR(ptr: priv->map)) { |
291 | dev_err(dev, "Unable to init regmap\n" ); |
292 | return PTR_ERR(ptr: priv->map); |
293 | } |
294 | |
295 | priv->panel_info = of_device_get_match_data(dev); |
296 | if (!priv->panel_info) |
297 | return -EINVAL; |
298 | |
299 | priv->supply = devm_regulator_get(dev, id: "power" ); |
300 | if (IS_ERR(ptr: priv->supply)) |
301 | return dev_err_probe(dev, err: PTR_ERR(ptr: priv->supply), |
302 | fmt: "Failed to get power supply\n" ); |
303 | |
304 | priv->reset_gpio = devm_gpiod_get(dev, con_id: "reset" , flags: GPIOD_OUT_HIGH); |
305 | if (IS_ERR(ptr: priv->reset_gpio)) |
306 | return dev_err_probe(dev, err: PTR_ERR(ptr: priv->reset_gpio), |
307 | fmt: "Failed to get reset GPIO\n" ); |
308 | |
309 | drm_panel_init(panel: &priv->panel, dev, funcs: &y030xx067a_funcs, |
310 | DRM_MODE_CONNECTOR_DPI); |
311 | |
312 | err = drm_panel_of_backlight(panel: &priv->panel); |
313 | if (err) |
314 | return err; |
315 | |
316 | drm_panel_add(panel: &priv->panel); |
317 | |
318 | return 0; |
319 | } |
320 | |
321 | static void y030xx067a_remove(struct spi_device *spi) |
322 | { |
323 | struct y030xx067a *priv = spi_get_drvdata(spi); |
324 | |
325 | drm_panel_remove(panel: &priv->panel); |
326 | drm_panel_disable(panel: &priv->panel); |
327 | drm_panel_unprepare(panel: &priv->panel); |
328 | } |
329 | |
330 | static const struct drm_display_mode y030xx067a_modes[] = { |
331 | { /* 60 Hz */ |
332 | .clock = 14400, |
333 | .hdisplay = 320, |
334 | .hsync_start = 320 + 10, |
335 | .hsync_end = 320 + 10 + 37, |
336 | .htotal = 320 + 10 + 37 + 33, |
337 | .vdisplay = 480, |
338 | .vsync_start = 480 + 84, |
339 | .vsync_end = 480 + 84 + 20, |
340 | .vtotal = 480 + 84 + 20 + 16, |
341 | .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, |
342 | }, |
343 | { /* 50 Hz */ |
344 | .clock = 12000, |
345 | .hdisplay = 320, |
346 | .hsync_start = 320 + 10, |
347 | .hsync_end = 320 + 10 + 37, |
348 | .htotal = 320 + 10 + 37 + 33, |
349 | .vdisplay = 480, |
350 | .vsync_start = 480 + 84, |
351 | .vsync_end = 480 + 84 + 20, |
352 | .vtotal = 480 + 84 + 20 + 16, |
353 | .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, |
354 | }, |
355 | }; |
356 | |
357 | static const struct y030xx067a_info y030xx067a_info = { |
358 | .display_modes = y030xx067a_modes, |
359 | .num_modes = ARRAY_SIZE(y030xx067a_modes), |
360 | .width_mm = 69, |
361 | .height_mm = 51, |
362 | .bus_format = MEDIA_BUS_FMT_RGB888_3X8_DELTA, |
363 | .bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE | DRM_BUS_FLAG_DE_LOW, |
364 | }; |
365 | |
366 | static const struct of_device_id y030xx067a_of_match[] = { |
367 | { .compatible = "abt,y030xx067a" , .data = &y030xx067a_info }, |
368 | { /* sentinel */ } |
369 | }; |
370 | MODULE_DEVICE_TABLE(of, y030xx067a_of_match); |
371 | |
372 | static struct spi_driver y030xx067a_driver = { |
373 | .driver = { |
374 | .name = "abt-y030xx067a" , |
375 | .of_match_table = y030xx067a_of_match, |
376 | }, |
377 | .probe = y030xx067a_probe, |
378 | .remove = y030xx067a_remove, |
379 | }; |
380 | module_spi_driver(y030xx067a_driver); |
381 | |
382 | MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>" ); |
383 | MODULE_AUTHOR("Christophe Branchereau <cbranchereau@gmail.com>" ); |
384 | MODULE_LICENSE("GPL v2" ); |
385 | |