Linux Audio

Check our new training course

Loading...
Note: File does not exist in v5.4.
  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");