1 // SPDX-License-Identifier: GPL-2.0
3 * Copyright (C) 2020 BayLibre, SAS
4 * Author: Neil Armstrong <narmstrong@baylibre.com>
7 #include <linux/delay.h>
8 #include <linux/gpio/consumer.h>
9 #include <linux/module.h>
11 #include <linux/regulator/consumer.h>
13 #include <video/mipi_display.h>
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>
21 struct tdo_tl070wsh30_panel
{
22 struct drm_panel base
;
23 struct mipi_dsi_device
*link
;
25 struct regulator
*supply
;
26 struct gpio_desc
*reset_gpio
;
32 struct tdo_tl070wsh30_panel
*to_tdo_tl070wsh30_panel(struct drm_panel
*panel
)
34 return container_of(panel
, struct tdo_tl070wsh30_panel
, base
);
37 static int tdo_tl070wsh30_panel_prepare(struct drm_panel
*panel
)
39 struct tdo_tl070wsh30_panel
*tdo_tl070wsh30
= to_tdo_tl070wsh30_panel(panel
);
42 if (tdo_tl070wsh30
->prepared
)
45 err
= regulator_enable(tdo_tl070wsh30
->supply
);
49 usleep_range(10000, 11000);
51 gpiod_set_value_cansleep(tdo_tl070wsh30
->reset_gpio
, 1);
53 usleep_range(10000, 11000);
55 gpiod_set_value_cansleep(tdo_tl070wsh30
->reset_gpio
, 0);
59 err
= mipi_dsi_dcs_exit_sleep_mode(tdo_tl070wsh30
->link
);
61 dev_err(panel
->dev
, "failed to exit sleep mode: %d\n", err
);
62 regulator_disable(tdo_tl070wsh30
->supply
);
68 err
= mipi_dsi_dcs_set_display_on(tdo_tl070wsh30
->link
);
70 dev_err(panel
->dev
, "failed to set display on: %d\n", err
);
71 regulator_disable(tdo_tl070wsh30
->supply
);
77 tdo_tl070wsh30
->prepared
= true;
82 static int tdo_tl070wsh30_panel_unprepare(struct drm_panel
*panel
)
84 struct tdo_tl070wsh30_panel
*tdo_tl070wsh30
= to_tdo_tl070wsh30_panel(panel
);
87 if (!tdo_tl070wsh30
->prepared
)
90 err
= mipi_dsi_dcs_set_display_off(tdo_tl070wsh30
->link
);
92 dev_err(panel
->dev
, "failed to set display off: %d\n", err
);
94 usleep_range(10000, 11000);
96 err
= mipi_dsi_dcs_enter_sleep_mode(tdo_tl070wsh30
->link
);
98 dev_err(panel
->dev
, "failed to enter sleep mode: %d\n", err
);
102 usleep_range(10000, 11000);
104 regulator_disable(tdo_tl070wsh30
->supply
);
106 tdo_tl070wsh30
->prepared
= false;
111 static const struct drm_display_mode default_mode
= {
114 .hsync_start
= 1024 + 46,
115 .hsync_end
= 1024 + 46 + 80,
116 .htotal
= 1024 + 46 + 80 + 100,
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
,
124 static int tdo_tl070wsh30_panel_get_modes(struct drm_panel
*panel
,
125 struct drm_connector
*connector
)
127 struct drm_display_mode
*mode
;
129 mode
= drm_mode_duplicate(connector
->dev
, &default_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
));
137 drm_mode_set_name(mode
);
139 drm_mode_probed_add(connector
, mode
);
141 connector
->display_info
.width_mm
= 154;
142 connector
->display_info
.height_mm
= 85;
143 connector
->display_info
.bpc
= 8;
148 static 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
,
154 static const struct of_device_id tdo_tl070wsh30_of_match
[] = {
155 { .compatible
= "tdo,tl070wsh30", },
158 MODULE_DEVICE_TABLE(of
, tdo_tl070wsh30_of_match
);
160 static int tdo_tl070wsh30_panel_add(struct tdo_tl070wsh30_panel
*tdo_tl070wsh30
)
162 struct device
*dev
= &tdo_tl070wsh30
->link
->dev
;
165 tdo_tl070wsh30
->supply
= devm_regulator_get(dev
, "power");
166 if (IS_ERR(tdo_tl070wsh30
->supply
))
167 return PTR_ERR(tdo_tl070wsh30
->supply
);
169 tdo_tl070wsh30
->reset_gpio
= devm_gpiod_get(dev
, "reset",
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
);
177 drm_panel_init(&tdo_tl070wsh30
->base
, &tdo_tl070wsh30
->link
->dev
,
178 &tdo_tl070wsh30_panel_funcs
, DRM_MODE_CONNECTOR_DSI
);
180 err
= drm_panel_of_backlight(&tdo_tl070wsh30
->base
);
184 drm_panel_add(&tdo_tl070wsh30
->base
);
189 static int tdo_tl070wsh30_panel_probe(struct mipi_dsi_device
*dsi
)
191 struct tdo_tl070wsh30_panel
*tdo_tl070wsh30
;
195 dsi
->format
= MIPI_DSI_FMT_RGB888
;
196 dsi
->mode_flags
= MIPI_DSI_MODE_VIDEO
| MIPI_DSI_MODE_VIDEO_BURST
| MIPI_DSI_MODE_LPM
;
198 tdo_tl070wsh30
= devm_kzalloc(&dsi
->dev
, sizeof(*tdo_tl070wsh30
),
203 mipi_dsi_set_drvdata(dsi
, tdo_tl070wsh30
);
204 tdo_tl070wsh30
->link
= dsi
;
206 err
= tdo_tl070wsh30_panel_add(tdo_tl070wsh30
);
210 return mipi_dsi_attach(dsi
);
213 static int tdo_tl070wsh30_panel_remove(struct mipi_dsi_device
*dsi
)
215 struct tdo_tl070wsh30_panel
*tdo_tl070wsh30
= mipi_dsi_get_drvdata(dsi
);
218 err
= mipi_dsi_detach(dsi
);
220 dev_err(&dsi
->dev
, "failed to detach from DSI host: %d\n", err
);
222 drm_panel_remove(&tdo_tl070wsh30
->base
);
223 drm_panel_disable(&tdo_tl070wsh30
->base
);
224 drm_panel_unprepare(&tdo_tl070wsh30
->base
);
229 static void tdo_tl070wsh30_panel_shutdown(struct mipi_dsi_device
*dsi
)
231 struct tdo_tl070wsh30_panel
*tdo_tl070wsh30
= mipi_dsi_get_drvdata(dsi
);
233 drm_panel_disable(&tdo_tl070wsh30
->base
);
234 drm_panel_unprepare(&tdo_tl070wsh30
->base
);
237 static struct mipi_dsi_driver tdo_tl070wsh30_panel_driver
= {
239 .name
= "panel-tdo-tl070wsh30",
240 .of_match_table
= tdo_tl070wsh30_of_match
,
242 .probe
= tdo_tl070wsh30_panel_probe
,
243 .remove
= tdo_tl070wsh30_panel_remove
,
244 .shutdown
= tdo_tl070wsh30_panel_shutdown
,
246 module_mipi_dsi_driver(tdo_tl070wsh30_panel_driver
);
248 MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
249 MODULE_DESCRIPTION("TDO TL070WSH30 panel driver");
250 MODULE_LICENSE("GPL v2");