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(&msg);
51 	spi_message_add_tail(&xfer, &msg);
52 
53 	return spi_sync(ctx->spi, &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, 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, (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, 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 
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 
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 
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 
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 
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 	.vrefresh = 60,
210 };
211 
212 static int lg4573_get_modes(struct drm_panel *panel,
213 			    struct drm_connector *connector)
214 {
215 	struct drm_display_mode *mode;
216 
217 	mode = drm_mode_duplicate(connector->dev, &default_mode);
218 	if (!mode) {
219 		dev_err(panel->dev, "failed to add mode %ux%ux@%u\n",
220 			default_mode.hdisplay, default_mode.vdisplay,
221 			default_mode.vrefresh);
222 		return -ENOMEM;
223 	}
224 
225 	drm_mode_set_name(mode);
226 
227 	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
228 	drm_mode_probed_add(connector, mode);
229 
230 	connector->display_info.width_mm = 61;
231 	connector->display_info.height_mm = 103;
232 
233 	return 1;
234 }
235 
236 static const struct drm_panel_funcs lg4573_drm_funcs = {
237 	.disable = lg4573_disable,
238 	.enable = lg4573_enable,
239 	.get_modes = lg4573_get_modes,
240 };
241 
242 static int lg4573_probe(struct spi_device *spi)
243 {
244 	struct lg4573 *ctx;
245 	int ret;
246 
247 	ctx = devm_kzalloc(&spi->dev, sizeof(*ctx), GFP_KERNEL);
248 	if (!ctx)
249 		return -ENOMEM;
250 
251 	ctx->spi = spi;
252 
253 	spi_set_drvdata(spi, ctx);
254 	spi->bits_per_word = 8;
255 
256 	ret = spi_setup(spi);
257 	if (ret < 0) {
258 		dev_err(&spi->dev, "SPI setup failed: %d\n", ret);
259 		return ret;
260 	}
261 
262 	drm_panel_init(&ctx->panel, &spi->dev, &lg4573_drm_funcs,
263 		       DRM_MODE_CONNECTOR_DPI);
264 
265 	return drm_panel_add(&ctx->panel);
266 }
267 
268 static int lg4573_remove(struct spi_device *spi)
269 {
270 	struct lg4573 *ctx = spi_get_drvdata(spi);
271 
272 	lg4573_display_off(ctx);
273 	drm_panel_remove(&ctx->panel);
274 
275 	return 0;
276 }
277 
278 static const struct of_device_id lg4573_of_match[] = {
279 	{ .compatible = "lg,lg4573" },
280 	{ }
281 };
282 MODULE_DEVICE_TABLE(of, lg4573_of_match);
283 
284 static struct spi_driver lg4573_driver = {
285 	.probe = lg4573_probe,
286 	.remove = lg4573_remove,
287 	.driver = {
288 		.name = "lg4573",
289 		.of_match_table = lg4573_of_match,
290 	},
291 };
292 module_spi_driver(lg4573_driver);
293 
294 MODULE_AUTHOR("Heiko Schocher <hs@denx.de>");
295 MODULE_DESCRIPTION("lg4573 LCD Driver");
296 MODULE_LICENSE("GPL v2");
297