1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (C) 2016 InforceComputing |
4 | * Copyright (C) 2016 Linaro Ltd |
5 | * Copyright (C) 2023 BayLibre, SAS |
6 | * |
7 | * Authors: |
8 | * - Vinay Simha BN <simhavcs@gmail.com> |
9 | * - Sumit Semwal <sumit.semwal@linaro.org> |
10 | * - Guillaume La Roque <glaroque@baylibre.com> |
11 | * |
12 | */ |
13 | |
14 | #include <linux/backlight.h> |
15 | #include <linux/delay.h> |
16 | #include <linux/gpio/consumer.h> |
17 | #include <linux/module.h> |
18 | #include <linux/of.h> |
19 | #include <linux/regulator/consumer.h> |
20 | |
21 | #include <video/mipi_display.h> |
22 | |
23 | #include <drm/drm_mipi_dsi.h> |
24 | #include <drm/drm_modes.h> |
25 | #include <drm/drm_panel.h> |
26 | |
27 | #define DSI_REG_MCAP 0xB0 |
28 | #define DSI_REG_IS 0xB3 /* Interface Setting */ |
29 | #define DSI_REG_IIS 0xB4 /* Interface ID Setting */ |
30 | #define DSI_REG_CTRL 0xB6 |
31 | |
32 | enum { |
33 | IOVCC = 0, |
34 | POWER = 1 |
35 | }; |
36 | |
37 | struct stk_panel { |
38 | const struct drm_display_mode *mode; |
39 | struct backlight_device *backlight; |
40 | struct drm_panel base; |
41 | struct gpio_desc *enable_gpio; /* Power IC supply enable */ |
42 | struct gpio_desc *reset_gpio; /* External reset */ |
43 | struct mipi_dsi_device *dsi; |
44 | struct regulator_bulk_data supplies[2]; |
45 | }; |
46 | |
47 | static inline struct stk_panel *to_stk_panel(struct drm_panel *panel) |
48 | { |
49 | return container_of(panel, struct stk_panel, base); |
50 | } |
51 | |
52 | static int stk_panel_init(struct stk_panel *stk) |
53 | { |
54 | struct mipi_dsi_device *dsi = stk->dsi; |
55 | struct device *dev = &stk->dsi->dev; |
56 | int ret; |
57 | |
58 | ret = mipi_dsi_dcs_soft_reset(dsi); |
59 | if (ret < 0) { |
60 | dev_err(dev, "failed to mipi_dsi_dcs_soft_reset: %d\n" , ret); |
61 | return ret; |
62 | } |
63 | mdelay(5); |
64 | |
65 | ret = mipi_dsi_dcs_exit_sleep_mode(dsi); |
66 | if (ret < 0) { |
67 | dev_err(dev, "failed to set exit sleep mode: %d\n" , ret); |
68 | return ret; |
69 | } |
70 | msleep(msecs: 120); |
71 | |
72 | mipi_dsi_generic_write_seq(dsi, DSI_REG_MCAP, 0x04); |
73 | |
74 | /* Interface setting, video mode */ |
75 | mipi_dsi_generic_write_seq(dsi, DSI_REG_IS, 0x14, 0x08, 0x00, 0x22, 0x00); |
76 | mipi_dsi_generic_write_seq(dsi, DSI_REG_IIS, 0x0C, 0x00); |
77 | mipi_dsi_generic_write_seq(dsi, DSI_REG_CTRL, 0x3A, 0xD3); |
78 | |
79 | ret = mipi_dsi_dcs_set_display_brightness(dsi, brightness: 0x77); |
80 | if (ret < 0) { |
81 | dev_err(dev, "failed to write display brightness: %d\n" , ret); |
82 | return ret; |
83 | } |
84 | |
85 | mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_WRITE_CONTROL_DISPLAY, |
86 | MIPI_DCS_WRITE_MEMORY_START); |
87 | |
88 | ret = mipi_dsi_dcs_set_pixel_format(dsi, format: 0x77); |
89 | if (ret < 0) { |
90 | dev_err(dev, "failed to set pixel format: %d\n" , ret); |
91 | return ret; |
92 | } |
93 | |
94 | ret = mipi_dsi_dcs_set_column_address(dsi, start: 0, end: stk->mode->hdisplay - 1); |
95 | if (ret < 0) { |
96 | dev_err(dev, "failed to set column address: %d\n" , ret); |
97 | return ret; |
98 | } |
99 | |
100 | ret = mipi_dsi_dcs_set_page_address(dsi, start: 0, end: stk->mode->vdisplay - 1); |
101 | if (ret < 0) { |
102 | dev_err(dev, "failed to set page address: %d\n" , ret); |
103 | return ret; |
104 | } |
105 | |
106 | return 0; |
107 | } |
108 | |
109 | static int stk_panel_on(struct stk_panel *stk) |
110 | { |
111 | struct mipi_dsi_device *dsi = stk->dsi; |
112 | struct device *dev = &stk->dsi->dev; |
113 | int ret; |
114 | |
115 | ret = mipi_dsi_dcs_set_display_on(dsi); |
116 | if (ret < 0) |
117 | dev_err(dev, "failed to set display on: %d\n" , ret); |
118 | |
119 | mdelay(20); |
120 | |
121 | return ret; |
122 | } |
123 | |
124 | static void stk_panel_off(struct stk_panel *stk) |
125 | { |
126 | struct mipi_dsi_device *dsi = stk->dsi; |
127 | struct device *dev = &stk->dsi->dev; |
128 | int ret; |
129 | |
130 | dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; |
131 | |
132 | ret = mipi_dsi_dcs_set_display_off(dsi); |
133 | if (ret < 0) |
134 | dev_err(dev, "failed to set display off: %d\n" , ret); |
135 | |
136 | ret = mipi_dsi_dcs_enter_sleep_mode(dsi); |
137 | if (ret < 0) |
138 | dev_err(dev, "failed to enter sleep mode: %d\n" , ret); |
139 | |
140 | msleep(msecs: 100); |
141 | } |
142 | |
143 | static int stk_panel_unprepare(struct drm_panel *panel) |
144 | { |
145 | struct stk_panel *stk = to_stk_panel(panel); |
146 | |
147 | stk_panel_off(stk); |
148 | regulator_bulk_disable(ARRAY_SIZE(stk->supplies), consumers: stk->supplies); |
149 | gpiod_set_value(desc: stk->reset_gpio, value: 0); |
150 | gpiod_set_value(desc: stk->enable_gpio, value: 1); |
151 | |
152 | return 0; |
153 | } |
154 | |
155 | static int stk_panel_prepare(struct drm_panel *panel) |
156 | { |
157 | struct stk_panel *stk = to_stk_panel(panel); |
158 | struct device *dev = &stk->dsi->dev; |
159 | int ret; |
160 | |
161 | gpiod_set_value(desc: stk->reset_gpio, value: 0); |
162 | gpiod_set_value(desc: stk->enable_gpio, value: 0); |
163 | ret = regulator_enable(regulator: stk->supplies[IOVCC].consumer); |
164 | if (ret < 0) |
165 | return ret; |
166 | |
167 | mdelay(8); |
168 | ret = regulator_enable(regulator: stk->supplies[POWER].consumer); |
169 | if (ret < 0) |
170 | goto iovccoff; |
171 | |
172 | mdelay(20); |
173 | gpiod_set_value(desc: stk->enable_gpio, value: 1); |
174 | mdelay(20); |
175 | gpiod_set_value(desc: stk->reset_gpio, value: 1); |
176 | mdelay(10); |
177 | ret = stk_panel_init(stk); |
178 | if (ret < 0) { |
179 | dev_err(dev, "failed to init panel: %d\n" , ret); |
180 | goto poweroff; |
181 | } |
182 | |
183 | ret = stk_panel_on(stk); |
184 | if (ret < 0) { |
185 | dev_err(dev, "failed to set panel on: %d\n" , ret); |
186 | goto poweroff; |
187 | } |
188 | |
189 | return 0; |
190 | |
191 | poweroff: |
192 | regulator_disable(regulator: stk->supplies[POWER].consumer); |
193 | iovccoff: |
194 | regulator_disable(regulator: stk->supplies[IOVCC].consumer); |
195 | gpiod_set_value(desc: stk->reset_gpio, value: 0); |
196 | gpiod_set_value(desc: stk->enable_gpio, value: 0); |
197 | |
198 | return ret; |
199 | } |
200 | |
201 | static const struct drm_display_mode default_mode = { |
202 | .clock = 163204, |
203 | .hdisplay = 1200, |
204 | .hsync_start = 1200 + 144, |
205 | .hsync_end = 1200 + 144 + 16, |
206 | .htotal = 1200 + 144 + 16 + 45, |
207 | .vdisplay = 1920, |
208 | .vsync_start = 1920 + 8, |
209 | .vsync_end = 1920 + 8 + 4, |
210 | .vtotal = 1920 + 8 + 4 + 4, |
211 | .width_mm = 95, |
212 | .height_mm = 151, |
213 | }; |
214 | |
215 | static int stk_panel_get_modes(struct drm_panel *panel, |
216 | struct drm_connector *connector) |
217 | { |
218 | struct drm_display_mode *mode; |
219 | |
220 | mode = drm_mode_duplicate(dev: connector->dev, mode: &default_mode); |
221 | if (!mode) { |
222 | dev_err(panel->dev, "failed to add mode %ux%ux@%u\n" , |
223 | default_mode.hdisplay, default_mode.vdisplay, |
224 | drm_mode_vrefresh(&default_mode)); |
225 | return -ENOMEM; |
226 | } |
227 | |
228 | drm_mode_set_name(mode); |
229 | drm_mode_probed_add(connector, mode); |
230 | connector->display_info.width_mm = default_mode.width_mm; |
231 | connector->display_info.height_mm = default_mode.height_mm; |
232 | return 1; |
233 | } |
234 | |
235 | static int dsi_dcs_bl_get_brightness(struct backlight_device *bl) |
236 | { |
237 | struct mipi_dsi_device *dsi = bl_get_data(bl_dev: bl); |
238 | int ret; |
239 | u16 brightness; |
240 | |
241 | dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; |
242 | ret = mipi_dsi_dcs_get_display_brightness(dsi, brightness: &brightness); |
243 | if (ret < 0) |
244 | return ret; |
245 | |
246 | dsi->mode_flags |= MIPI_DSI_MODE_LPM; |
247 | return brightness & 0xff; |
248 | } |
249 | |
250 | static int dsi_dcs_bl_update_status(struct backlight_device *bl) |
251 | { |
252 | struct mipi_dsi_device *dsi = bl_get_data(bl_dev: bl); |
253 | struct device *dev = &dsi->dev; |
254 | int ret; |
255 | |
256 | dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; |
257 | ret = mipi_dsi_dcs_set_display_brightness(dsi, brightness: bl->props.brightness); |
258 | if (ret < 0) { |
259 | dev_err(dev, "failed to set DSI control: %d\n" , ret); |
260 | return ret; |
261 | } |
262 | |
263 | dsi->mode_flags |= MIPI_DSI_MODE_LPM; |
264 | return 0; |
265 | } |
266 | |
267 | static const struct backlight_ops dsi_bl_ops = { |
268 | .update_status = dsi_dcs_bl_update_status, |
269 | .get_brightness = dsi_dcs_bl_get_brightness, |
270 | }; |
271 | |
272 | static struct backlight_device * |
273 | drm_panel_create_dsi_backlight(struct mipi_dsi_device *dsi) |
274 | { |
275 | struct device *dev = &dsi->dev; |
276 | struct backlight_properties props = { |
277 | .type = BACKLIGHT_RAW, |
278 | .brightness = 255, |
279 | .max_brightness = 255, |
280 | }; |
281 | |
282 | return devm_backlight_device_register(dev, name: dev_name(dev), parent: dev, devdata: dsi, |
283 | ops: &dsi_bl_ops, props: &props); |
284 | } |
285 | |
286 | static const struct drm_panel_funcs stk_panel_funcs = { |
287 | .unprepare = stk_panel_unprepare, |
288 | .prepare = stk_panel_prepare, |
289 | .get_modes = stk_panel_get_modes, |
290 | }; |
291 | |
292 | static const struct of_device_id stk_of_match[] = { |
293 | { .compatible = "startek,kd070fhfid015" , }, |
294 | { } |
295 | }; |
296 | MODULE_DEVICE_TABLE(of, stk_of_match); |
297 | |
298 | static int stk_panel_add(struct stk_panel *stk) |
299 | { |
300 | struct device *dev = &stk->dsi->dev; |
301 | int ret; |
302 | |
303 | stk->mode = &default_mode; |
304 | |
305 | stk->supplies[IOVCC].supply = "iovcc" ; |
306 | stk->supplies[POWER].supply = "power" ; |
307 | ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(stk->supplies), consumers: stk->supplies); |
308 | if (ret) { |
309 | dev_err(dev, "regulator_bulk failed\n" ); |
310 | return ret; |
311 | } |
312 | |
313 | stk->reset_gpio = devm_gpiod_get(dev, con_id: "reset" , flags: GPIOD_OUT_LOW); |
314 | if (IS_ERR(ptr: stk->reset_gpio)) { |
315 | ret = PTR_ERR(ptr: stk->reset_gpio); |
316 | dev_err(dev, "cannot get reset-gpios %d\n" , ret); |
317 | return ret; |
318 | } |
319 | |
320 | stk->enable_gpio = devm_gpiod_get(dev, con_id: "enable" , flags: GPIOD_OUT_LOW); |
321 | if (IS_ERR(ptr: stk->enable_gpio)) { |
322 | ret = PTR_ERR(ptr: stk->enable_gpio); |
323 | dev_err(dev, "cannot get enable-gpio %d\n" , ret); |
324 | return ret; |
325 | } |
326 | |
327 | stk->backlight = drm_panel_create_dsi_backlight(dsi: stk->dsi); |
328 | if (IS_ERR(ptr: stk->backlight)) { |
329 | ret = PTR_ERR(ptr: stk->backlight); |
330 | dev_err(dev, "failed to register backlight %d\n" , ret); |
331 | return ret; |
332 | } |
333 | |
334 | drm_panel_init(panel: &stk->base, dev: &stk->dsi->dev, funcs: &stk_panel_funcs, |
335 | DRM_MODE_CONNECTOR_DSI); |
336 | |
337 | drm_panel_add(panel: &stk->base); |
338 | |
339 | return 0; |
340 | } |
341 | |
342 | static int stk_panel_probe(struct mipi_dsi_device *dsi) |
343 | { |
344 | struct stk_panel *stk; |
345 | int ret; |
346 | |
347 | dsi->lanes = 4; |
348 | dsi->format = MIPI_DSI_FMT_RGB888; |
349 | dsi->mode_flags = (MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM); |
350 | |
351 | stk = devm_kzalloc(dev: &dsi->dev, size: sizeof(*stk), GFP_KERNEL); |
352 | if (!stk) |
353 | return -ENOMEM; |
354 | |
355 | mipi_dsi_set_drvdata(dsi, data: stk); |
356 | |
357 | stk->dsi = dsi; |
358 | |
359 | ret = stk_panel_add(stk); |
360 | if (ret < 0) |
361 | return ret; |
362 | |
363 | ret = mipi_dsi_attach(dsi); |
364 | if (ret < 0) |
365 | drm_panel_remove(panel: &stk->base); |
366 | |
367 | return 0; |
368 | } |
369 | |
370 | static void stk_panel_remove(struct mipi_dsi_device *dsi) |
371 | { |
372 | struct stk_panel *stk = mipi_dsi_get_drvdata(dsi); |
373 | int err; |
374 | |
375 | err = mipi_dsi_detach(dsi); |
376 | if (err < 0) |
377 | dev_err(&dsi->dev, "failed to detach from DSI host: %d\n" , |
378 | err); |
379 | |
380 | drm_panel_remove(panel: &stk->base); |
381 | } |
382 | |
383 | static struct mipi_dsi_driver stk_panel_driver = { |
384 | .driver = { |
385 | .name = "panel-startek-kd070fhfid015" , |
386 | .of_match_table = stk_of_match, |
387 | }, |
388 | .probe = stk_panel_probe, |
389 | .remove = stk_panel_remove, |
390 | }; |
391 | module_mipi_dsi_driver(stk_panel_driver); |
392 | |
393 | MODULE_AUTHOR("Guillaume La Roque <glaroque@baylibre.com>" ); |
394 | MODULE_DESCRIPTION("STARTEK KD070FHFID015" ); |
395 | MODULE_LICENSE("GPL" ); |
396 | |