351 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Copyright (C) 2016 InforceComputing4 * Copyright (C) 2016 Linaro Ltd5 * Copyright (C) 2023 BayLibre, SAS6 *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 0xb028#define DSI_REG_IS 0xb3 /* Interface Setting */29#define DSI_REG_IIS 0xb4 /* Interface ID Setting */30#define DSI_REG_CTRL 0xb631 32enum {33 IOVCC = 0,34 POWER = 135};36 37struct 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 47static inline struct stk_panel *to_stk_panel(struct drm_panel *panel)48{49 return container_of(panel, struct stk_panel, base);50}51 52static int stk_panel_init(struct stk_panel *stk)53{54 struct mipi_dsi_device *dsi = stk->dsi;55 struct mipi_dsi_multi_context dsi_ctx = {.dsi = dsi};56 57 mipi_dsi_dcs_soft_reset_multi(&dsi_ctx);58 mipi_dsi_msleep(&dsi_ctx, 5);59 mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx);60 mipi_dsi_msleep(&dsi_ctx, 120);61 62 mipi_dsi_generic_write_seq_multi(&dsi_ctx, DSI_REG_MCAP, 0x04);63 64 /* Interface setting, video mode */65 mipi_dsi_generic_write_seq_multi(&dsi_ctx, DSI_REG_IS, 0x14, 0x08, 0x00, 0x22, 0x00);66 mipi_dsi_generic_write_seq_multi(&dsi_ctx, DSI_REG_IIS, 0x0c, 0x00);67 mipi_dsi_generic_write_seq_multi(&dsi_ctx, DSI_REG_CTRL, 0x3a, 0xd3);68 69 mipi_dsi_dcs_set_display_brightness_multi(&dsi_ctx, 0x77);70 71 mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY,72 MIPI_DCS_WRITE_MEMORY_START);73 74 mipi_dsi_dcs_set_pixel_format_multi(&dsi_ctx, 0x77);75 mipi_dsi_dcs_set_column_address_multi(&dsi_ctx, 0, stk->mode->hdisplay - 1);76 mipi_dsi_dcs_set_page_address_multi(&dsi_ctx, 0, stk->mode->vdisplay - 1);77 78 return dsi_ctx.accum_err;79}80 81static int stk_panel_on(struct stk_panel *stk)82{83 struct mipi_dsi_device *dsi = stk->dsi;84 struct mipi_dsi_multi_context dsi_ctx = {.dsi = dsi};85 86 mipi_dsi_dcs_set_display_on_multi(&dsi_ctx);87 88 mipi_dsi_msleep(&dsi_ctx, 20);89 90 return dsi_ctx.accum_err;91}92 93static void stk_panel_off(struct stk_panel *stk)94{95 struct mipi_dsi_device *dsi = stk->dsi;96 struct mipi_dsi_multi_context dsi_ctx = {.dsi = dsi};97 98 dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;99 100 mipi_dsi_dcs_set_display_off_multi(&dsi_ctx);101 mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx);102 103 mipi_dsi_msleep(&dsi_ctx, 100);104}105 106static int stk_panel_unprepare(struct drm_panel *panel)107{108 struct stk_panel *stk = to_stk_panel(panel);109 110 stk_panel_off(stk);111 regulator_bulk_disable(ARRAY_SIZE(stk->supplies), stk->supplies);112 gpiod_set_value(stk->reset_gpio, 0);113 gpiod_set_value(stk->enable_gpio, 1);114 115 return 0;116}117 118static int stk_panel_prepare(struct drm_panel *panel)119{120 struct stk_panel *stk = to_stk_panel(panel);121 int ret;122 123 gpiod_set_value(stk->reset_gpio, 0);124 gpiod_set_value(stk->enable_gpio, 0);125 ret = regulator_enable(stk->supplies[IOVCC].consumer);126 if (ret < 0)127 return ret;128 129 mdelay(8);130 ret = regulator_enable(stk->supplies[POWER].consumer);131 if (ret < 0)132 goto iovccoff;133 134 mdelay(20);135 gpiod_set_value(stk->enable_gpio, 1);136 mdelay(20);137 gpiod_set_value(stk->reset_gpio, 1);138 mdelay(10);139 ret = stk_panel_init(stk);140 if (ret < 0)141 goto poweroff;142 143 ret = stk_panel_on(stk);144 if (ret < 0)145 goto poweroff;146 147 return 0;148 149poweroff:150 regulator_disable(stk->supplies[POWER].consumer);151iovccoff:152 regulator_disable(stk->supplies[IOVCC].consumer);153 gpiod_set_value(stk->reset_gpio, 0);154 gpiod_set_value(stk->enable_gpio, 0);155 156 return ret;157}158 159static const struct drm_display_mode default_mode = {160 .clock = 163204,161 .hdisplay = 1200,162 .hsync_start = 1200 + 144,163 .hsync_end = 1200 + 144 + 16,164 .htotal = 1200 + 144 + 16 + 45,165 .vdisplay = 1920,166 .vsync_start = 1920 + 8,167 .vsync_end = 1920 + 8 + 4,168 .vtotal = 1920 + 8 + 4 + 4,169 .width_mm = 95,170 .height_mm = 151,171};172 173static int stk_panel_get_modes(struct drm_panel *panel,174 struct drm_connector *connector)175{176 struct drm_display_mode *mode;177 178 mode = drm_mode_duplicate(connector->dev, &default_mode);179 if (!mode) {180 dev_err(panel->dev, "failed to add mode %ux%ux@%u\n",181 default_mode.hdisplay, default_mode.vdisplay,182 drm_mode_vrefresh(&default_mode));183 return -ENOMEM;184 }185 186 drm_mode_set_name(mode);187 drm_mode_probed_add(connector, mode);188 connector->display_info.width_mm = default_mode.width_mm;189 connector->display_info.height_mm = default_mode.height_mm;190 return 1;191}192 193static int dsi_dcs_bl_get_brightness(struct backlight_device *bl)194{195 struct mipi_dsi_device *dsi = bl_get_data(bl);196 int ret;197 u16 brightness;198 199 dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;200 ret = mipi_dsi_dcs_get_display_brightness(dsi, &brightness);201 if (ret < 0)202 return ret;203 204 dsi->mode_flags |= MIPI_DSI_MODE_LPM;205 return brightness & 0xff;206}207 208static int dsi_dcs_bl_update_status(struct backlight_device *bl)209{210 struct mipi_dsi_device *dsi = bl_get_data(bl);211 struct mipi_dsi_multi_context dsi_ctx = {.dsi = dsi};212 213 dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;214 mipi_dsi_dcs_set_display_brightness_multi(&dsi_ctx, bl->props.brightness);215 if (dsi_ctx.accum_err)216 return dsi_ctx.accum_err;217 218 dsi->mode_flags |= MIPI_DSI_MODE_LPM;219 return dsi_ctx.accum_err;220}221 222static const struct backlight_ops dsi_bl_ops = {223 .update_status = dsi_dcs_bl_update_status,224 .get_brightness = dsi_dcs_bl_get_brightness,225};226 227static struct backlight_device *228drm_panel_create_dsi_backlight(struct mipi_dsi_device *dsi)229{230 struct device *dev = &dsi->dev;231 struct backlight_properties props = {232 .type = BACKLIGHT_RAW,233 .brightness = 255,234 .max_brightness = 255,235 };236 237 return devm_backlight_device_register(dev, dev_name(dev), dev, dsi,238 &dsi_bl_ops, &props);239}240 241static const struct drm_panel_funcs stk_panel_funcs = {242 .unprepare = stk_panel_unprepare,243 .prepare = stk_panel_prepare,244 .get_modes = stk_panel_get_modes,245};246 247static const struct of_device_id stk_of_match[] = {248 { .compatible = "startek,kd070fhfid015", },249 { }250};251MODULE_DEVICE_TABLE(of, stk_of_match);252 253static int stk_panel_add(struct stk_panel *stk)254{255 struct device *dev = &stk->dsi->dev;256 int ret;257 258 stk->mode = &default_mode;259 260 stk->supplies[IOVCC].supply = "iovcc";261 stk->supplies[POWER].supply = "power";262 ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(stk->supplies), stk->supplies);263 if (ret) {264 dev_err(dev, "regulator_bulk failed\n");265 return ret;266 }267 268 stk->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);269 if (IS_ERR(stk->reset_gpio)) {270 ret = PTR_ERR(stk->reset_gpio);271 dev_err(dev, "cannot get reset-gpios %d\n", ret);272 return ret;273 }274 275 stk->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW);276 if (IS_ERR(stk->enable_gpio)) {277 ret = PTR_ERR(stk->enable_gpio);278 dev_err(dev, "cannot get enable-gpio %d\n", ret);279 return ret;280 }281 282 stk->backlight = drm_panel_create_dsi_backlight(stk->dsi);283 if (IS_ERR(stk->backlight)) {284 ret = PTR_ERR(stk->backlight);285 dev_err(dev, "failed to register backlight %d\n", ret);286 return ret;287 }288 289 drm_panel_init(&stk->base, &stk->dsi->dev, &stk_panel_funcs,290 DRM_MODE_CONNECTOR_DSI);291 292 drm_panel_add(&stk->base);293 294 return 0;295}296 297static int stk_panel_probe(struct mipi_dsi_device *dsi)298{299 struct stk_panel *stk;300 int ret;301 302 dsi->lanes = 4;303 dsi->format = MIPI_DSI_FMT_RGB888;304 dsi->mode_flags = (MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM);305 306 stk = devm_kzalloc(&dsi->dev, sizeof(*stk), GFP_KERNEL);307 if (!stk)308 return -ENOMEM;309 310 mipi_dsi_set_drvdata(dsi, stk);311 312 stk->dsi = dsi;313 314 ret = stk_panel_add(stk);315 if (ret < 0)316 return ret;317 318 ret = mipi_dsi_attach(dsi);319 if (ret < 0)320 drm_panel_remove(&stk->base);321 322 return 0;323}324 325static void stk_panel_remove(struct mipi_dsi_device *dsi)326{327 struct stk_panel *stk = mipi_dsi_get_drvdata(dsi);328 int err;329 330 err = mipi_dsi_detach(dsi);331 if (err < 0)332 dev_err(&dsi->dev, "failed to detach from DSI host: %d\n",333 err);334 335 drm_panel_remove(&stk->base);336}337 338static struct mipi_dsi_driver stk_panel_driver = {339 .driver = {340 .name = "panel-startek-kd070fhfid015",341 .of_match_table = stk_of_match,342 },343 .probe = stk_panel_probe,344 .remove = stk_panel_remove,345};346module_mipi_dsi_driver(stk_panel_driver);347 348MODULE_AUTHOR("Guillaume La Roque <glaroque@baylibre.com>");349MODULE_DESCRIPTION("STARTEK KD070FHFID015");350MODULE_LICENSE("GPL");351