Commit | Line | Data |
---|---|---|
ff219937 AH |
1 | /* |
2 | * ld9040 AMOLED LCD drm_panel driver. | |
3 | * | |
4 | * Copyright (c) 2014 Samsung Electronics Co., Ltd | |
5 | * Derived from drivers/video/backlight/ld9040.c | |
6 | * | |
7 | * Andrzej Hajda <a.hajda@samsung.com> | |
8 | * | |
9 | * This program is free software; you can redistribute it and/or modify | |
10 | * it under the terms of the GNU General Public License version 2 as | |
11 | * published by the Free Software Foundation. | |
12 | */ | |
13 | ||
14 | #include <drm/drmP.h> | |
15 | #include <drm/drm_panel.h> | |
16 | ||
17 | #include <linux/gpio/consumer.h> | |
18 | #include <linux/regulator/consumer.h> | |
19 | #include <linux/spi/spi.h> | |
20 | ||
21 | #include <video/mipi_display.h> | |
22 | #include <video/of_videomode.h> | |
23 | #include <video/videomode.h> | |
24 | ||
25 | /* Manufacturer Command Set */ | |
26 | #define MCS_MANPWR 0xb0 | |
27 | #define MCS_ELVSS_ON 0xb1 | |
28 | #define MCS_USER_SETTING 0xf0 | |
29 | #define MCS_DISPCTL 0xf2 | |
dbd751ee | 30 | #define MCS_POWER_CTRL 0xf4 |
ff219937 AH |
31 | #define MCS_GTCON 0xf7 |
32 | #define MCS_PANEL_CONDITION 0xf8 | |
33 | #define MCS_GAMMA_SET1 0xf9 | |
34 | #define MCS_GAMMA_CTRL 0xfb | |
35 | ||
36 | /* array of gamma tables for gamma value 2.2 */ | |
37 | static u8 const ld9040_gammas[25][22] = { | |
38 | { 0xf9, 0x00, 0x13, 0xb2, 0xba, 0xd2, 0x00, 0x30, 0x00, 0xaf, 0xc0, | |
39 | 0xb8, 0xcd, 0x00, 0x3d, 0x00, 0xa8, 0xb8, 0xb7, 0xcd, 0x00, 0x44 }, | |
40 | { 0xf9, 0x00, 0x13, 0xb9, 0xb9, 0xd0, 0x00, 0x3c, 0x00, 0xaf, 0xbf, | |
41 | 0xb6, 0xcb, 0x00, 0x4b, 0x00, 0xa8, 0xb9, 0xb5, 0xcc, 0x00, 0x52 }, | |
42 | { 0xf9, 0x00, 0x13, 0xba, 0xb9, 0xcd, 0x00, 0x41, 0x00, 0xb0, 0xbe, | |
43 | 0xb5, 0xc9, 0x00, 0x51, 0x00, 0xa9, 0xb9, 0xb5, 0xca, 0x00, 0x57 }, | |
44 | { 0xf9, 0x00, 0x13, 0xb9, 0xb8, 0xcd, 0x00, 0x46, 0x00, 0xb1, 0xbc, | |
45 | 0xb5, 0xc8, 0x00, 0x56, 0x00, 0xaa, 0xb8, 0xb4, 0xc9, 0x00, 0x5d }, | |
46 | { 0xf9, 0x00, 0x13, 0xba, 0xb8, 0xcb, 0x00, 0x4b, 0x00, 0xb3, 0xbc, | |
47 | 0xb4, 0xc7, 0x00, 0x5c, 0x00, 0xac, 0xb8, 0xb4, 0xc8, 0x00, 0x62 }, | |
48 | { 0xf9, 0x00, 0x13, 0xbb, 0xb7, 0xca, 0x00, 0x4f, 0x00, 0xb4, 0xbb, | |
49 | 0xb3, 0xc7, 0x00, 0x60, 0x00, 0xad, 0xb8, 0xb4, 0xc7, 0x00, 0x67 }, | |
50 | { 0xf9, 0x00, 0x47, 0xba, 0xb6, 0xca, 0x00, 0x53, 0x00, 0xb5, 0xbb, | |
51 | 0xb3, 0xc6, 0x00, 0x65, 0x00, 0xae, 0xb8, 0xb3, 0xc7, 0x00, 0x6c }, | |
52 | { 0xf9, 0x00, 0x71, 0xbb, 0xb5, 0xc8, 0x00, 0x57, 0x00, 0xb5, 0xbb, | |
53 | 0xb0, 0xc5, 0x00, 0x6a, 0x00, 0xae, 0xb9, 0xb1, 0xc6, 0x00, 0x70 }, | |
54 | { 0xf9, 0x00, 0x7b, 0xbb, 0xb4, 0xc8, 0x00, 0x5b, 0x00, 0xb5, 0xba, | |
55 | 0xb1, 0xc4, 0x00, 0x6e, 0x00, 0xae, 0xb9, 0xb0, 0xc5, 0x00, 0x75 }, | |
56 | { 0xf9, 0x00, 0x82, 0xba, 0xb4, 0xc7, 0x00, 0x5f, 0x00, 0xb5, 0xba, | |
57 | 0xb0, 0xc3, 0x00, 0x72, 0x00, 0xae, 0xb8, 0xb0, 0xc3, 0x00, 0x7a }, | |
58 | { 0xf9, 0x00, 0x89, 0xba, 0xb3, 0xc8, 0x00, 0x62, 0x00, 0xb6, 0xba, | |
59 | 0xaf, 0xc3, 0x00, 0x76, 0x00, 0xaf, 0xb7, 0xae, 0xc4, 0x00, 0x7e }, | |
60 | { 0xf9, 0x00, 0x8b, 0xb9, 0xb3, 0xc7, 0x00, 0x65, 0x00, 0xb7, 0xb8, | |
61 | 0xaf, 0xc3, 0x00, 0x7a, 0x00, 0x80, 0xb6, 0xae, 0xc4, 0x00, 0x81 }, | |
62 | { 0xf9, 0x00, 0x93, 0xba, 0xb3, 0xc5, 0x00, 0x69, 0x00, 0xb8, 0xb9, | |
63 | 0xae, 0xc1, 0x00, 0x7f, 0x00, 0xb0, 0xb6, 0xae, 0xc3, 0x00, 0x85 }, | |
64 | { 0xf9, 0x00, 0x97, 0xba, 0xb2, 0xc5, 0x00, 0x6c, 0x00, 0xb8, 0xb8, | |
65 | 0xae, 0xc1, 0x00, 0x82, 0x00, 0xb0, 0xb6, 0xae, 0xc2, 0x00, 0x89 }, | |
66 | { 0xf9, 0x00, 0x9a, 0xba, 0xb1, 0xc4, 0x00, 0x6f, 0x00, 0xb8, 0xb8, | |
67 | 0xad, 0xc0, 0x00, 0x86, 0x00, 0xb0, 0xb7, 0xad, 0xc0, 0x00, 0x8d }, | |
68 | { 0xf9, 0x00, 0x9c, 0xb9, 0xb0, 0xc4, 0x00, 0x72, 0x00, 0xb8, 0xb8, | |
69 | 0xac, 0xbf, 0x00, 0x8a, 0x00, 0xb0, 0xb6, 0xac, 0xc0, 0x00, 0x91 }, | |
70 | { 0xf9, 0x00, 0x9e, 0xba, 0xb0, 0xc2, 0x00, 0x75, 0x00, 0xb9, 0xb8, | |
71 | 0xab, 0xbe, 0x00, 0x8e, 0x00, 0xb0, 0xb6, 0xac, 0xbf, 0x00, 0x94 }, | |
72 | { 0xf9, 0x00, 0xa0, 0xb9, 0xaf, 0xc3, 0x00, 0x77, 0x00, 0xb9, 0xb7, | |
73 | 0xab, 0xbe, 0x00, 0x90, 0x00, 0xb0, 0xb6, 0xab, 0xbf, 0x00, 0x97 }, | |
74 | { 0xf9, 0x00, 0xa2, 0xb9, 0xaf, 0xc2, 0x00, 0x7a, 0x00, 0xb9, 0xb7, | |
75 | 0xaa, 0xbd, 0x00, 0x94, 0x00, 0xb0, 0xb5, 0xab, 0xbf, 0x00, 0x9a }, | |
76 | { 0xf9, 0x00, 0xa4, 0xb9, 0xaf, 0xc1, 0x00, 0x7d, 0x00, 0xb9, 0xb6, | |
77 | 0xaa, 0xbb, 0x00, 0x97, 0x00, 0xb1, 0xb5, 0xaa, 0xbf, 0x00, 0x9d }, | |
78 | { 0xf9, 0x00, 0xa4, 0xb8, 0xb0, 0xbf, 0x00, 0x80, 0x00, 0xb8, 0xb6, | |
79 | 0xaa, 0xbc, 0x00, 0x9a, 0x00, 0xb0, 0xb5, 0xab, 0xbd, 0x00, 0xa0 }, | |
80 | { 0xf9, 0x00, 0xa8, 0xb8, 0xae, 0xbe, 0x00, 0x84, 0x00, 0xb9, 0xb7, | |
81 | 0xa8, 0xbc, 0x00, 0x9d, 0x00, 0xb2, 0xb5, 0xaa, 0xbc, 0x00, 0xa4 }, | |
82 | { 0xf9, 0x00, 0xa9, 0xb6, 0xad, 0xbf, 0x00, 0x86, 0x00, 0xb8, 0xb5, | |
83 | 0xa8, 0xbc, 0x00, 0xa0, 0x00, 0xb3, 0xb3, 0xa9, 0xbc, 0x00, 0xa7 }, | |
84 | { 0xf9, 0x00, 0xa9, 0xb7, 0xae, 0xbd, 0x00, 0x89, 0x00, 0xb7, 0xb6, | |
85 | 0xa8, 0xba, 0x00, 0xa4, 0x00, 0xb1, 0xb4, 0xaa, 0xbb, 0x00, 0xaa }, | |
86 | { 0xf9, 0x00, 0xa7, 0xb4, 0xae, 0xbf, 0x00, 0x91, 0x00, 0xb2, 0xb4, | |
87 | 0xaa, 0xbb, 0x00, 0xac, 0x00, 0xb3, 0xb1, 0xaa, 0xbc, 0x00, 0xb3 }, | |
88 | }; | |
89 | ||
90 | struct ld9040 { | |
91 | struct device *dev; | |
92 | struct drm_panel panel; | |
93 | ||
94 | struct regulator_bulk_data supplies[2]; | |
95 | struct gpio_desc *reset_gpio; | |
96 | u32 power_on_delay; | |
97 | u32 reset_delay; | |
98 | struct videomode vm; | |
99 | u32 width_mm; | |
100 | u32 height_mm; | |
101 | ||
102 | int brightness; | |
103 | ||
104 | /* This field is tested by functions directly accessing bus before | |
105 | * transfer, transfer is skipped if it is set. In case of transfer | |
106 | * failure or unexpected response the field is set to error value. | |
107 | * Such construct allows to eliminate many checks in higher level | |
108 | * functions. | |
109 | */ | |
110 | int error; | |
111 | }; | |
112 | ||
dff2ed27 TR |
113 | static inline struct ld9040 *panel_to_ld9040(struct drm_panel *panel) |
114 | { | |
115 | return container_of(panel, struct ld9040, panel); | |
116 | } | |
ff219937 AH |
117 | |
118 | static int ld9040_clear_error(struct ld9040 *ctx) | |
119 | { | |
120 | int ret = ctx->error; | |
121 | ||
122 | ctx->error = 0; | |
123 | return ret; | |
124 | } | |
125 | ||
126 | static int ld9040_spi_write_word(struct ld9040 *ctx, u16 data) | |
127 | { | |
128 | struct spi_device *spi = to_spi_device(ctx->dev); | |
129 | struct spi_transfer xfer = { | |
130 | .len = 2, | |
131 | .tx_buf = &data, | |
132 | }; | |
133 | struct spi_message msg; | |
134 | ||
135 | spi_message_init(&msg); | |
136 | spi_message_add_tail(&xfer, &msg); | |
137 | ||
138 | return spi_sync(spi, &msg); | |
139 | } | |
140 | ||
141 | static void ld9040_dcs_write(struct ld9040 *ctx, const u8 *data, size_t len) | |
142 | { | |
143 | int ret = 0; | |
144 | ||
145 | if (ctx->error < 0 || len == 0) | |
146 | return; | |
147 | ||
eccda2d1 | 148 | dev_dbg(ctx->dev, "writing dcs seq: %*ph\n", (int)len, data); |
ff219937 AH |
149 | ret = ld9040_spi_write_word(ctx, *data); |
150 | ||
151 | while (!ret && --len) { | |
152 | ++data; | |
153 | ret = ld9040_spi_write_word(ctx, *data | 0x100); | |
154 | } | |
155 | ||
156 | if (ret) { | |
eccda2d1 TR |
157 | dev_err(ctx->dev, "error %d writing dcs seq: %*ph\n", ret, |
158 | (int)len, data); | |
ff219937 AH |
159 | ctx->error = ret; |
160 | } | |
161 | ||
162 | usleep_range(300, 310); | |
163 | } | |
164 | ||
165 | #define ld9040_dcs_write_seq_static(ctx, seq...) \ | |
166 | ({\ | |
167 | static const u8 d[] = { seq };\ | |
168 | ld9040_dcs_write(ctx, d, ARRAY_SIZE(d));\ | |
169 | }) | |
170 | ||
171 | static void ld9040_brightness_set(struct ld9040 *ctx) | |
172 | { | |
173 | ld9040_dcs_write(ctx, ld9040_gammas[ctx->brightness], | |
174 | ARRAY_SIZE(ld9040_gammas[ctx->brightness])); | |
175 | ||
176 | ld9040_dcs_write_seq_static(ctx, MCS_GAMMA_CTRL, 0x02, 0x5a); | |
177 | } | |
178 | ||
179 | static void ld9040_init(struct ld9040 *ctx) | |
180 | { | |
181 | ld9040_dcs_write_seq_static(ctx, MCS_USER_SETTING, 0x5a, 0x5a); | |
182 | ld9040_dcs_write_seq_static(ctx, MCS_PANEL_CONDITION, | |
183 | 0x05, 0x65, 0x96, 0x71, 0x7d, 0x19, 0x3b, 0x0d, | |
184 | 0x19, 0x7e, 0x0d, 0xe2, 0x00, 0x00, 0x7e, 0x7d, | |
185 | 0x07, 0x07, 0x20, 0x20, 0x20, 0x02, 0x02); | |
186 | ld9040_dcs_write_seq_static(ctx, MCS_DISPCTL, | |
187 | 0x02, 0x08, 0x08, 0x10, 0x10); | |
188 | ld9040_dcs_write_seq_static(ctx, MCS_MANPWR, 0x04); | |
dbd751ee AH |
189 | ld9040_dcs_write_seq_static(ctx, MCS_POWER_CTRL, |
190 | 0x0a, 0x87, 0x25, 0x6a, 0x44, 0x02, 0x88); | |
ff219937 AH |
191 | ld9040_dcs_write_seq_static(ctx, MCS_ELVSS_ON, 0x0d, 0x00, 0x16); |
192 | ld9040_dcs_write_seq_static(ctx, MCS_GTCON, 0x09, 0x00, 0x00); | |
193 | ld9040_brightness_set(ctx); | |
194 | ld9040_dcs_write_seq_static(ctx, MIPI_DCS_EXIT_SLEEP_MODE); | |
195 | ld9040_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_ON); | |
196 | } | |
197 | ||
198 | static int ld9040_power_on(struct ld9040 *ctx) | |
199 | { | |
200 | int ret; | |
201 | ||
202 | ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies); | |
203 | if (ret < 0) | |
204 | return ret; | |
205 | ||
206 | msleep(ctx->power_on_delay); | |
207 | gpiod_set_value(ctx->reset_gpio, 0); | |
208 | msleep(ctx->reset_delay); | |
209 | gpiod_set_value(ctx->reset_gpio, 1); | |
210 | msleep(ctx->reset_delay); | |
211 | ||
212 | return 0; | |
213 | } | |
214 | ||
215 | static int ld9040_power_off(struct ld9040 *ctx) | |
216 | { | |
217 | return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); | |
218 | } | |
219 | ||
220 | static int ld9040_disable(struct drm_panel *panel) | |
81410282 AK |
221 | { |
222 | return 0; | |
223 | } | |
224 | ||
225 | static int ld9040_unprepare(struct drm_panel *panel) | |
ff219937 AH |
226 | { |
227 | struct ld9040 *ctx = panel_to_ld9040(panel); | |
228 | ||
229 | msleep(120); | |
230 | ld9040_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_OFF); | |
231 | ld9040_dcs_write_seq_static(ctx, MIPI_DCS_ENTER_SLEEP_MODE); | |
232 | msleep(40); | |
233 | ||
234 | ld9040_clear_error(ctx); | |
235 | ||
236 | return ld9040_power_off(ctx); | |
237 | } | |
238 | ||
099b3e86 | 239 | static int ld9040_prepare(struct drm_panel *panel) |
ff219937 AH |
240 | { |
241 | struct ld9040 *ctx = panel_to_ld9040(panel); | |
242 | int ret; | |
243 | ||
244 | ret = ld9040_power_on(ctx); | |
245 | if (ret < 0) | |
246 | return ret; | |
247 | ||
248 | ld9040_init(ctx); | |
249 | ||
250 | ret = ld9040_clear_error(ctx); | |
251 | ||
252 | if (ret < 0) | |
81410282 | 253 | ld9040_unprepare(panel); |
ff219937 AH |
254 | |
255 | return ret; | |
256 | } | |
257 | ||
81410282 AK |
258 | static int ld9040_enable(struct drm_panel *panel) |
259 | { | |
260 | return 0; | |
261 | } | |
262 | ||
ff219937 AH |
263 | static int ld9040_get_modes(struct drm_panel *panel) |
264 | { | |
265 | struct drm_connector *connector = panel->connector; | |
266 | struct ld9040 *ctx = panel_to_ld9040(panel); | |
267 | struct drm_display_mode *mode; | |
268 | ||
269 | mode = drm_mode_create(connector->dev); | |
270 | if (!mode) { | |
271 | DRM_ERROR("failed to create a new display mode\n"); | |
272 | return 0; | |
273 | } | |
274 | ||
275 | drm_display_mode_from_videomode(&ctx->vm, mode); | |
276 | mode->width_mm = ctx->width_mm; | |
277 | mode->height_mm = ctx->height_mm; | |
278 | connector->display_info.width_mm = mode->width_mm; | |
279 | connector->display_info.height_mm = mode->height_mm; | |
280 | ||
281 | mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; | |
282 | drm_mode_probed_add(connector, mode); | |
283 | ||
284 | return 1; | |
285 | } | |
286 | ||
287 | static const struct drm_panel_funcs ld9040_drm_funcs = { | |
288 | .disable = ld9040_disable, | |
099b3e86 AK |
289 | .unprepare = ld9040_unprepare, |
290 | .prepare = ld9040_prepare, | |
ff219937 AH |
291 | .enable = ld9040_enable, |
292 | .get_modes = ld9040_get_modes, | |
293 | }; | |
294 | ||
295 | static int ld9040_parse_dt(struct ld9040 *ctx) | |
296 | { | |
297 | struct device *dev = ctx->dev; | |
298 | struct device_node *np = dev->of_node; | |
299 | int ret; | |
300 | ||
301 | ret = of_get_videomode(np, &ctx->vm, 0); | |
302 | if (ret < 0) | |
303 | return ret; | |
304 | ||
305 | of_property_read_u32(np, "power-on-delay", &ctx->power_on_delay); | |
306 | of_property_read_u32(np, "reset-delay", &ctx->reset_delay); | |
307 | of_property_read_u32(np, "panel-width-mm", &ctx->width_mm); | |
308 | of_property_read_u32(np, "panel-height-mm", &ctx->height_mm); | |
309 | ||
310 | return 0; | |
311 | } | |
312 | ||
313 | static int ld9040_probe(struct spi_device *spi) | |
314 | { | |
315 | struct device *dev = &spi->dev; | |
316 | struct ld9040 *ctx; | |
317 | int ret; | |
318 | ||
319 | ctx = devm_kzalloc(dev, sizeof(struct ld9040), GFP_KERNEL); | |
320 | if (!ctx) | |
321 | return -ENOMEM; | |
322 | ||
323 | spi_set_drvdata(spi, ctx); | |
324 | ||
325 | ctx->dev = dev; | |
326 | ctx->brightness = ARRAY_SIZE(ld9040_gammas) - 1; | |
327 | ||
328 | ret = ld9040_parse_dt(ctx); | |
329 | if (ret < 0) | |
330 | return ret; | |
331 | ||
332 | ctx->supplies[0].supply = "vdd3"; | |
333 | ctx->supplies[1].supply = "vci"; | |
334 | ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), | |
335 | ctx->supplies); | |
336 | if (ret < 0) | |
337 | return ret; | |
338 | ||
8a8cc83c | 339 | ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); |
ff219937 AH |
340 | if (IS_ERR(ctx->reset_gpio)) { |
341 | dev_err(dev, "cannot get reset-gpios %ld\n", | |
342 | PTR_ERR(ctx->reset_gpio)); | |
343 | return PTR_ERR(ctx->reset_gpio); | |
344 | } | |
ff219937 AH |
345 | |
346 | spi->bits_per_word = 9; | |
347 | ret = spi_setup(spi); | |
348 | if (ret < 0) { | |
349 | dev_err(dev, "spi setup failed.\n"); | |
350 | return ret; | |
351 | } | |
352 | ||
353 | drm_panel_init(&ctx->panel); | |
354 | ctx->panel.dev = dev; | |
355 | ctx->panel.funcs = &ld9040_drm_funcs; | |
356 | ||
357 | return drm_panel_add(&ctx->panel); | |
358 | } | |
359 | ||
360 | static int ld9040_remove(struct spi_device *spi) | |
361 | { | |
362 | struct ld9040 *ctx = spi_get_drvdata(spi); | |
363 | ||
364 | ld9040_power_off(ctx); | |
365 | drm_panel_remove(&ctx->panel); | |
366 | ||
367 | return 0; | |
368 | } | |
369 | ||
370 | static struct of_device_id ld9040_of_match[] = { | |
371 | { .compatible = "samsung,ld9040" }, | |
372 | { } | |
373 | }; | |
374 | MODULE_DEVICE_TABLE(of, ld9040_of_match); | |
375 | ||
376 | static struct spi_driver ld9040_driver = { | |
377 | .probe = ld9040_probe, | |
378 | .remove = ld9040_remove, | |
379 | .driver = { | |
380 | .name = "ld9040", | |
381 | .owner = THIS_MODULE, | |
382 | .of_match_table = ld9040_of_match, | |
383 | }, | |
384 | }; | |
385 | module_spi_driver(ld9040_driver); | |
386 | ||
387 | MODULE_AUTHOR("Andrzej Hajda <a.hajda@samsung.com>"); | |
388 | MODULE_DESCRIPTION("ld9040 LCD Driver"); | |
389 | MODULE_LICENSE("GPL v2"); |