1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2015 Heiko Schocher <hs@denx.de> |
4 | * |
5 | * from: |
6 | * drivers/gpu/drm/panel/panel-ld9040.c |
7 | * ld9040 AMOLED LCD drm_panel driver. |
8 | * |
9 | * Copyright (c) 2014 Samsung Electronics Co., Ltd |
10 | * Derived from drivers/video/backlight/ld9040.c |
11 | * |
12 | * Andrzej Hajda <a.hajda@samsung.com> |
13 | */ |
14 | |
15 | #include <linux/delay.h> |
16 | #include <linux/gpio/consumer.h> |
17 | #include <linux/module.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 | #include <drm/drm_device.h> |
26 | #include <drm/drm_modes.h> |
27 | #include <drm/drm_panel.h> |
28 | |
29 | struct lg4573 { |
30 | struct drm_panel panel; |
31 | struct spi_device *spi; |
32 | struct videomode vm; |
33 | }; |
34 | |
35 | static inline struct lg4573 *panel_to_lg4573(struct drm_panel *panel) |
36 | { |
37 | return container_of(panel, struct lg4573, panel); |
38 | } |
39 | |
40 | static int lg4573_spi_write_u16(struct lg4573 *ctx, u16 data) |
41 | { |
42 | struct spi_transfer xfer = { |
43 | .len = 2, |
44 | }; |
45 | __be16 temp = cpu_to_be16(data); |
46 | struct spi_message msg; |
47 | |
48 | dev_dbg(ctx->panel.dev, "writing data: %x\n" , data); |
49 | xfer.tx_buf = &temp; |
50 | spi_message_init(m: &msg); |
51 | spi_message_add_tail(t: &xfer, m: &msg); |
52 | |
53 | return spi_sync(spi: ctx->spi, message: &msg); |
54 | } |
55 | |
56 | static int lg4573_spi_write_u16_array(struct lg4573 *ctx, const u16 *buffer, |
57 | unsigned int count) |
58 | { |
59 | unsigned int i; |
60 | int ret; |
61 | |
62 | for (i = 0; i < count; i++) { |
63 | ret = lg4573_spi_write_u16(ctx, data: buffer[i]); |
64 | if (ret) |
65 | return ret; |
66 | } |
67 | |
68 | return 0; |
69 | } |
70 | |
71 | static int lg4573_spi_write_dcs(struct lg4573 *ctx, u8 dcs) |
72 | { |
73 | return lg4573_spi_write_u16(ctx, data: (0x70 << 8 | dcs)); |
74 | } |
75 | |
76 | static int lg4573_display_on(struct lg4573 *ctx) |
77 | { |
78 | int ret; |
79 | |
80 | ret = lg4573_spi_write_dcs(ctx, dcs: MIPI_DCS_EXIT_SLEEP_MODE); |
81 | if (ret) |
82 | return ret; |
83 | |
84 | msleep(msecs: 5); |
85 | |
86 | return lg4573_spi_write_dcs(ctx, dcs: MIPI_DCS_SET_DISPLAY_ON); |
87 | } |
88 | |
89 | static int lg4573_display_off(struct lg4573 *ctx) |
90 | { |
91 | int ret; |
92 | |
93 | ret = lg4573_spi_write_dcs(ctx, dcs: MIPI_DCS_SET_DISPLAY_OFF); |
94 | if (ret) |
95 | return ret; |
96 | |
97 | msleep(msecs: 120); |
98 | |
99 | return lg4573_spi_write_dcs(ctx, dcs: MIPI_DCS_ENTER_SLEEP_MODE); |
100 | } |
101 | |
102 | static int lg4573_display_mode_settings(struct lg4573 *ctx) |
103 | { |
104 | static const u16 display_mode_settings[] = { |
105 | 0x703A, 0x7270, 0x70B1, 0x7208, |
106 | 0x723B, 0x720F, 0x70B2, 0x7200, |
107 | 0x72C8, 0x70B3, 0x7200, 0x70B4, |
108 | 0x7200, 0x70B5, 0x7242, 0x7210, |
109 | 0x7210, 0x7200, 0x7220, 0x70B6, |
110 | 0x720B, 0x720F, 0x723C, 0x7213, |
111 | 0x7213, 0x72E8, 0x70B7, 0x7246, |
112 | 0x7206, 0x720C, 0x7200, 0x7200, |
113 | }; |
114 | |
115 | dev_dbg(ctx->panel.dev, "transfer display mode settings\n" ); |
116 | return lg4573_spi_write_u16_array(ctx, buffer: display_mode_settings, |
117 | ARRAY_SIZE(display_mode_settings)); |
118 | } |
119 | |
120 | static int lg4573_power_settings(struct lg4573 *ctx) |
121 | { |
122 | static const u16 power_settings[] = { |
123 | 0x70C0, 0x7201, 0x7211, 0x70C3, |
124 | 0x7207, 0x7203, 0x7204, 0x7204, |
125 | 0x7204, 0x70C4, 0x7212, 0x7224, |
126 | 0x7218, 0x7218, 0x7202, 0x7249, |
127 | 0x70C5, 0x726F, 0x70C6, 0x7241, |
128 | 0x7263, |
129 | }; |
130 | |
131 | dev_dbg(ctx->panel.dev, "transfer power settings\n" ); |
132 | return lg4573_spi_write_u16_array(ctx, buffer: power_settings, |
133 | ARRAY_SIZE(power_settings)); |
134 | } |
135 | |
136 | static int lg4573_gamma_settings(struct lg4573 *ctx) |
137 | { |
138 | static const u16 gamma_settings[] = { |
139 | 0x70D0, 0x7203, 0x7207, 0x7273, |
140 | 0x7235, 0x7200, 0x7201, 0x7220, |
141 | 0x7200, 0x7203, 0x70D1, 0x7203, |
142 | 0x7207, 0x7273, 0x7235, 0x7200, |
143 | 0x7201, 0x7220, 0x7200, 0x7203, |
144 | 0x70D2, 0x7203, 0x7207, 0x7273, |
145 | 0x7235, 0x7200, 0x7201, 0x7220, |
146 | 0x7200, 0x7203, 0x70D3, 0x7203, |
147 | 0x7207, 0x7273, 0x7235, 0x7200, |
148 | 0x7201, 0x7220, 0x7200, 0x7203, |
149 | 0x70D4, 0x7203, 0x7207, 0x7273, |
150 | 0x7235, 0x7200, 0x7201, 0x7220, |
151 | 0x7200, 0x7203, 0x70D5, 0x7203, |
152 | 0x7207, 0x7273, 0x7235, 0x7200, |
153 | 0x7201, 0x7220, 0x7200, 0x7203, |
154 | }; |
155 | |
156 | dev_dbg(ctx->panel.dev, "transfer gamma settings\n" ); |
157 | return lg4573_spi_write_u16_array(ctx, buffer: gamma_settings, |
158 | ARRAY_SIZE(gamma_settings)); |
159 | } |
160 | |
161 | static int lg4573_init(struct lg4573 *ctx) |
162 | { |
163 | int ret; |
164 | |
165 | dev_dbg(ctx->panel.dev, "initializing LCD\n" ); |
166 | |
167 | ret = lg4573_display_mode_settings(ctx); |
168 | if (ret) |
169 | return ret; |
170 | |
171 | ret = lg4573_power_settings(ctx); |
172 | if (ret) |
173 | return ret; |
174 | |
175 | return lg4573_gamma_settings(ctx); |
176 | } |
177 | |
178 | static int lg4573_power_on(struct lg4573 *ctx) |
179 | { |
180 | return lg4573_display_on(ctx); |
181 | } |
182 | |
183 | static int lg4573_disable(struct drm_panel *panel) |
184 | { |
185 | struct lg4573 *ctx = panel_to_lg4573(panel); |
186 | |
187 | return lg4573_display_off(ctx); |
188 | } |
189 | |
190 | static int lg4573_enable(struct drm_panel *panel) |
191 | { |
192 | struct lg4573 *ctx = panel_to_lg4573(panel); |
193 | |
194 | lg4573_init(ctx); |
195 | |
196 | return lg4573_power_on(ctx); |
197 | } |
198 | |
199 | static const struct drm_display_mode default_mode = { |
200 | .clock = 28341, |
201 | .hdisplay = 480, |
202 | .hsync_start = 480 + 10, |
203 | .hsync_end = 480 + 10 + 59, |
204 | .htotal = 480 + 10 + 59 + 10, |
205 | .vdisplay = 800, |
206 | .vsync_start = 800 + 15, |
207 | .vsync_end = 800 + 15 + 15, |
208 | .vtotal = 800 + 15 + 15 + 15, |
209 | }; |
210 | |
211 | static int lg4573_get_modes(struct drm_panel *panel, |
212 | struct drm_connector *connector) |
213 | { |
214 | struct drm_display_mode *mode; |
215 | |
216 | mode = drm_mode_duplicate(dev: connector->dev, mode: &default_mode); |
217 | if (!mode) { |
218 | dev_err(panel->dev, "failed to add mode %ux%ux@%u\n" , |
219 | default_mode.hdisplay, default_mode.vdisplay, |
220 | drm_mode_vrefresh(&default_mode)); |
221 | return -ENOMEM; |
222 | } |
223 | |
224 | drm_mode_set_name(mode); |
225 | |
226 | mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; |
227 | drm_mode_probed_add(connector, mode); |
228 | |
229 | connector->display_info.width_mm = 61; |
230 | connector->display_info.height_mm = 103; |
231 | |
232 | return 1; |
233 | } |
234 | |
235 | static const struct drm_panel_funcs lg4573_drm_funcs = { |
236 | .disable = lg4573_disable, |
237 | .enable = lg4573_enable, |
238 | .get_modes = lg4573_get_modes, |
239 | }; |
240 | |
241 | static int lg4573_probe(struct spi_device *spi) |
242 | { |
243 | struct lg4573 *ctx; |
244 | int ret; |
245 | |
246 | ctx = devm_kzalloc(dev: &spi->dev, size: sizeof(*ctx), GFP_KERNEL); |
247 | if (!ctx) |
248 | return -ENOMEM; |
249 | |
250 | ctx->spi = spi; |
251 | |
252 | spi_set_drvdata(spi, data: ctx); |
253 | spi->bits_per_word = 8; |
254 | |
255 | ret = spi_setup(spi); |
256 | if (ret < 0) { |
257 | dev_err(&spi->dev, "SPI setup failed: %d\n" , ret); |
258 | return ret; |
259 | } |
260 | |
261 | drm_panel_init(panel: &ctx->panel, dev: &spi->dev, funcs: &lg4573_drm_funcs, |
262 | DRM_MODE_CONNECTOR_DPI); |
263 | |
264 | drm_panel_add(panel: &ctx->panel); |
265 | |
266 | return 0; |
267 | } |
268 | |
269 | static void lg4573_remove(struct spi_device *spi) |
270 | { |
271 | struct lg4573 *ctx = spi_get_drvdata(spi); |
272 | |
273 | lg4573_display_off(ctx); |
274 | drm_panel_remove(panel: &ctx->panel); |
275 | } |
276 | |
277 | static const struct of_device_id lg4573_of_match[] = { |
278 | { .compatible = "lg,lg4573" }, |
279 | { } |
280 | }; |
281 | MODULE_DEVICE_TABLE(of, lg4573_of_match); |
282 | |
283 | static struct spi_driver lg4573_driver = { |
284 | .probe = lg4573_probe, |
285 | .remove = lg4573_remove, |
286 | .driver = { |
287 | .name = "lg4573" , |
288 | .of_match_table = lg4573_of_match, |
289 | }, |
290 | }; |
291 | module_spi_driver(lg4573_driver); |
292 | |
293 | MODULE_AUTHOR("Heiko Schocher <hs@denx.de>" ); |
294 | MODULE_DESCRIPTION("lg4573 LCD Driver" ); |
295 | MODULE_LICENSE("GPL v2" ); |
296 | |