xref: /linux/drivers/gpu/drm/panel/panel-samsung-s6e3ha8.c (revision aa628ebb069e37ec5297361a952f7b7b9d980585)
1 // SPDX-License-Identifier: GPL-2.0-only
2 //
3 // Generated with linux-mdss-dsi-panel-driver-generator from vendor device tree:
4 //	Copyright (c) 2013, The Linux Foundation. All rights reserved.
5 // Copyright (c) 2024 Dzmitry Sankouski <dsankouski@gmail.com>
6 
7 #include <linux/delay.h>
8 #include <linux/gpio/consumer.h>
9 #include <linux/module.h>
10 #include <linux/of.h>
11 #include <linux/regulator/consumer.h>
12 
13 #include <drm/display/drm_dsc.h>
14 #include <drm/display/drm_dsc_helper.h>
15 #include <drm/drm_mipi_dsi.h>
16 #include <drm/drm_probe_helper.h>
17 #include <drm/drm_panel.h>
18 
19 struct s6e3ha8 {
20 	struct drm_panel panel;
21 	struct mipi_dsi_device *dsi;
22 	struct drm_dsc_config dsc;
23 	struct gpio_desc *reset_gpio;
24 	struct regulator_bulk_data *supplies;
25 };
26 
27 const struct regulator_bulk_data s6e3ha8_supplies[] = {
28 	{ .supply = "vdd3" },
29 	{ .supply = "vci" },
30 	{ .supply = "vddr" },
31 };
32 
33 static inline
34 struct s6e3ha8 *to_s6e3ha8_amb577px01_wqhd(struct drm_panel *panel)
35 {
36 	return container_of(panel, struct s6e3ha8, panel);
37 }
38 
39 #define s6e3ha8_test_key_on_lvl2(ctx) \
40 	mipi_dsi_dcs_write_seq_multi(ctx, 0xf0, 0x5a, 0x5a)
41 #define s6e3ha8_test_key_off_lvl2(ctx) \
42 	mipi_dsi_dcs_write_seq_multi(ctx, 0xf0, 0xa5, 0xa5)
43 #define s6e3ha8_test_key_on_lvl3(ctx) \
44 	mipi_dsi_dcs_write_seq_multi(ctx, 0xfc, 0x5a, 0x5a)
45 #define s6e3ha8_test_key_off_lvl3(ctx) \
46 	mipi_dsi_dcs_write_seq_multi(ctx, 0xfc, 0xa5, 0xa5)
47 #define s6e3ha8_test_key_on_lvl1(ctx) \
48 	mipi_dsi_dcs_write_seq_multi(ctx, 0x9f, 0xa5, 0xa5)
49 #define s6e3ha8_test_key_off_lvl1(ctx) \
50 	mipi_dsi_dcs_write_seq_multi(ctx, 0x9f, 0x5a, 0x5a)
51 #define s6e3ha8_afc_off(ctx) \
52 	mipi_dsi_dcs_write_seq_multi(ctx, 0xe2, 0x00, 0x00)
53 
54 static void s6e3ha8_amb577px01_wqhd_reset(struct s6e3ha8 *priv)
55 {
56 	gpiod_set_value_cansleep(priv->reset_gpio, 1);
57 	usleep_range(5000, 6000);
58 	gpiod_set_value_cansleep(priv->reset_gpio, 0);
59 	usleep_range(5000, 6000);
60 	gpiod_set_value_cansleep(priv->reset_gpio, 1);
61 	usleep_range(5000, 6000);
62 }
63 
64 static int s6e3ha8_amb577px01_wqhd_on(struct s6e3ha8 *priv)
65 {
66 	struct mipi_dsi_device *dsi = priv->dsi;
67 	struct mipi_dsi_multi_context ctx = { .dsi = dsi };
68 
69 	dsi->mode_flags |= MIPI_DSI_MODE_LPM;
70 
71 	s6e3ha8_test_key_on_lvl1(&ctx);
72 
73 	s6e3ha8_test_key_on_lvl2(&ctx);
74 	mipi_dsi_compression_mode_multi(&ctx, true);
75 	s6e3ha8_test_key_off_lvl2(&ctx);
76 
77 	mipi_dsi_dcs_exit_sleep_mode_multi(&ctx);
78 	usleep_range(5000, 6000);
79 
80 	s6e3ha8_test_key_on_lvl2(&ctx);
81 	mipi_dsi_dcs_write_seq_multi(&ctx, 0xf2, 0x13);
82 	s6e3ha8_test_key_off_lvl2(&ctx);
83 	usleep_range(10000, 11000);
84 
85 	s6e3ha8_test_key_on_lvl2(&ctx);
86 	mipi_dsi_dcs_write_seq_multi(&ctx, 0xf2, 0x13);
87 	s6e3ha8_test_key_off_lvl2(&ctx);
88 
89 	/* OMOK setting 1 (Initial setting) - Scaler Latch Setting Guide */
90 	s6e3ha8_test_key_on_lvl2(&ctx);
91 	mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x07);
92 	/* latch setting 1 : Scaler on/off & address setting & PPS setting -> Image update latch */
93 	mipi_dsi_dcs_write_seq_multi(&ctx, 0xf2, 0x3c, 0x10);
94 	mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x0b);
95 	/* latch setting 2 : Ratio change mode -> Image update latch */
96 	mipi_dsi_dcs_write_seq_multi(&ctx, 0xf2, 0x30);
97 	/* OMOK setting 2 - Seamless setting guide : WQHD */
98 	mipi_dsi_dcs_write_seq_multi(&ctx, 0x2a, 0x00, 0x00, 0x05, 0x9f); /* CASET */
99 	mipi_dsi_dcs_write_seq_multi(&ctx, 0x2b, 0x00, 0x00, 0x0b, 0x8f); /* PASET */
100 	mipi_dsi_dcs_write_seq_multi(&ctx, 0xba, 0x01); /* scaler setup : scaler off */
101 	s6e3ha8_test_key_off_lvl2(&ctx);
102 
103 	mipi_dsi_dcs_write_seq_multi(&ctx, 0x35, 0x00); /* TE Vsync ON */
104 
105 	s6e3ha8_test_key_on_lvl2(&ctx);
106 	mipi_dsi_dcs_write_seq_multi(&ctx, 0xed, 0x4c); /* ERR_FG */
107 	s6e3ha8_test_key_off_lvl2(&ctx);
108 
109 	s6e3ha8_test_key_on_lvl3(&ctx);
110 	/* FFC Setting 897.6Mbps */
111 	mipi_dsi_dcs_write_seq_multi(&ctx, 0xc5, 0x0d, 0x10, 0xb4, 0x3e, 0x01);
112 	s6e3ha8_test_key_off_lvl3(&ctx);
113 
114 	s6e3ha8_test_key_on_lvl2(&ctx);
115 	mipi_dsi_dcs_write_seq_multi(&ctx, 0xb9,
116 				   0x00, 0xb0, 0x81, 0x09, 0x00, 0x00, 0x00,
117 				   0x11, 0x03); /* TSP HSYNC Setting */
118 	s6e3ha8_test_key_off_lvl2(&ctx);
119 
120 	s6e3ha8_test_key_on_lvl2(&ctx);
121 	mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x03);
122 	mipi_dsi_dcs_write_seq_multi(&ctx, 0xf6, 0x43);
123 	s6e3ha8_test_key_off_lvl2(&ctx);
124 
125 	s6e3ha8_test_key_on_lvl2(&ctx);
126 	/* Brightness condition set */
127 	mipi_dsi_dcs_write_seq_multi(&ctx, 0xca,
128 				   0x07, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80,
129 				   0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
130 				   0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
131 				   0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
132 				   0x80, 0x80, 0x80, 0x00, 0x00, 0x00);
133 	mipi_dsi_dcs_write_seq_multi(&ctx, 0xb1, 0x00, 0x0c); /* AID Set : 0% */
134 	mipi_dsi_dcs_write_seq_multi(&ctx, 0xb5,
135 				   0x19, 0xdc, 0x16, 0x01, 0x34, 0x67, 0x9a,
136 				   0xcd, 0x01, 0x22, 0x33, 0x44, 0x00, 0x00,
137 				   0x05, 0x55, 0xcc, 0x0c, 0x01, 0x11, 0x11,
138 				   0x10); /* MPS/ELVSS Setting */
139 	mipi_dsi_dcs_write_seq_multi(&ctx, 0xf4, 0xeb, 0x28); /* VINT */
140 	mipi_dsi_dcs_write_seq_multi(&ctx, 0xf7, 0x03); /* Gamma, LTPS(AID) update */
141 	s6e3ha8_test_key_off_lvl2(&ctx);
142 
143 	s6e3ha8_test_key_off_lvl1(&ctx);
144 
145 	return ctx.accum_err;
146 }
147 
148 static int s6e3ha8_enable(struct drm_panel *panel)
149 {
150 	struct s6e3ha8 *priv = to_s6e3ha8_amb577px01_wqhd(panel);
151 	struct mipi_dsi_device *dsi = priv->dsi;
152 	struct mipi_dsi_multi_context ctx = { .dsi = dsi };
153 
154 	s6e3ha8_test_key_on_lvl1(&ctx);
155 	mipi_dsi_dcs_set_display_on_multi(&ctx);
156 	s6e3ha8_test_key_off_lvl1(&ctx);
157 
158 	return ctx.accum_err;
159 }
160 
161 static int s6e3ha8_disable(struct drm_panel *panel)
162 {
163 	struct s6e3ha8 *priv = to_s6e3ha8_amb577px01_wqhd(panel);
164 	struct mipi_dsi_device *dsi = priv->dsi;
165 	struct mipi_dsi_multi_context ctx = { .dsi = dsi };
166 
167 	s6e3ha8_test_key_on_lvl1(&ctx);
168 	mipi_dsi_dcs_set_display_off_multi(&ctx);
169 	s6e3ha8_test_key_off_lvl1(&ctx);
170 	mipi_dsi_msleep(&ctx, 20);
171 
172 	s6e3ha8_test_key_on_lvl2(&ctx);
173 	s6e3ha8_afc_off(&ctx);
174 	s6e3ha8_test_key_off_lvl2(&ctx);
175 
176 	mipi_dsi_msleep(&ctx, 160);
177 
178 	return ctx.accum_err;
179 }
180 
181 static int s6e3ha8_amb577px01_wqhd_prepare(struct drm_panel *panel)
182 {
183 	struct s6e3ha8 *priv = to_s6e3ha8_amb577px01_wqhd(panel);
184 	struct mipi_dsi_device *dsi = priv->dsi;
185 	struct mipi_dsi_multi_context ctx = { .dsi = dsi };
186 	struct drm_dsc_picture_parameter_set pps;
187 	int ret;
188 
189 	ret = regulator_bulk_enable(ARRAY_SIZE(s6e3ha8_supplies), priv->supplies);
190 	if (ret < 0)
191 		return ret;
192 	mipi_dsi_msleep(&ctx, 120);
193 	s6e3ha8_amb577px01_wqhd_reset(priv);
194 
195 	ret = s6e3ha8_amb577px01_wqhd_on(priv);
196 	if (ret < 0) {
197 		gpiod_set_value_cansleep(priv->reset_gpio, 1);
198 		goto err;
199 	}
200 
201 	drm_dsc_pps_payload_pack(&pps, &priv->dsc);
202 
203 	s6e3ha8_test_key_on_lvl1(&ctx);
204 	mipi_dsi_picture_parameter_set_multi(&ctx, &pps);
205 	s6e3ha8_test_key_off_lvl1(&ctx);
206 
207 	mipi_dsi_msleep(&ctx, 28);
208 
209 	return ctx.accum_err;
210 err:
211 	regulator_bulk_disable(ARRAY_SIZE(s6e3ha8_supplies), priv->supplies);
212 	return ret;
213 }
214 
215 static int s6e3ha8_amb577px01_wqhd_unprepare(struct drm_panel *panel)
216 {
217 	struct s6e3ha8 *priv = to_s6e3ha8_amb577px01_wqhd(panel);
218 
219 	return regulator_bulk_disable(ARRAY_SIZE(s6e3ha8_supplies), priv->supplies);
220 }
221 
222 static const struct drm_display_mode s6e3ha8_amb577px01_wqhd_mode = {
223 	.clock = (1440 + 116 + 44 + 120) * (2960 + 120 + 80 + 124) * 60 / 1000,
224 	.hdisplay = 1440,
225 	.hsync_start = 1440 + 116,
226 	.hsync_end = 1440 + 116 + 44,
227 	.htotal = 1440 + 116 + 44 + 120,
228 	.vdisplay = 2960,
229 	.vsync_start = 2960 + 120,
230 	.vsync_end = 2960 + 120 + 80,
231 	.vtotal = 2960 + 120 + 80 + 124,
232 	.width_mm = 64,
233 	.height_mm = 132,
234 };
235 
236 static int s6e3ha8_amb577px01_wqhd_get_modes(struct drm_panel *panel,
237 					     struct drm_connector *connector)
238 {
239 	return drm_connector_helper_get_modes_fixed(connector, &s6e3ha8_amb577px01_wqhd_mode);
240 }
241 
242 static const struct drm_panel_funcs s6e3ha8_amb577px01_wqhd_panel_funcs = {
243 	.prepare = s6e3ha8_amb577px01_wqhd_prepare,
244 	.unprepare = s6e3ha8_amb577px01_wqhd_unprepare,
245 	.get_modes = s6e3ha8_amb577px01_wqhd_get_modes,
246 	.enable = s6e3ha8_enable,
247 	.disable = s6e3ha8_disable,
248 };
249 
250 static int s6e3ha8_amb577px01_wqhd_probe(struct mipi_dsi_device *dsi)
251 {
252 	struct device *dev = &dsi->dev;
253 	struct s6e3ha8 *priv;
254 	int ret;
255 
256 	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
257 	if (!priv)
258 		return -ENOMEM;
259 
260 	ret = devm_regulator_bulk_get_const(dev, ARRAY_SIZE(s6e3ha8_supplies),
261 				      s6e3ha8_supplies,
262 				      &priv->supplies);
263 	if (ret < 0) {
264 		dev_err(dev, "failed to get regulators: %d\n", ret);
265 		return ret;
266 	}
267 
268 	priv->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
269 	if (IS_ERR(priv->reset_gpio))
270 		return dev_err_probe(dev, PTR_ERR(priv->reset_gpio),
271 				     "Failed to get reset-gpios\n");
272 
273 	priv->dsi = dsi;
274 	mipi_dsi_set_drvdata(dsi, priv);
275 
276 	dsi->lanes = 4;
277 	dsi->format = MIPI_DSI_FMT_RGB888;
278 	dsi->mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS |
279 		MIPI_DSI_MODE_VIDEO_NO_HFP | MIPI_DSI_MODE_VIDEO_NO_HBP |
280 		MIPI_DSI_MODE_VIDEO_NO_HSA | MIPI_DSI_MODE_NO_EOT_PACKET;
281 
282 	drm_panel_init(&priv->panel, dev, &s6e3ha8_amb577px01_wqhd_panel_funcs,
283 		       DRM_MODE_CONNECTOR_DSI);
284 	priv->panel.prepare_prev_first = true;
285 
286 	drm_panel_add(&priv->panel);
287 
288 	/* This panel only supports DSC; unconditionally enable it */
289 	dsi->dsc = &priv->dsc;
290 
291 	priv->dsc.dsc_version_major = 1;
292 	priv->dsc.dsc_version_minor = 1;
293 
294 	priv->dsc.slice_height = 40;
295 	priv->dsc.slice_width = 720;
296 	WARN_ON(1440 % priv->dsc.slice_width);
297 	priv->dsc.slice_count = 1440 / priv->dsc.slice_width;
298 	priv->dsc.bits_per_component = 8;
299 	priv->dsc.bits_per_pixel = 8 << 4; /* 4 fractional bits */
300 	priv->dsc.block_pred_enable = true;
301 
302 	ret = mipi_dsi_attach(dsi);
303 	if (ret < 0) {
304 		dev_err(dev, "Failed to attach to DSI host: %d\n", ret);
305 		drm_panel_remove(&priv->panel);
306 		return ret;
307 	}
308 
309 	return 0;
310 }
311 
312 static void s6e3ha8_amb577px01_wqhd_remove(struct mipi_dsi_device *dsi)
313 {
314 	struct s6e3ha8 *priv = mipi_dsi_get_drvdata(dsi);
315 	int ret;
316 
317 	ret = mipi_dsi_detach(dsi);
318 	if (ret < 0)
319 		dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret);
320 
321 	drm_panel_remove(&priv->panel);
322 }
323 
324 static const struct of_device_id s6e3ha8_amb577px01_wqhd_of_match[] = {
325 	{ .compatible = "samsung,s6e3ha8" },
326 	{ /* sentinel */ }
327 };
328 MODULE_DEVICE_TABLE(of, s6e3ha8_amb577px01_wqhd_of_match);
329 
330 static struct mipi_dsi_driver s6e3ha8_amb577px01_wqhd_driver = {
331 	.probe = s6e3ha8_amb577px01_wqhd_probe,
332 	.remove = s6e3ha8_amb577px01_wqhd_remove,
333 	.driver = {
334 		.name = "panel-s6e3ha8",
335 		.of_match_table = s6e3ha8_amb577px01_wqhd_of_match,
336 	},
337 };
338 module_mipi_dsi_driver(s6e3ha8_amb577px01_wqhd_driver);
339 
340 MODULE_AUTHOR("Dzmitry Sankouski <dsankouski@gmail.com>");
341 MODULE_DESCRIPTION("DRM driver for S6E3HA8 panel");
342 MODULE_LICENSE("GPL");
343