summaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm/panel
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/panel')
-rw-r--r--drivers/gpu/drm/panel/Kconfig720
-rw-r--r--drivers/gpu/drm/panel/Makefile75
-rw-r--r--drivers/gpu/drm/panel/panel-abt-y030xx067a.c383
-rw-r--r--drivers/gpu/drm/panel/panel-arm-versatile.c376
-rw-r--r--drivers/gpu/drm/panel/panel-asus-z00t-tm5p5-n35596.c355
-rw-r--r--drivers/gpu/drm/panel/panel-boe-bf060y8m-aj0.c443
-rw-r--r--drivers/gpu/drm/panel/panel-boe-himax8279d.c963
-rw-r--r--drivers/gpu/drm/panel/panel-boe-tv101wum-nl6.c1694
-rw-r--r--drivers/gpu/drm/panel/panel-dsi-cm.c652
-rw-r--r--drivers/gpu/drm/panel/panel-ebbg-ft8719.c283
-rw-r--r--drivers/gpu/drm/panel/panel-edp.c2023
-rw-r--r--drivers/gpu/drm/panel/panel-elida-kd35t133.c359
-rw-r--r--drivers/gpu/drm/panel/panel-feixin-k101-im2ba02.c515
-rw-r--r--drivers/gpu/drm/panel/panel-feiyang-fy07024di26a30d.c262
-rw-r--r--drivers/gpu/drm/panel/panel-ilitek-ili9322.c948
-rw-r--r--drivers/gpu/drm/panel/panel-ilitek-ili9341.c794
-rw-r--r--drivers/gpu/drm/panel/panel-ilitek-ili9881c.c976
-rw-r--r--drivers/gpu/drm/panel/panel-innolux-ej030na.c308
-rw-r--r--drivers/gpu/drm/panel/panel-innolux-p079zca.c551
-rw-r--r--drivers/gpu/drm/panel/panel-jdi-fhd-r63452.c321
-rw-r--r--drivers/gpu/drm/panel/panel-jdi-lt070me05000.c523
-rw-r--r--drivers/gpu/drm/panel/panel-khadas-ts050.c868
-rw-r--r--drivers/gpu/drm/panel/panel-kingdisplay-kd097d04.c460
-rw-r--r--drivers/gpu/drm/panel/panel-leadtek-ltk050h3146w.c671
-rw-r--r--drivers/gpu/drm/panel/panel-leadtek-ltk500hd1829.c513
-rw-r--r--drivers/gpu/drm/panel/panel-lg-lb035q02.c242
-rw-r--r--drivers/gpu/drm/panel/panel-lg-lg4573.c295
-rw-r--r--drivers/gpu/drm/panel/panel-lvds.c262
-rw-r--r--drivers/gpu/drm/panel/panel-mantix-mlaf057we51.c369
-rw-r--r--drivers/gpu/drm/panel/panel-nec-nl8048hl11.c253
-rw-r--r--drivers/gpu/drm/panel/panel-newvision-nv3052c.c482
-rw-r--r--drivers/gpu/drm/panel/panel-novatek-nt35510.c1097
-rw-r--r--drivers/gpu/drm/panel/panel-novatek-nt35560.c559
-rw-r--r--drivers/gpu/drm/panel/panel-novatek-nt35950.c708
-rw-r--r--drivers/gpu/drm/panel/panel-novatek-nt36672a.c719
-rw-r--r--drivers/gpu/drm/panel/panel-novatek-nt39016.c360
-rw-r--r--drivers/gpu/drm/panel/panel-olimex-lcd-olinuxino.c320
-rw-r--r--drivers/gpu/drm/panel/panel-orisetech-otm8009a.c527
-rw-r--r--drivers/gpu/drm/panel/panel-osd-osd101t2587-53ts.c247
-rw-r--r--drivers/gpu/drm/panel/panel-panasonic-vvx10f034n00.c289
-rw-r--r--drivers/gpu/drm/panel/panel-raspberrypi-touchscreen.c514
-rw-r--r--drivers/gpu/drm/panel/panel-raydium-rm67191.c659
-rw-r--r--drivers/gpu/drm/panel/panel-raydium-rm68200.c442
-rw-r--r--drivers/gpu/drm/panel/panel-ronbo-rb070d30.c238
-rw-r--r--drivers/gpu/drm/panel/panel-samsung-atna33xc20.c391
-rw-r--r--drivers/gpu/drm/panel/panel-samsung-db7430.c346
-rw-r--r--drivers/gpu/drm/panel/panel-samsung-ld9040.c394
-rw-r--r--drivers/gpu/drm/panel/panel-samsung-s6d16d0.c241
-rw-r--r--drivers/gpu/drm/panel/panel-samsung-s6d27a1.c319
-rw-r--r--drivers/gpu/drm/panel/panel-samsung-s6e3ha2.c782
-rw-r--r--drivers/gpu/drm/panel/panel-samsung-s6e63j0x03.c521
-rw-r--r--drivers/gpu/drm/panel/panel-samsung-s6e63m0-dsi.c140
-rw-r--r--drivers/gpu/drm/panel/panel-samsung-s6e63m0-spi.c88
-rw-r--r--drivers/gpu/drm/panel/panel-samsung-s6e63m0.c762
-rw-r--r--drivers/gpu/drm/panel/panel-samsung-s6e63m0.h40
-rw-r--r--drivers/gpu/drm/panel/panel-samsung-s6e88a0-ams452ef01.c287
-rw-r--r--drivers/gpu/drm/panel/panel-samsung-s6e8aa0.c1060
-rw-r--r--drivers/gpu/drm/panel/panel-samsung-sofef00.c346
-rw-r--r--drivers/gpu/drm/panel/panel-seiko-43wvf1g.c345
-rw-r--r--drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c440
-rw-r--r--drivers/gpu/drm/panel/panel-sharp-ls037v7dw01.c223
-rw-r--r--drivers/gpu/drm/panel/panel-sharp-ls043t1le01.c351
-rw-r--r--drivers/gpu/drm/panel/panel-sharp-ls060t1sx01.c331
-rw-r--r--drivers/gpu/drm/panel/panel-simple.c4703
-rw-r--r--drivers/gpu/drm/panel/panel-sitronix-st7701.c803
-rw-r--r--drivers/gpu/drm/panel/panel-sitronix-st7703.c638
-rw-r--r--drivers/gpu/drm/panel/panel-sitronix-st7789v.c415
-rw-r--r--drivers/gpu/drm/panel/panel-sony-acx565akm.c691
-rw-r--r--drivers/gpu/drm/panel/panel-sony-tulip-truly-nt35521.c550
-rw-r--r--drivers/gpu/drm/panel/panel-tdo-tl070wsh30.c248
-rw-r--r--drivers/gpu/drm/panel/panel-tpo-td028ttec1.c392
-rw-r--r--drivers/gpu/drm/panel/panel-tpo-td043mtea1.c506
-rw-r--r--drivers/gpu/drm/panel/panel-tpo-tpg110.c480
-rw-r--r--drivers/gpu/drm/panel/panel-truly-nt35597.c653
-rw-r--r--drivers/gpu/drm/panel/panel-visionox-rm69299.c286
-rw-r--r--drivers/gpu/drm/panel/panel-widechips-ws2401.c440
-rw-r--r--drivers/gpu/drm/panel/panel-xinpeng-xpp055c272.c375
77 files changed, 43205 insertions, 0 deletions
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
new file mode 100644
index 000000000..a582ddd58
--- /dev/null
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -0,0 +1,720 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config DRM_PANEL
+ bool
+ depends on DRM
+ help
+ Panel registration and lookup framework.
+
+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
+ depends on MFD_SYSCON
+ select VIDEOMODE_HELPERS
+ help
+ This driver supports the ARM Versatile panels connected to ARM
+ 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_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_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_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
+ depends on BACKLIGHT_CLASS_DEVICE
+ select VIDEOMODE_HELPERS
+ help
+ This driver supports LVDS panels that don't require device-specific
+ 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 (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_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_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_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_ILITEK_IL9322
+ tristate "Ilitek ILI9322 320x240 QVGA panels"
+ depends on OF && SPI
+ select REGMAP
+ help
+ 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 OF && 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_ILI9881C
+ tristate "Ilitek ILI9881C-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 ILI9881c 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
+ depends on DRM_MIPI_DSI
+ depends on BACKLIGHT_CLASS_DEVICE
+ help
+ Say Y here if you want to enable support for Innolux P079ZCA
+ TFT-LCD modules. The panel has a 1024x768 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_JDI_LT070ME05000
+ tristate "JDI LT070ME05000 WUXGA 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 JDI DSI video mode
+ panel as found in Google Nexus 7 (2013) devices.
+ The panel has a 1200(RGB)×1920 (WUXGA) resolution and uses
+ 24 bit per pixel.
+
+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_SAMSUNG_LD9040
+ tristate "Samsung LD9040 RGB/SPI panel"
+ depends on OF && SPI
+ select VIDEOMODE_HELPERS
+
+config DRM_PANEL_LG_LB035Q02
+ tristate "LG LB035Q024573 RGB panel"
+ depends on GPIOLIB && OF && SPI
+ help
+ Say Y here if you want to enable support for the LB035Q02 RGB panel
+ (found on the Gumstix Overo Palo35 board). To compile this driver as
+ a module, choose M here.
+
+config DRM_PANEL_LG_LG4573
+ tristate "LG4573 RGB/SPI panel"
+ depends on OF && SPI
+ select VIDEOMODE_HELPERS
+ help
+ Say Y here if you want to enable support for LG4573 RGB panel.
+ To compile this driver as a module, choose M here.
+
+config DRM_PANEL_NEC_NL8048HL11
+ tristate "NEC NL8048HL11 RGB panel"
+ depends on GPIOLIB && OF && SPI
+ help
+ Say Y here if you want to enable support for the NEC NL8048HL11 RGB
+ panel (found on the Zoom2/3/3630 SDP boards). To compile this driver
+ as a module, choose M here.
+
+config DRM_PANEL_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_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_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_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_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.
+
+ Say Y here if you want to enable support for Olimex Ltd.
+ LCD-OLinuXino panel.
+
+config DRM_PANEL_ORISETECH_OTM8009A
+ tristate "Orise Technology otm8009a 480x800 dsi 2dl 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 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
+ depends on DRM_MIPI_DSI
+ depends on BACKLIGHT_CLASS_DEVICE
+ help
+ Say Y here if you want to enable support for Panasonic VVX10F034N00
+ WUXGA (1920x1200) Novatek NT1397-based DSI panel as found in some
+ Xperia Z2 tablets
+
+config DRM_PANEL_RASPBERRYPI_TOUCHSCREEN
+ tristate "Raspberry Pi 7-inch touchscreen panel"
+ depends on DRM_MIPI_DSI
+ help
+ Say Y here if you want to enable support for the Raspberry
+ Pi 7" Touchscreen. To compile this driver as a module,
+ choose M here.
+
+config DRM_PANEL_RAYDIUM_RM67191
+ tristate "Raydium RM67191 FHD 1080x1920 DSI video mode panel"
+ depends on OF
+ depends on DRM_MIPI_DSI
+ depends on BACKLIGHT_CLASS_DEVICE
+ help
+ Say Y here if you want to enable support for Raydium RM67191 FHD
+ (1080x1920) DSI panel.
+
+config DRM_PANEL_RAYDIUM_RM68200
+ tristate "Raydium RM68200 720x1280 DSI video mode panel"
+ depends on OF
+ depends on DRM_MIPI_DSI
+ depends on BACKLIGHT_CLASS_DEVICE
+ help
+ Say Y here if you want to enable support for Raydium RM68200
+ 720x1280 DSI video mode 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_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_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_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_S6E3HA2
+ tristate "Samsung S6E3HA2 DSI video mode panel"
+ depends on OF
+ depends on DRM_MIPI_DSI
+ depends on BACKLIGHT_CLASS_DEVICE
+ select VIDEOMODE_HELPERS
+
+config DRM_PANEL_SAMSUNG_S6E63J0X03
+ tristate "Samsung S6E63J0X03 DSI command mode panel"
+ depends on OF
+ depends on DRM_MIPI_DSI
+ 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_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_S6E8AA0
+ tristate "Samsung S6E8AA0 DSI video mode panel"
+ depends on OF
+ select DRM_MIPI_DSI
+ select VIDEOMODE_HELPERS
+
+config DRM_PANEL_SAMSUNG_SOFEF00
+ tristate "Samsung sofef00/s6e3fc2x01 OnePlus 6/6T DSI cmd mode panels"
+ 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
+ command mode panels found in the OnePlus 6/6T smartphones.
+
+ The panels are 2280x1080@60Hz and 2340x1080@60Hz respectively
+
+config DRM_PANEL_SEIKO_43WVF1G
+ tristate "Seiko 43WVF1G panel"
+ depends on OF
+ depends on BACKLIGHT_CLASS_DEVICE
+ select VIDEOMODE_HELPERS
+ help
+ Say Y here if you want to enable support for the Seiko
+ 43WVF1G controller for 800x480 LCD panels
+
+config DRM_PANEL_SHARP_LQ101R1SX01
+ tristate "Sharp LQ101R1SX01 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 LQ101R1SX01
+ 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 and has a built-in LED backlight.
+
+ 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
+ depends on DRM_MIPI_DSI
+ depends on BACKLIGHT_CLASS_DEVICE
+ help
+ 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 DRM_MIPI_DSI
+ 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
+ depends on BACKLIGHT_CLASS_DEVICE
+ help
+ 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_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_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
+ depends on DRM_MIPI_DSI
+ help
+ Say Y here if you want to enable support for Truly NT35597 WQXGA Dual DSI
+ Video Mode 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_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
new file mode 100644
index 000000000..34e717382
--- /dev/null
+++ b/drivers/gpu/drm/panel/Makefile
@@ -0,0 +1,75 @@
+# 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_BOE_BF060Y8M_AJ0) += panel-boe-bf060y8m-aj0.o
+obj-$(CONFIG_DRM_PANEL_BOE_HIMAX8279D) += panel-boe-himax8279d.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_ILITEK_IL9322) += panel-ilitek-ili9322.o
+obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9341) += panel-ilitek-ili9341.o
+obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9881C) += panel-ilitek-ili9881c.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_JDI_LT070ME05000) += panel-jdi-lt070me05000.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_LG_LB035Q02) += panel-lg-lb035q02.o
+obj-$(CONFIG_DRM_PANEL_LG_LG4573) += panel-lg-lg4573.o
+obj-$(CONFIG_DRM_PANEL_NEC_NL8048HL11) += panel-nec-nl8048hl11.o
+obj-$(CONFIG_DRM_PANEL_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_NT36672A) += panel-novatek-nt36672a.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_OTM8009A) += panel-orisetech-otm8009a.o
+obj-$(CONFIG_DRM_PANEL_OSD_OSD101T2587_53TS) += panel-osd-osd101t2587-53ts.o
+obj-$(CONFIG_DRM_PANEL_PANASONIC_VVX10F034N00) += panel-panasonic-vvx10f034n00.o
+obj-$(CONFIG_DRM_PANEL_RASPBERRYPI_TOUCHSCREEN) += panel-raspberrypi-touchscreen.o
+obj-$(CONFIG_DRM_PANEL_RAYDIUM_RM67191) += panel-raydium-rm67191.o
+obj-$(CONFIG_DRM_PANEL_RAYDIUM_RM68200) += panel-raydium-rm68200.o
+obj-$(CONFIG_DRM_PANEL_RONBO_RB070D30) += panel-ronbo-rb070d30.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_S6E3HA2) += panel-samsung-s6e3ha2.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_AMS452EF01) += panel-samsung-s6e88a0-ams452ef01.o
+obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E8AA0) += panel-samsung-s6e8aa0.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_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_SONY_ACX565AKM) += panel-sony-acx565akm.o
+obj-$(CONFIG_DRM_PANEL_SONY_TULIP_TRULY_NT35521) += panel-sony-tulip-truly-nt35521.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_RM69299) += panel-visionox-rm69299.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 000000000..1cc0f1d09
--- /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_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_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ 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");
+
+ drm_panel_init(&priv->panel, dev, &y030xx067a_funcs,
+ DRM_MODE_CONNECTOR_DPI);
+
+ 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_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/panel/panel-arm-versatile.c b/drivers/gpu/drm/panel/panel-arm-versatile.c
new file mode 100644
index 000000000..503ecea72
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-arm-versatile.c
@@ -0,0 +1,376 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Panel driver for the ARM Versatile family reference designs from
+ * ARM Limited.
+ *
+ * Author:
+ * Linus Walleij <linus.wallei@linaro.org>
+ *
+ * On the Versatile AB, these panels come mounted on daughterboards
+ * named "IB1" or "IB2" (Interface Board 1 & 2 respectively.) They
+ * are documented in ARM DUI 0225D Appendix C and D. These daughter
+ * boards support TFT display panels.
+ *
+ * - The IB1 is a passive board where the display connector defines a
+ * few wires for encoding the display type for autodetection,
+ * suitable display settings can then be looked up from this setting.
+ * The magic bits can be read out from the system controller.
+ *
+ * - The IB2 is a more complex board intended for GSM phone development
+ * with some logic and a control register, which needs to be accessed
+ * and the board display needs to be turned on explicitly.
+ *
+ * On the Versatile PB, a special CLCD adaptor board is available
+ * supporting the same displays as the Versatile AB, plus one more
+ * Epson QCIF display.
+ *
+ */
+
+#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>
+
+#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
+ * unutilized starting with the RealView series.
+ */
+#define SYS_CLCD 0x50
+
+/* The Versatile can detect the connected panel type */
+#define SYS_CLCD_CLCDID_MASK (BIT(8)|BIT(9)|BIT(10)|BIT(11)|BIT(12))
+#define SYS_CLCD_ID_SANYO_3_8 (0x00 << 8)
+#define SYS_CLCD_ID_SHARP_8_4 (0x01 << 8)
+#define SYS_CLCD_ID_EPSON_2_2 (0x02 << 8)
+#define SYS_CLCD_ID_SANYO_2_5 (0x07 << 8)
+#define SYS_CLCD_ID_VGA (0x1f << 8)
+
+/* IB2 control register for the Versatile daughterboard */
+#define IB2_CTRL 0x00
+#define IB2_CTRL_LCD_SD BIT(1) /* 1 = shut down LCD */
+#define IB2_CTRL_LCD_BL_ON BIT(0)
+#define IB2_CTRL_LCD_MASK (BIT(0)|BIT(1))
+
+/**
+ * struct versatile_panel_type - lookup struct for the supported panels
+ */
+struct versatile_panel_type {
+ /**
+ * @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;
+ /**
+ * @width_mm: the panel width in mm
+ */
+ u32 width_mm;
+ /**
+ * @height_mm: the panel height in mm
+ */
+ u32 height_mm;
+ /**
+ * @ib2: the panel may be connected on an IB2 daughterboard
+ */
+ bool ib2;
+};
+
+/**
+ * struct versatile_panel - state container for the Versatile panels
+ */
+struct versatile_panel {
+ /**
+ * @dev: the container device
+ */
+ struct device *dev;
+ /**
+ * @panel: the DRM panel instance for this device
+ */
+ struct drm_panel panel;
+ /**
+ * @panel_type: the Versatile panel type as detected
+ */
+ const struct versatile_panel_type *panel_type;
+ /**
+ * @map: map to the parent syscon where the main register reside
+ */
+ struct regmap *map;
+ /**
+ * @ib2_map: map to the IB2 syscon, if applicable
+ */
+ struct regmap *ib2_map;
+};
+
+static const struct versatile_panel_type versatile_panels[] = {
+ /*
+ * Sanyo TM38QV67A02A - 3.8 inch QVGA (320x240) Color TFT
+ * found on the Versatile AB IB1 connector or the Versatile
+ * PB adaptor board connector.
+ */
+ {
+ .name = "Sanyo TM38QV67A02A",
+ .magic = SYS_CLCD_ID_SANYO_3_8,
+ .width_mm = 79,
+ .height_mm = 54,
+ .mode = {
+ .clock = 10000,
+ .hdisplay = 320,
+ .hsync_start = 320 + 6,
+ .hsync_end = 320 + 6 + 6,
+ .htotal = 320 + 6 + 6 + 6,
+ .vdisplay = 240,
+ .vsync_start = 240 + 5,
+ .vsync_end = 240 + 5 + 6,
+ .vtotal = 240 + 5 + 6 + 5,
+ .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC,
+ },
+ },
+ /*
+ * Sharp LQ084V1DG21 640x480 VGA Color TFT module
+ * found on the Versatile AB IB1 connector or the Versatile
+ * PB adaptor board connector.
+ */
+ {
+ .name = "Sharp LQ084V1DG21",
+ .magic = SYS_CLCD_ID_SHARP_8_4,
+ .width_mm = 171,
+ .height_mm = 130,
+ .mode = {
+ .clock = 25000,
+ .hdisplay = 640,
+ .hsync_start = 640 + 24,
+ .hsync_end = 640 + 24 + 96,
+ .htotal = 640 + 24 + 96 + 24,
+ .vdisplay = 480,
+ .vsync_start = 480 + 11,
+ .vsync_end = 480 + 11 + 2,
+ .vtotal = 480 + 11 + 2 + 32,
+ .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC,
+ },
+ },
+ /*
+ * Epson L2F50113T00 - 2.2 inch QCIF 176x220 Color TFT
+ * found on the Versatile PB adaptor board connector.
+ */
+ {
+ .name = "Epson L2F50113T00",
+ .magic = SYS_CLCD_ID_EPSON_2_2,
+ .width_mm = 34,
+ .height_mm = 45,
+ .mode = {
+ .clock = 62500,
+ .hdisplay = 176,
+ .hsync_start = 176 + 2,
+ .hsync_end = 176 + 2 + 3,
+ .htotal = 176 + 2 + 3 + 3,
+ .vdisplay = 220,
+ .vsync_start = 220 + 0,
+ .vsync_end = 220 + 0 + 2,
+ .vtotal = 220 + 0 + 2 + 1,
+ .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC,
+ },
+ .bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE,
+ },
+ /*
+ * Sanyo ALR252RGT 240x320 portrait display found on the
+ * Versatile AB IB2 daughterboard for GSM prototyping.
+ */
+ {
+ .name = "Sanyo ALR252RGT",
+ .magic = SYS_CLCD_ID_SANYO_2_5,
+ .width_mm = 37,
+ .height_mm = 50,
+ .mode = {
+ .clock = 5400,
+ .hdisplay = 240,
+ .hsync_start = 240 + 10,
+ .hsync_end = 240 + 10 + 10,
+ .htotal = 240 + 10 + 10 + 20,
+ .vdisplay = 320,
+ .vsync_start = 320 + 2,
+ .vsync_end = 320 + 2 + 2,
+ .vtotal = 320 + 2 + 2 + 2,
+ .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
+ },
+ .bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE,
+ .ib2 = true,
+ },
+};
+
+static inline struct versatile_panel *
+to_versatile_panel(struct drm_panel *panel)
+{
+ return container_of(panel, struct versatile_panel, panel);
+}
+
+static int versatile_panel_disable(struct drm_panel *panel)
+{
+ struct versatile_panel *vpanel = to_versatile_panel(panel);
+
+ /* If we're on an IB2 daughterboard, turn off display */
+ if (vpanel->ib2_map) {
+ dev_dbg(vpanel->dev, "disable IB2 display\n");
+ regmap_update_bits(vpanel->ib2_map,
+ IB2_CTRL,
+ IB2_CTRL_LCD_MASK,
+ IB2_CTRL_LCD_SD);
+ }
+
+ return 0;
+}
+
+static int versatile_panel_enable(struct drm_panel *panel)
+{
+ struct versatile_panel *vpanel = to_versatile_panel(panel);
+
+ /* If we're on an IB2 daughterboard, turn on display */
+ if (vpanel->ib2_map) {
+ dev_dbg(vpanel->dev, "enable IB2 display\n");
+ regmap_update_bits(vpanel->ib2_map,
+ IB2_CTRL,
+ IB2_CTRL_LCD_MASK,
+ IB2_CTRL_LCD_BL_ON);
+ }
+
+ return 0;
+}
+
+static int versatile_panel_get_modes(struct drm_panel *panel,
+ struct drm_connector *connector)
+{
+ struct versatile_panel *vpanel = to_versatile_panel(panel);
+ struct drm_display_mode *mode;
+
+ 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(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;
+
+ mode->width_mm = vpanel->panel_type->width_mm;
+ mode->height_mm = vpanel->panel_type->height_mm;
+ drm_mode_probed_add(connector, mode);
+
+ return 1;
+}
+
+static const struct drm_panel_funcs versatile_panel_drm_funcs = {
+ .disable = versatile_panel_disable,
+ .enable = versatile_panel_enable,
+ .get_modes = versatile_panel_get_modes,
+};
+
+static int versatile_panel_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct versatile_panel *vpanel;
+ struct device *parent;
+ struct regmap *map;
+ int ret;
+ u32 val;
+ int i;
+
+ parent = dev->parent;
+ if (!parent) {
+ dev_err(dev, "no parent for versatile panel\n");
+ return -ENODEV;
+ }
+ map = syscon_node_to_regmap(parent->of_node);
+ if (IS_ERR(map)) {
+ dev_err(dev, "no regmap for versatile panel parent\n");
+ return PTR_ERR(map);
+ }
+
+ vpanel = devm_kzalloc(dev, sizeof(*vpanel), GFP_KERNEL);
+ if (!vpanel)
+ return -ENOMEM;
+
+ ret = regmap_read(map, SYS_CLCD, &val);
+ if (ret) {
+ dev_err(dev, "cannot access syscon regs\n");
+ return ret;
+ }
+
+ val &= SYS_CLCD_CLCDID_MASK;
+
+ for (i = 0; i < ARRAY_SIZE(versatile_panels); i++) {
+ const struct versatile_panel_type *pt;
+
+ pt = &versatile_panels[i];
+ if (pt->magic == val) {
+ vpanel->panel_type = pt;
+ break;
+ }
+ }
+
+ /* No panel detected or VGA, let's leave this show */
+ if (i == ARRAY_SIZE(versatile_panels)) {
+ dev_info(dev, "no panel detected\n");
+ return -ENODEV;
+ }
+
+ dev_info(dev, "detected: %s\n", vpanel->panel_type->name);
+ vpanel->dev = dev;
+ vpanel->map = map;
+
+ /* Check if the panel is mounted on an IB2 daughterboard */
+ if (vpanel->panel_type->ib2) {
+ vpanel->ib2_map = syscon_regmap_lookup_by_compatible(
+ "arm,versatile-ib2-syscon");
+ if (IS_ERR(vpanel->ib2_map))
+ vpanel->ib2_map = NULL;
+ else
+ dev_info(dev, "panel mounted on IB2 daughterboard\n");
+ }
+
+ drm_panel_init(&vpanel->panel, dev, &versatile_panel_drm_funcs,
+ DRM_MODE_CONNECTOR_DPI);
+
+ drm_panel_add(&vpanel->panel);
+
+ return 0;
+}
+
+static const struct of_device_id versatile_panel_match[] = {
+ { .compatible = "arm,versatile-tft-panel", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, versatile_panel_match);
+
+static struct platform_driver versatile_panel_driver = {
+ .probe = versatile_panel_probe,
+ .driver = {
+ .name = "versatile-tft-panel",
+ .of_match_table = versatile_panel_match,
+ },
+};
+module_platform_driver(versatile_panel_driver);
+
+MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
+MODULE_DESCRIPTION("ARM Versatile panel driver");
+MODULE_LICENSE("GPL v2");
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 000000000..b3235781e
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-asus-z00t-tm5p5-n35596.c
@@ -0,0 +1,355 @@
+// 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;
+ bool prepared;
+};
+
+static inline struct tm5p5_nt35596 *to_tm5p5_nt35596(struct drm_panel *panel)
+{
+ return container_of(panel, struct tm5p5_nt35596, panel);
+}
+
+#define dsi_generic_write_seq(dsi, seq...) do { \
+ static const u8 d[] = { seq }; \
+ int ret; \
+ ret = mipi_dsi_generic_write(dsi, d, ARRAY_SIZE(d)); \
+ if (ret < 0) \
+ return ret; \
+ } while (0)
+
+#define dsi_dcs_write_seq(dsi, seq...) do { \
+ static const u8 d[] = { seq }; \
+ int ret; \
+ ret = mipi_dsi_dcs_write_buffer(dsi, d, ARRAY_SIZE(d)); \
+ if (ret < 0) \
+ return ret; \
+ } while (0)
+
+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 int tm5p5_nt35596_on(struct tm5p5_nt35596 *ctx)
+{
+ struct mipi_dsi_device *dsi = ctx->dsi;
+
+ dsi_generic_write_seq(dsi, 0xff, 0x05);
+ dsi_generic_write_seq(dsi, 0xfb, 0x01);
+ dsi_generic_write_seq(dsi, 0xc5, 0x31);
+ dsi_generic_write_seq(dsi, 0xff, 0x04);
+ dsi_generic_write_seq(dsi, 0x01, 0x84);
+ dsi_generic_write_seq(dsi, 0x05, 0x25);
+ dsi_generic_write_seq(dsi, 0x06, 0x01);
+ dsi_generic_write_seq(dsi, 0x07, 0x20);
+ dsi_generic_write_seq(dsi, 0x08, 0x06);
+ dsi_generic_write_seq(dsi, 0x09, 0x08);
+ dsi_generic_write_seq(dsi, 0x0a, 0x10);
+ dsi_generic_write_seq(dsi, 0x0b, 0x10);
+ dsi_generic_write_seq(dsi, 0x0c, 0x10);
+ dsi_generic_write_seq(dsi, 0x0d, 0x14);
+ dsi_generic_write_seq(dsi, 0x0e, 0x14);
+ dsi_generic_write_seq(dsi, 0x0f, 0x14);
+ dsi_generic_write_seq(dsi, 0x10, 0x14);
+ dsi_generic_write_seq(dsi, 0x11, 0x14);
+ dsi_generic_write_seq(dsi, 0x12, 0x14);
+ dsi_generic_write_seq(dsi, 0x17, 0xf3);
+ dsi_generic_write_seq(dsi, 0x18, 0xc0);
+ dsi_generic_write_seq(dsi, 0x19, 0xc0);
+ dsi_generic_write_seq(dsi, 0x1a, 0xc0);
+ dsi_generic_write_seq(dsi, 0x1b, 0xb3);
+ dsi_generic_write_seq(dsi, 0x1c, 0xb3);
+ dsi_generic_write_seq(dsi, 0x1d, 0xb3);
+ dsi_generic_write_seq(dsi, 0x1e, 0xb3);
+ dsi_generic_write_seq(dsi, 0x1f, 0xb3);
+ dsi_generic_write_seq(dsi, 0x20, 0xb3);
+ dsi_generic_write_seq(dsi, 0xfb, 0x01);
+ dsi_generic_write_seq(dsi, 0xff, 0x00);
+ dsi_generic_write_seq(dsi, 0xfb, 0x01);
+ dsi_generic_write_seq(dsi, 0x35, 0x01);
+ dsi_generic_write_seq(dsi, 0xd3, 0x06);
+ dsi_generic_write_seq(dsi, 0xd4, 0x04);
+ dsi_generic_write_seq(dsi, 0x5e, 0x0d);
+ dsi_generic_write_seq(dsi, 0x11, 0x00);
+ msleep(100);
+ dsi_generic_write_seq(dsi, 0x29, 0x00);
+ dsi_generic_write_seq(dsi, 0x53, 0x24);
+
+ return 0;
+}
+
+static int tm5p5_nt35596_off(struct tm5p5_nt35596 *ctx)
+{
+ struct mipi_dsi_device *dsi = ctx->dsi;
+ struct device *dev = &dsi->dev;
+ int ret;
+
+ ret = mipi_dsi_dcs_set_display_off(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set display off: %d\n", ret);
+ return ret;
+ }
+ msleep(60);
+
+ ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to enter sleep mode: %d\n", ret);
+ return ret;
+ }
+
+ dsi_dcs_write_seq(dsi, 0x4f, 0x01);
+
+ return 0;
+}
+
+static int tm5p5_nt35596_prepare(struct drm_panel *panel)
+{
+ struct tm5p5_nt35596 *ctx = to_tm5p5_nt35596(panel);
+ struct device *dev = &ctx->dsi->dev;
+ int ret;
+
+ if (ctx->prepared)
+ return 0;
+
+ 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;
+ }
+
+ tm5p5_nt35596_reset(ctx);
+
+ ret = tm5p5_nt35596_on(ctx);
+ if (ret < 0) {
+ dev_err(dev, "Failed to initialize panel: %d\n", ret);
+ gpiod_set_value_cansleep(ctx->reset_gpio, 0);
+ regulator_bulk_disable(ARRAY_SIZE(ctx->supplies),
+ ctx->supplies);
+ return ret;
+ }
+
+ ctx->prepared = true;
+ return 0;
+}
+
+static int tm5p5_nt35596_unprepare(struct drm_panel *panel)
+{
+ struct tm5p5_nt35596 *ctx = to_tm5p5_nt35596(panel);
+ struct device *dev = &ctx->dsi->dev;
+ int ret;
+
+ if (!ctx->prepared)
+ return 0;
+
+ ret = tm5p5_nt35596_off(ctx);
+ if (ret < 0)
+ dev_err(dev, "Failed to un-initialize panel: %d\n", ret);
+
+ gpiod_set_value_cansleep(ctx->reset_gpio, 0);
+ regulator_bulk_disable(ARRAY_SIZE(ctx->supplies),
+ ctx->supplies);
+
+ ctx->prepared = false;
+ return 0;
+}
+
+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_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ 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;
+
+ drm_panel_init(&ctx->panel, dev, &tm5p5_nt35596_panel_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ 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-boe-bf060y8m-aj0.c b/drivers/gpu/drm/panel/panel-boe-bf060y8m-aj0.c
new file mode 100644
index 000000000..ad58840ed
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-boe-bf060y8m-aj0.c
@@ -0,0 +1,443 @@
+// 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;
+ bool prepared;
+};
+
+static inline
+struct boe_bf060y8m_aj0 *to_boe_bf060y8m_aj0(struct drm_panel *panel)
+{
+ return container_of(panel, struct boe_bf060y8m_aj0, panel);
+}
+
+#define dsi_dcs_write_seq(dsi, seq...) do { \
+ static const u8 d[] = { seq }; \
+ int ret; \
+ ret = mipi_dsi_dcs_write_buffer(dsi, d, ARRAY_SIZE(d)); \
+ if (ret < 0) \
+ return ret; \
+ } while (0)
+
+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 device *dev = &dsi->dev;
+ int ret;
+
+ dsi_dcs_write_seq(dsi, 0xb0, 0xa5, 0x00);
+ dsi_dcs_write_seq(dsi, 0xb2, 0x00, 0x4c);
+ dsi_dcs_write_seq(dsi, MIPI_DCS_SET_3D_CONTROL, 0x10);
+ dsi_dcs_write_seq(dsi, MIPI_DCS_WRITE_POWER_SAVE, DCS_ALLOW_HBM_RANGE);
+ dsi_dcs_write_seq(dsi, 0xf8,
+ 0x00, 0x08, 0x10, 0x00, 0x22, 0x00, 0x00, 0x2d);
+
+ ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to exit sleep mode: %d\n", ret);
+ return ret;
+ }
+ msleep(30);
+
+ dsi_dcs_write_seq(dsi, 0xb0, 0xa5, 0x00);
+ dsi_dcs_write_seq(dsi, 0xc0,
+ 0x08, 0x48, 0x65, 0x33, 0x33, 0x33,
+ 0x2a, 0x31, 0x39, 0x20, 0x09);
+ dsi_dcs_write_seq(dsi, 0xc1, 0x00, 0x00, 0x00, 0x1f, 0x1f,
+ 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f,
+ 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f);
+ dsi_dcs_write_seq(dsi, 0xe2, 0x20, 0x04, 0x10, 0x12, 0x92,
+ 0x4f, 0x8f, 0x44, 0x84, 0x83, 0x83, 0x83,
+ 0x5c, 0x5c, 0x5c);
+ dsi_dcs_write_seq(dsi, 0xde, 0x01, 0x2c, 0x00, 0x77, 0x3e);
+
+ msleep(30);
+
+ ret = mipi_dsi_dcs_set_display_on(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set display on: %d\n", ret);
+ return ret;
+ }
+ msleep(50);
+
+ return 0;
+}
+
+static int boe_bf060y8m_aj0_off(struct boe_bf060y8m_aj0 *boe)
+{
+ struct mipi_dsi_device *dsi = boe->dsi;
+ struct device *dev = &dsi->dev;
+ int ret;
+
+ /* OFF commands sent in HS mode */
+ dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
+ ret = mipi_dsi_dcs_set_display_off(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set display off: %d\n", ret);
+ return ret;
+ }
+ msleep(20);
+
+ ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to enter sleep mode: %d\n", ret);
+ return ret;
+ }
+ usleep_range(1000, 2000);
+ dsi->mode_flags |= MIPI_DSI_MODE_LPM;
+
+ return 0;
+}
+
+static int boe_bf060y8m_aj0_prepare(struct drm_panel *panel)
+{
+ struct boe_bf060y8m_aj0 *boe = to_boe_bf060y8m_aj0(panel);
+ struct device *dev = &boe->dsi->dev;
+ int ret;
+
+ if (boe->prepared)
+ return 0;
+
+ /*
+ * 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) {
+ dev_err(dev, "Failed to initialize panel: %d\n", ret);
+ gpiod_set_value_cansleep(boe->reset_gpio, 1);
+ return ret;
+ }
+
+ boe->prepared = true;
+ return 0;
+
+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);
+ struct device *dev = &boe->dsi->dev;
+ int ret;
+
+ if (!boe->prepared)
+ return 0;
+
+ ret = boe_bf060y8m_aj0_off(boe);
+ if (ret < 0)
+ dev_err(dev, "Failed to un-initialize panel: %d\n", ret);
+
+ gpiod_set_value_cansleep(boe->reset_gpio, 1);
+ ret = regulator_bulk_disable(ARRAY_SIZE(boe->vregs), boe->vregs);
+
+ boe->prepared = false;
+ 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);
+ int ret;
+
+ ret = mipi_dsi_dcs_set_display_brightness(dsi, brightness);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+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_kzalloc(dev, sizeof(*boe), GFP_KERNEL);
+ if (!boe)
+ return -ENOMEM;
+
+ 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;
+
+ drm_panel_init(&boe->panel, dev, &boe_bf060y8m_aj0_panel_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ 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 000000000..d879b3b14
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-boe-himax8279d.c
@@ -0,0 +1,963 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2019, Huaqin Telecom Technology Co., Ltd
+ *
+ * Author: Jerry Han <jerry.han.hq@gmail.com>
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+
+#include <linux/gpio/consumer.h>
+#include <linux/regulator/consumer.h>
+
+#include <drm/drm_device.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+
+#include <video/mipi_display.h>
+
+struct panel_cmd {
+ char cmd;
+ char data;
+};
+
+struct panel_desc {
+ const struct drm_display_mode *display_mode;
+ unsigned int bpc;
+ unsigned int width_mm;
+ unsigned int height_mm;
+
+ unsigned long mode_flags;
+ enum mipi_dsi_pixel_format format;
+ unsigned int lanes;
+ const struct panel_cmd *on_cmds;
+ unsigned int on_cmds_num;
+};
+
+struct panel_info {
+ struct drm_panel base;
+ struct mipi_dsi_device *link;
+ const struct panel_desc *desc;
+
+ struct gpio_desc *enable_gpio;
+ struct gpio_desc *pp33_gpio;
+ struct gpio_desc *pp18_gpio;
+
+ bool prepared;
+ bool enabled;
+};
+
+static inline struct panel_info *to_panel_info(struct drm_panel *panel)
+{
+ return container_of(panel, struct panel_info, base);
+}
+
+static void disable_gpios(struct panel_info *pinfo)
+{
+ gpiod_set_value(pinfo->enable_gpio, 0);
+ gpiod_set_value(pinfo->pp33_gpio, 0);
+ gpiod_set_value(pinfo->pp18_gpio, 0);
+}
+
+static int send_mipi_cmds(struct drm_panel *panel, const struct panel_cmd *cmds)
+{
+ struct panel_info *pinfo = to_panel_info(panel);
+ unsigned int i = 0;
+ int err;
+
+ for (i = 0; i < pinfo->desc->on_cmds_num; i++) {
+ err = mipi_dsi_dcs_write_buffer(pinfo->link, &cmds[i],
+ sizeof(struct panel_cmd));
+
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+static int boe_panel_disable(struct drm_panel *panel)
+{
+ struct panel_info *pinfo = to_panel_info(panel);
+ int err;
+
+ if (!pinfo->enabled)
+ return 0;
+
+ err = mipi_dsi_dcs_set_display_off(pinfo->link);
+ if (err < 0) {
+ dev_err(panel->dev, "failed to set display off: %d\n", err);
+ return err;
+ }
+
+ pinfo->enabled = false;
+
+ return 0;
+}
+
+static int boe_panel_unprepare(struct drm_panel *panel)
+{
+ struct panel_info *pinfo = to_panel_info(panel);
+ int err;
+
+ if (!pinfo->prepared)
+ return 0;
+
+ err = mipi_dsi_dcs_set_display_off(pinfo->link);
+ if (err < 0)
+ 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);
+
+ pinfo->prepared = false;
+
+ return 0;
+}
+
+static int boe_panel_prepare(struct drm_panel *panel)
+{
+ struct panel_info *pinfo = to_panel_info(panel);
+ int err;
+
+ if (pinfo->prepared)
+ return 0;
+
+ gpiod_set_value(pinfo->pp18_gpio, 1);
+ /* T1: 5ms - 6ms */
+ usleep_range(5000, 6000);
+ gpiod_set_value(pinfo->pp33_gpio, 1);
+
+ /* reset sequence */
+ /* T2: 14ms - 15ms */
+ usleep_range(14000, 15000);
+ gpiod_set_value(pinfo->enable_gpio, 1);
+
+ /* T3: 1ms - 2ms */
+ usleep_range(1000, 2000);
+ gpiod_set_value(pinfo->enable_gpio, 0);
+
+ /* T4: 1ms - 2ms */
+ usleep_range(1000, 2000);
+ gpiod_set_value(pinfo->enable_gpio, 1);
+
+ /* T5: 5ms - 6ms */
+ usleep_range(5000, 6000);
+
+ /* send init code */
+ err = send_mipi_cmds(panel, pinfo->desc->on_cmds);
+ if (err < 0) {
+ 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);
+
+ pinfo->prepared = true;
+
+ return 0;
+
+poweroff:
+ disable_gpios(pinfo);
+ return err;
+}
+
+static int boe_panel_enable(struct drm_panel *panel)
+{
+ struct panel_info *pinfo = to_panel_info(panel);
+ int ret;
+
+ if (pinfo->enabled)
+ return 0;
+
+ usleep_range(120000, 121000);
+
+ ret = mipi_dsi_dcs_set_display_on(pinfo->link);
+ if (ret < 0) {
+ dev_err(panel->dev, "failed to set display on: %d\n", ret);
+ return ret;
+ }
+
+ pinfo->enabled = true;
+
+ return 0;
+}
+
+static int boe_panel_get_modes(struct drm_panel *panel,
+ struct drm_connector *connector)
+{
+ struct panel_info *pinfo = to_panel_info(panel);
+ const struct drm_display_mode *m = pinfo->desc->display_mode;
+ struct drm_display_mode *mode;
+
+ mode = drm_mode_duplicate(connector->dev, m);
+ if (!mode) {
+ 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)) {
+ ret = PTR_ERR(pinfo->pp18_gpio);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to get pp18 gpio: %d\n", ret);
+ return ret;
+ }
+
+ pinfo->pp33_gpio = devm_gpiod_get(dev, "pp33", GPIOD_OUT_HIGH);
+ if (IS_ERR(pinfo->pp33_gpio)) {
+ ret = PTR_ERR(pinfo->pp33_gpio);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to get pp33 gpio: %d\n", ret);
+ return ret;
+ }
+
+ pinfo->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_HIGH);
+ if (IS_ERR(pinfo->enable_gpio)) {
+ ret = PTR_ERR(pinfo->enable_gpio);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to get enable gpio: %d\n", ret);
+ return ret;
+ }
+
+ drm_panel_init(&pinfo->base, dev, &panel_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ ret = drm_panel_of_backlight(&pinfo->base);
+ if (ret)
+ return ret;
+
+ 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_kzalloc(&dsi->dev, sizeof(*pinfo), GFP_KERNEL);
+ if (!pinfo)
+ return -ENOMEM;
+
+ desc = of_device_get_match_data(&dsi->dev);
+ dsi->mode_flags = desc->mode_flags;
+ dsi->format = desc->format;
+ dsi->lanes = desc->lanes;
+ pinfo->desc = desc;
+
+ pinfo->link = dsi;
+ mipi_dsi_set_drvdata(dsi, pinfo);
+
+ err = panel_add(pinfo);
+ if (err < 0)
+ return err;
+
+ err = mipi_dsi_attach(dsi);
+ if (err < 0)
+ drm_panel_remove(&pinfo->base);
+
+ return err;
+}
+
+static void panel_remove(struct mipi_dsi_device *dsi)
+{
+ struct panel_info *pinfo = mipi_dsi_get_drvdata(dsi);
+ int err;
+
+ err = boe_panel_disable(&pinfo->base);
+ if (err < 0)
+ dev_err(&dsi->dev, "failed to disable panel: %d\n", err);
+
+ err = boe_panel_unprepare(&pinfo->base);
+ if (err < 0)
+ dev_err(&dsi->dev, "failed to unprepare 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);
+
+ drm_panel_remove(&pinfo->base);
+}
+
+static void panel_shutdown(struct mipi_dsi_device *dsi)
+{
+ struct panel_info *pinfo = mipi_dsi_get_drvdata(dsi);
+
+ boe_panel_disable(&pinfo->base);
+ boe_panel_unprepare(&pinfo->base);
+}
+
+static struct mipi_dsi_driver panel_driver = {
+ .driver = {
+ .name = "panel-boe-himax8279d",
+ .of_match_table = panel_of_match,
+ },
+ .probe = panel_probe,
+ .remove = panel_remove,
+ .shutdown = panel_shutdown,
+};
+module_mipi_dsi_driver(panel_driver);
+
+MODULE_AUTHOR("Jerry Han <jerry.han.hq@gmail.com>");
+MODULE_DESCRIPTION("Boe Himax8279d driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/panel/panel-boe-tv101wum-nl6.c b/drivers/gpu/drm/panel/panel-boe-tv101wum-nl6.c
new file mode 100644
index 000000000..1c008bd91
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-boe-tv101wum-nl6.c
@@ -0,0 +1,1694 @@
+// 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/of_device.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 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;
+ const struct panel_init_cmd *init_cmds;
+ 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;
+
+ bool prepared;
+};
+
+enum dsi_cmd_type {
+ INIT_DCS_CMD,
+ DELAY_CMD,
+};
+
+struct panel_init_cmd {
+ enum dsi_cmd_type type;
+ size_t len;
+ const char *data;
+};
+
+#define _INIT_DCS_CMD(...) { \
+ .type = INIT_DCS_CMD, \
+ .len = sizeof((char[]){__VA_ARGS__}), \
+ .data = (char[]){__VA_ARGS__} }
+
+#define _INIT_DELAY_CMD(...) { \
+ .type = DELAY_CMD,\
+ .len = sizeof((char[]){__VA_ARGS__}), \
+ .data = (char[]){__VA_ARGS__} }
+
+static const struct panel_init_cmd boe_tv110c9m_init_cmd[] = {
+ _INIT_DCS_CMD(0xFF, 0x20),
+ _INIT_DCS_CMD(0xFB, 0x01),
+ _INIT_DCS_CMD(0x05, 0xD9),
+ _INIT_DCS_CMD(0x07, 0x78),
+ _INIT_DCS_CMD(0x08, 0x5A),
+ _INIT_DCS_CMD(0x0D, 0x63),
+ _INIT_DCS_CMD(0x0E, 0x91),
+ _INIT_DCS_CMD(0x0F, 0x73),
+ _INIT_DCS_CMD(0x95, 0xE6),
+ _INIT_DCS_CMD(0x96, 0xF0),
+ _INIT_DCS_CMD(0x30, 0x00),
+ _INIT_DCS_CMD(0x6D, 0x66),
+ _INIT_DCS_CMD(0x75, 0xA2),
+ _INIT_DCS_CMD(0x77, 0x3B),
+
+ _INIT_DCS_CMD(0xB0, 0x00, 0x08, 0x00, 0x23, 0x00, 0x4D, 0x00, 0x6D, 0x00, 0x89, 0x00, 0xA1, 0x00, 0xB6, 0x00, 0xC9),
+ _INIT_DCS_CMD(0xB1, 0x00, 0xDA, 0x01, 0x13, 0x01, 0x3C, 0x01, 0x7E, 0x01, 0xAB, 0x01, 0xF7, 0x02, 0x2F, 0x02, 0x31),
+ _INIT_DCS_CMD(0xB2, 0x02, 0x67, 0x02, 0xA6, 0x02, 0xD1, 0x03, 0x08, 0x03, 0x2E, 0x03, 0x5B, 0x03, 0x6B, 0x03, 0x7B),
+ _INIT_DCS_CMD(0xB3, 0x03, 0x8E, 0x03, 0xA2, 0x03, 0xB7, 0x03, 0xE7, 0x03, 0xFD, 0x03, 0xFF),
+
+ _INIT_DCS_CMD(0xB4, 0x00, 0x08, 0x00, 0x23, 0x00, 0x4D, 0x00, 0x6D, 0x00, 0x89, 0x00, 0xA1, 0x00, 0xB6, 0x00, 0xC9),
+ _INIT_DCS_CMD(0xB5, 0x00, 0xDA, 0x01, 0x13, 0x01, 0x3C, 0x01, 0x7E, 0x01, 0xAB, 0x01, 0xF7, 0x02, 0x2F, 0x02, 0x31),
+ _INIT_DCS_CMD(0xB6, 0x02, 0x67, 0x02, 0xA6, 0x02, 0xD1, 0x03, 0x08, 0x03, 0x2E, 0x03, 0x5B, 0x03, 0x6B, 0x03, 0x7B),
+ _INIT_DCS_CMD(0xB7, 0x03, 0x8E, 0x03, 0xA2, 0x03, 0xB7, 0x03, 0xE7, 0x03, 0xFD, 0x03, 0xFF),
+ _INIT_DCS_CMD(0xB8, 0x00, 0x08, 0x00, 0x23, 0x00, 0x4D, 0x00, 0x6D, 0x00, 0x89, 0x00, 0xA1, 0x00, 0xB6, 0x00, 0xC9),
+ _INIT_DCS_CMD(0xB9, 0x00, 0xDA, 0x01, 0x13, 0x01, 0x3C, 0x01, 0x7E, 0x01, 0xAB, 0x01, 0xF7, 0x02, 0x2F, 0x02, 0x31),
+ _INIT_DCS_CMD(0xBA, 0x02, 0x67, 0x02, 0xA6, 0x02, 0xD1, 0x03, 0x08, 0x03, 0x2E, 0x03, 0x5B, 0x03, 0x6B, 0x03, 0x7B),
+ _INIT_DCS_CMD(0xBB, 0x03, 0x8E, 0x03, 0xA2, 0x03, 0xB7, 0x03, 0xE7, 0x03, 0xFD, 0x03, 0xFF),
+
+ _INIT_DCS_CMD(0xFF, 0x21),
+ _INIT_DCS_CMD(0xFB, 0x01),
+
+ _INIT_DCS_CMD(0xB0, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x45, 0x00, 0x65, 0x00, 0x81, 0x00, 0x99, 0x00, 0xAE, 0x00, 0xC1),
+ _INIT_DCS_CMD(0xB1, 0x00, 0xD2, 0x01, 0x0B, 0x01, 0x34, 0x01, 0x76, 0x01, 0xA3, 0x01, 0xEF, 0x02, 0x27, 0x02, 0x29),
+ _INIT_DCS_CMD(0xB2, 0x02, 0x5F, 0x02, 0x9E, 0x02, 0xC9, 0x03, 0x00, 0x03, 0x26, 0x03, 0x53, 0x03, 0x63, 0x03, 0x73),
+
+ _INIT_DCS_CMD(0xB3, 0x03, 0x86, 0x03, 0x9A, 0x03, 0xAF, 0x03, 0xDF, 0x03, 0xF5, 0x03, 0xE0),
+ _INIT_DCS_CMD(0xB4, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x45, 0x00, 0x65, 0x00, 0x81, 0x00, 0x99, 0x00, 0xAE, 0x00, 0xC1),
+ _INIT_DCS_CMD(0xB5, 0x00, 0xD2, 0x01, 0x0B, 0x01, 0x34, 0x01, 0x76, 0x01, 0xA3, 0x01, 0xEF, 0x02, 0x27, 0x02, 0x29),
+ _INIT_DCS_CMD(0xB6, 0x02, 0x5F, 0x02, 0x9E, 0x02, 0xC9, 0x03, 0x00, 0x03, 0x26, 0x03, 0x53, 0x03, 0x63, 0x03, 0x73),
+ _INIT_DCS_CMD(0xB7, 0x03, 0x86, 0x03, 0x9A, 0x03, 0xAF, 0x03, 0xDF, 0x03, 0xF5, 0x03, 0xE0),
+
+ _INIT_DCS_CMD(0xB8, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x45, 0x00, 0x65, 0x00, 0x81, 0x00, 0x99, 0x00, 0xAE, 0x00, 0xC1),
+ _INIT_DCS_CMD(0xB9, 0x00, 0xD2, 0x01, 0x0B, 0x01, 0x34, 0x01, 0x76, 0x01, 0xA3, 0x01, 0xEF, 0x02, 0x27, 0x02, 0x29),
+ _INIT_DCS_CMD(0xBA, 0x02, 0x5F, 0x02, 0x9E, 0x02, 0xC9, 0x03, 0x00, 0x03, 0x26, 0x03, 0x53, 0x03, 0x63, 0x03, 0x73),
+
+ _INIT_DCS_CMD(0xBB, 0x03, 0x86, 0x03, 0x9A, 0x03, 0xAF, 0x03, 0xDF, 0x03, 0xF5, 0x03, 0xE0),
+ _INIT_DCS_CMD(0xFF, 0x24),
+ _INIT_DCS_CMD(0xFB, 0x01),
+
+ _INIT_DCS_CMD(0x00, 0x00),
+ _INIT_DCS_CMD(0x01, 0x00),
+
+ _INIT_DCS_CMD(0x02, 0x1C),
+ _INIT_DCS_CMD(0x03, 0x1C),
+
+ _INIT_DCS_CMD(0x04, 0x1D),
+ _INIT_DCS_CMD(0x05, 0x1D),
+
+ _INIT_DCS_CMD(0x06, 0x04),
+ _INIT_DCS_CMD(0x07, 0x04),
+
+ _INIT_DCS_CMD(0x08, 0x0F),
+ _INIT_DCS_CMD(0x09, 0x0F),
+
+ _INIT_DCS_CMD(0x0A, 0x0E),
+ _INIT_DCS_CMD(0x0B, 0x0E),
+
+ _INIT_DCS_CMD(0x0C, 0x0D),
+ _INIT_DCS_CMD(0x0D, 0x0D),
+
+ _INIT_DCS_CMD(0x0E, 0x0C),
+ _INIT_DCS_CMD(0x0F, 0x0C),
+
+ _INIT_DCS_CMD(0x10, 0x08),
+ _INIT_DCS_CMD(0x11, 0x08),
+
+ _INIT_DCS_CMD(0x12, 0x00),
+ _INIT_DCS_CMD(0x13, 0x00),
+ _INIT_DCS_CMD(0x14, 0x00),
+ _INIT_DCS_CMD(0x15, 0x00),
+
+ _INIT_DCS_CMD(0x16, 0x00),
+ _INIT_DCS_CMD(0x17, 0x00),
+
+ _INIT_DCS_CMD(0x18, 0x1C),
+ _INIT_DCS_CMD(0x19, 0x1C),
+
+ _INIT_DCS_CMD(0x1A, 0x1D),
+ _INIT_DCS_CMD(0x1B, 0x1D),
+
+ _INIT_DCS_CMD(0x1C, 0x04),
+ _INIT_DCS_CMD(0x1D, 0x04),
+
+ _INIT_DCS_CMD(0x1E, 0x0F),
+ _INIT_DCS_CMD(0x1F, 0x0F),
+
+ _INIT_DCS_CMD(0x20, 0x0E),
+ _INIT_DCS_CMD(0x21, 0x0E),
+
+ _INIT_DCS_CMD(0x22, 0x0D),
+ _INIT_DCS_CMD(0x23, 0x0D),
+
+ _INIT_DCS_CMD(0x24, 0x0C),
+ _INIT_DCS_CMD(0x25, 0x0C),
+
+ _INIT_DCS_CMD(0x26, 0x08),
+ _INIT_DCS_CMD(0x27, 0x08),
+
+ _INIT_DCS_CMD(0x28, 0x00),
+ _INIT_DCS_CMD(0x29, 0x00),
+ _INIT_DCS_CMD(0x2A, 0x00),
+ _INIT_DCS_CMD(0x2B, 0x00),
+
+ _INIT_DCS_CMD(0x2D, 0x20),
+ _INIT_DCS_CMD(0x2F, 0x0A),
+ _INIT_DCS_CMD(0x30, 0x44),
+ _INIT_DCS_CMD(0x33, 0x0C),
+ _INIT_DCS_CMD(0x34, 0x32),
+
+ _INIT_DCS_CMD(0x37, 0x44),
+ _INIT_DCS_CMD(0x38, 0x40),
+ _INIT_DCS_CMD(0x39, 0x00),
+ _INIT_DCS_CMD(0x3A, 0x5D),
+ _INIT_DCS_CMD(0x3B, 0x60),
+ _INIT_DCS_CMD(0x3D, 0x42),
+ _INIT_DCS_CMD(0x3F, 0x06),
+ _INIT_DCS_CMD(0x43, 0x06),
+ _INIT_DCS_CMD(0x47, 0x66),
+ _INIT_DCS_CMD(0x4A, 0x5D),
+ _INIT_DCS_CMD(0x4B, 0x60),
+ _INIT_DCS_CMD(0x4C, 0x91),
+ _INIT_DCS_CMD(0x4D, 0x21),
+ _INIT_DCS_CMD(0x4E, 0x43),
+ _INIT_DCS_CMD(0x51, 0x12),
+ _INIT_DCS_CMD(0x52, 0x34),
+ _INIT_DCS_CMD(0x55, 0x82, 0x02),
+ _INIT_DCS_CMD(0x56, 0x04),
+ _INIT_DCS_CMD(0x58, 0x21),
+ _INIT_DCS_CMD(0x59, 0x30),
+ _INIT_DCS_CMD(0x5A, 0x60),
+ _INIT_DCS_CMD(0x5B, 0x50),
+ _INIT_DCS_CMD(0x5E, 0x00, 0x06),
+ _INIT_DCS_CMD(0x5F, 0x00),
+ _INIT_DCS_CMD(0x65, 0x82),
+ _INIT_DCS_CMD(0x7E, 0x20),
+ _INIT_DCS_CMD(0x7F, 0x3C),
+ _INIT_DCS_CMD(0x82, 0x04),
+ _INIT_DCS_CMD(0x97, 0xC0),
+
+ _INIT_DCS_CMD(0xB6, 0x05, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x05, 0x00, 0x00),
+ _INIT_DCS_CMD(0x91, 0x44),
+ _INIT_DCS_CMD(0x92, 0xA9),
+ _INIT_DCS_CMD(0x93, 0x1A),
+ _INIT_DCS_CMD(0x94, 0x96),
+ _INIT_DCS_CMD(0xD7, 0x55),
+ _INIT_DCS_CMD(0xDA, 0x0A),
+ _INIT_DCS_CMD(0xDE, 0x08),
+ _INIT_DCS_CMD(0xDB, 0x05),
+ _INIT_DCS_CMD(0xDC, 0xA9),
+ _INIT_DCS_CMD(0xDD, 0x22),
+
+ _INIT_DCS_CMD(0xDF, 0x05),
+ _INIT_DCS_CMD(0xE0, 0xA9),
+ _INIT_DCS_CMD(0xE1, 0x05),
+ _INIT_DCS_CMD(0xE2, 0xA9),
+ _INIT_DCS_CMD(0xE3, 0x05),
+ _INIT_DCS_CMD(0xE4, 0xA9),
+ _INIT_DCS_CMD(0xE5, 0x05),
+ _INIT_DCS_CMD(0xE6, 0xA9),
+ _INIT_DCS_CMD(0x5C, 0x00),
+ _INIT_DCS_CMD(0x5D, 0x00),
+ _INIT_DCS_CMD(0x8D, 0x00),
+ _INIT_DCS_CMD(0x8E, 0x00),
+ _INIT_DCS_CMD(0xB5, 0x90),
+ _INIT_DCS_CMD(0xFF, 0x25),
+ _INIT_DCS_CMD(0xFB, 0x01),
+ _INIT_DCS_CMD(0x05, 0x00),
+ _INIT_DCS_CMD(0x19, 0x07),
+ _INIT_DCS_CMD(0x1F, 0x60),
+ _INIT_DCS_CMD(0x20, 0x50),
+ _INIT_DCS_CMD(0x26, 0x60),
+ _INIT_DCS_CMD(0x27, 0x50),
+ _INIT_DCS_CMD(0x33, 0x60),
+ _INIT_DCS_CMD(0x34, 0x50),
+ _INIT_DCS_CMD(0x3F, 0xE0),
+ _INIT_DCS_CMD(0x40, 0x00),
+ _INIT_DCS_CMD(0x44, 0x00),
+ _INIT_DCS_CMD(0x45, 0x40),
+ _INIT_DCS_CMD(0x48, 0x60),
+ _INIT_DCS_CMD(0x49, 0x50),
+ _INIT_DCS_CMD(0x5B, 0x00),
+ _INIT_DCS_CMD(0x5C, 0x00),
+ _INIT_DCS_CMD(0x5D, 0x00),
+ _INIT_DCS_CMD(0x5E, 0xD0),
+ _INIT_DCS_CMD(0x61, 0x60),
+ _INIT_DCS_CMD(0x62, 0x50),
+ _INIT_DCS_CMD(0xF1, 0x10),
+ _INIT_DCS_CMD(0xFF, 0x2A),
+ _INIT_DCS_CMD(0xFB, 0x01),
+
+ _INIT_DCS_CMD(0x64, 0x16),
+ _INIT_DCS_CMD(0x67, 0x16),
+ _INIT_DCS_CMD(0x6A, 0x16),
+
+ _INIT_DCS_CMD(0x70, 0x30),
+
+ _INIT_DCS_CMD(0xA2, 0xF3),
+ _INIT_DCS_CMD(0xA3, 0xFF),
+ _INIT_DCS_CMD(0xA4, 0xFF),
+ _INIT_DCS_CMD(0xA5, 0xFF),
+
+ _INIT_DCS_CMD(0xD6, 0x08),
+
+ _INIT_DCS_CMD(0xFF, 0x26),
+ _INIT_DCS_CMD(0xFB, 0x01),
+ _INIT_DCS_CMD(0x00, 0xA1),
+
+ _INIT_DCS_CMD(0x02, 0x31),
+ _INIT_DCS_CMD(0x04, 0x28),
+ _INIT_DCS_CMD(0x06, 0x30),
+ _INIT_DCS_CMD(0x0C, 0x16),
+ _INIT_DCS_CMD(0x0D, 0x0D),
+ _INIT_DCS_CMD(0x0F, 0x00),
+ _INIT_DCS_CMD(0x11, 0x00),
+ _INIT_DCS_CMD(0x12, 0x50),
+ _INIT_DCS_CMD(0x13, 0x56),
+ _INIT_DCS_CMD(0x14, 0x57),
+ _INIT_DCS_CMD(0x15, 0x00),
+ _INIT_DCS_CMD(0x16, 0x10),
+ _INIT_DCS_CMD(0x17, 0xA0),
+ _INIT_DCS_CMD(0x18, 0x86),
+ _INIT_DCS_CMD(0x19, 0x0D),
+ _INIT_DCS_CMD(0x1A, 0x7F),
+ _INIT_DCS_CMD(0x1B, 0x0C),
+ _INIT_DCS_CMD(0x1C, 0xBF),
+ _INIT_DCS_CMD(0x22, 0x00),
+ _INIT_DCS_CMD(0x23, 0x00),
+ _INIT_DCS_CMD(0x2A, 0x0D),
+ _INIT_DCS_CMD(0x2B, 0x7F),
+
+ _INIT_DCS_CMD(0x1D, 0x00),
+ _INIT_DCS_CMD(0x1E, 0x65),
+ _INIT_DCS_CMD(0x1F, 0x65),
+ _INIT_DCS_CMD(0x24, 0x00),
+ _INIT_DCS_CMD(0x25, 0x65),
+ _INIT_DCS_CMD(0x2F, 0x05),
+ _INIT_DCS_CMD(0x30, 0x65),
+ _INIT_DCS_CMD(0x31, 0x05),
+ _INIT_DCS_CMD(0x32, 0x7D),
+ _INIT_DCS_CMD(0x39, 0x00),
+ _INIT_DCS_CMD(0x3A, 0x65),
+ _INIT_DCS_CMD(0x20, 0x01),
+ _INIT_DCS_CMD(0x33, 0x11),
+ _INIT_DCS_CMD(0x34, 0x78),
+ _INIT_DCS_CMD(0x35, 0x16),
+ _INIT_DCS_CMD(0xC8, 0x04),
+ _INIT_DCS_CMD(0xC9, 0x9E),
+ _INIT_DCS_CMD(0xCA, 0x4E),
+ _INIT_DCS_CMD(0xCB, 0x00),
+
+ _INIT_DCS_CMD(0xA9, 0x49),
+ _INIT_DCS_CMD(0xAA, 0x4B),
+ _INIT_DCS_CMD(0xAB, 0x48),
+ _INIT_DCS_CMD(0xAC, 0x43),
+ _INIT_DCS_CMD(0xAD, 0x40),
+ _INIT_DCS_CMD(0xAE, 0x50),
+ _INIT_DCS_CMD(0xAF, 0x44),
+ _INIT_DCS_CMD(0xB0, 0x54),
+ _INIT_DCS_CMD(0xB1, 0x4E),
+ _INIT_DCS_CMD(0xB2, 0x4D),
+ _INIT_DCS_CMD(0xB3, 0x4C),
+ _INIT_DCS_CMD(0xB4, 0x41),
+ _INIT_DCS_CMD(0xB5, 0x47),
+ _INIT_DCS_CMD(0xB6, 0x53),
+ _INIT_DCS_CMD(0xB7, 0x3E),
+ _INIT_DCS_CMD(0xB8, 0x51),
+ _INIT_DCS_CMD(0xB9, 0x3C),
+ _INIT_DCS_CMD(0xBA, 0x3B),
+ _INIT_DCS_CMD(0xBB, 0x46),
+ _INIT_DCS_CMD(0xBC, 0x45),
+ _INIT_DCS_CMD(0xBD, 0x55),
+ _INIT_DCS_CMD(0xBE, 0x3D),
+ _INIT_DCS_CMD(0xBF, 0x3F),
+ _INIT_DCS_CMD(0xC0, 0x52),
+ _INIT_DCS_CMD(0xC1, 0x4A),
+ _INIT_DCS_CMD(0xC2, 0x39),
+ _INIT_DCS_CMD(0xC3, 0x4F),
+ _INIT_DCS_CMD(0xC4, 0x3A),
+ _INIT_DCS_CMD(0xC5, 0x42),
+ _INIT_DCS_CMD(0xFF, 0x27),
+ _INIT_DCS_CMD(0xFB, 0x01),
+
+ _INIT_DCS_CMD(0x56, 0x06),
+ _INIT_DCS_CMD(0x58, 0x80),
+ _INIT_DCS_CMD(0x59, 0x75),
+ _INIT_DCS_CMD(0x5A, 0x00),
+ _INIT_DCS_CMD(0x5B, 0x02),
+ _INIT_DCS_CMD(0x5C, 0x00),
+ _INIT_DCS_CMD(0x5D, 0x00),
+ _INIT_DCS_CMD(0x5E, 0x20),
+ _INIT_DCS_CMD(0x5F, 0x10),
+ _INIT_DCS_CMD(0x60, 0x00),
+ _INIT_DCS_CMD(0x61, 0x2E),
+ _INIT_DCS_CMD(0x62, 0x00),
+ _INIT_DCS_CMD(0x63, 0x01),
+ _INIT_DCS_CMD(0x64, 0x43),
+ _INIT_DCS_CMD(0x65, 0x2D),
+ _INIT_DCS_CMD(0x66, 0x00),
+ _INIT_DCS_CMD(0x67, 0x01),
+ _INIT_DCS_CMD(0x68, 0x44),
+
+ _INIT_DCS_CMD(0x00, 0x00),
+ _INIT_DCS_CMD(0x78, 0x00),
+ _INIT_DCS_CMD(0xC3, 0x00),
+
+ _INIT_DCS_CMD(0xFF, 0x2A),
+ _INIT_DCS_CMD(0xFB, 0x01),
+
+ _INIT_DCS_CMD(0x22, 0x2F),
+ _INIT_DCS_CMD(0x23, 0x08),
+
+ _INIT_DCS_CMD(0x24, 0x00),
+ _INIT_DCS_CMD(0x25, 0x65),
+ _INIT_DCS_CMD(0x26, 0xF8),
+ _INIT_DCS_CMD(0x27, 0x00),
+ _INIT_DCS_CMD(0x28, 0x1A),
+ _INIT_DCS_CMD(0x29, 0x00),
+ _INIT_DCS_CMD(0x2A, 0x1A),
+ _INIT_DCS_CMD(0x2B, 0x00),
+ _INIT_DCS_CMD(0x2D, 0x1A),
+
+ _INIT_DCS_CMD(0xFF, 0x23),
+ _INIT_DCS_CMD(0xFB, 0x01),
+
+ _INIT_DCS_CMD(0x00, 0x80),
+ _INIT_DCS_CMD(0x07, 0x00),
+
+ _INIT_DCS_CMD(0xFF, 0xE0),
+ _INIT_DCS_CMD(0xFB, 0x01),
+ _INIT_DCS_CMD(0x14, 0x60),
+ _INIT_DCS_CMD(0x16, 0xC0),
+
+ _INIT_DCS_CMD(0xFF, 0xF0),
+ _INIT_DCS_CMD(0xFB, 0x01),
+ _INIT_DCS_CMD(0x3A, 0x08),
+
+ _INIT_DCS_CMD(0xFF, 0x10),
+ _INIT_DCS_CMD(0xFB, 0x01),
+ _INIT_DCS_CMD(0xB9, 0x01),
+ _INIT_DCS_CMD(0xFF, 0x20),
+ _INIT_DCS_CMD(0xFB, 0x01),
+ _INIT_DCS_CMD(0x18, 0x40),
+
+ _INIT_DCS_CMD(0xFF, 0x10),
+ _INIT_DCS_CMD(0xFB, 0x01),
+ _INIT_DCS_CMD(0xB9, 0x02),
+ _INIT_DCS_CMD(0x35, 0x00),
+ _INIT_DCS_CMD(0x51, 0x00, 0xFF),
+ _INIT_DCS_CMD(0x53, 0x24),
+ _INIT_DCS_CMD(0x55, 0x00),
+ _INIT_DCS_CMD(0xBB, 0x13),
+ _INIT_DCS_CMD(0x3B, 0x03, 0x96, 0x1A, 0x04, 0x04),
+ _INIT_DELAY_CMD(100),
+ _INIT_DCS_CMD(0x11),
+ _INIT_DELAY_CMD(200),
+ _INIT_DCS_CMD(0x29),
+ _INIT_DELAY_CMD(100),
+ {},
+};
+
+static const struct panel_init_cmd inx_hj110iz_init_cmd[] = {
+ _INIT_DCS_CMD(0xFF, 0x20),
+ _INIT_DCS_CMD(0xFB, 0x01),
+ _INIT_DCS_CMD(0x05, 0xD1),
+ _INIT_DCS_CMD(0x0D, 0x63),
+ _INIT_DCS_CMD(0x07, 0x8C),
+ _INIT_DCS_CMD(0x08, 0x4B),
+ _INIT_DCS_CMD(0x0E, 0x91),
+ _INIT_DCS_CMD(0x0F, 0x69),
+ _INIT_DCS_CMD(0x95, 0xF5),
+ _INIT_DCS_CMD(0x96, 0xF5),
+ _INIT_DCS_CMD(0x9D, 0x00),
+ _INIT_DCS_CMD(0x9E, 0x00),
+ _INIT_DCS_CMD(0x69, 0x98),
+ _INIT_DCS_CMD(0x75, 0xA2),
+ _INIT_DCS_CMD(0x77, 0xB3),
+ _INIT_DCS_CMD(0xFF, 0x24),
+ _INIT_DCS_CMD(0xFB, 0x01),
+ _INIT_DCS_CMD(0x91, 0x44),
+ _INIT_DCS_CMD(0x92, 0x7A),
+ _INIT_DCS_CMD(0x93, 0x1A),
+ _INIT_DCS_CMD(0x94, 0x40),
+ _INIT_DCS_CMD(0x9A, 0x08),
+ _INIT_DCS_CMD(0x60, 0x96),
+ _INIT_DCS_CMD(0x61, 0xD0),
+ _INIT_DCS_CMD(0x63, 0x70),
+ _INIT_DCS_CMD(0xC2, 0xCF),
+ _INIT_DCS_CMD(0x9B, 0x0F),
+ _INIT_DCS_CMD(0x9A, 0x08),
+ _INIT_DCS_CMD(0x00, 0x03),
+ _INIT_DCS_CMD(0x01, 0x03),
+ _INIT_DCS_CMD(0x02, 0x03),
+ _INIT_DCS_CMD(0x03, 0x03),
+ _INIT_DCS_CMD(0x04, 0x03),
+ _INIT_DCS_CMD(0x05, 0x03),
+ _INIT_DCS_CMD(0x06, 0x22),
+ _INIT_DCS_CMD(0x07, 0x06),
+ _INIT_DCS_CMD(0x08, 0x00),
+ _INIT_DCS_CMD(0x09, 0x1D),
+ _INIT_DCS_CMD(0x0A, 0x1C),
+ _INIT_DCS_CMD(0x0B, 0x13),
+ _INIT_DCS_CMD(0x0C, 0x12),
+ _INIT_DCS_CMD(0x0D, 0x11),
+ _INIT_DCS_CMD(0x0E, 0x10),
+ _INIT_DCS_CMD(0x0F, 0x0F),
+ _INIT_DCS_CMD(0x10, 0x0E),
+ _INIT_DCS_CMD(0x11, 0x0D),
+ _INIT_DCS_CMD(0x12, 0x0C),
+ _INIT_DCS_CMD(0x13, 0x04),
+ _INIT_DCS_CMD(0x14, 0x03),
+ _INIT_DCS_CMD(0x15, 0x03),
+ _INIT_DCS_CMD(0x16, 0x03),
+ _INIT_DCS_CMD(0x17, 0x03),
+ _INIT_DCS_CMD(0x18, 0x03),
+ _INIT_DCS_CMD(0x19, 0x03),
+ _INIT_DCS_CMD(0x1A, 0x03),
+ _INIT_DCS_CMD(0x1B, 0x03),
+ _INIT_DCS_CMD(0x1C, 0x22),
+ _INIT_DCS_CMD(0x1D, 0x06),
+ _INIT_DCS_CMD(0x1E, 0x00),
+ _INIT_DCS_CMD(0x1F, 0x1D),
+ _INIT_DCS_CMD(0x20, 0x1C),
+ _INIT_DCS_CMD(0x21, 0x13),
+ _INIT_DCS_CMD(0x22, 0x12),
+ _INIT_DCS_CMD(0x23, 0x11),
+ _INIT_DCS_CMD(0x24, 0x10),
+ _INIT_DCS_CMD(0x25, 0x0F),
+ _INIT_DCS_CMD(0x26, 0x0E),
+ _INIT_DCS_CMD(0x27, 0x0D),
+ _INIT_DCS_CMD(0x28, 0x0C),
+ _INIT_DCS_CMD(0x29, 0x04),
+ _INIT_DCS_CMD(0x2A, 0x03),
+ _INIT_DCS_CMD(0x2B, 0x03),
+
+ _INIT_DCS_CMD(0x2F, 0x05),
+ _INIT_DCS_CMD(0x30, 0x32),
+ _INIT_DCS_CMD(0x31, 0x43),
+ _INIT_DCS_CMD(0x33, 0x05),
+ _INIT_DCS_CMD(0x34, 0x32),
+ _INIT_DCS_CMD(0x35, 0x43),
+ _INIT_DCS_CMD(0x37, 0x44),
+ _INIT_DCS_CMD(0x38, 0x40),
+ _INIT_DCS_CMD(0x39, 0x00),
+ _INIT_DCS_CMD(0x3A, 0x18),
+ _INIT_DCS_CMD(0x3B, 0x00),
+ _INIT_DCS_CMD(0x3D, 0x93),
+ _INIT_DCS_CMD(0xAB, 0x44),
+ _INIT_DCS_CMD(0xAC, 0x40),
+
+ _INIT_DCS_CMD(0x4D, 0x21),
+ _INIT_DCS_CMD(0x4E, 0x43),
+ _INIT_DCS_CMD(0x4F, 0x65),
+ _INIT_DCS_CMD(0x50, 0x87),
+ _INIT_DCS_CMD(0x51, 0x78),
+ _INIT_DCS_CMD(0x52, 0x56),
+ _INIT_DCS_CMD(0x53, 0x34),
+ _INIT_DCS_CMD(0x54, 0x21),
+ _INIT_DCS_CMD(0x55, 0x83),
+ _INIT_DCS_CMD(0x56, 0x08),
+ _INIT_DCS_CMD(0x58, 0x21),
+ _INIT_DCS_CMD(0x59, 0x40),
+ _INIT_DCS_CMD(0x5A, 0x00),
+ _INIT_DCS_CMD(0x5B, 0x2C),
+ _INIT_DCS_CMD(0x5E, 0x00, 0x10),
+ _INIT_DCS_CMD(0x5F, 0x00),
+
+ _INIT_DCS_CMD(0x7A, 0x00),
+ _INIT_DCS_CMD(0x7B, 0x00),
+ _INIT_DCS_CMD(0x7C, 0x00),
+ _INIT_DCS_CMD(0x7D, 0x00),
+ _INIT_DCS_CMD(0x7E, 0x20),
+ _INIT_DCS_CMD(0x7F, 0x3C),
+ _INIT_DCS_CMD(0x80, 0x00),
+ _INIT_DCS_CMD(0x81, 0x00),
+ _INIT_DCS_CMD(0x82, 0x08),
+ _INIT_DCS_CMD(0x97, 0x02),
+ _INIT_DCS_CMD(0xC5, 0x10),
+ _INIT_DCS_CMD(0xDA, 0x05),
+ _INIT_DCS_CMD(0xDB, 0x01),
+ _INIT_DCS_CMD(0xDC, 0x7A),
+ _INIT_DCS_CMD(0xDD, 0x55),
+ _INIT_DCS_CMD(0xDE, 0x27),
+ _INIT_DCS_CMD(0xDF, 0x01),
+ _INIT_DCS_CMD(0xE0, 0x7A),
+ _INIT_DCS_CMD(0xE1, 0x01),
+ _INIT_DCS_CMD(0xE2, 0x7A),
+ _INIT_DCS_CMD(0xE3, 0x01),
+ _INIT_DCS_CMD(0xE4, 0x7A),
+ _INIT_DCS_CMD(0xE5, 0x01),
+ _INIT_DCS_CMD(0xE6, 0x7A),
+ _INIT_DCS_CMD(0xE7, 0x00),
+ _INIT_DCS_CMD(0xE8, 0x00),
+ _INIT_DCS_CMD(0xE9, 0x01),
+ _INIT_DCS_CMD(0xEA, 0x7A),
+ _INIT_DCS_CMD(0xEB, 0x01),
+ _INIT_DCS_CMD(0xEE, 0x7A),
+ _INIT_DCS_CMD(0xEF, 0x01),
+ _INIT_DCS_CMD(0xF0, 0x7A),
+
+ _INIT_DCS_CMD(0xB6, 0x05, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x05, 0x00, 0x00),
+ _INIT_DCS_CMD(0xFF, 0x25),
+ _INIT_DCS_CMD(0xFB, 0x01),
+
+ _INIT_DCS_CMD(0x05, 0x00),
+
+ _INIT_DCS_CMD(0x13, 0x02),
+ _INIT_DCS_CMD(0x14, 0xDF),
+ _INIT_DCS_CMD(0xF1, 0x10),
+ _INIT_DCS_CMD(0x1E, 0x00),
+ _INIT_DCS_CMD(0x1F, 0x00),
+ _INIT_DCS_CMD(0x20, 0x2C),
+ _INIT_DCS_CMD(0x25, 0x00),
+ _INIT_DCS_CMD(0x26, 0x00),
+ _INIT_DCS_CMD(0x27, 0x2C),
+ _INIT_DCS_CMD(0x3F, 0x80),
+ _INIT_DCS_CMD(0x40, 0x00),
+ _INIT_DCS_CMD(0x43, 0x00),
+
+ _INIT_DCS_CMD(0x44, 0x18),
+ _INIT_DCS_CMD(0x45, 0x00),
+
+ _INIT_DCS_CMD(0x48, 0x00),
+ _INIT_DCS_CMD(0x49, 0x2C),
+ _INIT_DCS_CMD(0x5B, 0x80),
+ _INIT_DCS_CMD(0x5C, 0x00),
+ _INIT_DCS_CMD(0x5D, 0x00),
+ _INIT_DCS_CMD(0x5E, 0x00),
+ _INIT_DCS_CMD(0x61, 0x00),
+ _INIT_DCS_CMD(0x62, 0x2C),
+ _INIT_DCS_CMD(0x68, 0x10),
+ _INIT_DCS_CMD(0xFF, 0x26),
+ _INIT_DCS_CMD(0xFB, 0x01),
+
+ _INIT_DCS_CMD(0x00, 0xA1),
+ _INIT_DCS_CMD(0x02, 0x31),
+ _INIT_DCS_CMD(0x0A, 0xF2),
+ _INIT_DCS_CMD(0x04, 0x28),
+ _INIT_DCS_CMD(0x06, 0x30),
+ _INIT_DCS_CMD(0x0C, 0x16),
+ _INIT_DCS_CMD(0x0D, 0x0D),
+ _INIT_DCS_CMD(0x0F, 0x00),
+ _INIT_DCS_CMD(0x11, 0x00),
+ _INIT_DCS_CMD(0x12, 0x50),
+ _INIT_DCS_CMD(0x13, 0x56),
+ _INIT_DCS_CMD(0x14, 0x57),
+ _INIT_DCS_CMD(0x15, 0x00),
+ _INIT_DCS_CMD(0x16, 0x10),
+ _INIT_DCS_CMD(0x17, 0xA0),
+ _INIT_DCS_CMD(0x18, 0x86),
+ _INIT_DCS_CMD(0x22, 0x00),
+ _INIT_DCS_CMD(0x23, 0x00),
+ _INIT_DCS_CMD(0x19, 0x0D),
+ _INIT_DCS_CMD(0x1A, 0x7F),
+ _INIT_DCS_CMD(0x1B, 0x0C),
+ _INIT_DCS_CMD(0x1C, 0xBF),
+ _INIT_DCS_CMD(0x2A, 0x0D),
+ _INIT_DCS_CMD(0x2B, 0x7F),
+ _INIT_DCS_CMD(0x20, 0x00),
+
+ _INIT_DCS_CMD(0x1D, 0x00),
+ _INIT_DCS_CMD(0x1E, 0x78),
+ _INIT_DCS_CMD(0x1F, 0x78),
+
+ _INIT_DCS_CMD(0x2F, 0x03),
+ _INIT_DCS_CMD(0x30, 0x78),
+ _INIT_DCS_CMD(0x33, 0x78),
+ _INIT_DCS_CMD(0x34, 0x66),
+ _INIT_DCS_CMD(0x35, 0x11),
+
+ _INIT_DCS_CMD(0x39, 0x10),
+ _INIT_DCS_CMD(0x3A, 0x78),
+ _INIT_DCS_CMD(0x3B, 0x06),
+
+ _INIT_DCS_CMD(0xC8, 0x04),
+ _INIT_DCS_CMD(0xC9, 0x84),
+ _INIT_DCS_CMD(0xCA, 0x4E),
+ _INIT_DCS_CMD(0xCB, 0x00),
+
+ _INIT_DCS_CMD(0xA9, 0x50),
+ _INIT_DCS_CMD(0xAA, 0x4F),
+ _INIT_DCS_CMD(0xAB, 0x4D),
+ _INIT_DCS_CMD(0xAC, 0x4A),
+ _INIT_DCS_CMD(0xAD, 0x48),
+ _INIT_DCS_CMD(0xAE, 0x46),
+ _INIT_DCS_CMD(0xFF, 0x27),
+ _INIT_DCS_CMD(0xFB, 0x01),
+ _INIT_DCS_CMD(0xC0, 0x18),
+ _INIT_DCS_CMD(0xC1, 0x00),
+ _INIT_DCS_CMD(0xC2, 0x00),
+ _INIT_DCS_CMD(0x56, 0x06),
+ _INIT_DCS_CMD(0x58, 0x80),
+ _INIT_DCS_CMD(0x59, 0x75),
+ _INIT_DCS_CMD(0x5A, 0x00),
+ _INIT_DCS_CMD(0x5B, 0x02),
+ _INIT_DCS_CMD(0x5C, 0x00),
+ _INIT_DCS_CMD(0x5D, 0x00),
+ _INIT_DCS_CMD(0x5E, 0x20),
+ _INIT_DCS_CMD(0x5F, 0x10),
+ _INIT_DCS_CMD(0x60, 0x00),
+ _INIT_DCS_CMD(0x61, 0x2E),
+ _INIT_DCS_CMD(0x62, 0x00),
+ _INIT_DCS_CMD(0x63, 0x01),
+ _INIT_DCS_CMD(0x64, 0x43),
+ _INIT_DCS_CMD(0x65, 0x2D),
+ _INIT_DCS_CMD(0x66, 0x00),
+ _INIT_DCS_CMD(0x67, 0x01),
+ _INIT_DCS_CMD(0x68, 0x43),
+ _INIT_DCS_CMD(0x98, 0x01),
+ _INIT_DCS_CMD(0xB4, 0x03),
+ _INIT_DCS_CMD(0x9B, 0xBD),
+ _INIT_DCS_CMD(0xA0, 0x90),
+ _INIT_DCS_CMD(0xAB, 0x1B),
+ _INIT_DCS_CMD(0xBC, 0x0C),
+ _INIT_DCS_CMD(0xBD, 0x28),
+
+ _INIT_DCS_CMD(0xFF, 0x2A),
+ _INIT_DCS_CMD(0xFB, 0x01),
+
+ _INIT_DCS_CMD(0x22, 0x2F),
+ _INIT_DCS_CMD(0x23, 0x08),
+
+ _INIT_DCS_CMD(0x24, 0x00),
+ _INIT_DCS_CMD(0x25, 0x65),
+ _INIT_DCS_CMD(0x26, 0xF8),
+ _INIT_DCS_CMD(0x27, 0x00),
+ _INIT_DCS_CMD(0x28, 0x1A),
+ _INIT_DCS_CMD(0x29, 0x00),
+ _INIT_DCS_CMD(0x2A, 0x1A),
+ _INIT_DCS_CMD(0x2B, 0x00),
+ _INIT_DCS_CMD(0x2D, 0x1A),
+
+ _INIT_DCS_CMD(0x64, 0x96),
+ _INIT_DCS_CMD(0x65, 0x00),
+ _INIT_DCS_CMD(0x66, 0x00),
+ _INIT_DCS_CMD(0x6A, 0x96),
+ _INIT_DCS_CMD(0x6B, 0x00),
+ _INIT_DCS_CMD(0x6C, 0x00),
+ _INIT_DCS_CMD(0x70, 0x92),
+ _INIT_DCS_CMD(0x71, 0x00),
+ _INIT_DCS_CMD(0x72, 0x00),
+ _INIT_DCS_CMD(0xA2, 0x33),
+ _INIT_DCS_CMD(0xA3, 0x30),
+ _INIT_DCS_CMD(0xA4, 0xC0),
+ _INIT_DCS_CMD(0xE8, 0x00),
+ _INIT_DCS_CMD(0x97, 0x3C),
+ _INIT_DCS_CMD(0x98, 0x02),
+ _INIT_DCS_CMD(0x99, 0x95),
+ _INIT_DCS_CMD(0x9A, 0x06),
+ _INIT_DCS_CMD(0x9B, 0x00),
+ _INIT_DCS_CMD(0x9C, 0x0B),
+ _INIT_DCS_CMD(0x9D, 0x0A),
+ _INIT_DCS_CMD(0x9E, 0x90),
+ _INIT_DCS_CMD(0xFF, 0xF0),
+ _INIT_DCS_CMD(0xFB, 0x01),
+ _INIT_DCS_CMD(0x3A, 0x08),
+ _INIT_DCS_CMD(0xFF, 0xD0),
+ _INIT_DCS_CMD(0xFB, 0x01),
+ _INIT_DCS_CMD(0x00, 0x33),
+ _INIT_DCS_CMD(0x08, 0x01),
+ _INIT_DCS_CMD(0x09, 0xBF),
+ _INIT_DCS_CMD(0x2F, 0x33),
+ _INIT_DCS_CMD(0xFF, 0x23),
+ _INIT_DCS_CMD(0xFB, 0x01),
+ _INIT_DCS_CMD(0x00, 0x80),
+ _INIT_DCS_CMD(0x07, 0x00),
+ _INIT_DCS_CMD(0xFF, 0x20),
+ _INIT_DCS_CMD(0xFB, 0x01),
+ _INIT_DCS_CMD(0x30, 0x00),
+ _INIT_DCS_CMD(0xFF, 0x24),
+ _INIT_DCS_CMD(0x5C, 0x88),
+ _INIT_DCS_CMD(0x5D, 0x08),
+ _INIT_DCS_CMD(0xFF, 0x10),
+ _INIT_DCS_CMD(0xB9, 0x01),
+ _INIT_DCS_CMD(0xFF, 0x20),
+ _INIT_DCS_CMD(0x18, 0x40),
+ _INIT_DCS_CMD(0xFF, 0x10),
+ _INIT_DCS_CMD(0xB9, 0x02),
+ _INIT_DCS_CMD(0xFF, 0x10),
+ _INIT_DCS_CMD(0xFB, 0x01),
+ _INIT_DCS_CMD(0xBB, 0x13),
+ _INIT_DCS_CMD(0x3B, 0x03, 0x96, 0x1A, 0x04, 0x04),
+ _INIT_DCS_CMD(0x35, 0x00),
+ _INIT_DCS_CMD(0x51, 0x0F, 0xFF),
+ _INIT_DCS_CMD(0x53, 0x24),
+ _INIT_DELAY_CMD(100),
+ _INIT_DCS_CMD(0x11),
+ _INIT_DELAY_CMD(200),
+ _INIT_DCS_CMD(0x29),
+ _INIT_DELAY_CMD(100),
+ {},
+};
+
+static const struct panel_init_cmd boe_init_cmd[] = {
+ _INIT_DELAY_CMD(24),
+ _INIT_DCS_CMD(0xB0, 0x05),
+ _INIT_DCS_CMD(0xB1, 0xE5),
+ _INIT_DCS_CMD(0xB3, 0x52),
+ _INIT_DCS_CMD(0xB0, 0x00),
+ _INIT_DCS_CMD(0xB3, 0x88),
+ _INIT_DCS_CMD(0xB0, 0x04),
+ _INIT_DCS_CMD(0xB8, 0x00),
+ _INIT_DCS_CMD(0xB0, 0x00),
+ _INIT_DCS_CMD(0xB6, 0x03),
+ _INIT_DCS_CMD(0xBA, 0x8B),
+ _INIT_DCS_CMD(0xBF, 0x1A),
+ _INIT_DCS_CMD(0xC0, 0x0F),
+ _INIT_DCS_CMD(0xC2, 0x0C),
+ _INIT_DCS_CMD(0xC3, 0x02),
+ _INIT_DCS_CMD(0xC4, 0x0C),
+ _INIT_DCS_CMD(0xC5, 0x02),
+ _INIT_DCS_CMD(0xB0, 0x01),
+ _INIT_DCS_CMD(0xE0, 0x26),
+ _INIT_DCS_CMD(0xE1, 0x26),
+ _INIT_DCS_CMD(0xDC, 0x00),
+ _INIT_DCS_CMD(0xDD, 0x00),
+ _INIT_DCS_CMD(0xCC, 0x26),
+ _INIT_DCS_CMD(0xCD, 0x26),
+ _INIT_DCS_CMD(0xC8, 0x00),
+ _INIT_DCS_CMD(0xC9, 0x00),
+ _INIT_DCS_CMD(0xD2, 0x03),
+ _INIT_DCS_CMD(0xD3, 0x03),
+ _INIT_DCS_CMD(0xE6, 0x04),
+ _INIT_DCS_CMD(0xE7, 0x04),
+ _INIT_DCS_CMD(0xC4, 0x09),
+ _INIT_DCS_CMD(0xC5, 0x09),
+ _INIT_DCS_CMD(0xD8, 0x0A),
+ _INIT_DCS_CMD(0xD9, 0x0A),
+ _INIT_DCS_CMD(0xC2, 0x0B),
+ _INIT_DCS_CMD(0xC3, 0x0B),
+ _INIT_DCS_CMD(0xD6, 0x0C),
+ _INIT_DCS_CMD(0xD7, 0x0C),
+ _INIT_DCS_CMD(0xC0, 0x05),
+ _INIT_DCS_CMD(0xC1, 0x05),
+ _INIT_DCS_CMD(0xD4, 0x06),
+ _INIT_DCS_CMD(0xD5, 0x06),
+ _INIT_DCS_CMD(0xCA, 0x07),
+ _INIT_DCS_CMD(0xCB, 0x07),
+ _INIT_DCS_CMD(0xDE, 0x08),
+ _INIT_DCS_CMD(0xDF, 0x08),
+ _INIT_DCS_CMD(0xB0, 0x02),
+ _INIT_DCS_CMD(0xC0, 0x00),
+ _INIT_DCS_CMD(0xC1, 0x0D),
+ _INIT_DCS_CMD(0xC2, 0x17),
+ _INIT_DCS_CMD(0xC3, 0x26),
+ _INIT_DCS_CMD(0xC4, 0x31),
+ _INIT_DCS_CMD(0xC5, 0x1C),
+ _INIT_DCS_CMD(0xC6, 0x2C),
+ _INIT_DCS_CMD(0xC7, 0x33),
+ _INIT_DCS_CMD(0xC8, 0x31),
+ _INIT_DCS_CMD(0xC9, 0x37),
+ _INIT_DCS_CMD(0xCA, 0x37),
+ _INIT_DCS_CMD(0xCB, 0x37),
+ _INIT_DCS_CMD(0xCC, 0x39),
+ _INIT_DCS_CMD(0xCD, 0x2E),
+ _INIT_DCS_CMD(0xCE, 0x2F),
+ _INIT_DCS_CMD(0xCF, 0x2F),
+ _INIT_DCS_CMD(0xD0, 0x07),
+ _INIT_DCS_CMD(0xD2, 0x00),
+ _INIT_DCS_CMD(0xD3, 0x0D),
+ _INIT_DCS_CMD(0xD4, 0x17),
+ _INIT_DCS_CMD(0xD5, 0x26),
+ _INIT_DCS_CMD(0xD6, 0x31),
+ _INIT_DCS_CMD(0xD7, 0x3F),
+ _INIT_DCS_CMD(0xD8, 0x3F),
+ _INIT_DCS_CMD(0xD9, 0x3F),
+ _INIT_DCS_CMD(0xDA, 0x3F),
+ _INIT_DCS_CMD(0xDB, 0x37),
+ _INIT_DCS_CMD(0xDC, 0x37),
+ _INIT_DCS_CMD(0xDD, 0x37),
+ _INIT_DCS_CMD(0xDE, 0x39),
+ _INIT_DCS_CMD(0xDF, 0x2E),
+ _INIT_DCS_CMD(0xE0, 0x2F),
+ _INIT_DCS_CMD(0xE1, 0x2F),
+ _INIT_DCS_CMD(0xE2, 0x07),
+ _INIT_DCS_CMD(0xB0, 0x03),
+ _INIT_DCS_CMD(0xC8, 0x0B),
+ _INIT_DCS_CMD(0xC9, 0x07),
+ _INIT_DCS_CMD(0xC3, 0x00),
+ _INIT_DCS_CMD(0xE7, 0x00),
+ _INIT_DCS_CMD(0xC5, 0x2A),
+ _INIT_DCS_CMD(0xDE, 0x2A),
+ _INIT_DCS_CMD(0xCA, 0x43),
+ _INIT_DCS_CMD(0xC9, 0x07),
+ _INIT_DCS_CMD(0xE4, 0xC0),
+ _INIT_DCS_CMD(0xE5, 0x0D),
+ _INIT_DCS_CMD(0xCB, 0x00),
+ _INIT_DCS_CMD(0xB0, 0x06),
+ _INIT_DCS_CMD(0xB8, 0xA5),
+ _INIT_DCS_CMD(0xC0, 0xA5),
+ _INIT_DCS_CMD(0xC7, 0x0F),
+ _INIT_DCS_CMD(0xD5, 0x32),
+ _INIT_DCS_CMD(0xB8, 0x00),
+ _INIT_DCS_CMD(0xC0, 0x00),
+ _INIT_DCS_CMD(0xBC, 0x00),
+ _INIT_DCS_CMD(0xB0, 0x07),
+ _INIT_DCS_CMD(0xB1, 0x00),
+ _INIT_DCS_CMD(0xB2, 0x02),
+ _INIT_DCS_CMD(0xB3, 0x0F),
+ _INIT_DCS_CMD(0xB4, 0x25),
+ _INIT_DCS_CMD(0xB5, 0x39),
+ _INIT_DCS_CMD(0xB6, 0x4E),
+ _INIT_DCS_CMD(0xB7, 0x72),
+ _INIT_DCS_CMD(0xB8, 0x97),
+ _INIT_DCS_CMD(0xB9, 0xDC),
+ _INIT_DCS_CMD(0xBA, 0x22),
+ _INIT_DCS_CMD(0xBB, 0xA4),
+ _INIT_DCS_CMD(0xBC, 0x2B),
+ _INIT_DCS_CMD(0xBD, 0x2F),
+ _INIT_DCS_CMD(0xBE, 0xA9),
+ _INIT_DCS_CMD(0xBF, 0x25),
+ _INIT_DCS_CMD(0xC0, 0x61),
+ _INIT_DCS_CMD(0xC1, 0x97),
+ _INIT_DCS_CMD(0xC2, 0xB2),
+ _INIT_DCS_CMD(0xC3, 0xCD),
+ _INIT_DCS_CMD(0xC4, 0xD9),
+ _INIT_DCS_CMD(0xC5, 0xE7),
+ _INIT_DCS_CMD(0xC6, 0xF4),
+ _INIT_DCS_CMD(0xC7, 0xFA),
+ _INIT_DCS_CMD(0xC8, 0xFC),
+ _INIT_DCS_CMD(0xC9, 0x00),
+ _INIT_DCS_CMD(0xCA, 0x00),
+ _INIT_DCS_CMD(0xCB, 0x16),
+ _INIT_DCS_CMD(0xCC, 0xAF),
+ _INIT_DCS_CMD(0xCD, 0xFF),
+ _INIT_DCS_CMD(0xCE, 0xFF),
+ _INIT_DCS_CMD(0xB0, 0x08),
+ _INIT_DCS_CMD(0xB1, 0x04),
+ _INIT_DCS_CMD(0xB2, 0x05),
+ _INIT_DCS_CMD(0xB3, 0x11),
+ _INIT_DCS_CMD(0xB4, 0x24),
+ _INIT_DCS_CMD(0xB5, 0x39),
+ _INIT_DCS_CMD(0xB6, 0x4F),
+ _INIT_DCS_CMD(0xB7, 0x72),
+ _INIT_DCS_CMD(0xB8, 0x98),
+ _INIT_DCS_CMD(0xB9, 0xDC),
+ _INIT_DCS_CMD(0xBA, 0x23),
+ _INIT_DCS_CMD(0xBB, 0xA6),
+ _INIT_DCS_CMD(0xBC, 0x2C),
+ _INIT_DCS_CMD(0xBD, 0x30),
+ _INIT_DCS_CMD(0xBE, 0xAA),
+ _INIT_DCS_CMD(0xBF, 0x26),
+ _INIT_DCS_CMD(0xC0, 0x62),
+ _INIT_DCS_CMD(0xC1, 0x9B),
+ _INIT_DCS_CMD(0xC2, 0xB5),
+ _INIT_DCS_CMD(0xC3, 0xCF),
+ _INIT_DCS_CMD(0xC4, 0xDB),
+ _INIT_DCS_CMD(0xC5, 0xE8),
+ _INIT_DCS_CMD(0xC6, 0xF5),
+ _INIT_DCS_CMD(0xC7, 0xFA),
+ _INIT_DCS_CMD(0xC8, 0xFC),
+ _INIT_DCS_CMD(0xC9, 0x00),
+ _INIT_DCS_CMD(0xCA, 0x00),
+ _INIT_DCS_CMD(0xCB, 0x16),
+ _INIT_DCS_CMD(0xCC, 0xAF),
+ _INIT_DCS_CMD(0xCD, 0xFF),
+ _INIT_DCS_CMD(0xCE, 0xFF),
+ _INIT_DCS_CMD(0xB0, 0x09),
+ _INIT_DCS_CMD(0xB1, 0x04),
+ _INIT_DCS_CMD(0xB2, 0x02),
+ _INIT_DCS_CMD(0xB3, 0x16),
+ _INIT_DCS_CMD(0xB4, 0x24),
+ _INIT_DCS_CMD(0xB5, 0x3B),
+ _INIT_DCS_CMD(0xB6, 0x4F),
+ _INIT_DCS_CMD(0xB7, 0x73),
+ _INIT_DCS_CMD(0xB8, 0x99),
+ _INIT_DCS_CMD(0xB9, 0xE0),
+ _INIT_DCS_CMD(0xBA, 0x26),
+ _INIT_DCS_CMD(0xBB, 0xAD),
+ _INIT_DCS_CMD(0xBC, 0x36),
+ _INIT_DCS_CMD(0xBD, 0x3A),
+ _INIT_DCS_CMD(0xBE, 0xAE),
+ _INIT_DCS_CMD(0xBF, 0x2A),
+ _INIT_DCS_CMD(0xC0, 0x66),
+ _INIT_DCS_CMD(0xC1, 0x9E),
+ _INIT_DCS_CMD(0xC2, 0xB8),
+ _INIT_DCS_CMD(0xC3, 0xD1),
+ _INIT_DCS_CMD(0xC4, 0xDD),
+ _INIT_DCS_CMD(0xC5, 0xE9),
+ _INIT_DCS_CMD(0xC6, 0xF6),
+ _INIT_DCS_CMD(0xC7, 0xFA),
+ _INIT_DCS_CMD(0xC8, 0xFC),
+ _INIT_DCS_CMD(0xC9, 0x00),
+ _INIT_DCS_CMD(0xCA, 0x00),
+ _INIT_DCS_CMD(0xCB, 0x16),
+ _INIT_DCS_CMD(0xCC, 0xAF),
+ _INIT_DCS_CMD(0xCD, 0xFF),
+ _INIT_DCS_CMD(0xCE, 0xFF),
+ _INIT_DCS_CMD(0xB0, 0x0A),
+ _INIT_DCS_CMD(0xB1, 0x00),
+ _INIT_DCS_CMD(0xB2, 0x02),
+ _INIT_DCS_CMD(0xB3, 0x0F),
+ _INIT_DCS_CMD(0xB4, 0x25),
+ _INIT_DCS_CMD(0xB5, 0x39),
+ _INIT_DCS_CMD(0xB6, 0x4E),
+ _INIT_DCS_CMD(0xB7, 0x72),
+ _INIT_DCS_CMD(0xB8, 0x97),
+ _INIT_DCS_CMD(0xB9, 0xDC),
+ _INIT_DCS_CMD(0xBA, 0x22),
+ _INIT_DCS_CMD(0xBB, 0xA4),
+ _INIT_DCS_CMD(0xBC, 0x2B),
+ _INIT_DCS_CMD(0xBD, 0x2F),
+ _INIT_DCS_CMD(0xBE, 0xA9),
+ _INIT_DCS_CMD(0xBF, 0x25),
+ _INIT_DCS_CMD(0xC0, 0x61),
+ _INIT_DCS_CMD(0xC1, 0x97),
+ _INIT_DCS_CMD(0xC2, 0xB2),
+ _INIT_DCS_CMD(0xC3, 0xCD),
+ _INIT_DCS_CMD(0xC4, 0xD9),
+ _INIT_DCS_CMD(0xC5, 0xE7),
+ _INIT_DCS_CMD(0xC6, 0xF4),
+ _INIT_DCS_CMD(0xC7, 0xFA),
+ _INIT_DCS_CMD(0xC8, 0xFC),
+ _INIT_DCS_CMD(0xC9, 0x00),
+ _INIT_DCS_CMD(0xCA, 0x00),
+ _INIT_DCS_CMD(0xCB, 0x16),
+ _INIT_DCS_CMD(0xCC, 0xAF),
+ _INIT_DCS_CMD(0xCD, 0xFF),
+ _INIT_DCS_CMD(0xCE, 0xFF),
+ _INIT_DCS_CMD(0xB0, 0x0B),
+ _INIT_DCS_CMD(0xB1, 0x04),
+ _INIT_DCS_CMD(0xB2, 0x05),
+ _INIT_DCS_CMD(0xB3, 0x11),
+ _INIT_DCS_CMD(0xB4, 0x24),
+ _INIT_DCS_CMD(0xB5, 0x39),
+ _INIT_DCS_CMD(0xB6, 0x4F),
+ _INIT_DCS_CMD(0xB7, 0x72),
+ _INIT_DCS_CMD(0xB8, 0x98),
+ _INIT_DCS_CMD(0xB9, 0xDC),
+ _INIT_DCS_CMD(0xBA, 0x23),
+ _INIT_DCS_CMD(0xBB, 0xA6),
+ _INIT_DCS_CMD(0xBC, 0x2C),
+ _INIT_DCS_CMD(0xBD, 0x30),
+ _INIT_DCS_CMD(0xBE, 0xAA),
+ _INIT_DCS_CMD(0xBF, 0x26),
+ _INIT_DCS_CMD(0xC0, 0x62),
+ _INIT_DCS_CMD(0xC1, 0x9B),
+ _INIT_DCS_CMD(0xC2, 0xB5),
+ _INIT_DCS_CMD(0xC3, 0xCF),
+ _INIT_DCS_CMD(0xC4, 0xDB),
+ _INIT_DCS_CMD(0xC5, 0xE8),
+ _INIT_DCS_CMD(0xC6, 0xF5),
+ _INIT_DCS_CMD(0xC7, 0xFA),
+ _INIT_DCS_CMD(0xC8, 0xFC),
+ _INIT_DCS_CMD(0xC9, 0x00),
+ _INIT_DCS_CMD(0xCA, 0x00),
+ _INIT_DCS_CMD(0xCB, 0x16),
+ _INIT_DCS_CMD(0xCC, 0xAF),
+ _INIT_DCS_CMD(0xCD, 0xFF),
+ _INIT_DCS_CMD(0xCE, 0xFF),
+ _INIT_DCS_CMD(0xB0, 0x0C),
+ _INIT_DCS_CMD(0xB1, 0x04),
+ _INIT_DCS_CMD(0xB2, 0x02),
+ _INIT_DCS_CMD(0xB3, 0x16),
+ _INIT_DCS_CMD(0xB4, 0x24),
+ _INIT_DCS_CMD(0xB5, 0x3B),
+ _INIT_DCS_CMD(0xB6, 0x4F),
+ _INIT_DCS_CMD(0xB7, 0x73),
+ _INIT_DCS_CMD(0xB8, 0x99),
+ _INIT_DCS_CMD(0xB9, 0xE0),
+ _INIT_DCS_CMD(0xBA, 0x26),
+ _INIT_DCS_CMD(0xBB, 0xAD),
+ _INIT_DCS_CMD(0xBC, 0x36),
+ _INIT_DCS_CMD(0xBD, 0x3A),
+ _INIT_DCS_CMD(0xBE, 0xAE),
+ _INIT_DCS_CMD(0xBF, 0x2A),
+ _INIT_DCS_CMD(0xC0, 0x66),
+ _INIT_DCS_CMD(0xC1, 0x9E),
+ _INIT_DCS_CMD(0xC2, 0xB8),
+ _INIT_DCS_CMD(0xC3, 0xD1),
+ _INIT_DCS_CMD(0xC4, 0xDD),
+ _INIT_DCS_CMD(0xC5, 0xE9),
+ _INIT_DCS_CMD(0xC6, 0xF6),
+ _INIT_DCS_CMD(0xC7, 0xFA),
+ _INIT_DCS_CMD(0xC8, 0xFC),
+ _INIT_DCS_CMD(0xC9, 0x00),
+ _INIT_DCS_CMD(0xCA, 0x00),
+ _INIT_DCS_CMD(0xCB, 0x16),
+ _INIT_DCS_CMD(0xCC, 0xAF),
+ _INIT_DCS_CMD(0xCD, 0xFF),
+ _INIT_DCS_CMD(0xCE, 0xFF),
+ _INIT_DCS_CMD(0xB0, 0x00),
+ _INIT_DCS_CMD(0xB3, 0x08),
+ _INIT_DCS_CMD(0xB0, 0x04),
+ _INIT_DCS_CMD(0xB8, 0x68),
+ _INIT_DELAY_CMD(150),
+ {},
+};
+
+static const struct panel_init_cmd auo_kd101n80_45na_init_cmd[] = {
+ _INIT_DELAY_CMD(24),
+ _INIT_DCS_CMD(0x11),
+ _INIT_DELAY_CMD(120),
+ _INIT_DCS_CMD(0x29),
+ _INIT_DELAY_CMD(120),
+ {},
+};
+
+static const struct panel_init_cmd auo_b101uan08_3_init_cmd[] = {
+ _INIT_DELAY_CMD(24),
+ _INIT_DCS_CMD(0xB0, 0x01),
+ _INIT_DCS_CMD(0xC0, 0x48),
+ _INIT_DCS_CMD(0xC1, 0x48),
+ _INIT_DCS_CMD(0xC2, 0x47),
+ _INIT_DCS_CMD(0xC3, 0x47),
+ _INIT_DCS_CMD(0xC4, 0x46),
+ _INIT_DCS_CMD(0xC5, 0x46),
+ _INIT_DCS_CMD(0xC6, 0x45),
+ _INIT_DCS_CMD(0xC7, 0x45),
+ _INIT_DCS_CMD(0xC8, 0x64),
+ _INIT_DCS_CMD(0xC9, 0x64),
+ _INIT_DCS_CMD(0xCA, 0x4F),
+ _INIT_DCS_CMD(0xCB, 0x4F),
+ _INIT_DCS_CMD(0xCC, 0x40),
+ _INIT_DCS_CMD(0xCD, 0x40),
+ _INIT_DCS_CMD(0xCE, 0x66),
+ _INIT_DCS_CMD(0xCF, 0x66),
+ _INIT_DCS_CMD(0xD0, 0x4F),
+ _INIT_DCS_CMD(0xD1, 0x4F),
+ _INIT_DCS_CMD(0xD2, 0x41),
+ _INIT_DCS_CMD(0xD3, 0x41),
+ _INIT_DCS_CMD(0xD4, 0x48),
+ _INIT_DCS_CMD(0xD5, 0x48),
+ _INIT_DCS_CMD(0xD6, 0x47),
+ _INIT_DCS_CMD(0xD7, 0x47),
+ _INIT_DCS_CMD(0xD8, 0x46),
+ _INIT_DCS_CMD(0xD9, 0x46),
+ _INIT_DCS_CMD(0xDA, 0x45),
+ _INIT_DCS_CMD(0xDB, 0x45),
+ _INIT_DCS_CMD(0xDC, 0x64),
+ _INIT_DCS_CMD(0xDD, 0x64),
+ _INIT_DCS_CMD(0xDE, 0x4F),
+ _INIT_DCS_CMD(0xDF, 0x4F),
+ _INIT_DCS_CMD(0xE0, 0x40),
+ _INIT_DCS_CMD(0xE1, 0x40),
+ _INIT_DCS_CMD(0xE2, 0x66),
+ _INIT_DCS_CMD(0xE3, 0x66),
+ _INIT_DCS_CMD(0xE4, 0x4F),
+ _INIT_DCS_CMD(0xE5, 0x4F),
+ _INIT_DCS_CMD(0xE6, 0x41),
+ _INIT_DCS_CMD(0xE7, 0x41),
+ _INIT_DELAY_CMD(150),
+ {},
+};
+
+static inline struct boe_panel *to_boe_panel(struct drm_panel *panel)
+{
+ return container_of(panel, struct boe_panel, base);
+}
+
+static int boe_panel_init_dcs_cmd(struct boe_panel *boe)
+{
+ struct mipi_dsi_device *dsi = boe->dsi;
+ struct drm_panel *panel = &boe->base;
+ int i, err = 0;
+
+ if (boe->desc->init_cmds) {
+ const struct panel_init_cmd *init_cmds = boe->desc->init_cmds;
+
+ for (i = 0; init_cmds[i].len != 0; i++) {
+ const struct panel_init_cmd *cmd = &init_cmds[i];
+
+ switch (cmd->type) {
+ case DELAY_CMD:
+ msleep(cmd->data[0]);
+ err = 0;
+ break;
+
+ case INIT_DCS_CMD:
+ err = mipi_dsi_dcs_write(dsi, cmd->data[0],
+ cmd->len <= 1 ? NULL :
+ &cmd->data[1],
+ cmd->len - 1);
+ break;
+
+ default:
+ err = -EINVAL;
+ }
+
+ if (err < 0) {
+ dev_err(panel->dev,
+ "failed to write command %u\n", i);
+ return err;
+ }
+ }
+ }
+ return 0;
+}
+
+static int boe_panel_enter_sleep_mode(struct boe_panel *boe)
+{
+ struct mipi_dsi_device *dsi = boe->dsi;
+ int ret;
+
+ 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 boe_panel_disable(struct drm_panel *panel)
+{
+ struct boe_panel *boe = to_boe_panel(panel);
+ int ret;
+
+ ret = boe_panel_enter_sleep_mode(boe);
+ if (ret < 0) {
+ dev_err(panel->dev, "failed to set panel off: %d\n", ret);
+ return ret;
+ }
+
+ msleep(150);
+
+ return 0;
+}
+
+static int boe_panel_unprepare(struct drm_panel *panel)
+{
+ struct boe_panel *boe = to_boe_panel(panel);
+
+ if (!boe->prepared)
+ return 0;
+
+ 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);
+ }
+
+ boe->prepared = false;
+
+ return 0;
+}
+
+static int boe_panel_prepare(struct drm_panel *panel)
+{
+ struct boe_panel *boe = to_boe_panel(panel);
+ int ret;
+
+ if (boe->prepared)
+ return 0;
+
+ 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) {
+ mipi_dsi_dcs_nop(boe->dsi);
+ 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_panel_init_dcs_cmd(boe);
+ if (ret < 0) {
+ dev_err(panel->dev, "failed to init panel: %d\n", ret);
+ goto poweroff;
+ }
+
+ boe->prepared = true;
+
+ return 0;
+
+poweroff:
+ regulator_disable(boe->avee);
+poweroffavdd:
+ regulator_disable(boe->avdd);
+poweroff1v8:
+ usleep_range(5000, 7000);
+ regulator_disable(boe->pp1800);
+ gpiod_set_value(boe->enable_gpio, 0);
+
+ 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_cmds = boe_tv110c9m_init_cmd,
+};
+
+static const struct drm_display_mode inx_hj110iz_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 + 1,
+ .vtotal = 2000 + 26 + 1 + 149,
+ .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_cmds = inx_hj110iz_init_cmd,
+};
+
+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_cmds = boe_init_cmd,
+ .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_cmds = auo_kd101n80_45na_init_cmd,
+ .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_cmds = boe_init_cmd,
+};
+
+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_cmds = auo_b101uan08_3_init_cmd,
+ .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_cmds = boe_init_cmd,
+ .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);
+
+ drm_panel_init(&boe->base, dev, &boe_panel_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+ 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_kzalloc(&dsi->dev, sizeof(*boe), GFP_KERNEL);
+ if (!boe)
+ return -ENOMEM;
+
+ 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_shutdown(struct mipi_dsi_device *dsi)
+{
+ struct boe_panel *boe = mipi_dsi_get_drvdata(dsi);
+
+ drm_panel_disable(&boe->base);
+ drm_panel_unprepare(&boe->base);
+}
+
+static void boe_panel_remove(struct mipi_dsi_device *dsi)
+{
+ struct boe_panel *boe = mipi_dsi_get_drvdata(dsi);
+ int ret;
+
+ boe_panel_shutdown(dsi);
+
+ 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
+ },
+ { /* 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,
+ .shutdown = boe_panel_shutdown,
+};
+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 000000000..ba17bcc44
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-dsi-cm.c
@@ -0,0 +1,652 @@
+// 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_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 <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, &param, 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_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
+ if (!ddata)
+ return -ENOMEM;
+
+ 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);
+
+ drm_panel_init(&ddata->panel, dev, &dsicm_panel_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ 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 000000000..e85d63a17
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-ebbg-ft8719.c
@@ -0,0 +1,283 @@
+// 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 device *dev = &dsi->dev;
+ int ret;
+
+ dsi->mode_flags |= MIPI_DSI_MODE_LPM;
+
+ ret = mipi_dsi_dcs_set_display_brightness(dsi, 0x00ff);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set display brightness: %d\n", ret);
+ return ret;
+ }
+
+ mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x24);
+ mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_WRITE_POWER_SAVE, 0x00);
+
+ ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to exit sleep mode: %d\n", ret);
+ return ret;
+ }
+ msleep(90);
+
+ ret = mipi_dsi_dcs_set_display_on(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set display on: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ebbg_ft8719_off(struct ebbg_ft8719 *ctx)
+{
+ struct mipi_dsi_device *dsi = ctx->dsi;
+ struct device *dev = &dsi->dev;
+ int ret;
+
+ dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
+
+ 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(10000, 11000);
+
+ ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to enter sleep mode: %d\n", ret);
+ return ret;
+ }
+ msleep(90);
+
+ return 0;
+}
+
+static int ebbg_ft8719_prepare(struct drm_panel *panel)
+{
+ struct ebbg_ft8719 *ctx = to_ebbg_ft8719(panel);
+ struct device *dev = &ctx->dsi->dev;
+ 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) {
+ dev_err(dev, "Failed to initialize panel: %d\n", ret);
+ 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);
+ struct device *dev = &ctx->dsi->dev;
+ int ret;
+
+ ret = ebbg_ft8719_off(ctx);
+ if (ret < 0)
+ dev_err(dev, "Failed to un-initialize panel: %d\n", ret);
+
+ gpiod_set_value_cansleep(ctx->reset_gpio, 1);
+
+ ret = regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
+ if (ret)
+ dev_err(panel->dev, "Failed to disable regulators: %d\n", ret);
+
+ 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_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ 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;
+
+ drm_panel_init(&ctx->panel, dev, &ebbg_ft8719_panel_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ 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 000000000..ecd623874
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-edp.c
@@ -0,0 +1,2023 @@
+/*
+ * 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;
+
+ /**
+ * @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 not specified in a standard way 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 {
+ /** @panel_id: 32-bit ID for panel, encoded with drm_edid_encode_panel_id(). */
+ u32 panel_id;
+
+ /** @delay: The power sequencing delays needed for this panel. */
+ const struct panel_delay *delay;
+
+ /** @name: Name of this panel (for printing to logs). */
+ const char *name;
+};
+
+struct panel_edp {
+ struct drm_panel base;
+ bool enabled;
+ bool no_hpd;
+
+ bool prepared;
+
+ ktime_t prepared_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;
+
+ struct edid *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_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();
+
+ 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->enabled)
+ return 0;
+
+ if (p->desc->delay.disable)
+ msleep(p->desc->delay.disable);
+
+ p->enabled = false;
+
+ return 0;
+}
+
+static int panel_edp_suspend(struct device *dev)
+{
+ struct panel_edp *p = dev_get_drvdata(dev);
+
+ gpiod_set_value_cansleep(p->enable_gpio, 0);
+ regulator_disable(p->supply);
+ p->unprepared_time = ktime_get();
+
+ return 0;
+}
+
+static int panel_edp_unprepare(struct drm_panel *panel)
+{
+ struct panel_edp *p = to_panel_edp(panel);
+ int ret;
+
+ /* Unpreparing when already unprepared is a no-op */
+ if (!p->prepared)
+ return 0;
+
+ pm_runtime_mark_last_busy(panel->dev);
+ ret = pm_runtime_put_autosuspend(panel->dev);
+ if (ret < 0)
+ return ret;
+ p->prepared = false;
+
+ 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);
+
+ 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();
+
+ return 0;
+
+error:
+ gpiod_set_value_cansleep(p->enable_gpio, 0);
+ regulator_disable(p->supply);
+ p->unprepared_time = ktime_get();
+
+ 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)
+{
+ struct panel_edp *p = to_panel_edp(panel);
+ int ret;
+
+ /* Preparing when already prepared is a no-op */
+ if (p->prepared)
+ return 0;
+
+ ret = pm_runtime_get_sync(panel->dev);
+ if (ret < 0) {
+ pm_runtime_put_autosuspend(panel->dev);
+ return ret;
+ }
+
+ p->prepared = true;
+
+ return 0;
+}
+
+static int panel_edp_enable(struct drm_panel *panel)
+{
+ struct panel_edp *p = to_panel_edp(panel);
+ unsigned int delay;
+
+ if (p->enabled)
+ return 0;
+
+ 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);
+
+ p->enabled = true;
+
+ 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;
+
+ /* probe EDID if a DDC bus is available */
+ if (p->ddc) {
+ pm_runtime_get_sync(panel->dev);
+
+ if (!p->edid)
+ p->edid = drm_get_edid(connector, p->ddc);
+
+ if (p->edid)
+ num += drm_add_edid_modes(connector, p->edid);
+
+ pm_runtime_mark_last_busy(panel->dev);
+ pm_runtime_put_autosuspend(panel->dev);
+ }
+
+ /*
+ * Add hard-coded panel modes. Don't call this if there are no timings
+ * and no modes (the generic edp-panel case) because it will clobber
+ * the display_info that was already set by drm_add_edid_modes().
+ */
+ if (p->desc->num_timings || p->desc->num_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->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);
+
+static int generic_edp_panel_probe(struct device *dev, struct panel_edp *panel)
+{
+ struct panel_desc *desc;
+ 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 read EDID: %d\n", ret);
+ goto exit;
+ }
+
+ panel_id = drm_edid_get_panel_id(panel->ddc);
+ if (!panel_id) {
+ dev_err(dev, "Couldn't identify panel via EDID\n");
+ ret = -EIO;
+ goto exit;
+ }
+ drm_edid_decode_panel_id(panel_id, vend, &product_id);
+
+ panel->detected_panel = find_edp_panel(panel_id);
+
+ /*
+ * 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);
+
+ /*
+ * It's highly likely that the panel will work if we use very
+ * conservative timings, so let's do that. We already know that
+ * the HPD-related delays must have worked since we got this
+ * far, so we really just need the "unprepare" / "enable"
+ * delays. We don't need "prepare_to_enable" since that
+ * overlaps the "enable" delay anyway.
+ *
+ * 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);
+ } else {
+ dev_info(dev, "Detected %s %s (%#06x)\n",
+ vend, panel->detected_panel->name, product_id);
+
+ /* Update the delay; everything else comes from EDID */
+ desc->delay = *panel->detected_panel->delay;
+ }
+
+ ret = 0;
+exit:
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+
+ return ret;
+}
+
+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_kzalloc(dev, sizeof(*panel), GFP_KERNEL);
+ if (!panel)
+ return -ENOMEM;
+
+ panel->enabled = false;
+ 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);
+
+ drm_panel_init(&panel->base, dev, &panel_edp_funcs, DRM_MODE_CONNECTOR_eDP);
+
+ 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);
+ if (err)
+ goto err_finished_pm_runtime;
+ }
+
+ 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 int panel_edp_remove(struct device *dev)
+{
+ struct panel_edp *panel = dev_get_drvdata(dev);
+
+ drm_panel_remove(&panel->base);
+ drm_panel_disable(&panel->base);
+ drm_panel_unprepare(&panel->base);
+
+ 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);
+
+ kfree(panel->edid);
+ panel->edid = NULL;
+
+ return 0;
+}
+
+static void panel_edp_shutdown(struct device *dev)
+{
+ struct panel_edp *panel = dev_get_drvdata(dev);
+
+ drm_panel_disable(&panel->base);
+ drm_panel_unprepare(&panel->base);
+}
+
+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_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_b116xw03_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 panel_desc auo_b116xw03 = {
+ .modes = &auo_b116xw03_mode,
+ .num_modes = 1,
+ .bpc = 6,
+ .size = {
+ .width = 256,
+ .height = 144,
+ },
+ .delay = {
+ .enable = 400,
+ },
+};
+
+static const struct drm_display_mode auo_b133han05_mode = {
+ .clock = 142600,
+ .hdisplay = 1920,
+ .hsync_start = 1920 + 58,
+ .hsync_end = 1920 + 58 + 42,
+ .htotal = 1920 + 58 + 42 + 60,
+ .vdisplay = 1080,
+ .vsync_start = 1080 + 3,
+ .vsync_end = 1080 + 3 + 5,
+ .vtotal = 1080 + 3 + 5 + 54,
+};
+
+static const struct panel_desc auo_b133han05 = {
+ .modes = &auo_b133han05_mode,
+ .num_modes = 1,
+ .bpc = 8,
+ .size = {
+ .width = 293,
+ .height = 165,
+ },
+ .delay = {
+ .hpd_reliable = 100,
+ .enable = 20,
+ .unprepare = 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 auo_b140han06_mode = {
+ .clock = 141000,
+ .hdisplay = 1920,
+ .hsync_start = 1920 + 16,
+ .hsync_end = 1920 + 16 + 16,
+ .htotal = 1920 + 16 + 16 + 152,
+ .vdisplay = 1080,
+ .vsync_start = 1080 + 3,
+ .vsync_end = 1080 + 3 + 14,
+ .vtotal = 1080 + 3 + 14 + 19,
+};
+
+static const struct panel_desc auo_b140han06 = {
+ .modes = &auo_b140han06_mode,
+ .num_modes = 1,
+ .bpc = 8,
+ .size = {
+ .width = 309,
+ .height = 174,
+ },
+ .delay = {
+ .hpd_reliable = 100,
+ .enable = 20,
+ .unprepare = 50,
+ },
+};
+
+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 ivo_m133nwf4_r0_mode = {
+ .clock = 138778,
+ .hdisplay = 1920,
+ .hsync_start = 1920 + 24,
+ .hsync_end = 1920 + 24 + 48,
+ .htotal = 1920 + 24 + 48 + 88,
+ .vdisplay = 1080,
+ .vsync_start = 1080 + 3,
+ .vsync_end = 1080 + 3 + 12,
+ .vtotal = 1080 + 3 + 12 + 17,
+ .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC,
+};
+
+static const struct panel_desc ivo_m133nwf4_r0 = {
+ .modes = &ivo_m133nwf4_r0_mode,
+ .num_modes = 1,
+ .bpc = 8,
+ .size = {
+ .width = 294,
+ .height = 165,
+ },
+ .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 drm_display_mode sharp_lq140m1jw46_mode[] = {
+ {
+ .clock = 346500,
+ .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 + 69,
+ .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC,
+ }, {
+ .clock = 144370,
+ .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 + 69,
+ .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC,
+ },
+};
+
+static const struct panel_desc sharp_lq140m1jw46 = {
+ .modes = sharp_lq140m1jw46_mode,
+ .num_modes = ARRAY_SIZE(sharp_lq140m1jw46_mode),
+ .bpc = 8,
+ .size = {
+ .width = 309,
+ .height = 174,
+ },
+ .delay = {
+ .hpd_absent = 80,
+ .enable = 50,
+ .unprepare = 500,
+ },
+};
+
+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,
+ .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC,
+};
+
+static const struct panel_desc starry_kr122ea0sra = {
+ .modes = &starry_kr122ea0sra_mode,
+ .num_modes = 1,
+ .size = {
+ .width = 263,
+ .height = 164,
+ },
+ .delay = {
+ /* TODO: should be hpd-absent and no-hpd should be set? */
+ .hpd_reliable = 10 + 200,
+ .enable = 50,
+ .unprepare = 10 + 500,
+ },
+};
+
+static const struct of_device_id platform_of_match[] = {
+ {
+ /* Must be first */
+ .compatible = "edp-panel",
+ }, {
+ .compatible = "auo,b101ean01",
+ .data = &auo_b101ean01,
+ }, {
+ .compatible = "auo,b116xa01",
+ .data = &auo_b116xak01,
+ }, {
+ .compatible = "auo,b116xw03",
+ .data = &auo_b116xw03,
+ }, {
+ .compatible = "auo,b133han05",
+ .data = &auo_b133han05,
+ }, {
+ .compatible = "auo,b133htn01",
+ .data = &auo_b133htn01,
+ }, {
+ .compatible = "auo,b133xtn01",
+ .data = &auo_b133xtn01,
+ }, {
+ .compatible = "auo,b140han06",
+ .data = &auo_b140han06,
+ }, {
+ .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 = "ivo,m133nwf4-r0",
+ .data = &ivo_m133nwf4_r0,
+ }, {
+ .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,
+ }, {
+ .compatible = "sharp,lq140m1jw46",
+ .data = &sharp_lq140m1jw46,
+ }, {
+ .compatible = "starry,kr122ea0sra",
+ .data = &starry_kr122ea0sra,
+ }, {
+ /* 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_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_e80_d50 = {
+ .hpd_absent = 200,
+ .unprepare = 500,
+ .enable = 80,
+ .disable = 50,
+};
+
+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,
+};
+
+#define EDP_PANEL_ENTRY(vend_chr_0, vend_chr_1, vend_chr_2, product_id, _delay, _name) \
+{ \
+ .name = _name, \
+ .panel_id = drm_edid_encode_panel_id(vend_chr_0, vend_chr_1, vend_chr_2, \
+ product_id), \
+ .delay = _delay \
+}
+
+/*
+ * 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', 0x1062, &delay_200_500_e50, "B120XAN01.0"),
+ 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', 0x405c, &auo_b116xak01.delay, "B116XAK01.0"),
+ EDP_PANEL_ENTRY('A', 'U', 'O', 0x615c, &delay_200_500_e50, "B116XAN06.1"),
+ EDP_PANEL_ENTRY('A', 'U', 'O', 0x8594, &delay_200_500_e50, "B133UAN01.0"),
+
+ EDP_PANEL_ENTRY('B', 'O', 'E', 0x0786, &delay_200_500_p2e80, "NV116WHM-T01"),
+ EDP_PANEL_ENTRY('B', 'O', 'E', 0x07d1, &boe_nv133fhm_n61.delay, "NV133FHM-N61"),
+ EDP_PANEL_ENTRY('B', 'O', 'E', 0x082d, &boe_nv133fhm_n61.delay, "NV133FHM-N62"),
+ EDP_PANEL_ENTRY('B', 'O', 'E', 0x094b, &delay_200_500_e50, "NT116WHM-N21"),
+ EDP_PANEL_ENTRY('B', 'O', 'E', 0x098d, &boe_nv110wtm_n61.delay, "NV110WTM-N61"),
+ EDP_PANEL_ENTRY('B', 'O', 'E', 0x09dd, &delay_200_500_e50, "NT116WHM-N21"),
+ EDP_PANEL_ENTRY('B', 'O', 'E', 0x0a5d, &delay_200_500_e50, "NV116WHM-N45"),
+ EDP_PANEL_ENTRY('B', 'O', 'E', 0x0ac5, &delay_200_500_e50, "NV116WHM-N4C"),
+
+ 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', 0x1154, &delay_200_500_e80_d50, "N116BCA-EA2"),
+ EDP_PANEL_ENTRY('C', 'M', 'N', 0x1247, &delay_200_500_e80_d50, "N120ACA-EA1"),
+
+ EDP_PANEL_ENTRY('I', 'V', 'O', 0x057d, &delay_200_500_e200, "R140NWF5 RH"),
+ EDP_PANEL_ENTRY('I', 'V', 'O', 0x854b, &delay_200_500_p2e100, "R133NW4K-R0"),
+
+ EDP_PANEL_ENTRY('K', 'D', 'B', 0x0624, &kingdisplay_kd116n21_30nv_a010.delay, "116N21-30NV-A010"),
+ EDP_PANEL_ENTRY('K', 'D', 'B', 0x1120, &delay_200_500_e80_d50, "116N29-30NK-C007"),
+
+ EDP_PANEL_ENTRY('S', 'H', 'P', 0x1511, &delay_200_500_e50, "LQ140M1JW48"),
+ EDP_PANEL_ENTRY('S', 'H', 'P', 0x1523, &sharp_lq140m1jw46.delay, "LQ140M1JW46"),
+ EDP_PANEL_ENTRY('S', 'H', 'P', 0x154c, &delay_200_500_p2e100, "LQ116M1JW10"),
+
+ EDP_PANEL_ENTRY('S', 'T', 'A', 0x0100, &delay_100_500_e200, "2081116HHD028001-51D"),
+
+ { /* sentinal */ }
+};
+
+static const struct edp_panel_entry *find_edp_panel(u32 panel_id)
+{
+ const struct edp_panel_entry *panel;
+
+ if (!panel_id)
+ return NULL;
+
+ for (panel = edp_panels; panel->panel_id; panel++)
+ if (panel->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 int panel_edp_platform_remove(struct platform_device *pdev)
+{
+ return 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 000000000..3a7fc3ca6
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-elida-kd35t133.c
@@ -0,0 +1,359 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Elida kd35t133 5.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;
+ bool prepared;
+};
+
+static inline struct kd35t133 *panel_to_kd35t133(struct drm_panel *panel)
+{
+ return container_of(panel, struct kd35t133, panel);
+}
+
+#define dsi_dcs_write_seq(dsi, cmd, seq...) do { \
+ static const u8 b[] = { cmd, seq }; \
+ int ret; \
+ ret = mipi_dsi_dcs_write_buffer(dsi, b, ARRAY_SIZE(b)); \
+ if (ret < 0) \
+ return ret; \
+ } while (0)
+
+static int kd35t133_init_sequence(struct kd35t133 *ctx)
+{
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+ struct device *dev = ctx->dev;
+
+ /*
+ * Init sequence was supplied by the panel vendor with minimal
+ * documentation.
+ */
+ dsi_dcs_write_seq(dsi, KD35T133_CMD_POSITIVEGAMMA,
+ 0x00, 0x13, 0x18, 0x04, 0x0f, 0x06, 0x3a, 0x56,
+ 0x4d, 0x03, 0x0a, 0x06, 0x30, 0x3e, 0x0f);
+ dsi_dcs_write_seq(dsi, KD35T133_CMD_NEGATIVEGAMMA,
+ 0x00, 0x13, 0x18, 0x01, 0x11, 0x06, 0x38, 0x34,
+ 0x4d, 0x06, 0x0d, 0x0b, 0x31, 0x37, 0x0f);
+ dsi_dcs_write_seq(dsi, KD35T133_CMD_POWERCONTROL1, 0x18, 0x17);
+ dsi_dcs_write_seq(dsi, KD35T133_CMD_POWERCONTROL2, 0x41);
+ dsi_dcs_write_seq(dsi, KD35T133_CMD_VCOMCONTROL, 0x00, 0x1a, 0x80);
+ dsi_dcs_write_seq(dsi, MIPI_DCS_SET_ADDRESS_MODE, 0x48);
+ dsi_dcs_write_seq(dsi, MIPI_DCS_SET_PIXEL_FORMAT, 0x55);
+ dsi_dcs_write_seq(dsi, KD35T133_CMD_INTERFACEMODECTRL, 0x00);
+ dsi_dcs_write_seq(dsi, KD35T133_CMD_FRAMERATECTRL, 0xa0);
+ dsi_dcs_write_seq(dsi, KD35T133_CMD_DISPLAYINVERSIONCTRL, 0x02);
+ dsi_dcs_write_seq(dsi, KD35T133_CMD_DISPLAYFUNCTIONCTRL,
+ 0x20, 0x02);
+ dsi_dcs_write_seq(dsi, KD35T133_CMD_SETIMAGEFUNCTION, 0x00);
+ dsi_dcs_write_seq(dsi, KD35T133_CMD_ADJUSTCONTROL3,
+ 0xa9, 0x51, 0x2c, 0x82);
+ mipi_dsi_dcs_write(dsi, MIPI_DCS_ENTER_INVERT_MODE, NULL, 0);
+
+ dev_dbg(dev, "Panel init sequence done\n");
+ return 0;
+}
+
+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);
+ int ret;
+
+ if (!ctx->prepared)
+ return 0;
+
+ ret = mipi_dsi_dcs_set_display_off(dsi);
+ if (ret < 0)
+ dev_err(ctx->dev, "failed to set display off: %d\n", ret);
+
+ 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;
+ }
+
+ gpiod_set_value_cansleep(ctx->reset_gpio, 1);
+
+ regulator_disable(ctx->iovcc);
+ regulator_disable(ctx->vdd);
+
+ ctx->prepared = false;
+
+ 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);
+ int ret;
+
+ if (ctx->prepared)
+ return 0;
+
+ 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;
+ }
+
+ ret = regulator_enable(ctx->iovcc);
+ if (ret < 0) {
+ dev_err(ctx->dev, "Failed to enable iovcc supply: %d\n", ret);
+ 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);
+
+ 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_iovcc;
+ }
+
+ msleep(250);
+
+ ret = kd35t133_init_sequence(ctx);
+ if (ret < 0) {
+ dev_err(ctx->dev, "Panel init sequence failed: %d\n", ret);
+ goto disable_iovcc;
+ }
+
+ 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_iovcc;
+ }
+
+ msleep(50);
+
+ ctx->prepared = true;
+
+ return 0;
+
+disable_iovcc:
+ regulator_disable(ctx->iovcc);
+disable_vdd:
+ regulator_disable(ctx->vdd);
+ return ret;
+}
+
+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);
+ /*
+ * 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 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_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(ctx->reset_gpio)) {
+ 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;
+
+ drm_panel_init(&ctx->panel, &dsi->dev, &kd35t133_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ ret = drm_panel_of_backlight(&ctx->panel);
+ if (ret)
+ return ret;
+
+ drm_panel_add(&ctx->panel);
+
+ ret = mipi_dsi_attach(dsi);
+ if (ret < 0) {
+ dev_err(dev, "mipi_dsi_attach failed: %d\n", ret);
+ drm_panel_remove(&ctx->panel);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void kd35t133_shutdown(struct mipi_dsi_device *dsi)
+{
+ struct kd35t133 *ctx = mipi_dsi_get_drvdata(dsi);
+ int ret;
+
+ ret = drm_panel_unprepare(&ctx->panel);
+ if (ret < 0)
+ dev_err(&dsi->dev, "Failed to unprepare panel: %d\n", ret);
+
+ ret = drm_panel_disable(&ctx->panel);
+ if (ret < 0)
+ dev_err(&dsi->dev, "Failed to disable panel: %d\n", ret);
+}
+
+static void kd35t133_remove(struct mipi_dsi_device *dsi)
+{
+ struct kd35t133 *ctx = mipi_dsi_get_drvdata(dsi);
+ int ret;
+
+ kd35t133_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 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,
+ .shutdown = kd35t133_shutdown,
+};
+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 000000000..76572c922
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-feixin-k101-im2ba02.c
@@ -0,0 +1,515 @@
+// 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/of_device.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_kzalloc(&dsi->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ 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");
+
+ drm_panel_init(&ctx->panel, &dsi->dev, &k101_im2ba02_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ 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 000000000..df493da50
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-feiyang-fy07024di26a30d.c
@@ -0,0 +1,262 @@
+// 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/of_device.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_kzalloc(&dsi->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ mipi_dsi_set_drvdata(dsi, ctx);
+ ctx->dsi = dsi;
+
+ drm_panel_init(&ctx->panel, &dsi->dev, &feiyang_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ ctx->dvdd = devm_regulator_get(&dsi->dev, "dvdd");
+ if (IS_ERR(ctx->dvdd))
+ 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-ilitek-ili9322.c b/drivers/gpu/drm/panel/panel-ilitek-ili9322.c
new file mode 100644
index 000000000..3dfafa585
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-ilitek-ili9322.c
@@ -0,0 +1,948 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Ilitek ILI9322 TFT LCD drm_panel driver.
+ *
+ * This panel can be configured to support:
+ * - 8-bit serial RGB interface
+ * - 24-bit parallel RGB interface
+ * - 8-bit ITU-R BT.601 interface
+ * - 8-bit ITU-R BT.656 interface
+ * - Up to 320RGBx240 dots resolution TFT LCD displays
+ * - Scaling, brightness and contrast
+ *
+ * The scaling means that the display accepts a 640x480 or 720x480
+ * input and rescales it to fit to the 320x240 display. So what we
+ * present to the system is something else than what comes out on the
+ * actual display.
+ *
+ * Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org>
+ * Derived from drivers/drm/gpu/panel/panel-samsung-ld9040.c
+ */
+
+#include <linux/bitops.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+
+#include <video/mipi_display.h>
+#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
+
+/*
+ * Voltage on the communication interface, from 0.7 (0x00)
+ * to 1.32 (0x1f) times the VREG1OUT voltage in 2% increments.
+ * 1.00 (0x0f) is the default.
+ */
+#define ILI9322_VCOM_AMP 0x01
+
+/*
+ * High voltage on the communication signals, from 0.37 (0x00) to
+ * 1.0 (0x3f) times the VREGOUT1 voltage in 1% increments.
+ * 0.83 (0x2e) is the default.
+ */
+#define ILI9322_VCOM_HIGH 0x02
+
+/*
+ * VREG1 voltage regulator from 3.6V (0x00) to 6.0V (0x18) in 0.1V
+ * increments. 5.4V (0x12) is the default. This is the reference
+ * voltage for the VCOM levels and the greyscale level.
+ */
+#define ILI9322_VREG1_VOLTAGE 0x03
+
+/* Describes the incoming signal */
+#define ILI9322_ENTRY 0x06
+/* 0 = right-to-left, 1 = left-to-right (default), horizontal flip */
+#define ILI9322_ENTRY_HDIR BIT(0)
+/* 0 = down-to-up, 1 = up-to-down (default), vertical flip */
+#define ILI9322_ENTRY_VDIR BIT(1)
+/* NTSC, PAL or autodetect */
+#define ILI9322_ENTRY_NTSC (0 << 2)
+#define ILI9322_ENTRY_PAL (1 << 2)
+#define ILI9322_ENTRY_AUTODETECT (3 << 2)
+/* Input format */
+#define ILI9322_ENTRY_SERIAL_RGB_THROUGH (0 << 4)
+#define ILI9322_ENTRY_SERIAL_RGB_ALIGNED (1 << 4)
+#define ILI9322_ENTRY_SERIAL_RGB_DUMMY_320X240 (2 << 4)
+#define ILI9322_ENTRY_SERIAL_RGB_DUMMY_360X240 (3 << 4)
+#define ILI9322_ENTRY_DISABLE_1 (4 << 4)
+#define ILI9322_ENTRY_PARALLEL_RGB_THROUGH (5 << 4)
+#define ILI9322_ENTRY_PARALLEL_RGB_ALIGNED (6 << 4)
+#define ILI9322_ENTRY_YUV_640Y_320CBCR_25_54_MHZ (7 << 4)
+#define ILI9322_ENTRY_YUV_720Y_360CBCR_27_MHZ (8 << 4)
+#define ILI9322_ENTRY_DISABLE_2 (9 << 4)
+#define ILI9322_ENTRY_ITU_R_BT_656_720X360 (10 << 4)
+#define ILI9322_ENTRY_ITU_R_BT_656_640X320 (11 << 4)
+
+/* Power control */
+#define ILI9322_POW_CTRL 0x07
+#define ILI9322_POW_CTRL_STB BIT(0) /* 0 = standby, 1 = normal */
+#define ILI9322_POW_CTRL_VGL BIT(1) /* 0 = off, 1 = on */
+#define ILI9322_POW_CTRL_VGH BIT(2) /* 0 = off, 1 = on */
+#define ILI9322_POW_CTRL_DDVDH BIT(3) /* 0 = off, 1 = on */
+#define ILI9322_POW_CTRL_VCOM BIT(4) /* 0 = off, 1 = on */
+#define ILI9322_POW_CTRL_VCL BIT(5) /* 0 = off, 1 = on */
+#define ILI9322_POW_CTRL_AUTO BIT(6) /* 0 = interactive, 1 = auto */
+#define ILI9322_POW_CTRL_STANDBY (ILI9322_POW_CTRL_VGL | \
+ ILI9322_POW_CTRL_VGH | \
+ ILI9322_POW_CTRL_DDVDH | \
+ ILI9322_POW_CTRL_VCL | \
+ ILI9322_POW_CTRL_AUTO | \
+ BIT(7))
+#define ILI9322_POW_CTRL_DEFAULT (ILI9322_POW_CTRL_STANDBY | \
+ ILI9322_POW_CTRL_STB)
+
+/* Vertical back porch bits 0..5 */
+#define ILI9322_VBP 0x08
+
+/* Horizontal back porch, 8 bits */
+#define ILI9322_HBP 0x09
+
+/*
+ * Polarity settings:
+ * 1 = positive polarity
+ * 0 = negative polarity
+ */
+#define ILI9322_POL 0x0a
+#define ILI9322_POL_DCLK BIT(0) /* 1 default */
+#define ILI9322_POL_HSYNC BIT(1) /* 0 default */
+#define ILI9322_POL_VSYNC BIT(2) /* 0 default */
+#define ILI9322_POL_DE BIT(3) /* 1 default */
+/*
+ * 0 means YCBCR are ordered Cb0,Y0,Cr0,Y1,Cb2,Y2,Cr2,Y3 (default)
+ * in RGB mode this means RGB comes in RGBRGB
+ * 1 means YCBCR are ordered Cr0,Y0,Cb0,Y1,Cr2,Y2,Cb2,Y3
+ * in RGB mode this means RGB comes in BGRBGR
+ */
+#define ILI9322_POL_YCBCR_MODE BIT(4)
+/* Formula A for YCbCR->RGB = 0, Formula B = 1 */
+#define ILI9322_POL_FORMULA BIT(5)
+/* Reverse polarity: 0 = 0..255, 1 = 255..0 */
+#define ILI9322_POL_REV BIT(6)
+
+#define ILI9322_IF_CTRL 0x0b
+#define ILI9322_IF_CTRL_HSYNC_VSYNC 0x00
+#define ILI9322_IF_CTRL_HSYNC_VSYNC_DE BIT(2)
+#define ILI9322_IF_CTRL_DE_ONLY BIT(3)
+#define ILI9322_IF_CTRL_SYNC_DISABLED (BIT(2) | BIT(3))
+#define ILI9322_IF_CTRL_LINE_INVERSION BIT(0) /* Not set means frame inv */
+
+#define ILI9322_GLOBAL_RESET 0x04
+#define ILI9322_GLOBAL_RESET_ASSERT 0x00 /* bit 0 = 0 -> reset */
+
+/*
+ * 4+4 bits of negative and positive gamma correction
+ * Upper nybble, bits 4-7 are negative gamma
+ * Lower nybble, bits 0-3 are positive gamma
+ */
+#define ILI9322_GAMMA_1 0x10
+#define ILI9322_GAMMA_2 0x11
+#define ILI9322_GAMMA_3 0x12
+#define ILI9322_GAMMA_4 0x13
+#define ILI9322_GAMMA_5 0x14
+#define ILI9322_GAMMA_6 0x15
+#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
+ * be selected by electronic straps on the display. However it is possible
+ * to select another mode or override the electronic default with this
+ * setting.
+ */
+enum ili9322_input {
+ ILI9322_INPUT_SRGB_THROUGH = 0x0,
+ ILI9322_INPUT_SRGB_ALIGNED = 0x1,
+ ILI9322_INPUT_SRGB_DUMMY_320X240 = 0x2,
+ ILI9322_INPUT_SRGB_DUMMY_360X240 = 0x3,
+ ILI9322_INPUT_DISABLED_1 = 0x4,
+ ILI9322_INPUT_PRGB_THROUGH = 0x5,
+ ILI9322_INPUT_PRGB_ALIGNED = 0x6,
+ ILI9322_INPUT_YUV_640X320_YCBCR = 0x7,
+ ILI9322_INPUT_YUV_720X360_YCBCR = 0x8,
+ ILI9322_INPUT_DISABLED_2 = 0x9,
+ ILI9322_INPUT_ITU_R_BT656_720X360_YCBCR = 0xa,
+ ILI9322_INPUT_ITU_R_BT656_640X320_YCBCR = 0xb,
+ ILI9322_INPUT_UNKNOWN = 0xc,
+};
+
+static const char * const ili9322_inputs[] = {
+ "8 bit serial RGB through",
+ "8 bit serial RGB aligned",
+ "8 bit serial RGB dummy 320x240",
+ "8 bit serial RGB dummy 360x240",
+ "disabled 1",
+ "24 bit parallel RGB through",
+ "24 bit parallel RGB aligned",
+ "24 bit YUV 640Y 320CbCr",
+ "24 bit YUV 720Y 360CbCr",
+ "disabled 2",
+ "8 bit ITU-R BT.656 720Y 360CbCr",
+ "8 bit ITU-R BT.656 640Y 320CbCr",
+};
+
+/**
+ * struct ili9322_config - the system specific ILI9322 configuration
+ * @width_mm: physical panel width [mm]
+ * @height_mm: physical panel height [mm]
+ * @flip_horizontal: flip the image horizontally (right-to-left scan)
+ * (only in RGB and YUV modes)
+ * @flip_vertical: flip the image vertically (down-to-up scan)
+ * (only in RGB and YUV modes)
+ * @input: the input/entry type used in this system, if this is set to
+ * ILI9322_INPUT_UNKNOWN the driver will try to figure it out by probing
+ * the hardware
+ * @vreg1out_mv: the output in microvolts for the VREGOUT1 regulator used
+ * to drive the physical display. Valid ranges are 3600 thru 6000 in 100
+ * microvolt increments. If not specified, hardware defaults will be
+ * used (4.5V).
+ * @vcom_high_percent: the percentage of VREGOUT1 used for the peak
+ * voltage on the communications link. Valid ranges are 37 thru 100
+ * percent. If not specified, hardware defaults will be used (91%).
+ * @vcom_amplitude_percent: the percentage of VREGOUT1 used for the
+ * peak-to-peak amplitude of the communcation signals to the physical
+ * display. Valid ranges are 70 thru 132 percent in increments if two
+ * percent. Odd percentages will be truncated. If not specified, hardware
+ * defaults will be used (114%).
+ * @dclk_active_high: data/pixel clock active high, data will be clocked
+ * in on the rising edge of the DCLK (this is usually the case).
+ * @syncmode: The synchronization mode, what sync signals are emitted.
+ * See the enum for details.
+ * @de_active_high: DE (data entry) is active high
+ * @hsync_active_high: HSYNC is active high
+ * @vsync_active_high: VSYNC is active high
+ * @gamma_corr_pos: a set of 8 nybbles describing positive
+ * gamma correction for voltages V1 thru V8. Valid range 0..15
+ * @gamma_corr_neg: a set of 8 nybbles describing negative
+ * gamma correction for voltages V1 thru V8. Valid range 0..15
+ *
+ * These adjust what grayscale voltage will be output for input data V1 = 0,
+ * V2 = 16, V3 = 48, V4 = 96, V5 = 160, V6 = 208, V7 = 240 and V8 = 255.
+ * The curve is shaped like this:
+ *
+ * ^
+ * | V8
+ * | V7
+ * | V6
+ * | V5
+ * | V4
+ * | V3
+ * | V2
+ * | V1
+ * +----------------------------------------------------------->
+ * 0 16 48 96 160 208 240 255
+ *
+ * The negative and postive gamma values adjust the V1 thru V8 up/down
+ * according to the datasheet specifications. This is a property of the
+ * physical display connected to the display controller and may vary.
+ * If defined, both arrays must be supplied in full. If the properties
+ * are not supplied, hardware defaults will be used.
+ */
+struct ili9322_config {
+ u32 width_mm;
+ u32 height_mm;
+ bool flip_horizontal;
+ bool flip_vertical;
+ enum ili9322_input input;
+ u32 vreg1out_mv;
+ u32 vcom_high_percent;
+ u32 vcom_amplitude_percent;
+ bool dclk_active_high;
+ bool de_active_high;
+ bool hsync_active_high;
+ bool vsync_active_high;
+ u8 syncmode;
+ u8 gamma_corr_pos[8];
+ u8 gamma_corr_neg[8];
+};
+
+struct ili9322 {
+ struct device *dev;
+ const struct ili9322_config *conf;
+ struct drm_panel panel;
+ struct regmap *regmap;
+ struct regulator_bulk_data supplies[3];
+ struct gpio_desc *reset_gpio;
+ enum ili9322_input input;
+ struct videomode vm;
+ u8 gamma[8];
+ u8 vreg1out;
+ u8 vcom_high;
+ u8 vcom_amplitude;
+};
+
+static inline struct ili9322 *panel_to_ili9322(struct drm_panel *panel)
+{
+ return container_of(panel, struct ili9322, panel);
+}
+
+static int ili9322_regmap_spi_write(void *context, const void *data,
+ size_t count)
+{
+ struct device *dev = context;
+ struct spi_device *spi = to_spi_device(dev);
+ u8 buf[2];
+
+ /* Clear bit 7 to write */
+ memcpy(buf, data, 2);
+ buf[0] &= ~0x80;
+
+ dev_dbg(dev, "WRITE: %02x %02x\n", buf[0], buf[1]);
+ return spi_write_then_read(spi, buf, 2, NULL, 0);
+}
+
+static int ili9322_regmap_spi_read(void *context, const void *reg,
+ size_t reg_size, void *val, size_t val_size)
+{
+ struct device *dev = context;
+ struct spi_device *spi = to_spi_device(dev);
+ u8 buf[1];
+
+ /* Set bit 7 to 1 to read */
+ memcpy(buf, reg, 1);
+ dev_dbg(dev, "READ: %02x reg size = %zu, val size = %zu\n",
+ buf[0], reg_size, val_size);
+ buf[0] |= 0x80;
+
+ return spi_write_then_read(spi, buf, 1, val, 1);
+}
+
+static 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 */
+ if (reg == 0x00)
+ return false;
+ return true;
+}
+
+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,
+ .writeable_reg = ili9322_writeable_reg,
+};
+
+static int ili9322_init(struct drm_panel *panel, struct ili9322 *ili)
+{
+ u8 reg;
+ int ret;
+ int i;
+
+ /* Reset display */
+ ret = regmap_write(ili->regmap, ILI9322_GLOBAL_RESET,
+ ILI9322_GLOBAL_RESET_ASSERT);
+ if (ret) {
+ dev_err(ili->dev, "can't issue GRESET (%d)\n", ret);
+ return ret;
+ }
+
+ /* Set up the main voltage regulator */
+ if (ili->vreg1out != U8_MAX) {
+ ret = regmap_write(ili->regmap, ILI9322_VREG1_VOLTAGE,
+ ili->vreg1out);
+ if (ret) {
+ dev_err(ili->dev, "can't set up VREG1OUT (%d)\n", ret);
+ return ret;
+ }
+ }
+
+ if (ili->vcom_amplitude != U8_MAX) {
+ ret = regmap_write(ili->regmap, ILI9322_VCOM_AMP,
+ ili->vcom_amplitude);
+ if (ret) {
+ dev_err(ili->dev,
+ "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,
+ ili->vcom_high);
+ if (ret) {
+ 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++) {
+ ret = regmap_write(ili->regmap, ILI9322_GAMMA_1 + i,
+ ili->gamma[i]);
+ if (ret) {
+ dev_err(ili->dev,
+ "can't write gamma V%d to 0x%02x (%d)\n",
+ i + 1, ILI9322_GAMMA_1 + i, ret);
+ return ret;
+ }
+ }
+
+ /*
+ * Polarity and inverted color order for RGB input.
+ * None of this applies in the BT.656 mode.
+ */
+ reg = 0;
+ if (ili->conf->dclk_active_high)
+ reg = ILI9322_POL_DCLK;
+ if (ili->conf->de_active_high)
+ reg |= ILI9322_POL_DE;
+ if (ili->conf->hsync_active_high)
+ reg |= ILI9322_POL_HSYNC;
+ if (ili->conf->vsync_active_high)
+ reg |= ILI9322_POL_VSYNC;
+ ret = regmap_write(ili->regmap, ILI9322_POL, reg);
+ if (ret) {
+ dev_err(ili->dev, "can't write POL register (%d)\n", ret);
+ return ret;
+ }
+
+ /*
+ * Set up interface control.
+ * This is not used in the BT.656 mode (no H/Vsync or DE signals).
+ */
+ reg = ili->conf->syncmode;
+ reg |= ILI9322_IF_CTRL_LINE_INVERSION;
+ ret = regmap_write(ili->regmap, ILI9322_IF_CTRL, reg);
+ if (ret) {
+ dev_err(ili->dev, "can't write IF CTRL register (%d)\n", ret);
+ return ret;
+ }
+
+ /* Set up the input mode */
+ reg = (ili->input << 4);
+ /* These are inverted, setting to 1 is the default, clearing flips */
+ if (!ili->conf->flip_horizontal)
+ reg |= ILI9322_ENTRY_HDIR;
+ if (!ili->conf->flip_vertical)
+ reg |= ILI9322_ENTRY_VDIR;
+ reg |= ILI9322_ENTRY_AUTODETECT;
+ ret = regmap_write(ili->regmap, ILI9322_ENTRY, reg);
+ if (ret) {
+ dev_err(ili->dev, "can't write ENTRY reg (%d)\n", ret);
+ return ret;
+ }
+ dev_info(ili->dev, "display is in %s mode, syncmode %02x\n",
+ ili9322_inputs[ili->input],
+ ili->conf->syncmode);
+
+ dev_info(ili->dev, "initialized display\n");
+
+ return 0;
+}
+
+/*
+ * This power-on sequence if from the datasheet, page 57.
+ */
+static int ili9322_power_on(struct ili9322 *ili)
+{
+ int ret;
+
+ /* Assert RESET */
+ gpiod_set_value(ili->reset_gpio, 1);
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(ili->supplies), ili->supplies);
+ if (ret < 0) {
+ dev_err(ili->dev, "unable to enable regulators\n");
+ return ret;
+ }
+ msleep(20);
+
+ /* De-assert RESET */
+ gpiod_set_value(ili->reset_gpio, 0);
+
+ msleep(10);
+
+ return 0;
+}
+
+static int ili9322_power_off(struct ili9322 *ili)
+{
+ return regulator_bulk_disable(ARRAY_SIZE(ili->supplies), ili->supplies);
+}
+
+static int ili9322_disable(struct drm_panel *panel)
+{
+ struct ili9322 *ili = panel_to_ili9322(panel);
+ int ret;
+
+ ret = regmap_write(ili->regmap, ILI9322_POW_CTRL,
+ ILI9322_POW_CTRL_STANDBY);
+ if (ret) {
+ dev_err(ili->dev, "unable to go to standby mode\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ili9322_unprepare(struct drm_panel *panel)
+{
+ struct ili9322 *ili = panel_to_ili9322(panel);
+
+ return ili9322_power_off(ili);
+}
+
+static int ili9322_prepare(struct drm_panel *panel)
+{
+ struct ili9322 *ili = panel_to_ili9322(panel);
+ int ret;
+
+ ret = ili9322_power_on(ili);
+ if (ret < 0)
+ return ret;
+
+ ret = ili9322_init(panel, ili);
+ if (ret < 0)
+ ili9322_unprepare(panel);
+
+ return ret;
+}
+
+static int ili9322_enable(struct drm_panel *panel)
+{
+ struct ili9322 *ili = panel_to_ili9322(panel);
+ int ret;
+
+ ret = regmap_write(ili->regmap, ILI9322_POW_CTRL,
+ ILI9322_POW_CTRL_DEFAULT);
+ if (ret) {
+ dev_err(ili->dev, "unable to enable panel\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+/* Serial RGB modes */
+static const struct drm_display_mode srgb_320x240_mode = {
+ .clock = 24535,
+ .hdisplay = 320,
+ .hsync_start = 320 + 359,
+ .hsync_end = 320 + 359 + 1,
+ .htotal = 320 + 359 + 1 + 241,
+ .vdisplay = 240,
+ .vsync_start = 240 + 4,
+ .vsync_end = 240 + 4 + 1,
+ .vtotal = 262,
+ .flags = 0,
+};
+
+static const struct drm_display_mode srgb_360x240_mode = {
+ .clock = 27000,
+ .hdisplay = 360,
+ .hsync_start = 360 + 35,
+ .hsync_end = 360 + 35 + 1,
+ .htotal = 360 + 35 + 1 + 241,
+ .vdisplay = 240,
+ .vsync_start = 240 + 21,
+ .vsync_end = 240 + 21 + 1,
+ .vtotal = 262,
+ .flags = 0,
+};
+
+/* This is the only mode listed for parallel RGB in the datasheet */
+static const struct drm_display_mode prgb_320x240_mode = {
+ .clock = 64000,
+ .hdisplay = 320,
+ .hsync_start = 320 + 38,
+ .hsync_end = 320 + 38 + 1,
+ .htotal = 320 + 38 + 1 + 50,
+ .vdisplay = 240,
+ .vsync_start = 240 + 4,
+ .vsync_end = 240 + 4 + 1,
+ .vtotal = 262,
+ .flags = 0,
+};
+
+/* YUV modes */
+static const struct drm_display_mode yuv_640x320_mode = {
+ .clock = 24540,
+ .hdisplay = 640,
+ .hsync_start = 640 + 252,
+ .hsync_end = 640 + 252 + 1,
+ .htotal = 640 + 252 + 1 + 28,
+ .vdisplay = 320,
+ .vsync_start = 320 + 4,
+ .vsync_end = 320 + 4 + 1,
+ .vtotal = 320 + 4 + 1 + 18,
+ .flags = 0,
+};
+
+static const struct drm_display_mode yuv_720x360_mode = {
+ .clock = 27000,
+ .hdisplay = 720,
+ .hsync_start = 720 + 252,
+ .hsync_end = 720 + 252 + 1,
+ .htotal = 720 + 252 + 1 + 24,
+ .vdisplay = 360,
+ .vsync_start = 360 + 4,
+ .vsync_end = 360 + 4 + 1,
+ .vtotal = 360 + 4 + 1 + 18,
+ .flags = 0,
+};
+
+/* BT.656 VGA mode, 640x480 */
+static const struct drm_display_mode itu_r_bt_656_640_mode = {
+ .clock = 24540,
+ .hdisplay = 640,
+ .hsync_start = 640 + 3,
+ .hsync_end = 640 + 3 + 1,
+ .htotal = 640 + 3 + 1 + 272,
+ .vdisplay = 480,
+ .vsync_start = 480 + 4,
+ .vsync_end = 480 + 4 + 1,
+ .vtotal = 500,
+ .flags = 0,
+};
+
+/* BT.656 D1 mode 720x480 */
+static const struct drm_display_mode itu_r_bt_656_720_mode = {
+ .clock = 27000,
+ .hdisplay = 720,
+ .hsync_start = 720 + 3,
+ .hsync_end = 720 + 3 + 1,
+ .htotal = 720 + 3 + 1 + 272,
+ .vdisplay = 480,
+ .vsync_start = 480 + 4,
+ .vsync_end = 480 + 4 + 1,
+ .vtotal = 500,
+ .flags = 0,
+};
+
+static int ili9322_get_modes(struct drm_panel *panel,
+ struct drm_connector *connector)
+{
+ struct ili9322 *ili = panel_to_ili9322(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->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(drm, &srgb_320x240_mode);
+ break;
+ case ILI9322_INPUT_SRGB_DUMMY_360X240:
+ mode = drm_mode_duplicate(drm, &srgb_360x240_mode);
+ break;
+ case ILI9322_INPUT_PRGB_THROUGH:
+ case ILI9322_INPUT_PRGB_ALIGNED:
+ mode = drm_mode_duplicate(drm, &prgb_320x240_mode);
+ break;
+ case ILI9322_INPUT_YUV_640X320_YCBCR:
+ mode = drm_mode_duplicate(drm, &yuv_640x320_mode);
+ break;
+ case ILI9322_INPUT_YUV_720X360_YCBCR:
+ mode = drm_mode_duplicate(drm, &yuv_720x360_mode);
+ break;
+ case ILI9322_INPUT_ITU_R_BT656_720X360_YCBCR:
+ mode = drm_mode_duplicate(drm, &itu_r_bt_656_720_mode);
+ break;
+ case ILI9322_INPUT_ITU_R_BT656_640X320_YCBCR:
+ mode = drm_mode_duplicate(drm, &itu_r_bt_656_640_mode);
+ break;
+ default:
+ mode = NULL;
+ break;
+ }
+ if (!mode) {
+ dev_err(panel->dev, "bad mode or failed to add mode\n");
+ return -EINVAL;
+ }
+ drm_mode_set_name(mode);
+ /*
+ * This is the preferred mode because most people are going
+ * to want to use the display with VGA type graphics.
+ */
+ mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+
+ /* Set up the polarity */
+ if (ili->conf->hsync_active_high)
+ mode->flags |= DRM_MODE_FLAG_PHSYNC;
+ else
+ mode->flags |= DRM_MODE_FLAG_NHSYNC;
+ if (ili->conf->vsync_active_high)
+ mode->flags |= DRM_MODE_FLAG_PVSYNC;
+ else
+ mode->flags |= DRM_MODE_FLAG_NVSYNC;
+
+ mode->width_mm = ili->conf->width_mm;
+ mode->height_mm = ili->conf->height_mm;
+ drm_mode_probed_add(connector, mode);
+
+ return 1; /* Number of modes */
+}
+
+static const struct drm_panel_funcs ili9322_drm_funcs = {
+ .disable = ili9322_disable,
+ .unprepare = ili9322_unprepare,
+ .prepare = ili9322_prepare,
+ .enable = ili9322_enable,
+ .get_modes = ili9322_get_modes,
+};
+
+static int ili9322_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct ili9322 *ili;
+ const struct regmap_config *regmap_config;
+ u8 gamma;
+ u32 val;
+ int ret;
+ int i;
+
+ ili = devm_kzalloc(dev, sizeof(struct ili9322), GFP_KERNEL);
+ if (!ili)
+ return -ENOMEM;
+
+ spi_set_drvdata(spi, ili);
+
+ ili->dev = dev;
+
+ /*
+ * Every new incarnation of this display must have a unique
+ * data entry for the system in this driver.
+ */
+ ili->conf = of_device_get_match_data(dev);
+ if (!ili->conf) {
+ dev_err(dev, "missing device configuration\n");
+ return -ENODEV;
+ }
+
+ val = ili->conf->vreg1out_mv;
+ if (!val) {
+ /* Default HW value, do not touch (should be 4.5V) */
+ ili->vreg1out = U8_MAX;
+ } else {
+ if (val < 3600) {
+ dev_err(dev, "too low VREG1OUT\n");
+ return -EINVAL;
+ }
+ if (val > 6000) {
+ dev_err(dev, "too high VREG1OUT\n");
+ return -EINVAL;
+ }
+ if ((val % 100) != 0) {
+ dev_err(dev, "VREG1OUT is no even 100 microvolt\n");
+ return -EINVAL;
+ }
+ val -= 3600;
+ val /= 100;
+ dev_dbg(dev, "VREG1OUT = 0x%02x\n", val);
+ ili->vreg1out = val;
+ }
+
+ val = ili->conf->vcom_high_percent;
+ if (!val) {
+ /* Default HW value, do not touch (should be 91%) */
+ ili->vcom_high = U8_MAX;
+ } else {
+ if (val < 37) {
+ dev_err(dev, "too low VCOM high\n");
+ return -EINVAL;
+ }
+ if (val > 100) {
+ dev_err(dev, "too high VCOM high\n");
+ return -EINVAL;
+ }
+ val -= 37;
+ dev_dbg(dev, "VCOM high = 0x%02x\n", val);
+ ili->vcom_high = val;
+ }
+
+ val = ili->conf->vcom_amplitude_percent;
+ if (!val) {
+ /* Default HW value, do not touch (should be 114%) */
+ ili->vcom_high = U8_MAX;
+ } else {
+ if (val < 70) {
+ dev_err(dev, "too low VCOM amplitude\n");
+ return -EINVAL;
+ }
+ if (val > 132) {
+ dev_err(dev, "too high VCOM amplitude\n");
+ return -EINVAL;
+ }
+ val -= 70;
+ val >>= 1; /* Increments of 2% */
+ dev_dbg(dev, "VCOM amplitude = 0x%02x\n", val);
+ ili->vcom_amplitude = val;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(ili->gamma); i++) {
+ val = ili->conf->gamma_corr_neg[i];
+ if (val > 15) {
+ dev_err(dev, "negative gamma %u > 15, capping\n", val);
+ val = 15;
+ }
+ gamma = val << 4;
+ val = ili->conf->gamma_corr_pos[i];
+ if (val > 15) {
+ dev_err(dev, "positive gamma %u > 15, capping\n", val);
+ val = 15;
+ }
+ gamma |= val;
+ ili->gamma[i] = gamma;
+ dev_dbg(dev, "gamma V%d: 0x%02x\n", i + 1, gamma);
+ }
+
+ ili->supplies[0].supply = "vcc"; /* 2.7-3.6 V */
+ ili->supplies[1].supply = "iovcc"; /* 1.65-3.6V */
+ ili->supplies[2].supply = "vci"; /* 2.7-3.6V */
+ ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ili->supplies),
+ ili->supplies);
+ if (ret < 0)
+ return ret;
+ ret = regulator_set_voltage(ili->supplies[0].consumer,
+ 2700000, 3600000);
+ if (ret)
+ return ret;
+ ret = regulator_set_voltage(ili->supplies[1].consumer,
+ 1650000, 3600000);
+ if (ret)
+ return ret;
+ ret = regulator_set_voltage(ili->supplies[2].consumer,
+ 2700000, 3600000);
+ if (ret)
+ return ret;
+
+ ili->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(ili->reset_gpio)) {
+ dev_err(dev, "failed to get RESET GPIO\n");
+ return PTR_ERR(ili->reset_gpio);
+ }
+
+ spi->bits_per_word = 8;
+ ret = spi_setup(spi);
+ if (ret < 0) {
+ dev_err(dev, "spi setup failed.\n");
+ return ret;
+ }
+ regmap_config = &ili9322_regmap_config;
+ ili->regmap = devm_regmap_init(dev, &ili9322_regmap_bus, dev,
+ regmap_config);
+ if (IS_ERR(ili->regmap)) {
+ dev_err(dev, "failed to allocate register map\n");
+ return PTR_ERR(ili->regmap);
+ }
+
+ ret = regmap_read(ili->regmap, ILI9322_CHIP_ID, &val);
+ if (ret) {
+ dev_err(dev, "can't get chip ID (%d)\n", ret);
+ return ret;
+ }
+ if (val != ILI9322_CHIP_ID_MAGIC) {
+ dev_err(dev, "chip ID 0x%0x2, expected 0x%02x\n", val,
+ ILI9322_CHIP_ID_MAGIC);
+ return -ENODEV;
+ }
+
+ /* Probe the system to find the display setting */
+ if (ili->conf->input == ILI9322_INPUT_UNKNOWN) {
+ ret = regmap_read(ili->regmap, ILI9322_ENTRY, &val);
+ if (ret) {
+ dev_err(dev, "can't get entry setting (%d)\n", ret);
+ return ret;
+ }
+ /* Input enum corresponds to HW setting */
+ ili->input = (val >> 4) & 0x0f;
+ if (ili->input >= ILI9322_INPUT_UNKNOWN)
+ ili->input = ILI9322_INPUT_UNKNOWN;
+ } else {
+ ili->input = ili->conf->input;
+ }
+
+ drm_panel_init(&ili->panel, dev, &ili9322_drm_funcs,
+ DRM_MODE_CONNECTOR_DPI);
+
+ drm_panel_add(&ili->panel);
+
+ return 0;
+}
+
+static void ili9322_remove(struct spi_device *spi)
+{
+ struct ili9322 *ili = spi_get_drvdata(spi);
+
+ ili9322_power_off(ili);
+ drm_panel_remove(&ili->panel);
+}
+
+/*
+ * The D-Link DIR-685 panel is marked LM918A01-1A SY-B4-091116-E0199
+ */
+static const struct ili9322_config ili9322_dir_685 = {
+ .width_mm = 65,
+ .height_mm = 50,
+ .input = ILI9322_INPUT_ITU_R_BT656_640X320_YCBCR,
+ .vreg1out_mv = 4600,
+ .vcom_high_percent = 91,
+ .vcom_amplitude_percent = 114,
+ .syncmode = ILI9322_IF_CTRL_SYNC_DISABLED,
+ .dclk_active_high = true,
+ .gamma_corr_neg = { 0xa, 0x5, 0x7, 0x7, 0x7, 0x5, 0x1, 0x6 },
+ .gamma_corr_pos = { 0x7, 0x7, 0x3, 0x2, 0x3, 0x5, 0x7, 0x2 },
+};
+
+static const struct of_device_id ili9322_of_match[] = {
+ {
+ .compatible = "dlink,dir-685-panel",
+ .data = &ili9322_dir_685,
+ },
+ {
+ .compatible = "ilitek,ili9322",
+ .data = NULL,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ili9322_of_match);
+
+static struct spi_driver ili9322_driver = {
+ .probe = ili9322_probe,
+ .remove = ili9322_remove,
+ .driver = {
+ .name = "panel-ilitek-ili9322",
+ .of_match_table = ili9322_of_match,
+ },
+};
+module_spi_driver(ili9322_driver);
+
+MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
+MODULE_DESCRIPTION("ILI9322 LCD panel driver");
+MODULE_LICENSE("GPL v2");
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 000000000..39dc40cf6
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-ilitek-ili9341.c
@@ -0,0 +1,794 @@
+// 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"
+ *
+ * For only-dbi part, copy from David's code (drm/tiny/ili9341.c)
+ * Copyright 2018 David Lechner <david@lechnology.com>
+ */
+
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/of_device.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_fb_helper.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: TODO: need comments for this register */
+ u8 power_b[ILI9341_POWER_B_LEN];
+ /* power_seq: TODO: need comments for this register */
+ u8 power_seq[ILI9341_POWER_SEQ_LEN];
+ /* dtca: TODO: need comments for this register */
+ u8 dtca[ILI9341_DTCA_LEN];
+ /* dtcb: TODO: need comments for this register */
+ u8 dtcb[ILI9341_DTCB_LEN];
+ /* power_a: TODO: need comments for this register */
+ u8 power_a[ILI9341_POWER_A_LEN];
+ /* frc: Frame Rate Control (In Normal Mode/Full Colors) (B1h) */
+ u8 frc[ILI9341_FRC_LEN];
+ /* prc: TODO: need comments for this register */
+ 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: TODO: need comments for this register */
+ 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 {
+ struct device *dev;
+ 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 ret;
+}
+
+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 void ili9341_dbi_enable(struct drm_simple_display_pipe *pipe,
+ struct drm_crtc_state *crtc_state,
+ struct drm_plane_state *plane_state)
+{
+ struct mipi_dbi_dev *dbidev = drm_to_mipi_dbi_dev(pipe->crtc.dev);
+ struct mipi_dbi *dbi = &dbidev->dbi;
+ u8 addr_mode;
+ int ret, idx;
+
+ if (!drm_dev_enter(pipe->crtc.dev, &idx))
+ return;
+
+ ret = mipi_dbi_poweron_conditional_reset(dbidev);
+ if (ret < 0)
+ goto out_exit;
+ if (ret == 1)
+ goto out_enable;
+
+ mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_OFF);
+
+ mipi_dbi_command(dbi, ILI9341_POWERB, 0x00, 0xc1, 0x30);
+ mipi_dbi_command(dbi, ILI9341_POWER_SEQ, 0x64, 0x03, 0x12, 0x81);
+ mipi_dbi_command(dbi, ILI9341_DTCA, 0x85, 0x00, 0x78);
+ mipi_dbi_command(dbi, ILI9341_POWERA, 0x39, 0x2c, 0x00, 0x34, 0x02);
+ mipi_dbi_command(dbi, ILI9341_PRC, ILI9341_DBI_PRC_NORMAL);
+ mipi_dbi_command(dbi, ILI9341_DTCB, 0x00, 0x00);
+
+ /* Power Control */
+ mipi_dbi_command(dbi, ILI9341_POWER1, ILI9341_DBI_VCOMH_4P6V);
+ mipi_dbi_command(dbi, ILI9341_POWER2, ILI9341_DBI_PWR_2_DEFAULT);
+ /* VCOM */
+ mipi_dbi_command(dbi, ILI9341_VCOM1, ILI9341_DBI_VCOM_1_VMH_4P25V,
+ ILI9341_DBI_VCOM_1_VML_1P5V);
+ mipi_dbi_command(dbi, ILI9341_VCOM2, ILI9341_DBI_VCOM_2_DEC_58);
+
+ /* Memory Access Control */
+ mipi_dbi_command(dbi, MIPI_DCS_SET_PIXEL_FORMAT,
+ MIPI_DCS_PIXEL_FMT_16BIT);
+
+ /* Frame Rate */
+ mipi_dbi_command(dbi, ILI9341_FRC, ILI9341_DBI_FRC_DIVA & 0x03,
+ ILI9341_DBI_FRC_RTNA & 0x1f);
+
+ /* Gamma */
+ mipi_dbi_command(dbi, ILI9341_3GAMMA_EN, 0x00);
+ mipi_dbi_command(dbi, MIPI_DCS_SET_GAMMA_CURVE, ILI9341_GAMMA_CURVE_1);
+ mipi_dbi_command(dbi, ILI9341_PGAMMA,
+ 0x0f, 0x31, 0x2b, 0x0c, 0x0e, 0x08, 0x4e, 0xf1,
+ 0x37, 0x07, 0x10, 0x03, 0x0e, 0x09, 0x00);
+ mipi_dbi_command(dbi, ILI9341_NGAMMA,
+ 0x00, 0x0e, 0x14, 0x03, 0x11, 0x07, 0x31, 0xc1,
+ 0x48, 0x08, 0x0f, 0x0c, 0x31, 0x36, 0x0f);
+
+ /* DDRAM */
+ mipi_dbi_command(dbi, ILI9341_ETMOD, ILI9341_DBI_EMS_GAS |
+ ILI9341_DBI_EMS_DTS |
+ ILI9341_DBI_EMS_GON);
+
+ /* Display */
+ mipi_dbi_command(dbi, ILI9341_DFC, 0x08, 0x82, 0x27, 0x00);
+ mipi_dbi_command(dbi, MIPI_DCS_EXIT_SLEEP_MODE);
+ msleep(100);
+
+ mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_ON);
+ msleep(100);
+
+out_enable:
+ switch (dbidev->rotation) {
+ default:
+ addr_mode = ILI9341_MADCTL_MX;
+ break;
+ case 90:
+ addr_mode = ILI9341_MADCTL_MV;
+ break;
+ case 180:
+ addr_mode = ILI9341_MADCTL_MY;
+ break;
+ case 270:
+ addr_mode = ILI9341_MADCTL_MV | ILI9341_MADCTL_MY |
+ ILI9341_MADCTL_MX;
+ break;
+ }
+
+ addr_mode |= ILI9341_MADCTL_BGR;
+ mipi_dbi_command(dbi, MIPI_DCS_SET_ADDRESS_MODE, addr_mode);
+ mipi_dbi_enable_flush(dbidev, crtc_state, plane_state);
+ drm_info(&dbidev->drm, "Initialized display serial interface\n");
+out_exit:
+ drm_dev_exit(idx);
+}
+
+static const struct drm_simple_display_pipe_funcs ili9341_dbi_funcs = {
+ .mode_valid = mipi_dbi_pipe_mode_valid,
+ .enable = ili9341_dbi_enable,
+ .disable = mipi_dbi_pipe_disable,
+ .update = mipi_dbi_pipe_update,
+ .prepare_fb = drm_gem_simple_display_pipe_prepare_fb,
+};
+
+static const struct drm_display_mode ili9341_dbi_mode = {
+ DRM_SIMPLE_MODE(240, 320, 37, 49),
+};
+
+DEFINE_DRM_GEM_DMA_FOPS(ili9341_dbi_fops);
+
+static struct drm_driver ili9341_dbi_driver = {
+ .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
+ .fops = &ili9341_dbi_fops,
+ DRM_GEM_DMA_DRIVER_OPS_VMAP,
+ .debugfs_init = mipi_dbi_debugfs_init,
+ .name = "ili9341",
+ .desc = "Ilitek ILI9341",
+ .date = "20210716",
+ .major = 1,
+ .minor = 0,
+};
+
+static int ili9341_dbi_probe(struct spi_device *spi, struct gpio_desc *dc,
+ struct gpio_desc *reset)
+{
+ struct device *dev = &spi->dev;
+ struct mipi_dbi_dev *dbidev;
+ struct mipi_dbi *dbi;
+ struct drm_device *drm;
+ struct regulator *vcc;
+ u32 rotation = 0;
+ int ret;
+
+ vcc = devm_regulator_get_optional(dev, "vcc");
+ if (IS_ERR(vcc)) {
+ dev_err(dev, "get optional vcc failed\n");
+ vcc = NULL;
+ }
+
+ dbidev = devm_drm_dev_alloc(dev, &ili9341_dbi_driver,
+ struct mipi_dbi_dev, drm);
+ if (IS_ERR(dbidev))
+ return PTR_ERR(dbidev);
+
+ dbi = &dbidev->dbi;
+ drm = &dbidev->drm;
+ dbi->reset = reset;
+ dbidev->regulator = vcc;
+
+ drm_mode_config_init(drm);
+
+ dbidev->backlight = devm_of_find_backlight(dev);
+ if (IS_ERR(dbidev->backlight))
+ return PTR_ERR(dbidev->backlight);
+
+ device_property_read_u32(dev, "rotation", &rotation);
+
+ ret = mipi_dbi_spi_init(spi, dbi, dc);
+ if (ret)
+ return ret;
+
+ ret = mipi_dbi_dev_init(dbidev, &ili9341_dbi_funcs,
+ &ili9341_dbi_mode, rotation);
+ if (ret)
+ return ret;
+
+ drm_mode_config_reset(drm);
+
+ ret = drm_dev_register(drm, 0);
+ if (ret)
+ return ret;
+
+ spi_set_drvdata(spi, drm);
+
+ drm_fbdev_generic_setup(drm, 0);
+
+ return 0;
+}
+
+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_kzalloc(dev, sizeof(struct ili9341), GFP_KERNEL);
+ if (!ili)
+ return -ENOMEM;
+
+ 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 = of_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_init(&ili->panel, dev, &ili9341_dpi_funcs,
+ DRM_MODE_CONNECTOR_DPI);
+ 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;
+ const struct spi_device_id *id = spi_get_device_id(spi);
+
+ reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(reset))
+ dev_err(dev, "Failed to get gpio 'reset'\n");
+
+ dc = devm_gpiod_get_optional(dev, "dc", GPIOD_OUT_LOW);
+ if (IS_ERR(dc))
+ dev_err(dev, "Failed to get gpio 'dc'\n");
+
+ if (!strcmp(id->name, "sf-tc240t-9370-t"))
+ return ili9341_dpi_probe(spi, dc, reset);
+ else if (!strcmp(id->name, "yx240qv29"))
+ return ili9341_dbi_probe(spi, dc, reset);
+
+ return -1;
+}
+
+static void ili9341_remove(struct spi_device *spi)
+{
+ const struct spi_device_id *id = spi_get_device_id(spi);
+ struct ili9341 *ili = spi_get_drvdata(spi);
+ struct drm_device *drm = spi_get_drvdata(spi);
+
+ if (!strcmp(id->name, "sf-tc240t-9370-t")) {
+ ili9341_dpi_power_off(ili);
+ drm_panel_remove(&ili->panel);
+ } else if (!strcmp(id->name, "yx240qv29")) {
+ drm_dev_unplug(drm);
+ drm_atomic_helper_shutdown(drm);
+ }
+}
+
+static void ili9341_shutdown(struct spi_device *spi)
+{
+ const struct spi_device_id *id = spi_get_device_id(spi);
+
+ if (!strcmp(id->name, "yx240qv29"))
+ drm_atomic_helper_shutdown(spi_get_drvdata(spi));
+}
+
+static const struct of_device_id ili9341_of_match[] = {
+ {
+ .compatible = "st,sf-tc240t-9370-t",
+ .data = &ili9341_stm32f429_disco_data,
+ },
+ {
+ /* porting from tiny/ili9341.c
+ * for original mipi dbi compitable
+ */
+ .compatible = "adafruit,yx240qv29",
+ .data = NULL,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ili9341_of_match);
+
+static const struct spi_device_id ili9341_id[] = {
+ { "yx240qv29", 0 },
+ { "sf-tc240t-9370-t", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, ili9341_id);
+
+static struct spi_driver ili9341_driver = {
+ .probe = ili9341_probe,
+ .remove = ili9341_remove,
+ .shutdown = ili9341_shutdown,
+ .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-ili9881c.c b/drivers/gpu/drm/panel/panel-ilitek-ili9881c.c
new file mode 100644
index 000000000..cbb68caa3
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-ilitek-ili9881c.c
@@ -0,0 +1,976 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2017-2018, Bootlin
+ */
+
+#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_device.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>
+
+enum ili9881c_op {
+ ILI9881C_SWITCH_PAGE,
+ ILI9881C_COMMAND,
+};
+
+struct ili9881c_instr {
+ enum ili9881c_op op;
+
+ union arg {
+ struct cmd {
+ u8 cmd;
+ u8 data;
+ } cmd;
+ u8 page;
+ } 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;
+};
+
+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;
+};
+
+#define ILI9881C_SWITCH_PAGE_INSTR(_page) \
+ { \
+ .op = ILI9881C_SWITCH_PAGE, \
+ .arg = { \
+ .page = (_page), \
+ }, \
+ }
+
+#define ILI9881C_COMMAND_INSTR(_cmd, _data) \
+ { \
+ .op = ILI9881C_COMMAND, \
+ .arg = { \
+ .cmd = { \
+ .cmd = (_cmd), \
+ .data = (_data), \
+ }, \
+ }, \
+ }
+
+static const struct ili9881c_instr lhr050h41_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, 0x03),
+ ILI9881C_COMMAND_INSTR(0x05, 0x00),
+ ILI9881C_COMMAND_INSTR(0x06, 0x06),
+ ILI9881C_COMMAND_INSTR(0x07, 0x06),
+ ILI9881C_COMMAND_INSTR(0x08, 0x00),
+ ILI9881C_COMMAND_INSTR(0x09, 0x18),
+ ILI9881C_COMMAND_INSTR(0x0a, 0x04),
+ ILI9881C_COMMAND_INSTR(0x0b, 0x00),
+ ILI9881C_COMMAND_INSTR(0x0c, 0x02),
+ ILI9881C_COMMAND_INSTR(0x0d, 0x03),
+ ILI9881C_COMMAND_INSTR(0x0e, 0x00),
+ ILI9881C_COMMAND_INSTR(0x0f, 0x25),
+ ILI9881C_COMMAND_INSTR(0x10, 0x25),
+ 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, 0x0C),
+ 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, 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, 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, 0x02),
+ ILI9881C_COMMAND_INSTR(0x61, 0x02),
+ ILI9881C_COMMAND_INSTR(0x62, 0x02),
+ ILI9881C_COMMAND_INSTR(0x63, 0x02),
+ ILI9881C_COMMAND_INSTR(0x64, 0x02),
+ 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, 0x0C),
+ ILI9881C_COMMAND_INSTR(0x6b, 0x02),
+ 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),
+ 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, 0x02),
+ ILI9881C_COMMAND_INSTR(0x78, 0x02),
+ ILI9881C_COMMAND_INSTR(0x79, 0x02),
+ ILI9881C_COMMAND_INSTR(0x7a, 0x02),
+ 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, 0x0C),
+ ILI9881C_COMMAND_INSTR(0x81, 0x02),
+ 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_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(0x26, 0x76),
+ 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(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),
+};
+
+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 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 inline struct ili9881c *panel_to_ili9881c(struct drm_panel *panel)
+{
+ return container_of(panel, struct ili9881c, panel);
+}
+
+/*
+ * The panel seems to accept some private DCS commands that map
+ * directly to registers.
+ *
+ * It is organised by page, with each page having its own set of
+ * registers, and the first page looks like it's holding the standard
+ * DCS commands.
+ *
+ * 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)
+{
+ 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;
+}
+
+static int ili9881c_send_cmd_data(struct ili9881c *ctx, 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;
+}
+
+static int ili9881c_prepare(struct drm_panel *panel)
+{
+ struct ili9881c *ctx = panel_to_ili9881c(panel);
+ unsigned int i;
+ int ret;
+
+ /* Power the panel */
+ ret = regulator_enable(ctx->power);
+ if (ret)
+ return ret;
+ msleep(5);
+
+ /* And reset it */
+ gpiod_set_value(ctx->reset, 1);
+ msleep(20);
+
+ gpiod_set_value(ctx->reset, 0);
+ msleep(20);
+
+ 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);
+ else if (instr->op == ILI9881C_COMMAND)
+ ret = ili9881c_send_cmd_data(ctx, instr->arg.cmd.cmd,
+ instr->arg.cmd.data);
+
+ if (ret)
+ return ret;
+ }
+
+ ret = ili9881c_switch_page(ctx, 0);
+ if (ret)
+ return ret;
+
+ ret = mipi_dsi_dcs_set_tear_on(ctx->dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
+ if (ret)
+ return ret;
+
+ ret = mipi_dsi_dcs_exit_sleep_mode(ctx->dsi);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int ili9881c_enable(struct drm_panel *panel)
+{
+ struct ili9881c *ctx = panel_to_ili9881c(panel);
+
+ msleep(120);
+
+ mipi_dsi_dcs_set_display_on(ctx->dsi);
+
+ return 0;
+}
+
+static int ili9881c_disable(struct drm_panel *panel)
+{
+ struct ili9881c *ctx = panel_to_ili9881c(panel);
+
+ return mipi_dsi_dcs_set_display_off(ctx->dsi);
+}
+
+static int ili9881c_unprepare(struct drm_panel *panel)
+{
+ struct ili9881c *ctx = panel_to_ili9881c(panel);
+
+ mipi_dsi_dcs_enter_sleep_mode(ctx->dsi);
+ regulator_disable(ctx->power);
+ gpiod_set_value(ctx->reset, 1);
+
+ return 0;
+}
+
+static const struct drm_display_mode lhr050h41_default_mode = {
+ .clock = 62000,
+
+ .hdisplay = 720,
+ .hsync_start = 720 + 10,
+ .hsync_end = 720 + 10 + 20,
+ .htotal = 720 + 10 + 20 + 30,
+
+ .vdisplay = 1280,
+ .vsync_start = 1280 + 10,
+ .vsync_end = 1280 + 10 + 10,
+ .vtotal = 1280 + 10 + 10 + 20,
+
+ .width_mm = 62,
+ .height_mm = 110,
+};
+
+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,
+
+ .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 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 int ili9881c_get_modes(struct drm_panel *panel,
+ struct drm_connector *connector)
+{
+ struct ili9881c *ctx = panel_to_ili9881c(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;
+
+ /*
+ * 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 ili9881c *ctx;
+ int ret;
+
+ ctx = devm_kzalloc(&dsi->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+ mipi_dsi_set_drvdata(dsi, ctx);
+ ctx->dsi = dsi;
+ ctx->desc = of_device_get_match_data(&dsi->dev);
+
+ drm_panel_init(&ctx->panel, &dsi->dev, &ili9881c_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ ctx->power = devm_regulator_get(&dsi->dev, "power");
+ if (IS_ERR(ctx->power))
+ 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;
+ }
+
+ ret = drm_panel_of_backlight(&ctx->panel);
+ if (ret)
+ return ret;
+
+ drm_panel_add(&ctx->panel);
+
+ dsi->mode_flags = ctx->desc->mode_flags;
+ dsi->format = MIPI_DSI_FMT_RGB888;
+ dsi->lanes = 4;
+
+ return mipi_dsi_attach(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);
+}
+
+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,
+};
+
+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,
+};
+
+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,
+};
+
+static const struct of_device_id ili9881c_of_match[] = {
+ { .compatible = "bananapi,lhr050h41", .data = &lhr050h41_desc },
+ { .compatible = "feixin,k101-im2byl02", .data = &k101_im2byl02_desc },
+ { .compatible = "wanchanglong,w552946aba", .data = &w552946aba_desc },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ili9881c_of_match);
+
+static struct mipi_dsi_driver ili9881c_dsi_driver = {
+ .probe = ili9881c_dsi_probe,
+ .remove = ili9881c_dsi_remove,
+ .driver = {
+ .name = "ili9881c-dsi",
+ .of_match_table = ili9881c_of_match,
+ },
+};
+module_mipi_dsi_driver(ili9881c_dsi_driver);
+
+MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
+MODULE_DESCRIPTION("Ilitek ILI9881C Controller Driver");
+MODULE_LICENSE("GPL v2");
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 000000000..b2b0ebc9e
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-innolux-ej030na.c
@@ -0,0 +1,308 @@
+// 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_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_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ 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");
+
+ drm_panel_init(&priv->panel, dev, &ej030na_funcs,
+ DRM_MODE_CONNECTOR_DPI);
+
+ 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_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/panel/panel-innolux-p079zca.c b/drivers/gpu/drm/panel/panel-innolux-p079zca.c
new file mode 100644
index 000000000..9992d0d4c
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-innolux-p079zca.c
@@ -0,0 +1,551 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * 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/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 panel_init_cmd {
+ size_t len;
+ const char *data;
+};
+
+#define _INIT_CMD(...) { \
+ .len = sizeof((char[]){__VA_ARGS__}), \
+ .data = (char[]){__VA_ARGS__} }
+
+struct panel_desc {
+ const struct drm_display_mode *mode;
+ unsigned int bpc;
+ struct {
+ unsigned int width;
+ unsigned int height;
+ } size;
+
+ unsigned long flags;
+ enum mipi_dsi_pixel_format format;
+ const struct panel_init_cmd *init_cmds;
+ unsigned int lanes;
+ const char * const *supply_names;
+ unsigned int num_supplies;
+ unsigned int sleep_mode_delay;
+ unsigned int power_down_delay;
+};
+
+struct innolux_panel {
+ struct drm_panel base;
+ struct mipi_dsi_device *link;
+ const struct panel_desc *desc;
+
+ struct regulator_bulk_data *supplies;
+ struct gpio_desc *enable_gpio;
+
+ bool prepared;
+ bool enabled;
+};
+
+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)
+{
+ struct innolux_panel *innolux = to_innolux_panel(panel);
+
+ if (!innolux->enabled)
+ return 0;
+
+ 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;
+
+ err = mipi_dsi_dcs_set_display_off(innolux->link);
+ if (err < 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) {
+ dev_err(panel->dev, "failed to enter sleep mode: %d\n", err);
+ return err;
+ }
+
+ if (innolux->desc->sleep_mode_delay)
+ msleep(innolux->desc->sleep_mode_delay);
+
+ gpiod_set_value_cansleep(innolux->enable_gpio, 0);
+
+ if (innolux->desc->power_down_delay)
+ msleep(innolux->desc->power_down_delay);
+
+ err = regulator_bulk_disable(innolux->desc->num_supplies,
+ innolux->supplies);
+ if (err < 0)
+ return err;
+
+ innolux->prepared = false;
+
+ return 0;
+}
+
+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,
+ innolux->supplies);
+ if (err < 0)
+ return err;
+
+ /* p079zca: t2 (20ms), p097pfg: t4 (15ms) */
+ usleep_range(20000, 21000);
+
+ gpiod_set_value_cansleep(innolux->enable_gpio, 1);
+
+ /* 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;
+ }
+ }
+ }
+
+ err = mipi_dsi_dcs_exit_sleep_mode(innolux->link);
+ if (err < 0) {
+ dev_err(panel->dev, "failed to exit sleep mode: %d\n", err);
+ goto poweroff;
+ }
+
+ /* T6: 120ms - 1000ms*/
+ msleep(120);
+
+ err = mipi_dsi_dcs_set_display_on(innolux->link);
+ if (err < 0) {
+ 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:
+ gpiod_set_value_cansleep(innolux->enable_gpio, 0);
+ regulator_bulk_disable(innolux->desc->num_supplies, innolux->supplies);
+
+ return err;
+}
+
+static int innolux_panel_enable(struct drm_panel *panel)
+{
+ struct innolux_panel *innolux = to_innolux_panel(panel);
+
+ if (innolux->enabled)
+ return 0;
+
+ innolux->enabled = true;
+
+ return 0;
+}
+
+static const char * const innolux_p079zca_supply_names[] = {
+ "power",
+};
+
+static const struct drm_display_mode innolux_p079zca_mode = {
+ .clock = 56900,
+ .hdisplay = 768,
+ .hsync_start = 768 + 40,
+ .hsync_end = 768 + 40 + 40,
+ .htotal = 768 + 40 + 40 + 40,
+ .vdisplay = 1024,
+ .vsync_start = 1024 + 20,
+ .vsync_end = 1024 + 20 + 4,
+ .vtotal = 1024 + 20 + 4 + 20,
+};
+
+static const struct panel_desc innolux_p079zca_panel_desc = {
+ .mode = &innolux_p079zca_mode,
+ .bpc = 8,
+ .size = {
+ .width = 120,
+ .height = 160,
+ },
+ .flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
+ MIPI_DSI_MODE_LPM,
+ .format = MIPI_DSI_FMT_RGB888,
+ .lanes = 4,
+ .supply_names = innolux_p079zca_supply_names,
+ .num_supplies = ARRAY_SIZE(innolux_p079zca_supply_names),
+ .power_down_delay = 80, /* T8: 80ms - 1000ms */
+};
+
+static const char * const innolux_p097pfg_supply_names[] = {
+ "avdd",
+ "avee",
+};
+
+static const struct drm_display_mode innolux_p097pfg_mode = {
+ .clock = 229000,
+ .hdisplay = 1536,
+ .hsync_start = 1536 + 100,
+ .hsync_end = 1536 + 100 + 24,
+ .htotal = 1536 + 100 + 24 + 100,
+ .vdisplay = 2048,
+ .vsync_start = 2048 + 100,
+ .vsync_end = 2048 + 100 + 2,
+ .vtotal = 2048 + 100 + 2 + 18,
+};
+
+/*
+ * 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 const struct panel_desc innolux_p097pfg_panel_desc = {
+ .mode = &innolux_p097pfg_mode,
+ .bpc = 8,
+ .size = {
+ .width = 147,
+ .height = 196,
+ },
+ .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,
+ .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,
+ 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(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;
+ }
+
+ drm_mode_set_name(mode);
+
+ drm_mode_probed_add(connector, mode);
+
+ 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,
+};
+
+static const struct of_device_id innolux_of_match[] = {
+ { .compatible = "innolux,p079zca",
+ .data = &innolux_p079zca_panel_desc
+ },
+ { .compatible = "innolux,p097pfg",
+ .data = &innolux_p097pfg_panel_desc
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, innolux_of_match);
+
+static int innolux_panel_add(struct mipi_dsi_device *dsi,
+ const struct panel_desc *desc)
+{
+ struct innolux_panel *innolux;
+ struct device *dev = &dsi->dev;
+ int err, i;
+
+ innolux = devm_kzalloc(dev, sizeof(*innolux), GFP_KERNEL);
+ if (!innolux)
+ return -ENOMEM;
+
+ innolux->desc = desc;
+
+ innolux->supplies = devm_kcalloc(dev, desc->num_supplies,
+ sizeof(*innolux->supplies),
+ GFP_KERNEL);
+ if (!innolux->supplies)
+ return -ENOMEM;
+
+ for (i = 0; i < desc->num_supplies; i++)
+ innolux->supplies[i].supply = desc->supply_names[i];
+
+ err = devm_regulator_bulk_get(dev, desc->num_supplies,
+ innolux->supplies);
+ if (err < 0)
+ return err;
+
+ innolux->enable_gpio = devm_gpiod_get_optional(dev, "enable",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(innolux->enable_gpio)) {
+ err = PTR_ERR(innolux->enable_gpio);
+ dev_dbg(dev, "failed to get enable gpio: %d\n", err);
+ innolux->enable_gpio = NULL;
+ }
+
+ drm_panel_init(&innolux->base, dev, &innolux_panel_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ 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;
+
+ return 0;
+}
+
+static void innolux_panel_del(struct innolux_panel *innolux)
+{
+ drm_panel_remove(&innolux->base);
+}
+
+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);
+ dsi->mode_flags = desc->flags;
+ dsi->format = desc->format;
+ dsi->lanes = desc->lanes;
+
+ err = innolux_panel_add(dsi, desc);
+ if (err < 0)
+ return err;
+
+ err = mipi_dsi_attach(dsi);
+ if (err < 0) {
+ innolux = mipi_dsi_get_drvdata(dsi);
+ innolux_panel_del(innolux);
+ return err;
+ }
+
+ return 0;
+}
+
+static void innolux_panel_remove(struct mipi_dsi_device *dsi)
+{
+ struct innolux_panel *innolux = mipi_dsi_get_drvdata(dsi);
+ int err;
+
+ err = drm_panel_unprepare(&innolux->base);
+ if (err < 0)
+ dev_err(&dsi->dev, "failed to unprepare panel: %d\n", err);
+
+ err = drm_panel_disable(&innolux->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);
+
+ innolux_panel_del(innolux);
+}
+
+static void innolux_panel_shutdown(struct mipi_dsi_device *dsi)
+{
+ struct innolux_panel *innolux = mipi_dsi_get_drvdata(dsi);
+
+ drm_panel_unprepare(&innolux->base);
+ drm_panel_disable(&innolux->base);
+}
+
+static struct mipi_dsi_driver innolux_panel_driver = {
+ .driver = {
+ .name = "panel-innolux-p079zca",
+ .of_match_table = innolux_of_match,
+ },
+ .probe = innolux_panel_probe,
+ .remove = innolux_panel_remove,
+ .shutdown = innolux_panel_shutdown,
+};
+module_mipi_dsi_driver(innolux_panel_driver);
+
+MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>");
+MODULE_AUTHOR("Lin Huang <hl@rock-chips.com>");
+MODULE_DESCRIPTION("Innolux P079ZCA panel driver");
+MODULE_LICENSE("GPL v2");
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 000000000..d8765b229
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-jdi-fhd-r63452.c
@@ -0,0 +1,321 @@
+// 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;
+ bool prepared;
+};
+
+static inline struct jdi_fhd_r63452 *to_jdi_fhd_r63452(struct drm_panel *panel)
+{
+ return container_of(panel, struct jdi_fhd_r63452, panel);
+}
+
+#define dsi_generic_write_seq(dsi, seq...) do { \
+ static const u8 d[] = { seq }; \
+ int ret; \
+ ret = mipi_dsi_generic_write(dsi, d, ARRAY_SIZE(d)); \
+ if (ret < 0) \
+ return ret; \
+ } while (0)
+
+#define dsi_dcs_write_seq(dsi, seq...) do { \
+ static const u8 d[] = { seq }; \
+ int ret; \
+ ret = mipi_dsi_dcs_write_buffer(dsi, d, ARRAY_SIZE(d)); \
+ if (ret < 0) \
+ return ret; \
+ } while (0)
+
+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 device *dev = &dsi->dev;
+ int ret;
+
+ dsi->mode_flags |= MIPI_DSI_MODE_LPM;
+
+ dsi_generic_write_seq(dsi, 0xb0, 0x00);
+ dsi_generic_write_seq(dsi, 0xd6, 0x01);
+ dsi_generic_write_seq(dsi, 0xec,
+ 0x64, 0xdc, 0xec, 0x3b, 0x52, 0x00, 0x0b, 0x0b,
+ 0x13, 0x15, 0x68, 0x0b, 0xb5);
+ dsi_generic_write_seq(dsi, 0xb0, 0x03);
+
+ 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);
+ return ret;
+ }
+
+ dsi_dcs_write_seq(dsi, MIPI_DCS_SET_ADDRESS_MODE, 0x00);
+
+ ret = mipi_dsi_dcs_set_pixel_format(dsi, 0x77);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set pixel format: %d\n", ret);
+ return ret;
+ }
+
+ ret = mipi_dsi_dcs_set_column_address(dsi, 0x0000, 0x0437);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set column address: %d\n", ret);
+ return ret;
+ }
+
+ ret = mipi_dsi_dcs_set_page_address(dsi, 0x0000, 0x077f);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set page address: %d\n", ret);
+ return ret;
+ }
+
+ ret = mipi_dsi_dcs_set_tear_scanline(dsi, 0x0000);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set tear scanline: %d\n", ret);
+ return ret;
+ }
+
+ ret = mipi_dsi_dcs_set_display_brightness(dsi, 0x00ff);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set display brightness: %d\n", ret);
+ return ret;
+ }
+
+ dsi_dcs_write_seq(dsi, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x24);
+ dsi_dcs_write_seq(dsi, MIPI_DCS_WRITE_POWER_SAVE, 0x00);
+ dsi_dcs_write_seq(dsi, MIPI_DCS_SET_CABC_MIN_BRIGHTNESS, 0x00);
+ dsi_dcs_write_seq(dsi, 0x84, 0x00);
+
+ ret = mipi_dsi_dcs_set_display_on(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set display on: %d\n", ret);
+ return ret;
+ }
+ msleep(20);
+
+ ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to exit sleep mode: %d\n", ret);
+ return ret;
+ }
+ msleep(80);
+
+ dsi_generic_write_seq(dsi, 0xb0, 0x04);
+ dsi_dcs_write_seq(dsi, 0x84, 0x00);
+ dsi_generic_write_seq(dsi, 0xc8, 0x11);
+ dsi_generic_write_seq(dsi, 0xb0, 0x03);
+
+ return 0;
+}
+
+static int jdi_fhd_r63452_off(struct jdi_fhd_r63452 *ctx)
+{
+ struct mipi_dsi_device *dsi = ctx->dsi;
+ struct device *dev = &dsi->dev;
+ int ret;
+
+ dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
+
+ dsi_generic_write_seq(dsi, 0xb0, 0x00);
+ dsi_generic_write_seq(dsi, 0xd6, 0x01);
+ dsi_generic_write_seq(dsi, 0xec,
+ 0x64, 0xdc, 0xec, 0x3b, 0x52, 0x00, 0x0b, 0x0b,
+ 0x13, 0x15, 0x68, 0x0b, 0x95);
+ dsi_generic_write_seq(dsi, 0xb0, 0x03);
+
+ 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(2000, 3000);
+
+ ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to enter sleep mode: %d\n", ret);
+ return ret;
+ }
+ msleep(120);
+
+ return 0;
+}
+
+static int jdi_fhd_r63452_prepare(struct drm_panel *panel)
+{
+ struct jdi_fhd_r63452 *ctx = to_jdi_fhd_r63452(panel);
+ struct device *dev = &ctx->dsi->dev;
+ int ret;
+
+ if (ctx->prepared)
+ return 0;
+
+ jdi_fhd_r63452_reset(ctx);
+
+ ret = jdi_fhd_r63452_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;
+ }
+
+ ctx->prepared = true;
+ return 0;
+}
+
+static int jdi_fhd_r63452_unprepare(struct drm_panel *panel)
+{
+ struct jdi_fhd_r63452 *ctx = to_jdi_fhd_r63452(panel);
+ struct device *dev = &ctx->dsi->dev;
+ int ret;
+
+ if (!ctx->prepared)
+ return 0;
+
+ ret = jdi_fhd_r63452_off(ctx);
+ if (ret < 0)
+ dev_err(dev, "Failed to un-initialize panel: %d\n", ret);
+
+ gpiod_set_value_cansleep(ctx->reset_gpio, 1);
+
+ ctx->prepared = false;
+ 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_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ 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;
+
+ drm_panel_init(&ctx->panel, dev, &jdi_fhd_r63452_panel_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ 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-lt070me05000.c b/drivers/gpu/drm/panel/panel-jdi-lt070me05000.c
new file mode 100644
index 000000000..8f4f137a2
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-jdi-lt070me05000.c
@@ -0,0 +1,523 @@
+// 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
+ */
+
+#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_modes.h>
+#include <drm/drm_panel.h>
+
+static const char * const regulator_names[] = {
+ "vddp",
+ "iovcc"
+};
+
+struct jdi_panel {
+ struct drm_panel base;
+ struct mipi_dsi_device *dsi;
+
+ struct regulator_bulk_data supplies[ARRAY_SIZE(regulator_names)];
+
+ struct gpio_desc *enable_gpio;
+ struct gpio_desc *reset_gpio;
+ struct gpio_desc *dcdc_en_gpio;
+ struct backlight_device *backlight;
+
+ bool prepared;
+ bool enabled;
+
+ const struct drm_display_mode *mode;
+};
+
+static inline struct jdi_panel *to_jdi_panel(struct drm_panel *panel)
+{
+ return container_of(panel, struct jdi_panel, base);
+}
+
+static int jdi_panel_init(struct jdi_panel *jdi)
+{
+ struct mipi_dsi_device *dsi = jdi->dsi;
+ struct device *dev = &jdi->dsi->dev;
+ int ret;
+
+ dsi->mode_flags |= MIPI_DSI_MODE_LPM;
+
+ ret = mipi_dsi_dcs_soft_reset(dsi);
+ if (ret < 0)
+ return ret;
+
+ usleep_range(10000, 20000);
+
+ ret = mipi_dsi_dcs_set_pixel_format(dsi, MIPI_DCS_PIXEL_FMT_24BIT << 4);
+ if (ret < 0) {
+ dev_err(dev, "failed to set pixel format: %d\n", ret);
+ return ret;
+ }
+
+ ret = mipi_dsi_dcs_set_column_address(dsi, 0, jdi->mode->hdisplay - 1);
+ if (ret < 0) {
+ dev_err(dev, "failed to set column address: %d\n", ret);
+ return ret;
+ }
+
+ ret = mipi_dsi_dcs_set_page_address(dsi, 0, jdi->mode->vdisplay - 1);
+ if (ret < 0) {
+ dev_err(dev, "failed to set page address: %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * BIT(5) BCTRL = 1 Backlight Control Block On, Brightness registers
+ * are active
+ * BIT(3) BL = 1 Backlight Control On
+ * BIT(2) DD = 0 Display Dimming is Off
+ */
+ ret = mipi_dsi_dcs_write(dsi, MIPI_DCS_WRITE_CONTROL_DISPLAY,
+ (u8[]){ 0x24 }, 1);
+ if (ret < 0) {
+ dev_err(dev, "failed to write control display: %d\n", ret);
+ return ret;
+ }
+
+ /* CABC off */
+ ret = mipi_dsi_dcs_write(dsi, MIPI_DCS_WRITE_POWER_SAVE,
+ (u8[]){ 0x00 }, 1);
+ if (ret < 0) {
+ dev_err(dev, "failed to set cabc off: %d\n", ret);
+ return ret;
+ }
+
+ ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
+ if (ret < 0) {
+ dev_err(dev, "failed to set exit sleep mode: %d\n", ret);
+ return ret;
+ }
+
+ msleep(120);
+
+ ret = mipi_dsi_generic_write(dsi, (u8[]){0xB0, 0x00}, 2);
+ if (ret < 0) {
+ dev_err(dev, "failed to set mcap: %d\n", ret);
+ return ret;
+ }
+
+ mdelay(10);
+
+ /* Interface setting, video mode */
+ ret = mipi_dsi_generic_write(dsi, (u8[])
+ {0xB3, 0x26, 0x08, 0x00, 0x20, 0x00}, 6);
+ if (ret < 0) {
+ dev_err(dev, "failed to set display interface setting: %d\n"
+ , ret);
+ return ret;
+ }
+
+ mdelay(20);
+
+ ret = mipi_dsi_generic_write(dsi, (u8[]){0xB0, 0x03}, 2);
+ if (ret < 0) {
+ dev_err(dev, "failed to set default values for mcap: %d\n"
+ , ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int jdi_panel_on(struct jdi_panel *jdi)
+{
+ struct mipi_dsi_device *dsi = jdi->dsi;
+ struct device *dev = &jdi->dsi->dev;
+ int ret;
+
+ dsi->mode_flags |= MIPI_DSI_MODE_LPM;
+
+ ret = mipi_dsi_dcs_set_display_on(dsi);
+ if (ret < 0)
+ dev_err(dev, "failed to set display on: %d\n", ret);
+
+ return ret;
+}
+
+static void jdi_panel_off(struct jdi_panel *jdi)
+{
+ struct mipi_dsi_device *dsi = jdi->dsi;
+ struct device *dev = &jdi->dsi->dev;
+ int ret;
+
+ dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
+
+ ret = mipi_dsi_dcs_set_display_off(dsi);
+ if (ret < 0)
+ dev_err(dev, "failed to set display off: %d\n", ret);
+
+ ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
+ if (ret < 0)
+ dev_err(dev, "failed to enter sleep mode: %d\n", ret);
+
+ msleep(100);
+}
+
+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;
+}
+
+static int jdi_panel_unprepare(struct drm_panel *panel)
+{
+ struct jdi_panel *jdi = to_jdi_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);
+ if (ret < 0)
+ dev_err(dev, "regulator disable failed, %d\n", ret);
+
+ gpiod_set_value(jdi->enable_gpio, 0);
+
+ gpiod_set_value(jdi->reset_gpio, 1);
+
+ gpiod_set_value(jdi->dcdc_en_gpio, 0);
+
+ jdi->prepared = false;
+
+ return 0;
+}
+
+static int jdi_panel_prepare(struct drm_panel *panel)
+{
+ struct jdi_panel *jdi = to_jdi_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);
+ return ret;
+ }
+
+ msleep(20);
+
+ gpiod_set_value(jdi->dcdc_en_gpio, 1);
+ usleep_range(10, 20);
+
+ gpiod_set_value(jdi->reset_gpio, 0);
+ usleep_range(10, 20);
+
+ gpiod_set_value(jdi->enable_gpio, 1);
+ usleep_range(10, 20);
+
+ ret = jdi_panel_init(jdi);
+ if (ret < 0) {
+ dev_err(dev, "failed to init panel: %d\n", ret);
+ goto poweroff;
+ }
+
+ ret = jdi_panel_on(jdi);
+ if (ret < 0) {
+ dev_err(dev, "failed to set panel on: %d\n", ret);
+ goto poweroff;
+ }
+
+ jdi->prepared = true;
+
+ return 0;
+
+poweroff:
+ ret = regulator_bulk_disable(ARRAY_SIZE(jdi->supplies), jdi->supplies);
+ if (ret < 0)
+ dev_err(dev, "regulator disable failed, %d\n", ret);
+
+ gpiod_set_value(jdi->enable_gpio, 0);
+
+ gpiod_set_value(jdi->reset_gpio, 1);
+
+ gpiod_set_value(jdi->dcdc_en_gpio, 0);
+
+ return ret;
+}
+
+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;
+}
+
+static const struct drm_display_mode default_mode = {
+ .clock = 155493,
+ .hdisplay = 1200,
+ .hsync_start = 1200 + 48,
+ .hsync_end = 1200 + 48 + 32,
+ .htotal = 1200 + 48 + 32 + 60,
+ .vdisplay = 1920,
+ .vsync_start = 1920 + 3,
+ .vsync_end = 1920 + 3 + 5,
+ .vtotal = 1920 + 3 + 5 + 6,
+ .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_jdi_panel(panel);
+ struct device *dev = &jdi->dsi->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 = 95;
+ connector->display_info.height_mm = 151;
+
+ 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 = bl->props.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);
+ int ret;
+
+ dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
+
+ ret = mipi_dsi_dcs_set_display_brightness(dsi, bl->props.brightness);
+ if (ret < 0)
+ return ret;
+
+ dsi->mode_flags |= MIPI_DSI_MODE_LPM;
+
+ return 0;
+}
+
+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;
+
+ memset(&props, 0, sizeof(props));
+ props.type = BACKLIGHT_RAW;
+ props.brightness = 255;
+ props.max_brightness = 255;
+
+ return devm_backlight_device_register(dev, dev_name(dev), dev, dsi,
+ &dsi_bl_ops, &props);
+}
+
+static const struct drm_panel_funcs jdi_panel_funcs = {
+ .disable = jdi_panel_disable,
+ .unprepare = jdi_panel_unprepare,
+ .prepare = jdi_panel_prepare,
+ .enable = jdi_panel_enable,
+ .get_modes = jdi_panel_get_modes,
+};
+
+static const struct of_device_id jdi_of_match[] = {
+ { .compatible = "jdi,lt070me05000", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, jdi_of_match);
+
+static int jdi_panel_add(struct jdi_panel *jdi)
+{
+ struct device *dev = &jdi->dsi->dev;
+ int ret;
+ unsigned int i;
+
+ jdi->mode = &default_mode;
+
+ for (i = 0; i < ARRAY_SIZE(jdi->supplies); i++)
+ jdi->supplies[i].supply = regulator_names[i];
+
+ 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;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ 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->dsi->dev, &jdi_panel_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ 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);
+}
+
+static int jdi_panel_probe(struct mipi_dsi_device *dsi)
+{
+ struct jdi_panel *jdi;
+ int ret;
+
+ dsi->lanes = 4;
+ dsi->format = MIPI_DSI_FMT_RGB888;
+ 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;
+
+ mipi_dsi_set_drvdata(dsi, jdi);
+
+ jdi->dsi = dsi;
+
+ ret = jdi_panel_add(jdi);
+ if (ret < 0)
+ return ret;
+
+ ret = mipi_dsi_attach(dsi);
+ if (ret < 0) {
+ jdi_panel_del(jdi);
+ return ret;
+ }
+
+ return 0;
+}
+
+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);
+}
+
+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 = {
+ .driver = {
+ .name = "panel-jdi-lt070me05000",
+ .of_match_table = jdi_of_match,
+ },
+ .probe = jdi_panel_probe,
+ .remove = jdi_panel_remove,
+ .shutdown = jdi_panel_shutdown,
+};
+module_mipi_dsi_driver(jdi_panel_driver);
+
+MODULE_AUTHOR("Sumit Semwal <sumit.semwal@linaro.org>");
+MODULE_AUTHOR("Vinay Simha BN <simhavcs@gmail.com>");
+MODULE_DESCRIPTION("JDI LT070ME05000 WUXGA");
+MODULE_LICENSE("GPL v2");
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 000000000..1ab1ebe30
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-khadas-ts050.c
@@ -0,0 +1,868 @@
+// 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;
+
+ bool prepared;
+ bool enabled;
+};
+
+struct khadas_ts050_panel_cmd {
+ u8 cmd;
+ u8 data;
+};
+
+/* Only the CMD1 User Command set is documented */
+static const struct khadas_ts050_panel_cmd init_code[] = {
+ /* Select Unknown CMD Page (Undocumented) */
+ {0xff, 0xee},
+ /* Reload CMD1: Don't reload default value to register */
+ {0xfb, 0x01},
+ {0x1f, 0x45},
+ {0x24, 0x4f},
+ {0x38, 0xc8},
+ {0x39, 0x27},
+ {0x1e, 0x77},
+ {0x1d, 0x0f},
+ {0x7e, 0x71},
+ {0x7c, 0x03},
+ {0xff, 0x00},
+ {0xfb, 0x01},
+ {0x35, 0x01},
+ /* Select CMD2 Page0 (Undocumented) */
+ {0xff, 0x01},
+ /* Reload CMD1: Don't reload default value to register */
+ {0xfb, 0x01},
+ {0x00, 0x01},
+ {0x01, 0x55},
+ {0x02, 0x40},
+ {0x05, 0x40},
+ {0x06, 0x4a},
+ {0x07, 0x24},
+ {0x08, 0x0c},
+ {0x0b, 0x7d},
+ {0x0c, 0x7d},
+ {0x0e, 0xb0},
+ {0x0f, 0xae},
+ {0x11, 0x10},
+ {0x12, 0x10},
+ {0x13, 0x03},
+ {0x14, 0x4a},
+ {0x15, 0x12},
+ {0x16, 0x12},
+ {0x18, 0x00},
+ {0x19, 0x77},
+ {0x1a, 0x55},
+ {0x1b, 0x13},
+ {0x1c, 0x00},
+ {0x1d, 0x00},
+ {0x1e, 0x13},
+ {0x1f, 0x00},
+ {0x23, 0x00},
+ {0x24, 0x00},
+ {0x25, 0x00},
+ {0x26, 0x00},
+ {0x27, 0x00},
+ {0x28, 0x00},
+ {0x35, 0x00},
+ {0x66, 0x00},
+ {0x58, 0x82},
+ {0x59, 0x02},
+ {0x5a, 0x02},
+ {0x5b, 0x02},
+ {0x5c, 0x82},
+ {0x5d, 0x82},
+ {0x5e, 0x02},
+ {0x5f, 0x02},
+ {0x72, 0x31},
+ /* Select CMD2 Page4 (Undocumented) */
+ {0xff, 0x05},
+ /* Reload CMD1: Don't reload default value to register */
+ {0xfb, 0x01},
+ {0x00, 0x01},
+ {0x01, 0x0b},
+ {0x02, 0x0c},
+ {0x03, 0x09},
+ {0x04, 0x0a},
+ {0x05, 0x00},
+ {0x06, 0x0f},
+ {0x07, 0x10},
+ {0x08, 0x00},
+ {0x09, 0x00},
+ {0x0a, 0x00},
+ {0x0b, 0x00},
+ {0x0c, 0x00},
+ {0x0d, 0x13},
+ {0x0e, 0x15},
+ {0x0f, 0x17},
+ {0x10, 0x01},
+ {0x11, 0x0b},
+ {0x12, 0x0c},
+ {0x13, 0x09},
+ {0x14, 0x0a},
+ {0x15, 0x00},
+ {0x16, 0x0f},
+ {0x17, 0x10},
+ {0x18, 0x00},
+ {0x19, 0x00},
+ {0x1a, 0x00},
+ {0x1b, 0x00},
+ {0x1c, 0x00},
+ {0x1d, 0x13},
+ {0x1e, 0x15},
+ {0x1f, 0x17},
+ {0x20, 0x00},
+ {0x21, 0x03},
+ {0x22, 0x01},
+ {0x23, 0x40},
+ {0x24, 0x40},
+ {0x25, 0xed},
+ {0x29, 0x58},
+ {0x2a, 0x12},
+ {0x2b, 0x01},
+ {0x4b, 0x06},
+ {0x4c, 0x11},
+ {0x4d, 0x20},
+ {0x4e, 0x02},
+ {0x4f, 0x02},
+ {0x50, 0x20},
+ {0x51, 0x61},
+ {0x52, 0x01},
+ {0x53, 0x63},
+ {0x54, 0x77},
+ {0x55, 0xed},
+ {0x5b, 0x00},
+ {0x5c, 0x00},
+ {0x5d, 0x00},
+ {0x5e, 0x00},
+ {0x5f, 0x15},
+ {0x60, 0x75},
+ {0x61, 0x00},
+ {0x62, 0x00},
+ {0x63, 0x00},
+ {0x64, 0x00},
+ {0x65, 0x00},
+ {0x66, 0x00},
+ {0x67, 0x00},
+ {0x68, 0x04},
+ {0x69, 0x00},
+ {0x6a, 0x00},
+ {0x6c, 0x40},
+ {0x75, 0x01},
+ {0x76, 0x01},
+ {0x7a, 0x80},
+ {0x7b, 0xa3},
+ {0x7c, 0xd8},
+ {0x7d, 0x60},
+ {0x7f, 0x15},
+ {0x80, 0x81},
+ {0x83, 0x05},
+ {0x93, 0x08},
+ {0x94, 0x10},
+ {0x8a, 0x00},
+ {0x9b, 0x0f},
+ {0xea, 0xff},
+ {0xec, 0x00},
+ /* Select CMD2 Page0 (Undocumented) */
+ {0xff, 0x01},
+ /* Reload CMD1: Don't reload default value to register */
+ {0xfb, 0x01},
+ {0x75, 0x00},
+ {0x76, 0xdf},
+ {0x77, 0x00},
+ {0x78, 0xe4},
+ {0x79, 0x00},
+ {0x7a, 0xed},
+ {0x7b, 0x00},
+ {0x7c, 0xf6},
+ {0x7d, 0x00},
+ {0x7e, 0xff},
+ {0x7f, 0x01},
+ {0x80, 0x07},
+ {0x81, 0x01},
+ {0x82, 0x10},
+ {0x83, 0x01},
+ {0x84, 0x18},
+ {0x85, 0x01},
+ {0x86, 0x20},
+ {0x87, 0x01},
+ {0x88, 0x3d},
+ {0x89, 0x01},
+ {0x8a, 0x56},
+ {0x8b, 0x01},
+ {0x8c, 0x84},
+ {0x8d, 0x01},
+ {0x8e, 0xab},
+ {0x8f, 0x01},
+ {0x90, 0xec},
+ {0x91, 0x02},
+ {0x92, 0x22},
+ {0x93, 0x02},
+ {0x94, 0x23},
+ {0x95, 0x02},
+ {0x96, 0x55},
+ {0x97, 0x02},
+ {0x98, 0x8b},
+ {0x99, 0x02},
+ {0x9a, 0xaf},
+ {0x9b, 0x02},
+ {0x9c, 0xdf},
+ {0x9d, 0x03},
+ {0x9e, 0x01},
+ {0x9f, 0x03},
+ {0xa0, 0x2c},
+ {0xa2, 0x03},
+ {0xa3, 0x39},
+ {0xa4, 0x03},
+ {0xa5, 0x47},
+ {0xa6, 0x03},
+ {0xa7, 0x56},
+ {0xa9, 0x03},
+ {0xaa, 0x66},
+ {0xab, 0x03},
+ {0xac, 0x76},
+ {0xad, 0x03},
+ {0xae, 0x85},
+ {0xaf, 0x03},
+ {0xb0, 0x90},
+ {0xb1, 0x03},
+ {0xb2, 0xcb},
+ {0xb3, 0x00},
+ {0xb4, 0xdf},
+ {0xb5, 0x00},
+ {0xb6, 0xe4},
+ {0xb7, 0x00},
+ {0xb8, 0xed},
+ {0xb9, 0x00},
+ {0xba, 0xf6},
+ {0xbb, 0x00},
+ {0xbc, 0xff},
+ {0xbd, 0x01},
+ {0xbe, 0x07},
+ {0xbf, 0x01},
+ {0xc0, 0x10},
+ {0xc1, 0x01},
+ {0xc2, 0x18},
+ {0xc3, 0x01},
+ {0xc4, 0x20},
+ {0xc5, 0x01},
+ {0xc6, 0x3d},
+ {0xc7, 0x01},
+ {0xc8, 0x56},
+ {0xc9, 0x01},
+ {0xca, 0x84},
+ {0xcb, 0x01},
+ {0xcc, 0xab},
+ {0xcd, 0x01},
+ {0xce, 0xec},
+ {0xcf, 0x02},
+ {0xd0, 0x22},
+ {0xd1, 0x02},
+ {0xd2, 0x23},
+ {0xd3, 0x02},
+ {0xd4, 0x55},
+ {0xd5, 0x02},
+ {0xd6, 0x8b},
+ {0xd7, 0x02},
+ {0xd8, 0xaf},
+ {0xd9, 0x02},
+ {0xda, 0xdf},
+ {0xdb, 0x03},
+ {0xdc, 0x01},
+ {0xdd, 0x03},
+ {0xde, 0x2c},
+ {0xdf, 0x03},
+ {0xe0, 0x39},
+ {0xe1, 0x03},
+ {0xe2, 0x47},
+ {0xe3, 0x03},
+ {0xe4, 0x56},
+ {0xe5, 0x03},
+ {0xe6, 0x66},
+ {0xe7, 0x03},
+ {0xe8, 0x76},
+ {0xe9, 0x03},
+ {0xea, 0x85},
+ {0xeb, 0x03},
+ {0xec, 0x90},
+ {0xed, 0x03},
+ {0xee, 0xcb},
+ {0xef, 0x00},
+ {0xf0, 0xbb},
+ {0xf1, 0x00},
+ {0xf2, 0xc0},
+ {0xf3, 0x00},
+ {0xf4, 0xcc},
+ {0xf5, 0x00},
+ {0xf6, 0xd6},
+ {0xf7, 0x00},
+ {0xf8, 0xe1},
+ {0xf9, 0x00},
+ {0xfa, 0xea},
+ /* Select CMD2 Page2 (Undocumented) */
+ {0xff, 0x02},
+ /* Reload CMD1: Don't reload default value to register */
+ {0xfb, 0x01},
+ {0x00, 0x00},
+ {0x01, 0xf4},
+ {0x02, 0x00},
+ {0x03, 0xef},
+ {0x04, 0x01},
+ {0x05, 0x07},
+ {0x06, 0x01},
+ {0x07, 0x28},
+ {0x08, 0x01},
+ {0x09, 0x44},
+ {0x0a, 0x01},
+ {0x0b, 0x76},
+ {0x0c, 0x01},
+ {0x0d, 0xa0},
+ {0x0e, 0x01},
+ {0x0f, 0xe7},
+ {0x10, 0x02},
+ {0x11, 0x1f},
+ {0x12, 0x02},
+ {0x13, 0x22},
+ {0x14, 0x02},
+ {0x15, 0x54},
+ {0x16, 0x02},
+ {0x17, 0x8b},
+ {0x18, 0x02},
+ {0x19, 0xaf},
+ {0x1a, 0x02},
+ {0x1b, 0xe0},
+ {0x1c, 0x03},
+ {0x1d, 0x01},
+ {0x1e, 0x03},
+ {0x1f, 0x2d},
+ {0x20, 0x03},
+ {0x21, 0x39},
+ {0x22, 0x03},
+ {0x23, 0x47},
+ {0x24, 0x03},
+ {0x25, 0x57},
+ {0x26, 0x03},
+ {0x27, 0x65},
+ {0x28, 0x03},
+ {0x29, 0x77},
+ {0x2a, 0x03},
+ {0x2b, 0x85},
+ {0x2d, 0x03},
+ {0x2f, 0x8f},
+ {0x30, 0x03},
+ {0x31, 0xcb},
+ {0x32, 0x00},
+ {0x33, 0xbb},
+ {0x34, 0x00},
+ {0x35, 0xc0},
+ {0x36, 0x00},
+ {0x37, 0xcc},
+ {0x38, 0x00},
+ {0x39, 0xd6},
+ {0x3a, 0x00},
+ {0x3b, 0xe1},
+ {0x3d, 0x00},
+ {0x3f, 0xea},
+ {0x40, 0x00},
+ {0x41, 0xf4},
+ {0x42, 0x00},
+ {0x43, 0xfe},
+ {0x44, 0x01},
+ {0x45, 0x07},
+ {0x46, 0x01},
+ {0x47, 0x28},
+ {0x48, 0x01},
+ {0x49, 0x44},
+ {0x4a, 0x01},
+ {0x4b, 0x76},
+ {0x4c, 0x01},
+ {0x4d, 0xa0},
+ {0x4e, 0x01},
+ {0x4f, 0xe7},
+ {0x50, 0x02},
+ {0x51, 0x1f},
+ {0x52, 0x02},
+ {0x53, 0x22},
+ {0x54, 0x02},
+ {0x55, 0x54},
+ {0x56, 0x02},
+ {0x58, 0x8b},
+ {0x59, 0x02},
+ {0x5a, 0xaf},
+ {0x5b, 0x02},
+ {0x5c, 0xe0},
+ {0x5d, 0x03},
+ {0x5e, 0x01},
+ {0x5f, 0x03},
+ {0x60, 0x2d},
+ {0x61, 0x03},
+ {0x62, 0x39},
+ {0x63, 0x03},
+ {0x64, 0x47},
+ {0x65, 0x03},
+ {0x66, 0x57},
+ {0x67, 0x03},
+ {0x68, 0x65},
+ {0x69, 0x03},
+ {0x6a, 0x77},
+ {0x6b, 0x03},
+ {0x6c, 0x85},
+ {0x6d, 0x03},
+ {0x6e, 0x8f},
+ {0x6f, 0x03},
+ {0x70, 0xcb},
+ {0x71, 0x00},
+ {0x72, 0x00},
+ {0x73, 0x00},
+ {0x74, 0x21},
+ {0x75, 0x00},
+ {0x76, 0x4c},
+ {0x77, 0x00},
+ {0x78, 0x6b},
+ {0x79, 0x00},
+ {0x7a, 0x85},
+ {0x7b, 0x00},
+ {0x7c, 0x9a},
+ {0x7d, 0x00},
+ {0x7e, 0xad},
+ {0x7f, 0x00},
+ {0x80, 0xbe},
+ {0x81, 0x00},
+ {0x82, 0xcd},
+ {0x83, 0x01},
+ {0x84, 0x01},
+ {0x85, 0x01},
+ {0x86, 0x29},
+ {0x87, 0x01},
+ {0x88, 0x68},
+ {0x89, 0x01},
+ {0x8a, 0x98},
+ {0x8b, 0x01},
+ {0x8c, 0xe5},
+ {0x8d, 0x02},
+ {0x8e, 0x1e},
+ {0x8f, 0x02},
+ {0x90, 0x30},
+ {0x91, 0x02},
+ {0x92, 0x52},
+ {0x93, 0x02},
+ {0x94, 0x88},
+ {0x95, 0x02},
+ {0x96, 0xaa},
+ {0x97, 0x02},
+ {0x98, 0xd7},
+ {0x99, 0x02},
+ {0x9a, 0xf7},
+ {0x9b, 0x03},
+ {0x9c, 0x21},
+ {0x9d, 0x03},
+ {0x9e, 0x2e},
+ {0x9f, 0x03},
+ {0xa0, 0x3d},
+ {0xa2, 0x03},
+ {0xa3, 0x4c},
+ {0xa4, 0x03},
+ {0xa5, 0x5e},
+ {0xa6, 0x03},
+ {0xa7, 0x71},
+ {0xa9, 0x03},
+ {0xaa, 0x86},
+ {0xab, 0x03},
+ {0xac, 0x94},
+ {0xad, 0x03},
+ {0xae, 0xfa},
+ {0xaf, 0x00},
+ {0xb0, 0x00},
+ {0xb1, 0x00},
+ {0xb2, 0x21},
+ {0xb3, 0x00},
+ {0xb4, 0x4c},
+ {0xb5, 0x00},
+ {0xb6, 0x6b},
+ {0xb7, 0x00},
+ {0xb8, 0x85},
+ {0xb9, 0x00},
+ {0xba, 0x9a},
+ {0xbb, 0x00},
+ {0xbc, 0xad},
+ {0xbd, 0x00},
+ {0xbe, 0xbe},
+ {0xbf, 0x00},
+ {0xc0, 0xcd},
+ {0xc1, 0x01},
+ {0xc2, 0x01},
+ {0xc3, 0x01},
+ {0xc4, 0x29},
+ {0xc5, 0x01},
+ {0xc6, 0x68},
+ {0xc7, 0x01},
+ {0xc8, 0x98},
+ {0xc9, 0x01},
+ {0xca, 0xe5},
+ {0xcb, 0x02},
+ {0xcc, 0x1e},
+ {0xcd, 0x02},
+ {0xce, 0x20},
+ {0xcf, 0x02},
+ {0xd0, 0x52},
+ {0xd1, 0x02},
+ {0xd2, 0x88},
+ {0xd3, 0x02},
+ {0xd4, 0xaa},
+ {0xd5, 0x02},
+ {0xd6, 0xd7},
+ {0xd7, 0x02},
+ {0xd8, 0xf7},
+ {0xd9, 0x03},
+ {0xda, 0x21},
+ {0xdb, 0x03},
+ {0xdc, 0x2e},
+ {0xdd, 0x03},
+ {0xde, 0x3d},
+ {0xdf, 0x03},
+ {0xe0, 0x4c},
+ {0xe1, 0x03},
+ {0xe2, 0x5e},
+ {0xe3, 0x03},
+ {0xe4, 0x71},
+ {0xe5, 0x03},
+ {0xe6, 0x86},
+ {0xe7, 0x03},
+ {0xe8, 0x94},
+ {0xe9, 0x03},
+ {0xea, 0xfa},
+ /* Select CMD2 Page0 (Undocumented) */
+ {0xff, 0x01},
+ /* Reload CMD1: Don't reload default value to register */
+ {0xfb, 0x01},
+ /* Select CMD2 Page1 (Undocumented) */
+ {0xff, 0x02},
+ /* Reload CMD1: Don't reload default value to register */
+ {0xfb, 0x01},
+ /* Select CMD2 Page3 (Undocumented) */
+ {0xff, 0x04},
+ /* Reload CMD1: Don't reload default value to register */
+ {0xfb, 0x01},
+ /* Select CMD1 */
+ {0xff, 0x00},
+ {0xd3, 0x05}, /* RGBMIPICTRL: VSYNC back porch = 5 */
+ {0xd4, 0x04}, /* RGBMIPICTRL: VSYNC front porch = 4 */
+};
+
+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;
+
+ if (khadas_ts050->prepared)
+ return 0;
+
+ 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 < ARRAY_SIZE(init_code); i++) {
+ err = mipi_dsi_dcs_write(khadas_ts050->link,
+ init_code[i].cmd,
+ &init_code[i].data, 1);
+ 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);
+
+ khadas_ts050->prepared = true;
+
+ 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;
+
+ if (!khadas_ts050->prepared)
+ return 0;
+
+ khadas_ts050->prepared = false;
+
+ 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_enable(struct drm_panel *panel)
+{
+ struct khadas_ts050_panel *khadas_ts050 = to_khadas_ts050_panel(panel);
+
+ khadas_ts050->enabled = true;
+
+ 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;
+
+ if (!khadas_ts050->enabled)
+ return 0;
+
+ 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);
+
+ khadas_ts050->enabled = false;
+
+ return 0;
+}
+
+static const struct drm_display_mode default_mode = {
+ .clock = 120000,
+ .hdisplay = 1088,
+ .hsync_start = 1088 + 104,
+ .hsync_end = 1088 + 104 + 4,
+ .htotal = 1088 + 104 + 4 + 127,
+ .vdisplay = 1920,
+ .vsync_start = 1920 + 4,
+ .vsync_end = 1920 + 4 + 2,
+ .vtotal = 1920 + 4 + 2 + 3,
+ .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,
+ .enable = khadas_ts050_panel_enable,
+ .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", },
+ { /* 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");
+
+ drm_panel_init(&khadas_ts050->base, &khadas_ts050->link->dev,
+ &khadas_ts050_panel_funcs, DRM_MODE_CONNECTOR_DSI);
+
+ 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;
+
+ 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_kzalloc(&dsi->dev, sizeof(*khadas_ts050),
+ GFP_KERNEL);
+ if (!khadas_ts050)
+ return -ENOMEM;
+
+ 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);
+ drm_panel_disable(&khadas_ts050->base);
+ drm_panel_unprepare(&khadas_ts050->base);
+}
+
+static void khadas_ts050_panel_shutdown(struct mipi_dsi_device *dsi)
+{
+ struct khadas_ts050_panel *khadas_ts050 = mipi_dsi_get_drvdata(dsi);
+
+ drm_panel_disable(&khadas_ts050->base);
+ drm_panel_unprepare(&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,
+ .shutdown = khadas_ts050_panel_shutdown,
+};
+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 000000000..17f8d80cf
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-kingdisplay-kd097d04.c
@@ -0,0 +1,460 @@
+// 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;
+
+ bool prepared;
+ bool enabled;
+};
+
+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;
+
+ if (!kingdisplay->enabled)
+ return 0;
+
+ err = mipi_dsi_dcs_set_display_off(kingdisplay->link);
+ if (err < 0)
+ dev_err(panel->dev, "failed to set display off: %d\n", err);
+
+ kingdisplay->enabled = false;
+
+ return 0;
+}
+
+static int kingdisplay_panel_unprepare(struct drm_panel *panel)
+{
+ struct kingdisplay_panel *kingdisplay = to_kingdisplay_panel(panel);
+ int err;
+
+ if (!kingdisplay->prepared)
+ return 0;
+
+ 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;
+
+ kingdisplay->prepared = false;
+
+ 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;
+
+ if (kingdisplay->prepared)
+ return 0;
+
+ 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);
+
+ kingdisplay->prepared = true;
+
+ 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 int kingdisplay_panel_enable(struct drm_panel *panel)
+{
+ struct kingdisplay_panel *kingdisplay = to_kingdisplay_panel(panel);
+
+ if (kingdisplay->enabled)
+ return 0;
+
+ kingdisplay->enabled = true;
+
+ return 0;
+}
+
+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,
+ .enable = kingdisplay_panel_enable,
+ .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;
+ }
+
+ drm_panel_init(&kingdisplay->base, &kingdisplay->link->dev,
+ &kingdisplay_panel_funcs, DRM_MODE_CONNECTOR_DSI);
+
+ 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;
+
+ kingdisplay = devm_kzalloc(&dsi->dev, sizeof(*kingdisplay), GFP_KERNEL);
+ if (!kingdisplay)
+ return -ENOMEM;
+
+ 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 = drm_panel_unprepare(&kingdisplay->base);
+ if (err < 0)
+ dev_err(&dsi->dev, "failed to unprepare panel: %d\n", err);
+
+ err = drm_panel_disable(&kingdisplay->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);
+
+ kingdisplay_panel_del(kingdisplay);
+}
+
+static void kingdisplay_panel_shutdown(struct mipi_dsi_device *dsi)
+{
+ struct kingdisplay_panel *kingdisplay = mipi_dsi_get_drvdata(dsi);
+
+ drm_panel_unprepare(&kingdisplay->base);
+ drm_panel_disable(&kingdisplay->base);
+}
+
+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,
+ .shutdown = kingdisplay_panel_shutdown,
+};
+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 000000000..5619f186d
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-leadtek-ltk050h3146w.c
@@ -0,0 +1,671 @@
+// 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/of_device.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 struct drm_display_mode *mode;
+ int (*init)(struct ltk050h3146w *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;
+ bool prepared;
+};
+
+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);
+}
+
+#define dsi_dcs_write_seq(dsi, cmd, seq...) do { \
+ static const u8 b[] = { cmd, seq }; \
+ int ret; \
+ ret = mipi_dsi_dcs_write_buffer(dsi, b, ARRAY_SIZE(b)); \
+ if (ret < 0) \
+ return ret; \
+ } while (0)
+
+static int ltk050h3146w_init_sequence(struct ltk050h3146w *ctx)
+{
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+ int ret;
+
+ /*
+ * Init sequence was supplied by the panel vendor without much
+ * documentation.
+ */
+ dsi_dcs_write_seq(dsi, 0xdf, 0x93, 0x65, 0xf8);
+ dsi_dcs_write_seq(dsi, 0xb0, 0x01, 0x03, 0x02, 0x00, 0x64, 0x06,
+ 0x01);
+ dsi_dcs_write_seq(dsi, 0xb2, 0x00, 0xb5);
+ dsi_dcs_write_seq(dsi, 0xb3, 0x00, 0xb5);
+ dsi_dcs_write_seq(dsi, 0xb7, 0x00, 0xbf, 0x00, 0x00, 0xbf, 0x00);
+
+ dsi_dcs_write_seq(dsi, 0xb9, 0x00, 0xc4, 0x23, 0x07);
+ dsi_dcs_write_seq(dsi, 0xbb, 0x02, 0x01, 0x24, 0x00, 0x28, 0x0f,
+ 0x28, 0x04, 0xcc, 0xcc, 0xcc);
+ dsi_dcs_write_seq(dsi, 0xbc, 0x0f, 0x04);
+ dsi_dcs_write_seq(dsi, 0xbe, 0x1e, 0xf2);
+ dsi_dcs_write_seq(dsi, 0xc0, 0x26, 0x03);
+ dsi_dcs_write_seq(dsi, 0xc1, 0x00, 0x12);
+ dsi_dcs_write_seq(dsi, 0xc3, 0x04, 0x02, 0x02, 0x76, 0x01, 0x80,
+ 0x80);
+ dsi_dcs_write_seq(dsi, 0xc4, 0x24, 0x80, 0xb4, 0x81, 0x12, 0x0f,
+ 0x16, 0x00, 0x00);
+ dsi_dcs_write_seq(dsi, 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);
+ dsi_dcs_write_seq(dsi, 0xd0, 0x1e, 0x1f, 0x57, 0x58, 0x48, 0x4a,
+ 0x44, 0x46, 0x40, 0x1f, 0x42, 0x1f, 0x1f, 0x1f,
+ 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f);
+ dsi_dcs_write_seq(dsi, 0xd1, 0x1e, 0x1f, 0x57, 0x58, 0x49, 0x4b,
+ 0x45, 0x47, 0x41, 0x1f, 0x43, 0x1f, 0x1f, 0x1f,
+ 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f);
+ dsi_dcs_write_seq(dsi, 0xd2, 0x1f, 0x1e, 0x17, 0x18, 0x07, 0x05,
+ 0x0b, 0x09, 0x03, 0x1f, 0x01, 0x1f, 0x1f, 0x1f,
+ 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f);
+ dsi_dcs_write_seq(dsi, 0xd3, 0x1f, 0x1e, 0x17, 0x18, 0x06, 0x04,
+ 0x0a, 0x08, 0x02, 0x1f, 0x00, 0x1f, 0x1f, 0x1f,
+ 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f);
+ dsi_dcs_write_seq(dsi, 0xd4, 0x00, 0x00, 0x00, 0x0c, 0x06, 0x20,
+ 0x01, 0x02, 0x00, 0x60, 0x15, 0xb0, 0x30, 0x03,
+ 0x04, 0x00, 0x60, 0x72, 0x0a, 0x00, 0x60, 0x08);
+ dsi_dcs_write_seq(dsi, 0xd5, 0x00, 0x06, 0x06, 0x00, 0x30, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xbc, 0x50, 0x00, 0x05,
+ 0x21, 0x00, 0x60);
+ dsi_dcs_write_seq(dsi, 0xdd, 0x2c, 0xa3, 0x00);
+ dsi_dcs_write_seq(dsi, 0xde, 0x02);
+ dsi_dcs_write_seq(dsi, 0xb2, 0x32, 0x1c);
+ dsi_dcs_write_seq(dsi, 0xb7, 0x3b, 0x70, 0x00, 0x04);
+ dsi_dcs_write_seq(dsi, 0xc1, 0x11);
+ dsi_dcs_write_seq(dsi, 0xbb, 0x21, 0x22, 0x23, 0x24, 0x36, 0x37);
+ dsi_dcs_write_seq(dsi, 0xc2, 0x20, 0x38, 0x1e, 0x84);
+ dsi_dcs_write_seq(dsi, 0xde, 0x00);
+
+ ret = mipi_dsi_dcs_set_tear_on(dsi, 1);
+ if (ret < 0) {
+ dev_err(ctx->dev, "failed to set tear on: %d\n", ret);
+ return ret;
+ }
+
+ msleep(60);
+
+ return 0;
+}
+
+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 = &ltk050h3146w_mode,
+ .init = ltk050h3146w_init_sequence,
+};
+
+static int ltk050h3146w_a2_select_page(struct ltk050h3146w *ctx, int page)
+{
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+ u8 d[3] = { 0x98, 0x81, page };
+
+ return mipi_dsi_dcs_write(dsi, 0xff, d, ARRAY_SIZE(d));
+}
+
+static int ltk050h3146w_a2_write_page(struct ltk050h3146w *ctx, int page,
+ const struct ltk050h3146w_cmd *cmds,
+ int num)
+{
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+ int i, ret;
+
+ ret = ltk050h3146w_a2_select_page(ctx, page);
+ if (ret < 0) {
+ dev_err(ctx->dev, "failed to select page %d: %d\n", page, ret);
+ return ret;
+ }
+
+ for (i = 0; i < num; i++) {
+ ret = mipi_dsi_generic_write(dsi, &cmds[i],
+ sizeof(struct ltk050h3146w_cmd));
+ if (ret < 0) {
+ dev_err(ctx->dev, "failed to write page %d init cmds: %d\n", page, ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int ltk050h3146w_a2_init_sequence(struct ltk050h3146w *ctx)
+{
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+ int ret;
+
+ /*
+ * Init sequence was supplied by the panel vendor without much
+ * documentation.
+ */
+ ret = ltk050h3146w_a2_write_page(ctx, 3, page3_cmds,
+ ARRAY_SIZE(page3_cmds));
+ if (ret < 0)
+ return ret;
+
+ ret = ltk050h3146w_a2_write_page(ctx, 4, page4_cmds,
+ ARRAY_SIZE(page4_cmds));
+ if (ret < 0)
+ return ret;
+
+ ret = ltk050h3146w_a2_write_page(ctx, 1, page1_cmds,
+ ARRAY_SIZE(page1_cmds));
+ if (ret < 0)
+ return ret;
+
+ ret = ltk050h3146w_a2_select_page(ctx, 0);
+ if (ret < 0) {
+ dev_err(ctx->dev, "failed to select page 0: %d\n", ret);
+ return ret;
+ }
+
+ /* vendor code called this without param, where there should be one */
+ ret = mipi_dsi_dcs_set_tear_on(dsi, 0);
+ if (ret < 0) {
+ dev_err(ctx->dev, "failed to set tear on: %d\n", ret);
+ return ret;
+ }
+
+ msleep(60);
+
+ return 0;
+}
+
+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 = &ltk050h3146w_a2_mode,
+ .init = ltk050h3146w_a2_init_sequence,
+};
+
+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);
+ int ret;
+
+ if (!ctx->prepared)
+ return 0;
+
+ ret = mipi_dsi_dcs_set_display_off(dsi);
+ if (ret < 0) {
+ dev_err(ctx->dev, "failed to set display off: %d\n", ret);
+ return 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;
+ }
+
+ regulator_disable(ctx->iovcc);
+ regulator_disable(ctx->vci);
+
+ ctx->prepared = false;
+
+ 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);
+ int ret;
+
+ if (ctx->prepared)
+ return 0;
+
+ dev_dbg(ctx->dev, "Resetting the panel\n");
+ ret = regulator_enable(ctx->vci);
+ 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_vci;
+ }
+
+ gpiod_set_value_cansleep(ctx->reset_gpio, 1);
+ usleep_range(5000, 6000);
+ gpiod_set_value_cansleep(ctx->reset_gpio, 0);
+ msleep(20);
+
+ ret = ctx->panel_desc->init(ctx);
+ if (ret < 0) {
+ dev_err(ctx->dev, "Panel init sequence failed: %d\n", ret);
+ goto disable_iovcc;
+ }
+
+ 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_iovcc;
+ }
+
+ /* T9: 120ms */
+ msleep(120);
+
+ 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_iovcc;
+ }
+
+ msleep(50);
+
+ ctx->prepared = true;
+
+ return 0;
+
+disable_iovcc:
+ regulator_disable(ctx->iovcc);
+disable_vci:
+ regulator_disable(ctx->vci);
+ return ret;
+}
+
+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_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ 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->vci = devm_regulator_get(dev, "vci");
+ if (IS_ERR(ctx->vci)) {
+ ret = PTR_ERR(ctx->vci);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "Failed to request vci regulator: %d\n", ret);
+ return ret;
+ }
+
+ ctx->iovcc = devm_regulator_get(dev, "iovcc");
+ if (IS_ERR(ctx->iovcc)) {
+ ret = PTR_ERR(ctx->iovcc);
+ if (ret != -EPROBE_DEFER)
+ 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;
+
+ drm_panel_init(&ctx->panel, &dsi->dev, &ltk050h3146w_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ ret = drm_panel_of_backlight(&ctx->panel);
+ if (ret)
+ return ret;
+
+ drm_panel_add(&ctx->panel);
+
+ ret = mipi_dsi_attach(dsi);
+ if (ret < 0) {
+ dev_err(dev, "mipi_dsi_attach failed: %d\n", ret);
+ drm_panel_remove(&ctx->panel);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void ltk050h3146w_shutdown(struct mipi_dsi_device *dsi)
+{
+ struct ltk050h3146w *ctx = mipi_dsi_get_drvdata(dsi);
+ int ret;
+
+ ret = drm_panel_unprepare(&ctx->panel);
+ if (ret < 0)
+ dev_err(&dsi->dev, "Failed to unprepare panel: %d\n", ret);
+
+ ret = drm_panel_disable(&ctx->panel);
+ if (ret < 0)
+ dev_err(&dsi->dev, "Failed to disable panel: %d\n", ret);
+}
+
+static void ltk050h3146w_remove(struct mipi_dsi_device *dsi)
+{
+ struct ltk050h3146w *ctx = mipi_dsi_get_drvdata(dsi);
+ int ret;
+
+ ltk050h3146w_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 of_device_id ltk050h3146w_of_match[] = {
+ {
+ .compatible = "leadtek,ltk050h3146w",
+ .data = &ltk050h3146w_data,
+ },
+ {
+ .compatible = "leadtek,ltk050h3146w-a2",
+ .data = &ltk050h3146w_a2_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,
+ .shutdown = ltk050h3146w_shutdown,
+};
+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 000000000..39e408c9f
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-leadtek-ltk500hd1829.c
@@ -0,0 +1,513 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2019 Theobroma Systems Design und Consulting GmbH
+ *
+ * base on panel-kingdisplay-kd097d04.c
+ * Copyright (c) 2017, Fuzhou Rockchip Electronics Co., Ltd
+ */
+
+#include <linux/backlight.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regulator/consumer.h>
+
+#include <video/mipi_display.h>
+
+#include <drm/drm_crtc.h>
+#include <drm/drm_device.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+
+struct ltk500hd1829 {
+ struct device *dev;
+ struct drm_panel panel;
+ struct gpio_desc *reset_gpio;
+ struct regulator *vcc;
+ struct regulator *iovcc;
+ bool prepared;
+};
+
+struct ltk500hd1829_cmd {
+ char cmd;
+ char data;
+};
+
+/*
+ * There is no description in the Reference Manual about these commands.
+ * We received them from the vendor, so just use them as is.
+ */
+static const struct ltk500hd1829_cmd init_code[] = {
+ { 0xE0, 0x00 },
+ { 0xE1, 0x93 },
+ { 0xE2, 0x65 },
+ { 0xE3, 0xF8 },
+ { 0x80, 0x03 },
+ { 0xE0, 0x04 },
+ { 0x2D, 0x03 },
+ { 0xE0, 0x01 },
+ { 0x00, 0x00 },
+ { 0x01, 0xB6 },
+ { 0x03, 0x00 },
+ { 0x04, 0xC5 },
+ { 0x17, 0x00 },
+ { 0x18, 0xBF },
+ { 0x19, 0x01 },
+ { 0x1A, 0x00 },
+ { 0x1B, 0xBF },
+ { 0x1C, 0x01 },
+ { 0x1F, 0x7C },
+ { 0x20, 0x26 },
+ { 0x21, 0x26 },
+ { 0x22, 0x4E },
+ { 0x37, 0x09 },
+ { 0x38, 0x04 },
+ { 0x39, 0x08 },
+ { 0x3A, 0x1F },
+ { 0x3B, 0x1F },
+ { 0x3C, 0x78 },
+ { 0x3D, 0xFF },
+ { 0x3E, 0xFF },
+ { 0x3F, 0x00 },
+ { 0x40, 0x04 },
+ { 0x41, 0xA0 },
+ { 0x43, 0x0F },
+ { 0x44, 0x0A },
+ { 0x45, 0x24 },
+ { 0x55, 0x01 },
+ { 0x56, 0x01 },
+ { 0x57, 0xA5 },
+ { 0x58, 0x0A },
+ { 0x59, 0x4A },
+ { 0x5A, 0x38 },
+ { 0x5B, 0x10 },
+ { 0x5C, 0x19 },
+ { 0x5D, 0x7C },
+ { 0x5E, 0x64 },
+ { 0x5F, 0x54 },
+ { 0x60, 0x48 },
+ { 0x61, 0x44 },
+ { 0x62, 0x35 },
+ { 0x63, 0x3A },
+ { 0x64, 0x24 },
+ { 0x65, 0x3B },
+ { 0x66, 0x39 },
+ { 0x67, 0x37 },
+ { 0x68, 0x56 },
+ { 0x69, 0x41 },
+ { 0x6A, 0x47 },
+ { 0x6B, 0x2F },
+ { 0x6C, 0x23 },
+ { 0x6D, 0x13 },
+ { 0x6E, 0x02 },
+ { 0x6F, 0x08 },
+ { 0x70, 0x7C },
+ { 0x71, 0x64 },
+ { 0x72, 0x54 },
+ { 0x73, 0x48 },
+ { 0x74, 0x44 },
+ { 0x75, 0x35 },
+ { 0x76, 0x3A },
+ { 0x77, 0x22 },
+ { 0x78, 0x3B },
+ { 0x79, 0x39 },
+ { 0x7A, 0x38 },
+ { 0x7B, 0x52 },
+ { 0x7C, 0x41 },
+ { 0x7D, 0x47 },
+ { 0x7E, 0x2F },
+ { 0x7F, 0x23 },
+ { 0x80, 0x13 },
+ { 0x81, 0x02 },
+ { 0x82, 0x08 },
+ { 0xE0, 0x02 },
+ { 0x00, 0x57 },
+ { 0x01, 0x77 },
+ { 0x02, 0x44 },
+ { 0x03, 0x46 },
+ { 0x04, 0x48 },
+ { 0x05, 0x4A },
+ { 0x06, 0x4C },
+ { 0x07, 0x4E },
+ { 0x08, 0x50 },
+ { 0x09, 0x55 },
+ { 0x0A, 0x52 },
+ { 0x0B, 0x55 },
+ { 0x0C, 0x55 },
+ { 0x0D, 0x55 },
+ { 0x0E, 0x55 },
+ { 0x0F, 0x55 },
+ { 0x10, 0x55 },
+ { 0x11, 0x55 },
+ { 0x12, 0x55 },
+ { 0x13, 0x40 },
+ { 0x14, 0x55 },
+ { 0x15, 0x55 },
+ { 0x16, 0x57 },
+ { 0x17, 0x77 },
+ { 0x18, 0x45 },
+ { 0x19, 0x47 },
+ { 0x1A, 0x49 },
+ { 0x1B, 0x4B },
+ { 0x1C, 0x4D },
+ { 0x1D, 0x4F },
+ { 0x1E, 0x51 },
+ { 0x1F, 0x55 },
+ { 0x20, 0x53 },
+ { 0x21, 0x55 },
+ { 0x22, 0x55 },
+ { 0x23, 0x55 },
+ { 0x24, 0x55 },
+ { 0x25, 0x55 },
+ { 0x26, 0x55 },
+ { 0x27, 0x55 },
+ { 0x28, 0x55 },
+ { 0x29, 0x41 },
+ { 0x2A, 0x55 },
+ { 0x2B, 0x55 },
+ { 0x2C, 0x57 },
+ { 0x2D, 0x77 },
+ { 0x2E, 0x4F },
+ { 0x2F, 0x4D },
+ { 0x30, 0x4B },
+ { 0x31, 0x49 },
+ { 0x32, 0x47 },
+ { 0x33, 0x45 },
+ { 0x34, 0x41 },
+ { 0x35, 0x55 },
+ { 0x36, 0x53 },
+ { 0x37, 0x55 },
+ { 0x38, 0x55 },
+ { 0x39, 0x55 },
+ { 0x3A, 0x55 },
+ { 0x3B, 0x55 },
+ { 0x3C, 0x55 },
+ { 0x3D, 0x55 },
+ { 0x3E, 0x55 },
+ { 0x3F, 0x51 },
+ { 0x40, 0x55 },
+ { 0x41, 0x55 },
+ { 0x42, 0x57 },
+ { 0x43, 0x77 },
+ { 0x44, 0x4E },
+ { 0x45, 0x4C },
+ { 0x46, 0x4A },
+ { 0x47, 0x48 },
+ { 0x48, 0x46 },
+ { 0x49, 0x44 },
+ { 0x4A, 0x40 },
+ { 0x4B, 0x55 },
+ { 0x4C, 0x52 },
+ { 0x4D, 0x55 },
+ { 0x4E, 0x55 },
+ { 0x4F, 0x55 },
+ { 0x50, 0x55 },
+ { 0x51, 0x55 },
+ { 0x52, 0x55 },
+ { 0x53, 0x55 },
+ { 0x54, 0x55 },
+ { 0x55, 0x50 },
+ { 0x56, 0x55 },
+ { 0x57, 0x55 },
+ { 0x58, 0x40 },
+ { 0x59, 0x00 },
+ { 0x5A, 0x00 },
+ { 0x5B, 0x10 },
+ { 0x5C, 0x09 },
+ { 0x5D, 0x30 },
+ { 0x5E, 0x01 },
+ { 0x5F, 0x02 },
+ { 0x60, 0x30 },
+ { 0x61, 0x03 },
+ { 0x62, 0x04 },
+ { 0x63, 0x06 },
+ { 0x64, 0x6A },
+ { 0x65, 0x75 },
+ { 0x66, 0x0F },
+ { 0x67, 0xB3 },
+ { 0x68, 0x0B },
+ { 0x69, 0x06 },
+ { 0x6A, 0x6A },
+ { 0x6B, 0x10 },
+ { 0x6C, 0x00 },
+ { 0x6D, 0x04 },
+ { 0x6E, 0x04 },
+ { 0x6F, 0x88 },
+ { 0x70, 0x00 },
+ { 0x71, 0x00 },
+ { 0x72, 0x06 },
+ { 0x73, 0x7B },
+ { 0x74, 0x00 },
+ { 0x75, 0xBC },
+ { 0x76, 0x00 },
+ { 0x77, 0x05 },
+ { 0x78, 0x2E },
+ { 0x79, 0x00 },
+ { 0x7A, 0x00 },
+ { 0x7B, 0x00 },
+ { 0x7C, 0x00 },
+ { 0x7D, 0x03 },
+ { 0x7E, 0x7B },
+ { 0xE0, 0x04 },
+ { 0x09, 0x10 },
+ { 0x2B, 0x2B },
+ { 0x2E, 0x44 },
+ { 0xE0, 0x00 },
+ { 0xE6, 0x02 },
+ { 0xE7, 0x02 },
+ { 0x35, 0x00 },
+};
+
+static inline
+struct ltk500hd1829 *panel_to_ltk500hd1829(struct drm_panel *panel)
+{
+ return container_of(panel, struct ltk500hd1829, panel);
+}
+
+static int ltk500hd1829_unprepare(struct drm_panel *panel)
+{
+ struct ltk500hd1829 *ctx = panel_to_ltk500hd1829(panel);
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+ int ret;
+
+ if (!ctx->prepared)
+ return 0;
+
+ ret = mipi_dsi_dcs_set_display_off(dsi);
+ if (ret < 0)
+ 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);
+
+ ctx->prepared = false;
+
+ return 0;
+}
+
+static int ltk500hd1829_prepare(struct drm_panel *panel)
+{
+ struct ltk500hd1829 *ctx = panel_to_ltk500hd1829(panel);
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+ unsigned int i;
+ int ret;
+
+ if (ctx->prepared)
+ return 0;
+
+ ret = regulator_enable(ctx->vcc);
+ if (ret < 0) {
+ 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 < ARRAY_SIZE(init_code); i++) {
+ ret = mipi_dsi_generic_write(dsi, &init_code[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;
+ }
+
+ ctx->prepared = true;
+
+ return 0;
+
+disable_iovcc:
+ regulator_disable(ctx->iovcc);
+disable_vcc:
+ regulator_disable(ctx->vcc);
+ return ret;
+}
+
+static const struct drm_display_mode default_mode = {
+ .hdisplay = 720,
+ .hsync_start = 720 + 50,
+ .hsync_end = 720 + 50 + 50,
+ .htotal = 720 + 50 + 50 + 50,
+ .vdisplay = 1280,
+ .vsync_start = 1280 + 30,
+ .vsync_end = 1280 + 30 + 4,
+ .vtotal = 1280 + 30 + 4 + 12,
+ .clock = 69217,
+ .width_mm = 62,
+ .height_mm = 110,
+};
+
+static int ltk500hd1829_get_modes(struct drm_panel *panel,
+ struct drm_connector *connector)
+{
+ struct ltk500hd1829 *ctx = panel_to_ltk500hd1829(panel);
+ struct drm_display_mode *mode;
+
+ mode = drm_mode_duplicate(connector->dev, &default_mode);
+ if (!mode) {
+ 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 ltk500hd1829_funcs = {
+ .unprepare = ltk500hd1829_unprepare,
+ .prepare = ltk500hd1829_prepare,
+ .get_modes = ltk500hd1829_get_modes,
+};
+
+static int ltk500hd1829_probe(struct mipi_dsi_device *dsi)
+{
+ struct ltk500hd1829 *ctx;
+ struct device *dev = &dsi->dev;
+ int ret;
+
+ ctx = devm_kzalloc(&dsi->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(ctx->reset_gpio)) {
+ 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;
+
+ drm_panel_init(&ctx->panel, &dsi->dev, &ltk500hd1829_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ ret = drm_panel_of_backlight(&ctx->panel);
+ if (ret)
+ return ret;
+
+ drm_panel_add(&ctx->panel);
+
+ ret = mipi_dsi_attach(dsi);
+ if (ret < 0) {
+ dev_err(dev, "mipi_dsi_attach failed: %d\n", ret);
+ drm_panel_remove(&ctx->panel);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void ltk500hd1829_shutdown(struct mipi_dsi_device *dsi)
+{
+ struct ltk500hd1829 *ctx = mipi_dsi_get_drvdata(dsi);
+ int ret;
+
+ ret = drm_panel_unprepare(&ctx->panel);
+ if (ret < 0)
+ dev_err(&dsi->dev, "Failed to unprepare panel: %d\n", ret);
+
+ ret = drm_panel_disable(&ctx->panel);
+ if (ret < 0)
+ dev_err(&dsi->dev, "Failed to disable panel: %d\n", ret);
+}
+
+static void ltk500hd1829_remove(struct mipi_dsi_device *dsi)
+{
+ struct ltk500hd1829 *ctx = mipi_dsi_get_drvdata(dsi);
+ int ret;
+
+ ltk500hd1829_shutdown(dsi);
+
+ ret = mipi_dsi_detach(dsi);
+ if (ret < 0)
+ 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,ltk500hd1829", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ltk500hd1829_of_match);
+
+static struct mipi_dsi_driver ltk500hd1829_driver = {
+ .driver = {
+ .name = "panel-leadtek-ltk500hd1829",
+ .of_match_table = ltk500hd1829_of_match,
+ },
+ .probe = ltk500hd1829_probe,
+ .remove = ltk500hd1829_remove,
+ .shutdown = ltk500hd1829_shutdown,
+};
+module_mipi_dsi_driver(ltk500hd1829_driver);
+
+MODULE_AUTHOR("Heiko Stuebner <heiko.stuebner@theobroma-systems.com>");
+MODULE_DESCRIPTION("Leadtek LTK500HD1829 panel driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/panel/panel-lg-lb035q02.c b/drivers/gpu/drm/panel/panel-lg-lb035q02.c
new file mode 100644
index 000000000..9d0d4faa3
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-lg-lb035q02.c
@@ -0,0 +1,242 @@
+// 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_kzalloc(&spi->dev, sizeof(*lcd), GFP_KERNEL);
+ if (!lcd)
+ return -ENOMEM;
+
+ spi_set_drvdata(spi, lcd);
+ lcd->spi = spi;
+
+ lcd->enable_gpio = devm_gpiod_get(&spi->dev, "enable", GPIOD_OUT_LOW);
+ if (IS_ERR(lcd->enable_gpio)) {
+ dev_err(&spi->dev, "failed to parse enable gpio\n");
+ return PTR_ERR(lcd->enable_gpio);
+ }
+
+ ret = lb035q02_init(lcd);
+ if (ret < 0)
+ return ret;
+
+ drm_panel_init(&lcd->panel, &lcd->spi->dev, &lb035q02_funcs,
+ DRM_MODE_CONNECTOR_DPI);
+
+ 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-lg4573.c b/drivers/gpu/drm/panel/panel-lg-lg4573.c
new file mode 100644
index 000000000..cf246d15b
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-lg-lg4573.c
@@ -0,0 +1,295 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2015 Heiko Schocher <hs@denx.de>
+ *
+ * from:
+ * drivers/gpu/drm/panel/panel-ld9040.c
+ * ld9040 AMOLED LCD drm_panel driver.
+ *
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd
+ * Derived from drivers/video/backlight/ld9040.c
+ *
+ * Andrzej Hajda <a.hajda@samsung.com>
+*/
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+
+#include <video/mipi_display.h>
+#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;
+ struct videomode vm;
+};
+
+static inline struct lg4573 *panel_to_lg4573(struct drm_panel *panel)
+{
+ return container_of(panel, struct lg4573, panel);
+}
+
+static int lg4573_spi_write_u16(struct lg4573 *ctx, u16 data)
+{
+ struct spi_transfer xfer = {
+ .len = 2,
+ };
+ __be16 temp = cpu_to_be16(data);
+ struct spi_message msg;
+
+ dev_dbg(ctx->panel.dev, "writing data: %x\n", data);
+ xfer.tx_buf = &temp;
+ spi_message_init(&msg);
+ spi_message_add_tail(&xfer, &msg);
+
+ return spi_sync(ctx->spi, &msg);
+}
+
+static int lg4573_spi_write_u16_array(struct lg4573 *ctx, const u16 *buffer,
+ unsigned int count)
+{
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < count; i++) {
+ ret = lg4573_spi_write_u16(ctx, buffer[i]);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int lg4573_spi_write_dcs(struct lg4573 *ctx, u8 dcs)
+{
+ return lg4573_spi_write_u16(ctx, (0x70 << 8 | dcs));
+}
+
+static int lg4573_display_on(struct lg4573 *ctx)
+{
+ int ret;
+
+ ret = lg4573_spi_write_dcs(ctx, MIPI_DCS_EXIT_SLEEP_MODE);
+ if (ret)
+ return ret;
+
+ msleep(5);
+
+ return lg4573_spi_write_dcs(ctx, MIPI_DCS_SET_DISPLAY_ON);
+}
+
+static int lg4573_display_off(struct lg4573 *ctx)
+{
+ int ret;
+
+ ret = lg4573_spi_write_dcs(ctx, MIPI_DCS_SET_DISPLAY_OFF);
+ if (ret)
+ return ret;
+
+ msleep(120);
+
+ return lg4573_spi_write_dcs(ctx, MIPI_DCS_ENTER_SLEEP_MODE);
+}
+
+static int lg4573_display_mode_settings(struct lg4573 *ctx)
+{
+ static const u16 display_mode_settings[] = {
+ 0x703A, 0x7270, 0x70B1, 0x7208,
+ 0x723B, 0x720F, 0x70B2, 0x7200,
+ 0x72C8, 0x70B3, 0x7200, 0x70B4,
+ 0x7200, 0x70B5, 0x7242, 0x7210,
+ 0x7210, 0x7200, 0x7220, 0x70B6,
+ 0x720B, 0x720F, 0x723C, 0x7213,
+ 0x7213, 0x72E8, 0x70B7, 0x7246,
+ 0x7206, 0x720C, 0x7200, 0x7200,
+ };
+
+ dev_dbg(ctx->panel.dev, "transfer display mode settings\n");
+ return lg4573_spi_write_u16_array(ctx, display_mode_settings,
+ ARRAY_SIZE(display_mode_settings));
+}
+
+static int lg4573_power_settings(struct lg4573 *ctx)
+{
+ static const u16 power_settings[] = {
+ 0x70C0, 0x7201, 0x7211, 0x70C3,
+ 0x7207, 0x7203, 0x7204, 0x7204,
+ 0x7204, 0x70C4, 0x7212, 0x7224,
+ 0x7218, 0x7218, 0x7202, 0x7249,
+ 0x70C5, 0x726F, 0x70C6, 0x7241,
+ 0x7263,
+ };
+
+ dev_dbg(ctx->panel.dev, "transfer power settings\n");
+ return lg4573_spi_write_u16_array(ctx, power_settings,
+ ARRAY_SIZE(power_settings));
+}
+
+static int lg4573_gamma_settings(struct lg4573 *ctx)
+{
+ static const u16 gamma_settings[] = {
+ 0x70D0, 0x7203, 0x7207, 0x7273,
+ 0x7235, 0x7200, 0x7201, 0x7220,
+ 0x7200, 0x7203, 0x70D1, 0x7203,
+ 0x7207, 0x7273, 0x7235, 0x7200,
+ 0x7201, 0x7220, 0x7200, 0x7203,
+ 0x70D2, 0x7203, 0x7207, 0x7273,
+ 0x7235, 0x7200, 0x7201, 0x7220,
+ 0x7200, 0x7203, 0x70D3, 0x7203,
+ 0x7207, 0x7273, 0x7235, 0x7200,
+ 0x7201, 0x7220, 0x7200, 0x7203,
+ 0x70D4, 0x7203, 0x7207, 0x7273,
+ 0x7235, 0x7200, 0x7201, 0x7220,
+ 0x7200, 0x7203, 0x70D5, 0x7203,
+ 0x7207, 0x7273, 0x7235, 0x7200,
+ 0x7201, 0x7220, 0x7200, 0x7203,
+ };
+
+ dev_dbg(ctx->panel.dev, "transfer gamma settings\n");
+ return lg4573_spi_write_u16_array(ctx, gamma_settings,
+ ARRAY_SIZE(gamma_settings));
+}
+
+static int lg4573_init(struct lg4573 *ctx)
+{
+ int ret;
+
+ dev_dbg(ctx->panel.dev, "initializing LCD\n");
+
+ ret = lg4573_display_mode_settings(ctx);
+ if (ret)
+ return ret;
+
+ ret = lg4573_power_settings(ctx);
+ if (ret)
+ return ret;
+
+ return lg4573_gamma_settings(ctx);
+}
+
+static int lg4573_power_on(struct lg4573 *ctx)
+{
+ return lg4573_display_on(ctx);
+}
+
+static int lg4573_disable(struct drm_panel *panel)
+{
+ struct lg4573 *ctx = panel_to_lg4573(panel);
+
+ return lg4573_display_off(ctx);
+}
+
+static int lg4573_enable(struct drm_panel *panel)
+{
+ struct lg4573 *ctx = panel_to_lg4573(panel);
+
+ lg4573_init(ctx);
+
+ return lg4573_power_on(ctx);
+}
+
+static const struct drm_display_mode default_mode = {
+ .clock = 28341,
+ .hdisplay = 480,
+ .hsync_start = 480 + 10,
+ .hsync_end = 480 + 10 + 59,
+ .htotal = 480 + 10 + 59 + 10,
+ .vdisplay = 800,
+ .vsync_start = 800 + 15,
+ .vsync_end = 800 + 15 + 15,
+ .vtotal = 800 + 15 + 15 + 15,
+};
+
+static int lg4573_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);
+
+ mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+ drm_mode_probed_add(connector, mode);
+
+ connector->display_info.width_mm = 61;
+ connector->display_info.height_mm = 103;
+
+ return 1;
+}
+
+static const struct drm_panel_funcs lg4573_drm_funcs = {
+ .disable = lg4573_disable,
+ .enable = lg4573_enable,
+ .get_modes = lg4573_get_modes,
+};
+
+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->spi = spi;
+
+ spi_set_drvdata(spi, ctx);
+ spi->bits_per_word = 8;
+
+ ret = spi_setup(spi);
+ if (ret < 0) {
+ dev_err(&spi->dev, "SPI setup failed: %d\n", ret);
+ return ret;
+ }
+
+ drm_panel_init(&ctx->panel, &spi->dev, &lg4573_drm_funcs,
+ DRM_MODE_CONNECTOR_DPI);
+
+ drm_panel_add(&ctx->panel);
+
+ return 0;
+}
+
+static void lg4573_remove(struct spi_device *spi)
+{
+ struct lg4573 *ctx = spi_get_drvdata(spi);
+
+ lg4573_display_off(ctx);
+ drm_panel_remove(&ctx->panel);
+}
+
+static const struct of_device_id lg4573_of_match[] = {
+ { .compatible = "lg,lg4573" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, lg4573_of_match);
+
+static struct spi_driver lg4573_driver = {
+ .probe = lg4573_probe,
+ .remove = lg4573_remove,
+ .driver = {
+ .name = "lg4573",
+ .of_match_table = lg4573_of_match,
+ },
+};
+module_spi_driver(lg4573_driver);
+
+MODULE_AUTHOR("Heiko Schocher <hs@denx.de>");
+MODULE_DESCRIPTION("lg4573 LCD Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/panel/panel-lvds.c b/drivers/gpu/drm/panel/panel-lvds.c
new file mode 100644
index 000000000..de8758c30
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-lvds.c
@@ -0,0 +1,262 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Generic LVDS panel driver
+ *
+ * Copyright (C) 2016 Laurent Pinchart
+ * Copyright (C) 2016 Renesas Electronics Corporation
+ *
+ * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ */
+
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.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 drm_display_mode dmode;
+ u32 bus_flags;
+ unsigned int bus_format;
+
+ 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)
+{
+ return container_of(panel, struct panel_lvds, panel);
+}
+
+static int panel_lvds_unprepare(struct drm_panel *panel)
+{
+ struct panel_lvds *lvds = to_panel_lvds(panel);
+
+ if (lvds->enable_gpio)
+ gpiod_set_value_cansleep(lvds->enable_gpio, 0);
+
+ if (lvds->supply)
+ regulator_disable(lvds->supply);
+
+ return 0;
+}
+
+static int panel_lvds_prepare(struct drm_panel *panel)
+{
+ struct panel_lvds *lvds = to_panel_lvds(panel);
+
+ if (lvds->supply) {
+ int err;
+
+ err = regulator_enable(lvds->supply);
+ if (err < 0) {
+ dev_err(lvds->dev, "failed to enable supply: %d\n",
+ err);
+ return err;
+ }
+ }
+
+ if (lvds->enable_gpio)
+ gpiod_set_value_cansleep(lvds->enable_gpio, 1);
+
+ return 0;
+}
+
+static int panel_lvds_get_modes(struct drm_panel *panel,
+ struct drm_connector *connector)
+{
+ struct panel_lvds *lvds = to_panel_lvds(panel);
+ struct drm_display_mode *mode;
+
+ mode = drm_mode_duplicate(connector->dev, &lvds->dmode);
+ if (!mode)
+ return 0;
+
+ mode->type |= DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+ drm_mode_probed_add(connector, mode);
+
+ 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->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 = {
+ .unprepare = panel_lvds_unprepare,
+ .prepare = panel_lvds_prepare,
+ .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;
+ int ret;
+
+ ret = of_drm_get_panel_orientation(np, &lvds->orientation);
+ if (ret < 0) {
+ dev_err(lvds->dev, "%pOF: failed to get orientation %d\n", np, ret);
+ return ret;
+ }
+
+ ret = of_get_drm_panel_display_mode(np, &lvds->dmode, &lvds->bus_flags);
+ if (ret < 0) {
+ dev_err(lvds->dev, "%pOF: problems parsing panel-timing (%d)\n",
+ np, ret);
+ return ret;
+ }
+
+ of_property_read_string(np, "label", &lvds->label);
+
+ 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 ret;
+ }
+
+ lvds->bus_format = ret;
+
+ 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;
+}
+
+static int panel_lvds_probe(struct platform_device *pdev)
+{
+ struct panel_lvds *lvds;
+ int ret;
+
+ lvds = devm_kzalloc(&pdev->dev, sizeof(*lvds), GFP_KERNEL);
+ if (!lvds)
+ return -ENOMEM;
+
+ lvds->dev = &pdev->dev;
+
+ ret = panel_lvds_parse_dt(lvds);
+ if (ret < 0)
+ return ret;
+
+ lvds->supply = devm_regulator_get_optional(lvds->dev, "power");
+ if (IS_ERR(lvds->supply)) {
+ ret = PTR_ERR(lvds->supply);
+
+ if (ret != -ENODEV) {
+ if (ret != -EPROBE_DEFER)
+ dev_err(lvds->dev, "failed to request regulator: %d\n",
+ ret);
+ return ret;
+ }
+
+ lvds->supply = NULL;
+ }
+
+ /* Get GPIOs and backlight controller. */
+ lvds->enable_gpio = devm_gpiod_get_optional(lvds->dev, "enable",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(lvds->enable_gpio)) {
+ ret = PTR_ERR(lvds->enable_gpio);
+ dev_err(lvds->dev, "failed to request %s GPIO: %d\n",
+ "enable", ret);
+ return ret;
+ }
+
+ lvds->reset_gpio = devm_gpiod_get_optional(lvds->dev, "reset",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(lvds->reset_gpio)) {
+ ret = PTR_ERR(lvds->reset_gpio);
+ dev_err(lvds->dev, "failed to request %s GPIO: %d\n",
+ "reset", ret);
+ return ret;
+ }
+
+ /*
+ * 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
+ * panels that require a specific power sequence will need a dedicated
+ * driver.
+ */
+
+ /* Register the panel. */
+ drm_panel_init(&lvds->panel, lvds->dev, &panel_lvds_funcs,
+ DRM_MODE_CONNECTOR_LVDS);
+
+ ret = drm_panel_of_backlight(&lvds->panel);
+ if (ret)
+ return ret;
+
+ drm_panel_add(&lvds->panel);
+
+ dev_set_drvdata(lvds->dev, lvds);
+ return 0;
+}
+
+static int panel_lvds_remove(struct platform_device *pdev)
+{
+ struct panel_lvds *lvds = platform_get_drvdata(pdev);
+
+ drm_panel_remove(&lvds->panel);
+
+ drm_panel_disable(&lvds->panel);
+
+ return 0;
+}
+
+static const struct of_device_id panel_lvds_of_table[] = {
+ { .compatible = "panel-lvds", },
+ { /* Sentinel */ },
+};
+
+MODULE_DEVICE_TABLE(of, panel_lvds_of_table);
+
+static struct platform_driver panel_lvds_driver = {
+ .probe = panel_lvds_probe,
+ .remove = panel_lvds_remove,
+ .driver = {
+ .name = "panel-lvds",
+ .of_match_table = panel_lvds_of_table,
+ },
+};
+
+module_platform_driver(panel_lvds_driver);
+
+MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
+MODULE_DESCRIPTION("LVDS 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 000000000..772e3b6ac
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-mantix-mlaf057we51.c
@@ -0,0 +1,369 @@
+// 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_device.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);
+}
+
+#define dsi_generic_write_seq(dsi, seq...) do { \
+ static const u8 d[] = { seq }; \
+ int ret; \
+ ret = mipi_dsi_generic_write(dsi, d, ARRAY_SIZE(d)); \
+ if (ret < 0) \
+ return ret; \
+ } while (0)
+
+static int mantix_init_sequence(struct mantix *ctx)
+{
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+ struct device *dev = ctx->dev;
+
+ /*
+ * Init sequence was supplied by the panel vendor.
+ */
+ dsi_generic_write_seq(dsi, MANTIX_CMD_OTP_STOP_RELOAD_MIPI, 0x5A);
+
+ dsi_generic_write_seq(dsi, MANTIX_CMD_INT_CANCEL, 0x03);
+ dsi_generic_write_seq(dsi, MANTIX_CMD_OTP_STOP_RELOAD_MIPI, 0x5A, 0x03);
+ dsi_generic_write_seq(dsi, 0x80, 0xA9, 0x00);
+
+ dsi_generic_write_seq(dsi, MANTIX_CMD_OTP_STOP_RELOAD_MIPI, 0x5A, 0x09);
+ dsi_generic_write_seq(dsi, 0x80, 0x64, 0x00, 0x64, 0x00, 0x00);
+ msleep(20);
+
+ dsi_generic_write_seq(dsi, MANTIX_CMD_SPI_FINISH, 0xA5);
+ dsi_generic_write_seq(dsi, MANTIX_CMD_OTP_STOP_RELOAD_MIPI, 0x00, 0x2F);
+ msleep(20);
+
+ dev_dbg(dev, "Panel init sequence done\n");
+ return 0;
+}
+
+static int mantix_enable(struct drm_panel *panel)
+{
+ struct mantix *ctx = panel_to_mantix(panel);
+ struct device *dev = ctx->dev;
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(dev);
+ int ret;
+
+ ret = mantix_init_sequence(ctx);
+ if (ret < 0) {
+ dev_err(ctx->dev, "Panel init sequence failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to exit sleep mode\n");
+ return ret;
+ }
+ msleep(20);
+
+ ret = mipi_dsi_dcs_set_display_on(dsi);
+ if (ret)
+ return ret;
+ usleep_range(10000, 12000);
+
+ ret = mipi_dsi_turn_on_peripheral(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to turn on peripheral\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+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);
+ int ret;
+
+ ret = mipi_dsi_dcs_set_display_off(dsi);
+ if (ret < 0)
+ dev_err(ctx->dev, "Failed to turn off the display: %d\n", ret);
+
+ ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
+ if (ret < 0)
+ dev_err(ctx->dev, "Failed to enter sleep mode: %d\n", ret);
+
+
+ return 0;
+}
+
+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_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+ 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");
+
+ drm_panel_init(&ctx->panel, dev, &mantix_drm_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ ret = drm_panel_of_backlight(&ctx->panel);
+ if (ret)
+ return ret;
+
+ drm_panel_add(&ctx->panel);
+
+ ret = mipi_dsi_attach(dsi);
+ if (ret < 0) {
+ 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 000000000..81c5c541a
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-nec-nl8048hl11.c
@@ -0,0 +1,253 @@
+// 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_kzalloc(&spi->dev, sizeof(*lcd), GFP_KERNEL);
+ if (!lcd)
+ return -ENOMEM;
+
+ spi_set_drvdata(spi, lcd);
+ lcd->spi = spi;
+
+ lcd->reset_gpio = devm_gpiod_get(&spi->dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(lcd->reset_gpio)) {
+ dev_err(&spi->dev, "failed to parse reset gpio\n");
+ return PTR_ERR(lcd->reset_gpio);
+ }
+
+ spi->mode = SPI_MODE_0;
+ spi->bits_per_word = 32;
+
+ ret = spi_setup(spi);
+ if (ret < 0) {
+ dev_err(&spi->dev, "failed to setup SPI: %d\n", ret);
+ return ret;
+ }
+
+ ret = nl8048_init(lcd);
+ if (ret < 0)
+ return ret;
+
+ drm_panel_init(&lcd->panel, &lcd->spi->dev, &nl8048_funcs,
+ DRM_MODE_CONNECTOR_DPI);
+
+ 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-nv3052c.c b/drivers/gpu/drm/panel/panel-newvision-nv3052c.c
new file mode 100644
index 000000000..cf078f0d3
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-newvision-nv3052c.c
@@ -0,0 +1,482 @@
+// 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_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_panel_info {
+ const struct drm_display_mode *display_modes;
+ unsigned int num_modes;
+ u16 width_mm, height_mm;
+ u32 bus_format, bus_flags;
+};
+
+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;
+};
+
+struct nv3052c_reg {
+ u8 cmd;
+ u8 val;
+};
+
+static const struct nv3052c_reg nv3052c_panel_regs[] = {
+ { 0xff, 0x30 },
+ { 0xff, 0x52 },
+ { 0xff, 0x01 },
+ { 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 },
+ { 0x39, 0xd7 },
+ { 0x3a, 0x4a },
+ { 0x28, 0x40 },
+ { 0x29, 0x01 },
+ { 0x2a, 0xdf },
+ { 0x49, 0x3c },
+ { 0x91, 0x77 },
+ { 0x92, 0x77 },
+ { 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 },
+ { 0xff, 0x30 },
+ { 0xff, 0x52 },
+ { 0xff, 0x02 },
+ { 0xb0, 0x0b },
+ { 0xb1, 0x16 },
+ { 0xb2, 0x17 },
+ { 0xb3, 0x2c },
+ { 0xb4, 0x32 },
+ { 0xb5, 0x3b },
+ { 0xb6, 0x29 },
+ { 0xb7, 0x40 },
+ { 0xb8, 0x0d },
+ { 0xb9, 0x05 },
+ { 0xba, 0x12 },
+ { 0xbb, 0x10 },
+ { 0xbc, 0x12 },
+ { 0xbd, 0x15 },
+ { 0xbe, 0x19 },
+ { 0xbf, 0x0e },
+ { 0xc0, 0x16 },
+ { 0xc1, 0x0a },
+ { 0xd0, 0x0c },
+ { 0xd1, 0x17 },
+ { 0xd2, 0x14 },
+ { 0xd3, 0x2e },
+ { 0xd4, 0x32 },
+ { 0xd5, 0x3c },
+ { 0xd6, 0x22 },
+ { 0xd7, 0x3d },
+ { 0xd8, 0x0d },
+ { 0xd9, 0x07 },
+ { 0xda, 0x13 },
+ { 0xdb, 0x13 },
+ { 0xdc, 0x11 },
+ { 0xdd, 0x15 },
+ { 0xde, 0x19 },
+ { 0xdf, 0x10 },
+ { 0xe0, 0x17 },
+ { 0xe1, 0x0a },
+ { 0xff, 0x30 },
+ { 0xff, 0x52 },
+ { 0xff, 0x03 },
+ { 0x00, 0x2a },
+ { 0x01, 0x2a },
+ { 0x02, 0x2a },
+ { 0x03, 0x2a },
+ { 0x04, 0x61 },
+ { 0x05, 0x80 },
+ { 0x06, 0xc7 },
+ { 0x07, 0x01 },
+ { 0x08, 0x03 },
+ { 0x09, 0x04 },
+ { 0x70, 0x22 },
+ { 0x71, 0x80 },
+ { 0x30, 0x2a },
+ { 0x31, 0x2a },
+ { 0x32, 0x2a },
+ { 0x33, 0x2a },
+ { 0x34, 0x61 },
+ { 0x35, 0xc5 },
+ { 0x36, 0x80 },
+ { 0x37, 0x23 },
+ { 0x40, 0x03 },
+ { 0x41, 0x04 },
+ { 0x42, 0x05 },
+ { 0x43, 0x06 },
+ { 0x44, 0x11 },
+ { 0x45, 0xe8 },
+ { 0x46, 0xe9 },
+ { 0x47, 0x11 },
+ { 0x48, 0xea },
+ { 0x49, 0xeb },
+ { 0x50, 0x07 },
+ { 0x51, 0x08 },
+ { 0x52, 0x09 },
+ { 0x53, 0x0a },
+ { 0x54, 0x11 },
+ { 0x55, 0xec },
+ { 0x56, 0xed },
+ { 0x57, 0x11 },
+ { 0x58, 0xef },
+ { 0x59, 0xf0 },
+ { 0xb1, 0x01 },
+ { 0xb4, 0x15 },
+ { 0xb5, 0x16 },
+ { 0xb6, 0x09 },
+ { 0xb7, 0x0f },
+ { 0xb8, 0x0d },
+ { 0xb9, 0x0b },
+ { 0xba, 0x00 },
+ { 0xc7, 0x02 },
+ { 0xca, 0x17 },
+ { 0xcb, 0x18 },
+ { 0xcc, 0x0a },
+ { 0xcd, 0x10 },
+ { 0xce, 0x0e },
+ { 0xcf, 0x0c },
+ { 0xd0, 0x00 },
+ { 0x81, 0x00 },
+ { 0x84, 0x15 },
+ { 0x85, 0x16 },
+ { 0x86, 0x10 },
+ { 0x87, 0x0a },
+ { 0x88, 0x0c },
+ { 0x89, 0x0e },
+ { 0x8a, 0x02 },
+ { 0x97, 0x00 },
+ { 0x9a, 0x17 },
+ { 0x9b, 0x18 },
+ { 0x9c, 0x0f },
+ { 0x9d, 0x09 },
+ { 0x9e, 0x0b },
+ { 0x9f, 0x0d },
+ { 0xa0, 0x01 },
+ { 0xff, 0x30 },
+ { 0xff, 0x52 },
+ { 0xff, 0x02 },
+ { 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 },
+ { 0xff, 0x30 },
+ { 0xff, 0x52 },
+ { 0xff, 0x00 },
+ { 0x36, 0x0a },
+};
+
+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);
+ 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);
+
+ for (i = 0; i < ARRAY_SIZE(nv3052c_panel_regs); i++) {
+ err = mipi_dbi_command(dbi, nv3052c_panel_regs[i].cmd,
+ nv3052c_panel_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_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ 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);
+
+ drm_panel_init(&priv->panel, dev, &nv3052c_funcs,
+ DRM_MODE_CONNECTOR_DPI);
+
+ 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 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,
+};
+
+static const struct of_device_id nv3052c_of_match[] = {
+ { .compatible = "leadtek,ltk035c5444t", .data = &ltk035c5444t_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,
+ },
+ .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_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 000000000..493c3c23f
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-novatek-nt35510.c
@@ -0,0 +1,1097 @@
+// 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_device.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 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_VGH_LEN 3
+#define NT35510_P1_VGL_LEN 3
+#define NT35510_P1_VGP_LEN 3
+#define NT35510_P1_VGN_LEN 3
+/* BT1CTR thru BT5CTR setting 3 bytes */
+#define NT35510_P1_BT1CTR_LEN 3
+#define NT35510_P1_BT2CTR_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
+
+/**
+ * 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;
+ /**
+ * @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];
+ /**
+ * @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];
+ /**
+ * @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];
+};
+
+/**
+ * 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_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;
+
+ /* 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 dopctr[NT35510_P0_DOPCTR_LEN];
+ u8 gseqctr[NT35510_P0_GSEQCTR_LEN];
+ u8 dpfrctr[NT35510_P0_DPFRCTR1_LEN];
+ /* FIXME: set up any rotation (assume none for now) */
+ u8 addr_mode = NT35510_ROTATE_0_SETTING;
+ u8 val;
+ int ret;
+
+ /* Enable TE, EoTP and RGB pixel format */
+ dopctr[0] = NT35510_DOPCTR_0_DSITE | NT35510_DOPCTR_0_EOTP |
+ NT35510_DOPCTR_0_N565;
+ dopctr[1] = NT35510_DOPCTR_1_CTB;
+ ret = nt35510_send_long(nt, dsi, NT35510_P0_DOPCTR,
+ NT35510_P0_DOPCTR_LEN,
+ dopctr);
+ if (ret)
+ return ret;
+
+ ret = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_ADDRESS_MODE, &addr_mode,
+ sizeof(addr_mode));
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Source data hold time, default 0x05 = 2.5us
+ * 0x00..0x3F = 0 .. 31.5us in steps of 0.5us
+ * 0x0A = 5us
+ */
+ val = 0x0A;
+ ret = mipi_dsi_dcs_write(dsi, NT35510_P0_SDHDTCTR, &val,
+ sizeof(val));
+ if (ret < 0)
+ return ret;
+
+ /* EQ control for gate signals, 0x00 = 0 us */
+ gseqctr[0] = 0x00;
+ gseqctr[1] = 0x00;
+ ret = nt35510_send_long(nt, dsi, NT35510_P0_GSEQCTR,
+ NT35510_P0_GSEQCTR_LEN,
+ 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;
+
+ 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);
+
+ 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_kzalloc(dev, sizeof(struct nt35510), GFP_KERNEL);
+ if (!nt)
+ return -ENOMEM;
+ 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;
+ dsi->mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS;
+
+ /*
+ * 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;
+ }
+
+ 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_ASIS);
+ if (IS_ERR(nt->reset_gpio)) {
+ dev_err(dev, "error getting RESET GPIO\n");
+ return PTR_ERR(nt->reset_gpio);
+ }
+
+ drm_panel_init(&nt->panel, dev, &nt35510_drm_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ /*
+ * 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;
+ bl->props.brightness = 255;
+ bl->props.power = FB_BLANK_POWERDOWN;
+ 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, /* HFP = 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,
+ },
+ /* 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 },
+ /* 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 },
+ /* 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 of_device_id nt35510_of_match[] = {
+ {
+ .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 000000000..cc7f96d70
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-novatek-nt35560.c
@@ -0,0 +1,559 @@
+// 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/of_device.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_device *dsi = to_mipi_dsi_device(nt->dev);
+ int period_ns = 1023;
+ int duty_ns = bl->props.brightness;
+ u8 pwm_ratio;
+ u8 pwm_div;
+ u8 par;
+ int ret;
+
+ if (backlight_is_blank(bl)) {
+ /* Disable backlight */
+ par = 0x00;
+ ret = mipi_dsi_dcs_write(dsi, MIPI_DCS_WRITE_CONTROL_DISPLAY,
+ &par, 1);
+ if (ret) {
+ dev_err(nt->dev, "failed to disable display backlight (%d)\n", ret);
+ return ret;
+ }
+ return 0;
+ }
+
+ /* 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);
+ ret = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_DISPLAY_BRIGHTNESS,
+ &pwm_ratio, 1);
+ if (ret < 0) {
+ dev_err(nt->dev, "failed to set display PWM ratio (%d)\n", ret);
+ return ret;
+ }
+
+ /*
+ * Sequence to write PWMDIV:
+ * address data
+ * 0xF3 0xAA CMD2 Unlock
+ * 0x00 0x01 Enter CMD2 page 0
+ * 0X7D 0x01 No reload MTP of CMD2 P1
+ * 0x22 PWMDIV
+ * 0x7F 0xAA CMD2 page 1 lock
+ */
+ par = 0xaa;
+ ret = mipi_dsi_dcs_write(dsi, 0xf3, &par, 1);
+ if (ret < 0) {
+ dev_err(nt->dev, "failed to unlock CMD 2 (%d)\n", ret);
+ return ret;
+ }
+ par = 0x01;
+ ret = mipi_dsi_dcs_write(dsi, 0x00, &par, 1);
+ if (ret < 0) {
+ dev_err(nt->dev, "failed to enter page 1 (%d)\n", ret);
+ return ret;
+ }
+ par = 0x01;
+ ret = mipi_dsi_dcs_write(dsi, 0x7d, &par, 1);
+ if (ret < 0) {
+ dev_err(nt->dev, "failed to disable MTP reload (%d)\n", ret);
+ return ret;
+ }
+ ret = mipi_dsi_dcs_write(dsi, 0x22, &pwm_div, 1);
+ if (ret < 0) {
+ dev_err(nt->dev, "failed to set PWM divisor (%d)\n", ret);
+ return ret;
+ }
+ par = 0xaa;
+ ret = mipi_dsi_dcs_write(dsi, 0x7f, &par, 1);
+ if (ret < 0) {
+ dev_err(nt->dev, "failed to lock CMD 2 (%d)\n", ret);
+ return ret;
+ }
+
+ /* Enable backlight */
+ par = 0x24;
+ ret = mipi_dsi_dcs_write(dsi, MIPI_DCS_WRITE_CONTROL_DISPLAY,
+ &par, 1);
+ if (ret < 0) {
+ dev_err(nt->dev, "failed to enable display backlight (%d)\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+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 int nt35560_read_id(struct nt35560 *nt)
+{
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(nt->dev);
+ u8 vendor, version, panel;
+ u16 val;
+ int ret;
+
+ ret = mipi_dsi_dcs_read(dsi, NT35560_DCS_READ_ID1, &vendor, 1);
+ if (ret < 0) {
+ dev_err(nt->dev, "could not vendor ID byte\n");
+ return ret;
+ }
+ ret = mipi_dsi_dcs_read(dsi, NT35560_DCS_READ_ID2, &version, 1);
+ if (ret < 0) {
+ dev_err(nt->dev, "could not read device version byte\n");
+ return ret;
+ }
+ ret = mipi_dsi_dcs_read(dsi, NT35560_DCS_READ_ID3, &panel, 1);
+ if (ret < 0) {
+ dev_err(nt->dev, "could not read panel ID byte\n");
+ return ret;
+ }
+
+ if (vendor == 0x00) {
+ dev_err(nt->dev, "device vendor ID is zero\n");
+ return -ENODEV;
+ }
+
+ val = (vendor << 8) | panel;
+ switch (val) {
+ case DISPLAY_SONY_ACX424AKP_ID1:
+ case DISPLAY_SONY_ACX424AKP_ID2:
+ case DISPLAY_SONY_ACX424AKP_ID3:
+ case DISPLAY_SONY_ACX424AKP_ID4:
+ dev_info(nt->dev, "MTP vendor: %02x, version: %02x, panel: %02x\n",
+ vendor, version, panel);
+ break;
+ default:
+ dev_info(nt->dev, "unknown vendor: %02x, version: %02x, panel: %02x\n",
+ vendor, version, panel);
+ break;
+ }
+
+ return 0;
+}
+
+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_device *dsi = to_mipi_dsi_device(nt->dev);
+ const u8 mddi = 3;
+ int ret;
+
+ ret = nt35560_power_on(nt);
+ if (ret)
+ return ret;
+
+ ret = nt35560_read_id(nt);
+ if (ret) {
+ dev_err(nt->dev, "failed to read panel ID (%d)\n", ret);
+ goto err_power_off;
+ }
+
+ /* Enabe tearing mode: send TE (tearing effect) at VBLANK */
+ ret = mipi_dsi_dcs_set_tear_on(dsi,
+ MIPI_DSI_DCS_TEAR_MODE_VBLANK);
+ if (ret) {
+ dev_err(nt->dev, "failed to enable vblank TE (%d)\n", ret);
+ goto err_power_off;
+ }
+
+ /*
+ * Set MDDI
+ *
+ * This presumably deactivates the Qualcomm MDDI interface and
+ * selects DSI, similar code is found in other drivers such as the
+ * Sharp LS043T1LE01 which makes us suspect that this panel may be
+ * using a Novatek NT35565 or similar display driver chip that shares
+ * this command. Due to the lack of documentation we cannot know for
+ * sure.
+ */
+ ret = mipi_dsi_dcs_write(dsi, NT35560_DCS_SET_MDDI,
+ &mddi, sizeof(mddi));
+ if (ret < 0) {
+ dev_err(nt->dev, "failed to set MDDI (%d)\n", ret);
+ goto err_power_off;
+ }
+
+ /* Exit sleep mode */
+ ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
+ if (ret) {
+ dev_err(nt->dev, "failed to exit sleep mode (%d)\n", ret);
+ goto err_power_off;
+ }
+ msleep(140);
+
+ ret = mipi_dsi_dcs_set_display_on(dsi);
+ if (ret) {
+ dev_err(nt->dev, "failed to turn display on (%d)\n", ret);
+ goto err_power_off;
+ }
+ if (nt->video_mode) {
+ /* In video mode turn peripheral on */
+ ret = mipi_dsi_turn_on_peripheral(dsi);
+ if (ret) {
+ dev_err(nt->dev, "failed to turn on peripheral\n");
+ goto err_power_off;
+ }
+ }
+
+ return 0;
+
+err_power_off:
+ nt35560_power_off(nt);
+ return ret;
+}
+
+static int nt35560_unprepare(struct drm_panel *panel)
+{
+ struct nt35560 *nt = panel_to_nt35560(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;
+ }
+
+ /* 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;
+ }
+ 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_kzalloc(dev, sizeof(struct nt35560), GFP_KERNEL);
+ if (!nt)
+ return -ENOMEM;
+ 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");
+
+ drm_panel_init(&nt->panel, dev, &nt35560_drm_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ 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 000000000..5d04957b1
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-novatek-nt35950.c
@@ -0,0 +1,708 @@
+// 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_device.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;
+ bool prepared;
+};
+
+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);
+}
+
+#define dsi_dcs_write_seq(dsi, seq...) do { \
+ static const u8 d[] = { seq }; \
+ int ret; \
+ ret = mipi_dsi_dcs_write_buffer(dsi, d, ARRAY_SIZE(d)); \
+ if (ret < 0) \
+ return ret; \
+ } while (0)
+
+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
+ * @nt: Main driver structure
+ * @page: Page number (0-7)
+ *
+ * Return: Number of transferred bytes or negative number on error
+ */
+static int nt35950_set_cmd2_page(struct nt35950 *nt, u8 page)
+{
+ const u8 mauc_cmd2_page[] = { MCS_CMD_MAUCCTR, 0x55, 0xaa, 0x52,
+ 0x08, page };
+ int ret;
+
+ ret = mipi_dsi_dcs_write_buffer(nt->dsi[0], mauc_cmd2_page,
+ ARRAY_SIZE(mauc_cmd2_page));
+ if (ret < 0)
+ return ret;
+
+ nt->last_page = page;
+ return 0;
+}
+
+/*
+ * nt35950_set_data_compression - Set data compression mode
+ * @nt: Main driver structure
+ * @comp_mode: Compression mode
+ *
+ * Return: Number of transferred bytes or negative number on error
+ */
+static int nt35950_set_data_compression(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;
+ int ret;
+
+ /* Set CMD2 Page 0 if we're not there yet */
+ if (last_page != 0) {
+ ret = nt35950_set_cmd2_page(nt, 0);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = mipi_dsi_dcs_write_buffer(nt->dsi[0], cmd_data_compression,
+ ARRAY_SIZE(cmd_data_compression));
+ if (ret < 0)
+ return ret;
+
+ ret = mipi_dsi_dcs_write_buffer(nt->dsi[0], cmd_vesa_dsc_on,
+ ARRAY_SIZE(cmd_vesa_dsc_on));
+ if (ret < 0)
+ return ret;
+
+ /* Set the vesa dsc setting on Page 4 */
+ ret = nt35950_set_cmd2_page(nt, 4);
+ if (ret < 0)
+ return ret;
+
+ /* Display Stream Compression setting, always 0x03 */
+ ret = mipi_dsi_dcs_write_buffer(nt->dsi[0], cmd_vesa_dsc_setting,
+ ARRAY_SIZE(cmd_vesa_dsc_setting));
+ if (ret < 0)
+ return ret;
+
+ /* Get back to the previously set page */
+ return nt35950_set_cmd2_page(nt, last_page);
+}
+
+/*
+ * nt35950_set_scaler - Enable/disable resolution upscaling
+ * @nt: Main driver structure
+ * @scale_up: Scale up function control
+ *
+ * Return: Number of transferred bytes or negative number on error
+ */
+static int nt35950_set_scaler(struct nt35950 *nt, u8 scale_up)
+{
+ u8 cmd_scaler[] = { MCS_PARAM_SCALER_FUNCTION, scale_up };
+
+ return mipi_dsi_dcs_write_buffer(nt->dsi[0], cmd_scaler,
+ ARRAY_SIZE(cmd_scaler));
+}
+
+/*
+ * nt35950_set_scale_mode - Resolution upscaling mode
+ * @nt: Main driver structure
+ * @mode: Scaler mode (MCS_DATA_COMPRESSION_*)
+ *
+ * Return: Number of transferred bytes or negative number on error
+ */
+static int nt35950_set_scale_mode(struct nt35950 *nt, u8 mode)
+{
+ u8 cmd_scaler[] = { MCS_PARAM_SCALEUP_MODE, mode };
+
+ return mipi_dsi_dcs_write_buffer(nt->dsi[0], cmd_scaler,
+ ARRAY_SIZE(cmd_scaler));
+}
+
+/*
+ * nt35950_inject_black_image - Display a completely black image
+ * @nt: Main driver structure
+ *
+ * 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.
+ *
+ * Return: Number of transferred bytes or negative number on error
+ */
+static int nt35950_inject_black_image(struct nt35950 *nt)
+{
+ const u8 cmd0_black_img[] = { 0x6f, 0x01 };
+ const u8 cmd1_black_img[] = { 0xf3, 0x10 };
+ u8 cmd_test[] = { 0xff, 0xaa, 0x55, 0xa5, 0x80 };
+ int ret;
+
+ /* Enable test command */
+ ret = mipi_dsi_dcs_write_buffer(nt->dsi[0], cmd_test, ARRAY_SIZE(cmd_test));
+ if (ret < 0)
+ return ret;
+
+ /* Send a black image */
+ ret = mipi_dsi_dcs_write_buffer(nt->dsi[0], cmd0_black_img,
+ ARRAY_SIZE(cmd0_black_img));
+ if (ret < 0)
+ return ret;
+ ret = mipi_dsi_dcs_write_buffer(nt->dsi[0], cmd1_black_img,
+ ARRAY_SIZE(cmd1_black_img));
+ if (ret < 0)
+ return ret;
+
+ /* Disable test command */
+ cmd_test[ARRAY_SIZE(cmd_test) - 1] = 0x00;
+ return mipi_dsi_dcs_write_buffer(nt->dsi[0], cmd_test, ARRAY_SIZE(cmd_test));
+}
+
+/*
+ * nt35950_set_dispout - Set Display Output register parameters
+ * @nt: Main driver structure
+ *
+ * Return: Number of transferred bytes or negative number on error
+ */
+static int nt35950_set_dispout(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;
+
+ return mipi_dsi_dcs_write_buffer(nt->dsi[0], 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 device *dev = &dsi->dev;
+ int ret;
+
+ 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;
+
+ ret = nt35950_set_cmd2_page(nt, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = nt35950_set_data_compression(nt, mode_data[nt->cur_mode].compression);
+ if (ret < 0)
+ return ret;
+
+ ret = nt35950_set_scale_mode(nt, mode_data[nt->cur_mode].scaler_mode);
+ if (ret < 0)
+ return ret;
+
+ ret = nt35950_set_scaler(nt, mode_data[nt->cur_mode].scaler_on);
+ if (ret < 0)
+ return ret;
+
+ ret = nt35950_set_dispout(nt);
+ if (ret < 0)
+ return ret;
+
+ 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);
+ return ret;
+ }
+
+ ret = mipi_dsi_dcs_set_tear_scanline(dsi, 0);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set tear scanline: %d\n", ret);
+ return ret;
+ }
+
+ /* CMD2 Page 1 */
+ ret = nt35950_set_cmd2_page(nt, 1);
+ if (ret < 0)
+ return ret;
+
+ /* Unknown command */
+ dsi_dcs_write_seq(dsi, 0xd4, 0x88, 0x88);
+
+ /* CMD2 Page 7 */
+ ret = nt35950_set_cmd2_page(nt, 7);
+ if (ret < 0)
+ return ret;
+
+ /* Enable SubPixel Rendering */
+ dsi_dcs_write_seq(dsi, MCS_PARAM_SPR_EN, 0x01);
+
+ /* SPR Mode: YYG Rainbow-RGB */
+ dsi_dcs_write_seq(dsi, MCS_PARAM_SPR_MODE, MCS_SPR_MODE_YYG_RAINBOW_RGB);
+
+ /* CMD3 */
+ ret = nt35950_inject_black_image(nt);
+ if (ret < 0)
+ return ret;
+
+ ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
+ if (ret < 0)
+ return ret;
+ msleep(120);
+
+ ret = mipi_dsi_dcs_set_display_on(dsi);
+ if (ret < 0)
+ return ret;
+ msleep(120);
+
+ nt->dsi[0]->mode_flags &= ~MIPI_DSI_MODE_LPM;
+ nt->dsi[1]->mode_flags &= ~MIPI_DSI_MODE_LPM;
+
+ return 0;
+}
+
+static int nt35950_off(struct nt35950 *nt)
+{
+ struct device *dev = &nt->dsi[0]->dev;
+ int ret;
+
+ ret = mipi_dsi_dcs_set_display_off(nt->dsi[0]);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set display off: %d\n", ret);
+ goto set_lpm;
+ }
+ usleep_range(10000, 11000);
+
+ ret = mipi_dsi_dcs_enter_sleep_mode(nt->dsi[0]);
+ if (ret < 0) {
+ dev_err(dev, "Failed to enter sleep mode: %d\n", ret);
+ goto set_lpm;
+ }
+ msleep(150);
+
+set_lpm:
+ nt->dsi[0]->mode_flags |= MIPI_DSI_MODE_LPM;
+ nt->dsi[1]->mode_flags |= MIPI_DSI_MODE_LPM;
+
+ return 0;
+}
+
+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);
+ struct device *dev = &nt->dsi[0]->dev;
+ int ret;
+
+ if (nt->prepared)
+ return 0;
+
+ 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);
+ if (ret < 0) {
+ dev_err(dev, "Failed to initialize panel: %d\n", ret);
+ goto end;
+ }
+ nt->prepared = true;
+
+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);
+ struct device *dev = &nt->dsi[0]->dev;
+ int ret;
+
+ if (!nt->prepared)
+ return 0;
+
+ ret = nt35950_off(nt);
+ if (ret < 0)
+ dev_err(dev, "Failed to deinitialize panel: %d\n", ret);
+
+ gpiod_set_value_cansleep(nt->reset_gpio, 0);
+ regulator_bulk_disable(ARRAY_SIZE(nt->vregs), nt->vregs);
+
+ nt->prepared = false;
+ 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_kzalloc(dev, sizeof(*nt), GFP_KERNEL);
+ if (!nt)
+ return -ENOMEM;
+
+ 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) {
+ dev_err(dev, "Cannot get secondary DSI host\n");
+ return -EPROBE_DEFER;
+ }
+
+ nt->dsi[1] = mipi_dsi_device_register_full(dsi_r_host, info);
+ if (!nt->dsi[1]) {
+ dev_err(dev, "Cannot get secondary DSI node\n");
+ return -ENODEV;
+ }
+ num_dsis++;
+ }
+
+ nt->dsi[0] = dsi;
+ mipi_dsi_set_drvdata(dsi, nt);
+
+ drm_panel_init(&nt->panel, dev, &nt35950_panel_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ 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-nt36672a.c b/drivers/gpu/drm/panel/panel-novatek-nt36672a.c
new file mode 100644
index 000000000..73bcffa1e
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-novatek-nt36672a.c
@@ -0,0 +1,719 @@
+// 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/of_device.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;
+
+ bool prepared;
+};
+
+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;
+
+ if (!pinfo->prepared)
+ return 0;
+
+ /* 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);
+
+ pinfo->prepared = false;
+
+ 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;
+
+ if (pinfo->prepared)
+ return 0;
+
+ 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);
+
+ pinfo->prepared = true;
+
+ 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];
+
+ 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");
+
+ for (i = 0; i < ARRAY_SIZE(pinfo->supplies); i++) {
+ ret = regulator_set_load(pinfo->supplies[i].consumer,
+ nt36672a_regulator_enable_loads[i]);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to set regulator enable loads\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");
+
+ drm_panel_init(&pinfo->base, dev, &panel_funcs, DRM_MODE_CONNECTOR_DSI);
+
+ 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_kzalloc(&dsi->dev, sizeof(*pinfo), GFP_KERNEL);
+ if (!pinfo)
+ return -ENOMEM;
+
+ desc = of_device_get_match_data(&dsi->dev);
+ dsi->mode_flags = desc->mode_flags;
+ dsi->format = desc->format;
+ dsi->lanes = desc->lanes;
+ pinfo->desc = desc;
+ pinfo->link = dsi;
+
+ mipi_dsi_set_drvdata(dsi, pinfo);
+
+ err = 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 = drm_panel_unprepare(&pinfo->base);
+ if (err < 0)
+ dev_err(&dsi->dev, "failed to unprepare panel: %d\n", err);
+
+ err = drm_panel_disable(&pinfo->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);
+
+ drm_panel_remove(&pinfo->base);
+}
+
+static void nt36672a_panel_shutdown(struct mipi_dsi_device *dsi)
+{
+ struct nt36672a_panel *pinfo = mipi_dsi_get_drvdata(dsi);
+
+ drm_panel_disable(&pinfo->base);
+ drm_panel_unprepare(&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,
+ .shutdown = nt36672a_panel_shutdown,
+};
+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-nt39016.c b/drivers/gpu/drm/panel/panel-novatek-nt39016.c
new file mode 100644
index 000000000..f58cfb10b
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-novatek-nt39016.c
@@ -0,0 +1,360 @@
+// 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/of_device.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+
+enum nt39016_regs {
+ NT39016_REG_SYSTEM,
+ NT39016_REG_TIMING,
+ NT39016_REG_OP,
+ NT39016_REG_DATA_IN,
+ NT39016_REG_SRC_TIMING_DELAY,
+ NT39016_REG_GATE_TIMING_DELAY,
+ NT39016_REG_RESERVED,
+ NT39016_REG_INITIAL_FUNC,
+ NT39016_REG_CONTRAST,
+ NT39016_REG_BRIGHTNESS,
+ NT39016_REG_HUE_SATURATION,
+ NT39016_REG_RB_SUBCONTRAST,
+ NT39016_REG_R_SUBBRIGHTNESS,
+ NT39016_REG_B_SUBBRIGHTNESS,
+ NT39016_REG_VCOMDC,
+ NT39016_REG_VCOMAC,
+ NT39016_REG_VGAM2,
+ NT39016_REG_VGAM34,
+ NT39016_REG_VGAM56,
+ NT39016_REG_VCOMDC_TRIM = 0x1e,
+ NT39016_REG_DISPLAY_MODE = 0x20,
+};
+
+#define NT39016_SYSTEM_RESET_N BIT(0)
+#define NT39016_SYSTEM_STANDBY BIT(1)
+
+struct nt39016_panel_info {
+ 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_kzalloc(dev, sizeof(*panel), GFP_KERNEL);
+ if (!panel)
+ return -ENOMEM;
+
+ 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);
+ }
+
+ drm_panel_init(&panel->drm_panel, dev, &nt39016_funcs,
+ DRM_MODE_CONNECTOR_DPI);
+
+ 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_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
new file mode 100644
index 000000000..36a46cb7f
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-olimex-lcd-olinuxino.c
@@ -0,0 +1,320 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * LCD-OLinuXino support for panel driver
+ *
+ * Copyright (C) 2018 Olimex Ltd.
+ * Author: Stefan Mavrodiev <stefan@olimex.com>
+ */
+
+#include <linux/crc32.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/regulator/consumer.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
+
+struct lcd_olinuxino_mode {
+ u32 pixelclock;
+ u32 hactive;
+ u32 hfp;
+ u32 hbp;
+ u32 hpw;
+ u32 vactive;
+ u32 vfp;
+ u32 vbp;
+ u32 vpw;
+ u32 refresh;
+ u32 flags;
+};
+
+struct lcd_olinuxino_info {
+ char name[32];
+ u32 width_mm;
+ u32 height_mm;
+ u32 bpc;
+ u32 bus_format;
+ u32 bus_flag;
+} __attribute__((__packed__));
+
+struct lcd_olinuxino_eeprom {
+ u32 header;
+ u32 id;
+ char revision[4];
+ u32 serial;
+ struct lcd_olinuxino_info info;
+ u32 num_modes;
+ u8 reserved[180];
+ u32 checksum;
+} __attribute__((__packed__));
+
+struct lcd_olinuxino {
+ struct drm_panel panel;
+ struct device *dev;
+ struct i2c_client *client;
+ struct mutex mutex;
+
+ bool prepared;
+ bool enabled;
+
+ struct regulator *supply;
+ struct gpio_desc *enable_gpio;
+
+ struct lcd_olinuxino_eeprom eeprom;
+};
+
+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;
+
+ 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;
+}
+
+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)
+{
+ struct lcd_olinuxino *lcd = to_lcd_olinuxino(panel);
+
+ if (lcd->enabled)
+ return 0;
+
+ lcd->enabled = true;
+
+ return 0;
+}
+
+static int lcd_olinuxino_get_modes(struct drm_panel *panel,
+ struct drm_connector *connector)
+{
+ struct lcd_olinuxino *lcd = to_lcd_olinuxino(panel);
+ struct lcd_olinuxino_info *lcd_info = &lcd->eeprom.info;
+ struct lcd_olinuxino_mode *lcd_mode;
+ struct drm_display_mode *mode;
+ u32 i, num = 0;
+
+ for (i = 0; i < lcd->eeprom.num_modes; i++) {
+ lcd_mode = (struct lcd_olinuxino_mode *)
+ &lcd->eeprom.reserved[i * sizeof(*lcd_mode)];
+
+ mode = drm_mode_create(connector->dev);
+ if (!mode) {
+ dev_err(panel->dev, "failed to add mode %ux%u@%u\n",
+ lcd_mode->hactive,
+ lcd_mode->vactive,
+ lcd_mode->refresh);
+ continue;
+ }
+
+ mode->clock = lcd_mode->pixelclock;
+ mode->hdisplay = lcd_mode->hactive;
+ mode->hsync_start = lcd_mode->hactive + lcd_mode->hfp;
+ mode->hsync_end = lcd_mode->hactive + lcd_mode->hfp +
+ lcd_mode->hpw;
+ mode->htotal = lcd_mode->hactive + lcd_mode->hfp +
+ lcd_mode->hpw + lcd_mode->hbp;
+ mode->vdisplay = lcd_mode->vactive;
+ mode->vsync_start = lcd_mode->vactive + lcd_mode->vfp;
+ mode->vsync_end = lcd_mode->vactive + lcd_mode->vfp +
+ lcd_mode->vpw;
+ mode->vtotal = lcd_mode->vactive + lcd_mode->vfp +
+ lcd_mode->vpw + lcd_mode->vbp;
+
+ /* Always make the first mode preferred */
+ if (i == 0)
+ mode->type |= DRM_MODE_TYPE_PREFERRED;
+ mode->type |= DRM_MODE_TYPE_DRIVER;
+
+ drm_mode_set_name(mode);
+ drm_mode_probed_add(connector, mode);
+
+ num++;
+ }
+
+ 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;
+
+ if (lcd_info->bus_format)
+ drm_display_info_set_bus_formats(&connector->display_info,
+ &lcd_info->bus_format, 1);
+ connector->display_info.bus_flags = lcd_info->bus_flag;
+
+ return num;
+}
+
+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)
+{
+ struct device *dev = &client->dev;
+ struct lcd_olinuxino *lcd;
+ u32 checksum, i;
+ int ret = 0;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C |
+ I2C_FUNC_SMBUS_READ_I2C_BLOCK))
+ return -ENODEV;
+
+ lcd = devm_kzalloc(dev, sizeof(*lcd), GFP_KERNEL);
+ if (!lcd)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, lcd);
+ lcd->dev = dev;
+ lcd->client = client;
+
+ mutex_init(&lcd->mutex);
+
+ /* Copy data into buffer */
+ for (i = 0; i < LCD_OLINUXINO_DATA_LEN; i += I2C_SMBUS_BLOCK_MAX) {
+ mutex_lock(&lcd->mutex);
+ ret = i2c_smbus_read_i2c_block_data(client,
+ i,
+ I2C_SMBUS_BLOCK_MAX,
+ (u8 *)&lcd->eeprom + i);
+ mutex_unlock(&lcd->mutex);
+ if (ret < 0) {
+ dev_err(dev, "error reading from device at %02x\n", i);
+ return ret;
+ }
+ }
+
+ /* Check configuration checksum */
+ checksum = ~crc32(~0, (u8 *)&lcd->eeprom, 252);
+ if (checksum != lcd->eeprom.checksum) {
+ dev_err(dev, "configuration checksum does not match!\n");
+ return -EINVAL;
+ }
+
+ /* Check magic header */
+ if (lcd->eeprom.header != LCD_OLINUXINO_HEADER_MAGIC) {
+ dev_err(dev, "magic header does not match\n");
+ return -EINVAL;
+ }
+
+ dev_info(dev, "Detected %s, Rev. %s, Serial: %08x\n",
+ lcd->eeprom.info.name,
+ lcd->eeprom.revision,
+ lcd->eeprom.serial);
+
+ /*
+ * The eeprom can hold up to 4 modes.
+ * If the stored value is bigger, overwrite it.
+ */
+ if (lcd->eeprom.num_modes > 4) {
+ dev_warn(dev, "invalid number of modes, falling back to 4\n");
+ 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);
+
+ lcd->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW);
+ if (IS_ERR(lcd->enable_gpio))
+ return PTR_ERR(lcd->enable_gpio);
+
+ drm_panel_init(&lcd->panel, dev, &lcd_olinuxino_funcs,
+ DRM_MODE_CONNECTOR_DPI);
+
+ ret = drm_panel_of_backlight(&lcd->panel);
+ if (ret)
+ return ret;
+
+ drm_panel_add(&lcd->panel);
+
+ return 0;
+}
+
+static void lcd_olinuxino_remove(struct i2c_client *client)
+{
+ struct lcd_olinuxino *panel = i2c_get_clientdata(client);
+
+ drm_panel_remove(&panel->panel);
+
+ drm_panel_disable(&panel->panel);
+ drm_panel_unprepare(&panel->panel);
+}
+
+static const struct of_device_id lcd_olinuxino_of_ids[] = {
+ { .compatible = "olimex,lcd-olinuxino" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, lcd_olinuxino_of_ids);
+
+static struct i2c_driver lcd_olinuxino_driver = {
+ .driver = {
+ .name = "lcd_olinuxino",
+ .of_match_table = lcd_olinuxino_of_ids,
+ },
+ .probe = lcd_olinuxino_probe,
+ .remove = lcd_olinuxino_remove,
+};
+
+module_i2c_driver(lcd_olinuxino_driver);
+
+MODULE_AUTHOR("Stefan Mavrodiev <stefan@olimex.com>");
+MODULE_DESCRIPTION("LCD-OLinuXino driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/panel/panel-orisetech-otm8009a.c b/drivers/gpu/drm/panel/panel-orisetech-otm8009a.c
new file mode 100644
index 000000000..898b892f1
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-orisetech-otm8009a.c
@@ -0,0 +1,527 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) STMicroelectronics SA 2017
+ *
+ * Authors: Philippe Cornu <philippe.cornu@st.com>
+ * Yannick Fertre <yannick.fertre@st.com>
+ */
+
+#include <linux/backlight.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.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
+
+/* Manufacturer Command Set */
+#define MCS_ADRSFT 0x0000 /* Address Shift Function */
+#define MCS_PANSET 0xB3A6 /* Panel Type Setting */
+#define MCS_SD_CTRL 0xC0A2 /* Source Driver Timing Setting */
+#define MCS_P_DRV_M 0xC0B4 /* Panel Driving Mode */
+#define MCS_OSC_ADJ 0xC181 /* Oscillator Adjustment for Idle/Normal mode */
+#define MCS_RGB_VID_SET 0xC1A1 /* RGB Video Mode Setting */
+#define MCS_SD_PCH_CTRL 0xC480 /* Source Driver Precharge Control */
+#define MCS_NO_DOC1 0xC48A /* Command not documented */
+#define MCS_PWR_CTRL1 0xC580 /* Power Control Setting 1 */
+#define MCS_PWR_CTRL2 0xC590 /* Power Control Setting 2 for Normal Mode */
+#define MCS_PWR_CTRL4 0xC5B0 /* Power Control Setting 4 for DC Voltage */
+#define MCS_PANCTRLSET1 0xCB80 /* Panel Control Setting 1 */
+#define MCS_PANCTRLSET2 0xCB90 /* Panel Control Setting 2 */
+#define MCS_PANCTRLSET3 0xCBA0 /* Panel Control Setting 3 */
+#define MCS_PANCTRLSET4 0xCBB0 /* Panel Control Setting 4 */
+#define MCS_PANCTRLSET5 0xCBC0 /* Panel Control Setting 5 */
+#define MCS_PANCTRLSET6 0xCBD0 /* Panel Control Setting 6 */
+#define MCS_PANCTRLSET7 0xCBE0 /* Panel Control Setting 7 */
+#define MCS_PANCTRLSET8 0xCBF0 /* Panel Control Setting 8 */
+#define MCS_PANU2D1 0xCC80 /* Panel U2D Setting 1 */
+#define MCS_PANU2D2 0xCC90 /* Panel U2D Setting 2 */
+#define MCS_PANU2D3 0xCCA0 /* Panel U2D Setting 3 */
+#define MCS_PAND2U1 0xCCB0 /* Panel D2U Setting 1 */
+#define MCS_PAND2U2 0xCCC0 /* Panel D2U Setting 2 */
+#define MCS_PAND2U3 0xCCD0 /* Panel D2U Setting 3 */
+#define MCS_GOAVST 0xCE80 /* GOA VST Setting */
+#define MCS_GOACLKA1 0xCEA0 /* GOA CLKA1 Setting */
+#define MCS_GOACLKA3 0xCEB0 /* GOA CLKA3 Setting */
+#define MCS_GOAECLK 0xCFC0 /* GOA ECLK Setting */
+#define MCS_NO_DOC2 0xCFD0 /* Command not documented */
+#define MCS_GVDDSET 0xD800 /* GVDD/NGVDD */
+#define MCS_VCOMDC 0xD900 /* VCOM Voltage Setting */
+#define MCS_GMCT2_2P 0xE100 /* Gamma Correction 2.2+ Setting */
+#define MCS_GMCT2_2N 0xE200 /* Gamma Correction 2.2- Setting */
+#define MCS_NO_DOC3 0xF5B6 /* Command not documented */
+#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;
+ struct backlight_device *bl_dev;
+ struct gpio_desc *reset_gpio;
+ struct regulator *supply;
+ bool prepared;
+ bool enabled;
+};
+
+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)
+{
+ return container_of(panel, struct otm8009a, panel);
+}
+
+static void otm8009a_dcs_write_buf(struct otm8009a *ctx, const void *data,
+ size_t len)
+{
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+
+ if (mipi_dsi_dcs_write_buffer(dsi, data, len) < 0)
+ dev_warn(ctx->dev, "mipi dsi dcs write buffer failed\n");
+}
+
+#define dcs_write_seq(ctx, seq...) \
+({ \
+ static const u8 d[] = { seq }; \
+ otm8009a_dcs_write_buf(ctx, d, ARRAY_SIZE(d)); \
+})
+
+#define dcs_write_cmd_at(ctx, cmd, seq...) \
+({ \
+ dcs_write_seq(ctx, MCS_ADRSFT, (cmd) & 0xFF); \
+ dcs_write_seq(ctx, (cmd) >> 8, seq); \
+})
+
+static int otm8009a_init_sequence(struct otm8009a *ctx)
+{
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+ int ret;
+
+ /* Enter CMD2 */
+ dcs_write_cmd_at(ctx, MCS_CMD2_ENA1, 0x80, 0x09, 0x01);
+
+ /* Enter Orise Command2 */
+ dcs_write_cmd_at(ctx, MCS_CMD2_ENA2, 0x80, 0x09);
+
+ dcs_write_cmd_at(ctx, MCS_SD_PCH_CTRL, 0x30);
+ mdelay(10);
+
+ dcs_write_cmd_at(ctx, MCS_NO_DOC1, 0x40);
+ mdelay(10);
+
+ dcs_write_cmd_at(ctx, MCS_PWR_CTRL4 + 1, 0xA9);
+ dcs_write_cmd_at(ctx, MCS_PWR_CTRL2 + 1, 0x34);
+ dcs_write_cmd_at(ctx, MCS_P_DRV_M, 0x50);
+ dcs_write_cmd_at(ctx, MCS_VCOMDC, 0x4E);
+ dcs_write_cmd_at(ctx, MCS_OSC_ADJ, 0x66); /* 65Hz */
+ dcs_write_cmd_at(ctx, MCS_PWR_CTRL2 + 2, 0x01);
+ dcs_write_cmd_at(ctx, MCS_PWR_CTRL2 + 5, 0x34);
+ dcs_write_cmd_at(ctx, MCS_PWR_CTRL2 + 4, 0x33);
+ dcs_write_cmd_at(ctx, MCS_GVDDSET, 0x79, 0x79);
+ dcs_write_cmd_at(ctx, MCS_SD_CTRL + 1, 0x1B);
+ dcs_write_cmd_at(ctx, MCS_PWR_CTRL1 + 2, 0x83);
+ dcs_write_cmd_at(ctx, MCS_SD_PCH_CTRL + 1, 0x83);
+ dcs_write_cmd_at(ctx, MCS_RGB_VID_SET, 0x0E);
+ dcs_write_cmd_at(ctx, MCS_PANSET, 0x00, 0x01);
+
+ dcs_write_cmd_at(ctx, MCS_GOAVST, 0x85, 0x01, 0x00, 0x84, 0x01, 0x00);
+ dcs_write_cmd_at(ctx, MCS_GOACLKA1, 0x18, 0x04, 0x03, 0x39, 0x00, 0x00,
+ 0x00, 0x18, 0x03, 0x03, 0x3A, 0x00, 0x00, 0x00);
+ dcs_write_cmd_at(ctx, MCS_GOACLKA3, 0x18, 0x02, 0x03, 0x3B, 0x00, 0x00,
+ 0x00, 0x18, 0x01, 0x03, 0x3C, 0x00, 0x00, 0x00);
+ dcs_write_cmd_at(ctx, MCS_GOAECLK, 0x01, 0x01, 0x20, 0x20, 0x00, 0x00,
+ 0x01, 0x02, 0x00, 0x00);
+
+ dcs_write_cmd_at(ctx, MCS_NO_DOC2, 0x00);
+
+ dcs_write_cmd_at(ctx, MCS_PANCTRLSET1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+ dcs_write_cmd_at(ctx, MCS_PANCTRLSET2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0);
+ dcs_write_cmd_at(ctx, MCS_PANCTRLSET3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0);
+ dcs_write_cmd_at(ctx, MCS_PANCTRLSET4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+ dcs_write_cmd_at(ctx, MCS_PANCTRLSET5, 0, 4, 4, 4, 4, 4, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0);
+ dcs_write_cmd_at(ctx, MCS_PANCTRLSET6, 0, 0, 0, 0, 0, 0, 4, 4, 4, 4,
+ 4, 0, 0, 0, 0);
+ dcs_write_cmd_at(ctx, MCS_PANCTRLSET7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+ dcs_write_cmd_at(ctx, MCS_PANCTRLSET8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);
+
+ dcs_write_cmd_at(ctx, MCS_PANU2D1, 0x00, 0x26, 0x09, 0x0B, 0x01, 0x25,
+ 0x00, 0x00, 0x00, 0x00);
+ dcs_write_cmd_at(ctx, MCS_PANU2D2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0x0A, 0x0C, 0x02);
+ dcs_write_cmd_at(ctx, MCS_PANU2D3, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
+ dcs_write_cmd_at(ctx, MCS_PAND2U1, 0x00, 0x25, 0x0C, 0x0A, 0x02, 0x26,
+ 0x00, 0x00, 0x00, 0x00);
+ dcs_write_cmd_at(ctx, MCS_PAND2U2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0x0B, 0x09, 0x01);
+ dcs_write_cmd_at(ctx, MCS_PAND2U3, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
+
+ dcs_write_cmd_at(ctx, MCS_PWR_CTRL1 + 1, 0x66);
+
+ dcs_write_cmd_at(ctx, MCS_NO_DOC3, 0x06);
+
+ dcs_write_cmd_at(ctx, MCS_GMCT2_2P, 0x00, 0x09, 0x0F, 0x0E, 0x07, 0x10,
+ 0x0B, 0x0A, 0x04, 0x07, 0x0B, 0x08, 0x0F, 0x10, 0x0A,
+ 0x01);
+ dcs_write_cmd_at(ctx, MCS_GMCT2_2N, 0x00, 0x09, 0x0F, 0x0E, 0x07, 0x10,
+ 0x0B, 0x0A, 0x04, 0x07, 0x0B, 0x08, 0x0F, 0x10, 0x0A,
+ 0x01);
+
+ /* Exit CMD2 */
+ dcs_write_cmd_at(ctx, MCS_CMD2_ENA1, 0xFF, 0xFF, 0xFF);
+
+ ret = mipi_dsi_dcs_nop(dsi);
+ if (ret)
+ return ret;
+
+ ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
+ if (ret)
+ return ret;
+
+ /* Wait for sleep out exit */
+ mdelay(120);
+
+ /* Default portrait 480x800 rgb24 */
+ dcs_write_seq(ctx, MIPI_DCS_SET_ADDRESS_MODE, 0x00);
+
+ 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, OTM8009A_VDISPLAY - 1);
+ if (ret)
+ return ret;
+
+ /* See otm8009a driver documentation for pixel format descriptions */
+ ret = mipi_dsi_dcs_set_pixel_format(dsi, MIPI_DCS_PIXEL_FMT_24BIT |
+ MIPI_DCS_PIXEL_FMT_24BIT << 4);
+ if (ret)
+ return ret;
+
+ /* Disable CABC feature */
+ dcs_write_seq(ctx, MIPI_DCS_WRITE_POWER_SAVE, 0x00);
+
+ ret = mipi_dsi_dcs_set_display_on(dsi);
+ if (ret)
+ return ret;
+
+ ret = mipi_dsi_dcs_nop(dsi);
+ if (ret)
+ return ret;
+
+ /* 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;
+}
+
+static int otm8009a_disable(struct drm_panel *panel)
+{
+ struct otm8009a *ctx = panel_to_otm8009a(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);
+ if (ret)
+ return ret;
+
+ ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
+ if (ret)
+ return ret;
+
+ msleep(120);
+
+ ctx->enabled = false;
+
+ return 0;
+}
+
+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);
+ }
+
+ regulator_disable(ctx->supply);
+
+ ctx->prepared = false;
+
+ return 0;
+}
+
+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) {
+ dev_err(panel->dev, "failed to enable supply: %d\n", ret);
+ return ret;
+ }
+
+ if (ctx->reset_gpio) {
+ gpiod_set_value_cansleep(ctx->reset_gpio, 0);
+ gpiod_set_value_cansleep(ctx->reset_gpio, 1);
+ msleep(20);
+ gpiod_set_value_cansleep(ctx->reset_gpio, 0);
+ msleep(100);
+ }
+
+ ret = otm8009a_init_sequence(ctx);
+ if (ret)
+ return ret;
+
+ ctx->prepared = true;
+
+ return 0;
+}
+
+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,
+ struct drm_connector *connector)
+{
+ struct drm_display_mode *mode;
+ 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);
+ }
+
+ connector->display_info.width_mm = mode->width_mm;
+ connector->display_info.height_mm = mode->height_mm;
+
+ return num_modes;
+}
+
+static const struct drm_panel_funcs otm8009a_drm_funcs = {
+ .disable = otm8009a_disable,
+ .unprepare = otm8009a_unprepare,
+ .prepare = otm8009a_prepare,
+ .enable = otm8009a_enable,
+ .get_modes = otm8009a_get_modes,
+};
+
+/*
+ * DSI-BASED BACKLIGHT
+ */
+
+static int otm8009a_backlight_update_status(struct backlight_device *bd)
+{
+ struct otm8009a *ctx = bl_get_data(bd);
+ u8 data[2];
+
+ if (!ctx->prepared) {
+ dev_dbg(&bd->dev, "lcd not ready yet for setting its backlight!\n");
+ return -ENXIO;
+ }
+
+ if (bd->props.power <= FB_BLANK_NORMAL) {
+ /* 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(ctx, data, ARRAY_SIZE(data));
+
+ /* set Brightness Control & Backlight on */
+ data[1] = 0x24;
+
+ } else {
+ /* Power off the backlight: set Brightness Control & Bl off */
+ data[1] = 0;
+ }
+
+ /* Update Brightness Control & Backlight */
+ data[0] = MIPI_DCS_WRITE_CONTROL_DISPLAY;
+ otm8009a_dcs_write_buf(ctx, data, ARRAY_SIZE(data));
+
+ return 0;
+}
+
+static const struct backlight_ops otm8009a_backlight_ops = {
+ .update_status = otm8009a_backlight_update_status,
+};
+
+static int otm8009a_probe(struct mipi_dsi_device *dsi)
+{
+ struct device *dev = &dsi->dev;
+ struct otm8009a *ctx;
+ int ret;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(ctx->reset_gpio)) {
+ dev_err(dev, "cannot get reset-gpio\n");
+ return PTR_ERR(ctx->reset_gpio);
+ }
+
+ ctx->supply = devm_regulator_get(dev, "power");
+ if (IS_ERR(ctx->supply)) {
+ ret = PTR_ERR(ctx->supply);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to request regulator: %d\n", ret);
+ return ret;
+ }
+
+ mipi_dsi_set_drvdata(dsi, ctx);
+
+ ctx->dev = dev;
+
+ 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_CLOCK_NON_CONTINUOUS;
+
+ drm_panel_init(&ctx->panel, dev, &otm8009a_drm_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ ctx->bl_dev = devm_backlight_device_register(dev, dev_name(dev),
+ dev, ctx,
+ &otm8009a_backlight_ops,
+ NULL);
+ if (IS_ERR(ctx->bl_dev)) {
+ ret = PTR_ERR(ctx->bl_dev);
+ dev_err(dev, "failed to register backlight: %d\n", ret);
+ return ret;
+ }
+
+ 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.type = BACKLIGHT_RAW;
+
+ drm_panel_add(&ctx->panel);
+
+ ret = mipi_dsi_attach(dsi);
+ if (ret < 0) {
+ dev_err(dev, "mipi_dsi_attach failed. Is host ready?\n");
+ drm_panel_remove(&ctx->panel);
+ return ret;
+ }
+
+ return 0;
+}
+
+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);
+}
+
+static const struct of_device_id orisetech_otm8009a_of_match[] = {
+ { .compatible = "orisetech,otm8009a" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, orisetech_otm8009a_of_match);
+
+static struct mipi_dsi_driver orisetech_otm8009a_driver = {
+ .probe = otm8009a_probe,
+ .remove = otm8009a_remove,
+ .driver = {
+ .name = "panel-orisetech-otm8009a",
+ .of_match_table = orisetech_otm8009a_of_match,
+ },
+};
+module_mipi_dsi_driver(orisetech_otm8009a_driver);
+
+MODULE_AUTHOR("Philippe Cornu <philippe.cornu@st.com>");
+MODULE_AUTHOR("Yannick Fertre <yannick.fertre@st.com>");
+MODULE_DESCRIPTION("DRM driver for Orise Tech OTM8009A MIPI DSI panel");
+MODULE_LICENSE("GPL v2");
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 000000000..493e0504f
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-osd-osd101t2587-53ts.c
@@ -0,0 +1,247 @@
+// 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;
+
+ bool prepared;
+ bool enabled;
+
+ 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;
+
+ if (!osd101t2587->enabled)
+ return 0;
+
+ ret = mipi_dsi_shutdown_peripheral(osd101t2587->dsi);
+
+ osd101t2587->enabled = false;
+
+ return ret;
+}
+
+static int osd101t2587_panel_unprepare(struct drm_panel *panel)
+{
+ struct osd101t2587_panel *osd101t2587 = ti_osd_panel(panel);
+
+ if (!osd101t2587->prepared)
+ return 0;
+
+ regulator_disable(osd101t2587->supply);
+ osd101t2587->prepared = false;
+
+ return 0;
+}
+
+static int osd101t2587_panel_prepare(struct drm_panel *panel)
+{
+ struct osd101t2587_panel *osd101t2587 = ti_osd_panel(panel);
+ int ret;
+
+ if (osd101t2587->prepared)
+ return 0;
+
+ ret = regulator_enable(osd101t2587->supply);
+ if (!ret)
+ osd101t2587->prepared = true;
+
+ return ret;
+}
+
+static int osd101t2587_panel_enable(struct drm_panel *panel)
+{
+ struct osd101t2587_panel *osd101t2587 = ti_osd_panel(panel);
+ int ret;
+
+ if (osd101t2587->enabled)
+ return 0;
+
+ ret = mipi_dsi_turn_on_peripheral(osd101t2587->dsi);
+ if (ret)
+ return ret;
+
+ osd101t2587->enabled = true;
+
+ 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);
+
+ drm_panel_init(&osd101t2587->base, &osd101t2587->dsi->dev,
+ &osd101t2587_panel_funcs, DRM_MODE_CONNECTOR_DSI);
+
+ 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_kzalloc(&dsi->dev, sizeof(*osd101t2587), GFP_KERNEL);
+ if (!osd101t2587)
+ return -ENOMEM;
+
+ 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;
+
+ ret = drm_panel_disable(&osd101t2587->base);
+ if (ret < 0)
+ dev_warn(&dsi->dev, "failed to disable panel: %d\n", ret);
+
+ drm_panel_unprepare(&osd101t2587->base);
+ 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 void osd101t2587_panel_shutdown(struct mipi_dsi_device *dsi)
+{
+ struct osd101t2587_panel *osd101t2587 = mipi_dsi_get_drvdata(dsi);
+
+ drm_panel_disable(&osd101t2587->base);
+ drm_panel_unprepare(&osd101t2587->base);
+}
+
+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,
+ .shutdown = osd101t2587_panel_shutdown,
+};
+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
new file mode 100644
index 000000000..8ba6d8287
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-panasonic-vvx10f034n00.c
@@ -0,0 +1,289 @@
+// 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>
+ */
+
+#include <linux/delay.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_panel.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
+ * track of earliest wakeup time and delay subsequent prepare call accordingly
+ */
+#define MIN_POFF_MS (500)
+
+struct wuxga_nt_panel {
+ struct drm_panel base;
+ struct mipi_dsi_device *dsi;
+
+ struct regulator *supply;
+
+ bool prepared;
+ bool enabled;
+
+ ktime_t earliest_wake;
+
+ const struct drm_display_mode *mode;
+};
+
+static inline struct wuxga_nt_panel *to_wuxga_nt_panel(struct drm_panel *panel)
+{
+ return container_of(panel, struct wuxga_nt_panel, base);
+}
+
+static int wuxga_nt_panel_on(struct wuxga_nt_panel *wuxga_nt)
+{
+ return mipi_dsi_turn_on_peripheral(wuxga_nt->dsi);
+}
+
+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);
+
+ wuxga_nt->enabled = false;
+
+ return mipi_ret ? mipi_ret : bl_ret;
+}
+
+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;
+}
+
+static int wuxga_nt_panel_prepare(struct drm_panel *panel)
+{
+ struct wuxga_nt_panel *wuxga_nt = to_wuxga_nt_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
+ */
+ enablewait = ktime_ms_delta(wuxga_nt->earliest_wake, ktime_get_real());
+
+ /* Sanity check, this should never happen */
+ if (enablewait > MIN_POFF_MS)
+ enablewait = MIN_POFF_MS;
+
+ if (enablewait > 0)
+ msleep(enablewait);
+
+ ret = regulator_enable(wuxga_nt->supply);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * A minimum delay of 250ms is required after power-up until commands
+ * can be sent
+ */
+ msleep(250);
+
+ ret = wuxga_nt_panel_on(wuxga_nt);
+ if (ret < 0) {
+ dev_err(panel->dev, "failed to set panel on: %d\n", ret);
+ goto poweroff;
+ }
+
+ wuxga_nt->prepared = true;
+
+ return 0;
+
+poweroff:
+ regulator_disable(wuxga_nt->supply);
+
+ 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;
+
+ wuxga_nt->enabled = true;
+
+ return 0;
+}
+
+static const struct drm_display_mode default_mode = {
+ .clock = 164402,
+ .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,
+};
+
+static int wuxga_nt_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 = 217;
+ connector->display_info.height_mm = 136;
+
+ return 1;
+}
+
+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,
+};
+
+static const struct of_device_id wuxga_nt_of_match[] = {
+ { .compatible = "panasonic,vvx10f034n00", },
+ { }
+};
+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;
+ int ret;
+
+ wuxga_nt->mode = &default_mode;
+
+ wuxga_nt->supply = devm_regulator_get(dev, "power");
+ if (IS_ERR(wuxga_nt->supply))
+ return PTR_ERR(wuxga_nt->supply);
+
+ drm_panel_init(&wuxga_nt->base, &wuxga_nt->dsi->dev,
+ &wuxga_nt_panel_funcs, DRM_MODE_CONNECTOR_DSI);
+
+ ret = drm_panel_of_backlight(&wuxga_nt->base);
+ if (ret)
+ return ret;
+
+ drm_panel_add(&wuxga_nt->base);
+
+ return 0;
+}
+
+static void wuxga_nt_panel_del(struct wuxga_nt_panel *wuxga_nt)
+{
+ if (wuxga_nt->base.dev)
+ drm_panel_remove(&wuxga_nt->base);
+}
+
+static int wuxga_nt_panel_probe(struct mipi_dsi_device *dsi)
+{
+ struct wuxga_nt_panel *wuxga_nt;
+ int ret;
+
+ dsi->lanes = 4;
+ dsi->format = MIPI_DSI_FMT_RGB888;
+ dsi->mode_flags = MIPI_DSI_MODE_VIDEO |
+ MIPI_DSI_MODE_VIDEO_HSE |
+ 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;
+
+ mipi_dsi_set_drvdata(dsi, wuxga_nt);
+
+ wuxga_nt->dsi = dsi;
+
+ ret = wuxga_nt_panel_add(wuxga_nt);
+ if (ret < 0)
+ return ret;
+
+ ret = mipi_dsi_attach(dsi);
+ if (ret < 0) {
+ wuxga_nt_panel_del(wuxga_nt);
+ return ret;
+ }
+
+ return 0;
+}
+
+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 = drm_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);
+}
+
+static void wuxga_nt_panel_shutdown(struct mipi_dsi_device *dsi)
+{
+ struct wuxga_nt_panel *wuxga_nt = mipi_dsi_get_drvdata(dsi);
+
+ drm_panel_disable(&wuxga_nt->base);
+}
+
+static struct mipi_dsi_driver wuxga_nt_panel_driver = {
+ .driver = {
+ .name = "panel-panasonic-vvx10f034n00",
+ .of_match_table = wuxga_nt_of_match,
+ },
+ .probe = wuxga_nt_panel_probe,
+ .remove = wuxga_nt_panel_remove,
+ .shutdown = wuxga_nt_panel_shutdown,
+};
+module_mipi_dsi_driver(wuxga_nt_panel_driver);
+
+MODULE_AUTHOR("Werner Johansson <werner.johansson@sonymobile.com>");
+MODULE_DESCRIPTION("Panasonic VVX10F034N00 Novatek NT1397-based WUXGA (1920x1200) video mode panel driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/panel/panel-raspberrypi-touchscreen.c b/drivers/gpu/drm/panel/panel-raspberrypi-touchscreen.c
new file mode 100644
index 000000000..79f852465
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-raspberrypi-touchscreen.c
@@ -0,0 +1,514 @@
+/*
+ * Copyright © 2016-2017 Broadcom
+ *
+ * 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.
+ *
+ * Portions of this file (derived from panel-simple.c) are:
+ *
+ * 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.
+ */
+
+/*
+ * Raspberry Pi 7" touchscreen panel driver.
+ *
+ * The 7" touchscreen consists of a DPI LCD panel, a Toshiba
+ * TC358762XBG DSI-DPI bridge, and an I2C-connected Atmel ATTINY88-MUR
+ * controlling power management, the LCD PWM, and initial register
+ * setup of the Tohsiba.
+ *
+ * This driver controls the TC358762 and ATTINY88, presenting a DSI
+ * device with a drm_panel.
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/fb.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_crtc.h>
+#include <drm/drm_device.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_panel.h>
+
+#define RPI_DSI_DRIVER_NAME "rpi-ts-dsi"
+
+/* I2C registers of the Atmel microcontroller. */
+enum REG_ADDR {
+ REG_ID = 0x80,
+ REG_PORTA, /* BIT(2) for horizontal flip, BIT(3) for vertical flip */
+ REG_PORTB,
+ REG_PORTC,
+ REG_PORTD,
+ REG_POWERON,
+ REG_PWM,
+ REG_DDRA,
+ REG_DDRB,
+ REG_DDRC,
+ REG_DDRD,
+ REG_TEST,
+ REG_WR_ADDRL,
+ REG_WR_ADDRH,
+ REG_READH,
+ REG_READL,
+ REG_WRITEH,
+ REG_WRITEL,
+ REG_ID2,
+};
+
+/* DSI D-PHY Layer Registers */
+#define D0W_DPHYCONTTX 0x0004
+#define CLW_DPHYCONTRX 0x0020
+#define D0W_DPHYCONTRX 0x0024
+#define D1W_DPHYCONTRX 0x0028
+#define COM_DPHYCONTRX 0x0038
+#define CLW_CNTRL 0x0040
+#define D0W_CNTRL 0x0044
+#define D1W_CNTRL 0x0048
+#define DFTMODE_CNTRL 0x0054
+
+/* DSI PPI Layer Registers */
+#define PPI_STARTPPI 0x0104
+#define PPI_BUSYPPI 0x0108
+#define PPI_LINEINITCNT 0x0110
+#define PPI_LPTXTIMECNT 0x0114
+#define PPI_CLS_ATMR 0x0140
+#define PPI_D0S_ATMR 0x0144
+#define PPI_D1S_ATMR 0x0148
+#define PPI_D0S_CLRSIPOCOUNT 0x0164
+#define PPI_D1S_CLRSIPOCOUNT 0x0168
+#define CLS_PRE 0x0180
+#define D0S_PRE 0x0184
+#define D1S_PRE 0x0188
+#define CLS_PREP 0x01A0
+#define D0S_PREP 0x01A4
+#define D1S_PREP 0x01A8
+#define CLS_ZERO 0x01C0
+#define D0S_ZERO 0x01C4
+#define D1S_ZERO 0x01C8
+#define PPI_CLRFLG 0x01E0
+#define PPI_CLRSIPO 0x01E4
+#define HSTIMEOUT 0x01F0
+#define HSTIMEOUTENABLE 0x01F4
+
+/* DSI Protocol Layer Registers */
+#define DSI_STARTDSI 0x0204
+#define DSI_BUSYDSI 0x0208
+#define DSI_LANEENABLE 0x0210
+# define DSI_LANEENABLE_CLOCK BIT(0)
+# define DSI_LANEENABLE_D0 BIT(1)
+# define DSI_LANEENABLE_D1 BIT(2)
+
+#define DSI_LANESTATUS0 0x0214
+#define DSI_LANESTATUS1 0x0218
+#define DSI_INTSTATUS 0x0220
+#define DSI_INTMASK 0x0224
+#define DSI_INTCLR 0x0228
+#define DSI_LPTXTO 0x0230
+#define DSI_MODE 0x0260
+#define DSI_PAYLOAD0 0x0268
+#define DSI_PAYLOAD1 0x026C
+#define DSI_SHORTPKTDAT 0x0270
+#define DSI_SHORTPKTREQ 0x0274
+#define DSI_BTASTA 0x0278
+#define DSI_BTACLR 0x027C
+
+/* DSI General Registers */
+#define DSIERRCNT 0x0300
+#define DSISIGMOD 0x0304
+
+/* DSI Application Layer Registers */
+#define APLCTRL 0x0400
+#define APLSTAT 0x0404
+#define APLERR 0x0408
+#define PWRMOD 0x040C
+#define RDPKTLN 0x0410
+#define PXLFMT 0x0414
+#define MEMWRCMD 0x0418
+
+/* LCDC/DPI Host Registers */
+#define LCDCTRL 0x0420
+#define HSR 0x0424
+#define HDISPR 0x0428
+#define VSR 0x042C
+#define VDISPR 0x0430
+#define VFUEN 0x0434
+
+/* DBI-B Host Registers */
+#define DBIBCTRL 0x0440
+
+/* SPI Master Registers */
+#define SPICMR 0x0450
+#define SPITCR 0x0454
+
+/* System Controller Registers */
+#define SYSSTAT 0x0460
+#define SYSCTRL 0x0464
+#define SYSPLL1 0x0468
+#define SYSPLL2 0x046C
+#define SYSPLL3 0x0470
+#define SYSPMCTRL 0x047C
+
+/* GPIO Registers */
+#define GPIOC 0x0480
+#define GPIOO 0x0484
+#define GPIOI 0x0488
+
+/* I2C Registers */
+#define I2CCLKCTRL 0x0490
+
+/* Chip/Rev Registers */
+#define IDREG 0x04A0
+
+/* Debug Registers */
+#define WCMDQUEUE 0x0500
+#define RCMDQUEUE 0x0504
+
+struct rpi_touchscreen {
+ struct drm_panel base;
+ struct mipi_dsi_device *dsi;
+ struct i2c_client *i2c;
+};
+
+static const struct drm_display_mode rpi_touchscreen_modes[] = {
+ {
+ /* Modeline comes from the Raspberry Pi firmware, with HFP=1
+ * plugged in and clock re-computed from that.
+ */
+ .clock = 25979400 / 1000,
+ .hdisplay = 800,
+ .hsync_start = 800 + 1,
+ .hsync_end = 800 + 1 + 2,
+ .htotal = 800 + 1 + 2 + 46,
+ .vdisplay = 480,
+ .vsync_start = 480 + 7,
+ .vsync_end = 480 + 7 + 2,
+ .vtotal = 480 + 7 + 2 + 21,
+ },
+};
+
+static struct rpi_touchscreen *panel_to_ts(struct drm_panel *panel)
+{
+ return container_of(panel, struct rpi_touchscreen, base);
+}
+
+static int rpi_touchscreen_i2c_read(struct rpi_touchscreen *ts, u8 reg)
+{
+ return i2c_smbus_read_byte_data(ts->i2c, reg);
+}
+
+static void rpi_touchscreen_i2c_write(struct rpi_touchscreen *ts,
+ u8 reg, u8 val)
+{
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(ts->i2c, reg, val);
+ if (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)
+{
+ u8 msg[] = {
+ reg,
+ reg >> 8,
+ val,
+ val >> 8,
+ val >> 16,
+ val >> 24,
+ };
+
+ mipi_dsi_generic_write(ts->dsi, msg, sizeof(msg));
+
+ return 0;
+}
+
+static int rpi_touchscreen_disable(struct drm_panel *panel)
+{
+ struct rpi_touchscreen *ts = panel_to_ts(panel);
+
+ rpi_touchscreen_i2c_write(ts, REG_PWM, 0);
+
+ rpi_touchscreen_i2c_write(ts, REG_POWERON, 0);
+ udelay(1);
+
+ return 0;
+}
+
+static int rpi_touchscreen_noop(struct drm_panel *panel)
+{
+ return 0;
+}
+
+static int rpi_touchscreen_prepare(struct drm_panel *panel)
+{
+ struct rpi_touchscreen *ts = panel_to_ts(panel);
+ int i;
+
+ rpi_touchscreen_i2c_write(ts, REG_POWERON, 1);
+ /* Wait for nPWRDWN to go low to indicate poweron is done. */
+ for (i = 0; i < 100; i++) {
+ if (rpi_touchscreen_i2c_read(ts, REG_PORTB) & 1)
+ break;
+ }
+
+ rpi_touchscreen_write(ts, DSI_LANEENABLE,
+ DSI_LANEENABLE_CLOCK |
+ DSI_LANEENABLE_D0);
+ rpi_touchscreen_write(ts, PPI_D0S_CLRSIPOCOUNT, 0x05);
+ rpi_touchscreen_write(ts, PPI_D1S_CLRSIPOCOUNT, 0x05);
+ rpi_touchscreen_write(ts, PPI_D0S_ATMR, 0x00);
+ rpi_touchscreen_write(ts, PPI_D1S_ATMR, 0x00);
+ rpi_touchscreen_write(ts, PPI_LPTXTIMECNT, 0x03);
+
+ rpi_touchscreen_write(ts, SPICMR, 0x00);
+ rpi_touchscreen_write(ts, LCDCTRL, 0x00100150);
+ rpi_touchscreen_write(ts, SYSCTRL, 0x040f);
+ msleep(100);
+
+ rpi_touchscreen_write(ts, PPI_STARTPPI, 0x01);
+ 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);
+
+ /* Default to the same orientation as the closed source
+ * firmware used for the panel. Runtime rotation
+ * configuration will be supported using VC4's plane
+ * orientation bits.
+ */
+ rpi_touchscreen_i2c_write(ts, REG_PORTA, BIT(2));
+
+ return 0;
+}
+
+static int rpi_touchscreen_get_modes(struct drm_panel *panel,
+ struct drm_connector *connector)
+{
+ unsigned int i, num = 0;
+ static const u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+
+ for (i = 0; i < ARRAY_SIZE(rpi_touchscreen_modes); i++) {
+ const struct drm_display_mode *m = &rpi_touchscreen_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));
+ continue;
+ }
+
+ 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);
+ num++;
+ }
+
+ connector->display_info.bpc = 8;
+ connector->display_info.width_mm = 154;
+ connector->display_info.height_mm = 86;
+ drm_display_info_set_bus_formats(&connector->display_info,
+ &bus_format, 1);
+
+ return num;
+}
+
+static const struct drm_panel_funcs rpi_touchscreen_funcs = {
+ .disable = rpi_touchscreen_disable,
+ .unprepare = 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)
+{
+ struct device *dev = &i2c->dev;
+ struct rpi_touchscreen *ts;
+ struct device_node *endpoint, *dsi_host_node;
+ struct mipi_dsi_host *host;
+ 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;
+
+ i2c_set_clientdata(i2c, ts);
+
+ ts->i2c = i2c;
+
+ ver = rpi_touchscreen_i2c_read(ts, REG_ID);
+ if (ver < 0) {
+ dev_err(dev, "Atmel I2C read failed: %d\n", ver);
+ return -ENODEV;
+ }
+
+ switch (ver) {
+ case 0xde: /* ver 1 */
+ case 0xc3: /* ver 2 */
+ break;
+ default:
+ dev_err(dev, "Unknown Atmel firmware revision: 0x%02x\n", ver);
+ return -ENODEV;
+ }
+
+ /* Turn off at boot, so we can cleanly sequence powering on. */
+ 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);
+ 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) {
+ of_node_put(endpoint);
+ return -EPROBE_DEFER;
+ }
+
+ 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);
+ if (IS_ERR(ts->dsi)) {
+ dev_err(dev, "DSI device registration failed: %ld\n",
+ PTR_ERR(ts->dsi));
+ return PTR_ERR(ts->dsi);
+ }
+
+ drm_panel_init(&ts->base, dev, &rpi_touchscreen_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ /* This appears last, as it's what will unblock the DSI host
+ * driver's component bind function.
+ */
+ drm_panel_add(&ts->base);
+
+ return 0;
+
+error:
+ of_node_put(endpoint);
+ return -ENODEV;
+}
+
+static void rpi_touchscreen_remove(struct i2c_client *i2c)
+{
+ struct rpi_touchscreen *ts = i2c_get_clientdata(i2c);
+
+ mipi_dsi_detach(ts->dsi);
+
+ drm_panel_remove(&ts->base);
+
+ mipi_dsi_device_unregister(ts->dsi);
+}
+
+static int rpi_touchscreen_dsi_probe(struct mipi_dsi_device *dsi)
+{
+ int ret;
+
+ dsi->mode_flags = (MIPI_DSI_MODE_VIDEO |
+ MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
+ MIPI_DSI_MODE_LPM);
+ dsi->format = MIPI_DSI_FMT_RGB888;
+ dsi->lanes = 1;
+
+ ret = mipi_dsi_attach(dsi);
+
+ if (ret)
+ dev_err(&dsi->dev, "failed to attach dsi to host: %d\n", ret);
+
+ return ret;
+}
+
+static struct mipi_dsi_driver rpi_touchscreen_dsi_driver = {
+ .driver.name = RPI_DSI_DRIVER_NAME,
+ .probe = rpi_touchscreen_dsi_probe,
+};
+
+static const struct of_device_id rpi_touchscreen_of_ids[] = {
+ { .compatible = "raspberrypi,7inch-touchscreen-panel" },
+ { } /* sentinel */
+};
+MODULE_DEVICE_TABLE(of, rpi_touchscreen_of_ids);
+
+static struct i2c_driver rpi_touchscreen_driver = {
+ .driver = {
+ .name = "rpi_touchscreen",
+ .of_match_table = rpi_touchscreen_of_ids,
+ },
+ .probe = rpi_touchscreen_probe,
+ .remove = rpi_touchscreen_remove,
+};
+
+static int __init rpi_touchscreen_init(void)
+{
+ mipi_dsi_driver_register(&rpi_touchscreen_dsi_driver);
+ return i2c_add_driver(&rpi_touchscreen_driver);
+}
+module_init(rpi_touchscreen_init);
+
+static void __exit rpi_touchscreen_exit(void)
+{
+ i2c_del_driver(&rpi_touchscreen_driver);
+ mipi_dsi_driver_unregister(&rpi_touchscreen_dsi_driver);
+}
+module_exit(rpi_touchscreen_exit);
+
+MODULE_AUTHOR("Eric Anholt <eric@anholt.net>");
+MODULE_DESCRIPTION("Raspberry Pi 7-inch touchscreen driver");
+MODULE_LICENSE("GPL v2");
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 000000000..dbb1ed4ef
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-raydium-rm67191.c
@@ -0,0 +1,659 @@
+// 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;
+ bool enabled;
+};
+
+static const struct drm_display_mode default_mode = {
+ .clock = 132000,
+ .hdisplay = 1080,
+ .hsync_start = 1080 + 20,
+ .hsync_end = 1080 + 20 + 2,
+ .htotal = 1080 + 20 + 2 + 34,
+ .vdisplay = 1920,
+ .vsync_start = 1920 + 10,
+ .vsync_end = 1920 + 10 + 2,
+ .vtotal = 1920 + 10 + 2 + 4,
+ .width_mm = 68,
+ .height_mm = 121,
+ .flags = DRM_MODE_FLAG_NHSYNC |
+ DRM_MODE_FLAG_NVSYNC,
+};
+
+static inline struct rad_panel *to_rad_panel(struct drm_panel *panel)
+{
+ return container_of(panel, struct rad_panel, panel);
+}
+
+static int rad_panel_push_cmd_list(struct mipi_dsi_device *dsi)
+{
+ size_t i;
+ size_t count = ARRAY_SIZE(manufacturer_cmd_set);
+ int ret = 0;
+
+ for (i = 0; i < count; i++) {
+ const struct cmd_set_entry *entry = &manufacturer_cmd_set[i];
+ u8 buffer[2] = { entry->cmd, entry->param };
+
+ ret = mipi_dsi_generic_write(dsi, &buffer, sizeof(buffer));
+ if (ret < 0)
+ return ret;
+ }
+
+ return ret;
+};
+
+static int color_format_from_dsi_format(enum mipi_dsi_pixel_format format)
+{
+ switch (format) {
+ case MIPI_DSI_FMT_RGB565:
+ return COL_FMT_16BPP;
+ case MIPI_DSI_FMT_RGB666:
+ case MIPI_DSI_FMT_RGB666_PACKED:
+ return COL_FMT_18BPP;
+ case MIPI_DSI_FMT_RGB888:
+ return COL_FMT_24BPP;
+ default:
+ return COL_FMT_24BPP; /* for backward compatibility */
+ }
+};
+
+static int rad_panel_prepare(struct drm_panel *panel)
+{
+ struct rad_panel *rad = to_rad_panel(panel);
+ int ret;
+
+ if (rad->prepared)
+ return 0;
+
+ ret = regulator_bulk_enable(rad->num_supplies, rad->supplies);
+ if (ret)
+ return ret;
+
+ if (rad->reset) {
+ gpiod_set_value_cansleep(rad->reset, 1);
+ usleep_range(3000, 5000);
+ gpiod_set_value_cansleep(rad->reset, 0);
+ usleep_range(18000, 20000);
+ }
+
+ rad->prepared = true;
+
+ return 0;
+}
+
+static int rad_panel_unprepare(struct drm_panel *panel)
+{
+ struct rad_panel *rad = to_rad_panel(panel);
+ int ret;
+
+ if (!rad->prepared)
+ return 0;
+
+ /*
+ * Right after asserting the reset, we need to release it, so that the
+ * touch driver can have an active connection with the touch controller
+ * even after the display is turned off.
+ */
+ if (rad->reset) {
+ gpiod_set_value_cansleep(rad->reset, 1);
+ usleep_range(15000, 17000);
+ gpiod_set_value_cansleep(rad->reset, 0);
+ }
+
+ ret = regulator_bulk_disable(rad->num_supplies, rad->supplies);
+ if (ret)
+ return ret;
+
+ rad->prepared = false;
+
+ return 0;
+}
+
+static int rad_panel_enable(struct drm_panel *panel)
+{
+ struct rad_panel *rad = to_rad_panel(panel);
+ struct mipi_dsi_device *dsi = rad->dsi;
+ struct device *dev = &dsi->dev;
+ int color_format = color_format_from_dsi_format(dsi->format);
+ int ret;
+
+ if (rad->enabled)
+ return 0;
+
+ dsi->mode_flags |= MIPI_DSI_MODE_LPM;
+
+ ret = rad_panel_push_cmd_list(dsi);
+ if (ret < 0) {
+ 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);
+
+ rad->enabled = true;
+
+ return 0;
+
+fail:
+ gpiod_set_value_cansleep(rad->reset, 1);
+
+ return ret;
+}
+
+static int rad_panel_disable(struct drm_panel *panel)
+{
+ struct rad_panel *rad = to_rad_panel(panel);
+ struct mipi_dsi_device *dsi = rad->dsi;
+ struct device *dev = &dsi->dev;
+ int ret;
+
+ if (!rad->enabled)
+ return 0;
+
+ dsi->mode_flags |= MIPI_DSI_MODE_LPM;
+
+ backlight_disable(rad->backlight);
+
+ usleep_range(10000, 12000);
+
+ ret = mipi_dsi_dcs_set_display_off(dsi);
+ if (ret < 0) {
+ 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;
+ }
+
+ rad->enabled = false;
+
+ return 0;
+}
+
+static int rad_panel_get_modes(struct drm_panel *panel,
+ struct drm_connector *connector)
+{
+ struct drm_display_mode *mode;
+
+ mode = drm_mode_duplicate(connector->dev, &default_mode);
+ if (!mode) {
+ 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_kzalloc(&dsi->dev, sizeof(*panel), GFP_KERNEL);
+ if (!panel)
+ return -ENOMEM;
+
+ mipi_dsi_set_drvdata(dsi, panel);
+
+ panel->dsi = dsi;
+
+ dsi->format = MIPI_DSI_FMT_RGB888;
+ dsi->mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO;
+
+ ret = of_property_read_u32(np, "video-mode", &video_mode);
+ if (!ret) {
+ switch (video_mode) {
+ case 0:
+ /* burst mode */
+ dsi->mode_flags |= MIPI_DSI_MODE_VIDEO_BURST;
+ break;
+ case 1:
+ /* non-burst mode with sync event */
+ break;
+ case 2:
+ /* non-burst mode with sync pulse */
+ dsi->mode_flags |= MIPI_DSI_MODE_VIDEO_SYNC_PULSE;
+ break;
+ default:
+ dev_warn(dev, "invalid video mode %d\n", video_mode);
+ break;
+ }
+ }
+
+ ret = of_property_read_u32(np, "dsi-lanes", &dsi->lanes);
+ if (ret) {
+ dev_err(dev, "Failed to get dsi-lanes property (%d)\n", ret);
+ return ret;
+ }
+
+ panel->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(panel->reset))
+ return PTR_ERR(panel->reset);
+
+ memset(&bl_props, 0, sizeof(bl_props));
+ bl_props.type = BACKLIGHT_RAW;
+ bl_props.brightness = 255;
+ bl_props.max_brightness = 255;
+
+ panel->backlight = devm_backlight_device_register(dev, dev_name(dev),
+ dev, dsi, &rad_bl_ops,
+ &bl_props);
+ if (IS_ERR(panel->backlight)) {
+ ret = PTR_ERR(panel->backlight);
+ dev_err(dev, "Failed to register backlight (%d)\n", ret);
+ return ret;
+ }
+
+ ret = rad_init_regulators(panel);
+ if (ret)
+ return ret;
+
+ drm_panel_init(&panel->panel, dev, &rad_panel_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+ dev_set_drvdata(dev, panel);
+
+ 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 void rad_panel_shutdown(struct mipi_dsi_device *dsi)
+{
+ struct rad_panel *rad = mipi_dsi_get_drvdata(dsi);
+
+ rad_panel_disable(&rad->panel);
+ rad_panel_unprepare(&rad->panel);
+}
+
+static const struct of_device_id rad_of_match[] = {
+ { .compatible = "raydium,rm67191", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, rad_of_match);
+
+static struct mipi_dsi_driver rad_panel_driver = {
+ .driver = {
+ .name = "panel-raydium-rm67191",
+ .of_match_table = rad_of_match,
+ },
+ .probe = rad_panel_probe,
+ .remove = rad_panel_remove,
+ .shutdown = rad_panel_shutdown,
+};
+module_mipi_dsi_driver(rad_panel_driver);
+
+MODULE_AUTHOR("Robert Chiras <robert.chiras@nxp.com>");
+MODULE_DESCRIPTION("DRM Driver for Raydium RM67191 MIPI DSI panel");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/panel/panel-raydium-rm68200.c b/drivers/gpu/drm/panel/panel-raydium-rm68200.c
new file mode 100644
index 000000000..5f9b34058
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-raydium-rm68200.c
@@ -0,0 +1,442 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) STMicroelectronics SA 2017
+ *
+ * Authors: Philippe Cornu <philippe.cornu@st.com>
+ * Yannick Fertre <yannick.fertre@st.com>
+ */
+
+#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>
+
+/*** Manufacturer Command Set ***/
+#define MCS_CMD_MODE_SW 0xFE /* CMD Mode Switch */
+#define MCS_CMD1_UCS 0x00 /* User Command Set (UCS = CMD1) */
+#define MCS_CMD2_P0 0x01 /* Manufacture Command Set Page0 (CMD2 P0) */
+#define MCS_CMD2_P1 0x02 /* Manufacture Command Set Page1 (CMD2 P1) */
+#define MCS_CMD2_P2 0x03 /* Manufacture Command Set Page2 (CMD2 P2) */
+#define MCS_CMD2_P3 0x04 /* Manufacture Command Set Page3 (CMD2 P3) */
+
+/* CMD2 P0 commands (Display Options and Power) */
+#define MCS_STBCTR 0x12 /* TE1 Output Setting Zig-Zag Connection */
+#define MCS_SGOPCTR 0x16 /* Source Bias Current */
+#define MCS_SDCTR 0x1A /* Source Output Delay Time */
+#define MCS_INVCTR 0x1B /* Inversion Type */
+#define MCS_EXT_PWR_IC 0x24 /* External PWR IC Control */
+#define MCS_SETAVDD 0x27 /* PFM Control for AVDD Output */
+#define MCS_SETAVEE 0x29 /* PFM Control for AVEE Output */
+#define MCS_BT2CTR 0x2B /* DDVDL Charge Pump Control */
+#define MCS_BT3CTR 0x2F /* VGH Charge Pump Control */
+#define MCS_BT4CTR 0x34 /* VGL Charge Pump Control */
+#define MCS_VCMCTR 0x46 /* VCOM Output Level Control */
+#define MCS_SETVGN 0x52 /* VG M/S N Control */
+#define MCS_SETVGP 0x54 /* VG M/S P Control */
+#define MCS_SW_CTRL 0x5F /* Interface Control for PFM and MIPI */
+
+/* CMD2 P2 commands (GOA Timing Control) - no description in datasheet */
+#define GOA_VSTV1 0x00
+#define GOA_VSTV2 0x07
+#define GOA_VCLK1 0x0E
+#define GOA_VCLK2 0x17
+#define GOA_VCLK_OPT1 0x20
+#define GOA_BICLK1 0x2A
+#define GOA_BICLK2 0x37
+#define GOA_BICLK3 0x44
+#define GOA_BICLK4 0x4F
+#define GOA_BICLK_OPT1 0x5B
+#define GOA_BICLK_OPT2 0x60
+#define MCS_GOA_GPO1 0x6D
+#define MCS_GOA_GPO2 0x71
+#define MCS_GOA_EQ 0x74
+#define MCS_GOA_CLK_GALLON 0x7C
+#define MCS_GOA_FS_SEL0 0x7E
+#define MCS_GOA_FS_SEL1 0x87
+#define MCS_GOA_FS_SEL2 0x91
+#define MCS_GOA_FS_SEL3 0x9B
+#define MCS_GOA_BS_SEL0 0xAC
+#define MCS_GOA_BS_SEL1 0xB5
+#define MCS_GOA_BS_SEL2 0xBF
+#define MCS_GOA_BS_SEL3 0xC9
+#define MCS_GOA_BS_SEL4 0xD3
+
+/* CMD2 P3 commands (Gamma) */
+#define MCS_GAMMA_VP 0x60 /* Gamma VP1~VP16 */
+#define MCS_GAMMA_VN 0x70 /* Gamma VN1~VN16 */
+
+struct rm68200 {
+ struct device *dev;
+ struct drm_panel panel;
+ struct gpio_desc *reset_gpio;
+ struct regulator *supply;
+ bool prepared;
+ bool enabled;
+};
+
+static const struct drm_display_mode default_mode = {
+ .clock = 54000,
+ .hdisplay = 720,
+ .hsync_start = 720 + 48,
+ .hsync_end = 720 + 48 + 9,
+ .htotal = 720 + 48 + 9 + 48,
+ .vdisplay = 1280,
+ .vsync_start = 1280 + 12,
+ .vsync_end = 1280 + 12 + 5,
+ .vtotal = 1280 + 12 + 5 + 12,
+ .flags = 0,
+ .width_mm = 68,
+ .height_mm = 122,
+};
+
+static inline struct rm68200 *panel_to_rm68200(struct drm_panel *panel)
+{
+ return container_of(panel, struct rm68200, panel);
+}
+
+static void rm68200_dcs_write_buf(struct rm68200 *ctx, const void *data,
+ size_t len)
+{
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+ int err;
+
+ err = mipi_dsi_dcs_write_buffer(dsi, data, len);
+ if (err < 0)
+ 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)
+{
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+ int err;
+
+ err = mipi_dsi_dcs_write(dsi, cmd, &value, 1);
+ if (err < 0)
+ dev_err_ratelimited(ctx->dev, "MIPI DSI DCS write failed: %d\n", err);
+}
+
+#define dcs_write_seq(ctx, seq...) \
+({ \
+ static const u8 d[] = { seq }; \
+ \
+ rm68200_dcs_write_buf(ctx, d, ARRAY_SIZE(d)); \
+})
+
+/*
+ * This panel is not able to auto-increment all cmd addresses so for some of
+ * them, we need to send them one by one...
+ */
+#define dcs_write_cmd_seq(ctx, cmd, seq...) \
+({ \
+ static const u8 d[] = { seq }; \
+ unsigned int i; \
+ \
+ for (i = 0; i < ARRAY_SIZE(d) ; i++) \
+ rm68200_dcs_write_cmd(ctx, cmd + i, d[i]); \
+})
+
+static void rm68200_init_sequence(struct rm68200 *ctx)
+{
+ /* Enter CMD2 with page 0 */
+ dcs_write_seq(ctx, MCS_CMD_MODE_SW, MCS_CMD2_P0);
+ dcs_write_cmd_seq(ctx, MCS_EXT_PWR_IC, 0xC0, 0x53, 0x00);
+ dcs_write_seq(ctx, MCS_BT2CTR, 0xE5);
+ dcs_write_seq(ctx, MCS_SETAVDD, 0x0A);
+ dcs_write_seq(ctx, MCS_SETAVEE, 0x0A);
+ dcs_write_seq(ctx, MCS_SGOPCTR, 0x52);
+ dcs_write_seq(ctx, MCS_BT3CTR, 0x53);
+ dcs_write_seq(ctx, MCS_BT4CTR, 0x5A);
+ dcs_write_seq(ctx, MCS_INVCTR, 0x00);
+ dcs_write_seq(ctx, MCS_STBCTR, 0x0A);
+ dcs_write_seq(ctx, MCS_SDCTR, 0x06);
+ dcs_write_seq(ctx, MCS_VCMCTR, 0x56);
+ dcs_write_seq(ctx, MCS_SETVGN, 0xA0, 0x00);
+ dcs_write_seq(ctx, MCS_SETVGP, 0xA0, 0x00);
+ dcs_write_seq(ctx, MCS_SW_CTRL, 0x11); /* 2 data lanes, see doc */
+
+ dcs_write_seq(ctx, MCS_CMD_MODE_SW, MCS_CMD2_P2);
+ dcs_write_seq(ctx, GOA_VSTV1, 0x05);
+ dcs_write_seq(ctx, 0x02, 0x0B);
+ dcs_write_seq(ctx, 0x03, 0x0F);
+ dcs_write_seq(ctx, 0x04, 0x7D, 0x00, 0x50);
+ dcs_write_cmd_seq(ctx, GOA_VSTV2, 0x05, 0x16, 0x0D, 0x11, 0x7D, 0x00,
+ 0x50);
+ dcs_write_cmd_seq(ctx, GOA_VCLK1, 0x07, 0x08, 0x01, 0x02, 0x00, 0x7D,
+ 0x00, 0x85, 0x08);
+ dcs_write_cmd_seq(ctx, GOA_VCLK2, 0x03, 0x04, 0x05, 0x06, 0x00, 0x7D,
+ 0x00, 0x85, 0x08);
+ dcs_write_seq(ctx, GOA_VCLK_OPT1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00);
+ dcs_write_cmd_seq(ctx, GOA_BICLK1, 0x07, 0x08);
+ dcs_write_seq(ctx, 0x2D, 0x01);
+ dcs_write_seq(ctx, 0x2F, 0x02, 0x00, 0x40, 0x05, 0x08, 0x54, 0x7D,
+ 0x00);
+ dcs_write_cmd_seq(ctx, GOA_BICLK2, 0x03, 0x04, 0x05, 0x06, 0x00);
+ dcs_write_seq(ctx, 0x3D, 0x40);
+ dcs_write_seq(ctx, 0x3F, 0x05, 0x08, 0x54, 0x7D, 0x00);
+ dcs_write_seq(ctx, GOA_BICLK3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00);
+ dcs_write_seq(ctx, GOA_BICLK4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00);
+ dcs_write_seq(ctx, 0x58, 0x00, 0x00, 0x00);
+ dcs_write_seq(ctx, GOA_BICLK_OPT1, 0x00, 0x00, 0x00, 0x00, 0x00);
+ dcs_write_seq(ctx, GOA_BICLK_OPT2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
+ dcs_write_seq(ctx, MCS_GOA_GPO1, 0x00, 0x00, 0x00, 0x00);
+ dcs_write_seq(ctx, MCS_GOA_GPO2, 0x00, 0x20, 0x00);
+ dcs_write_seq(ctx, MCS_GOA_EQ, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x00, 0x00);
+ dcs_write_seq(ctx, MCS_GOA_CLK_GALLON, 0x00, 0x00);
+ dcs_write_cmd_seq(ctx, MCS_GOA_FS_SEL0, 0xBF, 0x02, 0x06, 0x14, 0x10,
+ 0x16, 0x12, 0x08, 0x3F);
+ dcs_write_cmd_seq(ctx, MCS_GOA_FS_SEL1, 0x3F, 0x3F, 0x3F, 0x3F, 0x0C,
+ 0x0A, 0x0E, 0x3F, 0x3F, 0x00);
+ dcs_write_cmd_seq(ctx, MCS_GOA_FS_SEL2, 0x04, 0x3F, 0x3F, 0x3F, 0x3F,
+ 0x05, 0x01, 0x3F, 0x3F, 0x0F);
+ dcs_write_cmd_seq(ctx, MCS_GOA_FS_SEL3, 0x0B, 0x0D, 0x3F, 0x3F, 0x3F,
+ 0x3F);
+ dcs_write_cmd_seq(ctx, 0xA2, 0x3F, 0x09, 0x13, 0x17, 0x11, 0x15);
+ dcs_write_cmd_seq(ctx, 0xA9, 0x07, 0x03, 0x3F);
+ dcs_write_cmd_seq(ctx, MCS_GOA_BS_SEL0, 0x3F, 0x05, 0x01, 0x17, 0x13,
+ 0x15, 0x11, 0x0F, 0x3F);
+ dcs_write_cmd_seq(ctx, MCS_GOA_BS_SEL1, 0x3F, 0x3F, 0x3F, 0x3F, 0x0B,
+ 0x0D, 0x09, 0x3F, 0x3F, 0x07);
+ dcs_write_cmd_seq(ctx, MCS_GOA_BS_SEL2, 0x03, 0x3F, 0x3F, 0x3F, 0x3F,
+ 0x02, 0x06, 0x3F, 0x3F, 0x08);
+ dcs_write_cmd_seq(ctx, MCS_GOA_BS_SEL3, 0x0C, 0x0A, 0x3F, 0x3F, 0x3F,
+ 0x3F, 0x3F, 0x0E, 0x10, 0x14);
+ dcs_write_cmd_seq(ctx, MCS_GOA_BS_SEL4, 0x12, 0x16, 0x00, 0x04, 0x3F);
+ dcs_write_seq(ctx, 0xDC, 0x02);
+ dcs_write_seq(ctx, 0xDE, 0x12);
+
+ dcs_write_seq(ctx, MCS_CMD_MODE_SW, 0x0E); /* No documentation */
+ dcs_write_seq(ctx, 0x01, 0x75);
+
+ dcs_write_seq(ctx, MCS_CMD_MODE_SW, MCS_CMD2_P3);
+ dcs_write_cmd_seq(ctx, MCS_GAMMA_VP, 0x00, 0x0C, 0x12, 0x0E, 0x06,
+ 0x12, 0x0E, 0x0B, 0x15, 0x0B, 0x10, 0x07, 0x0F,
+ 0x12, 0x0C, 0x00);
+ dcs_write_cmd_seq(ctx, MCS_GAMMA_VN, 0x00, 0x0C, 0x12, 0x0E, 0x06,
+ 0x12, 0x0E, 0x0B, 0x15, 0x0B, 0x10, 0x07, 0x0F,
+ 0x12, 0x0C, 0x00);
+
+ /* Exit CMD2 */
+ 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;
+
+ 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)
+ dev_warn(panel->dev, "failed to set display off: %d\n", ret);
+
+ ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
+ if (ret)
+ dev_warn(panel->dev, "failed to enter sleep mode: %d\n", ret);
+
+ msleep(120);
+
+ if (ctx->reset_gpio) {
+ gpiod_set_value_cansleep(ctx->reset_gpio, 1);
+ msleep(20);
+ }
+
+ regulator_disable(ctx->supply);
+
+ ctx->prepared = false;
+
+ return 0;
+}
+
+static int rm68200_prepare(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 = regulator_enable(ctx->supply);
+ if (ret < 0) {
+ dev_err(ctx->dev, "failed to enable supply: %d\n", ret);
+ return ret;
+ }
+
+ if (ctx->reset_gpio) {
+ gpiod_set_value_cansleep(ctx->reset_gpio, 1);
+ msleep(20);
+ gpiod_set_value_cansleep(ctx->reset_gpio, 0);
+ msleep(100);
+ }
+
+ rm68200_init_sequence(ctx);
+
+ ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
+ if (ret)
+ return ret;
+
+ msleep(125);
+
+ ret = mipi_dsi_dcs_set_display_on(dsi);
+ if (ret)
+ return ret;
+
+ 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;
+
+ ctx->enabled = true;
+
+ return 0;
+}
+
+static int rm68200_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;
+
+ 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,
+};
+
+static int rm68200_probe(struct mipi_dsi_device *dsi)
+{
+ struct device *dev = &dsi->dev;
+ struct rm68200 *ctx;
+ int ret;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(ctx->reset_gpio)) {
+ ret = PTR_ERR(ctx->reset_gpio);
+ dev_err(dev, "cannot get reset GPIO: %d\n", ret);
+ return ret;
+ }
+
+ ctx->supply = devm_regulator_get(dev, "power");
+ if (IS_ERR(ctx->supply)) {
+ ret = PTR_ERR(ctx->supply);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "cannot get regulator: %d\n", ret);
+ return ret;
+ }
+
+ mipi_dsi_set_drvdata(dsi, ctx);
+
+ ctx->dev = dev;
+
+ 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_CLOCK_NON_CONTINUOUS;
+
+ drm_panel_init(&ctx->panel, dev, &rm68200_drm_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ ret = drm_panel_of_backlight(&ctx->panel);
+ if (ret)
+ return ret;
+
+ drm_panel_add(&ctx->panel);
+
+ 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 rm68200_remove(struct mipi_dsi_device *dsi)
+{
+ struct rm68200 *ctx = mipi_dsi_get_drvdata(dsi);
+
+ mipi_dsi_detach(dsi);
+ drm_panel_remove(&ctx->panel);
+}
+
+static const struct of_device_id raydium_rm68200_of_match[] = {
+ { .compatible = "raydium,rm68200" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, raydium_rm68200_of_match);
+
+static struct mipi_dsi_driver raydium_rm68200_driver = {
+ .probe = rm68200_probe,
+ .remove = rm68200_remove,
+ .driver = {
+ .name = "panel-raydium-rm68200",
+ .of_match_table = raydium_rm68200_of_match,
+ },
+};
+module_mipi_dsi_driver(raydium_rm68200_driver);
+
+MODULE_AUTHOR("Philippe Cornu <philippe.cornu@st.com>");
+MODULE_AUTHOR("Yannick Fertre <yannick.fertre@st.com>");
+MODULE_DESCRIPTION("DRM Driver for Raydium RM68200 MIPI DSI panel");
+MODULE_LICENSE("GPL v2");
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 000000000..a8a98c91b
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-ronbo-rb070d30.c
@@ -0,0 +1,238 @@
+// 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/fb.h>
+#include <linux/kernel.h>
+#include <linux/media-bus-format.h>
+#include <linux/module.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(ctx->gpios.power, 1);
+ msleep(20);
+ gpiod_set_value(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(ctx->gpios.reset, 0);
+ gpiod_set_value(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_kzalloc(&dsi->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ 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;
+
+ drm_panel_init(&ctx->panel, &dsi->dev, &rb070d30_panel_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ ctx->gpios.reset = devm_gpiod_get(&dsi->dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(ctx->gpios.reset)) {
+ 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-atna33xc20.c b/drivers/gpu/drm/panel/panel-samsung-atna33xc20.c
new file mode 100644
index 000000000..5a8b978c6
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-samsung-atna33xc20.c
@@ -0,0 +1,391 @@
+// 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 prepared;
+ bool enabled;
+ 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;
+
+ struct edid *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();
+
+ 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);
+
+ ret = regulator_disable(p->supply);
+ if (ret)
+ return ret;
+ p->powered_off_time = ktime_get();
+ 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;
+ p->powered_on_time = ktime_get();
+
+ 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);
+
+ return ret;
+ }
+
+ 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);
+
+ return ret;
+ }
+
+ /*
+ * 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;
+}
+
+static int atana33xc20_disable(struct drm_panel *panel)
+{
+ struct atana33xc20_panel *p = to_atana33xc20(panel);
+
+ /* Disabling when already disabled is a no-op */
+ if (!p->enabled)
+ return 0;
+
+ gpiod_set_value_cansleep(p->el_on3_gpio, 0);
+ p->el_on3_off_time = ktime_get();
+ p->enabled = false;
+
+ /*
+ * 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);
+
+ /* Enabling when already enabled is a no-op */
+ if (p->enabled)
+ return 0;
+
+ /*
+ * 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);
+ p->enabled = true;
+
+ return 0;
+}
+
+static int atana33xc20_unprepare(struct drm_panel *panel)
+{
+ struct atana33xc20_panel *p = to_atana33xc20(panel);
+ int ret;
+
+ /* Unpreparing when already unprepared is a no-op */
+ if (!p->prepared)
+ return 0;
+
+ /*
+ * 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;
+ p->prepared = false;
+
+ return 0;
+}
+
+static int atana33xc20_prepare(struct drm_panel *panel)
+{
+ struct atana33xc20_panel *p = to_atana33xc20(panel);
+ int ret;
+
+ /* Preparing when already prepared is a no-op */
+ if (p->prepared)
+ return 0;
+
+ ret = pm_runtime_get_sync(panel->dev);
+ if (ret < 0) {
+ pm_runtime_put_autosuspend(panel->dev);
+ return ret;
+ }
+ p->prepared = true;
+
+ 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->edid)
+ p->edid = drm_get_edid(connector, &aux_ep->aux->ddc);
+ num = drm_add_edid_modes(connector, p->edid);
+
+ 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_kzalloc(dev, sizeof(*panel), GFP_KERNEL);
+ if (!panel)
+ return -ENOMEM;
+ 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, 1000);
+ pm_runtime_use_autosuspend(dev);
+ ret = devm_add_action_or_reset(dev, atana33xc20_dont_use_autosuspend, dev);
+ if (ret)
+ return ret;
+
+ drm_panel_init(&panel->base, dev, &atana33xc20_funcs, DRM_MODE_CONNECTOR_eDP);
+
+ 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);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to register dp aux backlight\n");
+
+ 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_panel_disable(&panel->base);
+ drm_panel_unprepare(&panel->base);
+
+ kfree(panel->edid);
+}
+
+static void atana33xc20_shutdown(struct dp_aux_ep_device *aux_ep)
+{
+ struct device *dev = &aux_ep->dev;
+ struct atana33xc20_panel *panel = dev_get_drvdata(dev);
+
+ drm_panel_disable(&panel->base);
+ drm_panel_unprepare(&panel->base);
+}
+
+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,
+ .shutdown = atana33xc20_shutdown,
+};
+
+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 000000000..04640c525
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-samsung-db7430.c
@@ -0,0 +1,346 @@
+// 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;
+ /** @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];
+};
+
+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_kzalloc(dev, sizeof(*db), GFP_KERNEL);
+ if (!db)
+ return -ENOMEM;
+ 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");
+
+ drm_panel_init(&db->panel, dev, &db7430_drm_funcs,
+ DRM_MODE_CONNECTOR_DPI);
+
+ /* 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 struct spi_driver db7430_driver = {
+ .probe = db7430_probe,
+ .remove = db7430_remove,
+ .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
new file mode 100644
index 000000000..01eb211f3
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-samsung-ld9040.c
@@ -0,0 +1,394 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ld9040 AMOLED LCD drm_panel driver.
+ *
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd
+ * Derived from drivers/video/backlight/ld9040.c
+ *
+ * Andrzej Hajda <a.hajda@samsung.com>
+*/
+
+#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>
+
+#include <video/mipi_display.h>
+#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
+#define MCS_USER_SETTING 0xf0
+#define MCS_DISPCTL 0xf2
+#define MCS_POWER_CTRL 0xf4
+#define MCS_GTCON 0xf7
+#define MCS_PANEL_CONDITION 0xf8
+#define MCS_GAMMA_SET1 0xf9
+#define MCS_GAMMA_CTRL 0xfb
+
+/* array of gamma tables for gamma value 2.2 */
+static u8 const ld9040_gammas[25][22] = {
+ { 0xf9, 0x00, 0x13, 0xb2, 0xba, 0xd2, 0x00, 0x30, 0x00, 0xaf, 0xc0,
+ 0xb8, 0xcd, 0x00, 0x3d, 0x00, 0xa8, 0xb8, 0xb7, 0xcd, 0x00, 0x44 },
+ { 0xf9, 0x00, 0x13, 0xb9, 0xb9, 0xd0, 0x00, 0x3c, 0x00, 0xaf, 0xbf,
+ 0xb6, 0xcb, 0x00, 0x4b, 0x00, 0xa8, 0xb9, 0xb5, 0xcc, 0x00, 0x52 },
+ { 0xf9, 0x00, 0x13, 0xba, 0xb9, 0xcd, 0x00, 0x41, 0x00, 0xb0, 0xbe,
+ 0xb5, 0xc9, 0x00, 0x51, 0x00, 0xa9, 0xb9, 0xb5, 0xca, 0x00, 0x57 },
+ { 0xf9, 0x00, 0x13, 0xb9, 0xb8, 0xcd, 0x00, 0x46, 0x00, 0xb1, 0xbc,
+ 0xb5, 0xc8, 0x00, 0x56, 0x00, 0xaa, 0xb8, 0xb4, 0xc9, 0x00, 0x5d },
+ { 0xf9, 0x00, 0x13, 0xba, 0xb8, 0xcb, 0x00, 0x4b, 0x00, 0xb3, 0xbc,
+ 0xb4, 0xc7, 0x00, 0x5c, 0x00, 0xac, 0xb8, 0xb4, 0xc8, 0x00, 0x62 },
+ { 0xf9, 0x00, 0x13, 0xbb, 0xb7, 0xca, 0x00, 0x4f, 0x00, 0xb4, 0xbb,
+ 0xb3, 0xc7, 0x00, 0x60, 0x00, 0xad, 0xb8, 0xb4, 0xc7, 0x00, 0x67 },
+ { 0xf9, 0x00, 0x47, 0xba, 0xb6, 0xca, 0x00, 0x53, 0x00, 0xb5, 0xbb,
+ 0xb3, 0xc6, 0x00, 0x65, 0x00, 0xae, 0xb8, 0xb3, 0xc7, 0x00, 0x6c },
+ { 0xf9, 0x00, 0x71, 0xbb, 0xb5, 0xc8, 0x00, 0x57, 0x00, 0xb5, 0xbb,
+ 0xb0, 0xc5, 0x00, 0x6a, 0x00, 0xae, 0xb9, 0xb1, 0xc6, 0x00, 0x70 },
+ { 0xf9, 0x00, 0x7b, 0xbb, 0xb4, 0xc8, 0x00, 0x5b, 0x00, 0xb5, 0xba,
+ 0xb1, 0xc4, 0x00, 0x6e, 0x00, 0xae, 0xb9, 0xb0, 0xc5, 0x00, 0x75 },
+ { 0xf9, 0x00, 0x82, 0xba, 0xb4, 0xc7, 0x00, 0x5f, 0x00, 0xb5, 0xba,
+ 0xb0, 0xc3, 0x00, 0x72, 0x00, 0xae, 0xb8, 0xb0, 0xc3, 0x00, 0x7a },
+ { 0xf9, 0x00, 0x89, 0xba, 0xb3, 0xc8, 0x00, 0x62, 0x00, 0xb6, 0xba,
+ 0xaf, 0xc3, 0x00, 0x76, 0x00, 0xaf, 0xb7, 0xae, 0xc4, 0x00, 0x7e },
+ { 0xf9, 0x00, 0x8b, 0xb9, 0xb3, 0xc7, 0x00, 0x65, 0x00, 0xb7, 0xb8,
+ 0xaf, 0xc3, 0x00, 0x7a, 0x00, 0x80, 0xb6, 0xae, 0xc4, 0x00, 0x81 },
+ { 0xf9, 0x00, 0x93, 0xba, 0xb3, 0xc5, 0x00, 0x69, 0x00, 0xb8, 0xb9,
+ 0xae, 0xc1, 0x00, 0x7f, 0x00, 0xb0, 0xb6, 0xae, 0xc3, 0x00, 0x85 },
+ { 0xf9, 0x00, 0x97, 0xba, 0xb2, 0xc5, 0x00, 0x6c, 0x00, 0xb8, 0xb8,
+ 0xae, 0xc1, 0x00, 0x82, 0x00, 0xb0, 0xb6, 0xae, 0xc2, 0x00, 0x89 },
+ { 0xf9, 0x00, 0x9a, 0xba, 0xb1, 0xc4, 0x00, 0x6f, 0x00, 0xb8, 0xb8,
+ 0xad, 0xc0, 0x00, 0x86, 0x00, 0xb0, 0xb7, 0xad, 0xc0, 0x00, 0x8d },
+ { 0xf9, 0x00, 0x9c, 0xb9, 0xb0, 0xc4, 0x00, 0x72, 0x00, 0xb8, 0xb8,
+ 0xac, 0xbf, 0x00, 0x8a, 0x00, 0xb0, 0xb6, 0xac, 0xc0, 0x00, 0x91 },
+ { 0xf9, 0x00, 0x9e, 0xba, 0xb0, 0xc2, 0x00, 0x75, 0x00, 0xb9, 0xb8,
+ 0xab, 0xbe, 0x00, 0x8e, 0x00, 0xb0, 0xb6, 0xac, 0xbf, 0x00, 0x94 },
+ { 0xf9, 0x00, 0xa0, 0xb9, 0xaf, 0xc3, 0x00, 0x77, 0x00, 0xb9, 0xb7,
+ 0xab, 0xbe, 0x00, 0x90, 0x00, 0xb0, 0xb6, 0xab, 0xbf, 0x00, 0x97 },
+ { 0xf9, 0x00, 0xa2, 0xb9, 0xaf, 0xc2, 0x00, 0x7a, 0x00, 0xb9, 0xb7,
+ 0xaa, 0xbd, 0x00, 0x94, 0x00, 0xb0, 0xb5, 0xab, 0xbf, 0x00, 0x9a },
+ { 0xf9, 0x00, 0xa4, 0xb9, 0xaf, 0xc1, 0x00, 0x7d, 0x00, 0xb9, 0xb6,
+ 0xaa, 0xbb, 0x00, 0x97, 0x00, 0xb1, 0xb5, 0xaa, 0xbf, 0x00, 0x9d },
+ { 0xf9, 0x00, 0xa4, 0xb8, 0xb0, 0xbf, 0x00, 0x80, 0x00, 0xb8, 0xb6,
+ 0xaa, 0xbc, 0x00, 0x9a, 0x00, 0xb0, 0xb5, 0xab, 0xbd, 0x00, 0xa0 },
+ { 0xf9, 0x00, 0xa8, 0xb8, 0xae, 0xbe, 0x00, 0x84, 0x00, 0xb9, 0xb7,
+ 0xa8, 0xbc, 0x00, 0x9d, 0x00, 0xb2, 0xb5, 0xaa, 0xbc, 0x00, 0xa4 },
+ { 0xf9, 0x00, 0xa9, 0xb6, 0xad, 0xbf, 0x00, 0x86, 0x00, 0xb8, 0xb5,
+ 0xa8, 0xbc, 0x00, 0xa0, 0x00, 0xb3, 0xb3, 0xa9, 0xbc, 0x00, 0xa7 },
+ { 0xf9, 0x00, 0xa9, 0xb7, 0xae, 0xbd, 0x00, 0x89, 0x00, 0xb7, 0xb6,
+ 0xa8, 0xba, 0x00, 0xa4, 0x00, 0xb1, 0xb4, 0xaa, 0xbb, 0x00, 0xaa },
+ { 0xf9, 0x00, 0xa7, 0xb4, 0xae, 0xbf, 0x00, 0x91, 0x00, 0xb2, 0xb4,
+ 0xaa, 0xbb, 0x00, 0xac, 0x00, 0xb3, 0xb1, 0xaa, 0xbc, 0x00, 0xb3 },
+};
+
+struct ld9040 {
+ struct device *dev;
+ struct drm_panel panel;
+
+ struct regulator_bulk_data supplies[2];
+ struct gpio_desc *reset_gpio;
+ u32 power_on_delay;
+ u32 reset_delay;
+ struct videomode vm;
+ u32 width_mm;
+ u32 height_mm;
+
+ int brightness;
+
+ /* 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 inline struct ld9040 *panel_to_ld9040(struct drm_panel *panel)
+{
+ return container_of(panel, struct ld9040, panel);
+}
+
+static int ld9040_clear_error(struct ld9040 *ctx)
+{
+ int ret = ctx->error;
+
+ ctx->error = 0;
+ return ret;
+}
+
+static int ld9040_spi_write_word(struct ld9040 *ctx, u16 data)
+{
+ struct spi_device *spi = to_spi_device(ctx->dev);
+ struct spi_transfer xfer = {
+ .len = 2,
+ .tx_buf = &data,
+ };
+ struct spi_message msg;
+
+ spi_message_init(&msg);
+ spi_message_add_tail(&xfer, &msg);
+
+ return spi_sync(spi, &msg);
+}
+
+static void ld9040_dcs_write(struct ld9040 *ctx, const u8 *data, size_t len)
+{
+ int ret = 0;
+
+ if (ctx->error < 0 || len == 0)
+ return;
+
+ dev_dbg(ctx->dev, "writing dcs seq: %*ph\n", (int)len, data);
+ ret = ld9040_spi_write_word(ctx, *data);
+
+ while (!ret && --len) {
+ ++data;
+ ret = ld9040_spi_write_word(ctx, *data | 0x100);
+ }
+
+ if (ret) {
+ dev_err(ctx->dev, "error %d writing dcs seq: %*ph\n", ret,
+ (int)len, data);
+ ctx->error = ret;
+ }
+
+ usleep_range(300, 310);
+}
+
+#define ld9040_dcs_write_seq_static(ctx, seq...) \
+({\
+ static const u8 d[] = { seq };\
+ ld9040_dcs_write(ctx, d, ARRAY_SIZE(d));\
+})
+
+static void ld9040_brightness_set(struct ld9040 *ctx)
+{
+ ld9040_dcs_write(ctx, ld9040_gammas[ctx->brightness],
+ ARRAY_SIZE(ld9040_gammas[ctx->brightness]));
+
+ ld9040_dcs_write_seq_static(ctx, MCS_GAMMA_CTRL, 0x02, 0x5a);
+}
+
+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);
+ ld9040_dcs_write_seq_static(ctx, MCS_DISPCTL,
+ 0x02, 0x08, 0x08, 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_GTCON, 0x09, 0x00, 0x00);
+ ld9040_brightness_set(ctx);
+ ld9040_dcs_write_seq_static(ctx, MIPI_DCS_EXIT_SLEEP_MODE);
+ ld9040_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_ON);
+}
+
+static int ld9040_power_on(struct ld9040 *ctx)
+{
+ int ret;
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
+ if (ret < 0)
+ return ret;
+
+ msleep(ctx->power_on_delay);
+ gpiod_set_value(ctx->reset_gpio, 0);
+ msleep(ctx->reset_delay);
+ gpiod_set_value(ctx->reset_gpio, 1);
+ msleep(ctx->reset_delay);
+
+ return 0;
+}
+
+static int ld9040_power_off(struct ld9040 *ctx)
+{
+ return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
+}
+
+static int ld9040_disable(struct drm_panel *panel)
+{
+ return 0;
+}
+
+static int ld9040_unprepare(struct drm_panel *panel)
+{
+ struct ld9040 *ctx = panel_to_ld9040(panel);
+
+ msleep(120);
+ ld9040_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_OFF);
+ ld9040_dcs_write_seq_static(ctx, MIPI_DCS_ENTER_SLEEP_MODE);
+ msleep(40);
+
+ ld9040_clear_error(ctx);
+
+ return ld9040_power_off(ctx);
+}
+
+static int ld9040_prepare(struct drm_panel *panel)
+{
+ struct ld9040 *ctx = panel_to_ld9040(panel);
+ int ret;
+
+ ret = ld9040_power_on(ctx);
+ if (ret < 0)
+ return ret;
+
+ ld9040_init(ctx);
+
+ ret = ld9040_clear_error(ctx);
+
+ if (ret < 0)
+ ld9040_unprepare(panel);
+
+ return ret;
+}
+
+static int ld9040_enable(struct drm_panel *panel)
+{
+ return 0;
+}
+
+static int ld9040_get_modes(struct drm_panel *panel,
+ struct drm_connector *connector)
+{
+ struct ld9040 *ctx = panel_to_ld9040(panel);
+ struct drm_display_mode *mode;
+
+ mode = drm_mode_create(connector->dev);
+ if (!mode) {
+ dev_err(panel->dev, "failed to create a new display mode\n");
+ return 0;
+ }
+
+ drm_display_mode_from_videomode(&ctx->vm, mode);
+ mode->width_mm = ctx->width_mm;
+ mode->height_mm = ctx->height_mm;
+ connector->display_info.width_mm = mode->width_mm;
+ connector->display_info.height_mm = mode->height_mm;
+
+ mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+ drm_mode_probed_add(connector, mode);
+
+ return 1;
+}
+
+static const struct drm_panel_funcs ld9040_drm_funcs = {
+ .disable = ld9040_disable,
+ .unprepare = ld9040_unprepare,
+ .prepare = ld9040_prepare,
+ .enable = ld9040_enable,
+ .get_modes = ld9040_get_modes,
+};
+
+static int ld9040_parse_dt(struct ld9040 *ctx)
+{
+ struct device *dev = ctx->dev;
+ struct device_node *np = dev->of_node;
+ int ret;
+
+ ret = of_get_videomode(np, &ctx->vm, 0);
+ if (ret < 0)
+ return ret;
+
+ of_property_read_u32(np, "power-on-delay", &ctx->power_on_delay);
+ of_property_read_u32(np, "reset-delay", &ctx->reset_delay);
+ of_property_read_u32(np, "panel-width-mm", &ctx->width_mm);
+ of_property_read_u32(np, "panel-height-mm", &ctx->height_mm);
+
+ return 0;
+}
+
+static int ld9040_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct ld9040 *ctx;
+ int ret;
+
+ ctx = devm_kzalloc(dev, sizeof(struct ld9040), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ spi_set_drvdata(spi, ctx);
+
+ ctx->dev = dev;
+ ctx->brightness = ARRAY_SIZE(ld9040_gammas) - 1;
+
+ ret = ld9040_parse_dt(ctx);
+ if (ret < 0)
+ return ret;
+
+ 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)
+ 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);
+ }
+
+ spi->bits_per_word = 9;
+ ret = spi_setup(spi);
+ if (ret < 0) {
+ dev_err(dev, "spi setup failed.\n");
+ return ret;
+ }
+
+ drm_panel_init(&ctx->panel, dev, &ld9040_drm_funcs,
+ DRM_MODE_CONNECTOR_DPI);
+
+ drm_panel_add(&ctx->panel);
+
+ return 0;
+}
+
+static void ld9040_remove(struct spi_device *spi)
+{
+ struct ld9040 *ctx = spi_get_drvdata(spi);
+
+ ld9040_power_off(ctx);
+ drm_panel_remove(&ctx->panel);
+}
+
+static const struct of_device_id ld9040_of_match[] = {
+ { .compatible = "samsung,ld9040" },
+ { }
+};
+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,
+ },
+};
+module_spi_driver(ld9040_driver);
+
+MODULE_AUTHOR("Andrzej Hajda <a.hajda@samsung.com>");
+MODULE_DESCRIPTION("ld9040 LCD Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/panel/panel-samsung-s6d16d0.c b/drivers/gpu/drm/panel/panel-samsung-s6d16d0.c
new file mode 100644
index 000000000..008e2b0d6
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-samsung-s6d16d0.c
@@ -0,0 +1,241 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * MIPI-DSI Samsung s6d16d0 panel driver. This is a 864x480
+ * AMOLED panel with a command-only DSI interface.
+ */
+
+#include <drm/drm_modes.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_panel.h>
+
+#include <linux/gpio/consumer.h>
+#include <linux/regulator/consumer.h>
+#include <linux/delay.h>
+#include <linux/of_device.h>
+#include <linux/module.h>
+
+struct s6d16d0 {
+ struct device *dev;
+ struct drm_panel panel;
+ struct regulator *supply;
+ struct gpio_desc *reset_gpio;
+};
+
+/*
+ * The timings are not very helpful as the display is used in
+ * command mode.
+ */
+static const struct drm_display_mode samsung_s6d16d0_mode = {
+ /* HS clock, (htotal*vtotal*vrefresh)/1000 */
+ .clock = 420160,
+ .hdisplay = 864,
+ .hsync_start = 864 + 154,
+ .hsync_end = 864 + 154 + 16,
+ .htotal = 864 + 154 + 16 + 32,
+ .vdisplay = 480,
+ .vsync_start = 480 + 1,
+ .vsync_end = 480 + 1 + 1,
+ .vtotal = 480 + 1 + 1 + 1,
+ .width_mm = 84,
+ .height_mm = 48,
+};
+
+static inline struct s6d16d0 *panel_to_s6d16d0(struct drm_panel *panel)
+{
+ return container_of(panel, struct s6d16d0, panel);
+}
+
+static int s6d16d0_unprepare(struct drm_panel *panel)
+{
+ struct s6d16d0 *s6 = panel_to_s6d16d0(panel);
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(s6->dev);
+ int ret;
+
+ /* Enter sleep mode */
+ ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
+ if (ret) {
+ dev_err(s6->dev, "failed to enter sleep mode (%d)\n", ret);
+ return ret;
+ }
+
+ /* Assert RESET */
+ gpiod_set_value_cansleep(s6->reset_gpio, 1);
+ regulator_disable(s6->supply);
+
+ return 0;
+}
+
+static int s6d16d0_prepare(struct drm_panel *panel)
+{
+ struct s6d16d0 *s6 = panel_to_s6d16d0(panel);
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(s6->dev);
+ int ret;
+
+ ret = regulator_enable(s6->supply);
+ if (ret) {
+ dev_err(s6->dev, "failed to enable supply (%d)\n", ret);
+ return ret;
+ }
+
+ /* Assert RESET */
+ gpiod_set_value_cansleep(s6->reset_gpio, 1);
+ udelay(10);
+ /* De-assert RESET */
+ gpiod_set_value_cansleep(s6->reset_gpio, 0);
+ msleep(120);
+
+ /* Enabe tearing mode: send TE (tearing effect) at VBLANK */
+ ret = mipi_dsi_dcs_set_tear_on(dsi,
+ MIPI_DSI_DCS_TEAR_MODE_VBLANK);
+ if (ret) {
+ 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) {
+ dev_err(s6->dev, "failed to exit sleep mode (%d)\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int s6d16d0_enable(struct drm_panel *panel)
+{
+ struct s6d16d0 *s6 = panel_to_s6d16d0(panel);
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(s6->dev);
+ int ret;
+
+ ret = mipi_dsi_dcs_set_display_on(dsi);
+ if (ret) {
+ dev_err(s6->dev, "failed to turn display on (%d)\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int s6d16d0_disable(struct drm_panel *panel)
+{
+ struct s6d16d0 *s6 = panel_to_s6d16d0(panel);
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(s6->dev);
+ int ret;
+
+ ret = mipi_dsi_dcs_set_display_off(dsi);
+ if (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,
+ struct drm_connector *connector)
+{
+ struct drm_display_mode *mode;
+
+ mode = drm_mode_duplicate(connector->dev, &samsung_s6d16d0_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 s6d16d0_drm_funcs = {
+ .disable = s6d16d0_disable,
+ .unprepare = s6d16d0_unprepare,
+ .prepare = s6d16d0_prepare,
+ .enable = s6d16d0_enable,
+ .get_modes = s6d16d0_get_modes,
+};
+
+static int s6d16d0_probe(struct mipi_dsi_device *dsi)
+{
+ struct device *dev = &dsi->dev;
+ struct s6d16d0 *s6;
+ int ret;
+
+ s6 = devm_kzalloc(dev, sizeof(struct s6d16d0), GFP_KERNEL);
+ if (!s6)
+ return -ENOMEM;
+
+ mipi_dsi_set_drvdata(dsi, s6);
+ s6->dev = dev;
+
+ dsi->lanes = 2;
+ dsi->format = MIPI_DSI_FMT_RGB888;
+ dsi->hs_rate = 420160000;
+ dsi->lp_rate = 19200000;
+ /*
+ * This display uses command mode so no MIPI_DSI_MODE_VIDEO
+ * or MIPI_DSI_MODE_VIDEO_SYNC_PULSE
+ *
+ * As we only send commands we do not need to be continuously
+ * clocked.
+ */
+ dsi->mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS;
+
+ s6->supply = devm_regulator_get(dev, "vdd1");
+ if (IS_ERR(s6->supply))
+ return PTR_ERR(s6->supply);
+
+ /* This asserts RESET by default */
+ s6->reset_gpio = devm_gpiod_get_optional(dev, "reset",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(s6->reset_gpio)) {
+ ret = PTR_ERR(s6->reset_gpio);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to request GPIO (%d)\n", ret);
+ return ret;
+ }
+
+ drm_panel_init(&s6->panel, dev, &s6d16d0_drm_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ drm_panel_add(&s6->panel);
+
+ ret = mipi_dsi_attach(dsi);
+ if (ret < 0)
+ drm_panel_remove(&s6->panel);
+
+ return ret;
+}
+
+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);
+}
+
+static const struct of_device_id s6d16d0_of_match[] = {
+ { .compatible = "samsung,s6d16d0" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, s6d16d0_of_match);
+
+static struct mipi_dsi_driver s6d16d0_driver = {
+ .probe = s6d16d0_probe,
+ .remove = s6d16d0_remove,
+ .driver = {
+ .name = "panel-samsung-s6d16d0",
+ .of_match_table = s6d16d0_of_match,
+ },
+};
+module_mipi_dsi_driver(s6d16d0_driver);
+
+MODULE_AUTHOR("Linus Wallei <linus.walleij@linaro.org>");
+MODULE_DESCRIPTION("MIPI-DSI s6d16d0 Panel Driver");
+MODULE_LICENSE("GPL v2");
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 000000000..2adb223a8
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-samsung-s6d27a1.c
@@ -0,0 +1,319 @@
+// 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_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ 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;
+
+ drm_panel_init(&ctx->panel, dev, &s6d27a1_drm_funcs,
+ DRM_MODE_CONNECTOR_DPI);
+
+ 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-s6e3ha2.c b/drivers/gpu/drm/panel/panel-samsung-s6e3ha2.c
new file mode 100644
index 000000000..439ef3073
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-samsung-s6e3ha2.c
@@ -0,0 +1,782 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MIPI-DSI based s6e3ha2 AMOLED 5.7 inch panel driver.
+ *
+ * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * Donghwa Lee <dh09.lee@samsung.com>
+ * Hyungwon Hwang <human.hwang@samsung.com>
+ * Hoegeun Kwon <hoegeun.kwon@samsung.com>
+ */
+
+#include <linux/backlight.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/of_device.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
+
+#define S6E3HA2_NUM_GAMMA_STEPS 46
+#define S6E3HA2_GAMMA_CMD_CNT 35
+#define S6E3HA2_VINT_STATUS_MAX 10
+
+static const u8 gamma_tbl[S6E3HA2_NUM_GAMMA_STEPS][S6E3HA2_GAMMA_CMD_CNT] = {
+ { 0x00, 0xb8, 0x00, 0xc3, 0x00, 0xb1, 0x89, 0x87, 0x87, 0x82, 0x83,
+ 0x85, 0x88, 0x8b, 0x8b, 0x84, 0x88, 0x82, 0x82, 0x89, 0x86, 0x8c,
+ 0x94, 0x84, 0xb1, 0xaf, 0x8e, 0xcf, 0xad, 0xc9, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xb8, 0x00, 0xc3, 0x00, 0xb1, 0x89, 0x87, 0x87, 0x84, 0x84,
+ 0x85, 0x87, 0x8b, 0x8a, 0x84, 0x88, 0x82, 0x82, 0x89, 0x86, 0x8a,
+ 0x93, 0x84, 0xb0, 0xae, 0x8e, 0xc9, 0xa8, 0xc5, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xb8, 0x00, 0xc3, 0x00, 0xb1, 0x89, 0x87, 0x87, 0x83, 0x83,
+ 0x85, 0x86, 0x8a, 0x8a, 0x84, 0x88, 0x81, 0x84, 0x8a, 0x88, 0x8a,
+ 0x91, 0x84, 0xb1, 0xae, 0x8b, 0xd5, 0xb2, 0xcc, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xb8, 0x00, 0xc3, 0x00, 0xb1, 0x89, 0x87, 0x87, 0x83, 0x83,
+ 0x85, 0x86, 0x8a, 0x8a, 0x84, 0x87, 0x81, 0x84, 0x8a, 0x87, 0x8a,
+ 0x91, 0x85, 0xae, 0xac, 0x8a, 0xc3, 0xa3, 0xc0, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xb8, 0x00, 0xc3, 0x00, 0xb1, 0x88, 0x86, 0x87, 0x85, 0x85,
+ 0x86, 0x85, 0x88, 0x89, 0x84, 0x89, 0x82, 0x84, 0x87, 0x85, 0x8b,
+ 0x91, 0x88, 0xad, 0xab, 0x8a, 0xb7, 0x9b, 0xb6, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xb8, 0x00, 0xc3, 0x00, 0xb1, 0x89, 0x87, 0x87, 0x83, 0x83,
+ 0x85, 0x86, 0x89, 0x8a, 0x84, 0x89, 0x83, 0x83, 0x86, 0x84, 0x8b,
+ 0x90, 0x84, 0xb0, 0xae, 0x8b, 0xce, 0xad, 0xc8, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xb8, 0x00, 0xc3, 0x00, 0xb1, 0x89, 0x87, 0x87, 0x83, 0x83,
+ 0x85, 0x87, 0x89, 0x8a, 0x83, 0x87, 0x82, 0x85, 0x88, 0x87, 0x89,
+ 0x8f, 0x84, 0xac, 0xaa, 0x89, 0xb1, 0x98, 0xaf, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xb8, 0x00, 0xc3, 0x00, 0xb1, 0x89, 0x87, 0x87, 0x83, 0x83,
+ 0x85, 0x86, 0x88, 0x89, 0x84, 0x88, 0x83, 0x82, 0x85, 0x84, 0x8c,
+ 0x91, 0x86, 0xac, 0xaa, 0x89, 0xc2, 0xa5, 0xbd, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xb8, 0x00, 0xc3, 0x00, 0xb1, 0x88, 0x86, 0x87, 0x84, 0x84,
+ 0x85, 0x87, 0x89, 0x8a, 0x83, 0x87, 0x82, 0x85, 0x88, 0x87, 0x88,
+ 0x8b, 0x82, 0xad, 0xaa, 0x8a, 0xc2, 0xa5, 0xbd, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xb8, 0x00, 0xc3, 0x00, 0xb1, 0x89, 0x87, 0x87, 0x83, 0x83,
+ 0x85, 0x86, 0x87, 0x89, 0x84, 0x88, 0x83, 0x82, 0x85, 0x84, 0x8a,
+ 0x8e, 0x84, 0xae, 0xac, 0x89, 0xda, 0xb7, 0xd0, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xb8, 0x00, 0xc3, 0x00, 0xb1, 0x88, 0x86, 0x87, 0x84, 0x84,
+ 0x85, 0x86, 0x87, 0x89, 0x84, 0x88, 0x83, 0x80, 0x83, 0x82, 0x8b,
+ 0x8e, 0x85, 0xac, 0xaa, 0x89, 0xc8, 0xaa, 0xc1, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xb8, 0x00, 0xc3, 0x00, 0xb1, 0x88, 0x86, 0x87, 0x84, 0x84,
+ 0x85, 0x86, 0x87, 0x89, 0x81, 0x85, 0x81, 0x84, 0x86, 0x84, 0x8c,
+ 0x8c, 0x84, 0xa9, 0xa8, 0x87, 0xa3, 0x92, 0xa1, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xb8, 0x00, 0xc3, 0x00, 0xb1, 0x88, 0x86, 0x87, 0x84, 0x84,
+ 0x85, 0x86, 0x87, 0x89, 0x84, 0x86, 0x83, 0x80, 0x83, 0x81, 0x8c,
+ 0x8d, 0x84, 0xaa, 0xaa, 0x89, 0xce, 0xaf, 0xc5, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xb8, 0x00, 0xc3, 0x00, 0xb1, 0x88, 0x86, 0x87, 0x84, 0x84,
+ 0x85, 0x86, 0x87, 0x89, 0x81, 0x83, 0x80, 0x83, 0x85, 0x85, 0x8c,
+ 0x8c, 0x84, 0xa8, 0xa8, 0x88, 0xb5, 0x9f, 0xb0, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xb8, 0x00, 0xc3, 0x00, 0xb1, 0x88, 0x86, 0x87, 0x84, 0x84,
+ 0x86, 0x86, 0x87, 0x88, 0x81, 0x83, 0x80, 0x83, 0x85, 0x85, 0x8c,
+ 0x8b, 0x84, 0xab, 0xa8, 0x86, 0xd4, 0xb4, 0xc9, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xb8, 0x00, 0xc3, 0x00, 0xb1, 0x88, 0x86, 0x87, 0x84, 0x84,
+ 0x86, 0x86, 0x87, 0x88, 0x81, 0x83, 0x80, 0x84, 0x84, 0x85, 0x8b,
+ 0x8a, 0x83, 0xa6, 0xa5, 0x84, 0xbb, 0xa4, 0xb3, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xb8, 0x00, 0xc3, 0x00, 0xb1, 0x88, 0x86, 0x87, 0x84, 0x84,
+ 0x86, 0x85, 0x86, 0x86, 0x82, 0x85, 0x81, 0x82, 0x83, 0x84, 0x8e,
+ 0x8b, 0x83, 0xa4, 0xa3, 0x8a, 0xa1, 0x93, 0x9d, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xb8, 0x00, 0xc3, 0x00, 0xb1, 0x88, 0x86, 0x87, 0x83, 0x83,
+ 0x85, 0x86, 0x87, 0x87, 0x82, 0x85, 0x81, 0x82, 0x82, 0x84, 0x8e,
+ 0x8b, 0x83, 0xa4, 0xa2, 0x86, 0xc1, 0xa9, 0xb7, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xb8, 0x00, 0xc3, 0x00, 0xb1, 0x88, 0x86, 0x87, 0x83, 0x83,
+ 0x85, 0x86, 0x87, 0x87, 0x82, 0x85, 0x81, 0x82, 0x82, 0x84, 0x8d,
+ 0x89, 0x82, 0xa2, 0xa1, 0x84, 0xa7, 0x98, 0xa1, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xb8, 0x00, 0xc3, 0x00, 0xb1, 0x88, 0x86, 0x87, 0x83, 0x83,
+ 0x85, 0x86, 0x87, 0x87, 0x82, 0x85, 0x81, 0x83, 0x83, 0x85, 0x8c,
+ 0x87, 0x7f, 0xa2, 0x9d, 0x88, 0x8d, 0x88, 0x8b, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xbb, 0x00, 0xc5, 0x00, 0xb4, 0x87, 0x86, 0x86, 0x84, 0x83,
+ 0x86, 0x87, 0x87, 0x87, 0x80, 0x82, 0x7f, 0x86, 0x86, 0x88, 0x8a,
+ 0x84, 0x7e, 0x9d, 0x9c, 0x82, 0x8d, 0x88, 0x8b, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xbd, 0x00, 0xc7, 0x00, 0xb7, 0x87, 0x85, 0x85, 0x84, 0x83,
+ 0x86, 0x86, 0x86, 0x88, 0x81, 0x83, 0x80, 0x83, 0x84, 0x85, 0x8a,
+ 0x85, 0x7e, 0x9c, 0x9b, 0x85, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xc0, 0x00, 0xca, 0x00, 0xbb, 0x87, 0x86, 0x85, 0x83, 0x83,
+ 0x85, 0x86, 0x86, 0x88, 0x81, 0x83, 0x80, 0x84, 0x85, 0x86, 0x89,
+ 0x83, 0x7d, 0x9c, 0x99, 0x87, 0x7b, 0x7b, 0x7c, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xc4, 0x00, 0xcd, 0x00, 0xbe, 0x87, 0x86, 0x85, 0x83, 0x83,
+ 0x86, 0x85, 0x85, 0x87, 0x81, 0x82, 0x80, 0x82, 0x82, 0x83, 0x8a,
+ 0x85, 0x7f, 0x9f, 0x9b, 0x86, 0xb4, 0xa1, 0xac, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xc7, 0x00, 0xd0, 0x00, 0xc2, 0x87, 0x85, 0x85, 0x83, 0x82,
+ 0x85, 0x85, 0x85, 0x86, 0x82, 0x83, 0x80, 0x82, 0x82, 0x84, 0x87,
+ 0x86, 0x80, 0x9e, 0x9a, 0x87, 0xa7, 0x98, 0xa1, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xca, 0x00, 0xd2, 0x00, 0xc5, 0x87, 0x85, 0x84, 0x82, 0x82,
+ 0x84, 0x85, 0x85, 0x86, 0x81, 0x82, 0x7f, 0x82, 0x82, 0x84, 0x88,
+ 0x86, 0x81, 0x9d, 0x98, 0x86, 0x8d, 0x88, 0x8b, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xce, 0x00, 0xd6, 0x00, 0xca, 0x86, 0x85, 0x84, 0x83, 0x83,
+ 0x85, 0x84, 0x84, 0x85, 0x81, 0x82, 0x80, 0x81, 0x81, 0x82, 0x89,
+ 0x86, 0x81, 0x9c, 0x97, 0x86, 0xa7, 0x98, 0xa1, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xd1, 0x00, 0xd9, 0x00, 0xce, 0x86, 0x84, 0x83, 0x83, 0x82,
+ 0x85, 0x85, 0x85, 0x86, 0x81, 0x83, 0x81, 0x82, 0x82, 0x83, 0x86,
+ 0x83, 0x7f, 0x99, 0x95, 0x86, 0xbb, 0xa4, 0xb3, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xd4, 0x00, 0xdb, 0x00, 0xd1, 0x86, 0x85, 0x83, 0x83, 0x82,
+ 0x85, 0x84, 0x84, 0x85, 0x80, 0x83, 0x82, 0x80, 0x80, 0x81, 0x87,
+ 0x84, 0x81, 0x98, 0x93, 0x85, 0xae, 0x9c, 0xa8, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xd8, 0x00, 0xde, 0x00, 0xd6, 0x86, 0x84, 0x83, 0x81, 0x81,
+ 0x83, 0x85, 0x85, 0x85, 0x82, 0x83, 0x81, 0x81, 0x81, 0x83, 0x86,
+ 0x84, 0x80, 0x98, 0x91, 0x85, 0x7b, 0x7b, 0x7c, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xdc, 0x00, 0xe2, 0x00, 0xda, 0x85, 0x84, 0x83, 0x82, 0x82,
+ 0x84, 0x84, 0x84, 0x85, 0x81, 0x82, 0x82, 0x80, 0x80, 0x81, 0x83,
+ 0x82, 0x7f, 0x99, 0x93, 0x86, 0x94, 0x8b, 0x92, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xdf, 0x00, 0xe5, 0x00, 0xde, 0x85, 0x84, 0x82, 0x82, 0x82,
+ 0x84, 0x83, 0x83, 0x84, 0x81, 0x81, 0x80, 0x83, 0x82, 0x84, 0x82,
+ 0x81, 0x7f, 0x99, 0x92, 0x86, 0x7b, 0x7b, 0x7c, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xe4, 0x00, 0xe9, 0x00, 0xe3, 0x84, 0x83, 0x82, 0x81, 0x81,
+ 0x82, 0x83, 0x83, 0x84, 0x80, 0x81, 0x80, 0x83, 0x83, 0x84, 0x80,
+ 0x81, 0x7c, 0x99, 0x92, 0x87, 0xa1, 0x93, 0x9d, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xe4, 0x00, 0xe9, 0x00, 0xe3, 0x85, 0x84, 0x83, 0x81, 0x81,
+ 0x82, 0x82, 0x82, 0x83, 0x80, 0x81, 0x80, 0x81, 0x80, 0x82, 0x83,
+ 0x82, 0x80, 0x91, 0x8d, 0x83, 0x9a, 0x90, 0x96, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xe4, 0x00, 0xe9, 0x00, 0xe3, 0x84, 0x83, 0x82, 0x81, 0x81,
+ 0x82, 0x83, 0x83, 0x84, 0x80, 0x81, 0x80, 0x81, 0x80, 0x82, 0x83,
+ 0x81, 0x7f, 0x91, 0x8c, 0x82, 0x8d, 0x88, 0x8b, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xe4, 0x00, 0xe9, 0x00, 0xe3, 0x84, 0x83, 0x82, 0x81, 0x81,
+ 0x82, 0x83, 0x83, 0x83, 0x82, 0x82, 0x81, 0x81, 0x80, 0x82, 0x82,
+ 0x82, 0x7f, 0x94, 0x89, 0x84, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xe4, 0x00, 0xe9, 0x00, 0xe3, 0x84, 0x83, 0x82, 0x81, 0x81,
+ 0x82, 0x83, 0x83, 0x83, 0x82, 0x82, 0x81, 0x81, 0x80, 0x82, 0x83,
+ 0x82, 0x7f, 0x91, 0x85, 0x81, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xe4, 0x00, 0xe9, 0x00, 0xe3, 0x84, 0x83, 0x82, 0x81, 0x81,
+ 0x82, 0x83, 0x83, 0x83, 0x80, 0x80, 0x7f, 0x83, 0x82, 0x84, 0x83,
+ 0x82, 0x7f, 0x90, 0x84, 0x81, 0x9a, 0x90, 0x96, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xe4, 0x00, 0xe9, 0x00, 0xe3, 0x84, 0x83, 0x82, 0x80, 0x80,
+ 0x82, 0x83, 0x83, 0x83, 0x80, 0x80, 0x7f, 0x80, 0x80, 0x81, 0x81,
+ 0x82, 0x83, 0x7e, 0x80, 0x7c, 0xa4, 0x97, 0x9f, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xe9, 0x00, 0xec, 0x00, 0xe8, 0x84, 0x83, 0x82, 0x81, 0x81,
+ 0x82, 0x82, 0x82, 0x83, 0x7f, 0x7f, 0x7f, 0x81, 0x80, 0x82, 0x83,
+ 0x83, 0x84, 0x79, 0x7c, 0x79, 0xb1, 0xa0, 0xaa, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xed, 0x00, 0xf0, 0x00, 0xec, 0x83, 0x83, 0x82, 0x80, 0x80,
+ 0x81, 0x82, 0x82, 0x82, 0x7f, 0x7f, 0x7e, 0x81, 0x81, 0x82, 0x80,
+ 0x81, 0x81, 0x84, 0x84, 0x83, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xf1, 0x00, 0xf4, 0x00, 0xf1, 0x83, 0x82, 0x82, 0x80, 0x80,
+ 0x81, 0x82, 0x82, 0x82, 0x80, 0x80, 0x80, 0x80, 0x80, 0x81, 0x7d,
+ 0x7e, 0x7f, 0x84, 0x84, 0x83, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xf6, 0x00, 0xf7, 0x00, 0xf5, 0x82, 0x82, 0x81, 0x80, 0x80,
+ 0x80, 0x82, 0x82, 0x82, 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x7f, 0x82,
+ 0x82, 0x82, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 0x00, 0xfa, 0x00, 0xfb, 0x00, 0xfa, 0x81, 0x81, 0x81, 0x80, 0x80,
+ 0x80, 0x82, 0x82, 0x82, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00 },
+ { 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,
+ 0x00, 0x00 },
+ { 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,
+ 0x00, 0x00 }
+};
+
+static const unsigned char vint_table[S6E3HA2_VINT_STATUS_MAX] = {
+ 0x18, 0x19, 0x1a, 0x1b, 0x1c,
+ 0x1d, 0x1e, 0x1f, 0x20, 0x21
+};
+
+enum s6e3ha2_type {
+ HA2_TYPE,
+ HF2_TYPE,
+};
+
+struct s6e3ha2_panel_desc {
+ const struct drm_display_mode *mode;
+ enum s6e3ha2_type type;
+};
+
+struct s6e3ha2 {
+ struct device *dev;
+ struct drm_panel panel;
+ struct backlight_device *bl_dev;
+
+ struct regulator_bulk_data supplies[2];
+ struct gpio_desc *reset_gpio;
+ struct gpio_desc *enable_gpio;
+
+ const struct s6e3ha2_panel_desc *desc;
+};
+
+static int s6e3ha2_dcs_write(struct s6e3ha2 *ctx, const void *data, size_t len)
+{
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+
+ return mipi_dsi_dcs_write_buffer(dsi, data, len);
+}
+
+#define s6e3ha2_dcs_write_seq_static(ctx, seq...) do { \
+ static const u8 d[] = { seq }; \
+ int ret; \
+ ret = s6e3ha2_dcs_write(ctx, d, ARRAY_SIZE(d)); \
+ if (ret < 0) \
+ return ret; \
+} while (0)
+
+#define s6e3ha2_call_write_func(ret, func) do { \
+ ret = (func); \
+ if (ret < 0) \
+ return ret; \
+} while (0)
+
+static int s6e3ha2_test_key_on_f0(struct s6e3ha2 *ctx)
+{
+ s6e3ha2_dcs_write_seq_static(ctx, 0xf0, 0x5a, 0x5a);
+ return 0;
+}
+
+static int s6e3ha2_test_key_off_f0(struct s6e3ha2 *ctx)
+{
+ s6e3ha2_dcs_write_seq_static(ctx, 0xf0, 0xa5, 0xa5);
+ return 0;
+}
+
+static int s6e3ha2_test_key_on_fc(struct s6e3ha2 *ctx)
+{
+ s6e3ha2_dcs_write_seq_static(ctx, 0xfc, 0x5a, 0x5a);
+ return 0;
+}
+
+static int s6e3ha2_test_key_off_fc(struct s6e3ha2 *ctx)
+{
+ s6e3ha2_dcs_write_seq_static(ctx, 0xfc, 0xa5, 0xa5);
+ return 0;
+}
+
+static int s6e3ha2_single_dsi_set(struct s6e3ha2 *ctx)
+{
+ s6e3ha2_dcs_write_seq_static(ctx, 0xf2, 0x67);
+ s6e3ha2_dcs_write_seq_static(ctx, 0xf9, 0x09);
+ return 0;
+}
+
+static int s6e3ha2_freq_calibration(struct s6e3ha2 *ctx)
+{
+ s6e3ha2_dcs_write_seq_static(ctx, 0xfd, 0x1c);
+ if (ctx->desc->type == HF2_TYPE)
+ s6e3ha2_dcs_write_seq_static(ctx, 0xf2, 0x67, 0x40, 0xc5);
+ s6e3ha2_dcs_write_seq_static(ctx, 0xfe, 0x20, 0x39);
+ s6e3ha2_dcs_write_seq_static(ctx, 0xfe, 0xa0);
+ s6e3ha2_dcs_write_seq_static(ctx, 0xfe, 0x20);
+
+ if (ctx->desc->type == HA2_TYPE)
+ s6e3ha2_dcs_write_seq_static(ctx, 0xce, 0x03, 0x3b, 0x12, 0x62,
+ 0x40, 0x80, 0xc0, 0x28, 0x28,
+ 0x28, 0x28, 0x39, 0xc5);
+ else
+ s6e3ha2_dcs_write_seq_static(ctx, 0xce, 0x03, 0x3b, 0x14, 0x6d,
+ 0x40, 0x80, 0xc0, 0x28, 0x28,
+ 0x28, 0x28, 0x39, 0xc5);
+
+ return 0;
+}
+
+static int s6e3ha2_aor_control(struct s6e3ha2 *ctx)
+{
+ s6e3ha2_dcs_write_seq_static(ctx, 0xb2, 0x03, 0x10);
+ return 0;
+}
+
+static int s6e3ha2_caps_elvss_set(struct s6e3ha2 *ctx)
+{
+ s6e3ha2_dcs_write_seq_static(ctx, 0xb6, 0x9c, 0x0a);
+ return 0;
+}
+
+static int s6e3ha2_acl_off(struct s6e3ha2 *ctx)
+{
+ s6e3ha2_dcs_write_seq_static(ctx, 0x55, 0x00);
+ return 0;
+}
+
+static int s6e3ha2_acl_off_opr(struct s6e3ha2 *ctx)
+{
+ s6e3ha2_dcs_write_seq_static(ctx, 0xb5, 0x40);
+ return 0;
+}
+
+static int s6e3ha2_test_global(struct s6e3ha2 *ctx)
+{
+ s6e3ha2_dcs_write_seq_static(ctx, 0xb0, 0x07);
+ return 0;
+}
+
+static int s6e3ha2_test(struct s6e3ha2 *ctx)
+{
+ s6e3ha2_dcs_write_seq_static(ctx, 0xb8, 0x19);
+ return 0;
+}
+
+static int s6e3ha2_touch_hsync_on1(struct s6e3ha2 *ctx)
+{
+ s6e3ha2_dcs_write_seq_static(ctx, 0xbd, 0x33, 0x11, 0x02,
+ 0x16, 0x02, 0x16);
+ return 0;
+}
+
+static int s6e3ha2_pentile_control(struct s6e3ha2 *ctx)
+{
+ s6e3ha2_dcs_write_seq_static(ctx, 0xc0, 0x00, 0x00, 0xd8, 0xd8);
+ return 0;
+}
+
+static int s6e3ha2_poc_global(struct s6e3ha2 *ctx)
+{
+ s6e3ha2_dcs_write_seq_static(ctx, 0xb0, 0x20);
+ return 0;
+}
+
+static int s6e3ha2_poc_setting(struct s6e3ha2 *ctx)
+{
+ s6e3ha2_dcs_write_seq_static(ctx, 0xfe, 0x08);
+ return 0;
+}
+
+static int s6e3ha2_pcd_set_off(struct s6e3ha2 *ctx)
+{
+ s6e3ha2_dcs_write_seq_static(ctx, 0xcc, 0x40, 0x51);
+ return 0;
+}
+
+static int s6e3ha2_err_fg_set(struct s6e3ha2 *ctx)
+{
+ s6e3ha2_dcs_write_seq_static(ctx, 0xed, 0x44);
+ return 0;
+}
+
+static int s6e3ha2_hbm_off(struct s6e3ha2 *ctx)
+{
+ s6e3ha2_dcs_write_seq_static(ctx, 0x53, 0x00);
+ return 0;
+}
+
+static int s6e3ha2_te_start_setting(struct s6e3ha2 *ctx)
+{
+ s6e3ha2_dcs_write_seq_static(ctx, 0xb9, 0x10, 0x09, 0xff, 0x00, 0x09);
+ return 0;
+}
+
+static int s6e3ha2_gamma_update(struct s6e3ha2 *ctx)
+{
+ s6e3ha2_dcs_write_seq_static(ctx, 0xf7, 0x03);
+ ndelay(100); /* need for 100ns delay */
+ s6e3ha2_dcs_write_seq_static(ctx, 0xf7, 0x00);
+ return 0;
+}
+
+static int s6e3ha2_get_brightness(struct backlight_device *bl_dev)
+{
+ return bl_dev->props.brightness;
+}
+
+static int s6e3ha2_set_vint(struct s6e3ha2 *ctx)
+{
+ struct backlight_device *bl_dev = ctx->bl_dev;
+ unsigned int brightness = bl_dev->props.brightness;
+ unsigned char data[] = { 0xf4, 0x8b,
+ vint_table[brightness * (S6E3HA2_VINT_STATUS_MAX - 1) /
+ S6E3HA2_MAX_BRIGHTNESS] };
+
+ return s6e3ha2_dcs_write(ctx, data, ARRAY_SIZE(data));
+}
+
+static unsigned int s6e3ha2_get_brightness_index(unsigned int brightness)
+{
+ return (brightness * (S6E3HA2_NUM_GAMMA_STEPS - 1)) /
+ S6E3HA2_MAX_BRIGHTNESS;
+}
+
+static int s6e3ha2_update_gamma(struct s6e3ha2 *ctx, unsigned int brightness)
+{
+ struct backlight_device *bl_dev = ctx->bl_dev;
+ unsigned int index = s6e3ha2_get_brightness_index(brightness);
+ u8 data[S6E3HA2_GAMMA_CMD_CNT + 1] = { 0xca, };
+ int ret;
+
+ memcpy(data + 1, gamma_tbl + index, S6E3HA2_GAMMA_CMD_CNT);
+ s6e3ha2_call_write_func(ret,
+ s6e3ha2_dcs_write(ctx, data, ARRAY_SIZE(data)));
+
+ s6e3ha2_call_write_func(ret, s6e3ha2_gamma_update(ctx));
+ bl_dev->props.brightness = brightness;
+
+ return 0;
+}
+
+static int s6e3ha2_set_brightness(struct backlight_device *bl_dev)
+{
+ struct s6e3ha2 *ctx = bl_get_data(bl_dev);
+ unsigned int brightness = bl_dev->props.brightness;
+ int ret;
+
+ if (brightness < S6E3HA2_MIN_BRIGHTNESS ||
+ brightness > bl_dev->props.max_brightness) {
+ dev_err(ctx->dev, "Invalid brightness: %u\n", brightness);
+ return -EINVAL;
+ }
+
+ if (bl_dev->props.power > FB_BLANK_NORMAL)
+ return -EPERM;
+
+ s6e3ha2_call_write_func(ret, s6e3ha2_test_key_on_f0(ctx));
+ s6e3ha2_call_write_func(ret, s6e3ha2_update_gamma(ctx, brightness));
+ s6e3ha2_call_write_func(ret, s6e3ha2_aor_control(ctx));
+ s6e3ha2_call_write_func(ret, s6e3ha2_set_vint(ctx));
+ s6e3ha2_call_write_func(ret, s6e3ha2_test_key_off_f0(ctx));
+
+ return 0;
+}
+
+static const struct backlight_ops s6e3ha2_bl_ops = {
+ .get_brightness = s6e3ha2_get_brightness,
+ .update_status = s6e3ha2_set_brightness,
+};
+
+static int s6e3ha2_panel_init(struct s6e3ha2 *ctx)
+{
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+ int ret;
+
+ s6e3ha2_call_write_func(ret, mipi_dsi_dcs_exit_sleep_mode(dsi));
+ usleep_range(5000, 6000);
+
+ s6e3ha2_call_write_func(ret, s6e3ha2_test_key_on_f0(ctx));
+ s6e3ha2_call_write_func(ret, s6e3ha2_single_dsi_set(ctx));
+ s6e3ha2_call_write_func(ret, s6e3ha2_test_key_on_fc(ctx));
+ s6e3ha2_call_write_func(ret, s6e3ha2_freq_calibration(ctx));
+ s6e3ha2_call_write_func(ret, s6e3ha2_test_key_off_fc(ctx));
+ s6e3ha2_call_write_func(ret, s6e3ha2_test_key_off_f0(ctx));
+
+ return 0;
+}
+
+static int s6e3ha2_power_off(struct s6e3ha2 *ctx)
+{
+ return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
+}
+
+static int s6e3ha2_disable(struct drm_panel *panel)
+{
+ struct s6e3ha2 *ctx = container_of(panel, struct s6e3ha2, panel);
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+ int ret;
+
+ s6e3ha2_call_write_func(ret, mipi_dsi_dcs_enter_sleep_mode(dsi));
+ s6e3ha2_call_write_func(ret, mipi_dsi_dcs_set_display_off(dsi));
+
+ msleep(40);
+ ctx->bl_dev->props.power = FB_BLANK_NORMAL;
+
+ return 0;
+}
+
+static int s6e3ha2_unprepare(struct drm_panel *panel)
+{
+ struct s6e3ha2 *ctx = container_of(panel, struct s6e3ha2, panel);
+
+ return s6e3ha2_power_off(ctx);
+}
+
+static int s6e3ha2_power_on(struct s6e3ha2 *ctx)
+{
+ int ret;
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
+ if (ret < 0)
+ return ret;
+
+ msleep(120);
+
+ gpiod_set_value(ctx->enable_gpio, 0);
+ usleep_range(5000, 6000);
+ gpiod_set_value(ctx->enable_gpio, 1);
+
+ gpiod_set_value(ctx->reset_gpio, 1);
+ usleep_range(5000, 6000);
+ gpiod_set_value(ctx->reset_gpio, 0);
+ usleep_range(5000, 6000);
+
+ return 0;
+}
+static int s6e3ha2_prepare(struct drm_panel *panel)
+{
+ struct s6e3ha2 *ctx = container_of(panel, struct s6e3ha2, panel);
+ int ret;
+
+ ret = s6e3ha2_power_on(ctx);
+ if (ret < 0)
+ return ret;
+
+ ret = s6e3ha2_panel_init(ctx);
+ if (ret < 0)
+ goto err;
+
+ ctx->bl_dev->props.power = FB_BLANK_NORMAL;
+
+ return 0;
+
+err:
+ s6e3ha2_power_off(ctx);
+ return ret;
+}
+
+static int s6e3ha2_enable(struct drm_panel *panel)
+{
+ struct s6e3ha2 *ctx = container_of(panel, struct s6e3ha2, panel);
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+ int ret;
+
+ /* common setting */
+ s6e3ha2_call_write_func(ret,
+ mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK));
+
+ s6e3ha2_call_write_func(ret, s6e3ha2_test_key_on_f0(ctx));
+ s6e3ha2_call_write_func(ret, s6e3ha2_test_key_on_fc(ctx));
+ s6e3ha2_call_write_func(ret, s6e3ha2_touch_hsync_on1(ctx));
+ s6e3ha2_call_write_func(ret, s6e3ha2_pentile_control(ctx));
+ s6e3ha2_call_write_func(ret, s6e3ha2_poc_global(ctx));
+ s6e3ha2_call_write_func(ret, s6e3ha2_poc_setting(ctx));
+ s6e3ha2_call_write_func(ret, s6e3ha2_test_key_off_fc(ctx));
+
+ /* pcd setting off for TB */
+ s6e3ha2_call_write_func(ret, s6e3ha2_pcd_set_off(ctx));
+ s6e3ha2_call_write_func(ret, s6e3ha2_err_fg_set(ctx));
+ s6e3ha2_call_write_func(ret, s6e3ha2_te_start_setting(ctx));
+
+ /* brightness setting */
+ s6e3ha2_call_write_func(ret, s6e3ha2_set_brightness(ctx->bl_dev));
+ s6e3ha2_call_write_func(ret, s6e3ha2_aor_control(ctx));
+ s6e3ha2_call_write_func(ret, s6e3ha2_caps_elvss_set(ctx));
+ s6e3ha2_call_write_func(ret, s6e3ha2_gamma_update(ctx));
+ s6e3ha2_call_write_func(ret, s6e3ha2_acl_off(ctx));
+ s6e3ha2_call_write_func(ret, s6e3ha2_acl_off_opr(ctx));
+ s6e3ha2_call_write_func(ret, s6e3ha2_hbm_off(ctx));
+
+ /* elvss temp compensation */
+ s6e3ha2_call_write_func(ret, s6e3ha2_test_global(ctx));
+ s6e3ha2_call_write_func(ret, s6e3ha2_test(ctx));
+ 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;
+
+ return 0;
+}
+
+static const struct drm_display_mode s6e3ha2_mode = {
+ .clock = 222372,
+ .hdisplay = 1440,
+ .hsync_start = 1440 + 1,
+ .hsync_end = 1440 + 1 + 1,
+ .htotal = 1440 + 1 + 1 + 1,
+ .vdisplay = 2560,
+ .vsync_start = 2560 + 1,
+ .vsync_end = 2560 + 1 + 1,
+ .vtotal = 2560 + 1 + 1 + 15,
+ .flags = 0,
+};
+
+static const struct s6e3ha2_panel_desc samsung_s6e3ha2 = {
+ .mode = &s6e3ha2_mode,
+ .type = HA2_TYPE,
+};
+
+static const struct drm_display_mode s6e3hf2_mode = {
+ .clock = 247856,
+ .hdisplay = 1600,
+ .hsync_start = 1600 + 1,
+ .hsync_end = 1600 + 1 + 1,
+ .htotal = 1600 + 1 + 1 + 1,
+ .vdisplay = 2560,
+ .vsync_start = 2560 + 1,
+ .vsync_end = 2560 + 1 + 1,
+ .vtotal = 2560 + 1 + 1 + 15,
+ .flags = 0,
+};
+
+static const struct s6e3ha2_panel_desc samsung_s6e3hf2 = {
+ .mode = &s6e3hf2_mode,
+ .type = HF2_TYPE,
+};
+
+static int s6e3ha2_get_modes(struct drm_panel *panel,
+ struct drm_connector *connector)
+{
+ struct s6e3ha2 *ctx = container_of(panel, struct s6e3ha2, panel);
+ struct drm_display_mode *mode;
+
+ mode = drm_mode_duplicate(connector->dev, ctx->desc->mode);
+ if (!mode) {
+ dev_err(panel->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;
+ drm_mode_probed_add(connector, mode);
+
+ connector->display_info.width_mm = 71;
+ connector->display_info.height_mm = 125;
+
+ return 1;
+}
+
+static const struct drm_panel_funcs s6e3ha2_drm_funcs = {
+ .disable = s6e3ha2_disable,
+ .unprepare = s6e3ha2_unprepare,
+ .prepare = s6e3ha2_prepare,
+ .enable = s6e3ha2_enable,
+ .get_modes = s6e3ha2_get_modes,
+};
+
+static int s6e3ha2_probe(struct mipi_dsi_device *dsi)
+{
+ struct device *dev = &dsi->dev;
+ struct s6e3ha2 *ctx;
+ int ret;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ mipi_dsi_set_drvdata(dsi, ctx);
+
+ ctx->dev = dev;
+ ctx->desc = of_device_get_match_data(dev);
+
+ 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;
+
+ 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)) {
+ dev_err(dev, "cannot get reset-gpios %ld\n",
+ PTR_ERR(ctx->reset_gpio));
+ return PTR_ERR(ctx->reset_gpio);
+ }
+
+ ctx->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_HIGH);
+ if (IS_ERR(ctx->enable_gpio)) {
+ dev_err(dev, "cannot get enable-gpios %ld\n",
+ PTR_ERR(ctx->enable_gpio));
+ return PTR_ERR(ctx->enable_gpio);
+ }
+
+ ctx->bl_dev = backlight_device_register("s6e3ha2", dev, ctx,
+ &s6e3ha2_bl_ops, NULL);
+ if (IS_ERR(ctx->bl_dev)) {
+ dev_err(dev, "failed to register backlight device\n");
+ return PTR_ERR(ctx->bl_dev);
+ }
+
+ 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;
+
+ drm_panel_init(&ctx->panel, dev, &s6e3ha2_drm_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ drm_panel_add(&ctx->panel);
+
+ ret = mipi_dsi_attach(dsi);
+ if (ret < 0)
+ goto remove_panel;
+
+ return ret;
+
+remove_panel:
+ drm_panel_remove(&ctx->panel);
+ backlight_device_unregister(ctx->bl_dev);
+
+ return ret;
+}
+
+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);
+}
+
+static const struct of_device_id s6e3ha2_of_match[] = {
+ { .compatible = "samsung,s6e3ha2", .data = &samsung_s6e3ha2 },
+ { .compatible = "samsung,s6e3hf2", .data = &samsung_s6e3hf2 },
+ { }
+};
+MODULE_DEVICE_TABLE(of, s6e3ha2_of_match);
+
+static struct mipi_dsi_driver s6e3ha2_driver = {
+ .probe = s6e3ha2_probe,
+ .remove = s6e3ha2_remove,
+ .driver = {
+ .name = "panel-samsung-s6e3ha2",
+ .of_match_table = s6e3ha2_of_match,
+ },
+};
+module_mipi_dsi_driver(s6e3ha2_driver);
+
+MODULE_AUTHOR("Donghwa Lee <dh09.lee@samsung.com>");
+MODULE_AUTHOR("Hyungwon Hwang <human.hwang@samsung.com>");
+MODULE_AUTHOR("Hoegeun Kwon <hoegeun.kwon@samsung.com>");
+MODULE_DESCRIPTION("MIPI-DSI based s6e3ha2 AMOLED Panel Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/panel/panel-samsung-s6e63j0x03.c b/drivers/gpu/drm/panel/panel-samsung-s6e63j0x03.c
new file mode 100644
index 000000000..9c3e76171
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-samsung-s6e63j0x03.c
@@ -0,0 +1,521 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MIPI-DSI based S6E63J0X03 AMOLED lcd 1.63 inch panel driver.
+ *
+ * Copyright (c) 2014-2017 Samsung Electronics Co., Ltd
+ *
+ * Inki Dae <inki.dae@samsung.com>
+ * Hoegeun Kwon <hoegeun.kwon@samsung.com>
+ */
+
+#include <linux/backlight.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.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
+
+#define MAX_BRIGHTNESS 100
+#define DEFAULT_BRIGHTNESS 80
+
+#define NUM_GAMMA_STEPS 9
+#define GAMMA_CMD_CNT 28
+
+#define FIRST_COLUMN 20
+
+struct s6e63j0x03 {
+ struct device *dev;
+ struct drm_panel panel;
+ struct backlight_device *bl_dev;
+
+ struct regulator_bulk_data supplies[2];
+ struct gpio_desc *reset_gpio;
+};
+
+static const struct drm_display_mode default_mode = {
+ .clock = 4649,
+ .hdisplay = 320,
+ .hsync_start = 320 + 1,
+ .hsync_end = 320 + 1 + 1,
+ .htotal = 320 + 1 + 1 + 1,
+ .vdisplay = 320,
+ .vsync_start = 320 + 150,
+ .vsync_end = 320 + 150 + 1,
+ .vtotal = 320 + 150 + 1 + 2,
+ .flags = 0,
+};
+
+static const unsigned char gamma_tbl[NUM_GAMMA_STEPS][GAMMA_CMD_CNT] = {
+ { /* Gamma 10 */
+ MCS_MTP_SET3,
+ 0x00, 0x00, 0x00, 0x7f, 0x7f, 0x7f, 0x52, 0x6b, 0x6f, 0x26,
+ 0x28, 0x2d, 0x28, 0x26, 0x27, 0x33, 0x34, 0x32, 0x36, 0x36,
+ 0x35, 0x00, 0xab, 0x00, 0xae, 0x00, 0xbf
+ },
+ { /* gamma 30 */
+ MCS_MTP_SET3,
+ 0x00, 0x00, 0x00, 0x70, 0x7f, 0x7f, 0x4e, 0x64, 0x69, 0x26,
+ 0x27, 0x2a, 0x28, 0x29, 0x27, 0x31, 0x32, 0x31, 0x35, 0x34,
+ 0x35, 0x00, 0xc4, 0x00, 0xca, 0x00, 0xdc
+ },
+ { /* gamma 60 */
+ MCS_MTP_SET3,
+ 0x00, 0x00, 0x00, 0x65, 0x7b, 0x7d, 0x5f, 0x67, 0x68, 0x2a,
+ 0x28, 0x29, 0x28, 0x2a, 0x27, 0x31, 0x2f, 0x30, 0x34, 0x33,
+ 0x34, 0x00, 0xd9, 0x00, 0xe4, 0x00, 0xf5
+ },
+ { /* gamma 90 */
+ MCS_MTP_SET3,
+ 0x00, 0x00, 0x00, 0x4d, 0x6f, 0x71, 0x67, 0x6a, 0x6c, 0x29,
+ 0x28, 0x28, 0x28, 0x29, 0x27, 0x30, 0x2e, 0x30, 0x32, 0x31,
+ 0x31, 0x00, 0xea, 0x00, 0xf6, 0x01, 0x09
+ },
+ { /* gamma 120 */
+ MCS_MTP_SET3,
+ 0x00, 0x00, 0x00, 0x3d, 0x66, 0x68, 0x69, 0x69, 0x69, 0x28,
+ 0x28, 0x27, 0x28, 0x28, 0x27, 0x30, 0x2e, 0x2f, 0x31, 0x31,
+ 0x30, 0x00, 0xf9, 0x01, 0x05, 0x01, 0x1b
+ },
+ { /* gamma 150 */
+ MCS_MTP_SET3,
+ 0x00, 0x00, 0x00, 0x31, 0x51, 0x53, 0x66, 0x66, 0x67, 0x28,
+ 0x29, 0x27, 0x28, 0x27, 0x27, 0x2e, 0x2d, 0x2e, 0x31, 0x31,
+ 0x30, 0x01, 0x04, 0x01, 0x11, 0x01, 0x29
+ },
+ { /* gamma 200 */
+ MCS_MTP_SET3,
+ 0x00, 0x00, 0x00, 0x2f, 0x4f, 0x51, 0x67, 0x65, 0x65, 0x29,
+ 0x2a, 0x28, 0x27, 0x25, 0x26, 0x2d, 0x2c, 0x2c, 0x30, 0x30,
+ 0x30, 0x01, 0x14, 0x01, 0x23, 0x01, 0x3b
+ },
+ { /* gamma 240 */
+ MCS_MTP_SET3,
+ 0x00, 0x00, 0x00, 0x2c, 0x4d, 0x50, 0x65, 0x63, 0x64, 0x2a,
+ 0x2c, 0x29, 0x26, 0x24, 0x25, 0x2c, 0x2b, 0x2b, 0x30, 0x30,
+ 0x30, 0x01, 0x1e, 0x01, 0x2f, 0x01, 0x47
+ },
+ { /* gamma 300 */
+ MCS_MTP_SET3,
+ 0x00, 0x00, 0x00, 0x38, 0x61, 0x64, 0x65, 0x63, 0x64, 0x28,
+ 0x2a, 0x27, 0x26, 0x23, 0x25, 0x2b, 0x2b, 0x2a, 0x30, 0x2f,
+ 0x30, 0x01, 0x2d, 0x01, 0x3f, 0x01, 0x57
+ }
+};
+
+static inline struct s6e63j0x03 *panel_to_s6e63j0x03(struct drm_panel *panel)
+{
+ return container_of(panel, struct s6e63j0x03, panel);
+}
+
+static inline ssize_t s6e63j0x03_dcs_write_seq(struct s6e63j0x03 *ctx,
+ const void *seq, size_t len)
+{
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+
+ return mipi_dsi_dcs_write_buffer(dsi, seq, len);
+}
+
+#define s6e63j0x03_dcs_write_seq_static(ctx, seq...) \
+ ({ \
+ static const u8 d[] = { seq }; \
+ s6e63j0x03_dcs_write_seq(ctx, d, ARRAY_SIZE(d)); \
+ })
+
+static inline int s6e63j0x03_enable_lv2_command(struct s6e63j0x03 *ctx)
+{
+ return s6e63j0x03_dcs_write_seq_static(ctx, MCS_LEVEL2_KEY, 0x5a, 0x5a);
+}
+
+static inline int s6e63j0x03_apply_mtp_key(struct s6e63j0x03 *ctx, bool on)
+{
+ if (on)
+ return s6e63j0x03_dcs_write_seq_static(ctx,
+ MCS_MTP_KEY, 0x5a, 0x5a);
+
+ return s6e63j0x03_dcs_write_seq_static(ctx, MCS_MTP_KEY, 0xa5, 0xa5);
+}
+
+static int s6e63j0x03_power_on(struct s6e63j0x03 *ctx)
+{
+ int ret;
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
+ if (ret < 0)
+ return ret;
+
+ msleep(30);
+
+ gpiod_set_value(ctx->reset_gpio, 1);
+ usleep_range(1000, 2000);
+ gpiod_set_value(ctx->reset_gpio, 0);
+ usleep_range(5000, 6000);
+
+ return 0;
+}
+
+static int s6e63j0x03_power_off(struct s6e63j0x03 *ctx)
+{
+ return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
+}
+
+static unsigned int s6e63j0x03_get_brightness_index(unsigned int brightness)
+{
+ unsigned int index;
+
+ index = brightness / (MAX_BRIGHTNESS / NUM_GAMMA_STEPS);
+
+ if (index >= NUM_GAMMA_STEPS)
+ index = NUM_GAMMA_STEPS - 1;
+
+ return index;
+}
+
+static int s6e63j0x03_update_gamma(struct s6e63j0x03 *ctx,
+ unsigned int brightness)
+{
+ struct backlight_device *bl_dev = ctx->bl_dev;
+ unsigned int index = s6e63j0x03_get_brightness_index(brightness);
+ int ret;
+
+ ret = s6e63j0x03_apply_mtp_key(ctx, true);
+ if (ret < 0)
+ return ret;
+
+ ret = s6e63j0x03_dcs_write_seq(ctx, gamma_tbl[index], GAMMA_CMD_CNT);
+ if (ret < 0)
+ return ret;
+
+ ret = s6e63j0x03_apply_mtp_key(ctx, false);
+ if (ret < 0)
+ return ret;
+
+ bl_dev->props.brightness = brightness;
+
+ return 0;
+}
+
+static int s6e63j0x03_set_brightness(struct backlight_device *bl_dev)
+{
+ struct s6e63j0x03 *ctx = bl_get_data(bl_dev);
+ unsigned int brightness = bl_dev->props.brightness;
+
+ return s6e63j0x03_update_gamma(ctx, brightness);
+}
+
+static const struct backlight_ops s6e63j0x03_bl_ops = {
+ .update_status = s6e63j0x03_set_brightness,
+};
+
+static int s6e63j0x03_disable(struct drm_panel *panel)
+{
+ struct s6e63j0x03 *ctx = panel_to_s6e63j0x03(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)
+ return ret;
+
+ ctx->bl_dev->props.power = FB_BLANK_NORMAL;
+
+ ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
+ if (ret < 0)
+ return ret;
+
+ msleep(120);
+
+ return 0;
+}
+
+static int s6e63j0x03_unprepare(struct drm_panel *panel)
+{
+ struct s6e63j0x03 *ctx = panel_to_s6e63j0x03(panel);
+ int ret;
+
+ ret = s6e63j0x03_power_off(ctx);
+ if (ret < 0)
+ return ret;
+
+ ctx->bl_dev->props.power = FB_BLANK_POWERDOWN;
+
+ return 0;
+}
+
+static int s6e63j0x03_panel_init(struct s6e63j0x03 *ctx)
+{
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+ int ret;
+
+ ret = s6e63j0x03_enable_lv2_command(ctx);
+ if (ret < 0)
+ return ret;
+
+ ret = s6e63j0x03_apply_mtp_key(ctx, true);
+ if (ret < 0)
+ return ret;
+
+ /* set porch adjustment */
+ ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xf2, 0x1c, 0x28);
+ if (ret < 0)
+ return ret;
+
+ /* set frame freq */
+ ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xb5, 0x00, 0x02, 0x00);
+ if (ret < 0)
+ return ret;
+
+ /* set caset, paset */
+ ret = mipi_dsi_dcs_set_column_address(dsi, FIRST_COLUMN,
+ default_mode.hdisplay - 1 + FIRST_COLUMN);
+ if (ret < 0)
+ return ret;
+
+ ret = mipi_dsi_dcs_set_page_address(dsi, 0, default_mode.vdisplay - 1);
+ if (ret < 0)
+ return ret;
+
+ /* set ltps timming 0, 1 */
+ ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xf8, 0x08, 0x08, 0x08, 0x17,
+ 0x00, 0x2a, 0x02, 0x26, 0x00, 0x00, 0x02, 0x00, 0x00);
+ if (ret < 0)
+ return ret;
+
+ ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xf7, 0x02);
+ if (ret < 0)
+ return ret;
+
+ /* set param pos te_edge */
+ ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xb0, 0x01);
+ if (ret < 0)
+ return ret;
+
+ /* set te rising edge */
+ ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xe2, 0x0f);
+ if (ret < 0)
+ return ret;
+
+ /* set param pos default */
+ ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xb0, 0x00);
+ if (ret < 0)
+ return ret;
+
+ ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
+ if (ret < 0)
+ return ret;
+
+ ret = s6e63j0x03_apply_mtp_key(ctx, false);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int s6e63j0x03_prepare(struct drm_panel *panel)
+{
+ struct s6e63j0x03 *ctx = panel_to_s6e63j0x03(panel);
+ int ret;
+
+ ret = s6e63j0x03_power_on(ctx);
+ if (ret < 0)
+ return ret;
+
+ ret = s6e63j0x03_panel_init(ctx);
+ if (ret < 0)
+ goto err;
+
+ ctx->bl_dev->props.power = FB_BLANK_NORMAL;
+
+ return 0;
+
+err:
+ s6e63j0x03_power_off(ctx);
+ return ret;
+}
+
+static int s6e63j0x03_enable(struct drm_panel *panel)
+{
+ struct s6e63j0x03 *ctx = panel_to_s6e63j0x03(panel);
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+ int ret;
+
+ msleep(120);
+
+ ret = s6e63j0x03_apply_mtp_key(ctx, true);
+ if (ret < 0)
+ return ret;
+
+ /* set elvss_cond */
+ ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xb1, 0x00, 0x09);
+ if (ret < 0)
+ return ret;
+
+ /* set pos */
+ ret = s6e63j0x03_dcs_write_seq_static(ctx,
+ MIPI_DCS_SET_ADDRESS_MODE, 0x40);
+ if (ret < 0)
+ return ret;
+
+ /* set default white brightness */
+ ret = mipi_dsi_dcs_set_display_brightness(dsi, 0x00ff);
+ if (ret < 0)
+ return ret;
+
+ /* set white ctrl */
+ ret = s6e63j0x03_dcs_write_seq_static(ctx,
+ MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x20);
+ if (ret < 0)
+ return ret;
+
+ /* set acl off */
+ ret = s6e63j0x03_dcs_write_seq_static(ctx,
+ MIPI_DCS_WRITE_POWER_SAVE, 0x00);
+ if (ret < 0)
+ return ret;
+
+ ret = mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
+ if (ret < 0)
+ return ret;
+
+ ret = s6e63j0x03_apply_mtp_key(ctx, false);
+ if (ret < 0)
+ return ret;
+
+ ret = mipi_dsi_dcs_set_display_on(dsi);
+ if (ret < 0)
+ return ret;
+
+ ctx->bl_dev->props.power = FB_BLANK_UNBLANK;
+
+ return 0;
+}
+
+static int s6e63j0x03_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 = 29;
+ connector->display_info.height_mm = 29;
+
+ return 1;
+}
+
+static const struct drm_panel_funcs s6e63j0x03_funcs = {
+ .disable = s6e63j0x03_disable,
+ .unprepare = s6e63j0x03_unprepare,
+ .prepare = s6e63j0x03_prepare,
+ .enable = s6e63j0x03_enable,
+ .get_modes = s6e63j0x03_get_modes,
+};
+
+static int s6e63j0x03_probe(struct mipi_dsi_device *dsi)
+{
+ struct device *dev = &dsi->dev;
+ struct s6e63j0x03 *ctx;
+ int ret;
+
+ ctx = devm_kzalloc(dev, sizeof(struct s6e63j0x03), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ 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_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)
+ 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))
+ return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio),
+ "cannot get reset-gpio\n");
+
+ drm_panel_init(&ctx->panel, dev, &s6e63j0x03_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ ctx->bl_dev = backlight_device_register("s6e63j0x03", dev, ctx,
+ &s6e63j0x03_bl_ops, NULL);
+ 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;
+
+ drm_panel_add(&ctx->panel);
+
+ ret = mipi_dsi_attach(dsi);
+ if (ret < 0)
+ goto remove_panel;
+
+ return ret;
+
+remove_panel:
+ drm_panel_remove(&ctx->panel);
+ backlight_device_unregister(ctx->bl_dev);
+
+ return ret;
+}
+
+static void s6e63j0x03_remove(struct mipi_dsi_device *dsi)
+{
+ struct s6e63j0x03 *ctx = mipi_dsi_get_drvdata(dsi);
+
+ mipi_dsi_detach(dsi);
+ drm_panel_remove(&ctx->panel);
+
+ backlight_device_unregister(ctx->bl_dev);
+}
+
+static const struct of_device_id s6e63j0x03_of_match[] = {
+ { .compatible = "samsung,s6e63j0x03" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, s6e63j0x03_of_match);
+
+static struct mipi_dsi_driver s6e63j0x03_driver = {
+ .probe = s6e63j0x03_probe,
+ .remove = s6e63j0x03_remove,
+ .driver = {
+ .name = "panel_samsung_s6e63j0x03",
+ .of_match_table = s6e63j0x03_of_match,
+ },
+};
+module_mipi_dsi_driver(s6e63j0x03_driver);
+
+MODULE_AUTHOR("Inki Dae <inki.dae@samsung.com>");
+MODULE_AUTHOR("Hoegeun Kwon <hoegeun.kwon@samsung.com>");
+MODULE_DESCRIPTION("MIPI-DSI based s6e63j0x03 AMOLED LCD Panel Driver");
+MODULE_LICENSE("GPL v2");
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 000000000..ed3895e4c
--- /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/of_device.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 000000000..d99afcc67
--- /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 000000000..b34fa4d5d
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-samsung-s6e63m0.c
@@ -0,0 +1,762 @@
+// 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/gpio/consumer.h>
+#include <linux/module.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;
+
+ bool prepared;
+ bool enabled;
+
+ /*
+ * 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);
+
+ if (!ctx->enabled)
+ return 0;
+
+ 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);
+
+ ctx->enabled = false;
+
+ return 0;
+}
+
+static int s6e63m0_unprepare(struct drm_panel *panel)
+{
+ struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
+ int ret;
+
+ if (!ctx->prepared)
+ return 0;
+
+ s6e63m0_clear_error(ctx);
+
+ ret = s6e63m0_power_off(ctx);
+ if (ret < 0)
+ return ret;
+
+ ctx->prepared = false;
+
+ return 0;
+}
+
+static int s6e63m0_prepare(struct drm_panel *panel)
+{
+ struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
+ int ret;
+
+ if (ctx->prepared)
+ return 0;
+
+ 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);
+
+ ctx->prepared = true;
+
+ return ret;
+}
+
+static int s6e63m0_enable(struct drm_panel *panel)
+{
+ struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
+
+ if (ctx->enabled)
+ return 0;
+
+ 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);
+
+ ctx->enabled = true;
+
+ 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;
+ ctx->enabled = false;
+ ctx->prepared = false;
+
+ 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 000000000..c926eca1c
--- /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-ams452ef01.c b/drivers/gpu/drm/panel/panel-samsung-s6e88a0-ams452ef01.c
new file mode 100644
index 000000000..97ff7a185
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-samsung-s6e88a0-ams452ef01.c
@@ -0,0 +1,287 @@
+// 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;
+
+ bool prepared;
+};
+
+static inline struct
+s6e88a0_ams452ef01 *to_s6e88a0_ams452ef01(struct drm_panel *panel)
+{
+ return container_of(panel, struct s6e88a0_ams452ef01, panel);
+}
+
+#define dsi_dcs_write_seq(dsi, seq...) do { \
+ static const u8 d[] = { seq }; \
+ int ret; \
+ ret = mipi_dsi_dcs_write_buffer(dsi, d, ARRAY_SIZE(d)); \
+ if (ret < 0) \
+ return ret; \
+ } while (0)
+
+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 device *dev = &dsi->dev;
+ int ret;
+
+ dsi->mode_flags |= MIPI_DSI_MODE_LPM;
+
+ dsi_dcs_write_seq(dsi, 0xf0, 0x5a, 0x5a); // enable LEVEL2 commands
+ dsi_dcs_write_seq(dsi, 0xcc, 0x4c); // set Pixel Clock Divider polarity
+
+ ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to exit sleep mode: %d\n", ret);
+ return ret;
+ }
+ msleep(120);
+
+ // set default brightness/gama
+ dsi_dcs_write_seq(dsi, 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
+ dsi_dcs_write_seq(dsi, 0xb2, 0x40, 0x0a, 0x17, 0x00, 0x0a);
+ dsi_dcs_write_seq(dsi, 0xb6, 0x2c, 0x0b); // set default elvss voltage
+ dsi_dcs_write_seq(dsi, MIPI_DCS_WRITE_POWER_SAVE, 0x00);
+ dsi_dcs_write_seq(dsi, 0xf7, 0x03); // gamma/aor update
+ dsi_dcs_write_seq(dsi, 0xf0, 0xa5, 0xa5); // disable LEVEL2 commands
+
+ ret = mipi_dsi_dcs_set_display_on(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set display on: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int s6e88a0_ams452ef01_off(struct s6e88a0_ams452ef01 *ctx)
+{
+ struct mipi_dsi_device *dsi = ctx->dsi;
+ struct device *dev = &dsi->dev;
+ int ret;
+
+ dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
+
+ ret = mipi_dsi_dcs_set_display_off(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set display off: %d\n", ret);
+ return ret;
+ }
+ msleep(35);
+
+ ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to enter sleep mode: %d\n", ret);
+ return ret;
+ }
+ msleep(120);
+
+ return 0;
+}
+
+static int s6e88a0_ams452ef01_prepare(struct drm_panel *panel)
+{
+ struct s6e88a0_ams452ef01 *ctx = to_s6e88a0_ams452ef01(panel);
+ struct device *dev = &ctx->dsi->dev;
+ int ret;
+
+ if (ctx->prepared)
+ return 0;
+
+ 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;
+ }
+
+ s6e88a0_ams452ef01_reset(ctx);
+
+ ret = s6e88a0_ams452ef01_on(ctx);
+ if (ret < 0) {
+ dev_err(dev, "Failed to initialize panel: %d\n", ret);
+ gpiod_set_value_cansleep(ctx->reset_gpio, 0);
+ regulator_bulk_disable(ARRAY_SIZE(ctx->supplies),
+ ctx->supplies);
+ return ret;
+ }
+
+ ctx->prepared = true;
+ return 0;
+}
+
+static int s6e88a0_ams452ef01_unprepare(struct drm_panel *panel)
+{
+ struct s6e88a0_ams452ef01 *ctx = to_s6e88a0_ams452ef01(panel);
+ struct device *dev = &ctx->dsi->dev;
+ int ret;
+
+ if (!ctx->prepared)
+ return 0;
+
+ ret = s6e88a0_ams452ef01_off(ctx);
+ if (ret < 0)
+ dev_err(dev, "Failed to un-initialize panel: %d\n", ret);
+
+ gpiod_set_value_cansleep(ctx->reset_gpio, 0);
+ regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
+
+ ctx->prepared = false;
+ 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_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ 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_init(&ctx->panel, dev, &s6e88a0_ams452ef01_panel_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ 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
new file mode 100644
index 000000000..ebf4c2d39
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-samsung-s6e8aa0.c
@@ -0,0 +1,1060 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MIPI-DSI based s6e8aa0 AMOLED LCD 5.3 inch panel driver.
+ *
+ * Copyright (c) 2013 Samsung Electronics Co., Ltd
+ *
+ * Inki Dae, <inki.dae@samsung.com>
+ * Donghwa Lee, <dh09.lee@samsung.com>
+ * Joongmock Shin <jmock.shin@samsung.com>
+ * Eunchul Kim <chulspro.kim@samsung.com>
+ * Tomasz Figa <t.figa@samsung.com>
+ * Andrzej Hajda <a.hajda@samsung.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 <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
+
+#define PANELCTL_SS_MASK (1 << 5)
+#define PANELCTL_SS_1_800 (0 << 5)
+#define PANELCTL_SS_800_1 (1 << 5)
+#define PANELCTL_GTCON_MASK (7 << 2)
+#define PANELCTL_GTCON_110 (6 << 2)
+#define PANELCTL_GTCON_111 (7 << 2)
+
+#define PANELCTL_CLK1_CON_MASK (7 << 3)
+#define PANELCTL_CLK1_000 (0 << 3)
+#define PANELCTL_CLK1_001 (1 << 3)
+#define PANELCTL_CLK2_CON_MASK (7 << 0)
+#define PANELCTL_CLK2_000 (0 << 0)
+#define PANELCTL_CLK2_001 (1 << 0)
+
+#define PANELCTL_INT1_CON_MASK (7 << 3)
+#define PANELCTL_INT1_000 (0 << 3)
+#define PANELCTL_INT1_001 (1 << 3)
+#define PANELCTL_INT2_CON_MASK (7 << 0)
+#define PANELCTL_INT2_000 (0 << 0)
+#define PANELCTL_INT2_001 (1 << 0)
+
+#define PANELCTL_BICTL_CON_MASK (7 << 3)
+#define PANELCTL_BICTL_000 (0 << 3)
+#define PANELCTL_BICTL_001 (1 << 3)
+#define PANELCTL_BICTLB_CON_MASK (7 << 0)
+#define PANELCTL_BICTLB_000 (0 << 0)
+#define PANELCTL_BICTLB_001 (1 << 0)
+
+#define PANELCTL_EM_CLK1_CON_MASK (7 << 3)
+#define PANELCTL_EM_CLK1_110 (6 << 3)
+#define PANELCTL_EM_CLK1_111 (7 << 3)
+#define PANELCTL_EM_CLK1B_CON_MASK (7 << 0)
+#define PANELCTL_EM_CLK1B_110 (6 << 0)
+#define PANELCTL_EM_CLK1B_111 (7 << 0)
+
+#define PANELCTL_EM_CLK2_CON_MASK (7 << 3)
+#define PANELCTL_EM_CLK2_110 (6 << 3)
+#define PANELCTL_EM_CLK2_111 (7 << 3)
+#define PANELCTL_EM_CLK2B_CON_MASK (7 << 0)
+#define PANELCTL_EM_CLK2B_110 (6 << 0)
+#define PANELCTL_EM_CLK2B_111 (7 << 0)
+
+#define PANELCTL_EM_INT1_CON_MASK (7 << 3)
+#define PANELCTL_EM_INT1_000 (0 << 3)
+#define PANELCTL_EM_INT1_001 (1 << 3)
+#define PANELCTL_EM_INT2_CON_MASK (7 << 0)
+#define PANELCTL_EM_INT2_000 (0 << 0)
+#define PANELCTL_EM_INT2_001 (1 << 0)
+
+#define AID_DISABLE (0x4)
+#define AID_1 (0x5)
+#define AID_2 (0x6)
+#define AID_3 (0x7)
+
+typedef u8 s6e8aa0_gamma_table[GAMMA_TABLE_LEN];
+
+struct s6e8aa0_variant {
+ u8 version;
+ const s6e8aa0_gamma_table *gamma_tables;
+};
+
+struct s6e8aa0 {
+ struct device *dev;
+ struct drm_panel panel;
+
+ struct regulator_bulk_data supplies[2];
+ struct gpio_desc *reset_gpio;
+ u32 power_on_delay;
+ u32 reset_delay;
+ u32 init_delay;
+ bool flip_horizontal;
+ bool flip_vertical;
+ struct videomode vm;
+ u32 width_mm;
+ u32 height_mm;
+
+ u8 version;
+ u8 id;
+ const struct s6e8aa0_variant *variant;
+ int brightness;
+
+ /* This field is tested by functions directly accessing DSI 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 inline struct s6e8aa0 *panel_to_s6e8aa0(struct drm_panel *panel)
+{
+ return container_of(panel, struct s6e8aa0, panel);
+}
+
+static int s6e8aa0_clear_error(struct s6e8aa0 *ctx)
+{
+ int ret = ctx->error;
+
+ ctx->error = 0;
+ return ret;
+}
+
+static void s6e8aa0_dcs_write(struct s6e8aa0 *ctx, const void *data, size_t len)
+{
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+ ssize_t ret;
+
+ if (ctx->error < 0)
+ return;
+
+ ret = mipi_dsi_dcs_write_buffer(dsi, data, len);
+ if (ret < 0) {
+ dev_err(ctx->dev, "error %zd writing dcs seq: %*ph\n", ret,
+ (int)len, data);
+ ctx->error = ret;
+ }
+}
+
+static int s6e8aa0_dcs_read(struct s6e8aa0 *ctx, u8 cmd, void *data, size_t len)
+{
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+ int ret;
+
+ if (ctx->error < 0)
+ return ctx->error;
+
+ ret = mipi_dsi_dcs_read(dsi, cmd, data, len);
+ if (ret < 0) {
+ dev_err(ctx->dev, "error %d reading dcs seq(%#x)\n", ret, cmd);
+ ctx->error = ret;
+ }
+
+ return ret;
+}
+
+#define s6e8aa0_dcs_write_seq(ctx, seq...) \
+({\
+ const u8 d[] = { seq };\
+ BUILD_BUG_ON_MSG(ARRAY_SIZE(d) > 64, "DCS sequence too big for stack");\
+ s6e8aa0_dcs_write(ctx, d, ARRAY_SIZE(d));\
+})
+
+#define s6e8aa0_dcs_write_seq_static(ctx, seq...) \
+({\
+ static const u8 d[] = { seq };\
+ s6e8aa0_dcs_write(ctx, d, ARRAY_SIZE(d));\
+})
+
+static void s6e8aa0_apply_level_1_key(struct s6e8aa0 *ctx)
+{
+ s6e8aa0_dcs_write_seq_static(ctx, 0xf0, 0x5a, 0x5a);
+}
+
+static void s6e8aa0_panel_cond_set_v142(struct s6e8aa0 *ctx)
+{
+ static const u8 aids[] = {
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x60, 0x80, 0xA0
+ };
+ u8 aid = aids[ctx->id >> 5];
+ u8 cfg = 0x3d;
+ u8 clk_con = 0xc8;
+ u8 int_con = 0x08;
+ u8 bictl_con = 0x48;
+ u8 em_clk1_con = 0xff;
+ u8 em_clk2_con = 0xff;
+ u8 em_int_con = 0xc8;
+
+ if (ctx->flip_vertical) {
+ /* GTCON */
+ cfg &= ~(PANELCTL_GTCON_MASK);
+ cfg |= (PANELCTL_GTCON_110);
+ }
+
+ if (ctx->flip_horizontal) {
+ /* SS */
+ cfg &= ~(PANELCTL_SS_MASK);
+ cfg |= (PANELCTL_SS_1_800);
+ }
+
+ if (ctx->flip_horizontal || ctx->flip_vertical) {
+ /* CLK1,2_CON */
+ clk_con &= ~(PANELCTL_CLK1_CON_MASK |
+ PANELCTL_CLK2_CON_MASK);
+ clk_con |= (PANELCTL_CLK1_000 | PANELCTL_CLK2_001);
+
+ /* INT1,2_CON */
+ int_con &= ~(PANELCTL_INT1_CON_MASK |
+ PANELCTL_INT2_CON_MASK);
+ int_con |= (PANELCTL_INT1_000 | PANELCTL_INT2_001);
+
+ /* BICTL,B_CON */
+ bictl_con &= ~(PANELCTL_BICTL_CON_MASK |
+ PANELCTL_BICTLB_CON_MASK);
+ bictl_con |= (PANELCTL_BICTL_000 |
+ PANELCTL_BICTLB_001);
+
+ /* EM_CLK1,1B_CON */
+ em_clk1_con &= ~(PANELCTL_EM_CLK1_CON_MASK |
+ PANELCTL_EM_CLK1B_CON_MASK);
+ em_clk1_con |= (PANELCTL_EM_CLK1_110 |
+ PANELCTL_EM_CLK1B_110);
+
+ /* EM_CLK2,2B_CON */
+ em_clk2_con &= ~(PANELCTL_EM_CLK2_CON_MASK |
+ PANELCTL_EM_CLK2B_CON_MASK);
+ em_clk2_con |= (PANELCTL_EM_CLK2_110 |
+ PANELCTL_EM_CLK2B_110);
+
+ /* EM_INT1,2_CON */
+ em_int_con &= ~(PANELCTL_EM_INT1_CON_MASK |
+ PANELCTL_EM_INT2_CON_MASK);
+ em_int_con |= (PANELCTL_EM_INT1_000 |
+ PANELCTL_EM_INT2_001);
+ }
+
+ s6e8aa0_dcs_write_seq(ctx,
+ 0xf8, cfg, 0x35, 0x00, 0x00, 0x00, 0x93, 0x00,
+ 0x3c, 0x78, 0x08, 0x27, 0x7d, 0x3f, 0x00, 0x00,
+ 0x00, 0x20, aid, 0x08, 0x6e, 0x00, 0x00, 0x00,
+ 0x02, 0x07, 0x07, 0x23, 0x23, 0xc0, clk_con, int_con,
+ bictl_con, 0xc1, 0x00, 0xc1, em_clk1_con, em_clk2_con,
+ em_int_con);
+}
+
+static void s6e8aa0_panel_cond_set(struct s6e8aa0 *ctx)
+{
+ if (ctx->version < 142)
+ s6e8aa0_dcs_write_seq_static(ctx,
+ 0xf8, 0x19, 0x35, 0x00, 0x00, 0x00, 0x94, 0x00,
+ 0x3c, 0x78, 0x10, 0x27, 0x08, 0x6e, 0x00, 0x00,
+ 0x00, 0x00, 0x04, 0x08, 0x6e, 0x00, 0x00, 0x00,
+ 0x00, 0x07, 0x07, 0x23, 0x6e, 0xc0, 0xc1, 0x01,
+ 0x81, 0xc1, 0x00, 0xc3, 0xf6, 0xf6, 0xc1
+ );
+ else
+ s6e8aa0_panel_cond_set_v142(ctx);
+}
+
+static void s6e8aa0_display_condition_set(struct s6e8aa0 *ctx)
+{
+ s6e8aa0_dcs_write_seq_static(ctx, 0xf2, 0x80, 0x03, 0x0d);
+}
+
+static void s6e8aa0_etc_source_control(struct s6e8aa0 *ctx)
+{
+ s6e8aa0_dcs_write_seq_static(ctx, 0xf6, 0x00, 0x02, 0x00);
+}
+
+static void s6e8aa0_etc_pentile_control(struct s6e8aa0 *ctx)
+{
+ static const u8 pent32[] = {
+ 0xb6, 0x0c, 0x02, 0x03, 0x32, 0xc0, 0x44, 0x44, 0xc0, 0x00
+ };
+
+ static const u8 pent142[] = {
+ 0xb6, 0x0c, 0x02, 0x03, 0x32, 0xff, 0x44, 0x44, 0xc0, 0x00
+ };
+
+ if (ctx->version < 142)
+ s6e8aa0_dcs_write(ctx, pent32, ARRAY_SIZE(pent32));
+ else
+ s6e8aa0_dcs_write(ctx, pent142, ARRAY_SIZE(pent142));
+}
+
+static void s6e8aa0_etc_power_control(struct s6e8aa0 *ctx)
+{
+ static const u8 pwr142[] = {
+ 0xf4, 0xcf, 0x0a, 0x12, 0x10, 0x1e, 0x33, 0x02
+ };
+
+ static const u8 pwr32[] = {
+ 0xf4, 0xcf, 0x0a, 0x15, 0x10, 0x19, 0x33, 0x02
+ };
+
+ if (ctx->version < 142)
+ s6e8aa0_dcs_write(ctx, pwr32, ARRAY_SIZE(pwr32));
+ else
+ s6e8aa0_dcs_write(ctx, pwr142, ARRAY_SIZE(pwr142));
+}
+
+static void s6e8aa0_etc_elvss_control(struct s6e8aa0 *ctx)
+{
+ u8 id = ctx->id ? 0 : 0x95;
+
+ s6e8aa0_dcs_write_seq(ctx, 0xb1, 0x04, id);
+}
+
+static void s6e8aa0_elvss_nvm_set_v142(struct s6e8aa0 *ctx)
+{
+ u8 br;
+
+ switch (ctx->brightness) {
+ case 0 ... 6: /* 30cd ~ 100cd */
+ br = 0xdf;
+ break;
+ case 7 ... 11: /* 120cd ~ 150cd */
+ br = 0xdd;
+ break;
+ case 12 ... 15: /* 180cd ~ 210cd */
+ default:
+ br = 0xd9;
+ break;
+ case 16 ... 24: /* 240cd ~ 300cd */
+ br = 0xd0;
+ break;
+ }
+
+ s6e8aa0_dcs_write_seq(ctx, 0xd9, 0x14, 0x40, 0x0c, 0xcb, 0xce, 0x6e,
+ 0xc4, 0x0f, 0x40, 0x41, br, 0x00, 0x60, 0x19);
+}
+
+static void s6e8aa0_elvss_nvm_set(struct s6e8aa0 *ctx)
+{
+ if (ctx->version < 142)
+ s6e8aa0_dcs_write_seq_static(ctx,
+ 0xd9, 0x14, 0x40, 0x0c, 0xcb, 0xce, 0x6e, 0xc4, 0x07,
+ 0x40, 0x41, 0xc1, 0x00, 0x60, 0x19);
+ else
+ s6e8aa0_elvss_nvm_set_v142(ctx);
+};
+
+static void s6e8aa0_apply_level_2_key(struct s6e8aa0 *ctx)
+{
+ s6e8aa0_dcs_write_seq_static(ctx, 0xfc, 0x5a, 0x5a);
+}
+
+static const s6e8aa0_gamma_table s6e8aa0_gamma_tables_v142[GAMMA_LEVEL_NUM] = {
+ {
+ 0xfa, 0x01, 0x71, 0x31, 0x7b, 0x62, 0x55, 0x55,
+ 0xaf, 0xb1, 0xb1, 0xbd, 0xce, 0xb7, 0x9a, 0xb1,
+ 0x90, 0xb2, 0xc4, 0xae, 0x00, 0x60, 0x00, 0x40,
+ 0x00, 0x70,
+ }, {
+ 0xfa, 0x01, 0x71, 0x31, 0x7b, 0x74, 0x68, 0x69,
+ 0xb8, 0xc1, 0xb7, 0xbd, 0xcd, 0xb8, 0x93, 0xab,
+ 0x88, 0xb4, 0xc4, 0xb1, 0x00, 0x6b, 0x00, 0x4d,
+ 0x00, 0x7d,
+ }, {
+ 0xfa, 0x01, 0x71, 0x31, 0x7b, 0x95, 0x8a, 0x89,
+ 0xb4, 0xc6, 0xb2, 0xc5, 0xd2, 0xbf, 0x90, 0xa8,
+ 0x85, 0xb5, 0xc4, 0xb3, 0x00, 0x7b, 0x00, 0x5d,
+ 0x00, 0x8f,
+ }, {
+ 0xfa, 0x01, 0x71, 0x31, 0x7b, 0x9f, 0x98, 0x92,
+ 0xb3, 0xc4, 0xb0, 0xbc, 0xcc, 0xb4, 0x91, 0xa6,
+ 0x87, 0xb5, 0xc5, 0xb4, 0x00, 0x87, 0x00, 0x6a,
+ 0x00, 0x9e,
+ }, {
+ 0xfa, 0x01, 0x71, 0x31, 0x7b, 0x99, 0x93, 0x8b,
+ 0xb2, 0xc2, 0xb0, 0xbd, 0xce, 0xb4, 0x90, 0xa6,
+ 0x87, 0xb3, 0xc3, 0xb2, 0x00, 0x8d, 0x00, 0x70,
+ 0x00, 0xa4,
+ }, {
+ 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa7, 0xa5, 0x99,
+ 0xb2, 0xc2, 0xb0, 0xbb, 0xcd, 0xb1, 0x93, 0xa7,
+ 0x8a, 0xb2, 0xc1, 0xb0, 0x00, 0x92, 0x00, 0x75,
+ 0x00, 0xaa,
+ }, {
+ 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa0, 0xa0, 0x93,
+ 0xb6, 0xc4, 0xb4, 0xb5, 0xc8, 0xaa, 0x94, 0xa9,
+ 0x8c, 0xb2, 0xc0, 0xb0, 0x00, 0x97, 0x00, 0x7a,
+ 0x00, 0xaf,
+ }, {
+ 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa3, 0xa7, 0x96,
+ 0xb3, 0xc2, 0xb0, 0xba, 0xcb, 0xb0, 0x94, 0xa8,
+ 0x8c, 0xb0, 0xbf, 0xaf, 0x00, 0x9f, 0x00, 0x83,
+ 0x00, 0xb9,
+ }, {
+ 0xfa, 0x01, 0x71, 0x31, 0x7b, 0x9d, 0xa2, 0x90,
+ 0xb6, 0xc5, 0xb3, 0xb8, 0xc9, 0xae, 0x94, 0xa8,
+ 0x8d, 0xaf, 0xbd, 0xad, 0x00, 0xa4, 0x00, 0x88,
+ 0x00, 0xbf,
+ }, {
+ 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa6, 0xac, 0x97,
+ 0xb4, 0xc4, 0xb1, 0xbb, 0xcb, 0xb2, 0x93, 0xa7,
+ 0x8d, 0xae, 0xbc, 0xad, 0x00, 0xa7, 0x00, 0x8c,
+ 0x00, 0xc3,
+ }, {
+ 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa2, 0xa9, 0x93,
+ 0xb6, 0xc5, 0xb2, 0xba, 0xc9, 0xb0, 0x93, 0xa7,
+ 0x8d, 0xae, 0xbb, 0xac, 0x00, 0xab, 0x00, 0x90,
+ 0x00, 0xc8,
+ }, {
+ 0xfa, 0x01, 0x71, 0x31, 0x7b, 0x9e, 0xa6, 0x8f,
+ 0xb7, 0xc6, 0xb3, 0xb8, 0xc8, 0xb0, 0x93, 0xa6,
+ 0x8c, 0xae, 0xbb, 0xad, 0x00, 0xae, 0x00, 0x93,
+ 0x00, 0xcc,
+ }, {
+ 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xab, 0xb4, 0x9c,
+ 0xb3, 0xc3, 0xaf, 0xb7, 0xc7, 0xaf, 0x93, 0xa6,
+ 0x8c, 0xaf, 0xbc, 0xad, 0x00, 0xb1, 0x00, 0x97,
+ 0x00, 0xcf,
+ }, {
+ 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa6, 0xb1, 0x98,
+ 0xb1, 0xc2, 0xab, 0xba, 0xc9, 0xb2, 0x93, 0xa6,
+ 0x8d, 0xae, 0xba, 0xab, 0x00, 0xb5, 0x00, 0x9b,
+ 0x00, 0xd4,
+ }, {
+ 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa3, 0xae, 0x94,
+ 0xb2, 0xc3, 0xac, 0xbb, 0xca, 0xb4, 0x91, 0xa4,
+ 0x8a, 0xae, 0xba, 0xac, 0x00, 0xb8, 0x00, 0x9e,
+ 0x00, 0xd8,
+ }, {
+ 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xab, 0xb7, 0x9c,
+ 0xae, 0xc0, 0xa9, 0xba, 0xc9, 0xb3, 0x92, 0xa5,
+ 0x8b, 0xad, 0xb9, 0xab, 0x00, 0xbb, 0x00, 0xa1,
+ 0x00, 0xdc,
+ }, {
+ 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa7, 0xb4, 0x97,
+ 0xb0, 0xc1, 0xaa, 0xb9, 0xc8, 0xb2, 0x92, 0xa5,
+ 0x8c, 0xae, 0xb9, 0xab, 0x00, 0xbe, 0x00, 0xa4,
+ 0x00, 0xdf,
+ }, {
+ 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa3, 0xb0, 0x94,
+ 0xb0, 0xc2, 0xab, 0xbb, 0xc9, 0xb3, 0x91, 0xa4,
+ 0x8b, 0xad, 0xb8, 0xaa, 0x00, 0xc1, 0x00, 0xa8,
+ 0x00, 0xe2,
+ }, {
+ 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa3, 0xb0, 0x94,
+ 0xae, 0xbf, 0xa8, 0xb9, 0xc8, 0xb3, 0x92, 0xa4,
+ 0x8b, 0xad, 0xb7, 0xa9, 0x00, 0xc4, 0x00, 0xab,
+ 0x00, 0xe6,
+ }, {
+ 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa7, 0xb6, 0x98,
+ 0xaf, 0xc0, 0xa8, 0xb8, 0xc7, 0xb2, 0x93, 0xa5,
+ 0x8d, 0xad, 0xb7, 0xa9, 0x00, 0xc7, 0x00, 0xae,
+ 0x00, 0xe9,
+ }, {
+ 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa4, 0xb3, 0x95,
+ 0xaf, 0xc1, 0xa9, 0xb9, 0xc8, 0xb3, 0x92, 0xa4,
+ 0x8b, 0xad, 0xb7, 0xaa, 0x00, 0xc9, 0x00, 0xb0,
+ 0x00, 0xec,
+ }, {
+ 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa4, 0xb3, 0x95,
+ 0xac, 0xbe, 0xa6, 0xbb, 0xc9, 0xb4, 0x90, 0xa3,
+ 0x8a, 0xad, 0xb7, 0xa9, 0x00, 0xcc, 0x00, 0xb4,
+ 0x00, 0xf0,
+ }, {
+ 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa0, 0xb0, 0x91,
+ 0xae, 0xc0, 0xa6, 0xba, 0xc8, 0xb4, 0x91, 0xa4,
+ 0x8b, 0xad, 0xb7, 0xa9, 0x00, 0xcf, 0x00, 0xb7,
+ 0x00, 0xf3,
+ }, {
+ 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa7, 0xb8, 0x98,
+ 0xab, 0xbd, 0xa4, 0xbb, 0xc9, 0xb5, 0x91, 0xa3,
+ 0x8b, 0xac, 0xb6, 0xa8, 0x00, 0xd1, 0x00, 0xb9,
+ 0x00, 0xf6,
+ }, {
+ 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa4, 0xb5, 0x95,
+ 0xa9, 0xbc, 0xa1, 0xbb, 0xc9, 0xb5, 0x91, 0xa3,
+ 0x8a, 0xad, 0xb6, 0xa8, 0x00, 0xd6, 0x00, 0xbf,
+ 0x00, 0xfc,
+ },
+};
+
+static const s6e8aa0_gamma_table s6e8aa0_gamma_tables_v96[GAMMA_LEVEL_NUM] = {
+ {
+ 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xff, 0x00, 0xff,
+ 0xdf, 0x1f, 0xd7, 0xdc, 0xb7, 0xe1, 0xc0, 0xaf,
+ 0xc4, 0xd2, 0xd0, 0xcf, 0x00, 0x4d, 0x00, 0x40,
+ 0x00, 0x5f,
+ }, {
+ 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xff, 0x00, 0xff,
+ 0xd5, 0x35, 0xcf, 0xdc, 0xc1, 0xe1, 0xbf, 0xb3,
+ 0xc1, 0xd2, 0xd1, 0xce, 0x00, 0x53, 0x00, 0x46,
+ 0x00, 0x67,
+ }, {
+ 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xff, 0x00, 0xff,
+ 0xd2, 0x64, 0xcf, 0xdb, 0xc6, 0xe1, 0xbd, 0xb3,
+ 0xbd, 0xd2, 0xd2, 0xce, 0x00, 0x59, 0x00, 0x4b,
+ 0x00, 0x6e,
+ }, {
+ 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xff, 0x00, 0xff,
+ 0xd0, 0x7c, 0xcf, 0xdb, 0xc9, 0xe0, 0xbc, 0xb4,
+ 0xbb, 0xcf, 0xd1, 0xcc, 0x00, 0x5f, 0x00, 0x50,
+ 0x00, 0x75,
+ }, {
+ 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xff, 0x00, 0xff,
+ 0xd0, 0x8e, 0xd1, 0xdb, 0xcc, 0xdf, 0xbb, 0xb6,
+ 0xb9, 0xd0, 0xd1, 0xcd, 0x00, 0x63, 0x00, 0x54,
+ 0x00, 0x7a,
+ }, {
+ 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xff, 0x00, 0xff,
+ 0xd1, 0x9e, 0xd5, 0xda, 0xcd, 0xdd, 0xbb, 0xb7,
+ 0xb9, 0xce, 0xce, 0xc9, 0x00, 0x68, 0x00, 0x59,
+ 0x00, 0x81,
+ }, {
+ 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xff, 0x00, 0xff,
+ 0xd0, 0xa5, 0xd6, 0xda, 0xcf, 0xdd, 0xbb, 0xb7,
+ 0xb8, 0xcc, 0xcd, 0xc7, 0x00, 0x6c, 0x00, 0x5c,
+ 0x00, 0x86,
+ }, {
+ 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xff, 0x1f, 0xfe,
+ 0xd0, 0xae, 0xd7, 0xd9, 0xd0, 0xdb, 0xb9, 0xb6,
+ 0xb5, 0xca, 0xcc, 0xc5, 0x00, 0x74, 0x00, 0x63,
+ 0x00, 0x90,
+ }, {
+ 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xff, 0x1f, 0xf9,
+ 0xcf, 0xb0, 0xd6, 0xd9, 0xd1, 0xdb, 0xb9, 0xb6,
+ 0xb4, 0xca, 0xcb, 0xc5, 0x00, 0x77, 0x00, 0x66,
+ 0x00, 0x94,
+ }, {
+ 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xff, 0x1f, 0xf7,
+ 0xcf, 0xb3, 0xd7, 0xd8, 0xd1, 0xd9, 0xb7, 0xb6,
+ 0xb3, 0xc9, 0xca, 0xc3, 0x00, 0x7b, 0x00, 0x69,
+ 0x00, 0x99,
+
+ }, {
+ 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xfd, 0x2f, 0xf7,
+ 0xdf, 0xb5, 0xd6, 0xd8, 0xd1, 0xd8, 0xb6, 0xb5,
+ 0xb2, 0xca, 0xcb, 0xc4, 0x00, 0x7e, 0x00, 0x6c,
+ 0x00, 0x9d,
+ }, {
+ 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xfa, 0x2f, 0xf5,
+ 0xce, 0xb6, 0xd5, 0xd7, 0xd2, 0xd8, 0xb6, 0xb4,
+ 0xb0, 0xc7, 0xc9, 0xc1, 0x00, 0x84, 0x00, 0x71,
+ 0x00, 0xa5,
+ }, {
+ 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xf7, 0x2f, 0xf2,
+ 0xce, 0xb9, 0xd5, 0xd8, 0xd2, 0xd8, 0xb4, 0xb4,
+ 0xaf, 0xc7, 0xc9, 0xc1, 0x00, 0x87, 0x00, 0x73,
+ 0x00, 0xa8,
+ }, {
+ 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xf5, 0x2f, 0xf0,
+ 0xdf, 0xba, 0xd5, 0xd7, 0xd2, 0xd7, 0xb4, 0xb4,
+ 0xaf, 0xc5, 0xc7, 0xbf, 0x00, 0x8a, 0x00, 0x76,
+ 0x00, 0xac,
+ }, {
+ 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xf2, 0x2f, 0xed,
+ 0xcE, 0xbb, 0xd4, 0xd6, 0xd2, 0xd6, 0xb5, 0xb4,
+ 0xaF, 0xc5, 0xc7, 0xbf, 0x00, 0x8c, 0x00, 0x78,
+ 0x00, 0xaf,
+ }, {
+ 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xef, 0x2f, 0xeb,
+ 0xcd, 0xbb, 0xd2, 0xd7, 0xd3, 0xd6, 0xb3, 0xb4,
+ 0xae, 0xc5, 0xc6, 0xbe, 0x00, 0x91, 0x00, 0x7d,
+ 0x00, 0xb6,
+ }, {
+ 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xee, 0x2f, 0xea,
+ 0xce, 0xbd, 0xd4, 0xd6, 0xd2, 0xd5, 0xb2, 0xb3,
+ 0xad, 0xc3, 0xc4, 0xbb, 0x00, 0x94, 0x00, 0x7f,
+ 0x00, 0xba,
+ }, {
+ 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xec, 0x2f, 0xe8,
+ 0xce, 0xbe, 0xd3, 0xd6, 0xd3, 0xd5, 0xb2, 0xb2,
+ 0xac, 0xc3, 0xc5, 0xbc, 0x00, 0x96, 0x00, 0x81,
+ 0x00, 0xbd,
+ }, {
+ 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xeb, 0x2f, 0xe7,
+ 0xce, 0xbf, 0xd3, 0xd6, 0xd2, 0xd5, 0xb1, 0xb2,
+ 0xab, 0xc2, 0xc4, 0xbb, 0x00, 0x99, 0x00, 0x83,
+ 0x00, 0xc0,
+ }, {
+ 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xef, 0x5f, 0xe9,
+ 0xca, 0xbf, 0xd3, 0xd5, 0xd2, 0xd4, 0xb2, 0xb2,
+ 0xab, 0xc1, 0xc4, 0xba, 0x00, 0x9b, 0x00, 0x85,
+ 0x00, 0xc3,
+ }, {
+ 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xea, 0x5f, 0xe8,
+ 0xee, 0xbf, 0xd2, 0xd5, 0xd2, 0xd4, 0xb1, 0xb2,
+ 0xab, 0xc1, 0xc2, 0xb9, 0x00, 0x9D, 0x00, 0x87,
+ 0x00, 0xc6,
+ }, {
+ 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xe9, 0x5f, 0xe7,
+ 0xcd, 0xbf, 0xd2, 0xd6, 0xd2, 0xd4, 0xb1, 0xb2,
+ 0xab, 0xbe, 0xc0, 0xb7, 0x00, 0xa1, 0x00, 0x8a,
+ 0x00, 0xca,
+ }, {
+ 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xe8, 0x61, 0xe6,
+ 0xcd, 0xbf, 0xd1, 0xd6, 0xd3, 0xd4, 0xaf, 0xb0,
+ 0xa9, 0xbe, 0xc1, 0xb7, 0x00, 0xa3, 0x00, 0x8b,
+ 0x00, 0xce,
+ }, {
+ 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xe8, 0x62, 0xe5,
+ 0xcc, 0xc0, 0xd0, 0xd6, 0xd2, 0xd4, 0xaf, 0xb1,
+ 0xa9, 0xbd, 0xc0, 0xb6, 0x00, 0xa5, 0x00, 0x8d,
+ 0x00, 0xd0,
+ }, {
+ 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xe7, 0x7f, 0xe3,
+ 0xcc, 0xc1, 0xd0, 0xd5, 0xd3, 0xd3, 0xae, 0xaf,
+ 0xa8, 0xbe, 0xc0, 0xb7, 0x00, 0xa8, 0x00, 0x90,
+ 0x00, 0xd3,
+ }
+};
+
+static const s6e8aa0_gamma_table s6e8aa0_gamma_tables_v32[GAMMA_LEVEL_NUM] = {
+ {
+ 0xfa, 0x01, 0x43, 0x14, 0x45, 0x72, 0x5e, 0x6b,
+ 0xa1, 0xa7, 0x9a, 0xb4, 0xcb, 0xb8, 0x92, 0xac,
+ 0x97, 0xb4, 0xc3, 0xb5, 0x00, 0x4e, 0x00, 0x37,
+ 0x00, 0x58,
+ }, {
+ 0xfa, 0x01, 0x43, 0x14, 0x45, 0x85, 0x71, 0x7d,
+ 0xa6, 0xb6, 0xa1, 0xb5, 0xca, 0xba, 0x93, 0xac,
+ 0x98, 0xb2, 0xc0, 0xaf, 0x00, 0x59, 0x00, 0x43,
+ 0x00, 0x64,
+ }, {
+ 0xfa, 0x01, 0x43, 0x14, 0x45, 0xa4, 0x94, 0x9e,
+ 0xa0, 0xbb, 0x9c, 0xc3, 0xd2, 0xc6, 0x93, 0xaa,
+ 0x95, 0xb7, 0xc2, 0xb4, 0x00, 0x65, 0x00, 0x50,
+ 0x00, 0x74,
+ }, {
+ 0xfa, 0x01, 0x43, 0x14, 0x45, 0xad, 0xa1, 0xa6,
+ 0xa0, 0xb9, 0x9b, 0xc3, 0xd1, 0xc8, 0x90, 0xa6,
+ 0x90, 0xbb, 0xc3, 0xb7, 0x00, 0x6f, 0x00, 0x5b,
+ 0x00, 0x80,
+ }, {
+ 0xfa, 0x01, 0x43, 0x14, 0x45, 0xa6, 0x9d, 0x9f,
+ 0x9f, 0xb8, 0x9a, 0xc7, 0xd5, 0xcc, 0x90, 0xa5,
+ 0x8f, 0xb8, 0xc1, 0xb6, 0x00, 0x74, 0x00, 0x60,
+ 0x00, 0x85,
+ }, {
+ 0xfa, 0x01, 0x43, 0x14, 0x45, 0xb3, 0xae, 0xae,
+ 0x9e, 0xb7, 0x9a, 0xc8, 0xd6, 0xce, 0x91, 0xa6,
+ 0x90, 0xb6, 0xc0, 0xb3, 0x00, 0x78, 0x00, 0x65,
+ 0x00, 0x8a,
+ }, {
+ 0xfa, 0x01, 0x43, 0x14, 0x45, 0xad, 0xa9, 0xa8,
+ 0xa3, 0xb9, 0x9e, 0xc4, 0xd3, 0xcb, 0x94, 0xa6,
+ 0x90, 0xb6, 0xbf, 0xb3, 0x00, 0x7c, 0x00, 0x69,
+ 0x00, 0x8e,
+ }, {
+ 0xfa, 0x01, 0x43, 0x14, 0x45, 0xaf, 0xaf, 0xa9,
+ 0xa5, 0xbc, 0xa2, 0xc7, 0xd5, 0xcd, 0x93, 0xa5,
+ 0x8f, 0xb4, 0xbd, 0xb1, 0x00, 0x83, 0x00, 0x70,
+ 0x00, 0x96,
+ }, {
+ 0xfa, 0x01, 0x43, 0x14, 0x45, 0xa9, 0xab, 0xa3,
+ 0xaa, 0xbf, 0xa7, 0xc5, 0xd3, 0xcb, 0x93, 0xa5,
+ 0x8f, 0xb2, 0xbb, 0xb0, 0x00, 0x86, 0x00, 0x74,
+ 0x00, 0x9b,
+ }, {
+ 0xfa, 0x01, 0x43, 0x14, 0x45, 0xb1, 0xb5, 0xab,
+ 0xab, 0xc0, 0xa9, 0xc7, 0xd4, 0xcc, 0x94, 0xa4,
+ 0x8f, 0xb1, 0xbb, 0xaf, 0x00, 0x8a, 0x00, 0x77,
+ 0x00, 0x9e,
+ }, {
+ 0xfa, 0x01, 0x43, 0x14, 0x45, 0xad, 0xb2, 0xa7,
+ 0xae, 0xc2, 0xab, 0xc5, 0xd3, 0xca, 0x93, 0xa4,
+ 0x8f, 0xb1, 0xba, 0xae, 0x00, 0x8d, 0x00, 0x7b,
+ 0x00, 0xa2,
+ }, {
+ 0xfa, 0x01, 0x43, 0x14, 0x45, 0xa9, 0xaf, 0xa3,
+ 0xb0, 0xc3, 0xae, 0xc4, 0xd1, 0xc8, 0x93, 0xa4,
+ 0x8f, 0xb1, 0xba, 0xaf, 0x00, 0x8f, 0x00, 0x7d,
+ 0x00, 0xa5,
+ }, {
+ 0xfa, 0x01, 0x43, 0x14, 0x45, 0xb4, 0xbd, 0xaf,
+ 0xae, 0xc1, 0xab, 0xc2, 0xd0, 0xc6, 0x94, 0xa4,
+ 0x8f, 0xb1, 0xba, 0xaf, 0x00, 0x92, 0x00, 0x80,
+ 0x00, 0xa8,
+ }, {
+ 0xfa, 0x01, 0x43, 0x14, 0x45, 0xb0, 0xb9, 0xac,
+ 0xad, 0xc1, 0xab, 0xc4, 0xd1, 0xc7, 0x95, 0xa4,
+ 0x90, 0xb0, 0xb9, 0xad, 0x00, 0x95, 0x00, 0x84,
+ 0x00, 0xac,
+ }, {
+ 0xfa, 0x01, 0x43, 0x14, 0x45, 0xad, 0xb6, 0xa7,
+ 0xaf, 0xc2, 0xae, 0xc5, 0xd1, 0xc7, 0x93, 0xa3,
+ 0x8e, 0xb0, 0xb9, 0xad, 0x00, 0x98, 0x00, 0x86,
+ 0x00, 0xaf,
+ }, {
+ 0xfa, 0x01, 0x43, 0x14, 0x45, 0xb4, 0xbf, 0xaf,
+ 0xad, 0xc1, 0xab, 0xc3, 0xd0, 0xc6, 0x94, 0xa3,
+ 0x8f, 0xaf, 0xb8, 0xac, 0x00, 0x9a, 0x00, 0x89,
+ 0x00, 0xb2,
+ }, {
+ 0xfa, 0x01, 0x43, 0x14, 0x45, 0xb0, 0xbc, 0xac,
+ 0xaf, 0xc2, 0xad, 0xc2, 0xcf, 0xc4, 0x94, 0xa3,
+ 0x90, 0xaf, 0xb8, 0xad, 0x00, 0x9c, 0x00, 0x8b,
+ 0x00, 0xb5,
+ }, {
+ 0xfa, 0x01, 0x43, 0x14, 0x45, 0xad, 0xb9, 0xa7,
+ 0xb1, 0xc4, 0xaf, 0xc3, 0xcf, 0xc5, 0x94, 0xa3,
+ 0x8f, 0xae, 0xb7, 0xac, 0x00, 0x9f, 0x00, 0x8e,
+ 0x00, 0xb8,
+ }, {
+ 0xfa, 0x01, 0x43, 0x14, 0x45, 0xad, 0xb9, 0xa7,
+ 0xaf, 0xc2, 0xad, 0xc1, 0xce, 0xc3, 0x95, 0xa3,
+ 0x90, 0xad, 0xb6, 0xab, 0x00, 0xa2, 0x00, 0x91,
+ 0x00, 0xbb,
+ }, {
+ 0xfa, 0x01, 0x43, 0x14, 0x45, 0xb1, 0xbe, 0xac,
+ 0xb1, 0xc4, 0xaf, 0xc1, 0xcd, 0xc1, 0x95, 0xa4,
+ 0x91, 0xad, 0xb6, 0xab, 0x00, 0xa4, 0x00, 0x93,
+ 0x00, 0xbd,
+ }, {
+ 0xfa, 0x01, 0x43, 0x14, 0x45, 0xad, 0xbb, 0xa8,
+ 0xb3, 0xc5, 0xb2, 0xc1, 0xcd, 0xc2, 0x95, 0xa3,
+ 0x90, 0xad, 0xb6, 0xab, 0x00, 0xa6, 0x00, 0x95,
+ 0x00, 0xc0,
+ }, {
+ 0xfa, 0x01, 0x43, 0x14, 0x45, 0xad, 0xbb, 0xa8,
+ 0xb0, 0xc3, 0xaf, 0xc2, 0xce, 0xc2, 0x94, 0xa2,
+ 0x90, 0xac, 0xb6, 0xab, 0x00, 0xa8, 0x00, 0x98,
+ 0x00, 0xc3,
+ }, {
+ 0xfa, 0x01, 0x43, 0x14, 0x45, 0xa9, 0xb8, 0xa5,
+ 0xb3, 0xc5, 0xb2, 0xc1, 0xcc, 0xc0, 0x95, 0xa2,
+ 0x90, 0xad, 0xb6, 0xab, 0x00, 0xaa, 0x00, 0x9a,
+ 0x00, 0xc5,
+ }, {
+ 0xfa, 0x01, 0x43, 0x14, 0x45, 0xb0, 0xc0, 0xac,
+ 0xb0, 0xc3, 0xaf, 0xc1, 0xcd, 0xc1, 0x95, 0xa2,
+ 0x90, 0xac, 0xb5, 0xa9, 0x00, 0xac, 0x00, 0x9c,
+ 0x00, 0xc8,
+ }, {
+ 0xfa, 0x01, 0x43, 0x14, 0x45, 0xad, 0xbd, 0xa8,
+ 0xaf, 0xc2, 0xaf, 0xc1, 0xcc, 0xc0, 0x95, 0xa2,
+ 0x90, 0xac, 0xb5, 0xaa, 0x00, 0xb1, 0x00, 0xa1,
+ 0x00, 0xcc,
+ },
+};
+
+static const struct s6e8aa0_variant s6e8aa0_variants[] = {
+ {
+ .version = 32,
+ .gamma_tables = s6e8aa0_gamma_tables_v32,
+ }, {
+ .version = 96,
+ .gamma_tables = s6e8aa0_gamma_tables_v96,
+ }, {
+ .version = 142,
+ .gamma_tables = s6e8aa0_gamma_tables_v142,
+ }, {
+ .version = 210,
+ .gamma_tables = s6e8aa0_gamma_tables_v142,
+ }
+};
+
+static void s6e8aa0_brightness_set(struct s6e8aa0 *ctx)
+{
+ const u8 *gamma;
+
+ if (ctx->error)
+ return;
+
+ gamma = ctx->variant->gamma_tables[ctx->brightness];
+
+ if (ctx->version >= 142)
+ s6e8aa0_elvss_nvm_set(ctx);
+
+ s6e8aa0_dcs_write(ctx, gamma, GAMMA_TABLE_LEN);
+
+ /* update gamma table. */
+ s6e8aa0_dcs_write_seq_static(ctx, 0xf7, 0x03);
+}
+
+static void s6e8aa0_panel_init(struct s6e8aa0 *ctx)
+{
+ s6e8aa0_apply_level_1_key(ctx);
+ s6e8aa0_apply_level_2_key(ctx);
+ msleep(20);
+
+ s6e8aa0_dcs_write_seq_static(ctx, MIPI_DCS_EXIT_SLEEP_MODE);
+ msleep(40);
+
+ s6e8aa0_panel_cond_set(ctx);
+ s6e8aa0_display_condition_set(ctx);
+ s6e8aa0_brightness_set(ctx);
+ s6e8aa0_etc_source_control(ctx);
+ s6e8aa0_etc_pentile_control(ctx);
+ s6e8aa0_elvss_nvm_set(ctx);
+ s6e8aa0_etc_power_control(ctx);
+ s6e8aa0_etc_elvss_control(ctx);
+ msleep(ctx->init_delay);
+}
+
+static void s6e8aa0_set_maximum_return_packet_size(struct s6e8aa0 *ctx,
+ u16 size)
+{
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+ int ret;
+
+ if (ctx->error < 0)
+ return;
+
+ ret = mipi_dsi_set_maximum_return_packet_size(dsi, size);
+ if (ret < 0) {
+ dev_err(ctx->dev,
+ "error %d setting maximum return packet size to %d\n",
+ ret, size);
+ ctx->error = ret;
+ }
+}
+
+static void s6e8aa0_read_mtp_id(struct s6e8aa0 *ctx)
+{
+ u8 id[3];
+ int ret, i;
+
+ ret = s6e8aa0_dcs_read(ctx, 0xd1, id, ARRAY_SIZE(id));
+ if (ret < 0 || ret < ARRAY_SIZE(id) || id[0] == 0x00) {
+ dev_err(ctx->dev, "read id failed\n");
+ ctx->error = -EIO;
+ return;
+ }
+
+ dev_info(ctx->dev, "ID: 0x%2x, 0x%2x, 0x%2x\n", id[0], id[1], id[2]);
+
+ for (i = 0; i < ARRAY_SIZE(s6e8aa0_variants); ++i) {
+ if (id[1] == s6e8aa0_variants[i].version)
+ break;
+ }
+ if (i >= ARRAY_SIZE(s6e8aa0_variants)) {
+ dev_err(ctx->dev, "unsupported display version %d\n", id[1]);
+ ctx->error = -EINVAL;
+ return;
+ }
+
+ ctx->variant = &s6e8aa0_variants[i];
+ ctx->version = id[1];
+ ctx->id = id[2];
+}
+
+static void s6e8aa0_set_sequence(struct s6e8aa0 *ctx)
+{
+ s6e8aa0_set_maximum_return_packet_size(ctx, 3);
+ s6e8aa0_read_mtp_id(ctx);
+ s6e8aa0_panel_init(ctx);
+ s6e8aa0_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_ON);
+}
+
+static int s6e8aa0_power_on(struct s6e8aa0 *ctx)
+{
+ int ret;
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
+ if (ret < 0)
+ return ret;
+
+ msleep(ctx->power_on_delay);
+
+ gpiod_set_value(ctx->reset_gpio, 0);
+ usleep_range(10000, 11000);
+ gpiod_set_value(ctx->reset_gpio, 1);
+
+ msleep(ctx->reset_delay);
+
+ return 0;
+}
+
+static int s6e8aa0_power_off(struct s6e8aa0 *ctx)
+{
+ return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
+}
+
+static int s6e8aa0_disable(struct drm_panel *panel)
+{
+ return 0;
+}
+
+static int s6e8aa0_unprepare(struct drm_panel *panel)
+{
+ struct s6e8aa0 *ctx = panel_to_s6e8aa0(panel);
+
+ s6e8aa0_dcs_write_seq_static(ctx, MIPI_DCS_ENTER_SLEEP_MODE);
+ s6e8aa0_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_OFF);
+ msleep(40);
+
+ s6e8aa0_clear_error(ctx);
+
+ return s6e8aa0_power_off(ctx);
+}
+
+static int s6e8aa0_prepare(struct drm_panel *panel)
+{
+ struct s6e8aa0 *ctx = panel_to_s6e8aa0(panel);
+ int ret;
+
+ ret = s6e8aa0_power_on(ctx);
+ if (ret < 0)
+ return ret;
+
+ s6e8aa0_set_sequence(ctx);
+ ret = ctx->error;
+
+ if (ret < 0)
+ s6e8aa0_unprepare(panel);
+
+ return ret;
+}
+
+static int s6e8aa0_enable(struct drm_panel *panel)
+{
+ return 0;
+}
+
+static int s6e8aa0_get_modes(struct drm_panel *panel,
+ struct drm_connector *connector)
+{
+ struct s6e8aa0 *ctx = panel_to_s6e8aa0(panel);
+ struct drm_display_mode *mode;
+
+ mode = drm_mode_create(connector->dev);
+ if (!mode) {
+ dev_err(panel->dev, "failed to create a new display mode\n");
+ return 0;
+ }
+
+ drm_display_mode_from_videomode(&ctx->vm, mode);
+ mode->width_mm = ctx->width_mm;
+ mode->height_mm = ctx->height_mm;
+ connector->display_info.width_mm = mode->width_mm;
+ connector->display_info.height_mm = mode->height_mm;
+
+ mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+ drm_mode_probed_add(connector, mode);
+
+ return 1;
+}
+
+static const struct drm_panel_funcs s6e8aa0_drm_funcs = {
+ .disable = s6e8aa0_disable,
+ .unprepare = s6e8aa0_unprepare,
+ .prepare = s6e8aa0_prepare,
+ .enable = s6e8aa0_enable,
+ .get_modes = s6e8aa0_get_modes,
+};
+
+static int s6e8aa0_parse_dt(struct s6e8aa0 *ctx)
+{
+ struct device *dev = ctx->dev;
+ struct device_node *np = dev->of_node;
+ int ret;
+
+ ret = of_get_videomode(np, &ctx->vm, 0);
+ if (ret < 0)
+ return ret;
+
+ of_property_read_u32(np, "power-on-delay", &ctx->power_on_delay);
+ of_property_read_u32(np, "reset-delay", &ctx->reset_delay);
+ of_property_read_u32(np, "init-delay", &ctx->init_delay);
+ of_property_read_u32(np, "panel-width-mm", &ctx->width_mm);
+ of_property_read_u32(np, "panel-height-mm", &ctx->height_mm);
+
+ ctx->flip_horizontal = of_property_read_bool(np, "flip-horizontal");
+ ctx->flip_vertical = of_property_read_bool(np, "flip-vertical");
+
+ return 0;
+}
+
+static int s6e8aa0_probe(struct mipi_dsi_device *dsi)
+{
+ struct device *dev = &dsi->dev;
+ struct s6e8aa0 *ctx;
+ int ret;
+
+ ctx = devm_kzalloc(dev, sizeof(struct s6e8aa0), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ 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_VSYNC_FLUSH | MIPI_DSI_MODE_VIDEO_AUTO_VERT;
+
+ ret = s6e8aa0_parse_dt(ctx);
+ if (ret < 0)
+ return ret;
+
+ 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);
+ }
+
+ ctx->brightness = GAMMA_LEVEL_NUM - 1;
+
+ drm_panel_init(&ctx->panel, dev, &s6e8aa0_drm_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ drm_panel_add(&ctx->panel);
+
+ ret = mipi_dsi_attach(dsi);
+ if (ret < 0)
+ drm_panel_remove(&ctx->panel);
+
+ return ret;
+}
+
+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);
+}
+
+static const struct of_device_id s6e8aa0_of_match[] = {
+ { .compatible = "samsung,s6e8aa0" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, s6e8aa0_of_match);
+
+static struct mipi_dsi_driver s6e8aa0_driver = {
+ .probe = s6e8aa0_probe,
+ .remove = s6e8aa0_remove,
+ .driver = {
+ .name = "panel-samsung-s6e8aa0",
+ .of_match_table = s6e8aa0_of_match,
+ },
+};
+module_mipi_dsi_driver(s6e8aa0_driver);
+
+MODULE_AUTHOR("Donghwa Lee <dh09.lee@samsung.com>");
+MODULE_AUTHOR("Inki Dae <inki.dae@samsung.com>");
+MODULE_AUTHOR("Joongmock Shin <jmock.shin@samsung.com>");
+MODULE_AUTHOR("Eunchul Kim <chulspro.kim@samsung.com>");
+MODULE_AUTHOR("Tomasz Figa <t.figa@samsung.com>");
+MODULE_AUTHOR("Andrzej Hajda <a.hajda@samsung.com>");
+MODULE_DESCRIPTION("MIPI-DSI based s6e8aa0 AMOLED LCD Panel Driver");
+MODULE_LICENSE("GPL v2");
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 000000000..1a0d24595
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-samsung-sofef00.c
@@ -0,0 +1,346 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2020 Caleb Connolly <caleb@connolly.tech>
+ * 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/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>
+
+struct sofef00_panel {
+ struct drm_panel panel;
+ struct mipi_dsi_device *dsi;
+ struct regulator *supply;
+ struct gpio_desc *reset_gpio;
+ const struct drm_display_mode *mode;
+ bool prepared;
+};
+
+static inline
+struct sofef00_panel *to_sofef00_panel(struct drm_panel *panel)
+{
+ return container_of(panel, struct sofef00_panel, panel);
+}
+
+#define dsi_dcs_write_seq(dsi, seq...) do { \
+ static const u8 d[] = { seq }; \
+ int ret; \
+ ret = mipi_dsi_dcs_write_buffer(dsi, d, ARRAY_SIZE(d)); \
+ if (ret < 0) \
+ return ret; \
+ } while (0)
+
+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 device *dev = &dsi->dev;
+ int ret;
+
+ dsi->mode_flags |= MIPI_DSI_MODE_LPM;
+
+ ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to exit sleep mode: %d\n", ret);
+ return ret;
+ }
+ usleep_range(10000, 11000);
+
+ dsi_dcs_write_seq(dsi, 0xf0, 0x5a, 0x5a);
+
+ 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);
+ return ret;
+ }
+
+ dsi_dcs_write_seq(dsi, 0xf0, 0xa5, 0xa5);
+ dsi_dcs_write_seq(dsi, 0xf0, 0x5a, 0x5a);
+ dsi_dcs_write_seq(dsi, 0xb0, 0x07);
+ dsi_dcs_write_seq(dsi, 0xb6, 0x12);
+ dsi_dcs_write_seq(dsi, 0xf0, 0xa5, 0xa5);
+ dsi_dcs_write_seq(dsi, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x20);
+ dsi_dcs_write_seq(dsi, MIPI_DCS_WRITE_POWER_SAVE, 0x00);
+
+ ret = mipi_dsi_dcs_set_display_on(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set display on: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sofef00_panel_off(struct sofef00_panel *ctx)
+{
+ struct mipi_dsi_device *dsi = ctx->dsi;
+ struct device *dev = &dsi->dev;
+ int ret;
+
+ dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
+
+ ret = mipi_dsi_dcs_set_display_off(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set display off: %d\n", ret);
+ return ret;
+ }
+ msleep(40);
+
+ ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to enter sleep mode: %d\n", ret);
+ return ret;
+ }
+ msleep(160);
+
+ return 0;
+}
+
+static int sofef00_panel_prepare(struct drm_panel *panel)
+{
+ struct sofef00_panel *ctx = to_sofef00_panel(panel);
+ struct device *dev = &ctx->dsi->dev;
+ int ret;
+
+ if (ctx->prepared)
+ return 0;
+
+ ret = regulator_enable(ctx->supply);
+ if (ret < 0) {
+ dev_err(dev, "Failed to enable regulator: %d\n", ret);
+ return ret;
+ }
+
+ sofef00_panel_reset(ctx);
+
+ ret = sofef00_panel_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;
+ }
+
+ ctx->prepared = true;
+ return 0;
+}
+
+static int sofef00_panel_unprepare(struct drm_panel *panel)
+{
+ struct sofef00_panel *ctx = to_sofef00_panel(panel);
+ struct device *dev = &ctx->dsi->dev;
+ int ret;
+
+ if (!ctx->prepared)
+ return 0;
+
+ ret = sofef00_panel_off(ctx);
+ if (ret < 0)
+ dev_err(dev, "Failed to un-initialize panel: %d\n", ret);
+
+ regulator_disable(ctx->supply);
+
+ ctx->prepared = false;
+ return 0;
+}
+
+static const struct drm_display_mode enchilada_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,
+};
+
+static const struct drm_display_mode fajita_panel_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 sofef00_panel_get_modes(struct drm_panel *panel, struct drm_connector *connector)
+{
+ struct drm_display_mode *mode;
+ struct sofef00_panel *ctx = to_sofef00_panel(panel);
+
+ mode = drm_mode_duplicate(connector->dev, ctx->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 sofef00_panel_panel_funcs = {
+ .prepare = sofef00_panel_prepare,
+ .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;
+
+ brightness = (u16)backlight_get_brightness(bl);
+ // This panel needs the high and low bytes swapped for the brightness value
+ brightness = __swab16(brightness);
+
+ err = mipi_dsi_dcs_set_display_brightness(dsi, brightness);
+ if (err < 0)
+ return err;
+
+ 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 = 1023,
+ .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_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->mode = of_device_get_match_data(dev);
+
+ if (!ctx->mode) {
+ dev_err(dev, "Missing device mode\n");
+ return -ENODEV;
+ }
+
+ ctx->supply = devm_regulator_get(dev, "vddio");
+ if (IS_ERR(ctx->supply))
+ return dev_err_probe(dev, PTR_ERR(ctx->supply),
+ "Failed to get vddio regulator\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;
+
+ drm_panel_init(&ctx->panel, dev, &sofef00_panel_panel_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ 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[] = {
+ { // OnePlus 6 / enchilada
+ .compatible = "samsung,sofef00",
+ .data = &enchilada_panel_mode,
+ },
+ { // OnePlus 6T / fajita
+ .compatible = "samsung,s6e3fc2x01",
+ .data = &fajita_panel_mode,
+ },
+ { /* 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-oneplus6",
+ .of_match_table = sofef00_panel_of_match,
+ },
+};
+
+module_mipi_dsi_driver(sofef00_panel_driver);
+
+MODULE_AUTHOR("Caleb Connolly <caleb@connolly.tech>");
+MODULE_DESCRIPTION("DRM driver for Samsung AMOLED DSI panels found in OnePlus 6/6T phones");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/panel/panel-seiko-43wvf1g.c b/drivers/gpu/drm/panel/panel-seiko-43wvf1g.c
new file mode 100644
index 000000000..76160e5d4
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-seiko-43wvf1g.c
@@ -0,0 +1,345 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2017 NXP Semiconductors.
+ * Author: Marco Franchi <marco.franchi@nxp.com>
+ *
+ * Based on Panel Simple driver by Thierry Reding <treding@nvidia.com>
+ */
+
+#include <linux/delay.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 <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;
+ const struct display_timing *timings;
+ unsigned int num_timings;
+
+ 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
+ */
+ struct {
+ unsigned int width;
+ unsigned int height;
+ } size;
+
+ u32 bus_format;
+ u32 bus_flags;
+};
+
+struct seiko_panel {
+ struct drm_panel base;
+ bool prepared;
+ bool enabled;
+ const struct seiko_panel_desc *desc;
+ struct regulator *dvdd;
+ struct regulator *avdd;
+};
+
+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,
+ struct drm_connector *connector)
+{
+ 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(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++;
+ }
+
+ 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++;
+ }
+
+ 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;
+ if (panel->desc->bus_format)
+ drm_display_info_set_bus_formats(&connector->display_info,
+ &panel->desc->bus_format, 1);
+ connector->display_info.bus_flags = panel->desc->bus_flags;
+
+ return num;
+}
+
+static int seiko_panel_disable(struct drm_panel *panel)
+{
+ struct seiko_panel *p = to_seiko_panel(panel);
+
+ if (!p->enabled)
+ return 0;
+
+ 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;
+
+ regulator_disable(p->avdd);
+
+ /* Add a 100ms delay as per the panel datasheet */
+ msleep(100);
+
+ regulator_disable(p->dvdd);
+
+ p->prepared = false;
+
+ return 0;
+}
+
+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);
+ return err;
+ }
+
+ /* Add a 100ms delay as per the panel datasheet */
+ msleep(100);
+
+ err = regulator_enable(p->avdd);
+ if (err < 0) {
+ dev_err(panel->dev, "failed to enable avdd: %d\n", err);
+ goto disable_dvdd;
+ }
+
+ p->prepared = true;
+
+ return 0;
+
+disable_dvdd:
+ regulator_disable(p->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;
+
+ p->enabled = true;
+
+ return 0;
+}
+
+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, connector);
+}
+
+static int seiko_panel_get_timings(struct drm_panel *panel,
+ unsigned int num_timings,
+ struct display_timing *timings)
+{
+ struct seiko_panel *p = to_seiko_panel(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 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,
+};
+
+static int seiko_panel_probe(struct device *dev,
+ const struct seiko_panel_desc *desc)
+{
+ struct seiko_panel *panel;
+ int err;
+
+ panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL);
+ if (!panel)
+ return -ENOMEM;
+
+ panel->enabled = false;
+ panel->prepared = false;
+ panel->desc = desc;
+
+ 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);
+
+ drm_panel_init(&panel->base, dev, &seiko_panel_funcs,
+ DRM_MODE_CONNECTOR_DPI);
+
+ 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)
+{
+ struct seiko_panel *panel = platform_get_drvdata(pdev);
+
+ drm_panel_remove(&panel->base);
+ drm_panel_disable(&panel->base);
+
+ return 0;
+}
+
+static void seiko_panel_shutdown(struct platform_device *pdev)
+{
+ struct seiko_panel *panel = platform_get_drvdata(pdev);
+
+ drm_panel_disable(&panel->base);
+}
+
+static const struct display_timing seiko_43wvf1g_timing = {
+ .pixelclock = { 33500000, 33500000, 33500000 },
+ .hactive = { 800, 800, 800 },
+ .hfront_porch = { 164, 164, 164 },
+ .hback_porch = { 89, 89, 89 },
+ .hsync_len = { 10, 10, 10 },
+ .vactive = { 480, 480, 480 },
+ .vfront_porch = { 10, 10, 10 },
+ .vback_porch = { 23, 23, 23 },
+ .vsync_len = { 10, 10, 10 },
+ .flags = DISPLAY_FLAGS_DE_LOW,
+};
+
+static const struct seiko_panel_desc seiko_43wvf1g = {
+ .timings = &seiko_43wvf1g_timing,
+ .num_timings = 1,
+ .bpc = 8,
+ .size = {
+ .width = 93,
+ .height = 57,
+ },
+ .bus_format = MEDIA_BUS_FMT_RGB888_1X24,
+ .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE,
+};
+
+static const struct of_device_id platform_of_match[] = {
+ {
+ .compatible = "sii,43wvf1g",
+ .data = &seiko_43wvf1g,
+ }, {
+ /* sentinel */
+ }
+};
+MODULE_DEVICE_TABLE(of, platform_of_match);
+
+static int seiko_panel_platform_probe(struct platform_device *pdev)
+{
+ const struct of_device_id *id;
+
+ id = of_match_node(platform_of_match, pdev->dev.of_node);
+ if (!id)
+ return -ENODEV;
+
+ return seiko_panel_probe(&pdev->dev, id->data);
+}
+
+static struct platform_driver seiko_panel_platform_driver = {
+ .driver = {
+ .name = "seiko_panel",
+ .of_match_table = platform_of_match,
+ },
+ .probe = seiko_panel_platform_probe,
+ .remove = seiko_panel_remove,
+ .shutdown = seiko_panel_shutdown,
+};
+module_platform_driver(seiko_panel_platform_driver);
+
+MODULE_AUTHOR("Marco Franchi <marco.franchi@nxp.com>");
+MODULE_DESCRIPTION("Seiko 43WVF1G panel driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c b/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c
new file mode 100644
index 000000000..14851408a
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c
@@ -0,0 +1,440 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2014 NVIDIA Corporation
+ */
+
+#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_panel.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 regulator *supply;
+
+ bool prepared;
+ bool enabled;
+
+ 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, base);
+}
+
+static void sharp_wait_frames(struct sharp_panel *sharp, unsigned int frames)
+{
+ unsigned int refresh = drm_mode_vrefresh(sharp->mode);
+
+ if (WARN_ON(frames > refresh))
+ return;
+
+ msleep(1000 / (refresh / frames));
+}
+
+static int sharp_panel_write(struct sharp_panel *sharp, u16 offset, u8 value)
+{
+ u8 payload[3] = { offset >> 8, offset & 0xff, value };
+ struct mipi_dsi_device *dsi = sharp->link1;
+ ssize_t err;
+
+ err = mipi_dsi_generic_write(dsi, payload, sizeof(payload));
+ if (err < 0) {
+ dev_err(&dsi->dev, "failed to write %02x to %04x: %zd\n",
+ value, offset, err);
+ return err;
+ }
+
+ err = mipi_dsi_dcs_nop(dsi);
+ if (err < 0) {
+ dev_err(&dsi->dev, "failed to send DCS nop: %zd\n", err);
+ return err;
+ }
+
+ usleep_range(10, 20);
+
+ return 0;
+}
+
+static __maybe_unused int sharp_panel_read(struct sharp_panel *sharp,
+ u16 offset, u8 *value)
+{
+ ssize_t err;
+
+ cpu_to_be16s(&offset);
+
+ err = mipi_dsi_generic_read(sharp->link1, &offset, sizeof(offset),
+ value, sizeof(*value));
+ if (err < 0)
+ dev_err(&sharp->link1->dev, "failed to read from %04x: %zd\n",
+ offset, err);
+
+ return err;
+}
+
+static int sharp_panel_disable(struct drm_panel *panel)
+{
+ struct sharp_panel *sharp = to_sharp_panel(panel);
+
+ if (!sharp->enabled)
+ return 0;
+
+ 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);
+ if (err < 0)
+ dev_err(panel->dev, "failed to set display off: %d\n", err);
+
+ err = mipi_dsi_dcs_enter_sleep_mode(sharp->link1);
+ if (err < 0)
+ dev_err(panel->dev, "failed to enter sleep mode: %d\n", err);
+
+ msleep(120);
+
+ regulator_disable(sharp->supply);
+
+ sharp->prepared = false;
+
+ return 0;
+}
+
+static int sharp_setup_symmetrical_split(struct mipi_dsi_device *left,
+ struct mipi_dsi_device *right,
+ const struct drm_display_mode *mode)
+{
+ int err;
+
+ err = mipi_dsi_dcs_set_column_address(left, 0, mode->hdisplay / 2 - 1);
+ if (err < 0) {
+ dev_err(&left->dev, "failed to set column address: %d\n", err);
+ return err;
+ }
+
+ err = mipi_dsi_dcs_set_page_address(left, 0, mode->vdisplay - 1);
+ if (err < 0) {
+ dev_err(&left->dev, "failed to set page address: %d\n", err);
+ return err;
+ }
+
+ err = mipi_dsi_dcs_set_column_address(right, mode->hdisplay / 2,
+ mode->hdisplay - 1);
+ if (err < 0) {
+ dev_err(&right->dev, "failed to set column address: %d\n", err);
+ return err;
+ }
+
+ err = mipi_dsi_dcs_set_page_address(right, 0, mode->vdisplay - 1);
+ if (err < 0) {
+ dev_err(&right->dev, "failed to set page address: %d\n", err);
+ return err;
+ }
+
+ return 0;
+}
+
+static int sharp_panel_prepare(struct drm_panel *panel)
+{
+ struct sharp_panel *sharp = to_sharp_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;
+
+ /*
+ * According to the datasheet, the panel needs around 10 ms to fully
+ * power up. At least another 120 ms is required before exiting sleep
+ * mode to make sure the panel is ready. Throw in another 20 ms for
+ * good measure.
+ */
+ msleep(150);
+
+ err = mipi_dsi_dcs_exit_sleep_mode(sharp->link1);
+ if (err < 0) {
+ dev_err(panel->dev, "failed to exit sleep mode: %d\n", err);
+ goto poweroff;
+ }
+
+ /*
+ * The MIPI DCS specification mandates this delay only between the
+ * exit_sleep_mode and enter_sleep_mode commands, so it isn't strictly
+ * necessary here.
+ */
+ /*
+ msleep(120);
+ */
+
+ /* set left-right mode */
+ err = sharp_panel_write(sharp, 0x1000, 0x2a);
+ if (err < 0) {
+ dev_err(panel->dev, "failed to set left-right mode: %d\n", err);
+ goto poweroff;
+ }
+
+ /* enable command mode */
+ err = sharp_panel_write(sharp, 0x1001, 0x01);
+ if (err < 0) {
+ dev_err(panel->dev, "failed to enable command mode: %d\n", err);
+ goto poweroff;
+ }
+
+ err = mipi_dsi_dcs_set_pixel_format(sharp->link1, format);
+ if (err < 0) {
+ dev_err(panel->dev, "failed to set pixel format: %d\n", err);
+ goto poweroff;
+ }
+
+ /*
+ * 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.
+ */
+ err = sharp_setup_symmetrical_split(sharp->link1, sharp->link2,
+ sharp->mode);
+ if (err < 0) {
+ dev_err(panel->dev, "failed to set up symmetrical split: %d\n",
+ err);
+ goto poweroff;
+ }
+
+ err = mipi_dsi_dcs_set_display_on(sharp->link1);
+ if (err < 0) {
+ dev_err(panel->dev, "failed to set display on: %d\n", err);
+ goto poweroff;
+ }
+
+ sharp->prepared = true;
+
+ /* wait for 6 frames before continuing */
+ sharp_wait_frames(sharp, 6);
+
+ return 0;
+
+poweroff:
+ regulator_disable(sharp->supply);
+ return err;
+}
+
+static int sharp_panel_enable(struct drm_panel *panel)
+{
+ struct sharp_panel *sharp = to_sharp_panel(panel);
+
+ if (sharp->enabled)
+ return 0;
+
+ sharp->enabled = true;
+
+ return 0;
+}
+
+static const struct drm_display_mode default_mode = {
+ .clock = 278000,
+ .hdisplay = 2560,
+ .hsync_start = 2560 + 128,
+ .hsync_end = 2560 + 128 + 64,
+ .htotal = 2560 + 128 + 64 + 64,
+ .vdisplay = 1600,
+ .vsync_start = 1600 + 4,
+ .vsync_end = 1600 + 4 + 8,
+ .vtotal = 1600 + 4 + 8 + 32,
+};
+
+static int sharp_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 = 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,
+};
+
+static const struct of_device_id sharp_of_match[] = {
+ { .compatible = "sharp,lq101r1sx01", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, sharp_of_match);
+
+static int sharp_panel_add(struct sharp_panel *sharp)
+{
+ int ret;
+
+ sharp->mode = &default_mode;
+
+ sharp->supply = devm_regulator_get(&sharp->link1->dev, "power");
+ if (IS_ERR(sharp->supply))
+ return PTR_ERR(sharp->supply);
+
+ drm_panel_init(&sharp->base, &sharp->link1->dev, &sharp_panel_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ ret = drm_panel_of_backlight(&sharp->base);
+ if (ret)
+ return ret;
+
+ drm_panel_add(&sharp->base);
+
+ return 0;
+}
+
+static void sharp_panel_del(struct sharp_panel *sharp)
+{
+ if (sharp->base.dev)
+ drm_panel_remove(&sharp->base);
+
+ if (sharp->link2)
+ put_device(&sharp->link2->dev);
+}
+
+static int sharp_panel_probe(struct mipi_dsi_device *dsi)
+{
+ struct mipi_dsi_device *secondary = NULL;
+ struct sharp_panel *sharp;
+ struct device_node *np;
+ int err;
+
+ dsi->lanes = 4;
+ dsi->format = MIPI_DSI_FMT_RGB888;
+ dsi->mode_flags = MIPI_DSI_MODE_LPM;
+
+ /* 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) {
+ sharp = devm_kzalloc(&dsi->dev, sizeof(*sharp), GFP_KERNEL);
+ if (!sharp) {
+ put_device(&secondary->dev);
+ return -ENOMEM;
+ }
+
+ mipi_dsi_set_drvdata(dsi, sharp);
+
+ sharp->link2 = secondary;
+ sharp->link1 = dsi;
+
+ err = sharp_panel_add(sharp);
+ if (err < 0) {
+ put_device(&secondary->dev);
+ return err;
+ }
+ }
+
+ err = mipi_dsi_attach(dsi);
+ if (err < 0) {
+ if (secondary)
+ sharp_panel_del(sharp);
+
+ return err;
+ }
+
+ return 0;
+}
+
+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;
+ }
+
+ err = drm_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);
+}
+
+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;
+
+ drm_panel_disable(&sharp->base);
+}
+
+static struct mipi_dsi_driver sharp_panel_driver = {
+ .driver = {
+ .name = "panel-sharp-lq101r1sx01",
+ .of_match_table = sharp_of_match,
+ },
+ .probe = sharp_panel_probe,
+ .remove = sharp_panel_remove,
+ .shutdown = sharp_panel_shutdown,
+};
+module_mipi_dsi_driver(sharp_panel_driver);
+
+MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>");
+MODULE_DESCRIPTION("Sharp LQ101R1SX01 panel driver");
+MODULE_LICENSE("GPL v2");
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 000000000..a07d0f6c3
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-sharp-ls037v7dw01.c
@@ -0,0 +1,223 @@
+// 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_kzalloc(&pdev->dev, sizeof(*lcd), GFP_KERNEL);
+ if (!lcd)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, lcd);
+ lcd->pdev = pdev;
+
+ lcd->vdd = devm_regulator_get(&pdev->dev, "envdd");
+ if (IS_ERR(lcd->vdd))
+ 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_init(&lcd->panel, &pdev->dev, &ls037v7dw01_funcs,
+ DRM_MODE_CONNECTOR_DPI);
+
+ drm_panel_add(&lcd->panel);
+
+ return 0;
+}
+
+static int ls037v7dw01_remove(struct platform_device *pdev)
+{
+ struct ls037v7dw01_panel *lcd = platform_get_drvdata(pdev);
+
+ drm_panel_remove(&lcd->panel);
+ drm_panel_disable(&lcd->panel);
+ drm_panel_unprepare(&lcd->panel);
+
+ return 0;
+}
+
+static const struct of_device_id ls037v7dw01_of_match[] = {
+ { .compatible = "sharp,ls037v7dw01", },
+ { /* sentinel */ },
+};
+
+MODULE_DEVICE_TABLE(of, ls037v7dw01_of_match);
+
+static struct platform_driver ls037v7dw01_driver = {
+ .probe = ls037v7dw01_probe,
+ .remove = ls037v7dw01_remove,
+ .driver = {
+ .name = "panel-sharp-ls037v7dw01",
+ .of_match_table = ls037v7dw01_of_match,
+ },
+};
+
+module_platform_driver(ls037v7dw01_driver);
+
+MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
+MODULE_DESCRIPTION("Sharp LS037V7DW01 Panel Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/panel/panel-sharp-ls043t1le01.c b/drivers/gpu/drm/panel/panel-sharp-ls043t1le01.c
new file mode 100644
index 000000000..ef148504c
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-sharp-ls043t1le01.c
@@ -0,0 +1,351 @@
+// 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>
+ */
+
+#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_panel.h>
+
+struct sharp_nt_panel {
+ struct drm_panel base;
+ struct mipi_dsi_device *dsi;
+
+ 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)
+{
+ return container_of(panel, struct sharp_nt_panel, base);
+}
+
+static int sharp_nt_panel_init(struct sharp_nt_panel *sharp_nt)
+{
+ struct mipi_dsi_device *dsi = sharp_nt->dsi;
+ int ret;
+
+ dsi->mode_flags |= MIPI_DSI_MODE_LPM;
+
+ ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
+ if (ret < 0)
+ return ret;
+
+ msleep(120);
+
+ /* Novatek two-lane operation */
+ ret = mipi_dsi_dcs_write(dsi, 0xae, (u8[]){ 0x03 }, 1);
+ if (ret < 0)
+ return ret;
+
+ /* 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;
+
+ return 0;
+}
+
+static int sharp_nt_panel_on(struct sharp_nt_panel *sharp_nt)
+{
+ struct mipi_dsi_device *dsi = sharp_nt->dsi;
+ int ret;
+
+ dsi->mode_flags |= MIPI_DSI_MODE_LPM;
+
+ ret = mipi_dsi_dcs_set_display_on(dsi);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int sharp_nt_panel_off(struct sharp_nt_panel *sharp_nt)
+{
+ struct mipi_dsi_device *dsi = sharp_nt->dsi;
+ int ret;
+
+ 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;
+
+ sharp_nt->enabled = false;
+
+ return 0;
+}
+
+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);
+ return ret;
+ }
+
+ regulator_disable(sharp_nt->supply);
+ if (sharp_nt->reset_gpio)
+ gpiod_set_value(sharp_nt->reset_gpio, 0);
+
+ sharp_nt->prepared = false;
+
+ return 0;
+}
+
+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;
+
+ msleep(20);
+
+ if (sharp_nt->reset_gpio) {
+ gpiod_set_value(sharp_nt->reset_gpio, 1);
+ msleep(1);
+ gpiod_set_value(sharp_nt->reset_gpio, 0);
+ msleep(1);
+ gpiod_set_value(sharp_nt->reset_gpio, 1);
+ msleep(10);
+ }
+
+ ret = sharp_nt_panel_init(sharp_nt);
+ if (ret < 0) {
+ dev_err(panel->dev, "failed to init panel: %d\n", ret);
+ goto poweroff;
+ }
+
+ ret = sharp_nt_panel_on(sharp_nt);
+ if (ret < 0) {
+ dev_err(panel->dev, "failed to set panel on: %d\n", ret);
+ goto poweroff;
+ }
+
+ sharp_nt->prepared = true;
+
+ return 0;
+
+poweroff:
+ regulator_disable(sharp_nt->supply);
+ if (sharp_nt->reset_gpio)
+ gpiod_set_value(sharp_nt->reset_gpio, 0);
+ 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;
+
+ sharp_nt->enabled = true;
+
+ return 0;
+}
+
+static const struct drm_display_mode default_mode = {
+ .clock = (540 + 48 + 32 + 80) * (960 + 3 + 10 + 15) * 60 / 1000,
+ .hdisplay = 540,
+ .hsync_start = 540 + 48,
+ .hsync_end = 540 + 48 + 32,
+ .htotal = 540 + 48 + 32 + 80,
+ .vdisplay = 960,
+ .vsync_start = 960 + 3,
+ .vsync_end = 960 + 3 + 10,
+ .vtotal = 960 + 3 + 10 + 15,
+};
+
+static int sharp_nt_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 = 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;
+ int ret;
+
+ sharp_nt->mode = &default_mode;
+
+ sharp_nt->supply = devm_regulator_get(dev, "avdd");
+ if (IS_ERR(sharp_nt->supply))
+ return PTR_ERR(sharp_nt->supply);
+
+ sharp_nt->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(sharp_nt->reset_gpio)) {
+ dev_err(dev, "cannot get reset-gpios %ld\n",
+ PTR_ERR(sharp_nt->reset_gpio));
+ sharp_nt->reset_gpio = NULL;
+ } else {
+ gpiod_set_value(sharp_nt->reset_gpio, 0);
+ }
+
+ drm_panel_init(&sharp_nt->base, &sharp_nt->dsi->dev,
+ &sharp_nt_panel_funcs, DRM_MODE_CONNECTOR_DSI);
+
+ ret = drm_panel_of_backlight(&sharp_nt->base);
+ if (ret)
+ return ret;
+
+ drm_panel_add(&sharp_nt->base);
+
+ return 0;
+}
+
+static void sharp_nt_panel_del(struct sharp_nt_panel *sharp_nt)
+{
+ if (sharp_nt->base.dev)
+ drm_panel_remove(&sharp_nt->base);
+}
+
+static int sharp_nt_panel_probe(struct mipi_dsi_device *dsi)
+{
+ struct sharp_nt_panel *sharp_nt;
+ int ret;
+
+ 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_NO_EOT_PACKET;
+
+ sharp_nt = devm_kzalloc(&dsi->dev, sizeof(*sharp_nt), GFP_KERNEL);
+ if (!sharp_nt)
+ return -ENOMEM;
+
+ mipi_dsi_set_drvdata(dsi, sharp_nt);
+
+ sharp_nt->dsi = dsi;
+
+ ret = sharp_nt_panel_add(sharp_nt);
+ if (ret < 0)
+ return ret;
+
+ ret = mipi_dsi_attach(dsi);
+ if (ret < 0) {
+ sharp_nt_panel_del(sharp_nt);
+ return ret;
+ }
+
+ return 0;
+}
+
+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 = drm_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);
+}
+
+static void sharp_nt_panel_shutdown(struct mipi_dsi_device *dsi)
+{
+ struct sharp_nt_panel *sharp_nt = mipi_dsi_get_drvdata(dsi);
+
+ drm_panel_disable(&sharp_nt->base);
+}
+
+static const struct of_device_id sharp_nt_of_match[] = {
+ { .compatible = "sharp,ls043t1le01-qhd", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, sharp_nt_of_match);
+
+static struct mipi_dsi_driver sharp_nt_panel_driver = {
+ .driver = {
+ .name = "panel-sharp-ls043t1le01-qhd",
+ .of_match_table = sharp_nt_of_match,
+ },
+ .probe = sharp_nt_panel_probe,
+ .remove = sharp_nt_panel_remove,
+ .shutdown = sharp_nt_panel_shutdown,
+};
+module_mipi_dsi_driver(sharp_nt_panel_driver);
+
+MODULE_AUTHOR("Werner Johansson <werner.johansson@sonymobile.com>");
+MODULE_DESCRIPTION("Sharp LS043T1LE01 NT35565-based qHD (540x960) video mode panel driver");
+MODULE_LICENSE("GPL v2");
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 000000000..8a4e0c1fe
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-sharp-ls060t1sx01.c
@@ -0,0 +1,331 @@
+// 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;
+ bool prepared;
+};
+
+static inline struct sharp_ls060 *to_sharp_ls060(struct drm_panel *panel)
+{
+ return container_of(panel, struct sharp_ls060, panel);
+}
+
+#define dsi_dcs_write_seq(dsi, seq...) ({ \
+ static const u8 d[] = { seq }; \
+ \
+ mipi_dsi_dcs_write_buffer(dsi, d, ARRAY_SIZE(d)); \
+ })
+
+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 device *dev = &dsi->dev;
+ int ret;
+
+ dsi->mode_flags |= MIPI_DSI_MODE_LPM;
+
+ ret = dsi_dcs_write_seq(dsi, 0xbb, 0x13);
+ if (ret < 0) {
+ dev_err(dev, "Failed to send command: %d\n", ret);
+ return ret;
+ }
+
+ ret = dsi_dcs_write_seq(dsi, MIPI_DCS_WRITE_MEMORY_START);
+ if (ret < 0) {
+ dev_err(dev, "Failed to send command: %d\n", ret);
+ return ret;
+ }
+
+ ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to exit sleep mode: %d\n", ret);
+ return ret;
+ }
+ msleep(120);
+
+ ret = mipi_dsi_dcs_set_display_on(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set display on: %d\n", ret);
+ return ret;
+ }
+ msleep(50);
+
+ return 0;
+}
+
+static int sharp_ls060_off(struct sharp_ls060 *ctx)
+{
+ struct mipi_dsi_device *dsi = ctx->dsi;
+ struct device *dev = &dsi->dev;
+ int ret;
+
+ dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
+
+ 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(2000, 3000);
+
+ ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to enter sleep mode: %d\n", ret);
+ return ret;
+ }
+ msleep(121);
+
+ return 0;
+}
+
+static int sharp_ls060_prepare(struct drm_panel *panel)
+{
+ struct sharp_ls060 *ctx = to_sharp_ls060(panel);
+ struct device *dev = &ctx->dsi->dev;
+ int ret;
+
+ if (ctx->prepared)
+ return 0;
+
+ 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) {
+ dev_err(dev, "Failed to initialize panel: %d\n", ret);
+ goto err_on;
+ }
+
+ ctx->prepared = true;
+
+ 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);
+ struct device *dev = &ctx->dsi->dev;
+ int ret;
+
+ if (!ctx->prepared)
+ return 0;
+
+ ret = sharp_ls060_off(ctx);
+ if (ret < 0)
+ dev_err(dev, "Failed to un-initialize panel: %d\n", ret);
+
+ 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);
+
+ ctx->prepared = false;
+ 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_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ 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;
+
+ drm_panel_init(&ctx->panel, dev, &sharp_ls060_panel_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ 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
new file mode 100644
index 000000000..b714ee1bc
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-simple.c
@@ -0,0 +1,4703 @@
+/*
+ * 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/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/media-bus-format.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/drm_crtc.h>
+#include <drm/drm_device.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_panel.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;
+
+ /** @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 {
+ /**
+ * @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;
+
+ /**
+ * @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_simple {
+ struct drm_panel base;
+ bool enabled;
+
+ bool prepared;
+
+ ktime_t prepared_time;
+ ktime_t unprepared_time;
+
+ const struct panel_desc *desc;
+
+ struct regulator *supply;
+ struct i2c_adapter *ddc;
+
+ struct gpio_desc *enable_gpio;
+
+ struct edid *edid;
+
+ struct drm_display_mode override_mode;
+
+ enum drm_panel_orientation orientation;
+};
+
+static inline struct panel_simple *to_panel_simple(struct drm_panel *panel)
+{
+ return container_of(panel, struct panel_simple, base);
+}
+
+static unsigned int panel_simple_get_timings_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_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_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(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_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;
+ if (panel->desc->bus_format)
+ drm_display_info_set_bus_formats(&connector->display_info,
+ &panel->desc->bus_format, 1);
+ connector->display_info.bus_flags = panel->desc->bus_flags;
+
+ return num;
+}
+
+static void panel_simple_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();
+
+ 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->enabled)
+ return 0;
+
+ if (p->desc->delay.disable)
+ msleep(p->desc->delay.disable);
+
+ p->enabled = false;
+
+ return 0;
+}
+
+static int panel_simple_suspend(struct device *dev)
+{
+ 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();
+
+ kfree(p->edid);
+ p->edid = NULL;
+
+ return 0;
+}
+
+static int panel_simple_unprepare(struct drm_panel *panel)
+{
+ struct panel_simple *p = to_panel_simple(panel);
+ int ret;
+
+ /* Unpreparing when already unprepared is a no-op */
+ if (!p->prepared)
+ return 0;
+
+ pm_runtime_mark_last_busy(panel->dev);
+ ret = pm_runtime_put_autosuspend(panel->dev);
+ if (ret < 0)
+ return ret;
+ p->prepared = false;
+
+ return 0;
+}
+
+static int panel_simple_resume(struct device *dev)
+{
+ struct panel_simple *p = dev_get_drvdata(dev);
+ int err;
+
+ panel_simple_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);
+
+ if (p->desc->delay.prepare)
+ msleep(p->desc->delay.prepare);
+
+ p->prepared_time = ktime_get();
+
+ return 0;
+}
+
+static int panel_simple_prepare(struct drm_panel *panel)
+{
+ struct panel_simple *p = to_panel_simple(panel);
+ int ret;
+
+ /* Preparing when already prepared is a no-op */
+ if (p->prepared)
+ return 0;
+
+ ret = pm_runtime_get_sync(panel->dev);
+ if (ret < 0) {
+ pm_runtime_put_autosuspend(panel->dev);
+ return ret;
+ }
+
+ p->prepared = true;
+
+ return 0;
+}
+
+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);
+
+ p->enabled = true;
+
+ return 0;
+}
+
+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) {
+ pm_runtime_get_sync(panel->dev);
+
+ if (!p->edid)
+ p->edid = drm_get_edid(connector, p->ddc);
+
+ if (p->edid)
+ num += drm_add_edid_modes(connector, p->edid);
+
+ pm_runtime_mark_last_busy(panel->dev);
+ pm_runtime_put_autosuspend(panel->dev);
+ }
+
+ /* add hard-coded panel modes */
+ 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;
+}
+
+static int panel_simple_get_timings(struct drm_panel *panel,
+ unsigned int num_timings,
+ struct display_timing *timings)
+{
+ struct panel_simple *p = to_panel_simple(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_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 struct panel_desc panel_dpi;
+
+static int panel_dpi_probe(struct device *dev,
+ struct panel_simple *panel)
+{
+ 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 -ENOMEM;
+
+ timing = devm_kzalloc(dev, sizeof(*timing), GFP_KERNEL);
+ if (!timing)
+ return -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 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;
+
+ panel->desc = desc;
+
+ return 0;
+}
+
+#define PANEL_SIMPLE_BOUNDS_CHECK(to_check, bounds, field) \
+ (to_check->field.typ >= bounds->field.min && \
+ to_check->field.typ <= bounds->field.max)
+static void panel_simple_parse_panel_timing_node(struct device *dev,
+ struct panel_simple *panel,
+ const struct display_timing *ot)
+{
+ const struct panel_desc *desc = panel->desc;
+ struct videomode vm;
+ unsigned int i;
+
+ if (WARN_ON(desc->num_modes)) {
+ dev_err(dev, "Reject override mode: panel has a fixed mode\n");
+ return;
+ }
+ if (WARN_ON(!desc->num_timings)) {
+ dev_err(dev, "Reject override mode: no timings specified\n");
+ return;
+ }
+
+ for (i = 0; i < panel->desc->num_timings; i++) {
+ const struct display_timing *dt = &panel->desc->timings[i];
+
+ if (!PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, hactive) ||
+ !PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, hfront_porch) ||
+ !PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, hback_porch) ||
+ !PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, hsync_len) ||
+ !PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, vactive) ||
+ !PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, vfront_porch) ||
+ !PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, vback_porch) ||
+ !PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, vsync_len))
+ continue;
+
+ if (ot->flags != dt->flags)
+ continue;
+
+ videomode_from_timing(ot, &vm);
+ drm_display_mode_from_videomode(&vm, &panel->override_mode);
+ panel->override_mode.type |= DRM_MODE_TYPE_DRIVER |
+ DRM_MODE_TYPE_PREFERRED;
+ break;
+ }
+
+ if (WARN_ON(!panel->override_mode.type))
+ dev_err(dev, "Reject override mode: No display_timing found\n");
+}
+
+static int panel_simple_probe(struct device *dev, const struct panel_desc *desc)
+{
+ struct 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;
+
+ panel->enabled = false;
+ panel->prepared_time = 0;
+ panel->desc = desc;
+
+ 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;
+ }
+
+ if (desc == &panel_dpi) {
+ /* Handle the generic panel-dpi binding */
+ err = panel_dpi_probe(dev, panel);
+ if (err)
+ goto free_ddc;
+ desc = panel->desc;
+ } else {
+ if (!of_get_display_timing(dev->of_node, "panel-timing", &dt))
+ panel_simple_parse_panel_timing_node(dev, panel, &dt);
+ }
+
+ 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);
+
+ /*
+ * 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);
+
+ drm_panel_init(&panel->base, dev, &panel_simple_funcs, connector_type);
+
+ 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 0;
+
+disable_pm_runtime:
+ pm_runtime_dont_use_autosuspend(dev);
+ pm_runtime_disable(dev);
+free_ddc:
+ if (panel->ddc)
+ put_device(&panel->ddc->dev);
+
+ return err;
+}
+
+static void panel_simple_remove(struct device *dev)
+{
+ struct panel_simple *panel = dev_get_drvdata(dev);
+
+ drm_panel_remove(&panel->base);
+ drm_panel_disable(&panel->base);
+ drm_panel_unprepare(&panel->base);
+
+ pm_runtime_dont_use_autosuspend(dev);
+ pm_runtime_disable(dev);
+ if (panel->ddc)
+ put_device(&panel->ddc->dev);
+}
+
+static void panel_simple_shutdown(struct device *dev)
+{
+ struct panel_simple *panel = dev_get_drvdata(dev);
+
+ drm_panel_disable(&panel->base);
+ drm_panel_unprepare(&panel->base);
+}
+
+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,
+};
+
+static const struct panel_desc ampire_am_1280800n3tzqw_t00h = {
+ .modes = &ampire_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,
+ .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_PHSYNC | DRM_MODE_FLAG_PVSYNC,
+};
+
+static const struct panel_desc ampire_am_480272h3tmqw_t01h = {
+ .modes = &ampire_am_480272h3tmqw_t01h_mode,
+ .num_modes = 1,
+ .bpc = 8,
+ .size = {
+ .width = 99,
+ .height = 58,
+ },
+ .bus_format = MEDIA_BUS_FMT_RGB888_1X24,
+};
+
+static const struct drm_display_mode ampire_am800480r3tmqwa1h_mode = {
+ .clock = 33333,
+ .hdisplay = 800,
+ .hsync_start = 800 + 0,
+ .hsync_end = 800 + 0 + 255,
+ .htotal = 800 + 0 + 255 + 0,
+ .vdisplay = 480,
+ .vsync_start = 480 + 2,
+ .vsync_end = 480 + 2 + 45,
+ .vtotal = 480 + 2 + 45 + 0,
+ .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC,
+};
+
+static const struct panel_desc ampire_am800480r3tmqwa1h = {
+ .modes = &ampire_am800480r3tmqwa1h_mode,
+ .num_modes = 1,
+ .bpc = 6,
+ .size = {
+ .width = 152,
+ .height = 91,
+ },
+ .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 = &ampire_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,
+ .hsync_start = 1024 + 156,
+ .hsync_end = 1024 + 156 + 8,
+ .htotal = 1024 + 156 + 8 + 156,
+ .vdisplay = 600,
+ .vsync_start = 600 + 16,
+ .vsync_end = 600 + 16 + 6,
+ .vtotal = 600 + 16 + 6 + 16,
+};
+
+static const struct panel_desc auo_b101aw03 = {
+ .modes = &auo_b101aw03_mode,
+ .num_modes = 1,
+ .bpc = 6,
+ .size = {
+ .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 auo_b101xtn01_mode = {
+ .clock = 72000,
+ .hdisplay = 1366,
+ .hsync_start = 1366 + 20,
+ .hsync_end = 1366 + 20 + 70,
+ .htotal = 1366 + 20 + 70,
+ .vdisplay = 768,
+ .vsync_start = 768 + 14,
+ .vsync_end = 768 + 14 + 42,
+ .vtotal = 768 + 14 + 42,
+ .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC,
+};
+
+static const struct panel_desc auo_b101xtn01 = {
+ .modes = &auo_b101xtn01_mode,
+ .num_modes = 1,
+ .bpc = 6,
+ .size = {
+ .width = 223,
+ .height = 125,
+ },
+};
+
+static const struct display_timing auo_g070vvn01_timings = {
+ .pixelclock = { 33300000, 34209000, 45000000 },
+ .hactive = { 800, 800, 800 },
+ .hfront_porch = { 20, 40, 200 },
+ .hback_porch = { 87, 40, 1 },
+ .hsync_len = { 1, 48, 87 },
+ .vactive = { 480, 480, 480 },
+ .vfront_porch = { 5, 13, 200 },
+ .vback_porch = { 31, 31, 29 },
+ .vsync_len = { 1, 1, 3 },
+};
+
+static const struct panel_desc auo_g070vvn01 = {
+ .timings = &auo_g070vvn01_timings,
+ .num_timings = 1,
+ .bpc = 8,
+ .size = {
+ .width = 152,
+ .height = 91,
+ },
+ .delay = {
+ .prepare = 200,
+ .enable = 50,
+ .disable = 50,
+ .unprepare = 1000,
+ },
+};
+
+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,
+};
+
+static const struct panel_desc auo_g101evn010 = {
+ .modes = &auo_g101evn010_mode,
+ .num_modes = 1,
+ .bpc = 6,
+ .size = {
+ .width = 216,
+ .height = 135,
+ },
+ .bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG,
+ .connector_type = DRM_MODE_CONNECTOR_LVDS,
+};
+
+static const struct drm_display_mode auo_g104sn02_mode = {
+ .clock = 40000,
+ .hdisplay = 800,
+ .hsync_start = 800 + 40,
+ .hsync_end = 800 + 40 + 216,
+ .htotal = 800 + 40 + 216 + 128,
+ .vdisplay = 600,
+ .vsync_start = 600 + 10,
+ .vsync_end = 600 + 10 + 35,
+ .vtotal = 600 + 10 + 35 + 2,
+};
+
+static const struct panel_desc auo_g104sn02 = {
+ .modes = &auo_g104sn02_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 = {
+ .pixelclock = { 134000000, 141200000, 149000000 },
+ .hactive = { 1920, 1920, 1920 },
+ .hfront_porch = { 39, 58, 77 },
+ .hback_porch = { 59, 88, 117 },
+ .hsync_len = { 28, 42, 56 },
+ .vactive = { 1080, 1080, 1080 },
+ .vfront_porch = { 3, 8, 11 },
+ .vback_porch = { 5, 14, 19 },
+ .vsync_len = { 4, 14, 19 },
+};
+
+static const struct panel_desc auo_g133han01 = {
+ .timings = &auo_g133han01_timings,
+ .num_timings = 1,
+ .bpc = 8,
+ .size = {
+ .width = 293,
+ .height = 165,
+ },
+ .delay = {
+ .prepare = 200,
+ .enable = 50,
+ .disable = 50,
+ .unprepare = 1000,
+ },
+ .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA,
+ .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 = { 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 },
+ .vsync_len = { 2, 5, 20 },
+};
+
+static const struct panel_desc auo_g185han01 = {
+ .timings = &auo_g185han01_timings,
+ .num_timings = 1,
+ .bpc = 8,
+ .size = {
+ .width = 409,
+ .height = 230,
+ },
+ .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_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_p320hvn03_timings = {
+ .pixelclock = { 106000000, 148500000, 164000000 },
+ .hactive = { 1920, 1920, 1920 },
+ .hfront_porch = { 25, 50, 130 },
+ .hback_porch = { 25, 50, 130 },
+ .hsync_len = { 20, 40, 105 },
+ .vactive = { 1080, 1080, 1080 },
+ .vfront_porch = { 8, 17, 150 },
+ .vback_porch = { 8, 17, 150 },
+ .vsync_len = { 4, 11, 100 },
+};
+
+static const struct panel_desc auo_p320hvn03 = {
+ .timings = &auo_p320hvn03_timings,
+ .num_timings = 1,
+ .bpc = 8,
+ .size = {
+ .width = 698,
+ .height = 393,
+ },
+ .delay = {
+ .prepare = 1,
+ .enable = 450,
+ .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 = {
+ .clock = 148800,
+ .hdisplay = 1920,
+ .hsync_start = 1920 + 88,
+ .hsync_end = 1920 + 88 + 44,
+ .htotal = 1920 + 88 + 44 + 148,
+ .vdisplay = 1080,
+ .vsync_start = 1080 + 4,
+ .vsync_end = 1080 + 4 + 5,
+ .vtotal = 1080 + 4 + 5 + 36,
+};
+
+static const struct panel_desc auo_t215hvn01 = {
+ .modes = &auo_t215hvn01_mode,
+ .num_modes = 1,
+ .bpc = 8,
+ .size = {
+ .width = 430,
+ .height = 270,
+ },
+ .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 = {
+ .clock = 51200,
+ .hdisplay = 1024,
+ .hsync_start = 1024 + 160,
+ .hsync_end = 1024 + 160 + 4,
+ .htotal = 1024 + 160 + 4 + 156,
+ .vdisplay = 600,
+ .vsync_start = 600 + 17,
+ .vsync_end = 600 + 17 + 1,
+ .vtotal = 600 + 17 + 1 + 17,
+};
+
+static const struct panel_desc avic_tm070ddh03 = {
+ .modes = &avic_tm070ddh03_mode,
+ .num_modes = 1,
+ .bpc = 8,
+ .size = {
+ .width = 154,
+ .height = 90,
+ },
+ .delay = {
+ .prepare = 20,
+ .enable = 200,
+ .disable = 200,
+ },
+};
+
+static const struct drm_display_mode bananapi_s070wv20_ct16_mode = {
+ .clock = 30000,
+ .hdisplay = 800,
+ .hsync_start = 800 + 40,
+ .hsync_end = 800 + 40 + 48,
+ .htotal = 800 + 40 + 48 + 40,
+ .vdisplay = 480,
+ .vsync_start = 480 + 13,
+ .vsync_end = 480 + 13 + 3,
+ .vtotal = 480 + 13 + 3 + 29,
+};
+
+static const struct panel_desc bananapi_s070wv20_ct16 = {
+ .modes = &bananapi_s070wv20_ct16_mode,
+ .num_modes = 1,
+ .bpc = 6,
+ .size = {
+ .width = 154,
+ .height = 86,
+ },
+};
+
+static const struct drm_display_mode boe_hv070wsa_mode = {
+ .clock = 42105,
+ .hdisplay = 1024,
+ .hsync_start = 1024 + 30,
+ .hsync_end = 1024 + 30 + 30,
+ .htotal = 1024 + 30 + 30 + 30,
+ .vdisplay = 600,
+ .vsync_start = 600 + 10,
+ .vsync_end = 600 + 10 + 10,
+ .vtotal = 600 + 10 + 10 + 10,
+};
+
+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 cdtech_s043wq26h_ct7_mode = {
+ .clock = 9000,
+ .hdisplay = 480,
+ .hsync_start = 480 + 5,
+ .hsync_end = 480 + 5 + 5,
+ .htotal = 480 + 5 + 5 + 40,
+ .vdisplay = 272,
+ .vsync_start = 272 + 8,
+ .vsync_end = 272 + 8 + 8,
+ .vtotal = 272 + 8 + 8 + 8,
+ .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
+};
+
+static const struct panel_desc cdtech_s043wq26h_ct7 = {
+ .modes = &cdtech_s043wq26h_ct7_mode,
+ .num_modes = 1,
+ .bpc = 8,
+ .size = {
+ .width = 95,
+ .height = 54,
+ },
+ .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 = {
+ .clock = 35000,
+ .hdisplay = 800,
+ .hsync_start = 800 + 40,
+ .hsync_end = 800 + 40 + 40,
+ .htotal = 800 + 40 + 40 + 48,
+ .vdisplay = 480,
+ .vsync_start = 480 + 29,
+ .vsync_end = 480 + 29 + 13,
+ .vtotal = 480 + 29 + 13 + 3,
+ .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
+};
+
+static const struct panel_desc cdtech_s070wv95_ct16 = {
+ .modes = &cdtech_s070wv95_ct16_mode,
+ .num_modes = 1,
+ .bpc = 8,
+ .size = {
+ .width = 154,
+ .height = 85,
+ },
+};
+
+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,
+ .hsync_start = 800 + 49,
+ .hsync_end = 800 + 49 + 33,
+ .htotal = 800 + 49 + 33 + 17,
+ .vdisplay = 1280,
+ .vsync_start = 1280 + 1,
+ .vsync_end = 1280 + 1 + 7,
+ .vtotal = 1280 + 1 + 7 + 15,
+ .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC,
+};
+
+static const struct panel_desc chunghwa_claa070wp03xg = {
+ .modes = &chunghwa_claa070wp03xg_mode,
+ .num_modes = 1,
+ .bpc = 6,
+ .size = {
+ .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 = {
+ .clock = 72070,
+ .hdisplay = 1366,
+ .hsync_start = 1366 + 58,
+ .hsync_end = 1366 + 58 + 58,
+ .htotal = 1366 + 58 + 58 + 58,
+ .vdisplay = 768,
+ .vsync_start = 768 + 4,
+ .vsync_end = 768 + 4 + 4,
+ .vtotal = 768 + 4 + 4 + 4,
+};
+
+static const struct panel_desc chunghwa_claa101wa01a = {
+ .modes = &chunghwa_claa101wa01a_mode,
+ .num_modes = 1,
+ .bpc = 6,
+ .size = {
+ .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 = {
+ .clock = 69300,
+ .hdisplay = 1366,
+ .hsync_start = 1366 + 48,
+ .hsync_end = 1366 + 48 + 32,
+ .htotal = 1366 + 48 + 32 + 20,
+ .vdisplay = 768,
+ .vsync_start = 768 + 16,
+ .vsync_end = 768 + 16 + 8,
+ .vtotal = 768 + 16 + 8 + 16,
+};
+
+static const struct panel_desc chunghwa_claa101wb01 = {
+ .modes = &chunghwa_claa101wb01_mode,
+ .num_modes = 1,
+ .bpc = 6,
+ .size = {
+ .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 = {
+ .clock = 33260,
+ .hdisplay = 800,
+ .hsync_start = 800 + 40,
+ .hsync_end = 800 + 40 + 128,
+ .htotal = 800 + 40 + 128 + 88,
+ .vdisplay = 480,
+ .vsync_start = 480 + 10,
+ .vsync_end = 480 + 10 + 2,
+ .vtotal = 480 + 10 + 2 + 33,
+ .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC,
+};
+
+static const struct panel_desc dataimage_scf0700c48ggu18 = {
+ .modes = &dataimage_scf0700c48ggu18_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_DRIVE_POSEDGE,
+};
+
+static const struct display_timing dlc_dlc0700yzg_1_timing = {
+ .pixelclock = { 45000000, 51200000, 57000000 },
+ .hactive = { 1024, 1024, 1024 },
+ .hfront_porch = { 100, 106, 113 },
+ .hback_porch = { 100, 106, 113 },
+ .hsync_len = { 100, 108, 114 },
+ .vactive = { 600, 600, 600 },
+ .vfront_porch = { 8, 11, 15 },
+ .vback_porch = { 8, 11, 15 },
+ .vsync_len = { 9, 13, 15 },
+ .flags = DISPLAY_FLAGS_DE_HIGH,
+};
+
+static const struct panel_desc dlc_dlc0700yzg_1 = {
+ .timings = &dlc_dlc0700yzg_1_timing,
+ .num_timings = 1,
+ .bpc = 6,
+ .size = {
+ .width = 154,
+ .height = 86,
+ },
+ .delay = {
+ .prepare = 30,
+ .enable = 200,
+ .disable = 200,
+ },
+ .bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG,
+ .connector_type = DRM_MODE_CONNECTOR_LVDS,
+};
+
+static const struct display_timing dlc_dlc1010gig_timing = {
+ .pixelclock = { 68900000, 71100000, 73400000 },
+ .hactive = { 1280, 1280, 1280 },
+ .hfront_porch = { 43, 53, 63 },
+ .hback_porch = { 43, 53, 63 },
+ .hsync_len = { 44, 54, 64 },
+ .vactive = { 800, 800, 800 },
+ .vfront_porch = { 5, 8, 11 },
+ .vback_porch = { 5, 8, 11 },
+ .vsync_len = { 5, 7, 11 },
+ .flags = DISPLAY_FLAGS_DE_HIGH,
+};
+
+static const struct panel_desc dlc_dlc1010gig = {
+ .timings = &dlc_dlc1010gig_timing,
+ .num_timings = 1,
+ .bpc = 8,
+ .size = {
+ .width = 216,
+ .height = 135,
+ },
+ .delay = {
+ .prepare = 60,
+ .enable = 150,
+ .disable = 100,
+ .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 = {
+ .clock = 25175,
+ .hdisplay = 640,
+ .hsync_start = 640 + 16,
+ .hsync_end = 640 + 16 + 30,
+ .htotal = 640 + 16 + 30 + 114,
+ .vdisplay = 480,
+ .vsync_start = 480 + 10,
+ .vsync_end = 480 + 10 + 3,
+ .vtotal = 480 + 10 + 3 + 32,
+ .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC,
+};
+
+static const struct panel_desc edt_et057090dhu = {
+ .modes = &edt_et057090dhu_mode,
+ .num_modes = 1,
+ .bpc = 6,
+ .size = {
+ .width = 115,
+ .height = 86,
+ },
+ .bus_format = MEDIA_BUS_FMT_RGB666_1X18,
+ .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 = {
+ .clock = 33260,
+ .hdisplay = 800,
+ .hsync_start = 800 + 40,
+ .hsync_end = 800 + 40 + 128,
+ .htotal = 800 + 40 + 128 + 88,
+ .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 edt_etm0700g0dh6 = {
+ .modes = &edt_etm0700g0dh6_mode,
+ .num_modes = 1,
+ .bpc = 6,
+ .size = {
+ .width = 152,
+ .height = 91,
+ },
+ .bus_format = MEDIA_BUS_FMT_RGB666_1X18,
+ .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 = {
+ .modes = &edt_etm0700g0dh6_mode,
+ .num_modes = 1,
+ .bpc = 6,
+ .size = {
+ .width = 152,
+ .height = 91,
+ },
+ .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 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 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_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 = {
+ .clock = 32260,
+ .hdisplay = 800,
+ .hsync_start = 800 + 168,
+ .hsync_end = 800 + 168 + 64,
+ .htotal = 800 + 168 + 64 + 88,
+ .vdisplay = 480,
+ .vsync_start = 480 + 37,
+ .vsync_end = 480 + 37 + 2,
+ .vtotal = 480 + 37 + 2 + 8,
+};
+
+static const struct panel_desc foxlink_fl500wvr00_a0t = {
+ .modes = &foxlink_fl500wvr00_a0t_mode,
+ .num_modes = 1,
+ .bpc = 8,
+ .size = {
+ .width = 108,
+ .height = 65,
+ },
+ .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,
+ .hsync_start = 480 + 5,
+ .hsync_end = 480 + 5 + 1,
+ .htotal = 480 + 5 + 1 + 40,
+ .vdisplay = 272,
+ .vsync_start = 272 + 8,
+ .vsync_end = 272 + 8 + 1,
+ .vtotal = 272 + 8 + 1 + 8,
+};
+
+static const struct panel_desc giantplus_gpg482739qs5 = {
+ .modes = &giantplus_gpg482739qs5_mode,
+ .num_modes = 1,
+ .bpc = 8,
+ .size = {
+ .width = 95,
+ .height = 54,
+ },
+ .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 },
+ .hfront_porch = { 1, 1, 10 },
+ .hback_porch = { 1, 1, 10 },
+ /*
+ * According to the data sheet, the minimum horizontal blanking interval
+ * is 54 clocks (1 + 52 + 1), but tests with a Nitrogen6X have shown the
+ * minimum working horizontal blanking interval to be 60 clocks.
+ */
+ .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_hsd070pww1 = {
+ .timings = &hannstar_hsd070pww1_timing,
+ .num_timings = 1,
+ .bpc = 6,
+ .size = {
+ .width = 151,
+ .height = 94,
+ },
+ .bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG,
+ .connector_type = DRM_MODE_CONNECTOR_LVDS,
+};
+
+static const struct display_timing hannstar_hsd100pxn1_timing = {
+ .pixelclock = { 55000000, 65000000, 75000000 },
+ .hactive = { 1024, 1024, 1024 },
+ .hfront_porch = { 40, 40, 40 },
+ .hback_porch = { 220, 220, 220 },
+ .hsync_len = { 20, 60, 100 },
+ .vactive = { 768, 768, 768 },
+ .vfront_porch = { 7, 7, 7 },
+ .vback_porch = { 21, 21, 21 },
+ .vsync_len = { 10, 10, 10 },
+ .flags = DISPLAY_FLAGS_DE_HIGH,
+};
+
+static const struct panel_desc hannstar_hsd100pxn1 = {
+ .timings = &hannstar_hsd100pxn1_timing,
+ .num_timings = 1,
+ .bpc = 6,
+ .size = {
+ .width = 203,
+ .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 = {
+ .clock = 33333,
+ .hdisplay = 800,
+ .hsync_start = 800 + 85,
+ .hsync_end = 800 + 85 + 86,
+ .htotal = 800 + 85 + 86 + 85,
+ .vdisplay = 480,
+ .vsync_start = 480 + 16,
+ .vsync_end = 480 + 16 + 13,
+ .vtotal = 480 + 16 + 13 + 16,
+};
+
+static const struct panel_desc hitachi_tx23d38vm0caa = {
+ .modes = &hitachi_tx23d38vm0caa_mode,
+ .num_modes = 1,
+ .bpc = 6,
+ .size = {
+ .width = 195,
+ .height = 117,
+ },
+ .delay = {
+ .enable = 160,
+ .disable = 160,
+ },
+};
+
+static const struct drm_display_mode innolux_at043tn24_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 innolux_at043tn24 = {
+ .modes = &innolux_at043tn24_mode,
+ .num_modes = 1,
+ .bpc = 8,
+ .size = {
+ .width = 95,
+ .height = 54,
+ },
+ .bus_format = MEDIA_BUS_FMT_RGB888_1X24,
+ .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 = {
+ .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_at070tn92 = {
+ .modes = &innolux_at070tn92_mode,
+ .num_modes = 1,
+ .size = {
+ .width = 154,
+ .height = 86,
+ },
+ .bus_format = MEDIA_BUS_FMT_RGB888_1X24,
+};
+
+static const struct display_timing innolux_g070y2_l01_timing = {
+ .pixelclock = { 28000000, 29500000, 32000000 },
+ .hactive = { 800, 800, 800 },
+ .hfront_porch = { 61, 91, 141 },
+ .hback_porch = { 60, 90, 140 },
+ .hsync_len = { 12, 12, 12 },
+ .vactive = { 480, 480, 480 },
+ .vfront_porch = { 4, 9, 30 },
+ .vback_porch = { 4, 8, 28 },
+ .vsync_len = { 2, 2, 2 },
+ .flags = DISPLAY_FLAGS_DE_HIGH,
+};
+
+static const struct panel_desc innolux_g070y2_l01 = {
+ .timings = &innolux_g070y2_l01_timing,
+ .num_timings = 1,
+ .bpc = 8,
+ .size = {
+ .width = 152,
+ .height = 91,
+ },
+ .delay = {
+ .prepare = 10,
+ .enable = 100,
+ .disable = 100,
+ .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 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 = { 30, 60, 70 },
+ .hback_porch = { 30, 60, 70 },
+ .hsync_len = { 22, 40, 60 },
+ .vactive = { 800, 800, 800 },
+ .vfront_porch = { 3, 8, 14 },
+ .vback_porch = { 3, 8, 14 },
+ .vsync_len = { 4, 7, 12 },
+ .flags = DISPLAY_FLAGS_DE_HIGH,
+};
+
+static const struct panel_desc innolux_g101ice_l01 = {
+ .timings = &innolux_g101ice_l01_timing,
+ .num_timings = 1,
+ .bpc = 8,
+ .size = {
+ .width = 217,
+ .height = 135,
+ },
+ .delay = {
+ .enable = 200,
+ .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 = {
+ .pixelclock = { 67450000, 71000000, 74550000 },
+ .hactive = { 1280, 1280, 1280 },
+ .hfront_porch = { 40, 80, 160 },
+ .hback_porch = { 39, 79, 159 },
+ .hsync_len = { 1, 1, 1 },
+ .vactive = { 800, 800, 800 },
+ .vfront_porch = { 5, 11, 100 },
+ .vback_porch = { 4, 11, 99 },
+ .vsync_len = { 1, 1, 1 },
+};
+
+static const struct panel_desc innolux_g121i1_l01 = {
+ .timings = &innolux_g121i1_l01_timing,
+ .num_timings = 1,
+ .bpc = 6,
+ .size = {
+ .width = 261,
+ .height = 163,
+ },
+ .delay = {
+ .enable = 200,
+ .disable = 20,
+ },
+ .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,
+ .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
+};
+
+static const struct panel_desc innolux_g121x1_l03 = {
+ .modes = &innolux_g121x1_l03_mode,
+ .num_modes = 1,
+ .bpc = 6,
+ .size = {
+ .width = 246,
+ .height = 185,
+ },
+ .delay = {
+ .enable = 200,
+ .unprepare = 200,
+ .disable = 400,
+ },
+};
+
+static const struct drm_display_mode innolux_n156bge_l21_mode = {
+ .clock = 69300,
+ .hdisplay = 1366,
+ .hsync_start = 1366 + 16,
+ .hsync_end = 1366 + 16 + 34,
+ .htotal = 1366 + 16 + 34 + 50,
+ .vdisplay = 768,
+ .vsync_start = 768 + 2,
+ .vsync_end = 768 + 2 + 6,
+ .vtotal = 768 + 2 + 6 + 12,
+};
+
+static const struct panel_desc innolux_n156bge_l21 = {
+ .modes = &innolux_n156bge_l21_mode,
+ .num_modes = 1,
+ .bpc = 6,
+ .size = {
+ .width = 344,
+ .height = 193,
+ },
+ .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 = {
+ .clock = 51501,
+ .hdisplay = 1024,
+ .hsync_start = 1024 + 128,
+ .hsync_end = 1024 + 128 + 64,
+ .htotal = 1024 + 128 + 64 + 128,
+ .vdisplay = 600,
+ .vsync_start = 600 + 16,
+ .vsync_end = 600 + 16 + 4,
+ .vtotal = 600 + 16 + 4 + 16,
+};
+
+static const struct panel_desc innolux_zj070na_01p = {
+ .modes = &innolux_zj070na_01p_mode,
+ .num_modes = 1,
+ .bpc = 6,
+ .size = {
+ .width = 154,
+ .height = 90,
+ },
+};
+
+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 },
+};
+
+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 },
+ .hfront_porch = { 16, 36, 56 },
+ .hback_porch = { 16, 36, 56 },
+ .hsync_len = { 8, 8, 8 },
+ .vactive = { 480, 480, 480 },
+ .vfront_porch = { 6, 21, 33 },
+ .vback_porch = { 6, 21, 33 },
+ .vsync_len = { 8, 8, 8 },
+ .flags = DISPLAY_FLAGS_DE_HIGH,
+};
+
+static const struct panel_desc koe_tx31d200vm0baa = {
+ .timings = &koe_tx31d200vm0baa_timing,
+ .num_timings = 1,
+ .bpc = 6,
+ .size = {
+ .width = 292,
+ .height = 109,
+ },
+ .bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG,
+ .connector_type = DRM_MODE_CONNECTOR_LVDS,
+};
+
+static const struct display_timing kyo_tcg121xglp_timing = {
+ .pixelclock = { 52000000, 65000000, 71000000 },
+ .hactive = { 1024, 1024, 1024 },
+ .hfront_porch = { 2, 2, 2 },
+ .hback_porch = { 2, 2, 2 },
+ .hsync_len = { 86, 124, 244 },
+ .vactive = { 768, 768, 768 },
+ .vfront_porch = { 2, 2, 2 },
+ .vback_porch = { 2, 2, 2 },
+ .vsync_len = { 6, 34, 73 },
+ .flags = DISPLAY_FLAGS_DE_HIGH,
+};
+
+static const struct panel_desc kyo_tcg121xglp = {
+ .timings = &kyo_tcg121xglp_timing,
+ .num_timings = 1,
+ .bpc = 8,
+ .size = {
+ .width = 246,
+ .height = 184,
+ },
+ .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG,
+ .connector_type = DRM_MODE_CONNECTOR_LVDS,
+};
+
+static const struct drm_display_mode lemaker_bl035_rgb_002_mode = {
+ .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 lemaker_bl035_rgb_002 = {
+ .modes = &lemaker_bl035_rgb_002_mode,
+ .num_modes = 1,
+ .size = {
+ .width = 70,
+ .height = 52,
+ },
+ .bus_format = MEDIA_BUS_FMT_RGB888_1X24,
+ .bus_flags = DRM_BUS_FLAG_DE_LOW,
+};
+
+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,
+};
+
+static const struct panel_desc lg_lb070wv8 = {
+ .modes = &lg_lb070wv8_mode,
+ .num_modes = 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 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 logictechno_lttd800480070_l2rt = {
+ .modes = &logictechno_lttd800480070_l2rt_mode,
+ .num_modes = 1,
+ .bpc = 8,
+ .size = {
+ .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 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 logictechno_lttd800480070_l6wh_rt = {
+ .modes = &logictechno_lttd800480070_l6wh_rt_mode,
+ .num_modes = 1,
+ .bpc = 8,
+ .size = {
+ .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 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 logicpd_type_28 = {
+ .modes = &logicpd_type_28_mode,
+ .num_modes = 1,
+ .bpc = 8,
+ .size = {
+ .width = 105,
+ .height = 67,
+ },
+ .delay = {
+ .prepare = 200,
+ .enable = 200,
+ .unprepare = 200,
+ .disable = 200,
+ },
+ .bus_format = MEDIA_BUS_FMT_RGB888_1X24,
+ .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE |
+ DRM_BUS_FLAG_SYNC_DRIVE_NEGEDGE,
+ .connector_type = DRM_MODE_CONNECTOR_DPI,
+};
+
+static const struct drm_display_mode mitsubishi_aa070mc01_mode = {
+ .clock = 30400,
+ .hdisplay = 800,
+ .hsync_start = 800 + 0,
+ .hsync_end = 800 + 1,
+ .htotal = 800 + 0 + 1 + 160,
+ .vdisplay = 480,
+ .vsync_start = 480 + 0,
+ .vsync_end = 480 + 48 + 1,
+ .vtotal = 480 + 48 + 1 + 0,
+ .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
+};
+
+static const struct panel_desc mitsubishi_aa070mc01 = {
+ .modes = &mitsubishi_aa070mc01_mode,
+ .num_modes = 1,
+ .bpc = 8,
+ .size = {
+ .width = 152,
+ .height = 91,
+ },
+
+ .delay = {
+ .enable = 200,
+ .unprepare = 200,
+ .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 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 nec_nl12880bc20_05_timing = {
+ .pixelclock = { 67000000, 71000000, 75000000 },
+ .hactive = { 1280, 1280, 1280 },
+ .hfront_porch = { 2, 30, 30 },
+ .hback_porch = { 6, 100, 100 },
+ .hsync_len = { 2, 30, 30 },
+ .vactive = { 800, 800, 800 },
+ .vfront_porch = { 5, 5, 5 },
+ .vback_porch = { 11, 11, 11 },
+ .vsync_len = { 7, 7, 7 },
+};
+
+static const struct panel_desc nec_nl12880bc20_05 = {
+ .timings = &nec_nl12880bc20_05_timing,
+ .num_timings = 1,
+ .bpc = 8,
+ .size = {
+ .width = 261,
+ .height = 163,
+ },
+ .delay = {
+ .enable = 50,
+ .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 = {
+ .clock = 10870,
+ .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 + 4,
+ .vtotal = 272 + 2 + 4 + 2,
+ .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC,
+};
+
+static const struct panel_desc nec_nl4827hc19_05b = {
+ .modes = &nec_nl4827hc19_05b_mode,
+ .num_modes = 1,
+ .bpc = 8,
+ .size = {
+ .width = 95,
+ .height = 54,
+ },
+ .bus_format = MEDIA_BUS_FMT_RGB888_1X24,
+ .bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE,
+};
+
+static const struct drm_display_mode netron_dy_e231732_mode = {
+ .clock = 66000,
+ .hdisplay = 1024,
+ .hsync_start = 1024 + 160,
+ .hsync_end = 1024 + 160 + 70,
+ .htotal = 1024 + 160 + 70 + 90,
+ .vdisplay = 600,
+ .vsync_start = 600 + 127,
+ .vsync_end = 600 + 127 + 20,
+ .vtotal = 600 + 127 + 20 + 3,
+};
+
+static const struct panel_desc netron_dy_e231732 = {
+ .modes = &netron_dy_e231732_mode,
+ .num_modes = 1,
+ .size = {
+ .width = 154,
+ .height = 87,
+ },
+ .bus_format = MEDIA_BUS_FMT_RGB666_1X18,
+};
+
+static const struct drm_display_mode newhaven_nhd_43_480272ef_atxl_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_NVSYNC | DRM_MODE_FLAG_NHSYNC,
+};
+
+static const struct panel_desc newhaven_nhd_43_480272ef_atxl = {
+ .modes = &newhaven_nhd_43_480272ef_atxl_mode,
+ .num_modes = 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 |
+ DRM_BUS_FLAG_SYNC_DRIVE_POSEDGE,
+ .connector_type = DRM_MODE_CONNECTOR_DPI,
+};
+
+static const struct display_timing nlt_nl192108ac18_02d_timing = {
+ .pixelclock = { 130000000, 148350000, 163000000 },
+ .hactive = { 1920, 1920, 1920 },
+ .hfront_porch = { 80, 100, 100 },
+ .hback_porch = { 100, 120, 120 },
+ .hsync_len = { 50, 60, 60 },
+ .vactive = { 1080, 1080, 1080 },
+ .vfront_porch = { 12, 30, 30 },
+ .vback_porch = { 4, 10, 10 },
+ .vsync_len = { 4, 5, 5 },
+};
+
+static const struct panel_desc nlt_nl192108ac18_02d = {
+ .timings = &nlt_nl192108ac18_02d_timing,
+ .num_timings = 1,
+ .bpc = 8,
+ .size = {
+ .width = 344,
+ .height = 194,
+ },
+ .delay = {
+ .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 = {
+ .clock = 29500,
+ .hdisplay = 800,
+ .hsync_start = 800 + 130,
+ .hsync_end = 800 + 130 + 98,
+ .htotal = 800 + 0 + 130 + 98,
+ .vdisplay = 480,
+ .vsync_start = 480 + 10,
+ .vsync_end = 480 + 10 + 50,
+ .vtotal = 480 + 0 + 10 + 50,
+};
+
+static const struct panel_desc nvd_9128 = {
+ .modes = &nvd_9128_mode,
+ .num_modes = 1,
+ .bpc = 8,
+ .size = {
+ .width = 156,
+ .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 = {
+ .pixelclock = { 30000000, 30000000, 40000000 },
+ .hactive = { 800, 800, 800 },
+ .hfront_porch = { 40, 40, 40 },
+ .hback_porch = { 40, 40, 40 },
+ .hsync_len = { 1, 48, 48 },
+ .vactive = { 480, 480, 480 },
+ .vfront_porch = { 13, 13, 13 },
+ .vback_porch = { 29, 29, 29 },
+ .vsync_len = { 3, 3, 3 },
+ .flags = DISPLAY_FLAGS_DE_HIGH,
+};
+
+static const struct panel_desc okaya_rs800480t_7x0gp = {
+ .timings = &okaya_rs800480t_7x0gp_timing,
+ .num_timings = 1,
+ .bpc = 6,
+ .size = {
+ .width = 154,
+ .height = 87,
+ },
+ .delay = {
+ .prepare = 41,
+ .enable = 50,
+ .unprepare = 41,
+ .disable = 50,
+ },
+ .bus_format = MEDIA_BUS_FMT_RGB666_1X18,
+};
+
+static const struct drm_display_mode olimex_lcd_olinuxino_43ts_mode = {
+ .clock = 9000,
+ .hdisplay = 480,
+ .hsync_start = 480 + 5,
+ .hsync_end = 480 + 5 + 30,
+ .htotal = 480 + 5 + 30 + 10,
+ .vdisplay = 272,
+ .vsync_start = 272 + 8,
+ .vsync_end = 272 + 8 + 5,
+ .vtotal = 272 + 8 + 5 + 3,
+};
+
+static const struct panel_desc olimex_lcd_olinuxino_43ts = {
+ .modes = &olimex_lcd_olinuxino_43ts_mode,
+ .num_modes = 1,
+ .size = {
+ .width = 95,
+ .height = 54,
+ },
+ .bus_format = MEDIA_BUS_FMT_RGB888_1X24,
+};
+
+/*
+ * 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
+ * installation instructions.
+ */
+static const struct drm_display_mode ontat_yx700wv03_mode = {
+ .clock = 29500,
+ .hdisplay = 800,
+ .hsync_start = 824,
+ .hsync_end = 896,
+ .htotal = 992,
+ .vdisplay = 480,
+ .vsync_start = 483,
+ .vsync_end = 493,
+ .vtotal = 500,
+ .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC,
+};
+
+/*
+ * Specification at:
+ * https://www.adafruit.com/images/product-files/2406/c3163.pdf
+ */
+static const struct panel_desc ontat_yx700wv03 = {
+ .modes = &ontat_yx700wv03_mode,
+ .num_modes = 1,
+ .bpc = 8,
+ .size = {
+ .width = 154,
+ .height = 83,
+ },
+ .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,
+ .hsync_start = 480 + 10,
+ .hsync_end = 480 + 10 + 10,
+ .htotal = 480 + 10 + 10 + 15,
+ .vdisplay = 800,
+ .vsync_start = 800 + 3,
+ .vsync_end = 800 + 3 + 3,
+ .vtotal = 800 + 3 + 3 + 3,
+};
+
+static const struct panel_desc ortustech_com43h4m85ulc = {
+ .modes = &ortustech_com43h4m85ulc_mode,
+ .num_modes = 1,
+ .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_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_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,
+ .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 qd43003c0_40_mode = {
+ .clock = 9000,
+ .hdisplay = 480,
+ .hsync_start = 480 + 8,
+ .hsync_end = 480 + 8 + 4,
+ .htotal = 480 + 8 + 4 + 39,
+ .vdisplay = 272,
+ .vsync_start = 272 + 4,
+ .vsync_end = 272 + 4 + 10,
+ .vtotal = 272 + 4 + 10 + 2,
+};
+
+static const struct panel_desc qd43003c0_40 = {
+ .modes = &qd43003c0_40_mode,
+ .num_modes = 1,
+ .bpc = 8,
+ .size = {
+ .width = 95,
+ .height = 53,
+ },
+ .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 rocktech_rk070er9427_timing = {
+ .pixelclock = { 26400000, 33300000, 46800000 },
+ .hactive = { 800, 800, 800 },
+ .hfront_porch = { 16, 210, 354 },
+ .hback_porch = { 46, 46, 46 },
+ .hsync_len = { 1, 1, 1 },
+ .vactive = { 480, 480, 480 },
+ .vfront_porch = { 7, 22, 147 },
+ .vback_porch = { 23, 23, 23 },
+ .vsync_len = { 1, 1, 1 },
+ .flags = DISPLAY_FLAGS_DE_HIGH,
+};
+
+static const struct panel_desc rocktech_rk070er9427 = {
+ .timings = &rocktech_rk070er9427_timing,
+ .num_timings = 1,
+ .bpc = 6,
+ .size = {
+ .width = 154,
+ .height = 86,
+ },
+ .delay = {
+ .prepare = 41,
+ .enable = 50,
+ .unprepare = 41,
+ .disable = 50,
+ },
+ .bus_format = MEDIA_BUS_FMT_RGB666_1X18,
+};
+
+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 rocktech_rk101ii01d_ct = {
+ .modes = &rocktech_rk101ii01d_ct_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 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 drm_display_mode samsung_ltn101nt05_mode = {
+ .clock = 54030,
+ .hdisplay = 1024,
+ .hsync_start = 1024 + 24,
+ .hsync_end = 1024 + 24 + 136,
+ .htotal = 1024 + 24 + 136 + 160,
+ .vdisplay = 600,
+ .vsync_start = 600 + 3,
+ .vsync_end = 600 + 3 + 6,
+ .vtotal = 600 + 3 + 6 + 61,
+};
+
+static const struct panel_desc samsung_ltn101nt05 = {
+ .modes = &samsung_ltn101nt05_mode,
+ .num_modes = 1,
+ .bpc = 6,
+ .size = {
+ .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 satoz_sat050at40h12r2_timing = {
+ .pixelclock = {33300000, 33300000, 50000000},
+ .hactive = {800, 800, 800},
+ .hfront_porch = {16, 210, 354},
+ .hback_porch = {46, 46, 46},
+ .hsync_len = {1, 1, 40},
+ .vactive = {480, 480, 480},
+ .vfront_porch = {7, 22, 147},
+ .vback_porch = {23, 23, 23},
+ .vsync_len = {1, 1, 20},
+};
+
+static const struct panel_desc satoz_sat050at40h12r2 = {
+ .timings = &satoz_sat050at40h12r2_timing,
+ .num_timings = 1,
+ .bpc = 8,
+ .size = {
+ .width = 108,
+ .height = 65,
+ },
+ .bus_format = MEDIA_BUS_FMT_RGB888_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 = 8,
+ .size = {
+ .width = 152, /* 152.4mm */
+ .height = 91, /* 91.4mm */
+ },
+ .bus_format = MEDIA_BUS_FMT_RGB888_1X24,
+ .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE |
+ DRM_BUS_FLAG_SYNC_DRIVE_POSEDGE,
+};
+
+static const struct drm_display_mode sharp_lq035q7db03_mode = {
+ .clock = 5500,
+ .hdisplay = 240,
+ .hsync_start = 240 + 16,
+ .hsync_end = 240 + 16 + 7,
+ .htotal = 240 + 16 + 7 + 5,
+ .vdisplay = 320,
+ .vsync_start = 320 + 9,
+ .vsync_end = 320 + 9 + 1,
+ .vtotal = 320 + 9 + 1 + 7,
+};
+
+static const struct panel_desc sharp_lq035q7db03 = {
+ .modes = &sharp_lq035q7db03_mode,
+ .num_modes = 1,
+ .bpc = 6,
+ .size = {
+ .width = 54,
+ .height = 72,
+ },
+ .bus_format = MEDIA_BUS_FMT_RGB666_1X18,
+};
+
+static const struct display_timing sharp_lq101k1ly04_timing = {
+ .pixelclock = { 60000000, 65000000, 80000000 },
+ .hactive = { 1280, 1280, 1280 },
+ .hfront_porch = { 20, 20, 20 },
+ .hback_porch = { 20, 20, 20 },
+ .hsync_len = { 10, 10, 10 },
+ .vactive = { 800, 800, 800 },
+ .vfront_porch = { 4, 4, 4 },
+ .vback_porch = { 4, 4, 4 },
+ .vsync_len = { 4, 4, 4 },
+ .flags = DISPLAY_FLAGS_PIXDATA_POSEDGE,
+};
+
+static const struct panel_desc sharp_lq101k1ly04 = {
+ .timings = &sharp_lq101k1ly04_timing,
+ .num_timings = 1,
+ .bpc = 8,
+ .size = {
+ .width = 217,
+ .height = 136,
+ },
+ .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA,
+ .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,
+ },
+ { /* 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 panel_desc sharp_ls020b1dd01d = {
+ .modes = sharp_ls020b1dd01d_modes,
+ .num_modes = ARRAY_SIZE(sharp_ls020b1dd01d_modes),
+ .bpc = 6,
+ .size = {
+ .width = 42,
+ .height = 28,
+ },
+ .bus_format = MEDIA_BUS_FMT_RGB565_1X16,
+ .bus_flags = DRM_BUS_FLAG_DE_HIGH
+ | DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE
+ | DRM_BUS_FLAG_SHARP_SIGNALS,
+};
+
+static const struct drm_display_mode shelly_sca07010_bfn_lnn_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 shelly_sca07010_bfn_lnn = {
+ .modes = &shelly_sca07010_bfn_lnn_mode,
+ .num_modes = 1,
+ .size = {
+ .width = 152,
+ .height = 91,
+ },
+ .bus_format = MEDIA_BUS_FMT_RGB666_1X18,
+};
+
+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_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 = 152,
+ .height = 91,
+ },
+ .delay = {
+ .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 = {
+ .pixelclock = { 62600000, 68200000, 78100000 },
+ .hactive = { 1280, 1280, 1280 },
+ .hfront_porch = { 15, 64, 159 },
+ .hback_porch = { 5, 5, 5 },
+ .hsync_len = { 1, 1, 256 },
+ .vactive = { 800, 800, 800 },
+ .vfront_porch = { 3, 40, 99 },
+ .vback_porch = { 2, 2, 2 },
+ .vsync_len = { 1, 1, 128 },
+ .flags = DISPLAY_FLAGS_DE_HIGH,
+};
+
+static const struct panel_desc tianma_tm070jdhg30 = {
+ .timings = &tianma_tm070jdhg30_timing,
+ .num_timings = 1,
+ .bpc = 8,
+ .size = {
+ .width = 151,
+ .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,
+};
+
+static const struct display_timing tianma_tm070rvhg71_timing = {
+ .pixelclock = { 27700000, 29200000, 39600000 },
+ .hactive = { 800, 800, 800 },
+ .hfront_porch = { 12, 40, 212 },
+ .hback_porch = { 88, 88, 88 },
+ .hsync_len = { 1, 1, 40 },
+ .vactive = { 480, 480, 480 },
+ .vfront_porch = { 1, 13, 88 },
+ .vback_porch = { 32, 32, 32 },
+ .vsync_len = { 1, 1, 3 },
+ .flags = DISPLAY_FLAGS_DE_HIGH,
+};
+
+static const struct panel_desc tianma_tm070rvhg71 = {
+ .timings = &tianma_tm070rvhg71_timing,
+ .num_timings = 1,
+ .bpc = 8,
+ .size = {
+ .width = 154,
+ .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 drm_display_mode toshiba_lt089ac29000_mode = {
+ .clock = 79500,
+ .hdisplay = 1280,
+ .hsync_start = 1280 + 192,
+ .hsync_end = 1280 + 192 + 128,
+ .htotal = 1280 + 192 + 128 + 64,
+ .vdisplay = 768,
+ .vsync_start = 768 + 20,
+ .vsync_end = 768 + 20 + 7,
+ .vtotal = 768 + 20 + 7 + 3,
+};
+
+static const struct panel_desc toshiba_lt089ac29000 = {
+ .modes = &toshiba_lt089ac29000_mode,
+ .num_modes = 1,
+ .size = {
+ .width = 194,
+ .height = 116,
+ },
+ .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 = {
+ .clock = 33260,
+ .hdisplay = 800,
+ .hsync_start = 800 + 40,
+ .hsync_end = 800 + 40 + 128,
+ .htotal = 800 + 40 + 128 + 88,
+ .vdisplay = 480,
+ .vsync_start = 480 + 10,
+ .vsync_end = 480 + 10 + 2,
+ .vtotal = 480 + 10 + 2 + 33,
+};
+
+static const struct panel_desc tpk_f07a_0102 = {
+ .modes = &tpk_f07a_0102_mode,
+ .num_modes = 1,
+ .size = {
+ .width = 152,
+ .height = 91,
+ },
+ .bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE,
+};
+
+static const struct drm_display_mode tpk_f10a_0102_mode = {
+ .clock = 45000,
+ .hdisplay = 1024,
+ .hsync_start = 1024 + 176,
+ .hsync_end = 1024 + 176 + 5,
+ .htotal = 1024 + 176 + 5 + 88,
+ .vdisplay = 600,
+ .vsync_start = 600 + 20,
+ .vsync_end = 600 + 20 + 5,
+ .vtotal = 600 + 20 + 5 + 25,
+};
+
+static const struct panel_desc tpk_f10a_0102 = {
+ .modes = &tpk_f10a_0102_mode,
+ .num_modes = 1,
+ .size = {
+ .width = 223,
+ .height = 125,
+ },
+};
+
+static const struct display_timing urt_umsh_8596md_timing = {
+ .pixelclock = { 33260000, 33260000, 33260000 },
+ .hactive = { 800, 800, 800 },
+ .hfront_porch = { 41, 41, 41 },
+ .hback_porch = { 216 - 128, 216 - 128, 216 - 128 },
+ .hsync_len = { 71, 128, 128 },
+ .vactive = { 480, 480, 480 },
+ .vfront_porch = { 10, 10, 10 },
+ .vback_porch = { 35 - 2, 35 - 2, 35 - 2 },
+ .vsync_len = { 2, 2, 2 },
+ .flags = DISPLAY_FLAGS_DE_HIGH | DISPLAY_FLAGS_PIXDATA_NEGEDGE |
+ DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW,
+};
+
+static const struct panel_desc urt_umsh_8596md_lvds = {
+ .timings = &urt_umsh_8596md_timing,
+ .num_timings = 1,
+ .bpc = 6,
+ .size = {
+ .width = 152,
+ .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 = {
+ .timings = &urt_umsh_8596md_timing,
+ .num_timings = 1,
+ .bpc = 6,
+ .size = {
+ .width = 152,
+ .height = 91,
+ },
+ .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,
+ .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,
+ .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC,
+};
+
+static const struct panel_desc winstar_wf35ltiacd = {
+ .modes = &winstar_wf35ltiacd_mode,
+ .num_modes = 1,
+ .bpc = 8,
+ .size = {
+ .width = 70,
+ .height = 53,
+ },
+ .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 arm_rtsm_mode[] = {
+ {
+ .clock = 65000,
+ .hdisplay = 1024,
+ .hsync_start = 1024 + 24,
+ .hsync_end = 1024 + 24 + 136,
+ .htotal = 1024 + 24 + 136 + 160,
+ .vdisplay = 768,
+ .vsync_start = 768 + 3,
+ .vsync_end = 768 + 3 + 6,
+ .vtotal = 768 + 3 + 6 + 29,
+ .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC,
+ },
+};
+
+static const struct panel_desc arm_rtsm = {
+ .modes = arm_rtsm_mode,
+ .num_modes = 1,
+ .bpc = 8,
+ .size = {
+ .width = 400,
+ .height = 300,
+ },
+ .bus_format = MEDIA_BUS_FMT_RGB888_1X24,
+};
+
+static const struct of_device_id platform_of_match[] = {
+ {
+ .compatible = "ampire,am-1280800n3tzqw-t00h",
+ .data = &ampire_am_1280800n3tzqw_t00h,
+ }, {
+ .compatible = "ampire,am-480272h3tmqw-t01h",
+ .data = &ampire_am_480272h3tmqw_t01h,
+ }, {
+ .compatible = "ampire,am800480r3tmqwa1h",
+ .data = &ampire_am800480r3tmqwa1h,
+ }, {
+ .compatible = "ampire,am800600p5tmqw-tb8h",
+ .data = &ampire_am800600p5tmqwtb8h,
+ }, {
+ .compatible = "arm,rtsm-display",
+ .data = &arm_rtsm,
+ }, {
+ .compatible = "armadeus,st0700-adapt",
+ .data = &armadeus_st0700_adapt,
+ }, {
+ .compatible = "auo,b101aw03",
+ .data = &auo_b101aw03,
+ }, {
+ .compatible = "auo,b101xtn01",
+ .data = &auo_b101xtn01,
+ }, {
+ .compatible = "auo,g070vvn01",
+ .data = &auo_g070vvn01,
+ }, {
+ .compatible = "auo,g101evn010",
+ .data = &auo_g101evn010,
+ }, {
+ .compatible = "auo,g104sn02",
+ .data = &auo_g104sn02,
+ }, {
+ .compatible = "auo,g121ean01",
+ .data = &auo_g121ean01,
+ }, {
+ .compatible = "auo,g133han01",
+ .data = &auo_g133han01,
+ }, {
+ .compatible = "auo,g156xtn01",
+ .data = &auo_g156xtn01,
+ }, {
+ .compatible = "auo,g185han01",
+ .data = &auo_g185han01,
+ }, {
+ .compatible = "auo,g190ean01",
+ .data = &auo_g190ean01,
+ }, {
+ .compatible = "auo,p320hvn03",
+ .data = &auo_p320hvn03,
+ }, {
+ .compatible = "auo,t215hvn01",
+ .data = &auo_t215hvn01,
+ }, {
+ .compatible = "avic,tm070ddh03",
+ .data = &avic_tm070ddh03,
+ }, {
+ .compatible = "bananapi,s070wv20-ct16",
+ .data = &bananapi_s070wv20_ct16,
+ }, {
+ .compatible = "boe,hv070wsa-100",
+ .data = &boe_hv070wsa
+ }, {
+ .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,
+ }, {
+ .compatible = "chunghwa,claa101wa01a",
+ .data = &chunghwa_claa101wa01a
+ }, {
+ .compatible = "chunghwa,claa101wb01",
+ .data = &chunghwa_claa101wb01
+ }, {
+ .compatible = "dataimage,fg040346dsswbg04",
+ .data = &dataimage_fg040346dsswbg04,
+ }, {
+ .compatible = "dataimage,fg1001l0dsswmg01",
+ .data = &dataimage_fg1001l0dsswmg01,
+ }, {
+ .compatible = "dataimage,scf0700c48ggu18",
+ .data = &dataimage_scf0700c48ggu18,
+ }, {
+ .compatible = "dlc,dlc0700yzg-1",
+ .data = &dlc_dlc0700yzg_1,
+ }, {
+ .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,
+ }, {
+ .compatible = "edt,et070080dh6",
+ .data = &edt_etm0700g0dh6,
+ }, {
+ .compatible = "edt,etm0700g0dh6",
+ .data = &edt_etm0700g0dh6,
+ }, {
+ .compatible = "edt,etm0700g0bdh6",
+ .data = &edt_etm0700g0bdh6,
+ }, {
+ .compatible = "edt,etm0700g0edh6",
+ .data = &edt_etm0700g0bdh6,
+ }, {
+ .compatible = "edt,etml0700y5dha",
+ .data = &edt_etml0700y5dha,
+ }, {
+ .compatible = "edt,etmv570g2dhu",
+ .data = &edt_etmv570g2dhu,
+ }, {
+ .compatible = "eink,vb3300-kca",
+ .data = &eink_vb3300_kca,
+ }, {
+ .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
+ }, {
+ .compatible = "innolux,at043tn24",
+ .data = &innolux_at043tn24,
+ }, {
+ .compatible = "innolux,at070tn92",
+ .data = &innolux_at070tn92,
+ }, {
+ .compatible = "innolux,g070y2-l01",
+ .data = &innolux_g070y2_l01,
+ }, {
+ .compatible = "innolux,g070y2-t02",
+ .data = &innolux_g070y2_t02,
+ }, {
+ .compatible = "innolux,g101ice-l01",
+ .data = &innolux_g101ice_l01
+ }, {
+ .compatible = "innolux,g121i1-l01",
+ .data = &innolux_g121i1_l01
+ }, {
+ .compatible = "innolux,g121x1-l03",
+ .data = &innolux_g121x1_l03,
+ }, {
+ .compatible = "innolux,n156bge-l21",
+ .data = &innolux_n156bge_l21,
+ }, {
+ .compatible = "innolux,zj070na-01p",
+ .data = &innolux_zj070na_01p,
+ }, {
+ .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 = "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 = "logictechno,lttd800480070-l2rt",
+ .data = &logictechno_lttd800480070_l2rt,
+ }, {
+ .compatible = "logictechno,lttd800480070-l6wh-rt",
+ .data = &logictechno_lttd800480070_l6wh_rt,
+ }, {
+ .compatible = "mitsubishi,aa070mc01-ca1",
+ .data = &mitsubishi_aa070mc01,
+ }, {
+ .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 = "nec,nl12880bc20-05",
+ .data = &nec_nl12880bc20_05,
+ }, {
+ .compatible = "nec,nl4827hc19-05b",
+ .data = &nec_nl4827hc19_05b,
+ }, {
+ .compatible = "netron-dy,e231732",
+ .data = &netron_dy_e231732,
+ }, {
+ .compatible = "newhaven,nhd-4.3-480272ef-atxl",
+ .data = &newhaven_nhd_43_480272ef_atxl,
+ }, {
+ .compatible = "nlt,nl192108ac18-02d",
+ .data = &nlt_nl192108ac18_02d,
+ }, {
+ .compatible = "nvd,9128",
+ .data = &nvd_9128,
+ }, {
+ .compatible = "okaya,rs800480t-7x0gp",
+ .data = &okaya_rs800480t_7x0gp,
+ }, {
+ .compatible = "olimex,lcd-olinuxino-43-ts",
+ .data = &olimex_lcd_olinuxino_43ts,
+ }, {
+ .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,ph800480t013-idf02",
+ .data = &powertip_ph800480t013_idf02,
+ }, {
+ .compatible = "qiaodian,qd43003c0-40",
+ .data = &qd43003c0_40,
+ }, {
+ .compatible = "qishenglong,gopher2b-lcd",
+ .data = &qishenglong_gopher2b_lcd,
+ }, {
+ .compatible = "rocktech,rk070er9427",
+ .data = &rocktech_rk070er9427,
+ }, {
+ .compatible = "rocktech,rk101ii01d-ct",
+ .data = &rocktech_rk101ii01d_ct,
+ }, {
+ .compatible = "samsung,ltl101al01",
+ .data = &samsung_ltl101al01,
+ }, {
+ .compatible = "samsung,ltn101nt05",
+ .data = &samsung_ltn101nt05,
+ }, {
+ .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,ls020b1dd01d",
+ .data = &sharp_ls020b1dd01d,
+ }, {
+ .compatible = "shelly,sca07010-bfn-lnn",
+ .data = &shelly_sca07010_bfn_lnn,
+ }, {
+ .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,tm070jdhg30",
+ .data = &tianma_tm070jdhg30,
+ }, {
+ .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 = "tpk,f07a-0102",
+ .data = &tpk_f07a_0102,
+ }, {
+ .compatible = "tpk,f10a-0102",
+ .data = &tpk_f10a_0102,
+ }, {
+ .compatible = "urt,umsh-8596md-t",
+ .data = &urt_umsh_8596md_parallel,
+ }, {
+ .compatible = "urt,umsh-8596md-1t",
+ .data = &urt_umsh_8596md_parallel,
+ }, {
+ .compatible = "urt,umsh-8596md-7t",
+ .data = &urt_umsh_8596md_parallel,
+ }, {
+ .compatible = "urt,umsh-8596md-11t",
+ .data = &urt_umsh_8596md_lvds,
+ }, {
+ .compatible = "urt,umsh-8596md-19t",
+ .data = &urt_umsh_8596md_lvds,
+ }, {
+ .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,
+ }, {
+ /* Must be the last entry */
+ .compatible = "panel-dpi",
+ .data = &panel_dpi,
+ }, {
+ /* sentinel */
+ }
+};
+MODULE_DEVICE_TABLE(of, platform_of_match);
+
+static int panel_simple_platform_probe(struct platform_device *pdev)
+{
+ const struct of_device_id *id;
+
+ id = of_match_node(platform_of_match, pdev->dev.of_node);
+ if (!id)
+ return -ENODEV;
+
+ return panel_simple_probe(&pdev->dev, id->data);
+}
+
+static int panel_simple_platform_remove(struct platform_device *pdev)
+{
+ panel_simple_remove(&pdev->dev);
+
+ return 0;
+}
+
+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,
+ .hsync_start = 1200 + 62,
+ .hsync_end = 1200 + 62 + 4,
+ .htotal = 1200 + 62 + 4 + 62,
+ .vdisplay = 1920,
+ .vsync_start = 1920 + 9,
+ .vsync_end = 1920 + 9 + 2,
+ .vtotal = 1920 + 9 + 2 + 8,
+};
+
+static const struct panel_desc_dsi auo_b080uan01 = {
+ .desc = {
+ .modes = &auo_b080uan01_mode,
+ .num_modes = 1,
+ .bpc = 8,
+ .size = {
+ .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,
+ .lanes = 4,
+};
+
+static const struct drm_display_mode boe_tv080wum_nl0_mode = {
+ .clock = 160000,
+ .hdisplay = 1200,
+ .hsync_start = 1200 + 120,
+ .hsync_end = 1200 + 120 + 20,
+ .htotal = 1200 + 120 + 20 + 21,
+ .vdisplay = 1920,
+ .vsync_start = 1920 + 21,
+ .vsync_end = 1920 + 21 + 3,
+ .vtotal = 1920 + 21 + 3 + 18,
+ .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC,
+};
+
+static const struct panel_desc_dsi boe_tv080wum_nl0 = {
+ .desc = {
+ .modes = &boe_tv080wum_nl0_mode,
+ .num_modes = 1,
+ .size = {
+ .width = 107,
+ .height = 172,
+ },
+ .connector_type = DRM_MODE_CONNECTOR_DSI,
+ },
+ .flags = MIPI_DSI_MODE_VIDEO |
+ MIPI_DSI_MODE_VIDEO_BURST |
+ MIPI_DSI_MODE_VIDEO_SYNC_PULSE,
+ .format = MIPI_DSI_FMT_RGB888,
+ .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,
+};
+
+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,
+ },
+ .connector_type = DRM_MODE_CONNECTOR_DSI,
+ },
+ .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,
+ .hsync_start = 720 + 12,
+ .hsync_end = 720 + 12 + 4,
+ .htotal = 720 + 12 + 4 + 112,
+ .vdisplay = 1280,
+ .vsync_start = 1280 + 8,
+ .vsync_end = 1280 + 8 + 4,
+ .vtotal = 1280 + 8 + 4 + 12,
+};
+
+static const struct panel_desc_dsi lg_lh500wx1_sd03 = {
+ .desc = {
+ .modes = &lg_lh500wx1_sd03_mode,
+ .num_modes = 1,
+ .bpc = 8,
+ .size = {
+ .width = 62,
+ .height = 110,
+ },
+ .connector_type = DRM_MODE_CONNECTOR_DSI,
+ },
+ .flags = MIPI_DSI_MODE_VIDEO,
+ .format = MIPI_DSI_FMT_RGB888,
+ .lanes = 4,
+};
+
+static const struct drm_display_mode panasonic_vvx10f004b00_mode = {
+ .clock = 157200,
+ .hdisplay = 1920,
+ .hsync_start = 1920 + 154,
+ .hsync_end = 1920 + 154 + 16,
+ .htotal = 1920 + 154 + 16 + 32,
+ .vdisplay = 1200,
+ .vsync_start = 1200 + 17,
+ .vsync_end = 1200 + 17 + 2,
+ .vtotal = 1200 + 17 + 2 + 16,
+};
+
+static const struct panel_desc_dsi panasonic_vvx10f004b00 = {
+ .desc = {
+ .modes = &panasonic_vvx10f004b00_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_SYNC_PULSE |
+ MIPI_DSI_CLOCK_NON_CONTINUOUS,
+ .format = MIPI_DSI_FMT_RGB888,
+ .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",
+ .data = &auo_b080uan01
+ }, {
+ .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 */
+ }
+};
+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;
+ 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;
+
+ dsi->mode_flags = desc->flags;
+ dsi->format = desc->format;
+ dsi->lanes = desc->lanes;
+
+ err = mipi_dsi_attach(dsi);
+ if (err) {
+ struct panel_simple *panel = mipi_dsi_get_drvdata(dsi);
+
+ drm_panel_remove(&panel->base);
+ }
+
+ return err;
+}
+
+static void panel_simple_dsi_remove(struct mipi_dsi_device *dsi)
+{
+ int err;
+
+ err = mipi_dsi_detach(dsi);
+ if (err < 0)
+ dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", err);
+
+ panel_simple_remove(&dsi->dev);
+}
+
+static void panel_simple_dsi_shutdown(struct mipi_dsi_device *dsi)
+{
+ panel_simple_shutdown(&dsi->dev);
+}
+
+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,
+ .shutdown = panel_simple_dsi_shutdown,
+};
+
+static int __init panel_simple_init(void)
+{
+ int err;
+
+ err = platform_driver_register(&panel_simple_platform_driver);
+ if (err < 0)
+ return err;
+
+ if (IS_ENABLED(CONFIG_DRM_MIPI_DSI)) {
+ err = mipi_dsi_driver_register(&panel_simple_dsi_driver);
+ if (err < 0)
+ 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);
+
+static void __exit panel_simple_exit(void)
+{
+ if (IS_ENABLED(CONFIG_DRM_MIPI_DSI))
+ mipi_dsi_driver_unregister(&panel_simple_dsi_driver);
+
+ platform_driver_unregister(&panel_simple_platform_driver);
+}
+module_exit(panel_simple_exit);
+
+MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>");
+MODULE_DESCRIPTION("DRM Driver for Simple Panels");
+MODULE_LICENSE("GPL and additional rights");
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 000000000..54b28992d
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-sitronix-st7701.c
@@ -0,0 +1,803 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2019, 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/bitfield.h>
+#include <linux/gpio/consumer.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/regulator/consumer.h>
+
+#include <video/mipi_display.h>
+
+/* Command2 BKx selection command */
+#define DSI_CMD2BKX_SEL 0xFF
+
+/* Command2, BK0 commands */
+#define DSI_CMD2_BK0_PVGAMCTRL 0xB0 /* Positive Voltage Gamma Control */
+#define DSI_CMD2_BK0_NVGAMCTRL 0xB1 /* Negative Voltage Gamma Control */
+#define DSI_CMD2_BK0_LNESET 0xC0 /* Display Line setting */
+#define DSI_CMD2_BK0_PORCTRL 0xC1 /* Porch control */
+#define DSI_CMD2_BK0_INVSEL 0xC2 /* Inversion selection, Frame Rate Control */
+
+/* Command2, BK1 commands */
+#define DSI_CMD2_BK1_VRHS 0xB0 /* Vop amplitude setting */
+#define DSI_CMD2_BK1_VCOM 0xB1 /* VCOM amplitude setting */
+#define DSI_CMD2_BK1_VGHSS 0xB2 /* VGH Voltage setting */
+#define DSI_CMD2_BK1_TESTCMD 0xB3 /* TEST Command Setting */
+#define DSI_CMD2_BK1_VGLS 0xB5 /* VGL Voltage setting */
+#define DSI_CMD2_BK1_PWCTLR1 0xB7 /* Power Control 1 */
+#define DSI_CMD2_BK1_PWCTLR2 0xB8 /* Power Control 2 */
+#define DSI_CMD2_BK1_SPD1 0xC1 /* Source pre_drive timing set1 */
+#define DSI_CMD2_BK1_SPD2 0xC2 /* Source EQ2 Setting */
+#define DSI_CMD2_BK1_MIPISET1 0xD0 /* MIPI Setting 1 */
+
+/*
+ * Command2 with BK function selection.
+ *
+ * BIT[4].....CN2
+ * BIT[1:0]...BKXSEL
+ * 1:00 = CMD2BK0, Command2 BK0
+ * 1:01 = CMD2BK1, Command2 BK1
+ * 1:11 = CMD2BK3, Command2 BK3
+ * 0:00 = Command2 disable
+ */
+#define DSI_CMD2BK0_SEL 0x10
+#define DSI_CMD2BK1_SEL 0x11
+#define DSI_CMD2BK3_SEL 0x13
+#define DSI_CMD2BKX_SEL_NONE 0x00
+
+/* Command2, BK0 bytes */
+#define DSI_CMD2_BK0_GAMCTRL_AJ_MASK GENMASK(7, 6)
+#define DSI_CMD2_BK0_GAMCTRL_VC0_MASK GENMASK(3, 0)
+#define DSI_CMD2_BK0_GAMCTRL_VC4_MASK GENMASK(5, 0)
+#define DSI_CMD2_BK0_GAMCTRL_VC8_MASK GENMASK(5, 0)
+#define DSI_CMD2_BK0_GAMCTRL_VC16_MASK GENMASK(4, 0)
+#define DSI_CMD2_BK0_GAMCTRL_VC24_MASK GENMASK(4, 0)
+#define DSI_CMD2_BK0_GAMCTRL_VC52_MASK GENMASK(3, 0)
+#define DSI_CMD2_BK0_GAMCTRL_VC80_MASK GENMASK(5, 0)
+#define DSI_CMD2_BK0_GAMCTRL_VC108_MASK GENMASK(3, 0)
+#define DSI_CMD2_BK0_GAMCTRL_VC147_MASK GENMASK(3, 0)
+#define DSI_CMD2_BK0_GAMCTRL_VC175_MASK GENMASK(5, 0)
+#define DSI_CMD2_BK0_GAMCTRL_VC203_MASK GENMASK(3, 0)
+#define DSI_CMD2_BK0_GAMCTRL_VC231_MASK GENMASK(4, 0)
+#define DSI_CMD2_BK0_GAMCTRL_VC239_MASK GENMASK(4, 0)
+#define DSI_CMD2_BK0_GAMCTRL_VC247_MASK GENMASK(5, 0)
+#define DSI_CMD2_BK0_GAMCTRL_VC251_MASK GENMASK(5, 0)
+#define DSI_CMD2_BK0_GAMCTRL_VC255_MASK GENMASK(4, 0)
+#define DSI_CMD2_BK0_LNESET_LINE_MASK GENMASK(6, 0)
+#define DSI_CMD2_BK0_LNESET_LDE_EN BIT(7)
+#define DSI_CMD2_BK0_LNESET_LINEDELTA GENMASK(1, 0)
+#define DSI_CMD2_BK0_PORCTRL_VBP_MASK GENMASK(7, 0)
+#define DSI_CMD2_BK0_PORCTRL_VFP_MASK GENMASK(7, 0)
+#define DSI_CMD2_BK0_INVSEL_ONES_MASK GENMASK(5, 4)
+#define DSI_CMD2_BK0_INVSEL_NLINV_MASK GENMASK(2, 0)
+#define DSI_CMD2_BK0_INVSEL_RTNI_MASK GENMASK(4, 0)
+
+/* Command2, BK1 bytes */
+#define DSI_CMD2_BK1_VRHA_MASK GENMASK(7, 0)
+#define DSI_CMD2_BK1_VCOM_MASK GENMASK(7, 0)
+#define DSI_CMD2_BK1_VGHSS_MASK GENMASK(3, 0)
+#define DSI_CMD2_BK1_TESTCMD_VAL BIT(7)
+#define DSI_CMD2_BK1_VGLS_ONES BIT(6)
+#define DSI_CMD2_BK1_VGLS_MASK GENMASK(3, 0)
+#define DSI_CMD2_BK1_PWRCTRL1_AP_MASK GENMASK(7, 6)
+#define DSI_CMD2_BK1_PWRCTRL1_APIS_MASK GENMASK(3, 2)
+#define DSI_CMD2_BK1_PWRCTRL1_APOS_MASK GENMASK(1, 0)
+#define DSI_CMD2_BK1_PWRCTRL2_AVDD_MASK GENMASK(5, 4)
+#define DSI_CMD2_BK1_PWRCTRL2_AVCL_MASK GENMASK(1, 0)
+#define DSI_CMD2_BK1_SPD1_ONES_MASK GENMASK(6, 4)
+#define DSI_CMD2_BK1_SPD1_T2D_MASK GENMASK(3, 0)
+#define DSI_CMD2_BK1_SPD2_ONES_MASK GENMASK(6, 4)
+#define DSI_CMD2_BK1_SPD2_T3D_MASK GENMASK(3, 0)
+#define DSI_CMD2_BK1_MIPISET1_ONES BIT(7)
+#define DSI_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;
+ const struct st7701_panel_desc *desc;
+
+ struct regulator_bulk_data supplies[2];
+ struct gpio_desc *reset;
+ unsigned int sleep_delay;
+};
+
+static inline struct st7701 *panel_to_st7701(struct drm_panel *panel)
+{
+ return container_of(panel, struct st7701, panel);
+}
+
+static inline int st7701_dsi_write(struct st7701 *st7701, const void *seq,
+ size_t len)
+{
+ return mipi_dsi_dcs_write_buffer(st7701->dsi, seq, len);
+}
+
+#define ST7701_DSI(st7701, seq...) \
+ { \
+ const u8 d[] = { seq }; \
+ st7701_dsi_write(st7701, 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_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_DSI(st7701, MIPI_DCS_SOFT_RESET, 0x00);
+
+ /* We need to wait 5ms before sending new commands */
+ msleep(5);
+
+ ST7701_DSI(st7701, MIPI_DCS_EXIT_SLEEP_MODE, 0x00);
+
+ msleep(st7701->sleep_delay);
+
+ /* Command2, BK0 */
+ ST7701_DSI(st7701, DSI_CMD2BKX_SEL,
+ 0x77, 0x01, 0x00, 0x00, DSI_CMD2BK0_SEL);
+ mipi_dsi_dcs_write(st7701->dsi, DSI_CMD2_BK0_PVGAMCTRL,
+ desc->pv_gamma, ARRAY_SIZE(desc->pv_gamma));
+ mipi_dsi_dcs_write(st7701->dsi, DSI_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_DSI(st7701, DSI_CMD2_BK0_LNESET,
+ FIELD_PREP(DSI_CMD2_BK0_LNESET_LINE_MASK, linecount8 - 1) |
+ (linecountrem2 ? DSI_CMD2_BK0_LNESET_LDE_EN : 0),
+ FIELD_PREP(DSI_CMD2_BK0_LNESET_LINEDELTA, linecountrem2));
+ ST7701_DSI(st7701, DSI_CMD2_BK0_PORCTRL,
+ FIELD_PREP(DSI_CMD2_BK0_PORCTRL_VBP_MASK,
+ mode->vtotal - mode->vsync_end),
+ FIELD_PREP(DSI_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_DSI(st7701, DSI_CMD2_BK0_INVSEL,
+ DSI_CMD2_BK0_INVSEL_ONES_MASK |
+ FIELD_PREP(DSI_CMD2_BK0_INVSEL_NLINV_MASK, desc->nlinv),
+ FIELD_PREP(DSI_CMD2_BK0_INVSEL_RTNI_MASK,
+ (clamp((u32)mode->htotal, 512U, 1008U) - 512) / 16));
+
+ /* Command2, BK1 */
+ ST7701_DSI(st7701, DSI_CMD2BKX_SEL,
+ 0x77, 0x01, 0x00, 0x00, DSI_CMD2BK1_SEL);
+
+ /* Vop = 3.5375V + (VRHA[7:0] * 0.0125V) */
+ ST7701_DSI(st7701, DSI_CMD2_BK1_VRHS,
+ FIELD_PREP(DSI_CMD2_BK1_VRHA_MASK,
+ DIV_ROUND_CLOSEST(desc->vop_uv - 3537500, 12500)));
+
+ /* Vcom = 0.1V + (VCOM[7:0] * 0.0125V) */
+ ST7701_DSI(st7701, DSI_CMD2_BK1_VCOM,
+ FIELD_PREP(DSI_CMD2_BK1_VCOM_MASK,
+ DIV_ROUND_CLOSEST(desc->vcom_uv - 100000, 12500)));
+
+ /* Vgh = 11.5V + (VGHSS[7:0] * 0.5V) */
+ ST7701_DSI(st7701, DSI_CMD2_BK1_VGHSS,
+ FIELD_PREP(DSI_CMD2_BK1_VGHSS_MASK,
+ DIV_ROUND_CLOSEST(clamp(desc->vgh_mv,
+ (u16)11500,
+ (u16)17000) - 11500,
+ 500)));
+
+ ST7701_DSI(st7701, DSI_CMD2_BK1_TESTCMD, DSI_CMD2_BK1_TESTCMD_VAL);
+
+ /* Vgl is non-linear */
+ ST7701_DSI(st7701, DSI_CMD2_BK1_VGLS,
+ DSI_CMD2_BK1_VGLS_ONES |
+ FIELD_PREP(DSI_CMD2_BK1_VGLS_MASK, st7701_vgls_map(st7701)));
+
+ ST7701_DSI(st7701, DSI_CMD2_BK1_PWCTLR1,
+ FIELD_PREP(DSI_CMD2_BK1_PWRCTRL1_AP_MASK,
+ desc->gamma_op_bias) |
+ FIELD_PREP(DSI_CMD2_BK1_PWRCTRL1_APIS_MASK,
+ desc->input_op_bias) |
+ FIELD_PREP(DSI_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_DSI(st7701, DSI_CMD2_BK1_PWCTLR2,
+ FIELD_PREP(DSI_CMD2_BK1_PWRCTRL2_AVDD_MASK,
+ DIV_ROUND_CLOSEST(desc->avdd_mv - 6200, 200)) |
+ FIELD_PREP(DSI_CMD2_BK1_PWRCTRL2_AVCL_MASK,
+ DIV_ROUND_CLOSEST(-4400 - desc->avcl_mv, 200)));
+
+ /* T2D = 0.2us * T2D[3:0] */
+ ST7701_DSI(st7701, DSI_CMD2_BK1_SPD1,
+ DSI_CMD2_BK1_SPD1_ONES_MASK |
+ FIELD_PREP(DSI_CMD2_BK1_SPD1_T2D_MASK,
+ DIV_ROUND_CLOSEST(desc->t2d_ns, 200)));
+
+ /* T3D = 4us + (0.8us * T3D[3:0]) */
+ ST7701_DSI(st7701, DSI_CMD2_BK1_SPD2,
+ DSI_CMD2_BK1_SPD2_ONES_MASK |
+ FIELD_PREP(DSI_CMD2_BK1_SPD2_T3D_MASK,
+ DIV_ROUND_CLOSEST(desc->t3d_ns - 4000, 800)));
+
+ ST7701_DSI(st7701, DSI_CMD2_BK1_MIPISET1,
+ DSI_CMD2_BK1_MIPISET1_ONES |
+ (desc->eot_en ? DSI_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_DSI(st7701, 0xE0, 0x00, 0x00, 0x02);
+ ST7701_DSI(st7701, 0xE1, 0x0B, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0E,
+ 0x00, 0x00, 0x44, 0x44);
+ ST7701_DSI(st7701, 0xE2, 0x33, 0x33, 0x44, 0x44, 0x64, 0x00, 0x66,
+ 0x00, 0x65, 0x00, 0x67, 0x00, 0x00);
+ ST7701_DSI(st7701, 0xE3, 0x00, 0x00, 0x33, 0x33);
+ ST7701_DSI(st7701, 0xE4, 0x44, 0x44);
+ ST7701_DSI(st7701, 0xE5, 0x0C, 0x78, 0x3C, 0xA0, 0x0E, 0x78, 0x3C,
+ 0xA0, 0x10, 0x78, 0x3C, 0xA0, 0x12, 0x78, 0x3C, 0xA0);
+ ST7701_DSI(st7701, 0xE6, 0x00, 0x00, 0x33, 0x33);
+ ST7701_DSI(st7701, 0xE7, 0x44, 0x44);
+ ST7701_DSI(st7701, 0xE8, 0x0D, 0x78, 0x3C, 0xA0, 0x0F, 0x78, 0x3C,
+ 0xA0, 0x11, 0x78, 0x3C, 0xA0, 0x13, 0x78, 0x3C, 0xA0);
+ ST7701_DSI(st7701, 0xEB, 0x02, 0x02, 0x39, 0x39, 0xEE, 0x44, 0x00);
+ ST7701_DSI(st7701, 0xEC, 0x00, 0x00);
+ ST7701_DSI(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_DSI(st7701, 0xEE, 0x42);
+ ST7701_DSI(st7701, 0xE0, 0x00, 0x00, 0x02);
+
+ ST7701_DSI(st7701, 0xE1,
+ 0x04, 0xA0, 0x06, 0xA0,
+ 0x05, 0xA0, 0x07, 0xA0,
+ 0x00, 0x44, 0x44);
+ ST7701_DSI(st7701, 0xE2,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00);
+ ST7701_DSI(st7701, 0xE3,
+ 0x00, 0x00, 0x22, 0x22);
+ ST7701_DSI(st7701, 0xE4, 0x44, 0x44);
+ ST7701_DSI(st7701, 0xE5,
+ 0x0C, 0x90, 0xA0, 0xA0,
+ 0x0E, 0x92, 0xA0, 0xA0,
+ 0x08, 0x8C, 0xA0, 0xA0,
+ 0x0A, 0x8E, 0xA0, 0xA0);
+ ST7701_DSI(st7701, 0xE6,
+ 0x00, 0x00, 0x22, 0x22);
+ ST7701_DSI(st7701, 0xE7, 0x44, 0x44);
+ ST7701_DSI(st7701, 0xE8,
+ 0x0D, 0x91, 0xA0, 0xA0,
+ 0x0F, 0x93, 0xA0, 0xA0,
+ 0x09, 0x8D, 0xA0, 0xA0,
+ 0x0B, 0x8F, 0xA0, 0xA0);
+ ST7701_DSI(st7701, 0xEB,
+ 0x00, 0x00, 0xE4, 0xE4,
+ 0x44, 0x00, 0x00);
+ ST7701_DSI(st7701, 0xED,
+ 0xFF, 0xF5, 0x47, 0x6F,
+ 0x0B, 0xA1, 0xAB, 0xFF,
+ 0xFF, 0xBA, 0x1A, 0xB0,
+ 0xF6, 0x74, 0x5F, 0xFF);
+ ST7701_DSI(st7701, 0xEF,
+ 0x08, 0x08, 0x08, 0x40,
+ 0x3F, 0x64);
+
+ ST7701_DSI(st7701, DSI_CMD2BKX_SEL,
+ 0x77, 0x01, 0x00, 0x00, DSI_CMD2BKX_SEL_NONE);
+
+ ST7701_DSI(st7701, DSI_CMD2BKX_SEL,
+ 0x77, 0x01, 0x00, 0x00, DSI_CMD2BK3_SEL);
+ ST7701_DSI(st7701, 0xE6, 0x7C);
+ ST7701_DSI(st7701, 0xE8, 0x00, 0x0E);
+
+ ST7701_DSI(st7701, DSI_CMD2BKX_SEL,
+ 0x77, 0x01, 0x00, 0x00, DSI_CMD2BKX_SEL_NONE);
+ ST7701_DSI(st7701, 0x11);
+ msleep(120);
+
+ ST7701_DSI(st7701, DSI_CMD2BKX_SEL,
+ 0x77, 0x01, 0x00, 0x00, DSI_CMD2BK3_SEL);
+ ST7701_DSI(st7701, 0xE8, 0x00, 0x0C);
+ msleep(10);
+ ST7701_DSI(st7701, 0xE8, 0x00, 0x00);
+
+ ST7701_DSI(st7701, DSI_CMD2BKX_SEL,
+ 0x77, 0x01, 0x00, 0x00, DSI_CMD2BKX_SEL_NONE);
+ ST7701_DSI(st7701, 0x11);
+ msleep(120);
+ ST7701_DSI(st7701, 0xE8, 0x00, 0x00);
+
+ ST7701_DSI(st7701, DSI_CMD2BKX_SEL,
+ 0x77, 0x01, 0x00, 0x00, DSI_CMD2BKX_SEL_NONE);
+
+ ST7701_DSI(st7701, 0x3A, 0x70);
+}
+
+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_DSI(st7701, DSI_CMD2BKX_SEL,
+ 0x77, 0x01, 0x00, 0x00, DSI_CMD2BKX_SEL_NONE);
+
+ return 0;
+}
+
+static int st7701_enable(struct drm_panel *panel)
+{
+ struct st7701 *st7701 = panel_to_st7701(panel);
+
+ ST7701_DSI(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_DSI(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_DSI(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(&st7701->dsi->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;
+
+ return 1;
+}
+
+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,
+};
+
+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(DSI_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC0_MASK, 0),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC4_MASK, 0xe),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC8_MASK, 0x15),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC16_MASK, 0xf),
+
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC24_MASK, 0x11),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC52_MASK, 0x8),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC80_MASK, 0x8),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC108_MASK, 0x8),
+
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC147_MASK, 0x8),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC175_MASK, 0x23),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC203_MASK, 0x4),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC231_MASK, 0x13),
+
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC239_MASK, 0x12),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC247_MASK, 0x2b),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC251_MASK, 0x34),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC255_MASK, 0x1f)
+ },
+ .nv_gamma = {
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC0_MASK, 0),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC4_MASK, 0xe),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_AJ_MASK, 0x2) |
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC8_MASK, 0x15),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC16_MASK, 0xf),
+
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC24_MASK, 0x13),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC52_MASK, 0x7),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC80_MASK, 0x9),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC108_MASK, 0x8),
+
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC147_MASK, 0x8),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC175_MASK, 0x22),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC203_MASK, 0x4),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC231_MASK, 0x10),
+
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC239_MASK, 0xe),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC247_MASK, 0x2c),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC251_MASK, 0x34),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+ CFIELD_PREP(DSI_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(DSI_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC0_MASK, 0),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC4_MASK, 0x10),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC8_MASK, 0x17),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC16_MASK, 0xd),
+
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC24_MASK, 0x11),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC52_MASK, 0x6),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC80_MASK, 0x5),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC108_MASK, 0x8),
+
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC147_MASK, 0x7),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC175_MASK, 0x1f),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC203_MASK, 0x4),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC231_MASK, 0x11),
+
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC239_MASK, 0xe),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC247_MASK, 0x29),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC251_MASK, 0x30),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC255_MASK, 0x1f)
+ },
+ .nv_gamma = {
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC0_MASK, 0),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC4_MASK, 0xd),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC8_MASK, 0x14),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC16_MASK, 0xe),
+
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC24_MASK, 0x11),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC52_MASK, 0x6),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC80_MASK, 0x4),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC108_MASK, 0x8),
+
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC147_MASK, 0x8),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC175_MASK, 0x20),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC203_MASK, 0x5),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC231_MASK, 0x13),
+
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC239_MASK, 0x13),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC247_MASK, 0x26),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_VC251_MASK, 0x30),
+ CFIELD_PREP(DSI_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+ CFIELD_PREP(DSI_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 int st7701_dsi_probe(struct mipi_dsi_device *dsi)
+{
+ const struct st7701_panel_desc *desc;
+ struct st7701 *st7701;
+ int ret;
+
+ st7701 = devm_kzalloc(&dsi->dev, sizeof(*st7701), GFP_KERNEL);
+ if (!st7701)
+ return -ENOMEM;
+
+ desc = of_device_get_match_data(&dsi->dev);
+ dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
+ MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS;
+ dsi->format = desc->format;
+ dsi->lanes = desc->lanes;
+
+ st7701->supplies[0].supply = "VCC";
+ st7701->supplies[1].supply = "IOVCC";
+
+ ret = devm_regulator_bulk_get(&dsi->dev, ARRAY_SIZE(st7701->supplies),
+ st7701->supplies);
+ if (ret < 0)
+ return ret;
+
+ st7701->reset = devm_gpiod_get(&dsi->dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(st7701->reset)) {
+ dev_err(&dsi->dev, "Couldn't get our reset GPIO\n");
+ return PTR_ERR(st7701->reset);
+ }
+
+ drm_panel_init(&st7701->panel, &dsi->dev, &st7701_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ /**
+ * Once sleep out has been issued, ST7701 IC required to wait 120ms
+ * 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);
+
+ mipi_dsi_set_drvdata(dsi, st7701);
+ st7701->dsi = dsi;
+ st7701->desc = desc;
+
+ ret = mipi_dsi_attach(dsi);
+ if (ret)
+ goto err_attach;
+
+ return 0;
+
+err_attach:
+ drm_panel_remove(&st7701->panel);
+ return ret;
+}
+
+static void st7701_dsi_remove(struct mipi_dsi_device *dsi)
+{
+ struct st7701 *st7701 = mipi_dsi_get_drvdata(dsi);
+
+ mipi_dsi_detach(dsi);
+ drm_panel_remove(&st7701->panel);
+}
+
+static const struct of_device_id st7701_of_match[] = {
+ { .compatible = "densitron,dmt028vghmcmi-1a", .data = &dmt028vghmcmi_1a_desc },
+ { .compatible = "techstar,ts8550b", .data = &ts8550b_desc },
+ { }
+};
+MODULE_DEVICE_TABLE(of, st7701_of_match);
+
+static struct mipi_dsi_driver st7701_dsi_driver = {
+ .probe = st7701_dsi_probe,
+ .remove = st7701_dsi_remove,
+ .driver = {
+ .name = "st7701",
+ .of_match_table = st7701_of_match,
+ },
+};
+module_mipi_dsi_driver(st7701_dsi_driver);
+
+MODULE_AUTHOR("Jagan Teki <jagan@amarulasolutions.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 000000000..b6e514aab
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-sitronix-st7703.c
@@ -0,0 +1,638 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for panels based on Sitronix ST7703 controller, souch 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_device.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_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_SETPANEL 0xCC
+#define ST7703_CMD_UNKNOWN_C6 0xC6
+#define ST7703_CMD_SETGAMMA 0xE0
+#define ST7703_CMD_SETEQ 0xE3
+#define ST7703_CMD_SETGIP1 0xE9
+#define ST7703_CMD_SETGIP2 0xEA
+
+struct st7703 {
+ struct device *dev;
+ struct drm_panel panel;
+ struct gpio_desc *reset_gpio;
+ struct regulator *vcc;
+ struct regulator *iovcc;
+ bool prepared;
+
+ struct dentry *debugfs;
+ const struct st7703_panel_desc *desc;
+};
+
+struct st7703_panel_desc {
+ const struct drm_display_mode *mode;
+ unsigned int lanes;
+ unsigned long mode_flags;
+ enum mipi_dsi_pixel_format format;
+ int (*init_sequence)(struct st7703 *ctx);
+};
+
+static inline struct st7703 *panel_to_st7703(struct drm_panel *panel)
+{
+ return container_of(panel, struct st7703, panel);
+}
+
+#define dsi_generic_write_seq(dsi, seq...) do { \
+ static const u8 d[] = { seq }; \
+ int ret; \
+ ret = mipi_dsi_generic_write(dsi, d, ARRAY_SIZE(d)); \
+ if (ret < 0) \
+ return ret; \
+ } while (0)
+
+static int jh057n_init_sequence(struct st7703 *ctx)
+{
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+
+ /*
+ * 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.
+ */
+ dsi_generic_write_seq(dsi, ST7703_CMD_SETEXTC,
+ 0xF1, 0x12, 0x83);
+ dsi_generic_write_seq(dsi, ST7703_CMD_SETRGBIF,
+ 0x10, 0x10, 0x05, 0x05, 0x03, 0xFF, 0x00, 0x00,
+ 0x00, 0x00);
+ dsi_generic_write_seq(dsi, ST7703_CMD_SETSCR,
+ 0x73, 0x73, 0x50, 0x50, 0x00, 0x00, 0x08, 0x70,
+ 0x00);
+ dsi_generic_write_seq(dsi, ST7703_CMD_SETVDC, 0x4E);
+ dsi_generic_write_seq(dsi, ST7703_CMD_SETPANEL, 0x0B);
+ dsi_generic_write_seq(dsi, ST7703_CMD_SETCYC, 0x80);
+ dsi_generic_write_seq(dsi, ST7703_CMD_SETDISP, 0xF0, 0x12, 0x30);
+ dsi_generic_write_seq(dsi, ST7703_CMD_SETEQ,
+ 0x07, 0x07, 0x0B, 0x0B, 0x03, 0x0B, 0x00, 0x00,
+ 0x00, 0x00, 0xFF, 0x00, 0xC0, 0x10);
+ dsi_generic_write_seq(dsi, ST7703_CMD_SETBGP, 0x08, 0x08);
+ msleep(20);
+
+ dsi_generic_write_seq(dsi, ST7703_CMD_SETVCOM, 0x3F, 0x3F);
+ dsi_generic_write_seq(dsi, ST7703_CMD_UNKNOWN_BF, 0x02, 0x11, 0x00);
+ dsi_generic_write_seq(dsi, ST7703_CMD_SETGIP1,
+ 0x82, 0x10, 0x06, 0x05, 0x9E, 0x0A, 0xA5, 0x12,
+ 0x31, 0x23, 0x37, 0x83, 0x04, 0xBC, 0x27, 0x38,
+ 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);
+ dsi_generic_write_seq(dsi, 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);
+ dsi_generic_write_seq(dsi, 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);
+
+ return 0;
+}
+
+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,
+};
+
+#define dsi_dcs_write_seq(dsi, cmd, seq...) do { \
+ static const u8 d[] = { seq }; \
+ int ret; \
+ ret = mipi_dsi_dcs_write(dsi, cmd, d, ARRAY_SIZE(d)); \
+ if (ret < 0) \
+ return ret; \
+ } while (0)
+
+
+static int xbd599_init_sequence(struct st7703 *ctx)
+{
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+
+ /*
+ * Init sequence was supplied by the panel vendor.
+ */
+
+ /* Magic sequence to unlock user commands below. */
+ dsi_dcs_write_seq(dsi, ST7703_CMD_SETEXTC, 0xF1, 0x12, 0x83);
+
+ dsi_dcs_write_seq(dsi, 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);
+
+ dsi_dcs_write_seq(dsi, 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 */
+ dsi_dcs_write_seq(dsi, 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. */
+ dsi_dcs_write_seq(dsi, 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?) */
+ dsi_dcs_write_seq(dsi, ST7703_CMD_SETVDC, 0x4E);
+
+ /*
+ * SS_PANEL = 1 (reverse scan), GS_PANEL = 0 (normal scan)
+ * REV_PANEL = 1 (normally black panel), BGR_PANEL = 1 (BGR)
+ */
+ dsi_dcs_write_seq(dsi, ST7703_CMD_SETPANEL, 0x0B);
+
+ /* Zig-Zag Type C column inversion. */
+ dsi_dcs_write_seq(dsi, ST7703_CMD_SETCYC, 0x80);
+
+ /* Set display resolution. */
+ dsi_dcs_write_seq(dsi, 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
+ */);
+
+ dsi_dcs_write_seq(dsi, 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
+ */);
+
+ /* Undocumented command. */
+ dsi_dcs_write_seq(dsi, ST7703_CMD_UNKNOWN_C6, 0x01, 0x00, 0xFF, 0xFF, 0x00);
+
+ dsi_dcs_write_seq(dsi, 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. */
+ dsi_dcs_write_seq(dsi, ST7703_CMD_SETBGP,
+ 0x07, /* VREF_SEL = 4.2V */
+ 0x07 /* NVREF_SEL = 4.2V */);
+ msleep(20);
+
+ dsi_dcs_write_seq(dsi, ST7703_CMD_SETVCOM,
+ 0x2C, /* VCOMDC_F = -0.67V */
+ 0x2C /* VCOMDC_B = -0.67V */);
+
+ /* Undocumented command. */
+ dsi_dcs_write_seq(dsi, ST7703_CMD_UNKNOWN_BF, 0x02, 0x11, 0x00);
+
+ /* This command is to set forward GIP timing. */
+ dsi_dcs_write_seq(dsi, 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. */
+ dsi_dcs_write_seq(dsi, 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. */
+ dsi_dcs_write_seq(dsi, 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);
+
+ return 0;
+}
+
+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 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);
+ int ret;
+
+ ret = ctx->desc->init_sequence(ctx);
+ if (ret < 0) {
+ dev_err(ctx->dev, "Panel init sequence failed: %d\n", ret);
+ return ret;
+ }
+
+ msleep(20);
+
+ ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
+ if (ret < 0) {
+ dev_err(ctx->dev, "Failed to exit sleep mode: %d\n", ret);
+ return ret;
+ }
+
+ /* Panel is operational 120 msec after reset */
+ msleep(60);
+
+ ret = mipi_dsi_dcs_set_display_on(dsi);
+ if (ret)
+ return ret;
+
+ dev_dbg(ctx->dev, "Panel init sequence done\n");
+
+ return 0;
+}
+
+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);
+ int ret;
+
+ ret = mipi_dsi_dcs_set_display_off(dsi);
+ if (ret < 0)
+ dev_err(ctx->dev, "Failed to turn off the display: %d\n", ret);
+
+ ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
+ if (ret < 0)
+ dev_err(ctx->dev, "Failed to enter sleep mode: %d\n", ret);
+
+ return 0;
+}
+
+static int st7703_unprepare(struct drm_panel *panel)
+{
+ struct st7703 *ctx = panel_to_st7703(panel);
+
+ if (!ctx->prepared)
+ return 0;
+
+ gpiod_set_value_cansleep(ctx->reset_gpio, 1);
+ regulator_disable(ctx->iovcc);
+ regulator_disable(ctx->vcc);
+ ctx->prepared = false;
+
+ return 0;
+}
+
+static int st7703_prepare(struct drm_panel *panel)
+{
+ struct st7703 *ctx = panel_to_st7703(panel);
+ int ret;
+
+ if (ctx->prepared)
+ return 0;
+
+ 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);
+
+ ctx->prepared = true;
+
+ 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 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,
+};
+
+static int allpixelson_set(void *data, u64 val)
+{
+ struct st7703 *ctx = data;
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+
+ dev_dbg(ctx->dev, "Setting all pixels on\n");
+ dsi_generic_write_seq(dsi, ST7703_CMD_ALL_PIXEL_ON);
+ msleep(val * 1000);
+ /* Reset the panel to get video back */
+ drm_panel_disable(&ctx->panel);
+ drm_panel_unprepare(&ctx->panel);
+ drm_panel_prepare(&ctx->panel);
+ drm_panel_enable(&ctx->panel);
+
+ return 0;
+}
+
+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_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ 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");
+
+ drm_panel_init(&ctx->panel, dev, &st7703_drm_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ ret = drm_panel_of_backlight(&ctx->panel);
+ if (ret)
+ return ret;
+
+ drm_panel_add(&ctx->panel);
+
+ ret = mipi_dsi_attach(dsi);
+ if (ret < 0) {
+ 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_shutdown(struct mipi_dsi_device *dsi)
+{
+ struct st7703 *ctx = mipi_dsi_get_drvdata(dsi);
+ int ret;
+
+ ret = drm_panel_unprepare(&ctx->panel);
+ if (ret < 0)
+ dev_err(&dsi->dev, "Failed to unprepare panel: %d\n", ret);
+
+ ret = drm_panel_disable(&ctx->panel);
+ if (ret < 0)
+ dev_err(&dsi->dev, "Failed to disable panel: %d\n", ret);
+}
+
+static void st7703_remove(struct mipi_dsi_device *dsi)
+{
+ struct st7703 *ctx = mipi_dsi_get_drvdata(dsi);
+ int ret;
+
+ st7703_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);
+
+ st7703_debugfs_remove(ctx);
+}
+
+static const struct of_device_id st7703_of_match[] = {
+ { .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,
+ .shutdown = st7703_shutdown,
+ .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
new file mode 100644
index 000000000..bbc4569cb
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-sitronix-st7789v.c
@@ -0,0 +1,415 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2017 Free Electrons
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+
+#include <video/mipi_display.h>
+
+#include <drm/drm_device.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+
+#define ST7789V_COLMOD_RGB_FMT_18BITS (6 << 4)
+#define ST7789V_COLMOD_CTRL_FMT_18BITS (6 << 0)
+
+#define ST7789V_RAMCTRL_CMD 0xb0
+#define ST7789V_RAMCTRL_RM_RGB BIT(4)
+#define ST7789V_RAMCTRL_DM_RGB BIT(0)
+#define ST7789V_RAMCTRL_MAGIC (3 << 6)
+#define ST7789V_RAMCTRL_EPF(n) (((n) & 3) << 4)
+
+#define ST7789V_RGBCTRL_CMD 0xb1
+#define ST7789V_RGBCTRL_WO BIT(7)
+#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_VBP(n) ((n) & 0x7f)
+#define ST7789V_RGBCTRL_HBP(n) ((n) & 0x1f)
+
+#define ST7789V_PORCTRL_CMD 0xb2
+#define ST7789V_PORCTRL_IDLE_BP(n) (((n) & 0xf) << 4)
+#define ST7789V_PORCTRL_IDLE_FP(n) ((n) & 0xf)
+#define ST7789V_PORCTRL_PARTIAL_BP(n) (((n) & 0xf) << 4)
+#define ST7789V_PORCTRL_PARTIAL_FP(n) ((n) & 0xf)
+
+#define ST7789V_GCTRL_CMD 0xb7
+#define ST7789V_GCTRL_VGHS(n) (((n) & 7) << 4)
+#define ST7789V_GCTRL_VGLS(n) ((n) & 7)
+
+#define ST7789V_VCOMS_CMD 0xbb
+
+#define ST7789V_LCMCTRL_CMD 0xc0
+#define ST7789V_LCMCTRL_XBGR BIT(5)
+#define ST7789V_LCMCTRL_XMX BIT(3)
+#define ST7789V_LCMCTRL_XMH BIT(2)
+
+#define ST7789V_VDVVRHEN_CMD 0xc2
+#define ST7789V_VDVVRHEN_CMDEN BIT(0)
+
+#define ST7789V_VRHS_CMD 0xc3
+
+#define ST7789V_VDVS_CMD 0xc4
+
+#define ST7789V_FRCTRL2_CMD 0xc6
+
+#define ST7789V_PWCTRL1_CMD 0xd0
+#define ST7789V_PWCTRL1_MAGIC 0xa4
+#define ST7789V_PWCTRL1_AVDD(n) (((n) & 3) << 6)
+#define ST7789V_PWCTRL1_AVCL(n) (((n) & 3) << 4)
+#define ST7789V_PWCTRL1_VDS(n) ((n) & 3)
+
+#define ST7789V_PVGAMCTRL_CMD 0xe0
+#define ST7789V_PVGAMCTRL_JP0(n) (((n) & 3) << 4)
+#define ST7789V_PVGAMCTRL_JP1(n) (((n) & 3) << 4)
+#define ST7789V_PVGAMCTRL_VP0(n) ((n) & 0xf)
+#define ST7789V_PVGAMCTRL_VP1(n) ((n) & 0x3f)
+#define ST7789V_PVGAMCTRL_VP2(n) ((n) & 0x3f)
+#define ST7789V_PVGAMCTRL_VP4(n) ((n) & 0x1f)
+#define ST7789V_PVGAMCTRL_VP6(n) ((n) & 0x1f)
+#define ST7789V_PVGAMCTRL_VP13(n) ((n) & 0xf)
+#define ST7789V_PVGAMCTRL_VP20(n) ((n) & 0x7f)
+#define ST7789V_PVGAMCTRL_VP27(n) ((n) & 7)
+#define ST7789V_PVGAMCTRL_VP36(n) (((n) & 7) << 4)
+#define ST7789V_PVGAMCTRL_VP43(n) ((n) & 0x7f)
+#define ST7789V_PVGAMCTRL_VP50(n) ((n) & 0xf)
+#define ST7789V_PVGAMCTRL_VP57(n) ((n) & 0x1f)
+#define ST7789V_PVGAMCTRL_VP59(n) ((n) & 0x1f)
+#define ST7789V_PVGAMCTRL_VP61(n) ((n) & 0x3f)
+#define ST7789V_PVGAMCTRL_VP62(n) ((n) & 0x3f)
+#define ST7789V_PVGAMCTRL_VP63(n) (((n) & 0xf) << 4)
+
+#define ST7789V_NVGAMCTRL_CMD 0xe1
+#define ST7789V_NVGAMCTRL_JN0(n) (((n) & 3) << 4)
+#define ST7789V_NVGAMCTRL_JN1(n) (((n) & 3) << 4)
+#define ST7789V_NVGAMCTRL_VN0(n) ((n) & 0xf)
+#define ST7789V_NVGAMCTRL_VN1(n) ((n) & 0x3f)
+#define ST7789V_NVGAMCTRL_VN2(n) ((n) & 0x3f)
+#define ST7789V_NVGAMCTRL_VN4(n) ((n) & 0x1f)
+#define ST7789V_NVGAMCTRL_VN6(n) ((n) & 0x1f)
+#define ST7789V_NVGAMCTRL_VN13(n) ((n) & 0xf)
+#define ST7789V_NVGAMCTRL_VN20(n) ((n) & 0x7f)
+#define ST7789V_NVGAMCTRL_VN27(n) ((n) & 7)
+#define ST7789V_NVGAMCTRL_VN36(n) (((n) & 7) << 4)
+#define ST7789V_NVGAMCTRL_VN43(n) ((n) & 0x7f)
+#define ST7789V_NVGAMCTRL_VN50(n) ((n) & 0xf)
+#define ST7789V_NVGAMCTRL_VN57(n) ((n) & 0x1f)
+#define ST7789V_NVGAMCTRL_VN59(n) ((n) & 0x1f)
+#define ST7789V_NVGAMCTRL_VN61(n) ((n) & 0x3f)
+#define ST7789V_NVGAMCTRL_VN62(n) ((n) & 0x3f)
+#define ST7789V_NVGAMCTRL_VN63(n) (((n) & 0xf) << 4)
+
+#define ST7789V_TEST(val, func) \
+ do { \
+ if ((val = (func))) \
+ return val; \
+ } while (0)
+
+struct st7789v {
+ struct drm_panel panel;
+ struct spi_device *spi;
+ struct gpio_desc *reset;
+ struct regulator *power;
+};
+
+enum st7789v_prefix {
+ ST7789V_COMMAND = 0,
+ ST7789V_DATA = 1,
+};
+
+static inline struct st7789v *panel_to_st7789v(struct drm_panel *panel)
+{
+ return container_of(panel, struct st7789v, panel);
+}
+
+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);
+}
+
+static int st7789v_write_command(struct st7789v *ctx, u8 cmd)
+{
+ return st7789v_spi_write(ctx, ST7789V_COMMAND, cmd);
+}
+
+static int st7789v_write_data(struct st7789v *ctx, u8 cmd)
+{
+ return st7789v_spi_write(ctx, ST7789V_DATA, cmd);
+}
+
+static const struct drm_display_mode default_mode = {
+ .clock = 7000,
+ .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,
+};
+
+static int st7789v_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);
+
+ mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+ drm_mode_probed_add(connector, mode);
+
+ connector->display_info.width_mm = 61;
+ connector->display_info.height_mm = 103;
+
+ return 1;
+}
+
+static int st7789v_prepare(struct drm_panel *panel)
+{
+ struct st7789v *ctx = panel_to_st7789v(panel);
+ int ret;
+
+ ret = regulator_enable(ctx->power);
+ if (ret)
+ return ret;
+
+ gpiod_set_value(ctx->reset, 1);
+ msleep(30);
+ gpiod_set_value(ctx->reset, 0);
+ msleep(120);
+
+ ST7789V_TEST(ret, st7789v_write_command(ctx, MIPI_DCS_EXIT_SLEEP_MODE));
+
+ /* We need to wait 120ms after a sleep out command */
+ msleep(120);
+
+ ST7789V_TEST(ret, st7789v_write_command(ctx,
+ MIPI_DCS_SET_ADDRESS_MODE));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, 0));
+
+ 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_command(ctx, ST7789V_PORCTRL_CMD));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, 0xc));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, 0xc));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, 0));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_PORCTRL_IDLE_BP(3) |
+ ST7789V_PORCTRL_IDLE_FP(3)));
+ ST7789V_TEST(ret, st7789v_write_data(ctx,
+ ST7789V_PORCTRL_PARTIAL_BP(3) |
+ ST7789V_PORCTRL_PARTIAL_FP(3)));
+
+ ST7789V_TEST(ret, st7789v_write_command(ctx, ST7789V_GCTRL_CMD));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_GCTRL_VGLS(5) |
+ ST7789V_GCTRL_VGHS(3)));
+
+ ST7789V_TEST(ret, st7789v_write_command(ctx, ST7789V_VCOMS_CMD));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, 0x2b));
+
+ ST7789V_TEST(ret, st7789v_write_command(ctx, ST7789V_LCMCTRL_CMD));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_LCMCTRL_XMH |
+ ST7789V_LCMCTRL_XMX |
+ ST7789V_LCMCTRL_XBGR));
+
+ ST7789V_TEST(ret, st7789v_write_command(ctx, ST7789V_VDVVRHEN_CMD));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_VDVVRHEN_CMDEN));
+
+ ST7789V_TEST(ret, st7789v_write_command(ctx, ST7789V_VRHS_CMD));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, 0xf));
+
+ ST7789V_TEST(ret, st7789v_write_command(ctx, ST7789V_VDVS_CMD));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, 0x20));
+
+ ST7789V_TEST(ret, st7789v_write_command(ctx, ST7789V_FRCTRL2_CMD));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, 0xf));
+
+ ST7789V_TEST(ret, st7789v_write_command(ctx, ST7789V_PWCTRL1_CMD));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_PWCTRL1_MAGIC));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_PWCTRL1_AVDD(2) |
+ ST7789V_PWCTRL1_AVCL(2) |
+ ST7789V_PWCTRL1_VDS(1)));
+
+ ST7789V_TEST(ret, st7789v_write_command(ctx, ST7789V_PVGAMCTRL_CMD));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_PVGAMCTRL_VP63(0xd)));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_PVGAMCTRL_VP1(0xca)));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_PVGAMCTRL_VP2(0xe)));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_PVGAMCTRL_VP4(8)));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_PVGAMCTRL_VP6(9)));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_PVGAMCTRL_VP13(7)));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_PVGAMCTRL_VP20(0x2d)));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_PVGAMCTRL_VP27(0xb) |
+ ST7789V_PVGAMCTRL_VP36(3)));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_PVGAMCTRL_VP43(0x3d)));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_PVGAMCTRL_JP1(3) |
+ ST7789V_PVGAMCTRL_VP50(4)));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_PVGAMCTRL_VP57(0xa)));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_PVGAMCTRL_VP59(0xa)));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_PVGAMCTRL_VP61(0x1b)));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_PVGAMCTRL_VP62(0x28)));
+
+ ST7789V_TEST(ret, st7789v_write_command(ctx, ST7789V_NVGAMCTRL_CMD));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_NVGAMCTRL_VN63(0xd)));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_NVGAMCTRL_VN1(0xca)));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_NVGAMCTRL_VN2(0xf)));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_NVGAMCTRL_VN4(8)));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_NVGAMCTRL_VN6(8)));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_NVGAMCTRL_VN13(7)));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_NVGAMCTRL_VN20(0x2e)));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_NVGAMCTRL_VN27(0xc) |
+ ST7789V_NVGAMCTRL_VN36(5)));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_NVGAMCTRL_VN43(0x40)));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_NVGAMCTRL_JN1(3) |
+ ST7789V_NVGAMCTRL_VN50(4)));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_NVGAMCTRL_VN57(9)));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_NVGAMCTRL_VN59(0xb)));
+ 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));
+
+ ST7789V_TEST(ret, st7789v_write_command(ctx, ST7789V_RAMCTRL_CMD));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_RAMCTRL_DM_RGB |
+ ST7789V_RAMCTRL_RM_RGB));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_RAMCTRL_EPF(3) |
+ ST7789V_RAMCTRL_MAGIC));
+
+ ST7789V_TEST(ret, st7789v_write_command(ctx, ST7789V_RGBCTRL_CMD));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_RGBCTRL_WO |
+ ST7789V_RGBCTRL_RCM(2) |
+ ST7789V_RGBCTRL_VSYNC_HIGH |
+ ST7789V_RGBCTRL_HSYNC_HIGH |
+ ST7789V_RGBCTRL_PCLK_HIGH));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_RGBCTRL_VBP(8)));
+ ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_RGBCTRL_HBP(20)));
+
+ return 0;
+}
+
+static int st7789v_enable(struct drm_panel *panel)
+{
+ struct st7789v *ctx = panel_to_st7789v(panel);
+
+ return st7789v_write_command(ctx, MIPI_DCS_SET_DISPLAY_ON);
+}
+
+static int st7789v_disable(struct drm_panel *panel)
+{
+ struct st7789v *ctx = panel_to_st7789v(panel);
+ int ret;
+
+ ST7789V_TEST(ret, st7789v_write_command(ctx, MIPI_DCS_SET_DISPLAY_OFF));
+
+ return 0;
+}
+
+static int st7789v_unprepare(struct drm_panel *panel)
+{
+ struct st7789v *ctx = panel_to_st7789v(panel);
+ int ret;
+
+ ST7789V_TEST(ret, st7789v_write_command(ctx, MIPI_DCS_ENTER_SLEEP_MODE));
+
+ regulator_disable(ctx->power);
+
+ return 0;
+}
+
+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,
+};
+
+static int st7789v_probe(struct spi_device *spi)
+{
+ struct st7789v *ctx;
+ int ret;
+
+ ctx = devm_kzalloc(&spi->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ spi_set_drvdata(spi, ctx);
+ ctx->spi = spi;
+
+ drm_panel_init(&ctx->panel, &spi->dev, &st7789v_drm_funcs,
+ DRM_MODE_CONNECTOR_DPI);
+
+ ctx->power = devm_regulator_get(&spi->dev, "power");
+ if (IS_ERR(ctx->power))
+ return PTR_ERR(ctx->power);
+
+ 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);
+ }
+
+ ret = drm_panel_of_backlight(&ctx->panel);
+ if (ret)
+ return ret;
+
+ drm_panel_add(&ctx->panel);
+
+ return 0;
+}
+
+static void st7789v_remove(struct spi_device *spi)
+{
+ struct st7789v *ctx = spi_get_drvdata(spi);
+
+ drm_panel_remove(&ctx->panel);
+}
+
+static const struct of_device_id st7789v_of_match[] = {
+ { .compatible = "sitronix,st7789v" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, st7789v_of_match);
+
+static struct spi_driver st7789v_driver = {
+ .probe = st7789v_probe,
+ .remove = st7789v_remove,
+ .driver = {
+ .name = "st7789v",
+ .of_match_table = st7789v_of_match,
+ },
+};
+module_spi_driver(st7789v_driver);
+
+MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
+MODULE_DESCRIPTION("Sitronix st7789v LCD Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/panel/panel-sony-acx565akm.c b/drivers/gpu/drm/panel/panel-sony-acx565akm.c
new file mode 100644
index 000000000..3d6a28605
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-sony-acx565akm.c
@@ -0,0 +1,691 @@
+// 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 = FB_BLANK_UNBLANK,
+ .type = BACKLIGHT_RAW,
+ };
+ int ret;
+
+ lcd->backlight = backlight_device_register(lcd->name, &lcd->spi->dev,
+ lcd, &acx565akm_bl_ops,
+ &props);
+ if (IS_ERR(lcd->backlight)) {
+ ret = PTR_ERR(lcd->backlight);
+ lcd->backlight = NULL;
+ return ret;
+ }
+
+ if (lcd->has_cabc) {
+ ret = sysfs_create_group(&lcd->backlight->dev.kobj,
+ &acx565akm_cabc_attr_group);
+ if (ret < 0) {
+ dev_err(&lcd->spi->dev,
+ "%s failed to create sysfs files\n", __func__);
+ backlight_device_unregister(lcd->backlight);
+ return ret;
+ }
+
+ lcd->cabc_mode = acx565akm_get_hw_cabc_mode(lcd);
+ }
+
+ lcd->backlight->props.max_brightness = 255;
+ lcd->backlight->props.brightness = acx565akm_get_actual_brightness(lcd);
+
+ acx565akm_bl_update_status_locked(lcd->backlight);
+
+ return 0;
+}
+
+static void acx565akm_backlight_cleanup(struct acx565akm_panel *lcd)
+{
+ if (lcd->has_cabc)
+ sysfs_remove_group(&lcd->backlight->dev.kobj,
+ &acx565akm_cabc_attr_group);
+
+ backlight_device_unregister(lcd->backlight);
+}
+
+/* -----------------------------------------------------------------------------
+ * DRM Bridge Operations
+ */
+
+static void acx565akm_set_sleep_mode(struct acx565akm_panel *lcd, int on)
+{
+ int cmd = on ? MIPI_DCS_ENTER_SLEEP_MODE : MIPI_DCS_EXIT_SLEEP_MODE;
+ unsigned long wait;
+
+ /*
+ * We have to keep 120msec between sleep in/out commands.
+ * (8.2.15, 8.2.16).
+ */
+ wait = lcd->hw_guard_end - jiffies;
+ if ((long)wait > 0 && wait <= lcd->hw_guard_wait) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(wait);
+ }
+
+ acx565akm_cmd(lcd, cmd);
+
+ lcd->hw_guard_wait = msecs_to_jiffies(120);
+ lcd->hw_guard_end = jiffies + lcd->hw_guard_wait;
+}
+
+static void acx565akm_set_display_state(struct acx565akm_panel *lcd,
+ int enabled)
+{
+ int cmd = enabled ? MIPI_DCS_SET_DISPLAY_ON : MIPI_DCS_SET_DISPLAY_OFF;
+
+ acx565akm_cmd(lcd, cmd);
+}
+
+static int acx565akm_power_on(struct acx565akm_panel *lcd)
+{
+ /*FIXME tweak me */
+ msleep(50);
+
+ gpiod_set_value(lcd->reset_gpio, 1);
+
+ if (lcd->enabled) {
+ dev_dbg(&lcd->spi->dev, "panel already enabled\n");
+ return 0;
+ }
+
+ /*
+ * We have to meet all the following delay requirements:
+ * 1. tRW: reset pulse width 10usec (7.12.1)
+ * 2. tRT: reset cancel time 5msec (7.12.1)
+ * 3. Providing PCLK,HS,VS signals for 2 frames = ~50msec worst
+ * case (7.6.2)
+ * 4. 120msec before the sleep out command (7.12.1)
+ */
+ msleep(120);
+
+ acx565akm_set_sleep_mode(lcd, 0);
+ lcd->enabled = true;
+
+ /* 5msec between sleep out and the next command. (8.2.16) */
+ usleep_range(5000, 10000);
+ acx565akm_set_display_state(lcd, 1);
+ acx565akm_set_cabc_mode(lcd, lcd->cabc_mode);
+
+ return acx565akm_bl_update_status_locked(lcd->backlight);
+}
+
+static void acx565akm_power_off(struct acx565akm_panel *lcd)
+{
+ if (!lcd->enabled)
+ return;
+
+ acx565akm_set_display_state(lcd, 0);
+ acx565akm_set_sleep_mode(lcd, 1);
+ lcd->enabled = false;
+ /*
+ * We have to provide PCLK,HS,VS signals for 2 frames (worst case
+ * ~50msec) after sending the sleep in command and asserting the
+ * reset signal. We probably could assert the reset w/o the delay
+ * but we still delay to avoid possible artifacts. (7.6.1)
+ */
+ msleep(50);
+
+ gpiod_set_value(lcd->reset_gpio, 0);
+
+ /* FIXME need to tweak this delay */
+ msleep(100);
+}
+
+static int acx565akm_disable(struct drm_panel *panel)
+{
+ struct acx565akm_panel *lcd = to_acx565akm_device(panel);
+
+ mutex_lock(&lcd->mutex);
+ acx565akm_power_off(lcd);
+ mutex_unlock(&lcd->mutex);
+
+ return 0;
+}
+
+static int acx565akm_enable(struct drm_panel *panel)
+{
+ struct acx565akm_panel *lcd = to_acx565akm_device(panel);
+
+ mutex_lock(&lcd->mutex);
+ acx565akm_power_on(lcd);
+ mutex_unlock(&lcd->mutex);
+
+ return 0;
+}
+
+static const struct drm_display_mode acx565akm_mode = {
+ .clock = 24000,
+ .hdisplay = 800,
+ .hsync_start = 800 + 28,
+ .hsync_end = 800 + 28 + 4,
+ .htotal = 800 + 28 + 4 + 24,
+ .vdisplay = 480,
+ .vsync_start = 480 + 3,
+ .vsync_end = 480 + 3 + 3,
+ .vtotal = 480 + 3 + 3 + 4,
+ .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
+ .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
+ .width_mm = 77,
+ .height_mm = 46,
+};
+
+static int acx565akm_get_modes(struct drm_panel *panel,
+ struct drm_connector *connector)
+{
+ struct drm_display_mode *mode;
+
+ mode = drm_mode_duplicate(connector->dev, &acx565akm_mode);
+ if (!mode)
+ return -ENOMEM;
+
+ drm_mode_set_name(mode);
+ drm_mode_probed_add(connector, mode);
+
+ connector->display_info.width_mm = acx565akm_mode.width_mm;
+ connector->display_info.height_mm = acx565akm_mode.height_mm;
+ connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH
+ | DRM_BUS_FLAG_SYNC_SAMPLE_POSEDGE
+ | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE;
+
+ return 1;
+}
+
+static const struct drm_panel_funcs acx565akm_funcs = {
+ .disable = acx565akm_disable,
+ .enable = acx565akm_enable,
+ .get_modes = acx565akm_get_modes,
+};
+
+/* -----------------------------------------------------------------------------
+ * Probe, Detect and Remove
+ */
+
+static int acx565akm_detect(struct acx565akm_panel *lcd)
+{
+ __be32 value;
+ u32 status;
+ int ret = 0;
+
+ /*
+ * After being taken out of reset the panel needs 5ms before the first
+ * command can be sent.
+ */
+ gpiod_set_value(lcd->reset_gpio, 1);
+ usleep_range(5000, 10000);
+
+ acx565akm_read(lcd, MIPI_DCS_GET_DISPLAY_STATUS, (u8 *)&value, 4);
+ status = __be32_to_cpu(value);
+ lcd->enabled = (status & (1 << 17)) && (status & (1 << 10));
+
+ dev_dbg(&lcd->spi->dev,
+ "LCD panel %s by bootloader (status 0x%04x)\n",
+ lcd->enabled ? "enabled" : "disabled ", status);
+
+ acx565akm_read(lcd, MIPI_DCS_GET_DISPLAY_ID, lcd->display_id, 3);
+ dev_dbg(&lcd->spi->dev, "MIPI display ID: %02x%02x%02x\n",
+ lcd->display_id[0], lcd->display_id[1], lcd->display_id[2]);
+
+ switch (lcd->display_id[0]) {
+ case 0x10:
+ lcd->model = MIPID_VER_ACX565AKM;
+ lcd->name = "acx565akm";
+ lcd->has_bc = 1;
+ lcd->has_cabc = 1;
+ break;
+ case 0x29:
+ lcd->model = MIPID_VER_L4F00311;
+ lcd->name = "l4f00311";
+ break;
+ case 0x45:
+ lcd->model = MIPID_VER_LPH8923;
+ lcd->name = "lph8923";
+ break;
+ case 0x83:
+ lcd->model = MIPID_VER_LS041Y3;
+ lcd->name = "ls041y3";
+ break;
+ default:
+ lcd->name = "unknown";
+ dev_err(&lcd->spi->dev, "unknown display ID\n");
+ ret = -ENODEV;
+ goto done;
+ }
+
+ lcd->revision = lcd->display_id[1];
+
+ dev_info(&lcd->spi->dev, "%s rev %02x panel detected\n",
+ lcd->name, lcd->revision);
+
+done:
+ if (!lcd->enabled)
+ gpiod_set_value(lcd->reset_gpio, 0);
+
+ return ret;
+}
+
+static int acx565akm_probe(struct spi_device *spi)
+{
+ struct acx565akm_panel *lcd;
+ int ret;
+
+ lcd = devm_kzalloc(&spi->dev, sizeof(*lcd), GFP_KERNEL);
+ if (!lcd)
+ return -ENOMEM;
+
+ spi_set_drvdata(spi, lcd);
+ spi->mode = SPI_MODE_3;
+
+ lcd->spi = spi;
+ mutex_init(&lcd->mutex);
+
+ lcd->reset_gpio = devm_gpiod_get(&spi->dev, "reset", GPIOD_OUT_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_init(&lcd->panel, &lcd->spi->dev, &acx565akm_funcs,
+ DRM_MODE_CONNECTOR_DPI);
+
+ 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);
+
+ drm_panel_disable(&lcd->panel);
+ drm_panel_unprepare(&lcd->panel);
+}
+
+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-tulip-truly-nt35521.c b/drivers/gpu/drm/panel/panel-sony-tulip-truly-nt35521.c
new file mode 100644
index 000000000..fa9be3c29
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-sony-tulip-truly-nt35521.c
@@ -0,0 +1,550 @@
+// 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;
+ bool prepared;
+ bool enabled;
+};
+
+static inline
+struct truly_nt35521 *to_truly_nt35521(struct drm_panel *panel)
+{
+ return container_of(panel, struct truly_nt35521, panel);
+}
+
+#define dsi_generic_write_seq(dsi, seq...) do { \
+ static const u8 d[] = { seq }; \
+ int ret; \
+ ret = mipi_dsi_generic_write(dsi, d, ARRAY_SIZE(d)); \
+ if (ret < 0) \
+ return ret; \
+ } while (0)
+
+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 device *dev = &dsi->dev;
+ int ret;
+
+ dsi->mode_flags |= MIPI_DSI_MODE_LPM;
+
+ dsi_generic_write_seq(dsi, 0xf0, 0x55, 0xaa, 0x52, 0x08, 0x00);
+ dsi_generic_write_seq(dsi, 0xff, 0xaa, 0x55, 0xa5, 0x80);
+ dsi_generic_write_seq(dsi, 0x6f, 0x11, 0x00);
+ dsi_generic_write_seq(dsi, 0xf7, 0x20, 0x00);
+ dsi_generic_write_seq(dsi, 0x6f, 0x01);
+ dsi_generic_write_seq(dsi, 0xb1, 0x21);
+ dsi_generic_write_seq(dsi, 0xbd, 0x01, 0xa0, 0x10, 0x08, 0x01);
+ dsi_generic_write_seq(dsi, 0xb8, 0x01, 0x02, 0x0c, 0x02);
+ dsi_generic_write_seq(dsi, 0xbb, 0x11, 0x11);
+ dsi_generic_write_seq(dsi, 0xbc, 0x00, 0x00);
+ dsi_generic_write_seq(dsi, 0xb6, 0x02);
+ dsi_generic_write_seq(dsi, 0xf0, 0x55, 0xaa, 0x52, 0x08, 0x01);
+ dsi_generic_write_seq(dsi, 0xb0, 0x09, 0x09);
+ dsi_generic_write_seq(dsi, 0xb1, 0x09, 0x09);
+ dsi_generic_write_seq(dsi, 0xbc, 0x8c, 0x00);
+ dsi_generic_write_seq(dsi, 0xbd, 0x8c, 0x00);
+ dsi_generic_write_seq(dsi, 0xca, 0x00);
+ dsi_generic_write_seq(dsi, 0xc0, 0x04);
+ dsi_generic_write_seq(dsi, 0xbe, 0xb5);
+ dsi_generic_write_seq(dsi, 0xb3, 0x35, 0x35);
+ dsi_generic_write_seq(dsi, 0xb4, 0x25, 0x25);
+ dsi_generic_write_seq(dsi, 0xb9, 0x43, 0x43);
+ dsi_generic_write_seq(dsi, 0xba, 0x24, 0x24);
+ dsi_generic_write_seq(dsi, 0xf0, 0x55, 0xaa, 0x52, 0x08, 0x02);
+ dsi_generic_write_seq(dsi, 0xee, 0x03);
+ dsi_generic_write_seq(dsi, 0xb0,
+ 0x00, 0xb2, 0x00, 0xb3, 0x00, 0xb6, 0x00, 0xc3,
+ 0x00, 0xce, 0x00, 0xe1, 0x00, 0xf3, 0x01, 0x11);
+ dsi_generic_write_seq(dsi, 0xb1,
+ 0x01, 0x2e, 0x01, 0x5c, 0x01, 0x82, 0x01, 0xc3,
+ 0x01, 0xfe, 0x02, 0x00, 0x02, 0x37, 0x02, 0x77);
+ dsi_generic_write_seq(dsi, 0xb2,
+ 0x02, 0xa1, 0x02, 0xd7, 0x02, 0xfe, 0x03, 0x2c,
+ 0x03, 0x4b, 0x03, 0x63, 0x03, 0x8f, 0x03, 0x90);
+ dsi_generic_write_seq(dsi, 0xb3, 0x03, 0x96, 0x03, 0x98);
+ dsi_generic_write_seq(dsi, 0xb4,
+ 0x00, 0x81, 0x00, 0x8b, 0x00, 0x9c, 0x00, 0xa9,
+ 0x00, 0xb5, 0x00, 0xcb, 0x00, 0xdf, 0x01, 0x02);
+ dsi_generic_write_seq(dsi, 0xb5,
+ 0x01, 0x1f, 0x01, 0x51, 0x01, 0x7a, 0x01, 0xbf,
+ 0x01, 0xfa, 0x01, 0xfc, 0x02, 0x34, 0x02, 0x76);
+ dsi_generic_write_seq(dsi, 0xb6,
+ 0x02, 0x9f, 0x02, 0xd7, 0x02, 0xfc, 0x03, 0x2c,
+ 0x03, 0x4a, 0x03, 0x63, 0x03, 0x8f, 0x03, 0xa2);
+ dsi_generic_write_seq(dsi, 0xb7, 0x03, 0xb8, 0x03, 0xba);
+ dsi_generic_write_seq(dsi, 0xb8,
+ 0x00, 0x01, 0x00, 0x02, 0x00, 0x0e, 0x00, 0x2a,
+ 0x00, 0x41, 0x00, 0x67, 0x00, 0x87, 0x00, 0xb9);
+ dsi_generic_write_seq(dsi, 0xb9,
+ 0x00, 0xe2, 0x01, 0x22, 0x01, 0x54, 0x01, 0xa3,
+ 0x01, 0xe6, 0x01, 0xe7, 0x02, 0x24, 0x02, 0x67);
+ dsi_generic_write_seq(dsi, 0xba,
+ 0x02, 0x93, 0x02, 0xcd, 0x02, 0xf6, 0x03, 0x31,
+ 0x03, 0x6c, 0x03, 0xe9, 0x03, 0xef, 0x03, 0xf4);
+ dsi_generic_write_seq(dsi, 0xbb, 0x03, 0xf6, 0x03, 0xf7);
+ dsi_generic_write_seq(dsi, 0xf0, 0x55, 0xaa, 0x52, 0x08, 0x03);
+ dsi_generic_write_seq(dsi, 0xb0, 0x22, 0x00);
+ dsi_generic_write_seq(dsi, 0xb1, 0x22, 0x00);
+ dsi_generic_write_seq(dsi, 0xb2, 0x05, 0x00, 0x60, 0x00, 0x00);
+ dsi_generic_write_seq(dsi, 0xb3, 0x05, 0x00, 0x60, 0x00, 0x00);
+ dsi_generic_write_seq(dsi, 0xb4, 0x05, 0x00, 0x60, 0x00, 0x00);
+ dsi_generic_write_seq(dsi, 0xb5, 0x05, 0x00, 0x60, 0x00, 0x00);
+ dsi_generic_write_seq(dsi, 0xba, 0x53, 0x00, 0x60, 0x00, 0x00);
+ dsi_generic_write_seq(dsi, 0xbb, 0x53, 0x00, 0x60, 0x00, 0x00);
+ dsi_generic_write_seq(dsi, 0xbc, 0x53, 0x00, 0x60, 0x00, 0x00);
+ dsi_generic_write_seq(dsi, 0xbd, 0x53, 0x00, 0x60, 0x00, 0x00);
+ dsi_generic_write_seq(dsi, 0xc0, 0x00, 0x34, 0x00, 0x00);
+ dsi_generic_write_seq(dsi, 0xc1, 0x00, 0x00, 0x34, 0x00);
+ dsi_generic_write_seq(dsi, 0xc2, 0x00, 0x00, 0x34, 0x00);
+ dsi_generic_write_seq(dsi, 0xc3, 0x00, 0x00, 0x34, 0x00);
+ dsi_generic_write_seq(dsi, 0xc4, 0x60);
+ dsi_generic_write_seq(dsi, 0xc5, 0xc0);
+ dsi_generic_write_seq(dsi, 0xc6, 0x00);
+ dsi_generic_write_seq(dsi, 0xc7, 0x00);
+ dsi_generic_write_seq(dsi, 0xf0, 0x55, 0xaa, 0x52, 0x08, 0x05);
+ dsi_generic_write_seq(dsi, 0xb0, 0x17, 0x06);
+ dsi_generic_write_seq(dsi, 0xb1, 0x17, 0x06);
+ dsi_generic_write_seq(dsi, 0xb2, 0x17, 0x06);
+ dsi_generic_write_seq(dsi, 0xb3, 0x17, 0x06);
+ dsi_generic_write_seq(dsi, 0xb4, 0x17, 0x06);
+ dsi_generic_write_seq(dsi, 0xb5, 0x17, 0x06);
+ dsi_generic_write_seq(dsi, 0xb6, 0x17, 0x06);
+ dsi_generic_write_seq(dsi, 0xb7, 0x17, 0x06);
+ dsi_generic_write_seq(dsi, 0xb8, 0x00);
+ dsi_generic_write_seq(dsi, 0xb9, 0x00, 0x03);
+ dsi_generic_write_seq(dsi, 0xba, 0x00, 0x00);
+ dsi_generic_write_seq(dsi, 0xbb, 0x02, 0x03);
+ dsi_generic_write_seq(dsi, 0xbc, 0x02, 0x03);
+ dsi_generic_write_seq(dsi, 0xbd, 0x03, 0x03, 0x00, 0x03, 0x03);
+ dsi_generic_write_seq(dsi, 0xc0, 0x0b);
+ dsi_generic_write_seq(dsi, 0xc1, 0x09);
+ dsi_generic_write_seq(dsi, 0xc2, 0xa6);
+ dsi_generic_write_seq(dsi, 0xc3, 0x05);
+ dsi_generic_write_seq(dsi, 0xc4, 0x00);
+ dsi_generic_write_seq(dsi, 0xc5, 0x02);
+ dsi_generic_write_seq(dsi, 0xc6, 0x22);
+ dsi_generic_write_seq(dsi, 0xc7, 0x03);
+ dsi_generic_write_seq(dsi, 0xc8, 0x07, 0x20);
+ dsi_generic_write_seq(dsi, 0xc9, 0x03, 0x20);
+ dsi_generic_write_seq(dsi, 0xca, 0x01, 0x60);
+ dsi_generic_write_seq(dsi, 0xcb, 0x01, 0x60);
+ dsi_generic_write_seq(dsi, 0xcc, 0x00, 0x00, 0x02);
+ dsi_generic_write_seq(dsi, 0xcd, 0x00, 0x00, 0x02);
+ dsi_generic_write_seq(dsi, 0xce, 0x00, 0x00, 0x02);
+ dsi_generic_write_seq(dsi, 0xcf, 0x00, 0x00, 0x02);
+ dsi_generic_write_seq(dsi, 0xd1, 0x00, 0x05, 0x01, 0x07, 0x10);
+ dsi_generic_write_seq(dsi, 0xd2, 0x10, 0x05, 0x05, 0x03, 0x10);
+ dsi_generic_write_seq(dsi, 0xd3, 0x20, 0x00, 0x43, 0x07, 0x10);
+ dsi_generic_write_seq(dsi, 0xd4, 0x30, 0x00, 0x43, 0x07, 0x10);
+ dsi_generic_write_seq(dsi, 0xd0,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
+ dsi_generic_write_seq(dsi, 0xd5,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00);
+ dsi_generic_write_seq(dsi, 0xd6,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00);
+ dsi_generic_write_seq(dsi, 0xd7,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00);
+ dsi_generic_write_seq(dsi, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00);
+ dsi_generic_write_seq(dsi, 0xe5, 0x06);
+ dsi_generic_write_seq(dsi, 0xe6, 0x06);
+ dsi_generic_write_seq(dsi, 0xe7, 0x00);
+ dsi_generic_write_seq(dsi, 0xe8, 0x06);
+ dsi_generic_write_seq(dsi, 0xe9, 0x06);
+ dsi_generic_write_seq(dsi, 0xea, 0x06);
+ dsi_generic_write_seq(dsi, 0xeb, 0x00);
+ dsi_generic_write_seq(dsi, 0xec, 0x00);
+ dsi_generic_write_seq(dsi, 0xed, 0x30);
+ dsi_generic_write_seq(dsi, 0xf0, 0x55, 0xaa, 0x52, 0x08, 0x06);
+ dsi_generic_write_seq(dsi, 0xb0, 0x31, 0x31);
+ dsi_generic_write_seq(dsi, 0xb1, 0x31, 0x31);
+ dsi_generic_write_seq(dsi, 0xb2, 0x2d, 0x2e);
+ dsi_generic_write_seq(dsi, 0xb3, 0x31, 0x34);
+ dsi_generic_write_seq(dsi, 0xb4, 0x29, 0x2a);
+ dsi_generic_write_seq(dsi, 0xb5, 0x12, 0x10);
+ dsi_generic_write_seq(dsi, 0xb6, 0x18, 0x16);
+ dsi_generic_write_seq(dsi, 0xb7, 0x00, 0x02);
+ dsi_generic_write_seq(dsi, 0xb8, 0x08, 0x31);
+ dsi_generic_write_seq(dsi, 0xb9, 0x31, 0x31);
+ dsi_generic_write_seq(dsi, 0xba, 0x31, 0x31);
+ dsi_generic_write_seq(dsi, 0xbb, 0x31, 0x08);
+ dsi_generic_write_seq(dsi, 0xbc, 0x03, 0x01);
+ dsi_generic_write_seq(dsi, 0xbd, 0x17, 0x19);
+ dsi_generic_write_seq(dsi, 0xbe, 0x11, 0x13);
+ dsi_generic_write_seq(dsi, 0xbf, 0x2a, 0x29);
+ dsi_generic_write_seq(dsi, 0xc0, 0x34, 0x31);
+ dsi_generic_write_seq(dsi, 0xc1, 0x2e, 0x2d);
+ dsi_generic_write_seq(dsi, 0xc2, 0x31, 0x31);
+ dsi_generic_write_seq(dsi, 0xc3, 0x31, 0x31);
+ dsi_generic_write_seq(dsi, 0xc4, 0x31, 0x31);
+ dsi_generic_write_seq(dsi, 0xc5, 0x31, 0x31);
+ dsi_generic_write_seq(dsi, 0xc6, 0x2e, 0x2d);
+ dsi_generic_write_seq(dsi, 0xc7, 0x31, 0x34);
+ dsi_generic_write_seq(dsi, 0xc8, 0x29, 0x2a);
+ dsi_generic_write_seq(dsi, 0xc9, 0x17, 0x19);
+ dsi_generic_write_seq(dsi, 0xca, 0x11, 0x13);
+ dsi_generic_write_seq(dsi, 0xcb, 0x03, 0x01);
+ dsi_generic_write_seq(dsi, 0xcc, 0x08, 0x31);
+ dsi_generic_write_seq(dsi, 0xcd, 0x31, 0x31);
+ dsi_generic_write_seq(dsi, 0xce, 0x31, 0x31);
+ dsi_generic_write_seq(dsi, 0xcf, 0x31, 0x08);
+ dsi_generic_write_seq(dsi, 0xd0, 0x00, 0x02);
+ dsi_generic_write_seq(dsi, 0xd1, 0x12, 0x10);
+ dsi_generic_write_seq(dsi, 0xd2, 0x18, 0x16);
+ dsi_generic_write_seq(dsi, 0xd3, 0x2a, 0x29);
+ dsi_generic_write_seq(dsi, 0xd4, 0x34, 0x31);
+ dsi_generic_write_seq(dsi, 0xd5, 0x2d, 0x2e);
+ dsi_generic_write_seq(dsi, 0xd6, 0x31, 0x31);
+ dsi_generic_write_seq(dsi, 0xd7, 0x31, 0x31);
+ dsi_generic_write_seq(dsi, 0xe5, 0x31, 0x31);
+ dsi_generic_write_seq(dsi, 0xe6, 0x31, 0x31);
+ dsi_generic_write_seq(dsi, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00);
+ dsi_generic_write_seq(dsi, 0xd9, 0x00, 0x00, 0x00, 0x00, 0x00);
+ dsi_generic_write_seq(dsi, 0xe7, 0x00);
+ dsi_generic_write_seq(dsi, 0x6f, 0x02);
+ dsi_generic_write_seq(dsi, 0xf7, 0x47);
+ dsi_generic_write_seq(dsi, 0x6f, 0x0a);
+ dsi_generic_write_seq(dsi, 0xf7, 0x02);
+ dsi_generic_write_seq(dsi, 0x6f, 0x17);
+ dsi_generic_write_seq(dsi, 0xf4, 0x60);
+ dsi_generic_write_seq(dsi, 0x6f, 0x01);
+ dsi_generic_write_seq(dsi, 0xf9, 0x46);
+ dsi_generic_write_seq(dsi, 0x6f, 0x11);
+ dsi_generic_write_seq(dsi, 0xf3, 0x01);
+ dsi_generic_write_seq(dsi, 0x35, 0x00);
+ dsi_generic_write_seq(dsi, 0xf0, 0x55, 0xaa, 0x52, 0x08, 0x00);
+ dsi_generic_write_seq(dsi, 0xd9, 0x02, 0x03, 0x00);
+ dsi_generic_write_seq(dsi, 0xf0, 0x55, 0xaa, 0x52, 0x00, 0x00);
+ dsi_generic_write_seq(dsi, 0xf0, 0x55, 0xaa, 0x52, 0x08, 0x00);
+ dsi_generic_write_seq(dsi, 0xb1, 0x6c, 0x21);
+ dsi_generic_write_seq(dsi, 0xf0, 0x55, 0xaa, 0x52, 0x00, 0x00);
+ dsi_generic_write_seq(dsi, 0x35, 0x00);
+
+ ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to exit sleep mode: %d\n", ret);
+ return ret;
+ }
+ msleep(120);
+
+ ret = mipi_dsi_dcs_set_display_on(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set display on: %d\n", ret);
+ return ret;
+ }
+ usleep_range(1000, 2000);
+
+ dsi_generic_write_seq(dsi, 0x53, 0x24);
+
+ return 0;
+}
+
+static int truly_nt35521_off(struct truly_nt35521 *ctx)
+{
+ struct mipi_dsi_device *dsi = ctx->dsi;
+ struct device *dev = &dsi->dev;
+ int ret;
+
+ dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
+
+ ret = mipi_dsi_dcs_set_display_off(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set display off: %d\n", ret);
+ return ret;
+ }
+ msleep(50);
+
+ ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to enter sleep mode: %d\n", ret);
+ return ret;
+ }
+ msleep(150);
+
+ return 0;
+}
+
+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;
+
+ if (ctx->prepared)
+ return 0;
+
+ 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;
+ }
+
+ ctx->prepared = true;
+ 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;
+
+ if (!ctx->prepared)
+ return 0;
+
+ 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);
+
+ ctx->prepared = false;
+ return 0;
+}
+
+static int truly_nt35521_enable(struct drm_panel *panel)
+{
+ struct truly_nt35521 *ctx = to_truly_nt35521(panel);
+
+ if (ctx->enabled)
+ return 0;
+
+ gpiod_set_value_cansleep(ctx->blen_gpio, 1);
+
+ ctx->enabled = true;
+ return 0;
+}
+
+static int truly_nt35521_disable(struct drm_panel *panel)
+{
+ struct truly_nt35521 *ctx = to_truly_nt35521(panel);
+
+ if (!ctx->enabled)
+ return 0;
+
+ gpiod_set_value_cansleep(ctx->blen_gpio, 0);
+
+ ctx->enabled = false;
+ 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_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ 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;
+
+ drm_panel_init(&ctx->panel, dev, &truly_nt35521_panel_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ 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-tdo-tl070wsh30.c b/drivers/gpu/drm/panel/panel-tdo-tl070wsh30.c
new file mode 100644
index 000000000..d8487bc6d
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-tdo-tl070wsh30.c
@@ -0,0 +1,248 @@
+// 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;
+
+ bool prepared;
+};
+
+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;
+
+ if (tdo_tl070wsh30->prepared)
+ return 0;
+
+ 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);
+
+ tdo_tl070wsh30->prepared = true;
+
+ 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;
+
+ if (!tdo_tl070wsh30->prepared)
+ return 0;
+
+ 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);
+
+ tdo_tl070wsh30->prepared = false;
+
+ 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);
+ drm_panel_disable(&tdo_tl070wsh30->base);
+ drm_panel_unprepare(&tdo_tl070wsh30->base);
+}
+
+static void tdo_tl070wsh30_panel_shutdown(struct mipi_dsi_device *dsi)
+{
+ struct tdo_tl070wsh30_panel *tdo_tl070wsh30 = mipi_dsi_get_drvdata(dsi);
+
+ drm_panel_disable(&tdo_tl070wsh30->base);
+ drm_panel_unprepare(&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,
+ .shutdown = tdo_tl070wsh30_panel_shutdown,
+};
+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 000000000..4dbf8b88f
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-tpo-td028ttec1.c
@@ -0,0 +1,392 @@
+// 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)
+
+/*
+ * noinline_for_stack so we don't get multiple copies of tx_buf
+ * on the stack in case of gcc-plugin-structleak
+ */
+static int noinline_for_stack
+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_kzalloc(&spi->dev, sizeof(*lcd), GFP_KERNEL);
+ if (!lcd)
+ return -ENOMEM;
+
+ spi_set_drvdata(spi, lcd);
+ lcd->spi = spi;
+
+ spi->mode = SPI_MODE_3;
+ spi->bits_per_word = 9;
+
+ ret = spi_setup(spi);
+ if (ret < 0) {
+ dev_err(&spi->dev, "failed to setup SPI: %d\n", ret);
+ return ret;
+ }
+
+ drm_panel_init(&lcd->panel, &lcd->spi->dev, &td028ttec1_funcs,
+ DRM_MODE_CONNECTOR_DPI);
+
+ ret = drm_panel_of_backlight(&lcd->panel);
+ if (ret)
+ return ret;
+
+ 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 000000000..cf4609bb9
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-tpo-td043mtea1.c
@@ -0,0 +1,506 @@
+// 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_kzalloc(&spi->dev, sizeof(*lcd), GFP_KERNEL);
+ if (lcd == NULL)
+ return -ENOMEM;
+
+ spi_set_drvdata(spi, lcd);
+ lcd->spi = spi;
+ lcd->mode = TPO_R02_MODE_800x480;
+ memcpy(lcd->gamma, td043mtea1_def_gamma, sizeof(lcd->gamma));
+
+ lcd->vcc_reg = devm_regulator_get(&spi->dev, "vcc");
+ if (IS_ERR(lcd->vcc_reg))
+ 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_init(&lcd->panel, &lcd->spi->dev, &td043mtea1_funcs,
+ DRM_MODE_CONNECTOR_DPI);
+
+ 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 000000000..735f1ea25
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-tpo-tpg110.c
@@ -0,0 +1,480 @@
+// 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_kzalloc(dev, sizeof(*tpg), GFP_KERNEL);
+ if (!tpg)
+ return -ENOMEM;
+ 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;
+
+ drm_panel_init(&tpg->panel, dev, &tpg110_drm_funcs,
+ DRM_MODE_CONNECTOR_DPI);
+
+ ret = drm_panel_of_backlight(&tpg->panel);
+ if (ret)
+ return ret;
+
+ spi_set_drvdata(spi, tpg);
+
+ 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 struct spi_driver tpg110_driver = {
+ .probe = tpg110_probe,
+ .remove = tpg110_remove,
+ .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
new file mode 100644
index 000000000..b31cffb66
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-truly-nt35597.c
@@ -0,0 +1,653 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018, 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_device.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",
+ "vdispn",
+};
+
+static unsigned long const regulator_enable_loads[] = {
+ 62000,
+ 100000,
+ 100000,
+};
+
+static unsigned long const regulator_disable_loads[] = {
+ 80,
+ 100,
+ 100,
+};
+
+struct cmd_set {
+ u8 commands[4];
+ u8 size;
+};
+
+struct nt35597_config {
+ u32 width_mm;
+ u32 height_mm;
+ const char *panel_name;
+ const struct cmd_set *panel_on_cmds;
+ u32 num_on_cmds;
+ const struct drm_display_mode *dm;
+};
+
+struct truly_nt35597 {
+ struct device *dev;
+ struct drm_panel panel;
+
+ struct regulator_bulk_data supplies[ARRAY_SIZE(regulator_names)];
+
+ struct gpio_desc *reset_gpio;
+ struct gpio_desc *mode_gpio;
+
+ struct backlight_device *backlight;
+
+ 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)
+{
+ return container_of(panel, struct truly_nt35597, panel);
+}
+
+static const struct cmd_set qcom_2k_panel_magic_cmds[] = {
+ /* CMD2_P0 */
+ { { 0xff, 0x20 }, 2 },
+ { { 0xfb, 0x01 }, 2 },
+ { { 0x00, 0x01 }, 2 },
+ { { 0x01, 0x55 }, 2 },
+ { { 0x02, 0x45 }, 2 },
+ { { 0x05, 0x40 }, 2 },
+ { { 0x06, 0x19 }, 2 },
+ { { 0x07, 0x1e }, 2 },
+ { { 0x0b, 0x73 }, 2 },
+ { { 0x0c, 0x73 }, 2 },
+ { { 0x0e, 0xb0 }, 2 },
+ { { 0x0f, 0xae }, 2 },
+ { { 0x11, 0xb8 }, 2 },
+ { { 0x13, 0x00 }, 2 },
+ { { 0x58, 0x80 }, 2 },
+ { { 0x59, 0x01 }, 2 },
+ { { 0x5a, 0x00 }, 2 },
+ { { 0x5b, 0x01 }, 2 },
+ { { 0x5c, 0x80 }, 2 },
+ { { 0x5d, 0x81 }, 2 },
+ { { 0x5e, 0x00 }, 2 },
+ { { 0x5f, 0x01 }, 2 },
+ { { 0x72, 0x11 }, 2 },
+ { { 0x68, 0x03 }, 2 },
+ /* CMD2_P4 */
+ { { 0xFF, 0x24 }, 2 },
+ { { 0xFB, 0x01 }, 2 },
+ { { 0x00, 0x1C }, 2 },
+ { { 0x01, 0x0B }, 2 },
+ { { 0x02, 0x0C }, 2 },
+ { { 0x03, 0x01 }, 2 },
+ { { 0x04, 0x0F }, 2 },
+ { { 0x05, 0x10 }, 2 },
+ { { 0x06, 0x10 }, 2 },
+ { { 0x07, 0x10 }, 2 },
+ { { 0x08, 0x89 }, 2 },
+ { { 0x09, 0x8A }, 2 },
+ { { 0x0A, 0x13 }, 2 },
+ { { 0x0B, 0x13 }, 2 },
+ { { 0x0C, 0x15 }, 2 },
+ { { 0x0D, 0x15 }, 2 },
+ { { 0x0E, 0x17 }, 2 },
+ { { 0x0F, 0x17 }, 2 },
+ { { 0x10, 0x1C }, 2 },
+ { { 0x11, 0x0B }, 2 },
+ { { 0x12, 0x0C }, 2 },
+ { { 0x13, 0x01 }, 2 },
+ { { 0x14, 0x0F }, 2 },
+ { { 0x15, 0x10 }, 2 },
+ { { 0x16, 0x10 }, 2 },
+ { { 0x17, 0x10 }, 2 },
+ { { 0x18, 0x89 }, 2 },
+ { { 0x19, 0x8A }, 2 },
+ { { 0x1A, 0x13 }, 2 },
+ { { 0x1B, 0x13 }, 2 },
+ { { 0x1C, 0x15 }, 2 },
+ { { 0x1D, 0x15 }, 2 },
+ { { 0x1E, 0x17 }, 2 },
+ { { 0x1F, 0x17 }, 2 },
+ /* STV */
+ { { 0x20, 0x40 }, 2 },
+ { { 0x21, 0x01 }, 2 },
+ { { 0x22, 0x00 }, 2 },
+ { { 0x23, 0x40 }, 2 },
+ { { 0x24, 0x40 }, 2 },
+ { { 0x25, 0x6D }, 2 },
+ { { 0x26, 0x40 }, 2 },
+ { { 0x27, 0x40 }, 2 },
+ /* Vend */
+ { { 0xE0, 0x00 }, 2 },
+ { { 0xDC, 0x21 }, 2 },
+ { { 0xDD, 0x22 }, 2 },
+ { { 0xDE, 0x07 }, 2 },
+ { { 0xDF, 0x07 }, 2 },
+ { { 0xE3, 0x6D }, 2 },
+ { { 0xE1, 0x07 }, 2 },
+ { { 0xE2, 0x07 }, 2 },
+ /* UD */
+ { { 0x29, 0xD8 }, 2 },
+ { { 0x2A, 0x2A }, 2 },
+ /* CLK */
+ { { 0x4B, 0x03 }, 2 },
+ { { 0x4C, 0x11 }, 2 },
+ { { 0x4D, 0x10 }, 2 },
+ { { 0x4E, 0x01 }, 2 },
+ { { 0x4F, 0x01 }, 2 },
+ { { 0x50, 0x10 }, 2 },
+ { { 0x51, 0x00 }, 2 },
+ { { 0x52, 0x80 }, 2 },
+ { { 0x53, 0x00 }, 2 },
+ { { 0x56, 0x00 }, 2 },
+ { { 0x54, 0x07 }, 2 },
+ { { 0x58, 0x07 }, 2 },
+ { { 0x55, 0x25 }, 2 },
+ /* Reset XDONB */
+ { { 0x5B, 0x43 }, 2 },
+ { { 0x5C, 0x00 }, 2 },
+ { { 0x5F, 0x73 }, 2 },
+ { { 0x60, 0x73 }, 2 },
+ { { 0x63, 0x22 }, 2 },
+ { { 0x64, 0x00 }, 2 },
+ { { 0x67, 0x08 }, 2 },
+ { { 0x68, 0x04 }, 2 },
+ /* Resolution:1440x2560 */
+ { { 0x72, 0x02 }, 2 },
+ /* mux */
+ { { 0x7A, 0x80 }, 2 },
+ { { 0x7B, 0x91 }, 2 },
+ { { 0x7C, 0xD8 }, 2 },
+ { { 0x7D, 0x60 }, 2 },
+ { { 0x7F, 0x15 }, 2 },
+ { { 0x75, 0x15 }, 2 },
+ /* ABOFF */
+ { { 0xB3, 0xC0 }, 2 },
+ { { 0xB4, 0x00 }, 2 },
+ { { 0xB5, 0x00 }, 2 },
+ /* Source EQ */
+ { { 0x78, 0x00 }, 2 },
+ { { 0x79, 0x00 }, 2 },
+ { { 0x80, 0x00 }, 2 },
+ { { 0x83, 0x00 }, 2 },
+ /* FP BP */
+ { { 0x93, 0x0A }, 2 },
+ { { 0x94, 0x0A }, 2 },
+ /* Inversion Type */
+ { { 0x8A, 0x00 }, 2 },
+ { { 0x9B, 0xFF }, 2 },
+ /* IMGSWAP =1 @PortSwap=1 */
+ { { 0x9D, 0xB0 }, 2 },
+ { { 0x9F, 0x63 }, 2 },
+ { { 0x98, 0x10 }, 2 },
+ /* FRM */
+ { { 0xEC, 0x00 }, 2 },
+ /* CMD1 */
+ { { 0xFF, 0x10 }, 2 },
+ /* VBP+VSA=,VFP = 10H */
+ { { 0x3B, 0x03, 0x0A, 0x0A }, 4 },
+ /* FTE on */
+ { { 0x35, 0x00 }, 2 },
+ /* EN_BK =1(auto black) */
+ { { 0xE5, 0x01 }, 2 },
+ /* CMD mode(10) VDO mode(03) */
+ { { 0xBB, 0x03 }, 2 },
+ /* Non Reload MTP */
+ { { 0xFB, 0x01 }, 2 },
+};
+
+static int truly_dcs_write(struct drm_panel *panel, u32 command)
+{
+ struct truly_nt35597 *ctx = panel_to_ctx(panel);
+ int i, ret;
+
+ for (i = 0; i < ARRAY_SIZE(ctx->dsi); i++) {
+ ret = mipi_dsi_dcs_write(ctx->dsi[i], command, NULL, 0);
+ if (ret < 0) {
+ dev_err(ctx->dev, "cmd 0x%x failed for dsi = %d\n", command, i);
+ }
+ }
+
+ return ret;
+}
+
+static int truly_dcs_write_buf(struct drm_panel *panel,
+ u32 size, const u8 *buf)
+{
+ struct truly_nt35597 *ctx = panel_to_ctx(panel);
+ int ret = 0;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ctx->dsi); i++) {
+ ret = mipi_dsi_dcs_write_buffer(ctx->dsi[i], buf, size);
+ if (ret < 0) {
+ dev_err(ctx->dev, "failed to tx cmd [%d], err: %d\n", i, ret);
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+static int truly_35597_power_on(struct truly_nt35597 *ctx)
+{
+ int ret, i;
+
+ for (i = 0; i < ARRAY_SIZE(ctx->supplies); i++) {
+ ret = regulator_set_load(ctx->supplies[i].consumer,
+ regulator_enable_loads[i]);
+ if (ret)
+ return ret;
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Reset sequence of truly 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, 0);
+ usleep_range(10000, 20000);
+ gpiod_set_value(ctx->reset_gpio, 1);
+ usleep_range(10000, 20000);
+ gpiod_set_value(ctx->reset_gpio, 0);
+ usleep_range(10000, 20000);
+
+ return 0;
+}
+
+static int truly_nt35597_power_off(struct truly_nt35597 *ctx)
+{
+ int ret = 0;
+ int i;
+
+ gpiod_set_value(ctx->reset_gpio, 1);
+
+ for (i = 0; i < ARRAY_SIZE(ctx->supplies); i++) {
+ ret = regulator_set_load(ctx->supplies[i].consumer,
+ regulator_disable_loads[i]);
+ if (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) {
+ dev_err(ctx->dev, "regulator_bulk_disable failed %d\n", ret);
+ }
+ return ret;
+}
+
+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)
+ dev_err(ctx->dev, "backlight disable failed %d\n", ret);
+ }
+
+ ctx->enabled = false;
+ return 0;
+}
+
+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) {
+ dev_err(ctx->dev, "set_display_off cmd failed ret = %d\n", ret);
+ }
+
+ /* 120ms delay required here as per DCS spec */
+ msleep(120);
+
+ ret = truly_dcs_write(panel, MIPI_DCS_ENTER_SLEEP_MODE);
+ if (ret < 0) {
+ dev_err(ctx->dev, "enter_sleep cmd failed ret = %d\n", ret);
+ }
+
+ ret = truly_nt35597_power_off(ctx);
+ if (ret < 0)
+ dev_err(ctx->dev, "power_off failed ret = %d\n", ret);
+
+ ctx->prepared = false;
+ return ret;
+}
+
+static int truly_nt35597_prepare(struct drm_panel *panel)
+{
+ struct truly_nt35597 *ctx = panel_to_ctx(panel);
+ int ret;
+ int i;
+ const struct cmd_set *panel_on_cmds;
+ const struct nt35597_config *config;
+ u32 num_cmds;
+
+ if (ctx->prepared)
+ return 0;
+
+ ret = truly_35597_power_on(ctx);
+ if (ret < 0)
+ return ret;
+
+ ctx->dsi[0]->mode_flags |= MIPI_DSI_MODE_LPM;
+ ctx->dsi[1]->mode_flags |= MIPI_DSI_MODE_LPM;
+
+ config = ctx->config;
+ panel_on_cmds = config->panel_on_cmds;
+ num_cmds = config->num_on_cmds;
+
+ for (i = 0; i < num_cmds; i++) {
+ ret = truly_dcs_write_buf(panel,
+ panel_on_cmds[i].size,
+ panel_on_cmds[i].commands);
+ if (ret < 0) {
+ 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) {
+ dev_err(ctx->dev, "exit_sleep_mode cmd failed ret = %d\n", ret);
+ goto power_off;
+ }
+
+ /* Per DSI spec wait 120ms after sending exit sleep DCS command */
+ msleep(120);
+
+ ret = truly_dcs_write(panel, MIPI_DCS_SET_DISPLAY_ON);
+ if (ret < 0) {
+ 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))
+ dev_err(ctx->dev, "power_off failed\n");
+ return ret;
+}
+
+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)
+ 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,
+ struct drm_connector *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_duplicate(connector->dev, config->dm);
+ if (!mode) {
+ 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;
+ mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+ drm_mode_probed_add(connector, mode);
+
+ return 1;
+}
+
+static const struct drm_panel_funcs truly_nt35597_drm_funcs = {
+ .disable = truly_nt35597_disable,
+ .unprepare = truly_nt35597_unprepare,
+ .prepare = truly_nt35597_prepare,
+ .enable = truly_nt35597_enable,
+ .get_modes = truly_nt35597_get_modes,
+};
+
+static int truly_nt35597_panel_add(struct truly_nt35597 *ctx)
+{
+ struct device *dev = ctx->dev;
+ int ret, i;
+
+ 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)) {
+ 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)) {
+ 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, dev, &truly_nt35597_drm_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+ drm_panel_add(&ctx->panel);
+
+ return 0;
+}
+
+static const struct drm_display_mode qcom_sdm845_mtp_2k_mode = {
+ .name = "1440x2560",
+ .clock = 268316,
+ .hdisplay = 1440,
+ .hsync_start = 1440 + 200,
+ .hsync_end = 1440 + 200 + 32,
+ .htotal = 1440 + 200 + 32 + 64,
+ .vdisplay = 2560,
+ .vsync_start = 2560 + 8,
+ .vsync_end = 2560 + 8 + 1,
+ .vtotal = 2560 + 8 + 1 + 7,
+ .flags = 0,
+};
+
+static const struct nt35597_config nt35597_dir = {
+ .width_mm = 74,
+ .height_mm = 131,
+ .panel_name = "qcom_sdm845_mtp_2k_panel",
+ .dm = &qcom_sdm845_mtp_2k_mode,
+ .panel_on_cmds = qcom_2k_panel_magic_cmds,
+ .num_on_cmds = ARRAY_SIZE(qcom_2k_panel_magic_cmds),
+};
+
+static int truly_nt35597_probe(struct mipi_dsi_device *dsi)
+{
+ struct device *dev = &dsi->dev;
+ struct truly_nt35597 *ctx;
+ struct mipi_dsi_device *dsi1_device;
+ struct device_node *dsi1;
+ struct mipi_dsi_host *dsi1_host;
+ struct mipi_dsi_device *dsi_dev;
+ int ret = 0;
+ int i;
+
+ const struct mipi_dsi_device_info info = {
+ .type = "trulynt35597",
+ .channel = 0,
+ .node = NULL,
+ };
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+
+ if (!ctx)
+ return -ENOMEM;
+
+ /*
+ * This device represents itself as one with two input ports which are
+ * fed by the output ports of the two DSI controllers . The DSI0 is
+ * the master controller and has most of the panel related info in its
+ * child node.
+ */
+
+ ctx->config = of_device_get_match_data(dev);
+
+ if (!ctx->config) {
+ dev_err(dev, "missing device configuration\n");
+ return -ENODEV;
+ }
+
+ dsi1 = of_graph_get_remote_node(dsi->dev.of_node, 1, -1);
+ if (!dsi1) {
+ 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) {
+ dev_err(dev, "failed to find dsi host\n");
+ return -EPROBE_DEFER;
+ }
+
+ /* register the second DSI device */
+ dsi1_device = mipi_dsi_device_register_full(dsi1_host, &info);
+ if (IS_ERR(dsi1_device)) {
+ dev_err(dev, "failed to create dsi device\n");
+ return PTR_ERR(dsi1_device);
+ }
+
+ mipi_dsi_set_drvdata(dsi, ctx);
+
+ ctx->dev = dev;
+ ctx->dsi[0] = dsi;
+ ctx->dsi[1] = dsi1_device;
+
+ ret = truly_nt35597_panel_add(ctx);
+ if (ret) {
+ dev_err(dev, "failed to add panel\n");
+ goto err_panel_add;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(ctx->dsi); i++) {
+ dsi_dev = ctx->dsi[i];
+ dsi_dev->lanes = 4;
+ dsi_dev->format = MIPI_DSI_FMT_RGB888;
+ dsi_dev->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM |
+ MIPI_DSI_CLOCK_NON_CONTINUOUS;
+ ret = mipi_dsi_attach(dsi_dev);
+ if (ret < 0) {
+ dev_err(dev, "dsi attach failed i = %d\n", i);
+ goto err_dsi_attach;
+ }
+ }
+
+ return 0;
+
+err_dsi_attach:
+ drm_panel_remove(&ctx->panel);
+err_panel_add:
+ mipi_dsi_device_unregister(dsi1_device);
+ return ret;
+}
+
+static void truly_nt35597_remove(struct mipi_dsi_device *dsi)
+{
+ struct truly_nt35597 *ctx = mipi_dsi_get_drvdata(dsi);
+
+ if (ctx->dsi[0])
+ mipi_dsi_detach(ctx->dsi[0]);
+ if (ctx->dsi[1]) {
+ mipi_dsi_detach(ctx->dsi[1]);
+ mipi_dsi_device_unregister(ctx->dsi[1]);
+ }
+
+ drm_panel_remove(&ctx->panel);
+}
+
+static const struct of_device_id truly_nt35597_of_match[] = {
+ {
+ .compatible = "truly,nt35597-2K-display",
+ .data = &nt35597_dir,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, truly_nt35597_of_match);
+
+static struct mipi_dsi_driver truly_nt35597_driver = {
+ .driver = {
+ .name = "panel-truly-nt35597",
+ .of_match_table = truly_nt35597_of_match,
+ },
+ .probe = truly_nt35597_probe,
+ .remove = truly_nt35597_remove,
+};
+module_mipi_dsi_driver(truly_nt35597_driver);
+
+MODULE_DESCRIPTION("Truly NT35597 DSI Panel Driver");
+MODULE_LICENSE("GPL v2");
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 000000000..ec228c269
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-visionox-rm69299.c
@@ -0,0 +1,286 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2019, The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/of_device.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 {
+ struct drm_panel panel;
+ struct regulator_bulk_data supplies[2];
+ struct gpio_desc *reset_gpio;
+ struct mipi_dsi_device *dsi;
+ bool prepared;
+ bool enabled;
+};
+
+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(ctx->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(ctx->supplies), ctx->supplies);
+}
+
+static int visionox_rm69299_unprepare(struct drm_panel *panel)
+{
+ struct visionox_rm69299 *ctx = panel_to_ctx(panel);
+ int ret;
+
+ ctx->dsi->mode_flags = 0;
+
+ ret = mipi_dsi_dcs_write(ctx->dsi, MIPI_DCS_SET_DISPLAY_OFF, NULL, 0);
+ if (ret < 0)
+ dev_err(ctx->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_write(ctx->dsi, MIPI_DCS_ENTER_SLEEP_MODE, NULL, 0);
+ if (ret < 0) {
+ dev_err(ctx->panel.dev, "enter_sleep cmd failed ret = %d\n", ret);
+ }
+
+ ret = visionox_rm69299_power_off(ctx);
+
+ ctx->prepared = false;
+ return ret;
+}
+
+static int visionox_rm69299_prepare(struct drm_panel *panel)
+{
+ struct visionox_rm69299 *ctx = panel_to_ctx(panel);
+ int ret;
+
+ if (ctx->prepared)
+ return 0;
+
+ ret = visionox_rm69299_power_on(ctx);
+ if (ret < 0)
+ return ret;
+
+ ctx->dsi->mode_flags |= MIPI_DSI_MODE_LPM;
+
+ ret = mipi_dsi_dcs_write_buffer(ctx->dsi, (u8[]) { 0xfe, 0x00 }, 2);
+ if (ret < 0) {
+ dev_err(ctx->panel.dev, "cmd set tx 0 failed, ret = %d\n", ret);
+ goto power_off;
+ }
+
+ ret = mipi_dsi_dcs_write_buffer(ctx->dsi, (u8[]) { 0xc2, 0x08 }, 2);
+ if (ret < 0) {
+ dev_err(ctx->panel.dev, "cmd set tx 1 failed, ret = %d\n", ret);
+ goto power_off;
+ }
+
+ ret = mipi_dsi_dcs_write_buffer(ctx->dsi, (u8[]) { 0x35, 0x00 }, 2);
+ if (ret < 0) {
+ dev_err(ctx->panel.dev, "cmd set tx 2 failed, ret = %d\n", ret);
+ goto power_off;
+ }
+
+ ret = mipi_dsi_dcs_write_buffer(ctx->dsi, (u8[]) { 0x51, 0xff }, 2);
+ if (ret < 0) {
+ dev_err(ctx->panel.dev, "cmd set tx 3 failed, ret = %d\n", ret);
+ goto power_off;
+ }
+
+ ret = mipi_dsi_dcs_write(ctx->dsi, MIPI_DCS_EXIT_SLEEP_MODE, NULL, 0);
+ if (ret < 0) {
+ dev_err(ctx->panel.dev, "exit_sleep_mode cmd failed ret = %d\n", ret);
+ goto power_off;
+ }
+
+ /* Per DSI spec wait 120ms after sending exit sleep DCS command */
+ msleep(120);
+
+ ret = mipi_dsi_dcs_write(ctx->dsi, MIPI_DCS_SET_DISPLAY_ON, NULL, 0);
+ if (ret < 0) {
+ dev_err(ctx->panel.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:
+ return ret;
+}
+
+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 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,
+ &visionox_rm69299_1080x2248_60hz);
+ 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_probe(struct mipi_dsi_device *dsi)
+{
+ struct device *dev = &dsi->dev;
+ struct visionox_rm69299 *ctx;
+ int ret;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ mipi_dsi_set_drvdata(dsi, ctx);
+
+ ctx->panel.dev = dev;
+ ctx->dsi = dsi;
+
+ ctx->supplies[0].supply = "vdda";
+ ctx->supplies[1].supply = "vdd3p3";
+
+ ret = devm_regulator_bulk_get(ctx->panel.dev, ARRAY_SIZE(ctx->supplies),
+ ctx->supplies);
+ if (ret < 0)
+ return ret;
+
+ ctx->reset_gpio = devm_gpiod_get(ctx->panel.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);
+ }
+
+ drm_panel_init(&ctx->panel, dev, &visionox_rm69299_drm_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+ ctx->panel.dev = dev;
+ ctx->panel.funcs = &visionox_rm69299_drm_funcs;
+ 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;
+ }
+
+ ret = regulator_set_load(ctx->supplies[0].consumer, 32000);
+ if (ret) {
+ dev_err(dev, "regulator set load failed for vdda supply ret = %d\n", ret);
+ goto err_set_load;
+ }
+
+ ret = regulator_set_load(ctx->supplies[1].consumer, 13200);
+ if (ret) {
+ dev_err(dev, "regulator set load failed for vdd3p3 supply ret = %d\n", ret);
+ goto err_set_load;
+ }
+
+ return 0;
+
+err_set_load:
+ mipi_dsi_detach(dsi);
+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);
+ mipi_dsi_device_unregister(ctx->dsi);
+
+ drm_panel_remove(&ctx->panel);
+}
+
+static const struct of_device_id visionox_rm69299_of_match[] = {
+ { .compatible = "visionox,rm69299-1080p-display", },
+ { /* 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-widechips-ws2401.c b/drivers/gpu/drm/panel/panel-widechips-ws2401.c
new file mode 100644
index 000000000..236f3cb2b
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-widechips-ws2401.c
@@ -0,0 +1,440 @@
+// 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_kzalloc(dev, sizeof(*ws), GFP_KERNEL);
+ if (!ws)
+ return -ENOMEM;
+ 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);
+
+ drm_panel_init(&ws->panel, dev, &ws2401_drm_funcs,
+ DRM_MODE_CONNECTOR_DPI);
+
+ 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 struct spi_driver ws2401_driver = {
+ .probe = ws2401_probe,
+ .remove = ws2401_remove,
+ .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 000000000..2c54733ee
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-xinpeng-xpp055c272.c
@@ -0,0 +1,375 @@
+// 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;
+ bool prepared;
+};
+
+static inline struct xpp055c272 *panel_to_xpp055c272(struct drm_panel *panel)
+{
+ return container_of(panel, struct xpp055c272, panel);
+}
+
+#define dsi_generic_write_seq(dsi, cmd, seq...) do { \
+ static const u8 b[] = { cmd, seq }; \
+ int ret; \
+ ret = mipi_dsi_dcs_write_buffer(dsi, b, ARRAY_SIZE(b)); \
+ if (ret < 0) \
+ return ret; \
+ } while (0)
+
+static int xpp055c272_init_sequence(struct xpp055c272 *ctx)
+{
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+ struct device *dev = ctx->dev;
+
+ /*
+ * Init sequence was supplied by the panel vendor without much
+ * documentation.
+ */
+ dsi_generic_write_seq(dsi, XPP055C272_CMD_SETEXTC, 0xf1, 0x12, 0x83);
+ dsi_generic_write_seq(dsi, XPP055C272_CMD_SETMIPI,
+ 0x33, 0x81, 0x05, 0xf9, 0x0e, 0x0e, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x25,
+ 0x00, 0x91, 0x0a, 0x00, 0x00, 0x02, 0x4f, 0x01,
+ 0x00, 0x00, 0x37);
+ dsi_generic_write_seq(dsi, XPP055C272_CMD_SETPOWER_EXT, 0x25);
+ dsi_generic_write_seq(dsi, XPP055C272_CMD_SETPCR, 0x02, 0x11, 0x00);
+ dsi_generic_write_seq(dsi, XPP055C272_CMD_SETRGBIF,
+ 0x0c, 0x10, 0x0a, 0x50, 0x03, 0xff, 0x00, 0x00,
+ 0x00, 0x00);
+ dsi_generic_write_seq(dsi, XPP055C272_CMD_SETSCR,
+ 0x73, 0x73, 0x50, 0x50, 0x00, 0x00, 0x08, 0x70,
+ 0x00);
+ dsi_generic_write_seq(dsi, XPP055C272_CMD_SETVDC, 0x46);
+ dsi_generic_write_seq(dsi, XPP055C272_CMD_SETPANEL, 0x0b);
+ dsi_generic_write_seq(dsi, XPP055C272_CMD_SETCYC, 0x80);
+ dsi_generic_write_seq(dsi, XPP055C272_CMD_SETDISP, 0xc8, 0x12, 0x30);
+ dsi_generic_write_seq(dsi, XPP055C272_CMD_SETEQ,
+ 0x07, 0x07, 0x0B, 0x0B, 0x03, 0x0B, 0x00, 0x00,
+ 0x00, 0x00, 0xFF, 0x00, 0xC0, 0x10);
+ dsi_generic_write_seq(dsi, XPP055C272_CMD_SETPOWER,
+ 0x53, 0x00, 0x1e, 0x1e, 0x77, 0xe1, 0xcc, 0xdd,
+ 0x67, 0x77, 0x33, 0x33);
+ dsi_generic_write_seq(dsi, XPP055C272_CMD_SETECO, 0x00, 0x00, 0xff,
+ 0xff, 0x01, 0xff);
+ dsi_generic_write_seq(dsi, XPP055C272_CMD_SETBGP, 0x09, 0x09);
+ msleep(20);
+
+ dsi_generic_write_seq(dsi, XPP055C272_CMD_SETVCOM, 0x87, 0x95);
+ dsi_generic_write_seq(dsi, XPP055C272_CMD_SETGIP1,
+ 0xc2, 0x10, 0x05, 0x05, 0x10, 0x05, 0xa0, 0x12,
+ 0x31, 0x23, 0x3f, 0x81, 0x0a, 0xa0, 0x37, 0x18,
+ 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x80,
+ 0x01, 0x00, 0x00, 0x00, 0x48, 0xf8, 0x86, 0x42,
+ 0x08, 0x88, 0x88, 0x80, 0x88, 0x88, 0x88, 0x58,
+ 0xf8, 0x87, 0x53, 0x18, 0x88, 0x88, 0x81, 0x88,
+ 0x88, 0x88, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
+ dsi_generic_write_seq(dsi, XPP055C272_CMD_SETGIP2,
+ 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x1f, 0x88, 0x81, 0x35,
+ 0x78, 0x88, 0x88, 0x85, 0x88, 0x88, 0x88, 0x0f,
+ 0x88, 0x80, 0x24, 0x68, 0x88, 0x88, 0x84, 0x88,
+ 0x88, 0x88, 0x23, 0x10, 0x00, 0x00, 0x1c, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x05,
+ 0xa0, 0x00, 0x00, 0x00, 0x00);
+ dsi_generic_write_seq(dsi, XPP055C272_CMD_SETGAMMA,
+ 0x00, 0x06, 0x08, 0x2a, 0x31, 0x3f, 0x38, 0x36,
+ 0x07, 0x0c, 0x0d, 0x11, 0x13, 0x12, 0x13, 0x11,
+ 0x18, 0x00, 0x06, 0x08, 0x2a, 0x31, 0x3f, 0x38,
+ 0x36, 0x07, 0x0c, 0x0d, 0x11, 0x13, 0x12, 0x13,
+ 0x11, 0x18);
+
+ msleep(60);
+
+ dev_dbg(dev, "Panel init sequence done\n");
+ return 0;
+}
+
+static int xpp055c272_unprepare(struct drm_panel *panel)
+{
+ struct xpp055c272 *ctx = panel_to_xpp055c272(panel);
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+ int ret;
+
+ if (!ctx->prepared)
+ return 0;
+
+ ret = mipi_dsi_dcs_set_display_off(dsi);
+ if (ret < 0)
+ dev_err(ctx->dev, "failed to set display off: %d\n", 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;
+ }
+
+ regulator_disable(ctx->iovcc);
+ regulator_disable(ctx->vci);
+
+ ctx->prepared = false;
+
+ return 0;
+}
+
+static int xpp055c272_prepare(struct drm_panel *panel)
+{
+ struct xpp055c272 *ctx = panel_to_xpp055c272(panel);
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+ int ret;
+
+ if (ctx->prepared)
+ return 0;
+
+ dev_dbg(ctx->dev, "Resetting the panel\n");
+ ret = regulator_enable(ctx->vci);
+ 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_vci;
+ }
+
+ gpiod_set_value_cansleep(ctx->reset_gpio, 1);
+ /* T6: 10us */
+ usleep_range(10, 20);
+ gpiod_set_value_cansleep(ctx->reset_gpio, 0);
+
+ /* T8: 20ms */
+ msleep(20);
+
+ ret = xpp055c272_init_sequence(ctx);
+ if (ret < 0) {
+ dev_err(ctx->dev, "Panel init sequence failed: %d\n", ret);
+ goto disable_iovcc;
+ }
+
+ 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_iovcc;
+ }
+
+ /* T9: 120ms */
+ msleep(120);
+
+ 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_iovcc;
+ }
+
+ msleep(50);
+
+ ctx->prepared = true;
+
+ return 0;
+
+disable_iovcc:
+ regulator_disable(ctx->iovcc);
+disable_vci:
+ regulator_disable(ctx->vci);
+ return ret;
+}
+
+static const struct drm_display_mode default_mode = {
+ .hdisplay = 720,
+ .hsync_start = 720 + 40,
+ .hsync_end = 720 + 40 + 10,
+ .htotal = 720 + 40 + 10 + 40,
+ .vdisplay = 1280,
+ .vsync_start = 1280 + 22,
+ .vsync_end = 1280 + 22 + 4,
+ .vtotal = 1280 + 22 + 4 + 11,
+ .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_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(ctx->reset_gpio))
+ 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;
+
+ drm_panel_init(&ctx->panel, &dsi->dev, &xpp055c272_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ ret = drm_panel_of_backlight(&ctx->panel);
+ if (ret)
+ return ret;
+
+ drm_panel_add(&ctx->panel);
+
+ ret = mipi_dsi_attach(dsi);
+ if (ret < 0) {
+ dev_err(dev, "mipi_dsi_attach failed: %d\n", ret);
+ drm_panel_remove(&ctx->panel);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void xpp055c272_shutdown(struct mipi_dsi_device *dsi)
+{
+ struct xpp055c272 *ctx = mipi_dsi_get_drvdata(dsi);
+ int ret;
+
+ ret = drm_panel_unprepare(&ctx->panel);
+ if (ret < 0)
+ dev_err(&dsi->dev, "Failed to unprepare panel: %d\n", ret);
+
+ ret = drm_panel_disable(&ctx->panel);
+ if (ret < 0)
+ dev_err(&dsi->dev, "Failed to disable panel: %d\n", ret);
+}
+
+static void xpp055c272_remove(struct mipi_dsi_device *dsi)
+{
+ struct xpp055c272 *ctx = mipi_dsi_get_drvdata(dsi);
+ int ret;
+
+ xpp055c272_shutdown(dsi);
+
+ ret = mipi_dsi_detach(dsi);
+ if (ret < 0)
+ 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,
+ .shutdown = xpp055c272_shutdown,
+};
+module_mipi_dsi_driver(xpp055c272_driver);
+
+MODULE_AUTHOR("Heiko Stuebner <heiko.stuebner@theobroma-systems.com>");
+MODULE_DESCRIPTION("DRM driver for Xinpeng xpp055c272 MIPI DSI panel");
+MODULE_LICENSE("GPL v2");