1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2015 Red Hat |
4 | * Copyright (C) 2015 Sony Mobile Communications Inc. |
5 | * Author: Werner Johansson <werner.johansson@sonymobile.com> |
6 | * |
7 | * Based on AUO panel driver by Rob Clark <robdclark@gmail.com> |
8 | */ |
9 | |
10 | #include <linux/delay.h> |
11 | #include <linux/gpio/consumer.h> |
12 | #include <linux/module.h> |
13 | #include <linux/of.h> |
14 | #include <linux/regulator/consumer.h> |
15 | |
16 | #include <video/mipi_display.h> |
17 | |
18 | #include <drm/drm_crtc.h> |
19 | #include <drm/drm_device.h> |
20 | #include <drm/drm_mipi_dsi.h> |
21 | #include <drm/drm_panel.h> |
22 | |
23 | struct sharp_nt_panel { |
24 | struct drm_panel base; |
25 | struct mipi_dsi_device *dsi; |
26 | |
27 | struct regulator *supply; |
28 | struct gpio_desc *reset_gpio; |
29 | |
30 | bool prepared; |
31 | }; |
32 | |
33 | static inline struct sharp_nt_panel *to_sharp_nt_panel(struct drm_panel *panel) |
34 | { |
35 | return container_of(panel, struct sharp_nt_panel, base); |
36 | } |
37 | |
38 | static int sharp_nt_panel_init(struct sharp_nt_panel *sharp_nt) |
39 | { |
40 | struct mipi_dsi_device *dsi = sharp_nt->dsi; |
41 | int ret; |
42 | |
43 | dsi->mode_flags |= MIPI_DSI_MODE_LPM; |
44 | |
45 | ret = mipi_dsi_dcs_exit_sleep_mode(dsi); |
46 | if (ret < 0) |
47 | return ret; |
48 | |
49 | msleep(msecs: 120); |
50 | |
51 | /* Novatek two-lane operation */ |
52 | ret = mipi_dsi_dcs_write(dsi, cmd: 0xae, data: (u8[]){ 0x03 }, len: 1); |
53 | if (ret < 0) |
54 | return ret; |
55 | |
56 | /* Set both MCU and RGB I/F to 24bpp */ |
57 | ret = mipi_dsi_dcs_set_pixel_format(dsi, MIPI_DCS_PIXEL_FMT_24BIT | |
58 | (MIPI_DCS_PIXEL_FMT_24BIT << 4)); |
59 | if (ret < 0) |
60 | return ret; |
61 | |
62 | return 0; |
63 | } |
64 | |
65 | static int sharp_nt_panel_on(struct sharp_nt_panel *sharp_nt) |
66 | { |
67 | struct mipi_dsi_device *dsi = sharp_nt->dsi; |
68 | int ret; |
69 | |
70 | dsi->mode_flags |= MIPI_DSI_MODE_LPM; |
71 | |
72 | ret = mipi_dsi_dcs_set_display_on(dsi); |
73 | if (ret < 0) |
74 | return ret; |
75 | |
76 | return 0; |
77 | } |
78 | |
79 | static int sharp_nt_panel_off(struct sharp_nt_panel *sharp_nt) |
80 | { |
81 | struct mipi_dsi_device *dsi = sharp_nt->dsi; |
82 | int ret; |
83 | |
84 | dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; |
85 | |
86 | ret = mipi_dsi_dcs_set_display_off(dsi); |
87 | if (ret < 0) |
88 | return ret; |
89 | |
90 | ret = mipi_dsi_dcs_enter_sleep_mode(dsi); |
91 | if (ret < 0) |
92 | return ret; |
93 | |
94 | return 0; |
95 | } |
96 | |
97 | static int sharp_nt_panel_unprepare(struct drm_panel *panel) |
98 | { |
99 | struct sharp_nt_panel *sharp_nt = to_sharp_nt_panel(panel); |
100 | int ret; |
101 | |
102 | if (!sharp_nt->prepared) |
103 | return 0; |
104 | |
105 | ret = sharp_nt_panel_off(sharp_nt); |
106 | if (ret < 0) { |
107 | dev_err(panel->dev, "failed to set panel off: %d\n" , ret); |
108 | return ret; |
109 | } |
110 | |
111 | regulator_disable(regulator: sharp_nt->supply); |
112 | if (sharp_nt->reset_gpio) |
113 | gpiod_set_value(desc: sharp_nt->reset_gpio, value: 0); |
114 | |
115 | sharp_nt->prepared = false; |
116 | |
117 | return 0; |
118 | } |
119 | |
120 | static int sharp_nt_panel_prepare(struct drm_panel *panel) |
121 | { |
122 | struct sharp_nt_panel *sharp_nt = to_sharp_nt_panel(panel); |
123 | int ret; |
124 | |
125 | if (sharp_nt->prepared) |
126 | return 0; |
127 | |
128 | ret = regulator_enable(regulator: sharp_nt->supply); |
129 | if (ret < 0) |
130 | return ret; |
131 | |
132 | msleep(msecs: 20); |
133 | |
134 | if (sharp_nt->reset_gpio) { |
135 | gpiod_set_value(desc: sharp_nt->reset_gpio, value: 1); |
136 | msleep(msecs: 1); |
137 | gpiod_set_value(desc: sharp_nt->reset_gpio, value: 0); |
138 | msleep(msecs: 1); |
139 | gpiod_set_value(desc: sharp_nt->reset_gpio, value: 1); |
140 | msleep(msecs: 10); |
141 | } |
142 | |
143 | ret = sharp_nt_panel_init(sharp_nt); |
144 | if (ret < 0) { |
145 | dev_err(panel->dev, "failed to init panel: %d\n" , ret); |
146 | goto poweroff; |
147 | } |
148 | |
149 | ret = sharp_nt_panel_on(sharp_nt); |
150 | if (ret < 0) { |
151 | dev_err(panel->dev, "failed to set panel on: %d\n" , ret); |
152 | goto poweroff; |
153 | } |
154 | |
155 | sharp_nt->prepared = true; |
156 | |
157 | return 0; |
158 | |
159 | poweroff: |
160 | regulator_disable(regulator: sharp_nt->supply); |
161 | if (sharp_nt->reset_gpio) |
162 | gpiod_set_value(desc: sharp_nt->reset_gpio, value: 0); |
163 | return ret; |
164 | } |
165 | |
166 | static const struct drm_display_mode default_mode = { |
167 | .clock = (540 + 48 + 32 + 80) * (960 + 3 + 10 + 15) * 60 / 1000, |
168 | .hdisplay = 540, |
169 | .hsync_start = 540 + 48, |
170 | .hsync_end = 540 + 48 + 32, |
171 | .htotal = 540 + 48 + 32 + 80, |
172 | .vdisplay = 960, |
173 | .vsync_start = 960 + 3, |
174 | .vsync_end = 960 + 3 + 10, |
175 | .vtotal = 960 + 3 + 10 + 15, |
176 | }; |
177 | |
178 | static int sharp_nt_panel_get_modes(struct drm_panel *panel, |
179 | struct drm_connector *connector) |
180 | { |
181 | struct drm_display_mode *mode; |
182 | |
183 | mode = drm_mode_duplicate(dev: connector->dev, mode: &default_mode); |
184 | if (!mode) { |
185 | dev_err(panel->dev, "failed to add mode %ux%u@%u\n" , |
186 | default_mode.hdisplay, default_mode.vdisplay, |
187 | drm_mode_vrefresh(&default_mode)); |
188 | return -ENOMEM; |
189 | } |
190 | |
191 | drm_mode_set_name(mode); |
192 | |
193 | drm_mode_probed_add(connector, mode); |
194 | |
195 | connector->display_info.width_mm = 54; |
196 | connector->display_info.height_mm = 95; |
197 | |
198 | return 1; |
199 | } |
200 | |
201 | static const struct drm_panel_funcs sharp_nt_panel_funcs = { |
202 | .unprepare = sharp_nt_panel_unprepare, |
203 | .prepare = sharp_nt_panel_prepare, |
204 | .get_modes = sharp_nt_panel_get_modes, |
205 | }; |
206 | |
207 | static int sharp_nt_panel_add(struct sharp_nt_panel *sharp_nt) |
208 | { |
209 | struct device *dev = &sharp_nt->dsi->dev; |
210 | int ret; |
211 | |
212 | sharp_nt->supply = devm_regulator_get(dev, id: "avdd" ); |
213 | if (IS_ERR(ptr: sharp_nt->supply)) |
214 | return PTR_ERR(ptr: sharp_nt->supply); |
215 | |
216 | sharp_nt->reset_gpio = devm_gpiod_get(dev, con_id: "reset" , flags: GPIOD_OUT_LOW); |
217 | if (IS_ERR(ptr: sharp_nt->reset_gpio)) { |
218 | dev_err(dev, "cannot get reset-gpios %ld\n" , |
219 | PTR_ERR(sharp_nt->reset_gpio)); |
220 | sharp_nt->reset_gpio = NULL; |
221 | } else { |
222 | gpiod_set_value(desc: sharp_nt->reset_gpio, value: 0); |
223 | } |
224 | |
225 | drm_panel_init(panel: &sharp_nt->base, dev: &sharp_nt->dsi->dev, |
226 | funcs: &sharp_nt_panel_funcs, DRM_MODE_CONNECTOR_DSI); |
227 | |
228 | ret = drm_panel_of_backlight(panel: &sharp_nt->base); |
229 | if (ret) |
230 | return ret; |
231 | |
232 | drm_panel_add(panel: &sharp_nt->base); |
233 | |
234 | return 0; |
235 | } |
236 | |
237 | static void sharp_nt_panel_del(struct sharp_nt_panel *sharp_nt) |
238 | { |
239 | if (sharp_nt->base.dev) |
240 | drm_panel_remove(panel: &sharp_nt->base); |
241 | } |
242 | |
243 | static int sharp_nt_panel_probe(struct mipi_dsi_device *dsi) |
244 | { |
245 | struct sharp_nt_panel *sharp_nt; |
246 | int ret; |
247 | |
248 | dsi->lanes = 2; |
249 | dsi->format = MIPI_DSI_FMT_RGB888; |
250 | dsi->mode_flags = MIPI_DSI_MODE_VIDEO | |
251 | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | |
252 | MIPI_DSI_MODE_VIDEO_HSE | |
253 | MIPI_DSI_CLOCK_NON_CONTINUOUS | |
254 | MIPI_DSI_MODE_NO_EOT_PACKET; |
255 | |
256 | sharp_nt = devm_kzalloc(dev: &dsi->dev, size: sizeof(*sharp_nt), GFP_KERNEL); |
257 | if (!sharp_nt) |
258 | return -ENOMEM; |
259 | |
260 | mipi_dsi_set_drvdata(dsi, data: sharp_nt); |
261 | |
262 | sharp_nt->dsi = dsi; |
263 | |
264 | ret = sharp_nt_panel_add(sharp_nt); |
265 | if (ret < 0) |
266 | return ret; |
267 | |
268 | ret = mipi_dsi_attach(dsi); |
269 | if (ret < 0) { |
270 | sharp_nt_panel_del(sharp_nt); |
271 | return ret; |
272 | } |
273 | |
274 | return 0; |
275 | } |
276 | |
277 | static void sharp_nt_panel_remove(struct mipi_dsi_device *dsi) |
278 | { |
279 | struct sharp_nt_panel *sharp_nt = mipi_dsi_get_drvdata(dsi); |
280 | int ret; |
281 | |
282 | ret = drm_panel_disable(panel: &sharp_nt->base); |
283 | if (ret < 0) |
284 | dev_err(&dsi->dev, "failed to disable panel: %d\n" , ret); |
285 | |
286 | ret = mipi_dsi_detach(dsi); |
287 | if (ret < 0) |
288 | dev_err(&dsi->dev, "failed to detach from DSI host: %d\n" , ret); |
289 | |
290 | sharp_nt_panel_del(sharp_nt); |
291 | } |
292 | |
293 | static void sharp_nt_panel_shutdown(struct mipi_dsi_device *dsi) |
294 | { |
295 | struct sharp_nt_panel *sharp_nt = mipi_dsi_get_drvdata(dsi); |
296 | |
297 | drm_panel_disable(panel: &sharp_nt->base); |
298 | } |
299 | |
300 | static const struct of_device_id sharp_nt_of_match[] = { |
301 | { .compatible = "sharp,ls043t1le01-qhd" , }, |
302 | { } |
303 | }; |
304 | MODULE_DEVICE_TABLE(of, sharp_nt_of_match); |
305 | |
306 | static struct mipi_dsi_driver sharp_nt_panel_driver = { |
307 | .driver = { |
308 | .name = "panel-sharp-ls043t1le01-qhd" , |
309 | .of_match_table = sharp_nt_of_match, |
310 | }, |
311 | .probe = sharp_nt_panel_probe, |
312 | .remove = sharp_nt_panel_remove, |
313 | .shutdown = sharp_nt_panel_shutdown, |
314 | }; |
315 | module_mipi_dsi_driver(sharp_nt_panel_driver); |
316 | |
317 | MODULE_AUTHOR("Werner Johansson <werner.johansson@sonymobile.com>" ); |
318 | MODULE_DESCRIPTION("Sharp LS043T1LE01 NT35565-based qHD (540x960) video mode panel driver" ); |
319 | MODULE_LICENSE("GPL v2" ); |
320 | |