diff options
Diffstat (limited to 'drivers/gpu/drm/panel')
45 files changed, 6893 insertions, 682 deletions
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index d9d931aa6e26..ae44ac2ec106 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -18,6 +18,17 @@ config DRM_PANEL_ARM_VERSATILE reference designs. The panel is detected using special registers in the Versatile family syscon registers. +config DRM_PANEL_BOE_HIMAX8279D + tristate "Boe Himax8279d 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 Boe Himax8279d + TFT-LCD modules. The panel has a 1200x1920 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_LVDS tristate "Generic LVDS panel driver" depends on OF @@ -98,11 +109,30 @@ 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 select VIDEOMODE_HELPERS +config DRM_PANEL_LG_LB035Q02 + tristate "LG LB035Q024573 RGB panel" + depends on GPIOLIB && OF && SPI + help + Say Y here if you want to enable support for the LB035Q02 RGB panel + (found on the Gumstix Overo Palo35 board). To compile this driver as + a module, choose M here. + config DRM_PANEL_LG_LG4573 tristate "LG4573 RGB/SPI panel" depends on OF && SPI @@ -111,6 +141,23 @@ config DRM_PANEL_LG_LG4573 Say Y here if you want to enable support for LG4573 RGB panel. To compile this driver as a module, choose M here. +config DRM_PANEL_NEC_NL8048HL11 + tristate "NEC NL8048HL11 RGB panel" + depends on GPIOLIB && OF && SPI + help + Say Y here if you want to enable support for the NEC NL8048HL11 RGB + panel (found on the Zoom2/3/3630 SDP boards). To compile this driver + as a module, choose M here. + +config DRM_PANEL_NOVATEK_NT39016 + tristate "Novatek NT39016 RGB/SPI panel" + depends on OF && SPI + depends on BACKLIGHT_CLASS_DEVICE + select REGMAP_SPI + help + Say Y here if you want to enable support for the panels built + around the Novatek NT39016 display controller. + config DRM_PANEL_OLIMEX_LCD_OLINUXINO tristate "Olimex LCD-OLinuXino panel" depends on OF @@ -159,6 +206,15 @@ config DRM_PANEL_RASPBERRYPI_TOUCHSCREEN Pi 7" Touchscreen. To compile this driver as a module, choose M here. +config DRM_PANEL_RAYDIUM_RM67191 + tristate "Raydium RM67191 FHD 1080x1920 DSI video mode 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 Raydium RM67191 FHD + (1080x1920) DSI panel. + config DRM_PANEL_RAYDIUM_RM68200 tristate "Raydium RM68200 720x1280 DSI video mode panel" depends on OF @@ -248,6 +304,13 @@ config DRM_PANEL_SHARP_LQ101R1SX01 To compile this driver as a module, choose M here: the module will be called panel-sharp-lq101r1sx01. +config DRM_PANEL_SHARP_LS037V7DW01 + tristate "Sharp LS037V7DW01 VGA LCD panel" + depends on GPIOLIB && OF && REGULATOR + help + Say Y here if you want to enable support for Sharp LS037V7DW01 VGA + (480x640) LCD panel (found on the TI SDP3430 board). + config DRM_PANEL_SHARP_LS043T1LE01 tristate "Sharp LS043T1LE01 qHD video mode panel" depends on OF @@ -275,6 +338,40 @@ 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 + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y here if you want to enable support for the Sony ACX565AKM + 800x600 3.5" panel (found on the Nokia N900). + +config DRM_PANEL_TPO_TD028TTEC1 + tristate "Toppoly (TPO) TD028TTEC1 panel driver" + depends on OF && SPI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y here if you want to enable support for TPO TD028TTEC1 480x640 + 2.8" panel (found on the OpenMoko Neo FreeRunner and Neo 1973). + +config DRM_PANEL_TPO_TD043MTEA1 + tristate "Toppoly (TPO) TD043MTEA1 panel driver" + depends on GPIOLIB && OF && REGULATOR && SPI + help + Say Y here if you want to enable support for TPO TD043MTEA1 800x480 + 4.3" panel (found on the OMAP3 Pandora board). + config DRM_PANEL_TPO_TPG110 tristate "TPO TPG 800x400 panel" depends on OF && SPI && GPIOLIB @@ -291,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 fb0cb3aaa9e6..7c4d3c581fd4 100644 --- a/drivers/gpu/drm/panel/Makefile +++ b/drivers/gpu/drm/panel/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_DRM_PANEL_ARM_VERSATILE) += panel-arm-versatile.o +obj-$(CONFIG_DRM_PANEL_BOE_HIMAX8279D) += panel-boe-himax8279d.o obj-$(CONFIG_DRM_PANEL_LVDS) += panel-lvds.o obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.o obj-$(CONFIG_DRM_PANEL_FEIYANG_FY07024DI26A30D) += panel-feiyang-fy07024di26a30d.o @@ -8,12 +9,17 @@ 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 +obj-$(CONFIG_DRM_PANEL_NOVATEK_NT39016) += panel-novatek-nt39016.o obj-$(CONFIG_DRM_PANEL_OLIMEX_LCD_OLINUXINO) += panel-olimex-lcd-olinuxino.o obj-$(CONFIG_DRM_PANEL_ORISETECH_OTM8009A) += panel-orisetech-otm8009a.o obj-$(CONFIG_DRM_PANEL_OSD_OSD101T2587_53TS) += panel-osd-osd101t2587-53ts.o obj-$(CONFIG_DRM_PANEL_PANASONIC_VVX10F034N00) += panel-panasonic-vvx10f034n00.o obj-$(CONFIG_DRM_PANEL_RASPBERRYPI_TOUCHSCREEN) += panel-raspberrypi-touchscreen.o +obj-$(CONFIG_DRM_PANEL_RAYDIUM_RM67191) += panel-raydium-rm67191.o obj-$(CONFIG_DRM_PANEL_RAYDIUM_RM68200) += panel-raydium-rm68200.o obj-$(CONFIG_DRM_PANEL_ROCKTECH_JH057N00900) += panel-rocktech-jh057n00900.o obj-$(CONFIG_DRM_PANEL_RONBO_RB070D30) += panel-ronbo-rb070d30.o @@ -25,8 +31,14 @@ obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E63M0) += panel-samsung-s6e63m0.o obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E8AA0) += panel-samsung-s6e8aa0.o obj-$(CONFIG_DRM_PANEL_SEIKO_43WVF1G) += panel-seiko-43wvf1g.o obj-$(CONFIG_DRM_PANEL_SHARP_LQ101R1SX01) += panel-sharp-lq101r1sx01.o +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-arm-versatile.c b/drivers/gpu/drm/panel/panel-arm-versatile.c index 5f72c922a04b..41444a73c980 100644 --- a/drivers/gpu/drm/panel/panel-arm-versatile.c +++ b/drivers/gpu/drm/panel/panel-arm-versatile.c @@ -260,9 +260,9 @@ static int versatile_panel_enable(struct drm_panel *panel) return 0; } -static int versatile_panel_get_modes(struct drm_panel *panel) +static int versatile_panel_get_modes(struct drm_panel *panel, + struct drm_connector *connector) { - struct drm_connector *connector = panel->connector; struct versatile_panel *vpanel = to_versatile_panel(panel); struct drm_display_mode *mode; @@ -270,7 +270,7 @@ static int versatile_panel_get_modes(struct drm_panel *panel) connector->display_info.height_mm = vpanel->panel_type->height_mm; connector->display_info.bus_flags = vpanel->panel_type->bus_flags; - mode = drm_mode_duplicate(panel->drm, &vpanel->panel_type->mode); + mode = drm_mode_duplicate(connector->dev, &vpanel->panel_type->mode); drm_mode_set_name(mode); mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; @@ -350,9 +350,8 @@ static int versatile_panel_probe(struct platform_device *pdev) dev_info(dev, "panel mounted on IB2 daughterboard\n"); } - drm_panel_init(&vpanel->panel); - vpanel->panel.dev = dev; - vpanel->panel.funcs = &versatile_panel_drm_funcs; + drm_panel_init(&vpanel->panel, dev, &versatile_panel_drm_funcs, + DRM_MODE_CONNECTOR_DPI); return drm_panel_add(&vpanel->panel); } diff --git a/drivers/gpu/drm/panel/panel-boe-himax8279d.c b/drivers/gpu/drm/panel/panel-boe-himax8279d.c new file mode 100644 index 000000000000..74d58ee7d04c --- /dev/null +++ b/drivers/gpu/drm/panel/panel-boe-himax8279d.c @@ -0,0 +1,978 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2019, Huaqin Telecom Technology Co., Ltd + * + * Author: Jerry Han <jerry.han.hq@gmail.com> + * + */ + +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> + +#include <linux/gpio/consumer.h> +#include <linux/regulator/consumer.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> + +#include <video/mipi_display.h> + +struct panel_cmd { + char cmd; + char data; +}; + +struct panel_desc { + const struct drm_display_mode *display_mode; + unsigned int bpc; + unsigned int width_mm; + unsigned int height_mm; + + unsigned long mode_flags; + enum mipi_dsi_pixel_format format; + unsigned int lanes; + const struct panel_cmd *on_cmds; + unsigned int on_cmds_num; +}; + +struct panel_info { + struct drm_panel base; + struct mipi_dsi_device *link; + const struct panel_desc *desc; + + struct gpio_desc *enable_gpio; + struct gpio_desc *pp33_gpio; + struct gpio_desc *pp18_gpio; + + bool prepared; + bool enabled; +}; + +static inline struct panel_info *to_panel_info(struct drm_panel *panel) +{ + return container_of(panel, struct panel_info, base); +} + +static void disable_gpios(struct panel_info *pinfo) +{ + gpiod_set_value(pinfo->enable_gpio, 0); + gpiod_set_value(pinfo->pp33_gpio, 0); + gpiod_set_value(pinfo->pp18_gpio, 0); +} + +static int send_mipi_cmds(struct drm_panel *panel, const struct panel_cmd *cmds) +{ + struct panel_info *pinfo = to_panel_info(panel); + unsigned int i = 0; + int err; + + for (i = 0; i < pinfo->desc->on_cmds_num; i++) { + err = mipi_dsi_dcs_write_buffer(pinfo->link, &cmds[i], + sizeof(struct panel_cmd)); + + if (err < 0) + return err; + } + + return 0; +} + +static int boe_panel_disable(struct drm_panel *panel) +{ + struct panel_info *pinfo = to_panel_info(panel); + int err; + + if (!pinfo->enabled) + return 0; + + err = mipi_dsi_dcs_set_display_off(pinfo->link); + if (err < 0) { + DRM_DEV_ERROR(panel->dev, "failed to set display off: %d\n", + err); + return err; + } + + pinfo->enabled = false; + + return 0; +} + +static int boe_panel_unprepare(struct drm_panel *panel) +{ + struct panel_info *pinfo = to_panel_info(panel); + int err; + + if (!pinfo->prepared) + return 0; + + err = mipi_dsi_dcs_set_display_off(pinfo->link); + if (err < 0) + DRM_DEV_ERROR(panel->dev, "failed to set display off: %d\n", + err); + + err = mipi_dsi_dcs_enter_sleep_mode(pinfo->link); + if (err < 0) + DRM_DEV_ERROR(panel->dev, "failed to enter sleep mode: %d\n", + err); + + /* sleep_mode_delay: 1ms - 2ms */ + usleep_range(1000, 2000); + + disable_gpios(pinfo); + + pinfo->prepared = false; + + return 0; +} + +static int boe_panel_prepare(struct drm_panel *panel) +{ + struct panel_info *pinfo = to_panel_info(panel); + int err; + + if (pinfo->prepared) + return 0; + + gpiod_set_value(pinfo->pp18_gpio, 1); + /* T1: 5ms - 6ms */ + usleep_range(5000, 6000); + gpiod_set_value(pinfo->pp33_gpio, 1); + + /* reset sequence */ + /* T2: 14ms - 15ms */ + usleep_range(14000, 15000); + gpiod_set_value(pinfo->enable_gpio, 1); + + /* T3: 1ms - 2ms */ + usleep_range(1000, 2000); + gpiod_set_value(pinfo->enable_gpio, 0); + + /* T4: 1ms - 2ms */ + usleep_range(1000, 2000); + gpiod_set_value(pinfo->enable_gpio, 1); + + /* T5: 5ms - 6ms */ + usleep_range(5000, 6000); + + /* send init code */ + err = send_mipi_cmds(panel, pinfo->desc->on_cmds); + if (err < 0) { + DRM_DEV_ERROR(panel->dev, "failed to send DCS Init Code: %d\n", + err); + goto poweroff; + } + + err = mipi_dsi_dcs_exit_sleep_mode(pinfo->link); + if (err < 0) { + DRM_DEV_ERROR(panel->dev, "failed to exit sleep mode: %d\n", + err); + goto poweroff; + } + + /* T6: 120ms - 121ms */ + usleep_range(120000, 121000); + + err = mipi_dsi_dcs_set_display_on(pinfo->link); + if (err < 0) { + DRM_DEV_ERROR(panel->dev, "failed to set display on: %d\n", + err); + goto poweroff; + } + + /* T7: 20ms - 21ms */ + usleep_range(20000, 21000); + + pinfo->prepared = true; + + return 0; + +poweroff: + disable_gpios(pinfo); + return err; +} + +static int boe_panel_enable(struct drm_panel *panel) +{ + struct panel_info *pinfo = to_panel_info(panel); + int ret; + + if (pinfo->enabled) + return 0; + + usleep_range(120000, 121000); + + ret = mipi_dsi_dcs_set_display_on(pinfo->link); + if (ret < 0) { + DRM_DEV_ERROR(panel->dev, "failed to set display on: %d\n", + ret); + return ret; + } + + pinfo->enabled = true; + + return 0; +} + +static int boe_panel_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct panel_info *pinfo = to_panel_info(panel); + const struct drm_display_mode *m = pinfo->desc->display_mode; + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, m); + if (!mode) { + DRM_DEV_ERROR(pinfo->base.dev, "failed to add mode %ux%u@%u\n", + m->hdisplay, m->vdisplay, m->vrefresh); + return -ENOMEM; + } + + drm_mode_set_name(mode); + + drm_mode_probed_add(connector, mode); + + connector->display_info.width_mm = pinfo->desc->width_mm; + connector->display_info.height_mm = pinfo->desc->height_mm; + connector->display_info.bpc = pinfo->desc->bpc; + + return 1; +} + +static const struct drm_panel_funcs panel_funcs = { + .disable = boe_panel_disable, + .unprepare = boe_panel_unprepare, + .prepare = boe_panel_prepare, + .enable = boe_panel_enable, + .get_modes = boe_panel_get_modes, +}; + +static const struct drm_display_mode default_display_mode = { + .clock = 159420, + .hdisplay = 1200, + .hsync_start = 1200 + 80, + .hsync_end = 1200 + 80 + 60, + .htotal = 1200 + 80 + 60 + 24, + .vdisplay = 1920, + .vsync_start = 1920 + 10, + .vsync_end = 1920 + 10 + 14, + .vtotal = 1920 + 10 + 14 + 4, + .vrefresh = 60, +}; + +/* 8 inch */ +static const struct panel_cmd boe_himax8279d8p_on_cmds[] = { + { 0xB0, 0x05 }, + { 0xB1, 0xE5 }, + { 0xB3, 0x52 }, + { 0xC0, 0x00 }, + { 0xC2, 0x57 }, + { 0xD9, 0x85 }, + { 0xB0, 0x01 }, + { 0xC8, 0x00 }, + { 0xC9, 0x00 }, + { 0xCC, 0x26 }, + { 0xCD, 0x26 }, + { 0xDC, 0x00 }, + { 0xDD, 0x00 }, + { 0xE0, 0x26 }, + { 0xE1, 0x26 }, + { 0xB0, 0x03 }, + { 0xC3, 0x2A }, + { 0xE7, 0x2A }, + { 0xC5, 0x2A }, + { 0xDE, 0x2A }, + { 0xBC, 0x02 }, + { 0xCB, 0x02 }, + { 0xB0, 0x00 }, + { 0xB6, 0x03 }, + { 0xBA, 0x8B }, + { 0xBF, 0x15 }, + { 0xC0, 0x18 }, + { 0xC2, 0x14 }, + { 0xC3, 0x02 }, + { 0xC4, 0x14 }, + { 0xC5, 0x02 }, + { 0xCC, 0x0A }, + { 0xB0, 0x06 }, + { 0xC0, 0xA5 }, + { 0xD5, 0x20 }, + { 0xC0, 0x00 }, + { 0xB0, 0x02 }, + { 0xC0, 0x00 }, + { 0xC1, 0x02 }, + { 0xC2, 0x06 }, + { 0xC3, 0x16 }, + { 0xC4, 0x0E }, + { 0xC5, 0x18 }, + { 0xC6, 0x26 }, + { 0xC7, 0x32 }, + { 0xC8, 0x3F }, + { 0xC9, 0x3F }, + { 0xCA, 0x3F }, + { 0xCB, 0x3F }, + { 0xCC, 0x3D }, + { 0xCD, 0x2F }, + { 0xCE, 0x2F }, + { 0xCF, 0x2F }, + { 0xD0, 0x07 }, + { 0xD2, 0x00 }, + { 0xD3, 0x02 }, + { 0xD4, 0x06 }, + { 0xD5, 0x12 }, + { 0xD6, 0x0A }, + { 0xD7, 0x14 }, + { 0xD8, 0x22 }, + { 0xD9, 0x2E }, + { 0xDA, 0x3D }, + { 0xDB, 0x3F }, + { 0xDC, 0x3F }, + { 0xDD, 0x3F }, + { 0xDE, 0x3D }, + { 0xDF, 0x2F }, + { 0xE0, 0x2F }, + { 0xE1, 0x2F }, + { 0xE2, 0x07 }, + { 0xB0, 0x07 }, + { 0xB1, 0x18 }, + { 0xB2, 0x19 }, + { 0xB3, 0x2E }, + { 0xB4, 0x52 }, + { 0xB5, 0x72 }, + { 0xB6, 0x8C }, + { 0xB7, 0xBD }, + { 0xB8, 0xEB }, + { 0xB9, 0x47 }, + { 0xBA, 0x96 }, + { 0xBB, 0x1E }, + { 0xBC, 0x90 }, + { 0xBD, 0x93 }, + { 0xBE, 0xFA }, + { 0xBF, 0x56 }, + { 0xC0, 0x8C }, + { 0xC1, 0xB7 }, + { 0xC2, 0xCC }, + { 0xC3, 0xDF }, + { 0xC4, 0xE8 }, + { 0xC5, 0xF0 }, + { 0xC6, 0xF8 }, + { 0xC7, 0xFA }, + { 0xC8, 0xFC }, + { 0xC9, 0x00 }, + { 0xCA, 0x00 }, + { 0xCB, 0x5A }, + { 0xCC, 0xAF }, + { 0xCD, 0xFF }, + { 0xCE, 0xFF }, + { 0xB0, 0x08 }, + { 0xB1, 0x04 }, + { 0xB2, 0x15 }, + { 0xB3, 0x2D }, + { 0xB4, 0x51 }, + { 0xB5, 0x72 }, + { 0xB6, 0x8D }, + { 0xB7, 0xBE }, + { 0xB8, 0xED }, + { 0xB9, 0x4A }, + { 0xBA, 0x9A }, + { 0xBB, 0x23 }, + { 0xBC, 0x95 }, + { 0xBD, 0x98 }, + { 0xBE, 0xFF }, + { 0xBF, 0x59 }, + { 0xC0, 0x8E }, + { 0xC1, 0xB9 }, + { 0xC2, 0xCD }, + { 0xC3, 0xDF }, + { 0xC4, 0xE8 }, + { 0xC5, 0xF0 }, + { 0xC6, 0xF8 }, + { 0xC7, 0xFA }, + { 0xC8, 0xFC }, + { 0xC9, 0x00 }, + { 0xCA, 0x00 }, + { 0xCB, 0x5A }, + { 0xCC, 0xAF }, + { 0xCD, 0xFF }, + { 0xCE, 0xFF }, + { 0xB0, 0x09 }, + { 0xB1, 0x04 }, + { 0xB2, 0x2C }, + { 0xB3, 0x36 }, + { 0xB4, 0x53 }, + { 0xB5, 0x73 }, + { 0xB6, 0x8E }, + { 0xB7, 0xC0 }, + { 0xB8, 0xEF }, + { 0xB9, 0x4C }, + { 0xBA, 0x9D }, + { 0xBB, 0x25 }, + { 0xBC, 0x96 }, + { 0xBD, 0x9A }, + { 0xBE, 0x01 }, + { 0xBF, 0x59 }, + { 0xC0, 0x8E }, + { 0xC1, 0xB9 }, + { 0xC2, 0xCD }, + { 0xC3, 0xDF }, + { 0xC4, 0xE8 }, + { 0xC5, 0xF0 }, + { 0xC6, 0xF8 }, + { 0xC7, 0xFA }, + { 0xC8, 0xFC }, + { 0xC9, 0x00 }, + { 0xCA, 0x00 }, + { 0xCB, 0x5A }, + { 0xCC, 0xBF }, + { 0xCD, 0xFF }, + { 0xCE, 0xFF }, + { 0xB0, 0x0A }, + { 0xB1, 0x18 }, + { 0xB2, 0x19 }, + { 0xB3, 0x2E }, + { 0xB4, 0x52 }, + { 0xB5, 0x72 }, + { 0xB6, 0x8C }, + { 0xB7, 0xBD }, + { 0xB8, 0xEB }, + { 0xB9, 0x47 }, + { 0xBA, 0x96 }, + { 0xBB, 0x1E }, + { 0xBC, 0x90 }, + { 0xBD, 0x93 }, + { 0xBE, 0xFA }, + { 0xBF, 0x56 }, + { 0xC0, 0x8C }, + { 0xC1, 0xB7 }, + { 0xC2, 0xCC }, + { 0xC3, 0xDF }, + { 0xC4, 0xE8 }, + { 0xC5, 0xF0 }, + { 0xC6, 0xF8 }, + { 0xC7, 0xFA }, + { 0xC8, 0xFC }, + { 0xC9, 0x00 }, + { 0xCA, 0x00 }, + { 0xCB, 0x5A }, + { 0xCC, 0xAF }, + { 0xCD, 0xFF }, + { 0xCE, 0xFF }, + { 0xB0, 0x0B }, + { 0xB1, 0x04 }, + { 0xB2, 0x15 }, + { 0xB3, 0x2D }, + { 0xB4, 0x51 }, + { 0xB5, 0x72 }, + { 0xB6, 0x8D }, + { 0xB7, 0xBE }, + { 0xB8, 0xED }, + { 0xB9, 0x4A }, + { 0xBA, 0x9A }, + { 0xBB, 0x23 }, + { 0xBC, 0x95 }, + { 0xBD, 0x98 }, + { 0xBE, 0xFF }, + { 0xBF, 0x59 }, + { 0xC0, 0x8E }, + { 0xC1, 0xB9 }, + { 0xC2, 0xCD }, + { 0xC3, 0xDF }, + { 0xC4, 0xE8 }, + { 0xC5, 0xF0 }, + { 0xC6, 0xF8 }, + { 0xC7, 0xFA }, + { 0xC8, 0xFC }, + { 0xC9, 0x00 }, + { 0xCA, 0x00 }, + { 0xCB, 0x5A }, + { 0xCC, 0xAF }, + { 0xCD, 0xFF }, + { 0xCE, 0xFF }, + { 0xB0, 0x0C }, + { 0xB1, 0x04 }, + { 0xB2, 0x2C }, + { 0xB3, 0x36 }, + { 0xB4, 0x53 }, + { 0xB5, 0x73 }, + { 0xB6, 0x8E }, + { 0xB7, 0xC0 }, + { 0xB8, 0xEF }, + { 0xB9, 0x4C }, + { 0xBA, 0x9D }, + { 0xBB, 0x25 }, + { 0xBC, 0x96 }, + { 0xBD, 0x9A }, + { 0xBE, 0x01 }, + { 0xBF, 0x59 }, + { 0xC0, 0x8E }, + { 0xC1, 0xB9 }, + { 0xC2, 0xCD }, + { 0xC3, 0xDF }, + { 0xC4, 0xE8 }, + { 0xC5, 0xF0 }, + { 0xC6, 0xF8 }, + { 0xC7, 0xFA }, + { 0xC8, 0xFC }, + { 0xC9, 0x00 }, + { 0xCA, 0x00 }, + { 0xCB, 0x5A }, + { 0xCC, 0xBF }, + { 0xCD, 0xFF }, + { 0xCE, 0xFF }, + { 0xB0, 0x04 }, + { 0xB5, 0x02 }, + { 0xB6, 0x01 }, +}; + +static const struct panel_desc boe_himax8279d8p_panel_desc = { + .display_mode = &default_display_mode, + .bpc = 8, + .width_mm = 107, + .height_mm = 172, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_CLOCK_NON_CONTINUOUS | MIPI_DSI_MODE_LPM, + .format = MIPI_DSI_FMT_RGB888, + .lanes = 4, + .on_cmds = boe_himax8279d8p_on_cmds, + .on_cmds_num = 260, +}; + +/* 10 inch */ +static const struct panel_cmd boe_himax8279d10p_on_cmds[] = { + { 0xB0, 0x05 }, + { 0xB1, 0xE5 }, + { 0xB3, 0x52 }, + { 0xB0, 0x00 }, + { 0xB6, 0x03 }, + { 0xBA, 0x8B }, + { 0xBF, 0x1A }, + { 0xC0, 0x0F }, + { 0xC2, 0x0C }, + { 0xC3, 0x02 }, + { 0xC4, 0x0C }, + { 0xC5, 0x02 }, + { 0xB0, 0x01 }, + { 0xE0, 0x26 }, + { 0xE1, 0x26 }, + { 0xDC, 0x00 }, + { 0xDD, 0x00 }, + { 0xCC, 0x26 }, + { 0xCD, 0x26 }, + { 0xC8, 0x00 }, + { 0xC9, 0x00 }, + { 0xD2, 0x03 }, + { 0xD3, 0x03 }, + { 0xE6, 0x04 }, + { 0xE7, 0x04 }, + { 0xC4, 0x09 }, + { 0xC5, 0x09 }, + { 0xD8, 0x0A }, + { 0xD9, 0x0A }, + { 0xC2, 0x0B }, + { 0xC3, 0x0B }, + { 0xD6, 0x0C }, + { 0xD7, 0x0C }, + { 0xC0, 0x05 }, + { 0xC1, 0x05 }, + { 0xD4, 0x06 }, + { 0xD5, 0x06 }, + { 0xCA, 0x07 }, + { 0xCB, 0x07 }, + { 0xDE, 0x08 }, + { 0xDF, 0x08 }, + { 0xB0, 0x02 }, + { 0xC0, 0x00 }, + { 0xC1, 0x0D }, + { 0xC2, 0x17 }, + { 0xC3, 0x26 }, + { 0xC4, 0x31 }, + { 0xC5, 0x1C }, + { 0xC6, 0x2C }, + { 0xC7, 0x33 }, + { 0xC8, 0x31 }, + { 0xC9, 0x37 }, + { 0xCA, 0x37 }, + { 0xCB, 0x37 }, + { 0xCC, 0x39 }, + { 0xCD, 0x2E }, + { 0xCE, 0x2F }, + { 0xCF, 0x2F }, + { 0xD0, 0x07 }, + { 0xD2, 0x00 }, + { 0xD3, 0x0D }, + { 0xD4, 0x17 }, + { 0xD5, 0x26 }, + { 0xD6, 0x31 }, + { 0xD7, 0x3F }, + { 0xD8, 0x3F }, + { 0xD9, 0x3F }, + { 0xDA, 0x3F }, + { 0xDB, 0x37 }, + { 0xDC, 0x37 }, + { 0xDD, 0x37 }, + { 0xDE, 0x39 }, + { 0xDF, 0x2E }, + { 0xE0, 0x2F }, + { 0xE1, 0x2F }, + { 0xE2, 0x07 }, + { 0xB0, 0x03 }, + { 0xC8, 0x0B }, + { 0xC9, 0x07 }, + { 0xC3, 0x00 }, + { 0xE7, 0x00 }, + { 0xC5, 0x2A }, + { 0xDE, 0x2A }, + { 0xCA, 0x43 }, + { 0xC9, 0x07 }, + { 0xE4, 0xC0 }, + { 0xE5, 0x0D }, + { 0xCB, 0x01 }, + { 0xBC, 0x01 }, + { 0xB0, 0x06 }, + { 0xB8, 0xA5 }, + { 0xC0, 0xA5 }, + { 0xC7, 0x0F }, + { 0xD5, 0x32 }, + { 0xB8, 0x00 }, + { 0xC0, 0x00 }, + { 0xBC, 0x00 }, + { 0xB0, 0x07 }, + { 0xB1, 0x00 }, + { 0xB2, 0x05 }, + { 0xB3, 0x10 }, + { 0xB4, 0x22 }, + { 0xB5, 0x36 }, + { 0xB6, 0x4A }, + { 0xB7, 0x6C }, + { 0xB8, 0x9A }, + { 0xB9, 0xD7 }, + { 0xBA, 0x17 }, + { 0xBB, 0x92 }, + { 0xBC, 0x15 }, + { 0xBD, 0x18 }, + { 0xBE, 0x8C }, + { 0xBF, 0x00 }, + { 0xC0, 0x3A }, + { 0xC1, 0x72 }, + { 0xC2, 0x8C }, + { 0xC3, 0xA5 }, + { 0xC4, 0xB1 }, + { 0xC5, 0xBE }, + { 0xC6, 0xCA }, + { 0xC7, 0xD1 }, + { 0xC8, 0xD4 }, + { 0xC9, 0x00 }, + { 0xCA, 0x00 }, + { 0xCB, 0x16 }, + { 0xCC, 0xAF }, + { 0xCD, 0xFF }, + { 0xCE, 0xFF }, + { 0xB0, 0x08 }, + { 0xB1, 0x04 }, + { 0xB2, 0x05 }, + { 0xB3, 0x11 }, + { 0xB4, 0x24 }, + { 0xB5, 0x39 }, + { 0xB6, 0x4E }, + { 0xB7, 0x72 }, + { 0xB8, 0xA3 }, + { 0xB9, 0xE1 }, + { 0xBA, 0x25 }, + { 0xBB, 0xA8 }, + { 0xBC, 0x2E }, + { 0xBD, 0x32 }, + { 0xBE, 0xAD }, + { 0xBF, 0x28 }, + { 0xC0, 0x63 }, + { 0xC1, 0x9B }, + { 0xC2, 0xB5 }, + { 0xC3, 0xCF }, + { 0xC4, 0xDB }, + { 0xC5, 0xE8 }, + { 0xC6, 0xF5 }, + { 0xC7, 0xFA }, + { 0xC8, 0xFC }, + { 0xC9, 0x00 }, + { 0xCA, 0x00 }, + { 0xCB, 0x16 }, + { 0xCC, 0xAF }, + { 0xCD, 0xFF }, + { 0xCE, 0xFF }, + { 0xB0, 0x09 }, + { 0xB1, 0x04 }, + { 0xB2, 0x04 }, + { 0xB3, 0x0F }, + { 0xB4, 0x22 }, + { 0xB5, 0x37 }, + { 0xB6, 0x4D }, + { 0xB7, 0x71 }, + { 0xB8, 0xA2 }, + { 0xB9, 0xE1 }, + { 0xBA, 0x26 }, + { 0xBB, 0xA9 }, + { 0xBC, 0x2F }, + { 0xBD, 0x33 }, + { 0xBE, 0xAC }, + { 0xBF, 0x24 }, + { 0xC0, 0x5D }, + { 0xC1, 0x94 }, + { 0xC2, 0xAC }, + { 0xC3, 0xC5 }, + { 0xC4, 0xD1 }, + { 0xC5, 0xDC }, + { 0xC6, 0xE8 }, + { 0xC7, 0xED }, + { 0xC8, 0xF0 }, + { 0xC9, 0x00 }, + { 0xCA, 0x00 }, + { 0xCB, 0x16 }, + { 0xCC, 0xAF }, + { 0xCD, 0xFF }, + { 0xCE, 0xFF }, + { 0xB0, 0x0A }, + { 0xB1, 0x00 }, + { 0xB2, 0x05 }, + { 0xB3, 0x10 }, + { 0xB4, 0x22 }, + { 0xB5, 0x36 }, + { 0xB6, 0x4A }, + { 0xB7, 0x6C }, + { 0xB8, 0x9A }, + { 0xB9, 0xD7 }, + { 0xBA, 0x17 }, + { 0xBB, 0x92 }, + { 0xBC, 0x15 }, + { 0xBD, 0x18 }, + { 0xBE, 0x8C }, + { 0xBF, 0x00 }, + { 0xC0, 0x3A }, + { 0xC1, 0x72 }, + { 0xC2, 0x8C }, + { 0xC3, 0xA5 }, + { 0xC4, 0xB1 }, + { 0xC5, 0xBE }, + { 0xC6, 0xCA }, + { 0xC7, 0xD1 }, + { 0xC8, 0xD4 }, + { 0xC9, 0x00 }, + { 0xCA, 0x00 }, + { 0xCB, 0x16 }, + { 0xCC, 0xAF }, + { 0xCD, 0xFF }, + { 0xCE, 0xFF }, + { 0xB0, 0x0B }, + { 0xB1, 0x04 }, + { 0xB2, 0x05 }, + { 0xB3, 0x11 }, + { 0xB4, 0x24 }, + { 0xB5, 0x39 }, + { 0xB6, 0x4E }, + { 0xB7, 0x72 }, + { 0xB8, 0xA3 }, + { 0xB9, 0xE1 }, + { 0xBA, 0x25 }, + { 0xBB, 0xA8 }, + { 0xBC, 0x2E }, + { 0xBD, 0x32 }, + { 0xBE, 0xAD }, + { 0xBF, 0x28 }, + { 0xC0, 0x63 }, + { 0xC1, 0x9B }, + { 0xC2, 0xB5 }, + { 0xC3, 0xCF }, + { 0xC4, 0xDB }, + { 0xC5, 0xE8 }, + { 0xC6, 0xF5 }, + { 0xC7, 0xFA }, + { 0xC8, 0xFC }, + { 0xC9, 0x00 }, + { 0xCA, 0x00 }, + { 0xCB, 0x16 }, + { 0xCC, 0xAF }, + { 0xCD, 0xFF }, + { 0xCE, 0xFF }, + { 0xB0, 0x0C }, + { 0xB1, 0x04 }, + { 0xB2, 0x04 }, + { 0xB3, 0x0F }, + { 0xB4, 0x22 }, + { 0xB5, 0x37 }, + { 0xB6, 0x4D }, + { 0xB7, 0x71 }, + { 0xB8, 0xA2 }, + { 0xB9, 0xE1 }, + { 0xBA, 0x26 }, + { 0xBB, 0xA9 }, + { 0xBC, 0x2F }, + { 0xBD, 0x33 }, + { 0xBE, 0xAC }, + { 0xBF, 0x24 }, + { 0xC0, 0x5D }, + { 0xC1, 0x94 }, + { 0xC2, 0xAC }, + { 0xC3, 0xC5 }, + { 0xC4, 0xD1 }, + { 0xC5, 0xDC }, + { 0xC6, 0xE8 }, + { 0xC7, 0xED }, + { 0xC8, 0xF0 }, + { 0xC9, 0x00 }, + { 0xCA, 0x00 }, + { 0xCB, 0x16 }, + { 0xCC, 0xAF }, + { 0xCD, 0xFF }, + { 0xCE, 0xFF }, +}; + +static const struct panel_desc boe_himax8279d10p_panel_desc = { + .display_mode = &default_display_mode, + .bpc = 8, + .width_mm = 135, + .height_mm = 216, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_CLOCK_NON_CONTINUOUS | MIPI_DSI_MODE_LPM, + .format = MIPI_DSI_FMT_RGB888, + .lanes = 4, + .on_cmds = boe_himax8279d10p_on_cmds, + .on_cmds_num = 283, +}; + +static const struct of_device_id panel_of_match[] = { + { + .compatible = "boe,himax8279d8p", + .data = &boe_himax8279d8p_panel_desc, + }, + { + .compatible = "boe,himax8279d10p", + .data = &boe_himax8279d10p_panel_desc, + }, + { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, panel_of_match); + +static int panel_add(struct panel_info *pinfo) +{ + struct device *dev = &pinfo->link->dev; + int ret; + + pinfo->pp18_gpio = devm_gpiod_get(dev, "pp18", GPIOD_OUT_HIGH); + if (IS_ERR(pinfo->pp18_gpio)) { + ret = PTR_ERR(pinfo->pp18_gpio); + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(dev, "failed to get pp18 gpio: %d\n", + ret); + return ret; + } + + pinfo->pp33_gpio = devm_gpiod_get(dev, "pp33", GPIOD_OUT_HIGH); + if (IS_ERR(pinfo->pp33_gpio)) { + ret = PTR_ERR(pinfo->pp33_gpio); + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(dev, "failed to get pp33 gpio: %d\n", + ret); + return ret; + } + + pinfo->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_HIGH); + if (IS_ERR(pinfo->enable_gpio)) { + ret = PTR_ERR(pinfo->enable_gpio); + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(dev, "failed to get enable gpio: %d\n", + ret); + return ret; + } + + drm_panel_init(&pinfo->base, dev, &panel_funcs, + DRM_MODE_CONNECTOR_DSI); + + ret = drm_panel_of_backlight(&pinfo->base); + if (ret) + return ret; + + return drm_panel_add(&pinfo->base); +} + +static int panel_probe(struct mipi_dsi_device *dsi) +{ + struct panel_info *pinfo; + const struct panel_desc *desc; + int err; + + pinfo = devm_kzalloc(&dsi->dev, sizeof(*pinfo), GFP_KERNEL); + if (!pinfo) + return -ENOMEM; + + desc = of_device_get_match_data(&dsi->dev); + dsi->mode_flags = desc->mode_flags; + dsi->format = desc->format; + dsi->lanes = desc->lanes; + pinfo->desc = desc; + + pinfo->link = dsi; + mipi_dsi_set_drvdata(dsi, pinfo); + + err = panel_add(pinfo); + if (err < 0) + return err; + + err = mipi_dsi_attach(dsi); + if (err < 0) + drm_panel_remove(&pinfo->base); + + return err; +} + +static int panel_remove(struct mipi_dsi_device *dsi) +{ + struct panel_info *pinfo = mipi_dsi_get_drvdata(dsi); + int err; + + err = boe_panel_disable(&pinfo->base); + if (err < 0) + DRM_DEV_ERROR(&dsi->dev, "failed to disable panel: %d\n", + err); + + err = boe_panel_unprepare(&pinfo->base); + if (err < 0) + DRM_DEV_ERROR(&dsi->dev, "failed to unprepare panel: %d\n", + err); + + err = mipi_dsi_detach(dsi); + if (err < 0) + DRM_DEV_ERROR(&dsi->dev, "failed to detach from DSI host: %d\n", + err); + + drm_panel_remove(&pinfo->base); + + return 0; +} + +static void panel_shutdown(struct mipi_dsi_device *dsi) +{ + struct panel_info *pinfo = mipi_dsi_get_drvdata(dsi); + + boe_panel_disable(&pinfo->base); + boe_panel_unprepare(&pinfo->base); +} + +static struct mipi_dsi_driver panel_driver = { + .driver = { + .name = "panel-boe-himax8279d", + .of_match_table = panel_of_match, + }, + .probe = panel_probe, + .remove = panel_remove, + .shutdown = panel_shutdown, +}; +module_mipi_dsi_driver(panel_driver); + +MODULE_AUTHOR("Jerry Han <jerry.han.hq@gmail.com>"); +MODULE_DESCRIPTION("Boe Himax8279d driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-feiyang-fy07024di26a30d.c b/drivers/gpu/drm/panel/panel-feiyang-fy07024di26a30d.c index dabf59e0f56f..95b789ab9d29 100644 --- a/drivers/gpu/drm/panel/panel-feiyang-fy07024di26a30d.c +++ b/drivers/gpu/drm/panel/panel-feiyang-fy07024di26a30d.c @@ -9,7 +9,6 @@ #include <drm/drm_panel.h> #include <drm/drm_print.h> -#include <linux/backlight.h> #include <linux/gpio/consumer.h> #include <linux/delay.h> #include <linux/module.h> @@ -22,7 +21,6 @@ struct feiyang { struct drm_panel panel; struct mipi_dsi_device *dsi; - struct backlight_device *backlight; struct regulator *dvdd; struct regulator *avdd; struct gpio_desc *reset; @@ -102,7 +100,6 @@ static int feiyang_enable(struct drm_panel *panel) msleep(200); mipi_dsi_dcs_set_display_on(ctx->dsi); - backlight_enable(ctx->backlight); return 0; } @@ -111,7 +108,6 @@ static int feiyang_disable(struct drm_panel *panel) { struct feiyang *ctx = panel_to_feiyang(panel); - backlight_disable(ctx->backlight); return mipi_dsi_dcs_set_display_off(ctx->dsi); } @@ -162,13 +158,13 @@ static const struct drm_display_mode feiyang_default_mode = { .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, }; -static int feiyang_get_modes(struct drm_panel *panel) +static int feiyang_get_modes(struct drm_panel *panel, + struct drm_connector *connector) { - struct drm_connector *connector = panel->connector; struct feiyang *ctx = panel_to_feiyang(panel); struct drm_display_mode *mode; - mode = drm_mode_duplicate(panel->drm, &feiyang_default_mode); + mode = drm_mode_duplicate(connector->dev, &feiyang_default_mode); if (!mode) { DRM_DEV_ERROR(&ctx->dsi->dev, "failed to add mode %ux%ux@%u\n", feiyang_default_mode.hdisplay, @@ -204,9 +200,8 @@ static int feiyang_dsi_probe(struct mipi_dsi_device *dsi) mipi_dsi_set_drvdata(dsi, ctx); ctx->dsi = dsi; - drm_panel_init(&ctx->panel); - ctx->panel.dev = &dsi->dev; - ctx->panel.funcs = &feiyang_funcs; + drm_panel_init(&ctx->panel, &dsi->dev, &feiyang_funcs, + DRM_MODE_CONNECTOR_DSI); ctx->dvdd = devm_regulator_get(&dsi->dev, "dvdd"); if (IS_ERR(ctx->dvdd)) { @@ -226,9 +221,9 @@ static int feiyang_dsi_probe(struct mipi_dsi_device *dsi) return PTR_ERR(ctx->reset); } - ctx->backlight = devm_of_find_backlight(&dsi->dev); - if (IS_ERR(ctx->backlight)) - return PTR_ERR(ctx->backlight); + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return ret; ret = drm_panel_add(&ctx->panel); if (ret < 0) diff --git a/drivers/gpu/drm/panel/panel-ilitek-ili9322.c b/drivers/gpu/drm/panel/panel-ilitek-ili9322.c index 53dd1e128795..f394d53a7da4 100644 --- a/drivers/gpu/drm/panel/panel-ilitek-ili9322.c +++ b/drivers/gpu/drm/panel/panel-ilitek-ili9322.c @@ -349,7 +349,6 @@ static const struct regmap_config ili9322_regmap_config = { static int ili9322_init(struct drm_panel *panel, struct ili9322 *ili) { - struct drm_connector *connector = panel->connector; u8 reg; int ret; int i; @@ -407,23 +406,11 @@ static int ili9322_init(struct drm_panel *panel, struct ili9322 *ili) * Polarity and inverted color order for RGB input. * None of this applies in the BT.656 mode. */ - if (ili->conf->dclk_active_high) { + reg = 0; + if (ili->conf->dclk_active_high) reg = ILI9322_POL_DCLK; - connector->display_info.bus_flags |= - DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE; - } else { - reg = 0; - connector->display_info.bus_flags |= - DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE; - } - if (ili->conf->de_active_high) { + if (ili->conf->de_active_high) reg |= ILI9322_POL_DE; - connector->display_info.bus_flags |= - DRM_BUS_FLAG_DE_HIGH; - } else { - connector->display_info.bus_flags |= - DRM_BUS_FLAG_DE_LOW; - } if (ili->conf->hsync_active_high) reg |= ILI9322_POL_HSYNC; if (ili->conf->vsync_active_high) @@ -654,37 +641,49 @@ static const struct drm_display_mode itu_r_bt_656_720_mode = { .flags = 0, }; -static int ili9322_get_modes(struct drm_panel *panel) +static int ili9322_get_modes(struct drm_panel *panel, + struct drm_connector *connector) { - struct drm_connector *connector = panel->connector; struct ili9322 *ili = panel_to_ili9322(panel); + struct drm_device *drm = connector->dev; struct drm_display_mode *mode; + struct drm_display_info *info; - connector->display_info.width_mm = ili->conf->width_mm; - connector->display_info.height_mm = ili->conf->height_mm; + info = &connector->display_info; + info->width_mm = ili->conf->width_mm; + info->height_mm = ili->conf->height_mm; + if (ili->conf->dclk_active_high) + info->bus_flags |= DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE; + else + info->bus_flags |= DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE; + + if (ili->conf->de_active_high) + info->bus_flags |= DRM_BUS_FLAG_DE_HIGH; + else + info->bus_flags |= DRM_BUS_FLAG_DE_LOW; switch (ili->input) { case ILI9322_INPUT_SRGB_DUMMY_320X240: - mode = drm_mode_duplicate(panel->drm, &srgb_320x240_mode); + mode = drm_mode_duplicate(drm, &srgb_320x240_mode); break; case ILI9322_INPUT_SRGB_DUMMY_360X240: - mode = drm_mode_duplicate(panel->drm, &srgb_360x240_mode); + mode = drm_mode_duplicate(drm, &srgb_360x240_mode); break; case ILI9322_INPUT_PRGB_THROUGH: case ILI9322_INPUT_PRGB_ALIGNED: - mode = drm_mode_duplicate(panel->drm, &prgb_320x240_mode); + mode = drm_mode_duplicate(drm, &prgb_320x240_mode); break; case ILI9322_INPUT_YUV_640X320_YCBCR: - mode = drm_mode_duplicate(panel->drm, &yuv_640x320_mode); + mode = drm_mode_duplicate(drm, &yuv_640x320_mode); break; case ILI9322_INPUT_YUV_720X360_YCBCR: - mode = drm_mode_duplicate(panel->drm, &yuv_720x360_mode); + mode = drm_mode_duplicate(drm, &yuv_720x360_mode); break; case ILI9322_INPUT_ITU_R_BT656_720X360_YCBCR: - mode = drm_mode_duplicate(panel->drm, &itu_r_bt_656_720_mode); + mode = drm_mode_duplicate(drm, &itu_r_bt_656_720_mode); break; case ILI9322_INPUT_ITU_R_BT656_640X320_YCBCR: - mode = drm_mode_duplicate(panel->drm, &itu_r_bt_656_640_mode); + mode = drm_mode_duplicate(drm, &itu_r_bt_656_640_mode); break; default: mode = NULL; @@ -897,9 +896,8 @@ static int ili9322_probe(struct spi_device *spi) ili->input = ili->conf->input; } - drm_panel_init(&ili->panel); - ili->panel.dev = dev; - ili->panel.funcs = &ili9322_drm_funcs; + drm_panel_init(&ili->panel, dev, &ili9322_drm_funcs, + DRM_MODE_CONNECTOR_DPI); return drm_panel_add(&ili->panel); } diff --git a/drivers/gpu/drm/panel/panel-ilitek-ili9881c.c b/drivers/gpu/drm/panel/panel-ilitek-ili9881c.c index 3ad4a46c4e94..f54077c216a3 100644 --- a/drivers/gpu/drm/panel/panel-ilitek-ili9881c.c +++ b/drivers/gpu/drm/panel/panel-ilitek-ili9881c.c @@ -3,7 +3,6 @@ * Copyright (C) 2017-2018, Bootlin */ -#include <linux/backlight.h> #include <linux/delay.h> #include <linux/device.h> #include <linux/err.h> @@ -25,7 +24,6 @@ struct ili9881c { struct drm_panel panel; struct mipi_dsi_device *dsi; - struct backlight_device *backlight; struct regulator *power; struct gpio_desc *reset; }; @@ -348,7 +346,6 @@ static int ili9881c_enable(struct drm_panel *panel) msleep(120); mipi_dsi_dcs_set_display_on(ctx->dsi); - backlight_enable(ctx->backlight); return 0; } @@ -357,7 +354,6 @@ static int ili9881c_disable(struct drm_panel *panel) { struct ili9881c *ctx = panel_to_ili9881c(panel); - backlight_disable(ctx->backlight); return mipi_dsi_dcs_set_display_off(ctx->dsi); } @@ -387,13 +383,13 @@ static const struct drm_display_mode bananapi_default_mode = { .vtotal = 1280 + 10 + 10 + 20, }; -static int ili9881c_get_modes(struct drm_panel *panel) +static int ili9881c_get_modes(struct drm_panel *panel, + struct drm_connector *connector) { - struct drm_connector *connector = panel->connector; struct ili9881c *ctx = panel_to_ili9881c(panel); struct drm_display_mode *mode; - mode = drm_mode_duplicate(panel->drm, &bananapi_default_mode); + mode = drm_mode_duplicate(connector->dev, &bananapi_default_mode); if (!mode) { dev_err(&ctx->dsi->dev, "failed to add mode %ux%ux@%u\n", bananapi_default_mode.hdisplay, @@ -407,8 +403,8 @@ static int ili9881c_get_modes(struct drm_panel *panel) mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; drm_mode_probed_add(connector, mode); - panel->connector->display_info.width_mm = 62; - panel->connector->display_info.height_mm = 110; + connector->display_info.width_mm = 62; + connector->display_info.height_mm = 110; return 1; } @@ -423,7 +419,6 @@ static const struct drm_panel_funcs ili9881c_funcs = { static int ili9881c_dsi_probe(struct mipi_dsi_device *dsi) { - struct device_node *np; struct ili9881c *ctx; int ret; @@ -433,9 +428,8 @@ static int ili9881c_dsi_probe(struct mipi_dsi_device *dsi) mipi_dsi_set_drvdata(dsi, ctx); ctx->dsi = dsi; - drm_panel_init(&ctx->panel); - ctx->panel.dev = &dsi->dev; - ctx->panel.funcs = &ili9881c_funcs; + drm_panel_init(&ctx->panel, &dsi->dev, &ili9881c_funcs, + DRM_MODE_CONNECTOR_DSI); ctx->power = devm_regulator_get(&dsi->dev, "power"); if (IS_ERR(ctx->power)) { @@ -449,14 +443,9 @@ static int ili9881c_dsi_probe(struct mipi_dsi_device *dsi) return PTR_ERR(ctx->reset); } - np = of_parse_phandle(dsi->dev.of_node, "backlight", 0); - if (np) { - ctx->backlight = of_find_backlight_by_node(np); - of_node_put(np); - - if (!ctx->backlight) - return -EPROBE_DEFER; - } + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return ret; ret = drm_panel_add(&ctx->panel); if (ret < 0) @@ -476,9 +465,6 @@ static int ili9881c_dsi_remove(struct mipi_dsi_device *dsi) mipi_dsi_detach(dsi); drm_panel_remove(&ctx->panel); - if (ctx->backlight) - put_device(&ctx->backlight->dev); - return 0; } diff --git a/drivers/gpu/drm/panel/panel-innolux-p079zca.c b/drivers/gpu/drm/panel/panel-innolux-p079zca.c index d92d1c98878c..7419f1f0acee 100644 --- a/drivers/gpu/drm/panel/panel-innolux-p079zca.c +++ b/drivers/gpu/drm/panel/panel-innolux-p079zca.c @@ -3,7 +3,6 @@ * 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> @@ -52,7 +51,6 @@ struct innolux_panel { struct mipi_dsi_device *link; const struct panel_desc *desc; - struct backlight_device *backlight; struct regulator_bulk_data *supplies; struct gpio_desc *enable_gpio; @@ -72,8 +70,6 @@ static int innolux_panel_disable(struct drm_panel *panel) if (!innolux->enabled) return 0; - backlight_disable(innolux->backlight); - innolux->enabled = false; return 0; @@ -204,18 +200,10 @@ poweroff: static int innolux_panel_enable(struct drm_panel *panel) { struct innolux_panel *innolux = to_innolux_panel(panel); - int ret; if (innolux->enabled) return 0; - ret = backlight_enable(innolux->backlight); - if (ret) { - DRM_DEV_ERROR(panel->drm->dev, - "Failed to enable backlight %d\n", ret); - return ret; - } - innolux->enabled = true; return 0; @@ -403,28 +391,27 @@ static const struct panel_desc innolux_p097pfg_panel_desc = { .sleep_mode_delay = 100, /* T15 */ }; -static int innolux_panel_get_modes(struct drm_panel *panel) +static int innolux_panel_get_modes(struct drm_panel *panel, + struct drm_connector *connector) { struct innolux_panel *innolux = to_innolux_panel(panel); const struct drm_display_mode *m = innolux->desc->mode; struct drm_display_mode *mode; - mode = drm_mode_duplicate(panel->drm, m); + mode = drm_mode_duplicate(connector->dev, m); if (!mode) { - DRM_DEV_ERROR(panel->drm->dev, "failed to add mode %ux%ux@%u\n", + DRM_DEV_ERROR(panel->dev, "failed to add mode %ux%ux@%u\n", m->hdisplay, m->vdisplay, m->vrefresh); return -ENOMEM; } drm_mode_set_name(mode); - drm_mode_probed_add(panel->connector, mode); + drm_mode_probed_add(connector, mode); - panel->connector->display_info.width_mm = - innolux->desc->size.width; - panel->connector->display_info.height_mm = - innolux->desc->size.height; - panel->connector->display_info.bpc = innolux->desc->bpc; + connector->display_info.width_mm = innolux->desc->size.width; + connector->display_info.height_mm = innolux->desc->size.height; + connector->display_info.bpc = innolux->desc->bpc; return 1; } @@ -483,13 +470,12 @@ static int innolux_panel_add(struct mipi_dsi_device *dsi, innolux->enable_gpio = NULL; } - innolux->backlight = devm_of_find_backlight(dev); - if (IS_ERR(innolux->backlight)) - return PTR_ERR(innolux->backlight); + drm_panel_init(&innolux->base, dev, &innolux_panel_funcs, + DRM_MODE_CONNECTOR_DSI); - drm_panel_init(&innolux->base); - innolux->base.funcs = &innolux_panel_funcs; - innolux->base.dev = dev; + err = drm_panel_of_backlight(&innolux->base); + if (err) + return err; err = drm_panel_add(&innolux->base); if (err < 0) @@ -528,12 +514,12 @@ static int innolux_panel_remove(struct mipi_dsi_device *dsi) struct innolux_panel *innolux = mipi_dsi_get_drvdata(dsi); int err; - err = innolux_panel_unprepare(&innolux->base); + err = drm_panel_unprepare(&innolux->base); if (err < 0) DRM_DEV_ERROR(&dsi->dev, "failed to unprepare panel: %d\n", err); - err = innolux_panel_disable(&innolux->base); + err = drm_panel_disable(&innolux->base); if (err < 0) DRM_DEV_ERROR(&dsi->dev, "failed to disable panel: %d\n", err); @@ -551,8 +537,8 @@ static void innolux_panel_shutdown(struct mipi_dsi_device *dsi) { struct innolux_panel *innolux = mipi_dsi_get_drvdata(dsi); - innolux_panel_unprepare(&innolux->base); - innolux_panel_disable(&innolux->base); + drm_panel_unprepare(&innolux->base); + drm_panel_disable(&innolux->base); } static struct mipi_dsi_driver innolux_panel_driver = { diff --git a/drivers/gpu/drm/panel/panel-jdi-lt070me05000.c b/drivers/gpu/drm/panel/panel-jdi-lt070me05000.c index ff3e89e61e3f..4bfd8c877c8e 100644 --- a/drivers/gpu/drm/panel/panel-jdi-lt070me05000.c +++ b/drivers/gpu/drm/panel/panel-jdi-lt070me05000.c @@ -300,13 +300,14 @@ static const struct drm_display_mode default_mode = { .flags = 0, }; -static int jdi_panel_get_modes(struct drm_panel *panel) +static int jdi_panel_get_modes(struct drm_panel *panel, + struct drm_connector *connector) { struct drm_display_mode *mode; struct jdi_panel *jdi = to_jdi_panel(panel); struct device *dev = &jdi->dsi->dev; - mode = drm_mode_duplicate(panel->drm, &default_mode); + mode = drm_mode_duplicate(connector->dev, &default_mode); if (!mode) { dev_err(dev, "failed to add mode %ux%ux@%u\n", default_mode.hdisplay, default_mode.vdisplay, @@ -316,10 +317,10 @@ static int jdi_panel_get_modes(struct drm_panel *panel) drm_mode_set_name(mode); - drm_mode_probed_add(panel->connector, mode); + drm_mode_probed_add(connector, mode); - panel->connector->display_info.width_mm = 95; - panel->connector->display_info.height_mm = 151; + connector->display_info.width_mm = 95; + connector->display_info.height_mm = 151; return 1; } @@ -437,9 +438,8 @@ static int jdi_panel_add(struct jdi_panel *jdi) return ret; } - drm_panel_init(&jdi->base); - jdi->base.funcs = &jdi_panel_funcs; - jdi->base.dev = &jdi->dsi->dev; + drm_panel_init(&jdi->base, &jdi->dsi->dev, &jdi_panel_funcs, + DRM_MODE_CONNECTOR_DSI); ret = drm_panel_add(&jdi->base); diff --git a/drivers/gpu/drm/panel/panel-kingdisplay-kd097d04.c b/drivers/gpu/drm/panel/panel-kingdisplay-kd097d04.c index 3ac04eb8d0fe..bac1a2a06c92 100644 --- a/drivers/gpu/drm/panel/panel-kingdisplay-kd097d04.c +++ b/drivers/gpu/drm/panel/panel-kingdisplay-kd097d04.c @@ -3,7 +3,6 @@ * 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> @@ -23,7 +22,6 @@ struct kingdisplay_panel { struct drm_panel base; struct mipi_dsi_device *link; - struct backlight_device *backlight; struct regulator *supply; struct gpio_desc *enable_gpio; @@ -191,8 +189,6 @@ static int kingdisplay_panel_disable(struct drm_panel *panel) if (!kingdisplay->enabled) return 0; - backlight_disable(kingdisplay->backlight); - err = mipi_dsi_dcs_set_display_off(kingdisplay->link); if (err < 0) DRM_DEV_ERROR(panel->dev, "failed to set display off: %d\n", @@ -303,18 +299,10 @@ poweroff: static int kingdisplay_panel_enable(struct drm_panel *panel) { struct kingdisplay_panel *kingdisplay = to_kingdisplay_panel(panel); - int ret; if (kingdisplay->enabled) return 0; - ret = backlight_enable(kingdisplay->backlight); - if (ret) { - DRM_DEV_ERROR(panel->drm->dev, - "Failed to enable backlight %d\n", ret); - return ret; - } - kingdisplay->enabled = true; return 0; @@ -333,13 +321,14 @@ static const struct drm_display_mode default_mode = { .vrefresh = 60, }; -static int kingdisplay_panel_get_modes(struct drm_panel *panel) +static int kingdisplay_panel_get_modes(struct drm_panel *panel, + struct drm_connector *connector) { struct drm_display_mode *mode; - mode = drm_mode_duplicate(panel->drm, &default_mode); + mode = drm_mode_duplicate(connector->dev, &default_mode); if (!mode) { - DRM_DEV_ERROR(panel->drm->dev, "failed to add mode %ux%ux@%u\n", + DRM_DEV_ERROR(panel->dev, "failed to add mode %ux%ux@%u\n", default_mode.hdisplay, default_mode.vdisplay, default_mode.vrefresh); return -ENOMEM; @@ -347,11 +336,11 @@ static int kingdisplay_panel_get_modes(struct drm_panel *panel) drm_mode_set_name(mode); - drm_mode_probed_add(panel->connector, mode); + drm_mode_probed_add(connector, mode); - panel->connector->display_info.width_mm = 147; - panel->connector->display_info.height_mm = 196; - panel->connector->display_info.bpc = 8; + connector->display_info.width_mm = 147; + connector->display_info.height_mm = 196; + connector->display_info.bpc = 8; return 1; } @@ -387,13 +376,12 @@ static int kingdisplay_panel_add(struct kingdisplay_panel *kingdisplay) kingdisplay->enable_gpio = NULL; } - kingdisplay->backlight = devm_of_find_backlight(dev); - if (IS_ERR(kingdisplay->backlight)) - return PTR_ERR(kingdisplay->backlight); + drm_panel_init(&kingdisplay->base, &kingdisplay->link->dev, + &kingdisplay_panel_funcs, DRM_MODE_CONNECTOR_DSI); - drm_panel_init(&kingdisplay->base); - kingdisplay->base.funcs = &kingdisplay_panel_funcs; - kingdisplay->base.dev = &kingdisplay->link->dev; + err = drm_panel_of_backlight(&kingdisplay->base); + if (err) + return err; return drm_panel_add(&kingdisplay->base); } @@ -432,12 +420,12 @@ static int kingdisplay_panel_remove(struct mipi_dsi_device *dsi) struct kingdisplay_panel *kingdisplay = mipi_dsi_get_drvdata(dsi); int err; - err = kingdisplay_panel_unprepare(&kingdisplay->base); + err = drm_panel_unprepare(&kingdisplay->base); if (err < 0) DRM_DEV_ERROR(&dsi->dev, "failed to unprepare panel: %d\n", err); - err = kingdisplay_panel_disable(&kingdisplay->base); + err = drm_panel_disable(&kingdisplay->base); if (err < 0) DRM_DEV_ERROR(&dsi->dev, "failed to disable panel: %d\n", err); @@ -455,8 +443,8 @@ static void kingdisplay_panel_shutdown(struct mipi_dsi_device *dsi) { struct kingdisplay_panel *kingdisplay = mipi_dsi_get_drvdata(dsi); - kingdisplay_panel_unprepare(&kingdisplay->base); - kingdisplay_panel_disable(&kingdisplay->base); + drm_panel_unprepare(&kingdisplay->base); + drm_panel_disable(&kingdisplay->base); } static struct mipi_dsi_driver kingdisplay_panel_driver = { 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-lb035q02.c b/drivers/gpu/drm/panel/panel-lg-lb035q02.c new file mode 100644 index 000000000000..e90efeaba4ad --- /dev/null +++ b/drivers/gpu/drm/panel/panel-lg-lb035q02.c @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * LG.Philips LB035Q02 LCD Panel Driver + * + * Copyright (C) 2019 Texas Instruments Incorporated + * + * Based on the omapdrm-specific panel-lgphilips-lb035q02 driver + * + * Copyright (C) 2013 Texas Instruments Incorporated + * Author: Tomi Valkeinen <tomi.valkeinen@ti.com> + * + * Based on a driver by: Steve Sakoman <steve@sakoman.com> + */ + +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/spi/spi.h> + +#include <drm/drm_connector.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +struct lb035q02_device { + struct drm_panel panel; + + struct spi_device *spi; + struct gpio_desc *enable_gpio; +}; + +#define to_lb035q02_device(p) container_of(p, struct lb035q02_device, panel) + +static int lb035q02_write(struct lb035q02_device *lcd, u16 reg, u16 val) +{ + struct spi_message msg; + struct spi_transfer index_xfer = { + .len = 3, + .cs_change = 1, + }; + struct spi_transfer value_xfer = { + .len = 3, + }; + u8 buffer[16]; + + spi_message_init(&msg); + + /* register index */ + buffer[0] = 0x70; + buffer[1] = 0x00; + buffer[2] = reg & 0x7f; + index_xfer.tx_buf = buffer; + spi_message_add_tail(&index_xfer, &msg); + + /* register value */ + buffer[4] = 0x72; + buffer[5] = val >> 8; + buffer[6] = val; + value_xfer.tx_buf = buffer + 4; + spi_message_add_tail(&value_xfer, &msg); + + return spi_sync(lcd->spi, &msg); +} + +static int lb035q02_init(struct lb035q02_device *lcd) +{ + /* Init sequence from page 28 of the lb035q02 spec. */ + static const struct { + u16 index; + u16 value; + } init_data[] = { + { 0x01, 0x6300 }, + { 0x02, 0x0200 }, + { 0x03, 0x0177 }, + { 0x04, 0x04c7 }, + { 0x05, 0xffc0 }, + { 0x06, 0xe806 }, + { 0x0a, 0x4008 }, + { 0x0b, 0x0000 }, + { 0x0d, 0x0030 }, + { 0x0e, 0x2800 }, + { 0x0f, 0x0000 }, + { 0x16, 0x9f80 }, + { 0x17, 0x0a0f }, + { 0x1e, 0x00c1 }, + { 0x30, 0x0300 }, + { 0x31, 0x0007 }, + { 0x32, 0x0000 }, + { 0x33, 0x0000 }, + { 0x34, 0x0707 }, + { 0x35, 0x0004 }, + { 0x36, 0x0302 }, + { 0x37, 0x0202 }, + { 0x3a, 0x0a0d }, + { 0x3b, 0x0806 }, + }; + + unsigned int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(init_data); ++i) { + ret = lb035q02_write(lcd, init_data[i].index, + init_data[i].value); + if (ret < 0) + return ret; + } + + return 0; +} + +static int lb035q02_disable(struct drm_panel *panel) +{ + struct lb035q02_device *lcd = to_lb035q02_device(panel); + + gpiod_set_value_cansleep(lcd->enable_gpio, 0); + + return 0; +} + +static int lb035q02_enable(struct drm_panel *panel) +{ + struct lb035q02_device *lcd = to_lb035q02_device(panel); + + gpiod_set_value_cansleep(lcd->enable_gpio, 1); + + return 0; +} + +static const struct drm_display_mode lb035q02_mode = { + .clock = 6500, + .hdisplay = 320, + .hsync_start = 320 + 20, + .hsync_end = 320 + 20 + 2, + .htotal = 320 + 20 + 2 + 68, + .vdisplay = 240, + .vsync_start = 240 + 4, + .vsync_end = 240 + 4 + 2, + .vtotal = 240 + 4 + 2 + 18, + .vrefresh = 60, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + .width_mm = 70, + .height_mm = 53, +}; + +static int lb035q02_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &lb035q02_mode); + if (!mode) + return -ENOMEM; + + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + + connector->display_info.width_mm = lb035q02_mode.width_mm; + connector->display_info.height_mm = lb035q02_mode.height_mm; + /* + * FIXME: According to the datasheet pixel data is sampled on the + * rising edge of the clock, but the code running on the Gumstix Overo + * Palo35 indicates sampling on the negative edge. This should be + * tested on a real device. + */ + connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH + | DRM_BUS_FLAG_SYNC_SAMPLE_POSEDGE + | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE; + + return 1; +} + +static const struct drm_panel_funcs lb035q02_funcs = { + .disable = lb035q02_disable, + .enable = lb035q02_enable, + .get_modes = lb035q02_get_modes, +}; + +static int lb035q02_probe(struct spi_device *spi) +{ + struct lb035q02_device *lcd; + int ret; + + lcd = devm_kzalloc(&spi->dev, sizeof(*lcd), GFP_KERNEL); + if (!lcd) + return -ENOMEM; + + spi_set_drvdata(spi, lcd); + lcd->spi = spi; + + lcd->enable_gpio = devm_gpiod_get(&spi->dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(lcd->enable_gpio)) { + dev_err(&spi->dev, "failed to parse enable gpio\n"); + return PTR_ERR(lcd->enable_gpio); + } + + ret = lb035q02_init(lcd); + if (ret < 0) + return ret; + + drm_panel_init(&lcd->panel, &lcd->spi->dev, &lb035q02_funcs, + DRM_MODE_CONNECTOR_DPI); + + return drm_panel_add(&lcd->panel); +} + +static int lb035q02_remove(struct spi_device *spi) +{ + struct lb035q02_device *lcd = spi_get_drvdata(spi); + + drm_panel_remove(&lcd->panel); + drm_panel_disable(&lcd->panel); + + return 0; +} + +static const struct of_device_id lb035q02_of_match[] = { + { .compatible = "lgphilips,lb035q02", }, + { /* sentinel */ }, +}; + +MODULE_DEVICE_TABLE(of, lb035q02_of_match); + +static const struct spi_device_id lb035q02_ids[] = { + { "lb035q02", 0 }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(spi, lb035q02_ids); + +static struct spi_driver lb035q02_driver = { + .probe = lb035q02_probe, + .remove = lb035q02_remove, + .id_table = lb035q02_ids, + .driver = { + .name = "panel-lg-lb035q02", + .of_match_table = lb035q02_of_match, + }, +}; + +module_spi_driver(lb035q02_driver); + +MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>"); +MODULE_DESCRIPTION("LG.Philips LB035Q02 LCD Panel driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-lg-lg4573.c b/drivers/gpu/drm/panel/panel-lg-lg4573.c index 41bf02d122a1..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); @@ -209,14 +209,14 @@ static const struct drm_display_mode default_mode = { .vrefresh = 60, }; -static int lg4573_get_modes(struct drm_panel *panel) +static int lg4573_get_modes(struct drm_panel *panel, + struct drm_connector *connector) { - struct drm_connector *connector = panel->connector; struct drm_display_mode *mode; - mode = drm_mode_duplicate(panel->drm, &default_mode); + mode = drm_mode_duplicate(connector->dev, &default_mode); if (!mode) { - dev_err(panel->drm->dev, "failed to add mode %ux%ux@%u\n", + dev_err(panel->dev, "failed to add mode %ux%ux@%u\n", default_mode.hdisplay, default_mode.vdisplay, default_mode.vrefresh); return -ENOMEM; @@ -227,8 +227,8 @@ static int lg4573_get_modes(struct drm_panel *panel) mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; drm_mode_probed_add(connector, mode); - panel->connector->display_info.width_mm = 61; - panel->connector->display_info.height_mm = 103; + connector->display_info.width_mm = 61; + connector->display_info.height_mm = 103; return 1; } @@ -259,9 +259,8 @@ static int lg4573_probe(struct spi_device *spi) return ret; } - drm_panel_init(&ctx->panel); - ctx->panel.dev = &spi->dev; - ctx->panel.funcs = &lg4573_drm_funcs; + drm_panel_init(&ctx->panel, &spi->dev, &lg4573_drm_funcs, + DRM_MODE_CONNECTOR_DPI); return drm_panel_add(&ctx->panel); } diff --git a/drivers/gpu/drm/panel/panel-lvds.c b/drivers/gpu/drm/panel/panel-lvds.c index 1ec57d0806a8..5ce3f4a2b7a1 100644 --- a/drivers/gpu/drm/panel/panel-lvds.c +++ b/drivers/gpu/drm/panel/panel-lvds.c @@ -8,7 +8,6 @@ * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) */ -#include <linux/backlight.h> #include <linux/gpio/consumer.h> #include <linux/module.h> #include <linux/of_platform.h> @@ -34,7 +33,6 @@ struct panel_lvds { unsigned int bus_format; bool data_mirror; - struct backlight_device *backlight; struct regulator *supply; struct gpio_desc *enable_gpio; @@ -46,19 +44,6 @@ static inline struct panel_lvds *to_panel_lvds(struct drm_panel *panel) return container_of(panel, struct panel_lvds, panel); } -static int panel_lvds_disable(struct drm_panel *panel) -{ - struct panel_lvds *lvds = to_panel_lvds(panel); - - if (lvds->backlight) { - lvds->backlight->props.power = FB_BLANK_POWERDOWN; - lvds->backlight->props.state |= BL_CORE_FBBLANK; - backlight_update_status(lvds->backlight); - } - - return 0; -} - static int panel_lvds_unprepare(struct drm_panel *panel) { struct panel_lvds *lvds = to_panel_lvds(panel); @@ -93,26 +78,13 @@ static int panel_lvds_prepare(struct drm_panel *panel) return 0; } -static int panel_lvds_enable(struct drm_panel *panel) +static int panel_lvds_get_modes(struct drm_panel *panel, + struct drm_connector *connector) { struct panel_lvds *lvds = to_panel_lvds(panel); - - if (lvds->backlight) { - lvds->backlight->props.state &= ~BL_CORE_FBBLANK; - lvds->backlight->props.power = FB_BLANK_UNBLANK; - backlight_update_status(lvds->backlight); - } - - return 0; -} - -static int panel_lvds_get_modes(struct drm_panel *panel) -{ - struct panel_lvds *lvds = to_panel_lvds(panel); - struct drm_connector *connector = lvds->panel.connector; struct drm_display_mode *mode; - mode = drm_mode_create(lvds->panel.drm); + mode = drm_mode_create(connector->dev); if (!mode) return 0; @@ -132,10 +104,8 @@ static int panel_lvds_get_modes(struct drm_panel *panel) } static const struct drm_panel_funcs panel_lvds_funcs = { - .disable = panel_lvds_disable, .unprepare = panel_lvds_unprepare, .prepare = panel_lvds_prepare, - .enable = panel_lvds_enable, .get_modes = panel_lvds_get_modes, }; @@ -147,8 +117,11 @@ static int panel_lvds_parse_dt(struct panel_lvds *lvds) int ret; ret = of_get_display_timing(np, "panel-timing", &timing); - if (ret < 0) + if (ret < 0) { + dev_err(lvds->dev, "%pOF: problems parsing panel-timing (%d)\n", + np, ret); return ret; + } videomode_from_timing(&timing, &lvds->video_mode); @@ -194,7 +167,6 @@ static int panel_lvds_parse_dt(struct panel_lvds *lvds) static int panel_lvds_probe(struct platform_device *pdev) { struct panel_lvds *lvds; - struct device_node *np; int ret; lvds = devm_kzalloc(&pdev->dev, sizeof(*lvds), GFP_KERNEL); @@ -240,15 +212,6 @@ static int panel_lvds_probe(struct platform_device *pdev) return ret; } - np = of_parse_phandle(lvds->dev->of_node, "backlight", 0); - if (np) { - lvds->backlight = of_find_backlight_by_node(np); - of_node_put(np); - - if (!lvds->backlight) - return -EPROBE_DEFER; - } - /* * TODO: Handle all power supplies specified in the DT node in a generic * way for panels that don't care about power supply ordering. LVDS @@ -257,20 +220,19 @@ static int panel_lvds_probe(struct platform_device *pdev) */ /* Register the panel. */ - drm_panel_init(&lvds->panel); - lvds->panel.dev = lvds->dev; - lvds->panel.funcs = &panel_lvds_funcs; + drm_panel_init(&lvds->panel, lvds->dev, &panel_lvds_funcs, + DRM_MODE_CONNECTOR_LVDS); + + ret = drm_panel_of_backlight(&lvds->panel); + if (ret) + return ret; ret = drm_panel_add(&lvds->panel); if (ret < 0) - goto error; + return ret; dev_set_drvdata(lvds->dev, lvds); return 0; - -error: - put_device(&lvds->backlight->dev); - return ret; } static int panel_lvds_remove(struct platform_device *pdev) @@ -279,10 +241,7 @@ static int panel_lvds_remove(struct platform_device *pdev) drm_panel_remove(&lvds->panel); - panel_lvds_disable(&lvds->panel); - - if (lvds->backlight) - put_device(&lvds->backlight->dev); + drm_panel_disable(&lvds->panel); return 0; } diff --git a/drivers/gpu/drm/panel/panel-nec-nl8048hl11.c b/drivers/gpu/drm/panel/panel-nec-nl8048hl11.c new file mode 100644 index 000000000000..c4f83f6384e1 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-nec-nl8048hl11.c @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * NEC NL8048HL11 Panel Driver + * + * Copyright (C) 2019 Texas Instruments Incorporated + * + * Based on the omapdrm-specific panel-nec-nl8048hl11 driver + * + * Copyright (C) 2010 Texas Instruments Incorporated + * Author: Erik Gilling <konkers@android.com> + */ + +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/pm.h> +#include <linux/spi/spi.h> + +#include <drm/drm_connector.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +struct nl8048_panel { + struct drm_panel panel; + + struct spi_device *spi; + struct gpio_desc *reset_gpio; +}; + +#define to_nl8048_device(p) container_of(p, struct nl8048_panel, panel) + +static int nl8048_write(struct nl8048_panel *lcd, unsigned char addr, + unsigned char value) +{ + u8 data[4] = { value, 0x01, addr, 0x00 }; + int ret; + + ret = spi_write(lcd->spi, data, sizeof(data)); + if (ret) + dev_err(&lcd->spi->dev, "SPI write to %u failed: %d\n", + addr, ret); + + return ret; +} + +static int nl8048_init(struct nl8048_panel *lcd) +{ + static const struct { + unsigned char addr; + unsigned char data; + } nl8048_init_seq[] = { + { 3, 0x01 }, { 0, 0x00 }, { 1, 0x01 }, { 4, 0x00 }, + { 5, 0x14 }, { 6, 0x24 }, { 16, 0xd7 }, { 17, 0x00 }, + { 18, 0x00 }, { 19, 0x55 }, { 20, 0x01 }, { 21, 0x70 }, + { 22, 0x1e }, { 23, 0x25 }, { 24, 0x25 }, { 25, 0x02 }, + { 26, 0x02 }, { 27, 0xa0 }, { 32, 0x2f }, { 33, 0x0f }, + { 34, 0x0f }, { 35, 0x0f }, { 36, 0x0f }, { 37, 0x0f }, + { 38, 0x0f }, { 39, 0x00 }, { 40, 0x02 }, { 41, 0x02 }, + { 42, 0x02 }, { 43, 0x0f }, { 44, 0x0f }, { 45, 0x0f }, + { 46, 0x0f }, { 47, 0x0f }, { 48, 0x0f }, { 49, 0x0f }, + { 50, 0x00 }, { 51, 0x02 }, { 52, 0x02 }, { 53, 0x02 }, + { 80, 0x0c }, { 83, 0x42 }, { 84, 0x42 }, { 85, 0x41 }, + { 86, 0x14 }, { 89, 0x88 }, { 90, 0x01 }, { 91, 0x00 }, + { 92, 0x02 }, { 93, 0x0c }, { 94, 0x1c }, { 95, 0x27 }, + { 98, 0x49 }, { 99, 0x27 }, { 102, 0x76 }, { 103, 0x27 }, + { 112, 0x01 }, { 113, 0x0e }, { 114, 0x02 }, { 115, 0x0c }, + { 118, 0x0c }, { 121, 0x30 }, { 130, 0x00 }, { 131, 0x00 }, + { 132, 0xfc }, { 134, 0x00 }, { 136, 0x00 }, { 138, 0x00 }, + { 139, 0x00 }, { 140, 0x00 }, { 141, 0xfc }, { 143, 0x00 }, + { 145, 0x00 }, { 147, 0x00 }, { 148, 0x00 }, { 149, 0x00 }, + { 150, 0xfc }, { 152, 0x00 }, { 154, 0x00 }, { 156, 0x00 }, + { 157, 0x00 }, + }; + + unsigned int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(nl8048_init_seq); ++i) { + ret = nl8048_write(lcd, nl8048_init_seq[i].addr, + nl8048_init_seq[i].data); + if (ret < 0) + return ret; + } + + udelay(20); + + return nl8048_write(lcd, 2, 0x00); +} + +static int nl8048_disable(struct drm_panel *panel) +{ + struct nl8048_panel *lcd = to_nl8048_device(panel); + + gpiod_set_value_cansleep(lcd->reset_gpio, 0); + + return 0; +} + +static int nl8048_enable(struct drm_panel *panel) +{ + struct nl8048_panel *lcd = to_nl8048_device(panel); + + gpiod_set_value_cansleep(lcd->reset_gpio, 1); + + return 0; +} + +static const struct drm_display_mode nl8048_mode = { + /* NEC PIX Clock Ratings MIN:21.8MHz TYP:23.8MHz MAX:25.7MHz */ + .clock = 23800, + .hdisplay = 800, + .hsync_start = 800 + 6, + .hsync_end = 800 + 6 + 1, + .htotal = 800 + 6 + 1 + 4, + .vdisplay = 480, + .vsync_start = 480 + 3, + .vsync_end = 480 + 3 + 1, + .vtotal = 480 + 3 + 1 + 4, + .vrefresh = 60, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + .width_mm = 89, + .height_mm = 53, +}; + +static int nl8048_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &nl8048_mode); + if (!mode) + return -ENOMEM; + + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + + connector->display_info.width_mm = nl8048_mode.width_mm; + connector->display_info.height_mm = nl8048_mode.height_mm; + connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH + | DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE + | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE; + + return 1; +} + +static const struct drm_panel_funcs nl8048_funcs = { + .disable = nl8048_disable, + .enable = nl8048_enable, + .get_modes = nl8048_get_modes, +}; + +static int __maybe_unused nl8048_suspend(struct device *dev) +{ + struct nl8048_panel *lcd = dev_get_drvdata(dev); + + nl8048_write(lcd, 2, 0x01); + msleep(40); + + return 0; +} + +static int __maybe_unused nl8048_resume(struct device *dev) +{ + struct nl8048_panel *lcd = dev_get_drvdata(dev); + + /* Reinitialize the panel. */ + spi_setup(lcd->spi); + nl8048_write(lcd, 2, 0x00); + nl8048_init(lcd); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(nl8048_pm_ops, nl8048_suspend, nl8048_resume); + +static int nl8048_probe(struct spi_device *spi) +{ + struct nl8048_panel *lcd; + int ret; + + lcd = devm_kzalloc(&spi->dev, sizeof(*lcd), GFP_KERNEL); + if (!lcd) + return -ENOMEM; + + spi_set_drvdata(spi, lcd); + lcd->spi = spi; + + lcd->reset_gpio = devm_gpiod_get(&spi->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(lcd->reset_gpio)) { + dev_err(&spi->dev, "failed to parse reset gpio\n"); + return PTR_ERR(lcd->reset_gpio); + } + + spi->mode = SPI_MODE_0; + spi->bits_per_word = 32; + + ret = spi_setup(spi); + if (ret < 0) { + dev_err(&spi->dev, "failed to setup SPI: %d\n", ret); + return ret; + } + + ret = nl8048_init(lcd); + if (ret < 0) + return ret; + + drm_panel_init(&lcd->panel, &lcd->spi->dev, &nl8048_funcs, + DRM_MODE_CONNECTOR_DPI); + + return drm_panel_add(&lcd->panel); +} + +static int nl8048_remove(struct spi_device *spi) +{ + struct nl8048_panel *lcd = spi_get_drvdata(spi); + + drm_panel_remove(&lcd->panel); + drm_panel_disable(&lcd->panel); + drm_panel_unprepare(&lcd->panel); + + return 0; +} + +static const struct of_device_id nl8048_of_match[] = { + { .compatible = "nec,nl8048hl11", }, + { /* sentinel */ }, +}; + +MODULE_DEVICE_TABLE(of, nl8048_of_match); + +static const struct spi_device_id nl8048_ids[] = { + { "nl8048hl11", 0 }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(spi, nl8048_ids); + +static struct spi_driver nl8048_driver = { + .probe = nl8048_probe, + .remove = nl8048_remove, + .id_table = nl8048_ids, + .driver = { + .name = "panel-nec-nl8048hl11", + .pm = &nl8048_pm_ops, + .of_match_table = nl8048_of_match, + }, +}; + +module_spi_driver(nl8048_driver); + +MODULE_AUTHOR("Erik Gilling <konkers@android.com>"); +MODULE_DESCRIPTION("NEC-NL8048HL11 Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-novatek-nt39016.c b/drivers/gpu/drm/panel/panel-novatek-nt39016.c new file mode 100644 index 000000000000..a470810f7dbe --- /dev/null +++ b/drivers/gpu/drm/panel/panel-novatek-nt39016.c @@ -0,0 +1,358 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Novatek NT39016 TFT LCD panel driver + * + * Copyright (C) 2017, Maarten ter Huurne <maarten@treewalker.org> + * Copyright (C) 2019, Paul Cercueil <paul@crapouillou.net> + */ + +#include <linux/backlight.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/gpio/consumer.h> +#include <linux/media-bus-format.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/spi/spi.h> + +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +enum nt39016_regs { + NT39016_REG_SYSTEM, + NT39016_REG_TIMING, + NT39016_REG_OP, + NT39016_REG_DATA_IN, + NT39016_REG_SRC_TIMING_DELAY, + NT39016_REG_GATE_TIMING_DELAY, + NT39016_REG_RESERVED, + NT39016_REG_INITIAL_FUNC, + NT39016_REG_CONTRAST, + NT39016_REG_BRIGHTNESS, + NT39016_REG_HUE_SATURATION, + NT39016_REG_RB_SUBCONTRAST, + NT39016_REG_R_SUBBRIGHTNESS, + NT39016_REG_B_SUBBRIGHTNESS, + NT39016_REG_VCOMDC, + NT39016_REG_VCOMAC, + NT39016_REG_VGAM2, + NT39016_REG_VGAM34, + NT39016_REG_VGAM56, + NT39016_REG_VCOMDC_TRIM = 0x1e, + NT39016_REG_DISPLAY_MODE = 0x20, +}; + +#define NT39016_SYSTEM_RESET_N BIT(0) +#define NT39016_SYSTEM_STANDBY BIT(1) + +struct nt39016_panel_info { + struct drm_display_mode display_mode; + u16 width_mm, height_mm; + u32 bus_format, bus_flags; +}; + +struct nt39016 { + struct drm_panel drm_panel; + struct device *dev; + struct regmap *map; + struct regulator *supply; + const struct nt39016_panel_info *panel_info; + + struct gpio_desc *reset_gpio; + + struct backlight_device *backlight; +}; + +static inline struct nt39016 *to_nt39016(struct drm_panel *panel) +{ + return container_of(panel, struct nt39016, drm_panel); +} + +#define RV(REG, VAL) { .reg = (REG), .def = (VAL), .delay_us = 2 } +static const struct reg_sequence nt39016_panel_regs[] = { + RV(NT39016_REG_SYSTEM, 0x00), + RV(NT39016_REG_TIMING, 0x00), + RV(NT39016_REG_OP, 0x03), + RV(NT39016_REG_DATA_IN, 0xCC), + RV(NT39016_REG_SRC_TIMING_DELAY, 0x46), + RV(NT39016_REG_GATE_TIMING_DELAY, 0x05), + RV(NT39016_REG_RESERVED, 0x00), + RV(NT39016_REG_INITIAL_FUNC, 0x00), + RV(NT39016_REG_CONTRAST, 0x08), + RV(NT39016_REG_BRIGHTNESS, 0x40), + RV(NT39016_REG_HUE_SATURATION, 0x88), + RV(NT39016_REG_RB_SUBCONTRAST, 0x88), + RV(NT39016_REG_R_SUBBRIGHTNESS, 0x20), + RV(NT39016_REG_B_SUBBRIGHTNESS, 0x20), + RV(NT39016_REG_VCOMDC, 0x67), + RV(NT39016_REG_VCOMAC, 0xA4), + RV(NT39016_REG_VGAM2, 0x04), + RV(NT39016_REG_VGAM34, 0x24), + RV(NT39016_REG_VGAM56, 0x24), + RV(NT39016_REG_DISPLAY_MODE, 0x00), +}; + +#undef RV + +static const struct regmap_range nt39016_regmap_no_ranges[] = { + regmap_reg_range(0x13, 0x1D), + regmap_reg_range(0x1F, 0x1F), +}; + +static const struct regmap_access_table nt39016_regmap_access_table = { + .no_ranges = nt39016_regmap_no_ranges, + .n_no_ranges = ARRAY_SIZE(nt39016_regmap_no_ranges), +}; + +static const struct regmap_config nt39016_regmap_config = { + .reg_bits = 6, + .pad_bits = 2, + .val_bits = 8, + + .max_register = NT39016_REG_DISPLAY_MODE, + .wr_table = &nt39016_regmap_access_table, + .write_flag_mask = 0x02, + + .cache_type = REGCACHE_FLAT, +}; + +static int nt39016_prepare(struct drm_panel *drm_panel) +{ + struct nt39016 *panel = to_nt39016(drm_panel); + int err; + + err = regulator_enable(panel->supply); + if (err) { + dev_err(panel->dev, "Failed to enable power supply: %d", err); + return err; + } + + /* + * Reset the NT39016. + * The documentation says the reset pulse should be at least 40 us to + * pass the glitch filter, but when testing I see some resets fail and + * some succeed when using a 70 us delay, so we use 100 us instead. + */ + gpiod_set_value_cansleep(panel->reset_gpio, 1); + usleep_range(100, 1000); + gpiod_set_value_cansleep(panel->reset_gpio, 0); + udelay(2); + + /* Init all registers. */ + err = regmap_multi_reg_write(panel->map, nt39016_panel_regs, + ARRAY_SIZE(nt39016_panel_regs)); + if (err) { + dev_err(panel->dev, "Failed to init registers: %d", err); + goto err_disable_regulator; + } + + return 0; + +err_disable_regulator: + regulator_disable(panel->supply); + return err; +} + +static int nt39016_unprepare(struct drm_panel *drm_panel) +{ + struct nt39016 *panel = to_nt39016(drm_panel); + + gpiod_set_value_cansleep(panel->reset_gpio, 1); + + regulator_disable(panel->supply); + + return 0; +} + +static int nt39016_enable(struct drm_panel *drm_panel) +{ + struct nt39016 *panel = to_nt39016(drm_panel); + int ret; + + ret = regmap_write(panel->map, NT39016_REG_SYSTEM, + NT39016_SYSTEM_RESET_N | NT39016_SYSTEM_STANDBY); + if (ret) { + dev_err(panel->dev, "Unable to enable panel: %d", ret); + return ret; + } + + if (panel->backlight) { + /* Wait for the picture to be ready before enabling backlight */ + msleep(150); + + ret = backlight_enable(panel->backlight); + } + + return ret; +} + +static int nt39016_disable(struct drm_panel *drm_panel) +{ + struct nt39016 *panel = to_nt39016(drm_panel); + int err; + + backlight_disable(panel->backlight); + + err = regmap_write(panel->map, NT39016_REG_SYSTEM, + NT39016_SYSTEM_RESET_N); + if (err) { + dev_err(panel->dev, "Unable to disable panel: %d", err); + return err; + } + + return 0; +} + +static int nt39016_get_modes(struct drm_panel *drm_panel, + struct drm_connector *connector) +{ + struct nt39016 *panel = to_nt39016(drm_panel); + const struct nt39016_panel_info *panel_info = panel->panel_info; + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &panel_info->display_mode); + if (!mode) + return -ENOMEM; + + drm_mode_set_name(mode); + + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + drm_mode_probed_add(connector, mode); + + connector->display_info.bpc = 8; + connector->display_info.width_mm = panel_info->width_mm; + connector->display_info.height_mm = panel_info->height_mm; + + drm_display_info_set_bus_formats(&connector->display_info, + &panel_info->bus_format, 1); + connector->display_info.bus_flags = panel_info->bus_flags; + + return 1; +} + +static const struct drm_panel_funcs nt39016_funcs = { + .prepare = nt39016_prepare, + .unprepare = nt39016_unprepare, + .enable = nt39016_enable, + .disable = nt39016_disable, + .get_modes = nt39016_get_modes, +}; + +static int nt39016_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct nt39016 *panel; + int err; + + panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL); + if (!panel) + return -ENOMEM; + + panel->dev = dev; + spi_set_drvdata(spi, panel); + + panel->panel_info = of_device_get_match_data(dev); + if (!panel->panel_info) + return -EINVAL; + + panel->supply = devm_regulator_get(dev, "power"); + if (IS_ERR(panel->supply)) { + dev_err(dev, "Failed to get power supply"); + return PTR_ERR(panel->supply); + } + + panel->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(panel->reset_gpio)) { + dev_err(dev, "Failed to get reset GPIO"); + return PTR_ERR(panel->reset_gpio); + } + + spi->bits_per_word = 8; + spi->mode = SPI_MODE_3 | SPI_3WIRE; + err = spi_setup(spi); + if (err) { + dev_err(dev, "Failed to setup SPI"); + return err; + } + + panel->map = devm_regmap_init_spi(spi, &nt39016_regmap_config); + if (IS_ERR(panel->map)) { + dev_err(dev, "Failed to init regmap"); + return PTR_ERR(panel->map); + } + + panel->backlight = devm_of_find_backlight(dev); + if (IS_ERR(panel->backlight)) { + err = PTR_ERR(panel->backlight); + if (err != -EPROBE_DEFER) + dev_err(dev, "Failed to get backlight handle"); + return err; + } + + drm_panel_init(&panel->drm_panel, dev, &nt39016_funcs, + DRM_MODE_CONNECTOR_DPI); + + err = drm_panel_add(&panel->drm_panel); + if (err < 0) { + dev_err(dev, "Failed to register panel"); + return err; + } + + return 0; +} + +static int nt39016_remove(struct spi_device *spi) +{ + struct nt39016 *panel = spi_get_drvdata(spi); + + drm_panel_remove(&panel->drm_panel); + + nt39016_disable(&panel->drm_panel); + nt39016_unprepare(&panel->drm_panel); + + return 0; +} + +static const struct nt39016_panel_info kd035g6_info = { + .display_mode = { + .clock = 6000, + .hdisplay = 320, + .hsync_start = 320 + 10, + .hsync_end = 320 + 10 + 50, + .htotal = 320 + 10 + 50 + 20, + .vdisplay = 240, + .vsync_start = 240 + 5, + .vsync_end = 240 + 5 + 1, + .vtotal = 240 + 5 + 1 + 4, + .vrefresh = 60, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + }, + .width_mm = 71, + .height_mm = 53, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_PIXDATA_NEGEDGE, +}; + +static const struct of_device_id nt39016_of_match[] = { + { .compatible = "kingdisplay,kd035g6-54nt", .data = &kd035g6_info }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, nt39016_of_match); + +static struct spi_driver nt39016_driver = { + .driver = { + .name = "nt39016", + .of_match_table = nt39016_of_match, + }, + .probe = nt39016_probe, + .remove = nt39016_remove, +}; + +module_spi_driver(nt39016_driver); + +MODULE_AUTHOR("Maarten ter Huurne <maarten@treewalker.org>"); +MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-olimex-lcd-olinuxino.c b/drivers/gpu/drm/panel/panel-olimex-lcd-olinuxino.c index 2bae1db3ff34..09deb99981a4 100644 --- a/drivers/gpu/drm/panel/panel-olimex-lcd-olinuxino.c +++ b/drivers/gpu/drm/panel/panel-olimex-lcd-olinuxino.c @@ -6,7 +6,6 @@ * Author: Stefan Mavrodiev <stefan@olimex.com> */ -#include <linux/backlight.h> #include <linux/crc32.h> #include <linux/gpio/consumer.h> #include <linux/i2c.h> @@ -68,7 +67,6 @@ struct lcd_olinuxino { bool prepared; bool enabled; - struct backlight_device *backlight; struct regulator *supply; struct gpio_desc *enable_gpio; @@ -87,8 +85,6 @@ static int lcd_olinuxino_disable(struct drm_panel *panel) if (!lcd->enabled) return 0; - backlight_disable(lcd->backlight); - lcd->enabled = false; return 0; @@ -134,19 +130,16 @@ static int lcd_olinuxino_enable(struct drm_panel *panel) if (lcd->enabled) return 0; - backlight_enable(lcd->backlight); - lcd->enabled = true; return 0; } -static int lcd_olinuxino_get_modes(struct drm_panel *panel) +static int lcd_olinuxino_get_modes(struct drm_panel *panel, + struct drm_connector *connector) { struct lcd_olinuxino *lcd = to_lcd_olinuxino(panel); - struct drm_connector *connector = lcd->panel.connector; struct lcd_olinuxino_info *lcd_info = &lcd->eeprom.info; - struct drm_device *drm = lcd->panel.drm; struct lcd_olinuxino_mode *lcd_mode; struct drm_display_mode *mode; u32 i, num = 0; @@ -155,13 +148,13 @@ static int lcd_olinuxino_get_modes(struct drm_panel *panel) lcd_mode = (struct lcd_olinuxino_mode *) &lcd->eeprom.reserved[i * sizeof(*lcd_mode)]; - mode = drm_mode_create(drm); + mode = drm_mode_create(connector->dev); if (!mode) { - dev_err(drm->dev, "failed to add mode %ux%u@%u\n", + dev_err(panel->dev, "failed to add mode %ux%u@%u\n", lcd_mode->hactive, lcd_mode->vactive, lcd_mode->refresh); - continue; + continue; } mode->clock = lcd_mode->pixelclock; @@ -284,13 +277,12 @@ static int lcd_olinuxino_probe(struct i2c_client *client, if (IS_ERR(lcd->enable_gpio)) return PTR_ERR(lcd->enable_gpio); - lcd->backlight = devm_of_find_backlight(dev); - if (IS_ERR(lcd->backlight)) - return PTR_ERR(lcd->backlight); + drm_panel_init(&lcd->panel, dev, &lcd_olinuxino_funcs, + DRM_MODE_CONNECTOR_DPI); - drm_panel_init(&lcd->panel); - lcd->panel.dev = dev; - lcd->panel.funcs = &lcd_olinuxino_funcs; + ret = drm_panel_of_backlight(&lcd->panel); + if (ret) + return ret; return drm_panel_add(&lcd->panel); } @@ -301,8 +293,8 @@ static int lcd_olinuxino_remove(struct i2c_client *client) drm_panel_remove(&panel->panel); - lcd_olinuxino_disable(&panel->panel); - lcd_olinuxino_unprepare(&panel->panel); + drm_panel_disable(&panel->panel); + drm_panel_unprepare(&panel->panel); return 0; } diff --git a/drivers/gpu/drm/panel/panel-orisetech-otm8009a.c b/drivers/gpu/drm/panel/panel-orisetech-otm8009a.c index c7b48df8869a..bb0c992171e8 100644 --- a/drivers/gpu/drm/panel/panel-orisetech-otm8009a.c +++ b/drivers/gpu/drm/panel/panel-orisetech-otm8009a.c @@ -349,11 +349,12 @@ static int otm8009a_enable(struct drm_panel *panel) return 0; } -static int otm8009a_get_modes(struct drm_panel *panel) +static int otm8009a_get_modes(struct drm_panel *panel, + struct drm_connector *connector) { struct drm_display_mode *mode; - mode = drm_mode_duplicate(panel->drm, &default_mode); + mode = drm_mode_duplicate(connector->dev, &default_mode); if (!mode) { DRM_ERROR("failed to add mode %ux%ux@%u\n", default_mode.hdisplay, default_mode.vdisplay, @@ -364,10 +365,10 @@ static int otm8009a_get_modes(struct drm_panel *panel) drm_mode_set_name(mode); mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; - drm_mode_probed_add(panel->connector, mode); + drm_mode_probed_add(connector, mode); - panel->connector->display_info.width_mm = mode->width_mm; - panel->connector->display_info.height_mm = mode->height_mm; + connector->display_info.width_mm = mode->width_mm; + connector->display_info.height_mm = mode->height_mm; return 1; } @@ -455,9 +456,8 @@ static int otm8009a_probe(struct mipi_dsi_device *dsi) dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | MIPI_DSI_MODE_LPM; - drm_panel_init(&ctx->panel); - ctx->panel.dev = dev; - ctx->panel.funcs = &otm8009a_drm_funcs; + drm_panel_init(&ctx->panel, dev, &otm8009a_drm_funcs, + DRM_MODE_CONNECTOR_DSI); ctx->bl_dev = devm_backlight_device_register(dev, dev_name(dev), dsi->host->dev, ctx, diff --git a/drivers/gpu/drm/panel/panel-osd-osd101t2587-53ts.c b/drivers/gpu/drm/panel/panel-osd-osd101t2587-53ts.c index e0e20ecff916..3a0229d60095 100644 --- a/drivers/gpu/drm/panel/panel-osd-osd101t2587-53ts.c +++ b/drivers/gpu/drm/panel/panel-osd-osd101t2587-53ts.c @@ -4,7 +4,6 @@ * Author: Peter Ujfalusi <peter.ujfalusi@ti.com> */ -#include <linux/backlight.h> #include <linux/module.h> #include <linux/of.h> #include <linux/regulator/consumer.h> @@ -20,7 +19,6 @@ struct osd101t2587_panel { struct drm_panel base; struct mipi_dsi_device *dsi; - struct backlight_device *backlight; struct regulator *supply; bool prepared; @@ -42,8 +40,6 @@ static int osd101t2587_panel_disable(struct drm_panel *panel) if (!osd101t2587->enabled) return 0; - backlight_disable(osd101t2587->backlight); - ret = mipi_dsi_shutdown_peripheral(osd101t2587->dsi); osd101t2587->enabled = false; @@ -91,8 +87,6 @@ static int osd101t2587_panel_enable(struct drm_panel *panel) if (ret) return ret; - backlight_enable(osd101t2587->backlight); - osd101t2587->enabled = true; return ret; @@ -112,14 +106,15 @@ static const struct drm_display_mode default_mode_osd101t2587 = { .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, }; -static int osd101t2587_panel_get_modes(struct drm_panel *panel) +static int osd101t2587_panel_get_modes(struct drm_panel *panel, + struct drm_connector *connector) { struct osd101t2587_panel *osd101t2587 = ti_osd_panel(panel); struct drm_display_mode *mode; - mode = drm_mode_duplicate(panel->drm, osd101t2587->default_mode); + mode = drm_mode_duplicate(connector->dev, osd101t2587->default_mode); if (!mode) { - dev_err(panel->drm->dev, "failed to add mode %ux%ux@%u\n", + dev_err(panel->dev, "failed to add mode %ux%ux@%u\n", osd101t2587->default_mode->hdisplay, osd101t2587->default_mode->vdisplay, osd101t2587->default_mode->vrefresh); @@ -128,10 +123,10 @@ static int osd101t2587_panel_get_modes(struct drm_panel *panel) drm_mode_set_name(mode); - drm_mode_probed_add(panel->connector, mode); + drm_mode_probed_add(connector, mode); - panel->connector->display_info.width_mm = 217; - panel->connector->display_info.height_mm = 136; + connector->display_info.width_mm = 217; + connector->display_info.height_mm = 136; return 1; } @@ -157,18 +152,18 @@ MODULE_DEVICE_TABLE(of, osd101t2587_of_match); static int osd101t2587_panel_add(struct osd101t2587_panel *osd101t2587) { struct device *dev = &osd101t2587->dsi->dev; + int ret; osd101t2587->supply = devm_regulator_get(dev, "power"); if (IS_ERR(osd101t2587->supply)) return PTR_ERR(osd101t2587->supply); - osd101t2587->backlight = devm_of_find_backlight(dev); - if (IS_ERR(osd101t2587->backlight)) - return PTR_ERR(osd101t2587->backlight); + drm_panel_init(&osd101t2587->base, &osd101t2587->dsi->dev, + &osd101t2587_panel_funcs, DRM_MODE_CONNECTOR_DSI); - drm_panel_init(&osd101t2587->base); - osd101t2587->base.funcs = &osd101t2587_panel_funcs; - osd101t2587->base.dev = &osd101t2587->dsi->dev; + ret = drm_panel_of_backlight(&osd101t2587->base); + if (ret) + return ret; return drm_panel_add(&osd101t2587->base); } @@ -215,12 +210,11 @@ static int osd101t2587_panel_remove(struct mipi_dsi_device *dsi) struct osd101t2587_panel *osd101t2587 = mipi_dsi_get_drvdata(dsi); int ret; - ret = osd101t2587_panel_disable(&osd101t2587->base); + ret = drm_panel_disable(&osd101t2587->base); if (ret < 0) dev_warn(&dsi->dev, "failed to disable panel: %d\n", ret); - osd101t2587_panel_unprepare(&osd101t2587->base); - + drm_panel_unprepare(&osd101t2587->base); drm_panel_remove(&osd101t2587->base); ret = mipi_dsi_detach(dsi); @@ -234,8 +228,8 @@ static void osd101t2587_panel_shutdown(struct mipi_dsi_device *dsi) { struct osd101t2587_panel *osd101t2587 = mipi_dsi_get_drvdata(dsi); - osd101t2587_panel_disable(&osd101t2587->base); - osd101t2587_panel_unprepare(&osd101t2587->base); + drm_panel_disable(&osd101t2587->base); + drm_panel_unprepare(&osd101t2587->base); } static struct mipi_dsi_driver osd101t2587_panel_driver = { diff --git a/drivers/gpu/drm/panel/panel-panasonic-vvx10f034n00.c b/drivers/gpu/drm/panel/panel-panasonic-vvx10f034n00.c index 3dff0b3f73c2..69693451462e 100644 --- a/drivers/gpu/drm/panel/panel-panasonic-vvx10f034n00.c +++ b/drivers/gpu/drm/panel/panel-panasonic-vvx10f034n00.c @@ -7,7 +7,6 @@ * Based on AUO panel driver by Rob Clark <robdclark@gmail.com> */ -#include <linux/backlight.h> #include <linux/delay.h> #include <linux/module.h> #include <linux/of.h> @@ -31,7 +30,6 @@ struct wuxga_nt_panel { struct drm_panel base; struct mipi_dsi_device *dsi; - struct backlight_device *backlight; struct regulator *supply; bool prepared; @@ -62,12 +60,6 @@ static int wuxga_nt_panel_disable(struct drm_panel *panel) mipi_ret = mipi_dsi_shutdown_peripheral(wuxga_nt->dsi); - if (wuxga_nt->backlight) { - wuxga_nt->backlight->props.power = FB_BLANK_POWERDOWN; - wuxga_nt->backlight->props.state |= BL_CORE_FBBLANK; - bl_ret = backlight_update_status(wuxga_nt->backlight); - } - wuxga_nt->enabled = false; return mipi_ret ? mipi_ret : bl_ret; @@ -142,12 +134,6 @@ static int wuxga_nt_panel_enable(struct drm_panel *panel) if (wuxga_nt->enabled) return 0; - if (wuxga_nt->backlight) { - wuxga_nt->backlight->props.power = FB_BLANK_UNBLANK; - wuxga_nt->backlight->props.state &= ~BL_CORE_FBBLANK; - backlight_update_status(wuxga_nt->backlight); - } - wuxga_nt->enabled = true; return 0; @@ -166,24 +152,25 @@ static const struct drm_display_mode default_mode = { .vrefresh = 60, }; -static int wuxga_nt_panel_get_modes(struct drm_panel *panel) +static int wuxga_nt_panel_get_modes(struct drm_panel *panel, + struct drm_connector *connector) { struct drm_display_mode *mode; - mode = drm_mode_duplicate(panel->drm, &default_mode); + mode = drm_mode_duplicate(connector->dev, &default_mode); if (!mode) { - dev_err(panel->drm->dev, "failed to add mode %ux%ux@%u\n", - default_mode.hdisplay, default_mode.vdisplay, - default_mode.vrefresh); + dev_err(panel->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); - drm_mode_probed_add(panel->connector, mode); + drm_mode_probed_add(connector, mode); - panel->connector->display_info.width_mm = 217; - panel->connector->display_info.height_mm = 136; + connector->display_info.width_mm = 217; + connector->display_info.height_mm = 136; return 1; } @@ -205,7 +192,6 @@ MODULE_DEVICE_TABLE(of, wuxga_nt_of_match); static int wuxga_nt_panel_add(struct wuxga_nt_panel *wuxga_nt) { struct device *dev = &wuxga_nt->dsi->dev; - struct device_node *np; int ret; wuxga_nt->mode = &default_mode; @@ -214,39 +200,20 @@ static int wuxga_nt_panel_add(struct wuxga_nt_panel *wuxga_nt) if (IS_ERR(wuxga_nt->supply)) return PTR_ERR(wuxga_nt->supply); - np = of_parse_phandle(dev->of_node, "backlight", 0); - if (np) { - wuxga_nt->backlight = of_find_backlight_by_node(np); - of_node_put(np); - - if (!wuxga_nt->backlight) - return -EPROBE_DEFER; - } - - drm_panel_init(&wuxga_nt->base); - wuxga_nt->base.funcs = &wuxga_nt_panel_funcs; - wuxga_nt->base.dev = &wuxga_nt->dsi->dev; + drm_panel_init(&wuxga_nt->base, &wuxga_nt->dsi->dev, + &wuxga_nt_panel_funcs, DRM_MODE_CONNECTOR_DSI); - ret = drm_panel_add(&wuxga_nt->base); - if (ret < 0) - goto put_backlight; - - return 0; - -put_backlight: - if (wuxga_nt->backlight) - put_device(&wuxga_nt->backlight->dev); + ret = drm_panel_of_backlight(&wuxga_nt->base); + if (ret) + return ret; - return ret; + return drm_panel_add(&wuxga_nt->base); } static void wuxga_nt_panel_del(struct wuxga_nt_panel *wuxga_nt) { if (wuxga_nt->base.dev) drm_panel_remove(&wuxga_nt->base); - - if (wuxga_nt->backlight) - put_device(&wuxga_nt->backlight->dev); } static int wuxga_nt_panel_probe(struct mipi_dsi_device *dsi) @@ -281,7 +248,7 @@ static int wuxga_nt_panel_remove(struct mipi_dsi_device *dsi) struct wuxga_nt_panel *wuxga_nt = mipi_dsi_get_drvdata(dsi); int ret; - ret = wuxga_nt_panel_disable(&wuxga_nt->base); + ret = drm_panel_disable(&wuxga_nt->base); if (ret < 0) dev_err(&dsi->dev, "failed to disable panel: %d\n", ret); @@ -298,7 +265,7 @@ static void wuxga_nt_panel_shutdown(struct mipi_dsi_device *dsi) { struct wuxga_nt_panel *wuxga_nt = mipi_dsi_get_drvdata(dsi); - wuxga_nt_panel_disable(&wuxga_nt->base); + drm_panel_disable(&wuxga_nt->base); } static struct mipi_dsi_driver wuxga_nt_panel_driver = { diff --git a/drivers/gpu/drm/panel/panel-raspberrypi-touchscreen.c b/drivers/gpu/drm/panel/panel-raspberrypi-touchscreen.c index 28c0620dfe0f..8f078b7dd89e 100644 --- a/drivers/gpu/drm/panel/panel-raspberrypi-touchscreen.c +++ b/drivers/gpu/drm/panel/panel-raspberrypi-touchscreen.c @@ -44,8 +44,6 @@ #include <linux/delay.h> #include <linux/err.h> #include <linux/fb.h> -#include <linux/gpio.h> -#include <linux/gpio/consumer.h> #include <linux/i2c.h> #include <linux/module.h> #include <linux/of.h> @@ -311,10 +309,9 @@ static int rpi_touchscreen_enable(struct drm_panel *panel) return 0; } -static int rpi_touchscreen_get_modes(struct drm_panel *panel) +static int rpi_touchscreen_get_modes(struct drm_panel *panel, + struct drm_connector *connector) { - struct drm_connector *connector = panel->connector; - struct drm_device *drm = panel->drm; unsigned int i, num = 0; static const u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24; @@ -322,9 +319,9 @@ static int rpi_touchscreen_get_modes(struct drm_panel *panel) const struct drm_display_mode *m = &rpi_touchscreen_modes[i]; struct drm_display_mode *mode; - mode = drm_mode_duplicate(drm, m); + mode = drm_mode_duplicate(connector->dev, m); if (!mode) { - dev_err(drm->dev, "failed to add mode %ux%u@%u\n", + dev_err(panel->dev, "failed to add mode %ux%u@%u\n", m->hdisplay, m->vdisplay, m->vrefresh); continue; } @@ -399,7 +396,13 @@ static int rpi_touchscreen_probe(struct i2c_client *i2c, /* Look up the DSI host. It needs to probe before we do. */ endpoint = of_graph_get_next_endpoint(dev->of_node, NULL); + if (!endpoint) + return -ENODEV; + dsi_host_node = of_graph_get_remote_port_parent(endpoint); + if (!dsi_host_node) + goto error; + host = of_find_mipi_dsi_host_by_node(dsi_host_node); of_node_put(dsi_host_node); if (!host) { @@ -408,6 +411,9 @@ static int rpi_touchscreen_probe(struct i2c_client *i2c, } info.node = of_graph_get_remote_port(endpoint); + if (!info.node) + goto error; + of_node_put(endpoint); ts->dsi = mipi_dsi_device_register_full(host, &info); @@ -417,8 +423,8 @@ static int rpi_touchscreen_probe(struct i2c_client *i2c, return PTR_ERR(ts->dsi); } - ts->base.dev = dev; - ts->base.funcs = &rpi_touchscreen_funcs; + drm_panel_init(&ts->base, dev, &rpi_touchscreen_funcs, + DRM_MODE_CONNECTOR_DSI); /* This appears last, as it's what will unblock the DSI host * driver's component bind function. @@ -428,6 +434,10 @@ static int rpi_touchscreen_probe(struct i2c_client *i2c, return ret; return 0; + +error: + of_node_put(endpoint); + return -ENODEV; } static int rpi_touchscreen_remove(struct i2c_client *i2c) diff --git a/drivers/gpu/drm/panel/panel-raydium-rm67191.c b/drivers/gpu/drm/panel/panel-raydium-rm67191.c new file mode 100644 index 000000000000..313637d53d28 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-raydium-rm67191.c @@ -0,0 +1,667 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Raydium RM67191 MIPI-DSI panel driver + * + * Copyright 2019 NXP + */ + +#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 <video/of_videomode.h> +#include <video/videomode.h> + +#include <drm/drm_crtc.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> + +/* Panel specific color-format bits */ +#define COL_FMT_16BPP 0x55 +#define COL_FMT_18BPP 0x66 +#define COL_FMT_24BPP 0x77 + +/* Write Manufacture Command Set Control */ +#define WRMAUCCTR 0xFE + +/* Manufacturer Command Set pages (CMD2) */ +struct cmd_set_entry { + u8 cmd; + u8 param; +}; + +/* + * There is no description in the Reference Manual about these commands. + * We received them from vendor, so just use them as is. + */ +static const struct cmd_set_entry manufacturer_cmd_set[] = { + {0xFE, 0x0B}, + {0x28, 0x40}, + {0x29, 0x4F}, + {0xFE, 0x0E}, + {0x4B, 0x00}, + {0x4C, 0x0F}, + {0x4D, 0x20}, + {0x4E, 0x40}, + {0x4F, 0x60}, + {0x50, 0xA0}, + {0x51, 0xC0}, + {0x52, 0xE0}, + {0x53, 0xFF}, + {0xFE, 0x0D}, + {0x18, 0x08}, + {0x42, 0x00}, + {0x08, 0x41}, + {0x46, 0x02}, + {0x72, 0x09}, + {0xFE, 0x0A}, + {0x24, 0x17}, + {0x04, 0x07}, + {0x1A, 0x0C}, + {0x0F, 0x44}, + {0xFE, 0x04}, + {0x00, 0x0C}, + {0x05, 0x08}, + {0x06, 0x08}, + {0x08, 0x08}, + {0x09, 0x08}, + {0x0A, 0xE6}, + {0x0B, 0x8C}, + {0x1A, 0x12}, + {0x1E, 0xE0}, + {0x29, 0x93}, + {0x2A, 0x93}, + {0x2F, 0x02}, + {0x31, 0x02}, + {0x33, 0x05}, + {0x37, 0x2D}, + {0x38, 0x2D}, + {0x3A, 0x1E}, + {0x3B, 0x1E}, + {0x3D, 0x27}, + {0x3F, 0x80}, + {0x40, 0x40}, + {0x41, 0xE0}, + {0x4F, 0x2F}, + {0x50, 0x1E}, + {0xFE, 0x06}, + {0x00, 0xCC}, + {0x05, 0x05}, + {0x07, 0xA2}, + {0x08, 0xCC}, + {0x0D, 0x03}, + {0x0F, 0xA2}, + {0x32, 0xCC}, + {0x37, 0x05}, + {0x39, 0x83}, + {0x3A, 0xCC}, + {0x41, 0x04}, + {0x43, 0x83}, + {0x44, 0xCC}, + {0x49, 0x05}, + {0x4B, 0xA2}, + {0x4C, 0xCC}, + {0x51, 0x03}, + {0x53, 0xA2}, + {0x75, 0xCC}, + {0x7A, 0x03}, + {0x7C, 0x83}, + {0x7D, 0xCC}, + {0x82, 0x02}, + {0x84, 0x83}, + {0x85, 0xEC}, + {0x86, 0x0F}, + {0x87, 0xFF}, + {0x88, 0x00}, + {0x8A, 0x02}, + {0x8C, 0xA2}, + {0x8D, 0xEA}, + {0x8E, 0x01}, + {0x8F, 0xE8}, + {0xFE, 0x06}, + {0x90, 0x0A}, + {0x92, 0x06}, + {0x93, 0xA0}, + {0x94, 0xA8}, + {0x95, 0xEC}, + {0x96, 0x0F}, + {0x97, 0xFF}, + {0x98, 0x00}, + {0x9A, 0x02}, + {0x9C, 0xA2}, + {0xAC, 0x04}, + {0xFE, 0x06}, + {0xB1, 0x12}, + {0xB2, 0x17}, + {0xB3, 0x17}, + {0xB4, 0x17}, + {0xB5, 0x17}, + {0xB6, 0x11}, + {0xB7, 0x08}, + {0xB8, 0x09}, + {0xB9, 0x06}, + {0xBA, 0x07}, + {0xBB, 0x17}, + {0xBC, 0x17}, + {0xBD, 0x17}, + {0xBE, 0x17}, + {0xBF, 0x17}, + {0xC0, 0x17}, + {0xC1, 0x17}, + {0xC2, 0x17}, + {0xC3, 0x17}, + {0xC4, 0x0F}, + {0xC5, 0x0E}, + {0xC6, 0x00}, + {0xC7, 0x01}, + {0xC8, 0x10}, + {0xFE, 0x06}, + {0x95, 0xEC}, + {0x8D, 0xEE}, + {0x44, 0xEC}, + {0x4C, 0xEC}, + {0x32, 0xEC}, + {0x3A, 0xEC}, + {0x7D, 0xEC}, + {0x75, 0xEC}, + {0x00, 0xEC}, + {0x08, 0xEC}, + {0x85, 0xEC}, + {0xA6, 0x21}, + {0xA7, 0x05}, + {0xA9, 0x06}, + {0x82, 0x06}, + {0x41, 0x06}, + {0x7A, 0x07}, + {0x37, 0x07}, + {0x05, 0x06}, + {0x49, 0x06}, + {0x0D, 0x04}, + {0x51, 0x04}, +}; + +static const u32 rad_bus_formats[] = { + MEDIA_BUS_FMT_RGB888_1X24, + MEDIA_BUS_FMT_RGB666_1X18, + MEDIA_BUS_FMT_RGB565_1X16, +}; + +static const u32 rad_bus_flags = DRM_BUS_FLAG_DE_LOW | + DRM_BUS_FLAG_PIXDATA_NEGEDGE; + +struct rad_panel { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + + struct gpio_desc *reset; + struct backlight_device *backlight; + + struct regulator_bulk_data *supplies; + unsigned int num_supplies; + + bool prepared; + bool enabled; +}; + +static const struct drm_display_mode default_mode = { + .clock = 132000, + .hdisplay = 1080, + .hsync_start = 1080 + 20, + .hsync_end = 1080 + 20 + 2, + .htotal = 1080 + 20 + 2 + 34, + .vdisplay = 1920, + .vsync_start = 1920 + 10, + .vsync_end = 1920 + 10 + 2, + .vtotal = 1920 + 10 + 2 + 4, + .vrefresh = 60, + .width_mm = 68, + .height_mm = 121, + .flags = DRM_MODE_FLAG_NHSYNC | + DRM_MODE_FLAG_NVSYNC, +}; + +static inline struct rad_panel *to_rad_panel(struct drm_panel *panel) +{ + return container_of(panel, struct rad_panel, panel); +} + +static int rad_panel_push_cmd_list(struct mipi_dsi_device *dsi) +{ + size_t i; + size_t count = ARRAY_SIZE(manufacturer_cmd_set); + int ret = 0; + + for (i = 0; i < count; i++) { + const struct cmd_set_entry *entry = &manufacturer_cmd_set[i]; + u8 buffer[2] = { entry->cmd, entry->param }; + + ret = mipi_dsi_generic_write(dsi, &buffer, sizeof(buffer)); + if (ret < 0) + return ret; + } + + return ret; +}; + +static int color_format_from_dsi_format(enum mipi_dsi_pixel_format format) +{ + switch (format) { + case MIPI_DSI_FMT_RGB565: + return COL_FMT_16BPP; + case MIPI_DSI_FMT_RGB666: + case MIPI_DSI_FMT_RGB666_PACKED: + return COL_FMT_18BPP; + case MIPI_DSI_FMT_RGB888: + return COL_FMT_24BPP; + default: + return COL_FMT_24BPP; /* for backward compatibility */ + } +}; + +static int rad_panel_prepare(struct drm_panel *panel) +{ + struct rad_panel *rad = to_rad_panel(panel); + int ret; + + if (rad->prepared) + return 0; + + ret = regulator_bulk_enable(rad->num_supplies, rad->supplies); + if (ret) + return ret; + + if (rad->reset) { + gpiod_set_value_cansleep(rad->reset, 1); + usleep_range(3000, 5000); + gpiod_set_value_cansleep(rad->reset, 0); + usleep_range(18000, 20000); + } + + rad->prepared = true; + + return 0; +} + +static int rad_panel_unprepare(struct drm_panel *panel) +{ + struct rad_panel *rad = to_rad_panel(panel); + int ret; + + if (!rad->prepared) + return 0; + + /* + * Right after asserting the reset, we need to release it, so that the + * touch driver can have an active connection with the touch controller + * even after the display is turned off. + */ + if (rad->reset) { + gpiod_set_value_cansleep(rad->reset, 1); + usleep_range(15000, 17000); + gpiod_set_value_cansleep(rad->reset, 0); + } + + ret = regulator_bulk_disable(rad->num_supplies, rad->supplies); + if (ret) + return ret; + + rad->prepared = false; + + return 0; +} + +static int rad_panel_enable(struct drm_panel *panel) +{ + struct rad_panel *rad = to_rad_panel(panel); + struct mipi_dsi_device *dsi = rad->dsi; + struct device *dev = &dsi->dev; + int color_format = color_format_from_dsi_format(dsi->format); + int ret; + + if (rad->enabled) + return 0; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + ret = rad_panel_push_cmd_list(dsi); + if (ret < 0) { + DRM_DEV_ERROR(dev, "Failed to send MCS (%d)\n", ret); + goto fail; + } + + /* Select User Command Set table (CMD1) */ + ret = mipi_dsi_generic_write(dsi, (u8[]){ WRMAUCCTR, 0x00 }, 2); + if (ret < 0) + goto fail; + + /* Software reset */ + ret = mipi_dsi_dcs_soft_reset(dsi); + if (ret < 0) { + DRM_DEV_ERROR(dev, "Failed to do Software Reset (%d)\n", ret); + goto fail; + } + + usleep_range(15000, 17000); + + /* Set DSI mode */ + ret = mipi_dsi_generic_write(dsi, (u8[]){ 0xC2, 0x0B }, 2); + if (ret < 0) { + DRM_DEV_ERROR(dev, "Failed to set DSI mode (%d)\n", ret); + goto fail; + } + /* Set tear ON */ + ret = mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK); + if (ret < 0) { + DRM_DEV_ERROR(dev, "Failed to set tear ON (%d)\n", ret); + goto fail; + } + /* Set tear scanline */ + ret = mipi_dsi_dcs_set_tear_scanline(dsi, 0x380); + if (ret < 0) { + DRM_DEV_ERROR(dev, "Failed to set tear scanline (%d)\n", ret); + goto fail; + } + /* Set pixel format */ + ret = mipi_dsi_dcs_set_pixel_format(dsi, color_format); + DRM_DEV_DEBUG_DRIVER(dev, "Interface color format set to 0x%x\n", + color_format); + if (ret < 0) { + DRM_DEV_ERROR(dev, "Failed to set pixel format (%d)\n", ret); + goto fail; + } + /* Exit sleep mode */ + ret = mipi_dsi_dcs_exit_sleep_mode(dsi); + if (ret < 0) { + DRM_DEV_ERROR(dev, "Failed to exit sleep mode (%d)\n", ret); + goto fail; + } + + usleep_range(5000, 7000); + + ret = mipi_dsi_dcs_set_display_on(dsi); + if (ret < 0) { + DRM_DEV_ERROR(dev, "Failed to set display ON (%d)\n", ret); + goto fail; + } + + backlight_enable(rad->backlight); + + rad->enabled = true; + + return 0; + +fail: + gpiod_set_value_cansleep(rad->reset, 1); + + return ret; +} + +static int rad_panel_disable(struct drm_panel *panel) +{ + struct rad_panel *rad = to_rad_panel(panel); + struct mipi_dsi_device *dsi = rad->dsi; + struct device *dev = &dsi->dev; + int ret; + + if (!rad->enabled) + return 0; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + backlight_disable(rad->backlight); + + usleep_range(10000, 12000); + + ret = mipi_dsi_dcs_set_display_off(dsi); + if (ret < 0) { + DRM_DEV_ERROR(dev, "Failed to set display OFF (%d)\n", ret); + return ret; + } + + usleep_range(5000, 10000); + + ret = mipi_dsi_dcs_enter_sleep_mode(dsi); + if (ret < 0) { + DRM_DEV_ERROR(dev, "Failed to enter sleep mode (%d)\n", ret); + return ret; + } + + rad->enabled = false; + + return 0; +} + +static int rad_panel_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &default_mode); + if (!mode) { + DRM_DEV_ERROR(panel->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; + drm_mode_probed_add(connector, mode); + + connector->display_info.width_mm = mode->width_mm; + connector->display_info.height_mm = mode->height_mm; + connector->display_info.bus_flags = rad_bus_flags; + + drm_display_info_set_bus_formats(&connector->display_info, + rad_bus_formats, + ARRAY_SIZE(rad_bus_formats)); + return 1; +} + +static int rad_bl_get_brightness(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + struct rad_panel *rad = mipi_dsi_get_drvdata(dsi); + u16 brightness; + int ret; + + if (!rad->prepared) + return 0; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + ret = mipi_dsi_dcs_get_display_brightness(dsi, &brightness); + if (ret < 0) + return ret; + + bl->props.brightness = brightness; + + return brightness & 0xff; +} + +static int rad_bl_update_status(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + struct rad_panel *rad = mipi_dsi_get_drvdata(dsi); + int ret = 0; + + if (!rad->prepared) + return 0; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + ret = mipi_dsi_dcs_set_display_brightness(dsi, bl->props.brightness); + if (ret < 0) + return ret; + + return 0; +} + +static const struct backlight_ops rad_bl_ops = { + .update_status = rad_bl_update_status, + .get_brightness = rad_bl_get_brightness, +}; + +static const struct drm_panel_funcs rad_panel_funcs = { + .prepare = rad_panel_prepare, + .unprepare = rad_panel_unprepare, + .enable = rad_panel_enable, + .disable = rad_panel_disable, + .get_modes = rad_panel_get_modes, +}; + +static const char * const rad_supply_names[] = { + "v3p3", + "v1p8", +}; + +static int rad_init_regulators(struct rad_panel *rad) +{ + struct device *dev = &rad->dsi->dev; + int i; + + rad->num_supplies = ARRAY_SIZE(rad_supply_names); + rad->supplies = devm_kcalloc(dev, rad->num_supplies, + sizeof(*rad->supplies), GFP_KERNEL); + if (!rad->supplies) + return -ENOMEM; + + for (i = 0; i < rad->num_supplies; i++) + rad->supplies[i].supply = rad_supply_names[i]; + + return devm_regulator_bulk_get(dev, rad->num_supplies, rad->supplies); +}; + +static int rad_panel_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct device_node *np = dev->of_node; + struct rad_panel *panel; + struct backlight_properties bl_props; + int ret; + u32 video_mode; + + panel = devm_kzalloc(&dsi->dev, sizeof(*panel), GFP_KERNEL); + if (!panel) + return -ENOMEM; + + mipi_dsi_set_drvdata(dsi, panel); + + panel->dsi = dsi; + + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | + MIPI_DSI_CLOCK_NON_CONTINUOUS; + + ret = of_property_read_u32(np, "video-mode", &video_mode); + if (!ret) { + switch (video_mode) { + case 0: + /* burst mode */ + dsi->mode_flags |= MIPI_DSI_MODE_VIDEO_BURST; + break; + case 1: + /* non-burst mode with sync event */ + break; + case 2: + /* non-burst mode with sync pulse */ + dsi->mode_flags |= MIPI_DSI_MODE_VIDEO_SYNC_PULSE; + break; + default: + dev_warn(dev, "invalid video mode %d\n", video_mode); + break; + } + } + + ret = of_property_read_u32(np, "dsi-lanes", &dsi->lanes); + if (ret) { + dev_err(dev, "Failed to get dsi-lanes property (%d)\n", ret); + return ret; + } + + panel->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(panel->reset)) + return PTR_ERR(panel->reset); + + memset(&bl_props, 0, sizeof(bl_props)); + bl_props.type = BACKLIGHT_RAW; + bl_props.brightness = 255; + bl_props.max_brightness = 255; + + panel->backlight = devm_backlight_device_register(dev, dev_name(dev), + dev, dsi, &rad_bl_ops, + &bl_props); + if (IS_ERR(panel->backlight)) { + ret = PTR_ERR(panel->backlight); + dev_err(dev, "Failed to register backlight (%d)\n", ret); + return ret; + } + + ret = rad_init_regulators(panel); + if (ret) + return ret; + + drm_panel_init(&panel->panel, dev, &rad_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + dev_set_drvdata(dev, panel); + + ret = drm_panel_add(&panel->panel); + if (ret) + return ret; + + ret = mipi_dsi_attach(dsi); + if (ret) + drm_panel_remove(&panel->panel); + + return ret; +} + +static int rad_panel_remove(struct mipi_dsi_device *dsi) +{ + struct rad_panel *rad = mipi_dsi_get_drvdata(dsi); + struct device *dev = &dsi->dev; + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret) + DRM_DEV_ERROR(dev, "Failed to detach from host (%d)\n", + ret); + + drm_panel_remove(&rad->panel); + + return 0; +} + +static void rad_panel_shutdown(struct mipi_dsi_device *dsi) +{ + struct rad_panel *rad = mipi_dsi_get_drvdata(dsi); + + rad_panel_disable(&rad->panel); + rad_panel_unprepare(&rad->panel); +} + +static const struct of_device_id rad_of_match[] = { + { .compatible = "raydium,rm67191", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rad_of_match); + +static struct mipi_dsi_driver rad_panel_driver = { + .driver = { + .name = "panel-raydium-rm67191", + .of_match_table = rad_of_match, + }, + .probe = rad_panel_probe, + .remove = rad_panel_remove, + .shutdown = rad_panel_shutdown, +}; +module_mipi_dsi_driver(rad_panel_driver); + +MODULE_AUTHOR("Robert Chiras <robert.chiras@nxp.com>"); +MODULE_DESCRIPTION("DRM Driver for Raydium RM67191 MIPI DSI panel"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-raydium-rm68200.c b/drivers/gpu/drm/panel/panel-raydium-rm68200.c index ba889625ad43..e8982948e0ea 100644 --- a/drivers/gpu/drm/panel/panel-raydium-rm68200.c +++ b/drivers/gpu/drm/panel/panel-raydium-rm68200.c @@ -6,9 +6,9 @@ * Yannick Fertre <yannick.fertre@st.com> */ -#include <linux/backlight.h> #include <linux/delay.h> #include <linux/gpio/consumer.h> +#include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/regulator/consumer.h> @@ -78,7 +78,6 @@ struct rm68200 { struct drm_panel panel; struct gpio_desc *reset_gpio; struct regulator *supply; - struct backlight_device *backlight; bool prepared; bool enabled; }; @@ -242,8 +241,6 @@ static int rm68200_disable(struct drm_panel *panel) if (!ctx->enabled) return 0; - backlight_disable(ctx->backlight); - ctx->enabled = false; return 0; @@ -328,18 +325,17 @@ static int rm68200_enable(struct drm_panel *panel) if (ctx->enabled) return 0; - backlight_enable(ctx->backlight); - ctx->enabled = true; return 0; } -static int rm68200_get_modes(struct drm_panel *panel) +static int rm68200_get_modes(struct drm_panel *panel, + struct drm_connector *connector) { struct drm_display_mode *mode; - mode = drm_mode_duplicate(panel->drm, &default_mode); + mode = drm_mode_duplicate(connector->dev, &default_mode); if (!mode) { DRM_ERROR("failed to add mode %ux%ux@%u\n", default_mode.hdisplay, default_mode.vdisplay, @@ -350,10 +346,10 @@ static int rm68200_get_modes(struct drm_panel *panel) drm_mode_set_name(mode); mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; - drm_mode_probed_add(panel->connector, mode); + drm_mode_probed_add(connector, mode); - panel->connector->display_info.width_mm = mode->width_mm; - panel->connector->display_info.height_mm = mode->height_mm; + connector->display_info.width_mm = mode->width_mm; + connector->display_info.height_mm = mode->height_mm; return 1; } @@ -391,10 +387,6 @@ static int rm68200_probe(struct mipi_dsi_device *dsi) return ret; } - ctx->backlight = devm_of_find_backlight(dev); - if (IS_ERR(ctx->backlight)) - return PTR_ERR(ctx->backlight); - mipi_dsi_set_drvdata(dsi, ctx); ctx->dev = dev; @@ -404,9 +396,12 @@ static int rm68200_probe(struct mipi_dsi_device *dsi) dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | MIPI_DSI_MODE_LPM; - drm_panel_init(&ctx->panel); - ctx->panel.dev = dev; - ctx->panel.funcs = &rm68200_drm_funcs; + drm_panel_init(&ctx->panel, dev, &rm68200_drm_funcs, + DRM_MODE_CONNECTOR_DSI); + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return ret; drm_panel_add(&ctx->panel); diff --git a/drivers/gpu/drm/panel/panel-rocktech-jh057n00900.c b/drivers/gpu/drm/panel/panel-rocktech-jh057n00900.c index 6dcb692c4701..38ff742bc120 100644 --- a/drivers/gpu/drm/panel/panel-rocktech-jh057n00900.c +++ b/drivers/gpu/drm/panel/panel-rocktech-jh057n00900.c @@ -5,19 +5,22 @@ * 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 <linux/backlight.h> #include <linux/debugfs.h> #include <linux/delay.h> #include <linux/gpio/consumer.h> #include <linux/media-bus-format.h> +#include <linux/mod_devicetable.h> #include <linux/module.h> +#include <linux/regulator/consumer.h> + #include <video/display_timing.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 DRV_NAME "panel-rocktech-jh057n00900" /* Manufacturer specific Commands send via DSI */ @@ -33,6 +36,7 @@ #define ST7703_CMD_SETEXTC 0xB9 #define ST7703_CMD_SETMIPI 0xBA #define ST7703_CMD_SETVDC 0xBC +#define ST7703_CMD_UNKNOWN0 0xBF #define ST7703_CMD_SETSCR 0xC0 #define ST7703_CMD_SETPOWER 0xC1 #define ST7703_CMD_SETPANEL 0xCC @@ -45,7 +49,8 @@ struct jh057n { struct device *dev; struct drm_panel panel; struct gpio_desc *reset_gpio; - struct backlight_device *backlight; + struct regulator *vcc; + struct regulator *iovcc; bool prepared; struct dentry *debugfs; @@ -94,7 +99,7 @@ static int jh057n_init_sequence(struct jh057n *ctx) msleep(20); dsi_generic_write_seq(dsi, ST7703_CMD_SETVCOM, 0x3F, 0x3F); - dsi_generic_write_seq(dsi, 0xBF, 0x02, 0x11, 0x00); + dsi_generic_write_seq(dsi, ST7703_CMD_UNKNOWN0, 0x02, 0x11, 0x00); dsi_generic_write_seq(dsi, ST7703_CMD_SETGIP1, 0x82, 0x10, 0x06, 0x05, 0x9E, 0x0A, 0xA5, 0x12, 0x31, 0x23, 0x37, 0x83, 0x04, 0xBC, 0x27, 0x38, @@ -123,7 +128,7 @@ static int jh057n_init_sequence(struct jh057n *ctx) ret = mipi_dsi_dcs_exit_sleep_mode(dsi); if (ret < 0) { - DRM_DEV_ERROR(dev, "Failed to exit sleep mode\n"); + DRM_DEV_ERROR(dev, "Failed to exit sleep mode: %d\n", ret); return ret; } /* Panel is operational 120 msec after reset */ @@ -139,26 +144,35 @@ static int jh057n_init_sequence(struct jh057n *ctx) static int jh057n_enable(struct drm_panel *panel) { struct jh057n *ctx = panel_to_jh057n(panel); + int ret; - return backlight_enable(ctx->backlight); + ret = jh057n_init_sequence(ctx); + if (ret < 0) { + DRM_DEV_ERROR(ctx->dev, "Panel init sequence failed: %d\n", + ret); + return ret; + } + + return 0; } static int jh057n_disable(struct drm_panel *panel) { struct jh057n *ctx = panel_to_jh057n(panel); + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); - return backlight_disable(ctx->backlight); + return mipi_dsi_dcs_set_display_off(dsi); } static int jh057n_unprepare(struct drm_panel *panel) { struct jh057n *ctx = panel_to_jh057n(panel); - struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); if (!ctx->prepared) return 0; - mipi_dsi_dcs_set_display_off(dsi); + regulator_disable(ctx->iovcc); + regulator_disable(ctx->vcc); ctx->prepared = false; return 0; @@ -173,21 +187,31 @@ static int jh057n_prepare(struct drm_panel *panel) return 0; DRM_DEV_DEBUG_DRIVER(ctx->dev, "Resetting the panel\n"); + ret = regulator_enable(ctx->vcc); + if (ret < 0) { + DRM_DEV_ERROR(ctx->dev, + "Failed to enable vcc 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); usleep_range(20, 40); gpiod_set_value_cansleep(ctx->reset_gpio, 0); msleep(20); - ret = jh057n_init_sequence(ctx); - if (ret < 0) { - DRM_DEV_ERROR(ctx->dev, "Panel init sequence failed: %d\n", - ret); - return ret; - } - ctx->prepared = true; return 0; + +disable_vcc: + regulator_disable(ctx->vcc); + return ret; } static const struct drm_display_mode default_mode = { @@ -206,12 +230,13 @@ static const struct drm_display_mode default_mode = { .height_mm = 130, }; -static int jh057n_get_modes(struct drm_panel *panel) +static int jh057n_get_modes(struct drm_panel *panel, + struct drm_connector *connector) { struct jh057n *ctx = panel_to_jh057n(panel); struct drm_display_mode *mode; - mode = drm_mode_duplicate(panel->drm, &default_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, @@ -222,9 +247,9 @@ static int jh057n_get_modes(struct drm_panel *panel) drm_mode_set_name(mode); mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; - panel->connector->display_info.width_mm = mode->width_mm; - panel->connector->display_info.height_mm = mode->height_mm; - drm_mode_probed_add(panel->connector, mode); + connector->display_info.width_mm = mode->width_mm; + connector->display_info.height_mm = mode->height_mm; + drm_mode_probed_add(connector, mode); return 1; } @@ -296,19 +321,39 @@ static int jh057n_probe(struct mipi_dsi_device *dsi) dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | MIPI_DSI_MODE_VIDEO_SYNC_PULSE; - ctx->backlight = devm_of_find_backlight(dev); - if (IS_ERR(ctx->backlight)) - return PTR_ERR(ctx->backlight); + 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; + } + + drm_panel_init(&ctx->panel, dev, &jh057n_drm_funcs, + DRM_MODE_CONNECTOR_DSI); - drm_panel_init(&ctx->panel); - ctx->panel.dev = dev; - ctx->panel.funcs = &jh057n_drm_funcs; + 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. Is host ready?\n"); + DRM_DEV_ERROR(dev, + "mipi_dsi_attach failed (%d). Is host ready?\n", + ret); drm_panel_remove(&ctx->panel); return ret; } @@ -327,12 +372,12 @@ static void jh057n_shutdown(struct mipi_dsi_device *dsi) struct jh057n *ctx = mipi_dsi_get_drvdata(dsi); int ret; - ret = jh057n_unprepare(&ctx->panel); + ret = drm_panel_unprepare(&ctx->panel); if (ret < 0) DRM_DEV_ERROR(&dsi->dev, "Failed to unprepare panel: %d\n", ret); - ret = jh057n_disable(&ctx->panel); + ret = drm_panel_disable(&ctx->panel); if (ret < 0) DRM_DEV_ERROR(&dsi->dev, "Failed to disable panel: %d\n", ret); diff --git a/drivers/gpu/drm/panel/panel-ronbo-rb070d30.c b/drivers/gpu/drm/panel/panel-ronbo-rb070d30.c index 3c15764f0c03..ef18559e237e 100644 --- a/drivers/gpu/drm/panel/panel-ronbo-rb070d30.c +++ b/drivers/gpu/drm/panel/panel-ronbo-rb070d30.c @@ -7,7 +7,6 @@ * This file based on panel-ilitek-ili9881c.c */ -#include <linux/backlight.h> #include <linux/delay.h> #include <linux/device.h> #include <linux/err.h> @@ -29,7 +28,6 @@ struct rb070d30_panel { struct drm_panel panel; struct mipi_dsi_device *dsi; - struct backlight_device *backlight; struct regulator *supply; struct { @@ -84,22 +82,13 @@ static int rb070d30_panel_enable(struct drm_panel *panel) if (ret) return ret; - ret = backlight_enable(ctx->backlight); - if (ret) - goto out; - return 0; - -out: - mipi_dsi_dcs_enter_sleep_mode(ctx->dsi); - return ret; } static int rb070d30_panel_disable(struct drm_panel *panel) { struct rb070d30_panel *ctx = panel_to_rb070d30_panel(panel); - backlight_disable(ctx->backlight); return mipi_dsi_dcs_enter_sleep_mode(ctx->dsi); } @@ -120,14 +109,14 @@ static const struct drm_display_mode default_mode = { .height_mm = 85, }; -static int rb070d30_panel_get_modes(struct drm_panel *panel) +static int rb070d30_panel_get_modes(struct drm_panel *panel, + struct drm_connector *connector) { - struct drm_connector *connector = panel->connector; struct rb070d30_panel *ctx = panel_to_rb070d30_panel(panel); struct drm_display_mode *mode; static const u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24; - mode = drm_mode_duplicate(panel->drm, &default_mode); + mode = drm_mode_duplicate(connector->dev, &default_mode); if (!mode) { DRM_DEV_ERROR(&ctx->dsi->dev, "Failed to add mode " DRM_MODE_FMT "\n", @@ -140,9 +129,9 @@ static int rb070d30_panel_get_modes(struct drm_panel *panel) mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; drm_mode_probed_add(connector, mode); - panel->connector->display_info.bpc = 8; - panel->connector->display_info.width_mm = mode->width_mm; - panel->connector->display_info.height_mm = mode->height_mm; + connector->display_info.bpc = 8; + connector->display_info.width_mm = mode->width_mm; + connector->display_info.height_mm = mode->height_mm; drm_display_info_set_bus_formats(&connector->display_info, &bus_format, 1); @@ -173,9 +162,8 @@ static int rb070d30_panel_dsi_probe(struct mipi_dsi_device *dsi) mipi_dsi_set_drvdata(dsi, ctx); ctx->dsi = dsi; - drm_panel_init(&ctx->panel); - ctx->panel.dev = &dsi->dev; - ctx->panel.funcs = &rb070d30_panel_funcs; + drm_panel_init(&ctx->panel, &dsi->dev, &rb070d30_panel_funcs, + DRM_MODE_CONNECTOR_DSI); ctx->gpios.reset = devm_gpiod_get(&dsi->dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(ctx->gpios.reset)) { @@ -209,11 +197,9 @@ static int rb070d30_panel_dsi_probe(struct mipi_dsi_device *dsi) return PTR_ERR(ctx->gpios.shlr); } - ctx->backlight = devm_of_find_backlight(&dsi->dev); - if (IS_ERR(ctx->backlight)) { - DRM_DEV_ERROR(&dsi->dev, "Couldn't get our backlight\n"); - return PTR_ERR(ctx->backlight); - } + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return ret; ret = drm_panel_add(&ctx->panel); if (ret < 0) diff --git a/drivers/gpu/drm/panel/panel-samsung-ld9040.c b/drivers/gpu/drm/panel/panel-samsung-ld9040.c index 3be902dcedc0..3c52f15f7a1c 100644 --- a/drivers/gpu/drm/panel/panel-samsung-ld9040.c +++ b/drivers/gpu/drm/panel/panel-samsung-ld9040.c @@ -261,9 +261,9 @@ static int ld9040_enable(struct drm_panel *panel) return 0; } -static int ld9040_get_modes(struct drm_panel *panel) +static int ld9040_get_modes(struct drm_panel *panel, + struct drm_connector *connector) { - struct drm_connector *connector = panel->connector; struct ld9040 *ctx = panel_to_ld9040(panel); struct drm_display_mode *mode; @@ -351,9 +351,8 @@ static int ld9040_probe(struct spi_device *spi) return ret; } - drm_panel_init(&ctx->panel); - ctx->panel.dev = dev; - ctx->panel.funcs = &ld9040_drm_funcs; + drm_panel_init(&ctx->panel, dev, &ld9040_drm_funcs, + DRM_MODE_CONNECTOR_DPI); return drm_panel_add(&ctx->panel); } diff --git a/drivers/gpu/drm/panel/panel-samsung-s6d16d0.c b/drivers/gpu/drm/panel/panel-samsung-s6d16d0.c index f75bef24e050..2150043dcf6b 100644 --- a/drivers/gpu/drm/panel/panel-samsung-s6d16d0.c +++ b/drivers/gpu/drm/panel/panel-samsung-s6d16d0.c @@ -143,12 +143,12 @@ static int s6d16d0_disable(struct drm_panel *panel) return 0; } -static int s6d16d0_get_modes(struct drm_panel *panel) +static int s6d16d0_get_modes(struct drm_panel *panel, + struct drm_connector *connector) { - struct drm_connector *connector = panel->connector; struct drm_display_mode *mode; - mode = drm_mode_duplicate(panel->drm, &samsung_s6d16d0_mode); + mode = drm_mode_duplicate(connector->dev, &samsung_s6d16d0_mode); if (!mode) { DRM_ERROR("bad mode or failed to add mode\n"); return -EINVAL; @@ -215,9 +215,8 @@ static int s6d16d0_probe(struct mipi_dsi_device *dsi) return ret; } - drm_panel_init(&s6->panel); - s6->panel.dev = dev; - s6->panel.funcs = &s6d16d0_drm_funcs; + drm_panel_init(&s6->panel, dev, &s6d16d0_drm_funcs, + DRM_MODE_CONNECTOR_DSI); ret = drm_panel_add(&s6->panel); if (ret < 0) diff --git a/drivers/gpu/drm/panel/panel-samsung-s6e3ha2.c b/drivers/gpu/drm/panel/panel-samsung-s6e3ha2.c index b923de23ed65..36ebd5a4ac7b 100644 --- a/drivers/gpu/drm/panel/panel-samsung-s6e3ha2.c +++ b/drivers/gpu/drm/panel/panel-samsung-s6e3ha2.c @@ -645,13 +645,13 @@ static const struct s6e3ha2_panel_desc samsung_s6e3hf2 = { .type = HF2_TYPE, }; -static int s6e3ha2_get_modes(struct drm_panel *panel) +static int s6e3ha2_get_modes(struct drm_panel *panel, + struct drm_connector *connector) { - struct drm_connector *connector = panel->connector; struct s6e3ha2 *ctx = container_of(panel, struct s6e3ha2, panel); struct drm_display_mode *mode; - mode = drm_mode_duplicate(panel->drm, ctx->desc->mode); + mode = drm_mode_duplicate(connector->dev, ctx->desc->mode); if (!mode) { DRM_ERROR("failed to add mode %ux%ux@%u\n", ctx->desc->mode->hdisplay, ctx->desc->mode->vdisplay, @@ -732,9 +732,8 @@ static int s6e3ha2_probe(struct mipi_dsi_device *dsi) ctx->bl_dev->props.brightness = S6E3HA2_DEFAULT_BRIGHTNESS; ctx->bl_dev->props.power = FB_BLANK_POWERDOWN; - drm_panel_init(&ctx->panel); - ctx->panel.dev = dev; - ctx->panel.funcs = &s6e3ha2_drm_funcs; + drm_panel_init(&ctx->panel, dev, &s6e3ha2_drm_funcs, + DRM_MODE_CONNECTOR_DSI); ret = drm_panel_add(&ctx->panel); if (ret < 0) diff --git a/drivers/gpu/drm/panel/panel-samsung-s6e63j0x03.c b/drivers/gpu/drm/panel/panel-samsung-s6e63j0x03.c index cd90fa700c49..a3570e0a90a8 100644 --- a/drivers/gpu/drm/panel/panel-samsung-s6e63j0x03.c +++ b/drivers/gpu/drm/panel/panel-samsung-s6e63j0x03.c @@ -400,12 +400,12 @@ static int s6e63j0x03_enable(struct drm_panel *panel) return 0; } -static int s6e63j0x03_get_modes(struct drm_panel *panel) +static int s6e63j0x03_get_modes(struct drm_panel *panel, + struct drm_connector *connector) { - struct drm_connector *connector = panel->connector; struct drm_display_mode *mode; - mode = drm_mode_duplicate(panel->drm, &default_mode); + mode = drm_mode_duplicate(connector->dev, &default_mode); if (!mode) { DRM_ERROR("failed to add mode %ux%ux@%u\n", default_mode.hdisplay, default_mode.vdisplay, @@ -466,9 +466,8 @@ static int s6e63j0x03_probe(struct mipi_dsi_device *dsi) return PTR_ERR(ctx->reset_gpio); } - drm_panel_init(&ctx->panel); - ctx->panel.dev = dev; - ctx->panel.funcs = &s6e63j0x03_funcs; + drm_panel_init(&ctx->panel, dev, &s6e63j0x03_funcs, + DRM_MODE_CONNECTOR_DSI); ctx->bl_dev = backlight_device_register("s6e63j0x03", dev, ctx, &s6e63j0x03_bl_ops, NULL); diff --git a/drivers/gpu/drm/panel/panel-samsung-s6e63m0.c b/drivers/gpu/drm/panel/panel-samsung-s6e63m0.c index 142d395ea512..a5f76eb4fa25 100644 --- a/drivers/gpu/drm/panel/panel-samsung-s6e63m0.c +++ b/drivers/gpu/drm/panel/panel-samsung-s6e63m0.c @@ -362,12 +362,12 @@ static int s6e63m0_enable(struct drm_panel *panel) return 0; } -static int s6e63m0_get_modes(struct drm_panel *panel) +static int s6e63m0_get_modes(struct drm_panel *panel, + struct drm_connector *connector) { - struct drm_connector *connector = panel->connector; struct drm_display_mode *mode; - mode = drm_mode_duplicate(panel->drm, &default_mode); + mode = drm_mode_duplicate(connector->dev, &default_mode); if (!mode) { DRM_ERROR("failed to add mode %ux%ux@%u\n", default_mode.hdisplay, default_mode.vdisplay, @@ -473,9 +473,8 @@ static int s6e63m0_probe(struct spi_device *spi) return ret; } - drm_panel_init(&ctx->panel); - ctx->panel.dev = dev; - ctx->panel.funcs = &s6e63m0_drm_funcs; + drm_panel_init(&ctx->panel, dev, &s6e63m0_drm_funcs, + DRM_MODE_CONNECTOR_DPI); ret = s6e63m0_backlight_register(ctx); if (ret < 0) diff --git a/drivers/gpu/drm/panel/panel-samsung-s6e8aa0.c b/drivers/gpu/drm/panel/panel-samsung-s6e8aa0.c index 81858267723a..8a028d2bd0d6 100644 --- a/drivers/gpu/drm/panel/panel-samsung-s6e8aa0.c +++ b/drivers/gpu/drm/panel/panel-samsung-s6e8aa0.c @@ -920,9 +920,9 @@ static int s6e8aa0_enable(struct drm_panel *panel) return 0; } -static int s6e8aa0_get_modes(struct drm_panel *panel) +static int s6e8aa0_get_modes(struct drm_panel *panel, + struct drm_connector *connector) { - struct drm_connector *connector = panel->connector; struct s6e8aa0 *ctx = panel_to_s6e8aa0(panel); struct drm_display_mode *mode; @@ -1017,9 +1017,8 @@ static int s6e8aa0_probe(struct mipi_dsi_device *dsi) ctx->brightness = GAMMA_LEVEL_NUM - 1; - drm_panel_init(&ctx->panel); - ctx->panel.dev = dev; - ctx->panel.funcs = &s6e8aa0_drm_funcs; + drm_panel_init(&ctx->panel, dev, &s6e8aa0_drm_funcs, + DRM_MODE_CONNECTOR_DSI); ret = drm_panel_add(&ctx->panel); if (ret < 0) diff --git a/drivers/gpu/drm/panel/panel-seiko-43wvf1g.c b/drivers/gpu/drm/panel/panel-seiko-43wvf1g.c index 18b22b1294fb..40fcbbbacb2c 100644 --- a/drivers/gpu/drm/panel/panel-seiko-43wvf1g.c +++ b/drivers/gpu/drm/panel/panel-seiko-43wvf1g.c @@ -6,7 +6,6 @@ * Based on Panel Simple driver by Thierry Reding <treding@nvidia.com> */ -#include <linux/backlight.h> #include <linux/delay.h> #include <linux/module.h> #include <linux/of.h> @@ -46,7 +45,6 @@ struct seiko_panel { bool prepared; bool enabled; const struct seiko_panel_desc *desc; - struct backlight_device *backlight; struct regulator *dvdd; struct regulator *avdd; }; @@ -56,10 +54,9 @@ static inline struct seiko_panel *to_seiko_panel(struct drm_panel *panel) return container_of(panel, struct seiko_panel, base); } -static int seiko_panel_get_fixed_modes(struct seiko_panel *panel) +static int seiko_panel_get_fixed_modes(struct seiko_panel *panel, + struct drm_connector *connector) { - struct drm_connector *connector = panel->base.connector; - struct drm_device *drm = panel->base.drm; struct drm_display_mode *mode; unsigned int i, num = 0; @@ -71,9 +68,9 @@ static int seiko_panel_get_fixed_modes(struct seiko_panel *panel) struct videomode vm; videomode_from_timing(dt, &vm); - mode = drm_mode_create(drm); + mode = drm_mode_create(connector->dev); if (!mode) { - dev_err(drm->dev, "failed to add mode %ux%u\n", + dev_err(panel->base.dev, "failed to add mode %ux%u\n", dt->hactive.typ, dt->vactive.typ); continue; } @@ -92,9 +89,9 @@ static int seiko_panel_get_fixed_modes(struct seiko_panel *panel) for (i = 0; i < panel->desc->num_modes; i++) { const struct drm_display_mode *m = &panel->desc->modes[i]; - mode = drm_mode_duplicate(drm, m); + mode = drm_mode_duplicate(connector->dev, m); if (!mode) { - dev_err(drm->dev, "failed to add mode %ux%u@%u\n", + dev_err(panel->base.dev, "failed to add mode %ux%u@%u\n", m->hdisplay, m->vdisplay, m->vrefresh); continue; } @@ -128,12 +125,6 @@ static int seiko_panel_disable(struct drm_panel *panel) if (!p->enabled) return 0; - if (p->backlight) { - p->backlight->props.power = FB_BLANK_POWERDOWN; - p->backlight->props.state |= BL_CORE_FBBLANK; - backlight_update_status(p->backlight); - } - p->enabled = false; return 0; @@ -197,23 +188,18 @@ static int seiko_panel_enable(struct drm_panel *panel) if (p->enabled) return 0; - if (p->backlight) { - p->backlight->props.state &= ~BL_CORE_FBBLANK; - p->backlight->props.power = FB_BLANK_UNBLANK; - backlight_update_status(p->backlight); - } - p->enabled = true; return 0; } -static int seiko_panel_get_modes(struct drm_panel *panel) +static int seiko_panel_get_modes(struct drm_panel *panel, + struct drm_connector *connector) { struct seiko_panel *p = to_seiko_panel(panel); /* add hard-coded panel modes */ - return seiko_panel_get_fixed_modes(p); + return seiko_panel_get_fixed_modes(p, connector); } static int seiko_panel_get_timings(struct drm_panel *panel, @@ -245,7 +231,6 @@ static const struct drm_panel_funcs seiko_panel_funcs = { static int seiko_panel_probe(struct device *dev, const struct seiko_panel_desc *desc) { - struct device_node *backlight; struct seiko_panel *panel; int err; @@ -265,18 +250,12 @@ static int seiko_panel_probe(struct device *dev, if (IS_ERR(panel->avdd)) return PTR_ERR(panel->avdd); - backlight = of_parse_phandle(dev->of_node, "backlight", 0); - if (backlight) { - panel->backlight = of_find_backlight_by_node(backlight); - of_node_put(backlight); + drm_panel_init(&panel->base, dev, &seiko_panel_funcs, + DRM_MODE_CONNECTOR_DPI); - if (!panel->backlight) - return -EPROBE_DEFER; - } - - drm_panel_init(&panel->base); - panel->base.dev = dev; - panel->base.funcs = &seiko_panel_funcs; + err = drm_panel_of_backlight(&panel->base); + if (err) + return err; err = drm_panel_add(&panel->base); if (err < 0) @@ -292,11 +271,7 @@ static int seiko_panel_remove(struct platform_device *pdev) struct seiko_panel *panel = dev_get_drvdata(&pdev->dev); drm_panel_remove(&panel->base); - - seiko_panel_disable(&panel->base); - - if (panel->backlight) - put_device(&panel->backlight->dev); + drm_panel_disable(&panel->base); return 0; } @@ -305,7 +280,7 @@ static void seiko_panel_shutdown(struct platform_device *pdev) { struct seiko_panel *panel = dev_get_drvdata(&pdev->dev); - seiko_panel_disable(&panel->base); + drm_panel_disable(&panel->base); } static const struct display_timing seiko_43wvf1g_timing = { diff --git a/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c b/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c index e910b4ad1310..b5d1977221a7 100644 --- a/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c +++ b/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c @@ -3,7 +3,6 @@ * Copyright (C) 2014 NVIDIA Corporation */ -#include <linux/backlight.h> #include <linux/delay.h> #include <linux/gpio/consumer.h> #include <linux/module.h> @@ -23,7 +22,6 @@ struct sharp_panel { struct mipi_dsi_device *link1; struct mipi_dsi_device *link2; - struct backlight_device *backlight; struct regulator *supply; bool prepared; @@ -94,8 +92,6 @@ static int sharp_panel_disable(struct drm_panel *panel) if (!sharp->enabled) return 0; - backlight_disable(sharp->backlight); - sharp->enabled = false; return 0; @@ -258,8 +254,6 @@ static int sharp_panel_enable(struct drm_panel *panel) if (sharp->enabled) return 0; - backlight_enable(sharp->backlight); - sharp->enabled = true; return 0; @@ -278,13 +272,14 @@ static const struct drm_display_mode default_mode = { .vrefresh = 60, }; -static int sharp_panel_get_modes(struct drm_panel *panel) +static int sharp_panel_get_modes(struct drm_panel *panel, + struct drm_connector *connector) { struct drm_display_mode *mode; - mode = drm_mode_duplicate(panel->drm, &default_mode); + mode = drm_mode_duplicate(connector->dev, &default_mode); if (!mode) { - dev_err(panel->drm->dev, "failed to add mode %ux%ux@%u\n", + dev_err(panel->dev, "failed to add mode %ux%ux@%u\n", default_mode.hdisplay, default_mode.vdisplay, default_mode.vrefresh); return -ENOMEM; @@ -292,10 +287,10 @@ static int sharp_panel_get_modes(struct drm_panel *panel) drm_mode_set_name(mode); - drm_mode_probed_add(panel->connector, mode); + drm_mode_probed_add(connector, mode); - panel->connector->display_info.width_mm = 217; - panel->connector->display_info.height_mm = 136; + connector->display_info.width_mm = 217; + connector->display_info.height_mm = 136; return 1; } @@ -316,7 +311,7 @@ MODULE_DEVICE_TABLE(of, sharp_of_match); static int sharp_panel_add(struct sharp_panel *sharp) { - struct device *dev = &sharp->link1->dev; + int ret; sharp->mode = &default_mode; @@ -324,14 +319,12 @@ static int sharp_panel_add(struct sharp_panel *sharp) if (IS_ERR(sharp->supply)) return PTR_ERR(sharp->supply); - sharp->backlight = devm_of_find_backlight(dev); - - if (IS_ERR(sharp->backlight)) - return PTR_ERR(sharp->backlight); + drm_panel_init(&sharp->base, &sharp->link1->dev, &sharp_panel_funcs, + DRM_MODE_CONNECTOR_DSI); - drm_panel_init(&sharp->base); - sharp->base.funcs = &sharp_panel_funcs; - sharp->base.dev = &sharp->link1->dev; + ret = drm_panel_of_backlight(&sharp->base); + if (ret) + return ret; return drm_panel_add(&sharp->base); } @@ -408,7 +401,7 @@ static int sharp_panel_remove(struct mipi_dsi_device *dsi) return 0; } - err = sharp_panel_disable(&sharp->base); + err = drm_panel_disable(&sharp->base); if (err < 0) dev_err(&dsi->dev, "failed to disable panel: %d\n", err); @@ -429,7 +422,7 @@ static void sharp_panel_shutdown(struct mipi_dsi_device *dsi) if (!sharp) return; - sharp_panel_disable(&sharp->base); + drm_panel_disable(&sharp->base); } static struct mipi_dsi_driver sharp_panel_driver = { diff --git a/drivers/gpu/drm/panel/panel-sharp-ls037v7dw01.c b/drivers/gpu/drm/panel/panel-sharp-ls037v7dw01.c new file mode 100644 index 000000000000..1cf3f02435c1 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-sharp-ls037v7dw01.c @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Sharp LS037V7DW01 LCD Panel Driver + * + * Copyright (C) 2019 Texas Instruments Incorporated + * + * Based on the omapdrm-specific panel-sharp-ls037v7dw01 driver + * + * Copyright (C) 2013 Texas Instruments Incorporated + * Author: Tomi Valkeinen <tomi.valkeinen@ti.com> + */ + +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> + +#include <drm/drm_connector.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +struct ls037v7dw01_panel { + struct drm_panel panel; + struct platform_device *pdev; + + struct regulator *vdd; + struct gpio_desc *resb_gpio; /* low = reset active min 20 us */ + struct gpio_desc *ini_gpio; /* high = power on */ + struct gpio_desc *mo_gpio; /* low = 480x640, high = 240x320 */ + struct gpio_desc *lr_gpio; /* high = conventional horizontal scanning */ + struct gpio_desc *ud_gpio; /* high = conventional vertical scanning */ +}; + +#define to_ls037v7dw01_device(p) \ + container_of(p, struct ls037v7dw01_panel, panel) + +static int ls037v7dw01_disable(struct drm_panel *panel) +{ + struct ls037v7dw01_panel *lcd = to_ls037v7dw01_device(panel); + + gpiod_set_value_cansleep(lcd->ini_gpio, 0); + gpiod_set_value_cansleep(lcd->resb_gpio, 0); + + /* Wait at least 5 vsyncs after disabling the LCD. */ + msleep(100); + + return 0; +} + +static int ls037v7dw01_unprepare(struct drm_panel *panel) +{ + struct ls037v7dw01_panel *lcd = to_ls037v7dw01_device(panel); + + regulator_disable(lcd->vdd); + return 0; +} + +static int ls037v7dw01_prepare(struct drm_panel *panel) +{ + struct ls037v7dw01_panel *lcd = to_ls037v7dw01_device(panel); + int ret; + + ret = regulator_enable(lcd->vdd); + if (ret < 0) + dev_err(&lcd->pdev->dev, "%s: failed to enable regulator\n", + __func__); + + return ret; +} + +static int ls037v7dw01_enable(struct drm_panel *panel) +{ + struct ls037v7dw01_panel *lcd = to_ls037v7dw01_device(panel); + + /* Wait couple of vsyncs before enabling the LCD. */ + msleep(50); + + gpiod_set_value_cansleep(lcd->resb_gpio, 1); + gpiod_set_value_cansleep(lcd->ini_gpio, 1); + + return 0; +} + +static const struct drm_display_mode ls037v7dw01_mode = { + .clock = 19200, + .hdisplay = 480, + .hsync_start = 480 + 1, + .hsync_end = 480 + 1 + 2, + .htotal = 480 + 1 + 2 + 28, + .vdisplay = 640, + .vsync_start = 640 + 1, + .vsync_end = 640 + 1 + 1, + .vtotal = 640 + 1 + 1 + 1, + .vrefresh = 58, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + .width_mm = 56, + .height_mm = 75, +}; + +static int ls037v7dw01_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &ls037v7dw01_mode); + if (!mode) + return -ENOMEM; + + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + + connector->display_info.width_mm = ls037v7dw01_mode.width_mm; + connector->display_info.height_mm = ls037v7dw01_mode.height_mm; + /* + * FIXME: According to the datasheet pixel data is sampled on the + * rising edge of the clock, but the code running on the SDP3430 + * indicates sampling on the negative edge. This should be tested on a + * real device. + */ + connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH + | DRM_BUS_FLAG_SYNC_SAMPLE_POSEDGE + | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE; + + return 1; +} + +static const struct drm_panel_funcs ls037v7dw01_funcs = { + .disable = ls037v7dw01_disable, + .unprepare = ls037v7dw01_unprepare, + .prepare = ls037v7dw01_prepare, + .enable = ls037v7dw01_enable, + .get_modes = ls037v7dw01_get_modes, +}; + +static int ls037v7dw01_probe(struct platform_device *pdev) +{ + struct ls037v7dw01_panel *lcd; + + lcd = devm_kzalloc(&pdev->dev, sizeof(*lcd), GFP_KERNEL); + if (!lcd) + return -ENOMEM; + + platform_set_drvdata(pdev, lcd); + lcd->pdev = pdev; + + lcd->vdd = devm_regulator_get(&pdev->dev, "envdd"); + if (IS_ERR(lcd->vdd)) { + dev_err(&pdev->dev, "failed to get regulator\n"); + return PTR_ERR(lcd->vdd); + } + + lcd->ini_gpio = devm_gpiod_get(&pdev->dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(lcd->ini_gpio)) { + dev_err(&pdev->dev, "failed to get enable gpio\n"); + return PTR_ERR(lcd->ini_gpio); + } + + lcd->resb_gpio = devm_gpiod_get(&pdev->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(lcd->resb_gpio)) { + dev_err(&pdev->dev, "failed to get reset gpio\n"); + return PTR_ERR(lcd->resb_gpio); + } + + lcd->mo_gpio = devm_gpiod_get_index(&pdev->dev, "mode", 0, + GPIOD_OUT_LOW); + if (IS_ERR(lcd->mo_gpio)) { + dev_err(&pdev->dev, "failed to get mode[0] gpio\n"); + return PTR_ERR(lcd->mo_gpio); + } + + lcd->lr_gpio = devm_gpiod_get_index(&pdev->dev, "mode", 1, + GPIOD_OUT_LOW); + if (IS_ERR(lcd->lr_gpio)) { + dev_err(&pdev->dev, "failed to get mode[1] gpio\n"); + return PTR_ERR(lcd->lr_gpio); + } + + lcd->ud_gpio = devm_gpiod_get_index(&pdev->dev, "mode", 2, + GPIOD_OUT_LOW); + if (IS_ERR(lcd->ud_gpio)) { + dev_err(&pdev->dev, "failed to get mode[2] gpio\n"); + return PTR_ERR(lcd->ud_gpio); + } + + drm_panel_init(&lcd->panel, &pdev->dev, &ls037v7dw01_funcs, + DRM_MODE_CONNECTOR_DPI); + + return drm_panel_add(&lcd->panel); +} + +static int ls037v7dw01_remove(struct platform_device *pdev) +{ + struct ls037v7dw01_panel *lcd = platform_get_drvdata(pdev); + + drm_panel_remove(&lcd->panel); + drm_panel_disable(&lcd->panel); + drm_panel_unprepare(&lcd->panel); + + return 0; +} + +static const struct of_device_id ls037v7dw01_of_match[] = { + { .compatible = "sharp,ls037v7dw01", }, + { /* sentinel */ }, +}; + +MODULE_DEVICE_TABLE(of, ls037v7dw01_of_match); + +static struct platform_driver ls037v7dw01_driver = { + .probe = ls037v7dw01_probe, + .remove = ls037v7dw01_remove, + .driver = { + .name = "panel-sharp-ls037v7dw01", + .of_match_table = ls037v7dw01_of_match, + }, +}; + +module_platform_driver(ls037v7dw01_driver); + +MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>"); +MODULE_DESCRIPTION("Sharp LS037V7DW01 Panel Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-sharp-ls043t1le01.c b/drivers/gpu/drm/panel/panel-sharp-ls043t1le01.c index c39abde9f9f1..ce586c6d70c7 100644 --- a/drivers/gpu/drm/panel/panel-sharp-ls043t1le01.c +++ b/drivers/gpu/drm/panel/panel-sharp-ls043t1le01.c @@ -7,7 +7,6 @@ * Based on AUO panel driver by Rob Clark <robdclark@gmail.com> */ -#include <linux/backlight.h> #include <linux/delay.h> #include <linux/gpio/consumer.h> #include <linux/module.h> @@ -25,7 +24,6 @@ struct sharp_nt_panel { struct drm_panel base; struct mipi_dsi_device *dsi; - struct backlight_device *backlight; struct regulator *supply; struct gpio_desc *reset_gpio; @@ -107,8 +105,6 @@ static int sharp_nt_panel_disable(struct drm_panel *panel) if (!sharp_nt->enabled) return 0; - backlight_disable(sharp_nt->backlight); - sharp_nt->enabled = false; return 0; @@ -190,8 +186,6 @@ static int sharp_nt_panel_enable(struct drm_panel *panel) if (sharp_nt->enabled) return 0; - backlight_enable(sharp_nt->backlight); - sharp_nt->enabled = true; return 0; @@ -210,24 +204,25 @@ static const struct drm_display_mode default_mode = { .vrefresh = 60, }; -static int sharp_nt_panel_get_modes(struct drm_panel *panel) +static int sharp_nt_panel_get_modes(struct drm_panel *panel, + struct drm_connector *connector) { struct drm_display_mode *mode; - mode = drm_mode_duplicate(panel->drm, &default_mode); + mode = drm_mode_duplicate(connector->dev, &default_mode); if (!mode) { - dev_err(panel->drm->dev, "failed to add mode %ux%ux@%u\n", - default_mode.hdisplay, default_mode.vdisplay, - default_mode.vrefresh); + dev_err(panel->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); - drm_mode_probed_add(panel->connector, mode); + drm_mode_probed_add(connector, mode); - panel->connector->display_info.width_mm = 54; - panel->connector->display_info.height_mm = 95; + connector->display_info.width_mm = 54; + connector->display_info.height_mm = 95; return 1; } @@ -243,6 +238,7 @@ static const struct drm_panel_funcs sharp_nt_panel_funcs = { static int sharp_nt_panel_add(struct sharp_nt_panel *sharp_nt) { struct device *dev = &sharp_nt->dsi->dev; + int ret; sharp_nt->mode = &default_mode; @@ -259,14 +255,12 @@ static int sharp_nt_panel_add(struct sharp_nt_panel *sharp_nt) gpiod_set_value(sharp_nt->reset_gpio, 0); } - sharp_nt->backlight = devm_of_find_backlight(dev); + drm_panel_init(&sharp_nt->base, &sharp_nt->dsi->dev, + &sharp_nt_panel_funcs, DRM_MODE_CONNECTOR_DSI); - if (IS_ERR(sharp_nt->backlight)) - return PTR_ERR(sharp_nt->backlight); - - drm_panel_init(&sharp_nt->base); - sharp_nt->base.funcs = &sharp_nt_panel_funcs; - sharp_nt->base.dev = &sharp_nt->dsi->dev; + ret = drm_panel_of_backlight(&sharp_nt->base); + if (ret) + return ret; return drm_panel_add(&sharp_nt->base); } @@ -309,7 +303,7 @@ static int sharp_nt_panel_remove(struct mipi_dsi_device *dsi) struct sharp_nt_panel *sharp_nt = mipi_dsi_get_drvdata(dsi); int ret; - ret = sharp_nt_panel_disable(&sharp_nt->base); + ret = drm_panel_disable(&sharp_nt->base); if (ret < 0) dev_err(&dsi->dev, "failed to disable panel: %d\n", ret); @@ -326,7 +320,7 @@ static void sharp_nt_panel_shutdown(struct mipi_dsi_device *dsi) { struct sharp_nt_panel *sharp_nt = mipi_dsi_get_drvdata(dsi); - sharp_nt_panel_disable(&sharp_nt->base); + drm_panel_disable(&sharp_nt->base); } static const struct of_device_id sharp_nt_of_match[] = { diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index 5a93c4edf1e4..e14c14ac62b5 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -21,7 +21,6 @@ * DEALINGS IN THE SOFTWARE. */ -#include <linux/backlight.h> #include <linux/delay.h> #include <linux/gpio/consumer.h> #include <linux/module.h> @@ -30,6 +29,7 @@ #include <linux/regulator/consumer.h> #include <video/display_timing.h> +#include <video/of_display_timing.h> #include <video/videomode.h> #include <drm/drm_crtc.h> @@ -37,6 +37,22 @@ #include <drm/drm_mipi_dsi.h> #include <drm/drm_panel.h> +/** + * @modes: Pointer to array of fixed modes appropriate for this panel. If + * only one mode then this can just be the address of this the mode. + * NOTE: cannot be used with "timings" and also if this is specified + * then you cannot override the mode in the device tree. + * @num_modes: Number of elements in modes array. + * @timings: Pointer to array of display timings. NOTE: cannot be used with + * "modes" and also these will be used to validate a device tree + * override if one is present. + * @num_timings: Number of elements in timings array. + * @bpc: Bits per color. + * @size: Structure containing the physical size of this panel. + * @delay: Structure containing various delay values for this panel. + * @bus_format: See MEDIA_BUS_FMT_... defines. + * @bus_flags: See DRM_BUS_FLAG_... defines. + */ struct panel_desc { const struct drm_display_mode *modes; unsigned int num_modes; @@ -77,6 +93,7 @@ struct panel_desc { u32 bus_format; u32 bus_flags; + int connector_type; }; struct panel_simple { @@ -87,11 +104,12 @@ struct panel_simple { const struct panel_desc *desc; - struct backlight_device *backlight; struct regulator *supply; struct i2c_adapter *ddc; struct gpio_desc *enable_gpio; + + struct drm_display_mode override_mode; }; static inline struct panel_simple *to_panel_simple(struct drm_panel *panel) @@ -99,24 +117,20 @@ static inline struct panel_simple *to_panel_simple(struct drm_panel *panel) return container_of(panel, struct panel_simple, base); } -static int panel_simple_get_fixed_modes(struct panel_simple *panel) +static unsigned int panel_simple_get_timings_modes(struct panel_simple *panel, + struct drm_connector *connector) { - struct drm_connector *connector = panel->base.connector; - struct drm_device *drm = panel->base.drm; struct drm_display_mode *mode; unsigned int i, num = 0; - if (!panel->desc) - return 0; - for (i = 0; i < panel->desc->num_timings; i++) { const struct display_timing *dt = &panel->desc->timings[i]; struct videomode vm; videomode_from_timing(dt, &vm); - mode = drm_mode_create(drm); + mode = drm_mode_create(connector->dev); if (!mode) { - dev_err(drm->dev, "failed to add mode %ux%u\n", + dev_err(panel->base.dev, "failed to add mode %ux%u\n", dt->hactive.typ, dt->vactive.typ); continue; } @@ -132,12 +146,21 @@ static int panel_simple_get_fixed_modes(struct panel_simple *panel) num++; } + return num; +} + +static unsigned int panel_simple_get_display_modes(struct panel_simple *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + unsigned int i, num = 0; + for (i = 0; i < panel->desc->num_modes; i++) { const struct drm_display_mode *m = &panel->desc->modes[i]; - mode = drm_mode_duplicate(drm, m); + mode = drm_mode_duplicate(connector->dev, m); if (!mode) { - dev_err(drm->dev, "failed to add mode %ux%u@%u\n", + dev_err(panel->base.dev, "failed to add mode %ux%u@%u\n", m->hdisplay, m->vdisplay, m->vrefresh); continue; } @@ -153,6 +176,44 @@ static int panel_simple_get_fixed_modes(struct panel_simple *panel) num++; } + return num; +} + +static int panel_simple_get_non_edid_modes(struct panel_simple *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + bool has_override = panel->override_mode.type; + unsigned int num = 0; + + if (!panel->desc) + return 0; + + if (has_override) { + mode = drm_mode_duplicate(connector->dev, + &panel->override_mode); + if (mode) { + drm_mode_probed_add(connector, mode); + num = 1; + } else { + dev_err(panel->base.dev, "failed to add override mode\n"); + } + } + + /* Only add timings if override was not there or failed to validate */ + if (num == 0 && panel->desc->num_timings) + num = panel_simple_get_timings_modes(panel, connector); + + /* + * Only add fixed modes if timings/override added no mode. + * + * We should only ever have either the display timings specified + * or a fixed mode. Anything else is rather bogus. + */ + WARN_ON(panel->desc->num_timings && panel->desc->num_modes); + if (num == 0) + num = panel_simple_get_display_modes(panel, connector); + connector->display_info.bpc = panel->desc->bpc; connector->display_info.width_mm = panel->desc->size.width; connector->display_info.height_mm = panel->desc->size.height; @@ -171,12 +232,6 @@ static int panel_simple_disable(struct drm_panel *panel) if (!p->enabled) return 0; - if (p->backlight) { - p->backlight->props.power = FB_BLANK_POWERDOWN; - p->backlight->props.state |= BL_CORE_FBBLANK; - backlight_update_status(p->backlight); - } - if (p->desc->delay.disable) msleep(p->desc->delay.disable); @@ -242,34 +297,30 @@ static int panel_simple_enable(struct drm_panel *panel) if (p->desc->delay.enable) msleep(p->desc->delay.enable); - if (p->backlight) { - p->backlight->props.state &= ~BL_CORE_FBBLANK; - p->backlight->props.power = FB_BLANK_UNBLANK; - backlight_update_status(p->backlight); - } - p->enabled = true; return 0; } -static int panel_simple_get_modes(struct drm_panel *panel) +static int panel_simple_get_modes(struct drm_panel *panel, + struct drm_connector *connector) { struct panel_simple *p = to_panel_simple(panel); int num = 0; /* probe EDID if a DDC bus is available */ if (p->ddc) { - struct edid *edid = drm_get_edid(panel->connector, p->ddc); - drm_connector_update_edid_property(panel->connector, edid); + struct edid *edid = drm_get_edid(connector, p->ddc); + + drm_connector_update_edid_property(connector, edid); if (edid) { - num += drm_add_edid_modes(panel->connector, edid); + num += drm_add_edid_modes(connector, edid); kfree(edid); } } /* add hard-coded panel modes */ - num += panel_simple_get_fixed_modes(p); + num += panel_simple_get_non_edid_modes(p, connector); return num; } @@ -300,10 +351,58 @@ static const struct drm_panel_funcs panel_simple_funcs = { .get_timings = panel_simple_get_timings, }; +#define PANEL_SIMPLE_BOUNDS_CHECK(to_check, bounds, field) \ + (to_check->field.typ >= bounds->field.min && \ + to_check->field.typ <= bounds->field.max) +static void panel_simple_parse_panel_timing_node(struct device *dev, + struct panel_simple *panel, + const struct display_timing *ot) +{ + const struct panel_desc *desc = panel->desc; + struct videomode vm; + unsigned int i; + + if (WARN_ON(desc->num_modes)) { + dev_err(dev, "Reject override mode: panel has a fixed mode\n"); + return; + } + if (WARN_ON(!desc->num_timings)) { + dev_err(dev, "Reject override mode: no timings specified\n"); + return; + } + + for (i = 0; i < panel->desc->num_timings; i++) { + const struct display_timing *dt = &panel->desc->timings[i]; + + if (!PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, hactive) || + !PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, hfront_porch) || + !PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, hback_porch) || + !PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, hsync_len) || + !PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, vactive) || + !PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, vfront_porch) || + !PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, vback_porch) || + !PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, vsync_len)) + continue; + + if (ot->flags != dt->flags) + continue; + + videomode_from_timing(ot, &vm); + drm_display_mode_from_videomode(&vm, &panel->override_mode); + panel->override_mode.type |= DRM_MODE_TYPE_DRIVER | + DRM_MODE_TYPE_PREFERRED; + break; + } + + if (WARN_ON(!panel->override_mode.type)) + dev_err(dev, "Reject override mode: No display_timing found\n"); +} + static int panel_simple_probe(struct device *dev, const struct panel_desc *desc) { - struct device_node *backlight, *ddc; struct panel_simple *panel; + struct display_timing dt; + struct device_node *ddc; int err; panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL); @@ -329,29 +428,24 @@ static int panel_simple_probe(struct device *dev, const struct panel_desc *desc) return err; } - backlight = of_parse_phandle(dev->of_node, "backlight", 0); - if (backlight) { - panel->backlight = of_find_backlight_by_node(backlight); - of_node_put(backlight); - - if (!panel->backlight) - return -EPROBE_DEFER; - } - ddc = of_parse_phandle(dev->of_node, "ddc-i2c-bus", 0); if (ddc) { panel->ddc = of_find_i2c_adapter_by_node(ddc); of_node_put(ddc); - if (!panel->ddc) { - err = -EPROBE_DEFER; - goto free_backlight; - } + if (!panel->ddc) + return -EPROBE_DEFER; } - drm_panel_init(&panel->base); - panel->base.dev = dev; - panel->base.funcs = &panel_simple_funcs; + if (!of_get_display_timing(dev->of_node, "panel-timing", &dt)) + panel_simple_parse_panel_timing_node(dev, panel, &dt); + + drm_panel_init(&panel->base, dev, &panel_simple_funcs, + desc->connector_type); + + err = drm_panel_of_backlight(&panel->base); + if (err) + goto free_ddc; err = drm_panel_add(&panel->base); if (err < 0) @@ -364,9 +458,6 @@ static int panel_simple_probe(struct device *dev, const struct panel_desc *desc) free_ddc: if (panel->ddc) put_device(&panel->ddc->dev); -free_backlight: - if (panel->backlight) - put_device(&panel->backlight->dev); return err; } @@ -376,16 +467,12 @@ static int panel_simple_remove(struct device *dev) struct panel_simple *panel = dev_get_drvdata(dev); drm_panel_remove(&panel->base); - - panel_simple_disable(&panel->base); - panel_simple_unprepare(&panel->base); + drm_panel_disable(&panel->base); + drm_panel_unprepare(&panel->base); if (panel->ddc) put_device(&panel->ddc->dev); - if (panel->backlight) - put_device(&panel->backlight->dev); - return 0; } @@ -393,8 +480,8 @@ static void panel_simple_shutdown(struct device *dev) { struct panel_simple *panel = dev_get_drvdata(dev); - panel_simple_disable(&panel->base); - panel_simple_unprepare(&panel->base); + drm_panel_disable(&panel->base); + drm_panel_unprepare(&panel->base); } static const struct drm_display_mode ampire_am_480272h3tmqw_t01h_mode = { @@ -496,22 +583,21 @@ static const struct panel_desc auo_b101aw03 = { }, }; -static const struct drm_display_mode auo_b101ean01_mode = { - .clock = 72500, - .hdisplay = 1280, - .hsync_start = 1280 + 119, - .hsync_end = 1280 + 119 + 32, - .htotal = 1280 + 119 + 32 + 21, - .vdisplay = 800, - .vsync_start = 800 + 4, - .vsync_end = 800 + 4 + 20, - .vtotal = 800 + 4 + 20 + 8, - .vrefresh = 60, +static const struct display_timing auo_b101ean01_timing = { + .pixelclock = { 65300000, 72500000, 75000000 }, + .hactive = { 1280, 1280, 1280 }, + .hfront_porch = { 18, 119, 119 }, + .hback_porch = { 21, 21, 21 }, + .hsync_len = { 32, 32, 32 }, + .vactive = { 800, 800, 800 }, + .vfront_porch = { 4, 4, 4 }, + .vback_porch = { 8, 8, 8 }, + .vsync_len = { 18, 20, 20 }, }; static const struct panel_desc auo_b101ean01 = { - .modes = &auo_b101ean01_mode, - .num_modes = 1, + .timings = &auo_b101ean01_timing, + .num_timings = 1, .bpc = 6, .size = { .width = 217, @@ -543,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, @@ -719,14 +834,15 @@ static const struct panel_desc auo_g133han01 = { .unprepare = 1000, }, .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct display_timing auo_g185han01_timings = { .pixelclock = { 120000000, 144000000, 175000000 }, .hactive = { 1920, 1920, 1920 }, - .hfront_porch = { 18, 60, 74 }, - .hback_porch = { 12, 44, 54 }, - .hsync_len = { 10, 24, 32 }, + .hfront_porch = { 36, 120, 148 }, + .hback_porch = { 24, 88, 108 }, + .hsync_len = { 20, 48, 64 }, .vactive = { 1080, 1080, 1080 }, .vfront_porch = { 6, 10, 40 }, .vback_porch = { 2, 5, 20 }, @@ -748,6 +864,7 @@ static const struct panel_desc auo_g185han01 = { .unprepare = 1000, }, .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct display_timing auo_p320hvn03_timings = { @@ -776,6 +893,7 @@ static const struct panel_desc auo_p320hvn03 = { .unprepare = 500, }, .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct drm_display_mode auo_t215hvn01_mode = { @@ -919,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, @@ -1091,6 +1241,7 @@ static const struct panel_desc dlc_dlc0700yzg_1 = { .disable = 200, }, .bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct display_timing dlc_dlc1010gig_timing = { @@ -1121,6 +1272,7 @@ static const struct panel_desc dlc_dlc1010gig = { .unprepare = 60, }, .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct drm_display_mode edt_et035012dm6_mode = { @@ -1335,6 +1487,31 @@ static const struct panel_desc giantplus_gpg482739qs5 = { .bus_format = MEDIA_BUS_FMT_RGB888_1X24, }; +static const struct display_timing giantplus_gpm940b0_timing = { + .pixelclock = { 13500000, 27000000, 27500000 }, + .hactive = { 320, 320, 320 }, + .hfront_porch = { 14, 686, 718 }, + .hback_porch = { 50, 70, 255 }, + .hsync_len = { 1, 1, 1 }, + .vactive = { 240, 240, 240 }, + .vfront_porch = { 1, 1, 179 }, + .vback_porch = { 1, 21, 31 }, + .vsync_len = { 1, 1, 6 }, + .flags = DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW, +}; + +static const struct panel_desc giantplus_gpm940b0 = { + .timings = &giantplus_gpm940b0_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 60, + .height = 45, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_3X8, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_NEGEDGE, +}; + static const struct display_timing hannstar_hsd070pww1_timing = { .pixelclock = { 64300000, 71100000, 82000000 }, .hactive = { 1280, 1280, 1280 }, @@ -1362,6 +1539,7 @@ static const struct panel_desc hannstar_hsd070pww1 = { .height = 94, }, .bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct display_timing hannstar_hsd100pxn1_timing = { @@ -1386,6 +1564,7 @@ static const struct panel_desc hannstar_hsd100pxn1 = { .height = 152, }, .bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct drm_display_mode hitachi_tx23d38vm0caa_mode = { @@ -1492,6 +1671,7 @@ static const struct panel_desc innolux_g070y2_l01 = { .unprepare = 800, }, .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct display_timing innolux_g101ice_l01_timing = { @@ -1520,6 +1700,7 @@ static const struct panel_desc innolux_g101ice_l01 = { .disable = 200, }, .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct display_timing innolux_g121i1_l01_timing = { @@ -1547,6 +1728,7 @@ static const struct panel_desc innolux_g121i1_l01 = { .disable = 20, }, .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct drm_display_mode innolux_g121x1_l03_mode = { @@ -1578,23 +1760,32 @@ static const struct panel_desc innolux_g121x1_l03 = { }, }; -static const struct drm_display_mode innolux_n116bge_mode = { - .clock = 76420, - .hdisplay = 1366, - .hsync_start = 1366 + 136, - .hsync_end = 1366 + 136 + 30, - .htotal = 1366 + 136 + 30 + 60, - .vdisplay = 768, - .vsync_start = 768 + 8, - .vsync_end = 768 + 8 + 12, - .vtotal = 768 + 8 + 12 + 12, - .vrefresh = 60, - .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, +/* + * Datasheet specifies that at 60 Hz refresh rate: + * - total horizontal time: { 1506, 1592, 1716 } + * - total vertical time: { 788, 800, 868 } + * + * ...but doesn't go into exactly how that should be split into a front + * porch, back porch, or sync length. For now we'll leave a single setting + * here which allows a bit of tweaking of the pixel clock at the expense of + * refresh rate. + */ +static const struct display_timing innolux_n116bge_timing = { + .pixelclock = { 72600000, 76420000, 80240000 }, + .hactive = { 1366, 1366, 1366 }, + .hfront_porch = { 136, 136, 136 }, + .hback_porch = { 60, 60, 60 }, + .hsync_len = { 30, 30, 30 }, + .vactive = { 768, 768, 768 }, + .vfront_porch = { 8, 8, 8 }, + .vback_porch = { 12, 12, 12 }, + .vsync_len = { 12, 12, 12 }, + .flags = DISPLAY_FLAGS_VSYNC_LOW | DISPLAY_FLAGS_HSYNC_LOW, }; static const struct panel_desc innolux_n116bge = { - .modes = &innolux_n116bge_mode, - .num_modes = 1, + .timings = &innolux_n116bge_timing, + .num_timings = 1, .bpc = 6, .size = { .width = 256, @@ -1721,6 +1912,7 @@ static const struct panel_desc koe_tx31d200vm0baa = { .height = 109, }, .bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct display_timing kyo_tcg121xglp_timing = { @@ -1745,6 +1937,7 @@ static const struct panel_desc kyo_tcg121xglp = { .height = 184, }, .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct drm_display_mode lemaker_bl035_rgb_002_mode = { @@ -1793,6 +1986,7 @@ static const struct panel_desc lg_lb070wv8 = { .height = 91, }, .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct drm_display_mode lg_lp079qx1_sp0v_mode = { @@ -1900,6 +2094,40 @@ static const struct drm_display_mode mitsubishi_aa070mc01_mode = { .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, }; +static const struct drm_display_mode logicpd_type_28_mode = { + .clock = 9000, + .hdisplay = 480, + .hsync_start = 480 + 3, + .hsync_end = 480 + 3 + 42, + .htotal = 480 + 3 + 42 + 2, + + .vdisplay = 272, + .vsync_start = 272 + 2, + .vsync_end = 272 + 2 + 11, + .vtotal = 272 + 2 + 11 + 3, + .vrefresh = 60, + .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC, +}; + +static const struct panel_desc logicpd_type_28 = { + .modes = &logicpd_type_28_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 105, + .height = 67, + }, + .delay = { + .prepare = 200, + .enable = 200, + .unprepare = 200, + .disable = 200, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE | + DRM_BUS_FLAG_SYNC_DRIVE_NEGEDGE, +}; + static const struct panel_desc mitsubishi_aa070mc01 = { .modes = &mitsubishi_aa070mc01_mode, .num_modes = 1, @@ -1915,6 +2143,7 @@ static const struct panel_desc mitsubishi_aa070mc01 = { .disable = 400, }, .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, .bus_flags = DRM_BUS_FLAG_DE_HIGH, }; @@ -1943,6 +2172,7 @@ static const struct panel_desc nec_nl12880bc20_05 = { .disable = 50, }, .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct drm_display_mode nec_nl4827hc19_05b_mode = { @@ -2045,6 +2275,7 @@ static const struct panel_desc nlt_nl192108ac18_02d = { .unprepare = 500, }, .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct drm_display_mode nvd_9128_mode = { @@ -2068,6 +2299,7 @@ static const struct panel_desc nvd_9128 = { .height = 88, }, .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct display_timing okaya_rs800480t_7x0gp_timing = { @@ -2157,6 +2389,33 @@ static const struct panel_desc ontat_yx700wv03 = { .bus_format = MEDIA_BUS_FMT_RGB666_1X18, }; +static const struct drm_display_mode ortustech_com37h3m_mode = { + .clock = 22153, + .hdisplay = 480, + .hsync_start = 480 + 8, + .hsync_end = 480 + 8 + 10, + .htotal = 480 + 8 + 10 + 10, + .vdisplay = 640, + .vsync_start = 640 + 4, + .vsync_end = 640 + 4 + 3, + .vtotal = 640 + 4 + 3 + 4, + .vrefresh = 60, + .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, +}; + +static const struct panel_desc ortustech_com37h3m = { + .modes = &ortustech_com37h3m_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 56, /* 56.16mm */ + .height = 75, /* 74.88mm */ + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_POSEDGE | + DRM_BUS_FLAG_SYNC_DRIVE_POSEDGE, +}; + static const struct drm_display_mode ortustech_com43h4m85ulc_mode = { .clock = 25000, .hdisplay = 480, @@ -2206,6 +2465,7 @@ static const struct panel_desc osddisplays_osd070t1718_19ts = { }, .bus_format = MEDIA_BUS_FMT_RGB888_1X24, .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE, + .connector_type = DRM_MODE_CONNECTOR_DPI, }; static const struct drm_display_mode pda_91_00156_a0_mode = { @@ -2354,6 +2614,83 @@ 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, + .hsync_start = 1920 + 48, + .hsync_end = 1920 + 48 + 32, + .htotal = 1920 + 48 + 32 + 80, + .vdisplay = 1280, + .vsync_start = 1280 + 3, + .vsync_end = 1280 + 3 + 10, + .vtotal = 1280 + 3 + 10 + 57, + .vrefresh = 60, + .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC, +}; + +static const struct panel_desc sharp_ld_d5116z01b = { + .modes = &sharp_ld_d5116z01b_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 260, + .height = 120, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_DATA_MSB_TO_LSB, +}; + +static const struct drm_display_mode sharp_lq070y3dg3b_mode = { + .clock = 33260, + .hdisplay = 800, + .hsync_start = 800 + 64, + .hsync_end = 800 + 64 + 128, + .htotal = 800 + 64 + 128 + 64, + .vdisplay = 480, + .vsync_start = 480 + 8, + .vsync_end = 480 + 8 + 2, + .vtotal = 480 + 8 + 2 + 35, + .vrefresh = 60, + .flags = DISPLAY_FLAGS_PIXDATA_POSEDGE, +}; + +static const struct panel_desc sharp_lq070y3dg3b = { + .modes = &sharp_lq070y3dg3b_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 152, /* 152.4mm */ + .height = 91, /* 91.4mm */ + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_POSEDGE | + DRM_BUS_FLAG_SYNC_DRIVE_POSEDGE, +}; + static const struct drm_display_mode sharp_lq035q7db03_mode = { .clock = 5500, .hdisplay = 240, @@ -2400,6 +2737,7 @@ static const struct panel_desc sharp_lq101k1ly04 = { .height = 136, }, .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct display_timing sharp_lq123p1jx31_timing = { @@ -2454,6 +2792,33 @@ static const struct panel_desc sharp_lq150x1lg11 = { .bus_format = MEDIA_BUS_FMT_RGB565_1X16, }; +static const struct display_timing sharp_ls020b1dd01d_timing = { + .pixelclock = { 2000000, 4200000, 5000000 }, + .hactive = { 240, 240, 240 }, + .hfront_porch = { 66, 66, 66 }, + .hback_porch = { 1, 1, 1 }, + .hsync_len = { 1, 1, 1 }, + .vactive = { 160, 160, 160 }, + .vfront_porch = { 52, 52, 52 }, + .vback_porch = { 6, 6, 6 }, + .vsync_len = { 10, 10, 10 }, + .flags = DISPLAY_FLAGS_HSYNC_HIGH | DISPLAY_FLAGS_VSYNC_LOW, +}; + +static const struct panel_desc sharp_ls020b1dd01d = { + .timings = &sharp_ls020b1dd01d_timing, + .num_timings = 1, + .bpc = 6, + .size = { + .width = 42, + .height = 28, + }, + .bus_format = MEDIA_BUS_FMT_RGB565_1X16, + .bus_flags = DRM_BUS_FLAG_DE_HIGH + | DRM_BUS_FLAG_PIXDATA_NEGEDGE + | DRM_BUS_FLAG_SHARP_SIGNALS, +}; + static const struct drm_display_mode shelly_sca07010_bfn_lnn_mode = { .clock = 33300, .hdisplay = 800, @@ -2552,6 +2917,7 @@ static const struct panel_desc tianma_tm070jdhg30 = { .height = 95, }, .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct display_timing tianma_tm070rvhg71_timing = { @@ -2576,6 +2942,65 @@ static const struct panel_desc tianma_tm070rvhg71 = { .height = 86, }, .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + +static const struct drm_display_mode ti_nspire_cx_lcd_mode[] = { + { + .clock = 10000, + .hdisplay = 320, + .hsync_start = 320 + 50, + .hsync_end = 320 + 50 + 6, + .htotal = 320 + 50 + 6 + 38, + .vdisplay = 240, + .vsync_start = 240 + 3, + .vsync_end = 240 + 3 + 1, + .vtotal = 240 + 3 + 1 + 17, + .vrefresh = 60, + .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, + }, +}; + +static const struct panel_desc ti_nspire_cx_lcd_panel = { + .modes = ti_nspire_cx_lcd_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 65, + .height = 49, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_PIXDATA_NEGEDGE, +}; + +static const struct drm_display_mode ti_nspire_classic_lcd_mode[] = { + { + .clock = 10000, + .hdisplay = 320, + .hsync_start = 320 + 6, + .hsync_end = 320 + 6 + 6, + .htotal = 320 + 6 + 6 + 6, + .vdisplay = 240, + .vsync_start = 240 + 0, + .vsync_end = 240 + 0 + 1, + .vtotal = 240 + 0 + 1 + 0, + .vrefresh = 60, + .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC, + }, +}; + +static const struct panel_desc ti_nspire_classic_lcd_panel = { + .modes = ti_nspire_classic_lcd_mode, + .num_modes = 1, + /* The grayscale panel has 8 bit for the color .. Y (black) */ + .bpc = 8, + .size = { + .width = 71, + .height = 53, + }, + /* This is the grayscale bus format */ + .bus_format = MEDIA_BUS_FMT_Y8_1X8, + .bus_flags = DRM_BUS_FLAG_PIXDATA_POSEDGE, }; static const struct drm_display_mode toshiba_lt089ac29000_mode = { @@ -2600,6 +3025,7 @@ static const struct panel_desc toshiba_lt089ac29000 = { }, .bus_format = MEDIA_BUS_FMT_RGB888_1X24, .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct drm_display_mode tpk_f07a_0102_mode = { @@ -2670,6 +3096,7 @@ static const struct panel_desc urt_umsh_8596md_lvds = { .height = 91, }, .bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct panel_desc urt_umsh_8596md_parallel = { @@ -2784,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, }, { @@ -2826,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, }, { @@ -2883,6 +3316,9 @@ static const struct of_device_id platform_of_match[] = { .compatible = "giantplus,gpg482739qs5", .data = &giantplus_gpg482739qs5 }, { + .compatible = "giantplus,gpm940b0", + .data = &giantplus_gpm940b0, + }, { .compatible = "hannstar,hsd070pww1", .data = &hannstar_hsd070pww1, }, { @@ -2949,6 +3385,9 @@ static const struct of_device_id platform_of_match[] = { .compatible = "lg,lp129qe", .data = &lg_lp129qe, }, { + .compatible = "logicpd,type28", + .data = &logicpd_type_28, + }, { .compatible = "mitsubishi,aa070mc01-ca1", .data = &mitsubishi_aa070mc01, }, { @@ -2979,6 +3418,12 @@ static const struct of_device_id platform_of_match[] = { .compatible = "ontat,yx700wv03", .data = &ontat_yx700wv03, }, { + .compatible = "ortustech,com37h3m05dtc", + .data = &ortustech_com37h3m, + }, { + .compatible = "ortustech,com37h3m99dtc", + .data = &ortustech_com37h3m, + }, { .compatible = "ortustech,com43h4m85ulc", .data = &ortustech_com43h4m85ulc, }, { @@ -3003,9 +3448,18 @@ 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, + }, { .compatible = "sharp,lq035q7db03", .data = &sharp_lq035q7db03, }, { + .compatible = "sharp,lq070y3dg3b", + .data = &sharp_lq070y3dg3b, + }, { .compatible = "sharp,lq101k1ly04", .data = &sharp_lq101k1ly04, }, { @@ -3015,6 +3469,9 @@ static const struct of_device_id platform_of_match[] = { .compatible = "sharp,lq150x1lg11", .data = &sharp_lq150x1lg11, }, { + .compatible = "sharp,ls020b1dd01d", + .data = &sharp_ls020b1dd01d, + }, { .compatible = "shelly,sca07010-bfn-lnn", .data = &shelly_sca07010_bfn_lnn, }, { @@ -3030,6 +3487,12 @@ static const struct of_device_id platform_of_match[] = { .compatible = "tianma,tm070rvhg71", .data = &tianma_tm070rvhg71, }, { + .compatible = "ti,nspire-cx-lcd-panel", + .data = &ti_nspire_cx_lcd_panel, + }, { + .compatible = "ti,nspire-classic-lcd-panel", + .data = &ti_nspire_classic_lcd_panel, + }, { .compatible = "toshiba,lt089ac29000", .data = &toshiba_lt089ac29000, }, { diff --git a/drivers/gpu/drm/panel/panel-sitronix-st7701.c b/drivers/gpu/drm/panel/panel-sitronix-st7701.c index 09c5d9a6f9fa..4b4f2558e3b4 100644 --- a/drivers/gpu/drm/panel/panel-sitronix-st7701.c +++ b/drivers/gpu/drm/panel/panel-sitronix-st7701.c @@ -9,7 +9,6 @@ #include <drm/drm_panel.h> #include <drm/drm_print.h> -#include <linux/backlight.h> #include <linux/gpio/consumer.h> #include <linux/delay.h> #include <linux/module.h> @@ -103,7 +102,6 @@ struct st7701 { struct mipi_dsi_device *dsi; const struct st7701_panel_desc *desc; - struct backlight_device *backlight; struct regulator_bulk_data *supplies; struct gpio_desc *reset; unsigned int sleep_delay; @@ -223,7 +221,6 @@ static int st7701_enable(struct drm_panel *panel) struct st7701 *st7701 = panel_to_st7701(panel); ST7701_DSI(st7701, MIPI_DCS_SET_DISPLAY_ON, 0x00); - backlight_enable(st7701->backlight); return 0; } @@ -232,7 +229,6 @@ static int st7701_disable(struct drm_panel *panel) { struct st7701 *st7701 = panel_to_st7701(panel); - backlight_disable(st7701->backlight); ST7701_DSI(st7701, MIPI_DCS_SET_DISPLAY_OFF, 0x00); return 0; @@ -264,13 +260,14 @@ static int st7701_unprepare(struct drm_panel *panel) return 0; } -static int st7701_get_modes(struct drm_panel *panel) +static int st7701_get_modes(struct drm_panel *panel, + struct drm_connector *connector) { struct st7701 *st7701 = panel_to_st7701(panel); const struct drm_display_mode *desc_mode = st7701->desc->mode; struct drm_display_mode *mode; - mode = drm_mode_duplicate(panel->drm, desc_mode); + mode = drm_mode_duplicate(connector->dev, desc_mode); if (!mode) { DRM_DEV_ERROR(&st7701->dsi->dev, "failed to add mode %ux%ux@%u\n", @@ -280,10 +277,10 @@ static int st7701_get_modes(struct drm_panel *panel) } drm_mode_set_name(mode); - drm_mode_probed_add(panel->connector, mode); + drm_mode_probed_add(connector, mode); - panel->connector->display_info.width_mm = desc_mode->width_mm; - panel->connector->display_info.height_mm = desc_mode->height_mm; + connector->display_info.width_mm = desc_mode->width_mm; + connector->display_info.height_mm = desc_mode->height_mm; return 1; } @@ -365,11 +362,8 @@ static int st7701_dsi_probe(struct mipi_dsi_device *dsi) return PTR_ERR(st7701->reset); } - st7701->backlight = devm_of_find_backlight(&dsi->dev); - if (IS_ERR(st7701->backlight)) - return PTR_ERR(st7701->backlight); - - drm_panel_init(&st7701->panel); + drm_panel_init(&st7701->panel, &dsi->dev, &st7701_funcs, + DRM_MODE_CONNECTOR_DSI); /** * Once sleep out has been issued, ST7701 IC required to wait 120ms @@ -381,8 +375,10 @@ static int st7701_dsi_probe(struct mipi_dsi_device *dsi) * ts8550b and there is no valid documentation for that. */ st7701->sleep_delay = 120 + desc->panel_sleep_delay; - st7701->panel.funcs = &st7701_funcs; - st7701->panel.dev = &dsi->dev; + + ret = drm_panel_of_backlight(&st7701->panel); + if (ret) + return ret; ret = drm_panel_add(&st7701->panel); if (ret < 0) diff --git a/drivers/gpu/drm/panel/panel-sitronix-st7789v.c b/drivers/gpu/drm/panel/panel-sitronix-st7789v.c index 5e3e92ea9ea6..cc02c54c1b2e 100644 --- a/drivers/gpu/drm/panel/panel-sitronix-st7789v.c +++ b/drivers/gpu/drm/panel/panel-sitronix-st7789v.c @@ -3,7 +3,6 @@ * Copyright (C) 2017 Free Electrons */ -#include <linux/backlight.h> #include <linux/delay.h> #include <linux/gpio/consumer.h> #include <linux/module.h> @@ -116,7 +115,6 @@ struct st7789v { struct drm_panel panel; struct spi_device *spi; struct gpio_desc *reset; - struct backlight_device *backlight; struct regulator *power; }; @@ -170,14 +168,14 @@ static const struct drm_display_mode default_mode = { .vrefresh = 60, }; -static int st7789v_get_modes(struct drm_panel *panel) +static int st7789v_get_modes(struct drm_panel *panel, + struct drm_connector *connector) { - struct drm_connector *connector = panel->connector; struct drm_display_mode *mode; - mode = drm_mode_duplicate(panel->drm, &default_mode); + mode = drm_mode_duplicate(connector->dev, &default_mode); if (!mode) { - dev_err(panel->drm->dev, "failed to add mode %ux%ux@%u\n", + dev_err(panel->dev, "failed to add mode %ux%ux@%u\n", default_mode.hdisplay, default_mode.vdisplay, default_mode.vrefresh); return -ENOMEM; @@ -188,8 +186,8 @@ static int st7789v_get_modes(struct drm_panel *panel) mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; drm_mode_probed_add(connector, mode); - panel->connector->display_info.width_mm = 61; - panel->connector->display_info.height_mm = 103; + connector->display_info.width_mm = 61; + connector->display_info.height_mm = 103; return 1; } @@ -323,12 +321,6 @@ static int st7789v_enable(struct drm_panel *panel) { struct st7789v *ctx = panel_to_st7789v(panel); - if (ctx->backlight) { - ctx->backlight->props.state &= ~BL_CORE_FBBLANK; - ctx->backlight->props.power = FB_BLANK_UNBLANK; - backlight_update_status(ctx->backlight); - } - return st7789v_write_command(ctx, MIPI_DCS_SET_DISPLAY_ON); } @@ -339,12 +331,6 @@ static int st7789v_disable(struct drm_panel *panel) ST7789V_TEST(ret, st7789v_write_command(ctx, MIPI_DCS_SET_DISPLAY_OFF)); - if (ctx->backlight) { - ctx->backlight->props.power = FB_BLANK_POWERDOWN; - ctx->backlight->props.state |= BL_CORE_FBBLANK; - backlight_update_status(ctx->backlight); - } - return 0; } @@ -370,7 +356,6 @@ static const struct drm_panel_funcs st7789v_drm_funcs = { static int st7789v_probe(struct spi_device *spi) { - struct device_node *backlight; struct st7789v *ctx; int ret; @@ -381,8 +366,8 @@ static int st7789v_probe(struct spi_device *spi) spi_set_drvdata(spi, ctx); ctx->spi = spi; - ctx->panel.dev = &spi->dev; - ctx->panel.funcs = &st7789v_drm_funcs; + drm_panel_init(&ctx->panel, &spi->dev, &st7789v_drm_funcs, + DRM_MODE_CONNECTOR_DPI); ctx->power = devm_regulator_get(&spi->dev, "power"); if (IS_ERR(ctx->power)) @@ -394,26 +379,15 @@ static int st7789v_probe(struct spi_device *spi) return PTR_ERR(ctx->reset); } - backlight = of_parse_phandle(spi->dev.of_node, "backlight", 0); - if (backlight) { - ctx->backlight = of_find_backlight_by_node(backlight); - of_node_put(backlight); - - if (!ctx->backlight) - return -EPROBE_DEFER; - } + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return ret; ret = drm_panel_add(&ctx->panel); if (ret < 0) - goto err_free_backlight; + return ret; return 0; - -err_free_backlight: - if (ctx->backlight) - put_device(&ctx->backlight->dev); - - return ret; } static int st7789v_remove(struct spi_device *spi) @@ -422,9 +396,6 @@ static int st7789v_remove(struct spi_device *spi) drm_panel_remove(&ctx->panel); - if (ctx->backlight) - put_device(&ctx->backlight->dev); - return 0; } 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-sony-acx565akm.c b/drivers/gpu/drm/panel/panel-sony-acx565akm.c new file mode 100644 index 000000000000..5c4b6f6e5c2d --- /dev/null +++ b/drivers/gpu/drm/panel/panel-sony-acx565akm.c @@ -0,0 +1,707 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Sony ACX565AKM LCD Panel driver + * + * Copyright (C) 2019 Texas Instruments Incorporated + * + * Based on the omapdrm-specific panel-sony-acx565akm driver + * + * Copyright (C) 2010 Nokia Corporation + * Author: Imre Deak <imre.deak@nokia.com> + */ + +/* + * TODO (to be addressed with hardware access to test the changes): + * + * - Update backlight support to use backlight_update_status() etc. + * - Use prepare/unprepare for the basic power on/off of the backligt + */ + +#include <linux/backlight.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/sched.h> +#include <linux/spi/spi.h> +#include <video/mipi_display.h> + +#include <drm/drm_connector.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +#define CTRL_DISP_BRIGHTNESS_CTRL_ON BIT(5) +#define CTRL_DISP_AMBIENT_LIGHT_CTRL_ON BIT(4) +#define CTRL_DISP_BACKLIGHT_ON BIT(2) +#define CTRL_DISP_AUTO_BRIGHTNESS_ON BIT(1) + +#define MIPID_CMD_WRITE_CABC 0x55 +#define MIPID_CMD_READ_CABC 0x56 + +#define MIPID_VER_LPH8923 3 +#define MIPID_VER_LS041Y3 4 +#define MIPID_VER_L4F00311 8 +#define MIPID_VER_ACX565AKM 9 + +struct acx565akm_panel { + struct drm_panel panel; + + struct spi_device *spi; + struct gpio_desc *reset_gpio; + struct backlight_device *backlight; + + struct mutex mutex; + + const char *name; + u8 display_id[3]; + int model; + int revision; + bool has_bc; + bool has_cabc; + + bool enabled; + unsigned int cabc_mode; + /* + * Next value of jiffies when we can issue the next sleep in/out + * command. + */ + unsigned long hw_guard_end; + unsigned long hw_guard_wait; /* max guard time in jiffies */ +}; + +#define to_acx565akm_device(p) container_of(p, struct acx565akm_panel, panel) + +static void acx565akm_transfer(struct acx565akm_panel *lcd, int cmd, + const u8 *wbuf, int wlen, u8 *rbuf, int rlen) +{ + struct spi_message m; + struct spi_transfer *x, xfer[5]; + int ret; + + spi_message_init(&m); + + memset(xfer, 0, sizeof(xfer)); + x = &xfer[0]; + + cmd &= 0xff; + x->tx_buf = &cmd; + x->bits_per_word = 9; + x->len = 2; + + if (rlen > 1 && wlen == 0) { + /* + * Between the command and the response data there is a + * dummy clock cycle. Add an extra bit after the command + * word to account for this. + */ + x->bits_per_word = 10; + cmd <<= 1; + } + spi_message_add_tail(x, &m); + + if (wlen) { + x++; + x->tx_buf = wbuf; + x->len = wlen; + x->bits_per_word = 9; + spi_message_add_tail(x, &m); + } + + if (rlen) { + x++; + x->rx_buf = rbuf; + x->len = rlen; + spi_message_add_tail(x, &m); + } + + ret = spi_sync(lcd->spi, &m); + if (ret < 0) + dev_dbg(&lcd->spi->dev, "spi_sync %d\n", ret); +} + +static inline void acx565akm_cmd(struct acx565akm_panel *lcd, int cmd) +{ + acx565akm_transfer(lcd, cmd, NULL, 0, NULL, 0); +} + +static inline void acx565akm_write(struct acx565akm_panel *lcd, + int reg, const u8 *buf, int len) +{ + acx565akm_transfer(lcd, reg, buf, len, NULL, 0); +} + +static inline void acx565akm_read(struct acx565akm_panel *lcd, + int reg, u8 *buf, int len) +{ + acx565akm_transfer(lcd, reg, NULL, 0, buf, len); +} + +/* ----------------------------------------------------------------------------- + * Auto Brightness Control Via sysfs + */ + +static unsigned int acx565akm_get_cabc_mode(struct acx565akm_panel *lcd) +{ + return lcd->cabc_mode; +} + +static void acx565akm_set_cabc_mode(struct acx565akm_panel *lcd, + unsigned int mode) +{ + u16 cabc_ctrl; + + lcd->cabc_mode = mode; + if (!lcd->enabled) + return; + cabc_ctrl = 0; + acx565akm_read(lcd, MIPID_CMD_READ_CABC, (u8 *)&cabc_ctrl, 1); + cabc_ctrl &= ~3; + cabc_ctrl |= (1 << 8) | (mode & 3); + acx565akm_write(lcd, MIPID_CMD_WRITE_CABC, (u8 *)&cabc_ctrl, 2); +} + +static unsigned int acx565akm_get_hw_cabc_mode(struct acx565akm_panel *lcd) +{ + u8 cabc_ctrl; + + acx565akm_read(lcd, MIPID_CMD_READ_CABC, &cabc_ctrl, 1); + return cabc_ctrl & 3; +} + +static const char * const acx565akm_cabc_modes[] = { + "off", /* always used when CABC is not supported */ + "ui", + "still-image", + "moving-image", +}; + +static ssize_t cabc_mode_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct acx565akm_panel *lcd = dev_get_drvdata(dev); + const char *mode_str; + int mode; + + if (!lcd->has_cabc) + mode = 0; + else + mode = acx565akm_get_cabc_mode(lcd); + + mode_str = "unknown"; + if (mode >= 0 && mode < ARRAY_SIZE(acx565akm_cabc_modes)) + mode_str = acx565akm_cabc_modes[mode]; + + return sprintf(buf, "%s\n", mode_str); +} + +static ssize_t cabc_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct acx565akm_panel *lcd = dev_get_drvdata(dev); + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(acx565akm_cabc_modes); i++) { + const char *mode_str = acx565akm_cabc_modes[i]; + int cmp_len = strlen(mode_str); + + if (count > 0 && buf[count - 1] == '\n') + count--; + if (count != cmp_len) + continue; + + if (strncmp(buf, mode_str, cmp_len) == 0) + break; + } + + if (i == ARRAY_SIZE(acx565akm_cabc_modes)) + return -EINVAL; + + if (!lcd->has_cabc && i != 0) + return -EINVAL; + + mutex_lock(&lcd->mutex); + acx565akm_set_cabc_mode(lcd, i); + mutex_unlock(&lcd->mutex); + + return count; +} + +static ssize_t cabc_available_modes_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct acx565akm_panel *lcd = dev_get_drvdata(dev); + unsigned int i; + size_t len = 0; + + if (!lcd->has_cabc) + return sprintf(buf, "%s\n", acx565akm_cabc_modes[0]); + + for (i = 0; i < ARRAY_SIZE(acx565akm_cabc_modes); i++) + len += sprintf(&buf[len], "%s%s", i ? " " : "", + acx565akm_cabc_modes[i]); + + buf[len++] = '\n'; + + return len; +} + +static DEVICE_ATTR_RW(cabc_mode); +static DEVICE_ATTR_RO(cabc_available_modes); + +static struct attribute *acx565akm_cabc_attrs[] = { + &dev_attr_cabc_mode.attr, + &dev_attr_cabc_available_modes.attr, + NULL, +}; + +static const struct attribute_group acx565akm_cabc_attr_group = { + .attrs = acx565akm_cabc_attrs, +}; + +/* ----------------------------------------------------------------------------- + * Backlight Device + */ + +static int acx565akm_get_actual_brightness(struct acx565akm_panel *lcd) +{ + u8 bv; + + acx565akm_read(lcd, MIPI_DCS_GET_DISPLAY_BRIGHTNESS, &bv, 1); + + return bv; +} + +static void acx565akm_set_brightness(struct acx565akm_panel *lcd, int level) +{ + u16 ctrl; + int bv; + + bv = level | (1 << 8); + acx565akm_write(lcd, MIPI_DCS_SET_DISPLAY_BRIGHTNESS, (u8 *)&bv, 2); + + acx565akm_read(lcd, MIPI_DCS_GET_CONTROL_DISPLAY, (u8 *)&ctrl, 1); + if (level) + ctrl |= CTRL_DISP_BRIGHTNESS_CTRL_ON | + CTRL_DISP_BACKLIGHT_ON; + else + ctrl &= ~(CTRL_DISP_BRIGHTNESS_CTRL_ON | + CTRL_DISP_BACKLIGHT_ON); + + ctrl |= 1 << 8; + acx565akm_write(lcd, MIPI_DCS_WRITE_CONTROL_DISPLAY, (u8 *)&ctrl, 2); +} + +static int acx565akm_bl_update_status_locked(struct backlight_device *dev) +{ + struct acx565akm_panel *lcd = dev_get_drvdata(&dev->dev); + int level; + + if (dev->props.fb_blank == FB_BLANK_UNBLANK && + dev->props.power == FB_BLANK_UNBLANK) + level = dev->props.brightness; + else + level = 0; + + acx565akm_set_brightness(lcd, level); + + return 0; +} + +static int acx565akm_bl_update_status(struct backlight_device *dev) +{ + struct acx565akm_panel *lcd = dev_get_drvdata(&dev->dev); + int ret; + + mutex_lock(&lcd->mutex); + ret = acx565akm_bl_update_status_locked(dev); + mutex_unlock(&lcd->mutex); + + return ret; +} + +static int acx565akm_bl_get_intensity(struct backlight_device *dev) +{ + struct acx565akm_panel *lcd = dev_get_drvdata(&dev->dev); + unsigned int intensity; + + mutex_lock(&lcd->mutex); + + if (dev->props.fb_blank == FB_BLANK_UNBLANK && + dev->props.power == FB_BLANK_UNBLANK) + intensity = acx565akm_get_actual_brightness(lcd); + else + intensity = 0; + + mutex_unlock(&lcd->mutex); + + return intensity; +} + +static const struct backlight_ops acx565akm_bl_ops = { + .get_brightness = acx565akm_bl_get_intensity, + .update_status = acx565akm_bl_update_status, +}; + +static int acx565akm_backlight_init(struct acx565akm_panel *lcd) +{ + struct backlight_properties props = { + .fb_blank = FB_BLANK_UNBLANK, + .power = FB_BLANK_UNBLANK, + .type = BACKLIGHT_RAW, + }; + int ret; + + lcd->backlight = backlight_device_register(lcd->name, &lcd->spi->dev, + lcd, &acx565akm_bl_ops, + &props); + if (IS_ERR(lcd->backlight)) { + ret = PTR_ERR(lcd->backlight); + lcd->backlight = NULL; + return ret; + } + + if (lcd->has_cabc) { + ret = sysfs_create_group(&lcd->backlight->dev.kobj, + &acx565akm_cabc_attr_group); + if (ret < 0) { + dev_err(&lcd->spi->dev, + "%s failed to create sysfs files\n", __func__); + backlight_device_unregister(lcd->backlight); + return ret; + } + + lcd->cabc_mode = acx565akm_get_hw_cabc_mode(lcd); + } + + lcd->backlight->props.max_brightness = 255; + lcd->backlight->props.brightness = acx565akm_get_actual_brightness(lcd); + + acx565akm_bl_update_status_locked(lcd->backlight); + + return 0; +} + +static void acx565akm_backlight_cleanup(struct acx565akm_panel *lcd) +{ + if (lcd->has_cabc) + sysfs_remove_group(&lcd->backlight->dev.kobj, + &acx565akm_cabc_attr_group); + + backlight_device_unregister(lcd->backlight); +} + +/* ----------------------------------------------------------------------------- + * DRM Bridge Operations + */ + +static void acx565akm_set_sleep_mode(struct acx565akm_panel *lcd, int on) +{ + int cmd = on ? MIPI_DCS_ENTER_SLEEP_MODE : MIPI_DCS_EXIT_SLEEP_MODE; + unsigned long wait; + + /* + * We have to keep 120msec between sleep in/out commands. + * (8.2.15, 8.2.16). + */ + wait = lcd->hw_guard_end - jiffies; + if ((long)wait > 0 && wait <= lcd->hw_guard_wait) { + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(wait); + } + + acx565akm_cmd(lcd, cmd); + + lcd->hw_guard_wait = msecs_to_jiffies(120); + lcd->hw_guard_end = jiffies + lcd->hw_guard_wait; +} + +static void acx565akm_set_display_state(struct acx565akm_panel *lcd, + int enabled) +{ + int cmd = enabled ? MIPI_DCS_SET_DISPLAY_ON : MIPI_DCS_SET_DISPLAY_OFF; + + acx565akm_cmd(lcd, cmd); +} + +static int acx565akm_power_on(struct acx565akm_panel *lcd) +{ + /*FIXME tweak me */ + msleep(50); + + gpiod_set_value(lcd->reset_gpio, 1); + + if (lcd->enabled) { + dev_dbg(&lcd->spi->dev, "panel already enabled\n"); + return 0; + } + + /* + * We have to meet all the following delay requirements: + * 1. tRW: reset pulse width 10usec (7.12.1) + * 2. tRT: reset cancel time 5msec (7.12.1) + * 3. Providing PCLK,HS,VS signals for 2 frames = ~50msec worst + * case (7.6.2) + * 4. 120msec before the sleep out command (7.12.1) + */ + msleep(120); + + acx565akm_set_sleep_mode(lcd, 0); + lcd->enabled = true; + + /* 5msec between sleep out and the next command. (8.2.16) */ + usleep_range(5000, 10000); + acx565akm_set_display_state(lcd, 1); + acx565akm_set_cabc_mode(lcd, lcd->cabc_mode); + + return acx565akm_bl_update_status_locked(lcd->backlight); +} + +static void acx565akm_power_off(struct acx565akm_panel *lcd) +{ + if (!lcd->enabled) + return; + + acx565akm_set_display_state(lcd, 0); + acx565akm_set_sleep_mode(lcd, 1); + lcd->enabled = false; + /* + * We have to provide PCLK,HS,VS signals for 2 frames (worst case + * ~50msec) after sending the sleep in command and asserting the + * reset signal. We probably could assert the reset w/o the delay + * but we still delay to avoid possible artifacts. (7.6.1) + */ + msleep(50); + + gpiod_set_value(lcd->reset_gpio, 0); + + /* FIXME need to tweak this delay */ + msleep(100); +} + +static int acx565akm_disable(struct drm_panel *panel) +{ + struct acx565akm_panel *lcd = to_acx565akm_device(panel); + + mutex_lock(&lcd->mutex); + acx565akm_power_off(lcd); + mutex_unlock(&lcd->mutex); + + return 0; +} + +static int acx565akm_enable(struct drm_panel *panel) +{ + struct acx565akm_panel *lcd = to_acx565akm_device(panel); + + mutex_lock(&lcd->mutex); + acx565akm_power_on(lcd); + mutex_unlock(&lcd->mutex); + + return 0; +} + +static const struct drm_display_mode acx565akm_mode = { + .clock = 24000, + .hdisplay = 800, + .hsync_start = 800 + 28, + .hsync_end = 800 + 28 + 4, + .htotal = 800 + 28 + 4 + 24, + .vdisplay = 480, + .vsync_start = 480 + 3, + .vsync_end = 480 + 3 + 3, + .vtotal = 480 + 3 + 3 + 4, + .vrefresh = 57, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + .width_mm = 77, + .height_mm = 46, +}; + +static int acx565akm_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &acx565akm_mode); + if (!mode) + return -ENOMEM; + + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + + connector->display_info.width_mm = acx565akm_mode.width_mm; + connector->display_info.height_mm = acx565akm_mode.height_mm; + connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH + | DRM_BUS_FLAG_SYNC_SAMPLE_POSEDGE + | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE; + + return 1; +} + +static const struct drm_panel_funcs acx565akm_funcs = { + .disable = acx565akm_disable, + .enable = acx565akm_enable, + .get_modes = acx565akm_get_modes, +}; + +/* ----------------------------------------------------------------------------- + * Probe, Detect and Remove + */ + +static int acx565akm_detect(struct acx565akm_panel *lcd) +{ + __be32 value; + u32 status; + int ret = 0; + + /* + * After being taken out of reset the panel needs 5ms before the first + * command can be sent. + */ + gpiod_set_value(lcd->reset_gpio, 1); + usleep_range(5000, 10000); + + acx565akm_read(lcd, MIPI_DCS_GET_DISPLAY_STATUS, (u8 *)&value, 4); + status = __be32_to_cpu(value); + lcd->enabled = (status & (1 << 17)) && (status & (1 << 10)); + + dev_dbg(&lcd->spi->dev, + "LCD panel %s by bootloader (status 0x%04x)\n", + lcd->enabled ? "enabled" : "disabled ", status); + + acx565akm_read(lcd, MIPI_DCS_GET_DISPLAY_ID, lcd->display_id, 3); + dev_dbg(&lcd->spi->dev, "MIPI display ID: %02x%02x%02x\n", + lcd->display_id[0], lcd->display_id[1], lcd->display_id[2]); + + switch (lcd->display_id[0]) { + case 0x10: + lcd->model = MIPID_VER_ACX565AKM; + lcd->name = "acx565akm"; + lcd->has_bc = 1; + lcd->has_cabc = 1; + break; + case 0x29: + lcd->model = MIPID_VER_L4F00311; + lcd->name = "l4f00311"; + break; + case 0x45: + lcd->model = MIPID_VER_LPH8923; + lcd->name = "lph8923"; + break; + case 0x83: + lcd->model = MIPID_VER_LS041Y3; + lcd->name = "ls041y3"; + break; + default: + lcd->name = "unknown"; + dev_err(&lcd->spi->dev, "unknown display ID\n"); + ret = -ENODEV; + goto done; + } + + lcd->revision = lcd->display_id[1]; + + dev_info(&lcd->spi->dev, "%s rev %02x panel detected\n", + lcd->name, lcd->revision); + +done: + if (!lcd->enabled) + gpiod_set_value(lcd->reset_gpio, 0); + + return ret; +} + +static int acx565akm_probe(struct spi_device *spi) +{ + struct acx565akm_panel *lcd; + int ret; + + lcd = devm_kzalloc(&spi->dev, sizeof(*lcd), GFP_KERNEL); + if (!lcd) + return -ENOMEM; + + spi_set_drvdata(spi, lcd); + spi->mode = SPI_MODE_3; + + lcd->spi = spi; + mutex_init(&lcd->mutex); + + lcd->reset_gpio = devm_gpiod_get(&spi->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(lcd->reset_gpio)) { + dev_err(&spi->dev, "failed to get reset GPIO\n"); + return PTR_ERR(lcd->reset_gpio); + } + + ret = acx565akm_detect(lcd); + if (ret < 0) { + dev_err(&spi->dev, "panel detection failed\n"); + return ret; + } + + if (lcd->has_bc) { + ret = acx565akm_backlight_init(lcd); + if (ret < 0) + return ret; + } + + drm_panel_init(&lcd->panel, &lcd->spi->dev, &acx565akm_funcs, + DRM_MODE_CONNECTOR_DPI); + + ret = drm_panel_add(&lcd->panel); + if (ret < 0) { + if (lcd->has_bc) + acx565akm_backlight_cleanup(lcd); + return ret; + } + + return 0; +} + +static int acx565akm_remove(struct spi_device *spi) +{ + struct acx565akm_panel *lcd = spi_get_drvdata(spi); + + drm_panel_remove(&lcd->panel); + + if (lcd->has_bc) + acx565akm_backlight_cleanup(lcd); + + drm_panel_disable(&lcd->panel); + drm_panel_unprepare(&lcd->panel); + + return 0; +} + +static const struct of_device_id acx565akm_of_match[] = { + { .compatible = "sony,acx565akm", }, + { /* sentinel */ }, +}; + +MODULE_DEVICE_TABLE(of, acx565akm_of_match); + +static const struct spi_device_id acx565akm_ids[] = { + { "acx565akm", 0 }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(spi, acx565akm_ids); + +static struct spi_driver acx565akm_driver = { + .probe = acx565akm_probe, + .remove = acx565akm_remove, + .id_table = acx565akm_ids, + .driver = { + .name = "panel-sony-acx565akm", + .of_match_table = acx565akm_of_match, + }, +}; + +module_spi_driver(acx565akm_driver); + +MODULE_AUTHOR("Nokia Corporation"); +MODULE_DESCRIPTION("Sony ACX565AKM LCD Panel Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-tpo-td028ttec1.c b/drivers/gpu/drm/panel/panel-tpo-td028ttec1.c new file mode 100644 index 000000000000..cf29405a2dbe --- /dev/null +++ b/drivers/gpu/drm/panel/panel-tpo-td028ttec1.c @@ -0,0 +1,391 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Toppoly TD028TTEC1 Panel Driver + * + * Copyright (C) 2019 Texas Instruments Incorporated + * + * Based on the omapdrm-specific panel-tpo-td028ttec1 driver + * + * Copyright (C) 2008 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@ti.com> + * + * Neo 1973 code (jbt6k74.c): + * Copyright (C) 2006-2007 OpenMoko, Inc. + * Author: Harald Welte <laforge@openmoko.org> + * + * Ported and adapted from Neo 1973 U-Boot by: + * H. Nikolaus Schaller <hns@goldelico.com> + */ + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/spi/spi.h> + +#include <drm/drm_connector.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +#define JBT_COMMAND 0x000 +#define JBT_DATA 0x100 + +#define JBT_REG_SLEEP_IN 0x10 +#define JBT_REG_SLEEP_OUT 0x11 + +#define JBT_REG_DISPLAY_OFF 0x28 +#define JBT_REG_DISPLAY_ON 0x29 + +#define JBT_REG_RGB_FORMAT 0x3a +#define JBT_REG_QUAD_RATE 0x3b + +#define JBT_REG_POWER_ON_OFF 0xb0 +#define JBT_REG_BOOSTER_OP 0xb1 +#define JBT_REG_BOOSTER_MODE 0xb2 +#define JBT_REG_BOOSTER_FREQ 0xb3 +#define JBT_REG_OPAMP_SYSCLK 0xb4 +#define JBT_REG_VSC_VOLTAGE 0xb5 +#define JBT_REG_VCOM_VOLTAGE 0xb6 +#define JBT_REG_EXT_DISPL 0xb7 +#define JBT_REG_OUTPUT_CONTROL 0xb8 +#define JBT_REG_DCCLK_DCEV 0xb9 +#define JBT_REG_DISPLAY_MODE1 0xba +#define JBT_REG_DISPLAY_MODE2 0xbb +#define JBT_REG_DISPLAY_MODE 0xbc +#define JBT_REG_ASW_SLEW 0xbd +#define JBT_REG_DUMMY_DISPLAY 0xbe +#define JBT_REG_DRIVE_SYSTEM 0xbf + +#define JBT_REG_SLEEP_OUT_FR_A 0xc0 +#define JBT_REG_SLEEP_OUT_FR_B 0xc1 +#define JBT_REG_SLEEP_OUT_FR_C 0xc2 +#define JBT_REG_SLEEP_IN_LCCNT_D 0xc3 +#define JBT_REG_SLEEP_IN_LCCNT_E 0xc4 +#define JBT_REG_SLEEP_IN_LCCNT_F 0xc5 +#define JBT_REG_SLEEP_IN_LCCNT_G 0xc6 + +#define JBT_REG_GAMMA1_FINE_1 0xc7 +#define JBT_REG_GAMMA1_FINE_2 0xc8 +#define JBT_REG_GAMMA1_INCLINATION 0xc9 +#define JBT_REG_GAMMA1_BLUE_OFFSET 0xca + +#define JBT_REG_BLANK_CONTROL 0xcf +#define JBT_REG_BLANK_TH_TV 0xd0 +#define JBT_REG_CKV_ON_OFF 0xd1 +#define JBT_REG_CKV_1_2 0xd2 +#define JBT_REG_OEV_TIMING 0xd3 +#define JBT_REG_ASW_TIMING_1 0xd4 +#define JBT_REG_ASW_TIMING_2 0xd5 + +#define JBT_REG_HCLOCK_VGA 0xec +#define JBT_REG_HCLOCK_QVGA 0xed + +struct td028ttec1_panel { + struct drm_panel panel; + + struct spi_device *spi; +}; + +#define to_td028ttec1_device(p) container_of(p, struct td028ttec1_panel, panel) + +static int jbt_ret_write_0(struct td028ttec1_panel *lcd, u8 reg, int *err) +{ + struct spi_device *spi = lcd->spi; + u16 tx_buf = JBT_COMMAND | reg; + int ret; + + if (err && *err) + return *err; + + ret = spi_write(spi, (u8 *)&tx_buf, sizeof(tx_buf)); + if (ret < 0) { + dev_err(&spi->dev, "%s: SPI write failed: %d\n", __func__, ret); + if (err) + *err = ret; + } + + return ret; +} + +static int jbt_reg_write_1(struct td028ttec1_panel *lcd, + u8 reg, u8 data, int *err) +{ + struct spi_device *spi = lcd->spi; + u16 tx_buf[2]; + int ret; + + if (err && *err) + return *err; + + tx_buf[0] = JBT_COMMAND | reg; + tx_buf[1] = JBT_DATA | data; + + ret = spi_write(spi, (u8 *)tx_buf, sizeof(tx_buf)); + if (ret < 0) { + dev_err(&spi->dev, "%s: SPI write failed: %d\n", __func__, ret); + if (err) + *err = ret; + } + + return ret; +} + +static int jbt_reg_write_2(struct td028ttec1_panel *lcd, + u8 reg, u16 data, int *err) +{ + struct spi_device *spi = lcd->spi; + u16 tx_buf[3]; + int ret; + + if (err && *err) + return *err; + + tx_buf[0] = JBT_COMMAND | reg; + tx_buf[1] = JBT_DATA | (data >> 8); + tx_buf[2] = JBT_DATA | (data & 0xff); + + ret = spi_write(spi, (u8 *)tx_buf, sizeof(tx_buf)); + if (ret < 0) { + dev_err(&spi->dev, "%s: SPI write failed: %d\n", __func__, ret); + if (err) + *err = ret; + } + + return ret; +} + +static int td028ttec1_prepare(struct drm_panel *panel) +{ + struct td028ttec1_panel *lcd = to_td028ttec1_device(panel); + unsigned int i; + int ret = 0; + + /* Three times command zero */ + for (i = 0; i < 3; ++i) { + jbt_ret_write_0(lcd, 0x00, &ret); + usleep_range(1000, 2000); + } + + /* deep standby out */ + jbt_reg_write_1(lcd, JBT_REG_POWER_ON_OFF, 0x17, &ret); + + /* RGB I/F on, RAM write off, QVGA through, SIGCON enable */ + jbt_reg_write_1(lcd, JBT_REG_DISPLAY_MODE, 0x80, &ret); + + /* Quad mode off */ + jbt_reg_write_1(lcd, JBT_REG_QUAD_RATE, 0x00, &ret); + + /* AVDD on, XVDD on */ + jbt_reg_write_1(lcd, JBT_REG_POWER_ON_OFF, 0x16, &ret); + + /* Output control */ + jbt_reg_write_2(lcd, JBT_REG_OUTPUT_CONTROL, 0xfff9, &ret); + + /* Sleep mode off */ + jbt_ret_write_0(lcd, JBT_REG_SLEEP_OUT, &ret); + + /* at this point we have like 50% grey */ + + /* initialize register set */ + jbt_reg_write_1(lcd, JBT_REG_DISPLAY_MODE1, 0x01, &ret); + jbt_reg_write_1(lcd, JBT_REG_DISPLAY_MODE2, 0x00, &ret); + jbt_reg_write_1(lcd, JBT_REG_RGB_FORMAT, 0x60, &ret); + jbt_reg_write_1(lcd, JBT_REG_DRIVE_SYSTEM, 0x10, &ret); + jbt_reg_write_1(lcd, JBT_REG_BOOSTER_OP, 0x56, &ret); + jbt_reg_write_1(lcd, JBT_REG_BOOSTER_MODE, 0x33, &ret); + jbt_reg_write_1(lcd, JBT_REG_BOOSTER_FREQ, 0x11, &ret); + jbt_reg_write_1(lcd, JBT_REG_BOOSTER_FREQ, 0x11, &ret); + jbt_reg_write_1(lcd, JBT_REG_OPAMP_SYSCLK, 0x02, &ret); + jbt_reg_write_1(lcd, JBT_REG_VSC_VOLTAGE, 0x2b, &ret); + jbt_reg_write_1(lcd, JBT_REG_VCOM_VOLTAGE, 0x40, &ret); + jbt_reg_write_1(lcd, JBT_REG_EXT_DISPL, 0x03, &ret); + jbt_reg_write_1(lcd, JBT_REG_DCCLK_DCEV, 0x04, &ret); + /* + * default of 0x02 in JBT_REG_ASW_SLEW responsible for 72Hz requirement + * to avoid red / blue flicker + */ + jbt_reg_write_1(lcd, JBT_REG_ASW_SLEW, 0x04, &ret); + jbt_reg_write_1(lcd, JBT_REG_DUMMY_DISPLAY, 0x00, &ret); + + jbt_reg_write_1(lcd, JBT_REG_SLEEP_OUT_FR_A, 0x11, &ret); + jbt_reg_write_1(lcd, JBT_REG_SLEEP_OUT_FR_B, 0x11, &ret); + jbt_reg_write_1(lcd, JBT_REG_SLEEP_OUT_FR_C, 0x11, &ret); + jbt_reg_write_2(lcd, JBT_REG_SLEEP_IN_LCCNT_D, 0x2040, &ret); + jbt_reg_write_2(lcd, JBT_REG_SLEEP_IN_LCCNT_E, 0x60c0, &ret); + jbt_reg_write_2(lcd, JBT_REG_SLEEP_IN_LCCNT_F, 0x1020, &ret); + jbt_reg_write_2(lcd, JBT_REG_SLEEP_IN_LCCNT_G, 0x60c0, &ret); + + jbt_reg_write_2(lcd, JBT_REG_GAMMA1_FINE_1, 0x5533, &ret); + jbt_reg_write_1(lcd, JBT_REG_GAMMA1_FINE_2, 0x00, &ret); + jbt_reg_write_1(lcd, JBT_REG_GAMMA1_INCLINATION, 0x00, &ret); + jbt_reg_write_1(lcd, JBT_REG_GAMMA1_BLUE_OFFSET, 0x00, &ret); + + jbt_reg_write_2(lcd, JBT_REG_HCLOCK_VGA, 0x1f0, &ret); + jbt_reg_write_1(lcd, JBT_REG_BLANK_CONTROL, 0x02, &ret); + jbt_reg_write_2(lcd, JBT_REG_BLANK_TH_TV, 0x0804, &ret); + + jbt_reg_write_1(lcd, JBT_REG_CKV_ON_OFF, 0x01, &ret); + jbt_reg_write_2(lcd, JBT_REG_CKV_1_2, 0x0000, &ret); + + jbt_reg_write_2(lcd, JBT_REG_OEV_TIMING, 0x0d0e, &ret); + jbt_reg_write_2(lcd, JBT_REG_ASW_TIMING_1, 0x11a4, &ret); + jbt_reg_write_1(lcd, JBT_REG_ASW_TIMING_2, 0x0e, &ret); + + return ret; +} + +static int td028ttec1_enable(struct drm_panel *panel) +{ + struct td028ttec1_panel *lcd = to_td028ttec1_device(panel); + int ret; + + ret = jbt_ret_write_0(lcd, JBT_REG_DISPLAY_ON, NULL); + if (ret) + return ret; + + return 0; +} + +static int td028ttec1_disable(struct drm_panel *panel) +{ + struct td028ttec1_panel *lcd = to_td028ttec1_device(panel); + + jbt_ret_write_0(lcd, JBT_REG_DISPLAY_OFF, NULL); + + return 0; +} + +static int td028ttec1_unprepare(struct drm_panel *panel) +{ + struct td028ttec1_panel *lcd = to_td028ttec1_device(panel); + + jbt_reg_write_2(lcd, JBT_REG_OUTPUT_CONTROL, 0x8002, NULL); + jbt_ret_write_0(lcd, JBT_REG_SLEEP_IN, NULL); + jbt_reg_write_1(lcd, JBT_REG_POWER_ON_OFF, 0x00, NULL); + + return 0; +} + +static const struct drm_display_mode td028ttec1_mode = { + .clock = 22153, + .hdisplay = 480, + .hsync_start = 480 + 24, + .hsync_end = 480 + 24 + 8, + .htotal = 480 + 24 + 8 + 8, + .vdisplay = 640, + .vsync_start = 640 + 4, + .vsync_end = 640 + 4 + 2, + .vtotal = 640 + 4 + 2 + 2, + .vrefresh = 66, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + .width_mm = 43, + .height_mm = 58, +}; + +static int td028ttec1_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &td028ttec1_mode); + if (!mode) + return -ENOMEM; + + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + + connector->display_info.width_mm = td028ttec1_mode.width_mm; + connector->display_info.height_mm = td028ttec1_mode.height_mm; + /* + * FIXME: According to the datasheet sync signals are sampled on the + * rising edge of the clock, but the code running on the OpenMoko Neo + * FreeRunner and Neo 1973 indicates sampling on the falling edge. This + * should be tested on a real device. + */ + connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH + | DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE + | DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE; + + return 1; +} + +static const struct drm_panel_funcs td028ttec1_funcs = { + .prepare = td028ttec1_prepare, + .enable = td028ttec1_enable, + .disable = td028ttec1_disable, + .unprepare = td028ttec1_unprepare, + .get_modes = td028ttec1_get_modes, +}; + +static int td028ttec1_probe(struct spi_device *spi) +{ + struct td028ttec1_panel *lcd; + int ret; + + lcd = devm_kzalloc(&spi->dev, sizeof(*lcd), GFP_KERNEL); + if (!lcd) + return -ENOMEM; + + spi_set_drvdata(spi, lcd); + lcd->spi = spi; + + spi->mode = SPI_MODE_3; + spi->bits_per_word = 9; + + ret = spi_setup(spi); + if (ret < 0) { + dev_err(&spi->dev, "failed to setup SPI: %d\n", ret); + return ret; + } + + drm_panel_init(&lcd->panel, &lcd->spi->dev, &td028ttec1_funcs, + DRM_MODE_CONNECTOR_DPI); + + ret = drm_panel_of_backlight(&lcd->panel); + if (ret) + return ret; + + return drm_panel_add(&lcd->panel); +} + +static int td028ttec1_remove(struct spi_device *spi) +{ + struct td028ttec1_panel *lcd = spi_get_drvdata(spi); + + drm_panel_remove(&lcd->panel); + drm_panel_disable(&lcd->panel); + drm_panel_unprepare(&lcd->panel); + + return 0; +} + +static const struct of_device_id td028ttec1_of_match[] = { + { .compatible = "tpo,td028ttec1", }, + /* DT backward compatibility. */ + { .compatible = "toppoly,td028ttec1", }, + { /* sentinel */ }, +}; + +MODULE_DEVICE_TABLE(of, td028ttec1_of_match); + +static const struct spi_device_id td028ttec1_ids[] = { + { "td028ttec1", 0 }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(spi, td028ttec1_ids); + +static struct spi_driver td028ttec1_driver = { + .probe = td028ttec1_probe, + .remove = td028ttec1_remove, + .id_table = td028ttec1_ids, + .driver = { + .name = "panel-tpo-td028ttec1", + .of_match_table = td028ttec1_of_match, + }, +}; + +module_spi_driver(td028ttec1_driver); + +MODULE_AUTHOR("H. Nikolaus Schaller <hns@goldelico.com>"); +MODULE_DESCRIPTION("Toppoly TD028TTEC1 panel driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-tpo-td043mtea1.c b/drivers/gpu/drm/panel/panel-tpo-td043mtea1.c new file mode 100644 index 000000000000..75f1f1f1b6de --- /dev/null +++ b/drivers/gpu/drm/panel/panel-tpo-td043mtea1.c @@ -0,0 +1,515 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Toppoly TD043MTEA1 Panel Driver + * + * Copyright (C) 2019 Texas Instruments Incorporated + * + * Based on the omapdrm-specific panel-tpo-td043mtea1 driver + * + * Author: Gražvydas Ignotas <notasas@gmail.com> + */ + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/regulator/consumer.h> +#include <linux/spi/spi.h> + +#include <drm/drm_connector.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +#define TPO_R02_MODE(x) ((x) & 7) +#define TPO_R02_MODE_800x480 7 +#define TPO_R02_NCLK_RISING BIT(3) +#define TPO_R02_HSYNC_HIGH BIT(4) +#define TPO_R02_VSYNC_HIGH BIT(5) + +#define TPO_R03_NSTANDBY BIT(0) +#define TPO_R03_EN_CP_CLK BIT(1) +#define TPO_R03_EN_VGL_PUMP BIT(2) +#define TPO_R03_EN_PWM BIT(3) +#define TPO_R03_DRIVING_CAP_100 BIT(4) +#define TPO_R03_EN_PRE_CHARGE BIT(6) +#define TPO_R03_SOFTWARE_CTL BIT(7) + +#define TPO_R04_NFLIP_H BIT(0) +#define TPO_R04_NFLIP_V BIT(1) +#define TPO_R04_CP_CLK_FREQ_1H BIT(2) +#define TPO_R04_VGL_FREQ_1H BIT(4) + +#define TPO_R03_VAL_NORMAL \ + (TPO_R03_NSTANDBY | TPO_R03_EN_CP_CLK | TPO_R03_EN_VGL_PUMP | \ + TPO_R03_EN_PWM | TPO_R03_DRIVING_CAP_100 | TPO_R03_EN_PRE_CHARGE | \ + TPO_R03_SOFTWARE_CTL) + +#define TPO_R03_VAL_STANDBY \ + (TPO_R03_DRIVING_CAP_100 | TPO_R03_EN_PRE_CHARGE | \ + TPO_R03_SOFTWARE_CTL) + +static const u16 td043mtea1_def_gamma[12] = { + 105, 315, 381, 431, 490, 537, 579, 686, 780, 837, 880, 1023 +}; + +struct td043mtea1_panel { + struct drm_panel panel; + + struct spi_device *spi; + struct regulator *vcc_reg; + struct gpio_desc *reset_gpio; + + unsigned int mode; + u16 gamma[12]; + bool vmirror; + bool powered_on; + bool spi_suspended; + bool power_on_resume; +}; + +#define to_td043mtea1_device(p) container_of(p, struct td043mtea1_panel, panel) + +/* ----------------------------------------------------------------------------- + * Hardware Access + */ + +static int td043mtea1_write(struct td043mtea1_panel *lcd, u8 addr, u8 value) +{ + struct spi_message msg; + struct spi_transfer xfer; + u16 data; + int ret; + + spi_message_init(&msg); + + memset(&xfer, 0, sizeof(xfer)); + + data = ((u16)addr << 10) | (1 << 8) | value; + xfer.tx_buf = &data; + xfer.bits_per_word = 16; + xfer.len = 2; + spi_message_add_tail(&xfer, &msg); + + ret = spi_sync(lcd->spi, &msg); + if (ret < 0) + dev_warn(&lcd->spi->dev, "failed to write to LCD reg (%d)\n", + ret); + + return ret; +} + +static void td043mtea1_write_gamma(struct td043mtea1_panel *lcd) +{ + const u16 *gamma = lcd->gamma; + unsigned int i; + u8 val; + + /* gamma bits [9:8] */ + for (val = i = 0; i < 4; i++) + val |= (gamma[i] & 0x300) >> ((i + 1) * 2); + td043mtea1_write(lcd, 0x11, val); + + for (val = i = 0; i < 4; i++) + val |= (gamma[i + 4] & 0x300) >> ((i + 1) * 2); + td043mtea1_write(lcd, 0x12, val); + + for (val = i = 0; i < 4; i++) + val |= (gamma[i + 8] & 0x300) >> ((i + 1) * 2); + td043mtea1_write(lcd, 0x13, val); + + /* gamma bits [7:0] */ + for (i = 0; i < 12; i++) + td043mtea1_write(lcd, 0x14 + i, gamma[i] & 0xff); +} + +static int td043mtea1_write_mirror(struct td043mtea1_panel *lcd) +{ + u8 reg4 = TPO_R04_NFLIP_H | TPO_R04_NFLIP_V | + TPO_R04_CP_CLK_FREQ_1H | TPO_R04_VGL_FREQ_1H; + if (lcd->vmirror) + reg4 &= ~TPO_R04_NFLIP_V; + + return td043mtea1_write(lcd, 4, reg4); +} + +static int td043mtea1_power_on(struct td043mtea1_panel *lcd) +{ + int ret; + + if (lcd->powered_on) + return 0; + + ret = regulator_enable(lcd->vcc_reg); + if (ret < 0) + return ret; + + /* Wait for the panel to stabilize. */ + msleep(160); + + gpiod_set_value(lcd->reset_gpio, 0); + + td043mtea1_write(lcd, 2, TPO_R02_MODE(lcd->mode) | TPO_R02_NCLK_RISING); + td043mtea1_write(lcd, 3, TPO_R03_VAL_NORMAL); + td043mtea1_write(lcd, 0x20, 0xf0); + td043mtea1_write(lcd, 0x21, 0xf0); + td043mtea1_write_mirror(lcd); + td043mtea1_write_gamma(lcd); + + lcd->powered_on = true; + + return 0; +} + +static void td043mtea1_power_off(struct td043mtea1_panel *lcd) +{ + if (!lcd->powered_on) + return; + + td043mtea1_write(lcd, 3, TPO_R03_VAL_STANDBY | TPO_R03_EN_PWM); + + gpiod_set_value(lcd->reset_gpio, 1); + + /* wait for at least 2 vsyncs before cutting off power */ + msleep(50); + + td043mtea1_write(lcd, 3, TPO_R03_VAL_STANDBY); + + regulator_disable(lcd->vcc_reg); + + lcd->powered_on = false; +} + +/* ----------------------------------------------------------------------------- + * sysfs + */ + +static ssize_t vmirror_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct td043mtea1_panel *lcd = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", lcd->vmirror); +} + +static ssize_t vmirror_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct td043mtea1_panel *lcd = dev_get_drvdata(dev); + int val; + int ret; + + ret = kstrtoint(buf, 0, &val); + if (ret < 0) + return ret; + + lcd->vmirror = !!val; + + ret = td043mtea1_write_mirror(lcd); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t mode_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct td043mtea1_panel *lcd = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", lcd->mode); +} + +static ssize_t mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct td043mtea1_panel *lcd = dev_get_drvdata(dev); + long val; + int ret; + + ret = kstrtol(buf, 0, &val); + if (ret != 0 || val & ~7) + return -EINVAL; + + lcd->mode = val; + + val |= TPO_R02_NCLK_RISING; + td043mtea1_write(lcd, 2, val); + + return count; +} + +static ssize_t gamma_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct td043mtea1_panel *lcd = dev_get_drvdata(dev); + ssize_t len = 0; + unsigned int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(lcd->gamma); i++) { + ret = snprintf(buf + len, PAGE_SIZE - len, "%u ", + lcd->gamma[i]); + if (ret < 0) + return ret; + len += ret; + } + buf[len - 1] = '\n'; + + return len; +} + +static ssize_t gamma_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct td043mtea1_panel *lcd = dev_get_drvdata(dev); + unsigned int g[12]; + unsigned int i; + int ret; + + ret = sscanf(buf, "%u %u %u %u %u %u %u %u %u %u %u %u", + &g[0], &g[1], &g[2], &g[3], &g[4], &g[5], + &g[6], &g[7], &g[8], &g[9], &g[10], &g[11]); + if (ret != 12) + return -EINVAL; + + for (i = 0; i < 12; i++) + lcd->gamma[i] = g[i]; + + td043mtea1_write_gamma(lcd); + + return count; +} + +static DEVICE_ATTR_RW(vmirror); +static DEVICE_ATTR_RW(mode); +static DEVICE_ATTR_RW(gamma); + +static struct attribute *td043mtea1_attrs[] = { + &dev_attr_vmirror.attr, + &dev_attr_mode.attr, + &dev_attr_gamma.attr, + NULL, +}; + +static const struct attribute_group td043mtea1_attr_group = { + .attrs = td043mtea1_attrs, +}; + +/* ----------------------------------------------------------------------------- + * Panel Operations + */ + +static int td043mtea1_unprepare(struct drm_panel *panel) +{ + struct td043mtea1_panel *lcd = to_td043mtea1_device(panel); + + if (!lcd->spi_suspended) + td043mtea1_power_off(lcd); + + return 0; +} + +static int td043mtea1_prepare(struct drm_panel *panel) +{ + struct td043mtea1_panel *lcd = to_td043mtea1_device(panel); + int ret; + + /* + * If we are resuming from system suspend, SPI might not be enabled + * yet, so we'll program the LCD from SPI PM resume callback. + */ + if (lcd->spi_suspended) + return 0; + + ret = td043mtea1_power_on(lcd); + if (ret) { + dev_err(&lcd->spi->dev, "%s: power on failed (%d)\n", + __func__, ret); + return ret; + } + + return 0; +} + +static const struct drm_display_mode td043mtea1_mode = { + .clock = 36000, + .hdisplay = 800, + .hsync_start = 800 + 68, + .hsync_end = 800 + 68 + 1, + .htotal = 800 + 68 + 1 + 214, + .vdisplay = 480, + .vsync_start = 480 + 39, + .vsync_end = 480 + 39 + 1, + .vtotal = 480 + 39 + 1 + 34, + .vrefresh = 60, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + .width_mm = 94, + .height_mm = 56, +}; + +static int td043mtea1_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &td043mtea1_mode); + if (!mode) + return -ENOMEM; + + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + + connector->display_info.width_mm = td043mtea1_mode.width_mm; + connector->display_info.height_mm = td043mtea1_mode.height_mm; + /* + * FIXME: According to the datasheet sync signals are sampled on the + * rising edge of the clock, but the code running on the OMAP3 Pandora + * indicates sampling on the falling edge. This should be tested on a + * real device. + */ + connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH + | DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE + | DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE; + + return 1; +} + +static const struct drm_panel_funcs td043mtea1_funcs = { + .unprepare = td043mtea1_unprepare, + .prepare = td043mtea1_prepare, + .get_modes = td043mtea1_get_modes, +}; + +/* ----------------------------------------------------------------------------- + * Power Management, Probe and Remove + */ + +static int __maybe_unused td043mtea1_suspend(struct device *dev) +{ + struct td043mtea1_panel *lcd = dev_get_drvdata(dev); + + if (lcd->powered_on) { + td043mtea1_power_off(lcd); + lcd->powered_on = true; + } + + lcd->spi_suspended = true; + + return 0; +} + +static int __maybe_unused td043mtea1_resume(struct device *dev) +{ + struct td043mtea1_panel *lcd = dev_get_drvdata(dev); + int ret; + + lcd->spi_suspended = false; + + if (lcd->powered_on) { + lcd->powered_on = false; + ret = td043mtea1_power_on(lcd); + if (ret) + return ret; + } + + return 0; +} + +static SIMPLE_DEV_PM_OPS(td043mtea1_pm_ops, td043mtea1_suspend, + td043mtea1_resume); + +static int td043mtea1_probe(struct spi_device *spi) +{ + struct td043mtea1_panel *lcd; + int ret; + + lcd = devm_kzalloc(&spi->dev, sizeof(*lcd), GFP_KERNEL); + if (lcd == NULL) + return -ENOMEM; + + spi_set_drvdata(spi, lcd); + lcd->spi = spi; + lcd->mode = TPO_R02_MODE_800x480; + memcpy(lcd->gamma, td043mtea1_def_gamma, sizeof(lcd->gamma)); + + lcd->vcc_reg = devm_regulator_get(&spi->dev, "vcc"); + if (IS_ERR(lcd->vcc_reg)) { + dev_err(&spi->dev, "failed to get VCC regulator\n"); + return PTR_ERR(lcd->vcc_reg); + } + + lcd->reset_gpio = devm_gpiod_get(&spi->dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(lcd->reset_gpio)) { + dev_err(&spi->dev, "failed to get reset GPIO\n"); + return PTR_ERR(lcd->reset_gpio); + } + + spi->bits_per_word = 16; + spi->mode = SPI_MODE_0; + + ret = spi_setup(spi); + if (ret < 0) { + dev_err(&spi->dev, "failed to setup SPI: %d\n", ret); + return ret; + } + + ret = sysfs_create_group(&spi->dev.kobj, &td043mtea1_attr_group); + if (ret < 0) { + dev_err(&spi->dev, "failed to create sysfs files\n"); + return ret; + } + + drm_panel_init(&lcd->panel, &lcd->spi->dev, &td043mtea1_funcs, + DRM_MODE_CONNECTOR_DPI); + + ret = drm_panel_add(&lcd->panel); + if (ret < 0) { + sysfs_remove_group(&spi->dev.kobj, &td043mtea1_attr_group); + return ret; + } + + return 0; +} + +static int td043mtea1_remove(struct spi_device *spi) +{ + struct td043mtea1_panel *lcd = spi_get_drvdata(spi); + + drm_panel_remove(&lcd->panel); + drm_panel_disable(&lcd->panel); + drm_panel_unprepare(&lcd->panel); + + sysfs_remove_group(&spi->dev.kobj, &td043mtea1_attr_group); + + return 0; +} + +static const struct of_device_id td043mtea1_of_match[] = { + { .compatible = "tpo,td043mtea1", }, + { /* sentinel */ }, +}; + +MODULE_DEVICE_TABLE(of, td043mtea1_of_match); + +static const struct spi_device_id td043mtea1_ids[] = { + { "td043mtea1", 0 }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(spi, td043mtea1_ids); + +static struct spi_driver td043mtea1_driver = { + .probe = td043mtea1_probe, + .remove = td043mtea1_remove, + .id_table = td043mtea1_ids, + .driver = { + .name = "panel-tpo-td043mtea1", + .pm = &td043mtea1_pm_ops, + .of_match_table = td043mtea1_of_match, + }, +}; + +module_spi_driver(td043mtea1_driver); + +MODULE_AUTHOR("Gražvydas Ignotas <notasas@gmail.com>"); +MODULE_DESCRIPTION("TPO TD043MTEA1 Panel Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-tpo-tpg110.c b/drivers/gpu/drm/panel/panel-tpo-tpg110.c index 71591e5f5938..8472d018c16f 100644 --- a/drivers/gpu/drm/panel/panel-tpo-tpg110.c +++ b/drivers/gpu/drm/panel/panel-tpo-tpg110.c @@ -14,13 +14,13 @@ #include <drm/drm_panel.h> #include <drm/drm_print.h> -#include <linux/backlight.h> #include <linux/bitops.h> #include <linux/delay.h> #include <linux/gpio/consumer.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> +#include <linux/of.h> #include <linux/platform_device.h> #include <linux/spi/spi.h> @@ -77,10 +77,6 @@ struct tpg110 { */ struct drm_panel panel; /** - * @backlight: backlight for this panel - */ - struct backlight_device *backlight; - /** * @panel_type: the panel mode as detected */ const struct tpg110_panel_mode *panel_mode; @@ -356,8 +352,6 @@ static int tpg110_disable(struct drm_panel *panel) val &= ~TPG110_CTRL2_PM; tpg110_write_reg(tpg, TPG110_CTRL2_PM, val); - backlight_disable(tpg->backlight); - return 0; } @@ -366,8 +360,6 @@ static int tpg110_enable(struct drm_panel *panel) struct tpg110 *tpg = to_tpg110(panel); u8 val; - backlight_enable(tpg->backlight); - /* Take chip out of standby */ val = tpg110_read_reg(tpg, TPG110_CTRL2_PM); val |= TPG110_CTRL2_PM; @@ -384,9 +376,9 @@ static int tpg110_enable(struct drm_panel *panel) * presents the mode that is configured for the system under use, * and which is detected by reading the registers of the display. */ -static int tpg110_get_modes(struct drm_panel *panel) +static int tpg110_get_modes(struct drm_panel *panel, + struct drm_connector *connector) { - struct drm_connector *connector = panel->connector; struct tpg110 *tpg = to_tpg110(panel); struct drm_display_mode *mode; @@ -394,7 +386,7 @@ static int tpg110_get_modes(struct drm_panel *panel) connector->display_info.height_mm = tpg->height; connector->display_info.bus_flags = tpg->panel_mode->bus_flags; - mode = drm_mode_duplicate(panel->drm, &tpg->panel_mode->mode); + mode = drm_mode_duplicate(connector->dev, &tpg->panel_mode->mode); drm_mode_set_name(mode); mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; @@ -432,11 +424,6 @@ static int tpg110_probe(struct spi_device *spi) if (ret) DRM_DEV_ERROR(dev, "no panel height specified\n"); - /* Look for some optional backlight */ - tpg->backlight = devm_of_find_backlight(dev); - if (IS_ERR(tpg->backlight)) - return PTR_ERR(tpg->backlight); - /* This asserts the GRESTB signal, putting the display into reset */ tpg->grestb = devm_gpiod_get(dev, "grestb", GPIOD_OUT_HIGH); if (IS_ERR(tpg->grestb)) { @@ -457,9 +444,13 @@ static int tpg110_probe(struct spi_device *spi) if (ret) return ret; - drm_panel_init(&tpg->panel); - tpg->panel.dev = dev; - tpg->panel.funcs = &tpg110_drm_funcs; + drm_panel_init(&tpg->panel, dev, &tpg110_drm_funcs, + DRM_MODE_CONNECTOR_DPI); + + ret = drm_panel_of_backlight(&tpg->panel); + if (ret) + return ret; + spi_set_drvdata(spi, tpg); return drm_panel_add(&tpg->panel); diff --git a/drivers/gpu/drm/panel/panel-truly-nt35597.c b/drivers/gpu/drm/panel/panel-truly-nt35597.c index 77e1311b7c69..012ca62bf30e 100644 --- a/drivers/gpu/drm/panel/panel-truly-nt35597.c +++ b/drivers/gpu/drm/panel/panel-truly-nt35597.c @@ -454,9 +454,9 @@ static int truly_nt35597_enable(struct drm_panel *panel) return 0; } -static int truly_nt35597_get_modes(struct drm_panel *panel) +static int truly_nt35597_get_modes(struct drm_panel *panel, + struct drm_connector *connector) { - struct drm_connector *connector = panel->connector; struct truly_nt35597 *ctx = panel_to_ctx(panel); struct drm_display_mode *mode; const struct nt35597_config *config; @@ -518,9 +518,8 @@ static int truly_nt35597_panel_add(struct truly_nt35597 *ctx) /* dual port */ gpiod_set_value(ctx->mode_gpio, 0); - drm_panel_init(&ctx->panel); - ctx->panel.dev = dev; - ctx->panel.funcs = &truly_nt35597_drm_funcs; + drm_panel_init(&ctx->panel, dev, &truly_nt35597_drm_funcs, + DRM_MODE_CONNECTOR_DSI); drm_panel_add(&ctx->panel); return 0; 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"); |