169312a77SGuillaume La Roque // SPDX-License-Identifier: GPL-2.0 269312a77SGuillaume La Roque /* 369312a77SGuillaume La Roque * Copyright (C) 2016 InforceComputing 469312a77SGuillaume La Roque * Copyright (C) 2016 Linaro Ltd 569312a77SGuillaume La Roque * Copyright (C) 2023 BayLibre, SAS 669312a77SGuillaume La Roque * 769312a77SGuillaume La Roque * Authors: 869312a77SGuillaume La Roque * - Vinay Simha BN <simhavcs@gmail.com> 969312a77SGuillaume La Roque * - Sumit Semwal <sumit.semwal@linaro.org> 1069312a77SGuillaume La Roque * - Guillaume La Roque <glaroque@baylibre.com> 1169312a77SGuillaume La Roque * 1269312a77SGuillaume La Roque */ 1369312a77SGuillaume La Roque 1469312a77SGuillaume La Roque #include <linux/backlight.h> 1569312a77SGuillaume La Roque #include <linux/delay.h> 1669312a77SGuillaume La Roque #include <linux/gpio/consumer.h> 1769312a77SGuillaume La Roque #include <linux/module.h> 1869312a77SGuillaume La Roque #include <linux/of.h> 1969312a77SGuillaume La Roque #include <linux/regulator/consumer.h> 2069312a77SGuillaume La Roque 2169312a77SGuillaume La Roque #include <video/mipi_display.h> 2269312a77SGuillaume La Roque 2369312a77SGuillaume La Roque #include <drm/drm_mipi_dsi.h> 2469312a77SGuillaume La Roque #include <drm/drm_modes.h> 2569312a77SGuillaume La Roque #include <drm/drm_panel.h> 2669312a77SGuillaume La Roque 27*b080a607STejas Vipin #define DSI_REG_MCAP 0xb0 28*b080a607STejas Vipin #define DSI_REG_IS 0xb3 /* Interface Setting */ 29*b080a607STejas Vipin #define DSI_REG_IIS 0xb4 /* Interface ID Setting */ 30*b080a607STejas Vipin #define DSI_REG_CTRL 0xb6 3169312a77SGuillaume La Roque 3269312a77SGuillaume La Roque enum { 3369312a77SGuillaume La Roque IOVCC = 0, 3469312a77SGuillaume La Roque POWER = 1 3569312a77SGuillaume La Roque }; 3669312a77SGuillaume La Roque 3769312a77SGuillaume La Roque struct stk_panel { 3869312a77SGuillaume La Roque const struct drm_display_mode *mode; 3969312a77SGuillaume La Roque struct backlight_device *backlight; 4069312a77SGuillaume La Roque struct drm_panel base; 4169312a77SGuillaume La Roque struct gpio_desc *enable_gpio; /* Power IC supply enable */ 4269312a77SGuillaume La Roque struct gpio_desc *reset_gpio; /* External reset */ 4369312a77SGuillaume La Roque struct mipi_dsi_device *dsi; 4469312a77SGuillaume La Roque struct regulator_bulk_data supplies[2]; 4569312a77SGuillaume La Roque }; 4669312a77SGuillaume La Roque 4769312a77SGuillaume La Roque static inline struct stk_panel *to_stk_panel(struct drm_panel *panel) 4869312a77SGuillaume La Roque { 4969312a77SGuillaume La Roque return container_of(panel, struct stk_panel, base); 5069312a77SGuillaume La Roque } 5169312a77SGuillaume La Roque 5269312a77SGuillaume La Roque static int stk_panel_init(struct stk_panel *stk) 5369312a77SGuillaume La Roque { 5469312a77SGuillaume La Roque struct mipi_dsi_device *dsi = stk->dsi; 55*b080a607STejas Vipin struct mipi_dsi_multi_context dsi_ctx = {.dsi = dsi}; 5669312a77SGuillaume La Roque 57*b080a607STejas Vipin mipi_dsi_dcs_soft_reset_multi(&dsi_ctx); 58*b080a607STejas Vipin mipi_dsi_msleep(&dsi_ctx, 5); 59*b080a607STejas Vipin mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); 60*b080a607STejas Vipin mipi_dsi_msleep(&dsi_ctx, 120); 6169312a77SGuillaume La Roque 62*b080a607STejas Vipin mipi_dsi_generic_write_seq_multi(&dsi_ctx, DSI_REG_MCAP, 0x04); 6369312a77SGuillaume La Roque 6469312a77SGuillaume La Roque /* Interface setting, video mode */ 65*b080a607STejas Vipin mipi_dsi_generic_write_seq_multi(&dsi_ctx, DSI_REG_IS, 0x14, 0x08, 0x00, 0x22, 0x00); 66*b080a607STejas Vipin mipi_dsi_generic_write_seq_multi(&dsi_ctx, DSI_REG_IIS, 0x0c, 0x00); 67*b080a607STejas Vipin mipi_dsi_generic_write_seq_multi(&dsi_ctx, DSI_REG_CTRL, 0x3a, 0xd3); 6869312a77SGuillaume La Roque 69*b080a607STejas Vipin mipi_dsi_dcs_set_display_brightness_multi(&dsi_ctx, 0x77); 7069312a77SGuillaume La Roque 71*b080a607STejas Vipin mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY, 7269312a77SGuillaume La Roque MIPI_DCS_WRITE_MEMORY_START); 7369312a77SGuillaume La Roque 74*b080a607STejas Vipin mipi_dsi_dcs_set_pixel_format_multi(&dsi_ctx, 0x77); 75*b080a607STejas Vipin mipi_dsi_dcs_set_column_address_multi(&dsi_ctx, 0, stk->mode->hdisplay - 1); 76*b080a607STejas Vipin mipi_dsi_dcs_set_page_address_multi(&dsi_ctx, 0, stk->mode->vdisplay - 1); 7769312a77SGuillaume La Roque 78*b080a607STejas Vipin return dsi_ctx.accum_err; 7969312a77SGuillaume La Roque } 8069312a77SGuillaume La Roque 8169312a77SGuillaume La Roque static int stk_panel_on(struct stk_panel *stk) 8269312a77SGuillaume La Roque { 8369312a77SGuillaume La Roque struct mipi_dsi_device *dsi = stk->dsi; 84*b080a607STejas Vipin struct mipi_dsi_multi_context dsi_ctx = {.dsi = dsi}; 8569312a77SGuillaume La Roque 86*b080a607STejas Vipin mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); 8769312a77SGuillaume La Roque 88*b080a607STejas Vipin mipi_dsi_msleep(&dsi_ctx, 20); 8969312a77SGuillaume La Roque 90*b080a607STejas Vipin return dsi_ctx.accum_err; 9169312a77SGuillaume La Roque } 9269312a77SGuillaume La Roque 9369312a77SGuillaume La Roque static void stk_panel_off(struct stk_panel *stk) 9469312a77SGuillaume La Roque { 9569312a77SGuillaume La Roque struct mipi_dsi_device *dsi = stk->dsi; 96*b080a607STejas Vipin struct mipi_dsi_multi_context dsi_ctx = {.dsi = dsi}; 9769312a77SGuillaume La Roque 9869312a77SGuillaume La Roque dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; 9969312a77SGuillaume La Roque 100*b080a607STejas Vipin mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); 101*b080a607STejas Vipin mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); 10269312a77SGuillaume La Roque 103*b080a607STejas Vipin mipi_dsi_msleep(&dsi_ctx, 100); 10469312a77SGuillaume La Roque } 10569312a77SGuillaume La Roque 10669312a77SGuillaume La Roque static int stk_panel_unprepare(struct drm_panel *panel) 10769312a77SGuillaume La Roque { 10869312a77SGuillaume La Roque struct stk_panel *stk = to_stk_panel(panel); 10969312a77SGuillaume La Roque 11069312a77SGuillaume La Roque stk_panel_off(stk); 11169312a77SGuillaume La Roque regulator_bulk_disable(ARRAY_SIZE(stk->supplies), stk->supplies); 11269312a77SGuillaume La Roque gpiod_set_value(stk->reset_gpio, 0); 11369312a77SGuillaume La Roque gpiod_set_value(stk->enable_gpio, 1); 11469312a77SGuillaume La Roque 11569312a77SGuillaume La Roque return 0; 11669312a77SGuillaume La Roque } 11769312a77SGuillaume La Roque 11869312a77SGuillaume La Roque static int stk_panel_prepare(struct drm_panel *panel) 11969312a77SGuillaume La Roque { 12069312a77SGuillaume La Roque struct stk_panel *stk = to_stk_panel(panel); 12169312a77SGuillaume La Roque int ret; 12269312a77SGuillaume La Roque 12369312a77SGuillaume La Roque gpiod_set_value(stk->reset_gpio, 0); 12469312a77SGuillaume La Roque gpiod_set_value(stk->enable_gpio, 0); 12569312a77SGuillaume La Roque ret = regulator_enable(stk->supplies[IOVCC].consumer); 12669312a77SGuillaume La Roque if (ret < 0) 12769312a77SGuillaume La Roque return ret; 12869312a77SGuillaume La Roque 12969312a77SGuillaume La Roque mdelay(8); 13069312a77SGuillaume La Roque ret = regulator_enable(stk->supplies[POWER].consumer); 13169312a77SGuillaume La Roque if (ret < 0) 13269312a77SGuillaume La Roque goto iovccoff; 13369312a77SGuillaume La Roque 13469312a77SGuillaume La Roque mdelay(20); 13569312a77SGuillaume La Roque gpiod_set_value(stk->enable_gpio, 1); 13669312a77SGuillaume La Roque mdelay(20); 13769312a77SGuillaume La Roque gpiod_set_value(stk->reset_gpio, 1); 13869312a77SGuillaume La Roque mdelay(10); 13969312a77SGuillaume La Roque ret = stk_panel_init(stk); 140*b080a607STejas Vipin if (ret < 0) 14169312a77SGuillaume La Roque goto poweroff; 14269312a77SGuillaume La Roque 14369312a77SGuillaume La Roque ret = stk_panel_on(stk); 144*b080a607STejas Vipin if (ret < 0) 14569312a77SGuillaume La Roque goto poweroff; 14669312a77SGuillaume La Roque 14769312a77SGuillaume La Roque return 0; 14869312a77SGuillaume La Roque 14969312a77SGuillaume La Roque poweroff: 15069312a77SGuillaume La Roque regulator_disable(stk->supplies[POWER].consumer); 15169312a77SGuillaume La Roque iovccoff: 15269312a77SGuillaume La Roque regulator_disable(stk->supplies[IOVCC].consumer); 15369312a77SGuillaume La Roque gpiod_set_value(stk->reset_gpio, 0); 15469312a77SGuillaume La Roque gpiod_set_value(stk->enable_gpio, 0); 15569312a77SGuillaume La Roque 15669312a77SGuillaume La Roque return ret; 15769312a77SGuillaume La Roque } 15869312a77SGuillaume La Roque 15969312a77SGuillaume La Roque static const struct drm_display_mode default_mode = { 16069312a77SGuillaume La Roque .clock = 163204, 16169312a77SGuillaume La Roque .hdisplay = 1200, 16269312a77SGuillaume La Roque .hsync_start = 1200 + 144, 16369312a77SGuillaume La Roque .hsync_end = 1200 + 144 + 16, 16469312a77SGuillaume La Roque .htotal = 1200 + 144 + 16 + 45, 16569312a77SGuillaume La Roque .vdisplay = 1920, 16669312a77SGuillaume La Roque .vsync_start = 1920 + 8, 16769312a77SGuillaume La Roque .vsync_end = 1920 + 8 + 4, 16869312a77SGuillaume La Roque .vtotal = 1920 + 8 + 4 + 4, 16969312a77SGuillaume La Roque .width_mm = 95, 17069312a77SGuillaume La Roque .height_mm = 151, 17169312a77SGuillaume La Roque }; 17269312a77SGuillaume La Roque 17369312a77SGuillaume La Roque static int stk_panel_get_modes(struct drm_panel *panel, 17469312a77SGuillaume La Roque struct drm_connector *connector) 17569312a77SGuillaume La Roque { 17669312a77SGuillaume La Roque struct drm_display_mode *mode; 17769312a77SGuillaume La Roque 17869312a77SGuillaume La Roque mode = drm_mode_duplicate(connector->dev, &default_mode); 17969312a77SGuillaume La Roque if (!mode) { 18069312a77SGuillaume La Roque dev_err(panel->dev, "failed to add mode %ux%ux@%u\n", 18169312a77SGuillaume La Roque default_mode.hdisplay, default_mode.vdisplay, 18269312a77SGuillaume La Roque drm_mode_vrefresh(&default_mode)); 18369312a77SGuillaume La Roque return -ENOMEM; 18469312a77SGuillaume La Roque } 18569312a77SGuillaume La Roque 18669312a77SGuillaume La Roque drm_mode_set_name(mode); 18769312a77SGuillaume La Roque drm_mode_probed_add(connector, mode); 18869312a77SGuillaume La Roque connector->display_info.width_mm = default_mode.width_mm; 18969312a77SGuillaume La Roque connector->display_info.height_mm = default_mode.height_mm; 19069312a77SGuillaume La Roque return 1; 19169312a77SGuillaume La Roque } 19269312a77SGuillaume La Roque 19369312a77SGuillaume La Roque static int dsi_dcs_bl_get_brightness(struct backlight_device *bl) 19469312a77SGuillaume La Roque { 19569312a77SGuillaume La Roque struct mipi_dsi_device *dsi = bl_get_data(bl); 19669312a77SGuillaume La Roque int ret; 19769312a77SGuillaume La Roque u16 brightness; 19869312a77SGuillaume La Roque 19969312a77SGuillaume La Roque dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; 20069312a77SGuillaume La Roque ret = mipi_dsi_dcs_get_display_brightness(dsi, &brightness); 20169312a77SGuillaume La Roque if (ret < 0) 20269312a77SGuillaume La Roque return ret; 20369312a77SGuillaume La Roque 20469312a77SGuillaume La Roque dsi->mode_flags |= MIPI_DSI_MODE_LPM; 20569312a77SGuillaume La Roque return brightness & 0xff; 20669312a77SGuillaume La Roque } 20769312a77SGuillaume La Roque 20869312a77SGuillaume La Roque static int dsi_dcs_bl_update_status(struct backlight_device *bl) 20969312a77SGuillaume La Roque { 21069312a77SGuillaume La Roque struct mipi_dsi_device *dsi = bl_get_data(bl); 211*b080a607STejas Vipin struct mipi_dsi_multi_context dsi_ctx = {.dsi = dsi}; 21269312a77SGuillaume La Roque 21369312a77SGuillaume La Roque dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; 214*b080a607STejas Vipin mipi_dsi_dcs_set_display_brightness_multi(&dsi_ctx, bl->props.brightness); 215*b080a607STejas Vipin if (dsi_ctx.accum_err) 216*b080a607STejas Vipin return dsi_ctx.accum_err; 21769312a77SGuillaume La Roque 21869312a77SGuillaume La Roque dsi->mode_flags |= MIPI_DSI_MODE_LPM; 219*b080a607STejas Vipin return dsi_ctx.accum_err; 22069312a77SGuillaume La Roque } 22169312a77SGuillaume La Roque 22269312a77SGuillaume La Roque static const struct backlight_ops dsi_bl_ops = { 22369312a77SGuillaume La Roque .update_status = dsi_dcs_bl_update_status, 22469312a77SGuillaume La Roque .get_brightness = dsi_dcs_bl_get_brightness, 22569312a77SGuillaume La Roque }; 22669312a77SGuillaume La Roque 22769312a77SGuillaume La Roque static struct backlight_device * 22869312a77SGuillaume La Roque drm_panel_create_dsi_backlight(struct mipi_dsi_device *dsi) 22969312a77SGuillaume La Roque { 23069312a77SGuillaume La Roque struct device *dev = &dsi->dev; 23169312a77SGuillaume La Roque struct backlight_properties props = { 23269312a77SGuillaume La Roque .type = BACKLIGHT_RAW, 23369312a77SGuillaume La Roque .brightness = 255, 23469312a77SGuillaume La Roque .max_brightness = 255, 23569312a77SGuillaume La Roque }; 23669312a77SGuillaume La Roque 23769312a77SGuillaume La Roque return devm_backlight_device_register(dev, dev_name(dev), dev, dsi, 23869312a77SGuillaume La Roque &dsi_bl_ops, &props); 23969312a77SGuillaume La Roque } 24069312a77SGuillaume La Roque 24169312a77SGuillaume La Roque static const struct drm_panel_funcs stk_panel_funcs = { 24269312a77SGuillaume La Roque .unprepare = stk_panel_unprepare, 24369312a77SGuillaume La Roque .prepare = stk_panel_prepare, 24469312a77SGuillaume La Roque .get_modes = stk_panel_get_modes, 24569312a77SGuillaume La Roque }; 24669312a77SGuillaume La Roque 24769312a77SGuillaume La Roque static const struct of_device_id stk_of_match[] = { 24869312a77SGuillaume La Roque { .compatible = "startek,kd070fhfid015", }, 24969312a77SGuillaume La Roque { } 25069312a77SGuillaume La Roque }; 25169312a77SGuillaume La Roque MODULE_DEVICE_TABLE(of, stk_of_match); 25269312a77SGuillaume La Roque 25369312a77SGuillaume La Roque static int stk_panel_add(struct stk_panel *stk) 25469312a77SGuillaume La Roque { 25569312a77SGuillaume La Roque struct device *dev = &stk->dsi->dev; 25669312a77SGuillaume La Roque int ret; 25769312a77SGuillaume La Roque 25869312a77SGuillaume La Roque stk->mode = &default_mode; 25969312a77SGuillaume La Roque 26069312a77SGuillaume La Roque stk->supplies[IOVCC].supply = "iovcc"; 26169312a77SGuillaume La Roque stk->supplies[POWER].supply = "power"; 26269312a77SGuillaume La Roque ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(stk->supplies), stk->supplies); 26369312a77SGuillaume La Roque if (ret) { 26469312a77SGuillaume La Roque dev_err(dev, "regulator_bulk failed\n"); 26569312a77SGuillaume La Roque return ret; 26669312a77SGuillaume La Roque } 26769312a77SGuillaume La Roque 26869312a77SGuillaume La Roque stk->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); 26969312a77SGuillaume La Roque if (IS_ERR(stk->reset_gpio)) { 27069312a77SGuillaume La Roque ret = PTR_ERR(stk->reset_gpio); 27169312a77SGuillaume La Roque dev_err(dev, "cannot get reset-gpios %d\n", ret); 27269312a77SGuillaume La Roque return ret; 27369312a77SGuillaume La Roque } 27469312a77SGuillaume La Roque 27569312a77SGuillaume La Roque stk->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW); 27669312a77SGuillaume La Roque if (IS_ERR(stk->enable_gpio)) { 27769312a77SGuillaume La Roque ret = PTR_ERR(stk->enable_gpio); 27869312a77SGuillaume La Roque dev_err(dev, "cannot get enable-gpio %d\n", ret); 27969312a77SGuillaume La Roque return ret; 28069312a77SGuillaume La Roque } 28169312a77SGuillaume La Roque 28269312a77SGuillaume La Roque stk->backlight = drm_panel_create_dsi_backlight(stk->dsi); 28369312a77SGuillaume La Roque if (IS_ERR(stk->backlight)) { 28469312a77SGuillaume La Roque ret = PTR_ERR(stk->backlight); 28569312a77SGuillaume La Roque dev_err(dev, "failed to register backlight %d\n", ret); 28669312a77SGuillaume La Roque return ret; 28769312a77SGuillaume La Roque } 28869312a77SGuillaume La Roque 28969312a77SGuillaume La Roque drm_panel_init(&stk->base, &stk->dsi->dev, &stk_panel_funcs, 29069312a77SGuillaume La Roque DRM_MODE_CONNECTOR_DSI); 29169312a77SGuillaume La Roque 29269312a77SGuillaume La Roque drm_panel_add(&stk->base); 29369312a77SGuillaume La Roque 29469312a77SGuillaume La Roque return 0; 29569312a77SGuillaume La Roque } 29669312a77SGuillaume La Roque 29769312a77SGuillaume La Roque static int stk_panel_probe(struct mipi_dsi_device *dsi) 29869312a77SGuillaume La Roque { 29969312a77SGuillaume La Roque struct stk_panel *stk; 30069312a77SGuillaume La Roque int ret; 30169312a77SGuillaume La Roque 30269312a77SGuillaume La Roque dsi->lanes = 4; 30369312a77SGuillaume La Roque dsi->format = MIPI_DSI_FMT_RGB888; 30469312a77SGuillaume La Roque dsi->mode_flags = (MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM); 30569312a77SGuillaume La Roque 30669312a77SGuillaume La Roque stk = devm_kzalloc(&dsi->dev, sizeof(*stk), GFP_KERNEL); 30769312a77SGuillaume La Roque if (!stk) 30869312a77SGuillaume La Roque return -ENOMEM; 30969312a77SGuillaume La Roque 31069312a77SGuillaume La Roque mipi_dsi_set_drvdata(dsi, stk); 31169312a77SGuillaume La Roque 31269312a77SGuillaume La Roque stk->dsi = dsi; 31369312a77SGuillaume La Roque 31469312a77SGuillaume La Roque ret = stk_panel_add(stk); 31569312a77SGuillaume La Roque if (ret < 0) 31669312a77SGuillaume La Roque return ret; 31769312a77SGuillaume La Roque 31869312a77SGuillaume La Roque ret = mipi_dsi_attach(dsi); 31969312a77SGuillaume La Roque if (ret < 0) 32069312a77SGuillaume La Roque drm_panel_remove(&stk->base); 32169312a77SGuillaume La Roque 32269312a77SGuillaume La Roque return 0; 32369312a77SGuillaume La Roque } 32469312a77SGuillaume La Roque 32569312a77SGuillaume La Roque static void stk_panel_remove(struct mipi_dsi_device *dsi) 32669312a77SGuillaume La Roque { 32769312a77SGuillaume La Roque struct stk_panel *stk = mipi_dsi_get_drvdata(dsi); 32869312a77SGuillaume La Roque int err; 32969312a77SGuillaume La Roque 33069312a77SGuillaume La Roque err = mipi_dsi_detach(dsi); 33169312a77SGuillaume La Roque if (err < 0) 33269312a77SGuillaume La Roque dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", 33369312a77SGuillaume La Roque err); 33469312a77SGuillaume La Roque 33569312a77SGuillaume La Roque drm_panel_remove(&stk->base); 33669312a77SGuillaume La Roque } 33769312a77SGuillaume La Roque 33869312a77SGuillaume La Roque static struct mipi_dsi_driver stk_panel_driver = { 33969312a77SGuillaume La Roque .driver = { 34069312a77SGuillaume La Roque .name = "panel-startek-kd070fhfid015", 34169312a77SGuillaume La Roque .of_match_table = stk_of_match, 34269312a77SGuillaume La Roque }, 34369312a77SGuillaume La Roque .probe = stk_panel_probe, 34469312a77SGuillaume La Roque .remove = stk_panel_remove, 34569312a77SGuillaume La Roque }; 34669312a77SGuillaume La Roque module_mipi_dsi_driver(stk_panel_driver); 34769312a77SGuillaume La Roque 34869312a77SGuillaume La Roque MODULE_AUTHOR("Guillaume La Roque <glaroque@baylibre.com>"); 34969312a77SGuillaume La Roque MODULE_DESCRIPTION("STARTEK KD070FHFID015"); 35069312a77SGuillaume La Roque MODULE_LICENSE("GPL"); 351