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