Loading...
Note: File does not exist in v3.1.
1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * MIPI-DSI Samsung s6d16d0 panel driver. This is a 864x480
4 * AMOLED panel with a command-only DSI interface.
5 */
6
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>
11
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>
17
18struct s6d16d0 {
19 struct device *dev;
20 struct drm_panel panel;
21 struct regulator *supply;
22 struct gpio_desc *reset_gpio;
23};
24
25/*
26 * The timings are not very helpful as the display is used in
27 * command mode.
28 */
29static const struct drm_display_mode samsung_s6d16d0_mode = {
30 /* HS clock, (htotal*vtotal*vrefresh)/1000 */
31 .clock = 420160,
32 .hdisplay = 864,
33 .hsync_start = 864 + 154,
34 .hsync_end = 864 + 154 + 16,
35 .htotal = 864 + 154 + 16 + 32,
36 .vdisplay = 480,
37 .vsync_start = 480 + 1,
38 .vsync_end = 480 + 1 + 1,
39 .vtotal = 480 + 1 + 1 + 1,
40 .width_mm = 84,
41 .height_mm = 48,
42};
43
44static inline struct s6d16d0 *panel_to_s6d16d0(struct drm_panel *panel)
45{
46 return container_of(panel, struct s6d16d0, panel);
47}
48
49static int s6d16d0_unprepare(struct drm_panel *panel)
50{
51 struct s6d16d0 *s6 = panel_to_s6d16d0(panel);
52 struct mipi_dsi_device *dsi = to_mipi_dsi_device(s6->dev);
53 int ret;
54
55 /* Enter sleep mode */
56 ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
57 if (ret) {
58 DRM_DEV_ERROR(s6->dev, "failed to enter sleep mode (%d)\n",
59 ret);
60 return ret;
61 }
62
63 /* Assert RESET */
64 gpiod_set_value_cansleep(s6->reset_gpio, 1);
65 regulator_disable(s6->supply);
66
67 return 0;
68}
69
70static int s6d16d0_prepare(struct drm_panel *panel)
71{
72 struct s6d16d0 *s6 = panel_to_s6d16d0(panel);
73 struct mipi_dsi_device *dsi = to_mipi_dsi_device(s6->dev);
74 int ret;
75
76 ret = regulator_enable(s6->supply);
77 if (ret) {
78 DRM_DEV_ERROR(s6->dev, "failed to enable supply (%d)\n", ret);
79 return ret;
80 }
81
82 /* Assert RESET */
83 gpiod_set_value_cansleep(s6->reset_gpio, 1);
84 udelay(10);
85 /* De-assert RESET */
86 gpiod_set_value_cansleep(s6->reset_gpio, 0);
87 msleep(120);
88
89 /* Enabe tearing mode: send TE (tearing effect) at VBLANK */
90 ret = mipi_dsi_dcs_set_tear_on(dsi,
91 MIPI_DSI_DCS_TEAR_MODE_VBLANK);
92 if (ret) {
93 DRM_DEV_ERROR(s6->dev, "failed to enable vblank TE (%d)\n",
94 ret);
95 return ret;
96 }
97 /* Exit sleep mode and power on */
98 ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
99 if (ret) {
100 DRM_DEV_ERROR(s6->dev, "failed to exit sleep mode (%d)\n",
101 ret);
102 return ret;
103 }
104
105 return 0;
106}
107
108static int s6d16d0_enable(struct drm_panel *panel)
109{
110 struct s6d16d0 *s6 = panel_to_s6d16d0(panel);
111 struct mipi_dsi_device *dsi = to_mipi_dsi_device(s6->dev);
112 int ret;
113
114 ret = mipi_dsi_dcs_set_display_on(dsi);
115 if (ret) {
116 DRM_DEV_ERROR(s6->dev, "failed to turn display on (%d)\n",
117 ret);
118 return ret;
119 }
120
121 return 0;
122}
123
124static int s6d16d0_disable(struct drm_panel *panel)
125{
126 struct s6d16d0 *s6 = panel_to_s6d16d0(panel);
127 struct mipi_dsi_device *dsi = to_mipi_dsi_device(s6->dev);
128 int ret;
129
130 ret = mipi_dsi_dcs_set_display_off(dsi);
131 if (ret) {
132 DRM_DEV_ERROR(s6->dev, "failed to turn display off (%d)\n",
133 ret);
134 return ret;
135 }
136
137 return 0;
138}
139
140static int s6d16d0_get_modes(struct drm_panel *panel,
141 struct drm_connector *connector)
142{
143 struct drm_display_mode *mode;
144
145 mode = drm_mode_duplicate(connector->dev, &samsung_s6d16d0_mode);
146 if (!mode) {
147 DRM_ERROR("bad mode or failed to add mode\n");
148 return -EINVAL;
149 }
150 drm_mode_set_name(mode);
151 mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
152
153 connector->display_info.width_mm = mode->width_mm;
154 connector->display_info.height_mm = mode->height_mm;
155
156 drm_mode_probed_add(connector, mode);
157
158 return 1; /* Number of modes */
159}
160
161static const struct drm_panel_funcs s6d16d0_drm_funcs = {
162 .disable = s6d16d0_disable,
163 .unprepare = s6d16d0_unprepare,
164 .prepare = s6d16d0_prepare,
165 .enable = s6d16d0_enable,
166 .get_modes = s6d16d0_get_modes,
167};
168
169static int s6d16d0_probe(struct mipi_dsi_device *dsi)
170{
171 struct device *dev = &dsi->dev;
172 struct s6d16d0 *s6;
173 int ret;
174
175 s6 = devm_kzalloc(dev, sizeof(struct s6d16d0), GFP_KERNEL);
176 if (!s6)
177 return -ENOMEM;
178
179 mipi_dsi_set_drvdata(dsi, s6);
180 s6->dev = dev;
181
182 dsi->lanes = 2;
183 dsi->format = MIPI_DSI_FMT_RGB888;
184 dsi->hs_rate = 420160000;
185 dsi->lp_rate = 19200000;
186 /*
187 * This display uses command mode so no MIPI_DSI_MODE_VIDEO
188 * or MIPI_DSI_MODE_VIDEO_SYNC_PULSE
189 *
190 * As we only send commands we do not need to be continuously
191 * clocked.
192 */
193 dsi->mode_flags =
194 MIPI_DSI_CLOCK_NON_CONTINUOUS |
195 MIPI_DSI_MODE_EOT_PACKET;
196
197 s6->supply = devm_regulator_get(dev, "vdd1");
198 if (IS_ERR(s6->supply))
199 return PTR_ERR(s6->supply);
200
201 /* This asserts RESET by default */
202 s6->reset_gpio = devm_gpiod_get_optional(dev, "reset",
203 GPIOD_OUT_HIGH);
204 if (IS_ERR(s6->reset_gpio)) {
205 ret = PTR_ERR(s6->reset_gpio);
206 if (ret != -EPROBE_DEFER)
207 DRM_DEV_ERROR(dev, "failed to request GPIO (%d)\n",
208 ret);
209 return ret;
210 }
211
212 drm_panel_init(&s6->panel, dev, &s6d16d0_drm_funcs,
213 DRM_MODE_CONNECTOR_DSI);
214
215 ret = drm_panel_add(&s6->panel);
216 if (ret < 0)
217 return ret;
218
219 ret = mipi_dsi_attach(dsi);
220 if (ret < 0)
221 drm_panel_remove(&s6->panel);
222
223 return ret;
224}
225
226static int s6d16d0_remove(struct mipi_dsi_device *dsi)
227{
228 struct s6d16d0 *s6 = mipi_dsi_get_drvdata(dsi);
229
230 mipi_dsi_detach(dsi);
231 drm_panel_remove(&s6->panel);
232
233 return 0;
234}
235
236static const struct of_device_id s6d16d0_of_match[] = {
237 { .compatible = "samsung,s6d16d0" },
238 { }
239};
240MODULE_DEVICE_TABLE(of, s6d16d0_of_match);
241
242static struct mipi_dsi_driver s6d16d0_driver = {
243 .probe = s6d16d0_probe,
244 .remove = s6d16d0_remove,
245 .driver = {
246 .name = "panel-samsung-s6d16d0",
247 .of_match_table = s6d16d0_of_match,
248 },
249};
250module_mipi_dsi_driver(s6d16d0_driver);
251
252MODULE_AUTHOR("Linus Wallei <linus.walleij@linaro.org>");
253MODULE_DESCRIPTION("MIPI-DSI s6d16d0 Panel Driver");
254MODULE_LICENSE("GPL v2");