diff options
Diffstat (limited to 'drivers/gpu/drm/panel')
-rw-r--r-- | drivers/gpu/drm/panel/Kconfig | 32 | ||||
-rw-r--r-- | drivers/gpu/drm/panel/Makefile | 3 | ||||
-rw-r--r-- | drivers/gpu/drm/panel/panel-leadtek-ltk500hd1829.c | 531 | ||||
-rw-r--r-- | drivers/gpu/drm/panel/panel-lg-lg4573.c | 2 | ||||
-rw-r--r-- | drivers/gpu/drm/panel/panel-simple.c | 94 | ||||
-rw-r--r-- | drivers/gpu/drm/panel/panel-sony-acx424akp.c | 550 | ||||
-rw-r--r-- | drivers/gpu/drm/panel/panel-xinpeng-xpp055c272.c | 398 |
7 files changed, 1609 insertions, 1 deletions
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index 683ff77a3733..ae44ac2ec106 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -109,6 +109,17 @@ config DRM_PANEL_KINGDISPLAY_KD097D04 24 bit RGB per pixel. It provides a MIPI DSI interface to the host and has a built-in LED backlight. +config DRM_PANEL_LEADTEK_LTK500HD1829 + tristate "Leadtek LTK500HD1829 panel" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y here if you want to enable support for Kingdisplay kd097d04 + TFT-LCD modules. The panel has a 1536x2048 resolution and uses + 24 bit RGB per pixel. It provides a MIPI DSI interface to + the host and has a built-in LED backlight. + config DRM_PANEL_SAMSUNG_LD9040 tristate "Samsung LD9040 RGB/SPI panel" depends on OF && SPI @@ -327,6 +338,17 @@ config DRM_PANEL_SITRONIX_ST7789V Say Y here if you want to enable support for the Sitronix ST7789V controller for 240x320 LCD panels +config DRM_PANEL_SONY_ACX424AKP + tristate "Sony ACX424AKP DSI command mode panel" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + select VIDEOMODE_HELPERS + help + Say Y here if you want to enable the Sony ACX424 display + panel. This panel supports DSI in both command and video + mode. + config DRM_PANEL_SONY_ACX565AKM tristate "Sony ACX565AKM panel" depends on GPIOLIB && OF && SPI @@ -366,4 +388,14 @@ config DRM_PANEL_TRULY_NT35597_WQXGA help Say Y here if you want to enable support for Truly NT35597 WQXGA Dual DSI Video Mode panel + +config DRM_PANEL_XINPENG_XPP055C272 + tristate "Xinpeng XPP055C272 panel driver" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y here if you want to enable support for the Xinpeng + XPP055C272 controller for 720x1280 LCD panels with MIPI/RGB/SPI + system interfaces. endmenu diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile index 8783476110a3..7c4d3c581fd4 100644 --- a/drivers/gpu/drm/panel/Makefile +++ b/drivers/gpu/drm/panel/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9881C) += panel-ilitek-ili9881c.o obj-$(CONFIG_DRM_PANEL_INNOLUX_P079ZCA) += panel-innolux-p079zca.o obj-$(CONFIG_DRM_PANEL_JDI_LT070ME05000) += panel-jdi-lt070me05000.o obj-$(CONFIG_DRM_PANEL_KINGDISPLAY_KD097D04) += panel-kingdisplay-kd097d04.o +obj-$(CONFIG_DRM_PANEL_LEADTEK_LTK500HD1829) += panel-leadtek-ltk500hd1829.o obj-$(CONFIG_DRM_PANEL_LG_LB035Q02) += panel-lg-lb035q02.o obj-$(CONFIG_DRM_PANEL_LG_LG4573) += panel-lg-lg4573.o obj-$(CONFIG_DRM_PANEL_NEC_NL8048HL11) += panel-nec-nl8048hl11.o @@ -34,8 +35,10 @@ obj-$(CONFIG_DRM_PANEL_SHARP_LS037V7DW01) += panel-sharp-ls037v7dw01.o obj-$(CONFIG_DRM_PANEL_SHARP_LS043T1LE01) += panel-sharp-ls043t1le01.o obj-$(CONFIG_DRM_PANEL_SITRONIX_ST7701) += panel-sitronix-st7701.o obj-$(CONFIG_DRM_PANEL_SITRONIX_ST7789V) += panel-sitronix-st7789v.o +obj-$(CONFIG_DRM_PANEL_SONY_ACX424AKP) += panel-sony-acx424akp.o obj-$(CONFIG_DRM_PANEL_SONY_ACX565AKM) += panel-sony-acx565akm.o obj-$(CONFIG_DRM_PANEL_TPO_TD028TTEC1) += panel-tpo-td028ttec1.o obj-$(CONFIG_DRM_PANEL_TPO_TD043MTEA1) += panel-tpo-td043mtea1.o obj-$(CONFIG_DRM_PANEL_TPO_TPG110) += panel-tpo-tpg110.o obj-$(CONFIG_DRM_PANEL_TRULY_NT35597_WQXGA) += panel-truly-nt35597.o +obj-$(CONFIG_DRM_PANEL_XINPENG_XPP055C272) += panel-xinpeng-xpp055c272.o diff --git a/drivers/gpu/drm/panel/panel-leadtek-ltk500hd1829.c b/drivers/gpu/drm/panel/panel-leadtek-ltk500hd1829.c new file mode 100644 index 000000000000..76ecf2de9c44 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-leadtek-ltk500hd1829.c @@ -0,0 +1,531 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2019 Theobroma Systems Design und Consulting GmbH + * + * base on panel-kingdisplay-kd097d04.c + * Copyright (c) 2017, Fuzhou Rockchip Electronics Co., Ltd + */ + +#include <linux/backlight.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regulator/consumer.h> + +#include <video/mipi_display.h> + +#include <drm/drm_crtc.h> +#include <drm/drm_device.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> + +struct ltk500hd1829 { + struct device *dev; + struct drm_panel panel; + struct gpio_desc *reset_gpio; + struct regulator *vcc; + struct regulator *iovcc; + bool prepared; +}; + +struct ltk500hd1829_cmd { + char cmd; + char data; +}; + +/* + * There is no description in the Reference Manual about these commands. + * We received them from the vendor, so just use them as is. + */ +static const struct ltk500hd1829_cmd init_code[] = { + { 0xE0, 0x00 }, + { 0xE1, 0x93 }, + { 0xE2, 0x65 }, + { 0xE3, 0xF8 }, + { 0x80, 0x03 }, + { 0xE0, 0x04 }, + { 0x2D, 0x03 }, + { 0xE0, 0x01 }, + { 0x00, 0x00 }, + { 0x01, 0xB6 }, + { 0x03, 0x00 }, + { 0x04, 0xC5 }, + { 0x17, 0x00 }, + { 0x18, 0xBF }, + { 0x19, 0x01 }, + { 0x1A, 0x00 }, + { 0x1B, 0xBF }, + { 0x1C, 0x01 }, + { 0x1F, 0x7C }, + { 0x20, 0x26 }, + { 0x21, 0x26 }, + { 0x22, 0x4E }, + { 0x37, 0x09 }, + { 0x38, 0x04 }, + { 0x39, 0x08 }, + { 0x3A, 0x1F }, + { 0x3B, 0x1F }, + { 0x3C, 0x78 }, + { 0x3D, 0xFF }, + { 0x3E, 0xFF }, + { 0x3F, 0x00 }, + { 0x40, 0x04 }, + { 0x41, 0xA0 }, + { 0x43, 0x0F }, + { 0x44, 0x0A }, + { 0x45, 0x24 }, + { 0x55, 0x01 }, + { 0x56, 0x01 }, + { 0x57, 0xA5 }, + { 0x58, 0x0A }, + { 0x59, 0x4A }, + { 0x5A, 0x38 }, + { 0x5B, 0x10 }, + { 0x5C, 0x19 }, + { 0x5D, 0x7C }, + { 0x5E, 0x64 }, + { 0x5F, 0x54 }, + { 0x60, 0x48 }, + { 0x61, 0x44 }, + { 0x62, 0x35 }, + { 0x63, 0x3A }, + { 0x64, 0x24 }, + { 0x65, 0x3B }, + { 0x66, 0x39 }, + { 0x67, 0x37 }, + { 0x68, 0x56 }, + { 0x69, 0x41 }, + { 0x6A, 0x47 }, + { 0x6B, 0x2F }, + { 0x6C, 0x23 }, + { 0x6D, 0x13 }, + { 0x6E, 0x02 }, + { 0x6F, 0x08 }, + { 0x70, 0x7C }, + { 0x71, 0x64 }, + { 0x72, 0x54 }, + { 0x73, 0x48 }, + { 0x74, 0x44 }, + { 0x75, 0x35 }, + { 0x76, 0x3A }, + { 0x77, 0x22 }, + { 0x78, 0x3B }, + { 0x79, 0x39 }, + { 0x7A, 0x38 }, + { 0x7B, 0x52 }, + { 0x7C, 0x41 }, + { 0x7D, 0x47 }, + { 0x7E, 0x2F }, + { 0x7F, 0x23 }, + { 0x80, 0x13 }, + { 0x81, 0x02 }, + { 0x82, 0x08 }, + { 0xE0, 0x02 }, + { 0x00, 0x57 }, + { 0x01, 0x77 }, + { 0x02, 0x44 }, + { 0x03, 0x46 }, + { 0x04, 0x48 }, + { 0x05, 0x4A }, + { 0x06, 0x4C }, + { 0x07, 0x4E }, + { 0x08, 0x50 }, + { 0x09, 0x55 }, + { 0x0A, 0x52 }, + { 0x0B, 0x55 }, + { 0x0C, 0x55 }, + { 0x0D, 0x55 }, + { 0x0E, 0x55 }, + { 0x0F, 0x55 }, + { 0x10, 0x55 }, + { 0x11, 0x55 }, + { 0x12, 0x55 }, + { 0x13, 0x40 }, + { 0x14, 0x55 }, + { 0x15, 0x55 }, + { 0x16, 0x57 }, + { 0x17, 0x77 }, + { 0x18, 0x45 }, + { 0x19, 0x47 }, + { 0x1A, 0x49 }, + { 0x1B, 0x4B }, + { 0x1C, 0x4D }, + { 0x1D, 0x4F }, + { 0x1E, 0x51 }, + { 0x1F, 0x55 }, + { 0x20, 0x53 }, + { 0x21, 0x55 }, + { 0x22, 0x55 }, + { 0x23, 0x55 }, + { 0x24, 0x55 }, + { 0x25, 0x55 }, + { 0x26, 0x55 }, + { 0x27, 0x55 }, + { 0x28, 0x55 }, + { 0x29, 0x41 }, + { 0x2A, 0x55 }, + { 0x2B, 0x55 }, + { 0x2C, 0x57 }, + { 0x2D, 0x77 }, + { 0x2E, 0x4F }, + { 0x2F, 0x4D }, + { 0x30, 0x4B }, + { 0x31, 0x49 }, + { 0x32, 0x47 }, + { 0x33, 0x45 }, + { 0x34, 0x41 }, + { 0x35, 0x55 }, + { 0x36, 0x53 }, + { 0x37, 0x55 }, + { 0x38, 0x55 }, + { 0x39, 0x55 }, + { 0x3A, 0x55 }, + { 0x3B, 0x55 }, + { 0x3C, 0x55 }, + { 0x3D, 0x55 }, + { 0x3E, 0x55 }, + { 0x3F, 0x51 }, + { 0x40, 0x55 }, + { 0x41, 0x55 }, + { 0x42, 0x57 }, + { 0x43, 0x77 }, + { 0x44, 0x4E }, + { 0x45, 0x4C }, + { 0x46, 0x4A }, + { 0x47, 0x48 }, + { 0x48, 0x46 }, + { 0x49, 0x44 }, + { 0x4A, 0x40 }, + { 0x4B, 0x55 }, + { 0x4C, 0x52 }, + { 0x4D, 0x55 }, + { 0x4E, 0x55 }, + { 0x4F, 0x55 }, + { 0x50, 0x55 }, + { 0x51, 0x55 }, + { 0x52, 0x55 }, + { 0x53, 0x55 }, + { 0x54, 0x55 }, + { 0x55, 0x50 }, + { 0x56, 0x55 }, + { 0x57, 0x55 }, + { 0x58, 0x40 }, + { 0x59, 0x00 }, + { 0x5A, 0x00 }, + { 0x5B, 0x10 }, + { 0x5C, 0x09 }, + { 0x5D, 0x30 }, + { 0x5E, 0x01 }, + { 0x5F, 0x02 }, + { 0x60, 0x30 }, + { 0x61, 0x03 }, + { 0x62, 0x04 }, + { 0x63, 0x06 }, + { 0x64, 0x6A }, + { 0x65, 0x75 }, + { 0x66, 0x0F }, + { 0x67, 0xB3 }, + { 0x68, 0x0B }, + { 0x69, 0x06 }, + { 0x6A, 0x6A }, + { 0x6B, 0x10 }, + { 0x6C, 0x00 }, + { 0x6D, 0x04 }, + { 0x6E, 0x04 }, + { 0x6F, 0x88 }, + { 0x70, 0x00 }, + { 0x71, 0x00 }, + { 0x72, 0x06 }, + { 0x73, 0x7B }, + { 0x74, 0x00 }, + { 0x75, 0xBC }, + { 0x76, 0x00 }, + { 0x77, 0x05 }, + { 0x78, 0x2E }, + { 0x79, 0x00 }, + { 0x7A, 0x00 }, + { 0x7B, 0x00 }, + { 0x7C, 0x00 }, + { 0x7D, 0x03 }, + { 0x7E, 0x7B }, + { 0xE0, 0x04 }, + { 0x09, 0x10 }, + { 0x2B, 0x2B }, + { 0x2E, 0x44 }, + { 0xE0, 0x00 }, + { 0xE6, 0x02 }, + { 0xE7, 0x02 }, + { 0x35, 0x00 }, +}; + +static inline +struct ltk500hd1829 *panel_to_ltk500hd1829(struct drm_panel *panel) +{ + return container_of(panel, struct ltk500hd1829, panel); +} + +static int ltk500hd1829_unprepare(struct drm_panel *panel) +{ + struct ltk500hd1829 *ctx = panel_to_ltk500hd1829(panel); + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + int ret; + + if (!ctx->prepared) + return 0; + + ret = mipi_dsi_dcs_set_display_off(dsi); + if (ret < 0) + DRM_DEV_ERROR(panel->dev, "failed to set display off: %d\n", + ret); + + ret = mipi_dsi_dcs_enter_sleep_mode(dsi); + if (ret < 0) { + DRM_DEV_ERROR(panel->dev, "failed to enter sleep mode: %d\n", + ret); + } + + /* 120ms to enter sleep mode */ + msleep(120); + + regulator_disable(ctx->iovcc); + regulator_disable(ctx->vcc); + + ctx->prepared = false; + + return 0; +} + +static int ltk500hd1829_prepare(struct drm_panel *panel) +{ + struct ltk500hd1829 *ctx = panel_to_ltk500hd1829(panel); + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + unsigned int i; + int ret; + + if (ctx->prepared) + return 0; + + ret = regulator_enable(ctx->vcc); + if (ret < 0) { + DRM_DEV_ERROR(ctx->dev, + "Failed to enable vci supply: %d\n", ret); + return ret; + } + ret = regulator_enable(ctx->iovcc); + if (ret < 0) { + DRM_DEV_ERROR(ctx->dev, + "Failed to enable iovcc supply: %d\n", ret); + goto disable_vcc; + } + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + /* tRW: 10us */ + usleep_range(10, 20); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + + /* tRT: >= 5ms */ + usleep_range(5000, 6000); + + for (i = 0; i < ARRAY_SIZE(init_code); i++) { + ret = mipi_dsi_generic_write(dsi, &init_code[i], + sizeof(struct ltk500hd1829_cmd)); + if (ret < 0) { + DRM_DEV_ERROR(panel->dev, + "failed to write init cmds: %d\n", ret); + goto disable_iovcc; + } + } + + ret = mipi_dsi_dcs_exit_sleep_mode(dsi); + if (ret < 0) { + DRM_DEV_ERROR(panel->dev, "failed to exit sleep mode: %d\n", + ret); + goto disable_iovcc; + } + + /* 120ms to exit sleep mode */ + msleep(120); + + ret = mipi_dsi_dcs_set_display_on(dsi); + if (ret < 0) { + DRM_DEV_ERROR(panel->dev, "failed to set display on: %d\n", + ret); + goto disable_iovcc; + } + + ctx->prepared = true; + + return 0; + +disable_iovcc: + regulator_disable(ctx->iovcc); +disable_vcc: + regulator_disable(ctx->vcc); + return ret; +} + +static const struct drm_display_mode default_mode = { + .hdisplay = 720, + .hsync_start = 720 + 50, + .hsync_end = 720 + 50 + 50, + .htotal = 720 + 50 + 50 + 50, + .vdisplay = 1280, + .vsync_start = 1280 + 30, + .vsync_end = 1280 + 30 + 4, + .vtotal = 1280 + 30 + 4 + 12, + .vrefresh = 60, + .clock = 41600, + .width_mm = 62, + .height_mm = 110, +}; + +static int ltk500hd1829_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct ltk500hd1829 *ctx = panel_to_ltk500hd1829(panel); + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &default_mode); + if (!mode) { + DRM_DEV_ERROR(ctx->dev, "failed to add mode %ux%ux@%u\n", + default_mode.hdisplay, default_mode.vdisplay, + default_mode.vrefresh); + return -ENOMEM; + } + + drm_mode_set_name(mode); + + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + connector->display_info.width_mm = mode->width_mm; + connector->display_info.height_mm = mode->height_mm; + drm_mode_probed_add(connector, mode); + + return 1; +} + +static const struct drm_panel_funcs ltk500hd1829_funcs = { + .unprepare = ltk500hd1829_unprepare, + .prepare = ltk500hd1829_prepare, + .get_modes = ltk500hd1829_get_modes, +}; + +static int ltk500hd1829_probe(struct mipi_dsi_device *dsi) +{ + struct ltk500hd1829 *ctx; + struct device *dev = &dsi->dev; + int ret; + + ctx = devm_kzalloc(&dsi->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->reset_gpio)) { + DRM_DEV_ERROR(dev, "cannot get reset gpio\n"); + return PTR_ERR(ctx->reset_gpio); + } + + ctx->vcc = devm_regulator_get(dev, "vcc"); + if (IS_ERR(ctx->vcc)) { + ret = PTR_ERR(ctx->vcc); + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(dev, + "Failed to request vcc regulator: %d\n", + ret); + return ret; + } + + ctx->iovcc = devm_regulator_get(dev, "iovcc"); + if (IS_ERR(ctx->iovcc)) { + ret = PTR_ERR(ctx->iovcc); + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(dev, + "Failed to request iovcc regulator: %d\n", + ret); + return ret; + } + + mipi_dsi_set_drvdata(dsi, ctx); + + ctx->dev = dev; + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_EOT_PACKET; + + drm_panel_init(&ctx->panel, &dsi->dev, <k500hd1829_funcs, + DRM_MODE_CONNECTOR_DSI); + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return ret; + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + DRM_DEV_ERROR(dev, "mipi_dsi_attach failed: %d\n", ret); + drm_panel_remove(&ctx->panel); + return ret; + } + + return 0; +} + +static void ltk500hd1829_shutdown(struct mipi_dsi_device *dsi) +{ + struct ltk500hd1829 *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = drm_panel_unprepare(&ctx->panel); + if (ret < 0) + DRM_DEV_ERROR(&dsi->dev, "Failed to unprepare panel: %d\n", + ret); + + ret = drm_panel_disable(&ctx->panel); + if (ret < 0) + DRM_DEV_ERROR(&dsi->dev, "Failed to disable panel: %d\n", + ret); +} + +static int ltk500hd1829_remove(struct mipi_dsi_device *dsi) +{ + struct ltk500hd1829 *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ltk500hd1829_shutdown(dsi); + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + DRM_DEV_ERROR(&dsi->dev, "failed to detach from DSI host: %d\n", + ret); + + drm_panel_remove(&ctx->panel); + + return 0; +} + +static const struct of_device_id ltk500hd1829_of_match[] = { + { .compatible = "leadtek,ltk500hd1829", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ltk500hd1829_of_match); + +static struct mipi_dsi_driver ltk500hd1829_driver = { + .driver = { + .name = "panel-leadtek-ltk500hd1829", + .of_match_table = ltk500hd1829_of_match, + }, + .probe = ltk500hd1829_probe, + .remove = ltk500hd1829_remove, + .shutdown = ltk500hd1829_shutdown, +}; +module_mipi_dsi_driver(ltk500hd1829_driver); + +MODULE_AUTHOR("Heiko Stuebner <heiko.stuebner@theobroma-systems.com>"); +MODULE_DESCRIPTION("Leadtek LTK500HD1829 panel driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-lg-lg4573.c b/drivers/gpu/drm/panel/panel-lg-lg4573.c index 20235ff0bbc4..b262b53dbd85 100644 --- a/drivers/gpu/drm/panel/panel-lg-lg4573.c +++ b/drivers/gpu/drm/panel/panel-lg-lg4573.c @@ -42,7 +42,7 @@ static int lg4573_spi_write_u16(struct lg4573 *ctx, u16 data) struct spi_transfer xfer = { .len = 2, }; - u16 temp = cpu_to_be16(data); + __be16 temp = cpu_to_be16(data); struct spi_message msg; dev_dbg(ctx->panel.dev, "writing data: %x\n", data); diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index ba3f85f36c2f..e14c14ac62b5 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -629,6 +629,35 @@ static const struct panel_desc auo_b101xtn01 = { }, }; +static const struct drm_display_mode auo_b116xak01_mode = { + .clock = 69300, + .hdisplay = 1366, + .hsync_start = 1366 + 48, + .hsync_end = 1366 + 48 + 32, + .htotal = 1366 + 48 + 32 + 10, + .vdisplay = 768, + .vsync_start = 768 + 4, + .vsync_end = 768 + 4 + 6, + .vtotal = 768 + 4 + 6 + 15, + .vrefresh = 60, + .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, +}; + +static const struct panel_desc auo_b116xak01 = { + .modes = &auo_b116xak01_mode, + .num_modes = 1, + .bpc = 6, + .size = { + .width = 256, + .height = 144, + }, + .delay = { + .hpd_absent_delay = 200, + }, + .bus_format = MEDIA_BUS_FMT_RGB666_1X18, + .connector_type = DRM_MODE_CONNECTOR_eDP, +}; + static const struct drm_display_mode auo_b116xw03_mode = { .clock = 70589, .hdisplay = 1366, @@ -1008,6 +1037,38 @@ static const struct panel_desc boe_nv101wxmn51 = { }, }; +static const struct drm_display_mode boe_nv140fhmn49_modes[] = { + { + .clock = 148500, + .hdisplay = 1920, + .hsync_start = 1920 + 48, + .hsync_end = 1920 + 48 + 32, + .htotal = 2200, + .vdisplay = 1080, + .vsync_start = 1080 + 3, + .vsync_end = 1080 + 3 + 5, + .vtotal = 1125, + .vrefresh = 60, + }, +}; + +static const struct panel_desc boe_nv140fhmn49 = { + .modes = boe_nv140fhmn49_modes, + .num_modes = ARRAY_SIZE(boe_nv140fhmn49_modes), + .bpc = 6, + .size = { + .width = 309, + .height = 174, + }, + .delay = { + .prepare = 210, + .enable = 50, + .unprepare = 160, + }, + .bus_format = MEDIA_BUS_FMT_RGB666_1X18, + .connector_type = DRM_MODE_CONNECTOR_eDP, +}; + static const struct drm_display_mode cdtech_s043wq26h_ct7_mode = { .clock = 9000, .hdisplay = 480, @@ -2553,6 +2614,30 @@ static const struct panel_desc samsung_ltn140at29_301 = { }, }; +static const struct display_timing satoz_sat050at40h12r2_timing = { + .pixelclock = {33300000, 33300000, 50000000}, + .hactive = {800, 800, 800}, + .hfront_porch = {16, 210, 354}, + .hback_porch = {46, 46, 46}, + .hsync_len = {1, 1, 40}, + .vactive = {480, 480, 480}, + .vfront_porch = {7, 22, 147}, + .vback_porch = {23, 23, 23}, + .vsync_len = {1, 1, 20}, +}; + +static const struct panel_desc satoz_sat050at40h12r2 = { + .timings = &satoz_sat050at40h12r2_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 108, + .height = 65, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + static const struct drm_display_mode sharp_ld_d5116z01b_mode = { .clock = 168480, .hdisplay = 1920, @@ -3126,6 +3211,9 @@ static const struct of_device_id platform_of_match[] = { .compatible = "auo,b101xtn01", .data = &auo_b101xtn01, }, { + .compatible = "auo,b116xa01", + .data = &auo_b116xak01, + }, { .compatible = "auo,b116xw03", .data = &auo_b116xw03, }, { @@ -3168,6 +3256,9 @@ static const struct of_device_id platform_of_match[] = { .compatible = "boe,nv101wxmn51", .data = &boe_nv101wxmn51, }, { + .compatible = "boe,nv140fhmn49", + .data = &boe_nv140fhmn49, + }, { .compatible = "cdtech,s043wq26h-ct7", .data = &cdtech_s043wq26h_ct7, }, { @@ -3357,6 +3448,9 @@ static const struct of_device_id platform_of_match[] = { .compatible = "samsung,ltn140at29-301", .data = &samsung_ltn140at29_301, }, { + .compatible = "satoz,sat050at40h12r2", + .data = &satoz_sat050at40h12r2, + }, { .compatible = "sharp,ld-d5116z01b", .data = &sharp_ld_d5116z01b, }, { diff --git a/drivers/gpu/drm/panel/panel-sony-acx424akp.c b/drivers/gpu/drm/panel/panel-sony-acx424akp.c new file mode 100644 index 000000000000..de0abf76ae6f --- /dev/null +++ b/drivers/gpu/drm/panel/panel-sony-acx424akp.c @@ -0,0 +1,550 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * MIPI-DSI Sony ACX424AKP panel driver. This is a 480x864 + * AMOLED panel with a command-only DSI interface. + * + * Copyright (C) Linaro Ltd. 2019 + * Author: Linus Walleij + * Based on code and know-how from Marcus Lorentzon + * Copyright (C) ST-Ericsson SA 2010 + */ +#include <linux/backlight.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regulator/consumer.h> + +#include <video/mipi_display.h> + +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> + +#define ACX424_DCS_READ_ID1 0xDA +#define ACX424_DCS_READ_ID2 0xDB +#define ACX424_DCS_READ_ID3 0xDC +#define ACX424_DCS_SET_MDDI 0xAE + +/* + * Sony seems to use vendor ID 0x81 + */ +#define DISPLAY_SONY_ACX424AKP_ID1 0x811b +#define DISPLAY_SONY_ACX424AKP_ID2 0x811a +/* + * The third ID looks like a bug, vendor IDs begin at 0x80 + * and panel 00 ... seems like default values. + */ +#define DISPLAY_SONY_ACX424AKP_ID3 0x8000 + +struct acx424akp { + struct drm_panel panel; + struct device *dev; + struct backlight_device *bl; + struct regulator *supply; + struct gpio_desc *reset_gpio; + bool video_mode; +}; + +static const struct drm_display_mode sony_acx424akp_vid_mode = { + .clock = 330000, + .hdisplay = 480, + .hsync_start = 480 + 15, + .hsync_end = 480 + 15 + 0, + .htotal = 480 + 15 + 0 + 15, + .vdisplay = 864, + .vsync_start = 864 + 14, + .vsync_end = 864 + 14 + 1, + .vtotal = 864 + 14 + 1 + 11, + .vrefresh = 60, + .width_mm = 48, + .height_mm = 84, + .flags = DRM_MODE_FLAG_PVSYNC, +}; + +/* + * The timings are not very helpful as the display is used in + * command mode using the maximum HS frequency. + */ +static const struct drm_display_mode sony_acx424akp_cmd_mode = { + .clock = 420160, + .hdisplay = 480, + .hsync_start = 480 + 154, + .hsync_end = 480 + 154 + 16, + .htotal = 480 + 154 + 16 + 32, + .vdisplay = 864, + .vsync_start = 864 + 1, + .vsync_end = 864 + 1 + 1, + .vtotal = 864 + 1 + 1 + 1, + /* + * Some desired refresh rate, experiments at the maximum "pixel" + * clock speed (HS clock 420 MHz) yields around 117Hz. + */ + .vrefresh = 60, + .width_mm = 48, + .height_mm = 84, +}; + +static inline struct acx424akp *panel_to_acx424akp(struct drm_panel *panel) +{ + return container_of(panel, struct acx424akp, panel); +} + +#define FOSC 20 /* 20Mhz */ +#define SCALE_FACTOR_NS_DIV_MHZ 1000 + +static int acx424akp_set_brightness(struct backlight_device *bl) +{ + struct acx424akp *acx = bl_get_data(bl); + struct mipi_dsi_device *dsi = to_mipi_dsi_device(acx->dev); + int period_ns = 1023; + int duty_ns = bl->props.brightness; + u8 pwm_ratio; + u8 pwm_div; + u8 par; + int ret; + + /* Calculate the PWM duty cycle in n/256's */ + pwm_ratio = max(((duty_ns * 256) / period_ns) - 1, 1); + pwm_div = max(1, + ((FOSC * period_ns) / 256) / + SCALE_FACTOR_NS_DIV_MHZ); + + /* Set up PWM dutycycle ONE byte (differs from the standard) */ + DRM_DEV_DEBUG(acx->dev, "calculated duty cycle %02x\n", pwm_ratio); + ret = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_DISPLAY_BRIGHTNESS, + &pwm_ratio, 1); + if (ret < 0) { + DRM_DEV_ERROR(acx->dev, + "failed to set display PWM ratio (%d)\n", + ret); + return ret; + } + + /* + * Sequence to write PWMDIV: + * address data + * 0xF3 0xAA CMD2 Unlock + * 0x00 0x01 Enter CMD2 page 0 + * 0X7D 0x01 No reload MTP of CMD2 P1 + * 0x22 PWMDIV + * 0x7F 0xAA CMD2 page 1 lock + */ + par = 0xaa; + ret = mipi_dsi_dcs_write(dsi, 0xf3, &par, 1); + if (ret < 0) { + DRM_DEV_ERROR(acx->dev, + "failed to unlock CMD 2 (%d)\n", + ret); + return ret; + } + par = 0x01; + ret = mipi_dsi_dcs_write(dsi, 0x00, &par, 1); + if (ret < 0) { + DRM_DEV_ERROR(acx->dev, + "failed to enter page 1 (%d)\n", + ret); + return ret; + } + par = 0x01; + ret = mipi_dsi_dcs_write(dsi, 0x7d, &par, 1); + if (ret < 0) { + DRM_DEV_ERROR(acx->dev, + "failed to disable MTP reload (%d)\n", + ret); + return ret; + } + ret = mipi_dsi_dcs_write(dsi, 0x22, &pwm_div, 1); + if (ret < 0) { + DRM_DEV_ERROR(acx->dev, + "failed to set PWM divisor (%d)\n", + ret); + return ret; + } + par = 0xaa; + ret = mipi_dsi_dcs_write(dsi, 0x7f, &par, 1); + if (ret < 0) { + DRM_DEV_ERROR(acx->dev, + "failed to lock CMD 2 (%d)\n", + ret); + return ret; + } + + /* Enable backlight */ + par = 0x24; + ret = mipi_dsi_dcs_write(dsi, MIPI_DCS_WRITE_CONTROL_DISPLAY, + &par, 1); + if (ret < 0) { + DRM_DEV_ERROR(acx->dev, + "failed to enable display backlight (%d)\n", + ret); + return ret; + } + + return 0; +} + +static const struct backlight_ops acx424akp_bl_ops = { + .update_status = acx424akp_set_brightness, +}; + +static int acx424akp_read_id(struct acx424akp *acx) +{ + struct mipi_dsi_device *dsi = to_mipi_dsi_device(acx->dev); + u8 vendor, version, panel; + u16 val; + int ret; + + ret = mipi_dsi_dcs_read(dsi, ACX424_DCS_READ_ID1, &vendor, 1); + if (ret < 0) { + DRM_DEV_ERROR(acx->dev, "could not vendor ID byte\n"); + return ret; + } + ret = mipi_dsi_dcs_read(dsi, ACX424_DCS_READ_ID2, &version, 1); + if (ret < 0) { + DRM_DEV_ERROR(acx->dev, "could not read device version byte\n"); + return ret; + } + ret = mipi_dsi_dcs_read(dsi, ACX424_DCS_READ_ID3, &panel, 1); + if (ret < 0) { + DRM_DEV_ERROR(acx->dev, "could not read panel ID byte\n"); + return ret; + } + + if (vendor == 0x00) { + DRM_DEV_ERROR(acx->dev, "device vendor ID is zero\n"); + return -ENODEV; + } + + val = (vendor << 8) | panel; + switch (val) { + case DISPLAY_SONY_ACX424AKP_ID1: + case DISPLAY_SONY_ACX424AKP_ID2: + case DISPLAY_SONY_ACX424AKP_ID3: + DRM_DEV_INFO(acx->dev, + "MTP vendor: %02x, version: %02x, panel: %02x\n", + vendor, version, panel); + break; + default: + DRM_DEV_INFO(acx->dev, + "unknown vendor: %02x, version: %02x, panel: %02x\n", + vendor, version, panel); + break; + } + + return 0; +} + +static int acx424akp_power_on(struct acx424akp *acx) +{ + int ret; + + ret = regulator_enable(acx->supply); + if (ret) { + DRM_DEV_ERROR(acx->dev, "failed to enable supply (%d)\n", ret); + return ret; + } + + /* Assert RESET */ + gpiod_set_value_cansleep(acx->reset_gpio, 1); + udelay(20); + /* De-assert RESET */ + gpiod_set_value_cansleep(acx->reset_gpio, 0); + usleep_range(11000, 20000); + + return 0; +} + +static void acx424akp_power_off(struct acx424akp *acx) +{ + /* Assert RESET */ + gpiod_set_value_cansleep(acx->reset_gpio, 1); + usleep_range(11000, 20000); + + regulator_disable(acx->supply); +} + +static int acx424akp_prepare(struct drm_panel *panel) +{ + struct acx424akp *acx = panel_to_acx424akp(panel); + struct mipi_dsi_device *dsi = to_mipi_dsi_device(acx->dev); + const u8 mddi = 3; + int ret; + + ret = acx424akp_power_on(acx); + if (ret) + return ret; + + ret = acx424akp_read_id(acx); + if (ret) { + DRM_DEV_ERROR(acx->dev, "failed to read panel ID (%d)\n", ret); + goto err_power_off; + } + + /* Enabe tearing mode: send TE (tearing effect) at VBLANK */ + ret = mipi_dsi_dcs_set_tear_on(dsi, + MIPI_DSI_DCS_TEAR_MODE_VBLANK); + if (ret) { + DRM_DEV_ERROR(acx->dev, "failed to enable vblank TE (%d)\n", + ret); + goto err_power_off; + } + + /* + * Set MDDI + * + * This presumably deactivates the Qualcomm MDDI interface and + * selects DSI, similar code is found in other drivers such as the + * Sharp LS043T1LE01 which makes us suspect that this panel may be + * using a Novatek NT35565 or similar display driver chip that shares + * this command. Due to the lack of documentation we cannot know for + * sure. + */ + ret = mipi_dsi_dcs_write(dsi, ACX424_DCS_SET_MDDI, + &mddi, sizeof(mddi)); + if (ret < 0) { + DRM_DEV_ERROR(acx->dev, "failed to set MDDI (%d)\n", ret); + goto err_power_off; + } + + /* Exit sleep mode */ + ret = mipi_dsi_dcs_exit_sleep_mode(dsi); + if (ret) { + DRM_DEV_ERROR(acx->dev, "failed to exit sleep mode (%d)\n", + ret); + goto err_power_off; + } + msleep(140); + + ret = mipi_dsi_dcs_set_display_on(dsi); + if (ret) { + DRM_DEV_ERROR(acx->dev, "failed to turn display on (%d)\n", + ret); + goto err_power_off; + } + if (acx->video_mode) { + /* In video mode turn peripheral on */ + ret = mipi_dsi_turn_on_peripheral(dsi); + if (ret) { + dev_err(acx->dev, "failed to turn on peripheral\n"); + goto err_power_off; + } + } + + acx->bl->props.power = FB_BLANK_NORMAL; + + return 0; + +err_power_off: + acx424akp_power_off(acx); + return ret; +} + +static int acx424akp_unprepare(struct drm_panel *panel) +{ + struct acx424akp *acx = panel_to_acx424akp(panel); + struct mipi_dsi_device *dsi = to_mipi_dsi_device(acx->dev); + u8 par; + int ret; + + /* Disable backlight */ + par = 0x00; + ret = mipi_dsi_dcs_write(dsi, MIPI_DCS_WRITE_CONTROL_DISPLAY, + &par, 1); + if (ret) { + DRM_DEV_ERROR(acx->dev, + "failed to disable display backlight (%d)\n", + ret); + return ret; + } + + ret = mipi_dsi_dcs_set_display_off(dsi); + if (ret) { + DRM_DEV_ERROR(acx->dev, "failed to turn display off (%d)\n", + ret); + return ret; + } + + /* Enter sleep mode */ + ret = mipi_dsi_dcs_enter_sleep_mode(dsi); + if (ret) { + DRM_DEV_ERROR(acx->dev, "failed to enter sleep mode (%d)\n", + ret); + return ret; + } + msleep(85); + + acx424akp_power_off(acx); + acx->bl->props.power = FB_BLANK_POWERDOWN; + + return 0; +} + +static int acx424akp_enable(struct drm_panel *panel) +{ + struct acx424akp *acx = panel_to_acx424akp(panel); + + /* + * The backlight is on as long as the display is on + * so no use to call backlight_enable() here. + */ + acx->bl->props.power = FB_BLANK_UNBLANK; + + return 0; +} + +static int acx424akp_disable(struct drm_panel *panel) +{ + struct acx424akp *acx = panel_to_acx424akp(panel); + + /* + * The backlight is on as long as the display is on + * so no use to call backlight_disable() here. + */ + acx->bl->props.power = FB_BLANK_NORMAL; + + return 0; +} + +static int acx424akp_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct acx424akp *acx = panel_to_acx424akp(panel); + struct drm_display_mode *mode; + + if (acx->video_mode) + mode = drm_mode_duplicate(connector->dev, + &sony_acx424akp_vid_mode); + else + mode = drm_mode_duplicate(connector->dev, + &sony_acx424akp_cmd_mode); + if (!mode) { + DRM_ERROR("bad mode or failed to add mode\n"); + return -EINVAL; + } + drm_mode_set_name(mode); + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + + connector->display_info.width_mm = mode->width_mm; + connector->display_info.height_mm = mode->height_mm; + + drm_mode_probed_add(connector, mode); + + return 1; /* Number of modes */ +} + +static const struct drm_panel_funcs acx424akp_drm_funcs = { + .disable = acx424akp_disable, + .unprepare = acx424akp_unprepare, + .prepare = acx424akp_prepare, + .enable = acx424akp_enable, + .get_modes = acx424akp_get_modes, +}; + +static int acx424akp_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct acx424akp *acx; + int ret; + + acx = devm_kzalloc(dev, sizeof(struct acx424akp), GFP_KERNEL); + if (!acx) + return -ENOMEM; + acx->video_mode = of_property_read_bool(dev->of_node, + "enforce-video-mode"); + + mipi_dsi_set_drvdata(dsi, acx); + acx->dev = dev; + + dsi->lanes = 2; + dsi->format = MIPI_DSI_FMT_RGB888; + /* + * FIXME: these come from the ST-Ericsson vendor driver for the + * HREF520 and seems to reflect limitations in the PLLs on that + * platform, if you have the datasheet, please cross-check the + * actual max rates. + */ + dsi->lp_rate = 19200000; + dsi->hs_rate = 420160000; + + if (acx->video_mode) + /* Burst mode using event for sync */ + dsi->mode_flags = + MIPI_DSI_MODE_VIDEO | + MIPI_DSI_MODE_VIDEO_BURST; + else + dsi->mode_flags = + MIPI_DSI_CLOCK_NON_CONTINUOUS | + MIPI_DSI_MODE_EOT_PACKET; + + acx->supply = devm_regulator_get(dev, "vddi"); + if (IS_ERR(acx->supply)) + return PTR_ERR(acx->supply); + + /* This asserts RESET by default */ + acx->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(acx->reset_gpio)) { + ret = PTR_ERR(acx->reset_gpio); + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(dev, "failed to request GPIO (%d)\n", + ret); + return ret; + } + + drm_panel_init(&acx->panel, dev, &acx424akp_drm_funcs, + DRM_MODE_CONNECTOR_DSI); + + acx->bl = devm_backlight_device_register(dev, "acx424akp", dev, acx, + &acx424akp_bl_ops, NULL); + if (IS_ERR(acx->bl)) { + DRM_DEV_ERROR(dev, "failed to register backlight device\n"); + return PTR_ERR(acx->bl); + } + acx->bl->props.max_brightness = 1023; + acx->bl->props.brightness = 512; + acx->bl->props.power = FB_BLANK_POWERDOWN; + + ret = drm_panel_add(&acx->panel); + if (ret < 0) + return ret; + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + drm_panel_remove(&acx->panel); + return ret; + } + + return 0; +} + +static int acx424akp_remove(struct mipi_dsi_device *dsi) +{ + struct acx424akp *acx = mipi_dsi_get_drvdata(dsi); + + mipi_dsi_detach(dsi); + drm_panel_remove(&acx->panel); + + return 0; +} + +static const struct of_device_id acx424akp_of_match[] = { + { .compatible = "sony,acx424akp" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, acx424akp_of_match); + +static struct mipi_dsi_driver acx424akp_driver = { + .probe = acx424akp_probe, + .remove = acx424akp_remove, + .driver = { + .name = "panel-sony-acx424akp", + .of_match_table = acx424akp_of_match, + }, +}; +module_mipi_dsi_driver(acx424akp_driver); + +MODULE_AUTHOR("Linus Wallei <linus.walleij@linaro.org>"); +MODULE_DESCRIPTION("MIPI-DSI Sony acx424akp Panel Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-xinpeng-xpp055c272.c b/drivers/gpu/drm/panel/panel-xinpeng-xpp055c272.c new file mode 100644 index 000000000000..1645aceab597 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-xinpeng-xpp055c272.c @@ -0,0 +1,398 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Xinpeng xpp055c272 5.5" MIPI-DSI panel driver + * Copyright (C) 2019 Theobroma Systems Design und Consulting GmbH + * + * based on + * + * Rockteck jh057n00900 5.5" MIPI-DSI panel driver + * Copyright (C) Purism SPC 2019 + */ + +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> + +#include <video/display_timing.h> +#include <video/mipi_display.h> + +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/media-bus-format.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regulator/consumer.h> + +/* Manufacturer specific Commands send via DSI */ +#define XPP055C272_CMD_ALL_PIXEL_OFF 0x22 +#define XPP055C272_CMD_ALL_PIXEL_ON 0x23 +#define XPP055C272_CMD_SETDISP 0xb2 +#define XPP055C272_CMD_SETRGBIF 0xb3 +#define XPP055C272_CMD_SETCYC 0xb4 +#define XPP055C272_CMD_SETBGP 0xb5 +#define XPP055C272_CMD_SETVCOM 0xb6 +#define XPP055C272_CMD_SETOTP 0xb7 +#define XPP055C272_CMD_SETPOWER_EXT 0xb8 +#define XPP055C272_CMD_SETEXTC 0xb9 +#define XPP055C272_CMD_SETMIPI 0xbA +#define XPP055C272_CMD_SETVDC 0xbc +#define XPP055C272_CMD_SETPCR 0xbf +#define XPP055C272_CMD_SETSCR 0xc0 +#define XPP055C272_CMD_SETPOWER 0xc1 +#define XPP055C272_CMD_SETECO 0xc6 +#define XPP055C272_CMD_SETPANEL 0xcc +#define XPP055C272_CMD_SETGAMMA 0xe0 +#define XPP055C272_CMD_SETEQ 0xe3 +#define XPP055C272_CMD_SETGIP1 0xe9 +#define XPP055C272_CMD_SETGIP2 0xea + +struct xpp055c272 { + struct device *dev; + struct drm_panel panel; + struct gpio_desc *reset_gpio; + struct regulator *vci; + struct regulator *iovcc; + bool prepared; +}; + +static inline struct xpp055c272 *panel_to_xpp055c272(struct drm_panel *panel) +{ + return container_of(panel, struct xpp055c272, panel); +} + +#define dsi_generic_write_seq(dsi, cmd, seq...) do { \ + static const u8 d[] = { seq }; \ + int ret; \ + ret = mipi_dsi_dcs_write(dsi, cmd, d, ARRAY_SIZE(d)); \ + if (ret < 0) \ + return ret; \ + } while (0) + +static int xpp055c272_init_sequence(struct xpp055c272 *ctx) +{ + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + struct device *dev = ctx->dev; + + /* + * Init sequence was supplied by the panel vendor without much + * documentation. + */ + dsi_generic_write_seq(dsi, XPP055C272_CMD_SETEXTC, 0xf1, 0x12, 0x83); + dsi_generic_write_seq(dsi, XPP055C272_CMD_SETMIPI, + 0x33, 0x81, 0x05, 0xf9, 0x0e, 0x0e, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x25, + 0x00, 0x91, 0x0a, 0x00, 0x00, 0x02, 0x4f, 0x01, + 0x00, 0x00, 0x37); + dsi_generic_write_seq(dsi, XPP055C272_CMD_SETPOWER_EXT, 0x25); + dsi_generic_write_seq(dsi, XPP055C272_CMD_SETPCR, 0x02, 0x11, 0x00); + dsi_generic_write_seq(dsi, XPP055C272_CMD_SETRGBIF, + 0x0c, 0x10, 0x0a, 0x50, 0x03, 0xff, 0x00, 0x00, + 0x00, 0x00); + dsi_generic_write_seq(dsi, XPP055C272_CMD_SETSCR, + 0x73, 0x73, 0x50, 0x50, 0x00, 0x00, 0x08, 0x70, + 0x00); + dsi_generic_write_seq(dsi, XPP055C272_CMD_SETVDC, 0x46); + dsi_generic_write_seq(dsi, XPP055C272_CMD_SETPANEL, 0x0b); + dsi_generic_write_seq(dsi, XPP055C272_CMD_SETCYC, 0x80); + dsi_generic_write_seq(dsi, XPP055C272_CMD_SETDISP, 0xc8, 0x12, 0x30); + dsi_generic_write_seq(dsi, XPP055C272_CMD_SETEQ, + 0x07, 0x07, 0x0B, 0x0B, 0x03, 0x0B, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0x00, 0xC0, 0x10); + dsi_generic_write_seq(dsi, XPP055C272_CMD_SETPOWER, + 0x53, 0x00, 0x1e, 0x1e, 0x77, 0xe1, 0xcc, 0xdd, + 0x67, 0x77, 0x33, 0x33); + dsi_generic_write_seq(dsi, XPP055C272_CMD_SETECO, 0x00, 0x00, 0xff, + 0xff, 0x01, 0xff); + dsi_generic_write_seq(dsi, XPP055C272_CMD_SETBGP, 0x09, 0x09); + msleep(20); + + dsi_generic_write_seq(dsi, XPP055C272_CMD_SETVCOM, 0x87, 0x95); + dsi_generic_write_seq(dsi, XPP055C272_CMD_SETGIP1, + 0xc2, 0x10, 0x05, 0x05, 0x10, 0x05, 0xa0, 0x12, + 0x31, 0x23, 0x3f, 0x81, 0x0a, 0xa0, 0x37, 0x18, + 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x80, + 0x01, 0x00, 0x00, 0x00, 0x48, 0xf8, 0x86, 0x42, + 0x08, 0x88, 0x88, 0x80, 0x88, 0x88, 0x88, 0x58, + 0xf8, 0x87, 0x53, 0x18, 0x88, 0x88, 0x81, 0x88, + 0x88, 0x88, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + dsi_generic_write_seq(dsi, XPP055C272_CMD_SETGIP2, + 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1f, 0x88, 0x81, 0x35, + 0x78, 0x88, 0x88, 0x85, 0x88, 0x88, 0x88, 0x0f, + 0x88, 0x80, 0x24, 0x68, 0x88, 0x88, 0x84, 0x88, + 0x88, 0x88, 0x23, 0x10, 0x00, 0x00, 0x1c, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x05, + 0xa0, 0x00, 0x00, 0x00, 0x00); + dsi_generic_write_seq(dsi, XPP055C272_CMD_SETGAMMA, + 0x00, 0x06, 0x08, 0x2a, 0x31, 0x3f, 0x38, 0x36, + 0x07, 0x0c, 0x0d, 0x11, 0x13, 0x12, 0x13, 0x11, + 0x18, 0x00, 0x06, 0x08, 0x2a, 0x31, 0x3f, 0x38, + 0x36, 0x07, 0x0c, 0x0d, 0x11, 0x13, 0x12, 0x13, + 0x11, 0x18); + + msleep(60); + + DRM_DEV_DEBUG_DRIVER(dev, "Panel init sequence done\n"); + return 0; +} + +static int xpp055c272_unprepare(struct drm_panel *panel) +{ + struct xpp055c272 *ctx = panel_to_xpp055c272(panel); + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + int ret; + + if (!ctx->prepared) + return 0; + + ret = mipi_dsi_dcs_set_display_off(dsi); + if (ret < 0) + DRM_DEV_ERROR(ctx->dev, "failed to set display off: %d\n", + ret); + + mipi_dsi_dcs_enter_sleep_mode(dsi); + if (ret < 0) { + DRM_DEV_ERROR(ctx->dev, "failed to enter sleep mode: %d\n", + ret); + return ret; + } + + regulator_disable(ctx->iovcc); + regulator_disable(ctx->vci); + + ctx->prepared = false; + + return 0; +} + +static int xpp055c272_prepare(struct drm_panel *panel) +{ + struct xpp055c272 *ctx = panel_to_xpp055c272(panel); + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + int ret; + + if (ctx->prepared) + return 0; + + DRM_DEV_DEBUG_DRIVER(ctx->dev, "Resetting the panel\n"); + ret = regulator_enable(ctx->vci); + if (ret < 0) { + DRM_DEV_ERROR(ctx->dev, + "Failed to enable vci supply: %d\n", ret); + return ret; + } + ret = regulator_enable(ctx->iovcc); + if (ret < 0) { + DRM_DEV_ERROR(ctx->dev, + "Failed to enable iovcc supply: %d\n", ret); + goto disable_vci; + } + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + /* T6: 10us */ + usleep_range(10, 20); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + + /* T8: 20ms */ + msleep(20); + + ret = xpp055c272_init_sequence(ctx); + if (ret < 0) { + DRM_DEV_ERROR(ctx->dev, "Panel init sequence failed: %d\n", + ret); + goto disable_iovcc; + } + + ret = mipi_dsi_dcs_exit_sleep_mode(dsi); + if (ret < 0) { + DRM_DEV_ERROR(ctx->dev, "Failed to exit sleep mode: %d\n", ret); + goto disable_iovcc; + } + + /* T9: 120ms */ + msleep(120); + + ret = mipi_dsi_dcs_set_display_on(dsi); + if (ret < 0) { + DRM_DEV_ERROR(ctx->dev, "Failed to set display on: %d\n", ret); + goto disable_iovcc; + } + + msleep(50); + + ctx->prepared = true; + + return 0; + +disable_iovcc: + regulator_disable(ctx->iovcc); +disable_vci: + regulator_disable(ctx->vci); + return ret; +} + +static const struct drm_display_mode default_mode = { + .hdisplay = 720, + .hsync_start = 720 + 40, + .hsync_end = 720 + 40 + 10, + .htotal = 720 + 40 + 10 + 40, + .vdisplay = 1280, + .vsync_start = 1280 + 22, + .vsync_end = 1280 + 22 + 4, + .vtotal = 1280 + 22 + 4 + 11, + .vrefresh = 60, + .clock = 64000, + .width_mm = 68, + .height_mm = 121, +}; + +static int xpp055c272_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct xpp055c272 *ctx = panel_to_xpp055c272(panel); + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &default_mode); + if (!mode) { + DRM_DEV_ERROR(ctx->dev, "Failed to add mode %ux%u@%u\n", + default_mode.hdisplay, default_mode.vdisplay, + default_mode.vrefresh); + return -ENOMEM; + } + + drm_mode_set_name(mode); + + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + connector->display_info.width_mm = mode->width_mm; + connector->display_info.height_mm = mode->height_mm; + drm_mode_probed_add(connector, mode); + + return 1; +} + +static const struct drm_panel_funcs xpp055c272_funcs = { + .unprepare = xpp055c272_unprepare, + .prepare = xpp055c272_prepare, + .get_modes = xpp055c272_get_modes, +}; + +static int xpp055c272_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct xpp055c272 *ctx; + int ret; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->reset_gpio)) { + DRM_DEV_ERROR(dev, "cannot get reset gpio\n"); + return PTR_ERR(ctx->reset_gpio); + } + + ctx->vci = devm_regulator_get(dev, "vci"); + if (IS_ERR(ctx->vci)) { + ret = PTR_ERR(ctx->vci); + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(dev, + "Failed to request vci regulator: %d\n", + ret); + return ret; + } + + ctx->iovcc = devm_regulator_get(dev, "iovcc"); + if (IS_ERR(ctx->iovcc)) { + ret = PTR_ERR(ctx->iovcc); + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(dev, + "Failed to request iovcc regulator: %d\n", + ret); + return ret; + } + + mipi_dsi_set_drvdata(dsi, ctx); + + ctx->dev = dev; + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_EOT_PACKET; + + drm_panel_init(&ctx->panel, &dsi->dev, &xpp055c272_funcs, + DRM_MODE_CONNECTOR_DSI); + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return ret; + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + DRM_DEV_ERROR(dev, "mipi_dsi_attach failed: %d\n", ret); + drm_panel_remove(&ctx->panel); + return ret; + } + + return 0; +} + +static void xpp055c272_shutdown(struct mipi_dsi_device *dsi) +{ + struct xpp055c272 *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = drm_panel_unprepare(&ctx->panel); + if (ret < 0) + DRM_DEV_ERROR(&dsi->dev, "Failed to unprepare panel: %d\n", + ret); + + ret = drm_panel_disable(&ctx->panel); + if (ret < 0) + DRM_DEV_ERROR(&dsi->dev, "Failed to disable panel: %d\n", + ret); +} + +static int xpp055c272_remove(struct mipi_dsi_device *dsi) +{ + struct xpp055c272 *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + xpp055c272_shutdown(dsi); + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + DRM_DEV_ERROR(&dsi->dev, "Failed to detach from DSI host: %d\n", + ret); + + drm_panel_remove(&ctx->panel); + + return 0; +} + +static const struct of_device_id xpp055c272_of_match[] = { + { .compatible = "xinpeng,xpp055c272" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, xpp055c272_of_match); + +static struct mipi_dsi_driver xpp055c272_driver = { + .driver = { + .name = "panel-xinpeng-xpp055c272", + .of_match_table = xpp055c272_of_match, + }, + .probe = xpp055c272_probe, + .remove = xpp055c272_remove, + .shutdown = xpp055c272_shutdown, +}; +module_mipi_dsi_driver(xpp055c272_driver); + +MODULE_AUTHOR("Heiko Stuebner <heiko.stuebner@theobroma-systems.com>"); +MODULE_DESCRIPTION("DRM driver for Xinpeng xpp055c272 MIPI DSI panel"); +MODULE_LICENSE("GPL v2"); |