diff options
Diffstat (limited to 'drivers/gpu/drm/panel')
124 files changed, 57277 insertions, 2502 deletions
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index 3f3537719beb..76f6af819037 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only config DRM_PANEL bool depends on DRM @@ -7,6 +8,15 @@ config DRM_PANEL menu "Display Panels" depends on DRM && DRM_PANEL +config DRM_PANEL_ABT_Y030XX067A + tristate "ABT Y030XX067A 320x480 LCD panel" + depends on OF && SPI + select REGMAP_SPI + help + Say Y here to enable support for the Asia Better Technology Ltd. + Y030XX067A 320x480 3.0" panel as found in the YLM RG-280M, RG-300 + and RG-99 handheld gaming consoles. + config DRM_PANEL_ARM_VERSATILE tristate "ARM Versatile panel driver" depends on OF @@ -17,6 +27,132 @@ config DRM_PANEL_ARM_VERSATILE reference designs. The panel is detected using special registers in the Versatile family syscon registers. +config DRM_PANEL_ASUS_Z00T_TM5P5_NT35596 + tristate "ASUS Z00T TM5P5 NT35596 panel" + depends on GPIOLIB && OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y here if you want to enable support for the ASUS TMP5P5 + NT35596 1080x1920 video mode panel as found in some Asus + Zenfone 2 Laser Z00T devices. + +config DRM_PANEL_AUO_A030JTN01 + tristate "AUO A030JTN01" + depends on SPI + select REGMAP_SPI + help + Say Y here to enable support for the AUO A030JTN01 320x480 3.0" panel + as found in the YLM RS-97 handheld gaming console. + +config DRM_PANEL_BOE_BF060Y8M_AJ0 + tristate "Boe BF060Y8M-AJ0 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 BF060Y8M-AJ0 + 5.99" AMOLED modules. The panel has a 1080x2160 resolution and + uses 24 bit RGB per pixel. It provides a MIPI DSI interface to + the host and backlight is controlled through DSI commands. + +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_BOE_TD4320 + tristate "BOE TD4320 DSI 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 TD4320 1080x2340 + video mode panel found in Xiaomi Redmi Note 7 smartphones. + +config DRM_PANEL_BOE_TH101MB31UIG002_28A + tristate "Boe TH101MB31UIG002-28A 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 + TH101MB31UIG002-28A TFT-LCD modules. The panel has a 800x1280 + 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_BOE_TV101WUM_NL6 + tristate "BOE TV101WUM and AUO KD101N80 45NA 1200x1920 panel" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y here if you want to support for BOE TV101WUM and AUO KD101N80 + 45NA WUXGA PANEL DSI Video Mode panel + +config DRM_PANEL_BOE_TV101WUM_LL2 + tristate "BOE TV101WUM LL2 1200x1920 panel" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y here if you want to support for BOE TV101WUM-LL2 + WUXGA PANEL DSI Video Mode panel + +config DRM_PANEL_EBBG_FT8719 + tristate "EBBG FT8719 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 EBBG FT8719 + video mode panel. Mainly found on Xiaomi Poco F1 mobile phone. + The panel has a resolution of 1080x2246. It provides a MIPI DSI + interface to the host. + +config DRM_PANEL_ELIDA_KD35T133 + tristate "Elida KD35T133 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 Elida + KD35T133 controller for 320x480 LCD panels with MIPI-DSI + system interfaces. + +config DRM_PANEL_FEIXIN_K101_IM2BA02 + tristate "Feixin K101 IM2BA02 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 the Feixin K101 IM2BA02 + 4-lane 800x1280 MIPI DSI panel. + +config DRM_PANEL_FEIYANG_FY07024DI26A30D + tristate "Feiyang FY07024DI26A30-D MIPI-DSI LCD panel" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y if you want to enable support for panels based on the + Feiyang FY07024DI26A30-D MIPI-DSI interface. + +config DRM_PANEL_DSI_CM + tristate "Generic DSI command mode panels" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + DRM panel driver for DSI command mode panels with support for + embedded and external backlights. + config DRM_PANEL_LVDS tristate "Generic LVDS panel driver" depends on OF @@ -27,16 +163,70 @@ config DRM_PANEL_LVDS handling of power supplies or control signals. It implements automatic backlight handling if the panel is attached to a backlight controller. -config DRM_PANEL_SIMPLE - tristate "support for simple panels" +config DRM_PANEL_HIMAX_HX8279 + tristate "Himax HX8279-based panels" depends on OF + depends on DRM_MIPI_DSI depends on BACKLIGHT_CLASS_DEVICE - select VIDEOMODE_HELPERS help - DRM panel driver for dumb panels that need at most a regulator and - a GPIO to be powered up. Optionally a backlight can be attached so - that it can be automatically turned off when the panel goes into a - low power state. + Say Y if you want to enable support for panels based on the + Himax HX8279 controller, such as the Startek KD070FHFID078 + 7.0" 1200x1920 IPS LCD panel that uses a MIPI-DSI interface + and others. + +config DRM_PANEL_HIMAX_HX83102 + tristate "Himax HX83102-based panels" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y if you want to enable support for panels based on the + Himax HX83102 controller. + +config DRM_PANEL_HIMAX_HX83112A + tristate "Himax HX83112A-based DSI panel" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + select DRM_KMS_HELPER + help + Say Y here if you want to enable support for Himax HX83112A-based + display panels, such as the one found in the Fairphone 4 smartphone. + +config DRM_PANEL_HIMAX_HX83112B + tristate "Himax HX83112B-based DSI panel" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + select DRM_KMS_HELPER + help + Say Y here if you want to enable support for Himax HX83112B-based + display panels, such as the one found in the Fairphone 3 smartphone. + +config DRM_PANEL_HIMAX_HX8394 + tristate "HIMAX HX8394 MIPI-DSI LCD panels" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y if you want to enable support for panels based on the + Himax HX8394 controller, such as the HannStar HSD060BHW4 + 720x1440 TFT LCD panel that uses a MIPI-DSI interface. + + If M is selected the module will be called panel-himax-hx8394. + +config DRM_PANEL_HYDIS_HV101HD1 + tristate "Hydis HV101HD1 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 the Hydis HV101HD1 + 2-lane 1366x768 MIPI DSI panel found in ASUS VivoTab RT TF600T. + HV101HD1 is a color active matrix TFT LCD module using amorphous + silicon TFT's (Thin Film Transistors) as an active switching devices. + + If M is selected the module will be called panel-hydis-hv101hd1 config DRM_PANEL_ILITEK_IL9322 tristate "Ilitek ILI9322 320x240 QVGA panels" @@ -46,6 +236,36 @@ config DRM_PANEL_ILITEK_IL9322 Say Y here if you want to enable support for Ilitek IL9322 QVGA (320x240) RGB, YUV and ITU-T BT.656 panels. +config DRM_PANEL_ILITEK_ILI9341 + tristate "Ilitek ILI9341 240x320 QVGA panels" + depends on SPI + select DRM_KMS_HELPER + select DRM_GEM_DMA_HELPER + depends on BACKLIGHT_CLASS_DEVICE + select DRM_MIPI_DBI + help + Say Y here if you want to enable support for Ilitek IL9341 + QVGA (240x320) RGB panels. support serial & parallel rgb + interface. + +config DRM_PANEL_ILITEK_ILI9805 + tristate "Ilitek ILI9805-based panels" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y if you want to enable support for panels based on the + Ilitek ILI9805 controller. + +config DRM_PANEL_ILITEK_ILI9806E + tristate "Ilitek ILI9806E-based panels" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y if you want to enable support for panels based on the + Ilitek ILI9806E controller. + config DRM_PANEL_ILITEK_ILI9881C tristate "Ilitek ILI9881C-based panels" depends on OF @@ -55,6 +275,24 @@ config DRM_PANEL_ILITEK_ILI9881C Say Y if you want to enable support for panels based on the Ilitek ILI9881c controller. +config DRM_PANEL_ILITEK_ILI9882T + tristate "Ilitek ILI9882t-based panels" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y if you want to enable support for panels based on the + Ilitek ILI9882t controller. + +config DRM_PANEL_INNOLUX_EJ030NA + tristate "Innolux EJ030NA 320x480 LCD panel" + depends on OF && SPI + select REGMAP_SPI + help + Say Y here to enable support for the Innolux/Chimei EJ030NA + 320x480 3.0" panel as found in the RS97 V2.1, RG300(non-ips) + and LDK handheld gaming consoles. + config DRM_PANEL_INNOLUX_P079ZCA tristate "Innolux P079ZCA panel" depends on OF @@ -66,6 +304,27 @@ config DRM_PANEL_INNOLUX_P079ZCA 24 bit RGB per pixel. It provides a MIPI DSI interface to the host and has a built-in LED backlight. +config DRM_PANEL_JADARD_JD9365DA_H3 + tristate "Jadard JD9365DA-H3 WXGA DSI 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 Jadard JD9365DA-H3 + WXGA MIPI DSI panel. The panel support TFT dot matrix LCD with + 800RGBx1280 dots at maximum. + +config DRM_PANEL_JDI_LPM102A188A + tristate "JDI LPM102A188A DSI panel" + depends on OF && GPIOLIB + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y here if you want to enable support for JDI LPM102A188A DSI + command mode panel as found in Google Pixel C devices. + The panel has a 2560×1800 resolution. It provides a MIPI DSI interface + to the host. + config DRM_PANEL_JDI_LT070ME05000 tristate "JDI LT070ME05000 WUXGA DSI panel" depends on OF @@ -77,10 +336,90 @@ config DRM_PANEL_JDI_LT070ME05000 The panel has a 1200(RGB)×1920 (WUXGA) resolution and uses 24 bit per pixel. -config DRM_PANEL_SAMSUNG_LD9040 - tristate "Samsung LD9040 RGB/SPI panel" - depends on OF && SPI +config DRM_PANEL_JDI_R63452 + tristate "JDI R63452 Full HD DSI 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 the JDI R63452 + DSI command mode panel as found in Xiaomi Mi 5 Devices. + +config DRM_PANEL_KHADAS_TS050 + tristate "Khadas TS050 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 Khadas TS050 TFT-LCD + panel module. The panel has a 1080x1920 resolution and uses + 24 bit RGB per pixel. It provides a MIPI DSI interface to + the host, a built-in LED backlight and touch controller. + +config DRM_PANEL_KINGDISPLAY_KD097D04 + tristate "Kingdisplay kd097d04 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_LEADTEK_LTK050H3146W + tristate "Leadtek LTK050H3146W 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 Leadtek LTK050H3146W + TFT-LCD modules. The panel has a 720x1280 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_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_LINCOLNTECH_LCD197 + tristate "Lincoln Technologies lcd197 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 lincolntech lcd197 + TFT-LCD modules. The panel has a 1080x1920 resolution and uses + 24 bit RGB per pixel. It provides a MIPI DSI interface to + the host. + +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_LD070WX3 + tristate "LG LD070WX3 MIPI DSI 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 support for the LD070WX3 MIPI DSI + panel found in the NVIDIA Tegra Note 7 tablet. + + To compile this driver as a module, choose M here: the module will + be called panel-lg-ld070wx3. config DRM_PANEL_LG_LG4573 tristate "LG4573 RGB/SPI panel" @@ -90,11 +429,157 @@ 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_LG_SW43408 + tristate "LG SW43408 panel" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + select DRM_DISPLAY_DSC_HELPER + select DRM_DISPLAY_HELPER + help + Say Y here if you want to enable support for LG sw43408 panel. + The panel has a 1080x2160@60Hz 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_MAGNACHIP_D53E6EA8966 + tristate "Magnachip D53E6EA8966 DSI panel" + depends on OF && SPI + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + select DRM_MIPI_DBI + help + DRM panel driver for the Samsung AMS495QA01 panel controlled + with the Magnachip D53E6EA8966 panel IC. This panel receives + video data via DSI but commands via 9-bit SPI using DBI. + +config DRM_PANEL_MANTIX_MLAF057WE51 + tristate "Mantix MLAF057WE51-X MIPI-DSI LCD 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 the Mantix + MLAF057WE51-X MIPI DSI panel as e.g. used in the Librem 5. It + has a resolution of 720x1440 pixels, a built in backlight and touch + controller. + +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_NEWVISION_NV3051D + tristate "NewVision NV3051D DSI panel" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + This driver supports the NV3051D based panel found on the Anbernic + RG353P and RG353V. + +config DRM_PANEL_NEWVISION_NV3052C + tristate "NewVision NV3052C RGB/SPI panel" + depends on OF && SPI + depends on BACKLIGHT_CLASS_DEVICE + select DRM_MIPI_DBI + help + Say Y here if you want to enable support for the panels built + around the NewVision NV3052C display controller. + +config DRM_PANEL_NOVATEK_NT35510 + tristate "Novatek NT35510 RGB 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 panels built + around the Novatek NT35510 display controller, such as some + Hydis panels. + +config DRM_PANEL_NOVATEK_NT35560 + tristate "Novatek NT35560 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 Novatek NT35560 display + controller. This panel supports DSI in both command and video + mode. This supports several panels such as Sony ACX424AKM and + ACX424AKP. + +config DRM_PANEL_NOVATEK_NT35950 + tristate "Novatek NT35950 DSI 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 the panels built + around the Novatek NT35950 display controller, such as some + Sharp panels used in Sony Xperia Z5 Premium and XZ Premium + mobile phones. + +config DRM_PANEL_NOVATEK_NT36523 + tristate "Novatek NT36523 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 panels built + around the Novatek NT36523 display controller, such as some + Boe panels used in Xiaomi Mi Pad 5 and 5 Pro tablets. + +config DRM_PANEL_NOVATEK_NT36672A + tristate "Novatek NT36672A DSI 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 the panels built + around the Novatek NT36672A display controller, such as some + Tianma panels used in a few Xiaomi Poco F1 mobile phones. + +config DRM_PANEL_NOVATEK_NT36672E + tristate "Novatek NT36672E DSI 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 Novatek NT36672E DSI Video Mode + LCD panel module. The panel has a resolution of 1080x2408 and uses 24 bit + RGB per pixel. + +config DRM_PANEL_NOVATEK_NT37801 + tristate "Novatek NT37801/NT37810 AMOLED DSI panel" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + select DRM_DISPLAY_DSC_HELPER + select DRM_DISPLAY_HELPER + help + Say Y here if you want to enable support for Novatek NT37801 (or + NT37810) AMOLED DSI Video Mode LCD panel module with 1440x3200 + resolution. + +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 depends on I2C depends on BACKLIGHT_CLASS_DEVICE + select CRC32 help The panel is used with different sizes LCDs, from 480x272 to 1280x800, and 24 bit per pixel. @@ -102,6 +587,15 @@ config DRM_PANEL_OLIMEX_LCD_OLINUXINO Say Y here if you want to enable support for Olimex Ltd. LCD-OLinuXino panel. +config DRM_PANEL_ORISETECH_OTA5601A + tristate "Orise Technology ota5601a RGB/SPI panel" + depends on 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 Orise Technology OTA9601A display controller. + config DRM_PANEL_ORISETECH_OTM8009A tristate "Orise Technology otm8009a 480x800 dsi 2dl panel" depends on OF @@ -111,6 +605,15 @@ config DRM_PANEL_ORISETECH_OTM8009A Say Y here if you want to enable support for Orise Technology otm8009a 480x800 dsi 2dl panel. +config DRM_PANEL_OSD_OSD101T2587_53TS + tristate "OSD OSD101T2587-53TS DSI 1920x1200 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 One Stop Displays + OSD101T2587-53TS 10.1" 1920x1200 dsi panel. + config DRM_PANEL_PANASONIC_VVX10F034N00 tristate "Panasonic VVX10F034N00 1920x1200 video mode panel" depends on OF @@ -129,6 +632,25 @@ 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_RM67200 + tristate "Raydium RM67200-based DSI panel" + depends on OF + depends on DRM_MIPI_DSI + help + Say Y here if you want to enable support for Raydium RM67200-based + DSI video mode panels. This panel controller can be found in the + Wanchanglong W552793BAA panel found on the Rockchip RK3588 EVB1 + evaluation boards. + config DRM_PANEL_RAYDIUM_RM68200 tristate "Raydium RM68200 720x1280 DSI video mode panel" depends on OF @@ -138,12 +660,173 @@ config DRM_PANEL_RAYDIUM_RM68200 Say Y here if you want to enable support for Raydium RM68200 720x1280 DSI video mode panel. +config DRM_PANEL_RAYDIUM_RM692E5 + tristate "Raydium RM692E5-based DSI panel" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + select DRM_DISPLAY_DSC_HELPER + select DRM_DISPLAY_HELPER + help + Say Y here if you want to enable support for Raydium RM692E5-based + display panels, such as the one found in the Fairphone 5 smartphone. + +config DRM_PANEL_RAYDIUM_RM69380 + tristate "Raydium RM69380-based DSI panel" + depends on OF && GPIOLIB + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y here if you want to enable support for Raydium RM69380-based + display panels. + + This panel controller can be found in the Lenovo Xiaoxin Pad Pro 2021 + in combination with an EDO OLED panel. + +config DRM_PANEL_RENESAS_R61307 + tristate "Renesas R61307 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 KOE tx13d100vm0eaa + IPS-LCD module with Renesas R69328 IC. The panel has a 1024x768 + resolution and uses 24 bit RGB per pixel. + + This panel controller can be found in LG Optimus Vu P895 smartphone + in combination with LCD panel. + +config DRM_PANEL_RENESAS_R69328 + tristate "Renesas R69328 720x1280 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 JDI dx12d100vm0eaa + IPS-LCD module with Renesas R69328 IC. The panel has a 720x1280 + resolution and uses 24 bit RGB per pixel. + + This panel controller can be found in LG Optimus 4X P895 smartphone + in combination with LCD panel. + +config DRM_PANEL_RONBO_RB070D30 + tristate "Ronbo Electronics RB070D30 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 Ronbo Electronics + RB070D30 1024x600 DSI panel. + +config DRM_PANEL_SAMSUNG_AMS581VF01 + tristate "Samsung AMS581VF01 panel" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y or M here if you want to enable support for the + Samsung AMS581VF01 FHD Plus (2340x1080@60Hz) CMD mode panel. + +config DRM_PANEL_SAMSUNG_AMS639RQ08 + tristate "Samsung AMS639RQ08 panel" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y or M here if you want to enable support for the + Samsung AMS639RQ08 FHD Plus (2340x1080@60Hz) CMD mode panel. + +config DRM_PANEL_SAMSUNG_S6E88A0_AMS427AP24 + tristate "Samsung AMS427AP24 panel with S6E88A0 controller" + depends on GPIOLIB && OF && REGULATOR + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y here if you want to enable support for Samsung AMS427AP24 panel + with S6E88A0 controller (found in Samsung Galaxy S4 Mini Value Edition + GT-I9195I). To compile this driver as a module, choose M here. + +config DRM_PANEL_SAMSUNG_S6E88A0_AMS452EF01 + tristate "Samsung AMS452EF01 panel with S6E88A0 DSI video mode controller" + depends on OF + select DRM_MIPI_DSI + select VIDEOMODE_HELPERS + +config DRM_PANEL_SAMSUNG_ATNA33XC20 + tristate "Samsung ATNA33XC20 eDP panel" + depends on OF + depends on BACKLIGHT_CLASS_DEVICE + depends on PM + select DRM_DISPLAY_DP_HELPER + select DRM_DISPLAY_HELPER + select DRM_DISPLAY_DP_AUX_BUS + help + DRM panel driver for the Samsung ATNA33XC20 panel. This panel can't + be handled by the DRM_PANEL_SIMPLE driver because its power + sequencing is non-standard. + +config DRM_PANEL_SAMSUNG_DB7430 + tristate "Samsung DB7430-based DPI panels" + depends on OF && SPI && GPIOLIB + depends on BACKLIGHT_CLASS_DEVICE + select DRM_MIPI_DBI + help + Say Y here if you want to enable support for the Samsung + DB7430 DPI display controller used in such devices as the + LMS397KF04 480x800 DPI panel. + +config DRM_PANEL_SAMSUNG_LD9040 + tristate "Samsung LD9040 RGB/SPI panel" + depends on OF && SPI + depends on BACKLIGHT_CLASS_DEVICE + select VIDEOMODE_HELPERS + +config DRM_PANEL_SAMSUNG_S6E3FA7 + tristate "Samsung S6E3FA7 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 Samsung S6E3FA7 + 1920x2220 panel. + config DRM_PANEL_SAMSUNG_S6D16D0 tristate "Samsung S6D16D0 DSI video mode panel" depends on OF depends on DRM_MIPI_DSI select VIDEOMODE_HELPERS +config DRM_PANEL_SAMSUNG_S6D27A1 + tristate "Samsung S6D27A1 DPI panel driver" + depends on OF && SPI && GPIOLIB + select DRM_MIPI_DBI + help + Say Y here if you want to enable support for the Samsung + S6D27A1 DPI 480x800 panel. + + This panel can be found in Samsung Galaxy Ace 2 + GT-I8160 mobile phone. + +config DRM_PANEL_SAMSUNG_S6D7AA0 + tristate "Samsung S6D7AA0 MIPI-DSI video mode panel controller" + depends on OF + depends on BACKLIGHT_CLASS_DEVICE + select DRM_MIPI_DSI + select VIDEOMODE_HELPERS + +config DRM_PANEL_SAMSUNG_S6E3FC2X01 + tristate "Samsung S6E3FC2X01 DSI panel controller" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + select VIDEOMODE_HELPERS + help + Say Y or M here if you want to enable support for the + Samsung S6E3FC2 DDIC and connected MIPI DSI panel. + Currently supported panels: + + Samsung AMS641RW (found in the OnePlus 6T smartphone) + config DRM_PANEL_SAMSUNG_S6E3HA2 tristate "Samsung S6E3HA2 DSI video mode panel" depends on OF @@ -151,6 +834,14 @@ config DRM_PANEL_SAMSUNG_S6E3HA2 depends on BACKLIGHT_CLASS_DEVICE select VIDEOMODE_HELPERS +config DRM_PANEL_SAMSUNG_S6E3HA8 + tristate "Samsung S6E3HA8 DSI video mode panel" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + select DRM_DISPLAY_DSC_HELPER + select VIDEOMODE_HELPERS + config DRM_PANEL_SAMSUNG_S6E63J0X03 tristate "Samsung S6E63J0X03 DSI command mode panel" depends on OF @@ -158,12 +849,63 @@ config DRM_PANEL_SAMSUNG_S6E63J0X03 depends on BACKLIGHT_CLASS_DEVICE select VIDEOMODE_HELPERS +config DRM_PANEL_SAMSUNG_S6E63M0 + tristate "Samsung S6E63M0 RGB panel" + depends on OF + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y here if you want to enable support for Samsung S6E63M0 + AMOLED LCD panel. This panel can be accessed using SPI or + DSI. + +config DRM_PANEL_SAMSUNG_S6E63M0_SPI + tristate "Samsung S6E63M0 RGB SPI interface" + depends on SPI + depends on DRM_PANEL_SAMSUNG_S6E63M0 + default DRM_PANEL_SAMSUNG_S6E63M0 + select DRM_MIPI_DBI + help + Say Y here if you want to be able to access the Samsung + S6E63M0 panel using SPI. + +config DRM_PANEL_SAMSUNG_S6E63M0_DSI + tristate "Samsung S6E63M0 RGB DSI interface" + depends on DRM_MIPI_DSI + depends on DRM_PANEL_SAMSUNG_S6E63M0 + help + Say Y here if you want to be able to access the Samsung + S6E63M0 panel using DSI. + config DRM_PANEL_SAMSUNG_S6E8AA0 tristate "Samsung S6E8AA0 DSI video mode panel" depends on OF select DRM_MIPI_DSI select VIDEOMODE_HELPERS +config DRM_PANEL_SAMSUNG_S6E8AA5X01_AMS561RA01 + tristate "Samsung AMS561RA01 panel with S6E8AA5X01 controller" + depends on GPIOLIB && OF && REGULATOR + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y here if you want to enable support for Samsung AMS561RA01 + panel, which uses Samsung's S6E8AA5X01 controller. The panel has a + ~5.6 inch AMOLED display, and the controller is driven by the MIPI + DSI protocol with 4 lanes. + +config DRM_PANEL_SAMSUNG_SOFEF00 + tristate "Samsung SOFEF00 DSI panel controller" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + select VIDEOMODE_HELPERS + help + Say Y or M here if you want to enable support for the Samsung AMOLED + panel SOFEF00 DDIC and connected panel. + Currently supported panels: + + Samsung AMS628NW01 (found in OnePlus 6, 1080x2280@60Hz) + config DRM_PANEL_SEIKO_43WVF1G tristate "Seiko 43WVF1G panel" depends on OF @@ -173,6 +915,21 @@ config DRM_PANEL_SEIKO_43WVF1G Say Y here if you want to enable support for the Seiko 43WVF1G controller for 800x480 LCD panels +config DRM_PANEL_SHARP_LQ079L1SX01 + tristate "Sharp LQ079L1SX01 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 support for Sharp LQ079L1SX01 + TFT-LCD modules. The panel has a 2560x1600 resolution and uses + 24 bit RGB per pixel. It provides a dual MIPI DSI interface to + the host. + + To compile this driver as a module, choose M here: the module + will be called panel-sharp-lq079l1sx01. + config DRM_PANEL_SHARP_LQ101R1SX01 tristate "Sharp LQ101R1SX01 panel" depends on OF @@ -187,6 +944,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 @@ -196,6 +960,40 @@ config DRM_PANEL_SHARP_LS043T1LE01 Say Y here if you want to enable support for Sharp LS043T1LE01 qHD (540x960) DSI panel as found on the Qualcomm APQ8074 Dragonboard +config DRM_PANEL_SHARP_LS060T1SX01 + tristate "Sharp LS060T1SX01 FullHD 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 Sharp LS060T1SX01 6.0" + FullHD (1080x1920) DSI panel as found in Dragonboard Display Adapter + Bundle. + +config DRM_PANEL_SITRONIX_ST7701 + tristate "Sitronix ST7701 panel driver" + depends on OF + depends on SPI || DRM_MIPI_DSI + select DRM_MIPI_DBI if SPI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y here if you want to enable support for the Sitronix + ST7701 controller for 480X864 LCD panels with MIPI/RGB/SPI + system interfaces. + +config DRM_PANEL_SITRONIX_ST7703 + tristate "Sitronix ST7703 based MIPI touchscreen panels" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y here if you want to enable support for Sitronix ST7703 based + panels, souch as Rocktech JH057N00900 MIPI DSI panel as e.g. used in + the Librem 5 devkit. It has a resolution of 720x1440 pixels, a built + in backlight and touch controller. + Touch input support is provided by the goodix driver and needs to be + selected separately. + config DRM_PANEL_SITRONIX_ST7789V tristate "Sitronix ST7789V panel" depends on OF && SPI @@ -204,6 +1002,137 @@ 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_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_SONY_TD4353_JDI + tristate "Sony TD4353 JDI panel" + depends on GPIOLIB && OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y here if you want to enable support for the Sony Tama + TD4353 JDI command mode panel as found on some Sony Xperia + XZ2 and XZ2 Compact smartphones. + +config DRM_PANEL_SONY_TULIP_TRULY_NT35521 + tristate "Sony Tulip Truly NT35521 panel" + depends on GPIOLIB && OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y here if you want to enable support for the Sony Tulip + NT35521 1280x720 video mode panel as found on Sony Xperia M4 + Aqua phone. + +config DRM_PANEL_STARTEK_KD070FHFID015 + tristate "STARTEK KD070FHFID015 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 STARTEK KD070FHFID015 DSI panel + based on RENESAS-R69429 controller. The panel is a 7-inch TFT LCD display + with a resolution of 1024 x 600 pixels. It provides a MIPI DSI interface to + the host, a built-in LED backlight and touch controller. + +config DRM_PANEL_EDP + tristate "support for simple Embedded DisplayPort panels" + depends on OF + depends on BACKLIGHT_CLASS_DEVICE + depends on PM + select VIDEOMODE_HELPERS + select DRM_DISPLAY_DP_HELPER + select DRM_DISPLAY_HELPER + select DRM_DISPLAY_DP_AUX_BUS + select DRM_KMS_HELPER + help + DRM panel driver for dumb eDP panels that need at most a regulator and + a GPIO to be powered up. Optionally a backlight can be attached so + that it can be automatically turned off when the panel goes into a + low power state. + +config DRM_PANEL_SIMPLE + tristate "support for simple panels (other than eDP ones)" + depends on OF + depends on BACKLIGHT_CLASS_DEVICE + depends on PM + select VIDEOMODE_HELPERS + help + DRM panel driver for dumb non-eDP panels that need at most a regulator + and a GPIO to be powered up. Optionally a backlight can be attached so + that it can be automatically turned off when the panel goes into a + low power state. + +config DRM_PANEL_SUMMIT + tristate "Apple Summit display panel" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y if you want to enable support for the "Summit" display panel + used as a touchbar on certain Apple laptops. + +config DRM_PANEL_SYNAPTICS_R63353 + tristate "Synaptics R63353-based panels" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y if you want to enable support for panels based on the + Synaptics R63353 controller. + +config DRM_PANEL_SYNAPTICS_TDDI + tristate "Synaptics TDDI display panels" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y if you want to enable support for the Synaptics TDDI display + panels. There are multiple MIPI DSI panels manufactured under the TDDI + namesake, with varying resolutions and data lanes. They also have a + built-in LED backlight and a touch controller. + +config DRM_PANEL_TDO_TL070WSH30 + tristate "TDO TL070WSH30 DSI 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 TDO TL070WSH30 TFT-LCD + panel module. The panel has a 1024×600 resolution and uses + 24 bit RGB per pixel. It provides a MIPI DSI interface to + the host, a built-in LED backlight and touch controller. + +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 + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y here if you want to enable support for TPO TPG110 + 400CH LTPS TFT LCD Single Chip Digital Driver for up to + 800x400 LCD panels. + config DRM_PANEL_TRULY_NT35597_WQXGA tristate "Truly WQXGA" depends on OF @@ -211,4 +1140,73 @@ 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_VISIONOX_G2647FB105 + tristate "Visionox G2647FB105" + 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 Visionox + G2647FB105 (2340x1080@60Hz) AMOLED DSI cmd mode panel. + +config DRM_PANEL_VISIONOX_R66451 + tristate "Visionox R66451" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + select DRM_DISPLAY_DSC_HELPER + select DRM_DISPLAY_HELPER + help + Say Y here if you want to enable support for Visionox + R66451 1080x2340 AMOLED DSI panel. + +config DRM_PANEL_VISIONOX_RM69299 + tristate "Visionox RM69299" + depends on OF + depends on DRM_MIPI_DSI + help + Say Y here if you want to enable support for Visionox + RM69299 DSI Video Mode panel. + +config DRM_PANEL_VISIONOX_RM692E5 + tristate "Visionox RM692E5" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + select DRM_DISPLAY_DSC_HELPER + select DRM_DISPLAY_HELPER + help + Say Y here if you want to enable support for Visionox RM692E5 amoled + display panels, such as the one found in the Nothing Phone (1) + smartphone. + +config DRM_PANEL_VISIONOX_VTDR6130 + tristate "Visionox VTDR6130" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y here if you want to enable support for Visionox + VTDR6130 1080x2400 AMOLED DSI panel. + +config DRM_PANEL_WIDECHIPS_WS2401 + tristate "Widechips WS2401 DPI panel driver" + depends on SPI && GPIOLIB + depends on BACKLIGHT_CLASS_DEVICE + select DRM_MIPI_DBI + help + Say Y here if you want to enable support for the Widechips WS2401 DPI + 480x800 display controller used in panels such as Samsung LMS380KF01. + This display is used in the Samsung Galaxy Ace 2 GT-I8160 (Codina). + +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 4396658a7996..b9562a6fdcb3 100644 --- a/drivers/gpu/drm/panel/Makefile +++ b/drivers/gpu/drm/panel/Makefile @@ -1,24 +1,122 @@ # SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_DRM_PANEL_ABT_Y030XX067A) += panel-abt-y030xx067a.o obj-$(CONFIG_DRM_PANEL_ARM_VERSATILE) += panel-arm-versatile.o +obj-$(CONFIG_DRM_PANEL_ASUS_Z00T_TM5P5_NT35596) += panel-asus-z00t-tm5p5-n35596.o +obj-$(CONFIG_DRM_PANEL_AUO_A030JTN01) += panel-auo-a030jtn01.o +obj-$(CONFIG_DRM_PANEL_BOE_BF060Y8M_AJ0) += panel-boe-bf060y8m-aj0.o +obj-$(CONFIG_DRM_PANEL_BOE_HIMAX8279D) += panel-boe-himax8279d.o +obj-$(CONFIG_DRM_PANEL_BOE_TD4320) += panel-boe-td4320.o +obj-$(CONFIG_DRM_PANEL_BOE_TH101MB31UIG002_28A) += panel-boe-th101mb31ig002-28a.o +obj-$(CONFIG_DRM_PANEL_BOE_TV101WUM_LL2) += panel-boe-tv101wum-ll2.o +obj-$(CONFIG_DRM_PANEL_BOE_TV101WUM_NL6) += panel-boe-tv101wum-nl6.o +obj-$(CONFIG_DRM_PANEL_DSI_CM) += panel-dsi-cm.o obj-$(CONFIG_DRM_PANEL_LVDS) += panel-lvds.o obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.o +obj-$(CONFIG_DRM_PANEL_EDP) += panel-edp.o +obj-$(CONFIG_DRM_PANEL_EBBG_FT8719) += panel-ebbg-ft8719.o +obj-$(CONFIG_DRM_PANEL_ELIDA_KD35T133) += panel-elida-kd35t133.o +obj-$(CONFIG_DRM_PANEL_FEIXIN_K101_IM2BA02) += panel-feixin-k101-im2ba02.o +obj-$(CONFIG_DRM_PANEL_FEIYANG_FY07024DI26A30D) += panel-feiyang-fy07024di26a30d.o +obj-$(CONFIG_DRM_PANEL_HIMAX_HX8279) += panel-himax-hx8279.o +obj-$(CONFIG_DRM_PANEL_HIMAX_HX83102) += panel-himax-hx83102.o +obj-$(CONFIG_DRM_PANEL_HIMAX_HX83112A) += panel-himax-hx83112a.o +obj-$(CONFIG_DRM_PANEL_HIMAX_HX83112B) += panel-himax-hx83112b.o +obj-$(CONFIG_DRM_PANEL_HIMAX_HX8394) += panel-himax-hx8394.o +obj-$(CONFIG_DRM_PANEL_HYDIS_HV101HD1) += panel-hydis-hv101hd1.o obj-$(CONFIG_DRM_PANEL_ILITEK_IL9322) += panel-ilitek-ili9322.o +obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9341) += panel-ilitek-ili9341.o +obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9805) += panel-ilitek-ili9805.o +obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9806E) += panel-ilitek-ili9806e.o obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9881C) += panel-ilitek-ili9881c.o +obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9882T) += panel-ilitek-ili9882t.o +obj-$(CONFIG_DRM_PANEL_INNOLUX_EJ030NA) += panel-innolux-ej030na.o obj-$(CONFIG_DRM_PANEL_INNOLUX_P079ZCA) += panel-innolux-p079zca.o +obj-$(CONFIG_DRM_PANEL_JADARD_JD9365DA_H3) += panel-jadard-jd9365da-h3.o obj-$(CONFIG_DRM_PANEL_JDI_LT070ME05000) += panel-jdi-lt070me05000.o +obj-$(CONFIG_DRM_PANEL_JDI_LPM102A188A) += panel-jdi-lpm102a188a.o +obj-$(CONFIG_DRM_PANEL_JDI_R63452) += panel-jdi-fhd-r63452.o +obj-$(CONFIG_DRM_PANEL_KHADAS_TS050) += panel-khadas-ts050.o +obj-$(CONFIG_DRM_PANEL_KINGDISPLAY_KD097D04) += panel-kingdisplay-kd097d04.o +obj-$(CONFIG_DRM_PANEL_LEADTEK_LTK050H3146W) += panel-leadtek-ltk050h3146w.o +obj-$(CONFIG_DRM_PANEL_LEADTEK_LTK500HD1829) += panel-leadtek-ltk500hd1829.o +obj-$(CONFIG_DRM_PANEL_LINCOLNTECH_LCD197) += panel-lincolntech-lcd197.o +obj-$(CONFIG_DRM_PANEL_LG_LB035Q02) += panel-lg-lb035q02.o +obj-$(CONFIG_DRM_PANEL_LG_LD070WX3) += panel-lg-ld070wx3.o obj-$(CONFIG_DRM_PANEL_LG_LG4573) += panel-lg-lg4573.o +obj-$(CONFIG_DRM_PANEL_LG_SW43408) += panel-lg-sw43408.o +obj-$(CONFIG_DRM_PANEL_MAGNACHIP_D53E6EA8966) += panel-magnachip-d53e6ea8966.o +obj-$(CONFIG_DRM_PANEL_NEC_NL8048HL11) += panel-nec-nl8048hl11.o +obj-$(CONFIG_DRM_PANEL_NEWVISION_NV3051D) += panel-newvision-nv3051d.o +obj-$(CONFIG_DRM_PANEL_NEWVISION_NV3052C) += panel-newvision-nv3052c.o +obj-$(CONFIG_DRM_PANEL_NOVATEK_NT35510) += panel-novatek-nt35510.o +obj-$(CONFIG_DRM_PANEL_NOVATEK_NT35560) += panel-novatek-nt35560.o +obj-$(CONFIG_DRM_PANEL_NOVATEK_NT35950) += panel-novatek-nt35950.o +obj-$(CONFIG_DRM_PANEL_NOVATEK_NT36523) += panel-novatek-nt36523.o +obj-$(CONFIG_DRM_PANEL_NOVATEK_NT36672A) += panel-novatek-nt36672a.o +obj-$(CONFIG_DRM_PANEL_NOVATEK_NT36672E) += panel-novatek-nt36672e.o +obj-$(CONFIG_DRM_PANEL_NOVATEK_NT37801) += panel-novatek-nt37801.o +obj-$(CONFIG_DRM_PANEL_NOVATEK_NT39016) += panel-novatek-nt39016.o +obj-$(CONFIG_DRM_PANEL_MANTIX_MLAF057WE51) += panel-mantix-mlaf057we51.o obj-$(CONFIG_DRM_PANEL_OLIMEX_LCD_OLINUXINO) += panel-olimex-lcd-olinuxino.o +obj-$(CONFIG_DRM_PANEL_ORISETECH_OTA5601A) += panel-orisetech-ota5601a.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_RM67200) += panel-raydium-rm67200.o obj-$(CONFIG_DRM_PANEL_RAYDIUM_RM68200) += panel-raydium-rm68200.o +obj-$(CONFIG_DRM_PANEL_RAYDIUM_RM692E5) += panel-raydium-rm692e5.o +obj-$(CONFIG_DRM_PANEL_RAYDIUM_RM69380) += panel-raydium-rm69380.o +obj-$(CONFIG_DRM_PANEL_RENESAS_R61307) += panel-renesas-r61307.o +obj-$(CONFIG_DRM_PANEL_RENESAS_R69328) += panel-renesas-r69328.o +obj-$(CONFIG_DRM_PANEL_RONBO_RB070D30) += panel-ronbo-rb070d30.o +obj-$(CONFIG_DRM_PANEL_SAMSUNG_AMS581VF01) += panel-samsung-ams581vf01.o +obj-$(CONFIG_DRM_PANEL_SAMSUNG_AMS639RQ08) += panel-samsung-ams639rq08.o +obj-$(CONFIG_DRM_PANEL_SAMSUNG_ATNA33XC20) += panel-samsung-atna33xc20.o +obj-$(CONFIG_DRM_PANEL_SAMSUNG_DB7430) += panel-samsung-db7430.o obj-$(CONFIG_DRM_PANEL_SAMSUNG_LD9040) += panel-samsung-ld9040.o obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6D16D0) += panel-samsung-s6d16d0.o +obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6D27A1) += panel-samsung-s6d27a1.o +obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6D7AA0) += panel-samsung-s6d7aa0.o +obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E3FA7) += panel-samsung-s6e3fa7.o +obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E3FC2X01) += panel-samsung-s6e3fc2x01.o obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E3HA2) += panel-samsung-s6e3ha2.o +obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E3HA8) += panel-samsung-s6e3ha8.o obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E63J0X03) += panel-samsung-s6e63j0x03.o +obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E63M0) += panel-samsung-s6e63m0.o +obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E63M0_SPI) += panel-samsung-s6e63m0-spi.o +obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E63M0_DSI) += panel-samsung-s6e63m0-dsi.o +obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E88A0_AMS427AP24) += panel-samsung-s6e88a0-ams427ap24.o +obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E88A0_AMS452EF01) += panel-samsung-s6e88a0-ams452ef01.o obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E8AA0) += panel-samsung-s6e8aa0.o +obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E8AA5X01_AMS561RA01) += panel-samsung-s6e8aa5x01-ams561ra01.o +obj-$(CONFIG_DRM_PANEL_SAMSUNG_SOFEF00) += panel-samsung-sofef00.o obj-$(CONFIG_DRM_PANEL_SEIKO_43WVF1G) += panel-seiko-43wvf1g.o +obj-$(CONFIG_DRM_PANEL_SHARP_LQ079L1SX01) += panel-sharp-lq079l1sx01.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_SHARP_LS060T1SX01) += panel-sharp-ls060t1sx01.o +obj-$(CONFIG_DRM_PANEL_SITRONIX_ST7701) += panel-sitronix-st7701.o +obj-$(CONFIG_DRM_PANEL_SITRONIX_ST7703) += panel-sitronix-st7703.o obj-$(CONFIG_DRM_PANEL_SITRONIX_ST7789V) += panel-sitronix-st7789v.o +obj-$(CONFIG_DRM_PANEL_SUMMIT) += panel-summit.o +obj-$(CONFIG_DRM_PANEL_SYNAPTICS_R63353) += panel-synaptics-r63353.o +obj-$(CONFIG_DRM_PANEL_SYNAPTICS_TDDI) += panel-synaptics-tddi.o +obj-$(CONFIG_DRM_PANEL_SONY_ACX565AKM) += panel-sony-acx565akm.o +obj-$(CONFIG_DRM_PANEL_SONY_TD4353_JDI) += panel-sony-td4353-jdi.o +obj-$(CONFIG_DRM_PANEL_SONY_TULIP_TRULY_NT35521) += panel-sony-tulip-truly-nt35521.o +obj-$(CONFIG_DRM_PANEL_STARTEK_KD070FHFID015) += panel-startek-kd070fhfid015.o +obj-$(CONFIG_DRM_PANEL_TDO_TL070WSH30) += panel-tdo-tl070wsh30.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_VISIONOX_G2647FB105) += panel-visionox-g2647fb105.o +obj-$(CONFIG_DRM_PANEL_VISIONOX_RM69299) += panel-visionox-rm69299.o +obj-$(CONFIG_DRM_PANEL_VISIONOX_RM692E5) += panel-visionox-rm692e5.o +obj-$(CONFIG_DRM_PANEL_VISIONOX_VTDR6130) += panel-visionox-vtdr6130.o +obj-$(CONFIG_DRM_PANEL_VISIONOX_R66451) += panel-visionox-r66451.o +obj-$(CONFIG_DRM_PANEL_WIDECHIPS_WS2401) += panel-widechips-ws2401.o +obj-$(CONFIG_DRM_PANEL_XINPENG_XPP055C272) += panel-xinpeng-xpp055c272.o diff --git a/drivers/gpu/drm/panel/panel-abt-y030xx067a.c b/drivers/gpu/drm/panel/panel-abt-y030xx067a.c new file mode 100644 index 000000000000..87fb0fd29658 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-abt-y030xx067a.c @@ -0,0 +1,383 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Asia Better Technology Ltd. Y030XX067A IPS LCD panel driver + * + * Copyright (C) 2020, Paul Cercueil <paul@crapouillou.net> + * Copyright (C) 2020, Christophe Branchereau <cbranchereau@gmail.com> + */ + +#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/platform_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> + +#define REG00_VBRT_CTRL(val) (val) + +#define REG01_COM_DC(val) (val) + +#define REG02_DA_CONTRAST(val) (val) +#define REG02_VESA_SEL(val) ((val) << 5) +#define REG02_COMDC_SW BIT(7) + +#define REG03_VPOSITION(val) (val) +#define REG03_BSMOUNT BIT(5) +#define REG03_COMTST BIT(6) +#define REG03_HPOSITION1 BIT(7) + +#define REG04_HPOSITION1(val) (val) + +#define REG05_CLIP BIT(0) +#define REG05_NVM_VREFRESH BIT(1) +#define REG05_SLFR BIT(2) +#define REG05_SLBRCHARGE(val) ((val) << 3) +#define REG05_PRECHARGE_LEVEL(val) ((val) << 6) + +#define REG06_TEST5 BIT(0) +#define REG06_SLDWN BIT(1) +#define REG06_SLRGT BIT(2) +#define REG06_TEST2 BIT(3) +#define REG06_XPSAVE BIT(4) +#define REG06_GAMMA_SEL(val) ((val) << 5) +#define REG06_NT BIT(7) + +#define REG07_TEST1 BIT(0) +#define REG07_HDVD_POL BIT(1) +#define REG07_CK_POL BIT(2) +#define REG07_TEST3 BIT(3) +#define REG07_TEST4 BIT(4) +#define REG07_480_LINEMASK BIT(5) +#define REG07_AMPTST(val) ((val) << 6) + +#define REG08_SLHRC(val) (val) +#define REG08_CLOCK_DIV(val) ((val) << 2) +#define REG08_PANEL(val) ((val) << 5) + +#define REG09_SUB_BRIGHT_R(val) (val) +#define REG09_NW_NB BIT(6) +#define REG09_IPCON BIT(7) + +#define REG0A_SUB_BRIGHT_B(val) (val) +#define REG0A_PAIR BIT(6) +#define REG0A_DE_SEL BIT(7) + +#define REG0B_MBK_POSITION(val) (val) +#define REG0B_HD_FREERUN BIT(4) +#define REG0B_VD_FREERUN BIT(5) +#define REG0B_YUV2BIN(val) ((val) << 6) + +#define REG0C_CONTRAST_R(val) (val) +#define REG0C_DOUBLEREAD BIT(7) + +#define REG0D_CONTRAST_G(val) (val) +#define REG0D_RGB_YUV BIT(7) + +#define REG0E_CONTRAST_B(val) (val) +#define REG0E_PIXELCOLORDRIVE BIT(7) + +#define REG0F_ASPECT BIT(0) +#define REG0F_OVERSCAN(val) ((val) << 1) +#define REG0F_FRAMEWIDTH(val) ((val) << 3) + +#define REG10_BRIGHT(val) (val) + +#define REG11_SIG_GAIN(val) (val) +#define REG11_SIGC_CNTL BIT(6) +#define REG11_SIGC_POL BIT(7) + +#define REG12_COLOR(val) (val) +#define REG12_PWCKSEL(val) ((val) << 6) + +#define REG13_4096LEVEL_CNTL(val) (val) +#define REG13_SL4096(val) ((val) << 4) +#define REG13_LIMITER_CONTROL BIT(7) + +#define REG14_PANEL_TEST(val) (val) + +#define REG15_NVM_LINK0 BIT(0) +#define REG15_NVM_LINK1 BIT(1) +#define REG15_NVM_LINK2 BIT(2) +#define REG15_NVM_LINK3 BIT(3) +#define REG15_NVM_LINK4 BIT(4) +#define REG15_NVM_LINK5 BIT(5) +#define REG15_NVM_LINK6 BIT(6) +#define REG15_NVM_LINK7 BIT(7) + +struct y030xx067a_info { + const struct drm_display_mode *display_modes; + unsigned int num_modes; + u16 width_mm, height_mm; + u32 bus_format, bus_flags; +}; + +struct y030xx067a { + struct drm_panel panel; + struct spi_device *spi; + struct regmap *map; + + const struct y030xx067a_info *panel_info; + + struct regulator *supply; + struct gpio_desc *reset_gpio; +}; + +static inline struct y030xx067a *to_y030xx067a(struct drm_panel *panel) +{ + return container_of(panel, struct y030xx067a, panel); +} + +static const struct reg_sequence y030xx067a_init_sequence[] = { + { 0x00, REG00_VBRT_CTRL(0x7f) }, + { 0x01, REG01_COM_DC(0x3c) }, + { 0x02, REG02_VESA_SEL(0x3) | REG02_DA_CONTRAST(0x1f) }, + { 0x03, REG03_VPOSITION(0x0a) }, + { 0x04, REG04_HPOSITION1(0xd2) }, + { 0x05, REG05_CLIP | REG05_NVM_VREFRESH | REG05_SLBRCHARGE(0x2) }, + { 0x06, REG06_NT }, + { 0x07, 0 }, + { 0x08, REG08_PANEL(0x1) | REG08_CLOCK_DIV(0x2) }, + { 0x09, REG09_SUB_BRIGHT_R(0x20) }, + { 0x0a, REG0A_SUB_BRIGHT_B(0x20) }, + { 0x0b, REG0B_HD_FREERUN | REG0B_VD_FREERUN }, + { 0x0c, REG0C_CONTRAST_R(0x00) }, + { 0x0d, REG0D_CONTRAST_G(0x00) }, + { 0x0e, REG0E_CONTRAST_B(0x10) }, + { 0x0f, 0 }, + { 0x10, REG10_BRIGHT(0x7f) }, + { 0x11, REG11_SIGC_CNTL | REG11_SIG_GAIN(0x3f) }, + { 0x12, REG12_COLOR(0x20) | REG12_PWCKSEL(0x1) }, + { 0x13, REG13_4096LEVEL_CNTL(0x8) }, + { 0x14, 0 }, + { 0x15, 0 }, +}; + +static int y030xx067a_prepare(struct drm_panel *panel) +{ + struct y030xx067a *priv = to_y030xx067a(panel); + struct device *dev = &priv->spi->dev; + int err; + + err = regulator_enable(priv->supply); + if (err) { + dev_err(dev, "Failed to enable power supply: %d\n", err); + return err; + } + + /* Reset the chip */ + gpiod_set_value_cansleep(priv->reset_gpio, 1); + usleep_range(1000, 20000); + gpiod_set_value_cansleep(priv->reset_gpio, 0); + usleep_range(1000, 20000); + + err = regmap_multi_reg_write(priv->map, y030xx067a_init_sequence, + ARRAY_SIZE(y030xx067a_init_sequence)); + if (err) { + dev_err(dev, "Failed to init registers: %d\n", err); + goto err_disable_regulator; + } + + return 0; + +err_disable_regulator: + regulator_disable(priv->supply); + return err; +} + +static int y030xx067a_unprepare(struct drm_panel *panel) +{ + struct y030xx067a *priv = to_y030xx067a(panel); + + gpiod_set_value_cansleep(priv->reset_gpio, 1); + regulator_disable(priv->supply); + + return 0; +} + +static int y030xx067a_enable(struct drm_panel *panel) +{ + struct y030xx067a *priv = to_y030xx067a(panel); + + regmap_set_bits(priv->map, 0x06, REG06_XPSAVE); + + if (panel->backlight) { + /* Wait for the picture to be ready before enabling backlight */ + msleep(120); + } + + return 0; +} + +static int y030xx067a_disable(struct drm_panel *panel) +{ + struct y030xx067a *priv = to_y030xx067a(panel); + + regmap_clear_bits(priv->map, 0x06, REG06_XPSAVE); + + return 0; +} + +static int y030xx067a_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct y030xx067a *priv = to_y030xx067a(panel); + const struct y030xx067a_info *panel_info = priv->panel_info; + struct drm_display_mode *mode; + unsigned int i; + + for (i = 0; i < panel_info->num_modes; i++) { + mode = drm_mode_duplicate(connector->dev, + &panel_info->display_modes[i]); + if (!mode) + return -ENOMEM; + + drm_mode_set_name(mode); + + mode->type = DRM_MODE_TYPE_DRIVER; + if (panel_info->num_modes == 1) + mode->type |= 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 panel_info->num_modes; +} + +static const struct drm_panel_funcs y030xx067a_funcs = { + .prepare = y030xx067a_prepare, + .unprepare = y030xx067a_unprepare, + .enable = y030xx067a_enable, + .disable = y030xx067a_disable, + .get_modes = y030xx067a_get_modes, +}; + +static const struct regmap_config y030xx067a_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x15, + .cache_type = REGCACHE_FLAT, +}; + +static int y030xx067a_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct y030xx067a *priv; + int err; + + priv = devm_drm_panel_alloc(dev, struct y030xx067a, panel, + &y030xx067a_funcs, DRM_MODE_CONNECTOR_DPI); + if (IS_ERR(priv)) + return PTR_ERR(priv); + + priv->spi = spi; + spi_set_drvdata(spi, priv); + + priv->map = devm_regmap_init_spi(spi, &y030xx067a_regmap_config); + if (IS_ERR(priv->map)) { + dev_err(dev, "Unable to init regmap\n"); + return PTR_ERR(priv->map); + } + + priv->panel_info = of_device_get_match_data(dev); + if (!priv->panel_info) + return -EINVAL; + + priv->supply = devm_regulator_get(dev, "power"); + if (IS_ERR(priv->supply)) + return dev_err_probe(dev, PTR_ERR(priv->supply), + "Failed to get power supply\n"); + + priv->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(priv->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(priv->reset_gpio), + "Failed to get reset GPIO\n"); + + err = drm_panel_of_backlight(&priv->panel); + if (err) + return err; + + drm_panel_add(&priv->panel); + + return 0; +} + +static void y030xx067a_remove(struct spi_device *spi) +{ + struct y030xx067a *priv = spi_get_drvdata(spi); + + drm_panel_remove(&priv->panel); + drm_panel_disable(&priv->panel); + drm_panel_unprepare(&priv->panel); +} + +static const struct drm_display_mode y030xx067a_modes[] = { + { /* 60 Hz */ + .clock = 14400, + .hdisplay = 320, + .hsync_start = 320 + 10, + .hsync_end = 320 + 10 + 37, + .htotal = 320 + 10 + 37 + 33, + .vdisplay = 480, + .vsync_start = 480 + 84, + .vsync_end = 480 + 84 + 20, + .vtotal = 480 + 84 + 20 + 16, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + }, + { /* 50 Hz */ + .clock = 12000, + .hdisplay = 320, + .hsync_start = 320 + 10, + .hsync_end = 320 + 10 + 37, + .htotal = 320 + 10 + 37 + 33, + .vdisplay = 480, + .vsync_start = 480 + 84, + .vsync_end = 480 + 84 + 20, + .vtotal = 480 + 84 + 20 + 16, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + }, +}; + +static const struct y030xx067a_info y030xx067a_info = { + .display_modes = y030xx067a_modes, + .num_modes = ARRAY_SIZE(y030xx067a_modes), + .width_mm = 69, + .height_mm = 51, + .bus_format = MEDIA_BUS_FMT_RGB888_3X8_DELTA, + .bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE | DRM_BUS_FLAG_DE_LOW, +}; + +static const struct of_device_id y030xx067a_of_match[] = { + { .compatible = "abt,y030xx067a", .data = &y030xx067a_info }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, y030xx067a_of_match); + +static struct spi_driver y030xx067a_driver = { + .driver = { + .name = "abt-y030xx067a", + .of_match_table = y030xx067a_of_match, + }, + .probe = y030xx067a_probe, + .remove = y030xx067a_remove, +}; +module_spi_driver(y030xx067a_driver); + +MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>"); +MODULE_AUTHOR("Christophe Branchereau <cbranchereau@gmail.com>"); +MODULE_DESCRIPTION("Asia Better Technology Ltd. Y030XX067A IPS LCD panel driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-arm-versatile.c b/drivers/gpu/drm/panel/panel-arm-versatile.c index b428c4678106..ea5119018df4 100644 --- a/drivers/gpu/drm/panel/panel-arm-versatile.c +++ b/drivers/gpu/drm/panel/panel-arm-versatile.c @@ -25,13 +25,12 @@ * Epson QCIF display. * */ -#include <drm/drmP.h> -#include <drm/drm_panel.h> #include <linux/bitops.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/mfd/syscon.h> +#include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/regmap.h> @@ -39,6 +38,9 @@ #include <video/of_videomode.h> #include <video/videomode.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + /* * This configuration register in the Versatile and RealView * family is uniformly present but appears more and more @@ -141,7 +143,6 @@ static const struct versatile_panel_type versatile_panels[] = { .vsync_start = 240 + 5, .vsync_end = 240 + 5 + 6, .vtotal = 240 + 5 + 6 + 5, - .vrefresh = 116, .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC, }, }, @@ -165,7 +166,6 @@ static const struct versatile_panel_type versatile_panels[] = { .vsync_start = 480 + 11, .vsync_end = 480 + 11 + 2, .vtotal = 480 + 11 + 2 + 32, - .vrefresh = 60, .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC, }, }, @@ -188,10 +188,9 @@ static const struct versatile_panel_type versatile_panels[] = { .vsync_start = 220 + 0, .vsync_end = 220 + 0 + 2, .vtotal = 220 + 0 + 2 + 1, - .vrefresh = 390, .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC, }, - .bus_flags = DRM_BUS_FLAG_PIXDATA_NEGEDGE, + .bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE, }, /* * Sanyo ALR252RGT 240x320 portrait display found on the @@ -212,10 +211,9 @@ static const struct versatile_panel_type versatile_panels[] = { .vsync_start = 320 + 2, .vsync_end = 320 + 2 + 2, .vtotal = 320 + 2 + 2 + 2, - .vrefresh = 116, .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, }, - .bus_flags = DRM_BUS_FLAG_PIXDATA_NEGEDGE, + .bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE, .ib2 = true, }, }; @@ -258,19 +256,19 @@ 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; - strncpy(connector->display_info.name, vpanel->panel_type->name, - DRM_DISPLAY_INFO_LEN); connector->display_info.width_mm = vpanel->panel_type->width_mm; 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); + if (!mode) + return -ENOMEM; drm_mode_set_name(mode); mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; @@ -308,9 +306,11 @@ static int versatile_panel_probe(struct platform_device *pdev) return PTR_ERR(map); } - vpanel = devm_kzalloc(dev, sizeof(*vpanel), GFP_KERNEL); - if (!vpanel) - return -ENOMEM; + vpanel = devm_drm_panel_alloc(dev, struct versatile_panel, panel, + &versatile_panel_drm_funcs, + DRM_MODE_CONNECTOR_DPI); + if (IS_ERR(vpanel)) + return PTR_ERR(vpanel); ret = regmap_read(map, SYS_CLCD, &val); if (ret) { @@ -350,11 +350,9 @@ 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_add(&vpanel->panel); - return drm_panel_add(&vpanel->panel); + return 0; } static const struct of_device_id versatile_panel_match[] = { diff --git a/drivers/gpu/drm/panel/panel-asus-z00t-tm5p5-n35596.c b/drivers/gpu/drm/panel/panel-asus-z00t-tm5p5-n35596.c new file mode 100644 index 000000000000..db006576d704 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-asus-z00t-tm5p5-n35596.c @@ -0,0 +1,307 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#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 <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +struct tm5p5_nt35596 { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct regulator_bulk_data supplies[2]; + struct gpio_desc *reset_gpio; +}; + +static inline struct tm5p5_nt35596 *to_tm5p5_nt35596(struct drm_panel *panel) +{ + return container_of(panel, struct tm5p5_nt35596, panel); +} + +static void tm5p5_nt35596_reset(struct tm5p5_nt35596 *ctx) +{ + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(1000, 2000); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(1000, 2000); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(15000, 16000); +} + +static void tm5p5_nt35596_on(struct mipi_dsi_multi_context *dsi_ctx) +{ + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xff, 0x05); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xfb, 0x01); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xc5, 0x31); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xff, 0x04); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x01, 0x84); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x05, 0x25); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x06, 0x01); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x07, 0x20); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x08, 0x06); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x09, 0x08); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x0a, 0x10); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x0b, 0x10); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x0c, 0x10); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x0d, 0x14); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x0e, 0x14); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x0f, 0x14); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x10, 0x14); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x11, 0x14); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x12, 0x14); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x17, 0xf3); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x18, 0xc0); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x19, 0xc0); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x1a, 0xc0); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x1b, 0xb3); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x1c, 0xb3); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x1d, 0xb3); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x1e, 0xb3); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x1f, 0xb3); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x20, 0xb3); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xfb, 0x01); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xff, 0x00); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xfb, 0x01); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x35, 0x01); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xd3, 0x06); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xd4, 0x04); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x5e, 0x0d); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x11, 0x00); + + mipi_dsi_msleep(dsi_ctx, 100); + + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x29, 0x00); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x53, 0x24); +} + +static void tm5p5_nt35596_off(struct mipi_dsi_multi_context *dsi_ctx) +{ + mipi_dsi_dcs_set_display_off_multi(dsi_ctx); + + mipi_dsi_msleep(dsi_ctx, 60); + + mipi_dsi_dcs_enter_sleep_mode_multi(dsi_ctx); + + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x4f, 0x01); +} + +static int tm5p5_nt35596_prepare(struct drm_panel *panel) +{ + struct tm5p5_nt35596 *ctx = to_tm5p5_nt35596(panel); + struct mipi_dsi_multi_context dsi_ctx = {.dsi = ctx->dsi}; + + dsi_ctx.accum_err = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + if (dsi_ctx.accum_err) + return dsi_ctx.accum_err; + + tm5p5_nt35596_reset(ctx); + + tm5p5_nt35596_on(&dsi_ctx); + + if (dsi_ctx.accum_err) { + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), + ctx->supplies); + } + + return dsi_ctx.accum_err; +} + +static int tm5p5_nt35596_unprepare(struct drm_panel *panel) +{ + struct tm5p5_nt35596 *ctx = to_tm5p5_nt35596(panel); + struct mipi_dsi_multi_context dsi_ctx = {.dsi = ctx->dsi}; + + tm5p5_nt35596_off(&dsi_ctx); + + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), + ctx->supplies); + + return dsi_ctx.accum_err; +} + +static const struct drm_display_mode tm5p5_nt35596_mode = { + .clock = (1080 + 100 + 8 + 16) * (1920 + 4 + 2 + 4) * 60 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 100, + .hsync_end = 1080 + 100 + 8, + .htotal = 1080 + 100 + 8 + 16, + .vdisplay = 1920, + .vsync_start = 1920 + 4, + .vsync_end = 1920 + 4 + 2, + .vtotal = 1920 + 4 + 2 + 4, + .width_mm = 68, + .height_mm = 121, +}; + +static int tm5p5_nt35596_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &tm5p5_nt35596_mode); + if (!mode) + 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 tm5p5_nt35596_panel_funcs = { + .prepare = tm5p5_nt35596_prepare, + .unprepare = tm5p5_nt35596_unprepare, + .get_modes = tm5p5_nt35596_get_modes, +}; + +static int tm5p5_nt35596_bl_update_status(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + u16 brightness = backlight_get_brightness(bl); + int ret; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + ret = mipi_dsi_dcs_set_display_brightness(dsi, brightness); + if (ret < 0) + return ret; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + return 0; +} + +static int tm5p5_nt35596_bl_get_brightness(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + u16 brightness = bl->props.brightness; + int ret; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + ret = mipi_dsi_dcs_get_display_brightness(dsi, &brightness); + if (ret < 0) + return ret; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + return brightness & 0xff; +} + +static const struct backlight_ops tm5p5_nt35596_bl_ops = { + .update_status = tm5p5_nt35596_bl_update_status, + .get_brightness = tm5p5_nt35596_bl_get_brightness, +}; + +static struct backlight_device * +tm5p5_nt35596_create_backlight(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + const struct backlight_properties props = { + .type = BACKLIGHT_RAW, + .brightness = 255, + .max_brightness = 255, + }; + + return devm_backlight_device_register(dev, dev_name(dev), dev, dsi, + &tm5p5_nt35596_bl_ops, &props); +} + +static int tm5p5_nt35596_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct tm5p5_nt35596 *ctx; + int ret; + + ctx = devm_drm_panel_alloc(dev, struct tm5p5_nt35596, panel, + &tm5p5_nt35596_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ctx->supplies[0].supply = "vdd"; + ctx->supplies[1].supply = "vddio"; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), + ctx->supplies); + if (ret < 0) { + dev_err(dev, "Failed to get regulators: %d\n", ret); + return ret; + } + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->reset_gpio)) { + ret = PTR_ERR(ctx->reset_gpio); + dev_err(dev, "Failed to get reset-gpios: %d\n", ret); + return ret; + } + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_NO_EOT_PACKET | + MIPI_DSI_CLOCK_NON_CONTINUOUS | MIPI_DSI_MODE_LPM; + + ctx->panel.backlight = tm5p5_nt35596_create_backlight(dsi); + if (IS_ERR(ctx->panel.backlight)) { + ret = PTR_ERR(ctx->panel.backlight); + dev_err(dev, "Failed to create backlight: %d\n", ret); + return ret; + } + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "Failed to attach to DSI host: %d\n", ret); + return ret; + } + + return 0; +} + +static void tm5p5_nt35596_remove(struct mipi_dsi_device *dsi) +{ + struct tm5p5_nt35596 *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, + "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id tm5p5_nt35596_of_match[] = { + { .compatible = "asus,z00t-tm5p5-n35596" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, tm5p5_nt35596_of_match); + +static struct mipi_dsi_driver tm5p5_nt35596_driver = { + .probe = tm5p5_nt35596_probe, + .remove = tm5p5_nt35596_remove, + .driver = { + .name = "panel-tm5p5-nt35596", + .of_match_table = tm5p5_nt35596_of_match, + }, +}; +module_mipi_dsi_driver(tm5p5_nt35596_driver); + +MODULE_AUTHOR("Konrad Dybcio <konradybcio@gmail.com>"); +MODULE_DESCRIPTION("DRM driver for tm5p5 nt35596 1080p video mode dsi panel"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-auo-a030jtn01.c b/drivers/gpu/drm/panel/panel-auo-a030jtn01.c new file mode 100644 index 000000000000..6e52bf6830e1 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-auo-a030jtn01.c @@ -0,0 +1,307 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AU Optronics A030JTN01.0 TFT LCD panel driver + * + * Copyright (C) 2023, Paul Cercueil <paul@crapouillou.net> + * Copyright (C) 2023, Christophe Branchereau <cbranchereau@gmail.com> + */ + +#include <linux/bitfield.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/gpio/consumer.h> +#include <linux/media-bus-format.h> +#include <linux/mod_devicetable.h> +#include <linux/module.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> + +#define REG05 0x05 +#define REG06 0x06 +#define REG07 0x07 + +#define REG05_STDBY BIT(0) +#define REG06_VBLK GENMASK(4, 0) +#define REG07_HBLK GENMASK(7, 0) + + +struct a030jtn01_info { + const struct drm_display_mode *display_modes; + unsigned int num_modes; + u16 width_mm, height_mm; + u32 bus_format, bus_flags; +}; + +struct a030jtn01 { + struct drm_panel panel; + struct spi_device *spi; + struct regmap *map; + + const struct a030jtn01_info *panel_info; + + struct regulator *supply; + struct gpio_desc *reset_gpio; +}; + +static inline struct a030jtn01 *to_a030jtn01(struct drm_panel *panel) +{ + return container_of(panel, struct a030jtn01, panel); +} + +static int a030jtn01_prepare(struct drm_panel *panel) +{ + struct a030jtn01 *priv = to_a030jtn01(panel); + struct device *dev = &priv->spi->dev; + unsigned int dummy; + int err; + + err = regulator_enable(priv->supply); + if (err) { + dev_err(dev, "Failed to enable power supply: %d\n", err); + return err; + } + + usleep_range(1000, 8000); + + /* Reset the chip */ + gpiod_set_value_cansleep(priv->reset_gpio, 1); + usleep_range(100, 8000); + gpiod_set_value_cansleep(priv->reset_gpio, 0); + usleep_range(2000, 8000); + + /* + * No idea why, but a register read (doesn't matter which) is needed to + * properly initialize the chip after a reset; otherwise, the colors + * will be wrong. It doesn't seem to be timing-related as a msleep(200) + * doesn't fix it. + */ + err = regmap_read(priv->map, REG05, &dummy); + if (err) + goto err_disable_regulator; + + /* Use (24 + 6) == 0x1e as the vertical back porch */ + err = regmap_write(priv->map, REG06, FIELD_PREP(REG06_VBLK, 0x1e)); + if (err) + goto err_disable_regulator; + + /* Use (42 + 30) * 3 == 0xd8 as the horizontal back porch */ + err = regmap_write(priv->map, REG07, FIELD_PREP(REG07_HBLK, 0xd8)); + if (err) + goto err_disable_regulator; + + return 0; + +err_disable_regulator: + gpiod_set_value_cansleep(priv->reset_gpio, 1); + regulator_disable(priv->supply); + return err; +} + +static int a030jtn01_unprepare(struct drm_panel *panel) +{ + struct a030jtn01 *priv = to_a030jtn01(panel); + + gpiod_set_value_cansleep(priv->reset_gpio, 1); + regulator_disable(priv->supply); + + return 0; +} + +static int a030jtn01_enable(struct drm_panel *panel) +{ + struct a030jtn01 *priv = to_a030jtn01(panel); + int ret; + + ret = regmap_set_bits(priv->map, REG05, REG05_STDBY); + if (ret) + return ret; + + /* Wait for the picture to be stable */ + if (panel->backlight) + msleep(100); + + return 0; +} + +static int a030jtn01_disable(struct drm_panel *panel) +{ + struct a030jtn01 *priv = to_a030jtn01(panel); + + return regmap_clear_bits(priv->map, REG05, REG05_STDBY); +} + +static int a030jtn01_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct a030jtn01 *priv = to_a030jtn01(panel); + const struct a030jtn01_info *panel_info = priv->panel_info; + struct drm_display_mode *mode; + unsigned int i; + + for (i = 0; i < panel_info->num_modes; i++) { + mode = drm_mode_duplicate(connector->dev, + &panel_info->display_modes[i]); + if (!mode) + return -ENOMEM; + + drm_mode_set_name(mode); + + mode->type = DRM_MODE_TYPE_DRIVER; + if (panel_info->num_modes == 1) + mode->type |= 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 panel_info->num_modes; +} + +static const struct drm_panel_funcs a030jtn01_funcs = { + .prepare = a030jtn01_prepare, + .unprepare = a030jtn01_unprepare, + .enable = a030jtn01_enable, + .disable = a030jtn01_disable, + .get_modes = a030jtn01_get_modes, +}; + +static bool a030jtn01_has_reg(struct device *dev, unsigned int reg) +{ + static const u32 a030jtn01_regs_mask = 0x001823f1fb; + + return a030jtn01_regs_mask & BIT(reg); +}; + +static const struct regmap_config a030jtn01_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .read_flag_mask = 0x40, + .max_register = 0x1c, + .readable_reg = a030jtn01_has_reg, + .writeable_reg = a030jtn01_has_reg, +}; + +static int a030jtn01_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct a030jtn01 *priv; + int err; + + spi->mode |= SPI_MODE_3 | SPI_3WIRE; + + priv = devm_drm_panel_alloc(dev, struct a030jtn01, panel, + &a030jtn01_funcs, DRM_MODE_CONNECTOR_DPI); + if (IS_ERR(priv)) + return PTR_ERR(priv); + + priv->spi = spi; + spi_set_drvdata(spi, priv); + + priv->map = devm_regmap_init_spi(spi, &a030jtn01_regmap_config); + if (IS_ERR(priv->map)) + return dev_err_probe(dev, PTR_ERR(priv->map), "Unable to init regmap"); + + priv->panel_info = spi_get_device_match_data(spi); + if (!priv->panel_info) + return -EINVAL; + + priv->supply = devm_regulator_get(dev, "power"); + if (IS_ERR(priv->supply)) + return dev_err_probe(dev, PTR_ERR(priv->supply), "Failed to get power supply"); + + priv->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(priv->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(priv->reset_gpio), "Failed to get reset GPIO"); + + err = drm_panel_of_backlight(&priv->panel); + if (err) + return err; + + drm_panel_add(&priv->panel); + + return 0; +} + +static void a030jtn01_remove(struct spi_device *spi) +{ + struct a030jtn01 *priv = spi_get_drvdata(spi); + + drm_panel_remove(&priv->panel); + drm_panel_disable(&priv->panel); + drm_panel_unprepare(&priv->panel); +} + +static const struct drm_display_mode a030jtn01_modes[] = { + { /* 60 Hz */ + .clock = 14400, + .hdisplay = 320, + .hsync_start = 320 + 8, + .hsync_end = 320 + 8 + 42, + .htotal = 320 + 8 + 42 + 30, + .vdisplay = 480, + .vsync_start = 480 + 90, + .vsync_end = 480 + 90 + 24, + .vtotal = 480 + 90 + 24 + 6, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + }, + { /* 50 Hz */ + .clock = 12000, + .hdisplay = 320, + .hsync_start = 320 + 8, + .hsync_end = 320 + 8 + 42, + .htotal = 320 + 8 + 42 + 30, + .vdisplay = 480, + .vsync_start = 480 + 90, + .vsync_end = 480 + 90 + 24, + .vtotal = 480 + 90 + 24 + 6, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + }, +}; + +static const struct a030jtn01_info a030jtn01_info = { + .display_modes = a030jtn01_modes, + .num_modes = ARRAY_SIZE(a030jtn01_modes), + .width_mm = 70, + .height_mm = 51, + .bus_format = MEDIA_BUS_FMT_RGB888_3X8_DELTA, + .bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE, +}; + +static const struct spi_device_id a030jtn01_id[] = { + { "a030jtn01", (kernel_ulong_t) &a030jtn01_info }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(spi, a030jtn01_id); + +static const struct of_device_id a030jtn01_of_match[] = { + { .compatible = "auo,a030jtn01" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, a030jtn01_of_match); + +static struct spi_driver a030jtn01_driver = { + .driver = { + .name = "auo-a030jtn01", + .of_match_table = a030jtn01_of_match, + }, + .id_table = a030jtn01_id, + .probe = a030jtn01_probe, + .remove = a030jtn01_remove, +}; +module_spi_driver(a030jtn01_driver); + +MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>"); +MODULE_AUTHOR("Christophe Branchereau <cbranchereau@gmail.com>"); +MODULE_DESCRIPTION("AU Optronics A030JTN01.0 TFT LCD panel driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-boe-bf060y8m-aj0.c b/drivers/gpu/drm/panel/panel-boe-bf060y8m-aj0.c new file mode 100644 index 000000000000..84c21c62a43e --- /dev/null +++ b/drivers/gpu/drm/panel/panel-boe-bf060y8m-aj0.c @@ -0,0 +1,401 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * BOE BF060Y8M-AJ0 5.99" MIPI-DSI OLED Panel on SW43404 DriverIC + * + * Copyright (c) 2020 AngeloGioacchino Del Regno + * <angelogioacchino.delregno@somainline.org> + */ + +#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> + +#define DCS_ALLOW_HBM_RANGE 0x0c +#define DCS_DISALLOW_HBM_RANGE 0x08 + +enum boe_bf060y8m_aj0_supplies { + BF060Y8M_VREG_VCC, + BF060Y8M_VREG_VDDIO, + BF060Y8M_VREG_VCI, + BF060Y8M_VREG_EL_VDD, + BF060Y8M_VREG_EL_VSS, + BF060Y8M_VREG_MAX +}; + +struct boe_bf060y8m_aj0 { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct regulator_bulk_data vregs[BF060Y8M_VREG_MAX]; + struct gpio_desc *reset_gpio; +}; + +static inline +struct boe_bf060y8m_aj0 *to_boe_bf060y8m_aj0(struct drm_panel *panel) +{ + return container_of(panel, struct boe_bf060y8m_aj0, panel); +} + +static void boe_bf060y8m_aj0_reset(struct boe_bf060y8m_aj0 *boe) +{ + gpiod_set_value_cansleep(boe->reset_gpio, 0); + usleep_range(2000, 3000); + gpiod_set_value_cansleep(boe->reset_gpio, 1); + usleep_range(15000, 16000); + gpiod_set_value_cansleep(boe->reset_gpio, 0); + usleep_range(5000, 6000); +} + +static int boe_bf060y8m_aj0_on(struct boe_bf060y8m_aj0 *boe) +{ + struct mipi_dsi_device *dsi = boe->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0xa5, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb2, 0x00, 0x4c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_3D_CONTROL, 0x10); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_POWER_SAVE, DCS_ALLOW_HBM_RANGE); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf8, + 0x00, 0x08, 0x10, 0x00, 0x22, 0x00, 0x00, 0x2d); + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 30); + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0xa5, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc0, + 0x08, 0x48, 0x65, 0x33, 0x33, 0x33, + 0x2a, 0x31, 0x39, 0x20, 0x09); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc1, 0x00, 0x00, 0x00, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe2, 0x20, 0x04, 0x10, 0x12, 0x92, + 0x4f, 0x8f, 0x44, 0x84, 0x83, 0x83, 0x83, + 0x5c, 0x5c, 0x5c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xde, 0x01, 0x2c, 0x00, 0x77, 0x3e); + + mipi_dsi_msleep(&dsi_ctx, 30); + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 50); + + return dsi_ctx.accum_err; +} + +static void boe_bf060y8m_aj0_off(struct boe_bf060y8m_aj0 *boe) +{ + struct mipi_dsi_device *dsi = boe->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + /* OFF commands sent in HS mode */ + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 20); + + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + mipi_dsi_usleep_range(&dsi_ctx, 1000, 2000); + dsi->mode_flags |= MIPI_DSI_MODE_LPM; +} + +static int boe_bf060y8m_aj0_prepare(struct drm_panel *panel) +{ + struct boe_bf060y8m_aj0 *boe = to_boe_bf060y8m_aj0(panel); + int ret; + + /* + * Enable EL Driving Voltage first - doing that at the beginning + * or at the end of the power sequence doesn't matter, so enable + * it here to avoid yet another usleep at the end. + */ + ret = regulator_enable(boe->vregs[BF060Y8M_VREG_EL_VDD].consumer); + if (ret) + return ret; + ret = regulator_enable(boe->vregs[BF060Y8M_VREG_EL_VSS].consumer); + if (ret) + goto err_elvss; + + ret = regulator_enable(boe->vregs[BF060Y8M_VREG_VCC].consumer); + if (ret) + goto err_vcc; + usleep_range(1000, 2000); + ret = regulator_enable(boe->vregs[BF060Y8M_VREG_VDDIO].consumer); + if (ret) + goto err_vddio; + usleep_range(500, 1000); + ret = regulator_enable(boe->vregs[BF060Y8M_VREG_VCI].consumer); + if (ret) + goto err_vci; + usleep_range(2000, 3000); + + boe_bf060y8m_aj0_reset(boe); + + ret = boe_bf060y8m_aj0_on(boe); + if (ret < 0) { + gpiod_set_value_cansleep(boe->reset_gpio, 1); + goto err_on; + } + + return 0; + +err_on: + regulator_disable(boe->vregs[BF060Y8M_VREG_VCI].consumer); +err_vci: + regulator_disable(boe->vregs[BF060Y8M_VREG_VDDIO].consumer); +err_vddio: + regulator_disable(boe->vregs[BF060Y8M_VREG_VCC].consumer); +err_vcc: + regulator_disable(boe->vregs[BF060Y8M_VREG_EL_VSS].consumer); +err_elvss: + regulator_disable(boe->vregs[BF060Y8M_VREG_EL_VDD].consumer); + return ret; +} + +static int boe_bf060y8m_aj0_unprepare(struct drm_panel *panel) +{ + struct boe_bf060y8m_aj0 *boe = to_boe_bf060y8m_aj0(panel); + + boe_bf060y8m_aj0_off(boe); + + gpiod_set_value_cansleep(boe->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(boe->vregs), boe->vregs); + + return 0; +} + +static const struct drm_display_mode boe_bf060y8m_aj0_mode = { + .clock = 165268, + .hdisplay = 1080, + .hsync_start = 1080 + 36, + .hsync_end = 1080 + 36 + 24, + .htotal = 1080 + 36 + 24 + 96, + .vdisplay = 2160, + .vsync_start = 2160 + 16, + .vsync_end = 2160 + 16 + 1, + .vtotal = 2160 + 16 + 1 + 15, + .width_mm = 68, /* 68.04 mm */ + .height_mm = 136, /* 136.08 mm */ +}; + +static int boe_bf060y8m_aj0_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &boe_bf060y8m_aj0_mode); + if (!mode) + 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 boe_bf060y8m_aj0_panel_funcs = { + .prepare = boe_bf060y8m_aj0_prepare, + .unprepare = boe_bf060y8m_aj0_unprepare, + .get_modes = boe_bf060y8m_aj0_get_modes, +}; + +static int boe_bf060y8m_aj0_bl_update_status(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + u16 brightness = backlight_get_brightness(bl); + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + mipi_dsi_dcs_set_display_brightness_multi(&dsi_ctx, brightness); + + return dsi_ctx.accum_err; +} + +static int boe_bf060y8m_aj0_bl_get_brightness(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + u16 brightness; + int ret; + + ret = mipi_dsi_dcs_get_display_brightness(dsi, &brightness); + if (ret < 0) + return ret; + + return brightness & 0xff; +} + +static const struct backlight_ops boe_bf060y8m_aj0_bl_ops = { + .update_status = boe_bf060y8m_aj0_bl_update_status, + .get_brightness = boe_bf060y8m_aj0_bl_get_brightness, +}; + +static struct backlight_device * +boe_bf060y8m_aj0_create_backlight(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + const struct backlight_properties props = { + .type = BACKLIGHT_RAW, + .brightness = 127, + .max_brightness = 255, + .scale = BACKLIGHT_SCALE_NON_LINEAR, + }; + + return devm_backlight_device_register(dev, dev_name(dev), dev, dsi, + &boe_bf060y8m_aj0_bl_ops, &props); +} + +static int boe_bf060y8m_aj0_init_vregs(struct boe_bf060y8m_aj0 *boe, + struct device *dev) +{ + struct regulator *vreg; + int ret; + + boe->vregs[BF060Y8M_VREG_VCC].supply = "vcc"; + boe->vregs[BF060Y8M_VREG_VDDIO].supply = "vddio"; + boe->vregs[BF060Y8M_VREG_VCI].supply = "vci"; + boe->vregs[BF060Y8M_VREG_EL_VDD].supply = "elvdd"; + boe->vregs[BF060Y8M_VREG_EL_VSS].supply = "elvss"; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(boe->vregs), + boe->vregs); + if (ret < 0) { + dev_err(dev, "Failed to get regulators: %d\n", ret); + return ret; + } + + vreg = boe->vregs[BF060Y8M_VREG_VCC].consumer; + ret = regulator_is_supported_voltage(vreg, 2700000, 3600000); + if (!ret) + return ret; + + vreg = boe->vregs[BF060Y8M_VREG_VDDIO].consumer; + ret = regulator_is_supported_voltage(vreg, 1620000, 1980000); + if (!ret) + return ret; + + vreg = boe->vregs[BF060Y8M_VREG_VCI].consumer; + ret = regulator_is_supported_voltage(vreg, 2600000, 3600000); + if (!ret) + return ret; + + vreg = boe->vregs[BF060Y8M_VREG_EL_VDD].consumer; + ret = regulator_is_supported_voltage(vreg, 4400000, 4800000); + if (!ret) + return ret; + + /* ELVSS is negative: -5.00V to -1.40V */ + vreg = boe->vregs[BF060Y8M_VREG_EL_VSS].consumer; + ret = regulator_is_supported_voltage(vreg, 1400000, 5000000); + if (!ret) + return ret; + + /* + * Set min/max rated current, known only for VCI and VDDIO and, + * in case of failure, just go on gracefully, as this step is not + * guaranteed to succeed on all regulator HW but do a debug print + * to inform the developer during debugging. + * In any case, these two supplies are also optional, so they may + * be fixed-regulator which, at the time of writing, does not + * support fake current limiting. + */ + vreg = boe->vregs[BF060Y8M_VREG_VDDIO].consumer; + ret = regulator_set_current_limit(vreg, 1500, 2500); + if (ret) + dev_dbg(dev, "Current limit cannot be set on %s: %d\n", + boe->vregs[1].supply, ret); + + vreg = boe->vregs[BF060Y8M_VREG_VCI].consumer; + ret = regulator_set_current_limit(vreg, 20000, 40000); + if (ret) + dev_dbg(dev, "Current limit cannot be set on %s: %d\n", + boe->vregs[2].supply, ret); + + return 0; +} + +static int boe_bf060y8m_aj0_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct boe_bf060y8m_aj0 *boe; + int ret; + + boe = devm_drm_panel_alloc(dev, struct boe_bf060y8m_aj0, panel, + &boe_bf060y8m_aj0_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(boe)) + return PTR_ERR(boe); + + ret = boe_bf060y8m_aj0_init_vregs(boe, dev); + if (ret) + return dev_err_probe(dev, ret, + "Failed to initialize supplies.\n"); + + boe->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_ASIS); + if (IS_ERR(boe->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(boe->reset_gpio), + "Failed to get reset-gpios\n"); + + boe->dsi = dsi; + mipi_dsi_set_drvdata(dsi, boe); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_NO_EOT_PACKET | + MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_CLOCK_NON_CONTINUOUS | + MIPI_DSI_MODE_LPM; + + boe->panel.prepare_prev_first = true; + + boe->panel.backlight = boe_bf060y8m_aj0_create_backlight(dsi); + if (IS_ERR(boe->panel.backlight)) + return dev_err_probe(dev, PTR_ERR(boe->panel.backlight), + "Failed to create backlight\n"); + + drm_panel_add(&boe->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "Failed to attach to DSI host: %d\n", ret); + return ret; + } + + return 0; +} + +static void boe_bf060y8m_aj0_remove(struct mipi_dsi_device *dsi) +{ + struct boe_bf060y8m_aj0 *boe = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&boe->panel); +} + +static const struct of_device_id boe_bf060y8m_aj0_of_match[] = { + { .compatible = "boe,bf060y8m-aj0" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, boe_bf060y8m_aj0_of_match); + +static struct mipi_dsi_driver boe_bf060y8m_aj0_driver = { + .probe = boe_bf060y8m_aj0_probe, + .remove = boe_bf060y8m_aj0_remove, + .driver = { + .name = "panel-sw43404-boe-fhd-amoled", + .of_match_table = boe_bf060y8m_aj0_of_match, + }, +}; +module_mipi_dsi_driver(boe_bf060y8m_aj0_driver); + +MODULE_AUTHOR("AngeloGioacchino Del Regno <angelogioacchino.delregno@somainline.org>"); +MODULE_DESCRIPTION("BOE BF060Y8M-AJ0 MIPI-DSI OLED panel"); +MODULE_LICENSE("GPL v2"); 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..4a8560b4b899 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-boe-himax8279d.c @@ -0,0 +1,915 @@ +// 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/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 <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; +}; + +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; + + err = mipi_dsi_dcs_set_display_off(pinfo->link); + if (err < 0) { + dev_err(panel->dev, "failed to set display off: %d\n", err); + return err; + } + + return 0; +} + +static int boe_panel_unprepare(struct drm_panel *panel) +{ + struct panel_info *pinfo = to_panel_info(panel); + int err; + + err = mipi_dsi_dcs_set_display_off(pinfo->link); + if (err < 0) + dev_err(panel->dev, "failed to set display off: %d\n", err); + + err = mipi_dsi_dcs_enter_sleep_mode(pinfo->link); + if (err < 0) + dev_err(panel->dev, "failed to enter sleep mode: %d\n", err); + + /* sleep_mode_delay: 1ms - 2ms */ + usleep_range(1000, 2000); + + disable_gpios(pinfo); + + return 0; +} + +static int boe_panel_prepare(struct drm_panel *panel) +{ + struct panel_info *pinfo = to_panel_info(panel); + int err; + + 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) { + dev_err(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) { + dev_err(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) { + dev_err(panel->dev, "failed to set display on: %d\n", err); + goto poweroff; + } + + /* T7: 20ms - 21ms */ + usleep_range(20000, 21000); + + 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; + + usleep_range(120000, 121000); + + ret = mipi_dsi_dcs_set_display_on(pinfo->link); + if (ret < 0) { + dev_err(panel->dev, "failed to set display on: %d\n", ret); + return ret; + } + + 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) { + dev_err(pinfo->base.dev, "failed to add mode %ux%u@%u\n", + m->hdisplay, m->vdisplay, drm_mode_vrefresh(m)); + 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, +}; + +/* 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)) { + return dev_err_probe(dev, PTR_ERR(pinfo->pp18_gpio), + "failed to get pp18 gpio\n"); + } + + pinfo->pp33_gpio = devm_gpiod_get(dev, "pp33", GPIOD_OUT_HIGH); + if (IS_ERR(pinfo->pp33_gpio)) { + return dev_err_probe(dev, PTR_ERR(pinfo->pp33_gpio), + "failed to get pp33 gpio\n"); + } + + pinfo->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_HIGH); + if (IS_ERR(pinfo->enable_gpio)) { + return dev_err_probe(dev, PTR_ERR(pinfo->enable_gpio), + "failed to get enable gpio\n"); + } + + ret = drm_panel_of_backlight(&pinfo->base); + if (ret) + return ret; + + drm_panel_add(&pinfo->base); + + return 0; +} + +static int panel_probe(struct mipi_dsi_device *dsi) +{ + struct panel_info *pinfo; + const struct panel_desc *desc; + int err; + + pinfo = devm_drm_panel_alloc(&dsi->dev, __typeof(*pinfo), base, + &panel_funcs, DRM_MODE_CONNECTOR_DSI); + + if (IS_ERR(pinfo)) + return PTR_ERR(pinfo); + + 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 void panel_remove(struct mipi_dsi_device *dsi) +{ + struct panel_info *pinfo = mipi_dsi_get_drvdata(dsi); + int err; + + err = mipi_dsi_detach(dsi); + if (err < 0) + dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", err); + + drm_panel_remove(&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, +}; +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-boe-td4320.c b/drivers/gpu/drm/panel/panel-boe-td4320.c new file mode 100644 index 000000000000..1956daa2c71b --- /dev/null +++ b/drivers/gpu/drm/panel/panel-boe-td4320.c @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2024 Barnabas Czeman <barnabas.czeman@mainlining.org> +// Generated with linux-mdss-dsi-panel-driver-generator from vendor device tree: +// Copyright (c) 2013, The Linux Foundation. All rights reserved. + +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/mod_devicetable.h> +#include <linux/module.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_probe_helper.h> + +struct boe_td4320 { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct regulator_bulk_data *supplies; + struct gpio_desc *reset_gpio; +}; + +static const struct regulator_bulk_data boe_td4320_supplies[] = { + { .supply = "iovcc" }, + { .supply = "vsn" }, + { .supply = "vsp" }, +}; + +static inline struct boe_td4320 *to_boe_td4320(struct drm_panel *panel) +{ + return container_of(panel, struct boe_td4320, panel); +} + +static void boe_td4320_reset(struct boe_td4320 *ctx) +{ + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(1000, 2000); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(5000, 6000); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + msleep(30); +} + +static int boe_td4320_on(struct boe_td4320 *ctx) +{ + struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; + + ctx->dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb0, 0x04); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xd6, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb8, + 0x19, 0x55, 0x00, 0xbe, 0x00, 0x00, + 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb9, + 0x4d, 0x55, 0x05, 0xe6, 0x00, 0x02, + 0x03); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xba, + 0x9b, 0x5b, 0x07, 0xe6, 0x00, 0x13, + 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xf9, + 0x44, 0x3f, 0x00, 0x8d, 0xbf); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xce, + 0x5d, 0x00, 0x0f, 0x1f, 0x2f, 0x3f, + 0x4f, 0x5f, 0x6f, 0x7f, 0x8f, 0x9f, + 0xaf, 0xbf, 0xcf, 0xdf, 0xef, 0xff, + 0x04, 0x00, 0x02, 0x02, 0x42, 0x01, + 0x69, 0x5a, 0x40, 0x40, 0x00, 0x00, + 0x04, 0xfa, 0x00); + mipi_dsi_dcs_set_display_brightness_multi(&dsi_ctx, 0x00b8); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY, + 0x2c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_POWER_SAVE, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x11, 0x00); + mipi_dsi_msleep(&dsi_ctx, 96); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x29, 0x00); + mipi_dsi_msleep(&dsi_ctx, 20); + + return dsi_ctx.accum_err; +} + +static int boe_td4320_off(struct boe_td4320 *ctx) +{ + struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; + + ctx->dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 20); + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 120); + + return dsi_ctx.accum_err; +} + +static int boe_td4320_prepare(struct drm_panel *panel) +{ + struct boe_td4320 *ctx = to_boe_td4320(panel); + struct device *dev = &ctx->dsi->dev; + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(boe_td4320_supplies), ctx->supplies); + if (ret < 0) { + dev_err(dev, "Failed to enable regulators: %d\n", ret); + return ret; + } + + boe_td4320_reset(ctx); + + ret = boe_td4320_on(ctx); + if (ret < 0) { + dev_err(dev, "Failed to initialize panel: %d\n", ret); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(boe_td4320_supplies), ctx->supplies); + return ret; + } + + return 0; +} + +static int boe_td4320_unprepare(struct drm_panel *panel) +{ + struct boe_td4320 *ctx = to_boe_td4320(panel); + struct device *dev = &ctx->dsi->dev; + int ret; + + ret = boe_td4320_off(ctx); + if (ret < 0) + dev_err(dev, "Failed to un-initialize panel: %d\n", ret); + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(boe_td4320_supplies), ctx->supplies); + + return 0; +} + +static const struct drm_display_mode boe_td4320_mode = { + .clock = (1080 + 86 + 2 + 100) * (2340 + 4 + 4 + 60) * 60 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 86, + .hsync_end = 1080 + 86 + 2, + .htotal = 1080 + 86 + 2 + 100, + .vdisplay = 2340, + .vsync_start = 2340 + 4, + .vsync_end = 2340 + 4 + 4, + .vtotal = 2340 + 4 + 4 + 60, + .width_mm = 67, + .height_mm = 145, + .type = DRM_MODE_TYPE_DRIVER, +}; + +static int boe_td4320_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + return drm_connector_helper_get_modes_fixed(connector, &boe_td4320_mode); +} + +static const struct drm_panel_funcs boe_td4320_panel_funcs = { + .prepare = boe_td4320_prepare, + .unprepare = boe_td4320_unprepare, + .get_modes = boe_td4320_get_modes, +}; + +static int boe_td4320_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct boe_td4320 *ctx; + int ret; + + ctx = devm_drm_panel_alloc(dev, struct boe_td4320, panel, + &boe_td4320_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ret = devm_regulator_bulk_get_const(dev, + ARRAY_SIZE(boe_td4320_supplies), + boe_td4320_supplies, + &ctx->supplies); + if (ret < 0) + return ret; + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), + "Failed to get reset-gpios\n"); + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_CLOCK_NON_CONTINUOUS; + + ctx->panel.prepare_prev_first = true; + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return dev_err_probe(dev, ret, "Failed to get backlight\n"); + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + drm_panel_remove(&ctx->panel); + return dev_err_probe(dev, ret, "Failed to attach to DSI host\n"); + } + + return 0; +} + +static void boe_td4320_remove(struct mipi_dsi_device *dsi) +{ + struct boe_td4320 *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id boe_td4320_of_match[] = { + { .compatible = "boe,td4320" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, boe_td4320_of_match); + +static struct mipi_dsi_driver boe_td4320_driver = { + .probe = boe_td4320_probe, + .remove = boe_td4320_remove, + .driver = { + .name = "panel-boe-td4320", + .of_match_table = boe_td4320_of_match, + }, +}; +module_mipi_dsi_driver(boe_td4320_driver); + +MODULE_AUTHOR("Barnabas Czeman <barnabas.czeman@mainlining.org>"); +MODULE_DESCRIPTION("DRM driver for boe td4320 fhdplus video mode dsi panel"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-boe-th101mb31ig002-28a.c b/drivers/gpu/drm/panel/panel-boe-th101mb31ig002-28a.c new file mode 100644 index 000000000000..f33d4f855929 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-boe-th101mb31ig002-28a.c @@ -0,0 +1,438 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023 Alexander Warnecke <awarnecke002@hotmail.com> + * Copyright (c) 2023 Manuel Traut <manut@mecka.net> + * Copyright (c) 2023 Dang Huynh <danct12@riseup.net> + */ + +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/regulator/consumer.h> + +#include <drm/drm_connector.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> +#include <drm/drm_probe_helper.h> + +struct boe_th101mb31ig002; + +struct panel_desc { + const struct drm_display_mode *modes; + unsigned long mode_flags; + enum mipi_dsi_pixel_format format; + int (*init)(struct boe_th101mb31ig002 *ctx); + unsigned int lanes; + bool lp11_before_reset; + unsigned int vcioo_to_lp11_delay_ms; + unsigned int lp11_to_reset_delay_ms; + unsigned int backlight_off_to_display_off_delay_ms; + unsigned int enter_sleep_to_reset_down_delay_ms; + unsigned int power_off_delay_ms; +}; + +struct boe_th101mb31ig002 { + struct drm_panel panel; + + struct mipi_dsi_device *dsi; + + const struct panel_desc *desc; + + struct regulator *power; + struct gpio_desc *enable; + struct gpio_desc *reset; + + enum drm_panel_orientation orientation; +}; + +static void boe_th101mb31ig002_reset(struct boe_th101mb31ig002 *ctx) +{ + gpiod_direction_output(ctx->reset, 0); + usleep_range(10, 100); + gpiod_direction_output(ctx->reset, 1); + usleep_range(10, 100); + gpiod_direction_output(ctx->reset, 0); + usleep_range(5000, 6000); +} + +static int boe_th101mb31ig002_enable(struct boe_th101mb31ig002 *ctx) +{ + struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe0, 0xab, 0xba); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe1, 0xba, 0xab); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb1, 0x10, 0x01, 0x47, 0xff); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb2, 0x0c, 0x14, 0x04, 0x50, 0x50, 0x14); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb3, 0x56, 0x53, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb4, 0x33, 0x30, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb6, 0xb0, 0x00, 0x00, 0x10, 0x00, 0x10, + 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb8, 0x05, 0x12, 0x29, 0x49, 0x48, 0x00, + 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb9, 0x7c, 0x65, 0x55, 0x49, 0x46, 0x36, + 0x3b, 0x24, 0x3d, 0x3c, 0x3d, 0x5c, 0x4c, + 0x55, 0x47, 0x46, 0x39, 0x26, 0x06, 0x7c, + 0x65, 0x55, 0x49, 0x46, 0x36, 0x3b, 0x24, + 0x3d, 0x3c, 0x3d, 0x5c, 0x4c, 0x55, 0x47, + 0x46, 0x39, 0x26, 0x06); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x00, 0xff, 0x87, 0x12, 0x34, 0x44, 0x44, + 0x44, 0x44, 0x98, 0x04, 0x98, 0x04, 0x0f, + 0x00, 0x00, 0xc1); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc1, 0x54, 0x94, 0x02, 0x85, 0x9f, 0x00, + 0x7f, 0x00, 0x54, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc2, 0x17, 0x09, 0x08, 0x89, 0x08, 0x11, + 0x22, 0x20, 0x44, 0xff, 0x18, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc3, 0x86, 0x46, 0x05, 0x05, 0x1c, 0x1c, + 0x1d, 0x1d, 0x02, 0x1f, 0x1f, 0x1e, 0x1e, + 0x0f, 0x0f, 0x0d, 0x0d, 0x13, 0x13, 0x11, + 0x11, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc4, 0x07, 0x07, 0x04, 0x04, 0x1c, 0x1c, + 0x1d, 0x1d, 0x02, 0x1f, 0x1f, 0x1e, 0x1e, + 0x0e, 0x0e, 0x0c, 0x0c, 0x12, 0x12, 0x10, + 0x10, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc6, 0x2a, 0x2a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc8, 0x21, 0x00, 0x31, 0x42, 0x34, 0x16); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xca, 0xcb, 0x43); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xcd, 0x0e, 0x4b, 0x4b, 0x20, 0x19, 0x6b, + 0x06, 0xb3); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xd2, 0xe3, 0x2b, 0x38, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xd4, 0x00, 0x01, 0x00, 0x0e, 0x04, 0x44, + 0x08, 0x10, 0x00, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe6, 0x80, 0x01, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf0, 0x12, 0x03, 0x20, 0x00, 0xff); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf3, 0x00); + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + + mipi_dsi_msleep(&dsi_ctx, 120); + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + + return dsi_ctx.accum_err; +} + +static int starry_er88577_init_cmd(struct boe_th101mb31ig002 *ctx) +{ + struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; + + msleep(70); + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe0, 0xab, 0xba); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe1, 0xba, 0xab); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb1, 0x10, 0x01, 0x47, 0xff); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb2, 0x0c, 0x14, 0x04, 0x50, 0x50, 0x14); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb3, 0x56, 0x53, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb4, 0x33, 0x30, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb6, 0xb0, 0x00, 0x00, 0x10, 0x00, 0x10, + 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb8, 0x05, 0x12, 0x29, 0x49, 0x40); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb9, 0x7c, 0x61, 0x4f, 0x42, 0x3e, 0x2d, + 0x31, 0x1a, 0x33, 0x33, 0x33, 0x52, 0x40, + 0x47, 0x38, 0x34, 0x26, 0x0e, 0x06, 0x7c, + 0x61, 0x4f, 0x42, 0x3e, 0x2d, 0x31, 0x1a, + 0x33, 0x33, 0x33, 0x52, 0x40, 0x47, 0x38, + 0x34, 0x26, 0x0e, 0x06); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc0, 0xcc, 0x76, 0x12, 0x34, 0x44, 0x44, + 0x44, 0x44, 0x98, 0x04, 0x98, 0x04, 0x0f, + 0x00, 0x00, 0xc1); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc1, 0x54, 0x94, 0x02, 0x85, 0x9f, 0x00, + 0x6f, 0x00, 0x54, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc2, 0x17, 0x09, 0x08, 0x89, 0x08, 0x11, + 0x22, 0x20, 0x44, 0xff, 0x18, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc3, 0x87, 0x47, 0x05, 0x05, 0x1c, 0x1c, + 0x1d, 0x1d, 0x02, 0x1e, 0x1e, 0x1f, 0x1f, + 0x0f, 0x0f, 0x0d, 0x0d, 0x13, 0x13, 0x11, + 0x11, 0x24); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc4, 0x06, 0x06, 0x04, 0x04, 0x1c, 0x1c, + 0x1d, 0x1d, 0x02, 0x1e, 0x1e, 0x1f, 0x1f, + 0x0e, 0x0e, 0x0c, 0x0c, 0x12, 0x12, 0x10, + 0x10, 0x24); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc8, 0x21, 0x00, 0x31, 0x42, 0x34, 0x16); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xca, 0xcb, 0x43); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xcd, 0x0e, 0x4b, 0x4b, 0x20, 0x19, 0x6b, + 0x06, 0xb3); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xd1, 0x40, 0x0d, 0xff, 0x0f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xd2, 0xe3, 0x2b, 0x38, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xd3, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x33, 0x20, 0x3a, 0xd5, 0x86, 0xf3); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xd4, 0x00, 0x01, 0x00, 0x0e, 0x04, 0x44, + 0x08, 0x10, 0x00, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe6, 0x80, 0x09, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf0, 0x12, 0x03, 0x20, 0x00, 0xff); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf3, 0x00); + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + + mipi_dsi_msleep(&dsi_ctx, 120); + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + + mipi_dsi_msleep(&dsi_ctx, 20); + + return dsi_ctx.accum_err; +} + +static int boe_th101mb31ig002_disable(struct drm_panel *panel) +{ + struct boe_th101mb31ig002 *ctx = container_of(panel, + struct boe_th101mb31ig002, + panel); + struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; + + if (ctx->desc->backlight_off_to_display_off_delay_ms) + mipi_dsi_msleep(&dsi_ctx, ctx->desc->backlight_off_to_display_off_delay_ms); + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + + mipi_dsi_msleep(&dsi_ctx, 120); + + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + + if (ctx->desc->enter_sleep_to_reset_down_delay_ms) + mipi_dsi_msleep(&dsi_ctx, ctx->desc->enter_sleep_to_reset_down_delay_ms); + + return dsi_ctx.accum_err; +} + +static int boe_th101mb31ig002_unprepare(struct drm_panel *panel) +{ + struct boe_th101mb31ig002 *ctx = container_of(panel, + struct boe_th101mb31ig002, + panel); + + gpiod_set_value_cansleep(ctx->reset, 1); + gpiod_set_value_cansleep(ctx->enable, 0); + regulator_disable(ctx->power); + + if (ctx->desc->power_off_delay_ms) + msleep(ctx->desc->power_off_delay_ms); + + return 0; +} + +static int boe_th101mb31ig002_prepare(struct drm_panel *panel) +{ + struct boe_th101mb31ig002 *ctx = container_of(panel, + struct boe_th101mb31ig002, + panel); + struct device *dev = &ctx->dsi->dev; + int ret; + + ret = regulator_enable(ctx->power); + if (ret) { + dev_err(dev, "Failed to enable power supply: %d\n", ret); + return ret; + } + + if (ctx->desc->vcioo_to_lp11_delay_ms) + msleep(ctx->desc->vcioo_to_lp11_delay_ms); + + if (ctx->desc->lp11_before_reset) { + ret = mipi_dsi_dcs_nop(ctx->dsi); + if (ret) + return ret; + } + + if (ctx->desc->lp11_to_reset_delay_ms) + msleep(ctx->desc->lp11_to_reset_delay_ms); + + gpiod_set_value_cansleep(ctx->enable, 1); + msleep(50); + boe_th101mb31ig002_reset(ctx); + + ret = ctx->desc->init(ctx); + if (ret) + return ret; + + return 0; +} + +static const struct drm_display_mode boe_th101mb31ig002_default_mode = { + .clock = 73500, + .hdisplay = 800, + .hsync_start = 800 + 64, + .hsync_end = 800 + 64 + 16, + .htotal = 800 + 64 + 16 + 64, + .vdisplay = 1280, + .vsync_start = 1280 + 2, + .vsync_end = 1280 + 2 + 4, + .vtotal = 1280 + 2 + 4 + 12, + .width_mm = 135, + .height_mm = 216, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, +}; + +static const struct panel_desc boe_th101mb31ig002_desc = { + .modes = &boe_th101mb31ig002_default_mode, + .lanes = 4, + .format = MIPI_DSI_FMT_RGB888, + .mode_flags = MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_NO_EOT_PACKET | + MIPI_DSI_MODE_LPM, + .init = boe_th101mb31ig002_enable, +}; + +static const struct drm_display_mode starry_er88577_default_mode = { + .clock = (800 + 25 + 25 + 25) * (1280 + 20 + 4 + 12) * 60 / 1000, + .hdisplay = 800, + .hsync_start = 800 + 25, + .hsync_end = 800 + 25 + 25, + .htotal = 800 + 25 + 25 + 25, + .vdisplay = 1280, + .vsync_start = 1280 + 20, + .vsync_end = 1280 + 20 + 4, + .vtotal = 1280 + 20 + 4 + 12, + .width_mm = 135, + .height_mm = 216, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, +}; + +static const struct panel_desc starry_er88577_desc = { + .modes = &starry_er88577_default_mode, + .lanes = 4, + .format = MIPI_DSI_FMT_RGB888, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_MODE_LPM, + .init = starry_er88577_init_cmd, + .lp11_before_reset = true, + .vcioo_to_lp11_delay_ms = 5, + .lp11_to_reset_delay_ms = 50, + .backlight_off_to_display_off_delay_ms = 100, + .enter_sleep_to_reset_down_delay_ms = 100, + .power_off_delay_ms = 1000, +}; + +static int boe_th101mb31ig002_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct boe_th101mb31ig002 *ctx = container_of(panel, + struct boe_th101mb31ig002, + panel); + const struct drm_display_mode *desc_mode = ctx->desc->modes; + + connector->display_info.bpc = 8; + /* + * TODO: Remove once all drm drivers call + * drm_connector_set_orientation_from_panel() + */ + drm_connector_set_panel_orientation(connector, ctx->orientation); + + return drm_connector_helper_get_modes_fixed(connector, desc_mode); +} + +static enum drm_panel_orientation +boe_th101mb31ig002_get_orientation(struct drm_panel *panel) +{ + struct boe_th101mb31ig002 *ctx = container_of(panel, + struct boe_th101mb31ig002, + panel); + + return ctx->orientation; +} + +static const struct drm_panel_funcs boe_th101mb31ig002_funcs = { + .prepare = boe_th101mb31ig002_prepare, + .unprepare = boe_th101mb31ig002_unprepare, + .disable = boe_th101mb31ig002_disable, + .get_modes = boe_th101mb31ig002_get_modes, + .get_orientation = boe_th101mb31ig002_get_orientation, +}; + +static int boe_th101mb31ig002_dsi_probe(struct mipi_dsi_device *dsi) +{ + struct boe_th101mb31ig002 *ctx; + const struct panel_desc *desc; + int ret; + + ctx = devm_drm_panel_alloc(&dsi->dev, struct boe_th101mb31ig002, panel, + &boe_th101mb31ig002_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + mipi_dsi_set_drvdata(dsi, ctx); + ctx->dsi = dsi; + + desc = of_device_get_match_data(&dsi->dev); + dsi->lanes = desc->lanes; + dsi->format = desc->format; + dsi->mode_flags = desc->mode_flags; + ctx->desc = desc; + + ctx->power = devm_regulator_get(&dsi->dev, "power"); + if (IS_ERR(ctx->power)) + return dev_err_probe(&dsi->dev, PTR_ERR(ctx->power), + "Failed to get power regulator\n"); + + ctx->enable = devm_gpiod_get(&dsi->dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(ctx->enable)) + return dev_err_probe(&dsi->dev, PTR_ERR(ctx->enable), + "Failed to get enable GPIO\n"); + + ctx->reset = devm_gpiod_get_optional(&dsi->dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->reset)) + return dev_err_probe(&dsi->dev, PTR_ERR(ctx->reset), + "Failed to get reset GPIO\n"); + + ret = of_drm_get_panel_orientation(dsi->dev.of_node, + &ctx->orientation); + if (ret) + return dev_err_probe(&dsi->dev, ret, + "Failed to get orientation\n"); + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return ret; + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err_probe(&dsi->dev, ret, + "Failed to attach panel to DSI host\n"); + drm_panel_remove(&ctx->panel); + return ret; + } + + return 0; +} + +static void boe_th101mb31ig002_dsi_remove(struct mipi_dsi_device *dsi) +{ + struct boe_th101mb31ig002 *ctx = mipi_dsi_get_drvdata(dsi); + + mipi_dsi_detach(dsi); + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id boe_th101mb31ig002_of_match[] = { + { + .compatible = "boe,th101mb31ig002-28a", + .data = &boe_th101mb31ig002_desc + }, + { + .compatible = "starry,er88577", + .data = &starry_er88577_desc + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, boe_th101mb31ig002_of_match); + +static struct mipi_dsi_driver boe_th101mb31ig002_driver = { + .driver = { + .name = "boe-th101mb31ig002-28a", + .of_match_table = boe_th101mb31ig002_of_match, + }, + .probe = boe_th101mb31ig002_dsi_probe, + .remove = boe_th101mb31ig002_dsi_remove, +}; +module_mipi_dsi_driver(boe_th101mb31ig002_driver); + +MODULE_AUTHOR("Alexander Warnecke <awarnecke002@hotmail.com>"); +MODULE_DESCRIPTION("BOE TH101MB31IG002-28A MIPI-DSI LCD panel"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-boe-tv101wum-ll2.c b/drivers/gpu/drm/panel/panel-boe-tv101wum-ll2.c new file mode 100644 index 000000000000..20b6e11a7d84 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-boe-tv101wum-ll2.c @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Generated with linux-mdss-dsi-panel-driver-generator from vendor device tree: +// Copyright (c) 2013, The Linux Foundation. All rights reserved. +// Copyright (c) 2024, Neil Armstrong <neil.armstrong@linaro.org> + +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regulator/consumer.h> + +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> +#include <drm/drm_probe_helper.h> + +struct boe_tv101wum_ll2 { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data *supplies; +}; + +static const struct regulator_bulk_data boe_tv101wum_ll2_supplies[] = { + { .supply = "vsp" }, + { .supply = "vsn" }, +}; + +static inline struct boe_tv101wum_ll2 *to_boe_tv101wum_ll2(struct drm_panel *panel) +{ + return container_of(panel, struct boe_tv101wum_ll2, panel); +} + +static void boe_tv101wum_ll2_reset(struct boe_tv101wum_ll2 *ctx) +{ + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(5000, 6000); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(5000, 6000); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + + msleep(120); +} + +static int boe_tv101wum_ll2_on(struct boe_tv101wum_ll2 *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + + mipi_dsi_msleep(&dsi_ctx, 120); + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x50, 0x5a, 0x0e); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x80, 0xff, 0x81, 0x68, 0x6c, 0x22, + 0x6d, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x50, 0x5a, 0x23); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x90, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x94, 0x2c, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x50, 0x5a, 0x19); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xa2, 0x38); + + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x50, 0x5a, 0x0c); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x80, 0xfd); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x50, 0x00); + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + + mipi_dsi_msleep(&dsi_ctx, 20); + + return dsi_ctx.accum_err; +} + +static void boe_tv101wum_ll2_off(struct boe_tv101wum_ll2 *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + + mipi_dsi_msleep(&dsi_ctx, 70); + + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + + mipi_dsi_msleep(&dsi_ctx, 20); + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x04, 0x5a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x05, 0x5a); + + mipi_dsi_msleep(&dsi_ctx, 150); +} + +static int boe_tv101wum_ll2_prepare(struct drm_panel *panel) +{ + struct boe_tv101wum_ll2 *ctx = to_boe_tv101wum_ll2(panel); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(boe_tv101wum_ll2_supplies), + ctx->supplies); + if (ret < 0) + return ret; + + boe_tv101wum_ll2_reset(ctx); + + ret = boe_tv101wum_ll2_on(ctx); + if (ret < 0) { + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(boe_tv101wum_ll2_supplies), + ctx->supplies); + return ret; + } + + return 0; +} + +static int boe_tv101wum_ll2_unprepare(struct drm_panel *panel) +{ + struct boe_tv101wum_ll2 *ctx = to_boe_tv101wum_ll2(panel); + + /* Ignore errors on failure, in any case set gpio and disable regulators */ + boe_tv101wum_ll2_off(ctx); + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + + regulator_bulk_disable(ARRAY_SIZE(boe_tv101wum_ll2_supplies), + ctx->supplies); + + return 0; +} + +static const struct drm_display_mode boe_tv101wum_ll2_mode = { + .clock = (1200 + 27 + 8 + 12) * (1920 + 155 + 8 + 32) * 60 / 1000, + .hdisplay = 1200, + .hsync_start = 1200 + 27, + .hsync_end = 1200 + 27 + 8, + .htotal = 1200 + 27 + 8 + 12, + .vdisplay = 1920, + .vsync_start = 1920 + 155, + .vsync_end = 1920 + 155 + 8, + .vtotal = 1920 + 155 + 8 + 32, + .width_mm = 136, + .height_mm = 217, + .type = DRM_MODE_TYPE_DRIVER, +}; + +static int boe_tv101wum_ll2_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + /* We do not set display_info.bpc since unset value is bpc=8 by default */ + return drm_connector_helper_get_modes_fixed(connector, &boe_tv101wum_ll2_mode); +} + +static const struct drm_panel_funcs boe_tv101wum_ll2_panel_funcs = { + .prepare = boe_tv101wum_ll2_prepare, + .unprepare = boe_tv101wum_ll2_unprepare, + .get_modes = boe_tv101wum_ll2_get_modes, +}; + +static int boe_tv101wum_ll2_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct boe_tv101wum_ll2 *ctx; + int ret; + + ctx = devm_drm_panel_alloc(dev, struct boe_tv101wum_ll2, panel, + &boe_tv101wum_ll2_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ret = devm_regulator_bulk_get_const(&dsi->dev, + ARRAY_SIZE(boe_tv101wum_ll2_supplies), + boe_tv101wum_ll2_supplies, + &ctx->supplies); + if (ret < 0) + return ret; + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), + "Failed to get reset-gpios\n"); + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_VIDEO_HSE; + + ctx->panel.prepare_prev_first = true; + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return dev_err_probe(dev, ret, "Failed to get backlight\n"); + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + drm_panel_remove(&ctx->panel); + return dev_err_probe(dev, ret, "Failed to attach to DSI host\n"); + } + + return 0; +} + +static void boe_tv101wum_ll2_remove(struct mipi_dsi_device *dsi) +{ + struct boe_tv101wum_ll2 *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id boe_tv101wum_ll2_of_match[] = { + { .compatible = "boe,tv101wum-ll2" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, boe_tv101wum_ll2_of_match); + +static struct mipi_dsi_driver boe_tv101wum_ll2_driver = { + .probe = boe_tv101wum_ll2_probe, + .remove = boe_tv101wum_ll2_remove, + .driver = { + .name = "panel-boe-tv101wum_ll2", + .of_match_table = boe_tv101wum_ll2_of_match, + }, +}; +module_mipi_dsi_driver(boe_tv101wum_ll2_driver); + +MODULE_DESCRIPTION("DRM driver for BOE TV101WUM-LL2 Panel"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-boe-tv101wum-nl6.c b/drivers/gpu/drm/panel/panel-boe-tv101wum-nl6.c new file mode 100644 index 000000000000..d5fe105bdbdd --- /dev/null +++ b/drivers/gpu/drm/panel/panel-boe-tv101wum-nl6.c @@ -0,0 +1,1826 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018 MediaTek Inc. + * Author: Jitao Shi <jitao.shi@mediatek.com> + */ + +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regulator/consumer.h> + +#include <drm/drm_connector.h> +#include <drm/drm_crtc.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_panel.h> + +#include <video/mipi_display.h> + +struct boe_panel; + +struct panel_desc { + const struct drm_display_mode *modes; + unsigned int bpc; + + /** + * @width_mm: width of the panel's active display area + * @height_mm: height of the panel's active display area + */ + struct { + unsigned int width_mm; + unsigned int height_mm; + } size; + + unsigned long mode_flags; + enum mipi_dsi_pixel_format format; + int (*init)(struct boe_panel *boe); + unsigned int lanes; + bool discharge_on_disable; + bool lp11_before_reset; +}; + +struct boe_panel { + struct drm_panel base; + struct mipi_dsi_device *dsi; + + const struct panel_desc *desc; + + enum drm_panel_orientation orientation; + struct regulator *pp3300; + struct regulator *pp1800; + struct regulator *avee; + struct regulator *avdd; + struct gpio_desc *enable_gpio; +}; + +#define NT36523_DCS_SWITCH_PAGE 0xff + +#define nt36523_switch_page(ctx, page) \ + mipi_dsi_dcs_write_seq_multi(ctx, NT36523_DCS_SWITCH_PAGE, (page)) + +static void nt36523_enable_reload_cmds(struct mipi_dsi_multi_context *ctx) +{ + mipi_dsi_dcs_write_seq_multi(ctx, 0xfb, 0x01); +} + +static int boe_tv110c9m_init(struct boe_panel *boe) +{ + struct mipi_dsi_multi_context ctx = { .dsi = boe->dsi }; + + nt36523_switch_page(&ctx, 0x20); + nt36523_enable_reload_cmds(&ctx); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x05, 0xd9); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x07, 0x78); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x08, 0x5a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0d, 0x63); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0e, 0x91); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0f, 0x73); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x95, 0xe6); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x96, 0xf0); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x30, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x6d, 0x66); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x75, 0xa2); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x77, 0x3b); + + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x00, 0x08, 0x00, 0x23, 0x00, 0x4d, 0x00, 0x6d, + 0x00, 0x89, 0x00, 0xa1, 0x00, 0xb6, 0x00, 0xc9); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb1, 0x00, 0xda, 0x01, 0x13, 0x01, 0x3c, 0x01, 0x7e, + 0x01, 0xab, 0x01, 0xf7, 0x02, 0x2f, 0x02, 0x31); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb2, 0x02, 0x67, 0x02, 0xa6, 0x02, 0xd1, 0x03, 0x08, + 0x03, 0x2e, 0x03, 0x5b, 0x03, 0x6b, 0x03, 0x7b); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb3, 0x03, 0x8e, 0x03, 0xa2, 0x03, 0xb7, 0x03, 0xe7, + 0x03, 0xfd, 0x03, 0xff); + + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb4, 0x00, 0x08, 0x00, 0x23, 0x00, 0x4d, 0x00, 0x6d, + 0x00, 0x89, 0x00, 0xa1, 0x00, 0xb6, 0x00, 0xc9); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb5, 0x00, 0xda, 0x01, 0x13, 0x01, 0x3c, 0x01, 0x7e, + 0x01, 0xab, 0x01, 0xf7, 0x02, 0x2f, 0x02, 0x31); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb6, 0x02, 0x67, 0x02, 0xa6, 0x02, 0xd1, 0x03, 0x08, + 0x03, 0x2e, 0x03, 0x5b, 0x03, 0x6b, 0x03, 0x7b); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb7, 0x03, 0x8e, 0x03, 0xa2, 0x03, 0xb7, 0x03, 0xe7, + 0x03, 0xfd, 0x03, 0xff); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb8, 0x00, 0x08, 0x00, 0x23, 0x00, 0x4d, 0x00, 0x6d, + 0x00, 0x89, 0x00, 0xa1, 0x00, 0xb6, 0x00, 0xc9); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb9, 0x00, 0xda, 0x01, 0x13, 0x01, 0x3c, 0x01, 0x7e, + 0x01, 0xab, 0x01, 0xf7, 0x02, 0x2f, 0x02, 0x31); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xba, 0x02, 0x67, 0x02, 0xa6, 0x02, 0xd1, 0x03, 0x08, + 0x03, 0x2e, 0x03, 0x5b, 0x03, 0x6b, 0x03, 0x7b); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbb, 0x03, 0x8e, 0x03, 0xa2, 0x03, 0xb7, 0x03, 0xe7, + 0x03, 0xfd, 0x03, 0xff); + + nt36523_switch_page(&ctx, 0x21); + nt36523_enable_reload_cmds(&ctx); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x45, 0x00, 0x65, + 0x00, 0x81, 0x00, 0x99, 0x00, 0xae, 0x00, 0xc1); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb1, 0x00, 0xd2, 0x01, 0x0b, 0x01, 0x34, 0x01, 0x76, + 0x01, 0xa3, 0x01, 0xef, 0x02, 0x27, 0x02, 0x29); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb2, 0x02, 0x5f, 0x02, 0x9e, 0x02, 0xc9, 0x03, 0x00, + 0x03, 0x26, 0x03, 0x53, 0x03, 0x63, 0x03, 0x73); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb3, 0x03, 0x86, 0x03, 0x9a, 0x03, 0xaf, 0x03, 0xdf, + 0x03, 0xf5, 0x03, 0xe0); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb4, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x45, 0x00, 0x65, + 0x00, 0x81, 0x00, 0x99, 0x00, 0xae, 0x00, 0xc1); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb5, 0x00, 0xd2, 0x01, 0x0b, 0x01, 0x34, 0x01, 0x76, + 0x01, 0xa3, 0x01, 0xef, 0x02, 0x27, 0x02, 0x29); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb6, 0x02, 0x5f, 0x02, 0x9e, 0x02, 0xc9, 0x03, 0x00, + 0x03, 0x26, 0x03, 0x53, 0x03, 0x63, 0x03, 0x73); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb7, 0x03, 0x86, 0x03, 0x9a, 0x03, 0xaf, 0x03, 0xdf, + 0x03, 0xf5, 0x03, 0xe0); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb8, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x45, 0x00, 0x65, + 0x00, 0x81, 0x00, 0x99, 0x00, 0xae, 0x00, 0xc1); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb9, 0x00, 0xd2, 0x01, 0x0b, 0x01, 0x34, 0x01, 0x76, + 0x01, 0xa3, 0x01, 0xef, 0x02, 0x27, 0x02, 0x29); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xba, 0x02, 0x5f, 0x02, 0x9e, 0x02, 0xc9, 0x03, 0x00, + 0x03, 0x26, 0x03, 0x53, 0x03, 0x63, 0x03, 0x73); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbb, 0x03, 0x86, 0x03, 0x9a, 0x03, 0xaf, 0x03, 0xdf, + 0x03, 0xf5, 0x03, 0xe0); + + nt36523_switch_page(&ctx, 0x24); + nt36523_enable_reload_cmds(&ctx); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x01, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x02, 0x1c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x03, 0x1c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x04, 0x1d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x05, 0x1d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x06, 0x04); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x07, 0x04); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x08, 0x0f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x09, 0x0f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0a, 0x0e); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0b, 0x0e); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0c, 0x0d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0d, 0x0d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0e, 0x0c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0f, 0x0c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x10, 0x08); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x11, 0x08); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x12, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x13, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x14, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x15, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x16, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x17, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x18, 0x1c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x19, 0x1c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1a, 0x1d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1b, 0x1d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1c, 0x04); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1d, 0x04); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1e, 0x0f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1f, 0x0f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x20, 0x0e); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x21, 0x0e); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x22, 0x0d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x23, 0x0d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x24, 0x0c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x25, 0x0c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x26, 0x08); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x27, 0x08); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x28, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x29, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x2a, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x2b, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x2d, 0x20); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x2f, 0x0a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x30, 0x44); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x33, 0x0c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x34, 0x32); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x37, 0x44); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x38, 0x40); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x39, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x3a, 0x5d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x3b, 0x60); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x3d, 0x42); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x3f, 0x06); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x43, 0x06); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x47, 0x66); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x4a, 0x5d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x4b, 0x60); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x4c, 0x91); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x4d, 0x21); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x4e, 0x43); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x51, 0x12); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x52, 0x34); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x55, 0x82, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x56, 0x04); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x58, 0x21); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x59, 0x30); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5a, 0x60); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5b, 0x50); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5e, 0x00, 0x06); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5f, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x65, 0x82); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x7e, 0x20); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x7f, 0x3c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x82, 0x04); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x97, 0xc0); + + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb6, 0x05, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x05, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x91, 0x44); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x92, 0xa9); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x93, 0x1a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x94, 0x96); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd7, 0x55); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xda, 0x0a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xde, 0x08); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xdb, 0x05); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xdc, 0xa9); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xdd, 0x22); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xdf, 0x05); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe0, 0xa9); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe1, 0x05); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe2, 0xa9); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe3, 0x05); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe4, 0xa9); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe5, 0x05); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe6, 0xa9); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5c, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5d, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x8d, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x8e, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb5, 0x90); + + nt36523_switch_page(&ctx, 0x25); + nt36523_enable_reload_cmds(&ctx); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x05, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x19, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1f, 0x60); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x20, 0x50); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x26, 0x60); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x27, 0x50); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x33, 0x60); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x34, 0x50); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x3f, 0xe0); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x40, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x44, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x45, 0x40); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x48, 0x60); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x49, 0x50); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5b, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5c, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5d, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5e, 0xd0); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x61, 0x60); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x62, 0x50); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xf1, 0x10); + + nt36523_switch_page(&ctx, 0x2a); + nt36523_enable_reload_cmds(&ctx); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x64, 0x16); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x67, 0x16); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x6a, 0x16); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x70, 0x30); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xa2, 0xf3); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xa3, 0xff); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xa4, 0xff); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xa5, 0xff); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd6, 0x08); + + nt36523_switch_page(&ctx, 0x26); + nt36523_enable_reload_cmds(&ctx); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x00, 0xa1); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x02, 0x31); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x04, 0x28); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x06, 0x30); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0c, 0x16); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0d, 0x0d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0f, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x11, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x12, 0x50); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x13, 0x56); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x14, 0x57); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x15, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x16, 0x10); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x17, 0xa0); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x18, 0x86); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x19, 0x0d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1a, 0x7f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1b, 0x0c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1c, 0xbf); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x22, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x23, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x2a, 0x0d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x2b, 0x7f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1d, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1e, 0x65); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1f, 0x65); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x24, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x25, 0x65); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x2f, 0x05); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x30, 0x65); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x31, 0x05); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x32, 0x7d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x39, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x3a, 0x65); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x20, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x33, 0x11); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x34, 0x78); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x35, 0x16); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc8, 0x04); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc9, 0x9e); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xca, 0x4e); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcb, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xa9, 0x49); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xaa, 0x4b); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xab, 0x48); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xac, 0x43); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xad, 0x40); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xae, 0x50); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xaf, 0x44); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x54); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb1, 0x4e); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb2, 0x4d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb3, 0x4c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb4, 0x41); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb5, 0x47); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb6, 0x53); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb7, 0x3e); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb8, 0x51); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb9, 0x3c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xba, 0x3b); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbb, 0x46); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbc, 0x45); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbd, 0x55); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbe, 0x3d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbf, 0x3f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc0, 0x52); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc1, 0x4a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc2, 0x39); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc3, 0x4f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc4, 0x3a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc5, 0x42); + + nt36523_switch_page(&ctx, 0x27); + nt36523_enable_reload_cmds(&ctx); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x56, 0x06); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x58, 0x80); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x59, 0x75); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5a, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5b, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5c, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5d, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5e, 0x20); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5f, 0x10); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x60, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x61, 0x2e); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x62, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x63, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x64, 0x43); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x65, 0x2d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x66, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x67, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x68, 0x44); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x78, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc3, 0x00); + + nt36523_switch_page(&ctx, 0x2a); + nt36523_enable_reload_cmds(&ctx); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x22, 0x2f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x23, 0x08); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x24, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x25, 0x65); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x26, 0xf8); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x27, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x28, 0x1a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x29, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x2a, 0x1a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x2b, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x2d, 0x1a); + + nt36523_switch_page(&ctx, 0x23); + nt36523_enable_reload_cmds(&ctx); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x00, 0x80); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x07, 0x00); + + nt36523_switch_page(&ctx, 0xe0); + nt36523_enable_reload_cmds(&ctx); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x14, 0x60); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x16, 0xc0); + + nt36523_switch_page(&ctx, 0xf0); + nt36523_enable_reload_cmds(&ctx); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x3a, 0x08); + + nt36523_switch_page(&ctx, 0x10); + nt36523_enable_reload_cmds(&ctx); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb9, 0x01); + + nt36523_switch_page(&ctx, 0x20); + nt36523_enable_reload_cmds(&ctx); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x18, 0x40); + + nt36523_switch_page(&ctx, 0x10); + nt36523_enable_reload_cmds(&ctx); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb9, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x35, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x51, 0x00, 0xff); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x53, 0x24); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x55, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbb, 0x13); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x3b, 0x03, 0x96, 0x1a, 0x04, 0x04); + + mipi_dsi_msleep(&ctx, 100); + + mipi_dsi_dcs_write_seq_multi(&ctx, 0x11); + + mipi_dsi_msleep(&ctx, 200); + + mipi_dsi_dcs_write_seq_multi(&ctx, 0x29); + + mipi_dsi_msleep(&ctx, 100); + + return 0; +}; + +static int inx_hj110iz_init(struct boe_panel *boe) +{ + struct mipi_dsi_multi_context ctx = { .dsi = boe->dsi }; + + nt36523_switch_page(&ctx, 0x20); + nt36523_enable_reload_cmds(&ctx); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x05, 0xd1); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x06, 0xc0); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x07, 0x87); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x08, 0x4b); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0d, 0x63); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0e, 0x91); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0f, 0x69); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x94, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x95, 0xf5); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x96, 0xf5); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x9d, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x9e, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x69, 0x98); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x75, 0xa2); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x77, 0xb3); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x58, 0x43); + + nt36523_switch_page(&ctx, 0x24); + nt36523_enable_reload_cmds(&ctx); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x91, 0x44); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x92, 0x4c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x94, 0x86); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x60, 0x96); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x61, 0xd0); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x63, 0x70); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc2, 0xca); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x00, 0x03); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x01, 0x03); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x02, 0x03); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x03, 0x29); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x04, 0x22); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x05, 0x22); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x06, 0x0b); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x07, 0x1d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x08, 0x1c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x09, 0x05); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0a, 0x08); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0b, 0x09); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0c, 0x0a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0d, 0x0c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0e, 0x0d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0f, 0x0e); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x10, 0x0f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x11, 0x10); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x12, 0x11); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x13, 0x04); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x14, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x15, 0x03); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x16, 0x03); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x17, 0x03); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x18, 0x03); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x19, 0x29); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1a, 0x22); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1b, 0x22); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1c, 0x0b); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1d, 0x1d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1e, 0x1c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1f, 0x05); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x20, 0x08); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x21, 0x09); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x22, 0x0a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x23, 0x0c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x24, 0x0d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x25, 0x0e); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x26, 0x0f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x27, 0x10); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x28, 0x11); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x29, 0x04); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x2a, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x2b, 0x03); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x2f, 0x0a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x30, 0x35); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x37, 0xa7); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x39, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x3a, 0x46); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x3b, 0x32); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x3d, 0x12); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x3f, 0x33); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x40, 0x31); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x41, 0x40); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x42, 0x42); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x47, 0x77); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x48, 0x77); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x4a, 0x45); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x4b, 0x45); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x4c, 0x14); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x4d, 0x21); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x4e, 0x43); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x4f, 0x65); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x55, 0x06); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x56, 0x06); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x58, 0x21); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x59, 0x70); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5a, 0x46); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5b, 0x32); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5c, 0x88); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5e, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5f, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x7a, 0xff); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x7b, 0xff); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x7c, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x7d, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x7e, 0x20); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x7f, 0x3c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x80, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x81, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x82, 0x08); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x97, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc5, 0x10); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd7, 0x55); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd8, 0x55); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd9, 0x23); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xda, 0x05); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xdb, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xdc, 0x65); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xdd, 0x55); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xde, 0x27); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xdf, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe0, 0x65); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe1, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe2, 0x65); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe3, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe4, 0x65); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe5, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe6, 0x65); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe7, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe8, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe9, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xea, 0x65); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xeb, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xee, 0x65); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xef, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xf0, 0x65); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb6, 0x05, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x05, 0x00, 0x00); + + nt36523_switch_page(&ctx, 0x25); + nt36523_enable_reload_cmds(&ctx); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x05, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xf1, 0x10); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1e, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1f, 0x46); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x20, 0x32); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x25, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x26, 0x46); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x27, 0x32); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x3f, 0x80); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x40, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x43, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x44, 0x46); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x45, 0x46); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x48, 0x46); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x49, 0x32); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5b, 0x80); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5c, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5d, 0x46); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5e, 0x32); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5f, 0x46); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x60, 0x32); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x61, 0x46); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x62, 0x32); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x68, 0x0c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x6c, 0x0d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x6e, 0x0d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x78, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x79, 0xc5); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x7a, 0x0c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x7b, 0xb0); + + nt36523_switch_page(&ctx, 0x26); + nt36523_enable_reload_cmds(&ctx); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x00, 0xa1); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x02, 0x31); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0a, 0xf4); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x04, 0x50); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x06, 0x30); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0c, 0x16); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0d, 0x0d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0f, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x11, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x12, 0x50); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x13, 0x40); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x14, 0x58); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x15, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x16, 0x10); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x17, 0xa0); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x18, 0x86); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x22, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x23, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x19, 0x0e); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1a, 0x31); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1b, 0x0d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1c, 0x29); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x2a, 0x0e); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x2b, 0x31); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1d, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1e, 0x62); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1f, 0x62); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x2f, 0x06); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x30, 0x62); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x31, 0x06); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x32, 0x7f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x33, 0x11); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x34, 0x89); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x35, 0x67); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x39, 0x0b); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x3a, 0x62); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x3b, 0x06); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc8, 0x04); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc9, 0x89); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xca, 0x4e); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcb, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xa9, 0x3f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xaa, 0x3e); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xab, 0x3d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xac, 0x3c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xad, 0x3b); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xae, 0x3a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xaf, 0x39); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x38); + + nt36523_switch_page(&ctx, 0x27); + nt36523_enable_reload_cmds(&ctx); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd0, 0x11); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd1, 0x54); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xde, 0x43); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xdf, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc0, 0x18); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc1, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc2, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc3, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x56, 0x06); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x58, 0x80); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x59, 0x78); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5a, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5b, 0x18); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5c, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5d, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5e, 0x20); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5f, 0x10); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x60, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x61, 0x1c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x62, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x63, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x64, 0x44); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x65, 0x1b); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x66, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x67, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x68, 0x44); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x98, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb4, 0x03); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x9b, 0xbe); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xab, 0x14); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbc, 0x08); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbd, 0x28); + + nt36523_switch_page(&ctx, 0x2a); + nt36523_enable_reload_cmds(&ctx); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x22, 0x2f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x23, 0x08); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x24, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x25, 0x62); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x26, 0xf8); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x27, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x28, 0x1a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x29, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x2a, 0x1a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x2b, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x2d, 0x1a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x64, 0x96); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x65, 0x10); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x66, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x67, 0x96); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x68, 0x10); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x69, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x6a, 0x96); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x6b, 0x10); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x6c, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x70, 0x92); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x71, 0x10); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x72, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x79, 0x96); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x7a, 0x10); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x88, 0x96); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x89, 0x10); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xa2, 0x3f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xa3, 0x30); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xa4, 0xc0); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xa5, 0x03); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe8, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x97, 0x3c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x98, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x99, 0x95); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x9a, 0x06); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x9b, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x9c, 0x0b); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x9d, 0x0a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x9e, 0x90); + + nt36523_switch_page(&ctx, 0x25); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x13, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x14, 0xd7); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xdb, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xdc, 0xd7); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x17, 0xcf); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x19, 0x0f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1b, 0x5b); + + nt36523_switch_page(&ctx, 0x20); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x24, 0x00, 0x38, + 0x00, 0x4c, 0x00, 0x5e, 0x00, 0x6f, 0x00, 0x7e); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb1, 0x00, 0x8c, 0x00, 0xbe, 0x00, 0xe5, 0x01, 0x27, + 0x01, 0x58, 0x01, 0xa8, 0x01, 0xe8, 0x01, 0xea); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb2, 0x02, 0x28, 0x02, 0x71, 0x02, 0x9e, 0x02, 0xda, + 0x03, 0x00, 0x03, 0x31, 0x03, 0x40, 0x03, 0x51); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb3, 0x03, 0x62, 0x03, 0x75, 0x03, 0x89, 0x03, 0x9c, + 0x03, 0xaa, 0x03, 0xb2); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb4, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x27, 0x00, 0x3d, + 0x00, 0x52, 0x00, 0x64, 0x00, 0x75, 0x00, 0x84); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb5, 0x00, 0x93, 0x00, 0xc5, 0x00, 0xec, 0x01, 0x2c, + 0x01, 0x5d, 0x01, 0xac, 0x01, 0xec, 0x01, 0xee); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb6, 0x02, 0x2b, 0x02, 0x73, 0x02, 0xa0, 0x02, 0xdb, + 0x03, 0x01, 0x03, 0x31, 0x03, 0x41, 0x03, 0x51); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb7, 0x03, 0x63, 0x03, 0x75, 0x03, 0x89, 0x03, 0x9c, + 0x03, 0xaa, 0x03, 0xb2); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb8, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x2a, 0x00, 0x40, + 0x00, 0x56, 0x00, 0x68, 0x00, 0x7a, 0x00, 0x89); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb9, 0x00, 0x98, 0x00, 0xc9, 0x00, 0xf1, 0x01, 0x30, + 0x01, 0x61, 0x01, 0xb0, 0x01, 0xef, 0x01, 0xf1); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xba, 0x02, 0x2e, 0x02, 0x76, 0x02, 0xa3, 0x02, 0xdd, + 0x03, 0x02, 0x03, 0x32, 0x03, 0x42, 0x03, 0x53); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbb, 0x03, 0x66, 0x03, 0x75, 0x03, 0x89, 0x03, 0x9c, + 0x03, 0xaa, 0x03, 0xb2); + + nt36523_switch_page(&ctx, 0x21); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x24, 0x00, 0x38, + 0x00, 0x4c, 0x00, 0x5e, 0x00, 0x6f, 0x00, 0x7e); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb1, 0x00, 0x8c, 0x00, 0xbe, 0x00, 0xe5, 0x01, 0x27, + 0x01, 0x58, 0x01, 0xa8, 0x01, 0xe8, 0x01, 0xea); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb2, 0x02, 0x28, 0x02, 0x71, 0x02, 0x9e, 0x02, 0xda, + 0x03, 0x00, 0x03, 0x31, 0x03, 0x40, 0x03, 0x51); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb3, 0x03, 0x62, 0x03, 0x77, 0x03, 0x90, 0x03, 0xac, + 0x03, 0xca, 0x03, 0xda); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb4, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x27, 0x00, 0x3d, + 0x00, 0x52, 0x00, 0x64, 0x00, 0x75, 0x00, 0x84); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb5, 0x00, 0x93, 0x00, 0xc5, 0x00, 0xec, 0x01, 0x2c, + 0x01, 0x5d, 0x01, 0xac, 0x01, 0xec, 0x01, 0xee); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb6, 0x02, 0x2b, 0x02, 0x73, 0x02, 0xa0, 0x02, 0xdb, + 0x03, 0x01, 0x03, 0x31, 0x03, 0x41, 0x03, 0x51); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb7, 0x03, 0x63, 0x03, 0x77, 0x03, 0x90, 0x03, 0xac, + 0x03, 0xca, 0x03, 0xda); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb8, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x2a, 0x00, 0x40, + 0x00, 0x56, 0x00, 0x68, 0x00, 0x7a, 0x00, 0x89); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb9, 0x00, 0x98, 0x00, 0xc9, 0x00, 0xf1, 0x01, 0x30, + 0x01, 0x61, 0x01, 0xb0, 0x01, 0xef, 0x01, 0xf1); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xba, 0x02, 0x2e, 0x02, 0x76, 0x02, 0xa3, 0x02, 0xdd, + 0x03, 0x02, 0x03, 0x32, 0x03, 0x42, 0x03, 0x53); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbb, 0x03, 0x66, 0x03, 0x77, 0x03, 0x90, 0x03, 0xac, + 0x03, 0xca, 0x03, 0xda); + + nt36523_switch_page(&ctx, 0xf0); + nt36523_enable_reload_cmds(&ctx); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x3a, 0x08); + + nt36523_switch_page(&ctx, 0x10); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb9, 0x01); + + nt36523_switch_page(&ctx, 0x20); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x18, 0x40); + + nt36523_switch_page(&ctx, 0x10); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb9, 0x02); + + nt36523_switch_page(&ctx, 0x10); + nt36523_enable_reload_cmds(&ctx); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x35, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x3b, 0x03, 0xae, 0x1a, 0x04, 0x04); + + mipi_dsi_msleep(&ctx, 100); + + mipi_dsi_dcs_write_seq_multi(&ctx, 0x11); + + mipi_dsi_msleep(&ctx, 200); + + mipi_dsi_dcs_write_seq_multi(&ctx, 0x29); + + mipi_dsi_msleep(&ctx, 100); + + return 0; +}; + +static int boe_init(struct boe_panel *boe) +{ + struct mipi_dsi_multi_context ctx = { .dsi = boe->dsi }; + + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x05); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb1, 0xe5); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb3, 0x52); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb3, 0x88); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x04); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb8, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb6, 0x03); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xba, 0x8b); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbf, 0x1a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc0, 0x0f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc2, 0x0c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc3, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc4, 0x0c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc5, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe0, 0x26); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe1, 0x26); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xdc, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xdd, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcc, 0x26); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcd, 0x26); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc8, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc9, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd2, 0x03); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd3, 0x03); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe6, 0x04); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe7, 0x04); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc4, 0x09); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc5, 0x09); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd8, 0x0a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd9, 0x0a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc2, 0x0b); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc3, 0x0b); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd6, 0x0c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd7, 0x0c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc0, 0x05); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc1, 0x05); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd4, 0x06); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd5, 0x06); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xca, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcb, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xde, 0x08); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xdf, 0x08); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc0, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc1, 0x0d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc2, 0x17); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc3, 0x26); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc4, 0x31); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc5, 0x1c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc6, 0x2c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc7, 0x33); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc8, 0x31); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc9, 0x37); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xca, 0x37); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcb, 0x37); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcc, 0x39); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcd, 0x2e); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xce, 0x2f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcf, 0x2f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd0, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd2, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd3, 0x0d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd4, 0x17); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd5, 0x26); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd6, 0x31); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd7, 0x3f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd8, 0x3f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd9, 0x3f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xda, 0x3f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xdb, 0x37); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xdc, 0x37); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xdd, 0x37); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xde, 0x39); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xdf, 0x2e); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe0, 0x2f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe1, 0x2f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe2, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x03); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc8, 0x0b); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc9, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc3, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe7, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc5, 0x2a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xde, 0x2a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xca, 0x43); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc9, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe4, 0xc0); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe5, 0x0d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcb, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x06); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb8, 0xa5); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc0, 0xa5); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc7, 0x0f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd5, 0x32); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb8, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc0, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbc, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb1, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb2, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb3, 0x0f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb4, 0x25); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb5, 0x39); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb6, 0x4e); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb7, 0x72); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb8, 0x97); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb9, 0xdc); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xba, 0x22); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbb, 0xa4); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbc, 0x2b); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbd, 0x2f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbe, 0xa9); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbf, 0x25); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc0, 0x61); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc1, 0x97); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc2, 0xb2); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc3, 0xcd); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc4, 0xd9); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc5, 0xe7); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc6, 0xf4); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc7, 0xfa); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc8, 0xfc); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc9, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xca, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcb, 0x16); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcc, 0xaf); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcd, 0xff); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xce, 0xff); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x08); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb1, 0x04); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb2, 0x05); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb3, 0x11); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb4, 0x24); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb5, 0x39); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb6, 0x4f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb7, 0x72); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb8, 0x98); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb9, 0xdc); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xba, 0x23); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbb, 0xa6); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbc, 0x2c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbd, 0x30); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbe, 0xaa); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbf, 0x26); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc0, 0x62); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc1, 0x9b); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc2, 0xb5); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc3, 0xcf); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc4, 0xdb); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc5, 0xe8); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc6, 0xf5); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc7, 0xfa); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc8, 0xfc); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc9, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xca, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcb, 0x16); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcc, 0xaf); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcd, 0xff); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xce, 0xff); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x09); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb1, 0x04); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb2, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb3, 0x16); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb4, 0x24); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb5, 0x3b); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb6, 0x4f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb7, 0x73); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb8, 0x99); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb9, 0xe0); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xba, 0x26); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbb, 0xad); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbc, 0x36); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbd, 0x3a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbe, 0xae); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbf, 0x2a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc0, 0x66); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc1, 0x9e); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc2, 0xb8); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc3, 0xd1); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc4, 0xdd); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc5, 0xe9); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc6, 0xf6); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc7, 0xfa); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc8, 0xfc); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc9, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xca, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcb, 0x16); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcc, 0xaf); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcd, 0xff); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xce, 0xff); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x0a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb1, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb2, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb3, 0x0f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb4, 0x25); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb5, 0x39); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb6, 0x4e); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb7, 0x72); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb8, 0x97); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb9, 0xdc); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xba, 0x22); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbb, 0xa4); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbc, 0x2b); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbd, 0x2f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbe, 0xa9); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbf, 0x25); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc0, 0x61); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc1, 0x97); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc2, 0xb2); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc3, 0xcd); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc4, 0xd9); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc5, 0xe7); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc6, 0xf4); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc7, 0xfa); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc8, 0xfc); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc9, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xca, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcb, 0x16); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcc, 0xaf); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcd, 0xff); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xce, 0xff); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x0b); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb1, 0x04); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb2, 0x05); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb3, 0x11); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb4, 0x24); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb5, 0x39); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb6, 0x4f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb7, 0x72); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb8, 0x98); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb9, 0xdc); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xba, 0x23); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbb, 0xa6); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbc, 0x2c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbd, 0x30); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbe, 0xaa); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbf, 0x26); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc0, 0x62); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc1, 0x9b); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc2, 0xb5); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc3, 0xcf); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc4, 0xdb); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc5, 0xe8); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc6, 0xf5); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc7, 0xfa); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc8, 0xfc); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc9, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xca, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcb, 0x16); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcc, 0xaf); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcd, 0xff); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xce, 0xff); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x0c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb1, 0x04); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb2, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb3, 0x16); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb4, 0x24); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb5, 0x3b); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb6, 0x4f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb7, 0x73); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb8, 0x99); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb9, 0xe0); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xba, 0x26); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbb, 0xad); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbc, 0x36); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbd, 0x3a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbe, 0xae); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbf, 0x2a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc0, 0x66); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc1, 0x9e); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc2, 0xb8); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc3, 0xd1); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc4, 0xdd); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc5, 0xe9); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc6, 0xf6); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc7, 0xfa); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc8, 0xfc); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc9, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xca, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcb, 0x16); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcc, 0xaf); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcd, 0xff); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xce, 0xff); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb3, 0x08); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x04); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb8, 0x68); + + mipi_dsi_msleep(&ctx, 150); + + return 0; +}; + +static int auo_kd101n80_45na_init(struct boe_panel *boe) +{ + struct mipi_dsi_multi_context ctx = { .dsi = boe->dsi }; + + msleep(24); + + mipi_dsi_dcs_write_seq_multi(&ctx, 0x11); + + mipi_dsi_msleep(&ctx, 120); + + mipi_dsi_dcs_write_seq_multi(&ctx, 0x29); + + mipi_dsi_msleep(&ctx, 120); + + return 0; +}; + +static int auo_b101uan08_3_init(struct boe_panel *boe) +{ + struct mipi_dsi_multi_context ctx = { .dsi = boe->dsi }; + + msleep(24); + + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc0, 0x48); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc1, 0x48); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc2, 0x47); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc3, 0x47); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc4, 0x46); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc5, 0x46); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc6, 0x45); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc7, 0x45); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc8, 0x64); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc9, 0x64); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xca, 0x4f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcb, 0x4f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcc, 0x40); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcd, 0x40); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xce, 0x66); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcf, 0x66); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd0, 0x4f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd1, 0x4f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd2, 0x41); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd3, 0x41); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd4, 0x48); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd5, 0x48); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd6, 0x47); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd7, 0x47); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd8, 0x46); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd9, 0x46); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xda, 0x45); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xdb, 0x45); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xdc, 0x64); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xdd, 0x64); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xde, 0x4f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xdf, 0x4f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe0, 0x40); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe1, 0x40); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe2, 0x66); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe3, 0x66); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe4, 0x4f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe5, 0x4f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe6, 0x41); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe7, 0x41); + + mipi_dsi_msleep(&ctx, 150); + + return 0; +}; + +static int starry_qfh032011_53g_init(struct boe_panel *boe) +{ + struct mipi_dsi_multi_context ctx = { .dsi = boe->dsi }; + + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc3, 0x4f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc4, 0x40); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc5, 0x40); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc6, 0x40); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc7, 0x40); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc8, 0x4d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc9, 0x52); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xca, 0x51); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcd, 0x5d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xce, 0x5b); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcf, 0x4b); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd0, 0x49); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd1, 0x47); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd2, 0x45); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd3, 0x41); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd7, 0x50); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd8, 0x40); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd9, 0x40); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xda, 0x40); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xdb, 0x40); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xdc, 0x4e); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xdd, 0x52); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xde, 0x51); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe1, 0x5e); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe2, 0x5c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe3, 0x4c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe4, 0x4a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe5, 0x48); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe6, 0x46); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe7, 0x42); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x03); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbe, 0x03); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcc, 0x44); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc8, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc9, 0x05); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xca, 0x42); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcd, 0x3e); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcf, 0x60); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd2, 0x04); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd3, 0x04); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd4, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd5, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd6, 0x03); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd7, 0x04); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd9, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xdb, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe4, 0xf0); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe5, 0x0a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcc, 0x08); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc2, 0x08); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc4, 0x10); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc0, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc1, 0x0a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc2, 0x20); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc3, 0x24); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc4, 0x23); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc5, 0x29); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc6, 0x23); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc7, 0x1c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc8, 0x19); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc9, 0x17); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xca, 0x17); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcb, 0x18); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcc, 0x1a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcd, 0x1e); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xce, 0x20); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcf, 0x23); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd0, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd1, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd2, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd3, 0x0a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd4, 0x13); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd5, 0x1c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd6, 0x1a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd7, 0x13); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd8, 0x17); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd9, 0x1c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xda, 0x19); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xdb, 0x17); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xdc, 0x17); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xdd, 0x18); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xde, 0x1a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xdf, 0x1e); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe0, 0x20); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe1, 0x23); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe2, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0X11); + + mipi_dsi_msleep(&ctx, 120); + + mipi_dsi_dcs_write_seq_multi(&ctx, 0X29); + + mipi_dsi_msleep(&ctx, 80); + + return 0; +}; + +static inline struct boe_panel *to_boe_panel(struct drm_panel *panel) +{ + return container_of(panel, struct boe_panel, base); +} + +static int boe_panel_disable(struct drm_panel *panel) +{ + struct boe_panel *boe = to_boe_panel(panel); + struct mipi_dsi_multi_context ctx = { .dsi = boe->dsi }; + + boe->dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + mipi_dsi_dcs_set_display_off_multi(&ctx); + mipi_dsi_dcs_enter_sleep_mode_multi(&ctx); + + mipi_dsi_msleep(&ctx, 150); + + return ctx.accum_err; +} + +static int boe_panel_unprepare(struct drm_panel *panel) +{ + struct boe_panel *boe = to_boe_panel(panel); + + if (boe->desc->discharge_on_disable) { + regulator_disable(boe->avee); + regulator_disable(boe->avdd); + usleep_range(5000, 7000); + gpiod_set_value(boe->enable_gpio, 0); + usleep_range(5000, 7000); + regulator_disable(boe->pp1800); + regulator_disable(boe->pp3300); + } else { + gpiod_set_value(boe->enable_gpio, 0); + usleep_range(1000, 2000); + regulator_disable(boe->avee); + regulator_disable(boe->avdd); + usleep_range(5000, 7000); + regulator_disable(boe->pp1800); + regulator_disable(boe->pp3300); + } + + return 0; +} + +static int boe_panel_prepare(struct drm_panel *panel) +{ + struct boe_panel *boe = to_boe_panel(panel); + int ret; + + gpiod_set_value(boe->enable_gpio, 0); + usleep_range(1000, 1500); + + ret = regulator_enable(boe->pp3300); + if (ret < 0) + return ret; + + ret = regulator_enable(boe->pp1800); + if (ret < 0) + return ret; + + usleep_range(3000, 5000); + + ret = regulator_enable(boe->avdd); + if (ret < 0) + goto poweroff1v8; + ret = regulator_enable(boe->avee); + if (ret < 0) + goto poweroffavdd; + + usleep_range(10000, 11000); + + if (boe->desc->lp11_before_reset) { + ret = mipi_dsi_dcs_nop(boe->dsi); + if (ret < 0) { + dev_err(&boe->dsi->dev, "Failed to send NOP: %d\n", ret); + goto poweroff; + } + usleep_range(1000, 2000); + } + gpiod_set_value(boe->enable_gpio, 1); + usleep_range(1000, 2000); + gpiod_set_value(boe->enable_gpio, 0); + usleep_range(1000, 2000); + gpiod_set_value(boe->enable_gpio, 1); + usleep_range(6000, 10000); + + ret = boe->desc->init(boe); + if (ret < 0) + goto poweroff; + + return 0; + +poweroff: + gpiod_set_value(boe->enable_gpio, 0); + regulator_disable(boe->avee); +poweroffavdd: + regulator_disable(boe->avdd); +poweroff1v8: + usleep_range(5000, 7000); + regulator_disable(boe->pp1800); + + return ret; +} + +static int boe_panel_enable(struct drm_panel *panel) +{ + msleep(130); + return 0; +} + +static const struct drm_display_mode boe_tv110c9m_default_mode = { + .clock = 166594, + .hdisplay = 1200, + .hsync_start = 1200 + 40, + .hsync_end = 1200 + 40 + 8, + .htotal = 1200 + 40 + 8 + 28, + .vdisplay = 2000, + .vsync_start = 2000 + 26, + .vsync_end = 2000 + 26 + 2, + .vtotal = 2000 + 26 + 2 + 148, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, +}; + +static const struct panel_desc boe_tv110c9m_desc = { + .modes = &boe_tv110c9m_default_mode, + .bpc = 8, + .size = { + .width_mm = 143, + .height_mm = 238, + }, + .lanes = 4, + .format = MIPI_DSI_FMT_RGB888, + .mode_flags = MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_VIDEO + | MIPI_DSI_MODE_VIDEO_HSE + | MIPI_DSI_CLOCK_NON_CONTINUOUS + | MIPI_DSI_MODE_VIDEO_BURST, + .init = boe_tv110c9m_init, +}; + +static const struct drm_display_mode inx_hj110iz_default_mode = { + .clock = 168432, + .hdisplay = 1200, + .hsync_start = 1200 + 40, + .hsync_end = 1200 + 40 + 8, + .htotal = 1200 + 40 + 8 + 28, + .vdisplay = 2000, + .vsync_start = 2000 + 26, + .vsync_end = 2000 + 26 + 2, + .vtotal = 2000 + 26 + 2 + 172, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, +}; + +static const struct panel_desc inx_hj110iz_desc = { + .modes = &inx_hj110iz_default_mode, + .bpc = 8, + .size = { + .width_mm = 143, + .height_mm = 238, + }, + .lanes = 4, + .format = MIPI_DSI_FMT_RGB888, + .mode_flags = MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_VIDEO + | MIPI_DSI_MODE_VIDEO_HSE + | MIPI_DSI_CLOCK_NON_CONTINUOUS + | MIPI_DSI_MODE_VIDEO_BURST, + .init = inx_hj110iz_init, +}; + +static const struct drm_display_mode boe_tv101wum_nl6_default_mode = { + .clock = 159425, + .hdisplay = 1200, + .hsync_start = 1200 + 100, + .hsync_end = 1200 + 100 + 40, + .htotal = 1200 + 100 + 40 + 24, + .vdisplay = 1920, + .vsync_start = 1920 + 10, + .vsync_end = 1920 + 10 + 14, + .vtotal = 1920 + 10 + 14 + 4, +}; + +static const struct panel_desc boe_tv101wum_nl6_desc = { + .modes = &boe_tv101wum_nl6_default_mode, + .bpc = 8, + .size = { + .width_mm = 135, + .height_mm = 216, + }, + .lanes = 4, + .format = MIPI_DSI_FMT_RGB888, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_MODE_LPM, + .init = boe_init, + .discharge_on_disable = false, +}; + +static const struct drm_display_mode auo_kd101n80_45na_default_mode = { + .clock = 157000, + .hdisplay = 1200, + .hsync_start = 1200 + 60, + .hsync_end = 1200 + 60 + 24, + .htotal = 1200 + 60 + 24 + 56, + .vdisplay = 1920, + .vsync_start = 1920 + 16, + .vsync_end = 1920 + 16 + 4, + .vtotal = 1920 + 16 + 4 + 16, +}; + +static const struct panel_desc auo_kd101n80_45na_desc = { + .modes = &auo_kd101n80_45na_default_mode, + .bpc = 8, + .size = { + .width_mm = 135, + .height_mm = 216, + }, + .lanes = 4, + .format = MIPI_DSI_FMT_RGB888, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_MODE_LPM, + .init = auo_kd101n80_45na_init, + .discharge_on_disable = true, +}; + +static const struct drm_display_mode boe_tv101wum_n53_default_mode = { + .clock = 159916, + .hdisplay = 1200, + .hsync_start = 1200 + 80, + .hsync_end = 1200 + 80 + 24, + .htotal = 1200 + 80 + 24 + 60, + .vdisplay = 1920, + .vsync_start = 1920 + 20, + .vsync_end = 1920 + 20 + 4, + .vtotal = 1920 + 20 + 4 + 10, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, +}; + +static const struct panel_desc boe_tv101wum_n53_desc = { + .modes = &boe_tv101wum_n53_default_mode, + .bpc = 8, + .size = { + .width_mm = 135, + .height_mm = 216, + }, + .lanes = 4, + .format = MIPI_DSI_FMT_RGB888, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_MODE_LPM, + .init = boe_init, +}; + +static const struct drm_display_mode auo_b101uan08_3_default_mode = { + .clock = 159667, + .hdisplay = 1200, + .hsync_start = 1200 + 60, + .hsync_end = 1200 + 60 + 4, + .htotal = 1200 + 60 + 4 + 80, + .vdisplay = 1920, + .vsync_start = 1920 + 34, + .vsync_end = 1920 + 34 + 2, + .vtotal = 1920 + 34 + 2 + 24, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, +}; + +static const struct panel_desc auo_b101uan08_3_desc = { + .modes = &auo_b101uan08_3_default_mode, + .bpc = 8, + .size = { + .width_mm = 135, + .height_mm = 216, + }, + .lanes = 4, + .format = MIPI_DSI_FMT_RGB888, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_MODE_LPM, + .init = auo_b101uan08_3_init, + .lp11_before_reset = true, +}; + +static const struct drm_display_mode boe_tv105wum_nw0_default_mode = { + .clock = 159916, + .hdisplay = 1200, + .hsync_start = 1200 + 80, + .hsync_end = 1200 + 80 + 24, + .htotal = 1200 + 80 + 24 + 60, + .vdisplay = 1920, + .vsync_start = 1920 + 20, + .vsync_end = 1920 + 20 + 4, + .vtotal = 1920 + 20 + 4 + 10, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, +}; + +static const struct panel_desc boe_tv105wum_nw0_desc = { + .modes = &boe_tv105wum_nw0_default_mode, + .bpc = 8, + .size = { + .width_mm = 141, + .height_mm = 226, + }, + .lanes = 4, + .format = MIPI_DSI_FMT_RGB888, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_MODE_LPM, + .init = boe_init, + .lp11_before_reset = true, +}; + +static const struct drm_display_mode starry_qfh032011_53g_default_mode = { + .clock = 165731, + .hdisplay = 1200, + .hsync_start = 1200 + 100, + .hsync_end = 1200 + 100 + 10, + .htotal = 1200 + 100 + 10 + 100, + .vdisplay = 1920, + .vsync_start = 1920 + 14, + .vsync_end = 1920 + 14 + 10, + .vtotal = 1920 + 14 + 10 + 15, +}; + +static const struct panel_desc starry_qfh032011_53g_desc = { + .modes = &starry_qfh032011_53g_default_mode, + .bpc = 8, + .size = { + .width_mm = 135, + .height_mm = 216, + }, + .lanes = 4, + .format = MIPI_DSI_FMT_RGB888, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_MODE_LPM, + .init = starry_qfh032011_53g_init, + .lp11_before_reset = true, +}; + +static int boe_panel_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct boe_panel *boe = to_boe_panel(panel); + const struct drm_display_mode *m = boe->desc->modes; + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, m); + if (!mode) { + dev_err(panel->dev, "failed to add mode %ux%u@%u\n", + m->hdisplay, m->vdisplay, drm_mode_vrefresh(m)); + return -ENOMEM; + } + + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + + connector->display_info.width_mm = boe->desc->size.width_mm; + connector->display_info.height_mm = boe->desc->size.height_mm; + connector->display_info.bpc = boe->desc->bpc; + /* + * TODO: Remove once all drm drivers call + * drm_connector_set_orientation_from_panel() + */ + drm_connector_set_panel_orientation(connector, boe->orientation); + + return 1; +} + +static enum drm_panel_orientation boe_panel_get_orientation(struct drm_panel *panel) +{ + struct boe_panel *boe = to_boe_panel(panel); + + return boe->orientation; +} + +static const struct drm_panel_funcs boe_panel_funcs = { + .disable = boe_panel_disable, + .unprepare = boe_panel_unprepare, + .prepare = boe_panel_prepare, + .enable = boe_panel_enable, + .get_modes = boe_panel_get_modes, + .get_orientation = boe_panel_get_orientation, +}; + +static int boe_panel_add(struct boe_panel *boe) +{ + struct device *dev = &boe->dsi->dev; + int err; + + boe->avdd = devm_regulator_get(dev, "avdd"); + if (IS_ERR(boe->avdd)) + return PTR_ERR(boe->avdd); + + boe->avee = devm_regulator_get(dev, "avee"); + if (IS_ERR(boe->avee)) + return PTR_ERR(boe->avee); + + boe->pp3300 = devm_regulator_get(dev, "pp3300"); + if (IS_ERR(boe->pp3300)) + return PTR_ERR(boe->pp3300); + + boe->pp1800 = devm_regulator_get(dev, "pp1800"); + if (IS_ERR(boe->pp1800)) + return PTR_ERR(boe->pp1800); + + boe->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(boe->enable_gpio)) { + dev_err(dev, "cannot get reset-gpios %ld\n", + PTR_ERR(boe->enable_gpio)); + return PTR_ERR(boe->enable_gpio); + } + + gpiod_set_value(boe->enable_gpio, 0); + + boe->base.prepare_prev_first = true; + + err = of_drm_get_panel_orientation(dev->of_node, &boe->orientation); + if (err < 0) { + dev_err(dev, "%pOF: failed to get orientation %d\n", dev->of_node, err); + return err; + } + + err = drm_panel_of_backlight(&boe->base); + if (err) + return err; + + boe->base.funcs = &boe_panel_funcs; + boe->base.dev = &boe->dsi->dev; + + drm_panel_add(&boe->base); + + return 0; +} + +static int boe_panel_probe(struct mipi_dsi_device *dsi) +{ + struct boe_panel *boe; + int ret; + const struct panel_desc *desc; + + boe = devm_drm_panel_alloc(&dsi->dev, __typeof(*boe), base, + &boe_panel_funcs, DRM_MODE_CONNECTOR_DSI); + + if (IS_ERR(boe)) + return PTR_ERR(boe); + + desc = of_device_get_match_data(&dsi->dev); + dsi->lanes = desc->lanes; + dsi->format = desc->format; + dsi->mode_flags = desc->mode_flags; + boe->desc = desc; + boe->dsi = dsi; + ret = boe_panel_add(boe); + if (ret < 0) + return ret; + + mipi_dsi_set_drvdata(dsi, boe); + + ret = mipi_dsi_attach(dsi); + if (ret) + drm_panel_remove(&boe->base); + + return ret; +} + +static void boe_panel_remove(struct mipi_dsi_device *dsi) +{ + struct boe_panel *boe = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", ret); + + if (boe->base.dev) + drm_panel_remove(&boe->base); +} + +static const struct of_device_id boe_of_match[] = { + { .compatible = "boe,tv101wum-nl6", + .data = &boe_tv101wum_nl6_desc + }, + { .compatible = "auo,kd101n80-45na", + .data = &auo_kd101n80_45na_desc + }, + { .compatible = "boe,tv101wum-n53", + .data = &boe_tv101wum_n53_desc + }, + { .compatible = "auo,b101uan08.3", + .data = &auo_b101uan08_3_desc + }, + { .compatible = "boe,tv105wum-nw0", + .data = &boe_tv105wum_nw0_desc + }, + { .compatible = "boe,tv110c9m-ll3", + .data = &boe_tv110c9m_desc + }, + { .compatible = "innolux,hj110iz-01a", + .data = &inx_hj110iz_desc + }, + { .compatible = "starry,2081101qfh032011-53g", + .data = &starry_qfh032011_53g_desc + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, boe_of_match); + +static struct mipi_dsi_driver boe_panel_driver = { + .driver = { + .name = "panel-boe-tv101wum-nl6", + .of_match_table = boe_of_match, + }, + .probe = boe_panel_probe, + .remove = boe_panel_remove, +}; +module_mipi_dsi_driver(boe_panel_driver); + +MODULE_AUTHOR("Jitao Shi <jitao.shi@mediatek.com>"); +MODULE_DESCRIPTION("BOE tv101wum-nl6 1200x1920 video mode panel driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-dsi-cm.c b/drivers/gpu/drm/panel/panel-dsi-cm.c new file mode 100644 index 000000000000..ae6e9ffc46cb --- /dev/null +++ b/drivers/gpu/drm/panel/panel-dsi-cm.c @@ -0,0 +1,650 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Generic DSI Command Mode panel driver + * + * Copyright (C) 2013 Texas Instruments Incorporated - https://www.ti.com/ + * Author: Tomi Valkeinen <tomi.valkeinen@ti.com> + */ + +#include <linux/backlight.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regulator/consumer.h> + +#include <drm/drm_connector.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +#include <video/mipi_display.h> + +#define DCS_GET_ID1 0xda +#define DCS_GET_ID2 0xdb +#define DCS_GET_ID3 0xdc + +#define DCS_REGULATOR_SUPPLY_NUM 2 + +static const struct of_device_id dsicm_of_match[]; + +struct dsic_panel_data { + u32 xres; + u32 yres; + u32 refresh; + u32 width_mm; + u32 height_mm; + u32 max_hs_rate; + u32 max_lp_rate; + bool te_support; +}; + +struct panel_drv_data { + struct mipi_dsi_device *dsi; + struct drm_panel panel; + struct drm_display_mode mode; + + struct mutex lock; + + struct backlight_device *bldev; + struct backlight_device *extbldev; + + unsigned long hw_guard_end; /* next value of jiffies when we can + * issue the next sleep in/out command + */ + unsigned long hw_guard_wait; /* max guard time in jiffies */ + + const struct dsic_panel_data *panel_data; + + struct gpio_desc *reset_gpio; + + struct regulator_bulk_data supplies[DCS_REGULATOR_SUPPLY_NUM]; + + bool use_dsi_backlight; + + /* runtime variables */ + bool enabled; + + bool intro_printed; +}; + +static inline struct panel_drv_data *panel_to_ddata(struct drm_panel *panel) +{ + return container_of(panel, struct panel_drv_data, panel); +} + +static void dsicm_bl_power(struct panel_drv_data *ddata, bool enable) +{ + struct backlight_device *backlight; + + if (ddata->bldev) + backlight = ddata->bldev; + else if (ddata->extbldev) + backlight = ddata->extbldev; + else + return; + + if (enable) + backlight_enable(backlight); + else + backlight_disable(backlight); +} + +static void hw_guard_start(struct panel_drv_data *ddata, int guard_msec) +{ + ddata->hw_guard_wait = msecs_to_jiffies(guard_msec); + ddata->hw_guard_end = jiffies + ddata->hw_guard_wait; +} + +static void hw_guard_wait(struct panel_drv_data *ddata) +{ + unsigned long wait = ddata->hw_guard_end - jiffies; + + if ((long)wait > 0 && wait <= ddata->hw_guard_wait) { + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(wait); + } +} + +static int dsicm_dcs_read_1(struct panel_drv_data *ddata, u8 dcs_cmd, u8 *data) +{ + return mipi_dsi_dcs_read(ddata->dsi, dcs_cmd, data, 1); +} + +static int dsicm_dcs_write_1(struct panel_drv_data *ddata, u8 dcs_cmd, u8 param) +{ + return mipi_dsi_dcs_write(ddata->dsi, dcs_cmd, ¶m, 1); +} + +static int dsicm_sleep_in(struct panel_drv_data *ddata) + +{ + int r; + + hw_guard_wait(ddata); + + r = mipi_dsi_dcs_enter_sleep_mode(ddata->dsi); + if (r) + return r; + + hw_guard_start(ddata, 120); + + usleep_range(5000, 10000); + + return 0; +} + +static int dsicm_sleep_out(struct panel_drv_data *ddata) +{ + int r; + + hw_guard_wait(ddata); + + r = mipi_dsi_dcs_exit_sleep_mode(ddata->dsi); + if (r) + return r; + + hw_guard_start(ddata, 120); + + usleep_range(5000, 10000); + + return 0; +} + +static int dsicm_get_id(struct panel_drv_data *ddata, u8 *id1, u8 *id2, u8 *id3) +{ + int r; + + r = dsicm_dcs_read_1(ddata, DCS_GET_ID1, id1); + if (r) + return r; + r = dsicm_dcs_read_1(ddata, DCS_GET_ID2, id2); + if (r) + return r; + r = dsicm_dcs_read_1(ddata, DCS_GET_ID3, id3); + if (r) + return r; + + return 0; +} + +static int dsicm_set_update_window(struct panel_drv_data *ddata) +{ + struct mipi_dsi_device *dsi = ddata->dsi; + int r; + + r = mipi_dsi_dcs_set_column_address(dsi, 0, ddata->mode.hdisplay - 1); + if (r < 0) + return r; + + r = mipi_dsi_dcs_set_page_address(dsi, 0, ddata->mode.vdisplay - 1); + if (r < 0) + return r; + + return 0; +} + +static int dsicm_bl_update_status(struct backlight_device *dev) +{ + struct panel_drv_data *ddata = dev_get_drvdata(&dev->dev); + int r = 0; + int level = backlight_get_brightness(dev); + + dev_dbg(&ddata->dsi->dev, "update brightness to %d\n", level); + + mutex_lock(&ddata->lock); + + if (ddata->enabled) + r = dsicm_dcs_write_1(ddata, MIPI_DCS_SET_DISPLAY_BRIGHTNESS, + level); + + mutex_unlock(&ddata->lock); + + return r; +} + +static int dsicm_bl_get_intensity(struct backlight_device *dev) +{ + return backlight_get_brightness(dev); +} + +static const struct backlight_ops dsicm_bl_ops = { + .get_brightness = dsicm_bl_get_intensity, + .update_status = dsicm_bl_update_status, +}; + +static ssize_t num_dsi_errors_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct panel_drv_data *ddata = dev_get_drvdata(dev); + u8 errors = 0; + int r = -ENODEV; + + mutex_lock(&ddata->lock); + + if (ddata->enabled) + r = dsicm_dcs_read_1(ddata, MIPI_DCS_GET_ERROR_COUNT_ON_DSI, &errors); + + mutex_unlock(&ddata->lock); + + if (r) + return r; + + return sysfs_emit(buf, "%d\n", errors); +} + +static ssize_t hw_revision_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct panel_drv_data *ddata = dev_get_drvdata(dev); + u8 id1, id2, id3; + int r = -ENODEV; + + mutex_lock(&ddata->lock); + + if (ddata->enabled) + r = dsicm_get_id(ddata, &id1, &id2, &id3); + + mutex_unlock(&ddata->lock); + + if (r) + return r; + + return sysfs_emit(buf, "%02x.%02x.%02x\n", id1, id2, id3); +} + +static DEVICE_ATTR_RO(num_dsi_errors); +static DEVICE_ATTR_RO(hw_revision); + +static struct attribute *dsicm_attrs[] = { + &dev_attr_num_dsi_errors.attr, + &dev_attr_hw_revision.attr, + NULL, +}; + +static const struct attribute_group dsicm_attr_group = { + .attrs = dsicm_attrs, +}; + +static void dsicm_hw_reset(struct panel_drv_data *ddata) +{ + gpiod_set_value(ddata->reset_gpio, 1); + udelay(10); + /* reset the panel */ + gpiod_set_value(ddata->reset_gpio, 0); + /* assert reset */ + udelay(10); + gpiod_set_value(ddata->reset_gpio, 1); + /* wait after releasing reset */ + usleep_range(5000, 10000); +} + +static int dsicm_power_on(struct panel_drv_data *ddata) +{ + u8 id1, id2, id3; + int r; + + dsicm_hw_reset(ddata); + + ddata->dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + r = dsicm_sleep_out(ddata); + if (r) + goto err; + + r = dsicm_get_id(ddata, &id1, &id2, &id3); + if (r) + goto err; + + r = dsicm_dcs_write_1(ddata, MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0xff); + if (r) + goto err; + + r = dsicm_dcs_write_1(ddata, MIPI_DCS_WRITE_CONTROL_DISPLAY, + (1<<2) | (1<<5)); /* BL | BCTRL */ + if (r) + goto err; + + r = mipi_dsi_dcs_set_pixel_format(ddata->dsi, MIPI_DCS_PIXEL_FMT_24BIT); + if (r) + goto err; + + r = dsicm_set_update_window(ddata); + if (r) + goto err; + + r = mipi_dsi_dcs_set_display_on(ddata->dsi); + if (r) + goto err; + + if (ddata->panel_data->te_support) { + r = mipi_dsi_dcs_set_tear_on(ddata->dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK); + if (r) + goto err; + } + + /* possible panel bug */ + msleep(100); + + ddata->enabled = true; + + if (!ddata->intro_printed) { + dev_info(&ddata->dsi->dev, "panel revision %02x.%02x.%02x\n", + id1, id2, id3); + ddata->intro_printed = true; + } + + ddata->dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + return 0; +err: + dev_err(&ddata->dsi->dev, "error while enabling panel, issuing HW reset\n"); + + dsicm_hw_reset(ddata); + + return r; +} + +static int dsicm_power_off(struct panel_drv_data *ddata) +{ + int r; + + ddata->enabled = false; + + r = mipi_dsi_dcs_set_display_off(ddata->dsi); + if (!r) + r = dsicm_sleep_in(ddata); + + if (r) { + dev_err(&ddata->dsi->dev, + "error disabling panel, issuing HW reset\n"); + dsicm_hw_reset(ddata); + } + + return r; +} + +static int dsicm_prepare(struct drm_panel *panel) +{ + struct panel_drv_data *ddata = panel_to_ddata(panel); + int r; + + r = regulator_bulk_enable(ARRAY_SIZE(ddata->supplies), ddata->supplies); + if (r) + dev_err(&ddata->dsi->dev, "failed to enable supplies: %d\n", r); + + return r; +} + +static int dsicm_enable(struct drm_panel *panel) +{ + struct panel_drv_data *ddata = panel_to_ddata(panel); + int r; + + mutex_lock(&ddata->lock); + + r = dsicm_power_on(ddata); + if (r) + goto err; + + mutex_unlock(&ddata->lock); + + dsicm_bl_power(ddata, true); + + return 0; +err: + dev_err(&ddata->dsi->dev, "enable failed (%d)\n", r); + mutex_unlock(&ddata->lock); + return r; +} + +static int dsicm_unprepare(struct drm_panel *panel) +{ + struct panel_drv_data *ddata = panel_to_ddata(panel); + int r; + + r = regulator_bulk_disable(ARRAY_SIZE(ddata->supplies), ddata->supplies); + if (r) + dev_err(&ddata->dsi->dev, "failed to disable supplies: %d\n", r); + + return r; +} + +static int dsicm_disable(struct drm_panel *panel) +{ + struct panel_drv_data *ddata = panel_to_ddata(panel); + int r; + + dsicm_bl_power(ddata, false); + + mutex_lock(&ddata->lock); + + r = dsicm_power_off(ddata); + + mutex_unlock(&ddata->lock); + + return r; +} + +static int dsicm_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct panel_drv_data *ddata = panel_to_ddata(panel); + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &ddata->mode); + if (!mode) { + dev_err(&ddata->dsi->dev, "failed to add mode %ux%ux@%u kHz\n", + ddata->mode.hdisplay, ddata->mode.vdisplay, + ddata->mode.clock); + return -ENOMEM; + } + + connector->display_info.width_mm = ddata->panel_data->width_mm; + connector->display_info.height_mm = ddata->panel_data->height_mm; + + drm_mode_probed_add(connector, mode); + + return 1; +} + +static const struct drm_panel_funcs dsicm_panel_funcs = { + .unprepare = dsicm_unprepare, + .disable = dsicm_disable, + .prepare = dsicm_prepare, + .enable = dsicm_enable, + .get_modes = dsicm_get_modes, +}; + +static int dsicm_probe_of(struct mipi_dsi_device *dsi) +{ + struct backlight_device *backlight; + struct panel_drv_data *ddata = mipi_dsi_get_drvdata(dsi); + int err; + struct drm_display_mode *mode = &ddata->mode; + + ddata->reset_gpio = devm_gpiod_get(&dsi->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ddata->reset_gpio)) { + err = PTR_ERR(ddata->reset_gpio); + dev_err(&dsi->dev, "reset gpio request failed: %d", err); + return err; + } + + mode->hdisplay = mode->hsync_start = mode->hsync_end = mode->htotal = + ddata->panel_data->xres; + mode->vdisplay = mode->vsync_start = mode->vsync_end = mode->vtotal = + ddata->panel_data->yres; + mode->clock = ddata->panel_data->xres * ddata->panel_data->yres * + ddata->panel_data->refresh / 1000; + mode->width_mm = ddata->panel_data->width_mm; + mode->height_mm = ddata->panel_data->height_mm; + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + drm_mode_set_name(mode); + + ddata->supplies[0].supply = "vpnl"; + ddata->supplies[1].supply = "vddi"; + err = devm_regulator_bulk_get(&dsi->dev, ARRAY_SIZE(ddata->supplies), + ddata->supplies); + if (err) + return err; + + backlight = devm_of_find_backlight(&dsi->dev); + if (IS_ERR(backlight)) + return PTR_ERR(backlight); + + /* If no backlight device is found assume native backlight support */ + if (backlight) + ddata->extbldev = backlight; + else + ddata->use_dsi_backlight = true; + + return 0; +} + +static int dsicm_probe(struct mipi_dsi_device *dsi) +{ + struct panel_drv_data *ddata; + struct backlight_device *bldev = NULL; + struct device *dev = &dsi->dev; + int r; + + dev_dbg(dev, "probe\n"); + + ddata = devm_drm_panel_alloc(dev, struct panel_drv_data, panel, + &dsicm_panel_funcs, DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ddata)) + return PTR_ERR(ddata); + + mipi_dsi_set_drvdata(dsi, ddata); + ddata->dsi = dsi; + + ddata->panel_data = of_device_get_match_data(dev); + if (!ddata->panel_data) + return -ENODEV; + + r = dsicm_probe_of(dsi); + if (r) + return r; + + mutex_init(&ddata->lock); + + dsicm_hw_reset(ddata); + + if (ddata->use_dsi_backlight) { + struct backlight_properties props = { 0 }; + props.max_brightness = 255; + props.type = BACKLIGHT_RAW; + + bldev = devm_backlight_device_register(dev, dev_name(dev), + dev, ddata, &dsicm_bl_ops, &props); + if (IS_ERR(bldev)) { + r = PTR_ERR(bldev); + goto err_bl; + } + + ddata->bldev = bldev; + } + + r = sysfs_create_group(&dev->kobj, &dsicm_attr_group); + if (r) { + dev_err(dev, "failed to create sysfs files\n"); + goto err_bl; + } + + dsi->lanes = 2; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS | + MIPI_DSI_MODE_NO_EOT_PACKET; + dsi->hs_rate = ddata->panel_data->max_hs_rate; + dsi->lp_rate = ddata->panel_data->max_lp_rate; + + drm_panel_add(&ddata->panel); + + r = mipi_dsi_attach(dsi); + if (r < 0) + goto err_dsi_attach; + + return 0; + +err_dsi_attach: + drm_panel_remove(&ddata->panel); + sysfs_remove_group(&dsi->dev.kobj, &dsicm_attr_group); +err_bl: + if (ddata->extbldev) + put_device(&ddata->extbldev->dev); + + return r; +} + +static void dsicm_remove(struct mipi_dsi_device *dsi) +{ + struct panel_drv_data *ddata = mipi_dsi_get_drvdata(dsi); + + dev_dbg(&dsi->dev, "remove\n"); + + mipi_dsi_detach(dsi); + + drm_panel_remove(&ddata->panel); + + sysfs_remove_group(&dsi->dev.kobj, &dsicm_attr_group); + + if (ddata->extbldev) + put_device(&ddata->extbldev->dev); +} + +static const struct dsic_panel_data taal_data = { + .xres = 864, + .yres = 480, + .refresh = 60, + .width_mm = 0, + .height_mm = 0, + .max_hs_rate = 300000000, + .max_lp_rate = 10000000, + .te_support = true, +}; + +static const struct dsic_panel_data himalaya_data = { + .xres = 480, + .yres = 864, + .refresh = 60, + .width_mm = 49, + .height_mm = 88, + .max_hs_rate = 300000000, + .max_lp_rate = 10000000, + .te_support = false, +}; + +static const struct dsic_panel_data droid4_data = { + .xres = 540, + .yres = 960, + .refresh = 60, + .width_mm = 50, + .height_mm = 89, + .max_hs_rate = 300000000, + .max_lp_rate = 10000000, + .te_support = false, +}; + +static const struct of_device_id dsicm_of_match[] = { + { .compatible = "tpo,taal", .data = &taal_data }, + { .compatible = "nokia,himalaya", &himalaya_data }, + { .compatible = "motorola,droid4-panel", &droid4_data }, + {}, +}; + +MODULE_DEVICE_TABLE(of, dsicm_of_match); + +static struct mipi_dsi_driver dsicm_driver = { + .probe = dsicm_probe, + .remove = dsicm_remove, + .driver = { + .name = "panel-dsi-cm", + .of_match_table = dsicm_of_match, + }, +}; +module_mipi_dsi_driver(dsicm_driver); + +MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>"); +MODULE_DESCRIPTION("Generic DSI Command Mode Panel Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-ebbg-ft8719.c b/drivers/gpu/drm/panel/panel-ebbg-ft8719.c new file mode 100644 index 000000000000..fb9f9f42be4f --- /dev/null +++ b/drivers/gpu/drm/panel/panel-ebbg-ft8719.c @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2022 Joel Selvaraj <jo@jsfamily.in> + * Generated with linux-mdss-dsi-panel-driver-generator from vendor device tree: + * Copyright (c) 2013, The Linux Foundation. All rights reserved. + */ + +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/regulator/consumer.h> +#include <linux/module.h> +#include <linux/of.h> + +#include <video/mipi_display.h> + +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +static const char * const regulator_names[] = { + "vddio", + "vddpos", + "vddneg", +}; + +static const unsigned long regulator_enable_loads[] = { + 62000, + 100000, + 100000 +}; + +struct ebbg_ft8719 { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + + struct regulator_bulk_data supplies[ARRAY_SIZE(regulator_names)]; + + struct gpio_desc *reset_gpio; +}; + +static inline +struct ebbg_ft8719 *to_ebbg_ft8719(struct drm_panel *panel) +{ + return container_of(panel, struct ebbg_ft8719, panel); +} + +static void ebbg_ft8719_reset(struct ebbg_ft8719 *ctx) +{ + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(4000, 5000); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(1000, 2000); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(15000, 16000); +} + +static int ebbg_ft8719_on(struct ebbg_ft8719 *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + mipi_dsi_dcs_set_display_brightness_multi(&dsi_ctx, 0x00ff); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x24); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_POWER_SAVE, 0x00); + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 90); + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + + return dsi_ctx.accum_err; +} + +static int ebbg_ft8719_off(struct ebbg_ft8719 *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_usleep_range(&dsi_ctx, 10000, 11000); + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 90); + + return dsi_ctx.accum_err; +} + +static int ebbg_ft8719_prepare(struct drm_panel *panel) +{ + struct ebbg_ft8719 *ctx = to_ebbg_ft8719(panel); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + if (ret < 0) + return ret; + + ebbg_ft8719_reset(ctx); + + ret = ebbg_ft8719_on(ctx); + if (ret < 0) { + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + return ret; + } + + return 0; +} + +static int ebbg_ft8719_unprepare(struct drm_panel *panel) +{ + struct ebbg_ft8719 *ctx = to_ebbg_ft8719(panel); + + ebbg_ft8719_off(ctx); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + + return 0; +} + +static const struct drm_display_mode ebbg_ft8719_mode = { + .clock = (1080 + 28 + 4 + 16) * (2246 + 120 + 4 + 12) * 60 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 28, + .hsync_end = 1080 + 28 + 4, + .htotal = 1080 + 28 + 4 + 16, + .vdisplay = 2246, + .vsync_start = 2246 + 120, + .vsync_end = 2246 + 120 + 4, + .vtotal = 2246 + 120 + 4 + 12, + .width_mm = 68, + .height_mm = 141, +}; + +static int ebbg_ft8719_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &ebbg_ft8719_mode); + if (!mode) + 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 ebbg_ft8719_panel_funcs = { + .prepare = ebbg_ft8719_prepare, + .unprepare = ebbg_ft8719_unprepare, + .get_modes = ebbg_ft8719_get_modes, +}; + +static int ebbg_ft8719_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct ebbg_ft8719 *ctx; + int i, ret; + + ctx = devm_drm_panel_alloc(dev, struct ebbg_ft8719, panel, + &ebbg_ft8719_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + for (i = 0; i < ARRAY_SIZE(ctx->supplies); i++) + ctx->supplies[i].supply = regulator_names[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), + ctx->supplies); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get regulators\n"); + + for (i = 0; i < ARRAY_SIZE(ctx->supplies); i++) { + ret = regulator_set_load(ctx->supplies[i].consumer, + regulator_enable_loads[i]); + if (ret) + return dev_err_probe(dev, ret, + "Failed to set regulator load\n"); + } + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), + "Failed to get reset-gpios\n"); + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_CLOCK_NON_CONTINUOUS; + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return dev_err_probe(dev, ret, "Failed to get backlight\n"); + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "Failed to attach to DSI host: %d\n", ret); + drm_panel_remove(&ctx->panel); + return ret; + } + + return 0; +} + +static void ebbg_ft8719_remove(struct mipi_dsi_device *dsi) +{ + struct ebbg_ft8719 *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id ebbg_ft8719_of_match[] = { + { .compatible = "ebbg,ft8719" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ebbg_ft8719_of_match); + +static struct mipi_dsi_driver ebbg_ft8719_driver = { + .probe = ebbg_ft8719_probe, + .remove = ebbg_ft8719_remove, + .driver = { + .name = "panel-ebbg-ft8719", + .of_match_table = ebbg_ft8719_of_match, + }, +}; +module_mipi_dsi_driver(ebbg_ft8719_driver); + +MODULE_AUTHOR("Joel Selvaraj <jo@jsfamily.in>"); +MODULE_DESCRIPTION("DRM driver for EBBG FT8719 video dsi panel"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-edp.c b/drivers/gpu/drm/panel/panel-edp.c new file mode 100644 index 000000000000..415b894890ad --- /dev/null +++ b/drivers/gpu/drm/panel/panel-edp.c @@ -0,0 +1,2217 @@ +/* + * Copyright (C) 2013, NVIDIA Corporation. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sub license, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> + +#include <video/display_timing.h> +#include <video/of_display_timing.h> +#include <video/videomode.h> + +#include <drm/display/drm_dp_aux_bus.h> +#include <drm/display/drm_dp_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_device.h> +#include <drm/drm_edid.h> +#include <drm/drm_panel.h> + +/** + * struct panel_delay - Describes delays for a simple panel. + */ +struct panel_delay { + /** + * @hpd_reliable: Time for HPD to be reliable + * + * The time (in milliseconds) that it takes after powering the panel + * before the HPD signal is reliable. Ideally this is 0 but some panels, + * board designs, or bad pulldown configs can cause a glitch here. + * + * NOTE: on some old panel data this number appears to be much too big. + * Presumably some old panels simply didn't have HPD hooked up and put + * the hpd_absent here because this field predates the + * hpd_absent. While that works, it's non-ideal. + */ + unsigned int hpd_reliable; + + /** + * @hpd_absent: Time to wait if HPD isn't hooked up. + * + * Add this to the prepare delay if we know Hot Plug Detect isn't used. + * + * This is T3-max on eDP timing diagrams or the delay from power on + * until HPD is guaranteed to be asserted. + */ + unsigned int hpd_absent; + + /** + * @powered_on_to_enable: Time between panel powered on and enable. + * + * The minimum time, in milliseconds, that needs to have passed + * between when panel powered on and enable may begin. + * + * This is (T3+T4+T5+T6+T8)-min on eDP timing diagrams or after the + * power supply enabled until we can turn the backlight on and see + * valid data. + * + * This doesn't normally need to be set if timings are already met by + * prepare_to_enable or enable. + */ + unsigned int powered_on_to_enable; + + /** + * @prepare_to_enable: Time between prepare and enable. + * + * The minimum time, in milliseconds, that needs to have passed + * between when prepare finished and enable may begin. If at + * enable time less time has passed since prepare finished, + * the driver waits for the remaining time. + * + * If a fixed enable delay is also specified, we'll start + * counting before delaying for the fixed delay. + * + * If a fixed prepare delay is also specified, we won't start + * counting until after the fixed delay. We can't overlap this + * fixed delay with the min time because the fixed delay + * doesn't happen at the end of the function if a HPD GPIO was + * specified. + * + * In other words: + * prepare() + * ... + * // do fixed prepare delay + * // wait for HPD GPIO if applicable + * // start counting for prepare_to_enable + * + * enable() + * // do fixed enable delay + * // enforce prepare_to_enable min time + * + * This is usually (T4+T5+T6+T8)-min on eDP timing diagrams. + * It is effectively the time from HPD going high till you can + * turn on the backlight. + */ + unsigned int prepare_to_enable; + + /** + * @enable: Time for the panel to display a valid frame. + * + * The time (in milliseconds) that it takes for the panel to + * display the first valid frame after starting to receive + * video data. + * + * This is (T6-min + max(T7-max, T8-min)) on eDP timing diagrams or + * the delay after link training finishes until we can turn the + * backlight on and see valid data. + */ + unsigned int enable; + + /** + * @disable: Time for the panel to turn the display off. + * + * The time (in milliseconds) that it takes for the panel to + * turn the display off (no content is visible). + * + * This is T9-min (delay from backlight off to end of valid video + * data) on eDP timing diagrams. It is not common to set. + */ + unsigned int disable; + + /** + * @unprepare: Time to power down completely. + * + * The time (in milliseconds) that it takes for the panel + * to power itself down completely. + * + * This time is used to prevent a future "prepare" from + * starting until at least this many milliseconds has passed. + * If at prepare time less time has passed since unprepare + * finished, the driver waits for the remaining time. + * + * This is T12-min on eDP timing diagrams. + */ + unsigned int unprepare; +}; + +/** + * struct panel_desc - Describes a simple panel. + */ +struct panel_desc { + /** + * @modes: Pointer to array of fixed modes appropriate for this panel. + * + * If only one mode then this can just be the address of the mode. + * NOTE: cannot be used with "timings" and also if this is specified + * then you cannot override the mode in the device tree. + */ + const struct drm_display_mode *modes; + + /** @num_modes: Number of elements in modes array. */ + unsigned int num_modes; + + /** + * @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. + */ + const struct display_timing *timings; + + /** @num_timings: Number of elements in timings array. */ + unsigned int num_timings; + + /** @bpc: Bits per color. */ + unsigned int bpc; + + /** @size: Structure containing the physical size of this panel. */ + struct { + /** + * @size.width: Width (in mm) of the active display area. + */ + unsigned int width; + + /** + * @size.height: Height (in mm) of the active display area. + */ + unsigned int height; + } size; + + /** @delay: Structure containing various delay values for this panel. */ + struct panel_delay delay; +}; + +/** + * struct edp_panel_entry - Maps panel ID to delay / panel name. + */ +struct edp_panel_entry { + /** @ident: edid identity used for panel matching. */ + const struct drm_edid_ident ident; + + /** @delay: The power sequencing delays needed for this panel. */ + const struct panel_delay *delay; + + /** @override_edid_mode: Override the mode obtained by edid. */ + const struct drm_display_mode *override_edid_mode; +}; + +struct panel_edp { + struct drm_panel base; + bool no_hpd; + + ktime_t prepared_time; + ktime_t powered_on_time; + ktime_t unprepared_time; + + const struct panel_desc *desc; + + struct regulator *supply; + struct i2c_adapter *ddc; + struct drm_dp_aux *aux; + + struct gpio_desc *enable_gpio; + struct gpio_desc *hpd_gpio; + + const struct edp_panel_entry *detected_panel; + + const struct drm_edid *drm_edid; + + struct drm_display_mode override_mode; + + enum drm_panel_orientation orientation; +}; + +static inline struct panel_edp *to_panel_edp(struct drm_panel *panel) +{ + return container_of(panel, struct panel_edp, base); +} + +static unsigned int panel_edp_get_timings_modes(struct panel_edp *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + unsigned int i, num = 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(connector->dev); + if (!mode) { + dev_err(panel->base.dev, "failed to add mode %ux%u\n", + dt->hactive.typ, dt->vactive.typ); + continue; + } + + drm_display_mode_from_videomode(&vm, mode); + + mode->type |= DRM_MODE_TYPE_DRIVER; + + if (panel->desc->num_timings == 1) + mode->type |= DRM_MODE_TYPE_PREFERRED; + + drm_mode_probed_add(connector, mode); + num++; + } + + return num; +} + +static unsigned int panel_edp_get_display_modes(struct panel_edp *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(connector->dev, m); + if (!mode) { + dev_err(panel->base.dev, "failed to add mode %ux%u@%u\n", + m->hdisplay, m->vdisplay, + drm_mode_vrefresh(m)); + continue; + } + + mode->type |= DRM_MODE_TYPE_DRIVER; + + if (panel->desc->num_modes == 1) + mode->type |= DRM_MODE_TYPE_PREFERRED; + + drm_mode_set_name(mode); + + drm_mode_probed_add(connector, mode); + num++; + } + + return num; +} + +static int panel_edp_override_edid_mode(struct panel_edp *panel, + struct drm_connector *connector, + const struct drm_display_mode *override_mode) +{ + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, override_mode); + if (!mode) { + dev_err(panel->base.dev, "failed to add additional mode\n"); + return 0; + } + + mode->type |= DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + return 1; +} + +static int panel_edp_get_non_edid_modes(struct panel_edp *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_edp_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_edp_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; + + return num; +} + +static void panel_edp_wait(ktime_t start_ktime, unsigned int min_ms) +{ + ktime_t now_ktime, min_ktime; + + if (!min_ms) + return; + + min_ktime = ktime_add(start_ktime, ms_to_ktime(min_ms)); + now_ktime = ktime_get_boottime(); + + if (ktime_before(now_ktime, min_ktime)) + msleep(ktime_to_ms(ktime_sub(min_ktime, now_ktime)) + 1); +} + +static int panel_edp_disable(struct drm_panel *panel) +{ + struct panel_edp *p = to_panel_edp(panel); + + if (p->desc->delay.disable) + msleep(p->desc->delay.disable); + + return 0; +} + +static int panel_edp_suspend(struct device *dev) +{ + struct panel_edp *p = dev_get_drvdata(dev); + + drm_dp_dpcd_set_powered(p->aux, false); + gpiod_set_value_cansleep(p->enable_gpio, 0); + regulator_disable(p->supply); + p->unprepared_time = ktime_get_boottime(); + + return 0; +} + +static int panel_edp_unprepare(struct drm_panel *panel) +{ + int ret; + + ret = pm_runtime_put_sync_suspend(panel->dev); + if (ret < 0) + return ret; + + return 0; +} + +static int panel_edp_get_hpd_gpio(struct device *dev, struct panel_edp *p) +{ + p->hpd_gpio = devm_gpiod_get_optional(dev, "hpd", GPIOD_IN); + if (IS_ERR(p->hpd_gpio)) + return dev_err_probe(dev, PTR_ERR(p->hpd_gpio), + "failed to get 'hpd' GPIO\n"); + + return 0; +} + +static bool panel_edp_can_read_hpd(struct panel_edp *p) +{ + return !p->no_hpd && (p->hpd_gpio || (p->aux && p->aux->wait_hpd_asserted)); +} + +static int panel_edp_prepare_once(struct panel_edp *p) +{ + struct device *dev = p->base.dev; + unsigned int delay; + int err; + int hpd_asserted; + unsigned long hpd_wait_us; + + panel_edp_wait(p->unprepared_time, p->desc->delay.unprepare); + + err = regulator_enable(p->supply); + if (err < 0) { + dev_err(dev, "failed to enable supply: %d\n", err); + return err; + } + + gpiod_set_value_cansleep(p->enable_gpio, 1); + drm_dp_dpcd_set_powered(p->aux, true); + + p->powered_on_time = ktime_get_boottime(); + + delay = p->desc->delay.hpd_reliable; + if (p->no_hpd) + delay = max(delay, p->desc->delay.hpd_absent); + if (delay) + msleep(delay); + + if (panel_edp_can_read_hpd(p)) { + if (p->desc->delay.hpd_absent) + hpd_wait_us = p->desc->delay.hpd_absent * 1000UL; + else + hpd_wait_us = 2000000; + + if (p->hpd_gpio) { + err = readx_poll_timeout(gpiod_get_value_cansleep, + p->hpd_gpio, hpd_asserted, + hpd_asserted, 1000, hpd_wait_us); + if (hpd_asserted < 0) + err = hpd_asserted; + } else { + err = p->aux->wait_hpd_asserted(p->aux, hpd_wait_us); + } + + if (err) { + if (err != -ETIMEDOUT) + dev_err(dev, + "error waiting for hpd GPIO: %d\n", err); + goto error; + } + } + + p->prepared_time = ktime_get_boottime(); + + return 0; + +error: + drm_dp_dpcd_set_powered(p->aux, false); + gpiod_set_value_cansleep(p->enable_gpio, 0); + regulator_disable(p->supply); + p->unprepared_time = ktime_get_boottime(); + + return err; +} + +/* + * Some panels simply don't always come up and need to be power cycled to + * work properly. We'll allow for a handful of retries. + */ +#define MAX_PANEL_PREPARE_TRIES 5 + +static int panel_edp_resume(struct device *dev) +{ + struct panel_edp *p = dev_get_drvdata(dev); + int ret; + int try; + + for (try = 0; try < MAX_PANEL_PREPARE_TRIES; try++) { + ret = panel_edp_prepare_once(p); + if (ret != -ETIMEDOUT) + break; + } + + if (ret == -ETIMEDOUT) + dev_err(dev, "Prepare timeout after %d tries\n", try); + else if (try) + dev_warn(dev, "Prepare needed %d retries\n", try); + + return ret; +} + +static int panel_edp_prepare(struct drm_panel *panel) +{ + int ret; + + ret = pm_runtime_get_sync(panel->dev); + if (ret < 0) { + pm_runtime_put_autosuspend(panel->dev); + return ret; + } + + return 0; +} + +static int panel_edp_enable(struct drm_panel *panel) +{ + struct panel_edp *p = to_panel_edp(panel); + unsigned int delay; + + delay = p->desc->delay.enable; + + /* + * If there is a "prepare_to_enable" delay then that's supposed to be + * the delay from HPD going high until we can turn the backlight on. + * However, we can only count this if HPD is readable by the panel + * driver. + * + * If we aren't handling the HPD pin ourselves then the best we + * can do is assume that HPD went high immediately before we were + * called (and link training took zero time). Note that "no-hpd" + * actually counts as handling HPD ourselves since we're doing the + * worst case delay (in prepare) ourselves. + * + * NOTE: if we ever end up in this "if" statement then we're + * guaranteed that the panel_edp_wait() call below will do no delay. + * It already handles that case, though, so we don't need any special + * code for it. + */ + if (p->desc->delay.prepare_to_enable && + !panel_edp_can_read_hpd(p) && !p->no_hpd) + delay = max(delay, p->desc->delay.prepare_to_enable); + + if (delay) + msleep(delay); + + panel_edp_wait(p->prepared_time, p->desc->delay.prepare_to_enable); + + panel_edp_wait(p->powered_on_time, p->desc->delay.powered_on_to_enable); + + return 0; +} + +static int panel_edp_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct panel_edp *p = to_panel_edp(panel); + int num = 0; + bool has_hard_coded_modes = p->desc->num_timings || p->desc->num_modes; + bool has_override_edid_mode = p->detected_panel && + p->detected_panel != ERR_PTR(-EINVAL) && + p->detected_panel->override_edid_mode; + + /* probe EDID if a DDC bus is available */ + if (p->ddc) { + pm_runtime_get_sync(panel->dev); + + if (!p->drm_edid) + p->drm_edid = drm_edid_read_ddc(connector, p->ddc); + + drm_edid_connector_update(connector, p->drm_edid); + + /* + * If both edid and hard-coded modes exists, skip edid modes to + * avoid multiple preferred modes. + */ + if (p->drm_edid && !has_hard_coded_modes) { + if (has_override_edid_mode) { + /* + * override_edid_mode is specified. Use + * override_edid_mode instead of from edid. + */ + num += panel_edp_override_edid_mode(p, connector, + p->detected_panel->override_edid_mode); + } else { + num += drm_edid_connector_add_modes(connector); + } + } + + pm_runtime_mark_last_busy(panel->dev); + pm_runtime_put_autosuspend(panel->dev); + } + + if (has_hard_coded_modes) + num += panel_edp_get_non_edid_modes(p, connector); + else if (!num) + dev_warn(p->base.dev, "No display modes\n"); + + /* + * TODO: Remove once all drm drivers call + * drm_connector_set_orientation_from_panel() + */ + drm_connector_set_panel_orientation(connector, p->orientation); + + return num; +} + +static int panel_edp_get_timings(struct drm_panel *panel, + unsigned int num_timings, + struct display_timing *timings) +{ + struct panel_edp *p = to_panel_edp(panel); + unsigned int i; + + if (p->desc->num_timings < num_timings) + num_timings = p->desc->num_timings; + + if (timings) + for (i = 0; i < num_timings; i++) + timings[i] = p->desc->timings[i]; + + return p->desc->num_timings; +} + +static enum drm_panel_orientation panel_edp_get_orientation(struct drm_panel *panel) +{ + struct panel_edp *p = to_panel_edp(panel); + + return p->orientation; +} + +static int detected_panel_show(struct seq_file *s, void *data) +{ + struct drm_panel *panel = s->private; + struct panel_edp *p = to_panel_edp(panel); + + if (IS_ERR(p->detected_panel)) + seq_puts(s, "UNKNOWN\n"); + else if (!p->detected_panel) + seq_puts(s, "HARDCODED\n"); + else + seq_printf(s, "%s\n", p->detected_panel->ident.name); + + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(detected_panel); + +static void panel_edp_debugfs_init(struct drm_panel *panel, struct dentry *root) +{ + debugfs_create_file("detected_panel", 0600, root, panel, &detected_panel_fops); +} + +static const struct drm_panel_funcs panel_edp_funcs = { + .disable = panel_edp_disable, + .unprepare = panel_edp_unprepare, + .prepare = panel_edp_prepare, + .enable = panel_edp_enable, + .get_modes = panel_edp_get_modes, + .get_orientation = panel_edp_get_orientation, + .get_timings = panel_edp_get_timings, + .debugfs_init = panel_edp_debugfs_init, +}; + +#define PANEL_EDP_BOUNDS_CHECK(to_check, bounds, field) \ + (to_check->field.typ >= bounds->field.min && \ + to_check->field.typ <= bounds->field.max) +static void panel_edp_parse_panel_timing_node(struct device *dev, + struct panel_edp *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_EDP_BOUNDS_CHECK(ot, dt, hactive) || + !PANEL_EDP_BOUNDS_CHECK(ot, dt, hfront_porch) || + !PANEL_EDP_BOUNDS_CHECK(ot, dt, hback_porch) || + !PANEL_EDP_BOUNDS_CHECK(ot, dt, hsync_len) || + !PANEL_EDP_BOUNDS_CHECK(ot, dt, vactive) || + !PANEL_EDP_BOUNDS_CHECK(ot, dt, vfront_porch) || + !PANEL_EDP_BOUNDS_CHECK(ot, dt, vback_porch) || + !PANEL_EDP_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 const struct edp_panel_entry *find_edp_panel(u32 panel_id, const struct drm_edid *edid); + +static void panel_edp_set_conservative_timings(struct panel_edp *panel, struct panel_desc *desc) +{ + /* + * It's highly likely that the panel will work if we use very + * conservative timings, so let's do that. + * + * Nearly all panels have a "unprepare" delay of 500 ms though + * there are a few with 1000. Let's stick 2000 in just to be + * super conservative. + * + * An "enable" delay of 80 ms seems the most common, but we'll + * throw in 200 ms to be safe. + */ + desc->delay.unprepare = 2000; + desc->delay.enable = 200; + + panel->detected_panel = ERR_PTR(-EINVAL); +} + +static int generic_edp_panel_probe(struct device *dev, struct panel_edp *panel) +{ + struct panel_desc *desc; + const struct drm_edid *base_block; + u32 panel_id; + char vend[4]; + u16 product_id; + u32 reliable_ms = 0; + u32 absent_ms = 0; + int ret; + + desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); + if (!desc) + return -ENOMEM; + panel->desc = desc; + + /* + * Read the dts properties for the initial probe. These are used by + * the runtime resume code which will get called by the + * pm_runtime_get_sync() call below. + */ + of_property_read_u32(dev->of_node, "hpd-reliable-delay-ms", &reliable_ms); + desc->delay.hpd_reliable = reliable_ms; + of_property_read_u32(dev->of_node, "hpd-absent-delay-ms", &absent_ms); + desc->delay.hpd_absent = absent_ms; + + /* Power the panel on so we can read the EDID */ + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + dev_err(dev, + "Couldn't power on panel to ID it; using conservative timings: %d\n", + ret); + panel_edp_set_conservative_timings(panel, desc); + goto exit; + } + + base_block = drm_edid_read_base_block(panel->ddc); + if (base_block) { + panel_id = drm_edid_get_panel_id(base_block); + } else { + dev_err(dev, "Couldn't read EDID for ID; using conservative timings\n"); + panel_edp_set_conservative_timings(panel, desc); + goto exit; + } + drm_edid_decode_panel_id(panel_id, vend, &product_id); + + panel->detected_panel = find_edp_panel(panel_id, base_block); + + drm_edid_free(base_block); + + /* + * We're using non-optimized timings and want it really obvious that + * someone needs to add an entry to the table, so we'll do a WARN_ON + * splat. + */ + if (WARN_ON(!panel->detected_panel)) { + dev_warn(dev, + "Unknown panel %s %#06x, using conservative timings\n", + vend, product_id); + panel_edp_set_conservative_timings(panel, desc); + } else { + dev_info(dev, "Detected %s %s (%#06x)\n", + vend, panel->detected_panel->ident.name, product_id); + + /* Update the delay; everything else comes from EDID */ + desc->delay = *panel->detected_panel->delay; + } + +exit: + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return 0; +} + +static int panel_edp_probe(struct device *dev, const struct panel_desc *desc, + struct drm_dp_aux *aux) +{ + struct panel_edp *panel; + struct display_timing dt; + struct device_node *ddc; + int err; + + panel = devm_drm_panel_alloc(dev, struct panel_edp, base, + &panel_edp_funcs, DRM_MODE_CONNECTOR_eDP); + if (IS_ERR(panel)) + return PTR_ERR(panel); + + panel->prepared_time = 0; + panel->desc = desc; + panel->aux = aux; + + panel->no_hpd = of_property_read_bool(dev->of_node, "no-hpd"); + if (!panel->no_hpd) { + err = panel_edp_get_hpd_gpio(dev, panel); + if (err) + return err; + } + + panel->supply = devm_regulator_get(dev, "power"); + if (IS_ERR(panel->supply)) + return PTR_ERR(panel->supply); + + panel->enable_gpio = devm_gpiod_get_optional(dev, "enable", + GPIOD_OUT_LOW); + if (IS_ERR(panel->enable_gpio)) + return dev_err_probe(dev, PTR_ERR(panel->enable_gpio), + "failed to request GPIO\n"); + + err = of_drm_get_panel_orientation(dev->of_node, &panel->orientation); + if (err) { + dev_err(dev, "%pOF: failed to get orientation %d\n", dev->of_node, err); + return err; + } + + 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) + return -EPROBE_DEFER; + } else if (aux) { + panel->ddc = &aux->ddc; + } + + if (!of_get_display_timing(dev->of_node, "panel-timing", &dt)) + panel_edp_parse_panel_timing_node(dev, panel, &dt); + + dev_set_drvdata(dev, panel); + + err = drm_panel_of_backlight(&panel->base); + if (err) + goto err_finished_ddc_init; + + /* + * We use runtime PM for prepare / unprepare since those power the panel + * on and off and those can be very slow operations. This is important + * to optimize powering the panel on briefly to read the EDID before + * fully enabling the panel. + */ + pm_runtime_enable(dev); + pm_runtime_set_autosuspend_delay(dev, 1000); + pm_runtime_use_autosuspend(dev); + + if (of_device_is_compatible(dev->of_node, "edp-panel")) { + err = generic_edp_panel_probe(dev, panel); + if (err) { + dev_err_probe(dev, err, + "Couldn't detect panel nor find a fallback\n"); + goto err_finished_pm_runtime; + } + /* generic_edp_panel_probe() replaces desc in the panel */ + desc = panel->desc; + } else if (desc->bpc != 6 && desc->bpc != 8 && desc->bpc != 10) { + dev_warn(dev, "Expected bpc in {6,8,10} but got: %u\n", desc->bpc); + } + + if (!panel->base.backlight && panel->aux) { + pm_runtime_get_sync(dev); + err = drm_panel_dp_aux_backlight(&panel->base, panel->aux); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + /* + * Warn if we get an error, but don't consider it fatal. Having + * a panel where we can't control the backlight is better than + * no panel. + */ + if (err) + dev_warn(dev, "failed to register dp aux backlight: %d\n", err); + } + + drm_panel_add(&panel->base); + + return 0; + +err_finished_pm_runtime: + pm_runtime_dont_use_autosuspend(dev); + pm_runtime_disable(dev); +err_finished_ddc_init: + if (panel->ddc && (!panel->aux || panel->ddc != &panel->aux->ddc)) + put_device(&panel->ddc->dev); + + return err; +} + +static void panel_edp_shutdown(struct device *dev) +{ + struct panel_edp *panel = dev_get_drvdata(dev); + + /* + * NOTE: the following two calls don't really belong here. It is the + * responsibility of a correctly written DRM modeset driver to call + * drm_atomic_helper_shutdown() at shutdown time and that should + * cause the panel to be disabled / unprepared if needed. For now, + * however, we'll keep these calls due to the sheer number of + * different DRM modeset drivers used with panel-edp. Once we've + * confirmed that all DRM modeset drivers using this panel properly + * call drm_atomic_helper_shutdown() we can simply delete the two + * calls below. + * + * TO BE EXPLICIT: THE CALLS BELOW SHOULDN'T BE COPIED TO ANY NEW + * PANEL DRIVERS. + * + * FIXME: If we're still haven't figured out if all DRM modeset + * drivers properly call drm_atomic_helper_shutdown() but we _have_ + * managed to make sure that DRM modeset drivers get their shutdown() + * callback before the panel's shutdown() callback (perhaps using + * device link), we could add a WARN_ON here to help move forward. + */ + if (panel->base.enabled) + drm_panel_disable(&panel->base); + if (panel->base.prepared) + drm_panel_unprepare(&panel->base); +} + +static void panel_edp_remove(struct device *dev) +{ + struct panel_edp *panel = dev_get_drvdata(dev); + + drm_panel_remove(&panel->base); + panel_edp_shutdown(dev); + + pm_runtime_dont_use_autosuspend(dev); + pm_runtime_disable(dev); + if (panel->ddc && (!panel->aux || panel->ddc != &panel->aux->ddc)) + put_device(&panel->ddc->dev); + + drm_edid_free(panel->drm_edid); + panel->drm_edid = NULL; +} + +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 = { + .timings = &auo_b101ean01_timing, + .num_timings = 1, + .bpc = 6, + .size = { + .width = 217, + .height = 136, + }, +}; + +static const struct drm_display_mode auo_b116xa3_mode = { + .clock = 70589, + .hdisplay = 1366, + .hsync_start = 1366 + 40, + .hsync_end = 1366 + 40 + 40, + .htotal = 1366 + 40 + 40 + 32, + .vdisplay = 768, + .vsync_start = 768 + 10, + .vsync_end = 768 + 10 + 12, + .vtotal = 768 + 10 + 12 + 6, + .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, +}; + +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, + .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 = 200, + .unprepare = 500, + .enable = 50, + }, +}; + +static const struct drm_display_mode auo_b133htn01_mode = { + .clock = 150660, + .hdisplay = 1920, + .hsync_start = 1920 + 172, + .hsync_end = 1920 + 172 + 80, + .htotal = 1920 + 172 + 80 + 60, + .vdisplay = 1080, + .vsync_start = 1080 + 25, + .vsync_end = 1080 + 25 + 10, + .vtotal = 1080 + 25 + 10 + 10, +}; + +static const struct panel_desc auo_b133htn01 = { + .modes = &auo_b133htn01_mode, + .num_modes = 1, + .bpc = 6, + .size = { + .width = 293, + .height = 165, + }, + .delay = { + .hpd_reliable = 105, + .enable = 20, + .unprepare = 50, + }, +}; + +static const struct drm_display_mode auo_b133xtn01_mode = { + .clock = 69500, + .hdisplay = 1366, + .hsync_start = 1366 + 48, + .hsync_end = 1366 + 48 + 32, + .htotal = 1366 + 48 + 32 + 20, + .vdisplay = 768, + .vsync_start = 768 + 3, + .vsync_end = 768 + 3 + 6, + .vtotal = 768 + 3 + 6 + 13, +}; + +static const struct panel_desc auo_b133xtn01 = { + .modes = &auo_b133xtn01_mode, + .num_modes = 1, + .bpc = 6, + .size = { + .width = 293, + .height = 165, + }, +}; + +static const struct drm_display_mode boe_nv101wxmn51_modes[] = { + { + .clock = 71900, + .hdisplay = 1280, + .hsync_start = 1280 + 48, + .hsync_end = 1280 + 48 + 32, + .htotal = 1280 + 48 + 32 + 80, + .vdisplay = 800, + .vsync_start = 800 + 3, + .vsync_end = 800 + 3 + 5, + .vtotal = 800 + 3 + 5 + 24, + }, + { + .clock = 57500, + .hdisplay = 1280, + .hsync_start = 1280 + 48, + .hsync_end = 1280 + 48 + 32, + .htotal = 1280 + 48 + 32 + 80, + .vdisplay = 800, + .vsync_start = 800 + 3, + .vsync_end = 800 + 3 + 5, + .vtotal = 800 + 3 + 5 + 24, + }, +}; + +static const struct panel_desc boe_nv101wxmn51 = { + .modes = boe_nv101wxmn51_modes, + .num_modes = ARRAY_SIZE(boe_nv101wxmn51_modes), + .bpc = 8, + .size = { + .width = 217, + .height = 136, + }, + .delay = { + /* TODO: should be hpd-absent and no-hpd should be set? */ + .hpd_reliable = 210, + .enable = 50, + .unprepare = 160, + }, +}; + +static const struct drm_display_mode boe_nv110wtm_n61_modes[] = { + { + .clock = 207800, + .hdisplay = 2160, + .hsync_start = 2160 + 48, + .hsync_end = 2160 + 48 + 32, + .htotal = 2160 + 48 + 32 + 100, + .vdisplay = 1440, + .vsync_start = 1440 + 3, + .vsync_end = 1440 + 3 + 6, + .vtotal = 1440 + 3 + 6 + 31, + .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC, + }, + { + .clock = 138500, + .hdisplay = 2160, + .hsync_start = 2160 + 48, + .hsync_end = 2160 + 48 + 32, + .htotal = 2160 + 48 + 32 + 100, + .vdisplay = 1440, + .vsync_start = 1440 + 3, + .vsync_end = 1440 + 3 + 6, + .vtotal = 1440 + 3 + 6 + 31, + .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC, + }, +}; + +static const struct panel_desc boe_nv110wtm_n61 = { + .modes = boe_nv110wtm_n61_modes, + .num_modes = ARRAY_SIZE(boe_nv110wtm_n61_modes), + .bpc = 8, + .size = { + .width = 233, + .height = 155, + }, + .delay = { + .hpd_absent = 200, + .prepare_to_enable = 80, + .enable = 50, + .unprepare = 500, + }, +}; + +/* Also used for boe_nv133fhm_n62 */ +static const struct drm_display_mode boe_nv133fhm_n61_modes = { + .clock = 147840, + .hdisplay = 1920, + .hsync_start = 1920 + 48, + .hsync_end = 1920 + 48 + 32, + .htotal = 1920 + 48 + 32 + 200, + .vdisplay = 1080, + .vsync_start = 1080 + 3, + .vsync_end = 1080 + 3 + 6, + .vtotal = 1080 + 3 + 6 + 31, + .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC, +}; + +/* Also used for boe_nv133fhm_n62 */ +static const struct panel_desc boe_nv133fhm_n61 = { + .modes = &boe_nv133fhm_n61_modes, + .num_modes = 1, + .bpc = 6, + .size = { + .width = 294, + .height = 165, + }, + .delay = { + /* + * When power is first given to the panel there's a short + * spike on the HPD line. It was explained that this spike + * was until the TCON data download was complete. On + * one system this was measured at 8 ms. We'll put 15 ms + * in the prepare delay just to be safe. That means: + * - If HPD isn't hooked up you still have 200 ms delay. + * - If HPD is hooked up we won't try to look at it for the + * first 15 ms. + */ + .hpd_reliable = 15, + .hpd_absent = 200, + + .unprepare = 500, + }, +}; + +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, + }, +}; + +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 = { + /* TODO: should be hpd-absent and no-hpd should be set? */ + .hpd_reliable = 210, + .enable = 50, + .unprepare = 160, + }, +}; + +static const struct drm_display_mode innolux_n116bca_ea1_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, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, +}; + +static const struct panel_desc innolux_n116bca_ea1 = { + .modes = &innolux_n116bca_ea1_mode, + .num_modes = 1, + .bpc = 6, + .size = { + .width = 256, + .height = 144, + }, + .delay = { + .hpd_absent = 200, + .enable = 80, + .disable = 50, + .unprepare = 500, + }, +}; + +/* + * 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 = { + .timings = &innolux_n116bge_timing, + .num_timings = 1, + .bpc = 6, + .size = { + .width = 256, + .height = 144, + }, +}; + +static const struct drm_display_mode innolux_n125hce_gn1_mode = { + .clock = 162000, + .hdisplay = 1920, + .hsync_start = 1920 + 40, + .hsync_end = 1920 + 40 + 40, + .htotal = 1920 + 40 + 40 + 80, + .vdisplay = 1080, + .vsync_start = 1080 + 4, + .vsync_end = 1080 + 4 + 4, + .vtotal = 1080 + 4 + 4 + 24, +}; + +static const struct panel_desc innolux_n125hce_gn1 = { + .modes = &innolux_n125hce_gn1_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 276, + .height = 155, + }, +}; + +static const struct drm_display_mode innolux_p120zdg_bf1_mode = { + .clock = 206016, + .hdisplay = 2160, + .hsync_start = 2160 + 48, + .hsync_end = 2160 + 48 + 32, + .htotal = 2160 + 48 + 32 + 80, + .vdisplay = 1440, + .vsync_start = 1440 + 3, + .vsync_end = 1440 + 3 + 10, + .vtotal = 1440 + 3 + 10 + 27, + .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC, +}; + +static const struct panel_desc innolux_p120zdg_bf1 = { + .modes = &innolux_p120zdg_bf1_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 254, + .height = 169, + }, + .delay = { + .hpd_absent = 200, + .unprepare = 500, + }, +}; + +static const struct drm_display_mode kingdisplay_kd116n21_30nv_a010_mode = { + .clock = 81000, + .hdisplay = 1366, + .hsync_start = 1366 + 40, + .hsync_end = 1366 + 40 + 32, + .htotal = 1366 + 40 + 32 + 62, + .vdisplay = 768, + .vsync_start = 768 + 5, + .vsync_end = 768 + 5 + 5, + .vtotal = 768 + 5 + 5 + 122, + .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, +}; + +static const struct panel_desc kingdisplay_kd116n21_30nv_a010 = { + .modes = &kingdisplay_kd116n21_30nv_a010_mode, + .num_modes = 1, + .bpc = 6, + .size = { + .width = 256, + .height = 144, + }, + .delay = { + .hpd_absent = 200, + }, +}; + +static const struct drm_display_mode lg_lp079qx1_sp0v_mode = { + .clock = 200000, + .hdisplay = 1536, + .hsync_start = 1536 + 12, + .hsync_end = 1536 + 12 + 16, + .htotal = 1536 + 12 + 16 + 48, + .vdisplay = 2048, + .vsync_start = 2048 + 8, + .vsync_end = 2048 + 8 + 4, + .vtotal = 2048 + 8 + 4 + 8, + .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, +}; + +static const struct panel_desc lg_lp079qx1_sp0v = { + .modes = &lg_lp079qx1_sp0v_mode, + .num_modes = 1, + .size = { + .width = 129, + .height = 171, + }, +}; + +static const struct drm_display_mode lg_lp097qx1_spa1_mode = { + .clock = 205210, + .hdisplay = 2048, + .hsync_start = 2048 + 150, + .hsync_end = 2048 + 150 + 5, + .htotal = 2048 + 150 + 5 + 5, + .vdisplay = 1536, + .vsync_start = 1536 + 3, + .vsync_end = 1536 + 3 + 1, + .vtotal = 1536 + 3 + 1 + 9, +}; + +static const struct panel_desc lg_lp097qx1_spa1 = { + .modes = &lg_lp097qx1_spa1_mode, + .num_modes = 1, + .size = { + .width = 208, + .height = 147, + }, +}; + +static const struct drm_display_mode lg_lp120up1_mode = { + .clock = 162300, + .hdisplay = 1920, + .hsync_start = 1920 + 40, + .hsync_end = 1920 + 40 + 40, + .htotal = 1920 + 40 + 40 + 80, + .vdisplay = 1280, + .vsync_start = 1280 + 4, + .vsync_end = 1280 + 4 + 4, + .vtotal = 1280 + 4 + 4 + 12, +}; + +static const struct panel_desc lg_lp120up1 = { + .modes = &lg_lp120up1_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 267, + .height = 183, + }, +}; + +static const struct drm_display_mode lg_lp129qe_mode = { + .clock = 285250, + .hdisplay = 2560, + .hsync_start = 2560 + 48, + .hsync_end = 2560 + 48 + 32, + .htotal = 2560 + 48 + 32 + 80, + .vdisplay = 1700, + .vsync_start = 1700 + 3, + .vsync_end = 1700 + 3 + 10, + .vtotal = 1700 + 3 + 10 + 36, +}; + +static const struct panel_desc lg_lp129qe = { + .modes = &lg_lp129qe_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 272, + .height = 181, + }, +}; + +static const struct drm_display_mode neweast_wjfh116008a_modes[] = { + { + .clock = 138500, + .hdisplay = 1920, + .hsync_start = 1920 + 48, + .hsync_end = 1920 + 48 + 32, + .htotal = 1920 + 48 + 32 + 80, + .vdisplay = 1080, + .vsync_start = 1080 + 3, + .vsync_end = 1080 + 3 + 5, + .vtotal = 1080 + 3 + 5 + 23, + .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, + }, { + .clock = 110920, + .hdisplay = 1920, + .hsync_start = 1920 + 48, + .hsync_end = 1920 + 48 + 32, + .htotal = 1920 + 48 + 32 + 80, + .vdisplay = 1080, + .vsync_start = 1080 + 3, + .vsync_end = 1080 + 3 + 5, + .vtotal = 1080 + 3 + 5 + 23, + .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, + } +}; + +static const struct panel_desc neweast_wjfh116008a = { + .modes = neweast_wjfh116008a_modes, + .num_modes = 2, + .bpc = 6, + .size = { + .width = 260, + .height = 150, + }, + .delay = { + .hpd_reliable = 110, + .enable = 20, + .unprepare = 500, + }, +}; + +static const struct drm_display_mode samsung_lsn122dl01_c01_mode = { + .clock = 271560, + .hdisplay = 2560, + .hsync_start = 2560 + 48, + .hsync_end = 2560 + 48 + 32, + .htotal = 2560 + 48 + 32 + 80, + .vdisplay = 1600, + .vsync_start = 1600 + 2, + .vsync_end = 1600 + 2 + 5, + .vtotal = 1600 + 2 + 5 + 57, +}; + +static const struct panel_desc samsung_lsn122dl01_c01 = { + .modes = &samsung_lsn122dl01_c01_mode, + .num_modes = 1, + .size = { + .width = 263, + .height = 164, + }, +}; + +static const struct drm_display_mode samsung_ltn140at29_301_mode = { + .clock = 76300, + .hdisplay = 1366, + .hsync_start = 1366 + 64, + .hsync_end = 1366 + 64 + 48, + .htotal = 1366 + 64 + 48 + 128, + .vdisplay = 768, + .vsync_start = 768 + 2, + .vsync_end = 768 + 2 + 5, + .vtotal = 768 + 2 + 5 + 17, +}; + +static const struct panel_desc samsung_ltn140at29_301 = { + .modes = &samsung_ltn140at29_301_mode, + .num_modes = 1, + .bpc = 6, + .size = { + .width = 320, + .height = 187, + }, +}; + +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, + .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, + }, +}; + +static const struct display_timing sharp_lq123p1jx31_timing = { + .pixelclock = { 252750000, 252750000, 266604720 }, + .hactive = { 2400, 2400, 2400 }, + .hfront_porch = { 48, 48, 48 }, + .hback_porch = { 80, 80, 84 }, + .hsync_len = { 32, 32, 32 }, + .vactive = { 1600, 1600, 1600 }, + .vfront_porch = { 3, 3, 3 }, + .vback_porch = { 33, 33, 120 }, + .vsync_len = { 10, 10, 10 }, + .flags = DISPLAY_FLAGS_VSYNC_LOW | DISPLAY_FLAGS_HSYNC_LOW, +}; + +static const struct panel_desc sharp_lq123p1jx31 = { + .timings = &sharp_lq123p1jx31_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 259, + .height = 173, + }, + .delay = { + .hpd_reliable = 110, + .enable = 50, + .unprepare = 550, + }, +}; + +static const struct of_device_id platform_of_match[] = { + { + /* Must be first */ + .compatible = "edp-panel", + }, + /* + * Do not add panels to the list below unless they cannot be handled by + * the generic edp-panel compatible. + * + * The only two valid reasons are: + * - Because of the panel issues (e.g. broken EDID or broken + * identification). + * - Because the eDP drivers didn't wire up the AUX bus properly. + * NOTE that, though this is a marginally valid reason, + * some justification needs to be made for why the platform can't + * wire up the AUX bus properly. + * + * In all other cases the platform should use the aux-bus and declare + * the panel using the 'edp-panel' compatible as a device on the AUX + * bus. + */ + { + .compatible = "auo,b101ean01", + .data = &auo_b101ean01, + }, { + .compatible = "auo,b116xa01", + .data = &auo_b116xak01, + }, { + .compatible = "auo,b133htn01", + .data = &auo_b133htn01, + }, { + .compatible = "auo,b133xtn01", + .data = &auo_b133xtn01, + }, { + .compatible = "boe,nv101wxmn51", + .data = &boe_nv101wxmn51, + }, { + .compatible = "boe,nv110wtm-n61", + .data = &boe_nv110wtm_n61, + }, { + .compatible = "boe,nv133fhm-n61", + .data = &boe_nv133fhm_n61, + }, { + .compatible = "boe,nv133fhm-n62", + .data = &boe_nv133fhm_n61, + }, { + .compatible = "boe,nv140fhmn49", + .data = &boe_nv140fhmn49, + }, { + .compatible = "innolux,n116bca-ea1", + .data = &innolux_n116bca_ea1, + }, { + .compatible = "innolux,n116bge", + .data = &innolux_n116bge, + }, { + .compatible = "innolux,n125hce-gn1", + .data = &innolux_n125hce_gn1, + }, { + .compatible = "innolux,p120zdg-bf1", + .data = &innolux_p120zdg_bf1, + }, { + .compatible = "kingdisplay,kd116n21-30nv-a010", + .data = &kingdisplay_kd116n21_30nv_a010, + }, { + .compatible = "lg,lp079qx1-sp0v", + .data = &lg_lp079qx1_sp0v, + }, { + .compatible = "lg,lp097qx1-spa1", + .data = &lg_lp097qx1_spa1, + }, { + .compatible = "lg,lp120up1", + .data = &lg_lp120up1, + }, { + .compatible = "lg,lp129qe", + .data = &lg_lp129qe, + }, { + .compatible = "neweast,wjfh116008a", + .data = &neweast_wjfh116008a, + }, { + .compatible = "samsung,lsn122dl01-c01", + .data = &samsung_lsn122dl01_c01, + }, { + .compatible = "samsung,ltn140at29-301", + .data = &samsung_ltn140at29_301, + }, { + .compatible = "sharp,ld-d5116z01b", + .data = &sharp_ld_d5116z01b, + }, { + .compatible = "sharp,lq123p1jx31", + .data = &sharp_lq123p1jx31, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, platform_of_match); + +static const struct panel_delay delay_200_500_p2e80 = { + .hpd_absent = 200, + .unprepare = 500, + .prepare_to_enable = 80, +}; + +static const struct panel_delay delay_200_500_e50_p2e80 = { + .hpd_absent = 200, + .unprepare = 500, + .enable = 50, + .prepare_to_enable = 80, +}; + +static const struct panel_delay delay_200_500_p2e100 = { + .hpd_absent = 200, + .unprepare = 500, + .prepare_to_enable = 100, +}; + +static const struct panel_delay delay_200_500_e50 = { + .hpd_absent = 200, + .unprepare = 500, + .enable = 50, +}; + +static const struct panel_delay delay_200_500_e50_d50_p2e200 = { + .hpd_absent = 200, + .unprepare = 500, + .enable = 50, + .disable = 50, + .prepare_to_enable = 200, +}; + +static const struct panel_delay delay_200_500_e80 = { + .hpd_absent = 200, + .unprepare = 500, + .enable = 80, +}; + +static const struct panel_delay delay_200_500_e80_d50 = { + .hpd_absent = 200, + .unprepare = 500, + .enable = 80, + .disable = 50, +}; + +static const struct panel_delay delay_80_500_e50 = { + .hpd_absent = 80, + .unprepare = 500, + .enable = 50, +}; + +static const struct panel_delay delay_80_500_e80_p2e200 = { + .hpd_absent = 80, + .unprepare = 500, + .enable = 80, + .prepare_to_enable = 200, +}; + +static const struct panel_delay delay_100_500_e200 = { + .hpd_absent = 100, + .unprepare = 500, + .enable = 200, +}; + +static const struct panel_delay delay_200_500_e200 = { + .hpd_absent = 200, + .unprepare = 500, + .enable = 200, +}; + +static const struct panel_delay delay_200_500_e200_d200 = { + .hpd_absent = 200, + .unprepare = 500, + .enable = 200, + .disable = 200, +}; + +static const struct panel_delay delay_200_500_e200_d10 = { + .hpd_absent = 200, + .unprepare = 500, + .enable = 200, + .disable = 10, +}; + +static const struct panel_delay delay_200_500_e200_d50 = { + .hpd_absent = 200, + .unprepare = 500, + .enable = 200, + .disable = 50, +}; + +static const struct panel_delay delay_200_150_e200 = { + .hpd_absent = 200, + .unprepare = 150, + .enable = 200, +}; + +static const struct panel_delay delay_200_500_e50_po2e200 = { + .hpd_absent = 200, + .unprepare = 500, + .enable = 50, + .powered_on_to_enable = 200, +}; + +static const struct panel_delay delay_200_150_e50 = { + .hpd_absent = 200, + .unprepare = 150, + .enable = 50, +}; + +static const struct panel_delay delay_200_500_e250 = { + .hpd_absent = 200, + .unprepare = 500, + .enable = 250, +}; + +static const struct panel_delay delay_50_500_e200_d200_po2e335 = { + .hpd_absent = 50, + .unprepare = 500, + .enable = 200, + .disable = 200, + .powered_on_to_enable = 335, +}; + +static const struct panel_delay delay_200_500_e50_d100 = { + .hpd_absent = 200, + .unprepare = 500, + .enable = 50, + .disable = 100, +}; + +static const struct panel_delay delay_80_500_e50_d50 = { + .hpd_absent = 80, + .unprepare = 500, + .enable = 50, + .disable = 50, +}; + +#define EDP_PANEL_ENTRY(vend_chr_0, vend_chr_1, vend_chr_2, product_id, _delay, _name) \ +{ \ + .ident = { \ + .name = _name, \ + .panel_id = drm_edid_encode_panel_id(vend_chr_0, vend_chr_1, vend_chr_2, \ + product_id), \ + }, \ + .delay = _delay \ +} + +#define EDP_PANEL_ENTRY2(vend_chr_0, vend_chr_1, vend_chr_2, product_id, _delay, _name, _mode) \ +{ \ + .ident = { \ + .name = _name, \ + .panel_id = drm_edid_encode_panel_id(vend_chr_0, vend_chr_1, vend_chr_2, \ + product_id), \ + }, \ + .delay = _delay, \ + .override_edid_mode = _mode \ +} + +/* + * This table is used to figure out power sequencing delays for panels that + * are detected by EDID. Entries here may point to entries in the + * platform_of_match table (if a panel is listed in both places). + * + * Sort first by vendor, then by product ID. + */ +static const struct edp_panel_entry edp_panels[] = { + EDP_PANEL_ENTRY('A', 'U', 'O', 0x04a4, &delay_200_500_e50, "B122UAN01.0"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0x105c, &delay_200_500_e50, "B116XTN01.0"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0x1062, &delay_200_500_e50, "B120XAN01.0"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0x125c, &delay_200_500_e50, "Unknown"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0x145c, &delay_200_500_e50, "B116XAB01.4"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0x1999, &delay_200_500_e50, "Unknown"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0x1e9b, &delay_200_500_e50, "B133UAN02.1"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0x1ea5, &delay_200_500_e50, "B116XAK01.6"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0x203d, &delay_200_500_e50, "B140HTN02.0"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0x205c, &delay_200_500_e50, "B116XAN02.0"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0x208d, &delay_200_500_e50, "B140HTN02.1"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0x235c, &delay_200_500_e50, "B116XTN02.3"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0x239b, &delay_200_500_e50, "B116XAN06.1"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0x255c, &delay_200_500_e50, "B116XTN02.5"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0x30ed, &delay_200_500_e50, "G156HAN03.0"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0x403d, &delay_200_500_e50, "B140HAN04.0"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0x405c, &auo_b116xak01.delay, "B116XAN04.0"), + EDP_PANEL_ENTRY2('A', 'U', 'O', 0x405c, &auo_b116xak01.delay, "B116XAK01.0", + &auo_b116xa3_mode), + EDP_PANEL_ENTRY('A', 'U', 'O', 0x435c, &delay_200_500_e50, "Unknown"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0x52b0, &delay_200_500_e50, "B116XAK02.0"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0x582d, &delay_200_500_e50, "B133UAN01.0"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0x615c, &delay_200_500_e50, "B116XAN06.1"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0x635c, &delay_200_500_e50, "B116XAN06.3"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0x639c, &delay_200_500_e50, "B140HAK02.7"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0x723c, &delay_200_500_e50, "B140XTN07.2"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0x73aa, &delay_200_500_e50, "B116XTN02.3"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0x8594, &delay_200_500_e50, "B133UAN01.0"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0x8bba, &delay_200_500_e50, "B140UAN08.5"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0xa199, &delay_200_500_e50, "B116XAN06.1"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0xa7b3, &delay_200_500_e50, "B140UAN04.4"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0xb7a9, &delay_200_500_e50, "B140HAK03.3"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0xc4b4, &delay_200_500_e50, "B116XAT04.1"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0xc9a8, &delay_200_500_e50, "B140QAN08.H"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0xcdba, &delay_200_500_e50, "B140UAX01.2"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0xd497, &delay_200_500_e50, "B120XAN01.0"), + EDP_PANEL_ENTRY('A', 'U', 'O', 0xf390, &delay_200_500_e50, "B140XTN07.7"), + + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0607, &delay_200_500_e200, "Unknown"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0608, &delay_200_500_e50, "NT116WHM-N11"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0609, &delay_200_500_e50_po2e200, "NT116WHM-N21 V4.1"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0623, &delay_200_500_e200, "NT116WHM-N21 V4.0"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0668, &delay_200_500_e200, "Unknown"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x068f, &delay_200_500_e200, "Unknown"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x06e5, &delay_200_500_e200, "Unknown"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0705, &delay_200_500_e200, "Unknown"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0715, &delay_200_150_e200, "NT116WHM-N21"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0717, &delay_200_500_e50_po2e200, "NV133FHM-N42"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0731, &delay_200_500_e80, "NT116WHM-N42"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0741, &delay_200_500_e200, "NT116WHM-N44"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0744, &delay_200_500_e200, "Unknown"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x074c, &delay_200_500_e200, "Unknown"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0751, &delay_200_500_e200, "Unknown"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0754, &delay_200_500_e50_po2e200, "NV116WHM-N45"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0771, &delay_200_500_e200, "Unknown"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0786, &delay_200_500_p2e80, "NV116WHM-T01"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0797, &delay_200_500_e200, "Unknown"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x07a8, &delay_200_500_e50_po2e200, "NT116WHM-N21"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x07d1, &boe_nv133fhm_n61.delay, "NV133FHM-N61"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x07d3, &delay_200_500_e200, "Unknown"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x07f6, &delay_200_500_e200, "NT140FHM-N44"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x07f8, &delay_200_500_e200, "Unknown"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0813, &delay_200_500_e200, "Unknown"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0827, &delay_200_500_e50_p2e80, "NT140WHM-N44 V8.0"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x082d, &boe_nv133fhm_n61.delay, "NV133FHM-N62"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0843, &delay_200_500_e200, "Unknown"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x08b2, &delay_200_500_e200, "NT140WHM-N49"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0848, &delay_200_500_e200, "Unknown"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0849, &delay_200_500_e200, "Unknown"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x09c3, &delay_200_500_e50, "NT116WHM-N21,836X2"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x094b, &delay_200_500_e50, "NT116WHM-N21"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0951, &delay_200_500_e80, "NV116WHM-N47"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x095f, &delay_200_500_e50, "NE135FBM-N41 v8.1"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0964, &delay_200_500_e50, "NV133WUM-N61"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x096e, &delay_200_500_e50_po2e200, "NV116WHM-T07 V8.0"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0979, &delay_200_500_e50, "NV116WHM-N49 V8.0"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x098d, &boe_nv110wtm_n61.delay, "NV110WTM-N61"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0993, &delay_200_500_e80, "NV116WHM-T14 V8.0"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x09ad, &delay_200_500_e80, "NV116WHM-N47"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x09ae, &delay_200_500_e200, "NT140FHM-N45"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x09dd, &delay_200_500_e50, "NT116WHM-N21"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0a1b, &delay_200_500_e50, "NV133WUM-N63"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0a36, &delay_200_500_e200, "Unknown"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0a3e, &delay_200_500_e80_d50, "NV116WHM-N49"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0a5d, &delay_200_500_e50, "NV116WHM-N45"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0a6a, &delay_200_500_e80, "NV140WUM-N44"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0a84, &delay_200_500_e50, "NV133WUM-T01"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0ac5, &delay_200_500_e50, "NV116WHM-N4C"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0ae8, &delay_200_500_e50_p2e80, "NV140WUM-N41"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0b09, &delay_200_500_e50_po2e200, "NV140FHM-NZ"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0b1e, &delay_200_500_e80, "NE140QDM-N6A"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0b34, &delay_200_500_e80_d50, "NV122WUM-N41"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0b43, &delay_200_500_e200, "NV140FHM-T09"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0b56, &delay_200_500_e80, "NT140FHM-N47"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0b66, &delay_200_500_e80, "NE140WUM-N6G"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0c20, &delay_200_500_e80, "NT140FHM-N47"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0c93, &delay_200_500_e200, "Unknown"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0cb6, &delay_200_500_e200, "NT116WHM-N44"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0cf2, &delay_200_500_e200, "NV156FHM-N4S"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0cf6, &delay_200_500_e200, "NV140WUM-N64"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0cfa, &delay_200_500_e50, "NV116WHM-A4D"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0d45, &delay_200_500_e80, "NV116WHM-N4B"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0d73, &delay_200_500_e80, "NE140WUM-N6S"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0ddf, &delay_200_500_e80, "NV116WHM-T01"), + + EDP_PANEL_ENTRY('C', 'M', 'N', 0x1130, &delay_200_500_e50, "N116BGE-EB2"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x1132, &delay_200_500_e80_d50, "N116BGE-EA2"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x1138, &innolux_n116bca_ea1.delay, "N116BCA-EA1-RC4"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x1139, &delay_200_500_e80_d50, "N116BGE-EA2"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x1141, &delay_200_500_e80_d50, "Unknown"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x1145, &delay_200_500_e80_d50, "N116BCN-EB1"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x114a, &delay_200_500_e80_d50, "Unknown"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x114c, &innolux_n116bca_ea1.delay, "N116BCA-EA1"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x1152, &delay_200_500_e80_d50, "N116BCN-EA1"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x1153, &delay_200_500_e80_d50, "N116BGE-EA2"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x1154, &delay_200_500_e80_d50, "N116BCA-EA2"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x1156, &delay_200_500_e80_d50, "Unknown"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x1157, &delay_200_500_e80_d50, "N116BGE-EA2"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x115b, &delay_200_500_e80_d50, "N116BCN-EB1"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x115d, &delay_200_500_e80_d50, "N116BCA-EA2"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x115e, &delay_200_500_e80_d50, "N116BCA-EA1"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x115f, &delay_200_500_e80_d50, "N116BCL-EAK"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x1160, &delay_200_500_e80_d50, "N116BCJ-EAK"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x1161, &delay_200_500_e80, "N116BCP-EA2"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x1163, &delay_200_500_e80_d50, "N116BCJ-EAK"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x1247, &delay_200_500_e80_d50, "N120ACA-EA1"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x124c, &delay_200_500_e80_d50, "N122JCA-ENK"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x142b, &delay_200_500_e80_d50, "N140HCA-EAC"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x142e, &delay_200_500_e80_d50, "N140BGA-EA4"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x1441, &delay_200_500_e80_d50, "N140JCA-ELK"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x144f, &delay_200_500_e80_d50, "N140HGA-EA1"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x1468, &delay_200_500_e80, "N140HGA-EA1"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x148f, &delay_200_500_e80, "N140HCA-EAC"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x14a8, &delay_200_500_e80, "N140JCA-ELP"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x14d4, &delay_200_500_e80_d50, "N140HCA-EAC"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x14d6, &delay_200_500_e80_d50, "N140BGA-EA4"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x14e5, &delay_200_500_e80_d50, "N140HGA-EA1"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x1565, &delay_200_500_e80, "N156HCA-EAB"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x162b, &delay_200_500_e80_d50, "N160JCE-ELL"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x7402, &delay_200_500_e200_d50, "N116BCA-EAK"), + + EDP_PANEL_ENTRY('C', 'S', 'O', 0x1200, &delay_200_500_e50_d50_p2e200, "MNC207QS1-1"), + EDP_PANEL_ENTRY('C', 'S', 'O', 0x1413, &delay_200_500_e50_d50_p2e200, "MNE007JA1-2"), + + EDP_PANEL_ENTRY('C', 'S', 'W', 0x1100, &delay_200_500_e80_d50, "MNB601LS1-1"), + EDP_PANEL_ENTRY('C', 'S', 'W', 0x1103, &delay_200_500_e80_d50, "MNB601LS1-3"), + EDP_PANEL_ENTRY('C', 'S', 'W', 0x1104, &delay_200_500_e50_d100, "MNB601LS1-4"), + EDP_PANEL_ENTRY('C', 'S', 'W', 0x143f, &delay_200_500_e50, "MNE007QS3-6"), + EDP_PANEL_ENTRY('C', 'S', 'W', 0x1448, &delay_200_500_e50, "MNE007QS3-7"), + EDP_PANEL_ENTRY('C', 'S', 'W', 0x144b, &delay_200_500_e80, "MNE001BS1-4"), + EDP_PANEL_ENTRY('C', 'S', 'W', 0x1457, &delay_80_500_e80_p2e200, "MNE007QS3-8"), + EDP_PANEL_ENTRY('C', 'S', 'W', 0x1462, &delay_200_500_e50, "MNE007QS5-2"), + EDP_PANEL_ENTRY('C', 'S', 'W', 0x1468, &delay_200_500_e50, "MNE007QB2-2"), + EDP_PANEL_ENTRY('C', 'S', 'W', 0x146e, &delay_80_500_e50_d50, "MNE007QB3-1"), + EDP_PANEL_ENTRY('C', 'S', 'W', 0x1519, &delay_200_500_e80_d50, "MNF601BS1-3"), + + EDP_PANEL_ENTRY('E', 'T', 'C', 0x0000, &delay_50_500_e200_d200_po2e335, "LP079QX1-SP0V"), + + EDP_PANEL_ENTRY('H', 'K', 'C', 0x2d51, &delay_200_500_e200, "Unknown"), + EDP_PANEL_ENTRY('H', 'K', 'C', 0x2d5b, &delay_200_500_e200, "MB116AN01"), + EDP_PANEL_ENTRY('H', 'K', 'C', 0x2d5c, &delay_200_500_e200, "MB116AN01-2"), + + EDP_PANEL_ENTRY('I', 'V', 'O', 0x048e, &delay_200_500_e200_d10, "M116NWR6 R5"), + EDP_PANEL_ENTRY('I', 'V', 'O', 0x057d, &delay_200_500_e200, "R140NWF5 RH"), + EDP_PANEL_ENTRY('I', 'V', 'O', 0x854a, &delay_200_500_p2e100, "M133NW4J"), + EDP_PANEL_ENTRY('I', 'V', 'O', 0x854b, &delay_200_500_p2e100, "R133NW4K-R0"), + EDP_PANEL_ENTRY('I', 'V', 'O', 0x8c4d, &delay_200_150_e200, "R140NWFM R1"), + + EDP_PANEL_ENTRY('K', 'D', 'B', 0x044f, &delay_200_500_e80_d50, "Unknown"), + EDP_PANEL_ENTRY('K', 'D', 'B', 0x0624, &kingdisplay_kd116n21_30nv_a010.delay, "116N21-30NV-A010"), + EDP_PANEL_ENTRY('K', 'D', 'B', 0x1118, &delay_200_500_e50, "KD116N29-30NK-A005"), + EDP_PANEL_ENTRY('K', 'D', 'B', 0x1120, &delay_200_500_e80_d50, "116N29-30NK-C007"), + EDP_PANEL_ENTRY('K', 'D', 'B', 0x1212, &delay_200_500_e50, "KD116N0930A16"), + EDP_PANEL_ENTRY('K', 'D', 'B', 0x1707, &delay_200_150_e50, "KD116N2130B12"), + + EDP_PANEL_ENTRY('K', 'D', 'C', 0x0110, &delay_200_500_e50, "KD116N3730A07"), + EDP_PANEL_ENTRY('K', 'D', 'C', 0x0397, &delay_200_500_e50, "KD116N3730A12"), + EDP_PANEL_ENTRY('K', 'D', 'C', 0x044f, &delay_200_500_e50, "KD116N9-30NH-F3"), + EDP_PANEL_ENTRY('K', 'D', 'C', 0x05f1, &delay_200_500_e80_d50, "KD116N5-30NV-G7"), + EDP_PANEL_ENTRY('K', 'D', 'C', 0x0809, &delay_200_500_e50, "KD116N2930A15"), + EDP_PANEL_ENTRY('K', 'D', 'C', 0x1220, &delay_200_500_e50, "KD116N3730A05"), + + EDP_PANEL_ENTRY('L', 'G', 'D', 0x0000, &delay_200_500_e200_d200, "Unknown"), + EDP_PANEL_ENTRY('L', 'G', 'D', 0x048d, &delay_200_500_e200_d200, "Unknown"), + EDP_PANEL_ENTRY('L', 'G', 'D', 0x0497, &delay_200_500_e200_d200, "LP116WH7-SPB1"), + EDP_PANEL_ENTRY('L', 'G', 'D', 0x052c, &delay_200_500_e200_d200, "LP133WF2-SPL7"), + EDP_PANEL_ENTRY('L', 'G', 'D', 0x0537, &delay_200_500_e200_d200, "Unknown"), + EDP_PANEL_ENTRY('L', 'G', 'D', 0x054a, &delay_200_500_e200_d200, "LP116WH8-SPC1"), + EDP_PANEL_ENTRY('L', 'G', 'D', 0x0567, &delay_200_500_e200_d200, "Unknown"), + EDP_PANEL_ENTRY('L', 'G', 'D', 0x05af, &delay_200_500_e200_d200, "Unknown"), + EDP_PANEL_ENTRY('L', 'G', 'D', 0x05f1, &delay_200_500_e200_d200, "Unknown"), + EDP_PANEL_ENTRY('L', 'G', 'D', 0x0778, &delay_200_500_e200_d200, "134WT1"), + + EDP_PANEL_ENTRY('S', 'H', 'P', 0x1511, &delay_200_500_e50, "LQ140M1JW48"), + EDP_PANEL_ENTRY('S', 'H', 'P', 0x1523, &delay_80_500_e50, "LQ140M1JW46"), + EDP_PANEL_ENTRY('S', 'H', 'P', 0x153a, &delay_200_500_e50, "LQ140T1JH01"), + EDP_PANEL_ENTRY('S', 'H', 'P', 0x154c, &delay_200_500_p2e100, "LQ116M1JW10"), + EDP_PANEL_ENTRY('S', 'H', 'P', 0x158f, &delay_200_500_p2e100, "LQ134Z1"), + EDP_PANEL_ENTRY('S', 'H', 'P', 0x1593, &delay_200_500_p2e100, "LQ134N1"), + + EDP_PANEL_ENTRY('S', 'T', 'A', 0x0004, &delay_200_500_e200, "116KHD024006"), + EDP_PANEL_ENTRY('S', 'T', 'A', 0x0009, &delay_200_500_e250, "116QHD024002"), + EDP_PANEL_ENTRY('S', 'T', 'A', 0x0100, &delay_100_500_e200, "2081116HHD028001-51D"), + + EDP_PANEL_ENTRY('T', 'M', 'A', 0x0811, &delay_200_500_e80_d50, "TM140VDXP01-04"), + EDP_PANEL_ENTRY('T', 'M', 'A', 0x2094, &delay_200_500_e50_d100, "TL140VDMS03-01"), + + { /* sentinal */ } +}; + +static const struct edp_panel_entry *find_edp_panel(u32 panel_id, const struct drm_edid *edid) +{ + const struct edp_panel_entry *panel; + + if (!panel_id) + return NULL; + + /* + * Match with identity first. This allows handling the case where + * vendors incorrectly reused the same panel ID for multiple panels that + * need different settings. If there's no match, try again with panel + * ID, which should be unique. + */ + for (panel = edp_panels; panel->ident.panel_id; panel++) + if (drm_edid_match(edid, &panel->ident)) + return panel; + + for (panel = edp_panels; panel->ident.panel_id; panel++) + if (panel->ident.panel_id == panel_id) + return panel; + + return NULL; +} + +static int panel_edp_platform_probe(struct platform_device *pdev) +{ + const struct of_device_id *id; + + /* Skip one since "edp-panel" is only supported on DP AUX bus */ + id = of_match_node(platform_of_match + 1, pdev->dev.of_node); + if (!id) + return -ENODEV; + + return panel_edp_probe(&pdev->dev, id->data, NULL); +} + +static void panel_edp_platform_remove(struct platform_device *pdev) +{ + panel_edp_remove(&pdev->dev); +} + +static void panel_edp_platform_shutdown(struct platform_device *pdev) +{ + panel_edp_shutdown(&pdev->dev); +} + +static const struct dev_pm_ops panel_edp_pm_ops = { + SET_RUNTIME_PM_OPS(panel_edp_suspend, panel_edp_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + +static struct platform_driver panel_edp_platform_driver = { + .driver = { + .name = "panel-edp", + .of_match_table = platform_of_match, + .pm = &panel_edp_pm_ops, + }, + .probe = panel_edp_platform_probe, + .remove = panel_edp_platform_remove, + .shutdown = panel_edp_platform_shutdown, +}; + +static int panel_edp_dp_aux_ep_probe(struct dp_aux_ep_device *aux_ep) +{ + const struct of_device_id *id; + + id = of_match_node(platform_of_match, aux_ep->dev.of_node); + if (!id) + return -ENODEV; + + return panel_edp_probe(&aux_ep->dev, id->data, aux_ep->aux); +} + +static void panel_edp_dp_aux_ep_remove(struct dp_aux_ep_device *aux_ep) +{ + panel_edp_remove(&aux_ep->dev); +} + +static void panel_edp_dp_aux_ep_shutdown(struct dp_aux_ep_device *aux_ep) +{ + panel_edp_shutdown(&aux_ep->dev); +} + +static struct dp_aux_ep_driver panel_edp_dp_aux_ep_driver = { + .driver = { + .name = "panel-simple-dp-aux", + .of_match_table = platform_of_match, /* Same as platform one! */ + .pm = &panel_edp_pm_ops, + }, + .probe = panel_edp_dp_aux_ep_probe, + .remove = panel_edp_dp_aux_ep_remove, + .shutdown = panel_edp_dp_aux_ep_shutdown, +}; + +static int __init panel_edp_init(void) +{ + int err; + + err = platform_driver_register(&panel_edp_platform_driver); + if (err < 0) + return err; + + err = dp_aux_dp_driver_register(&panel_edp_dp_aux_ep_driver); + if (err < 0) + goto err_did_platform_register; + + return 0; + +err_did_platform_register: + platform_driver_unregister(&panel_edp_platform_driver); + + return err; +} +module_init(panel_edp_init); + +static void __exit panel_edp_exit(void) +{ + dp_aux_dp_driver_unregister(&panel_edp_dp_aux_ep_driver); + platform_driver_unregister(&panel_edp_platform_driver); +} +module_exit(panel_edp_exit); + +MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>"); +MODULE_DESCRIPTION("DRM Driver for Simple eDP Panels"); +MODULE_LICENSE("GPL and additional rights"); diff --git a/drivers/gpu/drm/panel/panel-elida-kd35t133.c b/drivers/gpu/drm/panel/panel-elida-kd35t133.c new file mode 100644 index 000000000000..1f177834d629 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-elida-kd35t133.c @@ -0,0 +1,298 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Elida kd35t133 3.5" MIPI-DSI panel driver + * Copyright (C) 2020 Theobroma Systems Design und Consulting GmbH + * + * based on + * + * Rockteck jh057n00900 5.5" MIPI-DSI panel driver + * Copyright (C) Purism SPC 2019 + */ + +#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> + +#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> + +/* Manufacturer specific Commands send via DSI */ +#define KD35T133_CMD_INTERFACEMODECTRL 0xb0 +#define KD35T133_CMD_FRAMERATECTRL 0xb1 +#define KD35T133_CMD_DISPLAYINVERSIONCTRL 0xb4 +#define KD35T133_CMD_DISPLAYFUNCTIONCTRL 0xb6 +#define KD35T133_CMD_POWERCONTROL1 0xc0 +#define KD35T133_CMD_POWERCONTROL2 0xc1 +#define KD35T133_CMD_VCOMCONTROL 0xc5 +#define KD35T133_CMD_POSITIVEGAMMA 0xe0 +#define KD35T133_CMD_NEGATIVEGAMMA 0xe1 +#define KD35T133_CMD_SETIMAGEFUNCTION 0xe9 +#define KD35T133_CMD_ADJUSTCONTROL3 0xf7 + +struct kd35t133 { + struct device *dev; + struct drm_panel panel; + struct gpio_desc *reset_gpio; + struct regulator *vdd; + struct regulator *iovcc; + enum drm_panel_orientation orientation; +}; + +static inline struct kd35t133 *panel_to_kd35t133(struct drm_panel *panel) +{ + return container_of(panel, struct kd35t133, panel); +} + +static void kd35t133_init_sequence(struct mipi_dsi_multi_context *dsi_ctx) +{ + /* + * Init sequence was supplied by the panel vendor with minimal + * documentation. + */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, KD35T133_CMD_POSITIVEGAMMA, + 0x00, 0x13, 0x18, 0x04, 0x0f, 0x06, 0x3a, 0x56, + 0x4d, 0x03, 0x0a, 0x06, 0x30, 0x3e, 0x0f); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, KD35T133_CMD_NEGATIVEGAMMA, + 0x00, 0x13, 0x18, 0x01, 0x11, 0x06, 0x38, 0x34, + 0x4d, 0x06, 0x0d, 0x0b, 0x31, 0x37, 0x0f); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, KD35T133_CMD_POWERCONTROL1, 0x18, 0x17); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, KD35T133_CMD_POWERCONTROL2, 0x41); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, KD35T133_CMD_VCOMCONTROL, 0x00, 0x1a, 0x80); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, MIPI_DCS_SET_ADDRESS_MODE, 0x48); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, MIPI_DCS_SET_PIXEL_FORMAT, 0x55); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, KD35T133_CMD_INTERFACEMODECTRL, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, KD35T133_CMD_FRAMERATECTRL, 0xa0); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, KD35T133_CMD_DISPLAYINVERSIONCTRL, 0x02); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, KD35T133_CMD_DISPLAYFUNCTIONCTRL, + 0x20, 0x02); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, KD35T133_CMD_SETIMAGEFUNCTION, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, KD35T133_CMD_ADJUSTCONTROL3, + 0xa9, 0x51, 0x2c, 0x82); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, MIPI_DCS_ENTER_INVERT_MODE); +} + +static int kd35t133_unprepare(struct drm_panel *panel) +{ + struct kd35t133 *ctx = panel_to_kd35t133(panel); + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + if (dsi_ctx.accum_err) + return dsi_ctx.accum_err; + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + + regulator_disable(ctx->iovcc); + regulator_disable(ctx->vdd); + + return 0; +} + +static int kd35t133_prepare(struct drm_panel *panel) +{ + struct kd35t133 *ctx = panel_to_kd35t133(panel); + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + dev_dbg(ctx->dev, "Resetting the panel\n"); + dsi_ctx.accum_err = regulator_enable(ctx->vdd); + if (dsi_ctx.accum_err) { + dev_err(ctx->dev, "Failed to enable vdd supply: %d\n", + dsi_ctx.accum_err); + return dsi_ctx.accum_err; + } + + dsi_ctx.accum_err = regulator_enable(ctx->iovcc); + if (dsi_ctx.accum_err) { + dev_err(ctx->dev, "Failed to enable iovcc supply: %d\n", + dsi_ctx.accum_err); + goto disable_vdd; + } + + msleep(20); + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(10, 20); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + + msleep(20); + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 250); + + kd35t133_init_sequence(&dsi_ctx); + if (!dsi_ctx.accum_err) + dev_dbg(ctx->dev, "Panel init sequence done\n"); + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 50); + + if (dsi_ctx.accum_err) + goto disable_iovcc; + + return 0; + +disable_iovcc: + regulator_disable(ctx->iovcc); +disable_vdd: + regulator_disable(ctx->vdd); + return dsi_ctx.accum_err; +} + +static const struct drm_display_mode default_mode = { + .hdisplay = 320, + .hsync_start = 320 + 130, + .hsync_end = 320 + 130 + 4, + .htotal = 320 + 130 + 4 + 130, + .vdisplay = 480, + .vsync_start = 480 + 2, + .vsync_end = 480 + 2 + 1, + .vtotal = 480 + 2 + 1 + 2, + .clock = 17000, + .width_mm = 42, + .height_mm = 82, +}; + +static int kd35t133_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct kd35t133 *ctx = panel_to_kd35t133(panel); + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &default_mode); + if (!mode) { + dev_err(ctx->dev, "Failed to add mode %ux%u@%u\n", + default_mode.hdisplay, default_mode.vdisplay, + drm_mode_vrefresh(&default_mode)); + 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 enum drm_panel_orientation kd35t133_get_orientation(struct drm_panel *panel) +{ + struct kd35t133 *ctx = panel_to_kd35t133(panel); + + return ctx->orientation; +} + +static const struct drm_panel_funcs kd35t133_funcs = { + .unprepare = kd35t133_unprepare, + .prepare = kd35t133_prepare, + .get_modes = kd35t133_get_modes, + .get_orientation = kd35t133_get_orientation, +}; + +static int kd35t133_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct kd35t133 *ctx; + int ret; + + ctx = devm_drm_panel_alloc(dev, struct kd35t133, panel, + &kd35t133_funcs, DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ctx->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->reset_gpio)) { + dev_err(dev, "cannot get reset gpio\n"); + return PTR_ERR(ctx->reset_gpio); + } + + ctx->vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(ctx->vdd)) { + ret = PTR_ERR(ctx->vdd); + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to request vdd 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) + dev_err(dev, "Failed to request iovcc regulator: %d\n", ret); + return ret; + } + + ret = of_drm_get_panel_orientation(dev->of_node, &ctx->orientation); + if (ret < 0) { + dev_err(dev, "%pOF: failed to get orientation %d\n", dev->of_node, ret); + return ret; + } + + mipi_dsi_set_drvdata(dsi, ctx); + + ctx->dev = dev; + + dsi->lanes = 1; + 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_NO_EOT_PACKET | + MIPI_DSI_CLOCK_NON_CONTINUOUS; + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return ret; + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "mipi_dsi_attach failed: %d\n", ret); + drm_panel_remove(&ctx->panel); + return ret; + } + + return 0; +} + +static void kd35t133_remove(struct mipi_dsi_device *dsi) +{ + struct kd35t133 *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id kd35t133_of_match[] = { + { .compatible = "elida,kd35t133" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, kd35t133_of_match); + +static struct mipi_dsi_driver kd35t133_driver = { + .driver = { + .name = "panel-elida-kd35t133", + .of_match_table = kd35t133_of_match, + }, + .probe = kd35t133_probe, + .remove = kd35t133_remove, +}; +module_mipi_dsi_driver(kd35t133_driver); + +MODULE_AUTHOR("Heiko Stuebner <heiko.stuebner@theobroma-systems.com>"); +MODULE_DESCRIPTION("DRM driver for Elida kd35t133 MIPI DSI panel"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-feixin-k101-im2ba02.c b/drivers/gpu/drm/panel/panel-feixin-k101-im2ba02.c new file mode 100644 index 000000000000..6225501cb174 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-feixin-k101-im2ba02.c @@ -0,0 +1,513 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2019-2020 Icenowy Zheng <icenowy@aosc.io> + */ + +#include <linux/gpio/consumer.h> +#include <linux/delay.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/regulator/consumer.h> + +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +#define K101_IM2BA02_INIT_CMD_LEN 2 + +static const char * const regulator_names[] = { + "dvdd", + "avdd", + "cvdd" +}; + +struct k101_im2ba02 { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + + struct regulator_bulk_data supplies[ARRAY_SIZE(regulator_names)]; + struct gpio_desc *reset; +}; + +static inline struct k101_im2ba02 *panel_to_k101_im2ba02(struct drm_panel *panel) +{ + return container_of(panel, struct k101_im2ba02, panel); +} + +struct k101_im2ba02_init_cmd { + u8 data[K101_IM2BA02_INIT_CMD_LEN]; +}; + +static const struct k101_im2ba02_init_cmd k101_im2ba02_init_cmds[] = { + /* Switch to page 0 */ + { .data = { 0xE0, 0x00 } }, + + /* Seems to be some password */ + { .data = { 0xE1, 0x93} }, + { .data = { 0xE2, 0x65 } }, + { .data = { 0xE3, 0xF8 } }, + + /* Lane number, 0x02 - 3 lanes, 0x03 - 4 lanes */ + { .data = { 0x80, 0x03 } }, + + /* Sequence control */ + { .data = { 0x70, 0x02 } }, + { .data = { 0x71, 0x23 } }, + { .data = { 0x72, 0x06 } }, + + /* Switch to page 1 */ + { .data = { 0xE0, 0x01 } }, + + /* Set VCOM */ + { .data = { 0x00, 0x00 } }, + { .data = { 0x01, 0x66 } }, + /* Set VCOM_Reverse */ + { .data = { 0x03, 0x00 } }, + { .data = { 0x04, 0x25 } }, + + /* Set Gamma Power, VG[MS][PN] */ + { .data = { 0x17, 0x00 } }, + { .data = { 0x18, 0x6D } }, + { .data = { 0x19, 0x00 } }, + { .data = { 0x1A, 0x00 } }, + { .data = { 0x1B, 0xBF } }, /* VGMN = -4.5V */ + { .data = { 0x1C, 0x00 } }, + + /* Set Gate Power */ + { .data = { 0x1F, 0x3E } }, /* VGH_R = 15V */ + { .data = { 0x20, 0x28 } }, /* VGL_R = -11V */ + { .data = { 0x21, 0x28 } }, /* VGL_R2 = -11V */ + { .data = { 0x22, 0x0E } }, /* PA[6:4] = 0, PA[0] = 0 */ + + /* Set Panel */ + { .data = { 0x37, 0x09 } }, /* SS = 1, BGR = 1 */ + + /* Set RGBCYC */ + { .data = { 0x38, 0x04 } }, /* JDT = 100 column inversion */ + { .data = { 0x39, 0x08 } }, /* RGB_N_EQ1 */ + { .data = { 0x3A, 0x12 } }, /* RGB_N_EQ2 */ + { .data = { 0x3C, 0x78 } }, /* set EQ3 for TE_H */ + { .data = { 0x3D, 0xFF } }, /* set CHGEN_ON */ + { .data = { 0x3E, 0xFF } }, /* set CHGEN_OFF */ + { .data = { 0x3F, 0x7F } }, /* set CHGEN_OFF2 */ + + /* Set TCON parameter */ + { .data = { 0x40, 0x06 } }, /* RSO = 800 points */ + { .data = { 0x41, 0xA0 } }, /* LN = 1280 lines */ + + /* Set power voltage */ + { .data = { 0x55, 0x0F } }, /* DCDCM */ + { .data = { 0x56, 0x01 } }, + { .data = { 0x57, 0x69 } }, + { .data = { 0x58, 0x0A } }, + { .data = { 0x59, 0x0A } }, + { .data = { 0x5A, 0x45 } }, + { .data = { 0x5B, 0x15 } }, + + /* Set gamma */ + { .data = { 0x5D, 0x7C } }, + { .data = { 0x5E, 0x65 } }, + { .data = { 0x5F, 0x55 } }, + { .data = { 0x60, 0x49 } }, + { .data = { 0x61, 0x44 } }, + { .data = { 0x62, 0x35 } }, + { .data = { 0x63, 0x3A } }, + { .data = { 0x64, 0x23 } }, + { .data = { 0x65, 0x3D } }, + { .data = { 0x66, 0x3C } }, + { .data = { 0x67, 0x3D } }, + { .data = { 0x68, 0x5D } }, + { .data = { 0x69, 0x4D } }, + { .data = { 0x6A, 0x56 } }, + { .data = { 0x6B, 0x48 } }, + { .data = { 0x6C, 0x45 } }, + { .data = { 0x6D, 0x38 } }, + { .data = { 0x6E, 0x25 } }, + { .data = { 0x6F, 0x00 } }, + { .data = { 0x70, 0x7C } }, + { .data = { 0x71, 0x65 } }, + { .data = { 0x72, 0x55 } }, + { .data = { 0x73, 0x49 } }, + { .data = { 0x74, 0x44 } }, + { .data = { 0x75, 0x35 } }, + { .data = { 0x76, 0x3A } }, + { .data = { 0x77, 0x23 } }, + { .data = { 0x78, 0x3D } }, + { .data = { 0x79, 0x3C } }, + { .data = { 0x7A, 0x3D } }, + { .data = { 0x7B, 0x5D } }, + { .data = { 0x7C, 0x4D } }, + { .data = { 0x7D, 0x56 } }, + { .data = { 0x7E, 0x48 } }, + { .data = { 0x7F, 0x45 } }, + { .data = { 0x80, 0x38 } }, + { .data = { 0x81, 0x25 } }, + { .data = { 0x82, 0x00 } }, + + /* Switch to page 2, for GIP */ + { .data = { 0xE0, 0x02 } }, + + { .data = { 0x00, 0x1E } }, + { .data = { 0x01, 0x1E } }, + { .data = { 0x02, 0x41 } }, + { .data = { 0x03, 0x41 } }, + { .data = { 0x04, 0x43 } }, + { .data = { 0x05, 0x43 } }, + { .data = { 0x06, 0x1F } }, + { .data = { 0x07, 0x1F } }, + { .data = { 0x08, 0x1F } }, + { .data = { 0x09, 0x1F } }, + { .data = { 0x0A, 0x1E } }, + { .data = { 0x0B, 0x1E } }, + { .data = { 0x0C, 0x1F } }, + { .data = { 0x0D, 0x47 } }, + { .data = { 0x0E, 0x47 } }, + { .data = { 0x0F, 0x45 } }, + { .data = { 0x10, 0x45 } }, + { .data = { 0x11, 0x4B } }, + { .data = { 0x12, 0x4B } }, + { .data = { 0x13, 0x49 } }, + { .data = { 0x14, 0x49 } }, + { .data = { 0x15, 0x1F } }, + + { .data = { 0x16, 0x1E } }, + { .data = { 0x17, 0x1E } }, + { .data = { 0x18, 0x40 } }, + { .data = { 0x19, 0x40 } }, + { .data = { 0x1A, 0x42 } }, + { .data = { 0x1B, 0x42 } }, + { .data = { 0x1C, 0x1F } }, + { .data = { 0x1D, 0x1F } }, + { .data = { 0x1E, 0x1F } }, + { .data = { 0x1F, 0x1f } }, + { .data = { 0x20, 0x1E } }, + { .data = { 0x21, 0x1E } }, + { .data = { 0x22, 0x1f } }, + { .data = { 0x23, 0x46 } }, + { .data = { 0x24, 0x46 } }, + { .data = { 0x25, 0x44 } }, + { .data = { 0x26, 0x44 } }, + { .data = { 0x27, 0x4A } }, + { .data = { 0x28, 0x4A } }, + { .data = { 0x29, 0x48 } }, + { .data = { 0x2A, 0x48 } }, + { .data = { 0x2B, 0x1f } }, + + { .data = { 0x2C, 0x1F } }, + { .data = { 0x2D, 0x1F } }, + { .data = { 0x2E, 0x42 } }, + { .data = { 0x2F, 0x42 } }, + { .data = { 0x30, 0x40 } }, + { .data = { 0x31, 0x40 } }, + { .data = { 0x32, 0x1E } }, + { .data = { 0x33, 0x1E } }, + { .data = { 0x34, 0x1F } }, + { .data = { 0x35, 0x1F } }, + { .data = { 0x36, 0x1E } }, + { .data = { 0x37, 0x1E } }, + { .data = { 0x38, 0x1F } }, + { .data = { 0x39, 0x48 } }, + { .data = { 0x3A, 0x48 } }, + { .data = { 0x3B, 0x4A } }, + { .data = { 0x3C, 0x4A } }, + { .data = { 0x3D, 0x44 } }, + { .data = { 0x3E, 0x44 } }, + { .data = { 0x3F, 0x46 } }, + { .data = { 0x40, 0x46 } }, + { .data = { 0x41, 0x1F } }, + + { .data = { 0x42, 0x1F } }, + { .data = { 0x43, 0x1F } }, + { .data = { 0x44, 0x43 } }, + { .data = { 0x45, 0x43 } }, + { .data = { 0x46, 0x41 } }, + { .data = { 0x47, 0x41 } }, + { .data = { 0x48, 0x1E } }, + { .data = { 0x49, 0x1E } }, + { .data = { 0x4A, 0x1E } }, + { .data = { 0x4B, 0x1F } }, + { .data = { 0x4C, 0x1E } }, + { .data = { 0x4D, 0x1E } }, + { .data = { 0x4E, 0x1F } }, + { .data = { 0x4F, 0x49 } }, + { .data = { 0x50, 0x49 } }, + { .data = { 0x51, 0x4B } }, + { .data = { 0x52, 0x4B } }, + { .data = { 0x53, 0x45 } }, + { .data = { 0x54, 0x45 } }, + { .data = { 0x55, 0x47 } }, + { .data = { 0x56, 0x47 } }, + { .data = { 0x57, 0x1F } }, + + { .data = { 0x58, 0x10 } }, + { .data = { 0x59, 0x00 } }, + { .data = { 0x5A, 0x00 } }, + { .data = { 0x5B, 0x30 } }, + { .data = { 0x5C, 0x02 } }, + { .data = { 0x5D, 0x40 } }, + { .data = { 0x5E, 0x01 } }, + { .data = { 0x5F, 0x02 } }, + { .data = { 0x60, 0x30 } }, + { .data = { 0x61, 0x01 } }, + { .data = { 0x62, 0x02 } }, + { .data = { 0x63, 0x6A } }, + { .data = { 0x64, 0x6A } }, + { .data = { 0x65, 0x05 } }, + { .data = { 0x66, 0x12 } }, + { .data = { 0x67, 0x74 } }, + { .data = { 0x68, 0x04 } }, + { .data = { 0x69, 0x6A } }, + { .data = { 0x6A, 0x6A } }, + { .data = { 0x6B, 0x08 } }, + + { .data = { 0x6C, 0x00 } }, + { .data = { 0x6D, 0x04 } }, + { .data = { 0x6E, 0x04 } }, + { .data = { 0x6F, 0x88 } }, + { .data = { 0x70, 0x00 } }, + { .data = { 0x71, 0x00 } }, + { .data = { 0x72, 0x06 } }, + { .data = { 0x73, 0x7B } }, + { .data = { 0x74, 0x00 } }, + { .data = { 0x75, 0x07 } }, + { .data = { 0x76, 0x00 } }, + { .data = { 0x77, 0x5D } }, + { .data = { 0x78, 0x17 } }, + { .data = { 0x79, 0x1F } }, + { .data = { 0x7A, 0x00 } }, + { .data = { 0x7B, 0x00 } }, + { .data = { 0x7C, 0x00 } }, + { .data = { 0x7D, 0x03 } }, + { .data = { 0x7E, 0x7B } }, + + { .data = { 0xE0, 0x04 } }, + { .data = { 0x2B, 0x2B } }, + { .data = { 0x2E, 0x44 } }, + + { .data = { 0xE0, 0x01 } }, + { .data = { 0x0E, 0x01 } }, + + { .data = { 0xE0, 0x03 } }, + { .data = { 0x98, 0x2F } }, + + { .data = { 0xE0, 0x00 } }, + { .data = { 0xE6, 0x02 } }, + { .data = { 0xE7, 0x02 } }, + + { .data = { 0x11, 0x00 } }, +}; + +static const struct k101_im2ba02_init_cmd timed_cmds[] = { + { .data = { 0x29, 0x00 } }, + { .data = { 0x35, 0x00 } }, +}; + +static int k101_im2ba02_prepare(struct drm_panel *panel) +{ + struct k101_im2ba02 *ctx = panel_to_k101_im2ba02(panel); + struct mipi_dsi_device *dsi = ctx->dsi; + unsigned int i; + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + if (ret) + return ret; + + msleep(30); + + gpiod_set_value(ctx->reset, 1); + msleep(50); + + gpiod_set_value(ctx->reset, 0); + msleep(50); + + gpiod_set_value(ctx->reset, 1); + msleep(200); + + for (i = 0; i < ARRAY_SIZE(k101_im2ba02_init_cmds); i++) { + const struct k101_im2ba02_init_cmd *cmd = &k101_im2ba02_init_cmds[i]; + + ret = mipi_dsi_dcs_write_buffer(dsi, cmd->data, K101_IM2BA02_INIT_CMD_LEN); + if (ret < 0) + goto powerdown; + } + + return 0; + +powerdown: + gpiod_set_value(ctx->reset, 0); + msleep(50); + + return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); +} + +static int k101_im2ba02_enable(struct drm_panel *panel) +{ + struct k101_im2ba02 *ctx = panel_to_k101_im2ba02(panel); + const struct k101_im2ba02_init_cmd *cmd = &timed_cmds[1]; + int ret; + + msleep(150); + + ret = mipi_dsi_dcs_set_display_on(ctx->dsi); + if (ret < 0) + return ret; + + msleep(50); + + return mipi_dsi_dcs_write_buffer(ctx->dsi, cmd->data, K101_IM2BA02_INIT_CMD_LEN); +} + +static int k101_im2ba02_disable(struct drm_panel *panel) +{ + struct k101_im2ba02 *ctx = panel_to_k101_im2ba02(panel); + + return mipi_dsi_dcs_set_display_off(ctx->dsi); +} + +static int k101_im2ba02_unprepare(struct drm_panel *panel) +{ + struct k101_im2ba02 *ctx = panel_to_k101_im2ba02(panel); + int ret; + + ret = mipi_dsi_dcs_set_display_off(ctx->dsi); + if (ret < 0) + dev_err(panel->dev, "failed to set display off: %d\n", ret); + + ret = mipi_dsi_dcs_enter_sleep_mode(ctx->dsi); + if (ret < 0) + dev_err(panel->dev, "failed to enter sleep mode: %d\n", ret); + + msleep(200); + + gpiod_set_value(ctx->reset, 0); + msleep(20); + + return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); +} + +static const struct drm_display_mode k101_im2ba02_default_mode = { + .clock = 70000, + + .hdisplay = 800, + .hsync_start = 800 + 20, + .hsync_end = 800 + 20 + 20, + .htotal = 800 + 20 + 20 + 20, + + .vdisplay = 1280, + .vsync_start = 1280 + 16, + .vsync_end = 1280 + 16 + 4, + .vtotal = 1280 + 16 + 4 + 4, + + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, + .width_mm = 136, + .height_mm = 217, +}; + +static int k101_im2ba02_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct k101_im2ba02 *ctx = panel_to_k101_im2ba02(panel); + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &k101_im2ba02_default_mode); + if (!mode) { + dev_err(&ctx->dsi->dev, "failed to add mode %ux%u@%u\n", + k101_im2ba02_default_mode.hdisplay, + k101_im2ba02_default_mode.vdisplay, + drm_mode_vrefresh(&k101_im2ba02_default_mode)); + 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 k101_im2ba02_funcs = { + .disable = k101_im2ba02_disable, + .unprepare = k101_im2ba02_unprepare, + .prepare = k101_im2ba02_prepare, + .enable = k101_im2ba02_enable, + .get_modes = k101_im2ba02_get_modes, +}; + +static int k101_im2ba02_dsi_probe(struct mipi_dsi_device *dsi) +{ + struct k101_im2ba02 *ctx; + unsigned int i; + int ret; + + ctx = devm_drm_panel_alloc(&dsi->dev, struct k101_im2ba02, panel, + &k101_im2ba02_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + mipi_dsi_set_drvdata(dsi, ctx); + ctx->dsi = dsi; + + for (i = 0; i < ARRAY_SIZE(ctx->supplies); i++) + ctx->supplies[i].supply = regulator_names[i]; + + ret = devm_regulator_bulk_get(&dsi->dev, ARRAY_SIZE(ctx->supplies), + ctx->supplies); + if (ret < 0) + return dev_err_probe(&dsi->dev, ret, "Couldn't get regulators\n"); + + ctx->reset = devm_gpiod_get(&dsi->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->reset)) + return dev_err_probe(&dsi->dev, PTR_ERR(ctx->reset), + "Couldn't get our reset GPIO\n"); + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return ret; + + drm_panel_add(&ctx->panel); + + dsi->mode_flags = MIPI_DSI_MODE_VIDEO; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->lanes = 4; + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + drm_panel_remove(&ctx->panel); + return ret; + } + + return 0; +} + +static void k101_im2ba02_dsi_remove(struct mipi_dsi_device *dsi) +{ + struct k101_im2ba02 *ctx = mipi_dsi_get_drvdata(dsi); + + mipi_dsi_detach(dsi); + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id k101_im2ba02_of_match[] = { + { .compatible = "feixin,k101-im2ba02", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, k101_im2ba02_of_match); + +static struct mipi_dsi_driver k101_im2ba02_driver = { + .probe = k101_im2ba02_dsi_probe, + .remove = k101_im2ba02_dsi_remove, + .driver = { + .name = "feixin-k101-im2ba02", + .of_match_table = k101_im2ba02_of_match, + }, +}; +module_mipi_dsi_driver(k101_im2ba02_driver); + +MODULE_AUTHOR("Icenowy Zheng <icenowy@aosc.io>"); +MODULE_DESCRIPTION("Feixin K101 IM2BA02 MIPI-DSI LCD panel"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-feiyang-fy07024di26a30d.c b/drivers/gpu/drm/panel/panel-feiyang-fy07024di26a30d.c new file mode 100644 index 000000000000..4f8d6d8c07e4 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-feiyang-fy07024di26a30d.c @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018 Amarula Solutions + * Author: Jagan Teki <jagan@amarulasolutions.com> + */ + +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +#include <linux/gpio/consumer.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/regulator/consumer.h> + +#define FEIYANG_INIT_CMD_LEN 2 + +struct feiyang { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + + struct regulator *dvdd; + struct regulator *avdd; + struct gpio_desc *reset; +}; + +static inline struct feiyang *panel_to_feiyang(struct drm_panel *panel) +{ + return container_of(panel, struct feiyang, panel); +} + +struct feiyang_init_cmd { + u8 data[FEIYANG_INIT_CMD_LEN]; +}; + +static const struct feiyang_init_cmd feiyang_init_cmds[] = { + { .data = { 0x80, 0x58 } }, + { .data = { 0x81, 0x47 } }, + { .data = { 0x82, 0xD4 } }, + { .data = { 0x83, 0x88 } }, + { .data = { 0x84, 0xA9 } }, + { .data = { 0x85, 0xC3 } }, + { .data = { 0x86, 0x82 } }, +}; + +static int feiyang_prepare(struct drm_panel *panel) +{ + struct feiyang *ctx = panel_to_feiyang(panel); + struct mipi_dsi_device *dsi = ctx->dsi; + unsigned int i; + int ret; + + ret = regulator_enable(ctx->dvdd); + if (ret) + return ret; + + /* T1 (dvdd start + dvdd rise) 0 < T1 <= 10ms */ + msleep(10); + + ret = regulator_enable(ctx->avdd); + if (ret) + return ret; + + /* T3 (dvdd rise + avdd start + avdd rise) T3 >= 20ms */ + msleep(20); + + gpiod_set_value(ctx->reset, 0); + + /* + * T5 + T6 (avdd rise + video & logic signal rise) + * T5 >= 10ms, 0 < T6 <= 10ms + */ + msleep(20); + + gpiod_set_value(ctx->reset, 1); + + /* T12 (video & logic signal rise + backlight rise) T12 >= 200ms */ + msleep(200); + + for (i = 0; i < ARRAY_SIZE(feiyang_init_cmds); i++) { + const struct feiyang_init_cmd *cmd = + &feiyang_init_cmds[i]; + + ret = mipi_dsi_dcs_write_buffer(dsi, cmd->data, + FEIYANG_INIT_CMD_LEN); + if (ret < 0) + return ret; + } + + return 0; +} + +static int feiyang_enable(struct drm_panel *panel) +{ + struct feiyang *ctx = panel_to_feiyang(panel); + + /* T12 (video & logic signal rise + backlight rise) T12 >= 200ms */ + msleep(200); + + mipi_dsi_dcs_set_display_on(ctx->dsi); + + return 0; +} + +static int feiyang_disable(struct drm_panel *panel) +{ + struct feiyang *ctx = panel_to_feiyang(panel); + + return mipi_dsi_dcs_set_display_off(ctx->dsi); +} + +static int feiyang_unprepare(struct drm_panel *panel) +{ + struct feiyang *ctx = panel_to_feiyang(panel); + int ret; + + ret = mipi_dsi_dcs_set_display_off(ctx->dsi); + if (ret < 0) + dev_err(panel->dev, "failed to set display off: %d\n", ret); + + ret = mipi_dsi_dcs_enter_sleep_mode(ctx->dsi); + if (ret < 0) + dev_err(panel->dev, "failed to enter sleep mode: %d\n", ret); + + /* T13 (backlight fall + video & logic signal fall) T13 >= 200ms */ + msleep(200); + + gpiod_set_value(ctx->reset, 0); + + regulator_disable(ctx->avdd); + + /* T11 (dvdd rise to fall) 0 < T11 <= 10ms */ + msleep(10); + + regulator_disable(ctx->dvdd); + + return 0; +} + +static const struct drm_display_mode feiyang_default_mode = { + .clock = 55000, + + .hdisplay = 1024, + .hsync_start = 1024 + 310, + .hsync_end = 1024 + 310 + 20, + .htotal = 1024 + 310 + 20 + 90, + + .vdisplay = 600, + .vsync_start = 600 + 12, + .vsync_end = 600 + 12 + 2, + .vtotal = 600 + 12 + 2 + 21, + + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, +}; + +static int feiyang_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct feiyang *ctx = panel_to_feiyang(panel); + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &feiyang_default_mode); + if (!mode) { + dev_err(&ctx->dsi->dev, "failed to add mode %ux%u@%u\n", + feiyang_default_mode.hdisplay, + feiyang_default_mode.vdisplay, + drm_mode_vrefresh(&feiyang_default_mode)); + return -ENOMEM; + } + + drm_mode_set_name(mode); + + drm_mode_probed_add(connector, mode); + + return 1; +} + +static const struct drm_panel_funcs feiyang_funcs = { + .disable = feiyang_disable, + .unprepare = feiyang_unprepare, + .prepare = feiyang_prepare, + .enable = feiyang_enable, + .get_modes = feiyang_get_modes, +}; + +static int feiyang_dsi_probe(struct mipi_dsi_device *dsi) +{ + struct feiyang *ctx; + int ret; + + ctx = devm_drm_panel_alloc(&dsi->dev, struct feiyang, panel, + &feiyang_funcs, DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + mipi_dsi_set_drvdata(dsi, ctx); + ctx->dsi = dsi; + + ctx->dvdd = devm_regulator_get(&dsi->dev, "dvdd"); + if (IS_ERR(ctx->dvdd)) + return dev_err_probe(&dsi->dev, PTR_ERR(ctx->dvdd), + "Couldn't get dvdd regulator\n"); + + ctx->avdd = devm_regulator_get(&dsi->dev, "avdd"); + if (IS_ERR(ctx->avdd)) + return dev_err_probe(&dsi->dev, PTR_ERR(ctx->avdd), + "Couldn't get avdd regulator\n"); + + ctx->reset = devm_gpiod_get_optional(&dsi->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->reset)) + return dev_err_probe(&dsi->dev, PTR_ERR(ctx->reset), + "Couldn't get our reset GPIO\n"); + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return ret; + + drm_panel_add(&ctx->panel); + + dsi->mode_flags = MIPI_DSI_MODE_VIDEO_BURST; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->lanes = 4; + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + drm_panel_remove(&ctx->panel); + return ret; + } + + return 0; +} + +static void feiyang_dsi_remove(struct mipi_dsi_device *dsi) +{ + struct feiyang *ctx = mipi_dsi_get_drvdata(dsi); + + mipi_dsi_detach(dsi); + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id feiyang_of_match[] = { + { .compatible = "feiyang,fy07024di26a30d", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, feiyang_of_match); + +static struct mipi_dsi_driver feiyang_driver = { + .probe = feiyang_dsi_probe, + .remove = feiyang_dsi_remove, + .driver = { + .name = "feiyang-fy07024di26a30d", + .of_match_table = feiyang_of_match, + }, +}; +module_mipi_dsi_driver(feiyang_driver); + +MODULE_AUTHOR("Jagan Teki <jagan@amarulasolutions.com>"); +MODULE_DESCRIPTION("Feiyang FY07024DI26A30-D MIPI-DSI LCD panel"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-himax-hx8279.c b/drivers/gpu/drm/panel/panel-himax-hx8279.c new file mode 100644 index 000000000000..9e443c719843 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-himax-hx8279.c @@ -0,0 +1,1296 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Himax HX8279 DriverIC panels driver + * + * Copyright (c) 2025 Collabora Ltd. + * AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com> + */ + +#include <linux/bitfield.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/regulator/consumer.h> + +#include <drm/drm_connector.h> +#include <drm/drm_crtc.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +/* Page selection */ +#define HX8279_REG_PAGE 0xb0 + #define HX8279_PAGE_SEL GENMASK(3, 0) + +/* Page 0 - Driver/Module Configuration */ +#define HX8279_P0_VGHS 0xbf +#define HX8279_P0_VGLS 0xc0 +#define HX8279_P0_VGPHS 0xc2 +#define HX8279_P0_VGNHS 0xc4 + #define HX8279_P0_VG_SEL GENMASK(4, 0) + #define HX8279_VGH_MIN_MV 8700 + #define HX8279_VGH_STEP_MV 300 + #define HX8279_VGL_MIN_MV 6700 + #define HX8279_VGL_STEP_MV 300 + #define HX8279_VGPNH_MIN_MV 4000 + #define HX8279_VGPNX_STEP_MV 50 + #define HX8279_VGH_VOLT_SEL(x) ((x - HX8279_VGH_MIN_MV) / HX8279_VGH_STEP_MV) + #define HX8279_VGL_VOLT_SEL(x) ((x - HX8279_VGL_MIN_MV) / HX8279_VGL_STEP_MV) + #define HX8279_VGPN_VOLT_SEL(x) ((x - HX8279_VGPNH_MIN_MV) / HX8279_VGPNX_STEP_MV) + +/* Page 1 - Gate driver On Array (GOA) Mux */ +#define HX8279_P1_REG_GOA_L 0xc0 +#define HX8279_P1_REG_GOUTL(x) (HX8279_P1_REG_GOA_L + (x)) +#define HX8279_P1_REG_GOA_R 0xd4 +#define HX8279_P1_REG_GOUTR(x) (HX8279_P1_REG_GOA_R + (x)) + #define HX8279_GOUT_STB GENMASK(7, 6) + #define HX8279_GOUT_SEL GENMASK(5, 0) + +/* Page 2 - Analog Gamma Configuration */ +#define HX8279_P2_REG_ANALOG_GAMMA 0xc0 + #define HX8279_P2_REG_GAMMA_T_PVP(x) (HX8279_P2_REG_ANALOG_GAMMA + (x)) /* 0..16 */ + #define HX8279_P2_REG_GAMMA_T_PVN(x) (HX8279_P2_REG_GAMMA_T_PVP(17) + (x)) /* 0..16 */ + +/* Page 3 - Gate driver On Array (GOA) Configuration */ +#define HX8279_P3_REG_UNKNOWN_BA 0xba +#define HX8279_P3_REG_GOA_CKV_FALL_PREC 0xbc +#define HX8279_P3_REG_GOA_TIMING_ODD 0xc2 + #define HX8279_P3_REG_GOA_TO(x) (HX8279_P3_REG_GOA_TIMING_ODD + x) /* GOA_T0..5 */ +#define HX8279_P3_REG_GOA_STVL 0xc8 + #define HX8279_P3_GOA_STV_LEAD GENMASK(4, 0) +#define HX8279_P3_REG_GOA_CKVL 0xc9 + #define HX8279_P3_GOA_CKV_LEAD GENMASK(4, 0) +#define HX8279_P3_REG_GOA_CKVD 0xca + #define HX8279_P3_GOA_CKV_NONOVERLAP BIT(7) + #define HX8279_P3_GOA_CKV_RESERVED BIT(6) + #define HX8279_P3_GOA_CKV_DUMMY GENMASK(5, 0) +#define HX8279_P3_REG_GOA_CKV_RISE_PREC 0xcb +#define HX8279_P3_REG_GOA_CLR1_W_ADJ 0xd2 +#define HX8279_P3_REG_GOA_CLR234_W_ADJ 0xd3 +#define HX8279_P3_REG_GOA_CLR1_CFG 0xd4 +#define HX8279_P3_REG_GOA_CLR_CFG(x) (HX8279_P3_REG_GOA_CLR1_CFG + (x)) /* CLR1..4 */ + #define HX8279_P3_GOA_CLR_CFG_POLARITY BIT(7) + #define HX8279_P3_GOA_CLR_CFG_STARTPOS GENMASK(6, 0) +#define HX8279_P3_REG_GOA_TIMING_EVEN 0xdd + #define HX8279_P3_REG_GOA_TE(x) (HX8279_P3_REG_GOA_TIMING_EVEN + x) +#define HX8279_P3_REG_UNKNOWN_E4 0xe4 +#define HX8279_P3_REG_UNKNOWN_E5 0xe5 + +/* Page 5 - MIPI */ +#define HX8279_P5_REG_TIMING 0xb3 + #define HX8279_P5_TIMING_THS_SETTLE GENMASK(7, 5) + #define HX8279_P5_TIMING_LHS_SETTLE BIT(4) + #define HX8279_P5_TIMING_TLPX GENMASK(3, 0) +#define HX8279_P5_REG_UNKNOWN_B8 0xb8 +#define HX8279_P5_REG_UNKNOWN_BC 0xbc +#define HX8279_P5_REG_UNKNOWN_D6 0xd6 + +/* Page 6 - Engineer */ +#define HX8279_P6_REG_ENGINEER_PWD 0xb8 +#define HX8279_P6_REG_INHOUSE_FUNC 0xc0 + #define HX8279_P6_ENG_UNLOCK_WORD 0xa5 +#define HX8279_P6_REG_GAMMA_CHOPPER 0xbc + #define HX8279_P6_GAMMA_POCGM_CTL GENMASK(6, 4) + #define HX8279_P6_GAMMA_POGCMD_CTL GENMASK(2, 0) +#define HX8279_P6_REG_VOLT_ADJ 0xc7 + /* For VCCIFS and VCCS - 0: 1450, 1: 1500, 2: 1550, 3: 1600 uV */ + #define HX8279_P6_VOLT_ADJ_VCCIFS GENMASK(3, 2) + #define HX8279_P6_VOLT_ADJ_VCCS GENMASK(1, 0) +#define HX8279_P6_REG_DLY_TIME_ADJ 0xd5 + +/* Page 7...12 - Digital Gamma Adjustment */ +#define HX8279_PG_DIGITAL_GAMMA 0xb1 +#define HX8279_DGAMMA_DGMA1_HI GENMASK(7, 6) +#define HX8279_DGAMMA_DGMA2_HI GENMASK(5, 4) +#define HX8279_DGAMMA_DGMA3_HI GENMASK(3, 2) +#define HX8279_DGAMMA_DGMA4_HI GENMASK(1, 0) +#define HX8279_PG_DGAMMA_NUM_LO_BYTES 24 +#define HX8279_PG_DGAMMA_NUM_HI_BYTES 6 + +struct hx8279 { + struct drm_panel panel; + struct mipi_dsi_device *dsi[2]; + struct regulator_bulk_data vregs[2]; + struct gpio_desc *enable_gpio; + struct gpio_desc *reset_gpio; + const struct hx8279_panel_desc *desc; + u8 last_page; + bool skip_voltage_config; + bool skip_goa_config; + bool skip_goa_timing; + bool skip_goa_even_timing; + bool skip_mipi_timing; +}; + +struct hx8279_panel_mode { + const struct drm_display_mode mode; + u8 bpc; + bool is_video_mode; +}; + +/** + * struct hx8279_goa_mux - Gate driver On Array Muxer + * @gout_l: Mux GOA signal to GOUT Left pin + * @gout_r: Mux GOA signal to GOUT Right pin + */ +struct hx8279_goa_mux { + u8 gout_l[20]; + u8 gout_r[20]; +}; + +/** + * struct hx8279_analog_gamma - Analog Gamma Adjustment + * @pos: Positive gamma op's input voltage, adjusted by VGP(H/L) + * @neg: Negative gamma op's input voltage, adjusted by VGN(H/L) + * + * Analog Gamma correction is performed with 17+17 reference voltages, + * changed with resistor streams, and defined with 17 register values + * for positive and 17 for negative. + * + * Each register holds resistance values, in 8.5ohms per unit, for the + * following gamma levels: + * 0, 8, 16, 28, 40, 56, 80, 128, 176, 200, 216, 228, 240, 248, 252, 255. + */ +struct hx8279_analog_gamma { + u8 pos[17]; + u8 neg[17]; +}; + +/** + * struct hx8279_digital_gamma - Digital Gamma Adjustment + * @r: Adjustment for red component + * @g: Adjustment for green component + * @b: Adjustment for blue component + * + * The layout of this structure follows the register layout to simplify + * both the handling and the declaration of those values in the driver. + * Gamma correction is internally done with a 24 segment piecewise + * linear interpolation; those segments are defined with 24 ten bits + * values of which: + * - The LOW eight bits for the first 24 registers start at the first + * register (at 0xb1) of the Digital Gamma Adjustment page; + * - The HIGH two bits for each of the 24 registers are contained + * in the last six registers; + * - The last six registers contain four groups of two-bits HI values + * for each of the first 24 registers, but in an inverted fashion, + * this means that the first two bits relate to the last register + * of a set of four. + * + * The 24 segments refer to the following gamma levels: + * 0, 1, 3, 7, 11, 15, 23, 31, 47, 63, 95, 127, 128, 160, + * 192, 208, 224, 232, 240, 244, 248, 252, 254, 255 + */ +struct hx8279_digital_gamma { + u8 r[HX8279_PG_DGAMMA_NUM_LO_BYTES + HX8279_PG_DGAMMA_NUM_HI_BYTES]; + u8 g[HX8279_PG_DGAMMA_NUM_LO_BYTES + HX8279_PG_DGAMMA_NUM_HI_BYTES]; + u8 b[HX8279_PG_DGAMMA_NUM_LO_BYTES + HX8279_PG_DGAMMA_NUM_HI_BYTES]; +}; + +struct hx8279_panel_desc { + const struct mipi_dsi_device_info dsi_info; + const struct hx8279_panel_mode *mode_data; + u8 num_lanes; + u8 num_modes; + + /* Page 0 */ + unsigned int vgh_mv; + unsigned int vgl_mv; + unsigned int vgph_mv; + unsigned int vgnh_mv; + + /* Page 1 */ + const struct hx8279_goa_mux *gmux; + + /* Page 2 */ + const struct hx8279_analog_gamma *agamma; + + /* Page 3 */ + u8 goa_unk_ba; + u8 goa_odd_timing[6]; + u8 goa_even_timing[6]; + u8 goa_stv_lead_time_ck; + u8 goa_ckv_lead_time_ck; + u8 goa_ckv_dummy_vblank_num; + u8 goa_ckv_rise_precharge; + u8 goa_ckv_fall_precharge; + bool goa_ckv_non_overlap_ctl; + u8 goa_clr1_width_adj; + u8 goa_clr234_width_adj; + s8 goa_clr_polarity[4]; + int goa_clr_start_pos[4]; + u8 goa_unk_e4; + u8 goa_unk_e5; + + /* Page 5 */ + u8 bta_tlpx; + bool lhs_settle_time_by_osc25; + u8 ths_settle_time; + u8 timing_unk_b8; + u8 timing_unk_bc; + u8 timing_unk_d6; + + /* Page 6 */ + u8 gamma_ctl; + u8 volt_adj; + u8 src_delay_time_adj_ck; + + /* Page 7..12 */ + const struct hx8279_digital_gamma *dgamma; +}; + +static inline struct hx8279 *to_hx8279(struct drm_panel *panel) +{ + return container_of(panel, struct hx8279, panel); +} + +static void hx8279_set_page(struct hx8279 *hx, + struct mipi_dsi_multi_context *dsi_ctx, u8 page) +{ + const u8 cmd_set_page[] = { HX8279_REG_PAGE, page }; + + if (hx->last_page == page) + return; + + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_page, ARRAY_SIZE(cmd_set_page)); + if (!dsi_ctx->accum_err) + hx->last_page = page; +} + +static void hx8279_set_module_config(struct hx8279 *hx, + struct mipi_dsi_multi_context *dsi_ctx) +{ + const struct hx8279_panel_desc *desc = hx->desc; + u8 cmd_set_voltage[2]; + + if (hx->skip_voltage_config) + return; + + /* Page 0 - Driver/Module Configuration */ + hx8279_set_page(hx, dsi_ctx, 0); + + if (desc->vgh_mv) { + cmd_set_voltage[0] = HX8279_P0_VGHS; + cmd_set_voltage[1] = HX8279_VGH_VOLT_SEL(desc->vgh_mv); + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_voltage, + ARRAY_SIZE(cmd_set_voltage)); + } + + if (desc->vgl_mv) { + cmd_set_voltage[0] = HX8279_P0_VGLS; + cmd_set_voltage[1] = HX8279_VGL_VOLT_SEL(desc->vgl_mv); + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_voltage, + ARRAY_SIZE(cmd_set_voltage)); + } + + if (desc->vgph_mv) { + cmd_set_voltage[0] = HX8279_P0_VGPHS; + cmd_set_voltage[1] = HX8279_VGPN_VOLT_SEL(desc->vgph_mv); + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_voltage, + ARRAY_SIZE(cmd_set_voltage)); + } + + if (desc->vgnh_mv) { + cmd_set_voltage[0] = HX8279_P0_VGNHS; + cmd_set_voltage[1] = HX8279_VGPN_VOLT_SEL(desc->vgnh_mv); + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_voltage, + ARRAY_SIZE(cmd_set_voltage)); + } +} + +static void hx8279_set_gmux(struct hx8279 *hx, + struct mipi_dsi_multi_context *dsi_ctx) +{ + const struct hx8279_goa_mux *gmux = hx->desc->gmux; + u8 cmd_set_gmux[2]; + int i; + + if (!gmux) + return; + + hx8279_set_page(hx, dsi_ctx, 1); + + for (i = 0; i < ARRAY_SIZE(gmux->gout_l); i++) { + cmd_set_gmux[0] = HX8279_P1_REG_GOUTL(i); + cmd_set_gmux[1] = gmux->gout_l[i]; + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_gmux, + ARRAY_SIZE(cmd_set_gmux)); + } + + for (i = 0; i < ARRAY_SIZE(gmux->gout_r); i++) { + cmd_set_gmux[0] = HX8279_P1_REG_GOUTR(i); + cmd_set_gmux[1] = gmux->gout_r[i]; + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_gmux, + ARRAY_SIZE(cmd_set_gmux)); + } +} + +static void hx8279_set_analog_gamma(struct hx8279 *hx, + struct mipi_dsi_multi_context *dsi_ctx) +{ + const struct hx8279_analog_gamma *agamma = hx->desc->agamma; + u8 cmd_set_ana_gamma[2]; + int i; + + if (!agamma) + return; + + hx8279_set_page(hx, dsi_ctx, 2); + + for (i = 0; i < ARRAY_SIZE(agamma->pos); i++) { + cmd_set_ana_gamma[0] = HX8279_P2_REG_GAMMA_T_PVP(i); + cmd_set_ana_gamma[1] = agamma->pos[i]; + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_ana_gamma, + ARRAY_SIZE(cmd_set_ana_gamma)); + } + + for (i = 0; i < ARRAY_SIZE(agamma->neg); i++) { + cmd_set_ana_gamma[0] = HX8279_P2_REG_GAMMA_T_PVN(i); + cmd_set_ana_gamma[1] = agamma->neg[i]; + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_ana_gamma, + ARRAY_SIZE(cmd_set_ana_gamma)); + } +} + +static void hx8279_set_goa_timing(struct hx8279 *hx, + struct mipi_dsi_multi_context *dsi_ctx) +{ + const struct hx8279_panel_desc *desc = hx->desc; + u8 cmd_set_goa_t[2]; + int i; + + if (hx->skip_goa_timing) + return; + + hx8279_set_page(hx, dsi_ctx, 3); + + for (i = 0; i < ARRAY_SIZE(desc->goa_odd_timing); i++) { + cmd_set_goa_t[0] = HX8279_P3_REG_GOA_TO(i); + cmd_set_goa_t[1] = desc->goa_odd_timing[i]; + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_goa_t, + ARRAY_SIZE(cmd_set_goa_t)); + } + + for (i = 0; i < ARRAY_SIZE(desc->goa_even_timing); i++) { + cmd_set_goa_t[0] = HX8279_P3_REG_GOA_TE(i); + cmd_set_goa_t[1] = desc->goa_odd_timing[i]; + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_goa_t, + ARRAY_SIZE(cmd_set_goa_t)); + } +} + +static void hx8279_set_goa_cfg(struct hx8279 *hx, + struct mipi_dsi_multi_context *dsi_ctx) +{ + const struct hx8279_panel_desc *desc = hx->desc; + u8 cmd_set_goa[2]; + int i; + + if (hx->skip_goa_config) + return; + + hx8279_set_page(hx, dsi_ctx, 3); + + if (desc->goa_unk_ba) { + cmd_set_goa[0] = HX8279_P3_REG_UNKNOWN_BA; + cmd_set_goa[1] = desc->goa_unk_ba; + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_goa, + ARRAY_SIZE(cmd_set_goa)); + } + + if (desc->goa_stv_lead_time_ck) { + cmd_set_goa[0] = HX8279_P3_REG_GOA_STVL; + cmd_set_goa[1] = FIELD_PREP(HX8279_P3_GOA_STV_LEAD, + desc->goa_stv_lead_time_ck); + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_goa, + ARRAY_SIZE(cmd_set_goa)); + } + + if (desc->goa_ckv_lead_time_ck) { + cmd_set_goa[0] = HX8279_P3_REG_GOA_CKVL; + cmd_set_goa[1] = FIELD_PREP(HX8279_P3_GOA_CKV_DUMMY, + desc->goa_ckv_lead_time_ck); + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_goa, + ARRAY_SIZE(cmd_set_goa)); + } + + if (desc->goa_ckv_dummy_vblank_num) { + cmd_set_goa[0] = HX8279_P3_REG_GOA_CKVD; + cmd_set_goa[1] = FIELD_PREP(HX8279_P3_GOA_CKV_LEAD, + desc->goa_ckv_dummy_vblank_num); + cmd_set_goa[1] |= FIELD_PREP(HX8279_P3_GOA_CKV_NONOVERLAP, + desc->goa_ckv_non_overlap_ctl); + /* RESERVED must be always set */ + cmd_set_goa[1] |= HX8279_P3_GOA_CKV_RESERVED; + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_goa, + ARRAY_SIZE(cmd_set_goa)); + } + + /* + * One of the two being more than zero means that we want to write + * both of them. Anyway, the register default is zero in this case. + */ + if (desc->goa_ckv_rise_precharge || desc->goa_ckv_fall_precharge) { + cmd_set_goa[0] = HX8279_P3_REG_GOA_CKV_RISE_PREC; + cmd_set_goa[1] = desc->goa_ckv_rise_precharge; + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_goa, + ARRAY_SIZE(cmd_set_goa)); + + cmd_set_goa[0] = HX8279_P3_REG_GOA_CKV_FALL_PREC; + cmd_set_goa[1] = desc->goa_ckv_fall_precharge; + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_goa, + ARRAY_SIZE(cmd_set_goa)); + } + + if (desc->goa_clr1_width_adj) { + cmd_set_goa[0] = HX8279_P3_REG_GOA_CLR1_W_ADJ; + cmd_set_goa[1] = desc->goa_clr1_width_adj; + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_goa, + ARRAY_SIZE(cmd_set_goa)); + } + + if (desc->goa_clr234_width_adj) { + cmd_set_goa[0] = HX8279_P3_REG_GOA_CLR234_W_ADJ; + cmd_set_goa[1] = desc->goa_clr234_width_adj; + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_goa, + ARRAY_SIZE(cmd_set_goa)); + } + + /* Polarity and Start Position arrays are of the same size */ + for (i = 0; i < ARRAY_SIZE(desc->goa_clr_polarity); i++) { + if (desc->goa_clr_polarity[i] < 0 || desc->goa_clr_start_pos[i] < 0) + continue; + + cmd_set_goa[0] = HX8279_P3_REG_GOA_CLR_CFG(i); + cmd_set_goa[1] = FIELD_PREP(HX8279_P3_GOA_CLR_CFG_STARTPOS, + desc->goa_clr_start_pos[i]); + cmd_set_goa[1] |= FIELD_PREP(HX8279_P3_GOA_CLR_CFG_POLARITY, + desc->goa_clr_polarity[i]); + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_goa, + ARRAY_SIZE(cmd_set_goa)); + } + + if (desc->goa_unk_e4) { + cmd_set_goa[0] = HX8279_P3_REG_UNKNOWN_E4; + cmd_set_goa[1] = desc->goa_unk_e4; + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_goa, + ARRAY_SIZE(cmd_set_goa)); + } + + cmd_set_goa[0] = HX8279_P3_REG_UNKNOWN_E5; + cmd_set_goa[1] = desc->goa_unk_e5; + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_goa, + ARRAY_SIZE(cmd_set_goa)); +} + +static void hx8279_set_mipi_cfg(struct hx8279 *hx, + struct mipi_dsi_multi_context *dsi_ctx) +{ + const struct hx8279_panel_desc *desc = hx->desc; + u8 cmd_set_mipi[2]; + + if (hx->skip_mipi_timing) + return; + + hx8279_set_page(hx, dsi_ctx, 5); + + if (desc->bta_tlpx || desc->ths_settle_time || desc->lhs_settle_time_by_osc25) { + cmd_set_mipi[0] = HX8279_P5_REG_TIMING; + cmd_set_mipi[1] = FIELD_PREP(HX8279_P5_TIMING_TLPX, desc->bta_tlpx); + cmd_set_mipi[1] |= FIELD_PREP(HX8279_P5_TIMING_THS_SETTLE, + desc->ths_settle_time); + cmd_set_mipi[1] |= FIELD_PREP(HX8279_P5_TIMING_LHS_SETTLE, + desc->lhs_settle_time_by_osc25); + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_mipi, + ARRAY_SIZE(cmd_set_mipi)); + } + + if (desc->timing_unk_b8) { + cmd_set_mipi[0] = HX8279_P5_REG_UNKNOWN_B8; + cmd_set_mipi[1] = desc->timing_unk_b8; + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_mipi, + ARRAY_SIZE(cmd_set_mipi)); + } + + if (desc->timing_unk_bc) { + cmd_set_mipi[0] = HX8279_P5_REG_UNKNOWN_BC; + cmd_set_mipi[1] = desc->timing_unk_bc; + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_mipi, + ARRAY_SIZE(cmd_set_mipi)); + } + + if (desc->timing_unk_d6) { + cmd_set_mipi[0] = HX8279_P5_REG_UNKNOWN_D6; + cmd_set_mipi[1] = desc->timing_unk_d6; + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_mipi, + ARRAY_SIZE(cmd_set_mipi)); + } +} + +static void hx8279_set_adv_cfg(struct hx8279 *hx, + struct mipi_dsi_multi_context *dsi_ctx) +{ + const struct hx8279_panel_desc *desc = hx->desc; + const u8 cmd_set_dly[] = { HX8279_P6_REG_DLY_TIME_ADJ, desc->src_delay_time_adj_ck }; + const u8 cmd_set_gamma[] = { HX8279_P6_REG_GAMMA_CHOPPER, desc->gamma_ctl }; + const u8 cmd_set_volt_adj[] = { HX8279_P6_REG_VOLT_ADJ, desc->volt_adj }; + u8 cmd_set_eng[] = { HX8279_P6_REG_ENGINEER_PWD, HX8279_P6_ENG_UNLOCK_WORD }; + + if (!desc->gamma_ctl && !desc->src_delay_time_adj_ck && !desc->volt_adj) + return; + + hx8279_set_page(hx, dsi_ctx, 6); + + /* Unlock ENG settings: write same word to both ENGINEER_PWD and INHOUSE_FUNC */ + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_eng, ARRAY_SIZE(cmd_set_eng)); + + cmd_set_eng[0] = HX8279_P6_REG_INHOUSE_FUNC; + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_eng, ARRAY_SIZE(cmd_set_eng)); + + /* Set Gamma Chopper and Gamma buffer Chopper control */ + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_gamma, ARRAY_SIZE(cmd_set_gamma)); + + /* Set Source delay time adjustment (CKV falling to Source off) */ + if (desc->src_delay_time_adj_ck) + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_dly, + ARRAY_SIZE(cmd_set_dly)); + + /* Set voltage adjustment */ + if (desc->volt_adj) + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_volt_adj, + ARRAY_SIZE(cmd_set_volt_adj)); + + /* Lock ENG settings again */ + cmd_set_eng[0] = HX8279_P6_REG_ENGINEER_PWD; + cmd_set_eng[1] = 0; + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_eng, ARRAY_SIZE(cmd_set_eng)); + + cmd_set_eng[0] = HX8279_P6_REG_INHOUSE_FUNC; + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_eng, ARRAY_SIZE(cmd_set_eng)); +} + +static void hx8279_set_digital_gamma(struct hx8279 *hx, + struct mipi_dsi_multi_context *dsi_ctx) +{ + const struct hx8279_digital_gamma *dgamma = hx->desc->dgamma; + u8 cmd_set_dig_gamma[2]; + int i; + + if (!dgamma) + return; + + /* + * Pages 7..9 are for RGB Positive, 10..12 are for RGB Negative: + * The first iteration sets all positive component registers, + * the second one sets all negatives. + */ + for (i = 0; i < 2; i++) { + u8 pg_neg = i * 3; + + hx8279_set_page(hx, dsi_ctx, 7 + pg_neg); + + for (i = 0; i < ARRAY_SIZE(dgamma->r); i++) { + cmd_set_dig_gamma[0] = HX8279_PG_DIGITAL_GAMMA + i; + cmd_set_dig_gamma[1] = dgamma->r[i]; + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_dig_gamma, + ARRAY_SIZE(cmd_set_dig_gamma)); + } + + hx8279_set_page(hx, dsi_ctx, 8 + pg_neg); + + for (i = 0; i < ARRAY_SIZE(dgamma->g); i++) { + cmd_set_dig_gamma[0] = HX8279_PG_DIGITAL_GAMMA + i; + cmd_set_dig_gamma[1] = dgamma->g[i]; + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_dig_gamma, + ARRAY_SIZE(cmd_set_dig_gamma)); + } + + hx8279_set_page(hx, dsi_ctx, 9 + pg_neg); + + for (i = 0; i < ARRAY_SIZE(dgamma->b); i++) { + cmd_set_dig_gamma[0] = HX8279_PG_DIGITAL_GAMMA + i; + cmd_set_dig_gamma[1] = dgamma->b[i]; + mipi_dsi_generic_write_multi(dsi_ctx, cmd_set_dig_gamma, + ARRAY_SIZE(cmd_set_dig_gamma)); + } + } +} + +static int hx8279_on(struct hx8279 *hx) +{ + struct mipi_dsi_device *dsi = hx->dsi[0]; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + /* Page 5 */ + hx8279_set_mipi_cfg(hx, &dsi_ctx); + + /* Page 1 */ + hx8279_set_gmux(hx, &dsi_ctx); + + /* Page 2 */ + hx8279_set_analog_gamma(hx, &dsi_ctx); + + /* Page 3 */ + hx8279_set_goa_cfg(hx, &dsi_ctx); + hx8279_set_goa_timing(hx, &dsi_ctx); + + /* Page 0 - Driver/Module Configuration */ + hx8279_set_module_config(hx, &dsi_ctx); + + /* Page 6 */ + hx8279_set_adv_cfg(hx, &dsi_ctx); + + /* Pages 7..12 */ + hx8279_set_digital_gamma(hx, &dsi_ctx); + + return dsi_ctx.accum_err; +} + +static void hx8279_power_off(struct hx8279 *hx) +{ + gpiod_set_value_cansleep(hx->reset_gpio, 0); + usleep_range(100, 500); + gpiod_set_value_cansleep(hx->enable_gpio, 0); + regulator_bulk_disable(ARRAY_SIZE(hx->vregs), hx->vregs); +} + +static int hx8279_disable(struct drm_panel *panel) +{ + struct hx8279 *hx = to_hx8279(panel); + struct mipi_dsi_device *dsi = hx->dsi[0]; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + + return 0; +} + +static int hx8279_enable(struct drm_panel *panel) +{ + struct hx8279 *hx = to_hx8279(panel); + struct mipi_dsi_device *dsi = hx->dsi[0]; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + + return 0; +} + +static int hx8279_prepare(struct drm_panel *panel) +{ + struct hx8279 *hx = to_hx8279(panel); + struct mipi_dsi_device *dsi = hx->dsi[0]; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(hx->vregs), hx->vregs); + if (ret) + return ret; + + gpiod_set_value_cansleep(hx->enable_gpio, 1); + usleep_range(5000, 6000); + gpiod_set_value_cansleep(hx->reset_gpio, 1); + usleep_range(6000, 7000); + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + if (hx->dsi[1]) + hx->dsi[1]->mode_flags |= MIPI_DSI_MODE_LPM; + + ret = hx8279_on(hx); + if (ret) { + hx8279_power_off(hx); + return ret; + } + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 130); + + return dsi_ctx.accum_err; +} + +static int hx8279_unprepare(struct drm_panel *panel) +{ + struct hx8279 *hx = to_hx8279(panel); + struct mipi_dsi_device *dsi = hx->dsi[0]; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 130); + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + if (hx->dsi[1]) + hx->dsi[1]->mode_flags &= ~MIPI_DSI_MODE_LPM; + + hx8279_power_off(hx); + + return dsi_ctx.accum_err; +} + +static int hx8279_get_modes(struct drm_panel *panel, struct drm_connector *connector) +{ + struct hx8279 *hx = to_hx8279(panel); + int i; + + for (i = 0; i < hx->desc->num_modes; i++) { + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &hx->desc->mode_data[i].mode); + if (!mode) + return -ENOMEM; + + drm_mode_set_name(mode); + + mode->type |= DRM_MODE_TYPE_DRIVER; + if (hx->desc->num_modes == 1) + mode->type |= DRM_MODE_TYPE_PREFERRED; + + drm_mode_probed_add(connector, mode); + } + + connector->display_info.bpc = hx->desc->mode_data[0].bpc; + connector->display_info.height_mm = hx->desc->mode_data[0].mode.height_mm; + connector->display_info.width_mm = hx->desc->mode_data[0].mode.width_mm; + + return hx->desc->num_modes; +} + +static const struct drm_panel_funcs hx8279_panel_funcs = { + .disable = hx8279_disable, + .unprepare = hx8279_unprepare, + .prepare = hx8279_prepare, + .enable = hx8279_enable, + .get_modes = hx8279_get_modes, +}; + +static int hx8279_init_vregs(struct hx8279 *hx, struct device *dev) +{ + int ret; + + hx->vregs[0].supply = "vdd"; + hx->vregs[1].supply = "iovcc"; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(hx->vregs), + hx->vregs); + if (ret < 0) + return ret; + + ret = regulator_is_supported_voltage(hx->vregs[0].consumer, + 3000000, 5000000); + if (!ret) + return -EINVAL; + + ret = regulator_is_supported_voltage(hx->vregs[1].consumer, + 1700000, 1900000); + if (!ret) + return -EINVAL; + + return 0; +} + +static int hx8279_check_gmux_config(struct hx8279 *hx, struct device *dev) +{ + const struct hx8279_panel_desc *desc = hx->desc; + const struct hx8279_goa_mux *gmux = desc->gmux; + int i; + + /* No gmux defined means we simply skip the GOA mux configuration */ + if (!gmux) + return 0; + + for (i = 0; i < ARRAY_SIZE(gmux->gout_l); i++) { + if (gmux->gout_l[i] > (HX8279_GOUT_STB | HX8279_GOUT_SEL)) + return dev_err_probe(dev, -EINVAL, + "Invalid value found in gout_l[%d]\n", i); + } + + for (i = 0; i < ARRAY_SIZE(gmux->gout_r); i++) { + if (gmux->gout_r[i] > (HX8279_GOUT_STB | HX8279_GOUT_SEL)) + return dev_err_probe(dev, -EINVAL, + "Invalid value found in gout_r[%d]\n", i); + } + + return 0; +} + +static int hx8279_check_goa_config(struct hx8279 *hx, struct device *dev) +{ + const struct hx8279_panel_desc *desc = hx->desc; + bool goa_odd_valid, goa_even_valid; + int i, num_zero, num_clr = 0; + + /* Up to 4 zero values is a valid configuration. Check them all. */ + num_zero = 1; + for (i = 0; i < ARRAY_SIZE(desc->goa_odd_timing); i++) { + if (desc->goa_odd_timing[i]) + num_zero++; + } + + goa_odd_valid = (num_zero != ARRAY_SIZE(desc->goa_odd_timing)); + + /* Up to 3 zeroes is a valid config. Check them all. */ + num_zero = 1; + for (i = 0; i < ARRAY_SIZE(desc->goa_even_timing); i++) { + if (desc->goa_even_timing[i]) + num_zero++; + } + + goa_even_valid = (num_zero != ARRAY_SIZE(desc->goa_even_timing)); + + /* Programming one without the other would make no sense! */ + if (goa_odd_valid != goa_even_valid) + return -EINVAL; + + /* We know that both are either true or false now, check just one */ + if (!goa_odd_valid) + hx->skip_goa_timing = true; + + if (!desc->goa_unk_ba && !desc->goa_stv_lead_time_ck && + !desc->goa_ckv_lead_time_ck && !desc->goa_ckv_dummy_vblank_num && + !desc->goa_ckv_rise_precharge && !desc->goa_ckv_fall_precharge && + !desc->goa_clr1_width_adj && !desc->goa_clr234_width_adj && + !desc->goa_unk_e4 && !desc->goa_unk_e5) { + hx->skip_goa_config = true; + return 0; + } + + if ((desc->goa_stv_lead_time_ck > HX8279_P3_GOA_STV_LEAD) || + (desc->goa_ckv_lead_time_ck > HX8279_P3_GOA_CKV_LEAD) || + (desc->goa_ckv_dummy_vblank_num > HX8279_P3_GOA_CKV_DUMMY)) + return dev_err_probe(dev, -EINVAL, + "Invalid lead timings in GOA config\n"); + + /* + * Don't perform zero check for polarity and start position, as + * both pol=0 and start=0 are valid configuration values. + */ + for (i = 0; i < ARRAY_SIZE(desc->goa_clr_start_pos); i++) { + if (desc->goa_clr_start_pos[i] < 0) + continue; + else if (desc->goa_clr_start_pos[i] > HX8279_P3_GOA_CLR_CFG_STARTPOS) + return dev_err_probe(dev, -EINVAL, + "Invalid start position for CLR%d\n", i + 1); + else + num_clr++; + } + if (!num_clr) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(desc->goa_clr_polarity); i++) { + if (num_clr < 0) + return -EINVAL; + + if (desc->goa_clr_polarity[i] < 0) + continue; + else if (desc->goa_clr_polarity[i] > 1) + return dev_err_probe(dev, -EINVAL, + "Invalid polarity for CLR%d\n", i + 1); + else + num_clr--; + } + + return 0; +} + +static int hx8279_check_dig_gamma(struct hx8279 *hx, struct device *dev, const u8 *component) +{ + u8 gamma_high_bits[4]; + u16 prev_val = 0; + int i, j, k, x; + + /* + * The gamma values are 10 bits long and shall be incremental + * to form a digital gamma correction reference curve. + * + * As for the registers format: the first 24 bytes contain each the + * lowest 8 bits for each of the gamma level references, and the last + * 6 bytes contain the high two bits of 4 registers at a time, where + * the first two bits are relative to the last register, and the last + * two are relative to the first register. + * + * Another way of saying, those are the first four LOW values: + * DGMA1_LO = 0xb1, DGMA2_LO = 0xb2, DGMA3_LO = 0xb3, DGMA4_LO = 0xb4 + * + * The high values for those four are at DGMA1_4_HI = 0xc9; + * ...and DGMA1_4_HI's data contains the following bits: + * [1:0] = DGMA4_HI, [3:2] = DGMA3_HI, [5:4] = DGMA2_HI, [7:6] = DGMA1_HI + */ + for (i = 0; i < HX8279_PG_DGAMMA_NUM_HI_BYTES; i++) { + k = HX8279_PG_DGAMMA_NUM_LO_BYTES + i; + j = i * 4; + x = 0; + + gamma_high_bits[0] = FIELD_GET(HX8279_DGAMMA_DGMA1_HI, component[k]); + gamma_high_bits[1] = FIELD_GET(HX8279_DGAMMA_DGMA2_HI, component[k]); + gamma_high_bits[2] = FIELD_GET(HX8279_DGAMMA_DGMA3_HI, component[k]); + gamma_high_bits[3] = FIELD_GET(HX8279_DGAMMA_DGMA4_HI, component[k]); + + do { + u16 cur_val = component[j] | (gamma_high_bits[x] << 8); + + if (cur_val < prev_val) + return dev_err_probe(dev, -EINVAL, + "Invalid dgamma values: %u < %u!\n", + cur_val, prev_val); + prev_val = cur_val; + j++; + x++; + } while (x < 4); + } + + return 0; +} + +static int hx8279_check_params(struct hx8279 *hx, struct device *dev) +{ + const struct hx8279_panel_desc *desc = hx->desc; + int ret; + + /* Voltages config validation */ + if (!desc->vgh_mv && !desc->vgl_mv && !desc->vgph_mv && !desc->vgnh_mv) + hx->skip_voltage_config = true; + else if ((desc->vgh_mv && desc->vgh_mv < HX8279_VGH_MIN_MV) || + (desc->vgl_mv && desc->vgl_mv < HX8279_VGL_MIN_MV) || + (desc->vgph_mv && desc->vgph_mv < HX8279_VGPNH_MIN_MV) || + (desc->vgnh_mv && desc->vgnh_mv < HX8279_VGPNH_MIN_MV)) + return -EINVAL; + + /* GOA Muxing validation */ + ret = hx8279_check_gmux_config(hx, dev); + if (ret) + return ret; + + /* GOA Configuration validation */ + ret = hx8279_check_goa_config(hx, dev); + if (ret) + return ret; + + /* MIPI Configuration validation */ + if (!desc->bta_tlpx && !desc->lhs_settle_time_by_osc25 && + !desc->ths_settle_time && !desc->timing_unk_b8 && + !desc->timing_unk_bc && !desc->timing_unk_d6) + hx->skip_mipi_timing = true; + + /* ENG/Gamma Configuration validation */ + if (desc->gamma_ctl > (HX8279_P6_GAMMA_POCGM_CTL | HX8279_P6_GAMMA_POGCMD_CTL)) + return -EINVAL; + + /* Digital Gamma values validation */ + if (desc->dgamma) { + ret = hx8279_check_dig_gamma(hx, dev, desc->dgamma->r); + if (ret) + return ret; + + ret = hx8279_check_dig_gamma(hx, dev, desc->dgamma->g); + if (ret) + return ret; + + ret = hx8279_check_dig_gamma(hx, dev, desc->dgamma->b); + if (ret) + return ret; + } + + return 0; +} + +static int hx8279_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct device_node *dsi_r; + struct hx8279 *hx; + int i, ret; + + hx = devm_drm_panel_alloc(dev, struct hx8279, panel, + &hx8279_panel_funcs, DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(hx)) + return PTR_ERR(hx); + + ret = hx8279_init_vregs(hx, dev); + if (ret) + return ret; + + hx->desc = device_get_match_data(dev); + if (!hx->desc) + return -ENODEV; + + /* + * In some DriverICs some or all fields may be OTP: perform a + * basic configuration check before writing to help avoiding + * irreparable mistakes. + * + * Please note that this is not perfect and will only check if + * the values may be plausible; values that are wrong for a + * specific display, but still plausible for DrIC config will + * be accepted. + */ + ret = hx8279_check_params(hx, dev); + if (ret) + return dev_err_probe(dev, ret, "Invalid DriverIC configuration\n"); + + /* The enable line may be always tied to VCCIO, so this is optional */ + hx->enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_ASIS); + if (IS_ERR(hx->enable_gpio)) + return dev_err_probe(dev, PTR_ERR(hx->enable_gpio), + "Failed to get enable GPIO\n"); + + hx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_ASIS); + if (IS_ERR(hx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(hx->reset_gpio), + "Failed to get reset GPIO\n"); + + /* If the panel is connected on two DSIs then DSI0 left, DSI1 right */ + dsi_r = of_graph_get_remote_node(dsi->dev.of_node, 1, -1); + if (dsi_r) { + const struct mipi_dsi_device_info *info = &hx->desc->dsi_info; + struct mipi_dsi_host *dsi_r_host; + + dsi_r_host = of_find_mipi_dsi_host_by_node(dsi_r); + of_node_put(dsi_r); + if (!dsi_r_host) + return dev_err_probe(dev, -EPROBE_DEFER, + "Cannot get secondary DSI host\n"); + + hx->dsi[1] = devm_mipi_dsi_device_register_full(dev, dsi_r_host, info); + if (IS_ERR(hx->dsi[1])) + return dev_err_probe(dev, PTR_ERR(hx->dsi[1]), + "Cannot get secondary DSI node\n"); + mipi_dsi_set_drvdata(hx->dsi[1], hx); + } + + hx->dsi[0] = dsi; + mipi_dsi_set_drvdata(dsi, hx); + + ret = drm_panel_of_backlight(&hx->panel); + if (ret) + return dev_err_probe(dev, ret, "Failed to get backlight\n"); + + drm_panel_add(&hx->panel); + + for (i = 0; i < 2; i++) { + if (!hx->dsi[i]) + continue; + + hx->dsi[i]->lanes = hx->desc->num_lanes; + hx->dsi[i]->format = MIPI_DSI_FMT_RGB888; + + hx->dsi[i]->mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS | + MIPI_DSI_MODE_LPM; + + if (hx->desc->mode_data[0].is_video_mode) + hx->dsi[i]->mode_flags |= MIPI_DSI_MODE_VIDEO | + MIPI_DSI_MODE_VIDEO_SYNC_PULSE; + + ret = devm_mipi_dsi_attach(dev, hx->dsi[i]); + if (ret < 0) { + drm_panel_remove(&hx->panel); + return dev_err_probe(dev, ret, + "Cannot attach to DSI%d host.\n", i); + } + } + + return 0; +} + +static void hx8279_remove(struct mipi_dsi_device *dsi) +{ + struct hx8279 *hx = mipi_dsi_get_drvdata(dsi); + + drm_panel_remove(&hx->panel); +} + +static const struct hx8279_panel_mode aoly_sl101pm1794fog_v15_modes[] = { + { + .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, + .width_mm = 136, + .height_mm = 217, + .type = DRM_MODE_TYPE_DRIVER + }, + .bpc = 8, + .is_video_mode = true, + }, +}; + +static const struct hx8279_panel_mode startek_kd070fhfid078_modes[] = { + { + .mode = { + .clock = 156458, + .hdisplay = 1200, + .hsync_start = 1200 + 50, + .hsync_end = 1200 + 50 + 24, + .htotal = 1200 + 50 + 24 + 66, + .vdisplay = 1920, + .vsync_start = 1920 + 14, + .vsync_end = 1920 + 14 + 2, + .vtotal = 1920 + 14 + 2 + 10, + .width_mm = 95, + .height_mm = 151, + .type = DRM_MODE_TYPE_DRIVER + }, + .bpc = 8, + .is_video_mode = true, + }, +}; + +static const struct hx8279_goa_mux aoly_sl101pm1794fog_v15_gmux = { + .gout_l = { 0x5, 0x5, 0xb, 0xb, 0x9, 0x9, 0x16, 0x16, 0xe, 0xe, + 0x7, 0x7, 0x26, 0x26, 0x15, 0x15, 0x1, 0x1, 0x3, 0x3 }, + .gout_r = { 0x6, 0x6, 0xc, 0xc, 0xa, 0xa, 0x16, 0x16, 0xe, 0xe, + 0x8, 0x8, 0x26, 0x26, 0x15, 0x15, 0x2, 0x2, 0x4, 0x4 }, +}; + +static const struct hx8279_analog_gamma aoly_sl101pm1794fog_v15_ana_gamma = { + .pos = { 0x0, 0xd, 0x17, 0x26, 0x31, 0x1c, 0x2c, 0x33, 0x31, + 0x37, 0x37, 0x37, 0x39, 0x2e, 0x2f, 0x2f, 0x7 }, + .neg = { 0x0, 0xd, 0x17, 0x26, 0x31, 0x3f, 0x3f, 0x3f, 0x3f, + 0x37, 0x37, 0x37, 0x39, 0x2e, 0x2f, 0x2f, 0x7 }, +}; + +static const struct hx8279_digital_gamma aoly_sl101pm1794fog_v15_dig_gamma = { + .r = { 0x0, 0x5, 0x10, 0x22, 0x36, 0x4a, 0x6c, 0x9a, 0xd7, 0x17, + 0x92, 0x15, 0x18, 0x8c, 0x0, 0x3a, 0x72, 0x8c, 0xa5, 0xb1, + 0xbe, 0xca, 0xd1, 0xd4, 0x0, 0x0, 0x16, 0xaf, 0xff, 0xff }, + .g = { 0x4, 0x5, 0x11, 0x24, 0x39, 0x4e, 0x72, 0xa3, 0xe1, 0x25, + 0xa8, 0x2e, 0x32, 0xad, 0x28, 0x63, 0x9b, 0xb5, 0xcf, 0xdb, + 0xe8, 0xf5, 0xfa, 0xfc, 0x0, 0x0, 0x16, 0xaf, 0xff, 0xff }, + .b = { 0x4, 0x4, 0xf, 0x22, 0x37, 0x4d, 0x71, 0xa2, 0xe1, 0x26, + 0xa9, 0x2f, 0x33, 0xac, 0x24, 0x5d, 0x94, 0xac, 0xc5, 0xd1, + 0xdc, 0xe8, 0xed, 0xf0, 0x0, 0x0, 0x16, 0xaf, 0xff, 0xff }, +}; + +static const struct hx8279_panel_desc aoly_sl101pm1794fog_v15 = { + .dsi_info = { + .type = "L101PM1794FOG-V15", + .channel = 0, + .node = NULL, + }, + .mode_data = aoly_sl101pm1794fog_v15_modes, + .num_modes = ARRAY_SIZE(aoly_sl101pm1794fog_v15_modes), + .num_lanes = 4, + + /* Driver/Module Configuration: LC Matrix voltages */ + .vgh_mv = 16500, + .vgl_mv = 11200, + .vgph_mv = 4600, + .vgnh_mv = 4600, + + /* Analog Gamma correction */ + .agamma = &aoly_sl101pm1794fog_v15_ana_gamma, + + /* Gate driver On Array (GOA) Muxing */ + .gmux = &aoly_sl101pm1794fog_v15_gmux, + + /* Gate driver On Array (GOA) Mux Config */ + .goa_unk_ba = 0xf0, + .goa_odd_timing = { 0, 0, 0, 42, 0, 0 }, + .goa_even_timing = { 1, 42, 0, 0 }, + .goa_stv_lead_time_ck = 11, + .goa_ckv_lead_time_ck = 7, + .goa_ckv_dummy_vblank_num = 3, + .goa_ckv_rise_precharge = 1, + .goa_clr1_width_adj = 0, + .goa_clr234_width_adj = 0, + .goa_clr_polarity = { 1, 0, 0, 0 }, + .goa_clr_start_pos = { 8, 9, 3, 4 }, + .goa_unk_e4 = 0xc0, + .goa_unk_e5 = 0x0d, + + /* MIPI Configuration */ + .bta_tlpx = 2, + .lhs_settle_time_by_osc25 = true, + .ths_settle_time = 2, + .timing_unk_b8 = 0xa5, + .timing_unk_bc = 0x20, + .timing_unk_d6 = 0x7f, + + /* ENG/Gamma Configuration */ + .gamma_ctl = 0, + .volt_adj = FIELD_PREP_CONST(HX8279_P6_VOLT_ADJ_VCCIFS, 3) | + FIELD_PREP_CONST(HX8279_P6_VOLT_ADJ_VCCS, 3), + .src_delay_time_adj_ck = 50, + + /* Digital Gamma Adjustment */ + .dgamma = &aoly_sl101pm1794fog_v15_dig_gamma, +}; + +static const struct hx8279_goa_mux startek_kd070fhfid078_gmux = { + .gout_l = { 0xd, 0xd, 0x6, 0x6, 0x8, 0x8, 0xa, 0xa, 0xc, 0xc, + 0x0, 0x0, 0xe, 0xe, 0x1, 0x1, 0x4, 0x4, 0x0, 0x0 }, + .gout_r = { 0xd, 0xd, 0x5, 0x5, 0x7, 0x7, 0x9, 0x9, 0xb, 0xb, + 0x0, 0x0, 0xe, 0xe, 0x1, 0x1, 0x3, 0x3, 0x0, 0x0 }, +}; + +static const struct hx8279_panel_desc startek_kd070fhfid078 = { + .dsi_info = { + .type = "KD070FHFID078", + .channel = 0, + .node = NULL, + }, + .mode_data = startek_kd070fhfid078_modes, + .num_modes = ARRAY_SIZE(startek_kd070fhfid078_modes), + .num_lanes = 4, + + /* Driver/Module Configuration: LC Matrix voltages */ + .vgh_mv = 18000, + .vgl_mv = 12100, + .vgph_mv = 5500, + .vgnh_mv = 5500, + + /* Gate driver On Array (GOA) Mux Config */ + .gmux = &startek_kd070fhfid078_gmux, + + /* Gate driver On Array (GOA) Configuration */ + .goa_unk_ba = 0xf0, + .goa_stv_lead_time_ck = 7, + .goa_ckv_lead_time_ck = 3, + .goa_ckv_dummy_vblank_num = 1, + .goa_ckv_rise_precharge = 0, + .goa_ckv_fall_precharge = 0, + .goa_clr1_width_adj = 1, + .goa_clr234_width_adj = 5, + .goa_clr_polarity = { 0, 1, -1, -1 }, + .goa_clr_start_pos = { 5, 10, -1, -1 }, + .goa_unk_e4 = 0xc0, + .goa_unk_e5 = 0x00, + + /* MIPI Configuration */ + .bta_tlpx = 2, + .lhs_settle_time_by_osc25 = true, + .ths_settle_time = 2, + .timing_unk_b8 = 0x7f, + .timing_unk_bc = 0x20, + .timing_unk_d6 = 0x7f, + + /* ENG/Gamma Configuration */ + .gamma_ctl = FIELD_PREP_CONST(HX8279_P6_GAMMA_POCGM_CTL, 1) | + FIELD_PREP_CONST(HX8279_P6_GAMMA_POGCMD_CTL, 1), + .src_delay_time_adj_ck = 72, +}; + +static const struct of_device_id hx8279_of_match[] = { + { .compatible = "aoly,sl101pm1794fog-v15", .data = &aoly_sl101pm1794fog_v15 }, + { .compatible = "startek,kd070fhfid078", .data = &startek_kd070fhfid078 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, hx8279_of_match); + +static struct mipi_dsi_driver hx8279_driver = { + .probe = hx8279_probe, + .remove = hx8279_remove, + .driver = { + .name = "panel-himax-hx8279", + .of_match_table = hx8279_of_match, + }, +}; +module_mipi_dsi_driver(hx8279_driver); + +MODULE_AUTHOR("AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>"); +MODULE_DESCRIPTION("Himax HX8279 DriverIC panels driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-himax-hx83102.c b/drivers/gpu/drm/panel/panel-himax-hx83102.c new file mode 100644 index 000000000000..4c432d207634 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-himax-hx83102.c @@ -0,0 +1,1088 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for panels based on Himax HX83102 controller, such as: + * + * - Starry 10.51" WUXGA MIPI-DSI panel + * + * Based on drivers/gpu/drm/panel/panel-himax-hx8394.c + */ + +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regulator/consumer.h> + +#include <drm/drm_connector.h> +#include <drm/drm_crtc.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_panel.h> + +#include <video/mipi_display.h> + +/* Manufacturer specific DSI commands */ +#define HX83102_SETPOWER 0xb1 +#define HX83102_SETDISP 0xb2 +#define HX83102_SETCYC 0xb4 +#define HX83102_UNKNOWN_B6 0xb6 +#define HX83102_UNKNOWN_B8 0xb8 +#define HX83102_SETEXTC 0xb9 +#define HX83102_SETMIPI 0xba +#define HX83102_SETVDC 0xbc +#define HX83102_SETBANK 0xbd +#define HX83102_UNKNOWN_BE 0xbe +#define HX83102_SETPTBA 0xbf +#define HX83102_SETSTBA 0xc0 +#define HX83102_SETTCON 0xc7 +#define HX83102_SETRAMDMY 0xc8 +#define HX83102_SETPWM 0xc9 +#define HX83102_SETCLOCK 0xcb +#define HX83102_SETPANEL 0xcc +#define HX83102_SETCASCADE 0xd0 +#define HX83102_SETPCTRL 0xd1 +#define HX83102_UNKNOWN_D2 0xd2 +#define HX83102_SETGIP0 0xd3 +#define HX83102_SETGIP1 0xd5 +#define HX83102_SETGIP2 0xd6 +#define HX83102_SETGIP3 0xd8 +#define HX83102_UNKNOWN_D9 0xd9 +#define HX83102_SETGMA 0xe0 +#define HX83102_UNKNOWN_E1 0xe1 +#define HX83102_SETTP1 0xe7 +#define HX83102_SETSPCCMD 0xe9 + +struct hx83102 { + struct drm_panel base; + struct mipi_dsi_device *dsi; + + const struct hx83102_panel_desc *desc; + + enum drm_panel_orientation orientation; + struct regulator *pp1800; + struct regulator *avee; + struct regulator *avdd; + struct gpio_desc *enable_gpio; +}; + +struct hx83102_panel_desc { + const struct drm_display_mode *modes; + + /** + * @width_mm: width of the panel's active display area + * @height_mm: height of the panel's active display area + */ + struct { + unsigned int width_mm; + unsigned int height_mm; + } size; + + int (*init)(struct hx83102 *ctx); +}; + +static inline struct hx83102 *panel_to_hx83102(struct drm_panel *panel) +{ + return container_of(panel, struct hx83102, base); +} + +static void hx83102_enable_extended_cmds(struct mipi_dsi_multi_context *dsi_ctx, bool enable) +{ + if (enable) + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX83102_SETEXTC, 0x83, 0x10, 0x21, 0x55, 0x00); + else + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX83102_SETEXTC, 0x00, 0x00, 0x00); +} + +static int starry_himax83102_j02_init(struct hx83102 *ctx) +{ + struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; + + hx83102_enable_extended_cmds(&dsi_ctx, true); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPOWER, 0x2c, 0xb5, 0xb5, 0x31, 0xf1, + 0x31, 0xd7, 0x2f, 0x36, 0x36, 0x36, 0x36, 0x1a, 0x8b, 0x11, + 0x65, 0x00, 0x88, 0xfa, 0xff, 0xff, 0x8f, 0xff, 0x08, 0x74, + 0x33); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETDISP, 0x00, 0x47, 0xb0, 0x80, 0x00, + 0x12, 0x72, 0x3c, 0xa3, 0x03, 0x03, 0x00, 0x00, 0x88, 0xf5); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETCYC, 0x76, 0x76, 0x76, 0x76, 0x76, + 0x76, 0x63, 0x5c, 0x63, 0x5c, 0x01, 0x9e); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xcd); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETMIPI, 0x84); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETVDC, 0x1b, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_UNKNOWN_BE, 0x20); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPTBA, 0xfc, 0xc4); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSTBA, 0x36, 0x36, 0x22, 0x11, 0x22, + 0xa0, 0x61, 0x08, 0xf5, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xcc); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETTCON, 0x80); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xc6); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETRAMDMY, 0x97); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPWM, 0x00, 0x1e, 0x13, 0x88, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETCLOCK, 0x08, 0x13, 0x07, 0x00, 0x0f, 0x33); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPANEL, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xc4); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETCASCADE, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPCTRL, 0x37, 0x06, 0x00, 0x02, 0x04, 0x0c, + 0xff); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_UNKNOWN_D2, 0x1f, 0x11, 0x1f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP0, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x08, 0x37, 0x47, 0x34, 0x3b, 0x12, 0x12, 0x03, 0x03, + 0x32, 0x10, 0x10, 0x00, 0x10, 0x32, 0x10, 0x08, 0x00, 0x08, 0x32, + 0x17, 0x94, 0x07, 0x94, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP1, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x19, 0x19, 0x40, 0x40, 0x1a, 0x1a, 0x1b, + 0x1b, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x20, 0x21, + 0x28, 0x29, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP2, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x40, 0x40, 0x19, 0x19, 0x1a, 0x1a, 0x1b, + 0x1b, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, 0x29, 0x28, + 0x21, 0x20, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP3, 0xaa, 0xba, 0xea, 0xaa, 0xaa, 0xa0, + 0xaa, 0xba, 0xea, 0xaa, 0xaa, 0xa0, 0xaa, 0xba, 0xea, 0xaa, 0xaa, + 0xa0, 0xaa, 0xba, 0xea, 0xaa, 0xaa, 0xa0, 0xaa, 0xba, 0xea, 0xaa, + 0xaa, 0xa0, 0xaa, 0xba, 0xea, 0xaa, 0xaa, 0xa0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGMA, 0x00, 0x09, 0x14, 0x1e, 0x26, 0x48, + 0x61, 0x67, 0x6c, 0x67, 0x7d, 0x7f, 0x80, 0x8b, 0x87, 0x8f, 0x98, + 0xab, 0xab, 0x55, 0x5c, 0x68, 0x73, 0x00, 0x09, 0x14, 0x1e, 0x26, + 0x48, 0x61, 0x67, 0x6c, 0x67, 0x7d, 0x7f, 0x80, 0x8b, 0x87, 0x8f, + 0x98, 0xab, 0xab, 0x55, 0x5c, 0x68, 0x73); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETTP1, 0x0e, 0x10, 0x10, 0x21, 0x2b, 0x9a, + 0x02, 0x54, 0x9a, 0x14, 0x14, 0x00, 0x00, 0x00, 0x00, 0x12, 0x05, + 0x02, 0x02, 0x10); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETBANK, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPOWER, 0x01, 0xbf, 0x11); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETCLOCK, 0x86); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_UNKNOWN_D2, 0x3c, 0xfa); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP0, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x80, 0x0c, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETTP1, 0x02, 0x00, 0x28, 0x01, 0x7e, 0x0f, + 0x7e, 0x10, 0xa0, 0x00, 0x00, 0x20, 0x40, 0x50, 0x40); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETBANK, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP3, 0xff, 0xff, 0xbf, 0xfe, 0xaa, 0xa0, + 0xff, 0xff, 0xbf, 0xfe, 0xaa, 0xa0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETTP1, 0xfe, 0x04, 0xfe, 0x04, 0xfe, 0x04, + 0x03, 0x03, 0x03, 0x26, 0x00, 0x26, 0x81, 0x02, 0x40, 0x00, 0x20, + 0x9e, 0x04, 0x03, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETBANK, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xc6); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETCYC, 0x03, 0xff, 0xf8); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP3, 0x00, 0x2a, 0xaa, 0xa8, 0x00, 0x00, + 0x00, 0x2a, 0xaa, 0xa8, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xfc, 0x00, + 0x00, 0x00, 0x3f, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x2a, 0xaa, 0xa8, + 0x00, 0x00, 0x00, 0x2a, 0xaa, 0xa8, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETBANK, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xc4); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETMIPI, 0x96); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETBANK, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xc5); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETMIPI, 0x4f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETBANK, 0x00); + + return dsi_ctx.accum_err; +}; + +static int boe_nv110wum_init(struct hx83102 *ctx) +{ + struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; + + msleep(60); + + hx83102_enable_extended_cmds(&dsi_ctx, true); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPOWER, 0x2c, 0xaf, 0xaf, 0x2b, 0xeb, 0x42, + 0xe1, 0x4d, 0x36, 0x36, 0x36, 0x36, 0x1a, 0x8b, 0x11, 0x65, 0x00, + 0x88, 0xfa, 0xff, 0xff, 0x8f, 0xff, 0x08, 0x9a, 0x33); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETDISP, 0x00, 0x47, 0xb0, 0x80, 0x00, 0x12, + 0x71, 0x3c, 0xa3, 0x11, 0x00, 0x00, 0x00, 0x88, 0xf5, 0x22, 0x8f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETCYC, 0x49, 0x49, 0x32, 0x32, 0x14, 0x32, + 0x84, 0x6e, 0x84, 0x6e, 0x01, 0x9c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xcd); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETMIPI, 0x84); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETVDC, 0x1b, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_UNKNOWN_BE, 0x20); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPTBA, 0xfc, 0x84); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSTBA, 0x36, 0x36, 0x22, 0x00, 0x00, 0xa0, + 0x61, 0x08, 0xf5, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xcc); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETTCON, 0x80); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xc6); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETRAMDMY, 0x97); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPWM, 0x00, 0x1e, 0x30, 0xd4, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETCLOCK, 0x08, 0x13, 0x07, 0x00, 0x0f, 0x34); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPANEL, 0x02, 0x03, 0x44); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xc4); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETCASCADE, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPCTRL, 0x37, 0x06, 0x00, 0x02, 0x04, 0x0c, 0xff); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_UNKNOWN_D2, 0x1f, 0x11, 0x1f, 0x11); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP0, 0x06, 0x00, 0x00, 0x00, 0x00, 0x04, + 0x08, 0x04, 0x08, 0x37, 0x37, 0x64, 0x4b, 0x11, 0x11, 0x03, 0x03, 0x32, + 0x10, 0x0e, 0x00, 0x0e, 0x32, 0x10, 0x0a, 0x00, 0x0a, 0x32, 0x17, 0x98, + 0x07, 0x98, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP1, 0x18, 0x18, 0x18, 0x18, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1f, 0x1f, 0x1f, 0x1f, 0x24, 0x24, 0x24, 0x24, 0x07, 0x06, + 0x07, 0x06, 0x05, 0x04, 0x05, 0x04, 0x03, 0x02, 0x03, 0x02, 0x01, 0x00, + 0x01, 0x00, 0x21, 0x20, 0x21, 0x20, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP3, 0xaf, 0xaa, 0xaa, 0xaa, 0xaa, 0xa0, + 0xaf, 0xaa, 0xaa, 0xaa, 0xaa, 0xa0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGMA, 0x00, 0x05, 0x0d, 0x14, 0x1b, 0x2c, + 0x44, 0x49, 0x51, 0x4c, 0x67, 0x6c, 0x71, 0x80, 0x7d, 0x84, 0x8d, 0xa0, + 0xa0, 0x4f, 0x58, 0x64, 0x73, 0x00, 0x05, 0x0d, 0x14, 0x1b, 0x2c, 0x44, + 0x49, 0x51, 0x4c, 0x67, 0x6c, 0x71, 0x80, 0x7d, 0x84, 0x8d, 0xa0, 0xa0, + 0x4f, 0x58, 0x64, 0x73); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETTP1, 0x07, 0x10, 0x10, 0x1a, 0x26, 0x9e, + 0x00, 0x53, 0x9b, 0x14, 0x14); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_UNKNOWN_E1, 0x11, 0x00, 0x00, 0x89, 0x30, 0x80, + 0x07, 0x80, 0x02, 0x58, 0x00, 0x14, 0x02, 0x58, 0x02, 0x58, 0x02, 0x00, + 0x02, 0x2c, 0x00, 0x20, 0x02, 0x02, 0x00, 0x08, 0x00, 0x0c, 0x05, 0x0e, + 0x04, 0x94, 0x18, 0x00, 0x10, 0xf0, 0x03, 0x0c, 0x20, 0x00, 0x06, 0x0b, + 0x0b, 0x33, 0x0e); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETBANK, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP3, 0xff, 0xff, 0xff, 0xff, 0xfa, 0xa0, + 0xff, 0xff, 0xff, 0xff, 0xfa, 0xa0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPOWER, 0x01, 0xbf, 0x11); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETCLOCK, 0x86); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_UNKNOWN_D2, 0x96); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xc9); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP0, 0x84); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xd1); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_UNKNOWN_E1, 0xf6, 0x2b, 0x34, 0x2b, 0x74, 0x3b, + 0x74, 0x6b, 0x74); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETTP1, 0x02, 0x00, 0x2b, 0x01, 0x7e, 0x0f, + 0x7e, 0x10, 0xa0, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETBANK, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETCYC, 0x02, 0x00, 0xbb, 0x11); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP3, 0xff, 0xaf, 0xff, 0xff, 0xfa, 0xa0, + 0xff, 0xaf, 0xff, 0xff, 0xfa, 0xa0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETTP1, 0xfe, 0x01, 0xfe, 0x01, 0xfe, 0x01, + 0x00, 0x00, 0x00, 0x23, 0x00, 0x23, 0x81, 0x02, 0x40, 0x00, 0x20, 0x65, + 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETBANK, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP3, 0xaa, 0xaf, 0xaa, 0xaa, 0xa0, 0x00, + 0xaa, 0xaf, 0xaa, 0xaa, 0xa0, 0x00, 0xaa, 0xaf, 0xaa, 0xaa, 0xa0, 0x00, + 0xaa, 0xaf, 0xaa, 0xaa, 0xa0, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xc6); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETCYC, 0x03, 0xff, 0xf8); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_UNKNOWN_E1, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETBANK, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xc4); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETMIPI, 0x96); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETBANK, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xc5); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETMIPI, 0x4f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETBANK, 0x00); + hx83102_enable_extended_cmds(&dsi_ctx, false); + + mipi_dsi_msleep(&dsi_ctx, 50); + + return dsi_ctx.accum_err; +}; + +static int csot_pna957qt1_1_init(struct hx83102 *ctx) +{ + struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; + + msleep(60); + + hx83102_enable_extended_cmds(&dsi_ctx, true); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xc4); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_UNKNOWN_D9, 0xd2); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPOWER, 0x2c, 0xb3, 0xb3, 0x31, 0xf1, 0x33, + 0xe0, 0x54, 0x36, 0x36, 0x3a, 0x3a, 0x32, 0x8b, 0x11, 0xe5, + 0x98); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xd9); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPOWER, 0x8b, 0x33); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETDISP, 0x00, 0x47, 0xb0, 0x80, 0x00, 0x2c, + 0x80, 0x3c, 0x9f, 0x22, 0x20, 0x00, 0x00, 0x98, 0x51); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETCYC, 0x41, 0x41, 0x41, 0x41, 0x64, 0x64, + 0x40, 0x84, 0x64, 0x84, 0x01, 0x9d, 0x01, 0x02, 0x01, 0x00, + 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETVDC, 0x1b, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_UNKNOWN_BE, 0x20); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPTBA, 0xfc, 0xc4, 0x80, 0x9c, 0x36, 0x00, + 0x0d, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSTBA, 0x32, 0x32, 0x22, 0x11, 0x22, 0xa0, + 0x31, 0x08, 0xf5, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xcc); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETTCON, 0x80); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xc6); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETRAMDMY, 0x97); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPWM, 0x00, 0x1e, 0x13, 0x88, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETCLOCK, 0x08, 0x13, 0x07, 0x00, 0x0f, + 0x36); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPANEL, 0x02, 0x03, 0x44); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPCTRL, 0x07, 0x06, 0x00, 0x02, 0x04, 0x2c, + 0xff); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP0, 0x06, 0x00, 0x00, 0x00, 0x40, 0x04, + 0x08, 0x04, 0x08, 0x37, 0x07, 0x44, 0x37, 0x2b, 0x2b, 0x03, + 0x03, 0x32, 0x10, 0x22, 0x00, 0x25, 0x32, 0x10, 0x29, 0x00, + 0x29, 0x32, 0x10, 0x08, 0x00, 0x08, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP1, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x07, 0x06, 0x07, 0x06, 0x05, 0x04, + 0x05, 0x04, 0x03, 0x02, 0x03, 0x02, 0x01, 0x00, 0x01, 0x00, + 0x18, 0x18, 0x25, 0x24, 0x25, 0x24, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1e, 0x1e, 0x1e, 0x1e, 0x20, 0x20, 0x20, 0x20); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP3, 0x0a, 0x2a, 0xaa, 0x8a, 0xaa, 0xa0, + 0x0a, 0x2a, 0xaa, 0x8a, 0xaa, 0xa0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGMA, 0x0a, 0x0e, 0x1a, 0x21, 0x28, 0x46, + 0x5c, 0x61, 0x63, 0x5e, 0x78, 0x7d, 0x80, 0x8e, 0x89, 0x90, + 0x98, 0xaa, 0xa8, 0x52, 0x59, 0x60, 0x6f, 0x06, 0x0a, 0x16, + 0x1d, 0x24, 0x46, 0x5c, 0x61, 0x6b, 0x66, 0x7c, 0x7d, 0x80, + 0x8e, 0x89, 0x90, 0x98, 0xaa, 0xa8, 0x52, 0x59, 0x60, 0x6f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETTP1, 0xe0, 0x10, 0x10, 0x0d, 0x1e, 0x9d, + 0x02, 0x52, 0x9d, 0x14, 0x14); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETBANK, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPOWER, 0x01, 0x7f, 0x11, 0xfd); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xc5); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETMIPI, 0x4f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETCLOCK, 0x86); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_UNKNOWN_D2, 0x64); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xc5); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP0, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP3, 0x0a, 0x2a, 0xaa, 0x8a, 0xaa, 0xa0, + 0x0a, 0x2a, 0xaa, 0x8a, 0xaa, 0xa0, 0x05, 0x15, 0x55, 0x45, + 0x55, 0x50, 0x05, 0x15, 0x55, 0x45, 0x55, 0x50); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETTP1, 0x02, 0x00, 0x24, 0x01, 0x7e, 0x0f, + 0x7c, 0x10, 0xa0, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETBANK, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETCLOCK, 0x03, 0x07, 0x00, 0x10, 0x7b); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP3, 0x0f, 0x3f, 0xff, 0xcf, 0xff, 0xf0, + 0x0f, 0x3f, 0xff, 0xcf, 0xff, 0xf0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETTP1, 0xfe, 0x01, 0xfe, 0x01, 0xfe, 0x01, + 0x00, 0x00, 0x00, 0x23, 0x00, 0x23, 0x81, 0x02, 0x40, 0x00, + 0x20, 0x9d, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETBANK, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETDISP, 0x66, 0x81); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xc6); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETCYC, 0x03, 0xff, 0xf8); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP3, 0x0a, 0x2a, 0xaa, 0x8a, 0xaa, 0xa0, + 0x0a, 0x2a, 0xaa, 0x8a, 0xaa, 0xa0, 0x0f, 0x2a, 0xaa, 0x8a, + 0xaa, 0xf0, 0x0f, 0x2a, 0xaa, 0x8a, 0xaa, 0xf0, 0x0a, 0x2a, + 0xaa, 0x8a, 0xaa, 0xa0, 0x0a, 0x2a, 0xaa, 0x8a, 0xaa, 0xa0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETBANK, 0x00); + hx83102_enable_extended_cmds(&dsi_ctx, false); + + mipi_dsi_msleep(&dsi_ctx, 60); + + return dsi_ctx.accum_err; +}; + +static int ivo_t109nw41_init(struct hx83102 *ctx) +{ + struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; + + msleep(60); + + hx83102_enable_extended_cmds(&dsi_ctx, true); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPOWER, 0x2c, 0xed, 0xed, 0x27, 0xe7, 0x52, + 0xf5, 0x39, 0x36, 0x36, 0x36, 0x36, 0x32, 0x8b, 0x11, 0x65, 0x00, 0x88, + 0xfa, 0xff, 0xff, 0x8f, 0xff, 0x08, 0xd6, 0x33); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETDISP, 0x00, 0x47, 0xb0, 0x80, 0x00, 0x12, + 0x71, 0x3c, 0xa3, 0x22, 0x20, 0x00, 0x00, 0x88, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETCYC, 0x35, 0x35, 0x43, 0x43, 0x35, 0x35, + 0x30, 0x7a, 0x30, 0x7a, 0x01, 0x9d); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xcd); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETMIPI, 0x84); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETVDC, 0x1b, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_UNKNOWN_BE, 0x20); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPTBA, 0xfc, 0xc4); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSTBA, 0x34, 0x34, 0x22, 0x11, 0x22, 0xa0, + 0x31, 0x08, 0xf5, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xcc); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETTCON, 0x80); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xd3); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETTCON, 0x22); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xc6); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETRAMDMY, 0x97); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPWM, 0x00, 0x1e, 0x13, 0x88, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETCLOCK, 0x08, 0x13, 0x07, 0x00, 0x0f, 0x34); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPANEL, 0x02, 0x03, 0x44); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xc4); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETCASCADE, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPCTRL, 0x07, 0x06, 0x00, 0x02, 0x04, 0x2c, + 0xff); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP0, 0x06, 0x00, 0x00, 0x00, 0x00, 0x08, + 0x08, 0x08, 0x08, 0x37, 0x07, 0x64, 0x7c, 0x11, 0x11, 0x03, 0x03, 0x32, + 0x10, 0x0e, 0x00, 0x0e, 0x32, 0x17, 0x97, 0x07, 0x97, 0x32, 0x00, 0x02, + 0x00, 0x02, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP1, 0x25, 0x24, 0x25, 0x24, 0x18, 0x18, + 0x18, 0x18, 0x07, 0x06, 0x07, 0x06, 0x05, 0x04, 0x05, 0x04, 0x03, 0x02, + 0x03, 0x02, 0x01, 0x00, 0x01, 0x00, 0x1e, 0x1e, 0x1e, 0x1e, 0x1f, 0x1f, + 0x1f, 0x1f, 0x21, 0x20, 0x21, 0x20, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP3, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa0, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGMA, 0x00, 0x07, 0x10, 0x17, 0x1c, 0x33, + 0x48, 0x50, 0x57, 0x50, 0x68, 0x6e, 0x71, 0x7f, 0x81, 0x8a, 0x8e, 0x9b, + 0x9c, 0x4d, 0x56, 0x5d, 0x73, 0x00, 0x07, 0x10, 0x17, 0x1c, 0x33, 0x48, + 0x50, 0x57, 0x50, 0x68, 0x6e, 0x71, 0x7f, 0x81, 0x8a, 0x8e, 0x9b, 0x9c, + 0x4d, 0x56, 0x5d, 0x73); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETTP1, 0x07, 0x10, 0x10, 0x1a, 0x26, 0x9e, + 0x00, 0x4f, 0xa0, 0x14, 0x14, 0x00, 0x00, 0x00, 0x00, 0x12, 0x0a, 0x02, + 0x02, 0x00, 0x33, 0x02, 0x04, 0x18, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETBANK, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPOWER, 0x01, 0x7f, 0x11, 0xfd); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETCLOCK, 0x86); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP0, 0x00, 0x00, 0x04, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa0, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETTP1, 0x02, 0x00, 0x2b, 0x01, 0x7e, 0x0f, + 0x7e, 0x10, 0xa0, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETBANK, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPTBA, 0xf2); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETCLOCK, 0x03, 0x07, 0x00, 0x10, 0x79); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP3, 0xff, 0xff, 0xff, 0xff, 0xfa, 0xa0, + 0xff, 0xff, 0xff, 0xff, 0xfa, 0xa0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETTP1, 0xfe, 0x01, 0xfe, 0x01, 0xfe, 0x01, + 0x00, 0x00, 0x00, 0x23, 0x00, 0x23, 0x81, 0x02, 0x40, 0x00, 0x20, 0x6e, + 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETBANK, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa0, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa0, 0xff, 0xff, 0xff, 0xff, 0xfa, 0xa0, + 0xff, 0xff, 0xff, 0xff, 0xfa, 0xa0, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa0, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xc6); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETCYC, 0x03, 0xff, 0xf8); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_UNKNOWN_E1, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETBANK, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_UNKNOWN_D2, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xc4); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETMIPI, 0x96); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETBANK, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xc5); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETMIPI, 0x4f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETBANK, 0x00); + hx83102_enable_extended_cmds(&dsi_ctx, false); + + mipi_dsi_msleep(&dsi_ctx, 60); + + return dsi_ctx.accum_err; +}; + +static int kingdisplay_kd110n11_51ie_init(struct hx83102 *ctx) +{ + struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; + + msleep(50); + + hx83102_enable_extended_cmds(&dsi_ctx, true); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xc4); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_UNKNOWN_D9, 0xd1); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPOWER, 0x2c, 0xb3, 0xb3, 0x31, 0xf1, + 0x33, 0xe0, 0x54, 0x36, 0x36, 0x3a, 0x3a, 0x32, 0x8b, + 0x11, 0xe5, 0x98); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xd9); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPOWER, 0x8b, 0x33); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETDISP, 0x00, 0x47, 0xb0, 0x80, 0x00, 0x2c, + 0x80, 0x3c, 0x9f, 0x22, 0x20, 0x00, 0x00, 0x98, 0x51); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETCYC, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, + 0x40, 0x84, 0x64, 0x84, 0x01, 0x9d, 0x01, 0x02, 0x01, 0x00, + 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETVDC, 0x1b, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_UNKNOWN_BE, 0x20); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPTBA, 0xfc, 0xc4, 0x80, 0x9c, 0x36, 0x00, + 0x0d, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSTBA, 0x32, 0x32, 0x22, 0x11, 0x22, 0xa0, + 0x31, 0x08, 0xf5, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xcc); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETTCON, 0x80); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xc6); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETRAMDMY, 0x97); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPWM, 0x00, 0x1e, 0x13, 0x88, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETCLOCK, 0x08, 0x13, 0x07, 0x00, + 0x0f, 0x36); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPANEL, 0x02, 0x03, 0x44); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPCTRL, 0x07, 0x06, 0x00, 0x02, + 0x04, 0x2c, 0xff); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP0, 0x06, 0x00, 0x00, 0x00, 0x40, 0x04, + 0x08, 0x04, 0x08, 0x37, 0x07, 0x44, 0x37, 0x2b, 0x2b, 0x03, + 0x03, 0x32, 0x10, 0x22, 0x00, 0x25, 0x32, 0x10, 0x29, 0x00, + 0x29, 0x32, 0x10, 0x08, 0x00, 0x08, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP1, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x07, 0x06, 0x07, 0x06, 0x05, 0x04, + 0x05, 0x04, 0x03, 0x02, 0x03, 0x02, 0x01, 0x00, 0x01, 0x00, + 0x18, 0x18, 0x25, 0x24, 0x25, 0x24, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1e, 0x1e, 0x1e, 0x1e, 0x20, 0x20, 0x20, 0x20); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP3, 0x0a, 0x2a, 0xaa, 0x8a, 0xaa, 0xa0, + 0x0a, 0x2a, 0xaa, 0x8a, 0xaa, 0xa0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETTP1, 0xe0, 0x10, 0x10, 0x0d, 0x1e, 0x9d, + 0x02, 0x52, 0x9d, 0x14, 0x14); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETBANK, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPOWER, 0x01, 0x7f, 0x11, 0xfd); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xc5); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETMIPI, 0x4f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETCLOCK, 0x86); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_UNKNOWN_D2, 0x64); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xc5); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP0, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP3, 0x0a, 0x2a, 0xaa, 0x8a, 0xaa, 0xa0, + 0x0a, 0x2a, 0xaa, 0x8a, 0xaa, 0xa0, 0x05, 0x15, 0x55, 0x45, + 0x55, 0x50, 0x05, 0x15, 0x55, 0x45, 0x55, 0x50); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETTP1, 0x02, 0x00, 0x24, 0x01, 0x7e, 0x0f, + 0x7c, 0x10, 0xa0, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETBANK, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETCLOCK, 0x03, 0x07, 0x00, 0x10, 0x7b); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP3, 0x0f, 0x3f, 0xff, 0xcf, 0xff, 0xf0, + 0x0f, 0x3f, 0xff, 0xcf, 0xff, 0xf0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETTP1, 0xfe, 0x01, 0xfe, 0x01, 0xfe, 0x01, + 0x00, 0x00, 0x00, 0x23, 0x00, 0x23, 0x81, 0x02, 0x40, 0x00, + 0x20, 0x9d, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETBANK, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETDISP, 0x66, 0x81); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xc6); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETCYC, 0x03, 0xff, 0xf8); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP3, 0x0a, 0x2a, 0xaa, 0x8a, 0xaa, 0xa0, + 0x0a, 0x2a, 0xaa, 0x8a, 0xaa, 0xa0, 0x0f, 0x2a, 0xaa, 0x8a, + 0xaa, 0xf0, 0x0f, 0x2a, 0xaa, 0x8a, 0xaa, 0xf0, 0x0a, 0x2a, + 0xaa, 0x8a, 0xaa, 0xa0, 0x0a, 0x2a, 0xaa, 0x8a, 0xaa, 0xa0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETBANK, 0x00); + hx83102_enable_extended_cmds(&dsi_ctx, false); + + return dsi_ctx.accum_err; +} + +static int starry_2082109qfh040022_50e_init(struct hx83102 *ctx) +{ + struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; + + msleep(50); + + hx83102_enable_extended_cmds(&dsi_ctx, true); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xc4); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_UNKNOWN_D9, 0xd1); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPOWER, 0x2c, 0xb5, 0xb5, 0x31, 0xf1, 0x33, + 0xc3, 0x57, 0x36, 0x36, 0x36, 0x36, 0x1a, 0x8b, 0x11, 0x65, + 0x00, 0x88, 0xfa, 0xff, 0xff, 0x8f, 0xff, 0x08, 0x3c, 0x33); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETDISP, 0x00, 0x47, 0xb0, 0x80, 0x00, 0x22, + 0x70, 0x3c, 0xa1, 0x22, 0x00, 0x00, 0x00, 0x88, 0xf4); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETCYC, 0x14, 0x16, 0x14, 0x50, 0x14, 0x50, + 0x0d, 0x6a, 0x0d, 0x6a, 0x01, 0x9e); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_UNKNOWN_B6, 0x34, 0x34, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_UNKNOWN_B8, 0x40); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xcd); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETMIPI, 0x84); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETVDC, 0x1b, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_UNKNOWN_BE, 0x20); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPTBA, 0xfc, 0xc4); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSTBA, 0x38, 0x38, 0x22, 0x11, 0x33, 0xa0, + 0x61, 0x08, 0xf5, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xcc); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETTCON, 0x80); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xc6); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETRAMDMY, 0x97); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPWM, 0x00, 0x1e, 0x30, 0xd4, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETCLOCK, 0x08, 0x13, 0x07, 0x00, 0x0f, + 0x16); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPANEL, 0x02, 0x03, 0x44); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xc4); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETCASCADE, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPCTRL, 0x37, 0x06, 0x00, 0x02, 0x04, + 0x2c, 0xff); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP0, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x3b, 0x03, 0x73, 0x3b, 0x21, 0x21, 0x03, + 0x03, 0x98, 0x10, 0x1d, 0x00, 0x1d, 0x32, 0x17, 0xa1, 0x07, + 0xa1, 0x43, 0x17, 0xa6, 0x07, 0xa6, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP1, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x40, 0x40, 0x18, 0x18, 0x18, 0x18, 0x2a, 0x2b, 0x1f, 0x1f, + 0x1e, 0x1e, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x0a, 0x0b, 0x20, 0x21, 0x18, 0x18, 0x18, 0x18); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP3, 0x02, 0xaa, 0xea, 0xaa, 0xaa, 0x00, + 0x02, 0xaa, 0xea, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETTP1, 0x07, 0x10, 0x10, 0x2a, 0x32, 0x9f, + 0x01, 0x5a, 0x91, 0x14, 0x14, 0x00, 0x00, 0x00, 0x00, 0x12, + 0x05, 0x02, 0x02, 0x10, 0x33, 0x02, 0x04, 0x18, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETBANK, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPOWER, 0x01, 0x7f, 0x11, 0xfd); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETCLOCK, 0x86); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_UNKNOWN_D2, 0x3d); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xc5); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP0, 0x00, 0x00, 0x00, 0x80, 0x80, 0x0c, + 0xa1); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP3, 0x03, 0xff, 0xff, 0xff, 0xff, 0x00, + 0x03, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETTP1, 0x02, 0x00, 0x2d, 0x01, 0x7f, 0x0f, + 0x7c, 0x10, 0xa0, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETBANK, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETPTBA, 0xf2); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETCLOCK, 0x02, 0x00, 0x00, 0x10, 0x58); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_UNKNOWN_D2, 0x0a, 0x0a, 0x05, 0x03, 0x0a, + 0x0a, 0x01, 0x03, 0x01, 0x01, 0x05, 0x0e); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xcc); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP0, 0x03, 0x1f, 0xe0, 0x11, 0x70); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP3, 0xab, 0xff, 0xff, 0xff, 0xff, 0xa0, + 0xab, 0xff, 0xff, 0xff, 0xff, 0xa0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETTP1, 0xfe, 0x01, 0xfe, 0x01, 0xfe, 0x01, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x81, 0x02, 0x40, 0x00, + 0x20, 0x9e, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETBANK, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xc6); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETCYC, 0x03, 0xff, 0xf8); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETGIP3, 0xaa, 0xab, 0xea, 0xaa, 0xaa, 0xa0, + 0xaa, 0xab, 0xea, 0xaa, 0xaa, 0xa0, 0xaa, 0xbf, 0xff, 0xff, + 0xfe, 0xa0, 0xaa, 0xbf, 0xff, 0xff, 0xfe, 0xa0, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xa0, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_UNKNOWN_E1, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETBANK, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xc4); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETMIPI, 0x96); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETBANK, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xc5); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETMIPI, 0x4f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETBANK, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0xcc); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETMIPI, 0x84); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETSPCCMD, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83102_SETBANK, 0x00); + hx83102_enable_extended_cmds(&dsi_ctx, false); + + mipi_dsi_msleep(&dsi_ctx, 110); + + return dsi_ctx.accum_err; +} + +static const struct drm_display_mode starry_mode = { + .clock = 162680, + .hdisplay = 1200, + .hsync_start = 1200 + 60, + .hsync_end = 1200 + 60 + 20, + .htotal = 1200 + 60 + 20 + 40, + .vdisplay = 1920, + .vsync_start = 1920 + 116, + .vsync_end = 1920 + 116 + 8, + .vtotal = 1920 + 116 + 8 + 12, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, +}; + +static const struct hx83102_panel_desc starry_desc = { + .modes = &starry_mode, + .size = { + .width_mm = 141, + .height_mm = 226, + }, + .init = starry_himax83102_j02_init, +}; + +static const struct drm_display_mode boe_tv110wum_default_mode = { + .clock = 167700, + .hdisplay = 1200, + .hsync_start = 1200 + 75, + .hsync_end = 1200 + 75 + 20, + .htotal = 1200 + 75 + 20 + 65, + .vdisplay = 1920, + .vsync_start = 1920 + 115, + .vsync_end = 1920 + 115 + 8, + .vtotal = 1920 + 115 + 8 + 12, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, +}; + +static const struct hx83102_panel_desc boe_nv110wum_desc = { + .modes = &boe_tv110wum_default_mode, + .size = { + .width_mm = 147, + .height_mm = 235, + }, + .init = boe_nv110wum_init, +}; + +static const struct drm_display_mode csot_pna957qt1_1_default_mode = { + .clock = 177958, + .hdisplay = 1200, + .hsync_start = 1200 + 124, + .hsync_end = 1200 + 124 + 80, + .htotal = 1200 + 124 + 80 + 40, + .vdisplay = 1920, + .vsync_start = 1920 + 88, + .vsync_end = 1920 + 88 + 8, + .vtotal = 1920 + 88 + 8 + 38, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, +}; + +static const struct hx83102_panel_desc csot_pna957qt1_1_desc = { + .modes = &csot_pna957qt1_1_default_mode, + .size = { + .width_mm = 147, + .height_mm = 235, + }, + .init = csot_pna957qt1_1_init, +}; + +static const struct drm_display_mode ivo_t109nw41_default_mode = { + .clock = 167700, + .hdisplay = 1200, + .hsync_start = 1200 + 75, + .hsync_end = 1200 + 75 + 20, + .htotal = 1200 + 75 + 20 + 65, + .vdisplay = 1920, + .vsync_start = 1920 + 115, + .vsync_end = 1920 + 115 + 8, + .vtotal = 1920 + 115 + 8 + 12, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, +}; + +static const struct hx83102_panel_desc ivo_t109nw41_desc = { + .modes = &ivo_t109nw41_default_mode, + .size = { + .width_mm = 147, + .height_mm = 235, + }, + .init = ivo_t109nw41_init, +}; + +static const struct drm_display_mode kingdisplay_kd110n11_51ie_default_mode = { + .clock = 182750, + .hdisplay = 1200, + .hsync_start = 1200 + 124, + .hsync_end = 1200 + 124 + 80, + .htotal = 1200 + 124 + 80 + 80, + .vdisplay = 1920, + .vsync_start = 1920 + 88, + .vsync_end = 1920 + 88 + 8, + .vtotal = 1920 + 88 + 8 + 38, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, +}; + +static const struct hx83102_panel_desc kingdisplay_kd110n11_51ie_desc = { + .modes = &kingdisplay_kd110n11_51ie_default_mode, + .size = { + .width_mm = 147, + .height_mm = 235, + }, + .init = kingdisplay_kd110n11_51ie_init, +}; + +static const struct drm_display_mode starry_2082109qfh040022_50e_default_mode = { + .clock = 192050, + .hdisplay = 1200, + .hsync_start = 1200 + 160, + .hsync_end = 1200 + 160 + 66, + .htotal = 1200 + 160 + 66 + 120, + .vdisplay = 1920, + .vsync_start = 1920 + 115, + .vsync_end = 1920 + 115 + 8, + .vtotal = 1920 + 115 + 8 + 28, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, +}; + +static const struct hx83102_panel_desc starry_2082109qfh040022_50e_desc = { + .modes = &starry_2082109qfh040022_50e_default_mode, + .size = { + .width_mm = 147, + .height_mm = 235, + }, + .init = starry_2082109qfh040022_50e_init, +}; + +static int hx83102_enable(struct drm_panel *panel) +{ + msleep(130); + return 0; +} + +static int hx83102_disable(struct drm_panel *panel) +{ + struct hx83102 *ctx = panel_to_hx83102(panel); + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + + mipi_dsi_msleep(&dsi_ctx, 150); + + return dsi_ctx.accum_err; +} + +static int hx83102_unprepare(struct drm_panel *panel) +{ + struct hx83102 *ctx = panel_to_hx83102(panel); + + gpiod_set_value(ctx->enable_gpio, 0); + usleep_range(1000, 2000); + regulator_disable(ctx->avee); + regulator_disable(ctx->avdd); + usleep_range(5000, 7000); + regulator_disable(ctx->pp1800); + + return 0; +} + +static int hx83102_prepare(struct drm_panel *panel) +{ + struct hx83102 *ctx = panel_to_hx83102(panel); + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + gpiod_set_value(ctx->enable_gpio, 0); + usleep_range(1000, 1500); + + dsi_ctx.accum_err = regulator_enable(ctx->pp1800); + if (dsi_ctx.accum_err) + return dsi_ctx.accum_err; + + usleep_range(3000, 5000); + + dsi_ctx.accum_err = regulator_enable(ctx->avdd); + if (dsi_ctx.accum_err) + goto poweroff1v8; + dsi_ctx.accum_err = regulator_enable(ctx->avee); + if (dsi_ctx.accum_err) + goto poweroffavdd; + + usleep_range(10000, 11000); + + mipi_dsi_dcs_nop_multi(&dsi_ctx); + if (dsi_ctx.accum_err) + goto poweroff; + + usleep_range(1000, 2000); + + gpiod_set_value(ctx->enable_gpio, 1); + usleep_range(1000, 2000); + gpiod_set_value(ctx->enable_gpio, 0); + usleep_range(1000, 2000); + gpiod_set_value(ctx->enable_gpio, 1); + usleep_range(6000, 10000); + + dsi_ctx.accum_err = ctx->desc->init(ctx); + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 120); + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + if (dsi_ctx.accum_err) + goto poweroff; + + return 0; + +poweroff: + gpiod_set_value(ctx->enable_gpio, 0); + regulator_disable(ctx->avee); +poweroffavdd: + regulator_disable(ctx->avdd); +poweroff1v8: + usleep_range(5000, 7000); + regulator_disable(ctx->pp1800); + + return dsi_ctx.accum_err; +} + +static int hx83102_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct hx83102 *ctx = panel_to_hx83102(panel); + const struct drm_display_mode *m = ctx->desc->modes; + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, m); + if (!mode) + return -ENOMEM; + + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + + connector->display_info.width_mm = ctx->desc->size.width_mm; + connector->display_info.height_mm = ctx->desc->size.height_mm; + connector->display_info.bpc = 8; + + return 1; +} + +static enum drm_panel_orientation hx83102_get_orientation(struct drm_panel *panel) +{ + struct hx83102 *ctx = panel_to_hx83102(panel); + + return ctx->orientation; +} + +static const struct drm_panel_funcs hx83102_drm_funcs = { + .disable = hx83102_disable, + .unprepare = hx83102_unprepare, + .prepare = hx83102_prepare, + .enable = hx83102_enable, + .get_modes = hx83102_get_modes, + .get_orientation = hx83102_get_orientation, +}; + +static int hx83102_panel_add(struct hx83102 *ctx) +{ + struct device *dev = &ctx->dsi->dev; + int err; + + ctx->avdd = devm_regulator_get(dev, "avdd"); + if (IS_ERR(ctx->avdd)) + return PTR_ERR(ctx->avdd); + + ctx->avee = devm_regulator_get(dev, "avee"); + if (IS_ERR(ctx->avee)) + return PTR_ERR(ctx->avee); + + ctx->pp1800 = devm_regulator_get(dev, "pp1800"); + if (IS_ERR(ctx->pp1800)) + return PTR_ERR(ctx->pp1800); + + ctx->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(ctx->enable_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->enable_gpio), "Cannot get enable GPIO\n"); + + ctx->base.prepare_prev_first = true; + + err = of_drm_get_panel_orientation(dev->of_node, &ctx->orientation); + if (err < 0) + return dev_err_probe(dev, err, "failed to get orientation\n"); + + err = drm_panel_of_backlight(&ctx->base); + if (err) + return err; + + ctx->base.funcs = &hx83102_drm_funcs; + ctx->base.dev = &ctx->dsi->dev; + + drm_panel_add(&ctx->base); + + return 0; +} + +static int hx83102_probe(struct mipi_dsi_device *dsi) +{ + struct hx83102 *ctx; + int ret; + const struct hx83102_panel_desc *desc; + + ctx = devm_drm_panel_alloc(&dsi->dev, __typeof(*ctx), base, + &hx83102_drm_funcs, DRM_MODE_CONNECTOR_DSI); + + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + desc = of_device_get_match_data(&dsi->dev); + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_MODE_LPM; + ctx->desc = desc; + ctx->dsi = dsi; + ret = hx83102_panel_add(ctx); + if (ret < 0) + return ret; + + mipi_dsi_set_drvdata(dsi, ctx); + + ret = mipi_dsi_attach(dsi); + if (ret) + drm_panel_remove(&ctx->base); + + return ret; +} + +static void hx83102_remove(struct mipi_dsi_device *dsi) +{ + struct hx83102 *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", ret); + + if (ctx->base.dev) + drm_panel_remove(&ctx->base); +} + +static const struct of_device_id hx83102_of_match[] = { + { .compatible = "boe,nv110wum-l60", + .data = &boe_nv110wum_desc + }, + { .compatible = "csot,pna957qt1-1", + .data = &csot_pna957qt1_1_desc + }, + { .compatible = "ivo,t109nw41", + .data = &ivo_t109nw41_desc + }, + { .compatible = "kingdisplay,kd110n11-51ie", + .data = &kingdisplay_kd110n11_51ie_desc + }, + { .compatible = "starry,2082109qfh040022-50e", + .data = &starry_2082109qfh040022_50e_desc + }, + { .compatible = "starry,himax83102-j02", + .data = &starry_desc + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, hx83102_of_match); + +static struct mipi_dsi_driver hx83102_driver = { + .probe = hx83102_probe, + .remove = hx83102_remove, + .driver = { + .name = "panel-himax-hx83102", + .of_match_table = hx83102_of_match, + }, +}; +module_mipi_dsi_driver(hx83102_driver); + +MODULE_AUTHOR("Cong Yang <yangcong5@huaqin.corp-partner.google.com>"); +MODULE_DESCRIPTION("DRM driver for Himax HX83102 based MIPI DSI panels"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-himax-hx83112a.c b/drivers/gpu/drm/panel/panel-himax-hx83112a.c new file mode 100644 index 000000000000..142cb1cc067a --- /dev/null +++ b/drivers/gpu/drm/panel/panel-himax-hx83112a.c @@ -0,0 +1,347 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Generated with linux-mdss-dsi-panel-driver-generator from vendor device tree. + * Copyright (c) 2024 Luca Weiss <luca.weiss@fairphone.com> + */ + +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regulator/consumer.h> + +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> +#include <drm/drm_probe_helper.h> + +/* Manufacturer specific DSI commands */ +#define HX83112A_SETPOWER1 0xb1 +#define HX83112A_SETDISP 0xb2 +#define HX83112A_SETDRV 0xb4 +#define HX83112A_SETEXTC 0xb9 +#define HX83112A_SETBANK 0xbd +#define HX83112A_SETPTBA 0xbf +#define HX83112A_SETDGCLUT 0xc1 +#define HX83112A_SETTCON 0xc7 +#define HX83112A_SETCLOCK 0xcb +#define HX83112A_SETPANEL 0xcc +#define HX83112A_SETPOWER2 0xd2 +#define HX83112A_SETGIP0 0xd3 +#define HX83112A_SETGIP1 0xd5 +#define HX83112A_SETGIP2 0xd6 +#define HX83112A_SETGIP3 0xd8 +#define HX83112A_SETTP1 0xe7 +#define HX83112A_UNKNOWN1 0xe9 + +struct hx83112a_panel { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct regulator_bulk_data supplies[3]; + struct gpio_desc *reset_gpio; +}; + +static inline struct hx83112a_panel *to_hx83112a_panel(struct drm_panel *panel) +{ + return container_of(panel, struct hx83112a_panel, panel); +} + +static void hx83112a_reset(struct hx83112a_panel *ctx) +{ + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + msleep(20); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + msleep(20); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + msleep(50); +} + +static int hx83112a_on(struct mipi_dsi_device *dsi) +{ + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETEXTC, 0x83, 0x11, 0x2a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETPOWER1, + 0x08, 0x28, 0x28, 0x83, 0x83, 0x4c, 0x4f, 0x33); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETDISP, + 0x00, 0x02, 0x00, 0x90, 0x24, 0x00, 0x08, 0x19, + 0xea, 0x11, 0x11, 0x00, 0x11, 0xa3); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETDRV, + 0x58, 0x68, 0x58, 0x68, 0x0f, 0xef, 0x0b, 0xc0, + 0x0b, 0xc0, 0x0b, 0xc0, 0x00, 0xff, 0x00, 0xff, + 0x00, 0x00, 0x14, 0x15, 0x00, 0x29, 0x11, 0x07, + 0x12, 0x00, 0x29); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETBANK, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETDRV, + 0x00, 0x12, 0x12, 0x11, 0x88, 0x12, 0x12, 0x00, + 0x53); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETBANK, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETBANK, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETDGCLUT, + 0xff, 0xfe, 0xfb, 0xf8, 0xf4, 0xf1, 0xed, 0xe6, + 0xe2, 0xde, 0xdb, 0xd6, 0xd3, 0xcf, 0xca, 0xc6, + 0xc2, 0xbe, 0xb9, 0xb0, 0xa7, 0x9e, 0x96, 0x8d, + 0x84, 0x7c, 0x74, 0x6b, 0x62, 0x5a, 0x51, 0x49, + 0x41, 0x39, 0x31, 0x29, 0x21, 0x19, 0x12, 0x0a, + 0x06, 0x05, 0x02, 0x01, 0x00, 0x00, 0xc9, 0xb3, + 0x08, 0x0e, 0xf2, 0xe1, 0x59, 0xf4, 0x22, 0xad, + 0x40); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETBANK, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETDGCLUT, + 0xff, 0xfe, 0xfb, 0xf8, 0xf4, 0xf1, 0xed, 0xe6, + 0xe2, 0xde, 0xdb, 0xd6, 0xd3, 0xcf, 0xca, 0xc6, + 0xc2, 0xbe, 0xb9, 0xb0, 0xa7, 0x9e, 0x96, 0x8d, + 0x84, 0x7c, 0x74, 0x6b, 0x62, 0x5a, 0x51, 0x49, + 0x41, 0x39, 0x31, 0x29, 0x21, 0x19, 0x12, 0x0a, + 0x06, 0x05, 0x02, 0x01, 0x00, 0x00, 0xc9, 0xb3, + 0x08, 0x0e, 0xf2, 0xe1, 0x59, 0xf4, 0x22, 0xad, + 0x40); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETBANK, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETDGCLUT, + 0xff, 0xfe, 0xfb, 0xf8, 0xf4, 0xf1, 0xed, 0xe6, + 0xe2, 0xde, 0xdb, 0xd6, 0xd3, 0xcf, 0xca, 0xc6, + 0xc2, 0xbe, 0xb9, 0xb0, 0xa7, 0x9e, 0x96, 0x8d, + 0x84, 0x7c, 0x74, 0x6b, 0x62, 0x5a, 0x51, 0x49, + 0x41, 0x39, 0x31, 0x29, 0x21, 0x19, 0x12, 0x0a, + 0x06, 0x05, 0x02, 0x01, 0x00, 0x00, 0xc9, 0xb3, + 0x08, 0x0e, 0xf2, 0xe1, 0x59, 0xf4, 0x22, 0xad, + 0x40); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETBANK, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETDGCLUT, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETTCON, + 0x70, 0x00, 0x04, 0xe0, 0x33, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETPANEL, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETPOWER2, 0x2b, 0x2b); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETGIP0, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, + 0x08, 0x03, 0x03, 0x22, 0x18, 0x07, 0x07, 0x07, + 0x07, 0x32, 0x10, 0x06, 0x00, 0x06, 0x32, 0x10, + 0x07, 0x00, 0x07, 0x32, 0x19, 0x31, 0x09, 0x31, + 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x08, + 0x09, 0x30, 0x00, 0x00, 0x00, 0x06, 0x0d, 0x00, + 0x0f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETBANK, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETGIP0, + 0x00, 0x00, 0x19, 0x10, 0x00, 0x0a, 0x00, 0x81); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETBANK, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETGIP1, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0xc0, 0xc0, 0x18, 0x18, 0x19, 0x19, 0x18, 0x18, + 0x40, 0x40, 0x18, 0x18, 0x18, 0x18, 0x3f, 0x3f, + 0x28, 0x28, 0x24, 0x24, 0x02, 0x03, 0x02, 0x03, + 0x00, 0x01, 0x00, 0x01, 0x31, 0x31, 0x31, 0x31, + 0x30, 0x30, 0x30, 0x30, 0x2f, 0x2f, 0x2f, 0x2f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETGIP2, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x40, 0x40, 0x18, 0x18, 0x18, 0x18, 0x19, 0x19, + 0x40, 0x40, 0x18, 0x18, 0x18, 0x18, 0x3f, 0x3f, + 0x24, 0x24, 0x28, 0x28, 0x01, 0x00, 0x01, 0x00, + 0x03, 0x02, 0x03, 0x02, 0x31, 0x31, 0x31, 0x31, + 0x30, 0x30, 0x30, 0x30, 0x2f, 0x2f, 0x2f, 0x2f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETGIP3, + 0xaa, 0xea, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xea, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xea, 0xab, 0xaa, + 0xaa, 0xaa, 0xaa, 0xea, 0xab, 0xaa, 0xaa, 0xaa); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETBANK, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETGIP3, + 0xaa, 0x2e, 0x28, 0x00, 0x00, 0x00, 0xaa, 0x2e, + 0x28, 0x00, 0x00, 0x00, 0xaa, 0xee, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xee, 0xaa, 0xaa, 0xaa, 0xaa); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETBANK, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETGIP3, + 0xaa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xaa, 0xff, + 0xff, 0xff, 0xff, 0xff); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETBANK, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETGIP3, + 0xaa, 0xaa, 0xea, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xea, 0xaa, 0xaa, 0xaa, 0xaa, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xaa, 0xff, 0xff, 0xff, 0xff, 0xff); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETBANK, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETTP1, + 0x0e, 0x0e, 0x1e, 0x65, 0x1c, 0x65, 0x00, 0x50, + 0x20, 0x20, 0x00, 0x00, 0x02, 0x02, 0x02, 0x05, + 0x14, 0x14, 0x32, 0xb9, 0x23, 0xb9, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETBANK, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETTP1, + 0x02, 0x00, 0xa8, 0x01, 0xa8, 0x0d, 0xa4, 0x0e); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETBANK, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETTP1, + 0x00, 0x00, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETBANK, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_UNKNOWN1, 0xc3); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETCLOCK, 0xd1, 0xd6); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_UNKNOWN1, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_UNKNOWN1, 0xc6); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_SETPTBA, 0x37); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112A_UNKNOWN1, 0x3f); + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 150); + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 50); + + return dsi_ctx.accum_err; +} + +static int hx83112a_disable(struct drm_panel *panel) +{ + struct hx83112a_panel *ctx = to_hx83112a_panel(panel); + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 20); + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 120); + + return dsi_ctx.accum_err; +} + +static int hx83112a_prepare(struct drm_panel *panel) +{ + struct hx83112a_panel *ctx = to_hx83112a_panel(panel); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + if (ret < 0) + return ret; + + hx83112a_reset(ctx); + + ret = hx83112a_on(ctx->dsi); + if (ret < 0) { + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + } + + return ret; +} + +static int hx83112a_unprepare(struct drm_panel *panel) +{ + struct hx83112a_panel *ctx = to_hx83112a_panel(panel); + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + + return 0; +} + +static const struct drm_display_mode hx83112a_mode = { + .clock = (1080 + 28 + 8 + 8) * (2340 + 27 + 5 + 5) * 60 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 28, + .hsync_end = 1080 + 28 + 8, + .htotal = 1080 + 28 + 8 + 8, + .vdisplay = 2340, + .vsync_start = 2340 + 27, + .vsync_end = 2340 + 27 + 5, + .vtotal = 2340 + 27 + 5 + 5, + .width_mm = 67, + .height_mm = 145, + .type = DRM_MODE_TYPE_DRIVER, +}; + +static int hx83112a_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + return drm_connector_helper_get_modes_fixed(connector, &hx83112a_mode); +} + +static const struct drm_panel_funcs hx83112a_panel_funcs = { + .prepare = hx83112a_prepare, + .unprepare = hx83112a_unprepare, + .disable = hx83112a_disable, + .get_modes = hx83112a_get_modes, +}; + +static int hx83112a_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct hx83112a_panel *ctx; + int ret; + + ctx = devm_drm_panel_alloc(dev, struct hx83112a_panel, panel, + &hx83112a_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ctx->supplies[0].supply = "vdd1"; + ctx->supplies[1].supply = "vsn"; + ctx->supplies[2].supply = "vsp"; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), + ctx->supplies); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get regulators\n"); + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), + "Failed to get reset-gpios\n"); + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_VIDEO_HSE | + MIPI_DSI_CLOCK_NON_CONTINUOUS; + + ctx->panel.prepare_prev_first = true; + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return dev_err_probe(dev, ret, "Failed to get backlight\n"); + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err_probe(dev, ret, "Failed to attach to DSI host\n"); + drm_panel_remove(&ctx->panel); + return ret; + } + + return 0; +} + +static void hx83112a_remove(struct mipi_dsi_device *dsi) +{ + struct hx83112a_panel *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id hx83112a_of_match[] = { + { .compatible = "djn,9a-3r063-1102b" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, hx83112a_of_match); + +static struct mipi_dsi_driver hx83112a_driver = { + .probe = hx83112a_probe, + .remove = hx83112a_remove, + .driver = { + .name = "panel-himax-hx83112a", + .of_match_table = hx83112a_of_match, + }, +}; +module_mipi_dsi_driver(hx83112a_driver); + +MODULE_DESCRIPTION("DRM driver for hx83112a-equipped DSI panels"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-himax-hx83112b.c b/drivers/gpu/drm/panel/panel-himax-hx83112b.c new file mode 100644 index 000000000000..263f79a967de --- /dev/null +++ b/drivers/gpu/drm/panel/panel-himax-hx83112b.c @@ -0,0 +1,430 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Generated with linux-mdss-dsi-panel-driver-generator from vendor device tree. + * Copyright (c) 2025 Luca Weiss <luca@lucaweiss.eu> + */ + +#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> + +#include <video/mipi_display.h> + +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> +#include <drm/drm_probe_helper.h> + +/* Manufacturer specific DSI commands */ +#define HX83112B_SETPOWER1 0xb1 +#define HX83112B_SETDISP 0xb2 +#define HX83112B_SETDRV 0xb4 +#define HX83112B_SETEXTC 0xb9 +#define HX83112B_SETBANK 0xbd +#define HX83112B_SETDGCLUT 0xc1 +#define HX83112B_SETDISMO 0xc2 +#define HX83112B_UNKNOWN1 0xc6 +#define HX83112B_SETPANEL 0xcc +#define HX83112B_UNKNOWN2 0xd1 +#define HX83112B_SETPOWER2 0xd2 +#define HX83112B_SETGIP0 0xd3 +#define HX83112B_SETGIP1 0xd5 +#define HX83112B_SETGIP2 0xd6 +#define HX83112B_SETGIP3 0xd8 +#define HX83112B_SETIDLE 0xdd +#define HX83112B_UNKNOWN3 0xe7 +#define HX83112B_UNKNOWN4 0xe9 + +struct hx83112b_panel { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct regulator_bulk_data *supplies; + struct gpio_desc *reset_gpio; +}; + +static const struct regulator_bulk_data hx83112b_supplies[] = { + { .supply = "iovcc" }, + { .supply = "vsn" }, + { .supply = "vsp" }, +}; + +static inline struct hx83112b_panel *to_hx83112b_panel(struct drm_panel *panel) +{ + return container_of(panel, struct hx83112b_panel, panel); +} + +static void hx83112b_reset(struct hx83112b_panel *ctx) +{ + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(10000, 11000); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(10000, 11000); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(10000, 11000); +} + +static int hx83112b_on(struct hx83112b_panel *ctx) +{ + struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETEXTC, 0x83, 0x11, 0x2b); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETBANK, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETDISMO, 0x08, 0x70); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETBANK, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETDISP, 0x04, 0x38, 0x08, 0x70); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETBANK, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETPOWER1, + 0xf8, 0x27, 0x27, 0x00, 0x00, 0x0b, 0x0e, + 0x0b, 0x0e, 0x33); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETPOWER2, 0x2d, 0x2d); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETDISP, + 0x80, 0x02, 0x18, 0x80, 0x70, 0x00, 0x08, + 0x1c, 0x08, 0x11, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_UNKNOWN4, 0xd1); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETDISP, 0x00, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_UNKNOWN4, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETBANK, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETDISP, 0xb5, 0x0a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETBANK, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETIDLE, + 0x00, 0x00, 0x08, 0x1c, 0x08, 0x34, 0x34, + 0x88); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETDRV, + 0x65, 0x6b, 0x00, 0x00, 0xd0, 0xd4, 0x36, + 0xcf, 0x06, 0xce, 0x00, 0xce, 0x00, 0x00, + 0x00, 0x07, 0x00, 0x2a, 0x07, 0x01, 0x07, + 0x00, 0x00, 0x2a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETBANK, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_UNKNOWN4, 0xc3); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETDRV, 0x01, 0x67, 0x2a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_UNKNOWN4, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETBANK, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETDGCLUT, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETBANK, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETDGCLUT, + 0xff, 0xfb, 0xf9, 0xf6, 0xf4, 0xf1, 0xef, + 0xea, 0xe7, 0xe5, 0xe2, 0xdf, 0xdd, 0xda, + 0xd8, 0xd5, 0xd2, 0xcf, 0xcc, 0xc5, 0xbe, + 0xb7, 0xb0, 0xa8, 0xa0, 0x98, 0x8e, 0x85, + 0x7b, 0x72, 0x69, 0x5e, 0x53, 0x48, 0x3e, + 0x35, 0x2b, 0x22, 0x17, 0x0d, 0x09, 0x07, + 0x05, 0x01, 0x00, 0x26, 0xf0, 0x86, 0x25, + 0x6e, 0xb6, 0xdd, 0xf3, 0xd8, 0xcc, 0x9b, + 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETBANK, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETDGCLUT, + 0xff, 0xfb, 0xf9, 0xf6, 0xf4, 0xf1, 0xef, + 0xea, 0xe7, 0xe5, 0xe2, 0xdf, 0xdd, 0xda, + 0xd8, 0xd5, 0xd2, 0xcf, 0xcc, 0xc5, 0xbe, + 0xb7, 0xb0, 0xa8, 0xa0, 0x98, 0x8e, 0x85, + 0x7b, 0x72, 0x69, 0x5e, 0x53, 0x48, 0x3e, + 0x35, 0x2b, 0x22, 0x17, 0x0d, 0x09, 0x07, + 0x05, 0x01, 0x00, 0x26, 0xf0, 0x86, 0x25, + 0x6e, 0xb6, 0xdd, 0xf3, 0xd8, 0xcc, 0x9b, + 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETBANK, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETDGCLUT, + 0xff, 0xfb, 0xf9, 0xf6, 0xf4, 0xf1, 0xef, + 0xea, 0xe7, 0xe5, 0xe2, 0xdf, 0xdd, 0xda, + 0xd8, 0xd5, 0xd2, 0xcf, 0xcc, 0xc5, 0xbe, + 0xb7, 0xb0, 0xa8, 0xa0, 0x98, 0x8e, 0x85, + 0x7b, 0x72, 0x69, 0x5e, 0x53, 0x48, 0x3e, + 0x35, 0x2b, 0x22, 0x17, 0x0d, 0x09, 0x07, + 0x05, 0x01, 0x00, 0x26, 0xf0, 0x86, 0x25, + 0x6e, 0xb6, 0xdd, 0xf3, 0xd8, 0xcc, 0x9b, + 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETBANK, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETDISMO, 0xc8); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETPANEL, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETGIP0, + 0x81, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x04, 0x00, 0x01, 0x13, 0x40, 0x04, 0x09, + 0x09, 0x0b, 0x0b, 0x32, 0x10, 0x08, 0x00, + 0x08, 0x32, 0x10, 0x08, 0x00, 0x08, 0x32, + 0x10, 0x08, 0x00, 0x08, 0x00, 0x00, 0x0a, + 0x08, 0x7b); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_UNKNOWN4, 0xc5); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_UNKNOWN1, 0xf7); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_UNKNOWN4, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_UNKNOWN4, 0xd4); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_UNKNOWN1, 0x6e); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_UNKNOWN4, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_UNKNOWN4, 0xef); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETGIP0, 0x0c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_UNKNOWN4, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETBANK, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_UNKNOWN4, 0xc8); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETGIP0, 0xa1); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_UNKNOWN4, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETBANK, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETGIP1, + 0x18, 0x18, 0x19, 0x18, 0x18, 0x20, 0x18, + 0x18, 0x18, 0x10, 0x10, 0x18, 0x18, 0x00, + 0x00, 0x18, 0x18, 0x01, 0x01, 0x18, 0x18, + 0x28, 0x28, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x2f, 0x2f, 0x30, 0x30, 0x31, 0x31, 0x35, + 0x35, 0x36, 0x36, 0x37, 0x37, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xfc, + 0xfc, 0x00, 0x00, 0xfc, 0xfc, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETGIP2, + 0x18, 0x18, 0x19, 0x18, 0x18, 0x20, 0x19, + 0x18, 0x18, 0x10, 0x10, 0x18, 0x18, 0x00, + 0x00, 0x18, 0x18, 0x01, 0x01, 0x18, 0x18, + 0x28, 0x28, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x2f, 0x2f, 0x30, 0x30, 0x31, 0x31, 0x35, + 0x35, 0x36, 0x36, 0x37, 0x37, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETGIP3, + 0xaa, 0xaa, 0xaa, 0xaf, 0xea, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaf, 0xea, 0xaa, 0xaa, 0xaa, + 0xab, 0xaf, 0xef, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaf, 0xea, 0xaa); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETBANK, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETGIP3, + 0xaa, 0xaa, 0xab, 0xaf, 0xea, 0xaa, 0xaa, + 0xaa, 0xae, 0xaf, 0xea, 0xaa); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETBANK, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETGIP3, + 0xaa, 0xaa, 0xaa, 0xaf, 0xea, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaf, 0xea, 0xaa); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETBANK, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETGIP3, + 0xba, 0xaa, 0xaa, 0xaf, 0xea, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaf, 0xea, 0xaa, 0xba, 0xaa, + 0xaa, 0xaf, 0xea, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaf, 0xea, 0xaa); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETBANK, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_UNKNOWN4, 0xe4); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_UNKNOWN3, 0x17, 0x69); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_UNKNOWN4, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_UNKNOWN3, + 0x09, 0x09, 0x00, 0x07, 0xe8, 0x00, 0x26, + 0x00, 0x07, 0x00, 0x00, 0xe8, 0x32, 0x00, + 0xe9, 0x0a, 0x0a, 0x00, 0x00, 0x00, 0x01, + 0x01, 0x00, 0x12, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETBANK, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_UNKNOWN3, + 0x02, 0x00, 0x01, 0x20, 0x01, 0x18, 0x08, + 0xa8, 0x09); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETBANK, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_UNKNOWN3, 0x20, 0x20, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETBANK, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_UNKNOWN3, + 0x00, 0xdc, 0x11, 0x70, 0x00, 0x20); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_UNKNOWN4, 0xc9); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_UNKNOWN3, + 0x2a, 0xce, 0x02, 0x70, 0x01, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_UNKNOWN4, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_SETBANK, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, HX83112B_UNKNOWN2, 0x27); + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 120); + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 20); + mipi_dsi_dcs_set_display_brightness_multi(&dsi_ctx, 0x0000); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY, + 0x24); + mipi_dsi_dcs_set_tear_on_multi(&dsi_ctx, MIPI_DSI_DCS_TEAR_MODE_VBLANK); + + return dsi_ctx.accum_err; +} + +static int hx83112b_off(struct hx83112b_panel *ctx) +{ + struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 20); + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 120); + + return dsi_ctx.accum_err; +} + +static int hx83112b_prepare(struct drm_panel *panel) +{ + struct hx83112b_panel *ctx = to_hx83112b_panel(panel); + struct device *dev = &ctx->dsi->dev; + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(hx83112b_supplies), ctx->supplies); + if (ret < 0) { + dev_err(dev, "Failed to enable regulators: %d\n", ret); + return ret; + } + + hx83112b_reset(ctx); + + ret = hx83112b_on(ctx); + if (ret < 0) { + dev_err(dev, "Failed to initialize panel: %d\n", ret); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(hx83112b_supplies), ctx->supplies); + return ret; + } + + return 0; +} + +static int hx83112b_unprepare(struct drm_panel *panel) +{ + struct hx83112b_panel *ctx = to_hx83112b_panel(panel); + struct device *dev = &ctx->dsi->dev; + int ret; + + ret = hx83112b_off(ctx); + if (ret < 0) + dev_err(dev, "Failed to un-initialize panel: %d\n", ret); + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(hx83112b_supplies), ctx->supplies); + + return 0; +} + +static const struct drm_display_mode hx83112b_mode = { + .clock = (1080 + 40 + 4 + 12) * (2160 + 32 + 2 + 2) * 60 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 40, + .hsync_end = 1080 + 40 + 4, + .htotal = 1080 + 40 + 4 + 12, + .vdisplay = 2160, + .vsync_start = 2160 + 32, + .vsync_end = 2160 + 32 + 2, + .vtotal = 2160 + 32 + 2 + 2, + .width_mm = 65, + .height_mm = 128, + .type = DRM_MODE_TYPE_DRIVER, +}; + +static int hx83112b_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + return drm_connector_helper_get_modes_fixed(connector, &hx83112b_mode); +} + +static const struct drm_panel_funcs hx83112b_panel_funcs = { + .prepare = hx83112b_prepare, + .unprepare = hx83112b_unprepare, + .get_modes = hx83112b_get_modes, +}; + +static int hx83112b_bl_update_status(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + u16 brightness = backlight_get_brightness(bl); + int ret; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + ret = mipi_dsi_dcs_set_display_brightness_large(dsi, brightness); + if (ret < 0) + return ret; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + return 0; +} + +static const struct backlight_ops hx83112b_bl_ops = { + .update_status = hx83112b_bl_update_status, +}; + +static struct backlight_device * +hx83112b_create_backlight(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + const struct backlight_properties props = { + .type = BACKLIGHT_RAW, + .brightness = 4095, + .max_brightness = 4095, + }; + + return devm_backlight_device_register(dev, dev_name(dev), dev, dsi, + &hx83112b_bl_ops, &props); +} + +static int hx83112b_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct hx83112b_panel *ctx; + int ret; + + ctx = devm_drm_panel_alloc(dev, struct hx83112b_panel, panel, + &hx83112b_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ret = devm_regulator_bulk_get_const(dev, + ARRAY_SIZE(hx83112b_supplies), + hx83112b_supplies, + &ctx->supplies); + if (ret < 0) + return ret; + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), + "Failed to get reset-gpios\n"); + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_CLOCK_NON_CONTINUOUS | + MIPI_DSI_MODE_VIDEO_NO_HSA | MIPI_DSI_MODE_LPM; + + ctx->panel.prepare_prev_first = true; + + ctx->panel.backlight = hx83112b_create_backlight(dsi); + if (IS_ERR(ctx->panel.backlight)) + return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight), + "Failed to create backlight\n"); + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + drm_panel_remove(&ctx->panel); + return dev_err_probe(dev, ret, "Failed to attach to DSI host\n"); + } + + return 0; +} + +static void hx83112b_remove(struct mipi_dsi_device *dsi) +{ + struct hx83112b_panel *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id hx83112b_of_match[] = { + { .compatible = "djn,98-03057-6598b-i" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, hx83112b_of_match); + +static struct mipi_dsi_driver hx83112b_driver = { + .probe = hx83112b_probe, + .remove = hx83112b_remove, + .driver = { + .name = "panel-himax-hx83112b", + .of_match_table = hx83112b_of_match, + }, +}; +module_mipi_dsi_driver(hx83112b_driver); + +MODULE_DESCRIPTION("DRM driver for hx83112b-equipped DSI panels"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-himax-hx8394.c b/drivers/gpu/drm/panel/panel-himax-hx8394.c new file mode 100644 index 000000000000..c4d3e09a228d --- /dev/null +++ b/drivers/gpu/drm/panel/panel-himax-hx8394.c @@ -0,0 +1,845 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for panels based on Himax HX8394 controller, such as: + * + * - HannStar HSD060BHW4 5.99" MIPI-DSI panel + * + * Copyright (C) 2021 Kamil TrzciÅ„ski + * + * Based on drivers/gpu/drm/panel/panel-sitronix-st7703.c + * Copyright (C) Purism SPC 2019 + */ + +#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/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> + +#define DRV_NAME "panel-himax-hx8394" + +/* Manufacturer specific commands sent via DSI, listed in HX8394-F datasheet */ +#define HX8394_CMD_SETSEQUENCE 0xb0 +#define HX8394_CMD_SETPOWER 0xb1 +#define HX8394_CMD_SETDISP 0xb2 +#define HX8394_CMD_SETCYC 0xb4 +#define HX8394_CMD_SETVCOM 0xb6 +#define HX8394_CMD_SETTE 0xb7 +#define HX8394_CMD_SETSENSOR 0xb8 +#define HX8394_CMD_SETEXTC 0xb9 +#define HX8394_CMD_SETMIPI 0xba +#define HX8394_CMD_SETOTP 0xbb +#define HX8394_CMD_SETREGBANK 0xbd +#define HX8394_CMD_UNKNOWN5 0xbf +#define HX8394_CMD_UNKNOWN1 0xc0 +#define HX8394_CMD_SETDGCLUT 0xc1 +#define HX8394_CMD_SETID 0xc3 +#define HX8394_CMD_SETDDB 0xc4 +#define HX8394_CMD_UNKNOWN2 0xc6 +#define HX8394_CMD_SETCABC 0xc9 +#define HX8394_CMD_SETCABCGAIN 0xca +#define HX8394_CMD_SETPANEL 0xcc +#define HX8394_CMD_SETOFFSET 0xd2 +#define HX8394_CMD_SETGIP0 0xd3 +#define HX8394_CMD_UNKNOWN3 0xd4 +#define HX8394_CMD_SETGIP1 0xd5 +#define HX8394_CMD_SETGIP2 0xd6 +#define HX8394_CMD_SETGPO 0xd6 +#define HX8394_CMD_UNKNOWN4 0xd8 +#define HX8394_CMD_SETSCALING 0xdd +#define HX8394_CMD_SETIDLE 0xdf +#define HX8394_CMD_SETGAMMA 0xe0 +#define HX8394_CMD_SETCHEMODE_DYN 0xe4 +#define HX8394_CMD_SETCHE 0xe5 +#define HX8394_CMD_SETCESEL 0xe6 +#define HX8394_CMD_SET_SP_CMD 0xe9 +#define HX8394_CMD_SETREADINDEX 0xfe +#define HX8394_CMD_GETSPIREAD 0xff + +struct hx8394 { + struct device *dev; + struct drm_panel panel; + struct gpio_desc *reset_gpio; + struct regulator *vcc; + struct regulator *iovcc; + enum drm_panel_orientation orientation; + + const struct hx8394_panel_desc *desc; +}; + +struct hx8394_panel_desc { + const struct drm_display_mode *mode; + unsigned int lanes; + unsigned long mode_flags; + enum mipi_dsi_pixel_format format; + void (*init_sequence)(struct mipi_dsi_multi_context *dsi_ctx); +}; + +static inline struct hx8394 *panel_to_hx8394(struct drm_panel *panel) +{ + return container_of(panel, struct hx8394, panel); +} + +static void hsd060bhw4_init_sequence(struct mipi_dsi_multi_context *dsi_ctx) +{ + /* 5.19.8 SETEXTC: Set extension command (B9h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETEXTC, + 0xff, 0x83, 0x94); + + /* 5.19.2 SETPOWER: Set power (B1h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETPOWER, + 0x48, 0x11, 0x71, 0x09, 0x32, 0x24, 0x71, 0x31, 0x55, 0x30); + + /* 5.19.9 SETMIPI: Set MIPI control (BAh) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETMIPI, + 0x63, 0x03, 0x68, 0x6b, 0xb2, 0xc0); + + /* 5.19.3 SETDISP: Set display related register (B2h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETDISP, + 0x00, 0x80, 0x78, 0x0c, 0x07); + + /* 5.19.4 SETCYC: Set display waveform cycles (B4h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETCYC, + 0x12, 0x63, 0x12, 0x63, 0x12, 0x63, 0x01, 0x0c, 0x7c, 0x55, + 0x00, 0x3f, 0x12, 0x6b, 0x12, 0x6b, 0x12, 0x6b, 0x01, 0x0c, + 0x7c); + + /* 5.19.19 SETGIP0: Set GIP Option0 (D3h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETGIP0, + 0x00, 0x00, 0x00, 0x00, 0x3c, 0x1c, 0x00, 0x00, 0x32, 0x10, + 0x09, 0x00, 0x09, 0x32, 0x15, 0xad, 0x05, 0xad, 0x32, 0x00, + 0x00, 0x00, 0x00, 0x37, 0x03, 0x0b, 0x0b, 0x37, 0x00, 0x00, + 0x00, 0x0c, 0x40); + + /* 5.19.20 Set GIP Option1 (D5h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETGIP1, + 0x19, 0x19, 0x18, 0x18, 0x1b, 0x1b, 0x1a, 0x1a, 0x00, 0x01, + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x20, 0x21, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x24, 0x25, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18); + + /* 5.19.21 Set GIP Option2 (D6h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETGIP2, + 0x18, 0x18, 0x19, 0x19, 0x1b, 0x1b, 0x1a, 0x1a, 0x07, 0x06, + 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, 0x25, 0x24, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x21, 0x20, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18); + + /* 5.19.25 SETGAMMA: Set gamma curve related setting (E0h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETGAMMA, + 0x00, 0x04, 0x0c, 0x12, 0x14, 0x18, 0x1a, 0x18, 0x31, 0x3f, + 0x4d, 0x4c, 0x54, 0x65, 0x6b, 0x70, 0x7f, 0x82, 0x7e, 0x8a, + 0x99, 0x4a, 0x48, 0x49, 0x4b, 0x4a, 0x4c, 0x4b, 0x7f, 0x00, + 0x04, 0x0c, 0x11, 0x13, 0x17, 0x1a, 0x18, 0x31, + 0x3f, 0x4d, 0x4c, 0x54, 0x65, 0x6b, 0x70, 0x7f, + 0x82, 0x7e, 0x8a, 0x99, 0x4a, 0x48, 0x49, 0x4b, + 0x4a, 0x4c, 0x4b, 0x7f); + + /* 5.19.17 SETPANEL (CCh) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETPANEL, + 0x0b); + + /* Unknown command, not listed in the HX8394-F datasheet */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_UNKNOWN1, + 0x1f, 0x31); + + /* 5.19.5 SETVCOM: Set VCOM voltage (B6h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETVCOM, + 0x7d, 0x7d); + + /* Unknown command, not listed in the HX8394-F datasheet */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_UNKNOWN3, + 0x02); + + /* 5.19.11 Set register bank (BDh) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETREGBANK, + 0x01); + + /* 5.19.2 SETPOWER: Set power (B1h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETPOWER, + 0x00); + + /* 5.19.11 Set register bank (BDh) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETREGBANK, + 0x00); + + /* Unknown command, not listed in the HX8394-F datasheet */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_UNKNOWN3, + 0xed); +} + +static const struct drm_display_mode hsd060bhw4_mode = { + .hdisplay = 720, + .hsync_start = 720 + 40, + .hsync_end = 720 + 40 + 46, + .htotal = 720 + 40 + 46 + 40, + .vdisplay = 1440, + .vsync_start = 1440 + 9, + .vsync_end = 1440 + 9 + 7, + .vtotal = 1440 + 9 + 7 + 7, + .clock = 74250, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + .width_mm = 68, + .height_mm = 136, +}; + +static const struct hx8394_panel_desc hsd060bhw4_desc = { + .mode = &hsd060bhw4_mode, + .lanes = 4, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST, + .format = MIPI_DSI_FMT_RGB888, + .init_sequence = hsd060bhw4_init_sequence, +}; + +static void powkiddy_x55_init_sequence(struct mipi_dsi_multi_context *dsi_ctx) +{ + /* 5.19.8 SETEXTC: Set extension command (B9h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETEXTC, + 0xff, 0x83, 0x94); + + /* 5.19.9 SETMIPI: Set MIPI control (BAh) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETMIPI, + 0x63, 0x03, 0x68, 0x6b, 0xb2, 0xc0); + + /* 5.19.2 SETPOWER: Set power (B1h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETPOWER, + 0x48, 0x12, 0x72, 0x09, 0x32, 0x54, 0x71, 0x71, 0x57, 0x47); + + /* 5.19.3 SETDISP: Set display related register (B2h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETDISP, + 0x00, 0x80, 0x64, 0x2c, 0x16, 0x2f); + + /* 5.19.4 SETCYC: Set display waveform cycles (B4h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETCYC, + 0x73, 0x74, 0x73, 0x74, 0x73, 0x74, 0x01, 0x0c, 0x86, 0x75, + 0x00, 0x3f, 0x73, 0x74, 0x73, 0x74, 0x73, 0x74, 0x01, 0x0c, + 0x86); + + /* 5.19.5 SETVCOM: Set VCOM voltage (B6h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETVCOM, + 0x6e, 0x6e); + + /* 5.19.19 SETGIP0: Set GIP Option0 (D3h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETGIP0, + 0x00, 0x00, 0x07, 0x07, 0x40, 0x07, 0x0c, 0x00, 0x08, 0x10, + 0x08, 0x00, 0x08, 0x54, 0x15, 0x0a, 0x05, 0x0a, 0x02, 0x15, + 0x06, 0x05, 0x06, 0x47, 0x44, 0x0a, 0x0a, 0x4b, 0x10, 0x07, + 0x07, 0x0c, 0x40); + + /* 5.19.20 Set GIP Option1 (D5h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETGIP1, + 0x1c, 0x1c, 0x1d, 0x1d, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x24, 0x25, 0x18, 0x18, + 0x26, 0x27, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x20, 0x21, + 0x18, 0x18, 0x18, 0x18); + + /* 5.19.21 Set GIP Option2 (D6h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETGIP2, + 0x1c, 0x1c, 0x1d, 0x1d, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, + 0x01, 0x00, 0x0b, 0x0a, 0x09, 0x08, 0x21, 0x20, 0x18, 0x18, + 0x27, 0x26, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x25, 0x24, + 0x18, 0x18, 0x18, 0x18); + + /* 5.19.25 SETGAMMA: Set gamma curve related setting (E0h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETGAMMA, + 0x00, 0x0a, 0x15, 0x1b, 0x1e, 0x21, 0x24, 0x22, 0x47, 0x56, + 0x65, 0x66, 0x6e, 0x82, 0x88, 0x8b, 0x9a, 0x9d, 0x98, 0xa8, + 0xb9, 0x5d, 0x5c, 0x61, 0x66, 0x6a, 0x6f, 0x7f, 0x7f, 0x00, + 0x0a, 0x15, 0x1b, 0x1e, 0x21, 0x24, 0x22, 0x47, 0x56, 0x65, + 0x65, 0x6e, 0x81, 0x87, 0x8b, 0x98, 0x9d, 0x99, 0xa8, 0xba, + 0x5d, 0x5d, 0x62, 0x67, 0x6b, 0x72, 0x7f, 0x7f); + + /* Unknown command, not listed in the HX8394-F datasheet */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_UNKNOWN1, + 0x1f, 0x31); + + /* 5.19.17 SETPANEL (CCh) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETPANEL, + 0x0b); + + /* Unknown command, not listed in the HX8394-F datasheet */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_UNKNOWN3, + 0x02); + + /* 5.19.11 Set register bank (BDh) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETREGBANK, + 0x02); + + /* Unknown command, not listed in the HX8394-F datasheet */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_UNKNOWN4, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff); + + /* 5.19.11 Set register bank (BDh) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETREGBANK, + 0x00); + + /* 5.19.11 Set register bank (BDh) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETREGBANK, + 0x01); + + /* 5.19.2 SETPOWER: Set power (B1h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETPOWER, + 0x00); + + /* 5.19.11 Set register bank (BDh) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETREGBANK, + 0x00); + + /* Unknown command, not listed in the HX8394-F datasheet */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_UNKNOWN5, + 0x40, 0x81, 0x50, 0x00, 0x1a, 0xfc, 0x01); + + /* Unknown command, not listed in the HX8394-F datasheet */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_UNKNOWN2, + 0xed); +} + +static const struct drm_display_mode powkiddy_x55_mode = { + .hdisplay = 720, + .hsync_start = 720 + 44, + .hsync_end = 720 + 44 + 20, + .htotal = 720 + 44 + 20 + 20, + .vdisplay = 1280, + .vsync_start = 1280 + 12, + .vsync_end = 1280 + 12 + 10, + .vtotal = 1280 + 12 + 10 + 10, + .clock = 63290, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + .width_mm = 67, + .height_mm = 121, +}; + +static const struct hx8394_panel_desc powkiddy_x55_desc = { + .mode = &powkiddy_x55_mode, + .lanes = 4, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_NO_EOT_PACKET, + .format = MIPI_DSI_FMT_RGB888, + .init_sequence = powkiddy_x55_init_sequence, +}; + +static void mchp_ac40t08a_init_sequence(struct mipi_dsi_multi_context *dsi_ctx) +{ + /* DCS commands do not seem to be sent correclty without this delay */ + mipi_dsi_msleep(dsi_ctx, 20); + + /* 5.19.8 SETEXTC: Set extension command (B9h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETEXTC, + 0xff, 0x83, 0x94); + + /* 5.19.9 SETMIPI: Set MIPI control (BAh) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETMIPI, + 0x63, 0x03, 0x68, 0x6b, 0xb2, 0xc0); + + /* 5.19.2 SETPOWER: Set power (B1h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETPOWER, + 0x48, 0x12, 0x72, 0x09, 0x32, 0x54, + 0x71, 0x71, 0x57, 0x47); + + /* 5.19.3 SETDISP: Set display related register (B2h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETDISP, + 0x00, 0x80, 0x64, 0x0c, 0x0d, 0x2f); + + /* 5.19.4 SETCYC: Set display waveform cycles (B4h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETCYC, + 0x73, 0x74, 0x73, 0x74, 0x73, 0x74, + 0x01, 0x0c, 0x86, 0x75, 0x00, 0x3f, + 0x73, 0x74, 0x73, 0x74, 0x73, 0x74, + 0x01, 0x0c, 0x86); + + /* 5.19.5 SETVCOM: Set VCOM voltage (B6h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETVCOM, + 0x6e, 0x6e); + + /* 5.19.19 SETGIP0: Set GIP Option0 (D3h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETGIP0, + 0x00, 0x00, 0x07, 0x07, 0x40, 0x07, + 0x0c, 0x00, 0x08, 0x10, 0x08, 0x00, + 0x08, 0x54, 0x15, 0x0a, 0x05, 0x0a, + 0x02, 0x15, 0x06, 0x05, 0x06, 0x47, + 0x44, 0x0a, 0x0a, 0x4b, 0x10, 0x07, + 0x07, 0x0c, 0x40); + + /* 5.19.20 Set GIP Option1 (D5h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETGIP1, + 0x1c, 0x1c, 0x1d, 0x1d, 0x00, 0x01, + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x24, 0x25, + 0x18, 0x18, 0x26, 0x27, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x20, 0x21, 0x18, 0x18, + 0x18, 0x18); + + /* 5.19.21 Set GIP Option2 (D6h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETGIP2, + 0x1c, 0x1c, 0x1d, 0x1d, 0x07, 0x06, + 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, + 0x0b, 0x0a, 0x09, 0x08, 0x21, 0x20, + 0x18, 0x18, 0x27, 0x26, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x25, 0x24, 0x18, 0x18, + 0x18, 0x18); + + /* 5.19.25 SETGAMMA: Set gamma curve related setting (E0h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETGAMMA, + 0x00, 0x0a, 0x15, 0x1b, 0x1e, 0x21, + 0x24, 0x22, 0x47, 0x56, 0x65, 0x66, + 0x6e, 0x82, 0x88, 0x8b, 0x9a, 0x9d, + 0x98, 0xa8, 0xb9, 0x5d, 0x5c, 0x61, + 0x66, 0x6a, 0x6f, 0x7f, 0x7f, 0x00, + 0x0a, 0x15, 0x1b, 0x1e, 0x21, 0x24, + 0x22, 0x47, 0x56, 0x65, 0x65, 0x6e, + 0x81, 0x87, 0x8b, 0x98, 0x9d, 0x99, + 0xa8, 0xba, 0x5d, 0x5d, 0x62, 0x67, + 0x6b, 0x72, 0x7f, 0x7f); + + /* Unknown command, not listed in the HX8394-F datasheet (C0H) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_UNKNOWN1, + 0x1f, 0x73); + + /* Set CABC control (C9h)*/ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETCABC, + 0x76, 0x00, 0x30); + + /* 5.19.17 SETPANEL (CCh) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETPANEL, + 0x0b); + + /* Unknown command, not listed in the HX8394-F datasheet (D4h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_UNKNOWN3, + 0x02); + + /* 5.19.11 Set register bank (BDh) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETREGBANK, + 0x02); + + /* 5.19.11 Set register bank (D8h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_UNKNOWN4, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff); + + /* 5.19.11 Set register bank (BDh) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETREGBANK, + 0x00); + + /* 5.19.11 Set register bank (BDh) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETREGBANK, + 0x01); + + /* 5.19.2 SETPOWER: Set power (B1h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETPOWER, + 0x00); + + /* 5.19.11 Set register bank (BDh) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETREGBANK, + 0x00); + + /* Unknown command, not listed in the HX8394-F datasheet (C6h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_UNKNOWN2, + 0xed); +} + +static const struct drm_display_mode mchp_ac40t08a_mode = { + .hdisplay = 720, + .hsync_start = 720 + 12, + .hsync_end = 720 + 12 + 24, + .htotal = 720 + 12 + 12 + 24, + .vdisplay = 1280, + .vsync_start = 1280 + 13, + .vsync_end = 1280 + 14, + .vtotal = 1280 + 14 + 13, + .clock = 60226, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + .width_mm = 76, + .height_mm = 132, +}; + +static const struct hx8394_panel_desc mchp_ac40t08a_desc = { + .mode = &mchp_ac40t08a_mode, + .lanes = 4, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST, + .format = MIPI_DSI_FMT_RGB888, + .init_sequence = mchp_ac40t08a_init_sequence, +}; + +/* + * HL055FHAV028C is based on Himax HX8399, so datasheet pages are + * slightly different than HX8394 based panels. + */ +static void hl055fhav028c_init_sequence(struct mipi_dsi_multi_context *dsi_ctx) +{ + /* 6.3.6 SETEXTC: Set extension command (B9h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETEXTC, + 0xff, 0x83, 0x99); + + /* 6.3.17 SETOFFSET: Set offset voltage (D2h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETOFFSET, + 0x77); + + /* 6.3.1 SETPOWER: Set power (B1h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETPOWER, + 0x02, 0x04, 0x74, 0x94, 0x01, 0x32, + 0x33, 0x11, 0x11, 0xab, 0x4d, 0x56, + 0x73, 0x02, 0x02); + + /* 6.3.2 SETDISP: Set display related register (B2h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETDISP, + 0x00, 0x80, 0x80, 0xae, 0x05, 0x07, + 0x5a, 0x11, 0x00, 0x00, 0x10, 0x1e, + 0x70, 0x03, 0xd4); + + /* 6.3.3 SETCYC: Set display waveform cycles (B4h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETCYC, + 0x00, 0xff, 0x02, 0xc0, 0x02, 0xc0, + 0x00, 0x00, 0x08, 0x00, 0x04, 0x06, + 0x00, 0x32, 0x04, 0x0a, 0x08, 0x21, + 0x03, 0x01, 0x00, 0x0f, 0xb8, 0x8b, + 0x02, 0xc0, 0x02, 0xc0, 0x00, 0x00, + 0x08, 0x00, 0x04, 0x06, 0x00, 0x32, + 0x04, 0x0a, 0x08, 0x01, 0x00, 0x0f, + 0xb8, 0x01); + + /* 6.3.18 SETGIP0: Set GIP Option0 (D3h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETGIP0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x10, 0x04, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x05, 0x05, 0x07, 0x00, 0x00, + 0x00, 0x05, 0x40); + + /* 6.3.19 Set GIP Option1 (D5h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETGIP1, + 0x18, 0x18, 0x19, 0x19, 0x18, 0x18, + 0x21, 0x20, 0x01, 0x00, 0x07, 0x06, + 0x05, 0x04, 0x03, 0x02, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x2f, 0x2f, + 0x30, 0x30, 0x31, 0x31, 0x18, 0x18, + 0x18, 0x18); + + /* 6.3.20 Set GIP Option2 (D6h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETGIP2, + 0x18, 0x18, 0x19, 0x19, 0x40, 0x40, + 0x20, 0x21, 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0x00, 0x01, 0x40, 0x40, + 0x40, 0x40, 0x40, 0x40, 0x2f, 0x2f, + 0x30, 0x30, 0x31, 0x31, 0x40, 0x40, + 0x40, 0x40); + + /* 6.3.21 Set GIP Option3 (D8h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_UNKNOWN4, + 0xa2, 0xaa, 0x02, 0xa0, 0xa2, 0xa8, + 0x02, 0xa0, 0xb0, 0x00, 0x00, 0x00, + 0xb0, 0x00, 0x00, 0x00); + + /* 6.3.9 Set register bank (BDh) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETREGBANK, + 0x01); + + /* 6.3.21 Set GIP Option3 (D8h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_UNKNOWN4, + 0xb0, 0x00, 0x00, 0x00, 0xb0, 0x00, + 0x00, 0x00, 0xe2, 0xaa, 0x03, 0xf0, + 0xe2, 0xaa, 0x03, 0xf0); + + /* 6.3.9 Set register bank (BDh) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETREGBANK, + 0x02); + + /* 6.3.21 Set GIP Option3 (D8h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_UNKNOWN4, + 0xe2, 0xaa, 0x03, 0xf0, 0xe2, 0xaa, + 0x03, 0xf0); + + /* 6.3.9 Set register bank (BDh) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETREGBANK, + 0x00); + + /* 6.3.4 SETVCOM: Set VCOM voltage (B6h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETVCOM, + 0x7a, 0x7a); + + /* 6.3.26 SETGAMMA: Set gamma curve related setting (E0h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETGAMMA, + 0x00, 0x18, 0x27, 0x24, 0x5a, 0x68, + 0x79, 0x78, 0x81, 0x8a, 0x92, 0x99, + 0x9e, 0xa7, 0xaf, 0xb4, 0xb9, 0xc3, + 0xc7, 0xd1, 0xc6, 0xd4, 0xd5, 0x6c, + 0x67, 0x71, 0x77, 0x00, 0x00, 0x18, + 0x27, 0x24, 0x5a, 0x68, 0x79, 0x78, + 0x81, 0x8a, 0x92, 0x99, 0x9e, 0xa7, + 0xaf, 0xb4, 0xb9, 0xc3, 0xc7, 0xd1, + 0xc6, 0xd4, 0xd5, 0x6c, 0x67, 0x77); + + /* Unknown command, not listed in the HX8399-C datasheet (C6h) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_UNKNOWN2, + 0xff, 0xf9); + + /* 6.3.16 SETPANEL (CCh) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, HX8394_CMD_SETPANEL, + 0x08); +} + +static const struct drm_display_mode hl055fhav028c_mode = { + .hdisplay = 1080, + .hsync_start = 1080 + 32, + .hsync_end = 1080 + 32 + 8, + .htotal = 1080 + 32 + 8 + 32, + .vdisplay = 1920, + .vsync_start = 1920 + 16, + .vsync_end = 1920 + 16 + 2, + .vtotal = 1920 + 16 + 2 + 14, + .clock = 134920, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + .width_mm = 70, + .height_mm = 127, +}; + +static const struct hx8394_panel_desc hl055fhav028c_desc = { + .mode = &hl055fhav028c_mode, + .lanes = 4, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST, + .format = MIPI_DSI_FMT_RGB888, + .init_sequence = hl055fhav028c_init_sequence, +}; + +static int hx8394_enable(struct drm_panel *panel) +{ + struct hx8394 *ctx = panel_to_hx8394(panel); + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + int ret; + + ctx->desc->init_sequence(&dsi_ctx); + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + + if (dsi_ctx.accum_err) + return dsi_ctx.accum_err; + /* Panel is operational 120 msec after reset */ + msleep(120); + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + if (dsi_ctx.accum_err) + goto sleep_in; + + return 0; + +sleep_in: + ret = dsi_ctx.accum_err; + dsi_ctx.accum_err = 0; + + /* This will probably fail, but let's try orderly power off anyway. */ + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 50); + + return ret; +} + +static int hx8394_disable(struct drm_panel *panel) +{ + struct hx8394 *ctx = panel_to_hx8394(panel); + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 50); /* about 3 frames */ + + return dsi_ctx.accum_err; +} + +static int hx8394_unprepare(struct drm_panel *panel) +{ + struct hx8394 *ctx = panel_to_hx8394(panel); + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + + regulator_disable(ctx->iovcc); + regulator_disable(ctx->vcc); + + return 0; +} + +static int hx8394_prepare(struct drm_panel *panel) +{ + struct hx8394 *ctx = panel_to_hx8394(panel); + int ret; + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + + ret = regulator_enable(ctx->vcc); + if (ret) { + dev_err(ctx->dev, "Failed to enable vcc supply: %d\n", ret); + return ret; + } + + ret = regulator_enable(ctx->iovcc); + if (ret) { + dev_err(ctx->dev, "Failed to enable iovcc supply: %d\n", ret); + goto disable_vcc; + } + + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + + msleep(180); + + return 0; + +disable_vcc: + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_disable(ctx->vcc); + return ret; +} + +static int hx8394_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct hx8394 *ctx = panel_to_hx8394(panel); + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, ctx->desc->mode); + if (!mode) { + dev_err(ctx->dev, "Failed to add mode %ux%u@%u\n", + ctx->desc->mode->hdisplay, ctx->desc->mode->vdisplay, + drm_mode_vrefresh(ctx->desc->mode)); + 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 enum drm_panel_orientation hx8394_get_orientation(struct drm_panel *panel) +{ + struct hx8394 *ctx = panel_to_hx8394(panel); + + return ctx->orientation; +} + +static const struct drm_panel_funcs hx8394_drm_funcs = { + .disable = hx8394_disable, + .unprepare = hx8394_unprepare, + .prepare = hx8394_prepare, + .enable = hx8394_enable, + .get_modes = hx8394_get_modes, + .get_orientation = hx8394_get_orientation, +}; + +static int hx8394_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct hx8394 *ctx; + int ret; + + ctx = devm_drm_panel_alloc(dev, struct hx8394, panel, + &hx8394_drm_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ctx->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), + "Failed to get reset gpio\n"); + + ret = of_drm_get_panel_orientation(dev->of_node, &ctx->orientation); + if (ret < 0) { + dev_err(dev, "%pOF: failed to get orientation %d\n", dev->of_node, ret); + return ret; + } + + mipi_dsi_set_drvdata(dsi, ctx); + + ctx->dev = dev; + ctx->desc = of_device_get_match_data(dev); + + dsi->mode_flags = ctx->desc->mode_flags; + dsi->format = ctx->desc->format; + dsi->lanes = ctx->desc->lanes; + + ctx->vcc = devm_regulator_get(dev, "vcc"); + if (IS_ERR(ctx->vcc)) + return dev_err_probe(dev, PTR_ERR(ctx->vcc), + "Failed to request vcc regulator\n"); + + ctx->iovcc = devm_regulator_get(dev, "iovcc"); + if (IS_ERR(ctx->iovcc)) + return dev_err_probe(dev, PTR_ERR(ctx->iovcc), + "Failed to request iovcc regulator\n"); + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return ret; + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err_probe(dev, ret, "mipi_dsi_attach failed\n"); + drm_panel_remove(&ctx->panel); + return ret; + } + + dev_dbg(dev, "%ux%u@%u %ubpp dsi %udl - ready\n", + ctx->desc->mode->hdisplay, ctx->desc->mode->vdisplay, + drm_mode_vrefresh(ctx->desc->mode), + mipi_dsi_pixel_format_to_bpp(dsi->format), dsi->lanes); + + return 0; +} + +static void hx8394_remove(struct mipi_dsi_device *dsi) +{ + struct hx8394 *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id hx8394_of_match[] = { + { .compatible = "hannstar,hsd060bhw4", .data = &hsd060bhw4_desc }, + { .compatible = "huiling,hl055fhav028c", .data = &hl055fhav028c_desc }, + { .compatible = "powkiddy,x55-panel", .data = &powkiddy_x55_desc }, + { .compatible = "microchip,ac40t08a-mipi-panel", .data = &mchp_ac40t08a_desc }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, hx8394_of_match); + +static struct mipi_dsi_driver hx8394_driver = { + .probe = hx8394_probe, + .remove = hx8394_remove, + .driver = { + .name = DRV_NAME, + .of_match_table = hx8394_of_match, + }, +}; +module_mipi_dsi_driver(hx8394_driver); + +MODULE_AUTHOR("Kamil TrzciÅ„ski <ayufan@ayufan.eu>"); +MODULE_DESCRIPTION("DRM driver for Himax HX8394 based MIPI DSI panels"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-hydis-hv101hd1.c b/drivers/gpu/drm/panel/panel-hydis-hv101hd1.c new file mode 100644 index 000000000000..46426c388932 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-hydis-hv101hd1.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include <linux/array_size.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/property.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> + +struct hv101hd1 { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct regulator_bulk_data *supplies; +}; + +static const struct regulator_bulk_data hv101hd1_supplies[] = { + { .supply = "vdd" }, + { .supply = "vio" }, +}; + +static inline struct hv101hd1 *to_hv101hd1(struct drm_panel *panel) +{ + return container_of(panel, struct hv101hd1, panel); +} + +static int hv101hd1_prepare(struct drm_panel *panel) +{ + struct hv101hd1 *hv = to_hv101hd1(panel); + struct mipi_dsi_multi_context ctx = { .dsi = hv->dsi }; + struct device *dev = &hv->dsi->dev; + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(hv101hd1_supplies), hv->supplies); + if (ret) { + dev_err(dev, "error enabling regulators (%d)\n", ret); + return ret; + } + + mipi_dsi_dcs_exit_sleep_mode_multi(&ctx); + mipi_dsi_msleep(&ctx, 20); + + mipi_dsi_dcs_set_display_on_multi(&ctx); + mipi_dsi_msleep(&ctx, 20); + + return 0; +} + +static int hv101hd1_disable(struct drm_panel *panel) +{ + struct hv101hd1 *hv = to_hv101hd1(panel); + struct mipi_dsi_multi_context ctx = { .dsi = hv->dsi }; + + mipi_dsi_dcs_set_display_off_multi(&ctx); + mipi_dsi_msleep(&ctx, 120); + mipi_dsi_dcs_enter_sleep_mode_multi(&ctx); + mipi_dsi_msleep(&ctx, 20); + + return 0; +} + +static int hv101hd1_unprepare(struct drm_panel *panel) +{ + struct hv101hd1 *hv = to_hv101hd1(panel); + + return regulator_bulk_disable(ARRAY_SIZE(hv101hd1_supplies), + hv->supplies); +} + +static const struct drm_display_mode hv101hd1_mode = { + .clock = (1366 + 74 + 36 + 24) * (768 + 21 + 7 + 4) * 60 / 1000, + .hdisplay = 1366, + .hsync_start = 1366 + 74, + .hsync_end = 1366 + 74 + 36, + .htotal = 1366 + 74 + 36 + 24, + .vdisplay = 768, + .vsync_start = 768 + 21, + .vsync_end = 768 + 21 + 7, + .vtotal = 768 + 21 + 7 + 4, + .width_mm = 140, + .height_mm = 220, +}; + +static int hv101hd1_get_modes(struct drm_panel *panel, struct drm_connector *connector) +{ + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &hv101hd1_mode); + if (!mode) + 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 hv101hd1_panel_funcs = { + .prepare = hv101hd1_prepare, + .disable = hv101hd1_disable, + .unprepare = hv101hd1_unprepare, + .get_modes = hv101hd1_get_modes, +}; + +static int hv101hd1_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct hv101hd1 *hv; + int ret; + + hv = devm_drm_panel_alloc(dev, struct hv101hd1, panel, + &hv101hd1_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(hv)) + return PTR_ERR(hv); + + ret = devm_regulator_bulk_get_const(dev, ARRAY_SIZE(hv101hd1_supplies), + hv101hd1_supplies, &hv->supplies); + if (ret) + return dev_err_probe(dev, ret, "failed to get regulators\n"); + + hv->dsi = dsi; + mipi_dsi_set_drvdata(dsi, hv); + + dsi->lanes = 2; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM; + + ret = drm_panel_of_backlight(&hv->panel); + if (ret) + return dev_err_probe(dev, ret, "Failed to get backlight\n"); + + drm_panel_add(&hv->panel); + + ret = mipi_dsi_attach(dsi); + if (ret) { + drm_panel_remove(&hv->panel); + return dev_err_probe(dev, ret, "Failed to attach to DSI host\n"); + } + + return 0; +} + +static void hv101hd1_remove(struct mipi_dsi_device *dsi) +{ + struct hv101hd1 *hv = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, + "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&hv->panel); +} + +static const struct of_device_id hv101hd1_of_match[] = { + { .compatible = "hydis,hv101hd1" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, hv101hd1_of_match); + +static struct mipi_dsi_driver hv101hd1_driver = { + .driver = { + .name = "panel-hv101hd1", + .of_match_table = hv101hd1_of_match, + }, + .probe = hv101hd1_probe, + .remove = hv101hd1_remove, +}; +module_mipi_dsi_driver(hv101hd1_driver); + +MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>"); +MODULE_DESCRIPTION("DRM driver for Hydis HV101HD1 panel"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-ilitek-ili9322.c b/drivers/gpu/drm/panel/panel-ilitek-ili9322.c index bd38bf4f1ba6..6ed544a83bdd 100644 --- a/drivers/gpu/drm/panel/panel-ilitek-ili9322.c +++ b/drivers/gpu/drm/panel/panel-ilitek-ili9322.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Ilitek ILI9322 TFT LCD drm_panel driver. * @@ -16,19 +17,13 @@ * * Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org> * Derived from drivers/drm/gpu/panel/panel-samsung-ld9040.c - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. */ -#include <drm/drmP.h> -#include <drm/drm_panel.h> - -#include <linux/of_device.h> #include <linux/bitops.h> #include <linux/gpio/consumer.h> #include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> #include <linux/regmap.h> #include <linux/regulator/consumer.h> #include <linux/spi/spi.h> @@ -37,6 +32,9 @@ #include <video/of_videomode.h> #include <video/videomode.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + #define ILI9322_CHIP_ID 0x00 #define ILI9322_CHIP_ID_MAGIC 0x96 @@ -155,7 +153,7 @@ #define ILI9322_GAMMA_7 0x16 #define ILI9322_GAMMA_8 0x17 -/** +/* * enum ili9322_input - the format of the incoming signal to the panel * * The panel can be connected to various input streams and four of them can @@ -320,18 +318,13 @@ static int ili9322_regmap_spi_read(void *context, const void *reg, return spi_write_then_read(spi, buf, 1, val, 1); } -static struct regmap_bus ili9322_regmap_bus = { +static const struct regmap_bus ili9322_regmap_bus = { .write = ili9322_regmap_spi_write, .read = ili9322_regmap_spi_read, .reg_format_endian_default = REGMAP_ENDIAN_BIG, .val_format_endian_default = REGMAP_ENDIAN_BIG, }; -static bool ili9322_volatile_reg(struct device *dev, unsigned int reg) -{ - return false; -} - static bool ili9322_writeable_reg(struct device *dev, unsigned int reg) { /* Just register 0 is read-only */ @@ -344,14 +337,12 @@ static const struct regmap_config ili9322_regmap_config = { .reg_bits = 8, .val_bits = 8, .max_register = 0x44, - .cache_type = REGCACHE_RBTREE, - .volatile_reg = ili9322_volatile_reg, + .cache_type = REGCACHE_MAPLE, .writeable_reg = ili9322_writeable_reg, }; static int ili9322_init(struct drm_panel *panel, struct ili9322 *ili) { - struct drm_connector *connector = panel->connector; u8 reg; int ret; int i; @@ -382,7 +373,7 @@ static int ili9322_init(struct drm_panel *panel, struct ili9322 *ili) "can't set up VCOM amplitude (%d)\n", ret); return ret; } - }; + } if (ili->vcom_high != U8_MAX) { ret = regmap_write(ili->regmap, ILI9322_VCOM_HIGH, @@ -391,7 +382,7 @@ static int ili9322_init(struct drm_panel *panel, struct ili9322 *ili) dev_err(ili->dev, "can't set up VCOM high (%d)\n", ret); return ret; } - }; + } /* Set up gamma correction */ for (i = 0; i < ARRAY_SIZE(ili->gamma); i++) { @@ -409,23 +400,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_POSEDGE; - } else { - reg = 0; - connector->display_info.bus_flags |= - DRM_BUS_FLAG_PIXDATA_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) @@ -555,7 +534,7 @@ static int ili9322_enable(struct drm_panel *panel) /* Serial RGB modes */ static const struct drm_display_mode srgb_320x240_mode = { - .clock = 2453500, + .clock = 24535, .hdisplay = 320, .hsync_start = 320 + 359, .hsync_end = 320 + 359 + 1, @@ -564,12 +543,11 @@ static const struct drm_display_mode srgb_320x240_mode = { .vsync_start = 240 + 4, .vsync_end = 240 + 4 + 1, .vtotal = 262, - .vrefresh = 60, .flags = 0, }; static const struct drm_display_mode srgb_360x240_mode = { - .clock = 2700000, + .clock = 27000, .hdisplay = 360, .hsync_start = 360 + 35, .hsync_end = 360 + 35 + 1, @@ -578,13 +556,12 @@ static const struct drm_display_mode srgb_360x240_mode = { .vsync_start = 240 + 21, .vsync_end = 240 + 21 + 1, .vtotal = 262, - .vrefresh = 60, .flags = 0, }; /* This is the only mode listed for parallel RGB in the datasheet */ static const struct drm_display_mode prgb_320x240_mode = { - .clock = 6400000, + .clock = 64000, .hdisplay = 320, .hsync_start = 320 + 38, .hsync_end = 320 + 38 + 1, @@ -593,13 +570,12 @@ static const struct drm_display_mode prgb_320x240_mode = { .vsync_start = 240 + 4, .vsync_end = 240 + 4 + 1, .vtotal = 262, - .vrefresh = 60, .flags = 0, }; /* YUV modes */ static const struct drm_display_mode yuv_640x320_mode = { - .clock = 2454000, + .clock = 24540, .hdisplay = 640, .hsync_start = 640 + 252, .hsync_end = 640 + 252 + 1, @@ -608,12 +584,11 @@ static const struct drm_display_mode yuv_640x320_mode = { .vsync_start = 320 + 4, .vsync_end = 320 + 4 + 1, .vtotal = 320 + 4 + 1 + 18, - .vrefresh = 60, .flags = 0, }; static const struct drm_display_mode yuv_720x360_mode = { - .clock = 2700000, + .clock = 27000, .hdisplay = 720, .hsync_start = 720 + 252, .hsync_end = 720 + 252 + 1, @@ -622,13 +597,12 @@ static const struct drm_display_mode yuv_720x360_mode = { .vsync_start = 360 + 4, .vsync_end = 360 + 4 + 1, .vtotal = 360 + 4 + 1 + 18, - .vrefresh = 60, .flags = 0, }; /* BT.656 VGA mode, 640x480 */ static const struct drm_display_mode itu_r_bt_656_640_mode = { - .clock = 2454000, + .clock = 24540, .hdisplay = 640, .hsync_start = 640 + 3, .hsync_end = 640 + 3 + 1, @@ -637,13 +611,12 @@ static const struct drm_display_mode itu_r_bt_656_640_mode = { .vsync_start = 480 + 4, .vsync_end = 480 + 4 + 1, .vtotal = 500, - .vrefresh = 60, .flags = 0, }; /* BT.656 D1 mode 720x480 */ static const struct drm_display_mode itu_r_bt_656_720_mode = { - .clock = 2700000, + .clock = 27000, .hdisplay = 720, .hsync_start = 720 + 3, .hsync_end = 720 + 3 + 1, @@ -652,50 +625,59 @@ static const struct drm_display_mode itu_r_bt_656_720_mode = { .vsync_start = 480 + 4, .vsync_end = 480 + 4 + 1, .vtotal = 500, - .vrefresh = 60, .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; - strncpy(connector->display_info.name, "ILI9322 TFT LCD driver\0", - DRM_DISPLAY_INFO_LEN); - 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; break; } if (!mode) { - DRM_ERROR("bad mode or failed to add mode\n"); + dev_err(panel->dev, "bad mode or failed to add mode\n"); return -EINVAL; } drm_mode_set_name(mode); @@ -740,9 +722,10 @@ static int ili9322_probe(struct spi_device *spi) int ret; int i; - ili = devm_kzalloc(dev, sizeof(struct ili9322), GFP_KERNEL); - if (!ili) - return -ENOMEM; + ili = devm_drm_panel_alloc(dev, struct ili9322, panel, + &ili9322_drm_funcs, DRM_MODE_CONNECTOR_DPI); + if (IS_ERR(ili)) + return PTR_ERR(ili); spi_set_drvdata(spi, ili); @@ -901,21 +884,17 @@ 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_add(&ili->panel); - return drm_panel_add(&ili->panel); + return 0; } -static int ili9322_remove(struct spi_device *spi) +static void ili9322_remove(struct spi_device *spi) { struct ili9322 *ili = spi_get_drvdata(spi); ili9322_power_off(ili); drm_panel_remove(&ili->panel); - - return 0; } /* diff --git a/drivers/gpu/drm/panel/panel-ilitek-ili9341.c b/drivers/gpu/drm/panel/panel-ilitek-ili9341.c new file mode 100644 index 000000000000..f7425dfaa50d --- /dev/null +++ b/drivers/gpu/drm/panel/panel-ilitek-ili9341.c @@ -0,0 +1,588 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Ilitek ILI9341 TFT LCD drm_panel driver. + * + * This panel can be configured to support: + * - 16-bit parallel RGB interface + * - 18-bit parallel RGB interface + * - 4-line serial spi interface + * + * Copyright (C) 2021 Dillon Min <dillon.minfei@gmail.com> + * + * For dbi+dpi part: + * Derived from drivers/drm/gpu/panel/panel-ilitek-ili9322.c + * the reuse of DBI abstraction part referred from Linus's patch + * "drm/panel: s6e63m0: Switch to DBI abstraction for SPI" + */ + +#include <linux/backlight.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/property.h> +#include <linux/regulator/consumer.h> +#include <linux/spi/spi.h> + +#include <video/mipi_display.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_drv.h> +#include <drm/drm_fbdev_dma.h> +#include <drm/drm_gem_atomic_helper.h> +#include <drm/drm_gem_dma_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_mipi_dbi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> + +#define ILI9341_RGB_INTERFACE 0xb0 /* RGB Interface Signal Control */ +#define ILI9341_FRC 0xb1 /* Frame Rate Control register */ +#define ILI9341_DFC 0xb6 /* Display Function Control register */ +#define ILI9341_POWER1 0xc0 /* Power Control 1 register */ +#define ILI9341_POWER2 0xc1 /* Power Control 2 register */ +#define ILI9341_VCOM1 0xc5 /* VCOM Control 1 register */ +#define ILI9341_VCOM2 0xc7 /* VCOM Control 2 register */ +#define ILI9341_POWERA 0xcb /* Power control A register */ +#define ILI9341_POWERB 0xcf /* Power control B register */ +#define ILI9341_PGAMMA 0xe0 /* Positive Gamma Correction register */ +#define ILI9341_NGAMMA 0xe1 /* Negative Gamma Correction register */ +#define ILI9341_DTCA 0xe8 /* Driver timing control A */ +#define ILI9341_DTCB 0xea /* Driver timing control B */ +#define ILI9341_POWER_SEQ 0xed /* Power on sequence register */ +#define ILI9341_3GAMMA_EN 0xf2 /* 3 Gamma enable register */ +#define ILI9341_INTERFACE 0xf6 /* Interface control register */ +#define ILI9341_PRC 0xf7 /* Pump ratio control register */ +#define ILI9341_ETMOD 0xb7 /* Entry mode set */ + +#define ILI9341_MADCTL_BGR BIT(3) +#define ILI9341_MADCTL_MV BIT(5) +#define ILI9341_MADCTL_MX BIT(6) +#define ILI9341_MADCTL_MY BIT(7) + +#define ILI9341_POWER_B_LEN 3 +#define ILI9341_POWER_SEQ_LEN 4 +#define ILI9341_DTCA_LEN 3 +#define ILI9341_DTCB_LEN 2 +#define ILI9341_POWER_A_LEN 5 +#define ILI9341_DFC_1_LEN 2 +#define ILI9341_FRC_LEN 2 +#define ILI9341_VCOM_1_LEN 2 +#define ILI9341_DFC_2_LEN 4 +#define ILI9341_COLUMN_ADDR_LEN 4 +#define ILI9341_PAGE_ADDR_LEN 4 +#define ILI9341_INTERFACE_LEN 3 +#define ILI9341_PGAMMA_LEN 15 +#define ILI9341_NGAMMA_LEN 15 +#define ILI9341_CA_LEN 3 + +#define ILI9341_PIXEL_DPI_16_BITS (BIT(6) | BIT(4)) +#define ILI9341_PIXEL_DPI_18_BITS (BIT(6) | BIT(5)) +#define ILI9341_GAMMA_CURVE_1 BIT(0) +#define ILI9341_IF_WE_MODE BIT(0) +#define ILI9341_IF_BIG_ENDIAN 0x00 +#define ILI9341_IF_DM_RGB BIT(2) +#define ILI9341_IF_DM_INTERNAL 0x00 +#define ILI9341_IF_DM_VSYNC BIT(3) +#define ILI9341_IF_RM_RGB BIT(1) +#define ILI9341_IF_RIM_RGB 0x00 + +#define ILI9341_COLUMN_ADDR 0x00ef +#define ILI9341_PAGE_ADDR 0x013f + +#define ILI9341_RGB_EPL BIT(0) +#define ILI9341_RGB_DPL BIT(1) +#define ILI9341_RGB_HSPL BIT(2) +#define ILI9341_RGB_VSPL BIT(3) +#define ILI9341_RGB_DE_MODE BIT(6) +#define ILI9341_RGB_DISP_PATH_MEM BIT(7) + +#define ILI9341_DBI_VCOMH_4P6V 0x23 +#define ILI9341_DBI_PWR_2_DEFAULT 0x10 +#define ILI9341_DBI_PRC_NORMAL 0x20 +#define ILI9341_DBI_VCOM_1_VMH_4P25V 0x3e +#define ILI9341_DBI_VCOM_1_VML_1P5V 0x28 +#define ILI9341_DBI_VCOM_2_DEC_58 0x86 +#define ILI9341_DBI_FRC_DIVA 0x00 +#define ILI9341_DBI_FRC_RTNA 0x1b +#define ILI9341_DBI_EMS_GAS BIT(0) +#define ILI9341_DBI_EMS_DTS BIT(1) +#define ILI9341_DBI_EMS_GON BIT(2) + +/* struct ili9341_config - the system specific ILI9341 configuration */ +struct ili9341_config { + u32 max_spi_speed; + /* mode: the drm display mode */ + const struct drm_display_mode mode; + /* ca: TODO: need comments for this register */ + u8 ca[ILI9341_CA_LEN]; + /* power_b: Power control B (CFh) */ + u8 power_b[ILI9341_POWER_B_LEN]; + /* power_seq: Power on sequence control (EDh) */ + u8 power_seq[ILI9341_POWER_SEQ_LEN]; + /* dtca: Driver timing control A (E8h) */ + u8 dtca[ILI9341_DTCA_LEN]; + /* dtcb: Driver timing control B (EAh) */ + u8 dtcb[ILI9341_DTCB_LEN]; + /* power_a: Power control A (CBh) */ + u8 power_a[ILI9341_POWER_A_LEN]; + /* frc: Frame Rate Control (In Normal Mode/Full Colors) (B1h) */ + u8 frc[ILI9341_FRC_LEN]; + /* prc: Pump ratio control (F7h) */ + u8 prc; + /* dfc_1: B6h DISCTRL (Display Function Control) */ + u8 dfc_1[ILI9341_DFC_1_LEN]; + /* power_1: Power Control 1 (C0h) */ + u8 power_1; + /* power_2: Power Control 2 (C1h) */ + u8 power_2; + /* vcom_1: VCOM Control 1(C5h) */ + u8 vcom_1[ILI9341_VCOM_1_LEN]; + /* vcom_2: VCOM Control 2(C7h) */ + u8 vcom_2; + /* address_mode: Memory Access Control (36h) */ + u8 address_mode; + /* g3amma_en: Enable 3G (F2h) */ + u8 g3amma_en; + /* rgb_interface: RGB Interface Signal Control (B0h) */ + u8 rgb_interface; + /* dfc_2: refer to dfc_1 */ + u8 dfc_2[ILI9341_DFC_2_LEN]; + /* column_addr: Column Address Set (2Ah) */ + u8 column_addr[ILI9341_COLUMN_ADDR_LEN]; + /* page_addr: Page Address Set (2Bh) */ + u8 page_addr[ILI9341_PAGE_ADDR_LEN]; + /* interface: Interface Control (F6h) */ + u8 interface[ILI9341_INTERFACE_LEN]; + /* + * pixel_format: This command sets the pixel format for the RGB + * image data used by + */ + u8 pixel_format; + /* + * gamma_curve: This command is used to select the desired Gamma + * curve for the + */ + u8 gamma_curve; + /* pgamma: Positive Gamma Correction (E0h) */ + u8 pgamma[ILI9341_PGAMMA_LEN]; + /* ngamma: Negative Gamma Correction (E1h) */ + u8 ngamma[ILI9341_NGAMMA_LEN]; +}; + +struct ili9341 { + const struct ili9341_config *conf; + struct drm_panel panel; + struct gpio_desc *reset_gpio; + struct gpio_desc *dc_gpio; + struct mipi_dbi *dbi; + u32 max_spi_speed; + struct regulator_bulk_data supplies[3]; +}; + +/* + * The Stm32f429-disco board has a panel ili9341 connected to ltdc controller + */ +static const struct ili9341_config ili9341_stm32f429_disco_data = { + .max_spi_speed = 10000000, + .mode = { + .clock = 6100, + .hdisplay = 240, + .hsync_start = 240 + 10,/* hfp 10 */ + .hsync_end = 240 + 10 + 10,/* hsync 10 */ + .htotal = 240 + 10 + 10 + 20,/* hbp 20 */ + .vdisplay = 320, + .vsync_start = 320 + 4,/* vfp 4 */ + .vsync_end = 320 + 4 + 2,/* vsync 2 */ + .vtotal = 320 + 4 + 2 + 2,/* vbp 2 */ + .flags = 0, + .width_mm = 65, + .height_mm = 50, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, + }, + .ca = {0xc3, 0x08, 0x50}, + .power_b = {0x00, 0xc1, 0x30}, + .power_seq = {0x64, 0x03, 0x12, 0x81}, + .dtca = {0x85, 0x00, 0x78}, + .power_a = {0x39, 0x2c, 0x00, 0x34, 0x02}, + .prc = 0x20, + .dtcb = {0x00, 0x00}, + /* 0x00 fosc, 0x1b 70hz */ + .frc = {0x00, 0x1b}, + /* + * 0x0a Interval scan, AGND AGND AGND AGND + * 0xa2 Normally white, G1 -> G320, S720 -> S1, + * Scan Cycle 5 frames,85ms + */ + .dfc_1 = {0x0a, 0xa2}, + /* 0x10 3.65v */ + .power_1 = 0x10, + /* 0x10 AVDD=vci*2, VGH=vci*7, VGL=-vci*4 */ + .power_2 = 0x10, + /* 0x45 VCOMH 4.425v, 0x15 VCOML -1.975*/ + .vcom_1 = {0x45, 0x15}, + /* 0x90 offset voltage, VMH-48, VML-48 */ + .vcom_2 = 0x90, + /* + * 0xc8 Row Address Order, Column Address Order + * BGR 1 + */ + .address_mode = 0xc8, + .g3amma_en = 0x00, + /* + * 0xc2 + * Display Data Path: Memory + * RGB: DE mode + * DOTCLK polarity set (data fetched at the falling time) + */ + .rgb_interface = ILI9341_RGB_DISP_PATH_MEM | + ILI9341_RGB_DE_MODE | + ILI9341_RGB_DPL, + /* + * 0x0a + * Gate outputs in non-display area: Interval scan + * Determine source/VCOM output in a non-display area in the partial + * display mode: AGND AGND AGND AGND + * + * 0xa7 + * Scan Cycle: 15 frames + * fFLM = 60Hz: 255ms + * Liquid crystal type: Normally white + * Gate Output Scan Direction: G1 -> G320 + * Source Output Scan Direction: S720 -> S1 + * + * 0x27 + * LCD Driver Line: 320 lines + * + * 0x04 + * PCDIV: 4 + */ + .dfc_2 = {0x0a, 0xa7, 0x27, 0x04}, + /* column address: 240 */ + .column_addr = {0x00, 0x00, (ILI9341_COLUMN_ADDR >> 4) & 0xff, + ILI9341_COLUMN_ADDR & 0xff}, + /* page address: 320 */ + .page_addr = {0x00, 0x00, (ILI9341_PAGE_ADDR >> 4) & 0xff, + ILI9341_PAGE_ADDR & 0xff}, + /* + * Memory write control: When the transfer number of data exceeds + * (EC-SC+1)*(EP-SP+1), the column and page number will be + * reset, and the exceeding data will be written into the following + * column and page. + * Display Operation Mode: RGB Interface Mode + * Interface for RAM Access: RGB interface + * 16- bit RGB interface (1 transfer/pixel) + */ + .interface = {ILI9341_IF_WE_MODE, 0x00, + ILI9341_IF_DM_RGB | ILI9341_IF_RM_RGB}, + /* DPI: 16 bits / pixel */ + .pixel_format = ILI9341_PIXEL_DPI_16_BITS, + /* Curve Selected: Gamma curve 1 (G2.2) */ + .gamma_curve = ILI9341_GAMMA_CURVE_1, + .pgamma = {0x0f, 0x29, 0x24, 0x0c, 0x0e, + 0x09, 0x4e, 0x78, 0x3c, 0x09, + 0x13, 0x05, 0x17, 0x11, 0x00}, + .ngamma = {0x00, 0x16, 0x1b, 0x04, 0x11, + 0x07, 0x31, 0x33, 0x42, 0x05, + 0x0c, 0x0a, 0x28, 0x2f, 0x0f}, +}; + +static inline struct ili9341 *panel_to_ili9341(struct drm_panel *panel) +{ + return container_of(panel, struct ili9341, panel); +} + +static void ili9341_dpi_init(struct ili9341 *ili) +{ + struct device *dev = (&ili->panel)->dev; + struct mipi_dbi *dbi = ili->dbi; + struct ili9341_config *cfg = (struct ili9341_config *)ili->conf; + + /* Power Control */ + mipi_dbi_command_stackbuf(dbi, 0xca, cfg->ca, ILI9341_CA_LEN); + mipi_dbi_command_stackbuf(dbi, ILI9341_POWERB, cfg->power_b, + ILI9341_POWER_B_LEN); + mipi_dbi_command_stackbuf(dbi, ILI9341_POWER_SEQ, cfg->power_seq, + ILI9341_POWER_SEQ_LEN); + mipi_dbi_command_stackbuf(dbi, ILI9341_DTCA, cfg->dtca, + ILI9341_DTCA_LEN); + mipi_dbi_command_stackbuf(dbi, ILI9341_POWERA, cfg->power_a, + ILI9341_POWER_A_LEN); + mipi_dbi_command(ili->dbi, ILI9341_PRC, cfg->prc); + mipi_dbi_command_stackbuf(dbi, ILI9341_DTCB, cfg->dtcb, + ILI9341_DTCB_LEN); + mipi_dbi_command_stackbuf(dbi, ILI9341_FRC, cfg->frc, ILI9341_FRC_LEN); + mipi_dbi_command_stackbuf(dbi, ILI9341_DFC, cfg->dfc_1, + ILI9341_DFC_1_LEN); + mipi_dbi_command(dbi, ILI9341_POWER1, cfg->power_1); + mipi_dbi_command(dbi, ILI9341_POWER2, cfg->power_2); + + /* VCOM */ + mipi_dbi_command_stackbuf(dbi, ILI9341_VCOM1, cfg->vcom_1, + ILI9341_VCOM_1_LEN); + mipi_dbi_command(dbi, ILI9341_VCOM2, cfg->vcom_2); + mipi_dbi_command(dbi, MIPI_DCS_SET_ADDRESS_MODE, cfg->address_mode); + + /* Gamma */ + mipi_dbi_command(dbi, ILI9341_3GAMMA_EN, cfg->g3amma_en); + mipi_dbi_command(dbi, ILI9341_RGB_INTERFACE, cfg->rgb_interface); + mipi_dbi_command_stackbuf(dbi, ILI9341_DFC, cfg->dfc_2, + ILI9341_DFC_2_LEN); + + /* Colomn address set */ + mipi_dbi_command_stackbuf(dbi, MIPI_DCS_SET_COLUMN_ADDRESS, + cfg->column_addr, ILI9341_COLUMN_ADDR_LEN); + + /* Page address set */ + mipi_dbi_command_stackbuf(dbi, MIPI_DCS_SET_PAGE_ADDRESS, + cfg->page_addr, ILI9341_PAGE_ADDR_LEN); + mipi_dbi_command_stackbuf(dbi, ILI9341_INTERFACE, cfg->interface, + ILI9341_INTERFACE_LEN); + + /* Format */ + mipi_dbi_command(dbi, MIPI_DCS_SET_PIXEL_FORMAT, cfg->pixel_format); + mipi_dbi_command(dbi, MIPI_DCS_WRITE_MEMORY_START); + msleep(200); + mipi_dbi_command(dbi, MIPI_DCS_SET_GAMMA_CURVE, cfg->gamma_curve); + mipi_dbi_command_stackbuf(dbi, ILI9341_PGAMMA, cfg->pgamma, + ILI9341_PGAMMA_LEN); + mipi_dbi_command_stackbuf(dbi, ILI9341_NGAMMA, cfg->ngamma, + ILI9341_NGAMMA_LEN); + mipi_dbi_command(dbi, MIPI_DCS_EXIT_SLEEP_MODE); + msleep(200); + mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_ON); + mipi_dbi_command(dbi, MIPI_DCS_WRITE_MEMORY_START); + + dev_info(dev, "Initialized display rgb interface\n"); +} + +static int ili9341_dpi_power_on(struct ili9341 *ili) +{ + struct device *dev = (&ili->panel)->dev; + int ret = 0; + + /* Assert RESET */ + gpiod_set_value(ili->reset_gpio, 1); + + /* Enable power */ + ret = regulator_bulk_enable(ARRAY_SIZE(ili->supplies), + ili->supplies); + if (ret < 0) { + dev_err(dev, "unable to enable vcc\n"); + return ret; + } + msleep(20); + + /* De-assert RESET */ + gpiod_set_value(ili->reset_gpio, 0); + msleep(20); + + return 0; +} + +static int ili9341_dpi_power_off(struct ili9341 *ili) +{ + /* Assert RESET */ + gpiod_set_value(ili->reset_gpio, 1); + + /* Disable power */ + return regulator_bulk_disable(ARRAY_SIZE(ili->supplies), + ili->supplies); +} + +static int ili9341_dpi_disable(struct drm_panel *panel) +{ + struct ili9341 *ili = panel_to_ili9341(panel); + + mipi_dbi_command(ili->dbi, MIPI_DCS_SET_DISPLAY_OFF); + return 0; +} + +static int ili9341_dpi_unprepare(struct drm_panel *panel) +{ + struct ili9341 *ili = panel_to_ili9341(panel); + + return ili9341_dpi_power_off(ili); +} + +static int ili9341_dpi_prepare(struct drm_panel *panel) +{ + struct ili9341 *ili = panel_to_ili9341(panel); + int ret; + + ret = ili9341_dpi_power_on(ili); + if (ret < 0) + return ret; + + ili9341_dpi_init(ili); + + return 0; +} + +static int ili9341_dpi_enable(struct drm_panel *panel) +{ + struct ili9341 *ili = panel_to_ili9341(panel); + + mipi_dbi_command(ili->dbi, MIPI_DCS_SET_DISPLAY_ON); + return 0; +} + +static int ili9341_dpi_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct ili9341 *ili = panel_to_ili9341(panel); + struct drm_device *drm = connector->dev; + struct drm_display_mode *mode; + struct drm_display_info *info; + + info = &connector->display_info; + info->width_mm = ili->conf->mode.width_mm; + info->height_mm = ili->conf->mode.height_mm; + + if (ili->conf->rgb_interface & ILI9341_RGB_DPL) + info->bus_flags |= DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE; + else + info->bus_flags |= DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE; + + if (ili->conf->rgb_interface & ILI9341_RGB_EPL) + info->bus_flags |= DRM_BUS_FLAG_DE_LOW; + else + info->bus_flags |= DRM_BUS_FLAG_DE_HIGH; + + mode = drm_mode_duplicate(drm, &ili->conf->mode); + if (!mode) { + drm_err(drm, "bad mode or failed to add mode\n"); + return -EINVAL; + } + drm_mode_set_name(mode); + + /* Set up the polarity */ + if (ili->conf->rgb_interface & ILI9341_RGB_HSPL) + mode->flags |= DRM_MODE_FLAG_PHSYNC; + else + mode->flags |= DRM_MODE_FLAG_NHSYNC; + + if (ili->conf->rgb_interface & ILI9341_RGB_VSPL) + mode->flags |= DRM_MODE_FLAG_PVSYNC; + else + mode->flags |= DRM_MODE_FLAG_NVSYNC; + + drm_mode_probed_add(connector, mode); + + return 1; /* Number of modes */ +} + +static const struct drm_panel_funcs ili9341_dpi_funcs = { + .disable = ili9341_dpi_disable, + .unprepare = ili9341_dpi_unprepare, + .prepare = ili9341_dpi_prepare, + .enable = ili9341_dpi_enable, + .get_modes = ili9341_dpi_get_modes, +}; + +static int ili9341_dpi_probe(struct spi_device *spi, struct gpio_desc *dc, + struct gpio_desc *reset) +{ + struct device *dev = &spi->dev; + struct ili9341 *ili; + int ret; + + ili = devm_drm_panel_alloc(dev, struct ili9341, panel, + &ili9341_dpi_funcs, + DRM_MODE_CONNECTOR_DPI); + if (IS_ERR(ili)) + return PTR_ERR(ili); + + ili->dbi = devm_kzalloc(dev, sizeof(struct mipi_dbi), + GFP_KERNEL); + if (!ili->dbi) + return -ENOMEM; + + ili->supplies[0].supply = "vci"; + ili->supplies[1].supply = "vddi"; + ili->supplies[2].supply = "vddi-led"; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ili->supplies), + ili->supplies); + if (ret < 0) { + dev_err(dev, "failed to get regulators: %d\n", ret); + return ret; + } + + ret = mipi_dbi_spi_init(spi, ili->dbi, dc); + if (ret) + return ret; + + spi_set_drvdata(spi, ili); + ili->reset_gpio = reset; + /* + * Every new incarnation of this display must have a unique + * data entry for the system in this driver. + */ + ili->conf = device_get_match_data(dev); + if (!ili->conf) { + dev_err(dev, "missing device configuration\n"); + return -ENODEV; + } + + ili->max_spi_speed = ili->conf->max_spi_speed; + drm_panel_add(&ili->panel); + + return 0; +} + +static int ili9341_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct gpio_desc *dc; + struct gpio_desc *reset; + + reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(reset)) + return dev_err_probe(dev, PTR_ERR(reset), "Failed to get gpio 'reset'\n"); + + dc = devm_gpiod_get_optional(dev, "dc", GPIOD_OUT_LOW); + if (IS_ERR(dc)) + return dev_err_probe(dev, PTR_ERR(dc), "Failed to get gpio 'dc'\n"); + + return ili9341_dpi_probe(spi, dc, reset); +} + +static void ili9341_remove(struct spi_device *spi) +{ + struct ili9341 *ili = spi_get_drvdata(spi); + + ili9341_dpi_power_off(ili); + drm_panel_remove(&ili->panel); +} + +static const struct of_device_id ili9341_of_match[] = { + { + .compatible = "st,sf-tc240t-9370-t", + .data = &ili9341_stm32f429_disco_data, + }, + { } +}; +MODULE_DEVICE_TABLE(of, ili9341_of_match); + +static const struct spi_device_id ili9341_id[] = { + { "sf-tc240t-9370-t", 0 }, + { } +}; +MODULE_DEVICE_TABLE(spi, ili9341_id); + +static struct spi_driver ili9341_driver = { + .probe = ili9341_probe, + .remove = ili9341_remove, + .id_table = ili9341_id, + .driver = { + .name = "panel-ilitek-ili9341", + .of_match_table = ili9341_of_match, + }, +}; +module_spi_driver(ili9341_driver); + +MODULE_AUTHOR("Dillon Min <dillon.minfei@gmail.com>"); +MODULE_DESCRIPTION("ILI9341 LCD panel driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-ilitek-ili9805.c b/drivers/gpu/drm/panel/panel-ilitek-ili9805.c new file mode 100644 index 000000000000..e6c483851f1f --- /dev/null +++ b/drivers/gpu/drm/panel/panel-ilitek-ili9805.c @@ -0,0 +1,405 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 BSH Hausgerate GmbH + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> + +#include <linux/gpio/consumer.h> +#include <linux/regulator/consumer.h> + +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +#include <video/mipi_display.h> + +#define ILI9805_EXTCMD_CMD_SET_ENABLE_REG (0xff) +#define ILI9805_SETEXTC_PARAMETER1 (0xff) +#define ILI9805_SETEXTC_PARAMETER2 (0x98) +#define ILI9805_SETEXTC_PARAMETER3 (0x05) + +#define ILI9805_INSTR(_delay, ...) { \ + .delay = (_delay), \ + .len = sizeof((u8[]) {__VA_ARGS__}), \ + .data = (u8[]){__VA_ARGS__} \ + } + +struct ili9805_instr { + size_t len; + const u8 *data; + u32 delay; +}; + +struct ili9805_desc { + const char *name; + const struct ili9805_instr *init; + const size_t init_length; + const struct drm_display_mode *mode; + u32 width_mm; + u32 height_mm; +}; + +struct ili9805 { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + const struct ili9805_desc *desc; + + struct regulator *dvdd; + struct regulator *avdd; + struct gpio_desc *reset_gpio; +}; + +static const struct ili9805_instr gpm1780a0_init[] = { + ILI9805_INSTR(100, ILI9805_EXTCMD_CMD_SET_ENABLE_REG, ILI9805_SETEXTC_PARAMETER1, + ILI9805_SETEXTC_PARAMETER2, ILI9805_SETEXTC_PARAMETER3), + ILI9805_INSTR(100, 0xFD, 0x0F, 0x10, 0x44, 0x00), + ILI9805_INSTR(0, 0xf8, 0x18, 0x02, 0x02, 0x18, 0x02, 0x02, 0x30, 0x00, + 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00), + ILI9805_INSTR(0, 0xB8, 0x62), + ILI9805_INSTR(0, 0xF1, 0x00), + ILI9805_INSTR(0, 0xF2, 0x00, 0x58, 0x40), + ILI9805_INSTR(0, 0xF3, 0x60, 0x83, 0x04), + ILI9805_INSTR(0, 0xFC, 0x04, 0x0F, 0x01), + ILI9805_INSTR(0, 0xEB, 0x08, 0x0F), + ILI9805_INSTR(0, 0xe0, 0x00, 0x08, 0x0d, 0x0e, 0x0e, 0x0d, 0x0a, 0x08, 0x04, + 0x08, 0x0d, 0x0f, 0x0b, 0x1c, 0x14, 0x0a), + ILI9805_INSTR(0, 0xe1, 0x00, 0x08, 0x0d, 0x0e, 0x0e, 0x0d, 0x0a, 0x08, 0x04, + 0x08, 0x0d, 0x0f, 0x0b, 0x1c, 0x14, 0x0a), + ILI9805_INSTR(10, 0xc1, 0x13, 0x39, 0x19, 0x06), + ILI9805_INSTR(10, 0xc7, 0xe5), + ILI9805_INSTR(10, 0xB1, 0x00, 0x12, 0x14), + ILI9805_INSTR(10, 0xB4, 0x02), + ILI9805_INSTR(0, 0xBB, 0x14, 0x55), + ILI9805_INSTR(0, MIPI_DCS_SET_ADDRESS_MODE, 0x08), + ILI9805_INSTR(0, MIPI_DCS_SET_PIXEL_FORMAT, 0x77), + ILI9805_INSTR(0, 0x20), + ILI9805_INSTR(0, 0xB0, 0x01), + ILI9805_INSTR(0, 0xB6, 0x31, 0x00, 0xef), + ILI9805_INSTR(0, 0xDF, 0x23), + ILI9805_INSTR(0, 0xB9, 0x02, 0x00), +}; + +static const struct ili9805_instr tm041xdhg01_init[] = { + ILI9805_INSTR(100, ILI9805_EXTCMD_CMD_SET_ENABLE_REG, ILI9805_SETEXTC_PARAMETER1, + ILI9805_SETEXTC_PARAMETER2, ILI9805_SETEXTC_PARAMETER3), + ILI9805_INSTR(100, 0xFD, 0x0F, 0x13, 0x44, 0x00), + ILI9805_INSTR(0, 0xf8, 0x18, 0x02, 0x02, 0x18, 0x02, 0x02, 0x30, 0x01, + 0x01, 0x30, 0x01, 0x01, 0x30, 0x01, 0x01), + ILI9805_INSTR(0, 0xB8, 0x74), + ILI9805_INSTR(0, 0xF1, 0x00), + ILI9805_INSTR(0, 0xF2, 0x00, 0x58, 0x40), + ILI9805_INSTR(0, 0xFC, 0x04, 0x0F, 0x01), + ILI9805_INSTR(0, 0xEB, 0x08, 0x0F), + ILI9805_INSTR(0, 0xe0, 0x01, 0x0d, 0x15, 0x0e, 0x0f, 0x0f, 0x0b, 0x08, 0x04, + 0x07, 0x0a, 0x0d, 0x0c, 0x15, 0x0f, 0x08), + ILI9805_INSTR(0, 0xe1, 0x01, 0x0d, 0x15, 0x0e, 0x0f, 0x0f, 0x0b, 0x08, 0x04, + 0x07, 0x0a, 0x0d, 0x0c, 0x15, 0x0f, 0x08), + ILI9805_INSTR(10, 0xc1, 0x15, 0x03, 0x03, 0x31), + ILI9805_INSTR(10, 0xB1, 0x00, 0x12, 0x14), + ILI9805_INSTR(10, 0xB4, 0x02), + ILI9805_INSTR(0, 0xBB, 0x14, 0x55), + ILI9805_INSTR(0, MIPI_DCS_SET_ADDRESS_MODE, 0x0a), + ILI9805_INSTR(0, MIPI_DCS_SET_PIXEL_FORMAT, 0x77), + ILI9805_INSTR(0, 0x20), + ILI9805_INSTR(0, 0xB0, 0x00), + ILI9805_INSTR(0, 0xB6, 0x01), + ILI9805_INSTR(0, 0xc2, 0x11), + ILI9805_INSTR(0, 0x51, 0xFF), + ILI9805_INSTR(0, 0x53, 0x24), + ILI9805_INSTR(0, 0x55, 0x00), +}; + +static inline struct ili9805 *panel_to_ili9805(struct drm_panel *panel) +{ + return container_of(panel, struct ili9805, panel); +} + +static int ili9805_power_on(struct ili9805 *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct device *dev = &dsi->dev; + int ret; + + ret = regulator_enable(ctx->avdd); + if (ret) { + dev_err(dev, "Failed to enable avdd regulator (%d)\n", ret); + return ret; + } + + ret = regulator_enable(ctx->dvdd); + if (ret) { + dev_err(dev, "Failed to enable dvdd regulator (%d)\n", ret); + regulator_disable(ctx->avdd); + return ret; + } + + gpiod_set_value(ctx->reset_gpio, 0); + usleep_range(5000, 10000); + gpiod_set_value(ctx->reset_gpio, 1); + msleep(120); + + return 0; +} + +static int ili9805_power_off(struct ili9805 *ctx) +{ + gpiod_set_value(ctx->reset_gpio, 0); + regulator_disable(ctx->dvdd); + regulator_disable(ctx->avdd); + + return 0; +} + +static int ili9805_activate(struct ili9805 *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct device *dev = &dsi->dev; + int i, ret; + + for (i = 0; i < ctx->desc->init_length; i++) { + const struct ili9805_instr *instr = &ctx->desc->init[i]; + + ret = mipi_dsi_dcs_write_buffer(ctx->dsi, instr->data, instr->len); + if (ret < 0) + return ret; + + if (instr->delay > 0) + msleep(instr->delay); + } + + ret = mipi_dsi_dcs_exit_sleep_mode(ctx->dsi); + if (ret) { + dev_err(dev, "Failed to exit sleep mode (%d)\n", ret); + return ret; + } + + usleep_range(5000, 6000); + + ret = mipi_dsi_dcs_set_display_on(ctx->dsi); + if (ret) { + dev_err(dev, "Failed to set display ON (%d)\n", ret); + return ret; + } + + return 0; +} + +static int ili9805_prepare(struct drm_panel *panel) +{ + struct ili9805 *ctx = panel_to_ili9805(panel); + int ret; + + ret = ili9805_power_on(ctx); + if (ret) + return ret; + + ret = ili9805_activate(ctx); + if (ret) { + ili9805_power_off(ctx); + return ret; + } + + return 0; +} + +static int ili9805_deactivate(struct ili9805 *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct device *dev = &dsi->dev; + int ret; + + ret = mipi_dsi_dcs_set_display_off(ctx->dsi); + if (ret < 0) { + dev_err(dev, "Failed to set display OFF (%d)\n", ret); + return ret; + } + + usleep_range(5000, 10000); + + ret = mipi_dsi_dcs_enter_sleep_mode(ctx->dsi); + if (ret < 0) { + dev_err(dev, "Failed to enter sleep mode (%d)\n", ret); + return ret; + } + + return 0; +} + +static int ili9805_unprepare(struct drm_panel *panel) +{ + struct ili9805 *ctx = panel_to_ili9805(panel); + + ili9805_deactivate(ctx); + ili9805_power_off(ctx); + + return 0; +} + +static const struct drm_display_mode gpm1780a0_timing = { + .clock = 26227, + + .hdisplay = 480, + .hsync_start = 480 + 10, + .hsync_end = 480 + 10 + 2, + .htotal = 480 + 10 + 2 + 36, + + .vdisplay = 480, + .vsync_start = 480 + 2, + .vsync_end = 480 + 10 + 4, + .vtotal = 480 + 2 + 4 + 10, +}; + +static const struct drm_display_mode tm041xdhg01_timing = { + .clock = 26227, + + .hdisplay = 480, + .hsync_start = 480 + 10, + .hsync_end = 480 + 10 + 2, + .htotal = 480 + 10 + 2 + 36, + + .vdisplay = 768, + .vsync_start = 768 + 2, + .vsync_end = 768 + 10 + 4, + .vtotal = 768 + 2 + 4 + 10, +}; + +static int ili9805_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct ili9805 *ctx = panel_to_ili9805(panel); + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, ctx->desc->mode); + if (!mode) { + dev_err(&ctx->dsi->dev, "failed to add mode %ux%ux@%u\n", + ctx->desc->mode->hdisplay, + ctx->desc->mode->vdisplay, + drm_mode_vrefresh(ctx->desc->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.width_mm = mode->width_mm; + connector->display_info.height_mm = mode->height_mm; + + return 1; +} + +static const struct drm_panel_funcs ili9805_funcs = { + .prepare = ili9805_prepare, + .unprepare = ili9805_unprepare, + .get_modes = ili9805_get_modes, +}; + +static int ili9805_dsi_probe(struct mipi_dsi_device *dsi) +{ + struct ili9805 *ctx; + int ret; + + ctx = devm_drm_panel_alloc(&dsi->dev, struct ili9805, panel, + &ili9805_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + mipi_dsi_set_drvdata(dsi, ctx); + ctx->dsi = dsi; + ctx->desc = of_device_get_match_data(&dsi->dev); + + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | + MIPI_DSI_CLOCK_NON_CONTINUOUS | MIPI_DSI_MODE_LPM | + MIPI_DSI_MODE_VIDEO_SYNC_PULSE | MIPI_DSI_MODE_NO_EOT_PACKET; + dsi->lanes = 2; + + ctx->dvdd = devm_regulator_get(&dsi->dev, "dvdd"); + if (IS_ERR(ctx->dvdd)) + return PTR_ERR(ctx->dvdd); + ctx->avdd = devm_regulator_get(&dsi->dev, "avdd"); + if (IS_ERR(ctx->avdd)) + return PTR_ERR(ctx->avdd); + + ctx->reset_gpio = devm_gpiod_get(&dsi->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->reset_gpio)) { + dev_err(&dsi->dev, "Couldn't get our reset GPIO\n"); + return PTR_ERR(ctx->reset_gpio); + } + + ctx->panel.prepare_prev_first = true; + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return ret; + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(&dsi->dev, "mipi_dsi_attach failed: %d\n", ret); + drm_panel_remove(&ctx->panel); + return ret; + } + + return 0; +} + +static void ili9805_dsi_remove(struct mipi_dsi_device *dsi) +{ + struct ili9805 *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", + ret); + + drm_panel_remove(&ctx->panel); +} + +static const struct ili9805_desc gpm1780a0_desc = { + .init = gpm1780a0_init, + .init_length = ARRAY_SIZE(gpm1780a0_init), + .mode = &gpm1780a0_timing, + .width_mm = 65, + .height_mm = 65, +}; + +static const struct ili9805_desc tm041xdhg01_desc = { + .init = tm041xdhg01_init, + .init_length = ARRAY_SIZE(tm041xdhg01_init), + .mode = &tm041xdhg01_timing, + .width_mm = 42, + .height_mm = 96, +}; + +static const struct of_device_id ili9805_of_match[] = { + { .compatible = "giantplus,gpm1790a0", .data = &gpm1780a0_desc }, + { .compatible = "tianma,tm041xdhg01", .data = &tm041xdhg01_desc }, + { } +}; +MODULE_DEVICE_TABLE(of, ili9805_of_match); + +static struct mipi_dsi_driver ili9805_dsi_driver = { + .probe = ili9805_dsi_probe, + .remove = ili9805_dsi_remove, + .driver = { + .name = "ili9805-dsi", + .of_match_table = ili9805_of_match, + }, +}; +module_mipi_dsi_driver(ili9805_dsi_driver); + +MODULE_AUTHOR("Matthias Proske <Matthias.Proske@bshg.com>"); +MODULE_AUTHOR("Michael Trimarchi <michael@amarulasolutions.com>"); +MODULE_DESCRIPTION("Ilitek ILI9805 Controller Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-ilitek-ili9806e.c b/drivers/gpu/drm/panel/panel-ilitek-ili9806e.c new file mode 100644 index 000000000000..18aa6222b0c5 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-ilitek-ili9806e.c @@ -0,0 +1,565 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/gpio/consumer.h> +#include <linux/kernel.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/property.h> +#include <linux/regulator/consumer.h> + +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> +#include <drm/drm_probe_helper.h> + +#include <video/mipi_display.h> + +struct panel_desc { + const struct drm_display_mode *display_mode; + unsigned long mode_flags; + enum mipi_dsi_pixel_format format; + unsigned int lanes; + void (*init_sequence)(struct mipi_dsi_multi_context *ctx); +}; + +struct ili9806e_panel { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[2]; + const struct panel_desc *desc; + enum drm_panel_orientation orientation; +}; + +static const char * const regulator_names[] = { + "vdd", + "vccio", +}; + +static inline struct ili9806e_panel *to_ili9806e_panel(struct drm_panel *panel) +{ + return container_of(panel, struct ili9806e_panel, panel); +} + +static int ili9806e_power_on(struct ili9806e_panel *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + int ret; + + gpiod_set_value(ctx->reset_gpio, 1); + + ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + if (ret < 0) { + dev_err(&dsi->dev, "regulator bulk enable failed: %d\n", ret); + return ret; + } + + usleep_range(10000, 20000); + gpiod_set_value(ctx->reset_gpio, 0); + usleep_range(10000, 20000); + + return 0; +} + +static int ili9806e_power_off(struct ili9806e_panel *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + int ret; + + gpiod_set_value(ctx->reset_gpio, 1); + + ret = regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + if (ret) + dev_err(&dsi->dev, "regulator bulk disable failed: %d\n", ret); + + return ret; +} + +static int ili9806e_on(struct ili9806e_panel *ili9806e) +{ + struct mipi_dsi_multi_context ctx = { .dsi = ili9806e->dsi }; + + if (ili9806e->desc->init_sequence) + ili9806e->desc->init_sequence(&ctx); + + mipi_dsi_dcs_exit_sleep_mode_multi(&ctx); + mipi_dsi_msleep(&ctx, 120); + mipi_dsi_dcs_set_display_on_multi(&ctx); + + return ctx.accum_err; +} + +static int ili9806e_off(struct ili9806e_panel *panel) +{ + struct mipi_dsi_multi_context ctx = { .dsi = panel->dsi }; + + mipi_dsi_dcs_set_display_off_multi(&ctx); + mipi_dsi_dcs_enter_sleep_mode_multi(&ctx); + mipi_dsi_msleep(&ctx, 120); + + return ctx.accum_err; +} + +static int ili9806e_prepare(struct drm_panel *panel) +{ + struct ili9806e_panel *ctx = to_ili9806e_panel(panel); + int ret; + + ret = ili9806e_power_on(ctx); + if (ret < 0) + return ret; + + ret = ili9806e_on(ctx); + if (ret < 0) { + ili9806e_power_off(ctx); + return ret; + } + + return 0; +} + +static int ili9806e_unprepare(struct drm_panel *panel) +{ + struct ili9806e_panel *ctx = to_ili9806e_panel(panel); + struct mipi_dsi_device *dsi = ctx->dsi; + int ret; + + ili9806e_off(ctx); + + ret = ili9806e_power_off(ctx); + if (ret < 0) + dev_err(&dsi->dev, "power off failed: %d\n", ret); + + return ret; +} + +static int ili9806e_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct ili9806e_panel *ctx = to_ili9806e_panel(panel); + const struct drm_display_mode *mode = ctx->desc->display_mode; + + return drm_connector_helper_get_modes_fixed(connector, mode); +} + +static enum drm_panel_orientation ili9806e_get_orientation(struct drm_panel *panel) +{ + struct ili9806e_panel *ctx = to_ili9806e_panel(panel); + + return ctx->orientation; +} + +static const struct drm_panel_funcs ili9806e_funcs = { + .prepare = ili9806e_prepare, + .unprepare = ili9806e_unprepare, + .get_modes = ili9806e_get_modes, + .get_orientation = ili9806e_get_orientation, +}; + +static int ili9806e_dsi_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct ili9806e_panel *ctx; + int i, ret; + + ctx = devm_drm_panel_alloc(dev, struct ili9806e_panel, panel, &ili9806e_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ctx->desc = device_get_match_data(dev); + + for (i = 0; i < ARRAY_SIZE(ctx->supplies); i++) + ctx->supplies[i].supply = regulator_names[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), + ctx->supplies); + if (ret < 0) + return ret; + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), + "Failed to get reset-gpios\n"); + + mipi_dsi_set_drvdata(dsi, ctx); + ctx->dsi = dsi; + + dsi->mode_flags = ctx->desc->mode_flags; + dsi->format = ctx->desc->format; + dsi->lanes = ctx->desc->lanes; + + ret = of_drm_get_panel_orientation(dev->of_node, &ctx->orientation); + if (ret) + return dev_err_probe(dev, ret, "Failed to get orientation\n"); + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return dev_err_probe(dev, ret, "Failed to get backlight\n"); + + ctx->panel.prepare_prev_first = true; + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err_probe(dev, ret, "Failed to attach to DSI host\n"); + drm_panel_remove(&ctx->panel); + return ret; + } + + return 0; +} + +static void ili9806e_dsi_remove(struct mipi_dsi_device *dsi) +{ + struct ili9806e_panel *ctx = mipi_dsi_get_drvdata(dsi); + + mipi_dsi_detach(dsi); + drm_panel_remove(&ctx->panel); +} + +static void com35h3p70ulc_init(struct mipi_dsi_multi_context *ctx) +{ + /* Switch to page 1 */ + mipi_dsi_dcs_write_seq_multi(ctx, 0xff, 0xff, 0x98, 0x06, 0x04, 0x01); + /* Interface Settings */ + mipi_dsi_dcs_write_seq_multi(ctx, 0x08, 0x18); + mipi_dsi_dcs_write_seq_multi(ctx, 0x21, 0x01); + /* Panel Settings */ + mipi_dsi_dcs_write_seq_multi(ctx, 0x30, 0x03); + mipi_dsi_dcs_write_seq_multi(ctx, 0x31, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x60, 0x0d); + mipi_dsi_dcs_write_seq_multi(ctx, 0x61, 0x08); + mipi_dsi_dcs_write_seq_multi(ctx, 0x62, 0x08); + mipi_dsi_dcs_write_seq_multi(ctx, 0x63, 0x09); + /* Power Control */ + mipi_dsi_dcs_write_seq_multi(ctx, 0x40, 0x30); + mipi_dsi_dcs_write_seq_multi(ctx, 0x41, 0x44); + mipi_dsi_dcs_write_seq_multi(ctx, 0x42, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x43, 0x89); + mipi_dsi_dcs_write_seq_multi(ctx, 0x44, 0x8e); + mipi_dsi_dcs_write_seq_multi(ctx, 0x45, 0xd9); + mipi_dsi_dcs_write_seq_multi(ctx, 0x46, 0x33); + mipi_dsi_dcs_write_seq_multi(ctx, 0x47, 0x33); + mipi_dsi_dcs_write_seq_multi(ctx, 0x50, 0x90); + mipi_dsi_dcs_write_seq_multi(ctx, 0x51, 0x90); + mipi_dsi_dcs_write_seq_multi(ctx, 0x56, 0x00); + /* Gamma Settings */ + mipi_dsi_dcs_write_seq_multi(ctx, 0xa0, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0xa1, 0x0c); + mipi_dsi_dcs_write_seq_multi(ctx, 0xa2, 0x13); + mipi_dsi_dcs_write_seq_multi(ctx, 0xa3, 0x0f); + mipi_dsi_dcs_write_seq_multi(ctx, 0xa4, 0x0a); + mipi_dsi_dcs_write_seq_multi(ctx, 0xa5, 0x0d); + mipi_dsi_dcs_write_seq_multi(ctx, 0xa6, 0x0c); + mipi_dsi_dcs_write_seq_multi(ctx, 0xa7, 0x0b); + mipi_dsi_dcs_write_seq_multi(ctx, 0xa8, 0x01); + mipi_dsi_dcs_write_seq_multi(ctx, 0xa9, 0x06); + mipi_dsi_dcs_write_seq_multi(ctx, 0xaa, 0x15); + mipi_dsi_dcs_write_seq_multi(ctx, 0xab, 0x07); + mipi_dsi_dcs_write_seq_multi(ctx, 0xac, 0x12); + mipi_dsi_dcs_write_seq_multi(ctx, 0xad, 0x28); + mipi_dsi_dcs_write_seq_multi(ctx, 0xae, 0x20); + mipi_dsi_dcs_write_seq_multi(ctx, 0xaf, 0x14); + mipi_dsi_dcs_write_seq_multi(ctx, 0xc0, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0xc1, 0x0c); + mipi_dsi_dcs_write_seq_multi(ctx, 0xc2, 0x13); + mipi_dsi_dcs_write_seq_multi(ctx, 0xc3, 0x0f); + mipi_dsi_dcs_write_seq_multi(ctx, 0xc4, 0x09); + mipi_dsi_dcs_write_seq_multi(ctx, 0xc5, 0x0d); + mipi_dsi_dcs_write_seq_multi(ctx, 0xc6, 0x0c); + mipi_dsi_dcs_write_seq_multi(ctx, 0xc7, 0x0b); + mipi_dsi_dcs_write_seq_multi(ctx, 0xc8, 0x01); + mipi_dsi_dcs_write_seq_multi(ctx, 0xc9, 0x06); + mipi_dsi_dcs_write_seq_multi(ctx, 0xca, 0x14); + mipi_dsi_dcs_write_seq_multi(ctx, 0xcb, 0x07); + mipi_dsi_dcs_write_seq_multi(ctx, 0xcc, 0x0f); + mipi_dsi_dcs_write_seq_multi(ctx, 0xcd, 0x21); + mipi_dsi_dcs_write_seq_multi(ctx, 0xce, 0x17); + mipi_dsi_dcs_write_seq_multi(ctx, 0xcf, 0x0a); + + /* Switch to page 7 */ + mipi_dsi_dcs_write_seq_multi(ctx, 0xff, 0xff, 0x98, 0x06, 0x04, 0x07); + /* Power Control */ + mipi_dsi_dcs_write_seq_multi(ctx, 0x06, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x18, 0x1d); + mipi_dsi_dcs_write_seq_multi(ctx, 0x17, 0x32); + + /* Switch to page 6 */ + mipi_dsi_dcs_write_seq_multi(ctx, 0xff, 0xff, 0x98, 0x06, 0x04, 0x06); + /* GIP settings */ + mipi_dsi_dcs_write_seq_multi(ctx, 0x00, 0x20); + mipi_dsi_dcs_write_seq_multi(ctx, 0x01, 0x02); + mipi_dsi_dcs_write_seq_multi(ctx, 0x02, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x03, 0x02); + mipi_dsi_dcs_write_seq_multi(ctx, 0x04, 0x01); + mipi_dsi_dcs_write_seq_multi(ctx, 0x05, 0x01); + mipi_dsi_dcs_write_seq_multi(ctx, 0x06, 0x88); + mipi_dsi_dcs_write_seq_multi(ctx, 0x07, 0x04); + mipi_dsi_dcs_write_seq_multi(ctx, 0x08, 0x03); + mipi_dsi_dcs_write_seq_multi(ctx, 0x09, 0x80); + mipi_dsi_dcs_write_seq_multi(ctx, 0x0a, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x0b, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x0c, 0x01); + mipi_dsi_dcs_write_seq_multi(ctx, 0x0d, 0x01); + mipi_dsi_dcs_write_seq_multi(ctx, 0x0e, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x0f, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x10, 0x55); + mipi_dsi_dcs_write_seq_multi(ctx, 0x11, 0x50); + mipi_dsi_dcs_write_seq_multi(ctx, 0x12, 0x01); + mipi_dsi_dcs_write_seq_multi(ctx, 0x13, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x14, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x15, 0x43); + mipi_dsi_dcs_write_seq_multi(ctx, 0x16, 0x0b); + mipi_dsi_dcs_write_seq_multi(ctx, 0x17, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x18, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x19, 0x10); + mipi_dsi_dcs_write_seq_multi(ctx, 0x1a, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x1b, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x1c, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x1d, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x20, 0x01); + mipi_dsi_dcs_write_seq_multi(ctx, 0x21, 0x23); + mipi_dsi_dcs_write_seq_multi(ctx, 0x22, 0x45); + mipi_dsi_dcs_write_seq_multi(ctx, 0x23, 0x67); + mipi_dsi_dcs_write_seq_multi(ctx, 0x24, 0x01); + mipi_dsi_dcs_write_seq_multi(ctx, 0x25, 0x23); + mipi_dsi_dcs_write_seq_multi(ctx, 0x26, 0x45); + mipi_dsi_dcs_write_seq_multi(ctx, 0x27, 0x67); + mipi_dsi_dcs_write_seq_multi(ctx, 0x30, 0x02); + mipi_dsi_dcs_write_seq_multi(ctx, 0x31, 0x22); + mipi_dsi_dcs_write_seq_multi(ctx, 0x32, 0x22); + mipi_dsi_dcs_write_seq_multi(ctx, 0x33, 0x88); + mipi_dsi_dcs_write_seq_multi(ctx, 0x34, 0xaa); + mipi_dsi_dcs_write_seq_multi(ctx, 0x35, 0xbb); + mipi_dsi_dcs_write_seq_multi(ctx, 0x36, 0x66); + mipi_dsi_dcs_write_seq_multi(ctx, 0x37, 0x22); + mipi_dsi_dcs_write_seq_multi(ctx, 0x38, 0x22); + mipi_dsi_dcs_write_seq_multi(ctx, 0x39, 0x22); + mipi_dsi_dcs_write_seq_multi(ctx, 0x3a, 0x22); + mipi_dsi_dcs_write_seq_multi(ctx, 0x3b, 0x22); + mipi_dsi_dcs_write_seq_multi(ctx, 0x3c, 0x22); + mipi_dsi_dcs_write_seq_multi(ctx, 0x3d, 0x22); + mipi_dsi_dcs_write_seq_multi(ctx, 0x3e, 0x22); + mipi_dsi_dcs_write_seq_multi(ctx, 0x3f, 0x22); + mipi_dsi_dcs_write_seq_multi(ctx, 0x40, 0x22); + mipi_dsi_dcs_write_seq_multi(ctx, 0x53, 0x12); + + /* Switch to page 0 */ + mipi_dsi_dcs_write_seq_multi(ctx, 0xff, 0xff, 0x98, 0x06, 0x04, 0x00); + /* Interface Pixel format */ + mipi_dsi_dcs_write_seq_multi(ctx, 0x3a, 0x60); +}; + +static const struct drm_display_mode com35h3p70ulc_default_mode = { + .clock = 22400, + .hdisplay = 480, + .hsync_start = 480 + 16, + .hsync_end = 480 + 16 + 16, + .htotal = 480 + 16 + 16 + 16, + .vdisplay = 640, + .vsync_start = 640 + 52, + .vsync_end = 640 + 52 + 4, + .vtotal = 640 + 52 + 4 + 16, + .width_mm = 53, + .height_mm = 71, +}; + +static const struct panel_desc com35h3p70ulc_desc = { + .init_sequence = com35h3p70ulc_init, + .display_mode = &com35h3p70ulc_default_mode, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_LPM, + .format = MIPI_DSI_FMT_RGB888, + .lanes = 2, +}; + +static void dmt028vghmcmi_1d_init(struct mipi_dsi_multi_context *ctx) +{ + mipi_dsi_dcs_write_seq_multi(ctx, 0xff, 0xff, 0x98, 0x06, 0x04, 0x01); + mipi_dsi_dcs_write_seq_multi(ctx, 0x08, 0x10); + mipi_dsi_dcs_write_seq_multi(ctx, 0x21, 0x01); + mipi_dsi_dcs_write_seq_multi(ctx, 0x30, 0x03); + mipi_dsi_dcs_write_seq_multi(ctx, 0x31, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x60, 0x06); + mipi_dsi_dcs_write_seq_multi(ctx, 0x61, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x62, 0x07); + mipi_dsi_dcs_write_seq_multi(ctx, 0x63, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x40, 0x16); + mipi_dsi_dcs_write_seq_multi(ctx, 0x41, 0x44); + mipi_dsi_dcs_write_seq_multi(ctx, 0x42, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x43, 0x83); + mipi_dsi_dcs_write_seq_multi(ctx, 0x44, 0x89); + mipi_dsi_dcs_write_seq_multi(ctx, 0x45, 0x8a); + mipi_dsi_dcs_write_seq_multi(ctx, 0x46, 0x44); + mipi_dsi_dcs_write_seq_multi(ctx, 0x47, 0x44); + mipi_dsi_dcs_write_seq_multi(ctx, 0x50, 0x78); + mipi_dsi_dcs_write_seq_multi(ctx, 0x51, 0x78); + mipi_dsi_dcs_write_seq_multi(ctx, 0x52, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x53, 0x6c); + mipi_dsi_dcs_write_seq_multi(ctx, 0x54, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x55, 0x6c); + mipi_dsi_dcs_write_seq_multi(ctx, 0x56, 0x00); + /* Gamma settings */ + mipi_dsi_dcs_write_seq_multi(ctx, 0xa0, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0xa1, 0x09); + mipi_dsi_dcs_write_seq_multi(ctx, 0xa2, 0x14); + mipi_dsi_dcs_write_seq_multi(ctx, 0xa3, 0x09); + mipi_dsi_dcs_write_seq_multi(ctx, 0xa4, 0x05); + mipi_dsi_dcs_write_seq_multi(ctx, 0xa5, 0x0a); + mipi_dsi_dcs_write_seq_multi(ctx, 0xa6, 0x07); + mipi_dsi_dcs_write_seq_multi(ctx, 0xa7, 0x07); + mipi_dsi_dcs_write_seq_multi(ctx, 0xa8, 0x08); + mipi_dsi_dcs_write_seq_multi(ctx, 0xa9, 0x0b); + mipi_dsi_dcs_write_seq_multi(ctx, 0xaa, 0x0c); + mipi_dsi_dcs_write_seq_multi(ctx, 0xab, 0x05); + mipi_dsi_dcs_write_seq_multi(ctx, 0xac, 0x0a); + mipi_dsi_dcs_write_seq_multi(ctx, 0xad, 0x19); + mipi_dsi_dcs_write_seq_multi(ctx, 0xae, 0x0b); + mipi_dsi_dcs_write_seq_multi(ctx, 0xaf, 0x00); + + mipi_dsi_dcs_write_seq_multi(ctx, 0xc0, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0xc1, 0x0c); + mipi_dsi_dcs_write_seq_multi(ctx, 0xc2, 0x14); + mipi_dsi_dcs_write_seq_multi(ctx, 0xc3, 0x11); + mipi_dsi_dcs_write_seq_multi(ctx, 0xc4, 0x05); + mipi_dsi_dcs_write_seq_multi(ctx, 0xc5, 0x0c); + mipi_dsi_dcs_write_seq_multi(ctx, 0xc6, 0x08); + mipi_dsi_dcs_write_seq_multi(ctx, 0xc7, 0x03); + mipi_dsi_dcs_write_seq_multi(ctx, 0xc8, 0x06); + mipi_dsi_dcs_write_seq_multi(ctx, 0xc9, 0x0a); + mipi_dsi_dcs_write_seq_multi(ctx, 0xca, 0x10); + mipi_dsi_dcs_write_seq_multi(ctx, 0xcb, 0x05); + mipi_dsi_dcs_write_seq_multi(ctx, 0xcc, 0x0d); + mipi_dsi_dcs_write_seq_multi(ctx, 0xcd, 0x15); + mipi_dsi_dcs_write_seq_multi(ctx, 0xce, 0x13); + mipi_dsi_dcs_write_seq_multi(ctx, 0xcf, 0x00); + + mipi_dsi_dcs_write_seq_multi(ctx, 0xff, 0xff, 0x98, 0x06, 0x04, 0x07); + mipi_dsi_dcs_write_seq_multi(ctx, 0x17, 0x22); + mipi_dsi_dcs_write_seq_multi(ctx, 0x18, 0x1d); + mipi_dsi_dcs_write_seq_multi(ctx, 0x02, 0x77); + mipi_dsi_dcs_write_seq_multi(ctx, 0xe1, 0x79); + mipi_dsi_dcs_write_seq_multi(ctx, 0x06, 0x13); + + mipi_dsi_dcs_write_seq_multi(ctx, 0xff, 0xff, 0x98, 0x06, 0x04, 0x06); + /* GIP 0 */ + mipi_dsi_dcs_write_seq_multi(ctx, 0x00, 0x21); + mipi_dsi_dcs_write_seq_multi(ctx, 0x01, 0x0a); + mipi_dsi_dcs_write_seq_multi(ctx, 0x02, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x03, 0x05); + mipi_dsi_dcs_write_seq_multi(ctx, 0x04, 0x01); + mipi_dsi_dcs_write_seq_multi(ctx, 0x05, 0x01); + mipi_dsi_dcs_write_seq_multi(ctx, 0x06, 0x98); + mipi_dsi_dcs_write_seq_multi(ctx, 0x07, 0x06); + mipi_dsi_dcs_write_seq_multi(ctx, 0x08, 0x01); + mipi_dsi_dcs_write_seq_multi(ctx, 0x09, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x0a, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x0b, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x0c, 0x01); + mipi_dsi_dcs_write_seq_multi(ctx, 0x0d, 0x01); + mipi_dsi_dcs_write_seq_multi(ctx, 0x0e, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x0f, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x10, 0xf7); + mipi_dsi_dcs_write_seq_multi(ctx, 0x11, 0xf0); + mipi_dsi_dcs_write_seq_multi(ctx, 0x12, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x13, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x14, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x15, 0xc0); + mipi_dsi_dcs_write_seq_multi(ctx, 0x16, 0x08); + mipi_dsi_dcs_write_seq_multi(ctx, 0x17, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x18, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x19, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x1a, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x1b, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x1c, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x1d, 0x00); + /* GIP 1 */ + mipi_dsi_dcs_write_seq_multi(ctx, 0x20, 0x01); + mipi_dsi_dcs_write_seq_multi(ctx, 0x21, 0x23); + mipi_dsi_dcs_write_seq_multi(ctx, 0x22, 0x44); + mipi_dsi_dcs_write_seq_multi(ctx, 0x23, 0x67); + mipi_dsi_dcs_write_seq_multi(ctx, 0x24, 0x01); + mipi_dsi_dcs_write_seq_multi(ctx, 0x25, 0x23); + mipi_dsi_dcs_write_seq_multi(ctx, 0x26, 0x45); + mipi_dsi_dcs_write_seq_multi(ctx, 0x27, 0x67); + /* GIP 2 */ + mipi_dsi_dcs_write_seq_multi(ctx, 0x30, 0x01); + mipi_dsi_dcs_write_seq_multi(ctx, 0x31, 0x22); + mipi_dsi_dcs_write_seq_multi(ctx, 0x32, 0x22); + mipi_dsi_dcs_write_seq_multi(ctx, 0x33, 0xbc); + mipi_dsi_dcs_write_seq_multi(ctx, 0x34, 0xad); + mipi_dsi_dcs_write_seq_multi(ctx, 0x35, 0xda); + mipi_dsi_dcs_write_seq_multi(ctx, 0x36, 0xcb); + mipi_dsi_dcs_write_seq_multi(ctx, 0x37, 0x22); + mipi_dsi_dcs_write_seq_multi(ctx, 0x38, 0x55); + mipi_dsi_dcs_write_seq_multi(ctx, 0x39, 0x76); + mipi_dsi_dcs_write_seq_multi(ctx, 0x3a, 0x67); + mipi_dsi_dcs_write_seq_multi(ctx, 0x3b, 0x88); + mipi_dsi_dcs_write_seq_multi(ctx, 0x3c, 0x22); + mipi_dsi_dcs_write_seq_multi(ctx, 0x3d, 0x11); + mipi_dsi_dcs_write_seq_multi(ctx, 0x3e, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x3f, 0x22); + mipi_dsi_dcs_write_seq_multi(ctx, 0x40, 0x22); + + mipi_dsi_dcs_write_seq_multi(ctx, 0x52, 0x10); + mipi_dsi_dcs_write_seq_multi(ctx, 0x53, 0x10); + mipi_dsi_dcs_write_seq_multi(ctx, 0x54, 0x13); + + mipi_dsi_dcs_write_seq_multi(ctx, 0xff, 0xff, 0x98, 0x06, 0x04, 0x00); +}; + +static const struct drm_display_mode dmt028vghmcmi_1d_default_mode = { + .clock = 22000, + + .hdisplay = 480, + .hsync_start = 480 + 20, + .hsync_end = 480 + 20 + 4, + .htotal = 480 + 20 + 4 + 10, + + .vdisplay = 640, + .vsync_start = 640 + 40, + .vsync_end = 640 + 40 + 4, + .vtotal = 640 + 40 + 4 + 20, + + .width_mm = 53, + .height_mm = 79, + + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, +}; + +static const struct panel_desc dmt028vghmcmi_1d_desc = { + .init_sequence = dmt028vghmcmi_1d_init, + .display_mode = &dmt028vghmcmi_1d_default_mode, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS, + .format = MIPI_DSI_FMT_RGB888, + .lanes = 2, +}; + +static const struct of_device_id ili9806e_of_match[] = { + { .compatible = "densitron,dmt028vghmcmi-1d", .data = &dmt028vghmcmi_1d_desc }, + { .compatible = "ortustech,com35h3p70ulc", .data = &com35h3p70ulc_desc }, + { } +}; +MODULE_DEVICE_TABLE(of, ili9806e_of_match); + +static struct mipi_dsi_driver ili9806e_dsi_driver = { + .driver = { + .name = "ili9806e-dsi", + .of_match_table = ili9806e_of_match, + }, + .probe = ili9806e_dsi_probe, + .remove = ili9806e_dsi_remove, +}; +module_mipi_dsi_driver(ili9806e_dsi_driver); + +MODULE_AUTHOR("Gunnar Dibbern <gunnar.dibbern@lht.dlh.de>"); +MODULE_AUTHOR("Michael Walle <mwalle@kernel.org>"); +MODULE_DESCRIPTION("Ilitek ILI9806E Controller Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-ilitek-ili9881c.c b/drivers/gpu/drm/panel/panel-ilitek-ili9881c.c index 3ad4a46c4e94..947b47841b01 100644 --- a/drivers/gpu/drm/panel/panel-ilitek-ili9881c.c +++ b/drivers/gpu/drm/panel/panel-ilitek-ili9881c.c @@ -3,14 +3,13 @@ * Copyright (C) 2017-2018, Bootlin */ -#include <linux/backlight.h> #include <linux/delay.h> #include <linux/device.h> #include <linux/err.h> #include <linux/errno.h> -#include <linux/fb.h> #include <linux/kernel.h> #include <linux/module.h> +#include <linux/of.h> #include <linux/gpio/consumer.h> #include <linux/regulator/consumer.h> @@ -21,15 +20,6 @@ #include <video/mipi_display.h> -struct ili9881c { - struct drm_panel panel; - struct mipi_dsi_device *dsi; - - struct backlight_device *backlight; - struct regulator *power; - struct gpio_desc *reset; -}; - enum ili9881c_op { ILI9881C_SWITCH_PAGE, ILI9881C_COMMAND, @@ -47,6 +37,27 @@ struct ili9881c_instr { } arg; }; +struct ili9881c_desc { + const struct ili9881c_instr *init; + const size_t init_length; + const struct drm_display_mode *mode; + const unsigned long mode_flags; + u8 default_address_mode; + unsigned int lanes; +}; + +struct ili9881c { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + const struct ili9881c_desc *desc; + + struct regulator *power; + struct gpio_desc *reset; + + enum drm_panel_orientation orientation; + u8 address_mode; +}; + #define ILI9881C_SWITCH_PAGE_INSTR(_page) \ { \ .op = ILI9881C_SWITCH_PAGE, \ @@ -66,7 +77,7 @@ struct ili9881c_instr { }, \ } -static const struct ili9881c_instr ili9881c_init[] = { +static const struct ili9881c_instr lhr050h41_init[] = { ILI9881C_SWITCH_PAGE_INSTR(3), ILI9881C_COMMAND_INSTR(0x01, 0x00), ILI9881C_COMMAND_INSTR(0x02, 0x00), @@ -89,7 +100,7 @@ static const struct ili9881c_instr ili9881c_init[] = { ILI9881C_COMMAND_INSTR(0x13, 0x00), ILI9881C_COMMAND_INSTR(0x14, 0x00), ILI9881C_COMMAND_INSTR(0x15, 0x00), - ILI9881C_COMMAND_INSTR(0x16, 0x0C), + ILI9881C_COMMAND_INSTR(0x16, 0x0c), ILI9881C_COMMAND_INSTR(0x17, 0x00), ILI9881C_COMMAND_INSTR(0x18, 0x00), ILI9881C_COMMAND_INSTR(0x19, 0x00), @@ -97,7 +108,7 @@ static const struct ili9881c_instr ili9881c_init[] = { ILI9881C_COMMAND_INSTR(0x1b, 0x00), ILI9881C_COMMAND_INSTR(0x1c, 0x00), ILI9881C_COMMAND_INSTR(0x1d, 0x00), - ILI9881C_COMMAND_INSTR(0x1e, 0xC0), + ILI9881C_COMMAND_INSTR(0x1e, 0xc0), ILI9881C_COMMAND_INSTR(0x1f, 0x80), ILI9881C_COMMAND_INSTR(0x20, 0x04), ILI9881C_COMMAND_INSTR(0x21, 0x01), @@ -123,7 +134,7 @@ static const struct ili9881c_instr ili9881c_init[] = { ILI9881C_COMMAND_INSTR(0x35, 0x00), ILI9881C_COMMAND_INSTR(0x36, 0x00), ILI9881C_COMMAND_INSTR(0x37, 0x00), - ILI9881C_COMMAND_INSTR(0x38, 0x3C), + ILI9881C_COMMAND_INSTR(0x38, 0x3c), ILI9881C_COMMAND_INSTR(0x39, 0x00), ILI9881C_COMMAND_INSTR(0x3a, 0x00), ILI9881C_COMMAND_INSTR(0x3b, 0x00), @@ -162,11 +173,11 @@ static const struct ili9881c_instr ili9881c_init[] = { ILI9881C_COMMAND_INSTR(0x67, 0x02), ILI9881C_COMMAND_INSTR(0x68, 0x02), ILI9881C_COMMAND_INSTR(0x69, 0x02), - ILI9881C_COMMAND_INSTR(0x6a, 0x0C), + ILI9881C_COMMAND_INSTR(0x6a, 0x0c), ILI9881C_COMMAND_INSTR(0x6b, 0x02), - ILI9881C_COMMAND_INSTR(0x6c, 0x0F), - ILI9881C_COMMAND_INSTR(0x6d, 0x0E), - ILI9881C_COMMAND_INSTR(0x6e, 0x0D), + ILI9881C_COMMAND_INSTR(0x6c, 0x0f), + ILI9881C_COMMAND_INSTR(0x6d, 0x0e), + ILI9881C_COMMAND_INSTR(0x6e, 0x0d), ILI9881C_COMMAND_INSTR(0x6f, 0x06), ILI9881C_COMMAND_INSTR(0x70, 0x07), ILI9881C_COMMAND_INSTR(0x71, 0x02), @@ -184,74 +195,1806 @@ static const struct ili9881c_instr ili9881c_init[] = { ILI9881C_COMMAND_INSTR(0x7d, 0x02), ILI9881C_COMMAND_INSTR(0x7e, 0x02), ILI9881C_COMMAND_INSTR(0x7f, 0x02), - ILI9881C_COMMAND_INSTR(0x80, 0x0C), + ILI9881C_COMMAND_INSTR(0x80, 0x0c), ILI9881C_COMMAND_INSTR(0x81, 0x02), - ILI9881C_COMMAND_INSTR(0x82, 0x0F), - ILI9881C_COMMAND_INSTR(0x83, 0x0E), - ILI9881C_COMMAND_INSTR(0x84, 0x0D), + ILI9881C_COMMAND_INSTR(0x82, 0x0f), + ILI9881C_COMMAND_INSTR(0x83, 0x0e), + ILI9881C_COMMAND_INSTR(0x84, 0x0d), ILI9881C_COMMAND_INSTR(0x85, 0x06), ILI9881C_COMMAND_INSTR(0x86, 0x07), ILI9881C_COMMAND_INSTR(0x87, 0x02), ILI9881C_COMMAND_INSTR(0x88, 0x02), ILI9881C_COMMAND_INSTR(0x89, 0x02), - ILI9881C_COMMAND_INSTR(0x8A, 0x02), + ILI9881C_COMMAND_INSTR(0x8a, 0x02), ILI9881C_SWITCH_PAGE_INSTR(4), - ILI9881C_COMMAND_INSTR(0x6C, 0x15), - ILI9881C_COMMAND_INSTR(0x6E, 0x22), - ILI9881C_COMMAND_INSTR(0x6F, 0x33), - ILI9881C_COMMAND_INSTR(0x3A, 0xA4), - ILI9881C_COMMAND_INSTR(0x8D, 0x0D), - ILI9881C_COMMAND_INSTR(0x87, 0xBA), + ILI9881C_COMMAND_INSTR(0x6c, 0x15), + ILI9881C_COMMAND_INSTR(0x6e, 0x22), + ILI9881C_COMMAND_INSTR(0x6f, 0x33), + ILI9881C_COMMAND_INSTR(0x3a, 0xa4), + ILI9881C_COMMAND_INSTR(0x8d, 0x0d), + ILI9881C_COMMAND_INSTR(0x87, 0xba), ILI9881C_COMMAND_INSTR(0x26, 0x76), - ILI9881C_COMMAND_INSTR(0xB2, 0xD1), + ILI9881C_COMMAND_INSTR(0xb2, 0xd1), ILI9881C_SWITCH_PAGE_INSTR(1), - ILI9881C_COMMAND_INSTR(0x22, 0x0A), - ILI9881C_COMMAND_INSTR(0x53, 0xDC), - ILI9881C_COMMAND_INSTR(0x55, 0xA7), + ILI9881C_COMMAND_INSTR(0x22, 0x0a), + ILI9881C_COMMAND_INSTR(0x53, 0xdc), + ILI9881C_COMMAND_INSTR(0x55, 0xa7), ILI9881C_COMMAND_INSTR(0x50, 0x78), ILI9881C_COMMAND_INSTR(0x51, 0x78), ILI9881C_COMMAND_INSTR(0x31, 0x02), ILI9881C_COMMAND_INSTR(0x60, 0x14), - ILI9881C_COMMAND_INSTR(0xA0, 0x2A), - ILI9881C_COMMAND_INSTR(0xA1, 0x39), - ILI9881C_COMMAND_INSTR(0xA2, 0x46), - ILI9881C_COMMAND_INSTR(0xA3, 0x0e), - ILI9881C_COMMAND_INSTR(0xA4, 0x12), - ILI9881C_COMMAND_INSTR(0xA5, 0x25), - ILI9881C_COMMAND_INSTR(0xA6, 0x19), - ILI9881C_COMMAND_INSTR(0xA7, 0x1d), - ILI9881C_COMMAND_INSTR(0xA8, 0xa6), - ILI9881C_COMMAND_INSTR(0xA9, 0x1C), - ILI9881C_COMMAND_INSTR(0xAA, 0x29), - ILI9881C_COMMAND_INSTR(0xAB, 0x85), - ILI9881C_COMMAND_INSTR(0xAC, 0x1C), - ILI9881C_COMMAND_INSTR(0xAD, 0x1B), - ILI9881C_COMMAND_INSTR(0xAE, 0x51), - ILI9881C_COMMAND_INSTR(0xAF, 0x22), - ILI9881C_COMMAND_INSTR(0xB0, 0x2d), - ILI9881C_COMMAND_INSTR(0xB1, 0x4f), - ILI9881C_COMMAND_INSTR(0xB2, 0x59), - ILI9881C_COMMAND_INSTR(0xB3, 0x3F), - ILI9881C_COMMAND_INSTR(0xC0, 0x2A), - ILI9881C_COMMAND_INSTR(0xC1, 0x3a), - ILI9881C_COMMAND_INSTR(0xC2, 0x45), - ILI9881C_COMMAND_INSTR(0xC3, 0x0e), - ILI9881C_COMMAND_INSTR(0xC4, 0x11), - ILI9881C_COMMAND_INSTR(0xC5, 0x24), - ILI9881C_COMMAND_INSTR(0xC6, 0x1a), - ILI9881C_COMMAND_INSTR(0xC7, 0x1c), - ILI9881C_COMMAND_INSTR(0xC8, 0xaa), - ILI9881C_COMMAND_INSTR(0xC9, 0x1C), - ILI9881C_COMMAND_INSTR(0xCA, 0x29), - ILI9881C_COMMAND_INSTR(0xCB, 0x96), - ILI9881C_COMMAND_INSTR(0xCC, 0x1C), - ILI9881C_COMMAND_INSTR(0xCD, 0x1B), - ILI9881C_COMMAND_INSTR(0xCE, 0x51), - ILI9881C_COMMAND_INSTR(0xCF, 0x22), - ILI9881C_COMMAND_INSTR(0xD0, 0x2b), - ILI9881C_COMMAND_INSTR(0xD1, 0x4b), - ILI9881C_COMMAND_INSTR(0xD2, 0x59), - ILI9881C_COMMAND_INSTR(0xD3, 0x3F), + ILI9881C_COMMAND_INSTR(0xa0, 0x2a), + ILI9881C_COMMAND_INSTR(0xa1, 0x39), + ILI9881C_COMMAND_INSTR(0xa2, 0x46), + ILI9881C_COMMAND_INSTR(0xa3, 0x0e), + ILI9881C_COMMAND_INSTR(0xa4, 0x12), + ILI9881C_COMMAND_INSTR(0xa5, 0x25), + ILI9881C_COMMAND_INSTR(0xa6, 0x19), + ILI9881C_COMMAND_INSTR(0xa7, 0x1d), + ILI9881C_COMMAND_INSTR(0xa8, 0xa6), + ILI9881C_COMMAND_INSTR(0xa9, 0x1c), + ILI9881C_COMMAND_INSTR(0xaa, 0x29), + ILI9881C_COMMAND_INSTR(0xab, 0x85), + ILI9881C_COMMAND_INSTR(0xac, 0x1c), + ILI9881C_COMMAND_INSTR(0xad, 0x1b), + ILI9881C_COMMAND_INSTR(0xae, 0x51), + ILI9881C_COMMAND_INSTR(0xaf, 0x22), + ILI9881C_COMMAND_INSTR(0xb0, 0x2d), + ILI9881C_COMMAND_INSTR(0xb1, 0x4f), + ILI9881C_COMMAND_INSTR(0xb2, 0x59), + ILI9881C_COMMAND_INSTR(0xb3, 0x3f), + ILI9881C_COMMAND_INSTR(0xc0, 0x2a), + ILI9881C_COMMAND_INSTR(0xc1, 0x3a), + ILI9881C_COMMAND_INSTR(0xc2, 0x45), + ILI9881C_COMMAND_INSTR(0xc3, 0x0e), + ILI9881C_COMMAND_INSTR(0xc4, 0x11), + ILI9881C_COMMAND_INSTR(0xc5, 0x24), + ILI9881C_COMMAND_INSTR(0xc6, 0x1a), + ILI9881C_COMMAND_INSTR(0xc7, 0x1c), + ILI9881C_COMMAND_INSTR(0xc8, 0xaa), + ILI9881C_COMMAND_INSTR(0xc9, 0x1c), + ILI9881C_COMMAND_INSTR(0xca, 0x29), + ILI9881C_COMMAND_INSTR(0xcb, 0x96), + ILI9881C_COMMAND_INSTR(0xcc, 0x1c), + ILI9881C_COMMAND_INSTR(0xcd, 0x1b), + ILI9881C_COMMAND_INSTR(0xce, 0x51), + ILI9881C_COMMAND_INSTR(0xcf, 0x22), + ILI9881C_COMMAND_INSTR(0xd0, 0x2b), + ILI9881C_COMMAND_INSTR(0xd1, 0x4b), + ILI9881C_COMMAND_INSTR(0xd2, 0x59), + ILI9881C_COMMAND_INSTR(0xd3, 0x3f), +}; + +static const struct ili9881c_instr k101_im2byl02_init[] = { + ILI9881C_SWITCH_PAGE_INSTR(3), + ILI9881C_COMMAND_INSTR(0x01, 0x00), + ILI9881C_COMMAND_INSTR(0x02, 0x00), + ILI9881C_COMMAND_INSTR(0x03, 0x73), + ILI9881C_COMMAND_INSTR(0x04, 0x00), + ILI9881C_COMMAND_INSTR(0x05, 0x00), + ILI9881C_COMMAND_INSTR(0x06, 0x08), + ILI9881C_COMMAND_INSTR(0x07, 0x00), + ILI9881C_COMMAND_INSTR(0x08, 0x00), + ILI9881C_COMMAND_INSTR(0x09, 0x00), + ILI9881C_COMMAND_INSTR(0x0a, 0x01), + ILI9881C_COMMAND_INSTR(0x0b, 0x01), + ILI9881C_COMMAND_INSTR(0x0c, 0x00), + ILI9881C_COMMAND_INSTR(0x0d, 0x01), + ILI9881C_COMMAND_INSTR(0x0e, 0x01), + ILI9881C_COMMAND_INSTR(0x0f, 0x00), + ILI9881C_COMMAND_INSTR(0x10, 0x00), + ILI9881C_COMMAND_INSTR(0x11, 0x00), + ILI9881C_COMMAND_INSTR(0x12, 0x00), + ILI9881C_COMMAND_INSTR(0x13, 0x00), + ILI9881C_COMMAND_INSTR(0x14, 0x00), + ILI9881C_COMMAND_INSTR(0x15, 0x00), + ILI9881C_COMMAND_INSTR(0x16, 0x00), + ILI9881C_COMMAND_INSTR(0x17, 0x00), + ILI9881C_COMMAND_INSTR(0x18, 0x00), + ILI9881C_COMMAND_INSTR(0x19, 0x00), + ILI9881C_COMMAND_INSTR(0x1a, 0x00), + ILI9881C_COMMAND_INSTR(0x1b, 0x00), + ILI9881C_COMMAND_INSTR(0x1c, 0x00), + ILI9881C_COMMAND_INSTR(0x1d, 0x00), + ILI9881C_COMMAND_INSTR(0x1e, 0x40), + ILI9881C_COMMAND_INSTR(0x1f, 0xc0), + ILI9881C_COMMAND_INSTR(0x20, 0x06), + ILI9881C_COMMAND_INSTR(0x21, 0x01), + ILI9881C_COMMAND_INSTR(0x22, 0x06), + ILI9881C_COMMAND_INSTR(0x23, 0x01), + ILI9881C_COMMAND_INSTR(0x24, 0x88), + ILI9881C_COMMAND_INSTR(0x25, 0x88), + ILI9881C_COMMAND_INSTR(0x26, 0x00), + ILI9881C_COMMAND_INSTR(0x27, 0x00), + ILI9881C_COMMAND_INSTR(0x28, 0x3b), + ILI9881C_COMMAND_INSTR(0x29, 0x03), + ILI9881C_COMMAND_INSTR(0x2a, 0x00), + ILI9881C_COMMAND_INSTR(0x2b, 0x00), + ILI9881C_COMMAND_INSTR(0x2c, 0x00), + ILI9881C_COMMAND_INSTR(0x2d, 0x00), + ILI9881C_COMMAND_INSTR(0x2e, 0x00), + ILI9881C_COMMAND_INSTR(0x2f, 0x00), + ILI9881C_COMMAND_INSTR(0x30, 0x00), + ILI9881C_COMMAND_INSTR(0x31, 0x00), + ILI9881C_COMMAND_INSTR(0x32, 0x00), + ILI9881C_COMMAND_INSTR(0x33, 0x00), + ILI9881C_COMMAND_INSTR(0x34, 0x00), /* GPWR1/2 non overlap time 2.62us */ + ILI9881C_COMMAND_INSTR(0x35, 0x00), + ILI9881C_COMMAND_INSTR(0x36, 0x00), + ILI9881C_COMMAND_INSTR(0x37, 0x00), + ILI9881C_COMMAND_INSTR(0x38, 0x00), + ILI9881C_COMMAND_INSTR(0x39, 0x00), + ILI9881C_COMMAND_INSTR(0x3a, 0x00), + ILI9881C_COMMAND_INSTR(0x3b, 0x00), + ILI9881C_COMMAND_INSTR(0x3c, 0x00), + ILI9881C_COMMAND_INSTR(0x3d, 0x00), + ILI9881C_COMMAND_INSTR(0x3e, 0x00), + ILI9881C_COMMAND_INSTR(0x3f, 0x00), + ILI9881C_COMMAND_INSTR(0x40, 0x00), + ILI9881C_COMMAND_INSTR(0x41, 0x00), + ILI9881C_COMMAND_INSTR(0x42, 0x00), + ILI9881C_COMMAND_INSTR(0x43, 0x00), + ILI9881C_COMMAND_INSTR(0x44, 0x00), + ILI9881C_COMMAND_INSTR(0x50, 0x01), + ILI9881C_COMMAND_INSTR(0x51, 0x23), + ILI9881C_COMMAND_INSTR(0x52, 0x45), + ILI9881C_COMMAND_INSTR(0x53, 0x67), + ILI9881C_COMMAND_INSTR(0x54, 0x89), + ILI9881C_COMMAND_INSTR(0x55, 0xab), + ILI9881C_COMMAND_INSTR(0x56, 0x01), + ILI9881C_COMMAND_INSTR(0x57, 0x23), + ILI9881C_COMMAND_INSTR(0x58, 0x45), + ILI9881C_COMMAND_INSTR(0x59, 0x67), + ILI9881C_COMMAND_INSTR(0x5a, 0x89), + ILI9881C_COMMAND_INSTR(0x5b, 0xab), + ILI9881C_COMMAND_INSTR(0x5c, 0xcd), + ILI9881C_COMMAND_INSTR(0x5d, 0xef), + ILI9881C_COMMAND_INSTR(0x5e, 0x00), + ILI9881C_COMMAND_INSTR(0x5f, 0x01), + ILI9881C_COMMAND_INSTR(0x60, 0x01), + ILI9881C_COMMAND_INSTR(0x61, 0x06), + ILI9881C_COMMAND_INSTR(0x62, 0x06), + ILI9881C_COMMAND_INSTR(0x63, 0x07), + ILI9881C_COMMAND_INSTR(0x64, 0x07), + ILI9881C_COMMAND_INSTR(0x65, 0x00), + ILI9881C_COMMAND_INSTR(0x66, 0x00), + ILI9881C_COMMAND_INSTR(0x67, 0x02), + ILI9881C_COMMAND_INSTR(0x68, 0x02), + ILI9881C_COMMAND_INSTR(0x69, 0x05), + ILI9881C_COMMAND_INSTR(0x6a, 0x05), + ILI9881C_COMMAND_INSTR(0x6b, 0x02), + ILI9881C_COMMAND_INSTR(0x6c, 0x0d), + ILI9881C_COMMAND_INSTR(0x6d, 0x0d), + ILI9881C_COMMAND_INSTR(0x6e, 0x0c), + ILI9881C_COMMAND_INSTR(0x6f, 0x0c), + ILI9881C_COMMAND_INSTR(0x70, 0x0f), + ILI9881C_COMMAND_INSTR(0x71, 0x0f), + ILI9881C_COMMAND_INSTR(0x72, 0x0e), + ILI9881C_COMMAND_INSTR(0x73, 0x0e), + ILI9881C_COMMAND_INSTR(0x74, 0x02), + ILI9881C_COMMAND_INSTR(0x75, 0x01), + ILI9881C_COMMAND_INSTR(0x76, 0x01), + ILI9881C_COMMAND_INSTR(0x77, 0x06), + ILI9881C_COMMAND_INSTR(0x78, 0x06), + ILI9881C_COMMAND_INSTR(0x79, 0x07), + ILI9881C_COMMAND_INSTR(0x7a, 0x07), + ILI9881C_COMMAND_INSTR(0x7b, 0x00), + ILI9881C_COMMAND_INSTR(0x7c, 0x00), + ILI9881C_COMMAND_INSTR(0x7d, 0x02), + ILI9881C_COMMAND_INSTR(0x7e, 0x02), + ILI9881C_COMMAND_INSTR(0x7f, 0x05), + ILI9881C_COMMAND_INSTR(0x80, 0x05), + ILI9881C_COMMAND_INSTR(0x81, 0x02), + ILI9881C_COMMAND_INSTR(0x82, 0x0d), + ILI9881C_COMMAND_INSTR(0x83, 0x0d), + ILI9881C_COMMAND_INSTR(0x84, 0x0c), + ILI9881C_COMMAND_INSTR(0x85, 0x0c), + ILI9881C_COMMAND_INSTR(0x86, 0x0f), + ILI9881C_COMMAND_INSTR(0x87, 0x0f), + ILI9881C_COMMAND_INSTR(0x88, 0x0e), + ILI9881C_COMMAND_INSTR(0x89, 0x0e), + ILI9881C_COMMAND_INSTR(0x8a, 0x02), + ILI9881C_SWITCH_PAGE_INSTR(4), + ILI9881C_COMMAND_INSTR(0x3b, 0xc0), /* ILI4003D sel */ + ILI9881C_COMMAND_INSTR(0x6c, 0x15), /* Set VCORE voltage = 1.5V */ + ILI9881C_COMMAND_INSTR(0x6e, 0x2a), /* di_pwr_reg=0 for power mode 2A, VGH clamp 18V */ + ILI9881C_COMMAND_INSTR(0x6f, 0x33), /* pumping ratio VGH=5x VGL=-3x */ + ILI9881C_COMMAND_INSTR(0x8d, 0x1b), /* VGL clamp -10V */ + ILI9881C_COMMAND_INSTR(0x87, 0xba), /* ESD */ + ILI9881C_COMMAND_INSTR(0x3a, 0x24), /* POWER SAVING */ + ILI9881C_COMMAND_INSTR(0x26, 0x76), + ILI9881C_COMMAND_INSTR(0xb2, 0xd1), + ILI9881C_SWITCH_PAGE_INSTR(1), + ILI9881C_COMMAND_INSTR(0x22, 0x0a), /* BGR, SS */ + ILI9881C_COMMAND_INSTR(0x31, 0x00), /* Zigzag type3 inversion */ + ILI9881C_COMMAND_INSTR(0x40, 0x53), /* ILI4003D sel */ + ILI9881C_COMMAND_INSTR(0x43, 0x66), + ILI9881C_COMMAND_INSTR(0x53, 0x4c), + ILI9881C_COMMAND_INSTR(0x50, 0x87), + ILI9881C_COMMAND_INSTR(0x51, 0x82), + ILI9881C_COMMAND_INSTR(0x60, 0x15), + ILI9881C_COMMAND_INSTR(0x61, 0x01), + ILI9881C_COMMAND_INSTR(0x62, 0x0c), + ILI9881C_COMMAND_INSTR(0x63, 0x00), + ILI9881C_COMMAND_INSTR(0xa0, 0x00), + ILI9881C_COMMAND_INSTR(0xa1, 0x13), /* VP251 */ + ILI9881C_COMMAND_INSTR(0xa2, 0x23), /* VP247 */ + ILI9881C_COMMAND_INSTR(0xa3, 0x14), /* VP243 */ + ILI9881C_COMMAND_INSTR(0xa4, 0x16), /* VP239 */ + ILI9881C_COMMAND_INSTR(0xa5, 0x29), /* VP231 */ + ILI9881C_COMMAND_INSTR(0xa6, 0x1e), /* VP219 */ + ILI9881C_COMMAND_INSTR(0xa7, 0x1d), /* VP203 */ + ILI9881C_COMMAND_INSTR(0xa8, 0x86), /* VP175 */ + ILI9881C_COMMAND_INSTR(0xa9, 0x1e), /* VP144 */ + ILI9881C_COMMAND_INSTR(0xaa, 0x29), /* VP111 */ + ILI9881C_COMMAND_INSTR(0xab, 0x74), /* VP80 */ + ILI9881C_COMMAND_INSTR(0xac, 0x19), /* VP52 */ + ILI9881C_COMMAND_INSTR(0xad, 0x17), /* VP36 */ + ILI9881C_COMMAND_INSTR(0xae, 0x4b), /* VP24 */ + ILI9881C_COMMAND_INSTR(0xaf, 0x20), /* VP16 */ + ILI9881C_COMMAND_INSTR(0xb0, 0x26), /* VP12 */ + ILI9881C_COMMAND_INSTR(0xb1, 0x4c), /* VP8 */ + ILI9881C_COMMAND_INSTR(0xb2, 0x5d), /* VP4 */ + ILI9881C_COMMAND_INSTR(0xb3, 0x3f), /* VP0 */ + ILI9881C_COMMAND_INSTR(0xc0, 0x00), /* VN255 GAMMA N */ + ILI9881C_COMMAND_INSTR(0xc1, 0x13), /* VN251 */ + ILI9881C_COMMAND_INSTR(0xc2, 0x23), /* VN247 */ + ILI9881C_COMMAND_INSTR(0xc3, 0x14), /* VN243 */ + ILI9881C_COMMAND_INSTR(0xc4, 0x16), /* VN239 */ + ILI9881C_COMMAND_INSTR(0xc5, 0x29), /* VN231 */ + ILI9881C_COMMAND_INSTR(0xc6, 0x1e), /* VN219 */ + ILI9881C_COMMAND_INSTR(0xc7, 0x1d), /* VN203 */ + ILI9881C_COMMAND_INSTR(0xc8, 0x86), /* VN175 */ + ILI9881C_COMMAND_INSTR(0xc9, 0x1e), /* VN144 */ + ILI9881C_COMMAND_INSTR(0xca, 0x29), /* VN111 */ + ILI9881C_COMMAND_INSTR(0xcb, 0x74), /* VN80 */ + ILI9881C_COMMAND_INSTR(0xcc, 0x19), /* VN52 */ + ILI9881C_COMMAND_INSTR(0xcd, 0x17), /* VN36 */ + ILI9881C_COMMAND_INSTR(0xce, 0x4b), /* VN24 */ + ILI9881C_COMMAND_INSTR(0xcf, 0x20), /* VN16 */ + ILI9881C_COMMAND_INSTR(0xd0, 0x26), /* VN12 */ + ILI9881C_COMMAND_INSTR(0xd1, 0x4c), /* VN8 */ + ILI9881C_COMMAND_INSTR(0xd2, 0x5d), /* VN4 */ + ILI9881C_COMMAND_INSTR(0xd3, 0x3f), /* VN0 */ +}; + +static const struct ili9881c_instr kd050hdfia020_init[] = { + ILI9881C_SWITCH_PAGE_INSTR(3), + ILI9881C_COMMAND_INSTR(0x01, 0x00), + ILI9881C_COMMAND_INSTR(0x02, 0x00), + ILI9881C_COMMAND_INSTR(0x03, 0x72), + ILI9881C_COMMAND_INSTR(0x04, 0x00), + ILI9881C_COMMAND_INSTR(0x05, 0x00), + ILI9881C_COMMAND_INSTR(0x06, 0x09), + ILI9881C_COMMAND_INSTR(0x07, 0x00), + ILI9881C_COMMAND_INSTR(0x08, 0x00), + ILI9881C_COMMAND_INSTR(0x09, 0x01), + ILI9881C_COMMAND_INSTR(0x0a, 0x00), + ILI9881C_COMMAND_INSTR(0x0b, 0x00), + ILI9881C_COMMAND_INSTR(0x0c, 0x01), + ILI9881C_COMMAND_INSTR(0x0d, 0x00), + ILI9881C_COMMAND_INSTR(0x0e, 0x00), + ILI9881C_COMMAND_INSTR(0x0f, 0x00), + ILI9881C_COMMAND_INSTR(0x10, 0x00), + ILI9881C_COMMAND_INSTR(0x11, 0x00), + ILI9881C_COMMAND_INSTR(0x12, 0x00), + ILI9881C_COMMAND_INSTR(0x13, 0x00), + ILI9881C_COMMAND_INSTR(0x14, 0x00), + ILI9881C_COMMAND_INSTR(0x15, 0x00), + ILI9881C_COMMAND_INSTR(0x16, 0x00), + ILI9881C_COMMAND_INSTR(0x17, 0x00), + ILI9881C_COMMAND_INSTR(0x18, 0x00), + ILI9881C_COMMAND_INSTR(0x19, 0x00), + ILI9881C_COMMAND_INSTR(0x1a, 0x00), + ILI9881C_COMMAND_INSTR(0x1b, 0x00), + ILI9881C_COMMAND_INSTR(0x1c, 0x00), + ILI9881C_COMMAND_INSTR(0x1d, 0x00), + ILI9881C_COMMAND_INSTR(0x1e, 0x40), + ILI9881C_COMMAND_INSTR(0x1f, 0x80), + ILI9881C_COMMAND_INSTR(0x20, 0x05), + ILI9881C_COMMAND_INSTR(0x20, 0x05), + ILI9881C_COMMAND_INSTR(0x21, 0x02), + ILI9881C_COMMAND_INSTR(0x22, 0x00), + ILI9881C_COMMAND_INSTR(0x23, 0x00), + ILI9881C_COMMAND_INSTR(0x24, 0x00), + ILI9881C_COMMAND_INSTR(0x25, 0x00), + ILI9881C_COMMAND_INSTR(0x26, 0x00), + ILI9881C_COMMAND_INSTR(0x27, 0x00), + ILI9881C_COMMAND_INSTR(0x28, 0x33), + ILI9881C_COMMAND_INSTR(0x29, 0x02), + ILI9881C_COMMAND_INSTR(0x2a, 0x00), + ILI9881C_COMMAND_INSTR(0x2b, 0x00), + ILI9881C_COMMAND_INSTR(0x2c, 0x00), + ILI9881C_COMMAND_INSTR(0x2d, 0x00), + ILI9881C_COMMAND_INSTR(0x2e, 0x00), + ILI9881C_COMMAND_INSTR(0x2f, 0x00), + ILI9881C_COMMAND_INSTR(0x30, 0x00), + ILI9881C_COMMAND_INSTR(0x31, 0x00), + ILI9881C_COMMAND_INSTR(0x32, 0x00), + ILI9881C_COMMAND_INSTR(0x32, 0x00), + ILI9881C_COMMAND_INSTR(0x33, 0x00), + ILI9881C_COMMAND_INSTR(0x34, 0x04), + ILI9881C_COMMAND_INSTR(0x35, 0x00), + ILI9881C_COMMAND_INSTR(0x36, 0x00), + ILI9881C_COMMAND_INSTR(0x37, 0x00), + ILI9881C_COMMAND_INSTR(0x38, 0x3c), + ILI9881C_COMMAND_INSTR(0x39, 0x00), + ILI9881C_COMMAND_INSTR(0x3a, 0x40), + ILI9881C_COMMAND_INSTR(0x3b, 0x40), + ILI9881C_COMMAND_INSTR(0x3c, 0x00), + ILI9881C_COMMAND_INSTR(0x3d, 0x00), + ILI9881C_COMMAND_INSTR(0x3e, 0x00), + ILI9881C_COMMAND_INSTR(0x3f, 0x00), + ILI9881C_COMMAND_INSTR(0x40, 0x00), + ILI9881C_COMMAND_INSTR(0x41, 0x00), + ILI9881C_COMMAND_INSTR(0x42, 0x00), + ILI9881C_COMMAND_INSTR(0x43, 0x00), + ILI9881C_COMMAND_INSTR(0x44, 0x00), + ILI9881C_COMMAND_INSTR(0x50, 0x01), + ILI9881C_COMMAND_INSTR(0x51, 0x23), + ILI9881C_COMMAND_INSTR(0x52, 0x45), + ILI9881C_COMMAND_INSTR(0x53, 0x67), + ILI9881C_COMMAND_INSTR(0x54, 0x89), + ILI9881C_COMMAND_INSTR(0x55, 0xab), + ILI9881C_COMMAND_INSTR(0x56, 0x01), + ILI9881C_COMMAND_INSTR(0x57, 0x23), + ILI9881C_COMMAND_INSTR(0x58, 0x45), + ILI9881C_COMMAND_INSTR(0x59, 0x67), + ILI9881C_COMMAND_INSTR(0x5a, 0x89), + ILI9881C_COMMAND_INSTR(0x5b, 0xab), + ILI9881C_COMMAND_INSTR(0x5c, 0xcd), + ILI9881C_COMMAND_INSTR(0x5d, 0xef), + ILI9881C_COMMAND_INSTR(0x5e, 0x11), + ILI9881C_COMMAND_INSTR(0x5f, 0x01), + ILI9881C_COMMAND_INSTR(0x60, 0x00), + ILI9881C_COMMAND_INSTR(0x61, 0x15), + ILI9881C_COMMAND_INSTR(0x62, 0x14), + ILI9881C_COMMAND_INSTR(0x63, 0x0e), + ILI9881C_COMMAND_INSTR(0x64, 0x0f), + ILI9881C_COMMAND_INSTR(0x65, 0x0c), + ILI9881C_COMMAND_INSTR(0x66, 0x0d), + ILI9881C_COMMAND_INSTR(0x67, 0x06), + ILI9881C_COMMAND_INSTR(0x68, 0x02), + ILI9881C_COMMAND_INSTR(0x69, 0x07), + ILI9881C_COMMAND_INSTR(0x6a, 0x02), + ILI9881C_COMMAND_INSTR(0x6b, 0x02), + ILI9881C_COMMAND_INSTR(0x6c, 0x02), + ILI9881C_COMMAND_INSTR(0x6d, 0x02), + ILI9881C_COMMAND_INSTR(0x6e, 0x02), + ILI9881C_COMMAND_INSTR(0x6f, 0x02), + ILI9881C_COMMAND_INSTR(0x70, 0x02), + ILI9881C_COMMAND_INSTR(0x71, 0x02), + ILI9881C_COMMAND_INSTR(0x72, 0x02), + ILI9881C_COMMAND_INSTR(0x73, 0x02), + ILI9881C_COMMAND_INSTR(0x74, 0x02), + ILI9881C_COMMAND_INSTR(0x75, 0x01), + ILI9881C_COMMAND_INSTR(0x76, 0x00), + ILI9881C_COMMAND_INSTR(0x77, 0x14), + ILI9881C_COMMAND_INSTR(0x78, 0x15), + ILI9881C_COMMAND_INSTR(0x79, 0x0e), + ILI9881C_COMMAND_INSTR(0x7a, 0x0f), + ILI9881C_COMMAND_INSTR(0x7b, 0x0c), + ILI9881C_COMMAND_INSTR(0x7c, 0x0d), + ILI9881C_COMMAND_INSTR(0x7d, 0x06), + ILI9881C_COMMAND_INSTR(0x7e, 0x02), + ILI9881C_COMMAND_INSTR(0x7f, 0x07), + ILI9881C_COMMAND_INSTR(0x80, 0x02), + ILI9881C_COMMAND_INSTR(0x81, 0x02), + ILI9881C_COMMAND_INSTR(0x83, 0x02), + ILI9881C_COMMAND_INSTR(0x84, 0x02), + ILI9881C_COMMAND_INSTR(0x85, 0x02), + ILI9881C_COMMAND_INSTR(0x86, 0x02), + ILI9881C_COMMAND_INSTR(0x87, 0x02), + ILI9881C_COMMAND_INSTR(0x88, 0x02), + ILI9881C_COMMAND_INSTR(0x89, 0x02), + ILI9881C_COMMAND_INSTR(0x8a, 0x02), + ILI9881C_SWITCH_PAGE_INSTR(0x4), + ILI9881C_COMMAND_INSTR(0x6c, 0x15), + ILI9881C_COMMAND_INSTR(0x6e, 0x2a), + ILI9881C_COMMAND_INSTR(0x6f, 0x33), + ILI9881C_COMMAND_INSTR(0x3a, 0x94), + ILI9881C_COMMAND_INSTR(0x8d, 0x15), + ILI9881C_COMMAND_INSTR(0x87, 0xba), + ILI9881C_COMMAND_INSTR(0x26, 0x76), + ILI9881C_COMMAND_INSTR(0xb2, 0xd1), + ILI9881C_COMMAND_INSTR(0xb5, 0x06), + ILI9881C_SWITCH_PAGE_INSTR(0x1), + ILI9881C_COMMAND_INSTR(0x22, 0x0a), + ILI9881C_COMMAND_INSTR(0x31, 0x00), + ILI9881C_COMMAND_INSTR(0x53, 0x90), + ILI9881C_COMMAND_INSTR(0x55, 0xa2), + ILI9881C_COMMAND_INSTR(0x50, 0xb7), + ILI9881C_COMMAND_INSTR(0x51, 0xb7), + ILI9881C_COMMAND_INSTR(0x60, 0x22), + ILI9881C_COMMAND_INSTR(0x61, 0x00), + ILI9881C_COMMAND_INSTR(0x62, 0x19), + ILI9881C_COMMAND_INSTR(0x63, 0x10), + ILI9881C_COMMAND_INSTR(0xa0, 0x08), + ILI9881C_COMMAND_INSTR(0xa1, 0x1a), + ILI9881C_COMMAND_INSTR(0xa2, 0x27), + ILI9881C_COMMAND_INSTR(0xa3, 0x15), + ILI9881C_COMMAND_INSTR(0xa4, 0x17), + ILI9881C_COMMAND_INSTR(0xa5, 0x2a), + ILI9881C_COMMAND_INSTR(0xa6, 0x1e), + ILI9881C_COMMAND_INSTR(0xa7, 0x1f), + ILI9881C_COMMAND_INSTR(0xa8, 0x8b), + ILI9881C_COMMAND_INSTR(0xa9, 0x1b), + ILI9881C_COMMAND_INSTR(0xaa, 0x27), + ILI9881C_COMMAND_INSTR(0xab, 0x78), + ILI9881C_COMMAND_INSTR(0xac, 0x18), + ILI9881C_COMMAND_INSTR(0xad, 0x18), + ILI9881C_COMMAND_INSTR(0xae, 0x4c), + ILI9881C_COMMAND_INSTR(0xaf, 0x21), + ILI9881C_COMMAND_INSTR(0xb0, 0x27), + ILI9881C_COMMAND_INSTR(0xb1, 0x54), + ILI9881C_COMMAND_INSTR(0xb2, 0x67), + ILI9881C_COMMAND_INSTR(0xb3, 0x39), + ILI9881C_COMMAND_INSTR(0xc0, 0x08), + ILI9881C_COMMAND_INSTR(0xc1, 0x1a), + ILI9881C_COMMAND_INSTR(0xc2, 0x27), + ILI9881C_COMMAND_INSTR(0xc3, 0x15), + ILI9881C_COMMAND_INSTR(0xc4, 0x17), + ILI9881C_COMMAND_INSTR(0xc5, 0x2a), + ILI9881C_COMMAND_INSTR(0xc6, 0x1e), + ILI9881C_COMMAND_INSTR(0xc7, 0x1f), + ILI9881C_COMMAND_INSTR(0xc8, 0x8b), + ILI9881C_COMMAND_INSTR(0xc9, 0x1b), + ILI9881C_COMMAND_INSTR(0xca, 0x27), + ILI9881C_COMMAND_INSTR(0xcb, 0x78), + ILI9881C_COMMAND_INSTR(0xcc, 0x18), + ILI9881C_COMMAND_INSTR(0xcd, 0x18), + ILI9881C_COMMAND_INSTR(0xce, 0x4c), + ILI9881C_COMMAND_INSTR(0xcf, 0x21), + ILI9881C_COMMAND_INSTR(0xd0, 0x27), + ILI9881C_COMMAND_INSTR(0xd1, 0x54), + ILI9881C_COMMAND_INSTR(0xd2, 0x67), + ILI9881C_COMMAND_INSTR(0xd3, 0x39), + ILI9881C_SWITCH_PAGE_INSTR(0), + ILI9881C_COMMAND_INSTR(0x35, 0x00), + ILI9881C_COMMAND_INSTR(0x3a, 0x7), +}; + +static const struct ili9881c_instr tl050hdv35_init[] = { + ILI9881C_SWITCH_PAGE_INSTR(3), + ILI9881C_COMMAND_INSTR(0x01, 0x00), + ILI9881C_COMMAND_INSTR(0x02, 0x00), + ILI9881C_COMMAND_INSTR(0x03, 0x73), + ILI9881C_COMMAND_INSTR(0x04, 0x00), + ILI9881C_COMMAND_INSTR(0x05, 0x00), + ILI9881C_COMMAND_INSTR(0x06, 0x0a), + ILI9881C_COMMAND_INSTR(0x07, 0x00), + ILI9881C_COMMAND_INSTR(0x08, 0x00), + ILI9881C_COMMAND_INSTR(0x09, 0x01), + ILI9881C_COMMAND_INSTR(0x0a, 0x00), + ILI9881C_COMMAND_INSTR(0x0b, 0x00), + ILI9881C_COMMAND_INSTR(0x0c, 0x01), + ILI9881C_COMMAND_INSTR(0x0d, 0x00), + ILI9881C_COMMAND_INSTR(0x0e, 0x00), + ILI9881C_COMMAND_INSTR(0x0f, 0x1d), + ILI9881C_COMMAND_INSTR(0x10, 0x1d), + ILI9881C_COMMAND_INSTR(0x15, 0x00), + ILI9881C_COMMAND_INSTR(0x16, 0x00), + ILI9881C_COMMAND_INSTR(0x17, 0x00), + ILI9881C_COMMAND_INSTR(0x18, 0x00), + ILI9881C_COMMAND_INSTR(0x19, 0x00), + ILI9881C_COMMAND_INSTR(0x1a, 0x00), + ILI9881C_COMMAND_INSTR(0x1b, 0x00), + ILI9881C_COMMAND_INSTR(0x1c, 0x00), + ILI9881C_COMMAND_INSTR(0x1d, 0x00), + ILI9881C_COMMAND_INSTR(0x1e, 0x40), + ILI9881C_COMMAND_INSTR(0x1f, 0x80), + ILI9881C_COMMAND_INSTR(0x20, 0x06), + ILI9881C_COMMAND_INSTR(0x21, 0x02), + ILI9881C_COMMAND_INSTR(0x28, 0x33), + ILI9881C_COMMAND_INSTR(0x29, 0x03), + ILI9881C_COMMAND_INSTR(0x2a, 0x00), + ILI9881C_COMMAND_INSTR(0x2b, 0x00), + ILI9881C_COMMAND_INSTR(0x2c, 0x00), + ILI9881C_COMMAND_INSTR(0x2d, 0x00), + ILI9881C_COMMAND_INSTR(0x2e, 0x00), + ILI9881C_COMMAND_INSTR(0x2f, 0x00), + ILI9881C_COMMAND_INSTR(0x35, 0x00), + ILI9881C_COMMAND_INSTR(0x36, 0x00), + ILI9881C_COMMAND_INSTR(0x37, 0x00), + ILI9881C_COMMAND_INSTR(0x38, 0x3c), + ILI9881C_COMMAND_INSTR(0x39, 0x00), + ILI9881C_COMMAND_INSTR(0x3a, 0x40), + ILI9881C_COMMAND_INSTR(0x3b, 0x40), + ILI9881C_COMMAND_INSTR(0x3c, 0x00), + ILI9881C_COMMAND_INSTR(0x3d, 0x00), + ILI9881C_COMMAND_INSTR(0x3e, 0x00), + ILI9881C_COMMAND_INSTR(0x3f, 0x00), + ILI9881C_COMMAND_INSTR(0x40, 0x00), + ILI9881C_COMMAND_INSTR(0x41, 0x00), + ILI9881C_COMMAND_INSTR(0x42, 0x00), + ILI9881C_COMMAND_INSTR(0x43, 0x00), + ILI9881C_COMMAND_INSTR(0x44, 0x00), + ILI9881C_COMMAND_INSTR(0x55, 0xab), + ILI9881C_COMMAND_INSTR(0x5a, 0x89), + ILI9881C_COMMAND_INSTR(0x5b, 0xab), + ILI9881C_COMMAND_INSTR(0x5c, 0xcd), + ILI9881C_COMMAND_INSTR(0x5d, 0xef), + ILI9881C_COMMAND_INSTR(0x5e, 0x11), + ILI9881C_COMMAND_INSTR(0x5f, 0x01), + ILI9881C_COMMAND_INSTR(0x60, 0x00), + ILI9881C_COMMAND_INSTR(0x61, 0x15), + ILI9881C_COMMAND_INSTR(0x62, 0x14), + ILI9881C_COMMAND_INSTR(0x63, 0x0e), + ILI9881C_COMMAND_INSTR(0x64, 0x0f), + ILI9881C_COMMAND_INSTR(0x65, 0x0c), + ILI9881C_COMMAND_INSTR(0x66, 0x0d), + ILI9881C_COMMAND_INSTR(0x67, 0x06), + ILI9881C_COMMAND_INSTR(0x68, 0x02), + ILI9881C_COMMAND_INSTR(0x69, 0x07), + ILI9881C_COMMAND_INSTR(0x6a, 0x02), + ILI9881C_COMMAND_INSTR(0x6b, 0x02), + ILI9881C_COMMAND_INSTR(0x6c, 0x02), + ILI9881C_COMMAND_INSTR(0x6d, 0x02), + ILI9881C_COMMAND_INSTR(0x6e, 0x02), + ILI9881C_COMMAND_INSTR(0x6f, 0x02), + ILI9881C_COMMAND_INSTR(0x70, 0x02), + ILI9881C_COMMAND_INSTR(0x71, 0x02), + ILI9881C_COMMAND_INSTR(0x72, 0x02), + ILI9881C_COMMAND_INSTR(0x73, 0x02), + ILI9881C_COMMAND_INSTR(0x74, 0x02), + ILI9881C_COMMAND_INSTR(0x75, 0x01), + ILI9881C_COMMAND_INSTR(0x76, 0x00), + ILI9881C_COMMAND_INSTR(0x77, 0x14), + ILI9881C_COMMAND_INSTR(0x78, 0x15), + ILI9881C_COMMAND_INSTR(0x79, 0x0e), + ILI9881C_COMMAND_INSTR(0x7a, 0x0f), + ILI9881C_COMMAND_INSTR(0x7b, 0x0c), + ILI9881C_COMMAND_INSTR(0x7c, 0x0d), + ILI9881C_COMMAND_INSTR(0x7d, 0x06), + ILI9881C_COMMAND_INSTR(0x7e, 0x02), + ILI9881C_COMMAND_INSTR(0x7f, 0x07), + ILI9881C_COMMAND_INSTR(0x88, 0x02), + ILI9881C_COMMAND_INSTR(0x89, 0x02), + ILI9881C_COMMAND_INSTR(0x8a, 0x02), + ILI9881C_SWITCH_PAGE_INSTR(4), + ILI9881C_COMMAND_INSTR(0x38, 0x01), + ILI9881C_COMMAND_INSTR(0x39, 0x00), + ILI9881C_COMMAND_INSTR(0x6c, 0x15), + ILI9881C_COMMAND_INSTR(0x6e, 0x2b), + ILI9881C_COMMAND_INSTR(0x6f, 0x33), + ILI9881C_COMMAND_INSTR(0x8d, 0x18), + ILI9881C_COMMAND_INSTR(0x87, 0xba), + ILI9881C_COMMAND_INSTR(0x26, 0x76), + ILI9881C_COMMAND_INSTR(0xb2, 0xd1), + ILI9881C_COMMAND_INSTR(0xb5, 0x06), + ILI9881C_COMMAND_INSTR(0x3a, 0x24), + ILI9881C_COMMAND_INSTR(0x35, 0x1f), + ILI9881C_COMMAND_INSTR(0x33, 0x14), + ILI9881C_COMMAND_INSTR(0x3b, 0x98), + ILI9881C_SWITCH_PAGE_INSTR(1), + ILI9881C_COMMAND_INSTR(0x22, 0x0a), + ILI9881C_COMMAND_INSTR(0x31, 0x00), + ILI9881C_COMMAND_INSTR(0x40, 0x33), + ILI9881C_COMMAND_INSTR(0x53, 0xa2), + ILI9881C_COMMAND_INSTR(0x55, 0x92), + ILI9881C_COMMAND_INSTR(0x50, 0x96), + ILI9881C_COMMAND_INSTR(0x51, 0x96), + ILI9881C_COMMAND_INSTR(0x60, 0x22), + ILI9881C_COMMAND_INSTR(0x61, 0x00), + ILI9881C_COMMAND_INSTR(0x62, 0x19), + ILI9881C_COMMAND_INSTR(0x63, 0x00), + ILI9881C_COMMAND_INSTR(0xa0, 0x08), + ILI9881C_COMMAND_INSTR(0xa1, 0x11), + ILI9881C_COMMAND_INSTR(0xa2, 0x19), + ILI9881C_COMMAND_INSTR(0xa3, 0x0d), + ILI9881C_COMMAND_INSTR(0xa4, 0x0d), + ILI9881C_COMMAND_INSTR(0xa5, 0x1e), + ILI9881C_COMMAND_INSTR(0xa6, 0x14), + ILI9881C_COMMAND_INSTR(0xa7, 0x17), + ILI9881C_COMMAND_INSTR(0xa8, 0x4f), + ILI9881C_COMMAND_INSTR(0xa9, 0x1a), + ILI9881C_COMMAND_INSTR(0xaa, 0x27), + ILI9881C_COMMAND_INSTR(0xab, 0x49), + ILI9881C_COMMAND_INSTR(0xac, 0x1a), + ILI9881C_COMMAND_INSTR(0xad, 0x18), + ILI9881C_COMMAND_INSTR(0xae, 0x4c), + ILI9881C_COMMAND_INSTR(0xaf, 0x22), + ILI9881C_COMMAND_INSTR(0xb0, 0x27), + ILI9881C_COMMAND_INSTR(0xb1, 0x4b), + ILI9881C_COMMAND_INSTR(0xb2, 0x60), + ILI9881C_COMMAND_INSTR(0xb3, 0x39), + ILI9881C_COMMAND_INSTR(0xc0, 0x08), + ILI9881C_COMMAND_INSTR(0xc1, 0x11), + ILI9881C_COMMAND_INSTR(0xc2, 0x19), + ILI9881C_COMMAND_INSTR(0xc3, 0x0d), + ILI9881C_COMMAND_INSTR(0xc4, 0x0d), + ILI9881C_COMMAND_INSTR(0xc5, 0x1e), + ILI9881C_COMMAND_INSTR(0xc6, 0x14), + ILI9881C_COMMAND_INSTR(0xc7, 0x17), + ILI9881C_COMMAND_INSTR(0xc8, 0x4f), + ILI9881C_COMMAND_INSTR(0xc9, 0x1a), + ILI9881C_COMMAND_INSTR(0xca, 0x27), + ILI9881C_COMMAND_INSTR(0xcb, 0x49), + ILI9881C_COMMAND_INSTR(0xcc, 0x1a), + ILI9881C_COMMAND_INSTR(0xcd, 0x18), + ILI9881C_COMMAND_INSTR(0xce, 0x4c), + ILI9881C_COMMAND_INSTR(0xcf, 0x33), + ILI9881C_COMMAND_INSTR(0xd0, 0x27), + ILI9881C_COMMAND_INSTR(0xd1, 0x4b), + ILI9881C_COMMAND_INSTR(0xd2, 0x60), + ILI9881C_COMMAND_INSTR(0xd3, 0x39), +}; + +static const struct ili9881c_instr w552946aaa_init[] = { + ILI9881C_SWITCH_PAGE_INSTR(3), + ILI9881C_COMMAND_INSTR(0x01, 0x00), + ILI9881C_COMMAND_INSTR(0x02, 0x00), + ILI9881C_COMMAND_INSTR(0x03, 0x53), + ILI9881C_COMMAND_INSTR(0x04, 0x53), + ILI9881C_COMMAND_INSTR(0x05, 0x13), + ILI9881C_COMMAND_INSTR(0x06, 0x04), + ILI9881C_COMMAND_INSTR(0x07, 0x02), + ILI9881C_COMMAND_INSTR(0x08, 0x02), + ILI9881C_COMMAND_INSTR(0x09, 0x00), + ILI9881C_COMMAND_INSTR(0x0a, 0x00), + ILI9881C_COMMAND_INSTR(0x0b, 0x00), + ILI9881C_COMMAND_INSTR(0x0c, 0x00), + ILI9881C_COMMAND_INSTR(0x0d, 0x00), + ILI9881C_COMMAND_INSTR(0x0e, 0x00), + ILI9881C_COMMAND_INSTR(0x0f, 0x00), + ILI9881C_COMMAND_INSTR(0x10, 0x00), + ILI9881C_COMMAND_INSTR(0x11, 0x00), + ILI9881C_COMMAND_INSTR(0x12, 0x00), + ILI9881C_COMMAND_INSTR(0x13, 0x00), + ILI9881C_COMMAND_INSTR(0x14, 0x00), + ILI9881C_COMMAND_INSTR(0x15, 0x08), + ILI9881C_COMMAND_INSTR(0x16, 0x10), + ILI9881C_COMMAND_INSTR(0x17, 0x00), + ILI9881C_COMMAND_INSTR(0x18, 0x08), + ILI9881C_COMMAND_INSTR(0x19, 0x00), + ILI9881C_COMMAND_INSTR(0x1a, 0x00), + ILI9881C_COMMAND_INSTR(0x1b, 0x00), + ILI9881C_COMMAND_INSTR(0x1c, 0x00), + ILI9881C_COMMAND_INSTR(0x1d, 0x00), + ILI9881C_COMMAND_INSTR(0x1e, 0xc0), + ILI9881C_COMMAND_INSTR(0x1f, 0x80), + ILI9881C_COMMAND_INSTR(0x20, 0x02), + ILI9881C_COMMAND_INSTR(0x21, 0x09), + ILI9881C_COMMAND_INSTR(0x22, 0x00), + ILI9881C_COMMAND_INSTR(0x23, 0x00), + ILI9881C_COMMAND_INSTR(0x24, 0x00), + ILI9881C_COMMAND_INSTR(0x25, 0x00), + ILI9881C_COMMAND_INSTR(0x26, 0x00), + ILI9881C_COMMAND_INSTR(0x27, 0x00), + ILI9881C_COMMAND_INSTR(0x28, 0x55), + ILI9881C_COMMAND_INSTR(0x29, 0x03), + ILI9881C_COMMAND_INSTR(0x2a, 0x00), + ILI9881C_COMMAND_INSTR(0x2b, 0x00), + ILI9881C_COMMAND_INSTR(0x2c, 0x00), + ILI9881C_COMMAND_INSTR(0x2d, 0x00), + ILI9881C_COMMAND_INSTR(0x2e, 0x00), + ILI9881C_COMMAND_INSTR(0x2f, 0x00), + ILI9881C_COMMAND_INSTR(0x30, 0x00), + ILI9881C_COMMAND_INSTR(0x31, 0x00), + ILI9881C_COMMAND_INSTR(0x32, 0x00), + ILI9881C_COMMAND_INSTR(0x33, 0x00), + ILI9881C_COMMAND_INSTR(0x34, 0x04), + ILI9881C_COMMAND_INSTR(0x35, 0x05), + ILI9881C_COMMAND_INSTR(0x36, 0x05), + ILI9881C_COMMAND_INSTR(0x37, 0x00), + ILI9881C_COMMAND_INSTR(0x38, 0x3c), + ILI9881C_COMMAND_INSTR(0x39, 0x35), + ILI9881C_COMMAND_INSTR(0x3a, 0x00), + ILI9881C_COMMAND_INSTR(0x3b, 0x40), + ILI9881C_COMMAND_INSTR(0x3c, 0x00), + ILI9881C_COMMAND_INSTR(0x3d, 0x00), + ILI9881C_COMMAND_INSTR(0x3e, 0x00), + ILI9881C_COMMAND_INSTR(0x3f, 0x00), + ILI9881C_COMMAND_INSTR(0x40, 0x00), + ILI9881C_COMMAND_INSTR(0x41, 0x88), + ILI9881C_COMMAND_INSTR(0x42, 0x00), + ILI9881C_COMMAND_INSTR(0x43, 0x00), + ILI9881C_COMMAND_INSTR(0x44, 0x1f), + ILI9881C_COMMAND_INSTR(0x50, 0x01), + ILI9881C_COMMAND_INSTR(0x51, 0x23), + ILI9881C_COMMAND_INSTR(0x52, 0x45), + ILI9881C_COMMAND_INSTR(0x53, 0x67), + ILI9881C_COMMAND_INSTR(0x54, 0x89), + ILI9881C_COMMAND_INSTR(0x55, 0xab), + ILI9881C_COMMAND_INSTR(0x56, 0x01), + ILI9881C_COMMAND_INSTR(0x57, 0x23), + ILI9881C_COMMAND_INSTR(0x58, 0x45), + ILI9881C_COMMAND_INSTR(0x59, 0x67), + ILI9881C_COMMAND_INSTR(0x5a, 0x89), + ILI9881C_COMMAND_INSTR(0x5b, 0xab), + ILI9881C_COMMAND_INSTR(0x5c, 0xcd), + ILI9881C_COMMAND_INSTR(0x5d, 0xef), + ILI9881C_COMMAND_INSTR(0x5e, 0x03), + ILI9881C_COMMAND_INSTR(0x5f, 0x14), + ILI9881C_COMMAND_INSTR(0x60, 0x15), + ILI9881C_COMMAND_INSTR(0x61, 0x0c), + ILI9881C_COMMAND_INSTR(0x62, 0x0d), + ILI9881C_COMMAND_INSTR(0x63, 0x0e), + ILI9881C_COMMAND_INSTR(0x64, 0x0f), + ILI9881C_COMMAND_INSTR(0x65, 0x10), + ILI9881C_COMMAND_INSTR(0x66, 0x11), + ILI9881C_COMMAND_INSTR(0x67, 0x08), + ILI9881C_COMMAND_INSTR(0x68, 0x02), + ILI9881C_COMMAND_INSTR(0x69, 0x0a), + ILI9881C_COMMAND_INSTR(0x6a, 0x02), + ILI9881C_COMMAND_INSTR(0x6b, 0x02), + ILI9881C_COMMAND_INSTR(0x6c, 0x02), + ILI9881C_COMMAND_INSTR(0x6d, 0x02), + ILI9881C_COMMAND_INSTR(0x6e, 0x02), + ILI9881C_COMMAND_INSTR(0x6f, 0x02), + ILI9881C_COMMAND_INSTR(0x70, 0x02), + ILI9881C_COMMAND_INSTR(0x71, 0x02), + ILI9881C_COMMAND_INSTR(0x72, 0x06), + ILI9881C_COMMAND_INSTR(0x73, 0x02), + ILI9881C_COMMAND_INSTR(0x74, 0x02), + ILI9881C_COMMAND_INSTR(0x75, 0x14), + ILI9881C_COMMAND_INSTR(0x76, 0x15), + ILI9881C_COMMAND_INSTR(0x77, 0x0f), + ILI9881C_COMMAND_INSTR(0x78, 0x0e), + ILI9881C_COMMAND_INSTR(0x79, 0x0d), + ILI9881C_COMMAND_INSTR(0x7a, 0x0c), + ILI9881C_COMMAND_INSTR(0x7b, 0x11), + ILI9881C_COMMAND_INSTR(0x7c, 0x10), + ILI9881C_COMMAND_INSTR(0x7d, 0x06), + ILI9881C_COMMAND_INSTR(0x7e, 0x02), + ILI9881C_COMMAND_INSTR(0x7f, 0x0a), + ILI9881C_COMMAND_INSTR(0x80, 0x02), + ILI9881C_COMMAND_INSTR(0x81, 0x02), + ILI9881C_COMMAND_INSTR(0x82, 0x02), + ILI9881C_COMMAND_INSTR(0x83, 0x02), + ILI9881C_COMMAND_INSTR(0x84, 0x02), + ILI9881C_COMMAND_INSTR(0x85, 0x02), + ILI9881C_COMMAND_INSTR(0x86, 0x02), + ILI9881C_COMMAND_INSTR(0x87, 0x02), + ILI9881C_COMMAND_INSTR(0x88, 0x08), + ILI9881C_COMMAND_INSTR(0x89, 0x02), + ILI9881C_COMMAND_INSTR(0x8a, 0x02), + ILI9881C_SWITCH_PAGE_INSTR(4), + ILI9881C_COMMAND_INSTR(0x00, 0x80), + ILI9881C_COMMAND_INSTR(0x70, 0x00), + ILI9881C_COMMAND_INSTR(0x71, 0x00), + ILI9881C_COMMAND_INSTR(0x66, 0xfe), + ILI9881C_COMMAND_INSTR(0x82, 0x15), + ILI9881C_COMMAND_INSTR(0x84, 0x15), + ILI9881C_COMMAND_INSTR(0x85, 0x15), + ILI9881C_COMMAND_INSTR(0x3a, 0x24), + ILI9881C_COMMAND_INSTR(0x32, 0xac), + ILI9881C_COMMAND_INSTR(0x8c, 0x80), + ILI9881C_COMMAND_INSTR(0x3c, 0xf5), + ILI9881C_COMMAND_INSTR(0x88, 0x33), + ILI9881C_SWITCH_PAGE_INSTR(1), + ILI9881C_COMMAND_INSTR(0x22, 0x0a), + ILI9881C_COMMAND_INSTR(0x31, 0x00), + ILI9881C_COMMAND_INSTR(0x53, 0x78), + ILI9881C_COMMAND_INSTR(0x55, 0x7b), + ILI9881C_COMMAND_INSTR(0x60, 0x20), + ILI9881C_COMMAND_INSTR(0x61, 0x00), + ILI9881C_COMMAND_INSTR(0x62, 0x0d), + ILI9881C_COMMAND_INSTR(0x63, 0x00), + ILI9881C_COMMAND_INSTR(0xa0, 0x00), + ILI9881C_COMMAND_INSTR(0xa1, 0x10), + ILI9881C_COMMAND_INSTR(0xa2, 0x1c), + ILI9881C_COMMAND_INSTR(0xa3, 0x13), + ILI9881C_COMMAND_INSTR(0xa4, 0x15), + ILI9881C_COMMAND_INSTR(0xa5, 0x26), + ILI9881C_COMMAND_INSTR(0xa6, 0x1a), + ILI9881C_COMMAND_INSTR(0xa7, 0x1d), + ILI9881C_COMMAND_INSTR(0xa8, 0x67), + ILI9881C_COMMAND_INSTR(0xa9, 0x1c), + ILI9881C_COMMAND_INSTR(0xaa, 0x29), + ILI9881C_COMMAND_INSTR(0xab, 0x5b), + ILI9881C_COMMAND_INSTR(0xac, 0x26), + ILI9881C_COMMAND_INSTR(0xad, 0x28), + ILI9881C_COMMAND_INSTR(0xae, 0x5c), + ILI9881C_COMMAND_INSTR(0xaf, 0x30), + ILI9881C_COMMAND_INSTR(0xb0, 0x31), + ILI9881C_COMMAND_INSTR(0xb1, 0x32), + ILI9881C_COMMAND_INSTR(0xb2, 0x00), + ILI9881C_COMMAND_INSTR(0xb1, 0x2e), + ILI9881C_COMMAND_INSTR(0xb2, 0x32), + ILI9881C_COMMAND_INSTR(0xb3, 0x00), + ILI9881C_COMMAND_INSTR(0xb6, 0x02), + ILI9881C_COMMAND_INSTR(0xb7, 0x03), + ILI9881C_COMMAND_INSTR(0xc0, 0x00), + ILI9881C_COMMAND_INSTR(0xc1, 0x10), + ILI9881C_COMMAND_INSTR(0xc2, 0x1c), + ILI9881C_COMMAND_INSTR(0xc3, 0x13), + ILI9881C_COMMAND_INSTR(0xc4, 0x15), + ILI9881C_COMMAND_INSTR(0xc5, 0x26), + ILI9881C_COMMAND_INSTR(0xc6, 0x1a), + ILI9881C_COMMAND_INSTR(0xc7, 0x1d), + ILI9881C_COMMAND_INSTR(0xc8, 0x67), + ILI9881C_COMMAND_INSTR(0xc9, 0x1c), + ILI9881C_COMMAND_INSTR(0xca, 0x29), + ILI9881C_COMMAND_INSTR(0xcb, 0x5b), + ILI9881C_COMMAND_INSTR(0xcc, 0x26), + ILI9881C_COMMAND_INSTR(0xcd, 0x28), + ILI9881C_COMMAND_INSTR(0xce, 0x5c), + ILI9881C_COMMAND_INSTR(0xcf, 0x30), + ILI9881C_COMMAND_INSTR(0xd0, 0x31), + ILI9881C_COMMAND_INSTR(0xd1, 0x2e), + ILI9881C_COMMAND_INSTR(0xd2, 0x32), + ILI9881C_COMMAND_INSTR(0xd3, 0x00), + ILI9881C_SWITCH_PAGE_INSTR(0), +}; + +static const struct ili9881c_instr w552946ab_init[] = { + ILI9881C_SWITCH_PAGE_INSTR(3), + ILI9881C_COMMAND_INSTR(0x01, 0x00), + ILI9881C_COMMAND_INSTR(0x02, 0x00), + ILI9881C_COMMAND_INSTR(0x03, 0x53), + ILI9881C_COMMAND_INSTR(0x04, 0x53), + ILI9881C_COMMAND_INSTR(0x05, 0x13), + ILI9881C_COMMAND_INSTR(0x06, 0x04), + ILI9881C_COMMAND_INSTR(0x07, 0x02), + ILI9881C_COMMAND_INSTR(0x08, 0x02), + ILI9881C_COMMAND_INSTR(0x09, 0x00), + ILI9881C_COMMAND_INSTR(0x0a, 0x00), + ILI9881C_COMMAND_INSTR(0x0b, 0x00), + ILI9881C_COMMAND_INSTR(0x0c, 0x00), + ILI9881C_COMMAND_INSTR(0x0d, 0x00), + ILI9881C_COMMAND_INSTR(0x0e, 0x00), + ILI9881C_COMMAND_INSTR(0x0f, 0x00), + + ILI9881C_COMMAND_INSTR(0x10, 0x00), + ILI9881C_COMMAND_INSTR(0x11, 0x00), + ILI9881C_COMMAND_INSTR(0x12, 0x00), + ILI9881C_COMMAND_INSTR(0x13, 0x00), + ILI9881C_COMMAND_INSTR(0x14, 0x00), + ILI9881C_COMMAND_INSTR(0x15, 0x08), + ILI9881C_COMMAND_INSTR(0x16, 0x10), + ILI9881C_COMMAND_INSTR(0x17, 0x00), + ILI9881C_COMMAND_INSTR(0x18, 0x08), + ILI9881C_COMMAND_INSTR(0x19, 0x00), + ILI9881C_COMMAND_INSTR(0x1a, 0x00), + ILI9881C_COMMAND_INSTR(0x1b, 0x00), + ILI9881C_COMMAND_INSTR(0x1c, 0x00), + ILI9881C_COMMAND_INSTR(0x1d, 0x00), + ILI9881C_COMMAND_INSTR(0x1e, 0xc0), + ILI9881C_COMMAND_INSTR(0x1f, 0x80), + + ILI9881C_COMMAND_INSTR(0x20, 0x02), + ILI9881C_COMMAND_INSTR(0x21, 0x09), + ILI9881C_COMMAND_INSTR(0x22, 0x00), + ILI9881C_COMMAND_INSTR(0x23, 0x00), + ILI9881C_COMMAND_INSTR(0x24, 0x00), + ILI9881C_COMMAND_INSTR(0x25, 0x00), + ILI9881C_COMMAND_INSTR(0x26, 0x00), + ILI9881C_COMMAND_INSTR(0x27, 0x00), + ILI9881C_COMMAND_INSTR(0x28, 0x55), + ILI9881C_COMMAND_INSTR(0x29, 0x03), + ILI9881C_COMMAND_INSTR(0x2a, 0x00), + ILI9881C_COMMAND_INSTR(0x2b, 0x00), + ILI9881C_COMMAND_INSTR(0x2c, 0x00), + ILI9881C_COMMAND_INSTR(0x2d, 0x00), + ILI9881C_COMMAND_INSTR(0x2e, 0x00), + ILI9881C_COMMAND_INSTR(0x2f, 0x00), + + ILI9881C_COMMAND_INSTR(0x30, 0x00), + ILI9881C_COMMAND_INSTR(0x31, 0x00), + ILI9881C_COMMAND_INSTR(0x32, 0x00), + ILI9881C_COMMAND_INSTR(0x33, 0x00), + ILI9881C_COMMAND_INSTR(0x34, 0x04), + ILI9881C_COMMAND_INSTR(0x35, 0x05), + ILI9881C_COMMAND_INSTR(0x36, 0x05), + ILI9881C_COMMAND_INSTR(0x37, 0x00), + ILI9881C_COMMAND_INSTR(0x38, 0x3c), + ILI9881C_COMMAND_INSTR(0x39, 0x35), + ILI9881C_COMMAND_INSTR(0x3a, 0x00), + ILI9881C_COMMAND_INSTR(0x3b, 0x40), + ILI9881C_COMMAND_INSTR(0x3c, 0x00), + ILI9881C_COMMAND_INSTR(0x3d, 0x00), + ILI9881C_COMMAND_INSTR(0x3e, 0x00), + ILI9881C_COMMAND_INSTR(0x3f, 0x00), + + ILI9881C_COMMAND_INSTR(0x40, 0x00), + ILI9881C_COMMAND_INSTR(0x41, 0x88), + ILI9881C_COMMAND_INSTR(0x42, 0x00), + ILI9881C_COMMAND_INSTR(0x43, 0x00), + ILI9881C_COMMAND_INSTR(0x44, 0x1f), + + ILI9881C_COMMAND_INSTR(0x50, 0x01), + ILI9881C_COMMAND_INSTR(0x51, 0x23), + ILI9881C_COMMAND_INSTR(0x52, 0x45), + ILI9881C_COMMAND_INSTR(0x53, 0x67), + ILI9881C_COMMAND_INSTR(0x54, 0x89), + ILI9881C_COMMAND_INSTR(0x55, 0xab), + ILI9881C_COMMAND_INSTR(0x56, 0x01), + ILI9881C_COMMAND_INSTR(0x57, 0x23), + ILI9881C_COMMAND_INSTR(0x58, 0x45), + ILI9881C_COMMAND_INSTR(0x59, 0x67), + ILI9881C_COMMAND_INSTR(0x5a, 0x89), + ILI9881C_COMMAND_INSTR(0x5b, 0xab), + ILI9881C_COMMAND_INSTR(0x5c, 0xcd), + ILI9881C_COMMAND_INSTR(0x5d, 0xef), + ILI9881C_COMMAND_INSTR(0x5e, 0x03), + ILI9881C_COMMAND_INSTR(0x5f, 0x14), + + ILI9881C_COMMAND_INSTR(0x60, 0x15), + ILI9881C_COMMAND_INSTR(0x61, 0x0c), + ILI9881C_COMMAND_INSTR(0x62, 0x0d), + ILI9881C_COMMAND_INSTR(0x63, 0x0e), + ILI9881C_COMMAND_INSTR(0x64, 0x0f), + ILI9881C_COMMAND_INSTR(0x65, 0x10), + ILI9881C_COMMAND_INSTR(0x66, 0x11), + ILI9881C_COMMAND_INSTR(0x67, 0x08), + ILI9881C_COMMAND_INSTR(0x68, 0x02), + ILI9881C_COMMAND_INSTR(0x69, 0x0a), + ILI9881C_COMMAND_INSTR(0x6a, 0x02), + ILI9881C_COMMAND_INSTR(0x6b, 0x02), + ILI9881C_COMMAND_INSTR(0x6c, 0x02), + ILI9881C_COMMAND_INSTR(0x6d, 0x02), + ILI9881C_COMMAND_INSTR(0x6e, 0x02), + ILI9881C_COMMAND_INSTR(0x6f, 0x02), + + ILI9881C_COMMAND_INSTR(0x70, 0x02), + ILI9881C_COMMAND_INSTR(0x71, 0x02), + ILI9881C_COMMAND_INSTR(0x72, 0x06), + ILI9881C_COMMAND_INSTR(0x73, 0x02), + ILI9881C_COMMAND_INSTR(0x74, 0x02), + ILI9881C_COMMAND_INSTR(0x75, 0x14), + ILI9881C_COMMAND_INSTR(0x76, 0x15), + ILI9881C_COMMAND_INSTR(0x77, 0x0f), + ILI9881C_COMMAND_INSTR(0x78, 0x0e), + ILI9881C_COMMAND_INSTR(0x79, 0x0d), + ILI9881C_COMMAND_INSTR(0x7a, 0x0c), + ILI9881C_COMMAND_INSTR(0x7b, 0x11), + ILI9881C_COMMAND_INSTR(0x7c, 0x10), + ILI9881C_COMMAND_INSTR(0x7d, 0x06), + ILI9881C_COMMAND_INSTR(0x7e, 0x02), + ILI9881C_COMMAND_INSTR(0x7f, 0x0a), + + ILI9881C_COMMAND_INSTR(0x80, 0x02), + ILI9881C_COMMAND_INSTR(0x81, 0x02), + ILI9881C_COMMAND_INSTR(0x82, 0x02), + ILI9881C_COMMAND_INSTR(0x83, 0x02), + ILI9881C_COMMAND_INSTR(0x84, 0x02), + ILI9881C_COMMAND_INSTR(0x85, 0x02), + ILI9881C_COMMAND_INSTR(0x86, 0x02), + ILI9881C_COMMAND_INSTR(0x87, 0x02), + ILI9881C_COMMAND_INSTR(0x88, 0x08), + ILI9881C_COMMAND_INSTR(0x89, 0x02), + ILI9881C_COMMAND_INSTR(0x8a, 0x02), + + ILI9881C_SWITCH_PAGE_INSTR(4), + ILI9881C_COMMAND_INSTR(0x00, 0x80), + ILI9881C_COMMAND_INSTR(0x70, 0x00), + ILI9881C_COMMAND_INSTR(0x71, 0x00), + ILI9881C_COMMAND_INSTR(0x66, 0xfe), + ILI9881C_COMMAND_INSTR(0x82, 0x15), + ILI9881C_COMMAND_INSTR(0x84, 0x15), + ILI9881C_COMMAND_INSTR(0x85, 0x15), + ILI9881C_COMMAND_INSTR(0x3a, 0x24), + ILI9881C_COMMAND_INSTR(0x32, 0xac), + ILI9881C_COMMAND_INSTR(0x8c, 0x80), + ILI9881C_COMMAND_INSTR(0x3c, 0xf5), + ILI9881C_COMMAND_INSTR(0x88, 0x33), + + ILI9881C_SWITCH_PAGE_INSTR(1), + ILI9881C_COMMAND_INSTR(0x22, 0x0a), + ILI9881C_COMMAND_INSTR(0x31, 0x00), + ILI9881C_COMMAND_INSTR(0x53, 0x78), + ILI9881C_COMMAND_INSTR(0x50, 0x5b), + ILI9881C_COMMAND_INSTR(0x51, 0x5b), + ILI9881C_COMMAND_INSTR(0x60, 0x20), + ILI9881C_COMMAND_INSTR(0x61, 0x00), + ILI9881C_COMMAND_INSTR(0x62, 0x0d), + ILI9881C_COMMAND_INSTR(0x63, 0x00), + + ILI9881C_COMMAND_INSTR(0xa0, 0x00), + ILI9881C_COMMAND_INSTR(0xa1, 0x10), + ILI9881C_COMMAND_INSTR(0xa2, 0x1c), + ILI9881C_COMMAND_INSTR(0xa3, 0x13), + ILI9881C_COMMAND_INSTR(0xa4, 0x15), + ILI9881C_COMMAND_INSTR(0xa5, 0x26), + ILI9881C_COMMAND_INSTR(0xa6, 0x1a), + ILI9881C_COMMAND_INSTR(0xa7, 0x1d), + ILI9881C_COMMAND_INSTR(0xa8, 0x67), + ILI9881C_COMMAND_INSTR(0xa9, 0x1c), + ILI9881C_COMMAND_INSTR(0xaa, 0x29), + ILI9881C_COMMAND_INSTR(0xab, 0x5b), + ILI9881C_COMMAND_INSTR(0xac, 0x26), + ILI9881C_COMMAND_INSTR(0xad, 0x28), + ILI9881C_COMMAND_INSTR(0xae, 0x5c), + ILI9881C_COMMAND_INSTR(0xaf, 0x30), + ILI9881C_COMMAND_INSTR(0xb0, 0x31), + ILI9881C_COMMAND_INSTR(0xb1, 0x2e), + ILI9881C_COMMAND_INSTR(0xb2, 0x32), + ILI9881C_COMMAND_INSTR(0xb3, 0x00), + + ILI9881C_COMMAND_INSTR(0xc0, 0x00), + ILI9881C_COMMAND_INSTR(0xc1, 0x10), + ILI9881C_COMMAND_INSTR(0xc2, 0x1c), + ILI9881C_COMMAND_INSTR(0xc3, 0x13), + ILI9881C_COMMAND_INSTR(0xc4, 0x15), + ILI9881C_COMMAND_INSTR(0xc5, 0x26), + ILI9881C_COMMAND_INSTR(0xc6, 0x1a), + ILI9881C_COMMAND_INSTR(0xc7, 0x1d), + ILI9881C_COMMAND_INSTR(0xc8, 0x67), + ILI9881C_COMMAND_INSTR(0xc9, 0x1c), + ILI9881C_COMMAND_INSTR(0xca, 0x29), + ILI9881C_COMMAND_INSTR(0xcb, 0x5b), + ILI9881C_COMMAND_INSTR(0xcc, 0x26), + ILI9881C_COMMAND_INSTR(0xcd, 0x28), + ILI9881C_COMMAND_INSTR(0xce, 0x5c), + ILI9881C_COMMAND_INSTR(0xcf, 0x30), + ILI9881C_COMMAND_INSTR(0xd0, 0x31), + ILI9881C_COMMAND_INSTR(0xd1, 0x2e), + ILI9881C_COMMAND_INSTR(0xd2, 0x32), + ILI9881C_COMMAND_INSTR(0xd3, 0x00), + ILI9881C_SWITCH_PAGE_INSTR(0), +}; + +static const struct ili9881c_instr am8001280g_init[] = { + ILI9881C_SWITCH_PAGE_INSTR(3), + ILI9881C_COMMAND_INSTR(0x01, 0x00), + ILI9881C_COMMAND_INSTR(0x02, 0x00), + ILI9881C_COMMAND_INSTR(0x03, 0x73), + ILI9881C_COMMAND_INSTR(0x04, 0xd3), + ILI9881C_COMMAND_INSTR(0x05, 0x00), + ILI9881C_COMMAND_INSTR(0x06, 0x0a), + ILI9881C_COMMAND_INSTR(0x07, 0x0e), + ILI9881C_COMMAND_INSTR(0x08, 0x00), + ILI9881C_COMMAND_INSTR(0x09, 0x01), + ILI9881C_COMMAND_INSTR(0x0a, 0x01), + ILI9881C_COMMAND_INSTR(0x0b, 0x01), + ILI9881C_COMMAND_INSTR(0x0c, 0x01), + ILI9881C_COMMAND_INSTR(0x0d, 0x01), + ILI9881C_COMMAND_INSTR(0x0e, 0x01), + ILI9881C_COMMAND_INSTR(0x0f, 0x01), + ILI9881C_COMMAND_INSTR(0x10, 0x01), + ILI9881C_COMMAND_INSTR(0x11, 0x00), + ILI9881C_COMMAND_INSTR(0x12, 0x00), + ILI9881C_COMMAND_INSTR(0x13, 0x00), + ILI9881C_COMMAND_INSTR(0x14, 0x00), + ILI9881C_COMMAND_INSTR(0x15, 0x00), + ILI9881C_COMMAND_INSTR(0x16, 0x00), + ILI9881C_COMMAND_INSTR(0x17, 0x00), + ILI9881C_COMMAND_INSTR(0x18, 0x00), + ILI9881C_COMMAND_INSTR(0x19, 0x00), + ILI9881C_COMMAND_INSTR(0x1a, 0x00), + ILI9881C_COMMAND_INSTR(0x1b, 0x00), + ILI9881C_COMMAND_INSTR(0x1c, 0x00), + ILI9881C_COMMAND_INSTR(0x1d, 0x00), + ILI9881C_COMMAND_INSTR(0x1e, 0x40), + ILI9881C_COMMAND_INSTR(0x1f, 0x80), + ILI9881C_COMMAND_INSTR(0x20, 0x06), + ILI9881C_COMMAND_INSTR(0x21, 0x01), + ILI9881C_COMMAND_INSTR(0x22, 0x00), + ILI9881C_COMMAND_INSTR(0x23, 0x00), + ILI9881C_COMMAND_INSTR(0x24, 0x00), + ILI9881C_COMMAND_INSTR(0x25, 0x00), + ILI9881C_COMMAND_INSTR(0x26, 0x00), + ILI9881C_COMMAND_INSTR(0x27, 0x00), + ILI9881C_COMMAND_INSTR(0x28, 0x33), + ILI9881C_COMMAND_INSTR(0x29, 0x03), + ILI9881C_COMMAND_INSTR(0x2a, 0x00), + ILI9881C_COMMAND_INSTR(0x2b, 0x00), + ILI9881C_COMMAND_INSTR(0x2c, 0x00), + ILI9881C_COMMAND_INSTR(0x2d, 0x00), + ILI9881C_COMMAND_INSTR(0x2e, 0x00), + ILI9881C_COMMAND_INSTR(0x2f, 0x00), + ILI9881C_COMMAND_INSTR(0x30, 0x00), + ILI9881C_COMMAND_INSTR(0x31, 0x00), + ILI9881C_COMMAND_INSTR(0x32, 0x00), + ILI9881C_COMMAND_INSTR(0x33, 0x00), + ILI9881C_COMMAND_INSTR(0x34, 0x03), + ILI9881C_COMMAND_INSTR(0x35, 0x00), + ILI9881C_COMMAND_INSTR(0x36, 0x03), + ILI9881C_COMMAND_INSTR(0x37, 0x00), + ILI9881C_COMMAND_INSTR(0x38, 0x00), + ILI9881C_COMMAND_INSTR(0x39, 0x00), + ILI9881C_COMMAND_INSTR(0x3a, 0x40), + ILI9881C_COMMAND_INSTR(0x3b, 0x40), + ILI9881C_COMMAND_INSTR(0x3c, 0x00), + ILI9881C_COMMAND_INSTR(0x3d, 0x00), + ILI9881C_COMMAND_INSTR(0x3e, 0x00), + ILI9881C_COMMAND_INSTR(0x3f, 0x00), + ILI9881C_COMMAND_INSTR(0x40, 0x00), + ILI9881C_COMMAND_INSTR(0x41, 0x00), + ILI9881C_COMMAND_INSTR(0x42, 0x00), + ILI9881C_COMMAND_INSTR(0x43, 0x00), + ILI9881C_COMMAND_INSTR(0x44, 0x00), + + ILI9881C_COMMAND_INSTR(0x50, 0x01), + ILI9881C_COMMAND_INSTR(0x51, 0x23), + ILI9881C_COMMAND_INSTR(0x52, 0x45), + ILI9881C_COMMAND_INSTR(0x53, 0x67), + ILI9881C_COMMAND_INSTR(0x54, 0x89), + ILI9881C_COMMAND_INSTR(0x55, 0xab), + ILI9881C_COMMAND_INSTR(0x56, 0x01), + ILI9881C_COMMAND_INSTR(0x57, 0x23), + ILI9881C_COMMAND_INSTR(0x58, 0x45), + ILI9881C_COMMAND_INSTR(0x59, 0x67), + ILI9881C_COMMAND_INSTR(0x5a, 0x89), + ILI9881C_COMMAND_INSTR(0x5b, 0xab), + ILI9881C_COMMAND_INSTR(0x5c, 0xcd), + ILI9881C_COMMAND_INSTR(0x5d, 0xef), + + ILI9881C_COMMAND_INSTR(0x5e, 0x11), + ILI9881C_COMMAND_INSTR(0x5f, 0x02), + ILI9881C_COMMAND_INSTR(0x60, 0x00), + ILI9881C_COMMAND_INSTR(0x61, 0x01), + ILI9881C_COMMAND_INSTR(0x62, 0x0d), + ILI9881C_COMMAND_INSTR(0x63, 0x0c), + ILI9881C_COMMAND_INSTR(0x64, 0x0f), + ILI9881C_COMMAND_INSTR(0x65, 0x0e), + ILI9881C_COMMAND_INSTR(0x66, 0x06), + ILI9881C_COMMAND_INSTR(0x67, 0x07), + ILI9881C_COMMAND_INSTR(0x68, 0x02), + ILI9881C_COMMAND_INSTR(0x69, 0x02), + ILI9881C_COMMAND_INSTR(0x6a, 0x08), + ILI9881C_COMMAND_INSTR(0x6b, 0x02), + ILI9881C_COMMAND_INSTR(0x6c, 0x02), + ILI9881C_COMMAND_INSTR(0x6d, 0x02), + ILI9881C_COMMAND_INSTR(0x6e, 0x02), + ILI9881C_COMMAND_INSTR(0x6f, 0x02), + ILI9881C_COMMAND_INSTR(0x70, 0x02), + ILI9881C_COMMAND_INSTR(0x71, 0x02), + ILI9881C_COMMAND_INSTR(0x72, 0x02), + ILI9881C_COMMAND_INSTR(0x73, 0x02), + ILI9881C_COMMAND_INSTR(0x74, 0x02), + ILI9881C_COMMAND_INSTR(0x75, 0x02), + ILI9881C_COMMAND_INSTR(0x76, 0x00), + ILI9881C_COMMAND_INSTR(0x77, 0x01), + ILI9881C_COMMAND_INSTR(0x78, 0x0d), + ILI9881C_COMMAND_INSTR(0x79, 0x0c), + ILI9881C_COMMAND_INSTR(0x7a, 0x0f), + ILI9881C_COMMAND_INSTR(0x7b, 0x0e), + ILI9881C_COMMAND_INSTR(0x7c, 0x06), + ILI9881C_COMMAND_INSTR(0x7d, 0x07), + ILI9881C_COMMAND_INSTR(0x7e, 0x02), + ILI9881C_COMMAND_INSTR(0x7f, 0x02), + ILI9881C_COMMAND_INSTR(0x80, 0x08), + ILI9881C_COMMAND_INSTR(0x81, 0x02), + ILI9881C_COMMAND_INSTR(0x82, 0x02), + ILI9881C_COMMAND_INSTR(0x83, 0x02), + ILI9881C_COMMAND_INSTR(0x84, 0x02), + ILI9881C_COMMAND_INSTR(0x85, 0x02), + ILI9881C_COMMAND_INSTR(0x86, 0x02), + ILI9881C_COMMAND_INSTR(0x87, 0x02), + ILI9881C_COMMAND_INSTR(0x88, 0x02), + ILI9881C_COMMAND_INSTR(0x89, 0x02), + ILI9881C_COMMAND_INSTR(0x8a, 0x02), + + ILI9881C_SWITCH_PAGE_INSTR(4), + ILI9881C_COMMAND_INSTR(0x6c, 0x15), + ILI9881C_COMMAND_INSTR(0x6e, 0x30), + ILI9881C_COMMAND_INSTR(0x6f, 0x33), + ILI9881C_COMMAND_INSTR(0x8d, 0x15), + ILI9881C_COMMAND_INSTR(0x3a, 0xa4), + ILI9881C_COMMAND_INSTR(0x87, 0xba), + ILI9881C_COMMAND_INSTR(0x26, 0x76), + ILI9881C_COMMAND_INSTR(0xb2, 0xd1), + + ILI9881C_SWITCH_PAGE_INSTR(1), + ILI9881C_COMMAND_INSTR(0x22, 0x0a), + ILI9881C_COMMAND_INSTR(0x31, 0x0b), + ILI9881C_COMMAND_INSTR(0x50, 0xa5), + ILI9881C_COMMAND_INSTR(0x51, 0xa0), + ILI9881C_COMMAND_INSTR(0x53, 0x70), + ILI9881C_COMMAND_INSTR(0x55, 0x7a), + ILI9881C_COMMAND_INSTR(0x60, 0x14), + + ILI9881C_COMMAND_INSTR(0xa0, 0x00), + ILI9881C_COMMAND_INSTR(0xa1, 0x53), + ILI9881C_COMMAND_INSTR(0xa2, 0x50), + ILI9881C_COMMAND_INSTR(0xa3, 0x20), + ILI9881C_COMMAND_INSTR(0xa4, 0x27), + ILI9881C_COMMAND_INSTR(0xa5, 0x33), + ILI9881C_COMMAND_INSTR(0xa6, 0x25), + ILI9881C_COMMAND_INSTR(0xa7, 0x25), + ILI9881C_COMMAND_INSTR(0xa8, 0xd4), + ILI9881C_COMMAND_INSTR(0xa9, 0x1a), + ILI9881C_COMMAND_INSTR(0xaa, 0x2b), + ILI9881C_COMMAND_INSTR(0xab, 0xb5), + ILI9881C_COMMAND_INSTR(0xac, 0x19), + ILI9881C_COMMAND_INSTR(0xad, 0x18), + ILI9881C_COMMAND_INSTR(0xae, 0x53), + ILI9881C_COMMAND_INSTR(0xaf, 0x1a), + ILI9881C_COMMAND_INSTR(0xb0, 0x25), + ILI9881C_COMMAND_INSTR(0xb1, 0x62), + ILI9881C_COMMAND_INSTR(0xb2, 0x6a), + ILI9881C_COMMAND_INSTR(0xb3, 0x31), + + ILI9881C_COMMAND_INSTR(0xc0, 0x00), + ILI9881C_COMMAND_INSTR(0xc1, 0x53), + ILI9881C_COMMAND_INSTR(0xc2, 0x50), + ILI9881C_COMMAND_INSTR(0xc3, 0x20), + ILI9881C_COMMAND_INSTR(0xc4, 0x27), + ILI9881C_COMMAND_INSTR(0xc5, 0x33), + ILI9881C_COMMAND_INSTR(0xc6, 0x25), + ILI9881C_COMMAND_INSTR(0xc7, 0x25), + ILI9881C_COMMAND_INSTR(0xc8, 0xd4), + ILI9881C_COMMAND_INSTR(0xc9, 0x1a), + ILI9881C_COMMAND_INSTR(0xca, 0x2b), + ILI9881C_COMMAND_INSTR(0xcb, 0xb5), + ILI9881C_COMMAND_INSTR(0xcc, 0x19), + ILI9881C_COMMAND_INSTR(0xcd, 0x18), + ILI9881C_COMMAND_INSTR(0xce, 0x53), + ILI9881C_COMMAND_INSTR(0xcf, 0x1a), + ILI9881C_COMMAND_INSTR(0xd0, 0x25), + ILI9881C_COMMAND_INSTR(0xd1, 0x62), + ILI9881C_COMMAND_INSTR(0xd2, 0x6a), + ILI9881C_COMMAND_INSTR(0xd3, 0x31), + ILI9881C_SWITCH_PAGE_INSTR(0), + ILI9881C_COMMAND_INSTR(MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x2c), + ILI9881C_COMMAND_INSTR(MIPI_DCS_WRITE_POWER_SAVE, 0x00), +}; + +static const struct ili9881c_instr rpi_5inch_init[] = { + ILI9881C_SWITCH_PAGE_INSTR(3), + ILI9881C_COMMAND_INSTR(0x01, 0x00), + ILI9881C_COMMAND_INSTR(0x02, 0x00), + ILI9881C_COMMAND_INSTR(0x03, 0x73), + ILI9881C_COMMAND_INSTR(0x04, 0x73), + ILI9881C_COMMAND_INSTR(0x05, 0x00), + ILI9881C_COMMAND_INSTR(0x06, 0x06), + ILI9881C_COMMAND_INSTR(0x07, 0x02), + ILI9881C_COMMAND_INSTR(0x08, 0x00), + ILI9881C_COMMAND_INSTR(0x09, 0x01), + ILI9881C_COMMAND_INSTR(0x0a, 0x01), + ILI9881C_COMMAND_INSTR(0x0b, 0x01), + ILI9881C_COMMAND_INSTR(0x0c, 0x01), + ILI9881C_COMMAND_INSTR(0x0d, 0x01), + ILI9881C_COMMAND_INSTR(0x0e, 0x01), + ILI9881C_COMMAND_INSTR(0x0f, 0x01), + ILI9881C_COMMAND_INSTR(0x10, 0x01), + ILI9881C_COMMAND_INSTR(0x11, 0x00), + ILI9881C_COMMAND_INSTR(0x12, 0x00), + ILI9881C_COMMAND_INSTR(0x13, 0x01), + ILI9881C_COMMAND_INSTR(0x14, 0x00), + ILI9881C_COMMAND_INSTR(0x15, 0x00), + ILI9881C_COMMAND_INSTR(0x16, 0x00), + ILI9881C_COMMAND_INSTR(0x17, 0x00), + ILI9881C_COMMAND_INSTR(0x18, 0x00), + ILI9881C_COMMAND_INSTR(0x19, 0x00), + ILI9881C_COMMAND_INSTR(0x1a, 0x00), + ILI9881C_COMMAND_INSTR(0x1b, 0x00), + ILI9881C_COMMAND_INSTR(0x1c, 0x00), + ILI9881C_COMMAND_INSTR(0x1d, 0x00), + ILI9881C_COMMAND_INSTR(0x1e, 0xc0), + ILI9881C_COMMAND_INSTR(0x1f, 0x80), + ILI9881C_COMMAND_INSTR(0x20, 0x04), + ILI9881C_COMMAND_INSTR(0x21, 0x03), + ILI9881C_COMMAND_INSTR(0x22, 0x00), + ILI9881C_COMMAND_INSTR(0x23, 0x00), + ILI9881C_COMMAND_INSTR(0x24, 0x00), + ILI9881C_COMMAND_INSTR(0x25, 0x00), + ILI9881C_COMMAND_INSTR(0x26, 0x00), + ILI9881C_COMMAND_INSTR(0x27, 0x00), + ILI9881C_COMMAND_INSTR(0x28, 0x33), + ILI9881C_COMMAND_INSTR(0x29, 0x03), + ILI9881C_COMMAND_INSTR(0x2a, 0x00), + ILI9881C_COMMAND_INSTR(0x2b, 0x00), + ILI9881C_COMMAND_INSTR(0x2c, 0x00), + ILI9881C_COMMAND_INSTR(0x2d, 0x00), + ILI9881C_COMMAND_INSTR(0x2e, 0x00), + ILI9881C_COMMAND_INSTR(0x2f, 0x00), + ILI9881C_COMMAND_INSTR(0x30, 0x00), + ILI9881C_COMMAND_INSTR(0x31, 0x00), + ILI9881C_COMMAND_INSTR(0x32, 0x00), + ILI9881C_COMMAND_INSTR(0x33, 0x00), + ILI9881C_COMMAND_INSTR(0x34, 0x03), + ILI9881C_COMMAND_INSTR(0x35, 0x00), + ILI9881C_COMMAND_INSTR(0x36, 0x03), + ILI9881C_COMMAND_INSTR(0x37, 0x00), + ILI9881C_COMMAND_INSTR(0x38, 0x00), + ILI9881C_COMMAND_INSTR(0x39, 0x00), + ILI9881C_COMMAND_INSTR(0x3a, 0x00), + ILI9881C_COMMAND_INSTR(0x3b, 0x00), + ILI9881C_COMMAND_INSTR(0x3c, 0x00), + ILI9881C_COMMAND_INSTR(0x3d, 0x00), + ILI9881C_COMMAND_INSTR(0x3e, 0x00), + ILI9881C_COMMAND_INSTR(0x3f, 0x00), + ILI9881C_COMMAND_INSTR(0x40, 0x00), + ILI9881C_COMMAND_INSTR(0x41, 0x00), + ILI9881C_COMMAND_INSTR(0x42, 0x00), + ILI9881C_COMMAND_INSTR(0x43, 0x00), + ILI9881C_COMMAND_INSTR(0x44, 0x00), + ILI9881C_COMMAND_INSTR(0x50, 0x01), + ILI9881C_COMMAND_INSTR(0x51, 0x23), + ILI9881C_COMMAND_INSTR(0x52, 0x45), + ILI9881C_COMMAND_INSTR(0x53, 0x67), + ILI9881C_COMMAND_INSTR(0x54, 0x89), + ILI9881C_COMMAND_INSTR(0x55, 0xab), + ILI9881C_COMMAND_INSTR(0x56, 0x01), + ILI9881C_COMMAND_INSTR(0x57, 0x23), + ILI9881C_COMMAND_INSTR(0x58, 0x45), + ILI9881C_COMMAND_INSTR(0x59, 0x67), + ILI9881C_COMMAND_INSTR(0x5a, 0x89), + ILI9881C_COMMAND_INSTR(0x5b, 0xab), + ILI9881C_COMMAND_INSTR(0x5c, 0xcd), + ILI9881C_COMMAND_INSTR(0x5d, 0xef), + ILI9881C_COMMAND_INSTR(0x5e, 0x10), + ILI9881C_COMMAND_INSTR(0x5f, 0x09), + ILI9881C_COMMAND_INSTR(0x60, 0x08), + ILI9881C_COMMAND_INSTR(0x61, 0x0f), + ILI9881C_COMMAND_INSTR(0x62, 0x0e), + ILI9881C_COMMAND_INSTR(0x63, 0x0d), + ILI9881C_COMMAND_INSTR(0x64, 0x0c), + ILI9881C_COMMAND_INSTR(0x65, 0x02), + ILI9881C_COMMAND_INSTR(0x66, 0x02), + ILI9881C_COMMAND_INSTR(0x67, 0x02), + ILI9881C_COMMAND_INSTR(0x68, 0x02), + ILI9881C_COMMAND_INSTR(0x69, 0x02), + ILI9881C_COMMAND_INSTR(0x6a, 0x02), + ILI9881C_COMMAND_INSTR(0x6b, 0x02), + ILI9881C_COMMAND_INSTR(0x6c, 0x02), + ILI9881C_COMMAND_INSTR(0x6d, 0x02), + ILI9881C_COMMAND_INSTR(0x6e, 0x02), + ILI9881C_COMMAND_INSTR(0x6f, 0x02), + ILI9881C_COMMAND_INSTR(0x70, 0x02), + ILI9881C_COMMAND_INSTR(0x71, 0x06), + ILI9881C_COMMAND_INSTR(0x72, 0x07), + ILI9881C_COMMAND_INSTR(0x73, 0x02), + ILI9881C_COMMAND_INSTR(0x74, 0x02), + ILI9881C_COMMAND_INSTR(0x75, 0x06), + ILI9881C_COMMAND_INSTR(0x76, 0x07), + ILI9881C_COMMAND_INSTR(0x77, 0x0e), + ILI9881C_COMMAND_INSTR(0x78, 0x0f), + ILI9881C_COMMAND_INSTR(0x79, 0x0c), + ILI9881C_COMMAND_INSTR(0x7a, 0x0d), + ILI9881C_COMMAND_INSTR(0x7b, 0x02), + ILI9881C_COMMAND_INSTR(0x7c, 0x02), + ILI9881C_COMMAND_INSTR(0x7d, 0x02), + ILI9881C_COMMAND_INSTR(0x7e, 0x02), + ILI9881C_COMMAND_INSTR(0x7f, 0x02), + ILI9881C_COMMAND_INSTR(0x80, 0x02), + ILI9881C_COMMAND_INSTR(0x81, 0x02), + ILI9881C_COMMAND_INSTR(0x82, 0x02), + ILI9881C_COMMAND_INSTR(0x83, 0x02), + ILI9881C_COMMAND_INSTR(0x84, 0x02), + ILI9881C_COMMAND_INSTR(0x85, 0x02), + ILI9881C_COMMAND_INSTR(0x86, 0x02), + ILI9881C_COMMAND_INSTR(0x87, 0x09), + ILI9881C_COMMAND_INSTR(0x88, 0x08), + ILI9881C_COMMAND_INSTR(0x89, 0x02), + ILI9881C_COMMAND_INSTR(0x8a, 0x02), + ILI9881C_SWITCH_PAGE_INSTR(4), + ILI9881C_COMMAND_INSTR(0x6c, 0x15), + ILI9881C_COMMAND_INSTR(0x6e, 0x2a), + ILI9881C_COMMAND_INSTR(0x6f, 0x57), + ILI9881C_COMMAND_INSTR(0x3a, 0xa4), + ILI9881C_COMMAND_INSTR(0x8d, 0x1a), + ILI9881C_COMMAND_INSTR(0x87, 0xba), + ILI9881C_COMMAND_INSTR(0x26, 0x76), + ILI9881C_COMMAND_INSTR(0xb2, 0xd1), + ILI9881C_SWITCH_PAGE_INSTR(1), + ILI9881C_COMMAND_INSTR(0x22, 0x0a), + ILI9881C_COMMAND_INSTR(0x31, 0x00), + ILI9881C_COMMAND_INSTR(0x53, 0x35), + ILI9881C_COMMAND_INSTR(0x55, 0x50), + ILI9881C_COMMAND_INSTR(0x50, 0xaf), + ILI9881C_COMMAND_INSTR(0x51, 0xaf), + ILI9881C_COMMAND_INSTR(0x60, 0x14), + ILI9881C_COMMAND_INSTR(0xa0, 0x08), + ILI9881C_COMMAND_INSTR(0xa1, 0x1d), + ILI9881C_COMMAND_INSTR(0xa2, 0x2c), + ILI9881C_COMMAND_INSTR(0xa3, 0x14), + ILI9881C_COMMAND_INSTR(0xa4, 0x19), + ILI9881C_COMMAND_INSTR(0xa5, 0x2e), + ILI9881C_COMMAND_INSTR(0xa6, 0x22), + ILI9881C_COMMAND_INSTR(0xa7, 0x23), + ILI9881C_COMMAND_INSTR(0xa8, 0x97), + ILI9881C_COMMAND_INSTR(0xa9, 0x1e), + ILI9881C_COMMAND_INSTR(0xaa, 0x29), + ILI9881C_COMMAND_INSTR(0xab, 0x7b), + ILI9881C_COMMAND_INSTR(0xac, 0x18), + ILI9881C_COMMAND_INSTR(0xad, 0x17), + ILI9881C_COMMAND_INSTR(0xae, 0x4b), + ILI9881C_COMMAND_INSTR(0xaf, 0x1f), + ILI9881C_COMMAND_INSTR(0xb0, 0x27), + ILI9881C_COMMAND_INSTR(0xb1, 0x52), + ILI9881C_COMMAND_INSTR(0xb2, 0x63), + ILI9881C_COMMAND_INSTR(0xb3, 0x39), + ILI9881C_COMMAND_INSTR(0xc0, 0x08), + ILI9881C_COMMAND_INSTR(0xc1, 0x1d), + ILI9881C_COMMAND_INSTR(0xc2, 0x2c), + ILI9881C_COMMAND_INSTR(0xc3, 0x14), + ILI9881C_COMMAND_INSTR(0xc4, 0x19), + ILI9881C_COMMAND_INSTR(0xc5, 0x2e), + ILI9881C_COMMAND_INSTR(0xc6, 0x22), + ILI9881C_COMMAND_INSTR(0xc7, 0x23), + ILI9881C_COMMAND_INSTR(0xc8, 0x97), + ILI9881C_COMMAND_INSTR(0xc9, 0x1e), + ILI9881C_COMMAND_INSTR(0xca, 0x29), + ILI9881C_COMMAND_INSTR(0xcb, 0x7b), + ILI9881C_COMMAND_INSTR(0xcc, 0x18), + ILI9881C_COMMAND_INSTR(0xcd, 0x17), + ILI9881C_COMMAND_INSTR(0xce, 0x4b), + ILI9881C_COMMAND_INSTR(0xcf, 0x1f), + ILI9881C_COMMAND_INSTR(0xd0, 0x27), + ILI9881C_COMMAND_INSTR(0xd1, 0x52), + ILI9881C_COMMAND_INSTR(0xd2, 0x63), + ILI9881C_COMMAND_INSTR(0xd3, 0x39), +}; + +static const struct ili9881c_instr rpi_7inch_init[] = { + ILI9881C_SWITCH_PAGE_INSTR(3), + ILI9881C_COMMAND_INSTR(0x01, 0x00), + ILI9881C_COMMAND_INSTR(0x02, 0x00), + ILI9881C_COMMAND_INSTR(0x03, 0x73), + ILI9881C_COMMAND_INSTR(0x04, 0x00), + ILI9881C_COMMAND_INSTR(0x05, 0x00), + ILI9881C_COMMAND_INSTR(0x06, 0x0a), + ILI9881C_COMMAND_INSTR(0x07, 0x00), + ILI9881C_COMMAND_INSTR(0x08, 0x00), + ILI9881C_COMMAND_INSTR(0x09, 0x61), + ILI9881C_COMMAND_INSTR(0x0a, 0x00), + ILI9881C_COMMAND_INSTR(0x0b, 0x00), + ILI9881C_COMMAND_INSTR(0x0c, 0x01), + ILI9881C_COMMAND_INSTR(0x0d, 0x00), + ILI9881C_COMMAND_INSTR(0x0e, 0x00), + ILI9881C_COMMAND_INSTR(0x0f, 0x61), + ILI9881C_COMMAND_INSTR(0x10, 0x61), + ILI9881C_COMMAND_INSTR(0x11, 0x00), + ILI9881C_COMMAND_INSTR(0x12, 0x00), + ILI9881C_COMMAND_INSTR(0x13, 0x00), + ILI9881C_COMMAND_INSTR(0x14, 0x00), + ILI9881C_COMMAND_INSTR(0x15, 0x00), + ILI9881C_COMMAND_INSTR(0x16, 0x00), + ILI9881C_COMMAND_INSTR(0x17, 0x00), + ILI9881C_COMMAND_INSTR(0x18, 0x00), + ILI9881C_COMMAND_INSTR(0x19, 0x00), + ILI9881C_COMMAND_INSTR(0x1a, 0x00), + ILI9881C_COMMAND_INSTR(0x1b, 0x00), + ILI9881C_COMMAND_INSTR(0x1c, 0x00), + ILI9881C_COMMAND_INSTR(0x1d, 0x00), + ILI9881C_COMMAND_INSTR(0x1e, 0x40), + ILI9881C_COMMAND_INSTR(0x1f, 0x80), + ILI9881C_COMMAND_INSTR(0x20, 0x06), + ILI9881C_COMMAND_INSTR(0x21, 0x01), + ILI9881C_COMMAND_INSTR(0x22, 0x00), + ILI9881C_COMMAND_INSTR(0x23, 0x00), + ILI9881C_COMMAND_INSTR(0x24, 0x00), + ILI9881C_COMMAND_INSTR(0x25, 0x00), + ILI9881C_COMMAND_INSTR(0x26, 0x00), + ILI9881C_COMMAND_INSTR(0x27, 0x00), + ILI9881C_COMMAND_INSTR(0x28, 0x33), + ILI9881C_COMMAND_INSTR(0x29, 0x03), + ILI9881C_COMMAND_INSTR(0x2a, 0x00), + ILI9881C_COMMAND_INSTR(0x2b, 0x00), + ILI9881C_COMMAND_INSTR(0x2c, 0x00), + ILI9881C_COMMAND_INSTR(0x2d, 0x00), + ILI9881C_COMMAND_INSTR(0x2e, 0x00), + ILI9881C_COMMAND_INSTR(0x2f, 0x00), + ILI9881C_COMMAND_INSTR(0x30, 0x00), + ILI9881C_COMMAND_INSTR(0x31, 0x00), + ILI9881C_COMMAND_INSTR(0x32, 0x00), + ILI9881C_COMMAND_INSTR(0x33, 0x00), + ILI9881C_COMMAND_INSTR(0x34, 0x04), + ILI9881C_COMMAND_INSTR(0x35, 0x00), + ILI9881C_COMMAND_INSTR(0x36, 0x00), + ILI9881C_COMMAND_INSTR(0x37, 0x00), + ILI9881C_COMMAND_INSTR(0x38, 0x3c), + ILI9881C_COMMAND_INSTR(0x39, 0x00), + ILI9881C_COMMAND_INSTR(0x3a, 0x00), + ILI9881C_COMMAND_INSTR(0x3b, 0x00), + ILI9881C_COMMAND_INSTR(0x3c, 0x00), + ILI9881C_COMMAND_INSTR(0x3d, 0x00), + ILI9881C_COMMAND_INSTR(0x3e, 0x00), + ILI9881C_COMMAND_INSTR(0x3f, 0x00), + ILI9881C_COMMAND_INSTR(0x40, 0x00), + ILI9881C_COMMAND_INSTR(0x41, 0x00), + ILI9881C_COMMAND_INSTR(0x42, 0x00), + ILI9881C_COMMAND_INSTR(0x43, 0x00), + ILI9881C_COMMAND_INSTR(0x44, 0x00), + ILI9881C_COMMAND_INSTR(0x50, 0x10), + ILI9881C_COMMAND_INSTR(0x51, 0x32), + ILI9881C_COMMAND_INSTR(0x52, 0x54), + ILI9881C_COMMAND_INSTR(0x53, 0x76), + ILI9881C_COMMAND_INSTR(0x54, 0x98), + ILI9881C_COMMAND_INSTR(0x55, 0xba), + ILI9881C_COMMAND_INSTR(0x56, 0x10), + ILI9881C_COMMAND_INSTR(0x57, 0x32), + ILI9881C_COMMAND_INSTR(0x58, 0x54), + ILI9881C_COMMAND_INSTR(0x59, 0x76), + ILI9881C_COMMAND_INSTR(0x5a, 0x98), + ILI9881C_COMMAND_INSTR(0x5b, 0xba), + ILI9881C_COMMAND_INSTR(0x5c, 0xdc), + ILI9881C_COMMAND_INSTR(0x5d, 0xfe), + ILI9881C_COMMAND_INSTR(0x5e, 0x00), + ILI9881C_COMMAND_INSTR(0x5f, 0x0e), + ILI9881C_COMMAND_INSTR(0x60, 0x0f), + ILI9881C_COMMAND_INSTR(0x61, 0x0c), + ILI9881C_COMMAND_INSTR(0x62, 0x0d), + ILI9881C_COMMAND_INSTR(0x63, 0x06), + ILI9881C_COMMAND_INSTR(0x64, 0x07), + ILI9881C_COMMAND_INSTR(0x65, 0x02), + ILI9881C_COMMAND_INSTR(0x66, 0x02), + ILI9881C_COMMAND_INSTR(0x67, 0x02), + ILI9881C_COMMAND_INSTR(0x68, 0x02), + ILI9881C_COMMAND_INSTR(0x69, 0x01), + ILI9881C_COMMAND_INSTR(0x6a, 0x00), + ILI9881C_COMMAND_INSTR(0x6b, 0x02), + ILI9881C_COMMAND_INSTR(0x6c, 0x15), + ILI9881C_COMMAND_INSTR(0x6d, 0x14), + ILI9881C_COMMAND_INSTR(0x6e, 0x02), + ILI9881C_COMMAND_INSTR(0x6f, 0x02), + ILI9881C_COMMAND_INSTR(0x70, 0x02), + ILI9881C_COMMAND_INSTR(0x71, 0x02), + ILI9881C_COMMAND_INSTR(0x72, 0x02), + ILI9881C_COMMAND_INSTR(0x73, 0x02), + ILI9881C_COMMAND_INSTR(0x74, 0x02), + ILI9881C_COMMAND_INSTR(0x75, 0x0e), + ILI9881C_COMMAND_INSTR(0x76, 0x0f), + ILI9881C_COMMAND_INSTR(0x77, 0x0c), + ILI9881C_COMMAND_INSTR(0x78, 0x0d), + ILI9881C_COMMAND_INSTR(0x79, 0x06), + ILI9881C_COMMAND_INSTR(0x7a, 0x07), + ILI9881C_COMMAND_INSTR(0x7b, 0x02), + ILI9881C_COMMAND_INSTR(0x7c, 0x02), + ILI9881C_COMMAND_INSTR(0x7d, 0x02), + ILI9881C_COMMAND_INSTR(0x7e, 0x02), + ILI9881C_COMMAND_INSTR(0x7f, 0x01), + ILI9881C_COMMAND_INSTR(0x80, 0x00), + ILI9881C_COMMAND_INSTR(0x81, 0x02), + ILI9881C_COMMAND_INSTR(0x82, 0x14), + ILI9881C_COMMAND_INSTR(0x83, 0x15), + ILI9881C_COMMAND_INSTR(0x84, 0x02), + ILI9881C_COMMAND_INSTR(0x85, 0x02), + ILI9881C_COMMAND_INSTR(0x86, 0x02), + ILI9881C_COMMAND_INSTR(0x87, 0x02), + ILI9881C_COMMAND_INSTR(0x88, 0x02), + ILI9881C_COMMAND_INSTR(0x89, 0x02), + ILI9881C_COMMAND_INSTR(0x8a, 0x02), + ILI9881C_SWITCH_PAGE_INSTR(4), + ILI9881C_COMMAND_INSTR(0x6c, 0x15), + ILI9881C_COMMAND_INSTR(0x6e, 0x2a), + ILI9881C_COMMAND_INSTR(0x6f, 0x33), + ILI9881C_COMMAND_INSTR(0x3b, 0x98), + ILI9881C_COMMAND_INSTR(0x3a, 0x94), + ILI9881C_COMMAND_INSTR(0x8d, 0x14), + ILI9881C_COMMAND_INSTR(0x87, 0xba), + ILI9881C_COMMAND_INSTR(0x26, 0x76), + ILI9881C_COMMAND_INSTR(0xb2, 0xd1), + ILI9881C_COMMAND_INSTR(0xb5, 0x06), + ILI9881C_COMMAND_INSTR(0x38, 0x01), + ILI9881C_COMMAND_INSTR(0x39, 0x00), + ILI9881C_SWITCH_PAGE_INSTR(1), + ILI9881C_COMMAND_INSTR(0x22, 0x0a), + ILI9881C_COMMAND_INSTR(0x31, 0x00), + ILI9881C_COMMAND_INSTR(0x53, 0x7d), + ILI9881C_COMMAND_INSTR(0x55, 0x8f), + ILI9881C_COMMAND_INSTR(0x40, 0x33), + ILI9881C_COMMAND_INSTR(0x50, 0x96), + ILI9881C_COMMAND_INSTR(0x51, 0x96), + ILI9881C_COMMAND_INSTR(0x60, 0x23), + ILI9881C_COMMAND_INSTR(0xa0, 0x08), + ILI9881C_COMMAND_INSTR(0xa1, 0x1d), + ILI9881C_COMMAND_INSTR(0xa2, 0x2a), + ILI9881C_COMMAND_INSTR(0xa3, 0x10), + ILI9881C_COMMAND_INSTR(0xa4, 0x15), + ILI9881C_COMMAND_INSTR(0xa5, 0x28), + ILI9881C_COMMAND_INSTR(0xa6, 0x1c), + ILI9881C_COMMAND_INSTR(0xa7, 0x1d), + ILI9881C_COMMAND_INSTR(0xa8, 0x7e), + ILI9881C_COMMAND_INSTR(0xa9, 0x1d), + ILI9881C_COMMAND_INSTR(0xaa, 0x29), + ILI9881C_COMMAND_INSTR(0xab, 0x6b), + ILI9881C_COMMAND_INSTR(0xac, 0x1a), + ILI9881C_COMMAND_INSTR(0xad, 0x18), + ILI9881C_COMMAND_INSTR(0xae, 0x4b), + ILI9881C_COMMAND_INSTR(0xaf, 0x20), + ILI9881C_COMMAND_INSTR(0xb0, 0x27), + ILI9881C_COMMAND_INSTR(0xb1, 0x50), + ILI9881C_COMMAND_INSTR(0xb2, 0x64), + ILI9881C_COMMAND_INSTR(0xb3, 0x39), + ILI9881C_COMMAND_INSTR(0xc0, 0x08), + ILI9881C_COMMAND_INSTR(0xc1, 0x1d), + ILI9881C_COMMAND_INSTR(0xc2, 0x2a), + ILI9881C_COMMAND_INSTR(0xc3, 0x10), + ILI9881C_COMMAND_INSTR(0xc4, 0x15), + ILI9881C_COMMAND_INSTR(0xc5, 0x28), + ILI9881C_COMMAND_INSTR(0xc6, 0x1c), + ILI9881C_COMMAND_INSTR(0xc7, 0x1d), + ILI9881C_COMMAND_INSTR(0xc8, 0x7e), + ILI9881C_COMMAND_INSTR(0xc9, 0x1d), + ILI9881C_COMMAND_INSTR(0xca, 0x29), + ILI9881C_COMMAND_INSTR(0xcb, 0x6b), + ILI9881C_COMMAND_INSTR(0xcc, 0x1a), + ILI9881C_COMMAND_INSTR(0xcd, 0x18), + ILI9881C_COMMAND_INSTR(0xce, 0x4b), + ILI9881C_COMMAND_INSTR(0xcf, 0x20), + ILI9881C_COMMAND_INSTR(0xd0, 0x27), + ILI9881C_COMMAND_INSTR(0xd1, 0x50), + ILI9881C_COMMAND_INSTR(0xd2, 0x64), + ILI9881C_COMMAND_INSTR(0xd3, 0x39), +}; + +static const struct ili9881c_instr bsd1218_a101kl68_init[] = { + ILI9881C_SWITCH_PAGE_INSTR(3), + ILI9881C_COMMAND_INSTR(0x01, 0x00), + ILI9881C_COMMAND_INSTR(0x02, 0x00), + ILI9881C_COMMAND_INSTR(0x03, 0x55), + ILI9881C_COMMAND_INSTR(0x04, 0x55), + ILI9881C_COMMAND_INSTR(0x05, 0x03), + ILI9881C_COMMAND_INSTR(0x06, 0x06), + ILI9881C_COMMAND_INSTR(0x07, 0x00), + ILI9881C_COMMAND_INSTR(0x08, 0x07), + ILI9881C_COMMAND_INSTR(0x09, 0x00), + ILI9881C_COMMAND_INSTR(0x0a, 0x00), + ILI9881C_COMMAND_INSTR(0x0b, 0x00), + ILI9881C_COMMAND_INSTR(0x0c, 0x00), + ILI9881C_COMMAND_INSTR(0x0d, 0x00), + ILI9881C_COMMAND_INSTR(0x0e, 0x00), + ILI9881C_COMMAND_INSTR(0x0f, 0x00), + ILI9881C_COMMAND_INSTR(0x10, 0x00), + ILI9881C_COMMAND_INSTR(0x11, 0x00), + ILI9881C_COMMAND_INSTR(0x12, 0x00), + ILI9881C_COMMAND_INSTR(0x13, 0x00), + ILI9881C_COMMAND_INSTR(0x14, 0x00), + ILI9881C_COMMAND_INSTR(0x15, 0x00), + ILI9881C_COMMAND_INSTR(0x16, 0x00), + ILI9881C_COMMAND_INSTR(0x17, 0x00), + ILI9881C_COMMAND_INSTR(0x18, 0x00), + ILI9881C_COMMAND_INSTR(0x19, 0x00), + ILI9881C_COMMAND_INSTR(0x1a, 0x00), + ILI9881C_COMMAND_INSTR(0x1b, 0x00), + ILI9881C_COMMAND_INSTR(0x1c, 0x00), + ILI9881C_COMMAND_INSTR(0x1d, 0x00), + ILI9881C_COMMAND_INSTR(0x1e, 0xc0), + ILI9881C_COMMAND_INSTR(0x1f, 0x80), + ILI9881C_COMMAND_INSTR(0x20, 0x04), + ILI9881C_COMMAND_INSTR(0x21, 0x03), + ILI9881C_COMMAND_INSTR(0x22, 0x00), + ILI9881C_COMMAND_INSTR(0x23, 0x00), + ILI9881C_COMMAND_INSTR(0x24, 0x00), + ILI9881C_COMMAND_INSTR(0x25, 0x00), + ILI9881C_COMMAND_INSTR(0x26, 0x00), + ILI9881C_COMMAND_INSTR(0x27, 0x00), + ILI9881C_COMMAND_INSTR(0x28, 0x33), + ILI9881C_COMMAND_INSTR(0x29, 0x33), + ILI9881C_COMMAND_INSTR(0x2a, 0x00), + ILI9881C_COMMAND_INSTR(0x2b, 0x00), + ILI9881C_COMMAND_INSTR(0x2c, 0x00), + ILI9881C_COMMAND_INSTR(0x2d, 0x00), + ILI9881C_COMMAND_INSTR(0x2e, 0x00), + ILI9881C_COMMAND_INSTR(0x2f, 0x00), + ILI9881C_COMMAND_INSTR(0x30, 0x00), + ILI9881C_COMMAND_INSTR(0x31, 0x00), + ILI9881C_COMMAND_INSTR(0x32, 0x00), + ILI9881C_COMMAND_INSTR(0x33, 0x00), + ILI9881C_COMMAND_INSTR(0x34, 0x04), + ILI9881C_COMMAND_INSTR(0x35, 0x00), + ILI9881C_COMMAND_INSTR(0x36, 0x00), + ILI9881C_COMMAND_INSTR(0x37, 0x00), + ILI9881C_COMMAND_INSTR(0x38, 0x3c), + ILI9881C_COMMAND_INSTR(0x39, 0x00), + ILI9881C_COMMAND_INSTR(0x3a, 0x00), + ILI9881C_COMMAND_INSTR(0x3b, 0x00), + ILI9881C_COMMAND_INSTR(0x3c, 0x00), + ILI9881C_COMMAND_INSTR(0x3d, 0x00), + ILI9881C_COMMAND_INSTR(0x3e, 0x00), + ILI9881C_COMMAND_INSTR(0x3f, 0x00), + ILI9881C_COMMAND_INSTR(0x40, 0x00), + ILI9881C_COMMAND_INSTR(0x41, 0x00), + ILI9881C_COMMAND_INSTR(0x42, 0x00), + ILI9881C_COMMAND_INSTR(0x43, 0x00), + ILI9881C_COMMAND_INSTR(0x44, 0x00), + ILI9881C_COMMAND_INSTR(0x50, 0x00), + ILI9881C_COMMAND_INSTR(0x51, 0x11), + ILI9881C_COMMAND_INSTR(0x52, 0x44), + ILI9881C_COMMAND_INSTR(0x53, 0x55), + ILI9881C_COMMAND_INSTR(0x54, 0x88), + ILI9881C_COMMAND_INSTR(0x55, 0xab), + ILI9881C_COMMAND_INSTR(0x56, 0x00), + ILI9881C_COMMAND_INSTR(0x57, 0x11), + ILI9881C_COMMAND_INSTR(0x58, 0x22), + ILI9881C_COMMAND_INSTR(0x59, 0x33), + ILI9881C_COMMAND_INSTR(0x5a, 0x44), + ILI9881C_COMMAND_INSTR(0x5b, 0x55), + ILI9881C_COMMAND_INSTR(0x5c, 0x66), + ILI9881C_COMMAND_INSTR(0x5d, 0x77), + ILI9881C_COMMAND_INSTR(0x5e, 0x00), + ILI9881C_COMMAND_INSTR(0x5f, 0x02), + ILI9881C_COMMAND_INSTR(0x60, 0x02), + ILI9881C_COMMAND_INSTR(0x61, 0x0a), + ILI9881C_COMMAND_INSTR(0x62, 0x09), + ILI9881C_COMMAND_INSTR(0x63, 0x08), + ILI9881C_COMMAND_INSTR(0x64, 0x13), + ILI9881C_COMMAND_INSTR(0x65, 0x12), + ILI9881C_COMMAND_INSTR(0x66, 0x11), + ILI9881C_COMMAND_INSTR(0x67, 0x10), + ILI9881C_COMMAND_INSTR(0x68, 0x0f), + ILI9881C_COMMAND_INSTR(0x69, 0x0e), + ILI9881C_COMMAND_INSTR(0x6a, 0x0d), + ILI9881C_COMMAND_INSTR(0x6b, 0x0c), + ILI9881C_COMMAND_INSTR(0x6c, 0x06), + ILI9881C_COMMAND_INSTR(0x6d, 0x07), + ILI9881C_COMMAND_INSTR(0x6e, 0x02), + ILI9881C_COMMAND_INSTR(0x6f, 0x02), + ILI9881C_COMMAND_INSTR(0x70, 0x02), + ILI9881C_COMMAND_INSTR(0x71, 0x02), + ILI9881C_COMMAND_INSTR(0x72, 0x02), + ILI9881C_COMMAND_INSTR(0x73, 0x02), + ILI9881C_COMMAND_INSTR(0x74, 0x02), + ILI9881C_COMMAND_INSTR(0x75, 0x02), + ILI9881C_COMMAND_INSTR(0x76, 0x02), + ILI9881C_COMMAND_INSTR(0x77, 0x0a), + ILI9881C_COMMAND_INSTR(0x78, 0x06), + ILI9881C_COMMAND_INSTR(0x79, 0x07), + ILI9881C_COMMAND_INSTR(0x7a, 0x10), + ILI9881C_COMMAND_INSTR(0x7b, 0x11), + ILI9881C_COMMAND_INSTR(0x7c, 0x12), + ILI9881C_COMMAND_INSTR(0x7d, 0x13), + ILI9881C_COMMAND_INSTR(0x7e, 0x0c), + ILI9881C_COMMAND_INSTR(0x7f, 0x0d), + ILI9881C_COMMAND_INSTR(0x80, 0x0e), + ILI9881C_COMMAND_INSTR(0x81, 0x0f), + ILI9881C_COMMAND_INSTR(0x82, 0x09), + ILI9881C_COMMAND_INSTR(0x83, 0x08), + ILI9881C_COMMAND_INSTR(0x84, 0x02), + ILI9881C_COMMAND_INSTR(0x85, 0x02), + ILI9881C_COMMAND_INSTR(0x86, 0x02), + ILI9881C_COMMAND_INSTR(0x87, 0x02), + ILI9881C_COMMAND_INSTR(0x88, 0x02), + ILI9881C_COMMAND_INSTR(0x89, 0x02), + ILI9881C_COMMAND_INSTR(0x8a, 0x02), + + ILI9881C_SWITCH_PAGE_INSTR(4), + ILI9881C_COMMAND_INSTR(0x6e, 0x2a), + ILI9881C_COMMAND_INSTR(0x6f, 0x37), + ILI9881C_COMMAND_INSTR(0x3a, 0x24), + ILI9881C_COMMAND_INSTR(0x8d, 0x19), + ILI9881C_COMMAND_INSTR(0x87, 0xba), + ILI9881C_COMMAND_INSTR(0xb2, 0xd1), + ILI9881C_COMMAND_INSTR(0x88, 0x0b), + ILI9881C_COMMAND_INSTR(0x38, 0x01), + ILI9881C_COMMAND_INSTR(0x39, 0x00), + ILI9881C_COMMAND_INSTR(0xb5, 0x02), + ILI9881C_COMMAND_INSTR(0x31, 0x25), + ILI9881C_COMMAND_INSTR(0x3b, 0x98), + + ILI9881C_SWITCH_PAGE_INSTR(1), + ILI9881C_COMMAND_INSTR(0x22, 0x0a), + ILI9881C_COMMAND_INSTR(0x31, 0x0c), + ILI9881C_COMMAND_INSTR(0x53, 0x40), + ILI9881C_COMMAND_INSTR(0x55, 0x45), + ILI9881C_COMMAND_INSTR(0x50, 0xb7), + ILI9881C_COMMAND_INSTR(0x51, 0xb2), + ILI9881C_COMMAND_INSTR(0x60, 0x07), + ILI9881C_COMMAND_INSTR(0xa0, 0x22), + ILI9881C_COMMAND_INSTR(0xa1, 0x3f), + ILI9881C_COMMAND_INSTR(0xa2, 0x4e), + ILI9881C_COMMAND_INSTR(0xa3, 0x17), + ILI9881C_COMMAND_INSTR(0xa4, 0x1a), + ILI9881C_COMMAND_INSTR(0xa5, 0x2d), + ILI9881C_COMMAND_INSTR(0xa6, 0x21), + ILI9881C_COMMAND_INSTR(0xa7, 0x22), + ILI9881C_COMMAND_INSTR(0xa8, 0xc4), + ILI9881C_COMMAND_INSTR(0xa9, 0x1b), + ILI9881C_COMMAND_INSTR(0xaa, 0x25), + ILI9881C_COMMAND_INSTR(0xab, 0xa7), + ILI9881C_COMMAND_INSTR(0xac, 0x1a), + ILI9881C_COMMAND_INSTR(0xad, 0x19), + ILI9881C_COMMAND_INSTR(0xae, 0x4b), + ILI9881C_COMMAND_INSTR(0xaf, 0x1f), + ILI9881C_COMMAND_INSTR(0xb0, 0x2a), + ILI9881C_COMMAND_INSTR(0xb1, 0x59), + ILI9881C_COMMAND_INSTR(0xb2, 0x64), + ILI9881C_COMMAND_INSTR(0xb3, 0x3f), + ILI9881C_COMMAND_INSTR(0xc0, 0x22), + ILI9881C_COMMAND_INSTR(0xc1, 0x48), + ILI9881C_COMMAND_INSTR(0xc2, 0x59), + ILI9881C_COMMAND_INSTR(0xc3, 0x15), + ILI9881C_COMMAND_INSTR(0xc4, 0x15), + ILI9881C_COMMAND_INSTR(0xc5, 0x28), + ILI9881C_COMMAND_INSTR(0xc6, 0x1c), + ILI9881C_COMMAND_INSTR(0xc7, 0x1e), + ILI9881C_COMMAND_INSTR(0xc8, 0xc4), + ILI9881C_COMMAND_INSTR(0xc9, 0x1c), + ILI9881C_COMMAND_INSTR(0xca, 0x2b), + ILI9881C_COMMAND_INSTR(0xcb, 0xa3), + ILI9881C_COMMAND_INSTR(0xcc, 0x1f), + ILI9881C_COMMAND_INSTR(0xcd, 0x1e), + ILI9881C_COMMAND_INSTR(0xce, 0x52), + ILI9881C_COMMAND_INSTR(0xcf, 0x24), + ILI9881C_COMMAND_INSTR(0xd0, 0x2a), + ILI9881C_COMMAND_INSTR(0xd1, 0x58), + ILI9881C_COMMAND_INSTR(0xd2, 0x68), + ILI9881C_COMMAND_INSTR(0xd3, 0x3f), }; static inline struct ili9881c *panel_to_ili9881c(struct drm_panel *panel) @@ -270,33 +2013,24 @@ static inline struct ili9881c *panel_to_ili9881c(struct drm_panel *panel) * So before any attempt at sending a command or data, we have to be * sure if we're in the right page or not. */ -static int ili9881c_switch_page(struct ili9881c *ctx, u8 page) +static void ili9881c_switch_page(struct mipi_dsi_multi_context *mctx, u8 page) { u8 buf[4] = { 0xff, 0x98, 0x81, page }; - int ret; - - ret = mipi_dsi_dcs_write_buffer(ctx->dsi, buf, sizeof(buf)); - if (ret < 0) - return ret; - return 0; + mipi_dsi_dcs_write_buffer_multi(mctx, buf, sizeof(buf)); } -static int ili9881c_send_cmd_data(struct ili9881c *ctx, u8 cmd, u8 data) +static void ili9881c_send_cmd_data(struct mipi_dsi_multi_context *mctx, u8 cmd, u8 data) { u8 buf[2] = { cmd, data }; - int ret; - - ret = mipi_dsi_dcs_write_buffer(ctx->dsi, buf, sizeof(buf)); - if (ret < 0) - return ret; - return 0; + mipi_dsi_dcs_write_buffer_multi(mctx, buf, sizeof(buf)); } static int ili9881c_prepare(struct drm_panel *panel) { struct ili9881c *ctx = panel_to_ili9881c(panel); + struct mipi_dsi_multi_context mctx = { .dsi = ctx->dsi }; unsigned int i; int ret; @@ -307,74 +2041,91 @@ static int ili9881c_prepare(struct drm_panel *panel) msleep(5); /* And reset it */ - gpiod_set_value(ctx->reset, 1); + gpiod_set_value_cansleep(ctx->reset, 1); msleep(20); - gpiod_set_value(ctx->reset, 0); + gpiod_set_value_cansleep(ctx->reset, 0); msleep(20); - for (i = 0; i < ARRAY_SIZE(ili9881c_init); i++) { - const struct ili9881c_instr *instr = &ili9881c_init[i]; + for (i = 0; i < ctx->desc->init_length; i++) { + const struct ili9881c_instr *instr = &ctx->desc->init[i]; if (instr->op == ILI9881C_SWITCH_PAGE) - ret = ili9881c_switch_page(ctx, instr->arg.page); + ili9881c_switch_page(&mctx, instr->arg.page); else if (instr->op == ILI9881C_COMMAND) - ret = ili9881c_send_cmd_data(ctx, instr->arg.cmd.cmd, - instr->arg.cmd.data); - - if (ret) - return ret; + ili9881c_send_cmd_data(&mctx, instr->arg.cmd.cmd, + instr->arg.cmd.data); } - ret = ili9881c_switch_page(ctx, 0); - if (ret) - return ret; + ili9881c_switch_page(&mctx, 0); - ret = mipi_dsi_dcs_set_tear_on(ctx->dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK); - if (ret) - return ret; + if (ctx->address_mode) + ili9881c_send_cmd_data(&mctx, MIPI_DCS_SET_ADDRESS_MODE, + ctx->address_mode); - ret = mipi_dsi_dcs_exit_sleep_mode(ctx->dsi); - if (ret) - return ret; + mipi_dsi_dcs_set_tear_on_multi(&mctx, MIPI_DSI_DCS_TEAR_MODE_VBLANK); + mipi_dsi_dcs_exit_sleep_mode_multi(&mctx); + mipi_dsi_msleep(&mctx, 120); + mipi_dsi_dcs_set_display_on_multi(&mctx); + if (mctx.accum_err) + goto disable_power; return 0; + +disable_power: + regulator_disable(ctx->power); + return mctx.accum_err; } -static int ili9881c_enable(struct drm_panel *panel) +static int ili9881c_unprepare(struct drm_panel *panel) { struct ili9881c *ctx = panel_to_ili9881c(panel); + struct mipi_dsi_multi_context mctx = { .dsi = ctx->dsi }; - msleep(120); - - mipi_dsi_dcs_set_display_on(ctx->dsi); - backlight_enable(ctx->backlight); + mipi_dsi_dcs_set_display_off_multi(&mctx); + mipi_dsi_dcs_enter_sleep_mode_multi(&mctx); + regulator_disable(ctx->power); + gpiod_set_value_cansleep(ctx->reset, 1); return 0; } -static int ili9881c_disable(struct drm_panel *panel) -{ - struct ili9881c *ctx = panel_to_ili9881c(panel); +static const struct drm_display_mode lhr050h41_default_mode = { + .clock = 62000, - backlight_disable(ctx->backlight); - return mipi_dsi_dcs_set_display_off(ctx->dsi); -} + .hdisplay = 720, + .hsync_start = 720 + 10, + .hsync_end = 720 + 10 + 20, + .htotal = 720 + 10 + 20 + 30, -static int ili9881c_unprepare(struct drm_panel *panel) -{ - struct ili9881c *ctx = panel_to_ili9881c(panel); + .vdisplay = 1280, + .vsync_start = 1280 + 10, + .vsync_end = 1280 + 10 + 10, + .vtotal = 1280 + 10 + 10 + 20, - mipi_dsi_dcs_enter_sleep_mode(ctx->dsi); - regulator_disable(ctx->power); - gpiod_set_value(ctx->reset, 1); + .width_mm = 62, + .height_mm = 110, +}; - return 0; -} +static const struct drm_display_mode k101_im2byl02_default_mode = { + .clock = 69700, + + .hdisplay = 800, + .hsync_start = 800 + 52, + .hsync_end = 800 + 52 + 8, + .htotal = 800 + 52 + 8 + 48, -static const struct drm_display_mode bananapi_default_mode = { + .vdisplay = 1280, + .vsync_start = 1280 + 16, + .vsync_end = 1280 + 16 + 6, + .vtotal = 1280 + 16 + 6 + 15, + + .width_mm = 135, + .height_mm = 217, +}; + +static const struct drm_display_mode kd050hdfia020_default_mode = { .clock = 62000, - .vrefresh = 60, .hdisplay = 720, .hsync_start = 720 + 10, @@ -385,20 +2136,142 @@ static const struct drm_display_mode bananapi_default_mode = { .vsync_start = 1280 + 10, .vsync_end = 1280 + 10 + 10, .vtotal = 1280 + 10 + 10 + 20, + + .width_mm = 62, + .height_mm = 110, }; -static int ili9881c_get_modes(struct drm_panel *panel) +static const struct drm_display_mode tl050hdv35_default_mode = { + .clock = 59400, + + .hdisplay = 720, + .hsync_start = 720 + 18, + .hsync_end = 720 + 18 + 3, + .htotal = 720 + 18 + 3 + 20, + + .vdisplay = 1280, + .vsync_start = 1280 + 26, + .vsync_end = 1280 + 26 + 6, + .vtotal = 1280 + 26 + 6 + 28, + + .width_mm = 62, + .height_mm = 110, +}; + +static const struct drm_display_mode w552946aaa_default_mode = { + .clock = 65000, + + .hdisplay = 720, + .hsync_start = 720 + 52, + .hsync_end = 720 + 52 + 8, + .htotal = 720 + 52 + 8 + 48, + + .vdisplay = 1280, + .vsync_start = 1280 + 16, + .vsync_end = 1280 + 16 + 6, + .vtotal = 1280 + 16 + 6 + 15, + + .width_mm = 68, + .height_mm = 121, +}; + +static const struct drm_display_mode w552946aba_default_mode = { + .clock = 64000, + + .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, + + .width_mm = 68, + .height_mm = 121, +}; + +static const struct drm_display_mode am8001280g_default_mode = { + .clock = 67911, + + .hdisplay = 800, + .hsync_start = 800 + 20, + .hsync_end = 800 + 20 + 32, + .htotal = 800 + 20 + 32 + 20, + + .vdisplay = 1280, + .vsync_start = 1280 + 6, + .vsync_end = 1280 + 6 + 8, + .vtotal = 1280 + 6 + 8 + 4, + + .width_mm = 94, + .height_mm = 151, +}; + +static const struct drm_display_mode rpi_5inch_default_mode = { + .clock = 83333, + + .hdisplay = 720, + .hsync_start = 720 + 110, + .hsync_end = 720 + 110 + 12, + .htotal = 720 + 110 + 12 + 95, + + .vdisplay = 1280, + .vsync_start = 1280 + 100, + .vsync_end = 1280 + 100 + 2, + .vtotal = 1280 + 100 + 2 + 100, + + .width_mm = 62, + .height_mm = 110, +}; + +static const struct drm_display_mode rpi_7inch_default_mode = { + .clock = 83330, + + .hdisplay = 720, + .hsync_start = 720 + 239, + .hsync_end = 720 + 239 + 33, + .htotal = 720 + 239 + 33 + 50, + + .vdisplay = 1280, + .vsync_start = 1280 + 20, + .vsync_end = 1280 + 20 + 2, + .vtotal = 1280 + 20 + 2 + 30, + + .width_mm = 90, + .height_mm = 151, +}; + +static const struct drm_display_mode bsd1218_a101kl68_default_mode = { + .clock = 70000, + + .hdisplay = 800, + .hsync_start = 800 + 40, + .hsync_end = 800 + 40 + 20, + .htotal = 800 + 40 + 20 + 20, + + .vdisplay = 1280, + .vsync_start = 1280 + 20, + .vsync_end = 1280 + 20 + 4, + .vtotal = 1280 + 20 + 4 + 20, + + .width_mm = 120, + .height_mm = 170, +}; + +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, ctx->desc->mode); if (!mode) { dev_err(&ctx->dsi->dev, "failed to add mode %ux%ux@%u\n", - bananapi_default_mode.hdisplay, - bananapi_default_mode.vdisplay, - bananapi_default_mode.vrefresh); + ctx->desc->mode->hdisplay, + ctx->desc->mode->vdisplay, + drm_mode_vrefresh(ctx->desc->mode)); return -ENOMEM; } @@ -407,83 +2280,191 @@ 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 = mode->width_mm; + connector->display_info.height_mm = mode->height_mm; + if (ctx->address_mode == 0x3) + connector->display_info.subpixel_order = SubPixelHorizontalBGR; + else + connector->display_info.subpixel_order = SubPixelHorizontalRGB; + + /* + * TODO: Remove once all drm drivers call + * drm_connector_set_orientation_from_panel() + */ + drm_connector_set_panel_orientation(connector, ctx->orientation); return 1; } +static enum drm_panel_orientation ili9881c_get_orientation(struct drm_panel *panel) +{ + struct ili9881c *ctx = panel_to_ili9881c(panel); + + return ctx->orientation; +} + static const struct drm_panel_funcs ili9881c_funcs = { .prepare = ili9881c_prepare, .unprepare = ili9881c_unprepare, - .enable = ili9881c_enable, - .disable = ili9881c_disable, .get_modes = ili9881c_get_modes, + .get_orientation = ili9881c_get_orientation, }; static int ili9881c_dsi_probe(struct mipi_dsi_device *dsi) { - struct device_node *np; struct ili9881c *ctx; int ret; - ctx = devm_kzalloc(&dsi->dev, sizeof(*ctx), GFP_KERNEL); - if (!ctx) - return -ENOMEM; + ctx = devm_drm_panel_alloc(&dsi->dev, struct ili9881c, panel, &ili9881c_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + mipi_dsi_set_drvdata(dsi, ctx); ctx->dsi = dsi; - - drm_panel_init(&ctx->panel); - ctx->panel.dev = &dsi->dev; - ctx->panel.funcs = &ili9881c_funcs; + ctx->desc = of_device_get_match_data(&dsi->dev); ctx->power = devm_regulator_get(&dsi->dev, "power"); - if (IS_ERR(ctx->power)) { - dev_err(&dsi->dev, "Couldn't get our power regulator\n"); - return PTR_ERR(ctx->power); + if (IS_ERR(ctx->power)) + return dev_err_probe(&dsi->dev, PTR_ERR(ctx->power), + "Couldn't get our power regulator\n"); + + ctx->reset = devm_gpiod_get_optional(&dsi->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->reset)) + return dev_err_probe(&dsi->dev, PTR_ERR(ctx->reset), + "Couldn't get our reset GPIO\n"); + + ret = of_drm_get_panel_orientation(dsi->dev.of_node, &ctx->orientation); + if (ret) { + dev_err(&dsi->dev, "%pOF: failed to get orientation: %d\n", + dsi->dev.of_node, ret); + return ret; } - ctx->reset = devm_gpiod_get(&dsi->dev, "reset", GPIOD_OUT_LOW); - if (IS_ERR(ctx->reset)) { - dev_err(&dsi->dev, "Couldn't get our reset GPIO\n"); - return PTR_ERR(ctx->reset); + ctx->address_mode = ctx->desc->default_address_mode; + if (ctx->orientation == DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP) { + ctx->address_mode ^= 0x03; + ctx->orientation = DRM_MODE_PANEL_ORIENTATION_NORMAL; } - 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; - } + ctx->panel.prepare_prev_first = true; - ret = drm_panel_add(&ctx->panel); - if (ret < 0) + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) return ret; - dsi->mode_flags = MIPI_DSI_MODE_VIDEO_SYNC_PULSE; + drm_panel_add(&ctx->panel); + + dsi->mode_flags = ctx->desc->mode_flags; dsi->format = MIPI_DSI_FMT_RGB888; - dsi->lanes = 4; + dsi->lanes = ctx->desc->lanes; return mipi_dsi_attach(dsi); } -static int ili9881c_dsi_remove(struct mipi_dsi_device *dsi) +static void ili9881c_dsi_remove(struct mipi_dsi_device *dsi) { struct ili9881c *ctx = mipi_dsi_get_drvdata(dsi); mipi_dsi_detach(dsi); drm_panel_remove(&ctx->panel); +} - if (ctx->backlight) - put_device(&ctx->backlight->dev); +static const struct ili9881c_desc lhr050h41_desc = { + .init = lhr050h41_init, + .init_length = ARRAY_SIZE(lhr050h41_init), + .mode = &lhr050h41_default_mode, + .mode_flags = MIPI_DSI_MODE_VIDEO_SYNC_PULSE, + .lanes = 4, +}; - return 0; -} +static const struct ili9881c_desc k101_im2byl02_desc = { + .init = k101_im2byl02_init, + .init_length = ARRAY_SIZE(k101_im2byl02_init), + .mode = &k101_im2byl02_default_mode, + .mode_flags = MIPI_DSI_MODE_VIDEO_SYNC_PULSE, + .lanes = 4, +}; + +static const struct ili9881c_desc kd050hdfia020_desc = { + .init = kd050hdfia020_init, + .init_length = ARRAY_SIZE(kd050hdfia020_init), + .mode = &kd050hdfia020_default_mode, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_MODE_LPM, +}; + +static const struct ili9881c_desc tl050hdv35_desc = { + .init = tl050hdv35_init, + .init_length = ARRAY_SIZE(tl050hdv35_init), + .mode = &tl050hdv35_default_mode, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_MODE_LPM, + .default_address_mode = 0x03, +}; + +static const struct ili9881c_desc w552946aaa_desc = { + .init = w552946aaa_init, + .init_length = ARRAY_SIZE(w552946aaa_init), + .mode = &w552946aaa_default_mode, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_NO_EOT_PACKET, + .lanes = 2, +}; + +static const struct ili9881c_desc w552946aba_desc = { + .init = w552946ab_init, + .init_length = ARRAY_SIZE(w552946ab_init), + .mode = &w552946aba_default_mode, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_NO_EOT_PACKET, + .lanes = 4, +}; + +static const struct ili9881c_desc am8001280g_desc = { + .init = am8001280g_init, + .init_length = ARRAY_SIZE(am8001280g_init), + .mode = &am8001280g_default_mode, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_CLOCK_NON_CONTINUOUS | MIPI_DSI_MODE_LPM, +}; + +static const struct ili9881c_desc rpi_5inch_desc = { + .init = rpi_5inch_init, + .init_length = ARRAY_SIZE(rpi_5inch_init), + .mode = &rpi_5inch_default_mode, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM, + .lanes = 2, +}; + +static const struct ili9881c_desc rpi_7inch_desc = { + .init = rpi_7inch_init, + .init_length = ARRAY_SIZE(rpi_7inch_init), + .mode = &rpi_7inch_default_mode, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM, + .lanes = 2, +}; + +static const struct ili9881c_desc bsd1218_a101kl68_desc = { + .init = bsd1218_a101kl68_init, + .init_length = ARRAY_SIZE(bsd1218_a101kl68_init), + .mode = &bsd1218_a101kl68_default_mode, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_NO_EOT_PACKET, + .lanes = 4, +}; static const struct of_device_id ili9881c_of_match[] = { - { .compatible = "bananapi,lhr050h41" }, + { .compatible = "bananapi,lhr050h41", .data = &lhr050h41_desc }, + { .compatible = "bestar,bsd1218-a101kl68", .data = &bsd1218_a101kl68_desc }, + { .compatible = "feixin,k101-im2byl02", .data = &k101_im2byl02_desc }, + { .compatible = "startek,kd050hdfia020", .data = &kd050hdfia020_desc }, + { .compatible = "tdo,tl050hdv35", .data = &tl050hdv35_desc }, + { .compatible = "wanchanglong,w552946aaa", .data = &w552946aaa_desc }, + { .compatible = "wanchanglong,w552946aba", .data = &w552946aba_desc }, + { .compatible = "ampire,am8001280g", .data = &am8001280g_desc }, + { .compatible = "raspberrypi,dsi-5inch", &rpi_5inch_desc }, + { .compatible = "raspberrypi,dsi-7inch", &rpi_7inch_desc }, { } }; MODULE_DEVICE_TABLE(of, ili9881c_of_match); diff --git a/drivers/gpu/drm/panel/panel-ilitek-ili9882t.c b/drivers/gpu/drm/panel/panel-ilitek-ili9882t.c new file mode 100644 index 000000000000..c52f20863fc7 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-ilitek-ili9882t.c @@ -0,0 +1,768 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Panels based on the Ilitek ILI9882T display controller. + */ +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regulator/consumer.h> + +#include <drm/drm_connector.h> +#include <drm/drm_crtc.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_panel.h> + +#include <video/mipi_display.h> + +struct ili9882t; + +/* + * Use this descriptor struct to describe different panels using the + * Ilitek ILI9882T display controller. + */ +struct panel_desc { + const struct drm_display_mode *modes; + unsigned int bpc; + + /** + * @width_mm: width of the panel's active display area + * @height_mm: height of the panel's active display area + */ + struct { + unsigned int width_mm; + unsigned int height_mm; + } size; + + unsigned long mode_flags; + enum mipi_dsi_pixel_format format; + int (*init)(struct ili9882t *boe); + unsigned int lanes; +}; + +struct ili9882t { + struct drm_panel base; + struct mipi_dsi_device *dsi; + + const struct panel_desc *desc; + + enum drm_panel_orientation orientation; + struct regulator *pp3300; + struct regulator *pp1800; + struct regulator *avee; + struct regulator *avdd; + struct gpio_desc *enable_gpio; +}; + +/* ILI9882-specific commands, add new commands as you decode them */ +#define ILI9882T_DCS_SWITCH_PAGE 0xFF + +#define ili9882t_switch_page(ctx, page) \ + mipi_dsi_dcs_write_seq_multi(ctx, ILI9882T_DCS_SWITCH_PAGE, \ + 0x98, 0x82, (page)) + +/* IL79900A-specific commands, add new commands as you decode them */ +#define IL79900A_DCS_SWITCH_PAGE 0xFF + +#define il79900a_switch_page(ctx, page) \ + mipi_dsi_dcs_write_seq_multi(ctx, IL79900A_DCS_SWITCH_PAGE, \ + 0x5a, 0xa5, (page)) + +static int starry_ili9882t_init(struct ili9882t *ili) +{ + struct mipi_dsi_multi_context ctx = { .dsi = ili->dsi }; + + usleep_range(5000, 5100); + + ili9882t_switch_page(&ctx, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x00, 0x42); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x01, 0x11); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x02, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x03, 0x00); + + mipi_dsi_dcs_write_seq_multi(&ctx, 0x04, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x05, 0x11); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x06, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x07, 0x00); + + mipi_dsi_dcs_write_seq_multi(&ctx, 0x08, 0x80); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x09, 0x81); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0a, 0x71); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0b, 0x00); + + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0c, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0e, 0x1a); + + mipi_dsi_dcs_write_seq_multi(&ctx, 0x24, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x25, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x26, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x27, 0x00); + + mipi_dsi_dcs_write_seq_multi(&ctx, 0x2c, 0xd4); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb9, 0x40); + + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x11); + + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe6, 0x32); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd1, 0x30); + + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd6, 0x55); + + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd0, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe3, 0x93); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe4, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe5, 0x80); + + mipi_dsi_dcs_write_seq_multi(&ctx, 0x31, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x32, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x33, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x34, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x35, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x36, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x37, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x38, 0x28); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x39, 0x29); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x3a, 0x11); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x3b, 0x13); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x3c, 0x15); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x3d, 0x17); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x3e, 0x09); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x3f, 0x0d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x40, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x41, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x42, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x43, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x44, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x45, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x46, 0x02); + + mipi_dsi_dcs_write_seq_multi(&ctx, 0x47, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x48, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x49, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x4a, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x4b, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x4c, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x4d, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x4e, 0x28); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x4f, 0x29); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x50, 0x10); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x51, 0x12); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x52, 0x14); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x53, 0x16); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x54, 0x08); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x55, 0x0c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x56, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x57, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x58, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x59, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5a, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5b, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5c, 0x02); + + mipi_dsi_dcs_write_seq_multi(&ctx, 0x61, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x62, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x63, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x64, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x65, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x66, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x67, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x68, 0x28); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x69, 0x29); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x6a, 0x16); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x6b, 0x14); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x6c, 0x12); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x6d, 0x10); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x6e, 0x0c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x6f, 0x08); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x70, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x71, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x72, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x73, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x74, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x75, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x76, 0x02); + + mipi_dsi_dcs_write_seq_multi(&ctx, 0x77, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x78, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x79, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x7a, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x7b, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x7c, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x7d, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x7e, 0x28); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x7f, 0x29); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x80, 0x17); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x81, 0x15); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x82, 0x13); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x83, 0x11); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x84, 0x0d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x85, 0x09); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x86, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x87, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x88, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x89, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x8a, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x8b, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x8c, 0x07); + + ili9882t_switch_page(&ctx, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x29, 0x3a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x2a, 0x3b); + + mipi_dsi_dcs_write_seq_multi(&ctx, 0x06, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x07, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x08, 0x0c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x09, 0x44); + + mipi_dsi_dcs_write_seq_multi(&ctx, 0x3c, 0x0a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x39, 0x11); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x3d, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x3a, 0x0c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x3b, 0x44); + + mipi_dsi_dcs_write_seq_multi(&ctx, 0x53, 0x1f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5e, 0x40); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x84, 0x00); + + ili9882t_switch_page(&ctx, 0x03); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x20, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x21, 0x3c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x22, 0xfa); + + ili9882t_switch_page(&ctx, 0x0a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe0, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe2, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe5, 0x91); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe6, 0x3c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe7, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe8, 0xfa); + + ili9882t_switch_page(&ctx, 0x12); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x87, 0x2c); + + ili9882t_switch_page(&ctx, 0x05); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x73, 0xe5); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x7f, 0x6b); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x6d, 0xa4); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x79, 0x54); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x69, 0x97); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x6a, 0x97); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xa5, 0x3f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x61, 0xda); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xa7, 0xf1); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5f, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x62, 0x3f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1d, 0x90); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x86, 0x87); + + ili9882t_switch_page(&ctx, 0x06); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc0, 0x80); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc1, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xca, 0x58); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcb, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xce, 0x58); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcf, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x67, 0x60); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x10, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x92, 0x22); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd3, 0x08); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd6, 0x55); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xdc, 0x38); + + ili9882t_switch_page(&ctx, 0x08); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe0, 0x00, 0x10, 0x2a, 0x4d, 0x61, 0x56, 0x6a, 0x6e, + 0x79, 0x76, 0x8f, 0x95, 0x98, 0xae, 0xaa, 0xb2, 0xbb, 0xce, + 0xc6, 0xbd, 0xd5, 0xe2, 0xe8); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe1, 0x00, 0x10, 0x2a, 0x4d, 0x61, 0x56, 0x6a, 0x6e, + 0x79, 0x76, 0x8f, 0x95, 0x98, 0xae, 0xaa, 0xb2, 0xbb, 0xce, + 0xc6, 0xbd, 0xd5, 0xe2, 0xe8); + + ili9882t_switch_page(&ctx, 0x04); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xba, 0x81); + + ili9882t_switch_page(&ctx, 0x0c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x00, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x01, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x02, 0x03); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x03, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x04, 0x03); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x05, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x06, 0x04); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x07, 0x03); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x08, 0x03); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x09, 0x04); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0a, 0x04); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0b, 0x05); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0c, 0x04); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0d, 0x06); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0e, 0x05); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x0f, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x10, 0x04); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x11, 0x08); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x12, 0x05); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x13, 0x09); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x14, 0x05); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x15, 0x0a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x16, 0x06); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x17, 0x0b); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x18, 0x05); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x19, 0x0c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1a, 0x06); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1b, 0x0d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1c, 0x06); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1d, 0x0e); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1e, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1f, 0x0f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x20, 0x06); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x21, 0x10); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x22, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x23, 0x11); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x24, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x25, 0x12); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x26, 0x08); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x27, 0x13); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x28, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x29, 0x14); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x2a, 0x08); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x2b, 0x15); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x2c, 0x08); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x2d, 0x16); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x2e, 0x09); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x2f, 0x17); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x30, 0x08); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x31, 0x18); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x32, 0x09); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x33, 0x19); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x34, 0x09); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x35, 0x1a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x36, 0x0a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x37, 0x1b); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x38, 0x0a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x39, 0x1c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x3a, 0x0a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x3b, 0x1d); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x3c, 0x0a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x3d, 0x1e); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x3e, 0x0a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x3f, 0x1f); + + ili9882t_switch_page(&ctx, 0x04); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xba, 0x01); + + ili9882t_switch_page(&ctx, 0x0e); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x02, 0x0c); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x20, 0x10); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x25, 0x16); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x26, 0xe0); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x27, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x29, 0x71); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x2a, 0x46); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x2b, 0x1f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x2d, 0xc7); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x31, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x32, 0xdf); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x33, 0x5a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x34, 0xc0); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x35, 0x5a); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x36, 0xc0); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x38, 0x65); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x80, 0x3e); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x81, 0xa0); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb1, 0xcc); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc0, 0x12); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc2, 0xcc); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc3, 0xcc); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc4, 0xcc); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc5, 0xcc); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc6, 0xcc); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc7, 0xcc); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc8, 0xcc); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc9, 0xcc); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x30, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x00, 0x81); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x08, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x09, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x07, 0x21); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x04, 0x10); + + ili9882t_switch_page(&ctx, 0x1e); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x60, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x64, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x6d, 0x00); + + ili9882t_switch_page(&ctx, 0x0b); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xa6, 0x44); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xa7, 0xb6); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xa8, 0x03); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xa9, 0x03); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xaa, 0x51); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xab, 0x51); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xac, 0x04); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbd, 0x92); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbe, 0xa1); + + ili9882t_switch_page(&ctx, 0x05); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x86, 0x87); + + ili9882t_switch_page(&ctx, 0x06); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x92, 0x22); + + ili9882t_switch_page(&ctx, 0x00); + mipi_dsi_dcs_exit_sleep_mode_multi(&ctx); + + mipi_dsi_msleep(&ctx, 120); + + mipi_dsi_dcs_set_display_on_multi(&ctx); + + mipi_dsi_msleep(&ctx, 20); + + return ctx.accum_err; +}; + +static int tianma_il79900a_init(struct ili9882t *ili) +{ + struct mipi_dsi_multi_context ctx = { .dsi = ili->dsi }; + + mipi_dsi_usleep_range(&ctx, 5000, 5100); + + il79900a_switch_page(&ctx, 0x06); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x3e, 0x62); + + il79900a_switch_page(&ctx, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x1b, 0x20); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5d, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x5e, 0x40); + + il79900a_switch_page(&ctx, 0x07); + mipi_dsi_dcs_write_seq_multi(&ctx, 0X29, 0x00); + + il79900a_switch_page(&ctx, 0x06); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x92, 0x22); + + il79900a_switch_page(&ctx, 0x00); + mipi_dsi_dcs_exit_sleep_mode_multi(&ctx); + + mipi_dsi_msleep(&ctx, 120); + + mipi_dsi_dcs_set_display_on_multi(&ctx); + + mipi_dsi_msleep(&ctx, 80); + + return 0; +}; + +static inline struct ili9882t *to_ili9882t(struct drm_panel *panel) +{ + return container_of(panel, struct ili9882t, base); +} + +static int ili9882t_disable(struct drm_panel *panel) +{ + struct ili9882t *ili = to_ili9882t(panel); + struct mipi_dsi_multi_context ctx = { .dsi = ili->dsi }; + + ili9882t_switch_page(&ctx, 0x00); + + ili->dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + mipi_dsi_dcs_set_display_off_multi(&ctx); + mipi_dsi_dcs_enter_sleep_mode_multi(&ctx); + + mipi_dsi_msleep(&ctx, 150); + + return ctx.accum_err; +} + +static int ili9882t_unprepare(struct drm_panel *panel) +{ + struct ili9882t *ili = to_ili9882t(panel); + + gpiod_set_value(ili->enable_gpio, 0); + usleep_range(1000, 2000); + regulator_disable(ili->avee); + regulator_disable(ili->avdd); + usleep_range(5000, 7000); + regulator_disable(ili->pp1800); + regulator_disable(ili->pp3300); + + return 0; +} + +static int ili9882t_prepare(struct drm_panel *panel) +{ + struct ili9882t *ili = to_ili9882t(panel); + int ret; + + gpiod_set_value(ili->enable_gpio, 0); + usleep_range(1000, 1500); + + ret = regulator_enable(ili->pp3300); + if (ret < 0) + return ret; + + ret = regulator_enable(ili->pp1800); + if (ret < 0) + return ret; + + usleep_range(3000, 5000); + + ret = regulator_enable(ili->avdd); + if (ret < 0) + goto poweroff1v8; + ret = regulator_enable(ili->avee); + if (ret < 0) + goto poweroffavdd; + + usleep_range(10000, 11000); + + // MIPI needs to keep the LP11 state before the lcm_reset pin is pulled high + ret = mipi_dsi_dcs_nop(ili->dsi); + if (ret < 0) { + dev_err(&ili->dsi->dev, "Failed to send NOP: %d\n", ret); + goto poweroff; + } + usleep_range(1000, 2000); + + gpiod_set_value(ili->enable_gpio, 1); + usleep_range(1000, 2000); + gpiod_set_value(ili->enable_gpio, 0); + msleep(50); + gpiod_set_value(ili->enable_gpio, 1); + usleep_range(6000, 10000); + + ret = ili->desc->init(ili); + if (ret < 0) + goto poweroff; + + return 0; + +poweroff: + gpiod_set_value(ili->enable_gpio, 0); + regulator_disable(ili->avee); +poweroffavdd: + regulator_disable(ili->avdd); +poweroff1v8: + usleep_range(5000, 7000); + regulator_disable(ili->pp1800); + + return ret; +} + +static int ili9882t_enable(struct drm_panel *panel) +{ + msleep(130); + return 0; +} + +static const struct drm_display_mode starry_ili9882t_default_mode = { + .clock = 165280, + .hdisplay = 1200, + .hsync_start = 1200 + 72, + .hsync_end = 1200 + 72 + 30, + .htotal = 1200 + 72 + 30 + 72, + .vdisplay = 1920, + .vsync_start = 1920 + 68, + .vsync_end = 1920 + 68 + 2, + .vtotal = 1920 + 68 + 2 + 10, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, +}; + +static const struct drm_display_mode tianma_il79900a_default_mode = { + .clock = 264355, + .hdisplay = 1600, + .hsync_start = 1600 + 20, + .hsync_end = 1600 + 20 + 4, + .htotal = 1600 + 20 + 4 + 20, + .vdisplay = 2560, + .vsync_start = 2560 + 82, + .vsync_end = 2560 + 82 + 2, + .vtotal = 2560 + 82 + 2 + 36, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, +}; + +static const struct panel_desc starry_ili9882t_desc = { + .modes = &starry_ili9882t_default_mode, + .bpc = 8, + .size = { + .width_mm = 141, + .height_mm = 226, + }, + .lanes = 4, + .format = MIPI_DSI_FMT_RGB888, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_MODE_LPM, + .init = starry_ili9882t_init, +}; + +static const struct panel_desc tianma_tl121bvms07_desc = { + .modes = &tianma_il79900a_default_mode, + .bpc = 8, + .size = { + .width_mm = 163, + .height_mm = 260, + }, + .lanes = 3, + .format = MIPI_DSI_FMT_RGB888, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_MODE_LPM, + .init = tianma_il79900a_init, +}; + +static int ili9882t_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct ili9882t *ili = to_ili9882t(panel); + const struct drm_display_mode *m = ili->desc->modes; + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, m); + if (!mode) { + dev_err(panel->dev, "failed to add mode %ux%u@%u\n", + m->hdisplay, m->vdisplay, drm_mode_vrefresh(m)); + return -ENOMEM; + } + + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + + connector->display_info.width_mm = ili->desc->size.width_mm; + connector->display_info.height_mm = ili->desc->size.height_mm; + connector->display_info.bpc = ili->desc->bpc; + + return 1; +} + +static enum drm_panel_orientation ili9882t_get_orientation(struct drm_panel *panel) +{ + struct ili9882t *ili = to_ili9882t(panel); + + return ili->orientation; +} + +static const struct drm_panel_funcs ili9882t_funcs = { + .disable = ili9882t_disable, + .unprepare = ili9882t_unprepare, + .prepare = ili9882t_prepare, + .enable = ili9882t_enable, + .get_modes = ili9882t_get_modes, + .get_orientation = ili9882t_get_orientation, +}; + +static int ili9882t_add(struct ili9882t *ili) +{ + struct device *dev = &ili->dsi->dev; + int err; + + ili->avdd = devm_regulator_get(dev, "avdd"); + if (IS_ERR(ili->avdd)) + return PTR_ERR(ili->avdd); + + ili->avee = devm_regulator_get(dev, "avee"); + if (IS_ERR(ili->avee)) + return PTR_ERR(ili->avee); + + ili->pp3300 = devm_regulator_get(dev, "pp3300"); + if (IS_ERR(ili->pp3300)) + return PTR_ERR(ili->pp3300); + + ili->pp1800 = devm_regulator_get(dev, "pp1800"); + if (IS_ERR(ili->pp1800)) + return PTR_ERR(ili->pp1800); + + ili->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(ili->enable_gpio)) { + dev_err(dev, "cannot get enable-gpios %ld\n", + PTR_ERR(ili->enable_gpio)); + return PTR_ERR(ili->enable_gpio); + } + + gpiod_set_value(ili->enable_gpio, 0); + + err = of_drm_get_panel_orientation(dev->of_node, &ili->orientation); + if (err < 0) { + dev_err(dev, "%pOF: failed to get orientation %d\n", dev->of_node, err); + return err; + } + + err = drm_panel_of_backlight(&ili->base); + if (err) + return err; + + ili->base.funcs = &ili9882t_funcs; + ili->base.dev = &ili->dsi->dev; + + drm_panel_add(&ili->base); + + return 0; +} + +static int ili9882t_probe(struct mipi_dsi_device *dsi) +{ + struct ili9882t *ili; + int ret; + const struct panel_desc *desc; + + ili = devm_drm_panel_alloc(&dsi->dev, __typeof(*ili), base, + &ili9882t_funcs, DRM_MODE_CONNECTOR_DSI); + + if (IS_ERR(ili)) + return PTR_ERR(ili); + + desc = of_device_get_match_data(&dsi->dev); + dsi->lanes = desc->lanes; + dsi->format = desc->format; + dsi->mode_flags = desc->mode_flags; + ili->desc = desc; + ili->dsi = dsi; + ret = ili9882t_add(ili); + if (ret < 0) + return ret; + + mipi_dsi_set_drvdata(dsi, ili); + + ret = mipi_dsi_attach(dsi); + if (ret) + drm_panel_remove(&ili->base); + + return ret; +} + +static void ili9882t_remove(struct mipi_dsi_device *dsi) +{ + struct ili9882t *ili = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", ret); + + if (ili->base.dev) + drm_panel_remove(&ili->base); +} + +static const struct of_device_id ili9882t_of_match[] = { + { .compatible = "starry,ili9882t", + .data = &starry_ili9882t_desc + }, + { .compatible = "tianma,tl121bvms07-00", + .data = &tianma_tl121bvms07_desc + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ili9882t_of_match); + +static struct mipi_dsi_driver ili9882t_driver = { + .driver = { + .name = "panel-ili9882t", + .of_match_table = ili9882t_of_match, + }, + .probe = ili9882t_probe, + .remove = ili9882t_remove, +}; +module_mipi_dsi_driver(ili9882t_driver); + +MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>"); +MODULE_DESCRIPTION("Ilitek ILI9882T-based panels driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-innolux-ej030na.c b/drivers/gpu/drm/panel/panel-innolux-ej030na.c new file mode 100644 index 000000000000..b2309900873b --- /dev/null +++ b/drivers/gpu/drm/panel/panel-innolux-ej030na.c @@ -0,0 +1,309 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Innolux/Chimei EJ030NA TFT LCD panel driver + * + * Copyright (C) 2020, Paul Cercueil <paul@crapouillou.net> + * Copyright (C) 2020, Christophe Branchereau <cbranchereau@gmail.com> + */ + +#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/platform_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> + +struct ej030na_info { + const struct drm_display_mode *display_modes; + unsigned int num_modes; + u16 width_mm, height_mm; + u32 bus_format, bus_flags; +}; + +struct ej030na { + struct drm_panel panel; + struct spi_device *spi; + struct regmap *map; + + const struct ej030na_info *panel_info; + + struct regulator *supply; + struct gpio_desc *reset_gpio; +}; + +static inline struct ej030na *to_ej030na(struct drm_panel *panel) +{ + return container_of(panel, struct ej030na, panel); +} + +static const struct reg_sequence ej030na_init_sequence[] = { + { 0x05, 0x1e }, + { 0x05, 0x5c }, + { 0x02, 0x14 }, + { 0x03, 0x40 }, + { 0x04, 0x07 }, + { 0x06, 0x12 }, + { 0x07, 0xd2 }, + { 0x0c, 0x06 }, + { 0x0d, 0x40 }, + { 0x0e, 0x40 }, + { 0x0f, 0x40 }, + { 0x10, 0x40 }, + { 0x11, 0x40 }, + { 0x2f, 0x40 }, + { 0x5a, 0x02 }, + + { 0x30, 0x07 }, + { 0x31, 0x57 }, + { 0x32, 0x53 }, + { 0x33, 0x77 }, + { 0x34, 0xb8 }, + { 0x35, 0xbd }, + { 0x36, 0xb8 }, + { 0x37, 0xe7 }, + { 0x38, 0x04 }, + { 0x39, 0xff }, + + { 0x40, 0x0b }, + { 0x41, 0xb8 }, + { 0x42, 0xab }, + { 0x43, 0xb9 }, + { 0x44, 0x6a }, + { 0x45, 0x56 }, + { 0x46, 0x61 }, + { 0x47, 0x08 }, + { 0x48, 0x0f }, + { 0x49, 0x0f }, +}; + +static int ej030na_prepare(struct drm_panel *panel) +{ + struct ej030na *priv = to_ej030na(panel); + struct device *dev = &priv->spi->dev; + int err; + + err = regulator_enable(priv->supply); + if (err) { + dev_err(dev, "Failed to enable power supply: %d\n", err); + return err; + } + + /* Reset the chip */ + gpiod_set_value_cansleep(priv->reset_gpio, 1); + usleep_range(50, 150); + gpiod_set_value_cansleep(priv->reset_gpio, 0); + usleep_range(50, 150); + + err = regmap_multi_reg_write(priv->map, ej030na_init_sequence, + ARRAY_SIZE(ej030na_init_sequence)); + if (err) { + dev_err(dev, "Failed to init registers: %d\n", err); + goto err_disable_regulator; + } + + return 0; + +err_disable_regulator: + regulator_disable(priv->supply); + return err; +} + +static int ej030na_unprepare(struct drm_panel *panel) +{ + struct ej030na *priv = to_ej030na(panel); + + gpiod_set_value_cansleep(priv->reset_gpio, 1); + regulator_disable(priv->supply); + + return 0; +} + +static int ej030na_enable(struct drm_panel *panel) +{ + struct ej030na *priv = to_ej030na(panel); + + /* standby off */ + regmap_write(priv->map, 0x2b, 0x01); + + if (panel->backlight) { + /* Wait for the picture to be ready before enabling backlight */ + msleep(120); + } + + return 0; +} + +static int ej030na_disable(struct drm_panel *panel) +{ + struct ej030na *priv = to_ej030na(panel); + + /* standby on */ + regmap_write(priv->map, 0x2b, 0x00); + + return 0; +} + +static int ej030na_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct ej030na *priv = to_ej030na(panel); + const struct ej030na_info *panel_info = priv->panel_info; + struct drm_display_mode *mode; + unsigned int i; + + for (i = 0; i < panel_info->num_modes; i++) { + mode = drm_mode_duplicate(connector->dev, + &panel_info->display_modes[i]); + if (!mode) + return -ENOMEM; + + drm_mode_set_name(mode); + + mode->type = DRM_MODE_TYPE_DRIVER; + if (panel_info->num_modes == 1) + mode->type |= 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 panel_info->num_modes; +} + +static const struct drm_panel_funcs ej030na_funcs = { + .prepare = ej030na_prepare, + .unprepare = ej030na_unprepare, + .enable = ej030na_enable, + .disable = ej030na_disable, + .get_modes = ej030na_get_modes, +}; + +static const struct regmap_config ej030na_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x5a, +}; + +static int ej030na_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct ej030na *priv; + int err; + + priv = devm_drm_panel_alloc(dev, struct ej030na, panel, + &ej030na_funcs, + DRM_MODE_CONNECTOR_DPI); + if (IS_ERR(priv)) + return PTR_ERR(priv); + + priv->spi = spi; + spi_set_drvdata(spi, priv); + + priv->map = devm_regmap_init_spi(spi, &ej030na_regmap_config); + if (IS_ERR(priv->map)) { + dev_err(dev, "Unable to init regmap\n"); + return PTR_ERR(priv->map); + } + + priv->panel_info = of_device_get_match_data(dev); + if (!priv->panel_info) + return -EINVAL; + + priv->supply = devm_regulator_get(dev, "power"); + if (IS_ERR(priv->supply)) + return dev_err_probe(dev, PTR_ERR(priv->supply), + "Failed to get power supply\n"); + + priv->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(priv->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(priv->reset_gpio), + "Failed to get reset GPIO\n"); + + err = drm_panel_of_backlight(&priv->panel); + if (err) + return err; + + drm_panel_add(&priv->panel); + + return 0; +} + +static void ej030na_remove(struct spi_device *spi) +{ + struct ej030na *priv = spi_get_drvdata(spi); + + drm_panel_remove(&priv->panel); + drm_panel_disable(&priv->panel); + drm_panel_unprepare(&priv->panel); +} + +static const struct drm_display_mode ej030na_modes[] = { + { /* 60 Hz */ + .clock = 14400, + .hdisplay = 320, + .hsync_start = 320 + 10, + .hsync_end = 320 + 10 + 37, + .htotal = 320 + 10 + 37 + 33, + .vdisplay = 480, + .vsync_start = 480 + 102, + .vsync_end = 480 + 102 + 9 + 9, + .vtotal = 480 + 102 + 9 + 9, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + }, + { /* 50 Hz */ + .clock = 12000, + .hdisplay = 320, + .hsync_start = 320 + 10, + .hsync_end = 320 + 10 + 37, + .htotal = 320 + 10 + 37 + 33, + .vdisplay = 480, + .vsync_start = 480 + 102, + .vsync_end = 480 + 102 + 9, + .vtotal = 480 + 102 + 9 + 9, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + }, +}; + +static const struct ej030na_info ej030na_info = { + .display_modes = ej030na_modes, + .num_modes = ARRAY_SIZE(ej030na_modes), + .width_mm = 70, + .height_mm = 51, + .bus_format = MEDIA_BUS_FMT_RGB888_3X8_DELTA, + .bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE | DRM_BUS_FLAG_DE_LOW, +}; + +static const struct of_device_id ej030na_of_match[] = { + { .compatible = "innolux,ej030na", .data = &ej030na_info }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ej030na_of_match); + +static struct spi_driver ej030na_driver = { + .driver = { + .name = "panel-innolux-ej030na", + .of_match_table = ej030na_of_match, + }, + .probe = ej030na_probe, + .remove = ej030na_remove, +}; +module_spi_driver(ej030na_driver); + +MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>"); +MODULE_AUTHOR("Christophe Branchereau <cbranchereau@gmail.com>"); +MODULE_DESCRIPTION("Innolux/Chimei EJ030NA TFT LCD panel driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-innolux-p079zca.c b/drivers/gpu/drm/panel/panel-innolux-p079zca.c index ca4ae45dd307..80afeeab9475 100644 --- a/drivers/gpu/drm/panel/panel-innolux-p079zca.c +++ b/drivers/gpu/drm/panel/panel-innolux-p079zca.c @@ -1,34 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2017, Fuzhou Rockchip Electronics Co., Ltd - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. */ -#include <linux/backlight.h> +#include <linux/delay.h> #include <linux/gpio/consumer.h> #include <linux/module.h> #include <linux/of.h> -#include <linux/of_device.h> #include <linux/regulator/consumer.h> -#include <drm/drmP.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 <video/mipi_display.h> - -struct panel_init_cmd { - size_t len; - const char *data; -}; - -#define _INIT_CMD(...) { \ - .len = sizeof((char[]){__VA_ARGS__}), \ - .data = (char[]){__VA_ARGS__} } +struct innolux_panel; struct panel_desc { const struct drm_display_mode *mode; @@ -40,7 +29,7 @@ struct panel_desc { unsigned long flags; enum mipi_dsi_pixel_format format; - const struct panel_init_cmd *init_cmds; + int (*init)(struct innolux_panel *innolux); unsigned int lanes; const char * const *supply_names; unsigned int num_supplies; @@ -53,13 +42,8 @@ struct innolux_panel { struct mipi_dsi_device *link; const struct panel_desc *desc; - struct backlight_device *backlight; struct regulator_bulk_data *supplies; - unsigned int num_supplies; struct gpio_desc *enable_gpio; - - bool prepared; - bool enabled; }; static inline struct innolux_panel *to_innolux_panel(struct drm_panel *panel) @@ -67,38 +51,18 @@ static inline struct innolux_panel *to_innolux_panel(struct drm_panel *panel) return container_of(panel, struct innolux_panel, base); } -static int innolux_panel_disable(struct drm_panel *panel) +static int innolux_panel_unprepare(struct drm_panel *panel) { struct innolux_panel *innolux = to_innolux_panel(panel); int err; - if (!innolux->enabled) - return 0; - - backlight_disable(innolux->backlight); - err = mipi_dsi_dcs_set_display_off(innolux->link); if (err < 0) - DRM_DEV_ERROR(panel->dev, "failed to set display off: %d\n", - err); - - innolux->enabled = false; - - return 0; -} - -static int innolux_panel_unprepare(struct drm_panel *panel) -{ - struct innolux_panel *innolux = to_innolux_panel(panel); - int err; - - if (!innolux->prepared) - return 0; + dev_err(panel->dev, "failed to set display off: %d\n", err); err = mipi_dsi_dcs_enter_sleep_mode(innolux->link); if (err < 0) { - DRM_DEV_ERROR(panel->dev, "failed to enter sleep mode: %d\n", - err); + dev_err(panel->dev, "failed to enter sleep mode: %d\n", err); return err; } @@ -115,8 +79,6 @@ static int innolux_panel_unprepare(struct drm_panel *panel) if (err < 0) return err; - innolux->prepared = false; - return 0; } @@ -125,9 +87,6 @@ static int innolux_panel_prepare(struct drm_panel *panel) struct innolux_panel *innolux = to_innolux_panel(panel); int err; - if (innolux->prepared) - return 0; - gpiod_set_value_cansleep(innolux->enable_gpio, 0); err = regulator_bulk_enable(innolux->desc->num_supplies, @@ -143,40 +102,15 @@ static int innolux_panel_prepare(struct drm_panel *panel) /* p079zca: t4, p097pfg: t5 */ usleep_range(20000, 21000); - if (innolux->desc->init_cmds) { - const struct panel_init_cmd *cmds = - innolux->desc->init_cmds; - unsigned int i; - - for (i = 0; cmds[i].len != 0; i++) { - const struct panel_init_cmd *cmd = &cmds[i]; - - err = mipi_dsi_generic_write(innolux->link, cmd->data, - cmd->len); - if (err < 0) { - dev_err(panel->dev, - "failed to write command %u\n", i); - goto poweroff; - } - - /* - * Included by random guessing, because without this - * (or at least, some delay), the panel sometimes - * didn't appear to pick up the command sequence. - */ - err = mipi_dsi_dcs_nop(innolux->link); - if (err < 0) { - dev_err(panel->dev, - "failed to send DCS nop: %d\n", err); - goto poweroff; - } - } + if (innolux->desc->init) { + err = innolux->desc->init(innolux); + if (err < 0) + goto poweroff; } err = mipi_dsi_dcs_exit_sleep_mode(innolux->link); if (err < 0) { - DRM_DEV_ERROR(panel->dev, "failed to exit sleep mode: %d\n", - err); + dev_err(panel->dev, "failed to exit sleep mode: %d\n", err); goto poweroff; } @@ -185,16 +119,13 @@ static int innolux_panel_prepare(struct drm_panel *panel) err = mipi_dsi_dcs_set_display_on(innolux->link); if (err < 0) { - DRM_DEV_ERROR(panel->dev, "failed to set display on: %d\n", - err); + dev_err(panel->dev, "failed to set display on: %d\n", err); goto poweroff; } /* T7: 5ms */ usleep_range(5000, 6000); - innolux->prepared = true; - return 0; poweroff: @@ -204,26 +135,6 @@ poweroff: return err; } -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; -} - static const char * const innolux_p079zca_supply_names[] = { "power", }; @@ -238,7 +149,6 @@ static const struct drm_display_mode innolux_p079zca_mode = { .vsync_start = 1024 + 20, .vsync_end = 1024 + 20 + 4, .vtotal = 1024 + 20 + 4 + 20, - .vrefresh = 60, }; static const struct panel_desc innolux_p079zca_panel_desc = { @@ -272,122 +182,139 @@ static const struct drm_display_mode innolux_p097pfg_mode = { .vsync_start = 2048 + 100, .vsync_end = 2048 + 100 + 2, .vtotal = 2048 + 100 + 2 + 18, - .vrefresh = 60, }; +static void innolux_panel_write_multi(struct mipi_dsi_multi_context *ctx, + const void *payload, size_t size) +{ + mipi_dsi_generic_write_multi(ctx, payload, size); + + /* + * Included by random guessing, because without this + * (or at least, some delay), the panel sometimes + * didn't appear to pick up the command sequence. + */ + mipi_dsi_dcs_nop_multi(ctx); +} + +#define innolux_panel_init_cmd_multi(ctx, seq...) \ + do { \ + static const u8 d[] = { seq }; \ + innolux_panel_write_multi(ctx, d, ARRAY_SIZE(d)); \ + } while (0) + +#define innolux_panel_switch_page(ctx, page) \ + innolux_panel_init_cmd_multi(ctx, 0xf0, 0x55, 0xaa, 0x52, 0x08, (page)) + /* * Display manufacturer failed to provide init sequencing according to * https://chromium-review.googlesource.com/c/chromiumos/third_party/coreboot/+/892065/ * so the init sequence stems from a register dump of a working panel. */ -static const struct panel_init_cmd innolux_p097pfg_init_cmds[] = { - /* page 0 */ - _INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00), - _INIT_CMD(0xB1, 0xE8, 0x11), - _INIT_CMD(0xB2, 0x25, 0x02), - _INIT_CMD(0xB5, 0x08, 0x00), - _INIT_CMD(0xBC, 0x0F, 0x00), - _INIT_CMD(0xB8, 0x03, 0x06, 0x00, 0x00), - _INIT_CMD(0xBD, 0x01, 0x90, 0x14, 0x14), - _INIT_CMD(0x6F, 0x01), - _INIT_CMD(0xC0, 0x03), - _INIT_CMD(0x6F, 0x02), - _INIT_CMD(0xC1, 0x0D), - _INIT_CMD(0xD9, 0x01, 0x09, 0x70), - _INIT_CMD(0xC5, 0x12, 0x21, 0x00), - _INIT_CMD(0xBB, 0x93, 0x93), - - /* page 1 */ - _INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x01), - _INIT_CMD(0xB3, 0x3C, 0x3C), - _INIT_CMD(0xB4, 0x0F, 0x0F), - _INIT_CMD(0xB9, 0x45, 0x45), - _INIT_CMD(0xBA, 0x14, 0x14), - _INIT_CMD(0xCA, 0x02), - _INIT_CMD(0xCE, 0x04), - _INIT_CMD(0xC3, 0x9B, 0x9B), - _INIT_CMD(0xD8, 0xC0, 0x03), - _INIT_CMD(0xBC, 0x82, 0x01), - _INIT_CMD(0xBD, 0x9E, 0x01), - - /* page 2 */ - _INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x02), - _INIT_CMD(0xB0, 0x82), - _INIT_CMD(0xD1, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x82, 0x00, 0xA5, - 0x00, 0xC1, 0x00, 0xEA, 0x01, 0x0D, 0x01, 0x40), - _INIT_CMD(0xD2, 0x01, 0x6A, 0x01, 0xA8, 0x01, 0xDC, 0x02, 0x29, - 0x02, 0x67, 0x02, 0x68, 0x02, 0xA8, 0x02, 0xF0), - _INIT_CMD(0xD3, 0x03, 0x19, 0x03, 0x49, 0x03, 0x67, 0x03, 0x8C, - 0x03, 0xA6, 0x03, 0xC7, 0x03, 0xDE, 0x03, 0xEC), - _INIT_CMD(0xD4, 0x03, 0xFF, 0x03, 0xFF), - _INIT_CMD(0xE0, 0x00, 0x00, 0x00, 0x86, 0x00, 0xC5, 0x00, 0xE5, - 0x00, 0xFF, 0x01, 0x26, 0x01, 0x45, 0x01, 0x75), - _INIT_CMD(0xE1, 0x01, 0x9C, 0x01, 0xD5, 0x02, 0x05, 0x02, 0x4D, - 0x02, 0x86, 0x02, 0x87, 0x02, 0xC3, 0x03, 0x03), - _INIT_CMD(0xE2, 0x03, 0x2A, 0x03, 0x56, 0x03, 0x72, 0x03, 0x94, - 0x03, 0xAC, 0x03, 0xCB, 0x03, 0xE0, 0x03, 0xED), - _INIT_CMD(0xE3, 0x03, 0xFF, 0x03, 0xFF), - - /* page 3 */ - _INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x03), - _INIT_CMD(0xB0, 0x00, 0x00, 0x00, 0x00), - _INIT_CMD(0xB1, 0x00, 0x00, 0x00, 0x00), - _INIT_CMD(0xB2, 0x00, 0x00, 0x06, 0x04, 0x01, 0x40, 0x85), - _INIT_CMD(0xB3, 0x10, 0x07, 0xFC, 0x04, 0x01, 0x40, 0x80), - _INIT_CMD(0xB6, 0xF0, 0x08, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, - 0x40, 0x80), - _INIT_CMD(0xBA, 0xC5, 0x07, 0x00, 0x04, 0x11, 0x25, 0x8C), - _INIT_CMD(0xBB, 0xC5, 0x07, 0x00, 0x03, 0x11, 0x25, 0x8C), - _INIT_CMD(0xC0, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x80, 0x80), - _INIT_CMD(0xC1, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x80, 0x80), - _INIT_CMD(0xC4, 0x00, 0x00), - _INIT_CMD(0xEF, 0x41), - - /* page 4 */ - _INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x04), - _INIT_CMD(0xEC, 0x4C), - - /* page 5 */ - _INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x05), - _INIT_CMD(0xB0, 0x13, 0x03, 0x03, 0x01), - _INIT_CMD(0xB1, 0x30, 0x00), - _INIT_CMD(0xB2, 0x02, 0x02, 0x00), - _INIT_CMD(0xB3, 0x82, 0x23, 0x82, 0x9D), - _INIT_CMD(0xB4, 0xC5, 0x75, 0x24, 0x57), - _INIT_CMD(0xB5, 0x00, 0xD4, 0x72, 0x11, 0x11, 0xAB, 0x0A), - _INIT_CMD(0xB6, 0x00, 0x00, 0xD5, 0x72, 0x24, 0x56), - _INIT_CMD(0xB7, 0x5C, 0xDC, 0x5C, 0x5C), - _INIT_CMD(0xB9, 0x0C, 0x00, 0x00, 0x01, 0x00), - _INIT_CMD(0xC0, 0x75, 0x11, 0x11, 0x54, 0x05), - _INIT_CMD(0xC6, 0x00, 0x00, 0x00, 0x00), - _INIT_CMD(0xD0, 0x00, 0x48, 0x08, 0x00, 0x00), - _INIT_CMD(0xD1, 0x00, 0x48, 0x09, 0x00, 0x00), - - /* page 6 */ - _INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x06), - _INIT_CMD(0xB0, 0x02, 0x32, 0x32, 0x08, 0x2F), - _INIT_CMD(0xB1, 0x2E, 0x15, 0x14, 0x13, 0x12), - _INIT_CMD(0xB2, 0x11, 0x10, 0x00, 0x3D, 0x3D), - _INIT_CMD(0xB3, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D), - _INIT_CMD(0xB4, 0x3D, 0x32), - _INIT_CMD(0xB5, 0x03, 0x32, 0x32, 0x09, 0x2F), - _INIT_CMD(0xB6, 0x2E, 0x1B, 0x1A, 0x19, 0x18), - _INIT_CMD(0xB7, 0x17, 0x16, 0x01, 0x3D, 0x3D), - _INIT_CMD(0xB8, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D), - _INIT_CMD(0xB9, 0x3D, 0x32), - _INIT_CMD(0xC0, 0x01, 0x32, 0x32, 0x09, 0x2F), - _INIT_CMD(0xC1, 0x2E, 0x1A, 0x1B, 0x16, 0x17), - _INIT_CMD(0xC2, 0x18, 0x19, 0x03, 0x3D, 0x3D), - _INIT_CMD(0xC3, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D), - _INIT_CMD(0xC4, 0x3D, 0x32), - _INIT_CMD(0xC5, 0x00, 0x32, 0x32, 0x08, 0x2F), - _INIT_CMD(0xC6, 0x2E, 0x14, 0x15, 0x10, 0x11), - _INIT_CMD(0xC7, 0x12, 0x13, 0x02, 0x3D, 0x3D), - _INIT_CMD(0xC8, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D), - _INIT_CMD(0xC9, 0x3D, 0x32), - - {}, -}; +static int innolux_p097pfg_init(struct innolux_panel *innolux) +{ + struct mipi_dsi_multi_context ctx = { .dsi = innolux->link }; + + innolux_panel_switch_page(&ctx, 0x00); + innolux_panel_init_cmd_multi(&ctx, 0xb1, 0xe8, 0x11); + innolux_panel_init_cmd_multi(&ctx, 0xb2, 0x25, 0x02); + innolux_panel_init_cmd_multi(&ctx, 0xb5, 0x08, 0x00); + innolux_panel_init_cmd_multi(&ctx, 0xbc, 0x0f, 0x00); + innolux_panel_init_cmd_multi(&ctx, 0xb8, 0x03, 0x06, 0x00, 0x00); + innolux_panel_init_cmd_multi(&ctx, 0xbd, 0x01, 0x90, 0x14, 0x14); + innolux_panel_init_cmd_multi(&ctx, 0x6f, 0x01); + innolux_panel_init_cmd_multi(&ctx, 0xc0, 0x03); + innolux_panel_init_cmd_multi(&ctx, 0x6f, 0x02); + innolux_panel_init_cmd_multi(&ctx, 0xc1, 0x0d); + innolux_panel_init_cmd_multi(&ctx, 0xd9, 0x01, 0x09, 0x70); + innolux_panel_init_cmd_multi(&ctx, 0xc5, 0x12, 0x21, 0x00); + innolux_panel_init_cmd_multi(&ctx, 0xbb, 0x93, 0x93); + + innolux_panel_switch_page(&ctx, 0x01); + innolux_panel_init_cmd_multi(&ctx, 0xb3, 0x3c, 0x3c); + innolux_panel_init_cmd_multi(&ctx, 0xb4, 0x0f, 0x0f); + innolux_panel_init_cmd_multi(&ctx, 0xb9, 0x45, 0x45); + innolux_panel_init_cmd_multi(&ctx, 0xba, 0x14, 0x14); + innolux_panel_init_cmd_multi(&ctx, 0xca, 0x02); + innolux_panel_init_cmd_multi(&ctx, 0xce, 0x04); + innolux_panel_init_cmd_multi(&ctx, 0xc3, 0x9b, 0x9b); + innolux_panel_init_cmd_multi(&ctx, 0xd8, 0xc0, 0x03); + innolux_panel_init_cmd_multi(&ctx, 0xbc, 0x82, 0x01); + innolux_panel_init_cmd_multi(&ctx, 0xbd, 0x9e, 0x01); + + innolux_panel_switch_page(&ctx, 0x02); + innolux_panel_init_cmd_multi(&ctx, 0xb0, 0x82); + innolux_panel_init_cmd_multi(&ctx, 0xd1, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x82, 0x00, 0xa5, + 0x00, 0xc1, 0x00, 0xea, 0x01, 0x0d, 0x01, 0x40); + innolux_panel_init_cmd_multi(&ctx, 0xd2, 0x01, 0x6a, 0x01, 0xa8, 0x01, 0xdc, 0x02, 0x29, + 0x02, 0x67, 0x02, 0x68, 0x02, 0xa8, 0x02, 0xf0); + innolux_panel_init_cmd_multi(&ctx, 0xd3, 0x03, 0x19, 0x03, 0x49, 0x03, 0x67, 0x03, 0x8c, + 0x03, 0xa6, 0x03, 0xc7, 0x03, 0xde, 0x03, 0xec); + innolux_panel_init_cmd_multi(&ctx, 0xd4, 0x03, 0xff, 0x03, 0xff); + innolux_panel_init_cmd_multi(&ctx, 0xe0, 0x00, 0x00, 0x00, 0x86, 0x00, 0xc5, 0x00, 0xe5, + 0x00, 0xff, 0x01, 0x26, 0x01, 0x45, 0x01, 0x75); + innolux_panel_init_cmd_multi(&ctx, 0xe1, 0x01, 0x9c, 0x01, 0xd5, 0x02, 0x05, 0x02, 0x4d, + 0x02, 0x86, 0x02, 0x87, 0x02, 0xc3, 0x03, 0x03); + innolux_panel_init_cmd_multi(&ctx, 0xe2, 0x03, 0x2a, 0x03, 0x56, 0x03, 0x72, 0x03, 0x94, + 0x03, 0xac, 0x03, 0xcb, 0x03, 0xe0, 0x03, 0xed); + innolux_panel_init_cmd_multi(&ctx, 0xe3, 0x03, 0xff, 0x03, 0xff); + + innolux_panel_switch_page(&ctx, 0x03); + innolux_panel_init_cmd_multi(&ctx, 0xb0, 0x00, 0x00, 0x00, 0x00); + innolux_panel_init_cmd_multi(&ctx, 0xb1, 0x00, 0x00, 0x00, 0x00); + innolux_panel_init_cmd_multi(&ctx, 0xb2, 0x00, 0x00, 0x06, 0x04, 0x01, 0x40, 0x85); + innolux_panel_init_cmd_multi(&ctx, 0xb3, 0x10, 0x07, 0xfc, 0x04, 0x01, 0x40, 0x80); + innolux_panel_init_cmd_multi(&ctx, 0xb6, 0xf0, 0x08, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, + 0x40, 0x80); + innolux_panel_init_cmd_multi(&ctx, 0xba, 0xc5, 0x07, 0x00, 0x04, 0x11, 0x25, 0x8c); + innolux_panel_init_cmd_multi(&ctx, 0xbb, 0xc5, 0x07, 0x00, 0x03, 0x11, 0x25, 0x8c); + innolux_panel_init_cmd_multi(&ctx, 0xc0, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x80, 0x80); + innolux_panel_init_cmd_multi(&ctx, 0xc1, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x80, 0x80); + innolux_panel_init_cmd_multi(&ctx, 0xc4, 0x00, 0x00); + innolux_panel_init_cmd_multi(&ctx, 0xef, 0x41); + + innolux_panel_switch_page(&ctx, 0x04); + innolux_panel_init_cmd_multi(&ctx, 0xec, 0x4c); + + innolux_panel_switch_page(&ctx, 0x05); + innolux_panel_init_cmd_multi(&ctx, 0xb0, 0x13, 0x03, 0x03, 0x01); + innolux_panel_init_cmd_multi(&ctx, 0xb1, 0x30, 0x00); + innolux_panel_init_cmd_multi(&ctx, 0xb2, 0x02, 0x02, 0x00); + innolux_panel_init_cmd_multi(&ctx, 0xb3, 0x82, 0x23, 0x82, 0x9d); + innolux_panel_init_cmd_multi(&ctx, 0xb4, 0xc5, 0x75, 0x24, 0x57); + innolux_panel_init_cmd_multi(&ctx, 0xb5, 0x00, 0xd4, 0x72, 0x11, 0x11, 0xab, 0x0a); + innolux_panel_init_cmd_multi(&ctx, 0xb6, 0x00, 0x00, 0xd5, 0x72, 0x24, 0x56); + innolux_panel_init_cmd_multi(&ctx, 0xb7, 0x5c, 0xdc, 0x5c, 0x5c); + innolux_panel_init_cmd_multi(&ctx, 0xb9, 0x0c, 0x00, 0x00, 0x01, 0x00); + innolux_panel_init_cmd_multi(&ctx, 0xc0, 0x75, 0x11, 0x11, 0x54, 0x05); + innolux_panel_init_cmd_multi(&ctx, 0xc6, 0x00, 0x00, 0x00, 0x00); + innolux_panel_init_cmd_multi(&ctx, 0xd0, 0x00, 0x48, 0x08, 0x00, 0x00); + innolux_panel_init_cmd_multi(&ctx, 0xd1, 0x00, 0x48, 0x09, 0x00, 0x00); + + innolux_panel_switch_page(&ctx, 0x06); + innolux_panel_init_cmd_multi(&ctx, 0xb0, 0x02, 0x32, 0x32, 0x08, 0x2f); + innolux_panel_init_cmd_multi(&ctx, 0xb1, 0x2e, 0x15, 0x14, 0x13, 0x12); + innolux_panel_init_cmd_multi(&ctx, 0xb2, 0x11, 0x10, 0x00, 0x3d, 0x3d); + innolux_panel_init_cmd_multi(&ctx, 0xb3, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d); + innolux_panel_init_cmd_multi(&ctx, 0xb4, 0x3d, 0x32); + innolux_panel_init_cmd_multi(&ctx, 0xb5, 0x03, 0x32, 0x32, 0x09, 0x2f); + innolux_panel_init_cmd_multi(&ctx, 0xb6, 0x2e, 0x1b, 0x1a, 0x19, 0x18); + innolux_panel_init_cmd_multi(&ctx, 0xb7, 0x17, 0x16, 0x01, 0x3d, 0x3d); + innolux_panel_init_cmd_multi(&ctx, 0xb8, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d); + innolux_panel_init_cmd_multi(&ctx, 0xb9, 0x3d, 0x32); + innolux_panel_init_cmd_multi(&ctx, 0xc0, 0x01, 0x32, 0x32, 0x09, 0x2f); + innolux_panel_init_cmd_multi(&ctx, 0xc1, 0x2e, 0x1a, 0x1b, 0x16, 0x17); + innolux_panel_init_cmd_multi(&ctx, 0xc2, 0x18, 0x19, 0x03, 0x3d, 0x3d); + innolux_panel_init_cmd_multi(&ctx, 0xc3, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d); + innolux_panel_init_cmd_multi(&ctx, 0xc4, 0x3d, 0x32); + innolux_panel_init_cmd_multi(&ctx, 0xc5, 0x00, 0x32, 0x32, 0x08, 0x2f); + innolux_panel_init_cmd_multi(&ctx, 0xc6, 0x2e, 0x14, 0x15, 0x10, 0x11); + innolux_panel_init_cmd_multi(&ctx, 0xc7, 0x12, 0x13, 0x02, 0x3d, 0x3d); + innolux_panel_init_cmd_multi(&ctx, 0xc8, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d); + innolux_panel_init_cmd_multi(&ctx, 0xc9, 0x3d, 0x32); + + return ctx.accum_err; +} static const struct panel_desc innolux_p097pfg_panel_desc = { .mode = &innolux_p097pfg_mode, @@ -399,44 +326,41 @@ static const struct panel_desc innolux_p097pfg_panel_desc = { .flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | MIPI_DSI_MODE_LPM, .format = MIPI_DSI_FMT_RGB888, - .init_cmds = innolux_p097pfg_init_cmds, + .init = innolux_p097pfg_init, .lanes = 4, .supply_names = innolux_p097pfg_supply_names, .num_supplies = ARRAY_SIZE(innolux_p097pfg_supply_names), .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", - m->hdisplay, m->vdisplay, m->vrefresh); + dev_err(panel->dev, "failed to add mode %ux%u@%u\n", + m->hdisplay, m->vdisplay, drm_mode_vrefresh(m)); 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; } static const struct drm_panel_funcs innolux_panel_funcs = { - .disable = innolux_panel_disable, .unprepare = innolux_panel_unprepare, .prepare = innolux_panel_prepare, - .enable = innolux_panel_enable, .get_modes = innolux_panel_get_modes, }; @@ -458,9 +382,11 @@ static int innolux_panel_add(struct mipi_dsi_device *dsi, struct device *dev = &dsi->dev; int err, i; - innolux = devm_kzalloc(dev, sizeof(*innolux), GFP_KERNEL); - if (!innolux) - return -ENOMEM; + innolux = devm_drm_panel_alloc(dev, struct innolux_panel, base, + &innolux_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(innolux)) + return PTR_ERR(innolux); innolux->desc = desc; @@ -486,18 +412,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); - innolux->base.funcs = &innolux_panel_funcs; - innolux->base.dev = dev; - - err = drm_panel_add(&innolux->base); - if (err < 0) + err = drm_panel_of_backlight(&innolux->base); + if (err) return err; + drm_panel_add(&innolux->base); + mipi_dsi_set_drvdata(dsi, innolux); innolux->link = dsi; @@ -512,6 +432,7 @@ static void innolux_panel_del(struct innolux_panel *innolux) static int innolux_panel_probe(struct mipi_dsi_device *dsi) { const struct panel_desc *desc; + struct innolux_panel *innolux; int err; desc = of_device_get_match_data(&dsi->dev); @@ -523,39 +444,27 @@ static int innolux_panel_probe(struct mipi_dsi_device *dsi) if (err < 0) return err; - return mipi_dsi_attach(dsi); + err = mipi_dsi_attach(dsi); + if (err < 0) { + innolux = mipi_dsi_get_drvdata(dsi); + innolux_panel_del(innolux); + return err; + } + + return 0; } -static int innolux_panel_remove(struct mipi_dsi_device *dsi) +static void 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); - if (err < 0) - DRM_DEV_ERROR(&dsi->dev, "failed to unprepare panel: %d\n", - err); - - err = innolux_panel_disable(&innolux->base); - if (err < 0) - DRM_DEV_ERROR(&dsi->dev, "failed to disable 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); + dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", err); innolux_panel_del(innolux); - - return 0; -} - -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); } static struct mipi_dsi_driver innolux_panel_driver = { @@ -565,7 +474,6 @@ static struct mipi_dsi_driver innolux_panel_driver = { }, .probe = innolux_panel_probe, .remove = innolux_panel_remove, - .shutdown = innolux_panel_shutdown, }; module_mipi_dsi_driver(innolux_panel_driver); diff --git a/drivers/gpu/drm/panel/panel-jadard-jd9365da-h3.c b/drivers/gpu/drm/panel/panel-jadard-jd9365da-h3.c new file mode 100644 index 000000000000..aa05316dc57b --- /dev/null +++ b/drivers/gpu/drm/panel/panel-jadard-jd9365da-h3.c @@ -0,0 +1,1216 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2019 Radxa Limited + * Copyright (c) 2022 Edgeble AI Technologies Pvt. Ltd. + * + * Author: + * - Jagan Teki <jagan@amarulasolutions.com> + * - Stephen Chen <stephen@radxa.com> + */ + +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> + +#include <linux/gpio/consumer.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regulator/consumer.h> + +struct jadard; + +struct jadard_panel_desc { + const struct drm_display_mode mode; + unsigned int lanes; + enum mipi_dsi_pixel_format format; + int (*init)(struct jadard *jadard); + bool lp11_before_reset; + bool reset_before_power_off_vcioo; + unsigned int vcioo_to_lp11_delay_ms; + unsigned int lp11_to_reset_delay_ms; + unsigned int backlight_off_to_display_off_delay_ms; + unsigned int display_off_to_enter_sleep_delay_ms; + unsigned int enter_sleep_to_reset_down_delay_ms; +}; + +struct jadard { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + const struct jadard_panel_desc *desc; + enum drm_panel_orientation orientation; + struct regulator *vdd; + struct regulator *vccio; + struct gpio_desc *reset; +}; + +#define JD9365DA_DCS_SWITCH_PAGE 0xe0 + +#define jd9365da_switch_page(dsi_ctx, page) \ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, JD9365DA_DCS_SWITCH_PAGE, (page)) + +static void jadard_enable_standard_cmds(struct mipi_dsi_multi_context *dsi_ctx) +{ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xe1, 0x93); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xe2, 0x65); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xe3, 0xf8); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x80, 0x03); +} + +static inline struct jadard *panel_to_jadard(struct drm_panel *panel) +{ + return container_of(panel, struct jadard, panel); +} + +static int jadard_disable(struct drm_panel *panel) +{ + struct jadard *jadard = panel_to_jadard(panel); + struct mipi_dsi_multi_context dsi_ctx = { .dsi = jadard->dsi }; + + if (jadard->desc->backlight_off_to_display_off_delay_ms) + mipi_dsi_msleep(&dsi_ctx, jadard->desc->backlight_off_to_display_off_delay_ms); + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + + if (jadard->desc->display_off_to_enter_sleep_delay_ms) + mipi_dsi_msleep(&dsi_ctx, jadard->desc->display_off_to_enter_sleep_delay_ms); + + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + + if (jadard->desc->enter_sleep_to_reset_down_delay_ms) + mipi_dsi_msleep(&dsi_ctx, jadard->desc->enter_sleep_to_reset_down_delay_ms); + + return dsi_ctx.accum_err; +} + +static int jadard_prepare(struct drm_panel *panel) +{ + struct jadard *jadard = panel_to_jadard(panel); + int ret; + + ret = regulator_enable(jadard->vccio); + if (ret) + return ret; + + ret = regulator_enable(jadard->vdd); + if (ret) + return ret; + + if (jadard->desc->vcioo_to_lp11_delay_ms) + msleep(jadard->desc->vcioo_to_lp11_delay_ms); + + if (jadard->desc->lp11_before_reset) { + ret = mipi_dsi_dcs_nop(jadard->dsi); + if (ret) + return ret; + } + + if (jadard->desc->lp11_to_reset_delay_ms) + msleep(jadard->desc->lp11_to_reset_delay_ms); + + gpiod_set_value(jadard->reset, 0); + msleep(5); + + gpiod_set_value(jadard->reset, 1); + msleep(10); + + gpiod_set_value(jadard->reset, 0); + msleep(130); + + ret = jadard->desc->init(jadard); + if (ret) + return ret; + + return 0; +} + +static int jadard_unprepare(struct drm_panel *panel) +{ + struct jadard *jadard = panel_to_jadard(panel); + + gpiod_set_value(jadard->reset, 0); + msleep(120); + + if (jadard->desc->reset_before_power_off_vcioo) { + gpiod_set_value(jadard->reset, 1); + + usleep_range(1000, 2000); + } + + regulator_disable(jadard->vdd); + regulator_disable(jadard->vccio); + + return 0; +} + +static int jadard_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct jadard *jadard = panel_to_jadard(panel); + const struct drm_display_mode *desc_mode = &jadard->desc->mode; + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, desc_mode); + if (!mode) { + DRM_DEV_ERROR(&jadard->dsi->dev, "failed to add mode %ux%ux@%u\n", + desc_mode->hdisplay, desc_mode->vdisplay, + drm_mode_vrefresh(desc_mode)); + return -ENOMEM; + } + + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + + connector->display_info.width_mm = mode->width_mm; + connector->display_info.height_mm = mode->height_mm; + + return 1; +} + +static enum drm_panel_orientation jadard_panel_get_orientation(struct drm_panel *panel) +{ + struct jadard *jadard = panel_to_jadard(panel); + + return jadard->orientation; +} + +static const struct drm_panel_funcs jadard_funcs = { + .disable = jadard_disable, + .unprepare = jadard_unprepare, + .prepare = jadard_prepare, + .get_modes = jadard_get_modes, + .get_orientation = jadard_panel_get_orientation, +}; + +static int radxa_display_8hd_ad002_init_cmds(struct jadard *jadard) +{ + struct mipi_dsi_multi_context dsi_ctx = { .dsi = jadard->dsi }; + + jd9365da_switch_page(&dsi_ctx, 0x00); + jadard_enable_standard_cmds(&dsi_ctx); + + jd9365da_switch_page(&dsi_ctx, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x01, 0x7E); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x03, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x04, 0x65); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0C, 0x74); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x17, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x18, 0xB7); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x19, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1A, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1B, 0xB7); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1C, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x24, 0xFE); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x37, 0x19); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x38, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x39, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3A, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3B, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3C, 0x70); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3D, 0xFF); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3E, 0xFF); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3F, 0xFF); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x40, 0x06); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x41, 0xA0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x43, 0x1E); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x44, 0x0F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x45, 0x28); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4B, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x55, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x56, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x57, 0xA9); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x58, 0x0A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x59, 0x0A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5A, 0x37); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5B, 0x19); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5D, 0x78); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5E, 0x63); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5F, 0x54); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x60, 0x49); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x61, 0x45); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x62, 0x38); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x63, 0x3D); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x64, 0x28); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x65, 0x43); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x66, 0x41); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x67, 0x43); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x68, 0x62); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x69, 0x50); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6A, 0x57); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6B, 0x49); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6C, 0x44); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6D, 0x37); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6E, 0x23); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6F, 0x10); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x70, 0x78); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x71, 0x63); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x72, 0x54); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x73, 0x49); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x74, 0x45); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x75, 0x38); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x76, 0x3D); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x77, 0x28); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x78, 0x43); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x79, 0x41); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7A, 0x43); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7B, 0x62); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7C, 0x50); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7D, 0x57); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7E, 0x49); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7F, 0x44); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x80, 0x37); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x81, 0x23); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x82, 0x10); + + jd9365da_switch_page(&dsi_ctx, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x00, 0x47); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x01, 0x47); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x02, 0x45); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x03, 0x45); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x04, 0x4B); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x05, 0x4B); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x06, 0x49); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x07, 0x49); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x08, 0x41); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x09, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0A, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0B, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0C, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0D, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0E, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0F, 0x5F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x10, 0x5F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x11, 0x57); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x12, 0x77); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x13, 0x35); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x14, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x15, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x16, 0x46); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x17, 0x46); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x18, 0x44); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x19, 0x44); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1A, 0x4A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1B, 0x4A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1C, 0x48); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1D, 0x48); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1E, 0x40); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1F, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x20, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x21, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x22, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x23, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x24, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x25, 0x5F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x26, 0x5F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x27, 0x57); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x28, 0x77); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x29, 0x35); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2A, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2B, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x58, 0x40); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x59, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5A, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5B, 0x10); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5C, 0x06); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5D, 0x40); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5E, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5F, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x60, 0x30); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x61, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x62, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x63, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x64, 0x6B); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x65, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x66, 0x0C); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x67, 0x73); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x68, 0x09); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x69, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6A, 0x56); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6B, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6C, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6D, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6E, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6F, 0x88); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x70, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x71, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x72, 0x06); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x73, 0x7B); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x74, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x75, 0xF8); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x76, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x77, 0xD5); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x78, 0x2E); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x79, 0x12); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7A, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7B, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7C, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7D, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7E, 0x7B); + + jd9365da_switch_page(&dsi_ctx, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x00, 0x0E); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x02, 0xB3); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x09, 0x60); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0E, 0x2A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x36, 0x59); + + jd9365da_switch_page(&dsi_ctx, 0x00); + + mipi_dsi_msleep(&dsi_ctx, 120); + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + + return dsi_ctx.accum_err; +}; + +static const struct jadard_panel_desc radxa_display_8hd_ad002_desc = { + .mode = { + .clock = 70000, + + .hdisplay = 800, + .hsync_start = 800 + 40, + .hsync_end = 800 + 40 + 18, + .htotal = 800 + 40 + 18 + 20, + + .vdisplay = 1280, + .vsync_start = 1280 + 20, + .vsync_end = 1280 + 20 + 4, + .vtotal = 1280 + 20 + 4 + 20, + + .width_mm = 127, + .height_mm = 199, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, + }, + .lanes = 4, + .format = MIPI_DSI_FMT_RGB888, + .init = radxa_display_8hd_ad002_init_cmds, +}; + +static int cz101b4001_init_cmds(struct jadard *jadard) +{ + struct mipi_dsi_multi_context dsi_ctx = { .dsi = jadard->dsi }; + + jd9365da_switch_page(&dsi_ctx, 0x00); + jadard_enable_standard_cmds(&dsi_ctx); + + jd9365da_switch_page(&dsi_ctx, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x01, 0x3B); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0C, 0x74); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x17, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x18, 0xAF); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x19, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1A, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1B, 0xAF); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1C, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x35, 0x26); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x37, 0x09); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x38, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x39, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3A, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3C, 0x78); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3D, 0xFF); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3E, 0xFF); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3F, 0x7F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x40, 0x06); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x41, 0xA0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x42, 0x81); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x43, 0x14); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x44, 0x23); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x45, 0x28); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x55, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x57, 0x69); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x59, 0x0A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5A, 0x2A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5B, 0x17); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5D, 0x7F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5E, 0x6B); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5F, 0x5C); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x60, 0x4F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x61, 0x4D); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x62, 0x3F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x63, 0x42); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x64, 0x2B); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x65, 0x44); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x66, 0x43); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x67, 0x43); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x68, 0x63); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x69, 0x52); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6A, 0x5A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6B, 0x4F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6C, 0x4E); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6D, 0x20); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6E, 0x0F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6F, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x70, 0x7F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x71, 0x6B); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x72, 0x5C); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x73, 0x4F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x74, 0x4D); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x75, 0x3F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x76, 0x42); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x77, 0x2B); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x78, 0x44); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x79, 0x43); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7A, 0x43); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7B, 0x63); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7C, 0x52); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7D, 0x5A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7E, 0x4F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7F, 0x4E); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x80, 0x20); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x81, 0x0F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x82, 0x00); + + jd9365da_switch_page(&dsi_ctx, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x00, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x01, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x02, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x03, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x04, 0x1E); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x05, 0x1E); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x06, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x07, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x08, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x09, 0x17); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0A, 0x17); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0B, 0x37); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0C, 0x37); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0D, 0x47); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0E, 0x47); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0F, 0x45); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x10, 0x45); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x11, 0x4B); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x12, 0x4B); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x13, 0x49); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x14, 0x49); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x15, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x16, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x17, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x18, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x19, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1A, 0x1E); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1B, 0x1E); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1C, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1D, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1E, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1F, 0x17); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x20, 0x17); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x21, 0x37); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x22, 0x37); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x23, 0x46); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x24, 0x46); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x25, 0x44); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x26, 0x44); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x27, 0x4A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x28, 0x4A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x29, 0x48); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2A, 0x48); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2B, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2C, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2D, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2E, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2F, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x30, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x31, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x32, 0x1E); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x33, 0x1E); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x34, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x35, 0x17); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x36, 0x17); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x37, 0x37); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x38, 0x37); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x39, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3A, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3B, 0x0A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3C, 0x0A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3D, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3E, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3F, 0x06); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x40, 0x06); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x41, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x42, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x43, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x44, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x45, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x46, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x47, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x48, 0x1E); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x49, 0x1E); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4A, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4B, 0x17); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4C, 0x17); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4D, 0x37); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4E, 0x37); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4F, 0x09); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x50, 0x09); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x51, 0x0B); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x52, 0x0B); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x53, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x54, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x55, 0x07); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x56, 0x07); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x57, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x58, 0x40); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5B, 0x30); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5C, 0x16); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5D, 0x34); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5E, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5F, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x63, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x64, 0x6A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x67, 0x73); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x68, 0x1D); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x69, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6A, 0x6A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6B, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6C, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6D, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6E, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6F, 0x88); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x75, 0xFF); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x77, 0xDD); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x78, 0x3F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x79, 0x15); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7A, 0x17); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7D, 0x14); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7E, 0x82); + + jd9365da_switch_page(&dsi_ctx, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x00, 0x0E); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x02, 0xB3); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x09, 0x61); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0E, 0x48); + + jd9365da_switch_page(&dsi_ctx, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xE6, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xE7, 0x0C); + + mipi_dsi_msleep(&dsi_ctx, 120); + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + + return dsi_ctx.accum_err; +}; + +static const struct jadard_panel_desc cz101b4001_desc = { + .mode = { + .clock = 70000, + + .hdisplay = 800, + .hsync_start = 800 + 40, + .hsync_end = 800 + 40 + 18, + .htotal = 800 + 40 + 18 + 20, + + .vdisplay = 1280, + .vsync_start = 1280 + 20, + .vsync_end = 1280 + 20 + 4, + .vtotal = 1280 + 20 + 4 + 20, + + .width_mm = 62, + .height_mm = 110, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, + }, + .lanes = 4, + .format = MIPI_DSI_FMT_RGB888, + .init = cz101b4001_init_cmds, +}; + +static int kingdisplay_kd101ne3_init_cmds(struct jadard *jadard) +{ + struct mipi_dsi_multi_context dsi_ctx = { .dsi = jadard->dsi }; + + jd9365da_switch_page(&dsi_ctx, 0x00); + jadard_enable_standard_cmds(&dsi_ctx); + + jd9365da_switch_page(&dsi_ctx, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0c, 0x74); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x17, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x18, 0xc7); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x19, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1a, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1b, 0xc7); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1c, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x24, 0xfe); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x37, 0x19); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x35, 0x28); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x38, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x39, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3a, 0x12); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3c, 0x7e); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3d, 0xff); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3e, 0xff); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3f, 0x7f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x40, 0x06); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x41, 0xa0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x43, 0x1e); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x44, 0x0b); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x55, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x57, 0x6a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x59, 0x0a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5a, 0x2e); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5b, 0x1a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5c, 0x15); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5d, 0x7f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5e, 0x61); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5f, 0x50); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x60, 0x43); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x61, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x62, 0x32); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x63, 0x35); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x64, 0x1f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x65, 0x38); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x66, 0x36); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x67, 0x36); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x68, 0x54); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x69, 0x42); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6a, 0x48); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6b, 0x39); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6c, 0x34); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6d, 0x26); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6e, 0x14); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x70, 0x7f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x71, 0x61); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x72, 0x50); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x73, 0x43); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x74, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x75, 0x32); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x76, 0x35); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x77, 0x1f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x78, 0x38); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x79, 0x36); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7a, 0x36); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7b, 0x54); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7c, 0x42); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7d, 0x48); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7e, 0x39); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7f, 0x34); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x80, 0x26); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x81, 0x14); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x82, 0x02); + + jd9365da_switch_page(&dsi_ctx, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x00, 0x52); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x01, 0x5f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x02, 0x5f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x03, 0x50); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x04, 0x77); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x05, 0x57); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x06, 0x5f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x07, 0x4e); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x08, 0x4c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x09, 0x5f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0a, 0x4a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0b, 0x48); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0c, 0x5f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0d, 0x46); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0e, 0x44); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0f, 0x40); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x10, 0x5f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x11, 0x5f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x12, 0x5f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x13, 0x5f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x14, 0x5f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x15, 0x5f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x16, 0x53); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x17, 0x5f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x18, 0x5f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x19, 0x51); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1a, 0x77); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1b, 0x57); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1c, 0x5f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1d, 0x4f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1e, 0x4d); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1f, 0x5f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x20, 0x4b); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x21, 0x49); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x22, 0x5f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x23, 0x47); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x24, 0x45); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x25, 0x41); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x26, 0x5f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x27, 0x5f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x28, 0x5f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x29, 0x5f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2a, 0x5f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2b, 0x5f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2c, 0x13); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2d, 0x1f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2e, 0x1f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2f, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x30, 0x17); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x31, 0x17); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x32, 0x1f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x33, 0x0d); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x34, 0x0f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x35, 0x1f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x36, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x37, 0x07); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x38, 0x1f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x39, 0x09); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3a, 0x0b); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3b, 0x11); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3c, 0x1f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3d, 0x1f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3e, 0x1f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3f, 0x1f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x40, 0x1f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x41, 0x1f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x42, 0x12); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x43, 0x1f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x44, 0x1f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x45, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x46, 0x17); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x47, 0x17); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x48, 0x1f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x49, 0x0c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4a, 0x0e); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4b, 0x1f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4c, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4d, 0x06); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4e, 0x1f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4f, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x50, 0x0a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x51, 0x10); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x52, 0x1f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x53, 0x1f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x54, 0x1f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x55, 0x1f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x56, 0x1f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x57, 0x1f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x58, 0x40); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5b, 0x10); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5c, 0x06); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5d, 0x40); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5e, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5f, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x60, 0x40); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x61, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x62, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x63, 0x6c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x64, 0x6c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x65, 0x75); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x66, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x67, 0xb4); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x68, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x69, 0x6c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6a, 0x6c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6b, 0x0c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6d, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6e, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x88); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x75, 0xbb); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x76, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x77, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x78, 0x2a); + + jd9365da_switch_page(&dsi_ctx, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x00, 0x0e); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x02, 0xb3); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x09, 0x61); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0e, 0x48); + + jd9365da_switch_page(&dsi_ctx, 0x00); + + mipi_dsi_msleep(&dsi_ctx, 120); + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + + mipi_dsi_msleep(&dsi_ctx, 120); + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + + mipi_dsi_msleep(&dsi_ctx, 20); + + return dsi_ctx.accum_err; +}; + +static const struct jadard_panel_desc kingdisplay_kd101ne3_40ti_desc = { + .mode = { + .clock = (800 + 24 + 24 + 24) * (1280 + 30 + 4 + 8) * 60 / 1000, + + .hdisplay = 800, + .hsync_start = 800 + 24, + .hsync_end = 800 + 24 + 24, + .htotal = 800 + 24 + 24 + 24, + + .vdisplay = 1280, + .vsync_start = 1280 + 30, + .vsync_end = 1280 + 30 + 4, + .vtotal = 1280 + 30 + 4 + 8, + + .width_mm = 135, + .height_mm = 216, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, + }, + .lanes = 4, + .format = MIPI_DSI_FMT_RGB888, + .init = kingdisplay_kd101ne3_init_cmds, + .lp11_before_reset = true, + .reset_before_power_off_vcioo = true, + .vcioo_to_lp11_delay_ms = 5, + .lp11_to_reset_delay_ms = 10, + .backlight_off_to_display_off_delay_ms = 100, + .display_off_to_enter_sleep_delay_ms = 50, + .enter_sleep_to_reset_down_delay_ms = 100, +}; + +static int melfas_lmfbx101117480_init_cmds(struct jadard *jadard) +{ + struct mipi_dsi_multi_context dsi_ctx = { .dsi = jadard->dsi }; + + jd9365da_switch_page(&dsi_ctx, 0x00); + jadard_enable_standard_cmds(&dsi_ctx); + + jd9365da_switch_page(&dsi_ctx, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0c, 0x74); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x17, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x18, 0xd7); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x19, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1a, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1b, 0xd7); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1c, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1f, 0x70); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x20, 0x2d); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x21, 0x2d); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x22, 0x7e); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x24, 0xfd); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x37, 0x19); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x35, 0x28); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x38, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x39, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3a, 0x12); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3c, 0x7e); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3d, 0xff); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3e, 0xff); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3f, 0x7f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x40, 0x06); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x41, 0xa0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x43, 0x1e); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x44, 0x0b); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0c, 0x74); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x55, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x56, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x57, 0x6a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x58, 0x09); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x59, 0x0a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5a, 0x2e); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5b, 0x1a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5c, 0x15); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5d, 0x73); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5e, 0x56); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5f, 0x43); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x60, 0x38); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x61, 0x36); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x62, 0x28); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x63, 0x2f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x64, 0x19); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x65, 0x32); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x66, 0x31); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x67, 0x31); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x68, 0x4f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x69, 0x3e); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6a, 0x47); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6b, 0x36); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6c, 0x31); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6d, 0x24); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6e, 0x12); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x70, 0x73); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x71, 0x56); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x72, 0x43); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x73, 0x38); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x74, 0x36); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x75, 0x28); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x76, 0x2f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x77, 0x19); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x78, 0x32); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x79, 0x31); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7a, 0x31); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7b, 0x4f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7c, 0x3e); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7d, 0x47); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7e, 0x36); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7f, 0x31); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x80, 0x24); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x81, 0x12); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x82, 0x02); + + jd9365da_switch_page(&dsi_ctx, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x00, 0x52); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x01, 0x55); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x02, 0x55); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x03, 0x50); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x04, 0x77); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x05, 0x57); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x06, 0x55); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x07, 0x4e); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x08, 0x4c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x09, 0x5f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0a, 0x4a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0b, 0x48); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0c, 0x55); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0d, 0x46); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0e, 0x44); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0f, 0x40); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x10, 0x55); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x11, 0x55); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x12, 0x55); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x13, 0x55); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x14, 0x55); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x15, 0x55); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x16, 0x53); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x17, 0x55); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x18, 0x55); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x19, 0x51); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1a, 0x77); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1b, 0x57); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1c, 0x55); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1d, 0x4f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1e, 0x4d); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1f, 0x5f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x20, 0x4b); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x21, 0x49); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x22, 0x55); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x23, 0x47); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x24, 0x45); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x25, 0x41); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x26, 0x55); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x27, 0x55); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x28, 0x55); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x29, 0x55); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2a, 0x55); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2b, 0x55); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2c, 0x13); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2d, 0x15); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2e, 0x15); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2f, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x30, 0x37); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x31, 0x17); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x32, 0x15); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x33, 0x0d); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x34, 0x0f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x35, 0x15); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x36, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x37, 0x07); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x38, 0x15); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x39, 0x09); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3a, 0x0b); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3b, 0x11); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3c, 0x15); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3d, 0x15); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3e, 0x15); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3f, 0x15); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x40, 0x15); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x41, 0x15); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x42, 0x12); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x43, 0x15); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x44, 0x15); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x45, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x46, 0x37); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x47, 0x17); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x48, 0x15); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x49, 0x0c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4a, 0x0e); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4b, 0x15); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4c, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4d, 0x06); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4e, 0x15); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4f, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x50, 0x0a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x51, 0x10); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x52, 0x15); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x53, 0x15); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x54, 0x15); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x55, 0x15); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x56, 0x15); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x57, 0x15); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x58, 0x40); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5b, 0x10); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5c, 0x06); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5d, 0x40); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5e, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5f, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x60, 0x40); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x61, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x62, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x63, 0x6c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x64, 0x6c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x65, 0x75); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x66, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x67, 0xb4); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x68, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x69, 0x6c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6a, 0x6c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6b, 0x0c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6d, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6e, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x88); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x75, 0xbb); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x76, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x77, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x78, 0x2a); + + jd9365da_switch_page(&dsi_ctx, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x02, 0x23); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x09, 0x11); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0e, 0x48); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x36, 0x49); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2b, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2e, 0x03); + + jd9365da_switch_page(&dsi_ctx, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe6, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe7, 0x06); + + mipi_dsi_msleep(&dsi_ctx, 120); + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + + mipi_dsi_msleep(&dsi_ctx, 120); + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + + mipi_dsi_msleep(&dsi_ctx, 20); + + return dsi_ctx.accum_err; +}; + +static const struct jadard_panel_desc melfas_lmfbx101117480_desc = { + .mode = { + .clock = (800 + 24 + 24 + 24) * (1280 + 30 + 4 + 8) * 60 / 1000, + + .hdisplay = 800, + .hsync_start = 800 + 24, + .hsync_end = 800 + 24 + 24, + .htotal = 800 + 24 + 24 + 24, + + .vdisplay = 1280, + .vsync_start = 1280 + 30, + .vsync_end = 1280 + 30 + 4, + .vtotal = 1280 + 30 + 4 + 8, + + .width_mm = 135, + .height_mm = 216, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, + }, + .lanes = 4, + .format = MIPI_DSI_FMT_RGB888, + .init = melfas_lmfbx101117480_init_cmds, + .lp11_before_reset = true, + .reset_before_power_off_vcioo = true, + .vcioo_to_lp11_delay_ms = 5, + .lp11_to_reset_delay_ms = 10, + .backlight_off_to_display_off_delay_ms = 100, + .display_off_to_enter_sleep_delay_ms = 50, + .enter_sleep_to_reset_down_delay_ms = 100, +}; + +static int jadard_dsi_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + const struct jadard_panel_desc *desc; + struct jadard *jadard; + int ret; + + jadard = devm_drm_panel_alloc(dev, struct jadard, panel, &jadard_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(jadard)) + return PTR_ERR(jadard); + + desc = of_device_get_match_data(dev); + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_NO_EOT_PACKET; + dsi->format = desc->format; + dsi->lanes = desc->lanes; + + jadard->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(jadard->reset)) + return dev_err_probe(&dsi->dev, PTR_ERR(jadard->reset), + "failed to get our reset GPIO\n"); + + jadard->vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(jadard->vdd)) + return dev_err_probe(&dsi->dev, PTR_ERR(jadard->vdd), + "failed to get vdd regulator\n"); + + jadard->vccio = devm_regulator_get(dev, "vccio"); + if (IS_ERR(jadard->vccio)) + return dev_err_probe(&dsi->dev, PTR_ERR(jadard->vccio), + "failed to get vccio regulator\n"); + + ret = of_drm_get_panel_orientation(dev->of_node, &jadard->orientation); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to get orientation\n"); + + ret = drm_panel_of_backlight(&jadard->panel); + if (ret) + return ret; + + drm_panel_add(&jadard->panel); + + mipi_dsi_set_drvdata(dsi, jadard); + jadard->dsi = dsi; + jadard->desc = desc; + + ret = mipi_dsi_attach(dsi); + if (ret < 0) + drm_panel_remove(&jadard->panel); + + return ret; +} + +static void jadard_dsi_remove(struct mipi_dsi_device *dsi) +{ + struct jadard *jadard = mipi_dsi_get_drvdata(dsi); + + mipi_dsi_detach(dsi); + drm_panel_remove(&jadard->panel); +} + +static const struct of_device_id jadard_of_match[] = { + { + .compatible = "chongzhou,cz101b4001", + .data = &cz101b4001_desc + }, + { + .compatible = "kingdisplay,kd101ne3-40ti", + .data = &kingdisplay_kd101ne3_40ti_desc + }, + { + .compatible = "melfas,lmfbx101117480", + .data = &melfas_lmfbx101117480_desc + }, + { + .compatible = "radxa,display-10hd-ad001", + .data = &cz101b4001_desc + }, + { + .compatible = "radxa,display-8hd-ad002", + .data = &radxa_display_8hd_ad002_desc + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, jadard_of_match); + +static struct mipi_dsi_driver jadard_driver = { + .probe = jadard_dsi_probe, + .remove = jadard_dsi_remove, + .driver = { + .name = "jadard-jd9365da", + .of_match_table = jadard_of_match, + }, +}; +module_mipi_dsi_driver(jadard_driver); + +MODULE_AUTHOR("Jagan Teki <jagan@edgeble.ai>"); +MODULE_AUTHOR("Stephen Chen <stephen@radxa.com>"); +MODULE_DESCRIPTION("Jadard JD9365DA-H3 WXGA DSI panel"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-jdi-fhd-r63452.c b/drivers/gpu/drm/panel/panel-jdi-fhd-r63452.c new file mode 100644 index 000000000000..cbe354b51bce --- /dev/null +++ b/drivers/gpu/drm/panel/panel-jdi-fhd-r63452.c @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021 Raffaele Tranquillini <raffaele.tranquillini@gmail.com> + * + * Generated using linux-mdss-dsi-panel-driver-generator from Lineage OS device tree: + * https://github.com/LineageOS/android_kernel_xiaomi_msm8996/blob/lineage-18.1/arch/arm/boot/dts/qcom/a1-msm8996-mtp.dtsi + */ + +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> + +#include <video/mipi_display.h> + +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +struct jdi_fhd_r63452 { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct gpio_desc *reset_gpio; +}; + +static inline struct jdi_fhd_r63452 *to_jdi_fhd_r63452(struct drm_panel *panel) +{ + return container_of(panel, struct jdi_fhd_r63452, panel); +} + +static void jdi_fhd_r63452_reset(struct jdi_fhd_r63452 *ctx) +{ + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(10000, 11000); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(1000, 2000); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(10000, 11000); +} + +static int jdi_fhd_r63452_on(struct jdi_fhd_r63452 *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb0, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xd6, 0x01); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xec, + 0x64, 0xdc, 0xec, 0x3b, 0x52, 0x00, 0x0b, 0x0b, + 0x13, 0x15, 0x68, 0x0b, 0xb5); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb0, 0x03); + + mipi_dsi_dcs_set_tear_on_multi(&dsi_ctx, MIPI_DSI_DCS_TEAR_MODE_VBLANK); + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_ADDRESS_MODE, 0x00); + + mipi_dsi_dcs_set_pixel_format_multi(&dsi_ctx, 0x77); + mipi_dsi_dcs_set_column_address_multi(&dsi_ctx, 0x0000, 0x0437); + mipi_dsi_dcs_set_page_address_multi(&dsi_ctx, 0x0000, 0x077f); + mipi_dsi_dcs_set_tear_scanline_multi(&dsi_ctx, 0x0000); + mipi_dsi_dcs_set_display_brightness_multi(&dsi_ctx, 0x00ff); + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x24); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_POWER_SAVE, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_CABC_MIN_BRIGHTNESS, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x84, 0x00); + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 20); + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 80); + + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb0, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x84, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xc8, 0x11); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb0, 0x03); + + return dsi_ctx.accum_err; +} + +static void jdi_fhd_r63452_off(struct jdi_fhd_r63452 *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb0, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xd6, 0x01); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xec, + 0x64, 0xdc, 0xec, 0x3b, 0x52, 0x00, 0x0b, 0x0b, + 0x13, 0x15, 0x68, 0x0b, 0x95); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb0, 0x03); + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_usleep_range(&dsi_ctx, 2000, 3000); + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 120); +} + +static int jdi_fhd_r63452_prepare(struct drm_panel *panel) +{ + struct jdi_fhd_r63452 *ctx = to_jdi_fhd_r63452(panel); + int ret; + + jdi_fhd_r63452_reset(ctx); + + ret = jdi_fhd_r63452_on(ctx); + if (ret < 0) + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + + return ret; +} + +static int jdi_fhd_r63452_unprepare(struct drm_panel *panel) +{ + struct jdi_fhd_r63452 *ctx = to_jdi_fhd_r63452(panel); + + /* + * NOTE: We don't return an error here as while the panel won't have + * been cleanly turned off at least we've asserted the reset signal + * so it should be safe to power it back on again later + */ + jdi_fhd_r63452_off(ctx); + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + + return 0; +} + +static const struct drm_display_mode jdi_fhd_r63452_mode = { + .clock = (1080 + 120 + 16 + 40) * (1920 + 4 + 2 + 4) * 60 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 120, + .hsync_end = 1080 + 120 + 16, + .htotal = 1080 + 120 + 16 + 40, + .vdisplay = 1920, + .vsync_start = 1920 + 4, + .vsync_end = 1920 + 4 + 2, + .vtotal = 1920 + 4 + 2 + 4, + .width_mm = 64, + .height_mm = 114, +}; + +static int jdi_fhd_r63452_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &jdi_fhd_r63452_mode); + if (!mode) + 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 jdi_fhd_r63452_panel_funcs = { + .prepare = jdi_fhd_r63452_prepare, + .unprepare = jdi_fhd_r63452_unprepare, + .get_modes = jdi_fhd_r63452_get_modes, +}; + +static int jdi_fhd_r63452_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct jdi_fhd_r63452 *ctx; + int ret; + + ctx = devm_drm_panel_alloc(dev, struct jdi_fhd_r63452, panel, + &jdi_fhd_r63452_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), + "Failed to get reset-gpios\n"); + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_CLOCK_NON_CONTINUOUS; + + ctx->panel.prepare_prev_first = true; + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return dev_err_probe(dev, ret, "Failed to get backlight\n"); + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "Failed to attach to DSI host: %d\n", ret); + return ret; + } + + return 0; +} + +static void jdi_fhd_r63452_remove(struct mipi_dsi_device *dsi) +{ + struct jdi_fhd_r63452 *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id jdi_fhd_r63452_of_match[] = { + { .compatible = "jdi,fhd-r63452" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, jdi_fhd_r63452_of_match); + +static struct mipi_dsi_driver jdi_fhd_r63452_driver = { + .probe = jdi_fhd_r63452_probe, + .remove = jdi_fhd_r63452_remove, + .driver = { + .name = "panel-jdi-fhd-r63452", + .of_match_table = jdi_fhd_r63452_of_match, + }, +}; +module_mipi_dsi_driver(jdi_fhd_r63452_driver); + +MODULE_AUTHOR("Raffaele Tranquillini <raffaele.tranquillini@gmail.com>"); +MODULE_DESCRIPTION("DRM driver for JDI FHD R63452 DSI panel, command mode"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-jdi-lpm102a188a.c b/drivers/gpu/drm/panel/panel-jdi-lpm102a188a.c new file mode 100644 index 000000000000..23462065d726 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-jdi-lpm102a188a.c @@ -0,0 +1,475 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2014 Google, Inc. + * + * Copyright (C) 2022 Diogo Ivo <diogo.ivo@tecnico.ulisboa.pt> + * + * Adapted from the downstream Pixel C driver written by Sean Paul + */ + +#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_mipi_dsi.h> +#include <drm/drm_panel.h> + +#define MCS_CMD_ACS_PROT 0xB0 +#define MCS_CMD_ACS_PROT_OFF (0 << 0) + +#define MCS_PWR_CTRL_FUNC 0xD0 +#define MCS_PWR_CTRL_PARAM1_DEFAULT (2 << 0) +#define MCS_PWR_CTRL_PARAM1_VGH_210_DIV (1 << 4) +#define MCS_PWR_CTRL_PARAM1_VGH_240_DIV (2 << 4) +#define MCS_PWR_CTRL_PARAM1_VGH_280_DIV (3 << 4) +#define MCS_PWR_CTRL_PARAM1_VGH_330_DIV (4 << 4) +#define MCS_PWR_CTRL_PARAM1_VGH_410_DIV (5 << 4) +#define MCS_PWR_CTRL_PARAM2_DEFAULT (9 << 4) +#define MCS_PWR_CTRL_PARAM2_VGL_210_DIV (1 << 0) +#define MCS_PWR_CTRL_PARAM2_VGL_240_DIV (2 << 0) +#define MCS_PWR_CTRL_PARAM2_VGL_280_DIV (3 << 0) +#define MCS_PWR_CTRL_PARAM2_VGL_330_DIV (4 << 0) +#define MCS_PWR_CTRL_PARAM2_VGL_410_DIV (5 << 0) + +struct jdi_panel { + struct drm_panel base; + struct mipi_dsi_device *link1; + struct mipi_dsi_device *link2; + + struct regulator *supply; + struct regulator *ddi_supply; + struct backlight_device *backlight; + + struct gpio_desc *enable_gpio; + struct gpio_desc *reset_gpio; + + const struct drm_display_mode *mode; +}; + +static inline struct jdi_panel *to_panel_jdi(struct drm_panel *panel) +{ + return container_of(panel, struct jdi_panel, base); +} + +static void jdi_wait_frames(struct jdi_panel *jdi, unsigned int frames) +{ + unsigned int refresh = drm_mode_vrefresh(jdi->mode); + + if (WARN_ON(frames > refresh)) + return; + + msleep(1000 / (refresh / frames)); +} + +static int jdi_panel_disable(struct drm_panel *panel) +{ + struct jdi_panel *jdi = to_panel_jdi(panel); + + backlight_disable(jdi->backlight); + + jdi_wait_frames(jdi, 2); + + return 0; +} + +static int jdi_panel_unprepare(struct drm_panel *panel) +{ + struct jdi_panel *jdi = to_panel_jdi(panel); + + /* + * One context per panel since we'll continue trying to shut down the + * other panel even if one isn't responding. + */ + struct mipi_dsi_multi_context dsi_ctx1 = { .dsi = jdi->link1 }; + struct mipi_dsi_multi_context dsi_ctx2 = { .dsi = jdi->link2 }; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx1); + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx2); + + /* Specified by JDI @ 50ms, subject to change */ + msleep(50); + + /* Doesn't hurt to try sleep mode even if display off fails */ + dsi_ctx1.accum_err = 0; + dsi_ctx2.accum_err = 0; + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx1); + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx2); + + /* Specified by JDI @ 150ms, subject to change */ + msleep(150); + + gpiod_set_value(jdi->reset_gpio, 1); + + /* T4 = 1ms */ + usleep_range(1000, 3000); + + gpiod_set_value(jdi->enable_gpio, 0); + + /* T5 = 2ms */ + usleep_range(2000, 4000); + + regulator_disable(jdi->ddi_supply); + + /* T6 = 2ms plus some time to discharge capacitors */ + usleep_range(7000, 9000); + + regulator_disable(jdi->supply); + /* Specified by JDI @ 20ms, subject to change */ + msleep(20); + + return 0; +} + +static void jdi_setup_symmetrical_split(struct mipi_dsi_multi_context *dsi_ctx, + struct mipi_dsi_device *left, + struct mipi_dsi_device *right, + const struct drm_display_mode *mode) +{ + mipi_dsi_dual(mipi_dsi_dcs_set_column_address_multi, + dsi_ctx, left, right, + 0, mode->hdisplay / 2 - 1); + mipi_dsi_dual(mipi_dsi_dcs_set_page_address_multi, + dsi_ctx, left, right, + 0, mode->vdisplay - 1); +} + +static void jdi_write_dcdc_registers(struct mipi_dsi_multi_context *dsi_ctx, + struct jdi_panel *jdi) +{ + /* Clear the manufacturer command access protection */ + mipi_dsi_dual_generic_write_seq_multi(dsi_ctx, jdi->link1, jdi->link2, + MCS_CMD_ACS_PROT, + MCS_CMD_ACS_PROT_OFF); + /* + * Change the VGH/VGL divide ratios to move the noise generated by the + * TCONN. This should hopefully avoid interaction with the backlight + * controller. + */ + mipi_dsi_dual_generic_write_seq_multi(dsi_ctx, jdi->link1, jdi->link2, + MCS_PWR_CTRL_FUNC, + MCS_PWR_CTRL_PARAM1_VGH_330_DIV | + MCS_PWR_CTRL_PARAM1_DEFAULT, + MCS_PWR_CTRL_PARAM2_VGL_410_DIV | + MCS_PWR_CTRL_PARAM2_DEFAULT); +} + +static int jdi_panel_prepare(struct drm_panel *panel) +{ + struct jdi_panel *jdi = to_panel_jdi(panel); + struct mipi_dsi_multi_context dsi_ctx = {}; + int err; + + /* Disable backlight to avoid showing random pixels + * with a conservative delay for it to take effect. + */ + backlight_disable(jdi->backlight); + jdi_wait_frames(jdi, 3); + + jdi->link1->mode_flags |= MIPI_DSI_MODE_LPM; + jdi->link2->mode_flags |= MIPI_DSI_MODE_LPM; + + err = regulator_enable(jdi->supply); + if (err < 0) { + dev_err(panel->dev, "failed to enable supply: %d\n", err); + return err; + } + /* T1 = 2ms */ + usleep_range(2000, 4000); + + err = regulator_enable(jdi->ddi_supply); + if (err < 0) { + dev_err(panel->dev, "failed to enable ddi_supply: %d\n", err); + goto supply_off; + } + /* T2 = 1ms */ + usleep_range(1000, 3000); + + gpiod_set_value(jdi->enable_gpio, 1); + /* T3 = 10ms */ + usleep_range(10000, 15000); + + gpiod_set_value(jdi->reset_gpio, 0); + /* Specified by JDI @ 3ms, subject to change */ + usleep_range(3000, 5000); + + /* + * TODO: The device supports both left-right and even-odd split + * configurations, but this driver currently supports only the left- + * right split. To support a different mode a mechanism needs to be + * put in place to communicate the configuration back to the DSI host + * controller. + */ + jdi_setup_symmetrical_split(&dsi_ctx, jdi->link1, jdi->link2, + jdi->mode); + + mipi_dsi_dual(mipi_dsi_dcs_set_tear_scanline_multi, + &dsi_ctx, jdi->link1, jdi->link2, + jdi->mode->vdisplay - 16); + + mipi_dsi_dual(mipi_dsi_dcs_set_tear_on_multi, + &dsi_ctx, jdi->link1, jdi->link2, + MIPI_DSI_DCS_TEAR_MODE_VBLANK); + + mipi_dsi_dual(mipi_dsi_dcs_set_pixel_format_multi, + &dsi_ctx, jdi->link1, jdi->link2, + MIPI_DCS_PIXEL_FMT_24BIT); + + mipi_dsi_dual(mipi_dsi_dcs_exit_sleep_mode_multi, + &dsi_ctx, jdi->link1, jdi->link2); + + jdi_write_dcdc_registers(&dsi_ctx, jdi); + /* + * We need to wait 150ms between mipi_dsi_dcs_exit_sleep_mode_multi() + * and mipi_dsi_dcs_set_display_on_multi(). + */ + mipi_dsi_msleep(&dsi_ctx, 150); + + mipi_dsi_dual(mipi_dsi_dcs_set_display_on_multi, + &dsi_ctx, jdi->link1, jdi->link2); + + if (dsi_ctx.accum_err < 0) { + err = dsi_ctx.accum_err; + goto poweroff; + } + + jdi->link1->mode_flags &= ~MIPI_DSI_MODE_LPM; + jdi->link2->mode_flags &= ~MIPI_DSI_MODE_LPM; + + return 0; + +poweroff: + regulator_disable(jdi->ddi_supply); + + /* T6 = 2ms plus some time to discharge capacitors */ + usleep_range(7000, 9000); +supply_off: + regulator_disable(jdi->supply); + /* Specified by JDI @ 20ms, subject to change */ + msleep(20); + + return err; +} + +static int jdi_panel_enable(struct drm_panel *panel) +{ + struct jdi_panel *jdi = to_panel_jdi(panel); + + /* + * Ensure we send image data before turning the backlight + * on, to avoid the display showing random pixels. + */ + jdi_wait_frames(jdi, 3); + + backlight_enable(jdi->backlight); + + return 0; +} + +static const struct drm_display_mode default_mode = { + .clock = (2560 + 80 + 80 + 80) * (1800 + 4 + 4 + 4) * 60 / 1000, + .hdisplay = 2560, + .hsync_start = 2560 + 80, + .hsync_end = 2560 + 80 + 80, + .htotal = 2560 + 80 + 80 + 80, + .vdisplay = 1800, + .vsync_start = 1800 + 4, + .vsync_end = 1800 + 4 + 4, + .vtotal = 1800 + 4 + 4 + 4, + .flags = 0, +}; + +static int jdi_panel_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + struct jdi_panel *jdi = to_panel_jdi(panel); + struct device *dev = &jdi->link1->dev; + + 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, + drm_mode_vrefresh(&default_mode)); + return -ENOMEM; + } + + drm_mode_set_name(mode); + + drm_mode_probed_add(connector, mode); + + connector->display_info.width_mm = 211; + connector->display_info.height_mm = 148; + connector->display_info.bpc = 8; + + return 1; +} + +static const struct drm_panel_funcs jdi_panel_funcs = { + .prepare = jdi_panel_prepare, + .enable = jdi_panel_enable, + .disable = jdi_panel_disable, + .unprepare = jdi_panel_unprepare, + .get_modes = jdi_panel_get_modes, +}; + +static const struct of_device_id jdi_of_match[] = { + { .compatible = "jdi,lpm102a188a", }, + { } +}; +MODULE_DEVICE_TABLE(of, jdi_of_match); + +static int jdi_panel_add(struct jdi_panel *jdi) +{ + struct device *dev = &jdi->link1->dev; + + jdi->mode = &default_mode; + + jdi->supply = devm_regulator_get(dev, "power"); + if (IS_ERR(jdi->supply)) + return dev_err_probe(dev, PTR_ERR(jdi->supply), + "failed to get power regulator\n"); + + jdi->ddi_supply = devm_regulator_get(dev, "ddi"); + if (IS_ERR(jdi->ddi_supply)) + return dev_err_probe(dev, PTR_ERR(jdi->ddi_supply), + "failed to get ddi regulator\n"); + + jdi->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(jdi->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(jdi->reset_gpio), + "failed to get reset gpio\n"); + /* T4 = 1ms */ + usleep_range(1000, 3000); + + jdi->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(jdi->enable_gpio)) + return dev_err_probe(dev, PTR_ERR(jdi->enable_gpio), + "failed to get enable gpio\n"); + /* T5 = 2ms */ + usleep_range(2000, 4000); + + jdi->backlight = devm_of_find_backlight(dev); + if (IS_ERR(jdi->backlight)) + return dev_err_probe(dev, PTR_ERR(jdi->backlight), + "failed to create backlight\n"); + + drm_panel_add(&jdi->base); + + return 0; +} + +static void jdi_panel_del(struct jdi_panel *jdi) +{ + if (jdi->base.dev) + drm_panel_remove(&jdi->base); + + if (jdi->link2) + put_device(&jdi->link2->dev); +} + +static int jdi_panel_dsi_probe(struct mipi_dsi_device *dsi) +{ + struct mipi_dsi_device *secondary = NULL; + struct jdi_panel *jdi; + struct device_node *np; + int err; + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = 0; + + /* Find DSI-LINK1 */ + np = of_parse_phandle(dsi->dev.of_node, "link2", 0); + if (np) { + secondary = of_find_mipi_dsi_device_by_node(np); + of_node_put(np); + + if (!secondary) + return -EPROBE_DEFER; + } + + /* register a panel for only the DSI-LINK1 interface */ + if (secondary) { + jdi = devm_drm_panel_alloc(&dsi->dev, __typeof(*jdi), + base, &jdi_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + + if (IS_ERR(jdi)) { + put_device(&secondary->dev); + return PTR_ERR(jdi); + } + + mipi_dsi_set_drvdata(dsi, jdi); + + jdi->link1 = dsi; + jdi->link2 = secondary; + + err = jdi_panel_add(jdi); + if (err < 0) { + put_device(&secondary->dev); + return err; + } + } + + err = mipi_dsi_attach(dsi); + if (err < 0) { + if (secondary) + jdi_panel_del(jdi); + + return err; + } + + return 0; +} + +static void jdi_panel_dsi_remove(struct mipi_dsi_device *dsi) +{ + struct jdi_panel *jdi = mipi_dsi_get_drvdata(dsi); + int err; + + /* only detach from host for the DSI-LINK2 interface */ + if (!jdi) + mipi_dsi_detach(dsi); + + err = jdi_panel_disable(&jdi->base); + if (err < 0) + dev_err(&dsi->dev, "failed to disable panel: %d\n", err); + + err = mipi_dsi_detach(dsi); + if (err < 0) + dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", err); + + jdi_panel_del(jdi); +} + +static void jdi_panel_dsi_shutdown(struct mipi_dsi_device *dsi) +{ + struct jdi_panel *jdi = mipi_dsi_get_drvdata(dsi); + + if (!jdi) + return; + + jdi_panel_disable(&jdi->base); +} + +static struct mipi_dsi_driver jdi_panel_dsi_driver = { + .driver = { + .name = "panel-jdi-lpm102a188a", + .of_match_table = jdi_of_match, + }, + .probe = jdi_panel_dsi_probe, + .remove = jdi_panel_dsi_remove, + .shutdown = jdi_panel_dsi_shutdown, +}; +module_mipi_dsi_driver(jdi_panel_dsi_driver); + +MODULE_AUTHOR("Sean Paul <seanpaul@chromium.org>"); +MODULE_AUTHOR("Diogo Ivo <diogo.ivo@tecnico.ulisboa.pt>"); +MODULE_DESCRIPTION("DRM Driver for JDI LPM102A188A DSI panel, command mode"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-jdi-lt070me05000.c b/drivers/gpu/drm/panel/panel-jdi-lt070me05000.c index 99caa7835e7b..3513e5c4dd8c 100644 --- a/drivers/gpu/drm/panel/panel-jdi-lt070me05000.c +++ b/drivers/gpu/drm/panel/panel-jdi-lt070me05000.c @@ -1,39 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2016 InforceComputing * Author: Vinay Simha BN <simhavcs@gmail.com> * * Copyright (C) 2016 Linaro Ltd * Author: Sumit Semwal <sumit.semwal@linaro.org> - * - * From internet archives, the panel for Nexus 7 2nd Gen, 2013 model is a - * JDI model LT070ME05000, and its data sheet is at: - * http://panelone.net/en/7-0-inch/JDI_LT070ME05000_7.0_inch-datasheet - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published by - * the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see <http://www.gnu.org/licenses/>. */ + #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 <drm/drmP.h> +#include <video/mipi_display.h> + #include <drm/drm_crtc.h> #include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> #include <drm/drm_panel.h> -#include <video/mipi_display.h> - static const char * const regulator_names[] = { "vddp", "iovcc" @@ -50,9 +37,6 @@ struct jdi_panel { struct gpio_desc *dcdc_en_gpio; struct backlight_device *backlight; - bool prepared; - bool enabled; - const struct drm_display_mode *mode; }; @@ -189,13 +173,8 @@ static int jdi_panel_disable(struct drm_panel *panel) { struct jdi_panel *jdi = to_jdi_panel(panel); - if (!jdi->enabled) - return 0; - backlight_disable(jdi->backlight); - jdi->enabled = false; - return 0; } @@ -205,9 +184,6 @@ static int jdi_panel_unprepare(struct drm_panel *panel) struct device *dev = &jdi->dsi->dev; int ret; - if (!jdi->prepared) - return 0; - jdi_panel_off(jdi); ret = regulator_bulk_disable(ARRAY_SIZE(jdi->supplies), jdi->supplies); @@ -220,8 +196,6 @@ static int jdi_panel_unprepare(struct drm_panel *panel) gpiod_set_value(jdi->dcdc_en_gpio, 0); - jdi->prepared = false; - return 0; } @@ -231,9 +205,6 @@ static int jdi_panel_prepare(struct drm_panel *panel) struct device *dev = &jdi->dsi->dev; int ret; - if (jdi->prepared) - return 0; - ret = regulator_bulk_enable(ARRAY_SIZE(jdi->supplies), jdi->supplies); if (ret < 0) { dev_err(dev, "regulator enable failed, %d\n", ret); @@ -263,8 +234,6 @@ static int jdi_panel_prepare(struct drm_panel *panel) goto poweroff; } - jdi->prepared = true; - return 0; poweroff: @@ -285,13 +254,8 @@ static int jdi_panel_enable(struct drm_panel *panel) { struct jdi_panel *jdi = to_jdi_panel(panel); - if (jdi->enabled) - return 0; - backlight_enable(jdi->backlight); - jdi->enabled = true; - return 0; } @@ -305,30 +269,30 @@ static const struct drm_display_mode default_mode = { .vsync_start = 1920 + 3, .vsync_end = 1920 + 3 + 5, .vtotal = 1920 + 3 + 5 + 6, - .vrefresh = 60, .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, - default_mode.vrefresh); + drm_mode_vrefresh(&default_mode)); 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 = 95; - panel->connector->display_info.height_mm = 151; + connector->display_info.width_mm = 95; + connector->display_info.height_mm = 151; return 1; } @@ -413,46 +377,34 @@ static int jdi_panel_add(struct jdi_panel *jdi) ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(jdi->supplies), jdi->supplies); - if (ret < 0) { - dev_err(dev, "failed to init regulator, ret=%d\n", ret); - return ret; - } + if (ret < 0) + return dev_err_probe(dev, ret, + "failed to init regulator, ret=%d\n", ret); jdi->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW); if (IS_ERR(jdi->enable_gpio)) { - ret = PTR_ERR(jdi->enable_gpio); - dev_err(dev, "cannot get enable-gpio %d\n", ret); - return ret; + return dev_err_probe(dev, PTR_ERR(jdi->enable_gpio), + "cannot get enable-gpio %d\n", ret); } jdi->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); - if (IS_ERR(jdi->reset_gpio)) { - ret = PTR_ERR(jdi->reset_gpio); - dev_err(dev, "cannot get reset-gpios %d\n", ret); - return ret; - } + if (IS_ERR(jdi->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(jdi->reset_gpio), + "cannot get reset-gpios %d\n", ret); jdi->dcdc_en_gpio = devm_gpiod_get(dev, "dcdc-en", GPIOD_OUT_LOW); - if (IS_ERR(jdi->dcdc_en_gpio)) { - ret = PTR_ERR(jdi->dcdc_en_gpio); - dev_err(dev, "cannot get dcdc-en-gpio %d\n", ret); - return ret; - } + if (IS_ERR(jdi->dcdc_en_gpio)) + return dev_err_probe(dev, PTR_ERR(jdi->dcdc_en_gpio), + "cannot get dcdc-en-gpio %d\n", ret); jdi->backlight = drm_panel_create_dsi_backlight(jdi->dsi); - if (IS_ERR(jdi->backlight)) { - ret = PTR_ERR(jdi->backlight); - dev_err(dev, "failed to register backlight %d\n", ret); - return ret; - } - - drm_panel_init(&jdi->base); - jdi->base.funcs = &jdi_panel_funcs; - jdi->base.dev = &jdi->dsi->dev; + if (IS_ERR(jdi->backlight)) + return dev_err_probe(dev, PTR_ERR(jdi->backlight), + "failed to register backlight %d\n", ret); - ret = drm_panel_add(&jdi->base); + drm_panel_add(&jdi->base); - return ret; + return 0; } static void jdi_panel_del(struct jdi_panel *jdi) @@ -471,9 +423,11 @@ static int jdi_panel_probe(struct mipi_dsi_device *dsi) dsi->mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS; - jdi = devm_kzalloc(&dsi->dev, sizeof(*jdi), GFP_KERNEL); - if (!jdi) - return -ENOMEM; + jdi = devm_drm_panel_alloc(&dsi->dev, __typeof(*jdi), base, + &jdi_panel_funcs, DRM_MODE_CONNECTOR_DSI); + + if (IS_ERR(jdi)) + return PTR_ERR(jdi); mipi_dsi_set_drvdata(dsi, jdi); @@ -483,33 +437,26 @@ static int jdi_panel_probe(struct mipi_dsi_device *dsi) if (ret < 0) return ret; - return mipi_dsi_attach(dsi); + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + jdi_panel_del(jdi); + return ret; + } + + return 0; } -static int jdi_panel_remove(struct mipi_dsi_device *dsi) +static void jdi_panel_remove(struct mipi_dsi_device *dsi) { struct jdi_panel *jdi = mipi_dsi_get_drvdata(dsi); int ret; - ret = jdi_panel_disable(&jdi->base); - if (ret < 0) - dev_err(&dsi->dev, "failed to disable panel: %d\n", ret); - ret = mipi_dsi_detach(dsi); if (ret < 0) dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", ret); jdi_panel_del(jdi); - - return 0; -} - -static void jdi_panel_shutdown(struct mipi_dsi_device *dsi) -{ - struct jdi_panel *jdi = mipi_dsi_get_drvdata(dsi); - - jdi_panel_disable(&jdi->base); } static struct mipi_dsi_driver jdi_panel_driver = { @@ -519,7 +466,6 @@ static struct mipi_dsi_driver jdi_panel_driver = { }, .probe = jdi_panel_probe, .remove = jdi_panel_remove, - .shutdown = jdi_panel_shutdown, }; module_mipi_dsi_driver(jdi_panel_driver); diff --git a/drivers/gpu/drm/panel/panel-khadas-ts050.c b/drivers/gpu/drm/panel/panel-khadas-ts050.c new file mode 100644 index 000000000000..67ca055f06f3 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-khadas-ts050.c @@ -0,0 +1,896 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 BayLibre, SAS + * Author: Neil Armstrong <narmstrong@baylibre.com> + */ + +#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> + +struct khadas_ts050_panel { + struct drm_panel base; + struct mipi_dsi_device *link; + + struct regulator *supply; + struct gpio_desc *reset_gpio; + struct gpio_desc *enable_gpio; + struct khadas_ts050_panel_data *panel_data; +}; + +struct khadas_ts050_panel_cmd { + u8 cmd; + u8 data[55]; + u8 size; +}; + +struct khadas_ts050_panel_data { + struct khadas_ts050_panel_cmd *init_code; + int len; +}; + +static const struct khadas_ts050_panel_cmd ts050v2_init_code[] = { + {0xB9, {0xFF, 0x83, 0x99}, 0x03}, + {0xBA, {0x63, 0x23, 0x68, 0xCF}, 0x04}, + {0xD2, {0x55}, 0x01}, + {0xB1, {0x02, 0x04, 0x70, 0x90, 0x01, 0x32, 0x33, + 0x11, 0x11, 0x4D, 0x57, 0x56, 0x73, 0x02, 0x02}, 0x0f}, + {0xB2, {0x00, 0x80, 0x80, 0xAE, 0x0A, 0x0E, 0x75, 0x11, 0x00, 0x00, 0x00}, 0x0b}, + {0xB4, {0x00, 0xFF, 0x04, 0xA4, 0x02, 0xA0, 0x00, 0x00, 0x10, 0x00, 0x00, 0x02, + 0x00, 0x24, 0x02, 0x04, 0x0A, 0x21, 0x03, 0x00, 0x00, 0x08, 0xA6, 0x88, + 0x04, 0xA4, 0x02, 0xA0, 0x00, 0x00, 0x10, 0x00, 0x00, 0x02, 0x00, 0x24, + 0x02, 0x04, 0x0A, 0x00, 0x00, 0x08, 0xA6, 0x00, 0x08, 0x11}, 0x2e}, + {0xD3, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, + 0x18, 0x32, 0x10, 0x09, 0x00, 0x09, 0x32, + 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x11, 0x00, 0x02, 0x02, 0x03, 0x00, 0x00, 0x00, 0x0A, + 0x40}, 0x21}, + {0xD5, {0x18, 0x18, 0x18, 0x18, 0x21, 0x20, 0x18, 0x18, 0x19, 0x19, 0x19, + 0x19, 0x18, 0x18, 0x18, 0x18, 0x03, 0x02, 0x01, 0x00, 0x2F, 0x2F, + 0x30, 0x30, 0x31, 0x31, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18}, 0x20}, + {0xD6, {0x18, 0x18, 0x18, 0x18, 0x20, 0x21, 0x19, 0x19, 0x18, 0x18, 0x19, + 0x19, 0x18, 0x18, 0x18, 0x18, 0x00, 0x01, 0x02, 0x03, 0x2F, 0x2F, + 0x30, 0x30, 0x31, 0x31, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18}, 0x20}, + {0xD8, {0x0A, 0xBE, 0xFA, 0xA0, 0x0A, 0xBE, 0xFA, 0xA0}, 0x08}, + {0xBD, {0x01}, 0x01}, + {0xD8, {0x0F, 0xFF, 0xFF, 0xE0, 0x0F, 0xFF, 0xFF, 0xE0}, 0x08}, + {0xBD, {0x02}, 0x01}, + {0xD8, {0x0F, 0xFF, 0xFF, 0xE0, 0x0F, 0xFF, 0xFF, 0xE0}, 0x08}, + {0xBD, {0x00}, 0x01}, + {0xE0, {0x01, 0x35, 0x41, 0x3B, 0x79, 0x81, 0x8C, 0x85, 0x8E, + 0x95, 0x9B, 0xA0, 0xA4, 0xAB, 0xB1, 0xB3, 0xB7, 0xC5, 0xBD, 0xC5, + 0xB6, 0xC2, 0xC2, 0x62, 0x5D, 0x66, 0x73, 0x01, 0x35, 0x41, 0x3B, + 0x79, 0x81, 0x8C, 0x85, 0x8E, 0x95, 0x9B, 0xA0, 0xA4, 0xAB, 0xB1, + 0xB3, 0xB7, 0xB5, 0xBD, 0xC5, 0xB6, 0xC2, 0xC2, 0x62, 0x5D, 0x66, + 0x73}, 0x36}, + {0xB6, {0x97, 0x97}, 0x02}, + {0xCC, {0xC8}, 0x02}, + {0xBF, {0x40, 0x41, 0x50, 0x19}, 0x04}, + {0xC6, {0xFF, 0xF9}, 0x02}, + {0xC0, {0x25, 0x5A}, 0x02}, +}; + +/* Only the CMD1 User Command set is documented */ +static const struct khadas_ts050_panel_cmd ts050_init_code[] = { + /* Select Unknown CMD Page (Undocumented) */ + {0xff, {0xee}, 0x01}, + /* Reload CMD1: Don't reload default value to register */ + {0xfb, {0x01}, 0x01}, + {0x1f, {0x45}, 0x01}, + {0x24, {0x4f}, 0x01}, + {0x38, {0xc8}, 0x01}, + {0x39, {0x27}, 0x01}, + {0x1e, {0x77}, 0x01}, + {0x1d, {0x0f}, 0x01}, + {0x7e, {0x71}, 0x01}, + {0x7c, {0x03}, 0x01}, + {0xff, {0x00}, 0x01}, + {0xfb, {0x01}, 0x01}, + {0x35, {0x01}, 0x01}, + /* Select CMD2 Page0 (Undocumented) */ + {0xff, {0x01}, 0x01}, + /* Reload CMD1: Don't reload default value to register */ + {0xfb, {0x01}, 0x01}, + {0x00, {0x01}, 0x01}, + {0x01, {0x55}, 0x01}, + {0x02, {0x40}, 0x01}, + {0x05, {0x40}, 0x01}, + {0x06, {0x4a}, 0x01}, + {0x07, {0x24}, 0x01}, + {0x08, {0x0c}, 0x01}, + {0x0b, {0x7d}, 0x01}, + {0x0c, {0x7d}, 0x01}, + {0x0e, {0xb0}, 0x01}, + {0x0f, {0xae}, 0x01}, + {0x11, {0x10}, 0x01}, + {0x12, {0x10}, 0x01}, + {0x13, {0x03}, 0x01}, + {0x14, {0x4a}, 0x01}, + {0x15, {0x12}, 0x01}, + {0x16, {0x12}, 0x01}, + {0x18, {0x00}, 0x01}, + {0x19, {0x77}, 0x01}, + {0x1a, {0x55}, 0x01}, + {0x1b, {0x13}, 0x01}, + {0x1c, {0x00}, 0x01}, + {0x1d, {0x00}, 0x01}, + {0x1e, {0x13}, 0x01}, + {0x1f, {0x00}, 0x01}, + {0x23, {0x00}, 0x01}, + {0x24, {0x00}, 0x01}, + {0x25, {0x00}, 0x01}, + {0x26, {0x00}, 0x01}, + {0x27, {0x00}, 0x01}, + {0x28, {0x00}, 0x01}, + {0x35, {0x00}, 0x01}, + {0x66, {0x00}, 0x01}, + {0x58, {0x82}, 0x01}, + {0x59, {0x02}, 0x01}, + {0x5a, {0x02}, 0x01}, + {0x5b, {0x02}, 0x01}, + {0x5c, {0x82}, 0x01}, + {0x5d, {0x82}, 0x01}, + {0x5e, {0x02}, 0x01}, + {0x5f, {0x02}, 0x01}, + {0x72, {0x31}, 0x01}, + /* Select CMD2 Page4 (Undocumented) */ + {0xff, {0x05}, 0x01}, + /* Reload CMD1: Don't reload default value to register */ + {0xfb, {0x01}, 0x01}, + {0x00, {0x01}, 0x01}, + {0x01, {0x0b}, 0x01}, + {0x02, {0x0c}, 0x01}, + {0x03, {0x09}, 0x01}, + {0x04, {0x0a}, 0x01}, + {0x05, {0x00}, 0x01}, + {0x06, {0x0f}, 0x01}, + {0x07, {0x10}, 0x01}, + {0x08, {0x00}, 0x01}, + {0x09, {0x00}, 0x01}, + {0x0a, {0x00}, 0x01}, + {0x0b, {0x00}, 0x01}, + {0x0c, {0x00}, 0x01}, + {0x0d, {0x13}, 0x01}, + {0x0e, {0x15}, 0x01}, + {0x0f, {0x17}, 0x01}, + {0x10, {0x01}, 0x01}, + {0x11, {0x0b}, 0x01}, + {0x12, {0x0c}, 0x01}, + {0x13, {0x09}, 0x01}, + {0x14, {0x0a}, 0x01}, + {0x15, {0x00}, 0x01}, + {0x16, {0x0f}, 0x01}, + {0x17, {0x10}, 0x01}, + {0x18, {0x00}, 0x01}, + {0x19, {0x00}, 0x01}, + {0x1a, {0x00}, 0x01}, + {0x1b, {0x00}, 0x01}, + {0x1c, {0x00}, 0x01}, + {0x1d, {0x13}, 0x01}, + {0x1e, {0x15}, 0x01}, + {0x1f, {0x17}, 0x01}, + {0x20, {0x00}, 0x01}, + {0x21, {0x03}, 0x01}, + {0x22, {0x01}, 0x01}, + {0x23, {0x40}, 0x01}, + {0x24, {0x40}, 0x01}, + {0x25, {0xed}, 0x01}, + {0x29, {0x58}, 0x01}, + {0x2a, {0x12}, 0x01}, + {0x2b, {0x01}, 0x01}, + {0x4b, {0x06}, 0x01}, + {0x4c, {0x11}, 0x01}, + {0x4d, {0x20}, 0x01}, + {0x4e, {0x02}, 0x01}, + {0x4f, {0x02}, 0x01}, + {0x50, {0x20}, 0x01}, + {0x51, {0x61}, 0x01}, + {0x52, {0x01}, 0x01}, + {0x53, {0x63}, 0x01}, + {0x54, {0x77}, 0x01}, + {0x55, {0xed}, 0x01}, + {0x5b, {0x00}, 0x01}, + {0x5c, {0x00}, 0x01}, + {0x5d, {0x00}, 0x01}, + {0x5e, {0x00}, 0x01}, + {0x5f, {0x15}, 0x01}, + {0x60, {0x75}, 0x01}, + {0x61, {0x00}, 0x01}, + {0x62, {0x00}, 0x01}, + {0x63, {0x00}, 0x01}, + {0x64, {0x00}, 0x01}, + {0x65, {0x00}, 0x01}, + {0x66, {0x00}, 0x01}, + {0x67, {0x00}, 0x01}, + {0x68, {0x04}, 0x01}, + {0x69, {0x00}, 0x01}, + {0x6a, {0x00}, 0x01}, + {0x6c, {0x40}, 0x01}, + {0x75, {0x01}, 0x01}, + {0x76, {0x01}, 0x01}, + {0x7a, {0x80}, 0x01}, + {0x7b, {0xa3}, 0x01}, + {0x7c, {0xd8}, 0x01}, + {0x7d, {0x60}, 0x01}, + {0x7f, {0x15}, 0x01}, + {0x80, {0x81}, 0x01}, + {0x83, {0x05}, 0x01}, + {0x93, {0x08}, 0x01}, + {0x94, {0x10}, 0x01}, + {0x8a, {0x00}, 0x01}, + {0x9b, {0x0f}, 0x01}, + {0xea, {0xff}, 0x01}, + {0xec, {0x00}, 0x01}, + /* Select CMD2 Page0 (Undocumented) */ + {0xff, {0x01}, 0x01}, + /* Reload CMD1: Don't reload default value to register */ + {0xfb, {0x01}, 0x01}, + {0x75, {0x00}, 0x01}, + {0x76, {0xdf}, 0x01}, + {0x77, {0x00}, 0x01}, + {0x78, {0xe4}, 0x01}, + {0x79, {0x00}, 0x01}, + {0x7a, {0xed}, 0x01}, + {0x7b, {0x00}, 0x01}, + {0x7c, {0xf6}, 0x01}, + {0x7d, {0x00}, 0x01}, + {0x7e, {0xff}, 0x01}, + {0x7f, {0x01}, 0x01}, + {0x80, {0x07}, 0x01}, + {0x81, {0x01}, 0x01}, + {0x82, {0x10}, 0x01}, + {0x83, {0x01}, 0x01}, + {0x84, {0x18}, 0x01}, + {0x85, {0x01}, 0x01}, + {0x86, {0x20}, 0x01}, + {0x87, {0x01}, 0x01}, + {0x88, {0x3d}, 0x01}, + {0x89, {0x01}, 0x01}, + {0x8a, {0x56}, 0x01}, + {0x8b, {0x01}, 0x01}, + {0x8c, {0x84}, 0x01}, + {0x8d, {0x01}, 0x01}, + {0x8e, {0xab}, 0x01}, + {0x8f, {0x01}, 0x01}, + {0x90, {0xec}, 0x01}, + {0x91, {0x02}, 0x01}, + {0x92, {0x22}, 0x01}, + {0x93, {0x02}, 0x01}, + {0x94, {0x23}, 0x01}, + {0x95, {0x02}, 0x01}, + {0x96, {0x55}, 0x01}, + {0x97, {0x02}, 0x01}, + {0x98, {0x8b}, 0x01}, + {0x99, {0x02}, 0x01}, + {0x9a, {0xaf}, 0x01}, + {0x9b, {0x02}, 0x01}, + {0x9c, {0xdf}, 0x01}, + {0x9d, {0x03}, 0x01}, + {0x9e, {0x01}, 0x01}, + {0x9f, {0x03}, 0x01}, + {0xa0, {0x2c}, 0x01}, + {0xa2, {0x03}, 0x01}, + {0xa3, {0x39}, 0x01}, + {0xa4, {0x03}, 0x01}, + {0xa5, {0x47}, 0x01}, + {0xa6, {0x03}, 0x01}, + {0xa7, {0x56}, 0x01}, + {0xa9, {0x03}, 0x01}, + {0xaa, {0x66}, 0x01}, + {0xab, {0x03}, 0x01}, + {0xac, {0x76}, 0x01}, + {0xad, {0x03}, 0x01}, + {0xae, {0x85}, 0x01}, + {0xaf, {0x03}, 0x01}, + {0xb0, {0x90}, 0x01}, + {0xb1, {0x03}, 0x01}, + {0xb2, {0xcb}, 0x01}, + {0xb3, {0x00}, 0x01}, + {0xb4, {0xdf}, 0x01}, + {0xb5, {0x00}, 0x01}, + {0xb6, {0xe4}, 0x01}, + {0xb7, {0x00}, 0x01}, + {0xb8, {0xed}, 0x01}, + {0xb9, {0x00}, 0x01}, + {0xba, {0xf6}, 0x01}, + {0xbb, {0x00}, 0x01}, + {0xbc, {0xff}, 0x01}, + {0xbd, {0x01}, 0x01}, + {0xbe, {0x07}, 0x01}, + {0xbf, {0x01}, 0x01}, + {0xc0, {0x10}, 0x01}, + {0xc1, {0x01}, 0x01}, + {0xc2, {0x18}, 0x01}, + {0xc3, {0x01}, 0x01}, + {0xc4, {0x20}, 0x01}, + {0xc5, {0x01}, 0x01}, + {0xc6, {0x3d}, 0x01}, + {0xc7, {0x01}, 0x01}, + {0xc8, {0x56}, 0x01}, + {0xc9, {0x01}, 0x01}, + {0xca, {0x84}, 0x01}, + {0xcb, {0x01}, 0x01}, + {0xcc, {0xab}, 0x01}, + {0xcd, {0x01}, 0x01}, + {0xce, {0xec}, 0x01}, + {0xcf, {0x02}, 0x01}, + {0xd0, {0x22}, 0x01}, + {0xd1, {0x02}, 0x01}, + {0xd2, {0x23}, 0x01}, + {0xd3, {0x02}, 0x01}, + {0xd4, {0x55}, 0x01}, + {0xd5, {0x02}, 0x01}, + {0xd6, {0x8b}, 0x01}, + {0xd7, {0x02}, 0x01}, + {0xd8, {0xaf}, 0x01}, + {0xd9, {0x02}, 0x01}, + {0xda, {0xdf}, 0x01}, + {0xdb, {0x03}, 0x01}, + {0xdc, {0x01}, 0x01}, + {0xdd, {0x03}, 0x01}, + {0xde, {0x2c}, 0x01}, + {0xdf, {0x03}, 0x01}, + {0xe0, {0x39}, 0x01}, + {0xe1, {0x03}, 0x01}, + {0xe2, {0x47}, 0x01}, + {0xe3, {0x03}, 0x01}, + {0xe4, {0x56}, 0x01}, + {0xe5, {0x03}, 0x01}, + {0xe6, {0x66}, 0x01}, + {0xe7, {0x03}, 0x01}, + {0xe8, {0x76}, 0x01}, + {0xe9, {0x03}, 0x01}, + {0xea, {0x85}, 0x01}, + {0xeb, {0x03}, 0x01}, + {0xec, {0x90}, 0x01}, + {0xed, {0x03}, 0x01}, + {0xee, {0xcb}, 0x01}, + {0xef, {0x00}, 0x01}, + {0xf0, {0xbb}, 0x01}, + {0xf1, {0x00}, 0x01}, + {0xf2, {0xc0}, 0x01}, + {0xf3, {0x00}, 0x01}, + {0xf4, {0xcc}, 0x01}, + {0xf5, {0x00}, 0x01}, + {0xf6, {0xd6}, 0x01}, + {0xf7, {0x00}, 0x01}, + {0xf8, {0xe1}, 0x01}, + {0xf9, {0x00}, 0x01}, + {0xfa, {0xea}, 0x01}, + /* Select CMD2 Page2 (Undocumented) */ + {0xff, {0x02}, 0x01}, + /* Reload CMD1: Don't reload default value to register */ + {0xfb, {0x01}, 0x01}, + {0x00, {0x00}, 0x01}, + {0x01, {0xf4}, 0x01}, + {0x02, {0x00}, 0x01}, + {0x03, {0xef}, 0x01}, + {0x04, {0x01}, 0x01}, + {0x05, {0x07}, 0x01}, + {0x06, {0x01}, 0x01}, + {0x07, {0x28}, 0x01}, + {0x08, {0x01}, 0x01}, + {0x09, {0x44}, 0x01}, + {0x0a, {0x01}, 0x01}, + {0x0b, {0x76}, 0x01}, + {0x0c, {0x01}, 0x01}, + {0x0d, {0xa0}, 0x01}, + {0x0e, {0x01}, 0x01}, + {0x0f, {0xe7}, 0x01}, + {0x10, {0x02}, 0x01}, + {0x11, {0x1f}, 0x01}, + {0x12, {0x02}, 0x01}, + {0x13, {0x22}, 0x01}, + {0x14, {0x02}, 0x01}, + {0x15, {0x54}, 0x01}, + {0x16, {0x02}, 0x01}, + {0x17, {0x8b}, 0x01}, + {0x18, {0x02}, 0x01}, + {0x19, {0xaf}, 0x01}, + {0x1a, {0x02}, 0x01}, + {0x1b, {0xe0}, 0x01}, + {0x1c, {0x03}, 0x01}, + {0x1d, {0x01}, 0x01}, + {0x1e, {0x03}, 0x01}, + {0x1f, {0x2d}, 0x01}, + {0x20, {0x03}, 0x01}, + {0x21, {0x39}, 0x01}, + {0x22, {0x03}, 0x01}, + {0x23, {0x47}, 0x01}, + {0x24, {0x03}, 0x01}, + {0x25, {0x57}, 0x01}, + {0x26, {0x03}, 0x01}, + {0x27, {0x65}, 0x01}, + {0x28, {0x03}, 0x01}, + {0x29, {0x77}, 0x01}, + {0x2a, {0x03}, 0x01}, + {0x2b, {0x85}, 0x01}, + {0x2d, {0x03}, 0x01}, + {0x2f, {0x8f}, 0x01}, + {0x30, {0x03}, 0x01}, + {0x31, {0xcb}, 0x01}, + {0x32, {0x00}, 0x01}, + {0x33, {0xbb}, 0x01}, + {0x34, {0x00}, 0x01}, + {0x35, {0xc0}, 0x01}, + {0x36, {0x00}, 0x01}, + {0x37, {0xcc}, 0x01}, + {0x38, {0x00}, 0x01}, + {0x39, {0xd6}, 0x01}, + {0x3a, {0x00}, 0x01}, + {0x3b, {0xe1}, 0x01}, + {0x3d, {0x00}, 0x01}, + {0x3f, {0xea}, 0x01}, + {0x40, {0x00}, 0x01}, + {0x41, {0xf4}, 0x01}, + {0x42, {0x00}, 0x01}, + {0x43, {0xfe}, 0x01}, + {0x44, {0x01}, 0x01}, + {0x45, {0x07}, 0x01}, + {0x46, {0x01}, 0x01}, + {0x47, {0x28}, 0x01}, + {0x48, {0x01}, 0x01}, + {0x49, {0x44}, 0x01}, + {0x4a, {0x01}, 0x01}, + {0x4b, {0x76}, 0x01}, + {0x4c, {0x01}, 0x01}, + {0x4d, {0xa0}, 0x01}, + {0x4e, {0x01}, 0x01}, + {0x4f, {0xe7}, 0x01}, + {0x50, {0x02}, 0x01}, + {0x51, {0x1f}, 0x01}, + {0x52, {0x02}, 0x01}, + {0x53, {0x22}, 0x01}, + {0x54, {0x02}, 0x01}, + {0x55, {0x54}, 0x01}, + {0x56, {0x02}, 0x01}, + {0x58, {0x8b}, 0x01}, + {0x59, {0x02}, 0x01}, + {0x5a, {0xaf}, 0x01}, + {0x5b, {0x02}, 0x01}, + {0x5c, {0xe0}, 0x01}, + {0x5d, {0x03}, 0x01}, + {0x5e, {0x01}, 0x01}, + {0x5f, {0x03}, 0x01}, + {0x60, {0x2d}, 0x01}, + {0x61, {0x03}, 0x01}, + {0x62, {0x39}, 0x01}, + {0x63, {0x03}, 0x01}, + {0x64, {0x47}, 0x01}, + {0x65, {0x03}, 0x01}, + {0x66, {0x57}, 0x01}, + {0x67, {0x03}, 0x01}, + {0x68, {0x65}, 0x01}, + {0x69, {0x03}, 0x01}, + {0x6a, {0x77}, 0x01}, + {0x6b, {0x03}, 0x01}, + {0x6c, {0x85}, 0x01}, + {0x6d, {0x03}, 0x01}, + {0x6e, {0x8f}, 0x01}, + {0x6f, {0x03}, 0x01}, + {0x70, {0xcb}, 0x01}, + {0x71, {0x00}, 0x01}, + {0x72, {0x00}, 0x01}, + {0x73, {0x00}, 0x01}, + {0x74, {0x21}, 0x01}, + {0x75, {0x00}, 0x01}, + {0x76, {0x4c}, 0x01}, + {0x77, {0x00}, 0x01}, + {0x78, {0x6b}, 0x01}, + {0x79, {0x00}, 0x01}, + {0x7a, {0x85}, 0x01}, + {0x7b, {0x00}, 0x01}, + {0x7c, {0x9a}, 0x01}, + {0x7d, {0x00}, 0x01}, + {0x7e, {0xad}, 0x01}, + {0x7f, {0x00}, 0x01}, + {0x80, {0xbe}, 0x01}, + {0x81, {0x00}, 0x01}, + {0x82, {0xcd}, 0x01}, + {0x83, {0x01}, 0x01}, + {0x84, {0x01}, 0x01}, + {0x85, {0x01}, 0x01}, + {0x86, {0x29}, 0x01}, + {0x87, {0x01}, 0x01}, + {0x88, {0x68}, 0x01}, + {0x89, {0x01}, 0x01}, + {0x8a, {0x98}, 0x01}, + {0x8b, {0x01}, 0x01}, + {0x8c, {0xe5}, 0x01}, + {0x8d, {0x02}, 0x01}, + {0x8e, {0x1e}, 0x01}, + {0x8f, {0x02}, 0x01}, + {0x90, {0x30}, 0x01}, + {0x91, {0x02}, 0x01}, + {0x92, {0x52}, 0x01}, + {0x93, {0x02}, 0x01}, + {0x94, {0x88}, 0x01}, + {0x95, {0x02}, 0x01}, + {0x96, {0xaa}, 0x01}, + {0x97, {0x02}, 0x01}, + {0x98, {0xd7}, 0x01}, + {0x99, {0x02}, 0x01}, + {0x9a, {0xf7}, 0x01}, + {0x9b, {0x03}, 0x01}, + {0x9c, {0x21}, 0x01}, + {0x9d, {0x03}, 0x01}, + {0x9e, {0x2e}, 0x01}, + {0x9f, {0x03}, 0x01}, + {0xa0, {0x3d}, 0x01}, + {0xa2, {0x03}, 0x01}, + {0xa3, {0x4c}, 0x01}, + {0xa4, {0x03}, 0x01}, + {0xa5, {0x5e}, 0x01}, + {0xa6, {0x03}, 0x01}, + {0xa7, {0x71}, 0x01}, + {0xa9, {0x03}, 0x01}, + {0xaa, {0x86}, 0x01}, + {0xab, {0x03}, 0x01}, + {0xac, {0x94}, 0x01}, + {0xad, {0x03}, 0x01}, + {0xae, {0xfa}, 0x01}, + {0xaf, {0x00}, 0x01}, + {0xb0, {0x00}, 0x01}, + {0xb1, {0x00}, 0x01}, + {0xb2, {0x21}, 0x01}, + {0xb3, {0x00}, 0x01}, + {0xb4, {0x4c}, 0x01}, + {0xb5, {0x00}, 0x01}, + {0xb6, {0x6b}, 0x01}, + {0xb7, {0x00}, 0x01}, + {0xb8, {0x85}, 0x01}, + {0xb9, {0x00}, 0x01}, + {0xba, {0x9a}, 0x01}, + {0xbb, {0x00}, 0x01}, + {0xbc, {0xad}, 0x01}, + {0xbd, {0x00}, 0x01}, + {0xbe, {0xbe}, 0x01}, + {0xbf, {0x00}, 0x01}, + {0xc0, {0xcd}, 0x01}, + {0xc1, {0x01}, 0x01}, + {0xc2, {0x01}, 0x01}, + {0xc3, {0x01}, 0x01}, + {0xc4, {0x29}, 0x01}, + {0xc5, {0x01}, 0x01}, + {0xc6, {0x68}, 0x01}, + {0xc7, {0x01}, 0x01}, + {0xc8, {0x98}, 0x01}, + {0xc9, {0x01}, 0x01}, + {0xca, {0xe5}, 0x01}, + {0xcb, {0x02}, 0x01}, + {0xcc, {0x1e}, 0x01}, + {0xcd, {0x02}, 0x01}, + {0xce, {0x20}, 0x01}, + {0xcf, {0x02}, 0x01}, + {0xd0, {0x52}, 0x01}, + {0xd1, {0x02}, 0x01}, + {0xd2, {0x88}, 0x01}, + {0xd3, {0x02}, 0x01}, + {0xd4, {0xaa}, 0x01}, + {0xd5, {0x02}, 0x01}, + {0xd6, {0xd7}, 0x01}, + {0xd7, {0x02}, 0x01}, + {0xd8, {0xf7}, 0x01}, + {0xd9, {0x03}, 0x01}, + {0xda, {0x21}, 0x01}, + {0xdb, {0x03}, 0x01}, + {0xdc, {0x2e}, 0x01}, + {0xdd, {0x03}, 0x01}, + {0xde, {0x3d}, 0x01}, + {0xdf, {0x03}, 0x01}, + {0xe0, {0x4c}, 0x01}, + {0xe1, {0x03}, 0x01}, + {0xe2, {0x5e}, 0x01}, + {0xe3, {0x03}, 0x01}, + {0xe4, {0x71}, 0x01}, + {0xe5, {0x03}, 0x01}, + {0xe6, {0x86}, 0x01}, + {0xe7, {0x03}, 0x01}, + {0xe8, {0x94}, 0x01}, + {0xe9, {0x03}, 0x01}, + {0xea, {0xfa}, 0x01}, + /* Select CMD2 Page0 (Undocumented) */ + {0xff, {0x01}, 0x01}, + /* Reload CMD1: Don't reload default value to register */ + {0xfb, {0x01}, 0x01}, + /* Select CMD2 Page1 (Undocumented) */ + {0xff, {0x02}, 0x01}, + /* Reload CMD1: Don't reload default value to register */ + {0xfb, {0x01}, 0x01}, + /* Select CMD2 Page3 (Undocumented) */ + {0xff, {0x04}, 0x01}, + /* Reload CMD1: Don't reload default value to register */ + {0xfb, {0x01}, 0x01}, + /* Select CMD1 */ + {0xff, {0x00}, 0x01}, + {0xd3, {0x22}, 0x01}, /* RGBMIPICTRL: VSYNC back porch = 34 */ + {0xd4, {0x04}, 0x01}, /* RGBMIPICTRL: VSYNC front porch = 4 */ +}; + +static struct khadas_ts050_panel_data ts050_panel_data = { + .init_code = (struct khadas_ts050_panel_cmd *)ts050_init_code, + .len = ARRAY_SIZE(ts050_init_code) +}; + +static struct khadas_ts050_panel_data ts050v2_panel_data = { + .init_code = (struct khadas_ts050_panel_cmd *)ts050v2_init_code, + .len = ARRAY_SIZE(ts050v2_init_code) +}; + +static inline +struct khadas_ts050_panel *to_khadas_ts050_panel(struct drm_panel *panel) +{ + return container_of(panel, struct khadas_ts050_panel, base); +} + +static int khadas_ts050_panel_prepare(struct drm_panel *panel) +{ + struct khadas_ts050_panel *khadas_ts050 = to_khadas_ts050_panel(panel); + unsigned int i; + int err; + + gpiod_set_value_cansleep(khadas_ts050->enable_gpio, 0); + + err = regulator_enable(khadas_ts050->supply); + if (err < 0) + return err; + + gpiod_set_value_cansleep(khadas_ts050->enable_gpio, 1); + + msleep(60); + + gpiod_set_value_cansleep(khadas_ts050->reset_gpio, 1); + + usleep_range(10000, 11000); + + gpiod_set_value_cansleep(khadas_ts050->reset_gpio, 0); + + /* Select CMD2 page 4 (Undocumented) */ + mipi_dsi_dcs_write(khadas_ts050->link, 0xff, (u8[]){ 0x05 }, 1); + + /* Reload CMD1: Don't reload default value to register */ + mipi_dsi_dcs_write(khadas_ts050->link, 0xfb, (u8[]){ 0x01 }, 1); + + mipi_dsi_dcs_write(khadas_ts050->link, 0xc5, (u8[]){ 0x01 }, 1); + + msleep(100); + + for (i = 0; i < khadas_ts050->panel_data->len; i++) { + err = mipi_dsi_dcs_write(khadas_ts050->link, + khadas_ts050->panel_data->init_code[i].cmd, + &khadas_ts050->panel_data->init_code[i].data, + khadas_ts050->panel_data->init_code[i].size); + if (err < 0) { + dev_err(panel->dev, "failed write cmds: %d\n", err); + goto poweroff; + } + } + + err = mipi_dsi_dcs_exit_sleep_mode(khadas_ts050->link); + if (err < 0) { + dev_err(panel->dev, "failed to exit sleep mode: %d\n", err); + goto poweroff; + } + + msleep(120); + + /* Select CMD1 */ + mipi_dsi_dcs_write(khadas_ts050->link, 0xff, (u8[]){ 0x00 }, 1); + + err = mipi_dsi_dcs_set_tear_on(khadas_ts050->link, + MIPI_DSI_DCS_TEAR_MODE_VBLANK); + if (err < 0) { + dev_err(panel->dev, "failed to set tear on: %d\n", err); + goto poweroff; + } + + err = mipi_dsi_dcs_set_display_on(khadas_ts050->link); + if (err < 0) { + dev_err(panel->dev, "failed to set display on: %d\n", err); + goto poweroff; + } + + usleep_range(10000, 11000); + + return 0; + +poweroff: + gpiod_set_value_cansleep(khadas_ts050->enable_gpio, 0); + gpiod_set_value_cansleep(khadas_ts050->reset_gpio, 1); + + regulator_disable(khadas_ts050->supply); + + return err; +} + +static int khadas_ts050_panel_unprepare(struct drm_panel *panel) +{ + struct khadas_ts050_panel *khadas_ts050 = to_khadas_ts050_panel(panel); + int err; + + err = mipi_dsi_dcs_enter_sleep_mode(khadas_ts050->link); + if (err < 0) + dev_err(panel->dev, "failed to enter sleep mode: %d\n", err); + + msleep(150); + + gpiod_set_value_cansleep(khadas_ts050->enable_gpio, 0); + gpiod_set_value_cansleep(khadas_ts050->reset_gpio, 1); + + err = regulator_disable(khadas_ts050->supply); + if (err < 0) + return err; + + return 0; +} + +static int khadas_ts050_panel_disable(struct drm_panel *panel) +{ + struct khadas_ts050_panel *khadas_ts050 = to_khadas_ts050_panel(panel); + int err; + + err = mipi_dsi_dcs_set_display_off(khadas_ts050->link); + if (err < 0) + dev_err(panel->dev, "failed to set display off: %d\n", err); + + usleep_range(10000, 11000); + + return 0; +} + +static const struct drm_display_mode default_mode = { + .clock = 160000, + .hdisplay = 1080, + .hsync_start = 1080 + 117, + .hsync_end = 1080 + 117 + 5, + .htotal = 1080 + 117 + 5 + 160, + .vdisplay = 1920, + .vsync_start = 1920 + 4, + .vsync_end = 1920 + 4 + 3, + .vtotal = 1920 + 4 + 3 + 31, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, +}; + +static int khadas_ts050_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) { + dev_err(panel->dev, "failed to add mode %ux%u@%u\n", + default_mode.hdisplay, default_mode.vdisplay, + drm_mode_vrefresh(&default_mode)); + return -ENOMEM; + } + + drm_mode_set_name(mode); + + drm_mode_probed_add(connector, mode); + + connector->display_info.width_mm = 64; + connector->display_info.height_mm = 118; + connector->display_info.bpc = 8; + + return 1; +} + +static const struct drm_panel_funcs khadas_ts050_panel_funcs = { + .prepare = khadas_ts050_panel_prepare, + .unprepare = khadas_ts050_panel_unprepare, + .disable = khadas_ts050_panel_disable, + .get_modes = khadas_ts050_panel_get_modes, +}; + +static const struct of_device_id khadas_ts050_of_match[] = { + { .compatible = "khadas,ts050", .data = &ts050_panel_data, }, + { .compatible = "khadas,ts050v2", .data = &ts050v2_panel_data, }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, khadas_ts050_of_match); + +static int khadas_ts050_panel_add(struct khadas_ts050_panel *khadas_ts050) +{ + struct device *dev = &khadas_ts050->link->dev; + int err; + + khadas_ts050->supply = devm_regulator_get(dev, "power"); + if (IS_ERR(khadas_ts050->supply)) + return dev_err_probe(dev, PTR_ERR(khadas_ts050->supply), + "failed to get power supply"); + + khadas_ts050->reset_gpio = devm_gpiod_get(dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(khadas_ts050->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(khadas_ts050->reset_gpio), + "failed to get reset gpio"); + + khadas_ts050->enable_gpio = devm_gpiod_get(dev, "enable", + GPIOD_OUT_HIGH); + if (IS_ERR(khadas_ts050->enable_gpio)) + return dev_err_probe(dev, PTR_ERR(khadas_ts050->enable_gpio), + "failed to get enable gpio"); + + err = drm_panel_of_backlight(&khadas_ts050->base); + if (err) + return err; + + drm_panel_add(&khadas_ts050->base); + + return 0; +} + +static int khadas_ts050_panel_probe(struct mipi_dsi_device *dsi) +{ + struct khadas_ts050_panel *khadas_ts050; + int err; + + const void *data = of_device_get_match_data(&dsi->dev); + + if (!data) { + dev_err(&dsi->dev, "No matching data\n"); + return -ENODEV; + } + + 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_NO_EOT_PACKET; + + khadas_ts050 = devm_drm_panel_alloc(&dsi->dev, __typeof(*khadas_ts050), + base, &khadas_ts050_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + + if (IS_ERR(khadas_ts050)) + return PTR_ERR(khadas_ts050); + + khadas_ts050->panel_data = (struct khadas_ts050_panel_data *)data; + mipi_dsi_set_drvdata(dsi, khadas_ts050); + khadas_ts050->link = dsi; + + err = khadas_ts050_panel_add(khadas_ts050); + if (err < 0) + return err; + + err = mipi_dsi_attach(dsi); + if (err) + drm_panel_remove(&khadas_ts050->base); + + return err; +} + +static void khadas_ts050_panel_remove(struct mipi_dsi_device *dsi) +{ + struct khadas_ts050_panel *khadas_ts050 = mipi_dsi_get_drvdata(dsi); + int err; + + err = mipi_dsi_detach(dsi); + if (err < 0) + dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", err); + + drm_panel_remove(&khadas_ts050->base); +} + +static struct mipi_dsi_driver khadas_ts050_panel_driver = { + .driver = { + .name = "panel-khadas-ts050", + .of_match_table = khadas_ts050_of_match, + }, + .probe = khadas_ts050_panel_probe, + .remove = khadas_ts050_panel_remove, +}; +module_mipi_dsi_driver(khadas_ts050_panel_driver); + +MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); +MODULE_DESCRIPTION("Khadas TS050 panel driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-kingdisplay-kd097d04.c b/drivers/gpu/drm/panel/panel-kingdisplay-kd097d04.c new file mode 100644 index 000000000000..893af9b16756 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-kingdisplay-kd097d04.c @@ -0,0 +1,412 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2017, Fuzhou Rockchip Electronics Co., Ltd + */ + +#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> + +struct kingdisplay_panel { + struct drm_panel base; + struct mipi_dsi_device *link; + + struct regulator *supply; + struct gpio_desc *enable_gpio; +}; + +struct kingdisplay_panel_cmd { + char cmd; + char data; +}; + +/* + * According to the discussion on + * https://review.coreboot.org/#/c/coreboot/+/22472/ + * the panel init array is not part of the panels datasheet but instead + * just came in this form from the panel vendor. + */ +static const struct kingdisplay_panel_cmd init_code[] = { + /* voltage setting */ + { 0xB0, 0x00 }, + { 0xB2, 0x02 }, + { 0xB3, 0x11 }, + { 0xB4, 0x00 }, + { 0xB6, 0x80 }, + /* VCOM disable */ + { 0xB7, 0x02 }, + { 0xB8, 0x80 }, + { 0xBA, 0x43 }, + /* VCOM setting */ + { 0xBB, 0x53 }, + /* VSP setting */ + { 0xBC, 0x0A }, + /* VSN setting */ + { 0xBD, 0x4A }, + /* VGH setting */ + { 0xBE, 0x2F }, + /* VGL setting */ + { 0xBF, 0x1A }, + { 0xF0, 0x39 }, + { 0xF1, 0x22 }, + /* Gamma setting */ + { 0xB0, 0x02 }, + { 0xC0, 0x00 }, + { 0xC1, 0x01 }, + { 0xC2, 0x0B }, + { 0xC3, 0x15 }, + { 0xC4, 0x22 }, + { 0xC5, 0x11 }, + { 0xC6, 0x15 }, + { 0xC7, 0x19 }, + { 0xC8, 0x1A }, + { 0xC9, 0x16 }, + { 0xCA, 0x18 }, + { 0xCB, 0x13 }, + { 0xCC, 0x18 }, + { 0xCD, 0x13 }, + { 0xCE, 0x1C }, + { 0xCF, 0x19 }, + { 0xD0, 0x21 }, + { 0xD1, 0x2C }, + { 0xD2, 0x2F }, + { 0xD3, 0x30 }, + { 0xD4, 0x19 }, + { 0xD5, 0x1F }, + { 0xD6, 0x00 }, + { 0xD7, 0x01 }, + { 0xD8, 0x0B }, + { 0xD9, 0x15 }, + { 0xDA, 0x22 }, + { 0xDB, 0x11 }, + { 0xDC, 0x15 }, + { 0xDD, 0x19 }, + { 0xDE, 0x1A }, + { 0xDF, 0x16 }, + { 0xE0, 0x18 }, + { 0xE1, 0x13 }, + { 0xE2, 0x18 }, + { 0xE3, 0x13 }, + { 0xE4, 0x1C }, + { 0xE5, 0x19 }, + { 0xE6, 0x21 }, + { 0xE7, 0x2C }, + { 0xE8, 0x2F }, + { 0xE9, 0x30 }, + { 0xEA, 0x19 }, + { 0xEB, 0x1F }, + /* GOA MUX setting */ + { 0xB0, 0x01 }, + { 0xC0, 0x10 }, + { 0xC1, 0x0F }, + { 0xC2, 0x0E }, + { 0xC3, 0x0D }, + { 0xC4, 0x0C }, + { 0xC5, 0x0B }, + { 0xC6, 0x0A }, + { 0xC7, 0x09 }, + { 0xC8, 0x08 }, + { 0xC9, 0x07 }, + { 0xCA, 0x06 }, + { 0xCB, 0x05 }, + { 0xCC, 0x00 }, + { 0xCD, 0x01 }, + { 0xCE, 0x02 }, + { 0xCF, 0x03 }, + { 0xD0, 0x04 }, + { 0xD6, 0x10 }, + { 0xD7, 0x0F }, + { 0xD8, 0x0E }, + { 0xD9, 0x0D }, + { 0xDA, 0x0C }, + { 0xDB, 0x0B }, + { 0xDC, 0x0A }, + { 0xDD, 0x09 }, + { 0xDE, 0x08 }, + { 0xDF, 0x07 }, + { 0xE0, 0x06 }, + { 0xE1, 0x05 }, + { 0xE2, 0x00 }, + { 0xE3, 0x01 }, + { 0xE4, 0x02 }, + { 0xE5, 0x03 }, + { 0xE6, 0x04 }, + { 0xE7, 0x00 }, + { 0xEC, 0xC0 }, + /* GOA timing setting */ + { 0xB0, 0x03 }, + { 0xC0, 0x01 }, + { 0xC2, 0x6F }, + { 0xC3, 0x6F }, + { 0xC5, 0x36 }, + { 0xC8, 0x08 }, + { 0xC9, 0x04 }, + { 0xCA, 0x41 }, + { 0xCC, 0x43 }, + { 0xCF, 0x60 }, + { 0xD2, 0x04 }, + { 0xD3, 0x04 }, + { 0xD4, 0x03 }, + { 0xD5, 0x02 }, + { 0xD6, 0x01 }, + { 0xD7, 0x00 }, + { 0xDB, 0x01 }, + { 0xDE, 0x36 }, + { 0xE6, 0x6F }, + { 0xE7, 0x6F }, + /* GOE setting */ + { 0xB0, 0x06 }, + { 0xB8, 0xA5 }, + { 0xC0, 0xA5 }, + { 0xD5, 0x3F }, +}; + +static inline +struct kingdisplay_panel *to_kingdisplay_panel(struct drm_panel *panel) +{ + return container_of(panel, struct kingdisplay_panel, base); +} + +static int kingdisplay_panel_disable(struct drm_panel *panel) +{ + struct kingdisplay_panel *kingdisplay = to_kingdisplay_panel(panel); + int err; + + err = mipi_dsi_dcs_set_display_off(kingdisplay->link); + if (err < 0) + dev_err(panel->dev, "failed to set display off: %d\n", err); + + return 0; +} + +static int kingdisplay_panel_unprepare(struct drm_panel *panel) +{ + struct kingdisplay_panel *kingdisplay = to_kingdisplay_panel(panel); + int err; + + err = mipi_dsi_dcs_enter_sleep_mode(kingdisplay->link); + if (err < 0) { + dev_err(panel->dev, "failed to enter sleep mode: %d\n", err); + return err; + } + + /* T15: 120ms */ + msleep(120); + + gpiod_set_value_cansleep(kingdisplay->enable_gpio, 0); + + err = regulator_disable(kingdisplay->supply); + if (err < 0) + return err; + + return 0; +} + +static int kingdisplay_panel_prepare(struct drm_panel *panel) +{ + struct kingdisplay_panel *kingdisplay = to_kingdisplay_panel(panel); + int err, regulator_err; + unsigned int i; + + gpiod_set_value_cansleep(kingdisplay->enable_gpio, 0); + + err = regulator_enable(kingdisplay->supply); + if (err < 0) + return err; + + /* T2: 15ms */ + usleep_range(15000, 16000); + + gpiod_set_value_cansleep(kingdisplay->enable_gpio, 1); + + /* T4: 15ms */ + usleep_range(15000, 16000); + + for (i = 0; i < ARRAY_SIZE(init_code); i++) { + err = mipi_dsi_generic_write(kingdisplay->link, &init_code[i], + sizeof(struct kingdisplay_panel_cmd)); + if (err < 0) { + dev_err(panel->dev, "failed write init cmds: %d\n", err); + goto poweroff; + } + } + + err = mipi_dsi_dcs_exit_sleep_mode(kingdisplay->link); + if (err < 0) { + dev_err(panel->dev, "failed to exit sleep mode: %d\n", err); + goto poweroff; + } + + /* T6: 120ms */ + msleep(120); + + err = mipi_dsi_dcs_set_display_on(kingdisplay->link); + if (err < 0) { + dev_err(panel->dev, "failed to set display on: %d\n", err); + goto poweroff; + } + + /* T7: 10ms */ + usleep_range(10000, 11000); + + return 0; + +poweroff: + gpiod_set_value_cansleep(kingdisplay->enable_gpio, 0); + + regulator_err = regulator_disable(kingdisplay->supply); + if (regulator_err) + dev_err(panel->dev, "failed to disable regulator: %d\n", regulator_err); + + return err; +} + +static const struct drm_display_mode default_mode = { + .clock = 229000, + .hdisplay = 1536, + .hsync_start = 1536 + 100, + .hsync_end = 1536 + 100 + 24, + .htotal = 1536 + 100 + 24 + 100, + .vdisplay = 2048, + .vsync_start = 2048 + 95, + .vsync_end = 2048 + 95 + 2, + .vtotal = 2048 + 95 + 2 + 23, +}; + +static int kingdisplay_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) { + dev_err(panel->dev, "failed to add mode %ux%u@%u\n", + default_mode.hdisplay, default_mode.vdisplay, + drm_mode_vrefresh(&default_mode)); + return -ENOMEM; + } + + drm_mode_set_name(mode); + + drm_mode_probed_add(connector, mode); + + connector->display_info.width_mm = 147; + connector->display_info.height_mm = 196; + connector->display_info.bpc = 8; + + return 1; +} + +static const struct drm_panel_funcs kingdisplay_panel_funcs = { + .disable = kingdisplay_panel_disable, + .unprepare = kingdisplay_panel_unprepare, + .prepare = kingdisplay_panel_prepare, + .get_modes = kingdisplay_panel_get_modes, +}; + +static const struct of_device_id kingdisplay_of_match[] = { + { .compatible = "kingdisplay,kd097d04", }, + { } +}; +MODULE_DEVICE_TABLE(of, kingdisplay_of_match); + +static int kingdisplay_panel_add(struct kingdisplay_panel *kingdisplay) +{ + struct device *dev = &kingdisplay->link->dev; + int err; + + kingdisplay->supply = devm_regulator_get(dev, "power"); + if (IS_ERR(kingdisplay->supply)) + return PTR_ERR(kingdisplay->supply); + + kingdisplay->enable_gpio = devm_gpiod_get_optional(dev, "enable", + GPIOD_OUT_HIGH); + if (IS_ERR(kingdisplay->enable_gpio)) { + err = PTR_ERR(kingdisplay->enable_gpio); + dev_dbg(dev, "failed to get enable gpio: %d\n", err); + kingdisplay->enable_gpio = NULL; + } + + err = drm_panel_of_backlight(&kingdisplay->base); + if (err) + return err; + + drm_panel_add(&kingdisplay->base); + + return 0; +} + +static void kingdisplay_panel_del(struct kingdisplay_panel *kingdisplay) +{ + drm_panel_remove(&kingdisplay->base); +} + +static int kingdisplay_panel_probe(struct mipi_dsi_device *dsi) +{ + struct kingdisplay_panel *kingdisplay; + int err; + + 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_NO_EOT_PACKET; + + kingdisplay = devm_drm_panel_alloc(&dsi->dev, __typeof(*kingdisplay), base, + &kingdisplay_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + + if (IS_ERR(kingdisplay)) + return PTR_ERR(kingdisplay); + + mipi_dsi_set_drvdata(dsi, kingdisplay); + kingdisplay->link = dsi; + + err = kingdisplay_panel_add(kingdisplay); + if (err < 0) + return err; + + err = mipi_dsi_attach(dsi); + if (err < 0) { + kingdisplay_panel_del(kingdisplay); + return err; + } + + return 0; +} + +static void kingdisplay_panel_remove(struct mipi_dsi_device *dsi) +{ + struct kingdisplay_panel *kingdisplay = mipi_dsi_get_drvdata(dsi); + int err; + + err = mipi_dsi_detach(dsi); + if (err < 0) + dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", err); + + kingdisplay_panel_del(kingdisplay); +} + +static struct mipi_dsi_driver kingdisplay_panel_driver = { + .driver = { + .name = "panel-kingdisplay-kd097d04", + .of_match_table = kingdisplay_of_match, + }, + .probe = kingdisplay_panel_probe, + .remove = kingdisplay_panel_remove, +}; +module_mipi_dsi_driver(kingdisplay_panel_driver); + +MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>"); +MODULE_AUTHOR("Nickey Yang <nickey.yang@rock-chips.com>"); +MODULE_DESCRIPTION("kingdisplay KD097D04 panel driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-leadtek-ltk050h3146w.c b/drivers/gpu/drm/panel/panel-leadtek-ltk050h3146w.c new file mode 100644 index 000000000000..0856df5a6ee2 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-leadtek-ltk050h3146w.c @@ -0,0 +1,639 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 Theobroma Systems Design und Consulting GmbH + */ + +#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> + +#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> + +struct ltk050h3146w_cmd { + char cmd; + char data; +}; + +struct ltk050h3146w; +struct ltk050h3146w_desc { + const unsigned long mode_flags; + const struct drm_display_mode *mode; + void (*init)(struct mipi_dsi_multi_context *dsi_ctx); +}; + +struct ltk050h3146w { + struct device *dev; + struct drm_panel panel; + struct gpio_desc *reset_gpio; + struct regulator *vci; + struct regulator *iovcc; + const struct ltk050h3146w_desc *panel_desc; +}; + +static const struct ltk050h3146w_cmd page1_cmds[] = { + { 0x22, 0x0A }, /* BGR SS GS */ + { 0x31, 0x00 }, /* column inversion */ + { 0x53, 0xA2 }, /* VCOM1 */ + { 0x55, 0xA2 }, /* VCOM2 */ + { 0x50, 0x81 }, /* VREG1OUT=5V */ + { 0x51, 0x85 }, /* VREG2OUT=-5V */ + { 0x62, 0x0D }, /* EQT Time setting */ +/* + * The vendor init selected page 1 here _again_ + * Is this supposed to be page 2? + */ + { 0xA0, 0x00 }, + { 0xA1, 0x1A }, + { 0xA2, 0x28 }, + { 0xA3, 0x13 }, + { 0xA4, 0x16 }, + { 0xA5, 0x29 }, + { 0xA6, 0x1D }, + { 0xA7, 0x1E }, + { 0xA8, 0x84 }, + { 0xA9, 0x1C }, + { 0xAA, 0x28 }, + { 0xAB, 0x75 }, + { 0xAC, 0x1A }, + { 0xAD, 0x19 }, + { 0xAE, 0x4D }, + { 0xAF, 0x22 }, + { 0xB0, 0x28 }, + { 0xB1, 0x54 }, + { 0xB2, 0x66 }, + { 0xB3, 0x39 }, + { 0xC0, 0x00 }, + { 0xC1, 0x1A }, + { 0xC2, 0x28 }, + { 0xC3, 0x13 }, + { 0xC4, 0x16 }, + { 0xC5, 0x29 }, + { 0xC6, 0x1D }, + { 0xC7, 0x1E }, + { 0xC8, 0x84 }, + { 0xC9, 0x1C }, + { 0xCA, 0x28 }, + { 0xCB, 0x75 }, + { 0xCC, 0x1A }, + { 0xCD, 0x19 }, + { 0xCE, 0x4D }, + { 0xCF, 0x22 }, + { 0xD0, 0x28 }, + { 0xD1, 0x54 }, + { 0xD2, 0x66 }, + { 0xD3, 0x39 }, +}; + +static const struct ltk050h3146w_cmd page3_cmds[] = { + { 0x01, 0x00 }, + { 0x02, 0x00 }, + { 0x03, 0x73 }, + { 0x04, 0x00 }, + { 0x05, 0x00 }, + { 0x06, 0x0a }, + { 0x07, 0x00 }, + { 0x08, 0x00 }, + { 0x09, 0x01 }, + { 0x0a, 0x00 }, + { 0x0b, 0x00 }, + { 0x0c, 0x01 }, + { 0x0d, 0x00 }, + { 0x0e, 0x00 }, + { 0x0f, 0x1d }, + { 0x10, 0x1d }, + { 0x11, 0x00 }, + { 0x12, 0x00 }, + { 0x13, 0x00 }, + { 0x14, 0x00 }, + { 0x15, 0x00 }, + { 0x16, 0x00 }, + { 0x17, 0x00 }, + { 0x18, 0x00 }, + { 0x19, 0x00 }, + { 0x1a, 0x00 }, + { 0x1b, 0x00 }, + { 0x1c, 0x00 }, + { 0x1d, 0x00 }, + { 0x1e, 0x40 }, + { 0x1f, 0x80 }, + { 0x20, 0x06 }, + { 0x21, 0x02 }, + { 0x22, 0x00 }, + { 0x23, 0x00 }, + { 0x24, 0x00 }, + { 0x25, 0x00 }, + { 0x26, 0x00 }, + { 0x27, 0x00 }, + { 0x28, 0x33 }, + { 0x29, 0x03 }, + { 0x2a, 0x00 }, + { 0x2b, 0x00 }, + { 0x2c, 0x00 }, + { 0x2d, 0x00 }, + { 0x2e, 0x00 }, + { 0x2f, 0x00 }, + { 0x30, 0x00 }, + { 0x31, 0x00 }, + { 0x32, 0x00 }, + { 0x33, 0x00 }, + { 0x34, 0x04 }, + { 0x35, 0x00 }, + { 0x36, 0x00 }, + { 0x37, 0x00 }, + { 0x38, 0x3C }, + { 0x39, 0x35 }, + { 0x3A, 0x01 }, + { 0x3B, 0x40 }, + { 0x3C, 0x00 }, + { 0x3D, 0x01 }, + { 0x3E, 0x00 }, + { 0x3F, 0x00 }, + { 0x40, 0x00 }, + { 0x41, 0x88 }, + { 0x42, 0x00 }, + { 0x43, 0x00 }, + { 0x44, 0x1F }, + { 0x50, 0x01 }, + { 0x51, 0x23 }, + { 0x52, 0x45 }, + { 0x53, 0x67 }, + { 0x54, 0x89 }, + { 0x55, 0xab }, + { 0x56, 0x01 }, + { 0x57, 0x23 }, + { 0x58, 0x45 }, + { 0x59, 0x67 }, + { 0x5a, 0x89 }, + { 0x5b, 0xab }, + { 0x5c, 0xcd }, + { 0x5d, 0xef }, + { 0x5e, 0x11 }, + { 0x5f, 0x01 }, + { 0x60, 0x00 }, + { 0x61, 0x15 }, + { 0x62, 0x14 }, + { 0x63, 0x0E }, + { 0x64, 0x0F }, + { 0x65, 0x0C }, + { 0x66, 0x0D }, + { 0x67, 0x06 }, + { 0x68, 0x02 }, + { 0x69, 0x07 }, + { 0x6a, 0x02 }, + { 0x6b, 0x02 }, + { 0x6c, 0x02 }, + { 0x6d, 0x02 }, + { 0x6e, 0x02 }, + { 0x6f, 0x02 }, + { 0x70, 0x02 }, + { 0x71, 0x02 }, + { 0x72, 0x02 }, + { 0x73, 0x02 }, + { 0x74, 0x02 }, + { 0x75, 0x01 }, + { 0x76, 0x00 }, + { 0x77, 0x14 }, + { 0x78, 0x15 }, + { 0x79, 0x0E }, + { 0x7a, 0x0F }, + { 0x7b, 0x0C }, + { 0x7c, 0x0D }, + { 0x7d, 0x06 }, + { 0x7e, 0x02 }, + { 0x7f, 0x07 }, + { 0x80, 0x02 }, + { 0x81, 0x02 }, + { 0x82, 0x02 }, + { 0x83, 0x02 }, + { 0x84, 0x02 }, + { 0x85, 0x02 }, + { 0x86, 0x02 }, + { 0x87, 0x02 }, + { 0x88, 0x02 }, + { 0x89, 0x02 }, + { 0x8A, 0x02 }, +}; + +static const struct ltk050h3146w_cmd page4_cmds[] = { + { 0x70, 0x00 }, + { 0x71, 0x00 }, + { 0x82, 0x0F }, /* VGH_MOD clamp level=15v */ + { 0x84, 0x0F }, /* VGH clamp level 15V */ + { 0x85, 0x0D }, /* VGL clamp level (-10V) */ + { 0x32, 0xAC }, + { 0x8C, 0x80 }, + { 0x3C, 0xF5 }, + { 0xB5, 0x07 }, /* GAMMA OP */ + { 0x31, 0x45 }, /* SOURCE OP */ + { 0x3A, 0x24 }, /* PS_EN OFF */ + { 0x88, 0x33 }, /* LVD */ +}; + +static inline +struct ltk050h3146w *panel_to_ltk050h3146w(struct drm_panel *panel) +{ + return container_of(panel, struct ltk050h3146w, panel); +} + +static void ltk050h3148w_init_sequence(struct mipi_dsi_multi_context *dsi_ctx) +{ + /* + * Init sequence was supplied by the panel vendor without much + * documentation. + */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb9, 0xff, 0x83, 0x94); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb1, 0x50, 0x15, 0x75, 0x09, 0x32, 0x44, + 0x71, 0x31, 0x55, 0x2f); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xba, 0x63, 0x03, 0x68, 0x6b, 0xb2, 0xc0); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xd2, 0x88); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb2, 0x00, 0x80, 0x64, 0x10, 0x07); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb4, 0x05, 0x70, 0x05, 0x70, 0x01, 0x70, + 0x01, 0x0c, 0x86, 0x75, 0x00, 0x3f, 0x01, 0x74, + 0x01, 0x74, 0x01, 0x74, 0x01, 0x0c, 0x86); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xd3, 0x00, 0x00, 0x07, 0x07, 0x40, 0x1e, + 0x08, 0x00, 0x32, 0x10, 0x08, 0x00, 0x08, 0x54, + 0x15, 0x10, 0x05, 0x04, 0x02, 0x12, 0x10, 0x05, + 0x07, 0x33, 0x34, 0x0c, 0x0c, 0x37, 0x10, 0x07, + 0x17, 0x11, 0x40); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xd5, 0x19, 0x19, 0x18, 0x18, 0x1b, 0x1b, + 0x1a, 0x1a, 0x04, 0x05, 0x06, 0x07, 0x00, 0x01, + 0x02, 0x03, 0x20, 0x21, 0x18, 0x18, 0x22, 0x23, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xd6, 0x18, 0x18, 0x19, 0x19, 0x1b, 0x1b, + 0x1a, 0x1a, 0x03, 0x02, 0x01, 0x00, 0x07, 0x06, + 0x05, 0x04, 0x23, 0x22, 0x18, 0x18, 0x21, 0x20, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xe0, 0x00, 0x03, 0x09, 0x11, 0x11, 0x14, + 0x18, 0x16, 0x2e, 0x3d, 0x4d, 0x4d, 0x58, 0x6c, + 0x72, 0x78, 0x88, 0x8b, 0x86, 0xa4, 0xb2, 0x58, + 0x55, 0x59, 0x5b, 0x5d, 0x60, 0x64, 0x7f, 0x00, + 0x03, 0x09, 0x0f, 0x11, 0x14, 0x18, 0x16, 0x2e, + 0x3d, 0x4d, 0x4d, 0x58, 0x6d, 0x73, 0x78, 0x88, + 0x8b, 0x87, 0xa5, 0xb2, 0x58, 0x55, 0x58, 0x5b, + 0x5d, 0x61, 0x65, 0x7f); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xcc, 0x0b); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc0, 0x1f, 0x31); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb6, 0xc4, 0xc4); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xbd, 0x01); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb1, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xbd, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc6, 0xef); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xd4, 0x02); + + mipi_dsi_dcs_set_tear_on_multi(dsi_ctx, 1); + mipi_dsi_msleep(dsi_ctx, 60); +} + +static const struct drm_display_mode ltk050h3148w_mode = { + .hdisplay = 720, + .hsync_start = 720 + 12, + .hsync_end = 720 + 12 + 6, + .htotal = 720 + 12 + 6 + 24, + .vdisplay = 1280, + .vsync_start = 1280 + 9, + .vsync_end = 1280 + 9 + 2, + .vtotal = 1280 + 9 + 2 + 16, + .clock = 59756, + .width_mm = 62, + .height_mm = 110, +}; + +static const struct ltk050h3146w_desc ltk050h3148w_data = { + .mode = <k050h3148w_mode, + .init = ltk050h3148w_init_sequence, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_MODE_VIDEO_BURST, +}; + +static void ltk050h3146w_init_sequence(struct mipi_dsi_multi_context *dsi_ctx) +{ + /* + * Init sequence was supplied by the panel vendor without much + * documentation. + */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xdf, 0x93, 0x65, 0xf8); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb0, 0x01, 0x03, 0x02, 0x00, 0x64, 0x06, + 0x01); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb2, 0x00, 0xb5); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb3, 0x00, 0xb5); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb7, 0x00, 0xbf, 0x00, 0x00, 0xbf, 0x00); + + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb9, 0x00, 0xc4, 0x23, 0x07); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xbb, 0x02, 0x01, 0x24, 0x00, 0x28, 0x0f, + 0x28, 0x04, 0xcc, 0xcc, 0xcc); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xbc, 0x0f, 0x04); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xbe, 0x1e, 0xf2); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc0, 0x26, 0x03); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc1, 0x00, 0x12); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc3, 0x04, 0x02, 0x02, 0x76, 0x01, 0x80, + 0x80); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc4, 0x24, 0x80, 0xb4, 0x81, 0x12, 0x0f, + 0x16, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc8, 0x7f, 0x72, 0x67, 0x5d, 0x5d, 0x50, + 0x56, 0x41, 0x59, 0x57, 0x55, 0x70, 0x5b, 0x5f, + 0x4f, 0x47, 0x38, 0x23, 0x08, 0x7f, 0x72, 0x67, + 0x5d, 0x5d, 0x50, 0x56, 0x41, 0x59, 0x57, 0x55, + 0x70, 0x5b, 0x5f, 0x4f, 0x47, 0x38, 0x23, 0x08); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xd0, 0x1e, 0x1f, 0x57, 0x58, 0x48, 0x4a, + 0x44, 0x46, 0x40, 0x1f, 0x42, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xd1, 0x1e, 0x1f, 0x57, 0x58, 0x49, 0x4b, + 0x45, 0x47, 0x41, 0x1f, 0x43, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xd2, 0x1f, 0x1e, 0x17, 0x18, 0x07, 0x05, + 0x0b, 0x09, 0x03, 0x1f, 0x01, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xd3, 0x1f, 0x1e, 0x17, 0x18, 0x06, 0x04, + 0x0a, 0x08, 0x02, 0x1f, 0x00, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xd4, 0x00, 0x00, 0x00, 0x0c, 0x06, 0x20, + 0x01, 0x02, 0x00, 0x60, 0x15, 0xb0, 0x30, 0x03, + 0x04, 0x00, 0x60, 0x72, 0x0a, 0x00, 0x60, 0x08); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xd5, 0x00, 0x06, 0x06, 0x00, 0x30, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xbc, 0x50, 0x00, 0x05, + 0x21, 0x00, 0x60); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xdd, 0x2c, 0xa3, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xde, 0x02); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb2, 0x32, 0x1c); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb7, 0x3b, 0x70, 0x00, 0x04); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc1, 0x11); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xbb, 0x21, 0x22, 0x23, 0x24, 0x36, 0x37); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc2, 0x20, 0x38, 0x1e, 0x84); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xde, 0x00); + + mipi_dsi_dcs_set_tear_on_multi(dsi_ctx, 1); + mipi_dsi_msleep(dsi_ctx, 60); +} + +static const struct drm_display_mode ltk050h3146w_mode = { + .hdisplay = 720, + .hsync_start = 720 + 42, + .hsync_end = 720 + 42 + 8, + .htotal = 720 + 42 + 8 + 42, + .vdisplay = 1280, + .vsync_start = 1280 + 12, + .vsync_end = 1280 + 12 + 4, + .vtotal = 1280 + 12 + 4 + 18, + .clock = 64018, + .width_mm = 62, + .height_mm = 110, +}; + +static const struct ltk050h3146w_desc ltk050h3146w_data = { + .mode = <k050h3146w_mode, + .init = ltk050h3146w_init_sequence, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_NO_EOT_PACKET, +}; + +static void ltk050h3146w_a2_select_page(struct mipi_dsi_multi_context *dsi_ctx, int page) +{ + u8 d[4] = { 0xff, 0x98, 0x81, page }; + + mipi_dsi_dcs_write_buffer_multi(dsi_ctx, d, ARRAY_SIZE(d)); +} + +static void ltk050h3146w_a2_write_page(struct mipi_dsi_multi_context *dsi_ctx, int page, + const struct ltk050h3146w_cmd *cmds, + int num) +{ + ltk050h3146w_a2_select_page(dsi_ctx, page); + + for (int i = 0; i < num; i++) + mipi_dsi_generic_write_multi(dsi_ctx, &cmds[i], + sizeof(struct ltk050h3146w_cmd)); +} + +static void ltk050h3146w_a2_init_sequence(struct mipi_dsi_multi_context *dsi_ctx) +{ + /* + * Init sequence was supplied by the panel vendor without much + * documentation. + */ + ltk050h3146w_a2_write_page(dsi_ctx, 3, page3_cmds, + ARRAY_SIZE(page3_cmds)); + ltk050h3146w_a2_write_page(dsi_ctx, 4, page4_cmds, + ARRAY_SIZE(page4_cmds)); + ltk050h3146w_a2_write_page(dsi_ctx, 1, page1_cmds, + ARRAY_SIZE(page1_cmds)); + ltk050h3146w_a2_select_page(dsi_ctx, 0); + + /* vendor code called this without param, where there should be one */ + mipi_dsi_dcs_set_tear_on_multi(dsi_ctx, 0); + + mipi_dsi_msleep(dsi_ctx, 60); +} + +static const struct drm_display_mode ltk050h3146w_a2_mode = { + .hdisplay = 720, + .hsync_start = 720 + 42, + .hsync_end = 720 + 42 + 10, + .htotal = 720 + 42 + 10 + 60, + .vdisplay = 1280, + .vsync_start = 1280 + 18, + .vsync_end = 1280 + 18 + 4, + .vtotal = 1280 + 18 + 4 + 12, + .clock = 65595, + .width_mm = 62, + .height_mm = 110, +}; + +static const struct ltk050h3146w_desc ltk050h3146w_a2_data = { + .mode = <k050h3146w_a2_mode, + .init = ltk050h3146w_a2_init_sequence, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_NO_EOT_PACKET, +}; + +static int ltk050h3146w_unprepare(struct drm_panel *panel) +{ + struct ltk050h3146w *ctx = panel_to_ltk050h3146w(panel); + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + if (dsi_ctx.accum_err) + return dsi_ctx.accum_err; + + regulator_disable(ctx->iovcc); + regulator_disable(ctx->vci); + + return 0; +} + +static int ltk050h3146w_prepare(struct drm_panel *panel) +{ + struct ltk050h3146w *ctx = panel_to_ltk050h3146w(panel); + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + dev_dbg(ctx->dev, "Resetting the panel\n"); + dsi_ctx.accum_err = regulator_enable(ctx->vci); + if (dsi_ctx.accum_err) { + dev_err(ctx->dev, "Failed to enable vci supply: %d\n", dsi_ctx.accum_err); + return dsi_ctx.accum_err; + } + dsi_ctx.accum_err = regulator_enable(ctx->iovcc); + if (dsi_ctx.accum_err) { + dev_err(ctx->dev, "Failed to enable iovcc supply: %d\n", dsi_ctx.accum_err); + goto disable_vci; + } + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(5000, 6000); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + msleep(20); + + ctx->panel_desc->init(&dsi_ctx); + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + /* T9: 120ms */ + mipi_dsi_msleep(&dsi_ctx, 120); + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 50); + + if (dsi_ctx.accum_err) + goto disable_iovcc; + + return 0; + +disable_iovcc: + regulator_disable(ctx->iovcc); +disable_vci: + regulator_disable(ctx->vci); + return dsi_ctx.accum_err; +} + +static int ltk050h3146w_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct ltk050h3146w *ctx = panel_to_ltk050h3146w(panel); + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, ctx->panel_desc->mode); + if (!mode) + 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 ltk050h3146w_funcs = { + .unprepare = ltk050h3146w_unprepare, + .prepare = ltk050h3146w_prepare, + .get_modes = ltk050h3146w_get_modes, +}; + +static int ltk050h3146w_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct ltk050h3146w *ctx; + int ret; + + ctx = devm_drm_panel_alloc(dev, struct ltk050h3146w, panel, + <k050h3146w_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ctx->panel_desc = of_device_get_match_data(dev); + if (!ctx->panel_desc) + return -EINVAL; + + ctx->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), "cannot get reset gpio\n"); + + ctx->vci = devm_regulator_get(dev, "vci"); + if (IS_ERR(ctx->vci)) + return dev_err_probe(dev, PTR_ERR(ctx->vci), "Failed to request vci regulator\n"); + + ctx->iovcc = devm_regulator_get(dev, "iovcc"); + if (IS_ERR(ctx->iovcc)) + return dev_err_probe(dev, PTR_ERR(ctx->iovcc), + "Failed to request iovcc regulator\n"); + + mipi_dsi_set_drvdata(dsi, ctx); + + ctx->dev = dev; + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = ctx->panel_desc->mode_flags; + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return ret; + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "mipi_dsi_attach failed: %d\n", ret); + drm_panel_remove(&ctx->panel); + return ret; + } + + return 0; +} + +static void ltk050h3146w_remove(struct mipi_dsi_device *dsi) +{ + struct ltk050h3146w *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id ltk050h3146w_of_match[] = { + { + .compatible = "leadtek,ltk050h3146w", + .data = <k050h3146w_data, + }, + { + .compatible = "leadtek,ltk050h3146w-a2", + .data = <k050h3146w_a2_data, + }, + { + .compatible = "leadtek,ltk050h3148w", + .data = <k050h3148w_data, + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ltk050h3146w_of_match); + +static struct mipi_dsi_driver ltk050h3146w_driver = { + .driver = { + .name = "panel-leadtek-ltk050h3146w", + .of_match_table = ltk050h3146w_of_match, + }, + .probe = ltk050h3146w_probe, + .remove = ltk050h3146w_remove, +}; +module_mipi_dsi_driver(ltk050h3146w_driver); + +MODULE_AUTHOR("Heiko Stuebner <heiko.stuebner@theobroma-systems.com>"); +MODULE_DESCRIPTION("DRM driver for Leadtek LTK050H3146W MIPI DSI panel"); +MODULE_LICENSE("GPL v2"); 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..7f19fd5b8060 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-leadtek-ltk500hd1829.c @@ -0,0 +1,701 @@ +// 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/of_device.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> + +struct ltk500hd1829_cmd { + char cmd; + char data; +}; + +struct ltk500hd1829_desc { + const struct drm_display_mode *mode; + const struct ltk500hd1829_cmd *init; + unsigned int num_init; +}; + +struct ltk500hd1829 { + struct device *dev; + struct drm_panel panel; + struct gpio_desc *reset_gpio; + struct regulator *vcc; + struct regulator *iovcc; + const struct ltk500hd1829_desc *panel_desc; +}; + +static const struct ltk500hd1829_cmd ltk101b4029w_init[] = { + /* Page0 */ + { 0xE0, 0x00 }, + /* PASSWORD */ + { 0xE1, 0x93 }, + { 0xE2, 0x65 }, + { 0xE3, 0xF8 }, + { 0x80, 0x03 }, /* 0X03:4-LANE; 0X02:3-LANE; 0X01:2-LANE */ + /* Page1 */ + { 0xE0, 0x01 }, + /* Set VCOM */ + { 0x00, 0x00 }, + { 0x01, 0x6F }, + /* Set Gamma Power, VGMP,VGMN,VGSP,VGSN */ + { 0x17, 0x00 }, + { 0x18, 0xAF }, /* 4.3V */ + { 0x19, 0x01 }, /* 0.3V */ + { 0x1A, 0x00 }, + { 0x1B, 0xAF }, /* 4.3V */ + { 0x1C, 0x01 }, /* 0.3V */ + /* Set Gate Power */ + { 0x1F, 0x3E }, /* VGH_R = 15V */ + { 0x20, 0x28 }, /* VGL_R = -12V */ + { 0x21, 0x28 }, /* VGL_R2 = -12V */ + { 0x22, 0x7E }, + /* SETPANEL */ + { 0x35, 0x26 }, + { 0x37, 0x09 }, + /* SET RGBCYC */ + { 0x38, 0x04 }, + { 0x39, 0x00 }, + { 0x3A, 0x01 }, + { 0x3C, 0x7C }, + { 0x3D, 0xFF }, + { 0x3E, 0xFF }, + { 0x3F, 0x7F }, + /* Set TCON */ + { 0x40, 0x06 }, /* RSO = 800 RGB */ + { 0x41, 0xA0 }, /* LN = 640->1280 line */ + { 0x42, 0x81 }, + { 0x43, 0x08 }, /* VFP = 8 */ + { 0x44, 0x0B }, /* VBP = 12 */ + { 0x45, 0x28 }, /* HBP = 40 */ + /* power voltage */ + { 0x55, 0x0F }, /* DCDCM = 0001, JD PWR_IC */ + { 0x57, 0x69 }, + { 0x59, 0x0A }, /* VCL = -2.9V */ + { 0x5A, 0x28 }, /* VGH = 15V */ + { 0x5B, 0x14 }, /* VGL = -11V */ + /* Gamma */ + { 0x5D, 0x7C }, + { 0x5E, 0x65 }, + { 0x5F, 0x55 }, + { 0x60, 0x47 }, + { 0x61, 0x43 }, + { 0x62, 0x32 }, + { 0x63, 0x34 }, + { 0x64, 0x1C }, + { 0x65, 0x33 }, + { 0x66, 0x31 }, + { 0x67, 0x30 }, + { 0x68, 0x4E }, + { 0x69, 0x3C }, + { 0x6A, 0x44 }, + { 0x6B, 0x35 }, + { 0x6C, 0x31 }, + { 0x6D, 0x23 }, + { 0x6E, 0x11 }, + { 0x6F, 0x00 }, + { 0x70, 0x7C }, + { 0x71, 0x65 }, + { 0x72, 0x55 }, + { 0x73, 0x47 }, + { 0x74, 0x43 }, + { 0x75, 0x32 }, + { 0x76, 0x34 }, + { 0x77, 0x1C }, + { 0x78, 0x33 }, + { 0x79, 0x31 }, + { 0x7A, 0x30 }, + { 0x7B, 0x4E }, + { 0x7C, 0x3C }, + { 0x7D, 0x44 }, + { 0x7E, 0x35 }, + { 0x7F, 0x31 }, + { 0x80, 0x23 }, + { 0x81, 0x11 }, + { 0x82, 0x00 }, + /* Page2, for GIP */ + { 0xE0, 0x02 }, + /* GIP_L Pin mapping */ + { 0x00, 0x1E }, + { 0x01, 0x1E }, + { 0x02, 0x41 }, + { 0x03, 0x41 }, + { 0x04, 0x43 }, + { 0x05, 0x43 }, + { 0x06, 0x1F }, + { 0x07, 0x1F }, + { 0x08, 0x35 }, + { 0x09, 0x1F }, + { 0x0A, 0x15 }, + { 0x0B, 0x15 }, + { 0x0C, 0x1F }, + { 0x0D, 0x47 }, + { 0x0E, 0x47 }, + { 0x0F, 0x45 }, + { 0x10, 0x45 }, + { 0x11, 0x4B }, + { 0x12, 0x4B }, + { 0x13, 0x49 }, + { 0x14, 0x49 }, + { 0x15, 0x1F }, + /* GIP_R Pin mapping */ + { 0x16, 0x1E }, + { 0x17, 0x1E }, + { 0x18, 0x40 }, + { 0x19, 0x40 }, + { 0x1A, 0x42 }, + { 0x1B, 0x42 }, + { 0x1C, 0x1F }, + { 0x1D, 0x1F }, + { 0x1E, 0x35 }, + { 0x1F, 0x1F }, + { 0x20, 0x15 }, + { 0x21, 0x15 }, + { 0x22, 0x1f }, + { 0x23, 0x46 }, + { 0x24, 0x46 }, + { 0x25, 0x44 }, + { 0x26, 0x44 }, + { 0x27, 0x4A }, + { 0x28, 0x4A }, + { 0x29, 0x48 }, + { 0x2A, 0x48 }, + { 0x2B, 0x1F }, + /* GIP Timing */ + { 0x58, 0x40 }, + { 0x5B, 0x30 }, + { 0x5C, 0x03 }, + { 0x5D, 0x30 }, + { 0x5E, 0x01 }, + { 0x5F, 0x02 }, + { 0x63, 0x14 }, + { 0x64, 0x6A }, + { 0x67, 0x73 }, + { 0x68, 0x05 }, + { 0x69, 0x14 }, + { 0x6A, 0x6A }, + { 0x6B, 0x08 }, + { 0x6C, 0x00 }, + { 0x6D, 0x00 }, + { 0x6E, 0x00 }, + { 0x6F, 0x88 }, + { 0x77, 0xDD }, + { 0x79, 0x0E }, + { 0x7A, 0x03 }, + { 0x7D, 0x14 }, + { 0x7E, 0x6A }, + /* Page4 */ + { 0xE0, 0x04 }, + { 0x09, 0x11 }, + { 0x0E, 0x48 }, + { 0x2B, 0x2B }, + { 0x2D, 0x03 }, + { 0x2E, 0x44 }, + /* Page0 */ + { 0xE0, 0x00 }, + { 0xE6, 0x02 }, + { 0xE7, 0x0C }, +}; + +static const struct drm_display_mode ltk101b4029w_mode = { + .hdisplay = 800, + .hsync_start = 800 + 18, + .hsync_end = 800 + 18 + 18, + .htotal = 800 + 18 + 18 + 18, + .vdisplay = 1280, + .vsync_start = 1280 + 24, + .vsync_end = 1280 + 24 + 4, + .vtotal = 1280 + 24 + 4 + 8, + .clock = 67330, + .width_mm = 136, + .height_mm = 218, +}; + +static const struct ltk500hd1829_desc ltk101b4029w_data = { + .mode = <k101b4029w_mode, + .init = ltk101b4029w_init, + .num_init = ARRAY_SIZE(ltk101b4029w_init), +}; + +/* + * 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 ltk500hd1829_init[] = { + { 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 const struct drm_display_mode ltk500hd1829_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, + .clock = 69217, + .width_mm = 62, + .height_mm = 110, +}; + +static const struct ltk500hd1829_desc ltk500hd1829_data = { + .mode = <k500hd1829_mode, + .init = ltk500hd1829_init, + .num_init = ARRAY_SIZE(ltk500hd1829_init), +}; + +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; + + ret = mipi_dsi_dcs_set_display_off(dsi); + if (ret < 0) + dev_err(panel->dev, "failed to set display off: %d\n", ret); + + ret = mipi_dsi_dcs_enter_sleep_mode(dsi); + if (ret < 0) { + dev_err(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); + + 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; + + ret = regulator_enable(ctx->vcc); + if (ret < 0) { + dev_err(ctx->dev, "Failed to enable vci supply: %d\n", ret); + return ret; + } + ret = regulator_enable(ctx->iovcc); + if (ret < 0) { + dev_err(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 < ctx->panel_desc->num_init; i++) { + ret = mipi_dsi_generic_write(dsi, &ctx->panel_desc->init[i], + sizeof(struct ltk500hd1829_cmd)); + if (ret < 0) { + dev_err(panel->dev, "failed to write init cmds: %d\n", ret); + goto disable_iovcc; + } + } + + ret = mipi_dsi_dcs_exit_sleep_mode(dsi); + if (ret < 0) { + dev_err(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) { + dev_err(panel->dev, "failed to set display on: %d\n", ret); + goto disable_iovcc; + } + + return 0; + +disable_iovcc: + regulator_disable(ctx->iovcc); +disable_vcc: + regulator_disable(ctx->vcc); + return ret; +} + +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, ctx->panel_desc->mode); + if (!mode) { + dev_err(ctx->dev, "failed to add mode %ux%u@%u\n", + ctx->panel_desc->mode->hdisplay, ctx->panel_desc->mode->vdisplay, + drm_mode_vrefresh(ctx->panel_desc->mode)); + 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_drm_panel_alloc(dev, struct ltk500hd1829, panel, + <k500hd1829_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ctx->panel_desc = of_device_get_match_data(dev); + if (!ctx->panel_desc) + return -EINVAL; + + ctx->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->reset_gpio)) { + dev_err(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) + dev_err(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) + dev_err(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_NO_EOT_PACKET; + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return ret; + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "mipi_dsi_attach failed: %d\n", ret); + drm_panel_remove(&ctx->panel); + return ret; + } + + return 0; +} + +static void ltk500hd1829_remove(struct mipi_dsi_device *dsi) +{ + struct ltk500hd1829 *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id ltk500hd1829_of_match[] = { + { + .compatible = "leadtek,ltk101b4029w", + .data = <k101b4029w_data, + }, + { + .compatible = "leadtek,ltk500hd1829", + .data = <k500hd1829_data, + }, + { /* 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, +}; +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..b2be6727bf73 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-lg-lb035q02.c @@ -0,0 +1,240 @@ +// 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, + .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_drm_panel_alloc(&spi->dev, struct lb035q02_device, panel, + &lb035q02_funcs, DRM_MODE_CONNECTOR_DPI); + if (IS_ERR(lcd)) + return PTR_ERR(lcd); + + 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_add(&lcd->panel); + + return 0; +} + +static void lb035q02_remove(struct spi_device *spi) +{ + struct lb035q02_device *lcd = spi_get_drvdata(spi); + + drm_panel_remove(&lcd->panel); + drm_panel_disable(&lcd->panel); +} + +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-ld070wx3.c b/drivers/gpu/drm/panel/panel-lg-ld070wx3.c new file mode 100644 index 000000000000..00cbfc5518a5 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-lg-ld070wx3.c @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include <linux/array_size.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/property.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_probe_helper.h> + +static const struct regulator_bulk_data lg_ld070wx3_supplies[] = { + { .supply = "vdd" }, { .supply = "vcc" }, +}; + +struct lg_ld070wx3 { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + + struct regulator_bulk_data *supplies; +}; + +static inline struct lg_ld070wx3 *to_lg_ld070wx3(struct drm_panel *panel) +{ + return container_of(panel, struct lg_ld070wx3, panel); +} + +static int lg_ld070wx3_prepare(struct drm_panel *panel) +{ + struct lg_ld070wx3 *priv = to_lg_ld070wx3(panel); + struct mipi_dsi_multi_context ctx = { .dsi = priv->dsi }; + struct device *dev = panel->dev; + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(lg_ld070wx3_supplies), priv->supplies); + if (ret < 0) { + dev_err(dev, "failed to enable power supplies: %d\n", ret); + return ret; + } + + /* + * According to spec delay between enabling supply is 0, + * for regulators to reach required voltage ~5ms needed. + * MIPI interface signal for setup requires additional + * 110ms which in total results in 115ms. + */ + mdelay(115); + + mipi_dsi_dcs_soft_reset_multi(&ctx); + mipi_dsi_msleep(&ctx, 20); + + /* Differential input impedance selection */ + mipi_dsi_dcs_write_seq_multi(&ctx, 0xae, 0x0b); + + /* Enter test mode 1 and 2*/ + mipi_dsi_dcs_write_seq_multi(&ctx, 0xee, 0xea); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xef, 0x5f); + + /* Increased MIPI CLK driving ability */ + mipi_dsi_dcs_write_seq_multi(&ctx, 0xf2, 0x68); + + /* Exit test mode 1 and 2 */ + mipi_dsi_dcs_write_seq_multi(&ctx, 0xee, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xef, 0x00); + + return ctx.accum_err; +} + +static int lg_ld070wx3_unprepare(struct drm_panel *panel) +{ + struct lg_ld070wx3 *priv = to_lg_ld070wx3(panel); + struct mipi_dsi_multi_context ctx = { .dsi = priv->dsi }; + + mipi_dsi_dcs_enter_sleep_mode_multi(&ctx); + + msleep(50); + + regulator_bulk_disable(ARRAY_SIZE(lg_ld070wx3_supplies), priv->supplies); + + /* power supply must be off for at least 1s after panel disable */ + msleep(1000); + + return 0; +} + +static const struct drm_display_mode lg_ld070wx3_mode = { + .clock = (800 + 32 + 48 + 8) * (1280 + 5 + 3 + 1) * 60 / 1000, + .hdisplay = 800, + .hsync_start = 800 + 32, + .hsync_end = 800 + 32 + 48, + .htotal = 800 + 32 + 48 + 8, + .vdisplay = 1280, + .vsync_start = 1280 + 5, + .vsync_end = 1280 + 5 + 3, + .vtotal = 1280 + 5 + 3 + 1, + .width_mm = 94, + .height_mm = 151, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, +}; + +static int lg_ld070wx3_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + return drm_connector_helper_get_modes_fixed(connector, &lg_ld070wx3_mode); +} + +static const struct drm_panel_funcs lg_ld070wx3_panel_funcs = { + .prepare = lg_ld070wx3_prepare, + .unprepare = lg_ld070wx3_unprepare, + .get_modes = lg_ld070wx3_get_modes, +}; + +static int lg_ld070wx3_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct lg_ld070wx3 *priv; + int ret; + + priv = devm_drm_panel_alloc(dev, struct lg_ld070wx3, panel, + &lg_ld070wx3_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(priv)) + return PTR_ERR(priv); + + ret = devm_regulator_bulk_get_const(dev, ARRAY_SIZE(lg_ld070wx3_supplies), + lg_ld070wx3_supplies, &priv->supplies); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to get supplies\n"); + + priv->dsi = dsi; + mipi_dsi_set_drvdata(dsi, priv); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM; + + ret = drm_panel_of_backlight(&priv->panel); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to get backlight\n"); + + drm_panel_add(&priv->panel); + + ret = devm_mipi_dsi_attach(dev, dsi); + if (ret < 0) { + drm_panel_remove(&priv->panel); + return dev_err_probe(dev, ret, "failed to attach to DSI host\n"); + } + + return 0; +} + +static void lg_ld070wx3_remove(struct mipi_dsi_device *dsi) +{ + struct lg_ld070wx3 *priv = mipi_dsi_get_drvdata(dsi); + + drm_panel_remove(&priv->panel); +} + +static const struct of_device_id lg_ld070wx3_of_match[] = { + { .compatible = "lg,ld070wx3-sl01" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, lg_ld070wx3_of_match); + +static struct mipi_dsi_driver lg_ld070wx3_driver = { + .driver = { + .name = "panel-lg-ld070wx3", + .of_match_table = lg_ld070wx3_of_match, + }, + .probe = lg_ld070wx3_probe, + .remove = lg_ld070wx3_remove, +}; +module_mipi_dsi_driver(lg_ld070wx3_driver); + +MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>"); +MODULE_DESCRIPTION("LG LD070WX3-SL01 DSI 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 6989238b276a..dec619902c15 100644 --- a/drivers/gpu/drm/panel/panel-lg-lg4573.c +++ b/drivers/gpu/drm/panel/panel-lg-lg4573.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2015 Heiko Schocher <hs@denx.de> * @@ -9,16 +10,11 @@ * Derived from drivers/video/backlight/ld9040.c * * Andrzej Hajda <a.hajda@samsung.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. */ -#include <drm/drmP.h> -#include <drm/drm_panel.h> - +#include <linux/delay.h> #include <linux/gpio/consumer.h> +#include <linux/module.h> #include <linux/regulator/consumer.h> #include <linux/spi/spi.h> @@ -26,6 +22,10 @@ #include <video/of_videomode.h> #include <video/videomode.h> +#include <drm/drm_device.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + struct lg4573 { struct drm_panel panel; struct spi_device *spi; @@ -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); @@ -197,7 +197,7 @@ static int lg4573_enable(struct drm_panel *panel) } static const struct drm_display_mode default_mode = { - .clock = 27000, + .clock = 28341, .hdisplay = 480, .hsync_start = 480 + 10, .hsync_end = 480 + 10 + 59, @@ -206,19 +206,18 @@ static const struct drm_display_mode default_mode = { .vsync_start = 800 + 15, .vsync_end = 800 + 15 + 15, .vtotal = 800 + 15 + 15 + 15, - .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); + drm_mode_vrefresh(&default_mode)); return -ENOMEM; } @@ -227,8 +226,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; } @@ -244,9 +243,11 @@ static int lg4573_probe(struct spi_device *spi) struct lg4573 *ctx; int ret; - ctx = devm_kzalloc(&spi->dev, sizeof(*ctx), GFP_KERNEL); - if (!ctx) - return -ENOMEM; + ctx = devm_drm_panel_alloc(&spi->dev, struct lg4573, panel, + &lg4573_drm_funcs, + DRM_MODE_CONNECTOR_DPI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); ctx->spi = spi; @@ -259,21 +260,17 @@ 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_add(&ctx->panel); - return drm_panel_add(&ctx->panel); + return 0; } -static int lg4573_remove(struct spi_device *spi) +static void lg4573_remove(struct spi_device *spi) { struct lg4573 *ctx = spi_get_drvdata(spi); lg4573_display_off(ctx); drm_panel_remove(&ctx->panel); - - return 0; } static const struct of_device_id lg4573_of_match[] = { diff --git a/drivers/gpu/drm/panel/panel-lg-sw43408.c b/drivers/gpu/drm/panel/panel-lg-sw43408.c new file mode 100644 index 000000000000..46a56ea92ad9 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-lg-sw43408.c @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2019-2024 Linaro Ltd + * Author: Sumit Semwal <sumit.semwal@linaro.org> + * Dmitry Baryshkov <dmitry.baryshkov@linaro.org> + */ + +#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_panel.h> +#include <drm/drm_probe_helper.h> +#include <drm/display/drm_dsc.h> +#include <drm/display/drm_dsc_helper.h> + +#define NUM_SUPPLIES 2 + +struct sw43408_panel { + struct drm_panel base; + struct mipi_dsi_device *link; + + struct regulator_bulk_data supplies[NUM_SUPPLIES]; + + struct gpio_desc *reset_gpio; + + struct drm_dsc_config dsc; +}; + +static inline struct sw43408_panel *to_panel_info(struct drm_panel *panel) +{ + return container_of(panel, struct sw43408_panel, base); +} + +static int sw43408_unprepare(struct drm_panel *panel) +{ + struct sw43408_panel *sw43408 = to_panel_info(panel); + struct mipi_dsi_multi_context ctx = { .dsi = sw43408->link }; + int ret; + + mipi_dsi_dcs_set_display_off_multi(&ctx); + + mipi_dsi_dcs_enter_sleep_mode_multi(&ctx); + + mipi_dsi_msleep(&ctx, 100); + + gpiod_set_value(sw43408->reset_gpio, 1); + + ret = regulator_bulk_disable(ARRAY_SIZE(sw43408->supplies), sw43408->supplies); + + return ret ? : ctx.accum_err; +} + +static int sw43408_program(struct drm_panel *panel) +{ + struct sw43408_panel *sw43408 = to_panel_info(panel); + struct mipi_dsi_multi_context ctx = { .dsi = sw43408->link }; + struct drm_dsc_picture_parameter_set pps; + + mipi_dsi_dcs_write_seq_multi(&ctx, MIPI_DCS_SET_GAMMA_CURVE, 0x02); + + mipi_dsi_dcs_set_tear_on_multi(&ctx, MIPI_DSI_DCS_TEAR_MODE_VBLANK); + + mipi_dsi_dcs_write_seq_multi(&ctx, 0x53, 0x0c, 0x30); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x55, 0x00, 0x70, 0xdf, 0x00, 0x70, 0xdf); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xf7, 0x01, 0x49, 0x0c); + + mipi_dsi_dcs_exit_sleep_mode_multi(&ctx); + + mipi_dsi_msleep(&ctx, 135); + + /* COMPRESSION_MODE moved after setting the PPS */ + + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0xac); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe5, + 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x0e, 0x10); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb5, + 0x75, 0x60, 0x2d, 0x5d, 0x80, 0x00, 0x0a, 0x0b, + 0x00, 0x05, 0x0b, 0x00, 0x80, 0x0d, 0x0e, 0x40, + 0x00, 0x0c, 0x00, 0x16, 0x00, 0xb8, 0x00, 0x80, + 0x0d, 0x0e, 0x40, 0x00, 0x0c, 0x00, 0x16, 0x00, + 0xb8, 0x00, 0x81, 0x00, 0x03, 0x03, 0x03, 0x01, + 0x01); + mipi_dsi_msleep(&ctx, 85); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcd, + 0x00, 0x00, 0x00, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x16, 0x16); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcb, 0x80, 0x5c, 0x07, 0x03, 0x28); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc0, 0x02, 0x02, 0x0f); + mipi_dsi_dcs_write_seq_multi(&ctx, 0x55, 0x04, 0x61, 0xdb, 0x04, 0x70, 0xdb); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0xca); + + mipi_dsi_dcs_set_display_on_multi(&ctx); + + mipi_dsi_msleep(&ctx, 50); + + sw43408->link->mode_flags &= ~MIPI_DSI_MODE_LPM; + + drm_dsc_pps_payload_pack(&pps, sw43408->link->dsc); + + mipi_dsi_picture_parameter_set_multi(&ctx, &pps); + + sw43408->link->mode_flags |= MIPI_DSI_MODE_LPM; + + /* + * This panel uses PPS selectors with offset: + * PPS 1 if pps_identifier is 0 + * PPS 2 if pps_identifier is 1 + */ + mipi_dsi_compression_mode_ext_multi(&ctx, true, + MIPI_DSI_COMPRESSION_DSC, 1); + return ctx.accum_err; +} + +static int sw43408_prepare(struct drm_panel *panel) +{ + struct sw43408_panel *ctx = to_panel_info(panel); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + if (ret < 0) + return ret; + + usleep_range(5000, 6000); + + gpiod_set_value(ctx->reset_gpio, 0); + usleep_range(9000, 10000); + gpiod_set_value(ctx->reset_gpio, 1); + usleep_range(1000, 2000); + gpiod_set_value(ctx->reset_gpio, 0); + usleep_range(9000, 10000); + + ret = sw43408_program(panel); + if (ret) + goto poweroff; + + return 0; + +poweroff: + gpiod_set_value(ctx->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + return ret; +} + +static const struct drm_display_mode sw43408_mode = { + .clock = (1080 + 20 + 32 + 20) * (2160 + 20 + 4 + 20) * 60 / 1000, + + .hdisplay = 1080, + .hsync_start = 1080 + 20, + .hsync_end = 1080 + 20 + 32, + .htotal = 1080 + 20 + 32 + 20, + + .vdisplay = 2160, + .vsync_start = 2160 + 20, + .vsync_end = 2160 + 20 + 4, + .vtotal = 2160 + 20 + 4 + 20, + + .width_mm = 62, + .height_mm = 124, + + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, +}; + +static int sw43408_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + return drm_connector_helper_get_modes_fixed(connector, &sw43408_mode); +} + +static int sw43408_backlight_update_status(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + u16 brightness = backlight_get_brightness(bl); + + return mipi_dsi_dcs_set_display_brightness_large(dsi, brightness); +} + +static const struct backlight_ops sw43408_backlight_ops = { + .update_status = sw43408_backlight_update_status, +}; + +static int sw43408_backlight_init(struct sw43408_panel *ctx) +{ + struct device *dev = &ctx->link->dev; + const struct backlight_properties props = { + .type = BACKLIGHT_PLATFORM, + .brightness = 255, + .max_brightness = 255, + }; + + ctx->base.backlight = devm_backlight_device_register(dev, dev_name(dev), dev, + ctx->link, + &sw43408_backlight_ops, + &props); + + if (IS_ERR(ctx->base.backlight)) + return dev_err_probe(dev, PTR_ERR(ctx->base.backlight), + "Failed to create backlight\n"); + + return 0; +} + +static const struct drm_panel_funcs sw43408_funcs = { + .unprepare = sw43408_unprepare, + .prepare = sw43408_prepare, + .get_modes = sw43408_get_modes, +}; + +static const struct of_device_id sw43408_of_match[] = { + { .compatible = "lg,sw43408", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, sw43408_of_match); + +static int sw43408_add(struct sw43408_panel *ctx) +{ + struct device *dev = &ctx->link->dev; + int ret; + + ctx->supplies[0].supply = "vddi"; /* 1.88 V */ + ctx->supplies[0].init_load_uA = 62000; + ctx->supplies[1].supply = "vpnl"; /* 3.0 V */ + ctx->supplies[1].init_load_uA = 857000; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), + ctx->supplies); + if (ret < 0) + return ret; + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->reset_gpio)) { + ret = PTR_ERR(ctx->reset_gpio); + return dev_err_probe(dev, ret, "cannot get reset gpio\n"); + } + + ret = sw43408_backlight_init(ctx); + if (ret < 0) + return ret; + + ctx->base.prepare_prev_first = true; + + drm_panel_add(&ctx->base); + return ret; +} + +static int sw43408_probe(struct mipi_dsi_device *dsi) +{ + struct sw43408_panel *ctx; + int ret; + + ctx = devm_drm_panel_alloc(&dsi->dev, __typeof(*ctx), base, + &sw43408_funcs, DRM_MODE_CONNECTOR_DSI); + + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + dsi->mode_flags = MIPI_DSI_MODE_LPM; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->lanes = 4; + + ctx->link = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + ret = sw43408_add(ctx); + if (ret < 0) + return ret; + + /* The panel works only in the DSC mode. Set DSC params. */ + ctx->dsc.dsc_version_major = 0x1; + ctx->dsc.dsc_version_minor = 0x1; + + /* slice_count * slice_width == width */ + ctx->dsc.slice_height = 16; + ctx->dsc.slice_width = 540; + ctx->dsc.slice_count = 2; + ctx->dsc.bits_per_component = 8; + ctx->dsc.bits_per_pixel = 8 << 4; + ctx->dsc.block_pred_enable = true; + + dsi->dsc = &ctx->dsc; + + return mipi_dsi_attach(dsi); +} + +static void sw43408_remove(struct mipi_dsi_device *dsi) +{ + struct sw43408_panel *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = sw43408_unprepare(&ctx->base); + if (ret < 0) + dev_err(&dsi->dev, "failed to unprepare panel: %d\n", ret); + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&ctx->base); +} + +static struct mipi_dsi_driver sw43408_driver = { + .driver = { + .name = "panel-lg-sw43408", + .of_match_table = sw43408_of_match, + }, + .probe = sw43408_probe, + .remove = sw43408_remove, +}; +module_mipi_dsi_driver(sw43408_driver); + +MODULE_AUTHOR("Sumit Semwal <sumit.semwal@linaro.org>"); +MODULE_DESCRIPTION("LG SW436408 MIPI-DSI LED panel"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-lincolntech-lcd197.c b/drivers/gpu/drm/panel/panel-lincolntech-lcd197.c new file mode 100644 index 000000000000..24b34443ace0 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-lincolntech-lcd197.c @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2024 BayLibre, SAS + * Author: Jerome Brunet <jbrunet@baylibre.com> + */ + +#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_device.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +struct lincoln_lcd197_panel { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct regulator *supply; + struct gpio_desc *enable_gpio; + struct gpio_desc *reset_gpio; +}; + +static inline +struct lincoln_lcd197_panel *to_lincoln_lcd197_panel(struct drm_panel *panel) +{ + return container_of(panel, struct lincoln_lcd197_panel, panel); +} + +static int lincoln_lcd197_panel_prepare(struct drm_panel *panel) +{ + struct lincoln_lcd197_panel *lcd = to_lincoln_lcd197_panel(panel); + struct mipi_dsi_multi_context ctx = { .dsi = lcd->dsi }; + int err; + + gpiod_set_value_cansleep(lcd->enable_gpio, 0); + err = regulator_enable(lcd->supply); + if (err < 0) + return err; + + gpiod_set_value_cansleep(lcd->enable_gpio, 1); + usleep_range(1000, 2000); + gpiod_set_value_cansleep(lcd->reset_gpio, 1); + usleep_range(5000, 6000); + gpiod_set_value_cansleep(lcd->reset_gpio, 0); + msleep(50); + + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb9, 0xff, 0x83, 0x99); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd2, 0x55); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb1, 0x02, 0x04, 0x70, 0x90, 0x01, + 0x32, 0x33, 0x11, 0x11, 0x4d, 0x57, 0x56, 0x73, + 0x02, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb2, 0x00, 0x80, 0x80, 0xae, 0x0a, + 0x0e, 0x75, 0x11, 0x00, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb4, 0x00, 0xff, 0x04, 0xa4, 0x02, + 0xa0, 0x00, 0x00, 0x10, 0x00, 0x00, 0x02, 0x00, + 0x24, 0x02, 0x04, 0x0a, 0x21, 0x03, 0x00, 0x00, + 0x08, 0xa6, 0x88, 0x04, 0xa4, 0x02, 0xa0, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x02, 0x00, 0x24, 0x02, + 0x04, 0x0a, 0x00, 0x00, 0x08, 0xa6, 0x00, 0x08, + 0x11); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd3, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x18, 0x18, 0x32, 0x10, 0x09, 0x00, 0x09, + 0x32, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x11, 0x00, 0x02, 0x02, 0x03, 0x00, + 0x00, 0x00, 0x0a, 0x40); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd5, 0x18, 0x18, 0x18, 0x18, 0x21, + 0x20, 0x18, 0x18, 0x19, 0x19, 0x19, 0x19, 0x18, + 0x18, 0x18, 0x18, 0x03, 0x02, 0x01, 0x00, 0x2f, + 0x2f, 0x30, 0x30, 0x31, 0x31, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd6, 0x18, 0x18, 0x18, 0x18, 0x20, + 0x21, 0x19, 0x19, 0x18, 0x18, 0x19, 0x19, 0x18, + 0x18, 0x18, 0x18, 0x00, 0x01, 0x02, 0x03, 0x2f, + 0x2f, 0x30, 0x30, 0x31, 0x31, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbd, 0x01); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd8, 0x0a, 0xbe, 0xfa, 0xa0, 0x0a, + 0xbe, 0xfa, 0xa0); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd8, 0x0f, 0xff, 0xff, 0xe0, 0x0f, + 0xff, 0xff, 0xe0); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbd, 0x02); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd8, 0x0f, 0xff, 0xff, 0xe0, 0x0f, + 0xff, 0xff, 0xe0); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe0, 0x01, 0x11, 0x1c, 0x17, 0x39, + 0x43, 0x54, 0x51, 0x5a, 0x64, 0x6c, 0x74, 0x7a, + 0x83, 0x8d, 0x92, 0x99, 0xa4, 0xa9, 0xb4, 0xaa, + 0xba, 0xbe, 0x63, 0x5e, 0x69, 0x73, 0x01, 0x11, + 0x1c, 0x17, 0x39, 0x43, 0x54, 0x51, 0x5a, 0x64, + 0x6c, 0x74, 0x7a, 0x83, 0x8d, 0x92, 0x99, 0xa4, + 0xa7, 0xb2, 0xa9, 0xba, 0xbe, 0x63, 0x5e, 0x69, + 0x73); + mipi_dsi_usleep_range(&ctx, 200, 300); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb6, 0x92, 0x92); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcc, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbf, 0x40, 0x41, 0x50, 0x49); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc6, 0xff, 0xf9); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc0, 0x25, 0x5a); + mipi_dsi_dcs_write_seq_multi(&ctx, MIPI_DCS_SET_ADDRESS_MODE, 0x02); + mipi_dsi_dcs_exit_sleep_mode_multi(&ctx); + mipi_dsi_msleep(&ctx, 120); + + if (ctx.accum_err) { + gpiod_set_value_cansleep(lcd->enable_gpio, 0); + gpiod_set_value_cansleep(lcd->reset_gpio, 1); + regulator_disable(lcd->supply); + } + + return ctx.accum_err; +} + +static int lincoln_lcd197_panel_unprepare(struct drm_panel *panel) +{ + struct lincoln_lcd197_panel *lcd = to_lincoln_lcd197_panel(panel); + struct mipi_dsi_multi_context ctx = { .dsi = lcd->dsi }; + + mipi_dsi_dcs_enter_sleep_mode_multi(&ctx); + mipi_dsi_usleep_range(&ctx, 5000, 6000); + gpiod_set_value_cansleep(lcd->enable_gpio, 0); + gpiod_set_value_cansleep(lcd->reset_gpio, 1); + regulator_disable(lcd->supply); + + return ctx.accum_err; +} + +static int lincoln_lcd197_panel_enable(struct drm_panel *panel) +{ + struct lincoln_lcd197_panel *lcd = to_lincoln_lcd197_panel(panel); + struct mipi_dsi_multi_context ctx = { .dsi = lcd->dsi }; + + mipi_dsi_dcs_set_display_on_multi(&ctx); + mipi_dsi_msleep(&ctx, 20); + + return ctx.accum_err; +} + +static int lincoln_lcd197_panel_disable(struct drm_panel *panel) +{ + struct lincoln_lcd197_panel *lcd = to_lincoln_lcd197_panel(panel); + struct mipi_dsi_multi_context ctx = { .dsi = lcd->dsi }; + + mipi_dsi_dcs_set_display_off_multi(&ctx); + mipi_dsi_msleep(&ctx, 50); + + return ctx.accum_err; +} + +static const struct drm_display_mode lcd197_mode = { + .clock = 154002, + .hdisplay = 1080, + .hsync_start = 1080 + 20, + .hsync_end = 1080 + 20 + 6, + .htotal = 1080 + 204, + .vdisplay = 1920, + .vsync_start = 1920 + 4, + .vsync_end = 1920 + 4 + 4, + .vtotal = 1920 + 79, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + .width_mm = 79, + .height_mm = 125, + .type = DRM_MODE_TYPE_DRIVER, +}; + +static int lincoln_lcd197_panel_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + return drm_connector_helper_get_modes_fixed(connector, &lcd197_mode); +} + +static const struct drm_panel_funcs lincoln_lcd197_panel_funcs = { + .prepare = lincoln_lcd197_panel_prepare, + .unprepare = lincoln_lcd197_panel_unprepare, + .enable = lincoln_lcd197_panel_enable, + .disable = lincoln_lcd197_panel_disable, + .get_modes = lincoln_lcd197_panel_get_modes, +}; + +static int lincoln_lcd197_panel_probe(struct mipi_dsi_device *dsi) +{ + struct lincoln_lcd197_panel *lcd; + struct device *dev = &dsi->dev; + int err; + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = (MIPI_DSI_MODE_VIDEO | + MIPI_DSI_MODE_VIDEO_BURST); + + lcd = devm_drm_panel_alloc(dev, struct lincoln_lcd197_panel, panel, + &lincoln_lcd197_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(lcd)) + return PTR_ERR(lcd); + + mipi_dsi_set_drvdata(dsi, lcd); + lcd->dsi = dsi; + + lcd->supply = devm_regulator_get(dev, "power"); + if (IS_ERR(lcd->supply)) + return dev_err_probe(dev, PTR_ERR(lcd->supply), + "failed to get power supply"); + + lcd->enable_gpio = devm_gpiod_get(dev, "enable", + GPIOD_OUT_HIGH); + if (IS_ERR(lcd->enable_gpio)) + return dev_err_probe(dev, PTR_ERR(lcd->enable_gpio), + "failed to get enable gpio"); + + lcd->reset_gpio = devm_gpiod_get(dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(lcd->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(lcd->reset_gpio), + "failed to get reset gpio"); + + err = drm_panel_of_backlight(&lcd->panel); + if (err) + return err; + + drm_panel_add(&lcd->panel); + err = mipi_dsi_attach(dsi); + if (err) + drm_panel_remove(&lcd->panel); + + return err; +} + +static void lincoln_lcd197_panel_remove(struct mipi_dsi_device *dsi) +{ + struct lincoln_lcd197_panel *lcd = mipi_dsi_get_drvdata(dsi); + int err; + + err = mipi_dsi_detach(dsi); + if (err < 0) + dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", err); + + drm_panel_remove(&lcd->panel); +} + +static const struct of_device_id lincoln_lcd197_of_match[] = { + { .compatible = "lincolntech,lcd197", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, lincoln_lcd197_of_match); + +static struct mipi_dsi_driver lincoln_lcd197_panel_driver = { + .driver = { + .name = "panel-lincolntech-lcd197", + .of_match_table = lincoln_lcd197_of_match, + }, + .probe = lincoln_lcd197_panel_probe, + .remove = lincoln_lcd197_panel_remove, +}; +module_mipi_dsi_driver(lincoln_lcd197_panel_driver); + +MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>"); +MODULE_DESCRIPTION("Lincoln Technologies LCD197 panel driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-lvds.c b/drivers/gpu/drm/panel/panel-lvds.c index 3f6550e6b6a4..46b07f38559f 100644 --- a/drivers/gpu/drm/panel/panel-lvds.c +++ b/drivers/gpu/drm/panel/panel-lvds.c @@ -8,38 +8,36 @@ * 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> +#include <linux/of.h> #include <linux/platform_device.h> #include <linux/regulator/consumer.h> #include <linux/slab.h> -#include <drm/drmP.h> -#include <drm/drm_crtc.h> -#include <drm/drm_panel.h> - #include <video/display_timing.h> #include <video/of_display_timing.h> #include <video/videomode.h> +#include <drm/drm_crtc.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> + struct panel_lvds { struct drm_panel panel; struct device *dev; const char *label; - unsigned int width; - unsigned int height; - struct videomode video_mode; + struct drm_display_mode dmode; + u32 bus_flags; unsigned int bus_format; - bool data_mirror; - struct backlight_device *backlight; struct regulator *supply; struct gpio_desc *enable_gpio; struct gpio_desc *reset_gpio; + + enum drm_panel_orientation orientation; }; static inline struct panel_lvds *to_panel_lvds(struct drm_panel *panel) @@ -47,19 +45,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); @@ -94,100 +79,80 @@ 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_duplicate(connector->dev, &lvds->dmode); if (!mode) return 0; - drm_display_mode_from_videomode(&lvds->video_mode, mode); mode->type |= DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; drm_mode_probed_add(connector, mode); - connector->display_info.width_mm = lvds->width; - connector->display_info.height_mm = lvds->height; + connector->display_info.width_mm = lvds->dmode.width_mm; + connector->display_info.height_mm = lvds->dmode.height_mm; drm_display_info_set_bus_formats(&connector->display_info, &lvds->bus_format, 1); - connector->display_info.bus_flags = lvds->data_mirror - ? DRM_BUS_FLAG_DATA_LSB_TO_MSB - : DRM_BUS_FLAG_DATA_MSB_TO_LSB; + connector->display_info.bus_flags = lvds->bus_flags; + + /* + * TODO: Remove once all drm drivers call + * drm_connector_set_orientation_from_panel() + */ + drm_connector_set_panel_orientation(connector, lvds->orientation); return 1; } +static enum drm_panel_orientation panel_lvds_get_orientation(struct drm_panel *panel) +{ + struct panel_lvds *lvds = to_panel_lvds(panel); + + return lvds->orientation; +} + 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, + .get_orientation = panel_lvds_get_orientation, }; static int panel_lvds_parse_dt(struct panel_lvds *lvds) { struct device_node *np = lvds->dev->of_node; - struct display_timing timing; - const char *mapping; int ret; - ret = of_get_display_timing(np, "panel-timing", &timing); - if (ret < 0) - return ret; - - videomode_from_timing(&timing, &lvds->video_mode); - - ret = of_property_read_u32(np, "width-mm", &lvds->width); + ret = of_drm_get_panel_orientation(np, &lvds->orientation); if (ret < 0) { - dev_err(lvds->dev, "%pOF: invalid or missing %s DT property\n", - np, "width-mm"); - return -ENODEV; + dev_err(lvds->dev, "%pOF: failed to get orientation %d\n", np, ret); + return ret; } - ret = of_property_read_u32(np, "height-mm", &lvds->height); + + ret = of_get_drm_panel_display_mode(np, &lvds->dmode, &lvds->bus_flags); if (ret < 0) { - dev_err(lvds->dev, "%pOF: invalid or missing %s DT property\n", - np, "height-mm"); - return -ENODEV; + dev_err(lvds->dev, "%pOF: problems parsing panel-timing (%d)\n", + np, ret); + return ret; } of_property_read_string(np, "label", &lvds->label); - ret = of_property_read_string(np, "data-mapping", &mapping); + ret = drm_of_lvds_get_data_mapping(np); if (ret < 0) { dev_err(lvds->dev, "%pOF: invalid or missing %s DT property\n", np, "data-mapping"); - return -ENODEV; + return ret; } - if (!strcmp(mapping, "jeida-18")) { - lvds->bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG; - } else if (!strcmp(mapping, "jeida-24")) { - lvds->bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA; - } else if (!strcmp(mapping, "vesa-24")) { - lvds->bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG; - } else { - dev_err(lvds->dev, "%pOF: invalid or missing %s DT property\n", - np, "data-mapping"); - return -EINVAL; - } + lvds->bus_format = ret; - lvds->data_mirror = of_property_read_bool(np, "data-mirror"); + lvds->bus_flags |= of_property_read_bool(np, "data-mirror") ? + DRM_BUS_FLAG_DATA_LSB_TO_MSB : + DRM_BUS_FLAG_DATA_MSB_TO_LSB; return 0; } @@ -195,12 +160,13 @@ 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); - if (!lvds) - return -ENOMEM; + lvds = devm_drm_panel_alloc(&pdev->dev, struct panel_lvds, panel, + &panel_lvds_funcs, + DRM_MODE_CONNECTOR_LVDS); + if (IS_ERR(lvds)) + return PTR_ERR(lvds); lvds->dev = &pdev->dev; @@ -241,15 +207,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,35 +214,23 @@ static int panel_lvds_probe(struct platform_device *pdev) * driver. */ - /* Register the panel. */ - drm_panel_init(&lvds->panel); - lvds->panel.dev = lvds->dev; - lvds->panel.funcs = &panel_lvds_funcs; + ret = drm_panel_of_backlight(&lvds->panel); + if (ret) + return ret; - ret = drm_panel_add(&lvds->panel); - if (ret < 0) - goto error; + drm_panel_add(&lvds->panel); 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) +static void panel_lvds_remove(struct platform_device *pdev) { - struct panel_lvds *lvds = dev_get_drvdata(&pdev->dev); + struct panel_lvds *lvds = platform_get_drvdata(pdev); drm_panel_remove(&lvds->panel); - panel_lvds_disable(&lvds->panel); - - if (lvds->backlight) - put_device(&lvds->backlight->dev); - - return 0; + drm_panel_disable(&lvds->panel); } static const struct of_device_id panel_lvds_of_table[] = { diff --git a/drivers/gpu/drm/panel/panel-magnachip-d53e6ea8966.c b/drivers/gpu/drm/panel/panel-magnachip-d53e6ea8966.c new file mode 100644 index 000000000000..cde168ec631c --- /dev/null +++ b/drivers/gpu/drm/panel/panel-magnachip-d53e6ea8966.c @@ -0,0 +1,520 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Magnachip d53e6ea8966 MIPI-DSI panel driver + * Copyright (C) 2023 Chris Morgan + */ + +#include <drm/drm_mipi_dbi.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> + +#include <linux/backlight.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/media-bus-format.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regulator/consumer.h> +#include <linux/spi/spi.h> + +#include <video/mipi_display.h> + +/* Forward declaration for use in backlight function */ +struct d53e6ea8966; + +/* Panel info, unique to each panel */ +struct d53e6ea8966_panel_info { + /** @display_modes: the supported display modes */ + const struct drm_display_mode *display_modes; + /** @num_modes: the number of supported display modes */ + unsigned int num_modes; + /** @width_mm: panel width in mm */ + u16 width_mm; + /** @height_mm: panel height in mm */ + u16 height_mm; + /** @bus_flags: drm bus flags for panel */ + u32 bus_flags; + /** @panel_init_seq: panel specific init sequence */ + void (*panel_init_seq)(struct d53e6ea8966 *db); + /** @backlight_register: panel backlight registration or NULL */ + int (*backlight_register)(struct d53e6ea8966 *db); +}; + +struct d53e6ea8966 { + /** @dev: the container device */ + struct device *dev; + /** @dbi: the DBI bus abstraction handle */ + struct mipi_dbi dbi; + /** @panel: the DRM panel instance for this device */ + struct drm_panel panel; + /** @reset: reset GPIO line */ + struct gpio_desc *reset; + /** @enable: enable GPIO line */ + struct gpio_desc *enable; + /** @reg_vdd: VDD supply regulator for panel logic */ + struct regulator *reg_vdd; + /** @reg_elvdd: ELVDD supply regulator for panel display */ + struct regulator *reg_elvdd; + /** @dsi_dev: DSI child device (panel) */ + struct mipi_dsi_device *dsi_dev; + /** @bl_dev: pseudo-backlight device for oled panel */ + struct backlight_device *bl_dev; + /** @panel_info: struct containing panel timing and info */ + const struct d53e6ea8966_panel_info *panel_info; +}; + +#define NUM_GAMMA_LEVELS 16 +#define GAMMA_TABLE_COUNT 23 +#define MAX_BRIGHTNESS (NUM_GAMMA_LEVELS - 1) + +#define MCS_ELVSS_ON 0xb1 +#define MCS_TEMP_SWIRE 0xb2 +#define MCS_PASSWORD_0 0xf0 +#define MCS_PASSWORD_1 0xf1 +#define MCS_ANALOG_PWR_CTL_0 0xf4 +#define MCS_ANALOG_PWR_CTL_1 0xf5 +#define MCS_GTCON_SET 0xf7 +#define MCS_GATELESS_SIGNAL_SET 0xf8 +#define MCS_SET_GAMMA 0xf9 + +static inline struct d53e6ea8966 *to_d53e6ea8966(struct drm_panel *panel) +{ + return container_of(panel, struct d53e6ea8966, panel); +} + +/* Table of gamma values provided in datasheet */ +static u8 ams495qa01_gamma[NUM_GAMMA_LEVELS][GAMMA_TABLE_COUNT] = { + {0x01, 0x79, 0x78, 0x8d, 0xd9, 0xdf, 0xd5, 0xcb, 0xcf, 0xc5, + 0xe5, 0xe0, 0xe4, 0xdc, 0xb8, 0xd4, 0xfa, 0xed, 0xe6, 0x2f, + 0x00, 0x2f}, + {0x01, 0x7d, 0x7c, 0x92, 0xd7, 0xdd, 0xd2, 0xcb, 0xd0, 0xc6, + 0xe5, 0xe1, 0xe3, 0xda, 0xbd, 0xd3, 0xfa, 0xed, 0xe6, 0x2f, + 0x00, 0x2f}, + {0x01, 0x7f, 0x7e, 0x95, 0xd7, 0xde, 0xd2, 0xcb, 0xcf, 0xc5, + 0xe5, 0xe3, 0xe3, 0xda, 0xbf, 0xd3, 0xfa, 0xed, 0xe6, 0x2f, + 0x00, 0x2f}, + {0x01, 0x82, 0x81, 0x99, 0xd6, 0xdd, 0xd1, 0xca, 0xcf, 0xc3, + 0xe4, 0xe3, 0xe3, 0xda, 0xc2, 0xd3, 0xfa, 0xed, 0xe6, 0x2f, + 0x00, 0x2f}, + {0x01, 0x84, 0x83, 0x9b, 0xd7, 0xde, 0xd2, 0xc8, 0xce, 0xc2, + 0xe4, 0xe3, 0xe2, 0xd9, 0xc3, 0xd3, 0xfa, 0xed, 0xe6, 0x2f, + 0x00, 0x2f}, + {0x01, 0x87, 0x86, 0x9f, 0xd6, 0xdd, 0xd1, 0xc7, 0xce, 0xc1, + 0xe4, 0xe3, 0xe2, 0xd9, 0xc6, 0xd3, 0xfa, 0xed, 0xe6, 0x2f, + 0x00, 0x2f}, + {0x01, 0x89, 0x89, 0xa2, 0xd5, 0xdb, 0xcf, 0xc8, 0xcf, 0xc2, + 0xe3, 0xe3, 0xe1, 0xd9, 0xc7, 0xd3, 0xfa, 0xed, 0xe6, 0x2f, + 0x00, 0x2f}, + {0x01, 0x8b, 0x8b, 0xa5, 0xd5, 0xdb, 0xcf, 0xc7, 0xce, 0xc0, + 0xe3, 0xe3, 0xe1, 0xd8, 0xc7, 0xd3, 0xfa, 0xed, 0xe6, 0x2f, + 0x00, 0x2f}, + {0x01, 0x8d, 0x8d, 0xa7, 0xd5, 0xdb, 0xcf, 0xc6, 0xce, 0xc0, + 0xe4, 0xe4, 0xe1, 0xd7, 0xc8, 0xd3, 0xfa, 0xed, 0xe6, 0x2f, + 0x00, 0x2f}, + {0x01, 0x8f, 0x8f, 0xaa, 0xd4, 0xdb, 0xce, 0xc6, 0xcd, 0xbf, + 0xe3, 0xe3, 0xe1, 0xd7, 0xca, 0xd3, 0xfa, 0xed, 0xe6, 0x2f, + 0x00, 0x2f}, + {0x01, 0x91, 0x91, 0xac, 0xd3, 0xda, 0xce, 0xc5, 0xcd, 0xbe, + 0xe3, 0xe3, 0xe0, 0xd7, 0xca, 0xd3, 0xfa, 0xed, 0xe6, 0x2f, + 0x00, 0x2f}, + {0x01, 0x93, 0x93, 0xaf, 0xd3, 0xda, 0xcd, 0xc5, 0xcd, 0xbe, + 0xe2, 0xe3, 0xdf, 0xd6, 0xca, 0xd3, 0xfa, 0xed, 0xe6, 0x2f, + 0x00, 0x2f}, + {0x01, 0x95, 0x95, 0xb1, 0xd2, 0xd9, 0xcc, 0xc4, 0xcd, 0xbe, + 0xe2, 0xe3, 0xdf, 0xd7, 0xcc, 0xd3, 0xfa, 0xed, 0xe6, 0x2f, + 0x00, 0x2f}, + {0x01, 0x99, 0x99, 0xb6, 0xd1, 0xd9, 0xcc, 0xc3, 0xcb, 0xbc, + 0xe2, 0xe4, 0xdf, 0xd6, 0xcc, 0xd3, 0xfa, 0xed, 0xe6, 0x2f, + 0x00, 0x2f}, + {0x01, 0x9c, 0x9c, 0xba, 0xd0, 0xd8, 0xcb, 0xc3, 0xcb, 0xbb, + 0xe2, 0xe4, 0xdf, 0xd6, 0xce, 0xd3, 0xfa, 0xed, 0xe6, 0x2f, + 0x00, 0x2f}, + {0x01, 0x9f, 0x9f, 0xbe, 0xcf, 0xd7, 0xc9, 0xc2, 0xcb, 0xbb, + 0xe1, 0xe3, 0xde, 0xd6, 0xd0, 0xd3, 0xfa, 0xed, 0xe6, 0x2f, + 0x00, 0x2f}, +}; + +/* + * Table of elvss values provided in datasheet and corresponds to + * gamma values. + */ +static u8 ams495qa01_elvss[NUM_GAMMA_LEVELS] = { + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x14, 0x14, 0x13, 0x12, +}; + +static int ams495qa01_update_gamma(struct mipi_dbi *dbi, int brightness) +{ + int tmp = brightness; + + mipi_dbi_command_buf(dbi, MCS_SET_GAMMA, ams495qa01_gamma[tmp], + ARRAY_SIZE(ams495qa01_gamma[tmp])); + mipi_dbi_command(dbi, MCS_SET_GAMMA, 0x00); + + /* Undocumented command */ + mipi_dbi_command(dbi, 0x26, 0x00); + + mipi_dbi_command(dbi, MCS_TEMP_SWIRE, ams495qa01_elvss[tmp]); + + return 0; +} + +static void ams495qa01_panel_init(struct d53e6ea8966 *db) +{ + struct mipi_dbi *dbi = &db->dbi; + + mipi_dbi_command(dbi, MCS_PASSWORD_0, 0x5a, 0x5a); + mipi_dbi_command(dbi, MCS_PASSWORD_1, 0x5a, 0x5a); + + /* Undocumented commands */ + mipi_dbi_command(dbi, 0xb0, 0x02); + mipi_dbi_command(dbi, 0xf3, 0x3b); + + mipi_dbi_command(dbi, MCS_ANALOG_PWR_CTL_0, 0x33, 0x42, 0x00, 0x08); + mipi_dbi_command(dbi, MCS_ANALOG_PWR_CTL_1, 0x00, 0x06, 0x26, 0x35, 0x03); + + /* Undocumented commands */ + mipi_dbi_command(dbi, 0xf6, 0x02); + mipi_dbi_command(dbi, 0xc6, 0x0b, 0x00, 0x00, 0x3c, 0x00, 0x22, + 0x00, 0x00, 0x00, 0x00); + + mipi_dbi_command(dbi, MCS_GTCON_SET, 0x20); + mipi_dbi_command(dbi, MCS_TEMP_SWIRE, 0x06, 0x06, 0x06, 0x06); + mipi_dbi_command(dbi, MCS_ELVSS_ON, 0x07, 0x00, 0x10); + mipi_dbi_command(dbi, MCS_GATELESS_SIGNAL_SET, 0x7f, 0x7a, + 0x89, 0x67, 0x26, 0x38, 0x00, 0x00, 0x09, + 0x67, 0x70, 0x88, 0x7a, 0x76, 0x05, 0x09, + 0x23, 0x23, 0x23); + + /* Undocumented commands */ + mipi_dbi_command(dbi, 0xb5, 0xff, 0xef, 0x35, 0x42, 0x0d, 0xd7, + 0xff, 0x07, 0xff, 0xff, 0xfd, 0x00, 0x01, + 0xff, 0x05, 0x12, 0x0f, 0xff, 0xff, 0xff, + 0xff); + mipi_dbi_command(dbi, 0xb4, 0x15); + mipi_dbi_command(dbi, 0xb3, 0x00); + + ams495qa01_update_gamma(dbi, MAX_BRIGHTNESS); +} + +static int d53e6ea8966_prepare(struct drm_panel *panel) +{ + struct d53e6ea8966 *db = to_d53e6ea8966(panel); + int ret; + + /* Power up */ + ret = regulator_enable(db->reg_vdd); + if (ret) { + dev_err(db->dev, "failed to enable vdd regulator: %d\n", ret); + return ret; + } + + if (db->reg_elvdd) { + ret = regulator_enable(db->reg_elvdd); + if (ret) { + dev_err(db->dev, + "failed to enable elvdd regulator: %d\n", ret); + regulator_disable(db->reg_vdd); + return ret; + } + } + + /* Enable */ + if (db->enable) + gpiod_set_value_cansleep(db->enable, 1); + + msleep(50); + + /* Reset */ + gpiod_set_value_cansleep(db->reset, 1); + usleep_range(1000, 5000); + gpiod_set_value_cansleep(db->reset, 0); + msleep(20); + + db->panel_info->panel_init_seq(db); + + return 0; +} + +static int d53e6ea8966_enable(struct drm_panel *panel) +{ + struct d53e6ea8966 *db = to_d53e6ea8966(panel); + struct mipi_dbi *dbi = &db->dbi; + + mipi_dbi_command(dbi, MIPI_DCS_EXIT_SLEEP_MODE); + msleep(200); + mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_ON); + usleep_range(10000, 15000); + + return 0; +} + +static int d53e6ea8966_disable(struct drm_panel *panel) +{ + struct d53e6ea8966 *db = to_d53e6ea8966(panel); + struct mipi_dbi *dbi = &db->dbi; + + mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_OFF); + msleep(20); + mipi_dbi_command(dbi, MIPI_DCS_ENTER_SLEEP_MODE); + msleep(100); + + return 0; +} + +static int d53e6ea8966_unprepare(struct drm_panel *panel) +{ + struct d53e6ea8966 *db = to_d53e6ea8966(panel); + + if (db->enable) + gpiod_set_value_cansleep(db->enable, 0); + + gpiod_set_value_cansleep(db->reset, 1); + + if (db->reg_elvdd) + regulator_disable(db->reg_elvdd); + + regulator_disable(db->reg_vdd); + msleep(100); + + return 0; +} + +static int d53e6ea8966_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct d53e6ea8966 *db = to_d53e6ea8966(panel); + const struct d53e6ea8966_panel_info *panel_info = db->panel_info; + struct drm_display_mode *mode; + static const u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24; + unsigned int i; + + for (i = 0; i < panel_info->num_modes; i++) { + mode = drm_mode_duplicate(connector->dev, + &panel_info->display_modes[i]); + if (!mode) + return -ENOMEM; + + drm_mode_set_name(mode); + 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; + connector->display_info.bus_flags = panel_info->bus_flags; + + drm_display_info_set_bus_formats(&connector->display_info, + &bus_format, 1); + + return 1; +} + +static const struct drm_panel_funcs d53e6ea8966_panel_funcs = { + .disable = d53e6ea8966_disable, + .enable = d53e6ea8966_enable, + .get_modes = d53e6ea8966_get_modes, + .prepare = d53e6ea8966_prepare, + .unprepare = d53e6ea8966_unprepare, +}; + +static int ams495qa01_set_brightness(struct backlight_device *bd) +{ + struct d53e6ea8966 *db = bl_get_data(bd); + struct mipi_dbi *dbi = &db->dbi; + int brightness = backlight_get_brightness(bd); + + ams495qa01_update_gamma(dbi, brightness); + + return 0; +} + +static const struct backlight_ops ams495qa01_backlight_ops = { + .update_status = ams495qa01_set_brightness, +}; + +static int ams495qa01_backlight_register(struct d53e6ea8966 *db) +{ + struct backlight_properties props = { + .type = BACKLIGHT_RAW, + .brightness = MAX_BRIGHTNESS, + .max_brightness = MAX_BRIGHTNESS, + }; + struct device *dev = db->dev; + int ret = 0; + + db->bl_dev = devm_backlight_device_register(dev, "panel", dev, db, + &ams495qa01_backlight_ops, + &props); + if (IS_ERR(db->bl_dev)) { + ret = PTR_ERR(db->bl_dev); + dev_err(dev, "error registering backlight device (%d)\n", ret); + } + + return ret; +} + +static int d53e6ea8966_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct mipi_dsi_host *dsi_host; + struct d53e6ea8966 *db; + int ret; + struct mipi_dsi_device_info info = { + .type = "d53e6ea8966", + .channel = 0, + .node = NULL, + }; + + db = devm_drm_panel_alloc(dev, struct d53e6ea8966, panel, + &d53e6ea8966_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(db)) + return PTR_ERR(db); + + spi_set_drvdata(spi, db); + + db->dev = dev; + + db->panel_info = of_device_get_match_data(dev); + if (!db->panel_info) + return -EINVAL; + + db->reg_vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(db->reg_vdd)) + return dev_err_probe(dev, PTR_ERR(db->reg_vdd), + "Failed to get vdd supply\n"); + + db->reg_elvdd = devm_regulator_get_optional(dev, "elvdd"); + if (IS_ERR(db->reg_elvdd)) + db->reg_elvdd = NULL; + + db->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(db->reset)) { + ret = PTR_ERR(db->reset); + return dev_err_probe(dev, ret, "no RESET GPIO\n"); + } + + db->enable = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(db->enable)) { + ret = PTR_ERR(db->enable); + return dev_err_probe(dev, ret, "cannot get ENABLE GPIO\n"); + } + + ret = mipi_dbi_spi_init(spi, &db->dbi, NULL); + if (ret) + return dev_err_probe(dev, ret, "MIPI DBI init failed\n"); + + dsi_host = drm_of_get_dsi_bus(dev); + if (IS_ERR(dsi_host)) { + ret = PTR_ERR(dsi_host); + return dev_err_probe(dev, ret, "Error attaching DSI bus\n"); + } + + db->dsi_dev = devm_mipi_dsi_device_register_full(dev, dsi_host, &info); + if (IS_ERR(db->dsi_dev)) { + dev_err(dev, "failed to register dsi device: %ld\n", + PTR_ERR(db->dsi_dev)); + return PTR_ERR(db->dsi_dev); + } + + db->dsi_dev->lanes = 2; + db->dsi_dev->format = MIPI_DSI_FMT_RGB888; + db->dsi_dev->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_NO_EOT_PACKET; + + if (db->panel_info->backlight_register) { + ret = db->panel_info->backlight_register(db); + if (ret < 0) + return ret; + db->panel.backlight = db->bl_dev; + } + + drm_panel_add(&db->panel); + + ret = devm_mipi_dsi_attach(dev, db->dsi_dev); + if (ret < 0) { + dev_err(dev, "mipi_dsi_attach failed: %d\n", ret); + drm_panel_remove(&db->panel); + return ret; + } + + return 0; +} + +static void d53e6ea8966_remove(struct spi_device *spi) +{ + struct d53e6ea8966 *db = spi_get_drvdata(spi); + + drm_panel_remove(&db->panel); +} + +static const struct drm_display_mode ams495qa01_modes[] = { + { /* 60hz */ + .clock = 33500, + .hdisplay = 960, + .hsync_start = 960 + 10, + .hsync_end = 960 + 10 + 2, + .htotal = 960 + 10 + 2 + 10, + .vdisplay = 544, + .vsync_start = 544 + 10, + .vsync_end = 544 + 10 + 2, + .vtotal = 544 + 10 + 2 + 10, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, + }, + { /* 50hz */ + .clock = 27800, + .hdisplay = 960, + .hsync_start = 960 + 10, + .hsync_end = 960 + 10 + 2, + .htotal = 960 + 10 + 2 + 10, + .vdisplay = 544, + .vsync_start = 544 + 10, + .vsync_end = 544 + 10 + 2, + .vtotal = 544 + 10 + 2 + 10, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + .type = DRM_MODE_TYPE_DRIVER, + }, +}; + +static const struct d53e6ea8966_panel_info ams495qa01_info = { + .display_modes = ams495qa01_modes, + .num_modes = ARRAY_SIZE(ams495qa01_modes), + .width_mm = 117, + .height_mm = 74, + .bus_flags = DRM_BUS_FLAG_DE_LOW | DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE, + .panel_init_seq = ams495qa01_panel_init, + .backlight_register = ams495qa01_backlight_register, +}; + +static const struct of_device_id d53e6ea8966_match[] = { + { .compatible = "samsung,ams495qa01", .data = &ams495qa01_info }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, d53e6ea8966_match); + +static const struct spi_device_id d53e6ea8966_ids[] = { + { "ams495qa01", 0 }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(spi, d53e6ea8966_ids); + +static struct spi_driver d53e6ea8966_driver = { + .driver = { + .name = "d53e6ea8966-panel", + .of_match_table = d53e6ea8966_match, + }, + .id_table = d53e6ea8966_ids, + .probe = d53e6ea8966_probe, + .remove = d53e6ea8966_remove, +}; +module_spi_driver(d53e6ea8966_driver); + +MODULE_AUTHOR("Chris Morgan <macromorgan@hotmail.com>"); +MODULE_DESCRIPTION("Magnachip d53e6ea8966 panel driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-mantix-mlaf057we51.c b/drivers/gpu/drm/panel/panel-mantix-mlaf057we51.c new file mode 100644 index 000000000000..55664f5d5aa5 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-mantix-mlaf057we51.c @@ -0,0 +1,335 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Mantix MLAF057WE51 5.7" MIPI-DSI panel driver + * + * Copyright (C) Purism SPC 2020 + */ + +#include <linux/backlight.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> + +#include <video/mipi_display.h> + +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +#define DRV_NAME "panel-mantix-mlaf057we51" + +/* Manufacturer specific Commands send via DSI */ +#define MANTIX_CMD_OTP_STOP_RELOAD_MIPI 0x41 +#define MANTIX_CMD_INT_CANCEL 0x4c +#define MANTIX_CMD_SPI_FINISH 0x90 + +struct mantix { + struct device *dev; + struct drm_panel panel; + + struct gpio_desc *reset_gpio; + struct gpio_desc *tp_rstn_gpio; + + struct regulator *avdd; + struct regulator *avee; + struct regulator *vddi; + + const struct drm_display_mode *default_mode; +}; + +static inline struct mantix *panel_to_mantix(struct drm_panel *panel) +{ + return container_of(panel, struct mantix, panel); +} + +static void mantix_init_sequence(struct mipi_dsi_multi_context *dsi_ctx) +{ + /* + * Init sequence was supplied by the panel vendor. + */ + mipi_dsi_generic_write_seq_multi(dsi_ctx, MANTIX_CMD_OTP_STOP_RELOAD_MIPI, 0x5a); + + mipi_dsi_generic_write_seq_multi(dsi_ctx, MANTIX_CMD_INT_CANCEL, 0x03); + mipi_dsi_generic_write_seq_multi(dsi_ctx, MANTIX_CMD_OTP_STOP_RELOAD_MIPI, 0x5a, 0x03); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x80, 0xa9, 0x00); + + mipi_dsi_generic_write_seq_multi(dsi_ctx, MANTIX_CMD_OTP_STOP_RELOAD_MIPI, 0x5a, 0x09); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x80, 0x64, 0x00, 0x64, 0x00, 0x00); + mipi_dsi_msleep(dsi_ctx, 20); + + mipi_dsi_generic_write_seq_multi(dsi_ctx, MANTIX_CMD_SPI_FINISH, 0xa5); + mipi_dsi_generic_write_seq_multi(dsi_ctx, MANTIX_CMD_OTP_STOP_RELOAD_MIPI, 0x00, 0x2f); + mipi_dsi_msleep(dsi_ctx, 20); +} + +static int mantix_enable(struct drm_panel *panel) +{ + struct mantix *ctx = panel_to_mantix(panel); + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + mantix_init_sequence(&dsi_ctx); + if (!dsi_ctx.accum_err) + dev_dbg(ctx->dev, "Panel init sequence done\n"); + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 20); + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + mipi_dsi_usleep_range(&dsi_ctx, 10000, 12000); + + mipi_dsi_turn_on_peripheral_multi(&dsi_ctx); + + return dsi_ctx.accum_err; +} + +static int mantix_disable(struct drm_panel *panel) +{ + struct mantix *ctx = panel_to_mantix(panel); + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + + return dsi_ctx.accum_err; +} + +static int mantix_unprepare(struct drm_panel *panel) +{ + struct mantix *ctx = panel_to_mantix(panel); + + gpiod_set_value_cansleep(ctx->tp_rstn_gpio, 1); + usleep_range(5000, 6000); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + + regulator_disable(ctx->avee); + regulator_disable(ctx->avdd); + /* T11 */ + usleep_range(5000, 6000); + regulator_disable(ctx->vddi); + /* T14 */ + msleep(50); + + return 0; +} + +static int mantix_prepare(struct drm_panel *panel) +{ + struct mantix *ctx = panel_to_mantix(panel); + int ret; + + /* Focaltech FT8006P, section 7.3.1 and 7.3.4 */ + dev_dbg(ctx->dev, "Resetting the panel\n"); + ret = regulator_enable(ctx->vddi); + if (ret < 0) { + dev_err(ctx->dev, "Failed to enable vddi supply: %d\n", ret); + return ret; + } + + /* T1 + T2 */ + usleep_range(8000, 10000); + + ret = regulator_enable(ctx->avdd); + if (ret < 0) { + dev_err(ctx->dev, "Failed to enable avdd supply: %d\n", ret); + return ret; + } + + /* T2d */ + usleep_range(3500, 4000); + ret = regulator_enable(ctx->avee); + if (ret < 0) { + dev_err(ctx->dev, "Failed to enable avee supply: %d\n", ret); + return ret; + } + + /* T3 + T4 + time for voltage to become stable: */ + usleep_range(6000, 7000); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + gpiod_set_value_cansleep(ctx->tp_rstn_gpio, 0); + + /* T6 */ + msleep(50); + + return 0; +} + +static const struct drm_display_mode default_mode_mantix = { + .hdisplay = 720, + .hsync_start = 720 + 45, + .hsync_end = 720 + 45 + 14, + .htotal = 720 + 45 + 14 + 25, + .vdisplay = 1440, + .vsync_start = 1440 + 130, + .vsync_end = 1440 + 130 + 8, + .vtotal = 1440 + 130 + 8 + 106, + .clock = 85298, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + .width_mm = 65, + .height_mm = 130, +}; + +static const struct drm_display_mode default_mode_ys = { + .hdisplay = 720, + .hsync_start = 720 + 45, + .hsync_end = 720 + 45 + 14, + .htotal = 720 + 45 + 14 + 25, + .vdisplay = 1440, + .vsync_start = 1440 + 175, + .vsync_end = 1440 + 175 + 8, + .vtotal = 1440 + 175 + 8 + 50, + .clock = 85298, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + .width_mm = 65, + .height_mm = 130, +}; + +static const u32 mantix_bus_formats[] = { + MEDIA_BUS_FMT_RGB888_1X24, +}; + +static int mantix_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct mantix *ctx = panel_to_mantix(panel); + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, ctx->default_mode); + if (!mode) { + dev_err(ctx->dev, "Failed to add mode %ux%u@%u\n", + ctx->default_mode->hdisplay, ctx->default_mode->vdisplay, + drm_mode_vrefresh(ctx->default_mode)); + 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); + + drm_display_info_set_bus_formats(&connector->display_info, + mantix_bus_formats, + ARRAY_SIZE(mantix_bus_formats)); + + return 1; +} + +static const struct drm_panel_funcs mantix_drm_funcs = { + .disable = mantix_disable, + .unprepare = mantix_unprepare, + .prepare = mantix_prepare, + .enable = mantix_enable, + .get_modes = mantix_get_modes, +}; + +static int mantix_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct mantix *ctx; + int ret; + + ctx = devm_drm_panel_alloc(dev, struct mantix, panel, &mantix_drm_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ctx->default_mode = of_device_get_match_data(dev); + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->reset_gpio)) { + dev_err(dev, "cannot get reset gpio\n"); + return PTR_ERR(ctx->reset_gpio); + } + + ctx->tp_rstn_gpio = devm_gpiod_get(dev, "mantix,tp-rstn", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->tp_rstn_gpio)) { + dev_err(dev, "cannot get tp-rstn gpio\n"); + return PTR_ERR(ctx->tp_rstn_gpio); + } + + 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_VIDEO_SYNC_PULSE; + + ctx->avdd = devm_regulator_get(dev, "avdd"); + if (IS_ERR(ctx->avdd)) + return dev_err_probe(dev, PTR_ERR(ctx->avdd), "Failed to request avdd regulator\n"); + + ctx->avee = devm_regulator_get(dev, "avee"); + if (IS_ERR(ctx->avee)) + return dev_err_probe(dev, PTR_ERR(ctx->avee), "Failed to request avee regulator\n"); + + ctx->vddi = devm_regulator_get(dev, "vddi"); + if (IS_ERR(ctx->vddi)) + return dev_err_probe(dev, PTR_ERR(ctx->vddi), "Failed to request vddi regulator\n"); + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return ret; + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "mipi_dsi_attach failed (%d). Is host ready?\n", ret); + drm_panel_remove(&ctx->panel); + return ret; + } + + dev_info(dev, "%ux%u@%u %ubpp dsi %udl - ready\n", + ctx->default_mode->hdisplay, ctx->default_mode->vdisplay, + drm_mode_vrefresh(ctx->default_mode), + mipi_dsi_pixel_format_to_bpp(dsi->format), dsi->lanes); + + return 0; +} + +static void mantix_shutdown(struct mipi_dsi_device *dsi) +{ + struct mantix *ctx = mipi_dsi_get_drvdata(dsi); + + drm_panel_unprepare(&ctx->panel); + drm_panel_disable(&ctx->panel); +} + +static void mantix_remove(struct mipi_dsi_device *dsi) +{ + struct mantix *ctx = mipi_dsi_get_drvdata(dsi); + + mantix_shutdown(dsi); + + mipi_dsi_detach(dsi); + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id mantix_of_match[] = { + { .compatible = "mantix,mlaf057we51-x", .data = &default_mode_mantix }, + { .compatible = "ys,ys57pss36bh5gq", .data = &default_mode_ys }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mantix_of_match); + +static struct mipi_dsi_driver mantix_driver = { + .probe = mantix_probe, + .remove = mantix_remove, + .shutdown = mantix_shutdown, + .driver = { + .name = DRV_NAME, + .of_match_table = mantix_of_match, + }, +}; +module_mipi_dsi_driver(mantix_driver); + +MODULE_AUTHOR("Guido Günther <agx@sigxcpu.org>"); +MODULE_DESCRIPTION("DRM driver for Mantix MLAF057WE51-X MIPI DSI panel"); +MODULE_LICENSE("GPL v2"); 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..d5c7210de4af --- /dev/null +++ b/drivers/gpu/drm/panel/panel-nec-nl8048hl11.c @@ -0,0 +1,251 @@ +// 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, + .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_drm_panel_alloc(&spi->dev, struct nl8048_panel, panel, + &nl8048_funcs, DRM_MODE_CONNECTOR_DPI); + if (IS_ERR(lcd)) + return PTR_ERR(lcd); + + 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_add(&lcd->panel); + + return 0; +} + +static void 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); +} + +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-newvision-nv3051d.c b/drivers/gpu/drm/panel/panel-newvision-nv3051d.c new file mode 100644 index 000000000000..22560384e48e --- /dev/null +++ b/drivers/gpu/drm/panel/panel-newvision-nv3051d.c @@ -0,0 +1,541 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * NV3051D MIPI-DSI panel driver for Anbernic RG353x + * Copyright (C) 2022 Chris Morgan + * + * based on + * + * Elida kd35t133 3.5" MIPI-DSI panel driver + * Copyright (C) Theobroma Systems 2020 + */ + +#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> + +#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> + +struct nv3051d_panel_info { + const struct drm_display_mode *display_modes; + unsigned int num_modes; + u16 width_mm, height_mm; + u32 bus_flags; + u32 mode_flags; +}; + +struct panel_nv3051d { + struct device *dev; + struct drm_panel panel; + struct gpio_desc *reset_gpio; + const struct nv3051d_panel_info *panel_info; + struct regulator *vdd; +}; + +static inline struct panel_nv3051d *panel_to_panelnv3051d(struct drm_panel *panel) +{ + return container_of(panel, struct panel_nv3051d, panel); +} + +static int panel_nv3051d_init_sequence(struct panel_nv3051d *ctx) +{ + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + struct mipi_dsi_multi_context dsi_ctx = {.dsi = dsi}; + + /* + * Init sequence was supplied by device vendor with no + * documentation. + */ + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xFF, 0x30); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xFF, 0x52); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xFF, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xE3, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x03, 0x40); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x04, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x05, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x24, 0x12); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x25, 0x1E); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x26, 0x28); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x27, 0x52); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x28, 0x57); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x29, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2A, 0xDF); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x38, 0x9C); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x39, 0xA7); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3A, 0x53); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x44, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x49, 0x3C); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x59, 0xFE); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5C, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x91, 0x77); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x92, 0x77); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xA0, 0x55); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xA1, 0x50); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xA4, 0x9C); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xA7, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xA8, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xA9, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xAA, 0xFC); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xAB, 0x28); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xAC, 0x06); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xAD, 0x06); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xAE, 0x06); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xAF, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xB0, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xB1, 0x26); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xB2, 0x28); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xB3, 0x28); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xB4, 0x33); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xB5, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xB6, 0x26); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xB7, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xB8, 0x26); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xFF, 0x30); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xFF, 0x52); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xFF, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xB1, 0x0E); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xD1, 0x0E); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xB4, 0x29); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xD4, 0x2B); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xB2, 0x0C); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xD2, 0x0A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xB3, 0x28); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xD3, 0x28); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xB6, 0x11); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xD6, 0x0D); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xB7, 0x32); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xD7, 0x30); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xC1, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xE1, 0x06); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xB8, 0x0A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xD8, 0x0A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xB9, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xD9, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xBD, 0x13); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xDD, 0x13); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xBC, 0x11); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xDC, 0x11); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xBB, 0x0F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xDB, 0x0F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xBA, 0x0F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xDA, 0x0F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xBE, 0x18); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xDE, 0x18); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xBF, 0x0F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xDF, 0x0F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xC0, 0x17); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xE0, 0x17); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xB5, 0x3B); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xD5, 0x3C); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xB0, 0x0B); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xD0, 0x0C); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xFF, 0x30); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xFF, 0x52); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xFF, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x00, 0x2A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x01, 0x2A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x02, 0x2A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x03, 0x2A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x04, 0x61); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x05, 0x80); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x06, 0xC7); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x07, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x08, 0x82); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x09, 0x83); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x30, 0x2A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x31, 0x2A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x32, 0x2A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x33, 0x2A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x34, 0x61); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x35, 0xC5); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x36, 0x80); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x37, 0x23); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x40, 0x82); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x41, 0x83); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x42, 0x80); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x43, 0x81); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x44, 0x11); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x45, 0xF2); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x46, 0xF1); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x47, 0x11); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x48, 0xF4); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x49, 0xF3); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x50, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x51, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x52, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x53, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x54, 0x11); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x55, 0xF6); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x56, 0xF5); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x57, 0x11); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x58, 0xF8); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x59, 0xF7); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7E, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7F, 0x80); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xE0, 0x5A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xB1, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xB4, 0x0E); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xB5, 0x0F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xB6, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xB7, 0x07); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xB8, 0x06); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xB9, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xBA, 0x0F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xC7, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xCA, 0x0E); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xCB, 0x0F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xCC, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xCD, 0x07); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xCE, 0x06); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xCF, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xD0, 0x0F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x81, 0x0F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x84, 0x0E); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x85, 0x0F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x86, 0x07); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x87, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x88, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x89, 0x06); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x8A, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x97, 0x0F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x9A, 0x0E); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x9B, 0x0F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x9C, 0x07); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x9D, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x9E, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x9F, 0x06); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xA0, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xFF, 0x30); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xFF, 0x52); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xFF, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x01, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x02, 0xDA); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x03, 0xBA); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x04, 0xA8); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x05, 0x9A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x06, 0x70); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x07, 0xFF); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x08, 0x91); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x09, 0x90); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0A, 0xFF); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0B, 0x8F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0C, 0x60); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0D, 0x58); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0E, 0x48); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0F, 0x38); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x10, 0x2B); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xFF, 0x30); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xFF, 0x52); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xFF, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x36, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3A, 0x70); + + dev_dbg(ctx->dev, "Panel init sequence done\n"); + + return 0; +} + +static int panel_nv3051d_unprepare(struct drm_panel *panel) +{ + struct panel_nv3051d *ctx = panel_to_panelnv3051d(panel); + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + int ret; + + ret = mipi_dsi_dcs_set_display_off(dsi); + if (ret < 0) + dev_err(ctx->dev, "failed to set display off: %d\n", ret); + + msleep(20); + + ret = mipi_dsi_dcs_enter_sleep_mode(dsi); + if (ret < 0) { + dev_err(ctx->dev, "failed to enter sleep mode: %d\n", ret); + return ret; + } + + usleep_range(10000, 15000); + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + + regulator_disable(ctx->vdd); + + return 0; +} + +static int panel_nv3051d_prepare(struct drm_panel *panel) +{ + struct panel_nv3051d *ctx = panel_to_panelnv3051d(panel); + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + int ret; + + dev_dbg(ctx->dev, "Resetting the panel\n"); + ret = regulator_enable(ctx->vdd); + if (ret < 0) { + dev_err(ctx->dev, "Failed to enable vdd supply: %d\n", ret); + return ret; + } + + usleep_range(2000, 3000); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + msleep(150); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + msleep(20); + + ret = panel_nv3051d_init_sequence(ctx); + if (ret < 0) { + dev_err(ctx->dev, "Panel init sequence failed: %d\n", ret); + goto disable_vdd; + } + + ret = mipi_dsi_dcs_exit_sleep_mode(dsi); + if (ret < 0) { + dev_err(ctx->dev, "Failed to exit sleep mode: %d\n", ret); + goto disable_vdd; + } + + msleep(200); + + ret = mipi_dsi_dcs_set_display_on(dsi); + if (ret < 0) { + dev_err(ctx->dev, "Failed to set display on: %d\n", ret); + goto disable_vdd; + } + + usleep_range(10000, 15000); + + return 0; + +disable_vdd: + regulator_disable(ctx->vdd); + return ret; +} + +static int panel_nv3051d_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct panel_nv3051d *ctx = panel_to_panelnv3051d(panel); + const struct nv3051d_panel_info *panel_info = ctx->panel_info; + struct drm_display_mode *mode; + unsigned int i; + + for (i = 0; i < panel_info->num_modes; i++) { + mode = drm_mode_duplicate(connector->dev, + &panel_info->display_modes[i]); + if (!mode) + return -ENOMEM; + + drm_mode_set_name(mode); + + mode->type = DRM_MODE_TYPE_DRIVER; + if (panel_info->num_modes == 1) + mode->type |= 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; + connector->display_info.bus_flags = panel_info->bus_flags; + + return panel_info->num_modes; +} + +static const struct drm_panel_funcs panel_nv3051d_funcs = { + .unprepare = panel_nv3051d_unprepare, + .prepare = panel_nv3051d_prepare, + .get_modes = panel_nv3051d_get_modes, +}; + +static int panel_nv3051d_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct panel_nv3051d *ctx; + int ret; + + ctx = devm_drm_panel_alloc(dev, struct panel_nv3051d, panel, + &panel_nv3051d_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ctx->dev = dev; + + ctx->panel_info = of_device_get_match_data(dev); + if (!ctx->panel_info) + return -EINVAL; + + ctx->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->reset_gpio)) { + dev_err(dev, "cannot get reset gpio\n"); + return PTR_ERR(ctx->reset_gpio); + } + + ctx->vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(ctx->vdd)) { + ret = PTR_ERR(ctx->vdd); + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to request vdd regulator: %d\n", ret); + return ret; + } + + mipi_dsi_set_drvdata(dsi, ctx); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = ctx->panel_info->mode_flags; + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return ret; + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "mipi_dsi_attach failed: %d\n", ret); + drm_panel_remove(&ctx->panel); + return ret; + } + + return 0; +} + +static void panel_nv3051d_shutdown(struct mipi_dsi_device *dsi) +{ + struct panel_nv3051d *ctx = mipi_dsi_get_drvdata(dsi); + + drm_panel_unprepare(&ctx->panel); + + drm_panel_disable(&ctx->panel); +} + +static void panel_nv3051d_remove(struct mipi_dsi_device *dsi) +{ + struct panel_nv3051d *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + panel_nv3051d_shutdown(dsi); + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&ctx->panel); +} + +static const struct drm_display_mode nv3051d_rgxx3_modes[] = { + { /* 120hz */ + .hdisplay = 640, + .hsync_start = 640 + 40, + .hsync_end = 640 + 40 + 2, + .htotal = 640 + 40 + 2 + 80, + .vdisplay = 480, + .vsync_start = 480 + 18, + .vsync_end = 480 + 18 + 2, + .vtotal = 480 + 18 + 2 + 28, + .clock = 48300, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + }, + { /* 100hz */ + .hdisplay = 640, + .hsync_start = 640 + 40, + .hsync_end = 640 + 40 + 2, + .htotal = 640 + 40 + 2 + 80, + .vdisplay = 480, + .vsync_start = 480 + 18, + .vsync_end = 480 + 18 + 2, + .vtotal = 480 + 18 + 2 + 28, + .clock = 40250, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + }, + { /* 60hz */ + .hdisplay = 640, + .hsync_start = 640 + 40, + .hsync_end = 640 + 40 + 2, + .htotal = 640 + 40 + 2 + 80, + .vdisplay = 480, + .vsync_start = 480 + 18, + .vsync_end = 480 + 18 + 2, + .vtotal = 480 + 18 + 2 + 28, + .clock = 24150, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + }, +}; + +static const struct drm_display_mode nv3051d_rk2023_modes[] = { + { + .hdisplay = 640, + .hsync_start = 640 + 40, + .hsync_end = 640 + 40 + 2, + .htotal = 640 + 40 + 2 + 80, + .vdisplay = 480, + .vsync_start = 480 + 18, + .vsync_end = 480 + 18 + 2, + .vtotal = 480 + 18 + 2 + 4, + .clock = 24150, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + }, +}; + +static const struct nv3051d_panel_info nv3051d_rg351v_info = { + .display_modes = nv3051d_rgxx3_modes, + .num_modes = ARRAY_SIZE(nv3051d_rgxx3_modes), + .width_mm = 70, + .height_mm = 57, + .bus_flags = DRM_BUS_FLAG_DE_LOW | DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_NO_EOT_PACKET | + MIPI_DSI_CLOCK_NON_CONTINUOUS, +}; + +static const struct nv3051d_panel_info nv3051d_rg353p_info = { + .display_modes = nv3051d_rgxx3_modes, + .num_modes = ARRAY_SIZE(nv3051d_rgxx3_modes), + .width_mm = 70, + .height_mm = 57, + .bus_flags = DRM_BUS_FLAG_DE_LOW | DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_NO_EOT_PACKET, +}; + +static const struct nv3051d_panel_info nv3051d_rk2023_info = { + .display_modes = nv3051d_rk2023_modes, + .num_modes = ARRAY_SIZE(nv3051d_rk2023_modes), + .width_mm = 70, + .height_mm = 57, + .bus_flags = DRM_BUS_FLAG_DE_LOW | DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_NO_EOT_PACKET, +}; + +static const struct of_device_id newvision_nv3051d_of_match[] = { + { .compatible = "anbernic,rg351v-panel", .data = &nv3051d_rg351v_info }, + { .compatible = "anbernic,rg353p-panel", .data = &nv3051d_rg353p_info }, + { .compatible = "powkiddy,rk2023-panel", .data = &nv3051d_rk2023_info }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, newvision_nv3051d_of_match); + +static struct mipi_dsi_driver newvision_nv3051d_driver = { + .driver = { + .name = "panel-newvision-nv3051d", + .of_match_table = newvision_nv3051d_of_match, + }, + .probe = panel_nv3051d_probe, + .remove = panel_nv3051d_remove, + .shutdown = panel_nv3051d_shutdown, +}; +module_mipi_dsi_driver(newvision_nv3051d_driver); + +MODULE_AUTHOR("Chris Morgan <macromorgan@hotmail.com>"); +MODULE_DESCRIPTION("DRM driver for Newvision NV3051D based MIPI DSI panels"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-newvision-nv3052c.c b/drivers/gpu/drm/panel/panel-newvision-nv3052c.c new file mode 100644 index 000000000000..18130bc14201 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-newvision-nv3052c.c @@ -0,0 +1,686 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * NewVision NV3052C IPS LCD panel driver + * + * Copyright (C) 2020, Paul Cercueil <paul@crapouillou.net> + * Copyright (C) 2022, Christophe Branchereau <cbranchereau@gmail.com> + */ + +#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/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/spi/spi.h> +#include <video/mipi_display.h> +#include <drm/drm_mipi_dbi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +struct nv3052c_reg { + u8 cmd; + u8 val; +}; + +struct nv3052c_panel_info { + const struct drm_display_mode *display_modes; + unsigned int num_modes; + u16 width_mm, height_mm; + u32 bus_format, bus_flags; + const struct nv3052c_reg *panel_regs; + unsigned int panel_regs_len; +}; + +struct nv3052c { + struct device *dev; + struct drm_panel panel; + struct mipi_dbi dbi; + const struct nv3052c_panel_info *panel_info; + struct regulator *supply; + struct gpio_desc *reset_gpio; +}; + +/* + * Common initialization registers for all currently + * supported displays. Mostly seem to be related + * to Gamma correction curves and output pad mappings. + */ +static const struct nv3052c_reg common_init_regs[] = { + // EXTC Command set enable, select page 2 + { 0xff, 0x30 }, { 0xff, 0x52 }, { 0xff, 0x02 }, + // Set gray scale voltage to adjust gamma + { 0xb0, 0x0b }, // PGAMVR0 + { 0xb1, 0x16 }, // PGAMVR1 + { 0xb2, 0x17 }, // PGAMVR2 + { 0xb3, 0x2c }, // PGAMVR3 + { 0xb4, 0x32 }, // PGAMVR4 + { 0xb5, 0x3b }, // PGAMVR5 + { 0xb6, 0x29 }, // PGAMPR0 + { 0xb7, 0x40 }, // PGAMPR1 + { 0xb8, 0x0d }, // PGAMPK0 + { 0xb9, 0x05 }, // PGAMPK1 + { 0xba, 0x12 }, // PGAMPK2 + { 0xbb, 0x10 }, // PGAMPK3 + { 0xbc, 0x12 }, // PGAMPK4 + { 0xbd, 0x15 }, // PGAMPK5 + { 0xbe, 0x19 }, // PGAMPK6 + { 0xbf, 0x0e }, // PGAMPK7 + { 0xc0, 0x16 }, // PGAMPK8 + { 0xc1, 0x0a }, // PGAMPK9 + // Set gray scale voltage to adjust gamma + { 0xd0, 0x0c }, // NGAMVR0 + { 0xd1, 0x17 }, // NGAMVR0 + { 0xd2, 0x14 }, // NGAMVR1 + { 0xd3, 0x2e }, // NGAMVR2 + { 0xd4, 0x32 }, // NGAMVR3 + { 0xd5, 0x3c }, // NGAMVR4 + { 0xd6, 0x22 }, // NGAMPR0 + { 0xd7, 0x3d }, // NGAMPR1 + { 0xd8, 0x0d }, // NGAMPK0 + { 0xd9, 0x07 }, // NGAMPK1 + { 0xda, 0x13 }, // NGAMPK2 + { 0xdb, 0x13 }, // NGAMPK3 + { 0xdc, 0x11 }, // NGAMPK4 + { 0xdd, 0x15 }, // NGAMPK5 + { 0xde, 0x19 }, // NGAMPK6 + { 0xdf, 0x10 }, // NGAMPK7 + { 0xe0, 0x17 }, // NGAMPK8 + { 0xe1, 0x0a }, // NGAMPK9 + // EXTC Command set enable, select page 3 + { 0xff, 0x30 }, { 0xff, 0x52 }, { 0xff, 0x03 }, + // Set various timing settings + { 0x00, 0x2a }, // GIP_VST_1 + { 0x01, 0x2a }, // GIP_VST_2 + { 0x02, 0x2a }, // GIP_VST_3 + { 0x03, 0x2a }, // GIP_VST_4 + { 0x04, 0x61 }, // GIP_VST_5 + { 0x05, 0x80 }, // GIP_VST_6 + { 0x06, 0xc7 }, // GIP_VST_7 + { 0x07, 0x01 }, // GIP_VST_8 + { 0x08, 0x03 }, // GIP_VST_9 + { 0x09, 0x04 }, // GIP_VST_10 + { 0x70, 0x22 }, // GIP_ECLK1 + { 0x71, 0x80 }, // GIP_ECLK2 + { 0x30, 0x2a }, // GIP_CLK_1 + { 0x31, 0x2a }, // GIP_CLK_2 + { 0x32, 0x2a }, // GIP_CLK_3 + { 0x33, 0x2a }, // GIP_CLK_4 + { 0x34, 0x61 }, // GIP_CLK_5 + { 0x35, 0xc5 }, // GIP_CLK_6 + { 0x36, 0x80 }, // GIP_CLK_7 + { 0x37, 0x23 }, // GIP_CLK_8 + { 0x40, 0x03 }, // GIP_CLKA_1 + { 0x41, 0x04 }, // GIP_CLKA_2 + { 0x42, 0x05 }, // GIP_CLKA_3 + { 0x43, 0x06 }, // GIP_CLKA_4 + { 0x44, 0x11 }, // GIP_CLKA_5 + { 0x45, 0xe8 }, // GIP_CLKA_6 + { 0x46, 0xe9 }, // GIP_CLKA_7 + { 0x47, 0x11 }, // GIP_CLKA_8 + { 0x48, 0xea }, // GIP_CLKA_9 + { 0x49, 0xeb }, // GIP_CLKA_10 + { 0x50, 0x07 }, // GIP_CLKB_1 + { 0x51, 0x08 }, // GIP_CLKB_2 + { 0x52, 0x09 }, // GIP_CLKB_3 + { 0x53, 0x0a }, // GIP_CLKB_4 + { 0x54, 0x11 }, // GIP_CLKB_5 + { 0x55, 0xec }, // GIP_CLKB_6 + { 0x56, 0xed }, // GIP_CLKB_7 + { 0x57, 0x11 }, // GIP_CLKB_8 + { 0x58, 0xef }, // GIP_CLKB_9 + { 0x59, 0xf0 }, // GIP_CLKB_10 + // Map internal GOA signals to GOA output pad + { 0xb1, 0x01 }, // PANELD2U2 + { 0xb4, 0x15 }, // PANELD2U5 + { 0xb5, 0x16 }, // PANELD2U6 + { 0xb6, 0x09 }, // PANELD2U7 + { 0xb7, 0x0f }, // PANELD2U8 + { 0xb8, 0x0d }, // PANELD2U9 + { 0xb9, 0x0b }, // PANELD2U10 + { 0xba, 0x00 }, // PANELD2U11 + { 0xc7, 0x02 }, // PANELD2U24 + { 0xca, 0x17 }, // PANELD2U27 + { 0xcb, 0x18 }, // PANELD2U28 + { 0xcc, 0x0a }, // PANELD2U29 + { 0xcd, 0x10 }, // PANELD2U30 + { 0xce, 0x0e }, // PANELD2U31 + { 0xcf, 0x0c }, // PANELD2U32 + { 0xd0, 0x00 }, // PANELD2U33 + // Map internal GOA signals to GOA output pad + { 0x81, 0x00 }, // PANELU2D2 + { 0x84, 0x15 }, // PANELU2D5 + { 0x85, 0x16 }, // PANELU2D6 + { 0x86, 0x10 }, // PANELU2D7 + { 0x87, 0x0a }, // PANELU2D8 + { 0x88, 0x0c }, // PANELU2D9 + { 0x89, 0x0e }, // PANELU2D10 + { 0x8a, 0x02 }, // PANELU2D11 + { 0x97, 0x00 }, // PANELU2D24 + { 0x9a, 0x17 }, // PANELU2D27 + { 0x9b, 0x18 }, // PANELU2D28 + { 0x9c, 0x0f }, // PANELU2D29 + { 0x9d, 0x09 }, // PANELU2D30 + { 0x9e, 0x0b }, // PANELU2D31 + { 0x9f, 0x0d }, // PANELU2D32 + { 0xa0, 0x01 }, // PANELU2D33 + // EXTC Command set enable, select page 2 + { 0xff, 0x30 }, { 0xff, 0x52 }, { 0xff, 0x02 }, + // Page 2 register values (0x01..0x10) are same for nv3051d and nv3052c + { 0x01, 0x01 }, + { 0x02, 0xda }, + { 0x03, 0xba }, + { 0x04, 0xa8 }, + { 0x05, 0x9a }, + { 0x06, 0x70 }, + { 0x07, 0xff }, + { 0x08, 0x91 }, + { 0x09, 0x90 }, + { 0x0a, 0xff }, + { 0x0b, 0x8f }, + { 0x0c, 0x60 }, + { 0x0d, 0x58 }, + { 0x0e, 0x48 }, + { 0x0f, 0x38 }, + { 0x10, 0x2b }, + // EXTC Command set enable, select page 0 + { 0xff, 0x30 }, { 0xff, 0x52 }, { 0xff, 0x00 }, + // Display Access Control + { 0x36, 0x0a }, // bgr = 1, ss = 1, gs = 0 + +}; + +static const struct nv3052c_reg ltk035c5444t_panel_regs[] = { + // EXTC Command set enable, select page 1 + { 0xff, 0x30 }, { 0xff, 0x52 }, { 0xff, 0x01 }, + // Mostly unknown registers + { 0xe3, 0x00 }, + { 0x40, 0x00 }, + { 0x03, 0x40 }, + { 0x04, 0x00 }, + { 0x05, 0x03 }, + { 0x08, 0x00 }, + { 0x09, 0x07 }, + { 0x0a, 0x01 }, + { 0x0b, 0x32 }, + { 0x0c, 0x32 }, + { 0x0d, 0x0b }, + { 0x0e, 0x00 }, + { 0x23, 0xa0 }, + { 0x24, 0x0c }, + { 0x25, 0x06 }, + { 0x26, 0x14 }, + { 0x27, 0x14 }, + { 0x38, 0xcc }, // VCOM_ADJ1 + { 0x39, 0xd7 }, // VCOM_ADJ2 + { 0x3a, 0x4a }, // VCOM_ADJ3 + { 0x28, 0x40 }, + { 0x29, 0x01 }, + { 0x2a, 0xdf }, + { 0x49, 0x3c }, + { 0x91, 0x77 }, // EXTPW_CTRL2 + { 0x92, 0x77 }, // EXTPW_CTRL3 + { 0xa0, 0x55 }, + { 0xa1, 0x50 }, + { 0xa4, 0x9c }, + { 0xa7, 0x02 }, + { 0xa8, 0x01 }, + { 0xa9, 0x01 }, + { 0xaa, 0xfc }, + { 0xab, 0x28 }, + { 0xac, 0x06 }, + { 0xad, 0x06 }, + { 0xae, 0x06 }, + { 0xaf, 0x03 }, + { 0xb0, 0x08 }, + { 0xb1, 0x26 }, + { 0xb2, 0x28 }, + { 0xb3, 0x28 }, + { 0xb4, 0x33 }, + { 0xb5, 0x08 }, + { 0xb6, 0x26 }, + { 0xb7, 0x08 }, + { 0xb8, 0x26 }, + { 0xf0, 0x00 }, + { 0xf6, 0xc0 }, +}; + +static const struct nv3052c_reg fs035vg158_panel_regs[] = { + // EXTC Command set enable, select page 1 + { 0xff, 0x30 }, { 0xff, 0x52 }, { 0xff, 0x01 }, + // Mostly unknown registers + { 0xe3, 0x00 }, + { 0x40, 0x00 }, + { 0x03, 0x40 }, + { 0x04, 0x00 }, + { 0x05, 0x03 }, + { 0x08, 0x00 }, + { 0x09, 0x07 }, + { 0x0a, 0x01 }, + { 0x0b, 0x32 }, + { 0x0c, 0x32 }, + { 0x0d, 0x0b }, + { 0x0e, 0x00 }, + { 0x23, 0x20 }, // RGB interface control: DE MODE PCLK-N + { 0x24, 0x0c }, + { 0x25, 0x06 }, + { 0x26, 0x14 }, + { 0x27, 0x14 }, + { 0x38, 0x9c }, //VCOM_ADJ1, different to ltk035c5444t + { 0x39, 0xa7 }, //VCOM_ADJ2, different to ltk035c5444t + { 0x3a, 0x50 }, //VCOM_ADJ3, different to ltk035c5444t + { 0x28, 0x40 }, + { 0x29, 0x01 }, + { 0x2a, 0xdf }, + { 0x49, 0x3c }, + { 0x91, 0x57 }, //EXTPW_CTRL2, different to ltk035c5444t + { 0x92, 0x57 }, //EXTPW_CTRL3, different to ltk035c5444t + { 0xa0, 0x55 }, + { 0xa1, 0x50 }, + { 0xa4, 0x9c }, + { 0xa7, 0x02 }, + { 0xa8, 0x01 }, + { 0xa9, 0x01 }, + { 0xaa, 0xfc }, + { 0xab, 0x28 }, + { 0xac, 0x06 }, + { 0xad, 0x06 }, + { 0xae, 0x06 }, + { 0xaf, 0x03 }, + { 0xb0, 0x08 }, + { 0xb1, 0x26 }, + { 0xb2, 0x28 }, + { 0xb3, 0x28 }, + { 0xb4, 0x03 }, // Unknown, different to ltk035c5444 + { 0xb5, 0x08 }, + { 0xb6, 0x26 }, + { 0xb7, 0x08 }, + { 0xb8, 0x26 }, + { 0xf0, 0x00 }, + { 0xf6, 0xc0 }, +}; + + +static const struct nv3052c_reg wl_355608_a8_panel_regs[] = { + // EXTC Command set enable, select page 1 + { 0xff, 0x30 }, { 0xff, 0x52 }, { 0xff, 0x01 }, + // Mostly unknown registers + { 0xe3, 0x00 }, + { 0x40, 0x00 }, + { 0x03, 0x40 }, + { 0x04, 0x00 }, + { 0x05, 0x03 }, + { 0x08, 0x00 }, + { 0x09, 0x07 }, + { 0x0a, 0x01 }, + { 0x0b, 0x32 }, + { 0x0c, 0x32 }, + { 0x0d, 0x0b }, + { 0x0e, 0x00 }, + { 0x23, 0xa0 }, + { 0x24, 0x0c }, + { 0x25, 0x06 }, + { 0x26, 0x14 }, + { 0x27, 0x14 }, + { 0x38, 0xcc }, // VCOM_ADJ1 + { 0x39, 0xd7 }, // VCOM_ADJ2 + { 0x3a, 0x44 }, // VCOM_ADJ3 + { 0x28, 0x40 }, + { 0x29, 0x01 }, + { 0x2a, 0xdf }, + { 0x49, 0x3c }, + { 0x91, 0x77 }, // EXTPW_CTRL2 + { 0x92, 0x77 }, // EXTPW_CTRL3 + { 0xa0, 0x55 }, + { 0xa1, 0x50 }, + { 0xa4, 0x9c }, + { 0xa7, 0x02 }, + { 0xa8, 0x01 }, + { 0xa9, 0x01 }, + { 0xaa, 0xfc }, + { 0xab, 0x28 }, + { 0xac, 0x06 }, + { 0xad, 0x06 }, + { 0xae, 0x06 }, + { 0xaf, 0x03 }, + { 0xb0, 0x08 }, + { 0xb1, 0x26 }, + { 0xb2, 0x28 }, + { 0xb3, 0x28 }, + { 0xb4, 0x33 }, + { 0xb5, 0x08 }, + { 0xb6, 0x26 }, + { 0xb7, 0x08 }, + { 0xb8, 0x26 }, + { 0xf0, 0x00 }, + { 0xf6, 0xc0 }, +}; + +static inline struct nv3052c *to_nv3052c(struct drm_panel *panel) +{ + return container_of(panel, struct nv3052c, panel); +} + +static int nv3052c_prepare(struct drm_panel *panel) +{ + struct nv3052c *priv = to_nv3052c(panel); + const struct nv3052c_reg *panel_regs = priv->panel_info->panel_regs; + unsigned int panel_regs_len = priv->panel_info->panel_regs_len; + struct mipi_dbi *dbi = &priv->dbi; + unsigned int i; + int err; + + err = regulator_enable(priv->supply); + if (err) { + dev_err(priv->dev, "Failed to enable power supply: %d\n", err); + return err; + } + + /* Reset the chip */ + gpiod_set_value_cansleep(priv->reset_gpio, 1); + usleep_range(10, 1000); + gpiod_set_value_cansleep(priv->reset_gpio, 0); + usleep_range(5000, 20000); + + /* Apply panel-specific initialization registers */ + for (i = 0; i < panel_regs_len; i++) { + err = mipi_dbi_command(dbi, panel_regs[i].cmd, + panel_regs[i].val); + + if (err) { + dev_err(priv->dev, "Unable to set register: %d\n", err); + goto err_disable_regulator; + } + } + + /* Apply common initialization registers */ + for (i = 0; i < ARRAY_SIZE(common_init_regs); i++) { + err = mipi_dbi_command(dbi, common_init_regs[i].cmd, + common_init_regs[i].val); + if (err) { + dev_err(priv->dev, "Unable to set register: %d\n", err); + goto err_disable_regulator; + } + } + + err = mipi_dbi_command(dbi, MIPI_DCS_EXIT_SLEEP_MODE); + if (err) { + dev_err(priv->dev, "Unable to exit sleep mode: %d\n", err); + goto err_disable_regulator; + } + + return 0; + +err_disable_regulator: + regulator_disable(priv->supply); + return err; +} + +static int nv3052c_unprepare(struct drm_panel *panel) +{ + struct nv3052c *priv = to_nv3052c(panel); + struct mipi_dbi *dbi = &priv->dbi; + int err; + + err = mipi_dbi_command(dbi, MIPI_DCS_ENTER_SLEEP_MODE); + if (err) + dev_err(priv->dev, "Unable to enter sleep mode: %d\n", err); + + gpiod_set_value_cansleep(priv->reset_gpio, 1); + regulator_disable(priv->supply); + + return 0; +} + +static int nv3052c_enable(struct drm_panel *panel) +{ + struct nv3052c *priv = to_nv3052c(panel); + struct mipi_dbi *dbi = &priv->dbi; + int err; + + err = mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_ON); + if (err) { + dev_err(priv->dev, "Unable to enable display: %d\n", err); + return err; + } + + if (panel->backlight) { + /* Wait for the picture to be ready before enabling backlight */ + msleep(120); + } + + return 0; +} + +static int nv3052c_disable(struct drm_panel *panel) +{ + struct nv3052c *priv = to_nv3052c(panel); + struct mipi_dbi *dbi = &priv->dbi; + int err; + + err = mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_OFF); + if (err) { + dev_err(priv->dev, "Unable to disable display: %d\n", err); + return err; + } + + return 0; +} + +static int nv3052c_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct nv3052c *priv = to_nv3052c(panel); + const struct nv3052c_panel_info *panel_info = priv->panel_info; + struct drm_display_mode *mode; + unsigned int i; + + for (i = 0; i < panel_info->num_modes; i++) { + mode = drm_mode_duplicate(connector->dev, + &panel_info->display_modes[i]); + if (!mode) + return -ENOMEM; + + drm_mode_set_name(mode); + + mode->type = DRM_MODE_TYPE_DRIVER; + if (panel_info->num_modes == 1) + mode->type |= 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 panel_info->num_modes; +} + +static const struct drm_panel_funcs nv3052c_funcs = { + .prepare = nv3052c_prepare, + .unprepare = nv3052c_unprepare, + .enable = nv3052c_enable, + .disable = nv3052c_disable, + .get_modes = nv3052c_get_modes, +}; + +static int nv3052c_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct nv3052c *priv; + int err; + + priv = devm_drm_panel_alloc(dev, struct nv3052c, panel, &nv3052c_funcs, + DRM_MODE_CONNECTOR_DPI); + if (IS_ERR(priv)) + return PTR_ERR(priv); + + priv->dev = dev; + + priv->panel_info = of_device_get_match_data(dev); + if (!priv->panel_info) + return -EINVAL; + + priv->supply = devm_regulator_get(dev, "power"); + if (IS_ERR(priv->supply)) + return dev_err_probe(dev, PTR_ERR(priv->supply), "Failed to get power supply\n"); + + priv->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(priv->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(priv->reset_gpio), "Failed to get reset GPIO\n"); + + err = mipi_dbi_spi_init(spi, &priv->dbi, NULL); + if (err) + return dev_err_probe(dev, err, "MIPI DBI init failed\n"); + + priv->dbi.read_commands = NULL; + + spi_set_drvdata(spi, priv); + + err = drm_panel_of_backlight(&priv->panel); + if (err) + return dev_err_probe(dev, err, "Failed to attach backlight\n"); + + drm_panel_add(&priv->panel); + + return 0; +} + +static void nv3052c_remove(struct spi_device *spi) +{ + struct nv3052c *priv = spi_get_drvdata(spi); + + drm_panel_remove(&priv->panel); + drm_panel_disable(&priv->panel); + drm_panel_unprepare(&priv->panel); +} + +static const struct drm_display_mode ltk035c5444t_modes[] = { + { /* 60 Hz */ + .clock = 24000, + .hdisplay = 640, + .hsync_start = 640 + 96, + .hsync_end = 640 + 96 + 16, + .htotal = 640 + 96 + 16 + 48, + .vdisplay = 480, + .vsync_start = 480 + 5, + .vsync_end = 480 + 5 + 2, + .vtotal = 480 + 5 + 2 + 13, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + }, + { /* 50 Hz */ + .clock = 18000, + .hdisplay = 640, + .hsync_start = 640 + 39, + .hsync_end = 640 + 39 + 2, + .htotal = 640 + 39 + 2 + 39, + .vdisplay = 480, + .vsync_start = 480 + 5, + .vsync_end = 480 + 5 + 2, + .vtotal = 480 + 5 + 2 + 13, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + }, +}; + +static const struct drm_display_mode fs035vg158_modes[] = { + { /* 60 Hz */ + .clock = 21000, + .hdisplay = 640, + .hsync_start = 640 + 34, + .hsync_end = 640 + 34 + 4, + .htotal = 640 + 34 + 4 + 20, + .vdisplay = 480, + .vsync_start = 480 + 12, + .vsync_end = 480 + 12 + 4, + .vtotal = 480 + 12 + 4 + 6, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + }, +}; + +static const struct drm_display_mode wl_355608_a8_mode[] = { + { + .clock = 24000, + .hdisplay = 640, + .hsync_start = 640 + 64, + .hsync_end = 640 + 64 + 20, + .htotal = 640 + 64 + 20 + 46, + .vdisplay = 480, + .vsync_start = 480 + 21, + .vsync_end = 480 + 21 + 4, + .vtotal = 480 + 21 + 4 + 15, + .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, + }, +}; + +static const struct nv3052c_panel_info ltk035c5444t_panel_info = { + .display_modes = ltk035c5444t_modes, + .num_modes = ARRAY_SIZE(ltk035c5444t_modes), + .width_mm = 77, + .height_mm = 64, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE, + .panel_regs = ltk035c5444t_panel_regs, + .panel_regs_len = ARRAY_SIZE(ltk035c5444t_panel_regs), +}; + +static const struct nv3052c_panel_info fs035vg158_panel_info = { + .display_modes = fs035vg158_modes, + .num_modes = ARRAY_SIZE(fs035vg158_modes), + .width_mm = 70, + .height_mm = 53, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE, + .panel_regs = fs035vg158_panel_regs, + .panel_regs_len = ARRAY_SIZE(fs035vg158_panel_regs), +}; + +static const struct nv3052c_panel_info wl_355608_a8_panel_info = { + .display_modes = wl_355608_a8_mode, + .num_modes = ARRAY_SIZE(wl_355608_a8_mode), + .width_mm = 150, + .height_mm = 94, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE, + .panel_regs = wl_355608_a8_panel_regs, + .panel_regs_len = ARRAY_SIZE(wl_355608_a8_panel_regs), +}; + +static const struct spi_device_id nv3052c_ids[] = { + { "ltk035c5444t", }, + { "fs035vg158", }, + { "rg35xx-plus-panel", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(spi, nv3052c_ids); + +static const struct of_device_id nv3052c_of_match[] = { + { .compatible = "leadtek,ltk035c5444t", .data = <k035c5444t_panel_info }, + { .compatible = "fascontek,fs035vg158", .data = &fs035vg158_panel_info }, + { .compatible = "anbernic,rg35xx-plus-panel", .data = &wl_355608_a8_panel_info }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, nv3052c_of_match); + +static struct spi_driver nv3052c_driver = { + .driver = { + .name = "nv3052c", + .of_match_table = nv3052c_of_match, + }, + .id_table = nv3052c_ids, + .probe = nv3052c_probe, + .remove = nv3052c_remove, +}; +module_spi_driver(nv3052c_driver); + +MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>"); +MODULE_AUTHOR("Christophe Branchereau <cbranchereau@gmail.com>"); +MODULE_AUTHOR("Ryan Walklin <ryan@testtoast.com"); +MODULE_DESCRIPTION("NewVision NV3052C IPS LCD panel driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-novatek-nt35510.c b/drivers/gpu/drm/panel/panel-novatek-nt35510.c new file mode 100644 index 000000000000..3189d89c7ca0 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-novatek-nt35510.c @@ -0,0 +1,1410 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Novatek NT35510 panel driver + * Copyright (C) 2020 Linus Walleij <linus.walleij@linaro.org> + * Based on code by Robert Teather (C) 2012 Samsung + * + * This display driver (and I refer to the physical component NT35510, + * not this Linux kernel software driver) can handle: + * 480x864, 480x854, 480x800, 480x720 and 480x640 pixel displays. + * It has 480x840x24bit SRAM embedded for storing a frame. + * When powered on the display is by default in 480x800 mode. + * + * The actual panels using this component have different names, but + * the code needed to set up and configure the panel will be similar, + * so they should all use the NT35510 driver with appropriate configuration + * per-panel, e.g. for physical size. + * + * This driver is for the DSI interface to panels using the NT35510. + * + * The NT35510 can also use an RGB (DPI) interface combined with an + * I2C or SPI interface for setting up the NT35510. If this is needed + * this panel driver should be refactored to also support that use + * case. + */ +#include <linux/backlight.h> +#include <linux/bitops.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regmap.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> + +#define NT35510_CMD_CORRECT_GAMMA BIT(0) +#define NT35510_CMD_CONTROL_DISPLAY BIT(1) +#define NT35510_CMD_SETVCMOFF BIT(2) + +#define MCS_CMD_MAUCCTR 0xF0 /* Manufacturer command enable */ +#define MCS_CMD_READ_ID1 0xDA +#define MCS_CMD_READ_ID2 0xDB +#define MCS_CMD_READ_ID3 0xDC +#define MCS_CMD_MTP_READ_SETTING 0xF8 /* Uncertain about name */ +#define MCS_CMD_MTP_READ_PARAM 0xFF /* Uncertain about name */ + +/* + * These manufacturer commands are available after we enable manufacturer + * command set (MCS) for page 0. + */ +#define NT35510_P0_DOPCTR 0xB1 +#define NT35510_P0_SDHDTCTR 0xB6 +#define NT35510_P0_GSEQCTR 0xB7 +#define NT35510_P0_SDEQCTR 0xB8 +#define NT35510_P0_SDVPCTR 0xBA +#define NT35510_P0_DPFRCTR1 0xBD +#define NT35510_P0_DPFRCTR2 0xBE +#define NT35510_P0_DPFRCTR3 0xBF +#define NT35510_P0_DPMCTR12 0xCC + +#define NT35510_P0_DOPCTR_LEN 2 +#define NT35510_P0_GSEQCTR_LEN 2 +#define NT35510_P0_SDEQCTR_LEN 4 +#define NT35510_P0_SDVPCTR_LEN 1 +#define NT35510_P0_DPFRCTR1_LEN 5 +#define NT35510_P0_DPFRCTR2_LEN 5 +#define NT35510_P0_DPFRCTR3_LEN 5 +#define NT35510_P0_DPMCTR12_LEN 3 + +#define NT35510_DOPCTR_0_RAMKP BIT(7) /* Contents kept in sleep */ +#define NT35510_DOPCTR_0_DSITE BIT(6) /* Enable TE signal */ +#define NT35510_DOPCTR_0_DSIG BIT(5) /* Enable generic read/write */ +#define NT35510_DOPCTR_0_DSIM BIT(4) /* Enable video mode on DSI */ +#define NT35510_DOPCTR_0_EOTP BIT(3) /* Support EoTP */ +#define NT35510_DOPCTR_0_N565 BIT(2) /* RGB or BGR pixel format */ +#define NT35510_DOPCTR_1_TW_PWR_SEL BIT(4) /* TE power selector */ +#define NT35510_DOPCTR_1_CRGB BIT(3) /* RGB or BGR byte order */ +#define NT35510_DOPCTR_1_CTB BIT(2) /* Vertical scanning direction */ +#define NT35510_DOPCTR_1_CRL BIT(1) /* Source driver data shift */ +#define NT35510_P0_SDVPCTR_PRG BIT(2) /* 0 = normal operation, 1 = VGLO */ +#define NT35510_P0_SDVPCTR_AVDD 0 /* source driver output = AVDD */ +#define NT35510_P0_SDVPCTR_OFFCOL 1 /* source driver output = off color */ +#define NT35510_P0_SDVPCTR_AVSS 2 /* source driver output = AVSS */ +#define NT35510_P0_SDVPCTR_HI_Z 3 /* source driver output = High impedance */ + +/* + * These manufacturer commands are available after we enable manufacturer + * command set (MCS) for page 1. + */ +#define NT35510_P1_SETAVDD 0xB0 +#define NT35510_P1_SETAVEE 0xB1 +#define NT35510_P1_SETVCL 0xB2 +#define NT35510_P1_SETVGH 0xB3 +#define NT35510_P1_SETVRGH 0xB4 +#define NT35510_P1_SETVGL 0xB5 +#define NT35510_P1_BT1CTR 0xB6 +#define NT35510_P1_BT2CTR 0xB7 +#define NT35510_P1_BT3CTR 0xB8 +#define NT35510_P1_BT4CTR 0xB9 /* VGH boosting times/freq */ +#define NT35510_P1_BT5CTR 0xBA +#define NT35510_P1_PFMCTR 0xBB +#define NT35510_P1_SETVGP 0xBC +#define NT35510_P1_SETVGN 0xBD +#define NT35510_P1_SETVCMOFF 0xBE +#define NT35510_P1_VGHCTR 0xBF /* VGH output ctrl */ +#define NT35510_P1_SET_GAMMA_RED_POS 0xD1 +#define NT35510_P1_SET_GAMMA_GREEN_POS 0xD2 +#define NT35510_P1_SET_GAMMA_BLUE_POS 0xD3 +#define NT35510_P1_SET_GAMMA_RED_NEG 0xD4 +#define NT35510_P1_SET_GAMMA_GREEN_NEG 0xD5 +#define NT35510_P1_SET_GAMMA_BLUE_NEG 0xD6 + +/* AVDD and AVEE setting 3 bytes */ +#define NT35510_P1_AVDD_LEN 3 +#define NT35510_P1_AVEE_LEN 3 +#define NT35510_P1_VCL_LEN 3 +#define NT35510_P1_VGH_LEN 3 +#define NT35510_P1_VGL_LEN 3 +#define NT35510_P1_VGP_LEN 3 +#define NT35510_P1_VGN_LEN 3 +#define NT35510_P1_VCMOFF_LEN 2 +/* BT1CTR thru BT5CTR setting 3 bytes */ +#define NT35510_P1_BT1CTR_LEN 3 +#define NT35510_P1_BT2CTR_LEN 3 +#define NT35510_P1_BT3CTR_LEN 3 +#define NT35510_P1_BT4CTR_LEN 3 +#define NT35510_P1_BT5CTR_LEN 3 +/* 52 gamma parameters times two per color: positive and negative */ +#define NT35510_P1_GAMMA_LEN 52 + +#define NT35510_WRCTRLD_BCTRL BIT(5) +#define NT35510_WRCTRLD_A BIT(4) +#define NT35510_WRCTRLD_DD BIT(3) +#define NT35510_WRCTRLD_BL BIT(2) +#define NT35510_WRCTRLD_DB BIT(1) +#define NT35510_WRCTRLD_G BIT(0) + +#define NT35510_WRCABC_OFF 0 +#define NT35510_WRCABC_UI_MODE 1 +#define NT35510_WRCABC_STILL_MODE 2 +#define NT35510_WRCABC_MOVING_MODE 3 + +/** + * struct nt35510_config - the display-specific NT35510 configuration + * + * Some of the settings provide an array of bytes, A, B C which mean: + * A = normal / idle off mode + * B = idle on mode + * C = partial / idle off mode + * + * Gamma correction arrays are 10bit numbers, two consecutive bytes + * makes out one point on the gamma correction curve. The points are + * not linearly placed along the X axis, we get points 0, 1, 3, 5 + * 7, 11, 15, 23, 31, 47, 63, 95, 127, 128, 160, 192, 208, 224, 232, + * 240, 244, 248, 250, 252, 254, 255. The voltages tuples form + * V0, V1, V3 ... V255, with 0x0000 being the lowest voltage and + * 0x03FF being the highest voltage. + * + * Each value must be strictly higher than the previous value forming + * a rising curve like this: + * + * ^ + * | V255 + * | V254 + * | .... + * | V5 + * | V3 + * | V1 + * | V0 + * +-------------------------------------------> + * + * The details about all settings can be found in the NT35510 Application + * Note. + */ +struct nt35510_config { + /** + * @width_mm: physical panel width [mm] + */ + u32 width_mm; + /** + * @height_mm: physical panel height [mm] + */ + u32 height_mm; + /** + * @mode: the display mode. This is only relevant outside the panel + * in video mode: in command mode this is configuring the internal + * timing in the display controller. + */ + const struct drm_display_mode mode; + /** + * @mode_flags: DSI operation mode related flags + */ + unsigned long mode_flags; + /** + * @cmds: enable DSI commands + */ + u32 cmds; + /** + * @avdd: setting for AVDD ranging from 0x00 = 6.5V to 0x14 = 4.5V + * in 0.1V steps the default is 0x05 which means 6.0V + */ + u8 avdd[NT35510_P1_AVDD_LEN]; + /** + * @bt1ctr: setting for boost power control for the AVDD step-up + * circuit (1) + * bits 0..2 in the lower nibble controls PCK, the booster clock + * frequency for the step-up circuit: + * 0 = Hsync/32 + * 1 = Hsync/16 + * 2 = Hsync/8 + * 3 = Hsync/4 + * 4 = Hsync/2 + * 5 = Hsync + * 6 = Hsync x 2 + * 7 = Hsync x 4 + * bits 4..6 in the upper nibble controls BTP, the boosting + * amplification for the step-up circuit: + * 0 = Disable + * 1 = 1.5 x VDDB + * 2 = 1.66 x VDDB + * 3 = 2 x VDDB + * 4 = 2.5 x VDDB + * 5 = 3 x VDDB + * The defaults are 4 and 4 yielding 0x44 + */ + u8 bt1ctr[NT35510_P1_BT1CTR_LEN]; + /** + * @avee: setting for AVEE ranging from 0x00 = -6.5V to 0x14 = -4.5V + * in 0.1V steps the default is 0x05 which means -6.0V + */ + u8 avee[NT35510_P1_AVEE_LEN]; + /** + * @bt2ctr: setting for boost power control for the AVEE step-up + * circuit (2) + * bits 0..2 in the lower nibble controls NCK, the booster clock + * frequency, the values are the same as for PCK in @bt1ctr. + * bits 4..5 in the upper nibble controls BTN, the boosting + * amplification for the step-up circuit. + * 0 = Disable + * 1 = -1.5 x VDDB + * 2 = -2 x VDDB + * 3 = -2.5 x VDDB + * 4 = -3 x VDDB + * The defaults are 4 and 3 yielding 0x34 + */ + u8 bt2ctr[NT35510_P1_BT2CTR_LEN]; + /** + * @vcl: setting for VCL ranging from 0x00 = -2.5V to 0x11 = -4.0V + * in 1V steps, the default is 0x00 which means -2.5V + */ + u8 vcl[NT35510_P1_VCL_LEN]; + /** + * @bt3ctr: setting for boost power control for the VCL step-up + * circuit (3) + * bits 0..2 in the lower nibble controls CLCK, the booster clock + * frequency, the values are the same as for PCK in @bt1ctr. + * bits 4..5 in the upper nibble controls BTCL, the boosting + * amplification for the step-up circuit. + * 0 = Disable + * 1 = -0.5 x VDDB + * 2 = -1 x VDDB + * 3 = -2 x VDDB + * The defaults are 4 and 2 yielding 0x24 + */ + u8 bt3ctr[NT35510_P1_BT3CTR_LEN]; + /** + * @vgh: setting for VGH ranging from 0x00 = 7.0V to 0x0B = 18.0V + * in 1V steps, the default is 0x08 which means 15V + */ + u8 vgh[NT35510_P1_VGH_LEN]; + /** + * @bt4ctr: setting for boost power control for the VGH step-up + * circuit (4) + * bits 0..2 in the lower nibble controls HCK, the booster clock + * frequency, the values are the same as for PCK in @bt1ctr. + * bits 4..5 in the upper nibble controls BTH, the boosting + * amplification for the step-up circuit. + * 0 = AVDD + VDDB + * 1 = AVDD - AVEE + * 2 = AVDD - AVEE + VDDB + * 3 = AVDD x 2 - AVEE + * The defaults are 4 and 3 yielding 0x34 + */ + u8 bt4ctr[NT35510_P1_BT4CTR_LEN]; + /** + * @vgl: setting for VGL ranging from 0x00 = -2V to 0x0f = -15V in + * 1V steps, the default is 0x08 which means -10V + */ + u8 vgl[NT35510_P1_VGL_LEN]; + /** + * @bt5ctr: setting for boost power control for the VGL step-up + * circuit (5) + * bits 0..2 in the lower nibble controls LCK, the booster clock + * frequency, the values are the same as for PCK in @bt1ctr. + * bits 4..5 in the upper nibble controls BTL, the boosting + * amplification for the step-up circuit. + * 0 = AVEE + VCL + * 1 = AVEE - AVDD + * 2 = AVEE + VCL - AVDD + * 3 = AVEE x 2 - AVDD + * The defaults are 3 and 2 yielding 0x32 + */ + u8 bt5ctr[NT35510_P1_BT5CTR_LEN]; + /** + * @vgp: setting for VGP, the positive gamma divider voltages + * VGMP the high voltage and VGSP the low voltage. + * The first byte contains bit 8 of VGMP and VGSP in bits 4 and 0 + * The second byte contains bit 0..7 of VGMP + * The third byte contains bit 0..7 of VGSP + * VGMP 0x00 = 3.0V .. 0x108 = 6.3V in steps of 12.5mV + * VGSP 0x00 = 0V .. 0x111 = 3.7V in steps of 12.5mV + */ + u8 vgp[NT35510_P1_VGP_LEN]; + /** + * @vgn: setting for VGN, the negative gamma divider voltages, + * same layout of bytes as @vgp. + */ + u8 vgn[NT35510_P1_VGN_LEN]; + /** + * @vcmoff: setting the DC VCOM offset voltage + * The first byte contains bit 8 of VCM in bit 0 and VCMOFFSEL in bit 4. + * The second byte contains bits 0..7 of VCM. + * VCMOFFSEL the common voltage offset mode. + * VCMOFFSEL 0x00 = VCOM .. 0x01 Gamma. + * The default is 0x00. + * VCM the VCOM output voltage (VCMOFFSEL = 0) or the internal register + * offset for gamma voltage (VCMOFFSEL = 1). + * VCM 0x00 = 0V/0 .. 0x118 = 3.5V/280 in steps of 12.5mV/1step + * The default is 0x00 = 0V/0. + */ + u8 vcmoff[NT35510_P1_VCMOFF_LEN]; + /** + * @dopctr: setting optional control for display + * ERR bits 0..1 in the first byte is the ERR pin output signal setting. + * 0 = Disable, ERR pin output low + * 1 = ERR pin output CRC error only + * 2 = ERR pin output ECC error only + * 3 = ERR pin output CRC and ECC error + * The default is 0. + * N565 bit 2 in the first byte is the 16-bit/pixel format selection. + * 0 = R[4:0] + G[5:3] & G[2:0] + B[4:0] + * 1 = G[2:0] + R[4:0] & B[4:0] + G[5:3] + * The default is 0. + * DIS_EoTP_HS bit 3 in the first byte is "DSI protocol violation" error + * reporting. + * 0 = reporting when error + * 1 = not reporting when error + * DSIM bit 4 in the first byte is the video mode data type enable + * 0 = Video mode data type disable + * 1 = Video mode data type enable + * The default is 0. + * DSIG bit 5 int the first byte is the generic r/w data type enable + * 0 = Generic r/w disable + * 1 = Generic r/w enable + * The default is 0. + * DSITE bit 6 in the first byte is TE line enable + * 0 = TE line is disabled + * 1 = TE line is enabled + * The default is 0. + * RAMKP bit 7 in the first byte is the frame memory keep/loss in + * sleep-in mode + * 0 = contents loss in sleep-in + * 1 = contents keep in sleep-in + * The default is 0. + * CRL bit 1 in the second byte is the source driver data shift + * direction selection. This bit is XOR operation with bit RSMX + * of 3600h command. + * 0 (RMSX = 0) = S1 -> S1440 + * 0 (RMSX = 1) = S1440 -> S1 + * 1 (RMSX = 0) = S1440 -> S1 + * 1 (RMSX = 1) = S1 -> S1440 + * The default is 0. + * CTB bit 2 in the second byte is the vertical scanning direction + * selection for gate control signals. This bit is XOR operation + * with bit ML of 3600h command. + * 0 (ML = 0) = Forward (top -> bottom) + * 0 (ML = 1) = Reverse (bottom -> top) + * 1 (ML = 0) = Reverse (bottom -> top) + * 1 (ML = 1) = Forward (top -> bottom) + * The default is 0. + * CRGB bit 3 in the second byte is RGB-BGR order selection. This + * bit is XOR operation with bit RGB of 3600h command. + * 0 (RGB = 0) = RGB/Normal + * 0 (RGB = 1) = BGR/RB swap + * 1 (RGB = 0) = BGR/RB swap + * 1 (RGB = 1) = RGB/Normal + * The default is 0. + * TE_PWR_SEL bit 4 in the second byte is the TE output voltage + * level selection (only valid when DSTB_SEL = 0 or DSTB_SEL = 1, + * VSEL = High and VDDI = 1.665~3.3V). + * 0 = TE output voltage level is VDDI + * 1 = TE output voltage level is VDDA + * The default is 0. + */ + u8 dopctr[NT35510_P0_DOPCTR_LEN]; + /** + * @madctl: Memory data access control + * RSMY bit 0 is flip vertical. Flips the display image top to down. + * RSMX bit 1 is flip horizontal. Flips the display image left to right. + * MH bit 2 is the horizontal refresh order. + * RGB bit 3 is the RGB-BGR order. + * 0 = RGB color sequence + * 1 = BGR color sequence + * ML bit 4 is the vertical refresh order. + * MV bit 5 is the row/column exchange. + * MX bit 6 is the column address order. + * MY bit 7 is the row address order. + */ + u8 madctl; + /** + * @sdhdtctr: source output data hold time + * 0x00..0x3F = 0..31.5us in steps of 0.5us + * The default is 0x05 = 2.5us. + */ + u8 sdhdtctr; + /** + * @gseqctr: EQ control for gate signals + * GFEQ_XX[3:0]: time setting of EQ step for falling edge in steps + * of 0.5us. + * The default is 0x07 = 3.5us + * GREQ_XX[7:4]: time setting of EQ step for rising edge in steps + * of 0.5us. + * The default is 0x07 = 3.5us + */ + u8 gseqctr[NT35510_P0_GSEQCTR_LEN]; + /** + * @sdeqctr: Source driver control settings, first byte is + * 0 for mode 1 and 1 for mode 2. Mode 1 uses two steps and + * mode 2 uses three steps meaning EQS3 is not used in mode + * 1. Mode 2 is default. The last three parameters are EQS1, EQS2 + * and EQS3, setting the rise time for each equalizer step: + * 0x00 = 0.0 us to 0x0f = 7.5 us in steps of 0.5us. The default + * is 0x07 = 3.5 us. + */ + u8 sdeqctr[NT35510_P0_SDEQCTR_LEN]; + /** + * @sdvpctr: power/voltage behaviour during vertical porch time + */ + u8 sdvpctr; + /** + * @t1: the number of pixel clocks on one scanline, range + * 0x100 (258 ticks) .. 0x3FF (1024 ticks) so the value + 1 + * clock ticks. + */ + u16 t1; + /** + * @vbp: vertical back porch toward the PANEL note: not toward + * the DSI host; these are separate interfaces, in from DSI host + * and out to the panel. + */ + u8 vbp; + /** + * @vfp: vertical front porch toward the PANEL. + */ + u8 vfp; + /** + * @psel: pixel clock divisor: 0 = 1, 1 = 2, 2 = 4, 3 = 8. + */ + u8 psel; + /** + * @dpmctr12: Display timing control 12 + * Byte 1 bit 4 selects LVGL voltage level: 0 = VGLX, 1 = VGL_REG + * Byte 1 bit 1 selects gate signal mode: 0 = non-overlap, 1 = overlap + * Byte 1 bit 0 selects output signal control R/L swap, 0 = normal + * 1 = swap all O->E, L->R + * Byte 2 is CLW delay clock for CK O/E and CKB O/E signals: + * 0x00 = 0us .. 0xFF = 12.75us in 0.05us steps + * Byte 3 is FTI_H0 delay time for STP O/E signals: + * 0x00 = 0us .. 0xFF = 12.75us in 0.05us steps + */ + u8 dpmctr12[NT35510_P0_DPMCTR12_LEN]; + /** + * @gamma_corr_pos_r: Red gamma correction parameters, positive + */ + u8 gamma_corr_pos_r[NT35510_P1_GAMMA_LEN]; + /** + * @gamma_corr_pos_g: Green gamma correction parameters, positive + */ + u8 gamma_corr_pos_g[NT35510_P1_GAMMA_LEN]; + /** + * @gamma_corr_pos_b: Blue gamma correction parameters, positive + */ + u8 gamma_corr_pos_b[NT35510_P1_GAMMA_LEN]; + /** + * @gamma_corr_neg_r: Red gamma correction parameters, negative + */ + u8 gamma_corr_neg_r[NT35510_P1_GAMMA_LEN]; + /** + * @gamma_corr_neg_g: Green gamma correction parameters, negative + */ + u8 gamma_corr_neg_g[NT35510_P1_GAMMA_LEN]; + /** + * @gamma_corr_neg_b: Blue gamma correction parameters, negative + */ + u8 gamma_corr_neg_b[NT35510_P1_GAMMA_LEN]; + /** + * @wrdisbv: write display brightness + * 0x00 value means the lowest brightness and 0xff value means + * the highest brightness. + * The default is 0x00. + */ + u8 wrdisbv; + /** + * @wrctrld: write control display + * G bit 0 selects gamma curve: 0 = Manual, 1 = Automatic + * DB bit 1 selects display brightness: 0 = Manual, 1 = Automatic + * BL bit 2 controls backlight control: 0 = Off, 1 = On + * DD bit 3 controls display dimming: 0 = Off, 1 = On + * A bit 4 controls LABC block: 0 = Off, 1 = On + * BCTRL bit 5 controls brightness block: 0 = Off, 1 = On + */ + u8 wrctrld; + /** + * @wrcabc: write content adaptive brightness control + * There is possible to use 4 different modes for content adaptive + * image functionality: + * 0: Off + * 1: User Interface Image (UI-Mode) + * 2: Still Picture Image (Still-Mode) + * 3: Moving Picture Image (Moving-Mode) + * The default is 0 + */ + u8 wrcabc; + /** + * @wrcabcmb: write CABC minimum brightness + * Set the minimum brightness value of the display for CABC + * function. + * 0x00 value means the lowest brightness for CABC and 0xff + * value means the highest brightness for CABC. + * The default is 0x00. + */ + u8 wrcabcmb; +}; + +/** + * struct nt35510 - state container for the NT35510 panel + */ +struct nt35510 { + /** + * @dev: the container device + */ + struct device *dev; + /** + * @conf: the specific panel configuration, as the NT35510 + * can be combined with many physical panels, they can have + * different physical dimensions and gamma correction etc, + * so this is stored in the config. + */ + const struct nt35510_config *conf; + /** + * @panel: the DRM panel object for the instance + */ + struct drm_panel panel; + /** + * @supplies: regulators supplying the panel + */ + struct regulator_bulk_data supplies[2]; + /** + * @reset_gpio: the reset line + */ + struct gpio_desc *reset_gpio; +}; + +/* Manufacturer command has strictly this byte sequence */ +static const u8 nt35510_mauc_mtp_read_param[] = { 0xAA, 0x55, 0x25, 0x01 }; +static const u8 nt35510_mauc_mtp_read_setting[] = { 0x01, 0x02, 0x00, 0x20, + 0x33, 0x13, 0x00, 0x40, + 0x00, 0x00, 0x23, 0x02 }; +static const u8 nt35510_mauc_select_page_0[] = { 0x55, 0xAA, 0x52, 0x08, 0x00 }; +static const u8 nt35510_mauc_select_page_1[] = { 0x55, 0xAA, 0x52, 0x08, 0x01 }; +static const u8 nt35510_vgh_on[] = { 0x01 }; + +static inline struct nt35510 *panel_to_nt35510(struct drm_panel *panel) +{ + return container_of(panel, struct nt35510, panel); +} + +#define NT35510_ROTATE_0_SETTING 0x02 +#define NT35510_ROTATE_180_SETTING 0x00 + +static int nt35510_send_long(struct nt35510 *nt, struct mipi_dsi_device *dsi, + u8 cmd, u8 cmdlen, const u8 *seq) +{ + const u8 *seqp = seq; + int cmdwritten = 0; + int chunk = cmdlen; + int ret; + + if (chunk > 15) + chunk = 15; + ret = mipi_dsi_dcs_write(dsi, cmd, seqp, chunk); + if (ret < 0) { + dev_err(nt->dev, "error sending DCS command seq cmd %02x\n", cmd); + return ret; + } + cmdwritten += chunk; + seqp += chunk; + + while (cmdwritten < cmdlen) { + chunk = cmdlen - cmdwritten; + if (chunk > 15) + chunk = 15; + ret = mipi_dsi_generic_write(dsi, seqp, chunk); + if (ret < 0) { + dev_err(nt->dev, "error sending generic write seq %02x\n", cmd); + return ret; + } + cmdwritten += chunk; + seqp += chunk; + } + dev_dbg(nt->dev, "sent command %02x %02x bytes\n", cmd, cmdlen); + return 0; +} + +static int nt35510_read_id(struct nt35510 *nt) +{ + struct mipi_dsi_device *dsi = to_mipi_dsi_device(nt->dev); + u8 id1, id2, id3; + int ret; + + ret = mipi_dsi_dcs_read(dsi, MCS_CMD_READ_ID1, &id1, 1); + if (ret < 0) { + dev_err(nt->dev, "could not read MTP ID1\n"); + return ret; + } + ret = mipi_dsi_dcs_read(dsi, MCS_CMD_READ_ID2, &id2, 1); + if (ret < 0) { + dev_err(nt->dev, "could not read MTP ID2\n"); + return ret; + } + ret = mipi_dsi_dcs_read(dsi, MCS_CMD_READ_ID3, &id3, 1); + if (ret < 0) { + dev_err(nt->dev, "could not read MTP ID3\n"); + return ret; + } + + /* + * Multi-Time Programmable (?) memory contains manufacturer + * ID (e.g. Hydis 0x55), driver ID (e.g. NT35510 0xc0) and + * version. + */ + dev_info(nt->dev, "MTP ID manufacturer: %02x version: %02x driver: %02x\n", id1, id2, id3); + + return 0; +} + +/** + * nt35510_setup_power() - set up power config in page 1 + * @nt: the display instance to set up + */ +static int nt35510_setup_power(struct nt35510 *nt) +{ + struct mipi_dsi_device *dsi = to_mipi_dsi_device(nt->dev); + int ret; + + ret = nt35510_send_long(nt, dsi, NT35510_P1_SETAVDD, + NT35510_P1_AVDD_LEN, + nt->conf->avdd); + if (ret) + return ret; + ret = nt35510_send_long(nt, dsi, NT35510_P1_BT1CTR, + NT35510_P1_BT1CTR_LEN, + nt->conf->bt1ctr); + if (ret) + return ret; + ret = nt35510_send_long(nt, dsi, NT35510_P1_SETAVEE, + NT35510_P1_AVEE_LEN, + nt->conf->avee); + if (ret) + return ret; + ret = nt35510_send_long(nt, dsi, NT35510_P1_BT2CTR, + NT35510_P1_BT2CTR_LEN, + nt->conf->bt2ctr); + if (ret) + return ret; + ret = nt35510_send_long(nt, dsi, NT35510_P1_SETVCL, + NT35510_P1_VCL_LEN, + nt->conf->vcl); + if (ret) + return ret; + ret = nt35510_send_long(nt, dsi, NT35510_P1_BT3CTR, + NT35510_P1_BT3CTR_LEN, + nt->conf->bt3ctr); + if (ret) + return ret; + ret = nt35510_send_long(nt, dsi, NT35510_P1_SETVGH, + NT35510_P1_VGH_LEN, + nt->conf->vgh); + if (ret) + return ret; + ret = nt35510_send_long(nt, dsi, NT35510_P1_BT4CTR, + NT35510_P1_BT4CTR_LEN, + nt->conf->bt4ctr); + if (ret) + return ret; + ret = nt35510_send_long(nt, dsi, NT35510_P1_VGHCTR, + ARRAY_SIZE(nt35510_vgh_on), + nt35510_vgh_on); + if (ret) + return ret; + ret = nt35510_send_long(nt, dsi, NT35510_P1_SETVGL, + NT35510_P1_VGL_LEN, + nt->conf->vgl); + if (ret) + return ret; + ret = nt35510_send_long(nt, dsi, NT35510_P1_BT5CTR, + NT35510_P1_BT5CTR_LEN, + nt->conf->bt5ctr); + if (ret) + return ret; + ret = nt35510_send_long(nt, dsi, NT35510_P1_SETVGP, + NT35510_P1_VGP_LEN, + nt->conf->vgp); + if (ret) + return ret; + ret = nt35510_send_long(nt, dsi, NT35510_P1_SETVGN, + NT35510_P1_VGN_LEN, + nt->conf->vgn); + if (ret) + return ret; + + if (nt->conf->cmds & NT35510_CMD_SETVCMOFF) { + ret = nt35510_send_long(nt, dsi, NT35510_P1_SETVCMOFF, + NT35510_P1_VCMOFF_LEN, + nt->conf->vcmoff); + if (ret) + return ret; + } + + /* Typically 10 ms */ + usleep_range(10000, 20000); + + return 0; +} + +/** + * nt35510_setup_display() - set up display config in page 0 + * @nt: the display instance to set up + */ +static int nt35510_setup_display(struct nt35510 *nt) +{ + struct mipi_dsi_device *dsi = to_mipi_dsi_device(nt->dev); + const struct nt35510_config *conf = nt->conf; + u8 dpfrctr[NT35510_P0_DPFRCTR1_LEN]; + int ret; + + ret = nt35510_send_long(nt, dsi, NT35510_P0_DOPCTR, + NT35510_P0_DOPCTR_LEN, + conf->dopctr); + if (ret) + return ret; + + ret = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_ADDRESS_MODE, &conf->madctl, + sizeof(conf->madctl)); + if (ret < 0) + return ret; + + ret = mipi_dsi_dcs_write(dsi, NT35510_P0_SDHDTCTR, &conf->sdhdtctr, + sizeof(conf->sdhdtctr)); + if (ret < 0) + return ret; + + ret = nt35510_send_long(nt, dsi, NT35510_P0_GSEQCTR, + NT35510_P0_GSEQCTR_LEN, + conf->gseqctr); + if (ret) + return ret; + + ret = nt35510_send_long(nt, dsi, NT35510_P0_SDEQCTR, + NT35510_P0_SDEQCTR_LEN, + conf->sdeqctr); + if (ret) + return ret; + + ret = mipi_dsi_dcs_write(dsi, NT35510_P0_SDVPCTR, + &conf->sdvpctr, 1); + if (ret < 0) + return ret; + + /* + * Display timing control for active and idle off mode: + * the first byte contains + * the two high bits of T1A and second byte the low 8 bits, and + * the valid range is 0x100 (257) to 0x3ff (1023) representing + * 258..1024 (+1) pixel clock ticks for one scanline. At 20MHz pixel + * clock this covers the range of 12.90us .. 51.20us in steps of + * 0.05us, the default is 0x184 (388) representing 389 ticks. + * The third byte is VBPDA, vertical back porch display active + * and the fourth VFPDA, vertical front porch display active, + * both given in number of scanlines in the range 0x02..0xff + * for 2..255 scanlines. The fifth byte is 2 bits selecting + * PSEL for active and idle off mode, how much the 20MHz clock + * is divided by 0..3. This needs to be adjusted to get the right + * frame rate. + */ + dpfrctr[0] = (conf->t1 >> 8) & 0xFF; + dpfrctr[1] = conf->t1 & 0xFF; + /* Vertical back porch */ + dpfrctr[2] = conf->vbp; + /* Vertical front porch */ + dpfrctr[3] = conf->vfp; + dpfrctr[4] = conf->psel; + ret = nt35510_send_long(nt, dsi, NT35510_P0_DPFRCTR1, + NT35510_P0_DPFRCTR1_LEN, + dpfrctr); + if (ret) + return ret; + /* For idle and partial idle off mode we decrease front porch by one */ + dpfrctr[3]--; + ret = nt35510_send_long(nt, dsi, NT35510_P0_DPFRCTR2, + NT35510_P0_DPFRCTR2_LEN, + dpfrctr); + if (ret) + return ret; + ret = nt35510_send_long(nt, dsi, NT35510_P0_DPFRCTR3, + NT35510_P0_DPFRCTR3_LEN, + dpfrctr); + if (ret) + return ret; + + /* Enable TE on vblank */ + ret = mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK); + if (ret) + return ret; + + /* Turn on the pads? */ + ret = nt35510_send_long(nt, dsi, NT35510_P0_DPMCTR12, + NT35510_P0_DPMCTR12_LEN, + conf->dpmctr12); + if (ret) + return ret; + + return 0; +} + +static int nt35510_set_brightness(struct backlight_device *bl) +{ + struct nt35510 *nt = bl_get_data(bl); + struct mipi_dsi_device *dsi = to_mipi_dsi_device(nt->dev); + u8 brightness = bl->props.brightness; + int ret; + + dev_dbg(nt->dev, "set brightness %d\n", brightness); + ret = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_DISPLAY_BRIGHTNESS, + &brightness, + sizeof(brightness)); + if (ret < 0) + return ret; + + return 0; +} + +static const struct backlight_ops nt35510_bl_ops = { + .update_status = nt35510_set_brightness, +}; + +/* + * This power-on sequence + */ +static int nt35510_power_on(struct nt35510 *nt) +{ + struct mipi_dsi_device *dsi = to_mipi_dsi_device(nt->dev); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(nt->supplies), nt->supplies); + if (ret < 0) { + dev_err(nt->dev, "unable to enable regulators\n"); + return ret; + } + + /* Toggle RESET in accordance with datasheet page 370 */ + if (nt->reset_gpio) { + gpiod_set_value(nt->reset_gpio, 1); + /* Active min 10 us according to datasheet, let's say 20 */ + usleep_range(20, 1000); + gpiod_set_value(nt->reset_gpio, 0); + /* + * 5 ms during sleep mode, 120 ms during sleep out mode + * according to datasheet, let's use 120-140 ms. + */ + usleep_range(120000, 140000); + } + + ret = nt35510_send_long(nt, dsi, MCS_CMD_MTP_READ_PARAM, + ARRAY_SIZE(nt35510_mauc_mtp_read_param), + nt35510_mauc_mtp_read_param); + if (ret) + return ret; + + ret = nt35510_send_long(nt, dsi, MCS_CMD_MTP_READ_SETTING, + ARRAY_SIZE(nt35510_mauc_mtp_read_setting), + nt35510_mauc_mtp_read_setting); + if (ret) + return ret; + + nt35510_read_id(nt); + + /* Set up stuff in manufacturer control, page 1 */ + ret = nt35510_send_long(nt, dsi, MCS_CMD_MAUCCTR, + ARRAY_SIZE(nt35510_mauc_select_page_1), + nt35510_mauc_select_page_1); + if (ret) + return ret; + + ret = nt35510_setup_power(nt); + if (ret) + return ret; + + if (nt->conf->cmds & NT35510_CMD_CORRECT_GAMMA) { + ret = nt35510_send_long(nt, dsi, NT35510_P1_SET_GAMMA_RED_POS, + NT35510_P1_GAMMA_LEN, + nt->conf->gamma_corr_pos_r); + if (ret) + return ret; + ret = nt35510_send_long(nt, dsi, NT35510_P1_SET_GAMMA_GREEN_POS, + NT35510_P1_GAMMA_LEN, + nt->conf->gamma_corr_pos_g); + if (ret) + return ret; + ret = nt35510_send_long(nt, dsi, NT35510_P1_SET_GAMMA_BLUE_POS, + NT35510_P1_GAMMA_LEN, + nt->conf->gamma_corr_pos_b); + if (ret) + return ret; + ret = nt35510_send_long(nt, dsi, NT35510_P1_SET_GAMMA_RED_NEG, + NT35510_P1_GAMMA_LEN, + nt->conf->gamma_corr_neg_r); + if (ret) + return ret; + ret = nt35510_send_long(nt, dsi, NT35510_P1_SET_GAMMA_GREEN_NEG, + NT35510_P1_GAMMA_LEN, + nt->conf->gamma_corr_neg_g); + if (ret) + return ret; + ret = nt35510_send_long(nt, dsi, NT35510_P1_SET_GAMMA_BLUE_NEG, + NT35510_P1_GAMMA_LEN, + nt->conf->gamma_corr_neg_b); + if (ret) + return ret; + } + + /* Set up stuff in manufacturer control, page 0 */ + ret = nt35510_send_long(nt, dsi, MCS_CMD_MAUCCTR, + ARRAY_SIZE(nt35510_mauc_select_page_0), + nt35510_mauc_select_page_0); + if (ret) + return ret; + + ret = nt35510_setup_display(nt); + if (ret) + return ret; + + return 0; +} + +static int nt35510_power_off(struct nt35510 *nt) +{ + int ret; + + ret = regulator_bulk_disable(ARRAY_SIZE(nt->supplies), nt->supplies); + if (ret) + return ret; + + if (nt->reset_gpio) + gpiod_set_value(nt->reset_gpio, 1); + + return 0; +} + +static int nt35510_unprepare(struct drm_panel *panel) +{ + struct nt35510 *nt = panel_to_nt35510(panel); + struct mipi_dsi_device *dsi = to_mipi_dsi_device(nt->dev); + int ret; + + ret = mipi_dsi_dcs_set_display_off(dsi); + if (ret) { + dev_err(nt->dev, "failed to turn display off (%d)\n", ret); + return ret; + } + usleep_range(10000, 20000); + + /* Enter sleep mode */ + ret = mipi_dsi_dcs_enter_sleep_mode(dsi); + if (ret) { + dev_err(nt->dev, "failed to enter sleep mode (%d)\n", ret); + return ret; + } + + /* Wait 4 frames, how much is that 5ms in the vendor driver */ + usleep_range(5000, 10000); + + ret = nt35510_power_off(nt); + if (ret) + return ret; + + return 0; +} + +static int nt35510_prepare(struct drm_panel *panel) +{ + struct nt35510 *nt = panel_to_nt35510(panel); + struct mipi_dsi_device *dsi = to_mipi_dsi_device(nt->dev); + int ret; + + ret = nt35510_power_on(nt); + if (ret) + return ret; + + /* Exit sleep mode */ + ret = mipi_dsi_dcs_exit_sleep_mode(dsi); + if (ret) { + dev_err(nt->dev, "failed to exit sleep mode (%d)\n", ret); + return ret; + } + /* Up to 120 ms */ + usleep_range(120000, 150000); + + if (nt->conf->cmds & NT35510_CMD_CONTROL_DISPLAY) { + ret = mipi_dsi_dcs_write(dsi, MIPI_DCS_WRITE_CONTROL_DISPLAY, + &nt->conf->wrctrld, + sizeof(nt->conf->wrctrld)); + if (ret < 0) + return ret; + + ret = mipi_dsi_dcs_write(dsi, MIPI_DCS_WRITE_POWER_SAVE, + &nt->conf->wrcabc, + sizeof(nt->conf->wrcabc)); + if (ret < 0) + return ret; + + ret = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_CABC_MIN_BRIGHTNESS, + &nt->conf->wrcabcmb, + sizeof(nt->conf->wrcabcmb)); + if (ret < 0) + return ret; + } + + ret = mipi_dsi_dcs_set_display_on(dsi); + if (ret) { + dev_err(nt->dev, "failed to turn display on (%d)\n", ret); + return ret; + } + /* Some 10 ms */ + usleep_range(10000, 20000); + + return 0; +} + +static int nt35510_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct nt35510 *nt = panel_to_nt35510(panel); + struct drm_display_mode *mode; + struct drm_display_info *info; + + info = &connector->display_info; + info->width_mm = nt->conf->width_mm; + info->height_mm = nt->conf->height_mm; + mode = drm_mode_duplicate(connector->dev, &nt->conf->mode); + if (!mode) { + dev_err(panel->dev, "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; + + mode->width_mm = nt->conf->width_mm; + mode->height_mm = nt->conf->height_mm; + drm_mode_probed_add(connector, mode); + + return 1; /* Number of modes */ +} + +static const struct drm_panel_funcs nt35510_drm_funcs = { + .unprepare = nt35510_unprepare, + .prepare = nt35510_prepare, + .get_modes = nt35510_get_modes, +}; + +static int nt35510_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct nt35510 *nt; + int ret; + + nt = devm_drm_panel_alloc(dev, struct nt35510, panel, + &nt35510_drm_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(nt)) + return PTR_ERR(nt); + + mipi_dsi_set_drvdata(dsi, nt); + nt->dev = dev; + + dsi->lanes = 2; + dsi->format = MIPI_DSI_FMT_RGB888; + /* + * Datasheet suggests max HS rate for NT35510 is 250 MHz + * (period time 4ns, see figure 7.6.4 page 365) and max LP rate is + * 20 MHz (period time 50ns, see figure 7.6.6. page 366). + * However these frequencies appear in source code for the Hydis + * HVA40WV1 panel and setting up the LP frequency makes the panel + * not work. + * + * TODO: if other panels prove to be closer to the datasheet, + * maybe make this a per-panel config in struct nt35510_config? + */ + dsi->hs_rate = 349440000; + dsi->lp_rate = 9600000; + + /* + * Every new incarnation of this display must have a unique + * data entry for the system in this driver. + */ + nt->conf = of_device_get_match_data(dev); + if (!nt->conf) { + dev_err(dev, "missing device configuration\n"); + return -ENODEV; + } + + dsi->mode_flags = nt->conf->mode_flags; + + nt->supplies[0].supply = "vdd"; /* 2.3-4.8 V */ + nt->supplies[1].supply = "vddi"; /* 1.65-3.3V */ + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(nt->supplies), + nt->supplies); + if (ret < 0) + return ret; + ret = regulator_set_voltage(nt->supplies[0].consumer, + 2300000, 4800000); + if (ret) + return ret; + ret = regulator_set_voltage(nt->supplies[1].consumer, + 1650000, 3300000); + if (ret) + return ret; + + nt->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(nt->reset_gpio)) { + dev_err(dev, "error getting RESET GPIO\n"); + return PTR_ERR(nt->reset_gpio); + } + + /* + * First, try to locate an external backlight (such as on GPIO) + * if this fails, assume we will want to use the internal backlight + * control. + */ + ret = drm_panel_of_backlight(&nt->panel); + if (ret) { + dev_err(dev, "error getting external backlight %d\n", ret); + return ret; + } + if (!nt->panel.backlight) { + struct backlight_device *bl; + + bl = devm_backlight_device_register(dev, "nt35510", dev, nt, + &nt35510_bl_ops, NULL); + if (IS_ERR(bl)) { + dev_err(dev, "failed to register backlight device\n"); + return PTR_ERR(bl); + } + bl->props.max_brightness = 255; + if (nt->conf->cmds & NT35510_CMD_CONTROL_DISPLAY) + bl->props.brightness = nt->conf->wrdisbv; + else + bl->props.brightness = 255; + bl->props.power = BACKLIGHT_POWER_OFF; + nt->panel.backlight = bl; + } + + drm_panel_add(&nt->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) + drm_panel_remove(&nt->panel); + + return 0; +} + +static void nt35510_remove(struct mipi_dsi_device *dsi) +{ + struct nt35510 *nt = mipi_dsi_get_drvdata(dsi); + int ret; + + mipi_dsi_detach(dsi); + /* Power off */ + ret = nt35510_power_off(nt); + if (ret) + dev_err(&dsi->dev, "Failed to power off\n"); + + drm_panel_remove(&nt->panel); +} + +/* + * These gamma correction values are 10bit tuples, so only bits 0 and 1 is + * ever used in the first byte. They form a positive and negative gamma + * correction curve for each color, values must be strictly higher for each + * step on the curve. As can be seen these default curves goes from 0x0001 + * to 0x03FE. + */ +#define NT35510_GAMMA_POS_DEFAULT 0x00, 0x01, 0x00, 0x43, 0x00, \ + 0x6B, 0x00, 0x87, 0x00, 0xA3, 0x00, 0xCE, 0x00, 0xF1, 0x01, \ + 0x27, 0x01, 0x53, 0x01, 0x98, 0x01, 0xCE, 0x02, 0x22, 0x02, \ + 0x83, 0x02, 0x78, 0x02, 0x9E, 0x02, 0xDD, 0x03, 0x00, 0x03, \ + 0x2E, 0x03, 0x54, 0x03, 0x7F, 0x03, 0x95, 0x03, 0xB3, 0x03, \ + 0xC2, 0x03, 0xE1, 0x03, 0xF1, 0x03, 0xFE + +#define NT35510_GAMMA_NEG_DEFAULT 0x00, 0x01, 0x00, 0x43, 0x00, \ + 0x6B, 0x00, 0x87, 0x00, 0xA3, 0x00, 0xCE, 0x00, 0xF1, 0x01, \ + 0x27, 0x01, 0x53, 0x01, 0x98, 0x01, 0xCE, 0x02, 0x22, 0x02, \ + 0x43, 0x02, 0x50, 0x02, 0x9E, 0x02, 0xDD, 0x03, 0x00, 0x03, \ + 0x2E, 0x03, 0x54, 0x03, 0x7F, 0x03, 0x95, 0x03, 0xB3, 0x03, \ + 0xC2, 0x03, 0xE1, 0x03, 0xF1, 0x03, 0xFE + +/* + * The Hydis HVA40WV1 panel + */ +static const struct nt35510_config nt35510_hydis_hva40wv1 = { + .width_mm = 52, + .height_mm = 86, + /** + * As the Hydis panel is used in command mode, the porches etc + * are settings programmed internally into the NT35510 controller + * and generated toward the physical display. As the panel is not + * used in video mode, these are not really exposed to the DSI + * host. + * + * Display frame rate control: + * Frame rate = (20 MHz / 1) / (389 * (7 + 50 + 800)) ~= 60 Hz + */ + .mode = { + /* The internal pixel clock of the NT35510 is 20 MHz */ + .clock = 20000, + .hdisplay = 480, + .hsync_start = 480 + 2, /* HFP = 2 */ + .hsync_end = 480 + 2 + 0, /* HSync = 0 */ + .htotal = 480 + 2 + 0 + 5, /* HBP = 5 */ + .vdisplay = 800, + .vsync_start = 800 + 2, /* VFP = 2 */ + .vsync_end = 800 + 2 + 0, /* VSync = 0 */ + .vtotal = 800 + 2 + 0 + 5, /* VBP = 5 */ + .flags = 0, + }, + .mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS, + .cmds = NT35510_CMD_CORRECT_GAMMA, + /* 0x09: AVDD = 5.6V */ + .avdd = { 0x09, 0x09, 0x09 }, + /* 0x34: PCK = Hsync/2, BTP = 2 x VDDB */ + .bt1ctr = { 0x34, 0x34, 0x34 }, + /* 0x09: AVEE = -5.6V */ + .avee = { 0x09, 0x09, 0x09 }, + /* 0x24: NCK = Hsync/2, BTN = -2 x VDDB */ + .bt2ctr = { 0x24, 0x24, 0x24 }, + /* VBCLA: -2.5V, VBCLB: -2.5V, VBCLC: -2.5V */ + .vcl = { 0x00, 0x00, 0x00 }, + /* 0x24: CLCK = Hsync/2, BTN = -1 x VDDB */ + .bt3ctr = { 0x24, 0x24, 0x24 }, + /* 0x05 = 12V */ + .vgh = { 0x05, 0x05, 0x05 }, + /* 0x24: NCKA = Hsync/2, VGH = 2 x AVDD - AVEE */ + .bt4ctr = { 0x24, 0x24, 0x24 }, + /* 0x0B = -13V */ + .vgl = { 0x0B, 0x0B, 0x0B }, + /* 0x24: LCKA = Hsync, VGL = AVDD + VCL - AVDD */ + .bt5ctr = { 0x24, 0x24, 0x24 }, + /* VGMP: 0x0A3 = 5.0375V, VGSP = 0V */ + .vgp = { 0x00, 0xA3, 0x00 }, + /* VGMP: 0x0A3 = 5.0375V, VGSP = 0V */ + .vgn = { 0x00, 0xA3, 0x00 }, + /* VCMOFFSEL = VCOM voltage offset mode, VCM = 0V */ + .vcmoff = { 0x00, 0x00 }, + /* Enable TE, EoTP and RGB pixel format */ + .dopctr = { NT35510_DOPCTR_0_DSITE | NT35510_DOPCTR_0_EOTP | + NT35510_DOPCTR_0_N565, NT35510_DOPCTR_1_CTB }, + .madctl = NT35510_ROTATE_0_SETTING, + /* 0x0A: SDT = 5 us */ + .sdhdtctr = 0x0A, + /* EQ control for gate signals, 0x00 = 0 us */ + .gseqctr = { 0x00, 0x00 }, + /* SDEQCTR: source driver EQ mode 2, 2.5 us rise time on each step */ + .sdeqctr = { 0x01, 0x05, 0x05, 0x05 }, + /* SDVPCTR: Normal operation off color during v porch */ + .sdvpctr = 0x01, + /* T1: number of pixel clocks on one scanline: 0x184 = 389 clocks */ + .t1 = 0x0184, + /* VBP: vertical back porch toward the panel */ + .vbp = 7, + /* VFP: vertical front porch toward the panel */ + .vfp = 50, + /* PSEL: divide pixel clock 20MHz with 1 (no clock downscaling) */ + .psel = 0, + /* DPTMCTR12: 0x03: LVGL = VGLX, overlap mode, swap R->L O->E */ + .dpmctr12 = { 0x03, 0x00, 0x00, }, + /* Default gamma correction values */ + .gamma_corr_pos_r = { NT35510_GAMMA_POS_DEFAULT }, + .gamma_corr_pos_g = { NT35510_GAMMA_POS_DEFAULT }, + .gamma_corr_pos_b = { NT35510_GAMMA_POS_DEFAULT }, + .gamma_corr_neg_r = { NT35510_GAMMA_NEG_DEFAULT }, + .gamma_corr_neg_g = { NT35510_GAMMA_NEG_DEFAULT }, + .gamma_corr_neg_b = { NT35510_GAMMA_NEG_DEFAULT }, +}; + +static const struct nt35510_config nt35510_frida_frd400b25025 = { + .width_mm = 52, + .height_mm = 86, + .mode = { + .clock = 23000, + .hdisplay = 480, + .hsync_start = 480 + 34, /* HFP = 34 */ + .hsync_end = 480 + 34 + 2, /* HSync = 2 */ + .htotal = 480 + 34 + 2 + 34, /* HBP = 34 */ + .vdisplay = 800, + .vsync_start = 800 + 15, /* VFP = 15 */ + .vsync_end = 800 + 15 + 12, /* VSync = 12 */ + .vtotal = 800 + 15 + 12 + 15, /* VBP = 15 */ + .flags = 0, + }, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_LPM, + .cmds = NT35510_CMD_CONTROL_DISPLAY | NT35510_CMD_SETVCMOFF, + /* 0x03: AVDD = 6.2V */ + .avdd = { 0x03, 0x03, 0x03 }, + /* 0x46: PCK = 2 x Hsync, BTP = 2.5 x VDDB */ + .bt1ctr = { 0x46, 0x46, 0x46 }, + /* 0x03: AVEE = -6.2V */ + .avee = { 0x03, 0x03, 0x03 }, + /* 0x36: PCK = 2 x Hsync, BTP = 2 x VDDB */ + .bt2ctr = { 0x36, 0x36, 0x36 }, + /* VBCLA: -2.5V, VBCLB: -2.5V, VBCLC: -3.5V */ + .vcl = { 0x00, 0x00, 0x02 }, + /* 0x26: CLCK = 2 x Hsync, BTN = -1 x VDDB */ + .bt3ctr = { 0x26, 0x26, 0x26 }, + /* 0x09 = 16V */ + .vgh = { 0x09, 0x09, 0x09 }, + /* 0x36: HCK = 2 x Hsync, VGH = 2 x AVDD - AVEE */ + .bt4ctr = { 0x36, 0x36, 0x36 }, + /* 0x08 = -10V */ + .vgl = { 0x08, 0x08, 0x08 }, + /* 0x26: LCK = 2 x Hsync, VGL = AVDD + VCL - AVDD */ + .bt5ctr = { 0x26, 0x26, 0x26 }, + /* VGMP: 0x080 = 4.6V, VGSP = 0V */ + .vgp = { 0x00, 0x80, 0x00 }, + /* VGMP: 0x080 = 4.6V, VGSP = 0V */ + .vgn = { 0x00, 0x80, 0x00 }, + /* VCMOFFSEL = VCOM voltage offset mode, VCM = -1V */ + .vcmoff = { 0x00, 0x50 }, + .dopctr = { NT35510_DOPCTR_0_RAMKP | NT35510_DOPCTR_0_DSITE | + NT35510_DOPCTR_0_DSIG | NT35510_DOPCTR_0_DSIM | + NT35510_DOPCTR_0_EOTP | NT35510_DOPCTR_0_N565, 0 }, + .madctl = NT35510_ROTATE_180_SETTING, + /* 0x03: SDT = 1.5 us */ + .sdhdtctr = 0x03, + /* EQ control for gate signals, 0x00 = 0 us */ + .gseqctr = { 0x00, 0x00 }, + /* SDEQCTR: source driver EQ mode 2, 1 us rise time on each step */ + .sdeqctr = { 0x01, 0x02, 0x02, 0x02 }, + /* SDVPCTR: Normal operation off color during v porch */ + .sdvpctr = 0x01, + /* T1: number of pixel clocks on one scanline: 0x184 = 389 clocks */ + .t1 = 0x0184, + /* VBP: vertical back porch toward the panel */ + .vbp = 0x1C, + /* VFP: vertical front porch toward the panel */ + .vfp = 0x1C, + /* PSEL: divide pixel clock 23MHz with 1 (no clock downscaling) */ + .psel = 0, + /* DPTMCTR12: 0x03: LVGL = VGLX, overlap mode, swap R->L O->E */ + .dpmctr12 = { 0x03, 0x00, 0x00, }, + /* write display brightness */ + .wrdisbv = 0x7f, + /* write control display */ + .wrctrld = NT35510_WRCTRLD_BCTRL | NT35510_WRCTRLD_DD | + NT35510_WRCTRLD_BL, + /* write content adaptive brightness control */ + .wrcabc = NT35510_WRCABC_STILL_MODE, + /* write CABC minimum brightness */ + .wrcabcmb = 0xff, +}; + +static const struct of_device_id nt35510_of_match[] = { + { + .compatible = "frida,frd400b25025", + .data = &nt35510_frida_frd400b25025, + }, + { + .compatible = "hydis,hva40wv1", + .data = &nt35510_hydis_hva40wv1, + }, + { } +}; +MODULE_DEVICE_TABLE(of, nt35510_of_match); + +static struct mipi_dsi_driver nt35510_driver = { + .probe = nt35510_probe, + .remove = nt35510_remove, + .driver = { + .name = "panel-novatek-nt35510", + .of_match_table = nt35510_of_match, + }, +}; +module_mipi_dsi_driver(nt35510_driver); + +MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>"); +MODULE_DESCRIPTION("NT35510-based panel driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-novatek-nt35560.c b/drivers/gpu/drm/panel/panel-novatek-nt35560.c new file mode 100644 index 000000000000..561e6643dcbb --- /dev/null +++ b/drivers/gpu/drm/panel/panel-novatek-nt35560.c @@ -0,0 +1,480 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * MIPI-DSI Novatek NT35560-based panel controller. + * + * Supported panels include: + * Sony ACX424AKM - a 480x854 AMOLED DSI panel + * Sony ACX424AKP - a 480x864 AMOLED DSI panel + * + * Copyright (C) Linaro Ltd. 2019-2021 + * Author: Linus Walleij + * Based on code and know-how from Marcus Lorentzon + * Copyright (C) ST-Ericsson SA 2010 + * Based on code and know-how from Johan Olson and Joakim Wesslen + * Copyright (C) Sony Ericsson Mobile Communications 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> + +#define NT35560_DCS_READ_ID1 0xDA +#define NT35560_DCS_READ_ID2 0xDB +#define NT35560_DCS_READ_ID3 0xDC +#define NT35560_DCS_SET_MDDI 0xAE + +/* + * Sony seems to use vendor ID 0x81 + */ +#define DISPLAY_SONY_ACX424AKP_ID1 0x8103 +#define DISPLAY_SONY_ACX424AKP_ID2 0x811a +#define DISPLAY_SONY_ACX424AKP_ID3 0x811b +/* + * The fourth ID looks like a bug, vendor IDs begin at 0x80 + * and panel 00 ... seems like default values. + */ +#define DISPLAY_SONY_ACX424AKP_ID4 0x8000 + +struct nt35560_config { + const struct drm_display_mode *vid_mode; + const struct drm_display_mode *cmd_mode; +}; + +struct nt35560 { + const struct nt35560_config *conf; + struct drm_panel panel; + struct device *dev; + struct regulator *supply; + struct gpio_desc *reset_gpio; + bool video_mode; +}; + +static const struct drm_display_mode sony_acx424akp_vid_mode = { + .clock = 27234, + .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, + .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 = 35478, + .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. + */ + .width_mm = 48, + .height_mm = 84, +}; + +static const struct nt35560_config sony_acx424akp_data = { + .vid_mode = &sony_acx424akp_vid_mode, + .cmd_mode = &sony_acx424akp_cmd_mode, +}; + +static const struct drm_display_mode sony_acx424akm_vid_mode = { + .clock = 27234, + .hdisplay = 480, + .hsync_start = 480 + 15, + .hsync_end = 480 + 15 + 0, + .htotal = 480 + 15 + 0 + 15, + .vdisplay = 854, + .vsync_start = 854 + 14, + .vsync_end = 854 + 14 + 1, + .vtotal = 854 + 14 + 1 + 11, + .width_mm = 46, + .height_mm = 82, + .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_acx424akm_cmd_mode = { + .clock = 35478, + .hdisplay = 480, + .hsync_start = 480 + 154, + .hsync_end = 480 + 154 + 16, + .htotal = 480 + 154 + 16 + 32, + .vdisplay = 854, + .vsync_start = 854 + 1, + .vsync_end = 854 + 1 + 1, + .vtotal = 854 + 1 + 1 + 1, + .width_mm = 46, + .height_mm = 82, +}; + +static const struct nt35560_config sony_acx424akm_data = { + .vid_mode = &sony_acx424akm_vid_mode, + .cmd_mode = &sony_acx424akm_cmd_mode, +}; + +static inline struct nt35560 *panel_to_nt35560(struct drm_panel *panel) +{ + return container_of(panel, struct nt35560, panel); +} + +#define FOSC 20 /* 20Mhz */ +#define SCALE_FACTOR_NS_DIV_MHZ 1000 + +static int nt35560_set_brightness(struct backlight_device *bl) +{ + struct nt35560 *nt = bl_get_data(bl); + struct mipi_dsi_multi_context dsi_ctx = { + .dsi = to_mipi_dsi_device(nt->dev) + }; + int duty_ns = bl->props.brightness; + int period_ns = 1023; + u8 pwm_ratio; + u8 pwm_div; + + if (backlight_is_blank(bl)) { + /* Disable backlight */ + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, + MIPI_DCS_WRITE_CONTROL_DISPLAY, + 0x00); + return dsi_ctx.accum_err; + } + + /* 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) */ + dev_dbg(nt->dev, "calculated duty cycle %02x\n", pwm_ratio); + + /* + * 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 + */ + mipi_dsi_dcs_write_var_seq_multi(&dsi_ctx, + MIPI_DCS_SET_DISPLAY_BRIGHTNESS, + pwm_ratio); + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf3, 0xaa); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x00, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7d, 0x01); + + mipi_dsi_dcs_write_var_seq_multi(&dsi_ctx, 0x22, pwm_div); + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7f, 0xaa); + + /* Enable backlight */ + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY, + 0x24); + + return dsi_ctx.accum_err; +} + +static const struct backlight_ops nt35560_bl_ops = { + .update_status = nt35560_set_brightness, +}; + +static const struct backlight_properties nt35560_bl_props = { + .type = BACKLIGHT_RAW, + .brightness = 512, + .max_brightness = 1023, +}; + +static void nt35560_read_id(struct mipi_dsi_multi_context *dsi_ctx) +{ + struct device dev = dsi_ctx->dsi->dev; + u8 vendor, version, panel; + u16 val; + + mipi_dsi_dcs_read_multi(dsi_ctx, NT35560_DCS_READ_ID1, &vendor, 1); + mipi_dsi_dcs_read_multi(dsi_ctx, NT35560_DCS_READ_ID2, &version, 1); + mipi_dsi_dcs_read_multi(dsi_ctx, NT35560_DCS_READ_ID3, &panel, 1); + + if (dsi_ctx->accum_err < 0) + return; + + if (vendor == 0x00) { + dev_err(&dev, "device vendor ID is zero\n"); + dsi_ctx->accum_err = -ENODEV; + return; + } + + val = (vendor << 8) | panel; + switch (val) { + case DISPLAY_SONY_ACX424AKP_ID1: + case DISPLAY_SONY_ACX424AKP_ID2: + case DISPLAY_SONY_ACX424AKP_ID3: + case DISPLAY_SONY_ACX424AKP_ID4: + dev_info(&dev, + "MTP vendor: %02x, version: %02x, panel: %02x\n", + vendor, version, panel); + break; + default: + dev_info(&dev, + "unknown vendor: %02x, version: %02x, panel: %02x\n", + vendor, version, panel); + break; + } +} + +static int nt35560_power_on(struct nt35560 *nt) +{ + int ret; + + ret = regulator_enable(nt->supply); + if (ret) { + dev_err(nt->dev, "failed to enable supply (%d)\n", ret); + return ret; + } + + /* Assert RESET */ + gpiod_set_value_cansleep(nt->reset_gpio, 1); + udelay(20); + /* De-assert RESET */ + gpiod_set_value_cansleep(nt->reset_gpio, 0); + usleep_range(11000, 20000); + + return 0; +} + +static void nt35560_power_off(struct nt35560 *nt) +{ + /* Assert RESET */ + gpiod_set_value_cansleep(nt->reset_gpio, 1); + usleep_range(11000, 20000); + + regulator_disable(nt->supply); +} + +static int nt35560_prepare(struct drm_panel *panel) +{ + struct nt35560 *nt = panel_to_nt35560(panel); + struct mipi_dsi_multi_context dsi_ctx = { + .dsi = to_mipi_dsi_device(nt->dev) + }; + int ret; + + ret = nt35560_power_on(nt); + if (ret) + return ret; + + nt35560_read_id(&dsi_ctx); + + /* Enable tearing mode: send TE (tearing effect) at VBLANK */ + mipi_dsi_dcs_set_tear_on_multi(&dsi_ctx, + MIPI_DSI_DCS_TEAR_MODE_VBLANK); + + /* + * Set MDDI + * + * This presumably deactivates the Qualcomm MDDI interface and + * selects DSI, similar code is found in other drivers such as the + * Sharp LS043T1LE01. + */ + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, NT35560_DCS_SET_MDDI, 3); + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 140); + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + if (nt->video_mode) { + mipi_dsi_turn_on_peripheral_multi(&dsi_ctx); + } + + if (dsi_ctx.accum_err < 0) + nt35560_power_off(nt); + return dsi_ctx.accum_err; +} + +static int nt35560_unprepare(struct drm_panel *panel) +{ + struct nt35560 *nt = panel_to_nt35560(panel); + struct mipi_dsi_multi_context dsi_ctx = { + .dsi = to_mipi_dsi_device(nt->dev) + }; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + + if (dsi_ctx.accum_err < 0) + return dsi_ctx.accum_err; + + msleep(85); + + nt35560_power_off(nt); + + return 0; +} + + +static int nt35560_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct nt35560 *nt = panel_to_nt35560(panel); + const struct nt35560_config *conf = nt->conf; + struct drm_display_mode *mode; + + if (nt->video_mode) + mode = drm_mode_duplicate(connector->dev, + conf->vid_mode); + else + mode = drm_mode_duplicate(connector->dev, + conf->cmd_mode); + if (!mode) { + dev_err(panel->dev, "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 nt35560_drm_funcs = { + .unprepare = nt35560_unprepare, + .prepare = nt35560_prepare, + .get_modes = nt35560_get_modes, +}; + +static int nt35560_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct nt35560 *nt; + int ret; + + nt = devm_drm_panel_alloc(dev, struct nt35560, panel, + &nt35560_drm_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(nt)) + return PTR_ERR(nt); + + nt->video_mode = of_property_read_bool(dev->of_node, + "enforce-video-mode"); + + mipi_dsi_set_drvdata(dsi, nt); + nt->dev = dev; + + nt->conf = of_device_get_match_data(dev); + if (!nt->conf) { + dev_err(dev, "missing device configuration\n"); + return -ENODEV; + } + + 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 (nt->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; + + nt->supply = devm_regulator_get(dev, "vddi"); + if (IS_ERR(nt->supply)) + return PTR_ERR(nt->supply); + + /* This asserts RESET by default */ + nt->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(nt->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(nt->reset_gpio), + "failed to request GPIO\n"); + + nt->panel.backlight = devm_backlight_device_register(dev, "nt35560", dev, nt, + &nt35560_bl_ops, &nt35560_bl_props); + if (IS_ERR(nt->panel.backlight)) + return dev_err_probe(dev, PTR_ERR(nt->panel.backlight), + "failed to register backlight device\n"); + + drm_panel_add(&nt->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + drm_panel_remove(&nt->panel); + return ret; + } + + return 0; +} + +static void nt35560_remove(struct mipi_dsi_device *dsi) +{ + struct nt35560 *nt = mipi_dsi_get_drvdata(dsi); + + mipi_dsi_detach(dsi); + drm_panel_remove(&nt->panel); +} + +static const struct of_device_id nt35560_of_match[] = { + { + .compatible = "sony,acx424akp", + .data = &sony_acx424akp_data, + }, + { + .compatible = "sony,acx424akm", + .data = &sony_acx424akm_data, + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, nt35560_of_match); + +static struct mipi_dsi_driver nt35560_driver = { + .probe = nt35560_probe, + .remove = nt35560_remove, + .driver = { + .name = "panel-novatek-nt35560", + .of_match_table = nt35560_of_match, + }, +}; +module_mipi_dsi_driver(nt35560_driver); + +MODULE_AUTHOR("Linus Wallei <linus.walleij@linaro.org>"); +MODULE_DESCRIPTION("MIPI-DSI Novatek NT35560 Panel Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-novatek-nt35950.c b/drivers/gpu/drm/panel/panel-novatek-nt35950.c new file mode 100644 index 000000000000..94aa6489d99f --- /dev/null +++ b/drivers/gpu/drm/panel/panel-novatek-nt35950.c @@ -0,0 +1,608 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Novatek NT35950 DriverIC panels driver + * + * Copyright (c) 2021 AngeloGioacchino Del Regno + * <angelogioacchino.delregno@somainline.org> + */ +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/regulator/consumer.h> + +#include <drm/drm_connector.h> +#include <drm/drm_crtc.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +#define MCS_CMD_MAUCCTR 0xf0 /* Manufacturer command enable */ +#define MCS_PARAM_SCALER_FUNCTION 0x58 /* Scale-up function */ +#define MCS_PARAM_SCALEUP_MODE 0xc9 + #define MCS_SCALEUP_SIMPLE 0x0 + #define MCS_SCALEUP_BILINEAR BIT(0) + #define MCS_SCALEUP_DUPLICATE (BIT(0) | BIT(4)) + +/* VESA Display Stream Compression param */ +#define MCS_PARAM_VESA_DSC_ON 0x03 + +/* Data Compression mode */ +#define MCS_PARAM_DATA_COMPRESSION 0x90 + #define MCS_DATA_COMPRESSION_NONE 0x00 + #define MCS_DATA_COMPRESSION_FBC 0x02 + #define MCS_DATA_COMPRESSION_DSC 0x03 + +/* Display Output control */ +#define MCS_PARAM_DISP_OUTPUT_CTRL 0xb4 + #define MCS_DISP_OUT_SRAM_EN BIT(0) + #define MCS_DISP_OUT_VIDEO_MODE BIT(4) + +/* VESA Display Stream Compression setting */ +#define MCS_PARAM_VESA_DSC_SETTING 0xc0 + +/* SubPixel Rendering (SPR) */ +#define MCS_PARAM_SPR_EN 0xe3 +#define MCS_PARAM_SPR_MODE 0xef + #define MCS_SPR_MODE_YYG_RAINBOW_RGB 0x01 + +#define NT35950_VREG_MAX 4 + +struct nt35950 { + struct drm_panel panel; + struct drm_connector *connector; + struct mipi_dsi_device *dsi[2]; + struct regulator_bulk_data vregs[NT35950_VREG_MAX]; + struct gpio_desc *reset_gpio; + const struct nt35950_panel_desc *desc; + + int cur_mode; + u8 last_page; +}; + +struct nt35950_panel_mode { + const struct drm_display_mode mode; + + bool enable_sram; + bool is_video_mode; + u8 scaler_on; + u8 scaler_mode; + u8 compression; + u8 spr_en; + u8 spr_mode; +}; + +struct nt35950_panel_desc { + const char *model_name; + const struct mipi_dsi_device_info dsi_info; + const struct nt35950_panel_mode *mode_data; + + bool is_dual_dsi; + u8 num_lanes; + u8 num_modes; +}; + +static inline struct nt35950 *to_nt35950(struct drm_panel *panel) +{ + return container_of(panel, struct nt35950, panel); +} + +static void nt35950_reset(struct nt35950 *nt) +{ + gpiod_set_value_cansleep(nt->reset_gpio, 1); + usleep_range(12000, 13000); + gpiod_set_value_cansleep(nt->reset_gpio, 0); + usleep_range(300, 400); + gpiod_set_value_cansleep(nt->reset_gpio, 1); + usleep_range(12000, 13000); +} + +/* + * nt35950_set_cmd2_page - Select manufacturer control (CMD2) page + * @dsi_ctx: context for mipi_dsi functions + * @nt: Main driver structure + * @page: Page number (0-7) + */ +static void nt35950_set_cmd2_page(struct mipi_dsi_multi_context *dsi_ctx, + struct nt35950 *nt, u8 page) +{ + const u8 mauc_cmd2_page[] = { MCS_CMD_MAUCCTR, 0x55, 0xaa, 0x52, + 0x08, page }; + + mipi_dsi_dcs_write_buffer_multi(dsi_ctx, mauc_cmd2_page, + ARRAY_SIZE(mauc_cmd2_page)); + if (!dsi_ctx->accum_err) + nt->last_page = page; +} + +/* + * nt35950_set_data_compression - Set data compression mode + * @dsi_ctx: context for mipi_dsi functions + * @nt: Main driver structure + * @comp_mode: Compression mode + */ +static void nt35950_set_data_compression(struct mipi_dsi_multi_context *dsi_ctx, + struct nt35950 *nt, u8 comp_mode) +{ + u8 cmd_data_compression[] = { MCS_PARAM_DATA_COMPRESSION, comp_mode }; + u8 cmd_vesa_dsc_on[] = { MCS_PARAM_VESA_DSC_ON, !!comp_mode }; + u8 cmd_vesa_dsc_setting[] = { MCS_PARAM_VESA_DSC_SETTING, 0x03 }; + u8 last_page = nt->last_page; + + /* Set CMD2 Page 0 if we're not there yet */ + if (last_page != 0) + nt35950_set_cmd2_page(dsi_ctx, nt, 0); + + mipi_dsi_dcs_write_buffer_multi(dsi_ctx, cmd_data_compression, + ARRAY_SIZE(cmd_data_compression)); + mipi_dsi_dcs_write_buffer_multi(dsi_ctx, cmd_vesa_dsc_on, + ARRAY_SIZE(cmd_vesa_dsc_on)); + + /* Set the vesa dsc setting on Page 4 */ + nt35950_set_cmd2_page(dsi_ctx, nt, 4); + + /* Display Stream Compression setting, always 0x03 */ + mipi_dsi_dcs_write_buffer_multi(dsi_ctx, cmd_vesa_dsc_setting, + ARRAY_SIZE(cmd_vesa_dsc_setting)); + + /* Get back to the previously set page */ + nt35950_set_cmd2_page(dsi_ctx, nt, last_page); +} + +/* + * nt35950_set_scaler - Enable/disable resolution upscaling + * @dsi_ctx: context for mipi_dsi functions + * @scale_up: Scale up function control + */ +static void nt35950_set_scaler(struct mipi_dsi_multi_context *dsi_ctx, + u8 scale_up) +{ + u8 cmd_scaler[] = { MCS_PARAM_SCALER_FUNCTION, scale_up }; + + mipi_dsi_dcs_write_buffer_multi(dsi_ctx, cmd_scaler, + ARRAY_SIZE(cmd_scaler)); +} + +/* + * nt35950_set_scale_mode - Resolution upscaling mode + * @dsi_ctx: context for mipi_dsi functions + * @mode: Scaler mode (MCS_DATA_COMPRESSION_*) + */ +static void nt35950_set_scale_mode(struct mipi_dsi_multi_context *dsi_ctx, + u8 mode) +{ + u8 cmd_scaler[] = { MCS_PARAM_SCALEUP_MODE, mode }; + + mipi_dsi_dcs_write_buffer_multi(dsi_ctx, cmd_scaler, + ARRAY_SIZE(cmd_scaler)); +} + +/* + * nt35950_inject_black_image - Display a completely black image + * @dsi_ctx: context for mipi_dsi functions + * + * After IC setup, the attached panel may show random data + * due to driveric behavior changes (resolution, compression, + * scaling, etc). This function, called after parameters setup, + * makes the driver ic to output a completely black image to + * the display. + * It makes sense to push a black image before sending the sleep-out + * and display-on commands. + */ +static void nt35950_inject_black_image(struct mipi_dsi_multi_context *dsi_ctx) +{ + const u8 cmd0_black_img[] = { 0x6f, 0x01 }; + const u8 cmd1_black_img[] = { 0xf3, 0x10 }; + u8 cmd_test[] = { 0xff, 0xaa, 0x55, 0xa5, 0x80 }; + + /* Enable test command */ + mipi_dsi_dcs_write_buffer_multi(dsi_ctx, cmd_test, ARRAY_SIZE(cmd_test)); + + /* Send a black image */ + mipi_dsi_dcs_write_buffer_multi(dsi_ctx, cmd0_black_img, + ARRAY_SIZE(cmd0_black_img)); + mipi_dsi_dcs_write_buffer_multi(dsi_ctx, cmd1_black_img, + ARRAY_SIZE(cmd1_black_img)); + + /* Disable test command */ + cmd_test[ARRAY_SIZE(cmd_test) - 1] = 0x00; + mipi_dsi_dcs_write_buffer_multi(dsi_ctx, cmd_test, ARRAY_SIZE(cmd_test)); +} + +/* + * nt35950_set_dispout - Set Display Output register parameters + * @nt: Main driver structure + * @dsi_ctx: context for mipi_dsi functions + */ +static void nt35950_set_dispout(struct mipi_dsi_multi_context *dsi_ctx, + struct nt35950 *nt) +{ + u8 cmd_dispout[] = { MCS_PARAM_DISP_OUTPUT_CTRL, 0x00 }; + const struct nt35950_panel_mode *mode_data = nt->desc->mode_data; + + if (mode_data[nt->cur_mode].is_video_mode) + cmd_dispout[1] |= MCS_DISP_OUT_VIDEO_MODE; + if (mode_data[nt->cur_mode].enable_sram) + cmd_dispout[1] |= MCS_DISP_OUT_SRAM_EN; + + mipi_dsi_dcs_write_buffer_multi(dsi_ctx, cmd_dispout, + ARRAY_SIZE(cmd_dispout)); +} + +static int nt35950_get_current_mode(struct nt35950 *nt) +{ + struct drm_connector *connector = nt->connector; + struct drm_crtc_state *crtc_state; + int i; + + /* Return the default (first) mode if no info available yet */ + if (!connector->state || !connector->state->crtc) + return 0; + + crtc_state = connector->state->crtc->state; + + for (i = 0; i < nt->desc->num_modes; i++) { + if (drm_mode_match(&crtc_state->mode, + &nt->desc->mode_data[i].mode, + DRM_MODE_MATCH_TIMINGS | DRM_MODE_MATCH_CLOCK)) + return i; + } + + return 0; +} + +static int nt35950_on(struct nt35950 *nt) +{ + const struct nt35950_panel_mode *mode_data = nt->desc->mode_data; + struct mipi_dsi_device *dsi = nt->dsi[0]; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + nt->cur_mode = nt35950_get_current_mode(nt); + nt->dsi[0]->mode_flags |= MIPI_DSI_MODE_LPM; + nt->dsi[1]->mode_flags |= MIPI_DSI_MODE_LPM; + + nt35950_set_cmd2_page(&dsi_ctx, nt, 0); + nt35950_set_data_compression(&dsi_ctx, nt, mode_data[nt->cur_mode].compression); + nt35950_set_scale_mode(&dsi_ctx, mode_data[nt->cur_mode].scaler_mode); + nt35950_set_scaler(&dsi_ctx, mode_data[nt->cur_mode].scaler_on); + nt35950_set_dispout(&dsi_ctx, nt); + + mipi_dsi_dcs_set_tear_on_multi(&dsi_ctx, MIPI_DSI_DCS_TEAR_MODE_VBLANK); + mipi_dsi_dcs_set_tear_scanline_multi(&dsi_ctx, 0); + + /* CMD2 Page 1 */ + nt35950_set_cmd2_page(&dsi_ctx, nt, 1); + + /* Unknown command */ + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xd4, 0x88, 0x88); + + /* CMD2 Page 7 */ + nt35950_set_cmd2_page(&dsi_ctx, nt, 7); + + /* Enable SubPixel Rendering */ + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_PARAM_SPR_EN, 0x01); + + /* SPR Mode: YYG Rainbow-RGB */ + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_PARAM_SPR_MODE, + MCS_SPR_MODE_YYG_RAINBOW_RGB); + + /* CMD3 */ + nt35950_inject_black_image(&dsi_ctx); + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 120); + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 120); + + if (dsi_ctx.accum_err) + return dsi_ctx.accum_err; + + nt->dsi[0]->mode_flags &= ~MIPI_DSI_MODE_LPM; + nt->dsi[1]->mode_flags &= ~MIPI_DSI_MODE_LPM; + + return 0; +} + +static void nt35950_off(struct nt35950 *nt) +{ + struct mipi_dsi_device *dsi = nt->dsi[0]; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_usleep_range(&dsi_ctx, 10000, 11000); + + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 150); + + nt->dsi[0]->mode_flags |= MIPI_DSI_MODE_LPM; + nt->dsi[1]->mode_flags |= MIPI_DSI_MODE_LPM; +} + +static int nt35950_sharp_init_vregs(struct nt35950 *nt, struct device *dev) +{ + int ret; + + nt->vregs[0].supply = "vddio"; + nt->vregs[1].supply = "avdd"; + nt->vregs[2].supply = "avee"; + nt->vregs[3].supply = "dvdd"; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(nt->vregs), + nt->vregs); + if (ret < 0) + return ret; + + ret = regulator_is_supported_voltage(nt->vregs[0].consumer, + 1750000, 1950000); + if (!ret) + return -EINVAL; + ret = regulator_is_supported_voltage(nt->vregs[1].consumer, + 5200000, 5900000); + if (!ret) + return -EINVAL; + /* AVEE is negative: -5.90V to -5.20V */ + ret = regulator_is_supported_voltage(nt->vregs[2].consumer, + 5200000, 5900000); + if (!ret) + return -EINVAL; + + ret = regulator_is_supported_voltage(nt->vregs[3].consumer, + 1300000, 1400000); + if (!ret) + return -EINVAL; + + return 0; +} + +static int nt35950_prepare(struct drm_panel *panel) +{ + struct nt35950 *nt = to_nt35950(panel); + int ret; + + ret = regulator_enable(nt->vregs[0].consumer); + if (ret) + return ret; + usleep_range(2000, 5000); + + ret = regulator_enable(nt->vregs[3].consumer); + if (ret) + goto end; + usleep_range(15000, 18000); + + ret = regulator_enable(nt->vregs[1].consumer); + if (ret) + goto end; + + ret = regulator_enable(nt->vregs[2].consumer); + if (ret) + goto end; + usleep_range(12000, 13000); + + nt35950_reset(nt); + + ret = nt35950_on(nt); + +end: + if (ret < 0) { + regulator_bulk_disable(ARRAY_SIZE(nt->vregs), nt->vregs); + return ret; + } + + return 0; +} + +static int nt35950_unprepare(struct drm_panel *panel) +{ + struct nt35950 *nt = to_nt35950(panel); + + nt35950_off(nt); + + gpiod_set_value_cansleep(nt->reset_gpio, 0); + regulator_bulk_disable(ARRAY_SIZE(nt->vregs), nt->vregs); + + return 0; +} + +static int nt35950_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct nt35950 *nt = to_nt35950(panel); + int i; + + for (i = 0; i < nt->desc->num_modes; i++) { + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, + &nt->desc->mode_data[i].mode); + if (!mode) + return -ENOMEM; + + drm_mode_set_name(mode); + + mode->type |= DRM_MODE_TYPE_DRIVER; + if (nt->desc->num_modes == 1) + mode->type |= DRM_MODE_TYPE_PREFERRED; + + drm_mode_probed_add(connector, mode); + } + + connector->display_info.bpc = 8; + connector->display_info.height_mm = nt->desc->mode_data[0].mode.height_mm; + connector->display_info.width_mm = nt->desc->mode_data[0].mode.width_mm; + nt->connector = connector; + + return nt->desc->num_modes; +} + +static const struct drm_panel_funcs nt35950_panel_funcs = { + .prepare = nt35950_prepare, + .unprepare = nt35950_unprepare, + .get_modes = nt35950_get_modes, +}; + +static int nt35950_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct device_node *dsi_r; + struct mipi_dsi_host *dsi_r_host; + struct nt35950 *nt; + const struct mipi_dsi_device_info *info; + int i, num_dsis = 1, ret; + + nt = devm_drm_panel_alloc(dev, struct nt35950, panel, &nt35950_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(nt)) + return PTR_ERR(nt); + + ret = nt35950_sharp_init_vregs(nt, dev); + if (ret) + return dev_err_probe(dev, ret, "Regulator init failure.\n"); + + nt->desc = of_device_get_match_data(dev); + if (!nt->desc) + return -ENODEV; + + nt->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_ASIS); + if (IS_ERR(nt->reset_gpio)) { + return dev_err_probe(dev, PTR_ERR(nt->reset_gpio), + "Failed to get reset gpio\n"); + } + + /* If the panel is connected on two DSIs then DSI0 left, DSI1 right */ + if (nt->desc->is_dual_dsi) { + info = &nt->desc->dsi_info; + dsi_r = of_graph_get_remote_node(dsi->dev.of_node, 1, -1); + if (!dsi_r) { + dev_err(dev, "Cannot get secondary DSI node.\n"); + return -ENODEV; + } + dsi_r_host = of_find_mipi_dsi_host_by_node(dsi_r); + of_node_put(dsi_r); + if (!dsi_r_host) + return dev_err_probe(dev, -EPROBE_DEFER, "Cannot get secondary DSI host\n"); + + nt->dsi[1] = mipi_dsi_device_register_full(dsi_r_host, info); + if (IS_ERR(nt->dsi[1])) { + dev_err(dev, "Cannot get secondary DSI node\n"); + return PTR_ERR(nt->dsi[1]); + } + num_dsis++; + } + + nt->dsi[0] = dsi; + mipi_dsi_set_drvdata(dsi, nt); + + ret = drm_panel_of_backlight(&nt->panel); + if (ret) { + if (num_dsis == 2) + mipi_dsi_device_unregister(nt->dsi[1]); + + return dev_err_probe(dev, ret, "Failed to get backlight\n"); + } + + drm_panel_add(&nt->panel); + + for (i = 0; i < num_dsis; i++) { + nt->dsi[i]->lanes = nt->desc->num_lanes; + nt->dsi[i]->format = MIPI_DSI_FMT_RGB888; + + nt->dsi[i]->mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS | + MIPI_DSI_MODE_LPM; + + if (nt->desc->mode_data[0].is_video_mode) + nt->dsi[i]->mode_flags |= MIPI_DSI_MODE_VIDEO; + + ret = mipi_dsi_attach(nt->dsi[i]); + if (ret < 0) { + /* If we fail to attach to either host, we're done */ + if (num_dsis == 2) + mipi_dsi_device_unregister(nt->dsi[1]); + + return dev_err_probe(dev, ret, + "Cannot attach to DSI%d host.\n", i); + } + } + + /* Make sure to set RESX LOW before starting the power-on sequence */ + gpiod_set_value_cansleep(nt->reset_gpio, 0); + return 0; +} + +static void nt35950_remove(struct mipi_dsi_device *dsi) +{ + struct nt35950 *nt = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(nt->dsi[0]); + if (ret < 0) + dev_err(&dsi->dev, + "Failed to detach from DSI0 host: %d\n", ret); + + if (nt->dsi[1]) { + ret = mipi_dsi_detach(nt->dsi[1]); + if (ret < 0) + dev_err(&dsi->dev, + "Failed to detach from DSI1 host: %d\n", ret); + mipi_dsi_device_unregister(nt->dsi[1]); + } + + drm_panel_remove(&nt->panel); +} + +static const struct nt35950_panel_mode sharp_ls055d1sx04_modes[] = { + { + /* 1920x1080 60Hz no compression */ + .mode = { + .clock = 214537, + .hdisplay = 1080, + .hsync_start = 1080 + 400, + .hsync_end = 1080 + 400 + 40, + .htotal = 1080 + 400 + 40 + 300, + .vdisplay = 1920, + .vsync_start = 1920 + 12, + .vsync_end = 1920 + 12 + 2, + .vtotal = 1920 + 12 + 2 + 10, + .width_mm = 68, + .height_mm = 121, + }, + .compression = MCS_DATA_COMPRESSION_NONE, + .enable_sram = true, + .is_video_mode = false, + .scaler_on = 1, + .scaler_mode = MCS_SCALEUP_DUPLICATE, + }, + /* TODO: Add 2160x3840 60Hz when DSC is supported */ +}; + +static const struct nt35950_panel_desc sharp_ls055d1sx04 = { + .model_name = "Sharp LS055D1SX04", + .dsi_info = { + .type = "LS055D1SX04", + .channel = 0, + .node = NULL, + }, + .mode_data = sharp_ls055d1sx04_modes, + .num_modes = ARRAY_SIZE(sharp_ls055d1sx04_modes), + .is_dual_dsi = true, + .num_lanes = 4, +}; + +static const struct of_device_id nt35950_of_match[] = { + { .compatible = "sharp,ls055d1sx04", .data = &sharp_ls055d1sx04 }, + { } +}; +MODULE_DEVICE_TABLE(of, nt35950_of_match); + +static struct mipi_dsi_driver nt35950_driver = { + .probe = nt35950_probe, + .remove = nt35950_remove, + .driver = { + .name = "panel-novatek-nt35950", + .of_match_table = nt35950_of_match, + }, +}; +module_mipi_dsi_driver(nt35950_driver); + +MODULE_AUTHOR("AngeloGioacchino Del Regno <angelogioacchino.delregno@somainline.org>"); +MODULE_DESCRIPTION("Novatek NT35950 DriverIC panels driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-novatek-nt36523.c b/drivers/gpu/drm/panel/panel-novatek-nt36523.c new file mode 100644 index 000000000000..226d91daf8c7 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-novatek-nt36523.c @@ -0,0 +1,1272 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Novatek NT36523 DriverIC panels driver + * + * Copyright (c) 2022, 2023 Jianhua Lu <lujianhua000@gmail.com> + */ + +#include <linux/backlight.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/regulator/consumer.h> + +#include <video/mipi_display.h> + +#include <drm/drm_connector.h> +#include <drm/drm_crtc.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +#define DSI_NUM_MIN 1 + +struct panel_info { + struct drm_panel panel; + struct mipi_dsi_device *dsi[2]; + const struct panel_desc *desc; + enum drm_panel_orientation orientation; + + struct gpio_desc *reset_gpio; + struct backlight_device *backlight; + struct regulator *vddio; +}; + +struct panel_desc { + unsigned int width_mm; + unsigned int height_mm; + + unsigned int bpc; + unsigned int lanes; + unsigned long mode_flags; + enum mipi_dsi_pixel_format format; + + const struct drm_display_mode *modes; + unsigned int num_modes; + const struct mipi_dsi_device_info dsi_info; + int (*init_sequence)(struct panel_info *pinfo); + + bool is_dual_dsi; + bool has_dcs_backlight; +}; + +static inline struct panel_info *to_panel_info(struct drm_panel *panel) +{ + return container_of(panel, struct panel_info, panel); +} + +static int elish_boe_init_sequence(struct panel_info *pinfo) +{ + struct mipi_dsi_device *dsi0 = pinfo->dsi[0]; + struct mipi_dsi_device *dsi1 = pinfo->dsi[1]; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = NULL }; + /* No datasheet, so write magic init sequence directly */ + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x10); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xb9, 0x05); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x20); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x18, 0x40); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x10); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xb9, 0x02); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x23); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x00, 0x80); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x01, 0x84); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x05, 0x2d); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x06, 0x00); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x07, 0x00); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x08, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x09, 0x45); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x11, 0x02); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x12, 0x80); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x15, 0x83); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x16, 0x0c); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x29, 0x0a); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x30, 0xff); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x31, 0xfe); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x32, 0xfd); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x33, 0xfb); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x34, 0xf8); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x35, 0xf5); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x36, 0xf3); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x37, 0xf2); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x38, 0xf2); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x39, 0xf2); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x3a, 0xef); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x3b, 0xec); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x3d, 0xe9); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x3f, 0xe5); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x40, 0xe5); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x41, 0xe5); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x2a, 0x13); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x45, 0xff); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x46, 0xf4); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x47, 0xe7); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x48, 0xda); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x49, 0xcd); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x4a, 0xc0); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x4b, 0xb3); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x4c, 0xb2); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x4d, 0xb2); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x4e, 0xb2); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x4f, 0x99); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x50, 0x80); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x51, 0x68); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x52, 0x66); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x53, 0x66); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x54, 0x66); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x2b, 0x0e); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x58, 0xff); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x59, 0xfb); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x5a, 0xf7); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x5b, 0xf3); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x5c, 0xef); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x5d, 0xe3); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x5e, 0xda); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x5f, 0xd8); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x60, 0xd8); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x61, 0xd8); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x62, 0xcb); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x63, 0xbf); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x64, 0xb3); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x65, 0xb2); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x66, 0xb2); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x67, 0xb2); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x2a); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x25, 0x47); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x30, 0x47); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x39, 0x47); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x26); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x19, 0x10); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x1a, 0xe0); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x1b, 0x10); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x1c, 0x00); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x2a, 0x10); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x2b, 0xe0); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x10); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0xf0); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x84, 0x08); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x85, 0x0c); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x20); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x51, 0x00); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x25); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x91, 0x1f); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x92, 0x0f); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x93, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x94, 0x18); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x95, 0x03); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x96, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x10); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xb0, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x25); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x19, 0x1f); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x1b, 0x1b); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x24); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xb8, 0x28); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x27); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xd0, 0x31); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xd1, 0x20); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xd2, 0x30); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xd4, 0x08); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xde, 0x80); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xdf, 0x02); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x26); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x00, 0x81); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x01, 0xb0); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x22); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x9f, 0x50); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x6f, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x70, 0x11); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x73, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x74, 0x49); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x76, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x77, 0x49); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xa0, 0x3f); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xa9, 0x50); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xaa, 0x28); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xab, 0x28); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xad, 0x10); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xb8, 0x00); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xb9, 0x49); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xba, 0x49); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xbb, 0x49); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xbe, 0x04); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xbf, 0x49); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xc0, 0x04); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xc1, 0x59); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xc2, 0x00); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xc5, 0x00); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xc6, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xc7, 0x48); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xca, 0x43); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xcb, 0x3c); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xce, 0x00); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xcf, 0x43); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xd0, 0x3c); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xd3, 0x43); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xd4, 0x3c); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xd7, 0x00); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xdc, 0x43); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xdd, 0x3c); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xe1, 0x43); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xe2, 0x3c); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xf2, 0x00); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xf3, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xf4, 0x48); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x25); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x13, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x14, 0x23); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xbc, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xbd, 0x23); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x2a); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x97, 0x3c); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x98, 0x02); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x99, 0x95); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x9a, 0x03); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x9b, 0x00); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x9c, 0x0b); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x9d, 0x0a); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x9e, 0x90); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x22); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x9f, 0x50); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x23); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xa3, 0x50); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0xe0); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x14, 0x60); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x16, 0xc0); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x4f, 0x02); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0xf0); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x3a, 0x08); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0xd0); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x02, 0xaf); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x09, 0xee); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x1c, 0x99); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x1d, 0x09); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x10); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x51, 0x0f, 0xff); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x53, 0x2c); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x35, 0x00); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xbb, 0x13); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x3b, 0x03, 0xac, 0x1a, 0x04, 0x04); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x11); + mipi_dsi_msleep(&dsi_ctx, 70); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x29); + + return dsi_ctx.accum_err; +} + +static int elish_csot_init_sequence(struct panel_info *pinfo) +{ + struct mipi_dsi_device *dsi0 = pinfo->dsi[0]; + struct mipi_dsi_device *dsi1 = pinfo->dsi[1]; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = NULL }; + /* No datasheet, so write magic init sequence directly */ + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x10); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xb9, 0x05); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x20); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x18, 0x40); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x10); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xb9, 0x02); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0xd0); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x02, 0xaf); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x00, 0x30); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x09, 0xee); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x1c, 0x99); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x1d, 0x09); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0xf0); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x3a, 0x08); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0xe0); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x4f, 0x02); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x20); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x58, 0x40); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x10); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x35, 0x00); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x23); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x00, 0x80); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x01, 0x84); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x05, 0x2d); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x06, 0x00); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x07, 0x00); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x08, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x09, 0x45); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x11, 0x02); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x12, 0x80); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x15, 0x83); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x16, 0x0c); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x29, 0x0a); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x30, 0xff); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x31, 0xfe); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x32, 0xfd); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x33, 0xfb); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x34, 0xf8); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x35, 0xf5); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x36, 0xf3); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x37, 0xf2); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x38, 0xf2); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x39, 0xf2); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x3a, 0xef); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x3b, 0xec); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x3d, 0xe9); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x3f, 0xe5); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x40, 0xe5); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x41, 0xe5); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x2a, 0x13); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x45, 0xff); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x46, 0xf4); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x47, 0xe7); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x48, 0xda); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x49, 0xcd); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x4a, 0xc0); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x4b, 0xb3); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x4c, 0xb2); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x4d, 0xb2); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x4e, 0xb2); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x4f, 0x99); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x50, 0x80); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x51, 0x68); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x52, 0x66); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x53, 0x66); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x54, 0x66); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x2b, 0x0e); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x58, 0xff); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x59, 0xfb); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x5a, 0xf7); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x5b, 0xf3); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x5c, 0xef); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x5d, 0xe3); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x5e, 0xda); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x5f, 0xd8); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x60, 0xd8); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x61, 0xd8); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x62, 0xcb); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x63, 0xbf); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x64, 0xb3); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x65, 0xb2); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x66, 0xb2); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x67, 0xb2); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x10); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x51, 0x0f, 0xff); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x53, 0x2c); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x55, 0x00); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xbb, 0x13); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x3b, 0x03, 0xac, 0x1a, 0x04, 0x04); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x2a); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x25, 0x46); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x30, 0x46); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x39, 0x46); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x26); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x01, 0xb0); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x19, 0x10); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x1a, 0xe0); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x1b, 0x10); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x1c, 0x00); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x2a, 0x10); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x2b, 0xe0); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0xf0); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x84, 0x08); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x85, 0x0c); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x20); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x51, 0x00); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x25); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x91, 0x1f); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x92, 0x0f); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x93, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x94, 0x18); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x95, 0x03); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x96, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x10); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xb0, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x25); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x19, 0x1f); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x1b, 0x1b); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x24); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xb8, 0x28); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x27); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xd0, 0x31); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xd1, 0x20); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xd4, 0x08); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xde, 0x80); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xdf, 0x02); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x26); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x00, 0x81); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x01, 0xb0); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x22); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x6f, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x70, 0x11); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x73, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x74, 0x4d); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xa0, 0x3f); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xa9, 0x50); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xaa, 0x28); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xab, 0x28); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xad, 0x10); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xb8, 0x00); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xb9, 0x4b); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xba, 0x96); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xbb, 0x4b); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xbe, 0x07); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xbf, 0x4b); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xc0, 0x07); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xc1, 0x5c); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xc2, 0x00); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xc5, 0x00); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xc6, 0x3f); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xc7, 0x00); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xca, 0x08); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xcb, 0x40); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xce, 0x00); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xcf, 0x08); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xd0, 0x40); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xd3, 0x08); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xd4, 0x40); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x25); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xbc, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xbd, 0x1c); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x2a); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x9a, 0x03); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0xff, 0x10); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x11); + mipi_dsi_msleep(&dsi_ctx, 70); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 0x29); + + return dsi_ctx.accum_err; +} + +static int j606f_boe_init_sequence(struct panel_info *pinfo) +{ + struct mipi_dsi_device *dsi = pinfo->dsi[0]; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x20); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x05, 0xd9); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x07, 0x78); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x08, 0x5a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0d, 0x63); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0e, 0x91); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0f, 0x73); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x95, 0xeb); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x96, 0xeb); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_PARTIAL_ROWS, 0x11); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6d, 0x66); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x75, 0xa2); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x77, 0xb3); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x00, 0x08, 0x00, 0x23, 0x00, 0x4d, 0x00, 0x6d, + 0x00, 0x89, 0x00, 0xa1, 0x00, 0xb6, 0x00, 0xc9); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb1, 0x00, 0xda, 0x01, 0x13, 0x01, 0x3c, 0x01, 0x7e, + 0x01, 0xab, 0x01, 0xf7, 0x02, 0x2f, 0x02, 0x31); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb2, 0x02, 0x67, 0x02, 0xa6, 0x02, 0xd1, 0x03, 0x08, + 0x03, 0x2e, 0x03, 0x5b, 0x03, 0x6b, 0x03, 0x7b); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb3, 0x03, 0x8e, 0x03, 0xa2, 0x03, 0xb7, 0x03, 0xe7, + 0x03, 0xfd, 0x03, 0xff); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb4, 0x00, 0x08, 0x00, 0x23, 0x00, 0x4d, 0x00, 0x6d, + 0x00, 0x89, 0x00, 0xa1, 0x00, 0xb6, 0x00, 0xc9); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb5, 0x00, 0xda, 0x01, 0x13, 0x01, 0x3c, 0x01, 0x7e, + 0x01, 0xab, 0x01, 0xf7, 0x02, 0x2f, 0x02, 0x31); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb6, 0x02, 0x67, 0x02, 0xa6, 0x02, 0xd1, 0x03, 0x08, + 0x03, 0x2e, 0x03, 0x5b, 0x03, 0x6b, 0x03, 0x7b); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb7, 0x03, 0x8e, 0x03, 0xa2, 0x03, 0xb7, 0x03, 0xe7, + 0x03, 0xfd, 0x03, 0xff); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb8, 0x00, 0x08, 0x00, 0x23, 0x00, 0x4d, 0x00, 0x6d, + 0x00, 0x89, 0x00, 0xa1, 0x00, 0xb6, 0x00, 0xc9); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb9, 0x00, 0xda, 0x01, 0x13, 0x01, 0x3c, 0x01, 0x7e, + 0x01, 0xab, 0x01, 0xf7, 0x02, 0x2f, 0x02, 0x31); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xba, 0x02, 0x67, 0x02, 0xa6, 0x02, 0xd1, 0x03, 0x08, + 0x03, 0x2e, 0x03, 0x5b, 0x03, 0x6b, 0x03, 0x7b); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xbb, 0x03, 0x8e, 0x03, 0xa2, 0x03, 0xb7, 0x03, 0xe7, + 0x03, 0xfd, 0x03, 0xff); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x21); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x45, 0x00, 0x65, + 0x00, 0x81, 0x00, 0x99, 0x00, 0xae, 0x00, 0xc1); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb1, 0x00, 0xd2, 0x01, 0x0b, 0x01, 0x34, 0x01, 0x76, + 0x01, 0xa3, 0x01, 0xef, 0x02, 0x27, 0x02, 0x29); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb2, 0x02, 0x5f, 0x02, 0x9e, 0x02, 0xc9, 0x03, 0x00, + 0x03, 0x26, 0x03, 0x53, 0x03, 0x63, 0x03, 0x73); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb3, 0x03, 0x86, 0x03, 0x9a, 0x03, 0xaf, 0x03, 0xdf, + 0x03, 0xf5, 0x03, 0xf7); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb4, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x45, 0x00, 0x65, + 0x00, 0x81, 0x00, 0x99, 0x00, 0xae, 0x00, 0xc1); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb5, 0x00, 0xd2, 0x01, 0x0b, 0x01, 0x34, 0x01, 0x76, + 0x01, 0xa3, 0x01, 0xef, 0x02, 0x27, 0x02, 0x29); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb6, 0x02, 0x5f, 0x02, 0x9e, 0x02, 0xc9, 0x03, 0x00, + 0x03, 0x26, 0x03, 0x53, 0x03, 0x63, 0x03, 0x73); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb7, 0x03, 0x86, 0x03, 0x9a, 0x03, 0xaf, 0x03, 0xdf, + 0x03, 0xf5, 0x03, 0xf7); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb8, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x45, 0x00, 0x65, + 0x00, 0x81, 0x00, 0x99, 0x00, 0xae, 0x00, 0xc1); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb9, 0x00, 0xd2, 0x01, 0x0b, 0x01, 0x34, 0x01, 0x76, + 0x01, 0xa3, 0x01, 0xef, 0x02, 0x27, 0x02, 0x29); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xba, 0x02, 0x5f, 0x02, 0x9e, 0x02, 0xc9, 0x03, 0x00, + 0x03, 0x26, 0x03, 0x53, 0x03, 0x63, 0x03, 0x73); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xbb, 0x03, 0x86, 0x03, 0x9a, 0x03, 0xaf, 0x03, 0xdf, + 0x03, 0xf5, 0x03, 0xf7); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x23); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x00, 0x80); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x07, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x11, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x12, 0x77); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x15, 0x07); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x16, 0x07); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x24); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x01, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x02, 0x1c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x03, 0x1c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x04, 0x1d); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x05, 0x1d); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x06, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x07, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x08, 0x0f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x09, 0x0f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0a, 0x0e); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0b, 0x0e); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0c, 0x0d); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0d, 0x0d); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0e, 0x0c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0f, 0x0c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x10, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x11, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x12, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x13, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x14, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x15, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x16, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x17, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x18, 0x1c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x19, 0x1c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1a, 0x1d); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1b, 0x1d); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1c, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1d, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1e, 0x0f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1f, 0x0f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x20, 0x0e); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x21, 0x0e); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x22, 0x0d); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x23, 0x0d); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x24, 0x0c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x25, 0x0c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_GAMMA_CURVE, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x27, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x28, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x29, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2a, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2b, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_LUT, 0x20); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2f, 0x0a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_PARTIAL_ROWS, 0x44); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x33, 0x0c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x34, 0x32); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x37, 0x44); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x38, 0x40); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x39, 0x00); + + mipi_dsi_dcs_set_pixel_format_multi(&dsi_ctx, 0x9a); + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3b, 0xa0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_3D_CONTROL, 0x42); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3f, 0x06); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x43, 0x06); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x47, 0x66); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4a, 0x9a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4b, 0xa0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4c, 0x91); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4d, 0x21); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4e, 0x43); + + mipi_dsi_dcs_set_display_brightness_multi(&dsi_ctx, 18); + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x52, 0x34); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x55, 0x82, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x56, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x58, 0x21); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x59, 0x30); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5a, 0xba); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5b, 0xa0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_CABC_MIN_BRIGHTNESS, 0x00, 0x06); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5f, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x65, 0x82); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7e, 0x20); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7f, 0x3c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x82, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x97, 0xc0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb6, + 0x05, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, + 0x05, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x92, 0xc4); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x93, 0x1a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x94, 0x5f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xd7, 0x55); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xda, 0x0a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xde, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xdb, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xdc, 0xc4); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xdd, 0x22); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xdf, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe0, 0xc4); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe1, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe2, 0xc4); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe3, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe4, 0xc4); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe5, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe6, 0xc4); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5c, 0x88); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5d, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x8d, 0x88); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x8e, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb5, 0x90); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x25); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x05, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x19, 0x07); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1f, 0xba); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x20, 0xa0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_GAMMA_CURVE, 0xba); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x27, 0xa0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x33, 0xba); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x34, 0xa0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3f, 0xe0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_VSYNC_TIMING, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x44, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_GET_SCANLINE, 0x40); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x48, 0xba); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x49, 0xa0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5b, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5c, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5d, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_CABC_MIN_BRIGHTNESS, 0xd0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x61, 0xba); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x62, 0xa0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf1, 0x10); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x2a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x64, 0x16); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x67, 0x16); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6a, 0x16); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x70, 0x30); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_READ_PPS_START, 0xf3); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xa3, 0xff); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xa4, 0xff); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xa5, 0xff); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xd6, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x26); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x00, 0xa1); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0a, 0xf2); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x04, 0x28); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x06, 0x30); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0c, 0x13); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0d, 0x0a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0f, 0x0a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x11, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x12, 0x50); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x13, 0x51); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x14, 0x65); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x15, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x16, 0x10); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x17, 0xa0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x18, 0x86); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x19, 0x11); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1a, 0x7b); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1b, 0x10); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1c, 0xbb); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x22, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x23, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2a, 0x11); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2b, 0x7b); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1d, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1e, 0xc3); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1f, 0xc3); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x24, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x25, 0xc3); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2f, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_PARTIAL_ROWS, 0xc3); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_PARTIAL_COLUMNS, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x32, 0xc3); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x39, 0x00); + + mipi_dsi_dcs_set_pixel_format_multi(&dsi_ctx, 0xc3); + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x20, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x33, 0x11); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x34, 0x78); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x35, 0x16); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc8, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc9, 0x82); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xca, 0x4e); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xcb, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_READ_PPS_CONTINUE, 0x4c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xaa, 0x47); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x27); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x56, 0x06); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x58, 0x80); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x59, 0x53); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5a, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5b, 0x14); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5c, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5d, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_CABC_MIN_BRIGHTNESS, 0x20); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5f, 0x10); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x60, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x61, 0x1d); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x62, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x63, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x64, 0x24); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x65, 0x1c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x66, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x67, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x68, 0x25); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x78, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc3, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xd1, 0x24); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xd2, 0x30); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x2a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x22, 0x2f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x23, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x24, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x25, 0xc3); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_GAMMA_CURVE, 0xf8); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x27, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x28, 0x1a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x29, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2a, 0x1a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2b, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_LUT, 0x1a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0xe0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x14, 0x60); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x16, 0xc0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0xf0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, 0x01); + + mipi_dsi_dcs_set_pixel_format_multi(&dsi_ctx, 0x08); + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x24); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, 0x01); + + mipi_dsi_dcs_set_pixel_format_multi(&dsi_ctx, 0x5d); + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3b, 0x60); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4a, 0x5d); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4b, 0x60); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5a, 0x70); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5b, 0x60); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x91, 0x44); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x92, 0x75); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xdb, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xdc, 0x75); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xdd, 0x22); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xdf, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe0, 0x75); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe1, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe2, 0x75); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe3, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe4, 0x75); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe5, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe6, 0x75); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5c, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5d, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x8d, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x8e, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x25); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1f, 0x70); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x20, 0x60); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_GAMMA_CURVE, 0x70); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x27, 0x60); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x33, 0x70); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x34, 0x60); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x48, 0x70); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x49, 0x60); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5b, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x61, 0x70); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x62, 0x60); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x26); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x02, 0x31); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x19, 0x0a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1a, 0x7f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1b, 0x0a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1c, 0x0c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2a, 0x0a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2b, 0x7f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1e, 0x75); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1f, 0x75); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x25, 0x75); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_PARTIAL_ROWS, 0x75); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_PARTIAL_COLUMNS, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x32, 0x8d); + + mipi_dsi_dcs_set_pixel_format_multi(&dsi_ctx, 0x75); + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x2a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x25, 0x75); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x10); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb9, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x20); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x18, 0x40); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x10); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb9, 0x02); + + mipi_dsi_dcs_set_tear_on_multi(&dsi_ctx, MIPI_DSI_DCS_TEAR_MODE_VBLANK); + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xbb, 0x13); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3b, 0x03, 0x5f, 0x1a, 0x04, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x10); + mipi_dsi_usleep_range(&dsi_ctx, 10000, 11000); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, 0x01); + + mipi_dsi_dcs_set_display_brightness_multi(&dsi_ctx, 0); + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x2c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_POWER_SAVE, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x68, 0x05, 0x01); + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 100); + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 30); + + return dsi_ctx.accum_err; +} + +static const struct drm_display_mode elish_boe_modes[] = { + { + .clock = (1600 + 60 + 8 + 60) * (2560 + 26 + 4 + 168) * 120 / 1000, + .hdisplay = 1600, + .hsync_start = 1600 + 60, + .hsync_end = 1600 + 60 + 8, + .htotal = 1600 + 60 + 8 + 60, + .vdisplay = 2560, + .vsync_start = 2560 + 26, + .vsync_end = 2560 + 26 + 4, + .vtotal = 2560 + 26 + 4 + 168, + }, +}; + +static const struct drm_display_mode elish_csot_modes[] = { + { + .clock = (1600 + 200 + 40 + 52) * (2560 + 26 + 4 + 168) * 120 / 1000, + .hdisplay = 1600, + .hsync_start = 1600 + 200, + .hsync_end = 1600 + 200 + 40, + .htotal = 1600 + 200 + 40 + 52, + .vdisplay = 2560, + .vsync_start = 2560 + 26, + .vsync_end = 2560 + 26 + 4, + .vtotal = 2560 + 26 + 4 + 168, + }, +}; + +static const struct drm_display_mode j606f_boe_modes[] = { + { + .clock = (1200 + 58 + 2 + 60) * (2000 + 26 + 2 + 93) * 60 / 1000, + .hdisplay = 1200, + .hsync_start = 1200 + 58, + .hsync_end = 1200 + 58 + 2, + .htotal = 1200 + 58 + 2 + 60, + .vdisplay = 2000, + .vsync_start = 2000 + 26, + .vsync_end = 2000 + 26 + 2, + .vtotal = 2000 + 26 + 2 + 93, + .width_mm = 143, + .height_mm = 235, + }, +}; + +static const struct panel_desc elish_boe_desc = { + .modes = elish_boe_modes, + .num_modes = ARRAY_SIZE(elish_boe_modes), + .dsi_info = { + .type = "BOE-elish", + .channel = 0, + .node = NULL, + }, + .width_mm = 127, + .height_mm = 203, + .bpc = 8, + .lanes = 3, + .format = MIPI_DSI_FMT_RGB888, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS | MIPI_DSI_MODE_LPM, + .init_sequence = elish_boe_init_sequence, + .is_dual_dsi = true, +}; + +static const struct panel_desc elish_csot_desc = { + .modes = elish_csot_modes, + .num_modes = ARRAY_SIZE(elish_csot_modes), + .dsi_info = { + .type = "CSOT-elish", + .channel = 0, + .node = NULL, + }, + .width_mm = 127, + .height_mm = 203, + .bpc = 8, + .lanes = 3, + .format = MIPI_DSI_FMT_RGB888, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS | MIPI_DSI_MODE_LPM, + .init_sequence = elish_csot_init_sequence, + .is_dual_dsi = true, +}; + +static const struct panel_desc j606f_boe_desc = { + .modes = j606f_boe_modes, + .num_modes = ARRAY_SIZE(j606f_boe_modes), + .width_mm = 143, + .height_mm = 235, + .bpc = 8, + .lanes = 4, + .format = MIPI_DSI_FMT_RGB888, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_CLOCK_NON_CONTINUOUS | MIPI_DSI_MODE_LPM, + .init_sequence = j606f_boe_init_sequence, + .has_dcs_backlight = true, +}; + +static void nt36523_reset(struct panel_info *pinfo) +{ + gpiod_set_value_cansleep(pinfo->reset_gpio, 1); + usleep_range(12000, 13000); + gpiod_set_value_cansleep(pinfo->reset_gpio, 0); + usleep_range(12000, 13000); + gpiod_set_value_cansleep(pinfo->reset_gpio, 1); + usleep_range(12000, 13000); + gpiod_set_value_cansleep(pinfo->reset_gpio, 0); + usleep_range(12000, 13000); +} + +static int nt36523_prepare(struct drm_panel *panel) +{ + struct panel_info *pinfo = to_panel_info(panel); + int ret; + + ret = regulator_enable(pinfo->vddio); + if (ret) { + dev_err(panel->dev, "failed to enable vddio regulator: %d\n", ret); + return ret; + } + + nt36523_reset(pinfo); + + ret = pinfo->desc->init_sequence(pinfo); + if (ret < 0) { + regulator_disable(pinfo->vddio); + dev_err(panel->dev, "failed to initialize panel: %d\n", ret); + return ret; + } + + return 0; +} + +static int nt36523_disable(struct drm_panel *panel) +{ + struct panel_info *pinfo = to_panel_info(panel); + int i; + + for (i = 0; i < DSI_NUM_MIN + pinfo->desc->is_dual_dsi; i++) { + struct mipi_dsi_multi_context dsi_ctx = { .dsi = pinfo->dsi[i]}; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + } + + for (i = 0; i < DSI_NUM_MIN + pinfo->desc->is_dual_dsi; i++) { + struct mipi_dsi_multi_context dsi_ctx = { .dsi = pinfo->dsi[i]}; + + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + } + + msleep(70); + + return 0; +} + +static int nt36523_unprepare(struct drm_panel *panel) +{ + struct panel_info *pinfo = to_panel_info(panel); + + gpiod_set_value_cansleep(pinfo->reset_gpio, 1); + regulator_disable(pinfo->vddio); + + return 0; +} + +static void nt36523_remove(struct mipi_dsi_device *dsi) +{ + struct panel_info *pinfo = mipi_dsi_get_drvdata(dsi); + + drm_panel_remove(&pinfo->panel); +} + +static int nt36523_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct panel_info *pinfo = to_panel_info(panel); + int i; + + for (i = 0; i < pinfo->desc->num_modes; i++) { + const struct drm_display_mode *m = &pinfo->desc->modes[i]; + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, m); + if (!mode) { + dev_err(panel->dev, "failed to add mode %ux%u@%u\n", + m->hdisplay, m->vdisplay, drm_mode_vrefresh(m)); + return -ENOMEM; + } + + mode->type = DRM_MODE_TYPE_DRIVER; + if (i == 0) + mode->type |= DRM_MODE_TYPE_PREFERRED; + + 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 pinfo->desc->num_modes; +} + +static enum drm_panel_orientation nt36523_get_orientation(struct drm_panel *panel) +{ + struct panel_info *pinfo = to_panel_info(panel); + + return pinfo->orientation; +} + +static const struct drm_panel_funcs nt36523_panel_funcs = { + .disable = nt36523_disable, + .prepare = nt36523_prepare, + .unprepare = nt36523_unprepare, + .get_modes = nt36523_get_modes, + .get_orientation = nt36523_get_orientation, +}; + +static int nt36523_bl_update_status(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + u16 brightness = backlight_get_brightness(bl); + int ret; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + ret = mipi_dsi_dcs_set_display_brightness_large(dsi, brightness); + if (ret < 0) + return ret; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + return 0; +} + +static int nt36523_bl_get_brightness(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + u16 brightness; + int ret; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + ret = mipi_dsi_dcs_get_display_brightness_large(dsi, &brightness); + if (ret < 0) + return ret; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + return brightness; +} + +static const struct backlight_ops nt36523_bl_ops = { + .update_status = nt36523_bl_update_status, + .get_brightness = nt36523_bl_get_brightness, +}; + +static struct backlight_device *nt36523_create_backlight(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + const struct backlight_properties props = { + .type = BACKLIGHT_RAW, + .brightness = 512, + .max_brightness = 4095, + .scale = BACKLIGHT_SCALE_NON_LINEAR, + }; + + return devm_backlight_device_register(dev, dev_name(dev), dev, dsi, + &nt36523_bl_ops, &props); +} + +static int nt36523_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct device_node *dsi1; + struct mipi_dsi_host *dsi1_host; + struct panel_info *pinfo; + const struct mipi_dsi_device_info *info; + int i, ret; + + pinfo = devm_drm_panel_alloc(dev, struct panel_info, panel, + &nt36523_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(pinfo)) + return PTR_ERR(pinfo); + + pinfo->vddio = devm_regulator_get(dev, "vddio"); + if (IS_ERR(pinfo->vddio)) + return dev_err_probe(dev, PTR_ERR(pinfo->vddio), "failed to get vddio regulator\n"); + + pinfo->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(pinfo->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(pinfo->reset_gpio), "failed to get reset gpio\n"); + + pinfo->desc = of_device_get_match_data(dev); + if (!pinfo->desc) + return -ENODEV; + + /* If the panel is dual dsi, register DSI1 */ + if (pinfo->desc->is_dual_dsi) { + info = &pinfo->desc->dsi_info; + + dsi1 = of_graph_get_remote_node(dsi->dev.of_node, 1, -1); + if (!dsi1) { + dev_err(dev, "cannot get secondary DSI node.\n"); + return -ENODEV; + } + + dsi1_host = of_find_mipi_dsi_host_by_node(dsi1); + of_node_put(dsi1); + if (!dsi1_host) + return dev_err_probe(dev, -EPROBE_DEFER, "cannot get secondary DSI host\n"); + + pinfo->dsi[1] = devm_mipi_dsi_device_register_full(dev, dsi1_host, info); + if (IS_ERR(pinfo->dsi[1])) { + dev_err(dev, "cannot get secondary DSI device\n"); + return PTR_ERR(pinfo->dsi[1]); + } + } + + pinfo->dsi[0] = dsi; + mipi_dsi_set_drvdata(dsi, pinfo); + + ret = of_drm_get_panel_orientation(dev->of_node, &pinfo->orientation); + if (ret < 0) { + dev_err(dev, "%pOF: failed to get orientation %d\n", dev->of_node, ret); + return ret; + } + + pinfo->panel.prepare_prev_first = true; + + if (pinfo->desc->has_dcs_backlight) { + pinfo->panel.backlight = nt36523_create_backlight(dsi); + if (IS_ERR(pinfo->panel.backlight)) + return dev_err_probe(dev, PTR_ERR(pinfo->panel.backlight), + "Failed to create backlight\n"); + } else { + ret = drm_panel_of_backlight(&pinfo->panel); + if (ret) + return dev_err_probe(dev, ret, "Failed to get backlight\n"); + } + + drm_panel_add(&pinfo->panel); + + for (i = 0; i < DSI_NUM_MIN + pinfo->desc->is_dual_dsi; i++) { + pinfo->dsi[i]->lanes = pinfo->desc->lanes; + pinfo->dsi[i]->format = pinfo->desc->format; + pinfo->dsi[i]->mode_flags = pinfo->desc->mode_flags; + + ret = devm_mipi_dsi_attach(dev, pinfo->dsi[i]); + if (ret < 0) + return dev_err_probe(dev, ret, "cannot attach to DSI%d host.\n", i); + } + + return 0; +} + +static const struct of_device_id nt36523_of_match[] = { + { + .compatible = "lenovo,j606f-boe-nt36523w", + .data = &j606f_boe_desc, + }, + { + .compatible = "xiaomi,elish-boe-nt36523", + .data = &elish_boe_desc, + }, + { + .compatible = "xiaomi,elish-csot-nt36523", + .data = &elish_csot_desc, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, nt36523_of_match); + +static struct mipi_dsi_driver nt36523_driver = { + .probe = nt36523_probe, + .remove = nt36523_remove, + .driver = { + .name = "panel-novatek-nt36523", + .of_match_table = nt36523_of_match, + }, +}; +module_mipi_dsi_driver(nt36523_driver); + +MODULE_AUTHOR("Jianhua Lu <lujianhua000@gmail.com>"); +MODULE_DESCRIPTION("DRM driver for Novatek NT36523 based MIPI DSI panels"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-novatek-nt36672a.c b/drivers/gpu/drm/panel/panel-novatek-nt36672a.c new file mode 100644 index 000000000000..29e1f6aea480 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-novatek-nt36672a.c @@ -0,0 +1,684 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2020 Linaro Ltd + * Author: Sumit Semwal <sumit.semwal@linaro.org> + * + * This driver is for the DSI interface to panels using the NT36672A display driver IC + * from Novatek. + * Currently supported are the Tianma FHD+ panels found in some Xiaomi phones, including + * some variants of the Poco F1 phone. + * + * Panels using the Novatek NT37762A IC should add appropriate configuration per-panel and + * use this driver. + */ + +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> + +#include <linux/gpio/consumer.h> +#include <linux/pinctrl/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 <video/mipi_display.h> + +struct nt36672a_panel_cmd { + const char data[2]; +}; + +static const char * const nt36672a_regulator_names[] = { + "vddio", + "vddpos", + "vddneg", +}; + +static unsigned long const nt36672a_regulator_enable_loads[] = { + 62000, + 100000, + 100000 +}; + +struct nt36672a_panel_desc { + const struct drm_display_mode *display_mode; + const char *panel_name; + + unsigned int width_mm; + unsigned int height_mm; + + unsigned long mode_flags; + enum mipi_dsi_pixel_format format; + unsigned int lanes; + + unsigned int num_on_cmds_1; + const struct nt36672a_panel_cmd *on_cmds_1; + unsigned int num_on_cmds_2; + const struct nt36672a_panel_cmd *on_cmds_2; + + unsigned int num_off_cmds; + const struct nt36672a_panel_cmd *off_cmds; +}; + +struct nt36672a_panel { + struct drm_panel base; + struct mipi_dsi_device *link; + const struct nt36672a_panel_desc *desc; + + struct regulator_bulk_data supplies[ARRAY_SIZE(nt36672a_regulator_names)]; + + struct gpio_desc *reset_gpio; +}; + +static inline struct nt36672a_panel *to_nt36672a_panel(struct drm_panel *panel) +{ + return container_of(panel, struct nt36672a_panel, base); +} + +static int nt36672a_send_cmds(struct drm_panel *panel, const struct nt36672a_panel_cmd *cmds, + int num) +{ + struct nt36672a_panel *pinfo = to_nt36672a_panel(panel); + unsigned int i; + int err; + + for (i = 0; i < num; i++) { + const struct nt36672a_panel_cmd *cmd = &cmds[i]; + + err = mipi_dsi_dcs_write(pinfo->link, cmd->data[0], cmd->data + 1, 1); + + if (err < 0) + return err; + } + + return 0; +} + +static int nt36672a_panel_power_off(struct drm_panel *panel) +{ + struct nt36672a_panel *pinfo = to_nt36672a_panel(panel); + int ret = 0; + + gpiod_set_value(pinfo->reset_gpio, 1); + + ret = regulator_bulk_disable(ARRAY_SIZE(pinfo->supplies), pinfo->supplies); + if (ret) + dev_err(panel->dev, "regulator_bulk_disable failed %d\n", ret); + + return ret; +} + +static int nt36672a_panel_unprepare(struct drm_panel *panel) +{ + struct nt36672a_panel *pinfo = to_nt36672a_panel(panel); + int ret; + + /* send off cmds */ + ret = nt36672a_send_cmds(panel, pinfo->desc->off_cmds, + pinfo->desc->num_off_cmds); + + if (ret < 0) + dev_err(panel->dev, "failed to send DCS off cmds: %d\n", ret); + + ret = mipi_dsi_dcs_set_display_off(pinfo->link); + if (ret < 0) + dev_err(panel->dev, "set_display_off cmd failed ret = %d\n", ret); + + /* 120ms delay required here as per DCS spec */ + msleep(120); + + ret = mipi_dsi_dcs_enter_sleep_mode(pinfo->link); + if (ret < 0) + dev_err(panel->dev, "enter_sleep cmd failed ret = %d\n", ret); + + /* 0x3C = 60ms delay */ + msleep(60); + + ret = nt36672a_panel_power_off(panel); + if (ret < 0) + dev_err(panel->dev, "power_off failed ret = %d\n", ret); + + return ret; +} + +static int nt36672a_panel_power_on(struct nt36672a_panel *pinfo) +{ + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(pinfo->supplies), pinfo->supplies); + if (ret < 0) + return ret; + + /* + * As per downstream kernel, Reset sequence of Tianma FHD panel requires the panel to + * be out of reset for 10ms, followed by being held in reset for 10ms. But for Android + * AOSP, we needed to bump it upto 200ms otherwise we get white screen sometimes. + * FIXME: Try to reduce this 200ms to a lesser value. + */ + gpiod_set_value(pinfo->reset_gpio, 1); + msleep(200); + gpiod_set_value(pinfo->reset_gpio, 0); + msleep(200); + + return 0; +} + +static int nt36672a_panel_prepare(struct drm_panel *panel) +{ + struct nt36672a_panel *pinfo = to_nt36672a_panel(panel); + int err; + + err = nt36672a_panel_power_on(pinfo); + if (err < 0) + goto poweroff; + + /* send first part of init cmds */ + err = nt36672a_send_cmds(panel, pinfo->desc->on_cmds_1, + pinfo->desc->num_on_cmds_1); + + if (err < 0) { + dev_err(panel->dev, "failed to send DCS Init 1st Code: %d\n", err); + goto poweroff; + } + + err = mipi_dsi_dcs_exit_sleep_mode(pinfo->link); + if (err < 0) { + dev_err(panel->dev, "failed to exit sleep mode: %d\n", err); + goto poweroff; + } + + /* 0x46 = 70 ms delay */ + msleep(70); + + err = mipi_dsi_dcs_set_display_on(pinfo->link); + if (err < 0) { + dev_err(panel->dev, "failed to Set Display ON: %d\n", err); + goto poweroff; + } + + /* Send rest of the init cmds */ + err = nt36672a_send_cmds(panel, pinfo->desc->on_cmds_2, + pinfo->desc->num_on_cmds_2); + + if (err < 0) { + dev_err(panel->dev, "failed to send DCS Init 2nd Code: %d\n", err); + goto poweroff; + } + + msleep(120); + + return 0; + +poweroff: + gpiod_set_value(pinfo->reset_gpio, 0); + return err; +} + +static int nt36672a_panel_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct nt36672a_panel *pinfo = to_nt36672a_panel(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) { + dev_err(panel->dev, "failed to add mode %ux%u@%u\n", m->hdisplay, + m->vdisplay, drm_mode_vrefresh(m)); + return -ENOMEM; + } + + connector->display_info.width_mm = pinfo->desc->width_mm; + connector->display_info.height_mm = pinfo->desc->height_mm; + + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + + return 1; +} + +static const struct drm_panel_funcs panel_funcs = { + .unprepare = nt36672a_panel_unprepare, + .prepare = nt36672a_panel_prepare, + .get_modes = nt36672a_panel_get_modes, +}; + +static const struct nt36672a_panel_cmd tianma_fhd_video_on_cmds_1[] = { + /* skin enhancement mode */ + { .data = {0xFF, 0x22} }, + { .data = {0x00, 0x40} }, + { .data = {0x01, 0xC0} }, + { .data = {0x02, 0x40} }, + { .data = {0x03, 0x40} }, + { .data = {0x04, 0x40} }, + { .data = {0x05, 0x40} }, + { .data = {0x06, 0x40} }, + { .data = {0x07, 0x40} }, + { .data = {0x08, 0x40} }, + { .data = {0x09, 0x40} }, + { .data = {0x0A, 0x40} }, + { .data = {0x0B, 0x40} }, + { .data = {0x0C, 0x40} }, + { .data = {0x0D, 0x40} }, + { .data = {0x0E, 0x40} }, + { .data = {0x0F, 0x40} }, + { .data = {0x10, 0x40} }, + { .data = {0x11, 0x50} }, + { .data = {0x12, 0x60} }, + { .data = {0x13, 0x70} }, + { .data = {0x14, 0x58} }, + { .data = {0x15, 0x68} }, + { .data = {0x16, 0x78} }, + { .data = {0x17, 0x77} }, + { .data = {0x18, 0x39} }, + { .data = {0x19, 0x2D} }, + { .data = {0x1A, 0x2E} }, + { .data = {0x1B, 0x32} }, + { .data = {0x1C, 0x37} }, + { .data = {0x1D, 0x3A} }, + { .data = {0x1E, 0x40} }, + { .data = {0x1F, 0x40} }, + { .data = {0x20, 0x40} }, + { .data = {0x21, 0x40} }, + { .data = {0x22, 0x40} }, + { .data = {0x23, 0x40} }, + { .data = {0x24, 0x40} }, + { .data = {0x25, 0x40} }, + { .data = {0x26, 0x40} }, + { .data = {0x27, 0x40} }, + { .data = {0x28, 0x40} }, + { .data = {0x2D, 0x00} }, + { .data = {0x2F, 0x40} }, + { .data = {0x30, 0x40} }, + { .data = {0x31, 0x40} }, + { .data = {0x32, 0x40} }, + { .data = {0x33, 0x40} }, + { .data = {0x34, 0x40} }, + { .data = {0x35, 0x40} }, + { .data = {0x36, 0x40} }, + { .data = {0x37, 0x40} }, + { .data = {0x38, 0x40} }, + { .data = {0x39, 0x40} }, + { .data = {0x3A, 0x40} }, + { .data = {0x3B, 0x40} }, + { .data = {0x3D, 0x40} }, + { .data = {0x3F, 0x40} }, + { .data = {0x40, 0x40} }, + { .data = {0x41, 0x40} }, + { .data = {0x42, 0x40} }, + { .data = {0x43, 0x40} }, + { .data = {0x44, 0x40} }, + { .data = {0x45, 0x40} }, + { .data = {0x46, 0x40} }, + { .data = {0x47, 0x40} }, + { .data = {0x48, 0x40} }, + { .data = {0x49, 0x40} }, + { .data = {0x4A, 0x40} }, + { .data = {0x4B, 0x40} }, + { .data = {0x4C, 0x40} }, + { .data = {0x4D, 0x40} }, + { .data = {0x4E, 0x40} }, + { .data = {0x4F, 0x40} }, + { .data = {0x50, 0x40} }, + { .data = {0x51, 0x40} }, + { .data = {0x52, 0x40} }, + { .data = {0x53, 0x01} }, + { .data = {0x54, 0x01} }, + { .data = {0x55, 0xFE} }, + { .data = {0x56, 0x77} }, + { .data = {0x58, 0xCD} }, + { .data = {0x59, 0xD0} }, + { .data = {0x5A, 0xD0} }, + { .data = {0x5B, 0x50} }, + { .data = {0x5C, 0x50} }, + { .data = {0x5D, 0x50} }, + { .data = {0x5E, 0x50} }, + { .data = {0x5F, 0x50} }, + { .data = {0x60, 0x50} }, + { .data = {0x61, 0x50} }, + { .data = {0x62, 0x50} }, + { .data = {0x63, 0x50} }, + { .data = {0x64, 0x50} }, + { .data = {0x65, 0x50} }, + { .data = {0x66, 0x50} }, + { .data = {0x67, 0x50} }, + { .data = {0x68, 0x50} }, + { .data = {0x69, 0x50} }, + { .data = {0x6A, 0x50} }, + { .data = {0x6B, 0x50} }, + { .data = {0x6C, 0x50} }, + { .data = {0x6D, 0x50} }, + { .data = {0x6E, 0x50} }, + { .data = {0x6F, 0x50} }, + { .data = {0x70, 0x07} }, + { .data = {0x71, 0x00} }, + { .data = {0x72, 0x00} }, + { .data = {0x73, 0x00} }, + { .data = {0x74, 0x06} }, + { .data = {0x75, 0x0C} }, + { .data = {0x76, 0x03} }, + { .data = {0x77, 0x09} }, + { .data = {0x78, 0x0F} }, + { .data = {0x79, 0x68} }, + { .data = {0x7A, 0x88} }, + { .data = {0x7C, 0x80} }, + { .data = {0x7D, 0x80} }, + { .data = {0x7E, 0x80} }, + { .data = {0x7F, 0x00} }, + { .data = {0x80, 0x00} }, + { .data = {0x81, 0x00} }, + { .data = {0x83, 0x01} }, + { .data = {0x84, 0x00} }, + { .data = {0x85, 0x80} }, + { .data = {0x86, 0x80} }, + { .data = {0x87, 0x80} }, + { .data = {0x88, 0x40} }, + { .data = {0x89, 0x91} }, + { .data = {0x8A, 0x98} }, + { .data = {0x8B, 0x80} }, + { .data = {0x8C, 0x80} }, + { .data = {0x8D, 0x80} }, + { .data = {0x8E, 0x80} }, + { .data = {0x8F, 0x80} }, + { .data = {0x90, 0x80} }, + { .data = {0x91, 0x80} }, + { .data = {0x92, 0x80} }, + { .data = {0x93, 0x80} }, + { .data = {0x94, 0x80} }, + { .data = {0x95, 0x80} }, + { .data = {0x96, 0x80} }, + { .data = {0x97, 0x80} }, + { .data = {0x98, 0x80} }, + { .data = {0x99, 0x80} }, + { .data = {0x9A, 0x80} }, + { .data = {0x9B, 0x80} }, + { .data = {0x9C, 0x80} }, + { .data = {0x9D, 0x80} }, + { .data = {0x9E, 0x80} }, + { .data = {0x9F, 0x80} }, + { .data = {0xA0, 0x8A} }, + { .data = {0xA2, 0x80} }, + { .data = {0xA6, 0x80} }, + { .data = {0xA7, 0x80} }, + { .data = {0xA9, 0x80} }, + { .data = {0xAA, 0x80} }, + { .data = {0xAB, 0x80} }, + { .data = {0xAC, 0x80} }, + { .data = {0xAD, 0x80} }, + { .data = {0xAE, 0x80} }, + { .data = {0xAF, 0x80} }, + { .data = {0xB7, 0x76} }, + { .data = {0xB8, 0x76} }, + { .data = {0xB9, 0x05} }, + { .data = {0xBA, 0x0D} }, + { .data = {0xBB, 0x14} }, + { .data = {0xBC, 0x0F} }, + { .data = {0xBD, 0x18} }, + { .data = {0xBE, 0x1F} }, + { .data = {0xBF, 0x05} }, + { .data = {0xC0, 0x0D} }, + { .data = {0xC1, 0x14} }, + { .data = {0xC2, 0x03} }, + { .data = {0xC3, 0x07} }, + { .data = {0xC4, 0x0A} }, + { .data = {0xC5, 0xA0} }, + { .data = {0xC6, 0x55} }, + { .data = {0xC7, 0xFF} }, + { .data = {0xC8, 0x39} }, + { .data = {0xC9, 0x44} }, + { .data = {0xCA, 0x12} }, + { .data = {0xCD, 0x80} }, + { .data = {0xDB, 0x80} }, + { .data = {0xDC, 0x80} }, + { .data = {0xDD, 0x80} }, + { .data = {0xE0, 0x80} }, + { .data = {0xE1, 0x80} }, + { .data = {0xE2, 0x80} }, + { .data = {0xE3, 0x80} }, + { .data = {0xE4, 0x80} }, + { .data = {0xE5, 0x40} }, + { .data = {0xE6, 0x40} }, + { .data = {0xE7, 0x40} }, + { .data = {0xE8, 0x40} }, + { .data = {0xE9, 0x40} }, + { .data = {0xEA, 0x40} }, + { .data = {0xEB, 0x40} }, + { .data = {0xEC, 0x40} }, + { .data = {0xED, 0x40} }, + { .data = {0xEE, 0x40} }, + { .data = {0xEF, 0x40} }, + { .data = {0xF0, 0x40} }, + { .data = {0xF1, 0x40} }, + { .data = {0xF2, 0x40} }, + { .data = {0xF3, 0x40} }, + { .data = {0xF4, 0x40} }, + { .data = {0xF5, 0x40} }, + { .data = {0xF6, 0x40} }, + { .data = {0xFB, 0x1} }, + { .data = {0xFF, 0x23} }, + { .data = {0xFB, 0x01} }, + /* dimming enable */ + { .data = {0x01, 0x84} }, + { .data = {0x05, 0x2D} }, + { .data = {0x06, 0x00} }, + /* resolution 1080*2246 */ + { .data = {0x11, 0x01} }, + { .data = {0x12, 0x7B} }, + { .data = {0x15, 0x6F} }, + { .data = {0x16, 0x0B} }, + /* UI mode */ + { .data = {0x29, 0x0A} }, + { .data = {0x30, 0xFF} }, + { .data = {0x31, 0xFF} }, + { .data = {0x32, 0xFF} }, + { .data = {0x33, 0xFF} }, + { .data = {0x34, 0xFF} }, + { .data = {0x35, 0xFF} }, + { .data = {0x36, 0xFF} }, + { .data = {0x37, 0xFF} }, + { .data = {0x38, 0xFC} }, + { .data = {0x39, 0xF8} }, + { .data = {0x3A, 0xF4} }, + { .data = {0x3B, 0xF1} }, + { .data = {0x3D, 0xEE} }, + { .data = {0x3F, 0xEB} }, + { .data = {0x40, 0xE8} }, + { .data = {0x41, 0xE5} }, + /* STILL mode */ + { .data = {0x2A, 0x13} }, + { .data = {0x45, 0xFF} }, + { .data = {0x46, 0xFF} }, + { .data = {0x47, 0xFF} }, + { .data = {0x48, 0xFF} }, + { .data = {0x49, 0xFF} }, + { .data = {0x4A, 0xFF} }, + { .data = {0x4B, 0xFF} }, + { .data = {0x4C, 0xFF} }, + { .data = {0x4D, 0xED} }, + { .data = {0x4E, 0xD5} }, + { .data = {0x4F, 0xBF} }, + { .data = {0x50, 0xA6} }, + { .data = {0x51, 0x96} }, + { .data = {0x52, 0x86} }, + { .data = {0x53, 0x76} }, + { .data = {0x54, 0x66} }, + /* MOVING mode */ + { .data = {0x2B, 0x0E} }, + { .data = {0x58, 0xFF} }, + { .data = {0x59, 0xFF} }, + { .data = {0x5A, 0xFF} }, + { .data = {0x5B, 0xFF} }, + { .data = {0x5C, 0xFF} }, + { .data = {0x5D, 0xFF} }, + { .data = {0x5E, 0xFF} }, + { .data = {0x5F, 0xFF} }, + { .data = {0x60, 0xF6} }, + { .data = {0x61, 0xEA} }, + { .data = {0x62, 0xE1} }, + { .data = {0x63, 0xD8} }, + { .data = {0x64, 0xCE} }, + { .data = {0x65, 0xC3} }, + { .data = {0x66, 0xBA} }, + { .data = {0x67, 0xB3} }, + { .data = {0xFF, 0x25} }, + { .data = {0xFB, 0x01} }, + { .data = {0x05, 0x04} }, + { .data = {0xFF, 0x26} }, + { .data = {0xFB, 0x01} }, + { .data = {0x1C, 0xAF} }, + { .data = {0xFF, 0x10} }, + { .data = {0xFB, 0x01} }, + { .data = {0x51, 0xFF} }, + { .data = {0x53, 0x24} }, + { .data = {0x55, 0x00} }, +}; + +static const struct nt36672a_panel_cmd tianma_fhd_video_on_cmds_2[] = { + { .data = {0xFF, 0x24} }, + { .data = {0xFB, 0x01} }, + { .data = {0xC3, 0x01} }, + { .data = {0xC4, 0x54} }, + { .data = {0xFF, 0x10} }, +}; + +static const struct nt36672a_panel_cmd tianma_fhd_video_off_cmds[] = { + { .data = {0xFF, 0x24} }, + { .data = {0xFB, 0x01} }, + { .data = {0xC3, 0x01} }, + { .data = {0xFF, 0x10} }, +}; + +static const struct drm_display_mode tianma_fhd_video_panel_default_mode = { + .clock = 161331, + + .hdisplay = 1080, + .hsync_start = 1080 + 40, + .hsync_end = 1080 + 40 + 20, + .htotal = 1080 + 40 + 20 + 44, + + .vdisplay = 2246, + .vsync_start = 2246 + 15, + .vsync_end = 2246 + 15 + 2, + .vtotal = 2246 + 15 + 2 + 8, + + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, +}; + +static const struct nt36672a_panel_desc tianma_fhd_video_panel_desc = { + .display_mode = &tianma_fhd_video_panel_default_mode, + + .width_mm = 68, + .height_mm = 136, + + .mode_flags = MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_VIDEO + | MIPI_DSI_MODE_VIDEO_HSE + | MIPI_DSI_CLOCK_NON_CONTINUOUS + | MIPI_DSI_MODE_VIDEO_BURST, + .format = MIPI_DSI_FMT_RGB888, + .lanes = 4, + .on_cmds_1 = tianma_fhd_video_on_cmds_1, + .num_on_cmds_1 = ARRAY_SIZE(tianma_fhd_video_on_cmds_1), + .on_cmds_2 = tianma_fhd_video_on_cmds_2, + .num_on_cmds_2 = ARRAY_SIZE(tianma_fhd_video_on_cmds_2), + .off_cmds = tianma_fhd_video_off_cmds, + .num_off_cmds = ARRAY_SIZE(tianma_fhd_video_off_cmds), +}; + +static int nt36672a_panel_add(struct nt36672a_panel *pinfo) +{ + struct device *dev = &pinfo->link->dev; + int i, ret; + + for (i = 0; i < ARRAY_SIZE(pinfo->supplies); i++) { + pinfo->supplies[i].supply = nt36672a_regulator_names[i]; + pinfo->supplies[i].init_load_uA = nt36672a_regulator_enable_loads[i]; + } + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(pinfo->supplies), + pinfo->supplies); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to get regulators\n"); + + pinfo->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(pinfo->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(pinfo->reset_gpio), + "failed to get reset gpio from DT\n"); + + ret = drm_panel_of_backlight(&pinfo->base); + if (ret) + return dev_err_probe(dev, ret, "Failed to get backlight\n"); + + drm_panel_add(&pinfo->base); + + return 0; +} + +static int nt36672a_panel_probe(struct mipi_dsi_device *dsi) +{ + struct nt36672a_panel *pinfo; + const struct nt36672a_panel_desc *desc; + int err; + + pinfo = devm_drm_panel_alloc(&dsi->dev, __typeof(*pinfo), base, + &panel_funcs, DRM_MODE_CONNECTOR_DSI); + + if (IS_ERR(pinfo)) + return PTR_ERR(pinfo); + + 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 = nt36672a_panel_add(pinfo); + if (err < 0) + return err; + + err = mipi_dsi_attach(dsi); + if (err < 0) { + drm_panel_remove(&pinfo->base); + return err; + } + + return 0; +} + +static void nt36672a_panel_remove(struct mipi_dsi_device *dsi) +{ + struct nt36672a_panel *pinfo = mipi_dsi_get_drvdata(dsi); + int err; + + err = mipi_dsi_detach(dsi); + if (err < 0) + dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", err); + + drm_panel_remove(&pinfo->base); +} + +static const struct of_device_id tianma_fhd_video_of_match[] = { + { .compatible = "tianma,fhd-video", .data = &tianma_fhd_video_panel_desc }, + { }, +}; +MODULE_DEVICE_TABLE(of, tianma_fhd_video_of_match); + +static struct mipi_dsi_driver nt36672a_panel_driver = { + .driver = { + .name = "panel-tianma-nt36672a", + .of_match_table = tianma_fhd_video_of_match, + }, + .probe = nt36672a_panel_probe, + .remove = nt36672a_panel_remove, +}; +module_mipi_dsi_driver(nt36672a_panel_driver); + +MODULE_AUTHOR("Sumit Semwal <sumit.semwal@linaro.org>"); +MODULE_DESCRIPTION("NOVATEK NT36672A based MIPI-DSI LCD panel driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-novatek-nt36672e.c b/drivers/gpu/drm/panel/panel-novatek-nt36672e.c new file mode 100644 index 000000000000..c5e00eb55722 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-novatek-nt36672e.c @@ -0,0 +1,608 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. + +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regulator/consumer.h> + +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +#include <video/mipi_display.h> + +static const char * const regulator_names[] = { + "vddi", + "avdd", + "avee", +}; + +static const unsigned long regulator_enable_loads[] = { + 62000, + 100000, + 100000, +}; + +struct panel_desc { + const struct drm_display_mode *display_mode; + u32 width_mm; + u32 height_mm; + unsigned long mode_flags; + enum mipi_dsi_pixel_format format; + unsigned int lanes; + const char *panel_name; + void (*init_sequence)(struct mipi_dsi_multi_context *ctx); +}; + +struct nt36672e_panel { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[3]; + const struct panel_desc *desc; +}; + +#define NT36672E_DCS_SWITCH_PAGE 0xff + +#define nt36672e_switch_page(ctx, page) \ + mipi_dsi_dcs_write_seq_multi(ctx, NT36672E_DCS_SWITCH_PAGE, (page)) + +static void nt36672e_enable_reload_cmds(struct mipi_dsi_multi_context *ctx) +{ + mipi_dsi_dcs_write_seq_multi(ctx, 0xfb, 0x01); +} + +static inline struct nt36672e_panel *to_nt36672e_panel(struct drm_panel *panel) +{ + return container_of(panel, struct nt36672e_panel, panel); +} + +static void nt36672e_1080x2408_60hz_init(struct mipi_dsi_multi_context *ctx) +{ + nt36672e_switch_page(ctx, 0x10); + nt36672e_enable_reload_cmds(ctx); + mipi_dsi_dcs_write_seq_multi(ctx, 0xb0, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0xc0, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0xc1, 0x89, 0x28, 0x00, 0x08, 0x00, 0xaa, 0x02, + 0x0e, 0x00, 0x2b, 0x00, 0x07, 0x0d, 0xb7, 0x0c, 0xb7); + mipi_dsi_dcs_write_seq_multi(ctx, 0xc2, 0x1b, 0xa0); + + nt36672e_switch_page(ctx, 0x20); + nt36672e_enable_reload_cmds(ctx); + mipi_dsi_dcs_write_seq_multi(ctx, 0x01, 0x66); + mipi_dsi_dcs_write_seq_multi(ctx, 0x06, 0x40); + mipi_dsi_dcs_write_seq_multi(ctx, 0x07, 0x38); + mipi_dsi_dcs_write_seq_multi(ctx, 0x2f, 0x83); + mipi_dsi_dcs_write_seq_multi(ctx, 0x69, 0x91); + mipi_dsi_dcs_write_seq_multi(ctx, 0x95, 0xd1); + mipi_dsi_dcs_write_seq_multi(ctx, 0x96, 0xd1); + mipi_dsi_dcs_write_seq_multi(ctx, 0xf2, 0x64); + mipi_dsi_dcs_write_seq_multi(ctx, 0xf3, 0x54); + mipi_dsi_dcs_write_seq_multi(ctx, 0xf4, 0x64); + mipi_dsi_dcs_write_seq_multi(ctx, 0xf5, 0x54); + mipi_dsi_dcs_write_seq_multi(ctx, 0xf6, 0x64); + mipi_dsi_dcs_write_seq_multi(ctx, 0xf7, 0x54); + mipi_dsi_dcs_write_seq_multi(ctx, 0xf8, 0x64); + mipi_dsi_dcs_write_seq_multi(ctx, 0xf9, 0x54); + + nt36672e_switch_page(ctx, 0x24); + nt36672e_enable_reload_cmds(ctx); + mipi_dsi_dcs_write_seq_multi(ctx, 0x01, 0x0f); + mipi_dsi_dcs_write_seq_multi(ctx, 0x03, 0x0c); + mipi_dsi_dcs_write_seq_multi(ctx, 0x05, 0x1d); + mipi_dsi_dcs_write_seq_multi(ctx, 0x08, 0x2f); + mipi_dsi_dcs_write_seq_multi(ctx, 0x09, 0x2e); + mipi_dsi_dcs_write_seq_multi(ctx, 0x0a, 0x2d); + mipi_dsi_dcs_write_seq_multi(ctx, 0x0b, 0x2c); + mipi_dsi_dcs_write_seq_multi(ctx, 0x11, 0x17); + mipi_dsi_dcs_write_seq_multi(ctx, 0x12, 0x13); + mipi_dsi_dcs_write_seq_multi(ctx, 0x13, 0x15); + mipi_dsi_dcs_write_seq_multi(ctx, 0x15, 0x14); + mipi_dsi_dcs_write_seq_multi(ctx, 0x16, 0x16); + mipi_dsi_dcs_write_seq_multi(ctx, 0x17, 0x18); + mipi_dsi_dcs_write_seq_multi(ctx, 0x1b, 0x01); + mipi_dsi_dcs_write_seq_multi(ctx, 0x1d, 0x1d); + mipi_dsi_dcs_write_seq_multi(ctx, 0x20, 0x2f); + mipi_dsi_dcs_write_seq_multi(ctx, 0x21, 0x2e); + mipi_dsi_dcs_write_seq_multi(ctx, 0x22, 0x2d); + mipi_dsi_dcs_write_seq_multi(ctx, 0x23, 0x2c); + mipi_dsi_dcs_write_seq_multi(ctx, 0x29, 0x17); + mipi_dsi_dcs_write_seq_multi(ctx, 0x2a, 0x13); + mipi_dsi_dcs_write_seq_multi(ctx, 0x2b, 0x15); + mipi_dsi_dcs_write_seq_multi(ctx, 0x2f, 0x14); + mipi_dsi_dcs_write_seq_multi(ctx, 0x30, 0x16); + mipi_dsi_dcs_write_seq_multi(ctx, 0x31, 0x18); + mipi_dsi_dcs_write_seq_multi(ctx, 0x32, 0x04); + mipi_dsi_dcs_write_seq_multi(ctx, 0x34, 0x10); + mipi_dsi_dcs_write_seq_multi(ctx, 0x35, 0x1f); + mipi_dsi_dcs_write_seq_multi(ctx, 0x36, 0x1f); + mipi_dsi_dcs_write_seq_multi(ctx, 0x4d, 0x14); + mipi_dsi_dcs_write_seq_multi(ctx, 0x4e, 0x36); + mipi_dsi_dcs_write_seq_multi(ctx, 0x4f, 0x36); + mipi_dsi_dcs_write_seq_multi(ctx, 0x53, 0x36); + mipi_dsi_dcs_write_seq_multi(ctx, 0x71, 0x30); + mipi_dsi_dcs_write_seq_multi(ctx, 0x79, 0x11); + mipi_dsi_dcs_write_seq_multi(ctx, 0x7a, 0x82); + mipi_dsi_dcs_write_seq_multi(ctx, 0x7b, 0x8f); + mipi_dsi_dcs_write_seq_multi(ctx, 0x7d, 0x04); + mipi_dsi_dcs_write_seq_multi(ctx, 0x80, 0x04); + mipi_dsi_dcs_write_seq_multi(ctx, 0x81, 0x04); + mipi_dsi_dcs_write_seq_multi(ctx, 0x82, 0x13); + mipi_dsi_dcs_write_seq_multi(ctx, 0x84, 0x31); + mipi_dsi_dcs_write_seq_multi(ctx, 0x85, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x86, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x87, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x90, 0x13); + mipi_dsi_dcs_write_seq_multi(ctx, 0x92, 0x31); + mipi_dsi_dcs_write_seq_multi(ctx, 0x93, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x94, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x95, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x9c, 0xf4); + mipi_dsi_dcs_write_seq_multi(ctx, 0x9d, 0x01); + mipi_dsi_dcs_write_seq_multi(ctx, 0xa0, 0x0f); + mipi_dsi_dcs_write_seq_multi(ctx, 0xa2, 0x0f); + mipi_dsi_dcs_write_seq_multi(ctx, 0xa3, 0x02); + mipi_dsi_dcs_write_seq_multi(ctx, 0xa4, 0x04); + mipi_dsi_dcs_write_seq_multi(ctx, 0xa5, 0x04); + mipi_dsi_dcs_write_seq_multi(ctx, 0xc6, 0xc0); + mipi_dsi_dcs_write_seq_multi(ctx, 0xc9, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0xd9, 0x80); + mipi_dsi_dcs_write_seq_multi(ctx, 0xe9, 0x02); + + nt36672e_switch_page(ctx, 0x25); + nt36672e_enable_reload_cmds(ctx); + mipi_dsi_dcs_write_seq_multi(ctx, 0x18, 0x22); + mipi_dsi_dcs_write_seq_multi(ctx, 0x19, 0xe4); + mipi_dsi_dcs_write_seq_multi(ctx, 0x21, 0x40); + mipi_dsi_dcs_write_seq_multi(ctx, 0x66, 0xd8); + mipi_dsi_dcs_write_seq_multi(ctx, 0x68, 0x50); + mipi_dsi_dcs_write_seq_multi(ctx, 0x69, 0x10); + mipi_dsi_dcs_write_seq_multi(ctx, 0x6b, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x6d, 0x0d); + mipi_dsi_dcs_write_seq_multi(ctx, 0x6e, 0x48); + mipi_dsi_dcs_write_seq_multi(ctx, 0x72, 0x41); + mipi_dsi_dcs_write_seq_multi(ctx, 0x73, 0x4a); + mipi_dsi_dcs_write_seq_multi(ctx, 0x74, 0xd0); + mipi_dsi_dcs_write_seq_multi(ctx, 0x77, 0x62); + mipi_dsi_dcs_write_seq_multi(ctx, 0x79, 0x7e); + mipi_dsi_dcs_write_seq_multi(ctx, 0x7d, 0x03); + mipi_dsi_dcs_write_seq_multi(ctx, 0x7e, 0x15); + mipi_dsi_dcs_write_seq_multi(ctx, 0x7f, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x84, 0x4d); + mipi_dsi_dcs_write_seq_multi(ctx, 0xcf, 0x80); + mipi_dsi_dcs_write_seq_multi(ctx, 0xd6, 0x80); + mipi_dsi_dcs_write_seq_multi(ctx, 0xd7, 0x80); + mipi_dsi_dcs_write_seq_multi(ctx, 0xef, 0x20); + mipi_dsi_dcs_write_seq_multi(ctx, 0xf0, 0x84); + + nt36672e_switch_page(ctx, 0x26); + nt36672e_enable_reload_cmds(ctx); + mipi_dsi_dcs_write_seq_multi(ctx, 0x81, 0x0f); + mipi_dsi_dcs_write_seq_multi(ctx, 0x83, 0x01); + mipi_dsi_dcs_write_seq_multi(ctx, 0x84, 0x03); + mipi_dsi_dcs_write_seq_multi(ctx, 0x85, 0x01); + mipi_dsi_dcs_write_seq_multi(ctx, 0x86, 0x03); + mipi_dsi_dcs_write_seq_multi(ctx, 0x87, 0x01); + mipi_dsi_dcs_write_seq_multi(ctx, 0x88, 0x05); + mipi_dsi_dcs_write_seq_multi(ctx, 0x8a, 0x1a); + mipi_dsi_dcs_write_seq_multi(ctx, 0x8b, 0x11); + mipi_dsi_dcs_write_seq_multi(ctx, 0x8c, 0x24); + mipi_dsi_dcs_write_seq_multi(ctx, 0x8e, 0x42); + mipi_dsi_dcs_write_seq_multi(ctx, 0x8f, 0x11); + mipi_dsi_dcs_write_seq_multi(ctx, 0x90, 0x11); + mipi_dsi_dcs_write_seq_multi(ctx, 0x91, 0x11); + mipi_dsi_dcs_write_seq_multi(ctx, 0x9a, 0x80); + mipi_dsi_dcs_write_seq_multi(ctx, 0x9b, 0x04); + mipi_dsi_dcs_write_seq_multi(ctx, 0x9c, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x9d, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x9e, 0x00); + + nt36672e_switch_page(ctx, 0x27); + nt36672e_enable_reload_cmds(ctx); + mipi_dsi_dcs_write_seq_multi(ctx, 0x01, 0x68); + mipi_dsi_dcs_write_seq_multi(ctx, 0x20, 0x81); + mipi_dsi_dcs_write_seq_multi(ctx, 0x21, 0x6a); + mipi_dsi_dcs_write_seq_multi(ctx, 0x25, 0x81); + mipi_dsi_dcs_write_seq_multi(ctx, 0x26, 0x94); + mipi_dsi_dcs_write_seq_multi(ctx, 0x6e, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x6f, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x70, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x71, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x72, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x75, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x76, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x77, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0x7d, 0x09); + mipi_dsi_dcs_write_seq_multi(ctx, 0x7e, 0x67); + mipi_dsi_dcs_write_seq_multi(ctx, 0x80, 0x23); + mipi_dsi_dcs_write_seq_multi(ctx, 0x82, 0x09); + mipi_dsi_dcs_write_seq_multi(ctx, 0x83, 0x67); + mipi_dsi_dcs_write_seq_multi(ctx, 0x88, 0x01); + mipi_dsi_dcs_write_seq_multi(ctx, 0x89, 0x10); + mipi_dsi_dcs_write_seq_multi(ctx, 0xa5, 0x10); + mipi_dsi_dcs_write_seq_multi(ctx, 0xa6, 0x23); + mipi_dsi_dcs_write_seq_multi(ctx, 0xa7, 0x01); + mipi_dsi_dcs_write_seq_multi(ctx, 0xb6, 0x40); + mipi_dsi_dcs_write_seq_multi(ctx, 0xe5, 0x02); + mipi_dsi_dcs_write_seq_multi(ctx, 0xe6, 0xd3); + mipi_dsi_dcs_write_seq_multi(ctx, 0xeb, 0x03); + mipi_dsi_dcs_write_seq_multi(ctx, 0xec, 0x28); + + nt36672e_switch_page(ctx, 0x2a); + nt36672e_enable_reload_cmds(ctx); + mipi_dsi_dcs_write_seq_multi(ctx, 0x00, 0x91); + mipi_dsi_dcs_write_seq_multi(ctx, 0x03, 0x20); + mipi_dsi_dcs_write_seq_multi(ctx, 0x07, 0x50); + mipi_dsi_dcs_write_seq_multi(ctx, 0x0a, 0x70); + mipi_dsi_dcs_write_seq_multi(ctx, 0x0c, 0x04); + mipi_dsi_dcs_write_seq_multi(ctx, 0x0d, 0x40); + mipi_dsi_dcs_write_seq_multi(ctx, 0x0f, 0x01); + mipi_dsi_dcs_write_seq_multi(ctx, 0x11, 0xe0); + mipi_dsi_dcs_write_seq_multi(ctx, 0x15, 0x0f); + mipi_dsi_dcs_write_seq_multi(ctx, 0x16, 0xa4); + mipi_dsi_dcs_write_seq_multi(ctx, 0x19, 0x0f); + mipi_dsi_dcs_write_seq_multi(ctx, 0x1a, 0x78); + mipi_dsi_dcs_write_seq_multi(ctx, 0x1b, 0x23); + mipi_dsi_dcs_write_seq_multi(ctx, 0x1d, 0x36); + mipi_dsi_dcs_write_seq_multi(ctx, 0x1e, 0x3e); + mipi_dsi_dcs_write_seq_multi(ctx, 0x1f, 0x3e); + mipi_dsi_dcs_write_seq_multi(ctx, 0x20, 0x3e); + mipi_dsi_dcs_write_seq_multi(ctx, 0x28, 0xfd); + mipi_dsi_dcs_write_seq_multi(ctx, 0x29, 0x12); + mipi_dsi_dcs_write_seq_multi(ctx, 0x2a, 0xe1); + mipi_dsi_dcs_write_seq_multi(ctx, 0x2d, 0x0a); + mipi_dsi_dcs_write_seq_multi(ctx, 0x30, 0x49); + mipi_dsi_dcs_write_seq_multi(ctx, 0x33, 0x96); + mipi_dsi_dcs_write_seq_multi(ctx, 0x34, 0xff); + mipi_dsi_dcs_write_seq_multi(ctx, 0x35, 0x40); + mipi_dsi_dcs_write_seq_multi(ctx, 0x36, 0xde); + mipi_dsi_dcs_write_seq_multi(ctx, 0x37, 0xf9); + mipi_dsi_dcs_write_seq_multi(ctx, 0x38, 0x45); + mipi_dsi_dcs_write_seq_multi(ctx, 0x39, 0xd9); + mipi_dsi_dcs_write_seq_multi(ctx, 0x3a, 0x49); + mipi_dsi_dcs_write_seq_multi(ctx, 0x4a, 0xf0); + mipi_dsi_dcs_write_seq_multi(ctx, 0x7a, 0x09); + mipi_dsi_dcs_write_seq_multi(ctx, 0x7b, 0x40); + mipi_dsi_dcs_write_seq_multi(ctx, 0x7f, 0xf0); + mipi_dsi_dcs_write_seq_multi(ctx, 0x83, 0x0f); + mipi_dsi_dcs_write_seq_multi(ctx, 0x84, 0xa4); + mipi_dsi_dcs_write_seq_multi(ctx, 0x87, 0x0f); + mipi_dsi_dcs_write_seq_multi(ctx, 0x88, 0x78); + mipi_dsi_dcs_write_seq_multi(ctx, 0x89, 0x23); + mipi_dsi_dcs_write_seq_multi(ctx, 0x8b, 0x36); + mipi_dsi_dcs_write_seq_multi(ctx, 0x8c, 0x7d); + mipi_dsi_dcs_write_seq_multi(ctx, 0x8d, 0x7d); + mipi_dsi_dcs_write_seq_multi(ctx, 0x8e, 0x7d); + + nt36672e_switch_page(ctx, 0x20); + nt36672e_enable_reload_cmds(ctx); + mipi_dsi_dcs_write_seq_multi(ctx, 0xb0, 0x00, 0x00, 0x00, 0x17, 0x00, 0x49, 0x00, + 0x6a, 0x00, 0x89, 0x00, 0x9f, 0x00, 0xb6, 0x00, 0xc8); + mipi_dsi_dcs_write_seq_multi(ctx, 0xb1, 0x00, 0xd9, 0x01, 0x10, 0x01, 0x3a, 0x01, + 0x7a, 0x01, 0xa9, 0x01, 0xf2, 0x02, 0x2d, 0x02, 0x2e); + mipi_dsi_dcs_write_seq_multi(ctx, 0xb2, 0x02, 0x64, 0x02, 0xa3, 0x02, 0xca, 0x03, + 0x00, 0x03, 0x1e, 0x03, 0x4a, 0x03, 0x59, 0x03, 0x6a); + mipi_dsi_dcs_write_seq_multi(ctx, 0xb3, 0x03, 0x7d, 0x03, 0x93, 0x03, 0xab, 0x03, + 0xc8, 0x03, 0xec, 0x03, 0xfe, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0xb4, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x51, 0x00, + 0x71, 0x00, 0x90, 0x00, 0xa7, 0x00, 0xbf, 0x00, 0xd1); + mipi_dsi_dcs_write_seq_multi(ctx, 0xb5, 0x00, 0xe2, 0x01, 0x1a, 0x01, 0x43, 0x01, + 0x83, 0x01, 0xb2, 0x01, 0xfa, 0x02, 0x34, 0x02, 0x36); + mipi_dsi_dcs_write_seq_multi(ctx, 0xb6, 0x02, 0x6b, 0x02, 0xa8, 0x02, 0xd0, 0x03, + 0x03, 0x03, 0x21, 0x03, 0x4d, 0x03, 0x5b, 0x03, 0x6b); + mipi_dsi_dcs_write_seq_multi(ctx, 0xb7, 0x03, 0x7e, 0x03, 0x94, 0x03, 0xac, 0x03, + 0xc8, 0x03, 0xec, 0x03, 0xfe, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0xb8, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x51, 0x00, + 0x72, 0x00, 0x92, 0x00, 0xa8, 0x00, 0xbf, 0x00, 0xd1); + mipi_dsi_dcs_write_seq_multi(ctx, 0xb9, 0x00, 0xe2, 0x01, 0x18, 0x01, 0x42, 0x01, + 0x81, 0x01, 0xaf, 0x01, 0xf5, 0x02, 0x2f, 0x02, 0x31); + mipi_dsi_dcs_write_seq_multi(ctx, 0xba, 0x02, 0x68, 0x02, 0xa6, 0x02, 0xcd, 0x03, + 0x01, 0x03, 0x1f, 0x03, 0x4a, 0x03, 0x59, 0x03, 0x6a); + mipi_dsi_dcs_write_seq_multi(ctx, 0xbb, 0x03, 0x7d, 0x03, 0x93, 0x03, 0xab, 0x03, + 0xc8, 0x03, 0xec, 0x03, 0xfe, 0x00, 0x00); + + nt36672e_switch_page(ctx, 0x21); + nt36672e_enable_reload_cmds(ctx); + mipi_dsi_dcs_write_seq_multi(ctx, 0xb0, 0x00, 0x00, 0x00, 0x17, 0x00, 0x49, 0x00, + 0x6a, 0x00, 0x89, 0x00, 0x9f, 0x00, 0xb6, 0x00, 0xc8); + mipi_dsi_dcs_write_seq_multi(ctx, 0xb1, 0x00, 0xd9, 0x01, 0x10, 0x01, 0x3a, 0x01, + 0x7a, 0x01, 0xa9, 0x01, 0xf2, 0x02, 0x2d, 0x02, 0x2e); + mipi_dsi_dcs_write_seq_multi(ctx, 0xb2, 0x02, 0x64, 0x02, 0xa3, 0x02, 0xca, 0x03, + 0x00, 0x03, 0x1e, 0x03, 0x4a, 0x03, 0x59, 0x03, 0x6a); + mipi_dsi_dcs_write_seq_multi(ctx, 0xb3, 0x03, 0x7d, 0x03, 0x93, 0x03, 0xab, 0x03, + 0xc8, 0x03, 0xec, 0x03, 0xfe, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0xb4, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x51, 0x00, + 0x71, 0x00, 0x90, 0x00, 0xa7, 0x00, 0xbf, 0x00, 0xd1); + mipi_dsi_dcs_write_seq_multi(ctx, 0xb5, 0x00, 0xe2, 0x01, 0x1a, 0x01, 0x43, 0x01, + 0x83, 0x01, 0xb2, 0x01, 0xfa, 0x02, 0x34, 0x02, 0x36); + mipi_dsi_dcs_write_seq_multi(ctx, 0xb6, 0x02, 0x6b, 0x02, 0xa8, 0x02, 0xd0, 0x03, + 0x03, 0x03, 0x21, 0x03, 0x4d, 0x03, 0x5b, 0x03, 0x6b); + mipi_dsi_dcs_write_seq_multi(ctx, 0xb7, 0x03, 0x7e, 0x03, 0x94, 0x03, 0xac, 0x03, + 0xc8, 0x03, 0xec, 0x03, 0xfe, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0xb8, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x51, 0x00, + 0x72, 0x00, 0x92, 0x00, 0xa8, 0x00, 0xbf, 0x00, 0xd1); + mipi_dsi_dcs_write_seq_multi(ctx, 0xb9, 0x00, 0xe2, 0x01, 0x18, 0x01, 0x42, 0x01, + 0x81, 0x01, 0xaf, 0x01, 0xf5, 0x02, 0x2f, 0x02, 0x31); + mipi_dsi_dcs_write_seq_multi(ctx, 0xba, 0x02, 0x68, 0x02, 0xa6, 0x02, 0xcd, 0x03, + 0x01, 0x03, 0x1f, 0x03, 0x4a, 0x03, 0x59, 0x03, 0x6a); + mipi_dsi_dcs_write_seq_multi(ctx, 0xbb, 0x03, 0x7d, 0x03, 0x93, 0x03, 0xab, 0x03, + 0xc8, 0x03, 0xec, 0x03, 0xfe, 0x00, 0x00); + + nt36672e_switch_page(ctx, 0x2c); + nt36672e_enable_reload_cmds(ctx); + mipi_dsi_dcs_write_seq_multi(ctx, 0x61, 0x1f); + mipi_dsi_dcs_write_seq_multi(ctx, 0x62, 0x1f); + mipi_dsi_dcs_write_seq_multi(ctx, 0x7e, 0x03); + mipi_dsi_dcs_write_seq_multi(ctx, 0x6a, 0x14); + mipi_dsi_dcs_write_seq_multi(ctx, 0x6b, 0x36); + mipi_dsi_dcs_write_seq_multi(ctx, 0x6c, 0x36); + mipi_dsi_dcs_write_seq_multi(ctx, 0x6d, 0x36); + mipi_dsi_dcs_write_seq_multi(ctx, 0x53, 0x04); + mipi_dsi_dcs_write_seq_multi(ctx, 0x54, 0x04); + mipi_dsi_dcs_write_seq_multi(ctx, 0x55, 0x04); + mipi_dsi_dcs_write_seq_multi(ctx, 0x56, 0x0f); + mipi_dsi_dcs_write_seq_multi(ctx, 0x58, 0x0f); + mipi_dsi_dcs_write_seq_multi(ctx, 0x59, 0x0f); + + nt36672e_switch_page(ctx, 0xf0); + nt36672e_enable_reload_cmds(ctx); + mipi_dsi_dcs_write_seq_multi(ctx, 0x5a, 0x00); + + nt36672e_switch_page(ctx, 0x10); + nt36672e_enable_reload_cmds(ctx); + mipi_dsi_dcs_write_seq_multi(ctx, 0x51, 0xff); + mipi_dsi_dcs_write_seq_multi(ctx, 0x53, 0x24); + mipi_dsi_dcs_write_seq_multi(ctx, 0x55, 0x01); +} + +static int nt36672e_power_on(struct nt36672e_panel *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + if (ret < 0) { + dev_err(&dsi->dev, "regulator bulk enable failed: %d\n", ret); + return ret; + } + + /* + * Reset sequence of nt36672e panel requires the panel to be out of reset + * for 10ms, followed by being held in reset for 10ms and then out again. + */ + gpiod_set_value(ctx->reset_gpio, 1); + usleep_range(10000, 20000); + gpiod_set_value(ctx->reset_gpio, 0); + usleep_range(10000, 20000); + gpiod_set_value(ctx->reset_gpio, 1); + usleep_range(10000, 20000); + + return 0; +} + +static int nt36672e_power_off(struct nt36672e_panel *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + int ret = 0; + + gpiod_set_value(ctx->reset_gpio, 0); + + ret = regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + if (ret) + dev_err(&dsi->dev, "regulator bulk disable failed: %d\n", ret); + + return ret; +} + +static int nt36672e_on(struct nt36672e_panel *nt36672e) +{ + struct mipi_dsi_multi_context ctx = { .dsi = nt36672e->dsi }; + const struct panel_desc *desc = nt36672e->desc; + + nt36672e->dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + if (desc->init_sequence) + desc->init_sequence(&ctx); + + mipi_dsi_dcs_exit_sleep_mode_multi(&ctx); + mipi_dsi_msleep(&ctx, 120); + + mipi_dsi_dcs_set_display_on_multi(&ctx); + + mipi_dsi_msleep(&ctx, 100); + + return ctx.accum_err; +} + +static int nt36672e_off(struct nt36672e_panel *panel) +{ + struct mipi_dsi_multi_context ctx = { .dsi = panel->dsi }; + + panel->dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + mipi_dsi_dcs_set_display_off_multi(&ctx); + mipi_dsi_msleep(&ctx, 20); + + mipi_dsi_dcs_enter_sleep_mode_multi(&ctx); + mipi_dsi_msleep(&ctx, 60); + + return ctx.accum_err; +} + +static int nt36672e_panel_prepare(struct drm_panel *panel) +{ + struct nt36672e_panel *ctx = to_nt36672e_panel(panel); + struct mipi_dsi_device *dsi = ctx->dsi; + int ret; + + ret = nt36672e_power_on(ctx); + if (ret < 0) + return ret; + + ret = nt36672e_on(ctx); + if (ret < 0) { + if (nt36672e_power_off(ctx)) + dev_err(&dsi->dev, "power off failed\n"); + return ret; + } + + return 0; +} + +static int nt36672e_panel_unprepare(struct drm_panel *panel) +{ + struct nt36672e_panel *ctx = to_nt36672e_panel(panel); + struct mipi_dsi_device *dsi = ctx->dsi; + int ret; + + nt36672e_off(ctx); + + ret = nt36672e_power_off(ctx); + if (ret < 0) + dev_err(&dsi->dev, "power off failed: %d\n", ret); + + return 0; +} + +static const struct drm_display_mode nt36672e_1080x2408_60hz = { + .name = "1080x2408", + .clock = 181690, + .hdisplay = 1080, + .hsync_start = 1080 + 76, + .hsync_end = 1080 + 76 + 12, + .htotal = 1080 + 76 + 12 + 56, + .vdisplay = 2408, + .vsync_start = 2408 + 46, + .vsync_end = 2408 + 46 + 10, + .vtotal = 2408 + 46 + 10 + 10, + .flags = 0, +}; + +static const struct panel_desc nt36672e_panel_desc = { + .display_mode = &nt36672e_1080x2408_60hz, + .width_mm = 74, + .height_mm = 131, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS, + .format = MIPI_DSI_FMT_RGB888, + .lanes = 4, + .panel_name = "nt36672e fhd plus panel", + .init_sequence = nt36672e_1080x2408_60hz_init, +}; + +static int nt36672e_panel_get_modes(struct drm_panel *panel, struct drm_connector *connector) +{ + struct nt36672e_panel *ctx = to_nt36672e_panel(panel); + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, ctx->desc->display_mode); + if (!mode) + return -ENOMEM; + + drm_mode_set_name(mode); + + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + connector->display_info.width_mm = ctx->desc->width_mm; + connector->display_info.height_mm = ctx->desc->height_mm; + drm_mode_probed_add(connector, mode); + + return 1; +} + +static const struct drm_panel_funcs nt36672e_drm_funcs = { + .prepare = nt36672e_panel_prepare, + .unprepare = nt36672e_panel_unprepare, + .get_modes = nt36672e_panel_get_modes, +}; + +static int nt36672e_panel_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct nt36672e_panel *ctx; + int i, ret = 0; + + ctx = devm_drm_panel_alloc(dev, struct nt36672e_panel, panel, + &nt36672e_drm_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ctx->desc = of_device_get_match_data(dev); + if (!ctx->desc) { + dev_err(dev, "missing device configuration\n"); + return -ENODEV; + } + + for (i = 0; i < ARRAY_SIZE(ctx->supplies); i++) { + ctx->supplies[i].supply = regulator_names[i]; + ctx->supplies[i].init_load_uA = regulator_enable_loads[i]; + } + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), + ctx->supplies); + if (ret < 0) + return ret; + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), "Failed to get reset-gpios\n"); + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + dsi->lanes = ctx->desc->lanes; + dsi->format = ctx->desc->format; + dsi->mode_flags = ctx->desc->mode_flags; + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return dev_err_probe(dev, ret, "Failed to get backlight\n"); + + ctx->panel.prepare_prev_first = true; + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "Failed to attach to DSI host: %d\n", ret); + goto err_dsi_attach; + } + + return 0; + +err_dsi_attach: + drm_panel_remove(&ctx->panel); + return ret; +} + +static void nt36672e_panel_remove(struct mipi_dsi_device *dsi) +{ + struct nt36672e_panel *ctx = mipi_dsi_get_drvdata(dsi); + + mipi_dsi_detach(ctx->dsi); + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id nt36672e_of_match[] = { + { + .compatible = "novatek,nt36672e", + .data = &nt36672e_panel_desc, + }, + { } +}; +MODULE_DEVICE_TABLE(of, nt36672e_of_match); + +static struct mipi_dsi_driver nt36672e_panel_driver = { + .driver = { + .name = "panel-novatek-nt36672e", + .of_match_table = nt36672e_of_match, + }, + .probe = nt36672e_panel_probe, + .remove = nt36672e_panel_remove, +}; +module_mipi_dsi_driver(nt36672e_panel_driver); + +MODULE_AUTHOR("Ritesh Kumar <quic_riteshk@quicinc.com>"); +MODULE_DESCRIPTION("Novatek NT36672E DSI Panel Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-novatek-nt37801.c b/drivers/gpu/drm/panel/panel-novatek-nt37801.c new file mode 100644 index 000000000000..d6a37d7e0cc6 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-novatek-nt37801.c @@ -0,0 +1,340 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2024 Linaro Limited + +#include <linux/backlight.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/regulator/consumer.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> + +#include <drm/display/drm_dsc.h> +#include <drm/display/drm_dsc_helper.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> +#include <drm/drm_probe_helper.h> + +#include <video/mipi_display.h> + +struct novatek_nt37801 { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct drm_dsc_config dsc; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data *supplies; +}; + +static const struct regulator_bulk_data novatek_nt37801_supplies[] = { + { .supply = "vddio" }, + { .supply = "vci" }, + { .supply = "vdd" }, +}; + +static inline struct novatek_nt37801 *to_novatek_nt37801(struct drm_panel *panel) +{ + return container_of(panel, struct novatek_nt37801, panel); +} + +static void novatek_nt37801_reset(struct novatek_nt37801 *ctx) +{ + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(10000, 21000); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(10000, 21000); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(10000, 21000); +} + +#define NT37801_DCS_SWITCH_PAGE 0xf0 + +#define novatek_nt37801_switch_page(dsi_ctx, page) \ + mipi_dsi_dcs_write_seq_multi((dsi_ctx), NT37801_DCS_SWITCH_PAGE, \ + 0x55, 0xaa, 0x52, 0x08, (page)) + +static int novatek_nt37801_on(struct novatek_nt37801 *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + novatek_nt37801_switch_page(&dsi_ctx, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc5, 0x0b, 0x0b, 0x0b); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0xaa, 0x55, 0xa5, 0x80); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf5, 0x10); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x1b); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf4, 0x55); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x18); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf8, 0x19); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x0f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfc, 0x00); + mipi_dsi_dcs_set_column_address_multi(&dsi_ctx, 0x0000, 0x059f); + mipi_dsi_dcs_set_page_address_multi(&dsi_ctx, 0x0000, 0x0c7f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x90, 0x03, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x91, + 0x89, 0x28, 0x00, 0x28, 0xc2, 0x00, 0x02, + 0x68, 0x04, 0x6c, 0x00, 0x0a, 0x02, 0x77, + 0x01, 0xe9, 0x10, 0xf0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0xaa, 0x55, 0xa5, 0x81); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x23); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, + 0x00, 0x01, 0x00, 0x11, 0x33, 0x33, 0x33, + 0x55, 0x57, 0xd0, 0x00, 0x00, 0x44, 0x56, + 0x77, 0x78, 0x9a, 0xbc, 0xdd, 0xf0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x06); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf3, 0xdc); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_GAMMA_CURVE, 0x00); + mipi_dsi_dcs_set_tear_on_multi(&dsi_ctx, MIPI_DSI_DCS_TEAR_MODE_VBLANK); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3b, 0x00, 0x18, 0x00, 0x10); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY, + 0x20); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x51, + 0x07, 0xff, 0x07, 0xff, 0x0f, 0xff); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5a, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5f, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x9c, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_MEMORY_START); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2f, 0x00); + + novatek_nt37801_switch_page(&dsi_ctx, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb2, 0x55, 0x01, 0xff, 0x03); + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 120); + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 20); + + return dsi_ctx.accum_err; +} + +static int novatek_nt37801_off(struct novatek_nt37801 *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 20); + + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 120); + + return dsi_ctx.accum_err; +} + +static int novatek_nt37801_prepare(struct drm_panel *panel) +{ + struct novatek_nt37801 *ctx = to_novatek_nt37801(panel); + struct device *dev = &ctx->dsi->dev; + struct drm_dsc_picture_parameter_set pps; + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(novatek_nt37801_supplies), + ctx->supplies); + if (ret < 0) + return ret; + + novatek_nt37801_reset(ctx); + + ret = novatek_nt37801_on(ctx); + if (ret < 0) + goto err; + + drm_dsc_pps_payload_pack(&pps, &ctx->dsc); + + ret = mipi_dsi_picture_parameter_set(ctx->dsi, &pps); + if (ret < 0) { + dev_err(panel->dev, "failed to transmit PPS: %d\n", ret); + goto err; + } + + ret = mipi_dsi_compression_mode(ctx->dsi, true); + if (ret < 0) { + dev_err(dev, "failed to enable compression mode: %d\n", ret); + goto err; + } + + msleep(28); + + return 0; + +err: + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(novatek_nt37801_supplies), + ctx->supplies); + + return ret; +} + +static int novatek_nt37801_unprepare(struct drm_panel *panel) +{ + struct novatek_nt37801 *ctx = to_novatek_nt37801(panel); + struct device *dev = &ctx->dsi->dev; + int ret; + + ret = novatek_nt37801_off(ctx); + if (ret < 0) + dev_err(dev, "Failed to un-initialize panel: %d\n", ret); + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + + regulator_bulk_disable(ARRAY_SIZE(novatek_nt37801_supplies), + ctx->supplies); + + return 0; +} + +static const struct drm_display_mode novatek_nt37801_mode = { + .clock = (1440 + 20 + 4 + 20) * (3200 + 20 + 2 + 18) * 120 / 1000, + .hdisplay = 1440, + .hsync_start = 1440 + 20, + .hsync_end = 1440 + 20 + 4, + .htotal = 1440 + 20 + 4 + 20, + .vdisplay = 3200, + .vsync_start = 3200 + 20, + .vsync_end = 3200 + 20 + 2, + .vtotal = 3200 + 20 + 2 + 18, + .type = DRM_MODE_TYPE_DRIVER, +}; + +static int novatek_nt37801_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + return drm_connector_helper_get_modes_fixed(connector, + &novatek_nt37801_mode); +} + +static const struct drm_panel_funcs novatek_nt37801_panel_funcs = { + .prepare = novatek_nt37801_prepare, + .unprepare = novatek_nt37801_unprepare, + .get_modes = novatek_nt37801_get_modes, +}; + +static int novatek_nt37801_bl_update_status(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + u16 brightness = backlight_get_brightness(bl); + int ret; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + ret = mipi_dsi_dcs_set_display_brightness_large(dsi, brightness); + if (ret < 0) + return ret; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + return 0; +} + +static const struct backlight_ops novatek_nt37801_bl_ops = { + .update_status = novatek_nt37801_bl_update_status, +}; + +static struct backlight_device * +novatek_nt37801_create_backlight(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + const struct backlight_properties props = { + .type = BACKLIGHT_RAW, + .brightness = 4095, + .max_brightness = 4095, + }; + + return devm_backlight_device_register(dev, dev_name(dev), dev, dsi, + &novatek_nt37801_bl_ops, &props); +} + +static int novatek_nt37801_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct novatek_nt37801 *ctx; + int ret; + + ctx = devm_drm_panel_alloc(dev, struct novatek_nt37801, panel, + &novatek_nt37801_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ret = devm_regulator_bulk_get_const(dev, + ARRAY_SIZE(novatek_nt37801_supplies), + novatek_nt37801_supplies, + &ctx->supplies); + if (ret < 0) + return ret; + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), + "Failed to get reset-gpios\n"); + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_NO_EOT_PACKET | MIPI_DSI_CLOCK_NON_CONTINUOUS; + + ctx->panel.prepare_prev_first = true; + ctx->panel.backlight = novatek_nt37801_create_backlight(dsi); + if (IS_ERR(ctx->panel.backlight)) + return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight), + "Failed to create backlight\n"); + + drm_panel_add(&ctx->panel); + + /* This panel only supports DSC; unconditionally enable it */ + dsi->dsc = &ctx->dsc; + ctx->dsc.dsc_version_major = 1; + ctx->dsc.dsc_version_minor = 1; + ctx->dsc.slice_height = 40; + ctx->dsc.slice_width = 720; + ctx->dsc.slice_count = 1440 / ctx->dsc.slice_width; + ctx->dsc.bits_per_component = 8; + ctx->dsc.bits_per_pixel = 8 << 4; /* 4 fractional bits */ + ctx->dsc.block_pred_enable = true; + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + drm_panel_remove(&ctx->panel); + return dev_err_probe(dev, ret, "Failed to attach to DSI host\n"); + } + + return 0; +} + +static void novatek_nt37801_remove(struct mipi_dsi_device *dsi) +{ + struct novatek_nt37801 *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id novatek_nt37801_of_match[] = { + { .compatible = "novatek,nt37801" }, + {} +}; +MODULE_DEVICE_TABLE(of, novatek_nt37801_of_match); + +static struct mipi_dsi_driver novatek_nt37801_driver = { + .probe = novatek_nt37801_probe, + .remove = novatek_nt37801_remove, + .driver = { + .name = "panel-novatek-nt37801", + .of_match_table = novatek_nt37801_of_match, + }, +}; +module_mipi_dsi_driver(novatek_nt37801_driver); + +MODULE_AUTHOR("Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>"); +MODULE_DESCRIPTION("Panel driver for the Novatek NT37801/NT37810 AMOLED DSI panel"); +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..a629976bae54 --- /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/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/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 { + const struct drm_display_mode *display_modes; + unsigned int num_modes; + u16 width_mm, height_mm; + u32 bus_format, bus_flags; +}; + +struct nt39016 { + struct drm_panel drm_panel; + struct regmap *map; + struct regulator *supply; + const struct nt39016_panel_info *panel_info; + + struct gpio_desc *reset_gpio; +}; + +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(drm_panel->dev, "Failed to enable power supply: %d\n", 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(drm_panel->dev, "Failed to init registers: %d\n", 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(drm_panel->dev, "Unable to enable panel: %d\n", ret); + return ret; + } + + if (drm_panel->backlight) { + /* Wait for the picture to be ready before enabling backlight */ + msleep(150); + } + + return 0; +} + +static int nt39016_disable(struct drm_panel *drm_panel) +{ + struct nt39016 *panel = to_nt39016(drm_panel); + int err; + + err = regmap_write(panel->map, NT39016_REG_SYSTEM, + NT39016_SYSTEM_RESET_N); + if (err) { + dev_err(drm_panel->dev, "Unable to disable panel: %d\n", 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; + unsigned int i; + + for (i = 0; i < panel_info->num_modes; i++) { + mode = drm_mode_duplicate(connector->dev, + &panel_info->display_modes[i]); + if (!mode) + return -ENOMEM; + + drm_mode_set_name(mode); + + mode->type = DRM_MODE_TYPE_DRIVER; + if (panel_info->num_modes == 1) + mode->type |= 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 panel_info->num_modes; +} + +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_drm_panel_alloc(dev, struct nt39016, drm_panel, &nt39016_funcs, + DRM_MODE_CONNECTOR_DPI); + if (IS_ERR(panel)) + return PTR_ERR(panel); + + 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)) + return dev_err_probe(dev, PTR_ERR(panel->supply), + "Failed to get power supply\n"); + + panel->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(panel->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(panel->reset_gpio), "Failed to get reset GPIO\n"); + + 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\n"); + return err; + } + + panel->map = devm_regmap_init_spi(spi, &nt39016_regmap_config); + if (IS_ERR(panel->map)) { + dev_err(dev, "Failed to init regmap\n"); + return PTR_ERR(panel->map); + } + + err = drm_panel_of_backlight(&panel->drm_panel); + if (err) + return dev_err_probe(dev, err, "Failed to get backlight handle\n"); + + drm_panel_add(&panel->drm_panel); + + return 0; +} + +static void 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); +} + +static const struct drm_display_mode kd035g6_display_modes[] = { + { /* 60 Hz */ + .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, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + }, + { /* 50 Hz */ + .clock = 5400, + .hdisplay = 320, + .hsync_start = 320 + 42, + .hsync_end = 320 + 42 + 50, + .htotal = 320 + 42 + 50 + 20, + .vdisplay = 240, + .vsync_start = 240 + 5, + .vsync_end = 240 + 5 + 1, + .vtotal = 240 + 5 + 1 + 4, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + }, +}; + +static const struct nt39016_panel_info kd035g6_info = { + .display_modes = kd035g6_display_modes, + .num_modes = ARRAY_SIZE(kd035g6_display_modes), + .width_mm = 71, + .height_mm = 53, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE, +}; + +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_DESCRIPTION("Novatek NT39016 TFT LCD panel driver"); +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 5e8d4523e9ed..66f99982f360 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> @@ -15,13 +14,13 @@ #include <linux/of.h> #include <linux/regulator/consumer.h> -#include <drm/drm_modes.h> -#include <drm/drm_panel.h> -#include <drm/drmP.h> - #include <video/videomode.h> #include <video/display_timing.h> +#include <drm/drm_device.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + #define LCD_OLINUXINO_HEADER_MAGIC 0x4F4CB727 #define LCD_OLINUXINO_DATA_LEN 256 @@ -65,10 +64,6 @@ struct lcd_olinuxino { struct i2c_client *client; struct mutex mutex; - bool prepared; - bool enabled; - - struct backlight_device *backlight; struct regulator *supply; struct gpio_desc *enable_gpio; @@ -80,32 +75,13 @@ static inline struct lcd_olinuxino *to_lcd_olinuxino(struct drm_panel *panel) return container_of(panel, struct lcd_olinuxino, panel); } -static int lcd_olinuxino_disable(struct drm_panel *panel) -{ - struct lcd_olinuxino *lcd = to_lcd_olinuxino(panel); - - if (!lcd->enabled) - return 0; - - backlight_disable(lcd->backlight); - - lcd->enabled = false; - - return 0; -} - static int lcd_olinuxino_unprepare(struct drm_panel *panel) { struct lcd_olinuxino *lcd = to_lcd_olinuxino(panel); - if (!lcd->prepared) - return 0; - gpiod_set_value_cansleep(lcd->enable_gpio, 0); regulator_disable(lcd->supply); - lcd->prepared = false; - return 0; } @@ -114,39 +90,20 @@ static int lcd_olinuxino_prepare(struct drm_panel *panel) struct lcd_olinuxino *lcd = to_lcd_olinuxino(panel); int ret; - if (lcd->prepared) - return 0; - ret = regulator_enable(lcd->supply); if (ret < 0) return ret; gpiod_set_value_cansleep(lcd->enable_gpio, 1); - lcd->prepared = true; return 0; } -static int lcd_olinuxino_enable(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); - - if (lcd->enabled) - return 0; - - backlight_enable(lcd->backlight); - - lcd->enabled = true; - - return 0; -} - -static int lcd_olinuxino_get_modes(struct drm_panel *panel) -{ - 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 +112,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; @@ -177,7 +134,6 @@ static int lcd_olinuxino_get_modes(struct drm_panel *panel) lcd_mode->vpw; mode->vtotal = lcd_mode->vactive + lcd_mode->vfp + lcd_mode->vpw + lcd_mode->vbp; - mode->vrefresh = lcd_mode->refresh; /* Always make the first mode preferred */ if (i == 0) @@ -190,7 +146,6 @@ static int lcd_olinuxino_get_modes(struct drm_panel *panel) num++; } - memcpy(connector->display_info.name, lcd_info->name, 32); connector->display_info.width_mm = lcd_info->width_mm; connector->display_info.height_mm = lcd_info->height_mm; connector->display_info.bpc = lcd_info->bpc; @@ -204,15 +159,12 @@ static int lcd_olinuxino_get_modes(struct drm_panel *panel) } static const struct drm_panel_funcs lcd_olinuxino_funcs = { - .disable = lcd_olinuxino_disable, .unprepare = lcd_olinuxino_unprepare, .prepare = lcd_olinuxino_prepare, - .enable = lcd_olinuxino_enable, .get_modes = lcd_olinuxino_get_modes, }; -static int lcd_olinuxino_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int lcd_olinuxino_probe(struct i2c_client *client) { struct device *dev = &client->dev; struct lcd_olinuxino *lcd; @@ -223,9 +175,11 @@ static int lcd_olinuxino_probe(struct i2c_client *client, I2C_FUNC_SMBUS_READ_I2C_BLOCK)) return -ENODEV; - lcd = devm_kzalloc(dev, sizeof(*lcd), GFP_KERNEL); - if (!lcd) - return -ENOMEM; + lcd = devm_drm_panel_alloc(dev, struct lcd_olinuxino, panel, + &lcd_olinuxino_funcs, + DRM_MODE_CONNECTOR_DPI); + if (IS_ERR(lcd)) + return PTR_ERR(lcd); i2c_set_clientdata(client, lcd); lcd->dev = dev; @@ -274,9 +228,6 @@ static int lcd_olinuxino_probe(struct i2c_client *client, lcd->eeprom.num_modes = 4; } - lcd->enabled = false; - lcd->prepared = false; - lcd->supply = devm_regulator_get(dev, "power"); if (IS_ERR(lcd->supply)) return PTR_ERR(lcd->supply); @@ -285,27 +236,20 @@ 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); + ret = drm_panel_of_backlight(&lcd->panel); + if (ret) + return ret; - drm_panel_init(&lcd->panel); - lcd->panel.dev = dev; - lcd->panel.funcs = &lcd_olinuxino_funcs; + drm_panel_add(&lcd->panel); - return drm_panel_add(&lcd->panel); + return 0; } -static int lcd_olinuxino_remove(struct i2c_client *client) +static void lcd_olinuxino_remove(struct i2c_client *client) { struct lcd_olinuxino *panel = i2c_get_clientdata(client); drm_panel_remove(&panel->panel); - - lcd_olinuxino_disable(&panel->panel); - lcd_olinuxino_unprepare(&panel->panel); - - return 0; } static const struct of_device_id lcd_olinuxino_of_ids[] = { diff --git a/drivers/gpu/drm/panel/panel-orisetech-ota5601a.c b/drivers/gpu/drm/panel/panel-orisetech-ota5601a.c new file mode 100644 index 000000000000..8a608972fc41 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-orisetech-ota5601a.c @@ -0,0 +1,360 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Orisetech OTA5601A TFT LCD panel driver + * + * Copyright (C) 2021, Christophe Branchereau <cbranchereau@gmail.com> + */ + +#include <linux/bits.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/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/spi/spi.h> + +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +#define OTA5601A_CTL 0x01 +#define OTA5601A_CTL_OFF 0x00 +#define OTA5601A_CTL_ON BIT(0) + +struct ota5601a_panel_info { + const struct drm_display_mode *display_modes; + unsigned int num_modes; + u16 width_mm, height_mm; + u32 bus_format, bus_flags; +}; + +struct ota5601a { + struct drm_panel drm_panel; + struct regmap *map; + struct regulator *supply; + const struct ota5601a_panel_info *panel_info; + + struct gpio_desc *reset_gpio; +}; + +static inline struct ota5601a *to_ota5601a(struct drm_panel *panel) +{ + return container_of(panel, struct ota5601a, drm_panel); +} + +static const struct reg_sequence ota5601a_panel_regs[] = { + { 0xfd, 0x00 }, /* Page Shift */ + { 0x02, 0x00 }, /* Reset */ + + { 0x18, 0x00 }, /* Interface Sel: RGB 24 Bits */ + { 0x34, 0x20 }, /* Undocumented */ + + { 0x0c, 0x01 }, /* Contrast set by CMD1 == within page 0x00 */ + { 0x0d, 0x48 }, /* R Brightness */ + { 0x0e, 0x48 }, /* G Brightness */ + { 0x0f, 0x48 }, /* B Brightness */ + { 0x07, 0x40 }, /* R Contrast */ + { 0x08, 0x33 }, /* G Contrast */ + { 0x09, 0x3a }, /* B Contrast */ + + { 0x16, 0x01 }, /* NTSC Sel */ + { 0x19, 0x8d }, /* VBLK */ + { 0x1a, 0x28 }, /* HBLK */ + { 0x1c, 0x00 }, /* Scan Shift Dir. */ + + { 0xfd, 0xc5 }, /* Page Shift */ + { 0x82, 0x0c }, /* PWR_CTRL Pump */ + { 0xa2, 0xb4 }, /* PWR_CTRL VGH/VGL */ + + { 0xfd, 0xc4 }, /* Page Shift - What follows is listed as "RGB 24bit Timing Set" */ + { 0x82, 0x45 }, + + { 0xfd, 0xc1 }, + { 0x91, 0x02 }, + + { 0xfd, 0xc0 }, + { 0xa1, 0x01 }, + { 0xa2, 0x1f }, + { 0xa3, 0x0b }, + { 0xa4, 0x38 }, + { 0xa5, 0x00 }, + { 0xa6, 0x0a }, + { 0xa7, 0x38 }, + { 0xa8, 0x00 }, + { 0xa9, 0x0a }, + { 0xaa, 0x37 }, + + { 0xfd, 0xce }, + { 0x81, 0x18 }, + { 0x82, 0x43 }, + { 0x83, 0x43 }, + { 0x91, 0x06 }, + { 0x93, 0x38 }, + { 0x94, 0x02 }, + { 0x95, 0x06 }, + { 0x97, 0x38 }, + { 0x98, 0x02 }, + { 0x99, 0x06 }, + { 0x9b, 0x38 }, + { 0x9c, 0x02 }, + + { 0xfd, 0x00 }, /* Page Shift */ +}; + +static const struct regmap_config ota5601a_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int ota5601a_prepare(struct drm_panel *drm_panel) +{ + struct ota5601a *panel = to_ota5601a(drm_panel); + int err; + + err = regulator_enable(panel->supply); + if (err) { + dev_err(drm_panel->dev, "Failed to enable power supply: %d\n", err); + return err; + } + + /* Reset to be held low for 10us min according to the doc, 10ms before sending commands */ + gpiod_set_value_cansleep(panel->reset_gpio, 1); + usleep_range(10, 30); + gpiod_set_value_cansleep(panel->reset_gpio, 0); + usleep_range(10000, 20000); + + /* Init all registers. */ + err = regmap_multi_reg_write(panel->map, ota5601a_panel_regs, + ARRAY_SIZE(ota5601a_panel_regs)); + if (err) { + dev_err(drm_panel->dev, "Failed to init registers: %d\n", err); + goto err_disable_regulator; + } + + msleep(120); + + return 0; + +err_disable_regulator: + regulator_disable(panel->supply); + return err; +} + +static int ota5601a_unprepare(struct drm_panel *drm_panel) +{ + struct ota5601a *panel = to_ota5601a(drm_panel); + + gpiod_set_value_cansleep(panel->reset_gpio, 1); + + regulator_disable(panel->supply); + + return 0; +} + +static int ota5601a_enable(struct drm_panel *drm_panel) +{ + struct ota5601a *panel = to_ota5601a(drm_panel); + int err; + + err = regmap_write(panel->map, OTA5601A_CTL, OTA5601A_CTL_ON); + + if (err) { + dev_err(drm_panel->dev, "Unable to enable panel: %d\n", err); + return err; + } + + if (drm_panel->backlight) { + /* Wait for the picture to be ready before enabling backlight */ + msleep(120); + } + + return 0; +} + +static int ota5601a_disable(struct drm_panel *drm_panel) +{ + struct ota5601a *panel = to_ota5601a(drm_panel); + int err; + + err = regmap_write(panel->map, OTA5601A_CTL, OTA5601A_CTL_OFF); + + if (err) { + dev_err(drm_panel->dev, "Unable to disable panel: %d\n", err); + return err; + } + + return 0; +} + +static int ota5601a_get_modes(struct drm_panel *drm_panel, + struct drm_connector *connector) +{ + struct ota5601a *panel = to_ota5601a(drm_panel); + const struct ota5601a_panel_info *panel_info = panel->panel_info; + struct drm_display_mode *mode; + unsigned int i; + + for (i = 0; i < panel_info->num_modes; i++) { + mode = drm_mode_duplicate(connector->dev, + &panel_info->display_modes[i]); + if (!mode) + return -ENOMEM; + + drm_mode_set_name(mode); + + mode->type = DRM_MODE_TYPE_DRIVER; + if (panel_info->num_modes == 1) + mode->type |= 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 panel_info->num_modes; +} + +static const struct drm_panel_funcs ota5601a_funcs = { + .prepare = ota5601a_prepare, + .unprepare = ota5601a_unprepare, + .enable = ota5601a_enable, + .disable = ota5601a_disable, + .get_modes = ota5601a_get_modes, +}; + +static int ota5601a_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + struct device *dev = &spi->dev; + struct ota5601a *panel; + int err; + + panel = devm_drm_panel_alloc(dev, struct ota5601a, drm_panel, + &ota5601a_funcs, + DRM_MODE_CONNECTOR_DPI); + if (IS_ERR(panel)) + return PTR_ERR(panel); + + spi_set_drvdata(spi, panel); + + panel->panel_info = (const struct ota5601a_panel_info *)id->driver_data; + 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\n"); + 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\n"); + 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\n"); + return err; + } + + panel->map = devm_regmap_init_spi(spi, &ota5601a_regmap_config); + if (IS_ERR(panel->map)) { + dev_err(dev, "Failed to init regmap\n"); + return PTR_ERR(panel->map); + } + + err = drm_panel_of_backlight(&panel->drm_panel); + if (err) + return dev_err_probe(dev, err, "Failed to get backlight handle\n"); + + drm_panel_add(&panel->drm_panel); + + return 0; +} + +static void ota5601a_remove(struct spi_device *spi) +{ + struct ota5601a *panel = spi_get_drvdata(spi); + + drm_panel_remove(&panel->drm_panel); + + ota5601a_disable(&panel->drm_panel); + ota5601a_unprepare(&panel->drm_panel); +} + +static const struct drm_display_mode gpt3_display_modes[] = { + { /* 60 Hz */ + .clock = 27000, + .hdisplay = 640, + .hsync_start = 640 + 220, + .hsync_end = 640 + 220 + 20, + .htotal = 640 + 220 + 20 + 20, + .vdisplay = 480, + .vsync_start = 480 + 7, + .vsync_end = 480 + 7 + 6, + .vtotal = 480 + 7 + 6 + 7, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + }, + + { /* 50 Hz */ + .clock = 24000, + .hdisplay = 640, + .hsync_start = 640 + 280, + .hsync_end = 640 + 280 + 20, + .htotal = 640 + 280 + 20 + 20, + .vdisplay = 480, + .vsync_start = 480 + 7, + .vsync_end = 480 + 7 + 6, + .vtotal = 480 + 7 + 6 + 7, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + }, +}; + +static const struct ota5601a_panel_info gpt3_info = { + .display_modes = gpt3_display_modes, + .num_modes = ARRAY_SIZE(gpt3_display_modes), + .width_mm = 71, + .height_mm = 51, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE, +}; + +static const struct spi_device_id gpt3_id[] = { + { "gpt3", (kernel_ulong_t)&gpt3_info }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(spi, gpt3_id); + +static const struct of_device_id ota5601a_of_match[] = { + { .compatible = "focaltech,gpt3" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ota5601a_of_match); + +static struct spi_driver ota5601a_driver = { + .driver = { + .name = "ota5601a", + .of_match_table = ota5601a_of_match, + }, + .id_table = gpt3_id, + .probe = ota5601a_probe, + .remove = ota5601a_remove, +}; + +module_spi_driver(ota5601a_driver); + +MODULE_AUTHOR("Christophe Branchereau <cbranchereau@gmail.com>"); +MODULE_DESCRIPTION("Orisetech OTA5601A TFT LCD panel driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-orisetech-otm8009a.c b/drivers/gpu/drm/panel/panel-orisetech-otm8009a.c index 87fa316e1d7b..a0f58c3b73f6 100644 --- a/drivers/gpu/drm/panel/panel-orisetech-otm8009a.c +++ b/drivers/gpu/drm/panel/panel-orisetech-otm8009a.c @@ -6,14 +6,19 @@ * Yannick Fertre <yannick.fertre@st.com> */ -#include <drm/drmP.h> -#include <drm/drm_mipi_dsi.h> -#include <drm/drm_panel.h> #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> + #include <video/mipi_display.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + #define OTM8009A_BACKLIGHT_DEFAULT 240 #define OTM8009A_BACKLIGHT_MAX 255 @@ -56,6 +61,9 @@ #define MCS_CMD2_ENA1 0xFF00 /* Enable Access Command2 "CMD2" */ #define MCS_CMD2_ENA2 0xFF80 /* Enable Access Orise Command2 */ +#define OTM8009A_HDISPLAY 480 +#define OTM8009A_VDISPLAY 800 + struct otm8009a { struct device *dev; struct drm_panel panel; @@ -63,23 +71,37 @@ struct otm8009a { struct gpio_desc *reset_gpio; struct regulator *supply; bool prepared; - bool enabled; }; -static const struct drm_display_mode default_mode = { - .clock = 32729, - .hdisplay = 480, - .hsync_start = 480 + 120, - .hsync_end = 480 + 120 + 63, - .htotal = 480 + 120 + 63 + 120, - .vdisplay = 800, - .vsync_start = 800 + 12, - .vsync_end = 800 + 12 + 12, - .vtotal = 800 + 12 + 12 + 12, - .vrefresh = 50, - .flags = 0, - .width_mm = 52, - .height_mm = 86, +static const struct drm_display_mode modes[] = { + { /* 50 Hz, preferred */ + .clock = 29700, + .hdisplay = 480, + .hsync_start = 480 + 98, + .hsync_end = 480 + 98 + 32, + .htotal = 480 + 98 + 32 + 98, + .vdisplay = 800, + .vsync_start = 800 + 15, + .vsync_end = 800 + 15 + 10, + .vtotal = 800 + 15 + 10 + 14, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + .width_mm = 52, + .height_mm = 86, + }, + { /* 60 Hz */ + .clock = 33000, + .hdisplay = 480, + .hsync_start = 480 + 70, + .hsync_end = 480 + 70 + 32, + .htotal = 480 + 70 + 32 + 72, + .vdisplay = 800, + .vsync_start = 800 + 15, + .vsync_end = 800 + 15 + 10, + .vtotal = 800 + 15 + 10 + 16, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + .width_mm = 52, + .height_mm = 86, + }, }; static inline struct otm8009a *panel_to_otm8009a(struct drm_panel *panel) @@ -93,21 +115,7 @@ static void otm8009a_dcs_write_buf(struct otm8009a *ctx, const void *data, struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); if (mipi_dsi_dcs_write_buffer(dsi, data, len) < 0) - DRM_WARN("mipi dsi dcs write buffer failed\n"); -} - -static void otm8009a_dcs_write_buf_hs(struct otm8009a *ctx, const void *data, - size_t len) -{ - struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); - - /* data will be sent in dsi hs mode (ie. no lpm) */ - dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; - - otm8009a_dcs_write_buf(ctx, data, len); - - /* restore back the dsi lpm mode */ - dsi->mode_flags |= MIPI_DSI_MODE_LPM; + dev_warn(ctx->dev, "mipi dsi dcs write buffer failed\n"); } #define dcs_write_seq(ctx, seq...) \ @@ -219,12 +227,11 @@ static int otm8009a_init_sequence(struct otm8009a *ctx) /* Default portrait 480x800 rgb24 */ dcs_write_seq(ctx, MIPI_DCS_SET_ADDRESS_MODE, 0x00); - ret = mipi_dsi_dcs_set_column_address(dsi, 0, - default_mode.hdisplay - 1); + ret = mipi_dsi_dcs_set_column_address(dsi, 0, OTM8009A_HDISPLAY - 1); if (ret) return ret; - ret = mipi_dsi_dcs_set_page_address(dsi, 0, default_mode.vdisplay - 1); + ret = mipi_dsi_dcs_set_page_address(dsi, 0, OTM8009A_VDISPLAY - 1); if (ret) return ret; @@ -248,6 +255,9 @@ static int otm8009a_init_sequence(struct otm8009a *ctx) /* Send Command GRAM memory write (no parameters) */ dcs_write_seq(ctx, MIPI_DCS_WRITE_MEMORY_START); + /* Wait a short while to let the panel be ready before the 1st frame */ + mdelay(10); + return 0; } @@ -257,9 +267,6 @@ static int otm8009a_disable(struct drm_panel *panel) struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); int ret; - if (!ctx->enabled) - return 0; /* This is not an issue so we return 0 here */ - backlight_disable(ctx->bl_dev); ret = mipi_dsi_dcs_set_display_off(dsi); @@ -272,8 +279,6 @@ static int otm8009a_disable(struct drm_panel *panel) msleep(120); - ctx->enabled = false; - return 0; } @@ -281,9 +286,6 @@ static int otm8009a_unprepare(struct drm_panel *panel) { struct otm8009a *ctx = panel_to_otm8009a(panel); - if (!ctx->prepared) - return 0; - if (ctx->reset_gpio) { gpiod_set_value_cansleep(ctx->reset_gpio, 1); msleep(20); @@ -301,12 +303,9 @@ static int otm8009a_prepare(struct drm_panel *panel) struct otm8009a *ctx = panel_to_otm8009a(panel); int ret; - if (ctx->prepared) - return 0; - ret = regulator_enable(ctx->supply); if (ret < 0) { - DRM_ERROR("failed to enable supply: %d\n", ret); + dev_err(panel->dev, "failed to enable supply: %d\n", ret); return ret; } @@ -331,37 +330,42 @@ static int otm8009a_enable(struct drm_panel *panel) { struct otm8009a *ctx = panel_to_otm8009a(panel); - if (ctx->enabled) - return 0; - backlight_enable(ctx->bl_dev); - ctx->enabled = true; - 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); - if (!mode) { - DRM_ERROR("failed to add mode %ux%ux@%u\n", - default_mode.hdisplay, default_mode.vdisplay, - default_mode.vrefresh); - return -ENOMEM; + unsigned int num_modes = ARRAY_SIZE(modes); + unsigned int i; + + for (i = 0; i < num_modes; i++) { + mode = drm_mode_duplicate(connector->dev, &modes[i]); + if (!mode) { + dev_err(panel->dev, "failed to add mode %ux%u@%u\n", + modes[i].hdisplay, + modes[i].vdisplay, + drm_mode_vrefresh(&modes[i])); + return -ENOMEM; + } + + mode->type = DRM_MODE_TYPE_DRIVER; + + /* Setting first mode as preferred */ + if (!i) + mode->type |= DRM_MODE_TYPE_PREFERRED; + + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); } - drm_mode_set_name(mode); - - mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; - drm_mode_probed_add(panel->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; + return num_modes; } static const struct drm_panel_funcs otm8009a_drm_funcs = { @@ -382,18 +386,18 @@ static int otm8009a_backlight_update_status(struct backlight_device *bd) u8 data[2]; if (!ctx->prepared) { - DRM_DEBUG("lcd not ready yet for setting its backlight!\n"); + dev_dbg(&bd->dev, "lcd not ready yet for setting its backlight!\n"); return -ENXIO; } - if (bd->props.power <= FB_BLANK_NORMAL) { + if (bd->props.power <= BACKLIGHT_POWER_REDUCED) { /* Power on the backlight with the requested brightness * Note We can not use mipi_dsi_dcs_set_display_brightness() * as otm8009a driver support only 8-bit brightness (1 param). */ data[0] = MIPI_DCS_SET_DISPLAY_BRIGHTNESS; data[1] = bd->props.brightness; - otm8009a_dcs_write_buf_hs(ctx, data, ARRAY_SIZE(data)); + otm8009a_dcs_write_buf(ctx, data, ARRAY_SIZE(data)); /* set Brightness Control & Backlight on */ data[1] = 0x24; @@ -405,7 +409,7 @@ static int otm8009a_backlight_update_status(struct backlight_device *bd) /* Update Brightness Control & Backlight */ data[0] = MIPI_DCS_WRITE_CONTROL_DISPLAY; - otm8009a_dcs_write_buf_hs(ctx, data, ARRAY_SIZE(data)); + otm8009a_dcs_write_buf(ctx, data, ARRAY_SIZE(data)); return 0; } @@ -420,9 +424,11 @@ static int otm8009a_probe(struct mipi_dsi_device *dsi) struct otm8009a *ctx; int ret; - ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); - if (!ctx) - return -ENOMEM; + ctx = devm_drm_panel_alloc(dev, struct otm8009a, panel, + &otm8009a_drm_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); ctx->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(ctx->reset_gpio)) { @@ -433,7 +439,8 @@ static int otm8009a_probe(struct mipi_dsi_device *dsi) ctx->supply = devm_regulator_get(dev, "power"); if (IS_ERR(ctx->supply)) { ret = PTR_ERR(ctx->supply); - dev_err(dev, "failed to request regulator: %d\n", ret); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to request regulator: %d\n", ret); return ret; } @@ -444,14 +451,10 @@ static int otm8009a_probe(struct mipi_dsi_device *dsi) dsi->lanes = 2; dsi->format = MIPI_DSI_FMT_RGB888; 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; + MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS; ctx->bl_dev = devm_backlight_device_register(dev, dev_name(dev), - dsi->host->dev, ctx, + dev, ctx, &otm8009a_backlight_ops, NULL); if (IS_ERR(ctx->bl_dev)) { @@ -462,7 +465,7 @@ static int otm8009a_probe(struct mipi_dsi_device *dsi) ctx->bl_dev->props.max_brightness = OTM8009A_BACKLIGHT_MAX; ctx->bl_dev->props.brightness = OTM8009A_BACKLIGHT_DEFAULT; - ctx->bl_dev->props.power = FB_BLANK_POWERDOWN; + ctx->bl_dev->props.power = BACKLIGHT_POWER_OFF; ctx->bl_dev->props.type = BACKLIGHT_RAW; drm_panel_add(&ctx->panel); @@ -471,21 +474,18 @@ static int otm8009a_probe(struct mipi_dsi_device *dsi) if (ret < 0) { dev_err(dev, "mipi_dsi_attach failed. Is host ready?\n"); drm_panel_remove(&ctx->panel); - backlight_device_unregister(ctx->bl_dev); return ret; } return 0; } -static int otm8009a_remove(struct mipi_dsi_device *dsi) +static void otm8009a_remove(struct mipi_dsi_device *dsi) { struct otm8009a *ctx = mipi_dsi_get_drvdata(dsi); mipi_dsi_detach(dsi); drm_panel_remove(&ctx->panel); - - return 0; } static const struct of_device_id orisetech_otm8009a_of_match[] = { diff --git a/drivers/gpu/drm/panel/panel-osd-osd101t2587-53ts.c b/drivers/gpu/drm/panel/panel-osd-osd101t2587-53ts.c new file mode 100644 index 000000000000..2334b77f348c --- /dev/null +++ b/drivers/gpu/drm/panel/panel-osd-osd101t2587-53ts.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 Texas Instruments Incorporated - https://www.ti.com + * Author: Peter Ujfalusi <peter.ujfalusi@ti.com> + */ + +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regulator/consumer.h> + +#include <drm/drm_crtc.h> +#include <drm/drm_device.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_panel.h> + +#include <video/mipi_display.h> + +struct osd101t2587_panel { + struct drm_panel base; + struct mipi_dsi_device *dsi; + + struct regulator *supply; + + const struct drm_display_mode *default_mode; +}; + +static inline struct osd101t2587_panel *ti_osd_panel(struct drm_panel *panel) +{ + return container_of(panel, struct osd101t2587_panel, base); +} + +static int osd101t2587_panel_disable(struct drm_panel *panel) +{ + struct osd101t2587_panel *osd101t2587 = ti_osd_panel(panel); + int ret; + + ret = mipi_dsi_shutdown_peripheral(osd101t2587->dsi); + + return ret; +} + +static int osd101t2587_panel_unprepare(struct drm_panel *panel) +{ + struct osd101t2587_panel *osd101t2587 = ti_osd_panel(panel); + + regulator_disable(osd101t2587->supply); + + return 0; +} + +static int osd101t2587_panel_prepare(struct drm_panel *panel) +{ + struct osd101t2587_panel *osd101t2587 = ti_osd_panel(panel); + + return regulator_enable(osd101t2587->supply); +} + +static int osd101t2587_panel_enable(struct drm_panel *panel) +{ + struct osd101t2587_panel *osd101t2587 = ti_osd_panel(panel); + int ret; + + ret = mipi_dsi_turn_on_peripheral(osd101t2587->dsi); + if (ret) + return ret; + + return ret; +} + +static const struct drm_display_mode default_mode_osd101t2587 = { + .clock = 164400, + .hdisplay = 1920, + .hsync_start = 1920 + 152, + .hsync_end = 1920 + 152 + 52, + .htotal = 1920 + 152 + 52 + 20, + .vdisplay = 1200, + .vsync_start = 1200 + 24, + .vsync_end = 1200 + 24 + 6, + .vtotal = 1200 + 24 + 6 + 48, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, +}; + +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(connector->dev, osd101t2587->default_mode); + if (!mode) { + dev_err(panel->dev, "failed to add mode %ux%ux@%u\n", + osd101t2587->default_mode->hdisplay, + osd101t2587->default_mode->vdisplay, + drm_mode_vrefresh(osd101t2587->default_mode)); + return -ENOMEM; + } + + drm_mode_set_name(mode); + + drm_mode_probed_add(connector, mode); + + connector->display_info.width_mm = 217; + connector->display_info.height_mm = 136; + + return 1; +} + +static const struct drm_panel_funcs osd101t2587_panel_funcs = { + .disable = osd101t2587_panel_disable, + .unprepare = osd101t2587_panel_unprepare, + .prepare = osd101t2587_panel_prepare, + .enable = osd101t2587_panel_enable, + .get_modes = osd101t2587_panel_get_modes, +}; + +static const struct of_device_id osd101t2587_of_match[] = { + { + .compatible = "osddisplays,osd101t2587-53ts", + .data = &default_mode_osd101t2587, + }, { + /* sentinel */ + } +}; +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); + + ret = drm_panel_of_backlight(&osd101t2587->base); + if (ret) + return ret; + + drm_panel_add(&osd101t2587->base); + + return 0; +} + +static int osd101t2587_panel_probe(struct mipi_dsi_device *dsi) +{ + struct osd101t2587_panel *osd101t2587; + const struct of_device_id *id; + int ret; + + id = of_match_node(osd101t2587_of_match, dsi->dev.of_node); + if (!id) + return -ENODEV; + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | + MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_MODE_NO_EOT_PACKET; + + osd101t2587 = devm_drm_panel_alloc(&dsi->dev, __typeof(*osd101t2587), base, + &osd101t2587_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + + if (IS_ERR(osd101t2587)) + return PTR_ERR(osd101t2587); + + mipi_dsi_set_drvdata(dsi, osd101t2587); + + osd101t2587->dsi = dsi; + osd101t2587->default_mode = id->data; + + ret = osd101t2587_panel_add(osd101t2587); + if (ret < 0) + return ret; + + ret = mipi_dsi_attach(dsi); + if (ret) + drm_panel_remove(&osd101t2587->base); + + return ret; +} + +static void osd101t2587_panel_remove(struct mipi_dsi_device *dsi) +{ + struct osd101t2587_panel *osd101t2587 = mipi_dsi_get_drvdata(dsi); + int ret; + + drm_panel_remove(&osd101t2587->base); + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", ret); +} + +static struct mipi_dsi_driver osd101t2587_panel_driver = { + .driver = { + .name = "panel-osd-osd101t2587-53ts", + .of_match_table = osd101t2587_of_match, + }, + .probe = osd101t2587_panel_probe, + .remove = osd101t2587_panel_remove, +}; +module_mipi_dsi_driver(osd101t2587_panel_driver); + +MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>"); +MODULE_DESCRIPTION("OSD101T2587-53TS DSI panel"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-panasonic-vvx10f034n00.c b/drivers/gpu/drm/panel/panel-panasonic-vvx10f034n00.c index cb4dfb98be0f..3c3308fc55df 100644 --- a/drivers/gpu/drm/panel/panel-panasonic-vvx10f034n00.c +++ b/drivers/gpu/drm/panel/panel-panasonic-vvx10f034n00.c @@ -1,35 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2015 Red Hat * Copyright (C) 2015 Sony Mobile Communications Inc. * Author: Werner Johansson <werner.johansson@sonymobile.com> * * Based on AUO panel driver by Rob Clark <robdclark@gmail.com> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published by - * the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see <http://www.gnu.org/licenses/>. */ -#include <linux/backlight.h> +#include <linux/delay.h> #include <linux/module.h> #include <linux/of.h> #include <linux/regulator/consumer.h> -#include <drm/drmP.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_panel.h> -#include <video/mipi_display.h> - /* * When power is turned off to this panel a minimum off time of 500ms has to be * observed before powering back on as there's no external reset pin. Keep @@ -41,12 +30,8 @@ struct wuxga_nt_panel { struct drm_panel base; struct mipi_dsi_device *dsi; - struct backlight_device *backlight; struct regulator *supply; - bool prepared; - bool enabled; - ktime_t earliest_wake; const struct drm_display_mode *mode; @@ -65,34 +50,16 @@ static int wuxga_nt_panel_on(struct wuxga_nt_panel *wuxga_nt) static int wuxga_nt_panel_disable(struct drm_panel *panel) { struct wuxga_nt_panel *wuxga_nt = to_wuxga_nt_panel(panel); - int mipi_ret, bl_ret = 0; - - if (!wuxga_nt->enabled) - return 0; - - 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; + return mipi_dsi_shutdown_peripheral(wuxga_nt->dsi); } static int wuxga_nt_panel_unprepare(struct drm_panel *panel) { struct wuxga_nt_panel *wuxga_nt = to_wuxga_nt_panel(panel); - if (!wuxga_nt->prepared) - return 0; - regulator_disable(wuxga_nt->supply); wuxga_nt->earliest_wake = ktime_add_ms(ktime_get_real(), MIN_POFF_MS); - wuxga_nt->prepared = false; return 0; } @@ -103,9 +70,6 @@ static int wuxga_nt_panel_prepare(struct drm_panel *panel) int ret; s64 enablewait; - if (wuxga_nt->prepared) - return 0; - /* * If the user re-enabled the panel before the required off-time then * we need to wait the remaining period before re-enabling regulator @@ -135,8 +99,6 @@ static int wuxga_nt_panel_prepare(struct drm_panel *panel) goto poweroff; } - wuxga_nt->prepared = true; - return 0; poweroff: @@ -145,24 +107,6 @@ poweroff: return ret; } -static int wuxga_nt_panel_enable(struct drm_panel *panel) -{ - struct wuxga_nt_panel *wuxga_nt = to_wuxga_nt_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; -} - static const struct drm_display_mode default_mode = { .clock = 164402, .hdisplay = 1920, @@ -173,27 +117,27 @@ static const struct drm_display_mode default_mode = { .vsync_start = 1200 + 24, .vsync_end = 1200 + 24 + 6, .vtotal = 1200 + 24 + 6 + 48, - .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, + drm_mode_vrefresh(&default_mode)); 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; } @@ -202,7 +146,6 @@ static const struct drm_panel_funcs wuxga_nt_panel_funcs = { .disable = wuxga_nt_panel_disable, .unprepare = wuxga_nt_panel_unprepare, .prepare = wuxga_nt_panel_prepare, - .enable = wuxga_nt_panel_enable, .get_modes = wuxga_nt_panel_get_modes, }; @@ -215,7 +158,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; @@ -224,39 +166,19 @@ 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; - } + ret = drm_panel_of_backlight(&wuxga_nt->base); + if (ret) + return ret; - drm_panel_init(&wuxga_nt->base); - wuxga_nt->base.funcs = &wuxga_nt_panel_funcs; - wuxga_nt->base.dev = &wuxga_nt->dsi->dev; - - ret = drm_panel_add(&wuxga_nt->base); - if (ret < 0) - goto put_backlight; + drm_panel_add(&wuxga_nt->base); return 0; - -put_backlight: - if (wuxga_nt->backlight) - put_device(&wuxga_nt->backlight->dev); - - return ret; } 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) @@ -271,9 +193,12 @@ static int wuxga_nt_panel_probe(struct mipi_dsi_device *dsi) MIPI_DSI_CLOCK_NON_CONTINUOUS | MIPI_DSI_MODE_LPM; - wuxga_nt = devm_kzalloc(&dsi->dev, sizeof(*wuxga_nt), GFP_KERNEL); - if (!wuxga_nt) - return -ENOMEM; + wuxga_nt = devm_drm_panel_alloc(&dsi->dev, __typeof(*wuxga_nt), base, + &wuxga_nt_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + + if (IS_ERR(wuxga_nt)) + return PTR_ERR(wuxga_nt); mipi_dsi_set_drvdata(dsi, wuxga_nt); @@ -283,32 +208,25 @@ static int wuxga_nt_panel_probe(struct mipi_dsi_device *dsi) if (ret < 0) return ret; - return mipi_dsi_attach(dsi); + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + wuxga_nt_panel_del(wuxga_nt); + return ret; + } + + return 0; } -static int wuxga_nt_panel_remove(struct mipi_dsi_device *dsi) +static void 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); - if (ret < 0) - dev_err(&dsi->dev, "failed to disable panel: %d\n", ret); - ret = mipi_dsi_detach(dsi); if (ret < 0) dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", ret); wuxga_nt_panel_del(wuxga_nt); - - return 0; -} - -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); } static struct mipi_dsi_driver wuxga_nt_panel_driver = { @@ -318,7 +236,6 @@ static struct mipi_dsi_driver wuxga_nt_panel_driver = { }, .probe = wuxga_nt_panel_probe, .remove = wuxga_nt_panel_remove, - .shutdown = wuxga_nt_panel_shutdown, }; module_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 2c9c9722734f..dc4bb8ad9131 100644 --- a/drivers/gpu/drm/panel/panel-raspberrypi-touchscreen.c +++ b/drivers/gpu/drm/panel/panel-raspberrypi-touchscreen.c @@ -29,7 +29,7 @@ * DEALINGS IN THE SOFTWARE. */ -/** +/* * Raspberry Pi 7" touchscreen panel driver. * * The 7" touchscreen consists of a DPI LCD panel, a Toshiba @@ -43,19 +43,15 @@ #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/media-bus-format.h> #include <linux/module.h> #include <linux/of.h> -#include <linux/of_device.h> #include <linux/of_graph.h> #include <linux/pm.h> -#include <drm/drm_panel.h> -#include <drm/drmP.h> #include <drm/drm_crtc.h> +#include <drm/drm_device.h> #include <drm/drm_mipi_dsi.h> #include <drm/drm_panel.h> @@ -212,7 +208,6 @@ static const struct drm_display_mode rpi_touchscreen_modes[] = { .vsync_start = 480 + 7, .vsync_end = 480 + 7 + 2, .vtotal = 480 + 7 + 2 + 21, - .vrefresh = 60, }, }; @@ -233,7 +228,7 @@ static void rpi_touchscreen_i2c_write(struct rpi_touchscreen *ts, ret = i2c_smbus_write_byte_data(ts->i2c, reg, val); if (ret) - dev_err(&ts->dsi->dev, "I2C write failed: %d\n", ret); + dev_err(&ts->i2c->dev, "I2C write failed: %d\n", ret); } static int rpi_touchscreen_write(struct rpi_touchscreen *ts, u16 reg, u32 val) @@ -269,7 +264,7 @@ static int rpi_touchscreen_noop(struct drm_panel *panel) return 0; } -static int rpi_touchscreen_enable(struct drm_panel *panel) +static int rpi_touchscreen_prepare(struct drm_panel *panel) { struct rpi_touchscreen *ts = panel_to_ts(panel); int i; @@ -299,6 +294,13 @@ static int rpi_touchscreen_enable(struct drm_panel *panel) rpi_touchscreen_write(ts, DSI_STARTDSI, 0x01); msleep(100); + return 0; +} + +static int rpi_touchscreen_enable(struct drm_panel *panel) +{ + struct rpi_touchscreen *ts = panel_to_ts(panel); + /* Turn on the backlight. */ rpi_touchscreen_i2c_write(ts, REG_PWM, 255); @@ -312,10 +314,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; @@ -323,10 +324,11 @@ 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", - m->hdisplay, m->vdisplay, m->vrefresh); + dev_err(panel->dev, "failed to add mode %ux%u@%u\n", + m->hdisplay, m->vdisplay, + drm_mode_vrefresh(m)); continue; } @@ -353,28 +355,30 @@ static int rpi_touchscreen_get_modes(struct drm_panel *panel) static const struct drm_panel_funcs rpi_touchscreen_funcs = { .disable = rpi_touchscreen_disable, .unprepare = rpi_touchscreen_noop, - .prepare = rpi_touchscreen_noop, + .prepare = rpi_touchscreen_prepare, .enable = rpi_touchscreen_enable, .get_modes = rpi_touchscreen_get_modes, }; -static int rpi_touchscreen_probe(struct i2c_client *i2c, - const struct i2c_device_id *id) +static int rpi_touchscreen_probe(struct i2c_client *i2c) { struct device *dev = &i2c->dev; struct rpi_touchscreen *ts; struct device_node *endpoint, *dsi_host_node; struct mipi_dsi_host *host; - int ret, ver; + int ver; struct mipi_dsi_device_info info = { .type = RPI_DSI_DRIVER_NAME, .channel = 0, .node = NULL, }; - ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL); - if (!ts) - return -ENOMEM; + ts = devm_drm_panel_alloc(dev, __typeof(*ts), base, + &rpi_touchscreen_funcs, + DRM_MODE_CONNECTOR_DSI); + + if (IS_ERR(ts)) + return PTR_ERR(ts); i2c_set_clientdata(i2c, ts); @@ -399,8 +403,14 @@ static int rpi_touchscreen_probe(struct i2c_client *i2c, rpi_touchscreen_i2c_write(ts, REG_POWERON, 0); /* Look up the DSI host. It needs to probe before we do. */ - endpoint = of_graph_get_next_endpoint(dev->of_node, NULL); + endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, -1); + 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) { @@ -409,6 +419,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); @@ -418,20 +431,19 @@ static int rpi_touchscreen_probe(struct i2c_client *i2c, return PTR_ERR(ts->dsi); } - ts->base.dev = dev; - ts->base.funcs = &rpi_touchscreen_funcs; - /* This appears last, as it's what will unblock the DSI host * driver's component bind function. */ - ret = drm_panel_add(&ts->base); - if (ret) - return ret; + drm_panel_add(&ts->base); return 0; + +error: + of_node_put(endpoint); + return -ENODEV; } -static int rpi_touchscreen_remove(struct i2c_client *i2c) +static void rpi_touchscreen_remove(struct i2c_client *i2c) { struct rpi_touchscreen *ts = i2c_get_clientdata(i2c); @@ -440,9 +452,6 @@ static int rpi_touchscreen_remove(struct i2c_client *i2c) drm_panel_remove(&ts->base); mipi_dsi_device_unregister(ts->dsi); - kfree(ts->dsi); - - return 0; } static int rpi_touchscreen_dsi_probe(struct mipi_dsi_device *dsi) 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..2af6aa47a551 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-raydium-rm67191.c @@ -0,0 +1,633 @@ +// 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/media-bus-format.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> + +/* 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_SAMPLE_POSEDGE; + +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; +}; + +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, + .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; + + 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; + + /* + * 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; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + ret = rad_panel_push_cmd_list(dsi); + if (ret < 0) { + dev_err(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) { + dev_err(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) { + dev_err(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) { + dev_err(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) { + dev_err(dev, "Failed to set tear scanline (%d)\n", ret); + goto fail; + } + /* Set pixel format */ + ret = mipi_dsi_dcs_set_pixel_format(dsi, color_format); + dev_dbg(dev, "Interface color format set to 0x%x\n", color_format); + if (ret < 0) { + dev_err(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) { + dev_err(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) { + dev_err(dev, "Failed to set display ON (%d)\n", ret); + goto fail; + } + + backlight_enable(rad->backlight); + + 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; + + 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) { + dev_err(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) { + dev_err(dev, "Failed to enter sleep mode (%d)\n", ret); + return ret; + } + + 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) { + dev_err(panel->dev, "failed to add mode %ux%u@%u\n", + default_mode.hdisplay, default_mode.vdisplay, + drm_mode_vrefresh(&default_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.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_drm_panel_alloc(dev, struct rad_panel, panel, + &rad_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(panel)) + return PTR_ERR(panel); + + 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; + + 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; + + dev_set_drvdata(dev, panel); + + drm_panel_add(&panel->panel); + + ret = mipi_dsi_attach(dsi); + if (ret) + drm_panel_remove(&panel->panel); + + return ret; +} + +static void 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) + dev_err(dev, "Failed to detach from host (%d)\n", ret); + + drm_panel_remove(&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, +}; +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-rm67200.c b/drivers/gpu/drm/panel/panel-raydium-rm67200.c new file mode 100644 index 000000000000..333faed62da7 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-raydium-rm67200.c @@ -0,0 +1,493 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2024 Collabora + +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/property.h> +#include <linux/regulator/consumer.h> + +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +struct raydium_rm67200_panel_info { + struct drm_display_mode mode; + const struct regulator_bulk_data *regulators; + int num_regulators; + void (*panel_setup)(struct mipi_dsi_multi_context *ctx); +}; + +struct raydium_rm67200 { + struct drm_panel panel; + const struct raydium_rm67200_panel_info *panel_info; + struct mipi_dsi_device *dsi; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data *supplies; + int num_supplies; +}; + +static inline struct raydium_rm67200 *to_raydium_rm67200(struct drm_panel *panel) +{ + return container_of(panel, struct raydium_rm67200, panel); +} + +static void raydium_rm67200_reset(struct raydium_rm67200 *ctx) +{ + if (ctx->reset_gpio) { + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + msleep(60); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + msleep(60); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + msleep(60); + } +} + +static void raydium_rm67200_write(struct mipi_dsi_multi_context *ctx, + u8 arg1, u8 arg2) +{ + u8 d[] = { arg1, arg2 }; + + mipi_dsi_generic_write_multi(ctx, d, ARRAY_SIZE(d)); +} + +static void w552793baa_setup(struct mipi_dsi_multi_context *ctx) +{ + raydium_rm67200_write(ctx, 0xfe, 0x21); + raydium_rm67200_write(ctx, 0x04, 0x00); + raydium_rm67200_write(ctx, 0x00, 0x64); + raydium_rm67200_write(ctx, 0x2a, 0x00); + raydium_rm67200_write(ctx, 0x26, 0x64); + raydium_rm67200_write(ctx, 0x54, 0x00); + raydium_rm67200_write(ctx, 0x50, 0x64); + raydium_rm67200_write(ctx, 0x7b, 0x00); + raydium_rm67200_write(ctx, 0x77, 0x64); + raydium_rm67200_write(ctx, 0xa2, 0x00); + raydium_rm67200_write(ctx, 0x9d, 0x64); + raydium_rm67200_write(ctx, 0xc9, 0x00); + raydium_rm67200_write(ctx, 0xc5, 0x64); + raydium_rm67200_write(ctx, 0x01, 0x71); + raydium_rm67200_write(ctx, 0x27, 0x71); + raydium_rm67200_write(ctx, 0x51, 0x71); + raydium_rm67200_write(ctx, 0x78, 0x71); + raydium_rm67200_write(ctx, 0x9e, 0x71); + raydium_rm67200_write(ctx, 0xc6, 0x71); + raydium_rm67200_write(ctx, 0x02, 0x89); + raydium_rm67200_write(ctx, 0x28, 0x89); + raydium_rm67200_write(ctx, 0x52, 0x89); + raydium_rm67200_write(ctx, 0x79, 0x89); + raydium_rm67200_write(ctx, 0x9f, 0x89); + raydium_rm67200_write(ctx, 0xc7, 0x89); + raydium_rm67200_write(ctx, 0x03, 0x9e); + raydium_rm67200_write(ctx, 0x29, 0x9e); + raydium_rm67200_write(ctx, 0x53, 0x9e); + raydium_rm67200_write(ctx, 0x7a, 0x9e); + raydium_rm67200_write(ctx, 0xa0, 0x9e); + raydium_rm67200_write(ctx, 0xc8, 0x9e); + raydium_rm67200_write(ctx, 0x09, 0x00); + raydium_rm67200_write(ctx, 0x05, 0xb0); + raydium_rm67200_write(ctx, 0x31, 0x00); + raydium_rm67200_write(ctx, 0x2b, 0xb0); + raydium_rm67200_write(ctx, 0x5a, 0x00); + raydium_rm67200_write(ctx, 0x55, 0xb0); + raydium_rm67200_write(ctx, 0x80, 0x00); + raydium_rm67200_write(ctx, 0x7c, 0xb0); + raydium_rm67200_write(ctx, 0xa7, 0x00); + raydium_rm67200_write(ctx, 0xa3, 0xb0); + raydium_rm67200_write(ctx, 0xce, 0x00); + raydium_rm67200_write(ctx, 0xca, 0xb0); + raydium_rm67200_write(ctx, 0x06, 0xc0); + raydium_rm67200_write(ctx, 0x2d, 0xc0); + raydium_rm67200_write(ctx, 0x56, 0xc0); + raydium_rm67200_write(ctx, 0x7d, 0xc0); + raydium_rm67200_write(ctx, 0xa4, 0xc0); + raydium_rm67200_write(ctx, 0xcb, 0xc0); + raydium_rm67200_write(ctx, 0x07, 0xcf); + raydium_rm67200_write(ctx, 0x2f, 0xcf); + raydium_rm67200_write(ctx, 0x58, 0xcf); + raydium_rm67200_write(ctx, 0x7e, 0xcf); + raydium_rm67200_write(ctx, 0xa5, 0xcf); + raydium_rm67200_write(ctx, 0xcc, 0xcf); + raydium_rm67200_write(ctx, 0x08, 0xdd); + raydium_rm67200_write(ctx, 0x30, 0xdd); + raydium_rm67200_write(ctx, 0x59, 0xdd); + raydium_rm67200_write(ctx, 0x7f, 0xdd); + raydium_rm67200_write(ctx, 0xa6, 0xdd); + raydium_rm67200_write(ctx, 0xcd, 0xdd); + raydium_rm67200_write(ctx, 0x0e, 0x15); + raydium_rm67200_write(ctx, 0x0a, 0xe9); + raydium_rm67200_write(ctx, 0x36, 0x15); + raydium_rm67200_write(ctx, 0x32, 0xe9); + raydium_rm67200_write(ctx, 0x5f, 0x15); + raydium_rm67200_write(ctx, 0x5b, 0xe9); + raydium_rm67200_write(ctx, 0x85, 0x15); + raydium_rm67200_write(ctx, 0x81, 0xe9); + raydium_rm67200_write(ctx, 0xad, 0x15); + raydium_rm67200_write(ctx, 0xa9, 0xe9); + raydium_rm67200_write(ctx, 0xd3, 0x15); + raydium_rm67200_write(ctx, 0xcf, 0xe9); + raydium_rm67200_write(ctx, 0x0b, 0x14); + raydium_rm67200_write(ctx, 0x33, 0x14); + raydium_rm67200_write(ctx, 0x5c, 0x14); + raydium_rm67200_write(ctx, 0x82, 0x14); + raydium_rm67200_write(ctx, 0xaa, 0x14); + raydium_rm67200_write(ctx, 0xd0, 0x14); + raydium_rm67200_write(ctx, 0x0c, 0x36); + raydium_rm67200_write(ctx, 0x34, 0x36); + raydium_rm67200_write(ctx, 0x5d, 0x36); + raydium_rm67200_write(ctx, 0x83, 0x36); + raydium_rm67200_write(ctx, 0xab, 0x36); + raydium_rm67200_write(ctx, 0xd1, 0x36); + raydium_rm67200_write(ctx, 0x0d, 0x6b); + raydium_rm67200_write(ctx, 0x35, 0x6b); + raydium_rm67200_write(ctx, 0x5e, 0x6b); + raydium_rm67200_write(ctx, 0x84, 0x6b); + raydium_rm67200_write(ctx, 0xac, 0x6b); + raydium_rm67200_write(ctx, 0xd2, 0x6b); + raydium_rm67200_write(ctx, 0x13, 0x5a); + raydium_rm67200_write(ctx, 0x0f, 0x94); + raydium_rm67200_write(ctx, 0x3b, 0x5a); + raydium_rm67200_write(ctx, 0x37, 0x94); + raydium_rm67200_write(ctx, 0x64, 0x5a); + raydium_rm67200_write(ctx, 0x60, 0x94); + raydium_rm67200_write(ctx, 0x8a, 0x5a); + raydium_rm67200_write(ctx, 0x86, 0x94); + raydium_rm67200_write(ctx, 0xb2, 0x5a); + raydium_rm67200_write(ctx, 0xae, 0x94); + raydium_rm67200_write(ctx, 0xd8, 0x5a); + raydium_rm67200_write(ctx, 0xd4, 0x94); + raydium_rm67200_write(ctx, 0x10, 0xd1); + raydium_rm67200_write(ctx, 0x38, 0xd1); + raydium_rm67200_write(ctx, 0x61, 0xd1); + raydium_rm67200_write(ctx, 0x87, 0xd1); + raydium_rm67200_write(ctx, 0xaf, 0xd1); + raydium_rm67200_write(ctx, 0xd5, 0xd1); + raydium_rm67200_write(ctx, 0x11, 0x04); + raydium_rm67200_write(ctx, 0x39, 0x04); + raydium_rm67200_write(ctx, 0x62, 0x04); + raydium_rm67200_write(ctx, 0x88, 0x04); + raydium_rm67200_write(ctx, 0xb0, 0x04); + raydium_rm67200_write(ctx, 0xd6, 0x04); + raydium_rm67200_write(ctx, 0x12, 0x05); + raydium_rm67200_write(ctx, 0x3a, 0x05); + raydium_rm67200_write(ctx, 0x63, 0x05); + raydium_rm67200_write(ctx, 0x89, 0x05); + raydium_rm67200_write(ctx, 0xb1, 0x05); + raydium_rm67200_write(ctx, 0xd7, 0x05); + raydium_rm67200_write(ctx, 0x18, 0xaa); + raydium_rm67200_write(ctx, 0x14, 0x36); + raydium_rm67200_write(ctx, 0x42, 0xaa); + raydium_rm67200_write(ctx, 0x3d, 0x36); + raydium_rm67200_write(ctx, 0x69, 0xaa); + raydium_rm67200_write(ctx, 0x65, 0x36); + raydium_rm67200_write(ctx, 0x8f, 0xaa); + raydium_rm67200_write(ctx, 0x8b, 0x36); + raydium_rm67200_write(ctx, 0xb7, 0xaa); + raydium_rm67200_write(ctx, 0xb3, 0x36); + raydium_rm67200_write(ctx, 0xdd, 0xaa); + raydium_rm67200_write(ctx, 0xd9, 0x36); + raydium_rm67200_write(ctx, 0x15, 0x74); + raydium_rm67200_write(ctx, 0x3f, 0x74); + raydium_rm67200_write(ctx, 0x66, 0x74); + raydium_rm67200_write(ctx, 0x8c, 0x74); + raydium_rm67200_write(ctx, 0xb4, 0x74); + raydium_rm67200_write(ctx, 0xda, 0x74); + raydium_rm67200_write(ctx, 0x16, 0x9f); + raydium_rm67200_write(ctx, 0x40, 0x9f); + raydium_rm67200_write(ctx, 0x67, 0x9f); + raydium_rm67200_write(ctx, 0x8d, 0x9f); + raydium_rm67200_write(ctx, 0xb5, 0x9f); + raydium_rm67200_write(ctx, 0xdb, 0x9f); + raydium_rm67200_write(ctx, 0x17, 0xdc); + raydium_rm67200_write(ctx, 0x41, 0xdc); + raydium_rm67200_write(ctx, 0x68, 0xdc); + raydium_rm67200_write(ctx, 0x8e, 0xdc); + raydium_rm67200_write(ctx, 0xb6, 0xdc); + raydium_rm67200_write(ctx, 0xdc, 0xdc); + raydium_rm67200_write(ctx, 0x1d, 0xff); + raydium_rm67200_write(ctx, 0x19, 0x03); + raydium_rm67200_write(ctx, 0x47, 0xff); + raydium_rm67200_write(ctx, 0x43, 0x03); + raydium_rm67200_write(ctx, 0x6e, 0xff); + raydium_rm67200_write(ctx, 0x6a, 0x03); + raydium_rm67200_write(ctx, 0x94, 0xff); + raydium_rm67200_write(ctx, 0x90, 0x03); + raydium_rm67200_write(ctx, 0xbc, 0xff); + raydium_rm67200_write(ctx, 0xb8, 0x03); + raydium_rm67200_write(ctx, 0xe2, 0xff); + raydium_rm67200_write(ctx, 0xde, 0x03); + raydium_rm67200_write(ctx, 0x1a, 0x35); + raydium_rm67200_write(ctx, 0x44, 0x35); + raydium_rm67200_write(ctx, 0x6b, 0x35); + raydium_rm67200_write(ctx, 0x91, 0x35); + raydium_rm67200_write(ctx, 0xb9, 0x35); + raydium_rm67200_write(ctx, 0xdf, 0x35); + raydium_rm67200_write(ctx, 0x1b, 0x45); + raydium_rm67200_write(ctx, 0x45, 0x45); + raydium_rm67200_write(ctx, 0x6c, 0x45); + raydium_rm67200_write(ctx, 0x92, 0x45); + raydium_rm67200_write(ctx, 0xba, 0x45); + raydium_rm67200_write(ctx, 0xe0, 0x45); + raydium_rm67200_write(ctx, 0x1c, 0x55); + raydium_rm67200_write(ctx, 0x46, 0x55); + raydium_rm67200_write(ctx, 0x6d, 0x55); + raydium_rm67200_write(ctx, 0x93, 0x55); + raydium_rm67200_write(ctx, 0xbb, 0x55); + raydium_rm67200_write(ctx, 0xe1, 0x55); + raydium_rm67200_write(ctx, 0x22, 0xff); + raydium_rm67200_write(ctx, 0x1e, 0x68); + raydium_rm67200_write(ctx, 0x4c, 0xff); + raydium_rm67200_write(ctx, 0x48, 0x68); + raydium_rm67200_write(ctx, 0x73, 0xff); + raydium_rm67200_write(ctx, 0x6f, 0x68); + raydium_rm67200_write(ctx, 0x99, 0xff); + raydium_rm67200_write(ctx, 0x95, 0x68); + raydium_rm67200_write(ctx, 0xc1, 0xff); + raydium_rm67200_write(ctx, 0xbd, 0x68); + raydium_rm67200_write(ctx, 0xe7, 0xff); + raydium_rm67200_write(ctx, 0xe3, 0x68); + raydium_rm67200_write(ctx, 0x1f, 0x7e); + raydium_rm67200_write(ctx, 0x49, 0x7e); + raydium_rm67200_write(ctx, 0x70, 0x7e); + raydium_rm67200_write(ctx, 0x96, 0x7e); + raydium_rm67200_write(ctx, 0xbe, 0x7e); + raydium_rm67200_write(ctx, 0xe4, 0x7e); + raydium_rm67200_write(ctx, 0x20, 0x97); + raydium_rm67200_write(ctx, 0x4a, 0x97); + raydium_rm67200_write(ctx, 0x71, 0x97); + raydium_rm67200_write(ctx, 0x97, 0x97); + raydium_rm67200_write(ctx, 0xbf, 0x97); + raydium_rm67200_write(ctx, 0xe5, 0x97); + raydium_rm67200_write(ctx, 0x21, 0xb5); + raydium_rm67200_write(ctx, 0x4b, 0xb5); + raydium_rm67200_write(ctx, 0x72, 0xb5); + raydium_rm67200_write(ctx, 0x98, 0xb5); + raydium_rm67200_write(ctx, 0xc0, 0xb5); + raydium_rm67200_write(ctx, 0xe6, 0xb5); + raydium_rm67200_write(ctx, 0x25, 0xf0); + raydium_rm67200_write(ctx, 0x23, 0xe8); + raydium_rm67200_write(ctx, 0x4f, 0xf0); + raydium_rm67200_write(ctx, 0x4d, 0xe8); + raydium_rm67200_write(ctx, 0x76, 0xf0); + raydium_rm67200_write(ctx, 0x74, 0xe8); + raydium_rm67200_write(ctx, 0x9c, 0xf0); + raydium_rm67200_write(ctx, 0x9a, 0xe8); + raydium_rm67200_write(ctx, 0xc4, 0xf0); + raydium_rm67200_write(ctx, 0xc2, 0xe8); + raydium_rm67200_write(ctx, 0xea, 0xf0); + raydium_rm67200_write(ctx, 0xe8, 0xe8); + raydium_rm67200_write(ctx, 0x24, 0xff); + raydium_rm67200_write(ctx, 0x4e, 0xff); + raydium_rm67200_write(ctx, 0x75, 0xff); + raydium_rm67200_write(ctx, 0x9b, 0xff); + raydium_rm67200_write(ctx, 0xc3, 0xff); + raydium_rm67200_write(ctx, 0xe9, 0xff); + raydium_rm67200_write(ctx, 0xfe, 0x3d); + raydium_rm67200_write(ctx, 0x00, 0x04); + raydium_rm67200_write(ctx, 0xfe, 0x23); + raydium_rm67200_write(ctx, 0x08, 0x82); + raydium_rm67200_write(ctx, 0x0a, 0x00); + raydium_rm67200_write(ctx, 0x0b, 0x00); + raydium_rm67200_write(ctx, 0x0c, 0x01); + raydium_rm67200_write(ctx, 0x16, 0x00); + raydium_rm67200_write(ctx, 0x18, 0x02); + raydium_rm67200_write(ctx, 0x1b, 0x04); + raydium_rm67200_write(ctx, 0x19, 0x04); + raydium_rm67200_write(ctx, 0x1c, 0x81); + raydium_rm67200_write(ctx, 0x1f, 0x00); + raydium_rm67200_write(ctx, 0x20, 0x03); + raydium_rm67200_write(ctx, 0x23, 0x04); + raydium_rm67200_write(ctx, 0x21, 0x01); + raydium_rm67200_write(ctx, 0x54, 0x63); + raydium_rm67200_write(ctx, 0x55, 0x54); + raydium_rm67200_write(ctx, 0x6e, 0x45); + raydium_rm67200_write(ctx, 0x6d, 0x36); + raydium_rm67200_write(ctx, 0xfe, 0x3d); + raydium_rm67200_write(ctx, 0x55, 0x78); + raydium_rm67200_write(ctx, 0xfe, 0x20); + raydium_rm67200_write(ctx, 0x26, 0x30); + raydium_rm67200_write(ctx, 0xfe, 0x3d); + raydium_rm67200_write(ctx, 0x20, 0x71); + raydium_rm67200_write(ctx, 0x50, 0x8f); + raydium_rm67200_write(ctx, 0x51, 0x8f); + raydium_rm67200_write(ctx, 0xfe, 0x00); + raydium_rm67200_write(ctx, 0x35, 0x00); +} + +static int raydium_rm67200_prepare(struct drm_panel *panel) +{ + struct raydium_rm67200 *ctx = to_raydium_rm67200(panel); + struct mipi_dsi_multi_context mctx = { .dsi = ctx->dsi }; + int ret; + + ret = regulator_bulk_enable(ctx->num_supplies, ctx->supplies); + if (ret < 0) + return ret; + + raydium_rm67200_reset(ctx); + + msleep(60); + + ctx->panel_info->panel_setup(&mctx); + mipi_dsi_dcs_exit_sleep_mode_multi(&mctx); + mipi_dsi_msleep(&mctx, 120); + mipi_dsi_dcs_set_display_on_multi(&mctx); + mipi_dsi_msleep(&mctx, 30); + + return 0; +} + +static int raydium_rm67200_unprepare(struct drm_panel *panel) +{ + struct raydium_rm67200 *ctx = to_raydium_rm67200(panel); + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_bulk_disable(ctx->num_supplies, ctx->supplies); + + msleep(60); + + return 0; +} + +static int raydium_rm67200_disable(struct drm_panel *panel) +{ + struct raydium_rm67200 *rm67200 = to_raydium_rm67200(panel); + struct mipi_dsi_multi_context ctx = { .dsi = rm67200->dsi }; + + mipi_dsi_dcs_set_display_off_multi(&ctx); + mipi_dsi_dcs_enter_sleep_mode_multi(&ctx); + mipi_dsi_msleep(&ctx, 60); + + return ctx.accum_err; +} + +static int raydium_rm67200_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct raydium_rm67200 *ctx = to_raydium_rm67200(panel); + + return drm_connector_helper_get_modes_fixed(connector, &ctx->panel_info->mode); +} + +static const struct drm_panel_funcs raydium_rm67200_funcs = { + .prepare = raydium_rm67200_prepare, + .unprepare = raydium_rm67200_unprepare, + .get_modes = raydium_rm67200_get_modes, + .disable = raydium_rm67200_disable, +}; + +static int raydium_rm67200_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct raydium_rm67200 *ctx; + int ret = 0; + + ctx = devm_drm_panel_alloc(dev, struct raydium_rm67200, panel, + &raydium_rm67200_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ctx->panel_info = device_get_match_data(dev); + if (!ctx->panel_info) + return -EINVAL; + + ctx->num_supplies = ctx->panel_info->num_regulators; + ret = devm_regulator_bulk_get_const(&dsi->dev, + ctx->panel_info->num_regulators, + ctx->panel_info->regulators, + &ctx->supplies); + if (ret < 0) + return ret; + + ctx->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), + "Failed to get reset-gpios\n"); + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + 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; + ctx->panel.prepare_prev_first = true; + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return ret; + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "Failed to attach to DSI host: %d\n", ret); + drm_panel_remove(&ctx->panel); + } + + return ret; +} + +static void raydium_rm67200_remove(struct mipi_dsi_device *dsi) +{ + struct raydium_rm67200 *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "Failed to detach DSI host: %d\n", ret); + + drm_panel_remove(&ctx->panel); +} + +static const struct regulator_bulk_data w552793baa_regulators[] = { + { .supply = "vdd", }, /* 2.8V */ + { .supply = "iovcc", }, /* 1.8V */ + { .supply = "vsp", }, /* +5.5V */ + { .supply = "vsn", }, /* -5.5V */ +}; + +static const struct raydium_rm67200_panel_info w552793baa_info = { + .mode = { + .clock = 132000, + .hdisplay = 1080, + .hsync_start = 1095, + .hsync_end = 1125, + .htotal = 1129, + .vdisplay = 1920, + .vsync_start = 1935, + .vsync_end = 1950, + .vtotal = 1952, + .width_mm = 68, /* 68.04mm */ + .height_mm = 121, /* 120.96mm */ + .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, + .type = DRM_MODE_TYPE_DRIVER, + }, + .regulators = w552793baa_regulators, + .num_regulators = ARRAY_SIZE(w552793baa_regulators), + .panel_setup = w552793baa_setup, +}; + +static const struct of_device_id raydium_rm67200_of_match[] = { + { .compatible = "wanchanglong,w552793baa", .data = &w552793baa_info }, + { /*sentinel*/ } +}; +MODULE_DEVICE_TABLE(of, raydium_rm67200_of_match); + +static struct mipi_dsi_driver raydium_rm67200_driver = { + .probe = raydium_rm67200_probe, + .remove = raydium_rm67200_remove, + .driver = { + .name = "panel-raydium-rm67200", + .of_match_table = raydium_rm67200_of_match, + }, +}; +module_mipi_dsi_driver(raydium_rm67200_driver); + +MODULE_AUTHOR("Sebastian Reichel <sebastian.reichel@collabora.com>"); +MODULE_DESCRIPTION("DRM driver for RM67200-equipped DSI panels"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-raydium-rm68200.c b/drivers/gpu/drm/panel/panel-raydium-rm68200.c index 77593533abcd..669b5f5c1ad9 100644 --- a/drivers/gpu/drm/panel/panel-raydium-rm68200.c +++ b/drivers/gpu/drm/panel/panel-raydium-rm68200.c @@ -6,14 +6,16 @@ * 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> #include <video/mipi_display.h> -#include <drm/drmP.h> #include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> #include <drm/drm_panel.h> /*** Manufacturer Command Set ***/ @@ -75,22 +77,18 @@ struct rm68200 { struct drm_panel panel; struct gpio_desc *reset_gpio; struct regulator *supply; - struct backlight_device *backlight; - bool prepared; - bool enabled; }; static const struct drm_display_mode default_mode = { - .clock = 52582, + .clock = 54000, .hdisplay = 720, - .hsync_start = 720 + 38, - .hsync_end = 720 + 38 + 8, - .htotal = 720 + 38 + 8 + 38, + .hsync_start = 720 + 48, + .hsync_end = 720 + 48 + 9, + .htotal = 720 + 48 + 9 + 48, .vdisplay = 1280, .vsync_start = 1280 + 12, - .vsync_end = 1280 + 12 + 4, - .vtotal = 1280 + 12 + 4 + 12, - .vrefresh = 50, + .vsync_end = 1280 + 12 + 5, + .vtotal = 1280 + 12 + 5 + 12, .flags = 0, .width_mm = 68, .height_mm = 122, @@ -109,8 +107,7 @@ static void rm68200_dcs_write_buf(struct rm68200 *ctx, const void *data, err = mipi_dsi_dcs_write_buffer(dsi, data, len); if (err < 0) - DRM_ERROR_RATELIMITED("MIPI DSI DCS write buffer failed: %d\n", - err); + dev_err_ratelimited(ctx->dev, "MIPI DSI DCS write buffer failed: %d\n", err); } static void rm68200_dcs_write_cmd(struct rm68200 *ctx, u8 cmd, u8 value) @@ -120,7 +117,7 @@ static void rm68200_dcs_write_cmd(struct rm68200 *ctx, u8 cmd, u8 value) err = mipi_dsi_dcs_write(dsi, cmd, &value, 1); if (err < 0) - DRM_ERROR_RATELIMITED("MIPI DSI DCS write failed: %d\n", err); + dev_err_ratelimited(ctx->dev, "MIPI DSI DCS write failed: %d\n", err); } #define dcs_write_seq(ctx, seq...) \ @@ -232,36 +229,19 @@ static void rm68200_init_sequence(struct rm68200 *ctx) dcs_write_seq(ctx, MCS_CMD_MODE_SW, MCS_CMD1_UCS); } -static int rm68200_disable(struct drm_panel *panel) -{ - struct rm68200 *ctx = panel_to_rm68200(panel); - - if (!ctx->enabled) - return 0; - - backlight_disable(ctx->backlight); - - ctx->enabled = false; - - return 0; -} - static int rm68200_unprepare(struct drm_panel *panel) { struct rm68200 *ctx = panel_to_rm68200(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) - DRM_WARN("failed to set display off: %d\n", ret); + dev_warn(panel->dev, "failed to set display off: %d\n", ret); ret = mipi_dsi_dcs_enter_sleep_mode(dsi); if (ret) - DRM_WARN("failed to enter sleep mode: %d\n", ret); + dev_warn(panel->dev, "failed to enter sleep mode: %d\n", ret); msleep(120); @@ -272,8 +252,6 @@ static int rm68200_unprepare(struct drm_panel *panel) regulator_disable(ctx->supply); - ctx->prepared = false; - return 0; } @@ -283,12 +261,9 @@ static int rm68200_prepare(struct drm_panel *panel) struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); int ret; - if (ctx->prepared) - return 0; - ret = regulator_enable(ctx->supply); if (ret < 0) { - DRM_ERROR("failed to enable supply: %d\n", ret); + dev_err(ctx->dev, "failed to enable supply: %d\n", ret); return ret; } @@ -313,53 +288,36 @@ static int rm68200_prepare(struct drm_panel *panel) msleep(20); - ctx->prepared = true; - - return 0; -} - -static int rm68200_enable(struct drm_panel *panel) -{ - struct rm68200 *ctx = panel_to_rm68200(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, - default_mode.vrefresh); + dev_err(panel->dev, "failed to add mode %ux%u@%u\n", + default_mode.hdisplay, default_mode.vdisplay, + drm_mode_vrefresh(&default_mode)); return -ENOMEM; } 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; } static const struct drm_panel_funcs rm68200_drm_funcs = { - .disable = rm68200_disable, .unprepare = rm68200_unprepare, .prepare = rm68200_prepare, - .enable = rm68200_enable, .get_modes = rm68200_get_modes, }; @@ -369,9 +327,11 @@ static int rm68200_probe(struct mipi_dsi_device *dsi) struct rm68200 *ctx; int ret; - ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); - if (!ctx) - return -ENOMEM; + ctx = devm_drm_panel_alloc(dev, struct rm68200, panel, + &rm68200_drm_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); ctx->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(ctx->reset_gpio)) { @@ -383,14 +343,11 @@ static int rm68200_probe(struct mipi_dsi_device *dsi) ctx->supply = devm_regulator_get(dev, "power"); if (IS_ERR(ctx->supply)) { ret = PTR_ERR(ctx->supply); - dev_err(dev, "cannot get regulator: %d\n", ret); + if (ret != -EPROBE_DEFER) + dev_err(dev, "cannot get regulator: %d\n", ret); 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; @@ -398,11 +355,11 @@ static int rm68200_probe(struct mipi_dsi_device *dsi) dsi->lanes = 2; 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_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS; - drm_panel_init(&ctx->panel); - ctx->panel.dev = dev; - ctx->panel.funcs = &rm68200_drm_funcs; + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return ret; drm_panel_add(&ctx->panel); @@ -416,14 +373,12 @@ static int rm68200_probe(struct mipi_dsi_device *dsi) return 0; } -static int rm68200_remove(struct mipi_dsi_device *dsi) +static void rm68200_remove(struct mipi_dsi_device *dsi) { struct rm68200 *ctx = mipi_dsi_get_drvdata(dsi); mipi_dsi_detach(dsi); drm_panel_remove(&ctx->panel); - - return 0; } static const struct of_device_id raydium_rm68200_of_match[] = { diff --git a/drivers/gpu/drm/panel/panel-raydium-rm692e5.c b/drivers/gpu/drm/panel/panel-raydium-rm692e5.c new file mode 100644 index 000000000000..8e9484768657 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-raydium-rm692e5.c @@ -0,0 +1,373 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Generated with linux-mdss-dsi-panel-driver-generator from vendor device tree. + * Copyright (c) 2023 Linaro Limited + */ + +#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 <drm/display/drm_dsc.h> +#include <drm/display/drm_dsc_helper.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +struct rm692e5_panel { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct drm_dsc_config dsc; + struct regulator_bulk_data supplies[3]; + struct gpio_desc *reset_gpio; +}; + +static inline struct rm692e5_panel *to_rm692e5_panel(struct drm_panel *panel) +{ + return container_of(panel, struct rm692e5_panel, panel); +} + +static void rm692e5_reset(struct rm692e5_panel *ctx) +{ + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(10000, 11000); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(5000, 6000); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(10000, 11000); +} + +static void rm692e5_on(struct mipi_dsi_multi_context *dsi_ctx) +{ + dsi_ctx->dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xfe, 0x41); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xd6, 0x00); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xfe, 0x16); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x8a, 0x87); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xfe, 0x71); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x82, 0x01); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xc6, 0x00); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xc7, 0x2c); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xc8, 0x64); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xc9, 0x3c); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xca, 0x80); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xcb, 0x02); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xcc, 0x02); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xfe, 0x38); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x18, 0x13); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xfe, 0xf4); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x00, 0xff); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x01, 0xff); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x02, 0xcf); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x03, 0xbc); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x04, 0xb9); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x05, 0x99); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x06, 0x02); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x07, 0x0a); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x08, 0xe0); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x09, 0x4c); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x0a, 0xeb); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x0b, 0xe8); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x0c, 0x32); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x0d, 0x07); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xfe, 0xf4); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x0d, 0xc0); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x0e, 0xff); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x0f, 0xff); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x10, 0x33); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x11, 0x6f); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x12, 0x6e); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x13, 0xa6); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x14, 0x80); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x15, 0x02); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x16, 0x38); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x17, 0xd3); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x18, 0x3a); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x19, 0xba); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x1a, 0xcc); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x1b, 0x01); + + mipi_dsi_dcs_nop_multi(dsi_ctx); + + mipi_dsi_msleep(dsi_ctx, 32); + + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xfe, 0x38); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x18, 0x13); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xfe, 0xd1); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xd3, 0x00); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xd0, 0x00); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xd2, 0x00); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xd4, 0x00); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xb4, 0x01); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xfe, 0xf9); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x00, 0xaf); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x1d, 0x37); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x44, 0x0a, 0x7b); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xfe, 0x00); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xfa, 0x01); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xc2, 0x08); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x35, 0x00); + mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x51, 0x05, 0x42); + + mipi_dsi_dcs_exit_sleep_mode_multi(dsi_ctx); + mipi_dsi_msleep(dsi_ctx, 100); + mipi_dsi_dcs_set_display_on_multi(dsi_ctx); +} + +static int rm692e5_disable(struct drm_panel *panel) +{ + struct rm692e5_panel *ctx = to_rm692e5_panel(panel); + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xfe, 0x00); + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + + mipi_dsi_msleep(&dsi_ctx, 100); + + return dsi_ctx.accum_err; +} + +static int rm692e5_prepare(struct drm_panel *panel) +{ + struct rm692e5_panel *ctx = to_rm692e5_panel(panel); + struct drm_dsc_picture_parameter_set pps; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; + + dsi_ctx.accum_err = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + if (dsi_ctx.accum_err) + return dsi_ctx.accum_err; + + rm692e5_reset(ctx); + + rm692e5_on(&dsi_ctx); + + drm_dsc_pps_payload_pack(&pps, &ctx->dsc); + + mipi_dsi_picture_parameter_set_multi(&dsi_ctx, &pps); + mipi_dsi_compression_mode_ext_multi(&dsi_ctx, true, MIPI_DSI_COMPRESSION_DSC, 0); + mipi_dsi_msleep(&dsi_ctx, 28); + + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xfe, 0x40); + + /* 0x05 -> 90Hz, 0x00 -> 60Hz */ + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xbd, 0x05); + + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xfe, 0x00); + + if (dsi_ctx.accum_err) { + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + } + + return dsi_ctx.accum_err; +} + +static int rm692e5_unprepare(struct drm_panel *panel) +{ + struct rm692e5_panel *ctx = to_rm692e5_panel(panel); + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + + return 0; +} + +static const struct drm_display_mode rm692e5_mode = { + .clock = (1224 + 32 + 8 + 8) * (2700 + 8 + 2 + 8) * 90 / 1000, + .hdisplay = 1224, + .hsync_start = 1224 + 32, + .hsync_end = 1224 + 32 + 8, + .htotal = 1224 + 32 + 8 + 8, + .vdisplay = 2700, + .vsync_start = 2700 + 8, + .vsync_end = 2700 + 8 + 2, + .vtotal = 2700 + 8 + 2 + 8, + .width_mm = 68, + .height_mm = 150, +}; + +static int rm692e5_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &rm692e5_mode); + if (!mode) + 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 rm692e5_panel_funcs = { + .prepare = rm692e5_prepare, + .unprepare = rm692e5_unprepare, + .disable = rm692e5_disable, + .get_modes = rm692e5_get_modes, +}; + +static int rm692e5_bl_update_status(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + u16 brightness = backlight_get_brightness(bl); + int ret; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + ret = mipi_dsi_dcs_set_display_brightness_large(dsi, brightness); + if (ret < 0) + return ret; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + return 0; +} + +static int rm692e5_bl_get_brightness(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + u16 brightness; + int ret; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + ret = mipi_dsi_dcs_get_display_brightness_large(dsi, &brightness); + if (ret < 0) + return ret; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + return brightness; +} + +static const struct backlight_ops rm692e5_bl_ops = { + .update_status = rm692e5_bl_update_status, + .get_brightness = rm692e5_bl_get_brightness, +}; + +static struct backlight_device * +rm692e5_create_backlight(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + const struct backlight_properties props = { + .type = BACKLIGHT_RAW, + .brightness = 4095, + .max_brightness = 4095, + }; + + return devm_backlight_device_register(dev, dev_name(dev), dev, dsi, + &rm692e5_bl_ops, &props); +} + +static int rm692e5_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct rm692e5_panel *ctx; + int ret; + + ctx = devm_drm_panel_alloc(dev, struct rm692e5_panel, panel, + &rm692e5_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ctx->supplies[0].supply = "vddio"; + ctx->supplies[1].supply = "dvdd"; + ctx->supplies[2].supply = "vci"; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), + ctx->supplies); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get regulators\n"); + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), + "Failed to get reset-gpios\n"); + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_NO_EOT_PACKET | + MIPI_DSI_CLOCK_NON_CONTINUOUS; + + ctx->panel.prepare_prev_first = true; + + ctx->panel.backlight = rm692e5_create_backlight(dsi); + if (IS_ERR(ctx->panel.backlight)) + return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight), + "Failed to create backlight\n"); + + drm_panel_add(&ctx->panel); + + /* This panel only supports DSC; unconditionally enable it */ + dsi->dsc = &ctx->dsc; + + /* TODO: Pass slice_per_pkt = 2 */ + ctx->dsc.dsc_version_major = 1; + ctx->dsc.dsc_version_minor = 1; + ctx->dsc.slice_height = 60; + ctx->dsc.slice_width = 1224; + + ctx->dsc.slice_count = 1224 / ctx->dsc.slice_width; + ctx->dsc.bits_per_component = 8; + ctx->dsc.bits_per_pixel = 8 << 4; /* 4 fractional bits */ + ctx->dsc.block_pred_enable = true; + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "Failed to attach to DSI host: %d\n", ret); + drm_panel_remove(&ctx->panel); + return ret; + } + + return 0; +} + +static void rm692e5_remove(struct mipi_dsi_device *dsi) +{ + struct rm692e5_panel *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id rm692e5_of_match[] = { + { .compatible = "fairphone,fp5-rm692e5-boe" }, + { } +}; +MODULE_DEVICE_TABLE(of, rm692e5_of_match); + +static struct mipi_dsi_driver rm692e5_driver = { + .probe = rm692e5_probe, + .remove = rm692e5_remove, + .driver = { + .name = "panel-rm692e5-boe-amoled", + .of_match_table = rm692e5_of_match, + }, +}; +module_mipi_dsi_driver(rm692e5_driver); + +MODULE_DESCRIPTION("DRM driver for rm692e5-equipped DSI panels"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-raydium-rm69380.c b/drivers/gpu/drm/panel/panel-raydium-rm69380.c new file mode 100644 index 000000000000..86769cadec97 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-raydium-rm69380.c @@ -0,0 +1,309 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Generated with linux-mdss-dsi-panel-driver-generator from vendor device tree. + * Copyright (c) 2024 David Wronek <david@mainlining.org> + */ + +#include <linux/backlight.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_graph.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_probe_helper.h> + +struct rm69380_panel { + struct drm_panel panel; + struct mipi_dsi_device *dsi[2]; + struct regulator_bulk_data supplies[2]; + struct gpio_desc *reset_gpio; +}; + +static inline +struct rm69380_panel *to_rm69380_panel(struct drm_panel *panel) +{ + return container_of(panel, struct rm69380_panel, panel); +} + +static void rm69380_reset(struct rm69380_panel *ctx) +{ + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(15000, 16000); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(10000, 11000); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + msleep(30); +} + +static int rm69380_on(struct rm69380_panel *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi[0]; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + if (ctx->dsi[1]) + ctx->dsi[1]->mode_flags |= MIPI_DSI_MODE_LPM; + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfe, 0xd4); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x00, 0x80); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfe, 0xd0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x48, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfe, 0x26); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x75, 0x3f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1d, 0x1a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfe, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x28); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc2, 0x08); + + mipi_dsi_dcs_set_tear_on_multi(&dsi_ctx, MIPI_DSI_DCS_TEAR_MODE_VBLANK); + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 20); + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 36); + + return dsi_ctx.accum_err; +} + +static void rm69380_off(struct rm69380_panel *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi[0]; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + if (ctx->dsi[1]) + ctx->dsi[1]->mode_flags &= ~MIPI_DSI_MODE_LPM; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 35); + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 20); +} + +static int rm69380_prepare(struct drm_panel *panel) +{ + struct rm69380_panel *ctx = to_rm69380_panel(panel); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + if (ret < 0) + return ret; + + rm69380_reset(ctx); + + ret = rm69380_on(ctx); + if (ret < 0) { + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + } + + return ret; +} + +static int rm69380_unprepare(struct drm_panel *panel) +{ + struct rm69380_panel *ctx = to_rm69380_panel(panel); + + rm69380_off(ctx); + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + + return 0; +} + +static const struct drm_display_mode rm69380_mode = { + .clock = (2560 + 32 + 12 + 38) * (1600 + 20 + 4 + 8) * 90 / 1000, + .hdisplay = 2560, + .hsync_start = 2560 + 32, + .hsync_end = 2560 + 32 + 12, + .htotal = 2560 + 32 + 12 + 38, + .vdisplay = 1600, + .vsync_start = 1600 + 20, + .vsync_end = 1600 + 20 + 4, + .vtotal = 1600 + 20 + 4 + 8, + .width_mm = 248, + .height_mm = 155, + .type = DRM_MODE_TYPE_DRIVER, +}; + +static int rm69380_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + return drm_connector_helper_get_modes_fixed(connector, &rm69380_mode); +} + +static const struct drm_panel_funcs rm69380_panel_funcs = { + .prepare = rm69380_prepare, + .unprepare = rm69380_unprepare, + .get_modes = rm69380_get_modes, +}; + +static int rm69380_bl_update_status(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + u16 brightness = backlight_get_brightness(bl); + int ret; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + ret = mipi_dsi_dcs_set_display_brightness_large(dsi, brightness); + if (ret < 0) + return ret; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + return 0; +} + +static int rm69380_bl_get_brightness(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + u16 brightness; + int ret; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + ret = mipi_dsi_dcs_get_display_brightness_large(dsi, &brightness); + if (ret < 0) + return ret; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + return brightness; +} + +static const struct backlight_ops rm69380_bl_ops = { + .update_status = rm69380_bl_update_status, + .get_brightness = rm69380_bl_get_brightness, +}; + +static struct backlight_device * +rm69380_create_backlight(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + const struct backlight_properties props = { + .type = BACKLIGHT_RAW, + .brightness = 511, + .max_brightness = 2047, + }; + + return devm_backlight_device_register(dev, dev_name(dev), dev, dsi, + &rm69380_bl_ops, &props); +} + +static int rm69380_probe(struct mipi_dsi_device *dsi) +{ + struct mipi_dsi_host *dsi_sec_host; + struct rm69380_panel *ctx; + struct device *dev = &dsi->dev; + struct device_node *dsi_sec; + int ret, i; + + ctx = devm_drm_panel_alloc(dev, struct rm69380_panel, panel, + &rm69380_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ctx->supplies[0].supply = "vddio"; + ctx->supplies[1].supply = "avdd"; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), + ctx->supplies); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get regulators\n"); + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), + "Failed to get reset-gpios\n"); + + dsi_sec = of_graph_get_remote_node(dsi->dev.of_node, 1, -1); + + if (dsi_sec) { + const struct mipi_dsi_device_info info = { "RM69380 DSI1", 0, + dsi_sec }; + + dsi_sec_host = of_find_mipi_dsi_host_by_node(dsi_sec); + of_node_put(dsi_sec); + if (!dsi_sec_host) + return dev_err_probe(dev, -EPROBE_DEFER, + "Cannot get secondary DSI host\n"); + + ctx->dsi[1] = + devm_mipi_dsi_device_register_full(dev, dsi_sec_host, &info); + if (IS_ERR(ctx->dsi[1])) + return dev_err_probe(dev, PTR_ERR(ctx->dsi[1]), + "Cannot get secondary DSI node\n"); + + mipi_dsi_set_drvdata(ctx->dsi[1], ctx); + } + + ctx->dsi[0] = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + ctx->panel.prepare_prev_first = true; + + ctx->panel.backlight = rm69380_create_backlight(dsi); + if (IS_ERR(ctx->panel.backlight)) + return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight), + "Failed to create backlight\n"); + + drm_panel_add(&ctx->panel); + + for (i = 0; i < ARRAY_SIZE(ctx->dsi); i++) { + if (!ctx->dsi[i]) + continue; + + dev_dbg(&ctx->dsi[i]->dev, "Binding DSI %d\n", i); + + ctx->dsi[i]->lanes = 4; + ctx->dsi[i]->format = MIPI_DSI_FMT_RGB888; + ctx->dsi[i]->mode_flags = MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_CLOCK_NON_CONTINUOUS; + + ret = devm_mipi_dsi_attach(dev, ctx->dsi[i]); + if (ret < 0) { + drm_panel_remove(&ctx->panel); + return dev_err_probe(dev, ret, + "Failed to attach to DSI%d\n", i); + } + } + + return 0; +} + +static void rm69380_remove(struct mipi_dsi_device *dsi) +{ + struct rm69380_panel *ctx = mipi_dsi_get_drvdata(dsi); + + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id rm69380_of_match[] = { + { .compatible = "lenovo,j716f-edo-rm69380" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rm69380_of_match); + +static struct mipi_dsi_driver rm69380_panel_driver = { + .probe = rm69380_probe, + .remove = rm69380_remove, + .driver = { + .name = "panel-raydium-rm69380", + .of_match_table = rm69380_of_match, + }, +}; +module_mipi_dsi_driver(rm69380_panel_driver); + +MODULE_AUTHOR("David Wronek <david@mainlining.org"); +MODULE_DESCRIPTION("DRM driver for Raydium RM69380-equipped DSI panels"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-renesas-r61307.c b/drivers/gpu/drm/panel/panel-renesas-r61307.c new file mode 100644 index 000000000000..319415194839 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-renesas-r61307.c @@ -0,0 +1,325 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/array_size.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/property.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> + +#define R61307_MACP 0xb0 /* Manufacturer CMD Protect */ +#define R61307_MACP_ON 0x03 +#define R61307_MACP_OFF 0x04 + +#define R61307_INVERSION 0xc1 +#define R61307_GAMMA_SET_A 0xc8 /* Gamma Setting A */ +#define R61307_GAMMA_SET_B 0xc9 /* Gamma Setting B */ +#define R61307_GAMMA_SET_C 0xca /* Gamma Setting C */ +#define R61307_CONTRAST_SET 0xcc + +struct renesas_r61307 { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + + struct regulator *vcc_supply; + struct regulator *iovcc_supply; + + struct gpio_desc *reset_gpio; + + bool prepared; + + bool dig_cont_adj; + bool inversion; + u32 gamma; +}; + +static const u8 gamma_setting[][25] = { + { /* sentinel */ }, + { + R61307_GAMMA_SET_A, + 0x00, 0x06, 0x0a, 0x0f, + 0x14, 0x1f, 0x1f, 0x17, + 0x12, 0x0c, 0x09, 0x06, + 0x00, 0x06, 0x0a, 0x0f, + 0x14, 0x1f, 0x1f, 0x17, + 0x12, 0x0c, 0x09, 0x06 + }, + { + R61307_GAMMA_SET_A, + 0x00, 0x05, 0x0b, 0x0f, + 0x11, 0x1d, 0x20, 0x18, + 0x18, 0x09, 0x07, 0x06, + 0x00, 0x05, 0x0b, 0x0f, + 0x11, 0x1d, 0x20, 0x18, + 0x18, 0x09, 0x07, 0x06 + }, + { + R61307_GAMMA_SET_A, + 0x0b, 0x0d, 0x10, 0x14, + 0x13, 0x1d, 0x20, 0x18, + 0x12, 0x09, 0x07, 0x06, + 0x0a, 0x0c, 0x10, 0x14, + 0x13, 0x1d, 0x20, 0x18, + 0x12, 0x09, 0x07, 0x06 + }, +}; + +static inline struct renesas_r61307 *to_renesas_r61307(struct drm_panel *panel) +{ + return container_of(panel, struct renesas_r61307, panel); +} + +static void renesas_r61307_reset(struct renesas_r61307 *priv) +{ + gpiod_set_value_cansleep(priv->reset_gpio, 1); + usleep_range(10000, 11000); + gpiod_set_value_cansleep(priv->reset_gpio, 0); + usleep_range(2000, 3000); +} + +static int renesas_r61307_prepare(struct drm_panel *panel) +{ + struct renesas_r61307 *priv = to_renesas_r61307(panel); + struct device *dev = &priv->dsi->dev; + int ret; + + if (priv->prepared) + return 0; + + ret = regulator_enable(priv->vcc_supply); + if (ret) { + dev_err(dev, "failed to enable vcc power supply\n"); + return ret; + } + + usleep_range(2000, 3000); + + ret = regulator_enable(priv->iovcc_supply); + if (ret) { + dev_err(dev, "failed to enable iovcc power supply\n"); + return ret; + } + + usleep_range(2000, 3000); + + renesas_r61307_reset(priv); + + priv->prepared = true; + return 0; +} + +static int renesas_r61307_enable(struct drm_panel *panel) +{ + struct renesas_r61307 *priv = to_renesas_r61307(panel); + struct mipi_dsi_multi_context ctx = { .dsi = priv->dsi }; + + mipi_dsi_dcs_exit_sleep_mode_multi(&ctx); + mipi_dsi_msleep(&ctx, 80); + + mipi_dsi_dcs_write_seq_multi(&ctx, MIPI_DCS_SET_ADDRESS_MODE, 0x00); + mipi_dsi_msleep(&ctx, 20); + + mipi_dsi_dcs_set_pixel_format_multi(&ctx, MIPI_DCS_PIXEL_FMT_24BIT << 4); + + /* MACP Off */ + mipi_dsi_generic_write_seq_multi(&ctx, R61307_MACP, R61307_MACP_OFF); + + if (priv->dig_cont_adj) + mipi_dsi_generic_write_seq_multi(&ctx, R61307_CONTRAST_SET, + 0xdc, 0xb4, 0xff); + + if (priv->gamma) + mipi_dsi_generic_write_multi(&ctx, gamma_setting[priv->gamma], + sizeof(gamma_setting[priv->gamma])); + + if (priv->inversion) + mipi_dsi_generic_write_seq_multi(&ctx, R61307_INVERSION, + 0x00, 0x50, 0x03, 0x22, + 0x16, 0x06, 0x60, 0x11); + else + mipi_dsi_generic_write_seq_multi(&ctx, R61307_INVERSION, + 0x00, 0x10, 0x03, 0x22, + 0x16, 0x06, 0x60, 0x01); + + /* MACP On */ + mipi_dsi_generic_write_seq_multi(&ctx, R61307_MACP, R61307_MACP_ON); + + mipi_dsi_dcs_set_display_on_multi(&ctx); + mipi_dsi_msleep(&ctx, 50); + + return 0; +} + +static int renesas_r61307_disable(struct drm_panel *panel) +{ + struct renesas_r61307 *priv = to_renesas_r61307(panel); + struct mipi_dsi_multi_context ctx = { .dsi = priv->dsi }; + + mipi_dsi_dcs_set_display_off_multi(&ctx); + mipi_dsi_msleep(&ctx, 100); + mipi_dsi_dcs_enter_sleep_mode_multi(&ctx); + + return 0; +} + +static int renesas_r61307_unprepare(struct drm_panel *panel) +{ + struct renesas_r61307 *priv = to_renesas_r61307(panel); + + if (!priv->prepared) + return 0; + + usleep_range(10000, 11000); + + gpiod_set_value_cansleep(priv->reset_gpio, 1); + usleep_range(5000, 6000); + + regulator_disable(priv->iovcc_supply); + usleep_range(2000, 3000); + regulator_disable(priv->vcc_supply); + + priv->prepared = false; + return 0; +} + +static const struct drm_display_mode renesas_r61307_mode = { + .clock = (768 + 116 + 81 + 5) * (1024 + 24 + 8 + 2) * 60 / 1000, + .hdisplay = 768, + .hsync_start = 768 + 116, + .hsync_end = 768 + 116 + 81, + .htotal = 768 + 116 + 81 + 5, + .vdisplay = 1024, + .vsync_start = 1024 + 24, + .vsync_end = 1024 + 24 + 8, + .vtotal = 1024 + 24 + 8 + 2, + .width_mm = 76, + .height_mm = 101, +}; + +static int renesas_r61307_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &renesas_r61307_mode); + if (!mode) + 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 renesas_r61307_panel_funcs = { + .prepare = renesas_r61307_prepare, + .enable = renesas_r61307_enable, + .disable = renesas_r61307_disable, + .unprepare = renesas_r61307_unprepare, + .get_modes = renesas_r61307_get_modes, +}; + +static int renesas_r61307_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct renesas_r61307 *priv; + int ret; + + priv = devm_drm_panel_alloc(dev, struct renesas_r61307, panel, + &renesas_r61307_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(priv)) + return PTR_ERR(priv); + + priv->vcc_supply = devm_regulator_get(dev, "vcc"); + if (IS_ERR(priv->vcc_supply)) + return dev_err_probe(dev, PTR_ERR(priv->vcc_supply), + "Failed to get vcc-supply\n"); + + priv->iovcc_supply = devm_regulator_get(dev, "iovcc"); + if (IS_ERR(priv->iovcc_supply)) + return dev_err_probe(dev, PTR_ERR(priv->iovcc_supply), + "Failed to get iovcc-supply\n"); + + priv->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(priv->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(priv->reset_gpio), + "Failed to get reset gpios\n"); + + if (device_property_read_bool(dev, "renesas,inversion")) + priv->inversion = true; + + if (device_property_read_bool(dev, "renesas,contrast")) + priv->dig_cont_adj = true; + + priv->gamma = 0; + device_property_read_u32(dev, "renesas,gamma", &priv->gamma); + + priv->dsi = dsi; + mipi_dsi_set_drvdata(dsi, priv); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_CLOCK_NON_CONTINUOUS | MIPI_DSI_MODE_LPM; + + ret = drm_panel_of_backlight(&priv->panel); + if (ret) + return dev_err_probe(dev, ret, "Failed to get backlight\n"); + + drm_panel_add(&priv->panel); + + ret = mipi_dsi_attach(dsi); + if (ret) { + drm_panel_remove(&priv->panel); + return dev_err_probe(dev, ret, "Failed to attach to DSI host\n"); + } + + return 0; +} + +static void renesas_r61307_remove(struct mipi_dsi_device *dsi) +{ + struct renesas_r61307 *priv = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret) + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&priv->panel); +} + +static const struct of_device_id renesas_r61307_of_match[] = { + { .compatible = "hit,tx13d100vm0eaa" }, + { .compatible = "koe,tx13d100vm0eaa" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, renesas_r61307_of_match); + +static struct mipi_dsi_driver renesas_r61307_driver = { + .probe = renesas_r61307_probe, + .remove = renesas_r61307_remove, + .driver = { + .name = "panel-renesas-r61307", + .of_match_table = renesas_r61307_of_match, + }, +}; +module_mipi_dsi_driver(renesas_r61307_driver); + +MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>"); +MODULE_DESCRIPTION("Renesas R61307-based panel driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-renesas-r69328.c b/drivers/gpu/drm/panel/panel-renesas-r69328.c new file mode 100644 index 000000000000..46287ab04c30 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-renesas-r69328.c @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/array_size.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/property.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> + +#define R69328_MACP 0xb0 /* Manufacturer Access CMD Protect */ +#define R69328_MACP_ON 0x03 +#define R69328_MACP_OFF 0x04 + +#define R69328_GAMMA_SET_A 0xc8 /* Gamma Setting A */ +#define R69328_GAMMA_SET_B 0xc9 /* Gamma Setting B */ +#define R69328_GAMMA_SET_C 0xca /* Gamma Setting C */ + +#define R69328_POWER_SET 0xd1 + +struct renesas_r69328 { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + + struct regulator *vdd_supply; + struct regulator *vddio_supply; + struct gpio_desc *reset_gpio; + + bool prepared; +}; + +static inline struct renesas_r69328 *to_renesas_r69328(struct drm_panel *panel) +{ + return container_of(panel, struct renesas_r69328, panel); +} + +static void renesas_r69328_reset(struct renesas_r69328 *priv) +{ + gpiod_set_value_cansleep(priv->reset_gpio, 1); + usleep_range(10000, 11000); + gpiod_set_value_cansleep(priv->reset_gpio, 0); + usleep_range(2000, 3000); +} + +static int renesas_r69328_prepare(struct drm_panel *panel) +{ + struct renesas_r69328 *priv = to_renesas_r69328(panel); + struct device *dev = &priv->dsi->dev; + int ret; + + if (priv->prepared) + return 0; + + ret = regulator_enable(priv->vdd_supply); + if (ret) { + dev_err(dev, "failed to enable vdd power supply\n"); + return ret; + } + + usleep_range(10000, 11000); + + ret = regulator_enable(priv->vddio_supply); + if (ret < 0) { + dev_err(dev, "failed to enable vddio power supply\n"); + return ret; + } + + usleep_range(10000, 11000); + + renesas_r69328_reset(priv); + + priv->prepared = true; + return 0; +} + +static int renesas_r69328_enable(struct drm_panel *panel) +{ + struct renesas_r69328 *priv = to_renesas_r69328(panel); + struct mipi_dsi_multi_context ctx = { .dsi = priv->dsi }; + + /* Set address mode */ + mipi_dsi_dcs_write_seq_multi(&ctx, MIPI_DCS_SET_ADDRESS_MODE, 0x00); + mipi_dsi_dcs_set_pixel_format_multi(&ctx, MIPI_DCS_PIXEL_FMT_24BIT << 4); + mipi_dsi_dcs_exit_sleep_mode_multi(&ctx); + + mipi_dsi_msleep(&ctx, 100); + + /* MACP Off */ + mipi_dsi_generic_write_seq_multi(&ctx, R69328_MACP, R69328_MACP_OFF); + + mipi_dsi_generic_write_seq_multi(&ctx, R69328_POWER_SET, 0x14, 0x1d, + 0x21, 0x67, 0x11, 0x9a); + + mipi_dsi_generic_write_seq_multi(&ctx, R69328_GAMMA_SET_A, 0x00, 0x1a, + 0x20, 0x28, 0x25, 0x24, 0x26, 0x15, 0x13, + 0x11, 0x18, 0x1e, 0x1c, 0x00, 0x00, 0x1a, + 0x20, 0x28, 0x25, 0x24, 0x26, 0x15, 0x13, + 0x11, 0x18, 0x1e, 0x1c, 0x00); + + mipi_dsi_generic_write_seq_multi(&ctx, R69328_GAMMA_SET_B, 0x00, 0x1a, + 0x20, 0x28, 0x25, 0x24, 0x26, 0x15, 0x13, + 0x11, 0x18, 0x1e, 0x1c, 0x00, 0x00, 0x1a, + 0x20, 0x28, 0x25, 0x24, 0x26, 0x15, 0x13, + 0x11, 0x18, 0x1e, 0x1c, 0x00); + + mipi_dsi_generic_write_seq_multi(&ctx, R69328_GAMMA_SET_C, 0x00, 0x1a, + 0x20, 0x28, 0x25, 0x24, 0x26, 0x15, 0x13, + 0x11, 0x18, 0x1e, 0x1c, 0x00, 0x00, 0x1a, + 0x20, 0x28, 0x25, 0x24, 0x26, 0x15, 0x13, + 0x11, 0x18, 0x1e, 0x1c, 0x00); + + /* MACP On */ + mipi_dsi_generic_write_seq_multi(&ctx, R69328_MACP, R69328_MACP_ON); + + mipi_dsi_dcs_set_display_on_multi(&ctx); + mipi_dsi_msleep(&ctx, 50); + + return 0; +} + +static int renesas_r69328_disable(struct drm_panel *panel) +{ + struct renesas_r69328 *priv = to_renesas_r69328(panel); + struct mipi_dsi_multi_context ctx = { .dsi = priv->dsi }; + + mipi_dsi_dcs_set_display_off_multi(&ctx); + mipi_dsi_msleep(&ctx, 60); + mipi_dsi_dcs_enter_sleep_mode_multi(&ctx); + + return 0; +} + +static int renesas_r69328_unprepare(struct drm_panel *panel) +{ + struct renesas_r69328 *priv = to_renesas_r69328(panel); + + if (!priv->prepared) + return 0; + + gpiod_set_value_cansleep(priv->reset_gpio, 1); + + usleep_range(5000, 6000); + + regulator_disable(priv->vddio_supply); + regulator_disable(priv->vdd_supply); + + priv->prepared = false; + return 0; +} + +static const struct drm_display_mode renesas_r69328_mode = { + .clock = (720 + 92 + 62 + 4) * (1280 + 6 + 3 + 1) * 60 / 1000, + .hdisplay = 720, + .hsync_start = 720 + 92, + .hsync_end = 720 + 92 + 62, + .htotal = 720 + 92 + 62 + 4, + .vdisplay = 1280, + .vsync_start = 1280 + 6, + .vsync_end = 1280 + 6 + 3, + .vtotal = 1280 + 6 + 3 + 1, + .width_mm = 59, + .height_mm = 105, +}; + +static int renesas_r69328_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &renesas_r69328_mode); + if (!mode) + 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 renesas_r69328_panel_funcs = { + .prepare = renesas_r69328_prepare, + .enable = renesas_r69328_enable, + .disable = renesas_r69328_disable, + .unprepare = renesas_r69328_unprepare, + .get_modes = renesas_r69328_get_modes, +}; + +static int renesas_r69328_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct renesas_r69328 *priv; + int ret; + + priv = devm_drm_panel_alloc(dev, struct renesas_r69328, panel, + &renesas_r69328_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(priv)) + return PTR_ERR(priv); + + priv->vdd_supply = devm_regulator_get(dev, "vdd"); + if (IS_ERR(priv->vdd_supply)) + return dev_err_probe(dev, PTR_ERR(priv->vdd_supply), + "Failed to get vdd-supply\n"); + + priv->vddio_supply = devm_regulator_get(dev, "vddio"); + if (IS_ERR(priv->vddio_supply)) + return dev_err_probe(dev, PTR_ERR(priv->vddio_supply), + "Failed to get vddio-supply\n"); + + priv->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(priv->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(priv->reset_gpio), + "Failed to get reset-gpios\n"); + + priv->dsi = dsi; + mipi_dsi_set_drvdata(dsi, priv); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_CLOCK_NON_CONTINUOUS | MIPI_DSI_MODE_LPM; + + ret = drm_panel_of_backlight(&priv->panel); + if (ret) + return dev_err_probe(dev, ret, "Failed to get backlight\n"); + + drm_panel_add(&priv->panel); + + ret = mipi_dsi_attach(dsi); + if (ret) { + drm_panel_remove(&priv->panel); + return dev_err_probe(dev, ret, "Failed to attach to DSI host\n"); + } + + return 0; +} + +static void renesas_r69328_remove(struct mipi_dsi_device *dsi) +{ + struct renesas_r69328 *priv = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret) + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&priv->panel); +} + +static const struct of_device_id renesas_r69328_of_match[] = { + { .compatible = "jdi,dx12d100vm0eaa" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, renesas_r69328_of_match); + +static struct mipi_dsi_driver renesas_r69328_driver = { + .probe = renesas_r69328_probe, + .remove = renesas_r69328_remove, + .driver = { + .name = "panel-renesas-r69328", + .of_match_table = renesas_r69328_of_match, + }, +}; +module_mipi_dsi_driver(renesas_r69328_driver); + +MODULE_AUTHOR("Maxim Schwalm <maxim.schwalm@gmail.com>"); +MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>"); +MODULE_DESCRIPTION("Renesas R69328-based panel driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-ronbo-rb070d30.c b/drivers/gpu/drm/panel/panel-ronbo-rb070d30.c new file mode 100644 index 000000000000..c3fbc459c7e0 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-ronbo-rb070d30.c @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018-2019, Bridge Systems BV + * Copyright (C) 2018-2019, Bootlin + * Copyright (C) 2017, Free Electrons + * + * This file based on panel-ilitek-ili9881c.c + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/media-bus-format.h> +#include <linux/module.h> +#include <linux/of.h> + +#include <linux/gpio/consumer.h> +#include <linux/regulator/consumer.h> + +#include <drm/drm_connector.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +struct rb070d30_panel { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct regulator *supply; + + struct { + struct gpio_desc *power; + struct gpio_desc *reset; + struct gpio_desc *updn; + struct gpio_desc *shlr; + } gpios; +}; + +static inline struct rb070d30_panel *panel_to_rb070d30_panel(struct drm_panel *panel) +{ + return container_of(panel, struct rb070d30_panel, panel); +} + +static int rb070d30_panel_prepare(struct drm_panel *panel) +{ + struct rb070d30_panel *ctx = panel_to_rb070d30_panel(panel); + int ret; + + ret = regulator_enable(ctx->supply); + if (ret < 0) { + dev_err(&ctx->dsi->dev, "Failed to enable supply: %d\n", ret); + return ret; + } + + msleep(20); + gpiod_set_value_cansleep(ctx->gpios.power, 1); + msleep(20); + gpiod_set_value_cansleep(ctx->gpios.reset, 1); + msleep(20); + return 0; +} + +static int rb070d30_panel_unprepare(struct drm_panel *panel) +{ + struct rb070d30_panel *ctx = panel_to_rb070d30_panel(panel); + + gpiod_set_value_cansleep(ctx->gpios.reset, 0); + gpiod_set_value_cansleep(ctx->gpios.power, 0); + regulator_disable(ctx->supply); + + return 0; +} + +static int rb070d30_panel_enable(struct drm_panel *panel) +{ + struct rb070d30_panel *ctx = panel_to_rb070d30_panel(panel); + + return mipi_dsi_dcs_exit_sleep_mode(ctx->dsi); +} + +static int rb070d30_panel_disable(struct drm_panel *panel) +{ + struct rb070d30_panel *ctx = panel_to_rb070d30_panel(panel); + + return mipi_dsi_dcs_enter_sleep_mode(ctx->dsi); +} + +/* Default timings */ +static const struct drm_display_mode default_mode = { + .clock = 51206, + .hdisplay = 1024, + .hsync_start = 1024 + 160, + .hsync_end = 1024 + 160 + 80, + .htotal = 1024 + 160 + 80 + 80, + .vdisplay = 600, + .vsync_start = 600 + 12, + .vsync_end = 600 + 12 + 10, + .vtotal = 600 + 12 + 10 + 13, + + .width_mm = 154, + .height_mm = 85, +}; + +static int rb070d30_panel_get_modes(struct drm_panel *panel, + struct drm_connector *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(connector->dev, &default_mode); + if (!mode) { + dev_err(&ctx->dsi->dev, "Failed to add mode " DRM_MODE_FMT "\n", + DRM_MODE_ARG(&default_mode)); + return -EINVAL; + } + + 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 = mode->width_mm; + connector->display_info.height_mm = mode->height_mm; + drm_display_info_set_bus_formats(&connector->display_info, + &bus_format, 1); + + return 1; +} + +static const struct drm_panel_funcs rb070d30_panel_funcs = { + .get_modes = rb070d30_panel_get_modes, + .prepare = rb070d30_panel_prepare, + .enable = rb070d30_panel_enable, + .disable = rb070d30_panel_disable, + .unprepare = rb070d30_panel_unprepare, +}; + +static int rb070d30_panel_dsi_probe(struct mipi_dsi_device *dsi) +{ + struct rb070d30_panel *ctx; + int ret; + + ctx = devm_drm_panel_alloc(&dsi->dev, struct rb070d30_panel, panel, + &rb070d30_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ctx->supply = devm_regulator_get(&dsi->dev, "vcc-lcd"); + if (IS_ERR(ctx->supply)) + return PTR_ERR(ctx->supply); + + mipi_dsi_set_drvdata(dsi, ctx); + ctx->dsi = dsi; + + ctx->gpios.reset = devm_gpiod_get(&dsi->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->gpios.reset)) { + dev_err(&dsi->dev, "Couldn't get our reset GPIO\n"); + return PTR_ERR(ctx->gpios.reset); + } + + ctx->gpios.power = devm_gpiod_get(&dsi->dev, "power", GPIOD_OUT_LOW); + if (IS_ERR(ctx->gpios.power)) { + dev_err(&dsi->dev, "Couldn't get our power GPIO\n"); + return PTR_ERR(ctx->gpios.power); + } + + /* + * We don't change the state of that GPIO later on but we need + * to force it into a low state. + */ + ctx->gpios.updn = devm_gpiod_get(&dsi->dev, "updn", GPIOD_OUT_LOW); + if (IS_ERR(ctx->gpios.updn)) { + dev_err(&dsi->dev, "Couldn't get our updn GPIO\n"); + return PTR_ERR(ctx->gpios.updn); + } + + /* + * We don't change the state of that GPIO later on but we need + * to force it into a low state. + */ + ctx->gpios.shlr = devm_gpiod_get(&dsi->dev, "shlr", GPIOD_OUT_LOW); + if (IS_ERR(ctx->gpios.shlr)) { + dev_err(&dsi->dev, "Couldn't get our shlr GPIO\n"); + return PTR_ERR(ctx->gpios.shlr); + } + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return ret; + + drm_panel_add(&ctx->panel); + + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | MIPI_DSI_MODE_LPM; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->lanes = 4; + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + drm_panel_remove(&ctx->panel); + return ret; + } + + return 0; +} + +static void rb070d30_panel_dsi_remove(struct mipi_dsi_device *dsi) +{ + struct rb070d30_panel *ctx = mipi_dsi_get_drvdata(dsi); + + mipi_dsi_detach(dsi); + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id rb070d30_panel_of_match[] = { + { .compatible = "ronbo,rb070d30" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, rb070d30_panel_of_match); + +static struct mipi_dsi_driver rb070d30_panel_driver = { + .probe = rb070d30_panel_dsi_probe, + .remove = rb070d30_panel_dsi_remove, + .driver = { + .name = "panel-ronbo-rb070d30", + .of_match_table = rb070d30_panel_of_match, + }, +}; +module_mipi_dsi_driver(rb070d30_panel_driver); + +MODULE_AUTHOR("Boris Brezillon <boris.brezillon@bootlin.com>"); +MODULE_AUTHOR("Konstantin Sudakov <k.sudakov@integrasources.com>"); +MODULE_DESCRIPTION("Ronbo RB070D30 Panel Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-samsung-ams581vf01.c b/drivers/gpu/drm/panel/panel-samsung-ams581vf01.c new file mode 100644 index 000000000000..188dd7cf0297 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-samsung-ams581vf01.c @@ -0,0 +1,283 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2024, Danila Tikhonov <danila@jiaxyga.com> + */ + +#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_probe_helper.h> + +/* Manufacturer Command Set */ +#define MCS_ACCESS_PROT_OFF 0xb0 +#define MCS_PASSWD 0xf0 + +struct ams581vf01 { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data *supplies; +}; + +static const struct regulator_bulk_data ams581vf01_supplies[] = { + { .supply = "vdd3p3" }, + { .supply = "vddio" }, + { .supply = "vsn" }, + { .supply = "vsp" }, +}; + +static inline struct ams581vf01 *to_ams581vf01(struct drm_panel *panel) +{ + return container_of(panel, struct ams581vf01, panel); +} + +static void ams581vf01_reset(struct ams581vf01 *ctx) +{ + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(10000, 11000); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(10000, 11000); +} + +static int ams581vf01_on(struct ams581vf01 *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + /* Sleep Out, Wait 10ms */ + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + usleep_range(10000, 11000); + + /* TE On */ + mipi_dsi_dcs_set_tear_on_multi(&dsi_ctx, MIPI_DSI_DCS_TEAR_MODE_VBLANK); + + /* MIC Setting */ + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_PASSWD, 0x5a, 0x5a); /* Unlock */ + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xeb, 0x17, + 0x41, 0x92, + 0x0e, 0x10, + 0x82, 0x5a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_PASSWD, 0xa5, 0xa5); /* Lock */ + + /* Column & Page Address Setting */ + mipi_dsi_dcs_set_column_address_multi(&dsi_ctx, 0x0000, 0x0437); + mipi_dsi_dcs_set_page_address_multi(&dsi_ctx, 0x0000, 0x0923); + + /* Brightness Setting */ + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x20); + + /* Horizontal & Vertical sync Setting */ + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_PASSWD, 0x5a, 0x5a); /* Unlock */ + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_ACCESS_PROT_OFF, 0x09); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe8, 0x11, 0x30); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_PASSWD, 0xa5, 0xa5); /* Lock */ + mipi_dsi_msleep(&dsi_ctx, 110); + + /* Display On */ + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + + return dsi_ctx.accum_err; +} + +static void ams581vf01_off(struct ams581vf01 *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + /* Display Off & Sleep In */ + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 20); + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + + /* VCI operating mode change */ + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_PASSWD, 0x5a, 0x5a); /* Unlock */ + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_ACCESS_PROT_OFF, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf4, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_PASSWD, 0xa5, 0xa5); /* Lock */ + + mipi_dsi_msleep(&dsi_ctx, 120); +} + +static int ams581vf01_prepare(struct drm_panel *panel) +{ + struct ams581vf01 *ctx = to_ams581vf01(panel); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(ams581vf01_supplies), + ctx->supplies); + if (ret < 0) + return ret; + + ams581vf01_reset(ctx); + + ret = ams581vf01_on(ctx); + if (ret < 0) { + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(ams581vf01_supplies), + ctx->supplies); + return ret; + } + + return 0; +} + +static int ams581vf01_unprepare(struct drm_panel *panel) +{ + struct ams581vf01 *ctx = to_ams581vf01(panel); + + ams581vf01_off(ctx); + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(ams581vf01_supplies), + ctx->supplies); + + return 0; +} + +static const struct drm_display_mode ams581vf01_mode = { + .clock = (1080 + 32 + 73 + 98) * (2340 + 8 + 1 + 8) * 60 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 32, + .hsync_end = 1080 + 32 + 73, + .htotal = 1080 + 32 + 73 + 98, + .vdisplay = 2340, + .vsync_start = 2340 + 8, + .vsync_end = 2340 + 8 + 1, + .vtotal = 2340 + 8 + 1 + 8, + .width_mm = 62, + .height_mm = 134, + .type = DRM_MODE_TYPE_DRIVER, +}; + +static int ams581vf01_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + return drm_connector_helper_get_modes_fixed(connector, &ams581vf01_mode); +} + +static const struct drm_panel_funcs ams581vf01_panel_funcs = { + .prepare = ams581vf01_prepare, + .unprepare = ams581vf01_unprepare, + .get_modes = ams581vf01_get_modes, +}; + +static int ams581vf01_bl_update_status(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + u16 brightness = backlight_get_brightness(bl); + int ret; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + ret = mipi_dsi_dcs_set_display_brightness_large(dsi, brightness); + if (ret < 0) + return ret; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + return 0; +} + +static const struct backlight_ops ams581vf01_bl_ops = { + .update_status = ams581vf01_bl_update_status, +}; + +static struct backlight_device * +ams581vf01_create_backlight(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + const struct backlight_properties props = { + .type = BACKLIGHT_RAW, + .brightness = 511, + .max_brightness = 1023, + }; + + return devm_backlight_device_register(dev, dev_name(dev), dev, dsi, + &ams581vf01_bl_ops, &props); +} + +static int ams581vf01_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct ams581vf01 *ctx; + int ret; + + ctx = devm_drm_panel_alloc(&dsi->dev, struct ams581vf01, panel, + &ams581vf01_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ret = devm_regulator_bulk_get_const(&dsi->dev, + ARRAY_SIZE(ams581vf01_supplies), + ams581vf01_supplies, + &ctx->supplies); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get regulators\n"); + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), + "Failed to get reset-gpios\n"); + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_CLOCK_NON_CONTINUOUS | MIPI_DSI_MODE_LPM; + + ctx->panel.prepare_prev_first = true; + + ctx->panel.backlight = ams581vf01_create_backlight(dsi); + if (IS_ERR(ctx->panel.backlight)) + return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight), + "Failed to create backlight\n"); + + drm_panel_add(&ctx->panel); + + ret = devm_mipi_dsi_attach(dev, dsi); + if (ret < 0) { + drm_panel_remove(&ctx->panel); + return dev_err_probe(dev, ret, "Failed to attach to DSI host\n"); + } + + return 0; +} + +static void ams581vf01_remove(struct mipi_dsi_device *dsi) +{ + struct ams581vf01 *ctx = mipi_dsi_get_drvdata(dsi); + + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id ams581vf01_of_match[] = { + { .compatible = "samsung,ams581vf01" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ams581vf01_of_match); + +static struct mipi_dsi_driver ams581vf01_driver = { + .probe = ams581vf01_probe, + .remove = ams581vf01_remove, + .driver = { + .name = "panel-samsung-ams581vf01", + .of_match_table = ams581vf01_of_match, + }, +}; +module_mipi_dsi_driver(ams581vf01_driver); + +MODULE_AUTHOR("Danila Tikhonov <danila@jiaxyga.com>"); +MODULE_DESCRIPTION("DRM driver for SAMSUNG AMS581VF01 cmd mode dsi panel"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-samsung-ams639rq08.c b/drivers/gpu/drm/panel/panel-samsung-ams639rq08.c new file mode 100644 index 000000000000..f8ebbd4a530b --- /dev/null +++ b/drivers/gpu/drm/panel/panel-samsung-ams639rq08.c @@ -0,0 +1,329 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2024, Danila Tikhonov <danila@jiaxyga.com> + */ + +#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_probe_helper.h> + +/* Manufacturer Command Set */ +#define MCS_ACCESS_PROT_OFF 0xb0 +#define MCS_UNKNOWN_B7 0xb7 +#define MCS_BIAS_CURRENT_CTRL 0xd1 +#define MCS_PASSWD1 0xf0 +#define MCS_PASSWD2 0xfc +#define MCS_UNKNOWN_FF 0xff + +struct ams639rq08 { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data *supplies; +}; + +static const struct regulator_bulk_data ams639rq08_supplies[] = { + { .supply = "vdd3p3" }, + { .supply = "vddio" }, + { .supply = "vsn" }, + { .supply = "vsp" }, +}; + +static inline struct ams639rq08 *to_ams639rq08(struct drm_panel *panel) +{ + return container_of(panel, struct ams639rq08, panel); +} + +static void ams639rq08_reset(struct ams639rq08 *ctx) +{ + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(1000, 2000); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(10000, 11000); +} + +static int ams639rq08_on(struct ams639rq08 *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + /* Delay 2ms for VCI1 power */ + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_PASSWD1, 0x5a, 0x5a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_PASSWD2, 0x5a, 0x5a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_ACCESS_PROT_OFF, 0x0c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_UNKNOWN_FF, 0x10); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_ACCESS_PROT_OFF, 0x2f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_BIAS_CURRENT_CTRL, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_PASSWD1, 0xa5, 0xa5); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_PASSWD2, 0xa5, 0xa5); + + /* Sleep Out */ + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + usleep_range(10000, 11000); + + /* TE OUT (Vsync On) */ + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_PASSWD1, 0x5a, 0x5a); + + mipi_dsi_dcs_set_tear_on_multi(&dsi_ctx, MIPI_DSI_DCS_TEAR_MODE_VBLANK); + + /* DBV Smooth Transition */ + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_UNKNOWN_B7, 0x01, 0x4b); + + /* Edge Dimming Speed Setting */ + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_ACCESS_PROT_OFF, 0x06); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_UNKNOWN_B7, 0x10); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_PASSWD1, 0xa5, 0xa5); + + /* Page Address Set */ + mipi_dsi_dcs_set_page_address_multi(&dsi_ctx, 0x0000, 0x0923); + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_PASSWD1, 0x5a, 0x5a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_PASSWD2, 0x5a, 0x5a); + + /* Set DDIC internal HFP */ + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_ACCESS_PROT_OFF, 0x23); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_BIAS_CURRENT_CTRL, 0x11); + + /* OFC Setting 84.1 Mhz */ + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe9, 0x11, 0x55, + 0xa6, 0x75, 0xa3, + 0xb9, 0xa1, 0x4a, + 0x00, 0x1a, 0xb8); + + /* Err_FG Setting */ + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe1, + 0x00, 0x00, 0x02, + 0x02, 0x42, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe2, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_ACCESS_PROT_OFF, 0x0c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe1, 0x19); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_PASSWD1, 0xa5, 0xa5); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_PASSWD2, 0xa5, 0xa5); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x20); + + /* Brightness Control */ + mipi_dsi_dcs_set_display_brightness_multi(&dsi_ctx, 0x0000); + + /* Display On */ + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_POWER_SAVE, 0x00); + mipi_dsi_msleep(&dsi_ctx, 67); + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + + return dsi_ctx.accum_err; +} + +static void ams639rq08_off(struct ams639rq08 *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 120); +} + +static int ams639rq08_prepare(struct drm_panel *panel) +{ + struct ams639rq08 *ctx = to_ams639rq08(panel); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(ams639rq08_supplies), + ctx->supplies); + if (ret < 0) + return ret; + + ams639rq08_reset(ctx); + + ret = ams639rq08_on(ctx); + if (ret < 0) { + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(ams639rq08_supplies), + ctx->supplies); + return ret; + } + + return 0; +} + +static int ams639rq08_unprepare(struct drm_panel *panel) +{ + struct ams639rq08 *ctx = to_ams639rq08(panel); + + ams639rq08_off(ctx); + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(ams639rq08_supplies), + ctx->supplies); + + return 0; +} + +static const struct drm_display_mode ams639rq08_mode = { + .clock = (1080 + 64 + 20 + 64) * (2340 + 64 + 20 + 64) * 60 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 64, + .hsync_end = 1080 + 64 + 20, + .htotal = 1080 + 64 + 20 + 64, + .vdisplay = 2340, + .vsync_start = 2340 + 64, + .vsync_end = 2340 + 64 + 20, + .vtotal = 2340 + 64 + 20 + 64, + .width_mm = 68, + .height_mm = 147, + .type = DRM_MODE_TYPE_DRIVER, +}; + +static int ams639rq08_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + return drm_connector_helper_get_modes_fixed(connector, &ams639rq08_mode); +} + +static const struct drm_panel_funcs ams639rq08_panel_funcs = { + .prepare = ams639rq08_prepare, + .unprepare = ams639rq08_unprepare, + .get_modes = ams639rq08_get_modes, +}; + +static int ams639rq08_bl_update_status(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + u16 brightness = backlight_get_brightness(bl); + int ret; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + ret = mipi_dsi_dcs_set_display_brightness_large(dsi, brightness); + if (ret < 0) + return ret; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + return 0; +} + +static int ams639rq08_bl_get_brightness(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + u16 brightness; + int ret; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + ret = mipi_dsi_dcs_get_display_brightness_large(dsi, &brightness); + if (ret < 0) + return ret; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + return brightness; +} + +static const struct backlight_ops ams639rq08_bl_ops = { + .update_status = ams639rq08_bl_update_status, + .get_brightness = ams639rq08_bl_get_brightness, +}; + +static struct backlight_device * +ams639rq08_create_backlight(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + const struct backlight_properties props = { + .type = BACKLIGHT_RAW, + .brightness = 1023, + .max_brightness = 2047, + }; + + return devm_backlight_device_register(dev, dev_name(dev), dev, dsi, + &ams639rq08_bl_ops, &props); +} + +static int ams639rq08_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct ams639rq08 *ctx; + int ret; + + ctx = devm_drm_panel_alloc(dev, struct ams639rq08, panel, + &ams639rq08_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ret = devm_regulator_bulk_get_const(&dsi->dev, + ARRAY_SIZE(ams639rq08_supplies), + ams639rq08_supplies, + &ctx->supplies); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get regulators\n"); + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), + "Failed to get reset-gpios\n"); + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_CLOCK_NON_CONTINUOUS | MIPI_DSI_MODE_LPM; + + ctx->panel.prepare_prev_first = true; + + ctx->panel.backlight = ams639rq08_create_backlight(dsi); + if (IS_ERR(ctx->panel.backlight)) + return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight), + "Failed to create backlight\n"); + + drm_panel_add(&ctx->panel); + + ret = devm_mipi_dsi_attach(dev, dsi); + if (ret < 0) { + drm_panel_remove(&ctx->panel); + return dev_err_probe(dev, ret, "Failed to attach to DSI host\n"); + } + + return 0; +} + +static void ams639rq08_remove(struct mipi_dsi_device *dsi) +{ + struct ams639rq08 *ctx = mipi_dsi_get_drvdata(dsi); + + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id ams639rq08_of_match[] = { + { .compatible = "samsung,ams639rq08" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ams639rq08_of_match); + +static struct mipi_dsi_driver ams639rq08_driver = { + .probe = ams639rq08_probe, + .remove = ams639rq08_remove, + .driver = { + .name = "panel-samsung-ams639rq08", + .of_match_table = ams639rq08_of_match, + }, +}; +module_mipi_dsi_driver(ams639rq08_driver); + +MODULE_AUTHOR("Danila Tikhonov <danila@jiaxyga.com>"); +MODULE_DESCRIPTION("DRM driver for SAMSUNG AMS639RQ08 cmd mode dsi panel"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-samsung-atna33xc20.c b/drivers/gpu/drm/panel/panel-samsung-atna33xc20.c new file mode 100644 index 000000000000..20ec27d2d6c2 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-samsung-atna33xc20.c @@ -0,0 +1,370 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2021 Google Inc. + * + * Panel driver for the Samsung ATNA33XC20 panel. This panel can't be handled + * by the DRM_PANEL_SIMPLE driver because its power sequencing is non-standard. + */ + +#include <linux/backlight.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> + +#include <drm/display/drm_dp_aux_bus.h> +#include <drm/display/drm_dp_helper.h> +#include <drm/drm_edid.h> +#include <drm/drm_panel.h> + +/* T3 VCC to HPD high is max 200 ms */ +#define HPD_MAX_MS 200 +#define HPD_MAX_US (HPD_MAX_MS * 1000) + +struct atana33xc20_panel { + struct drm_panel base; + bool el3_was_on; + + bool no_hpd; + struct gpio_desc *hpd_gpio; + + struct regulator *supply; + struct gpio_desc *el_on3_gpio; + struct drm_dp_aux *aux; + + const struct drm_edid *drm_edid; + + ktime_t powered_off_time; + ktime_t powered_on_time; + ktime_t el_on3_off_time; +}; + +static inline struct atana33xc20_panel *to_atana33xc20(struct drm_panel *panel) +{ + return container_of(panel, struct atana33xc20_panel, base); +} + +static void atana33xc20_wait(ktime_t start_ktime, unsigned int min_ms) +{ + ktime_t now_ktime, min_ktime; + + min_ktime = ktime_add(start_ktime, ms_to_ktime(min_ms)); + now_ktime = ktime_get_boottime(); + + if (ktime_before(now_ktime, min_ktime)) + msleep(ktime_to_ms(ktime_sub(min_ktime, now_ktime)) + 1); +} + +static int atana33xc20_suspend(struct device *dev) +{ + struct atana33xc20_panel *p = dev_get_drvdata(dev); + int ret; + + /* + * Note 3 (Example of power off sequence in detail) in spec + * specifies to wait 150 ms after deasserting EL3_ON before + * powering off. + */ + if (p->el3_was_on) + atana33xc20_wait(p->el_on3_off_time, 150); + + drm_dp_dpcd_set_powered(p->aux, false); + ret = regulator_disable(p->supply); + if (ret) + return ret; + p->powered_off_time = ktime_get_boottime(); + p->el3_was_on = false; + + return 0; +} + +static int atana33xc20_resume(struct device *dev) +{ + struct atana33xc20_panel *p = dev_get_drvdata(dev); + int hpd_asserted; + int ret; + + /* T12 (Power off time) is min 500 ms */ + atana33xc20_wait(p->powered_off_time, 500); + + ret = regulator_enable(p->supply); + if (ret) + return ret; + drm_dp_dpcd_set_powered(p->aux, true); + p->powered_on_time = ktime_get_boottime(); + + if (p->no_hpd) { + msleep(HPD_MAX_MS); + return 0; + } + + if (p->hpd_gpio) { + ret = readx_poll_timeout(gpiod_get_value_cansleep, p->hpd_gpio, + hpd_asserted, hpd_asserted, + 1000, HPD_MAX_US); + if (hpd_asserted < 0) + ret = hpd_asserted; + + if (ret) { + dev_warn(dev, "Error waiting for HPD GPIO: %d\n", ret); + goto error; + } + } else if (p->aux->wait_hpd_asserted) { + ret = p->aux->wait_hpd_asserted(p->aux, HPD_MAX_US); + + if (ret) { + dev_warn(dev, "Controller error waiting for HPD: %d\n", ret); + goto error; + } + } + + /* + * Note that it's possible that no_hpd is false, hpd_gpio is + * NULL, and wait_hpd_asserted is NULL. This is because + * wait_hpd_asserted() is optional even if HPD is hooked up to + * a dedicated pin on the eDP controller. In this case we just + * assume that the controller driver will wait for HPD at the + * right times. + */ + return 0; + +error: + drm_dp_dpcd_set_powered(p->aux, false); + regulator_disable(p->supply); + + return ret; +} + +static int atana33xc20_disable(struct drm_panel *panel) +{ + struct atana33xc20_panel *p = to_atana33xc20(panel); + + gpiod_set_value_cansleep(p->el_on3_gpio, 0); + p->el_on3_off_time = ktime_get_boottime(); + + /* + * Keep track of the fact that EL_ON3 was on but we haven't power + * cycled yet. This lets us know that "el_on3_off_time" is recent (we + * don't need to worry about ktime wraparounds) and also makes it + * obvious if we try to enable again without a power cycle (see the + * warning in atana33xc20_enable()). + */ + p->el3_was_on = true; + + /* + * Sleeping 20 ms here (after setting the GPIO) avoids a glitch when + * powering off. + */ + msleep(20); + + return 0; +} + +static int atana33xc20_enable(struct drm_panel *panel) +{ + struct atana33xc20_panel *p = to_atana33xc20(panel); + + /* + * Once EL_ON3 drops we absolutely need a power cycle before the next + * enable or the backlight will never come on again. The code ensures + * this because disable() is _always_ followed by unprepare() and + * unprepare() forces a suspend with pm_runtime_put_sync_suspend(), + * but let's track just to make sure since the requirement is so + * non-obvious. + */ + if (WARN_ON(p->el3_was_on)) + return -EIO; + + /* + * Note 2 (Example of power on sequence in detail) in spec specifies + * to wait 400 ms after powering on before asserting EL3_on. + */ + atana33xc20_wait(p->powered_on_time, 400); + + gpiod_set_value_cansleep(p->el_on3_gpio, 1); + + return 0; +} + +static int atana33xc20_unprepare(struct drm_panel *panel) +{ + int ret; + + /* + * Purposely do a put_sync, don't use autosuspend. The panel's tcon + * seems to sometimes crash when you stop giving it data and this is + * the best way to ensure it will come back. + * + * NOTE: we still want autosuspend for cases where we only turn on + * to get the EDID or otherwise send DP AUX commands to the panel. + */ + ret = pm_runtime_put_sync_suspend(panel->dev); + if (ret < 0) + return ret; + + return 0; +} + +static int atana33xc20_prepare(struct drm_panel *panel) +{ + int ret; + + ret = pm_runtime_get_sync(panel->dev); + if (ret < 0) { + pm_runtime_put_autosuspend(panel->dev); + return ret; + } + + return 0; +} + +static int atana33xc20_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct atana33xc20_panel *p = to_atana33xc20(panel); + struct dp_aux_ep_device *aux_ep = to_dp_aux_ep_dev(panel->dev); + int num = 0; + + pm_runtime_get_sync(panel->dev); + + if (!p->drm_edid) + p->drm_edid = drm_edid_read_ddc(connector, &aux_ep->aux->ddc); + + drm_edid_connector_update(connector, p->drm_edid); + + num = drm_edid_connector_add_modes(connector); + + pm_runtime_mark_last_busy(panel->dev); + pm_runtime_put_autosuspend(panel->dev); + + return num; +} + +static const struct drm_panel_funcs atana33xc20_funcs = { + .disable = atana33xc20_disable, + .enable = atana33xc20_enable, + .unprepare = atana33xc20_unprepare, + .prepare = atana33xc20_prepare, + .get_modes = atana33xc20_get_modes, +}; + +static void atana33xc20_runtime_disable(void *data) +{ + pm_runtime_disable(data); +} + +static void atana33xc20_dont_use_autosuspend(void *data) +{ + pm_runtime_dont_use_autosuspend(data); +} + +static int atana33xc20_probe(struct dp_aux_ep_device *aux_ep) +{ + struct atana33xc20_panel *panel; + struct device *dev = &aux_ep->dev; + int ret; + + panel = devm_drm_panel_alloc(dev, struct atana33xc20_panel, base, + &atana33xc20_funcs, + DRM_MODE_CONNECTOR_eDP); + if (IS_ERR(panel)) + return PTR_ERR(panel); + + dev_set_drvdata(dev, panel); + + panel->aux = aux_ep->aux; + + panel->supply = devm_regulator_get(dev, "power"); + if (IS_ERR(panel->supply)) + return dev_err_probe(dev, PTR_ERR(panel->supply), + "Failed to get power supply\n"); + + panel->el_on3_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(panel->el_on3_gpio)) + return dev_err_probe(dev, PTR_ERR(panel->el_on3_gpio), + "Failed to get enable GPIO\n"); + + panel->no_hpd = of_property_read_bool(dev->of_node, "no-hpd"); + if (!panel->no_hpd) { + panel->hpd_gpio = devm_gpiod_get_optional(dev, "hpd", GPIOD_IN); + if (IS_ERR(panel->hpd_gpio)) + return dev_err_probe(dev, PTR_ERR(panel->hpd_gpio), + "Failed to get HPD GPIO\n"); + } + + pm_runtime_enable(dev); + ret = devm_add_action_or_reset(dev, atana33xc20_runtime_disable, dev); + if (ret) + return ret; + pm_runtime_set_autosuspend_delay(dev, 2000); + pm_runtime_use_autosuspend(dev); + ret = devm_add_action_or_reset(dev, atana33xc20_dont_use_autosuspend, dev); + if (ret) + return ret; + + pm_runtime_get_sync(dev); + ret = drm_panel_dp_aux_backlight(&panel->base, aux_ep->aux); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + /* + * Warn if we get an error, but don't consider it fatal. Having + * a panel where we can't control the backlight is better than + * no panel. + */ + if (ret) + dev_warn(dev, "failed to register dp aux backlight: %d\n", ret); + + drm_panel_add(&panel->base); + + return 0; +} + +static void atana33xc20_remove(struct dp_aux_ep_device *aux_ep) +{ + struct device *dev = &aux_ep->dev; + struct atana33xc20_panel *panel = dev_get_drvdata(dev); + + drm_panel_remove(&panel->base); + + drm_edid_free(panel->drm_edid); +} + +static const struct of_device_id atana33xc20_dt_match[] = { + { .compatible = "samsung,atna33xc20", }, + { /* sentinal */ } +}; +MODULE_DEVICE_TABLE(of, atana33xc20_dt_match); + +static const struct dev_pm_ops atana33xc20_pm_ops = { + SET_RUNTIME_PM_OPS(atana33xc20_suspend, atana33xc20_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + +static struct dp_aux_ep_driver atana33xc20_driver = { + .driver = { + .name = "samsung_atana33xc20", + .of_match_table = atana33xc20_dt_match, + .pm = &atana33xc20_pm_ops, + }, + .probe = atana33xc20_probe, + .remove = atana33xc20_remove, +}; + +static int __init atana33xc20_init(void) +{ + return dp_aux_dp_driver_register(&atana33xc20_driver); +} +module_init(atana33xc20_init); + +static void __exit atana33xc20_exit(void) +{ + dp_aux_dp_driver_unregister(&atana33xc20_driver); +} +module_exit(atana33xc20_exit); + +MODULE_DESCRIPTION("Samsung ATANA33XC20 Panel Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-samsung-db7430.c b/drivers/gpu/drm/panel/panel-samsung-db7430.c new file mode 100644 index 000000000000..a97182f3c990 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-samsung-db7430.c @@ -0,0 +1,348 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Panel driver for the Samsung LMS397KF04 480x800 DPI RGB panel. + * According to the data sheet the display controller is called DB7430. + * Found in the Samsung Galaxy Beam GT-I8350 mobile phone. + * Linus Walleij <linus.walleij@linaro.org> + */ +#include <drm/drm_mipi_dbi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/media-bus-format.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regulator/consumer.h> +#include <linux/spi/spi.h> + +#include <video/mipi_display.h> + +#define DB7430_ACCESS_PROT_OFF 0xb0 +#define DB7430_UNKNOWN_B4 0xb4 +#define DB7430_USER_SELECT 0xb5 +#define DB7430_UNKNOWN_B7 0xb7 +#define DB7430_UNKNOWN_B8 0xb8 +#define DB7430_PANEL_DRIVING 0xc0 +#define DB7430_SOURCE_CONTROL 0xc1 +#define DB7430_GATE_INTERFACE 0xc4 +#define DB7430_DISPLAY_H_TIMING 0xc5 +#define DB7430_RGB_SYNC_OPTION 0xc6 +#define DB7430_GAMMA_SET_RED 0xc8 +#define DB7430_GAMMA_SET_GREEN 0xc9 +#define DB7430_GAMMA_SET_BLUE 0xca +#define DB7430_BIAS_CURRENT_CTRL 0xd1 +#define DB7430_DDV_CTRL 0xd2 +#define DB7430_GAMMA_CTRL_REF 0xd3 +#define DB7430_UNKNOWN_D4 0xd4 +#define DB7430_DCDC_CTRL 0xd5 +#define DB7430_VCL_CTRL 0xd6 +#define DB7430_UNKNOWN_F8 0xf8 +#define DB7430_UNKNOWN_FC 0xfc + +#define DATA_MASK 0x100 + +/** + * struct db7430 - state container for a panel controlled by the DB7430 + * controller + */ +struct db7430 { + /** @dev: the container device */ + struct device *dev; + /** @dbi: the DBI bus abstraction handle */ + struct mipi_dbi dbi; + /** @panel: the DRM panel instance for this device */ + struct drm_panel panel; + /** @reset: reset GPIO line */ + struct gpio_desc *reset; + /** @regulators: VCCIO and VIO supply regulators */ + struct regulator_bulk_data regulators[2]; +}; + +static const struct drm_display_mode db7430_480_800_mode = { + /* + * 31 ns period min (htotal*vtotal*vrefresh)/1000 + * gives a Vrefresh of ~71 Hz. + */ + .clock = 32258, + .hdisplay = 480, + .hsync_start = 480 + 10, + .hsync_end = 480 + 10 + 4, + .htotal = 480 + 10 + 4 + 40, + .vdisplay = 800, + .vsync_start = 800 + 6, + .vsync_end = 800 + 6 + 1, + .vtotal = 800 + 6 + 1 + 7, + .width_mm = 53, + .height_mm = 87, + .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, +}; + +static inline struct db7430 *to_db7430(struct drm_panel *panel) +{ + return container_of(panel, struct db7430, panel); +} + +static int db7430_power_on(struct db7430 *db) +{ + struct mipi_dbi *dbi = &db->dbi; + int ret; + + /* Power up */ + ret = regulator_bulk_enable(ARRAY_SIZE(db->regulators), + db->regulators); + if (ret) { + dev_err(db->dev, "failed to enable regulators: %d\n", ret); + return ret; + } + msleep(50); + + /* Assert reset >=1 ms */ + gpiod_set_value_cansleep(db->reset, 1); + usleep_range(1000, 5000); + /* De-assert reset */ + gpiod_set_value_cansleep(db->reset, 0); + /* Wait >= 10 ms */ + msleep(10); + dev_dbg(db->dev, "de-asserted RESET\n"); + + /* + * This is set to 0x0a (RGB/BGR order + horizontal flip) in order + * to make the display behave normally. If this is not set the displays + * normal output behaviour is horizontally flipped and BGR ordered. Do + * it twice because the first message doesn't always "take". + */ + mipi_dbi_command(dbi, MIPI_DCS_SET_ADDRESS_MODE, 0x0a); + mipi_dbi_command(dbi, MIPI_DCS_SET_ADDRESS_MODE, 0x0a); + mipi_dbi_command(dbi, DB7430_ACCESS_PROT_OFF, 0x00); + mipi_dbi_command(dbi, DB7430_PANEL_DRIVING, 0x28, 0x08); + mipi_dbi_command(dbi, DB7430_SOURCE_CONTROL, + 0x01, 0x30, 0x15, 0x05, 0x22); + mipi_dbi_command(dbi, DB7430_GATE_INTERFACE, + 0x10, 0x01, 0x00); + mipi_dbi_command(dbi, DB7430_DISPLAY_H_TIMING, + 0x06, 0x55, 0x03, 0x07, 0x0b, + 0x33, 0x00, 0x01, 0x03); + /* + * 0x00 in datasheet 0x01 in vendor code 0x00, it seems 0x01 means + * DE active high and 0x00 means DE active low. + */ + mipi_dbi_command(dbi, DB7430_RGB_SYNC_OPTION, 0x01); + mipi_dbi_command(dbi, DB7430_GAMMA_SET_RED, + /* R positive gamma */ 0x00, + 0x0A, 0x31, 0x3B, 0x4E, 0x58, 0x59, 0x5B, 0x58, 0x5E, 0x62, + 0x60, 0x61, 0x5E, 0x62, 0x55, 0x55, 0x7F, 0x08, + /* R negative gamma */ 0x00, + 0x0A, 0x31, 0x3B, 0x4E, 0x58, 0x59, 0x5B, 0x58, 0x5E, 0x62, + 0x60, 0x61, 0x5E, 0x62, 0x55, 0x55, 0x7F, 0x08); + mipi_dbi_command(dbi, DB7430_GAMMA_SET_GREEN, + /* G positive gamma */ 0x00, + 0x25, 0x15, 0x28, 0x3D, 0x4A, 0x48, 0x4C, 0x4A, 0x52, 0x59, + 0x59, 0x5B, 0x56, 0x60, 0x5D, 0x55, 0x7F, 0x0A, + /* G negative gamma */ 0x00, + 0x25, 0x15, 0x28, 0x3D, 0x4A, 0x48, 0x4C, 0x4A, 0x52, 0x59, + 0x59, 0x5B, 0x56, 0x60, 0x5D, 0x55, 0x7F, 0x0A); + mipi_dbi_command(dbi, DB7430_GAMMA_SET_BLUE, + /* B positive gamma */ 0x00, + 0x48, 0x10, 0x1F, 0x2F, 0x35, 0x38, 0x3D, 0x3C, 0x45, 0x4D, + 0x4E, 0x52, 0x51, 0x60, 0x7F, 0x7E, 0x7F, 0x0C, + /* B negative gamma */ 0x00, + 0x48, 0x10, 0x1F, 0x2F, 0x35, 0x38, 0x3D, 0x3C, 0x45, 0x4D, + 0x4E, 0x52, 0x51, 0x60, 0x7F, 0x7E, 0x7F, 0x0C); + mipi_dbi_command(dbi, DB7430_BIAS_CURRENT_CTRL, 0x33, 0x13); + mipi_dbi_command(dbi, DB7430_DDV_CTRL, 0x11, 0x00, 0x00); + mipi_dbi_command(dbi, DB7430_GAMMA_CTRL_REF, 0x50, 0x50); + mipi_dbi_command(dbi, DB7430_DCDC_CTRL, 0x2f, 0x11, 0x1e, 0x46); + mipi_dbi_command(dbi, DB7430_VCL_CTRL, 0x11, 0x0a); + + return 0; +} + +static int db7430_power_off(struct db7430 *db) +{ + /* Go into RESET and disable regulators */ + gpiod_set_value_cansleep(db->reset, 1); + return regulator_bulk_disable(ARRAY_SIZE(db->regulators), + db->regulators); +} + +static int db7430_unprepare(struct drm_panel *panel) +{ + return db7430_power_off(to_db7430(panel)); +} + +static int db7430_disable(struct drm_panel *panel) +{ + struct db7430 *db = to_db7430(panel); + struct mipi_dbi *dbi = &db->dbi; + + mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_OFF); + msleep(25); + mipi_dbi_command(dbi, MIPI_DCS_ENTER_SLEEP_MODE); + msleep(120); + + return 0; +} + +static int db7430_prepare(struct drm_panel *panel) +{ + return db7430_power_on(to_db7430(panel)); +} + +static int db7430_enable(struct drm_panel *panel) +{ + struct db7430 *db = to_db7430(panel); + struct mipi_dbi *dbi = &db->dbi; + + /* Exit sleep mode */ + mipi_dbi_command(dbi, MIPI_DCS_EXIT_SLEEP_MODE); + msleep(20); + + /* NVM (non-volatile memory) load sequence */ + mipi_dbi_command(dbi, DB7430_UNKNOWN_D4, 0x52, 0x5e); + mipi_dbi_command(dbi, DB7430_UNKNOWN_F8, 0x01, 0xf5, 0xf2, 0x71, 0x44); + mipi_dbi_command(dbi, DB7430_UNKNOWN_FC, 0x00, 0x08); + msleep(150); + + /* CABC turn on sequence (BC = backlight control) */ + mipi_dbi_command(dbi, DB7430_UNKNOWN_B4, 0x0f, 0x00, 0x50); + mipi_dbi_command(dbi, DB7430_USER_SELECT, 0x80); + mipi_dbi_command(dbi, DB7430_UNKNOWN_B7, 0x24); + mipi_dbi_command(dbi, DB7430_UNKNOWN_B8, 0x01); + + /* Turn on display */ + mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_ON); + + return 0; +} + +/** + * db7430_get_modes() - return the mode + * @panel: the panel to get the mode for + * @connector: reference to the central DRM connector control structure + */ +static int db7430_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct db7430 *db = to_db7430(panel); + struct drm_display_mode *mode; + static const u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24; + + mode = drm_mode_duplicate(connector->dev, &db7430_480_800_mode); + if (!mode) { + dev_err(db->dev, "failed to add mode\n"); + return -ENOMEM; + } + + connector->display_info.bpc = 8; + connector->display_info.width_mm = mode->width_mm; + connector->display_info.height_mm = mode->height_mm; + connector->display_info.bus_flags = + DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE; + drm_display_info_set_bus_formats(&connector->display_info, + &bus_format, 1); + + drm_mode_set_name(mode); + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + + drm_mode_probed_add(connector, mode); + + return 1; +} + +static const struct drm_panel_funcs db7430_drm_funcs = { + .disable = db7430_disable, + .unprepare = db7430_unprepare, + .prepare = db7430_prepare, + .enable = db7430_enable, + .get_modes = db7430_get_modes, +}; + +static int db7430_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct db7430 *db; + int ret; + + db = devm_drm_panel_alloc(dev, struct db7430, panel, &db7430_drm_funcs, + DRM_MODE_CONNECTOR_DPI); + if (IS_ERR(db)) + return PTR_ERR(db); + + db->dev = dev; + + /* + * VCI is the analog voltage supply + * VCCIO is the digital I/O voltage supply + */ + db->regulators[0].supply = "vci"; + db->regulators[1].supply = "vccio"; + ret = devm_regulator_bulk_get(dev, + ARRAY_SIZE(db->regulators), + db->regulators); + if (ret) + return dev_err_probe(dev, ret, "failed to get regulators\n"); + + db->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(db->reset)) { + ret = PTR_ERR(db->reset); + return dev_err_probe(dev, ret, "no RESET GPIO\n"); + } + + ret = mipi_dbi_spi_init(spi, &db->dbi, NULL); + if (ret) + return dev_err_probe(dev, ret, "MIPI DBI init failed\n"); + + /* FIXME: if no external backlight, use internal backlight */ + ret = drm_panel_of_backlight(&db->panel); + if (ret) + return dev_err_probe(dev, ret, "failed to add backlight\n"); + + spi_set_drvdata(spi, db); + + drm_panel_add(&db->panel); + dev_dbg(dev, "added panel\n"); + + return 0; +} + +static void db7430_remove(struct spi_device *spi) +{ + struct db7430 *db = spi_get_drvdata(spi); + + drm_panel_remove(&db->panel); +} + +/* + * The DB7430 display controller may be used in several display products, + * so list the different variants here and add per-variant data if needed. + */ +static const struct of_device_id db7430_match[] = { + { .compatible = "samsung,lms397kf04", }, + {}, +}; +MODULE_DEVICE_TABLE(of, db7430_match); + +static const struct spi_device_id db7430_ids[] = { + { "lms397kf04" }, + { }, +}; +MODULE_DEVICE_TABLE(spi, db7430_ids); + +static struct spi_driver db7430_driver = { + .probe = db7430_probe, + .remove = db7430_remove, + .id_table = db7430_ids, + .driver = { + .name = "db7430-panel", + .of_match_table = db7430_match, + }, +}; +module_spi_driver(db7430_driver); + +MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>"); +MODULE_DESCRIPTION("Samsung DB7430 panel driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-samsung-ld9040.c b/drivers/gpu/drm/panel/panel-samsung-ld9040.c index 3cf4cf6a6942..c7f2241523a0 100644 --- a/drivers/gpu/drm/panel/panel-samsung-ld9040.c +++ b/drivers/gpu/drm/panel/panel-samsung-ld9040.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * ld9040 AMOLED LCD drm_panel driver. * @@ -5,16 +6,13 @@ * Derived from drivers/video/backlight/ld9040.c * * Andrzej Hajda <a.hajda@samsung.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. */ -#include <drm/drmP.h> -#include <drm/drm_panel.h> - +#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 <linux/spi/spi.h> @@ -22,6 +20,9 @@ #include <video/of_videomode.h> #include <video/videomode.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + /* Manufacturer Command Set */ #define MCS_MANPWR 0xb0 #define MCS_ELVSS_ON 0xb1 @@ -180,15 +181,15 @@ static void ld9040_init(struct ld9040 *ctx) { ld9040_dcs_write_seq_static(ctx, MCS_USER_SETTING, 0x5a, 0x5a); ld9040_dcs_write_seq_static(ctx, MCS_PANEL_CONDITION, - 0x05, 0x65, 0x96, 0x71, 0x7d, 0x19, 0x3b, 0x0d, - 0x19, 0x7e, 0x0d, 0xe2, 0x00, 0x00, 0x7e, 0x7d, - 0x07, 0x07, 0x20, 0x20, 0x20, 0x02, 0x02); + 0x05, 0x5e, 0x96, 0x6b, 0x7d, 0x0d, 0x3f, 0x00, + 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x05, 0x1f, 0x1f, 0x1f, 0x00, 0x00); ld9040_dcs_write_seq_static(ctx, MCS_DISPCTL, - 0x02, 0x08, 0x08, 0x10, 0x10); + 0x02, 0x06, 0x0a, 0x10, 0x10); ld9040_dcs_write_seq_static(ctx, MCS_MANPWR, 0x04); ld9040_dcs_write_seq_static(ctx, MCS_POWER_CTRL, 0x0a, 0x87, 0x25, 0x6a, 0x44, 0x02, 0x88); - ld9040_dcs_write_seq_static(ctx, MCS_ELVSS_ON, 0x0d, 0x00, 0x16); + ld9040_dcs_write_seq_static(ctx, MCS_ELVSS_ON, 0x0f, 0x00, 0x16); ld9040_dcs_write_seq_static(ctx, MCS_GTCON, 0x09, 0x00, 0x00); ld9040_brightness_set(ctx); ld9040_dcs_write_seq_static(ctx, MIPI_DCS_EXIT_SLEEP_MODE); @@ -260,15 +261,15 @@ 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; mode = drm_mode_create(connector->dev); if (!mode) { - DRM_ERROR("failed to create a new display mode\n"); + dev_err(panel->dev, "failed to create a new display mode\n"); return 0; } @@ -310,20 +311,44 @@ static int ld9040_parse_dt(struct ld9040 *ctx) return 0; } +static int ld9040_bl_update_status(struct backlight_device *dev) +{ + struct ld9040 *ctx = bl_get_data(dev); + + ctx->brightness = backlight_get_brightness(dev); + ld9040_brightness_set(ctx); + + return 0; +} + +static const struct backlight_ops ld9040_bl_ops = { + .update_status = ld9040_bl_update_status, +}; + +static const struct backlight_properties ld9040_bl_props = { + .type = BACKLIGHT_RAW, + .scale = BACKLIGHT_SCALE_NON_LINEAR, + .max_brightness = ARRAY_SIZE(ld9040_gammas) - 1, + .brightness = ARRAY_SIZE(ld9040_gammas) - 1, +}; + static int ld9040_probe(struct spi_device *spi) { + struct backlight_device *bldev; struct device *dev = &spi->dev; struct ld9040 *ctx; int ret; - ctx = devm_kzalloc(dev, sizeof(struct ld9040), GFP_KERNEL); - if (!ctx) - return -ENOMEM; + ctx = devm_drm_panel_alloc(dev, struct ld9040, panel, + &ld9040_drm_funcs, + DRM_MODE_CONNECTOR_DPI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); spi_set_drvdata(spi, ctx); ctx->dev = dev; - ctx->brightness = ARRAY_SIZE(ld9040_gammas) - 1; + ctx->brightness = ld9040_bl_props.brightness; ret = ld9040_parse_dt(ctx); if (ret < 0) @@ -350,21 +375,23 @@ 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; + bldev = devm_backlight_device_register(dev, dev_name(dev), dev, + ctx, &ld9040_bl_ops, + &ld9040_bl_props); + if (IS_ERR(bldev)) + return PTR_ERR(bldev); + + drm_panel_add(&ctx->panel); - return drm_panel_add(&ctx->panel); + return 0; } -static int ld9040_remove(struct spi_device *spi) +static void ld9040_remove(struct spi_device *spi) { struct ld9040 *ctx = spi_get_drvdata(spi); ld9040_power_off(ctx); drm_panel_remove(&ctx->panel); - - return 0; } static const struct of_device_id ld9040_of_match[] = { @@ -373,9 +400,16 @@ static const struct of_device_id ld9040_of_match[] = { }; MODULE_DEVICE_TABLE(of, ld9040_of_match); +static const struct spi_device_id ld9040_ids[] = { + { "ld9040", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(spi, ld9040_ids); + static struct spi_driver ld9040_driver = { .probe = ld9040_probe, .remove = ld9040_remove, + .id_table = ld9040_ids, .driver = { .name = "panel-samsung-ld9040", .of_match_table = ld9040_of_match, diff --git a/drivers/gpu/drm/panel/panel-samsung-s6d16d0.c b/drivers/gpu/drm/panel/panel-samsung-s6d16d0.c index 33c22ee036f8..ba1a02000bb9 100644 --- a/drivers/gpu/drm/panel/panel-samsung-s6d16d0.c +++ b/drivers/gpu/drm/panel/panel-samsung-s6d16d0.c @@ -7,12 +7,11 @@ #include <drm/drm_modes.h> #include <drm/drm_mipi_dsi.h> #include <drm/drm_panel.h> -#include <drm/drm_print.h> #include <linux/gpio/consumer.h> #include <linux/regulator/consumer.h> #include <linux/delay.h> -#include <linux/of_device.h> +#include <linux/mod_devicetable.h> #include <linux/module.h> struct s6d16d0 { @@ -37,12 +36,6 @@ static const struct drm_display_mode samsung_s6d16d0_mode = { .vsync_start = 480 + 1, .vsync_end = 480 + 1 + 1, .vtotal = 480 + 1 + 1 + 1, - /* - * This depends on the clocking HS vs LP rate, this value - * is calculated as: - * vrefresh = (clock * 1000) / (htotal*vtotal) - */ - .vrefresh = 816, .width_mm = 84, .height_mm = 48, }; @@ -61,8 +54,7 @@ static int s6d16d0_unprepare(struct drm_panel *panel) /* Enter sleep mode */ ret = mipi_dsi_dcs_enter_sleep_mode(dsi); if (ret) { - DRM_DEV_ERROR(s6->dev, "failed to enter sleep mode (%d)\n", - ret); + dev_err(s6->dev, "failed to enter sleep mode (%d)\n", ret); return ret; } @@ -81,7 +73,7 @@ static int s6d16d0_prepare(struct drm_panel *panel) ret = regulator_enable(s6->supply); if (ret) { - DRM_DEV_ERROR(s6->dev, "failed to enable supply (%d)\n", ret); + dev_err(s6->dev, "failed to enable supply (%d)\n", ret); return ret; } @@ -96,15 +88,13 @@ static int s6d16d0_prepare(struct drm_panel *panel) ret = mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK); if (ret) { - DRM_DEV_ERROR(s6->dev, "failed to enable vblank TE (%d)\n", - ret); + dev_err(s6->dev, "failed to enable vblank TE (%d)\n", ret); return ret; } /* Exit sleep mode and power on */ ret = mipi_dsi_dcs_exit_sleep_mode(dsi); if (ret) { - DRM_DEV_ERROR(s6->dev, "failed to exit sleep mode (%d)\n", - ret); + dev_err(s6->dev, "failed to exit sleep mode (%d)\n", ret); return ret; } @@ -119,8 +109,7 @@ static int s6d16d0_enable(struct drm_panel *panel) ret = mipi_dsi_dcs_set_display_on(dsi); if (ret) { - DRM_DEV_ERROR(s6->dev, "failed to turn display on (%d)\n", - ret); + dev_err(s6->dev, "failed to turn display on (%d)\n", ret); return ret; } @@ -135,25 +124,21 @@ static int s6d16d0_disable(struct drm_panel *panel) ret = mipi_dsi_dcs_set_display_off(dsi); if (ret) { - DRM_DEV_ERROR(s6->dev, "failed to turn display off (%d)\n", - ret); + dev_err(s6->dev, "failed to turn display off (%d)\n", ret); return ret; } 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; - strncpy(connector->display_info.name, "Samsung S6D16D0\0", - DRM_DISPLAY_INFO_LEN); - - 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"); + dev_err(panel->dev, "bad mode or failed to add mode\n"); return -EINVAL; } drm_mode_set_name(mode); @@ -181,9 +166,11 @@ static int s6d16d0_probe(struct mipi_dsi_device *dsi) struct s6d16d0 *s6; int ret; - s6 = devm_kzalloc(dev, sizeof(struct s6d16d0), GFP_KERNEL); - if (!s6) - return -ENOMEM; + s6 = devm_drm_panel_alloc(dev, struct s6d16d0, panel, + &s6d16d0_drm_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(s6)) + return PTR_ERR(s6); mipi_dsi_set_drvdata(dsi, s6); s6->dev = dev; @@ -199,9 +186,7 @@ static int s6d16d0_probe(struct mipi_dsi_device *dsi) * As we only send commands we do not need to be continuously * clocked. */ - dsi->mode_flags = - MIPI_DSI_CLOCK_NON_CONTINUOUS | - MIPI_DSI_MODE_EOT_PACKET; + dsi->mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS; s6->supply = devm_regulator_get(dev, "vdd1"); if (IS_ERR(s6->supply)) @@ -213,18 +198,11 @@ static int s6d16d0_probe(struct mipi_dsi_device *dsi) if (IS_ERR(s6->reset_gpio)) { ret = PTR_ERR(s6->reset_gpio); if (ret != -EPROBE_DEFER) - DRM_DEV_ERROR(dev, "failed to request GPIO (%d)\n", - ret); + dev_err(dev, "failed to request GPIO (%d)\n", ret); return ret; } - drm_panel_init(&s6->panel); - s6->panel.dev = dev; - s6->panel.funcs = &s6d16d0_drm_funcs; - - ret = drm_panel_add(&s6->panel); - if (ret < 0) - return ret; + drm_panel_add(&s6->panel); ret = mipi_dsi_attach(dsi); if (ret < 0) @@ -233,14 +211,12 @@ static int s6d16d0_probe(struct mipi_dsi_device *dsi) return ret; } -static int s6d16d0_remove(struct mipi_dsi_device *dsi) +static void s6d16d0_remove(struct mipi_dsi_device *dsi) { struct s6d16d0 *s6 = mipi_dsi_get_drvdata(dsi); mipi_dsi_detach(dsi); drm_panel_remove(&s6->panel); - - return 0; } static const struct of_device_id s6d16d0_of_match[] = { diff --git a/drivers/gpu/drm/panel/panel-samsung-s6d27a1.c b/drivers/gpu/drm/panel/panel-samsung-s6d27a1.c new file mode 100644 index 000000000000..300dc19bd9d1 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-samsung-s6d27a1.c @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Panel driver for the Samsung S6D27A1 480x800 DPI RGB panel. + * Found in the Samsung Galaxy Ace 2 GT-I8160 mobile phone. + */ + +#include <drm/drm_mipi_dbi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/media-bus-format.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regulator/consumer.h> +#include <linux/spi/spi.h> + +#include <video/mipi_display.h> + +#define S6D27A1_PASSWD_L2 0xF0 /* Password Command for Level 2 Control */ +#define S6D27A1_RESCTL 0xB3 /* Resolution Select Control */ +#define S6D27A1_PANELCTL2 0xB4 /* ASG Signal Control */ +#define S6D27A1_READID1 0xDA /* Read panel ID 1 */ +#define S6D27A1_READID2 0xDB /* Read panel ID 2 */ +#define S6D27A1_READID3 0xDC /* Read panel ID 3 */ +#define S6D27A1_DISPCTL 0xF2 /* Display Control */ +#define S6D27A1_MANPWR 0xF3 /* Manual Control */ +#define S6D27A1_PWRCTL1 0xF4 /* Power Control */ +#define S6D27A1_SRCCTL 0xF6 /* Source Control */ +#define S6D27A1_PANELCTL 0xF7 /* Panel Control*/ + +static const u8 s6d27a1_dbi_read_commands[] = { + S6D27A1_READID1, + S6D27A1_READID2, + S6D27A1_READID3, + 0, /* sentinel */ +}; + +struct s6d27a1 { + struct device *dev; + struct mipi_dbi dbi; + struct drm_panel panel; + struct gpio_desc *reset; + struct regulator_bulk_data regulators[2]; +}; + +static const struct drm_display_mode s6d27a1_480_800_mode = { + /* + * The vendor driver states that the S6D27A1 panel + * has a pixel clock frequency of 49920000 Hz / 2 = 24960000 Hz. + */ + .clock = 24960, + .hdisplay = 480, + .hsync_start = 480 + 63, + .hsync_end = 480 + 63 + 2, + .htotal = 480 + 63 + 2 + 63, + .vdisplay = 800, + .vsync_start = 800 + 11, + .vsync_end = 800 + 11 + 2, + .vtotal = 800 + 11 + 2 + 10, + .width_mm = 50, + .height_mm = 84, + .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, +}; + +static inline struct s6d27a1 *to_s6d27a1(struct drm_panel *panel) +{ + return container_of(panel, struct s6d27a1, panel); +} + +static void s6d27a1_read_mtp_id(struct s6d27a1 *ctx) +{ + struct mipi_dbi *dbi = &ctx->dbi; + u8 id1, id2, id3; + int ret; + + ret = mipi_dbi_command_read(dbi, S6D27A1_READID1, &id1); + if (ret) { + dev_err(ctx->dev, "unable to read MTP ID 1\n"); + return; + } + ret = mipi_dbi_command_read(dbi, S6D27A1_READID2, &id2); + if (ret) { + dev_err(ctx->dev, "unable to read MTP ID 2\n"); + return; + } + ret = mipi_dbi_command_read(dbi, S6D27A1_READID3, &id3); + if (ret) { + dev_err(ctx->dev, "unable to read MTP ID 3\n"); + return; + } + dev_info(ctx->dev, "MTP ID: %02x %02x %02x\n", id1, id2, id3); +} + +static int s6d27a1_power_on(struct s6d27a1 *ctx) +{ + struct mipi_dbi *dbi = &ctx->dbi; + int ret; + + /* Power up */ + ret = regulator_bulk_enable(ARRAY_SIZE(ctx->regulators), + ctx->regulators); + if (ret) { + dev_err(ctx->dev, "failed to enable regulators: %d\n", ret); + return ret; + } + + msleep(20); + + /* Assert reset >=1 ms */ + gpiod_set_value_cansleep(ctx->reset, 1); + usleep_range(1000, 5000); + /* De-assert reset */ + gpiod_set_value_cansleep(ctx->reset, 0); + /* Wait >= 10 ms */ + msleep(20); + + /* + * Exit sleep mode and initialize display - some hammering is + * necessary. + */ + mipi_dbi_command(dbi, MIPI_DCS_EXIT_SLEEP_MODE); + mipi_dbi_command(dbi, MIPI_DCS_EXIT_SLEEP_MODE); + msleep(120); + + /* Magic to unlock level 2 control of the display */ + mipi_dbi_command(dbi, S6D27A1_PASSWD_L2, 0x5A, 0x5A); + + /* Configure resolution to 480RGBx800 */ + mipi_dbi_command(dbi, S6D27A1_RESCTL, 0x22); + + mipi_dbi_command(dbi, S6D27A1_PANELCTL2, 0x00, 0x02, 0x03, 0x04, 0x05, 0x08, 0x00, 0x0c); + + mipi_dbi_command(dbi, S6D27A1_MANPWR, 0x01, 0x00, 0x00, 0x08, 0x08, 0x02, 0x00); + + mipi_dbi_command(dbi, S6D27A1_DISPCTL, 0x19, 0x00, 0x08, 0x0D, 0x03, 0x41, 0x3F); + + mipi_dbi_command(dbi, S6D27A1_PWRCTL1, 0x00, 0x00, 0x00, 0x00, 0x55, + 0x44, 0x05, 0x88, 0x4B, 0x50); + + mipi_dbi_command(dbi, S6D27A1_SRCCTL, 0x03, 0x09, 0x8A, 0x00, 0x01, 0x16); + + mipi_dbi_command(dbi, S6D27A1_PANELCTL, 0x00, 0x05, 0x06, 0x07, 0x08, + 0x01, 0x09, 0x0D, 0x0A, 0x0E, + 0x0B, 0x0F, 0x0C, 0x10, 0x01, + 0x11, 0x12, 0x13, 0x14, 0x05, + 0x06, 0x07, 0x08, 0x01, 0x09, + 0x0D, 0x0A, 0x0E, 0x0B, 0x0F, + 0x0C, 0x10, 0x01, 0x11, 0x12, + 0x13, 0x14); + + /* lock the level 2 control */ + mipi_dbi_command(dbi, S6D27A1_PASSWD_L2, 0xA5, 0xA5); + + s6d27a1_read_mtp_id(ctx); + + return 0; +} + +static int s6d27a1_power_off(struct s6d27a1 *ctx) +{ + /* Go into RESET and disable regulators */ + gpiod_set_value_cansleep(ctx->reset, 1); + return regulator_bulk_disable(ARRAY_SIZE(ctx->regulators), + ctx->regulators); +} + +static int s6d27a1_unprepare(struct drm_panel *panel) +{ + struct s6d27a1 *ctx = to_s6d27a1(panel); + struct mipi_dbi *dbi = &ctx->dbi; + + mipi_dbi_command(dbi, MIPI_DCS_ENTER_SLEEP_MODE); + msleep(120); + return s6d27a1_power_off(to_s6d27a1(panel)); +} + +static int s6d27a1_disable(struct drm_panel *panel) +{ + struct s6d27a1 *ctx = to_s6d27a1(panel); + struct mipi_dbi *dbi = &ctx->dbi; + + mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_OFF); + msleep(25); + + return 0; +} + +static int s6d27a1_prepare(struct drm_panel *panel) +{ + return s6d27a1_power_on(to_s6d27a1(panel)); +} + +static int s6d27a1_enable(struct drm_panel *panel) +{ + struct s6d27a1 *ctx = to_s6d27a1(panel); + struct mipi_dbi *dbi = &ctx->dbi; + + mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_ON); + + return 0; +} + +static int s6d27a1_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct s6d27a1 *ctx = to_s6d27a1(panel); + struct drm_display_mode *mode; + static const u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24; + + mode = drm_mode_duplicate(connector->dev, &s6d27a1_480_800_mode); + if (!mode) { + dev_err(ctx->dev, "failed to add mode\n"); + return -ENOMEM; + } + + connector->display_info.bpc = 8; + connector->display_info.width_mm = mode->width_mm; + connector->display_info.height_mm = mode->height_mm; + connector->display_info.bus_flags = + DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE; + drm_display_info_set_bus_formats(&connector->display_info, + &bus_format, 1); + + drm_mode_set_name(mode); + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + + drm_mode_probed_add(connector, mode); + + return 1; +} + +static const struct drm_panel_funcs s6d27a1_drm_funcs = { + .disable = s6d27a1_disable, + .unprepare = s6d27a1_unprepare, + .prepare = s6d27a1_prepare, + .enable = s6d27a1_enable, + .get_modes = s6d27a1_get_modes, +}; + +static int s6d27a1_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct s6d27a1 *ctx; + int ret; + + ctx = devm_drm_panel_alloc(dev, struct s6d27a1, panel, + &s6d27a1_drm_funcs, + DRM_MODE_CONNECTOR_DPI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ctx->dev = dev; + + /* + * VCI is the analog voltage supply + * VCCIO is the digital I/O voltage supply + */ + ctx->regulators[0].supply = "vci"; + ctx->regulators[1].supply = "vccio"; + ret = devm_regulator_bulk_get(dev, + ARRAY_SIZE(ctx->regulators), + ctx->regulators); + if (ret) + return dev_err_probe(dev, ret, "failed to get regulators\n"); + + ctx->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->reset)) { + ret = PTR_ERR(ctx->reset); + return dev_err_probe(dev, ret, "no RESET GPIO\n"); + } + + ret = mipi_dbi_spi_init(spi, &ctx->dbi, NULL); + if (ret) + return dev_err_probe(dev, ret, "MIPI DBI init failed\n"); + + ctx->dbi.read_commands = s6d27a1_dbi_read_commands; + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return dev_err_probe(dev, ret, "failed to add backlight\n"); + + spi_set_drvdata(spi, ctx); + + drm_panel_add(&ctx->panel); + + return 0; +} + +static void s6d27a1_remove(struct spi_device *spi) +{ + struct s6d27a1 *ctx = spi_get_drvdata(spi); + + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id s6d27a1_match[] = { + { .compatible = "samsung,s6d27a1", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, s6d27a1_match); + +static struct spi_driver s6d27a1_driver = { + .probe = s6d27a1_probe, + .remove = s6d27a1_remove, + .driver = { + .name = "s6d27a1-panel", + .of_match_table = s6d27a1_match, + }, +}; +module_spi_driver(s6d27a1_driver); + +MODULE_AUTHOR("Markuss Broks <markuss.broks@gmail.com>"); +MODULE_DESCRIPTION("Samsung S6D27A1 panel driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-samsung-s6d7aa0.c b/drivers/gpu/drm/panel/panel-samsung-s6d7aa0.c new file mode 100644 index 000000000000..692020081524 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-samsung-s6d7aa0.c @@ -0,0 +1,492 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Samsung S6D7AA0 MIPI-DSI TFT LCD controller drm_panel driver. + * + * Copyright (C) 2022 Artur Weber <aweber.kernel@gmail.com> + */ + +#include <linux/backlight.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/regulator/consumer.h> +#include <linux/of.h> + +#include <video/mipi_display.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +/* Manufacturer command set */ +#define MCS_BL_CTL 0xc3 +#define MCS_OTP_RELOAD 0xd0 +#define MCS_PASSWD1 0xf0 +#define MCS_PASSWD2 0xf1 +#define MCS_PASSWD3 0xfc + +struct s6d7aa0 { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[2]; + const struct s6d7aa0_panel_desc *desc; +}; + +struct s6d7aa0_panel_desc { + unsigned int panel_type; + void (*init_func)(struct s6d7aa0 *ctx, struct mipi_dsi_multi_context *dsi_ctx); + void (*off_func)(struct mipi_dsi_multi_context *dsi_ctx); + const struct drm_display_mode *drm_mode; + unsigned long mode_flags; + u32 bus_flags; + bool has_backlight; + bool use_passwd3; +}; + +enum s6d7aa0_panels { + S6D7AA0_PANEL_LSL080AL02, + S6D7AA0_PANEL_LSL080AL03, + S6D7AA0_PANEL_LTL101AT01, +}; + +static inline struct s6d7aa0 *panel_to_s6d7aa0(struct drm_panel *panel) +{ + return container_of(panel, struct s6d7aa0, panel); +} + +static void s6d7aa0_reset(struct s6d7aa0 *ctx) +{ + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + msleep(50); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + msleep(50); +} + +static void s6d7aa0_lock(struct s6d7aa0 *ctx, struct mipi_dsi_multi_context *dsi_ctx, bool lock) +{ + if (lock) { + mipi_dsi_dcs_write_seq_multi(dsi_ctx, MCS_PASSWD1, 0xa5, 0xa5); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, MCS_PASSWD2, 0xa5, 0xa5); + if (ctx->desc->use_passwd3) + mipi_dsi_dcs_write_seq_multi(dsi_ctx, MCS_PASSWD3, 0x5a, 0x5a); + } else { + mipi_dsi_dcs_write_seq_multi(dsi_ctx, MCS_PASSWD1, 0x5a, 0x5a); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, MCS_PASSWD2, 0x5a, 0x5a); + if (ctx->desc->use_passwd3) + mipi_dsi_dcs_write_seq_multi(dsi_ctx, MCS_PASSWD3, 0xa5, 0xa5); + } +} + +static int s6d7aa0_on(struct s6d7aa0 *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + ctx->desc->init_func(ctx, &dsi_ctx); + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + + return dsi_ctx.accum_err; +} + +static void s6d7aa0_off(struct s6d7aa0 *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + ctx->desc->off_func(&dsi_ctx); + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 64); + + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + + mipi_dsi_msleep(&dsi_ctx, 120); +} + +static int s6d7aa0_prepare(struct drm_panel *panel) +{ + struct s6d7aa0 *ctx = panel_to_s6d7aa0(panel); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + if (ret < 0) + return ret; + + s6d7aa0_reset(ctx); + + ret = s6d7aa0_on(ctx); + if (ret < 0) { + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + return ret; + } + + return 0; +} + +static int s6d7aa0_disable(struct drm_panel *panel) +{ + struct s6d7aa0 *ctx = panel_to_s6d7aa0(panel); + + s6d7aa0_off(ctx); + + return 0; +} + +static int s6d7aa0_unprepare(struct drm_panel *panel) +{ + struct s6d7aa0 *ctx = panel_to_s6d7aa0(panel); + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + + return 0; +} + +/* Backlight control code */ + +static int s6d7aa0_bl_update_status(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + u16 brightness = backlight_get_brightness(bl); + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + mipi_dsi_dcs_set_display_brightness_multi(&dsi_ctx, brightness); + + return dsi_ctx.accum_err; +} + +static int s6d7aa0_bl_get_brightness(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + u16 brightness; + int ret; + + ret = mipi_dsi_dcs_get_display_brightness(dsi, &brightness); + if (ret < 0) + return ret; + + return brightness & 0xff; +} + +static const struct backlight_ops s6d7aa0_bl_ops = { + .update_status = s6d7aa0_bl_update_status, + .get_brightness = s6d7aa0_bl_get_brightness, +}; + +static struct backlight_device * +s6d7aa0_create_backlight(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + const struct backlight_properties props = { + .type = BACKLIGHT_RAW, + .brightness = 255, + .max_brightness = 255, + }; + + return devm_backlight_device_register(dev, dev_name(dev), dev, dsi, + &s6d7aa0_bl_ops, &props); +} + +/* Initialization code and structures for LSL080AL02 panel */ + +static void s6d7aa0_lsl080al02_init(struct s6d7aa0 *ctx, struct mipi_dsi_multi_context *dsi_ctx) +{ + mipi_dsi_usleep_range(dsi_ctx, 20000, 25000); + + s6d7aa0_lock(ctx, dsi_ctx, false); + + mipi_dsi_dcs_write_seq_multi(dsi_ctx, MCS_OTP_RELOAD, 0x00, 0x10); + mipi_dsi_usleep_range(dsi_ctx, 1000, 1500); + + /* SEQ_B6_PARAM_8_R01 */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb6, 0x10); + + /* BL_CTL_ON */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, MCS_BL_CTL, 0x40, 0x00, 0x28); + + mipi_dsi_usleep_range(dsi_ctx, 5000, 6000); + + mipi_dsi_dcs_write_seq_multi(dsi_ctx, MIPI_DCS_SET_ADDRESS_MODE, 0x04); + + mipi_dsi_dcs_exit_sleep_mode_multi(dsi_ctx); + + mipi_dsi_msleep(dsi_ctx, 120); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, MIPI_DCS_SET_ADDRESS_MODE, 0x00); + + s6d7aa0_lock(ctx, dsi_ctx, true); + + mipi_dsi_dcs_set_display_on_multi(dsi_ctx); +} + +static void s6d7aa0_lsl080al02_off(struct mipi_dsi_multi_context *dsi_ctx) +{ + /* BL_CTL_OFF */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, MCS_BL_CTL, 0x40, 0x00, 0x20); +} + +static const struct drm_display_mode s6d7aa0_lsl080al02_mode = { + .clock = (800 + 16 + 4 + 140) * (1280 + 8 + 4 + 4) * 60 / 1000, + .hdisplay = 800, + .hsync_start = 800 + 16, + .hsync_end = 800 + 16 + 4, + .htotal = 800 + 16 + 4 + 140, + .vdisplay = 1280, + .vsync_start = 1280 + 8, + .vsync_end = 1280 + 8 + 4, + .vtotal = 1280 + 8 + 4 + 4, + .width_mm = 108, + .height_mm = 173, +}; + +static const struct s6d7aa0_panel_desc s6d7aa0_lsl080al02_desc = { + .panel_type = S6D7AA0_PANEL_LSL080AL02, + .init_func = s6d7aa0_lsl080al02_init, + .off_func = s6d7aa0_lsl080al02_off, + .drm_mode = &s6d7aa0_lsl080al02_mode, + .mode_flags = MIPI_DSI_MODE_VIDEO_NO_HFP, + .bus_flags = 0, + + .has_backlight = false, + .use_passwd3 = false, +}; + +/* Initialization code and structures for LSL080AL03 panel */ + +static void s6d7aa0_lsl080al03_init(struct s6d7aa0 *ctx, struct mipi_dsi_multi_context *dsi_ctx) +{ + mipi_dsi_usleep_range(dsi_ctx, 20000, 25000); + + s6d7aa0_lock(ctx, dsi_ctx, false); + + if (ctx->desc->panel_type == S6D7AA0_PANEL_LSL080AL03) { + mipi_dsi_dcs_write_seq_multi(dsi_ctx, MCS_BL_CTL, 0xc7, 0x00, 0x29); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xbc, 0x01, 0x4e, 0xa0); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfd, 0x16, 0x10, 0x11, 0x23, + 0x09); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfe, 0x00, 0x02, 0x03, 0x21, + 0x80, 0x78); + } else if (ctx->desc->panel_type == S6D7AA0_PANEL_LTL101AT01) { + mipi_dsi_dcs_write_seq_multi(dsi_ctx, MCS_BL_CTL, 0x40, 0x00, 0x08); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xbc, 0x01, 0x4e, 0x0b); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfd, 0x16, 0x10, 0x11, 0x23, + 0x09); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfe, 0x00, 0x02, 0x03, 0x21, + 0x80, 0x68); + } + + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb3, 0x51); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x24); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xf2, 0x02, 0x08, 0x08); + + mipi_dsi_usleep_range(dsi_ctx, 10000, 11000); + + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc0, 0x80, 0x80, 0x30); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xcd, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xce, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc1, 0x03); + + mipi_dsi_dcs_exit_sleep_mode_multi(dsi_ctx); + s6d7aa0_lock(ctx, dsi_ctx, true); + mipi_dsi_dcs_set_display_on_multi(dsi_ctx); +} + +static void s6d7aa0_lsl080al03_off(struct mipi_dsi_multi_context *dsi_ctx) +{ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x22, 0x00); +} + +static const struct drm_display_mode s6d7aa0_lsl080al03_mode = { + .clock = (768 + 18 + 16 + 126) * (1024 + 8 + 2 + 6) * 60 / 1000, + .hdisplay = 768, + .hsync_start = 768 + 18, + .hsync_end = 768 + 18 + 16, + .htotal = 768 + 18 + 16 + 126, + .vdisplay = 1024, + .vsync_start = 1024 + 8, + .vsync_end = 1024 + 8 + 2, + .vtotal = 1024 + 8 + 2 + 6, + .width_mm = 122, + .height_mm = 163, +}; + +static const struct s6d7aa0_panel_desc s6d7aa0_lsl080al03_desc = { + .panel_type = S6D7AA0_PANEL_LSL080AL03, + .init_func = s6d7aa0_lsl080al03_init, + .off_func = s6d7aa0_lsl080al03_off, + .drm_mode = &s6d7aa0_lsl080al03_mode, + .mode_flags = MIPI_DSI_MODE_NO_EOT_PACKET, + .bus_flags = 0, + + .has_backlight = true, + .use_passwd3 = true, +}; + +/* Initialization structures for LTL101AT01 panel */ + +static const struct drm_display_mode s6d7aa0_ltl101at01_mode = { + .clock = (768 + 96 + 16 + 184) * (1024 + 8 + 2 + 6) * 60 / 1000, + .hdisplay = 768, + .hsync_start = 768 + 96, + .hsync_end = 768 + 96 + 16, + .htotal = 768 + 96 + 16 + 184, + .vdisplay = 1024, + .vsync_start = 1024 + 8, + .vsync_end = 1024 + 8 + 2, + .vtotal = 1024 + 8 + 2 + 6, + .width_mm = 148, + .height_mm = 197, +}; + +static const struct s6d7aa0_panel_desc s6d7aa0_ltl101at01_desc = { + .panel_type = S6D7AA0_PANEL_LTL101AT01, + .init_func = s6d7aa0_lsl080al03_init, /* Similar init to LSL080AL03 */ + .off_func = s6d7aa0_lsl080al03_off, + .drm_mode = &s6d7aa0_ltl101at01_mode, + .mode_flags = MIPI_DSI_MODE_NO_EOT_PACKET, + .bus_flags = 0, + + .has_backlight = true, + .use_passwd3 = true, +}; + +static int s6d7aa0_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + struct s6d7aa0 *ctx; + + ctx = container_of(panel, struct s6d7aa0, panel); + if (!ctx) + return -EINVAL; + + mode = drm_mode_duplicate(connector->dev, ctx->desc->drm_mode); + if (!mode) + 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; + connector->display_info.bus_flags = ctx->desc->bus_flags; + drm_mode_probed_add(connector, mode); + + return 1; +} + +static const struct drm_panel_funcs s6d7aa0_panel_funcs = { + .disable = s6d7aa0_disable, + .prepare = s6d7aa0_prepare, + .unprepare = s6d7aa0_unprepare, + .get_modes = s6d7aa0_get_modes, +}; + +static int s6d7aa0_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct s6d7aa0 *ctx; + int ret; + + ctx = devm_drm_panel_alloc(dev, struct s6d7aa0, panel, + &s6d7aa0_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ctx->desc = of_device_get_match_data(dev); + if (!ctx->desc) + return -ENODEV; + + ctx->supplies[0].supply = "power"; + ctx->supplies[1].supply = "vmipi"; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), + ctx->supplies); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get regulators\n"); + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), + "Failed to get reset-gpios\n"); + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST + | ctx->desc->mode_flags; + + ctx->panel.prepare_prev_first = true; + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return dev_err_probe(dev, ret, "Failed to get backlight\n"); + + /* Use DSI-based backlight as fallback if available */ + if (ctx->desc->has_backlight && !ctx->panel.backlight) { + ctx->panel.backlight = s6d7aa0_create_backlight(dsi); + if (IS_ERR(ctx->panel.backlight)) + return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight), + "Failed to create backlight\n"); + } + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "Failed to attach to DSI host: %d\n", ret); + drm_panel_remove(&ctx->panel); + return ret; + } + + return 0; +} + +static void s6d7aa0_remove(struct mipi_dsi_device *dsi) +{ + struct s6d7aa0 *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id s6d7aa0_of_match[] = { + { + .compatible = "samsung,lsl080al02", + .data = &s6d7aa0_lsl080al02_desc + }, + { + .compatible = "samsung,lsl080al03", + .data = &s6d7aa0_lsl080al03_desc + }, + { + .compatible = "samsung,ltl101at01", + .data = &s6d7aa0_ltl101at01_desc + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, s6d7aa0_of_match); + +static struct mipi_dsi_driver s6d7aa0_driver = { + .probe = s6d7aa0_probe, + .remove = s6d7aa0_remove, + .driver = { + .name = "panel-samsung-s6d7aa0", + .of_match_table = s6d7aa0_of_match, + }, +}; +module_mipi_dsi_driver(s6d7aa0_driver); + +MODULE_AUTHOR("Artur Weber <aweber.kernel@gmail.com>"); +MODULE_DESCRIPTION("Samsung S6D7AA0 MIPI-DSI LCD controller driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-samsung-s6e3fa7.c b/drivers/gpu/drm/panel/panel-samsung-s6e3fa7.c new file mode 100644 index 000000000000..f4d75eca3cdf --- /dev/null +++ b/drivers/gpu/drm/panel/panel-samsung-s6e3fa7.c @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for the Samsung S6E3FA7 panel. + * + * Copyright (c) 2022-2024, The Linux Foundation. All rights reserved. + * Generated with linux-mdss-dsi-panel-driver-generator from vendor device tree: + * Copyright (c) 2013, The Linux Foundation. All rights reserved. + */ + +#include <linux/backlight.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> + +#include <video/mipi_display.h> + +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +struct s6e3fa7_panel { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct gpio_desc *reset_gpio; +}; + +static inline struct s6e3fa7_panel *to_s6e3fa7_panel(struct drm_panel *panel) +{ + return container_of(panel, struct s6e3fa7_panel, panel); +} + +static void s6e3fa7_panel_reset(struct s6e3fa7_panel *ctx) +{ + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(1000, 2000); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(10000, 11000); +} + +static int s6e3fa7_panel_on(struct mipi_dsi_device *dsi) +{ + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 120); + mipi_dsi_dcs_set_tear_on_multi(&dsi_ctx, MIPI_DSI_DCS_TEAR_MODE_VBLANK); + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf0, 0x5a, 0x5a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf4, + 0xbb, 0x23, 0x19, 0x3a, 0x9f, 0x0f, 0x09, 0xc0, + 0x00, 0xb4, 0x37, 0x70, 0x79, 0x69); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf0, 0xa5, 0xa5); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x20); + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + + return dsi_ctx.accum_err; +} + +static int s6e3fa7_panel_prepare(struct drm_panel *panel) +{ + struct s6e3fa7_panel *ctx = to_s6e3fa7_panel(panel); + int ret; + + s6e3fa7_panel_reset(ctx); + + ret = s6e3fa7_panel_on(ctx->dsi); + if (ret < 0) + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + + return ret; +} + +static int s6e3fa7_panel_unprepare(struct drm_panel *panel) +{ + struct s6e3fa7_panel *ctx = to_s6e3fa7_panel(panel); + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + + return 0; +} + +static int s6e3fa7_panel_disable(struct drm_panel *panel) +{ + struct s6e3fa7_panel *ctx = to_s6e3fa7_panel(panel); + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 120); + + return dsi_ctx.accum_err; +} + +static const struct drm_display_mode s6e3fa7_panel_mode = { + .clock = (1080 + 32 + 32 + 78) * (2220 + 32 + 4 + 78) * 60 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 32, + .hsync_end = 1080 + 32 + 32, + .htotal = 1080 + 32 + 32 + 78, + .vdisplay = 2220, + .vsync_start = 2220 + 32, + .vsync_end = 2220 + 32 + 4, + .vtotal = 2220 + 32 + 4 + 78, + .width_mm = 62, + .height_mm = 127, +}; + +static int s6e3fa7_panel_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &s6e3fa7_panel_mode); + if (!mode) + 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 s6e3fa7_panel_funcs = { + .prepare = s6e3fa7_panel_prepare, + .unprepare = s6e3fa7_panel_unprepare, + .disable = s6e3fa7_panel_disable, + .get_modes = s6e3fa7_panel_get_modes, +}; + +static int s6e3fa7_panel_bl_update_status(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + u16 brightness = backlight_get_brightness(bl); + int ret; + + ret = mipi_dsi_dcs_set_display_brightness_large(dsi, brightness); + if (ret < 0) + return ret; + + return 0; +} + +static int s6e3fa7_panel_bl_get_brightness(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + u16 brightness; + int ret; + + ret = mipi_dsi_dcs_get_display_brightness_large(dsi, &brightness); + if (ret < 0) + return ret; + + return brightness; +} + +static const struct backlight_ops s6e3fa7_panel_bl_ops = { + .update_status = s6e3fa7_panel_bl_update_status, + .get_brightness = s6e3fa7_panel_bl_get_brightness, +}; + +static struct backlight_device * +s6e3fa7_panel_create_backlight(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + const struct backlight_properties props = { + .type = BACKLIGHT_RAW, + .brightness = 1023, + .max_brightness = 1023, + }; + + return devm_backlight_device_register(dev, dev_name(dev), dev, dsi, + &s6e3fa7_panel_bl_ops, &props); +} + +static int s6e3fa7_panel_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct s6e3fa7_panel *ctx; + int ret; + + ctx = devm_drm_panel_alloc(dev, struct s6e3fa7_panel, panel, + &s6e3fa7_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), + "Failed to get reset-gpios\n"); + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_CLOCK_NON_CONTINUOUS | MIPI_DSI_MODE_LPM; + + ctx->panel.prepare_prev_first = true; + + ctx->panel.backlight = s6e3fa7_panel_create_backlight(dsi); + if (IS_ERR(ctx->panel.backlight)) + return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight), + "Failed to create backlight\n"); + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "Failed to attach to DSI host: %d\n", ret); + drm_panel_remove(&ctx->panel); + return ret; + } + + return 0; +} + +static void s6e3fa7_panel_remove(struct mipi_dsi_device *dsi) +{ + struct s6e3fa7_panel *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id s6e3fa7_panel_of_match[] = { + { .compatible = "samsung,s6e3fa7-ams559nk06" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, s6e3fa7_panel_of_match); + +static struct mipi_dsi_driver s6e3fa7_panel_driver = { + .probe = s6e3fa7_panel_probe, + .remove = s6e3fa7_panel_remove, + .driver = { + .name = "panel-samsung-s6e3fa7", + .of_match_table = s6e3fa7_panel_of_match, + }, +}; +module_mipi_dsi_driver(s6e3fa7_panel_driver); + +MODULE_AUTHOR("Richard Acayan <mailingradian@gmail.com>"); +MODULE_DESCRIPTION("DRM driver for Samsung S6E3FA7 command mode DSI panel"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-samsung-s6e3fc2x01.c b/drivers/gpu/drm/panel/panel-samsung-s6e3fc2x01.c new file mode 100644 index 000000000000..e63080204af7 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-samsung-s6e3fc2x01.c @@ -0,0 +1,385 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2022 Nia Espera <a5b6@riseup.net> + * Copyright (c) 2025 David Heidelberg <david@ixit.cz> + */ + +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/regulator/consumer.h> +#include <linux/swab.h> +#include <linux/backlight.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_probe_helper.h> + +#define MCS_ELVSS_ON 0xb1 + +struct samsung_s6e3fc2x01 { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct regulator_bulk_data *supplies; + struct gpio_desc *reset_gpio; +}; + +static const struct regulator_bulk_data s6e3fc2x01_supplies[] = { + { .supply = "vddio" }, + { .supply = "vci" }, + { .supply = "poc" }, +}; + +static inline +struct samsung_s6e3fc2x01 *to_samsung_s6e3fc2x01(struct drm_panel *panel) +{ + return container_of(panel, struct samsung_s6e3fc2x01, panel); +} + +#define s6e3fc2x01_test_key_on_lvl1(ctx) \ + mipi_dsi_dcs_write_seq_multi(ctx, 0x9f, 0xa5, 0xa5) +#define s6e3fc2x01_test_key_off_lvl1(ctx) \ + mipi_dsi_dcs_write_seq_multi(ctx, 0x9f, 0x5a, 0x5a) +#define s6e3fc2x01_test_key_on_lvl2(ctx) \ + mipi_dsi_dcs_write_seq_multi(ctx, 0xf0, 0x5a, 0x5a) +#define s6e3fc2x01_test_key_off_lvl2(ctx) \ + mipi_dsi_dcs_write_seq_multi(ctx, 0xf0, 0xa5, 0xa5) +#define s6e3fc2x01_test_key_on_lvl3(ctx) \ + mipi_dsi_dcs_write_seq_multi(ctx, 0xfc, 0x5a, 0x5a) +#define s6e3fc2x01_test_key_off_lvl3(ctx) \ + mipi_dsi_dcs_write_seq_multi(ctx, 0xfc, 0xa5, 0xa5) + +static void s6e3fc2x01_reset(struct samsung_s6e3fc2x01 *ctx) +{ + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(5000, 6000); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(5000, 6000); +} + +static int s6e3fc2x01_on(struct samsung_s6e3fc2x01 *ctx) +{ + struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; + + s6e3fc2x01_test_key_on_lvl1(&dsi_ctx); + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + + mipi_dsi_usleep_range(&dsi_ctx, 10000, 11000); + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x0a); + mipi_dsi_usleep_range(&dsi_ctx, 10000, 11000); + + s6e3fc2x01_test_key_off_lvl1(&dsi_ctx); + + s6e3fc2x01_test_key_on_lvl2(&dsi_ctx); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xcd, 0x01); + s6e3fc2x01_test_key_off_lvl2(&dsi_ctx); + + mipi_dsi_usleep_range(&dsi_ctx, 15000, 16000); + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x0f); + mipi_dsi_usleep_range(&dsi_ctx, 10000, 11000); + + s6e3fc2x01_test_key_on_lvl1(&dsi_ctx); + mipi_dsi_dcs_set_tear_on_multi(&dsi_ctx, MIPI_DSI_DCS_TEAR_MODE_VBLANK); + s6e3fc2x01_test_key_off_lvl1(&dsi_ctx); + + s6e3fc2x01_test_key_on_lvl2(&dsi_ctx); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xeb, 0x17, + 0x41, 0x92, + 0x0e, 0x10, + 0x82, 0x5a); + s6e3fc2x01_test_key_off_lvl2(&dsi_ctx); + + /* Column & Page Address Setting */ + mipi_dsi_dcs_set_column_address_multi(&dsi_ctx, 0x0000, 0x0437); + mipi_dsi_dcs_set_page_address_multi(&dsi_ctx, 0x0000, 0x0923); + + /* Horizontal & Vertical sync Setting */ + s6e3fc2x01_test_key_on_lvl2(&dsi_ctx); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x09); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe8, 0x10, 0x30); + s6e3fc2x01_test_key_off_lvl2(&dsi_ctx); + + s6e3fc2x01_test_key_on_lvl3(&dsi_ctx); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe3, 0x88); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x07); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xed, 0x67); + s6e3fc2x01_test_key_off_lvl3(&dsi_ctx); + + s6e3fc2x01_test_key_on_lvl2(&dsi_ctx); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x07); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb7, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb7, 0x12); + s6e3fc2x01_test_key_off_lvl2(&dsi_ctx); + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x20); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_POWER_SAVE, 0x00); + mipi_dsi_usleep_range(&dsi_ctx, 1000, 2000); + + s6e3fc2x01_test_key_on_lvl2(&dsi_ctx); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_ELVSS_ON, 0x00, 0x01); + s6e3fc2x01_test_key_off_lvl2(&dsi_ctx); + + s6e3fc2x01_test_key_on_lvl2(&dsi_ctx); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb3, 0x00, 0xc1); + s6e3fc2x01_test_key_off_lvl2(&dsi_ctx); + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x78); + mipi_dsi_usleep_range(&dsi_ctx, 10000, 11000); + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x81, 0x90); + mipi_dsi_usleep_range(&dsi_ctx, 10000, 11000); + + s6e3fc2x01_test_key_on_lvl2(&dsi_ctx); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_ELVSS_ON, 0xc6, 0x00, 0x00, + 0x21, 0xed, 0x02, 0x08, 0x06, 0xc1, 0x27, + 0xfc, 0xdc, 0xe4, 0x00, 0xd9, 0xe6, 0xe7, + 0x00, 0xfc, 0xff, 0xea); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_ELVSS_ON, 0x00, 0x00); + s6e3fc2x01_test_key_off_lvl2(&dsi_ctx); + + mipi_dsi_usleep_range(&dsi_ctx, 10000, 11000); + + return dsi_ctx.accum_err; +} + +static int s6e3fc2x01_enable(struct drm_panel *panel) +{ + struct samsung_s6e3fc2x01 *ctx = to_samsung_s6e3fc2x01(panel); + struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; + + s6e3fc2x01_test_key_on_lvl1(&dsi_ctx); + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + s6e3fc2x01_test_key_off_lvl1(&dsi_ctx); + + return dsi_ctx.accum_err; +} + +static int s6e3fc2x01_off(struct samsung_s6e3fc2x01 *ctx) +{ + struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; + + s6e3fc2x01_test_key_on_lvl1(&dsi_ctx); + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + + mipi_dsi_usleep_range(&dsi_ctx, 10000, 11000); + + s6e3fc2x01_test_key_on_lvl2(&dsi_ctx); + mipi_dsi_usleep_range(&dsi_ctx, 16000, 17000); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x50); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb9, 0x82); + s6e3fc2x01_test_key_off_lvl2(&dsi_ctx); + mipi_dsi_usleep_range(&dsi_ctx, 16000, 17000); + + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + + s6e3fc2x01_test_key_off_lvl1(&dsi_ctx); + + s6e3fc2x01_test_key_on_lvl2(&dsi_ctx); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf4, 0x01); + s6e3fc2x01_test_key_off_lvl2(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 160); + + return dsi_ctx.accum_err; +} + +static int s6e3fc2x01_disable(struct drm_panel *panel) +{ + struct samsung_s6e3fc2x01 *ctx = to_samsung_s6e3fc2x01(panel); + + s6e3fc2x01_off(ctx); + + return 0; +} + +static int s6e3fc2x01_prepare(struct drm_panel *panel) +{ + struct samsung_s6e3fc2x01 *ctx = to_samsung_s6e3fc2x01(panel); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(s6e3fc2x01_supplies), ctx->supplies); + if (ret < 0) + return ret; + + s6e3fc2x01_reset(ctx); + + ret = s6e3fc2x01_on(ctx); + if (ret < 0) { + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(s6e3fc2x01_supplies), ctx->supplies); + return ret; + } + + return 0; +} + +static int s6e3fc2x01_unprepare(struct drm_panel *panel) +{ + struct samsung_s6e3fc2x01 *ctx = to_samsung_s6e3fc2x01(panel); + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(s6e3fc2x01_supplies), ctx->supplies); + + return 0; +} + +static const struct drm_display_mode ams641rw_mode = { + .clock = (1080 + 72 + 16 + 36) * (2340 + 32 + 4 + 18) * 60 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 72, + .hsync_end = 1080 + 72 + 16, + .htotal = 1080 + 72 + 16 + 36, + .vdisplay = 2340, + .vsync_start = 2340 + 32, + .vsync_end = 2340 + 32 + 4, + .vtotal = 2340 + 32 + 4 + 18, + .width_mm = 68, + .height_mm = 145, +}; + +static int s6e3fc2x01_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + return drm_connector_helper_get_modes_fixed(connector, &ams641rw_mode); +} + +static const struct drm_panel_funcs samsung_s6e3fc2x01_panel_funcs = { + .prepare = s6e3fc2x01_prepare, + .enable = s6e3fc2x01_enable, + .disable = s6e3fc2x01_disable, + .unprepare = s6e3fc2x01_unprepare, + .get_modes = s6e3fc2x01_get_modes, +}; + +static int s6e3fc2x01_panel_bl_update_status(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + u16 brightness = backlight_get_brightness(bl); + int err; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + err = mipi_dsi_dcs_set_display_brightness_large(dsi, brightness); + if (err < 0) + return err; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + return 0; +} + +static const struct backlight_ops s6e3fc2x01_panel_bl_ops = { + .update_status = s6e3fc2x01_panel_bl_update_status, +}; + +static struct backlight_device * +s6e3fc2x01_create_backlight(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + const struct backlight_properties props = { + .type = BACKLIGHT_PLATFORM, + .brightness = 512, + .max_brightness = 1023, + }; + + return devm_backlight_device_register(dev, dev_name(dev), dev, dsi, + &s6e3fc2x01_panel_bl_ops, &props); +} + +static int s6e3fc2x01_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct samsung_s6e3fc2x01 *ctx; + int ret; + + ctx = devm_drm_panel_alloc(dev, struct samsung_s6e3fc2x01, panel, + &samsung_s6e3fc2x01_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ret = devm_regulator_bulk_get_const(dev, + ARRAY_SIZE(s6e3fc2x01_supplies), + s6e3fc2x01_supplies, + &ctx->supplies); + if (ret) + return dev_err_probe(dev, ret, "Failed to get regulators\n"); + + + /* keep the display on for flicker-free experience */ + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), + "Failed to get reset-gpios\n"); + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_CLOCK_NON_CONTINUOUS | MIPI_DSI_MODE_LPM; + + ctx->panel.prepare_prev_first = true; + + ctx->panel.backlight = s6e3fc2x01_create_backlight(dsi); + if (IS_ERR(ctx->panel.backlight)) + return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight), + "Failed to create backlight\n"); + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "Failed to attach to DSI host: %d\n", ret); + drm_panel_remove(&ctx->panel); + return ret; + } + + return 0; +} + +static void s6e3fc2x01_remove(struct mipi_dsi_device *dsi) +{ + struct samsung_s6e3fc2x01 *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id s6e3fc2x01_of_match[] = { + { .compatible = "samsung,s6e3fc2x01-ams641rw", .data = &ams641rw_mode }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, s6e3fc2x01_of_match); + +static struct mipi_dsi_driver s6e3fc2x01_driver = { + .probe = s6e3fc2x01_probe, + .remove = s6e3fc2x01_remove, + .driver = { + .name = "panel-samsung-s6e3fc2x01", + .of_match_table = s6e3fc2x01_of_match, + }, +}; +module_mipi_dsi_driver(s6e3fc2x01_driver); + +MODULE_AUTHOR("David Heidelberg <david@ixit.cz>"); +MODULE_DESCRIPTION("DRM driver for Samsung S6E3FC2X01 DDIC"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-samsung-s6e3ha2.c b/drivers/gpu/drm/panel/panel-samsung-s6e3ha2.c index 797bbc7a264e..1db0c63b1131 100644 --- a/drivers/gpu/drm/panel/panel-samsung-s6e3ha2.c +++ b/drivers/gpu/drm/panel/panel-samsung-s6e3ha2.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * MIPI-DSI based s6e3ha2 AMOLED 5.7 inch panel driver. * @@ -5,20 +6,19 @@ * Donghwa Lee <dh09.lee@samsung.com> * Hyungwon Hwang <human.hwang@samsung.com> * Hoegeun Kwon <hoegeun.kwon@samsung.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. */ -#include <drm/drmP.h> -#include <drm/drm_mipi_dsi.h> -#include <drm/drm_panel.h> #include <linux/backlight.h> +#include <linux/delay.h> #include <linux/gpio/consumer.h> -#include <linux/of_device.h> +#include <linux/module.h> +#include <linux/of.h> #include <linux/regulator/consumer.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + #define S6E3HA2_MIN_BRIGHTNESS 0 #define S6E3HA2_MAX_BRIGHTNESS 100 #define S6E3HA2_DEFAULT_BRIGHTNESS 80 @@ -214,7 +214,7 @@ static const u8 gamma_tbl[S6E3HA2_NUM_GAMMA_STEPS][S6E3HA2_GAMMA_CMD_CNT] = { 0x00, 0x00 } }; -unsigned char vint_table[S6E3HA2_VINT_STATUS_MAX] = { +static const unsigned char vint_table[S6E3HA2_VINT_STATUS_MAX] = { 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21 }; @@ -458,7 +458,7 @@ static int s6e3ha2_set_brightness(struct backlight_device *bl_dev) return -EINVAL; } - if (bl_dev->props.power > FB_BLANK_NORMAL) + if (bl_dev->props.power > BACKLIGHT_POWER_REDUCED) return -EPERM; s6e3ha2_call_write_func(ret, s6e3ha2_test_key_on_f0(ctx)); @@ -508,7 +508,7 @@ static int s6e3ha2_disable(struct drm_panel *panel) s6e3ha2_call_write_func(ret, mipi_dsi_dcs_set_display_off(dsi)); msleep(40); - ctx->bl_dev->props.power = FB_BLANK_NORMAL; + ctx->bl_dev->props.power = BACKLIGHT_POWER_REDUCED; return 0; } @@ -554,7 +554,7 @@ static int s6e3ha2_prepare(struct drm_panel *panel) if (ret < 0) goto err; - ctx->bl_dev->props.power = FB_BLANK_NORMAL; + ctx->bl_dev->props.power = BACKLIGHT_POWER_REDUCED; return 0; @@ -601,7 +601,7 @@ static int s6e3ha2_enable(struct drm_panel *panel) s6e3ha2_call_write_func(ret, s6e3ha2_test_key_off_f0(ctx)); s6e3ha2_call_write_func(ret, mipi_dsi_dcs_set_display_on(dsi)); - ctx->bl_dev->props.power = FB_BLANK_UNBLANK; + ctx->bl_dev->props.power = BACKLIGHT_POWER_ON; return 0; } @@ -616,7 +616,6 @@ static const struct drm_display_mode s6e3ha2_mode = { .vsync_start = 2560 + 1, .vsync_end = 2560 + 1 + 1, .vtotal = 2560 + 1 + 1 + 15, - .vrefresh = 60, .flags = 0, }; @@ -635,7 +634,6 @@ static const struct drm_display_mode s6e3hf2_mode = { .vsync_start = 2560 + 1, .vsync_end = 2560 + 1 + 1, .vtotal = 2560 + 1 + 1 + 15, - .vrefresh = 60, .flags = 0, }; @@ -644,17 +642,17 @@ 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", + dev_err(panel->dev, "failed to add mode %ux%u@%u\n", ctx->desc->mode->hdisplay, ctx->desc->mode->vdisplay, - ctx->desc->mode->vrefresh); + drm_mode_vrefresh(ctx->desc->mode)); return -ENOMEM; } @@ -683,9 +681,11 @@ static int s6e3ha2_probe(struct mipi_dsi_device *dsi) struct s6e3ha2 *ctx; int ret; - ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); - if (!ctx) - return -ENOMEM; + ctx = devm_drm_panel_alloc(dev, struct s6e3ha2, panel, + &s6e3ha2_drm_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); mipi_dsi_set_drvdata(dsi, ctx); @@ -694,7 +694,9 @@ static int s6e3ha2_probe(struct mipi_dsi_device *dsi) dsi->lanes = 4; dsi->format = MIPI_DSI_FMT_RGB888; - dsi->mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS; + dsi->mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS | + MIPI_DSI_MODE_VIDEO_NO_HFP | MIPI_DSI_MODE_VIDEO_NO_HBP | + MIPI_DSI_MODE_VIDEO_NO_HSA | MIPI_DSI_MODE_NO_EOT_PACKET; ctx->supplies[0].supply = "vdd3"; ctx->supplies[1].supply = "vci"; @@ -729,15 +731,11 @@ static int s6e3ha2_probe(struct mipi_dsi_device *dsi) ctx->bl_dev->props.max_brightness = S6E3HA2_MAX_BRIGHTNESS; ctx->bl_dev->props.brightness = S6E3HA2_DEFAULT_BRIGHTNESS; - ctx->bl_dev->props.power = FB_BLANK_POWERDOWN; + ctx->bl_dev->props.power = BACKLIGHT_POWER_OFF; - drm_panel_init(&ctx->panel); - ctx->panel.dev = dev; - ctx->panel.funcs = &s6e3ha2_drm_funcs; + ctx->panel.prepare_prev_first = true; - ret = drm_panel_add(&ctx->panel); - if (ret < 0) - goto unregister_backlight; + drm_panel_add(&ctx->panel); ret = mipi_dsi_attach(dsi); if (ret < 0) @@ -747,22 +745,18 @@ static int s6e3ha2_probe(struct mipi_dsi_device *dsi) remove_panel: drm_panel_remove(&ctx->panel); - -unregister_backlight: backlight_device_unregister(ctx->bl_dev); return ret; } -static int s6e3ha2_remove(struct mipi_dsi_device *dsi) +static void s6e3ha2_remove(struct mipi_dsi_device *dsi) { struct s6e3ha2 *ctx = mipi_dsi_get_drvdata(dsi); mipi_dsi_detach(dsi); drm_panel_remove(&ctx->panel); backlight_device_unregister(ctx->bl_dev); - - return 0; } static const struct of_device_id s6e3ha2_of_match[] = { diff --git a/drivers/gpu/drm/panel/panel-samsung-s6e3ha8.c b/drivers/gpu/drm/panel/panel-samsung-s6e3ha8.c new file mode 100644 index 000000000000..550e9ef9bb71 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-samsung-s6e3ha8.c @@ -0,0 +1,342 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Generated with linux-mdss-dsi-panel-driver-generator from vendor device tree: +// Copyright (c) 2013, The Linux Foundation. All rights reserved. +// Copyright (c) 2024 Dzmitry Sankouski <dsankouski@gmail.com> + +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regulator/consumer.h> + +#include <drm/display/drm_dsc.h> +#include <drm/display/drm_dsc_helper.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_panel.h> + +struct s6e3ha8 { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct drm_dsc_config dsc; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data *supplies; +}; + +static const struct regulator_bulk_data s6e3ha8_supplies[] = { + { .supply = "vdd3" }, + { .supply = "vci" }, + { .supply = "vddr" }, +}; + +static inline +struct s6e3ha8 *to_s6e3ha8_amb577px01_wqhd(struct drm_panel *panel) +{ + return container_of(panel, struct s6e3ha8, panel); +} + +#define s6e3ha8_test_key_on_lvl2(ctx) \ + mipi_dsi_dcs_write_seq_multi(ctx, 0xf0, 0x5a, 0x5a) +#define s6e3ha8_test_key_off_lvl2(ctx) \ + mipi_dsi_dcs_write_seq_multi(ctx, 0xf0, 0xa5, 0xa5) +#define s6e3ha8_test_key_on_lvl3(ctx) \ + mipi_dsi_dcs_write_seq_multi(ctx, 0xfc, 0x5a, 0x5a) +#define s6e3ha8_test_key_off_lvl3(ctx) \ + mipi_dsi_dcs_write_seq_multi(ctx, 0xfc, 0xa5, 0xa5) +#define s6e3ha8_test_key_on_lvl1(ctx) \ + mipi_dsi_dcs_write_seq_multi(ctx, 0x9f, 0xa5, 0xa5) +#define s6e3ha8_test_key_off_lvl1(ctx) \ + mipi_dsi_dcs_write_seq_multi(ctx, 0x9f, 0x5a, 0x5a) +#define s6e3ha8_afc_off(ctx) \ + mipi_dsi_dcs_write_seq_multi(ctx, 0xe2, 0x00, 0x00) + +static void s6e3ha8_amb577px01_wqhd_reset(struct s6e3ha8 *priv) +{ + gpiod_set_value_cansleep(priv->reset_gpio, 1); + usleep_range(5000, 6000); + gpiod_set_value_cansleep(priv->reset_gpio, 0); + usleep_range(5000, 6000); + gpiod_set_value_cansleep(priv->reset_gpio, 1); + usleep_range(5000, 6000); +} + +static int s6e3ha8_amb577px01_wqhd_on(struct s6e3ha8 *priv) +{ + struct mipi_dsi_device *dsi = priv->dsi; + struct mipi_dsi_multi_context ctx = { .dsi = dsi }; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + s6e3ha8_test_key_on_lvl1(&ctx); + + s6e3ha8_test_key_on_lvl2(&ctx); + mipi_dsi_compression_mode_multi(&ctx, true); + s6e3ha8_test_key_off_lvl2(&ctx); + + mipi_dsi_dcs_exit_sleep_mode_multi(&ctx); + usleep_range(5000, 6000); + + s6e3ha8_test_key_on_lvl2(&ctx); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xf2, 0x13); + s6e3ha8_test_key_off_lvl2(&ctx); + usleep_range(10000, 11000); + + s6e3ha8_test_key_on_lvl2(&ctx); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xf2, 0x13); + s6e3ha8_test_key_off_lvl2(&ctx); + + /* OMOK setting 1 (Initial setting) - Scaler Latch Setting Guide */ + s6e3ha8_test_key_on_lvl2(&ctx); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x07); + /* latch setting 1 : Scaler on/off & address setting & PPS setting -> Image update latch */ + mipi_dsi_dcs_write_seq_multi(&ctx, 0xf2, 0x3c, 0x10); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x0b); + /* latch setting 2 : Ratio change mode -> Image update latch */ + mipi_dsi_dcs_write_seq_multi(&ctx, 0xf2, 0x30); + /* OMOK setting 2 - Seamless setting guide : WQHD */ + mipi_dsi_dcs_write_seq_multi(&ctx, 0x2a, 0x00, 0x00, 0x05, 0x9f); /* CASET */ + mipi_dsi_dcs_write_seq_multi(&ctx, 0x2b, 0x00, 0x00, 0x0b, 0x8f); /* PASET */ + mipi_dsi_dcs_write_seq_multi(&ctx, 0xba, 0x01); /* scaler setup : scaler off */ + s6e3ha8_test_key_off_lvl2(&ctx); + + mipi_dsi_dcs_write_seq_multi(&ctx, 0x35, 0x00); /* TE Vsync ON */ + + s6e3ha8_test_key_on_lvl2(&ctx); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xed, 0x4c); /* ERR_FG */ + s6e3ha8_test_key_off_lvl2(&ctx); + + s6e3ha8_test_key_on_lvl3(&ctx); + /* FFC Setting 897.6Mbps */ + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc5, 0x0d, 0x10, 0xb4, 0x3e, 0x01); + s6e3ha8_test_key_off_lvl3(&ctx); + + s6e3ha8_test_key_on_lvl2(&ctx); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb9, + 0x00, 0xb0, 0x81, 0x09, 0x00, 0x00, 0x00, + 0x11, 0x03); /* TSP HSYNC Setting */ + s6e3ha8_test_key_off_lvl2(&ctx); + + s6e3ha8_test_key_on_lvl2(&ctx); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x03); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xf6, 0x43); + s6e3ha8_test_key_off_lvl2(&ctx); + + s6e3ha8_test_key_on_lvl2(&ctx); + /* Brightness condition set */ + mipi_dsi_dcs_write_seq_multi(&ctx, 0xca, + 0x07, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x00, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb1, 0x00, 0x0c); /* AID Set : 0% */ + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb5, + 0x19, 0xdc, 0x16, 0x01, 0x34, 0x67, 0x9a, + 0xcd, 0x01, 0x22, 0x33, 0x44, 0x00, 0x00, + 0x05, 0x55, 0xcc, 0x0c, 0x01, 0x11, 0x11, + 0x10); /* MPS/ELVSS Setting */ + mipi_dsi_dcs_write_seq_multi(&ctx, 0xf4, 0xeb, 0x28); /* VINT */ + mipi_dsi_dcs_write_seq_multi(&ctx, 0xf7, 0x03); /* Gamma, LTPS(AID) update */ + s6e3ha8_test_key_off_lvl2(&ctx); + + s6e3ha8_test_key_off_lvl1(&ctx); + + return ctx.accum_err; +} + +static int s6e3ha8_enable(struct drm_panel *panel) +{ + struct s6e3ha8 *priv = to_s6e3ha8_amb577px01_wqhd(panel); + struct mipi_dsi_device *dsi = priv->dsi; + struct mipi_dsi_multi_context ctx = { .dsi = dsi }; + + s6e3ha8_test_key_on_lvl1(&ctx); + mipi_dsi_dcs_set_display_on_multi(&ctx); + s6e3ha8_test_key_off_lvl1(&ctx); + + return ctx.accum_err; +} + +static int s6e3ha8_disable(struct drm_panel *panel) +{ + struct s6e3ha8 *priv = to_s6e3ha8_amb577px01_wqhd(panel); + struct mipi_dsi_device *dsi = priv->dsi; + struct mipi_dsi_multi_context ctx = { .dsi = dsi }; + + s6e3ha8_test_key_on_lvl1(&ctx); + mipi_dsi_dcs_set_display_off_multi(&ctx); + s6e3ha8_test_key_off_lvl1(&ctx); + mipi_dsi_msleep(&ctx, 20); + + s6e3ha8_test_key_on_lvl2(&ctx); + s6e3ha8_afc_off(&ctx); + s6e3ha8_test_key_off_lvl2(&ctx); + + mipi_dsi_msleep(&ctx, 160); + + return ctx.accum_err; +} + +static int s6e3ha8_amb577px01_wqhd_prepare(struct drm_panel *panel) +{ + struct s6e3ha8 *priv = to_s6e3ha8_amb577px01_wqhd(panel); + struct mipi_dsi_device *dsi = priv->dsi; + struct mipi_dsi_multi_context ctx = { .dsi = dsi }; + struct drm_dsc_picture_parameter_set pps; + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(s6e3ha8_supplies), priv->supplies); + if (ret < 0) + return ret; + mipi_dsi_msleep(&ctx, 120); + s6e3ha8_amb577px01_wqhd_reset(priv); + + ret = s6e3ha8_amb577px01_wqhd_on(priv); + if (ret < 0) { + gpiod_set_value_cansleep(priv->reset_gpio, 1); + goto err; + } + + drm_dsc_pps_payload_pack(&pps, &priv->dsc); + + s6e3ha8_test_key_on_lvl1(&ctx); + mipi_dsi_picture_parameter_set_multi(&ctx, &pps); + s6e3ha8_test_key_off_lvl1(&ctx); + + mipi_dsi_msleep(&ctx, 28); + + return ctx.accum_err; +err: + regulator_bulk_disable(ARRAY_SIZE(s6e3ha8_supplies), priv->supplies); + return ret; +} + +static int s6e3ha8_amb577px01_wqhd_unprepare(struct drm_panel *panel) +{ + struct s6e3ha8 *priv = to_s6e3ha8_amb577px01_wqhd(panel); + + return regulator_bulk_disable(ARRAY_SIZE(s6e3ha8_supplies), priv->supplies); +} + +static const struct drm_display_mode s6e3ha8_amb577px01_wqhd_mode = { + .clock = (1440 + 116 + 44 + 120) * (2960 + 120 + 80 + 124) * 60 / 1000, + .hdisplay = 1440, + .hsync_start = 1440 + 116, + .hsync_end = 1440 + 116 + 44, + .htotal = 1440 + 116 + 44 + 120, + .vdisplay = 2960, + .vsync_start = 2960 + 120, + .vsync_end = 2960 + 120 + 80, + .vtotal = 2960 + 120 + 80 + 124, + .width_mm = 64, + .height_mm = 132, +}; + +static int s6e3ha8_amb577px01_wqhd_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + return drm_connector_helper_get_modes_fixed(connector, &s6e3ha8_amb577px01_wqhd_mode); +} + +static const struct drm_panel_funcs s6e3ha8_amb577px01_wqhd_panel_funcs = { + .prepare = s6e3ha8_amb577px01_wqhd_prepare, + .unprepare = s6e3ha8_amb577px01_wqhd_unprepare, + .get_modes = s6e3ha8_amb577px01_wqhd_get_modes, + .enable = s6e3ha8_enable, + .disable = s6e3ha8_disable, +}; + +static int s6e3ha8_amb577px01_wqhd_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct s6e3ha8 *priv; + int ret; + + priv = devm_drm_panel_alloc(dev, struct s6e3ha8, panel, + &s6e3ha8_amb577px01_wqhd_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(priv)) + return PTR_ERR(priv); + + ret = devm_regulator_bulk_get_const(dev, ARRAY_SIZE(s6e3ha8_supplies), + s6e3ha8_supplies, + &priv->supplies); + if (ret < 0) { + dev_err(dev, "failed to get regulators: %d\n", ret); + return ret; + } + + priv->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(priv->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(priv->reset_gpio), + "Failed to get reset-gpios\n"); + + priv->dsi = dsi; + mipi_dsi_set_drvdata(dsi, priv); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS | + MIPI_DSI_MODE_VIDEO_NO_HFP | MIPI_DSI_MODE_VIDEO_NO_HBP | + MIPI_DSI_MODE_VIDEO_NO_HSA | MIPI_DSI_MODE_NO_EOT_PACKET; + + priv->panel.prepare_prev_first = true; + + drm_panel_add(&priv->panel); + + /* This panel only supports DSC; unconditionally enable it */ + dsi->dsc = &priv->dsc; + + priv->dsc.dsc_version_major = 1; + priv->dsc.dsc_version_minor = 1; + + priv->dsc.slice_height = 40; + priv->dsc.slice_width = 720; + WARN_ON(1440 % priv->dsc.slice_width); + priv->dsc.slice_count = 1440 / priv->dsc.slice_width; + priv->dsc.bits_per_component = 8; + priv->dsc.bits_per_pixel = 8 << 4; /* 4 fractional bits */ + priv->dsc.block_pred_enable = true; + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "Failed to attach to DSI host: %d\n", ret); + drm_panel_remove(&priv->panel); + return ret; + } + + return 0; +} + +static void s6e3ha8_amb577px01_wqhd_remove(struct mipi_dsi_device *dsi) +{ + struct s6e3ha8 *priv = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&priv->panel); +} + +static const struct of_device_id s6e3ha8_amb577px01_wqhd_of_match[] = { + { .compatible = "samsung,s6e3ha8" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, s6e3ha8_amb577px01_wqhd_of_match); + +static struct mipi_dsi_driver s6e3ha8_amb577px01_wqhd_driver = { + .probe = s6e3ha8_amb577px01_wqhd_probe, + .remove = s6e3ha8_amb577px01_wqhd_remove, + .driver = { + .name = "panel-s6e3ha8", + .of_match_table = s6e3ha8_amb577px01_wqhd_of_match, + }, +}; +module_mipi_dsi_driver(s6e3ha8_amb577px01_wqhd_driver); + +MODULE_AUTHOR("Dzmitry Sankouski <dsankouski@gmail.com>"); +MODULE_DESCRIPTION("DRM driver for S6E3HA8 panel"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-samsung-s6e63j0x03.c b/drivers/gpu/drm/panel/panel-samsung-s6e63j0x03.c index aeb32aa58899..6f3d39556f92 100644 --- a/drivers/gpu/drm/panel/panel-samsung-s6e63j0x03.c +++ b/drivers/gpu/drm/panel/panel-samsung-s6e63j0x03.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * MIPI-DSI based S6E63J0X03 AMOLED lcd 1.63 inch panel driver. * @@ -5,20 +6,21 @@ * * Inki Dae <inki.dae@samsung.com> * Hoegeun Kwon <hoegeun.kwon@samsung.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. */ -#include <drm/drmP.h> -#include <drm/drm_mipi_dsi.h> -#include <drm/drm_panel.h> #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> + #include <video/mipi_display.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + #define MCS_LEVEL2_KEY 0xf0 #define MCS_MTP_KEY 0xf1 #define MCS_MTP_SET3 0xd4 @@ -50,7 +52,6 @@ static const struct drm_display_mode default_mode = { .vsync_start = 320 + 150, .vsync_end = 320 + 150 + 1, .vtotal = 320 + 150 + 1 + 2, - .vrefresh = 30, .flags = 0, }; @@ -225,7 +226,7 @@ static int s6e63j0x03_disable(struct drm_panel *panel) if (ret < 0) return ret; - ctx->bl_dev->props.power = FB_BLANK_NORMAL; + ctx->bl_dev->props.power = BACKLIGHT_POWER_REDUCED; ret = mipi_dsi_dcs_enter_sleep_mode(dsi); if (ret < 0) @@ -245,7 +246,7 @@ static int s6e63j0x03_unprepare(struct drm_panel *panel) if (ret < 0) return ret; - ctx->bl_dev->props.power = FB_BLANK_POWERDOWN; + ctx->bl_dev->props.power = BACKLIGHT_POWER_OFF; return 0; } @@ -332,7 +333,7 @@ static int s6e63j0x03_prepare(struct drm_panel *panel) if (ret < 0) goto err; - ctx->bl_dev->props.power = FB_BLANK_NORMAL; + ctx->bl_dev->props.power = BACKLIGHT_POWER_REDUCED; return 0; @@ -393,21 +394,21 @@ static int s6e63j0x03_enable(struct drm_panel *panel) if (ret < 0) return ret; - ctx->bl_dev->props.power = FB_BLANK_UNBLANK; + ctx->bl_dev->props.power = BACKLIGHT_POWER_ON; 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", + dev_err(panel->dev, "failed to add mode %ux%u@%u\n", default_mode.hdisplay, default_mode.vdisplay, - default_mode.vrefresh); + drm_mode_vrefresh(&default_mode)); return -ENOMEM; } @@ -436,9 +437,11 @@ static int s6e63j0x03_probe(struct mipi_dsi_device *dsi) struct s6e63j0x03 *ctx; int ret; - ctx = devm_kzalloc(dev, sizeof(struct s6e63j0x03), GFP_KERNEL); - if (!ctx) - return -ENOMEM; + ctx = devm_drm_panel_alloc(dev, struct s6e63j0x03, panel, + &s6e63j0x03_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); mipi_dsi_set_drvdata(dsi, ctx); @@ -446,42 +449,34 @@ static int s6e63j0x03_probe(struct mipi_dsi_device *dsi) dsi->lanes = 1; dsi->format = MIPI_DSI_FMT_RGB888; - dsi->mode_flags = MIPI_DSI_MODE_EOT_PACKET; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO_NO_HFP | + MIPI_DSI_MODE_VIDEO_NO_HBP | MIPI_DSI_MODE_VIDEO_NO_HSA; ctx->supplies[0].supply = "vdd3"; ctx->supplies[1].supply = "vci"; ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), ctx->supplies); - if (ret < 0) { - dev_err(dev, "failed to get regulators: %d\n", ret); - return ret; - } + if (ret < 0) + return dev_err_probe(dev, ret, "failed to get regulators\n"); ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); - if (IS_ERR(ctx->reset_gpio)) { - dev_err(dev, "cannot get reset-gpio: %ld\n", - PTR_ERR(ctx->reset_gpio)); - return PTR_ERR(ctx->reset_gpio); - } + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), + "cannot get reset-gpio\n"); - drm_panel_init(&ctx->panel); - ctx->panel.dev = dev; - ctx->panel.funcs = &s6e63j0x03_funcs; + ctx->panel.prepare_prev_first = true; ctx->bl_dev = backlight_device_register("s6e63j0x03", dev, ctx, &s6e63j0x03_bl_ops, NULL); - if (IS_ERR(ctx->bl_dev)) { - dev_err(dev, "failed to register backlight device\n"); - return PTR_ERR(ctx->bl_dev); - } + if (IS_ERR(ctx->bl_dev)) + return dev_err_probe(dev, PTR_ERR(ctx->bl_dev), + "failed to register backlight device\n"); ctx->bl_dev->props.max_brightness = MAX_BRIGHTNESS; ctx->bl_dev->props.brightness = DEFAULT_BRIGHTNESS; - ctx->bl_dev->props.power = FB_BLANK_POWERDOWN; + ctx->bl_dev->props.power = BACKLIGHT_POWER_OFF; - ret = drm_panel_add(&ctx->panel); - if (ret < 0) - goto unregister_backlight; + drm_panel_add(&ctx->panel); ret = mipi_dsi_attach(dsi); if (ret < 0) @@ -491,14 +486,12 @@ static int s6e63j0x03_probe(struct mipi_dsi_device *dsi) remove_panel: drm_panel_remove(&ctx->panel); - -unregister_backlight: backlight_device_unregister(ctx->bl_dev); return ret; } -static int s6e63j0x03_remove(struct mipi_dsi_device *dsi) +static void s6e63j0x03_remove(struct mipi_dsi_device *dsi) { struct s6e63j0x03 *ctx = mipi_dsi_get_drvdata(dsi); @@ -506,8 +499,6 @@ static int s6e63j0x03_remove(struct mipi_dsi_device *dsi) drm_panel_remove(&ctx->panel); backlight_device_unregister(ctx->bl_dev); - - return 0; } static const struct of_device_id s6e63j0x03_of_match[] = { diff --git a/drivers/gpu/drm/panel/panel-samsung-s6e63m0-dsi.c b/drivers/gpu/drm/panel/panel-samsung-s6e63m0-dsi.c new file mode 100644 index 000000000000..a89d925fdfb2 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-samsung-s6e63m0-dsi.c @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * DSI interface to the Samsung S6E63M0 panel. + * (C) 2019 Linus Walleij + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/mod_devicetable.h> + +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_print.h> + +#include "panel-samsung-s6e63m0.h" + +#define MCS_GLOBAL_PARAM 0xb0 +#define S6E63M0_DSI_MAX_CHUNK 15 /* CMD + 15 bytes max */ + +static int s6e63m0_dsi_dcs_read(struct device *dev, void *trsp, + const u8 cmd, u8 *data) +{ + struct mipi_dsi_device *dsi = to_mipi_dsi_device(dev); + int ret; + + ret = mipi_dsi_dcs_read(dsi, cmd, data, 1); + if (ret < 0) { + dev_err(dev, "could not read DCS CMD %02x\n", cmd); + return ret; + } + + dev_dbg(dev, "DSI read CMD %02x = %02x\n", cmd, *data); + + return 0; +} + +static int s6e63m0_dsi_dcs_write(struct device *dev, void *trsp, + const u8 *data, size_t len) +{ + struct mipi_dsi_device *dsi = to_mipi_dsi_device(dev); + const u8 *seqp = data; + u8 cmd; + u8 cmdwritten; + int remain; + int chunk; + int ret; + + dev_dbg(dev, "DSI writing dcs seq: %*ph\n", (int)len, data); + + /* Pick out and skip past the DCS command */ + cmd = *seqp; + seqp++; + cmdwritten = 0; + remain = len - 1; + chunk = remain; + + /* Send max S6E63M0_DSI_MAX_CHUNK bytes at a time */ + if (chunk > S6E63M0_DSI_MAX_CHUNK) + chunk = S6E63M0_DSI_MAX_CHUNK; + ret = mipi_dsi_dcs_write(dsi, cmd, seqp, chunk); + if (ret < 0) { + dev_err(dev, "error sending DCS command seq cmd %02x\n", cmd); + return ret; + } + cmdwritten += chunk; + seqp += chunk; + + while (cmdwritten < remain) { + chunk = remain - cmdwritten; + if (chunk > S6E63M0_DSI_MAX_CHUNK) + chunk = S6E63M0_DSI_MAX_CHUNK; + ret = mipi_dsi_dcs_write(dsi, MCS_GLOBAL_PARAM, &cmdwritten, 1); + if (ret < 0) { + dev_err(dev, "error sending CMD %02x global param %02x\n", + cmd, cmdwritten); + return ret; + } + ret = mipi_dsi_dcs_write(dsi, cmd, seqp, chunk); + if (ret < 0) { + dev_err(dev, "error sending CMD %02x chunk\n", cmd); + return ret; + } + cmdwritten += chunk; + seqp += chunk; + } + dev_dbg(dev, "sent command %02x %02x bytes\n", cmd, cmdwritten); + + usleep_range(8000, 9000); + + return 0; +} + +static int s6e63m0_dsi_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + int ret; + + dsi->lanes = 2; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->hs_rate = 349440000; + dsi->lp_rate = 9600000; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | + MIPI_DSI_MODE_VIDEO_BURST; + + ret = s6e63m0_probe(dev, NULL, s6e63m0_dsi_dcs_read, + s6e63m0_dsi_dcs_write, true); + if (ret) + return ret; + + ret = mipi_dsi_attach(dsi); + if (ret < 0) + s6e63m0_remove(dev); + + return ret; +} + +static void s6e63m0_dsi_remove(struct mipi_dsi_device *dsi) +{ + mipi_dsi_detach(dsi); + s6e63m0_remove(&dsi->dev); +} + +static const struct of_device_id s6e63m0_dsi_of_match[] = { + { .compatible = "samsung,s6e63m0" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, s6e63m0_dsi_of_match); + +static struct mipi_dsi_driver s6e63m0_dsi_driver = { + .probe = s6e63m0_dsi_probe, + .remove = s6e63m0_dsi_remove, + .driver = { + .name = "panel-samsung-s6e63m0", + .of_match_table = s6e63m0_dsi_of_match, + }, +}; +module_mipi_dsi_driver(s6e63m0_dsi_driver); + +MODULE_AUTHOR("Linus Walleij <linusw@kernel.org>"); +MODULE_DESCRIPTION("s6e63m0 LCD DSI Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-samsung-s6e63m0-spi.c b/drivers/gpu/drm/panel/panel-samsung-s6e63m0-spi.c new file mode 100644 index 000000000000..d99afcc672ca --- /dev/null +++ b/drivers/gpu/drm/panel/panel-samsung-s6e63m0-spi.c @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/spi/spi.h> +#include <linux/delay.h> + +#include <drm/drm_mipi_dbi.h> +#include <drm/drm_print.h> + +#include "panel-samsung-s6e63m0.h" + +static const u8 s6e63m0_dbi_read_commands[] = { + MCS_READ_ID1, + MCS_READ_ID2, + MCS_READ_ID3, + 0, /* sentinel */ +}; + +static int s6e63m0_spi_dcs_read(struct device *dev, void *trsp, + const u8 cmd, u8 *data) +{ + struct mipi_dbi *dbi = trsp; + int ret; + + ret = mipi_dbi_command_read(dbi, cmd, data); + if (ret) + dev_err(dev, "error on DBI read command %02x\n", cmd); + + return ret; +} + +static int s6e63m0_spi_dcs_write(struct device *dev, void *trsp, + const u8 *data, size_t len) +{ + struct mipi_dbi *dbi = trsp; + int ret; + + ret = mipi_dbi_command_stackbuf(dbi, data[0], (data + 1), (len - 1)); + usleep_range(300, 310); + + return ret; +} + +static int s6e63m0_spi_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct mipi_dbi *dbi; + int ret; + + dbi = devm_kzalloc(dev, sizeof(*dbi), GFP_KERNEL); + if (!dbi) + return -ENOMEM; + + ret = mipi_dbi_spi_init(spi, dbi, NULL); + if (ret) + return dev_err_probe(dev, ret, "MIPI DBI init failed\n"); + /* Register our custom MCS read commands */ + dbi->read_commands = s6e63m0_dbi_read_commands; + + return s6e63m0_probe(dev, dbi, s6e63m0_spi_dcs_read, + s6e63m0_spi_dcs_write, false); +} + +static void s6e63m0_spi_remove(struct spi_device *spi) +{ + s6e63m0_remove(&spi->dev); +} + +static const struct of_device_id s6e63m0_spi_of_match[] = { + { .compatible = "samsung,s6e63m0" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, s6e63m0_spi_of_match); + +static struct spi_driver s6e63m0_spi_driver = { + .probe = s6e63m0_spi_probe, + .remove = s6e63m0_spi_remove, + .driver = { + .name = "panel-samsung-s6e63m0", + .of_match_table = s6e63m0_spi_of_match, + }, +}; +module_spi_driver(s6e63m0_spi_driver); + +MODULE_AUTHOR("PaweÅ‚ Chmiel <pawel.mikolaj.chmiel@gmail.com>"); +MODULE_DESCRIPTION("s6e63m0 LCD SPI Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-samsung-s6e63m0.c b/drivers/gpu/drm/panel/panel-samsung-s6e63m0.c new file mode 100644 index 000000000000..ea241c89593b --- /dev/null +++ b/drivers/gpu/drm/panel/panel-samsung-s6e63m0.c @@ -0,0 +1,739 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * S6E63M0 AMOLED LCD drm_panel driver. + * + * Copyright (C) 2019 PaweÅ‚ Chmiel <pawel.mikolaj.chmiel@gmail.com> + * Derived from drivers/gpu/drm/panel-samsung-ld9040.c + * + * Andrzej Hajda <a.hajda@samsung.com> + */ + +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +#include <linux/backlight.h> +#include <linux/delay.h> +#include <linux/export.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/property.h> +#include <linux/regulator/consumer.h> +#include <linux/media-bus-format.h> + +#include <video/mipi_display.h> + +#include "panel-samsung-s6e63m0.h" + +#define S6E63M0_LCD_ID_VALUE_M2 0xA4 +#define S6E63M0_LCD_ID_VALUE_SM2 0xB4 +#define S6E63M0_LCD_ID_VALUE_SM2_1 0xB6 + +#define NUM_GAMMA_LEVELS 28 +#define GAMMA_TABLE_COUNT 23 + +#define MAX_BRIGHTNESS (NUM_GAMMA_LEVELS - 1) + +/* array of gamma tables for gamma value 2.2 */ +static u8 const s6e63m0_gamma_22[NUM_GAMMA_LEVELS][GAMMA_TABLE_COUNT] = { + /* 30 cd */ + { MCS_PGAMMACTL, 0x02, + 0x18, 0x08, 0x24, 0xA1, 0x51, 0x7B, 0xCE, + 0xCB, 0xC2, 0xC7, 0xCB, 0xBC, 0xDA, 0xDD, + 0xD3, 0x00, 0x53, 0x00, 0x52, 0x00, 0x6F, }, + /* 40 cd */ + { MCS_PGAMMACTL, 0x02, + 0x18, 0x08, 0x24, 0x97, 0x58, 0x71, 0xCC, + 0xCB, 0xC0, 0xC5, 0xC9, 0xBA, 0xD9, 0xDC, + 0xD1, 0x00, 0x5B, 0x00, 0x5A, 0x00, 0x7A, }, + /* 50 cd */ + { MCS_PGAMMACTL, 0x02, + 0x18, 0x08, 0x24, 0x96, 0x58, 0x72, 0xCB, + 0xCA, 0xBF, 0xC6, 0xC9, 0xBA, 0xD6, 0xD9, + 0xCD, 0x00, 0x61, 0x00, 0x61, 0x00, 0x83, }, + /* 60 cd */ + { MCS_PGAMMACTL, 0x02, + 0x18, 0x08, 0x24, 0x91, 0x5E, 0x6E, 0xC9, + 0xC9, 0xBD, 0xC4, 0xC9, 0xB8, 0xD3, 0xD7, + 0xCA, 0x00, 0x69, 0x00, 0x67, 0x00, 0x8D, }, + /* 70 cd */ + { MCS_PGAMMACTL, 0x02, + 0x18, 0x08, 0x24, 0x8E, 0x62, 0x6B, 0xC7, + 0xC9, 0xBB, 0xC3, 0xC7, 0xB7, 0xD3, 0xD7, + 0xCA, 0x00, 0x6E, 0x00, 0x6C, 0x00, 0x94, }, + /* 80 cd */ + { MCS_PGAMMACTL, 0x02, + 0x18, 0x08, 0x24, 0x89, 0x68, 0x65, 0xC9, + 0xC9, 0xBC, 0xC1, 0xC5, 0xB6, 0xD2, 0xD5, + 0xC9, 0x00, 0x73, 0x00, 0x72, 0x00, 0x9A, }, + /* 90 cd */ + { MCS_PGAMMACTL, 0x02, + 0x18, 0x08, 0x24, 0x89, 0x69, 0x64, 0xC7, + 0xC8, 0xBB, 0xC0, 0xC5, 0xB4, 0xD2, 0xD5, + 0xC9, 0x00, 0x77, 0x00, 0x76, 0x00, 0xA0, }, + /* 100 cd */ + { MCS_PGAMMACTL, 0x02, + 0x18, 0x08, 0x24, 0x86, 0x69, 0x60, 0xC6, + 0xC8, 0xBA, 0xBF, 0xC4, 0xB4, 0xD0, 0xD4, + 0xC6, 0x00, 0x7C, 0x00, 0x7A, 0x00, 0xA7, }, + /* 110 cd */ + { MCS_PGAMMACTL, 0x02, + 0x18, 0x08, 0x24, 0x86, 0x6A, 0x60, 0xC5, + 0xC7, 0xBA, 0xBD, 0xC3, 0xB2, 0xD0, 0xD4, + 0xC5, 0x00, 0x80, 0x00, 0x7E, 0x00, 0xAD, }, + /* 120 cd */ + { MCS_PGAMMACTL, 0x02, + 0x18, 0x08, 0x24, 0x82, 0x6B, 0x5E, 0xC4, + 0xC8, 0xB9, 0xBD, 0xC2, 0xB1, 0xCE, 0xD2, + 0xC4, 0x00, 0x85, 0x00, 0x82, 0x00, 0xB3, }, + /* 130 cd */ + { MCS_PGAMMACTL, 0x02, + 0x18, 0x08, 0x24, 0x8C, 0x6C, 0x60, 0xC3, + 0xC7, 0xB9, 0xBC, 0xC1, 0xAF, 0xCE, 0xD2, + 0xC3, 0x00, 0x88, 0x00, 0x86, 0x00, 0xB8, }, + /* 140 cd */ + { MCS_PGAMMACTL, 0x02, + 0x18, 0x08, 0x24, 0x80, 0x6C, 0x5F, 0xC1, + 0xC6, 0xB7, 0xBC, 0xC1, 0xAE, 0xCD, 0xD0, + 0xC2, 0x00, 0x8C, 0x00, 0x8A, 0x00, 0xBE, }, + /* 150 cd */ + { MCS_PGAMMACTL, 0x02, + 0x18, 0x08, 0x24, 0x80, 0x6E, 0x5F, 0xC1, + 0xC6, 0xB6, 0xBC, 0xC0, 0xAE, 0xCC, 0xD0, + 0xC2, 0x00, 0x8F, 0x00, 0x8D, 0x00, 0xC2, }, + /* 160 cd */ + { MCS_PGAMMACTL, 0x02, + 0x18, 0x08, 0x24, 0x7F, 0x6E, 0x5F, 0xC0, + 0xC6, 0xB5, 0xBA, 0xBF, 0xAD, 0xCB, 0xCF, + 0xC0, 0x00, 0x94, 0x00, 0x91, 0x00, 0xC8, }, + /* 170 cd */ + { MCS_PGAMMACTL, 0x02, + 0x18, 0x08, 0x24, 0x7C, 0x6D, 0x5C, 0xC0, + 0xC6, 0xB4, 0xBB, 0xBE, 0xAD, 0xCA, 0xCF, + 0xC0, 0x00, 0x96, 0x00, 0x94, 0x00, 0xCC, }, + /* 180 cd */ + { MCS_PGAMMACTL, 0x02, + 0x18, 0x08, 0x24, 0x7B, 0x6D, 0x5B, 0xC0, + 0xC5, 0xB3, 0xBA, 0xBE, 0xAD, 0xCA, 0xCE, + 0xBF, 0x00, 0x99, 0x00, 0x97, 0x00, 0xD0, }, + /* 190 cd */ + { MCS_PGAMMACTL, 0x02, + 0x18, 0x08, 0x24, 0x7A, 0x6D, 0x59, 0xC1, + 0xC5, 0xB4, 0xB8, 0xBD, 0xAC, 0xC9, 0xCE, + 0xBE, 0x00, 0x9D, 0x00, 0x9A, 0x00, 0xD5, }, + /* 200 cd */ + { MCS_PGAMMACTL, 0x02, + 0x18, 0x08, 0x24, 0x79, 0x6D, 0x58, 0xC1, + 0xC4, 0xB4, 0xB6, 0xBD, 0xAA, 0xCA, 0xCD, + 0xBE, 0x00, 0x9F, 0x00, 0x9D, 0x00, 0xD9, }, + /* 210 cd */ + { MCS_PGAMMACTL, 0x02, + 0x18, 0x08, 0x24, 0x79, 0x6D, 0x57, 0xC0, + 0xC4, 0xB4, 0xB7, 0xBD, 0xAA, 0xC8, 0xCC, + 0xBD, 0x00, 0xA2, 0x00, 0xA0, 0x00, 0xDD, }, + /* 220 cd */ + { MCS_PGAMMACTL, 0x02, + 0x18, 0x08, 0x24, 0x78, 0x6F, 0x58, 0xBF, + 0xC4, 0xB3, 0xB5, 0xBB, 0xA9, 0xC8, 0xCC, + 0xBC, 0x00, 0xA6, 0x00, 0xA3, 0x00, 0xE2, }, + /* 230 cd */ + { MCS_PGAMMACTL, 0x02, + 0x18, 0x08, 0x24, 0x75, 0x6F, 0x56, 0xBF, + 0xC3, 0xB2, 0xB6, 0xBB, 0xA8, 0xC7, 0xCB, + 0xBC, 0x00, 0xA8, 0x00, 0xA6, 0x00, 0xE6, }, + /* 240 cd */ + { MCS_PGAMMACTL, 0x02, + 0x18, 0x08, 0x24, 0x76, 0x6F, 0x56, 0xC0, + 0xC3, 0xB2, 0xB5, 0xBA, 0xA8, 0xC6, 0xCB, + 0xBB, 0x00, 0xAA, 0x00, 0xA8, 0x00, 0xE9, }, + /* 250 cd */ + { MCS_PGAMMACTL, 0x02, + 0x18, 0x08, 0x24, 0x74, 0x6D, 0x54, 0xBF, + 0xC3, 0xB2, 0xB4, 0xBA, 0xA7, 0xC6, 0xCA, + 0xBA, 0x00, 0xAD, 0x00, 0xAB, 0x00, 0xED, }, + /* 260 cd */ + { MCS_PGAMMACTL, 0x02, + 0x18, 0x08, 0x24, 0x74, 0x6E, 0x54, 0xBD, + 0xC2, 0xB0, 0xB5, 0xBA, 0xA7, 0xC5, 0xC9, + 0xBA, 0x00, 0xB0, 0x00, 0xAE, 0x00, 0xF1, }, + /* 270 cd */ + { MCS_PGAMMACTL, 0x02, + 0x18, 0x08, 0x24, 0x71, 0x6C, 0x50, 0xBD, + 0xC3, 0xB0, 0xB4, 0xB8, 0xA6, 0xC6, 0xC9, + 0xBB, 0x00, 0xB2, 0x00, 0xB1, 0x00, 0xF4, }, + /* 280 cd */ + { MCS_PGAMMACTL, 0x02, + 0x18, 0x08, 0x24, 0x6E, 0x6C, 0x4D, 0xBE, + 0xC3, 0xB1, 0xB3, 0xB8, 0xA5, 0xC6, 0xC8, + 0xBB, 0x00, 0xB4, 0x00, 0xB3, 0x00, 0xF7, }, + /* 290 cd */ + { MCS_PGAMMACTL, 0x02, + 0x18, 0x08, 0x24, 0x71, 0x70, 0x50, 0xBD, + 0xC1, 0xB0, 0xB2, 0xB8, 0xA4, 0xC6, 0xC7, + 0xBB, 0x00, 0xB6, 0x00, 0xB6, 0x00, 0xFA, }, + /* 300 cd */ + { MCS_PGAMMACTL, 0x02, + 0x18, 0x08, 0x24, 0x70, 0x6E, 0x4E, 0xBC, + 0xC0, 0xAF, 0xB3, 0xB8, 0xA5, 0xC5, 0xC7, + 0xBB, 0x00, 0xB9, 0x00, 0xB8, 0x00, 0xFC, }, +}; + +#define NUM_ACL_LEVELS 7 +#define ACL_TABLE_COUNT 28 + +static u8 const s6e63m0_acl[NUM_ACL_LEVELS][ACL_TABLE_COUNT] = { + /* NULL ACL */ + { MCS_BCMODE, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00 }, + /* 40P ACL */ + { MCS_BCMODE, + 0x4D, 0x96, 0x1D, 0x00, 0x00, 0x01, 0xDF, 0x00, + 0x00, 0x03, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x06, 0x0C, 0x11, 0x16, 0x1C, 0x21, 0x26, + 0x2B, 0x31, 0x36 }, + /* 43P ACL */ + { MCS_BCMODE, + 0x4D, 0x96, 0x1D, 0x00, 0x00, 0x01, 0xDF, 0x00, + 0x00, 0x03, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x07, 0x0C, 0x12, 0x18, 0x1E, 0x23, 0x29, + 0x2F, 0x34, 0x3A }, + /* 45P ACL */ + { MCS_BCMODE, + 0x4D, 0x96, 0x1D, 0x00, 0x00, 0x01, 0xDF, 0x00, + 0x00, 0x03, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x07, 0x0D, 0x13, 0x19, 0x1F, 0x25, 0x2B, + 0x31, 0x37, 0x3D }, + /* 47P ACL */ + { MCS_BCMODE, + 0x4D, 0x96, 0x1D, 0x00, 0x00, 0x01, 0xDF, 0x00, + 0x00, 0x03, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x07, 0x0E, 0x14, 0x1B, 0x21, 0x27, 0x2E, + 0x34, 0x3B, 0x41 }, + /* 48P ACL */ + { MCS_BCMODE, + 0x4D, 0x96, 0x1D, 0x00, 0x00, 0x01, 0xDF, 0x00, + 0x00, 0x03, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x08, 0x0E, 0x15, 0x1B, 0x22, 0x29, 0x2F, + 0x36, 0x3C, 0x43 }, + /* 50P ACL */ + { MCS_BCMODE, + 0x4D, 0x96, 0x1D, 0x00, 0x00, 0x01, 0xDF, 0x00, + 0x00, 0x03, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x08, 0x0F, 0x16, 0x1D, 0x24, 0x2A, 0x31, + 0x38, 0x3F, 0x46 }, +}; + +/* This tells us which ACL level goes with which gamma */ +static u8 const s6e63m0_acl_per_gamma[NUM_GAMMA_LEVELS] = { + /* 30 - 60 cd: ACL off/NULL */ + 0, 0, 0, 0, + /* 70 - 250 cd: 40P ACL */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + /* 260 - 300 cd: 50P ACL */ + 6, 6, 6, 6, 6, +}; + +/* The ELVSS backlight regulator has 5 levels */ +#define S6E63M0_ELVSS_LEVELS 5 + +static u8 const s6e63m0_elvss_offsets[S6E63M0_ELVSS_LEVELS] = { + 0x00, /* not set */ + 0x0D, /* 30 cd - 100 cd */ + 0x09, /* 110 cd - 160 cd */ + 0x07, /* 170 cd - 200 cd */ + 0x00, /* 210 cd - 300 cd */ +}; + +/* This tells us which ELVSS level goes with which gamma */ +static u8 const s6e63m0_elvss_per_gamma[NUM_GAMMA_LEVELS] = { + /* 30 - 100 cd */ + 1, 1, 1, 1, 1, 1, 1, 1, + /* 110 - 160 cd */ + 2, 2, 2, 2, 2, 2, + /* 170 - 200 cd */ + 3, 3, 3, 3, + /* 210 - 300 cd */ + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, +}; + +struct s6e63m0 { + struct device *dev; + void *transport_data; + int (*dcs_read)(struct device *dev, void *trsp, const u8 cmd, u8 *val); + int (*dcs_write)(struct device *dev, void *trsp, const u8 *data, size_t len); + struct drm_panel panel; + struct backlight_device *bl_dev; + u8 lcd_type; + u8 elvss_pulse; + bool dsi_mode; + + struct regulator_bulk_data supplies[2]; + struct gpio_desc *reset_gpio; + + /* + * This field is tested by functions directly accessing bus before + * transfer, transfer is skipped if it is set. In case of transfer + * failure or unexpected response the field is set to error value. + * Such construct allows to eliminate many checks in higher level + * functions. + */ + int error; +}; + +static const struct drm_display_mode default_mode = { + .clock = 25628, + .hdisplay = 480, + .hsync_start = 480 + 16, + .hsync_end = 480 + 16 + 2, + .htotal = 480 + 16 + 2 + 16, + .vdisplay = 800, + .vsync_start = 800 + 28, + .vsync_end = 800 + 28 + 2, + .vtotal = 800 + 28 + 2 + 1, + .width_mm = 53, + .height_mm = 89, + .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, +}; + +static inline struct s6e63m0 *panel_to_s6e63m0(struct drm_panel *panel) +{ + return container_of(panel, struct s6e63m0, panel); +} + +static int s6e63m0_clear_error(struct s6e63m0 *ctx) +{ + int ret = ctx->error; + + ctx->error = 0; + return ret; +} + +static void s6e63m0_dcs_read(struct s6e63m0 *ctx, const u8 cmd, u8 *data) +{ + if (ctx->error < 0) + return; + + ctx->error = ctx->dcs_read(ctx->dev, ctx->transport_data, cmd, data); +} + +static void s6e63m0_dcs_write(struct s6e63m0 *ctx, const u8 *data, size_t len) +{ + if (ctx->error < 0 || len == 0) + return; + + ctx->error = ctx->dcs_write(ctx->dev, ctx->transport_data, data, len); +} + +#define s6e63m0_dcs_write_seq_static(ctx, seq ...) \ + ({ \ + static const u8 d[] = { seq }; \ + s6e63m0_dcs_write(ctx, d, ARRAY_SIZE(d)); \ + }) + +static int s6e63m0_check_lcd_type(struct s6e63m0 *ctx) +{ + u8 id1, id2, id3; + int ret; + + s6e63m0_dcs_read(ctx, MCS_READ_ID1, &id1); + s6e63m0_dcs_read(ctx, MCS_READ_ID2, &id2); + s6e63m0_dcs_read(ctx, MCS_READ_ID3, &id3); + + ret = s6e63m0_clear_error(ctx); + if (ret) { + dev_err(ctx->dev, "error checking LCD type (%d)\n", ret); + ctx->lcd_type = 0x00; + return ret; + } + + dev_info(ctx->dev, "MTP ID: %02x %02x %02x\n", id1, id2, id3); + + /* + * We attempt to detect what panel is mounted on the controller. + * The third ID byte represents the desired ELVSS pulse for + * some displays. + */ + switch (id2) { + case S6E63M0_LCD_ID_VALUE_M2: + dev_info(ctx->dev, "detected LCD panel AMS397GE MIPI M2\n"); + ctx->elvss_pulse = id3; + break; + case S6E63M0_LCD_ID_VALUE_SM2: + case S6E63M0_LCD_ID_VALUE_SM2_1: + dev_info(ctx->dev, "detected LCD panel AMS397GE MIPI SM2\n"); + ctx->elvss_pulse = id3; + break; + default: + dev_info(ctx->dev, "unknown LCD panel type %02x\n", id2); + /* Default ELVSS pulse level */ + ctx->elvss_pulse = 0x16; + break; + } + + ctx->lcd_type = id2; + + return 0; +} + +static void s6e63m0_init(struct s6e63m0 *ctx) +{ + /* + * We do not know why there is a difference in the DSI mode. + * (No datasheet.) + * + * In the vendor driver this sequence is called + * "SEQ_PANEL_CONDITION_SET" or "DCS_CMD_SEQ_PANEL_COND_SET". + */ + if (ctx->dsi_mode) + s6e63m0_dcs_write_seq_static(ctx, MCS_PANELCTL, + 0x01, 0x2c, 0x2c, 0x07, 0x07, 0x5f, 0xb3, + 0x6d, 0x97, 0x1d, 0x3a, 0x0f, 0x00, 0x00); + else + s6e63m0_dcs_write_seq_static(ctx, MCS_PANELCTL, + 0x01, 0x27, 0x27, 0x07, 0x07, 0x54, 0x9f, + 0x63, 0x8f, 0x1a, 0x33, 0x0d, 0x00, 0x00); + + s6e63m0_dcs_write_seq_static(ctx, MCS_DISCTL, + 0x02, 0x03, 0x1c, 0x10, 0x10); + s6e63m0_dcs_write_seq_static(ctx, MCS_IFCTL, + 0x03, 0x00, 0x00); + + s6e63m0_dcs_write_seq_static(ctx, MCS_PGAMMACTL, + 0x00, 0x18, 0x08, 0x24, 0x64, 0x56, 0x33, + 0xb6, 0xba, 0xa8, 0xac, 0xb1, 0x9d, 0xc1, + 0xc1, 0xb7, 0x00, 0x9c, 0x00, 0x9f, 0x00, + 0xd6); + s6e63m0_dcs_write_seq_static(ctx, MCS_PGAMMACTL, + 0x01); + + s6e63m0_dcs_write_seq_static(ctx, MCS_SRCCTL, + 0x00, 0x8e, 0x07); + s6e63m0_dcs_write_seq_static(ctx, MCS_PENTILE_1, 0x6c); + + s6e63m0_dcs_write_seq_static(ctx, MCS_GAMMA_DELTA_Y_RED, + 0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17, + 0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b, + 0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a, + 0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23, + 0x21, 0x20, 0x1e, 0x1e); + + s6e63m0_dcs_write_seq_static(ctx, MCS_GAMMA_DELTA_X_RED, + 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x44, + 0x44, 0x55, 0x55, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66); + + s6e63m0_dcs_write_seq_static(ctx, MCS_GAMMA_DELTA_Y_GREEN, + 0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17, + 0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b, + 0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a, + 0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23, + 0x21, 0x20, 0x1e, 0x1e); + + s6e63m0_dcs_write_seq_static(ctx, MCS_GAMMA_DELTA_X_GREEN, + 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x44, + 0x44, 0x55, 0x55, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66); + + s6e63m0_dcs_write_seq_static(ctx, MCS_GAMMA_DELTA_Y_BLUE, + 0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17, + 0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b, + 0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a, + 0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23, + 0x21, 0x20, 0x1e, 0x1e); + + s6e63m0_dcs_write_seq_static(ctx, MCS_GAMMA_DELTA_X_BLUE, + 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x44, + 0x44, 0x55, 0x55, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66); + + s6e63m0_dcs_write_seq_static(ctx, MCS_BCMODE, + 0x4d, 0x96, 0x1d, 0x00, 0x00, 0x01, 0xdf, + 0x00, 0x00, 0x03, 0x1f, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x06, + 0x09, 0x0d, 0x0f, 0x12, 0x15, 0x18); + + s6e63m0_dcs_write_seq_static(ctx, MCS_TEMP_SWIRE, + 0x10, 0x10, 0x0b, 0x05); + + s6e63m0_dcs_write_seq_static(ctx, MCS_MIECTL1, + 0x01); + + s6e63m0_dcs_write_seq_static(ctx, MCS_ELVSS_ON, + 0x0b); +} + +static int s6e63m0_power_on(struct s6e63m0 *ctx) +{ + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + if (ret < 0) + return ret; + + msleep(25); + + /* Be sure to send a reset pulse */ + gpiod_set_value(ctx->reset_gpio, 1); + msleep(5); + gpiod_set_value(ctx->reset_gpio, 0); + msleep(120); + + return 0; +} + +static int s6e63m0_power_off(struct s6e63m0 *ctx) +{ + int ret; + + gpiod_set_value(ctx->reset_gpio, 1); + msleep(120); + + ret = regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + if (ret < 0) + return ret; + + return 0; +} + +static int s6e63m0_disable(struct drm_panel *panel) +{ + struct s6e63m0 *ctx = panel_to_s6e63m0(panel); + + backlight_disable(ctx->bl_dev); + + s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_OFF); + msleep(10); + s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_ENTER_SLEEP_MODE); + msleep(120); + + return 0; +} + +static int s6e63m0_unprepare(struct drm_panel *panel) +{ + struct s6e63m0 *ctx = panel_to_s6e63m0(panel); + int ret; + + s6e63m0_clear_error(ctx); + + ret = s6e63m0_power_off(ctx); + if (ret < 0) + return ret; + + return 0; +} + +static int s6e63m0_prepare(struct drm_panel *panel) +{ + struct s6e63m0 *ctx = panel_to_s6e63m0(panel); + int ret; + + ret = s6e63m0_power_on(ctx); + if (ret < 0) + return ret; + + /* Magic to unlock level 2 control of the display */ + s6e63m0_dcs_write_seq_static(ctx, MCS_LEVEL_2_KEY, 0x5a, 0x5a); + /* Magic to unlock MTP reading */ + s6e63m0_dcs_write_seq_static(ctx, MCS_MTP_KEY, 0x5a, 0x5a); + + ret = s6e63m0_check_lcd_type(ctx); + if (ret < 0) + return ret; + + s6e63m0_init(ctx); + + ret = s6e63m0_clear_error(ctx); + + if (ret < 0) + s6e63m0_unprepare(panel); + + return ret; +} + +static int s6e63m0_enable(struct drm_panel *panel) +{ + struct s6e63m0 *ctx = panel_to_s6e63m0(panel); + + s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_EXIT_SLEEP_MODE); + msleep(120); + s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_ON); + msleep(10); + + s6e63m0_dcs_write_seq_static(ctx, MCS_ERROR_CHECK, + 0xE7, 0x14, 0x60, 0x17, 0x0A, 0x49, 0xC3, + 0x8F, 0x19, 0x64, 0x91, 0x84, 0x76, 0x20, + 0x0F, 0x00); + + backlight_enable(ctx->bl_dev); + + return 0; +} + +static int s6e63m0_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + static const u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24; + + mode = drm_mode_duplicate(connector->dev, &default_mode); + if (!mode) { + dev_err(panel->dev, "failed to add mode %ux%u@%u\n", + default_mode.hdisplay, default_mode.vdisplay, + drm_mode_vrefresh(&default_mode)); + return -ENOMEM; + } + + 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); + connector->display_info.bus_flags = DRM_BUS_FLAG_DE_LOW | + DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE; + + drm_mode_set_name(mode); + + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + drm_mode_probed_add(connector, mode); + + return 1; +} + +static const struct drm_panel_funcs s6e63m0_drm_funcs = { + .disable = s6e63m0_disable, + .unprepare = s6e63m0_unprepare, + .prepare = s6e63m0_prepare, + .enable = s6e63m0_enable, + .get_modes = s6e63m0_get_modes, +}; + +static int s6e63m0_set_brightness(struct backlight_device *bd) +{ + struct s6e63m0 *ctx = bl_get_data(bd); + int brightness = bd->props.brightness; + u8 elvss_val; + u8 elvss_cmd_set[5]; + int i; + + /* Adjust ELVSS to candela level */ + i = s6e63m0_elvss_per_gamma[brightness]; + elvss_val = ctx->elvss_pulse + s6e63m0_elvss_offsets[i]; + if (elvss_val > 0x1f) + elvss_val = 0x1f; + elvss_cmd_set[0] = MCS_TEMP_SWIRE; + elvss_cmd_set[1] = elvss_val; + elvss_cmd_set[2] = elvss_val; + elvss_cmd_set[3] = elvss_val; + elvss_cmd_set[4] = elvss_val; + s6e63m0_dcs_write(ctx, elvss_cmd_set, 5); + + /* Update the ACL per gamma value */ + i = s6e63m0_acl_per_gamma[brightness]; + s6e63m0_dcs_write(ctx, s6e63m0_acl[i], + ARRAY_SIZE(s6e63m0_acl[i])); + + /* Update gamma table */ + s6e63m0_dcs_write(ctx, s6e63m0_gamma_22[brightness], + ARRAY_SIZE(s6e63m0_gamma_22[brightness])); + s6e63m0_dcs_write_seq_static(ctx, MCS_PGAMMACTL, 0x03); + + + return s6e63m0_clear_error(ctx); +} + +static const struct backlight_ops s6e63m0_backlight_ops = { + .update_status = s6e63m0_set_brightness, +}; + +static int s6e63m0_backlight_register(struct s6e63m0 *ctx, u32 max_brightness) +{ + struct backlight_properties props = { + .type = BACKLIGHT_RAW, + .brightness = max_brightness, + .max_brightness = max_brightness, + }; + struct device *dev = ctx->dev; + int ret = 0; + + ctx->bl_dev = devm_backlight_device_register(dev, "panel", dev, ctx, + &s6e63m0_backlight_ops, + &props); + if (IS_ERR(ctx->bl_dev)) { + ret = PTR_ERR(ctx->bl_dev); + dev_err(dev, "error registering backlight device (%d)\n", ret); + } + + return ret; +} + +int s6e63m0_probe(struct device *dev, void *trsp, + int (*dcs_read)(struct device *dev, void *trsp, const u8 cmd, u8 *val), + int (*dcs_write)(struct device *dev, void *trsp, const u8 *data, size_t len), + bool dsi_mode) +{ + struct s6e63m0 *ctx; + u32 max_brightness; + int ret; + + ctx = devm_kzalloc(dev, sizeof(struct s6e63m0), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->transport_data = trsp; + ctx->dsi_mode = dsi_mode; + ctx->dcs_read = dcs_read; + ctx->dcs_write = dcs_write; + dev_set_drvdata(dev, ctx); + + ctx->dev = dev; + + ret = device_property_read_u32(dev, "max-brightness", &max_brightness); + if (ret) + max_brightness = MAX_BRIGHTNESS; + if (max_brightness > MAX_BRIGHTNESS) { + dev_err(dev, "illegal max brightness specified\n"); + max_brightness = MAX_BRIGHTNESS; + } + + ctx->supplies[0].supply = "vdd3"; + ctx->supplies[1].supply = "vci"; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), + ctx->supplies); + if (ret < 0) { + dev_err(dev, "failed to get regulators: %d\n", ret); + return ret; + } + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->reset_gpio)) { + dev_err(dev, "cannot get reset-gpios %ld\n", PTR_ERR(ctx->reset_gpio)); + return PTR_ERR(ctx->reset_gpio); + } + + drm_panel_init(&ctx->panel, dev, &s6e63m0_drm_funcs, + dsi_mode ? DRM_MODE_CONNECTOR_DSI : + DRM_MODE_CONNECTOR_DPI); + + ret = s6e63m0_backlight_register(ctx, max_brightness); + if (ret < 0) + return ret; + + drm_panel_add(&ctx->panel); + + return 0; +} +EXPORT_SYMBOL_GPL(s6e63m0_probe); + +void s6e63m0_remove(struct device *dev) +{ + struct s6e63m0 *ctx = dev_get_drvdata(dev); + + drm_panel_remove(&ctx->panel); +} +EXPORT_SYMBOL_GPL(s6e63m0_remove); + +MODULE_AUTHOR("PaweÅ‚ Chmiel <pawel.mikolaj.chmiel@gmail.com>"); +MODULE_DESCRIPTION("s6e63m0 LCD Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-samsung-s6e63m0.h b/drivers/gpu/drm/panel/panel-samsung-s6e63m0.h new file mode 100644 index 000000000000..c926eca1c817 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-samsung-s6e63m0.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef _PANEL_SAMSUNG_S6E63M0_H +#define _PANEL_SAMSUNG_S6E63M0_H + +/* Manufacturer Command Set */ +#define MCS_ELVSS_ON 0xb1 +#define MCS_TEMP_SWIRE 0xb2 +#define MCS_PENTILE_1 0xb3 +#define MCS_PENTILE_2 0xb4 +#define MCS_GAMMA_DELTA_Y_RED 0xb5 +#define MCS_GAMMA_DELTA_X_RED 0xb6 +#define MCS_GAMMA_DELTA_Y_GREEN 0xb7 +#define MCS_GAMMA_DELTA_X_GREEN 0xb8 +#define MCS_GAMMA_DELTA_Y_BLUE 0xb9 +#define MCS_GAMMA_DELTA_X_BLUE 0xba +#define MCS_MIECTL1 0xc0 +#define MCS_BCMODE 0xc1 +#define MCS_ERROR_CHECK 0xd5 +#define MCS_READ_ID1 0xda +#define MCS_READ_ID2 0xdb +#define MCS_READ_ID3 0xdc +#define MCS_LEVEL_2_KEY 0xf0 +#define MCS_MTP_KEY 0xf1 +#define MCS_DISCTL 0xf2 +#define MCS_SRCCTL 0xf6 +#define MCS_IFCTL 0xf7 +#define MCS_PANELCTL 0xf8 +#define MCS_PGAMMACTL 0xfa + +int s6e63m0_probe(struct device *dev, void *trsp, + int (*dcs_read)(struct device *dev, void *trsp, + const u8 cmd, u8 *val), + int (*dcs_write)(struct device *dev, void *trsp, + const u8 *data, + size_t len), + bool dsi_mode); +void s6e63m0_remove(struct device *dev); + +#endif /* _PANEL_SAMSUNG_S6E63M0_H */ diff --git a/drivers/gpu/drm/panel/panel-samsung-s6e88a0-ams427ap24.c b/drivers/gpu/drm/panel/panel-samsung-s6e88a0-ams427ap24.c new file mode 100644 index 000000000000..7e2f4e043d62 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-samsung-s6e88a0-ams427ap24.c @@ -0,0 +1,768 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Samsung AMS427AP24 panel with S6E88A0 controller + * Copyright (c) 2024 Jakob Hauser <jahau@rocketmail.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/property.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_probe_helper.h> + +#define NUM_STEPS_CANDELA 54 +#define NUM_STEPS_AID 39 +#define NUM_STEPS_ELVSS 17 + +/* length of the payload data, thereof fixed and variable */ +#define FIX_LEN_AID 4 +#define FIX_LEN_ELVSS 2 +#define FIX_LEN_GAMMA 1 +#define VAR_LEN_AID 2 +#define VAR_LEN_ELVSS 1 +#define VAR_LEN_GAMMA 33 +#define LEN_AID (FIX_LEN_AID + VAR_LEN_AID) +#define LEN_ELVSS (FIX_LEN_ELVSS + VAR_LEN_ELVSS) +#define LEN_GAMMA (FIX_LEN_GAMMA + VAR_LEN_GAMMA) + +struct s6e88a0_ams427ap24 { + struct drm_panel panel; + struct backlight_device *bl_dev; + struct mipi_dsi_device *dsi; + struct regulator_bulk_data *supplies; + struct gpio_desc *reset_gpio; + bool flip_horizontal; +}; + +static const struct regulator_bulk_data s6e88a0_ams427ap24_supplies[] = { + { .supply = "vdd3" }, + { .supply = "vci" }, +}; + +static inline +struct s6e88a0_ams427ap24 *to_s6e88a0_ams427ap24(struct drm_panel *panel) +{ + return container_of(panel, struct s6e88a0_ams427ap24, panel); +} + +enum candela { + CANDELA_10CD, /* 0 */ + CANDELA_11CD, + CANDELA_12CD, + CANDELA_13CD, + CANDELA_14CD, + CANDELA_15CD, + CANDELA_16CD, + CANDELA_17CD, + CANDELA_19CD, + CANDELA_20CD, + CANDELA_21CD, + CANDELA_22CD, + CANDELA_24CD, + CANDELA_25CD, + CANDELA_27CD, + CANDELA_29CD, + CANDELA_30CD, + CANDELA_32CD, + CANDELA_34CD, + CANDELA_37CD, + CANDELA_39CD, + CANDELA_41CD, + CANDELA_44CD, + CANDELA_47CD, + CANDELA_50CD, + CANDELA_53CD, + CANDELA_56CD, + CANDELA_60CD, + CANDELA_64CD, + CANDELA_68CD, + CANDELA_72CD, + CANDELA_77CD, + CANDELA_82CD, + CANDELA_87CD, + CANDELA_93CD, + CANDELA_98CD, + CANDELA_105CD, + CANDELA_111CD, + CANDELA_119CD, + CANDELA_126CD, + CANDELA_134CD, + CANDELA_143CD, + CANDELA_152CD, + CANDELA_162CD, + CANDELA_172CD, + CANDELA_183CD, + CANDELA_195CD, + CANDELA_207CD, + CANDELA_220CD, + CANDELA_234CD, + CANDELA_249CD, + CANDELA_265CD, + CANDELA_282CD, + CANDELA_300CD, /* 53 */ +}; + +static const int s6e88a0_ams427ap24_br_to_cd[NUM_STEPS_CANDELA] = { + /* columns: brightness from, brightness till, candela */ + /* 0 */ 10, /* 10CD */ + /* 11 */ 11, /* 11CD */ + /* 12 */ 12, /* 12CD */ + /* 13 */ 13, /* 13CD */ + /* 14 */ 14, /* 14CD */ + /* 15 */ 15, /* 15CD */ + /* 16 */ 16, /* 16CD */ + /* 17 */ 17, /* 17CD */ + /* 18 */ 18, /* 19CD */ + /* 19 */ 19, /* 20CD */ + /* 20 */ 20, /* 21CD */ + /* 21 */ 21, /* 22CD */ + /* 22 */ 22, /* 24CD */ + /* 23 */ 23, /* 25CD */ + /* 24 */ 24, /* 27CD */ + /* 25 */ 25, /* 29CD */ + /* 26 */ 26, /* 30CD */ + /* 27 */ 27, /* 32CD */ + /* 28 */ 28, /* 34CD */ + /* 29 */ 29, /* 37CD */ + /* 30 */ 30, /* 39CD */ + /* 31 */ 32, /* 41CD */ + /* 33 */ 34, /* 44CD */ + /* 35 */ 36, /* 47CD */ + /* 37 */ 38, /* 50CD */ + /* 39 */ 40, /* 53CD */ + /* 41 */ 43, /* 56CD */ + /* 44 */ 46, /* 60CD */ + /* 47 */ 49, /* 64CD */ + /* 50 */ 52, /* 68CD */ + /* 53 */ 56, /* 72CD */ + /* 57 */ 59, /* 77CD */ + /* 60 */ 63, /* 82CD */ + /* 64 */ 67, /* 87CD */ + /* 68 */ 71, /* 93CD */ + /* 72 */ 76, /* 98CD */ + /* 77 */ 80, /* 105CD */ + /* 81 */ 86, /* 111CD */ + /* 87 */ 91, /* 119CD */ + /* 92 */ 97, /* 126CD */ + /* 98 */ 104, /* 134CD */ + /* 105 */ 110, /* 143CD */ + /* 111 */ 118, /* 152CD */ + /* 119 */ 125, /* 162CD */ + /* 126 */ 133, /* 172CD */ + /* 134 */ 142, /* 183CD */ + /* 143 */ 150, /* 195CD */ + /* 151 */ 160, /* 207CD */ + /* 161 */ 170, /* 220CD */ + /* 171 */ 181, /* 234CD */ + /* 182 */ 205, /* 249CD */ + /* 206 */ 234, /* 265CD */ + /* 235 */ 254, /* 282CD */ + /* 255 */ 255, /* 300CD */ +}; + +static const u8 s6e88a0_ams427ap24_aid[NUM_STEPS_AID][VAR_LEN_AID] = { + { 0x03, 0x77 }, /* AOR 90.9%, 10CD */ + { 0x03, 0x73 }, /* AOR 90.5%, 11CD */ + { 0x03, 0x69 }, /* AOR 89.4%, 12CD */ + { 0x03, 0x65 }, /* AOR 89.0%, 13CD */ + { 0x03, 0x61 }, /* AOR 88.6%, 14CD */ + { 0x03, 0x55 }, /* AOR 87.4%, 15CD */ + { 0x03, 0x50 }, /* AOR 86.9%, 16CD */ + { 0x03, 0x45 }, /* AOR 85.8%, 17CD */ + { 0x03, 0x35 }, /* AOR 84.1%, 19CD */ + { 0x03, 0x27 }, /* AOR 82.7%, 20CD */ + { 0x03, 0x23 }, /* AOR 82.3%, 21CD */ + { 0x03, 0x17 }, /* AOR 81.0%, 22CD */ + { 0x03, 0x11 }, /* AOR 80.4%, 24CD */ + { 0x03, 0x04 }, /* AOR 79.1%, 25CD */ + { 0x02, 0xf4 }, /* AOR 77.5%, 27CD */ + { 0x02, 0xe3 }, /* AOR 75.7%, 29CD */ + { 0x02, 0xd7 }, /* AOR 74.5%, 30CD */ + { 0x02, 0xc6 }, /* AOR 72.7%, 32CD */ + { 0x02, 0xb7 }, /* AOR 71.2%, 34CD */ + { 0x02, 0xa1 }, /* AOR 69.0%, 37CD */ + { 0x02, 0x91 }, /* AOR 67.3%, 39CD */ + { 0x02, 0x78 }, /* AOR 64.8%, 41CD */ + { 0x02, 0x62 }, /* AOR 62.5%, 44CD */ + { 0x02, 0x45 }, /* AOR 59.5%, 47CD */ + { 0x02, 0x30 }, /* AOR 57.4%, 50CD */ + { 0x02, 0x13 }, /* AOR 54.4%, 53CD */ + { 0x01, 0xf5 }, /* AOR 51.3%, 56CD */ + { 0x01, 0xd3 }, /* AOR 47.8%, 60CD */ + { 0x01, 0xb1 }, /* AOR 44.4%, 64CD */ + { 0x01, 0x87 }, /* AOR 40.1%, 68CD */ + { 0x01, 0x63 }, /* AOR 36.6%, 72CD */ + { 0x01, 0x35 }, /* AOR 31.7%, 77CD */ + { 0x01, 0x05 }, /* AOR 26.9%, 82CD */ + { 0x00, 0xd5 }, /* AOR 21.8%, 87CD */ + { 0x00, 0xa1 }, /* AOR 16.5%, 93CD */ + { 0x00, 0x6f }, /* AOR 11.4%, 98CD */ + { 0x00, 0x31 }, /* AOR 5.0%, 105CD */ + { 0x01, 0x86 }, /* AOR 40.0%, 111CD ~ 172CD */ + { 0x00, 0x08 }, /* AOR 0.6%, 183CD ~ 300CD */ +}; + +static const u8 s6e88a0_ams427ap24_elvss[NUM_STEPS_ELVSS][VAR_LEN_ELVSS] = { + { 0x14 }, /* 10CD ~ 111CD */ + { 0x13 }, /* 119CD */ + { 0x12 }, /* 126CD */ + { 0x12 }, /* 134CD */ + { 0x11 }, /* 143CD */ + { 0x10 }, /* 152CD */ + { 0x0f }, /* 162CD */ + { 0x0e }, /* 172CD */ + { 0x11 }, /* 183CD */ + { 0x11 }, /* 195CD */ + { 0x10 }, /* 207CD */ + { 0x0f }, /* 220CD */ + { 0x0f }, /* 234CD */ + { 0x0e }, /* 249CD */ + { 0x0d }, /* 265CD */ + { 0x0c }, /* 282CD */ + { 0x0b }, /* 300CD */ +}; + +static const u8 s6e88a0_ams427ap24_gamma[NUM_STEPS_CANDELA][VAR_LEN_GAMMA] = { + /* 10CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8c, 0x8a, 0x8a, 0x8c, 0x8b, + 0x8c, 0x87, 0x89, 0x89, 0x88, 0x87, 0x8c, 0x80, 0x82, 0x88, 0x7b, + 0x72, 0x8c, 0x60, 0x68, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + /* 11CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8c, 0x8a, 0x8a, 0x8c, 0x8b, + 0x8c, 0x87, 0x89, 0x89, 0x88, 0x87, 0x8c, 0x80, 0x82, 0x88, 0x7b, + 0x72, 0x8c, 0x60, 0x68, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + /* 12CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8c, 0x8a, 0x8a, 0x8b, 0x8b, + 0x8c, 0x88, 0x89, 0x8a, 0x88, 0x87, 0x8c, 0x81, 0x82, 0x87, 0x7a, + 0x72, 0x8b, 0x60, 0x68, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + /* 13CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8c, 0x8a, 0x8a, 0x8b, 0x8b, + 0x8c, 0x88, 0x89, 0x8a, 0x88, 0x87, 0x8c, 0x81, 0x82, 0x87, 0x7a, + 0x72, 0x8b, 0x61, 0x69, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + /* 14CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8c, 0x8a, 0x89, 0x8c, 0x8b, + 0x8c, 0x88, 0x89, 0x8a, 0x87, 0x86, 0x8a, 0x82, 0x82, 0x87, 0x79, + 0x71, 0x89, 0x63, 0x6c, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + /* 15CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8c, 0x8a, 0x8a, 0x8c, 0x8c, + 0x8c, 0x86, 0x87, 0x88, 0x85, 0x85, 0x8a, 0x83, 0x83, 0x88, 0x78, + 0x72, 0x89, 0x64, 0x6c, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + /* 16CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8c, 0x8a, 0x89, 0x8c, 0x8b, + 0x8c, 0x86, 0x88, 0x88, 0x86, 0x86, 0x8a, 0x84, 0x84, 0x88, 0x78, + 0x72, 0x89, 0x5d, 0x67, 0x8b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + /* 17CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8c, 0x8a, 0x89, 0x8b, 0x8b, + 0x8b, 0x87, 0x89, 0x89, 0x86, 0x86, 0x8a, 0x84, 0x83, 0x87, 0x78, + 0x73, 0x89, 0x64, 0x6e, 0x8e, 0x38, 0x32, 0x24, 0x00, 0x00, 0x00 }, + /* 19CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8c, 0x8a, 0x89, 0x8b, 0x8b, + 0x8b, 0x87, 0x89, 0x89, 0x86, 0x86, 0x89, 0x84, 0x84, 0x87, 0x77, + 0x72, 0x88, 0x65, 0x6f, 0x8e, 0x38, 0x32, 0x24, 0x00, 0x00, 0x00 }, + /* 20CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8c, 0x8a, 0x89, 0x8b, 0x8b, + 0x8b, 0x88, 0x89, 0x89, 0x85, 0x85, 0x88, 0x82, 0x83, 0x85, 0x79, + 0x73, 0x88, 0x65, 0x6f, 0x8e, 0x38, 0x32, 0x24, 0x00, 0x00, 0x00 }, + /* 21CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8c, 0x8a, 0x89, 0x8b, 0x8b, + 0x8b, 0x88, 0x89, 0x89, 0x85, 0x85, 0x88, 0x82, 0x83, 0x85, 0x79, + 0x74, 0x88, 0x65, 0x6f, 0x8e, 0x38, 0x32, 0x24, 0x00, 0x00, 0x00 }, + /* 22CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8c, 0x8a, 0x89, 0x8c, 0x8b, + 0x8c, 0x86, 0x88, 0x87, 0x86, 0x86, 0x89, 0x82, 0x83, 0x85, 0x7c, + 0x75, 0x87, 0x65, 0x6f, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + /* 24CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8c, 0x8a, 0x89, 0x8c, 0x8b, + 0x8c, 0x86, 0x88, 0x87, 0x86, 0x86, 0x89, 0x82, 0x83, 0x85, 0x7c, + 0x76, 0x86, 0x66, 0x6f, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + /* 25CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8c, 0x8a, 0x89, 0x8b, 0x8b, + 0x8b, 0x86, 0x89, 0x88, 0x87, 0x87, 0x89, 0x82, 0x82, 0x84, 0x7f, + 0x7a, 0x89, 0x6b, 0x73, 0x8f, 0x33, 0x2f, 0x22, 0x00, 0x00, 0x00 }, + /* 27CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8c, 0x8a, 0x89, 0x8b, 0x8b, + 0x8b, 0x86, 0x89, 0x88, 0x87, 0x87, 0x89, 0x82, 0x82, 0x84, 0x7f, + 0x7a, 0x89, 0x6b, 0x73, 0x8f, 0x33, 0x2f, 0x22, 0x00, 0x00, 0x00 }, + /* 29CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8c, 0x8a, 0x89, 0x8b, 0x8b, + 0x8b, 0x86, 0x89, 0x88, 0x85, 0x84, 0x87, 0x84, 0x85, 0x86, 0x80, + 0x7b, 0x88, 0x6a, 0x73, 0x8f, 0x33, 0x2f, 0x22, 0x00, 0x00, 0x00 }, + /* 30CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8c, 0x8a, 0x89, 0x8b, 0x8b, + 0x8b, 0x86, 0x89, 0x88, 0x85, 0x84, 0x87, 0x84, 0x85, 0x86, 0x80, + 0x7b, 0x88, 0x6a, 0x73, 0x8f, 0x33, 0x2f, 0x22, 0x00, 0x00, 0x00 }, + /* 32CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8c, 0x8a, 0x89, 0x8b, 0x8b, + 0x8b, 0x86, 0x89, 0x88, 0x85, 0x84, 0x87, 0x84, 0x85, 0x86, 0x80, + 0x7b, 0x88, 0x6a, 0x73, 0x8f, 0x33, 0x2f, 0x22, 0x00, 0x00, 0x00 }, + /* 34CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8c, 0x8a, 0x89, 0x8b, 0x8b, + 0x8b, 0x86, 0x89, 0x88, 0x85, 0x84, 0x87, 0x83, 0x84, 0x84, 0x7f, + 0x79, 0x86, 0x6c, 0x76, 0x91, 0x33, 0x2f, 0x22, 0x00, 0x00, 0x00 }, + /* 37CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8b, 0x89, 0x89, 0x8b, 0x8b, + 0x8b, 0x86, 0x88, 0x88, 0x87, 0x86, 0x87, 0x83, 0x84, 0x84, 0x7f, + 0x79, 0x86, 0x6c, 0x76, 0x90, 0x33, 0x2f, 0x22, 0x00, 0x00, 0x00 }, + /* 39CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8b, 0x89, 0x89, 0x8b, 0x8b, + 0x8b, 0x86, 0x88, 0x87, 0x84, 0x84, 0x86, 0x83, 0x85, 0x85, 0x80, + 0x79, 0x85, 0x6c, 0x76, 0x90, 0x33, 0x2f, 0x22, 0x00, 0x00, 0x00 }, + /* 41CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8b, 0x89, 0x89, 0x8b, 0x8b, + 0x8b, 0x86, 0x88, 0x87, 0x84, 0x84, 0x86, 0x81, 0x84, 0x83, 0x7f, + 0x79, 0x84, 0x6e, 0x79, 0x93, 0x33, 0x2f, 0x22, 0x00, 0x00, 0x00 }, + /* 44CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8b, 0x89, 0x89, 0x8b, 0x8b, + 0x8b, 0x86, 0x88, 0x87, 0x84, 0x84, 0x86, 0x81, 0x84, 0x83, 0x7f, + 0x79, 0x84, 0x6e, 0x79, 0x92, 0x33, 0x2f, 0x22, 0x00, 0x00, 0x00 }, + /* 47CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8b, 0x89, 0x89, 0x8b, 0x8b, + 0x8b, 0x86, 0x88, 0x87, 0x84, 0x85, 0x86, 0x81, 0x84, 0x83, 0x7f, + 0x79, 0x83, 0x6f, 0x79, 0x91, 0x33, 0x2f, 0x22, 0x00, 0x00, 0x00 }, + /* 50CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8b, 0x89, 0x89, 0x8b, 0x8b, + 0x8b, 0x86, 0x88, 0x87, 0x84, 0x85, 0x86, 0x82, 0x84, 0x83, 0x7f, + 0x79, 0x83, 0x6f, 0x79, 0x90, 0x33, 0x2f, 0x22, 0x00, 0x00, 0x00 }, + /* 53CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8b, 0x89, 0x89, 0x8b, 0x8b, + 0x8b, 0x86, 0x88, 0x87, 0x83, 0x83, 0x85, 0x84, 0x85, 0x85, 0x7f, + 0x79, 0x83, 0x70, 0x79, 0x8f, 0x33, 0x2f, 0x22, 0x00, 0x00, 0x00 }, + /* 56CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8b, 0x89, 0x89, 0x8b, 0x8a, + 0x8a, 0x87, 0x89, 0x87, 0x83, 0x83, 0x85, 0x84, 0x85, 0x84, 0x7f, + 0x79, 0x82, 0x70, 0x7a, 0x8e, 0x33, 0x2f, 0x22, 0x00, 0x00, 0x00 }, + /* 60CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8b, 0x89, 0x89, 0x8b, 0x8a, + 0x8a, 0x87, 0x89, 0x87, 0x83, 0x83, 0x85, 0x84, 0x85, 0x84, 0x7e, + 0x79, 0x82, 0x71, 0x7a, 0x8d, 0x33, 0x2f, 0x22, 0x00, 0x00, 0x00 }, + /* 64CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8b, 0x89, 0x89, 0x8b, 0x8a, + 0x8a, 0x86, 0x88, 0x86, 0x84, 0x84, 0x86, 0x82, 0x83, 0x82, 0x80, + 0x7a, 0x84, 0x71, 0x7a, 0x8c, 0x33, 0x2f, 0x22, 0x00, 0x00, 0x00 }, + /* 68CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8a, 0x89, 0x89, 0x8c, 0x8a, + 0x8a, 0x86, 0x88, 0x86, 0x84, 0x84, 0x86, 0x82, 0x84, 0x82, 0x81, + 0x7b, 0x83, 0x72, 0x7b, 0x8b, 0x33, 0x2f, 0x22, 0x00, 0x00, 0x00 }, + /* 72CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8a, 0x89, 0x89, 0x8c, 0x8a, + 0x8a, 0x86, 0x88, 0x86, 0x85, 0x85, 0x86, 0x82, 0x84, 0x82, 0x81, + 0x7b, 0x83, 0x72, 0x7c, 0x8a, 0x33, 0x2f, 0x22, 0x00, 0x00, 0x00 }, + /* 77CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8a, 0x89, 0x89, 0x8c, 0x8a, + 0x8a, 0x85, 0x87, 0x85, 0x85, 0x87, 0x87, 0x82, 0x84, 0x82, 0x81, + 0x7c, 0x82, 0x72, 0x7c, 0x89, 0x33, 0x2f, 0x22, 0x00, 0x00, 0x00 }, + /* 82CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8a, 0x89, 0x89, 0x8c, 0x8a, + 0x8a, 0x85, 0x87, 0x85, 0x85, 0x87, 0x87, 0x82, 0x84, 0x82, 0x81, + 0x7c, 0x82, 0x73, 0x7c, 0x88, 0x33, 0x2f, 0x22, 0x00, 0x00, 0x00 }, + /* 87CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8a, 0x89, 0x89, 0x8c, 0x8a, + 0x8a, 0x85, 0x87, 0x85, 0x84, 0x84, 0x86, 0x80, 0x84, 0x81, 0x80, + 0x7a, 0x82, 0x76, 0x7f, 0x89, 0x33, 0x2f, 0x22, 0x00, 0x00, 0x00 }, + /* 93CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8a, 0x89, 0x89, 0x8b, 0x8a, + 0x8a, 0x86, 0x87, 0x85, 0x84, 0x85, 0x86, 0x80, 0x84, 0x80, 0x80, + 0x7a, 0x82, 0x76, 0x80, 0x88, 0x33, 0x2f, 0x22, 0x00, 0x00, 0x00 }, + /* 98CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x8a, 0x89, 0x89, 0x8b, 0x8a, + 0x8a, 0x86, 0x87, 0x85, 0x85, 0x85, 0x86, 0x80, 0x84, 0x80, 0x80, + 0x7a, 0x82, 0x76, 0x80, 0x88, 0x33, 0x2f, 0x22, 0x00, 0x00, 0x00 }, + /* 105CD */ + { 0x00, 0xc8, 0x00, 0xc4, 0x00, 0xc5, 0x89, 0x88, 0x88, 0x8b, 0x8a, + 0x8a, 0x84, 0x87, 0x85, 0x85, 0x85, 0x85, 0x80, 0x84, 0x80, 0x7f, + 0x79, 0x81, 0x71, 0x7d, 0x87, 0x38, 0x32, 0x24, 0x00, 0x00, 0x00 }, + /* 111CD */ + { 0x00, 0xdf, 0x00, 0xde, 0x00, 0xde, 0x85, 0x85, 0x84, 0x87, 0x86, + 0x87, 0x85, 0x86, 0x85, 0x83, 0x83, 0x83, 0x81, 0x82, 0x82, 0x80, + 0x7d, 0x82, 0x75, 0x7f, 0x86, 0x85, 0x85, 0x82, 0x00, 0x00, 0x00 }, + /* 119CD */ + { 0x00, 0xe3, 0x00, 0xe1, 0x00, 0xe2, 0x85, 0x85, 0x84, 0x86, 0x85, + 0x85, 0x84, 0x85, 0x84, 0x83, 0x83, 0x83, 0x82, 0x82, 0x82, 0x7e, + 0x7b, 0x81, 0x75, 0x7f, 0x86, 0x85, 0x85, 0x82, 0x00, 0x00, 0x00 }, + /* 126CD */ + { 0x00, 0xe6, 0x00, 0xe5, 0x00, 0xe5, 0x85, 0x84, 0x84, 0x85, 0x85, + 0x85, 0x84, 0x84, 0x84, 0x82, 0x83, 0x83, 0x80, 0x81, 0x81, 0x80, + 0x7f, 0x83, 0x73, 0x7c, 0x84, 0x85, 0x85, 0x82, 0x00, 0x00, 0x00 }, + /* 134CD */ + { 0x00, 0xe9, 0x00, 0xe8, 0x00, 0xe8, 0x84, 0x84, 0x83, 0x85, 0x85, + 0x85, 0x84, 0x84, 0x83, 0x81, 0x82, 0x82, 0x81, 0x81, 0x81, 0x7f, + 0x7d, 0x81, 0x73, 0x7c, 0x83, 0x85, 0x85, 0x82, 0x00, 0x00, 0x00 }, + /* 143CD */ + { 0x00, 0xed, 0x00, 0xec, 0x00, 0xec, 0x84, 0x83, 0x83, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x83, 0x82, 0x83, 0x83, 0x81, 0x80, 0x81, 0x7f, + 0x7e, 0x81, 0x70, 0x79, 0x81, 0x85, 0x85, 0x82, 0x00, 0x00, 0x00 }, + /* 152CD */ + { 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x84, 0x84, 0x83, 0x81, 0x81, 0x81, 0x80, 0x80, 0x81, 0x80, + 0x80, 0x82, 0x6f, 0x78, 0x7f, 0x85, 0x85, 0x82, 0x00, 0x00, 0x00 }, + /* 162CD */ + { 0x00, 0xf4, 0x00, 0xf3, 0x00, 0xf4, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x82, 0x81, 0x81, 0x81, 0x81, 0x81, 0x80, 0x80, 0x81, 0x80, + 0x7f, 0x82, 0x6f, 0x78, 0x7f, 0x85, 0x85, 0x82, 0x00, 0x00, 0x00 }, + /* 172CD */ + { 0x00, 0xf8, 0x00, 0xf8, 0x00, 0xf8, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x81, 0x81, 0x80, 0x81, 0x80, 0x80, 0x80, 0x81, 0x81, + 0x80, 0x83, 0x6d, 0x76, 0x7d, 0x85, 0x85, 0x82, 0x00, 0x00, 0x00 }, + /* 183CD */ + { 0x00, 0xe0, 0x00, 0xdf, 0x00, 0xdf, 0x84, 0x84, 0x83, 0x86, 0x86, + 0x86, 0x83, 0x84, 0x83, 0x82, 0x82, 0x82, 0x81, 0x83, 0x81, 0x81, + 0x7e, 0x81, 0x80, 0x82, 0x84, 0x85, 0x85, 0x82, 0x00, 0x00, 0x00 }, + /* 195CD */ + { 0x00, 0xe4, 0x00, 0xe3, 0x00, 0xe3, 0x84, 0x83, 0x83, 0x85, 0x85, + 0x85, 0x83, 0x84, 0x83, 0x81, 0x82, 0x82, 0x82, 0x83, 0x81, 0x81, + 0x80, 0x82, 0x7d, 0x7f, 0x81, 0x85, 0x85, 0x82, 0x00, 0x00, 0x00 }, + /* 207CD */ + { 0x00, 0xe7, 0x00, 0xe6, 0x00, 0xe6, 0x83, 0x82, 0x82, 0x85, 0x85, + 0x85, 0x82, 0x83, 0x83, 0x82, 0x82, 0x82, 0x80, 0x81, 0x80, 0x81, + 0x80, 0x82, 0x7d, 0x7f, 0x81, 0x85, 0x85, 0x82, 0x00, 0x00, 0x00 }, + /* 220CD */ + { 0x00, 0xeb, 0x00, 0xea, 0x00, 0xea, 0x83, 0x83, 0x82, 0x84, 0x84, + 0x84, 0x82, 0x83, 0x82, 0x81, 0x81, 0x82, 0x81, 0x82, 0x81, 0x80, + 0x7e, 0x80, 0x7d, 0x7f, 0x81, 0x85, 0x85, 0x82, 0x00, 0x00, 0x00 }, + /* 234CD */ + { 0x00, 0xef, 0x00, 0xee, 0x00, 0xee, 0x83, 0x82, 0x82, 0x83, 0x83, + 0x83, 0x82, 0x82, 0x82, 0x81, 0x81, 0x81, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x81, 0x7b, 0x7c, 0x7f, 0x85, 0x85, 0x82, 0x00, 0x00, 0x00 }, + /* 249CD */ + { 0x00, 0xf3, 0x00, 0xf2, 0x00, 0xf2, 0x82, 0x81, 0x81, 0x83, 0x83, + 0x83, 0x82, 0x82, 0x82, 0x81, 0x81, 0x81, 0x80, 0x81, 0x80, 0x7f, + 0x7e, 0x7f, 0x7b, 0x7c, 0x7f, 0x85, 0x85, 0x82, 0x00, 0x00, 0x00 }, + /* 265CD */ + { 0x00, 0xf7, 0x00, 0xf7, 0x00, 0xf7, 0x81, 0x81, 0x80, 0x82, 0x82, + 0x82, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x81, 0x80, 0x7f, + 0x7e, 0x7f, 0x7b, 0x7c, 0x7f, 0x85, 0x85, 0x82, 0x00, 0x00, 0x00 }, + /* 282CD */ + { 0x00, 0xfb, 0x00, 0xfb, 0x00, 0xfb, 0x80, 0x80, 0x80, 0x81, 0x81, + 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x78, 0x79, 0x7d, 0x85, 0x85, 0x82, 0x00, 0x00, 0x00 }, + /* 300CD */ + { 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00 }, +}; + +static int s6e88a0_ams427ap24_set_brightness(struct backlight_device *bd) +{ + struct s6e88a0_ams427ap24 *ctx = bl_get_data(bd); + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + struct device *dev = &dsi->dev; + int brightness = bd->props.brightness; + int candela_enum; + u8 b2[LEN_AID] = { 0xb2, 0x40, 0x08, 0x20, 0x00, 0x00 }; + u8 b6[LEN_ELVSS] = { 0xb6, 0x28, 0x00 }; + u8 ca[LEN_GAMMA]; + + /* get candela enum from brightness */ + for (candela_enum = 0; candela_enum < NUM_STEPS_CANDELA; candela_enum++) + if (brightness <= s6e88a0_ams427ap24_br_to_cd[candela_enum]) + break; + + /* get aid */ + switch (candela_enum) { + case CANDELA_10CD ... CANDELA_105CD: + memcpy(&b2[FIX_LEN_AID], + s6e88a0_ams427ap24_aid[candela_enum], + VAR_LEN_AID); + break; + case CANDELA_111CD ... CANDELA_172CD: + memcpy(&b2[FIX_LEN_AID], + s6e88a0_ams427ap24_aid[CANDELA_111CD], + VAR_LEN_AID); + break; + case CANDELA_183CD ... CANDELA_300CD: + memcpy(&b2[FIX_LEN_AID], + s6e88a0_ams427ap24_aid[CANDELA_111CD + 1], + VAR_LEN_AID); + break; + default: + dev_err(dev, "Failed to get aid data\n"); + return -EINVAL; + } + + /* get elvss */ + if (candela_enum <= CANDELA_111CD) { + memcpy(&b6[FIX_LEN_ELVSS], + s6e88a0_ams427ap24_elvss[0], + VAR_LEN_ELVSS); + } else { + memcpy(&b6[FIX_LEN_ELVSS], + s6e88a0_ams427ap24_elvss[candela_enum - CANDELA_111CD], + VAR_LEN_ELVSS); + } + + /* get gamma */ + ca[0] = 0xca; + memcpy(&ca[FIX_LEN_GAMMA], + s6e88a0_ams427ap24_gamma[candela_enum], + VAR_LEN_GAMMA); + + /* write data */ + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf0, 0x5a, 0x5a); // level 1 key on + mipi_dsi_dcs_write_buffer_multi(&dsi_ctx, b2, ARRAY_SIZE(b2)); // set aid + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x55, 0x00); // acl off + mipi_dsi_dcs_write_buffer_multi(&dsi_ctx, b6, ARRAY_SIZE(b6)); // set elvss + mipi_dsi_dcs_write_buffer_multi(&dsi_ctx, ca, ARRAY_SIZE(ca)); // set gamma + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf7, 0x03); // gamma update + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf0, 0xa5, 0xa5); // level 1 key off + + return dsi_ctx.accum_err; +} + +static void s6e88a0_ams427ap24_reset(struct s6e88a0_ams427ap24 *ctx) +{ + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(5000, 6000); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(1000, 2000); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(18000, 19000); +} + +static int s6e88a0_ams427ap24_on(struct s6e88a0_ams427ap24 *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + struct device *dev = &dsi->dev; + int ret; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf0, 0x5a, 0x5a); // level 1 key on + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfc, 0x5a, 0x5a); // level 2 key on + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x11); // src latch set global 1 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfd, 0x11); // src latch set 1 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x13); // src latch set global 2 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfd, 0x18); // src latch set 2 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x02); // avdd set 1 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb8, 0x30); // avdd set 2 + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 20); + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf1, 0x5a, 0x5a); // level 3 key on + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xcc, 0x4c); // pixel clock divider pol. + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf2, 0x03, 0x0d); // unknown + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf1, 0xa5, 0xa5); // level 3 key off + + if (ctx->flip_horizontal) + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xcb, 0x0e); // flip display + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf0, 0xa5, 0xa5); // level 1 key off + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfc, 0xa5, 0xa5); // level 2 key off + + ret = s6e88a0_ams427ap24_set_brightness(ctx->bl_dev); + if (ret < 0) { + dev_err(dev, "Failed to set brightness: %d\n", ret); + return ret; + } + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + + return dsi_ctx.accum_err; +} + +static int s6e88a0_ams427ap24_off(struct s6e88a0_ams427ap24 *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 120); + + return dsi_ctx.accum_err; +} + +static int s6e88a0_ams427ap24_prepare(struct drm_panel *panel) +{ + struct s6e88a0_ams427ap24 *ctx = to_s6e88a0_ams427ap24(panel); + struct device *dev = &ctx->dsi->dev; + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(s6e88a0_ams427ap24_supplies), + ctx->supplies); + if (ret < 0) { + dev_err(dev, "Failed to enable regulators: %d\n", ret); + return ret; + } + + s6e88a0_ams427ap24_reset(ctx); + + ret = s6e88a0_ams427ap24_on(ctx); + if (ret < 0) { + dev_err(dev, "Failed to initialize panel: %d\n", ret); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(s6e88a0_ams427ap24_supplies), + ctx->supplies); + return ret; + } + + return 0; +} + +static int s6e88a0_ams427ap24_unprepare(struct drm_panel *panel) +{ + struct s6e88a0_ams427ap24 *ctx = to_s6e88a0_ams427ap24(panel); + struct device *dev = &ctx->dsi->dev; + int ret; + + ret = s6e88a0_ams427ap24_off(ctx); + if (ret < 0) + dev_err(dev, "Failed to un-initialize panel: %d\n", ret); + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(s6e88a0_ams427ap24_supplies), + ctx->supplies); + + return 0; +} + +static const struct drm_display_mode s6e88a0_ams427ap24_mode = { + .clock = (540 + 94 + 4 + 18) * (960 + 12 + 1 + 3) * 60 / 1000, + .hdisplay = 540, + .hsync_start = 540 + 94, + .hsync_end = 540 + 94 + 4, + .htotal = 540 + 94 + 4 + 18, + .vdisplay = 960, + .vsync_start = 960 + 12, + .vsync_end = 960 + 12 + 1, + .vtotal = 960 + 12 + 1 + 3, + .width_mm = 55, + .height_mm = 95, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, +}; + +static int s6e88a0_ams427ap24_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + return drm_connector_helper_get_modes_fixed(connector, + &s6e88a0_ams427ap24_mode); +} + +static const struct drm_panel_funcs s6e88a0_ams427ap24_panel_funcs = { + .prepare = s6e88a0_ams427ap24_prepare, + .unprepare = s6e88a0_ams427ap24_unprepare, + .get_modes = s6e88a0_ams427ap24_get_modes, +}; + +static const struct backlight_ops s6e88a0_ams427ap24_bl_ops = { + .update_status = s6e88a0_ams427ap24_set_brightness, +}; + +static int s6e88a0_ams427ap24_register_backlight(struct s6e88a0_ams427ap24 *ctx) +{ + struct backlight_properties props = { + .type = BACKLIGHT_RAW, + .brightness = 180, + .max_brightness = 255, + }; + struct mipi_dsi_device *dsi = ctx->dsi; + struct device *dev = &dsi->dev; + int ret = 0; + + ctx->bl_dev = devm_backlight_device_register(dev, dev_name(dev), dev, ctx, + &s6e88a0_ams427ap24_bl_ops, + &props); + if (IS_ERR(ctx->bl_dev)) { + ret = PTR_ERR(ctx->bl_dev); + dev_err(dev, "error registering backlight device (%d)\n", ret); + } + + return ret; +} + +static int s6e88a0_ams427ap24_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct s6e88a0_ams427ap24 *ctx; + int ret; + + ctx = devm_drm_panel_alloc(dev, struct s6e88a0_ams427ap24, panel, + &s6e88a0_ams427ap24_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ret = devm_regulator_bulk_get_const(dev, + ARRAY_SIZE(s6e88a0_ams427ap24_supplies), + s6e88a0_ams427ap24_supplies, + &ctx->supplies); + if (ret < 0) + return ret; + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), + "Failed to get reset-gpios\n"); + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + dsi->lanes = 2; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_NO_EOT_PACKET | MIPI_DSI_MODE_VIDEO_NO_HFP; + + ctx->panel.prepare_prev_first = true; + + ctx->flip_horizontal = device_property_read_bool(dev, "flip-horizontal"); + + ret = s6e88a0_ams427ap24_register_backlight(ctx); + if (ret < 0) + return ret; + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "Failed to attach to DSI host: %d\n", ret); + drm_panel_remove(&ctx->panel); + return ret; + } + + return 0; +} + +static void s6e88a0_ams427ap24_remove(struct mipi_dsi_device *dsi) +{ + struct s6e88a0_ams427ap24 *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id s6e88a0_ams427ap24_of_match[] = { + { .compatible = "samsung,s6e88a0-ams427ap24" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, s6e88a0_ams427ap24_of_match); + +static struct mipi_dsi_driver s6e88a0_ams427ap24_driver = { + .probe = s6e88a0_ams427ap24_probe, + .remove = s6e88a0_ams427ap24_remove, + .driver = { + .name = "panel-s6e88a0-ams427ap24", + .of_match_table = s6e88a0_ams427ap24_of_match, + }, +}; +module_mipi_dsi_driver(s6e88a0_ams427ap24_driver); + +MODULE_AUTHOR("Jakob Hauser <jahau@rocketmail.com>"); +MODULE_DESCRIPTION("Samsung AMS427AP24 panel with S6E88A0 controller"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-samsung-s6e88a0-ams452ef01.c b/drivers/gpu/drm/panel/panel-samsung-s6e88a0-ams452ef01.c new file mode 100644 index 000000000000..ca5cad41ff1d --- /dev/null +++ b/drivers/gpu/drm/panel/panel-samsung-s6e88a0-ams452ef01.c @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2019, Michael Srba + +#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> + +struct s6e88a0_ams452ef01 { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct regulator_bulk_data supplies[2]; + struct gpio_desc *reset_gpio; +}; + +static inline struct +s6e88a0_ams452ef01 *to_s6e88a0_ams452ef01(struct drm_panel *panel) +{ + return container_of(panel, struct s6e88a0_ams452ef01, panel); +} + +static void s6e88a0_ams452ef01_reset(struct s6e88a0_ams452ef01 *ctx) +{ + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(5000, 6000); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(1000, 2000); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(10000, 11000); +} + +static int s6e88a0_ams452ef01_on(struct s6e88a0_ams452ef01 *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf0, 0x5a, 0x5a); // enable LEVEL2 commands + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xcc, 0x4c); // set Pixel Clock Divider polarity + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 120); + + // set default brightness/gama + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xca, + 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,// V255 RR,GG,BB + 0x80, 0x80, 0x80, // V203 R,G,B + 0x80, 0x80, 0x80, // V151 R,G,B + 0x80, 0x80, 0x80, // V87 R,G,B + 0x80, 0x80, 0x80, // V51 R,G,B + 0x80, 0x80, 0x80, // V35 R,G,B + 0x80, 0x80, 0x80, // V23 R,G,B + 0x80, 0x80, 0x80, // V11 R,G,B + 0x6b, 0x68, 0x71, // V3 R,G,B + 0x00, 0x00, 0x00); // V1 R,G,B + // set default Amoled Off Ratio + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb2, 0x40, 0x0a, 0x17, 0x00, 0x0a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb6, 0x2c, 0x0b); // set default elvss voltage + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_POWER_SAVE, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf7, 0x03); // gamma/aor update + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf0, 0xa5, 0xa5); // disable LEVEL2 commands + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + + return dsi_ctx.accum_err; +} + +static void s6e88a0_ams452ef01_off(struct s6e88a0_ams452ef01 *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi}; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 35); + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 120); +} + +static int s6e88a0_ams452ef01_prepare(struct drm_panel *panel) +{ + struct s6e88a0_ams452ef01 *ctx = to_s6e88a0_ams452ef01(panel); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + if (ret < 0) + return ret; + + s6e88a0_ams452ef01_reset(ctx); + + ret = s6e88a0_ams452ef01_on(ctx); + if (ret < 0) { + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), + ctx->supplies); + return ret; + } + + return 0; +} + +static int s6e88a0_ams452ef01_unprepare(struct drm_panel *panel) +{ + struct s6e88a0_ams452ef01 *ctx = to_s6e88a0_ams452ef01(panel); + + s6e88a0_ams452ef01_off(ctx); + + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + + return 0; +} + +static const struct drm_display_mode s6e88a0_ams452ef01_mode = { + .clock = (540 + 88 + 4 + 20) * (960 + 14 + 2 + 8) * 60 / 1000, + .hdisplay = 540, + .hsync_start = 540 + 88, + .hsync_end = 540 + 88 + 4, + .htotal = 540 + 88 + 4 + 20, + .vdisplay = 960, + .vsync_start = 960 + 14, + .vsync_end = 960 + 14 + 2, + .vtotal = 960 + 14 + 2 + 8, + .width_mm = 56, + .height_mm = 100, +}; + +static int s6e88a0_ams452ef01_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &s6e88a0_ams452ef01_mode); + if (!mode) + 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 s6e88a0_ams452ef01_panel_funcs = { + .unprepare = s6e88a0_ams452ef01_unprepare, + .prepare = s6e88a0_ams452ef01_prepare, + .get_modes = s6e88a0_ams452ef01_get_modes, +}; + +static int s6e88a0_ams452ef01_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct s6e88a0_ams452ef01 *ctx; + int ret; + + ctx = devm_drm_panel_alloc(dev, struct s6e88a0_ams452ef01, panel, + &s6e88a0_ams452ef01_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ctx->supplies[0].supply = "vdd3"; + ctx->supplies[1].supply = "vci"; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), + ctx->supplies); + if (ret < 0) { + dev_err(dev, "Failed to get regulators: %d\n", ret); + return ret; + } + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->reset_gpio)) { + ret = PTR_ERR(ctx->reset_gpio); + dev_err(dev, "Failed to get reset-gpios: %d\n", ret); + return ret; + } + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + dsi->lanes = 2; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST; + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "Failed to attach to DSI host: %d\n", ret); + drm_panel_remove(&ctx->panel); + return ret; + } + + return 0; +} + +static void s6e88a0_ams452ef01_remove(struct mipi_dsi_device *dsi) +{ + struct s6e88a0_ams452ef01 *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id s6e88a0_ams452ef01_of_match[] = { + { .compatible = "samsung,s6e88a0-ams452ef01" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, s6e88a0_ams452ef01_of_match); + +static struct mipi_dsi_driver s6e88a0_ams452ef01_driver = { + .probe = s6e88a0_ams452ef01_probe, + .remove = s6e88a0_ams452ef01_remove, + .driver = { + .name = "panel-s6e88a0-ams452ef01", + .of_match_table = s6e88a0_ams452ef01_of_match, + }, +}; +module_mipi_dsi_driver(s6e88a0_ams452ef01_driver); + +MODULE_AUTHOR("Michael Srba <Michael.Srba@seznam.cz>"); +MODULE_DESCRIPTION("MIPI-DSI based Panel Driver for AMS452EF01 AMOLED LCD with a S6E88A0 controller"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-samsung-s6e8aa0.c b/drivers/gpu/drm/panel/panel-samsung-s6e8aa0.c index 6ad827b93ae1..1b5c500d4f4e 100644 --- a/drivers/gpu/drm/panel/panel-samsung-s6e8aa0.c +++ b/drivers/gpu/drm/panel/panel-samsung-s6e8aa0.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * MIPI-DSI based s6e8aa0 AMOLED LCD 5.3 inch panel driver. * @@ -9,23 +10,22 @@ * Eunchul Kim <chulspro.kim@samsung.com> * Tomasz Figa <t.figa@samsung.com> * Andrzej Hajda <a.hajda@samsung.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. */ -#include <drm/drmP.h> -#include <drm/drm_mipi_dsi.h> -#include <drm/drm_panel.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_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + #define LDI_MTP_LENGTH 24 #define GAMMA_LEVEL_NUM 25 #define GAMMA_TABLE_LEN 26 @@ -919,15 +919,15 @@ 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; mode = drm_mode_create(connector->dev); if (!mode) { - DRM_ERROR("failed to create a new display mode\n"); + dev_err(panel->dev, "failed to create a new display mode\n"); return 0; } @@ -979,9 +979,11 @@ static int s6e8aa0_probe(struct mipi_dsi_device *dsi) struct s6e8aa0 *ctx; int ret; - ctx = devm_kzalloc(dev, sizeof(struct s6e8aa0), GFP_KERNEL); - if (!ctx) - return -ENOMEM; + ctx = devm_drm_panel_alloc(dev, struct s6e8aa0, panel, + &s6e8aa0_drm_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); mipi_dsi_set_drvdata(dsi, ctx); @@ -990,9 +992,7 @@ static int s6e8aa0_probe(struct mipi_dsi_device *dsi) dsi->lanes = 4; dsi->format = MIPI_DSI_FMT_RGB888; dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST - | MIPI_DSI_MODE_VIDEO_HFP | MIPI_DSI_MODE_VIDEO_HBP - | MIPI_DSI_MODE_VIDEO_HSA | MIPI_DSI_MODE_EOT_PACKET - | MIPI_DSI_MODE_VSYNC_FLUSH | MIPI_DSI_MODE_VIDEO_AUTO_VERT; + | MIPI_DSI_MODE_VIDEO_AUTO_VERT; ret = s6e8aa0_parse_dt(ctx); if (ret < 0) @@ -1016,13 +1016,9 @@ 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; + ctx->panel.prepare_prev_first = true; - ret = drm_panel_add(&ctx->panel); - if (ret < 0) - return ret; + drm_panel_add(&ctx->panel); ret = mipi_dsi_attach(dsi); if (ret < 0) @@ -1031,14 +1027,12 @@ static int s6e8aa0_probe(struct mipi_dsi_device *dsi) return ret; } -static int s6e8aa0_remove(struct mipi_dsi_device *dsi) +static void s6e8aa0_remove(struct mipi_dsi_device *dsi) { struct s6e8aa0 *ctx = mipi_dsi_get_drvdata(dsi); mipi_dsi_detach(dsi); drm_panel_remove(&ctx->panel); - - return 0; } static const struct of_device_id s6e8aa0_of_match[] = { diff --git a/drivers/gpu/drm/panel/panel-samsung-s6e8aa5x01-ams561ra01.c b/drivers/gpu/drm/panel/panel-samsung-s6e8aa5x01-ams561ra01.c new file mode 100644 index 000000000000..56e10c7c3a76 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-samsung-s6e8aa5x01-ams561ra01.c @@ -0,0 +1,981 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Samsung AMS561RA01 panel with S6E8AA5X01 controller. + * + * Copyright (C) 2025 Kaustabh Chakraborty <kauschluss@disroot.org> + */ + +#include <linux/backlight.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regulator/consumer.h> + +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> +#include <drm/drm_probe_helper.h> + +/* Manufacturer Command Set */ +#define MCS_AIDCTL 0xb2 +#define MCS_ADAPTIVECTL 0xb5 +#define MCS_ELVSS 0xb6 +#define MCS_TEMPERCTL 0xb8 +#define MCS_PENTILE 0xc0 +#define MCS_GAMMACTL 0xca +#define MCS_LTPSCTL 0xcb +#define MCS_PCD 0xcc +#define MCS_ERRFLAG 0xe7 +#define MCS_ACCESSPROT 0xf0 +#define MCS_DISPCTL 0xf2 +#define MCS_GAMMAUPD 0xf7 + +#define GAMMA_CMD_LEN 34 +#define AID_CMD_LEN 3 + +static const struct { + u8 gamma[GAMMA_CMD_LEN]; + u8 aid[AID_CMD_LEN]; +} s6e8aa5x01_ams561ra01_cmds[] = { + { + /* 5 nits */ + { MCS_GAMMACTL, + 0x00, 0x98, 0x00, 0xa4, 0x00, 0x94, + 0x88, 0x89, 0x8a, 0x87, 0x87, 0x89, + 0x8d, 0x8c, 0x8d, 0x89, 0x8c, 0x8e, + 0x8e, 0x8f, 0x90, 0xa3, 0xa2, 0x9a, + 0xcf, 0xca, 0x9f, 0xe6, 0xff, 0xb4, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x05, 0xa5 }, + }, { + /* 6 nits */ + { MCS_GAMMACTL, + 0x00, 0x98, 0x00, 0xa4, 0x00, 0x95, + 0x88, 0x89, 0x8b, 0x87, 0x87, 0x89, + 0x8c, 0x8a, 0x8c, 0x85, 0x88, 0x8c, + 0x8b, 0x8c, 0x8e, 0xa2, 0xa2, 0x9a, + 0xd0, 0xcc, 0xa2, 0xed, 0xff, 0xb7, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x05, 0x95 }, + }, { + /* 7 nits */ + { MCS_GAMMACTL, + 0x00, 0x98, 0x00, 0xa4, 0x00, 0x95, + 0x88, 0x89, 0x8b, 0x87, 0x87, 0x89, + 0x8c, 0x8a, 0x8c, 0x85, 0x88, 0x8c, + 0x8b, 0x8c, 0x8e, 0xa2, 0xa2, 0x99, + 0xc8, 0xc4, 0x9d, 0xed, 0xff, 0xb7, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x05, 0x89 }, + }, { + /* 8 nits */ + { MCS_GAMMACTL, + 0x00, 0x98, 0x00, 0xa4, 0x00, 0x96, + 0x88, 0x89, 0x8a, 0x87, 0x87, 0x89, + 0x8a, 0x88, 0x8b, 0x83, 0x86, 0x8b, + 0x8c, 0x8b, 0x8d, 0x9d, 0x9f, 0x97, + 0xc7, 0xc3, 0x9c, 0xf5, 0xff, 0xbb, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x05, 0x7e }, + }, { + /* 9 nits */ + { MCS_GAMMACTL, + 0x00, 0x98, 0x00, 0xa4, 0x00, 0x96, + 0x88, 0x89, 0x8a, 0x87, 0x87, 0x89, + 0x89, 0x86, 0x8a, 0x82, 0x84, 0x88, + 0x90, 0x8f, 0x91, 0x95, 0x97, 0x94, + 0xc6, 0xc2, 0x9d, 0xf5, 0xff, 0xbb, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x05, 0x73 }, + }, { + /* 10 nits */ + { MCS_GAMMACTL, + 0x00, 0x98, 0x00, 0xa4, 0x00, 0x96, + 0x88, 0x89, 0x8a, 0x87, 0x87, 0x89, + 0x89, 0x86, 0x8a, 0x82, 0x84, 0x88, + 0x90, 0x8f, 0x91, 0x94, 0x97, 0x93, + 0xc6, 0xc2, 0x9e, 0xec, 0xff, 0xb7, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x05, 0x67 }, + }, { + /* 11 nits */ + { MCS_GAMMACTL, + 0x00, 0x98, 0x00, 0xa4, 0x00, 0x96, + 0x88, 0x89, 0x8a, 0x87, 0x87, 0x89, + 0x89, 0x86, 0x8a, 0x82, 0x84, 0x88, + 0x8b, 0x8b, 0x8d, 0x90, 0x93, 0x92, + 0xc5, 0xc1, 0x9c, 0xf5, 0xff, 0xbb, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x05, 0x56 }, + }, { + /* 12 nits */ + { MCS_GAMMACTL, + 0x00, 0x98, 0x00, 0xa4, 0x00, 0x96, + 0x88, 0x89, 0x8b, 0x87, 0x87, 0x89, + 0x89, 0x86, 0x89, 0x82, 0x84, 0x88, + 0x87, 0x86, 0x8a, 0x8c, 0x90, 0x8f, + 0xcd, 0xc9, 0xa1, 0xec, 0xff, 0xb7, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x05, 0x4a }, + }, { + /* 13 nits */ + { MCS_GAMMACTL, + 0x00, 0x98, 0x00, 0xa4, 0x00, 0x96, + 0x88, 0x89, 0x8b, 0x87, 0x87, 0x89, + 0x89, 0x86, 0x89, 0x82, 0x84, 0x88, + 0x87, 0x86, 0x8a, 0x8c, 0x90, 0x8e, + 0xc4, 0xbf, 0x9c, 0xf5, 0xff, 0xbb, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x05, 0x3b }, + }, { + /* 14 nits */ + { MCS_GAMMACTL, + 0x00, 0x98, 0x00, 0xa4, 0x00, 0x96, + 0x88, 0x89, 0x8b, 0x87, 0x87, 0x89, + 0x89, 0x86, 0x89, 0x82, 0x84, 0x88, + 0x87, 0x86, 0x89, 0x8c, 0x90, 0x8f, + 0xc2, 0xbf, 0x9c, 0xec, 0xff, 0xb7, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x05, 0x35 }, + }, { + /* 15 nits */ + { MCS_GAMMACTL, + 0x00, 0x98, 0x00, 0xa4, 0x00, 0x96, + 0x88, 0x89, 0x8b, 0x87, 0x87, 0x89, + 0x89, 0x86, 0x89, 0x82, 0x84, 0x88, + 0x87, 0x86, 0x89, 0x8c, 0x90, 0x8f, + 0xb7, 0xb6, 0x96, 0xec, 0xff, 0xb7, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x05, 0x25 }, + }, { + /* 16 nits */ + { MCS_GAMMACTL, + 0x00, 0x98, 0x00, 0xa4, 0x00, 0x96, + 0x88, 0x89, 0x8b, 0x87, 0x87, 0x89, + 0x89, 0x86, 0x89, 0x82, 0x84, 0x88, + 0x88, 0x86, 0x89, 0x8c, 0x90, 0x8f, + 0xb7, 0xb6, 0x96, 0xec, 0xff, 0xb7, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x05, 0x20 }, + }, { + /* 17 nits */ + { MCS_GAMMACTL, + 0x00, 0x98, 0x00, 0xa4, 0x00, 0x96, + 0x88, 0x89, 0x8b, 0x87, 0x87, 0x89, + 0x89, 0x86, 0x89, 0x7f, 0x80, 0x86, + 0x86, 0x85, 0x89, 0x88, 0x8c, 0x8e, + 0xbf, 0xbe, 0x9c, 0xec, 0xff, 0xb7, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x05, 0x11 }, + }, { + /* 19 nits */ + { MCS_GAMMACTL, + 0x00, 0x98, 0x00, 0xa4, 0x00, 0x96, + 0x88, 0x89, 0x8b, 0x87, 0x87, 0x89, + 0x89, 0x86, 0x89, 0x7f, 0x80, 0x86, + 0x87, 0x85, 0x89, 0x88, 0x8c, 0x8e, + 0xb3, 0xb4, 0x97, 0xeb, 0xff, 0xb7, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x04, 0xf2 }, + }, { + /* 20 nits */ + { MCS_GAMMACTL, + 0x00, 0x98, 0x00, 0xa4, 0x00, 0x95, + 0x88, 0x89, 0x8b, 0x87, 0x87, 0x89, + 0x89, 0x86, 0x89, 0x7f, 0x80, 0x86, + 0x87, 0x85, 0x89, 0x89, 0x8c, 0x8e, + 0xb3, 0xb4, 0x97, 0xeb, 0xff, 0xb7, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x04, 0xe4 }, + }, { + /* 21 nits */ + { MCS_GAMMACTL, + 0x00, 0x98, 0x00, 0xa4, 0x00, 0x96, + 0x88, 0x89, 0x8b, 0x87, 0x87, 0x89, + 0x8a, 0x88, 0x8b, 0x7d, 0x7e, 0x84, + 0x8c, 0x8a, 0x8c, 0x8e, 0x90, 0x8f, + 0xb6, 0xb6, 0x97, 0xe3, 0xff, 0xb3, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x04, 0xd5 }, + }, { + /* 22 nits */ + { MCS_GAMMACTL, + 0x00, 0x98, 0x00, 0xa4, 0x00, 0x97, + 0x88, 0x89, 0x8b, 0x87, 0x87, 0x89, + 0x8a, 0x88, 0x8b, 0x81, 0x82, 0x86, + 0x87, 0x86, 0x88, 0x8e, 0x90, 0x8f, + 0xb6, 0xb6, 0x95, 0xe3, 0xff, 0xb3, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x04, 0xc5 }, + }, { + /* 24 nits */ + { MCS_GAMMACTL, + 0x00, 0x98, 0x00, 0xa4, 0x00, 0x97, + 0x88, 0x89, 0x8b, 0x88, 0x88, 0x8a, + 0x8a, 0x87, 0x8a, 0x81, 0x82, 0x86, + 0x87, 0x86, 0x88, 0x8e, 0x90, 0x8f, + 0xb6, 0xb6, 0x94, 0xe3, 0xff, 0xb3, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x04, 0xa7 }, + }, { + /* 25 nits */ + { MCS_GAMMACTL, + 0x00, 0x98, 0x00, 0xa4, 0x00, 0x98, + 0x88, 0x89, 0x8b, 0x88, 0x88, 0x8a, + 0x8a, 0x87, 0x8a, 0x81, 0x82, 0x86, + 0x87, 0x86, 0x87, 0x8e, 0x90, 0x8f, + 0xbf, 0xbf, 0x9a, 0xda, 0xfa, 0xaf, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x04, 0x95 }, + }, { + /* 27 nits */ + { MCS_GAMMACTL, + 0x00, 0x98, 0x00, 0xa4, 0x00, 0x99, + 0x88, 0x89, 0x8b, 0x88, 0x88, 0x8a, + 0x8a, 0x87, 0x8a, 0x83, 0x86, 0x8a, + 0x88, 0x87, 0x87, 0x88, 0x8b, 0x8c, + 0xbf, 0xbf, 0x9a, 0xda, 0xfa, 0xaf, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x04, 0x76 }, + }, { + /* 29 nits */ + { MCS_GAMMACTL, + 0x00, 0x98, 0x00, 0xa4, 0x00, 0x99, + 0x88, 0x89, 0x8b, 0x88, 0x88, 0x8a, + 0x8a, 0x87, 0x8b, 0x83, 0x86, 0x89, + 0x88, 0x87, 0x88, 0x88, 0x8b, 0x8b, + 0xbf, 0xbf, 0x9a, 0xda, 0xfa, 0xaf, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x04, 0x54 }, + }, { + /* 30 nits */ + { MCS_GAMMACTL, + 0x00, 0x98, 0x00, 0xa4, 0x00, 0x9a, + 0x88, 0x89, 0x8b, 0x88, 0x88, 0x8a, + 0x8a, 0x87, 0x8a, 0x84, 0x86, 0x8a, + 0x87, 0x87, 0x87, 0x88, 0x8b, 0x8b, + 0xbf, 0xbf, 0x99, 0xda, 0xfa, 0xaf, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x04, 0x44 }, + }, { + /* 32 nits */ + { MCS_GAMMACTL, + 0x00, 0x98, 0x00, 0xa4, 0x00, 0x9a, + 0x89, 0x89, 0x8c, 0x88, 0x88, 0x8a, + 0x89, 0x87, 0x8a, 0x84, 0x86, 0x8a, + 0x87, 0x87, 0x87, 0x89, 0x8b, 0x8b, + 0xbf, 0xbf, 0x98, 0xd2, 0xf2, 0xac, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x04, 0x1f }, + }, { + /* 34 nits */ + { MCS_GAMMACTL, + 0x00, 0x98, 0x00, 0xa4, 0x00, 0x9b, + 0x88, 0x89, 0x8b, 0x88, 0x88, 0x8a, + 0x8b, 0x87, 0x8b, 0x83, 0x86, 0x89, + 0x87, 0x87, 0x88, 0x88, 0x8b, 0x8a, + 0xbf, 0xbf, 0x98, 0xd2, 0xf2, 0xac, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x03, 0xff }, + }, { + /* 37 nits */ + { MCS_GAMMACTL, + 0x00, 0x98, 0x00, 0xa4, 0x00, 0x9b, + 0x89, 0x89, 0x8c, 0x88, 0x88, 0x8a, + 0x8a, 0x87, 0x8a, 0x81, 0x82, 0x86, + 0x86, 0x86, 0x86, 0x8d, 0x90, 0x8d, + 0xc0, 0xbf, 0x9a, 0xd2, 0xf2, 0xac, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x03, 0xd3 }, + }, { + /* 39 nits */ + { MCS_GAMMACTL, + 0x00, 0x98, 0x00, 0xa4, 0x00, 0x9b, + 0x89, 0x89, 0x8c, 0x88, 0x88, 0x8a, + 0x8a, 0x87, 0x8a, 0x81, 0x82, 0x86, + 0x87, 0x86, 0x87, 0x8d, 0x90, 0x8d, + 0xb6, 0xb6, 0x93, 0xda, 0xf9, 0xaf, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x03, 0xb3 }, + }, { + /* 41 nits */ + { MCS_GAMMACTL, + 0x00, 0x98, 0x00, 0xa4, 0x00, 0x9b, + 0x89, 0x89, 0x8c, 0x88, 0x88, 0x8a, + 0x8a, 0x87, 0x8b, 0x81, 0x82, 0x85, + 0x87, 0x86, 0x87, 0x8d, 0x90, 0x8d, + 0xb6, 0xb6, 0x94, 0xda, 0xf9, 0xaf, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x03, 0x93 }, + }, { + /* 44 nits */ + { MCS_GAMMACTL, + 0x00, 0x98, 0x00, 0xa4, 0x00, 0x9b, + 0x89, 0x89, 0x8c, 0x88, 0x88, 0x8a, + 0x8a, 0x87, 0x8b, 0x81, 0x82, 0x86, + 0x87, 0x86, 0x86, 0x85, 0x87, 0x8a, + 0xbe, 0xbe, 0x99, 0xda, 0xf9, 0xaf, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x03, 0x66 }, + }, { + /* 47 nits */ + { MCS_GAMMACTL, + 0x00, 0x98, 0x00, 0xa4, 0x00, 0x9b, + 0x89, 0x89, 0x8c, 0x88, 0x88, 0x8a, + 0x8a, 0x87, 0x8b, 0x81, 0x82, 0x86, + 0x88, 0x86, 0x87, 0x84, 0x87, 0x89, + 0xb4, 0xb4, 0x94, 0xe2, 0xff, 0xb3, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x03, 0x40 }, + }, { + /* 50 nits */ + { MCS_GAMMACTL, + 0x00, 0x98, 0x00, 0xa4, 0x00, 0x9c, + 0x89, 0x89, 0x8b, 0x88, 0x88, 0x8a, + 0x8a, 0x87, 0x8b, 0x81, 0x82, 0x86, + 0x88, 0x86, 0x87, 0x84, 0x87, 0x89, + 0xb4, 0xb4, 0x95, 0xe2, 0xff, 0xb3, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x03, 0x0e }, + }, { + /* 53 nits */ + { MCS_GAMMACTL, + 0x00, 0x98, 0x00, 0xa4, 0x00, 0x9c, + 0x89, 0x89, 0x8b, 0x88, 0x88, 0x8a, + 0x8a, 0x87, 0x8b, 0x81, 0x82, 0x86, + 0x88, 0x86, 0x87, 0x85, 0x87, 0x8a, + 0xb4, 0xb4, 0x96, 0xe2, 0xff, 0xb3, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x02, 0xe2 }, + }, { + /* 56 nits */ + { MCS_GAMMACTL, + 0x00, 0x98, 0x00, 0xa4, 0x00, 0x9c, + 0x89, 0x89, 0x8b, 0x88, 0x88, 0x8a, + 0x8a, 0x87, 0x8b, 0x81, 0x82, 0x86, + 0x88, 0x86, 0x87, 0x85, 0x87, 0x8a, + 0xab, 0xab, 0x90, 0xdd, 0xf7, 0xaf, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x02, 0xb5 }, + }, { + /* 60 nits */ + { MCS_GAMMACTL, + 0x00, 0x98, 0x00, 0xa4, 0x00, 0x9c, + 0x89, 0x89, 0x8b, 0x88, 0x88, 0x8a, + 0x8a, 0x87, 0x8b, 0x82, 0x82, 0x87, + 0x83, 0x81, 0x84, 0x81, 0x84, 0x88, + 0xb3, 0xb3, 0x96, 0xcf, 0xe5, 0xa8, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x02, 0x77 }, + }, { + /* 64 nits */ + { MCS_GAMMACTL, + 0x00, 0x98, 0x00, 0xa4, 0x00, 0x9c, + 0x89, 0x89, 0x8b, 0x88, 0x88, 0x8a, + 0x8a, 0x87, 0x8b, 0x82, 0x82, 0x87, + 0x83, 0x81, 0x84, 0x82, 0x84, 0x88, + 0xb2, 0xb3, 0x97, 0xcf, 0xe5, 0xa8, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x02, 0x36 }, + }, { + /* 68 nits */ + { MCS_GAMMACTL, + 0x00, 0x9b, 0x00, 0xa6, 0x00, 0x9d, + 0x88, 0x88, 0x89, 0x89, 0x89, 0x8b, + 0x8a, 0x88, 0x8b, 0x7f, 0x80, 0x86, + 0x88, 0x86, 0x87, 0x7d, 0x7f, 0x85, + 0xb2, 0xb3, 0x97, 0xcf, 0xe5, 0xa8, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x02, 0x15 }, + }, { + /* 72 nits */ + { MCS_GAMMACTL, + 0x00, 0x9c, 0x00, 0xa9, 0x00, 0xa0, + 0x88, 0x88, 0x89, 0x88, 0x88, 0x8a, + 0x8c, 0x8a, 0x8d, 0x7f, 0x81, 0x85, + 0x84, 0x82, 0x84, 0x85, 0x87, 0x8a, + 0xaa, 0xab, 0x93, 0xcf, 0xe5, 0xa8, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x02, 0x15 }, + }, { + /* 77 nits */ + { MCS_GAMMACTL, + 0x00, 0xa1, 0x00, 0xad, 0x00, 0xa5, + 0x89, 0x89, 0x8a, 0x88, 0x87, 0x89, + 0x8c, 0x89, 0x8d, 0x7f, 0x81, 0x85, + 0x84, 0x83, 0x84, 0x81, 0x83, 0x86, + 0xaa, 0xab, 0x93, 0xc0, 0xd3, 0xa1, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x02, 0x15 }, + }, { + /* 82 nits */ + { MCS_GAMMACTL, + 0x00, 0xa5, 0x00, 0xb0, 0x00, 0xa9, + 0x88, 0x89, 0x89, 0x85, 0x86, 0x89, + 0x8a, 0x88, 0x8b, 0x82, 0x82, 0x87, + 0x81, 0x80, 0x82, 0x89, 0x8b, 0x8b, + 0xa2, 0xa3, 0x8e, 0xc0, 0xd3, 0xa1, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x02, 0x15 }, + }, { + /* 87 nits */ + { MCS_GAMMACTL, + 0x00, 0xab, 0x00, 0xb4, 0x00, 0xad, + 0x88, 0x89, 0x8a, 0x84, 0x86, 0x88, + 0x8a, 0x88, 0x8b, 0x7f, 0x7f, 0x84, + 0x86, 0x84, 0x85, 0x85, 0x86, 0x88, + 0xa2, 0xa3, 0x8f, 0xc0, 0xd3, 0xa1, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x02, 0x15 }, + }, { + /* 93 nits */ + { MCS_GAMMACTL, + 0x00, 0xaf, 0x00, 0xb9, 0x00, 0xb1, + 0x88, 0x89, 0x8a, 0x84, 0x85, 0x87, + 0x8a, 0x89, 0x8b, 0x7e, 0x7e, 0x83, + 0x87, 0x86, 0x86, 0x88, 0x8a, 0x89, + 0x9c, 0x9c, 0x8b, 0xc0, 0xd3, 0xa1, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x02, 0x15 }, + }, { + /* 98 nits */ + { MCS_GAMMACTL, + 0x00, 0xb3, 0x00, 0xbc, 0x00, 0xb5, + 0x88, 0x88, 0x88, 0x84, 0x84, 0x86, + 0x8a, 0x88, 0x8a, 0x7f, 0x7f, 0x84, + 0x84, 0x83, 0x84, 0x88, 0x8a, 0x89, + 0x9c, 0x9c, 0x8b, 0xc0, 0xd3, 0xa1, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x02, 0x15 }, + }, { + /* 105 nits */ + { MCS_GAMMACTL, + 0x00, 0xb7, 0x00, 0xc0, 0x00, 0xba, + 0x87, 0x87, 0x88, 0x85, 0x85, 0x87, + 0x89, 0x88, 0x89, 0x7f, 0x7f, 0x83, + 0x81, 0x80, 0x82, 0x88, 0x8a, 0x89, + 0x9c, 0x9c, 0x8c, 0xb2, 0xc2, 0x9a, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x02, 0x15 }, + }, { + /* 111 nits */ + { MCS_GAMMACTL, + 0x00, 0xbb, 0x00, 0xc3, 0x00, 0xbe, + 0x87, 0x87, 0x88, 0x85, 0x85, 0x88, + 0x88, 0x87, 0x89, 0x80, 0x80, 0x84, + 0x81, 0x81, 0x82, 0x85, 0x86, 0x87, + 0x9c, 0x9c, 0x8b, 0xb2, 0xc2, 0x9a, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x02, 0x15 }, + }, { + /* 119 nits */ + { MCS_GAMMACTL, + 0x00, 0xc0, 0x00, 0xc8, 0x00, 0xc4, + 0x87, 0x87, 0x88, 0x82, 0x84, 0x86, + 0x87, 0x85, 0x87, 0x82, 0x81, 0x84, + 0x83, 0x82, 0x83, 0x80, 0x81, 0x84, + 0x9c, 0x9c, 0x8c, 0xb2, 0xc2, 0x9a, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x02, 0x14 }, + }, { + /* 126 nits */ + { MCS_GAMMACTL, + 0x00, 0xc0, 0x00, 0xc8, 0x00, 0xc4, + 0x87, 0x87, 0x88, 0x82, 0x84, 0x86, + 0x87, 0x85, 0x87, 0x82, 0x81, 0x84, + 0x83, 0x82, 0x83, 0x80, 0x81, 0x84, + 0x9c, 0x9c, 0x8d, 0xb2, 0xc2, 0x9a, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x01, 0xde }, + }, { + /* 134 nits */ + { MCS_GAMMACTL, + 0x00, 0xc0, 0x00, 0xc8, 0x00, 0xc4, + 0x87, 0x87, 0x88, 0x82, 0x84, 0x86, + 0x87, 0x85, 0x87, 0x82, 0x81, 0x84, + 0x83, 0x82, 0x83, 0x80, 0x81, 0x84, + 0x9c, 0x9c, 0x8d, 0xa4, 0xb0, 0x92, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x01, 0x94 }, + }, { + /* 143 nits */ + { MCS_GAMMACTL, + 0x00, 0xc0, 0x00, 0xc8, 0x00, 0xc3, + 0x87, 0x87, 0x88, 0x82, 0x84, 0x86, + 0x87, 0x85, 0x87, 0x82, 0x81, 0x85, + 0x83, 0x82, 0x83, 0x80, 0x81, 0x84, + 0x92, 0x92, 0x89, 0xab, 0xb6, 0x96, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x01, 0x46 }, + }, { + /* 152 nits */ + { MCS_GAMMACTL, + 0x00, 0xc0, 0x00, 0xc8, 0x00, 0xc3, + 0x87, 0x87, 0x88, 0x83, 0x84, 0x86, + 0x87, 0x85, 0x87, 0x81, 0x81, 0x85, + 0x84, 0x82, 0x83, 0x80, 0x81, 0x83, + 0x92, 0x92, 0x8b, 0xab, 0xb6, 0x96, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x00, 0xfa }, + }, { + /* 162 nits */ + { MCS_GAMMACTL, + 0x00, 0xc0, 0x00, 0xc8, 0x00, 0xc3, + 0x87, 0x87, 0x88, 0x83, 0x84, 0x86, + 0x87, 0x85, 0x87, 0x81, 0x81, 0x84, + 0x84, 0x82, 0x84, 0x80, 0x81, 0x83, + 0x92, 0x92, 0x8b, 0x9d, 0xa4, 0x8e, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x00, 0xac }, + }, { + /* 172 nits */ + { MCS_GAMMACTL, + 0x00, 0xc0, 0x00, 0xc8, 0x00, 0xc3, + 0x87, 0x87, 0x88, 0x83, 0x84, 0x86, + 0x87, 0x85, 0x87, 0x81, 0x81, 0x84, + 0x84, 0x82, 0x83, 0x80, 0x81, 0x84, + 0x93, 0x92, 0x8c, 0x9d, 0xa4, 0x8e, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x00, 0x57 }, + }, { + /* 183 nits */ + { MCS_GAMMACTL, + 0x00, 0xc2, 0x00, 0xca, 0x00, 0xc5, + 0x86, 0x86, 0x87, 0x85, 0x84, 0x87, + 0x87, 0x86, 0x88, 0x7e, 0x80, 0x83, + 0x84, 0x82, 0x83, 0x80, 0x81, 0x83, + 0x93, 0x92, 0x8c, 0x9d, 0xa4, 0x8e, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x00, 0x10 }, + }, { + /* 195 nits */ + { MCS_GAMMACTL, + 0x00, 0xc7, 0x00, 0xce, 0x00, 0xc9, + 0x86, 0x87, 0x86, 0x83, 0x83, 0x85, + 0x85, 0x84, 0x86, 0x82, 0x82, 0x85, + 0x80, 0x80, 0x81, 0x81, 0x81, 0x84, + 0x93, 0x92, 0x8c, 0x9d, 0xa4, 0x8e, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x00, 0x10 }, + }, { + /* 207 nits */ + { MCS_GAMMACTL, + 0x00, 0xcc, 0x00, 0xd2, 0x00, 0xce, + 0x86, 0x86, 0x87, 0x81, 0x83, 0x84, + 0x84, 0x82, 0x84, 0x83, 0x83, 0x85, + 0x81, 0x81, 0x82, 0x7c, 0x7d, 0x81, + 0x93, 0x92, 0x8c, 0x9d, 0xa4, 0x8e, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x00, 0x10 }, + }, { + /* 220 nits */ + { MCS_GAMMACTL, + 0x00, 0xd1, 0x00, 0xd6, 0x00, 0xd3, + 0x86, 0x86, 0x86, 0x81, 0x83, 0x84, + 0x84, 0x82, 0x84, 0x80, 0x80, 0x83, + 0x81, 0x81, 0x82, 0x7c, 0x7d, 0x81, + 0x93, 0x92, 0x8c, 0x9d, 0xa4, 0x8e, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x00, 0x10 }, + }, { + /* 234 nits */ + { MCS_GAMMACTL, + 0x00, 0xd6, 0x00, 0xdb, 0x00, 0xd8, + 0x85, 0x85, 0x85, 0x81, 0x83, 0x84, + 0x83, 0x82, 0x83, 0x80, 0x80, 0x82, + 0x84, 0x82, 0x83, 0x79, 0x79, 0x7e, + 0x93, 0x92, 0x8d, 0x9d, 0xa4, 0x8e, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x00, 0x10 }, + }, { + /* 249 nits */ + { MCS_GAMMACTL, + 0x00, 0xdc, 0x00, 0xe0, 0x00, 0xdd, + 0x84, 0x84, 0x84, 0x81, 0x82, 0x83, + 0x84, 0x82, 0x84, 0x7f, 0x7f, 0x82, + 0x81, 0x80, 0x81, 0x80, 0x81, 0x82, + 0x8c, 0x8c, 0x86, 0x9d, 0xa4, 0x8e, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x00, 0x10 }, + }, { + /* 265 nits */ + { MCS_GAMMACTL, + 0x00, 0xe2, 0x00, 0xe5, 0x00, 0xe3, + 0x83, 0x83, 0x83, 0x81, 0x82, 0x83, + 0x82, 0x82, 0x83, 0x82, 0x81, 0x83, + 0x7f, 0x7e, 0x80, 0x7c, 0x7d, 0x80, + 0x8c, 0x8c, 0x86, 0x8e, 0x92, 0x87, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x00, 0x10 }, + }, { + /* 282 nits */ + { MCS_GAMMACTL, + 0x00, 0xe8, 0x00, 0xea, 0x00, 0xe9, + 0x83, 0x83, 0x83, 0x80, 0x82, 0x82, + 0x81, 0x82, 0x82, 0x82, 0x81, 0x82, + 0x81, 0x80, 0x81, 0x80, 0x80, 0x81, + 0x85, 0x85, 0x83, 0x8e, 0x92, 0x87, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x00, 0x10 }, + }, { + /* 300 nits */ + { MCS_GAMMACTL, + 0x00, 0xed, 0x00, 0xef, 0x00, 0xed, + 0x81, 0x82, 0x81, 0x81, 0x81, 0x82, + 0x82, 0x82, 0x83, 0x80, 0x80, 0x81, + 0x81, 0x81, 0x82, 0x83, 0x83, 0x83, + 0x80, 0x80, 0x7f, 0x8e, 0x92, 0x87, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x00, 0x10 }, + }, { + /* 316 nits */ + { MCS_GAMMACTL, + 0x00, 0xf3, 0x00, 0xf4, 0x00, 0xf3, + 0x80, 0x81, 0x80, 0x81, 0x81, 0x81, + 0x82, 0x82, 0x82, 0x81, 0x80, 0x81, + 0x82, 0x82, 0x83, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x7f, 0x80, 0x80, 0x80, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x00, 0x10 }, + }, { + /* 333 nits */ + { MCS_GAMMACTL, + 0x00, 0xf8, 0x00, 0xf8, 0x00, 0xf8, + 0x80, 0x81, 0x80, 0x81, 0x80, 0x81, + 0x81, 0x82, 0x82, 0x81, 0x80, 0x81, + 0x83, 0x83, 0x83, 0x7e, 0x7d, 0x7e, + 0x80, 0x80, 0x7f, 0x80, 0x80, 0x80, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x00, 0x10 }, + }, { + /* 360 nits */ + { MCS_GAMMACTL, + 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x00, 0x10 }, + }, { + /* 378 nits */ + { MCS_GAMMACTL, + 0x01, 0x04, 0x01, 0x03, 0x01, 0x04, + 0x7f, 0x7f, 0x80, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x80, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x00, 0x10 }, + }, { + /* 395 nits */ + { MCS_GAMMACTL, + 0x01, 0x09, 0x01, 0x07, 0x01, 0x08, + 0x7e, 0x7f, 0x80, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x80, 0x7f, 0x7f, 0x7e, 0x7e, 0x7e, + 0x80, 0x80, 0x7f, 0x7e, 0x7e, 0x7f, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x00, 0x10 }, + }, { + /* 413 nits */ + { MCS_GAMMACTL, + 0x01, 0x0e, 0x01, 0x0b, 0x01, 0x0c, + 0x7e, 0x7f, 0x80, 0x7e, 0x7e, 0x7e, + 0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x7f, + 0x80, 0x7f, 0x7f, 0x7d, 0x7d, 0x7d, + 0x80, 0x80, 0x7f, 0x7d, 0x7e, 0x7e, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x00, 0x10 }, + }, { + /* 430 nits */ + { MCS_GAMMACTL, + 0x01, 0x13, 0x01, 0x0f, 0x01, 0x10, + 0x7d, 0x7f, 0x80, 0x7e, 0x7e, 0x7e, + 0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x7f, + 0x80, 0x7f, 0x7f, 0x7d, 0x7d, 0x7d, + 0x80, 0x80, 0x7f, 0x7c, 0x7d, 0x7e, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x00, 0x10 }, + }, { + /* 448 nits */ + { MCS_GAMMACTL, + 0x01, 0x18, 0x01, 0x13, 0x01, 0x14, + 0x7c, 0x7e, 0x80, 0x7e, 0x7e, 0x7e, + 0x7e, 0x7e, 0x7d, 0x7e, 0x7f, 0x7e, + 0x80, 0x7f, 0x7f, 0x7c, 0x7c, 0x7c, + 0x80, 0x80, 0x7e, 0x7b, 0x7c, 0x7d, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x00, 0x10 }, + }, { + /* 465 nits */ + { MCS_GAMMACTL, + 0x01, 0x1d, 0x01, 0x17, 0x01, 0x18, + 0x7c, 0x7e, 0x80, 0x7d, 0x7d, 0x7d, + 0x7d, 0x7d, 0x7d, 0x7e, 0x7f, 0x7e, + 0x80, 0x7f, 0x7f, 0x7b, 0x7b, 0x7b, + 0x80, 0x80, 0x7e, 0x7a, 0x7c, 0x7d, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x00, 0x10 }, + }, { + /* 483 nits */ + { MCS_GAMMACTL, + 0x01, 0x22, 0x01, 0x1b, 0x01, 0x1c, + 0x7b, 0x7e, 0x80, 0x7d, 0x7d, 0x7d, + 0x7d, 0x7d, 0x7c, 0x7e, 0x7f, 0x7e, + 0x80, 0x7f, 0x7f, 0x7a, 0x7a, 0x7a, + 0x80, 0x80, 0x7e, 0x79, 0x7b, 0x7c, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x00, 0x10 }, + }, { + /* 500 nits */ + { MCS_GAMMACTL, + 0x01, 0x27, 0x01, 0x1f, 0x01, 0x20, + 0x7b, 0x7e, 0x80, 0x7d, 0x7d, 0x7d, + 0x7d, 0x7d, 0x7c, 0x7e, 0x7f, 0x7e, + 0x80, 0x7f, 0x7f, 0x7a, 0x7a, 0x7a, + 0x81, 0x80, 0x7e, 0x79, 0x7b, 0x7c, + 0x00, 0x00, 0x00, }, + { MCS_AIDCTL, 0x00, 0x10 }, + }, +}; + +struct s6e8aa5x01_ams561ra01_ctx { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct backlight_device *bl; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data *supplies; + u32 nr_supplies; +}; + +static const struct regulator_bulk_data s6e8aa5x01_ams561ra01_supplies[] = { + { .supply = "vdd" }, + { .supply = "vci" }, +}; + +static inline struct s6e8aa5x01_ams561ra01_ctx *to_ctx(struct drm_panel *panel) +{ + return container_of(panel, struct s6e8aa5x01_ams561ra01_ctx, panel); +} + +static int s6e8aa5x01_ams561ra01_update_status(struct backlight_device *bl) +{ + struct s6e8aa5x01_ams561ra01_ctx *ctx = bl_get_data(bl); + struct mipi_dsi_multi_context dsi = { .dsi = ctx->dsi }; + u16 lvl = backlight_get_brightness(bl); + + if (!ctx->panel.enabled) + return 0; + + mipi_dsi_dcs_write_seq_multi(&dsi, MCS_ACCESSPROT, 0x5a, 0x5a); + + mipi_dsi_dcs_write_buffer_multi(&dsi, + s6e8aa5x01_ams561ra01_cmds[lvl].gamma, + GAMMA_CMD_LEN); + mipi_dsi_dcs_write_buffer_multi(&dsi, + s6e8aa5x01_ams561ra01_cmds[lvl].aid, + AID_CMD_LEN); + mipi_dsi_dcs_write_seq_multi(&dsi, MCS_GAMMAUPD, 0x03); + + mipi_dsi_dcs_write_seq_multi(&dsi, MCS_ACCESSPROT, 0xa5, 0xa5); + + return dsi.accum_err; +} + +static int s6e8aa5x01_ams561ra01_prepare(struct drm_panel *panel) +{ + struct s6e8aa5x01_ams561ra01_ctx *ctx = to_ctx(panel); + struct device *dev = &ctx->dsi->dev; + int ret; + + ret = regulator_bulk_enable(ctx->nr_supplies, ctx->supplies); + if (ret < 0) { + dev_err(dev, "failed to enable regulators: %d\n", ret); + return ret; + } + + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(5000, 6000); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(5000, 6000); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(10000, 11000); + + return 0; +} + +static int s6e8aa5x01_ams561ra01_unprepare(struct drm_panel *panel) +{ + struct s6e8aa5x01_ams561ra01_ctx *ctx = to_ctx(panel); + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(5000, 6000); + + regulator_bulk_disable(ctx->nr_supplies, ctx->supplies); + + return 0; +} + +static int s6e8aa5x01_ams561ra01_enable(struct drm_panel *panel) +{ + struct s6e8aa5x01_ams561ra01_ctx *ctx = to_ctx(panel); + struct mipi_dsi_multi_context dsi = { .dsi = ctx->dsi }; + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi); + mipi_dsi_msleep(&dsi, 100); + + mipi_dsi_dcs_write_seq_multi(&dsi, MCS_ACCESSPROT, 0x5a, 0x5a); + + mipi_dsi_dcs_write_seq_multi(&dsi, MCS_PENTILE, 0xd8, 0xd8, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi, MCS_PCD, 0x5c); + mipi_dsi_dcs_write_seq_multi(&dsi, MCS_ERRFLAG, 0xed, 0xc7, 0x23, 0x67); + mipi_dsi_dcs_write_seq_multi(&dsi, MCS_DISPCTL, 0x0c, 0x0c, 0xb9, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi, MCS_LTPSCTL, + 0x00, 0x45, 0x10, 0x10, 0x08, 0x32, 0x54, 0x00, + 0x00, 0x00, 0x00, 0x07, 0x06, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x48, 0x5e, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x00, 0xad, 0x00, 0x00, + 0x08, 0x05, 0x2a, 0x54, 0x03, 0xcc, 0x00, 0xff, + 0xfb, 0x03, 0x0d, 0x00, 0x11, 0x0f, 0x02, 0x03, + 0x0b, 0x0c, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x00, 0x02, 0x03, 0x0b, + 0x0c, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13); + + mipi_dsi_dcs_write_seq_multi(&dsi, MCS_ACCESSPROT, 0xa5, 0xa5); + + mipi_dsi_dcs_set_display_on_multi(&dsi); + + return dsi.accum_err; +} + +static int s6e8aa5x01_ams561ra01_disable(struct drm_panel *panel) +{ + struct s6e8aa5x01_ams561ra01_ctx *ctx = to_ctx(panel); + struct mipi_dsi_multi_context dsi = { .dsi = ctx->dsi }; + + mipi_dsi_dcs_set_display_off_multi(&dsi); + mipi_dsi_msleep(&dsi, 100); + + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi); + mipi_dsi_msleep(&dsi, 150); + + return dsi.accum_err; +} + +static const struct drm_display_mode s6e8aa5x01_ams561ra01_mode = { + .clock = (720 + 62 + 2 + 26) * (1480 + 12 + 2 + 10) * 60 / 1000, + .hdisplay = 720, + .hsync_start = 720 + 62, + .hsync_end = 720 + 62 + 2, + .htotal = 720 + 62 + 2 + 26, + .vdisplay = 1480, + .vsync_start = 1480 + 12, + .vsync_end = 1480 + 12 + 2, + .vtotal = 1480 + 12 + 2 + 10, + .width_mm = 62, + .height_mm = 128, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, +}; + +static int s6e8aa5x01_ams561ra01_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + return drm_connector_helper_get_modes_fixed(connector, + &s6e8aa5x01_ams561ra01_mode); +} + +static const struct backlight_ops s6e8aa5x01_ams561ra01_bl_ops = { + .update_status = s6e8aa5x01_ams561ra01_update_status, +}; + +static const struct drm_panel_funcs s6e8aa5x01_ams561ra01_panel_funcs = { + .prepare = s6e8aa5x01_ams561ra01_prepare, + .unprepare = s6e8aa5x01_ams561ra01_unprepare, + .enable = s6e8aa5x01_ams561ra01_enable, + .disable = s6e8aa5x01_ams561ra01_disable, + .get_modes = s6e8aa5x01_ams561ra01_get_modes, +}; + +static int s6e8aa5x01_ams561ra01_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct s6e8aa5x01_ams561ra01_ctx *ctx; + int ret; + + ctx = devm_drm_panel_alloc(dev, struct s6e8aa5x01_ams561ra01_ctx, panel, + &s6e8aa5x01_ams561ra01_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + ctx->nr_supplies = ARRAY_SIZE(s6e8aa5x01_ams561ra01_supplies); + ret = devm_regulator_bulk_get_const(dev, ctx->nr_supplies, + s6e8aa5x01_ams561ra01_supplies, + &ctx->supplies); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to get regulators\n"); + + ctx->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_ASIS); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), + "failed to get reset-gpios\n"); + + ctx->bl = devm_backlight_device_register(dev, dev_name(dev), dev, ctx, + &s6e8aa5x01_ams561ra01_bl_ops, + NULL); + if (IS_ERR(ctx->bl)) + return dev_err_probe(dev, PTR_ERR(ctx->bl), + "failed to register backlight device\n"); + + ctx->bl->props.type = BACKLIGHT_PLATFORM; + ctx->bl->props.brightness = ARRAY_SIZE(s6e8aa5x01_ams561ra01_cmds) - 1; + ctx->bl->props.max_brightness = ctx->bl->props.brightness; + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_VIDEO_NO_HFP; + + ctx->panel.prepare_prev_first = true; + drm_panel_add(&ctx->panel); + + ret = devm_mipi_dsi_attach(dev, dsi); + if (ret < 0) { + drm_panel_remove(&ctx->panel); + return dev_err_probe(dev, ret, "failed to attach to DSI host\n"); + } + + return 0; +} + +static void s6e8aa5x01_ams561ra01_remove(struct mipi_dsi_device *dsi) +{ + struct s6e8aa5x01_ams561ra01_ctx *ctx = mipi_dsi_get_drvdata(dsi); + + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id s6e8aa5x01_ams561ra01_of_device_id[] = { + { .compatible = "samsung,s6e8aa5x01-ams561ra01" }, + { } +}; +MODULE_DEVICE_TABLE(of, s6e8aa5x01_ams561ra01_of_device_id); + +static struct mipi_dsi_driver s6e8aa5x01_ams561ra01_dsi_driver = { + .probe = s6e8aa5x01_ams561ra01_probe, + .remove = s6e8aa5x01_ams561ra01_remove, + .driver = { + .name = "panel-samsung-s6e8aa5x01-ams561ra01", + .of_match_table = s6e8aa5x01_ams561ra01_of_device_id, + }, +}; +module_mipi_dsi_driver(s6e8aa5x01_ams561ra01_dsi_driver); + +MODULE_AUTHOR("Kaustabh Chakraborty <kauschluss@disroot.org>"); +MODULE_DESCRIPTION("Samsung AMS561RA01 Panel with S6E8AA5X01 Controller"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-samsung-sofef00.c b/drivers/gpu/drm/panel/panel-samsung-sofef00.c new file mode 100644 index 000000000000..e00a497a7c96 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-samsung-sofef00.c @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2020 Casey Connolly <casey.connolly@linaro.org> + * Generated with linux-mdss-dsi-panel-driver-generator from vendor device tree: + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regulator/consumer.h> +#include <linux/backlight.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_probe_helper.h> + +struct sofef00_panel { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct regulator_bulk_data *supplies; + struct gpio_desc *reset_gpio; +}; + +static const struct regulator_bulk_data sofef00_supplies[] = { + { .supply = "vddio" }, + { .supply = "vci" }, + { .supply = "poc" }, +}; + +static inline +struct sofef00_panel *to_sofef00_panel(struct drm_panel *panel) +{ + return container_of(panel, struct sofef00_panel, panel); +} + +#define sofef00_test_key_on_lvl2(ctx) \ + mipi_dsi_dcs_write_seq_multi(ctx, 0xf0, 0x5a, 0x5a) +#define sofef00_test_key_off_lvl2(ctx) \ + mipi_dsi_dcs_write_seq_multi(ctx, 0xf0, 0xa5, 0xa5) + +static void sofef00_panel_reset(struct sofef00_panel *ctx) +{ + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(5000, 6000); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(2000, 3000); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(12000, 13000); +} + +static int sofef00_panel_on(struct sofef00_panel *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + mipi_dsi_usleep_range(&dsi_ctx, 10000, 11000); + + sofef00_test_key_on_lvl2(&dsi_ctx); + mipi_dsi_dcs_set_tear_on_multi(&dsi_ctx, MIPI_DSI_DCS_TEAR_MODE_VBLANK); + sofef00_test_key_off_lvl2(&dsi_ctx); + + sofef00_test_key_on_lvl2(&dsi_ctx); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x07); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb6, 0x12); + sofef00_test_key_off_lvl2(&dsi_ctx); + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x20); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_POWER_SAVE, 0x00); + + return dsi_ctx.accum_err; +} + +static int sofef00_enable(struct drm_panel *panel) +{ + struct sofef00_panel *ctx = to_sofef00_panel(panel); + struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + + return dsi_ctx.accum_err; +} + +static int sofef00_panel_off(struct sofef00_panel *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 40); + + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 160); + + return dsi_ctx.accum_err; +} + +static int sofef00_panel_prepare(struct drm_panel *panel) +{ + struct sofef00_panel *ctx = to_sofef00_panel(panel); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(sofef00_supplies), ctx->supplies); + if (ret < 0) + return ret; + + sofef00_panel_reset(ctx); + + ret = sofef00_panel_on(ctx); + if (ret < 0) { + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(sofef00_supplies), ctx->supplies); + return ret; + } + + return 0; +} + +static int sofef00_disable(struct drm_panel *panel) +{ + struct sofef00_panel *ctx = to_sofef00_panel(panel); + + sofef00_panel_off(ctx); + + return 0; +} + +static int sofef00_panel_unprepare(struct drm_panel *panel) +{ + struct sofef00_panel *ctx = to_sofef00_panel(panel); + + regulator_bulk_disable(ARRAY_SIZE(sofef00_supplies), ctx->supplies); + + return 0; +} + +static const struct drm_display_mode ams628nw01_panel_mode = { + .clock = (1080 + 112 + 16 + 36) * (2280 + 36 + 8 + 12) * 60 / 1000, + + .hdisplay = 1080, + .hsync_start = 1080 + 112, + .hsync_end = 1080 + 112 + 16, + .htotal = 1080 + 112 + 16 + 36, + + .vdisplay = 2280, + .vsync_start = 2280 + 36, + .vsync_end = 2280 + 36 + 8, + .vtotal = 2280 + 36 + 8 + 12, + + .width_mm = 68, + .height_mm = 145, + + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, +}; + +static int sofef00_panel_get_modes(struct drm_panel *panel, struct drm_connector *connector) +{ + return drm_connector_helper_get_modes_fixed(connector, &ams628nw01_panel_mode); +} + +static const struct drm_panel_funcs sofef00_panel_panel_funcs = { + .prepare = sofef00_panel_prepare, + .enable = sofef00_enable, + .disable = sofef00_disable, + .unprepare = sofef00_panel_unprepare, + .get_modes = sofef00_panel_get_modes, +}; + +static int sofef00_panel_bl_update_status(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + int err; + u16 brightness = (u16)backlight_get_brightness(bl); + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + err = mipi_dsi_dcs_set_display_brightness_large(dsi, brightness); + if (err < 0) + return err; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + return 0; +} + +static const struct backlight_ops sofef00_panel_bl_ops = { + .update_status = sofef00_panel_bl_update_status, +}; + +static struct backlight_device * +sofef00_create_backlight(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + const struct backlight_properties props = { + .type = BACKLIGHT_PLATFORM, + .brightness = 512, + .max_brightness = 1023, + }; + + return devm_backlight_device_register(dev, dev_name(dev), dev, dsi, + &sofef00_panel_bl_ops, &props); +} + +static int sofef00_panel_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct sofef00_panel *ctx; + int ret; + + ctx = devm_drm_panel_alloc(dev, struct sofef00_panel, panel, + &sofef00_panel_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ret = devm_regulator_bulk_get_const(dev, + ARRAY_SIZE(sofef00_supplies), + sofef00_supplies, + &ctx->supplies); + if (ret) + return dev_err_probe(dev, ret, "Failed to get regulators\n"); + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), + "Failed to get reset-gpios\n"); + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_CLOCK_NON_CONTINUOUS | MIPI_DSI_MODE_LPM; + + ctx->panel.prepare_prev_first = true; + + ctx->panel.backlight = sofef00_create_backlight(dsi); + if (IS_ERR(ctx->panel.backlight)) + return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight), + "Failed to create backlight\n"); + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "Failed to attach to DSI host: %d\n", ret); + drm_panel_remove(&ctx->panel); + return ret; + } + + return 0; +} + +static void sofef00_panel_remove(struct mipi_dsi_device *dsi) +{ + struct sofef00_panel *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id sofef00_panel_of_match[] = { + { .compatible = "samsung,sofef00" }, /* legacy */ + { .compatible = "samsung,sofef00-ams628nw01" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, sofef00_panel_of_match); + +static struct mipi_dsi_driver sofef00_panel_driver = { + .probe = sofef00_panel_probe, + .remove = sofef00_panel_remove, + .driver = { + .name = "panel-samsung-sofef00", + .of_match_table = sofef00_panel_of_match, + }, +}; + +module_mipi_dsi_driver(sofef00_panel_driver); + +MODULE_AUTHOR("Casey Connolly <casey.connolly@linaro.org>"); +MODULE_DESCRIPTION("DRM driver for Samsung SOFEF00 DDIC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-seiko-43wvf1g.c b/drivers/gpu/drm/panel/panel-seiko-43wvf1g.c index 2d99e28ff117..0935d83ee2db 100644 --- a/drivers/gpu/drm/panel/panel-seiko-43wvf1g.c +++ b/drivers/gpu/drm/panel/panel-seiko-43wvf1g.c @@ -6,18 +6,21 @@ * Based on Panel Simple driver by Thierry Reding <treding@nvidia.com> */ -#include <linux/backlight.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/platform_device.h> #include <linux/regulator/consumer.h> -#include <drm/drmP.h> -#include <drm/drm_crtc.h> -#include <drm/drm_panel.h> - #include <video/display_timing.h> #include <video/videomode.h> +#include <drm/drm_crtc.h> +#include <drm/drm_device.h> +#include <drm/drm_panel.h> + struct seiko_panel_desc { const struct drm_display_mode *modes; unsigned int num_modes; @@ -41,12 +44,10 @@ struct seiko_panel_desc { struct seiko_panel { struct drm_panel base; - bool prepared; - bool enabled; const struct seiko_panel_desc *desc; - struct backlight_device *backlight; struct regulator *dvdd; struct regulator *avdd; + struct gpio_desc *enable_gpio; }; static inline struct seiko_panel *to_seiko_panel(struct drm_panel *panel) @@ -54,10 +55,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; @@ -69,9 +69,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; } @@ -90,10 +90,11 @@ 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", - m->hdisplay, m->vdisplay, m->vrefresh); + dev_err(panel->base.dev, "failed to add mode %ux%u@%u\n", + m->hdisplay, m->vdisplay, + drm_mode_vrefresh(m)); continue; } @@ -119,30 +120,11 @@ static int seiko_panel_get_fixed_modes(struct seiko_panel *panel) return num; } -static int seiko_panel_disable(struct drm_panel *panel) -{ - struct seiko_panel *p = to_seiko_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; -} - static int seiko_panel_unprepare(struct drm_panel *panel) { struct seiko_panel *p = to_seiko_panel(panel); - if (!p->prepared) - return 0; + gpiod_set_value_cansleep(p->enable_gpio, 0); regulator_disable(p->avdd); @@ -151,8 +133,6 @@ static int seiko_panel_unprepare(struct drm_panel *panel) regulator_disable(p->dvdd); - p->prepared = false; - return 0; } @@ -161,9 +141,6 @@ static int seiko_panel_prepare(struct drm_panel *panel) struct seiko_panel *p = to_seiko_panel(panel); int err; - if (p->prepared) - return 0; - err = regulator_enable(p->dvdd); if (err < 0) { dev_err(panel->dev, "failed to enable dvdd: %d\n", err); @@ -179,7 +156,7 @@ static int seiko_panel_prepare(struct drm_panel *panel) goto disable_dvdd; } - p->prepared = true; + gpiod_set_value_cansleep(p->enable_gpio, 1); return 0; @@ -188,30 +165,13 @@ disable_dvdd: return err; } -static int seiko_panel_enable(struct drm_panel *panel) -{ - struct seiko_panel *p = to_seiko_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, @@ -232,10 +192,8 @@ static int seiko_panel_get_timings(struct drm_panel *panel, } static const struct drm_panel_funcs seiko_panel_funcs = { - .disable = seiko_panel_disable, .unprepare = seiko_panel_unprepare, .prepare = seiko_panel_prepare, - .enable = seiko_panel_enable, .get_modes = seiko_panel_get_modes, .get_timings = seiko_panel_get_timings, }; @@ -243,16 +201,15 @@ 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; - panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL); - if (!panel) - return -ENOMEM; + panel = devm_drm_panel_alloc(dev, struct seiko_panel, base, + &seiko_panel_funcs, + DRM_MODE_CONNECTOR_DPI); + if (IS_ERR(panel)) + return PTR_ERR(panel); - panel->enabled = false; - panel->prepared = false; panel->desc = desc; panel->dvdd = devm_regulator_get(dev, "dvdd"); @@ -263,47 +220,28 @@ 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); - - if (!panel->backlight) - return -EPROBE_DEFER; - } - - drm_panel_init(&panel->base); - panel->base.dev = dev; - panel->base.funcs = &seiko_panel_funcs; + panel->enable_gpio = devm_gpiod_get_optional(dev, "enable", + GPIOD_OUT_LOW); + if (IS_ERR(panel->enable_gpio)) + return dev_err_probe(dev, PTR_ERR(panel->enable_gpio), + "failed to request GPIO\n"); - err = drm_panel_add(&panel->base); - if (err < 0) + err = drm_panel_of_backlight(&panel->base); + if (err) return err; + drm_panel_add(&panel->base); + dev_set_drvdata(dev, panel); return 0; } -static int seiko_panel_remove(struct platform_device *pdev) +static void seiko_panel_remove(struct platform_device *pdev) { - struct seiko_panel *panel = dev_get_drvdata(&pdev->dev); + struct seiko_panel *panel = platform_get_drvdata(pdev); drm_panel_remove(&panel->base); - - seiko_panel_disable(&panel->base); - - if (panel->backlight) - put_device(&panel->backlight->dev); - - return 0; -} - -static void seiko_panel_shutdown(struct platform_device *pdev) -{ - struct seiko_panel *panel = dev_get_drvdata(&pdev->dev); - - seiko_panel_disable(&panel->base); } static const struct display_timing seiko_43wvf1g_timing = { @@ -328,7 +266,7 @@ static const struct seiko_panel_desc seiko_43wvf1g = { .height = 57, }, .bus_format = MEDIA_BUS_FMT_RGB888_1X24, - .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_NEGEDGE, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE, }; static const struct of_device_id platform_of_match[] = { @@ -359,7 +297,6 @@ static struct platform_driver seiko_panel_platform_driver = { }, .probe = seiko_panel_platform_probe, .remove = seiko_panel_remove, - .shutdown = seiko_panel_shutdown, }; module_platform_driver(seiko_panel_platform_driver); diff --git a/drivers/gpu/drm/panel/panel-sharp-lq079l1sx01.c b/drivers/gpu/drm/panel/panel-sharp-lq079l1sx01.c new file mode 100644 index 000000000000..8c00fde1c4a9 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-sharp-lq079l1sx01.c @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016 XiaoMi, Inc. + * Copyright (c) 2024 Svyatoslav Ryhel <clamor95@gmail.com> + */ + +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/regulator/consumer.h> + +#include <video/mipi_display.h> + +#include <drm/drm_connector.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_probe_helper.h> + +static const struct regulator_bulk_data sharp_supplies[] = { + { .supply = "avdd" }, { .supply = "vddio" }, + { .supply = "vsp" }, { .supply = "vsn" }, +}; + +struct sharp_panel { + struct drm_panel panel; + struct mipi_dsi_device *dsi[2]; + + struct gpio_desc *reset_gpio; + struct regulator_bulk_data *supplies; + + const struct drm_display_mode *mode; +}; + +static inline struct sharp_panel *to_sharp_panel(struct drm_panel *panel) +{ + return container_of(panel, struct sharp_panel, panel); +} + +static void sharp_panel_reset(struct sharp_panel *sharp) +{ + gpiod_set_value_cansleep(sharp->reset_gpio, 1); + usleep_range(2000, 3000); + gpiod_set_value_cansleep(sharp->reset_gpio, 0); + usleep_range(2000, 3000); +} + +static int sharp_panel_prepare(struct drm_panel *panel) +{ + struct sharp_panel *sharp = to_sharp_panel(panel); + struct device *dev = panel->dev; + struct mipi_dsi_device *dsi0 = sharp->dsi[0]; + struct mipi_dsi_device *dsi1 = sharp->dsi[1]; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = NULL }; + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(sharp_supplies), sharp->supplies); + if (ret) { + dev_err(dev, "error enabling regulators (%d)\n", ret); + return ret; + } + + msleep(24); + + if (sharp->reset_gpio) + sharp_panel_reset(sharp); + + msleep(32); + + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, MIPI_DCS_EXIT_SLEEP_MODE); + mipi_dsi_msleep(&dsi_ctx, 120); + + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, + MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0xff); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, + MIPI_DCS_WRITE_POWER_SAVE, 0x01); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, + MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x2c); + + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, MIPI_DCS_SET_DISPLAY_ON); + + return 0; +} + +static int sharp_panel_unprepare(struct drm_panel *panel) +{ + struct sharp_panel *sharp = to_sharp_panel(panel); + struct mipi_dsi_device *dsi0 = sharp->dsi[0]; + struct mipi_dsi_device *dsi1 = sharp->dsi[1]; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = NULL }; + + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, MIPI_DCS_SET_DISPLAY_OFF); + mipi_dsi_msleep(&dsi_ctx, 100); + mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, MIPI_DCS_ENTER_SLEEP_MODE); + mipi_dsi_msleep(&dsi_ctx, 150); + + if (sharp->reset_gpio) + gpiod_set_value_cansleep(sharp->reset_gpio, 1); + + return regulator_bulk_disable(ARRAY_SIZE(sharp_supplies), sharp->supplies); +} + +static const struct drm_display_mode default_mode = { + .clock = (1536 + 136 + 28 + 28) * (2048 + 14 + 8 + 2) * 60 / 1000, + .hdisplay = 1536, + .hsync_start = 1536 + 136, + .hsync_end = 1536 + 136 + 28, + .htotal = 1536 + 136 + 28 + 28, + .vdisplay = 2048, + .vsync_start = 2048 + 14, + .vsync_end = 2048 + 14 + 8, + .vtotal = 2048 + 14 + 8 + 2, + .width_mm = 120, + .height_mm = 160, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, +}; + +static int sharp_panel_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + return drm_connector_helper_get_modes_fixed(connector, &default_mode); +} + +static const struct drm_panel_funcs sharp_panel_funcs = { + .unprepare = sharp_panel_unprepare, + .prepare = sharp_panel_prepare, + .get_modes = sharp_panel_get_modes, +}; + +static int sharp_panel_probe(struct mipi_dsi_device *dsi) +{ + const struct mipi_dsi_device_info info = { "sharp-link1", 0, NULL }; + struct device *dev = &dsi->dev; + struct device_node *dsi_r; + struct mipi_dsi_host *dsi_r_host; + struct sharp_panel *sharp; + int i, ret; + + sharp = devm_drm_panel_alloc(dev, struct sharp_panel, panel, + &sharp_panel_funcs, DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(sharp)) + return PTR_ERR(sharp); + + ret = devm_regulator_bulk_get_const(dev, ARRAY_SIZE(sharp_supplies), + sharp_supplies, &sharp->supplies); + if (ret) + return dev_err_probe(dev, ret, "failed to get supplies\n"); + + sharp->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(sharp->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(sharp->reset_gpio), + "failed to get reset GPIO\n"); + + /* Panel is always connected to two DSI hosts, DSI0 is left, DSI1 is right */ + dsi_r = of_graph_get_remote_node(dsi->dev.of_node, 1, -1); + if (!dsi_r) + return dev_err_probe(dev, -ENODEV, "failed to find second DSI host node\n"); + + dsi_r_host = of_find_mipi_dsi_host_by_node(dsi_r); + of_node_put(dsi_r); + if (!dsi_r_host) + return dev_err_probe(dev, -EPROBE_DEFER, "cannot get secondary DSI host\n"); + + sharp->dsi[1] = devm_mipi_dsi_device_register_full(dev, dsi_r_host, &info); + if (IS_ERR(sharp->dsi[1])) + return dev_err_probe(dev, PTR_ERR(sharp->dsi[1]), + "second link registration failed\n"); + + sharp->dsi[0] = dsi; + mipi_dsi_set_drvdata(dsi, sharp); + + ret = drm_panel_of_backlight(&sharp->panel); + if (ret) + return dev_err_probe(dev, ret, "Failed to get backlight\n"); + + drm_panel_add(&sharp->panel); + + for (i = 0; i < ARRAY_SIZE(sharp->dsi); i++) { + if (!sharp->dsi[i]) + continue; + + sharp->dsi[i]->lanes = 4; + sharp->dsi[i]->format = MIPI_DSI_FMT_RGB888; + sharp->dsi[i]->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM; + + ret = devm_mipi_dsi_attach(dev, sharp->dsi[i]); + if (ret < 0) { + drm_panel_remove(&sharp->panel); + return dev_err_probe(dev, ret, "failed to attach to DSI%d\n", i); + } + } + + return 0; +} + +static void sharp_panel_remove(struct mipi_dsi_device *dsi) +{ + struct sharp_panel *sharp = mipi_dsi_get_drvdata(dsi); + + drm_panel_remove(&sharp->panel); +} + +static const struct of_device_id sharp_of_match[] = { + { .compatible = "sharp,lq079l1sx01" }, + { } +}; +MODULE_DEVICE_TABLE(of, sharp_of_match); + +static struct mipi_dsi_driver sharp_panel_driver = { + .driver = { + .name = "panel-sharp-lq079l1sx01", + .of_match_table = sharp_of_match, + }, + .probe = sharp_panel_probe, + .remove = sharp_panel_remove, +}; +module_mipi_dsi_driver(sharp_panel_driver); + +MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>"); +MODULE_DESCRIPTION("Sharp LQ079L1SX01 panel driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c b/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c index 02fc0f5423d4..d159b0e4fdb6 100644 --- a/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c +++ b/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c @@ -1,36 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2014 NVIDIA Corporation - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. */ -#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 <drm/drmP.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_panel.h> -#include <video/mipi_display.h> - struct sharp_panel { struct drm_panel base; /* the datasheet refers to them as DSI-LINK1 and DSI-LINK2 */ struct mipi_dsi_device *link1; struct mipi_dsi_device *link2; - struct backlight_device *backlight; struct regulator *supply; - bool prepared; - bool enabled; - const struct drm_display_mode *mode; }; @@ -89,28 +82,11 @@ static __maybe_unused int sharp_panel_read(struct sharp_panel *sharp, return err; } -static int sharp_panel_disable(struct drm_panel *panel) -{ - struct sharp_panel *sharp = to_sharp_panel(panel); - - if (!sharp->enabled) - return 0; - - backlight_disable(sharp->backlight); - - sharp->enabled = false; - - return 0; -} - static int sharp_panel_unprepare(struct drm_panel *panel) { struct sharp_panel *sharp = to_sharp_panel(panel); int err; - if (!sharp->prepared) - return 0; - sharp_wait_frames(sharp, 4); err = mipi_dsi_dcs_set_display_off(sharp->link1); @@ -125,8 +101,6 @@ static int sharp_panel_unprepare(struct drm_panel *panel) regulator_disable(sharp->supply); - sharp->prepared = false; - return 0; } @@ -170,9 +144,6 @@ static int sharp_panel_prepare(struct drm_panel *panel) u8 format = MIPI_DCS_PIXEL_FMT_24BIT; int err; - if (sharp->prepared) - return 0; - err = regulator_enable(sharp->supply); if (err < 0) return err; @@ -241,8 +212,6 @@ static int sharp_panel_prepare(struct drm_panel *panel) goto poweroff; } - sharp->prepared = true; - /* wait for 6 frames before continuing */ sharp_wait_frames(sharp, 6); @@ -253,20 +222,6 @@ poweroff: return err; } -static int sharp_panel_enable(struct drm_panel *panel) -{ - struct sharp_panel *sharp = to_sharp_panel(panel); - - if (sharp->enabled) - return 0; - - backlight_enable(sharp->backlight); - - sharp->enabled = true; - - return 0; -} - static const struct drm_display_mode default_mode = { .clock = 278000, .hdisplay = 2560, @@ -277,36 +232,34 @@ static const struct drm_display_mode default_mode = { .vsync_start = 1600 + 4, .vsync_end = 1600 + 4 + 8, .vtotal = 1600 + 4 + 8 + 32, - .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); + drm_mode_vrefresh(&default_mode)); 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; } static const struct drm_panel_funcs sharp_panel_funcs = { - .disable = sharp_panel_disable, .unprepare = sharp_panel_unprepare, .prepare = sharp_panel_prepare, - .enable = sharp_panel_enable, .get_modes = sharp_panel_get_modes, }; @@ -318,7 +271,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; @@ -326,16 +279,13 @@ 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); + ret = drm_panel_of_backlight(&sharp->base); + if (ret) + return ret; - drm_panel_init(&sharp->base); - sharp->base.funcs = &sharp_panel_funcs; - sharp->base.dev = &sharp->link1->dev; + drm_panel_add(&sharp->base); - return drm_panel_add(&sharp->base); + return 0; } static void sharp_panel_del(struct sharp_panel *sharp) @@ -370,10 +320,12 @@ static int sharp_panel_probe(struct mipi_dsi_device *dsi) /* register a panel for only the DSI-LINK1 interface */ if (secondary) { - sharp = devm_kzalloc(&dsi->dev, sizeof(*sharp), GFP_KERNEL); - if (!sharp) { + sharp = devm_drm_panel_alloc(&dsi->dev, __typeof(*sharp), base, + &sharp_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(sharp)) { put_device(&secondary->dev); - return -ENOMEM; + return PTR_ERR(sharp); } mipi_dsi_set_drvdata(dsi, sharp); @@ -399,39 +351,18 @@ static int sharp_panel_probe(struct mipi_dsi_device *dsi) return 0; } -static int sharp_panel_remove(struct mipi_dsi_device *dsi) +static void sharp_panel_remove(struct mipi_dsi_device *dsi) { struct sharp_panel *sharp = mipi_dsi_get_drvdata(dsi); int err; - /* only detach from host for the DSI-LINK2 interface */ - if (!sharp) { - mipi_dsi_detach(dsi); - return 0; - } - - err = sharp_panel_disable(&sharp->base); - if (err < 0) - dev_err(&dsi->dev, "failed to disable panel: %d\n", err); - err = mipi_dsi_detach(dsi); if (err < 0) dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", err); - sharp_panel_del(sharp); - - return 0; -} - -static void sharp_panel_shutdown(struct mipi_dsi_device *dsi) -{ - struct sharp_panel *sharp = mipi_dsi_get_drvdata(dsi); - - /* nothing to do for DSI-LINK2 */ - if (!sharp) - return; - - sharp_panel_disable(&sharp->base); + /* only detach from host for the DSI-LINK2 interface */ + if (sharp) + sharp_panel_del(sharp); } static struct mipi_dsi_driver sharp_panel_driver = { @@ -441,7 +372,6 @@ static struct mipi_dsi_driver sharp_panel_driver = { }, .probe = sharp_panel_probe, .remove = sharp_panel_remove, - .shutdown = sharp_panel_shutdown, }; module_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..938beac4655d --- /dev/null +++ b/drivers/gpu/drm/panel/panel-sharp-ls037v7dw01.c @@ -0,0 +1,219 @@ +// 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, + .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_drm_panel_alloc(&pdev->dev, struct ls037v7dw01_panel, panel, + &ls037v7dw01_funcs, DRM_MODE_CONNECTOR_DPI); + if (IS_ERR(lcd)) + return PTR_ERR(lcd); + + platform_set_drvdata(pdev, lcd); + lcd->pdev = pdev; + + lcd->vdd = devm_regulator_get(&pdev->dev, "envdd"); + if (IS_ERR(lcd->vdd)) + return dev_err_probe(&pdev->dev, PTR_ERR(lcd->vdd), + "failed to get regulator\n"); + + lcd->ini_gpio = devm_gpiod_get(&pdev->dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(lcd->ini_gpio)) + return dev_err_probe(&pdev->dev, PTR_ERR(lcd->ini_gpio), + "failed to get enable gpio\n"); + + lcd->resb_gpio = devm_gpiod_get(&pdev->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(lcd->resb_gpio)) + return dev_err_probe(&pdev->dev, PTR_ERR(lcd->resb_gpio), + "failed to get reset gpio\n"); + + 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_add(&lcd->panel); + + return 0; +} + +static void 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); +} + +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 e5cae0050f52..36abfa2e65e9 100644 --- a/drivers/gpu/drm/panel/panel-sharp-ls043t1le01.c +++ b/drivers/gpu/drm/panel/panel-sharp-ls043t1le01.c @@ -1,48 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2015 Red Hat * Copyright (C) 2015 Sony Mobile Communications Inc. * Author: Werner Johansson <werner.johansson@sonymobile.com> * * Based on AUO panel driver by Rob Clark <robdclark@gmail.com> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published by - * the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see <http://www.gnu.org/licenses/>. */ -#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 <drm/drmP.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_panel.h> -#include <video/mipi_display.h> - 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; - - bool prepared; - bool enabled; - - const struct drm_display_mode *mode; }; static inline struct sharp_nt_panel *to_sharp_nt_panel(struct drm_panel *panel) @@ -53,75 +36,49 @@ static inline struct sharp_nt_panel *to_sharp_nt_panel(struct drm_panel *panel) static int sharp_nt_panel_init(struct sharp_nt_panel *sharp_nt) { struct mipi_dsi_device *dsi = sharp_nt->dsi; - int ret; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; dsi->mode_flags |= MIPI_DSI_MODE_LPM; - ret = mipi_dsi_dcs_exit_sleep_mode(dsi); - if (ret < 0) - return ret; + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); - msleep(120); + mipi_dsi_msleep(&dsi_ctx, 120); /* Novatek two-lane operation */ - ret = mipi_dsi_dcs_write(dsi, 0xae, (u8[]){ 0x03 }, 1); - if (ret < 0) - return ret; + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xae, 0x03); /* Set both MCU and RGB I/F to 24bpp */ - ret = mipi_dsi_dcs_set_pixel_format(dsi, MIPI_DCS_PIXEL_FMT_24BIT | - (MIPI_DCS_PIXEL_FMT_24BIT << 4)); - if (ret < 0) - return ret; + mipi_dsi_dcs_set_pixel_format_multi(&dsi_ctx, + MIPI_DCS_PIXEL_FMT_24BIT | + (MIPI_DCS_PIXEL_FMT_24BIT << 4)); - return 0; + return dsi_ctx.accum_err; } static int sharp_nt_panel_on(struct sharp_nt_panel *sharp_nt) { struct mipi_dsi_device *dsi = sharp_nt->dsi; - int ret; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; dsi->mode_flags |= MIPI_DSI_MODE_LPM; - ret = mipi_dsi_dcs_set_display_on(dsi); - if (ret < 0) - return ret; + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); - return 0; + return dsi_ctx.accum_err; } static int sharp_nt_panel_off(struct sharp_nt_panel *sharp_nt) { struct mipi_dsi_device *dsi = sharp_nt->dsi; - int ret; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; - ret = mipi_dsi_dcs_set_display_off(dsi); - if (ret < 0) - return ret; - - ret = mipi_dsi_dcs_enter_sleep_mode(dsi); - if (ret < 0) - return ret; - - return 0; -} - - -static int sharp_nt_panel_disable(struct drm_panel *panel) -{ - struct sharp_nt_panel *sharp_nt = to_sharp_nt_panel(panel); - - if (!sharp_nt->enabled) - return 0; - - backlight_disable(sharp_nt->backlight); + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); - sharp_nt->enabled = false; + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); - return 0; + return dsi_ctx.accum_err; } static int sharp_nt_panel_unprepare(struct drm_panel *panel) @@ -129,9 +86,6 @@ static int sharp_nt_panel_unprepare(struct drm_panel *panel) struct sharp_nt_panel *sharp_nt = to_sharp_nt_panel(panel); int ret; - if (!sharp_nt->prepared) - return 0; - ret = sharp_nt_panel_off(sharp_nt); if (ret < 0) { dev_err(panel->dev, "failed to set panel off: %d\n", ret); @@ -142,8 +96,6 @@ static int sharp_nt_panel_unprepare(struct drm_panel *panel) if (sharp_nt->reset_gpio) gpiod_set_value(sharp_nt->reset_gpio, 0); - sharp_nt->prepared = false; - return 0; } @@ -152,9 +104,6 @@ static int sharp_nt_panel_prepare(struct drm_panel *panel) struct sharp_nt_panel *sharp_nt = to_sharp_nt_panel(panel); int ret; - if (sharp_nt->prepared) - return 0; - ret = regulator_enable(sharp_nt->supply); if (ret < 0) return ret; @@ -182,8 +131,6 @@ static int sharp_nt_panel_prepare(struct drm_panel *panel) goto poweroff; } - sharp_nt->prepared = true; - return 0; poweroff: @@ -193,68 +140,51 @@ poweroff: return ret; } -static int sharp_nt_panel_enable(struct drm_panel *panel) -{ - struct sharp_nt_panel *sharp_nt = to_sharp_nt_panel(panel); - - if (sharp_nt->enabled) - return 0; - - backlight_enable(sharp_nt->backlight); - - sharp_nt->enabled = true; - - return 0; -} - static const struct drm_display_mode default_mode = { - .clock = 41118, + .clock = (540 + 48 + 32 + 80) * (960 + 3 + 10 + 15) * 60 / 1000, .hdisplay = 540, .hsync_start = 540 + 48, - .hsync_end = 540 + 48 + 80, - .htotal = 540 + 48 + 80 + 32, + .hsync_end = 540 + 48 + 32, + .htotal = 540 + 48 + 32 + 80, .vdisplay = 960, .vsync_start = 960 + 3, - .vsync_end = 960 + 3 + 15, - .vtotal = 960 + 3 + 15 + 1, - .vrefresh = 60, + .vsync_end = 960 + 3 + 10, + .vtotal = 960 + 3 + 10 + 15, }; -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, + drm_mode_vrefresh(&default_mode)); 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; } static const struct drm_panel_funcs sharp_nt_panel_funcs = { - .disable = sharp_nt_panel_disable, .unprepare = sharp_nt_panel_unprepare, .prepare = sharp_nt_panel_prepare, - .enable = sharp_nt_panel_enable, .get_modes = sharp_nt_panel_get_modes, }; static int sharp_nt_panel_add(struct sharp_nt_panel *sharp_nt) { struct device *dev = &sharp_nt->dsi->dev; - - sharp_nt->mode = &default_mode; + int ret; sharp_nt->supply = devm_regulator_get(dev, "avdd"); if (IS_ERR(sharp_nt->supply)) @@ -269,16 +199,16 @@ 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); + ret = drm_panel_of_backlight(&sharp_nt->base); + if (ret) + return ret; - drm_panel_init(&sharp_nt->base); - sharp_nt->base.funcs = &sharp_nt_panel_funcs; - sharp_nt->base.dev = &sharp_nt->dsi->dev; + drm_panel_add(&sharp_nt->base); - return drm_panel_add(&sharp_nt->base); + return 0; } static void sharp_nt_panel_del(struct sharp_nt_panel *sharp_nt) @@ -295,9 +225,10 @@ static int sharp_nt_panel_probe(struct mipi_dsi_device *dsi) dsi->lanes = 2; dsi->format = MIPI_DSI_FMT_RGB888; dsi->mode_flags = MIPI_DSI_MODE_VIDEO | + MIPI_DSI_MODE_VIDEO_SYNC_PULSE | MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_CLOCK_NON_CONTINUOUS | - MIPI_DSI_MODE_EOT_PACKET; + MIPI_DSI_MODE_NO_EOT_PACKET; sharp_nt = devm_kzalloc(&dsi->dev, sizeof(*sharp_nt), GFP_KERNEL); if (!sharp_nt) @@ -311,32 +242,25 @@ static int sharp_nt_panel_probe(struct mipi_dsi_device *dsi) if (ret < 0) return ret; - return mipi_dsi_attach(dsi); + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + sharp_nt_panel_del(sharp_nt); + return ret; + } + + return 0; } -static int sharp_nt_panel_remove(struct mipi_dsi_device *dsi) +static void 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); - if (ret < 0) - dev_err(&dsi->dev, "failed to disable panel: %d\n", ret); - ret = mipi_dsi_detach(dsi); if (ret < 0) dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", ret); sharp_nt_panel_del(sharp_nt); - - return 0; -} - -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); } static const struct of_device_id sharp_nt_of_match[] = { @@ -352,7 +276,6 @@ static struct mipi_dsi_driver sharp_nt_panel_driver = { }, .probe = sharp_nt_panel_probe, .remove = sharp_nt_panel_remove, - .shutdown = sharp_nt_panel_shutdown, }; module_mipi_dsi_driver(sharp_nt_panel_driver); diff --git a/drivers/gpu/drm/panel/panel-sharp-ls060t1sx01.c b/drivers/gpu/drm/panel/panel-sharp-ls060t1sx01.c new file mode 100644 index 000000000000..0456f3d705e7 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-sharp-ls060t1sx01.c @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2021 Linaro Ltd. + * Generated with linux-mdss-dsi-panel-driver-generator from vendor device tree: + * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. + */ + +#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> + +struct sharp_ls060 { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct regulator *vddi_supply; + struct regulator *vddh_supply; + struct regulator *avdd_supply; + struct regulator *avee_supply; + struct gpio_desc *reset_gpio; +}; + +static inline struct sharp_ls060 *to_sharp_ls060(struct drm_panel *panel) +{ + return container_of(panel, struct sharp_ls060, panel); +} + +static void sharp_ls060_reset(struct sharp_ls060 *ctx) +{ + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(10000, 11000); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(10000, 11000); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(10000, 11000); +} + +static int sharp_ls060_on(struct sharp_ls060 *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xbb, 0x13); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_MEMORY_START); + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 120); + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 50); + + return dsi_ctx.accum_err; +} + +static void sharp_ls060_off(struct sharp_ls060 *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_usleep_range(&dsi_ctx, 2000, 3000); + + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 121); +} + +static int sharp_ls060_prepare(struct drm_panel *panel) +{ + struct sharp_ls060 *ctx = to_sharp_ls060(panel); + int ret; + + ret = regulator_enable(ctx->vddi_supply); + if (ret < 0) + return ret; + + ret = regulator_enable(ctx->avdd_supply); + if (ret < 0) + goto err_avdd; + + usleep_range(1000, 2000); + + ret = regulator_enable(ctx->avee_supply); + if (ret < 0) + goto err_avee; + + usleep_range(10000, 11000); + + ret = regulator_enable(ctx->vddh_supply); + if (ret < 0) + goto err_vddh; + + usleep_range(10000, 11000); + + sharp_ls060_reset(ctx); + + ret = sharp_ls060_on(ctx); + if (ret < 0) + goto err_on; + + return 0; + +err_on: + regulator_disable(ctx->vddh_supply); + + usleep_range(10000, 11000); + +err_vddh: + regulator_disable(ctx->avee_supply); + +err_avee: + regulator_disable(ctx->avdd_supply); + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + +err_avdd: + regulator_disable(ctx->vddi_supply); + + return ret; +} + +static int sharp_ls060_unprepare(struct drm_panel *panel) +{ + struct sharp_ls060 *ctx = to_sharp_ls060(panel); + + sharp_ls060_off(ctx); + + regulator_disable(ctx->vddh_supply); + + usleep_range(10000, 11000); + + regulator_disable(ctx->avee_supply); + regulator_disable(ctx->avdd_supply); + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + + regulator_disable(ctx->vddi_supply); + + return 0; +} + +static const struct drm_display_mode sharp_ls060_mode = { + .clock = (1080 + 96 + 16 + 64) * (1920 + 4 + 1 + 16) * 60 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 96, + .hsync_end = 1080 + 96 + 16, + .htotal = 1080 + 96 + 16 + 64, + .vdisplay = 1920, + .vsync_start = 1920 + 4, + .vsync_end = 1920 + 4 + 1, + .vtotal = 1920 + 4 + 1 + 16, + .width_mm = 75, + .height_mm = 132, +}; + +static int sharp_ls060_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &sharp_ls060_mode); + if (!mode) + 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 sharp_ls060_panel_funcs = { + .prepare = sharp_ls060_prepare, + .unprepare = sharp_ls060_unprepare, + .get_modes = sharp_ls060_get_modes, +}; + +static int sharp_ls060_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct sharp_ls060 *ctx; + int ret; + + ctx = devm_drm_panel_alloc(dev, struct sharp_ls060, panel, + &sharp_ls060_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ctx->vddi_supply = devm_regulator_get(dev, "vddi"); + if (IS_ERR(ctx->vddi_supply)) + return PTR_ERR(ctx->vddi_supply); + + ctx->vddh_supply = devm_regulator_get(dev, "vddh"); + if (IS_ERR(ctx->vddh_supply)) + return PTR_ERR(ctx->vddh_supply); + + ctx->avdd_supply = devm_regulator_get(dev, "avdd"); + if (IS_ERR(ctx->avdd_supply)) + return PTR_ERR(ctx->avdd_supply); + + ctx->avee_supply = devm_regulator_get(dev, "avee"); + if (IS_ERR(ctx->avee_supply)) + return PTR_ERR(ctx->avee_supply); + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), + "Failed to get reset-gpios\n"); + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_NO_EOT_PACKET | + MIPI_DSI_CLOCK_NON_CONTINUOUS; + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return dev_err_probe(dev, ret, "Failed to get backlight\n"); + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "Failed to attach to DSI host: %d\n", ret); + drm_panel_remove(&ctx->panel); + return ret; + } + + return 0; +} + +static void sharp_ls060_remove(struct mipi_dsi_device *dsi) +{ + struct sharp_ls060 *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id sharp_ls060t1sx01_of_match[] = { + { .compatible = "sharp,ls060t1sx01" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, sharp_ls060t1sx01_of_match); + +static struct mipi_dsi_driver sharp_ls060_driver = { + .probe = sharp_ls060_probe, + .remove = sharp_ls060_remove, + .driver = { + .name = "panel-sharp-ls060t1sx01", + .of_match_table = sharp_ls060t1sx01_of_match, + }, +}; +module_mipi_dsi_driver(sharp_ls060_driver); + +MODULE_AUTHOR("Dmitry Baryshkov <dmitry.baryshkov@linaro.org>"); +MODULE_DESCRIPTION("DRM driver for Sharp LS060T1SX01 1080p video mode dsi panel"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index 9c69e739a524..b26b682826bc 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -21,76 +21,147 @@ * DEALINGS IN THE SOFTWARE. */ -#include <linux/backlight.h> +#include <linux/delay.h> #include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/media-bus-format.h> #include <linux/module.h> +#include <linux/of_device.h> #include <linux/of_platform.h> #include <linux/platform_device.h> +#include <linux/pm_runtime.h> #include <linux/regulator/consumer.h> -#include <drm/drmP.h> +#include <video/display_timing.h> +#include <video/of_display_timing.h> +#include <video/videomode.h> + #include <drm/drm_crtc.h> +#include <drm/drm_device.h> +#include <drm/drm_edid.h> #include <drm/drm_mipi_dsi.h> #include <drm/drm_panel.h> +#include <drm/drm_of.h> -#include <video/display_timing.h> -#include <video/videomode.h> - +/** + * struct panel_desc - Describes a simple panel. + */ struct panel_desc { + /** + * @modes: Pointer to array of fixed modes appropriate for this panel. + * + * If only one mode then this can just be the address of the mode. + * NOTE: cannot be used with "timings" and also if this is specified + * then you cannot override the mode in the device tree. + */ const struct drm_display_mode *modes; + + /** @num_modes: Number of elements in modes array. */ unsigned int num_modes; + + /** + * @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. + */ const struct display_timing *timings; + + /** @num_timings: Number of elements in timings array. */ unsigned int num_timings; + /** @bpc: Bits per color. */ unsigned int bpc; - /** - * @width: width (in millimeters) of the panel's active display area - * @height: height (in millimeters) of the panel's active display area - */ + /** @size: Structure containing the physical size of this panel. */ struct { + /** + * @size.width: Width (in mm) of the active display area. + */ unsigned int width; + + /** + * @size.height: Height (in mm) of the active display area. + */ unsigned int height; } size; - /** - * @prepare: the time (in milliseconds) that it takes for the panel to - * become ready and start receiving video data - * @hpd_absent_delay: Add this to the prepare delay if we know Hot - * Plug Detect isn't used. - * @enable: the time (in milliseconds) that it takes for the panel to - * display the first valid frame after starting to receive - * video data - * @disable: the time (in milliseconds) that it takes for the panel to - * turn the display off (no content is visible) - * @unprepare: the time (in milliseconds) that it takes for the panel - * to power itself down completely - */ + /** @delay: Structure containing various delay values for this panel. */ struct { + /** + * @delay.prepare: Time for the panel to become ready. + * + * The time (in milliseconds) that it takes for the panel to + * become ready and start receiving video data + */ unsigned int prepare; - unsigned int hpd_absent_delay; + + /** + * @delay.enable: Time for the panel to display a valid frame. + * + * The time (in milliseconds) that it takes for the panel to + * display the first valid frame after starting to receive + * video data. + */ unsigned int enable; + + /** + * @delay.disable: Time for the panel to turn the display off. + * + * The time (in milliseconds) that it takes for the panel to + * turn the display off (no content is visible). + */ unsigned int disable; + + /** + * @delay.unprepare: Time to power down completely. + * + * The time (in milliseconds) that it takes for the panel + * to power itself down completely. + * + * This time is used to prevent a future "prepare" from + * starting until at least this many milliseconds has passed. + * If at prepare time less time has passed since unprepare + * finished, the driver waits for the remaining time. + */ unsigned int unprepare; } delay; + /** @bus_format: See MEDIA_BUS_FMT_... defines. */ u32 bus_format; + + /** @bus_flags: See DRM_BUS_FLAG_... defines. */ u32 bus_flags; + + /** @connector_type: LVDS, eDP, DSI, DPI, etc. */ + int connector_type; +}; + +struct panel_desc_dsi { + struct panel_desc desc; + + unsigned long flags; + enum mipi_dsi_pixel_format format; + unsigned int lanes; }; struct panel_simple { struct drm_panel base; - bool prepared; - bool enabled; - bool no_hpd; + + ktime_t unprepared_time; const struct panel_desc *desc; - struct backlight_device *backlight; struct regulator *supply; struct i2c_adapter *ddc; struct gpio_desc *enable_gpio; + + const struct drm_edid *drm_edid; + + struct drm_display_mode override_mode; + + enum drm_panel_orientation orientation; }; static inline struct panel_simple *to_panel_simple(struct drm_panel *panel) @@ -98,24 +169,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; } @@ -131,13 +198,23 @@ 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", - m->hdisplay, m->vdisplay, m->vrefresh); + dev_err(panel->base.dev, "failed to add mode %ux%u@%u\n", + m->hdisplay, m->vdisplay, + drm_mode_vrefresh(m)); continue; } @@ -152,6 +229,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; @@ -163,70 +278,86 @@ static int panel_simple_get_fixed_modes(struct panel_simple *panel) return num; } -static int panel_simple_disable(struct drm_panel *panel) +static void panel_simple_wait(ktime_t start_ktime, unsigned int min_ms) { - struct panel_simple *p = to_panel_simple(panel); + ktime_t now_ktime, min_ktime; - if (!p->enabled) - return 0; + if (!min_ms) + return; - if (p->backlight) { - p->backlight->props.power = FB_BLANK_POWERDOWN; - p->backlight->props.state |= BL_CORE_FBBLANK; - backlight_update_status(p->backlight); - } + min_ktime = ktime_add(start_ktime, ms_to_ktime(min_ms)); + now_ktime = ktime_get_boottime(); + + if (ktime_before(now_ktime, min_ktime)) + msleep(ktime_to_ms(ktime_sub(min_ktime, now_ktime)) + 1); +} + +static int panel_simple_disable(struct drm_panel *panel) +{ + struct panel_simple *p = to_panel_simple(panel); if (p->desc->delay.disable) msleep(p->desc->delay.disable); - p->enabled = false; - return 0; } -static int panel_simple_unprepare(struct drm_panel *panel) +static int panel_simple_suspend(struct device *dev) { - struct panel_simple *p = to_panel_simple(panel); - - if (!p->prepared) - return 0; + struct panel_simple *p = dev_get_drvdata(dev); gpiod_set_value_cansleep(p->enable_gpio, 0); - regulator_disable(p->supply); + p->unprepared_time = ktime_get_boottime(); + + drm_edid_free(p->drm_edid); + p->drm_edid = NULL; + + return 0; +} - if (p->desc->delay.unprepare) - msleep(p->desc->delay.unprepare); +static int panel_simple_unprepare(struct drm_panel *panel) +{ + int ret; - p->prepared = false; + pm_runtime_mark_last_busy(panel->dev); + ret = pm_runtime_put_autosuspend(panel->dev); + if (ret < 0) + return ret; return 0; } -static int panel_simple_prepare(struct drm_panel *panel) +static int panel_simple_resume(struct device *dev) { - struct panel_simple *p = to_panel_simple(panel); - unsigned int delay; + struct panel_simple *p = dev_get_drvdata(dev); int err; - if (p->prepared) - return 0; + panel_simple_wait(p->unprepared_time, p->desc->delay.unprepare); err = regulator_enable(p->supply); if (err < 0) { - dev_err(panel->dev, "failed to enable supply: %d\n", err); + dev_err(dev, "failed to enable supply: %d\n", err); return err; } gpiod_set_value_cansleep(p->enable_gpio, 1); - delay = p->desc->delay.prepare; - if (p->no_hpd) - delay += p->desc->delay.hpd_absent_delay; - if (delay) - msleep(delay); + if (p->desc->delay.prepare) + msleep(p->desc->delay.prepare); - p->prepared = true; + return 0; +} + +static int panel_simple_prepare(struct drm_panel *panel) +{ + int ret; + + ret = pm_runtime_get_sync(panel->dev); + if (ret < 0) { + pm_runtime_put_autosuspend(panel->dev); + return ret; + } return 0; } @@ -235,40 +366,41 @@ static int panel_simple_enable(struct drm_panel *panel) { struct panel_simple *p = to_panel_simple(panel); - if (p->enabled) - return 0; - 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); - if (edid) { - num += drm_add_edid_modes(panel->connector, edid); - kfree(edid); - } + pm_runtime_get_sync(panel->dev); + + if (!p->drm_edid) + p->drm_edid = drm_edid_read_ddc(connector, p->ddc); + + drm_edid_connector_update(connector, p->drm_edid); + + num += drm_edid_connector_add_modes(connector); + + pm_runtime_mark_last_busy(panel->dev); + pm_runtime_put_autosuspend(panel->dev); } /* add hard-coded panel modes */ - num += panel_simple_get_fixed_modes(p); + num += panel_simple_get_non_edid_modes(p, connector); + + /* + * TODO: Remove once all drm drivers call + * drm_connector_set_orientation_from_panel() + */ + drm_connector_set_panel_orientation(connector, p->orientation); return num; } @@ -290,51 +422,228 @@ static int panel_simple_get_timings(struct drm_panel *panel, return p->desc->num_timings; } +static enum drm_panel_orientation panel_simple_get_orientation(struct drm_panel *panel) +{ + struct panel_simple *p = to_panel_simple(panel); + + return p->orientation; +} + static const struct drm_panel_funcs panel_simple_funcs = { .disable = panel_simple_disable, .unprepare = panel_simple_unprepare, .prepare = panel_simple_prepare, .enable = panel_simple_enable, .get_modes = panel_simple_get_modes, + .get_orientation = panel_simple_get_orientation, .get_timings = panel_simple_get_timings, }; -static int panel_simple_probe(struct device *dev, const struct panel_desc *desc) +static struct panel_desc *panel_dpi_probe(struct device *dev) { - struct device_node *backlight, *ddc; + struct display_timing *timing; + const struct device_node *np; + struct panel_desc *desc; + unsigned int bus_flags; + struct videomode vm; + int ret; + + np = dev->of_node; + desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); + if (!desc) + return ERR_PTR(-ENOMEM); + + timing = devm_kzalloc(dev, sizeof(*timing), GFP_KERNEL); + if (!timing) + return ERR_PTR(-ENOMEM); + + ret = of_get_display_timing(np, "panel-timing", timing); + if (ret < 0) { + dev_err(dev, "%pOF: no panel-timing node found for \"panel-dpi\" binding\n", + np); + return ERR_PTR(ret); + } + + desc->timings = timing; + desc->num_timings = 1; + + of_property_read_u32(np, "width-mm", &desc->size.width); + of_property_read_u32(np, "height-mm", &desc->size.height); + + /* Extract bus_flags from display_timing */ + bus_flags = 0; + vm.flags = timing->flags; + drm_bus_flags_from_videomode(&vm, &bus_flags); + desc->bus_flags = bus_flags; + + /* We do not know the connector for the DT node, so guess it */ + desc->connector_type = DRM_MODE_CONNECTOR_DPI; + + return desc; +} + +#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_override_nondefault_lvds_datamapping(struct device *dev, + struct panel_simple *panel) +{ + int ret, bpc; + + ret = drm_of_lvds_get_data_mapping(dev->of_node); + if (ret < 0) { + if (ret == -EINVAL) + dev_warn(dev, "Ignore invalid data-mapping property\n"); + + /* + * Ignore non-existing or malformatted property, fallback to + * default data-mapping, and return 0. + */ + return 0; + } + + switch (ret) { + default: + WARN_ON(1); + fallthrough; + case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG: + fallthrough; + case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA: + bpc = 8; + break; + case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG: + bpc = 6; + } + + if (panel->desc->bpc != bpc || panel->desc->bus_format != ret) { + struct panel_desc *override_desc; + + override_desc = devm_kmemdup(dev, panel->desc, sizeof(*panel->desc), GFP_KERNEL); + if (!override_desc) + return -ENOMEM; + + override_desc->bus_format = ret; + override_desc->bpc = bpc; + panel->desc = override_desc; + } + + return 0; +} + +static const struct panel_desc *panel_simple_get_desc(struct device *dev) +{ + if (IS_ENABLED(CONFIG_DRM_MIPI_DSI) && + dev_is_mipi_dsi(dev)) { + const struct panel_desc_dsi *dsi_desc; + + dsi_desc = of_device_get_match_data(dev); + if (!dsi_desc) + return ERR_PTR(-ENODEV); + + return &dsi_desc->desc; + } + + if (dev_is_platform(dev)) { + const struct panel_desc *desc; + + desc = of_device_get_match_data(dev); + if (!desc) { + /* + * panel-dpi probes without a descriptor and + * panel_dpi_probe() will initialize one for us + * based on the device tree. + */ + if (of_device_is_compatible(dev->of_node, "panel-dpi")) + return panel_dpi_probe(dev); + else + return ERR_PTR(-ENODEV); + } + + return desc; + } + + return ERR_PTR(-ENODEV); +} + +static struct panel_simple *panel_simple_probe(struct device *dev) +{ + const struct panel_desc *desc; struct panel_simple *panel; + struct display_timing dt; + struct device_node *ddc; + int connector_type; + u32 bus_flags; int err; - panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL); - if (!panel) - return -ENOMEM; + desc = panel_simple_get_desc(dev); + if (IS_ERR(desc)) + return ERR_CAST(desc); - panel->enabled = false; - panel->prepared = false; - panel->desc = desc; + panel = devm_drm_panel_alloc(dev, struct panel_simple, base, + &panel_simple_funcs, desc->connector_type); + if (IS_ERR(panel)) + return ERR_CAST(panel); - panel->no_hpd = of_property_read_bool(dev->of_node, "no-hpd"); + panel->desc = desc; panel->supply = devm_regulator_get(dev, "power"); if (IS_ERR(panel->supply)) - return PTR_ERR(panel->supply); + return ERR_CAST(panel->supply); panel->enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_LOW); - if (IS_ERR(panel->enable_gpio)) { - err = PTR_ERR(panel->enable_gpio); - if (err != -EPROBE_DEFER) - dev_err(dev, "failed to request GPIO: %d\n", err); - 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; + if (IS_ERR(panel->enable_gpio)) + return dev_err_cast_probe(dev, panel->enable_gpio, + "failed to request GPIO\n"); + + err = of_drm_get_panel_orientation(dev->of_node, &panel->orientation); + if (err) { + dev_err(dev, "%pOF: failed to get orientation %d\n", dev->of_node, err); + return ERR_PTR(err); } ddc = of_parse_phandle(dev->of_node, "ddc-i2c-bus", 0); @@ -342,59 +651,175 @@ static int panel_simple_probe(struct device *dev, const struct panel_desc *desc) 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 ERR_PTR(-EPROBE_DEFER); } - drm_panel_init(&panel->base); - panel->base.dev = dev; - panel->base.funcs = &panel_simple_funcs; + if (!of_device_is_compatible(dev->of_node, "panel-dpi") && + !of_get_display_timing(dev->of_node, "panel-timing", &dt)) + panel_simple_parse_panel_timing_node(dev, panel, &dt); - err = drm_panel_add(&panel->base); - if (err < 0) + if (desc->connector_type == DRM_MODE_CONNECTOR_LVDS) { + /* Optional data-mapping property for overriding bus format */ + err = panel_simple_override_nondefault_lvds_datamapping(dev, panel); + if (err) + goto free_ddc; + } + + connector_type = desc->connector_type; + /* Catch common mistakes for panels. */ + switch (connector_type) { + case 0: + dev_warn(dev, "Specify missing connector_type\n"); + connector_type = DRM_MODE_CONNECTOR_DPI; + break; + case DRM_MODE_CONNECTOR_LVDS: + WARN_ON(desc->bus_flags & + ~(DRM_BUS_FLAG_DE_LOW | + DRM_BUS_FLAG_DE_HIGH | + DRM_BUS_FLAG_DATA_MSB_TO_LSB | + DRM_BUS_FLAG_DATA_LSB_TO_MSB)); + WARN_ON(desc->bus_format != MEDIA_BUS_FMT_RGB666_1X7X3_SPWG && + desc->bus_format != MEDIA_BUS_FMT_RGB888_1X7X4_SPWG && + desc->bus_format != MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA); + WARN_ON(desc->bus_format == MEDIA_BUS_FMT_RGB666_1X7X3_SPWG && + desc->bpc != 6); + WARN_ON((desc->bus_format == MEDIA_BUS_FMT_RGB888_1X7X4_SPWG || + desc->bus_format == MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA) && + desc->bpc != 8); + break; + case DRM_MODE_CONNECTOR_eDP: + dev_warn(dev, "eDP panels moved to panel-edp\n"); + err = -EINVAL; goto free_ddc; + case DRM_MODE_CONNECTOR_DSI: + if (desc->bpc != 6 && desc->bpc != 8) + dev_warn(dev, "Expected bpc in {6,8} but got: %u\n", desc->bpc); + break; + case DRM_MODE_CONNECTOR_DPI: + bus_flags = DRM_BUS_FLAG_DE_LOW | + DRM_BUS_FLAG_DE_HIGH | + DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE | + DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE | + DRM_BUS_FLAG_DATA_MSB_TO_LSB | + DRM_BUS_FLAG_DATA_LSB_TO_MSB | + DRM_BUS_FLAG_SYNC_SAMPLE_POSEDGE | + DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE; + if (desc->bus_flags & ~bus_flags) + dev_warn(dev, "Unexpected bus_flags(%d)\n", desc->bus_flags & ~bus_flags); + if (!(desc->bus_flags & bus_flags)) + dev_warn(dev, "Specify missing bus_flags\n"); + if (desc->bus_format == 0) + dev_warn(dev, "Specify missing bus_format\n"); + if (desc->bpc != 6 && desc->bpc != 8) + dev_warn(dev, "Expected bpc in {6,8} but got: %u\n", desc->bpc); + break; + default: + dev_warn(dev, "Specify a valid connector_type: %d\n", desc->connector_type); + connector_type = DRM_MODE_CONNECTOR_DPI; + break; + } dev_set_drvdata(dev, panel); - return 0; + /* + * We use runtime PM for prepare / unprepare since those power the panel + * on and off and those can be very slow operations. This is important + * to optimize powering the panel on briefly to read the EDID before + * fully enabling the panel. + */ + pm_runtime_enable(dev); + pm_runtime_set_autosuspend_delay(dev, 1000); + pm_runtime_use_autosuspend(dev); + + err = drm_panel_of_backlight(&panel->base); + if (err) { + dev_err_probe(dev, err, "Could not find backlight\n"); + goto disable_pm_runtime; + } + + drm_panel_add(&panel->base); + return panel; + +disable_pm_runtime: + pm_runtime_dont_use_autosuspend(dev); + pm_runtime_disable(dev); free_ddc: if (panel->ddc) put_device(&panel->ddc->dev); -free_backlight: - if (panel->backlight) - put_device(&panel->backlight->dev); - return err; + return ERR_PTR(err); } -static int panel_simple_remove(struct device *dev) +static void panel_simple_shutdown(struct device *dev) { struct panel_simple *panel = dev_get_drvdata(dev); - drm_panel_remove(&panel->base); + /* + * NOTE: the following two calls don't really belong here. It is the + * responsibility of a correctly written DRM modeset driver to call + * drm_atomic_helper_shutdown() at shutdown time and that should + * cause the panel to be disabled / unprepared if needed. For now, + * however, we'll keep these calls due to the sheer number of + * different DRM modeset drivers used with panel-simple. Once we've + * confirmed that all DRM modeset drivers using this panel properly + * call drm_atomic_helper_shutdown() we can simply delete the two + * calls below. + * + * TO BE EXPLICIT: THE CALLS BELOW SHOULDN'T BE COPIED TO ANY NEW + * PANEL DRIVERS. + * + * FIXME: If we're still haven't figured out if all DRM modeset + * drivers properly call drm_atomic_helper_shutdown() but we _have_ + * managed to make sure that DRM modeset drivers get their shutdown() + * callback before the panel's shutdown() callback (perhaps using + * device link), we could add a WARN_ON here to help move forward. + */ + if (panel->base.enabled) + drm_panel_disable(&panel->base); + if (panel->base.prepared) + drm_panel_unprepare(&panel->base); +} - panel_simple_disable(&panel->base); - panel_simple_unprepare(&panel->base); +static void panel_simple_remove(struct device *dev) +{ + struct panel_simple *panel = dev_get_drvdata(dev); + + drm_panel_remove(&panel->base); + panel_simple_shutdown(dev); + pm_runtime_dont_use_autosuspend(dev); + pm_runtime_disable(dev); if (panel->ddc) put_device(&panel->ddc->dev); - - if (panel->backlight) - put_device(&panel->backlight->dev); - - return 0; } -static void panel_simple_shutdown(struct device *dev) -{ - struct panel_simple *panel = dev_get_drvdata(dev); +static const struct drm_display_mode ampire_am_1280800n3tzqw_t00h_mode = { + .clock = 71100, + .hdisplay = 1280, + .hsync_start = 1280 + 40, + .hsync_end = 1280 + 40 + 80, + .htotal = 1280 + 40 + 80 + 40, + .vdisplay = 800, + .vsync_start = 800 + 3, + .vsync_end = 800 + 3 + 10, + .vtotal = 800 + 3 + 10 + 10, + .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC, +}; - panel_simple_disable(&panel->base); - panel_simple_unprepare(&panel->base); -} +static const struct panel_desc ampire_am_1280800n3tzqw_t00h = { + .modes = &ire_am_1280800n3tzqw_t00h_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 217, + .height = 136, + }, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; static const struct drm_display_mode ampire_am_480272h3tmqw_t01h_mode = { .clock = 9000, @@ -406,7 +831,6 @@ static const struct drm_display_mode ampire_am_480272h3tmqw_t01h_mode = { .vsync_start = 272 + 2, .vsync_end = 272 + 2 + 10, .vtotal = 272 + 2 + 10 + 2, - .vrefresh = 60, .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC, }; @@ -415,8 +839,8 @@ static const struct panel_desc ampire_am_480272h3tmqw_t01h = { .num_modes = 1, .bpc = 8, .size = { - .width = 105, - .height = 67, + .width = 99, + .height = 58, }, .bus_format = MEDIA_BUS_FMT_RGB888_1X24, }; @@ -431,10 +855,39 @@ static const struct drm_display_mode ampire_am800480r3tmqwa1h_mode = { .vsync_start = 480 + 2, .vsync_end = 480 + 2 + 45, .vtotal = 480 + 2 + 45 + 0, - .vrefresh = 60, .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC, }; +static const struct display_timing ampire_am_800480l1tmqw_t00h_timing = { + .pixelclock = { 29930000, 33260000, 36590000 }, + .hactive = { 800, 800, 800 }, + .hfront_porch = { 1, 40, 168 }, + .hback_porch = { 88, 88, 88 }, + .hsync_len = { 1, 128, 128 }, + .vactive = { 480, 480, 480 }, + .vfront_porch = { 1, 35, 37 }, + .vback_porch = { 8, 8, 8 }, + .vsync_len = { 1, 2, 2 }, + .flags = DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW | + DISPLAY_FLAGS_DE_HIGH | DISPLAY_FLAGS_PIXDATA_POSEDGE | + DISPLAY_FLAGS_SYNC_POSEDGE, +}; + +static const struct panel_desc ampire_am_800480l1tmqw_t00h = { + .timings = &ire_am_800480l1tmqw_t00h_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 111, + .height = 67, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | + DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE | + DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE, + .connector_type = DRM_MODE_CONNECTOR_DPI, +}; + static const struct panel_desc ampire_am800480r3tmqwa1h = { .modes = &ire_am800480r3tmqwa1h_mode, .num_modes = 1, @@ -446,6 +899,62 @@ static const struct panel_desc ampire_am800480r3tmqwa1h = { .bus_format = MEDIA_BUS_FMT_RGB666_1X18, }; +static const struct display_timing ampire_am800600p5tmqw_tb8h_timing = { + .pixelclock = { 34500000, 39600000, 50400000 }, + .hactive = { 800, 800, 800 }, + .hfront_porch = { 12, 112, 312 }, + .hback_porch = { 87, 87, 48 }, + .hsync_len = { 1, 1, 40 }, + .vactive = { 600, 600, 600 }, + .vfront_porch = { 1, 21, 61 }, + .vback_porch = { 38, 38, 19 }, + .vsync_len = { 1, 1, 20 }, + .flags = DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW | + DISPLAY_FLAGS_DE_HIGH | DISPLAY_FLAGS_PIXDATA_POSEDGE | + DISPLAY_FLAGS_SYNC_POSEDGE, +}; + +static const struct panel_desc ampire_am800600p5tmqwtb8h = { + .timings = &ire_am800600p5tmqw_tb8h_timing, + .num_timings = 1, + .bpc = 6, + .size = { + .width = 162, + .height = 122, + }, + .bus_format = MEDIA_BUS_FMT_RGB666_1X18, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | + DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE | + DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE, + .connector_type = DRM_MODE_CONNECTOR_DPI, +}; + +static const struct display_timing santek_st0700i5y_rbslw_f_timing = { + .pixelclock = { 26400000, 33300000, 46800000 }, + .hactive = { 800, 800, 800 }, + .hfront_porch = { 16, 210, 354 }, + .hback_porch = { 45, 36, 6 }, + .hsync_len = { 1, 10, 40 }, + .vactive = { 480, 480, 480 }, + .vfront_porch = { 7, 22, 147 }, + .vback_porch = { 22, 13, 3 }, + .vsync_len = { 1, 10, 20 }, + .flags = DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW | + DISPLAY_FLAGS_DE_HIGH | DISPLAY_FLAGS_PIXDATA_POSEDGE +}; + +static const struct panel_desc armadeus_st0700_adapt = { + .timings = &santek_st0700i5y_rbslw_f_timing, + .num_timings = 1, + .bpc = 6, + .size = { + .width = 154, + .height = 86, + }, + .bus_format = MEDIA_BUS_FMT_RGB666_1X18, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE, +}; + static const struct drm_display_mode auo_b101aw03_mode = { .clock = 51450, .hdisplay = 1024, @@ -456,7 +965,6 @@ static const struct drm_display_mode auo_b101aw03_mode = { .vsync_start = 600 + 16, .vsync_end = 600 + 16 + 6, .vtotal = 600 + 16 + 6 + 16, - .vrefresh = 60, }; static const struct panel_desc auo_b101aw03 = { @@ -467,29 +975,9 @@ static const struct panel_desc auo_b101aw03 = { .width = 223, .height = 125, }, -}; - -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 panel_desc auo_b101ean01 = { - .modes = &auo_b101ean01_mode, - .num_modes = 1, - .bpc = 6, - .size = { - .width = 217, - .height = 136, - }, + .bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct drm_display_mode auo_b101xtn01_mode = { @@ -502,7 +990,6 @@ static const struct drm_display_mode auo_b101xtn01_mode = { .vsync_start = 768 + 14, .vsync_end = 768 + 14 + 42, .vtotal = 768 + 14 + 42, - .vrefresh = 60, .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, }; @@ -526,7 +1013,7 @@ static const struct drm_display_mode auo_b116xw03_mode = { .vsync_start = 768 + 10, .vsync_end = 768 + 10 + 12, .vtotal = 768 + 10 + 12 + 6, - .vrefresh = 60, + .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, }; static const struct panel_desc auo_b116xw03 = { @@ -537,57 +1024,15 @@ static const struct panel_desc auo_b116xw03 = { .width = 256, .height = 144, }, -}; - -static const struct drm_display_mode auo_b133xtn01_mode = { - .clock = 69500, - .hdisplay = 1366, - .hsync_start = 1366 + 48, - .hsync_end = 1366 + 48 + 32, - .htotal = 1366 + 48 + 32 + 20, - .vdisplay = 768, - .vsync_start = 768 + 3, - .vsync_end = 768 + 3 + 6, - .vtotal = 768 + 3 + 6 + 13, - .vrefresh = 60, -}; - -static const struct panel_desc auo_b133xtn01 = { - .modes = &auo_b133xtn01_mode, - .num_modes = 1, - .bpc = 6, - .size = { - .width = 293, - .height = 165, - }, -}; - -static const struct drm_display_mode auo_b133htn01_mode = { - .clock = 150660, - .hdisplay = 1920, - .hsync_start = 1920 + 172, - .hsync_end = 1920 + 172 + 80, - .htotal = 1920 + 172 + 80 + 60, - .vdisplay = 1080, - .vsync_start = 1080 + 25, - .vsync_end = 1080 + 25 + 10, - .vtotal = 1080 + 25 + 10 + 10, - .vrefresh = 60, -}; - -static const struct panel_desc auo_b133htn01 = { - .modes = &auo_b133htn01_mode, - .num_modes = 1, - .bpc = 6, - .size = { - .width = 293, - .height = 165, - }, .delay = { - .prepare = 105, - .enable = 20, - .unprepare = 50, + .prepare = 1, + .enable = 200, + .disable = 200, + .unprepare = 500, }, + .bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct display_timing auo_g070vvn01_timings = { @@ -618,28 +1063,29 @@ static const struct panel_desc auo_g070vvn01 = { }, }; -static const struct drm_display_mode auo_g101evn010_mode = { - .clock = 68930, - .hdisplay = 1280, - .hsync_start = 1280 + 82, - .hsync_end = 1280 + 82 + 2, - .htotal = 1280 + 82 + 2 + 84, - .vdisplay = 800, - .vsync_start = 800 + 8, - .vsync_end = 800 + 8 + 2, - .vtotal = 800 + 8 + 2 + 6, - .vrefresh = 60, +static const struct display_timing auo_g101evn010_timing = { + .pixelclock = { 64000000, 68930000, 85000000 }, + .hactive = { 1280, 1280, 1280 }, + .hfront_porch = { 8, 64, 256 }, + .hback_porch = { 8, 64, 256 }, + .hsync_len = { 40, 168, 767 }, + .vactive = { 800, 800, 800 }, + .vfront_porch = { 4, 8, 100 }, + .vback_porch = { 4, 8, 100 }, + .vsync_len = { 8, 16, 223 }, }; static const struct panel_desc auo_g101evn010 = { - .modes = &auo_g101evn010_mode, - .num_modes = 1, + .timings = &auo_g101evn010_timing, + .num_timings = 1, .bpc = 6, .size = { .width = 216, .height = 135, }, - .bus_format = MEDIA_BUS_FMT_RGB666_1X18, + .bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct drm_display_mode auo_g104sn02_mode = { @@ -652,7 +1098,6 @@ static const struct drm_display_mode auo_g104sn02_mode = { .vsync_start = 600 + 10, .vsync_end = 600 + 10 + 35, .vtotal = 600 + 10 + 35 + 2, - .vrefresh = 60, }; static const struct panel_desc auo_g104sn02 = { @@ -663,6 +1108,56 @@ static const struct panel_desc auo_g104sn02 = { .width = 211, .height = 158, }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + +static const struct drm_display_mode auo_g104stn01_mode = { + .clock = 40000, + .hdisplay = 800, + .hsync_start = 800 + 40, + .hsync_end = 800 + 40 + 88, + .htotal = 800 + 40 + 88 + 128, + .vdisplay = 600, + .vsync_start = 600 + 1, + .vsync_end = 600 + 1 + 23, + .vtotal = 600 + 1 + 23 + 4, +}; + +static const struct panel_desc auo_g104stn01 = { + .modes = &auo_g104stn01_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 211, + .height = 158, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + +static const struct display_timing auo_g121ean01_timing = { + .pixelclock = { 60000000, 74400000, 90000000 }, + .hactive = { 1280, 1280, 1280 }, + .hfront_porch = { 20, 50, 100 }, + .hback_porch = { 20, 50, 100 }, + .hsync_len = { 30, 100, 200 }, + .vactive = { 800, 800, 800 }, + .vfront_porch = { 2, 10, 25 }, + .vback_porch = { 2, 10, 25 }, + .vsync_len = { 4, 18, 50 }, +}; + +static const struct panel_desc auo_g121ean01 = { + .timings = &auo_g121ean01_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 261, + .height = 163, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct display_timing auo_g133han01_timings = { @@ -692,14 +1187,70 @@ 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_g156han04_timings = { + .pixelclock = { 137000000, 141000000, 146000000 }, + .hactive = { 1920, 1920, 1920 }, + .hfront_porch = { 60, 60, 60 }, + .hback_porch = { 90, 92, 111 }, + .hsync_len = { 32, 32, 32 }, + .vactive = { 1080, 1080, 1080 }, + .vfront_porch = { 12, 12, 12 }, + .vback_porch = { 24, 36, 56 }, + .vsync_len = { 8, 8, 8 }, +}; + +static const struct panel_desc auo_g156han04 = { + .timings = &auo_g156han04_timings, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 344, + .height = 194, + }, + .delay = { + .prepare = 50, /* T2 */ + .enable = 200, /* T3 */ + .disable = 110, /* T10 */ + .unprepare = 1000, /* T13 */ + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + +static const struct drm_display_mode auo_g156xtn01_mode = { + .clock = 76000, + .hdisplay = 1366, + .hsync_start = 1366 + 33, + .hsync_end = 1366 + 33 + 67, + .htotal = 1560, + .vdisplay = 768, + .vsync_start = 768 + 4, + .vsync_end = 768 + 4 + 4, + .vtotal = 806, +}; + +static const struct panel_desc auo_g156xtn01 = { + .modes = &auo_g156xtn01_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 344, + .height = 194, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .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 }, @@ -721,6 +1272,61 @@ 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_g190ean01_timings = { + .pixelclock = { 90000000, 108000000, 135000000 }, + .hactive = { 1280, 1280, 1280 }, + .hfront_porch = { 126, 184, 1266 }, + .hback_porch = { 84, 122, 844 }, + .hsync_len = { 70, 102, 704 }, + .vactive = { 1024, 1024, 1024 }, + .vfront_porch = { 4, 26, 76 }, + .vback_porch = { 2, 8, 25 }, + .vsync_len = { 2, 8, 25 }, +}; + +static const struct panel_desc auo_g190ean01 = { + .timings = &auo_g190ean01_timings, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 376, + .height = 301, + }, + .delay = { + .prepare = 50, + .enable = 200, + .disable = 110, + .unprepare = 1000, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + +static const struct display_timing auo_p238han01_timings = { + .pixelclock = { 107400000, 142400000, 180000000 }, + .hactive = { 1920, 1920, 1920 }, + .hfront_porch = { 30, 70, 650 }, + .hback_porch = { 30, 70, 650 }, + .hsync_len = { 20, 40, 136 }, + .vactive = { 1080, 1080, 1080 }, + .vfront_porch = { 5, 19, 318 }, + .vback_porch = { 5, 19, 318 }, + .vsync_len = { 4, 12, 120 }, +}; + +static const struct panel_desc auo_p238han01 = { + .timings = &auo_p238han01_timings, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 527, + .height = 296, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct display_timing auo_p320hvn03_timings = { @@ -749,6 +1355,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 = { @@ -761,7 +1368,6 @@ static const struct drm_display_mode auo_t215hvn01_mode = { .vsync_start = 1080 + 4, .vsync_end = 1080 + 4 + 5, .vtotal = 1080 + 4 + 5 + 36, - .vrefresh = 60, }; static const struct panel_desc auo_t215hvn01 = { @@ -775,7 +1381,9 @@ static const struct panel_desc auo_t215hvn01 = { .delay = { .disable = 5, .unprepare = 1000, - } + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct drm_display_mode avic_tm070ddh03_mode = { @@ -788,7 +1396,6 @@ static const struct drm_display_mode avic_tm070ddh03_mode = { .vsync_start = 600 + 17, .vsync_end = 600 + 17 + 1, .vtotal = 600 + 17 + 1 + 17, - .vrefresh = 60, }; static const struct panel_desc avic_tm070ddh03 = { @@ -828,6 +1435,141 @@ static const struct panel_desc bananapi_s070wv20_ct16 = { }, }; +static const struct display_timing boe_av101hdt_a10_timing = { + .pixelclock = { 74210000, 75330000, 76780000, }, + .hactive = { 1280, 1280, 1280, }, + .hfront_porch = { 10, 42, 33, }, + .hback_porch = { 10, 18, 33, }, + .hsync_len = { 30, 10, 30, }, + .vactive = { 720, 720, 720, }, + .vfront_porch = { 200, 183, 200, }, + .vback_porch = { 8, 8, 8, }, + .vsync_len = { 2, 19, 2, }, + .flags = DISPLAY_FLAGS_DE_HIGH | DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW, +}; + +static const struct panel_desc boe_av101hdt_a10 = { + .timings = &boe_av101hdt_a10_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 224, + .height = 126, + }, + .delay = { + .enable = 50, + .disable = 50, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + +static const struct display_timing boe_av123z7m_n17_timing = { + .pixelclock = { 86600000, 88000000, 90800000, }, + .hactive = { 1920, 1920, 1920, }, + .hfront_porch = { 10, 10, 10, }, + .hback_porch = { 10, 10, 10, }, + .hsync_len = { 9, 12, 25, }, + .vactive = { 720, 720, 720, }, + .vfront_porch = { 7, 10, 13, }, + .vback_porch = { 7, 10, 13, }, + .vsync_len = { 7, 11, 14, }, + .flags = DISPLAY_FLAGS_DE_HIGH | DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW, +}; + +static const struct panel_desc boe_av123z7m_n17 = { + .timings = &boe_av123z7m_n17_timing, + .bpc = 8, + .num_timings = 1, + .size = { + .width = 292, + .height = 110, + }, + .delay = { + .prepare = 50, + .disable = 50, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + +static const struct drm_display_mode boe_bp101wx1_100_mode = { + .clock = 78945, + .hdisplay = 1280, + .hsync_start = 1280 + 0, + .hsync_end = 1280 + 0 + 2, + .htotal = 1280 + 62 + 0 + 2, + .vdisplay = 800, + .vsync_start = 800 + 8, + .vsync_end = 800 + 8 + 2, + .vtotal = 800 + 6 + 8 + 2, +}; + +static const struct panel_desc boe_bp082wx1_100 = { + .modes = &boe_bp101wx1_100_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 177, + .height = 110, + }, + .delay = { + .enable = 50, + .disable = 50, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + +static const struct panel_desc boe_bp101wx1_100 = { + .modes = &boe_bp101wx1_100_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 217, + .height = 136, + }, + .delay = { + .enable = 50, + .disable = 50, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + +static const struct display_timing boe_ev121wxm_n10_1850_timing = { + .pixelclock = { 69922000, 71000000, 72293000 }, + .hactive = { 1280, 1280, 1280 }, + .hfront_porch = { 48, 48, 48 }, + .hback_porch = { 80, 80, 80 }, + .hsync_len = { 32, 32, 32 }, + .vactive = { 800, 800, 800 }, + .vfront_porch = { 3, 3, 3 }, + .vback_porch = { 14, 14, 14 }, + .vsync_len = { 6, 6, 6 }, +}; + +static const struct panel_desc boe_ev121wxm_n10_1850 = { + .timings = &boe_ev121wxm_n10_1850_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 261, + .height = 163, + }, + .delay = { + .prepare = 9, + .enable = 300, + .unprepare = 300, + .disable = 560, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + static const struct drm_display_mode boe_hv070wsa_mode = { .clock = 42105, .hdisplay = 1024, @@ -838,58 +1580,45 @@ static const struct drm_display_mode boe_hv070wsa_mode = { .vsync_start = 600 + 10, .vsync_end = 600 + 10 + 10, .vtotal = 600 + 10 + 10 + 10, - .vrefresh = 60, }; static const struct panel_desc boe_hv070wsa = { .modes = &boe_hv070wsa_mode, .num_modes = 1, + .bpc = 8, .size = { .width = 154, .height = 90, }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; -static const struct drm_display_mode boe_nv101wxmn51_modes[] = { - { - .clock = 71900, - .hdisplay = 1280, - .hsync_start = 1280 + 48, - .hsync_end = 1280 + 48 + 32, - .htotal = 1280 + 48 + 32 + 80, - .vdisplay = 800, - .vsync_start = 800 + 3, - .vsync_end = 800 + 3 + 5, - .vtotal = 800 + 3 + 5 + 24, - .vrefresh = 60, - }, - { - .clock = 57500, - .hdisplay = 1280, - .hsync_start = 1280 + 48, - .hsync_end = 1280 + 48 + 32, - .htotal = 1280 + 48 + 32 + 80, - .vdisplay = 800, - .vsync_start = 800 + 3, - .vsync_end = 800 + 3 + 5, - .vtotal = 800 + 3 + 5 + 24, - .vrefresh = 48, - }, +static const struct display_timing cct_cmt430b19n00_timing = { + .pixelclock = { 8000000, 9000000, 12000000 }, + .hactive = { 480, 480, 480 }, + .hfront_porch = { 2, 8, 75 }, + .hback_porch = { 3, 43, 43 }, + .hsync_len = { 2, 4, 75 }, + .vactive = { 272, 272, 272 }, + .vfront_porch = { 2, 8, 37 }, + .vback_porch = { 2, 12, 12 }, + .vsync_len = { 2, 4, 37 }, + .flags = DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW }; -static const struct panel_desc boe_nv101wxmn51 = { - .modes = boe_nv101wxmn51_modes, - .num_modes = ARRAY_SIZE(boe_nv101wxmn51_modes), +static const struct panel_desc cct_cmt430b19n00 = { + .timings = &cct_cmt430b19n00_timing, + .num_timings = 1, .bpc = 8, .size = { - .width = 217, - .height = 136, - }, - .delay = { - .prepare = 210, - .enable = 50, - .unprepare = 160, + .width = 95, + .height = 53, }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE, + .connector_type = DRM_MODE_CONNECTOR_DPI, }; static const struct drm_display_mode cdtech_s043wq26h_ct7_mode = { @@ -902,7 +1631,6 @@ static const struct drm_display_mode cdtech_s043wq26h_ct7_mode = { .vsync_start = 272 + 8, .vsync_end = 272 + 8 + 8, .vtotal = 272 + 8 + 8 + 8, - .vrefresh = 60, .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, }; @@ -914,7 +1642,61 @@ static const struct panel_desc cdtech_s043wq26h_ct7 = { .width = 95, .height = 54, }, - .bus_flags = DRM_BUS_FLAG_PIXDATA_POSEDGE, + .bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE, +}; + +/* S070PWS19HP-FC21 2017/04/22 */ +static const struct drm_display_mode cdtech_s070pws19hp_fc21_mode = { + .clock = 51200, + .hdisplay = 1024, + .hsync_start = 1024 + 160, + .hsync_end = 1024 + 160 + 20, + .htotal = 1024 + 160 + 20 + 140, + .vdisplay = 600, + .vsync_start = 600 + 12, + .vsync_end = 600 + 12 + 3, + .vtotal = 600 + 12 + 3 + 20, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, +}; + +static const struct panel_desc cdtech_s070pws19hp_fc21 = { + .modes = &cdtech_s070pws19hp_fc21_mode, + .num_modes = 1, + .bpc = 6, + .size = { + .width = 154, + .height = 86, + }, + .bus_format = MEDIA_BUS_FMT_RGB666_1X18, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE, + .connector_type = DRM_MODE_CONNECTOR_DPI, +}; + +/* S070SWV29HG-DC44 2017/09/21 */ +static const struct drm_display_mode cdtech_s070swv29hg_dc44_mode = { + .clock = 33300, + .hdisplay = 800, + .hsync_start = 800 + 210, + .hsync_end = 800 + 210 + 2, + .htotal = 800 + 210 + 2 + 44, + .vdisplay = 480, + .vsync_start = 480 + 22, + .vsync_end = 480 + 22 + 2, + .vtotal = 480 + 22 + 2 + 21, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, +}; + +static const struct panel_desc cdtech_s070swv29hg_dc44 = { + .modes = &cdtech_s070swv29hg_dc44_mode, + .num_modes = 1, + .bpc = 6, + .size = { + .width = 154, + .height = 86, + }, + .bus_format = MEDIA_BUS_FMT_RGB666_1X18, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE, + .connector_type = DRM_MODE_CONNECTOR_DPI, }; static const struct drm_display_mode cdtech_s070wv95_ct16_mode = { @@ -927,7 +1709,6 @@ static const struct drm_display_mode cdtech_s070wv95_ct16_mode = { .vsync_start = 480 + 29, .vsync_end = 480 + 29 + 13, .vtotal = 480 + 29 + 13 + 3, - .vrefresh = 60, .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, }; @@ -941,6 +1722,36 @@ static const struct panel_desc cdtech_s070wv95_ct16 = { }, }; +static const struct display_timing chefree_ch101olhlwh_002_timing = { + .pixelclock = { 68900000, 71100000, 73400000 }, + .hactive = { 1280, 1280, 1280 }, + .hfront_porch = { 65, 80, 95 }, + .hback_porch = { 64, 79, 94 }, + .hsync_len = { 1, 1, 1 }, + .vactive = { 800, 800, 800 }, + .vfront_porch = { 7, 11, 14 }, + .vback_porch = { 7, 11, 14 }, + .vsync_len = { 1, 1, 1 }, + .flags = DISPLAY_FLAGS_DE_HIGH, +}; + +static const struct panel_desc chefree_ch101olhlwh_002 = { + .timings = &chefree_ch101olhlwh_002_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 217, + .height = 135, + }, + .delay = { + .enable = 200, + .disable = 200, + }, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + static const struct drm_display_mode chunghwa_claa070wp03xg_mode = { .clock = 66770, .hdisplay = 800, @@ -951,7 +1762,6 @@ static const struct drm_display_mode chunghwa_claa070wp03xg_mode = { .vsync_start = 1280 + 1, .vsync_end = 1280 + 1 + 7, .vtotal = 1280 + 1 + 7 + 15, - .vrefresh = 60, .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, }; @@ -963,6 +1773,9 @@ static const struct panel_desc chunghwa_claa070wp03xg = { .width = 94, .height = 150, }, + .bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct drm_display_mode chunghwa_claa101wa01a_mode = { @@ -975,7 +1788,6 @@ static const struct drm_display_mode chunghwa_claa101wa01a_mode = { .vsync_start = 768 + 4, .vsync_end = 768 + 4 + 4, .vtotal = 768 + 4 + 4 + 4, - .vrefresh = 60, }; static const struct panel_desc chunghwa_claa101wa01a = { @@ -986,6 +1798,9 @@ static const struct panel_desc chunghwa_claa101wa01a = { .width = 220, .height = 120, }, + .bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct drm_display_mode chunghwa_claa101wb01_mode = { @@ -998,7 +1813,6 @@ static const struct drm_display_mode chunghwa_claa101wb01_mode = { .vsync_start = 768 + 16, .vsync_end = 768 + 16 + 8, .vtotal = 768 + 16 + 8 + 16, - .vrefresh = 60, }; static const struct panel_desc chunghwa_claa101wb01 = { @@ -1009,6 +1823,58 @@ static const struct panel_desc chunghwa_claa101wb01 = { .width = 223, .height = 125, }, + .bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + +static const struct display_timing dataimage_fg040346dsswbg04_timing = { + .pixelclock = { 5000000, 9000000, 12000000 }, + .hactive = { 480, 480, 480 }, + .hfront_porch = { 12, 12, 12 }, + .hback_porch = { 12, 12, 12 }, + .hsync_len = { 21, 21, 21 }, + .vactive = { 272, 272, 272 }, + .vfront_porch = { 4, 4, 4 }, + .vback_porch = { 4, 4, 4 }, + .vsync_len = { 8, 8, 8 }, +}; + +static const struct panel_desc dataimage_fg040346dsswbg04 = { + .timings = &dataimage_fg040346dsswbg04_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 95, + .height = 54, + }, + .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 display_timing dataimage_fg1001l0dsswmg01_timing = { + .pixelclock = { 68900000, 71110000, 73400000 }, + .hactive = { 1280, 1280, 1280 }, + .vactive = { 800, 800, 800 }, + .hback_porch = { 100, 100, 100 }, + .hfront_porch = { 100, 100, 100 }, + .vback_porch = { 5, 5, 5 }, + .vfront_porch = { 5, 5, 5 }, + .hsync_len = { 24, 24, 24 }, + .vsync_len = { 3, 3, 3 }, + .flags = DISPLAY_FLAGS_DE_HIGH | DISPLAY_FLAGS_PIXDATA_POSEDGE | + DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW, +}; + +static const struct panel_desc dataimage_fg1001l0dsswmg01 = { + .timings = &dataimage_fg1001l0dsswmg01_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 217, + .height = 136, + }, }; static const struct drm_display_mode dataimage_scf0700c48ggu18_mode = { @@ -1021,7 +1887,6 @@ static const struct drm_display_mode dataimage_scf0700c48ggu18_mode = { .vsync_start = 480 + 10, .vsync_end = 480 + 10 + 2, .vtotal = 480 + 10 + 2 + 33, - .vrefresh = 60, .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, }; @@ -1034,7 +1899,7 @@ static const struct panel_desc dataimage_scf0700c48ggu18 = { .height = 91, }, .bus_format = MEDIA_BUS_FMT_RGB888_1X24, - .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_POSEDGE, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE, }; static const struct display_timing dlc_dlc0700yzg_1_timing = { @@ -1064,6 +1929,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 = { @@ -1094,6 +1960,114 @@ 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 = { + .clock = 6500, + .hdisplay = 320, + .hsync_start = 320 + 20, + .hsync_end = 320 + 20 + 30, + .htotal = 320 + 20 + 68, + .vdisplay = 240, + .vsync_start = 240 + 4, + .vsync_end = 240 + 4 + 4, + .vtotal = 240 + 4 + 4 + 14, + .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, +}; + +static const struct panel_desc edt_et035012dm6 = { + .modes = &edt_et035012dm6_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 70, + .height = 52, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_DE_LOW | DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE, +}; + +static const struct drm_display_mode edt_etm0350g0dh6_mode = { + .clock = 6520, + .hdisplay = 320, + .hsync_start = 320 + 20, + .hsync_end = 320 + 20 + 68, + .htotal = 320 + 20 + 68, + .vdisplay = 240, + .vsync_start = 240 + 4, + .vsync_end = 240 + 4 + 18, + .vtotal = 240 + 4 + 18, + .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, +}; + +static const struct panel_desc edt_etm0350g0dh6 = { + .modes = &edt_etm0350g0dh6_mode, + .num_modes = 1, + .bpc = 6, + .size = { + .width = 70, + .height = 53, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE, + .connector_type = DRM_MODE_CONNECTOR_DPI, +}; + +static const struct drm_display_mode edt_etm043080dh6gp_mode = { + .clock = 10870, + .hdisplay = 480, + .hsync_start = 480 + 8, + .hsync_end = 480 + 8 + 4, + .htotal = 480 + 8 + 4 + 41, + + /* + * IWG22M: Y resolution changed for "dc_linuxfb" module crashing while + * fb_align + */ + + .vdisplay = 288, + .vsync_start = 288 + 2, + .vsync_end = 288 + 2 + 4, + .vtotal = 288 + 2 + 4 + 10, +}; + +static const struct panel_desc edt_etm043080dh6gp = { + .modes = &edt_etm043080dh6gp_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 100, + .height = 65, + }, + .bus_format = MEDIA_BUS_FMT_RGB666_1X18, + .connector_type = DRM_MODE_CONNECTOR_DPI, +}; + +static const struct drm_display_mode edt_etm0430g0dh6_mode = { + .clock = 9000, + .hdisplay = 480, + .hsync_start = 480 + 2, + .hsync_end = 480 + 2 + 41, + .htotal = 480 + 2 + 41 + 2, + .vdisplay = 272, + .vsync_start = 272 + 2, + .vsync_end = 272 + 2 + 10, + .vtotal = 272 + 2 + 10 + 2, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, +}; + +static const struct panel_desc edt_etm0430g0dh6 = { + .modes = &edt_etm0430g0dh6_mode, + .num_modes = 1, + .bpc = 6, + .size = { + .width = 95, + .height = 54, + }, + .bus_format = MEDIA_BUS_FMT_RGB666_1X18, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE, + .connector_type = DRM_MODE_CONNECTOR_DPI, }; static const struct drm_display_mode edt_et057090dhu_mode = { @@ -1106,7 +2080,6 @@ static const struct drm_display_mode edt_et057090dhu_mode = { .vsync_start = 480 + 10, .vsync_end = 480 + 10 + 3, .vtotal = 480 + 10 + 3 + 32, - .vrefresh = 60, .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, }; @@ -1119,7 +2092,8 @@ static const struct panel_desc edt_et057090dhu = { .height = 86, }, .bus_format = MEDIA_BUS_FMT_RGB666_1X18, - .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_NEGEDGE, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE, + .connector_type = DRM_MODE_CONNECTOR_DPI, }; static const struct drm_display_mode edt_etm0700g0dh6_mode = { @@ -1132,7 +2106,6 @@ static const struct drm_display_mode edt_etm0700g0dh6_mode = { .vsync_start = 480 + 10, .vsync_end = 480 + 10 + 2, .vtotal = 480 + 10 + 2 + 33, - .vrefresh = 60, .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, }; @@ -1145,7 +2118,8 @@ static const struct panel_desc edt_etm0700g0dh6 = { .height = 91, }, .bus_format = MEDIA_BUS_FMT_RGB666_1X18, - .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_NEGEDGE, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE, + .connector_type = DRM_MODE_CONNECTOR_DPI, }; static const struct panel_desc edt_etm0700g0bdh6 = { @@ -1157,7 +2131,169 @@ static const struct panel_desc edt_etm0700g0bdh6 = { .height = 91, }, .bus_format = MEDIA_BUS_FMT_RGB666_1X18, - .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_POSEDGE, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE, + .connector_type = DRM_MODE_CONNECTOR_DPI, +}; + +static const struct display_timing edt_etml0700y5dha_timing = { + .pixelclock = { 40800000, 51200000, 67200000 }, + .hactive = { 1024, 1024, 1024 }, + .hfront_porch = { 30, 106, 125 }, + .hback_porch = { 30, 106, 125 }, + .hsync_len = { 30, 108, 126 }, + .vactive = { 600, 600, 600 }, + .vfront_porch = { 3, 12, 67}, + .vback_porch = { 3, 12, 67 }, + .vsync_len = { 4, 11, 66 }, + .flags = DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW | + DISPLAY_FLAGS_DE_HIGH, +}; + +static const struct panel_desc edt_etml0700y5dha = { + .timings = &edt_etml0700y5dha_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 155, + .height = 86, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + +static const struct display_timing edt_etml1010g3dra_timing = { + .pixelclock = { 66300000, 72400000, 78900000 }, + .hactive = { 1280, 1280, 1280 }, + .hfront_porch = { 12, 72, 132 }, + .hback_porch = { 86, 86, 86 }, + .hsync_len = { 2, 2, 2 }, + .vactive = { 800, 800, 800 }, + .vfront_porch = { 1, 15, 49 }, + .vback_porch = { 21, 21, 21 }, + .vsync_len = { 2, 2, 2 }, + .flags = DISPLAY_FLAGS_VSYNC_LOW | DISPLAY_FLAGS_HSYNC_LOW | + DISPLAY_FLAGS_DE_HIGH, +}; + +static const struct panel_desc edt_etml1010g3dra = { + .timings = &edt_etml1010g3dra_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 216, + .height = 135, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + +static const struct drm_display_mode edt_etmv570g2dhu_mode = { + .clock = 25175, + .hdisplay = 640, + .hsync_start = 640, + .hsync_end = 640 + 16, + .htotal = 640 + 16 + 30 + 114, + .vdisplay = 480, + .vsync_start = 480 + 10, + .vsync_end = 480 + 10 + 3, + .vtotal = 480 + 10 + 3 + 35, + .flags = DRM_MODE_FLAG_PVSYNC | DRM_MODE_FLAG_PHSYNC, +}; + +static const struct panel_desc edt_etmv570g2dhu = { + .modes = &edt_etmv570g2dhu_mode, + .num_modes = 1, + .bpc = 6, + .size = { + .width = 115, + .height = 86, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE, + .connector_type = DRM_MODE_CONNECTOR_DPI, +}; + +static const struct display_timing eink_vb3300_kca_timing = { + .pixelclock = { 40000000, 40000000, 40000000 }, + .hactive = { 334, 334, 334 }, + .hfront_porch = { 1, 1, 1 }, + .hback_porch = { 1, 1, 1 }, + .hsync_len = { 1, 1, 1 }, + .vactive = { 1405, 1405, 1405 }, + .vfront_porch = { 1, 1, 1 }, + .vback_porch = { 1, 1, 1 }, + .vsync_len = { 1, 1, 1 }, + .flags = DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW | + DISPLAY_FLAGS_DE_HIGH | DISPLAY_FLAGS_PIXDATA_POSEDGE, +}; + +static const struct panel_desc eink_vb3300_kca = { + .timings = &eink_vb3300_kca_timing, + .num_timings = 1, + .bpc = 6, + .size = { + .width = 157, + .height = 209, + }, + .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 display_timing evervision_vgg644804_timing = { + .pixelclock = { 25175000, 25175000, 25175000 }, + .hactive = { 640, 640, 640 }, + .hfront_porch = { 16, 16, 16 }, + .hback_porch = { 82, 114, 170 }, + .hsync_len = { 5, 30, 30 }, + .vactive = { 480, 480, 480 }, + .vfront_porch = { 10, 10, 10 }, + .vback_porch = { 30, 32, 34 }, + .vsync_len = { 1, 3, 5 }, + .flags = DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW | + DISPLAY_FLAGS_DE_HIGH | DISPLAY_FLAGS_PIXDATA_POSEDGE | + DISPLAY_FLAGS_SYNC_POSEDGE, +}; + +static const struct panel_desc evervision_vgg644804 = { + .timings = &evervision_vgg644804_timing, + .num_timings = 1, + .bpc = 6, + .size = { + .width = 115, + .height = 86, + }, + .bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + +static const struct display_timing evervision_vgg804821_timing = { + .pixelclock = { 27600000, 33300000, 50000000 }, + .hactive = { 800, 800, 800 }, + .hfront_porch = { 40, 66, 70 }, + .hback_porch = { 40, 67, 70 }, + .hsync_len = { 40, 67, 70 }, + .vactive = { 480, 480, 480 }, + .vfront_porch = { 6, 10, 10 }, + .vback_porch = { 7, 11, 11 }, + .vsync_len = { 7, 11, 11 }, + .flags = DISPLAY_FLAGS_HSYNC_HIGH | DISPLAY_FLAGS_VSYNC_HIGH | + DISPLAY_FLAGS_DE_HIGH | DISPLAY_FLAGS_PIXDATA_NEGEDGE | + DISPLAY_FLAGS_SYNC_NEGEDGE, +}; + +static const struct panel_desc evervision_vgg804821 = { + .timings = &evervision_vgg804821_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 108, + .height = 64, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE, }; static const struct drm_display_mode foxlink_fl500wvr00_a0t_mode = { @@ -1170,7 +2306,6 @@ static const struct drm_display_mode foxlink_fl500wvr00_a0t_mode = { .vsync_start = 480 + 37, .vsync_end = 480 + 37 + 2, .vtotal = 480 + 37 + 2 + 8, - .vrefresh = 60, }; static const struct panel_desc foxlink_fl500wvr00_a0t = { @@ -1184,6 +2319,68 @@ static const struct panel_desc foxlink_fl500wvr00_a0t = { .bus_format = MEDIA_BUS_FMT_RGB888_1X24, }; +static const struct drm_display_mode frida_frd350h54004_modes[] = { + { /* 60 Hz */ + .clock = 6000, + .hdisplay = 320, + .hsync_start = 320 + 44, + .hsync_end = 320 + 44 + 16, + .htotal = 320 + 44 + 16 + 20, + .vdisplay = 240, + .vsync_start = 240 + 2, + .vsync_end = 240 + 2 + 6, + .vtotal = 240 + 2 + 6 + 2, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + }, + { /* 50 Hz */ + .clock = 5400, + .hdisplay = 320, + .hsync_start = 320 + 56, + .hsync_end = 320 + 56 + 16, + .htotal = 320 + 56 + 16 + 40, + .vdisplay = 240, + .vsync_start = 240 + 2, + .vsync_end = 240 + 2 + 6, + .vtotal = 240 + 2 + 6 + 2, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + }, +}; + +static const struct panel_desc frida_frd350h54004 = { + .modes = frida_frd350h54004_modes, + .num_modes = ARRAY_SIZE(frida_frd350h54004_modes), + .bpc = 8, + .size = { + .width = 77, + .height = 64, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE, + .connector_type = DRM_MODE_CONNECTOR_DPI, +}; + +static const struct drm_display_mode friendlyarm_hd702e_mode = { + .clock = 67185, + .hdisplay = 800, + .hsync_start = 800 + 20, + .hsync_end = 800 + 20 + 24, + .htotal = 800 + 20 + 24 + 20, + .vdisplay = 1280, + .vsync_start = 1280 + 4, + .vsync_end = 1280 + 4 + 8, + .vtotal = 1280 + 4 + 8 + 4, + .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, +}; + +static const struct panel_desc friendlyarm_hd702e = { + .modes = &friendlyarm_hd702e_mode, + .num_modes = 1, + .size = { + .width = 94, + .height = 151, + }, +}; + static const struct drm_display_mode giantplus_gpg482739qs5_mode = { .clock = 9000, .hdisplay = 480, @@ -1194,7 +2391,6 @@ static const struct drm_display_mode giantplus_gpg482739qs5_mode = { .vsync_start = 272 + 8, .vsync_end = 272 + 8 + 1, .vtotal = 272 + 8 + 1 + 8, - .vrefresh = 60, }; static const struct panel_desc giantplus_gpg482739qs5 = { @@ -1208,6 +2404,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_SAMPLE_POSEDGE, +}; + static const struct display_timing hannstar_hsd070pww1_timing = { .pixelclock = { 64300000, 71100000, 82000000 }, .hactive = { 1280, 1280, 1280 }, @@ -1235,6 +2456,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 = { @@ -1259,6 +2481,32 @@ 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 display_timing hannstar_hsd101pww2_timing = { + .pixelclock = { 64300000, 71100000, 82000000 }, + .hactive = { 1280, 1280, 1280 }, + .hfront_porch = { 1, 1, 10 }, + .hback_porch = { 1, 1, 10 }, + .hsync_len = { 58, 158, 661 }, + .vactive = { 800, 800, 800 }, + .vfront_porch = { 1, 1, 10 }, + .vback_porch = { 1, 1, 10 }, + .vsync_len = { 1, 21, 203 }, + .flags = DISPLAY_FLAGS_DE_HIGH, +}; + +static const struct panel_desc hannstar_hsd101pww2 = { + .timings = &hannstar_hsd101pww2_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 217, + .height = 136, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct drm_display_mode hitachi_tx23d38vm0caa_mode = { @@ -1271,7 +2519,6 @@ static const struct drm_display_mode hitachi_tx23d38vm0caa_mode = { .vsync_start = 480 + 16, .vsync_end = 480 + 16 + 13, .vtotal = 480 + 16 + 13 + 16, - .vrefresh = 60, }; static const struct panel_desc hitachi_tx23d38vm0caa = { @@ -1298,7 +2545,6 @@ static const struct drm_display_mode innolux_at043tn24_mode = { .vsync_start = 272 + 2, .vsync_end = 272 + 2 + 10, .vtotal = 272 + 2 + 10 + 2, - .vrefresh = 60, .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, }; @@ -1311,7 +2557,8 @@ static const struct panel_desc innolux_at043tn24 = { .height = 54, }, .bus_format = MEDIA_BUS_FMT_RGB888_1X24, - .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_POSEDGE, + .connector_type = DRM_MODE_CONNECTOR_DPI, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE, }; static const struct drm_display_mode innolux_at070tn92_mode = { @@ -1324,7 +2571,6 @@ static const struct drm_display_mode innolux_at070tn92_mode = { .vsync_start = 480 + 22, .vsync_end = 480 + 22 + 10, .vtotal = 480 + 22 + 23 + 10, - .vrefresh = 60, }; static const struct panel_desc innolux_at070tn92 = { @@ -1337,6 +2583,38 @@ static const struct panel_desc innolux_at070tn92 = { .bus_format = MEDIA_BUS_FMT_RGB888_1X24, }; +static const struct display_timing innolux_g070ace_l01_timing = { + .pixelclock = { 25200000, 35000000, 35700000 }, + .hactive = { 800, 800, 800 }, + .hfront_porch = { 30, 32, 87 }, + .hback_porch = { 30, 32, 87 }, + .hsync_len = { 1, 1, 1 }, + .vactive = { 480, 480, 480 }, + .vfront_porch = { 3, 3, 3 }, + .vback_porch = { 13, 13, 13 }, + .vsync_len = { 1, 1, 4 }, + .flags = DISPLAY_FLAGS_DE_HIGH, +}; + +static const struct panel_desc innolux_g070ace_l01 = { + .timings = &innolux_g070ace_l01_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 152, + .height = 91, + }, + .delay = { + .prepare = 10, + .enable = 50, + .disable = 50, + .unprepare = 500, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + static const struct display_timing innolux_g070y2_l01_timing = { .pixelclock = { 28000000, 29500000, 32000000 }, .hactive = { 800, 800, 800 }, @@ -1353,7 +2631,7 @@ static const struct display_timing innolux_g070y2_l01_timing = { static const struct panel_desc innolux_g070y2_l01 = { .timings = &innolux_g070y2_l01_timing, .num_timings = 1, - .bpc = 6, + .bpc = 8, .size = { .width = 152, .height = 91, @@ -1365,18 +2643,77 @@ static const struct panel_desc innolux_g070y2_l01 = { .unprepare = 800, }, .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + +static const struct display_timing innolux_g070ace_lh3_timing = { + .pixelclock = { 25200000, 25400000, 35700000 }, + .hactive = { 800, 800, 800 }, + .hfront_porch = { 30, 32, 87 }, + .hback_porch = { 29, 31, 86 }, + .hsync_len = { 1, 1, 1 }, + .vactive = { 480, 480, 480 }, + .vfront_porch = { 4, 5, 65 }, + .vback_porch = { 3, 4, 65 }, + .vsync_len = { 1, 1, 1 }, + .flags = DISPLAY_FLAGS_DE_HIGH, +}; + +static const struct panel_desc innolux_g070ace_lh3 = { + .timings = &innolux_g070ace_lh3_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 152, + .height = 91, + }, + .delay = { + .prepare = 10, + .enable = 450, + .disable = 200, + .unprepare = 510, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + +static const struct drm_display_mode innolux_g070y2_t02_mode = { + .clock = 33333, + .hdisplay = 800, + .hsync_start = 800 + 210, + .hsync_end = 800 + 210 + 20, + .htotal = 800 + 210 + 20 + 46, + .vdisplay = 480, + .vsync_start = 480 + 22, + .vsync_end = 480 + 22 + 10, + .vtotal = 480 + 22 + 23 + 10, +}; + +static const struct panel_desc innolux_g070y2_t02 = { + .modes = &innolux_g070y2_t02_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 152, + .height = 92, + }, + .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 display_timing innolux_g101ice_l01_timing = { .pixelclock = { 60400000, 71100000, 74700000 }, .hactive = { 1280, 1280, 1280 }, - .hfront_porch = { 41, 80, 100 }, - .hback_porch = { 40, 79, 99 }, - .hsync_len = { 1, 1, 1 }, + .hfront_porch = { 30, 60, 70 }, + .hback_porch = { 30, 60, 70 }, + .hsync_len = { 22, 40, 60 }, .vactive = { 800, 800, 800 }, - .vfront_porch = { 5, 11, 14 }, - .vback_porch = { 4, 11, 14 }, - .vsync_len = { 1, 1, 1 }, + .vfront_porch = { 3, 8, 14 }, + .vback_porch = { 3, 8, 14 }, + .vsync_len = { 4, 7, 12 }, .flags = DISPLAY_FLAGS_DE_HIGH, }; @@ -1393,6 +2730,8 @@ static const struct panel_desc innolux_g101ice_l01 = { .disable = 200, }, .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct display_timing innolux_g121i1_l01_timing = { @@ -1419,26 +2758,26 @@ static const struct panel_desc innolux_g121i1_l01 = { .enable = 200, .disable = 20, }, - .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; -static const struct drm_display_mode innolux_g121x1_l03_mode = { - .clock = 65000, - .hdisplay = 1024, - .hsync_start = 1024 + 0, - .hsync_end = 1024 + 1, - .htotal = 1024 + 0 + 1 + 320, - .vdisplay = 768, - .vsync_start = 768 + 38, - .vsync_end = 768 + 38 + 1, - .vtotal = 768 + 38 + 1 + 0, - .vrefresh = 60, - .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, +static const struct display_timing innolux_g121x1_l03_timings = { + .pixelclock = { 57500000, 64900000, 74400000 }, + .hactive = { 1024, 1024, 1024 }, + .hfront_porch = { 90, 140, 190 }, + .hback_porch = { 90, 140, 190 }, + .hsync_len = { 36, 40, 60 }, + .vactive = { 768, 768, 768 }, + .vfront_porch = { 2, 15, 30 }, + .vback_porch = { 2, 15, 30 }, + .vsync_len = { 2, 8, 20 }, + .flags = DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW, }; static const struct panel_desc innolux_g121x1_l03 = { - .modes = &innolux_g121x1_l03_mode, - .num_modes = 1, + .timings = &innolux_g121x1_l03_timings, + .num_timings = 1, .bpc = 6, .size = { .width = 246, @@ -1449,30 +2788,58 @@ static const struct panel_desc innolux_g121x1_l03 = { .unprepare = 200, .disable = 400, }, + .bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; -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, +static const struct panel_desc innolux_g121xce_l01 = { + .timings = &innolux_g121x1_l03_timings, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 246, + .height = 185, + }, + .delay = { + .enable = 200, + .unprepare = 200, + .disable = 400, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; -static const struct panel_desc innolux_n116bge = { - .modes = &innolux_n116bge_mode, - .num_modes = 1, - .bpc = 6, +static const struct display_timing innolux_g156hce_l01_timings = { + .pixelclock = { 120000000, 141860000, 150000000 }, + .hactive = { 1920, 1920, 1920 }, + .hfront_porch = { 80, 90, 100 }, + .hback_porch = { 80, 90, 100 }, + .hsync_len = { 20, 30, 30 }, + .vactive = { 1080, 1080, 1080 }, + .vfront_porch = { 3, 10, 20 }, + .vback_porch = { 3, 10, 20 }, + .vsync_len = { 4, 10, 10 }, +}; + +static const struct panel_desc innolux_g156hce_l01 = { + .timings = &innolux_g156hce_l01_timings, + .num_timings = 1, + .bpc = 8, .size = { - .width = 256, - .height = 144, + .width = 344, + .height = 194, + }, + .delay = { + .prepare = 1, /* T1+T2 */ + .enable = 450, /* T5 */ + .disable = 200, /* T6 */ + .unprepare = 10, /* T3+T7 */ }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct drm_display_mode innolux_n156bge_l21_mode = { @@ -1485,7 +2852,6 @@ static const struct drm_display_mode innolux_n156bge_l21_mode = { .vsync_start = 768 + 2, .vsync_end = 768 + 2 + 6, .vtotal = 768 + 2 + 6 + 12, - .vrefresh = 60, }; static const struct panel_desc innolux_n156bge_l21 = { @@ -1496,34 +2862,9 @@ static const struct panel_desc innolux_n156bge_l21 = { .width = 344, .height = 193, }, -}; - -static const struct drm_display_mode innolux_p120zdg_bf1_mode = { - .clock = 206016, - .hdisplay = 2160, - .hsync_start = 2160 + 48, - .hsync_end = 2160 + 48 + 32, - .htotal = 2160 + 48 + 32 + 80, - .vdisplay = 1440, - .vsync_start = 1440 + 3, - .vsync_end = 1440 + 3 + 10, - .vtotal = 1440 + 3 + 10 + 27, - .vrefresh = 60, - .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC, -}; - -static const struct panel_desc innolux_p120zdg_bf1 = { - .modes = &innolux_p120zdg_bf1_mode, - .num_modes = 1, - .bpc = 8, - .size = { - .width = 254, - .height = 169, - }, - .delay = { - .hpd_absent_delay = 200, - .unprepare = 500, - }, + .bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct drm_display_mode innolux_zj070na_01p_mode = { @@ -1536,7 +2877,6 @@ static const struct drm_display_mode innolux_zj070na_01p_mode = { .vsync_start = 600 + 16, .vsync_end = 600 + 16 + 4, .vtotal = 600 + 16 + 4 + 16, - .vrefresh = 60, }; static const struct panel_desc innolux_zj070na_01p = { @@ -1549,6 +2889,93 @@ static const struct panel_desc innolux_zj070na_01p = { }, }; +static const struct display_timing jutouch_jt101tm023_timing = { + .pixelclock = { 66300000, 72400000, 78900000 }, + .hactive = { 1280, 1280, 1280 }, + .hfront_porch = { 12, 72, 132 }, + .hback_porch = { 88, 88, 88 }, + .hsync_len = { 10, 10, 48 }, + .vactive = { 800, 800, 800 }, + .vfront_porch = { 1, 15, 49 }, + .vback_porch = { 23, 23, 23 }, + .vsync_len = { 5, 6, 13 }, + .flags = DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW | + DISPLAY_FLAGS_DE_HIGH, +}; + +static const struct panel_desc jutouch_jt101tm023 = { + .timings = &jutouch_jt101tm023_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 217, + .height = 136, + }, + .delay = { + .enable = 50, + .disable = 50, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + + +static const struct display_timing koe_tx14d24vm1bpa_timing = { + .pixelclock = { 5580000, 5850000, 6200000 }, + .hactive = { 320, 320, 320 }, + .hfront_porch = { 30, 30, 30 }, + .hback_porch = { 30, 30, 30 }, + .hsync_len = { 1, 5, 17 }, + .vactive = { 240, 240, 240 }, + .vfront_porch = { 6, 6, 6 }, + .vback_porch = { 5, 5, 5 }, + .vsync_len = { 1, 2, 11 }, + .flags = DISPLAY_FLAGS_DE_HIGH, +}; + +static const struct panel_desc koe_tx14d24vm1bpa = { + .timings = &koe_tx14d24vm1bpa_timing, + .num_timings = 1, + .bpc = 6, + .size = { + .width = 115, + .height = 86, + }, +}; + +static const struct display_timing koe_tx26d202vm0bwa_timing = { + .pixelclock = { 151820000, 156720000, 159780000 }, + .hactive = { 1920, 1920, 1920 }, + .hfront_porch = { 105, 130, 142 }, + .hback_porch = { 45, 70, 82 }, + .hsync_len = { 30, 30, 30 }, + .vactive = { 1200, 1200, 1200}, + .vfront_porch = { 3, 5, 10 }, + .vback_porch = { 2, 5, 10 }, + .vsync_len = { 5, 5, 5 }, + .flags = DISPLAY_FLAGS_DE_HIGH, +}; + +static const struct panel_desc koe_tx26d202vm0bwa = { + .timings = &koe_tx26d202vm0bwa_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 217, + .height = 136, + }, + .delay = { + .prepare = 1000, + .enable = 1000, + .unprepare = 1000, + .disable = 1000, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + static const struct display_timing koe_tx31d200vm0baa_timing = { .pixelclock = { 39600000, 43200000, 48000000 }, .hactive = { 1280, 1280, 1280 }, @@ -1571,6 +2998,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 = { @@ -1595,121 +3023,297 @@ 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 lg_lb070wv8_mode = { - .clock = 33246, - .hdisplay = 800, - .hsync_start = 800 + 88, - .hsync_end = 800 + 88 + 80, - .htotal = 800 + 88 + 80 + 88, - .vdisplay = 480, - .vsync_start = 480 + 10, - .vsync_end = 480 + 10 + 25, - .vtotal = 480 + 10 + 25 + 10, - .vrefresh = 60, +static const struct drm_display_mode lemaker_bl035_rgb_002_mode = { + .clock = 7000, + .hdisplay = 320, + .hsync_start = 320 + 20, + .hsync_end = 320 + 20 + 30, + .htotal = 320 + 20 + 30 + 38, + .vdisplay = 240, + .vsync_start = 240 + 4, + .vsync_end = 240 + 4 + 3, + .vtotal = 240 + 4 + 3 + 15, }; -static const struct panel_desc lg_lb070wv8 = { - .modes = &lg_lb070wv8_mode, +static const struct panel_desc lemaker_bl035_rgb_002 = { + .modes = &lemaker_bl035_rgb_002_mode, .num_modes = 1, - .bpc = 16, + .size = { + .width = 70, + .height = 52, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_DE_LOW, +}; + +static const struct display_timing lg_lb070wv8_timing = { + .pixelclock = { 31950000, 33260000, 34600000 }, + .hactive = { 800, 800, 800 }, + .hfront_porch = { 88, 88, 88 }, + .hback_porch = { 88, 88, 88 }, + .hsync_len = { 80, 80, 80 }, + .vactive = { 480, 480, 480 }, + .vfront_porch = { 10, 10, 10 }, + .vback_porch = { 10, 10, 10 }, + .vsync_len = { 25, 25, 25 }, +}; + +static const struct panel_desc lg_lb070wv8 = { + .timings = &lg_lb070wv8_timing, + .num_timings = 1, + .bpc = 8, .size = { .width = 151, .height = 91, }, .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + +static const struct drm_display_mode lincolntech_lcd185_101ct_mode = { + .clock = 155127, + .hdisplay = 1920, + .hsync_start = 1920 + 128, + .hsync_end = 1920 + 128 + 20, + .htotal = 1920 + 128 + 20 + 12, + .vdisplay = 1200, + .vsync_start = 1200 + 19, + .vsync_end = 1200 + 19 + 4, + .vtotal = 1200 + 19 + 4 + 20, +}; + +static const struct panel_desc lincolntech_lcd185_101ct = { + .modes = &lincolntech_lcd185_101ct_mode, + .bpc = 8, + .num_modes = 1, + .size = { + .width = 217, + .height = 136, + }, + .delay = { + .prepare = 50, + .disable = 50, + }, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; -static const struct drm_display_mode lg_lp079qx1_sp0v_mode = { - .clock = 200000, - .hdisplay = 1536, - .hsync_start = 1536 + 12, - .hsync_end = 1536 + 12 + 16, - .htotal = 1536 + 12 + 16 + 48, - .vdisplay = 2048, - .vsync_start = 2048 + 8, - .vsync_end = 2048 + 8 + 4, - .vtotal = 2048 + 8 + 4 + 8, - .vrefresh = 60, +static const struct display_timing logictechno_lt161010_2nh_timing = { + .pixelclock = { 26400000, 33300000, 46800000 }, + .hactive = { 800, 800, 800 }, + .hfront_porch = { 16, 210, 354 }, + .hback_porch = { 46, 46, 46 }, + .hsync_len = { 1, 20, 40 }, + .vactive = { 480, 480, 480 }, + .vfront_porch = { 7, 22, 147 }, + .vback_porch = { 23, 23, 23 }, + .vsync_len = { 1, 10, 20 }, + .flags = DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW | + DISPLAY_FLAGS_DE_HIGH | DISPLAY_FLAGS_PIXDATA_POSEDGE | + DISPLAY_FLAGS_SYNC_POSEDGE, +}; + +static const struct panel_desc logictechno_lt161010_2nh = { + .timings = &logictechno_lt161010_2nh_timing, + .num_timings = 1, + .bpc = 6, + .size = { + .width = 154, + .height = 86, + }, + .bus_format = MEDIA_BUS_FMT_RGB666_1X18, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | + DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE | + DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE, + .connector_type = DRM_MODE_CONNECTOR_DPI, +}; + +static const struct display_timing logictechno_lt170410_2whc_timing = { + .pixelclock = { 68900000, 71100000, 73400000 }, + .hactive = { 1280, 1280, 1280 }, + .hfront_porch = { 23, 60, 71 }, + .hback_porch = { 23, 60, 71 }, + .hsync_len = { 15, 40, 47 }, + .vactive = { 800, 800, 800 }, + .vfront_porch = { 5, 7, 10 }, + .vback_porch = { 5, 7, 10 }, + .vsync_len = { 6, 9, 12 }, + .flags = DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW | + DISPLAY_FLAGS_DE_HIGH | DISPLAY_FLAGS_PIXDATA_POSEDGE | + DISPLAY_FLAGS_SYNC_POSEDGE, +}; + +static const struct panel_desc logictechno_lt170410_2whc = { + .timings = &logictechno_lt170410_2whc_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 217, + .height = 136, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + +static const struct drm_display_mode logictechno_lttd800480070_l2rt_mode = { + .clock = 33000, + .hdisplay = 800, + .hsync_start = 800 + 112, + .hsync_end = 800 + 112 + 3, + .htotal = 800 + 112 + 3 + 85, + .vdisplay = 480, + .vsync_start = 480 + 38, + .vsync_end = 480 + 38 + 3, + .vtotal = 480 + 38 + 3 + 29, .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, }; -static const struct panel_desc lg_lp079qx1_sp0v = { - .modes = &lg_lp079qx1_sp0v_mode, +static const struct panel_desc logictechno_lttd800480070_l2rt = { + .modes = &logictechno_lttd800480070_l2rt_mode, .num_modes = 1, + .bpc = 8, .size = { - .width = 129, - .height = 171, + .width = 154, + .height = 86, }, + .delay = { + .prepare = 45, + .enable = 100, + .disable = 100, + .unprepare = 45 + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE, + .connector_type = DRM_MODE_CONNECTOR_DPI, }; -static const struct drm_display_mode lg_lp097qx1_spa1_mode = { - .clock = 205210, - .hdisplay = 2048, - .hsync_start = 2048 + 150, - .hsync_end = 2048 + 150 + 5, - .htotal = 2048 + 150 + 5 + 5, - .vdisplay = 1536, - .vsync_start = 1536 + 3, - .vsync_end = 1536 + 3 + 1, - .vtotal = 1536 + 3 + 1 + 9, - .vrefresh = 60, +static const struct drm_display_mode logictechno_lttd800480070_l6wh_rt_mode = { + .clock = 33000, + .hdisplay = 800, + .hsync_start = 800 + 154, + .hsync_end = 800 + 154 + 3, + .htotal = 800 + 154 + 3 + 43, + .vdisplay = 480, + .vsync_start = 480 + 47, + .vsync_end = 480 + 47 + 3, + .vtotal = 480 + 47 + 3 + 20, + .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, }; -static const struct panel_desc lg_lp097qx1_spa1 = { - .modes = &lg_lp097qx1_spa1_mode, +static const struct panel_desc logictechno_lttd800480070_l6wh_rt = { + .modes = &logictechno_lttd800480070_l6wh_rt_mode, .num_modes = 1, + .bpc = 8, .size = { - .width = 208, - .height = 147, + .width = 154, + .height = 86, + }, + .delay = { + .prepare = 45, + .enable = 100, + .disable = 100, + .unprepare = 45 }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE, + .connector_type = DRM_MODE_CONNECTOR_DPI, }; -static const struct drm_display_mode lg_lp120up1_mode = { - .clock = 162300, - .hdisplay = 1920, - .hsync_start = 1920 + 40, - .hsync_end = 1920 + 40 + 40, - .htotal = 1920 + 40 + 40+ 80, - .vdisplay = 1280, - .vsync_start = 1280 + 4, - .vsync_end = 1280 + 4 + 4, - .vtotal = 1280 + 4 + 4 + 12, - .vrefresh = 60, +static const struct drm_display_mode logicpd_type_28_mode = { + .clock = 9107, + .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, + .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC, }; -static const struct panel_desc lg_lp120up1 = { - .modes = &lg_lp120up1_mode, +static const struct panel_desc logicpd_type_28 = { + .modes = &logicpd_type_28_mode, .num_modes = 1, .bpc = 8, .size = { - .width = 267, - .height = 183, + .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, + .connector_type = DRM_MODE_CONNECTOR_DPI, }; -static const struct drm_display_mode lg_lp129qe_mode = { - .clock = 285250, - .hdisplay = 2560, - .hsync_start = 2560 + 48, - .hsync_end = 2560 + 48 + 32, - .htotal = 2560 + 48 + 32 + 80, - .vdisplay = 1700, - .vsync_start = 1700 + 3, - .vsync_end = 1700 + 3 + 10, - .vtotal = 1700 + 3 + 10 + 36, - .vrefresh = 60, +static const struct drm_display_mode microtips_mf_101hiebcaf0_c_mode = { + .clock = 150275, + .hdisplay = 1920, + .hsync_start = 1920 + 32, + .hsync_end = 1920 + 32 + 52, + .htotal = 1920 + 32 + 52 + 24, + .vdisplay = 1200, + .vsync_start = 1200 + 24, + .vsync_end = 1200 + 24 + 8, + .vtotal = 1200 + 24 + 8 + 3, }; -static const struct panel_desc lg_lp129qe = { - .modes = &lg_lp129qe_mode, +static const struct panel_desc microtips_mf_101hiebcaf0_c = { + .modes = µtips_mf_101hiebcaf0_c_mode, + .bpc = 8, .num_modes = 1, + .size = { + .width = 217, + .height = 136, + }, + .delay = { + .prepare = 50, + .disable = 50, + }, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + +static const struct drm_display_mode microtips_mf_103hieb0ga0_mode = { + .clock = 93301, + .hdisplay = 1920, + .hsync_start = 1920 + 72, + .hsync_end = 1920 + 72 + 72, + .htotal = 1920 + 72 + 72 + 72, + .vdisplay = 720, + .vsync_start = 720 + 3, + .vsync_end = 720 + 3 + 3, + .vtotal = 720 + 3 + 3 + 2, +}; + +static const struct panel_desc microtips_mf_103hieb0ga0 = { + .modes = µtips_mf_103hieb0ga0_mode, .bpc = 8, + .num_modes = 1, .size = { - .width = 272, - .height = 181, + .width = 244, + .height = 92, }, + .delay = { + .prepare = 50, + .disable = 50, + }, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct drm_display_mode mitsubishi_aa070mc01_mode = { @@ -1722,7 +3326,6 @@ static const struct drm_display_mode mitsubishi_aa070mc01_mode = { .vsync_start = 480 + 0, .vsync_end = 480 + 48 + 1, .vtotal = 480 + 48 + 1 + 0, - .vrefresh = 60, .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, }; @@ -1741,9 +3344,181 @@ 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, }; +static const struct drm_display_mode mitsubishi_aa084xe01_mode = { + .clock = 56234, + .hdisplay = 1024, + .hsync_start = 1024 + 24, + .hsync_end = 1024 + 24 + 63, + .htotal = 1024 + 24 + 63 + 1, + .vdisplay = 768, + .vsync_start = 768 + 3, + .vsync_end = 768 + 3 + 6, + .vtotal = 768 + 3 + 6 + 1, + .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC, +}; + +static const struct panel_desc mitsubishi_aa084xe01 = { + .modes = &mitsubishi_aa084xe01_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 1024, + .height = 768, + }, + .bus_format = MEDIA_BUS_FMT_RGB565_1X16, + .connector_type = DRM_MODE_CONNECTOR_DPI, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE, +}; + +static const struct display_timing multi_inno_mi0700a2t_30_timing = { + .pixelclock = { 26400000, 33000000, 46800000 }, + .hactive = { 800, 800, 800 }, + .hfront_porch = { 16, 204, 354 }, + .hback_porch = { 46, 46, 46 }, + .hsync_len = { 1, 6, 40 }, + .vactive = { 480, 480, 480 }, + .vfront_porch = { 7, 22, 147 }, + .vback_porch = { 23, 23, 23 }, + .vsync_len = { 1, 3, 20 }, + .flags = DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW | + DISPLAY_FLAGS_DE_HIGH, +}; + +static const struct panel_desc multi_inno_mi0700a2t_30 = { + .timings = &multi_inno_mi0700a2t_30_timing, + .num_timings = 1, + .bpc = 6, + .size = { + .width = 153, + .height = 92, + }, + .bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + +static const struct display_timing multi_inno_mi0700s4t_6_timing = { + .pixelclock = { 29000000, 33000000, 38000000 }, + .hactive = { 800, 800, 800 }, + .hfront_porch = { 180, 210, 240 }, + .hback_porch = { 16, 16, 16 }, + .hsync_len = { 30, 30, 30 }, + .vactive = { 480, 480, 480 }, + .vfront_porch = { 12, 22, 32 }, + .vback_porch = { 10, 10, 10 }, + .vsync_len = { 13, 13, 13 }, + .flags = DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW | + DISPLAY_FLAGS_DE_HIGH | DISPLAY_FLAGS_PIXDATA_POSEDGE | + DISPLAY_FLAGS_SYNC_POSEDGE, +}; + +static const struct panel_desc multi_inno_mi0700s4t_6 = { + .timings = &multi_inno_mi0700s4t_6_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 154, + .height = 86, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | + DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE | + DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE, + .connector_type = DRM_MODE_CONNECTOR_DPI, +}; + +static const struct display_timing multi_inno_mi0800ft_9_timing = { + .pixelclock = { 32000000, 40000000, 50000000 }, + .hactive = { 800, 800, 800 }, + .hfront_porch = { 16, 210, 354 }, + .hback_porch = { 6, 26, 45 }, + .hsync_len = { 1, 20, 40 }, + .vactive = { 600, 600, 600 }, + .vfront_porch = { 1, 12, 77 }, + .vback_porch = { 3, 13, 22 }, + .vsync_len = { 1, 10, 20 }, + .flags = DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW | + DISPLAY_FLAGS_DE_HIGH | DISPLAY_FLAGS_PIXDATA_POSEDGE | + DISPLAY_FLAGS_SYNC_POSEDGE, +}; + +static const struct panel_desc multi_inno_mi0800ft_9 = { + .timings = &multi_inno_mi0800ft_9_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 162, + .height = 122, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | + DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE | + DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE, + .connector_type = DRM_MODE_CONNECTOR_DPI, +}; + +static const struct display_timing multi_inno_mi1010ait_1cp_timing = { + .pixelclock = { 68900000, 70000000, 73400000 }, + .hactive = { 1280, 1280, 1280 }, + .hfront_porch = { 30, 60, 71 }, + .hback_porch = { 30, 60, 71 }, + .hsync_len = { 10, 10, 48 }, + .vactive = { 800, 800, 800 }, + .vfront_porch = { 5, 10, 10 }, + .vback_porch = { 5, 10, 10 }, + .vsync_len = { 5, 6, 13 }, + .flags = DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW | + DISPLAY_FLAGS_DE_HIGH, +}; + +static const struct panel_desc multi_inno_mi1010ait_1cp = { + .timings = &multi_inno_mi1010ait_1cp_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 217, + .height = 136, + }, + .delay = { + .enable = 50, + .disable = 50, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + +static const struct display_timing multi_inno_mi1010z1t_1cp11_timing = { + .pixelclock = { 40800000, 51200000, 67200000 }, + .hactive = { 1024, 1024, 1024 }, + .hfront_porch = { 30, 110, 130 }, + .hback_porch = { 30, 110, 130 }, + .hsync_len = { 30, 100, 116 }, + .vactive = { 600, 600, 600 }, + .vfront_porch = { 4, 13, 80 }, + .vback_porch = { 4, 13, 80 }, + .vsync_len = { 2, 9, 40 }, + .flags = DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW | + DISPLAY_FLAGS_DE_HIGH, +}; + +static const struct panel_desc multi_inno_mi1010z1t_1cp11 = { + .timings = &multi_inno_mi1010z1t_1cp11_timing, + .num_timings = 1, + .bpc = 6, + .size = { + .width = 260, + .height = 162, + }, + .bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + static const struct display_timing nec_nl12880bc20_05_timing = { .pixelclock = { 67000000, 71000000, 75000000 }, .hactive = { 1280, 1280, 1280 }, @@ -1769,6 +3544,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 = { @@ -1781,7 +3557,6 @@ static const struct drm_display_mode nec_nl4827hc19_05b_mode = { .vsync_start = 272 + 2, .vsync_end = 272 + 2 + 4, .vtotal = 272 + 2 + 4 + 2, - .vrefresh = 74, .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, }; @@ -1794,7 +3569,7 @@ static const struct panel_desc nec_nl4827hc19_05b = { .height = 54, }, .bus_format = MEDIA_BUS_FMT_RGB888_1X24, - .bus_flags = DRM_BUS_FLAG_PIXDATA_POSEDGE, + .bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE, }; static const struct drm_display_mode netron_dy_e231732_mode = { @@ -1807,7 +3582,6 @@ static const struct drm_display_mode netron_dy_e231732_mode = { .vsync_start = 600 + 127, .vsync_end = 600 + 127 + 20, .vtotal = 600 + 127 + 20 + 3, - .vrefresh = 60, }; static const struct panel_desc netron_dy_e231732 = { @@ -1830,7 +3604,6 @@ static const struct drm_display_mode newhaven_nhd_43_480272ef_atxl_mode = { .vsync_start = 272 + 2, .vsync_end = 272 + 2 + 10, .vtotal = 272 + 2 + 10 + 2, - .vrefresh = 60, .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, }; @@ -1843,8 +3616,33 @@ static const struct panel_desc newhaven_nhd_43_480272ef_atxl = { .height = 54, }, .bus_format = MEDIA_BUS_FMT_RGB888_1X24, - .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_POSEDGE | - DRM_BUS_FLAG_SYNC_POSEDGE, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE | + DRM_BUS_FLAG_SYNC_DRIVE_POSEDGE, + .connector_type = DRM_MODE_CONNECTOR_DPI, +}; + +static const struct drm_display_mode nlt_nl13676bc25_03f_mode = { + .clock = 75400, + .hdisplay = 1366, + .hsync_start = 1366 + 14, + .hsync_end = 1366 + 14 + 56, + .htotal = 1366 + 14 + 56 + 64, + .vdisplay = 768, + .vsync_start = 768 + 1, + .vsync_end = 768 + 1 + 3, + .vtotal = 768 + 1 + 3 + 22, +}; + +static const struct panel_desc nlt_nl13676bc25_03f = { + .modes = &nlt_nl13676bc25_03f_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 363, + .height = 215, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct display_timing nlt_nl192108ac18_02d_timing = { @@ -1871,6 +3669,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 = { @@ -1894,6 +3693,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 = { @@ -1936,7 +3736,6 @@ static const struct drm_display_mode olimex_lcd_olinuxino_43ts_mode = { .vsync_start = 272 + 8, .vsync_end = 272 + 8 + 5, .vtotal = 272 + 8 + 5 + 3, - .vrefresh = 60, }; static const struct panel_desc olimex_lcd_olinuxino_43ts = { @@ -1949,6 +3748,62 @@ static const struct panel_desc olimex_lcd_olinuxino_43ts = { .bus_format = MEDIA_BUS_FMT_RGB888_1X24, }; +static const struct drm_display_mode olimex_lcd_olinuxino_5cts_mode = { + .clock = 33300, + .hdisplay = 800, + .hsync_start = 800 + 210, + .hsync_end = 800 + 210 + 20, + .htotal = 800 + 210 + 20 + 26, + .vdisplay = 480, + .vsync_start = 480 + 22, + .vsync_end = 480 + 22 + 10, + .vtotal = 480 + 22 + 10 + 13, +}; + +static const struct panel_desc olimex_lcd_olinuxino_5cts = { + .modes = &olimex_lcd_olinuxino_5cts_mode, + .num_modes = 1, + .size = { + .width = 154, + .height = 86, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, +}; + + +static const struct display_timing ontat_kd50g21_40nt_a1_timing = { + .pixelclock = { 30000000, 30000000, 50000000 }, + .hactive = { 800, 800, 800 }, + .hfront_porch = { 1, 40, 255 }, + .hback_porch = { 1, 40, 87 }, + .hsync_len = { 1, 48, 87 }, + .vactive = { 480, 480, 480 }, + .vfront_porch = { 1, 13, 255 }, + .vback_porch = { 1, 29, 29 }, + .vsync_len = { 3, 3, 31 }, + .flags = DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW | + DISPLAY_FLAGS_DE_HIGH | DISPLAY_FLAGS_PIXDATA_POSEDGE, +}; + +static const struct panel_desc ontat_kd50g21_40nt_a1 = { + .timings = &ontat_kd50g21_40nt_a1_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 108, + .height = 65, + }, + .delay = { + .prepare = 147, /* 5 VSDs */ + .enable = 147, /* 5 VSDs */ + .disable = 88, /* 3 VSDs */ + .unprepare = 117, /* 4 VSDs */ + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE, + .connector_type = DRM_MODE_CONNECTOR_DPI, +}; + /* * 800x480 CVT. The panel appears to be quite accepting, at least as far as * pixel clocks, but this is the timing that was being used in the Adafruit @@ -1964,7 +3819,6 @@ static const struct drm_display_mode ontat_yx700wv03_mode = { .vsync_start = 483, .vsync_end = 493, .vtotal = 500, - .vrefresh = 60, .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, }; @@ -1983,6 +3837,32 @@ static const struct panel_desc ontat_yx700wv03 = { .bus_format = MEDIA_BUS_FMT_RGB666_1X18, }; +static const struct drm_display_mode ortustech_com37h3m_mode = { + .clock = 22230, + .hdisplay = 480, + .hsync_start = 480 + 40, + .hsync_end = 480 + 40 + 10, + .htotal = 480 + 40 + 10 + 40, + .vdisplay = 640, + .vsync_start = 640 + 4, + .vsync_end = 640 + 4 + 2, + .vtotal = 640 + 4 + 2 + 4, + .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_SAMPLE_NEGEDGE | + DRM_BUS_FLAG_SYNC_DRIVE_POSEDGE, +}; + static const struct drm_display_mode ortustech_com43h4m85ulc_mode = { .clock = 25000, .hdisplay = 480, @@ -1993,19 +3873,174 @@ static const struct drm_display_mode ortustech_com43h4m85ulc_mode = { .vsync_start = 800 + 3, .vsync_end = 800 + 3 + 3, .vtotal = 800 + 3 + 3 + 3, - .vrefresh = 60, }; static const struct panel_desc ortustech_com43h4m85ulc = { .modes = &ortustech_com43h4m85ulc_mode, .num_modes = 1, - .bpc = 8, + .bpc = 6, .size = { .width = 56, .height = 93, }, + .bus_format = MEDIA_BUS_FMT_RGB666_1X18, + .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 osddisplays_osd070t1718_19ts_mode = { + .clock = 33000, + .hdisplay = 800, + .hsync_start = 800 + 210, + .hsync_end = 800 + 210 + 30, + .htotal = 800 + 210 + 30 + 16, + .vdisplay = 480, + .vsync_start = 480 + 22, + .vsync_end = 480 + 22 + 13, + .vtotal = 480 + 22 + 13 + 10, + .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, +}; + +static const struct panel_desc osddisplays_osd070t1718_19ts = { + .modes = &osddisplays_osd070t1718_19ts_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 152, + .height = 91, + }, .bus_format = MEDIA_BUS_FMT_RGB888_1X24, - .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_POSEDGE, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE | + DRM_BUS_FLAG_SYNC_DRIVE_POSEDGE, + .connector_type = DRM_MODE_CONNECTOR_DPI, +}; + +static const struct drm_display_mode pda_91_00156_a0_mode = { + .clock = 33300, + .hdisplay = 800, + .hsync_start = 800 + 1, + .hsync_end = 800 + 1 + 64, + .htotal = 800 + 1 + 64 + 64, + .vdisplay = 480, + .vsync_start = 480 + 1, + .vsync_end = 480 + 1 + 23, + .vtotal = 480 + 1 + 23 + 22, +}; + +static const struct panel_desc pda_91_00156_a0 = { + .modes = &pda_91_00156_a0_mode, + .num_modes = 1, + .size = { + .width = 152, + .height = 91, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, +}; + +static const struct drm_display_mode powertip_ph128800t004_zza01_mode = { + .clock = 71150, + .hdisplay = 1280, + .hsync_start = 1280 + 48, + .hsync_end = 1280 + 48 + 32, + .htotal = 1280 + 48 + 32 + 80, + .vdisplay = 800, + .vsync_start = 800 + 9, + .vsync_end = 800 + 9 + 8, + .vtotal = 800 + 9 + 8 + 6, + .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC, +}; + +static const struct panel_desc powertip_ph128800t004_zza01 = { + .modes = &powertip_ph128800t004_zza01_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 216, + .height = 135, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + +static const struct drm_display_mode powertip_ph128800t006_zhc01_mode = { + .clock = 66500, + .hdisplay = 1280, + .hsync_start = 1280 + 12, + .hsync_end = 1280 + 12 + 20, + .htotal = 1280 + 12 + 20 + 56, + .vdisplay = 800, + .vsync_start = 800 + 1, + .vsync_end = 800 + 1 + 3, + .vtotal = 800 + 1 + 3 + 20, + .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC, +}; + +static const struct panel_desc powertip_ph128800t006_zhc01 = { + .modes = &powertip_ph128800t006_zhc01_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 216, + .height = 135, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + +static const struct drm_display_mode powertip_ph800480t013_idf02_mode = { + .clock = 24750, + .hdisplay = 800, + .hsync_start = 800 + 54, + .hsync_end = 800 + 54 + 2, + .htotal = 800 + 54 + 2 + 44, + .vdisplay = 480, + .vsync_start = 480 + 49, + .vsync_end = 480 + 49 + 2, + .vtotal = 480 + 49 + 2 + 22, + .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, +}; + +static const struct panel_desc powertip_ph800480t013_idf02 = { + .modes = &powertip_ph800480t013_idf02_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 152, + .height = 91, + }, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | + DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE | + DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .connector_type = DRM_MODE_CONNECTOR_DPI, +}; + +static const struct drm_display_mode primeview_pm070wl4_mode = { + .clock = 32000, + .hdisplay = 800, + .hsync_start = 800 + 42, + .hsync_end = 800 + 42 + 128, + .htotal = 800 + 42 + 128 + 86, + .vdisplay = 480, + .vsync_start = 480 + 10, + .vsync_end = 480 + 10 + 2, + .vtotal = 480 + 10 + 2 + 33, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, +}; + +static const struct panel_desc primeview_pm070wl4 = { + .modes = &primeview_pm070wl4_mode, + .num_modes = 1, + .bpc = 6, + .size = { + .width = 152, + .height = 91, + }, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE, + .bus_format = MEDIA_BUS_FMT_RGB666_1X18, + .connector_type = DRM_MODE_CONNECTOR_DPI, }; static const struct drm_display_mode qd43003c0_40_mode = { @@ -2018,7 +4053,6 @@ static const struct drm_display_mode qd43003c0_40_mode = { .vsync_start = 272 + 4, .vsync_end = 272 + 4 + 10, .vtotal = 272 + 4 + 10 + 2, - .vrefresh = 60, }; static const struct panel_desc qd43003c0_40 = { @@ -2032,6 +4066,97 @@ static const struct panel_desc qd43003c0_40 = { .bus_format = MEDIA_BUS_FMT_RGB888_1X24, }; +static const struct drm_display_mode qishenglong_gopher2b_lcd_modes[] = { + { /* 60 Hz */ + .clock = 10800, + .hdisplay = 480, + .hsync_start = 480 + 77, + .hsync_end = 480 + 77 + 41, + .htotal = 480 + 77 + 41 + 2, + .vdisplay = 272, + .vsync_start = 272 + 16, + .vsync_end = 272 + 16 + 10, + .vtotal = 272 + 16 + 10 + 2, + .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, + }, + { /* 50 Hz */ + .clock = 10800, + .hdisplay = 480, + .hsync_start = 480 + 17, + .hsync_end = 480 + 17 + 41, + .htotal = 480 + 17 + 41 + 2, + .vdisplay = 272, + .vsync_start = 272 + 116, + .vsync_end = 272 + 116 + 10, + .vtotal = 272 + 116 + 10 + 2, + .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, + }, +}; + +static const struct panel_desc qishenglong_gopher2b_lcd = { + .modes = qishenglong_gopher2b_lcd_modes, + .num_modes = ARRAY_SIZE(qishenglong_gopher2b_lcd_modes), + .bpc = 8, + .size = { + .width = 95, + .height = 54, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE, + .connector_type = DRM_MODE_CONNECTOR_DPI, +}; + +static const struct display_timing raystar_rff500f_awh_dnn_timing = { + .pixelclock = { 23000000, 25000000, 27000000 }, + .hactive = { 800, 800, 800 }, + .hback_porch = { 4, 8, 48 }, + .hfront_porch = { 4, 8, 48 }, + .hsync_len = { 2, 4, 8 }, + .vactive = { 480, 480, 480 }, + .vback_porch = { 4, 8, 12 }, + .vfront_porch = { 4, 8, 12 }, + .vsync_len = { 2, 4, 8 }, +}; + +static const struct panel_desc raystar_rff500f_awh_dnn = { + .timings = &raystar_rff500f_awh_dnn_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 108, + .height = 65, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + +static const struct display_timing rocktech_rk043fn48h_timing = { + .pixelclock = { 6000000, 9000000, 12000000 }, + .hactive = { 480, 480, 480 }, + .hback_porch = { 8, 43, 43 }, + .hfront_porch = { 2, 8, 10 }, + .hsync_len = { 1, 1, 1 }, + .vactive = { 272, 272, 272 }, + .vback_porch = { 2, 12, 26 }, + .vfront_porch = { 1, 4, 4 }, + .vsync_len = { 1, 10, 10 }, + .flags = DISPLAY_FLAGS_VSYNC_LOW | DISPLAY_FLAGS_HSYNC_LOW | + DISPLAY_FLAGS_DE_HIGH | DISPLAY_FLAGS_PIXDATA_POSEDGE | + DISPLAY_FLAGS_SYNC_POSEDGE, +}; + +static const struct panel_desc rocktech_rk043fn48h = { + .timings = &rocktech_rk043fn48h_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 95, + .height = 54, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .connector_type = DRM_MODE_CONNECTOR_DPI, +}; + static const struct display_timing rocktech_rk070er9427_timing = { .pixelclock = { 26400000, 33300000, 46800000 }, .hactive = { 800, 800, 800 }, @@ -2062,26 +4187,95 @@ static const struct panel_desc rocktech_rk070er9427 = { .bus_format = MEDIA_BUS_FMT_RGB666_1X18, }; -static const struct drm_display_mode samsung_lsn122dl01_c01_mode = { - .clock = 271560, - .hdisplay = 2560, - .hsync_start = 2560 + 48, - .hsync_end = 2560 + 48 + 32, - .htotal = 2560 + 48 + 32 + 80, - .vdisplay = 1600, - .vsync_start = 1600 + 2, - .vsync_end = 1600 + 2 + 5, - .vtotal = 1600 + 2 + 5 + 57, - .vrefresh = 60, +static const struct drm_display_mode rocktech_rk101ii01d_ct_mode = { + .clock = 71100, + .hdisplay = 1280, + .hsync_start = 1280 + 48, + .hsync_end = 1280 + 48 + 32, + .htotal = 1280 + 48 + 32 + 80, + .vdisplay = 800, + .vsync_start = 800 + 2, + .vsync_end = 800 + 2 + 5, + .vtotal = 800 + 2 + 5 + 16, }; -static const struct panel_desc samsung_lsn122dl01_c01 = { - .modes = &samsung_lsn122dl01_c01_mode, +static const struct panel_desc rocktech_rk101ii01d_ct = { + .modes = &rocktech_rk101ii01d_ct_mode, + .bpc = 8, .num_modes = 1, .size = { - .width = 263, - .height = 164, + .width = 217, + .height = 136, + }, + .delay = { + .prepare = 50, + .disable = 50, + }, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + +static const struct display_timing samsung_ltl101al01_timing = { + .pixelclock = { 66663000, 66663000, 66663000 }, + .hactive = { 1280, 1280, 1280 }, + .hfront_porch = { 18, 18, 18 }, + .hback_porch = { 36, 36, 36 }, + .hsync_len = { 16, 16, 16 }, + .vactive = { 800, 800, 800 }, + .vfront_porch = { 4, 4, 4 }, + .vback_porch = { 16, 16, 16 }, + .vsync_len = { 3, 3, 3 }, + .flags = DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW, +}; + +static const struct panel_desc samsung_ltl101al01 = { + .timings = &samsung_ltl101al01_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 217, + .height = 135, + }, + .delay = { + .prepare = 40, + .enable = 300, + .disable = 200, + .unprepare = 600, }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + +static const struct display_timing samsung_ltl106al01_timing = { + .pixelclock = { 71980000, 71980000, 71980000 }, + .hactive = { 1366, 1366, 1366 }, + .hfront_porch = { 56, 56, 56 }, + .hback_porch = { 106, 106, 106 }, + .hsync_len = { 14, 14, 14 }, + .vactive = { 768, 768, 768 }, + .vfront_porch = { 3, 3, 3 }, + .vback_porch = { 6, 6, 6 }, + .vsync_len = { 1, 1, 1 }, + .flags = DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW, +}; + +static const struct panel_desc samsung_ltl106al01 = { + .timings = &samsung_ltl106al01_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 235, + .height = 132, + }, + .delay = { + .prepare = 5, + .enable = 10, + .disable = 10, + .unprepare = 5, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct drm_display_mode samsung_ltn101nt05_mode = { @@ -2094,7 +4288,6 @@ static const struct drm_display_mode samsung_ltn101nt05_mode = { .vsync_start = 600 + 3, .vsync_end = 600 + 3 + 6, .vtotal = 600 + 3 + 6 + 61, - .vrefresh = 60, }; static const struct panel_desc samsung_ltn101nt05 = { @@ -2105,29 +4298,59 @@ static const struct panel_desc samsung_ltn101nt05 = { .width = 223, .height = 125, }, + .bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; -static const struct drm_display_mode samsung_ltn140at29_301_mode = { - .clock = 76300, - .hdisplay = 1366, - .hsync_start = 1366 + 64, - .hsync_end = 1366 + 64 + 48, - .htotal = 1366 + 64 + 48 + 128, - .vdisplay = 768, - .vsync_start = 768 + 2, - .vsync_end = 768 + 2 + 5, - .vtotal = 768 + 2 + 5 + 17, - .vrefresh = 60, +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 samsung_ltn140at29_301 = { - .modes = &samsung_ltn140at29_301_mode, +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_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + +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, + .flags = DISPLAY_FLAGS_PIXDATA_POSEDGE, +}; + +static const struct panel_desc sharp_lq070y3dg3b = { + .modes = &sharp_lq070y3dg3b_mode, .num_modes = 1, - .bpc = 6, + .bpc = 8, .size = { - .width = 320, - .height = 187, + .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_SAMPLE_NEGEDGE | + DRM_BUS_FLAG_SYNC_DRIVE_POSEDGE, }; static const struct drm_display_mode sharp_lq035q7db03_mode = { @@ -2140,7 +4363,6 @@ static const struct drm_display_mode sharp_lq035q7db03_mode = { .vsync_start = 320 + 9, .vsync_end = 320 + 9 + 1, .vtotal = 320 + 9 + 1 + 7, - .vrefresh = 60, }; static const struct panel_desc sharp_lq035q7db03 = { @@ -2176,58 +4398,48 @@ static const struct panel_desc sharp_lq101k1ly04 = { .height = 136, }, .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA, -}; - -static const struct display_timing sharp_lq123p1jx31_timing = { - .pixelclock = { 252750000, 252750000, 266604720 }, - .hactive = { 2400, 2400, 2400 }, - .hfront_porch = { 48, 48, 48 }, - .hback_porch = { 80, 80, 84 }, - .hsync_len = { 32, 32, 32 }, - .vactive = { 1600, 1600, 1600 }, - .vfront_porch = { 3, 3, 3 }, - .vback_porch = { 33, 33, 120 }, - .vsync_len = { 10, 10, 10 }, - .flags = DISPLAY_FLAGS_VSYNC_LOW | DISPLAY_FLAGS_HSYNC_LOW, -}; - -static const struct panel_desc sharp_lq123p1jx31 = { - .timings = &sharp_lq123p1jx31_timing, - .num_timings = 1, - .bpc = 8, - .size = { - .width = 259, - .height = 173, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + +static const struct drm_display_mode sharp_ls020b1dd01d_modes[] = { + { /* 50 Hz */ + .clock = 3000, + .hdisplay = 240, + .hsync_start = 240 + 58, + .hsync_end = 240 + 58 + 1, + .htotal = 240 + 58 + 1 + 1, + .vdisplay = 160, + .vsync_start = 160 + 24, + .vsync_end = 160 + 24 + 10, + .vtotal = 160 + 24 + 10 + 6, + .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC, }, - .delay = { - .prepare = 110, - .enable = 50, - .unprepare = 550, + { /* 60 Hz */ + .clock = 3000, + .hdisplay = 240, + .hsync_start = 240 + 8, + .hsync_end = 240 + 8 + 1, + .htotal = 240 + 8 + 1 + 1, + .vdisplay = 160, + .vsync_start = 160 + 24, + .vsync_end = 160 + 24 + 10, + .vtotal = 160 + 24 + 10 + 6, + .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC, }, }; -static const struct drm_display_mode sharp_lq150x1lg11_mode = { - .clock = 71100, - .hdisplay = 1024, - .hsync_start = 1024 + 168, - .hsync_end = 1024 + 168 + 64, - .htotal = 1024 + 168 + 64 + 88, - .vdisplay = 768, - .vsync_start = 768 + 37, - .vsync_end = 768 + 37 + 2, - .vtotal = 768 + 37 + 2 + 8, - .vrefresh = 60, -}; - -static const struct panel_desc sharp_lq150x1lg11 = { - .modes = &sharp_lq150x1lg11_mode, - .num_modes = 1, +static const struct panel_desc sharp_ls020b1dd01d = { + .modes = sharp_ls020b1dd01d_modes, + .num_modes = ARRAY_SIZE(sharp_ls020b1dd01d_modes), .bpc = 6, .size = { - .width = 304, - .height = 228, + .width = 42, + .height = 28, }, .bus_format = MEDIA_BUS_FMT_RGB565_1X16, + .bus_flags = DRM_BUS_FLAG_DE_HIGH + | DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE + | DRM_BUS_FLAG_SHARP_SIGNALS, }; static const struct drm_display_mode shelly_sca07010_bfn_lnn_mode = { @@ -2240,7 +4452,6 @@ static const struct drm_display_mode shelly_sca07010_bfn_lnn_mode = { .vsync_start = 480 + 1, .vsync_end = 480 + 1 + 23, .vtotal = 480 + 1 + 23 + 22, - .vrefresh = 60, }; static const struct panel_desc shelly_sca07010_bfn_lnn = { @@ -2253,32 +4464,115 @@ static const struct panel_desc shelly_sca07010_bfn_lnn = { .bus_format = MEDIA_BUS_FMT_RGB666_1X18, }; -static const struct drm_display_mode starry_kr122ea0sra_mode = { - .clock = 147000, - .hdisplay = 1920, - .hsync_start = 1920 + 16, - .hsync_end = 1920 + 16 + 16, - .htotal = 1920 + 16 + 16 + 32, - .vdisplay = 1200, - .vsync_start = 1200 + 15, - .vsync_end = 1200 + 15 + 2, - .vtotal = 1200 + 15 + 2 + 18, - .vrefresh = 60, - .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, +static const struct drm_display_mode starry_kr070pe2t_mode = { + .clock = 33000, + .hdisplay = 800, + .hsync_start = 800 + 209, + .hsync_end = 800 + 209 + 1, + .htotal = 800 + 209 + 1 + 45, + .vdisplay = 480, + .vsync_start = 480 + 22, + .vsync_end = 480 + 22 + 1, + .vtotal = 480 + 22 + 1 + 22, }; -static const struct panel_desc starry_kr122ea0sra = { - .modes = &starry_kr122ea0sra_mode, +static const struct panel_desc starry_kr070pe2t = { + .modes = &starry_kr070pe2t_mode, .num_modes = 1, + .bpc = 8, + .size = { + .width = 152, + .height = 86, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE, + .connector_type = DRM_MODE_CONNECTOR_DPI, +}; + +static const struct display_timing startek_kd070wvfpa_mode = { + .pixelclock = { 25200000, 27200000, 30500000 }, + .hactive = { 800, 800, 800 }, + .hfront_porch = { 19, 44, 115 }, + .hback_porch = { 5, 16, 101 }, + .hsync_len = { 1, 2, 100 }, + .vactive = { 480, 480, 480 }, + .vfront_porch = { 5, 43, 67 }, + .vback_porch = { 5, 5, 67 }, + .vsync_len = { 1, 2, 66 }, + .flags = DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW | + DISPLAY_FLAGS_DE_HIGH | DISPLAY_FLAGS_PIXDATA_POSEDGE | + DISPLAY_FLAGS_SYNC_POSEDGE, +}; + +static const struct panel_desc startek_kd070wvfpa = { + .timings = &startek_kd070wvfpa_mode, + .num_timings = 1, + .bpc = 8, .size = { - .width = 263, - .height = 164, + .width = 152, + .height = 91, }, .delay = { - .prepare = 10 + 200, - .enable = 50, - .unprepare = 10 + 500, + .prepare = 20, + .enable = 200, + .disable = 200, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .connector_type = DRM_MODE_CONNECTOR_DPI, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | + DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE | + DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE, +}; + +static const struct display_timing tsd_tst043015cmhx_timing = { + .pixelclock = { 5000000, 9000000, 12000000 }, + .hactive = { 480, 480, 480 }, + .hfront_porch = { 4, 5, 65 }, + .hback_porch = { 36, 40, 255 }, + .hsync_len = { 1, 1, 1 }, + .vactive = { 272, 272, 272 }, + .vfront_porch = { 2, 8, 97 }, + .vback_porch = { 3, 8, 31 }, + .vsync_len = { 1, 1, 1 }, + + .flags = DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW | + DISPLAY_FLAGS_DE_HIGH | DISPLAY_FLAGS_PIXDATA_POSEDGE, +}; + +static const struct panel_desc tsd_tst043015cmhx = { + .timings = &tsd_tst043015cmhx_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 105, + .height = 67, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE, +}; + +static const struct drm_display_mode tfc_s9700rtwv43tr_01b_mode = { + .clock = 30000, + .hdisplay = 800, + .hsync_start = 800 + 39, + .hsync_end = 800 + 39 + 47, + .htotal = 800 + 39 + 47 + 39, + .vdisplay = 480, + .vsync_start = 480 + 13, + .vsync_end = 480 + 13 + 2, + .vtotal = 480 + 13 + 2 + 29, +}; + +static const struct panel_desc tfc_s9700rtwv43tr_01b = { + .modes = &tfc_s9700rtwv43tr_01b_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 155, + .height = 90, }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE, }; static const struct display_timing tianma_tm070jdhg30_timing = { @@ -2303,6 +4597,88 @@ static const struct panel_desc tianma_tm070jdhg30 = { .height = 95, }, .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, +}; + +static const struct panel_desc tianma_tm070jvhg33 = { + .timings = &tianma_tm070jdhg30_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 150, + .height = 94, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, +}; + +/* + * The TM070JDHG34-00 datasheet computes total blanking as back porch + + * front porch, not including sync pulse width. This is for both H and + * V. To make the total blanking and period correct, subtract the pulse + * width from the front porch. + * + * This works well for the Min and Typ values, but for Max values the sync + * pulse width is higher than back porch + front porch, so work around that + * by reducing the Max sync length value to 1 and then treating the Max + * porches as in the Min and Typ cases. + * + * Exact datasheet values are added as a comment where they differ from the + * ones implemented for the above reason. + * + * The P0700WXF1MBAA datasheet is even less detailed, only listing period + * and total blanking time, however the resulting values are the same as + * the TM070JDHG34-00. + */ +static const struct display_timing tianma_tm070jdhg34_00_timing = { + .pixelclock = { 68400000, 71900000, 78100000 }, + .hactive = { 1280, 1280, 1280 }, + .hfront_porch = { 130, 138, 158 }, /* 131, 139, 159 */ + .hback_porch = { 5, 5, 5 }, + .hsync_len = { 1, 1, 1 }, /* 1, 1, 256 */ + .vactive = { 800, 800, 800 }, + .vfront_porch = { 2, 39, 98 }, /* 3, 40, 99 */ + .vback_porch = { 2, 2, 2 }, + .vsync_len = { 1, 1, 1 }, /* 1, 1, 128 */ + .flags = DISPLAY_FLAGS_DE_HIGH, +}; + +static const struct panel_desc tianma_tm070jdhg34_00 = { + .timings = &tianma_tm070jdhg34_00_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 150, /* 149.76 */ + .height = 94, /* 93.60 */ + }, + .delay = { + .prepare = 15, /* Tp1 */ + .enable = 150, /* Tp2 */ + .disable = 150, /* Tp4 */ + .unprepare = 120, /* Tp3 */ + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + +static const struct panel_desc tianma_p0700wxf1mbaa = { + .timings = &tianma_tm070jdhg34_00_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 150, /* 149.76 */ + .height = 94, /* 93.60 */ + }, + .delay = { + .prepare = 18, /* Tr + Tp1 */ + .enable = 152, /* Tp2 + Tp5 */ + .disable = 152, /* Tp6 + Tp4 */ + .unprepare = 120, /* Tp3 */ + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct display_timing tianma_tm070rvhg71_timing = { @@ -2327,6 +4703,94 @@ 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, + .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_SAMPLE_POSEDGE, +}; + +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, + .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_SAMPLE_NEGEDGE, +}; + +static const struct display_timing topland_tian_g07017_01_timing = { + .pixelclock = { 44900000, 51200000, 63000000 }, + .hactive = { 1024, 1024, 1024 }, + .hfront_porch = { 16, 160, 216 }, + .hback_porch = { 160, 160, 160 }, + .hsync_len = { 1, 1, 140 }, + .vactive = { 600, 600, 600 }, + .vfront_porch = { 1, 12, 127 }, + .vback_porch = { 23, 23, 23 }, + .vsync_len = { 1, 1, 20 }, +}; + +static const struct panel_desc topland_tian_g07017_01 = { + .timings = &topland_tian_g07017_01_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 154, + .height = 86, + }, + .delay = { + .prepare = 1, /* 6.5 - 150µs PLL wake-up time */ + .enable = 100, /* 6.4 - Power on: 6 VSyncs */ + .disable = 84, /* 6.4 - Power off: 5 Vsyncs */ + .unprepare = 50, /* 6.4 - Power off: 3 Vsyncs */ + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, }; static const struct drm_display_mode toshiba_lt089ac29000_mode = { @@ -2339,7 +4803,6 @@ static const struct drm_display_mode toshiba_lt089ac29000_mode = { .vsync_start = 768 + 20, .vsync_end = 768 + 20 + 7, .vtotal = 768 + 20 + 7 + 3, - .vrefresh = 60, }; static const struct panel_desc toshiba_lt089ac29000 = { @@ -2349,8 +4812,9 @@ static const struct panel_desc toshiba_lt089ac29000 = { .width = 194, .height = 116, }, - .bus_format = MEDIA_BUS_FMT_RGB888_1X24, - .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_POSEDGE, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct drm_display_mode tpk_f07a_0102_mode = { @@ -2363,7 +4827,6 @@ static const struct drm_display_mode tpk_f07a_0102_mode = { .vsync_start = 480 + 10, .vsync_end = 480 + 10 + 2, .vtotal = 480 + 10 + 2 + 33, - .vrefresh = 60, }; static const struct panel_desc tpk_f07a_0102 = { @@ -2373,7 +4836,7 @@ static const struct panel_desc tpk_f07a_0102 = { .width = 152, .height = 91, }, - .bus_flags = DRM_BUS_FLAG_PIXDATA_POSEDGE, + .bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE, }; static const struct drm_display_mode tpk_f10a_0102_mode = { @@ -2386,7 +4849,6 @@ static const struct drm_display_mode tpk_f10a_0102_mode = { .vsync_start = 600 + 20, .vsync_end = 600 + 20 + 5, .vtotal = 600 + 20 + 5 + 25, - .vrefresh = 60, }; static const struct panel_desc tpk_f10a_0102 = { @@ -2421,6 +4883,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 = { @@ -2434,6 +4897,56 @@ static const struct panel_desc urt_umsh_8596md_parallel = { .bus_format = MEDIA_BUS_FMT_RGB666_1X18, }; +static const struct drm_display_mode vivax_tpc9150_panel_mode = { + .clock = 60000, + .hdisplay = 1024, + .hsync_start = 1024 + 160, + .hsync_end = 1024 + 160 + 100, + .htotal = 1024 + 160 + 100 + 60, + .vdisplay = 600, + .vsync_start = 600 + 12, + .vsync_end = 600 + 12 + 10, + .vtotal = 600 + 12 + 10 + 13, +}; + +static const struct panel_desc vivax_tpc9150_panel = { + .modes = &vivax_tpc9150_panel_mode, + .num_modes = 1, + .bpc = 6, + .size = { + .width = 200, + .height = 115, + }, + .bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + +static const struct drm_display_mode vl050_8048nt_c01_mode = { + .clock = 33333, + .hdisplay = 800, + .hsync_start = 800 + 210, + .hsync_end = 800 + 210 + 20, + .htotal = 800 + 210 + 20 + 46, + .vdisplay = 480, + .vsync_start = 480 + 22, + .vsync_end = 480 + 22 + 10, + .vtotal = 480 + 22 + 10 + 23, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, +}; + +static const struct panel_desc vl050_8048nt_c01 = { + .modes = &vl050_8048nt_c01_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 120, + .height = 76, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE, +}; + static const struct drm_display_mode winstar_wf35ltiacd_mode = { .clock = 6410, .hdisplay = 320, @@ -2444,7 +4957,6 @@ static const struct drm_display_mode winstar_wf35ltiacd_mode = { .vsync_start = 240 + 4, .vsync_end = 240 + 4 + 3, .vtotal = 240 + 4 + 3 + 15, - .vrefresh = 60, .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, }; @@ -2459,6 +4971,57 @@ static const struct panel_desc winstar_wf35ltiacd = { .bus_format = MEDIA_BUS_FMT_RGB888_1X24, }; +static const struct drm_display_mode yes_optoelectronics_ytc700tlag_05_201c_mode = { + .clock = 51200, + .hdisplay = 1024, + .hsync_start = 1024 + 100, + .hsync_end = 1024 + 100 + 100, + .htotal = 1024 + 100 + 100 + 120, + .vdisplay = 600, + .vsync_start = 600 + 10, + .vsync_end = 600 + 10 + 10, + .vtotal = 600 + 10 + 10 + 15, + .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC, +}; + +static const struct panel_desc yes_optoelectronics_ytc700tlag_05_201c = { + .modes = &yes_optoelectronics_ytc700tlag_05_201c_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 154, + .height = 90, + }, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + +static const struct drm_display_mode mchp_ac69t88a_mode = { + .clock = 25000, + .hdisplay = 800, + .hsync_start = 800 + 88, + .hsync_end = 800 + 88 + 5, + .htotal = 800 + 88 + 5 + 40, + .vdisplay = 480, + .vsync_start = 480 + 23, + .vsync_end = 480 + 23 + 5, + .vtotal = 480 + 23 + 5 + 1, +}; + +static const struct panel_desc mchp_ac69t88a = { + .modes = &mchp_ac69t88a_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 108, + .height = 65, + }, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + static const struct drm_display_mode arm_rtsm_mode[] = { { .clock = 65000, @@ -2470,7 +5033,6 @@ static const struct drm_display_mode arm_rtsm_mode[] = { .vsync_start = 768 + 3, .vsync_end = 768 + 3 + 6, .vtotal = 768 + 3 + 6 + 29, - .vrefresh = 60, .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, }, }; @@ -2488,33 +5050,36 @@ static const struct panel_desc arm_rtsm = { static const struct of_device_id platform_of_match[] = { { + .compatible = "ampire,am-1280800n3tzqw-t00h", + .data = &ire_am_1280800n3tzqw_t00h, + }, { .compatible = "ampire,am-480272h3tmqw-t01h", .data = &ire_am_480272h3tmqw_t01h, }, { + .compatible = "ampire,am-800480l1tmqw-t00h", + .data = &ire_am_800480l1tmqw_t00h, + }, { .compatible = "ampire,am800480r3tmqwa1h", .data = &ire_am800480r3tmqwa1h, }, { + .compatible = "ampire,am800600p5tmqw-tb8h", + .data = &ire_am800600p5tmqwtb8h, + }, { .compatible = "arm,rtsm-display", .data = &arm_rtsm, }, { + .compatible = "armadeus,st0700-adapt", + .data = &armadeus_st0700_adapt, + }, { .compatible = "auo,b101aw03", .data = &auo_b101aw03, }, { - .compatible = "auo,b101ean01", - .data = &auo_b101ean01, - }, { .compatible = "auo,b101xtn01", .data = &auo_b101xtn01, }, { .compatible = "auo,b116xw03", .data = &auo_b116xw03, }, { - .compatible = "auo,b133htn01", - .data = &auo_b133htn01, - }, { - .compatible = "auo,b133xtn01", - .data = &auo_b133xtn01, - }, { .compatible = "auo,g070vvn01", .data = &auo_g070vvn01, }, { @@ -2524,12 +5089,30 @@ static const struct of_device_id platform_of_match[] = { .compatible = "auo,g104sn02", .data = &auo_g104sn02, }, { + .compatible = "auo,g104stn01", + .data = &auo_g104stn01, + }, { + .compatible = "auo,g121ean01", + .data = &auo_g121ean01, + }, { .compatible = "auo,g133han01", .data = &auo_g133han01, }, { + .compatible = "auo,g156han04", + .data = &auo_g156han04, + }, { + .compatible = "auo,g156xtn01", + .data = &auo_g156xtn01, + }, { .compatible = "auo,g185han01", .data = &auo_g185han01, }, { + .compatible = "auo,g190ean01", + .data = &auo_g190ean01, + }, { + .compatible = "auo,p238han01", + .data = &auo_p238han01, + }, { .compatible = "auo,p320hvn03", .data = &auo_p320hvn03, }, { @@ -2542,18 +5125,42 @@ static const struct of_device_id platform_of_match[] = { .compatible = "bananapi,s070wv20-ct16", .data = &bananapi_s070wv20_ct16, }, { + .compatible = "boe,av101hdt-a10", + .data = &boe_av101hdt_a10, + }, { + .compatible = "boe,av123z7m-n17", + .data = &boe_av123z7m_n17, + }, { + .compatible = "boe,bp082wx1-100", + .data = &boe_bp082wx1_100, + }, { + .compatible = "boe,bp101wx1-100", + .data = &boe_bp101wx1_100, + }, { + .compatible = "boe,ev121wxm-n10-1850", + .data = &boe_ev121wxm_n10_1850, + }, { .compatible = "boe,hv070wsa-100", .data = &boe_hv070wsa }, { - .compatible = "boe,nv101wxmn51", - .data = &boe_nv101wxmn51, + .compatible = "cct,cmt430b19n00", + .data = &cct_cmt430b19n00, }, { .compatible = "cdtech,s043wq26h-ct7", .data = &cdtech_s043wq26h_ct7, }, { + .compatible = "cdtech,s070pws19hp-fc21", + .data = &cdtech_s070pws19hp_fc21, + }, { + .compatible = "cdtech,s070swv29hg-dc44", + .data = &cdtech_s070swv29hg_dc44, + }, { .compatible = "cdtech,s070wv95-ct16", .data = &cdtech_s070wv95_ct16, }, { + .compatible = "chefree,ch101olhlwh-002", + .data = &chefree_ch101olhlwh_002, + }, { .compatible = "chunghwa,claa070wp03xg", .data = &chunghwa_claa070wp03xg, }, { @@ -2563,6 +5170,12 @@ static const struct of_device_id platform_of_match[] = { .compatible = "chunghwa,claa101wb01", .data = &chunghwa_claa101wb01 }, { + .compatible = "dataimage,fg040346dsswbg04", + .data = &dataimage_fg040346dsswbg04, + }, { + .compatible = "dataimage,fg1001l0dsswmg01", + .data = &dataimage_fg1001l0dsswmg01, + }, { .compatible = "dataimage,scf0700c48ggu18", .data = &dataimage_scf0700c48ggu18, }, { @@ -2572,6 +5185,18 @@ static const struct of_device_id platform_of_match[] = { .compatible = "dlc,dlc1010gig", .data = &dlc_dlc1010gig, }, { + .compatible = "edt,et035012dm6", + .data = &edt_et035012dm6, + }, { + .compatible = "edt,etm0350g0dh6", + .data = &edt_etm0350g0dh6, + }, { + .compatible = "edt,etm043080dh6gp", + .data = &edt_etm043080dh6gp, + }, { + .compatible = "edt,etm0430g0dh6", + .data = &edt_etm0430g0dh6, + }, { .compatible = "edt,et057090dhu", .data = &edt_et057090dhu, }, { @@ -2587,18 +5212,48 @@ static const struct of_device_id platform_of_match[] = { .compatible = "edt,etm0700g0edh6", .data = &edt_etm0700g0bdh6, }, { + .compatible = "edt,etml0700y5dha", + .data = &edt_etml0700y5dha, + }, { + .compatible = "edt,etml1010g3dra", + .data = &edt_etml1010g3dra, + }, { + .compatible = "edt,etmv570g2dhu", + .data = &edt_etmv570g2dhu, + }, { + .compatible = "eink,vb3300-kca", + .data = &eink_vb3300_kca, + }, { + .compatible = "evervision,vgg644804", + .data = &evervision_vgg644804, + }, { + .compatible = "evervision,vgg804821", + .data = &evervision_vgg804821, + }, { .compatible = "foxlink,fl500wvr00-a0t", .data = &foxlink_fl500wvr00_a0t, }, { + .compatible = "frida,frd350h54004", + .data = &frida_frd350h54004, + }, { + .compatible = "friendlyarm,hd702e", + .data = &friendlyarm_hd702e, + }, { .compatible = "giantplus,gpg482739qs5", .data = &giantplus_gpg482739qs5 }, { + .compatible = "giantplus,gpm940b0", + .data = &giantplus_gpm940b0, + }, { .compatible = "hannstar,hsd070pww1", .data = &hannstar_hsd070pww1, }, { .compatible = "hannstar,hsd100pxn1", .data = &hannstar_hsd100pxn1, }, { + .compatible = "hannstar,hsd101pww2", + .data = &hannstar_hsd101pww2, + }, { .compatible = "hit,tx23d38vm0caa", .data = &hitachi_tx23d38vm0caa }, { @@ -2608,9 +5263,18 @@ static const struct of_device_id platform_of_match[] = { .compatible = "innolux,at070tn92", .data = &innolux_at070tn92, }, { + .compatible = "innolux,g070ace-l01", + .data = &innolux_g070ace_l01, + }, { + .compatible = "innolux,g070ace-lh3", + .data = &innolux_g070ace_lh3, + }, { .compatible = "innolux,g070y2-l01", .data = &innolux_g070y2_l01, }, { + .compatible = "innolux,g070y2-t02", + .data = &innolux_g070y2_t02, + }, { .compatible = "innolux,g101ice-l01", .data = &innolux_g101ice_l01 }, { @@ -2620,42 +5284,87 @@ static const struct of_device_id platform_of_match[] = { .compatible = "innolux,g121x1-l03", .data = &innolux_g121x1_l03, }, { - .compatible = "innolux,n116bge", - .data = &innolux_n116bge, + .compatible = "innolux,g121xce-l01", + .data = &innolux_g121xce_l01, + }, { + .compatible = "innolux,g156hce-l01", + .data = &innolux_g156hce_l01, }, { .compatible = "innolux,n156bge-l21", .data = &innolux_n156bge_l21, }, { - .compatible = "innolux,p120zdg-bf1", - .data = &innolux_p120zdg_bf1, - }, { .compatible = "innolux,zj070na-01p", .data = &innolux_zj070na_01p, }, { + .compatible = "jutouch,jt101tm023", + .data = &jutouch_jt101tm023, + }, { + .compatible = "koe,tx14d24vm1bpa", + .data = &koe_tx14d24vm1bpa, + }, { + .compatible = "koe,tx26d202vm0bwa", + .data = &koe_tx26d202vm0bwa, + }, { .compatible = "koe,tx31d200vm0baa", .data = &koe_tx31d200vm0baa, }, { .compatible = "kyo,tcg121xglp", .data = &kyo_tcg121xglp, }, { + .compatible = "lemaker,bl035-rgb-002", + .data = &lemaker_bl035_rgb_002, + }, { .compatible = "lg,lb070wv8", .data = &lg_lb070wv8, }, { - .compatible = "lg,lp079qx1-sp0v", - .data = &lg_lp079qx1_sp0v, + .compatible = "lincolntech,lcd185-101ct", + .data = &lincolntech_lcd185_101ct, + }, { + .compatible = "logicpd,type28", + .data = &logicpd_type_28, + }, { + .compatible = "logictechno,lt161010-2nhc", + .data = &logictechno_lt161010_2nh, + }, { + .compatible = "logictechno,lt161010-2nhr", + .data = &logictechno_lt161010_2nh, + }, { + .compatible = "logictechno,lt170410-2whc", + .data = &logictechno_lt170410_2whc, }, { - .compatible = "lg,lp097qx1-spa1", - .data = &lg_lp097qx1_spa1, + .compatible = "logictechno,lttd800480070-l2rt", + .data = &logictechno_lttd800480070_l2rt, }, { - .compatible = "lg,lp120up1", - .data = &lg_lp120up1, + .compatible = "logictechno,lttd800480070-l6wh-rt", + .data = &logictechno_lttd800480070_l6wh_rt, }, { - .compatible = "lg,lp129qe", - .data = &lg_lp129qe, + .compatible = "microtips,mf-101hiebcaf0", + .data = µtips_mf_101hiebcaf0_c, + }, { + .compatible = "microtips,mf-103hieb0ga0", + .data = µtips_mf_103hieb0ga0, }, { .compatible = "mitsubishi,aa070mc01-ca1", .data = &mitsubishi_aa070mc01, }, { + .compatible = "mitsubishi,aa084xe01", + .data = &mitsubishi_aa084xe01, + }, { + .compatible = "multi-inno,mi0700a2t-30", + .data = &multi_inno_mi0700a2t_30, + }, { + .compatible = "multi-inno,mi0700s4t-6", + .data = &multi_inno_mi0700s4t_6, + }, { + .compatible = "multi-inno,mi0800ft-9", + .data = &multi_inno_mi0800ft_9, + }, { + .compatible = "multi-inno,mi1010ait-1cp", + .data = &multi_inno_mi1010ait_1cp, + }, { + .compatible = "multi-inno,mi1010z1t-1cp11", + .data = &multi_inno_mi1010z1t_1cp11, + }, { .compatible = "nec,nl12880bc20-05", .data = &nec_nl12880bc20_05, }, { @@ -2668,6 +5377,9 @@ static const struct of_device_id platform_of_match[] = { .compatible = "newhaven,nhd-4.3-480272ef-atxl", .data = &newhaven_nhd_43_480272ef_atxl, }, { + .compatible = "nlt,nl13676bc25-03f", + .data = &nlt_nl13676bc25_03f, + }, { .compatible = "nlt,nl192108ac18-02d", .data = &nlt_nl192108ac18_02d, }, { @@ -2680,54 +5392,126 @@ static const struct of_device_id platform_of_match[] = { .compatible = "olimex,lcd-olinuxino-43-ts", .data = &olimex_lcd_olinuxino_43ts, }, { + .compatible = "olimex,lcd-olinuxino-5-cts", + .data = &olimex_lcd_olinuxino_5cts, + }, { + .compatible = "ontat,kd50g21-40nt-a1", + .data = &ontat_kd50g21_40nt_a1, + }, { .compatible = "ontat,yx700wv03", .data = &ontat_yx700wv03, }, { + .compatible = "ortustech,com37h3m05dtc", + .data = &ortustech_com37h3m, + }, { + .compatible = "ortustech,com37h3m99dtc", + .data = &ortustech_com37h3m, + }, { .compatible = "ortustech,com43h4m85ulc", .data = &ortustech_com43h4m85ulc, }, { + .compatible = "osddisplays,osd070t1718-19ts", + .data = &osddisplays_osd070t1718_19ts, + }, { + .compatible = "pda,91-00156-a0", + .data = &pda_91_00156_a0, + }, { + .compatible = "powertip,ph128800t004-zza01", + .data = &powertip_ph128800t004_zza01, + }, { + .compatible = "powertip,ph128800t006-zhc01", + .data = &powertip_ph128800t006_zhc01, + }, { + .compatible = "powertip,ph800480t013-idf02", + .data = &powertip_ph800480t013_idf02, + }, { + .compatible = "primeview,pm070wl4", + .data = &primeview_pm070wl4, + }, { .compatible = "qiaodian,qd43003c0-40", .data = &qd43003c0_40, }, { + .compatible = "qishenglong,gopher2b-lcd", + .data = &qishenglong_gopher2b_lcd, + }, { + .compatible = "raystar,rff500f-awh-dnn", + .data = &raystar_rff500f_awh_dnn, + }, { + .compatible = "rocktech,rk043fn48h", + .data = &rocktech_rk043fn48h, + }, { .compatible = "rocktech,rk070er9427", .data = &rocktech_rk070er9427, }, { - .compatible = "samsung,lsn122dl01-c01", - .data = &samsung_lsn122dl01_c01, + .compatible = "rocktech,rk101ii01d-ct", + .data = &rocktech_rk101ii01d_ct, + }, { + .compatible = "samsung,ltl101al01", + .data = &samsung_ltl101al01, + }, { + .compatible = "samsung,ltl106al01", + .data = &samsung_ltl106al01, }, { .compatible = "samsung,ltn101nt05", .data = &samsung_ltn101nt05, }, { - .compatible = "samsung,ltn140at29-301", - .data = &samsung_ltn140at29_301, + .compatible = "satoz,sat050at40h12r2", + .data = &satoz_sat050at40h12r2, }, { .compatible = "sharp,lq035q7db03", .data = &sharp_lq035q7db03, }, { + .compatible = "sharp,lq070y3dg3b", + .data = &sharp_lq070y3dg3b, + }, { .compatible = "sharp,lq101k1ly04", .data = &sharp_lq101k1ly04, }, { - .compatible = "sharp,lq123p1jx31", - .data = &sharp_lq123p1jx31, - }, { - .compatible = "sharp,lq150x1lg11", - .data = &sharp_lq150x1lg11, + .compatible = "sharp,ls020b1dd01d", + .data = &sharp_ls020b1dd01d, }, { .compatible = "shelly,sca07010-bfn-lnn", .data = &shelly_sca07010_bfn_lnn, }, { - .compatible = "starry,kr122ea0sra", - .data = &starry_kr122ea0sra, + .compatible = "starry,kr070pe2t", + .data = &starry_kr070pe2t, + }, { + .compatible = "startek,kd070wvfpa", + .data = &startek_kd070wvfpa, + }, { + .compatible = "team-source-display,tst043015cmhx", + .data = &tsd_tst043015cmhx, + }, { + .compatible = "tfc,s9700rtwv43tr-01b", + .data = &tfc_s9700rtwv43tr_01b, + }, { + .compatible = "tianma,p0700wxf1mbaa", + .data = &tianma_p0700wxf1mbaa, }, { .compatible = "tianma,tm070jdhg30", .data = &tianma_tm070jdhg30, }, { + .compatible = "tianma,tm070jdhg34-00", + .data = &tianma_tm070jdhg34_00, + }, { + .compatible = "tianma,tm070jvhg33", + .data = &tianma_tm070jvhg33, + }, { .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, }, { + .compatible = "topland,tian-g07017-01", + .data = &topland_tian_g07017_01, + }, { .compatible = "tpk,f07a-0102", .data = &tpk_f07a_0102, }, { @@ -2752,9 +5536,30 @@ static const struct of_device_id platform_of_match[] = { .compatible = "urt,umsh-8596md-20t", .data = &urt_umsh_8596md_parallel, }, { + .compatible = "vivax,tpc9150-panel", + .data = &vivax_tpc9150_panel, + }, { + .compatible = "vxt,vl050-8048nt-c01", + .data = &vl050_8048nt_c01, + }, { .compatible = "winstar,wf35ltiacd", .data = &winstar_wf35ltiacd, }, { + .compatible = "yes-optoelectronics,ytc700tlag-05-201c", + .data = &yes_optoelectronics_ytc700tlag_05_201c, + }, { + .compatible = "microchip,ac69t88a", + .data = &mchp_ac69t88a, + }, { + /* Must be the last entry */ + .compatible = "panel-dpi", + + /* + * Explicitly NULL, the panel_desc structure will be + * allocated by panel_dpi_probe(). + */ + .data = NULL, + }, { /* sentinel */ } }; @@ -2762,18 +5567,18 @@ MODULE_DEVICE_TABLE(of, platform_of_match); static int panel_simple_platform_probe(struct platform_device *pdev) { - const struct of_device_id *id; + struct panel_simple *panel; - id = of_match_node(platform_of_match, pdev->dev.of_node); - if (!id) - return -ENODEV; + panel = panel_simple_probe(&pdev->dev); + if (IS_ERR(panel)) + return PTR_ERR(panel); - return panel_simple_probe(&pdev->dev, id->data); + return 0; } -static int panel_simple_platform_remove(struct platform_device *pdev) +static void panel_simple_platform_remove(struct platform_device *pdev) { - return panel_simple_remove(&pdev->dev); + panel_simple_remove(&pdev->dev); } static void panel_simple_platform_shutdown(struct platform_device *pdev) @@ -2781,24 +5586,23 @@ static void panel_simple_platform_shutdown(struct platform_device *pdev) panel_simple_shutdown(&pdev->dev); } +static const struct dev_pm_ops panel_simple_pm_ops = { + SET_RUNTIME_PM_OPS(panel_simple_suspend, panel_simple_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + static struct platform_driver panel_simple_platform_driver = { .driver = { .name = "panel-simple", .of_match_table = platform_of_match, + .pm = &panel_simple_pm_ops, }, .probe = panel_simple_platform_probe, .remove = panel_simple_platform_remove, .shutdown = panel_simple_platform_shutdown, }; -struct panel_desc_dsi { - struct panel_desc desc; - - unsigned long flags; - enum mipi_dsi_pixel_format format; - unsigned int lanes; -}; - static const struct drm_display_mode auo_b080uan01_mode = { .clock = 154500, .hdisplay = 1200, @@ -2809,7 +5613,6 @@ static const struct drm_display_mode auo_b080uan01_mode = { .vsync_start = 1920 + 9, .vsync_end = 1920 + 9 + 2, .vtotal = 1920 + 9 + 2 + 8, - .vrefresh = 60, }; static const struct panel_desc_dsi auo_b080uan01 = { @@ -2821,6 +5624,7 @@ static const struct panel_desc_dsi auo_b080uan01 = { .width = 108, .height = 272, }, + .connector_type = DRM_MODE_CONNECTOR_DSI, }, .flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS, .format = MIPI_DSI_FMT_RGB888, @@ -2837,7 +5641,6 @@ static const struct drm_display_mode boe_tv080wum_nl0_mode = { .vsync_start = 1920 + 21, .vsync_end = 1920 + 21 + 3, .vtotal = 1920 + 21 + 3 + 18, - .vrefresh = 60, .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, }; @@ -2849,6 +5652,7 @@ static const struct panel_desc_dsi boe_tv080wum_nl0 = { .width = 107, .height = 172, }, + .connector_type = DRM_MODE_CONNECTOR_DSI, }, .flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | @@ -2857,34 +5661,6 @@ static const struct panel_desc_dsi boe_tv080wum_nl0 = { .lanes = 4, }; -static const struct drm_display_mode lg_ld070wx3_sl01_mode = { - .clock = 71000, - .hdisplay = 800, - .hsync_start = 800 + 32, - .hsync_end = 800 + 32 + 1, - .htotal = 800 + 32 + 1 + 57, - .vdisplay = 1280, - .vsync_start = 1280 + 28, - .vsync_end = 1280 + 28 + 1, - .vtotal = 1280 + 28 + 1 + 14, - .vrefresh = 60, -}; - -static const struct panel_desc_dsi lg_ld070wx3_sl01 = { - .desc = { - .modes = &lg_ld070wx3_sl01_mode, - .num_modes = 1, - .bpc = 8, - .size = { - .width = 94, - .height = 151, - }, - }, - .flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS, - .format = MIPI_DSI_FMT_RGB888, - .lanes = 4, -}; - static const struct drm_display_mode lg_lh500wx1_sd03_mode = { .clock = 67000, .hdisplay = 720, @@ -2895,7 +5671,6 @@ static const struct drm_display_mode lg_lh500wx1_sd03_mode = { .vsync_start = 1280 + 8, .vsync_end = 1280 + 8 + 4, .vtotal = 1280 + 8 + 4 + 12, - .vrefresh = 60, }; static const struct panel_desc_dsi lg_lh500wx1_sd03 = { @@ -2907,6 +5682,7 @@ static const struct panel_desc_dsi lg_lh500wx1_sd03 = { .width = 62, .height = 110, }, + .connector_type = DRM_MODE_CONNECTOR_DSI, }, .flags = MIPI_DSI_MODE_VIDEO, .format = MIPI_DSI_FMT_RGB888, @@ -2923,7 +5699,6 @@ static const struct drm_display_mode panasonic_vvx10f004b00_mode = { .vsync_start = 1200 + 17, .vsync_end = 1200 + 17 + 2, .vtotal = 1200 + 17 + 2 + 16, - .vrefresh = 60, }; static const struct panel_desc_dsi panasonic_vvx10f004b00 = { @@ -2935,6 +5710,7 @@ static const struct panel_desc_dsi panasonic_vvx10f004b00 = { .width = 217, .height = 136, }, + .connector_type = DRM_MODE_CONNECTOR_DSI, }, .flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | MIPI_DSI_CLOCK_NON_CONTINUOUS, @@ -2942,6 +5718,65 @@ static const struct panel_desc_dsi panasonic_vvx10f004b00 = { .lanes = 4, }; +static const struct drm_display_mode lg_acx467akm_7_mode = { + .clock = 150000, + .hdisplay = 1080, + .hsync_start = 1080 + 2, + .hsync_end = 1080 + 2 + 2, + .htotal = 1080 + 2 + 2 + 2, + .vdisplay = 1920, + .vsync_start = 1920 + 2, + .vsync_end = 1920 + 2 + 2, + .vtotal = 1920 + 2 + 2 + 2, +}; + +static const struct panel_desc_dsi lg_acx467akm_7 = { + .desc = { + .modes = &lg_acx467akm_7_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 62, + .height = 110, + }, + .connector_type = DRM_MODE_CONNECTOR_DSI, + }, + .flags = 0, + .format = MIPI_DSI_FMT_RGB888, + .lanes = 4, +}; + +static const struct drm_display_mode osd101t2045_53ts_mode = { + .clock = 154500, + .hdisplay = 1920, + .hsync_start = 1920 + 112, + .hsync_end = 1920 + 112 + 16, + .htotal = 1920 + 112 + 16 + 32, + .vdisplay = 1200, + .vsync_start = 1200 + 16, + .vsync_end = 1200 + 16 + 2, + .vtotal = 1200 + 16 + 2 + 16, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, +}; + +static const struct panel_desc_dsi osd101t2045_53ts = { + .desc = { + .modes = &osd101t2045_53ts_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 217, + .height = 136, + }, + .connector_type = DRM_MODE_CONNECTOR_DSI, + }, + .flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_MODE_NO_EOT_PACKET, + .format = MIPI_DSI_FMT_RGB888, + .lanes = 4, +}; + static const struct of_device_id dsi_of_match[] = { { .compatible = "auo,b080uan01", @@ -2950,15 +5785,18 @@ static const struct of_device_id dsi_of_match[] = { .compatible = "boe,tv080wum-nl0", .data = &boe_tv080wum_nl0 }, { - .compatible = "lg,ld070wx3-sl01", - .data = &lg_ld070wx3_sl01 - }, { .compatible = "lg,lh500wx1-sd03", .data = &lg_lh500wx1_sd03 }, { .compatible = "panasonic,vvx10f004b00", .data = &panasonic_vvx10f004b00 }, { + .compatible = "lg,acx467akm-7", + .data = &lg_acx467akm_7 + }, { + .compatible = "osddisplays,osd101t2045-53ts", + .data = &osd101t2045_53ts + }, { /* sentinel */ } }; @@ -2967,27 +5805,29 @@ MODULE_DEVICE_TABLE(of, dsi_of_match); static int panel_simple_dsi_probe(struct mipi_dsi_device *dsi) { const struct panel_desc_dsi *desc; - const struct of_device_id *id; + struct panel_simple *panel; int err; - id = of_match_node(dsi_of_match, dsi->dev.of_node); - if (!id) - return -ENODEV; - - desc = id->data; - - err = panel_simple_probe(&dsi->dev, &desc->desc); - if (err < 0) - return err; + panel = panel_simple_probe(&dsi->dev); + if (IS_ERR(panel)) + return PTR_ERR(panel); + desc = container_of(panel->desc, struct panel_desc_dsi, desc); dsi->mode_flags = desc->flags; dsi->format = desc->format; dsi->lanes = desc->lanes; - return mipi_dsi_attach(dsi); + err = mipi_dsi_attach(dsi); + if (err) { + struct panel_simple *panel = mipi_dsi_get_drvdata(dsi); + + drm_panel_remove(&panel->base); + } + + return err; } -static int panel_simple_dsi_remove(struct mipi_dsi_device *dsi) +static void panel_simple_dsi_remove(struct mipi_dsi_device *dsi) { int err; @@ -2995,7 +5835,7 @@ static int panel_simple_dsi_remove(struct mipi_dsi_device *dsi) if (err < 0) dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", err); - return panel_simple_remove(&dsi->dev); + panel_simple_remove(&dsi->dev); } static void panel_simple_dsi_shutdown(struct mipi_dsi_device *dsi) @@ -3007,6 +5847,7 @@ static struct mipi_dsi_driver panel_simple_dsi_driver = { .driver = { .name = "panel-simple-dsi", .of_match_table = dsi_of_match, + .pm = &panel_simple_pm_ops, }, .probe = panel_simple_dsi_probe, .remove = panel_simple_dsi_remove, @@ -3024,10 +5865,15 @@ static int __init panel_simple_init(void) if (IS_ENABLED(CONFIG_DRM_MIPI_DSI)) { err = mipi_dsi_driver_register(&panel_simple_dsi_driver); if (err < 0) - return err; + goto err_did_platform_register; } return 0; + +err_did_platform_register: + platform_driver_unregister(&panel_simple_platform_driver); + + return err; } module_init(panel_simple_init); diff --git a/drivers/gpu/drm/panel/panel-sitronix-st7701.c b/drivers/gpu/drm/panel/panel-sitronix-st7701.c new file mode 100644 index 000000000000..2f79ec4a2063 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-sitronix-st7701.c @@ -0,0 +1,1462 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2019, Amarula Solutions. + * Author: Jagan Teki <jagan@amarulasolutions.com> + */ + +#include <drm/drm_mipi_dbi.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +#include <linux/bitfield.h> +#include <linux/gpio/consumer.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regulator/consumer.h> +#include <linux/spi/spi.h> + +#include <video/mipi_display.h> + +/* Command2 BKx selection command */ +#define ST7701_CMD2BKX_SEL 0xFF +#define ST7701_CMD1 0 +#define ST7701_CMD2 BIT(4) +#define ST7701_CMD2BK_MASK GENMASK(3, 0) + +/* Command2, BK0 commands */ +#define ST7701_CMD2_BK0_PVGAMCTRL 0xB0 /* Positive Voltage Gamma Control */ +#define ST7701_CMD2_BK0_NVGAMCTRL 0xB1 /* Negative Voltage Gamma Control */ +#define ST7701_CMD2_BK0_LNESET 0xC0 /* Display Line setting */ +#define ST7701_CMD2_BK0_PORCTRL 0xC1 /* Porch control */ +#define ST7701_CMD2_BK0_INVSEL 0xC2 /* Inversion selection, Frame Rate Control */ + +/* Command2, BK1 commands */ +#define ST7701_CMD2_BK1_VRHS 0xB0 /* Vop amplitude setting */ +#define ST7701_CMD2_BK1_VCOM 0xB1 /* VCOM amplitude setting */ +#define ST7701_CMD2_BK1_VGHSS 0xB2 /* VGH Voltage setting */ +#define ST7701_CMD2_BK1_TESTCMD 0xB3 /* TEST Command Setting */ +#define ST7701_CMD2_BK1_VGLS 0xB5 /* VGL Voltage setting */ +#define ST7701_CMD2_BK1_PWCTLR1 0xB7 /* Power Control 1 */ +#define ST7701_CMD2_BK1_PWCTLR2 0xB8 /* Power Control 2 */ +#define ST7701_CMD2_BK1_SPD1 0xC1 /* Source pre_drive timing set1 */ +#define ST7701_CMD2_BK1_SPD2 0xC2 /* Source EQ2 Setting */ +#define ST7701_CMD2_BK1_MIPISET1 0xD0 /* MIPI Setting 1 */ + +/* Command2, BK0 bytes */ +#define ST7701_CMD2_BK0_GAMCTRL_AJ_MASK GENMASK(7, 6) +#define ST7701_CMD2_BK0_GAMCTRL_VC0_MASK GENMASK(3, 0) +#define ST7701_CMD2_BK0_GAMCTRL_VC4_MASK GENMASK(5, 0) +#define ST7701_CMD2_BK0_GAMCTRL_VC8_MASK GENMASK(5, 0) +#define ST7701_CMD2_BK0_GAMCTRL_VC16_MASK GENMASK(4, 0) +#define ST7701_CMD2_BK0_GAMCTRL_VC24_MASK GENMASK(4, 0) +#define ST7701_CMD2_BK0_GAMCTRL_VC52_MASK GENMASK(3, 0) +#define ST7701_CMD2_BK0_GAMCTRL_VC80_MASK GENMASK(5, 0) +#define ST7701_CMD2_BK0_GAMCTRL_VC108_MASK GENMASK(3, 0) +#define ST7701_CMD2_BK0_GAMCTRL_VC147_MASK GENMASK(3, 0) +#define ST7701_CMD2_BK0_GAMCTRL_VC175_MASK GENMASK(5, 0) +#define ST7701_CMD2_BK0_GAMCTRL_VC203_MASK GENMASK(3, 0) +#define ST7701_CMD2_BK0_GAMCTRL_VC231_MASK GENMASK(4, 0) +#define ST7701_CMD2_BK0_GAMCTRL_VC239_MASK GENMASK(4, 0) +#define ST7701_CMD2_BK0_GAMCTRL_VC247_MASK GENMASK(5, 0) +#define ST7701_CMD2_BK0_GAMCTRL_VC251_MASK GENMASK(5, 0) +#define ST7701_CMD2_BK0_GAMCTRL_VC255_MASK GENMASK(4, 0) +#define ST7701_CMD2_BK0_LNESET_LINE_MASK GENMASK(6, 0) +#define ST7701_CMD2_BK0_LNESET_LDE_EN BIT(7) +#define ST7701_CMD2_BK0_LNESET_LINEDELTA GENMASK(1, 0) +#define ST7701_CMD2_BK0_PORCTRL_VBP_MASK GENMASK(7, 0) +#define ST7701_CMD2_BK0_PORCTRL_VFP_MASK GENMASK(7, 0) +#define ST7701_CMD2_BK0_INVSEL_ONES_MASK GENMASK(5, 4) +#define ST7701_CMD2_BK0_INVSEL_NLINV_MASK GENMASK(2, 0) +#define ST7701_CMD2_BK0_INVSEL_RTNI_MASK GENMASK(4, 0) + +/* Command2, BK1 bytes */ +#define ST7701_CMD2_BK1_VRHA_MASK GENMASK(7, 0) +#define ST7701_CMD2_BK1_VCOM_MASK GENMASK(7, 0) +#define ST7701_CMD2_BK1_VGHSS_MASK GENMASK(3, 0) +#define ST7701_CMD2_BK1_TESTCMD_VAL BIT(7) +#define ST7701_CMD2_BK1_VGLS_ONES BIT(6) +#define ST7701_CMD2_BK1_VGLS_MASK GENMASK(3, 0) +#define ST7701_CMD2_BK1_PWRCTRL1_AP_MASK GENMASK(7, 6) +#define ST7701_CMD2_BK1_PWRCTRL1_APIS_MASK GENMASK(3, 2) +#define ST7701_CMD2_BK1_PWRCTRL1_APOS_MASK GENMASK(1, 0) +#define ST7701_CMD2_BK1_PWRCTRL2_AVDD_MASK GENMASK(5, 4) +#define ST7701_CMD2_BK1_PWRCTRL2_AVCL_MASK GENMASK(1, 0) +#define ST7701_CMD2_BK1_SPD1_ONES_MASK GENMASK(6, 4) +#define ST7701_CMD2_BK1_SPD1_T2D_MASK GENMASK(3, 0) +#define ST7701_CMD2_BK1_SPD2_ONES_MASK GENMASK(6, 4) +#define ST7701_CMD2_BK1_SPD2_T3D_MASK GENMASK(3, 0) +#define ST7701_CMD2_BK1_MIPISET1_ONES BIT(7) +#define ST7701_CMD2_BK1_MIPISET1_EOT_EN BIT(3) + +#define CFIELD_PREP(_mask, _val) \ + (((typeof(_mask))(_val) << (__builtin_ffsll(_mask) - 1)) & (_mask)) + +enum op_bias { + OP_BIAS_OFF = 0, + OP_BIAS_MIN, + OP_BIAS_MIDDLE, + OP_BIAS_MAX +}; + +struct st7701; + +struct st7701_panel_desc { + const struct drm_display_mode *mode; + unsigned int lanes; + enum mipi_dsi_pixel_format format; + unsigned int panel_sleep_delay; + + /* TFT matrix driver configuration, panel specific. */ + const u8 pv_gamma[16]; /* Positive voltage gamma control */ + const u8 nv_gamma[16]; /* Negative voltage gamma control */ + const u8 nlinv; /* Inversion selection */ + const u32 vop_uv; /* Vop in uV */ + const u32 vcom_uv; /* Vcom in uV */ + const u16 vgh_mv; /* Vgh in mV */ + const s16 vgl_mv; /* Vgl in mV */ + const u16 avdd_mv; /* Avdd in mV */ + const s16 avcl_mv; /* Avcl in mV */ + const enum op_bias gamma_op_bias; + const enum op_bias input_op_bias; + const enum op_bias output_op_bias; + const u16 t2d_ns; /* T2D in ns */ + const u16 t3d_ns; /* T3D in ns */ + const bool eot_en; + + /* GIP sequence, fully custom and undocumented. */ + void (*gip_sequence)(struct st7701 *st7701); +}; + +struct st7701 { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct mipi_dbi dbi; + const struct st7701_panel_desc *desc; + + struct regulator_bulk_data supplies[2]; + struct gpio_desc *reset; + unsigned int sleep_delay; + enum drm_panel_orientation orientation; + + int (*write_command)(struct st7701 *st7701, u8 cmd, const u8 *seq, + size_t len); +}; + +static inline struct st7701 *panel_to_st7701(struct drm_panel *panel) +{ + return container_of(panel, struct st7701, panel); +} + +static int st7701_dsi_write(struct st7701 *st7701, u8 cmd, const u8 *seq, + size_t len) +{ + return mipi_dsi_dcs_write(st7701->dsi, cmd, seq, len); +} + +static int st7701_dbi_write(struct st7701 *st7701, u8 cmd, const u8 *seq, + size_t len) +{ + return mipi_dbi_command_stackbuf(&st7701->dbi, cmd, seq, len); +} + +#define ST7701_WRITE(st7701, cmd, seq...) \ + { \ + const u8 d[] = { seq }; \ + st7701->write_command(st7701, cmd, d, ARRAY_SIZE(d)); \ + } + +static u8 st7701_vgls_map(struct st7701 *st7701) +{ + const struct st7701_panel_desc *desc = st7701->desc; + struct { + s32 vgl; + u8 val; + } map[16] = { + { -7060, 0x0 }, { -7470, 0x1 }, + { -7910, 0x2 }, { -8140, 0x3 }, + { -8650, 0x4 }, { -8920, 0x5 }, + { -9210, 0x6 }, { -9510, 0x7 }, + { -9830, 0x8 }, { -10170, 0x9 }, + { -10530, 0xa }, { -10910, 0xb }, + { -11310, 0xc }, { -11730, 0xd }, + { -12200, 0xe }, { -12690, 0xf } + }; + int i; + + for (i = 0; i < ARRAY_SIZE(map); i++) + if (desc->vgl_mv == map[i].vgl) + return map[i].val; + + return 0; +} + +static void st7701_switch_cmd_bkx(struct st7701 *st7701, bool cmd2, u8 bkx) +{ + u8 val; + + if (cmd2) + val = ST7701_CMD2 | FIELD_PREP(ST7701_CMD2BK_MASK, bkx); + else + val = ST7701_CMD1; + + ST7701_WRITE(st7701, ST7701_CMD2BKX_SEL, 0x77, 0x01, 0x00, 0x00, val); +} + +static void st7701_init_sequence(struct st7701 *st7701) +{ + const struct st7701_panel_desc *desc = st7701->desc; + const struct drm_display_mode *mode = desc->mode; + const u8 linecount8 = mode->vdisplay / 8; + const u8 linecountrem2 = (mode->vdisplay % 8) / 2; + + ST7701_WRITE(st7701, MIPI_DCS_SOFT_RESET, 0x00); + + /* We need to wait 5ms before sending new commands */ + msleep(5); + + ST7701_WRITE(st7701, MIPI_DCS_EXIT_SLEEP_MODE, 0x00); + + msleep(st7701->sleep_delay); + + /* Command2, BK0 */ + st7701_switch_cmd_bkx(st7701, true, 0); + + st7701->write_command(st7701, ST7701_CMD2_BK0_PVGAMCTRL, desc->pv_gamma, + ARRAY_SIZE(desc->pv_gamma)); + st7701->write_command(st7701, ST7701_CMD2_BK0_NVGAMCTRL, desc->nv_gamma, + ARRAY_SIZE(desc->nv_gamma)); + /* + * Vertical line count configuration: + * Line[6:0]: select number of vertical lines of the TFT matrix in + * multiples of 8 lines + * LDE_EN: enable sub-8-line granularity line count + * Line_delta[1:0]: add 0/2/4/6 extra lines to line count selected + * using Line[6:0] + * + * Total number of vertical lines: + * LN = ((Line[6:0] + 1) * 8) + (LDE_EN ? Line_delta[1:0] * 2 : 0) + */ + ST7701_WRITE(st7701, ST7701_CMD2_BK0_LNESET, + FIELD_PREP(ST7701_CMD2_BK0_LNESET_LINE_MASK, linecount8 - 1) | + (linecountrem2 ? ST7701_CMD2_BK0_LNESET_LDE_EN : 0), + FIELD_PREP(ST7701_CMD2_BK0_LNESET_LINEDELTA, linecountrem2)); + ST7701_WRITE(st7701, ST7701_CMD2_BK0_PORCTRL, + FIELD_PREP(ST7701_CMD2_BK0_PORCTRL_VBP_MASK, + mode->vtotal - mode->vsync_end), + FIELD_PREP(ST7701_CMD2_BK0_PORCTRL_VFP_MASK, + mode->vsync_start - mode->vdisplay)); + /* + * Horizontal pixel count configuration: + * PCLK = 512 + (RTNI[4:0] * 16) + * The PCLK is number of pixel clock per line, which matches + * mode htotal. The minimum is 512 PCLK. + */ + ST7701_WRITE(st7701, ST7701_CMD2_BK0_INVSEL, + ST7701_CMD2_BK0_INVSEL_ONES_MASK | + FIELD_PREP(ST7701_CMD2_BK0_INVSEL_NLINV_MASK, desc->nlinv), + FIELD_PREP(ST7701_CMD2_BK0_INVSEL_RTNI_MASK, + (clamp((u32)mode->htotal, 512U, 1008U) - 512) / 16)); + + /* Command2, BK1 */ + st7701_switch_cmd_bkx(st7701, true, 1); + + /* Vop = 3.5375V + (VRHA[7:0] * 0.0125V) */ + ST7701_WRITE(st7701, ST7701_CMD2_BK1_VRHS, + FIELD_PREP(ST7701_CMD2_BK1_VRHA_MASK, + DIV_ROUND_CLOSEST(desc->vop_uv - 3537500, 12500))); + + /* Vcom = 0.1V + (VCOM[7:0] * 0.0125V) */ + ST7701_WRITE(st7701, ST7701_CMD2_BK1_VCOM, + FIELD_PREP(ST7701_CMD2_BK1_VCOM_MASK, + DIV_ROUND_CLOSEST(desc->vcom_uv - 100000, 12500))); + + /* Vgh = 11.5V + (VGHSS[7:0] * 0.5V) */ + ST7701_WRITE(st7701, ST7701_CMD2_BK1_VGHSS, + FIELD_PREP(ST7701_CMD2_BK1_VGHSS_MASK, + DIV_ROUND_CLOSEST(clamp(desc->vgh_mv, + (u16)11500, + (u16)17000) - 11500, + 500))); + + ST7701_WRITE(st7701, ST7701_CMD2_BK1_TESTCMD, ST7701_CMD2_BK1_TESTCMD_VAL); + + /* Vgl is non-linear */ + ST7701_WRITE(st7701, ST7701_CMD2_BK1_VGLS, + ST7701_CMD2_BK1_VGLS_ONES | + FIELD_PREP(ST7701_CMD2_BK1_VGLS_MASK, st7701_vgls_map(st7701))); + + ST7701_WRITE(st7701, ST7701_CMD2_BK1_PWCTLR1, + FIELD_PREP(ST7701_CMD2_BK1_PWRCTRL1_AP_MASK, + desc->gamma_op_bias) | + FIELD_PREP(ST7701_CMD2_BK1_PWRCTRL1_APIS_MASK, + desc->input_op_bias) | + FIELD_PREP(ST7701_CMD2_BK1_PWRCTRL1_APOS_MASK, + desc->output_op_bias)); + + /* Avdd = 6.2V + (AVDD[1:0] * 0.2V) , Avcl = -4.4V - (AVCL[1:0] * 0.2V) */ + ST7701_WRITE(st7701, ST7701_CMD2_BK1_PWCTLR2, + FIELD_PREP(ST7701_CMD2_BK1_PWRCTRL2_AVDD_MASK, + DIV_ROUND_CLOSEST(desc->avdd_mv - 6200, 200)) | + FIELD_PREP(ST7701_CMD2_BK1_PWRCTRL2_AVCL_MASK, + DIV_ROUND_CLOSEST(-4400 - desc->avcl_mv, 200))); + + /* T2D = 0.2us * T2D[3:0] */ + ST7701_WRITE(st7701, ST7701_CMD2_BK1_SPD1, + ST7701_CMD2_BK1_SPD1_ONES_MASK | + FIELD_PREP(ST7701_CMD2_BK1_SPD1_T2D_MASK, + DIV_ROUND_CLOSEST(desc->t2d_ns, 200))); + + /* T3D = 4us + (0.8us * T3D[3:0]) */ + ST7701_WRITE(st7701, ST7701_CMD2_BK1_SPD2, + ST7701_CMD2_BK1_SPD2_ONES_MASK | + FIELD_PREP(ST7701_CMD2_BK1_SPD2_T3D_MASK, + DIV_ROUND_CLOSEST(desc->t3d_ns - 4000, 800))); + + ST7701_WRITE(st7701, ST7701_CMD2_BK1_MIPISET1, + ST7701_CMD2_BK1_MIPISET1_ONES | + (desc->eot_en ? ST7701_CMD2_BK1_MIPISET1_EOT_EN : 0)); +} + +static void ts8550b_gip_sequence(struct st7701 *st7701) +{ + /** + * ST7701_SPEC_V1.2 is unable to provide enough information above this + * specific command sequence, so grab the same from vendor BSP driver. + */ + ST7701_WRITE(st7701, 0xE0, 0x00, 0x00, 0x02); + ST7701_WRITE(st7701, 0xE1, 0x0B, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0E, + 0x00, 0x00, 0x44, 0x44); + ST7701_WRITE(st7701, 0xE2, 0x33, 0x33, 0x44, 0x44, 0x64, 0x00, 0x66, + 0x00, 0x65, 0x00, 0x67, 0x00, 0x00); + ST7701_WRITE(st7701, 0xE3, 0x00, 0x00, 0x33, 0x33); + ST7701_WRITE(st7701, 0xE4, 0x44, 0x44); + ST7701_WRITE(st7701, 0xE5, 0x0C, 0x78, 0x3C, 0xA0, 0x0E, 0x78, 0x3C, + 0xA0, 0x10, 0x78, 0x3C, 0xA0, 0x12, 0x78, 0x3C, 0xA0); + ST7701_WRITE(st7701, 0xE6, 0x00, 0x00, 0x33, 0x33); + ST7701_WRITE(st7701, 0xE7, 0x44, 0x44); + ST7701_WRITE(st7701, 0xE8, 0x0D, 0x78, 0x3C, 0xA0, 0x0F, 0x78, 0x3C, + 0xA0, 0x11, 0x78, 0x3C, 0xA0, 0x13, 0x78, 0x3C, 0xA0); + ST7701_WRITE(st7701, 0xEB, 0x02, 0x02, 0x39, 0x39, 0xEE, 0x44, 0x00); + ST7701_WRITE(st7701, 0xEC, 0x00, 0x00); + ST7701_WRITE(st7701, 0xED, 0xFF, 0xF1, 0x04, 0x56, 0x72, 0x3F, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF3, 0x27, 0x65, 0x40, 0x1F, 0xFF); +} + +static void dmt028vghmcmi_1a_gip_sequence(struct st7701 *st7701) +{ + ST7701_WRITE(st7701, 0xEE, 0x42); + ST7701_WRITE(st7701, 0xE0, 0x00, 0x00, 0x02); + + ST7701_WRITE(st7701, 0xE1, + 0x04, 0xA0, 0x06, 0xA0, + 0x05, 0xA0, 0x07, 0xA0, + 0x00, 0x44, 0x44); + ST7701_WRITE(st7701, 0xE2, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00); + ST7701_WRITE(st7701, 0xE3, + 0x00, 0x00, 0x22, 0x22); + ST7701_WRITE(st7701, 0xE4, 0x44, 0x44); + ST7701_WRITE(st7701, 0xE5, + 0x0C, 0x90, 0xA0, 0xA0, + 0x0E, 0x92, 0xA0, 0xA0, + 0x08, 0x8C, 0xA0, 0xA0, + 0x0A, 0x8E, 0xA0, 0xA0); + ST7701_WRITE(st7701, 0xE6, + 0x00, 0x00, 0x22, 0x22); + ST7701_WRITE(st7701, 0xE7, 0x44, 0x44); + ST7701_WRITE(st7701, 0xE8, + 0x0D, 0x91, 0xA0, 0xA0, + 0x0F, 0x93, 0xA0, 0xA0, + 0x09, 0x8D, 0xA0, 0xA0, + 0x0B, 0x8F, 0xA0, 0xA0); + ST7701_WRITE(st7701, 0xEB, + 0x00, 0x00, 0xE4, 0xE4, + 0x44, 0x00, 0x00); + ST7701_WRITE(st7701, 0xED, + 0xFF, 0xF5, 0x47, 0x6F, + 0x0B, 0xA1, 0xAB, 0xFF, + 0xFF, 0xBA, 0x1A, 0xB0, + 0xF6, 0x74, 0x5F, 0xFF); + ST7701_WRITE(st7701, 0xEF, + 0x08, 0x08, 0x08, 0x40, + 0x3F, 0x64); + + st7701_switch_cmd_bkx(st7701, false, 0); + + st7701_switch_cmd_bkx(st7701, true, 3); + ST7701_WRITE(st7701, 0xE6, 0x7C); + ST7701_WRITE(st7701, 0xE8, 0x00, 0x0E); + + st7701_switch_cmd_bkx(st7701, false, 0); + ST7701_WRITE(st7701, 0x11); + msleep(120); + + st7701_switch_cmd_bkx(st7701, true, 3); + ST7701_WRITE(st7701, 0xE8, 0x00, 0x0C); + msleep(10); + ST7701_WRITE(st7701, 0xE8, 0x00, 0x00); + + st7701_switch_cmd_bkx(st7701, false, 0); + ST7701_WRITE(st7701, 0x11); + msleep(120); + ST7701_WRITE(st7701, 0xE8, 0x00, 0x00); + + st7701_switch_cmd_bkx(st7701, false, 0); + + ST7701_WRITE(st7701, 0x3A, 0x70); +} + +static void kd50t048a_gip_sequence(struct st7701 *st7701) +{ + /** + * ST7701_SPEC_V1.2 is unable to provide enough information above this + * specific command sequence, so grab the same from vendor BSP driver. + */ + ST7701_WRITE(st7701, 0xE0, 0x00, 0x00, 0x02); + ST7701_WRITE(st7701, 0xE1, 0x08, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x09, + 0x00, 0x00, 0x33, 0x33); + ST7701_WRITE(st7701, 0xE2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + ST7701_WRITE(st7701, 0xE3, 0x00, 0x00, 0x33, 0x33); + ST7701_WRITE(st7701, 0xE4, 0x44, 0x44); + ST7701_WRITE(st7701, 0xE5, 0x0E, 0x60, 0xA0, 0xA0, 0x10, 0x60, 0xA0, + 0xA0, 0x0A, 0x60, 0xA0, 0xA0, 0x0C, 0x60, 0xA0, 0xA0); + ST7701_WRITE(st7701, 0xE6, 0x00, 0x00, 0x33, 0x33); + ST7701_WRITE(st7701, 0xE7, 0x44, 0x44); + ST7701_WRITE(st7701, 0xE8, 0x0D, 0x60, 0xA0, 0xA0, 0x0F, 0x60, 0xA0, + 0xA0, 0x09, 0x60, 0xA0, 0xA0, 0x0B, 0x60, 0xA0, 0xA0); + ST7701_WRITE(st7701, 0xEB, 0x02, 0x01, 0xE4, 0xE4, 0x44, 0x00, 0x40); + ST7701_WRITE(st7701, 0xEC, 0x02, 0x01); + ST7701_WRITE(st7701, 0xED, 0xAB, 0x89, 0x76, 0x54, 0x01, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x45, 0x67, 0x98, 0xBA); +} + +static void rg_arc_gip_sequence(struct st7701 *st7701) +{ + st7701_switch_cmd_bkx(st7701, true, 3); + ST7701_WRITE(st7701, 0xEF, 0x08); + st7701_switch_cmd_bkx(st7701, true, 0); + ST7701_WRITE(st7701, 0xC7, 0x04); + ST7701_WRITE(st7701, 0xCC, 0x38); + st7701_switch_cmd_bkx(st7701, true, 1); + ST7701_WRITE(st7701, 0xB9, 0x10); + ST7701_WRITE(st7701, 0xBC, 0x03); + ST7701_WRITE(st7701, 0xC0, 0x89); + ST7701_WRITE(st7701, 0xE0, 0x00, 0x00, 0x02); + ST7701_WRITE(st7701, 0xE1, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, + 0x00, 0x00, 0x20, 0x20); + ST7701_WRITE(st7701, 0xE2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + ST7701_WRITE(st7701, 0xE3, 0x00, 0x00, 0x33, 0x00); + ST7701_WRITE(st7701, 0xE4, 0x22, 0x00); + ST7701_WRITE(st7701, 0xE5, 0x04, 0x5C, 0xA0, 0xA0, 0x06, 0x5C, 0xA0, + 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + ST7701_WRITE(st7701, 0xE6, 0x00, 0x00, 0x33, 0x00); + ST7701_WRITE(st7701, 0xE7, 0x22, 0x00); + ST7701_WRITE(st7701, 0xE8, 0x05, 0x5C, 0xA0, 0xA0, 0x07, 0x5C, 0xA0, + 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + ST7701_WRITE(st7701, 0xEB, 0x02, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00); + ST7701_WRITE(st7701, 0xEC, 0x00, 0x00); + ST7701_WRITE(st7701, 0xED, 0xFA, 0x45, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB0, 0x54, 0xAF); + ST7701_WRITE(st7701, 0xEF, 0x08, 0x08, 0x08, 0x45, 0x3F, 0x54); + st7701_switch_cmd_bkx(st7701, false, 0); + ST7701_WRITE(st7701, MIPI_DCS_SET_ADDRESS_MODE, 0x17); + ST7701_WRITE(st7701, MIPI_DCS_SET_PIXEL_FORMAT, 0x77); + ST7701_WRITE(st7701, MIPI_DCS_EXIT_SLEEP_MODE, 0x00); + msleep(120); +} + +static void rg28xx_gip_sequence(struct st7701 *st7701) +{ + st7701_switch_cmd_bkx(st7701, true, 3); + ST7701_WRITE(st7701, 0xEF, 0x08); + + st7701_switch_cmd_bkx(st7701, true, 0); + ST7701_WRITE(st7701, 0xC3, 0x02, 0x10, 0x02); + ST7701_WRITE(st7701, 0xC7, 0x04); + ST7701_WRITE(st7701, 0xCC, 0x10); + + st7701_switch_cmd_bkx(st7701, true, 1); + ST7701_WRITE(st7701, 0xEE, 0x42); + ST7701_WRITE(st7701, 0xE0, 0x00, 0x00, 0x02); + + ST7701_WRITE(st7701, 0xE1, 0x04, 0xA0, 0x06, 0xA0, 0x05, 0xA0, 0x07, 0xA0, + 0x00, 0x44, 0x44); + ST7701_WRITE(st7701, 0xE2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00); + ST7701_WRITE(st7701, 0xE3, 0x00, 0x00, 0x22, 0x22); + ST7701_WRITE(st7701, 0xE4, 0x44, 0x44); + ST7701_WRITE(st7701, 0xE5, 0x0C, 0x90, 0xA0, 0xA0, 0x0E, 0x92, 0xA0, 0xA0, + 0x08, 0x8C, 0xA0, 0xA0, 0x0A, 0x8E, 0xA0, 0xA0); + ST7701_WRITE(st7701, 0xE6, 0x00, 0x00, 0x22, 0x22); + ST7701_WRITE(st7701, 0xE7, 0x44, 0x44); + ST7701_WRITE(st7701, 0xE8, 0x0D, 0x91, 0xA0, 0xA0, 0x0F, 0x93, 0xA0, 0xA0, + 0x09, 0x8D, 0xA0, 0xA0, 0x0B, 0x8F, 0xA0, 0xA0); + ST7701_WRITE(st7701, 0xEB, 0x00, 0x00, 0xE4, 0xE4, 0x44, 0x00, 0x40); + ST7701_WRITE(st7701, 0xED, 0xFF, 0xF5, 0x47, 0x6F, 0x0B, 0xA1, 0xBA, 0xFF, + 0xFF, 0xAB, 0x1A, 0xB0, 0xF6, 0x74, 0x5F, 0xFF); + ST7701_WRITE(st7701, 0xEF, 0x08, 0x08, 0x08, 0x45, 0x3F, 0x54); + + st7701_switch_cmd_bkx(st7701, false, 0); + + st7701_switch_cmd_bkx(st7701, true, 3); + ST7701_WRITE(st7701, 0xE6, 0x16); + ST7701_WRITE(st7701, 0xE8, 0x00, 0x0E); + + st7701_switch_cmd_bkx(st7701, false, 0); + ST7701_WRITE(st7701, MIPI_DCS_SET_ADDRESS_MODE, 0x10); + ST7701_WRITE(st7701, MIPI_DCS_EXIT_SLEEP_MODE); + msleep(120); + + st7701_switch_cmd_bkx(st7701, true, 3); + ST7701_WRITE(st7701, 0xE8, 0x00, 0x0C); + msleep(10); + ST7701_WRITE(st7701, 0xE8, 0x00, 0x00); + st7701_switch_cmd_bkx(st7701, false, 0); +} + +static void wf40eswaa6mnn0_gip_sequence(struct st7701 *st7701) +{ + ST7701_WRITE(st7701, 0xE0, 0x00, 0x28, 0x02); + ST7701_WRITE(st7701, 0xE1, 0x08, 0xA0, 0x00, 0x00, 0x07, 0xA0, 0x00, + 0x00, 0x00, 0x44, 0x44); + ST7701_WRITE(st7701, 0xE2, 0x11, 0x11, 0x44, 0x44, 0xED, 0xA0, 0x00, + 0x00, 0xEC, 0xA0, 0x00, 0x00); + ST7701_WRITE(st7701, 0xE3, 0x00, 0x00, 0x11, 0x11); + ST7701_WRITE(st7701, 0xE4, 0x44, 0x44); + ST7701_WRITE(st7701, 0xE5, 0x0A, 0xE9, 0xD8, 0xA0, 0x0C, 0xEB, 0xD8, + 0xA0, 0x0E, 0xED, 0xD8, 0xA0, 0x10, 0xEF, 0xD8, 0xA0); + ST7701_WRITE(st7701, 0xE6, 0x00, 0x00, 0x11, 0x11); + ST7701_WRITE(st7701, 0xE7, 0x44, 0x44); + ST7701_WRITE(st7701, 0xE8, 0x09, 0xE8, 0xD8, 0xA0, 0x0B, 0xEA, 0xD8, + 0xA0, 0x0D, 0xEC, 0xD8, 0xA0, 0x0F, 0xEE, 0xD8, 0xA0); + ST7701_WRITE(st7701, 0xEB, 0x00, 0x00, 0xE4, 0xE4, 0x88, 0x00, 0x40); + ST7701_WRITE(st7701, 0xEC, 0x3C, 0x00); + ST7701_WRITE(st7701, 0xED, 0xAB, 0x89, 0x76, 0x54, 0x02, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x45, 0x67, 0x98, 0xBA); + ST7701_WRITE(st7701, MIPI_DCS_SET_ADDRESS_MODE, 0); +} + +static int st7701_prepare(struct drm_panel *panel) +{ + struct st7701 *st7701 = panel_to_st7701(panel); + int ret; + + gpiod_set_value(st7701->reset, 0); + + ret = regulator_bulk_enable(ARRAY_SIZE(st7701->supplies), + st7701->supplies); + if (ret < 0) + return ret; + msleep(20); + + gpiod_set_value(st7701->reset, 1); + msleep(150); + + st7701_init_sequence(st7701); + + if (st7701->desc->gip_sequence) + st7701->desc->gip_sequence(st7701); + + /* Disable Command2 */ + st7701_switch_cmd_bkx(st7701, false, 0); + + return 0; +} + +static int st7701_enable(struct drm_panel *panel) +{ + struct st7701 *st7701 = panel_to_st7701(panel); + + ST7701_WRITE(st7701, MIPI_DCS_SET_DISPLAY_ON, 0x00); + + return 0; +} + +static int st7701_disable(struct drm_panel *panel) +{ + struct st7701 *st7701 = panel_to_st7701(panel); + + ST7701_WRITE(st7701, MIPI_DCS_SET_DISPLAY_OFF, 0x00); + + return 0; +} + +static int st7701_unprepare(struct drm_panel *panel) +{ + struct st7701 *st7701 = panel_to_st7701(panel); + + ST7701_WRITE(st7701, MIPI_DCS_ENTER_SLEEP_MODE, 0x00); + + msleep(st7701->sleep_delay); + + gpiod_set_value(st7701->reset, 0); + + /** + * During the Resetting period, the display will be blanked + * (The display is entering blanking sequence, which maximum + * time is 120 ms, when Reset Starts in Sleep Out –mode. The + * display remains the blank state in Sleep In –mode.) and + * then return to Default condition for Hardware Reset. + * + * So we need wait sleep_delay time to make sure reset completed. + */ + msleep(st7701->sleep_delay); + + regulator_bulk_disable(ARRAY_SIZE(st7701->supplies), st7701->supplies); + + return 0; +} + +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(connector->dev, desc_mode); + if (!mode) { + dev_err(panel->dev, "failed to add mode %ux%u@%u\n", + desc_mode->hdisplay, desc_mode->vdisplay, + drm_mode_vrefresh(desc_mode)); + return -ENOMEM; + } + + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + + connector->display_info.width_mm = desc_mode->width_mm; + connector->display_info.height_mm = desc_mode->height_mm; + + /* + * TODO: Remove once all drm drivers call + * drm_connector_set_orientation_from_panel() + */ + drm_connector_set_panel_orientation(connector, st7701->orientation); + + return 1; +} + +static enum drm_panel_orientation st7701_get_orientation(struct drm_panel *panel) +{ + struct st7701 *st7701 = panel_to_st7701(panel); + + return st7701->orientation; +} + +static const struct drm_panel_funcs st7701_funcs = { + .disable = st7701_disable, + .unprepare = st7701_unprepare, + .prepare = st7701_prepare, + .enable = st7701_enable, + .get_modes = st7701_get_modes, + .get_orientation = st7701_get_orientation, +}; + +static const struct drm_display_mode ts8550b_mode = { + .clock = 27500, + + .hdisplay = 480, + .hsync_start = 480 + 38, + .hsync_end = 480 + 38 + 12, + .htotal = 480 + 38 + 12 + 12, + + .vdisplay = 854, + .vsync_start = 854 + 18, + .vsync_end = 854 + 18 + 8, + .vtotal = 854 + 18 + 8 + 4, + + .width_mm = 69, + .height_mm = 139, + + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, +}; + +static const struct st7701_panel_desc ts8550b_desc = { + .mode = &ts8550b_mode, + .lanes = 2, + .format = MIPI_DSI_FMT_RGB888, + .panel_sleep_delay = 80, /* panel need extra 80ms for sleep out cmd */ + + .pv_gamma = { + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC0_MASK, 0), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC4_MASK, 0xe), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC8_MASK, 0x15), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC16_MASK, 0xf), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC24_MASK, 0x11), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC52_MASK, 0x8), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC80_MASK, 0x8), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC108_MASK, 0x8), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC147_MASK, 0x8), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC175_MASK, 0x23), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC203_MASK, 0x4), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC231_MASK, 0x13), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC239_MASK, 0x12), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC247_MASK, 0x2b), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC251_MASK, 0x34), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC255_MASK, 0x1f) + }, + .nv_gamma = { + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC0_MASK, 0), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC4_MASK, 0xe), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0x2) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC8_MASK, 0x15), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC16_MASK, 0xf), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC24_MASK, 0x13), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC52_MASK, 0x7), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC80_MASK, 0x9), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC108_MASK, 0x8), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC147_MASK, 0x8), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC175_MASK, 0x22), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC203_MASK, 0x4), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC231_MASK, 0x10), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC239_MASK, 0xe), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC247_MASK, 0x2c), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC251_MASK, 0x34), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC255_MASK, 0x1f) + }, + .nlinv = 7, + .vop_uv = 4400000, + .vcom_uv = 337500, + .vgh_mv = 15000, + .vgl_mv = -9510, + .avdd_mv = 6600, + .avcl_mv = -4400, + .gamma_op_bias = OP_BIAS_MAX, + .input_op_bias = OP_BIAS_MIN, + .output_op_bias = OP_BIAS_MIN, + .t2d_ns = 1600, + .t3d_ns = 10400, + .eot_en = true, + .gip_sequence = ts8550b_gip_sequence, +}; + +static const struct drm_display_mode dmt028vghmcmi_1a_mode = { + .clock = 22325, + + .hdisplay = 480, + .hsync_start = 480 + 40, + .hsync_end = 480 + 40 + 4, + .htotal = 480 + 40 + 4 + 20, + + .vdisplay = 640, + .vsync_start = 640 + 2, + .vsync_end = 640 + 2 + 40, + .vtotal = 640 + 2 + 40 + 16, + + .width_mm = 56, + .height_mm = 78, + + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, +}; + +static const struct st7701_panel_desc dmt028vghmcmi_1a_desc = { + .mode = &dmt028vghmcmi_1a_mode, + .lanes = 2, + .format = MIPI_DSI_FMT_RGB888, + .panel_sleep_delay = 5, /* panel need extra 5ms for sleep out cmd */ + + .pv_gamma = { + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC0_MASK, 0), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC4_MASK, 0x10), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC8_MASK, 0x17), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC16_MASK, 0xd), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC24_MASK, 0x11), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC52_MASK, 0x6), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC80_MASK, 0x5), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC108_MASK, 0x8), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC147_MASK, 0x7), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC175_MASK, 0x1f), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC203_MASK, 0x4), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC231_MASK, 0x11), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC239_MASK, 0xe), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC247_MASK, 0x29), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC251_MASK, 0x30), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC255_MASK, 0x1f) + }, + .nv_gamma = { + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC0_MASK, 0), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC4_MASK, 0xd), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC8_MASK, 0x14), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC16_MASK, 0xe), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC24_MASK, 0x11), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC52_MASK, 0x6), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC80_MASK, 0x4), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC108_MASK, 0x8), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC147_MASK, 0x8), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC175_MASK, 0x20), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC203_MASK, 0x5), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC231_MASK, 0x13), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC239_MASK, 0x13), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC247_MASK, 0x26), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC251_MASK, 0x30), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC255_MASK, 0x1f) + }, + .nlinv = 1, + .vop_uv = 4800000, + .vcom_uv = 1650000, + .vgh_mv = 15000, + .vgl_mv = -10170, + .avdd_mv = 6600, + .avcl_mv = -4400, + .gamma_op_bias = OP_BIAS_MIDDLE, + .input_op_bias = OP_BIAS_MIN, + .output_op_bias = OP_BIAS_MIN, + .t2d_ns = 1600, + .t3d_ns = 10400, + .eot_en = true, + .gip_sequence = dmt028vghmcmi_1a_gip_sequence, +}; + +static const struct drm_display_mode kd50t048a_mode = { + .clock = 27500, + + .hdisplay = 480, + .hsync_start = 480 + 2, + .hsync_end = 480 + 2 + 10, + .htotal = 480 + 2 + 10 + 2, + + .vdisplay = 854, + .vsync_start = 854 + 2, + .vsync_end = 854 + 2 + 2, + .vtotal = 854 + 2 + 2 + 17, + + .width_mm = 69, + .height_mm = 139, + + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, +}; + +static const struct st7701_panel_desc kd50t048a_desc = { + .mode = &kd50t048a_mode, + .lanes = 2, + .format = MIPI_DSI_FMT_RGB888, + .panel_sleep_delay = 0, + + .pv_gamma = { + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC0_MASK, 0), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC4_MASK, 0xd), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC8_MASK, 0x14), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC16_MASK, 0xd), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC24_MASK, 0x10), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC52_MASK, 0x5), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC80_MASK, 0x2), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC108_MASK, 0x8), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC147_MASK, 0x8), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC175_MASK, 0x1e), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC203_MASK, 0x5), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC231_MASK, 0x13), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC239_MASK, 0x11), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 2) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC247_MASK, 0x23), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC251_MASK, 0x29), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC255_MASK, 0x18) + }, + .nv_gamma = { + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC0_MASK, 0), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC4_MASK, 0xc), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC8_MASK, 0x14), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC16_MASK, 0xc), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC24_MASK, 0x10), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC52_MASK, 0x5), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC80_MASK, 0x3), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC108_MASK, 0x8), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC147_MASK, 0x7), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC175_MASK, 0x20), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC203_MASK, 0x5), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC231_MASK, 0x13), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC239_MASK, 0x11), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 2) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC247_MASK, 0x24), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC251_MASK, 0x29), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC255_MASK, 0x18) + }, + .nlinv = 1, + .vop_uv = 4887500, + .vcom_uv = 937500, + .vgh_mv = 15000, + .vgl_mv = -9510, + .avdd_mv = 6600, + .avcl_mv = -4400, + .gamma_op_bias = OP_BIAS_MIDDLE, + .input_op_bias = OP_BIAS_MIN, + .output_op_bias = OP_BIAS_MIN, + .t2d_ns = 1600, + .t3d_ns = 10400, + .eot_en = true, + .gip_sequence = kd50t048a_gip_sequence, +}; + +static const struct drm_display_mode rg_arc_mode = { + .clock = 25600, + + .hdisplay = 480, + .hsync_start = 480 + 60, + .hsync_end = 480 + 60 + 42, + .htotal = 480 + 60 + 42 + 60, + + .vdisplay = 640, + .vsync_start = 640 + 10, + .vsync_end = 640 + 10 + 4, + .vtotal = 640 + 10 + 4 + 16, + + .width_mm = 63, + .height_mm = 84, + + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, +}; + +static const struct st7701_panel_desc rg_arc_desc = { + .mode = &rg_arc_mode, + .lanes = 2, + .format = MIPI_DSI_FMT_RGB888, + .panel_sleep_delay = 80, + + .pv_gamma = { + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0x01) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC0_MASK, 0), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC4_MASK, 0x16), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC8_MASK, 0x1d), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC16_MASK, 0x0e), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC24_MASK, 0x12), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC52_MASK, 0x06), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC80_MASK, 0x0c), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC108_MASK, 0x0a), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC147_MASK, 0x09), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC175_MASK, 0x25), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC203_MASK, 0x00), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC231_MASK, 0x03), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC239_MASK, 0x00), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC247_MASK, 0x3f), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC251_MASK, 0x3f), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC255_MASK, 0x1c) + }, + .nv_gamma = { + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0x01) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC0_MASK, 0), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC4_MASK, 0x16), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC8_MASK, 0x1e), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC16_MASK, 0x0e), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC24_MASK, 0x11), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC52_MASK, 0x06), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC80_MASK, 0x0c), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC108_MASK, 0x08), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC147_MASK, 0x09), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC175_MASK, 0x26), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC203_MASK, 0x00), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC231_MASK, 0x15), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC239_MASK, 0x00), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC247_MASK, 0x3f), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC251_MASK, 0x3f), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC255_MASK, 0x1c) + }, + .nlinv = 0, + .vop_uv = 4500000, + .vcom_uv = 762500, + .vgh_mv = 15000, + .vgl_mv = -9510, + .avdd_mv = 6600, + .avcl_mv = -4400, + .gamma_op_bias = OP_BIAS_MIDDLE, + .input_op_bias = OP_BIAS_MIN, + .output_op_bias = OP_BIAS_MIN, + .t2d_ns = 1600, + .t3d_ns = 10400, + .eot_en = true, + .gip_sequence = rg_arc_gip_sequence, +}; + +static const struct drm_display_mode rg28xx_mode = { + .clock = 22325, + + .hdisplay = 480, + .hsync_start = 480 + 40, + .hsync_end = 480 + 40 + 4, + .htotal = 480 + 40 + 4 + 20, + + .vdisplay = 640, + .vsync_start = 640 + 2, + .vsync_end = 640 + 2 + 40, + .vtotal = 640 + 2 + 40 + 16, + + .width_mm = 44, + .height_mm = 58, + + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, +}; + +static const struct st7701_panel_desc rg28xx_desc = { + .mode = &rg28xx_mode, + + .panel_sleep_delay = 80, + + .pv_gamma = { + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC0_MASK, 0), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC4_MASK, 0x10), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC8_MASK, 0x17), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC16_MASK, 0xd), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC24_MASK, 0x11), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC52_MASK, 0x6), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC80_MASK, 0x5), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC108_MASK, 0x8), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC147_MASK, 0x7), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC175_MASK, 0x1f), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC203_MASK, 0x4), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC231_MASK, 0x11), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC239_MASK, 0xe), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC247_MASK, 0x29), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC251_MASK, 0x30), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC255_MASK, 0x1f) + }, + .nv_gamma = { + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC0_MASK, 0), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC4_MASK, 0xd), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC8_MASK, 0x14), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC16_MASK, 0xe), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC24_MASK, 0x11), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC52_MASK, 0x6), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC80_MASK, 0x4), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC108_MASK, 0x8), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC147_MASK, 0x8), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC175_MASK, 0x20), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC203_MASK, 0x5), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC231_MASK, 0x13), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC239_MASK, 0x13), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC247_MASK, 0x26), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC251_MASK, 0x30), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC255_MASK, 0x1f) + }, + .nlinv = 7, + .vop_uv = 4800000, + .vcom_uv = 1512500, + .vgh_mv = 15000, + .vgl_mv = -11730, + .avdd_mv = 6600, + .avcl_mv = -4400, + .gamma_op_bias = OP_BIAS_MIDDLE, + .input_op_bias = OP_BIAS_MIN, + .output_op_bias = OP_BIAS_MIN, + .t2d_ns = 1600, + .t3d_ns = 10400, + .eot_en = true, + .gip_sequence = rg28xx_gip_sequence, +}; + +static const struct drm_display_mode wf40eswaa6mnn0_mode = { + .clock = 18306, + + .hdisplay = 480, + .hsync_start = 480 + 2, + .hsync_end = 480 + 2 + 45, + .htotal = 480 + 2 + 45 + 13, + + .vdisplay = 480, + .vsync_start = 480 + 2, + .vsync_end = 480 + 2 + 70, + .vtotal = 480 + 2 + 70 + 13, + + .width_mm = 72, + .height_mm = 70, + + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, +}; + +static const struct st7701_panel_desc wf40eswaa6mnn0_desc = { + .mode = &wf40eswaa6mnn0_mode, + .lanes = 2, + .format = MIPI_DSI_FMT_RGB888, + .panel_sleep_delay = 0, + + .pv_gamma = { + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC0_MASK, 0x1), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC4_MASK, 0x08), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC8_MASK, 0x10), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC16_MASK, 0x0c), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC24_MASK, 0x10), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC52_MASK, 0x08), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC80_MASK, 0x10), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC108_MASK, 0x0c), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC147_MASK, 0x08), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC175_MASK, 0x22), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC203_MASK, 0x04), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC231_MASK, 0x14), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC239_MASK, 0x12), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC247_MASK, 0xb3), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC251_MASK, 0x3a), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC255_MASK, 0x1f) + }, + .nv_gamma = { + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC4_MASK, 0x13), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC4_MASK, 0x19), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC8_MASK, 0x1f), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC16_MASK, 0x0f), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC24_MASK, 0x14), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC52_MASK, 0x07), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC80_MASK, 0x07), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC108_MASK, 0x08), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC147_MASK, 0x07), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC175_MASK, 0x22), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC203_MASK, 0x02), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC231_MASK, 0xf), + + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC239_MASK, 0x0f), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC247_MASK, 0xa3), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC251_MASK, 0x29), + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) | + CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC255_MASK, 0x0d) + }, + .nlinv = 3, + .vop_uv = 4737500, + .vcom_uv = 662500, + .vgh_mv = 15000, + .vgl_mv = -10170, + .avdd_mv = 6600, + .avcl_mv = -4600, + .gamma_op_bias = OP_BIAS_MIDDLE, + .input_op_bias = OP_BIAS_MIDDLE, + .output_op_bias = OP_BIAS_MIN, + .t2d_ns = 1600, + .t3d_ns = 10400, + .eot_en = true, + .gip_sequence = wf40eswaa6mnn0_gip_sequence, +}; + +static void st7701_cleanup(void *data) +{ + struct st7701 *st7701 = (struct st7701 *)data; + + drm_panel_remove(&st7701->panel); + drm_panel_disable(&st7701->panel); + drm_panel_unprepare(&st7701->panel); +} + +static int st7701_probe(struct device *dev, int connector_type) +{ + const struct st7701_panel_desc *desc; + struct st7701 *st7701; + int ret; + + st7701 = devm_drm_panel_alloc(dev, struct st7701, panel, &st7701_funcs, + connector_type); + if (IS_ERR(st7701)) + return PTR_ERR(st7701); + + desc = of_device_get_match_data(dev); + if (!desc) + return -ENODEV; + + st7701->supplies[0].supply = "VCC"; + st7701->supplies[1].supply = "IOVCC"; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(st7701->supplies), + st7701->supplies); + if (ret < 0) + return ret; + + st7701->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(st7701->reset)) { + dev_err(dev, "Couldn't get our reset GPIO\n"); + return PTR_ERR(st7701->reset); + } + + ret = of_drm_get_panel_orientation(dev->of_node, &st7701->orientation); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get orientation\n"); + + st7701->panel.prepare_prev_first = true; + + /** + * Once sleep out has been issued, ST7701 IC required to wait 120ms + * before initiating new commands. + * + * On top of that some panels might need an extra delay to wait, so + * add panel specific delay for those cases. As now this panel specific + * delay information is referenced from those panel BSP driver, example + * ts8550b and there is no valid documentation for that. + */ + st7701->sleep_delay = 120 + desc->panel_sleep_delay; + + ret = drm_panel_of_backlight(&st7701->panel); + if (ret) + return ret; + + drm_panel_add(&st7701->panel); + + dev_set_drvdata(dev, st7701); + st7701->desc = desc; + + return devm_add_action_or_reset(dev, st7701_cleanup, st7701); +} + +static int st7701_dsi_probe(struct mipi_dsi_device *dsi) +{ + struct st7701 *st7701; + int err; + + err = st7701_probe(&dsi->dev, DRM_MODE_CONNECTOR_DSI); + if (err) + return err; + + st7701 = dev_get_drvdata(&dsi->dev); + st7701->dsi = dsi; + st7701->write_command = st7701_dsi_write; + + if (!st7701->desc->lanes) + return dev_err_probe(&dsi->dev, -EINVAL, "This panel is not for MIPI DSI\n"); + + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS; + dsi->format = st7701->desc->format; + dsi->lanes = st7701->desc->lanes; + + err = mipi_dsi_attach(dsi); + if (err) + return dev_err_probe(&dsi->dev, err, "Failed to init MIPI DSI\n"); + + return 0; +} + +static int st7701_spi_probe(struct spi_device *spi) +{ + struct st7701 *st7701; + struct gpio_desc *dc; + int err; + + err = st7701_probe(&spi->dev, DRM_MODE_CONNECTOR_DPI); + if (err) + return err; + + st7701 = dev_get_drvdata(&spi->dev); + st7701->write_command = st7701_dbi_write; + + dc = devm_gpiod_get_optional(&spi->dev, "dc", GPIOD_OUT_LOW); + if (IS_ERR(dc)) + return dev_err_probe(&spi->dev, PTR_ERR(dc), "Failed to get GPIO for D/CX\n"); + + err = mipi_dbi_spi_init(spi, &st7701->dbi, dc); + if (err) + return dev_err_probe(&spi->dev, err, "Failed to init MIPI DBI\n"); + st7701->dbi.read_commands = NULL; + + return 0; +} + +static void st7701_dsi_remove(struct mipi_dsi_device *dsi) +{ + mipi_dsi_detach(dsi); +} + +static const struct of_device_id st7701_dsi_of_match[] = { + { .compatible = "anbernic,rg-arc-panel", .data = &rg_arc_desc }, + { .compatible = "densitron,dmt028vghmcmi-1a", .data = &dmt028vghmcmi_1a_desc }, + { .compatible = "elida,kd50t048a", .data = &kd50t048a_desc }, + { .compatible = "techstar,ts8550b", .data = &ts8550b_desc }, + { .compatible = "winstar,wf40eswaa6mnn0", .data = &wf40eswaa6mnn0_desc }, + { } +}; +MODULE_DEVICE_TABLE(of, st7701_dsi_of_match); + +static const struct of_device_id st7701_spi_of_match[] = { + { .compatible = "anbernic,rg28xx-panel", .data = &rg28xx_desc }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, st7701_spi_of_match); + +static const struct spi_device_id st7701_spi_ids[] = { + { "rg28xx-panel" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(spi, st7701_spi_ids); + +static struct mipi_dsi_driver st7701_dsi_driver = { + .probe = st7701_dsi_probe, + .remove = st7701_dsi_remove, + .driver = { + .name = "st7701", + .of_match_table = st7701_dsi_of_match, + }, +}; + +static struct spi_driver st7701_spi_driver = { + .probe = st7701_spi_probe, + .id_table = st7701_spi_ids, + .driver = { + .name = "st7701", + .of_match_table = st7701_spi_of_match, + }, +}; + +static int __init st7701_driver_init(void) +{ + int err; + + if (IS_ENABLED(CONFIG_SPI)) { + err = spi_register_driver(&st7701_spi_driver); + if (err) + return err; + } + + if (IS_ENABLED(CONFIG_DRM_MIPI_DSI)) { + err = mipi_dsi_driver_register(&st7701_dsi_driver); + if (err) { + if (IS_ENABLED(CONFIG_SPI)) + spi_unregister_driver(&st7701_spi_driver); + return err; + } + } + + return 0; +} +module_init(st7701_driver_init); + +static void __exit st7701_driver_exit(void) +{ + if (IS_ENABLED(CONFIG_DRM_MIPI_DSI)) + mipi_dsi_driver_unregister(&st7701_dsi_driver); + + if (IS_ENABLED(CONFIG_SPI)) + spi_unregister_driver(&st7701_spi_driver); +} +module_exit(st7701_driver_exit); + +MODULE_AUTHOR("Jagan Teki <jagan@amarulasolutions.com>"); +MODULE_AUTHOR("Hironori KIKUCHI <kikuchan98@gmail.com>"); +MODULE_DESCRIPTION("Sitronix ST7701 LCD Panel Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-sitronix-st7703.c b/drivers/gpu/drm/panel/panel-sitronix-st7703.c new file mode 100644 index 000000000000..6c348fe28955 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-sitronix-st7703.c @@ -0,0 +1,940 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for panels based on Sitronix ST7703 controller, such as: + * + * - Rocktech jh057n00900 5.5" MIPI-DSI panel + * + * Copyright (C) Purism SPC 2019 + */ + +#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/of.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> + +#define DRV_NAME "panel-sitronix-st7703" + +/* Manufacturer specific Commands send via DSI */ +#define ST7703_CMD_ALL_PIXEL_OFF 0x22 +#define ST7703_CMD_ALL_PIXEL_ON 0x23 +#define ST7703_CMD_SETAPID 0xB1 +#define ST7703_CMD_SETDISP 0xB2 +#define ST7703_CMD_SETRGBIF 0xB3 +#define ST7703_CMD_SETCYC 0xB4 +#define ST7703_CMD_SETBGP 0xB5 +#define ST7703_CMD_SETVCOM 0xB6 +#define ST7703_CMD_SETOTP 0xB7 +#define ST7703_CMD_SETPOWER_EXT 0xB8 +#define ST7703_CMD_SETEXTC 0xB9 +#define ST7703_CMD_SETMIPI 0xBA +#define ST7703_CMD_SETVDC 0xBC +#define ST7703_CMD_UNKNOWN_BF 0xBF +#define ST7703_CMD_SETSCR 0xC0 +#define ST7703_CMD_SETPOWER 0xC1 +#define ST7703_CMD_SETECO 0xC6 +#define ST7703_CMD_SETIO 0xC7 +#define ST7703_CMD_SETCABC 0xC8 +#define ST7703_CMD_SETPANEL 0xCC +#define ST7703_CMD_SETGAMMA 0xE0 +#define ST7703_CMD_SETEQ 0xE3 +#define ST7703_CMD_SETGIP1 0xE9 +#define ST7703_CMD_SETGIP2 0xEA +#define ST7703_CMD_UNKNOWN_EF 0xEF + +struct st7703 { + struct device *dev; + struct drm_panel panel; + struct gpio_desc *reset_gpio; + struct regulator *vcc; + struct regulator *iovcc; + + struct dentry *debugfs; + const struct st7703_panel_desc *desc; + enum drm_panel_orientation orientation; +}; + +struct st7703_panel_desc { + const struct drm_display_mode *mode; + unsigned int lanes; + unsigned long mode_flags; + enum mipi_dsi_pixel_format format; + void (*init_sequence)(struct mipi_dsi_multi_context *dsi_ctx); +}; + +static inline struct st7703 *panel_to_st7703(struct drm_panel *panel) +{ + return container_of(panel, struct st7703, panel); +} + +static void jh057n_init_sequence(struct mipi_dsi_multi_context *dsi_ctx) +{ + /* + * Init sequence was supplied by the panel vendor. Most of the commands + * resemble the ST7703 but the number of parameters often don't match + * so it's likely a clone. + */ + mipi_dsi_generic_write_seq_multi(dsi_ctx, ST7703_CMD_SETEXTC, + 0xF1, 0x12, 0x83); + mipi_dsi_generic_write_seq_multi(dsi_ctx, ST7703_CMD_SETRGBIF, + 0x10, 0x10, 0x05, 0x05, 0x03, 0xFF, 0x00, 0x00, + 0x00, 0x00); + mipi_dsi_generic_write_seq_multi(dsi_ctx, ST7703_CMD_SETSCR, + 0x73, 0x73, 0x50, 0x50, 0x00, 0x00, 0x08, 0x70, + 0x00); + mipi_dsi_generic_write_seq_multi(dsi_ctx, ST7703_CMD_SETVDC, 0x4E); + mipi_dsi_generic_write_seq_multi(dsi_ctx, ST7703_CMD_SETPANEL, 0x0B); + mipi_dsi_generic_write_seq_multi(dsi_ctx, ST7703_CMD_SETCYC, 0x80); + mipi_dsi_generic_write_seq_multi(dsi_ctx, ST7703_CMD_SETDISP, 0xF0, 0x12, 0x30); + mipi_dsi_generic_write_seq_multi(dsi_ctx, ST7703_CMD_SETEQ, + 0x07, 0x07, 0x0B, 0x0B, 0x03, 0x0B, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0x00, 0xC0, 0x10); + mipi_dsi_generic_write_seq_multi(dsi_ctx, ST7703_CMD_SETBGP, 0x08, 0x08); + mipi_dsi_msleep(dsi_ctx, 20); + + mipi_dsi_generic_write_seq_multi(dsi_ctx, ST7703_CMD_SETVCOM, 0x3F, 0x3F); + mipi_dsi_generic_write_seq_multi(dsi_ctx, ST7703_CMD_UNKNOWN_BF, 0x02, 0x11, 0x00); + mipi_dsi_generic_write_seq_multi(dsi_ctx, ST7703_CMD_SETGIP1, + 0x82, 0x10, 0x06, 0x05, 0x9E, 0x0A, 0xA5, 0x12, + 0x31, 0x23, 0x37, 0x83, 0x04, 0xBC, 0x27, 0x38, + 0x0C, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0C, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x75, 0x75, 0x31, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x13, 0x88, 0x64, + 0x64, 0x20, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x02, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + mipi_dsi_generic_write_seq_multi(dsi_ctx, ST7703_CMD_SETGIP2, + 0x02, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x46, 0x02, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x64, 0x88, 0x13, + 0x57, 0x13, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x75, 0x88, 0x23, 0x14, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x0A, + 0xA5, 0x00, 0x00, 0x00, 0x00); + mipi_dsi_generic_write_seq_multi(dsi_ctx, ST7703_CMD_SETGAMMA, + 0x00, 0x09, 0x0E, 0x29, 0x2D, 0x3C, 0x41, 0x37, + 0x07, 0x0B, 0x0D, 0x10, 0x11, 0x0F, 0x10, 0x11, + 0x18, 0x00, 0x09, 0x0E, 0x29, 0x2D, 0x3C, 0x41, + 0x37, 0x07, 0x0B, 0x0D, 0x10, 0x11, 0x0F, 0x10, + 0x11, 0x18); + mipi_dsi_msleep(dsi_ctx, 20); +} + +static const struct drm_display_mode jh057n00900_mode = { + .hdisplay = 720, + .hsync_start = 720 + 90, + .hsync_end = 720 + 90 + 20, + .htotal = 720 + 90 + 20 + 20, + .vdisplay = 1440, + .vsync_start = 1440 + 20, + .vsync_end = 1440 + 20 + 4, + .vtotal = 1440 + 20 + 4 + 12, + .clock = 75276, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + .width_mm = 65, + .height_mm = 130, +}; + +static const struct st7703_panel_desc jh057n00900_panel_desc = { + .mode = &jh057n00900_mode, + .lanes = 4, + .mode_flags = MIPI_DSI_MODE_VIDEO | + MIPI_DSI_MODE_VIDEO_BURST | MIPI_DSI_MODE_VIDEO_SYNC_PULSE, + .format = MIPI_DSI_FMT_RGB888, + .init_sequence = jh057n_init_sequence, +}; + +static void xbd599_init_sequence(struct mipi_dsi_multi_context *dsi_ctx) +{ + /* + * Init sequence was supplied by the panel vendor. + */ + + /* Magic sequence to unlock user commands below. */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETEXTC, 0xF1, 0x12, 0x83); + + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETMIPI, + 0x33, /* VC_main = 0, Lane_Number = 3 (4 lanes) */ + 0x81, /* DSI_LDO_SEL = 1.7V, RTERM = 90 Ohm */ + 0x05, /* IHSRX = x6 (Low High Speed driving ability) */ + 0xF9, /* TX_CLK_SEL = fDSICLK/16 */ + 0x0E, /* HFP_OSC (min. HFP number in DSI mode) */ + 0x0E, /* HBP_OSC (min. HBP number in DSI mode) */ + /* The rest is undocumented in ST7703 datasheet */ + 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x44, 0x25, 0x00, 0x91, 0x0a, 0x00, 0x00, 0x02, + 0x4F, 0x11, 0x00, 0x00, 0x37); + + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETPOWER_EXT, + 0x25, /* PCCS = 2, ECP_DC_DIV = 1/4 HSYNC */ + 0x22, /* DT = 15ms XDK_ECP = x2 */ + 0x20, /* PFM_DC_DIV = /1 */ + 0x03 /* ECP_SYNC_EN = 1, VGX_SYNC_EN = 1 */); + + /* RGB I/F porch timing */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETRGBIF, + 0x10, /* VBP_RGB_GEN */ + 0x10, /* VFP_RGB_GEN */ + 0x05, /* DE_BP_RGB_GEN */ + 0x05, /* DE_FP_RGB_GEN */ + /* The rest is undocumented in ST7703 datasheet */ + 0x03, 0xFF, + 0x00, 0x00, + 0x00, 0x00); + + /* Source driving settings. */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETSCR, + 0x73, /* N_POPON */ + 0x73, /* N_NOPON */ + 0x50, /* I_POPON */ + 0x50, /* I_NOPON */ + 0x00, /* SCR[31,24] */ + 0xC0, /* SCR[23,16] */ + 0x08, /* SCR[15,8] */ + 0x70, /* SCR[7,0] */ + 0x00 /* Undocumented */); + + /* NVDDD_SEL = -1.8V, VDDD_SEL = out of range (possibly 1.9V?) */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETVDC, 0x4E); + + /* + * SS_PANEL = 1 (reverse scan), GS_PANEL = 0 (normal scan) + * REV_PANEL = 1 (normally black panel), BGR_PANEL = 1 (BGR) + */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETPANEL, 0x0B); + + /* Zig-Zag Type C column inversion. */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETCYC, 0x80); + + /* Set display resolution. */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETDISP, + 0xF0, /* NL = 240 */ + 0x12, /* RES_V_LSB = 0, BLK_CON = VSSD, + * RESO_SEL = 720RGB + */ + 0xF0 /* WHITE_GND_EN = 1 (GND), + * WHITE_FRAME_SEL = 7 frames, + * ISC = 0 frames + */); + + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETEQ, + 0x00, /* PNOEQ */ + 0x00, /* NNOEQ */ + 0x0B, /* PEQGND */ + 0x0B, /* NEQGND */ + 0x10, /* PEQVCI */ + 0x10, /* NEQVCI */ + 0x00, /* PEQVCI1 */ + 0x00, /* NEQVCI1 */ + 0x00, /* reserved */ + 0x00, /* reserved */ + 0xFF, /* reserved */ + 0x00, /* reserved */ + 0xC0, /* ESD_DET_DATA_WHITE = 1, ESD_WHITE_EN = 1 */ + 0x10 /* SLPIN_OPTION = 1 (no need vsync after sleep-in) + * VEDIO_NO_CHECK_EN = 0 + * ESD_WHITE_GND_EN = 0 + * ESD_DET_TIME_SEL = 0 frames + */); + + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETECO, 0x01, 0x00, 0xFF, 0xFF, 0x00); + + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETPOWER, + 0x74, /* VBTHS, VBTLS: VGH = 17V, VBL = -11V */ + 0x00, /* FBOFF_VGH = 0, FBOFF_VGL = 0 */ + 0x32, /* VRP */ + 0x32, /* VRN */ + 0x77, /* reserved */ + 0xF1, /* APS = 1 (small), + * VGL_DET_EN = 1, VGH_DET_EN = 1, + * VGL_TURBO = 1, VGH_TURBO = 1 + */ + 0xFF, /* VGH1_L_DIV, VGL1_L_DIV (1.5MHz) */ + 0xFF, /* VGH1_R_DIV, VGL1_R_DIV (1.5MHz) */ + 0xCC, /* VGH2_L_DIV, VGL2_L_DIV (2.6MHz) */ + 0xCC, /* VGH2_R_DIV, VGL2_R_DIV (2.6MHz) */ + 0x77, /* VGH3_L_DIV, VGL3_L_DIV (4.5MHz) */ + 0x77 /* VGH3_R_DIV, VGL3_R_DIV (4.5MHz) */); + + /* Reference voltage. */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETBGP, + 0x07, /* VREF_SEL = 4.2V */ + 0x07 /* NVREF_SEL = 4.2V */); + + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETVCOM, + 0x2C, /* VCOMDC_F = -0.67V */ + 0x2C /* VCOMDC_B = -0.67V */); + + /* Undocumented command. */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_UNKNOWN_BF, 0x02, 0x11, 0x00); + + /* This command is to set forward GIP timing. */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETGIP1, + 0x82, 0x10, 0x06, 0x05, 0xA2, 0x0A, 0xA5, 0x12, + 0x31, 0x23, 0x37, 0x83, 0x04, 0xBC, 0x27, 0x38, + 0x0C, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0C, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x75, 0x75, 0x31, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x13, 0x88, 0x64, + 0x64, 0x20, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x02, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + + /* This command is to set backward GIP timing. */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETGIP2, + 0x02, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x46, 0x02, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x64, 0x88, 0x13, + 0x57, 0x13, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x75, 0x88, 0x23, 0x14, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0A, + 0xA5, 0x00, 0x00, 0x00, 0x00); + + /* Adjust the gamma characteristics of the panel. */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETGAMMA, + 0x00, 0x09, 0x0D, 0x23, 0x27, 0x3C, 0x41, 0x35, + 0x07, 0x0D, 0x0E, 0x12, 0x13, 0x10, 0x12, 0x12, + 0x18, 0x00, 0x09, 0x0D, 0x23, 0x27, 0x3C, 0x41, + 0x35, 0x07, 0x0D, 0x0E, 0x12, 0x13, 0x10, 0x12, + 0x12, 0x18); +} + +static const struct drm_display_mode xbd599_mode = { + .hdisplay = 720, + .hsync_start = 720 + 40, + .hsync_end = 720 + 40 + 40, + .htotal = 720 + 40 + 40 + 40, + .vdisplay = 1440, + .vsync_start = 1440 + 18, + .vsync_end = 1440 + 18 + 10, + .vtotal = 1440 + 18 + 10 + 17, + .clock = 69000, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + .width_mm = 68, + .height_mm = 136, +}; + +static const struct st7703_panel_desc xbd599_desc = { + .mode = &xbd599_mode, + .lanes = 4, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE, + .format = MIPI_DSI_FMT_RGB888, + .init_sequence = xbd599_init_sequence, +}; + +static void rg353v2_init_sequence(struct mipi_dsi_multi_context *dsi_ctx) +{ + /* + * Init sequence was supplied by the panel vendor. + */ + + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETEXTC, 0xf1, 0x12, 0x83); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETAPID, 0x00, 0x00, 0x00, + 0xda, 0x80); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETDISP, 0x00, 0x13, 0x70); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETRGBIF, 0x10, 0x10, 0x28, + 0x28, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETCYC, 0x80); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETBGP, 0x0a, 0x0a); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETVCOM, 0x92, 0x92); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETPOWER_EXT, 0x25, 0x22, + 0xf0, 0x63); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETMIPI, 0x33, 0x81, 0x05, + 0xf9, 0x0e, 0x0e, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x44, 0x25, 0x00, 0x90, 0x0a, + 0x00, 0x00, 0x01, 0x4f, 0x01, 0x00, 0x00, 0x37); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETVDC, 0x47); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_UNKNOWN_BF, 0x02, 0x11, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETSCR, 0x73, 0x73, 0x50, 0x50, + 0x00, 0x00, 0x12, 0x50, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETPOWER, 0x53, 0xc0, 0x32, + 0x32, 0x77, 0xe1, 0xdd, 0xdd, 0x77, 0x77, 0x33, + 0x33); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETECO, 0x82, 0x00, 0xbf, 0xff, + 0x00, 0xff); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETIO, 0xb8, 0x00, 0x0a, 0x00, + 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETCABC, 0x10, 0x40, 0x1e, + 0x02); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETPANEL, 0x0b); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETGAMMA, 0x00, 0x07, 0x0d, + 0x37, 0x35, 0x3f, 0x41, 0x44, 0x06, 0x0c, 0x0d, + 0x0f, 0x11, 0x10, 0x12, 0x14, 0x1a, 0x00, 0x07, + 0x0d, 0x37, 0x35, 0x3f, 0x41, 0x44, 0x06, 0x0c, + 0x0d, 0x0f, 0x11, 0x10, 0x12, 0x14, 0x1a); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETEQ, 0x07, 0x07, 0x0b, 0x0b, + 0x0b, 0x0b, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, + 0xc0, 0x10); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETGIP1, 0xc8, 0x10, 0x02, 0x00, + 0x00, 0xb0, 0xb1, 0x11, 0x31, 0x23, 0x28, 0x80, + 0xb0, 0xb1, 0x27, 0x08, 0x00, 0x04, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x04, 0x02, 0x00, 0x00, 0x00, + 0x88, 0x88, 0xba, 0x60, 0x24, 0x08, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0xba, 0x71, 0x35, + 0x18, 0x88, 0x88, 0x88, 0x88, 0x88, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETGIP2, 0x97, 0x0a, 0x82, 0x02, + 0x03, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x81, 0x88, 0xba, 0x17, 0x53, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x80, 0x88, 0xba, 0x06, 0x42, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x23, 0x00, + 0x00, 0x02, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_UNKNOWN_EF, 0xff, 0xff, 0x01); +} + +static const struct drm_display_mode rg353v2_mode = { + .hdisplay = 640, + .hsync_start = 640 + 40, + .hsync_end = 640 + 40 + 2, + .htotal = 640 + 40 + 2 + 80, + .vdisplay = 480, + .vsync_start = 480 + 18, + .vsync_end = 480 + 18 + 2, + .vtotal = 480 + 18 + 2 + 28, + .clock = 24150, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + .width_mm = 70, + .height_mm = 57, +}; + +static const struct st7703_panel_desc rg353v2_desc = { + .mode = &rg353v2_mode, + .lanes = 4, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_NO_EOT_PACKET | MIPI_DSI_MODE_LPM, + .format = MIPI_DSI_FMT_RGB888, + .init_sequence = rg353v2_init_sequence, +}; + +static void rgb30panel_init_sequence(struct mipi_dsi_multi_context *dsi_ctx) +{ + /* Init sequence extracted from Powkiddy RGB30 BSP kernel. */ + + /* + * For some reason this specific panel must be taken out of sleep + * before the full init sequence, or else it will not display. + */ + mipi_dsi_dcs_exit_sleep_mode_multi(dsi_ctx); + mipi_dsi_msleep(dsi_ctx, 250); + + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETEXTC, 0xf1, 0x12, 0x83); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETMIPI, 0x33, 0x81, 0x05, 0xf9, + 0x0e, 0x0e, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x44, 0x25, 0x00, 0x90, 0x0a, 0x00, + 0x00, 0x01, 0x4f, 0x01, 0x00, 0x00, 0x37); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETPOWER_EXT, 0x25, 0x22, 0xf0, + 0x63); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_UNKNOWN_BF, 0x02, 0x11, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETRGBIF, 0x10, 0x10, 0x28, + 0x28, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETSCR, 0x73, 0x73, 0x50, 0x50, + 0x00, 0x00, 0x12, 0x70, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETVDC, 0x46); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETPANEL, 0x0b); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETCYC, 0x80); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETDISP, 0x3c, 0x12, 0x30); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETEQ, 0x07, 0x07, 0x0b, 0x0b, + 0x03, 0x0b, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, + 0xc0, 0x10); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETPOWER, 0x36, 0x00, 0x32, + 0x32, 0x77, 0xf1, 0xcc, 0xcc, 0x77, 0x77, 0x33, + 0x33); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETBGP, 0x0a, 0x0a); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETVCOM, 0x88, 0x88); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETGIP1, 0xc8, 0x10, 0x0a, 0x10, + 0x0f, 0xa1, 0x80, 0x12, 0x31, 0x23, 0x47, 0x86, + 0xa1, 0x80, 0x47, 0x08, 0x00, 0x00, 0x0d, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x48, 0x02, 0x8b, 0xaf, 0x46, 0x02, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x48, 0x13, 0x8b, 0xaf, 0x57, + 0x13, 0x88, 0x88, 0x88, 0x88, 0x88, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETGIP2, 0x96, 0x12, 0x01, 0x01, + 0x01, 0x78, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x4f, 0x31, 0x8b, 0xa8, 0x31, 0x75, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x4f, 0x20, 0x8b, 0xa8, 0x20, + 0x64, 0x88, 0x88, 0x88, 0x88, 0x88, 0x23, 0x00, + 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x40, 0xa1, 0x80, 0x00, 0x00, 0x00, + 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETGAMMA, 0x00, 0x0a, 0x0f, + 0x29, 0x3b, 0x3f, 0x42, 0x39, 0x06, 0x0d, 0x10, + 0x13, 0x15, 0x14, 0x15, 0x10, 0x17, 0x00, 0x0a, + 0x0f, 0x29, 0x3b, 0x3f, 0x42, 0x39, 0x06, 0x0d, + 0x10, 0x13, 0x15, 0x14, 0x15, 0x10, 0x17); +} + +static const struct drm_display_mode rgb30panel_mode = { + .hdisplay = 720, + .hsync_start = 720 + 45, + .hsync_end = 720 + 45 + 4, + .htotal = 720 + 45 + 4 + 45, + .vdisplay = 720, + .vsync_start = 720 + 15, + .vsync_end = 720 + 15 + 3, + .vtotal = 720 + 15 + 3 + 11, + .clock = 36570, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + .width_mm = 76, + .height_mm = 76, +}; + +static const struct st7703_panel_desc rgb30panel_desc = { + .mode = &rgb30panel_mode, + .lanes = 4, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_NO_EOT_PACKET | MIPI_DSI_MODE_LPM, + .format = MIPI_DSI_FMT_RGB888, + .init_sequence = rgb30panel_init_sequence, +}; + +static void rgb10max3_panel_init_sequence(struct mipi_dsi_multi_context *dsi_ctx) +{ + /* Init sequence extracted from Powkiddy RGB10MAX3 BSP kernel. */ + + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETEXTC, 0xf1, 0x12, 0x83); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETAPID, 0x00, 0x00, 0x00, 0xda, + 0x80); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETDISP, 0xc8, 0x02, 0x30); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETRGBIF, 0x10, 0x10, 0x28, + 0x28, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETCYC, 0x80); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETBGP, 0x04, 0x04); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETVCOM, 0x78, 0x78); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETPOWER_EXT, 0x25, 0x22, 0xf0, + 0x63); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETMIPI, 0x33, 0x81, 0x05, 0xf9, + 0x0e, 0x0e, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x44, 0x25, 0x00, 0x90, 0x0a, 0x00, + 0x00, 0x01, 0x4f, 0x01, 0x00, 0x00, 0x37); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETVDC, 0x47); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_UNKNOWN_BF, 0x02, 0x11, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETSCR, 0x73, 0x73, 0x50, 0x50, + 0x00, 0x00, 0x12, 0x70, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETPOWER, 0x25, 0x00, 0x32, + 0x32, 0x77, 0xe1, 0xff, 0xff, 0xcc, 0xcc, 0x77, + 0x77); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETECO, 0x82, 0x00, 0xbf, 0xff, + 0x00, 0xff); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETIO, 0xb8, 0x00, 0x0a, 0x00, + 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETCABC, 0x10, 0x40, 0x1e, + 0x02); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETPANEL, 0x0b); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETGAMMA, 0x00, 0x04, 0x07, + 0x2a, 0x39, 0x3f, 0x36, 0x31, 0x06, 0x0b, 0x0e, + 0x12, 0x14, 0x12, 0x13, 0x0f, 0x17, 0x00, 0x04, + 0x07, 0x2a, 0x39, 0x3f, 0x36, 0x31, 0x06, 0x0b, + 0x0e, 0x12, 0x14, 0x12, 0x13, 0x0f, 0x17); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETEQ, 0x03, 0x03, 0x03, 0x03, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0xff, 0x80, + 0xc0, 0x10); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETGIP1, 0xc8, 0x10, 0x08, 0x00, + 0x00, 0x41, 0xf8, 0x12, 0x31, 0x23, 0x37, 0x86, + 0x11, 0xc8, 0x37, 0x2a, 0x00, 0x00, 0x0c, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x88, 0x20, 0x46, 0x02, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0xff, 0x88, 0x31, 0x57, 0x13, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0xff, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETGIP2, 0x00, 0x1a, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x8f, 0x13, 0x31, 0x75, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0xf8, 0x8f, 0x02, 0x20, 0x64, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0xf8, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_UNKNOWN_EF, 0xff, 0xff, 0x01); +} + +static const struct drm_display_mode rgb10max3_panel_mode = { + .hdisplay = 720, + .hsync_start = 720 + 40, + .hsync_end = 720 + 40 + 10, + .htotal = 720 + 40 + 10 + 40, + .vdisplay = 1280, + .vsync_start = 1280 + 16, + .vsync_end = 1280 + 16 + 4, + .vtotal = 1280 + 16 + 4 + 14, + .clock = 63800, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + .width_mm = 62, + .height_mm = 109, +}; + +static const struct st7703_panel_desc rgb10max3_panel_desc = { + .mode = &rgb10max3_panel_mode, + .lanes = 4, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_NO_EOT_PACKET | MIPI_DSI_MODE_LPM, + .format = MIPI_DSI_FMT_RGB888, + .init_sequence = rgb10max3_panel_init_sequence, +}; + +static void gameforcechi_init_sequence(struct mipi_dsi_multi_context *dsi_ctx) +{ + /* + * Init sequence was supplied by the panel vendor. Panel will not + * respond to commands until it is brought out of sleep mode first. + */ + + mipi_dsi_dcs_exit_sleep_mode_multi(dsi_ctx); + mipi_dsi_msleep(dsi_ctx, 250); + + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETEXTC, 0xf1, 0x12, 0x83); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETMIPI, 0x31, 0x81, 0x05, 0xf9, + 0x0e, 0x0e, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x44, 0x25, 0x00, 0x91, 0x0a, 0x00, + 0x00, 0x02, 0x4f, 0xd1, 0x00, 0x00, 0x37); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETPOWER_EXT, 0x25); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_UNKNOWN_BF, 0x02, 0x11, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETRGBIF, 0x0c, 0x10, 0x0a, + 0x50, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETSCR, 0x73, 0x73, 0x50, 0x50, + 0x00, 0x00, 0x08, 0x70, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETVDC, 0x46); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETPANEL, 0x0b); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETCYC, 0x80); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETDISP, 0x00, 0x13, 0xf0); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETEQ, 0x07, 0x07, 0x0b, 0x0b, + 0x03, 0x0b, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, + 0xc0, 0x10); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETPOWER, 0x53, 0x00, 0x1e, + 0x1e, 0x77, 0xe1, 0xcc, 0xdd, 0x67, 0x77, 0x33, + 0x33); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETBGP, 0x10, 0x10); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETVCOM, 0x6c, 0x7c); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETGIP1, 0x08, 0x00, 0x0e, 0x00, + 0x00, 0xb0, 0xb1, 0x11, 0x31, 0x23, 0x28, 0x10, + 0xb0, 0xb1, 0x27, 0x08, 0x00, 0x04, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x04, 0x02, 0x00, 0x00, 0x00, + 0x88, 0x88, 0xba, 0x60, 0x24, 0x08, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0xba, 0x71, 0x35, + 0x18, 0x88, 0x88, 0x88, 0x88, 0x88, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETGIP2, 0x97, 0x0a, 0x82, 0x02, + 0x13, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x88, 0xba, 0x17, 0x53, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x81, 0x88, 0xba, 0x06, 0x42, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x23, 0x10, + 0x00, 0x02, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETGAMMA, 0x00, 0x07, 0x0b, + 0x27, 0x2d, 0x3f, 0x3b, 0x37, 0x05, 0x0a, 0x0b, + 0x0f, 0x11, 0x0f, 0x12, 0x12, 0x18, 0x00, 0x07, + 0x0b, 0x27, 0x2d, 0x3f, 0x3b, 0x37, 0x05, 0xa0, + 0x0b, 0x0f, 0x11, 0x0f, 0x12, 0x12, 0x18); +} + +static const struct drm_display_mode gameforcechi_mode = { + .hdisplay = 640, + .hsync_start = 640 + 40, + .hsync_end = 640 + 40 + 2, + .htotal = 640 + 40 + 2 + 80, + .vdisplay = 480, + .vsync_start = 480 + 17, + .vsync_end = 480 + 17 + 5, + .vtotal = 480 + 17 + 5 + 13, + .clock = 23546, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + .width_mm = 71, + .height_mm = 53, +}; + +static const struct st7703_panel_desc gameforcechi_desc = { + .mode = &gameforcechi_mode, + .lanes = 2, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_NO_EOT_PACKET | MIPI_DSI_MODE_LPM, + .format = MIPI_DSI_FMT_RGB888, + .init_sequence = gameforcechi_init_sequence, +}; + +static int st7703_enable(struct drm_panel *panel) +{ + struct st7703 *ctx = panel_to_st7703(panel); + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + struct mipi_dsi_multi_context dsi_ctx = {.dsi = dsi}; + + ctx->desc->init_sequence(&dsi_ctx); + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + + /* It takes the controller 120 msec to wake up after sleep. */ + mipi_dsi_msleep(&dsi_ctx, 120); + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + + if (!dsi_ctx.accum_err) + dev_dbg(ctx->dev, "Panel init sequence done\n"); + + return dsi_ctx.accum_err; +} + +static int st7703_disable(struct drm_panel *panel) +{ + struct st7703 *ctx = panel_to_st7703(panel); + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + struct mipi_dsi_multi_context dsi_ctx = {.dsi = dsi}; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + + /* It takes the controller 120 msec to enter sleep mode. */ + mipi_dsi_msleep(&dsi_ctx, 120); + + return dsi_ctx.accum_err; +} + +static int st7703_unprepare(struct drm_panel *panel) +{ + struct st7703 *ctx = panel_to_st7703(panel); + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_disable(ctx->iovcc); + regulator_disable(ctx->vcc); + + return 0; +} + +static int st7703_prepare(struct drm_panel *panel) +{ + struct st7703 *ctx = panel_to_st7703(panel); + int ret; + + dev_dbg(ctx->dev, "Resetting the panel\n"); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + + ret = regulator_enable(ctx->iovcc); + if (ret < 0) { + dev_err(ctx->dev, "Failed to enable iovcc supply: %d\n", ret); + return ret; + } + + ret = regulator_enable(ctx->vcc); + if (ret < 0) { + dev_err(ctx->dev, "Failed to enable vcc supply: %d\n", ret); + regulator_disable(ctx->iovcc); + return ret; + } + + /* Give power supplies time to stabilize before deasserting reset. */ + usleep_range(10000, 20000); + + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(15000, 20000); + + return 0; +} + +static const u32 mantix_bus_formats[] = { + MEDIA_BUS_FMT_RGB888_1X24, +}; + +static int st7703_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct st7703 *ctx = panel_to_st7703(panel); + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, ctx->desc->mode); + if (!mode) { + dev_err(ctx->dev, "Failed to add mode %ux%u@%u\n", + ctx->desc->mode->hdisplay, ctx->desc->mode->vdisplay, + drm_mode_vrefresh(ctx->desc->mode)); + 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); + + drm_display_info_set_bus_formats(&connector->display_info, + mantix_bus_formats, + ARRAY_SIZE(mantix_bus_formats)); + + return 1; +} + +static enum drm_panel_orientation st7703_get_orientation(struct drm_panel *panel) +{ + struct st7703 *st7703 = panel_to_st7703(panel); + + return st7703->orientation; +} + +static const struct drm_panel_funcs st7703_drm_funcs = { + .disable = st7703_disable, + .unprepare = st7703_unprepare, + .prepare = st7703_prepare, + .enable = st7703_enable, + .get_modes = st7703_get_modes, + .get_orientation = st7703_get_orientation, +}; + +static int allpixelson_set(void *data, u64 val) +{ + struct st7703 *ctx = data; + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + struct mipi_dsi_multi_context dsi_ctx = {.dsi = dsi}; + + dev_dbg(ctx->dev, "Setting all pixels on\n"); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, ST7703_CMD_ALL_PIXEL_ON); + mipi_dsi_msleep(&dsi_ctx, val * 1000); + + /* + * Reset the panel to get video back. NOTE: This isn't a + * particularly safe thing to do in general because it assumes + * that the screen was on to begin with, but this is just a + * debugfs file so it's not a huge deal. + */ + drm_panel_disable(&ctx->panel); + drm_panel_unprepare(&ctx->panel); + drm_panel_prepare(&ctx->panel); + drm_panel_enable(&ctx->panel); + + return dsi_ctx.accum_err; +} + +DEFINE_SIMPLE_ATTRIBUTE(allpixelson_fops, NULL, + allpixelson_set, "%llu\n"); + +static void st7703_debugfs_init(struct st7703 *ctx) +{ + ctx->debugfs = debugfs_create_dir(DRV_NAME, NULL); + + debugfs_create_file("allpixelson", 0600, ctx->debugfs, ctx, + &allpixelson_fops); +} + +static void st7703_debugfs_remove(struct st7703 *ctx) +{ + debugfs_remove_recursive(ctx->debugfs); + ctx->debugfs = NULL; +} + +static int st7703_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct st7703 *ctx; + int ret; + + ctx = devm_drm_panel_alloc(dev, struct st7703, panel, + &st7703_drm_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), "Failed to get reset gpio\n"); + + mipi_dsi_set_drvdata(dsi, ctx); + + ctx->dev = dev; + ctx->desc = of_device_get_match_data(dev); + + dsi->mode_flags = ctx->desc->mode_flags; + dsi->format = ctx->desc->format; + dsi->lanes = ctx->desc->lanes; + + ctx->vcc = devm_regulator_get(dev, "vcc"); + if (IS_ERR(ctx->vcc)) + return dev_err_probe(dev, PTR_ERR(ctx->vcc), "Failed to request vcc regulator\n"); + + ctx->iovcc = devm_regulator_get(dev, "iovcc"); + if (IS_ERR(ctx->iovcc)) + return dev_err_probe(dev, PTR_ERR(ctx->iovcc), + "Failed to request iovcc regulator\n"); + + ret = of_drm_get_panel_orientation(dsi->dev.of_node, &ctx->orientation); + if (ret < 0) + return dev_err_probe(&dsi->dev, ret, "Failed to get orientation\n"); + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return ret; + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "mipi_dsi_attach failed (%d). Is host ready?\n", ret); + drm_panel_remove(&ctx->panel); + return ret; + } + + dev_info(dev, "%ux%u@%u %ubpp dsi %udl - ready\n", + ctx->desc->mode->hdisplay, ctx->desc->mode->vdisplay, + drm_mode_vrefresh(ctx->desc->mode), + mipi_dsi_pixel_format_to_bpp(dsi->format), dsi->lanes); + + st7703_debugfs_init(ctx); + return 0; +} + +static void st7703_remove(struct mipi_dsi_device *dsi) +{ + struct st7703 *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&ctx->panel); + + st7703_debugfs_remove(ctx); +} + +static const struct of_device_id st7703_of_match[] = { + { .compatible = "anbernic,rg353v-panel-v2", .data = &rg353v2_desc }, + { .compatible = "gameforce,chi-panel", .data = &gameforcechi_desc }, + { .compatible = "powkiddy,rgb10max3-panel", .data = &rgb10max3_panel_desc }, + { .compatible = "powkiddy,rgb30-panel", .data = &rgb30panel_desc }, + { .compatible = "rocktech,jh057n00900", .data = &jh057n00900_panel_desc }, + { .compatible = "xingbangda,xbd599", .data = &xbd599_desc }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, st7703_of_match); + +static struct mipi_dsi_driver st7703_driver = { + .probe = st7703_probe, + .remove = st7703_remove, + .driver = { + .name = DRV_NAME, + .of_match_table = st7703_of_match, + }, +}; +module_mipi_dsi_driver(st7703_driver); + +MODULE_AUTHOR("Guido Günther <agx@sigxcpu.org>"); +MODULE_DESCRIPTION("DRM driver for Sitronix ST7703 based MIPI DSI panels"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-sitronix-st7789v.c b/drivers/gpu/drm/panel/panel-sitronix-st7789v.c index 74284e5afc5d..d5f821d6b23c 100644 --- a/drivers/gpu/drm/panel/panel-sitronix-st7789v.c +++ b/drivers/gpu/drm/panel/panel-sitronix-st7789v.c @@ -1,22 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2017 Free Electrons - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License version - * 2 as published by the Free Software Foundation. */ +#include <linux/delay.h> #include <linux/gpio/consumer.h> +#include <linux/module.h> #include <linux/regulator/consumer.h> #include <linux/spi/spi.h> -#include <drm/drmP.h> -#include <drm/drm_panel.h> - #include <video/mipi_display.h> +#include <linux/media-bus-format.h> -#define ST7789V_COLMOD_RGB_FMT_18BITS (6 << 4) -#define ST7789V_COLMOD_CTRL_FMT_18BITS (6 << 0) +#include <drm/drm_device.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> #define ST7789V_RAMCTRL_CMD 0xb0 #define ST7789V_RAMCTRL_RM_RGB BIT(4) @@ -29,7 +27,8 @@ #define ST7789V_RGBCTRL_RCM(n) (((n) & 3) << 5) #define ST7789V_RGBCTRL_VSYNC_HIGH BIT(3) #define ST7789V_RGBCTRL_HSYNC_HIGH BIT(2) -#define ST7789V_RGBCTRL_PCLK_HIGH BIT(1) +#define ST7789V_RGBCTRL_PCLK_FALLING BIT(1) +#define ST7789V_RGBCTRL_DE_LOW BIT(0) #define ST7789V_RGBCTRL_VBP(n) ((n) & 0x7f) #define ST7789V_RGBCTRL_HBP(n) ((n) & 0x1f) @@ -111,12 +110,26 @@ return val; \ } while (0) +#define ST7789V_IDS { 0x85, 0x85, 0x52 } +#define ST7789V_IDS_SIZE 3 + +struct st7789_panel_info { + const struct drm_display_mode *mode; + u32 bus_format; + u32 bus_flags; + bool invert_mode; + bool partial_mode; + u16 partial_start; + u16 partial_end; +}; + struct st7789v { struct drm_panel panel; + const struct st7789_panel_info *info; struct spi_device *spi; struct gpio_desc *reset; - struct backlight_device *backlight; struct regulator *power; + enum drm_panel_orientation orientation; }; enum st7789v_prefix { @@ -133,17 +146,12 @@ static int st7789v_spi_write(struct st7789v *ctx, enum st7789v_prefix prefix, u8 data) { struct spi_transfer xfer = { }; - struct spi_message msg; u16 txbuf = ((prefix & 1) << 8) | data; - spi_message_init(&msg); - xfer.tx_buf = &txbuf; - xfer.bits_per_word = 9; xfer.len = sizeof(txbuf); - spi_message_add_tail(&xfer, &msg); - return spi_sync(ctx->spi, &msg); + return spi_sync_transfer(ctx->spi, &xfer, 1); } static int st7789v_write_command(struct st7789v *ctx, u8 cmd) @@ -156,6 +164,76 @@ static int st7789v_write_data(struct st7789v *ctx, u8 cmd) return st7789v_spi_write(ctx, ST7789V_DATA, cmd); } +static int st7789v_read_data(struct st7789v *ctx, u8 cmd, u8 *buf, + unsigned int len) +{ + struct spi_transfer xfer[2] = { }; + struct spi_message msg; + u16 txbuf = ((ST7789V_COMMAND & 1) << 8) | cmd; + u16 rxbuf[4] = {}; + u8 bit9 = 0; + int ret, i; + + switch (len) { + case 1: + case 3: + case 4: + break; + default: + return -EOPNOTSUPP; + } + + spi_message_init(&msg); + + xfer[0].tx_buf = &txbuf; + xfer[0].len = sizeof(txbuf); + spi_message_add_tail(&xfer[0], &msg); + + xfer[1].rx_buf = rxbuf; + xfer[1].len = len * 2; + spi_message_add_tail(&xfer[1], &msg); + + ret = spi_sync(ctx->spi, &msg); + if (ret) + return ret; + + for (i = 0; i < len; i++) { + buf[i] = rxbuf[i] >> i | (bit9 << (9 - i)); + if (i) + bit9 = rxbuf[i] & GENMASK(i - 1, 0); + } + + return 0; +} + +static int st7789v_check_id(struct drm_panel *panel) +{ + const u8 st7789v_ids[ST7789V_IDS_SIZE] = ST7789V_IDS; + struct st7789v *ctx = panel_to_st7789v(panel); + bool invalid_ids = false; + int ret, i; + u8 ids[3]; + + if (ctx->spi->mode & SPI_NO_RX) + return 0; + + ret = st7789v_read_data(ctx, MIPI_DCS_GET_DISPLAY_ID, ids, ST7789V_IDS_SIZE); + if (ret) + return ret; + + for (i = 0; i < ST7789V_IDS_SIZE; i++) { + if (ids[i] != st7789v_ids[i]) { + invalid_ids = true; + break; + } + } + + if (invalid_ids) + return -EIO; + + return 0; +} + static const struct drm_display_mode default_mode = { .clock = 7000, .hdisplay = 240, @@ -166,19 +244,107 @@ static const struct drm_display_mode default_mode = { .vsync_start = 320 + 8, .vsync_end = 320 + 8 + 4, .vtotal = 320 + 8 + 4 + 4, - .vrefresh = 60, + .width_mm = 61, + .height_mm = 103, + .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC, +}; + +/* + * The mode data for this panel has been reverse engineered without access + * to the panel datasheet / manual. Using DRM_MODE_FLAG_PHSYNC like all + * other panels results in garbage data on the display. + */ +static const struct drm_display_mode t28cp45tn89_mode = { + .clock = 6008, + .hdisplay = 240, + .hsync_start = 240 + 38, + .hsync_end = 240 + 38 + 10, + .htotal = 240 + 38 + 10 + 10, + .vdisplay = 320, + .vsync_start = 320 + 8, + .vsync_end = 320 + 8 + 4, + .vtotal = 320 + 8 + 4 + 4, + .width_mm = 43, + .height_mm = 57, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC, }; -static int st7789v_get_modes(struct drm_panel *panel) +static const struct drm_display_mode et028013dma_mode = { + .clock = 3000, + .hdisplay = 240, + .hsync_start = 240 + 38, + .hsync_end = 240 + 38 + 10, + .htotal = 240 + 38 + 10 + 10, + .vdisplay = 320, + .vsync_start = 320 + 8, + .vsync_end = 320 + 8 + 4, + .vtotal = 320 + 8 + 4 + 4, + .width_mm = 43, + .height_mm = 58, + .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC, +}; + +static const struct drm_display_mode jt240mhqs_hwt_ek_e3_mode = { + .clock = 6000, + .hdisplay = 240, + .hsync_start = 240 + 38, + .hsync_end = 240 + 38 + 10, + .htotal = 240 + 38 + 10 + 10, + .vdisplay = 280, + .vsync_start = 280 + 48, + .vsync_end = 280 + 48 + 4, + .vtotal = 280 + 48 + 4 + 4, + .width_mm = 37, + .height_mm = 43, + .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC, +}; + +static const struct st7789_panel_info default_panel = { + .mode = &default_mode, + .invert_mode = true, + .bus_format = MEDIA_BUS_FMT_RGB666_1X18, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | + DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE, +}; + +static const struct st7789_panel_info t28cp45tn89_panel = { + .mode = &t28cp45tn89_mode, + .invert_mode = false, + .bus_format = MEDIA_BUS_FMT_RGB565_1X16, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | + DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE, +}; + +static const struct st7789_panel_info et028013dma_panel = { + .mode = &et028013dma_mode, + .invert_mode = true, + .bus_format = MEDIA_BUS_FMT_RGB666_1X18, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | + DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE, +}; + +static const struct st7789_panel_info jt240mhqs_hwt_ek_e3_panel = { + .mode = &jt240mhqs_hwt_ek_e3_mode, + .invert_mode = true, + .bus_format = MEDIA_BUS_FMT_RGB666_1X18, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | + DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE, + .partial_mode = true, + .partial_start = 38, + .partial_end = 318, +}; + +static int st7789v_get_modes(struct drm_panel *panel, + struct drm_connector *connector) { - struct drm_connector *connector = panel->connector; + struct st7789v *ctx = panel_to_st7789v(panel); struct drm_display_mode *mode; - mode = drm_mode_duplicate(panel->drm, &default_mode); + mode = drm_mode_duplicate(connector->dev, ctx->info->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", + ctx->info->mode->hdisplay, ctx->info->mode->vdisplay, + drm_mode_vrefresh(ctx->info->mode)); return -ENOMEM; } @@ -187,17 +353,65 @@ 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.bpc = 6; + connector->display_info.width_mm = ctx->info->mode->width_mm; + connector->display_info.height_mm = ctx->info->mode->height_mm; + connector->display_info.bus_flags = ctx->info->bus_flags; + drm_display_info_set_bus_formats(&connector->display_info, + &ctx->info->bus_format, 1); + + /* + * TODO: Remove once all drm drivers call + * drm_connector_set_orientation_from_panel() + */ + drm_connector_set_panel_orientation(connector, ctx->orientation); return 1; } +static enum drm_panel_orientation st7789v_get_orientation(struct drm_panel *p) +{ + struct st7789v *ctx = panel_to_st7789v(p); + + return ctx->orientation; +} + static int st7789v_prepare(struct drm_panel *panel) { struct st7789v *ctx = panel_to_st7789v(panel); + u8 mode, pixel_fmt, polarity; int ret; + if (!ctx->info->partial_mode) + mode = ST7789V_RGBCTRL_WO; + else + mode = 0; + + switch (ctx->info->bus_format) { + case MEDIA_BUS_FMT_RGB666_1X18: + pixel_fmt = MIPI_DCS_PIXEL_FMT_18BIT; + break; + case MEDIA_BUS_FMT_RGB565_1X16: + pixel_fmt = MIPI_DCS_PIXEL_FMT_16BIT; + break; + default: + dev_err(panel->dev, "unsupported bus format: %d\n", + ctx->info->bus_format); + return -EINVAL; + } + + pixel_fmt = (pixel_fmt << 4) | pixel_fmt; + + polarity = 0; + if (ctx->info->mode->flags & DRM_MODE_FLAG_PVSYNC) + polarity |= ST7789V_RGBCTRL_VSYNC_HIGH; + if (ctx->info->mode->flags & DRM_MODE_FLAG_PHSYNC) + polarity |= ST7789V_RGBCTRL_HSYNC_HIGH; + if (ctx->info->bus_flags & DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE) + polarity |= ST7789V_RGBCTRL_PCLK_FALLING; + if (ctx->info->bus_flags & DRM_BUS_FLAG_DE_LOW) + polarity |= ST7789V_RGBCTRL_DE_LOW; + ret = regulator_enable(ctx->power); if (ret) return ret; @@ -207,6 +421,14 @@ static int st7789v_prepare(struct drm_panel *panel) gpiod_set_value(ctx->reset, 0); msleep(120); + /* + * Avoid failing if the IDs are invalid in case the Rx bus width + * description is missing. + */ + ret = st7789v_check_id(panel); + if (ret) + dev_warn(panel->dev, "Unrecognized panel IDs"); + ST7789V_TEST(ret, st7789v_write_command(ctx, MIPI_DCS_EXIT_SLEEP_MODE)); /* We need to wait 120ms after a sleep out command */ @@ -218,9 +440,7 @@ static int st7789v_prepare(struct drm_panel *panel) ST7789V_TEST(ret, st7789v_write_command(ctx, MIPI_DCS_SET_PIXEL_FORMAT)); - ST7789V_TEST(ret, st7789v_write_data(ctx, - (MIPI_DCS_PIXEL_FMT_18BIT << 4) | - (MIPI_DCS_PIXEL_FMT_18BIT))); + ST7789V_TEST(ret, st7789v_write_data(ctx, pixel_fmt)); ST7789V_TEST(ret, st7789v_write_command(ctx, ST7789V_PORCTRL_CMD)); ST7789V_TEST(ret, st7789v_write_data(ctx, 0xc)); @@ -298,7 +518,44 @@ static int st7789v_prepare(struct drm_panel *panel) ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_NVGAMCTRL_VN61(0x1b))); ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_NVGAMCTRL_VN62(0x28))); - ST7789V_TEST(ret, st7789v_write_command(ctx, MIPI_DCS_ENTER_INVERT_MODE)); + if (ctx->info->invert_mode) { + ST7789V_TEST(ret, st7789v_write_command(ctx, + MIPI_DCS_ENTER_INVERT_MODE)); + } else { + ST7789V_TEST(ret, st7789v_write_command(ctx, + MIPI_DCS_EXIT_INVERT_MODE)); + } + + if (ctx->info->partial_mode) { + u8 area_data[4] = { + (ctx->info->partial_start >> 8) & 0xff, + (ctx->info->partial_start >> 0) & 0xff, + ((ctx->info->partial_end - 1) >> 8) & 0xff, + ((ctx->info->partial_end - 1) >> 0) & 0xff, + }; + + /* Caution: if userspace ever pushes a mode different from the + * expected one (i.e., the one advertised by get_modes), we'll + * add margins. + */ + + ST7789V_TEST(ret, st7789v_write_command( + ctx, MIPI_DCS_ENTER_PARTIAL_MODE)); + + ST7789V_TEST(ret, st7789v_write_command( + ctx, MIPI_DCS_SET_PAGE_ADDRESS)); + ST7789V_TEST(ret, st7789v_write_data(ctx, area_data[0])); + ST7789V_TEST(ret, st7789v_write_data(ctx, area_data[1])); + ST7789V_TEST(ret, st7789v_write_data(ctx, area_data[2])); + ST7789V_TEST(ret, st7789v_write_data(ctx, area_data[3])); + + ST7789V_TEST(ret, st7789v_write_command( + ctx, MIPI_DCS_SET_PARTIAL_ROWS)); + ST7789V_TEST(ret, st7789v_write_data(ctx, area_data[0])); + ST7789V_TEST(ret, st7789v_write_data(ctx, area_data[1])); + ST7789V_TEST(ret, st7789v_write_data(ctx, area_data[2])); + ST7789V_TEST(ret, st7789v_write_data(ctx, area_data[3])); + } ST7789V_TEST(ret, st7789v_write_command(ctx, ST7789V_RAMCTRL_CMD)); ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_RAMCTRL_DM_RGB | @@ -307,11 +564,9 @@ static int st7789v_prepare(struct drm_panel *panel) ST7789V_RAMCTRL_MAGIC)); ST7789V_TEST(ret, st7789v_write_command(ctx, ST7789V_RGBCTRL_CMD)); - ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_RGBCTRL_WO | + ST7789V_TEST(ret, st7789v_write_data(ctx, mode | ST7789V_RGBCTRL_RCM(2) | - ST7789V_RGBCTRL_VSYNC_HIGH | - ST7789V_RGBCTRL_HSYNC_HIGH | - ST7789V_RGBCTRL_PCLK_HIGH)); + polarity)); ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_RGBCTRL_VBP(8))); ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_RGBCTRL_HBP(20))); @@ -322,12 +577,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); } @@ -338,12 +587,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; } @@ -360,75 +603,80 @@ static int st7789v_unprepare(struct drm_panel *panel) } static const struct drm_panel_funcs st7789v_drm_funcs = { - .disable = st7789v_disable, - .enable = st7789v_enable, - .get_modes = st7789v_get_modes, - .prepare = st7789v_prepare, - .unprepare = st7789v_unprepare, + .disable = st7789v_disable, + .enable = st7789v_enable, + .get_modes = st7789v_get_modes, + .get_orientation = st7789v_get_orientation, + .prepare = st7789v_prepare, + .unprepare = st7789v_unprepare, }; static int st7789v_probe(struct spi_device *spi) { - struct device_node *backlight; + struct device *dev = &spi->dev; struct st7789v *ctx; int ret; - ctx = devm_kzalloc(&spi->dev, sizeof(*ctx), GFP_KERNEL); - if (!ctx) - return -ENOMEM; + ctx = devm_drm_panel_alloc(dev, struct st7789v, panel, + &st7789v_drm_funcs, DRM_MODE_CONNECTOR_DPI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); spi_set_drvdata(spi, ctx); ctx->spi = spi; - ctx->panel.dev = &spi->dev; - ctx->panel.funcs = &st7789v_drm_funcs; - - ctx->power = devm_regulator_get(&spi->dev, "power"); - if (IS_ERR(ctx->power)) - return PTR_ERR(ctx->power); + spi->bits_per_word = 9; + ret = spi_setup(spi); + if (ret < 0) + return dev_err_probe(&spi->dev, ret, "Failed to setup spi\n"); - ctx->reset = devm_gpiod_get(&spi->dev, "reset", GPIOD_OUT_LOW); - if (IS_ERR(ctx->reset)) { - dev_err(&spi->dev, "Couldn't get our reset line\n"); - return PTR_ERR(ctx->reset); - } + ctx->info = device_get_match_data(&spi->dev); - backlight = of_parse_phandle(spi->dev.of_node, "backlight", 0); - if (backlight) { - ctx->backlight = of_find_backlight_by_node(backlight); - of_node_put(backlight); + ctx->power = devm_regulator_get(dev, "power"); + ret = PTR_ERR_OR_ZERO(ctx->power); + if (ret) + return dev_err_probe(dev, ret, "Failed to get regulator\n"); - if (!ctx->backlight) - return -EPROBE_DEFER; - } + ctx->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + ret = PTR_ERR_OR_ZERO(ctx->reset); + if (ret) + return dev_err_probe(dev, ret, "Failed to get reset line\n"); - ret = drm_panel_add(&ctx->panel); - if (ret < 0) - goto err_free_backlight; + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return dev_err_probe(dev, ret, "Failed to get backlight\n"); - return 0; + ret = of_drm_get_panel_orientation(spi->dev.of_node, &ctx->orientation); + if (ret) + return dev_err_probe(&spi->dev, ret, "Failed to get orientation\n"); -err_free_backlight: - if (ctx->backlight) - put_device(&ctx->backlight->dev); + drm_panel_add(&ctx->panel); - return ret; + return 0; } -static int st7789v_remove(struct spi_device *spi) +static void st7789v_remove(struct spi_device *spi) { struct st7789v *ctx = spi_get_drvdata(spi); drm_panel_remove(&ctx->panel); - - if (ctx->backlight) - put_device(&ctx->backlight->dev); - - return 0; } +static const struct spi_device_id st7789v_spi_id[] = { + { "st7789v", (unsigned long) &default_panel }, + { "t28cp45tn89-v17", (unsigned long) &t28cp45tn89_panel }, + { "et028013dma", (unsigned long) &et028013dma_panel }, + { "jt240mhqs-hwt-ek-e3", (unsigned long) &jt240mhqs_hwt_ek_e3_panel }, + { } +}; +MODULE_DEVICE_TABLE(spi, st7789v_spi_id); + static const struct of_device_id st7789v_of_match[] = { - { .compatible = "sitronix,st7789v" }, + { .compatible = "sitronix,st7789v", .data = &default_panel }, + { .compatible = "inanbo,t28cp45tn89-v17", .data = &t28cp45tn89_panel }, + { .compatible = "edt,et028013dma", .data = &et028013dma_panel }, + { .compatible = "jasonic,jt240mhqs-hwt-ek-e3", + .data = &jt240mhqs_hwt_ek_e3_panel }, { } }; MODULE_DEVICE_TABLE(of, st7789v_of_match); @@ -436,6 +684,7 @@ MODULE_DEVICE_TABLE(of, st7789v_of_match); static struct spi_driver st7789v_driver = { .probe = st7789v_probe, .remove = st7789v_remove, + .id_table = st7789v_spi_id, .driver = { .name = "st7789v", .of_match_table = st7789v_of_match, 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..fe043de791b0 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-sony-acx565akm.c @@ -0,0 +1,682 @@ +// 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 = backlight_get_brightness(dev); + + 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 (!backlight_is_blank(dev)) + 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 = { + .power = BACKLIGHT_POWER_ON, + .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) +{ + 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, + .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: %3phN\n", lcd->display_id); + + 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_drm_panel_alloc(&spi->dev, struct acx565akm_panel, panel, + &acx565akm_funcs, DRM_MODE_CONNECTOR_DPI); + if (IS_ERR(lcd)) + return PTR_ERR(lcd); + + 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_HIGH); + 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_add(&lcd->panel); + + return 0; +} + +static void 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); +} + +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-sony-td4353-jdi.c b/drivers/gpu/drm/panel/panel-sony-td4353-jdi.c new file mode 100644 index 000000000000..7c989b70ab51 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-sony-td4353-jdi.c @@ -0,0 +1,257 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2022 Konrad Dybcio <konrad.dybcio@somainline.org> + * + * Generated with linux-mdss-dsi-panel-driver-generator with a + * substantial amount of manual adjustments. + * + * SONY Downstream kernel calls this one: + * - "JDI ID3" for Akari (XZ2) + * - "JDI ID4" for Apollo (XZ2 Compact) + */ + +#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> + +enum { + TYPE_TAMA_60HZ, + /* + * Leaving room for expansion - SONY very often uses + * *truly reliably* overclockable panels on their flagships! + */ +}; + +struct sony_td4353_jdi { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct regulator_bulk_data supplies[3]; + struct gpio_desc *panel_reset_gpio; + struct gpio_desc *touch_reset_gpio; + int type; +}; + +static inline struct sony_td4353_jdi *to_sony_td4353_jdi(struct drm_panel *panel) +{ + return container_of(panel, struct sony_td4353_jdi, panel); +} + +static int sony_td4353_jdi_on(struct sony_td4353_jdi *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + mipi_dsi_dcs_set_column_address_multi(&dsi_ctx, 0x0000, 1080 - 1); + mipi_dsi_dcs_set_page_address_multi(&dsi_ctx, 0x0000, 2160 - 1); + mipi_dsi_dcs_set_tear_scanline_multi(&dsi_ctx, 0); + mipi_dsi_dcs_set_tear_on_multi(&dsi_ctx, MIPI_DSI_DCS_TEAR_MODE_VBLANK); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_ADDRESS_MODE, 0x00); + + mipi_dsi_dcs_set_pixel_format_multi(&dsi_ctx, 0x77); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_PARTIAL_ROWS, + 0x00, 0x00, 0x08, 0x6f); + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 70); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_MEMORY_START); + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + + return dsi_ctx.accum_err; +} + +static void sony_td4353_jdi_off(struct sony_td4353_jdi *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 22); + mipi_dsi_dcs_set_tear_off_multi(&dsi_ctx); + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 80); +} + +static void sony_td4353_assert_reset_gpios(struct sony_td4353_jdi *ctx, int mode) +{ + gpiod_set_value_cansleep(ctx->touch_reset_gpio, mode); + gpiod_set_value_cansleep(ctx->panel_reset_gpio, mode); + usleep_range(5000, 5100); +} + +static int sony_td4353_jdi_prepare(struct drm_panel *panel) +{ + struct sony_td4353_jdi *ctx = to_sony_td4353_jdi(panel); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + if (ret < 0) + return ret; + + msleep(100); + + sony_td4353_assert_reset_gpios(ctx, 1); + + ret = sony_td4353_jdi_on(ctx); + if (ret < 0) { + sony_td4353_assert_reset_gpios(ctx, 0); + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + return ret; + } + + return 0; +} + +static int sony_td4353_jdi_unprepare(struct drm_panel *panel) +{ + struct sony_td4353_jdi *ctx = to_sony_td4353_jdi(panel); + + sony_td4353_jdi_off(ctx); + + sony_td4353_assert_reset_gpios(ctx, 0); + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + + return 0; +} + +static const struct drm_display_mode sony_td4353_jdi_mode_tama_60hz = { + .clock = (1080 + 4 + 8 + 8) * (2160 + 259 + 8 + 8) * 60 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 4, + .hsync_end = 1080 + 4 + 8, + .htotal = 1080 + 4 + 8 + 8, + .vdisplay = 2160, + .vsync_start = 2160 + 259, + .vsync_end = 2160 + 259 + 8, + .vtotal = 2160 + 259 + 8 + 8, + .width_mm = 64, + .height_mm = 128, +}; + +static int sony_td4353_jdi_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct sony_td4353_jdi *ctx = to_sony_td4353_jdi(panel); + struct drm_display_mode *mode = NULL; + + if (ctx->type == TYPE_TAMA_60HZ) + mode = drm_mode_duplicate(connector->dev, &sony_td4353_jdi_mode_tama_60hz); + else + return -EINVAL; + + if (!mode) + 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 sony_td4353_jdi_panel_funcs = { + .prepare = sony_td4353_jdi_prepare, + .unprepare = sony_td4353_jdi_unprepare, + .get_modes = sony_td4353_jdi_get_modes, +}; + +static int sony_td4353_jdi_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct sony_td4353_jdi *ctx; + int ret; + + ctx = devm_drm_panel_alloc(dev, struct sony_td4353_jdi, panel, + &sony_td4353_jdi_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ctx->type = (uintptr_t)of_device_get_match_data(dev); + + ctx->supplies[0].supply = "vddio"; + ctx->supplies[1].supply = "vsp"; + ctx->supplies[2].supply = "vsn"; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), + ctx->supplies); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get regulators\n"); + + ctx->panel_reset_gpio = devm_gpiod_get(dev, "panel-reset", GPIOD_ASIS); + if (IS_ERR(ctx->panel_reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->panel_reset_gpio), + "Failed to get panel-reset-gpios\n"); + + ctx->touch_reset_gpio = devm_gpiod_get(dev, "touch-reset", GPIOD_ASIS); + if (IS_ERR(ctx->touch_reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->touch_reset_gpio), + "Failed to get touch-reset-gpios\n"); + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS; + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return dev_err_probe(dev, ret, "Failed to get backlight\n"); + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "Failed to attach to DSI host: %d\n", ret); + drm_panel_remove(&ctx->panel); + return ret; + } + + return 0; +} + +static void sony_td4353_jdi_remove(struct mipi_dsi_device *dsi) +{ + struct sony_td4353_jdi *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id sony_td4353_jdi_of_match[] = { + { .compatible = "sony,td4353-jdi-tama", .data = (void *)TYPE_TAMA_60HZ }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, sony_td4353_jdi_of_match); + +static struct mipi_dsi_driver sony_td4353_jdi_driver = { + .probe = sony_td4353_jdi_probe, + .remove = sony_td4353_jdi_remove, + .driver = { + .name = "panel-sony-td4353-jdi", + .of_match_table = sony_td4353_jdi_of_match, + }, +}; +module_mipi_dsi_driver(sony_td4353_jdi_driver); + +MODULE_AUTHOR("Konrad Dybcio <konrad.dybcio@somainline.org>"); +MODULE_DESCRIPTION("DRM panel driver for SONY Xperia XZ2/XZ2c JDI panel"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-sony-tulip-truly-nt35521.c b/drivers/gpu/drm/panel/panel-sony-tulip-truly-nt35521.c new file mode 100644 index 000000000000..216a6ad8696e --- /dev/null +++ b/drivers/gpu/drm/panel/panel-sony-tulip-truly-nt35521.c @@ -0,0 +1,517 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021, Linaro Limited + * + * Generated with linux-mdss-dsi-panel-driver-generator from vendor device tree: + * Copyright (c) 2013, The Linux Foundation. All rights reserved. + */ + +#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 <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +struct truly_nt35521 { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct regulator_bulk_data supplies[2]; + struct gpio_desc *reset_gpio; + struct gpio_desc *blen_gpio; +}; + +#define NT35521_DCS_SWITCH_PAGE 0xf0 + +#define nt35521_switch_page(dsi_ctx, page) \ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, NT35521_DCS_SWITCH_PAGE, \ + 0x55, 0xaa, 0x52, 0x08, (page)) + +static inline +struct truly_nt35521 *to_truly_nt35521(struct drm_panel *panel) +{ + return container_of(panel, struct truly_nt35521, panel); +} + +static void truly_nt35521_reset(struct truly_nt35521 *ctx) +{ + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(1000, 2000); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(10000, 11000); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + msleep(150); +} + +static int truly_nt35521_on(struct truly_nt35521 *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + nt35521_switch_page(&dsi_ctx, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xff, 0xaa, 0x55, 0xa5, 0x80); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x6f, 0x11, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xf7, 0x20, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x6f, 0x01); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb1, 0x21); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xbd, 0x01, 0xa0, 0x10, 0x08, 0x01); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb8, 0x01, 0x02, 0x0c, 0x02); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xbb, 0x11, 0x11); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xbc, 0x00, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb6, 0x02); + + nt35521_switch_page(&dsi_ctx, 0x01); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb0, 0x09, 0x09); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb1, 0x09, 0x09); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xbc, 0x8c, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xbd, 0x8c, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xca, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xc0, 0x04); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xbe, 0xb5); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb3, 0x35, 0x35); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb4, 0x25, 0x25); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb9, 0x43, 0x43); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xba, 0x24, 0x24); + + nt35521_switch_page(&dsi_ctx, 0x02); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xee, 0x03); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb0, + 0x00, 0xb2, 0x00, 0xb3, 0x00, 0xb6, 0x00, 0xc3, + 0x00, 0xce, 0x00, 0xe1, 0x00, 0xf3, 0x01, 0x11); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb1, + 0x01, 0x2e, 0x01, 0x5c, 0x01, 0x82, 0x01, 0xc3, + 0x01, 0xfe, 0x02, 0x00, 0x02, 0x37, 0x02, 0x77); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb2, + 0x02, 0xa1, 0x02, 0xd7, 0x02, 0xfe, 0x03, 0x2c, + 0x03, 0x4b, 0x03, 0x63, 0x03, 0x8f, 0x03, 0x90); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb3, 0x03, 0x96, 0x03, 0x98); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb4, + 0x00, 0x81, 0x00, 0x8b, 0x00, 0x9c, 0x00, 0xa9, + 0x00, 0xb5, 0x00, 0xcb, 0x00, 0xdf, 0x01, 0x02); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb5, + 0x01, 0x1f, 0x01, 0x51, 0x01, 0x7a, 0x01, 0xbf, + 0x01, 0xfa, 0x01, 0xfc, 0x02, 0x34, 0x02, 0x76); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb6, + 0x02, 0x9f, 0x02, 0xd7, 0x02, 0xfc, 0x03, 0x2c, + 0x03, 0x4a, 0x03, 0x63, 0x03, 0x8f, 0x03, 0xa2); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb7, 0x03, 0xb8, 0x03, 0xba); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb8, + 0x00, 0x01, 0x00, 0x02, 0x00, 0x0e, 0x00, 0x2a, + 0x00, 0x41, 0x00, 0x67, 0x00, 0x87, 0x00, 0xb9); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb9, + 0x00, 0xe2, 0x01, 0x22, 0x01, 0x54, 0x01, 0xa3, + 0x01, 0xe6, 0x01, 0xe7, 0x02, 0x24, 0x02, 0x67); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xba, + 0x02, 0x93, 0x02, 0xcd, 0x02, 0xf6, 0x03, 0x31, + 0x03, 0x6c, 0x03, 0xe9, 0x03, 0xef, 0x03, 0xf4); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xbb, 0x03, 0xf6, 0x03, 0xf7); + + nt35521_switch_page(&dsi_ctx, 0x03); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb0, 0x22, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb1, 0x22, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb2, 0x05, 0x00, 0x60, 0x00, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb3, 0x05, 0x00, 0x60, 0x00, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb4, 0x05, 0x00, 0x60, 0x00, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb5, 0x05, 0x00, 0x60, 0x00, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xba, 0x53, 0x00, 0x60, 0x00, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xbb, 0x53, 0x00, 0x60, 0x00, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xbc, 0x53, 0x00, 0x60, 0x00, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xbd, 0x53, 0x00, 0x60, 0x00, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xc0, 0x00, 0x34, 0x00, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xc1, 0x00, 0x00, 0x34, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xc2, 0x00, 0x00, 0x34, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xc3, 0x00, 0x00, 0x34, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xc4, 0x60); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xc5, 0xc0); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xc6, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xc7, 0x00); + + nt35521_switch_page(&dsi_ctx, 0x05); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb0, 0x17, 0x06); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb1, 0x17, 0x06); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb2, 0x17, 0x06); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb3, 0x17, 0x06); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb4, 0x17, 0x06); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb5, 0x17, 0x06); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb6, 0x17, 0x06); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb7, 0x17, 0x06); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb8, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb9, 0x00, 0x03); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xba, 0x00, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xbb, 0x02, 0x03); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xbc, 0x02, 0x03); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xbd, 0x03, 0x03, 0x00, 0x03, 0x03); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xc0, 0x0b); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xc1, 0x09); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xc2, 0xa6); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xc3, 0x05); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xc4, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xc5, 0x02); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xc6, 0x22); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xc7, 0x03); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xc8, 0x07, 0x20); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xc9, 0x03, 0x20); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xca, 0x01, 0x60); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xcb, 0x01, 0x60); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xcc, 0x00, 0x00, 0x02); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xcd, 0x00, 0x00, 0x02); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xce, 0x00, 0x00, 0x02); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xcf, 0x00, 0x00, 0x02); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xd1, 0x00, 0x05, 0x01, 0x07, 0x10); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xd2, 0x10, 0x05, 0x05, 0x03, 0x10); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xd3, 0x20, 0x00, 0x43, 0x07, 0x10); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xd4, 0x30, 0x00, 0x43, 0x07, 0x10); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xd0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xd5, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xd6, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xd7, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xe5, 0x06); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xe6, 0x06); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xe7, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xe8, 0x06); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xe9, 0x06); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xea, 0x06); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xeb, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xec, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xed, 0x30); + + nt35521_switch_page(&dsi_ctx, 0x06); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb0, 0x31, 0x31); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb1, 0x31, 0x31); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb2, 0x2d, 0x2e); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb3, 0x31, 0x34); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb4, 0x29, 0x2a); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb5, 0x12, 0x10); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb6, 0x18, 0x16); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb7, 0x00, 0x02); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb8, 0x08, 0x31); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb9, 0x31, 0x31); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xba, 0x31, 0x31); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xbb, 0x31, 0x08); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xbc, 0x03, 0x01); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xbd, 0x17, 0x19); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xbe, 0x11, 0x13); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xbf, 0x2a, 0x29); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xc0, 0x34, 0x31); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xc1, 0x2e, 0x2d); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xc2, 0x31, 0x31); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xc3, 0x31, 0x31); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xc4, 0x31, 0x31); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xc5, 0x31, 0x31); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xc6, 0x2e, 0x2d); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xc7, 0x31, 0x34); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xc8, 0x29, 0x2a); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xc9, 0x17, 0x19); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xca, 0x11, 0x13); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xcb, 0x03, 0x01); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xcc, 0x08, 0x31); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xcd, 0x31, 0x31); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xce, 0x31, 0x31); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xcf, 0x31, 0x08); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xd0, 0x00, 0x02); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xd1, 0x12, 0x10); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xd2, 0x18, 0x16); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xd3, 0x2a, 0x29); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xd4, 0x34, 0x31); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xd5, 0x2d, 0x2e); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xd6, 0x31, 0x31); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xd7, 0x31, 0x31); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xe5, 0x31, 0x31); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xe6, 0x31, 0x31); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xd9, 0x00, 0x00, 0x00, 0x00, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xe7, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x6f, 0x02); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xf7, 0x47); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x6f, 0x0a); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xf7, 0x02); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x6f, 0x17); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xf4, 0x60); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x6f, 0x01); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xf9, 0x46); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x6f, 0x11); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xf3, 0x01); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x35, 0x00); + + nt35521_switch_page(&dsi_ctx, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xd9, 0x02, 0x03, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xf0, 0x55, 0xaa, 0x52, 0x00, 0x00); + + nt35521_switch_page(&dsi_ctx, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xb1, 0x6c, 0x21); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xf0, 0x55, 0xaa, 0x52, 0x00, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x35, 0x00); + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 120); + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + + usleep_range(1000, 2000); + + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x53, 0x24); + + return dsi_ctx.accum_err; +} + +static int truly_nt35521_off(struct truly_nt35521 *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 50); + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 150); + + return dsi_ctx.accum_err; +} + +static int truly_nt35521_prepare(struct drm_panel *panel) +{ + struct truly_nt35521 *ctx = to_truly_nt35521(panel); + struct device *dev = &ctx->dsi->dev; + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + if (ret < 0) { + dev_err(dev, "Failed to enable regulators: %d\n", ret); + return ret; + } + + truly_nt35521_reset(ctx); + + ret = truly_nt35521_on(ctx); + if (ret < 0) { + dev_err(dev, "Failed to initialize panel: %d\n", ret); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + return ret; + } + + return 0; +} + +static int truly_nt35521_unprepare(struct drm_panel *panel) +{ + struct truly_nt35521 *ctx = to_truly_nt35521(panel); + struct device *dev = &ctx->dsi->dev; + int ret; + + ret = truly_nt35521_off(ctx); + if (ret < 0) + dev_err(dev, "Failed to un-initialize panel: %d\n", ret); + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), + ctx->supplies); + + return 0; +} + +static int truly_nt35521_enable(struct drm_panel *panel) +{ + struct truly_nt35521 *ctx = to_truly_nt35521(panel); + + gpiod_set_value_cansleep(ctx->blen_gpio, 1); + + return 0; +} + +static int truly_nt35521_disable(struct drm_panel *panel) +{ + struct truly_nt35521 *ctx = to_truly_nt35521(panel); + + gpiod_set_value_cansleep(ctx->blen_gpio, 0); + + return 0; +} + +static const struct drm_display_mode truly_nt35521_mode = { + .clock = (720 + 232 + 20 + 112) * (1280 + 18 + 1 + 18) * 60 / 1000, + .hdisplay = 720, + .hsync_start = 720 + 232, + .hsync_end = 720 + 232 + 20, + .htotal = 720 + 232 + 20 + 112, + .vdisplay = 1280, + .vsync_start = 1280 + 18, + .vsync_end = 1280 + 18 + 1, + .vtotal = 1280 + 18 + 1 + 18, + .width_mm = 65, + .height_mm = 116, +}; + +static int truly_nt35521_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &truly_nt35521_mode); + if (!mode) + 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 truly_nt35521_panel_funcs = { + .prepare = truly_nt35521_prepare, + .unprepare = truly_nt35521_unprepare, + .enable = truly_nt35521_enable, + .disable = truly_nt35521_disable, + .get_modes = truly_nt35521_get_modes, +}; + +static int truly_nt35521_bl_update_status(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + u16 brightness = backlight_get_brightness(bl); + int ret; + + ret = mipi_dsi_dcs_set_display_brightness(dsi, brightness); + if (ret < 0) + return ret; + + return 0; +} + +static int truly_nt35521_bl_get_brightness(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + u16 brightness; + int ret; + + ret = mipi_dsi_dcs_get_display_brightness(dsi, &brightness); + if (ret < 0) + return ret; + + return brightness & 0xff; +} + +static const struct backlight_ops truly_nt35521_bl_ops = { + .update_status = truly_nt35521_bl_update_status, + .get_brightness = truly_nt35521_bl_get_brightness, +}; + +static struct backlight_device * +truly_nt35521_create_backlight(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + const struct backlight_properties props = { + .type = BACKLIGHT_RAW, + .brightness = 255, + .max_brightness = 255, + }; + + return devm_backlight_device_register(dev, dev_name(dev), dev, dsi, + &truly_nt35521_bl_ops, &props); +} + +static int truly_nt35521_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct truly_nt35521 *ctx; + int ret; + + ctx = devm_drm_panel_alloc(dev, struct truly_nt35521, panel, + &truly_nt35521_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ctx->supplies[0].supply = "positive5"; + ctx->supplies[1].supply = "negative5"; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), + ctx->supplies); + if (ret < 0) { + dev_err(dev, "Failed to get regulators: %d\n", ret); + return ret; + } + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), + "Failed to get reset-gpios\n"); + + ctx->blen_gpio = devm_gpiod_get(dev, "backlight", GPIOD_OUT_LOW); + if (IS_ERR(ctx->blen_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->blen_gpio), + "Failed to get backlight-gpios\n"); + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_NO_EOT_PACKET | + MIPI_DSI_CLOCK_NON_CONTINUOUS; + + ctx->panel.backlight = truly_nt35521_create_backlight(dsi); + if (IS_ERR(ctx->panel.backlight)) + return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight), + "Failed to create backlight\n"); + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "Failed to attach to DSI host: %d\n", ret); + drm_panel_remove(&ctx->panel); + return ret; + } + + return 0; +} + +static void truly_nt35521_remove(struct mipi_dsi_device *dsi) +{ + struct truly_nt35521 *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id truly_nt35521_of_match[] = { + { .compatible = "sony,tulip-truly-nt35521" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, truly_nt35521_of_match); + +static struct mipi_dsi_driver truly_nt35521_driver = { + .probe = truly_nt35521_probe, + .remove = truly_nt35521_remove, + .driver = { + .name = "panel-truly-nt35521", + .of_match_table = truly_nt35521_of_match, + }, +}; +module_mipi_dsi_driver(truly_nt35521_driver); + +MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>"); +MODULE_DESCRIPTION("DRM driver for Sony Tulip Truly NT35521 panel"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-startek-kd070fhfid015.c b/drivers/gpu/drm/panel/panel-startek-kd070fhfid015.c new file mode 100644 index 000000000000..c0c95355b743 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-startek-kd070fhfid015.c @@ -0,0 +1,350 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2016 InforceComputing + * Copyright (C) 2016 Linaro Ltd + * Copyright (C) 2023 BayLibre, SAS + * + * Authors: + * - Vinay Simha BN <simhavcs@gmail.com> + * - Sumit Semwal <sumit.semwal@linaro.org> + * - Guillaume La Roque <glaroque@baylibre.com> + * + */ + +#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> + +#define DSI_REG_MCAP 0xb0 +#define DSI_REG_IS 0xb3 /* Interface Setting */ +#define DSI_REG_IIS 0xb4 /* Interface ID Setting */ +#define DSI_REG_CTRL 0xb6 + +enum { + IOVCC = 0, + POWER = 1 +}; + +struct stk_panel { + const struct drm_display_mode *mode; + struct backlight_device *backlight; + struct drm_panel base; + struct gpio_desc *enable_gpio; /* Power IC supply enable */ + struct gpio_desc *reset_gpio; /* External reset */ + struct mipi_dsi_device *dsi; + struct regulator_bulk_data supplies[2]; +}; + +static inline struct stk_panel *to_stk_panel(struct drm_panel *panel) +{ + return container_of(panel, struct stk_panel, base); +} + +static int stk_panel_init(struct stk_panel *stk) +{ + struct mipi_dsi_device *dsi = stk->dsi; + struct mipi_dsi_multi_context dsi_ctx = {.dsi = dsi}; + + mipi_dsi_dcs_soft_reset_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 5); + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 120); + + mipi_dsi_generic_write_seq_multi(&dsi_ctx, DSI_REG_MCAP, 0x04); + + /* Interface setting, video mode */ + mipi_dsi_generic_write_seq_multi(&dsi_ctx, DSI_REG_IS, 0x14, 0x08, 0x00, 0x22, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, DSI_REG_IIS, 0x0c, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, DSI_REG_CTRL, 0x3a, 0xd3); + + mipi_dsi_dcs_set_display_brightness_multi(&dsi_ctx, 0x77); + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY, + MIPI_DCS_WRITE_MEMORY_START); + + mipi_dsi_dcs_set_pixel_format_multi(&dsi_ctx, 0x77); + mipi_dsi_dcs_set_column_address_multi(&dsi_ctx, 0, stk->mode->hdisplay - 1); + mipi_dsi_dcs_set_page_address_multi(&dsi_ctx, 0, stk->mode->vdisplay - 1); + + return dsi_ctx.accum_err; +} + +static int stk_panel_on(struct stk_panel *stk) +{ + struct mipi_dsi_device *dsi = stk->dsi; + struct mipi_dsi_multi_context dsi_ctx = {.dsi = dsi}; + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + + mipi_dsi_msleep(&dsi_ctx, 20); + + return dsi_ctx.accum_err; +} + +static void stk_panel_off(struct stk_panel *stk) +{ + struct mipi_dsi_device *dsi = stk->dsi; + struct mipi_dsi_multi_context dsi_ctx = {.dsi = dsi}; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + + mipi_dsi_msleep(&dsi_ctx, 100); +} + +static int stk_panel_unprepare(struct drm_panel *panel) +{ + struct stk_panel *stk = to_stk_panel(panel); + + stk_panel_off(stk); + regulator_bulk_disable(ARRAY_SIZE(stk->supplies), stk->supplies); + gpiod_set_value(stk->reset_gpio, 0); + gpiod_set_value(stk->enable_gpio, 1); + + return 0; +} + +static int stk_panel_prepare(struct drm_panel *panel) +{ + struct stk_panel *stk = to_stk_panel(panel); + int ret; + + gpiod_set_value(stk->reset_gpio, 0); + gpiod_set_value(stk->enable_gpio, 0); + ret = regulator_enable(stk->supplies[IOVCC].consumer); + if (ret < 0) + return ret; + + mdelay(8); + ret = regulator_enable(stk->supplies[POWER].consumer); + if (ret < 0) + goto iovccoff; + + mdelay(20); + gpiod_set_value(stk->enable_gpio, 1); + mdelay(20); + gpiod_set_value(stk->reset_gpio, 1); + mdelay(10); + ret = stk_panel_init(stk); + if (ret < 0) + goto poweroff; + + ret = stk_panel_on(stk); + if (ret < 0) + goto poweroff; + + return 0; + +poweroff: + regulator_disable(stk->supplies[POWER].consumer); +iovccoff: + regulator_disable(stk->supplies[IOVCC].consumer); + gpiod_set_value(stk->reset_gpio, 0); + gpiod_set_value(stk->enable_gpio, 0); + + return ret; +} + +static const struct drm_display_mode default_mode = { + .clock = 163204, + .hdisplay = 1200, + .hsync_start = 1200 + 144, + .hsync_end = 1200 + 144 + 16, + .htotal = 1200 + 144 + 16 + 45, + .vdisplay = 1920, + .vsync_start = 1920 + 8, + .vsync_end = 1920 + 8 + 4, + .vtotal = 1920 + 8 + 4 + 4, + .width_mm = 95, + .height_mm = 151, +}; + +static int stk_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) { + dev_err(panel->dev, "failed to add mode %ux%ux@%u\n", + default_mode.hdisplay, default_mode.vdisplay, + drm_mode_vrefresh(&default_mode)); + return -ENOMEM; + } + + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + connector->display_info.width_mm = default_mode.width_mm; + connector->display_info.height_mm = default_mode.height_mm; + return 1; +} + +static int dsi_dcs_bl_get_brightness(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + int ret; + u16 brightness; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + ret = mipi_dsi_dcs_get_display_brightness(dsi, &brightness); + if (ret < 0) + return ret; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + return brightness & 0xff; +} + +static int dsi_dcs_bl_update_status(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + struct mipi_dsi_multi_context dsi_ctx = {.dsi = dsi}; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + mipi_dsi_dcs_set_display_brightness_multi(&dsi_ctx, bl->props.brightness); + if (dsi_ctx.accum_err) + return dsi_ctx.accum_err; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + return dsi_ctx.accum_err; +} + +static const struct backlight_ops dsi_bl_ops = { + .update_status = dsi_dcs_bl_update_status, + .get_brightness = dsi_dcs_bl_get_brightness, +}; + +static struct backlight_device * +drm_panel_create_dsi_backlight(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct backlight_properties props = { + .type = BACKLIGHT_RAW, + .brightness = 255, + .max_brightness = 255, + }; + + return devm_backlight_device_register(dev, dev_name(dev), dev, dsi, + &dsi_bl_ops, &props); +} + +static const struct drm_panel_funcs stk_panel_funcs = { + .unprepare = stk_panel_unprepare, + .prepare = stk_panel_prepare, + .get_modes = stk_panel_get_modes, +}; + +static const struct of_device_id stk_of_match[] = { + { .compatible = "startek,kd070fhfid015", }, + { } +}; +MODULE_DEVICE_TABLE(of, stk_of_match); + +static int stk_panel_add(struct stk_panel *stk) +{ + struct device *dev = &stk->dsi->dev; + int ret; + + stk->mode = &default_mode; + + stk->supplies[IOVCC].supply = "iovcc"; + stk->supplies[POWER].supply = "power"; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(stk->supplies), stk->supplies); + if (ret) { + dev_err(dev, "regulator_bulk failed\n"); + return ret; + } + + stk->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(stk->reset_gpio)) { + ret = PTR_ERR(stk->reset_gpio); + dev_err(dev, "cannot get reset-gpios %d\n", ret); + return ret; + } + + stk->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(stk->enable_gpio)) { + ret = PTR_ERR(stk->enable_gpio); + dev_err(dev, "cannot get enable-gpio %d\n", ret); + return ret; + } + + stk->backlight = drm_panel_create_dsi_backlight(stk->dsi); + if (IS_ERR(stk->backlight)) { + ret = PTR_ERR(stk->backlight); + dev_err(dev, "failed to register backlight %d\n", ret); + return ret; + } + + drm_panel_init(&stk->base, &stk->dsi->dev, &stk_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + + drm_panel_add(&stk->base); + + return 0; +} + +static int stk_panel_probe(struct mipi_dsi_device *dsi) +{ + struct stk_panel *stk; + int ret; + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = (MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM); + + stk = devm_kzalloc(&dsi->dev, sizeof(*stk), GFP_KERNEL); + if (!stk) + return -ENOMEM; + + mipi_dsi_set_drvdata(dsi, stk); + + stk->dsi = dsi; + + ret = stk_panel_add(stk); + if (ret < 0) + return ret; + + ret = mipi_dsi_attach(dsi); + if (ret < 0) + drm_panel_remove(&stk->base); + + return 0; +} + +static void stk_panel_remove(struct mipi_dsi_device *dsi) +{ + struct stk_panel *stk = mipi_dsi_get_drvdata(dsi); + int err; + + err = mipi_dsi_detach(dsi); + if (err < 0) + dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", + err); + + drm_panel_remove(&stk->base); +} + +static struct mipi_dsi_driver stk_panel_driver = { + .driver = { + .name = "panel-startek-kd070fhfid015", + .of_match_table = stk_of_match, + }, + .probe = stk_panel_probe, + .remove = stk_panel_remove, +}; +module_mipi_dsi_driver(stk_panel_driver); + +MODULE_AUTHOR("Guillaume La Roque <glaroque@baylibre.com>"); +MODULE_DESCRIPTION("STARTEK KD070FHFID015"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-summit.c b/drivers/gpu/drm/panel/panel-summit.c new file mode 100644 index 000000000000..6d40b9ddfe02 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-summit.c @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include <linux/backlight.h> +#include <linux/mod_devicetable.h> +#include <linux/property.h> +#include <drm/drm_device.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_mode.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> +#include <drm/drm_probe_helper.h> +#include <video/mipi_display.h> + +struct summit_data { + struct mipi_dsi_device *dsi; + struct backlight_device *bl; + struct drm_panel panel; +}; + +static int summit_set_brightness(struct device *dev) +{ + struct summit_data *s_data = dev_get_drvdata(dev); + int level = backlight_get_brightness(s_data->bl); + + return mipi_dsi_dcs_set_display_brightness(s_data->dsi, level); +} + +static int summit_bl_update_status(struct backlight_device *dev) +{ + return summit_set_brightness(&dev->dev); +} + +static const struct backlight_ops summit_bl_ops = { + .update_status = summit_bl_update_status, +}; + +static struct drm_display_mode summit_mode = { + .vdisplay = 2008, + .hdisplay = 60, + .hsync_start = 60 + 8, + .hsync_end = 60 + 8 + 80, + .htotal = 60 + 8 + 80 + 40, + .vsync_start = 2008 + 1, + .vsync_end = 2008 + 1 + 15, + .vtotal = 2008 + 1 + 15 + 6, + .clock = ((60 + 8 + 80 + 40) * (2008 + 1 + 15 + 6) * 60) / 1000, + .type = DRM_MODE_TYPE_DRIVER, + .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC, +}; + +static int summit_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + connector->display_info.non_desktop = true; + drm_object_property_set_value(&connector->base, + connector->dev->mode_config.non_desktop_property, + connector->display_info.non_desktop); + + return drm_connector_helper_get_modes_fixed(connector, &summit_mode); +} + +static const struct drm_panel_funcs summit_panel_funcs = { + .get_modes = summit_get_modes, +}; + +static int summit_probe(struct mipi_dsi_device *dsi) +{ + struct backlight_properties props = { 0 }; + struct device *dev = &dsi->dev; + struct summit_data *s_data; + int ret; + + s_data = devm_drm_panel_alloc(dev, struct summit_data, panel, + &summit_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(s_data)) + return PTR_ERR(s_data); + + mipi_dsi_set_drvdata(dsi, s_data); + s_data->dsi = dsi; + + ret = device_property_read_u32(dev, "max-brightness", &props.max_brightness); + if (ret) + return ret; + props.type = BACKLIGHT_RAW; + + s_data->bl = devm_backlight_device_register(dev, dev_name(dev), + dev, s_data, &summit_bl_ops, &props); + if (IS_ERR(s_data->bl)) + return PTR_ERR(s_data->bl); + + drm_panel_add(&s_data->panel); + + return mipi_dsi_attach(dsi); +} + +static void summit_remove(struct mipi_dsi_device *dsi) +{ + struct summit_data *s_data = mipi_dsi_get_drvdata(dsi); + + mipi_dsi_detach(dsi); + drm_panel_remove(&s_data->panel); +} + +static int summit_suspend(struct device *dev) +{ + struct summit_data *s_data = dev_get_drvdata(dev); + + return mipi_dsi_dcs_set_display_brightness(s_data->dsi, 0); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(summit_pm_ops, summit_suspend, + summit_set_brightness); + +static const struct of_device_id summit_of_match[] = { + { .compatible = "apple,summit" }, + {}, +}; + +MODULE_DEVICE_TABLE(of, summit_of_match); + +static struct mipi_dsi_driver summit_driver = { + .probe = summit_probe, + .remove = summit_remove, + .driver = { + .name = "panel-summit", + .of_match_table = summit_of_match, + .pm = pm_sleep_ptr(&summit_pm_ops), + }, +}; +module_mipi_dsi_driver(summit_driver); + +MODULE_DESCRIPTION("Summit Display Panel Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-synaptics-r63353.c b/drivers/gpu/drm/panel/panel-synaptics-r63353.c new file mode 100644 index 000000000000..3a74d48753d9 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-synaptics-r63353.c @@ -0,0 +1,331 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Synaptics R63353 Controller driver + * + * Copyright (C) 2020 BSH Hausgerate GmbH + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/media-bus-format.h> + +#include <linux/gpio/consumer.h> +#include <linux/regulator/consumer.h> + +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +#include <video/mipi_display.h> + +#define R63353_INSTR(...) { \ + .len = sizeof((u8[]) {__VA_ARGS__}), \ + .data = (u8[]){__VA_ARGS__} \ + } + +struct r63353_instr { + size_t len; + const u8 *data; +}; + +static const struct r63353_instr sharp_ls068b3sx02_init[] = { + R63353_INSTR(0x51, 0xff), + R63353_INSTR(0x53, 0x0c), + R63353_INSTR(0x55, 0x00), + R63353_INSTR(0x84, 0x00), + R63353_INSTR(0x29), +}; + +struct r63353_desc { + const char *name; + const struct r63353_instr *init; + const size_t init_length; + const struct drm_display_mode *mode; + u32 width_mm; + u32 height_mm; +}; + +struct r63353_panel { + struct drm_panel base; + struct mipi_dsi_device *dsi; + + struct gpio_desc *reset_gpio; + struct regulator *dvdd; + struct regulator *avdd; + + struct r63353_desc *pdata; +}; + +static inline struct r63353_panel *to_r63353_panel(struct drm_panel *panel) +{ + return container_of(panel, struct r63353_panel, base); +} + +static int r63353_panel_power_on(struct r63353_panel *rpanel) +{ + struct mipi_dsi_device *dsi = rpanel->dsi; + struct device *dev = &dsi->dev; + int ret; + + ret = regulator_enable(rpanel->avdd); + if (ret) { + dev_err(dev, "Failed to enable avdd regulator (%d)\n", ret); + return ret; + } + + usleep_range(15000, 25000); + + ret = regulator_enable(rpanel->dvdd); + if (ret) { + dev_err(dev, "Failed to enable dvdd regulator (%d)\n", ret); + regulator_disable(rpanel->avdd); + return ret; + } + + usleep_range(300000, 350000); + gpiod_set_value(rpanel->reset_gpio, 1); + usleep_range(15000, 25000); + + return 0; +} + +static int r63353_panel_power_off(struct r63353_panel *rpanel) +{ + gpiod_set_value(rpanel->reset_gpio, 0); + regulator_disable(rpanel->dvdd); + regulator_disable(rpanel->avdd); + + return 0; +} + +static int r63353_panel_activate(struct r63353_panel *rpanel) +{ + struct mipi_dsi_device *dsi = rpanel->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + int i; + + mipi_dsi_dcs_soft_reset_multi(&dsi_ctx); + + mipi_dsi_usleep_range(&dsi_ctx, 15000, 17000); + + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + + for (i = 0; i < rpanel->pdata->init_length; i++) { + const struct r63353_instr *instr = &rpanel->pdata->init[i]; + + mipi_dsi_dcs_write_buffer_multi(&dsi_ctx, instr->data, + instr->len); + } + + mipi_dsi_msleep(&dsi_ctx, 120); + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + + mipi_dsi_usleep_range(&dsi_ctx, 5000, 10000); + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + + if (dsi_ctx.accum_err) + gpiod_set_value(rpanel->reset_gpio, 0); + + return dsi_ctx.accum_err; +} + +static int r63353_panel_prepare(struct drm_panel *panel) +{ + struct r63353_panel *rpanel = to_r63353_panel(panel); + struct mipi_dsi_device *dsi = rpanel->dsi; + struct device *dev = &dsi->dev; + int ret; + + dev_dbg(dev, "Preparing\n"); + + ret = r63353_panel_power_on(rpanel); + if (ret) + return ret; + + ret = r63353_panel_activate(rpanel); + if (ret) { + r63353_panel_power_off(rpanel); + return ret; + } + + dev_dbg(dev, "Prepared\n"); + return 0; +} + +static void r63353_panel_deactivate(struct r63353_panel *rpanel) +{ + struct mipi_dsi_device *dsi = rpanel->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + + mipi_dsi_usleep_range(&dsi_ctx, 5000, 10000); + + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); +} + +static int r63353_panel_unprepare(struct drm_panel *panel) +{ + struct r63353_panel *rpanel = to_r63353_panel(panel); + + r63353_panel_deactivate(rpanel); + r63353_panel_power_off(rpanel); + + return 0; +} + +static const struct drm_display_mode sharp_ls068b3sx02_timing = { + .clock = 70000, + .hdisplay = 640, + .hsync_start = 640 + 35, + .hsync_end = 640 + 35 + 2, + .htotal = 640 + 35 + 2 + 150, + .vdisplay = 1280, + .vsync_start = 1280 + 2, + .vsync_end = 1280 + 2 + 4, + .vtotal = 1280 + 2 + 4 + 0, +}; + +static int r63353_panel_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct r63353_panel *rpanel = to_r63353_panel(panel); + struct drm_display_mode *mode; + static const u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24; + + mode = drm_mode_duplicate(connector->dev, rpanel->pdata->mode); + if (!mode) + return -ENOMEM; + + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + connector->display_info.width_mm = rpanel->pdata->width_mm; + connector->display_info.height_mm = rpanel->pdata->height_mm; + + drm_display_info_set_bus_formats(&connector->display_info, + &bus_format, 1); + + return 1; +} + +static const struct drm_panel_funcs r63353_panel_funcs = { + .prepare = r63353_panel_prepare, + .unprepare = r63353_panel_unprepare, + .get_modes = r63353_panel_get_modes, +}; + +static int r63353_panel_probe(struct mipi_dsi_device *dsi) +{ + int ret = 0; + struct device *dev = &dsi->dev; + struct r63353_panel *panel; + + panel = devm_drm_panel_alloc(dev, struct r63353_panel, base, + &r63353_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(panel)) + return PTR_ERR(panel); + + mipi_dsi_set_drvdata(dsi, panel); + panel->dsi = dsi; + panel->pdata = (struct r63353_desc *)of_device_get_match_data(dev); + + dev_info(dev, "Panel %s\n", panel->pdata->name); + + dsi->lanes = 2; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | + MIPI_DSI_CLOCK_NON_CONTINUOUS | MIPI_DSI_MODE_LPM | + MIPI_DSI_MODE_VIDEO_SYNC_PULSE | MIPI_DSI_MODE_NO_EOT_PACKET; + + panel->dvdd = devm_regulator_get(dev, "dvdd"); + if (IS_ERR(panel->dvdd)) + return PTR_ERR(panel->dvdd); + panel->avdd = devm_regulator_get(dev, "avdd"); + if (IS_ERR(panel->avdd)) + return PTR_ERR(panel->avdd); + + panel->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(panel->reset_gpio)) { + dev_err(dev, "failed to get RESET GPIO\n"); + return PTR_ERR(panel->reset_gpio); + } + + panel->base.prepare_prev_first = true; + ret = drm_panel_of_backlight(&panel->base); + if (ret) + return ret; + + drm_panel_add(&panel->base); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "mipi_dsi_attach failed: %d\n", ret); + drm_panel_remove(&panel->base); + return ret; + } + + return ret; +} + +static void r63353_panel_remove(struct mipi_dsi_device *dsi) +{ + struct r63353_panel *rpanel = mipi_dsi_get_drvdata(dsi); + struct device *dev = &dsi->dev; + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(dev, "Failed to detach from host (%d)\n", ret); + + drm_panel_remove(&rpanel->base); +} + +static void r63353_panel_shutdown(struct mipi_dsi_device *dsi) +{ + struct r63353_panel *rpanel = mipi_dsi_get_drvdata(dsi); + + drm_panel_unprepare(&rpanel->base); +} + +static const struct r63353_desc sharp_ls068b3sx02_data = { + .name = "Sharp LS068B3SX02", + .mode = &sharp_ls068b3sx02_timing, + .init = sharp_ls068b3sx02_init, + .init_length = ARRAY_SIZE(sharp_ls068b3sx02_init), + .width_mm = 68, + .height_mm = 159, +}; + +static const struct of_device_id r63353_of_match[] = { + { .compatible = "sharp,ls068b3sx02", .data = &sharp_ls068b3sx02_data }, + { } +}; + +MODULE_DEVICE_TABLE(of, r63353_of_match); + +static struct mipi_dsi_driver r63353_panel_driver = { + .driver = { + .name = "r63353-dsi", + .of_match_table = r63353_of_match, + }, + .probe = r63353_panel_probe, + .remove = r63353_panel_remove, + .shutdown = r63353_panel_shutdown, +}; + +module_mipi_dsi_driver(r63353_panel_driver); + +MODULE_AUTHOR("Matthias Proske <Matthias.Proske@bshg.com>"); +MODULE_AUTHOR("Michael Trimarchi <michael@amarulasolutions.com>"); +MODULE_DESCRIPTION("Synaptics R63353 Controller Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-synaptics-tddi.c b/drivers/gpu/drm/panel/panel-synaptics-tddi.c new file mode 100644 index 000000000000..0aea1854710e --- /dev/null +++ b/drivers/gpu/drm/panel/panel-synaptics-tddi.c @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Synaptics TDDI display panel driver. + * + * Copyright (C) 2025 Kaustabh Chakraborty <kauschluss@disroot.org> + */ + +#include <linux/backlight.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_probe_helper.h> + +struct tddi_panel_data { + u8 lanes; + /* wait timings for panel enable */ + u8 delay_ms_sleep_exit; + u8 delay_ms_display_on; + /* wait timings for panel disable */ + u8 delay_ms_display_off; + u8 delay_ms_sleep_enter; +}; + +struct tddi_ctx { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct drm_display_mode mode; + struct backlight_device *backlight; + const struct tddi_panel_data *data; + struct regulator_bulk_data *supplies; + struct gpio_desc *reset_gpio; + struct gpio_desc *backlight_gpio; +}; + +static const struct regulator_bulk_data tddi_supplies[] = { + { .supply = "vio" }, + { .supply = "vsn" }, + { .supply = "vsp" }, +}; + +static inline struct tddi_ctx *to_tddi_ctx(struct drm_panel *panel) +{ + return container_of(panel, struct tddi_ctx, panel); +} + +static int tddi_update_status(struct backlight_device *backlight) +{ + struct tddi_ctx *ctx = bl_get_data(backlight); + struct mipi_dsi_multi_context dsi = { .dsi = ctx->dsi }; + u8 brightness = backlight_get_brightness(backlight); + + if (!ctx->panel.enabled) + return 0; + + mipi_dsi_dcs_set_display_brightness_multi(&dsi, brightness); + + return dsi.accum_err; +} + +static int tddi_prepare(struct drm_panel *panel) +{ + struct tddi_ctx *ctx = to_tddi_ctx(panel); + struct device *dev = &ctx->dsi->dev; + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(tddi_supplies), ctx->supplies); + if (ret < 0) { + dev_err(dev, "failed to enable regulators: %d\n", ret); + return ret; + } + + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(5000, 6000); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(5000, 6000); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(10000, 11000); + + gpiod_set_value_cansleep(ctx->backlight_gpio, 0); + usleep_range(5000, 6000); + + return 0; +} + +static int tddi_unprepare(struct drm_panel *panel) +{ + struct tddi_ctx *ctx = to_tddi_ctx(panel); + + gpiod_set_value_cansleep(ctx->backlight_gpio, 1); + usleep_range(5000, 6000); + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(5000, 6000); + + regulator_bulk_disable(ARRAY_SIZE(tddi_supplies), ctx->supplies); + + return 0; +} + +static int tddi_enable(struct drm_panel *panel) +{ + struct tddi_ctx *ctx = to_tddi_ctx(panel); + struct mipi_dsi_multi_context dsi = { .dsi = ctx->dsi }; + u8 brightness = ctx->backlight->props.brightness; + + mipi_dsi_dcs_write_seq_multi(&dsi, MIPI_DCS_WRITE_POWER_SAVE, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x0c); + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi); + mipi_dsi_msleep(&dsi, ctx->data->delay_ms_sleep_exit); + + /* sync the panel with the backlight's brightness level */ + mipi_dsi_dcs_set_display_brightness_multi(&dsi, brightness); + + mipi_dsi_dcs_set_display_on_multi(&dsi); + mipi_dsi_msleep(&dsi, ctx->data->delay_ms_display_on); + + return dsi.accum_err; +}; + +static int tddi_disable(struct drm_panel *panel) +{ + struct tddi_ctx *ctx = to_tddi_ctx(panel); + struct mipi_dsi_multi_context dsi = { .dsi = ctx->dsi }; + + mipi_dsi_dcs_set_display_off_multi(&dsi); + mipi_dsi_msleep(&dsi, ctx->data->delay_ms_display_off); + + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi); + mipi_dsi_msleep(&dsi, ctx->data->delay_ms_sleep_enter); + + return dsi.accum_err; +} + +static int tddi_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct tddi_ctx *ctx = to_tddi_ctx(panel); + + return drm_connector_helper_get_modes_fixed(connector, &ctx->mode); +} + +static const struct backlight_ops tddi_bl_ops = { + .update_status = tddi_update_status, +}; + +static const struct backlight_properties tddi_bl_props = { + .type = BACKLIGHT_PLATFORM, + .brightness = 255, + .max_brightness = 255, +}; + +static const struct drm_panel_funcs tddi_drm_panel_funcs = { + .prepare = tddi_prepare, + .unprepare = tddi_unprepare, + .enable = tddi_enable, + .disable = tddi_disable, + .get_modes = tddi_get_modes, +}; + +static int tddi_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct tddi_ctx *ctx; + int ret; + + ctx = devm_drm_panel_alloc(dev, struct tddi_ctx, panel, + &tddi_drm_panel_funcs, DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ctx->data = of_device_get_match_data(dev); + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + ret = devm_regulator_bulk_get_const(dev, ARRAY_SIZE(tddi_supplies), + tddi_supplies, &ctx->supplies); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to get regulators\n"); + + ctx->backlight_gpio = devm_gpiod_get_optional(dev, "backlight", GPIOD_ASIS); + if (IS_ERR(ctx->backlight_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->backlight_gpio), + "failed to get backlight-gpios\n"); + + ctx->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_ASIS); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), + "failed to get reset-gpios\n"); + + ret = of_get_drm_panel_display_mode(dev->of_node, &ctx->mode, NULL); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to get panel timings\n"); + + ctx->backlight = devm_backlight_device_register(dev, dev_name(dev), dev, + ctx, &tddi_bl_ops, + &tddi_bl_props); + if (IS_ERR(ctx->backlight)) + return dev_err_probe(dev, PTR_ERR(ctx->backlight), + "failed to register backlight device"); + + dsi->lanes = ctx->data->lanes; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_VIDEO_NO_HFP; + + ctx->panel.prepare_prev_first = true; + drm_panel_add(&ctx->panel); + + ret = devm_mipi_dsi_attach(dev, dsi); + if (ret < 0) { + drm_panel_remove(&ctx->panel); + return dev_err_probe(dev, ret, "failed to attach to DSI host\n"); + } + + return 0; +} + +static void tddi_remove(struct mipi_dsi_device *dsi) +{ + struct tddi_ctx *ctx = mipi_dsi_get_drvdata(dsi); + + drm_panel_remove(&ctx->panel); +} + +static const struct tddi_panel_data td4101_panel_data = { + .lanes = 2, + /* wait timings for panel enable */ + .delay_ms_sleep_exit = 100, + .delay_ms_display_on = 0, + /* wait timings for panel disable */ + .delay_ms_display_off = 20, + .delay_ms_sleep_enter = 90, +}; + +static const struct tddi_panel_data td4300_panel_data = { + .lanes = 4, + /* wait timings for panel enable */ + .delay_ms_sleep_exit = 100, + .delay_ms_display_on = 0, + /* wait timings for panel disable */ + .delay_ms_display_off = 0, + .delay_ms_sleep_enter = 0, +}; + +static const struct of_device_id tddi_of_device_id[] = { + { + .compatible = "syna,td4101-panel", + .data = &td4101_panel_data, + }, { + .compatible = "syna,td4300-panel", + .data = &td4300_panel_data, + }, { } +}; +MODULE_DEVICE_TABLE(of, tddi_of_device_id); + +static struct mipi_dsi_driver tddi_dsi_driver = { + .probe = tddi_probe, + .remove = tddi_remove, + .driver = { + .name = "panel-synaptics-tddi", + .of_match_table = tddi_of_device_id, + }, +}; +module_mipi_dsi_driver(tddi_dsi_driver); + +MODULE_AUTHOR("Kaustabh Chakraborty <kauschluss@disroot.org>"); +MODULE_DESCRIPTION("Synaptics TDDI Display Panel Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-tdo-tl070wsh30.c b/drivers/gpu/drm/panel/panel-tdo-tl070wsh30.c new file mode 100644 index 000000000000..227f97f9b136 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-tdo-tl070wsh30.c @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 BayLibre, SAS + * Author: Neil Armstrong <narmstrong@baylibre.com> + */ + +#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> + +struct tdo_tl070wsh30_panel { + struct drm_panel base; + struct mipi_dsi_device *link; + + struct regulator *supply; + struct gpio_desc *reset_gpio; +}; + +static inline +struct tdo_tl070wsh30_panel *to_tdo_tl070wsh30_panel(struct drm_panel *panel) +{ + return container_of(panel, struct tdo_tl070wsh30_panel, base); +} + +static int tdo_tl070wsh30_panel_prepare(struct drm_panel *panel) +{ + struct tdo_tl070wsh30_panel *tdo_tl070wsh30 = to_tdo_tl070wsh30_panel(panel); + int err; + + err = regulator_enable(tdo_tl070wsh30->supply); + if (err < 0) + return err; + + usleep_range(10000, 11000); + + gpiod_set_value_cansleep(tdo_tl070wsh30->reset_gpio, 1); + + usleep_range(10000, 11000); + + gpiod_set_value_cansleep(tdo_tl070wsh30->reset_gpio, 0); + + msleep(200); + + err = mipi_dsi_dcs_exit_sleep_mode(tdo_tl070wsh30->link); + if (err < 0) { + dev_err(panel->dev, "failed to exit sleep mode: %d\n", err); + regulator_disable(tdo_tl070wsh30->supply); + return err; + } + + msleep(200); + + err = mipi_dsi_dcs_set_display_on(tdo_tl070wsh30->link); + if (err < 0) { + dev_err(panel->dev, "failed to set display on: %d\n", err); + regulator_disable(tdo_tl070wsh30->supply); + return err; + } + + msleep(20); + + return 0; +} + +static int tdo_tl070wsh30_panel_unprepare(struct drm_panel *panel) +{ + struct tdo_tl070wsh30_panel *tdo_tl070wsh30 = to_tdo_tl070wsh30_panel(panel); + int err; + + err = mipi_dsi_dcs_set_display_off(tdo_tl070wsh30->link); + if (err < 0) + dev_err(panel->dev, "failed to set display off: %d\n", err); + + usleep_range(10000, 11000); + + err = mipi_dsi_dcs_enter_sleep_mode(tdo_tl070wsh30->link); + if (err < 0) { + dev_err(panel->dev, "failed to enter sleep mode: %d\n", err); + return err; + } + + usleep_range(10000, 11000); + + regulator_disable(tdo_tl070wsh30->supply); + + return 0; +} + +static const struct drm_display_mode default_mode = { + .clock = 47250, + .hdisplay = 1024, + .hsync_start = 1024 + 46, + .hsync_end = 1024 + 46 + 80, + .htotal = 1024 + 46 + 80 + 100, + .vdisplay = 600, + .vsync_start = 600 + 5, + .vsync_end = 600 + 5 + 5, + .vtotal = 600 + 5 + 5 + 20, + .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC, +}; + +static int tdo_tl070wsh30_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) { + dev_err(panel->dev, "failed to add mode %ux%u@%u\n", + default_mode.hdisplay, default_mode.vdisplay, + drm_mode_vrefresh(&default_mode)); + return -ENOMEM; + } + + drm_mode_set_name(mode); + + drm_mode_probed_add(connector, mode); + + connector->display_info.width_mm = 154; + connector->display_info.height_mm = 85; + connector->display_info.bpc = 8; + + return 1; +} + +static const struct drm_panel_funcs tdo_tl070wsh30_panel_funcs = { + .unprepare = tdo_tl070wsh30_panel_unprepare, + .prepare = tdo_tl070wsh30_panel_prepare, + .get_modes = tdo_tl070wsh30_panel_get_modes, +}; + +static const struct of_device_id tdo_tl070wsh30_of_match[] = { + { .compatible = "tdo,tl070wsh30", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, tdo_tl070wsh30_of_match); + +static int tdo_tl070wsh30_panel_add(struct tdo_tl070wsh30_panel *tdo_tl070wsh30) +{ + struct device *dev = &tdo_tl070wsh30->link->dev; + int err; + + tdo_tl070wsh30->supply = devm_regulator_get(dev, "power"); + if (IS_ERR(tdo_tl070wsh30->supply)) + return PTR_ERR(tdo_tl070wsh30->supply); + + tdo_tl070wsh30->reset_gpio = devm_gpiod_get(dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(tdo_tl070wsh30->reset_gpio)) { + err = PTR_ERR(tdo_tl070wsh30->reset_gpio); + dev_dbg(dev, "failed to get reset gpio: %d\n", err); + return err; + } + + drm_panel_init(&tdo_tl070wsh30->base, &tdo_tl070wsh30->link->dev, + &tdo_tl070wsh30_panel_funcs, DRM_MODE_CONNECTOR_DSI); + + err = drm_panel_of_backlight(&tdo_tl070wsh30->base); + if (err) + return err; + + drm_panel_add(&tdo_tl070wsh30->base); + + return 0; +} + +static int tdo_tl070wsh30_panel_probe(struct mipi_dsi_device *dsi) +{ + struct tdo_tl070wsh30_panel *tdo_tl070wsh30; + int err; + + 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; + + tdo_tl070wsh30 = devm_kzalloc(&dsi->dev, sizeof(*tdo_tl070wsh30), + GFP_KERNEL); + if (!tdo_tl070wsh30) + return -ENOMEM; + + mipi_dsi_set_drvdata(dsi, tdo_tl070wsh30); + tdo_tl070wsh30->link = dsi; + + err = tdo_tl070wsh30_panel_add(tdo_tl070wsh30); + if (err < 0) + return err; + + return mipi_dsi_attach(dsi); +} + +static void tdo_tl070wsh30_panel_remove(struct mipi_dsi_device *dsi) +{ + struct tdo_tl070wsh30_panel *tdo_tl070wsh30 = mipi_dsi_get_drvdata(dsi); + int err; + + err = mipi_dsi_detach(dsi); + if (err < 0) + dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", err); + + drm_panel_remove(&tdo_tl070wsh30->base); +} + +static struct mipi_dsi_driver tdo_tl070wsh30_panel_driver = { + .driver = { + .name = "panel-tdo-tl070wsh30", + .of_match_table = tdo_tl070wsh30_of_match, + }, + .probe = tdo_tl070wsh30_panel_probe, + .remove = tdo_tl070wsh30_panel_remove, +}; +module_mipi_dsi_driver(tdo_tl070wsh30_panel_driver); + +MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); +MODULE_DESCRIPTION("TDO TL070WSH30 panel driver"); +MODULE_LICENSE("GPL v2"); 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..ee86ff20c1bd --- /dev/null +++ b/drivers/gpu/drm/panel/panel-tpo-td028ttec1.c @@ -0,0 +1,387 @@ +// 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 noinline_for_stack +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 noinline_for_stack +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); + + return jbt_ret_write_0(lcd, JBT_REG_DISPLAY_ON, NULL); +} + +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, + .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_drm_panel_alloc(&spi->dev, struct td028ttec1_panel, panel, + &td028ttec1_funcs, + DRM_MODE_CONNECTOR_DPI); + if (IS_ERR(lcd)) + return PTR_ERR(lcd); + + 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; + } + + ret = drm_panel_of_backlight(&lcd->panel); + if (ret) + return ret; + + drm_panel_add(&lcd->panel); + + return 0; +} + +static void 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); +} + +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..b18af526b54c --- /dev/null +++ b/drivers/gpu/drm/panel/panel-tpo-td043mtea1.c @@ -0,0 +1,504 @@ +// 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 sysfs_emit(buf, "%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 sysfs_emit(buf, "%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, + .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_drm_panel_alloc(&spi->dev, struct td043mtea1_panel, panel, + &td043mtea1_funcs, DRM_MODE_CONNECTOR_DPI); + if (IS_ERR(lcd)) + return PTR_ERR(lcd); + + 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)) + return dev_err_probe(&spi->dev, PTR_ERR(lcd->vcc_reg), + "failed to get VCC regulator\n"); + + lcd->reset_gpio = devm_gpiod_get(&spi->dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(lcd->reset_gpio)) + return dev_err_probe(&spi->dev, PTR_ERR(lcd->reset_gpio), + "failed to get reset GPIO\n"); + + 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_add(&lcd->panel); + + return 0; +} + +static void 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); +} + +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 new file mode 100644 index 000000000000..0beba5c08956 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-tpo-tpg110.c @@ -0,0 +1,486 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Panel driver for the TPO TPG110 400CH LTPS TFT LCD Single Chip + * Digital Driver. + * + * This chip drives a TFT LCD, so it does not know what kind of + * display is actually connected to it, so the width and height of that + * display needs to be supplied from the machine configuration. + * + * Author: + * Linus Walleij <linus.walleij@linaro.org> + */ +#include <drm/drm_modes.h> +#include <drm/drm_panel.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> + +#define TPG110_TEST 0x00 +#define TPG110_CHIPID 0x01 +#define TPG110_CTRL1 0x02 +#define TPG110_RES_MASK GENMASK(2, 0) +#define TPG110_RES_800X480 0x07 +#define TPG110_RES_640X480 0x06 +#define TPG110_RES_480X272 0x05 +#define TPG110_RES_480X640 0x04 +#define TPG110_RES_480X272_D 0x01 /* Dual scan: outputs 800x480 */ +#define TPG110_RES_400X240_D 0x00 /* Dual scan: outputs 800x480 */ +#define TPG110_CTRL2 0x03 +#define TPG110_CTRL2_PM BIT(0) +#define TPG110_CTRL2_RES_PM_CTRL BIT(7) + +/** + * struct tpg110_panel_mode - lookup struct for the supported modes + */ +struct tpg110_panel_mode { + /** + * @name: the name of this panel + */ + const char *name; + /** + * @magic: the magic value from the detection register + */ + u32 magic; + /** + * @mode: the DRM display mode for this panel + */ + struct drm_display_mode mode; + /** + * @bus_flags: the DRM bus flags for this panel e.g. inverted clock + */ + u32 bus_flags; +}; + +/** + * struct tpg110 - state container for the TPG110 panel + */ +struct tpg110 { + /** + * @dev: the container device + */ + struct device *dev; + /** + * @spi: the corresponding SPI device + */ + struct spi_device *spi; + /** + * @panel: the DRM panel instance for this device + */ + struct drm_panel panel; + /** + * @panel_mode: the panel mode as detected + */ + const struct tpg110_panel_mode *panel_mode; + /** + * @width: the width of this panel in mm + */ + u32 width; + /** + * @height: the height of this panel in mm + */ + u32 height; + /** + * @grestb: reset GPIO line + */ + struct gpio_desc *grestb; +}; + +/* + * TPG110 modes, these are the simple modes, the dualscan modes that + * take 400x240 or 480x272 in and display as 800x480 are not listed. + */ +static const struct tpg110_panel_mode tpg110_modes[] = { + { + .name = "800x480 RGB", + .magic = TPG110_RES_800X480, + .mode = { + .clock = 33200, + .hdisplay = 800, + .hsync_start = 800 + 40, + .hsync_end = 800 + 40 + 1, + .htotal = 800 + 40 + 1 + 216, + .vdisplay = 480, + .vsync_start = 480 + 10, + .vsync_end = 480 + 10 + 1, + .vtotal = 480 + 10 + 1 + 35, + }, + .bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE, + }, + { + .name = "640x480 RGB", + .magic = TPG110_RES_640X480, + .mode = { + .clock = 25200, + .hdisplay = 640, + .hsync_start = 640 + 24, + .hsync_end = 640 + 24 + 1, + .htotal = 640 + 24 + 1 + 136, + .vdisplay = 480, + .vsync_start = 480 + 18, + .vsync_end = 480 + 18 + 1, + .vtotal = 480 + 18 + 1 + 27, + }, + .bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE, + }, + { + .name = "480x272 RGB", + .magic = TPG110_RES_480X272, + .mode = { + .clock = 9000, + .hdisplay = 480, + .hsync_start = 480 + 2, + .hsync_end = 480 + 2 + 1, + .htotal = 480 + 2 + 1 + 43, + .vdisplay = 272, + .vsync_start = 272 + 2, + .vsync_end = 272 + 2 + 1, + .vtotal = 272 + 2 + 1 + 12, + }, + .bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE, + }, + { + .name = "480x640 RGB", + .magic = TPG110_RES_480X640, + .mode = { + .clock = 20500, + .hdisplay = 480, + .hsync_start = 480 + 2, + .hsync_end = 480 + 2 + 1, + .htotal = 480 + 2 + 1 + 43, + .vdisplay = 640, + .vsync_start = 640 + 4, + .vsync_end = 640 + 4 + 1, + .vtotal = 640 + 4 + 1 + 8, + }, + .bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE, + }, + { + .name = "400x240 RGB", + .magic = TPG110_RES_400X240_D, + .mode = { + .clock = 8300, + .hdisplay = 400, + .hsync_start = 400 + 20, + .hsync_end = 400 + 20 + 1, + .htotal = 400 + 20 + 1 + 108, + .vdisplay = 240, + .vsync_start = 240 + 2, + .vsync_end = 240 + 2 + 1, + .vtotal = 240 + 2 + 1 + 20, + }, + .bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE, + }, +}; + +static inline struct tpg110 * +to_tpg110(struct drm_panel *panel) +{ + return container_of(panel, struct tpg110, panel); +} + +static u8 tpg110_readwrite_reg(struct tpg110 *tpg, bool write, + u8 address, u8 outval) +{ + struct spi_message m; + struct spi_transfer t[2]; + u8 buf[2]; + int ret; + + spi_message_init(&m); + memset(t, 0, sizeof(t)); + + if (write) { + /* + * Clear address bit 0, 1 when writing, just to be sure + * The actual bit indicating a write here is bit 1, bit + * 0 is just surplus to pad it up to 8 bits. + */ + buf[0] = address << 2; + buf[0] &= ~0x03; + buf[1] = outval; + + t[0].bits_per_word = 8; + t[0].tx_buf = &buf[0]; + t[0].len = 1; + + t[1].tx_buf = &buf[1]; + t[1].len = 1; + t[1].bits_per_word = 8; + } else { + /* Set address bit 0 to 1 to read */ + buf[0] = address << 1; + buf[0] |= 0x01; + + /* + * The last bit/clock is Hi-Z turnaround cycle, so we need + * to send only 7 bits here. The 8th bit is the high impedance + * turn-around cycle. + */ + t[0].bits_per_word = 7; + t[0].tx_buf = &buf[0]; + t[0].len = 1; + + t[1].rx_buf = &buf[1]; + t[1].len = 1; + t[1].bits_per_word = 8; + } + + spi_message_add_tail(&t[0], &m); + spi_message_add_tail(&t[1], &m); + ret = spi_sync(tpg->spi, &m); + if (ret) { + dev_err(tpg->dev, "SPI message error %d\n", ret); + return ret; + } + if (write) + return 0; + /* Read */ + return buf[1]; +} + +static u8 tpg110_read_reg(struct tpg110 *tpg, u8 address) +{ + return tpg110_readwrite_reg(tpg, false, address, 0); +} + +static void tpg110_write_reg(struct tpg110 *tpg, u8 address, u8 outval) +{ + tpg110_readwrite_reg(tpg, true, address, outval); +} + +static int tpg110_startup(struct tpg110 *tpg) +{ + u8 val; + int i; + + /* De-assert the reset signal */ + gpiod_set_value_cansleep(tpg->grestb, 0); + usleep_range(1000, 2000); + dev_dbg(tpg->dev, "de-asserted GRESTB\n"); + + /* Test display communication */ + tpg110_write_reg(tpg, TPG110_TEST, 0x55); + val = tpg110_read_reg(tpg, TPG110_TEST); + if (val != 0x55) { + dev_err(tpg->dev, "failed communication test\n"); + return -ENODEV; + } + + val = tpg110_read_reg(tpg, TPG110_CHIPID); + dev_info(tpg->dev, "TPG110 chip ID: %d version: %d\n", + val >> 4, val & 0x0f); + + /* Show display resolution */ + val = tpg110_read_reg(tpg, TPG110_CTRL1); + val &= TPG110_RES_MASK; + switch (val) { + case TPG110_RES_400X240_D: + dev_info(tpg->dev, "IN 400x240 RGB -> OUT 800x480 RGB (dual scan)\n"); + break; + case TPG110_RES_480X272_D: + dev_info(tpg->dev, "IN 480x272 RGB -> OUT 800x480 RGB (dual scan)\n"); + break; + case TPG110_RES_480X640: + dev_info(tpg->dev, "480x640 RGB\n"); + break; + case TPG110_RES_480X272: + dev_info(tpg->dev, "480x272 RGB\n"); + break; + case TPG110_RES_640X480: + dev_info(tpg->dev, "640x480 RGB\n"); + break; + case TPG110_RES_800X480: + dev_info(tpg->dev, "800x480 RGB\n"); + break; + default: + dev_err(tpg->dev, "ILLEGAL RESOLUTION 0x%02x\n", val); + break; + } + + /* From the producer side, this is the same resolution */ + if (val == TPG110_RES_480X272_D) + val = TPG110_RES_480X272; + + for (i = 0; i < ARRAY_SIZE(tpg110_modes); i++) { + const struct tpg110_panel_mode *pm; + + pm = &tpg110_modes[i]; + if (pm->magic == val) { + tpg->panel_mode = pm; + break; + } + } + if (i == ARRAY_SIZE(tpg110_modes)) { + dev_err(tpg->dev, "unsupported mode (%02x) detected\n", val); + return -ENODEV; + } + + val = tpg110_read_reg(tpg, TPG110_CTRL2); + dev_info(tpg->dev, "resolution and standby is controlled by %s\n", + (val & TPG110_CTRL2_RES_PM_CTRL) ? "software" : "hardware"); + /* Take control over resolution and standby */ + val |= TPG110_CTRL2_RES_PM_CTRL; + tpg110_write_reg(tpg, TPG110_CTRL2, val); + + return 0; +} + +static int tpg110_disable(struct drm_panel *panel) +{ + struct tpg110 *tpg = to_tpg110(panel); + u8 val; + + /* Put chip into standby */ + val = tpg110_read_reg(tpg, TPG110_CTRL2_PM); + val &= ~TPG110_CTRL2_PM; + tpg110_write_reg(tpg, TPG110_CTRL2_PM, val); + + return 0; +} + +static int tpg110_enable(struct drm_panel *panel) +{ + struct tpg110 *tpg = to_tpg110(panel); + u8 val; + + /* Take chip out of standby */ + val = tpg110_read_reg(tpg, TPG110_CTRL2_PM); + val |= TPG110_CTRL2_PM; + tpg110_write_reg(tpg, TPG110_CTRL2_PM, val); + + return 0; +} + +/** + * tpg110_get_modes() - return the appropriate mode + * @panel: the panel to get the mode for + * @connector: reference to the central DRM connector control structure + * + * This currently does not present a forest of modes, instead it + * 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, + struct drm_connector *connector) +{ + struct tpg110 *tpg = to_tpg110(panel); + struct drm_display_mode *mode; + + connector->display_info.width_mm = tpg->width; + connector->display_info.height_mm = tpg->height; + connector->display_info.bus_flags = tpg->panel_mode->bus_flags; + + mode = drm_mode_duplicate(connector->dev, &tpg->panel_mode->mode); + if (!mode) + return -ENOMEM; + drm_mode_set_name(mode); + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + + mode->width_mm = tpg->width; + mode->height_mm = tpg->height; + + drm_mode_probed_add(connector, mode); + + return 1; +} + +static const struct drm_panel_funcs tpg110_drm_funcs = { + .disable = tpg110_disable, + .enable = tpg110_enable, + .get_modes = tpg110_get_modes, +}; + +static int tpg110_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct device_node *np = dev->of_node; + struct tpg110 *tpg; + int ret; + + tpg = devm_drm_panel_alloc(dev, struct tpg110, panel, + &tpg110_drm_funcs, DRM_MODE_CONNECTOR_DPI); + if (IS_ERR(tpg)) + return PTR_ERR(tpg); + + tpg->dev = dev; + + /* We get the physical display dimensions from the DT */ + ret = of_property_read_u32(np, "width-mm", &tpg->width); + if (ret) + dev_err(dev, "no panel width specified\n"); + ret = of_property_read_u32(np, "height-mm", &tpg->height); + if (ret) + dev_err(dev, "no panel height specified\n"); + + /* 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)) { + dev_err(dev, "no GRESTB GPIO\n"); + return -ENODEV; + } + + spi->bits_per_word = 8; + spi->mode |= SPI_3WIRE_HIZ; + ret = spi_setup(spi); + if (ret < 0) { + dev_err(dev, "spi setup failed.\n"); + return ret; + } + tpg->spi = spi; + + ret = tpg110_startup(tpg); + if (ret) + return ret; + + ret = drm_panel_of_backlight(&tpg->panel); + if (ret) + return ret; + + spi_set_drvdata(spi, tpg); + + drm_panel_add(&tpg->panel); + + return 0; +} + +static void tpg110_remove(struct spi_device *spi) +{ + struct tpg110 *tpg = spi_get_drvdata(spi); + + drm_panel_remove(&tpg->panel); +} + +static const struct of_device_id tpg110_match[] = { + { .compatible = "tpo,tpg110", }, + {}, +}; +MODULE_DEVICE_TABLE(of, tpg110_match); + +static const struct spi_device_id tpg110_ids[] = { + { "tpg110" }, + { }, +}; +MODULE_DEVICE_TABLE(spi, tpg110_ids); + +static struct spi_driver tpg110_driver = { + .probe = tpg110_probe, + .remove = tpg110_remove, + .id_table = tpg110_ids, + .driver = { + .name = "tpo-tpg110-panel", + .of_match_table = tpg110_match, + }, +}; +module_spi_driver(tpg110_driver); + +MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>"); +MODULE_DESCRIPTION("TPO TPG110 panel driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-truly-nt35597.c b/drivers/gpu/drm/panel/panel-truly-nt35597.c index fc2a66c53db4..d447db912a61 100644 --- a/drivers/gpu/drm/panel/panel-truly-nt35597.c +++ b/drivers/gpu/drm/panel/panel-truly-nt35597.c @@ -3,18 +3,21 @@ * Copyright (c) 2018, The Linux Foundation. All rights reserved. */ -#include <drm/drmP.h> -#include <drm/drm_panel.h> -#include <drm/drm_mipi_dsi.h> - +#include <linux/backlight.h> +#include <linux/delay.h> #include <linux/gpio/consumer.h> -#include <linux/of_device.h> +#include <linux/module.h> +#include <linux/of.h> #include <linux/of_graph.h> #include <linux/pinctrl/consumer.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> + static const char * const regulator_names[] = { "vdda", "vdispp", @@ -61,8 +64,6 @@ struct truly_nt35597 { struct mipi_dsi_device *dsi[2]; const struct nt35597_config *config; - bool prepared; - bool enabled; }; static inline struct truly_nt35597 *panel_to_ctx(struct drm_panel *panel) @@ -227,9 +228,7 @@ static int truly_dcs_write(struct drm_panel *panel, u32 command) for (i = 0; i < ARRAY_SIZE(ctx->dsi); i++) { ret = mipi_dsi_dcs_write(ctx->dsi[i], command, NULL, 0); if (ret < 0) { - DRM_DEV_ERROR(ctx->dev, - "cmd 0x%x failed for dsi = %d\n", - command, i); + dev_err(ctx->dev, "cmd 0x%x failed for dsi = %d\n", command, i); } } @@ -246,8 +245,7 @@ static int truly_dcs_write_buf(struct drm_panel *panel, for (i = 0; i < ARRAY_SIZE(ctx->dsi); i++) { ret = mipi_dsi_dcs_write_buffer(ctx->dsi[i], buf, size); if (ret < 0) { - DRM_DEV_ERROR(ctx->dev, - "failed to tx cmd [%d], err: %d\n", i, ret); + dev_err(ctx->dev, "failed to tx cmd [%d], err: %d\n", i, ret); return ret; } } @@ -280,6 +278,7 @@ static int truly_35597_power_on(struct truly_nt35597 *ctx) gpiod_set_value(ctx->reset_gpio, 1); usleep_range(10000, 20000); gpiod_set_value(ctx->reset_gpio, 0); + usleep_range(10000, 20000); return 0; } @@ -295,16 +294,14 @@ static int truly_nt35597_power_off(struct truly_nt35597 *ctx) ret = regulator_set_load(ctx->supplies[i].consumer, regulator_disable_loads[i]); if (ret) { - DRM_DEV_ERROR(ctx->dev, - "regulator_set_load failed %d\n", ret); + dev_err(ctx->dev, "regulator_set_load failed %d\n", ret); return ret; } } ret = regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); if (ret) { - DRM_DEV_ERROR(ctx->dev, - "regulator_bulk_disable failed %d\n", ret); + dev_err(ctx->dev, "regulator_bulk_disable failed %d\n", ret); } return ret; } @@ -314,17 +311,12 @@ static int truly_nt35597_disable(struct drm_panel *panel) struct truly_nt35597 *ctx = panel_to_ctx(panel); int ret; - if (!ctx->enabled) - return 0; - if (ctx->backlight) { ret = backlight_disable(ctx->backlight); if (ret < 0) - DRM_DEV_ERROR(ctx->dev, "backlight disable failed %d\n", - ret); + dev_err(ctx->dev, "backlight disable failed %d\n", ret); } - ctx->enabled = false; return 0; } @@ -333,17 +325,12 @@ static int truly_nt35597_unprepare(struct drm_panel *panel) struct truly_nt35597 *ctx = panel_to_ctx(panel); int ret = 0; - if (!ctx->prepared) - return 0; - ctx->dsi[0]->mode_flags = 0; ctx->dsi[1]->mode_flags = 0; ret = truly_dcs_write(panel, MIPI_DCS_SET_DISPLAY_OFF); if (ret < 0) { - DRM_DEV_ERROR(ctx->dev, - "set_display_off cmd failed ret = %d\n", - ret); + dev_err(ctx->dev, "set_display_off cmd failed ret = %d\n", ret); } /* 120ms delay required here as per DCS spec */ @@ -351,15 +338,13 @@ static int truly_nt35597_unprepare(struct drm_panel *panel) ret = truly_dcs_write(panel, MIPI_DCS_ENTER_SLEEP_MODE); if (ret < 0) { - DRM_DEV_ERROR(ctx->dev, - "enter_sleep cmd failed ret = %d\n", ret); + dev_err(ctx->dev, "enter_sleep cmd failed ret = %d\n", ret); } ret = truly_nt35597_power_off(ctx); if (ret < 0) - DRM_DEV_ERROR(ctx->dev, "power_off failed ret = %d\n", ret); + dev_err(ctx->dev, "power_off failed ret = %d\n", ret); - ctx->prepared = false; return ret; } @@ -372,9 +357,6 @@ static int truly_nt35597_prepare(struct drm_panel *panel) const struct nt35597_config *config; u32 num_cmds; - if (ctx->prepared) - return 0; - ret = truly_35597_power_on(ctx); if (ret < 0) return ret; @@ -391,18 +373,14 @@ static int truly_nt35597_prepare(struct drm_panel *panel) panel_on_cmds[i].size, panel_on_cmds[i].commands); if (ret < 0) { - DRM_DEV_ERROR(ctx->dev, - "cmd set tx failed i = %d ret = %d\n", - i, ret); + dev_err(ctx->dev, "cmd set tx failed i = %d ret = %d\n", i, ret); goto power_off; } } ret = truly_dcs_write(panel, MIPI_DCS_EXIT_SLEEP_MODE); if (ret < 0) { - DRM_DEV_ERROR(ctx->dev, - "exit_sleep_mode cmd failed ret = %d\n", - ret); + dev_err(ctx->dev, "exit_sleep_mode cmd failed ret = %d\n", ret); goto power_off; } @@ -411,21 +389,18 @@ static int truly_nt35597_prepare(struct drm_panel *panel) ret = truly_dcs_write(panel, MIPI_DCS_SET_DISPLAY_ON); if (ret < 0) { - DRM_DEV_ERROR(ctx->dev, - "set_display_on cmd failed ret = %d\n", ret); + dev_err(ctx->dev, "set_display_on cmd failed ret = %d\n", ret); goto power_off; } /* Per DSI spec wait 120ms after sending set_display_on DCS command */ msleep(120); - ctx->prepared = true; - return 0; power_off: if (truly_nt35597_power_off(ctx)) - DRM_DEV_ERROR(ctx->dev, "power_off failed\n"); + dev_err(ctx->dev, "power_off failed\n"); return ret; } @@ -434,39 +409,31 @@ static int truly_nt35597_enable(struct drm_panel *panel) struct truly_nt35597 *ctx = panel_to_ctx(panel); int ret; - if (ctx->enabled) - return 0; - if (ctx->backlight) { ret = backlight_enable(ctx->backlight); if (ret < 0) - DRM_DEV_ERROR(ctx->dev, "backlight enable failed %d\n", - ret); + dev_err(ctx->dev, "backlight enable failed %d\n", ret); } - ctx->enabled = true; - 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; config = ctx->config; - mode = drm_mode_create(connector->dev); + mode = drm_mode_duplicate(connector->dev, config->dm); if (!mode) { - DRM_DEV_ERROR(ctx->dev, - "failed to create a new display mode\n"); + dev_err(ctx->dev, "failed to create a new display mode\n"); return 0; } connector->display_info.width_mm = config->width_mm; connector->display_info.height_mm = config->height_mm; - drm_mode_copy(mode, config->dm); mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; drm_mode_probed_add(connector, mode); @@ -485,9 +452,7 @@ static int truly_nt35597_panel_add(struct truly_nt35597 *ctx) { struct device *dev = ctx->dev; int ret, i; - const struct nt35597_config *config; - config = ctx->config; for (i = 0; i < ARRAY_SIZE(ctx->supplies); i++) ctx->supplies[i].supply = regulator_names[i]; @@ -498,24 +463,21 @@ static int truly_nt35597_panel_add(struct truly_nt35597 *ctx) ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(ctx->reset_gpio)) { - DRM_DEV_ERROR(dev, "cannot get reset gpio %ld\n", - PTR_ERR(ctx->reset_gpio)); + dev_err(dev, "cannot get reset gpio %ld\n", PTR_ERR(ctx->reset_gpio)); return PTR_ERR(ctx->reset_gpio); } ctx->mode_gpio = devm_gpiod_get(dev, "mode", GPIOD_OUT_LOW); if (IS_ERR(ctx->mode_gpio)) { - DRM_DEV_ERROR(dev, "cannot get mode gpio %ld\n", - PTR_ERR(ctx->mode_gpio)); + dev_err(dev, "cannot get mode gpio %ld\n", PTR_ERR(ctx->mode_gpio)); return PTR_ERR(ctx->mode_gpio); } /* 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; @@ -532,7 +494,6 @@ static const struct drm_display_mode qcom_sdm845_mtp_2k_mode = { .vsync_start = 2560 + 8, .vsync_end = 2560 + 8 + 1, .vtotal = 2560 + 8 + 1 + 7, - .vrefresh = 60, .flags = 0, }; @@ -583,22 +544,19 @@ static int truly_nt35597_probe(struct mipi_dsi_device *dsi) dsi1 = of_graph_get_remote_node(dsi->dev.of_node, 1, -1); if (!dsi1) { - DRM_DEV_ERROR(dev, - "failed to get remote node for dsi1_device\n"); + dev_err(dev, "failed to get remote node for dsi1_device\n"); return -ENODEV; } dsi1_host = of_find_mipi_dsi_host_by_node(dsi1); of_node_put(dsi1); - if (!dsi1_host) { - DRM_DEV_ERROR(dev, "failed to find dsi host\n"); - return -EPROBE_DEFER; - } + if (!dsi1_host) + return dev_err_probe(dev, -EPROBE_DEFER, "failed to find dsi host\n"); /* register the second DSI device */ dsi1_device = mipi_dsi_device_register_full(dsi1_host, &info); if (IS_ERR(dsi1_device)) { - DRM_DEV_ERROR(dev, "failed to create dsi device\n"); + dev_err(dev, "failed to create dsi device\n"); return PTR_ERR(dsi1_device); } @@ -610,7 +568,7 @@ static int truly_nt35597_probe(struct mipi_dsi_device *dsi) ret = truly_nt35597_panel_add(ctx); if (ret) { - DRM_DEV_ERROR(dev, "failed to add panel\n"); + dev_err(dev, "failed to add panel\n"); goto err_panel_add; } @@ -622,8 +580,7 @@ static int truly_nt35597_probe(struct mipi_dsi_device *dsi) MIPI_DSI_CLOCK_NON_CONTINUOUS; ret = mipi_dsi_attach(dsi_dev); if (ret < 0) { - DRM_DEV_ERROR(dev, - "dsi attach failed i = %d\n", i); + dev_err(dev, "dsi attach failed i = %d\n", i); goto err_dsi_attach; } } @@ -637,7 +594,7 @@ err_panel_add: return ret; } -static int truly_nt35597_remove(struct mipi_dsi_device *dsi) +static void truly_nt35597_remove(struct mipi_dsi_device *dsi) { struct truly_nt35597 *ctx = mipi_dsi_get_drvdata(dsi); @@ -649,7 +606,6 @@ static int truly_nt35597_remove(struct mipi_dsi_device *dsi) } drm_panel_remove(&ctx->panel); - return 0; } static const struct of_device_id truly_nt35597_of_match[] = { diff --git a/drivers/gpu/drm/panel/panel-visionox-g2647fb105.c b/drivers/gpu/drm/panel/panel-visionox-g2647fb105.c new file mode 100644 index 000000000000..413849f7b4de --- /dev/null +++ b/drivers/gpu/drm/panel/panel-visionox-g2647fb105.c @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Generated with linux-mdss-dsi-panel-driver-generator from vendor device tree: + * Copyright (c) 2013, The Linux Foundation. All rights reserved. + * Copyright (c) 2025, Alexander Baransky <sanyapilot496@gmail.com> + */ + +#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 <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +struct visionox_g2647fb105 { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data *supplies; +}; + +static const struct regulator_bulk_data visionox_g2647fb105_supplies[] = { + { .supply = "vdd3p3" }, + { .supply = "vddio" }, + { .supply = "vsn" }, + { .supply = "vsp" }, +}; + +static inline +struct visionox_g2647fb105 *to_visionox_g2647fb105(struct drm_panel *panel) +{ + return container_of(panel, struct visionox_g2647fb105, panel); +} + +static void visionox_g2647fb105_reset(struct visionox_g2647fb105 *ctx) +{ + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(1000, 2000); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(10000, 11000); +} + +static int visionox_g2647fb105_on(struct visionox_g2647fb105 *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x70, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfe, 0x40); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4d, 0x32); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfe, 0x40); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xbe, 0x17); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xbf, 0xbb); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc0, 0xdd); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc1, 0xff); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfe, 0xd0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x03, 0x24); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x04, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfe, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc2, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfe, 0x00); + + mipi_dsi_dcs_set_tear_on_multi(&dsi_ctx, MIPI_DSI_DCS_TEAR_MODE_VBLANK); + mipi_dsi_dcs_set_display_brightness_multi(&dsi_ctx, 0x0000); + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 100); + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + + return dsi_ctx.accum_err; +} + +static int visionox_g2647fb105_off(struct visionox_g2647fb105 *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 50); + + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 20); + + return dsi_ctx.accum_err; +} + +static int visionox_g2647fb105_prepare(struct drm_panel *panel) +{ + struct visionox_g2647fb105 *ctx = to_visionox_g2647fb105(panel); + struct device *dev = &ctx->dsi->dev; + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(visionox_g2647fb105_supplies), ctx->supplies); + if (ret < 0) { + dev_err(dev, "Failed to enable regulators: %d\n", ret); + return ret; + } + + visionox_g2647fb105_reset(ctx); + + ret = visionox_g2647fb105_on(ctx); + if (ret < 0) { + dev_err(dev, "Failed to initialize panel: %d\n", ret); + return ret; + } + + return 0; +} + +static int visionox_g2647fb105_unprepare(struct drm_panel *panel) +{ + struct visionox_g2647fb105 *ctx = to_visionox_g2647fb105(panel); + struct device *dev = &ctx->dsi->dev; + int ret; + + ret = visionox_g2647fb105_off(ctx); + if (ret < 0) + dev_err(dev, "Failed to un-initialize panel: %d\n", ret); + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(visionox_g2647fb105_supplies), ctx->supplies); + + return 0; +} + +static const struct drm_display_mode visionox_g2647fb105_mode = { + .clock = (1080 + 28 + 4 + 36) * (2340 + 8 + 4 + 4) * 60 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 28, + .hsync_end = 1080 + 28 + 4, + .htotal = 1080 + 28 + 4 + 36, + .vdisplay = 2340, + .vsync_start = 2340 + 8, + .vsync_end = 2340 + 8 + 4, + .vtotal = 2340 + 8 + 4 + 4, + .width_mm = 69, + .height_mm = 149, +}; + +static int visionox_g2647fb105_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &visionox_g2647fb105_mode); + if (!mode) + 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 visionox_g2647fb105_panel_funcs = { + .prepare = visionox_g2647fb105_prepare, + .unprepare = visionox_g2647fb105_unprepare, + .get_modes = visionox_g2647fb105_get_modes, +}; + +static int visionox_g2647fb105_bl_update_status(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + u16 brightness = backlight_get_brightness(bl); + int ret; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + ret = mipi_dsi_dcs_set_display_brightness_large(dsi, brightness); + if (ret < 0) + return ret; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + return 0; +} + +static const struct backlight_ops visionox_g2647fb105_bl_ops = { + .update_status = visionox_g2647fb105_bl_update_status, +}; + +static struct backlight_device * +visionox_g2647fb105_create_backlight(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + const struct backlight_properties props = { + .type = BACKLIGHT_RAW, + .brightness = 1023, + .max_brightness = 2047, + }; + + return devm_backlight_device_register(dev, dev_name(dev), dev, dsi, + &visionox_g2647fb105_bl_ops, &props); +} + +static int visionox_g2647fb105_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct visionox_g2647fb105 *ctx; + int ret; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ret = devm_regulator_bulk_get_const(dev, + ARRAY_SIZE(visionox_g2647fb105_supplies), + visionox_g2647fb105_supplies, + &ctx->supplies); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get regulators\n"); + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), + "Failed to get reset-gpios\n"); + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_CLOCK_NON_CONTINUOUS | MIPI_DSI_MODE_LPM; + + ctx->panel.prepare_prev_first = true; + + drm_panel_init(&ctx->panel, dev, &visionox_g2647fb105_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + ctx->panel.prepare_prev_first = true; + + ctx->panel.backlight = visionox_g2647fb105_create_backlight(dsi); + if (IS_ERR(ctx->panel.backlight)) + return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight), + "Failed to create backlight\n"); + + drm_panel_add(&ctx->panel); + + ret = devm_mipi_dsi_attach(dev, dsi); + if (ret < 0) { + drm_panel_remove(&ctx->panel); + return dev_err_probe(dev, ret, "Failed to attach to DSI host\n"); + } + + return 0; +} + +static void visionox_g2647fb105_remove(struct mipi_dsi_device *dsi) +{ + struct visionox_g2647fb105 *ctx = mipi_dsi_get_drvdata(dsi); + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id visionox_g2647fb105_of_match[] = { + { .compatible = "visionox,g2647fb105" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, visionox_g2647fb105_of_match); + +static struct mipi_dsi_driver visionox_g2647fb105_driver = { + .probe = visionox_g2647fb105_probe, + .remove = visionox_g2647fb105_remove, + .driver = { + .name = "panel-visionox-g2647fb105", + .of_match_table = visionox_g2647fb105_of_match, + }, +}; +module_mipi_dsi_driver(visionox_g2647fb105_driver); + +MODULE_AUTHOR("Alexander Baransky <sanyapilot496@gmail.com>"); +MODULE_DESCRIPTION("DRM driver for Visionox G2647FB105 AMOLED DSI panel"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-visionox-r66451.c b/drivers/gpu/drm/panel/panel-visionox-r66451.c new file mode 100644 index 000000000000..690cccedd438 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-visionox-r66451.c @@ -0,0 +1,349 @@ +//SPDX-License-Identifier: GPL-2.0-only +//Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. + +#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 <drm/drm_mipi_dsi.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> +#include <drm/display/drm_dsc.h> +#include <drm/display/drm_dsc_helper.h> + +#include <video/mipi_display.h> + +struct visionox_r66451 { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[2]; +}; + +static inline struct visionox_r66451 *to_visionox_r66451(struct drm_panel *panel) +{ + return container_of(panel, struct visionox_r66451, panel); +} + +static void visionox_r66451_reset(struct visionox_r66451 *ctx) +{ + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(10000, 10100); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(10000, 10100); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(10000, 10100); +} + +static int visionox_r66451_on(struct visionox_r66451 *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc2, + 0x09, 0x24, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x09, 0x3c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xd7, + 0x00, 0xb9, 0x3c, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a, + 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, + 0x3c, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x80); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xde, + 0x40, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, + 0x10, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x02, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe8, 0x00, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe4, 0x00, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc4, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x32); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xcf, + 0x64, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, + 0x00, 0x0b, 0x77, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x03); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xd3, + 0x45, 0x00, 0x00, 0x01, 0x13, 0x15, 0x00, 0x15, 0x07, + 0x0f, 0x77, 0x77, 0x77, 0x37, 0xb2, 0x11, 0x00, 0xa0, + 0x3c, 0x9c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xd7, + 0x00, 0xb9, 0x34, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a, + 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, + 0x34, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xd8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3a, 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x3a, + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, + 0x00, 0x32, 0x00, 0x0a, 0x00, 0x22); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xdf, + 0x50, 0x42, 0x58, 0x81, 0x2d, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x0f, 0xff, 0xd4, 0x0e, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0f, 0x53, 0xf1, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf7, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x80); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe4, 0x34, 0xb4, 0x00, 0x00, 0x00, 0x39, + 0x04, 0x09, 0x34); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe6, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xdf, 0x50, 0x40); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf3, 0x50, 0x00, 0x00, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf2, 0x11); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf3, 0x01, 0x00, 0x00, 0x00, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf4, 0x00, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf2, 0x19); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xdf, 0x50, 0x42); + mipi_dsi_dcs_set_tear_on_multi(&dsi_ctx, MIPI_DSI_DCS_TEAR_MODE_VBLANK); + mipi_dsi_dcs_set_column_address_multi(&dsi_ctx, 0, 1080 - 1); + mipi_dsi_dcs_set_page_address_multi(&dsi_ctx, 0, 2340 - 1); + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + return dsi_ctx.accum_err; +} + +static void visionox_r66451_off(struct visionox_r66451 *ctx) +{ + ctx->dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; +} + +static int visionox_r66451_prepare(struct drm_panel *panel) +{ + struct visionox_r66451 *ctx = to_visionox_r66451(panel); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), + ctx->supplies); + if (ret < 0) + return ret; + + visionox_r66451_reset(ctx); + + ret = visionox_r66451_on(ctx); + if (ret < 0) { + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + return ret; + } + + mipi_dsi_compression_mode(ctx->dsi, true); + + return 0; +} + +static int visionox_r66451_unprepare(struct drm_panel *panel) +{ + struct visionox_r66451 *ctx = to_visionox_r66451(panel); + + visionox_r66451_off(ctx); + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + + return 0; +} + +static const struct drm_display_mode visionox_r66451_mode = { + .clock = 345830, + .hdisplay = 1080, + .hsync_start = 1175, + .hsync_end = 1176, + .htotal = 1216, + .vdisplay = 2340, + .vsync_start = 2365, + .vsync_end = 2366, + .vtotal = 2370, + .width_mm = 0, + .height_mm = 0, + .type = DRM_MODE_TYPE_DRIVER, +}; + +static int visionox_r66451_enable(struct drm_panel *panel) +{ + struct visionox_r66451 *ctx = to_visionox_r66451(panel); + struct mipi_dsi_device *dsi = ctx->dsi; + struct drm_dsc_picture_parameter_set pps; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + if (!dsi->dsc) { + dev_err(&dsi->dev, "DSC not attached to DSI\n"); + return -ENODEV; + } + + drm_dsc_pps_payload_pack(&pps, dsi->dsc); + mipi_dsi_picture_parameter_set_multi(&dsi_ctx, &pps); + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 120); + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 20); + + return dsi_ctx.accum_err; +} + +static int visionox_r66451_disable(struct drm_panel *panel) +{ + struct visionox_r66451 *ctx = to_visionox_r66451(panel); + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 20); + + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 120); + + return dsi_ctx.accum_err; +} + +static int visionox_r66451_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + drm_connector_helper_get_modes_fixed(connector, &visionox_r66451_mode); + return 1; +} + +static const struct drm_panel_funcs visionox_r66451_funcs = { + .prepare = visionox_r66451_prepare, + .unprepare = visionox_r66451_unprepare, + .get_modes = visionox_r66451_get_modes, + .enable = visionox_r66451_enable, + .disable = visionox_r66451_disable, +}; + +static int visionox_r66451_bl_update_status(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + u16 brightness = backlight_get_brightness(bl); + + return mipi_dsi_dcs_set_display_brightness(dsi, brightness); +} + +static const struct backlight_ops visionox_r66451_bl_ops = { + .update_status = visionox_r66451_bl_update_status, +}; + +static struct backlight_device * +visionox_r66451_create_backlight(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + const struct backlight_properties props = { + .type = BACKLIGHT_RAW, + .brightness = 255, + .max_brightness = 4095, + }; + + return devm_backlight_device_register(dev, dev_name(dev), dev, dsi, + &visionox_r66451_bl_ops, &props); +} + +static int visionox_r66451_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct visionox_r66451 *ctx; + struct drm_dsc_config *dsc; + int ret = 0; + + ctx = devm_drm_panel_alloc(dev, struct visionox_r66451, panel, + &visionox_r66451_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + dsc = devm_kzalloc(dev, sizeof(*dsc), GFP_KERNEL); + if (!dsc) + return -ENOMEM; + + /* Set DSC params */ + dsc->dsc_version_major = 0x1; + dsc->dsc_version_minor = 0x2; + + dsc->slice_height = 20; + dsc->slice_width = 540; + dsc->slice_count = 2; + dsc->bits_per_component = 8; + dsc->bits_per_pixel = 8 << 4; + dsc->block_pred_enable = true; + + dsi->dsc = dsc; + + ctx->supplies[0].supply = "vddio"; + ctx->supplies[1].supply = "vdd"; + + ret = devm_regulator_bulk_get(&dsi->dev, ARRAY_SIZE(ctx->supplies), + ctx->supplies); + + if (ret < 0) + return ret; + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), "Failed to get reset-gpios\n"); + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS; + ctx->panel.prepare_prev_first = true; + + ctx->panel.backlight = visionox_r66451_create_backlight(dsi); + if (IS_ERR(ctx->panel.backlight)) + return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight), + "Failed to create backlight\n"); + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "Failed to attach to DSI host: %d\n", ret); + drm_panel_remove(&ctx->panel); + } + + return ret; +} + +static void visionox_r66451_remove(struct mipi_dsi_device *dsi) +{ + struct visionox_r66451 *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "Failed to detach DSI host: %d\n", ret); + + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id visionox_r66451_of_match[] = { + {.compatible = "visionox,r66451"}, + { /*sentinel*/ } +}; +MODULE_DEVICE_TABLE(of, visionox_r66451_of_match); + +static struct mipi_dsi_driver visionox_r66451_driver = { + .probe = visionox_r66451_probe, + .remove = visionox_r66451_remove, + .driver = { + .name = "panel-visionox-r66451", + .of_match_table = visionox_r66451_of_match, + }, +}; + +module_mipi_dsi_driver(visionox_r66451_driver); + +MODULE_AUTHOR("Jessica Zhang <quic_jesszhan@quicinc.com>"); +MODULE_DESCRIPTION("Panel driver for the Visionox R66451 AMOLED DSI panel"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-visionox-rm69299.c b/drivers/gpu/drm/panel/panel-visionox-rm69299.c new file mode 100644 index 000000000000..e5e688cf98fd --- /dev/null +++ b/drivers/gpu/drm/panel/panel-visionox-rm69299.c @@ -0,0 +1,445 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2019, The Linux Foundation. All rights reserved. + */ + +#include <linux/backlight.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/property.h> +#include <linux/mod_devicetable.h> +#include <linux/gpio/consumer.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> + +struct visionox_rm69299_panel_desc { + const struct drm_display_mode *mode; + const u8 *init_seq; + unsigned int init_seq_len; + int max_brightness; + int initial_brightness; +}; + +struct visionox_rm69299 { + struct drm_panel panel; + struct regulator_bulk_data *supplies; + struct gpio_desc *reset_gpio; + struct mipi_dsi_device *dsi; + const struct visionox_rm69299_panel_desc *desc; +}; + +static const struct regulator_bulk_data visionox_rm69299_supplies[] = { + { .supply = "vdda", .init_load_uA = 32000 }, + { .supply = "vdd3p3", .init_load_uA = 13200 }, +}; + +static const u8 visionox_rm69299_1080x2248_60hz_init_seq[][2] = { + { 0xfe, 0x00 }, { 0xc2, 0x08 }, { 0x35, 0x00 }, { 0x51, 0xff }, +}; + +static const u8 visionox_rm69299_1080x2160_60hz_init_seq[][2] = { + { 0xfe, 0x40 }, { 0x05, 0x04 }, { 0x06, 0x08 }, { 0x08, 0x04 }, + { 0x09, 0x08 }, { 0x0a, 0x07 }, { 0x0b, 0xcc }, { 0x0c, 0x07 }, + { 0x0d, 0x90 }, { 0x0f, 0x87 }, { 0x20, 0x8d }, { 0x21, 0x8d }, + { 0x24, 0x05 }, { 0x26, 0x05 }, { 0x28, 0x05 }, { 0x2a, 0x05 }, + { 0x2d, 0x28 }, { 0x2f, 0x28 }, { 0x30, 0x32 }, { 0x31, 0x32 }, + { 0x37, 0x80 }, { 0x38, 0x30 }, { 0x39, 0xa8 }, { 0x46, 0x48 }, + { 0x47, 0x48 }, { 0x6b, 0x10 }, { 0x6f, 0x02 }, { 0x74, 0x2b }, + { 0x80, 0x1a }, { 0xfe, 0x40 }, { 0x93, 0x10 }, { 0x16, 0x00 }, + { 0x85, 0x07 }, { 0x84, 0x01 }, { 0x86, 0x0f }, { 0x87, 0x05 }, + { 0x8c, 0x00 }, { 0x88, 0x2e }, { 0x89, 0x2e }, { 0x8b, 0x09 }, + { 0x95, 0x00 }, { 0x91, 0x00 }, { 0x90, 0x00 }, { 0x8d, 0xd0 }, + { 0x8a, 0x03 }, { 0xfe, 0xa0 }, { 0x13, 0x00 }, { 0x33, 0x00 }, + { 0x0b, 0x33 }, { 0x36, 0x1e }, { 0x31, 0x88 }, { 0x32, 0x88 }, + { 0x37, 0xf1 }, { 0xfe, 0x50 }, { 0x00, 0x00 }, { 0x01, 0x00 }, + { 0x02, 0x00 }, { 0x03, 0xe9 }, { 0x04, 0x00 }, { 0x05, 0xf6 }, + { 0x06, 0x01 }, { 0x07, 0x2c }, { 0x08, 0x01 }, { 0x09, 0x62 }, + { 0x0a, 0x01 }, { 0x0b, 0x98 }, { 0x0c, 0x01 }, { 0x0d, 0xbf }, + { 0x0e, 0x01 }, { 0x0f, 0xf6 }, { 0x10, 0x02 }, { 0x11, 0x24 }, + { 0x12, 0x02 }, { 0x13, 0x4e }, { 0x14, 0x02 }, { 0x15, 0x70 }, + { 0x16, 0x02 }, { 0x17, 0xaf }, { 0x18, 0x02 }, { 0x19, 0xe2 }, + { 0x1a, 0x03 }, { 0x1b, 0x1f }, { 0x1c, 0x03 }, { 0x1d, 0x52 }, + { 0x1e, 0x03 }, { 0x1f, 0x82 }, { 0x20, 0x03 }, { 0x21, 0xb6 }, + { 0x22, 0x03 }, { 0x23, 0xf0 }, { 0x24, 0x04 }, { 0x25, 0x1f }, + { 0x26, 0x04 }, { 0x27, 0x37 }, { 0x28, 0x04 }, { 0x29, 0x59 }, + { 0x2a, 0x04 }, { 0x2b, 0x68 }, { 0x30, 0x04 }, { 0x31, 0x85 }, + { 0x32, 0x04 }, { 0x33, 0xa2 }, { 0x34, 0x04 }, { 0x35, 0xbc }, + { 0x36, 0x04 }, { 0x37, 0xd8 }, { 0x38, 0x04 }, { 0x39, 0xf4 }, + { 0x3a, 0x05 }, { 0x3b, 0x0e }, { 0x40, 0x05 }, { 0x41, 0x13 }, + { 0x42, 0x05 }, { 0x43, 0x1f }, { 0x44, 0x05 }, { 0x45, 0x1f }, + { 0x46, 0x00 }, { 0x47, 0x00 }, { 0x48, 0x01 }, { 0x49, 0x43 }, + { 0x4a, 0x01 }, { 0x4b, 0x4c }, { 0x4c, 0x01 }, { 0x4d, 0x6f }, + { 0x4e, 0x01 }, { 0x4f, 0x92 }, { 0x50, 0x01 }, { 0x51, 0xb5 }, + { 0x52, 0x01 }, { 0x53, 0xd4 }, { 0x58, 0x02 }, { 0x59, 0x06 }, + { 0x5a, 0x02 }, { 0x5b, 0x33 }, { 0x5c, 0x02 }, { 0x5d, 0x59 }, + { 0x5e, 0x02 }, { 0x5f, 0x7d }, { 0x60, 0x02 }, { 0x61, 0xbd }, + { 0x62, 0x02 }, { 0x63, 0xf7 }, { 0x64, 0x03 }, { 0x65, 0x31 }, + { 0x66, 0x03 }, { 0x67, 0x63 }, { 0x68, 0x03 }, { 0x69, 0x9d }, + { 0x6a, 0x03 }, { 0x6b, 0xd2 }, { 0x6c, 0x04 }, { 0x6d, 0x05 }, + { 0x6e, 0x04 }, { 0x6f, 0x38 }, { 0x70, 0x04 }, { 0x71, 0x51 }, + { 0x72, 0x04 }, { 0x73, 0x70 }, { 0x74, 0x04 }, { 0x75, 0x85 }, + { 0x76, 0x04 }, { 0x77, 0xa1 }, { 0x78, 0x04 }, { 0x79, 0xc0 }, + { 0x7a, 0x04 }, { 0x7b, 0xd8 }, { 0x7c, 0x04 }, { 0x7d, 0xf2 }, + { 0x7e, 0x05 }, { 0x7f, 0x10 }, { 0x80, 0x05 }, { 0x81, 0x21 }, + { 0x82, 0x05 }, { 0x83, 0x2e }, { 0x84, 0x05 }, { 0x85, 0x3a }, + { 0x86, 0x05 }, { 0x87, 0x3e }, { 0x88, 0x00 }, { 0x89, 0x00 }, + { 0x8a, 0x01 }, { 0x8b, 0x86 }, { 0x8c, 0x01 }, { 0x8d, 0x8f }, + { 0x8e, 0x01 }, { 0x8f, 0xb3 }, { 0x90, 0x01 }, { 0x91, 0xd7 }, + { 0x92, 0x01 }, { 0x93, 0xfb }, { 0x94, 0x02 }, { 0x95, 0x18 }, + { 0x96, 0x02 }, { 0x97, 0x4f }, { 0x98, 0x02 }, { 0x99, 0x7e }, + { 0x9a, 0x02 }, { 0x9b, 0xa6 }, { 0x9c, 0x02 }, { 0x9d, 0xcf }, + { 0x9e, 0x03 }, { 0x9f, 0x14 }, { 0xa4, 0x03 }, { 0xa5, 0x52 }, + { 0xa6, 0x03 }, { 0xa7, 0x93 }, { 0xac, 0x03 }, { 0xad, 0xcf }, + { 0xae, 0x04 }, { 0xaf, 0x08 }, { 0xb0, 0x04 }, { 0xb1, 0x42 }, + { 0xb2, 0x04 }, { 0xb3, 0x7f }, { 0xb4, 0x04 }, { 0xb5, 0xb4 }, + { 0xb6, 0x04 }, { 0xb7, 0xcc }, { 0xb8, 0x04 }, { 0xb9, 0xf2 }, + { 0xba, 0x05 }, { 0xbb, 0x0c }, { 0xbc, 0x05 }, { 0xbd, 0x26 }, + { 0xbe, 0x05 }, { 0xbf, 0x4b }, { 0xc0, 0x05 }, { 0xc1, 0x64 }, + { 0xc2, 0x05 }, { 0xc3, 0x83 }, { 0xc4, 0x05 }, { 0xc5, 0xa1 }, + { 0xc6, 0x05 }, { 0xc7, 0xba }, { 0xc8, 0x05 }, { 0xc9, 0xc4 }, + { 0xca, 0x05 }, { 0xcb, 0xd5 }, { 0xcc, 0x05 }, { 0xcd, 0xd5 }, + { 0xce, 0x00 }, { 0xcf, 0xce }, { 0xd0, 0x00 }, { 0xd1, 0xdb }, + { 0xd2, 0x01 }, { 0xd3, 0x32 }, { 0xd4, 0x01 }, { 0xd5, 0x3b }, + { 0xd6, 0x01 }, { 0xd7, 0x74 }, { 0xd8, 0x01 }, { 0xd9, 0x7d }, + { 0xfe, 0x60 }, { 0x00, 0xcc }, { 0x01, 0x0f }, { 0x02, 0xff }, + { 0x03, 0x01 }, { 0x04, 0x00 }, { 0x05, 0x02 }, { 0x06, 0x00 }, + { 0x07, 0x00 }, { 0x09, 0xc4 }, { 0x0a, 0x00 }, { 0x0b, 0x04 }, + { 0x0c, 0x01 }, { 0x0d, 0x00 }, { 0x0e, 0x04 }, { 0x0f, 0x00 }, + { 0x10, 0x71 }, { 0x12, 0xc4 }, { 0x13, 0x00 }, { 0x14, 0x04 }, + { 0x15, 0x01 }, { 0x16, 0x00 }, { 0x17, 0x06 }, { 0x18, 0x00 }, + { 0x19, 0x71 }, { 0x1b, 0xc4 }, { 0x1c, 0x00 }, { 0x1d, 0x02 }, + { 0x1e, 0x00 }, { 0x1f, 0x00 }, { 0x20, 0x08 }, { 0x21, 0x66 }, + { 0x22, 0xb4 }, { 0x24, 0xc4 }, { 0x25, 0x00 }, { 0x26, 0x02 }, + { 0x27, 0x00 }, { 0x28, 0x00 }, { 0x29, 0x07 }, { 0x2a, 0x66 }, + { 0x2b, 0xb4 }, { 0x2f, 0xc4 }, { 0x30, 0x00 }, { 0x31, 0x04 }, + { 0x32, 0x01 }, { 0x33, 0x00 }, { 0x34, 0x03 }, { 0x35, 0x00 }, + { 0x36, 0x71 }, { 0x38, 0xc4 }, { 0x39, 0x00 }, { 0x3a, 0x04 }, + { 0x3b, 0x01 }, { 0x3d, 0x00 }, { 0x3f, 0x05 }, { 0x40, 0x00 }, + { 0x41, 0x71 }, { 0x83, 0xce }, { 0x84, 0x02 }, { 0x85, 0x20 }, + { 0x86, 0xdc }, { 0x87, 0x00 }, { 0x88, 0x04 }, { 0x89, 0x00 }, + { 0x8a, 0xbb }, { 0x8b, 0x80 }, { 0xc7, 0x0e }, { 0xc8, 0x05 }, + { 0xc9, 0x1f }, { 0xca, 0x06 }, { 0xcb, 0x00 }, { 0xcc, 0x03 }, + { 0xcd, 0x04 }, { 0xce, 0x1f }, { 0xcf, 0x1f }, { 0xd0, 0x1f }, + { 0xd1, 0x1f }, { 0xd2, 0x1f }, { 0xd3, 0x1f }, { 0xd4, 0x1f }, + { 0xd5, 0x1f }, { 0xd6, 0x1f }, { 0xd7, 0x17 }, { 0xd8, 0x1f }, + { 0xd9, 0x16 }, { 0xda, 0x1f }, { 0xdb, 0x0e }, { 0xdc, 0x01 }, + { 0xdd, 0x1f }, { 0xde, 0x02 }, { 0xdf, 0x00 }, { 0xe0, 0x03 }, + { 0xe1, 0x04 }, { 0xe2, 0x1f }, { 0xe3, 0x1f }, { 0xe4, 0x1f }, + { 0xe5, 0x1f }, { 0xe6, 0x1f }, { 0xe7, 0x1f }, { 0xe8, 0x1f }, + { 0xe9, 0x1f }, { 0xea, 0x1f }, { 0xeb, 0x17 }, { 0xec, 0x1f }, + { 0xed, 0x16 }, { 0xee, 0x1f }, { 0xef, 0x03 }, { 0xfe, 0x70 }, + { 0x5a, 0x0b }, { 0x5b, 0x0b }, { 0x5c, 0x55 }, { 0x5d, 0x24 }, + { 0xfe, 0x90 }, { 0x12, 0x24 }, { 0x13, 0x49 }, { 0x14, 0x92 }, + { 0x15, 0x86 }, { 0x16, 0x61 }, { 0x17, 0x18 }, { 0x18, 0x24 }, + { 0x19, 0x49 }, { 0x1a, 0x92 }, { 0x1b, 0x86 }, { 0x1c, 0x61 }, + { 0x1d, 0x18 }, { 0x1e, 0x24 }, { 0x1f, 0x49 }, { 0x20, 0x92 }, + { 0x21, 0x86 }, { 0x22, 0x61 }, { 0x23, 0x18 }, { 0xfe, 0x40 }, + { 0x0e, 0x10 }, { 0xfe, 0xa0 }, { 0x04, 0x80 }, { 0x16, 0x00 }, + { 0x26, 0x10 }, { 0x2f, 0x37 }, { 0xfe, 0xd0 }, { 0x06, 0x0f }, + { 0x4b, 0x00 }, { 0x56, 0x4a }, { 0xfe, 0x00 }, { 0xc2, 0x09 }, + { 0x35, 0x00 }, { 0xfe, 0x70 }, { 0x7d, 0x61 }, { 0x7f, 0x00 }, + { 0x7e, 0x4e }, { 0x52, 0x2c }, { 0x49, 0x00 }, { 0x4a, 0x00 }, + { 0x4b, 0x00 }, { 0x4c, 0x00 }, { 0x4d, 0xe8 }, { 0x4e, 0x25 }, + { 0x4f, 0x6e }, { 0x50, 0xae }, { 0x51, 0x2f }, { 0xad, 0xf4 }, + { 0xae, 0x8f }, { 0xaf, 0x00 }, { 0xb0, 0x54 }, { 0xb1, 0x3a }, + { 0xb2, 0x00 }, { 0xb3, 0x00 }, { 0xb4, 0x00 }, { 0xb5, 0x00 }, + { 0xb6, 0x18 }, { 0xb7, 0x30 }, { 0xb8, 0x4a }, { 0xb9, 0x98 }, + { 0xba, 0x30 }, { 0xbb, 0x60 }, { 0xbc, 0x50 }, { 0xbd, 0x00 }, + { 0xbe, 0x00 }, { 0xbf, 0x39 }, { 0xfe, 0x00 }, { 0x51, 0x66 }, +}; + +static inline struct visionox_rm69299 *panel_to_ctx(struct drm_panel *panel) +{ + return container_of(panel, struct visionox_rm69299, panel); +} + +static int visionox_rm69299_power_on(struct visionox_rm69299 *ctx) +{ + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(visionox_rm69299_supplies), + ctx->supplies); + if (ret < 0) + return ret; + + /* + * Reset sequence of visionox panel requires the panel to be + * out of reset for 10ms, followed by being held in reset + * for 10ms and then out again + */ + gpiod_set_value(ctx->reset_gpio, 1); + usleep_range(10000, 20000); + gpiod_set_value(ctx->reset_gpio, 0); + usleep_range(10000, 20000); + gpiod_set_value(ctx->reset_gpio, 1); + usleep_range(10000, 20000); + + return 0; +} + +static int visionox_rm69299_power_off(struct visionox_rm69299 *ctx) +{ + gpiod_set_value(ctx->reset_gpio, 0); + + return regulator_bulk_disable(ARRAY_SIZE(visionox_rm69299_supplies), + ctx->supplies); +} + +static int visionox_rm69299_unprepare(struct drm_panel *panel) +{ + struct visionox_rm69299 *ctx = panel_to_ctx(panel); + struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; + + ctx->dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + + /* 120ms delay required here as per DCS spec */ + mipi_dsi_msleep(&dsi_ctx, 120); + + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + + return visionox_rm69299_power_off(ctx); +} + +static int visionox_rm69299_prepare(struct drm_panel *panel) +{ + struct visionox_rm69299 *ctx = panel_to_ctx(panel); + struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; + int ret, i; + + ret = visionox_rm69299_power_on(ctx); + if (ret < 0) + return ret; + + ctx->dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + for (i = 0; i < ctx->desc->init_seq_len; i++) + mipi_dsi_dcs_write_buffer_multi(&dsi_ctx, &ctx->desc->init_seq[i * 2], 2); + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + + /* Per DSI spec wait 120ms after sending exit sleep DCS command */ + mipi_dsi_msleep(&dsi_ctx, 120); + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + + /* Per DSI spec wait 120ms after sending set_display_on DCS command */ + mipi_dsi_msleep(&dsi_ctx, 120); + + return dsi_ctx.accum_err; +} + +static const struct drm_display_mode visionox_rm69299_1080x2248_60hz = { + .name = "1080x2248", + .clock = 158695, + .hdisplay = 1080, + .hsync_start = 1080 + 26, + .hsync_end = 1080 + 26 + 2, + .htotal = 1080 + 26 + 2 + 36, + .vdisplay = 2248, + .vsync_start = 2248 + 56, + .vsync_end = 2248 + 56 + 4, + .vtotal = 2248 + 56 + 4 + 4, + .flags = 0, +}; + +static const struct drm_display_mode visionox_rm69299_1080x2160_60hz = { + .clock = (2160 + 8 + 4 + 4) * (1080 + 26 + 2 + 36) * 60 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 26, + .hsync_end = 1080 + 26 + 2, + .htotal = 1080 + 26 + 2 + 36, + .vdisplay = 2160, + .vsync_start = 2160 + 8, + .vsync_end = 2160 + 8 + 4, + .vtotal = 2160 + 8 + 4 + 4, + .flags = 0, +}; + +static int visionox_rm69299_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct visionox_rm69299 *ctx = panel_to_ctx(panel); + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, ctx->desc->mode); + if (!mode) { + dev_err(ctx->panel.dev, "failed to create a new display mode\n"); + return 0; + } + + connector->display_info.width_mm = 74; + connector->display_info.height_mm = 131; + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + drm_mode_probed_add(connector, mode); + + return 1; +} + +static const struct drm_panel_funcs visionox_rm69299_drm_funcs = { + .unprepare = visionox_rm69299_unprepare, + .prepare = visionox_rm69299_prepare, + .get_modes = visionox_rm69299_get_modes, +}; + +static int visionox_rm69299_bl_get_brightness(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + u16 brightness; + int ret; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + ret = mipi_dsi_dcs_get_display_brightness(dsi, &brightness); + if (ret < 0) + return ret; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + return brightness; +} + +static int visionox_rm69299_bl_update_status(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + u16 brightness = backlight_get_brightness(bl); + int ret; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + ret = mipi_dsi_dcs_set_display_brightness(dsi, brightness); + if (ret < 0) + return ret; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + return 0; +} + +static const struct backlight_ops visionox_rm69299_bl_ops = { + .update_status = visionox_rm69299_bl_update_status, + .get_brightness = visionox_rm69299_bl_get_brightness, +}; + +static struct backlight_device * +visionox_rm69299_create_backlight(struct visionox_rm69299 *ctx) +{ + struct device *dev = &ctx->dsi->dev; + const struct backlight_properties props = { + .type = BACKLIGHT_RAW, + .brightness = ctx->desc->initial_brightness, + .max_brightness = ctx->desc->max_brightness, + }; + + if (!ctx->desc->max_brightness) + return 0; + + return devm_backlight_device_register(dev, dev_name(dev), dev, ctx->dsi, + &visionox_rm69299_bl_ops, + &props); +} + +static int visionox_rm69299_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct visionox_rm69299 *ctx; + int ret; + + ctx = devm_drm_panel_alloc(dev, struct visionox_rm69299, panel, + &visionox_rm69299_drm_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ctx->desc = device_get_match_data(dev); + if (!ctx->desc) + return -EINVAL; + + mipi_dsi_set_drvdata(dsi, ctx); + + ctx->dsi = dsi; + + ret = devm_regulator_bulk_get_const(dev, ARRAY_SIZE(visionox_rm69299_supplies), + visionox_rm69299_supplies, &ctx->supplies); + if (ret < 0) + return ret; + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->reset_gpio)) { + dev_err(dev, "cannot get reset gpio %ld\n", PTR_ERR(ctx->reset_gpio)); + return PTR_ERR(ctx->reset_gpio); + } + + ctx->panel.backlight = visionox_rm69299_create_backlight(ctx); + if (IS_ERR(ctx->panel.backlight)) + return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight), + "Failed to create backlight\n"); + + drm_panel_add(&ctx->panel); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM | + MIPI_DSI_CLOCK_NON_CONTINUOUS; + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "dsi attach failed ret = %d\n", ret); + goto err_dsi_attach; + } + + return 0; + +err_dsi_attach: + drm_panel_remove(&ctx->panel); + return ret; +} + +static void visionox_rm69299_remove(struct mipi_dsi_device *dsi) +{ + struct visionox_rm69299 *ctx = mipi_dsi_get_drvdata(dsi); + + mipi_dsi_detach(ctx->dsi); + drm_panel_remove(&ctx->panel); +} + +const struct visionox_rm69299_panel_desc visionox_rm69299_1080p_display_desc = { + .mode = &visionox_rm69299_1080x2248_60hz, + .init_seq = (const u8 *)visionox_rm69299_1080x2248_60hz_init_seq, + .init_seq_len = ARRAY_SIZE(visionox_rm69299_1080x2248_60hz_init_seq), +}; + +const struct visionox_rm69299_panel_desc visionox_rm69299_shift_desc = { + .mode = &visionox_rm69299_1080x2160_60hz, + .init_seq = (const u8 *)visionox_rm69299_1080x2160_60hz_init_seq, + .init_seq_len = ARRAY_SIZE(visionox_rm69299_1080x2160_60hz_init_seq), + .max_brightness = 255, + .initial_brightness = 50, +}; + +static const struct of_device_id visionox_rm69299_of_match[] = { + { .compatible = "visionox,rm69299-1080p-display", + .data = &visionox_rm69299_1080p_display_desc }, + { .compatible = "visionox,rm69299-shift", + .data = &visionox_rm69299_shift_desc }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, visionox_rm69299_of_match); + +static struct mipi_dsi_driver visionox_rm69299_driver = { + .driver = { + .name = "panel-visionox-rm69299", + .of_match_table = visionox_rm69299_of_match, + }, + .probe = visionox_rm69299_probe, + .remove = visionox_rm69299_remove, +}; +module_mipi_dsi_driver(visionox_rm69299_driver); + +MODULE_DESCRIPTION("Visionox RM69299 DSI Panel Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-visionox-rm692e5.c b/drivers/gpu/drm/panel/panel-visionox-rm692e5.c new file mode 100644 index 000000000000..e53645d59413 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-visionox-rm692e5.c @@ -0,0 +1,442 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Generated with linux-mdss-dsi-panel-driver-generator from vendor device tree: + * Copyright (c) 2013, The Linux Foundation. All rights reserved. + * Copyright (c) 2025, Eugene Lepshy <fekz115@gmail.com> + * Copyright (c) 2025, Danila Tikhonov <danila@jiaxyga.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> + +#include <video/mipi_display.h> + +#include <drm/display/drm_dsc.h> +#include <drm/display/drm_dsc_helper.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> +#include <drm/drm_probe_helper.h> + +struct visionox_rm692e5 { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct drm_dsc_config dsc; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data *supplies; +}; + +static const struct regulator_bulk_data visionox_rm692e5_supplies[] = { + { .supply = "vddio" }, /* 1p8 */ + { .supply = "vdd" }, /* 3p3 */ +}; + +static inline +struct visionox_rm692e5 *to_visionox_rm692e5(struct drm_panel *panel) +{ + return container_of(panel, struct visionox_rm692e5, panel); +} + +static void visionox_rm692e5_reset(struct visionox_rm692e5 *ctx) +{ + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(10000, 11000); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(1000, 2000); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + msleep(32); +} + +static int visionox_rm692e5_on(struct visionox_rm692e5 *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfe, 0x40); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xbd, 0x07); + mipi_dsi_usleep_range(&dsi_ctx, 17000, 18000); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfe, 0xd2); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x50, 0x11); + mipi_dsi_dcs_set_display_brightness_multi(&dsi_ctx, 0x00ab); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x52, 0x30); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x09); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x54, 0x60); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_POWER_SAVE, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x56, 0x38); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x58, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x59, 0x14); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5a, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5b, 0x1c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5c, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5d, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_CABC_MIN_BRIGHTNESS, 0x20); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5f, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x60, 0xe8); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x61, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x62, 0x07); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x63, 0x0c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x64, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x65, 0x0e); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x66, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x67, 0x16); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x68, 0x18); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x69, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6a, 0x10); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6b, 0xf0); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6c, 0x07); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6d, 0x10); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6e, 0x20); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x70, 0x06); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x71, 0x0f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x72, 0x0f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x73, 0x33); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x74, 0x0e); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x75, 0x1c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x76, 0x2a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x77, 0x38); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x78, 0x46); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x79, 0x54); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7a, 0x62); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7b, 0x69); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7c, 0x70); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7d, 0x77); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7e, 0x79); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7f, 0x7b); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x80, 0x7d); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x81, 0x7e); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x82, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x83, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x84, 0x22); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x85, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x86, 0x2a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x87, 0x40); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x88, 0x2a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x89, 0xbe); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x8a, 0x3a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x8b, 0xfc); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x8c, 0x3a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x8d, 0xfa); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x8e, 0x3a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x8f, 0xf8); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x90, 0x3b); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x91, 0x38); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x92, 0x3b); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x93, 0x78); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x94, 0x3b); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x95, 0xb6); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x96, 0x4b); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x97, 0xf6); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x98, 0x4c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x99, 0x34); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x9a, 0x4c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x9b, 0x74); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x9c, 0x5c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x9d, 0x74); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x9e, 0x8c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x9f, 0xf4); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_READ_PPS_START, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xa3, 0x1c); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xa4, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xa5, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xa6, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xa7, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_READ_PPS_CONTINUE, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xaa, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xa0, 0x80); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfe, 0xa1); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xcd, 0x6b); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xce, 0xbb); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfe, 0xd1); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb4, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfe, 0x38); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x17, 0x0f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x18, 0x0f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfe, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfa, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc2, 0x08); + mipi_dsi_dcs_set_tear_on_multi(&dsi_ctx, MIPI_DSI_DCS_TEAR_MODE_VBLANK); + mipi_dsi_dcs_set_display_brightness_multi(&dsi_ctx, 0x000d); + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 50); + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + mipi_dsi_usleep_range(&dsi_ctx, 1000, 2000); + + return dsi_ctx.accum_err; +} + +static int visionox_rm692e5_disable(struct drm_panel *panel) +{ + struct visionox_rm692e5 *ctx = to_visionox_rm692e5(panel); + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_usleep_range(&dsi_ctx, 1000, 2000); + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + mipi_dsi_usleep_range(&dsi_ctx, 1000, 2000); + + return dsi_ctx.accum_err; +} + +static int visionox_rm692e5_prepare(struct drm_panel *panel) +{ + struct visionox_rm692e5 *ctx = to_visionox_rm692e5(panel); + struct drm_dsc_picture_parameter_set pps; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(visionox_rm692e5_supplies), + ctx->supplies); + if (ret < 0) + return ret; + + visionox_rm692e5_reset(ctx); + + ret = visionox_rm692e5_on(ctx); + if (ret < 0) { + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + goto err; + } + + drm_dsc_pps_payload_pack(&pps, &ctx->dsc); + mipi_dsi_picture_parameter_set_multi(&dsi_ctx, &pps); + mipi_dsi_compression_mode_ext_multi(&dsi_ctx, true, MIPI_DSI_COMPRESSION_DSC, 0); + + mipi_dsi_msleep(&dsi_ctx, 28); + + if (dsi_ctx.accum_err < 0) { + ret = dsi_ctx.accum_err; + goto err; + } + + return dsi_ctx.accum_err; +err: + regulator_bulk_disable(ARRAY_SIZE(visionox_rm692e5_supplies), + ctx->supplies); + return ret; +} + +static int visionox_rm692e5_unprepare(struct drm_panel *panel) +{ + struct visionox_rm692e5 *ctx = to_visionox_rm692e5(panel); + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(visionox_rm692e5_supplies), + ctx->supplies); + + return 0; +} + +static const struct drm_display_mode visionox_rm692e5_modes[] = { + /* Let's initialize the highest frequency first */ + { /* 120Hz mode */ + .clock = (1080 + 26 + 39 + 36) * (2400 + 16 + 21 + 16) * 120 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 26, + .hsync_end = 1080 + 26 + 39, + .htotal = 1080 + 26 + 39 + 36, + .vdisplay = 2400, + .vsync_start = 2400 + 16, + .vsync_end = 2400 + 16 + 21, + .vtotal = 2400 + 16 + 21 + 16, + .width_mm = 68, + .height_mm = 152, + .type = DRM_MODE_TYPE_DRIVER, + }, + { /* 90Hz mode */ + .clock = (1080 + 26 + 39 + 36) * (2400 + 16 + 21 + 16) * 90 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 26, + .hsync_end = 1080 + 26 + 39, + .htotal = 1080 + 26 + 39 + 36, + .vdisplay = 2400, + .vsync_start = 2400 + 16, + .vsync_end = 2400 + 16 + 21, + .vtotal = 2400 + 16 + 21 + 16, + .width_mm = 68, + .height_mm = 152, + .type = DRM_MODE_TYPE_DRIVER, + }, + { /* 60Hz mode */ + .clock = (1080 + 26 + 39 + 36) * (2400 + 16 + 21 + 16) * 60 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 26, + .hsync_end = 1080 + 26 + 39, + .htotal = 1080 + 26 + 39 + 36, + .vdisplay = 2400, + .vsync_start = 2400 + 16, + .vsync_end = 2400 + 16 + 21, + .vtotal = 2400 + 16 + 21 + 16, + .width_mm = 68, + .height_mm = 152, + .type = DRM_MODE_TYPE_DRIVER, + }, +}; + +static int visionox_rm692e5_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + int count = 0; + + for (int i = 0; i < ARRAY_SIZE(visionox_rm692e5_modes); i++) + count += drm_connector_helper_get_modes_fixed(connector, + &visionox_rm692e5_modes[i]); + + return count; +} + +static const struct drm_panel_funcs visionox_rm692e5_panel_funcs = { + .prepare = visionox_rm692e5_prepare, + .unprepare = visionox_rm692e5_unprepare, + .disable = visionox_rm692e5_disable, + .get_modes = visionox_rm692e5_get_modes, +}; + +static int visionox_rm692e5_bl_update_status(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + u16 brightness = backlight_get_brightness(bl); + int ret; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + ret = mipi_dsi_dcs_set_display_brightness_large(dsi, brightness); + if (ret < 0) + return ret; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + return 0; +} + +static int visionox_rm692e5_bl_get_brightness(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + u16 brightness; + int ret; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + ret = mipi_dsi_dcs_get_display_brightness_large(dsi, &brightness); + if (ret < 0) + return ret; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + return brightness; +} + +static const struct backlight_ops visionox_rm692e5_bl_ops = { + .update_status = visionox_rm692e5_bl_update_status, + .get_brightness = visionox_rm692e5_bl_get_brightness, +}; + +static struct backlight_device * +visionox_rm692e5_create_backlight(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + const struct backlight_properties props = { + .type = BACKLIGHT_RAW, + .brightness = 2047, + .max_brightness = 4095, + }; + + return devm_backlight_device_register(dev, dev_name(dev), dev, dsi, + &visionox_rm692e5_bl_ops, &props); +} + +static int visionox_rm692e5_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct visionox_rm692e5 *ctx; + int ret; + + ctx = devm_drm_panel_alloc(dev, struct visionox_rm692e5, panel, + &visionox_rm692e5_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ret = devm_regulator_bulk_get_const(&dsi->dev, + ARRAY_SIZE(visionox_rm692e5_supplies), + visionox_rm692e5_supplies, + &ctx->supplies); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get regulators\n"); + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), + "Failed to get reset-gpios\n"); + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS; + + ctx->panel.prepare_prev_first = true; + + ctx->panel.backlight = visionox_rm692e5_create_backlight(dsi); + if (IS_ERR(ctx->panel.backlight)) + return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight), + "Failed to create backlight\n"); + + drm_panel_add(&ctx->panel); + + dsi->dsc = &ctx->dsc; + ctx->dsc.dsc_version_major = 1; + ctx->dsc.dsc_version_minor = 1; + ctx->dsc.slice_height = 20; + ctx->dsc.slice_width = 540; + ctx->dsc.slice_count = 1080 / ctx->dsc.slice_width; + ctx->dsc.bits_per_component = 10; + ctx->dsc.bits_per_pixel = 8 << 4; + ctx->dsc.block_pred_enable = true; + + ret = devm_mipi_dsi_attach(dev, dsi); + if (ret < 0) { + drm_panel_remove(&ctx->panel); + return dev_err_probe(dev, ret, "Failed to attach to DSI host\n"); + } + + return 0; +} + +static void visionox_rm692e5_remove(struct mipi_dsi_device *dsi) +{ + struct visionox_rm692e5 *ctx = mipi_dsi_get_drvdata(dsi); + + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id visionox_rm692e5_of_match[] = { + { .compatible = "visionox,rm692e5" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, visionox_rm692e5_of_match); + +static struct mipi_dsi_driver visionox_rm692e5_driver = { + .probe = visionox_rm692e5_probe, + .remove = visionox_rm692e5_remove, + .driver = { + .name = "panel-visionox-rm692e5", + .of_match_table = visionox_rm692e5_of_match, + }, +}; +module_mipi_dsi_driver(visionox_rm692e5_driver); + +MODULE_AUTHOR("Eugene Lepshy <fekz115@gmail.com>"); +MODULE_AUTHOR("Danila Tikhonov <danila@jiaxyga.com>"); +MODULE_DESCRIPTION("DRM driver for Visionox RM692E5 cmd mode dsi panel"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-visionox-vtdr6130.c b/drivers/gpu/drm/panel/panel-visionox-vtdr6130.c new file mode 100644 index 000000000000..97a79411e1ec --- /dev/null +++ b/drivers/gpu/drm/panel/panel-visionox-vtdr6130.c @@ -0,0 +1,325 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2023, Linaro Limited + +#include <linux/backlight.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/regulator/consumer.h> +#include <linux/module.h> +#include <linux/of.h> + +#include <drm/display/drm_dsc.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +#include <video/mipi_display.h> + +struct visionox_vtdr6130 { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data *supplies; +}; + +static const struct regulator_bulk_data visionox_vtdr6130_supplies[] = { + { .supply = "vddio" }, + { .supply = "vci" }, + { .supply = "vdd" }, +}; + +static inline struct visionox_vtdr6130 *to_visionox_vtdr6130(struct drm_panel *panel) +{ + return container_of(panel, struct visionox_vtdr6130, panel); +} + +static void visionox_vtdr6130_reset(struct visionox_vtdr6130 *ctx) +{ + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(10000, 11000); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(10000, 11000); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(10000, 11000); +} + +static int visionox_vtdr6130_on(struct visionox_vtdr6130 *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + mipi_dsi_dcs_set_tear_on_multi(&dsi_ctx, MIPI_DSI_DCS_TEAR_MODE_VBLANK); + + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, + MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x20); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, + MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0x00, + 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x59, 0x09); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6c, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6d, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x70, 0x12, 0x00, 0x00, 0xab, + 0x30, 0x80, 0x09, 0x60, 0x04, 0x38, 0x00, + 0x28, 0x02, 0x1c, 0x02, 0x1c, 0x02, 0x00, + 0x02, 0x0e, 0x00, 0x20, 0x03, 0xdd, 0x00, + 0x07, 0x00, 0x0c, 0x02, 0x77, 0x02, 0x8b, + 0x18, 0x00, 0x10, 0xf0, 0x07, 0x10, 0x20, + 0x00, 0x06, 0x0f, 0x0f, 0x33, 0x0e, 0x1c, + 0x2a, 0x38, 0x46, 0x54, 0x62, 0x69, 0x70, + 0x77, 0x79, 0x7b, 0x7d, 0x7e, 0x02, 0x02, + 0x22, 0x00, 0x2a, 0x40, 0x2a, 0xbe, 0x3a, + 0xfc, 0x3a, 0xfa, 0x3a, 0xf8, 0x3b, 0x38, + 0x3b, 0x78, 0x3b, 0xb6, 0x4b, 0xb6, 0x4b, + 0xf4, 0x4b, 0xf4, 0x6c, 0x34, 0x84, 0x74, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf0, 0xaa, 0x10); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb1, 0x01, 0x38, 0x00, 0x14, + 0x00, 0x1c, 0x00, 0x01, 0x66, 0x00, 0x14, + 0x00, 0x14, 0x00, 0x01, 0x66, 0x00, 0x14, + 0x05, 0xcc, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf0, 0xaa, 0x13); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xce, 0x09, 0x11, 0x09, 0x11, + 0x08, 0xc1, 0x07, 0xfa, 0x05, 0xa4, 0x00, + 0x3c, 0x00, 0x34, 0x00, 0x24, 0x00, 0x0c, + 0x00, 0x0c, 0x04, 0x00, 0x35); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf0, 0xaa, 0x14); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb2, 0x03, 0x33); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb4, 0x00, 0x33, 0x00, 0x00, + 0x00, 0x3e, 0x00, 0x00, 0x00, 0x3e, 0x00, + 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb5, 0x00, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x06, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb9, 0x00, 0x00, 0x08, 0x09, + 0x09, 0x09); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xbc, 0x10, 0x00, 0x00, 0x06, + 0x11, 0x09, 0x3b, 0x09, 0x47, 0x09, 0x47, + 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xbe, 0x10, 0x10, 0x00, 0x08, + 0x22, 0x09, 0x19, 0x09, 0x25, 0x09, 0x25, + 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x5a, 0x80); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x65, 0x14); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfa, 0x08, 0x08, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x5a, 0x81); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x65, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf3, 0x0f); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf0, 0xaa, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x5a, 0x82); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf9, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x51, 0x83); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x65, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf8, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x5a, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x65, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf4, 0x9a); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x5a, 0x00); + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 120); + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 20); + + return dsi_ctx.accum_err; +} + +static void visionox_vtdr6130_off(struct visionox_vtdr6130 *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 20); + + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 120); +} + +static int visionox_vtdr6130_prepare(struct drm_panel *panel) +{ + struct visionox_vtdr6130 *ctx = to_visionox_vtdr6130(panel); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(visionox_vtdr6130_supplies), + ctx->supplies); + if (ret < 0) + return ret; + + visionox_vtdr6130_reset(ctx); + + ret = visionox_vtdr6130_on(ctx); + if (ret < 0) { + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(visionox_vtdr6130_supplies), + ctx->supplies); + return ret; + } + + return 0; +} + +static int visionox_vtdr6130_unprepare(struct drm_panel *panel) +{ + struct visionox_vtdr6130 *ctx = to_visionox_vtdr6130(panel); + + visionox_vtdr6130_off(ctx); + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + + regulator_bulk_disable(ARRAY_SIZE(visionox_vtdr6130_supplies), + ctx->supplies); + + return 0; +} + +static const struct drm_display_mode visionox_vtdr6130_mode = { + .clock = (1080 + 20 + 2 + 20) * (2400 + 20 + 2 + 18) * 144 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 20, + .hsync_end = 1080 + 20 + 2, + .htotal = 1080 + 20 + 2 + 20, + .vdisplay = 2400, + .vsync_start = 2400 + 20, + .vsync_end = 2400 + 20 + 2, + .vtotal = 2400 + 20 + 2 + 18, + .width_mm = 71, + .height_mm = 157, +}; + +static int visionox_vtdr6130_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &visionox_vtdr6130_mode); + if (!mode) + 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 visionox_vtdr6130_panel_funcs = { + .prepare = visionox_vtdr6130_prepare, + .unprepare = visionox_vtdr6130_unprepare, + .get_modes = visionox_vtdr6130_get_modes, +}; + +static int visionox_vtdr6130_bl_update_status(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + u16 brightness = backlight_get_brightness(bl); + + return mipi_dsi_dcs_set_display_brightness_large(dsi, brightness); +} + +static const struct backlight_ops visionox_vtdr6130_bl_ops = { + .update_status = visionox_vtdr6130_bl_update_status, +}; + +static struct backlight_device * +visionox_vtdr6130_create_backlight(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + const struct backlight_properties props = { + .type = BACKLIGHT_RAW, + .brightness = 4095, + .max_brightness = 4095, + }; + + return devm_backlight_device_register(dev, dev_name(dev), dev, dsi, + &visionox_vtdr6130_bl_ops, &props); +} + +static int visionox_vtdr6130_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct visionox_vtdr6130 *ctx; + int ret; + + ctx = devm_drm_panel_alloc(dev, struct visionox_vtdr6130, panel, + &visionox_vtdr6130_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ret = devm_regulator_bulk_get_const(&dsi->dev, + ARRAY_SIZE(visionox_vtdr6130_supplies), + visionox_vtdr6130_supplies, + &ctx->supplies); + if (ret < 0) + return ret; + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), + "Failed to get reset-gpios\n"); + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_NO_EOT_PACKET | + MIPI_DSI_CLOCK_NON_CONTINUOUS; + ctx->panel.prepare_prev_first = true; + + ctx->panel.backlight = visionox_vtdr6130_create_backlight(dsi); + if (IS_ERR(ctx->panel.backlight)) + return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight), + "Failed to create backlight\n"); + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "Failed to attach to DSI host: %d\n", ret); + drm_panel_remove(&ctx->panel); + return ret; + } + + return 0; +} + +static void visionox_vtdr6130_remove(struct mipi_dsi_device *dsi) +{ + struct visionox_vtdr6130 *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id visionox_vtdr6130_of_match[] = { + { .compatible = "visionox,vtdr6130" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, visionox_vtdr6130_of_match); + +static struct mipi_dsi_driver visionox_vtdr6130_driver = { + .probe = visionox_vtdr6130_probe, + .remove = visionox_vtdr6130_remove, + .driver = { + .name = "panel-visionox-vtdr6130", + .of_match_table = visionox_vtdr6130_of_match, + }, +}; +module_mipi_dsi_driver(visionox_vtdr6130_driver); + +MODULE_AUTHOR("Neil Armstrong <neil.armstrong@linaro.org>"); +MODULE_DESCRIPTION("Panel driver for the Visionox VTDR6130 AMOLED DSI panel"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-widechips-ws2401.c b/drivers/gpu/drm/panel/panel-widechips-ws2401.c new file mode 100644 index 000000000000..dd74610bd2eb --- /dev/null +++ b/drivers/gpu/drm/panel/panel-widechips-ws2401.c @@ -0,0 +1,446 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Panel driver for the WideChips WS2401 480x800 DPI RGB panel, used in + * the Samsung Mobile Display (SMD) LMS380KF01. + * Found in the Samsung Galaxy Ace 2 GT-I8160 mobile phone. + * Linus Walleij <linus.walleij@linaro.org> + * Inspired by code and know-how in the vendor driver by Gareth Phillips. + */ +#include <drm/drm_mipi_dbi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +#include <linux/backlight.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/media-bus-format.h> +#include <linux/module.h> +#include <linux/regulator/consumer.h> +#include <linux/spi/spi.h> + +#include <video/mipi_display.h> + +#define WS2401_RESCTL 0xb8 /* Resolution select control */ +#define WS2401_PSMPS 0xbd /* SMPS positive control */ +#define WS2401_NSMPS 0xbe /* SMPS negative control */ +#define WS2401_SMPS 0xbf +#define WS2401_BCMODE 0xc1 /* Backlight control mode */ +#define WS2401_WRBLCTL 0xc3 /* Backlight control */ +#define WS2401_WRDISBV 0xc4 /* Write manual brightness */ +#define WS2401_WRCTRLD 0xc6 /* Write BL control */ +#define WS2401_WRMIE 0xc7 /* Write MIE mode */ +#define WS2401_READ_ID1 0xda /* Read panel ID 1 */ +#define WS2401_READ_ID2 0xdb /* Read panel ID 2 */ +#define WS2401_READ_ID3 0xdc /* Read panel ID 3 */ +#define WS2401_GAMMA_R1 0xe7 /* Gamma red 1 */ +#define WS2401_GAMMA_G1 0xe8 /* Gamma green 1 */ +#define WS2401_GAMMA_B1 0xe9 /* Gamma blue 1 */ +#define WS2401_GAMMA_R2 0xea /* Gamma red 2 */ +#define WS2401_GAMMA_G2 0xeb /* Gamma green 2 */ +#define WS2401_GAMMA_B2 0xec /* Gamma blue 2 */ +#define WS2401_PASSWD1 0xf0 /* Password command for level 2 */ +#define WS2401_DISCTL 0xf2 /* Display control */ +#define WS2401_PWRCTL 0xf3 /* Power control */ +#define WS2401_VCOMCTL 0xf4 /* VCOM control */ +#define WS2401_SRCCTL 0xf5 /* Source control */ +#define WS2401_PANELCTL 0xf6 /* Panel control */ + +static const u8 ws2401_dbi_read_commands[] = { + WS2401_READ_ID1, + WS2401_READ_ID2, + WS2401_READ_ID3, + 0, /* sentinel */ +}; + +/** + * struct ws2401 - state container for a panel controlled by the WS2401 + * controller + */ +struct ws2401 { + /** @dev: the container device */ + struct device *dev; + /** @dbi: the DBI bus abstraction handle */ + struct mipi_dbi dbi; + /** @panel: the DRM panel instance for this device */ + struct drm_panel panel; + /** @width: the width of this panel in mm */ + u32 width; + /** @height: the height of this panel in mm */ + u32 height; + /** @reset: reset GPIO line */ + struct gpio_desc *reset; + /** @regulators: VCCIO and VIO supply regulators */ + struct regulator_bulk_data regulators[2]; + /** @internal_bl: If using internal backlight */ + bool internal_bl; +}; + +static const struct drm_display_mode lms380kf01_480_800_mode = { + /* + * The vendor driver states that the "SMD panel" has a clock + * frequency of 49920000 Hz / 2 = 24960000 Hz. + */ + .clock = 24960, + .hdisplay = 480, + .hsync_start = 480 + 8, + .hsync_end = 480 + 8 + 10, + .htotal = 480 + 8 + 10 + 8, + .vdisplay = 800, + .vsync_start = 800 + 8, + .vsync_end = 800 + 8 + 2, + .vtotal = 800 + 8 + 2 + 18, + .width_mm = 50, + .height_mm = 84, + .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, +}; + +static inline struct ws2401 *to_ws2401(struct drm_panel *panel) +{ + return container_of(panel, struct ws2401, panel); +} + +static void ws2401_read_mtp_id(struct ws2401 *ws) +{ + struct mipi_dbi *dbi = &ws->dbi; + u8 id1, id2, id3; + int ret; + + ret = mipi_dbi_command_read(dbi, WS2401_READ_ID1, &id1); + if (ret) { + dev_err(ws->dev, "unable to read MTP ID 1\n"); + return; + } + ret = mipi_dbi_command_read(dbi, WS2401_READ_ID2, &id2); + if (ret) { + dev_err(ws->dev, "unable to read MTP ID 2\n"); + return; + } + ret = mipi_dbi_command_read(dbi, WS2401_READ_ID3, &id3); + if (ret) { + dev_err(ws->dev, "unable to read MTP ID 3\n"); + return; + } + dev_info(ws->dev, "MTP ID: %02x %02x %02x\n", id1, id2, id3); +} + +static int ws2401_power_on(struct ws2401 *ws) +{ + struct mipi_dbi *dbi = &ws->dbi; + int ret; + + /* Power up */ + ret = regulator_bulk_enable(ARRAY_SIZE(ws->regulators), + ws->regulators); + if (ret) { + dev_err(ws->dev, "failed to enable regulators: %d\n", ret); + return ret; + } + msleep(10); + + /* Assert reset >=1 ms */ + gpiod_set_value_cansleep(ws->reset, 1); + usleep_range(1000, 5000); + /* De-assert reset */ + gpiod_set_value_cansleep(ws->reset, 0); + /* Wait >= 10 ms */ + msleep(10); + dev_dbg(ws->dev, "de-asserted RESET\n"); + + /* + * Exit sleep mode and initialize display - some hammering is + * necessary. + */ + mipi_dbi_command(dbi, MIPI_DCS_EXIT_SLEEP_MODE); + mipi_dbi_command(dbi, MIPI_DCS_EXIT_SLEEP_MODE); + msleep(50); + + /* Magic to unlock level 2 control of the display */ + mipi_dbi_command(dbi, WS2401_PASSWD1, 0x5a, 0x5a); + /* Configure resolution to 480RGBx800 */ + mipi_dbi_command(dbi, WS2401_RESCTL, 0x12); + /* Set addressing mode Flip V(d0), Flip H(d1) RGB/BGR(d3) */ + mipi_dbi_command(dbi, MIPI_DCS_SET_ADDRESS_MODE, 0x01); + /* Set pixel format: 24 bpp */ + mipi_dbi_command(dbi, MIPI_DCS_SET_PIXEL_FORMAT, 0x70); + mipi_dbi_command(dbi, WS2401_SMPS, 0x00, 0x0f); + mipi_dbi_command(dbi, WS2401_PSMPS, 0x06, 0x03, /* DDVDH: 4.6v */ + 0x7e, 0x03, 0x12, 0x37); + mipi_dbi_command(dbi, WS2401_NSMPS, 0x06, 0x03, /* DDVDH: -4.6v */ + 0x7e, 0x02, 0x15, 0x37); + mipi_dbi_command(dbi, WS2401_SMPS, 0x02, 0x0f); + mipi_dbi_command(dbi, WS2401_PWRCTL, 0x10, 0xA9, 0x00, 0x01, 0x44, + 0xb4, /* VGH:16.1v, VGL:-13.8v */ + 0x50, /* GREFP:4.2v (default) */ + 0x50, /* GREFN:-4.2v (default) */ + 0x00, + 0x44); /* VOUTL:-10v (default) */ + mipi_dbi_command(dbi, WS2401_DISCTL, 0x01, 0x00, 0x00, 0x00, 0x14, + 0x16); + mipi_dbi_command(dbi, WS2401_VCOMCTL, 0x30, 0x53, 0x53); + mipi_dbi_command(dbi, WS2401_SRCCTL, 0x03, 0x0C, 0x00, 0x00, 0x00, + 0x01, /* 2 dot inversion */ + 0x01, 0x06, 0x03); + mipi_dbi_command(dbi, WS2401_PANELCTL, 0x14, 0x00, 0x80, 0x00); + mipi_dbi_command(dbi, WS2401_WRMIE, 0x01); + + /* Set up gamma, probably these are P-gamma and N-gamma for each color */ + mipi_dbi_command(dbi, WS2401_GAMMA_R1, 0x00, + 0x5b, 0x42, 0x41, 0x3f, 0x42, 0x3d, 0x38, 0x2e, + 0x2b, 0x2a, 0x27, 0x22, 0x27, 0x0f, 0x00, 0x00); + mipi_dbi_command(dbi, WS2401_GAMMA_R2, 0x00, + 0x5b, 0x42, 0x41, 0x3f, 0x42, 0x3d, 0x38, 0x2e, + 0x2b, 0x2a, 0x27, 0x22, 0x27, 0x0f, 0x00, 0x00); + mipi_dbi_command(dbi, WS2401_GAMMA_G1, 0x00, + 0x59, 0x40, 0x3f, 0x3e, 0x41, 0x3d, 0x39, 0x2f, + 0x2c, 0x2b, 0x29, 0x25, 0x29, 0x19, 0x08, 0x00); + mipi_dbi_command(dbi, WS2401_GAMMA_G2, 0x00, + 0x59, 0x40, 0x3f, 0x3e, 0x41, 0x3d, 0x39, 0x2f, + 0x2c, 0x2b, 0x29, 0x25, 0x29, 0x19, 0x08, 0x00); + mipi_dbi_command(dbi, WS2401_GAMMA_B1, 0x00, + 0x57, 0x3b, 0x3a, 0x3b, 0x3f, 0x3b, 0x38, 0x27, + 0x38, 0x2a, 0x26, 0x22, 0x34, 0x0c, 0x09, 0x00); + mipi_dbi_command(dbi, WS2401_GAMMA_B2, 0x00, + 0x57, 0x3b, 0x3a, 0x3b, 0x3f, 0x3b, 0x38, 0x27, + 0x38, 0x2a, 0x26, 0x22, 0x34, 0x0c, 0x09, 0x00); + + if (ws->internal_bl) { + mipi_dbi_command(dbi, WS2401_WRCTRLD, 0x2c); + } else { + mipi_dbi_command(dbi, WS2401_WRCTRLD, 0x00); + /* + * When not using internal backlight we do not need any further + * L2 accesses to the panel so we close the door on our way out. + * Otherwise we need to leave the L2 door open. + */ + mipi_dbi_command(dbi, WS2401_PASSWD1, 0xa5, 0xa5); + } + + return 0; +} + +static int ws2401_power_off(struct ws2401 *ws) +{ + /* Go into RESET and disable regulators */ + gpiod_set_value_cansleep(ws->reset, 1); + return regulator_bulk_disable(ARRAY_SIZE(ws->regulators), + ws->regulators); +} + +static int ws2401_unprepare(struct drm_panel *panel) +{ + struct ws2401 *ws = to_ws2401(panel); + struct mipi_dbi *dbi = &ws->dbi; + + /* Make sure we disable backlight, if any */ + if (ws->internal_bl) + mipi_dbi_command(dbi, WS2401_WRCTRLD, 0x00); + mipi_dbi_command(dbi, MIPI_DCS_ENTER_SLEEP_MODE); + msleep(120); + return ws2401_power_off(to_ws2401(panel)); +} + +static int ws2401_disable(struct drm_panel *panel) +{ + struct ws2401 *ws = to_ws2401(panel); + struct mipi_dbi *dbi = &ws->dbi; + + mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_OFF); + msleep(25); + + return 0; +} + +static int ws2401_prepare(struct drm_panel *panel) +{ + return ws2401_power_on(to_ws2401(panel)); +} + +static int ws2401_enable(struct drm_panel *panel) +{ + struct ws2401 *ws = to_ws2401(panel); + struct mipi_dbi *dbi = &ws->dbi; + + mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_ON); + + return 0; +} + +/** + * ws2401_get_modes() - return the mode + * @panel: the panel to get the mode for + * @connector: reference to the central DRM connector control structure + */ +static int ws2401_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct ws2401 *ws = to_ws2401(panel); + struct drm_display_mode *mode; + static const u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24; + + /* + * We just support the LMS380KF01 so far, if we implement more panels + * this mode, the following connector display_info settings and + * probably the custom DCS sequences needs to selected based on what + * the target panel needs. + */ + mode = drm_mode_duplicate(connector->dev, &lms380kf01_480_800_mode); + if (!mode) { + dev_err(ws->dev, "failed to add mode\n"); + return -ENOMEM; + } + + connector->display_info.bpc = 8; + connector->display_info.width_mm = mode->width_mm; + connector->display_info.height_mm = mode->height_mm; + connector->display_info.bus_flags = + DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE; + drm_display_info_set_bus_formats(&connector->display_info, + &bus_format, 1); + + drm_mode_set_name(mode); + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + + drm_mode_probed_add(connector, mode); + + return 1; +} + +static const struct drm_panel_funcs ws2401_drm_funcs = { + .disable = ws2401_disable, + .unprepare = ws2401_unprepare, + .prepare = ws2401_prepare, + .enable = ws2401_enable, + .get_modes = ws2401_get_modes, +}; + +static int ws2401_set_brightness(struct backlight_device *bl) +{ + struct ws2401 *ws = bl_get_data(bl); + struct mipi_dbi *dbi = &ws->dbi; + u8 brightness = backlight_get_brightness(bl); + + if (backlight_is_blank(bl)) { + mipi_dbi_command(dbi, WS2401_WRCTRLD, 0x00); + } else { + mipi_dbi_command(dbi, WS2401_WRCTRLD, 0x2c); + mipi_dbi_command(dbi, WS2401_WRDISBV, brightness); + } + + return 0; +} + +static const struct backlight_ops ws2401_bl_ops = { + .update_status = ws2401_set_brightness, +}; + +static const struct backlight_properties ws2401_bl_props = { + .type = BACKLIGHT_PLATFORM, + .brightness = 120, + .max_brightness = U8_MAX, +}; + +static int ws2401_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct ws2401 *ws; + int ret; + + ws = devm_drm_panel_alloc(dev, struct ws2401, panel, &ws2401_drm_funcs, + DRM_MODE_CONNECTOR_DPI); + if (IS_ERR(ws)) + return PTR_ERR(ws); + + ws->dev = dev; + + /* + * VCI is the analog voltage supply + * VCCIO is the digital I/O voltage supply + */ + ws->regulators[0].supply = "vci"; + ws->regulators[1].supply = "vccio"; + ret = devm_regulator_bulk_get(dev, + ARRAY_SIZE(ws->regulators), + ws->regulators); + if (ret) + return dev_err_probe(dev, ret, "failed to get regulators\n"); + + ws->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ws->reset)) { + ret = PTR_ERR(ws->reset); + return dev_err_probe(dev, ret, "no RESET GPIO\n"); + } + + ret = mipi_dbi_spi_init(spi, &ws->dbi, NULL); + if (ret) + return dev_err_probe(dev, ret, "MIPI DBI init failed\n"); + ws->dbi.read_commands = ws2401_dbi_read_commands; + + ws2401_power_on(ws); + ws2401_read_mtp_id(ws); + ws2401_power_off(ws); + + ret = drm_panel_of_backlight(&ws->panel); + if (ret) + return dev_err_probe(dev, ret, + "failed to get external backlight device\n"); + + if (!ws->panel.backlight) { + dev_dbg(dev, "no external backlight, using internal backlight\n"); + ws->panel.backlight = + devm_backlight_device_register(dev, "ws2401", dev, ws, + &ws2401_bl_ops, &ws2401_bl_props); + if (IS_ERR(ws->panel.backlight)) + return dev_err_probe(dev, PTR_ERR(ws->panel.backlight), + "failed to register backlight device\n"); + } else { + dev_dbg(dev, "using external backlight\n"); + } + + spi_set_drvdata(spi, ws); + + drm_panel_add(&ws->panel); + dev_dbg(dev, "added panel\n"); + + return 0; +} + +static void ws2401_remove(struct spi_device *spi) +{ + struct ws2401 *ws = spi_get_drvdata(spi); + + drm_panel_remove(&ws->panel); +} + +/* + * Samsung LMS380KF01 is the one instance of this display controller that we + * know about, but if more are found, the controller can be parameterized + * here and used for other configurations. + */ +static const struct of_device_id ws2401_match[] = { + { .compatible = "samsung,lms380kf01", }, + {}, +}; +MODULE_DEVICE_TABLE(of, ws2401_match); + +static const struct spi_device_id ws2401_ids[] = { + { "lms380kf01" }, + { }, +}; +MODULE_DEVICE_TABLE(spi, ws2401_ids); + +static struct spi_driver ws2401_driver = { + .probe = ws2401_probe, + .remove = ws2401_remove, + .id_table = ws2401_ids, + .driver = { + .name = "ws2401-panel", + .of_match_table = ws2401_match, + }, +}; +module_spi_driver(ws2401_driver); + +MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>"); +MODULE_DESCRIPTION("Samsung WS2401 panel driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-xinpeng-xpp055c272.c b/drivers/gpu/drm/panel/panel-xinpeng-xpp055c272.c new file mode 100644 index 000000000000..fc6516373b5d --- /dev/null +++ b/drivers/gpu/drm/panel/panel-xinpeng-xpp055c272.c @@ -0,0 +1,319 @@ +// 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 <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; +}; + +static inline struct xpp055c272 *panel_to_xpp055c272(struct drm_panel *panel) +{ + return container_of(panel, struct xpp055c272, panel); +} + +static void xpp055c272_init_sequence(struct mipi_dsi_multi_context *dsi_ctx) +{ + /* + * Init sequence was supplied by the panel vendor without much + * documentation. + */ + mipi_dsi_dcs_write_seq_multi(dsi_ctx, XPP055C272_CMD_SETEXTC, 0xf1, 0x12, 0x83); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 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); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, XPP055C272_CMD_SETPOWER_EXT, 0x25); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, XPP055C272_CMD_SETPCR, 0x02, 0x11, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, XPP055C272_CMD_SETRGBIF, + 0x0c, 0x10, 0x0a, 0x50, 0x03, 0xff, 0x00, 0x00, + 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, XPP055C272_CMD_SETSCR, + 0x73, 0x73, 0x50, 0x50, 0x00, 0x00, 0x08, 0x70, + 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, XPP055C272_CMD_SETVDC, 0x46); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, XPP055C272_CMD_SETPANEL, 0x0b); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, XPP055C272_CMD_SETCYC, 0x80); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, XPP055C272_CMD_SETDISP, 0xc8, 0x12, 0x30); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, XPP055C272_CMD_SETEQ, + 0x07, 0x07, 0x0b, 0x0b, 0x03, 0x0b, 0x00, 0x00, + 0x00, 0x00, 0xff, 0x00, 0xC0, 0x10); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, XPP055C272_CMD_SETPOWER, + 0x53, 0x00, 0x1e, 0x1e, 0x77, 0xe1, 0xcc, 0xdd, + 0x67, 0x77, 0x33, 0x33); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, XPP055C272_CMD_SETECO, 0x00, 0x00, 0xff, + 0xff, 0x01, 0xff); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, XPP055C272_CMD_SETBGP, 0x09, 0x09); + mipi_dsi_msleep(dsi_ctx, 20); + + mipi_dsi_dcs_write_seq_multi(dsi_ctx, XPP055C272_CMD_SETVCOM, 0x87, 0x95); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 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); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 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); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 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); + + mipi_dsi_msleep(dsi_ctx, 60); +} + +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); + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + if (dsi_ctx.accum_err) + return dsi_ctx.accum_err; + + regulator_disable(ctx->iovcc); + regulator_disable(ctx->vci); + + 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); + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + dev_dbg(ctx->dev, "Resetting the panel\n"); + dsi_ctx.accum_err = regulator_enable(ctx->vci); + if (dsi_ctx.accum_err) { + dev_err(ctx->dev, "Failed to enable vci supply: %d\n", + dsi_ctx.accum_err); + return dsi_ctx.accum_err; + } + dsi_ctx.accum_err = regulator_enable(ctx->iovcc); + if (dsi_ctx.accum_err) { + dev_err(ctx->dev, "Failed to enable iovcc supply: %d\n", + dsi_ctx.accum_err); + 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); + + xpp055c272_init_sequence(&dsi_ctx); + if (!dsi_ctx.accum_err) + dev_dbg(ctx->dev, "Panel init sequence done\n"); + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + /* T9: 120ms */ + mipi_dsi_msleep(&dsi_ctx, 120); + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + + if (dsi_ctx.accum_err) + goto disable_iovcc; + + msleep(50); + + return 0; + +disable_iovcc: + regulator_disable(ctx->iovcc); +disable_vci: + regulator_disable(ctx->vci); + return dsi_ctx.accum_err; +} + +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, + .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) { + dev_err(ctx->dev, "Failed to add mode %ux%u@%u\n", + default_mode.hdisplay, default_mode.vdisplay, + drm_mode_vrefresh(&default_mode)); + 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_drm_panel_alloc(dev, struct xpp055c272, panel, + &xpp055c272_funcs, DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ctx->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), + "cannot get reset gpio\n"); + + ctx->vci = devm_regulator_get(dev, "vci"); + if (IS_ERR(ctx->vci)) + return dev_err_probe(dev, PTR_ERR(ctx->vci), + "Failed to request vci regulator\n"); + + ctx->iovcc = devm_regulator_get(dev, "iovcc"); + if (IS_ERR(ctx->iovcc)) + return dev_err_probe(dev, PTR_ERR(ctx->iovcc), + "Failed to request iovcc regulator\n"); + + 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_NO_EOT_PACKET; + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return ret; + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "mipi_dsi_attach failed: %d\n", ret); + drm_panel_remove(&ctx->panel); + return ret; + } + + return 0; +} + +static void xpp055c272_remove(struct mipi_dsi_device *dsi) +{ + struct xpp055c272 *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&ctx->panel); +} + +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, +}; +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"); |
