1 // SPDX-License-Identifier: GPL-2.0+
3 * MIPI-DSI Samsung s6d16d0 panel driver. This is a 864x480
4 * AMOLED panel with a command-only DSI interface.
7 #include <drm/drm_modes.h>
8 #include <drm/drm_mipi_dsi.h>
9 #include <drm/drm_panel.h>
10 #include <drm/drm_print.h>
12 #include <linux/gpio/consumer.h>
13 #include <linux/regulator/consumer.h>
14 #include <linux/delay.h>
15 #include <linux/of_device.h>
16 #include <linux/module.h>
20 struct drm_panel panel
;
21 struct regulator
*supply
;
22 struct gpio_desc
*reset_gpio
;
26 * The timings are not very helpful as the display is used in
29 static const struct drm_display_mode samsung_s6d16d0_mode
= {
30 /* HS clock, (htotal*vtotal*vrefresh)/1000 */
33 .hsync_start
= 864 + 154,
34 .hsync_end
= 864 + 154 + 16,
35 .htotal
= 864 + 154 + 16 + 32,
37 .vsync_start
= 480 + 1,
38 .vsync_end
= 480 + 1 + 1,
39 .vtotal
= 480 + 1 + 1 + 1,
41 * This depends on the clocking HS vs LP rate, this value
43 * vrefresh = (clock * 1000) / (htotal*vtotal)
50 static inline struct s6d16d0
*panel_to_s6d16d0(struct drm_panel
*panel
)
52 return container_of(panel
, struct s6d16d0
, panel
);
55 static int s6d16d0_unprepare(struct drm_panel
*panel
)
57 struct s6d16d0
*s6
= panel_to_s6d16d0(panel
);
58 struct mipi_dsi_device
*dsi
= to_mipi_dsi_device(s6
->dev
);
61 /* Enter sleep mode */
62 ret
= mipi_dsi_dcs_enter_sleep_mode(dsi
);
64 DRM_DEV_ERROR(s6
->dev
, "failed to enter sleep mode (%d)\n",
70 gpiod_set_value_cansleep(s6
->reset_gpio
, 1);
71 regulator_disable(s6
->supply
);
76 static int s6d16d0_prepare(struct drm_panel
*panel
)
78 struct s6d16d0
*s6
= panel_to_s6d16d0(panel
);
79 struct mipi_dsi_device
*dsi
= to_mipi_dsi_device(s6
->dev
);
82 ret
= regulator_enable(s6
->supply
);
84 DRM_DEV_ERROR(s6
->dev
, "failed to enable supply (%d)\n", ret
);
89 gpiod_set_value_cansleep(s6
->reset_gpio
, 1);
92 gpiod_set_value_cansleep(s6
->reset_gpio
, 0);
95 /* Enabe tearing mode: send TE (tearing effect) at VBLANK */
96 ret
= mipi_dsi_dcs_set_tear_on(dsi
,
97 MIPI_DSI_DCS_TEAR_MODE_VBLANK
);
99 DRM_DEV_ERROR(s6
->dev
, "failed to enable vblank TE (%d)\n",
103 /* Exit sleep mode and power on */
104 ret
= mipi_dsi_dcs_exit_sleep_mode(dsi
);
106 DRM_DEV_ERROR(s6
->dev
, "failed to exit sleep mode (%d)\n",
114 static int s6d16d0_enable(struct drm_panel
*panel
)
116 struct s6d16d0
*s6
= panel_to_s6d16d0(panel
);
117 struct mipi_dsi_device
*dsi
= to_mipi_dsi_device(s6
->dev
);
120 ret
= mipi_dsi_dcs_set_display_on(dsi
);
122 DRM_DEV_ERROR(s6
->dev
, "failed to turn display on (%d)\n",
130 static int s6d16d0_disable(struct drm_panel
*panel
)
132 struct s6d16d0
*s6
= panel_to_s6d16d0(panel
);
133 struct mipi_dsi_device
*dsi
= to_mipi_dsi_device(s6
->dev
);
136 ret
= mipi_dsi_dcs_set_display_off(dsi
);
138 DRM_DEV_ERROR(s6
->dev
, "failed to turn display off (%d)\n",
146 static int s6d16d0_get_modes(struct drm_panel
*panel
)
148 struct drm_connector
*connector
= panel
->connector
;
149 struct drm_display_mode
*mode
;
151 strncpy(connector
->display_info
.name
, "Samsung S6D16D0\0",
152 DRM_DISPLAY_INFO_LEN
);
154 mode
= drm_mode_duplicate(panel
->drm
, &samsung_s6d16d0_mode
);
156 DRM_ERROR("bad mode or failed to add mode\n");
159 drm_mode_set_name(mode
);
160 mode
->type
= DRM_MODE_TYPE_DRIVER
| DRM_MODE_TYPE_PREFERRED
;
162 connector
->display_info
.width_mm
= mode
->width_mm
;
163 connector
->display_info
.height_mm
= mode
->height_mm
;
165 drm_mode_probed_add(connector
, mode
);
167 return 1; /* Number of modes */
170 static const struct drm_panel_funcs s6d16d0_drm_funcs
= {
171 .disable
= s6d16d0_disable
,
172 .unprepare
= s6d16d0_unprepare
,
173 .prepare
= s6d16d0_prepare
,
174 .enable
= s6d16d0_enable
,
175 .get_modes
= s6d16d0_get_modes
,
178 static int s6d16d0_probe(struct mipi_dsi_device
*dsi
)
180 struct device
*dev
= &dsi
->dev
;
184 s6
= devm_kzalloc(dev
, sizeof(struct s6d16d0
), GFP_KERNEL
);
188 mipi_dsi_set_drvdata(dsi
, s6
);
192 dsi
->format
= MIPI_DSI_FMT_RGB888
;
193 dsi
->hs_rate
= 420160000;
194 dsi
->lp_rate
= 19200000;
196 * This display uses command mode so no MIPI_DSI_MODE_VIDEO
197 * or MIPI_DSI_MODE_VIDEO_SYNC_PULSE
199 * As we only send commands we do not need to be continuously
203 MIPI_DSI_CLOCK_NON_CONTINUOUS
|
204 MIPI_DSI_MODE_EOT_PACKET
;
206 s6
->supply
= devm_regulator_get(dev
, "vdd1");
207 if (IS_ERR(s6
->supply
))
208 return PTR_ERR(s6
->supply
);
210 /* This asserts RESET by default */
211 s6
->reset_gpio
= devm_gpiod_get_optional(dev
, "reset",
213 if (IS_ERR(s6
->reset_gpio
)) {
214 ret
= PTR_ERR(s6
->reset_gpio
);
215 if (ret
!= -EPROBE_DEFER
)
216 DRM_DEV_ERROR(dev
, "failed to request GPIO (%d)\n",
221 drm_panel_init(&s6
->panel
);
223 s6
->panel
.funcs
= &s6d16d0_drm_funcs
;
225 ret
= drm_panel_add(&s6
->panel
);
229 ret
= mipi_dsi_attach(dsi
);
231 drm_panel_remove(&s6
->panel
);
236 static int s6d16d0_remove(struct mipi_dsi_device
*dsi
)
238 struct s6d16d0
*s6
= mipi_dsi_get_drvdata(dsi
);
240 mipi_dsi_detach(dsi
);
241 drm_panel_remove(&s6
->panel
);
246 static const struct of_device_id s6d16d0_of_match
[] = {
247 { .compatible
= "samsung,s6d16d0" },
250 MODULE_DEVICE_TABLE(of
, s6d16d0_of_match
);
252 static struct mipi_dsi_driver s6d16d0_driver
= {
253 .probe
= s6d16d0_probe
,
254 .remove
= s6d16d0_remove
,
256 .name
= "panel-samsung-s6d16d0",
257 .of_match_table
= s6d16d0_of_match
,
260 module_mipi_dsi_driver(s6d16d0_driver
);
262 MODULE_AUTHOR("Linus Wallei <linus.walleij@linaro.org>");
263 MODULE_DESCRIPTION("MIPI-DSI s6d16d0 Panel Driver");
264 MODULE_LICENSE("GPL v2");