Linux Audio

Check our new training course

Loading...
Note: File does not exist in v5.4.
  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
 19struct 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
 27static const struct regulator_bulk_data s6e3ha8_supplies[] = {
 28	{ .supply = "vdd3" },
 29	{ .supply = "vci" },
 30	{ .supply = "vddr" },
 31};
 32
 33static inline
 34struct 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
 54static 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
 64static 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
148static 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
161static 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
181static 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;
210err:
211	regulator_bulk_disable(ARRAY_SIZE(s6e3ha8_supplies), priv->supplies);
212	return ret;
213}
214
215static 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
222static 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
236static 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
242static 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
250static 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
312static 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
324static const struct of_device_id s6e3ha8_amb577px01_wqhd_of_match[] = {
325	{ .compatible = "samsung,s6e3ha8" },
326	{ /* sentinel */ }
327};
328MODULE_DEVICE_TABLE(of, s6e3ha8_amb577px01_wqhd_of_match);
329
330static 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};
338module_mipi_dsi_driver(s6e3ha8_amb577px01_wqhd_driver);
339
340MODULE_AUTHOR("Dzmitry Sankouski <dsankouski@gmail.com>");
341MODULE_DESCRIPTION("DRM driver for S6E3HA8 panel");
342MODULE_LICENSE("GPL");