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