xref: /linux/drivers/gpu/drm/panel/panel-samsung-ams581vf01.c (revision 7f71507851fc7764b36a3221839607d3a45c2025)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Copyright (c) 2024, Danila Tikhonov <danila@jiaxyga.com>
4  */
5 
6 #include <linux/backlight.h>
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 <video/mipi_display.h>
14 
15 #include <drm/drm_mipi_dsi.h>
16 #include <drm/drm_modes.h>
17 #include <drm/drm_panel.h>
18 #include <drm/drm_probe_helper.h>
19 
20 /* Manufacturer Command Set */
21 #define MCS_ACCESS_PROT_OFF	0xb0
22 #define MCS_PASSWD		0xf0
23 
24 struct ams581vf01 {
25 	struct drm_panel panel;
26 	struct mipi_dsi_device *dsi;
27 	struct gpio_desc *reset_gpio;
28 	struct regulator_bulk_data *supplies;
29 };
30 
31 static const struct regulator_bulk_data ams581vf01_supplies[] = {
32 	{ .supply = "vdd3p3" },
33 	{ .supply = "vddio" },
34 	{ .supply = "vsn" },
35 	{ .supply = "vsp" },
36 };
37 
38 static inline struct ams581vf01 *to_ams581vf01(struct drm_panel *panel)
39 {
40 	return container_of(panel, struct ams581vf01, panel);
41 }
42 
43 static void ams581vf01_reset(struct ams581vf01 *ctx)
44 {
45 	gpiod_set_value_cansleep(ctx->reset_gpio, 1);
46 	usleep_range(10000, 11000);
47 	gpiod_set_value_cansleep(ctx->reset_gpio, 0);
48 	usleep_range(10000, 11000);
49 }
50 
51 static int ams581vf01_on(struct ams581vf01 *ctx)
52 {
53 	struct mipi_dsi_device *dsi = ctx->dsi;
54 	struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi };
55 
56 	/* Sleep Out, Wait 10ms */
57 	mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx);
58 	usleep_range(10000, 11000);
59 
60 	/* TE On */
61 	mipi_dsi_dcs_set_tear_on_multi(&dsi_ctx, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
62 
63 	/* MIC Setting */
64 	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_PASSWD, 0x5a, 0x5a); /* Unlock */
65 	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xeb, 0x17,
66 					       0x41, 0x92,
67 					       0x0e, 0x10,
68 					       0x82, 0x5a);
69 	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_PASSWD, 0xa5, 0xa5); /* Lock */
70 
71 	/* Column & Page Address Setting */
72 	mipi_dsi_dcs_set_column_address_multi(&dsi_ctx, 0x0000, 0x0437);
73 	mipi_dsi_dcs_set_page_address_multi(&dsi_ctx, 0x0000, 0x0923);
74 
75 	/* Brightness Setting */
76 	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x20);
77 
78 	/* Horizontal & Vertical sync Setting */
79 	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_PASSWD, 0x5a, 0x5a); /* Unlock */
80 	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_ACCESS_PROT_OFF, 0x09);
81 	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe8, 0x11, 0x30);
82 	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_PASSWD, 0xa5, 0xa5); /* Lock */
83 	mipi_dsi_msleep(&dsi_ctx, 110);
84 
85 	/* Display On */
86 	mipi_dsi_dcs_set_display_on_multi(&dsi_ctx);
87 
88 	return dsi_ctx.accum_err;
89 }
90 
91 static void ams581vf01_off(struct ams581vf01 *ctx)
92 {
93 	struct mipi_dsi_device *dsi = ctx->dsi;
94 	struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi };
95 
96 	/* Display Off & Sleep In */
97 	mipi_dsi_dcs_set_display_off_multi(&dsi_ctx);
98 	mipi_dsi_msleep(&dsi_ctx, 20);
99 	mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx);
100 
101 	/* VCI operating mode change */
102 	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_PASSWD, 0x5a, 0x5a); /* Unlock */
103 	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_ACCESS_PROT_OFF, 0x05);
104 	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf4, 0x01);
105 	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_PASSWD, 0xa5, 0xa5);  /* Lock */
106 
107 	mipi_dsi_msleep(&dsi_ctx, 120);
108 }
109 
110 static int ams581vf01_prepare(struct drm_panel *panel)
111 {
112 	struct ams581vf01 *ctx = to_ams581vf01(panel);
113 	int ret;
114 
115 	ret = regulator_bulk_enable(ARRAY_SIZE(ams581vf01_supplies),
116 				    ctx->supplies);
117 	if (ret < 0)
118 		return ret;
119 
120 	ams581vf01_reset(ctx);
121 
122 	ret = ams581vf01_on(ctx);
123 	if (ret < 0) {
124 		gpiod_set_value_cansleep(ctx->reset_gpio, 1);
125 		regulator_bulk_disable(ARRAY_SIZE(ams581vf01_supplies),
126 				       ctx->supplies);
127 		return ret;
128 	}
129 
130 	return 0;
131 }
132 
133 static int ams581vf01_unprepare(struct drm_panel *panel)
134 {
135 	struct ams581vf01 *ctx = to_ams581vf01(panel);
136 
137 	ams581vf01_off(ctx);
138 
139 	gpiod_set_value_cansleep(ctx->reset_gpio, 1);
140 	regulator_bulk_disable(ARRAY_SIZE(ams581vf01_supplies),
141 			       ctx->supplies);
142 
143 	return 0;
144 }
145 
146 static const struct drm_display_mode ams581vf01_mode = {
147 	.clock = (1080 + 32 + 73 + 98) * (2340 + 8 + 1 + 8) * 60 / 1000,
148 	.hdisplay = 1080,
149 	.hsync_start = 1080 + 32,
150 	.hsync_end = 1080 + 32 + 73,
151 	.htotal = 1080 + 32 + 73 + 98,
152 	.vdisplay = 2340,
153 	.vsync_start = 2340 + 8,
154 	.vsync_end = 2340 + 8 + 1,
155 	.vtotal = 2340 + 8 + 1 + 8,
156 	.width_mm = 62,
157 	.height_mm = 134,
158 	.type = DRM_MODE_TYPE_DRIVER,
159 };
160 
161 static int ams581vf01_get_modes(struct drm_panel *panel,
162 				 struct drm_connector *connector)
163 {
164 	return drm_connector_helper_get_modes_fixed(connector, &ams581vf01_mode);
165 }
166 
167 static const struct drm_panel_funcs ams581vf01_panel_funcs = {
168 	.prepare = ams581vf01_prepare,
169 	.unprepare = ams581vf01_unprepare,
170 	.get_modes = ams581vf01_get_modes,
171 };
172 
173 static int ams581vf01_bl_update_status(struct backlight_device *bl)
174 {
175 	struct mipi_dsi_device *dsi = bl_get_data(bl);
176 	u16 brightness = backlight_get_brightness(bl);
177 	int ret;
178 
179 	dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
180 
181 	ret = mipi_dsi_dcs_set_display_brightness_large(dsi, brightness);
182 	if (ret < 0)
183 		return ret;
184 
185 	dsi->mode_flags |= MIPI_DSI_MODE_LPM;
186 
187 	return 0;
188 }
189 
190 static const struct backlight_ops ams581vf01_bl_ops = {
191 	.update_status = ams581vf01_bl_update_status,
192 };
193 
194 static struct backlight_device *
195 ams581vf01_create_backlight(struct mipi_dsi_device *dsi)
196 {
197 	struct device *dev = &dsi->dev;
198 	const struct backlight_properties props = {
199 		.type = BACKLIGHT_RAW,
200 		.brightness = 511,
201 		.max_brightness = 1023,
202 	};
203 
204 	return devm_backlight_device_register(dev, dev_name(dev), dev, dsi,
205 						&ams581vf01_bl_ops, &props);
206 }
207 
208 static int ams581vf01_probe(struct mipi_dsi_device *dsi)
209 {
210 	struct device *dev = &dsi->dev;
211 	struct ams581vf01 *ctx;
212 	int ret;
213 
214 	ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
215 	if (!ctx)
216 		return -ENOMEM;
217 
218 	ret = devm_regulator_bulk_get_const(&dsi->dev,
219 					    ARRAY_SIZE(ams581vf01_supplies),
220 					    ams581vf01_supplies,
221 					    &ctx->supplies);
222 	if (ret < 0)
223 		return dev_err_probe(dev, ret, "Failed to get regulators\n");
224 
225 	ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
226 	if (IS_ERR(ctx->reset_gpio))
227 		return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio),
228 					"Failed to get reset-gpios\n");
229 
230 	ctx->dsi = dsi;
231 	mipi_dsi_set_drvdata(dsi, ctx);
232 
233 	dsi->lanes = 4;
234 	dsi->format = MIPI_DSI_FMT_RGB888;
235 	dsi->mode_flags = MIPI_DSI_MODE_VIDEO_BURST |
236 			  MIPI_DSI_CLOCK_NON_CONTINUOUS | MIPI_DSI_MODE_LPM;
237 
238 	drm_panel_init(&ctx->panel, dev, &ams581vf01_panel_funcs,
239 		       DRM_MODE_CONNECTOR_DSI);
240 	ctx->panel.prepare_prev_first = true;
241 
242 	ctx->panel.backlight = ams581vf01_create_backlight(dsi);
243 	if (IS_ERR(ctx->panel.backlight))
244 		return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight),
245 					"Failed to create backlight\n");
246 
247 	drm_panel_add(&ctx->panel);
248 
249 	ret = devm_mipi_dsi_attach(dev, dsi);
250 	if (ret < 0) {
251 		drm_panel_remove(&ctx->panel);
252 		return dev_err_probe(dev, ret, "Failed to attach to DSI host\n");
253 	}
254 
255 	return 0;
256 }
257 
258 static void ams581vf01_remove(struct mipi_dsi_device *dsi)
259 {
260 	struct ams581vf01 *ctx = mipi_dsi_get_drvdata(dsi);
261 
262 	drm_panel_remove(&ctx->panel);
263 }
264 
265 static const struct of_device_id ams581vf01_of_match[] = {
266 	{ .compatible = "samsung,ams581vf01" },
267 	{ /* sentinel */ }
268 };
269 MODULE_DEVICE_TABLE(of, ams581vf01_of_match);
270 
271 static struct mipi_dsi_driver ams581vf01_driver = {
272 	.probe = ams581vf01_probe,
273 	.remove = ams581vf01_remove,
274 	.driver = {
275 		.name = "panel-samsung-ams581vf01",
276 		.of_match_table = ams581vf01_of_match,
277 	},
278 };
279 module_mipi_dsi_driver(ams581vf01_driver);
280 
281 MODULE_AUTHOR("Danila Tikhonov <danila@jiaxyga.com>");
282 MODULE_DESCRIPTION("DRM driver for SAMSUNG AMS581VF01 cmd mode dsi panel");
283 MODULE_LICENSE("GPL");
284