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
panel_to_lg4573(struct drm_panel * panel)35 static inline struct lg4573 *panel_to_lg4573(struct drm_panel *panel)
36 {
37 return container_of(panel, struct lg4573, panel);
38 }
39
lg4573_spi_write_u16(struct lg4573 * ctx,u16 data)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(&msg);
51 spi_message_add_tail(&xfer, &msg);
52
53 return spi_sync(ctx->spi, &msg);
54 }
55
lg4573_spi_write_u16_array(struct lg4573 * ctx,const u16 * buffer,unsigned int count)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, buffer[i]);
64 if (ret)
65 return ret;
66 }
67
68 return 0;
69 }
70
lg4573_spi_write_dcs(struct lg4573 * ctx,u8 dcs)71 static int lg4573_spi_write_dcs(struct lg4573 *ctx, u8 dcs)
72 {
73 return lg4573_spi_write_u16(ctx, (0x70 << 8 | dcs));
74 }
75
lg4573_display_on(struct lg4573 * ctx)76 static int lg4573_display_on(struct lg4573 *ctx)
77 {
78 int ret;
79
80 ret = lg4573_spi_write_dcs(ctx, MIPI_DCS_EXIT_SLEEP_MODE);
81 if (ret)
82 return ret;
83
84 msleep(5);
85
86 return lg4573_spi_write_dcs(ctx, MIPI_DCS_SET_DISPLAY_ON);
87 }
88
lg4573_display_off(struct lg4573 * ctx)89 static int lg4573_display_off(struct lg4573 *ctx)
90 {
91 int ret;
92
93 ret = lg4573_spi_write_dcs(ctx, MIPI_DCS_SET_DISPLAY_OFF);
94 if (ret)
95 return ret;
96
97 msleep(120);
98
99 return lg4573_spi_write_dcs(ctx, MIPI_DCS_ENTER_SLEEP_MODE);
100 }
101
lg4573_display_mode_settings(struct lg4573 * ctx)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, display_mode_settings,
117 ARRAY_SIZE(display_mode_settings));
118 }
119
lg4573_power_settings(struct lg4573 * ctx)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, power_settings,
133 ARRAY_SIZE(power_settings));
134 }
135
lg4573_gamma_settings(struct lg4573 * ctx)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, gamma_settings,
158 ARRAY_SIZE(gamma_settings));
159 }
160
lg4573_init(struct lg4573 * ctx)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
lg4573_power_on(struct lg4573 * ctx)178 static int lg4573_power_on(struct lg4573 *ctx)
179 {
180 return lg4573_display_on(ctx);
181 }
182
lg4573_disable(struct drm_panel * panel)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
lg4573_enable(struct drm_panel * panel)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
lg4573_get_modes(struct drm_panel * panel,struct drm_connector * connector)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(connector->dev, &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
lg4573_probe(struct spi_device * spi)241 static int lg4573_probe(struct spi_device *spi)
242 {
243 struct lg4573 *ctx;
244 int ret;
245
246 ctx = devm_kzalloc(&spi->dev, sizeof(*ctx), GFP_KERNEL);
247 if (!ctx)
248 return -ENOMEM;
249
250 ctx->spi = spi;
251
252 spi_set_drvdata(spi, 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(&ctx->panel, &spi->dev, &lg4573_drm_funcs,
262 DRM_MODE_CONNECTOR_DPI);
263
264 drm_panel_add(&ctx->panel);
265
266 return 0;
267 }
268
lg4573_remove(struct spi_device * spi)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(&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