1 // SPDX-License-Identifier: GPL-2.0
3 * Copyright (C) 2016 InforceComputing
4 * Copyright (C) 2016 Linaro Ltd
5 * Copyright (C) 2023 BayLibre, SAS
14 #include <linux/backlight.h>
15 #include <linux/delay.h>
16 #include <linux/gpio/consumer.h>
17 #include <linux/module.h>
19 #include <linux/regulator/consumer.h>
21 #include <video/mipi_display.h>
23 #include <drm/drm_mipi_dsi.h>
24 #include <drm/drm_modes.h>
25 #include <drm/drm_panel.h>
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
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];
47 static inline struct stk_panel *to_stk_panel(struct drm_panel *panel)
49 return container_of(panel, struct stk_panel, base);
52 static int stk_panel_init(struct stk_panel *stk)
54 struct mipi_dsi_device *dsi = stk->dsi;
55 struct device *dev = &stk->dsi->dev;
58 ret = mipi_dsi_dcs_soft_reset(dsi);
60 dev_err(dev, "failed to mipi_dsi_dcs_soft_reset: %d\n", ret);
65 ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
67 dev_err(dev, "failed to set exit sleep mode: %d\n", ret);
72 mipi_dsi_generic_write_seq(dsi, DSI_REG_MCAP, 0x04);
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);
79 ret = mipi_dsi_dcs_set_display_brightness(dsi, 0x77);
81 dev_err(dev, "failed to write display brightness: %d\n", ret);
85 mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_WRITE_CONTROL_DISPLAY,
86 MIPI_DCS_WRITE_MEMORY_START);
88 ret = mipi_dsi_dcs_set_pixel_format(dsi, 0x77);
90 dev_err(dev, "failed to set pixel format: %d\n", ret);
94 ret = mipi_dsi_dcs_set_column_address(dsi, 0, stk->mode->hdisplay - 1);
96 dev_err(dev, "failed to set column address: %d\n", ret);
100 ret = mipi_dsi_dcs_set_page_address(dsi, 0, stk->mode->vdisplay - 1);
102 dev_err(dev, "failed to set page address: %d\n", ret);
109 static int stk_panel_on(struct stk_panel *stk)
111 struct mipi_dsi_device *dsi = stk->dsi;
112 struct device *dev = &stk->dsi->dev;
115 ret = mipi_dsi_dcs_set_display_on(dsi);
117 dev_err(dev, "failed to set display on: %d\n", ret);
124 static void stk_panel_off(struct stk_panel *stk)
126 struct mipi_dsi_device *dsi = stk->dsi;
127 struct device *dev = &stk->dsi->dev;
130 dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
132 ret = mipi_dsi_dcs_set_display_off(dsi);
134 dev_err(dev, "failed to set display off: %d\n", ret);
136 ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
138 dev_err(dev, "failed to enter sleep mode: %d\n", ret);
143 static int stk_panel_unprepare(struct drm_panel *panel)
145 struct stk_panel *stk = to_stk_panel(panel);
148 regulator_bulk_disable(ARRAY_SIZE(stk->supplies), stk->supplies);
149 gpiod_set_value(stk->reset_gpio, 0);
150 gpiod_set_value(stk->enable_gpio, 1);
155 static int stk_panel_prepare(struct drm_panel *panel)
157 struct stk_panel *stk = to_stk_panel(panel);
158 struct device *dev = &stk->dsi->dev;
161 gpiod_set_value(stk->reset_gpio, 0);
162 gpiod_set_value(stk->enable_gpio, 0);
163 ret = regulator_enable(stk->supplies[IOVCC].consumer);
168 ret = regulator_enable(stk->supplies[POWER].consumer);
173 gpiod_set_value(stk->enable_gpio, 1);
175 gpiod_set_value(stk->reset_gpio, 1);
177 ret = stk_panel_init(stk);
179 dev_err(dev, "failed to init panel: %d\n", ret);
183 ret = stk_panel_on(stk);
185 dev_err(dev, "failed to set panel on: %d\n", ret);
192 regulator_disable(stk->supplies[POWER].consumer);
194 regulator_disable(stk->supplies[IOVCC].consumer);
195 gpiod_set_value(stk->reset_gpio, 0);
196 gpiod_set_value(stk->enable_gpio, 0);
201 static const struct drm_display_mode default_mode = {
204 .hsync_start = 1200 + 144,
205 .hsync_end = 1200 + 144 + 16,
206 .htotal = 1200 + 144 + 16 + 45,
208 .vsync_start = 1920 + 8,
209 .vsync_end = 1920 + 8 + 4,
210 .vtotal = 1920 + 8 + 4 + 4,
215 static int stk_panel_get_modes(struct drm_panel *panel,
216 struct drm_connector *connector)
218 struct drm_display_mode *mode;
220 mode = drm_mode_duplicate(connector->dev, &default_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));
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;
235 static int dsi_dcs_bl_get_brightness(struct backlight_device *bl)
237 struct mipi_dsi_device *dsi = bl_get_data(bl);
241 dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
242 ret = mipi_dsi_dcs_get_display_brightness(dsi, &brightness);
246 dsi->mode_flags |= MIPI_DSI_MODE_LPM;
247 return brightness & 0xff;
250 static int dsi_dcs_bl_update_status(struct backlight_device *bl)
252 struct mipi_dsi_device *dsi = bl_get_data(bl);
253 struct device *dev = &dsi->dev;
256 dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
257 ret = mipi_dsi_dcs_set_display_brightness(dsi, bl->props.brightness);
259 dev_err(dev, "failed to set DSI control: %d\n", ret);
263 dsi->mode_flags |= MIPI_DSI_MODE_LPM;
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,
272 static struct backlight_device *
273 drm_panel_create_dsi_backlight(struct mipi_dsi_device *dsi)
275 struct device *dev = &dsi->dev;
276 struct backlight_properties props = {
277 .type = BACKLIGHT_RAW,
279 .max_brightness = 255,
282 return devm_backlight_device_register(dev, dev_name(dev), dev, dsi,
283 &dsi_bl_ops, &props);
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,
292 static const struct of_device_id stk_of_match[] = {
293 { .compatible = "startek,kd070fhfid015", },
296 MODULE_DEVICE_TABLE(of, stk_of_match);
298 static int stk_panel_add(struct stk_panel *stk)
300 struct device *dev = &stk->dsi->dev;
303 stk->mode = &default_mode;
305 stk->supplies[IOVCC].supply = "iovcc";
306 stk->supplies[POWER].supply = "power";
307 ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(stk->supplies), stk->supplies);
309 dev_err(dev, "regulator_bulk failed\n");
313 stk->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
314 if (IS_ERR(stk->reset_gpio)) {
315 ret = PTR_ERR(stk->reset_gpio);
316 dev_err(dev, "cannot get reset-gpios %d\n", ret);
320 stk->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW);
321 if (IS_ERR(stk->enable_gpio)) {
322 ret = PTR_ERR(stk->enable_gpio);
323 dev_err(dev, "cannot get enable-gpio %d\n", ret);
327 stk->backlight = drm_panel_create_dsi_backlight(stk->dsi);
328 if (IS_ERR(stk->backlight)) {
329 ret = PTR_ERR(stk->backlight);
330 dev_err(dev, "failed to register backlight %d\n", ret);
334 drm_panel_init(&stk->base, &stk->dsi->dev, &stk_panel_funcs,
335 DRM_MODE_CONNECTOR_DSI);
337 drm_panel_add(&stk->base);
342 static int stk_panel_probe(struct mipi_dsi_device *dsi)
344 struct stk_panel *stk;
348 dsi->format = MIPI_DSI_FMT_RGB888;
349 dsi->mode_flags = (MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM);
351 stk = devm_kzalloc(&dsi->dev, sizeof(*stk), GFP_KERNEL);
355 mipi_dsi_set_drvdata(dsi, stk);
359 ret = stk_panel_add(stk);
363 ret = mipi_dsi_attach(dsi);
365 drm_panel_remove(&stk->base);
370 static void stk_panel_remove(struct mipi_dsi_device *dsi)
372 struct stk_panel *stk = mipi_dsi_get_drvdata(dsi);
375 err = mipi_dsi_detach(dsi);
377 dev_err(&dsi->dev, "failed to detach from DSI host: %d\n",
380 drm_panel_remove(&stk->base);
383 static struct mipi_dsi_driver stk_panel_driver = {
385 .name = "panel-startek-kd070fhfid015",
386 .of_match_table = stk_of_match,
388 .probe = stk_panel_probe,
389 .remove = stk_panel_remove,
391 module_mipi_dsi_driver(stk_panel_driver);
394 MODULE_DESCRIPTION("STARTEK KD070FHFID015");
395 MODULE_LICENSE("GPL");