xref: /linux/drivers/gpu/drm/panel/panel-anbernic-td4310.c (revision 67f8bc848ee31831336bd478e57d2f993551902e)
1a81b4fe6SChris Morgan // SPDX-License-Identifier: GPL-2.0
2a81b4fe6SChris Morgan /*
3a81b4fe6SChris Morgan  * Driver for Anbernic panels with TD4310 panel controller.
4a81b4fe6SChris Morgan  *
5a81b4fe6SChris Morgan  * Copyright (C) 2026 Chris Morgan <macromorgan@hotmail.com>
6a81b4fe6SChris Morgan  *
7a81b4fe6SChris Morgan  */
8a81b4fe6SChris Morgan 
9a81b4fe6SChris Morgan #include <linux/gpio/consumer.h>
10a81b4fe6SChris Morgan #include <linux/module.h>
11a81b4fe6SChris Morgan #include <linux/of.h>
12a81b4fe6SChris Morgan #include <linux/regulator/consumer.h>
13a81b4fe6SChris Morgan 
14a81b4fe6SChris Morgan #include <drm/drm_mipi_dsi.h>
15*b5a8b1f2SThierry Reding #include <drm/drm_of.h>
16a81b4fe6SChris Morgan #include <drm/drm_panel.h>
17a81b4fe6SChris Morgan #include <drm/drm_probe_helper.h>
18a81b4fe6SChris Morgan 
19a81b4fe6SChris Morgan #include <video/mipi_display.h>
20a81b4fe6SChris Morgan 
21a81b4fe6SChris Morgan struct anbernic_panel_td4310_info {
22a81b4fe6SChris Morgan 	const struct drm_display_mode *display_mode;
23a81b4fe6SChris Morgan 	u16 width_mm;
24a81b4fe6SChris Morgan 	u16 height_mm;
25a81b4fe6SChris Morgan 	u32 bus_flags;
26a81b4fe6SChris Morgan 	unsigned long mode_flags;
27a81b4fe6SChris Morgan 	u32 format;
28a81b4fe6SChris Morgan 	u32 lanes;
29a81b4fe6SChris Morgan 	u16 prepare_delay;
30a81b4fe6SChris Morgan 	u16 reset_delay;
31a81b4fe6SChris Morgan 	u16 init_delay;
32a81b4fe6SChris Morgan 	u16 enable_delay;
33a81b4fe6SChris Morgan 	u16 disable_delay;
34a81b4fe6SChris Morgan 	u16 unprepare_delay;
35a81b4fe6SChris Morgan };
36a81b4fe6SChris Morgan 
37a81b4fe6SChris Morgan struct anbernic_panel_td4310 {
38a81b4fe6SChris Morgan 	struct device *dev;
39a81b4fe6SChris Morgan 	struct mipi_dsi_device *dsi;
40a81b4fe6SChris Morgan 	struct drm_panel panel;
41a81b4fe6SChris Morgan 	const struct anbernic_panel_td4310_info *panel_info;
42a81b4fe6SChris Morgan 	struct gpio_desc *reset_gpio;
43a81b4fe6SChris Morgan 	struct gpio_desc *enable_gpio;
44a81b4fe6SChris Morgan 	struct regulator *vdd;
45a81b4fe6SChris Morgan 	enum drm_panel_orientation orientation;
46a81b4fe6SChris Morgan };
47a81b4fe6SChris Morgan 
48a81b4fe6SChris Morgan static inline struct anbernic_panel_td4310 *panel_to_anbernic_panel_td4310(struct drm_panel *panel)
49a81b4fe6SChris Morgan {
50a81b4fe6SChris Morgan 	return container_of(panel, struct anbernic_panel_td4310, panel);
51a81b4fe6SChris Morgan }
52a81b4fe6SChris Morgan 
53a81b4fe6SChris Morgan static int panel_anbernic_td4310_prepare(struct drm_panel *panel)
54a81b4fe6SChris Morgan {
55a81b4fe6SChris Morgan 	struct anbernic_panel_td4310 *ctx = panel_to_anbernic_panel_td4310(panel);
56a81b4fe6SChris Morgan 	struct mipi_dsi_device *dsi = ctx->dsi;
57a81b4fe6SChris Morgan 	struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi };
58a81b4fe6SChris Morgan 	int ret;
59a81b4fe6SChris Morgan 
60a81b4fe6SChris Morgan 	ret = regulator_enable(ctx->vdd);
61a81b4fe6SChris Morgan 	if (ret)
62a81b4fe6SChris Morgan 		return ret;
63a81b4fe6SChris Morgan 
64a81b4fe6SChris Morgan 	ret = gpiod_set_value_cansleep(ctx->enable_gpio, 1);
65a81b4fe6SChris Morgan 	if (ret)
66a81b4fe6SChris Morgan 		goto err_enable;
67a81b4fe6SChris Morgan 
68a81b4fe6SChris Morgan 	if (ctx->panel_info->enable_delay)
69a81b4fe6SChris Morgan 		mipi_dsi_msleep(&dsi_ctx, ctx->panel_info->enable_delay);
70a81b4fe6SChris Morgan 
71a81b4fe6SChris Morgan 	ret = gpiod_set_value_cansleep(ctx->reset_gpio, 1);
72a81b4fe6SChris Morgan 	if (ret)
73a81b4fe6SChris Morgan 		goto err_reset;
74a81b4fe6SChris Morgan 
75a81b4fe6SChris Morgan 	mipi_dsi_msleep(&dsi_ctx, 10);
76a81b4fe6SChris Morgan 
77a81b4fe6SChris Morgan 	ret = gpiod_set_value_cansleep(ctx->reset_gpio, 0);
78a81b4fe6SChris Morgan 	if (ret)
79a81b4fe6SChris Morgan 		goto err_reset;
80a81b4fe6SChris Morgan 
81a81b4fe6SChris Morgan 	if (ctx->panel_info->reset_delay)
82a81b4fe6SChris Morgan 		mipi_dsi_msleep(&dsi_ctx, ctx->panel_info->reset_delay);
83a81b4fe6SChris Morgan 
84a81b4fe6SChris Morgan 	mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx);
85a81b4fe6SChris Morgan 	mipi_dsi_msleep(&dsi_ctx, ctx->panel_info->prepare_delay);
86a81b4fe6SChris Morgan 	mipi_dsi_dcs_set_display_on_multi(&dsi_ctx);
87a81b4fe6SChris Morgan 	mipi_dsi_msleep(&dsi_ctx, ctx->panel_info->prepare_delay);
88a81b4fe6SChris Morgan 
89a81b4fe6SChris Morgan 	if (dsi_ctx.accum_err) {
90a81b4fe6SChris Morgan 		ret = dsi_ctx.accum_err;
91a81b4fe6SChris Morgan 		goto err_reset;
92a81b4fe6SChris Morgan 	}
93a81b4fe6SChris Morgan 
94a81b4fe6SChris Morgan 	return 0;
95a81b4fe6SChris Morgan 
96a81b4fe6SChris Morgan err_reset:
97a81b4fe6SChris Morgan 	gpiod_set_value_cansleep(ctx->enable_gpio, 0);
98a81b4fe6SChris Morgan err_enable:
99a81b4fe6SChris Morgan 	regulator_disable(ctx->vdd);
100a81b4fe6SChris Morgan 	return ret;
101a81b4fe6SChris Morgan }
102a81b4fe6SChris Morgan 
103a81b4fe6SChris Morgan static int panel_anbernic_td4310_unprepare(struct drm_panel *panel)
104a81b4fe6SChris Morgan {
105a81b4fe6SChris Morgan 	struct anbernic_panel_td4310 *ctx = panel_to_anbernic_panel_td4310(panel);
106a81b4fe6SChris Morgan 	struct mipi_dsi_device *dsi = ctx->dsi;
107a81b4fe6SChris Morgan 	struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi };
108a81b4fe6SChris Morgan 
109a81b4fe6SChris Morgan 	mipi_dsi_dcs_set_display_off_multi(&dsi_ctx);
110a81b4fe6SChris Morgan 	mipi_dsi_msleep(&dsi_ctx, ctx->panel_info->unprepare_delay);
111a81b4fe6SChris Morgan 	mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx);
112a81b4fe6SChris Morgan 	mipi_dsi_msleep(&dsi_ctx, ctx->panel_info->disable_delay);
113a81b4fe6SChris Morgan 
114a81b4fe6SChris Morgan 	gpiod_set_value_cansleep(ctx->enable_gpio, 0);
115a81b4fe6SChris Morgan 
116a81b4fe6SChris Morgan 	gpiod_set_value_cansleep(ctx->reset_gpio, 1);
117a81b4fe6SChris Morgan 
118a81b4fe6SChris Morgan 	regulator_disable(ctx->vdd);
119a81b4fe6SChris Morgan 
120a81b4fe6SChris Morgan 	return 0;
121a81b4fe6SChris Morgan }
122a81b4fe6SChris Morgan 
123a81b4fe6SChris Morgan static int panel_anbernic_td4310_get_mode(struct drm_panel *panel,
124a81b4fe6SChris Morgan 				    struct drm_connector *connector)
125a81b4fe6SChris Morgan {
126a81b4fe6SChris Morgan 	struct anbernic_panel_td4310 *ctx = panel_to_anbernic_panel_td4310(panel);
127a81b4fe6SChris Morgan 	const struct anbernic_panel_td4310_info *panel_info = ctx->panel_info;
128a81b4fe6SChris Morgan 
129a81b4fe6SChris Morgan 	connector->display_info.bpc = 8;
130a81b4fe6SChris Morgan 	connector->display_info.width_mm = panel_info->width_mm;
131a81b4fe6SChris Morgan 	connector->display_info.height_mm = panel_info->height_mm;
132a81b4fe6SChris Morgan 	connector->display_info.bus_flags = panel_info->bus_flags;
133a81b4fe6SChris Morgan 
134a81b4fe6SChris Morgan 	return drm_connector_helper_get_modes_fixed(connector, panel_info->display_mode);
135a81b4fe6SChris Morgan }
136a81b4fe6SChris Morgan 
137a81b4fe6SChris Morgan static enum drm_panel_orientation panel_anbernic_td4310_get_orientation(struct drm_panel *panel)
138a81b4fe6SChris Morgan {
139a81b4fe6SChris Morgan 	struct anbernic_panel_td4310 *ctx = panel_to_anbernic_panel_td4310(panel);
140a81b4fe6SChris Morgan 
141a81b4fe6SChris Morgan 	return ctx->orientation;
142a81b4fe6SChris Morgan }
143a81b4fe6SChris Morgan 
144a81b4fe6SChris Morgan static const struct drm_panel_funcs panel_anbernic_td4310_funcs = {
145a81b4fe6SChris Morgan 	.prepare = panel_anbernic_td4310_prepare,
146a81b4fe6SChris Morgan 	.unprepare = panel_anbernic_td4310_unprepare,
147a81b4fe6SChris Morgan 	.get_modes = panel_anbernic_td4310_get_mode,
148a81b4fe6SChris Morgan 	.get_orientation = panel_anbernic_td4310_get_orientation,
149a81b4fe6SChris Morgan };
150a81b4fe6SChris Morgan 
151a81b4fe6SChris Morgan static int panel_anbernic_td4310_probe(struct mipi_dsi_device *dsi)
152a81b4fe6SChris Morgan {
153a81b4fe6SChris Morgan 	struct device *dev = &dsi->dev;
154a81b4fe6SChris Morgan 	struct anbernic_panel_td4310 *ctx;
155a81b4fe6SChris Morgan 	int ret;
156a81b4fe6SChris Morgan 
157a81b4fe6SChris Morgan 	ctx = devm_drm_panel_alloc(dev, struct anbernic_panel_td4310, panel,
158a81b4fe6SChris Morgan 				   &panel_anbernic_td4310_funcs,
159a81b4fe6SChris Morgan 				   DRM_MODE_CONNECTOR_DSI);
160a81b4fe6SChris Morgan 	if (IS_ERR(ctx))
161a81b4fe6SChris Morgan 		return PTR_ERR(ctx);
162a81b4fe6SChris Morgan 
163a81b4fe6SChris Morgan 	ctx->dev = dev;
164a81b4fe6SChris Morgan 
165a81b4fe6SChris Morgan 	ctx->panel_info = of_device_get_match_data(dev);
166a81b4fe6SChris Morgan 	if (!ctx->panel_info)
167a81b4fe6SChris Morgan 		return -EINVAL;
168a81b4fe6SChris Morgan 
169*b5a8b1f2SThierry Reding 	ret = drm_of_get_panel_orientation(dev->of_node, &ctx->orientation);
170a81b4fe6SChris Morgan 	if (ret < 0)
171a81b4fe6SChris Morgan 		return dev_err_probe(dev, ret, "Failed to get panel orientation\n");
172a81b4fe6SChris Morgan 
173a81b4fe6SChris Morgan 	ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
174a81b4fe6SChris Morgan 	if (IS_ERR(ctx->reset_gpio))
175a81b4fe6SChris Morgan 		return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio),
176a81b4fe6SChris Morgan 				     "Cannot get reset gpio\n");
177a81b4fe6SChris Morgan 
178a81b4fe6SChris Morgan 	ctx->enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_LOW);
179a81b4fe6SChris Morgan 	if (IS_ERR(ctx->enable_gpio))
180a81b4fe6SChris Morgan 		return dev_err_probe(dev, PTR_ERR(ctx->enable_gpio),
181a81b4fe6SChris Morgan 				     "Cannot get enable gpio\n");
182a81b4fe6SChris Morgan 
183a81b4fe6SChris Morgan 	ctx->vdd = devm_regulator_get(dev, "vdd");
184a81b4fe6SChris Morgan 	if (IS_ERR(ctx->vdd))
185a81b4fe6SChris Morgan 		return dev_err_probe(dev, PTR_ERR(ctx->vdd),
186a81b4fe6SChris Morgan 				     "Failed to request vdd regulator\n");
187a81b4fe6SChris Morgan 
188a81b4fe6SChris Morgan 	ctx->dsi = dsi;
189a81b4fe6SChris Morgan 	mipi_dsi_set_drvdata(dsi, ctx);
190a81b4fe6SChris Morgan 
191a81b4fe6SChris Morgan 	dsi->lanes = ctx->panel_info->lanes;
192a81b4fe6SChris Morgan 	dsi->format = ctx->panel_info->format;
193a81b4fe6SChris Morgan 	dsi->mode_flags = ctx->panel_info->mode_flags;
194a81b4fe6SChris Morgan 
195a81b4fe6SChris Morgan 	ret = drm_panel_of_backlight(&ctx->panel);
196a81b4fe6SChris Morgan 	if (ret)
197a81b4fe6SChris Morgan 		return ret;
198a81b4fe6SChris Morgan 
199a81b4fe6SChris Morgan 	devm_drm_panel_add(dev, &ctx->panel);
200a81b4fe6SChris Morgan 
201a81b4fe6SChris Morgan 	ret = devm_mipi_dsi_attach(dev, dsi);
202a81b4fe6SChris Morgan 	if (ret < 0)
203a81b4fe6SChris Morgan 		return dev_err_probe(dev, ret, "Failed to attach to DSI host\n");
204a81b4fe6SChris Morgan 
205a81b4fe6SChris Morgan 	return 0;
206a81b4fe6SChris Morgan }
207a81b4fe6SChris Morgan 
208a81b4fe6SChris Morgan static const struct drm_display_mode anbernic_vitapro_mode = {
209a81b4fe6SChris Morgan 	.clock = 140020,
210a81b4fe6SChris Morgan 	.hdisplay = 1080,
211a81b4fe6SChris Morgan 	.hsync_start = 1080 + 50,
212a81b4fe6SChris Morgan 	.hsync_end = 1080 + 50 + 4,
213a81b4fe6SChris Morgan 	.htotal = 1080 + 50 + 4 + 50,
214a81b4fe6SChris Morgan 	.vdisplay = 1920,
215a81b4fe6SChris Morgan 	.vsync_start = 1920 + 15,
216a81b4fe6SChris Morgan 	.vsync_end = 1920 + 15 + 4,
217a81b4fe6SChris Morgan 	.vtotal = 1920 + 15 + 4 + 32,
218a81b4fe6SChris Morgan 	.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
219a81b4fe6SChris Morgan };
220a81b4fe6SChris Morgan 
221a81b4fe6SChris Morgan static const struct anbernic_panel_td4310_info anbernic_vitapro_info = {
222a81b4fe6SChris Morgan 	.display_mode = &anbernic_vitapro_mode,
223a81b4fe6SChris Morgan 	.width_mm = 69,
224a81b4fe6SChris Morgan 	.height_mm = 121,
225a81b4fe6SChris Morgan 	.bus_flags = DRM_BUS_FLAG_DE_LOW | DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE,
226a81b4fe6SChris Morgan 	.mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
227a81b4fe6SChris Morgan 		      MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_NO_EOT_PACKET |
228a81b4fe6SChris Morgan 		      MIPI_DSI_CLOCK_NON_CONTINUOUS,
229a81b4fe6SChris Morgan 	.format = MIPI_DSI_FMT_RGB888,
230a81b4fe6SChris Morgan 	.lanes = 4,
231a81b4fe6SChris Morgan 	.prepare_delay = 50,
232a81b4fe6SChris Morgan 	.reset_delay = 220,
233a81b4fe6SChris Morgan 	.enable_delay = 120,
234a81b4fe6SChris Morgan 	.disable_delay = 50,
235a81b4fe6SChris Morgan 	.unprepare_delay = 20,
236a81b4fe6SChris Morgan };
237a81b4fe6SChris Morgan 
238a81b4fe6SChris Morgan static const struct of_device_id panel_anbernic_td4310_of_match[] = {
239a81b4fe6SChris Morgan 	{
240a81b4fe6SChris Morgan 		.compatible = "anbernic,panel-vita-pro",
241a81b4fe6SChris Morgan 		.data = &anbernic_vitapro_info,
242a81b4fe6SChris Morgan 	},
243a81b4fe6SChris Morgan 	{ }
244a81b4fe6SChris Morgan };
245a81b4fe6SChris Morgan MODULE_DEVICE_TABLE(of, panel_anbernic_td4310_of_match);
246a81b4fe6SChris Morgan 
247a81b4fe6SChris Morgan static struct mipi_dsi_driver anbernic_panel_td4310_driver = {
248a81b4fe6SChris Morgan 	.driver = {
249a81b4fe6SChris Morgan 		.name = "panel-anbernic-td4310",
250a81b4fe6SChris Morgan 		.of_match_table = panel_anbernic_td4310_of_match,
251a81b4fe6SChris Morgan 	},
252a81b4fe6SChris Morgan 	.probe	= panel_anbernic_td4310_probe,
253a81b4fe6SChris Morgan };
254a81b4fe6SChris Morgan module_mipi_dsi_driver(anbernic_panel_td4310_driver);
255a81b4fe6SChris Morgan 
256a81b4fe6SChris Morgan MODULE_AUTHOR("Chris Morgan <macromorgan@hotmail.com>");
257a81b4fe6SChris Morgan MODULE_DESCRIPTION("DRM driver for Anbernic TD4310 MIPI DSI panels");
258a81b4fe6SChris Morgan MODULE_LICENSE("GPL");
259