xref: /linux/drivers/gpu/drm/panel/panel-lg-sw43408.c (revision 29e208a08a8ebb0f214e815eee0a7beff778864f)
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Copyright (C) 2019-2024 Linaro Ltd
4  * Author: Sumit Semwal <sumit.semwal@linaro.org>
5  *	 Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
6  */
7 
8 #include <linux/backlight.h>
9 #include <linux/delay.h>
10 #include <linux/gpio/consumer.h>
11 #include <linux/module.h>
12 #include <linux/of.h>
13 #include <linux/regulator/consumer.h>
14 
15 #include <video/mipi_display.h>
16 
17 #include <drm/drm_mipi_dsi.h>
18 #include <drm/drm_panel.h>
19 #include <drm/drm_probe_helper.h>
20 #include <drm/display/drm_dsc.h>
21 #include <drm/display/drm_dsc_helper.h>
22 
23 static const struct regulator_bulk_data sw43408_supplies[] = {
24 	{ .supply = "vddi", /* 1.8 V */
25 	  .init_load_uA = 62000 },
26 	{ .supply = "vpnl", /* 3.0 V */
27 	  .init_load_uA = 857000 },
28 };
29 
30 struct sw43408_panel {
31 	struct drm_panel base;
32 	struct mipi_dsi_device *link;
33 
34 	struct regulator_bulk_data *supplies;
35 
36 	struct gpio_desc *reset_gpio;
37 
38 	struct drm_dsc_config dsc;
39 };
40 
41 static inline struct sw43408_panel *to_panel_info(struct drm_panel *panel)
42 {
43 	return container_of(panel, struct sw43408_panel, base);
44 }
45 
46 static int sw43408_unprepare(struct drm_panel *panel)
47 {
48 	struct sw43408_panel *sw43408 = to_panel_info(panel);
49 	struct mipi_dsi_multi_context ctx = { .dsi = sw43408->link };
50 	int ret;
51 
52 	mipi_dsi_dcs_set_display_off_multi(&ctx);
53 
54 	mipi_dsi_dcs_enter_sleep_mode_multi(&ctx);
55 
56 	mipi_dsi_msleep(&ctx, 100);
57 
58 	gpiod_set_value(sw43408->reset_gpio, 1);
59 
60 	ret = regulator_bulk_disable(ARRAY_SIZE(sw43408_supplies), sw43408->supplies);
61 
62 	return ret ? : ctx.accum_err;
63 }
64 
65 static int sw43408_program(struct drm_panel *panel)
66 {
67 	struct sw43408_panel *sw43408 = to_panel_info(panel);
68 	struct mipi_dsi_multi_context ctx = { .dsi = sw43408->link };
69 	struct drm_dsc_picture_parameter_set pps;
70 
71 	mipi_dsi_dcs_write_seq_multi(&ctx, MIPI_DCS_SET_GAMMA_CURVE, 0x02);
72 
73 	mipi_dsi_dcs_set_tear_on_multi(&ctx, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
74 
75 	mipi_dsi_dcs_write_seq_multi(&ctx, 0x53, 0x0c, 0x30);
76 	mipi_dsi_dcs_write_seq_multi(&ctx, 0x55, 0x00, 0x70, 0xdf, 0x00, 0x70, 0xdf);
77 	mipi_dsi_dcs_write_seq_multi(&ctx, 0xf7, 0x01, 0x49, 0x0c);
78 
79 	mipi_dsi_dcs_exit_sleep_mode_multi(&ctx);
80 
81 	mipi_dsi_msleep(&ctx, 135);
82 
83 	/* COMPRESSION_MODE moved after setting the PPS */
84 
85 	mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0xac);
86 	mipi_dsi_dcs_write_seq_multi(&ctx, 0xe5,
87 			       0x00, 0x3a, 0x00, 0x3a, 0x00, 0x0e, 0x10);
88 	mipi_dsi_dcs_write_seq_multi(&ctx, 0xb5,
89 			       0x75, 0x60, 0x2d, 0x5d, 0x80, 0x00, 0x0a, 0x0b,
90 			       0x00, 0x05, 0x0b, 0x00, 0x80, 0x0d, 0x0e, 0x40,
91 			       0x00, 0x0c, 0x00, 0x16, 0x00, 0xb8, 0x00, 0x80,
92 			       0x0d, 0x0e, 0x40, 0x00, 0x0c, 0x00, 0x16, 0x00,
93 			       0xb8, 0x00, 0x81, 0x00, 0x03, 0x03, 0x03, 0x01,
94 			       0x01);
95 	mipi_dsi_msleep(&ctx, 85);
96 	mipi_dsi_dcs_write_seq_multi(&ctx, 0xcd,
97 			       0x00, 0x00, 0x00, 0x19, 0x19, 0x19, 0x19, 0x19,
98 			       0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19,
99 			       0x16, 0x16);
100 	mipi_dsi_dcs_write_seq_multi(&ctx, 0xcb, 0x80, 0x5c, 0x07, 0x03, 0x28);
101 	mipi_dsi_dcs_write_seq_multi(&ctx, 0xc0, 0x02, 0x02, 0x0f);
102 	mipi_dsi_dcs_write_seq_multi(&ctx, 0x55, 0x04, 0x61, 0xdb, 0x04, 0x70, 0xdb);
103 	mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0xca);
104 
105 	mipi_dsi_dcs_set_display_on_multi(&ctx);
106 
107 	mipi_dsi_msleep(&ctx, 50);
108 
109 	sw43408->link->mode_flags &= ~MIPI_DSI_MODE_LPM;
110 
111 	drm_dsc_pps_payload_pack(&pps, sw43408->link->dsc);
112 
113 	mipi_dsi_picture_parameter_set_multi(&ctx, &pps);
114 
115 	sw43408->link->mode_flags |= MIPI_DSI_MODE_LPM;
116 
117 	/*
118 	 * This panel uses PPS selectors with offset:
119 	 * PPS 1 if pps_identifier is 0
120 	 * PPS 2 if pps_identifier is 1
121 	 */
122 	mipi_dsi_compression_mode_ext_multi(&ctx, true,
123 					    MIPI_DSI_COMPRESSION_DSC, 1);
124 	return ctx.accum_err;
125 }
126 
127 static void sw43408_reset(struct sw43408_panel *ctx)
128 {
129 	gpiod_set_value(ctx->reset_gpio, 0);
130 	usleep_range(9000, 10000);
131 	gpiod_set_value(ctx->reset_gpio, 1);
132 	usleep_range(1000, 2000);
133 	gpiod_set_value(ctx->reset_gpio, 0);
134 	usleep_range(9000, 10000);
135 }
136 
137 static int sw43408_prepare(struct drm_panel *panel)
138 {
139 	struct sw43408_panel *ctx = to_panel_info(panel);
140 	int ret;
141 
142 	ret = regulator_bulk_enable(ARRAY_SIZE(sw43408_supplies), ctx->supplies);
143 	if (ret < 0)
144 		return ret;
145 
146 	usleep_range(5000, 6000);
147 
148 	sw43408_reset(ctx);
149 
150 	ret = sw43408_program(panel);
151 	if (ret)
152 		goto poweroff;
153 
154 	return 0;
155 
156 poweroff:
157 	gpiod_set_value(ctx->reset_gpio, 1);
158 	regulator_bulk_disable(ARRAY_SIZE(sw43408_supplies), ctx->supplies);
159 	return ret;
160 }
161 
162 static const struct drm_display_mode lh546wf1_ed01_mode = {
163 	.clock = (1080 + 20 + 32 + 20) * (2160 + 20 + 4 + 20) * 60 / 1000,
164 
165 	.hdisplay = 1080,
166 	.hsync_start = 1080 + 20,
167 	.hsync_end = 1080 + 20 + 32,
168 	.htotal = 1080 + 20 + 32 + 20,
169 
170 	.vdisplay = 2160,
171 	.vsync_start = 2160 + 20,
172 	.vsync_end = 2160 + 20 + 4,
173 	.vtotal = 2160 + 20 + 4 + 20,
174 
175 	.width_mm = 62,
176 	.height_mm = 124,
177 
178 	.type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
179 };
180 
181 static int sw43408_get_modes(struct drm_panel *panel,
182 			     struct drm_connector *connector)
183 {
184 	return drm_connector_helper_get_modes_fixed(connector, &lh546wf1_ed01_mode);
185 }
186 
187 static int sw43408_backlight_update_status(struct backlight_device *bl)
188 {
189 	struct mipi_dsi_device *dsi = bl_get_data(bl);
190 	u16 brightness = backlight_get_brightness(bl);
191 
192 	return mipi_dsi_dcs_set_display_brightness_large(dsi, brightness);
193 }
194 
195 static const struct backlight_ops sw43408_backlight_ops = {
196 	.update_status = sw43408_backlight_update_status,
197 };
198 
199 static int sw43408_backlight_init(struct sw43408_panel *ctx)
200 {
201 	struct device *dev = &ctx->link->dev;
202 	const struct backlight_properties props = {
203 		.type = BACKLIGHT_PLATFORM,
204 		.brightness = 255,
205 		.max_brightness = 255,
206 	};
207 
208 	ctx->base.backlight = devm_backlight_device_register(dev, dev_name(dev), dev,
209 							     ctx->link,
210 							     &sw43408_backlight_ops,
211 							     &props);
212 
213 	if (IS_ERR(ctx->base.backlight))
214 		return dev_err_probe(dev, PTR_ERR(ctx->base.backlight),
215 				     "Failed to create backlight\n");
216 
217 	return 0;
218 }
219 
220 static const struct drm_panel_funcs sw43408_funcs = {
221 	.unprepare = sw43408_unprepare,
222 	.prepare = sw43408_prepare,
223 	.get_modes = sw43408_get_modes,
224 };
225 
226 static const struct of_device_id sw43408_of_match[] = {
227 	{ .compatible = "lg,sw43408", }, /* legacy */
228 	{ .compatible = "lg,sw43408-lh546wf1-ed01", },
229 	{ /* sentinel */ }
230 };
231 MODULE_DEVICE_TABLE(of, sw43408_of_match);
232 
233 static int sw43408_add(struct sw43408_panel *ctx)
234 {
235 	struct device *dev = &ctx->link->dev;
236 	int ret;
237 
238 	ret = devm_regulator_bulk_get_const(dev,
239 					    ARRAY_SIZE(sw43408_supplies),
240 					    sw43408_supplies,
241 					    &ctx->supplies);
242 	if (ret < 0)
243 		return ret;
244 
245 	ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
246 	if (IS_ERR(ctx->reset_gpio)) {
247 		return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio),
248 				     "Failed to get reset-gpios\n");
249 	}
250 
251 	ret = sw43408_backlight_init(ctx);
252 	if (ret < 0)
253 		return ret;
254 
255 	ctx->base.prepare_prev_first = true;
256 
257 	drm_panel_add(&ctx->base);
258 	return ret;
259 }
260 
261 static int sw43408_probe(struct mipi_dsi_device *dsi)
262 {
263 	struct sw43408_panel *ctx;
264 	int ret;
265 
266 	ctx = devm_drm_panel_alloc(&dsi->dev, __typeof(*ctx), base,
267 				   &sw43408_funcs, DRM_MODE_CONNECTOR_DSI);
268 
269 	if (IS_ERR(ctx))
270 		return PTR_ERR(ctx);
271 
272 	dsi->mode_flags = MIPI_DSI_MODE_LPM;
273 	dsi->format = MIPI_DSI_FMT_RGB888;
274 	dsi->lanes = 4;
275 
276 	ctx->link = dsi;
277 	mipi_dsi_set_drvdata(dsi, ctx);
278 
279 	ret = sw43408_add(ctx);
280 	if (ret < 0)
281 		return ret;
282 
283 	/* The panel works only in the DSC mode. Set DSC params. */
284 	ctx->dsc.dsc_version_major = 0x1;
285 	ctx->dsc.dsc_version_minor = 0x1;
286 
287 	/* slice_count * slice_width == width */
288 	ctx->dsc.slice_height = 16;
289 	ctx->dsc.slice_width = 540;
290 	ctx->dsc.slice_count = 2;
291 	ctx->dsc.bits_per_component = 8;
292 	ctx->dsc.bits_per_pixel = 8 << 4;
293 	ctx->dsc.block_pred_enable = true;
294 
295 	dsi->dsc = &ctx->dsc;
296 
297 	return mipi_dsi_attach(dsi);
298 }
299 
300 static void sw43408_remove(struct mipi_dsi_device *dsi)
301 {
302 	struct sw43408_panel *ctx = mipi_dsi_get_drvdata(dsi);
303 	int ret;
304 
305 	ret = mipi_dsi_detach(dsi);
306 	if (ret < 0)
307 		dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", ret);
308 
309 	drm_panel_remove(&ctx->base);
310 }
311 
312 static struct mipi_dsi_driver sw43408_driver = {
313 	.driver = {
314 		.name = "panel-lg-sw43408",
315 		.of_match_table = sw43408_of_match,
316 	},
317 	.probe = sw43408_probe,
318 	.remove = sw43408_remove,
319 };
320 module_mipi_dsi_driver(sw43408_driver);
321 
322 MODULE_AUTHOR("Sumit Semwal <sumit.semwal@linaro.org>");
323 MODULE_DESCRIPTION("LG SW436408 MIPI-DSI LED panel");
324 MODULE_LICENSE("GPL");
325