1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright (C) 2020 BayLibre, SAS
4 * Author: Neil Armstrong <narmstrong@baylibre.com>
5 */
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 <video/mipi_display.h>
14
15#include <drm/drm_crtc.h>
16#include <drm/drm_device.h>
17#include <drm/drm_mipi_dsi.h>
18#include <drm/drm_modes.h>
19#include <drm/drm_panel.h>
20
21struct tdo_tl070wsh30_panel {
22	struct drm_panel base;
23	struct mipi_dsi_device *link;
24
25	struct regulator *supply;
26	struct gpio_desc *reset_gpio;
27
28	bool prepared;
29};
30
31static inline
32struct tdo_tl070wsh30_panel *to_tdo_tl070wsh30_panel(struct drm_panel *panel)
33{
34	return container_of(panel, struct tdo_tl070wsh30_panel, base);
35}
36
37static int tdo_tl070wsh30_panel_prepare(struct drm_panel *panel)
38{
39	struct tdo_tl070wsh30_panel *tdo_tl070wsh30 = to_tdo_tl070wsh30_panel(panel);
40	int err;
41
42	if (tdo_tl070wsh30->prepared)
43		return 0;
44
45	err = regulator_enable(tdo_tl070wsh30->supply);
46	if (err < 0)
47		return err;
48
49	usleep_range(10000, 11000);
50
51	gpiod_set_value_cansleep(tdo_tl070wsh30->reset_gpio, 1);
52
53	usleep_range(10000, 11000);
54
55	gpiod_set_value_cansleep(tdo_tl070wsh30->reset_gpio, 0);
56
57	msleep(200);
58
59	err = mipi_dsi_dcs_exit_sleep_mode(tdo_tl070wsh30->link);
60	if (err < 0) {
61		dev_err(panel->dev, "failed to exit sleep mode: %d\n", err);
62		regulator_disable(tdo_tl070wsh30->supply);
63		return err;
64	}
65
66	msleep(200);
67
68	err = mipi_dsi_dcs_set_display_on(tdo_tl070wsh30->link);
69	if (err < 0) {
70		dev_err(panel->dev, "failed to set display on: %d\n", err);
71		regulator_disable(tdo_tl070wsh30->supply);
72		return err;
73	}
74
75	msleep(20);
76
77	tdo_tl070wsh30->prepared = true;
78
79	return 0;
80}
81
82static int tdo_tl070wsh30_panel_unprepare(struct drm_panel *panel)
83{
84	struct tdo_tl070wsh30_panel *tdo_tl070wsh30 = to_tdo_tl070wsh30_panel(panel);
85	int err;
86
87	if (!tdo_tl070wsh30->prepared)
88		return 0;
89
90	err = mipi_dsi_dcs_set_display_off(tdo_tl070wsh30->link);
91	if (err < 0)
92		dev_err(panel->dev, "failed to set display off: %d\n", err);
93
94	usleep_range(10000, 11000);
95
96	err = mipi_dsi_dcs_enter_sleep_mode(tdo_tl070wsh30->link);
97	if (err < 0) {
98		dev_err(panel->dev, "failed to enter sleep mode: %d\n", err);
99		return err;
100	}
101
102	usleep_range(10000, 11000);
103
104	regulator_disable(tdo_tl070wsh30->supply);
105
106	tdo_tl070wsh30->prepared = false;
107
108	return 0;
109}
110
111static const struct drm_display_mode default_mode = {
112	.clock = 47250,
113	.hdisplay = 1024,
114	.hsync_start = 1024 + 46,
115	.hsync_end = 1024 + 46 + 80,
116	.htotal = 1024 + 46 + 80 + 100,
117	.vdisplay = 600,
118	.vsync_start = 600 + 5,
119	.vsync_end = 600 + 5 + 5,
120	.vtotal = 600 + 5 + 5 + 20,
121	.flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC,
122};
123
124static int tdo_tl070wsh30_panel_get_modes(struct drm_panel *panel,
125				       struct drm_connector *connector)
126{
127	struct drm_display_mode *mode;
128
129	mode = drm_mode_duplicate(connector->dev, &default_mode);
130	if (!mode) {
131		dev_err(panel->dev, "failed to add mode %ux%u@%u\n",
132			default_mode.hdisplay, default_mode.vdisplay,
133			drm_mode_vrefresh(&default_mode));
134		return -ENOMEM;
135	}
136
137	drm_mode_set_name(mode);
138
139	drm_mode_probed_add(connector, mode);
140
141	connector->display_info.width_mm = 154;
142	connector->display_info.height_mm = 85;
143	connector->display_info.bpc = 8;
144
145	return 1;
146}
147
148static const struct drm_panel_funcs tdo_tl070wsh30_panel_funcs = {
149	.unprepare = tdo_tl070wsh30_panel_unprepare,
150	.prepare = tdo_tl070wsh30_panel_prepare,
151	.get_modes = tdo_tl070wsh30_panel_get_modes,
152};
153
154static const struct of_device_id tdo_tl070wsh30_of_match[] = {
155	{ .compatible = "tdo,tl070wsh30", },
156	{ /* sentinel */ }
157};
158MODULE_DEVICE_TABLE(of, tdo_tl070wsh30_of_match);
159
160static int tdo_tl070wsh30_panel_add(struct tdo_tl070wsh30_panel *tdo_tl070wsh30)
161{
162	struct device *dev = &tdo_tl070wsh30->link->dev;
163	int err;
164
165	tdo_tl070wsh30->supply = devm_regulator_get(dev, "power");
166	if (IS_ERR(tdo_tl070wsh30->supply))
167		return PTR_ERR(tdo_tl070wsh30->supply);
168
169	tdo_tl070wsh30->reset_gpio = devm_gpiod_get(dev, "reset",
170						    GPIOD_OUT_LOW);
171	if (IS_ERR(tdo_tl070wsh30->reset_gpio)) {
172		err = PTR_ERR(tdo_tl070wsh30->reset_gpio);
173		dev_dbg(dev, "failed to get reset gpio: %d\n", err);
174		return err;
175	}
176
177	drm_panel_init(&tdo_tl070wsh30->base, &tdo_tl070wsh30->link->dev,
178		       &tdo_tl070wsh30_panel_funcs, DRM_MODE_CONNECTOR_DSI);
179
180	err = drm_panel_of_backlight(&tdo_tl070wsh30->base);
181	if (err)
182		return err;
183
184	drm_panel_add(&tdo_tl070wsh30->base);
185
186	return 0;
187}
188
189static int tdo_tl070wsh30_panel_probe(struct mipi_dsi_device *dsi)
190{
191	struct tdo_tl070wsh30_panel *tdo_tl070wsh30;
192	int err;
193
194	dsi->lanes = 4;
195	dsi->format = MIPI_DSI_FMT_RGB888;
196	dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | MIPI_DSI_MODE_LPM;
197
198	tdo_tl070wsh30 = devm_kzalloc(&dsi->dev, sizeof(*tdo_tl070wsh30),
199				    GFP_KERNEL);
200	if (!tdo_tl070wsh30)
201		return -ENOMEM;
202
203	mipi_dsi_set_drvdata(dsi, tdo_tl070wsh30);
204	tdo_tl070wsh30->link = dsi;
205
206	err = tdo_tl070wsh30_panel_add(tdo_tl070wsh30);
207	if (err < 0)
208		return err;
209
210	return mipi_dsi_attach(dsi);
211}
212
213static void tdo_tl070wsh30_panel_remove(struct mipi_dsi_device *dsi)
214{
215	struct tdo_tl070wsh30_panel *tdo_tl070wsh30 = mipi_dsi_get_drvdata(dsi);
216	int err;
217
218	err = mipi_dsi_detach(dsi);
219	if (err < 0)
220		dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", err);
221
222	drm_panel_remove(&tdo_tl070wsh30->base);
223	drm_panel_disable(&tdo_tl070wsh30->base);
224	drm_panel_unprepare(&tdo_tl070wsh30->base);
225}
226
227static void tdo_tl070wsh30_panel_shutdown(struct mipi_dsi_device *dsi)
228{
229	struct tdo_tl070wsh30_panel *tdo_tl070wsh30 = mipi_dsi_get_drvdata(dsi);
230
231	drm_panel_disable(&tdo_tl070wsh30->base);
232	drm_panel_unprepare(&tdo_tl070wsh30->base);
233}
234
235static struct mipi_dsi_driver tdo_tl070wsh30_panel_driver = {
236	.driver = {
237		.name = "panel-tdo-tl070wsh30",
238		.of_match_table = tdo_tl070wsh30_of_match,
239	},
240	.probe = tdo_tl070wsh30_panel_probe,
241	.remove = tdo_tl070wsh30_panel_remove,
242	.shutdown = tdo_tl070wsh30_panel_shutdown,
243};
244module_mipi_dsi_driver(tdo_tl070wsh30_panel_driver);
245
246MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
247MODULE_DESCRIPTION("TDO TL070WSH30 panel driver");
248MODULE_LICENSE("GPL v2");
249