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