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 };
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(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 
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 	return drm_panel_add(&ctx->panel);
265 }
266 
267 static int lg4573_remove(struct spi_device *spi)
268 {
269 	struct lg4573 *ctx = spi_get_drvdata(spi);
270 
271 	lg4573_display_off(ctx);
272 	drm_panel_remove(&ctx->panel);
273 
274 	return 0;
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