From 4a1612eb56b8f804dc6debeded72c73ca2d2e7b9 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Sun, 22 Mar 2026 21:22:11 -0400 Subject: [PATCH] linux-postmarketos-rockchip: add rk3566-pinenote support Part-of: --- .../linux-postmarketos-rockchip/APKBUILD | 24 +- .../config-postmarketos-rockchip.aarch64 | 188 +- .../rk3566-pinenote-support.patch | 14628 ++++++++++++++++ .../rk3566-pinenote-ultra-suspend.patch | 160 + 4 files changed, 14920 insertions(+), 80 deletions(-) create mode 100644 device/community/linux-postmarketos-rockchip/rk3566-pinenote-support.patch create mode 100644 device/community/linux-postmarketos-rockchip/rk3566-pinenote-ultra-suspend.patch diff --git a/device/community/linux-postmarketos-rockchip/APKBUILD b/device/community/linux-postmarketos-rockchip/APKBUILD index ef8b3b1da..971831d6c 100644 --- a/device/community/linux-postmarketos-rockchip/APKBUILD +++ b/device/community/linux-postmarketos-rockchip/APKBUILD @@ -4,7 +4,7 @@ maintainer="Antoine Martin (ayakael) " pkgname=linux-postmarketos-rockchip pkgver=6.19.11 _kernver=${pkgver%.*} -pkgrel=0 +pkgrel=1 pkgdesc="Mainline kernel for rockchip devices" arch="aarch64" _carch="arm64" @@ -31,17 +31,23 @@ makedepends=" xz zstd " +# to allow A/B comparison testing +replaces="linux-pine64-pinenote" +provides="linux-pine64-pinenote=$pkgver-r$pkgrel" # Source _config="config-$_flavor.$arch" -source=" - https://cdn.kernel.org/pub/linux/kernel/v${pkgver%%.*}.x/linux-$_kernver.tar.xz - $_config -" # get patch releases if [ "${pkgver/*.}" -ne 0 ]; then - source="$source $pkgname-$pkgver.patch.xz::https://cdn.kernel.org/pub/linux/kernel/v${pkgver%%.*}.x/patch-$pkgver.xz" + source="$pkgname-$pkgver.patch.xz::https://cdn.kernel.org/pub/linux/kernel/v${pkgver%%.*}.x/patch-$pkgver.xz" fi +source=" + $source + https://cdn.kernel.org/pub/linux/kernel/v${pkgver%%.*}.x/linux-$_kernver.tar.xz + rk3566-pinenote-support.patch + rk3566-pinenote-ultra-suspend.patch + $_config +" builddir="$srcdir"/linux-$_kernver prepare() { @@ -86,7 +92,9 @@ package() { } sha512sums=" -01b29c7f4e5bc0c9802794c2cd027fece825f90417be229a71e60eefce530010d5d301749c54ae744e9d4a483518e769e2bb7e6e9209687681ad7fff11c3ed86 linux-6.19.tar.xz -fbf188a5446f7056fe786deb1185c842359773e562028e7c3cf11863732e0b16da2bde707f7dc555a9f0ed89bfebf05f24aa1c3916f68234cded45e2c7e63bd6 config-postmarketos-rockchip.aarch64 c68361a19d04529226beb12cdd3a764bdcf481dbad342d5626d83412a00857588942308e880a92fceac31c79fe6f389bcf7b1f171e7c57b0110ec2babc0e24ae linux-postmarketos-rockchip-6.19.11.patch.xz +01b29c7f4e5bc0c9802794c2cd027fece825f90417be229a71e60eefce530010d5d301749c54ae744e9d4a483518e769e2bb7e6e9209687681ad7fff11c3ed86 linux-6.19.tar.xz +7117f1d9355bfa033dc0fbe823fc9e06cf5ff8cf261f6159e7f27b7a481e974c5806d2e2b5baaba948e7e0d293430df7950c0f4b4cd337cdf21a31415b5692d9 rk3566-pinenote-support.patch +665bdc843912a22bc9f1840bf89195f6cccabd2b8ca3313f3c61b5503c84f98395e3de0d28bc31cbc9f1b4be4a41a483db156fad353506bb5be7abdc22e46501 rk3566-pinenote-ultra-suspend.patch +1e17b546ea0a426fb1d8368bcb5801fbdbe14e1b471908dcab245f1ef3f384bfabd8310a299b1eb822bc15510d5949db498ea524b6eaeb7ad8e08772b70c8561 config-postmarketos-rockchip.aarch64 " diff --git a/device/community/linux-postmarketos-rockchip/config-postmarketos-rockchip.aarch64 b/device/community/linux-postmarketos-rockchip/config-postmarketos-rockchip.aarch64 index 228b6210f..82b8c0c7d 100644 --- a/device/community/linux-postmarketos-rockchip/config-postmarketos-rockchip.aarch64 +++ b/device/community/linux-postmarketos-rockchip/config-postmarketos-rockchip.aarch64 @@ -449,7 +449,7 @@ CONFIG_ARM64_VA_BITS=48 CONFIG_ARM64_PA_BITS_48=y CONFIG_ARM64_PA_BITS=48 CONFIG_CPU_LITTLE_ENDIAN=y -CONFIG_NR_CPUS=8 +CONFIG_NR_CPUS=16 CONFIG_HOTPLUG_CPU=y # CONFIG_NUMA is not set # CONFIG_HZ_100 is not set @@ -592,7 +592,10 @@ CONFIG_PM_WAKELOCKS_LIMIT=100 CONFIG_PM_WAKELOCKS_GC=y # CONFIG_PM_QOS_CPU_SYSTEM_WAKEUP is not set CONFIG_PM=y -# CONFIG_PM_DEBUG is not set +CONFIG_PM_DEBUG=y +CONFIG_PM_ADVANCED_DEBUG=y +CONFIG_PM_TEST_SUSPEND=y +CONFIG_PM_SLEEP_DEBUG=y CONFIG_PM_CLK=y CONFIG_PM_GENERIC_DOMAINS=y CONFIG_WQ_POWER_EFFICIENT_DEFAULT=y @@ -1903,6 +1906,7 @@ CONFIG_PCI_HOST_COMMON=y CONFIG_PCI_HOST_GENERIC=m CONFIG_PCIE_ROCKCHIP=y CONFIG_PCIE_ROCKCHIP_HOST=y +CONFIG_PCIE_ROCKCHIP_EP=y # CONFIG_PCI_XGENE is not set # CONFIG_PCIE_XILINX is not set @@ -1910,6 +1914,7 @@ CONFIG_PCIE_ROCKCHIP_HOST=y # Cadence-based PCIe controllers # # CONFIG_PCIE_CADENCE_PLAT_HOST is not set +# CONFIG_PCIE_CADENCE_PLAT_EP is not set # end of Cadence-based PCIe controllers # @@ -1918,6 +1923,7 @@ CONFIG_PCIE_ROCKCHIP_HOST=y CONFIG_PCIE_DW=y # CONFIG_PCIE_DW_DEBUGFS is not set CONFIG_PCIE_DW_HOST=y +CONFIG_PCIE_DW_EP=y # CONFIG_PCIE_AL is not set # CONFIG_PCIE_AMD_MDB is not set # CONFIG_PCI_MESON is not set @@ -1925,8 +1931,10 @@ CONFIG_PCI_HISI=y # CONFIG_PCIE_KIRIN is not set CONFIG_PCIE_DW_PLAT=y CONFIG_PCIE_DW_PLAT_HOST=y +CONFIG_PCIE_DW_PLAT_EP=y CONFIG_PCIE_ROCKCHIP_DW=y CONFIG_PCIE_ROCKCHIP_DW_HOST=y +CONFIG_PCIE_ROCKCHIP_DW_EP=y # end of DesignWare-based PCIe controllers # @@ -1944,7 +1952,11 @@ CONFIG_PCIE_ROCKCHIP_DW_HOST=y # # PCI Endpoint # -# CONFIG_PCI_ENDPOINT is not set +CONFIG_PCI_ENDPOINT=y +# CONFIG_PCI_ENDPOINT_CONFIGFS is not set +# CONFIG_PCI_ENDPOINT_MSI_DOORBELL is not set +# CONFIG_PCI_EPF_TEST is not set +# CONFIG_PCI_EPF_NTB is not set # end of PCI Endpoint # @@ -2025,7 +2037,7 @@ CONFIG_CONNECTOR=m # # ARM System Control and Management Interface Protocol # -CONFIG_ARM_SCMI_PROTOCOL=m +CONFIG_ARM_SCMI_PROTOCOL=y # CONFIG_ARM_SCMI_RAW_MODE_SUPPORT is not set # CONFIG_ARM_SCMI_DEBUG_COUNTERS is not set CONFIG_ARM_SCMI_QUIRKS=y @@ -2036,8 +2048,8 @@ CONFIG_ARM_SCMI_QUIRKS=y CONFIG_ARM_SCMI_HAVE_TRANSPORT=y CONFIG_ARM_SCMI_HAVE_SHMEM=y CONFIG_ARM_SCMI_HAVE_MSG=y -CONFIG_ARM_SCMI_TRANSPORT_MAILBOX=m -CONFIG_ARM_SCMI_TRANSPORT_SMC=m +CONFIG_ARM_SCMI_TRANSPORT_MAILBOX=y +CONFIG_ARM_SCMI_TRANSPORT_SMC=y # CONFIG_ARM_SCMI_TRANSPORT_SMC_ATOMIC_ENABLE is not set CONFIG_ARM_SCMI_TRANSPORT_OPTEE=m # CONFIG_ARM_SCMI_TRANSPORT_VIRTIO is not set @@ -2049,15 +2061,16 @@ CONFIG_ARM_SCMI_TRANSPORT_OPTEE=m # CONFIG_IMX_SCMI_BBM_EXT is not set # end of ARM SCMI NXP i.MX Vendor Protocols -# CONFIG_ARM_SCMI_POWER_CONTROL is not set +CONFIG_ARM_SCMI_POWER_CONTROL=y # end of ARM System Control and Management Interface Protocol CONFIG_ARM_SCPI_PROTOCOL=m -# CONFIG_ARM_SDE_INTERFACE is not set +CONFIG_ARM_SDE_INTERFACE=y CONFIG_DMIID=y # CONFIG_DMI_SYSFS is not set CONFIG_FW_CFG_SYSFS=m # CONFIG_FW_CFG_SYSFS_CMDLINE is not set +CONFIG_ROCKCHIP_SIP=y # CONFIG_SYSFB_SIMPLEFB is not set # CONFIG_ARM_FFA_TRANSPORT is not set # CONFIG_GOOGLE_FIRMWARE is not set @@ -2344,17 +2357,17 @@ CONFIG_CB710_DEBUG_ASSUMPTIONS=y # # SCSI device support # -CONFIG_SCSI_MOD=y +CONFIG_SCSI_MOD=m # CONFIG_RAID_ATTRS is not set -CONFIG_SCSI_COMMON=y -CONFIG_SCSI=y +CONFIG_SCSI_COMMON=m +CONFIG_SCSI=m CONFIG_SCSI_DMA=y CONFIG_SCSI_PROC_FS=y # # SCSI support type (disk, tape, CD-ROM) # -CONFIG_BLK_DEV_SD=y +CONFIG_BLK_DEV_SD=m # CONFIG_CHR_DEV_ST is not set CONFIG_BLK_DEV_SR=m # CONFIG_CHR_DEV_SG is not set @@ -3331,7 +3344,7 @@ CONFIG_TOUCHSCREEN_ATMEL_MXT=m # CONFIG_TOUCHSCREEN_CY8CTMA140 is not set # CONFIG_TOUCHSCREEN_CY8CTMG110 is not set # CONFIG_TOUCHSCREEN_CYTTSP_CORE is not set -# CONFIG_TOUCHSCREEN_CYTTSP5 is not set +CONFIG_TOUCHSCREEN_CYTTSP5=m # CONFIG_TOUCHSCREEN_DYNAPRO is not set # CONFIG_TOUCHSCREEN_HAMPSHIRE is not set # CONFIG_TOUCHSCREEN_EETI is not set @@ -3436,7 +3449,7 @@ CONFIG_INPUT_UINPUT=y # CONFIG_INPUT_PCF8574 is not set CONFIG_INPUT_PWM_BEEPER=m # CONFIG_INPUT_PWM_VIBRA is not set -# CONFIG_INPUT_RK805_PWRKEY is not set +CONFIG_INPUT_RK805_PWRKEY=y # CONFIG_INPUT_GPIO_ROTARY_ENCODER is not set # CONFIG_INPUT_DA7280_HAPTICS is not set # CONFIG_INPUT_ADXL34X is not set @@ -3449,6 +3462,7 @@ CONFIG_INPUT_CMA3000_I2C=m # CONFIG_INPUT_DRV260X_HAPTICS is not set # CONFIG_INPUT_DRV2665_HAPTICS is not set # CONFIG_INPUT_DRV2667_HAPTICS is not set +CONFIG_INPUT_WS8100_PEN=m CONFIG_RMI4_CORE=y CONFIG_RMI4_I2C=m CONFIG_RMI4_SPI=m @@ -3604,7 +3618,7 @@ CONFIG_TCG_FTPM_TEE=m # CONFIG_I2C=y CONFIG_I2C_BOARDINFO=y -CONFIG_I2C_CHARDEV=m +CONFIG_I2C_CHARDEV=y CONFIG_I2C_MUX=y # @@ -3701,20 +3715,20 @@ CONFIG_SPI_MEM=y # # CONFIG_SPI_ALTERA is not set # CONFIG_SPI_AXI_SPI_ENGINE is not set -CONFIG_SPI_BITBANG=y +CONFIG_SPI_BITBANG=m # CONFIG_SPI_CADENCE is not set # CONFIG_SPI_CADENCE_QUADSPI is not set # CONFIG_SPI_CADENCE_XSPI is not set # CONFIG_SPI_CH341 is not set # CONFIG_SPI_DESIGNWARE is not set -CONFIG_SPI_GPIO=y +CONFIG_SPI_GPIO=m # CONFIG_SPI_FSL_SPI is not set # CONFIG_SPI_MICROCHIP_CORE_QSPI is not set # CONFIG_SPI_MICROCHIP_CORE_SPI is not set # CONFIG_SPI_OC_TINY is not set # CONFIG_SPI_PCI1XXXX is not set # CONFIG_SPI_PL022 is not set -CONFIG_SPI_ROCKCHIP=y +CONFIG_SPI_ROCKCHIP=m CONFIG_SPI_ROCKCHIP_SFC=m # CONFIG_SPI_SC18IS602 is not set # CONFIG_SPI_SIFIVE is not set @@ -3795,7 +3809,7 @@ CONFIG_PINCTRL_MAX77620=y # CONFIG_PINCTRL_PALMAS is not set # CONFIG_PINCTRL_RK805 is not set CONFIG_PINCTRL_ROCKCHIP=y -# CONFIG_PINCTRL_SCMI is not set +CONFIG_PINCTRL_SCMI=y CONFIG_PINCTRL_SINGLE=y # CONFIG_PINCTRL_STMFX is not set # CONFIG_PINCTRL_SX150X is not set @@ -3832,7 +3846,7 @@ CONFIG_GPIO_GENERIC_PLATFORM=y # CONFIG_GPIO_MB86S7X is not set CONFIG_GPIO_PL061=y # CONFIG_GPIO_POLARFIRE_SOC is not set -CONFIG_GPIO_ROCKCHIP=y +CONFIG_GPIO_ROCKCHIP=m # CONFIG_GPIO_SIFIVE is not set CONFIG_GPIO_SYSCON=y CONFIG_GPIO_XGENE=y @@ -3963,7 +3977,7 @@ CONFIG_CHARGER_GPIO=y # CONFIG_CHARGER_BQ25890 is not set # CONFIG_CHARGER_BQ25980 is not set # CONFIG_CHARGER_BQ256XX is not set -CONFIG_CHARGER_RK817=m +CONFIG_CHARGER_RK817=y # CONFIG_CHARGER_SMB347 is not set # CONFIG_CHARGER_TPS65090 is not set # CONFIG_BATTERY_GAUGE_LTC2941 is not set @@ -4009,7 +4023,7 @@ CONFIG_SENSORS_ADT7475=m CONFIG_SENSORS_ASC7621=m # CONFIG_SENSORS_ASUS_ROG_RYUJIN is not set # CONFIG_SENSORS_AXI_FAN_CONTROL is not set -# CONFIG_SENSORS_ARM_SCMI is not set +CONFIG_SENSORS_ARM_SCMI=m CONFIG_SENSORS_ARM_SCPI=m CONFIG_SENSORS_ATXP1=m # CONFIG_SENSORS_CHIPCAP2 is not set @@ -4254,14 +4268,14 @@ CONFIG_DEVFREQ_THERMAL=y CONFIG_THERMAL_EMULATION=y # CONFIG_THERMAL_MMIO is not set CONFIG_MAX77620_THERMAL=m -CONFIG_ROCKCHIP_THERMAL=m +CONFIG_ROCKCHIP_THERMAL=y CONFIG_GENERIC_ADC_THERMAL=m CONFIG_WATCHDOG=y -CONFIG_WATCHDOG_CORE=m +CONFIG_WATCHDOG_CORE=y # CONFIG_WATCHDOG_NOWAYOUT is not set CONFIG_WATCHDOG_HANDLE_BOOT_ENABLED=y CONFIG_WATCHDOG_OPEN_TIMEOUT=0 -# CONFIG_WATCHDOG_SYSFS is not set +CONFIG_WATCHDOG_SYSFS=y # CONFIG_WATCHDOG_HRTIMER_PRETIMEOUT is not set # @@ -4281,7 +4295,7 @@ CONFIG_WATCHDOG_OPEN_TIMEOUT=0 # CONFIG_ARM_SP805_WATCHDOG is not set # CONFIG_ARM_SBSA_WATCHDOG is not set # CONFIG_CADENCE_WATCHDOG is not set -CONFIG_DW_WATCHDOG=m +CONFIG_DW_WATCHDOG=y # CONFIG_MAX63XX_WATCHDOG is not set # CONFIG_MAX77620_WATCHDOG is not set # CONFIG_ARM_SMC_WATCHDOG is not set @@ -4464,7 +4478,7 @@ CONFIG_REGULATOR_FIXED_VOLTAGE=y # CONFIG_REGULATOR_ACT8865 is not set # CONFIG_REGULATOR_AD5398 is not set # CONFIG_REGULATOR_ADP5055 is not set -# CONFIG_REGULATOR_ARM_SCMI is not set +CONFIG_REGULATOR_ARM_SCMI=m # CONFIG_REGULATOR_AS3711 is not set # CONFIG_REGULATOR_AS3722 is not set # CONFIG_REGULATOR_AW37503 is not set @@ -4556,6 +4570,7 @@ CONFIG_REGULATOR_RK808=y # CONFIG_REGULATOR_TPS6507X is not set # CONFIG_REGULATOR_TPS65090 is not set # CONFIG_REGULATOR_TPS65132 is not set +CONFIG_REGULATOR_TPS65185=m # CONFIG_REGULATOR_TPS6524X is not set # CONFIG_REGULATOR_TPS6586X is not set CONFIG_REGULATOR_VCTRL=y @@ -4986,8 +5001,8 @@ CONFIG_DRM_MIPI_DSI=y CONFIG_DRM_KMS_HELPER=y # CONFIG_DRM_PANIC is not set CONFIG_DRM_CLIENT=y -CONFIG_DRM_CLIENT_LIB=y -CONFIG_DRM_CLIENT_SELECTION=y +CONFIG_DRM_CLIENT_LIB=m +CONFIG_DRM_CLIENT_SELECTION=m CONFIG_DRM_CLIENT_SETUP=y # @@ -5012,7 +5027,10 @@ CONFIG_DRM_DISPLAY_HDMI_CEC_HELPER=y CONFIG_DRM_DISPLAY_HDMI_CEC_NOTIFIER_HELPER=y CONFIG_DRM_DISPLAY_HDMI_HELPER=y CONFIG_DRM_DISPLAY_HDMI_STATE_HELPER=y -CONFIG_DRM_GEM_DMA_HELPER=y +CONFIG_DRM_EPD_HELPER=m +CONFIG_DRM_TTM=m +CONFIG_DRM_TTM_HELPER=m +CONFIG_DRM_GEM_DMA_HELPER=m CONFIG_DRM_GEM_SHMEM_HELPER=m CONFIG_DRM_SCHED=m @@ -5037,7 +5055,7 @@ CONFIG_DRM_SCHED=m # CONFIG_DRM_XE is not set CONFIG_DRM_VGEM=m # CONFIG_DRM_VKMS is not set -CONFIG_DRM_ROCKCHIP=y +CONFIG_DRM_ROCKCHIP=m CONFIG_ROCKCHIP_VOP=y CONFIG_ROCKCHIP_VOP2=y CONFIG_ROCKCHIP_ANALOGIX_DP=y @@ -5051,6 +5069,8 @@ CONFIG_ROCKCHIP_INNO_HDMI=y CONFIG_ROCKCHIP_LVDS=y CONFIG_ROCKCHIP_RGB=y CONFIG_ROCKCHIP_RK3066_HDMI=y +CONFIG_DRM_ROCKCHIP_EBC=m +# CONFIG_DRM_ROCKCHIP_EBC_3WIN_MODE is not set # CONFIG_DRM_VMWGFX is not set CONFIG_DRM_UDL=m # CONFIG_DRM_AST is not set @@ -5166,7 +5186,7 @@ CONFIG_DRM_PANEL_KINGDISPLAY_KD097D04=m # CONFIG_DRM_PANEL_SONY_TULIP_TRULY_NT35521 is not set # CONFIG_DRM_PANEL_STARTEK_KD070FHFID015 is not set CONFIG_DRM_PANEL_EDP=y -CONFIG_DRM_PANEL_SIMPLE=y +CONFIG_DRM_PANEL_SIMPLE=m # CONFIG_DRM_PANEL_SUMMIT is not set # CONFIG_DRM_PANEL_SYNAPTICS_R63353 is not set # CONFIG_DRM_PANEL_SYNAPTICS_TDDI is not set @@ -5193,7 +5213,7 @@ CONFIG_DRM_PANEL_BRIDGE=y # CONFIG_DRM_CHIPONE_ICN6211 is not set # CONFIG_DRM_CHRONTEL_CH7033 is not set CONFIG_DRM_CROS_EC_ANX7688=m -CONFIG_DRM_DISPLAY_CONNECTOR=y +CONFIG_DRM_DISPLAY_CONNECTOR=m CONFIG_DRM_I2C_NXP_TDA998X=m # CONFIG_DRM_ITE_IT6263 is not set # CONFIG_DRM_ITE_IT6505 is not set @@ -5229,23 +5249,23 @@ CONFIG_DRM_PARADE_PS8640=y # CONFIG_DRM_WAVESHARE_BRIDGE is not set CONFIG_DRM_ANALOGIX_ANX6345=m CONFIG_DRM_ANALOGIX_ANX78XX=m -CONFIG_DRM_ANALOGIX_DP=y +CONFIG_DRM_ANALOGIX_DP=m # CONFIG_DRM_ANALOGIX_ANX7625 is not set CONFIG_DRM_I2C_ADV7511=m CONFIG_DRM_I2C_ADV7511_AUDIO=y CONFIG_DRM_I2C_ADV7511_CEC=y # CONFIG_DRM_CDNS_DSI is not set # CONFIG_DRM_CDNS_MHDP8546 is not set -CONFIG_DRM_DW_DP=y -CONFIG_DRM_DW_HDMI=y +CONFIG_DRM_DW_DP=m +CONFIG_DRM_DW_HDMI=m CONFIG_DRM_DW_HDMI_AHB_AUDIO=m CONFIG_DRM_DW_HDMI_I2S_AUDIO=m CONFIG_DRM_DW_HDMI_GP_AUDIO=m CONFIG_DRM_DW_HDMI_CEC=m -CONFIG_DRM_DW_HDMI_QP=y +CONFIG_DRM_DW_HDMI_QP=m # CONFIG_DRM_DW_HDMI_QP_CEC is not set -CONFIG_DRM_DW_MIPI_DSI=y -CONFIG_DRM_DW_MIPI_DSI2=y +CONFIG_DRM_DW_MIPI_DSI=m +CONFIG_DRM_DW_MIPI_DSI2=m # end of Display Interface Bridges # CONFIG_DRM_ETNAVIV is not set @@ -5272,6 +5292,7 @@ CONFIG_DRM_PANFROST=m # CONFIG_DRM_PANTHOR is not set # CONFIG_DRM_TIDSS is not set # CONFIG_DRM_GUD is not set +CONFIG_DRM_GUD_GADGET=m # CONFIG_DRM_ST7571_I2C is not set # CONFIG_DRM_ST7586 is not set # CONFIG_DRM_ST7735R is not set @@ -5360,7 +5381,7 @@ CONFIG_BACKLIGHT_PWM=y # CONFIG_BACKLIGHT_ADP8860 is not set # CONFIG_BACKLIGHT_ADP8870 is not set # CONFIG_BACKLIGHT_LM3509 is not set -# CONFIG_BACKLIGHT_LM3630A is not set +CONFIG_BACKLIGHT_LM3630A=y # CONFIG_BACKLIGHT_LM3639 is not set CONFIG_BACKLIGHT_LP855X=y # CONFIG_BACKLIGHT_MP3309C is not set @@ -5407,7 +5428,7 @@ CONFIG_SND_HWDEP=m CONFIG_SND_SEQ_DEVICE=m CONFIG_SND_RAWMIDI=m CONFIG_SND_UMP=m -# CONFIG_SND_UMP_LEGACY_RAWMIDI is not set +CONFIG_SND_UMP_LEGACY_RAWMIDI=y CONFIG_SND_JACK=y CONFIG_SND_JACK_INPUT_DEV=y # CONFIG_SND_OSSEMUL is not set @@ -6126,7 +6147,7 @@ CONFIG_USB_ANNOUNCE_NEW_DEVICES=y # CONFIG_USB_DEFAULT_PERSIST=y # CONFIG_USB_FEW_INIT_RETRIES is not set -# CONFIG_USB_DYNAMIC_MINORS is not set +CONFIG_USB_DYNAMIC_MINORS=y CONFIG_USB_OTG=y # CONFIG_USB_OTG_PRODUCTLIST is not set # CONFIG_USB_OTG_DISABLE_EXTERNAL_HUB is not set @@ -6236,7 +6257,7 @@ CONFIG_USB_DWC3_DUAL_ROLE=y CONFIG_USB_DWC3_HAPS=y CONFIG_USB_DWC3_OF_SIMPLE=y CONFIG_USB_DWC3_GENERIC_PLAT=y -CONFIG_USB_DWC2=y +CONFIG_USB_DWC2=m # CONFIG_USB_DWC2_HOST is not set # @@ -6244,7 +6265,7 @@ CONFIG_USB_DWC2=y # # CONFIG_USB_DWC2_PERIPHERAL is not set CONFIG_USB_DWC2_DUAL_ROLE=y -CONFIG_USB_DWC2_PCI=y +CONFIG_USB_DWC2_PCI=m # CONFIG_USB_DWC2_DEBUG is not set # CONFIG_USB_DWC2_TRACK_MISSED_SOFS is not set CONFIG_USB_CHIPIDEA=y @@ -6391,30 +6412,44 @@ CONFIG_USB_LIBCOMPOSITE=m CONFIG_USB_F_ACM=m CONFIG_USB_U_SERIAL=m CONFIG_USB_U_ETHER=m +CONFIG_USB_U_AUDIO=m +CONFIG_USB_F_SERIAL=m +CONFIG_USB_F_OBEX=m CONFIG_USB_F_NCM=m +CONFIG_USB_F_ECM=m +CONFIG_USB_F_EEM=m +CONFIG_USB_F_SUBSET=m CONFIG_USB_F_RNDIS=m CONFIG_USB_F_MASS_STORAGE=m CONFIG_USB_F_FS=m +CONFIG_USB_F_UAC1=m +CONFIG_USB_F_UAC2=m +CONFIG_USB_F_UVC=m +CONFIG_USB_F_MIDI=m +CONFIG_USB_F_MIDI2=m +CONFIG_USB_F_HID=m +CONFIG_USB_F_GUD=m CONFIG_USB_CONFIGFS=m -# CONFIG_USB_CONFIGFS_SERIAL is not set +CONFIG_USB_CONFIGFS_SERIAL=y CONFIG_USB_CONFIGFS_ACM=y -# CONFIG_USB_CONFIGFS_OBEX is not set +CONFIG_USB_CONFIGFS_OBEX=y CONFIG_USB_CONFIGFS_NCM=y -# CONFIG_USB_CONFIGFS_ECM is not set -# CONFIG_USB_CONFIGFS_ECM_SUBSET is not set +CONFIG_USB_CONFIGFS_ECM=y +CONFIG_USB_CONFIGFS_ECM_SUBSET=y CONFIG_USB_CONFIGFS_RNDIS=y -# CONFIG_USB_CONFIGFS_EEM is not set +CONFIG_USB_CONFIGFS_EEM=y CONFIG_USB_CONFIGFS_MASS_STORAGE=y # CONFIG_USB_CONFIGFS_F_LB_SS is not set CONFIG_USB_CONFIGFS_F_FS=y -# CONFIG_USB_CONFIGFS_F_UAC1 is not set +CONFIG_USB_CONFIGFS_F_UAC1=y # CONFIG_USB_CONFIGFS_F_UAC1_LEGACY is not set -# CONFIG_USB_CONFIGFS_F_UAC2 is not set -# CONFIG_USB_CONFIGFS_F_MIDI is not set -# CONFIG_USB_CONFIGFS_F_MIDI2 is not set -# CONFIG_USB_CONFIGFS_F_HID is not set -# CONFIG_USB_CONFIGFS_F_UVC is not set +CONFIG_USB_CONFIGFS_F_UAC2=y +CONFIG_USB_CONFIGFS_F_MIDI=y +CONFIG_USB_CONFIGFS_F_MIDI2=y +CONFIG_USB_CONFIGFS_F_HID=y +CONFIG_USB_CONFIGFS_F_UVC=y # CONFIG_USB_CONFIGFS_F_PRINTER is not set +CONFIG_USB_CONFIGFS_F_GUD=y # # USB Gadget precomposed configurations @@ -6423,7 +6458,7 @@ CONFIG_USB_CONFIGFS_F_FS=y # CONFIG_USB_AUDIO is not set # CONFIG_USB_ETH is not set # CONFIG_USB_G_NCM is not set -# CONFIG_USB_GADGETFS is not set +CONFIG_USB_GADGETFS=y # CONFIG_USB_FUNCTIONFS is not set # CONFIG_USB_MASS_STORAGE is not set # CONFIG_USB_G_SERIAL is not set @@ -6431,7 +6466,9 @@ CONFIG_USB_CONFIGFS_F_FS=y # CONFIG_USB_G_PRINTER is not set # CONFIG_USB_CDC_COMPOSITE is not set # CONFIG_USB_G_ACM_MS is not set -# CONFIG_USB_G_MULTI is not set +CONFIG_USB_G_MULTI=m +CONFIG_USB_G_MULTI_RNDIS=y +# CONFIG_USB_G_MULTI_CDC is not set # CONFIG_USB_G_HID is not set # CONFIG_USB_G_DBGP is not set # CONFIG_USB_G_WEBCAM is not set @@ -6453,7 +6490,7 @@ CONFIG_CROS_EC_UCSI=m # CONFIG_TYPEC_RT1719 is not set # CONFIG_TYPEC_HD3SS3220 is not set # CONFIG_TYPEC_STUSB160X is not set -# CONFIG_TYPEC_WUSB3801 is not set +CONFIG_TYPEC_WUSB3801=m # # USB Type-C Multiplexer/DeMultiplexer Switch support @@ -6672,7 +6709,7 @@ CONFIG_RTC_INTF_DEV=y # CONFIG_RTC_DRV_MAX31335 is not set # CONFIG_RTC_DRV_MAX77686 is not set # CONFIG_RTC_DRV_NCT3018Y is not set -CONFIG_RTC_DRV_RK808=m +CONFIG_RTC_DRV_RK808=y # CONFIG_RTC_DRV_RS5C372 is not set # CONFIG_RTC_DRV_ISL1208 is not set # CONFIG_RTC_DRV_ISL12022 is not set @@ -6990,7 +7027,7 @@ CONFIG_COMMON_CLK=y # CONFIG_COMMON_CLK_MAX77686 is not set # CONFIG_COMMON_CLK_MAX9485 is not set CONFIG_COMMON_CLK_RK808=y -CONFIG_COMMON_CLK_SCMI=m +CONFIG_COMMON_CLK_SCMI=y CONFIG_COMMON_CLK_SCPI=m # CONFIG_COMMON_CLK_SI5341 is not set # CONFIG_COMMON_CLK_SI5351 is not set @@ -7066,9 +7103,9 @@ CONFIG_IOMMU_IO_PGTABLE_ARMV7S=y # end of Generic IOMMU Pagetable Support # CONFIG_IOMMU_DEBUGFS is not set -CONFIG_IOMMU_DEFAULT_DMA_STRICT=y +# CONFIG_IOMMU_DEFAULT_DMA_STRICT is not set # CONFIG_IOMMU_DEFAULT_DMA_LAZY is not set -# CONFIG_IOMMU_DEFAULT_PASSTHROUGH is not set +CONFIG_IOMMU_DEFAULT_PASSTHROUGH=y CONFIG_OF_IOMMU=y CONFIG_IOMMU_DMA=y # CONFIG_ARM_SMMU is not set @@ -7139,6 +7176,7 @@ CONFIG_ROCKCHIP_IOMMU=y CONFIG_ROCKCHIP_GRF=y CONFIG_ROCKCHIP_IODOMAIN=y +CONFIG_ROCKCHIP_SUSPEND_MODE=y # CONFIG_SOC_TI is not set # @@ -7156,8 +7194,8 @@ CONFIG_ROCKCHIP_IODOMAIN=y # # end of Amlogic PM Domains -CONFIG_ARM_SCMI_PERF_DOMAIN=m -CONFIG_ARM_SCMI_POWER_DOMAIN=m +CONFIG_ARM_SCMI_PERF_DOMAIN=y +CONFIG_ARM_SCMI_POWER_DOMAIN=y CONFIG_ARM_SCPI_POWER_DOMAIN=m # @@ -7187,7 +7225,7 @@ CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND=y CONFIG_DEVFREQ_GOV_PERFORMANCE=y CONFIG_DEVFREQ_GOV_POWERSAVE=y CONFIG_DEVFREQ_GOV_USERSPACE=y -# CONFIG_DEVFREQ_GOV_PASSIVE is not set +CONFIG_DEVFREQ_GOV_PASSIVE=y # # DEVFREQ Drivers @@ -7228,7 +7266,7 @@ CONFIG_IIO_TRIGGER=y CONFIG_IIO_CONSUMERS_PER_TRIGGER=2 CONFIG_IIO_SW_DEVICE=m CONFIG_IIO_SW_TRIGGER=m -# CONFIG_IIO_TRIGGERED_EVENT is not set +CONFIG_IIO_TRIGGERED_EVENT=m # # Accelerometers @@ -7261,7 +7299,9 @@ CONFIG_IIO_SW_TRIGGER=m # CONFIG_FXLS8962AF_SPI is not set CONFIG_HID_SENSOR_ACCEL_3D=m CONFIG_IIO_CROS_EC_ACCEL_LEGACY=m -# CONFIG_IIO_ST_ACCEL_3AXIS is not set +CONFIG_IIO_ST_ACCEL_3AXIS=m +CONFIG_IIO_ST_ACCEL_I2C_3AXIS=m +CONFIG_IIO_ST_ACCEL_SPI_3AXIS=m # CONFIG_IIO_KX022A_SPI is not set # CONFIG_IIO_KX022A_I2C is not set # CONFIG_KXSD9 is not set @@ -7454,7 +7494,7 @@ CONFIG_HID_SENSOR_IIO_TRIGGER=m # # IIO SCMI Sensors # -# CONFIG_IIO_SCMI is not set +CONFIG_IIO_SCMI=m # end of IIO SCMI Sensors # @@ -7463,6 +7503,10 @@ CONFIG_HID_SENSOR_IIO_TRIGGER=m # CONFIG_IIO_SSP_SENSORHUB is not set # end of SSP Sensor Common +CONFIG_IIO_ST_SENSORS_I2C=m +CONFIG_IIO_ST_SENSORS_SPI=m +CONFIG_IIO_ST_SENSORS_CORE=m + # # Digital to analog converters # @@ -7893,7 +7937,7 @@ CONFIG_IRQ_MSI_LIB=y CONFIG_ARCH_HAS_RESET_CONTROLLER=y CONFIG_RESET_CONTROLLER=y # CONFIG_RESET_GPIO is not set -CONFIG_RESET_SCMI=m +CONFIG_RESET_SCMI=y # CONFIG_RESET_TI_SYSCON is not set # CONFIG_RESET_TI_TPS380X is not set @@ -7926,12 +7970,12 @@ CONFIG_GENERIC_PHY_MIPI_DPHY=y CONFIG_PHY_ROCKCHIP_DP=y CONFIG_PHY_ROCKCHIP_DPHY_RX0=y CONFIG_PHY_ROCKCHIP_EMMC=y -CONFIG_PHY_ROCKCHIP_INNO_HDMI=y -CONFIG_PHY_ROCKCHIP_INNO_USB2=y +CONFIG_PHY_ROCKCHIP_INNO_HDMI=m +CONFIG_PHY_ROCKCHIP_INNO_USB2=m CONFIG_PHY_ROCKCHIP_INNO_CSIDPHY=m CONFIG_PHY_ROCKCHIP_INNO_DSIDPHY=m CONFIG_PHY_ROCKCHIP_NANENG_COMBO_PHY=m -CONFIG_PHY_ROCKCHIP_PCIE=y +CONFIG_PHY_ROCKCHIP_PCIE=m CONFIG_PHY_ROCKCHIP_SAMSUNG_DCPHY=m CONFIG_PHY_ROCKCHIP_SAMSUNG_HDPTX=m CONFIG_PHY_ROCKCHIP_SNPS_PCIE3=m diff --git a/device/community/linux-postmarketos-rockchip/rk3566-pinenote-support.patch b/device/community/linux-postmarketos-rockchip/rk3566-pinenote-support.patch new file mode 100644 index 000000000..0a081801a --- /dev/null +++ b/device/community/linux-postmarketos-rockchip/rk3566-pinenote-support.patch @@ -0,0 +1,14628 @@ +diff --git a/Documentation/ABI/testing/configfs-usb-gadget-gud b/Documentation/ABI/testing/configfs-usb-gadget-gud +new file mode 100644 +index 000000000..bbf32d494 +--- /dev/null ++++ b/Documentation/ABI/testing/configfs-usb-gadget-gud +@@ -0,0 +1,13 @@ ++What: /config/usb-gadget/gadget/functions/gud.name ++Date: Dec 2020 ++KernelVersion: 5.10 ++Description: ++ The attributes: ++ ++ drm_dev - DRM device number ++ backlight_dev - Backlight device name (optional) ++ The backlight brightness scale should be ++ perceptual not linear. ++ compression - 8-bit mask of compressions to include. ++ formats - Byte string of GUD_PIXEL_FORMAT_* values to include. ++ connectors - 32-bit mask of connectors to include. +diff --git a/Documentation/devicetree/bindings/leds/backlight/lm3630a-backlight.yaml b/Documentation/devicetree/bindings/leds/backlight/lm3630a-backlight.yaml +index 3c9b4054e..6915884d5 100644 +--- a/Documentation/devicetree/bindings/leds/backlight/lm3630a-backlight.yaml ++++ b/Documentation/devicetree/bindings/leds/backlight/lm3630a-backlight.yaml +@@ -33,6 +33,19 @@ properties: + description: GPIO to use to enable/disable the backlight (HWEN pin). + maxItems: 1 + ++ boost_use_1mhz: ++ description: | ++ boolean, if present, change the boost converter switching frequency from ++ the default 500 kHz to 1 MHz ++ type: boolean ++ ++ boost_frequency_shift: ++ description: | ++ boolean, if present, change boost converter switching frequency fromm 500 ++ kHz to 560 kHz or from 1 Mhz to 1.12 Mhz, depending on the boost ++ converter switching frequency ++ type: boolean ++ + required: + - compatible + - reg +diff --git a/Documentation/devicetree/bindings/regulator/tps65185.yaml b/Documentation/devicetree/bindings/regulator/tps65185.yaml +new file mode 100644 +index 000000000..3f53b14b9 +--- /dev/null ++++ b/Documentation/devicetree/bindings/regulator/tps65185.yaml +@@ -0,0 +1,228 @@ ++# make dt_binding_check DT_SCHEMA_FILES=tps65185.yaml ++ ++# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause ++%YAML 1.2 ++--- ++$id: http://devicetree.org/schemas/regulator/tps65185.yaml# ++$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: TPS65185x PMIC for E-Paper displays ++ ++maintainers: ++ - ??? ++ ++description: | ++ The TPS65185x is a power management IC (PMIC) for EPD displays, providing ++ positive and negative driving rails for EPD displays, as well as a ++ bias voltage (VCOM) line and a separate 3.3 V output. The IC is controlled ++ via I2C. ++ ++allOf: ++ - $ref: "regulator.yaml#" ++ ++properties: ++ compatible: ++ const: ti,tps65185 ++ ++ reg: ++ maxItems: 1 ++ ++ interrupts: ++ maxItems: 1 ++ ++ '#io-channel-cells': ++ const: 1 ++ ++ powerup-gpios: ++ description: GPIO to control the vdrive supply ++ maxItems: 1 ++ ++ pwr_good-gpios: ++ description: GPIO indicating vdrive supply state ++ maxItems: 1 ++ ++ vcom_ctrl-gpios: ++ description: GPIO to control VCOM voltage output ++ maxItems: 1 ++ ++ vin-supply: ++ description: Input supply phandle for vin input ++ ++ vin3p3-supply: ++ description: Input supply phandle for 3.3 Volt input ++ ++ wakeup-gpios: ++ description: GPIO to wake up the device from sleep ++ maxItems: 1 ++ ++ ti,up-sequence: ++ description: Controls the activation order of the four rails ++ grouped in the vdrive regulator (VDDH, VPOS, VEE, and VNEG) ++ via the UPSEQ0 register. ++ TODO how to document the four numbers and the possible 4 values? ++ nr 1 VNEG 0 -> strobe1, 1 -> strobe2, 2 -> strobe3, 3 -> strobe 4 ++ nr 2 VEE 0 -> strobe1, 1 -> strobe2, 2 -> strobe3, 3 -> strobe 4 ++ nr 3 VPOS 0 -> strobe1, 1 -> strobe2, 2 -> strobe3, 3 -> strobe 4 ++ nr 4 VDDH 0 -> strobe1, 1 -> strobe2, 2 -> strobe3, 3 -> strobe 4 ++ $ref: "/schemas/types.yaml#/definitions/uint32-array" ++ maxItems: 4 ++ ++ ti,up-delay-ms: ++ description: vdrive activation delays for the four phases (strobes). ++ possible delays are 3, 6, 9, 12 [ms] ++ maxItems: 4 ++ ++ ti,down-sequence: ++ description: TODO same as with up sequence ++ $ref: "/schemas/types.yaml#/definitions/uint32-array" ++ maxItems: 4 ++ ++ ti,down-delay-ms: ++ description: TODO same as with up delays ++ maxItems: 4 ++ ++ regulators: ++ type: object ++ description: List of regulators and its properties ++ ++ properties: ++ "v3p3": ++ type: object ++ $ref: regulator.yaml# ++ description: ++ 3.3 V output regulator ++ TODO How do we include the requirement for the PineNote to always ++ turn this on? Also, I think the driver at some locations assumes ++ this regulator stays on. ++ ++ properties: ++ regulator-name: true ++ regulator-always-on: true ++ regulator-boot-on: true ++ regulator-min-microvolt: true ++ regulator-max-microvolt: true ++ ++ required: ++ - regulator-name ++ - regulator-min-microvolt ++ - regulator-max-microvolt ++ ++ additionalProperties: false ++ ++ "vcom": ++ type: object ++ $ref: regulator.yaml# ++ description: ++ VCOM voltage (back-plane bias for the epd panel) output regulator, ++ -5.11 V to 0 V. (The minus sign is dropped here to conform to the ++ regulator framework). ++ TODO Make sure to note that by default the vcom value should be read ++ by the driver from the internal memory, but can also be specified ++ here. ++ ++ properties: ++ regulator-name: true ++ regulator-always-on: true ++ regulator-boot-on: true ++ ++ required: ++ - regulator-name ++ ++ additionalProperties: false ++ ++ "vdrive": ++ type: object ++ $ref: regulator.yaml# ++ description: ++ Controls the power rails (VDDH, VPOS, VEE, and VNEG). ++ ++ properties: ++ regulator-name: true ++ regulator-always-on: true ++ # TODO: this should probably be false. It works, but should not be ++ # the default as constantly driving vdrive can damage the epd ++ # display ++ regulator-boot-on: true ++ # TODO: This is a virtual regulator in the sense that it groups four ++ # different voltages whose voltages we do not have (direct) control ++ # over. Are these values required? ++ regulator-min-microvolt: true ++ regulator-max-microvolt: true ++ ++ required: ++ - regulator-name ++ - regulator-min-microvolt ++ - regulator-max-microvolt ++ ++ additionalProperties: false ++ ++ ++required: ++ - compatible ++ - reg ++ - interrupts ++ - powerup-gpios ++ - pwr_good-gpios ++ - vcom_ctrl-gpios ++ - vin-supply ++ - vin3p3-supply ++ - wakeup-gpios ++ - ti,up-sequence ++ - ti,up-delay-ms ++ - ti,down-sequence ++ - ti,down-delay-ms ++ - regulators ++ ++additionalProperties: false ++ ++examples: ++ - | ++ #include ++ #include ++ ++ i2c { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ ebc_pmic: pmic@68 { ++ compatible = "ti,tps65185"; ++ reg = <0x68>; ++ interrupt-parent = <&gpio3>; ++ interrupts = <6 IRQ_TYPE_LEVEL_LOW>; ++ #io-channel-cells = <1>; ++ pinctrl-0 = <&ebc_pmic_pins>; ++ pinctrl-names = "default"; ++ powerup-gpios = <&gpio3 8 GPIO_ACTIVE_HIGH>; ++ pwr_good-gpios = <&gpio3 7 GPIO_ACTIVE_HIGH>; ++ vcom_ctrl-gpios = <&gpio4 10 GPIO_ACTIVE_HIGH>; ++ vin-supply = <&vcc_bat>; ++ vin3p3-supply = <&vcc_3v3>; ++ wakeup-gpios = <&gpio3 5 GPIO_ACTIVE_HIGH>; ++ ti,up-sequence = <1>, <0>, <2>, <3>; ++ ti,up-delay-ms = <3>, <3>, <3>, <3>; ++ ti,down-sequence = <2>, <3>, <1>, <0>; ++ ti,down-delay-ms = <3>, <6>, <6>, <6>; ++ ++ regulators { ++ v3p3: v3p3 { ++ regulator-name = "v3p3"; ++ regulator-always-on; ++ regulator-min-microvolt = <3300000>; ++ regulator-max-microvolt = <3300000>; ++ }; ++ ++ vcom: vcom { ++ regulator-name = "vcom"; ++ /* voltage range is board-specific */ ++ /* regulator-min-microvolt = <930000>; */ ++ /* regulator-max-microvolt = <930000>; */ ++ }; ++ ++ vdrive: vdrive { ++ regulator-name = "vdrive"; ++ regulator-min-microvolt = <15000000>; ++ regulator-max-microvolt = <15000000>; ++ }; ++ }; ++ }; ++ }; +diff --git a/MAINTAINERS b/MAINTAINERS +index e08767323..ef133e67e 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -7863,7 +7863,9 @@ M: Ruben Wauters + S: Maintained + W: https://github.com/notro/gud/wiki + T: git https://gitlab.freedesktop.org/drm/misc/kernel.git ++F: Documentation/ABI/testing/configfs-usb-gadget-gud + F: drivers/gpu/drm/gud/ ++F: drivers/usb/gadget/function/f_gud.c + F: include/drm/gud.h + + DRM DRIVER FOR GRAIN MEDIA GM12U320 PROJECTORS +diff --git a/arch/arm64/boot/dts/rockchip/rk3566-pinenote-v1.2.dts b/arch/arm64/boot/dts/rockchip/rk3566-pinenote-v1.2.dts +index 6bbc4c675..92ef19676 100644 +--- a/arch/arm64/boot/dts/rockchip/rk3566-pinenote-v1.2.dts ++++ b/arch/arm64/boot/dts/rockchip/rk3566-pinenote-v1.2.dts +@@ -16,3 +16,39 @@ &pmu_io_domains { + &spk_amp { + VCC-supply = <&vcc_bat>; + }; ++ ++&usb2phy0_otg { ++ port { ++ usb2phy0_typec_hs: endpoint { ++ remote-endpoint = <&usbc_hs>; ++ }; ++ }; ++}; ++ ++&wusb3801 { ++ status = "okay"; ++ ++ connector { ++ compatible = "usb-c-connector"; ++ label = "USB-C"; ++ vbus-supply = <&otg_switch>; ++ power-role = "dual"; ++ try-power-role = "sink"; ++ data-role = "dual"; ++ typec-power-opmode = "default"; ++ pd-disable; ++ ++ ports { ++ #address-cells = <0x01>; ++ #size-cells = <0x00>; ++ ++ port@0 { ++ reg = <0x00>; ++ ++ usbc_hs: endpoint { ++ remote-endpoint = <&usb2phy0_typec_hs>; ++ }; ++ }; ++ }; ++ }; ++}; +diff --git a/arch/arm64/boot/dts/rockchip/rk3566-pinenote.dtsi b/arch/arm64/boot/dts/rockchip/rk3566-pinenote.dtsi +index 5c6f8cc40..bc8cb5945 100644 +--- a/arch/arm64/boot/dts/rockchip/rk3566-pinenote.dtsi ++++ b/arch/arm64/boot/dts/rockchip/rk3566-pinenote.dtsi +@@ -5,6 +5,7 @@ + #include + #include + #include ++#include + + #include "rk3566.dtsi" + +@@ -40,6 +41,51 @@ spk_amp: audio-amplifier { + sound-name-prefix = "Speaker Amp"; + }; + ++ battery_cell: battery-cell { ++ compatible = "simple-battery"; ++ charge-full-design-microamp-hours = <4000000>; ++ charge-term-current-microamp = <300000>; ++ constant-charge-current-max-microamp = <2000000>; ++ constant-charge-voltage-max-microvolt = <4200000>; ++ factory-internal-resistance-micro-ohms = <96000>; ++ voltage-max-design-microvolt = <4200000>; ++ voltage-min-design-microvolt = <3500000>; ++ ++ ocv-capacity-celsius = <20>; ++ ocv-capacity-table-0 = <4168000 100>, ++ <4109000 95>, <4066000 90>, <4023000 85>, <3985000 80>, ++ <3954000 75>, <3924000 70>, <3897000 65>, <3866000 60>, ++ <3826000 55>, <3804000 50>, <3789000 45>, <3777000 40>, ++ <3770000 35>, <3763000 30>, <3750000 25>, <3732000 20>, ++ <3710000 15>, <3680000 10>, <3670000 5>, <3500000 0>; ++ }; ++ ++ bt_sco_codec: bt-sco-codec { ++ compatible = "linux,bt-sco"; ++ #sound-dai-cells = <1>; ++ }; ++ ++// bt-sound { ++// compatible = "simple-audio-card"; ++// #address-cells = <1>; ++// #size-cells = <0>; ++// simple-audio-card,name = "PineNote Bluetooth"; ++// ++// simple-audio-card,dai-link@0 { ++// format = "i2s"; ++// frame-master = <&bt_link0_cpu>; ++// bitclock-master = <&bt_link0_cpu>; ++// ++// bt_link0_cpu: cpu { ++// sound-dai = <&i2s2_2ch>; ++// }; ++// ++// bt_link0_codec: codec { ++// sound-dai = <&bt_sco_codec 0>; ++// }; ++// }; ++// }; ++ + dmic_codec: dmic-codec { + compatible = "dmic-codec"; + num-channels = <6>; +@@ -71,9 +117,43 @@ led-0 { + color = ; + function = LED_FUNCTION_CHARGING; + gpios = <&gpio3 RK_PC5 GPIO_ACTIVE_HIGH>; ++ linux,default-trigger = "rk817-battery-charging"; ++ retain-state-suspended; + }; + }; + ++ panel { ++ compatible = "eink,ed103tc2"; ++ ++ port { ++ panel_in_ebc: endpoint { ++ remote-endpoint = <&ebc_out_panel>; ++ }; ++ }; ++ }; ++ ++ rockchip-suspend { ++ compatible = "rockchip,pm-rk3568"; ++ status = "okay"; ++ rockchip,sleep-debug-en = <0>; ++ rockchip,sleep-mode-config = < ++ (0 ++ | RKPM_SLP_CENTER_OFF ++ | RKPM_SLP_ARMOFF_LOGOFF ++ | RKPM_SLP_PMIC_LP ++ | RKPM_SLP_HW_PLLS_OFF ++ | RKPM_SLP_PMUALIVE_32K ++ | RKPM_SLP_OSC_DIS ++ | RKPM_SLP_32K_PVTM ++ ) ++ >; ++ rockchip,wakeup-config = < ++ (0 ++ | RKPM_GPIO_WKUP_EN ++ ) ++ >; ++ }; ++ + sdio_pwrseq: sdio-pwrseq { + compatible = "mmc-pwrseq-simple"; + clocks = <&rk817 1>; +@@ -131,6 +211,34 @@ link1_codec: codec { + }; + }; + ++ spi-gpio { ++ compatible = "spi-gpio"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ pinctrl-0 = <&ble_spi_pins>; ++ pinctrl-names = "default"; ++ mosi-gpios = <&gpio3 RK_PC1 GPIO_ACTIVE_HIGH>; ++ sck-gpios = <&gpio3 RK_PC3 GPIO_ACTIVE_HIGH>; ++ num-chipselects = <0>; ++ status = "okay"; ++ ++ /* TODO: write binding */ ++ bluetooth@0 { ++ compatible = "pine64,pinenote-ws8100-pen"; ++ reg = <0>; ++ cs-gpios = <&gpio3 RK_PC2 GPIO_ACTIVE_HIGH>; ++ host_state-gpios = <&gpio1 RK_PD6 GPIO_ACTIVE_HIGH>; ++ interrupt-parent = <&gpio0>; ++ interrupts = ; ++ pinctrl-0 = <&ble_host_state_h>, <&ble_int_l>, <&ble_rst_h>; ++ pinctrl-names = "default"; ++ reset-gpios = <&gpio3 RK_PC0 GPIO_ACTIVE_HIGH>; ++ spi-3wire; ++ spi-max-frequency = <10000>; ++ spi-cs-inactive-delay-ns = <7000000>; ++ }; ++ }; ++ + vbat_4g: regulator-vbat-4g { + compatible = "regulator-fixed"; + regulator-name = "vbat_4g"; +@@ -199,6 +307,27 @@ vdda_0v9: regulator-vdda-0v9 { + }; + }; + ++&cru { ++ // assigned-clocks = <&pmucru CLK_RTC_32K>, <&cru PLL_GPLL>, <&pmucru PLL_PPLL>, <&cru ACLK_RGA_PRE>, <&cru HCLK_RGA_PRE>; ++ assigned-clocks = <&pmucru CLK_RTC_32K>, ++ <&cru ACLK_TOP_HIGH>, <&cru ACLK_TOP_LOW>, ++ <&cru PLL_GPLL>, <&pmucru PLL_PPLL>, ++ <&cru ACLK_RGA_PRE>, <&cru CPLL_250M>, ++ <&cru HCLK_RGA_PRE>, <&cru HCLK_EBC>, ++ <&cru HCLK_JENC>, <&cru CPLL_333M>; ++ // assigned-clock-rates = <32768>, <1200000000>, <200000000>, <300000000>, <300000000>; ++ // assigned-clock-rates = <32768>, <1200000000>, <200000000>, <250000000>, <250000000>, <250000000>, <250000000>, <250000000>; ++ assigned-clock-rates = <32768>, ++ <500000000>, <400000000>, ++ <1200000000>, <200000000>, ++ <300000000>, <250000000>, ++ <300000000>, <300000000>, ++ <300000000>, <33333334>; ++ // stronger artifacting, I suspend instabilities due to too high clock frequencies ++ // assigned-clock-rates = <32768>, <1200000000>, <200000000>, <333400000>, <333400000>, <333400000>, <333400000>, <333400000>; ++ assigned-clock-parents = <&pmucru CLK_RTC32K_FRAC>; ++}; ++ + &cpu0 { + cpu-supply = <&vdd_cpu>; + }; +@@ -215,6 +344,29 @@ &cpu3 { + cpu-supply = <&vdd_cpu>; + }; + ++&ebc { ++ io-channels = <&ebc_pmic 0>; ++ panel-supply = <&v3p3>; ++ vcom-supply = <&vcom>; ++ vdrive-supply = <&vdrive>; ++ status = "okay"; ++ ++ port { ++ ebc_out_panel: endpoint { ++ remote-endpoint = <&panel_in_ebc>; ++ }; ++ }; ++}; ++ ++&eink { ++ status = "okay"; ++}; ++ ++&gpu { ++ mali-supply = <&vdd_gpu_npu>; ++ status = "okay"; ++}; ++ + &i2c0 { + status = "okay"; + +@@ -260,6 +412,22 @@ rk817: pmic@20 { + vcc8-supply = <&vcc_sys>; + vcc9-supply = <&dcdc_boost>; + ++ // used for kernel 5.17 ++ battery { ++ monitored-battery = <&battery_cell>; ++ rockchip,resistor-sense-micro-ohms = <10000>; ++ rockchip,sleep-enter-current-microamp = <150000>; ++ rockchip,sleep-filter-current-microamp = <100000>; ++ }; ++ ++ // newer kernels (at least >= 6.2) ++ rk817_charger: charger { ++ monitored-battery = <&battery_cell>; ++ rockchip,resistor-sense-micro-ohms = <10000>; ++ rockchip,sleep-enter-current-microamp = <150000>; ++ rockchip,sleep-filter-current-microamp = <100000>; ++ }; ++ + regulators { + vdd_logic: DCDC_REG1 { + regulator-name = "vdd_logic"; +@@ -277,6 +445,7 @@ regulator-state-mem { + + vdd_gpu_npu: DCDC_REG2 { + regulator-name = "vdd_gpu_npu"; ++ regulator-always-on; + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <1350000>; + regulator-ramp-delay = <6001>; +@@ -453,17 +622,43 @@ led-controller@36 { + pinctrl-names = "default"; + #address-cells = <1>; + #size-cells = <0>; ++ /* ti,boost-frequency-hz = <500000>; */ ++ /* ti,boost-frequency-hz = <560000>; */ ++ /* ti,boost-frequency-hz = <1000000>; */ ++ ti,boost-frequency-hz = <1120000>; ++ ++ /* ti,boost-ovp-microvolt = <16000000>; */ ++ /* ti,boost-ovp-microvolt = <24000000>; */ ++ ti,boost-ovp-microvolt = <32000000>; ++ /* ti,boost-ovp-microvolt = <40000000>; */ ++ ++ ti,boost-ocp-microamp = <600000>; ++ /* ti,boost-ocp-microamp = <800000>; */ ++ /* ti,boost-ocp-microamp = <1000000>; */ ++ /* ti,boost-ocp-microamp = <1200000>; */ + + led@0 { + reg = <0>; + label = "backlight_cool"; + default-brightness = <0>; ++ max-brightness = <255>; ++ // anything between 5000 (5 mA) and 28250 in 750 muA steps ++ /* ti,current-microamp = <5750>; */ ++ ti,current-microamp = <12500>; ++ /* ti,current-microamp = <19250>; */ ++ /* ti,current-microamp = <27500>; */ + }; + + led@1 { + reg = <1>; + label = "backlight_warm"; + default-brightness = <0>; ++ max-brightness = <255>; ++ // anything between 5000 (5 mA) and 28250 in 750 muA steps ++ /* ti,current-microamp = <5750>; */ ++ ti,current-microamp = <12500>; ++ /* ti,current-microamp = <19250>; */ ++ /* ti,current-microamp = <27500>; */ + }; + }; + +@@ -474,6 +669,7 @@ wusb3801: tcpc@60 { + interrupts = ; + pinctrl-0 = <&tcpc_int_l>; + pinctrl-names = "default"; ++ status = "okay"; + + connector { + compatible = "usb-c-connector"; +@@ -499,6 +695,56 @@ typec_hs_usb2phy0: endpoint { + }; + }; + }; ++ ++ /* TODO: write binding */ ++ ebc_pmic: pmic@68 { ++ compatible = "ti,tps65185"; ++ reg = <0x68>; ++ interrupt-parent = <&gpio3>; ++ interrupts = ; ++ #io-channel-cells = <1>; ++ pinctrl-0 = <&ebc_pmic_pins>; ++ pinctrl-names = "default"; ++ powerup-gpios = <&gpio3 RK_PB0 GPIO_ACTIVE_HIGH>; ++ pwr_good-gpios = <&gpio3 RK_PA7 GPIO_ACTIVE_HIGH>; ++ vcom_ctrl-gpios = <&gpio4 RK_PB2 GPIO_ACTIVE_HIGH>; ++ vin-supply = <&vcc_bat>; ++ vin3p3-supply = <&vcc_3v3>; ++ wakeup-gpios = <&gpio3 RK_PA5 GPIO_ACTIVE_HIGH>; ++ ti,up-sequence = <1>, <0>, <2>, <3>; ++ ti,up-delay-ms = <3>, <3>, <3>, <3>; ++ ti,down-sequence = <2>, <3>, <1>, <0>; ++ ti,down-delay-ms = <3>, <6>, <6>, <6>; ++ ++ regulators { ++ v3p3: v3p3 { ++ regulator-name = "v3p3"; ++ /* At least pinenote v1.1 and v1.2: Information provided by ++ * smaeul (Samuel Holland): Allowing this regulator to turn off ++ * can lead to interrupt storms ++ * While writing a driver for the TPS65185, I ran into an ++ * interrupt storm. The reason is that EBC_PMIC_INT is pulled ++ * up to VCC_EINK1 through R2003. But this is the output of the ++ * V3P3 switch. So that means the IRQ cannot be enabled while ++ * V3P3 is turned off. ++ */ ++ regulator-always-on; ++ regulator-min-microvolt = <3300000>; ++ regulator-max-microvolt = <3300000>; ++ }; ++ ++ vcom: vcom { ++ regulator-name = "vcom"; ++ /* voltage range is board-specific */ ++ }; ++ ++ vdrive: vdrive { ++ regulator-name = "vdrive"; ++ regulator-min-microvolt = <15000000>; ++ regulator-max-microvolt = <15000000>; ++ }; ++ }; ++ }; + }; + + &i2c5 { +@@ -514,6 +760,31 @@ accelerometer@18 { + st,drdy-int-pin = <1>; + vdd-supply = <&vcc_3v3>; + vddio-supply = <&vcc_3v3>; ++ // if we use panel-reflection=1 from the ebc driver, then the ++ // accelerometer must be rotated ++ mount-matrix = "-1", "0", "0", ++ "0", "1", "0", ++ "0", "0", "1"; ++ }; ++ ++ touchscreen@24 { ++ compatible = "cypress,tt21000"; ++ hid-descr-addr = <0x1>; ++ reg = <0x24>; ++ interrupt-parent = <&gpio0>; ++ interrupts = ; ++ pinctrl-0 = <&ts_int_l>, <&ts_rst_l>; ++ pinctrl-names = "default"; ++ reset-gpios = <&gpio0 RK_PA5 GPIO_ACTIVE_LOW>; ++ vdd-supply = <&vcc_3v3_pmu>; ++ ++ touchscreen-max-pressure = <46>; ++ touchscreen-min-x = <10>; ++ touchscreen-min-y = <5>; ++ touchscreen-size-x = <1863>; ++ touchscreen-size-y = <1399>; ++ touchscreen-x-mm = <1864>; ++ touchscreen-y-mm = <1400>; + }; + }; + +@@ -523,6 +794,11 @@ &i2s1_8ch { + status = "okay"; + }; + ++&i2s2_2ch { ++ rockchip,trcm-sync-tx-only; ++ status = "okay"; ++}; ++ + &pdm { + pinctrl-0 = <&pdmm0_clk1>, <&pdmm0_sdi1>, <&pdmm0_sdi2>; + /* microphones are on channels 1 and 2 */ +@@ -549,6 +825,29 @@ backlight_hwen_h: backlight-hwen-h { + }; + }; + ++ ble { ++ ble_host_state_h: ble-host-state-h { ++ rockchip,pins = <1 RK_PD6 RK_FUNC_GPIO &pcfg_pull_none>; ++ }; ++ ++ ble_int_l: ble-int-l { ++ rockchip,pins = <0 RK_PB5 RK_FUNC_GPIO &pcfg_pull_down>; ++ }; ++ ++ ble_rst_h: ble-rst-h { ++ rockchip,pins = <3 RK_PC0 RK_FUNC_GPIO &pcfg_pull_none>; ++ }; ++ ++ ble_spi_pins: ble-spi-pins { ++ rockchip,pins = /* data */ ++ <3 RK_PC1 RK_FUNC_GPIO &pcfg_pull_none>, ++ /* cs */ ++ <3 RK_PC2 RK_FUNC_GPIO &pcfg_pull_none>, ++ /* clk */ ++ <3 RK_PC3 RK_FUNC_GPIO &pcfg_pull_none>; ++ }; ++ }; ++ + bt { + bt_enable_h: bt-enable-h { + rockchip,pins = <0 RK_PC4 RK_FUNC_GPIO &pcfg_pull_none>; +@@ -563,6 +862,21 @@ bt_wake_h: bt-wake-h { + }; + }; + ++ ebc-pmic { ++ ebc_pmic_pins: ebc-pmic-pins { ++ rockchip,pins = /* wakeup */ ++ <3 RK_PA5 RK_FUNC_GPIO &pcfg_pull_none>, ++ /* int */ ++ <3 RK_PA6 RK_FUNC_GPIO &pcfg_pull_up>, ++ /* pwr_good */ ++ <3 RK_PA7 RK_FUNC_GPIO &pcfg_pull_none>, ++ /* pwrup */ ++ <3 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>, ++ /* vcom_ctrl */ ++ <4 RK_PB2 RK_FUNC_GPIO &pcfg_pull_none>; ++ }; ++ }; ++ + led { + led_pin: led-pin { + rockchip,pins = <3 RK_PC5 RK_FUNC_GPIO &pcfg_pull_none>; +@@ -611,6 +925,16 @@ tcpc_int_l: tcpc-int-l { + }; + }; + ++ touchscreen { ++ ts_int_l: ts-int-l { ++ rockchip,pins = <0 RK_PA6 RK_FUNC_GPIO &pcfg_pull_up>; ++ }; ++ ++ ts_rst_l: ts-rst-l { ++ rockchip,pins = <0 RK_PA5 RK_FUNC_GPIO &pcfg_pull_none>; ++ }; ++ }; ++ + vcc-wl { + vcc_wl_pin: vcc-wl-pin { + rockchip,pins = <0 RK_PA0 RK_FUNC_GPIO &pcfg_pull_none>; +@@ -681,16 +1005,19 @@ &uart1 { + pinctrl-names = "default"; + uart-has-rtscts; + status = "okay"; ++ rx-threshold = <1>; ++ tx-threshold = <1>; + + bluetooth { + compatible = "brcm,bcm43438-bt"; + clocks = <&rk817 1>; + clock-names = "lpo"; +- device-wakeup-gpios = <&gpio0 RK_PC2 GPIO_ACTIVE_HIGH>; +- host-wakeup-gpios = <&gpio0 RK_PC3 GPIO_ACTIVE_HIGH>; ++ host-wakeup-gpios = <&gpio0 RK_PC3 GPIO_ACTIVE_HIGH>; ++ device-wakeup-gpios = <&gpio0 RK_PC2 GPIO_ACTIVE_HIGH>; ++ shutdown-gpios = <&gpio0 RK_PC4 GPIO_ACTIVE_HIGH>; ++ max-speed = <3000000>; + pinctrl-0 = <&bt_enable_h>, <&bt_host_wake_l>, <&bt_wake_h>; + pinctrl-names = "default"; +- shutdown-gpios = <&gpio0 RK_PC4 GPIO_ACTIVE_HIGH>; + vbat-supply = <&vcc_wl>; + vddio-supply = <&vcca_1v8_pmu>; + }; +@@ -718,3 +1045,7 @@ usb2phy0_typec_hs: endpoint { + }; + }; + }; ++ ++&rga { ++ status = "okay"; ++}; +diff --git a/arch/arm64/boot/dts/rockchip/rk3566-quartz64-a.dts b/arch/arm64/boot/dts/rockchip/rk3566-quartz64-a.dts +index a9021c524..c5a1068ca 100644 +--- a/arch/arm64/boot/dts/rockchip/rk3566-quartz64-a.dts ++++ b/arch/arm64/boot/dts/rockchip/rk3566-quartz64-a.dts +@@ -212,6 +212,20 @@ vcc_sys: regulator-vcc-sys { + vin-supply = <&vbus>; + }; + ++ vcc_sys_ebc: vcc_sys_ebc { ++ compatible = "regulator-fixed"; ++ gpio = <&gpio0 RK_PB7 GPIO_ACTIVE_HIGH>; ++ enable-active-high; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&vcc_sys_ebc_h>; ++ regulator-always-on; ++ regulator-boot-on; ++ regulator-name = "vcc_sys_ebc"; ++ regulator-min-microvolt = <3300000>; ++ regulator-max-microvolt = <3300000>; ++ vin-supply = <&vcc_sys>; ++ }; ++ + /* sourced from vcc_sys, sdio module operates internally at 3.3v */ + vcc_wl: regulator-vcc-wl { + compatible = "regulator-fixed"; +@@ -222,6 +236,16 @@ vcc_wl: regulator-vcc-wl { + regulator-max-microvolt = <3300000>; + vin-supply = <&vcc_sys>; + }; ++ ++ panel { ++ compatible = "eink,ed103tc2"; ++ ++ port { ++ panel_in_ebc: endpoint { ++ remote-endpoint = <&ebc_out_panel>; ++ }; ++ }; ++ }; + }; + + &combphy1 { +@@ -248,6 +272,24 @@ &cpu3 { + cpu-supply = <&vdd_cpu>; + }; + ++&ebc { ++ status = "okay"; ++ io-channels = <&ebc_pmic 0>; ++ panel-supply = <&v3p3>; ++ vcom-supply = <&vcom>; ++ vdrive-supply = <&vdrive>; ++ ++ port { ++ ebc_out_panel: endpoint { ++ remote-endpoint = <&panel_in_ebc>; ++ }; ++ }; ++}; ++ ++&eink { ++ status = "okay"; ++}; ++ + &cpu_thermal { + trips { + cpu_hot: cpu_hot { +@@ -539,6 +581,64 @@ regulator-state-mem { + }; + }; + ++&i2c1{ ++ status = "okay"; ++ ebc_pmic: pmic@68 { ++ status = "okay"; ++ compatible = "ti,tps65185"; ++ // check ++ reg = <0x68>; ++ // changed 3->4 ++ interrupt-parent = <&gpio4>; ++ // changed Q64-A (EBC_PMIC_INT) ++ //interrupts = ; ++ interrupts = ; ++ #io-channel-cells = <1>; ++ pinctrl-0 = <&ebc_pmic_pins>; ++ pinctrl-names = "default"; ++ // [EBC_PMIC_ON] changed ++ /* powerup-gpios = <&gpio3 RK_PB0 GPIO_ACTIVE_HIGH>; */ ++ powerup-gpios = <&gpio4 RK_PC4 GPIO_ACTIVE_HIGH>; ++ // changed q64-A ++ /* pwr_good-gpios = <&gpio3 RK_PA7 GPIO_ACTIVE_HIGH>; */ ++ pwr_good-gpios = <&gpio4 RK_PB1 GPIO_ACTIVE_HIGH>; ++ // ok q64-A ++ vcom_ctrl-gpios = <&gpio4 RK_PB2 GPIO_ACTIVE_HIGH>; ++ // changed ++ vin-supply = <&vcc_sys_ebc>; ++ vin3p3-supply = <&vcc_sys_ebc>; ++ // changed q64-a ++ // wakeup-gpios = <&gpio3 RK_PA5 GPIO_ACTIVE_HIGH>; ++ wakeup-gpios = <&gpio4 RK_PC3 GPIO_ACTIVE_HIGH>; ++ ti,up-sequence = <1>, <0>, <2>, <3>; ++ ti,up-delay-ms = <3>, <3>, <3>, <3>; ++ ti,down-sequence = <2>, <3>, <1>, <0>; ++ ti,down-delay-ms = <3>, <6>, <6>, <6>; ++ ++ regulators { ++ v3p3: v3p3 { ++ regulator-name = "v3p3"; ++ regulator-always-on; ++ regulator-min-microvolt = <3300000>; ++ regulator-max-microvolt = <3300000>; ++ }; ++ ++ vcom: vcom { ++ regulator-name = "vcom"; ++ /* voltage range is board-specific */ ++ regulator-min-microvolt = <1170000>; ++ regulator-max-microvolt = <1170000>; ++ }; ++ ++ vdrive: vdrive { ++ regulator-name = "vdrive"; ++ regulator-min-microvolt = <15000000>; ++ regulator-max-microvolt = <15000000>; ++ }; ++ }; ++ }; ++}; ++ + /* i2c3 is exposed on con40 + * pin 3 - i2c3_sda_m0, pullup to vcc_3v3 + * pin 5 - i2c3_scl_m0, pullup to vcc_3v3 +@@ -640,6 +740,31 @@ vcc_sd_h: vcc-sd-h { + rockchip,pins = <0 RK_PA5 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; ++ ++ vcc_sys_ebc { ++ vcc_sys_ebc_h: vcc-sys-ebc-h { ++ rockchip,pins = <0 RK_PB7 RK_FUNC_GPIO &pcfg_pull_none>; ++ }; ++ }; ++ ++ ebc-pmic { ++ ebc_pmic_pins: ebc-pmic-pins { ++ rockchip,pins = /* wakeup EBC_PMIC_WAKEUP*/ ++ /* <3 RK_PA5 RK_FUNC_GPIO &pcfg_pull_none>, */ ++ <4 RK_PC3 RK_FUNC_GPIO &pcfg_pull_none>, ++ /* int EBC_PMIC_INT */ ++ /* <3 RK_PA6 RK_FUNC_GPIO &pcfg_pull_up>, */ ++ <4 RK_PB0 RK_FUNC_GPIO &pcfg_pull_up>, ++ /* pwr_good EBC_PMIC_PG*/ ++ /* <3 RK_PA7 RK_FUNC_GPIO &pcfg_pull_none>, */ ++ <4 RK_PB1 RK_FUNC_GPIO &pcfg_pull_none>, ++ /* pwrup EBC_PMIC_ON*/ ++ /* <3 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>, */ ++ <4 RK_PC4 RK_FUNC_GPIO &pcfg_pull_none>, ++ /* vcom_ctrl EBC_VCOM */ ++ <4 RK_PB2 RK_FUNC_GPIO &pcfg_pull_none>; ++ }; ++ }; + }; + + &pmu_io_domains { +diff --git a/arch/arm64/boot/dts/rockchip/rk356x-base.dtsi b/arch/arm64/boot/dts/rockchip/rk356x-base.dtsi +index a2c4957a5..86e70bb25 100644 +--- a/arch/arm64/boot/dts/rockchip/rk356x-base.dtsi ++++ b/arch/arm64/boot/dts/rockchip/rk356x-base.dtsi +@@ -127,6 +127,61 @@ l3_cache: l3-cache { + cache-sets = <512>; + }; + ++ cpu0_opp_table: opp-table-0 { ++ compatible = "operating-points-v2"; ++ opp-shared; ++ ++ opp-408000000 { ++ opp-hz = /bits/ 64 <408000000>; ++ opp-microvolt = <900000 900000 1150000>; ++ clock-latency-ns = <40000>; ++ }; ++ ++ opp-600000000 { ++ opp-hz = /bits/ 64 <600000000>; ++ opp-microvolt = <900000 900000 1150000>; ++ }; ++ ++ opp-816000000 { ++ opp-hz = /bits/ 64 <816000000>; ++ opp-microvolt = <900000 900000 1150000>; ++ opp-suspend; ++ }; ++ ++ opp-1104000000 { ++ opp-hz = /bits/ 64 <1104000000>; ++ opp-microvolt = <900000 900000 1150000>; ++ }; ++ ++ opp-1416000000 { ++ opp-hz = /bits/ 64 <1416000000>; ++ opp-microvolt = <900000 900000 1150000>; ++ }; ++ ++ opp-1608000000 { ++ opp-hz = /bits/ 64 <1608000000>; ++ opp-microvolt = <975000 975000 1150000>; ++ }; ++ ++ opp-1800000000 { ++ opp-hz = /bits/ 64 <1800000000>; ++ opp-microvolt = <1050000 1050000 1150000>; ++ }; ++ }; ++ ++ /* reserved-memory { */ ++ /* #address-cells = <0x00000002>; */ ++ /* #size-cells = <0x00000002>; */ ++ /* ranges; */ ++ ++ /* ebcframebuffer: buffer@10900000 { */ ++ /* compatible = "shared-dma-pool"; */ ++ /* no-map; */ ++ /* reg = <0x00000000 0x10900000 0x00000000 0x02000000>; */ ++ /* }; */ ++ /* }; */ ++ ++ + display_subsystem: display-subsystem { + compatible = "rockchip,display-subsystem"; + ports = <&vop_out>; +@@ -578,6 +633,29 @@ vpu: video-codec@fdea0400 { + power-domains = <&power RK3568_PD_VPU>; + }; + ++ ebc: ebc@fdec0000 { ++ compatible = "rockchip,rk3568-ebc"; ++ reg = <0x0 0xfdec0000 0x0 0x5000>; ++ interrupts = ; ++ clocks = <&cru HCLK_EBC>, <&cru DCLK_EBC>, <&cru CPLL_333M>; ++ clock-names = "hclk", "dclk", "cpll_333m"; ++ pinctrl-0 = <&ebc_pins>; ++ pinctrl-names = "default"; ++ power-domains = <&power RK3568_PD_RGA>; ++ resets = <&cru SRST_H_EBC>, <&cru SRST_D_EBC>; ++ reset-names = "hclk", "dclk"; ++ status = "disabled"; ++ }; ++ ++ eink: eink@fdf00000 { ++ compatible = "rockchip,rk3568-eink-tcon"; ++ reg = <0x0 0xfdf00000 0x0 0x74>; ++ clocks = <&cru PCLK_EINK>, <&cru HCLK_EINK>; ++ clock-names = "pclk", "hclk"; ++ interrupts = ; ++ status = "disabled"; ++ }; ++ + vdpu_mmu: iommu@fdea0800 { + compatible = "rockchip,rk3568-iommu"; + reg = <0x0 0xfdea0800 0x0 0x40>; +@@ -597,6 +675,7 @@ rga: rga@fdeb0000 { + resets = <&cru SRST_RGA_CORE>, <&cru SRST_A_RGA>, <&cru SRST_H_RGA>; + reset-names = "core", "axi", "ahb"; + power-domains = <&power RK3568_PD_RGA>; ++ status = "disabled"; + }; + + vepu: video-codec@fdee0000 { +@@ -1381,6 +1460,7 @@ uart1: serial@fe650000 { + clocks = <&cru SCLK_UART1>, <&cru PCLK_UART1>; + clock-names = "baudclk", "apb_pclk"; + dmas = <&dmac0 2>, <&dmac0 3>; ++ dma-names = "tx", "rx"; + pinctrl-0 = <&uart1m0_xfer>; + pinctrl-names = "default"; + reg-io-width = <4>; +diff --git a/arch/arm64/configs/pinenote_defconfig b/arch/arm64/configs/pinenote_defconfig +new file mode 100644 +index 000000000..7dc6c656b +--- /dev/null ++++ b/arch/arm64/configs/pinenote_defconfig +@@ -0,0 +1,1610 @@ ++CONFIG_DEFAULT_HOSTNAME="pinenote" ++CONFIG_SYSVIPC=y ++CONFIG_POSIX_MQUEUE=y ++CONFIG_GENERIC_IRQ_DEBUGFS=y ++CONFIG_NO_HZ_IDLE=y ++CONFIG_HIGH_RES_TIMERS=y ++CONFIG_BPF_SYSCALL=y ++CONFIG_BPF_JIT=y ++CONFIG_BPF_JIT_ALWAYS_ON=y ++CONFIG_IRQ_TIME_ACCOUNTING=y ++CONFIG_BSD_PROCESS_ACCT=y ++CONFIG_BSD_PROCESS_ACCT_V3=y ++CONFIG_TASKSTATS=y ++CONFIG_TASK_DELAY_ACCT=y ++CONFIG_TASK_XACCT=y ++CONFIG_TASK_IO_ACCOUNTING=y ++CONFIG_PSI=y ++CONFIG_PSI_DEFAULT_DISABLED=y ++CONFIG_IKCONFIG=m ++CONFIG_IKCONFIG_PROC=y ++CONFIG_LOG_BUF_SHIFT=18 ++CONFIG_LOG_CPU_MAX_BUF_SHIFT=0 ++CONFIG_CGROUPS=y ++CONFIG_MEMCG=y ++CONFIG_BLK_CGROUP=y ++CONFIG_CGROUP_SCHED=y ++CONFIG_CFS_BANDWIDTH=y ++CONFIG_RT_GROUP_SCHED=y ++CONFIG_CGROUP_PIDS=y ++CONFIG_CGROUP_RDMA=y ++CONFIG_CGROUP_FREEZER=y ++CONFIG_CGROUP_HUGETLB=y ++CONFIG_CPUSETS=y ++# CONFIG_PROC_PID_CPUSET is not set ++CONFIG_CGROUP_DEVICE=y ++CONFIG_CGROUP_CPUACCT=y ++CONFIG_CGROUP_PERF=y ++CONFIG_CGROUP_BPF=y ++CONFIG_CGROUP_MISC=y ++CONFIG_NAMESPACES=y ++CONFIG_USER_NS=y ++CONFIG_CHECKPOINT_RESTORE=y ++CONFIG_BLK_DEV_INITRD=y ++# CONFIG_RD_BZIP2 is not set ++# CONFIG_RD_LZMA is not set ++# CONFIG_RD_XZ is not set ++# CONFIG_RD_LZO is not set ++# CONFIG_RD_LZ4 is not set ++CONFIG_EXPERT=y ++# CONFIG_UID16 is not set ++# CONFIG_KEXEC=y ++# CONFIG_KEXEC_CORE=y ++# CONFIG_CRASH_CORE=y ++# ++# CONFIG_SYSFS_SYSCALL is not set ++CONFIG_KALLSYMS_ALL=y ++CONFIG_USERFAULTFD=y ++CONFIG_PERF_EVENTS=y ++# CONFIG_SLUB_DEBUG is not set ++# CONFIG_COMPAT_BRK is not set ++CONFIG_ARCH_ROCKCHIP=y ++# CONFIG_ARM64_ERRATUM_826319 is not set ++# CONFIG_ARM64_ERRATUM_827319 is not set ++# CONFIG_ARM64_ERRATUM_824069 is not set ++# CONFIG_ARM64_ERRATUM_819472 is not set ++# CONFIG_ARM64_ERRATUM_832075 is not set ++# CONFIG_ARM64_ERRATUM_845719 is not set ++# CONFIG_ARM64_ERRATUM_843419 is not set ++# CONFIG_ARM64_ERRATUM_1418040 is not set ++# CONFIG_ARM64_ERRATUM_1165522 is not set ++# CONFIG_ARM64_ERRATUM_1319367 is not set ++# CONFIG_ARM64_ERRATUM_1286807 is not set ++# CONFIG_ARM64_ERRATUM_1463225 is not set ++# CONFIG_ARM64_ERRATUM_1542419 is not set ++# CONFIG_ARM64_ERRATUM_1508412 is not set ++# CONFIG_ARM64_ERRATUM_2051678 is not set ++# CONFIG_ARM64_ERRATUM_2054223 is not set ++# CONFIG_ARM64_ERRATUM_2067961 is not set ++# CONFIG_CAVIUM_ERRATUM_22375 is not set ++# CONFIG_CAVIUM_ERRATUM_23154 is not set ++# CONFIG_CAVIUM_ERRATUM_27456 is not set ++# CONFIG_CAVIUM_ERRATUM_30115 is not set ++# CONFIG_CAVIUM_TX2_ERRATUM_219 is not set ++# CONFIG_FUJITSU_ERRATUM_010001 is not set ++# CONFIG_HISILICON_ERRATUM_161600802 is not set ++# CONFIG_QCOM_FALKOR_ERRATUM_1003 is not set ++# CONFIG_QCOM_FALKOR_ERRATUM_1009 is not set ++# CONFIG_QCOM_QDF2400_ERRATUM_0065 is not set ++# CONFIG_QCOM_FALKOR_ERRATUM_E1041 is not set ++# CONFIG_NVIDIA_CARMEL_CNP_ERRATUM is not set ++# CONFIG_SOCIONEXT_SYNQUACER_PREITS is not set ++CONFIG_SCHED_MC=y ++CONFIG_NR_CPUS=4 ++CONFIG_HZ_1000=y ++# CONFIG_UNMAP_KERNEL_AT_EL0 is not set ++# CONFIG_ARM64_TAGGED_ADDR_ABI is not set ++CONFIG_COMPAT=y ++CONFIG_ARMV8_DEPRECATED=y ++CONFIG_CP15_BARRIER_EMULATION=y ++# CONFIG_ARM64_HW_AFDBM is not set ++# CONFIG_ARM64_PTR_AUTH is not set ++# CONFIG_ARM64_AMU_EXTN is not set ++# CONFIG_ARM64_BTI is not set ++# CONFIG_ARM64_E0PD is not set ++# CONFIG_ARM64_EPAN is not set ++# CONFIG_ARM64_SVE is not set ++CONFIG_JUMP_LABEL=y ++# CONFIG_STACKPROTECTOR is not set ++CONFIG_MODULES=y ++CONFIG_MODULE_FORCE_LOAD=y ++CONFIG_MODULE_UNLOAD=y ++CONFIG_MODULE_FORCE_UNLOAD=y ++CONFIG_MODVERSIONS=y ++CONFIG_BLK_DEV_THROTTLING=y ++# CONFIG_BLK_DEBUG_FS is not set ++# CONFIG_MQ_IOSCHED_DEADLINE is not set ++# CONFIG_MQ_IOSCHED_KYBER is not set ++CONFIG_CMA=y ++CONFIG_CMA_DEBUG=y ++CONFIG_CMA_DEBUGFS=y ++CONFIG_CMA_SYSFS=y ++CONFIG_BLK_DEV_LOOP=y ++CONFIG_BLK_DEV_LOOP_MIN_COUNT=0 ++CONFIG_SCSI=m ++# CONFIG_SCSI_PROC_FS is not set ++CONFIG_BLK_DEV_SD=m ++CONFIG_BLK_DEV_SR=m ++CONFIG_CHR_DEV_SG=m ++CONFIG_SCSI_SCAN_ASYNC=y ++# CONFIG_SCSI_LOWLEVEL is not set ++CONFIG_MD=y ++CONFIG_BLK_DEV_DM=m ++CONFIG_DM_THIN_PROVISIONING=m ++# CONFIG_LEGACY_PTYS is not set ++# CONFIG_LDISC_AUTOLOAD is not set ++CONFIG_FB=y ++CONFIG_FRAMEBUFFER_CONSOLE=y ++CONFIG_FRAMEBUFFER_CONSOLE_ROTATION=y ++CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER=y ++CONFIG_RTC_CLASS=y ++CONFIG_RTC_DRV_RK808=y ++# CONFIG_VIRTIO_MENU is not set ++# CONFIG_VHOST_MENU is not set ++CONFIG_STAGING=y ++CONFIG_STAGING_MEDIA=y ++CONFIG_VIDEO_ROCKCHIP_VDEC=m ++# CONFIG_SURFACE_PLATFORMS is not set ++# CONFIG_FSL_ERRATUM_A008585 is not set ++# CONFIG_HISILICON_ERRATUM_161010101 is not set ++# CONFIG_ARM64_ERRATUM_858921 is not set ++CONFIG_EXTCON=y ++CONFIG_EXT4_FS=y ++CONFIG_EXT4_FS_POSIX_ACL=y ++CONFIG_EXT4_FS_SECURITY=y ++CONFIG_XFS_FS=y ++# CONFIG_XFS_SUPPORT_V4 is not set ++CONFIG_XFS_POSIX_ACL=y ++CONFIG_BTRFS_FS=m ++CONFIG_BTRFS_FS_POSIX_ACL=y ++CONFIG_F2FS_FS=y ++CONFIG_F2FS_FS_SECURITY=y ++# CONFIG_DNOTIFY is not set ++CONFIG_QUOTA=y ++# CONFIG_PRINT_QUOTA_WARNING is not set ++CONFIG_AUTOFS_FS=y ++CONFIG_FUSE_FS=m ++CONFIG_OVERLAY_FS=m ++CONFIG_ISO9660_FS=m ++CONFIG_JOLIET=y ++CONFIG_UDF_FS=m ++CONFIG_VFAT_FS=y ++CONFIG_FAT_DEFAULT_UTF8=y ++CONFIG_EXFAT_FS=m ++CONFIG_NTFS3_FS=m ++CONFIG_TMPFS=y ++CONFIG_TMPFS_POSIX_ACL=y ++CONFIG_TMPFS_INODE64=y ++CONFIG_HUGETLBFS=y ++CONFIG_CONFIGFS_FS=y ++CONFIG_EFIVAR_FS=y ++# CONFIG_MISC_FILESYSTEMS is not set ++CONFIG_NLS_CODEPAGE_437=y ++CONFIG_NLS_ISO8859_1=y ++CONFIG_NLS_UTF8=y ++CONFIG_UNICODE=y ++CONFIG_KEYS=y ++CONFIG_DMA_CMA=y ++CONFIG_PRINTK_TIME=y ++# CONFIG_DEBUG_MISC is not set ++CONFIG_DEBUG_INFO_DWARF5=y ++CONFIG_DEBUG_INFO_REDUCED=y ++CONFIG_DEBUG_INFO_SPLIT=y ++CONFIG_MAGIC_SYSRQ=y ++# CONFIG_MAGIC_SYSRQ_SERIAL is not set ++CONFIG_DEBUG_FS=y ++CONFIG_PANIC_ON_OOPS=y ++CONFIG_PANIC_TIMEOUT=1 ++CONFIG_SOFTLOCKUP_DETECTOR=y ++# CONFIG_DETECT_HUNG_TASK is not set ++CONFIG_WQ_WATCHDOG=y ++CONFIG_SCHEDSTATS=y ++CONFIG_CPU_HOTPLUG_STATE_CONTROL=y ++CONFIG_ENABLE_DEFAULT_TRACERS=y ++# CONFIG_UPROBE_EVENTS is not set ++# CONFIG_STRICT_DEVMEM is not set ++# CONFIG_RUNTIME_TESTING_MENU is not set ++CONFIG_ANDROID_BINDER_IPC=y ++CONFIG_ANDROID_BINDERFS=y ++CONFIG_ANDROID_BINDER_DEVICES="binder,hwbinder,vndbinder" ++CONFIG_DM_CRYPT=m ++CONFIG_KEXEC=y ++CONFIG_KEXEC_FILE=y ++CONFIG_KEXEC_CORE=y ++CONFIG_CRASH_CORE=y ++CONFIG_CRASH_DUMP=y ++CONFIG_PROC_VMCORE=y ++CONFIG_DYNAMIC_DEBUG=y ++CONFIG_PROC_KCORE=y ++CONFIG_RELOCATABLE=y ++CONFIG_FUNCTION_TRACER=y ++CONFIG_FUNCTION_GRAPH_TRACER=y ++CONFIG_FUNCTION_GRAPH_RETVAL=y ++CONFIG_OF_OVERLAY=y ++ ++## ++## file: arch/arm64/crypto/Kconfig ++## ++CONFIG_CRYPTO_GHASH_ARM64_CE=m ++CONFIG_CRYPTO_NHPOLY1305_NEON=m ++CONFIG_CRYPTO_POLY1305_NEON=m ++CONFIG_CRYPTO_SHA1_ARM64_CE=m ++CONFIG_CRYPTO_SHA256_ARM64=m ++CONFIG_CRYPTO_SHA2_ARM64_CE=m ++CONFIG_CRYPTO_SHA512_ARM64=m ++CONFIG_CRYPTO_SHA512_ARM64_CE=m ++CONFIG_CRYPTO_SHA3_ARM64=m ++CONFIG_CRYPTO_POLYVAL_ARM64_CE=m ++CONFIG_CRYPTO_AES_ARM64=m ++CONFIG_CRYPTO_AES_ARM64_CE=m ++CONFIG_CRYPTO_AES_ARM64_CE_BLK=m ++CONFIG_CRYPTO_AES_ARM64_NEON_BLK=m ++CONFIG_CRYPTO_CHACHA20_NEON=m ++CONFIG_CRYPTO_AES_ARM64_BS=m ++CONFIG_CRYPTO_AES_ARM64_CE_CCM=m ++CONFIG_CRYPTO_CRCT10DIF_ARM64_CE=m ++ ++## ++## file: block/Kconfig ++## ++CONFIG_BLOCK=y ++ ++## ++## file: crypto/Kconfig ++## ++CONFIG_CRYPTO=y ++CONFIG_CRYPTO_MANAGER=y ++CONFIG_CRYPTO_NULL=m ++CONFIG_CRYPTO_PCRYPT=m ++CONFIG_CRYPTO_AUTHENC=m ++CONFIG_CRYPTO_RSA=m ++CONFIG_CRYPTO_DH=m ++CONFIG_CRYPTO_ECDH=m ++CONFIG_CRYPTO_ECDSA=m ++CONFIG_CRYPTO_ECRDSA=m ++CONFIG_CRYPTO_CURVE25519=m ++CONFIG_CRYPTO_AES=m ++CONFIG_CRYPTO_AES_TI=m ++CONFIG_CRYPTO_BLOWFISH=m ++CONFIG_CRYPTO_DES=m ++CONFIG_CRYPTO_TWOFISH=m ++CONFIG_CRYPTO_CHACHA20=m ++CONFIG_CRYPTO_CBC=m ++CONFIG_CRYPTO_CTR=m ++CONFIG_CRYPTO_CTS=m ++CONFIG_CRYPTO_ECB=m ++CONFIG_CRYPTO_XTS=m ++CONFIG_CRYPTO_AEGIS128=m ++CONFIG_CRYPTO_CHACHA20POLY1305=m ++CONFIG_CRYPTO_CCM=m ++CONFIG_CRYPTO_GCM=m ++CONFIG_CRYPTO_SEQIV=m ++CONFIG_CRYPTO_ECHAINIV=m ++CONFIG_CRYPTO_ESSIV=m ++CONFIG_CRYPTO_BLAKE2B=m ++CONFIG_CRYPTO_CMAC=m ++CONFIG_CRYPTO_GHASH=m ++CONFIG_CRYPTO_HMAC=m ++CONFIG_CRYPTO_MD5=m ++CONFIG_CRYPTO_MICHAEL_MIC=m ++CONFIG_CRYPTO_POLY1305=m ++CONFIG_CRYPTO_SHA1=m ++CONFIG_CRYPTO_SHA256=y ++CONFIG_CRYPTO_SHA512=m ++CONFIG_CRYPTO_SHA3=m ++CONFIG_CRYPTO_STREEBOG=m ++CONFIG_CRYPTO_WP512=m ++CONFIG_CRYPTO_XCBC=m ++CONFIG_CRYPTO_XXHASH=m ++CONFIG_CRYPTO_CRC32C=m ++CONFIG_CRYPTO_CRC32=m ++CONFIG_CRYPTO_CRCT10DIF=m ++CONFIG_CRYPTO_CRC64_ROCKSOFT=m ++CONFIG_CRYPTO_DEFLATE=m ++CONFIG_CRYPTO_LZO=m ++CONFIG_CRYPTO_LZ4=m ++CONFIG_CRYPTO_LZ4HC=m ++CONFIG_CRYPTO_ZSTD=m ++CONFIG_CRYPTO_ANSI_CPRNG=m ++CONFIG_CRYPTO_DRBG_MENU=m ++CONFIG_CRYPTO_DRBG_HASH=y ++CONFIG_CRYPTO_DRBG_CTR=y ++CONFIG_CRYPTO_USER_API_HASH=m ++CONFIG_CRYPTO_USER_API_SKCIPHER=m ++CONFIG_CRYPTO_USER_API_RNG=m ++CONFIG_CRYPTO_USER_API_AEAD=m ++# CONFIG_CRYPTO_USER_API_ENABLE_OBSOLETE is not set ++ ++## ++## file: drivers/ata/Kconfig ++## ++CONFIG_ATA=m ++CONFIG_SATA_AHCI=m ++CONFIG_SATA_AHCI_PLATFORM=m ++CONFIG_AHCI_DWC=m ++ ++## ++## file: drivers/base/Kconfig ++## ++CONFIG_DEVTMPFS=y ++CONFIG_DEVTMPFS_MOUNT=y ++# CONFIG_ALLOW_DEV_COREDUMP is not set ++ ++## ++## file: drivers/bluetooth/Kconfig ++## ++CONFIG_BT_HCIUART=m ++CONFIG_BT_HCIUART_BCM=y ++ ++## ++## file: drivers/block/zram/Kconfig ++## ++CONFIG_ZRAM=m ++CONFIG_ZRAM_BACKEND_LZ4=y ++CONFIG_ZRAM_BACKEND_LZ4HC=y ++CONFIG_ZRAM_BACKEND_ZSTD=y ++CONFIG_ZRAM_BACKEND_DEFLATE=y ++CONFIG_ZRAM_BACKEND_842=y ++CONFIG_ZRAM_BACKEND_LZO=y ++CONFIG_ZRAM_DEF_COMP_ZSTD=y ++CONFIG_ZRAM_WRITEBACK=y ++CONFIG_ZRAM_MEMORY_TRACKING=y ++CONFIG_ZRAM_MULTI_COMP=y ++ ++## ++## file: drivers/base/firmware_loader/Kconfig ++## ++# CONFIG_FW_CACHE is not set ++ ++## ++## file: drivers/char/hw_random/Kconfig ++## ++CONFIG_HW_RANDOM=m ++# CONFIG_HW_RANDOM_TIMERIOMEM is not set ++# CONFIG_HW_RANDOM_INTEL is not set ++# CONFIG_HW_RANDOM_AMD is not set ++# CONFIG_HW_RANDOM_ATMEL is not set ++# CONFIG_HW_RANDOM_BA431 is not set ++# CONFIG_HW_RANDOM_BCM2835 is not set ++# CONFIG_HW_RANDOM_IPROC_RNG200 is not set ++# CONFIG_HW_RANDOM_GEODE is not set ++# CONFIG_HW_RANDOM_N2RNG is not set ++# CONFIG_HW_RANDOM_VIA is not set ++# CONFIG_HW_RANDOM_IXP4XX is not set ++# CONFIG_HW_RANDOM_OMAP is not set ++# CONFIG_HW_RANDOM_OMAP3_ROM is not set ++# CONFIG_HW_RANDOM_OCTEON is not set ++# CONFIG_HW_RANDOM_PASEMI is not set ++# CONFIG_HW_RANDOM_VIRTIO is not set ++# CONFIG_HW_RANDOM_MXC_RNGA is not set ++# CONFIG_HW_RANDOM_IMX_RNGC is not set ++# CONFIG_HW_RANDOM_INGENIC_RNG is not set ++# CONFIG_HW_RANDOM_INGENIC_TRNG is not set ++# CONFIG_HW_RANDOM_NOMADIK is not set ++# CONFIG_HW_RANDOM_PSERIES is not set ++# CONFIG_HW_RANDOM_POWERNV is not set ++# CONFIG_HW_RANDOM_HISI is not set ++# CONFIG_HW_RANDOM_HISTB is not set ++# CONFIG_HW_RANDOM_ST is not set ++# CONFIG_HW_RANDOM_XGENE is not set ++# CONFIG_HW_RANDOM_STM32 is not set ++# CONFIG_HW_RANDOM_PIC32 is not set ++# CONFIG_HW_RANDOM_POLARFIRE_SOC is not set ++# CONFIG_HW_RANDOM_MESON is not set ++# CONFIG_HW_RANDOM_CAVIUM is not set ++# CONFIG_HW_RANDOM_MTK is not set ++# CONFIG_HW_RANDOM_S390 is not set ++# CONFIG_HW_RANDOM_EXYNOS is not set ++# CONFIG_HW_RANDOM_OPTEE is not set ++# CONFIG_HW_RANDOM_NPCM is not set ++# CONFIG_HW_RANDOM_KEYSTONE is not set ++# CONFIG_HW_RANDOM_CCTRNG is not set ++# CONFIG_HW_RANDOM_XIPHERA is not set ++# CONFIG_HW_RANDOM_ARM_SMCCC_TRNG is not set ++# CONFIG_HW_RANDOM_CN10K is not set ++# CONFIG_HW_RANDOM_JH7110 is not set ++CONFIG_HW_RANDOM_ROCKCHIP=m ++ ++## ++## file: drivers/clk/Kconfig ++## ++CONFIG_COMMON_CLK=y ++CONFIG_COMMON_CLK_RK808=y ++CONFIG_COMMON_CLK_SCMI=y ++ ++## ++## file: drivers/clk/rockchip/Kconfig ++## ++# CONFIG_CLK_PX30 is not set ++# CONFIG_CLK_RK3308 is not set ++# CONFIG_CLK_RK3328 is not set ++# CONFIG_CLK_RK3368 is not set ++# CONFIG_CLK_RK3399 is not set ++CONFIG_CLK_RK3568=y ++# CONFIG_CLK_RK3576 is not set ++# CONFIG_CLK_RK3588 is not set ++ ++## ++## file: drivers/cpufreq/Kconfig ++## ++CONFIG_CPU_FREQ=y ++CONFIG_CPU_FREQ_STAT=y ++CONFIG_CPU_FREQ_DEFAULT_GOV_SCHEDUTIL=y ++CONFIG_CPU_FREQ_GOV_PERFORMANCE=y ++CONFIG_CPU_FREQ_GOV_POWERSAVE=y ++CONFIG_CPU_FREQ_GOV_USERSPACE=y ++CONFIG_CPU_FREQ_GOV_ONDEMAND=y ++CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y ++CONFIG_CPU_FREQ_GOV_SCHEDUTIL=y ++CONFIG_CPUFREQ_DT=y ++CONFIG_CPUFREQ_DT_PLATDEV=y ++ ++## ++## file: drivers/cpufreq/Kconfig.arm ++## ++CONFIG_ARM_SCMI_CPUFREQ=m ++ ++## ++## file: drivers/cpuidle/Kconfig ++## ++CONFIG_CPU_IDLE=y ++CONFIG_CPU_IDLE_GOV_MENU=y ++CONFIG_CPU_IDLE_GOV_TEO=y ++ ++## ++## file: drivers/cpuidle/Kconfig.arm ++## ++CONFIG_ARM_CPUIDLE=y ++CONFIG_ARM_PSCI_CPUIDLE=y ++CONFIG_ARM_PSCI_CPUIDLE_DOMAIN=y ++ ++## ++## file: drivers/crypto/Kconfig ++## ++CONFIG_CRYPTO_HW=y ++CONFIG_CRYPTO_DEV_ROCKCHIP=m ++ ++## ++## file: drivers/devfreq/Kconfig ++## ++CONFIG_PM_DEVFREQ=y ++CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND=y ++CONFIG_DEVFREQ_GOV_USERSPACE=m ++CONFIG_DEVFREQ_GOV_PASSIVE=m ++ ++## ++## file: drivers/devfreq/event/Kconfig ++## ++CONFIG_PM_DEVFREQ_EVENT=y ++CONFIG_DEVFREQ_EVENT_ROCKCHIP_DFI=m ++ ++## ++## file: drivers/dma/Kconfig ++## ++CONFIG_DMADEVICES=y ++CONFIG_PL330_DMA=y ++ ++## ++## file: drivers/firmware/Kconfig ++## ++CONFIG_ARM_SDE_INTERFACE=y ++CONFIG_FIRMWARE_MEMMAP=y ++CONFIG_DMI_SYSFS=y ++CONFIG_ROCKCHIP_SIP=y ++ ++## ++## file: drivers/firmware/arm_scmi/Kconfig ++## ++CONFIG_ARM_SCMI_PROTOCOL=y ++CONFIG_ARM_SCMI_POWER_CONTROL=y ++ ++## ++## file: drivers/firmware/arm_scmi/transports/Kconfig ++## ++CONFIG_ARM_SCMI_TRANSPORT_MAILBOX=y ++CONFIG_ARM_SCMI_TRANSPORT_SMC=y ++ ++## ++## file: drivers/firmware/efi/Kconfig ++## ++CONFIG_EFI_BOOTLOADER_CONTROL=y ++ ++## ++## file: drivers/firmware/smccc/Kconfig ++## ++# CONFIG_ARM_SMCCC_SOC_ID is not set ++ ++## ++## file: drivers/gpio/Kconfig ++## ++CONFIG_GPIOLIB=y ++CONFIG_GPIO_SYSFS=y ++CONFIG_GPIO_ROCKCHIP=m ++ ++## ++## file: drivers/gpu/drm/Kconfig ++## ++CONFIG_DRM=m ++CONFIG_DRM_VGEM=m ++ ++## ++## file: drivers/gpu/drm/bridge/Kconfig ++## ++CONFIG_DRM_DISPLAY_CONNECTOR=m ++ ++## ++## file: drivers/gpu/drm/panel/Kconfig ++## ++CONFIG_DRM_PANEL_SIMPLE=m ++ ++## ++## file: drivers/gpu/drm/panfrost/Kconfig ++## ++CONFIG_DRM_PANFROST=m ++ ++## ++## file: drivers/gpu/drm/rockchip/Kconfig ++## ++CONFIG_DRM_ROCKCHIP=m ++CONFIG_ROCKCHIP_VOP2=y ++CONFIG_ROCKCHIP_DW_HDMI=y ++CONFIG_ROCKCHIP_DW_MIPI_DSI=y ++CONFIG_ROCKCHIP_INNO_HDMI=y ++CONFIG_ROCKCHIP_LVDS=y ++CONFIG_DRM_ROCKCHIP_EBC=m ++ ++## ++## file: drivers/hid/Kconfig ++## ++CONFIG_HID_BATTERY_STRENGTH=y ++CONFIG_HIDRAW=y ++CONFIG_UHID=m ++CONFIG_HID_MICROSOFT=y ++ ++## ++## file: drivers/hid/i2c-hid/Kconfig ++## ++CONFIG_I2C_HID_OF=m ++CONFIG_I2C_HID_OF_GOODIX=m ++ ++## ++## file: drivers/hid/usbhid/Kconfig ++## ++CONFIG_USB_HIDDEV=y ++ ++## ++## file: drivers/hwmon/Kconfig ++## ++CONFIG_SENSORS_ARM_SCMI=m ++CONFIG_SENSORS_GPIO_FAN=m ++ ++## ++## file: drivers/i2c/Kconfig ++## ++CONFIG_I2C=y ++CONFIG_I2C_CHARDEV=y ++# CONFIG_I2C_HELPER_AUTO is not set ++ ++## ++## file: drivers/i2c/algos/Kconfig ++## ++CONFIG_I2C_ALGOBIT=y ++ ++## ++## file: drivers/i2c/busses/Kconfig ++## ++CONFIG_I2C_RK3X=y ++ ++## ++## file: drivers/iio/Kconfig ++## ++CONFIG_IIO=m ++CONFIG_IIO_SW_TRIGGER=m ++ ++## ++## file: drivers/iio/accel/Kconfig ++## ++CONFIG_IIO_ST_ACCEL_3AXIS=m ++CONFIG_IIO_ST_ACCEL_I2C_3AXIS=m ++ ++## ++## file: drivers/iio/adc/Kconfig ++## ++CONFIG_ROCKCHIP_SARADC=m ++ ++## ++## file: drivers/iio/common/scmi_sensors/Kconfig ++## ++CONFIG_IIO_SCMI=m ++ ++## ++## file: drivers/input/Kconfig ++## ++CONFIG_INPUT_MOUSEDEV=m ++CONFIG_INPUT_EVDEV=y ++ ++## ++## file: drivers/input/keyboard/Kconfig ++## ++CONFIG_KEYBOARD_ADC=m ++# CONFIG_KEYBOARD_ATKBD is not set ++CONFIG_KEYBOARD_GPIO=y ++ ++## ++## file: drivers/input/misc/Kconfig ++## ++CONFIG_INPUT_MISC=y ++CONFIG_INPUT_UINPUT=m ++CONFIG_INPUT_RK805_PWRKEY=y ++CONFIG_INPUT_WS8100_PEN=m ++ ++## ++## file: drivers/input/mouse/Kconfig ++## ++# CONFIG_INPUT_MOUSE is not set ++ ++## ++## file: drivers/input/serio/Kconfig ++## ++# CONFIG_SERIO is not set ++ ++## ++## file: drivers/input/touchscreen/Kconfig ++## ++CONFIG_INPUT_TOUCHSCREEN=y ++CONFIG_TOUCHSCREEN_CYTTSP5=m ++ ++## ++## file: drivers/iommu/Kconfig ++## ++CONFIG_IOMMU_DEFAULT_PASSTHROUGH=y ++CONFIG_ROCKCHIP_IOMMU=y ++ ++## ++## file: drivers/leds/Kconfig ++## ++CONFIG_NEW_LEDS=y ++CONFIG_LEDS_CLASS=y ++CONFIG_LEDS_CLASS_FLASH=y ++CONFIG_LEDS_CLASS_MULTICOLOR=y ++CONFIG_LEDS_GPIO=y ++ ++## ++## file: drivers/leds/trigger/Kconfig ++## ++CONFIG_LEDS_TRIGGER_TIMER=y ++CONFIG_LEDS_TRIGGER_ONESHOT=y ++CONFIG_LEDS_TRIGGER_HEARTBEAT=y ++CONFIG_LEDS_TRIGGER_BACKLIGHT=y ++CONFIG_LEDS_TRIGGER_CPU=y ++CONFIG_LEDS_TRIGGER_ACTIVITY=y ++CONFIG_LEDS_TRIGGER_GPIO=y ++CONFIG_LEDS_TRIGGER_DEFAULT_ON=y ++CONFIG_LEDS_TRIGGER_TRANSIENT=y ++CONFIG_LEDS_TRIGGER_CAMERA=y ++CONFIG_LEDS_TRIGGER_PANIC=y ++CONFIG_LEDS_TRIGGER_NETDEV=y ++CONFIG_LEDS_TRIGGER_PATTERN=y ++CONFIG_LEDS_TRIGGER_TTY=y ++ ++## ++## file: drivers/mailbox/Kconfig ++## ++CONFIG_MAILBOX=y ++CONFIG_ROCKCHIP_MBOX=y ++ ++## ++## file: drivers/media/Kconfig ++## ++CONFIG_MEDIA_SUPPORT=m ++CONFIG_MEDIA_PLATFORM_SUPPORT=y ++CONFIG_VIDEO_DEV=m ++ ++## ++## file: drivers/media/cec/Kconfig ++## ++# CONFIG_MEDIA_CEC_SUPPORT is not set ++ ++## ++## file: drivers/media/dvb-core/Kconfig ++## ++# CONFIG_DVB_NET is not set ++# CONFIG_DVB_DYNAMIC_MINORS is not set ++ ++## ++## file: drivers/media/dvb-frontends/Kconfig ++## ++# CONFIG_DVB_MXL5XX is not set ++# CONFIG_DVB_STB0899 is not set ++# CONFIG_DVB_STB6100 is not set ++# CONFIG_DVB_STV090x is not set ++# CONFIG_DVB_STV0910 is not set ++# CONFIG_DVB_STV6110x is not set ++# CONFIG_DVB_STV6111 is not set ++# CONFIG_DVB_DRXK is not set ++# CONFIG_DVB_MN88472 is not set ++# CONFIG_DVB_MN88473 is not set ++# CONFIG_DVB_SI2165 is not set ++# CONFIG_DVB_TDA18271C2DD is not set ++# CONFIG_DVB_CX24110 is not set ++# CONFIG_DVB_CX24116 is not set ++# CONFIG_DVB_CX24117 is not set ++# CONFIG_DVB_CX24120 is not set ++# CONFIG_DVB_CX24123 is not set ++# CONFIG_DVB_DS3000 is not set ++# CONFIG_DVB_MB86A16 is not set ++# CONFIG_DVB_MT312 is not set ++# CONFIG_DVB_S5H1420 is not set ++# CONFIG_DVB_SI21XX is not set ++# CONFIG_DVB_STB6000 is not set ++# CONFIG_DVB_STV0288 is not set ++# CONFIG_DVB_STV0299 is not set ++# CONFIG_DVB_STV0900 is not set ++# CONFIG_DVB_STV6110 is not set ++# CONFIG_DVB_TDA10071 is not set ++# CONFIG_DVB_TDA10086 is not set ++# CONFIG_DVB_TDA8083 is not set ++# CONFIG_DVB_TDA8261 is not set ++# CONFIG_DVB_TDA826X is not set ++# CONFIG_DVB_TS2020 is not set ++# CONFIG_DVB_TUA6100 is not set ++# CONFIG_DVB_TUNER_CX24113 is not set ++# CONFIG_DVB_TUNER_ITD1000 is not set ++# CONFIG_DVB_VES1X93 is not set ++# CONFIG_DVB_ZL10036 is not set ++# CONFIG_DVB_ZL10039 is not set ++# CONFIG_DVB_CX22700 is not set ++# CONFIG_DVB_CX22702 is not set ++# CONFIG_DVB_CXD2820R is not set ++# CONFIG_DVB_CXD2841ER is not set ++# CONFIG_DVB_DIB3000MB is not set ++# CONFIG_DVB_DIB3000MC is not set ++# CONFIG_DVB_DIB7000M is not set ++# CONFIG_DVB_DIB7000P is not set ++# CONFIG_DVB_DIB9000 is not set ++# CONFIG_DVB_DRXD is not set ++# CONFIG_DVB_EC100 is not set ++# CONFIG_DVB_L64781 is not set ++# CONFIG_DVB_MT352 is not set ++# CONFIG_DVB_NXT6000 is not set ++# CONFIG_DVB_S5H1432 is not set ++# CONFIG_DVB_SP887X is not set ++# CONFIG_DVB_STV0367 is not set ++# CONFIG_DVB_TDA10048 is not set ++# CONFIG_DVB_TDA1004X is not set ++# CONFIG_DVB_ZD1301_DEMOD is not set ++# CONFIG_DVB_ZL10353 is not set ++# CONFIG_DVB_STV0297 is not set ++# CONFIG_DVB_TDA10021 is not set ++# CONFIG_DVB_TDA10023 is not set ++# CONFIG_DVB_VES1820 is not set ++# CONFIG_DVB_AU8522_DTV is not set ++# CONFIG_DVB_AU8522_V4L is not set ++# CONFIG_DVB_BCM3510 is not set ++# CONFIG_DVB_LG2160 is not set ++# CONFIG_DVB_LGDT3305 is not set ++# CONFIG_DVB_LGDT330X is not set ++# CONFIG_DVB_MXL692 is not set ++# CONFIG_DVB_NXT200X is not set ++# CONFIG_DVB_OR51132 is not set ++# CONFIG_DVB_OR51211 is not set ++# CONFIG_DVB_S5H1409 is not set ++# CONFIG_DVB_S5H1411 is not set ++# CONFIG_DVB_DIB8000 is not set ++# CONFIG_DVB_MB86A20S is not set ++# CONFIG_DVB_S921 is not set ++# CONFIG_DVB_MN88443X is not set ++# CONFIG_DVB_TC90522 is not set ++# CONFIG_DVB_PLL is not set ++# CONFIG_DVB_TUNER_DIB0070 is not set ++# CONFIG_DVB_TUNER_DIB0090 is not set ++# CONFIG_DVB_A8293 is not set ++# CONFIG_DVB_AF9033 is not set ++# CONFIG_DVB_ASCOT2E is not set ++# CONFIG_DVB_ATBM8830 is not set ++# CONFIG_DVB_HELENE is not set ++# CONFIG_DVB_HORUS3A is not set ++# CONFIG_DVB_ISL6405 is not set ++# CONFIG_DVB_ISL6421 is not set ++# CONFIG_DVB_ISL6423 is not set ++# CONFIG_DVB_IX2505V is not set ++# CONFIG_DVB_LGS8GL5 is not set ++# CONFIG_DVB_LGS8GXX is not set ++# CONFIG_DVB_LNBH25 is not set ++# CONFIG_DVB_LNBH29 is not set ++# CONFIG_DVB_LNBP21 is not set ++# CONFIG_DVB_LNBP22 is not set ++# CONFIG_DVB_M88RS2000 is not set ++# CONFIG_DVB_TDA665x is not set ++# CONFIG_DVB_CXD2099 is not set ++# CONFIG_DVB_SP2 is not set ++ ++## ++## file: drivers/media/dvb-frontends/cxd2880/Kconfig ++## ++# CONFIG_DVB_CXD2880 is not set ++ ++## ++## file: drivers/media/dvb-frontends/drx39xyj/Kconfig ++## ++# CONFIG_DVB_DRX39XYJ is not set ++ ++## ++## file: drivers/media/platform/Kconfig ++## ++CONFIG_V4L_MEM2MEM_DRIVERS=y ++ ++## ++## file: drivers/media/platform/rockchip/rga/Kconfig ++## ++CONFIG_VIDEO_ROCKCHIP_RGA=m ++ ++## ++## file: drivers/media/platform/verisilicon/Kconfig ++## ++CONFIG_VIDEO_HANTRO=m ++CONFIG_VIDEO_HANTRO_ROCKCHIP=y ++ ++## ++## file: drivers/media/radio/Kconfig ++## ++# CONFIG_RADIO_ADAPTERS is not set ++ ++## ++## file: drivers/media/spi/Kconfig ++## ++# CONFIG_CXD2880_SPI_DRV is not set ++ ++## ++## file: drivers/media/tuners/Kconfig ++## ++# CONFIG_MEDIA_TUNER_E4000 is not set ++# CONFIG_MEDIA_TUNER_FC0011 is not set ++# CONFIG_MEDIA_TUNER_FC0012 is not set ++# CONFIG_MEDIA_TUNER_FC0013 is not set ++# CONFIG_MEDIA_TUNER_FC2580 is not set ++# CONFIG_MEDIA_TUNER_IT913X is not set ++# CONFIG_MEDIA_TUNER_M88RS6000T is not set ++# CONFIG_MEDIA_TUNER_MAX2165 is not set ++# CONFIG_MEDIA_TUNER_MC44S803 is not set ++# CONFIG_MEDIA_TUNER_MSI001 is not set ++# CONFIG_MEDIA_TUNER_MT2060 is not set ++# CONFIG_MEDIA_TUNER_MT2063 is not set ++# CONFIG_MEDIA_TUNER_MT20XX is not set ++# CONFIG_MEDIA_TUNER_MT2131 is not set ++# CONFIG_MEDIA_TUNER_MT2266 is not set ++# CONFIG_MEDIA_TUNER_MXL301RF is not set ++# CONFIG_MEDIA_TUNER_MXL5005S is not set ++# CONFIG_MEDIA_TUNER_MXL5007T is not set ++# CONFIG_MEDIA_TUNER_QM1D1B0004 is not set ++# CONFIG_MEDIA_TUNER_QM1D1C0042 is not set ++# CONFIG_MEDIA_TUNER_QT1010 is not set ++# CONFIG_MEDIA_TUNER_R820T is not set ++# CONFIG_MEDIA_TUNER_SI2157 is not set ++# CONFIG_MEDIA_TUNER_SIMPLE is not set ++# CONFIG_MEDIA_TUNER_TDA18212 is not set ++# CONFIG_MEDIA_TUNER_TDA18218 is not set ++# CONFIG_MEDIA_TUNER_TDA18250 is not set ++# CONFIG_MEDIA_TUNER_TDA18271 is not set ++# CONFIG_MEDIA_TUNER_TDA827X is not set ++# CONFIG_MEDIA_TUNER_TDA8290 is not set ++# CONFIG_MEDIA_TUNER_TDA9887 is not set ++# CONFIG_MEDIA_TUNER_TEA5761 is not set ++# CONFIG_MEDIA_TUNER_TEA5767 is not set ++# CONFIG_MEDIA_TUNER_TUA9001 is not set ++# CONFIG_MEDIA_TUNER_XC2028 is not set ++# CONFIG_MEDIA_TUNER_XC4000 is not set ++# CONFIG_MEDIA_TUNER_XC5000 is not set ++ ++## ++## file: drivers/media/usb/uvc/Kconfig ++## ++CONFIG_USB_VIDEO_CLASS=m ++CONFIG_USB_VIDEO_CLASS_INPUT_EVDEV=y ++ ++## ++## file: drivers/mfd/Kconfig ++## ++CONFIG_MFD_RK8XX_I2C=y ++ ++## ++## file: drivers/misc/Kconfig ++## ++CONFIG_SRAM=y ++ ++## ++## file: drivers/mmc/Kconfig ++## ++CONFIG_MMC=y ++ ++## ++## file: drivers/mmc/core/Kconfig ++## ++CONFIG_PWRSEQ_SIMPLE=y ++CONFIG_MMC_BLOCK_MINORS=32 ++ ++## ++## file: drivers/mmc/host/Kconfig ++## ++CONFIG_MMC_SDHCI=y ++CONFIG_MMC_SDHCI_PLTFM=y ++CONFIG_MMC_SDHCI_OF_DWCMSHC=y ++CONFIG_MMC_DW=y ++CONFIG_MMC_DW_ROCKCHIP=y ++ ++## ++## file: drivers/mtd/spi-nor/Kconfig ++## ++CONFIG_MTD_SPI_NOR=m ++ ++## ++## file: drivers/net/Kconfig ++## ++CONFIG_NETDEVICES=y ++CONFIG_DUMMY=m ++CONFIG_WIREGUARD=m ++CONFIG_MACVLAN=m ++CONFIG_IPVLAN=m ++CONFIG_VXLAN=m ++CONFIG_TUN=m ++CONFIG_VETH=m ++ ++## ++## file: drivers/net/ethernet/Kconfig ++## ++CONFIG_ETHERNET=y ++ ++## ++## file: drivers/net/ethernet/stmicro/stmmac/Kconfig ++## ++CONFIG_STMMAC_ETH=m ++CONFIG_STMMAC_PLATFORM=m ++CONFIG_DWMAC_ROCKCHIP=m ++ ++## ++## file: drivers/net/phy/Kconfig ++## ++CONFIG_MOTORCOMM_PHY=m ++ ++## ++## file: drivers/net/usb/Kconfig ++## ++CONFIG_USB_NET_DRIVERS=m ++CONFIG_USB_USBNET=m ++# CONFIG_USB_NET_AX8817X is not set ++# CONFIG_USB_NET_AX88179_178A is not set ++CONFIG_USB_NET_CDC_EEM=m ++# CONFIG_USB_NET_NET1080 is not set ++# CONFIG_USB_NET_CDC_SUBSET is not set ++# CONFIG_USB_NET_ZAURUS is not set ++ ++## ++## file: drivers/net/wireless/admtek/Kconfig ++## ++# CONFIG_WLAN_VENDOR_ADMTEK is not set ++ ++## ++## file: drivers/net/wireless/ath/Kconfig ++## ++# CONFIG_WLAN_VENDOR_ATH is not set ++ ++## ++## file: drivers/net/wireless/atmel/Kconfig ++## ++# CONFIG_WLAN_VENDOR_ATMEL is not set ++ ++## ++## file: drivers/net/wireless/broadcom/brcm80211/brcmfmac/Kconfig ++## ++CONFIG_BRCMFMAC=m ++ ++## ++## file: drivers/net/wireless/intel/Kconfig ++## ++# CONFIG_WLAN_VENDOR_INTEL is not set ++ ++## ++## file: drivers/net/wireless/intersil/Kconfig ++## ++# CONFIG_WLAN_VENDOR_INTERSIL is not set ++ ++## ++## file: drivers/net/wireless/marvell/Kconfig ++## ++# CONFIG_WLAN_VENDOR_MARVELL is not set ++ ++## ++## file: drivers/net/wireless/mediatek/Kconfig ++## ++# CONFIG_WLAN_VENDOR_MEDIATEK is not set ++ ++## ++## file: drivers/net/wireless/microchip/Kconfig ++## ++# CONFIG_WLAN_VENDOR_MICROCHIP is not set ++ ++## ++## file: drivers/net/wireless/quantenna/Kconfig ++## ++# CONFIG_WLAN_VENDOR_QUANTENNA is not set ++ ++## ++## file: drivers/net/wireless/ralink/Kconfig ++## ++# CONFIG_WLAN_VENDOR_RALINK is not set ++ ++## ++## file: drivers/net/wireless/realtek/Kconfig ++## ++# CONFIG_WLAN_VENDOR_REALTEK is not set ++ ++## ++## file: drivers/net/wireless/rsi/Kconfig ++## ++# CONFIG_WLAN_VENDOR_RSI is not set ++ ++## ++## file: drivers/net/wireless/st/Kconfig ++## ++# CONFIG_WLAN_VENDOR_ST is not set ++ ++## ++## file: drivers/net/wireless/ti/Kconfig ++## ++# CONFIG_WLAN_VENDOR_TI is not set ++ ++## ++## file: drivers/net/wireless/zydas/Kconfig ++## ++# CONFIG_WLAN_VENDOR_ZYDAS is not set ++ ++## ++## file: drivers/nvmem/Kconfig ++## ++CONFIG_NVMEM_ROCKCHIP_EFUSE=y ++CONFIG_NVMEM_ROCKCHIP_OTP=y ++ ++## ++## file: drivers/pci/Kconfig ++## ++CONFIG_PCI=y ++ ++## ++## file: drivers/pci/controller/dwc/Kconfig ++## ++CONFIG_PCIE_ROCKCHIP_DW_HOST=y ++CONFIG_PCIE_ROCKCHIP_DW_EP=y ++ ++## ++## file: drivers/pci/endpoint/Kconfig ++## ++CONFIG_PCI_ENDPOINT=y ++ ++## ++## file: drivers/perf/Kconfig ++## ++CONFIG_ARM_PMUV3=y ++ ++## ++## file: drivers/pinctrl/Kconfig ++## ++CONFIG_PINCTRL_ROCKCHIP=y ++CONFIG_PINCTRL_SCMI=y ++ ++## ++## file: drivers/phy/rockchip/Kconfig ++## ++CONFIG_PHY_ROCKCHIP_INNO_HDMI=m ++CONFIG_PHY_ROCKCHIP_INNO_USB2=m ++CONFIG_PHY_ROCKCHIP_INNO_CSIDPHY=m ++CONFIG_PHY_ROCKCHIP_INNO_DSIDPHY=m ++CONFIG_PHY_ROCKCHIP_NANENG_COMBO_PHY=m ++CONFIG_PHY_ROCKCHIP_PCIE=m ++ ++## ++## file: drivers/pmdomain/arm/Kconfig ++## ++CONFIG_ARM_SCMI_PERF_DOMAIN=y ++CONFIG_ARM_SCMI_POWER_DOMAIN=y ++ ++## ++## file: drivers/pmdomain/rockchip/Kconfig ++## ++CONFIG_ROCKCHIP_PM_DOMAINS=y ++ ++## ++## file: drivers/power/supply/Kconfig ++## ++CONFIG_CHARGER_RK817=y ++ ++## ++## file: drivers/pwm/Kconfig ++## ++CONFIG_PWM=y ++CONFIG_PWM_ROCKCHIP=y ++ ++## ++## file: drivers/regulator/Kconfig ++## ++CONFIG_REGULATOR=y ++CONFIG_REGULATOR_FIXED_VOLTAGE=y ++CONFIG_REGULATOR_ARM_SCMI=m ++CONFIG_REGULATOR_FAN53555=y ++CONFIG_REGULATOR_RK808=y ++CONFIG_REGULATOR_TPS65185=m ++ ++## ++## file: drivers/reset/Kconfig ++## ++CONFIG_RESET_SCMI=y ++ ++## ++## file: drivers/soc/rockchip/Kconfig ++## ++CONFIG_ROCKCHIP_GRF=y ++CONFIG_ROCKCHIP_IODOMAIN=y ++CONFIG_ROCKCHIP_SUSPEND_MODE=y ++ ++## ++## file: drivers/spi/Kconfig ++## ++CONFIG_SPI=y ++CONFIG_SPI_BITBANG=m ++CONFIG_SPI_GPIO=m ++CONFIG_SPI_ROCKCHIP=m ++CONFIG_SPI_ROCKCHIP_SFC=m ++CONFIG_SPI_SPIDEV=y ++ ++## ++## file: drivers/thermal/Kconfig ++## ++CONFIG_THERMAL=y ++CONFIG_THERMAL_NETLINK=y ++CONFIG_THERMAL_STATISTICS=y ++CONFIG_THERMAL_GOV_FAIR_SHARE=y ++CONFIG_THERMAL_GOV_BANG_BANG=y ++CONFIG_THERMAL_GOV_USER_SPACE=y ++CONFIG_THERMAL_GOV_POWER_ALLOCATOR=y ++CONFIG_CPU_THERMAL=y ++CONFIG_THERMAL_EMULATION=y ++CONFIG_ROCKCHIP_THERMAL=y ++ ++## ++## file: drivers/tty/serdev/Kconfig ++## ++CONFIG_SERIAL_DEV_BUS=y ++ ++## ++## file: drivers/tty/serial/Kconfig ++## ++CONFIG_SERIAL_8250=y ++# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set ++CONFIG_SERIAL_8250_CONSOLE=y ++CONFIG_SERIAL_8250_DW=y ++CONFIG_SERIAL_OF_PLATFORM=y ++ ++## ++## file: drivers/usb/Kconfig ++## ++CONFIG_USB=y ++ ++## ++## file: drivers/usb/common/Kconfig ++## ++CONFIG_USB_LED_TRIG=y ++ ++## ++## file: drivers/usb/core/Kconfig ++## ++CONFIG_USB_ANNOUNCE_NEW_DEVICES=y ++CONFIG_USB_DYNAMIC_MINORS=y ++CONFIG_USB_OTG=y ++CONFIG_USB_OTG_FSM=m ++CONFIG_USB_LEDS_TRIGGER_USBPORT=y ++ ++## ++## file: drivers/usb/dwc3/Kconfig ++## ++CONFIG_USB_DWC3=y ++ ++## ++## file: drivers/usb/gadget/Kconfig ++## ++CONFIG_USB_GADGET=y ++CONFIG_USB_CONFIGFS=m ++CONFIG_USB_CONFIGFS_F_GUD=y ++CONFIG_USB_CONFIGFS_SERIAL=y ++CONFIG_USB_CONFIGFS_ACM=y ++CONFIG_USB_CONFIGFS_OBEX=y ++CONFIG_USB_CONFIGFS_NCM=y ++CONFIG_USB_CONFIGFS_ECM=y ++CONFIG_USB_CONFIGFS_ECM_SUBSET=y ++CONFIG_USB_CONFIGFS_EEM=y ++CONFIG_USB_CONFIGFS_MASS_STORAGE=y ++CONFIG_USB_CONFIGFS_F_FS=y ++CONFIG_USB_CONFIGFS_F_UAC1=y ++CONFIG_USB_CONFIGFS_F_UAC2=y ++CONFIG_USB_CONFIGFS_F_MIDI=y ++CONFIG_USB_CONFIGFS_F_MIDI2=y ++CONFIG_USB_CONFIGFS_F_HID=y ++CONFIG_USB_CONFIGFS_F_UVC=y ++ ++## ++## file: drivers/usb/gadget/legacy/Kconfig ++## ++CONFIG_USB_GADGETFS=y ++CONFIG_USB_FUNCTIONFS=m ++CONFIG_USB_FUNCTIONFS_ETH=y ++CONFIG_USB_FUNCTIONFS_GENERIC=y ++CONFIG_USB_CDC_COMPOSITE=m ++CONFIG_USB_G_MULTI=m ++# CONFIG_USB_G_MULTI_RNDIS is not set ++ ++## ++## file: drivers/usb/host/Kconfig ++## ++CONFIG_USB_XHCI_HCD=y ++CONFIG_USB_EHCI_HCD=y ++CONFIG_USB_EHCI_ROOT_HUB_TT=y ++CONFIG_USB_EHCI_HCD_PLATFORM=y ++CONFIG_USB_OHCI_HCD=y ++CONFIG_USB_OHCI_HCD_PLATFORM=y ++ ++## ++## file: drivers/usb/phy/Kconfig ++## ++CONFIG_USB_PHY=y ++ ++## ++## file: drivers/usb/roles/Kconfig ++## ++CONFIG_USB_ROLE_SWITCH=m ++ ++## ++## file: drivers/usb/serial/Kconfig ++## ++CONFIG_USB_SERIAL=y ++CONFIG_USB_SERIAL_GENERIC=y ++CONFIG_USB_SERIAL_CP210X=m ++ ++## ++## file: drivers/usb/storage/Kconfig ++## ++CONFIG_USB_STORAGE=m ++CONFIG_USB_UAS=m ++ ++## ++## file: drivers/usb/typec/Kconfig ++## ++CONFIG_TYPEC=y ++CONFIG_TYPEC_WUSB3801=m ++ ++## ++## file: drivers/video/backlight/Kconfig ++## ++CONFIG_BACKLIGHT_CLASS_DEVICE=y ++CONFIG_BACKLIGHT_LM3630A=y ++ ++## ++## file: drivers/watchdog/Kconfig ++## ++CONFIG_WATCHDOG=y ++CONFIG_WATCHDOG_SYSFS=y ++CONFIG_DW_WATCHDOG=y ++ ++## ++## file: fs/Kconfig ++## ++CONFIG_FILE_LOCKING=y ++CONFIG_NETWORK_FILESYSTEMS=y ++ ++## ++## file: fs/Kconfig.binfmt ++## ++CONFIG_COREDUMP=y ++ ++## ++## file: fs/cachefiles/Kconfig ++## ++CONFIG_CACHEFILES=m ++ ++## ++## file: fs/netfs/Kconfig ++## ++CONFIG_FSCACHE=y ++CONFIG_FSCACHE_STATS=y ++ ++## ++## file: fs/nfs/Kconfig ++## ++CONFIG_NFS_FS=m ++CONFIG_NFS_V2=m ++CONFIG_NFS_V3=m ++CONFIG_NFS_V3_ACL=y ++CONFIG_NFS_V4=m ++CONFIG_NFS_V4_1=y ++CONFIG_NFS_V4_2=y ++CONFIG_NFS_V4_1_IMPLEMENTATION_ID_DOMAIN="kernel.org" ++# CONFIG_NFS_V4_1_MIGRATION is not set ++CONFIG_NFS_FSCACHE=y ++# CONFIG_NFS_USE_LEGACY_DNS is not set ++ ++## ++## file: fs/proc/Kconfig ++## ++CONFIG_PROC_FS=y ++CONFIG_PROC_SYSCTL=y ++CONFIG_PROC_PAGE_MONITOR=y ++ ++## ++## file: fs/smb/client/Kconfig ++## ++CONFIG_CIFS=m ++CONFIG_CIFS_UPCALL=y ++CONFIG_CIFS_XATTR=y ++CONFIG_CIFS_FSCACHE=y ++ ++## ++## file: init/Kconfig ++## ++CONFIG_MULTIUSER=y ++ ++## ++## file: kernel/power/Kconfig ++## ++CONFIG_PM_AUTOSLEEP=y ++CONFIG_PM_WAKELOCKS=y ++CONFIG_PM_DEBUG=y ++CONFIG_PM_ADVANCED_DEBUG=y ++CONFIG_PM_TEST_SUSPEND=y ++CONFIG_WQ_POWER_EFFICIENT_DEFAULT=y ++CONFIG_ENERGY_MODEL=y ++ ++## ++## file: mm/Kconfig ++## ++CONFIG_SWAP=y ++ ++## ++## file: net/Kconfig ++## ++CONFIG_NET=y ++CONFIG_INET=y ++CONFIG_NETFILTER=y ++CONFIG_BRIDGE_NETFILTER=m ++CONFIG_CGROUP_NET_PRIO=y ++# CONFIG_ETHTOOL_NETLINK is not set ++ ++## ++## file: net/8021q/Kconfig ++## ++CONFIG_VLAN_8021Q=m ++ ++## ++## file: net/bluetooth/Kconfig ++## ++CONFIG_BT=m ++CONFIG_BT_LEDS=y ++ ++## ++## file: net/bluetooth/bnep/Kconfig ++## ++CONFIG_BT_BNEP=m ++CONFIG_BT_BNEP_MC_FILTER=y ++CONFIG_BT_BNEP_PROTO_FILTER=y ++ ++## ++## file: net/bluetooth/hidp/Kconfig ++## ++CONFIG_BT_HIDP=m ++ ++## ++## file: net/bluetooth/rfcomm/Kconfig ++## ++CONFIG_BT_RFCOMM=y ++CONFIG_BT_RFCOMM_TTY=y ++ ++## ++## file: net/bridge/Kconfig ++## ++CONFIG_BRIDGE=m ++CONFIG_BRIDGE_VLAN_FILTERING=y ++ ++## ++## file: net/ipv4/Kconfig ++## ++CONFIG_IP_ADVANCED_ROUTER=y ++CONFIG_IP_MULTIPLE_TABLES=y ++CONFIG_SYN_COOKIES=y ++CONFIG_INET_AH=m ++CONFIG_INET_ESP=m ++CONFIG_INET_IPCOMP=m ++CONFIG_INET_DIAG=m ++ ++## ++## file: net/ipv4/netfilter/Kconfig ++## ++CONFIG_IP_NF_IPTABLES=m ++CONFIG_IP_NF_FILTER=m ++CONFIG_IP_NF_NAT=m ++CONFIG_IP_NF_TARGET_MASQUERADE=m ++CONFIG_IP_NF_TARGET_REDIRECT=m ++CONFIG_IP_NF_MANGLE=m ++ ++## ++## file: net/ipv6/Kconfig ++## ++CONFIG_IPV6=y ++CONFIG_INET6_AH=m ++CONFIG_INET6_ESP=m ++CONFIG_INET6_IPCOMP=m ++CONFIG_IPV6_SIT=m ++CONFIG_IPV6_MULTIPLE_TABLES=y ++ ++## ++## file: net/ipv6/netfilter/Kconfig ++## ++CONFIG_IP6_NF_IPTABLES=m ++ ++## ++## file: net/mac80211/Kconfig ++## ++CONFIG_MAC80211=m ++# CONFIG_MAC80211_RC_MINSTREL is not set ++CONFIG_MAC80211_LEDS=y ++CONFIG_MAC80211_DEBUGFS=y ++ ++## ++## file: net/netfilter/Kconfig ++## ++CONFIG_NF_CONNTRACK=m ++CONFIG_NF_TABLES=m ++CONFIG_NFT_CT=m ++CONFIG_NFT_CONNLIMIT=m ++CONFIG_NFT_MASQ=m ++CONFIG_NFT_REDIR=m ++CONFIG_NFT_REJECT=m ++CONFIG_NFT_COMPAT=m ++CONFIG_NFT_HASH=m ++CONFIG_NETFILTER_XTABLES=m ++CONFIG_NETFILTER_XT_TARGET_CHECKSUM=m ++CONFIG_NETFILTER_XT_TARGET_MARK=m ++CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=m ++CONFIG_NETFILTER_XT_MATCH_CONNTRACK=m ++CONFIG_NETFILTER_XT_MATCH_IPVS=m ++CONFIG_NETFILTER_XT_MATCH_POLICY=m ++ ++## ++## file: net/netfilter/ipvs/Kconfig ++## ++CONFIG_IP_VS=m ++CONFIG_IP_VS_PROTO_TCP=y ++CONFIG_IP_VS_PROTO_UDP=y ++CONFIG_IP_VS_RR=m ++CONFIG_IP_VS_NFCT=y ++ ++## ++## file: net/netlink/Kconfig ++## ++CONFIG_NETLINK_DIAG=y ++ ++## ++## file: net/packet/Kconfig ++## ++CONFIG_PACKET=y ++ ++## ++## file: net/rfkill/Kconfig ++## ++CONFIG_RFKILL=m ++CONFIG_RFKILL_INPUT=y ++ ++## ++## file: net/sched/Kconfig ++## ++CONFIG_NET_SCHED=y ++CONFIG_NET_CLS_CGROUP=m ++ ++## ++## file: net/unix/Kconfig ++## ++CONFIG_UNIX=y ++ ++## ++## file: net/wireless/Kconfig ++## ++CONFIG_CFG80211=m ++# CONFIG_CFG80211_CERTIFICATION_ONUS is not set ++CONFIG_CFG80211_REQUIRE_SIGNED_REGDB=y ++CONFIG_CFG80211_USE_KERNEL_REGDB_KEYS=y ++CONFIG_CFG80211_DEFAULT_PS=y ++CONFIG_CFG80211_DEBUGFS=y ++# CONFIG_CFG80211_CRDA_SUPPORT is not set ++ ++## ++## file: net/xfrm/Kconfig ++## ++CONFIG_XFRM_USER=m ++ ++## ++## file: security/Kconfig ++## ++CONFIG_LSM="landlock" ++CONFIG_SECURITY=y ++ ++## ++## file: security/landlock/Kconfig ++## ++CONFIG_SECURITY_LANDLOCK=y ++ ++## ++## file: sound/Kconfig ++## ++CONFIG_SOUND=m ++CONFIG_SND=m ++ ++## ++## file: sound/core/Kconfig ++## ++CONFIG_SND_HRTIMER=m ++CONFIG_SND_DYNAMIC_MINORS=y ++# CONFIG_SND_SUPPORT_OLD_API is not set ++CONFIG_SND_DEBUG=y ++CONFIG_SND_DEBUG_VERBOSE=y ++ ++## ++## file: sound/drivers/Kconfig ++## ++# CONFIG_SND_DRIVERS is not set ++ ++## ++## file: sound/soc/Kconfig ++## ++CONFIG_SND_SOC=m ++ ++## ++## file: sound/soc/codecs/Kconfig ++## ++CONFIG_SND_SOC_BT_SCO=m ++CONFIG_SND_SOC_DMIC=m ++CONFIG_SND_SOC_RK817=m ++CONFIG_SND_SOC_SIMPLE_AMPLIFIER=m ++CONFIG_SND_SOC_SPDIF=m ++ ++## ++## file: sound/soc/generic/Kconfig ++## ++CONFIG_SND_SIMPLE_CARD=m ++ ++## ++## file: sound/soc/rockchip/Kconfig ++## ++CONFIG_SND_SOC_ROCKCHIP=m ++CONFIG_SND_SOC_ROCKCHIP_I2S_TDM=m ++CONFIG_SND_SOC_ROCKCHIP_PDM=m ++CONFIG_SND_SOC_ROCKCHIP_SPDIF=m ++ ++## ++## file: sound/spi/Kconfig ++## ++# CONFIG_SND_SPI is not set ++ ++## ++## file: sound/usb/Kconfig ++## ++CONFIG_SND_USB=y ++CONFIG_SND_USB_AUDIO=m ++CONFIG_SND_USB_AUDIO_MIDI_V2=y ++CONFIG_SND_USB_UA101=m ++CONFIG_SND_USB_CAIAQ=m ++CONFIG_SND_USB_CAIAQ_INPUT=y ++CONFIG_SND_USB_6FIRE=m ++CONFIG_SND_USB_HIFACE=m ++ ++## ++## file: sound/usb/line6/Kconfig ++## ++CONFIG_SND_USB_POD=m ++CONFIG_SND_USB_PODHD=m ++CONFIG_SND_USB_TONEPORT=m ++CONFIG_SND_USB_VARIAX=m +diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig +index bbd2155d8..e062e6a03 100644 +--- a/drivers/firmware/Kconfig ++++ b/drivers/firmware/Kconfig +@@ -178,6 +178,13 @@ config MTK_ADSP_IPC + ADSP exists on some mtk processors. + Client might use shared memory to exchange information with ADSP. + ++config ROCKCHIP_SIP ++ tristate "Rockchip SIP interface" ++ depends on HAVE_ARM_SMCCC && ARCH_ROCKCHIP ++ help ++ Say Y here if you want to enable SIP callbacks for Rockchip platforms ++ This option enables support for communicating with the ATF. ++ + config SYSFB + bool + select BOOT_VESA_SUPPORT +diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile +index 4ddec2820..32850a0d1 100644 +--- a/drivers/firmware/Makefile ++++ b/drivers/firmware/Makefile +@@ -16,6 +16,7 @@ obj-$(CONFIG_FIRMWARE_MEMMAP) += memmap.o + obj-$(CONFIG_MTK_ADSP_IPC) += mtk-adsp-ipc.o + obj-$(CONFIG_RASPBERRYPI_FIRMWARE) += raspberrypi.o + obj-$(CONFIG_FW_CFG_SYSFS) += qemu_fw_cfg.o ++obj-$(CONFIG_ROCKCHIP_SIP) += rockchip_sip.o + obj-$(CONFIG_SYSFB) += sysfb.o + obj-$(CONFIG_SYSFB_SIMPLEFB) += sysfb_simplefb.o + obj-$(CONFIG_TH1520_AON_PROTOCOL) += thead,th1520-aon.o +diff --git a/drivers/firmware/rockchip_sip.c b/drivers/firmware/rockchip_sip.c +new file mode 100644 +index 000000000..7fe034df9 +--- /dev/null ++++ b/drivers/firmware/rockchip_sip.c +@@ -0,0 +1,547 @@ ++/* ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 as ++ * published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * Copyright (C) 2016, Fuzhou Rockchip Electronics Co., Ltd ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#ifdef CONFIG_ARM ++#include ++#endif ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#ifdef CONFIG_64BIT ++#define PSCI_FN_NATIVE(version, name) PSCI_##version##_FN64_##name ++#else ++#define PSCI_FN_NATIVE(version, name) PSCI_##version##_FN_##name ++#endif ++ ++#define SIZE_PAGE(n) ((n) << 12) ++ ++static struct arm_smccc_res __invoke_sip_fn_smc(unsigned long function_id, ++ unsigned long arg0, ++ unsigned long arg1, ++ unsigned long arg2) ++{ ++ struct arm_smccc_res res; ++ ++ arm_smccc_smc(function_id, arg0, arg1, arg2, 0, 0, 0, 0, &res); ++ return res; ++} ++ ++struct arm_smccc_res sip_smc_dram(u32 arg0, u32 arg1, u32 arg2) ++{ ++ return __invoke_sip_fn_smc(SIP_DRAM_CONFIG, arg0, arg1, arg2); ++} ++EXPORT_SYMBOL_GPL(sip_smc_dram); ++ ++struct arm_smccc_res sip_smc_get_atf_version(void) ++{ ++ return __invoke_sip_fn_smc(SIP_ATF_VERSION, 0, 0, 0); ++} ++EXPORT_SYMBOL_GPL(sip_smc_get_atf_version); ++ ++struct arm_smccc_res sip_smc_get_sip_version(void) ++{ ++ return __invoke_sip_fn_smc(SIP_SIP_VERSION, 0, 0, 0); ++} ++EXPORT_SYMBOL_GPL(sip_smc_get_sip_version); ++ ++int sip_smc_set_suspend_mode(u32 ctrl, u32 config1, u32 config2) ++{ ++ struct arm_smccc_res res; ++ ++ res = __invoke_sip_fn_smc(SIP_SUSPEND_MODE, ctrl, config1, config2); ++ return res.a0; ++} ++EXPORT_SYMBOL_GPL(sip_smc_set_suspend_mode); ++ ++struct arm_smccc_res sip_smc_get_suspend_info(u32 info) ++{ ++ struct arm_smccc_res res; ++ ++ res = __invoke_sip_fn_smc(SIP_SUSPEND_MODE, info, 0, 0); ++ return res; ++} ++EXPORT_SYMBOL_GPL(sip_smc_get_suspend_info); ++ ++int sip_smc_virtual_poweroff(void) ++{ ++ struct arm_smccc_res res; ++ ++ res = __invoke_sip_fn_smc(PSCI_FN_NATIVE(1_0, SYSTEM_SUSPEND), 0, 0, 0); ++ return res.a0; ++} ++EXPORT_SYMBOL_GPL(sip_smc_virtual_poweroff); ++ ++int sip_smc_remotectl_config(u32 func, u32 data) ++{ ++ struct arm_smccc_res res; ++ ++ res = __invoke_sip_fn_smc(SIP_REMOTECTL_CFG, func, data, 0); ++ ++ return res.a0; ++} ++EXPORT_SYMBOL_GPL(sip_smc_remotectl_config); ++ ++u32 sip_smc_secure_reg_read(u32 addr_phy) ++{ ++ struct arm_smccc_res res; ++ ++ res = __invoke_sip_fn_smc(SIP_ACCESS_REG, 0, addr_phy, SECURE_REG_RD); ++ if (res.a0) ++ pr_err("%s error: %d, addr phy: 0x%x\n", ++ __func__, (int)res.a0, addr_phy); ++ ++ return res.a1; ++} ++EXPORT_SYMBOL_GPL(sip_smc_secure_reg_read); ++ ++int sip_smc_secure_reg_write(u32 addr_phy, u32 val) ++{ ++ struct arm_smccc_res res; ++ ++ res = __invoke_sip_fn_smc(SIP_ACCESS_REG, val, addr_phy, SECURE_REG_WR); ++ if (res.a0) ++ pr_err("%s error: %d, addr phy: 0x%x\n", ++ __func__, (int)res.a0, addr_phy); ++ ++ return res.a0; ++} ++EXPORT_SYMBOL_GPL(sip_smc_secure_reg_write); ++ ++static void *sip_map(phys_addr_t start, size_t size) ++{ ++ struct page **pages; ++ phys_addr_t page_start; ++ unsigned int page_count; ++ pgprot_t prot; ++ unsigned int i; ++ void *vaddr; ++ ++ if (!pfn_valid(__phys_to_pfn(start))) ++ return ioremap(start, size); ++ ++ page_start = start - offset_in_page(start); ++ page_count = DIV_ROUND_UP(size + offset_in_page(start), PAGE_SIZE); ++ ++ prot = pgprot_noncached(PAGE_KERNEL); ++ ++ pages = kmalloc_array(page_count, sizeof(struct page *), GFP_KERNEL); ++ if (!pages) { ++ pr_err("%s: Failed to allocate array for %u pages\n", ++ __func__, page_count); ++ return NULL; ++ } ++ ++ for (i = 0; i < page_count; i++) ++ pages[i] = phys_to_page(page_start + i * PAGE_SIZE); ++ ++ vaddr = vmap(pages, page_count, VM_MAP, prot); ++ kfree(pages); ++ ++ /* ++ * Since vmap() uses page granularity, we must add the offset ++ * into the page here, to get the byte granularity address ++ * into the mapping to represent the actual "start" location. ++ */ ++ return vaddr + offset_in_page(start); ++} ++ ++struct arm_smccc_res sip_smc_request_share_mem(u32 page_num, ++ share_page_type_t page_type) ++{ ++ struct arm_smccc_res res; ++ unsigned long share_mem_phy; ++ ++ res = __invoke_sip_fn_smc(SIP_SHARE_MEM, page_num, page_type, 0); ++ if (IS_SIP_ERROR(res.a0)) ++ goto error; ++ ++ share_mem_phy = res.a1; ++ res.a1 = (unsigned long)sip_map(share_mem_phy, SIZE_PAGE(page_num)); ++ ++error: ++ return res; ++} ++EXPORT_SYMBOL_GPL(sip_smc_request_share_mem); ++ ++struct arm_smccc_res sip_smc_mcu_el3fiq(u32 arg0, u32 arg1, u32 arg2) ++{ ++ return __invoke_sip_fn_smc(SIP_MCU_EL3FIQ_CFG, arg0, arg1, arg2); ++} ++EXPORT_SYMBOL_GPL(sip_smc_mcu_el3fiq); ++ ++struct arm_smccc_res sip_smc_vpu_reset(u32 arg0, u32 arg1, u32 arg2) ++{ ++ struct arm_smccc_res res; ++ ++ res = __invoke_sip_fn_smc(PSCI_SIP_VPU_RESET, arg0, arg1, arg2); ++ return res; ++} ++EXPORT_SYMBOL_GPL(sip_smc_vpu_reset); ++ ++struct arm_smccc_res sip_smc_bus_config(u32 arg0, u32 arg1, u32 arg2) ++{ ++ struct arm_smccc_res res; ++ ++ res = __invoke_sip_fn_smc(SIP_BUS_CFG, arg0, arg1, arg2); ++ return res; ++} ++EXPORT_SYMBOL_GPL(sip_smc_bus_config); ++ ++struct arm_smccc_res sip_smc_lastlog_request(void) ++{ ++ struct arm_smccc_res res; ++ void __iomem *addr1, *addr2; ++ ++ res = __invoke_sip_fn_smc(SIP_LAST_LOG, local_clock(), 0, 0); ++ if (IS_SIP_ERROR(res.a0)) ++ return res; ++ ++ addr1 = sip_map(res.a1, res.a3); ++ if (!addr1) { ++ pr_err("%s: share memory buffer0 ioremap failed\n", __func__); ++ res.a0 = SIP_RET_INVALID_ADDRESS; ++ return res; ++ } ++ addr2 = sip_map(res.a2, res.a3); ++ if (!addr2) { ++ pr_err("%s: share memory buffer1 ioremap failed\n", __func__); ++ res.a0 = SIP_RET_INVALID_ADDRESS; ++ return res; ++ } ++ ++ res.a1 = (unsigned long)addr1; ++ res.a2 = (unsigned long)addr2; ++ ++ return res; ++} ++EXPORT_SYMBOL_GPL(sip_smc_lastlog_request); ++ ++/************************** fiq debugger **************************************/ ++/* ++ * AArch32 is not allowed to call SMC64(ATF framework does not support), so we ++ * don't change SIP_UARTDBG_FN to SIP_UARTDBG_CFG64 even when cpu is AArch32 ++ * mode. Let ATF support SIP_UARTDBG_CFG, and we just initialize SIP_UARTDBG_FN ++ * depends on compile option(CONFIG_ARM or CONFIG_ARM64). ++ */ ++#ifdef CONFIG_ARM64 ++#define SIP_UARTDBG_FN SIP_UARTDBG_CFG64 ++#else ++#define SIP_UARTDBG_FN SIP_UARTDBG_CFG ++static int firmware_64_32bit; ++#endif ++ ++static int fiq_sip_enabled; ++static int fiq_target_cpu; ++static phys_addr_t ft_fiq_mem_phy; ++static void __iomem *ft_fiq_mem_base; ++static void (*sip_fiq_debugger_uart_irq_tf)(struct pt_regs _pt_regs, ++ unsigned long cpu); ++int sip_fiq_debugger_is_enabled(void) ++{ ++ return fiq_sip_enabled; ++} ++EXPORT_SYMBOL_GPL(sip_fiq_debugger_is_enabled); ++ ++static struct pt_regs sip_fiq_debugger_get_pt_regs(void *reg_base, ++ unsigned long sp_el1) ++{ ++ struct pt_regs fiq_pt_regs; ++ __maybe_unused struct sm_nsec_ctx *nsec_ctx = reg_base; ++ __maybe_unused struct gp_regs_ctx *gp_regs = reg_base; ++ ++#ifdef CONFIG_ARM64 ++ /* ++ * 64-bit ATF + 64-bit kernel ++ */ ++ /* copy cpu context: x0 ~ spsr_el3 */ ++ memcpy(&fiq_pt_regs, reg_base, 8 * 31); ++ ++ /* copy pstate: spsr_el3 */ ++ memcpy(&fiq_pt_regs.pstate, reg_base + 0x110, 8); ++ fiq_pt_regs.sp = sp_el1; ++ ++ /* copy pc: elr_el3 */ ++ memcpy(&fiq_pt_regs.pc, reg_base + 0x118, 8); ++#else ++ if (firmware_64_32bit == FIRMWARE_ATF_64BIT) { ++ /* ++ * 64-bit ATF + 32-bit kernel ++ */ ++ fiq_pt_regs.ARM_r0 = gp_regs->x0; ++ fiq_pt_regs.ARM_r1 = gp_regs->x1; ++ fiq_pt_regs.ARM_r2 = gp_regs->x2; ++ fiq_pt_regs.ARM_r3 = gp_regs->x3; ++ fiq_pt_regs.ARM_r4 = gp_regs->x4; ++ fiq_pt_regs.ARM_r5 = gp_regs->x5; ++ fiq_pt_regs.ARM_r6 = gp_regs->x6; ++ fiq_pt_regs.ARM_r7 = gp_regs->x7; ++ fiq_pt_regs.ARM_r8 = gp_regs->x8; ++ fiq_pt_regs.ARM_r9 = gp_regs->x9; ++ fiq_pt_regs.ARM_r10 = gp_regs->x10; ++ fiq_pt_regs.ARM_fp = gp_regs->x11; ++ fiq_pt_regs.ARM_ip = gp_regs->x12; ++ fiq_pt_regs.ARM_sp = gp_regs->x19; /* aarch32 svc_r13 */ ++ fiq_pt_regs.ARM_lr = gp_regs->x18; /* aarch32 svc_r14 */ ++ fiq_pt_regs.ARM_cpsr = gp_regs->spsr_el3; ++ fiq_pt_regs.ARM_pc = gp_regs->elr_el3; ++ } else { ++ /* ++ * 32-bit tee firmware + 32-bit kernel ++ */ ++ fiq_pt_regs.ARM_r0 = nsec_ctx->r0; ++ fiq_pt_regs.ARM_r1 = nsec_ctx->r1; ++ fiq_pt_regs.ARM_r2 = nsec_ctx->r2; ++ fiq_pt_regs.ARM_r3 = nsec_ctx->r3; ++ fiq_pt_regs.ARM_r4 = nsec_ctx->r4; ++ fiq_pt_regs.ARM_r5 = nsec_ctx->r5; ++ fiq_pt_regs.ARM_r6 = nsec_ctx->r6; ++ fiq_pt_regs.ARM_r7 = nsec_ctx->r7; ++ fiq_pt_regs.ARM_r8 = nsec_ctx->r8; ++ fiq_pt_regs.ARM_r9 = nsec_ctx->r9; ++ fiq_pt_regs.ARM_r10 = nsec_ctx->r10; ++ fiq_pt_regs.ARM_fp = nsec_ctx->r11; ++ fiq_pt_regs.ARM_ip = nsec_ctx->r12; ++ fiq_pt_regs.ARM_sp = nsec_ctx->svc_sp; ++ fiq_pt_regs.ARM_lr = nsec_ctx->svc_lr; ++ fiq_pt_regs.ARM_cpsr = nsec_ctx->mon_spsr; ++ ++ /* ++ * 'nsec_ctx->mon_lr' is not the fiq break point's PC, because it will ++ * be override as 'psci_fiq_debugger_uart_irq_tf_cb' for optee-os to ++ * jump to fiq_debugger handler. ++ * ++ * As 'nsec_ctx->und_lr' is not used for kernel, so optee-os uses it to ++ * deliver fiq break point's PC. ++ * ++ */ ++ fiq_pt_regs.ARM_pc = nsec_ctx->und_lr; ++ } ++#endif ++ ++ return fiq_pt_regs; ++} ++ ++static void sip_fiq_debugger_uart_irq_tf_cb(unsigned long sp_el1, ++ unsigned long offset, ++ unsigned long cpu) ++{ ++ struct pt_regs fiq_pt_regs; ++ char *cpu_context; ++ ++ /* calling fiq handler */ ++ if (ft_fiq_mem_base) { ++ cpu_context = (char *)ft_fiq_mem_base + offset; ++ fiq_pt_regs = sip_fiq_debugger_get_pt_regs(cpu_context, sp_el1); ++ sip_fiq_debugger_uart_irq_tf(fiq_pt_regs, cpu); ++ } ++ ++ /* fiq handler done, return to EL3(then EL3 return to EL1 entry) */ ++ __invoke_sip_fn_smc(SIP_UARTDBG_FN, 0, 0, UARTDBG_CFG_OSHDL_TO_OS); ++} ++ ++int sip_fiq_debugger_uart_irq_tf_init(u32 irq_id, void *callback_fn) ++{ ++ struct arm_smccc_res res; ++ ++ /* init fiq debugger callback */ ++ sip_fiq_debugger_uart_irq_tf = callback_fn; ++ res = __invoke_sip_fn_smc(SIP_UARTDBG_FN, irq_id, ++ (unsigned long)sip_fiq_debugger_uart_irq_tf_cb, ++ UARTDBG_CFG_INIT); ++ if (IS_SIP_ERROR(res.a0)) { ++ pr_err("%s error: %d\n", __func__, (int)res.a0); ++ return res.a0; ++ } ++ ++ /* share memory ioremap */ ++ if (!ft_fiq_mem_base) { ++ ft_fiq_mem_phy = res.a1; ++ ft_fiq_mem_base = sip_map(ft_fiq_mem_phy, ++ FIQ_UARTDBG_SHARE_MEM_SIZE); ++ if (!ft_fiq_mem_base) { ++ pr_err("%s: share memory ioremap failed\n", __func__); ++ return -ENOMEM; ++ } ++ } ++ ++ fiq_sip_enabled = 1; ++ ++ return SIP_RET_SUCCESS; ++} ++EXPORT_SYMBOL_GPL(sip_fiq_debugger_uart_irq_tf_init); ++ ++static ulong cpu_logical_map_mpidr(u32 cpu) ++{ ++#ifdef MODULE ++ /* Empirically, local "cpu_logical_map()" for rockchip platforms */ ++ ulong mpidr = 0x00; ++ ++ if (cpu < 4) ++ /* 0x00, 0x01, 0x02, 0x03 */ ++ mpidr = cpu; ++ else if (cpu < 8) ++ /* 0x100, 0x101, 0x102, 0x103 */ ++ mpidr = 0x100 | (cpu - 4); ++ else ++ pr_err("Unsupported map cpu: %d\n", cpu); ++ ++ return mpidr; ++#else ++ return cpu_logical_map(cpu); ++#endif ++} ++ ++int sip_fiq_debugger_switch_cpu(u32 cpu) ++{ ++ struct arm_smccc_res res; ++ ++ fiq_target_cpu = cpu; ++ res = __invoke_sip_fn_smc(SIP_UARTDBG_FN, cpu_logical_map_mpidr(cpu), ++ 0, UARTDBG_CFG_OSHDL_CPUSW); ++ return res.a0; ++} ++ ++int sip_fiq_debugger_sdei_switch_cpu(u32 cur_cpu, u32 target_cpu, u32 flag) ++{ ++ struct arm_smccc_res res; ++ ++ res = __invoke_sip_fn_smc(SIP_SDEI_FIQ_DBG_SWITCH_CPU, ++ cur_cpu, target_cpu, flag); ++ return res.a0; ++} ++ ++int sip_fiq_debugger_sdei_get_event_id(u32 *fiq, u32 *sw_cpu, u32 *flag) ++{ ++ struct arm_smccc_res res; ++ ++ res = __invoke_sip_fn_smc(SIP_SDEI_FIQ_DBG_GET_EVENT_ID, ++ 0, 0, 0); ++ *fiq = res.a1; ++ *sw_cpu = res.a2; ++ if (flag) ++ *flag = res.a3; ++ ++ return res.a0; ++} ++ ++EXPORT_SYMBOL_GPL(sip_fiq_debugger_switch_cpu); ++ ++void sip_fiq_debugger_enable_debug(bool enable) ++{ ++ unsigned long val; ++ ++ val = enable ? UARTDBG_CFG_OSHDL_DEBUG_ENABLE : ++ UARTDBG_CFG_OSHDL_DEBUG_DISABLE; ++ ++ __invoke_sip_fn_smc(SIP_UARTDBG_FN, 0, 0, val); ++} ++EXPORT_SYMBOL_GPL(sip_fiq_debugger_enable_debug); ++ ++int sip_fiq_debugger_set_print_port(u32 port_phyaddr, u32 baudrate) ++{ ++ struct arm_smccc_res res; ++ ++ res = __invoke_sip_fn_smc(SIP_UARTDBG_FN, port_phyaddr, baudrate, ++ UARTDBG_CFG_PRINT_PORT); ++ return res.a0; ++} ++EXPORT_SYMBOL_GPL(sip_fiq_debugger_set_print_port); ++ ++int sip_fiq_debugger_request_share_memory(void) ++{ ++ struct arm_smccc_res res; ++ ++ /* request page share memory */ ++ res = sip_smc_request_share_mem(FIQ_UARTDBG_PAGE_NUMS, ++ SHARE_PAGE_TYPE_UARTDBG); ++ if (IS_SIP_ERROR(res.a0)) ++ return res.a0; ++ ++ return SIP_RET_SUCCESS; ++} ++EXPORT_SYMBOL_GPL(sip_fiq_debugger_request_share_memory); ++ ++int sip_fiq_debugger_get_target_cpu(void) ++{ ++ return fiq_target_cpu; ++} ++EXPORT_SYMBOL_GPL(sip_fiq_debugger_get_target_cpu); ++ ++void sip_fiq_debugger_enable_fiq(bool enable, uint32_t tgt_cpu) ++{ ++ u32 en; ++ ++ fiq_target_cpu = tgt_cpu; ++ en = enable ? UARTDBG_CFG_FIQ_ENABEL : UARTDBG_CFG_FIQ_DISABEL; ++ __invoke_sip_fn_smc(SIP_UARTDBG_FN, tgt_cpu, 0, en); ++} ++EXPORT_SYMBOL_GPL(sip_fiq_debugger_enable_fiq); ++ ++/******************************************************************************/ ++#ifdef CONFIG_ARM ++static __init int sip_firmware_init(void) ++{ ++ struct arm_smccc_res res; ++ ++ if (!psci_smp_available()) ++ return 0; ++ ++ /* ++ * OP-TEE works on kernel 3.10 and 4.4 and we have different sip ++ * implement. We should tell OP-TEE the current rockchip sip version. ++ */ ++ res = __invoke_sip_fn_smc(SIP_SIP_VERSION, SIP_IMPLEMENT_V2, ++ SECURE_REG_WR, 0); ++ if (IS_SIP_ERROR(res.a0)) ++ pr_err("%s: set rockchip sip version v2 failed\n", __func__); ++ ++ /* ++ * Currently, we support: ++ * ++ * 1. 64-bit ATF + 64-bit kernel; ++ * 2. 64-bit ATF + 32-bit kernel; ++ * 3. 32-bit TEE + 32-bit kernel; ++ * ++ * We need to detect which case of above and record in firmware_64_32bit ++ * We get info from cpuid and compare with all supported ARMv7 cpu. ++ */ ++ switch (read_cpuid_part()) { ++ case ARM_CPU_PART_CORTEX_A7: ++ case ARM_CPU_PART_CORTEX_A8: ++ case ARM_CPU_PART_CORTEX_A9: ++ case ARM_CPU_PART_CORTEX_A12: ++ case ARM_CPU_PART_CORTEX_A15: ++ case ARM_CPU_PART_CORTEX_A17: ++ firmware_64_32bit = FIRMWARE_TEE_32BIT; ++ break; ++ default: ++ firmware_64_32bit = FIRMWARE_ATF_64BIT; ++ break; ++ } ++ ++ return 0; ++} ++arch_initcall(sip_firmware_init); ++#endif ++ ++MODULE_DESCRIPTION("Rockchip SIP Call"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig +index ed85d0cee..4c15feed2 100644 +--- a/drivers/gpu/drm/Kconfig ++++ b/drivers/gpu/drm/Kconfig +@@ -185,6 +185,12 @@ config DRM_LOAD_EDID_FIRMWARE + + source "drivers/gpu/drm/display/Kconfig" + ++config DRM_EPD_HELPER ++ tristate ++ depends on DRM ++ help ++ Choose this if you need the EPD (LUT, etc.) helper functions ++ + config DRM_TTM + tristate + depends on DRM && MMU +diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile +index d26191717..9084bc106 100644 +--- a/drivers/gpu/drm/Makefile ++++ b/drivers/gpu/drm/Makefile +@@ -121,6 +121,12 @@ drm_dma_helper-$(CONFIG_DRM_FBDEV_EMULATION) += drm_fbdev_dma.o + drm_dma_helper-$(CONFIG_DRM_KMS_HELPER) += drm_fb_dma_helper.o + obj-$(CONFIG_DRM_GEM_DMA_HELPER) += drm_dma_helper.o + ++obj-$(CONFIG_DRM_EPD_HELPER) += drm_epd_helper.o ++ ++# drm_cma_helper-y := drm_gem_cma_helper.o ++# drm_cma_helper-$(CONFIG_DRM_KMS_HELPER) += drm_fb_cma_helper.o ++# obj-$(CONFIG_DRM_GEM_CMA_HELPER) += drm_cma_helper.o ++ + drm_shmem_helper-y := drm_gem_shmem_helper.o + drm_shmem_helper-$(CONFIG_DRM_FBDEV_EMULATION) += drm_fbdev_shmem.o + obj-$(CONFIG_DRM_GEM_SHMEM_HELPER) += drm_shmem_helper.o +@@ -188,7 +194,7 @@ obj-$(CONFIG_DRM_VKMS) += vkms/ + obj-$(CONFIG_DRM_NOUVEAU) +=nouveau/ + obj-$(CONFIG_DRM_NOVA) += nova/ + obj-$(CONFIG_DRM_EXYNOS) +=exynos/ +-obj-$(CONFIG_DRM_ROCKCHIP) +=rockchip/ ++obj-y +=rockchip/ + obj-$(CONFIG_DRM_GMA500) += gma500/ + obj-$(CONFIG_DRM_UDL) += udl/ + obj-$(CONFIG_DRM_AST) += ast/ +diff --git a/drivers/gpu/drm/drm_client.c b/drivers/gpu/drm/drm_client.c +index a82d741e6..45fd0adb3 100644 +--- a/drivers/gpu/drm/drm_client.c ++++ b/drivers/gpu/drm/drm_client.c +@@ -107,6 +107,36 @@ int drm_client_init(struct drm_device *dev, struct drm_client_dev *client, + } + EXPORT_SYMBOL(drm_client_init); + ++/** ++ * drm_client_init_from_id - Initialise a DRM client ++ * @minor_id: DRM minor id ++ * @client: DRM client ++ * @name: Client name ++ * @funcs: DRM client functions (optional) ++ * ++ * This function looks up the drm_device using the minor id and initializes the ++ * client using drm_client_init(). ++ * ++ * Returns: ++ * Zero on success or negative error code on failure. ++ */ ++int drm_client_init_from_id(unsigned int minor_id, struct drm_client_dev *client, ++ const char *name, const struct drm_client_funcs *funcs) ++{ ++ struct drm_minor *minor; ++ int ret; ++ ++ minor = drm_minor_acquire(&drm_minors_xa, minor_id); ++ if (IS_ERR(minor)) ++ return PTR_ERR(minor); ++ ++ ret = drm_client_init(minor->dev, client, name, funcs); ++ drm_minor_release(minor); ++ ++ return ret; ++} ++EXPORT_SYMBOL(drm_client_init_from_id); ++ + /** + * drm_client_register - Register client + * @client: DRM client +@@ -120,12 +150,20 @@ EXPORT_SYMBOL(drm_client_init); + * Registering a client generates a hotplug event that allows the client + * to set up its display from pre-existing outputs. The client must have + * initialized its state to able to handle the hotplug event successfully. ++ * ++ * Returns: ++ * True if the client has been registered, false if the DRM device has already ++ * been unregistered. + */ +-void drm_client_register(struct drm_client_dev *client) ++bool drm_client_register(struct drm_client_dev *client) + { + struct drm_device *dev = client->dev; ++ int idx; + int ret; + ++ if (!drm_dev_enter(client->dev, &idx)) ++ return false; ++ + mutex_lock(&dev->clientlist_mutex); + list_add(&client->list, &dev->clientlist); + +@@ -145,6 +183,10 @@ void drm_client_register(struct drm_client_dev *client) + drm_dbg_kms(dev, "client hotplug ret=%d\n", ret); + } + mutex_unlock(&dev->clientlist_mutex); ++ ++ drm_dev_exit(idx); ++ ++ return true; + } + EXPORT_SYMBOL(drm_client_register); + +diff --git a/drivers/gpu/drm/drm_client_modeset.c b/drivers/gpu/drm/drm_client_modeset.c +index 4a72f323e..c9f069461 100644 +--- a/drivers/gpu/drm/drm_client_modeset.c ++++ b/drivers/gpu/drm/drm_client_modeset.c +@@ -89,6 +89,10 @@ static void drm_client_modeset_release(struct drm_client_dev *client) + } + modeset->num_connectors = 0; + } ++ ++ kfree(client->properties); ++ client->properties = NULL; ++ client->num_properties = 0; + } + + void drm_client_modeset_free(struct drm_client_dev *client) +@@ -944,6 +948,130 @@ int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width, + } + EXPORT_SYMBOL(drm_client_modeset_probe); + ++/** ++ * drm_client_modeset_set() - Set modeset configuration ++ * @client: DRM client ++ * @connector: Connector ++ * @mode: Display mode ++ * @fb: Framebuffer ++ * ++ * This function releases any current modeset info, including properties, and ++ * sets the new modeset in the client's modeset array. ++ * ++ * Returns: ++ * Zero on success or negative error code on failure. ++ */ ++int drm_client_modeset_set(struct drm_client_dev *client, struct drm_connector *connector, ++ struct drm_display_mode *mode, struct drm_framebuffer *fb) ++{ ++ struct drm_mode_set *modeset; ++ int ret = -ENOENT; ++ ++ mutex_lock(&client->modeset_mutex); ++ ++ drm_client_modeset_release(client); ++ ++ drm_client_for_each_modeset(modeset, client) { ++ if (!connector_has_possible_crtc(connector, modeset->crtc)) ++ continue; ++ ++ modeset->mode = drm_mode_duplicate(client->dev, mode); ++ if (!modeset->mode) { ++ ret = -ENOMEM; ++ break; ++ } ++ ++ drm_mode_set_crtcinfo(modeset->mode, CRTC_INTERLACE_HALVE_V); ++ ++ drm_connector_get(connector); ++ modeset->connectors[modeset->num_connectors++] = connector; ++ ++ modeset->fb = fb; ++ ret = 0; ++ break; ++ } ++ ++ mutex_unlock(&client->modeset_mutex); ++ ++ return ret; ++} ++EXPORT_SYMBOL(drm_client_modeset_set); ++ ++/** ++ * drm_client_modeset_set_property() - Set a property on the current configuration ++ * @client: DRM client ++ * @obj: DRM Mode Object ++ * @prop: DRM Property ++ * @value: Property value ++ * ++ * Note: Currently only implemented for atomic drivers. ++ * ++ * Returns: ++ * Zero on success or negative error code on failure. ++ */ ++int drm_client_modeset_set_property(struct drm_client_dev *client, struct drm_mode_object *obj, ++ struct drm_property *prop, u64 value) ++{ ++ struct drm_client_property *properties; ++ int ret = 0; ++ ++ if (!prop) ++ return -EINVAL; ++ ++ if (!drm_drv_uses_atomic_modeset(client->dev)) ++ return -EOPNOTSUPP; ++ ++ mutex_lock(&client->modeset_mutex); ++ ++ properties = krealloc(client->properties, ++ (client->num_properties + 1) * sizeof(*properties), GFP_KERNEL); ++ if (!properties) { ++ ret = -ENOMEM; ++ goto unlock; ++ } ++ ++ properties[client->num_properties].obj = obj; ++ properties[client->num_properties].prop = prop; ++ properties[client->num_properties].value = value; ++ client->properties = properties; ++ client->num_properties++; ++unlock: ++ mutex_unlock(&client->modeset_mutex); ++ ++ return ret; ++} ++EXPORT_SYMBOL(drm_client_modeset_set_property); ++ ++/** ++ * drm_client_modeset_set_rotation() - Set rotation on the current configuration ++ * @client: DRM client ++ * @value: Rotation value ++ * ++ * Returns: ++ * Zero on success or negative error code on failure. ++ */ ++int drm_client_modeset_set_rotation(struct drm_client_dev *client, u64 value) ++{ ++ struct drm_plane *plane = NULL; ++ struct drm_mode_set *modeset; ++ ++ mutex_lock(&client->modeset_mutex); ++ drm_client_for_each_modeset(modeset, client) { ++ if (modeset->mode) { ++ plane = modeset->crtc->primary; ++ break; ++ } ++ } ++ mutex_unlock(&client->modeset_mutex); ++ ++ if (!plane) ++ return -ENOENT; ++ ++ return drm_client_modeset_set_property(client, &plane->base, ++ plane->rotation_property, value); ++} ++EXPORT_SYMBOL(drm_client_modeset_set_rotation); ++ + /** + * drm_client_rotation() - Check the initial rotation value + * @modeset: DRM modeset +@@ -1038,6 +1166,7 @@ static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool + struct drm_atomic_state *state; + struct drm_modeset_acquire_ctx ctx; + struct drm_mode_set *mode_set; ++ unsigned int i; + int ret; + + drm_modeset_acquire_init(&ctx, 0); +@@ -1098,6 +1227,14 @@ static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool + } + } + ++ for (i = 0; i < client->num_properties; i++) { ++ struct drm_client_property *prop = &client->properties[i]; ++ ++ ret = drm_atomic_set_property(state, NULL, prop->obj, prop->prop, prop->value, false); ++ if (ret) ++ goto out_state; ++ } ++ + if (check) + ret = drm_atomic_check_only(state); + else +@@ -1338,6 +1475,26 @@ int drm_client_modeset_wait_for_vblank(struct drm_client_dev *client, unsigned i + } + EXPORT_SYMBOL(drm_client_modeset_wait_for_vblank); + ++/** ++ * drm_client_modeset_disable() - Disable all outputs ++ * @client: DRM client ++ * ++ * This function disables all outputs by first clearing the modeset array and ++ * then committing the empty modesets. ++ * ++ * Returns: ++ * Zero on success or negative error code on failure. ++ */ ++int drm_client_modeset_disable(struct drm_client_dev *client) ++{ ++ mutex_lock(&client->modeset_mutex); ++ drm_client_modeset_release(client); ++ mutex_unlock(&client->modeset_mutex); ++ ++ return drm_client_modeset_commit(client); ++} ++EXPORT_SYMBOL(drm_client_modeset_disable); ++ + #ifdef CONFIG_DRM_KUNIT_TEST + #include "tests/drm_client_modeset_test.c" + #endif +diff --git a/drivers/gpu/drm/drm_epd_helper.c b/drivers/gpu/drm/drm_epd_helper.c +new file mode 100644 +index 000000000..47a5c8257 +--- /dev/null ++++ b/drivers/gpu/drm/drm_epd_helper.c +@@ -0,0 +1,711 @@ ++// SPDX-License-Identifier: GPL-2.0 OR MIT ++/* ++ * Electrophoretic Display Helper Library ++ * ++ * Copyright (C) 2022 Samuel Holland ++ * ++ * 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 COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS 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 ++#include ++#include ++ ++#include ++#include ++#include ++#include ++ ++/** ++ * DOC: overview ++ * ++ * This library provides functions for working with the lookup tables (LUTs) ++ * used by electrophoretic displays (EPDs). It fills in a LUT buffer based on ++ * the selected waveform, the panel temperature, and the buffer format needed ++ * by the driver. ++ */ ++ ++struct pvi_wbf_offset { ++ u8 b[3]; ++}; ++ ++struct pvi_wbf_pointer { ++ struct pvi_wbf_offset offset; ++ u8 checksum; ++}; ++ ++struct pvi_wbf_file_header { ++ __le32 checksum; // 0x00 ++ __le32 file_size; // 0x04 ++ __le32 serial; // 0x08 ++ u8 run_type; // 0x0c ++ u8 fpl_platform; // 0x0d ++ __le16 fpl_lot; // 0x0e ++ u8 mode_version; // 0x10 ++ u8 wf_version; // 0x11 ++ u8 wf_subversion; // 0x12 ++ u8 wf_type; // 0x13 ++ u8 panel_size; // 0x14 ++ u8 amepd_part_number; // 0x15 ++ u8 wf_rev; // 0x16 ++ u8 frame_rate_bcd; // 0x17 ++ u8 frame_rate_hex; // 0x18 ++ u8 vcom_offset; // 0x19 ++ u8 unknown[2]; // 0x1a ++ struct pvi_wbf_offset xwia; // 0x1c ++ u8 cs1; // 0x1f ++ struct pvi_wbf_offset wmta; // 0x20 ++ u8 fvsn; // 0x23 ++ u8 luts; // 0x24 ++ u8 mode_count; // 0x25 ++ u8 temp_range_count; // 0x26 ++ u8 advanced_wf_flags; // 0x27 ++ u8 eb; // 0x28 ++ u8 sb; // 0x29 ++ u8 reserved[5]; // 0x2a ++ u8 cs2; // 0x2f ++ u8 temp_range_table[]; // 0x30 ++}; ++static_assert(sizeof(struct pvi_wbf_file_header) == 0x30); ++ ++struct pvi_wbf_mode_info { ++ u8 versions[2]; ++ u8 format; ++ u8 modes[DRM_EPD_WF_MAX]; ++}; ++ ++static const struct pvi_wbf_mode_info pvi_wbf_mode_info_table[] = { ++ { ++ .versions = { ++ 0x09, ++ }, ++ .format = DRM_EPD_LUT_4BIT_PACKED, ++ .modes = { ++ [DRM_EPD_WF_RESET] = 0, ++ [DRM_EPD_WF_DU] = 1, ++ [DRM_EPD_WF_DU4] = 1, ++ [DRM_EPD_WF_GC16] = 2, ++ [DRM_EPD_WF_GL16] = 3, ++ [DRM_EPD_WF_GLR16] = 3, ++ [DRM_EPD_WF_GLD16] = 3, ++ [DRM_EPD_WF_A2] = 4, ++ [DRM_EPD_WF_GCC16] = 3, ++ }, ++ }, ++ { ++ .versions = { ++ 0x12, ++ }, ++ .format = DRM_EPD_LUT_4BIT_PACKED, ++ .modes = { ++ [DRM_EPD_WF_RESET] = 0, ++ [DRM_EPD_WF_DU] = 1, ++ [DRM_EPD_WF_DU4] = 7, ++ [DRM_EPD_WF_GC16] = 3, ++ [DRM_EPD_WF_GL16] = 3, ++ [DRM_EPD_WF_GLR16] = 5, ++ [DRM_EPD_WF_GLD16] = 6, ++ [DRM_EPD_WF_A2] = 4, ++ [DRM_EPD_WF_GCC16] = 5, ++ }, ++ }, ++ { ++ .versions = { ++ 0x16, ++ }, ++ .format = DRM_EPD_LUT_5BIT_PACKED, ++ .modes = { ++ [DRM_EPD_WF_RESET] = 0, ++ [DRM_EPD_WF_DU] = 1, ++ [DRM_EPD_WF_DU4] = 1, ++ [DRM_EPD_WF_GC16] = 2, ++ [DRM_EPD_WF_GL16] = 3, ++ [DRM_EPD_WF_GLR16] = 4, ++ [DRM_EPD_WF_GLD16] = 4, ++ [DRM_EPD_WF_A2] = 6, ++ [DRM_EPD_WF_GCC16] = 5, ++ }, ++ }, ++ { ++ .versions = { ++ 0x18, ++ 0x20, ++ }, ++ .format = DRM_EPD_LUT_5BIT_PACKED, ++ .modes = { ++ [DRM_EPD_WF_RESET] = 0, ++ [DRM_EPD_WF_DU] = 1, ++ [DRM_EPD_WF_DU4] = 1, ++ [DRM_EPD_WF_GC16] = 2, ++ [DRM_EPD_WF_GL16] = 3, ++ [DRM_EPD_WF_GLR16] = 4, ++ [DRM_EPD_WF_GLD16] = 5, ++ [DRM_EPD_WF_A2] = 6, ++ [DRM_EPD_WF_GCC16] = 4, ++ }, ++ }, ++ { ++ .versions = { ++ 0x19, ++ 0x43, ++ }, ++ .format = DRM_EPD_LUT_5BIT, ++ .modes = { ++ [DRM_EPD_WF_RESET] = 0, ++ [DRM_EPD_WF_DU] = 1, ++ [DRM_EPD_WF_DU4] = 7, ++ [DRM_EPD_WF_GC16] = 2, ++ [DRM_EPD_WF_GL16] = 3, ++ [DRM_EPD_WF_GLR16] = 4, ++ [DRM_EPD_WF_GLD16] = 5, ++ [DRM_EPD_WF_A2] = 6, ++ [DRM_EPD_WF_GCC16] = 4, ++ }, ++ }, ++ { ++ .versions = { ++ 0x23, ++ }, ++ .format = DRM_EPD_LUT_4BIT_PACKED, ++ .modes = { ++ [DRM_EPD_WF_RESET] = 0, ++ [DRM_EPD_WF_DU] = 1, ++ [DRM_EPD_WF_DU4] = 5, ++ [DRM_EPD_WF_GC16] = 2, ++ [DRM_EPD_WF_GL16] = 3, ++ [DRM_EPD_WF_GLR16] = 3, ++ [DRM_EPD_WF_GLD16] = 3, ++ [DRM_EPD_WF_A2] = 4, ++ [DRM_EPD_WF_GCC16] = 3, ++ }, ++ }, ++ { ++ .versions = { ++ 0x54, ++ }, ++ .format = DRM_EPD_LUT_4BIT_PACKED, ++ .modes = { ++ [DRM_EPD_WF_RESET] = 0, ++ [DRM_EPD_WF_DU] = 1, ++ [DRM_EPD_WF_DU4] = 1, ++ [DRM_EPD_WF_GC16] = 2, ++ [DRM_EPD_WF_GL16] = 3, ++ [DRM_EPD_WF_GLR16] = 4, ++ [DRM_EPD_WF_GLD16] = 4, ++ [DRM_EPD_WF_A2] = 5, ++ [DRM_EPD_WF_GCC16] = 4, ++ }, ++ }, ++}; ++ ++static const void *pvi_wbf_apply_offset(const struct drm_epd_lut_file *file, ++ const struct pvi_wbf_offset *offset) ++{ ++ u32 bytes = offset->b[0] | offset->b[1] << 8 | offset->b[2] << 16; ++ ++ if (bytes >= file->fw->size) ++ return NULL; ++ ++ return (const void *)file->header + bytes; ++} ++ ++static const void *pvi_wbf_dereference(const struct drm_epd_lut_file *file, ++ const struct pvi_wbf_pointer *ptr) ++{ ++ u8 sum = ptr->offset.b[0] + ptr->offset.b[1] + ptr->offset.b[2]; ++ ++ if (ptr->checksum != sum) ++ return NULL; ++ ++ return pvi_wbf_apply_offset(file, &ptr->offset); ++} ++ ++int pvi_wbf_get_mode_index(const struct drm_epd_lut_file *file, ++ enum drm_epd_waveform waveform) ++{ ++ if (waveform >= DRM_EPD_WF_MAX) ++ return -EINVAL; ++ ++ return file->mode_info->modes[waveform]; ++} ++EXPORT_SYMBOL(pvi_wbf_get_mode_index); ++ ++static int pvi_wbf_get_mode_info(struct drm_epd_lut_file *file) ++{ ++ u8 mode_version = file->header->mode_version; ++ const struct pvi_wbf_mode_info *mode_info; ++ int i, v; ++ ++ for (i = 0; i < ARRAY_SIZE(pvi_wbf_mode_info_table); i++) { ++ mode_info = &pvi_wbf_mode_info_table[i]; ++ ++ for (v = 0; v < ARRAY_SIZE(mode_info->versions); v++) { ++ if (mode_info->versions[v] == mode_version) { ++ file->mode_info = mode_info; ++ return 0; ++ } ++ } ++ } ++ ++ drm_err(file->dev, "Unknown PVI waveform version 0x%02x\n", ++ mode_version); ++ ++ return -ENOTSUPP; ++} ++ ++static int pvi_wbf_get_temp_index(const struct drm_epd_lut_file *file, ++ int temperature) ++{ ++ const struct pvi_wbf_file_header *header = file->header; ++ int i; ++ ++ for (i = 0; i < header->temp_range_count; i++) ++ if (temperature < header->temp_range_table[i]) ++ return i - 1; ++ ++ return header->temp_range_count - 1; ++} ++ ++static int pvi_wbf_validate_header(struct drm_epd_lut_file *file) ++{ ++ const struct pvi_wbf_file_header *header = file->header; ++ int ret; ++ ++ if (le32_to_cpu(header->file_size) > file->fw->size) ++ return -EINVAL; ++ ++ ret = pvi_wbf_get_mode_info(file); ++ if (ret) ++ return ret; ++ ++ drm_info(file->dev, "Loaded %d-bit PVI waveform version 0x%02x\n", ++ file->mode_info->format == DRM_EPD_LUT_5BIT_PACKED ? 5 : 4, ++ header->mode_version); ++ ++ return 0; ++} ++ ++static void drm_epd_lut_file_free(struct drm_device *dev, void *res) ++{ ++ struct drm_epd_lut_file *file = res; ++ ++ release_firmware(file->fw); ++} ++ ++/** ++ * drmm_epd_lut_file_init - Initialize a managed EPD LUT file ++ * ++ * @dev: The DRM device owning this LUT file ++ * @file: The LUT file to initialize ++ * @file_name: The filesystem name of the LUT file ++ * ++ * Return: negative errno on failure, 0 otherwise ++ */ ++int drmm_epd_lut_file_init(struct drm_device *dev, ++ struct drm_epd_lut_file *file, ++ const char *file_name) ++{ ++ int ret; ++ ++ ret = request_firmware(&file->fw, file_name, dev->dev); ++ if (ret) ++ return ret; ++ ++ ret = drmm_add_action_or_reset(dev, drm_epd_lut_file_free, file); ++ if (ret) ++ return ret; ++ ++ file->dev = dev; ++ file->header = (const void *)file->fw->data; ++ ++ ret = pvi_wbf_validate_header(file); ++ if (ret) ++ return ret; ++ ++ /* Only 5-bit waveform files are supported by drm_epd_lut_convert. ++ * Due to the structure of the input file, we need to fully decompose the ++ * data and cannot retain the packed state*/ ++ if (file->mode_info->format != DRM_EPD_LUT_5BIT) ++ return -ENOTSUPP; ++ ++ return 0; ++} ++EXPORT_SYMBOL(drmm_epd_lut_file_init); ++ ++/** ++ * drm_epd_lut_size_shift - Get the size of a LUT phase in power-of-2 bytes ++ * ++ * Note: the bit size referred to the formats refers to the number of ++ * transitions of a pixel. For example, the 5 bit lut indicates that we have ++ * 2^5 possible transitions, which in this case means that there are 32 ++ * different states. ++ * 4 bit corresponds to 16 grayscales ++ * ++ * @format: One of the LUT buffer formats ++ * ++ * Return: buffer size shift amount ++ */ ++static int drm_epd_lut_size_shift(enum drm_epd_lut_format format) ++{ ++ switch (format) { ++ case DRM_EPD_LUT_4BIT: ++ /* (4 bits/pixel)^2 / 1 pixel/byte */ ++ return 4 + 4; ++ case DRM_EPD_LUT_4BIT_PACKED: ++ /* (4 bits/pixel)^2 / 4 pixels/byte */ ++ return 4 + 4 - 2; ++ case DRM_EPD_LUT_5BIT: ++ /* (5 bits/pixel)^2 / 1 pixel/byte */ ++ return 5 + 5; ++ case DRM_EPD_LUT_5BIT_PACKED: ++ /* (5 bits/pixel)^2 / 4 pixels/byte */ ++ return 5 + 5 - 2; ++ } ++ ++ unreachable(); ++} ++ ++/* decode the lut into a DRM_EPD_LUT_5BIT structured buffer */ ++static int pvi_wbf_decode_lut(struct drm_epd_lut *lut, const u8 *lut_data) ++{ ++ unsigned int copies, max_bytes, size_shift, state, total_bytes; ++ struct drm_device *dev = lut->file->dev; ++ const u8 *in = lut_data; ++ u8 *out = lut->buf; ++ u8 token; ++ ++ u32 x=0; ++ u32 y=0; ++ int frame=0; ++ // note: we hardcode 32 transitions (5-bit) here ++ int maxpic = 0x20; ++ /* Aug 15 11:26:37 pinenote kernel: ebc: size_shift: 10 max_bytes: 262144 */ ++ /* Aug 15 11:26:37 pinenote kernel: ebc: total_bytes: 111616 */ ++ ++ size_shift = drm_epd_lut_size_shift(lut->file->mode_info->format); ++ max_bytes = lut->max_phases << size_shift; ++ total_bytes = 0; ++ state = 1; ++ ++ /* Read tokens until reaching the end-of-input marker. */ ++ while ((token = *in++) != 0xff) { ++ /* Special handling for the state switch token. */ ++ if (token == 0xfc) { ++ state = !state; ++ token = *in++; ++ } ++ ++ /* ++ * State 0 is a sequence of data bytes. ++ * State 1 is a sequence of [data byte, extra copies] pairs. ++ */ ++ copies = 1 + (state ? *in++ : 0); ++ ++ total_bytes += (copies * 4); ++ if (total_bytes > max_bytes) { ++ drm_err(dev, "LUT contains too many phases\n"); ++ lut->num_phases = 0; ++ return -EILSEQ; ++ } ++ ++ // due to the values being stored in transposed form, we need to ++ // decompose each byte separately ++ while (copies--){ ++ *(out + ((frame * 0x400) + (x + 0) * 0x20 + y)) = (token >> 0) & 0x3; ++ *(out + ((frame * 0x400) + (x + 1) * 0x20 + y)) = (token >> 2) & 0x3; ++ *(out + ((frame * 0x400) + (x + 2) * 0x20 + y)) = (token >> 4) & 0x3; ++ *(out + ((frame * 0x400) + (x + 3) * 0x20 + y)) = (token >> 6) & 0x3; ++ ++ x += 4; ++ if (x >= maxpic){ ++ x = 0; ++ y++; ++ if (y >= maxpic) { ++ y = 0; ++ frame++; ++ } ++ } ++ } ++ } ++ ++ lut->num_phases = total_bytes >> size_shift; ++ if (total_bytes != lut->num_phases << size_shift) { ++ drm_err(dev, "LUT contains a partial phase\n"); ++ lut->num_phases = 0; ++ return -EILSEQ; ++ } ++ ++ drm_dbg_core(dev, "LUT contains %d phases (%ld => %ld bytes)\n", ++ lut->num_phases, in - lut_data, out - lut->buf); ++ ++ return 0; ++} ++ ++static int pvi_wbf_get_lut(struct drm_epd_lut *lut, ++ int mode_index, int temp_index) ++{ ++ const struct pvi_wbf_pointer *mode_table, *temp_table; ++ const struct drm_epd_lut_file *file = lut->file; ++ const u8 *lut_data; ++ int ret; ++ ++ mode_table = pvi_wbf_apply_offset(file, &file->header->wmta); ++ if (!mode_table) ++ return -EFAULT; ++ ++ temp_table = pvi_wbf_dereference(file, &mode_table[mode_index]); ++ if (!temp_table) ++ return -EFAULT; ++ ++ lut_data = pvi_wbf_dereference(file, &temp_table[temp_index]); ++ if (!lut_data) ++ return -EFAULT; ++ ++ ret = pvi_wbf_decode_lut(lut, lut_data); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++/* we always convert from DRM_EPD_LUT_5BIT */ ++static void drm_epd_lut_convert(const struct drm_epd_lut *lut) ++{ ++ enum drm_epd_lut_format from = lut->file->mode_info->format; ++ enum drm_epd_lut_format to = lut->format; ++ u8 *buf = lut->buf; ++ size_t x, y; ++ unsigned int counter=0; ++ ++ if (to == from) ++ return; ++ ++ switch (to) { ++ case DRM_EPD_LUT_4BIT: ++ for (y = 0; y < 16 * lut->num_phases; ++y) { ++ for (x = 8; x--;) { ++ u8 byte = buf[16 * y + x]; ++ ++ buf[16 * y + 2 * x + 0] = (byte >> 0) & 0x03; ++ buf[16 * y + 2 * x + 1] = (byte >> 4) & 0x03; ++ } ++ } ++ for (; y < 16 * lut->max_phases; ++y) { ++ for (x = 8; x--;) { ++ buf[16 * y + 2 * x + 0] = 0; ++ buf[16 * y + 2 * x + 1] = 0; ++ } ++ } ++ break; ++ case DRM_EPD_LUT_4BIT_PACKED: ++ counter = 0; ++ for (y=0; y < 1024 * lut->num_phases; y=y+64){ ++ for (x=0; x<32; x=x+8){ ++ int index; ++ u8 byte1, byte2, byte3, byte4; ++ ++ index = y + x; ++ byte1 = buf[index]; ++ byte2 = buf[index + 2]; ++ byte3 = buf[index + 4]; ++ byte4 = buf[index + 6]; ++ ++ buf[counter] = byte1 | (byte2 << 2) | ++ (byte3 << 4) | (byte4 << 6); ++ counter++; ++ } ++ } ++ // clear the rest of the lut table ++ for (y=counter; y < 1024 * lut->max_phases; y++){ ++ buf[y] = 0; ++ } ++ ++ break; ++ case DRM_EPD_LUT_5BIT: ++ memset(buf + 256 * lut->num_phases, 0, ++ 256 * (lut->max_phases - lut->num_phases)); ++ for (x = 256 * lut->num_phases; x--;) { ++ u8 byte = buf[x]; ++ ++ buf[4 * x + 0] = (byte >> 0) & 0x03; ++ buf[4 * x + 1] = (byte >> 2) & 0x03; ++ buf[4 * x + 2] = (byte >> 4) & 0x03; ++ buf[4 * x + 3] = (byte >> 6) & 0x03; ++ } ++ break; ++ case DRM_EPD_LUT_5BIT_PACKED: ++ /* Nothing to do. */ ++ break; ++ } ++} ++ ++static int drm_epd_lut_update(struct drm_epd_lut *lut, ++ int mode_index, int temp_index) ++{ ++ int ret; ++ ++ ret = pvi_wbf_get_lut(lut, mode_index, temp_index); ++ if (ret) ++ return ret; ++ ++ drm_epd_lut_convert(lut); ++ ++ return 0; ++} ++ ++/** ++ * drm_epd_lut_set_temperature - Update the LUT due to panel temperature change ++ * ++ * @lut: The LUT structure ++ * @temperateure: The current panel temperature in degrees Celsius ++ * ++ * Return: negative errno on failure, 1 if LUT was changed, 0 otherwise ++ */ ++int drm_epd_lut_set_temperature(struct drm_epd_lut *lut, ++ int temperature) ++{ ++ int temp_index; ++ int ret; ++ ++ temp_index = pvi_wbf_get_temp_index(lut->file, temperature); ++ if (temp_index < 0) ++ return -ENOENT; ++ ++ if (temp_index == lut->temp_index) ++ return 0; ++ ++ drm_dbg_core(lut->file->dev, "LUT temperature changed (%d)\n", ++ temperature); ++ ++ ret = drm_epd_lut_update(lut, lut->mode_index, temp_index); ++ if (ret) ++ return ret; ++ ++ lut->temp_index = temp_index; ++ ++ return 1; ++} ++EXPORT_SYMBOL(drm_epd_lut_set_temperature); ++ ++/** ++ * drm_epd_lut_set_waveform - Update the LUT due to waveform selection change ++ * ++ * @lut: The LUT structure ++ * @waveform: The desired waveform ++ * ++ * Return: negative errno on failure, 1 if LUT was changed, 0 otherwise ++ */ ++int drm_epd_lut_set_waveform(struct drm_epd_lut *lut, ++ enum drm_epd_waveform waveform) ++{ ++ int mode_index; ++ int ret; ++ ++ mode_index = pvi_wbf_get_mode_index(lut->file, waveform); ++ if (mode_index < 0) ++ return -ENOENT; ++ ++ if (mode_index == lut->mode_index) ++ return 0; ++ ++ drm_dbg_core(lut->file->dev, "LUT waveform changed (%d)\n", ++ waveform); ++ ++ ret = drm_epd_lut_update(lut, mode_index, lut->temp_index); ++ if (ret) ++ return ret; ++ ++ lut->mode_index = mode_index; ++ ++ return 1; ++} ++EXPORT_SYMBOL(drm_epd_lut_set_waveform); ++ ++static void drm_epd_lut_free(struct drm_device *dev, void *res) ++{ ++ struct drm_epd_lut *lut = res; ++ ++ vfree(lut->buf); ++} ++ ++/** ++ * drmm_epd_lut_init - Initialize a managed EPD LUT from a LUT file ++ * ++ * @dev: The DRM device owning this LUT ++ * @lut: The LUT to initialize ++ * @file_name: The file name of the waveform firmware ++ * @format: The LUT buffer format needed by the driver ++ * @max_phases: The maximum number of waveform phases supported by the driver ++ * ++ * Return: negative errno on failure, 0 otherwise ++ */ ++int drmm_epd_lut_init(struct drm_epd_lut_file *file, ++ struct drm_epd_lut *lut, ++ enum drm_epd_lut_format format, ++ unsigned int max_phases) ++{ ++ size_t max_order; ++ int ret; ++ ++ /* Allocate a buffer large enough to convert the LUT in place. */ ++ max_order = max( ++ drm_epd_lut_size_shift(file->mode_info->format), ++ drm_epd_lut_size_shift(format) ++ ); ++ /* ++ * due to the x/y switch we need to first convert to DRM_EPD_LUT_5BIT ++ * */ ++ /* max_order = drm_epd_lut_size_shift(DRM_EPD_LUT_5BIT); */ ++ // max_phases: 256 ++ lut->buf = vmalloc(max_phases << max_order); ++ if (!lut->buf) ++ return -ENOMEM; ++ memset(lut->buf, 0, max_phases << max_order); ++ ++ ret = drmm_add_action_or_reset(file->dev, drm_epd_lut_free, lut); ++ if (ret) ++ return ret; ++ ++ lut->file = file; ++ lut->format = format; ++ lut->max_phases = max_phases; ++ lut->num_phases = 0; ++ ++ /* Set sane initial values for the waveform and temperature. */ ++ lut->mode_index = pvi_wbf_get_mode_index(file, DRM_EPD_WF_RESET); ++ if (lut->mode_index < 0) ++ return -ENOENT; ++ ++ lut->temp_index = pvi_wbf_get_temp_index(file, 25); ++ if (lut->temp_index < 0) ++ return -ENOENT; ++ ++ ret = drm_epd_lut_update(lut, lut->mode_index, lut->temp_index); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++EXPORT_SYMBOL(drmm_epd_lut_init); ++ ++MODULE_AUTHOR("Samuel Holland "); ++MODULE_DESCRIPTION("DRM EPD waveform LUT library"); ++MODULE_LICENSE("Dual MIT/GPL"); +diff --git a/drivers/gpu/drm/gud/Kconfig b/drivers/gpu/drm/gud/Kconfig +index b4d213694..292bdd499 100644 +--- a/drivers/gpu/drm/gud/Kconfig ++++ b/drivers/gpu/drm/gud/Kconfig +@@ -13,3 +13,9 @@ config DRM_GUD + adapters. + + If M is selected the module will be called gud. ++ ++config DRM_GUD_GADGET ++ tristate ++ depends on DRM ++ select LZ4_DECOMPRESS ++ select BACKLIGHT_CLASS_DEVICE +diff --git a/drivers/gpu/drm/gud/Makefile b/drivers/gpu/drm/gud/Makefile +index 68a1c622c..685003e79 100644 +--- a/drivers/gpu/drm/gud/Makefile ++++ b/drivers/gpu/drm/gud/Makefile +@@ -2,3 +2,4 @@ + + gud-y := gud_drv.o gud_pipe.o gud_connector.o + obj-$(CONFIG_DRM_GUD) += gud.o ++obj-$(CONFIG_DRM_GUD_GADGET) += gud_gadget.o +diff --git a/drivers/gpu/drm/gud/gud_gadget.c b/drivers/gpu/drm/gud/gud_gadget.c +new file mode 100644 +index 000000000..d82941bdf +--- /dev/null ++++ b/drivers/gpu/drm/gud/gud_gadget.c +@@ -0,0 +1,1386 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright 2020 Noralf Trønnes ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "gud_internal.h" ++ ++/* ++ * Concurrency: ++ * Calls into this module from f_gud_drm are serialized and run in process ++ * context except gud_gadget_ctrl_get() which is run in interrupt context. ++ * ++ * Termination: ++ * A DRM client can not release itself, only the DRM driver which resources the ++ * client uses can do that. ++ * This means that there are 2 paths to stop the gadget function: ++ * - Unregistering the DRM driver (module unload) ++ * - Disabling the USB gadget (configfs unbind) ++ * ++ * A use counter protects the gadget should the client go away. A kref is used ++ * to control the release of the gud_gadget structure shared by the 2 actors. ++ * ++ * Backlight: ++ * If there's a backlight device it's attached to the first connector. ++ */ ++ ++struct gud_gadget_connector { ++ struct drm_connector *connector; ++ ++ const struct gud_property_req *properties; ++ unsigned int num_properties; ++ const char *tv_mode_enum_names; ++ unsigned int num_tv_mode_enum_names; ++ struct backlight_device *backlight; ++ ++ spinlock_t lock; /* Protects the following members: */ ++ enum drm_connector_status status; ++ unsigned int width_mm; ++ unsigned int height_mm; ++ struct gud_display_mode_req *modes; ++ unsigned int num_modes; ++ void *edid; ++ size_t edid_len; ++ bool changed; ++}; ++ ++struct gud_gadget { ++ struct kref refcount; ++ refcount_t usecnt; ++ struct drm_client_dev client; ++ struct backlight_device *backlight; ++ u8 compression; ++ ++ const u8 *formats; ++ unsigned int format_count; ++ ++ const struct gud_property_req *properties; ++ unsigned int num_properties; ++ ++ struct gud_gadget_connector *connectors; ++ unsigned int connector_count; ++ ++ struct drm_rect set_buffer_rect; ++ u32 set_buffer_length; ++ u8 set_buffer_compression; ++ u32 set_buffer_compressed_length; ++ ++ struct drm_client_buffer *buffer; ++ struct drm_client_buffer *buffer_check; ++ u8 brightness; ++ bool check_ok; ++ ++ size_t max_buffer_size; ++ void *work_buf; ++ ++ struct work_struct flush_worker; ++ struct drm_rect flush_rect; ++}; ++ ++static int gud_gadget_probe_connector(struct gud_gadget_connector *gconn) ++{ ++ struct drm_connector *connector = gconn->connector; ++ struct gud_display_mode_req *modes = NULL; ++ struct drm_device *drm = connector->dev; ++ struct drm_display_mode *mode; ++ void *edid_data, *edid = NULL; ++ unsigned int num_modes = 0; ++ size_t edid_len = 0; ++ unsigned long flags; ++ unsigned int i = 0; ++ int ret = 0; ++ ++ mutex_lock(&drm->mode_config.mutex); ++ ++ connector->funcs->fill_modes(connector, ++ drm->mode_config.max_width, ++ drm->mode_config.max_height); ++ ++ list_for_each_entry(mode, &connector->modes, head) ++ num_modes++; ++ ++ if (!num_modes) ++ goto update; ++ ++ modes = kmalloc_array(num_modes, sizeof(*modes), GFP_KERNEL); ++ if (!modes) { ++ ret = -ENOMEM; ++ num_modes = 0; ++ goto update; ++ } ++ ++ list_for_each_entry(mode, &connector->modes, head) ++ gud_from_display_mode(&modes[i++], mode); ++ ++ if (!connector->edid_blob_ptr) ++ goto update; ++ ++ edid_data = connector->edid_blob_ptr->data; ++ edid_len = connector->edid_blob_ptr->length; ++ if (!edid_data || !edid_len) { ++ edid_len = 0; ++ goto update; ++ } ++ ++ edid = kmemdup(edid_data, edid_len, GFP_KERNEL); ++ if (!edid) { ++ ret = -ENOMEM; ++ edid_len = 0; ++ } ++ ++update: ++ spin_lock_irqsave(&gconn->lock, flags); ++ if (gconn->status != connector->status || gconn->num_modes != num_modes || ++ gconn->edid_len != edid_len || ++ (gconn->modes && modes && memcmp(gconn->modes, modes, num_modes * sizeof(*modes))) || ++ (gconn->edid && edid && memcmp(gconn->edid, edid, edid_len))) ++ gconn->changed = true; ++ swap(gconn->modes, modes); ++ gconn->num_modes = num_modes; ++ swap(gconn->edid, edid); ++ gconn->edid_len = edid_len; ++ gconn->width_mm = connector->display_info.width_mm; ++ gconn->height_mm = connector->display_info.height_mm; ++ gconn->status = connector->status; ++ spin_unlock_irqrestore(&gconn->lock, flags); ++ ++ mutex_unlock(&drm->mode_config.mutex); ++ ++ kfree(edid); ++ kfree(modes); ++ ++ return ret; ++} ++ ++static void gud_gadget_probe_connectors(struct gud_gadget *gdg) ++{ ++ unsigned int i; ++ ++ for (i = 0; i < gdg->connector_count; i++) ++ gud_gadget_probe_connector(&gdg->connectors[i]); ++} ++ ++static bool gud_gadget_check_buffer(struct gud_gadget *gdg, struct drm_client_buffer *buffer, ++ struct drm_display_mode *mode, u32 format) ++{ ++ struct drm_framebuffer *fb; ++ ++ if (!buffer) ++ return false; ++ ++ fb = buffer->fb; ++ ++ return fb->format->format == format && ++ fb->width == mode->hdisplay && fb->height == mode->vdisplay; ++} ++ ++static bool gud_gadget_set_connector_property(struct drm_client_dev *client, ++ struct drm_connector *connector, ++ u16 prop, u64 val, int *ret) ++{ ++ struct drm_mode_config *config = &connector->dev->mode_config; ++ struct drm_property *property; ++ ++ switch (prop) { ++ case GUD_PROPERTY_TV_LEFT_MARGIN: ++ property = config->tv_left_margin_property; ++ break; ++ case GUD_PROPERTY_TV_RIGHT_MARGIN: ++ property = config->tv_right_margin_property; ++ break; ++ case GUD_PROPERTY_TV_TOP_MARGIN: ++ property = config->tv_top_margin_property; ++ break; ++ case GUD_PROPERTY_TV_BOTTOM_MARGIN: ++ property = config->tv_bottom_margin_property; ++ break; ++ case GUD_PROPERTY_TV_MODE: ++ property = config->tv_mode_property; ++ break; ++ case GUD_PROPERTY_TV_BRIGHTNESS: ++ property = config->tv_brightness_property; ++ break; ++ case GUD_PROPERTY_TV_CONTRAST: ++ property = config->tv_contrast_property; ++ break; ++ case GUD_PROPERTY_TV_FLICKER_REDUCTION: ++ property = config->tv_flicker_reduction_property; ++ break; ++ case GUD_PROPERTY_TV_OVERSCAN: ++ property = config->tv_overscan_property; ++ break; ++ case GUD_PROPERTY_TV_SATURATION: ++ property = config->tv_saturation_property; ++ break; ++ case GUD_PROPERTY_TV_HUE: ++ property = config->tv_hue_property; ++ break; ++ default: ++ return false; ++ } ++ ++ *ret = drm_client_modeset_set_property(client, &connector->base, property, val); ++ ++ return true; ++} ++ ++static int gud_gadget_req_set_state_check(struct gud_gadget *gdg, unsigned int index, ++ const struct gud_state_req *req, size_t size) ++{ ++ struct drm_client_dev *client = &gdg->client; ++ struct drm_client_buffer *buffer; ++ struct drm_connector *connector; ++ unsigned int i, num_properties; ++ struct drm_display_mode mode; ++ u32 format; ++ int ret; ++ ++ flush_work(&gdg->flush_worker); ++ ++ if (index || size < sizeof(*req)) ++ return -EPROTO; ++ ++ if ((size - sizeof(*req)) % sizeof(*req->properties)) ++ return -EPROTO; ++ ++ num_properties = (size - sizeof(*req)) / sizeof(*req->properties); ++ ++ memset(&mode, 0, sizeof(mode)); ++ gud_to_display_mode(&mode, &req->mode); ++ ++ gdg->check_ok = false; ++ ++ if (!mode.hdisplay || !mode.vdisplay || req->format <= GUD_PIXEL_FORMAT_R1) ++ return -EINVAL; ++ ++ format = gud_to_fourcc(req->format); ++ if (!format) ++ return -EINVAL; ++ ++ if (req->connector >= gdg->connector_count) ++ return -EINVAL; ++ ++ connector = gdg->connectors[req->connector].connector; ++ ++ if (gdg->buffer_check) { ++ drm_client_buffer_delete(gdg->buffer_check); ++ gdg->buffer_check = NULL; ++ } ++ ++ if (!gud_gadget_check_buffer(gdg, gdg->buffer, &mode, format)) { ++ buffer = drm_client_buffer_create_dumb(client, mode.hdisplay, mode.vdisplay, ++ format); ++ if (IS_ERR(buffer)) ++ return PTR_ERR(buffer); ++ ++ gdg->buffer_check = buffer; ++ } else { ++ buffer = gdg->buffer; ++ } ++ ++ ret = drm_client_modeset_set(client, connector, &mode, buffer->fb); ++ if (ret) ++ return ret; ++ ++ for (i = 0; i < num_properties; i++) { ++ u16 prop = le16_to_cpu(req->properties[i].prop); ++ u64 val = le64_to_cpu(req->properties[i].val); ++ ++ if (gud_gadget_set_connector_property(client, connector, prop, val, &ret)) { ++ if (ret) ++ return ret; ++ continue; ++ } ++ ++ switch (prop) { ++ case GUD_PROPERTY_BACKLIGHT_BRIGHTNESS: ++ if (val > 100) ++ return -EINVAL; ++ gdg->brightness = val; ++ break; ++ case GUD_PROPERTY_ROTATION: ++ /* DRM UAPI matches the protocol so use value directly */ ++ ret = drm_client_modeset_set_rotation(client, val); ++ break; ++ default: ++ pr_err("%s: Unknown property: %u\n", __func__, prop); ++ continue; ++ } ++ ++ if (ret) ++ return ret; ++ } ++ ++ ret = drm_client_modeset_check(&gdg->client); ++ if (ret) ++ return ret; ++ ++ gdg->check_ok = true; ++ ++ return 0; ++} ++ ++static int gud_gadget_req_set_state_commit(struct gud_gadget *gdg, unsigned int index, size_t size) ++{ ++ int ret; ++ ++ if (index || size) ++ return -EPROTO; ++ ++ if (!gdg->check_ok) ++ return -EINVAL; ++ ++ if (gdg->backlight) { ++ int val, max_brightness = gdg->backlight->props.max_brightness; ++ ++ val = DIV64_U64_ROUND_UP(gdg->brightness * max_brightness, 100); ++ ret = backlight_device_set_brightness(gdg->backlight, val); ++ if (ret) ++ return ret; ++ } ++ ++ ret = drm_client_modeset_commit(&gdg->client); ++ if (ret) ++ return ret; ++ ++ if (gdg->buffer_check) { ++ drm_client_buffer_delete(gdg->buffer); ++ gdg->buffer = gdg->buffer_check; ++ gdg->buffer_check = NULL; ++ } ++ ++ return 0; ++} ++ ++/* ++ * Use a worker so the next frame can be received while the current frame is being flushed out. ++ * SPI panels often use 50ms to flush a full frame. ++ */ ++static void gud_gadget_flush_worker(struct work_struct *work) ++{ ++ struct gud_gadget *gdg = container_of(work, struct gud_gadget, flush_worker); ++ struct drm_client_buffer *buffer = gdg->buffer ? gdg->buffer : gdg->buffer_check; ++ int ret; ++ ++ ret = drm_client_buffer_flush(buffer, &gdg->flush_rect); ++ if (ret) ++ pr_debug("%s: drm_client_buffer_flush: error=%d\n", __func__, ret); ++} ++ ++static size_t gud_gadget_write_buffer_memcpy(struct drm_client_buffer *buffer, ++ const void *src, size_t len, ++ struct drm_rect *rect) ++{ ++ unsigned int cpp = buffer->fb->format->cpp[0]; ++ size_t src_pitch = drm_rect_width(rect) * cpp; ++ size_t dst_pitch = buffer->fb->pitches[0]; ++ struct iosys_map dst; ++ unsigned int y; ++ int ret; ++ ++ ret = drm_client_buffer_vmap(buffer, &dst); ++ if (ret) ++ return len; ++ ++ iosys_map_incr(&dst, rect->y1 * dst_pitch + rect->x1 * cpp); ++ ++ for (y = 0; y < drm_rect_height(rect) && len; y++) { ++ src_pitch = min(src_pitch, len); ++ iosys_map_memcpy_to(&dst, 0, src, src_pitch); ++ iosys_map_incr(&dst, dst_pitch); ++ src += src_pitch; ++ len -= src_pitch; ++ } ++ ++ drm_client_buffer_vunmap(buffer); ++ ++ return len; ++} ++ ++static bool gud_gadget_check_rect(struct drm_client_buffer *buffer, struct drm_rect *rect) ++{ ++ return buffer->fb && rect->x1 < rect->x2 && rect->y1 < rect->y2 && ++ rect->x2 <= buffer->fb->width && rect->y2 <= buffer->fb->height; ++} ++ ++int gud_gadget_write_buffer(struct gud_gadget *gdg, const void *buf, size_t len) ++{ ++ struct drm_client_buffer *buffer = gdg->buffer ? gdg->buffer : gdg->buffer_check; ++ struct drm_rect *rect = &gdg->set_buffer_rect; ++ u8 compression = gdg->set_buffer_compression; ++ struct drm_framebuffer *fb; ++ size_t remain; ++ int ret; ++ ++ pr_debug("%s: len=%zu compression=0x%x\n", __func__, len, compression); ++ ++ if (!refcount_inc_not_zero(&gdg->usecnt)) ++ return -ENODEV; ++ ++ if (WARN_ON_ONCE(!buffer)) { ++ ret = -ENOMEM; ++ goto out; ++ } ++ ++ if (!gud_gadget_check_rect(buffer, rect)) { ++ pr_err("%s: Rectangle doesn't fit: " DRM_RECT_FMT "\n", ++ __func__, DRM_RECT_ARG(rect)); ++ ret = -EINVAL; ++ goto out; ++ } ++ ++ fb = buffer->fb; ++ ++ if (fb->funcs->dirty) ++ flush_work(&gdg->flush_worker); ++ ++ if (compression & GUD_COMPRESSION_LZ4) { ++ if (len != gdg->set_buffer_compressed_length) { ++ pr_err("%s: Buffer compressed len differs: %zu != %u\n", ++ __func__, len, gdg->set_buffer_compressed_length); ++ ret = -EINVAL; ++ goto out; ++ } ++ ++ ret = LZ4_decompress_safe(buf, gdg->work_buf, len, gdg->max_buffer_size); ++ if (ret < 0) { ++ pr_err("%s: Failed to decompress buffer\n", __func__); ++ ret = -EIO; ++ goto out; ++ } ++ ++ buf = gdg->work_buf; ++ len = ret; ++ } ++ ++ if (len != gdg->set_buffer_length) { ++ pr_err("%s: Buffer len differs: %zu != %u\n", ++ __func__, len, gdg->set_buffer_length); ++ ret = -EINVAL; ++ goto out; ++ } ++ ++ remain = gud_gadget_write_buffer_memcpy(buffer, buf, len, rect); ++ if (remain) { ++ pr_err("%s: Failed to write buffer: remain=%zu\n", __func__, remain); ++ ret = -EIO; ++ goto out; ++ } ++ ++ if (fb->funcs->dirty) { ++ gdg->flush_rect = *rect; ++ queue_work(system_long_wq, &gdg->flush_worker); ++ } ++ ++ ret = 0; ++out: ++ refcount_dec(&gdg->usecnt); ++ ++ return ret; ++} ++EXPORT_SYMBOL(gud_gadget_write_buffer); ++ ++int gud_gadget_req_set_buffer(struct gud_gadget *gdg, const struct gud_set_buffer_req *req) ++{ ++ u32 compressed_length = le32_to_cpu(req->compressed_length); ++ u32 length = le32_to_cpu(req->length); ++ struct drm_client_buffer *buffer; ++ struct drm_rect rect; ++ int ret = 0; ++ u64 pitch; ++ ++ if (!refcount_inc_not_zero(&gdg->usecnt)) ++ return -ENODEV; ++ ++ buffer = gdg->buffer ? gdg->buffer : gdg->buffer_check; ++ if (!buffer) { ++ ret = -ENOENT; ++ goto out; ++ } ++ ++ drm_rect_init(&rect, le32_to_cpu(req->x), le32_to_cpu(req->y), ++ le32_to_cpu(req->width), le32_to_cpu(req->height)); ++ ++ pr_debug("%s: " DRM_RECT_FMT "\n", __func__, DRM_RECT_ARG(&rect)); ++ ++ if (!gud_gadget_check_rect(buffer, &rect)) { ++ ret = -EINVAL; ++ goto out; ++ } ++ ++ if (req->compression & ~GUD_COMPRESSION_LZ4) { ++ ret = -EINVAL; ++ goto out; ++ } ++ ++ gdg->set_buffer_rect = rect; ++ gdg->set_buffer_length = length; ++ ++ if (req->compression) { ++ if (!compressed_length) { ++ ret = -EINVAL; ++ goto out; ++ } ++ gdg->set_buffer_compression = req->compression; ++ gdg->set_buffer_compressed_length = compressed_length; ++ length = compressed_length; ++ } else { ++ gdg->set_buffer_compression = 0; ++ gdg->set_buffer_compressed_length = 0; ++ } ++ ++ pitch = drm_format_info_min_pitch(buffer->fb->format, 0, drm_rect_width(&rect)); ++ if (length > (drm_rect_height(&rect) * pitch)) { ++ pr_err("%s: Buffer is too big for rectangle: " DRM_RECT_FMT " len=%u\n", ++ __func__, DRM_RECT_ARG(&rect), length); ++ ret = -EINVAL; ++ goto out; ++ } ++out: ++ refcount_dec(&gdg->usecnt); ++ ++ return ret ? ret : length; ++} ++EXPORT_SYMBOL(gud_gadget_req_set_buffer); ++ ++static void gud_gadget_delete_buffers(struct gud_gadget *gdg) ++{ ++ drm_client_buffer_delete(gdg->buffer_check); ++ drm_client_buffer_delete(gdg->buffer); ++ gdg->buffer_check = NULL; ++ gdg->buffer = NULL; ++} ++ ++int gud_gadget_disable_pipe(struct gud_gadget *gdg) ++{ ++ int ret; ++ ++ cancel_work_sync(&gdg->flush_worker); ++ ret = drm_client_modeset_disable(&gdg->client); ++ gud_gadget_delete_buffers(gdg); ++ ++ return ret; ++} ++EXPORT_SYMBOL(gud_gadget_disable_pipe); ++ ++static int gud_gadget_req_get_descriptor(struct gud_gadget *gdg, unsigned int index, ++ void *data, size_t size) ++{ ++ struct drm_device *drm = gdg->client.dev; ++ struct gud_display_descriptor_req desc; ++ ++ if (index || !size) ++ return -EPROTO; ++ ++ desc.magic = cpu_to_le32(GUD_DISPLAY_MAGIC); ++ desc.version = 1; ++ desc.max_buffer_size = cpu_to_le32(gdg->max_buffer_size); ++ desc.flags = cpu_to_le32(GUD_DISPLAY_FLAG_STATUS_ON_SET); ++ desc.compression = GUD_COMPRESSION_LZ4 & gdg->compression; ++ ++ desc.min_width = cpu_to_le32(drm->mode_config.min_width); ++ desc.max_width = cpu_to_le32(drm->mode_config.max_width); ++ desc.min_height = cpu_to_le32(drm->mode_config.min_height); ++ desc.max_height = cpu_to_le32(drm->mode_config.max_height); ++ ++ size = min(size, sizeof(desc)); ++ memcpy(data, &desc, size); ++ ++ return size; ++} ++ ++static int gud_gadget_req_get_formats(struct gud_gadget *gdg, unsigned int index, ++ void *data, size_t size) ++{ ++ if (index || !size) ++ return -EPROTO; ++ ++ size = min_t(size_t, size, gdg->format_count); ++ memcpy(data, gdg->formats, size); ++ ++ return size; ++} ++ ++static int gud_gadget_req_get_properties(struct gud_gadget *gdg, unsigned int index, ++ void *data, size_t size) ++{ ++ size = rounddown(size, sizeof(*gdg->properties)); ++ if (index || !size) ++ return -EPROTO; ++ ++ size = min(size, gdg->num_properties * sizeof(*gdg->properties)); ++ memcpy(data, gdg->properties, size); ++ ++ return size; ++} ++ ++static void gud_gadget_req_get_connector(struct gud_gadget *gdg, unsigned int index, ++ struct gud_connector_descriptor_req *desc) ++{ ++ struct gud_gadget_connector *gconn; ++ u32 flags; ++ ++ memset(desc, 0, sizeof(*desc)); ++ ++ gconn = &gdg->connectors[index]; ++ ++ switch (gconn->connector->connector_type) { ++ case DRM_MODE_CONNECTOR_VGA: ++ desc->connector_type = GUD_CONNECTOR_TYPE_VGA; ++ break; ++ case DRM_MODE_CONNECTOR_DVII: ++ fallthrough; ++ case DRM_MODE_CONNECTOR_DVID: ++ fallthrough; ++ case DRM_MODE_CONNECTOR_DVIA: ++ desc->connector_type = GUD_CONNECTOR_TYPE_DVI; ++ break; ++ case DRM_MODE_CONNECTOR_Composite: ++ desc->connector_type = GUD_CONNECTOR_TYPE_COMPOSITE; ++ break; ++ case DRM_MODE_CONNECTOR_SVIDEO: ++ desc->connector_type = GUD_CONNECTOR_TYPE_SVIDEO; ++ break; ++ case DRM_MODE_CONNECTOR_Component: ++ desc->connector_type = GUD_CONNECTOR_TYPE_COMPONENT; ++ break; ++ case DRM_MODE_CONNECTOR_DisplayPort: ++ desc->connector_type = GUD_CONNECTOR_TYPE_DISPLAYPORT; ++ break; ++ case DRM_MODE_CONNECTOR_HDMIA: ++ fallthrough; ++ case DRM_MODE_CONNECTOR_HDMIB: ++ desc->connector_type = GUD_CONNECTOR_TYPE_HDMI; ++ break; ++ default: ++ desc->connector_type = GUD_CONNECTOR_TYPE_PANEL; ++ break; ++ }; ++ ++ flags = GUD_CONNECTOR_FLAGS_POLL_STATUS; ++ if (gconn->connector->interlace_allowed) ++ flags |= GUD_CONNECTOR_FLAGS_INTERLACE; ++ if (gconn->connector->doublescan_allowed) ++ flags |= GUD_CONNECTOR_FLAGS_DOUBLESCAN; ++ desc->flags = cpu_to_le32(flags); ++} ++ ++static int gud_gadget_req_get_connectors(struct gud_gadget *gdg, unsigned int index, ++ struct gud_connector_descriptor_req *descs, size_t size) ++{ ++ unsigned int i, num_connectors; ++ ++ size = rounddown(size, sizeof(*descs)); ++ if (index || !size) ++ return -EPROTO; ++ ++ num_connectors = min_t(size_t, size / sizeof(*descs), gdg->connector_count); ++ ++ for (i = 0; i < num_connectors; i++) ++ gud_gadget_req_get_connector(gdg, i, &descs[i]); ++ ++ return num_connectors * sizeof(*descs); ++} ++ ++static struct gud_gadget_connector * ++gud_gadget_get_gconn(struct gud_gadget *gdg, unsigned int index) ++{ ++ if (index >= gdg->connector_count) ++ return NULL; ++ ++ return &gdg->connectors[index]; ++} ++ ++static int gud_gadget_req_get_connector_properties(struct gud_gadget *gdg, unsigned int index, ++ void *data, size_t size) ++{ ++ struct gud_gadget_connector *gconn; ++ ++ size = rounddown(size, sizeof(*gconn->properties)); ++ if (!size) ++ return -EPROTO; ++ ++ gconn = gud_gadget_get_gconn(gdg, index); ++ if (!gconn) ++ return -EINVAL; ++ ++ size = min(size, gconn->num_properties * sizeof(*gconn->properties)); ++ memcpy(data, gconn->properties, size); ++ ++ return size; ++} ++ ++static int gud_gadget_req_get_connector_tv_mode_values(struct gud_gadget *gdg, unsigned int index, ++ void *data, size_t size) ++{ ++ struct gud_gadget_connector *gconn; ++ ++ size = rounddown(size, GUD_CONNECTOR_TV_MODE_NAME_LEN); ++ if (!size) ++ return -EPROTO; ++ ++ gconn = gud_gadget_get_gconn(gdg, index); ++ if (!gconn) ++ return -EINVAL; ++ ++ size = min_t(size_t, size, gconn->num_tv_mode_enum_names * GUD_CONNECTOR_TV_MODE_NAME_LEN); ++ memcpy(data, gconn->tv_mode_enum_names, size); ++ ++ return size; ++} ++ ++static int gud_gadget_req_get_connector_status(struct gud_gadget *gdg, unsigned int index, ++ u8 *status, size_t size) ++{ ++ struct gud_gadget_connector *gconn; ++ unsigned long flags; ++ ++ if (size != sizeof(*status)) ++ return -EPROTO; ++ ++ gconn = gud_gadget_get_gconn(gdg, index); ++ if (!gconn) ++ return -EINVAL; ++ ++ spin_lock_irqsave(&gconn->lock, flags); ++ ++ switch (gconn->status) { ++ case connector_status_disconnected: ++ *status = GUD_CONNECTOR_STATUS_DISCONNECTED; ++ break; ++ case connector_status_connected: ++ *status = GUD_CONNECTOR_STATUS_CONNECTED; ++ break; ++ default: ++ *status = GUD_CONNECTOR_STATUS_UNKNOWN; ++ break; ++ ++ }; ++ ++ if (gconn->changed) { ++ *status |= GUD_CONNECTOR_STATUS_CHANGED; ++ gconn->changed = false; ++ } ++ ++ spin_unlock_irqrestore(&gconn->lock, flags); ++ ++ return size; ++} ++ ++static int gud_gadget_req_get_connector_modes(struct gud_gadget *gdg, unsigned int index, ++ void *data, size_t size) ++{ ++ struct gud_gadget_connector *gconn; ++ unsigned long flags; ++ ++ size = rounddown(size, sizeof(*gconn->modes)); ++ if (!size) ++ return -EPROTO; ++ ++ gconn = gud_gadget_get_gconn(gdg, index); ++ if (!gconn) ++ return -EINVAL; ++ ++ spin_lock_irqsave(&gconn->lock, flags); ++ size = min(size, gconn->num_modes * sizeof(*gconn->modes)); ++ memcpy(data, gconn->modes, size); ++ spin_unlock_irqrestore(&gconn->lock, flags); ++ ++ return size; ++} ++ ++static int gud_gadget_req_get_connector_edid(struct gud_gadget *gdg, unsigned int index, ++ void *data, size_t size) ++{ ++ struct gud_gadget_connector *gconn; ++ unsigned long flags; ++ ++ size = rounddown(size, EDID_LENGTH); ++ if (!size) ++ return -EPROTO; ++ ++ gconn = gud_gadget_get_gconn(gdg, index); ++ if (!gconn) ++ return -EINVAL; ++ ++ spin_lock_irqsave(&gconn->lock, flags); ++ size = min(size, gconn->edid_len); ++ memcpy(data, gconn->edid, size); ++ spin_unlock_irqrestore(&gconn->lock, flags); ++ ++ return size; ++} ++ ++/* This runs in interrupt context */ ++int gud_gadget_req_get(struct gud_gadget *gdg, u8 request, u16 index, void *data, size_t size) ++{ ++ int ret; ++ ++ pr_debug("%s: request=0x%x index=%u size=%zu\n", __func__, request, index, size); ++ ++ if (!refcount_inc_not_zero(&gdg->usecnt)) ++ return -ENODEV; ++ ++ switch (request) { ++ case GUD_REQ_GET_DESCRIPTOR: ++ ret = gud_gadget_req_get_descriptor(gdg, index, data, size); ++ break; ++ case GUD_REQ_GET_FORMATS: ++ ret = gud_gadget_req_get_formats(gdg, index, data, size); ++ break; ++ case GUD_REQ_GET_PROPERTIES: ++ ret = gud_gadget_req_get_properties(gdg, index, data, size); ++ break; ++ case GUD_REQ_GET_CONNECTORS: ++ ret = gud_gadget_req_get_connectors(gdg, index, data, size); ++ break; ++ case GUD_REQ_GET_CONNECTOR_PROPERTIES: ++ ret = gud_gadget_req_get_connector_properties(gdg, index, data, size); ++ break; ++ case GUD_REQ_GET_CONNECTOR_TV_MODE_VALUES: ++ ret = gud_gadget_req_get_connector_tv_mode_values(gdg, index, data, size); ++ break; ++ case GUD_REQ_GET_CONNECTOR_STATUS: ++ ret = gud_gadget_req_get_connector_status(gdg, index, data, size); ++ break; ++ case GUD_REQ_GET_CONNECTOR_MODES: ++ ret = gud_gadget_req_get_connector_modes(gdg, index, data, size); ++ break; ++ case GUD_REQ_GET_CONNECTOR_EDID: ++ ret = gud_gadget_req_get_connector_edid(gdg, index, data, size); ++ break; ++ default: ++ ret = -EOPNOTSUPP; ++ break; ++ } ++ ++ refcount_dec(&gdg->usecnt); ++ ++ return ret; ++} ++EXPORT_SYMBOL(gud_gadget_req_get); ++ ++static int gud_gadget_req_set_connector_force_detect(struct gud_gadget *gdg, u16 index, size_t size) ++{ ++ struct gud_gadget_connector *gconn; ++ ++ if (size) ++ return -EPROTO; ++ ++ gconn = gud_gadget_get_gconn(gdg, index); ++ if (!gconn) ++ return -EINVAL; ++ ++ return gud_gadget_probe_connector(gconn); ++} ++ ++static int gud_gadget_req_set_controller_enable(struct gud_gadget *gdg, u16 index, ++ const u8 *enable, size_t size) ++{ ++ if (index || size != sizeof(*enable)) ++ return -EPROTO; ++ ++ return *enable ? 0 : gud_gadget_disable_pipe(gdg); ++} ++ ++static int gud_gadget_req_set_display_enable(struct gud_gadget *gdg, u16 index, ++ const u8 *enable, size_t size) ++{ ++ if (index || size != sizeof(*enable)) ++ return -EPROTO; ++ ++ return drm_client_modeset_dpms(&gdg->client, ++ *enable ? DRM_MODE_DPMS_ON : DRM_MODE_DPMS_OFF); ++} ++ ++int gud_gadget_req_set(struct gud_gadget *gdg, u8 request, u16 index, const void *data, size_t size) ++{ ++ int ret; ++ ++ pr_debug("%s: request=0x%x index=%u size=%zu\n", __func__, request, index, size); ++ ++ if (!refcount_inc_not_zero(&gdg->usecnt)) ++ return -ENODEV; ++ ++ switch (request) { ++ case GUD_REQ_SET_CONNECTOR_FORCE_DETECT: ++ ret = gud_gadget_req_set_connector_force_detect(gdg, index, size); ++ break; ++ case GUD_REQ_SET_STATE_CHECK: ++ ret = gud_gadget_req_set_state_check(gdg, index, data, size); ++ break; ++ case GUD_REQ_SET_STATE_COMMIT: ++ ret = gud_gadget_req_set_state_commit(gdg, index, size); ++ break; ++ case GUD_REQ_SET_CONTROLLER_ENABLE: ++ ret = gud_gadget_req_set_controller_enable(gdg, index, data, size); ++ break; ++ case GUD_REQ_SET_DISPLAY_ENABLE: ++ ret = gud_gadget_req_set_display_enable(gdg, index, data, size); ++ break; ++ default: ++ ret = -EOPNOTSUPP; ++ break; ++ } ++ ++ refcount_dec(&gdg->usecnt); ++ ++ return ret; ++} ++EXPORT_SYMBOL(gud_gadget_req_set); ++ ++static int gud_gadget_get_formats(struct gud_gadget *gdg, u8 *max_cpp, u8 *format_filter) ++{ ++ struct drm_device *drm = gdg->client.dev; ++ struct drm_plane *plane; ++ unsigned int i; ++ u8 *formats; ++ int ret; ++ ++ *max_cpp = 0; ++ ++ drm_for_each_plane(plane, drm) { ++ if (plane->type == DRM_PLANE_TYPE_PRIMARY) ++ break; ++ } ++ ++ formats = kmalloc(plane->format_count, GFP_KERNEL); ++ if (!formats) ++ return -ENOMEM; ++ ++ for (i = 0; i < plane->format_count; i++) { ++ const struct drm_format_info *info; ++ u8 format; ++ ++ info = drm_format_info(plane->format_types[i]); ++ if (info->num_planes != 1) ++ continue; ++ ++ format = gud_from_fourcc(info->format); ++ if (!format) ++ continue; ++ ++ if (format_filter[0]) { ++ bool found = false; ++ unsigned int j; ++ ++ for (j = 0; j < GUD_FORMATS_MAX_NUM; j++) { ++ if (!format_filter[j]) ++ break; ++ if (format_filter[j] == format) { ++ found = true; ++ break; ++ } ++ } ++ if (!found) ++ continue; ++ } ++ ++ if (*max_cpp < info->cpp[0]) ++ *max_cpp = info->cpp[0]; ++ ++ formats[gdg->format_count++] = format; ++ } ++ ++ if (!gdg->format_count) { ++ ret = -ENOENT; ++ goto err_free; ++ } ++ ++ gdg->formats = formats; ++ ++ return 0; ++ ++err_free: ++ kfree(formats); ++ ++ return ret; ++} ++ ++static int gud_gadget_get_rotation_property(struct drm_device *drm, u16 *prop, u64 *val) ++{ ++ struct drm_property_enum *prop_enum; ++ struct drm_plane *plane; ++ unsigned int num_props = 0; ++ u16 bitmask = 0; ++ ++ drm_for_each_plane(plane, drm) { ++ if (plane->type == DRM_PLANE_TYPE_PRIMARY) ++ break; ++ } ++ ++ if (!plane->rotation_property) ++ return 0; ++ ++ list_for_each_entry(prop_enum, &plane->rotation_property->enum_list, head) { ++ num_props++; ++ bitmask |= BIT(prop_enum->value); ++ } ++ ++ *prop = GUD_PROPERTY_ROTATION; ++ /* DRM UAPI matches the protocol so use value directly */ ++ *val = bitmask; ++ ++ return 1; ++} ++ ++static int gud_gadget_get_properties(struct gud_gadget *gdg) ++{ ++ struct gud_property_req *properties; ++ u16 prop; ++ u64 val; ++ int ret; ++ ++ ret = gud_gadget_get_rotation_property(gdg->client.dev, &prop, &val); ++ if (ret <= 0) ++ return ret; ++ ++ properties = kcalloc(1, sizeof(*properties), GFP_KERNEL); ++ if (!properties) ++ return -ENOMEM; ++ ++ gdg->properties = properties; ++ gdg->num_properties++; ++ ++ properties[0].prop = cpu_to_le16(prop); ++ properties[0].val = cpu_to_le64(val); ++ ++ return 0; ++} ++ ++static int gud_gadget_get_connector_properties(struct gud_gadget *gdg, ++ struct gud_gadget_connector *gconn) ++{ ++ struct drm_device *drm = gdg->client.dev; ++ struct drm_mode_config *config = &drm->mode_config; ++ struct drm_connector *connector = gconn->connector; ++ struct drm_object_properties *conn_props = connector->base.properties; ++ struct gud_property_req *properties; ++ unsigned int i, ret = 0; ++ u16 prop; ++ u64 val; ++ ++ mutex_lock(&drm->mode_config.mutex); ++ ++ if (!conn_props->count) ++ goto unlock; ++ ++ /* Add room for possible backlight */ ++ properties = kcalloc(conn_props->count + 1, sizeof(*properties), GFP_KERNEL); ++ if (!properties) { ++ ret = -ENOMEM; ++ goto unlock; ++ } ++ ++ gconn->properties = properties; ++ ++ for (i = 0; i < conn_props->count; i++) { ++ struct drm_property *property = conn_props->properties[i]; ++ ++ if (property == config->tv_left_margin_property) { ++ prop = GUD_PROPERTY_TV_LEFT_MARGIN; ++ val = connector->state->tv.margins.left; ++ } else if (property == config->tv_right_margin_property) { ++ prop = GUD_PROPERTY_TV_RIGHT_MARGIN; ++ val = connector->state->tv.margins.right; ++ } else if (property == config->tv_top_margin_property) { ++ prop = GUD_PROPERTY_TV_TOP_MARGIN; ++ val = connector->state->tv.margins.top; ++ } else if (property == config->tv_bottom_margin_property) { ++ prop = GUD_PROPERTY_TV_BOTTOM_MARGIN; ++ val = connector->state->tv.margins.bottom; ++ } else if (property == config->tv_mode_property) { ++ struct drm_property_enum *prop_enum; ++ char *buf; ++ ++ list_for_each_entry(prop_enum, &property->enum_list, head) ++ gconn->num_tv_mode_enum_names++; ++ ++ if (WARN_ON(!gconn->num_tv_mode_enum_names)) { ++ ret = -EINVAL; ++ goto unlock; ++ } ++ ++ buf = kcalloc(gconn->num_tv_mode_enum_names, ++ GUD_CONNECTOR_TV_MODE_NAME_LEN, GFP_KERNEL); ++ if (!buf) { ++ ret = -ENOMEM; ++ goto unlock; ++ } ++ ++ gconn->tv_mode_enum_names = buf; ++ ++ list_for_each_entry(prop_enum, &property->enum_list, head) { ++ strscpy(buf, prop_enum->name, GUD_CONNECTOR_TV_MODE_NAME_LEN); ++ buf += GUD_CONNECTOR_TV_MODE_NAME_LEN; ++ } ++ ++ prop = GUD_PROPERTY_TV_MODE; ++ val = connector->state->tv.mode; ++ } else if (property == config->tv_brightness_property) { ++ prop = GUD_PROPERTY_TV_BRIGHTNESS; ++ val = connector->state->tv.brightness; ++ } else if (property == config->tv_contrast_property) { ++ prop = GUD_PROPERTY_TV_CONTRAST; ++ val = connector->state->tv.contrast; ++ } else if (property == config->tv_flicker_reduction_property) { ++ prop = GUD_PROPERTY_TV_FLICKER_REDUCTION; ++ val = connector->state->tv.flicker_reduction; ++ } else if (property == config->tv_overscan_property) { ++ prop = GUD_PROPERTY_TV_OVERSCAN; ++ val = connector->state->tv.overscan; ++ } else if (property == config->tv_saturation_property) { ++ prop = GUD_PROPERTY_TV_SATURATION; ++ val = connector->state->tv.saturation; ++ } else if (property == config->tv_hue_property) { ++ prop = GUD_PROPERTY_TV_HUE; ++ val = connector->state->tv.hue; ++ } else { ++ continue; ++ } ++ ++ properties[gconn->num_properties].prop = cpu_to_le16(prop); ++ properties[gconn->num_properties++].val = cpu_to_le64(val); ++ } ++ ++ if (!connector->index && gdg->backlight) { ++ struct backlight_properties *props = &gdg->backlight->props; ++ ++ prop = GUD_PROPERTY_BACKLIGHT_BRIGHTNESS; ++ val = DIV64_U64_ROUND_UP(props->brightness * 100, props->max_brightness); ++ properties[gconn->num_properties].prop = cpu_to_le16(prop); ++ properties[gconn->num_properties++].val = cpu_to_le64(val); ++ gconn->backlight = gdg->backlight; ++ } ++unlock: ++ mutex_unlock(&drm->mode_config.mutex); ++ ++ return ret; ++} ++ ++static int gud_gadget_get_connectors(struct gud_gadget *gdg, u32 connectors_mask) ++{ ++ struct gud_gadget_connector *connectors = NULL; ++ struct drm_connector_list_iter conn_iter; ++ struct drm_device *drm = gdg->client.dev; ++ unsigned int connector_count = 0; ++ struct drm_connector *connector; ++ int ret = 0; ++ ++ drm_connector_list_iter_begin(drm, &conn_iter); ++ drm_client_for_each_connector_iter(connector, &conn_iter) { ++ struct gud_gadget_connector *tmp, *gconn; ++ ++ if (!((BIT(connector->index)) & connectors_mask)) ++ continue; ++ ++ tmp = krealloc(connectors, (connector_count + 1) * sizeof(*connectors), ++ GFP_KERNEL | __GFP_ZERO); ++ if (!tmp) { ++ ret = -ENOMEM; ++ break; ++ } ++ ++ connectors = tmp; ++ drm_connector_get(connector); ++ gconn = &connectors[connector_count++]; ++ gconn->connector = connector; ++ spin_lock_init(&gconn->lock); ++ ++ ret = gud_gadget_get_connector_properties(gdg, gconn); ++ if (ret) ++ break; ++ } ++ drm_connector_list_iter_end(&conn_iter); ++ ++ if (!connector_count) ++ ret = -ENOENT; ++ ++ gdg->connectors = connectors; ++ gdg->connector_count = connector_count; ++ ++ return ret; ++} ++ ++static void gud_gadget_release(struct kref *kref) ++{ ++ struct gud_gadget *gdg = container_of(kref, struct gud_gadget, refcount); ++ ++ kfree(gdg); ++} ++ ++static void gud_gadget_put(struct gud_gadget *gdg) ++{ ++ kref_put(&gdg->refcount, gud_gadget_release); ++} ++ ++static void gud_gadget_client_unregister(struct drm_client_dev *client) ++{ ++ struct gud_gadget *gdg = container_of(client, struct gud_gadget, client); ++ int timeout = 10000 / 50; ++ unsigned int i; ++ ++ /* ++ * If usecnt doesn't drop to zero, try waiting for the gadget, but we ++ * can't block the DRM driver forever. The worst case wait the gadget side ++ * can hit are tens of seconds through the call to drm_client_modeset_commit(). ++ */ ++ if (refcount_dec_and_test(&gdg->usecnt)) { ++ for (; timeout && refcount_read(&gdg->usecnt); timeout--) ++ msleep(50); ++ } ++ ++ if (!timeout) { ++ pr_err("%s: Timeout waiting for gadget side, will leak memory\n", __func__); ++ return; ++ } ++ ++ vfree(gdg->work_buf); ++ kfree(gdg->formats); ++ kfree(gdg->properties); ++ ++ for (i = 0; i < gdg->connector_count; i++) { ++ struct gud_gadget_connector *gconn = &gdg->connectors[i]; ++ ++ drm_connector_put(gconn->connector); ++ kfree(gconn->properties); ++ kfree(gconn->tv_mode_enum_names); ++ kfree(gconn->modes); ++ kfree(gconn->edid); ++ } ++ kfree(gdg->connectors); ++ ++ gud_gadget_delete_buffers(gdg); ++ drm_client_release(client); ++ gud_gadget_put(gdg); ++} ++ ++static int gud_gadget_client_hotplug(struct drm_client_dev *client) ++{ ++ struct gud_gadget *gdg = container_of(client, struct gud_gadget, client); ++ ++ gud_gadget_probe_connectors(gdg); ++ ++ return 0; ++} ++ ++static const struct drm_client_funcs gdg_client_funcs = { ++ .owner = THIS_MODULE, ++ .unregister = gud_gadget_client_unregister, ++ .hotplug = gud_gadget_client_hotplug, ++}; ++ ++struct gud_gadget *gud_gadget_init(unsigned int minor_id, const char *backlight, ++ size_t *max_buffer_size, u8 compression, u8 *formats, ++ u32 connectors) ++{ ++ struct gud_gadget *gdg; ++ u8 max_cpp; ++ int ret; ++ ++ gdg = kzalloc(sizeof(*gdg), GFP_KERNEL); ++ if (!gdg) ++ return ERR_PTR(-ENOMEM); ++ ++ INIT_WORK(&gdg->flush_worker, gud_gadget_flush_worker); ++ gdg->compression = compression; ++ ++ ret = drm_client_init_from_id(minor_id, &gdg->client, "gud-drm-gadget", &gdg_client_funcs); ++ if (ret) { ++ pr_err("Failed to aquire minor=%u\n", minor_id); ++ kfree(gdg); ++ return ERR_PTR(ret); ++ } ++ ++ refcount_set(&gdg->usecnt, 1); ++ /* The DRM driver (through the client) and f_gud_drm need one ref each */ ++ kref_init(&gdg->refcount); ++ kref_get(&gdg->refcount); ++ ++ if (backlight) { ++ gdg->backlight = backlight_device_get_by_name(backlight); ++ if (!gdg->backlight) { ++ pr_err("Failed to find backlight: %s\n", backlight); ++ ret = -ENODEV; ++ goto error_release; ++ } ++ } ++ ++ ret = gud_gadget_get_formats(gdg, &max_cpp, formats); ++ if (ret) { ++ pr_err("Failed to get formats\n"); ++ goto error_release; ++ } ++ ++ *max_buffer_size = gdg->client.dev->mode_config.max_width * ++ gdg->client.dev->mode_config.max_height * max_cpp; ++ /* f_gud_drm will kmalloc a buffer of this size */ ++ *max_buffer_size = min_t(size_t, *max_buffer_size, KMALLOC_MAX_SIZE); ++ ++ gdg->max_buffer_size = *max_buffer_size; ++ gdg->work_buf = vmalloc(gdg->max_buffer_size); ++ if (!gdg->work_buf) { ++ ret = -ENOMEM; ++ goto error_release; ++ } ++ ++ ret = gud_gadget_get_properties(gdg); ++ if (ret) { ++ pr_err("Failed to get properties\n"); ++ goto error_release; ++ } ++ ++ ret = gud_gadget_get_connectors(gdg, connectors); ++ if (ret) { ++ pr_err("Failed to get connectors\n"); ++ goto error_release; ++ } ++ ++ if (!drm_client_register(&gdg->client)) { ++ pr_err("DRM device is gone\n"); ++ ret = -ENODEV; ++ goto error_release; ++ } ++ ++ gud_gadget_probe_connectors(gdg); ++ ++ return gdg; ++ ++error_release: ++ gud_gadget_client_unregister(&gdg->client); ++ gud_gadget_fini(gdg); ++ ++ return ERR_PTR(ret); ++} ++EXPORT_SYMBOL(gud_gadget_init); ++ ++void gud_gadget_fini(struct gud_gadget *gdg) ++{ ++ if (gdg->backlight) ++ put_device(&gdg->backlight->dev); ++ gud_gadget_put(gdg); ++} ++EXPORT_SYMBOL(gud_gadget_fini); ++ ++MODULE_AUTHOR("Noralf Trønnes"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/gpu/drm/gud/gud_internal.h b/drivers/gpu/drm/gud/gud_internal.h +index d27c31648..a662b115a 100644 +--- a/drivers/gpu/drm/gud/gud_internal.h ++++ b/drivers/gpu/drm/gud/gud_internal.h +@@ -153,6 +153,8 @@ static inline void gud_to_display_mode(struct drm_display_mode *dst, + dst->hsync_start = le16_to_cpu(src->hsync_start); + dst->hsync_end = le16_to_cpu(src->hsync_end); + dst->htotal = le16_to_cpu(src->htotal); ++ // Workaround for PineNote ++ dst->hskew = 64; + dst->vdisplay = le16_to_cpu(src->vdisplay); + dst->vsync_start = le16_to_cpu(src->vsync_start); + dst->vsync_end = le16_to_cpu(src->vsync_end); +diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c +index 162cc58c7..32540f79a 100644 +--- a/drivers/gpu/drm/panel/panel-simple.c ++++ b/drivers/gpu/drm/panel/panel-simple.c +@@ -2214,6 +2214,254 @@ static const struct panel_desc edt_etmv570g2dhu = { + .connector_type = DRM_MODE_CONNECTOR_DPI, + }; + ++static const struct drm_display_mode eink_ed103tc2_mode[] ={ ++ /* clock frequency = refresh rate * (htotal * vtotal) / 1000 */ ++ // 0.5 Hz ++ { ++ .clock = 1568, ++ .hdisplay = 1872, ++ .hsync_start = 1872 + 56, ++ .hsync_end = 1872 + 56 + 144, ++ .htotal = 1872 + 56 + 144 + 136, ++ .hskew = 64, ++ .vdisplay = 1404, ++ .vsync_start = 1404 + 12, ++ .vsync_end = 1404 + 12 + 1, ++ .vtotal = 1404 + 12 + 1 + 4, ++ .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC | ++ DRM_MODE_FLAG_HSKEW | DRM_MODE_FLAG_CLKDIV2, ++ }, ++ // 1 Hz ++ { ++ .clock = 3137, ++ .hdisplay = 1872, ++ .hsync_start = 1872 + 56, ++ .hsync_end = 1872 + 56 + 144, ++ .htotal = 1872 + 56 + 144 + 136, ++ .hskew = 64, ++ .vdisplay = 1404, ++ .vsync_start = 1404 + 12, ++ .vsync_end = 1404 + 12 + 1, ++ .vtotal = 1404 + 12 + 1 + 4, ++ .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC | ++ DRM_MODE_FLAG_HSKEW | DRM_MODE_FLAG_CLKDIV2, ++ }, ++ // 5 Hz ++ { ++ .clock = 15687, ++ .hdisplay = 1872, ++ .hsync_start = 1872 + 56, ++ .hsync_end = 1872 + 56 + 144, ++ .htotal = 1872 + 56 + 144 + 136, ++ .hskew = 64, ++ .vdisplay = 1404, ++ .vsync_start = 1404 + 12, ++ .vsync_end = 1404 + 12 + 1, ++ .vtotal = 1404 + 12 + 1 + 4, ++ .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC | ++ DRM_MODE_FLAG_HSKEW | DRM_MODE_FLAG_CLKDIV2, ++ }, ++ // 15 Hz ++ { ++ .clock = 47063, ++ .hdisplay = 1872, ++ .hsync_start = 1872 + 56, ++ .hsync_end = 1872 + 56 + 144, ++ .htotal = 1872 + 56 + 144 + 136, ++ .hskew = 64, ++ .vdisplay = 1404, ++ .vsync_start = 1404 + 12, ++ .vsync_end = 1404 + 12 + 1, ++ .vtotal = 1404 + 12 + 1 + 4, ++ .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC | ++ DRM_MODE_FLAG_HSKEW | DRM_MODE_FLAG_CLKDIV2, ++ }, ++ // 20 Hz ++ { ++ .clock = 62751, ++ /* .clock = 200000, */ ++ .hdisplay = 1872, ++ .hsync_start = 1872 + 56, ++ .hsync_end = 1872 + 56 + 144, ++ .htotal = 1872 + 56 + 144 + 136, ++ .hskew = 64, ++ .vdisplay = 1404, ++ .vsync_start = 1404 + 12, ++ .vsync_end = 1404 + 12 + 1, ++ .vtotal = 1404 + 12 + 1 + 4, ++ .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC | ++ DRM_MODE_FLAG_HSKEW | DRM_MODE_FLAG_CLKDIV2, ++ }, ++ // 25 Hz ++ { ++ .clock = 78439, ++ .hdisplay = 1872, ++ .hsync_start = 1872 + 56, ++ .hsync_end = 1872 + 56 + 144, ++ .htotal = 1872 + 56 + 144 + 136, ++ .hskew = 64, ++ .vdisplay = 1404, ++ .vsync_start = 1404 + 12, ++ .vsync_end = 1404 + 12 + 1, ++ .vtotal = 1404 + 12 + 1 + 4, ++ .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC | ++ DRM_MODE_FLAG_HSKEW | DRM_MODE_FLAG_CLKDIV2, ++ }, ++ // 30 Hz ++ { ++ .clock = 94127, ++ .hdisplay = 1872, ++ .hsync_start = 1872 + 56, ++ .hsync_end = 1872 + 56 + 144, ++ .htotal = 1872 + 56 + 144 + 136, ++ .hskew = 64, ++ .vdisplay = 1404, ++ .vsync_start = 1404 + 12, ++ .vsync_end = 1404 + 12 + 1, ++ .vtotal = 1404 + 12 + 1 + 4, ++ .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC | ++ DRM_MODE_FLAG_HSKEW | DRM_MODE_FLAG_CLKDIV2, ++ }, ++ // 40 Hz ++ { ++ .clock = 125502, ++ .hdisplay = 1872, ++ .hsync_start = 1872 + 56, ++ .hsync_end = 1872 + 56 + 144, ++ .htotal = 1872 + 56 + 144 + 136, ++ .hskew = 64, ++ /* .hskew = 0, */ // <- does not work ++ /* .hskew = 10, */ ++ /* .hskew = 20, */ ++ /* .hskew = 30, */ ++ /* .hskew = 40, */ ++ /* .hskew = 50, */ ++ /* .hskew = 60, */ ++ /* .hskew = 70, */ ++ .vdisplay = 1404, ++ .vsync_start = 1404 + 12, ++ .vsync_end = 1404 + 12 + 1, ++ .vtotal = 1404 + 12 + 1 + 4, ++ .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC | ++ DRM_MODE_FLAG_HSKEW | DRM_MODE_FLAG_CLKDIV2 | ++ DRM_MODE_TYPE_PREFERRED, ++ }, ++ // 80 Hz ++ { ++ .clock = 251005, ++ .hdisplay = 1872, ++ .hsync_start = 1872 + 56, ++ .hsync_end = 1872 + 56 + 144, ++ .htotal = 1872 + 56 + 144 + 136, ++ .hskew = 64, ++ /* .hskew = 0, */ // <- does not work ++ /* .hskew = 10, */ ++ /* .hskew = 20, */ ++ /* .hskew = 30, */ ++ /* .hskew = 40, */ ++ /* .hskew = 50, */ ++ /* .hskew = 60, */ ++ /* .hskew = 70, */ ++ .vdisplay = 1404, ++ .vsync_start = 1404 + 12, ++ .vsync_end = 1404 + 12 + 1, ++ .vtotal = 1404 + 12 + 1 + 4, ++ .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC | ++ DRM_MODE_FLAG_HSKEW | DRM_MODE_FLAG_CLKDIV2 ++ }, ++ // 85 Hz ++ { ++ .clock = 266680, ++ .hdisplay = 1872, ++ .hsync_start = 1872 + 56, ++ .hsync_end = 1872 + 56 + 144, ++ .htotal = 1872 + 56 + 144 + 136, ++ .hskew = 64, ++ /* .hskew = 0, */ // <- does not work ++ /* .hskew = 10, */ ++ /* .hskew = 20, */ ++ /* .hskew = 30, */ ++ /* .hskew = 40, */ ++ /* .hskew = 50, */ ++ /* .hskew = 60, */ ++ /* .hskew = 70, */ ++ .vdisplay = 1404, ++ .vsync_start = 1404 + 12, ++ .vsync_end = 1404 + 12 + 1, ++ .vtotal = 1404 + 12 + 1 + 4, ++ .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC | ++ DRM_MODE_FLAG_HSKEW | DRM_MODE_FLAG_CLKDIV2 ++ }, ++ ++/* /1* modes that actually take the .clock seriously *1/ */ ++/* { */ ++/* /1* this will lead to dclk == 250 Mhz *1/ */ ++/* /1* .clock = 266693, *1/ */ ++/* /1* request dclk == 200 MHz *1/ */ ++/* .clock = 200000, */ ++/* .hdisplay = 1872, */ ++/* /1* hsync_start, for linux timing order, should be display + fp */ ++/* * according to the data sheet, display = ldl == 234 [dclk] = 1872 pixels, and fp == lel == 7 [dclk] == 56 pixels */ ++/* *1/ */ ++/* /1* data sheet days sync == lsl == 18 sclk = 18 * 8 pixels = 144 pixels *1/ */ ++/* .hsync_start = 1872 + 56, */ ++/* .hsync_end = 1872 + 56 + 144, */ ++/* .htotal = 1872 + 56 + 144 + 136, */ ++/* /1* .hsync_start = 1872 + 184, *1/ */ ++/* /1* .hsync_end = 1872 + 184 + 88, *1/ */ ++/* /1* .htotal = 1872 + 184 + 88 + 64, *1/ */ ++/* /1* .hskew = 136, *1/ */ ++/* .hskew = 64, */ ++/* .vdisplay = 1404, */ ++/* .vsync_start = 1404 + 12, */ ++/* .vsync_end = 1404 + 12 + 1, */ ++/* .vtotal = 1404 + 12 + 1 + 4, */ ++/* .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC | */ ++/* DRM_MODE_FLAG_HSKEW | DRM_MODE_FLAG_CLKDIV2, */ ++/* }, */ ++/* { */ ++/* .clock = 220000, */ ++/* .hdisplay = 1872, */ ++/* .hsync_start = 1872 + 56, */ ++/* .hsync_end = 1872 + 56 + 144, */ ++/* .htotal = 1872 + 56 + 144 + 136, */ ++/* .hskew = 64, */ ++/* .vdisplay = 1404, */ ++/* .vsync_start = 1404 + 12, */ ++/* .vsync_end = 1404 + 12 + 1, */ ++/* .vtotal = 1404 + 12 + 1 + 4, */ ++/* .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC | */ ++/* DRM_MODE_FLAG_HSKEW | DRM_MODE_FLAG_CLKDIV2, */ ++/* }, */ ++/* { */ ++/* .clock = 266693, */ ++/* .hdisplay = 1872, */ ++/* .hsync_start = 1872 + 56, */ ++/* .hsync_end = 1872 + 56 + 144, */ ++/* .htotal = 1872 + 56 + 144 + 136, */ ++/* .hskew = 64, */ ++/* .vdisplay = 1404, */ ++/* .vsync_start = 1404 + 12, */ ++/* .vsync_end = 1404 + 12 + 1, */ ++/* .vtotal = 1404 + 12 + 1 + 4, */ ++/* .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC | */ ++/* DRM_MODE_FLAG_HSKEW | DRM_MODE_FLAG_CLKDIV2, */ ++/* }, */ ++}; ++ ++static const struct panel_desc eink_ed103tc2 = { ++ .modes = eink_ed103tc2_mode, ++ .num_modes = 10, ++ .bpc = 4, ++ .size = { ++ .width = 210, ++ .height = 157, ++ }, ++ .bus_format = MEDIA_BUS_FMT_FIXED, ++ .bus_flags = DRM_BUS_FLAG_DE_LOW | DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE, ++ .connector_type = DRM_MODE_CONNECTOR_DPI, ++}; ++ + static const struct display_timing eink_vb3300_kca_timing = { + .pixelclock = { 40000000, 40000000, 40000000 }, + .hactive = { 334, 334, 334 }, +@@ -5220,6 +5468,9 @@ static const struct of_device_id platform_of_match[] = { + }, { + .compatible = "edt,etmv570g2dhu", + .data = &edt_etmv570g2dhu, ++ }, { ++ .compatible = "eink,ed103tc2", ++ .data = &eink_ed103tc2, + }, { + .compatible = "eink,vb3300-kca", + .data = &eink_vb3300_kca, +diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig +index b7b025814..1ca0396a8 100644 +--- a/drivers/gpu/drm/rockchip/Kconfig ++++ b/drivers/gpu/drm/rockchip/Kconfig +@@ -145,4 +145,26 @@ config ROCKCHIP_RK3066_HDMI + This selects support for Rockchip SoC specific extensions + for the RK3066 HDMI driver. If you want to enable + HDMI on RK3066 based SoC, you should select this option. ++ ++config DRM_ROCKCHIP_EBC ++ tristate "DRM Support for Rockchip EBC" ++ depends on DRM && IIO ++ select DRM_EPD_HELPER ++ select DRM_GEM_SHMEM_HELPER ++ select DRM_KMS_HELPER ++ select DRM_TTM_HELPER ++ help ++ This selects DRM/KMS support for the Rockchip E-Book Controller (EBC). ++ Choose this option if you have a Rockchip SoC and an electrophoretic ++ display. This hardware and driver is separate from the normal Rockchip ++ display hardware and DRM driver. ++ ++config DRM_ROCKCHIP_EBC_3WIN_MODE ++ bool "Enable 3WIN mode for Rockchip EBC" ++ depends on DRM_ROCKCHIP_EBC ++ help ++ This enables the module parameter direct_mode and ++ disables compile-time optimisations removing support ++ for 3WIN mode. ++ + endif +diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile +index 097f06239..eae5991e2 100644 +--- a/drivers/gpu/drm/rockchip/Makefile ++++ b/drivers/gpu/drm/rockchip/Makefile +@@ -21,3 +20,10 @@ rockchipdrm-$(CONFIG_ROCKCHIP_RGB) += rockchip_rgb.o + rockchipdrm-$(CONFIG_ROCKCHIP_RK3066_HDMI) += rk3066_hdmi.o + + obj-$(CONFIG_DRM_ROCKCHIP) += rockchipdrm.o ++ ++obj-$(CONFIG_DRM_ROCKCHIP_EBC) += rockchip_ebc.o rockchip_ebc_blit_neon.o ++ ++ifeq ($(CONFIG_KERNEL_MODE_NEON),y) ++ CFLAGS_rockchip_ebc_blit_neon.o += $(CC_FLAGS_FPU) -O3 ++ CFLAGS_REMOVE_rockchip_ebc_blit_neon.o += -mgeneral-regs-only ++endif +diff --git a/drivers/gpu/drm/rockchip/rockchip_ebc.c b/drivers/gpu/drm/rockchip/rockchip_ebc.c +new file mode 100644 +index 000000000..6453460f3 +--- /dev/null ++++ b/drivers/gpu/drm/rockchip/rockchip_ebc.c +@@ -0,0 +1,2527 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (C) 2021-2022 Samuel Holland ++ * Copyright (C) 2025 hrdl ++ */ ++ ++ ++#include "linux/spinlock.h" ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "rockchip_ebc.h" ++ ++#define EBC_DSP_START 0x0000 ++#define EBC_DSP_START_DSP_OUT_LOW BIT(31) ++#define EBC_DSP_START_DSP_SDCE_WIDTH(x) ((x) << 16) ++#define EBC_DSP_START_DSP_EINK_MODE BIT(13) ++#define EBC_DSP_START_SW_BURST_CTRL BIT(12) ++#define EBC_DSP_START_DSP_FRM_TOTAL(x) ((x) << 2) ++#define EBC_DSP_START_DSP_RST BIT(1) ++#define EBC_DSP_START_DSP_FRM_START BIT(0) ++#define EBC_EPD_CTRL 0x0004 ++#define EBC_EPD_CTRL_EINK_MODE_SWAP BIT(31) ++#define EBC_EPD_CTRL_DSP_GD_END(x) ((x) << 16) ++#define EBC_EPD_CTRL_DSP_GD_ST(x) ((x) << 8) ++#define EBC_EPD_CTRL_DSP_THREE_WIN_MODE BIT(7) ++#define EBC_EPD_CTRL_DSP_SDDW_MODE BIT(6) ++#define EBC_EPD_CTRL_EPD_AUO BIT(5) ++#define EBC_EPD_CTRL_EPD_PWR(x) ((x) << 2) ++#define EBC_EPD_CTRL_EPD_GDRL BIT(1) ++#define EBC_EPD_CTRL_EPD_SDSHR BIT(0) ++#define EBC_DSP_CTRL 0x0008 ++#define EBC_DSP_CTRL_DSP_SWAP_MODE(x) ((x) << 30) ++#define EBC_DSP_CTRL_DSP_DIFF_MODE BIT(29) ++#define EBC_DSP_CTRL_DSP_LUT_MODE BIT(28) ++#define EBC_DSP_CTRL_DSP_VCOM_MODE BIT(27) ++#define EBC_DSP_CTRL_DSP_GDOE_POL BIT(26) ++#define EBC_DSP_CTRL_DSP_GDSP_POL BIT(25) ++#define EBC_DSP_CTRL_DSP_GDCLK_POL BIT(24) ++#define EBC_DSP_CTRL_DSP_SDCE_POL BIT(23) ++#define EBC_DSP_CTRL_DSP_SDOE_POL BIT(22) ++#define EBC_DSP_CTRL_DSP_SDLE_POL BIT(21) ++#define EBC_DSP_CTRL_DSP_SDCLK_POL BIT(20) ++#define EBC_DSP_CTRL_DSP_SDCLK_DIV(x) ((x) << 16) ++#define EBC_DSP_CTRL_DSP_BACKGROUND(x) ((x) << 0) ++#define EBC_DSP_HTIMING0 0x000c ++#define EBC_DSP_HTIMING0_DSP_HTOTAL(x) ((x) << 16) ++#define EBC_DSP_HTIMING0_DSP_HS_END(x) ((x) << 0) ++#define EBC_DSP_HTIMING1 0x0010 ++#define EBC_DSP_HTIMING1_DSP_HACT_END(x) ((x) << 16) ++#define EBC_DSP_HTIMING1_DSP_HACT_ST(x) ((x) << 0) ++#define EBC_DSP_VTIMING0 0x0014 ++#define EBC_DSP_VTIMING0_DSP_VTOTAL(x) ((x) << 16) ++#define EBC_DSP_VTIMING0_DSP_VS_END(x) ((x) << 0) ++#define EBC_DSP_VTIMING1 0x0018 ++#define EBC_DSP_VTIMING1_DSP_VACT_END(x) ((x) << 16) ++#define EBC_DSP_VTIMING1_DSP_VACT_ST(x) ((x) << 0) ++#define EBC_DSP_ACT_INFO 0x001c ++#define EBC_DSP_ACT_INFO_DSP_HEIGHT(x) ((x) << 16) ++#define EBC_DSP_ACT_INFO_DSP_WIDTH(x) ((x) << 0) ++#define EBC_WIN_CTRL 0x0020 ++#define EBC_WIN_CTRL_WIN2_FIFO_THRESHOLD(x) ((x) << 19) ++#define EBC_WIN_CTRL_WIN_EN BIT(18) ++#define EBC_WIN_CTRL_AHB_INCR_NUM_REG(x) ((x) << 13) ++#define EBC_WIN_CTRL_AHB_BURST_REG(x) ((x) << 10) ++#define EBC_WIN_CTRL_WIN_FIFO_THRESHOLD(x) ((x) << 2) ++#define EBC_WIN_CTRL_WIN_FMT_Y4 (0x0 << 0) ++#define EBC_WIN_CTRL_WIN_FMT_Y8 (0x1 << 0) ++#define EBC_WIN_CTRL_WIN_FMT_XRGB8888 (0x2 << 0) ++#define EBC_WIN_CTRL_WIN_FMT_RGB565 (0x3 << 0) ++#define EBC_WIN_MST0 0x0024 ++#define EBC_WIN_MST1 0x0028 ++#define EBC_WIN_VIR 0x002c ++#define EBC_WIN_VIR_WIN_VIR_HEIGHT(x) ((x) << 16) ++#define EBC_WIN_VIR_WIN_VIR_WIDTH(x) ((x) << 0) ++#define EBC_WIN_ACT 0x0030 ++#define EBC_WIN_ACT_WIN_ACT_HEIGHT(x) ((x) << 16) ++#define EBC_WIN_ACT_WIN_ACT_WIDTH(x) ((x) << 0) ++#define EBC_WIN_DSP 0x0034 ++#define EBC_WIN_DSP_WIN_DSP_HEIGHT(x) ((x) << 16) ++#define EBC_WIN_DSP_WIN_DSP_WIDTH(x) ((x) << 0) ++#define EBC_WIN_DSP_ST 0x0038 ++#define EBC_WIN_DSP_ST_WIN_DSP_YST(x) ((x) << 16) ++#define EBC_WIN_DSP_ST_WIN_DSP_XST(x) ((x) << 0) ++#define EBC_INT_STATUS 0x003c ++#define EBC_INT_STATUS_DSP_FRM_INT_NUM(x) ((x) << 12) ++#define EBC_INT_STATUS_LINE_FLAG_INT_CLR BIT(11) ++#define EBC_INT_STATUS_DSP_FRM_INT_CLR BIT(10) ++#define EBC_INT_STATUS_DSP_END_INT_CLR BIT(9) ++#define EBC_INT_STATUS_FRM_END_INT_CLR BIT(8) ++#define EBC_INT_STATUS_LINE_FLAG_INT_MSK BIT(7) ++#define EBC_INT_STATUS_DSP_FRM_INT_MSK BIT(6) ++#define EBC_INT_STATUS_DSP_END_INT_MSK BIT(5) ++#define EBC_INT_STATUS_FRM_END_INT_MSK BIT(4) ++#define EBC_INT_STATUS_LINE_FLAG_INT_ST BIT(3) ++#define EBC_INT_STATUS_DSP_FRM_INT_ST BIT(2) ++#define EBC_INT_STATUS_DSP_END_INT_ST BIT(1) ++#define EBC_INT_STATUS_FRM_END_INT_ST BIT(0) ++#define EBC_VCOM0 0x0040 ++#define EBC_VCOM1 0x0044 ++#define EBC_VCOM2 0x0048 ++#define EBC_VCOM3 0x004c ++#define EBC_CONFIG_DONE 0x0050 ++#define EBC_CONFIG_DONE_REG_CONFIG_DONE BIT(0) ++#define EBC_VNUM 0x0054 ++#define EBC_VNUM_DSP_VCNT(x) ((x) << 16) ++#define EBC_VNUM_LINE_FLAG_NUM(x) ((x) << 0) ++#define EBC_WIN_MST2 0x0058 ++#define EBC_LUT_DATA 0x1000 ++ ++#define EBC_FRAME_PENDING -1 ++ ++#define EBC_MAX_PHASES 256 ++ ++#define EBC_NUM_LUT_REGS 0x1000 ++ ++#define EBC_FRAME_TIMEOUT msecs_to_jiffies(25) ++#define EBC_REFRESH_TIMEOUT msecs_to_jiffies(3000) ++#define EBC_SUSPEND_DELAY_MS 2000 ++ ++#define EBC_FIRMWARE "rockchip/ebc.wbf" ++MODULE_FIRMWARE(EBC_FIRMWARE); ++#define EBC_OFFCONTENT "rockchip/rockchip_ebc_default_screen_x4y4.bin" ++MODULE_FIRMWARE(EBC_OFFCONTENT); ++#define EBC_CUSTOM_WF "rockchip/custom_wf.bin" ++MODULE_FIRMWARE(EBC_CUSTOM_WF); ++ ++static const char *custom_wf_magic_version = "CLUT0002"; ++ ++#define ROCKCHIP_EBC_WORK_ITEM_CHANGE_LUT BIT(0) ++#define ROCKCHIP_EBC_WORK_ITEM_GLOBAL_REFRESH BIT(1) ++#define ROCKCHIP_EBC_WORK_ITEM_INIT BIT(2) ++#define ROCKCHIP_EBC_WORK_ITEM_SUSPEND BIT(3) ++#define ROCKCHIP_EBC_WORK_ITEM_RESCHEDULE BIT(4) ++#define ROCKCHIP_EBC_WORK_ITEM_ENABLE_NORMAL_MODE BIT(5) ++#define ROCKCHIP_EBC_WORK_ITEM_ENABLE_FAST_MODE BIT(6) ++#define ROCKCHIP_EBC_WORK_ITEM_ENABLE_ZERO_WAVEFORM_MODE BIT(7) ++#define ROCKCHIP_EBC_WORK_ITEM_DISABLE_ZERO_WAVEFORM_MODE BIT(8) ++#define ROCKCHIP_EBC_WORK_ITEM_ENABLE_PHASE_SEQUENCE_MODE BIT(9) ++ ++static const u8 dither_bayer_04[] = { ++ 7, 8, 2, 10, 7, 8, 2, 10, 7, 8, 2, 10, 7, 8, 2, 10, ++ 12, 4, 14, 6, 12, 4, 14, 6, 12, 4, 14, 6, 12, 4, 14, 6, ++ 3, 11, 1, 9, 3, 11, 1, 9, 3, 11, 1, 9, 3, 11, 1, 9, ++ 15, 7, 13, 5, 15, 7, 13, 5, 15, 7, 13, 5, 15, 7, 13, 5, ++}; ++ ++// https://momentsingraphics.de/BlueNoise.html : 16_16/LDR_LLL1_0.png >> 4 ++static const u8 dither_blue_noise_16[] = { ++ 6, 3, 8, 10, 7, 12, 4, 11, 12, 3, 9, 5, 4, 2, 5, 15, ++ 1, 6, 14, 13, 2, 15, 9, 1, 2, 6, 13, 10, 12, 8, 0, 10, ++ 7, 11, 4, 0, 4, 10, 7, 5, 13, 8, 15, 1, 7, 3, 14, 13, ++ 2, 12, 9, 8, 11, 6, 3, 14, 10, 3, 0, 11, 4, 15, 9, 4, ++ 0, 15, 3, 5, 14, 0, 12, 1, 11, 6, 9, 12, 2, 5, 11, 6, ++ 13, 10, 7, 2, 13, 9, 8, 4, 15, 5, 14, 3, 7, 9, 1, 8, ++ 5, 12, 1, 15, 4, 2, 11, 7, 0, 2, 10, 6, 15, 11, 13, 3, ++ 6, 11, 9, 7, 10, 6, 14, 8, 13, 9, 12, 0, 4, 1, 14, 2, ++ 14, 1, 4, 0, 12, 3, 1, 12, 5, 3, 7, 13, 8, 5, 7, 9, ++ 13, 8, 15, 10, 14, 6, 2, 15, 10, 1, 14, 11, 3, 12, 10, 0, ++ 6, 11, 3, 5, 8, 11, 9, 4, 2, 8, 6, 9, 2, 15, 5, 3, ++ 1, 4, 13, 2, 0, 4, 14, 7, 12, 15, 0, 4, 7, 1, 14, 8, ++ 15, 10, 7, 12, 15, 6, 9, 0, 13, 10, 6, 13, 12, 5, 12, 10, ++ 1, 5, 9, 1, 10, 11, 3, 1, 5, 4, 2, 8, 10, 3, 7, 2, ++ 13, 14, 3, 8, 5, 14, 13, 7, 9, 15, 11, 1, 15, 6, 0, 8, ++ 4, 11, 0, 13, 2, 6, 0, 8, 14, 5, 0, 7, 14, 12, 9, 11, ++}; ++ ++// https://momentsingraphics.de/BlueNoise.html : 32_32/LDR_LLL1_0.png >> 4 ++static const u8 dither_blue_noise_32[] = { ++ 9, 10, 13, 15, 9, 12, 13, 14, 8, 15, 2, 3, 15, 9, 6, 0, 15, 7, 3, 5, 4, 11, 14, 3, 7, 1, 4, 6, 9, 12, 5, 4, ++ 15, 5, 3, 11, 7, 4, 1, 6, 4, 10, 13, 7, 5, 2, 13, 4, 8, 10, 1, 14, 2, 13, 7, 2, 15, 9, 11, 5, 0, 13, 1, 7, ++ 2, 6, 1, 2, 14, 0, 10, 8, 11, 5, 0, 10, 8, 14, 11, 2, 11, 14, 12, 9, 7, 1, 10, 8, 5, 12, 2, 13, 14, 8, 3, 11, ++ 9, 14, 13, 8, 6, 9, 13, 2, 15, 1, 9, 14, 1, 4, 10, 6, 5, 0, 6, 4, 15, 11, 5, 0, 13, 3, 7, 10, 7, 2, 10, 14, ++ 1, 11, 5, 4, 12, 15, 5, 3, 7, 6, 12, 3, 6, 12, 1, 7, 13, 3, 8, 12, 0, 3, 14, 11, 6, 4, 15, 1, 4, 12, 5, 6, ++ 8, 0, 10, 7, 1, 11, 0, 9, 10, 14, 4, 11, 8, 15, 3, 14, 9, 15, 11, 2, 9, 6, 13, 9, 1, 10, 6, 12, 9, 0, 15, 3, ++ 4, 15, 9, 14, 3, 7, 4, 14, 13, 0, 8, 2, 5, 0, 10, 7, 2, 1, 5, 7, 13, 4, 7, 2, 11, 8, 14, 2, 8, 11, 13, 9, ++ 12, 5, 2, 13, 6, 10, 12, 1, 6, 2, 10, 14, 12, 9, 4, 12, 6, 10, 14, 10, 8, 0, 15, 1, 14, 4, 0, 13, 3, 6, 2, 7, ++ 14, 3, 11, 0, 8, 15, 3, 8, 11, 5, 15, 3, 7, 1, 13, 15, 0, 4, 13, 1, 3, 12, 10, 9, 5, 3, 11, 7, 15, 5, 12, 1, ++ 10, 6, 8, 5, 1, 12, 5, 2, 10, 9, 1, 13, 11, 5, 3, 8, 11, 8, 7, 5, 15, 6, 4, 13, 7, 15, 9, 6, 0, 10, 3, 8, ++ 13, 1, 13, 14, 10, 2, 7, 14, 13, 7, 6, 8, 0, 12, 9, 2, 6, 12, 2, 9, 11, 2, 8, 0, 12, 1, 10, 2, 8, 14, 15, 4, ++ 2, 6, 9, 4, 7, 12, 9, 0, 4, 3, 12, 2, 15, 4, 7, 10, 15, 0, 14, 5, 0, 14, 11, 5, 8, 3, 13, 12, 4, 11, 5, 0, ++ 12, 15, 11, 3, 1, 15, 5, 9, 13, 1, 14, 10, 6, 9, 14, 1, 4, 3, 10, 13, 8, 6, 12, 3, 15, 6, 4, 9, 1, 6, 9, 7, ++ 4, 9, 0, 5, 8, 13, 2, 6, 11, 7, 4, 11, 0, 3, 13, 5, 12, 7, 8, 1, 4, 2, 9, 14, 1, 10, 7, 11, 15, 3, 13, 10, ++ 1, 7, 14, 12, 6, 10, 3, 12, 14, 2, 8, 5, 12, 8, 2, 11, 8, 3, 11, 15, 12, 7, 0, 5, 11, 8, 0, 14, 2, 5, 0, 14, ++ 6, 4, 10, 2, 15, 1, 8, 5, 0, 10, 15, 4, 1, 15, 10, 0, 14, 13, 0, 5, 6, 11, 15, 9, 4, 2, 12, 5, 11, 9, 8, 12, ++ 11, 3, 13, 7, 4, 11, 13, 9, 7, 3, 13, 6, 14, 7, 5, 4, 6, 9, 2, 10, 13, 3, 1, 6, 13, 14, 9, 3, 7, 4, 15, 2, ++ 8, 1, 15, 9, 2, 6, 0, 15, 4, 11, 1, 11, 9, 3, 11, 15, 7, 3, 14, 7, 8, 4, 12, 8, 5, 0, 8, 15, 1, 13, 6, 9, ++ 13, 11, 5, 0, 14, 10, 8, 3, 12, 6, 8, 4, 0, 12, 1, 10, 0, 12, 5, 11, 1, 15, 13, 2, 10, 3, 6, 11, 1, 12, 3, 0, ++ 4, 6, 8, 11, 6, 4, 12, 1, 10, 14, 2, 15, 10, 5, 8, 13, 2, 9, 14, 0, 4, 9, 5, 7, 11, 14, 4, 12, 7, 10, 5, 14, ++ 10, 1, 12, 3, 13, 2, 14, 7, 5, 0, 7, 12, 3, 14, 4, 6, 4, 10, 3, 13, 10, 2, 6, 0, 15, 1, 9, 2, 5, 14, 9, 7, ++ 2, 15, 7, 1, 10, 8, 0, 11, 9, 13, 4, 7, 11, 0, 9, 12, 15, 1, 6, 7, 8, 14, 12, 10, 3, 7, 8, 15, 0, 3, 1, 13, ++ 4, 5, 9, 14, 4, 6, 15, 3, 2, 15, 10, 1, 5, 15, 7, 1, 13, 8, 11, 15, 2, 1, 4, 13, 5, 11, 13, 4, 10, 12, 8, 11, ++ 14, 12, 0, 3, 10, 13, 5, 9, 11, 1, 6, 13, 8, 2, 11, 3, 5, 4, 0, 9, 5, 11, 7, 9, 2, 0, 12, 6, 2, 15, 6, 0, ++ 8, 10, 13, 7, 11, 2, 0, 12, 6, 8, 14, 9, 3, 14, 6, 9, 14, 12, 14, 3, 10, 13, 3, 6, 15, 8, 3, 10, 7, 5, 9, 3, ++ 6, 2, 4, 8, 5, 15, 7, 10, 2, 4, 3, 11, 1, 12, 4, 8, 2, 7, 9, 6, 1, 7, 14, 10, 12, 5, 14, 0, 13, 13, 1, 14, ++ 4, 12, 15, 1, 9, 14, 3, 8, 13, 15, 0, 5, 7, 15, 0, 12, 10, 2, 0, 11, 13, 4, 0, 1, 3, 7, 9, 11, 4, 2, 8, 11, ++ 14, 7, 10, 0, 6, 11, 4, 0, 6, 12, 10, 9, 13, 10, 6, 3, 15, 5, 14, 7, 15, 9, 8, 11, 13, 2, 15, 1, 6, 7, 12, 0, ++ 2, 9, 3, 12, 5, 2, 12, 14, 9, 7, 1, 4, 2, 5, 1, 9, 13, 6, 10, 4, 3, 5, 7, 14, 5, 6, 10, 8, 12, 15, 10, 5, ++ 13, 5, 15, 8, 14, 7, 8, 1, 5, 3, 14, 13, 8, 15, 11, 7, 1, 8, 0, 12, 2, 12, 1, 13, 3, 1, 12, 0, 4, 3, 1, 8, ++ 11, 1, 4, 11, 0, 10, 15, 11, 9, 12, 7, 0, 10, 6, 3, 14, 4, 11, 14, 6, 8, 15, 4, 9, 10, 8, 5, 14, 7, 9, 13, 6, ++ 12, 0, 8, 6, 2, 3, 5, 2, 0, 6, 11, 4, 12, 1, 9, 12, 5, 2, 13, 9, 0, 10, 6, 0, 11, 13, 15, 2, 10, 2, 15, 3, ++}; ++ ++static int default_hint = ROCKCHIP_EBC_HINT_BIT_DEPTH_Y4 | ROCKCHIP_EBC_HINT_THRESHOLD | ROCKCHIP_EBC_HINT_REDRAW; ++module_param(default_hint, int, 0644); ++MODULE_PARM_DESC(default_hint, "hint to use for pixels not covered otherwise"); ++ ++static int redraw_delay = 0; ++module_param(redraw_delay, int, 0644); ++MODULE_PARM_DESC(redraw_delay, "number of hardware frames to delay redraws"); ++ ++static int early_cancellation_addition = 2; ++module_param(early_cancellation_addition, int, 0644); ++MODULE_PARM_DESC(early_cancellation_addition, "number of additional frames to drive a pixel when cancelling it"); ++ ++static bool shrink_virtual_window = false; ++module_param(shrink_virtual_window, bool, 0644); ++MODULE_PARM_DESC(shrink_virtual_window, "shrink virtual window to ongoing clip"); ++ ++static bool direct_mode = true; ++#ifdef CONFIG_DRM_ROCKCHIP_EBC_3WIN_MODE ++module_param(direct_mode, bool, 0444); ++MODULE_PARM_DESC(direct_mode, "Don't use the controller's 3WIN mode"); ++#endif ++ ++static int limit_fb_blits = -1; ++module_param(limit_fb_blits, int, S_IRUGO|S_IWUSR); ++MODULE_PARM_DESC(split_area_limit, "how many fb blits to allow. -1 does not limit"); ++ ++static bool no_off_screen = false; ++module_param(no_off_screen, bool, S_IRUGO|S_IWUSR); ++MODULE_PARM_DESC(no_off_screen, "If set to true, do not apply the off screen on next loop exit"); ++ ++/* delay parameters used to delay the return of plane_atomic_atomic */ ++/* see plane_atomic_update function for specific usage of these parameters */ ++static int delay_a = 200; ++module_param(delay_a, int, S_IRUGO|S_IWUSR); ++MODULE_PARM_DESC(delay_a, "delay_a"); ++ ++static int refresh_thread_wait_idle = 2000; ++module_param(refresh_thread_wait_idle, int, 0644); ++MODULE_PARM_DESC(refresh_thread_wait_idle, "Number of ms to wait and last frame start before stopping the refresh thread"); ++ ++#define DITHERING_BAYER 0 ++#define DITHERING_BLUE_NOISE_16 1 ++#define DITHERING_BLUE_NOISE_32 2 ++ ++static int dithering_method = 2; ++module_param(dithering_method, int, S_IRUGO|S_IWUSR); ++MODULE_PARM_DESC(dithering_method, "Dithering method, 0-2"); ++ ++static int bw_threshold = 7; ++module_param(bw_threshold, int, S_IRUGO|S_IWUSR); ++MODULE_PARM_DESC(bw_threshold, "black and white threshold"); ++ ++static int y2_dt_thresholds = 0x070f16; ++module_param(y2_dt_thresholds, int, S_IRUGO|S_IWUSR); ++MODULE_PARM_DESC(y2_dt_thresholds, "int whose lowest three bytes indicate thresholds when dithering"); ++ ++static int y2_th_thresholds = 0x04080c; ++module_param(y2_th_thresholds, int, S_IRUGO|S_IWUSR); ++MODULE_PARM_DESC(y2_th_thresholds, "int whose lowest three bytes indicate thresholds"); ++ ++static int dclk_select = 0; ++module_param(dclk_select, int, 0644); ++MODULE_PARM_DESC(dclk_select, "-1: use dclk from mode, 0: 200 MHz (default), 1: 250"); ++ ++static int temp_override = 0; ++module_param(temp_override, int, S_IRUGO|S_IWUSR); ++MODULE_PARM_DESC(temp_override, "Values > 0 override the temperature"); ++ ++/* Values for testing should be multiples of 8 and >= 8 ++ * ++ * */ ++static int hskew_override = 0; ++module_param(hskew_override, int, S_IRUGO|S_IWUSR); ++MODULE_PARM_DESC(hskew_override, "Override hskew value"); ++ ++static unsigned int rect_hint_batch = 20; ++module_param(rect_hint_batch, uint, S_IRUGO|S_IWUSR); ++MODULE_PARM_DESC(rect_hint_batch, "Batch size when reading rect_hints in ioctl"); ++ ++static int testing = 0; ++ ++DEFINE_DRM_GEM_FOPS(rockchip_ebc_fops); ++ ++static int ioctl_trigger_global_refresh(struct drm_device *dev, void *data, ++ struct drm_file *file_priv) ++{ ++ struct drm_rockchip_ebc_trigger_global_refresh *args = data; ++ struct rockchip_ebc *ebc = dev_get_drvdata(dev->dev); ++ ++ if (args->trigger_global_refresh){ ++ spin_lock(&ebc->work_item_lock); ++ ebc->work_item |= ROCKCHIP_EBC_WORK_ITEM_GLOBAL_REFRESH; ++ spin_unlock(&ebc->work_item_lock); ++ // try to trigger the refresh immediately ++ wake_up_process(ebc->refresh_thread); ++ } ++ ++ return 0; ++} ++ ++static int ioctl_set_off_screen(struct drm_device *dev, void *data, ++ struct drm_file *file_priv) ++{ ++ struct drm_rockchip_ebc_off_screen *args = data; ++ struct rockchip_ebc *ebc = dev_get_drvdata(dev->dev); ++ int res = 0; ++ void __user *ptr_off_screen = u64_to_user_ptr(args->ptr_screen_content); ++ ++ if (access_ok(ptr_off_screen, ebc->num_pixels)) { ++ res = copy_from_user(ebc->final_off_screen, ++ ptr_off_screen, ebc->num_pixels); ++ if (res) ++ pr_err("Could not copy off screen content from user-supplied data pointer (bytes not copied: %d)", ++ res); ++ } else ++ res = -EFAULT; ++ return res; ++} ++ ++struct ebc_crtc_state { ++ struct drm_crtc_state base; ++ struct rockchip_ebc_ctx *ctx; ++}; ++ ++ static inline struct ebc_crtc_state * ++to_ebc_crtc_state(struct drm_crtc_state *crtc_state) ++{ ++ return container_of(crtc_state, struct ebc_crtc_state, base); ++} ++ ++static int ioctl_extract_fbs(struct drm_device *dev, void *data, ++ struct drm_file *file_priv) ++{ ++ struct drm_rockchip_ebc_extract_fbs *args = data; ++ struct rockchip_ebc *ebc = dev_get_drvdata(dev->dev); ++ struct rockchip_ebc_ctx *ctx = to_ebc_crtc_state(READ_ONCE(ebc->crtc.state))->ctx; ++ int res = 0; ++ unsigned long num_pixels = ebc->num_pixels; ++ int refresh_index = ctx->refresh_index; ++ ++ if (args->ptr_packed_inner_outer_nextprev) { ++ if (access_ok(args->ptr_packed_inner_outer_nextprev, 3 * num_pixels)) ++ res |= copy_to_user( ++ args->ptr_packed_inner_outer_nextprev, ++ ebc->packed_inner_outer_nextprev, ++ 3 * num_pixels); ++ else ++ res |= -EFAULT; ++ } ++ if (args->ptr_hints) { ++ if (access_ok(args->ptr_hints, num_pixels)) ++ res |= copy_to_user(args->ptr_hints, ++ ctx->hints_buffer[refresh_index], ++ num_pixels); ++ else ++ res |= -EFAULT; ++ } ++ if (args->ptr_prelim_target) { ++ if (access_ok(args->ptr_prelim_target, num_pixels)) ++ res |= copy_to_user( ++ args->ptr_prelim_target, ++ ctx->prelim_target_buffer[refresh_index], ++ num_pixels); ++ else ++ res |= -EFAULT; ++ } ++ if (args->ptr_phase1) { ++ if (access_ok(args->ptr_phase1, num_pixels >> 2)) ++ res |= copy_to_user(args->ptr_phase1, ebc->phase[0], ++ ebc->phase_size); ++ else ++ res |= -EFAULT; ++ } ++ if (args->ptr_phase2) { ++ if (access_ok(args->ptr_phase2, num_pixels >> 2)) ++ res |= copy_to_user(args->ptr_phase2, ebc->phase[1], ++ ebc->phase_size); ++ else ++ res |= -EFAULT; ++ } ++ return res; ++} ++ ++/** rockchip_ebc_apply_rect_hints() - Apply pixel hints from userspace ++ * @ebc: Driver data ++ * @num_rects: Number of rectangle in the rect_hints array. ++ * @rect_hints: Array of rectangle with associated pixel hints. ++ * ++ * Context: This function should only be called from userspace, as it calls copy_from_user() ++ * a bunch of time. Additionally, kmalloc_array is call once to allocate a buffer. ++ * It can sleep during initial allocation, and data copy from userland. ++ * The function locks ebc->hints_ioctl_lock after copying a batch of rect_hints, and ++ * releases it before the next copy. ++ */ ++static int rockchip_ebc_apply_rect_hints( ++ struct rockchip_ebc *ebc, u32 num_rects, ++ struct drm_rockchip_ebc_rect_hint __user *rect_hints) ++{ ++ int ret = 0; ++ ++ if (!access_ok(rect_hints, num_rects * sizeof(*rect_hints))) { ++ return -EFAULT; ++ } ++ ++ struct drm_rockchip_ebc_rect_hint *rect_hint_arr = kmalloc_array(min(rect_hint_batch, num_rects), sizeof(*rect_hints), GFP_KERNEL); ++ if (!rect_hint_arr) { ++ return -EFAULT; ++ } ++ ++ size_t processed = 0; ++ while (num_rects > 0 && ret == 0) { ++ size_t to_process = min(rect_hint_batch, num_rects); ++ size_t copy_size = to_process * sizeof(*rect_hints); ++ size_t remain = copy_from_user(rect_hint_arr, rect_hints + processed, copy_size); ++ ++ // I'm not even sure this can really happen, but for now this will do. ++ if (remain > 0) { ++ // Alternatively, we could do an early return here. ++ to_process = (copy_size - remain) % sizeof(*rect_hints); ++ ret = -EFAULT; ++ } ++ ++ spin_lock(&ebc->hints_ioctl_lock); ++ // TODO: neon blit ++ for (int i = 0; i < to_process; ++i) { ++ struct drm_rect *r = (struct drm_rect *)&(rect_hint_arr[i].rect); ++ u8 hint = rect_hint_arr[i].hints & ROCKCHIP_EBC_HINT_MASK; ++ ++ for (unsigned int y = max(0, r->y1); ++ y < min(ebc->height, (u32)r->y2); ++y) { ++ unsigned int x1 = max(0, r->x1); ++ unsigned int x2 = min(ebc->pixel_pitch, (u32)r->x2); ++ unsigned int width = min(ebc->pixel_pitch, x2 - x1); ++ if (x1 < ebc->pixel_pitch) ++ memset(ebc->hints_ioctl + y * ebc->pixel_pitch + x1, hint, width); ++ } ++ } ++ spin_unlock(&ebc->hints_ioctl_lock); ++ ++ num_rects -= to_process; ++ processed += to_process; ++ } ++ ++ kfree(rect_hint_arr); ++ return ret; ++} ++ ++static int ioctl_rect_hints(struct drm_device *dev, void *data, ++ struct drm_file *file_priv) ++{ ++ struct drm_rockchip_ebc_rect_hints *rect_hints = data; ++ struct rockchip_ebc *ebc = dev_get_drvdata(dev->dev); ++ ++ // Alternatively, use separate buffer and only lock when copying final buffer ++ if (rect_hints->set_default_hint) { ++ int hint = rect_hints->default_hint & ROCKCHIP_EBC_HINT_MASK; ++ spin_lock(&ebc->hints_ioctl_lock); ++ memset(ebc->hints_ioctl, hint, ebc->num_pixels); ++ spin_unlock(&ebc->hints_ioctl_lock); ++ default_hint = hint; ++ } ++ ++ if (rect_hints->num_rects) ++ return rockchip_ebc_apply_rect_hints(ebc, rect_hints->num_rects, u64_to_user_ptr(rect_hints->rect_hints)); ++ ++ return 0; ++} ++ ++static int ioctl_mode(struct drm_device *dev, void *data, ++ struct drm_file *file_priv) ++{ ++ struct drm_rockchip_ebc_mode *mode = data; ++ struct rockchip_ebc *ebc = dev_get_drvdata(dev->dev); ++ int ret = 0; ++ ++ if (mode->set_driver_mode) { ++ spin_lock(&ebc->work_item_lock); ++ switch (mode->driver_mode) { ++ case ROCKCHIP_EBC_DRIVER_MODE_NORMAL: ++ ebc->work_item |= ++ ROCKCHIP_EBC_WORK_ITEM_ENABLE_NORMAL_MODE; ++ break; ++ case ROCKCHIP_EBC_DRIVER_MODE_FAST: ++ ebc->work_item |= ++ ROCKCHIP_EBC_WORK_ITEM_ENABLE_FAST_MODE; ++ break; ++ default: ++ ret |= -EINVAL; ++ } ++ spin_unlock(&ebc->work_item_lock); ++ } else { ++ mode->driver_mode = ebc->driver_mode; ++ } ++ if (mode->set_dither_mode) { ++ switch (mode->dither_mode) { ++ case ROCKCHIP_EBC_DITHER_MODE_BAYER: ++ ebc->dithering_texture_size_hint = 4; ++ ebc->dithering_texture = dither_bayer_04; ++ break; ++ case ROCKCHIP_EBC_DITHER_MODE_BLUE_NOISE_16: ++ ebc->dithering_texture_size_hint = 4; ++ ebc->dithering_texture = dither_blue_noise_16; ++ ebc->dithering_texture_size_hint = 16; ++ break; ++ case ROCKCHIP_EBC_DITHER_MODE_BLUE_NOISE_32: ++ ebc->dithering_texture = dither_blue_noise_32; ++ ebc->dithering_texture_size_hint = 32; ++ break; ++ default: ++ ret |= -EINVAL; ++ } ++ } else { ++ if (ebc->dithering_texture == dither_bayer_04) ++ mode->dither_mode = ROCKCHIP_EBC_DITHER_MODE_BAYER; ++ else if (ebc->dithering_texture == dither_blue_noise_16) ++ mode->dither_mode = ++ ROCKCHIP_EBC_DITHER_MODE_BLUE_NOISE_16; ++ else if (ebc->dithering_texture == dither_blue_noise_32) ++ mode->dither_mode = ++ ROCKCHIP_EBC_DITHER_MODE_BLUE_NOISE_32; ++ } ++ if (mode->set_redraw_delay) { ++ if (mode->redraw_delay != redraw_delay) { ++ redraw_delay = mode->redraw_delay; ++ spin_lock(&ebc->work_item_lock); ++ ebc->work_item |= ROCKCHIP_EBC_WORK_ITEM_CHANGE_LUT; ++ spin_unlock(&ebc->work_item_lock); ++ } ++ } else { ++ mode->redraw_delay = ebc->redraw_delay; ++ } ++ ++ return ret; ++} ++ ++static int ioctl_zero_waveform(struct drm_device *dev, void *data, ++ struct drm_file *file_priv) ++{ ++ struct drm_rockchip_ebc_zero_waveform *zero_waveform = data; ++ struct rockchip_ebc *ebc = dev_get_drvdata(dev->dev); ++ ++ if (zero_waveform->set_zero_waveform_mode) { ++ spin_lock(&ebc->work_item_lock); ++ if (zero_waveform->zero_waveform_mode) ++ ebc->work_item |= ++ ROCKCHIP_EBC_WORK_ITEM_ENABLE_ZERO_WAVEFORM_MODE; ++ else ++ ebc->work_item |= ++ ROCKCHIP_EBC_WORK_ITEM_DISABLE_ZERO_WAVEFORM_MODE; ++ spin_unlock(&ebc->work_item_lock); ++ } else { ++ zero_waveform->zero_waveform_mode = ++ ebc->driver_mode == ++ ROCKCHIP_EBC_DRIVER_MODE_ZERO_WAVEFORM; ++ } ++ ++ wake_up_process(ebc->refresh_thread); ++ return 0; ++} ++ ++static int ioctl_phase_sequence(struct drm_device *dev, void *data, ++ struct drm_file *file_priv) ++{ ++ struct rockchip_ebc *ebc = dev_get_drvdata(dev->dev); ++ ++ spin_lock(&ebc->phase_sequence_lock); ++ memcpy(ebc->phase_sequence, data, ++ sizeof(struct drm_rockchip_ebc_phase_sequence)); ++ ++ // Driving ++ ebc->phase_sequence->num_seqs = min(ebc->phase_sequence->num_seqs, ++ ROCKCHIP_EBC_MAX_PHASE_SEQUENCES); ++ for (int i = 0; i < ebc->phase_sequence->num_seqs; ++i) { ++ ebc->phase_sequence->elms[i].num_regions = ++ min(ebc->phase_sequence->elms[i].num_regions, ++ ROCKCHIP_EBC_MAX_REGIONS); ++ } ++ spin_unlock(&ebc->phase_sequence_lock); ++ spin_lock(&ebc->work_item_lock); ++ ebc->work_item |= ROCKCHIP_EBC_WORK_ITEM_ENABLE_PHASE_SEQUENCE_MODE; ++ spin_unlock(&ebc->work_item_lock); ++ ++ wake_up_process(ebc->refresh_thread); ++ ++ return 0; ++} ++ ++static const struct drm_ioctl_desc ioctls[DRM_COMMAND_END - DRM_COMMAND_BASE] = { ++ DRM_IOCTL_DEF_DRV(ROCKCHIP_EBC_GLOBAL_REFRESH, ++ ioctl_trigger_global_refresh, DRM_RENDER_ALLOW), ++ DRM_IOCTL_DEF_DRV(ROCKCHIP_EBC_OFF_SCREEN, ioctl_set_off_screen, ++ DRM_RENDER_ALLOW), ++ DRM_IOCTL_DEF_DRV(ROCKCHIP_EBC_EXTRACT_FBS, ioctl_extract_fbs, ++ DRM_RENDER_ALLOW), ++ DRM_IOCTL_DEF_DRV(ROCKCHIP_EBC_RECT_HINTS, ioctl_rect_hints, ++ DRM_RENDER_ALLOW), ++ DRM_IOCTL_DEF_DRV(ROCKCHIP_EBC_MODE, ioctl_mode, DRM_RENDER_ALLOW), ++ DRM_IOCTL_DEF_DRV(ROCKCHIP_EBC_ZERO_WAVEFORM, ioctl_zero_waveform, ++ DRM_RENDER_ALLOW), ++ DRM_IOCTL_DEF_DRV(ROCKCHIP_EBC_PHASE_SEQUENCE, ioctl_phase_sequence, ++ DRM_RENDER_ALLOW), ++}; ++ ++static const struct drm_driver rockchip_ebc_drm_driver = { ++ DRM_GEM_SHMEM_DRIVER_OPS, ++ DRM_FBDEV_SHMEM_DRIVER_OPS, ++ .major = 0, ++ .minor = 3, ++ .name = "rockchip-ebc", ++ .desc = "Rockchip E-Book Controller", ++ .driver_features = DRIVER_ATOMIC | DRIVER_GEM | DRIVER_MODESET, ++ .fops = &rockchip_ebc_fops, ++ .ioctls = ioctls, ++ .num_ioctls = DRM_ROCKCHIP_EBC_NUM_IOCTLS, ++}; ++ ++static const struct drm_mode_config_funcs rockchip_ebc_mode_config_funcs = { ++ .fb_create = drm_gem_fb_create_with_dirty, ++ .atomic_check = drm_atomic_helper_check, ++ .atomic_commit = drm_atomic_helper_commit, ++}; ++ ++static void rockchip_ebc_ctx_free(struct rockchip_ebc_ctx *ctx) ++{ ++ pr_info("EBC: rockchip_ebc_ctx_free"); ++ ++ vfree(ctx->hints_buffer[0]); ++ vfree(ctx->hints_buffer[1]); ++ vfree(ctx->hints_buffer[2]); ++ vfree(ctx->prelim_target_buffer[0]); ++ vfree(ctx->prelim_target_buffer[1]); ++ vfree(ctx->prelim_target_buffer[2]); ++ ++ kfree(ctx); ++} ++ ++static struct rockchip_ebc_ctx *rockchip_ebc_ctx_alloc(struct rockchip_ebc *ebc) ++{ ++ pr_debug("%s:%d\n", __func__, __LINE__); ++ struct rockchip_ebc_ctx *ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); ++ if (!ctx) ++ return NULL; ++ ++ ctx->hints_buffer[0] = vmalloc(ebc->num_pixels); ++ ctx->hints_buffer[1] = vmalloc(ebc->num_pixels); ++ ctx->hints_buffer[2] = vmalloc(ebc->num_pixels); ++ ctx->prelim_target_buffer[0] = vmalloc(ebc->num_pixels); ++ ctx->prelim_target_buffer[1] = vmalloc(ebc->num_pixels); ++ ctx->prelim_target_buffer[2] = vmalloc(ebc->num_pixels); ++ ++ if (!(ctx->hints_buffer[0] && ctx->hints_buffer[1] && ctx->hints_buffer[2] && ctx->prelim_target_buffer[0] && ctx->prelim_target_buffer[1] && ctx->prelim_target_buffer[2])) { ++ rockchip_ebc_ctx_free(ctx); ++ return NULL; ++ } ++ ++ kref_init(&ctx->kref); ++ spin_lock_init(&ctx->buffer_switch_lock); ++ for (int i = 0; i < 3; ++i) { ++ ctx->dst_clip[i] = DRM_RECT_EMPTY_EXTANDABLE; ++ ctx->src_clip_extended[i] = DRM_RECT_EMPTY_EXTANDABLE; ++ } ++ ++ return ctx; ++} ++ ++static void rockchip_ebc_ctx_release(struct kref *kref) ++{ ++ struct rockchip_ebc_ctx *ctx = ++ container_of(kref, struct rockchip_ebc_ctx, kref); ++ pr_info("ebc: %s", __func__); ++ ++ return rockchip_ebc_ctx_free(ctx); ++} ++ ++static void rockchip_ebc_change_lut(struct rockchip_ebc *ebc) ++{ ++ int temp_index; ++ struct drm_epd_lut_temp_v2 *lut; ++ struct drm_epd_lut_v2 *luts = &ebc->lut_custom; ++ ++ for (temp_index = 0; temp_index < luts->num_temp_ranges - 1; ++temp_index) { ++ if (ebc->temperature <= luts->luts[temp_index].temp_upper) ++ break; ++ } ++ ebc->lut_custom_active = lut = luts->luts + temp_index; ++ int waiting_remaining = redraw_delay; ++ for (int i = lut->offsets[ROCKCHIP_EBC_CUSTOM_WF_WAITING]; i < ROCKCHIP_EBC_CUSTOM_WF_SEQ_LENGTH; ++i) { ++ int waiting_this = max(0, min(waiting_remaining, 0x1f)); ++ waiting_remaining -= waiting_this; ++ if (!waiting_remaining || ++ i == ROCKCHIP_EBC_CUSTOM_WF_SEQ_LENGTH - 1) ++ waiting_this |= 0x20; ++ for (int next = 0; next < 16; ++next) { ++ for (int prev = 0; prev < 16; ++prev) { ++ lut->lut[(prev << (4 + ROCKCHIP_EBC_CUSTOM_WF_SEQ_SHIFT)) + (next << ROCKCHIP_EBC_CUSTOM_WF_SEQ_SHIFT) + i] = waiting_this; ++ } ++ } ++ } ++ ebc->redraw_delay = redraw_delay = redraw_delay - waiting_remaining; ++ // TODO: generalise for temperature ranges for which more than 31 phases are required ++ ebc->inner_0_15 = lut->lut[(0xf << ROCKCHIP_EBC_CUSTOM_WF_SEQ_SHIFT) + ++ lut->offsets[ROCKCHIP_EBC_CUSTOM_WF_DU]]; ++ ebc->inner_15_0 = ++ lut->lut[(0xf << (ROCKCHIP_EBC_CUSTOM_WF_SEQ_SHIFT + 4)) + ++ lut->offsets[ROCKCHIP_EBC_CUSTOM_WF_DU]]; ++} ++ ++static void print_lut(struct rockchip_ebc *ebc) ++{ ++ struct drm_epd_lut_temp_v2 *lut_active = ebc->lut_custom_active; ++ u8 *lut = lut_active->lut; ++ pr_info("%s temp_lower=%d temp_upper=%d offsets=%16ph", __func__, lut_active->temp_lower, lut_active->temp_upper, lut_active->offsets); ++ pr_info("%s lut=%64ph", __func__, lut + 15 * 64); ++} ++ ++static void rockchip_ebc_blit_direct_phase(struct rockchip_ebc *ebc, ++ u8 *phase_buffer, ++ struct drm_mode_rect *rect, u8 phase) ++{ ++ u8 phases[3] = {0x00, 0x55, 0xaa}; ++ phase = phases[phase & 3]; ++ int x1 = max(0, rect->x1); ++ int x2 = min((int) ebc->pixel_pitch, rect->x2); ++ ++ u8 *line = phase_buffer; ++ for (int y = max(0, rect->y1); y < min((int) ebc->height, rect->y2); ++y) { ++ memset(line + y * ebc->phase_pitch + (x1 >> 2), phase, (x2 >> 2) - (x1 >> 2)); ++ } ++} ++ ++static void rockchip_ebc_phase_sequence(struct rockchip_ebc *ebc) ++{ ++ struct drm_device *drm = &ebc->drm; ++ struct device *dev = drm->dev; ++ ++ spin_lock(&ebc->phase_sequence_lock); ++ struct drm_rockchip_ebc_phase_sequence *ps = ebc->phase_sequence; ++ ++ int previous_temp_override = temp_override; ++ u32 frame_counter; ++ if (ps->do_force_temperature) ++ temp_override = ps->force_temperature; ++ ++ if (ps->do_init || ps->do_gc16) ++ rockchip_ebc_change_lut(ebc); ++ ++ struct drm_epd_lut_temp_v2 *lut = ebc->lut_custom_active; ++ u8 phases[3] = { 0x00, 0x55, 0xaa }; ++ u32 frame = 0; ++ bool awaiting_completion = false; ++ ++ // TODO: consider performing do_init and go_gc16 in partial_refresh function ++ if (ps->do_init) { ++ u8 outer = lut->offsets[ROCKCHIP_EBC_CUSTOM_WF_INIT]; ++ u8 inner = lut->lut[outer]; ++ for (frame = 0;; frame++) { ++ u8 phase = phases[(inner & 0xc0) >> 6]; ++ dma_addr_t phase_handle = ebc->phase_handles[phase % 2]; ++ if (inner & 0x1f) { ++ memset(ebc->phase[frame % 2], phase, ebc->phase_size); ++ inner -= 1; ++ } ++ if (!(inner & 0x1f)) { ++ if (inner & 0x20) ++ break; ++ else ++ inner = lut->lut[++outer]; ++ } ++ dma_sync_single_for_device(dev, phase_handle, ++ ebc->phase_size, ++ DMA_TO_DEVICE); ++ if (awaiting_completion && !wait_for_completion_timeout(&ebc->display_end, EBC_FRAME_TIMEOUT)) { ++ pr_err("Frame %d timed out!\n", frame); ++ } ++ awaiting_completion = false; ++ regmap_write(ebc->regmap, EBC_WIN_MST0, phase_handle); ++ regmap_write(ebc->regmap, EBC_CONFIG_DONE, ++ EBC_CONFIG_DONE_REG_CONFIG_DONE); ++ regmap_write(ebc->regmap, EBC_DSP_START, ++ ebc->dsp_start | ++ EBC_DSP_START_DSP_FRM_START); ++ awaiting_completion = true; ++ } ++ fsleep(ps->delay_ms * 1000); ++ } ++ if (awaiting_completion && !wait_for_completion_timeout(&ebc->display_end, EBC_FRAME_TIMEOUT)) { ++ pr_err("Frame %d timed out!\n", frame); ++ } ++ awaiting_completion = false; ++ ++ if (ps->do_gc16) { ++ // TODO: ++ fsleep(ps->delay_ms * 1000); ++ } ++ ++ frame = 0; ++ for (int i_seq = 0; i_seq < ps->num_seqs; ++i_seq) { ++ struct drm_rockchip_ebc_phase_sequence_element *elm = ++ ps->elms + i_seq; ++ for (int i_frame = 0; i_frame < elm->num_frames; ++ ++i_frame, ++frame) { ++ ktime_t t1 = ktime_get(); ++ if (i_frame < 2) { ++ for (int i_region = 0; ++ i_region < elm->num_regions; ++i_region) { ++ rockchip_ebc_blit_direct_phase( ++ ebc, ebc->phase[frame % 2], ++ elm->rect + i_region, ++ elm->phase[i_region]); ++ } ++ } ++ dma_sync_single_for_device(dev, ebc->phase_handles[frame % 2], ++ ebc->phase_size, ++ DMA_TO_DEVICE); ++ if (awaiting_completion && !wait_for_completion_timeout(&ebc->display_end, EBC_FRAME_TIMEOUT)) { ++ pr_err("Frame %d timed out!\n", frame); ++ } ++ awaiting_completion = false; ++ regmap_write(ebc->regmap, EBC_WIN_MST0, ++ ebc->phase_handles[frame % 2]); ++ regmap_write(ebc->regmap, EBC_CONFIG_DONE, ++ EBC_CONFIG_DONE_REG_CONFIG_DONE); ++ s64 delay_frame = elm->delay_ms * 1000 - ++ ktime_us_delta(ktime_get(), t1); ++ if (delay_frame > 0) ++ fsleep(delay_frame); ++ regmap_write(ebc->regmap, EBC_DSP_START, ++ ebc->dsp_start | ++ EBC_DSP_START_DSP_FRM_START); ++ awaiting_completion = true; ++ } ++ ++ } ++ if (ps->do_force_temperature) { ++ temp_override = previous_temp_override; ++ rockchip_ebc_change_lut(ebc); ++ } ++ ++ spin_unlock(&ebc->phase_sequence_lock); ++} ++ ++static void rockchip_ebc_partial_refresh(struct rockchip_ebc *ebc, ++ struct rockchip_ebc_ctx *ctx) ++{ ++ struct drm_device *drm = &ebc->drm; ++ struct device *dev = drm->dev; ++ u32 frame; ++ ktime_t time_start_advance; ++ ktime_t time_advance_sync; ++ ktime_t time_sync_wait; ++ ktime_t times_wait_end[2]; ++ u32 min_frame_delay = 1000000; ++ u32 max_frame_delay = 0; ++ struct drm_rect clip_incoming = DRM_RECT_EMPTY_EXTANDABLE; ++ struct drm_rect clip_ongoing = DRM_RECT_EMPTY_EXTANDABLE; ++ ++ struct drm_rect clip_ongoing_or_waiting = clip_ongoing; ++ u32 work_item = ebc->work_item; ++ ktime_t time_last_start = ktime_get(); ++ ++ // TODO: move into logic setting these values ++ for (int i = 0; i < 16; ++i) { ++ u8 _sum = (i >= (y2_th_thresholds & 0xff)) + (i >= ((y2_th_thresholds >> 8) & 0xff)) + (i >= ((y2_th_thresholds >> 16) & 0xff)); ++ ((u8 *) ebc->lut_y2_y4)[i] = (_sum << 2) | _sum; ++ } ++ for (int i = 0; i < 32; ++i) { ++ u8 _sum = (i >= (y2_dt_thresholds & 0xff)) + (i >= ((y2_dt_thresholds >> 8) & 0xff)) + (i >= ((y2_dt_thresholds >> 16) & 0xff)); ++ ((u8 *) ebc->lut_y2_y4_dithered)[i] = (_sum << 2) | _sum; ++ } ++ ++ spin_lock(&ctx->buffer_switch_lock); ++ ctx->refresh_index = ctx->next_refresh_index; ++ for (int i = 0; i < 3; ++i) { ++ if (ctx->not_after_others[i] & (1 << ctx->refresh_index)) { ++ rockchip_ebc_drm_rect_extend_rect(&clip_incoming, ++ ctx->dst_clip + i); ++ ctx->dst_clip[i] = DRM_RECT_EMPTY_EXTANDABLE; ++ } ++ } ++ spin_unlock(&ctx->buffer_switch_lock); ++ u8 *prelim_target = ctx->prelim_target_buffer[ctx->refresh_index]; ++ u8 *hints = ctx->hints_buffer[ctx->refresh_index]; ++ ++ bool awaiting_completion = false; ++ bool awaiting_start = false; ++ bool no_schedule_until_clip_empty = false; ++ int enabling_mode = ebc->driver_mode; ++ int zero_waveform_mode_num_zero_phase_buffers = 0; ++ bool inhibit_suspend = false; ++ bool is_suspending = false; ++ ktime_t time_last_report = ktime_get(); ++ int num_frames_since_last_report = 0; ++ s64 max_advance_time_since_last_report = 0; ++ ++ for (frame = 0;; frame++) { ++ u8 *phase_buffer = ebc->phase[frame % 2]; ++ dma_addr_t phase_handle = ebc->phase_handles[frame % 2]; ++ // Used to reset the second phase buffer in direct mode after last_phase ++ work_item = ebc->work_item; ++ bool skip_advance = false; ++ ++ time_start_advance = ktime_get(); ++ // All currently scheduled pixels are IDLE or WAITING have finished and we have a work item ++ if (drm_rect_width(&clip_ongoing) <= 0 && work_item) { ++ // Refresh work item ++ spin_lock(&ebc->work_item_lock); ++ work_item |= ebc->work_item; ++ ebc->work_item = 0; ++ spin_unlock(&ebc->work_item_lock); ++ if (ebc->driver_mode == ++ ROCKCHIP_EBC_DRIVER_MODE_ZERO_WAVEFORM && ++ !(work_item & ++ ROCKCHIP_EBC_WORK_ITEM_DISABLE_ZERO_WAVEFORM_MODE)) { ++ pr_err("Ignoring work items until zero waveform mode is left explicitly"); ++ work_item = 0; ++ } ++ if ((work_item & ++ ROCKCHIP_EBC_WORK_ITEM_DISABLE_ZERO_WAVEFORM_MODE) && ++ (ebc->driver_mode == ++ ROCKCHIP_EBC_DRIVER_MODE_ZERO_WAVEFORM)) { ++ pr_warn("Disabling zero waveform mode"); ++ inhibit_suspend = false; ++ // Make sure to set a mode ++ if (!(work_item & ++ (ROCKCHIP_EBC_WORK_ITEM_ENABLE_ZERO_WAVEFORM_MODE | ++ ROCKCHIP_EBC_WORK_ITEM_ENABLE_FAST_MODE | ++ ROCKCHIP_EBC_WORK_ITEM_ENABLE_NORMAL_MODE))) { ++ work_item |= ++ ROCKCHIP_EBC_WORK_ITEM_ENABLE_NORMAL_MODE; ++ } ++ } ++ if ((work_item & ++ ROCKCHIP_EBC_WORK_ITEM_ENABLE_ZERO_WAVEFORM_MODE) && ++ (ebc->driver_mode != ++ ROCKCHIP_EBC_DRIVER_MODE_ZERO_WAVEFORM)) { ++ pr_info("Enabling zero waveform mode, finishing ongoing updates"); ++ // Finish ongoing updates to resume with a zero-only inner/outer buffer ++ no_schedule_until_clip_empty = true; ++ zero_waveform_mode_num_zero_phase_buffers = 0; ++ enabling_mode = ++ ROCKCHIP_EBC_DRIVER_MODE_ZERO_WAVEFORM; ++ inhibit_suspend = true; ++ } else if ((work_item & ++ ROCKCHIP_EBC_WORK_ITEM_ENABLE_FAST_MODE) && ++ ebc->driver_mode != ROCKCHIP_EBC_DRIVER_MODE_FAST) { ++ no_schedule_until_clip_empty = true; ++ enabling_mode = ROCKCHIP_EBC_DRIVER_MODE_FAST; ++ work_item |= ROCKCHIP_EBC_WORK_ITEM_GLOBAL_REFRESH; ++ } else if ((work_item & ++ ROCKCHIP_EBC_WORK_ITEM_ENABLE_NORMAL_MODE) && ++ ebc->driver_mode != ROCKCHIP_EBC_DRIVER_MODE_NORMAL) { ++ no_schedule_until_clip_empty = true; ++ enabling_mode = ROCKCHIP_EBC_DRIVER_MODE_NORMAL; ++ } else if (work_item & ++ ROCKCHIP_EBC_WORK_ITEM_ENABLE_PHASE_SEQUENCE_MODE) { ++ no_schedule_until_clip_empty = true; ++ enabling_mode = ROCKCHIP_EBC_DRIVER_MODE_PHASE_SEQUENCE; ++ } ++ if (work_item & ROCKCHIP_EBC_WORK_ITEM_CHANGE_LUT) { ++ rockchip_ebc_change_lut(ebc); ++ print_lut(ebc); ++ // Reset inner and outer to make sure ++ // pixels that were WAITING stay ++ // valid. For simplicity, set to IDLE. ++ scoped_ksimd() ++ rockchip_ebc_reset_inner_outer_neon(ebc); ++ } ++ if (work_item & ROCKCHIP_EBC_WORK_ITEM_INIT) { ++ clip_ongoing_or_waiting = ebc->screen_rect; ++ memset(prelim_target, 0xff, ebc->num_pixels); ++ scoped_ksimd() ++ rockchip_ebc_schedule_advance_neon(ebc, prelim_target, hints, phase_buffer, &clip_ongoing, &clip_ongoing_or_waiting, 0, ROCKCHIP_EBC_CUSTOM_WF_INIT, 0, ROCKCHIP_EBC_HINT_REDRAW, true); ++ skip_advance = true; ++ no_schedule_until_clip_empty = true; ++ } else if (work_item & ROCKCHIP_EBC_WORK_ITEM_SUSPEND) { ++ clip_ongoing_or_waiting = ebc->screen_rect; ++ if (!no_off_screen) { ++ // Use the highest-quality waveform to minimize visible artifacts ++ scoped_ksimd() ++ rockchip_ebc_schedule_advance_neon(ebc, ebc->final_off_screen, hints, phase_buffer, &clip_ongoing, &clip_ongoing_or_waiting, 0, ROCKCHIP_EBC_CUSTOM_WF_GC16, 0, ROCKCHIP_EBC_HINT_REDRAW, true); ++ } ++ no_off_screen = false; ++ skip_advance = true; ++ no_schedule_until_clip_empty = true; ++ is_suspending = true; ++ ebc->suspend_was_requested = 1; ++ } else if (work_item & ++ ROCKCHIP_EBC_WORK_ITEM_GLOBAL_REFRESH) { ++ if (ebc->driver_mode == ++ ROCKCHIP_EBC_DRIVER_MODE_FAST || ++ enabling_mode == ++ ROCKCHIP_EBC_DRIVER_MODE_FAST) { ++ ebc->driver_mode = ROCKCHIP_EBC_DRIVER_MODE_NORMAL; ++ enabling_mode = ++ ROCKCHIP_EBC_DRIVER_MODE_FAST; ++ // TODO: use NEON implementation ++ for (int i = 0; i < ebc->num_pixels; ++i) { ++ u8 prelim = prelim_target[i] & 0xf0; ++ prelim_target[i] = prelim | prelim >> 4; ++ } ++ } ++ clip_ongoing_or_waiting = ebc->screen_rect; ++ scoped_ksimd() ++ rockchip_ebc_schedule_advance_neon(ebc, prelim_target, hints, phase_buffer, &clip_ongoing, &clip_ongoing_or_waiting, 0, ROCKCHIP_EBC_CUSTOM_WF_GC16, 0, ROCKCHIP_EBC_HINT_REDRAW, true); ++ // Skip a single advance as we have performed one just now ++ skip_advance = true; ++ no_schedule_until_clip_empty = true; ++ ebc->suspend_was_requested = 0; ++ } ++ work_item = 0; ++ } else if (drm_rect_width(&clip_ongoing_or_waiting) <= 0 && ++ !inhibit_suspend && ++ (is_suspending || ++ ((drm_rect_width(&clip_incoming) <= 0) && ++ (ktime_ms_delta(ktime_get(), time_last_start) > ++ refresh_thread_wait_idle)))) { ++ // Wait before yielding the refresh thread ++ is_suspending = false; ++ break; ++ } else if (!no_schedule_until_clip_empty && !work_item) { ++ rockchip_ebc_drm_rect_extend_rect( ++ &clip_ongoing_or_waiting, &clip_incoming); ++ clip_incoming = DRM_RECT_EMPTY_EXTANDABLE; ++ } ++ pr_debug( ++ "%s frame=%d clip_ongoing=" DRM_RECT_FMT ++ " clip_ongoing_or_waiting=" DRM_RECT_FMT ++ " work_item=%d no_schedule_until_clip_empty=%d time_elapsed_since_last_start=%llu", ++ __func__, frame, DRM_RECT_ARG(&clip_ongoing), ++ DRM_RECT_ARG(&clip_ongoing_or_waiting), work_item, ++ no_schedule_until_clip_empty, ++ ktime_ms_delta(ktime_get(), time_last_start)); ++ pr_debug("%s ebc->driver_mode=%d enabling_mode=%d", __func__, ebc->driver_mode, enabling_mode); ++ if (drm_rect_width(&clip_ongoing_or_waiting) > 0 && ++ !skip_advance) { ++ u8 force_hint = 0; ++ // Disable redraws of redraw_delay <= 0 ++ u8 force_hint_mask = ebc->redraw_delay > 0 ? 0 : ROCKCHIP_EBC_HINT_REDRAW; ++ if (ebc->driver_mode == ROCKCHIP_EBC_DRIVER_MODE_FAST) { ++ scoped_ksimd() ++ rockchip_ebc_schedule_advance_fast_neon( ++ ebc, prelim_target, hints, phase_buffer, ++ &clip_ongoing, &clip_ongoing_or_waiting, ++ early_cancellation_addition, 0, ++ force_hint, force_hint_mask, ++ !no_schedule_until_clip_empty && ++ !work_item); ++ } else if (ebc->driver_mode == ROCKCHIP_EBC_DRIVER_MODE_NORMAL) { ++ scoped_ksimd() ++ rockchip_ebc_schedule_advance_neon( ++ ebc, prelim_target, hints, phase_buffer, ++ &clip_ongoing, &clip_ongoing_or_waiting, ++ early_cancellation_addition, 0, ++ force_hint, force_hint_mask, ++ !no_schedule_until_clip_empty && ++ !work_item); ++ } ++ } ++ if (drm_rect_width(&clip_ongoing) <= 0 && ++ no_schedule_until_clip_empty) { ++ if (enabling_mode == ++ ROCKCHIP_EBC_DRIVER_MODE_ZERO_WAVEFORM && ++ zero_waveform_mode_num_zero_phase_buffers < 2) { ++ memset(phase_buffer, 0, ebc->phase_size); ++ if (++zero_waveform_mode_num_zero_phase_buffers >= ++ 2) { ++ ebc->driver_mode = ++ ROCKCHIP_EBC_DRIVER_MODE_ZERO_WAVEFORM; ++ pr_info("Zero waveform mode enabled"); ++ } ++ } else { ++ switch (enabling_mode) { ++ case ROCKCHIP_EBC_DRIVER_MODE_NORMAL: ++ case ROCKCHIP_EBC_DRIVER_MODE_FAST: ++ ebc->driver_mode = enabling_mode; ++ no_schedule_until_clip_empty = false; ++ break; ++ case ROCKCHIP_EBC_DRIVER_MODE_PHASE_SEQUENCE: ++ rockchip_ebc_phase_sequence(ebc); ++ enabling_mode = ROCKCHIP_EBC_DRIVER_MODE_ZERO_WAVEFORM; ++ break; ++ } ++ } ++ } ++ pr_debug("%s schedul2 frame=%d clip_ongoing=" DRM_RECT_FMT " clip_ongoing_or_waiting=" DRM_RECT_FMT, ++ __func__, frame, DRM_RECT_ARG(&clip_ongoing), DRM_RECT_ARG(&clip_ongoing_or_waiting)); ++ ++ time_advance_sync = ktime_get(); ++ ++ u64 time_since_last_report = ++ ktime_ms_delta(time_advance_sync, time_last_report); ++ num_frames_since_last_report += 1; ++ max_advance_time_since_last_report = ++ max(max_advance_time_since_last_report, ++ ktime_us_delta(time_advance_sync, ++ time_start_advance)); ++ if (time_since_last_report >= 1000) { ++ pr_debug("%s rate num_frames=%d max_advance=%llu us", ++ __func__, num_frames_since_last_report, ++ max_advance_time_since_last_report); ++ time_last_report = time_advance_sync; ++ num_frames_since_last_report = 0; ++ max_advance_time_since_last_report = 0; ++ } ++ awaiting_start = drm_rect_width(&clip_ongoing) > 0 || ++ ebc->driver_mode == ++ ROCKCHIP_EBC_DRIVER_MODE_ZERO_WAVEFORM; ++ if (awaiting_start) { ++ // TODO: make sure we've synced all zeros as well ++ int win_start = clip_ongoing.y1 * ebc->phase_pitch + (direct_mode ? clip_ongoing.x1 / 4 : clip_ongoing.x1); ++ int win_end = clip_ongoing.y2 * ebc->phase_pitch + (direct_mode ? (clip_ongoing.x2 + 3) / 4 : clip_ongoing.x2); ++ /*win_start = 0;*/ ++ /*win_end = ebc->phase_size;*/ ++ dma_sync_single_for_device(dev, phase_handle + win_start, ++ win_end - win_start, DMA_TO_DEVICE); ++ } ++ time_sync_wait = ktime_get(); ++ ++ if (awaiting_completion && !wait_for_completion_timeout( ++ &ebc->display_end, EBC_FRAME_TIMEOUT)) ++ drm_err(drm, "Frame %d timed out!\n", frame); ++ pr_debug("%s:%d frame completion event received", __func__, __LINE__); ++ times_wait_end[0] = ktime_get(); ++ awaiting_completion = false; ++ ++ if (awaiting_start) { ++ if (shrink_virtual_window) { ++ u16 adj_win_width = ((clip_ongoing.x2 + 7) & ~7) - (clip_ongoing.x1 & ~7); ++ unsigned int win_start_offset = ebc->act_width * clip_ongoing.y1 + (clip_ongoing.x1 & ~7); ++ pr_debug("%s clip_ongoing=" DRM_RECT_FMT " adj_win_width=%ud win_start_offset=%ud", __func__, DRM_RECT_ARG(&clip_ongoing), adj_win_width, win_start_offset); ++ regmap_write(ebc->regmap, EBC_WIN_VIR, EBC_WIN_VIR_WIN_VIR_HEIGHT(drm_rect_height(&clip_ongoing)) | EBC_WIN_VIR_WIN_VIR_WIDTH(ebc->pixel_pitch)); ++ regmap_write(ebc->regmap, EBC_WIN_ACT, EBC_WIN_ACT_WIN_ACT_HEIGHT(drm_rect_height(&clip_ongoing)) | EBC_WIN_ACT_WIN_ACT_WIDTH(adj_win_width)); ++ regmap_write(ebc->regmap, EBC_WIN_DSP, EBC_WIN_DSP_WIN_DSP_HEIGHT(drm_rect_height(&clip_ongoing)) | EBC_WIN_DSP_WIN_DSP_WIDTH(adj_win_width)); ++ regmap_write(ebc->regmap, EBC_WIN_DSP_ST, EBC_WIN_DSP_ST_WIN_DSP_YST(ebc->vact_start + clip_ongoing.y1) | EBC_WIN_DSP_ST_WIN_DSP_XST(ebc->hact_start + clip_ongoing.x1 / 8)); ++ regmap_write(ebc->regmap, direct_mode ? EBC_WIN_MST0 : EBC_WIN_MST2, phase_handle + (direct_mode ? win_start_offset / 4 : win_start_offset)); ++ } else { ++ // TODO: restore other registers in case they were changed ++ regmap_write(ebc->regmap, direct_mode ? EBC_WIN_MST0 : EBC_WIN_MST2, phase_handle); ++ } ++ regmap_write(ebc->regmap, EBC_CONFIG_DONE, ++ EBC_CONFIG_DONE_REG_CONFIG_DONE); ++ awaiting_completion = true; ++ awaiting_start = false; ++ if (testing < 2) { ++ regmap_write( ++ ebc->regmap, EBC_DSP_START, ++ ebc->dsp_start | ++ EBC_DSP_START_DSP_FRM_START); ++ pr_debug("%s:%d frame started", __func__, __LINE__); ++ } ++ time_last_start = ktime_get(); ++ } ++ ++ // at this point the ebc is working. It does not access the final ++ // buffer directly, and therefore we can use the time to switch ++ // buffers or wait for a new update. ++ ++ u64 delta_advance = ktime_us_delta(time_advance_sync, time_start_advance); ++ u64 delta_sync = ktime_us_delta(time_sync_wait, time_advance_sync); ++ u64 delta_wait = ktime_us_delta(times_wait_end[0], time_sync_wait); ++ u64 delta_frame = frame > 0 ? ktime_us_delta(times_wait_end[0], times_wait_end[1]) : 0; ++ times_wait_end[1] = times_wait_end[0]; ++ u64 work_total = delta_advance + delta_sync + delta_wait; ++ if (delta_frame > max_frame_delay && delta_frame <= 100000) ++ max_frame_delay = delta_frame; ++ if (delta_frame < min_frame_delay && delta_frame > 0 && delta_frame <= 100000) ++ min_frame_delay = delta_frame; ++ pr_debug( ++ "%s: frame %i [us]: advance=%llu sync=%llu wait=%llu frame=%llu work_total=%llu", ++ __func__, frame, delta_advance, delta_sync, ++ delta_wait, delta_frame, work_total); ++ ++ // TODO: don't assume 85 Hz, as non-direct mode uses 80 Hz ++ // We have about max(0, 11700 - delta_advance - delta_wait) [us] that we can wait until we start delaying things ++ s64 switch_buffer = 11700 - delta_advance - delta_wait - 1000; ++ ++ // TODO: consider adding || !work_item ++ while(!is_suspending && ebc->driver_mode != ROCKCHIP_EBC_DRIVER_MODE_ZERO_WAVEFORM) { ++ spin_lock(&ctx->buffer_switch_lock); ++ ctx->refresh_index = ctx->next_refresh_index; ++ for (int i = 0; i < 3; ++i) { ++ // TODO: consider rejecting an update if it would incur a delay ++ if (ctx->not_after_others[i] & (1 << ctx->refresh_index)) { ++ rockchip_ebc_drm_rect_extend_rect(&clip_incoming, ++ ctx->dst_clip + i); ++ ctx->dst_clip[i] = DRM_RECT_EMPTY_EXTANDABLE; ++ } ++ } ++ spin_unlock(&ctx->buffer_switch_lock); ++ // TODO: use predicted processing time based on clip_incoming, as clip_incoming can be significantly larger than the previous one ++ s64 time_us_buffer = ++ switch_buffer - ++ ktime_us_delta(ktime_get(), times_wait_end[0]); ++ ++ if (time_us_buffer <= 0) ++ break; ++ ++ fsleep(time_us_buffer); ++ } ++ ++ prelim_target = ctx->prelim_target_buffer[ctx->refresh_index]; ++ hints = ctx->hints_buffer[ctx->refresh_index]; ++ ++ if (kthread_should_stop()) { ++ break; ++ }; ++ } ++} ++ ++static void rockchip_ebc_upd_temp(struct rockchip_ebc *ebc) ++{ ++ struct drm_device *drm = &ebc->drm; ++ int ret, temperature; ++ struct drm_epd_lut_temp_v2 *lut_active = ebc->lut_custom_active; ++ struct drm_epd_lut_v2 *luts = &ebc->lut_custom; ++ ++ ret = iio_read_channel_processed(ebc->temperature_channel, ++ &temperature); ++ pr_debug("%s ret=%d temperature=%d", __func__, ret, temperature); ++ ++ if (ret < 0) { ++ drm_err(drm, "Failed to get temperature: %d\n", ret); ++ } else { ++ // Convert from millicelsius to celsius. ++ temperature /= 1000; ++ if (temp_override > 0){ ++ printk(KERN_INFO "rockchip-ebc: override temperature from %i to %i\n", temperature, temp_override); ++ temperature = temp_override; ++ } ++ // Early cancellation is broken right now for lower temperatures ++ temperature = max(temperature, 19); ++ ebc->temperature = temperature; ++ ++ // TODO: figure out exclusivity/inclusivity and lowest and highest temperature range ++ if ((temperature < lut_active->temp_lower && ++ lut_active->temp_lower != luts->luts[0].temp_lower) || ++ (temperature > lut_active->temp_upper && ++ lut_active->temp_upper != ++ luts->luts[luts->num_temp_ranges - 1].temp_upper)) { ++ spin_lock(&ebc->work_item_lock); ++ ebc->work_item |= ROCKCHIP_EBC_WORK_ITEM_CHANGE_LUT; ++ spin_unlock(&ebc->work_item_lock); ++ } ++ } ++} ++ ++static void rockchip_ebc_refresh(struct rockchip_ebc *ebc, ++ struct rockchip_ebc_ctx *ctx) ++{ ++ struct drm_device *drm = &ebc->drm; ++ struct device *dev = drm->dev; ++ int ret; ++ ktime_t time_start_resume = ktime_get(); ++ ++ // Resume synchronously before writing to any registers ++ ret = pm_runtime_resume_and_get(dev); ++ if (ret < 0) { ++ drm_err(drm, "Failed to resume: %d\n", ret); ++ return; ++ } ++ pr_debug("%s pm_runtime_resume_and_get took %lld ms", __func__, ktime_ms_delta(ktime_get(), time_start_resume)); ++ ++ // TODO: do only once and restore at resume ++ if (!direct_mode) { ++ // Another 8-9 ms. Is it okay to do this only once? ++ regmap_bulk_write(ebc->regmap, EBC_LUT_DATA, ebc->hardware_wf, ++ EBC_NUM_LUT_REGS); ++ // TODO: do this only once ++ pr_debug("%s:%d hardware_wf written\n", __func__, __LINE__); ++ regmap_write(ebc->regmap, EBC_WIN_MST0, ebc->zero_handle); ++ regmap_write(ebc->regmap, EBC_WIN_MST1, ebc->zero_handle); ++ pr_debug("%s:%d EBC_WIN_MST? written\n", __func__, __LINE__); ++ } ++ ++ regmap_write(ebc->regmap, EBC_DSP_START, ebc->dsp_start); ++ ++ rockchip_ebc_partial_refresh(ebc, ctx); ++ ++ /* Drive the output pins low once the refresh is complete. */ ++ regmap_write(ebc->regmap, EBC_DSP_START, ++ ebc->dsp_start | ++ EBC_DSP_START_DSP_OUT_LOW); ++ ++ pr_debug("%s:%d EBC_DSP_START to low\n", __func__, __LINE__); ++ ++ pm_runtime_mark_last_busy(dev); ++ pm_runtime_put_autosuspend(dev); ++} ++ ++static int rockchip_ebc_temp_upd_thread(void *data) ++{ ++ struct rockchip_ebc *ebc = data; ++ while (!kthread_should_stop()) { ++ while (!kthread_should_park() && !kthread_should_stop()) { ++ set_current_state(TASK_RUNNING); ++ rockchip_ebc_upd_temp(ebc); ++ msleep_interruptible(10000); ++ } ++ if (!kthread_should_stop()) ++ kthread_parkme(); ++ } ++ return 0; ++} ++ ++static int rockchip_ebc_refresh_thread(void *data) ++{ ++ struct rockchip_ebc *ebc = data; ++ struct rockchip_ebc_ctx *ctx; ++ rockchip_ebc_change_lut(ebc); ++ ++ while (!kthread_should_stop()) { ++ pr_debug("%s:%d\n", __func__, __LINE__); ++ /* The context will change each time the thread is unparked. */ ++ ctx = to_ebc_crtc_state(READ_ONCE(ebc->crtc.state))->ctx; ++ ++ // this means rockchip_ebc_crtc_atomic_disable does not trigger a global refresh ++ // TODO: consider dropping this condition ++ if(ebc->suspend_was_requested == 1) { ++ // this means we are coming out from suspend. Reset the buffers to ++ // the before-suspend state ++ spin_lock(&ebc->work_item_lock); ++ ebc->work_item |= ROCKCHIP_EBC_WORK_ITEM_GLOBAL_REFRESH; ++ spin_unlock(&ebc->work_item_lock); ++ } ++ ++ // This shouldn't be necessary, but might be safer ++ memset(ebc->phase[0], 0, ebc->phase_size); ++ memset(ebc->phase[1], 0, ebc->phase_size); ++ ++ while ((!kthread_should_park()) && (!kthread_should_stop())) { ++ rockchip_ebc_refresh(ebc, ctx); ++ ++ set_current_state(TASK_IDLE); ++ if (!kthread_should_stop() && !kthread_should_park()) { ++ schedule(); ++ } ++ __set_current_state(TASK_RUNNING); ++ } ++ ++ if (!kthread_should_stop()){ ++ kthread_parkme(); ++ } ++ } ++ ++ return 0; ++} ++ ++static inline struct rockchip_ebc *crtc_to_ebc(struct drm_crtc *crtc) ++{ ++ return container_of(crtc, struct rockchip_ebc, crtc); ++} ++ ++static int rockchip_ebc_set_dclk(struct rockchip_ebc *ebc, ++ const struct drm_display_mode *mode) ++{ ++ int rate; ++ ++ if (direct_mode) { ++ rate = clk_set_rate(ebc->cpll_333m, 33333334); ++ if (rate < 0) ++ return rate; ++ rate = clk_set_rate(ebc->dclk, 34000000); ++ return rate; ++ } ++ ++ switch (dclk_select) { ++ case -1: ++ // TODO: consider adjusting cpll_333m ++ rate = clk_set_rate(ebc->dclk, mode->clock * 1000); ++ break; ++ case 0: ++ rate = clk_set_rate(ebc->dclk, 200000000); ++ break; ++ case 1: ++ rate = clk_set_rate(ebc->cpll_333m, 250000000); ++ if (rate < 0) ++ return rate; ++ rate = clk_set_rate(ebc->dclk, 250000000); ++ break; ++ default: ++ rate = -EINVAL; ++ } ++ return rate; ++} ++ ++static void rockchip_ebc_crtc_mode_set_nofb(struct drm_crtc *crtc) ++{ ++ struct rockchip_ebc *ebc = crtc_to_ebc(crtc); ++ struct drm_display_mode mode = crtc->state->adjusted_mode; ++ struct drm_display_mode sdck; ++ u16 hsync_width, vsync_width; ++ u16 hact_start, vact_start; ++ u16 pixels_per_sdck; ++ bool bus_16bit; ++ /* pr_info("ebc: %s", __func__); */ ++ /* from drm_modes.h: ++ * * The horizontal and vertical timings are defined per the following diagram. ++ * ++ * :: ++ * ++ * ++ * Active Front Sync Back ++ * Region Porch Porch ++ * <-----------------------><----------------><-------------><--------------> ++ * //////////////////////| ++ * ////////////////////// | ++ * ////////////////////// |.................. ................ ++ * _______________ ++ * <----- [hv]display -----> ++ * <------------- [hv]sync_start ------------> ++ * <--------------------- [hv]sync_end ---------------------> ++ * <-------------------------------- [hv]total ----------------------------->* ++ * ++ * */ ++ ++ /* ++ * Hardware needs horizontal timings in SDCK (source driver clock) ++ * cycles, not pixels. Bus width is either 8 bits (normal) or 16 bits ++ * (DRM_MODE_FLAG_CLKDIV2), and each pixel uses two data bits. ++ */ ++ bus_16bit = !!(mode.flags & DRM_MODE_FLAG_CLKDIV2); ++ pixels_per_sdck = bus_16bit ? 8 : 4; ++ sdck.hdisplay = mode.hdisplay / pixels_per_sdck; ++ sdck.hsync_start = mode.hsync_start / pixels_per_sdck; ++ sdck.hsync_end = mode.hsync_end / pixels_per_sdck; ++ sdck.htotal = mode.htotal / pixels_per_sdck; ++ ++ if (hskew_override > 0){ ++ pr_info( ++ "rockchip-ebc: overriding hskew value %i with new value: %i", ++ mode.hskew, hskew_override ++ ); ++ sdck.hskew = hskew_override / pixels_per_sdck; ++ } else { ++ // use the value supplied via the panel mode ++ sdck.hskew = mode.hskew / pixels_per_sdck; ++ } ++ ++ /* ++ * Linux timing order is display/fp/sync/bp. Hardware timing order is ++ * sync/bp/display/fp, aka sync/start/display/end. ++ */ ++ hact_start = sdck.htotal - sdck.hsync_start; ++ // mode.vtotal = 1404 + 12 + 1 + 4 ++ // mode.vsync_start = 1404 + 12 ++ // vact_start = 5 ++ vact_start = mode.vtotal - mode.vsync_start; ++ ++ hsync_width = sdck.hsync_end - sdck.hsync_start; ++ vsync_width = mode.vsync_end - mode.vsync_start; ++ ++ // TODO: error checking ++ rockchip_ebc_set_dclk(ebc, &mode); ++ ++ /* Display timings in ebc hardware: ++ + * GD_ST ++ + * GD_END ++ + * HTOTAL ++ + * HS_END ++ + * HACT_END ++ + * HACT_ST ++ + * VTOTAL ++ + * VS_END ++ + * VACT_END ++ + * VACT_ST ++ + * DSP_ACT_HEIGHT ++ + * DSP_ACT_WIDTH ++ + * ++ + * */ ++ ++ ebc->dsp_start = EBC_DSP_START_DSP_SDCE_WIDTH(sdck.hdisplay) | ++ EBC_DSP_START_SW_BURST_CTRL; ++ ++ ebc->act_width = mode.hdisplay; ++ ebc->act_height = mode.vdisplay; ++ ebc->vact_start = vact_start; ++ ebc->hact_start = hact_start; ++ ++ regmap_write(ebc->regmap, EBC_EPD_CTRL, ++ EBC_EPD_CTRL_DSP_GD_END(sdck.htotal - sdck.hskew) | ++ EBC_EPD_CTRL_DSP_GD_ST(hsync_width + sdck.hskew) | ++ EBC_EPD_CTRL_DSP_SDDW_MODE * bus_16bit | ++ (direct_mode ? 0 : ++ EBC_EPD_CTRL_DSP_THREE_WIN_MODE)); ++ ++ regmap_write(ebc->regmap, EBC_DSP_CTRL, ++ /* no swap */ ++ EBC_DSP_CTRL_DSP_SWAP_MODE(bus_16bit ? 2 : 3) | ++ EBC_DSP_CTRL_DSP_SDCLK_DIV(direct_mode ? 0 : pixels_per_sdck - 1)); ++ regmap_write(ebc->regmap, EBC_DSP_HTIMING0, ++ EBC_DSP_HTIMING0_DSP_HTOTAL(sdck.htotal) | ++ /* sync end == sync width */ ++ EBC_DSP_HTIMING0_DSP_HS_END(hsync_width)); ++ regmap_write(ebc->regmap, EBC_DSP_HTIMING1, ++ EBC_DSP_HTIMING1_DSP_HACT_END(hact_start + sdck.hdisplay) | ++ /* minus 1 for fixed delay in timing sequence */ ++ EBC_DSP_HTIMING1_DSP_HACT_ST(hact_start - 1)); ++ ++ /* vertical timings */ ++ regmap_write(ebc->regmap, EBC_DSP_VTIMING0, ++ EBC_DSP_VTIMING0_DSP_VTOTAL(mode.vtotal) | ++ /* sync end == sync width */ ++ /* mw: when comparing the data sheets, this is equal to FEL of the display, translating to 12 frames ++ * yet, here vsync_width computes to 1*/ ++ // = 1 ++ EBC_DSP_VTIMING0_DSP_VS_END(vsync_width)); ++ regmap_write(ebc->regmap, EBC_DSP_VTIMING1, ++ EBC_DSP_VTIMING1_DSP_VACT_END(vact_start + mode.vdisplay) | ++ EBC_DSP_VTIMING1_DSP_VACT_ST(vact_start)); ++ ++ /* active region */ ++ regmap_write(ebc->regmap, EBC_DSP_ACT_INFO, ++ EBC_DSP_ACT_INFO_DSP_HEIGHT(mode.vdisplay) | ++ EBC_DSP_ACT_INFO_DSP_WIDTH(mode.hdisplay)); ++ ++ /* misc */ ++ regmap_write(ebc->regmap, EBC_WIN_CTRL, ++ /* FIFO depth - 16 */ ++ EBC_WIN_CTRL_WIN2_FIFO_THRESHOLD(496) | ++ EBC_WIN_CTRL_WIN_EN | ++ /* INCR16 */ ++ EBC_WIN_CTRL_AHB_BURST_REG(7) | ++ /* FIFO depth - 16 */ ++ EBC_WIN_CTRL_WIN_FIFO_THRESHOLD(240) | ++ EBC_WIN_CTRL_WIN_FMT_Y4); ++ ++ /* To keep things simple, always use a window size matching the CRTC. */ ++ regmap_write(ebc->regmap, EBC_WIN_VIR, ++ EBC_WIN_VIR_WIN_VIR_HEIGHT(mode.vdisplay) | ++ EBC_WIN_VIR_WIN_VIR_WIDTH(mode.hdisplay)); ++ regmap_write(ebc->regmap, EBC_WIN_ACT, ++ EBC_WIN_ACT_WIN_ACT_HEIGHT(mode.vdisplay) | ++ EBC_WIN_ACT_WIN_ACT_WIDTH(mode.hdisplay)); ++ regmap_write(ebc->regmap, EBC_WIN_DSP, ++ EBC_WIN_DSP_WIN_DSP_HEIGHT(mode.vdisplay) | ++ EBC_WIN_DSP_WIN_DSP_WIDTH(mode.hdisplay)); ++ regmap_write(ebc->regmap, EBC_WIN_DSP_ST, ++ EBC_WIN_DSP_ST_WIN_DSP_YST(vact_start) | ++ EBC_WIN_DSP_ST_WIN_DSP_XST(hact_start)); ++} ++ ++static int rockchip_ebc_crtc_atomic_check(struct drm_crtc *crtc, ++ struct drm_atomic_state *state) ++{ ++ struct rockchip_ebc *ebc = crtc_to_ebc(crtc); ++ struct ebc_crtc_state *ebc_crtc_state; ++ struct drm_crtc_state *crtc_state; ++ struct rockchip_ebc_ctx *ctx; ++ pr_debug("ebc: %s", __func__); ++ ++ crtc_state = drm_atomic_get_new_crtc_state(state, crtc); ++ if (!crtc_state->mode_changed) ++ return 0; ++ ++ if (crtc_state->enable) { ++ struct drm_display_mode *mode = &crtc_state->adjusted_mode; ++ ++ int rate = rockchip_ebc_set_dclk(ebc, mode); ++ if (rate < 0) ++ return rate; ++ mode->clock = rate / 1000; ++ ++ ctx = rockchip_ebc_ctx_alloc(ebc); ++ if (!ctx) ++ return -ENOMEM; ++ } else { ++ ctx = NULL; ++ } ++ ++ ebc_crtc_state = to_ebc_crtc_state(crtc_state); ++ if (ebc_crtc_state->ctx) ++ kref_put(&ebc_crtc_state->ctx->kref, rockchip_ebc_ctx_release); ++ ebc_crtc_state->ctx = ctx; ++ ++ return 0; ++} ++ ++static void rockchip_ebc_crtc_atomic_flush(struct drm_crtc *crtc, ++ struct drm_atomic_state *state) ++{ ++ // TODO: consider moving copying to final_buffer, moving of areas, setting switch_required, waking up process here ++ // TODO: also look at atomic_commit ++ pr_debug("ebc: %s", __func__); ++} ++ ++static void rockchip_ebc_crtc_atomic_enable(struct drm_crtc *crtc, ++ struct drm_atomic_state *state) ++{ ++ struct rockchip_ebc *ebc = crtc_to_ebc(crtc); ++ struct drm_crtc_state *crtc_state; ++ pr_debug("ebc: %s", __func__); ++ ++ crtc_state = drm_atomic_get_new_crtc_state(state, crtc); ++ if (crtc_state->mode_changed) { ++ kthread_unpark(ebc->temp_upd_thread); ++ kthread_unpark(ebc->refresh_thread); ++ } ++} ++ ++static void rockchip_ebc_crtc_atomic_disable(struct drm_crtc *crtc, ++ struct drm_atomic_state *state) ++{ ++ struct rockchip_ebc *ebc = crtc_to_ebc(crtc); ++ struct drm_crtc_state *crtc_state; ++ pr_debug("ebc: %s", __func__); ++ ++ crtc_state = drm_atomic_get_new_crtc_state(state, crtc); ++ if (crtc_state->mode_changed){ ++ if (! ((ebc->refresh_thread->__state) & (TASK_DEAD))){ ++ kthread_park(ebc->refresh_thread); ++ } ++ if (!(ebc->temp_upd_thread->__state & TASK_DEAD)) { ++ kthread_park(ebc->temp_upd_thread); ++ } ++ } ++} ++ ++static const struct drm_crtc_helper_funcs rockchip_ebc_crtc_helper_funcs = { ++ .mode_set_nofb = rockchip_ebc_crtc_mode_set_nofb, ++ .atomic_check = rockchip_ebc_crtc_atomic_check, ++ .atomic_flush = rockchip_ebc_crtc_atomic_flush, ++ .atomic_enable = rockchip_ebc_crtc_atomic_enable, ++ .atomic_disable = rockchip_ebc_crtc_atomic_disable, ++}; ++ ++static void rockchip_ebc_crtc_destroy_state(struct drm_crtc *crtc, ++ struct drm_crtc_state *crtc_state); ++ ++static void rockchip_ebc_crtc_reset(struct drm_crtc *crtc) ++{ ++ struct ebc_crtc_state *ebc_crtc_state; ++ /* pr_info("ebc: %s", __func__); */ ++ ++ if (crtc->state) ++ rockchip_ebc_crtc_destroy_state(crtc, crtc->state); ++ ++ ebc_crtc_state = kzalloc(sizeof(*ebc_crtc_state), GFP_KERNEL); ++ if (!ebc_crtc_state) ++ return; ++ ++ __drm_atomic_helper_crtc_reset(crtc, &ebc_crtc_state->base); ++} ++ ++static struct drm_crtc_state * ++rockchip_ebc_crtc_duplicate_state(struct drm_crtc *crtc) ++{ ++ struct ebc_crtc_state *ebc_crtc_state; ++ /* pr_info("ebc: %s", __func__); */ ++ ++ if (!crtc->state) ++ return NULL; ++ ++ ebc_crtc_state = kzalloc(sizeof(*ebc_crtc_state), GFP_KERNEL); ++ if (!ebc_crtc_state) ++ return NULL; ++ ++ __drm_atomic_helper_crtc_duplicate_state(crtc, &ebc_crtc_state->base); ++ ++ ebc_crtc_state->ctx = to_ebc_crtc_state(crtc->state)->ctx; ++ if (ebc_crtc_state->ctx) ++ kref_get(&ebc_crtc_state->ctx->kref); ++ ++ return &ebc_crtc_state->base; ++} ++ ++static void rockchip_ebc_crtc_destroy_state(struct drm_crtc *crtc, ++ struct drm_crtc_state *crtc_state) ++{ ++ struct ebc_crtc_state *ebc_crtc_state = to_ebc_crtc_state(crtc_state); ++ ++ if (ebc_crtc_state->ctx) ++ kref_put(&ebc_crtc_state->ctx->kref, rockchip_ebc_ctx_release); ++ ++ __drm_atomic_helper_crtc_destroy_state(&ebc_crtc_state->base); ++ ++ kfree(ebc_crtc_state); ++} ++ ++static const struct drm_crtc_funcs rockchip_ebc_crtc_funcs = { ++ .reset = rockchip_ebc_crtc_reset, ++ .destroy = drm_crtc_cleanup, ++ .set_config = drm_atomic_helper_set_config, ++ .page_flip = drm_atomic_helper_page_flip, ++ .atomic_duplicate_state = rockchip_ebc_crtc_duplicate_state, ++ .atomic_destroy_state = rockchip_ebc_crtc_destroy_state, ++}; ++ ++/* ++ * Plane ++ */ ++ ++struct ebc_plane_state { ++ struct drm_shadow_plane_state base; ++ struct drm_rect clip; ++}; ++ ++static inline struct ebc_plane_state * ++to_ebc_plane_state(struct drm_plane_state *plane_state) ++{ ++ return container_of(plane_state, struct ebc_plane_state, base.base); ++} ++ ++static inline struct rockchip_ebc *plane_to_ebc(struct drm_plane *plane) ++{ ++ return container_of(plane, struct rockchip_ebc, plane); ++} ++ ++static int rockchip_ebc_plane_atomic_check(struct drm_plane *plane, ++ struct drm_atomic_state *state) ++{ ++ struct drm_atomic_helper_damage_iter iter; ++ struct ebc_plane_state *ebc_plane_state; ++ struct drm_plane_state *old_plane_state; ++ struct drm_plane_state *plane_state; ++ struct drm_crtc_state *crtc_state; ++ struct drm_rect clip; ++ int ret; ++ ++ plane_state = drm_atomic_get_new_plane_state(state, plane); ++ if (!plane_state->crtc) ++ return 0; ++ ++ crtc_state = drm_atomic_get_new_crtc_state(state, plane_state->crtc); ++ ret = drm_atomic_helper_check_plane_state(plane_state, crtc_state, ++ DRM_PLANE_NO_SCALING, ++ DRM_PLANE_NO_SCALING, ++ false, true); ++ if (ret) ++ return ret; ++ ++ ebc_plane_state = to_ebc_plane_state(plane_state); ++ old_plane_state = drm_atomic_get_old_plane_state(state, plane); ++ drm_atomic_helper_damage_iter_init(&iter, old_plane_state, plane_state); ++ drm_atomic_for_each_plane_damage(&iter, &clip) { ++ rockchip_ebc_drm_rect_extend_rect(&ebc_plane_state->clip, &clip); ++ } ++ ++ return 0; ++} ++ ++static void rockchip_ebc_plane_atomic_update(struct drm_plane *plane, ++ struct drm_atomic_state *state) ++{ ++ struct rockchip_ebc *ebc = plane_to_ebc(plane); ++ struct ebc_plane_state *ebc_plane_state; ++ struct drm_plane_state *plane_state; ++ struct drm_crtc_state *crtc_state; ++ struct rockchip_ebc_ctx *ctx; ++ struct drm_rect src; ++ const void *vaddr; ++ pr_debug("ebc %s", __func__); ++ plane_state = drm_atomic_get_new_plane_state(state, plane); ++ if (!plane_state->crtc) ++ return; ++ ++ crtc_state = drm_atomic_get_new_crtc_state(state, plane_state->crtc); ++ ctx = to_ebc_crtc_state(crtc_state)->ctx; ++ ++ drm_rect_fp_to_int(&src, &plane_state->src); ++ ++ ebc_plane_state = to_ebc_plane_state(plane_state); ++ vaddr = ebc_plane_state->base.data[0].vaddr; ++ ++ struct drm_rect src_clip = ebc_plane_state->clip; ++ ebc_plane_state->clip = DRM_RECT_EMPTY_EXTANDABLE; ++ if (drm_rect_width(&src_clip) <= 0) ++ return; ++ // NEON 16 byte alignment ++ src_clip.x1 = ++ max(0, min(src_clip.x1 & ~15, (int)ebc->pixel_pitch - 16)); ++ src_clip.x2 = min((src_clip.x2 + 15) & ~15, (int)ebc->pixel_pitch); ++ ++ src_clip.y1 = max(0, src_clip.y1); ++ src_clip.y2 = min((int) ebc->height, src_clip.y2); ++ ++ // This is the buffer we are allowed to modify, as it's not being read by the refresh thread ++ int idx_update = ctx->update_index; ++ ++ // Also blit areas blitted to the other two buffers since the last time this one was modified ++ struct drm_rect src_clip_extended = src_clip; ++ rockchip_ebc_drm_rect_extend_rect(&src_clip_extended, ctx->src_clip_extended + idx_update); ++ ++ rockchip_ebc_drm_rect_extend_rect(ctx->src_clip_extended + 0, ++ &src_clip); ++ rockchip_ebc_drm_rect_extend_rect(ctx->src_clip_extended + 1, ++ &src_clip); ++ rockchip_ebc_drm_rect_extend_rect(ctx->src_clip_extended + 2, ++ &src_clip); ++ ctx->src_clip_extended[idx_update] = DRM_RECT_EMPTY_EXTANDABLE; ++ ++ struct drm_rect dst_clip = src_clip; ++ struct drm_rect dst_clip_extended = src_clip_extended; ++ ++ // Horizontal flip ++ dst_clip.x1 = plane_state->dst.x2 - src_clip.x2; ++ dst_clip.x2 = plane_state->dst.x2 - src_clip.x1; ++ dst_clip_extended.x1 = plane_state->dst.x2 - src_clip_extended.x2; ++ dst_clip_extended.x2 = plane_state->dst.x2 - src_clip_extended.x1; ++ ++ // TODO: consider measuring required time and indicate to refresh thread when the next update is going to be available ++ ++ pr_debug("%s dst_clip=" DRM_RECT_FMT, __func__, DRM_RECT_ARG(&dst_clip)); ++ if (limit_fb_blits != 0) { ++ // the counter should only reach 0 here, -1 can only be externally set ++ limit_fb_blits -= (limit_fb_blits > 0) ? 1 : 0; ++ ++ switch (plane_state->fb->format->format) { ++ case DRM_FORMAT_RGB565: ++ scoped_ksimd() ++ rockchip_ebc_blit_fb_rgb565_y4_hints_neon( ++ ebc, &dst_clip_extended, ++ ctx->prelim_target_buffer[idx_update], ++ ctx->hints_buffer[idx_update], vaddr, ++ plane_state->fb, &src_clip_extended); ++ break; ++ case DRM_FORMAT_XRGB8888: ++ scoped_ksimd() ++ rockchip_ebc_blit_fb_xrgb8888_y4_hints_neon( ++ ebc, &dst_clip_extended, ++ ctx->prelim_target_buffer[idx_update], ++ ctx->hints_buffer[idx_update], vaddr, ++ plane_state->fb, &src_clip_extended); ++ break; ++ case DRM_FORMAT_R8: ++ scoped_ksimd() ++ rockchip_ebc_blit_fb_r8_y4_hints_neon( ++ ebc, &dst_clip_extended, ++ ctx->prelim_target_buffer[idx_update], ++ ctx->hints_buffer[idx_update], vaddr, ++ plane_state->fb, &src_clip_extended); ++ break; ++ } ++ } ++ ++ // Don't extend until now to avoid out-of-order updates and allowing the refresh thread to clear this area in the meantime ++ spin_lock(&ctx->buffer_switch_lock); ++ for (int i = 0; i < 3; ++i) { ++ ctx->not_after_others[i] |= (1 << idx_update); ++ } ++ ctx->not_after_others[idx_update] = (1 << idx_update); ++ rockchip_ebc_drm_rect_extend_rect(ctx->dst_clip + idx_update, ++ &dst_clip); ++ ctx->next_refresh_index = idx_update; ++ if ((ctx->update_index = (ctx->update_index + 1) % 3) == ++ ctx->refresh_index) { ++ ctx->update_index = (ctx->update_index + 1) % 3; ++ } ++ spin_unlock(&ctx->buffer_switch_lock); ++ ++ wake_up_process(ebc->refresh_thread); ++} ++ ++static const struct drm_plane_helper_funcs rockchip_ebc_plane_helper_funcs = { ++ /* .prepare_fb = drm_gem_prepare_shadow_fb, */ ++ /* .cleanup_fb = drm_gem_cleanup_shadow_fb, */ ++ .begin_fb_access = drm_gem_begin_shadow_fb_access, ++ .end_fb_access = drm_gem_end_shadow_fb_access, ++ .atomic_check = rockchip_ebc_plane_atomic_check, ++ .atomic_update = rockchip_ebc_plane_atomic_update, ++}; ++ ++static void rockchip_ebc_plane_destroy_state(struct drm_plane *plane, ++ struct drm_plane_state *plane_state); ++ ++static void rockchip_ebc_plane_reset(struct drm_plane *plane) ++{ ++ struct ebc_plane_state *ebc_plane_state; ++ pr_info("ebc: %s", __func__); ++ ++ if (plane->state) ++ rockchip_ebc_plane_destroy_state(plane, plane->state); ++ ++ ebc_plane_state = kzalloc(sizeof(*ebc_plane_state), GFP_KERNEL); ++ if (!ebc_plane_state) ++ return; ++ ++ __drm_gem_reset_shadow_plane(plane, &ebc_plane_state->base); ++ ebc_plane_state->clip = DRM_RECT_EMPTY_EXTANDABLE; ++} ++ ++static struct drm_plane_state * ++rockchip_ebc_plane_duplicate_state(struct drm_plane *plane) ++{ ++ struct ebc_plane_state *ebc_plane_state; ++ /* pr_info("ebc: %s", __func__); */ ++ ++ if (!plane->state) ++ return NULL; ++ ++ ebc_plane_state = kzalloc(sizeof(*ebc_plane_state), GFP_KERNEL); ++ if (!ebc_plane_state) ++ return NULL; ++ ++ __drm_gem_duplicate_shadow_plane_state(plane, &ebc_plane_state->base); ++ ++ ebc_plane_state->clip = DRM_RECT_EMPTY_EXTANDABLE; ++ ++ return &ebc_plane_state->base.base; ++} ++ ++static void rockchip_ebc_plane_destroy_state(struct drm_plane *plane, ++ struct drm_plane_state *plane_state) ++{ ++ struct ebc_plane_state *ebc_plane_state = to_ebc_plane_state(plane_state); ++ /* pr_info("ebc: %s", __func__); */ ++ ++ __drm_gem_destroy_shadow_plane_state(&ebc_plane_state->base); ++ ++ kfree(ebc_plane_state); ++} ++ ++static const struct drm_plane_funcs rockchip_ebc_plane_funcs = { ++ .update_plane = drm_atomic_helper_update_plane, ++ .disable_plane = drm_atomic_helper_disable_plane, ++ .destroy = drm_plane_cleanup, ++ .reset = rockchip_ebc_plane_reset, ++ .atomic_duplicate_state = rockchip_ebc_plane_duplicate_state, ++ .atomic_destroy_state = rockchip_ebc_plane_destroy_state, ++}; ++ ++static const u32 rockchip_ebc_plane_formats[] = { ++ DRM_FORMAT_XRGB8888, ++ DRM_FORMAT_RGB565, ++ DRM_FORMAT_R8, ++}; ++ ++static const u64 rockchip_ebc_plane_format_modifiers[] = { ++ DRM_FORMAT_MOD_LINEAR, ++ DRM_FORMAT_MOD_INVALID ++}; ++ ++static int rockchip_ebc_waveform_init(struct rockchip_ebc *ebc) ++{ ++ int ret; ++ struct drm_device *drm = &ebc->drm; ++ ++ const struct firmware * default_off_screen; ++ const struct firmware *custom_wf; ++ ++ ret = drmm_epd_lut_file_init(drm, &ebc->lut_file, EBC_FIRMWARE); ++ if (ret) ++ return ret; ++ ++ ret = drmm_epd_lut_init(&ebc->lut_file, &ebc->lut, ++ DRM_EPD_LUT_4BIT_PACKED, EBC_MAX_PHASES); ++ if (ret) ++ return ret; ++ ++ if (!request_firmware(&custom_wf, EBC_CUSTOM_WF, drm->dev)) { ++ size_t temp_range_size = 8 + ROCKCHIP_EBC_CUSTOM_WF_NUM_SEQS + ROCKCHIP_EBC_CUSTOM_WF_LUT_SIZE; ++ if ((custom_wf->size - 12) % temp_range_size) { ++ drm_err(drm, "Length error when loading custom_wf.bin\n"); ++ ret = -EINVAL; ++ } else if (memcmp(custom_wf_magic_version, custom_wf->data, 8)) { ++ drm_err(drm, "Versioned magic comparison failed. Got %8ph, expected %8ph\n", custom_wf->data, custom_wf_magic_version); ++ ret = -EINVAL; ++ } else { ++ unsigned int num_temp_ranges = (custom_wf->size - 12) / temp_range_size; ++ ebc->lut_custom.num_temp_ranges = num_temp_ranges; ++ ebc->lut_custom.luts = vzalloc(num_temp_ranges * sizeof(struct drm_epd_lut_temp_v2)); ++ if (!ebc->lut_custom.luts) { ++ drm_err(drm, "Failed to allocate lut_custom.luts\n"); ++ ret = -ENOMEM; ++ } else { ++ const u8 *fw_temp = custom_wf->data + 12; ++ for (int i = 0; i < num_temp_ranges; ++i) { ++ struct drm_epd_lut_temp_v2 *lut_temp = ebc->lut_custom.luts + i; ++ lut_temp->temp_lower = *fw_temp; ++ lut_temp->temp_upper = *(fw_temp + 4); ++ for (int wf = 0; wf < ROCKCHIP_EBC_CUSTOM_WF_NUM_SEQS; ++wf) ++ lut_temp->offsets[wf] = fw_temp[8+wf]; ++ memcpy(lut_temp->lut, fw_temp + 8 + ROCKCHIP_EBC_CUSTOM_WF_NUM_SEQS, ROCKCHIP_EBC_CUSTOM_WF_LUT_SIZE); ++ fw_temp += temp_range_size; ++ } ++ } ++ } ++ } else { ++ drm_err(drm, "Unable to load custom_wf.bin\n"); ++ ret = -EINVAL; ++ } ++ release_firmware(custom_wf); ++ if (ret) ++ return ret; ++ ++ // check if there is a default off-screen. Only the lowest four bits will be used per pixel ++ if (!request_firmware(&default_off_screen, EBC_OFFCONTENT, drm->dev)) ++ { ++ if (default_off_screen->size != ebc->num_pixels) ++ drm_err(drm, "Size of default off_screen data file is not %d\n", ebc->num_pixels); ++ else { ++ memcpy(ebc->final_off_screen, default_off_screen->data, ebc->num_pixels); ++ } ++ } else { ++ // fill the off-screen with some values ++ memset(ebc->final_off_screen, 0xff, ebc->num_pixels); ++ } ++ release_firmware(default_off_screen); ++ pr_debug("%s:%d\n", __func__, __LINE__); ++ ++ return 0; ++} ++ ++static int rockchip_ebc_drm_init(struct rockchip_ebc *ebc) ++{ ++ struct drm_device *drm = &ebc->drm; ++ struct drm_bridge *bridge; ++ int ret; ++ ++ ret = drmm_mode_config_init(drm); ++ if (ret) ++ return ret; ++ ++ drm->mode_config.max_width = DRM_SHADOW_PLANE_MAX_WIDTH; ++ drm->mode_config.max_height = DRM_SHADOW_PLANE_MAX_HEIGHT; ++ drm->mode_config.funcs = &rockchip_ebc_mode_config_funcs; ++ drm->mode_config.quirk_addfb_prefer_host_byte_order = true; ++ ++ drm_plane_helper_add(&ebc->plane, &rockchip_ebc_plane_helper_funcs); ++ ret = drm_universal_plane_init(drm, &ebc->plane, 0, ++ &rockchip_ebc_plane_funcs, ++ rockchip_ebc_plane_formats, ++ ARRAY_SIZE(rockchip_ebc_plane_formats), ++ rockchip_ebc_plane_format_modifiers, ++ DRM_PLANE_TYPE_PRIMARY, NULL); ++ if (ret) ++ return ret; ++ ++ drm_plane_enable_fb_damage_clips(&ebc->plane); ++ ++ drm_crtc_helper_add(&ebc->crtc, &rockchip_ebc_crtc_helper_funcs); ++ ret = drm_crtc_init_with_planes(drm, &ebc->crtc, &ebc->plane, NULL, ++ &rockchip_ebc_crtc_funcs, NULL); ++ if (ret) ++ return ret; ++ ++ ebc->encoder.possible_crtcs = drm_crtc_mask(&ebc->crtc); ++ // todo: consider using drmm_simple_encoder_alloc() ++ // see: https://www.kernel.org/doc/html/latest/gpu/drm-kms-helpers.html?highlight=drm_simple_encoder_init#c.drm_simple_encoder_init ++ ret = drm_simple_encoder_init(drm, &ebc->encoder, DRM_MODE_ENCODER_NONE); ++ if (ret) ++ return ret; ++ ++ bridge = devm_drm_of_get_bridge(drm->dev, drm->dev->of_node, 0, 0); ++ if (IS_ERR(bridge)) ++ return PTR_ERR(bridge); ++ ++ ret = drm_bridge_attach(&ebc->encoder, bridge, NULL, 0); ++ if (ret) ++ return ret; ++ ++ drm_mode_config_reset(drm); ++ ++ ret = drm_dev_register(drm, 0); ++ if (ret) ++ return ret; ++ ++ drm_client_setup_with_fourcc(drm, DRM_FORMAT_RGB565); ++ ++ return 0; ++} ++ ++static int __maybe_unused rockchip_ebc_suspend(struct device *dev) ++{ ++ int ret = pm_runtime_force_suspend(dev); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static int __maybe_unused rockchip_ebc_resume(struct device *dev) ++{ ++ struct rockchip_ebc *ebc = dev_get_drvdata(dev); ++ ++ pr_info("%s %lld", __func__, ktime_get()); ++ ++ pm_runtime_force_resume(dev); ++ ++ return drm_mode_config_helper_resume(&ebc->drm); ++} ++ ++static int rockchip_ebc_runtime_suspend(struct device *dev) ++{ ++ struct rockchip_ebc *ebc = dev_get_drvdata(dev); ++ ++ regcache_cache_only(ebc->regmap, true); ++ ++ clk_disable_unprepare(ebc->dclk); ++ clk_disable_unprepare(ebc->hclk); ++ regulator_bulk_disable(EBC_NUM_SUPPLIES, ebc->supplies); ++ ++ return 0; ++} ++ ++static int rockchip_ebc_runtime_resume(struct device *dev) ++{ ++ struct rockchip_ebc *ebc = dev_get_drvdata(dev); ++ int ret; ++ ++ ret = regulator_bulk_enable(EBC_NUM_SUPPLIES, ebc->supplies); ++ if (ret) ++ return ret; ++ ++ ret = clk_prepare_enable(ebc->hclk); ++ if (ret) ++ goto err_disable_supplies; ++ ++ ret = clk_prepare_enable(ebc->dclk); ++ if (ret) ++ goto err_disable_hclk; ++ ++ /* ++ * Do not restore the LUT registers here, because the temperature or ++ * waveform may have changed since the last refresh. Instead, have the ++ * refresh thread program the LUT during the next refresh. ++ */ ++ regcache_cache_only(ebc->regmap, false); ++ regcache_mark_dirty(ebc->regmap); ++ regcache_sync(ebc->regmap); ++ ++ regmap_write(ebc->regmap, EBC_INT_STATUS, ++ EBC_INT_STATUS_DSP_END_INT_CLR | ++ EBC_INT_STATUS_LINE_FLAG_INT_MSK | ++ EBC_INT_STATUS_DSP_FRM_INT_MSK | ++ EBC_INT_STATUS_FRM_END_INT_MSK); ++ ++ return 0; ++ ++err_disable_hclk: ++ clk_disable_unprepare(ebc->hclk); ++err_disable_supplies: ++ regulator_bulk_disable(EBC_NUM_SUPPLIES, ebc->supplies); ++ ++ return ret; ++} ++ ++static int rockchip_ebc_prepare(struct device *dev) ++{ ++ struct rockchip_ebc *ebc = dev_get_drvdata(dev); ++ int ret; ++ ++ spin_lock(&ebc->work_item_lock); ++ ebc->work_item |= ROCKCHIP_EBC_WORK_ITEM_SUSPEND; ++ spin_unlock(&ebc->work_item_lock); ++ ++ ret = drm_mode_config_helper_suspend(&ebc->drm); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static const struct dev_pm_ops rockchip_ebc_dev_pm_ops = { ++ .prepare = rockchip_ebc_prepare, ++ SET_SYSTEM_SLEEP_PM_OPS(rockchip_ebc_suspend, rockchip_ebc_resume) ++ SET_RUNTIME_PM_OPS(rockchip_ebc_runtime_suspend, ++ rockchip_ebc_runtime_resume, NULL) ++}; ++ ++static bool rockchip_ebc_volatile_reg(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case EBC_DSP_START: ++ case EBC_INT_STATUS: ++ case EBC_CONFIG_DONE: ++ case EBC_VNUM: ++ return true; ++ default: ++ /* Do not cache the LUT registers. */ ++ return reg > EBC_WIN_MST2; ++ } ++} ++ ++static const struct regmap_config rockchip_ebc_regmap_config = { ++ .reg_bits = 32, ++ .reg_stride = 4, ++ .val_bits = 32, ++ .volatile_reg = rockchip_ebc_volatile_reg, ++ .max_register = 0x4ffc, /* end of EBC_LUT_DATA */ ++ .cache_type = REGCACHE_FLAT, ++}; ++ ++static const char *const rockchip_ebc_supplies[EBC_NUM_SUPPLIES] = { ++ "panel", ++ "vcom", ++ "vdrive", ++}; ++ ++static irqreturn_t rockchip_ebc_irq(int irq, void *dev_id) ++{ ++ struct rockchip_ebc *ebc = dev_id; ++ unsigned int status; ++ ++ regmap_read(ebc->regmap, EBC_INT_STATUS, &status); ++ ++ pr_debug("%s status=%d", __func__, status); ++ if (status & EBC_INT_STATUS_DSP_END_INT_ST) { ++ status |= EBC_INT_STATUS_DSP_END_INT_CLR; ++ complete(&ebc->display_end); ++ } ++ ++ regmap_write(ebc->regmap, EBC_INT_STATUS, status); ++ ++ return IRQ_HANDLED; ++} ++ ++static int rockchip_ebc_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct rockchip_ebc *ebc; ++ void __iomem *base; ++ int i, ret; ++ pr_info("%s start", __func__); ++ ++ if (dma_set_mask(dev, DMA_BIT_MASK(32))){ ++ dev_warn(dev, "rockchip-ebc: No suitable DMA available\n"); ++ } ++ ++ ebc = devm_drm_dev_alloc(dev, &rockchip_ebc_drm_driver, ++ struct rockchip_ebc, drm); ++ if (IS_ERR(ebc)) ++ return PTR_ERR(ebc); ++ ++ unsigned int width = 1872; ++ unsigned int height = 1404; ++ ebc->direct_mode = direct_mode; ++ ebc->gray4_pitch = width / 2; ++ ebc->gray4_size = width * height / 2; ++ ebc->phase_pitch = ebc->direct_mode ? width / 4 : width; ++ ebc->phase_size = ebc->phase_pitch * height; ++ ebc->num_pixels = width * height; ++ ebc->pixel_pitch = width; ++ ebc->height = height; ++ ebc->screen_rect = DRM_RECT_INIT(0, 0, width, height); ++ ++ ebc->y4_threshold_y1 = bw_threshold; ++ ++ switch (dithering_method) { ++ case DITHERING_BAYER: ++ ebc->dithering_texture = dither_bayer_04; ++ ebc->dithering_texture_size_hint = 4; ++ break; ++ case DITHERING_BLUE_NOISE_16: ++ ebc->dithering_texture = dither_blue_noise_16; ++ ebc->dithering_texture_size_hint = 16; ++ break; ++ case DITHERING_BLUE_NOISE_32: ++ fallthrough; ++ default: ++ ebc->dithering_texture = dither_blue_noise_32; ++ ebc->dithering_texture_size_hint = 32; ++ break; ++ } ++ ++ ebc->final_off_screen = drmm_kzalloc(&ebc->drm, ebc->num_pixels, GFP_KERNEL); ++ ebc->packed_inner_outer_nextprev = vmalloc(3 * ebc->num_pixels); // drmm_kmalloc(&ebc->drm, ebc->num_pixels, GFP_KERNEL); ++ if (!direct_mode) { ++ ebc->hardware_wf = drmm_kzalloc(&ebc->drm, 4 * EBC_NUM_LUT_REGS, ++ GFP_KERNEL); ++ ebc->zero = drmm_kzalloc(&ebc->drm, ebc->num_pixels, GFP_KERNEL | GFP_DMA); ++ } ++ ebc->hints_ioctl = vmalloc(ebc->num_pixels); // drmm_kmalloc(&ebc->drm, ebc->num_pixels, GFP_KERNEL); ++ ebc->phase[0] = drmm_kzalloc(&ebc->drm, ebc->phase_size, GFP_KERNEL | GFP_DMA); ++ ebc->phase[1] = drmm_kzalloc(&ebc->drm, ebc->phase_size, GFP_KERNEL | GFP_DMA); ++ if (!(ebc->final_off_screen && ebc->packed_inner_outer_nextprev && ++ (direct_mode || (ebc->hardware_wf && ebc->zero)) && ++ ebc->hints_ioctl && ebc->phase[0] && ++ ebc->phase[1])) { ++ return dev_err_probe(dev, -ENOMEM, "Failed to allocate buffers\n"); ++ } ++ ebc->phase_sequence = drmm_kzalloc( ++ &ebc->drm, sizeof(struct drm_rockchip_ebc_phase_sequence), ++ GFP_KERNEL); ++ if (!ebc->phase_sequence) ++ return dev_err_probe(dev, -ENOMEM, "Failed to allocate phase_sequence buffer\n"); ++ ebc->phase_handles[0] = dma_map_single(dev, ebc->phase[0], ebc->phase_size, ++ DMA_TO_DEVICE); ++ if (dma_mapping_error(dev, ebc->phase_handles[0])) { ++ return dev_err_probe(dev, -ENOMEM, "phase_handles[0] dma mapping error"); ++ } ++ ebc->phase_handles[1] = dma_map_single(dev, ebc->phase[1], ebc->phase_size, ++ DMA_TO_DEVICE); ++ if (dma_mapping_error(dev, ebc->phase_handles[1])) { ++ dma_unmap_single(dev, ebc->phase_handles[0], ebc->phase_size, DMA_TO_DEVICE); ++ return dev_err_probe(dev, -ENOMEM, "phase_handles[1] dma mapping error"); ++ } ++ if (!direct_mode) { ++ ebc->zero_handle = dma_map_single( ++ dev, ebc->zero, ebc->gray4_size, DMA_TO_DEVICE); ++ if (dma_mapping_error(dev, ebc->zero_handle)) { ++ dma_unmap_single(dev, ebc->phase_handles[0], ebc->phase_size, DMA_TO_DEVICE); ++ dma_unmap_single(dev, ebc->phase_handles[1], ebc->phase_size, DMA_TO_DEVICE); ++ return dev_err_probe(dev, -ENOMEM, ++ "zero_handle dma mapping error"); ++ } ++ dma_sync_single_for_device(dev, ebc->zero_handle, ebc->gray4_size, DMA_TO_DEVICE); ++ } ++ dma_sync_single_for_device(dev, ebc->phase_handles[0], ebc->phase_size, DMA_TO_DEVICE); ++ dma_sync_single_for_device(dev, ebc->phase_handles[1], ebc->phase_size, ++ DMA_TO_DEVICE); ++ ++ memset(ebc->hints_ioctl, default_hint & ROCKCHIP_EBC_HINT_MASK, ++ ebc->num_pixels); ++ ++ // Custom waveform ++ if (!direct_mode) { ++ *((u32 *) ebc->hardware_wf + 16) = 0x55555555; ++ *((u32 *) ebc->hardware_wf + 32) = 0xaaaaaaaa; ++ } ++ ++ ebc->driver_mode = ROCKCHIP_EBC_DRIVER_MODE_NORMAL; ++ ebc->redraw_delay = redraw_delay; ++ ++ // Sensible temperature default ++ ebc->temperature = temp_override > 0 ? temp_override : 25; ++ ebc->work_item = ROCKCHIP_EBC_WORK_ITEM_CHANGE_LUT | ++ ROCKCHIP_EBC_WORK_ITEM_INIT; ++ ++ spin_lock_init(&ebc->work_item_lock); ++ spin_lock_init(&ebc->hints_ioctl_lock); ++ spin_lock_init(&ebc->phase_sequence_lock); ++ ebc->suspend_was_requested = 0; ++ ++ platform_set_drvdata(pdev, ebc); ++ init_completion(&ebc->display_end); ++ ++ base = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(base)) ++ return PTR_ERR(base); ++ ++ ebc->regmap = devm_regmap_init_mmio(dev, base, ++ &rockchip_ebc_regmap_config); ++ if (IS_ERR(ebc->regmap)) ++ return PTR_ERR(ebc->regmap); ++ ++ regcache_cache_only(ebc->regmap, true); ++ ++ ebc->dclk = devm_clk_get(dev, "dclk"); ++ if (IS_ERR(ebc->dclk)) ++ return dev_err_probe(dev, PTR_ERR(ebc->dclk), ++ "Failed to get dclk\n"); ++ ++ ebc->hclk = devm_clk_get(dev, "hclk"); ++ if (IS_ERR(ebc->hclk)) ++ return dev_err_probe(dev, PTR_ERR(ebc->hclk), ++ "Failed to get hclk\n"); ++ ++ ebc->cpll_333m = devm_clk_get(dev, "cpll_333m"); ++ if (IS_ERR(ebc->cpll_333m)) ++ return dev_err_probe(dev, PTR_ERR(ebc->cpll_333m), ++ "Failed to get cpll_333m\n"); ++ ++ ebc->temperature_channel = devm_iio_channel_get(dev, NULL); ++ if (IS_ERR(ebc->temperature_channel)) ++ return dev_err_probe(dev, PTR_ERR(ebc->temperature_channel), ++ "Failed to get temperature I/O channel\n"); ++ ++ for (i = 0; i < EBC_NUM_SUPPLIES; i++) ++ ebc->supplies[i].supply = rockchip_ebc_supplies[i]; ++ ++ ret = devm_regulator_bulk_get(dev, EBC_NUM_SUPPLIES, ebc->supplies); ++ if (ret) ++ return dev_err_probe(dev, ret, "Failed to get supplies\n"); ++ ++ ret = devm_request_irq(dev, platform_get_irq(pdev, 0), ++ rockchip_ebc_irq, 0, dev_name(dev), ebc); ++ if (ret < 0) ++ return dev_err_probe(dev, ret, "Failed to request IRQ\n"); ++ ++ pm_runtime_set_autosuspend_delay(dev, EBC_SUSPEND_DELAY_MS); ++ pm_runtime_use_autosuspend(dev); ++ pm_runtime_enable(dev); ++ if (!pm_runtime_enabled(dev)) { ++ ret = rockchip_ebc_runtime_resume(&pdev->dev); ++ if (ret) ++ return ret; ++ } ++ ++ ret = rockchip_ebc_waveform_init(ebc); ++ if (ret) ++ return ret; ++ ++ // Make sure ebc->lut_custom_active is initialised ++ rockchip_ebc_change_lut(ebc); ++ ++ ebc->temp_upd_thread = ++ kthread_create(rockchip_ebc_temp_upd_thread, ebc, ++ "ebc-tempupd/%s", dev_name(dev)); ++ if (IS_ERR(ebc->temp_upd_thread)) { ++ ret = dev_err_probe( ++ dev, PTR_ERR(ebc->temp_upd_thread), ++ "Failed to start temperature update thread"); ++ goto err_disable_pm; ++ } ++ kthread_park(ebc->temp_upd_thread); ++ ++ ebc->refresh_thread = kthread_create(rockchip_ebc_refresh_thread, ++ ebc, "ebc-refresh/%s", ++ dev_name(dev)); ++ if (IS_ERR(ebc->refresh_thread)) { ++ ret = dev_err_probe(dev, PTR_ERR(ebc->refresh_thread), ++ "Failed to start refresh thread\n"); ++ goto err_disable_pm; ++ } ++ ++ kthread_park(ebc->refresh_thread); ++ sched_set_fifo(ebc->refresh_thread); ++ ++ ret = rockchip_ebc_drm_init(ebc); ++ if (ret) ++ return ret; ++ ++ return 0; ++ ++err_disable_pm: ++ pm_runtime_disable(dev); ++ if (!pm_runtime_status_suspended(dev)) ++ rockchip_ebc_runtime_suspend(dev); ++ ++ return ret; ++} ++ ++static void rockchip_ebc_remove(struct platform_device *pdev) ++{ ++ struct rockchip_ebc *ebc = platform_get_drvdata(pdev); ++ struct device *dev = &pdev->dev; ++ // pr_info("%s executing", __func__); ++ ++ drm_dev_unregister(&ebc->drm); ++ kthread_stop(ebc->refresh_thread); ++ kthread_stop(ebc->temp_upd_thread); ++ drm_atomic_helper_shutdown(&ebc->drm); ++ ++ dma_unmap_single(dev, ebc->phase_handles[0], ebc->phase_size, DMA_TO_DEVICE); ++ dma_unmap_single(dev, ebc->phase_handles[1], ebc->phase_size, DMA_TO_DEVICE); ++ ++ if (!direct_mode) { ++ dma_unmap_single(dev, ebc->zero_handle, ebc->gray4_size, ++ DMA_TO_DEVICE); ++ } ++ ++ vfree(ebc->hints_ioctl); ++ vfree(ebc->packed_inner_outer_nextprev); ++ ++ pm_runtime_disable(dev); ++ if (!pm_runtime_status_suspended(dev)) ++ rockchip_ebc_runtime_suspend(dev); ++} ++ ++static void rockchip_ebc_shutdown(struct platform_device *pdev) ++{ ++ struct rockchip_ebc *ebc = platform_get_drvdata(pdev); ++ struct device *dev = &pdev->dev; ++ // pr_info("%s executing", __func__); ++ ++ kthread_stop(ebc->refresh_thread); ++ kthread_stop(ebc->temp_upd_thread); ++ drm_atomic_helper_shutdown(&ebc->drm); ++ ++ if (!pm_runtime_status_suspended(dev)) ++ rockchip_ebc_runtime_suspend(dev); ++} ++ ++static const struct of_device_id rockchip_ebc_of_match[] = { ++ { .compatible = "rockchip,rk3568-ebc" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, rockchip_ebc_of_match); ++ ++static struct platform_driver rockchip_ebc_driver = { ++ .probe = rockchip_ebc_probe, ++ .remove = rockchip_ebc_remove, ++ .shutdown = rockchip_ebc_shutdown, ++ .driver = { ++ .name = "rockchip-ebc", ++ .of_match_table = rockchip_ebc_of_match, ++ .pm = &rockchip_ebc_dev_pm_ops, ++ }, ++}; ++module_platform_driver(rockchip_ebc_driver); ++ ++MODULE_AUTHOR("Samuel Holland , Maximilian Weigand, hrdl "); ++MODULE_DESCRIPTION("Rockchip EBC driver"); ++MODULE_LICENSE("GPL v2"); +diff --git a/drivers/gpu/drm/rockchip/rockchip_ebc.h b/drivers/gpu/drm/rockchip/rockchip_ebc.h +new file mode 100644 +index 000000000..9d3ba1363 +--- /dev/null ++++ b/drivers/gpu/drm/rockchip/rockchip_ebc.h +@@ -0,0 +1,252 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Copyright (C) 2021-2022 Samuel Holland ++ Copyright (C) 2025 hrdl ++ */ ++ ++#ifndef _ROCKCHIP_EBC_H ++#define _ROCKCHIP_EBC_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define ROCKCHIP_EBC_CUSTOM_WF_NUM_SEQS 6 ++#define ROCKCHIP_EBC_CUSTOM_WF_SEQ_SHIFT 6 ++#define ROCKCHIP_EBC_CUSTOM_WF_SEQ_LENGTH (1 << ROCKCHIP_EBC_CUSTOM_WF_SEQ_SHIFT) ++#define ROCKCHIP_EBC_CUSTOM_WF_LUT_SIZE (16 * 16 * ROCKCHIP_EBC_CUSTOM_WF_SEQ_LENGTH) ++ ++// The order matters is related to various buffer formats and NEON optimisations ++ ++#define ROCKCHIP_EBC_CUSTOM_WF_DU 0 ++#define ROCKCHIP_EBC_CUSTOM_WF_DU4 1 ++#define ROCKCHIP_EBC_CUSTOM_WF_GL16 2 ++#define ROCKCHIP_EBC_CUSTOM_WF_GC16 3 ++#define ROCKCHIP_EBC_CUSTOM_WF_INIT 4 ++#define ROCKCHIP_EBC_CUSTOM_WF_WAITING 5 ++ ++#define EBC_NUM_SUPPLIES 3 ++ ++/* ++ * @lower_temp: Lower temperature limit for this waveform ++ * @upper_temp: Upper temperature limit for this waveform ++ * @offsets: offsets for DU, DU4, GL16, GC16, INIT ++ * @lut: compact LUT containing phase information (00, 01, 10) and inner nums at every outer num. Sequences start at offsets, are terminated by 0x00, and are index by src, dst, outer_num ++ */ ++struct drm_epd_lut_temp_v2 { ++ s32 temp_lower; ++ s32 temp_upper; ++ // 16 bytes for NEON loading ++ u8 offsets[16]; ++ u8 lut[ROCKCHIP_EBC_CUSTOM_WF_LUT_SIZE]; ++}; ++ ++struct drm_epd_lut_v2 { ++ // A single LUT struct for every temperature range ++ unsigned int num_temp_ranges; ++ struct drm_epd_lut_temp_v2 *luts; ++}; ++ ++struct rockchip_ebc { ++ struct clk *dclk; ++ struct clk *hclk; ++ struct clk *cpll_333m; ++ ++ // Hardware-related display timings ++ u32 dsp_start; ++ u16 act_width; ++ u16 act_height; ++ u16 hact_start; ++ u16 vact_start; ++ ++ // ebc: indicate frame completion ++ struct completion display_end; ++ struct iio_channel *temperature_channel; ++ struct regmap *regmap; ++ struct regulator_bulk_data supplies[EBC_NUM_SUPPLIES]; ++ ++ // DRM ++ struct drm_crtc crtc; ++ struct drm_device drm; ++ struct drm_encoder encoder; ++ struct drm_plane plane; ++ ++ // LUT holding waveform information ++ struct drm_epd_lut lut; ++ struct drm_epd_lut_file lut_file; ++ struct drm_epd_lut_v2 lut_custom; ++ struct drm_epd_lut_temp_v2 *lut_custom_active; ++ u8 inner_15_0; ++ u8 inner_0_15; ++ ++ struct task_struct *temp_upd_thread; ++ struct task_struct *refresh_thread; ++ ++ struct drm_rect screen_rect; ++ // final buffer to display before suspending ++ u8 *final_off_screen; ++ spinlock_t hints_ioctl_lock; ++ // Used to keep next/prev in 3WIN mode at zero. Read by controller ++ u8 *zero; ++ u8 *hardware_wf; ++ // inner: PPLI IIIII, indicating the current phase (0, 1, 2), whether this is the (L)ast entry, and the number of frames left until a phase shift is required ++ // outer: indicating the position in the driver-based LUT ++ // next_prev: NNNN PPPP, dispaly state after/before the ongoing/previous update finishes ++ u8 *packed_inner_outer_nextprev; ++ // Hints written by ioctl_rect_hints (for now) and read by rockchip_ebc_plane_atomic_update ++ u8 *hints_ioctl; ++ // Phase buffer, 1 pixel per byte in 3WIN mode and 4 in direct mode (not implemented yet). Written by refresh thread and read by controller. Flipped after every display frame ++ u8 *phase[2]; ++ dma_addr_t phase_handles[2]; ++ dma_addr_t zero_handle; ++ // Pitches and sizes ++ u32 gray4_pitch; ++ u32 gray4_size; ++ u32 phase_pitch; ++ u32 phase_size; ++ u32 num_pixels; ++ u32 pixel_pitch; ++ u32 height; ++ // Dithering / thresholding information ++ u8 y4_threshold_y1; ++ aligned_u64 lut_y2_y4[2]; ++ aligned_u64 lut_y2_y4_dithered[4]; ++ const u8 *dithering_texture; ++ // 4, 16, or 32 rows. Pitch is 16 for 4 and 16, and 32 for 32 ++ u8 dithering_texture_size_hint; ++ // whether we use direct mode or 3WIN mode. Only 3WIN mode is implemented right now ++ bool direct_mode; ++ int driver_mode; ++ int redraw_delay; ++ // Used to change the driver LUT due to temperature update, trigger a global refresh, or buffer changes ++ spinlock_t work_item_lock; ++ u32 work_item; ++ // used to detect when we are suspending so we can do different things to ++ // the ebc display depending on whether we are sleeping or suspending ++ int suspend_was_requested; ++ // Cached temperature in deg C ++ int temperature; ++ struct drm_rockchip_ebc_phase_sequence *phase_sequence; ++ spinlock_t phase_sequence_lock; ++}; ++ ++/** ++ * struct rockchip_ebc_ctx - DRM-related context for performing display refreshes ++ * ++ * @kref: Reference count, maintained as part of the CRTC's atomic state ++ * @buffer_switch_lock: Lock protecting write-access to refresh_buffer_index ++ * @refresh_index: which buffer is being read by the refresh thread ++ * @update_index: which buffer is the next one to be written by atomic_update ++ * @dst_clip: clip i belonging to buffer i. Independent of the the other buffers ++ * @src_clip_extended: the union of clips != i blitted after i was blitted ++ * @prelim_target_buffer: triple-buffered prelim_target ++ * @hints_buffer: triple-buffered hints_buffer ++*/ ++struct rockchip_ebc_ctx { ++ struct kref kref; ++ spinlock_t buffer_switch_lock; ++ int next_refresh_index; ++ int refresh_index; ++ int update_index; ++ struct drm_rect dst_clip[3]; ++ struct drm_rect src_clip_extended[3]; ++ u8 *prelim_target_buffer[3]; ++ u8 *hints_buffer[3]; ++ u8 not_after_others[3]; ++}; ++ ++/** ++ * struct rockchip_ebc_area - describes a damaged area of the display ++ * ++ * @list: Used to put this area in the state/context/refresh thread list ++ * @clip: The rectangular clip of this damage area ++ * @frame_begin: The frame number when this damage area starts being refreshed ++ */ ++struct rockchip_ebc_area { ++ struct drm_rect clip; ++}; ++ ++/** ++ * rockchip_ebc_drm_rect_extend - extend rect to include (x, y) ++ * @r: rectangle ++ * @x: x coordinate ++ * @y: y coordinate ++ */ ++inline void rockchip_ebc_drm_rect_extend(struct drm_rect *r, int x, int y) ++{ ++ r->x1 = min(r->x1, x); ++ r->x2 = max(r->x2, x + 1); ++ r->y1 = min(r->y1, y); ++ r->y2 = max(r->y2, y + 1); ++} ++ ++/** ++ * rockchip_ebc_drm_rect_extend_rect - extend rect r1 to include r2 ++ * @r: rectangle ++ * @x: x coordinate ++ * @y: y coordinate ++ */ ++inline void rockchip_ebc_drm_rect_extend_rect(struct drm_rect *r1, const struct drm_rect *r2) ++{ ++ r1->x1 = min(r1->x1, r2->x1); ++ r1->x2 = max(r1->x2, r2->x2); ++ r1->y1 = min(r1->y1, r2->y1); ++ r1->y2 = max(r1->y2, r2->y2); ++} ++ ++void rockchip_ebc_schedule_advance_fast_neon(const struct rockchip_ebc *ebc, ++ const u8 *prelim_target, u8 *hints, ++ u8 *phase_buffer, ++ struct drm_rect *clip_ongoing, ++ struct drm_rect *clip_ongoing_or_waiting, ++ u8 early_cancellation_addition, ++ u8 force_wf, u8 force_hint, ++ u8 force_hint_mask, bool allow_schedule); ++ ++void rockchip_ebc_schedule_advance_neon(const struct rockchip_ebc *ebc, ++ const u8 *prelim_target, u8 *hints, ++ u8 *phase_buffer, ++ struct drm_rect *clip_ongoing, ++ struct drm_rect *clip_ongoing_or_waiting, ++ u8 early_cancellation_addition, ++ u8 force_wf, u8 force_hint, ++ u8 force_hint_mask, bool allow_schedule); ++ ++void rockchip_ebc_blit_y4_high_low_neon(u8 *dst, u8 *y4_high_src, ++ u8 *y4_low_src, unsigned int size); ++ ++void rockchip_ebc_blit_y421_y4_neon(const struct rockchip_ebc *ebc, ++ u8 *y4_dst, const u8 *y421_src, ++ struct drm_rect *clip); ++ ++void rockchip_ebc_blit_fb_rgb565_y4_hints_neon(const struct rockchip_ebc *ebc, ++ struct drm_rect *dst_clip, ++ u8 *prelim_target_atomic_update, ++ u8 *hints_atomic_update, ++ const void *vaddr, ++ const struct drm_framebuffer *fb, ++ const struct drm_rect *src_clip); ++ ++void rockchip_ebc_blit_fb_xrgb8888_y4_hints_neon( ++ const struct rockchip_ebc *ebc, struct drm_rect *dst_clip, ++ u8 *prelim_target_atomic_update, u8 *hints_atomic_update, ++ const void *vaddr, const struct drm_framebuffer *fb, ++ const struct drm_rect *src_clip); ++ ++void rockchip_ebc_blit_fb_r8_y4_hints_neon(const struct rockchip_ebc *ebc, ++ struct drm_rect *dst_clip, ++ u8 *prelim_target_atomic_update, ++ u8 *hints_atomic_update, ++ const void *vaddr, ++ const struct drm_framebuffer *fb, ++ const struct drm_rect *src_clip); ++ ++void rockchip_ebc_reset_inner_outer_neon(const struct rockchip_ebc *ebc); ++ ++#define DRM_RECT_EMPTY_EXTANDABLE DRM_RECT_INIT(100000, 100000, -100000, -100000); ++ ++#endif /* _ROCKCHIP_EBC_H */ +diff --git a/drivers/gpu/drm/rockchip/rockchip_ebc_blit_neon.c b/drivers/gpu/drm/rockchip/rockchip_ebc_blit_neon.c +new file mode 100644 +index 000000000..2fae7d242 +--- /dev/null ++++ b/drivers/gpu/drm/rockchip/rockchip_ebc_blit_neon.c +@@ -0,0 +1,1253 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Copyright (C) 2025 hrdl ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include "rockchip_ebc.h" ++ ++#include ++ ++#define NEON_PR_INFO(_q8_name) \ ++ { \ ++ vst1q_u8(u8_tmp, (_q8_name)); \ ++ pr_info("%s %s \t=%16ph", __func__, #_q8_name, u8_tmp); \ ++ } ++ ++#define NEON_PR_INFOS(_q8s_name) \ ++ { \ ++ vst1_u8(u8_tmp, (_q8s_name)); \ ++ pr_info("%s %s \t=%8ph", __func__, #_q8s_name, u8_tmp); \ ++ } ++ ++// #define ROCKCHIP_EBC_DEBUG_NEON ++// #define ROCKCHIP_EBC_DEBUG_NEON_FAST ++ ++void rockchip_ebc_schedule_advance_fast_neon( ++ const struct rockchip_ebc *ebc, const u8 *prelim_target, u8 *hints, ++ u8 *phase_buffer, struct drm_rect *clip_ongoing, ++ struct drm_rect *clip_ongoing_or_waiting, ++ u8 early_cancellation_addition, u8 force_wf, u8 force_hint, ++ u8 force_hint_mask, bool allow_schedule) ++{ ++ unsigned int pixel_pitch = ebc->pixel_pitch; ++ unsigned int prefetch_pitch = (pixel_pitch >> 6) << 6; ++ unsigned int phases_prefetch_pitch = (pixel_pitch >> 8) << 6; ++ // unsigned int packed_pitch = 3 * pixel_pitch; ++ unsigned int phase_pitch = ebc->phase_pitch; ++ // struct drm_rect clip_ongoing_or_waiting_new = DRM_RECT_EMPTY_EXTANDABLE; ++ ++#ifdef ROCKCHIP_EBC_DEBUG_NEON_FAST ++ aligned_u64 _tmp[2]; ++ u8 *u8_tmp = (u8 *)_tmp; ++#endif ++ ++ // 16 byte block size for NEON ++ unsigned int x_start = max(0, min(clip_ongoing_or_waiting->x1 & ~15, ++ (int)ebc->pixel_pitch - 16)); ++ ++ uint8x16_t q8_0x00 = vdupq_n_u8(0x00); ++ uint8x16_t q8_0x01 = vdupq_n_u8(0x01); ++ uint8x16_t q8_0x0f = vdupq_n_u8(0x0f); ++ uint8x16_t q8_0x1f = vdupq_n_u8(0x1f); ++ uint8x16_t q8_inner_0_15 = vdupq_n_u8(ebc->inner_0_15); ++ uint8x16_t q8_inner_15_0 = vdupq_n_u8(ebc->inner_15_0); ++ uint8x16_t q8_early_cancellation_addition = ++ vdupq_n_u8(early_cancellation_addition); ++ uint8x16_t q8_allow_schedule = ++ vcgtq_u8(vdupq_n_u8(allow_schedule), q8_0x00); ++ ++ uint32x2_t q32s_0x10 = vdup_n_u32(0x10); ++ uint16x4_t q16s_mins = vdup_n_u16(0xffff); ++ uint16x4_t q16s_maxs = vdup_n_u16(0); ++ uint64x1_t q64s_0x00 = vdup_n_u64(0); ++ ++ for (unsigned int y = max(0, clip_ongoing_or_waiting->y1); ++ y < min((int) ebc->height, clip_ongoing_or_waiting->y2); ++y) { ++ int offset = y * pixel_pitch + x_start; ++ u8 *packed_inner_outer_nextprev_line = ebc->packed_inner_outer_nextprev + offset * 3; ++ u8 *phases_line = ++ phase_buffer + y * phase_pitch + (x_start >> 2); ++ const u8 *prelim_target_line = prelim_target + offset; ++ uint32x2_t q32s_xy = vdup_n_u32(y << 16 | x_start); ++ ++ for (unsigned int x = x_start; x < clip_ongoing_or_waiting->x2; ++ x += 16, packed_inner_outer_nextprev_line += 48, ++ phases_line += 4, prelim_target_line += 16) { ++ uint8x16x3_t q8_inner_outer_nextprev = ++ vld3q_u8(packed_inner_outer_nextprev_line); ++ /* __builtin_prefetch( */ ++ /* packed_inner_outer_nextprev_line + packed_pitch, 0, 0); */ ++ uint8x16_t q8_inner = q8_inner_outer_nextprev.val[0]; ++ uint8x16_t q8_outer = q8_inner_outer_nextprev.val[1]; ++ uint8x16_t q8_next_prev = ++ q8_inner_outer_nextprev.val[2]; ++ uint8x16_t q8_prelim_target = ++ vld1q_u8(prelim_target_line); ++ __builtin_prefetch(prelim_target_line + prefetch_pitch, 0, 0); ++ ++ // Extract packed Y4 values and hints ++ uint8x16_t q8_inner_num = vandq_u8(q8_inner, q8_0x1f); ++ uint8x16_t q8_next = vshrq_n_u8(q8_next_prev, 4); ++ uint8x16_t q8_prev = vandq_u8(q8_next_prev, q8_0x0f); ++ uint8x16_t q8_prelim = vshrq_n_u8(q8_prelim_target, 4); ++ ++ // Start transforming ++ uint8x16_t q8_inner_num_is_1 = ++ vceqq_u8(q8_inner_num, q8_0x01); ++ // Saturating subtraction ++ uint8x16_t q8_inner_num_new = ++ vqsubq_u8(q8_inner_num, q8_0x01); ++ // Restore phase and is_last information ++ uint8x16_t q8_inner_new = ++ vbslq_u8(q8_0x1f, q8_inner_num_new, q8_inner); ++ ++ // These can be scheduled (next != target, next != prelim) or sent to WAITING (next == prelim, next != target) ++ // TODO: merge these into a single q8_inner_num <= 1 ++ uint8x16_t q8_idle = vceqzq_u8(q8_inner); ++ uint8x16_t q8_idle_finish = ++ vorrq_u8(q8_idle, q8_inner_num_is_1); ++ ++ // 1. Schedule: start target or prelim ++ uint8x16_t q8_start_scheduled = vandq_u8( ++ q8_allow_schedule, ++ vmvnq_u8(vceqq_u8(q8_prelim, q8_next))); ++ uint8x16_t q8_next_new = vbslq_u8(q8_start_scheduled, ++ q8_prelim, q8_next); ++ uint8x16_t q8_prev_new = ++ vbslq_u8(q8_start_scheduled, q8_next, q8_prev); ++ ++ uint8x16_t q8_next_prev_new = vorrq_u8( ++ vshlq_n_u8(q8_next_new, 4), q8_prev_new); ++ ++ // 3. Move remaining finished ones to IDLE or WAITING ++ uint8x16_t q8_no_start_scheduled = ++ vmvnq_u8(q8_start_scheduled); ++ uint8x16_t q8_finish_no_start_scheduled = ++ vandq_u8(q8_inner_num_is_1, q8_no_start_scheduled); ++ uint8x16_t q8_outer_eq_0 = vceqzq_u8(q8_outer); ++ uint8x16_t q8_outer_gt_0 = vmvnq_u8(q8_outer_eq_0); ++ // Start waiting if: ++ // We finish, but we don't start schedule/redraw AND next != target, AND allow_schedule ++ // Or: We are not running AND we didn't schedule (next=prelim) AND next != target AND allow_schedule ++ ++ uint8x16_t q8_start_idle = vandq_u8( ++ q8_finish_no_start_scheduled, q8_outer_gt_0); ++ uint8x16_t q8_start = q8_start_scheduled; ++ ++ // 4. Get outer_new ++ uint8x16_t q8_outer_new = vbslq_u8(q8_inner_num_is_1, q8_0x00, q8_outer); ++ q8_outer_new = ++ vbslq_u8(q8_start, q8_0x01, q8_outer_new); ++ // Or: replace q8_wf with WAITING before the previous statement ++ ++ // 5. Get inner_new ++ uint8x16_t q8_inner_new_from_lut = ++ vbslq_u8(vceqzq_u8(q8_next_new), q8_inner_15_0, ++ q8_inner_0_15); ++ q8_inner_new = ++ vbslq_u8(q8_start, ++ q8_inner_new_from_lut, q8_inner_new); ++ q8_inner_new = ++ vbslq_u8(q8_start_idle, q8_0x01, q8_inner_new); ++ ++ // 6. Early cancellation ++ uint8x16_t q8_cancel = ++ vandq_u8(q8_start, vmvnq_u8(q8_idle_finish)); ++ uint8x16_t q8_to_subtract = ++ vandq_u8(q8_cancel, q8_inner_num); ++ q8_inner_num_new = ++ vqsubq_u8(vandq_u8(q8_inner_new, q8_0x1f), ++ q8_to_subtract); ++ q8_inner_num_new = vqaddq_u8( ++ q8_inner_num_new, ++ vandq_u8(q8_cancel, ++ q8_early_cancellation_addition)); ++ q8_inner_new = vbslq_u8(q8_0x1f, q8_inner_num_new, ++ q8_inner_new); ++ uint8x16x3_t q8_ionp_new = {{q8_inner_new, q8_outer_new, q8_next_prev_new}}; ++ vst3q_u8(packed_inner_outer_nextprev_line, q8_ionp_new); ++ ++#ifdef CONFIG_DRM_ROCKCHIP_EBC_3WIN_MODE ++ if (!direct_mode) { ++ vst1q_u8(phases_line, vshrq_n_u8(q8_inner_new, 6); ++ } else ++#endif ++ { ++ uint16x8_t q16_phases = vreinterpretq_u16_u8( ++ vshrq_n_u8(q8_inner_new, 6)); ++ q16_phases = vorrq_u16(q16_phases, ++ vshrq_n_u16(q16_phases, 6)); ++ uint8x8_t q8s_phases1 = vmovn_u16(q16_phases); ++ q16_phases = vreinterpretq_u16_u8( ++ vcombine_u8(q8s_phases1, q8s_phases1)); ++ uint8x8_t q8s_phases = vmovn_u16(vorrq_u16( ++ q16_phases, vshrq_n_u16(q16_phases, 4))); ++ __builtin_prefetch(phases_line + phases_prefetch_pitch, 1, 0); ++ vst1_lane_u32((u32 *)phases_line, ++ vreinterpret_u32_u8(q8s_phases), ++ 0); ++ } ++ ++#ifdef ROCKCHIP_EBC_DEBUG_NEON_FAST ++ if (x == 928 && y == 702) { ++ NEON_PR_INFO(q8_prelim_target); ++ NEON_PR_INFO(q8_finish_no_start_scheduled); ++ NEON_PR_INFO(q8_no_start_scheduled); ++ NEON_PR_INFO(q8_outer_gt_0); ++ NEON_PR_INFO(q8_start_idle); ++ NEON_PR_INFO(q8_next_prev_new); ++ NEON_PR_INFO(q8_next_prev); ++ NEON_PR_INFO(q8_inner); ++ NEON_PR_INFO(q8_outer); ++ NEON_PR_INFO(q8_allow_schedule); ++ NEON_PR_INFO(q8_inner_0_15); ++ NEON_PR_INFO(q8_inner_15_0); ++ NEON_PR_INFO(q8_inner_new); ++ NEON_PR_INFO(q8_inner_new_from_lut); ++ NEON_PR_INFO(q8_cancel); ++ NEON_PR_INFO(q8_to_subtract); ++ NEON_PR_INFO(q8_inner_num_new); ++ NEON_PR_INFO(q8_outer_new); ++ NEON_PR_INFO(q16_phases); ++ NEON_PR_INFOS(q8s_phases1); ++ NEON_PR_INFOS(q8s_phases); ++ } ++#endif ++ ++ uint64x1_t q64s_any_ongoing = vreinterpret_u64_u8( ++ vorr_u8(vget_high_u8(q8_inner_new), ++ vget_low_u8(q8_inner_new))); ++ uint32x2_t q32s_mask = ++ vreinterpret_u32_u64(vcgt_u64(q64s_any_ongoing, q64s_0x00)); ++ q16s_maxs = vmax_u16(q16s_maxs, ++ vreinterpret_u16_u32(vand_u32( ++ q32s_xy, q32s_mask))); ++ q16s_mins = vmin_u16( ++ q16s_mins, ++ vreinterpret_u16_u32(vorr_u32( ++ q32s_xy, vmvn_u32(q32s_mask)))); ++ q32s_xy = vqadd_u32(q32s_xy, q32s_0x10); ++ } ++ } ++ ++ aligned_u64 u64_mins, u64_maxs; ++ u16 *mins = (u16 *)&u64_mins; ++ u16 *maxs = (u16 *)&u64_maxs; ++ vst1_lane_u64(&u64_mins, vreinterpret_u64_u16(q16s_mins), 0); ++ vst1_lane_u64(&u64_maxs, vreinterpret_u64_u16(q16s_maxs), 0); ++ clip_ongoing->x1 = mins[0]; ++ clip_ongoing->y1 = mins[1]; ++ clip_ongoing->x2 = maxs[0] + 16; ++ clip_ongoing->y2 = maxs[1] + 1; ++ *clip_ongoing_or_waiting = *clip_ongoing; ++} ++EXPORT_SYMBOL(rockchip_ebc_schedule_advance_fast_neon); ++ ++void rockchip_ebc_schedule_advance_neon( ++ const struct rockchip_ebc *ebc, const u8 *prelim_target, u8 *hints, ++ u8 *phase_buffer, struct drm_rect *clip_ongoing, ++ struct drm_rect *clip_ongoing_or_waiting, ++ u8 early_cancellation_addition, u8 force_wf, u8 force_hint, ++ u8 force_hint_mask, bool allow_schedule) ++{ ++ bool direct_mode = ebc->direct_mode; ++ unsigned int pixel_pitch = ebc->pixel_pitch; ++ // unsigned int packed_pitch = 3 * pixel_pitch; ++ unsigned int phase_pitch = direct_mode ? ebc->phase_pitch : ++ ebc->pixel_pitch; ++ unsigned int prefetch_pitch = (pixel_pitch << 6) >> 6; ++ unsigned int phases_prefetch_pitch = (pixel_pitch >> 8) << 6; ++ u8 *lut = ebc->lut_custom_active->lut; ++ prefetch_range(lut, sizeof(struct drm_epd_lut_temp_v2)); ++ ++ // 16 u16 values used as addresses for non-NEON code ++ aligned_u64 _idxs[4]; ++ // 16 u8 values aligned for LUT using non-NEON code ++ aligned_u64 _lookup[2]; ++ ++ u16 *u16_idxs = (u16 *)_idxs; ++ u8 *u8_lookup = (u8 *)_lookup; ++ memset(u8_lookup, 0, 16); ++ // Initialise for q8_us_y4_binary_table ++ u8_lookup[0] = 0xff; ++ u8_lookup[15] = 0xff; ++ ++#ifdef ROCKCHIP_EBC_DEBUG_NEON ++ aligned_u64 _tmp[2]; ++ u8 *u8_tmp = (u8 *)_tmp; ++#endif ++ ++ // 16 byte block size for NEON ++ unsigned int x_start = max(0, min(clip_ongoing_or_waiting->x1 & ~15, ++ (int)ebc->pixel_pitch - 16)); ++ ++ u8 offset_waiting = ++ ebc->lut_custom_active->offsets[ROCKCHIP_EBC_CUSTOM_WF_WAITING]; ++ uint8x16_t q8_0x00 = vdupq_n_u8(0x00); ++ uint8x16_t q8_0x01 = vdupq_n_u8(0x01); ++ uint8x16_t q8_0x03 = vdupq_n_u8(0x03); ++ uint8x16_t q8_0x0f = vdupq_n_u8(0x0f); ++ uint8x16_t q8_0x1f = vdupq_n_u8(0x1f); ++ uint8x16_t q8_0x20 = vdupq_n_u8(0x20); ++ uint8x16_t q8_0x21 = vdupq_n_u8(0x21); ++ uint8x16_t q8_0x80 = vdupq_n_u8(0x80); ++ uint8x16_t q8_offset_waiting = vdupq_n_u8(offset_waiting); ++ uint8x16_t q8_force_wf = vdupq_n_u8(force_wf); ++ uint8x16_t q8_force_hint = vdupq_n_u8(force_hint); ++ uint8x16_t q8_force_hint_mask = vdupq_n_u8(force_hint_mask); ++ uint8x16_t q8_offsets_table = vld1q_u8(ebc->lut_custom_active->offsets); ++ uint8x16_t q8_is_y4_binary_table = vld1q_u8(u8_lookup); ++ uint8x16_t q8_early_cancellation_addition = ++ vdupq_n_u8(early_cancellation_addition); ++ uint8x16_t q8_force_wf_gt0 = vcgtq_u8(q8_force_wf, q8_0x00); ++ uint8x16_t q8_allow_schedule = ++ vcgtq_u8(vdupq_n_u8(allow_schedule), q8_0x00); ++ ++ uint32x2_t q32s_0x10 = vdup_n_u32(0x10); ++ uint16x4_t q16s_mins = vdup_n_u16(0xffff); ++ uint16x4_t q16s_maxs = vdup_n_u16(0); ++ uint64x1_t q64s_0x00 = vdup_n_u64(0); ++ ++ for (unsigned int y = max(0, clip_ongoing_or_waiting->y1); ++ y < min((int) ebc->height, clip_ongoing_or_waiting->y2); ++y) { ++ int offset = y * pixel_pitch + x_start; ++ u8 *packed_inner_outer_nextprev_line = ebc->packed_inner_outer_nextprev + offset * 3; ++ u8 *phases_line = phase_buffer + y * phase_pitch + ++ (x_start >> (direct_mode ? 2 : 0)); ++ const u8 *prelim_target_line = ++ prelim_target + offset; ++ u8 *hints_line = hints + offset; ++ uint32x2_t q32s_xy = vdup_n_u32(y << 16 | x_start); ++ ++ for (unsigned int x = x_start; x < clip_ongoing_or_waiting->x2; ++ x += 16, packed_inner_outer_nextprev_line += 48, ++ phases_line += direct_mode ? 4 : 16, ++ prelim_target_line += 16, hints_line += 16) { ++ uint8x16x3_t q8_inner_outer_nextprev = ++ vld3q_u8(packed_inner_outer_nextprev_line); ++ uint8x16_t q8_inner = q8_inner_outer_nextprev.val[0]; ++ uint8x16_t q8_outer = q8_inner_outer_nextprev.val[1]; ++ uint8x16_t q8_next_prev = ++ q8_inner_outer_nextprev.val[2]; ++ uint8x16_t q8_prelim_target = ++ vld1q_u8(prelim_target_line); ++ uint8x16_t q8_hints = vld1q_u8(hints_line); ++ __builtin_prefetch(packed_inner_outer_nextprev_line + 3 * prefetch_pitch, 0, 0); ++ __builtin_prefetch(prelim_target_line + prefetch_pitch, 0, 0); ++ __builtin_prefetch(hints_line + prefetch_pitch, 0, 0); ++ ++ // Extract packed Y4 values and hints ++ uint8x16_t q8_inner_num = vandq_u8(q8_inner, q8_0x1f); ++ uint8x16_t q8_inner_is_last = ++ vtstq_u8(q8_inner, q8_0x20); ++ uint8x16_t q8_next = vshrq_n_u8(q8_next_prev, 4); ++ uint8x16_t q8_prev = vandq_u8(q8_next_prev, q8_0x0f); ++ uint8x16_t q8_prelim = vshrq_n_u8(q8_prelim_target, 4); ++ uint8x16_t q8_target = ++ vandq_u8(q8_prelim_target, q8_0x0f); ++ q8_hints = vbslq_u8(q8_force_hint_mask, q8_force_hint, ++ q8_hints); ++ uint8x16_t q8_wf_target = ++ vandq_u8(vshrq_n_u8(q8_hints, 4), q8_0x03); ++ q8_wf_target = vbslq_u8(q8_force_wf_gt0, q8_force_wf, ++ q8_wf_target); ++ uint8x16_t q8_hint_redraw = vtstq_u8(q8_hints, q8_0x80); ++ uint8x16_t q8_hint_noredraw = vmvnq_u8(q8_hint_redraw); ++ ++ uint8x16_t q8_next_eq_target = ++ vceqq_u8(q8_next, q8_target); ++ ++ // Start transforming ++ uint8x16_t q8_inner_num_is_1 = ++ vceqq_u8(q8_inner_num, q8_0x01); ++ // Saturating subtraction ++ uint8x16_t q8_inner_num_new = ++ vqsubq_u8(q8_inner_num, q8_0x01); ++ // Restore phase and is_last information ++ uint8x16_t q8_inner_new = ++ vbslq_u8(q8_0x1f, q8_inner_num_new, q8_inner); ++ ++ // Transition these to WAITING or IDLE, allow rescheduling ++ uint8x16_t q8_finish = ++ vandq_u8(q8_inner_num_is_1, q8_inner_is_last); ++ // These can be rescheduled ++ uint8x16_t q8_waiting = ++ vcgeq_u8(q8_outer, q8_offset_waiting); ++ // Transition these to REDRAW, allow rescheduling ++ uint8x16_t q8_finish_waiting = ++ vandq_u8(q8_finish, q8_waiting); ++ // These can be scheduled, send to WAITING (hint changed in the meantime) ++ uint8x16_t q8_idle = vceqzq_u8(q8_inner); ++ uint8x16_t q8_waiting_idle = ++ vorrq_u8(q8_waiting, q8_idle); ++ uint8x16_t q8_waiting_idle_finish = ++ vorrq_u8(q8_waiting_idle, q8_finish); ++ ++ // For scheduling: choose between target/prelim ++ uint8x16_t q8_use_target = vorrq_u8( ++ vorrq_u8(q8_finish_waiting, q8_hint_noredraw), ++ vorrq_u8(q8_next_eq_target, q8_force_wf_gt0)); ++ uint8x16_t q8_target_or_prelim_new = ++ vbslq_u8(q8_use_target, q8_target, q8_prelim); ++ // Prelim uses waveform DU=0 ++ uint8x16_t q8_wf = ++ vandq_u8(q8_wf_target, q8_use_target); ++ uint8x16_t q8_wf_is_du = vceqzq_u8(q8_wf); ++ uint8x16_t q8_target_or_prelim_is_binary = vqtbl1q_u8( ++ q8_is_y4_binary_table, q8_target_or_prelim_new); ++ uint8x16_t q8_next_is_binary = ++ vqtbl1q_u8(q8_is_y4_binary_table, q8_next); ++ uint8x16_t q8_prev_is_binary = ++ vqtbl1q_u8(q8_is_y4_binary_table, q8_prev); ++ uint8x16_t q8_next_and_prev_are_binary = ++ vandq_u8(q8_next_is_binary, q8_prev_is_binary); ++ uint8x16_t q8_outer_is_du = vceqq_u8(q8_outer, q8_0x01); ++ uint8x16_t q8_src_cancellable = vandq_u8( ++ q8_next_and_prev_are_binary, q8_outer_is_du); ++ uint8x16_t q8_dst_cancellable = vandq_u8( ++ q8_wf_is_du, q8_target_or_prelim_is_binary); ++ ++ uint8x16_t q8_can_cancel = vandq_u8(q8_src_cancellable, ++ q8_dst_cancellable); ++ uint8x16_t q8_can_start_or_cancel = ++ vorrq_u8(q8_waiting_idle_finish, q8_can_cancel); ++ ++ // 1. Schedule: start target or prelim ++ uint8x16_t q8_start_scheduled = vandq_u8( ++ q8_allow_schedule, ++ vandq_u8(vmvnq_u8(vceqq_u8( ++ q8_target_or_prelim_new, ++ q8_next)), ++ q8_can_start_or_cancel)); ++ q8_start_scheduled = ++ vorrq_u8(q8_start_scheduled, q8_force_wf_gt0); ++ uint8x16_t q8_next_new = ++ vbslq_u8(q8_start_scheduled, ++ q8_target_or_prelim_new, q8_next); ++ uint8x16_t q8_prev_new = ++ vbslq_u8(q8_start_scheduled, q8_next, q8_prev); ++ ++ ++ // 2. Redraw: start target if still desired an we finished waiting ++ uint8x16_t q8_start_redraw = vandq_u8( ++ q8_allow_schedule, ++ vandq_u8( ++ vmvnq_u8(vceqq_u8(q8_next, q8_target)), ++ vandq_u8(q8_finish_waiting, ++ vmvnq_u8(q8_start_scheduled)))); ++ q8_next_new = vbslq_u8(q8_start_redraw, q8_target, ++ q8_next_new); ++ q8_prev_new = ++ vbslq_u8(q8_start_redraw, q8_next, q8_prev_new); ++ uint8x16_t q8_next_prev_new = vorrq_u8( ++ vshlq_n_u8(q8_next_new, 4), q8_prev_new); ++ ++ // 3. Move remaining finished ones to IDLE or WAITING ++ uint8x16_t q8_start_scheduled_or_redraw = ++ vorrq_u8(q8_start_scheduled, q8_start_redraw); ++ uint8x16_t q8_no_start_scheduled_or_redraw = ++ vmvnq_u8(q8_start_scheduled_or_redraw); ++ uint8x16_t q8_finish_no_start_scheduled_or_redraw = ++ vandq_u8(q8_finish, ++ q8_no_start_scheduled_or_redraw); ++ uint8x16_t q8_outer_eq_0 = vceqzq_u8(q8_outer); ++ uint8x16_t q8_outer_gt_0 = vmvnq_u8(q8_outer_eq_0); ++ // Start waiting if: ++ // We finish, but we don't start schedule/redraw AND next != target, AND allow_schedule ++ // Or: We are not running AND we didn't schedule (next=prelim) AND next != target AND allow_schedule ++ ++ uint8x16_t q8_start_waiting = vandq_u8( ++ vorrq_u8( ++ q8_finish_no_start_scheduled_or_redraw, ++ vandq_u8( ++ q8_outer_eq_0, ++ q8_no_start_scheduled_or_redraw)), ++ vandq_u8(q8_allow_schedule, ++ vmvnq_u8(q8_next_eq_target))); ++ uint8x16_t q8_start_idle = ++ vandq_u8(q8_finish_no_start_scheduled_or_redraw, ++ vandq_u8(vmvnq_u8(q8_start_waiting), ++ q8_outer_gt_0)); ++ uint8x16_t q8_start = vorrq_u8( ++ q8_start_scheduled_or_redraw, q8_start_waiting); ++ ++ // 4. Get outer_new ++ uint8x16_t q8_outer_new = ++ vandq_u8(vqaddq_u8(q8_outer, q8_0x01), ++ vmvnq_u8(q8_inner_is_last)); ++ q8_outer_new = vbslq_u8(q8_inner_num_is_1, q8_outer_new, ++ q8_outer); ++ q8_outer_new = vbslq_u8( ++ q8_start, vqtbl1q_u8(q8_offsets_table, q8_wf), ++ q8_outer_new); ++ // Or: replace q8_wf with WAITING before the previous statement ++ q8_outer_new = vbslq_u8(q8_start_waiting, ++ q8_offset_waiting, ++ q8_outer_new); ++ ++ // 5. Get inner_new ++ uint16x8_t q16_idx_low = ++ vmovl_u8(vget_low_u8(q8_outer_new)); ++ q16_idx_low = vaddq_u16( ++ q16_idx_low, ++ vqshlq_n_u16( ++ vmovl_u8(vget_low_u8(q8_prev_new)), ++ 4 + ROCKCHIP_EBC_CUSTOM_WF_SEQ_SHIFT)); ++ q16_idx_low = vaddq_u16( ++ q16_idx_low, ++ vshll_n_u8(vget_low_u8(q8_next_new), ++ ROCKCHIP_EBC_CUSTOM_WF_SEQ_SHIFT)); ++ vst1q_u16(u16_idxs, q16_idx_low); ++ uint16x8_t q16_idx_high = ++ vmovl_u8(vget_high_u8(q8_outer_new)); ++ q16_idx_high = vaddq_u16( ++ q16_idx_high, ++ vqshlq_n_u16( ++ vmovl_u8(vget_high_u8(q8_prev_new)), ++ 4 + ROCKCHIP_EBC_CUSTOM_WF_SEQ_SHIFT)); ++ q16_idx_high = vaddq_u16( ++ q16_idx_high, ++ vshll_n_u8(vget_high_u8(q8_next_new), ++ ROCKCHIP_EBC_CUSTOM_WF_SEQ_SHIFT)); ++ vst1q_u16(u16_idxs + 8, q16_idx_high); ++ u8_lookup[0] = lut[u16_idxs[0]]; ++ u8_lookup[1] = lut[u16_idxs[1]]; ++ u8_lookup[2] = lut[u16_idxs[2]]; ++ u8_lookup[3] = lut[u16_idxs[3]]; ++ u8_lookup[4] = lut[u16_idxs[4]]; ++ u8_lookup[5] = lut[u16_idxs[5]]; ++ u8_lookup[6] = lut[u16_idxs[6]]; ++ u8_lookup[7] = lut[u16_idxs[7]]; ++ u8_lookup[8] = lut[u16_idxs[8]]; ++ u8_lookup[9] = lut[u16_idxs[9]]; ++ u8_lookup[10] = lut[u16_idxs[10]]; ++ u8_lookup[11] = lut[u16_idxs[11]]; ++ u8_lookup[12] = lut[u16_idxs[12]]; ++ u8_lookup[13] = lut[u16_idxs[13]]; ++ u8_lookup[14] = lut[u16_idxs[14]]; ++ u8_lookup[15] = lut[u16_idxs[15]]; ++ uint8x16_t q8_inner_new_from_lut = vld1q_u8(u8_lookup); ++ q8_inner_new = ++ vbslq_u8(vorrq_u8(q8_inner_num_is_1, q8_start), ++ q8_inner_new_from_lut, q8_inner_new); ++ q8_inner_new = ++ vbslq_u8(q8_start_idle, q8_0x21, q8_inner_new); ++ ++ // 6. Early cancellation ++ // TODO: DU4 may be cancellable as well for binary src and dst ++ uint8x16_t q8_cancel = vandq_u8( ++ q8_start, ++ vandq_u8(q8_can_cancel, ++ vmvnq_u8(q8_waiting_idle_finish))); ++ uint8x16_t q8_to_subtract = ++ vandq_u8(q8_cancel, q8_inner_num); ++ q8_inner_num_new = ++ vqsubq_u8(vandq_u8(q8_inner_new, q8_0x1f), ++ q8_to_subtract); ++ q8_inner_num_new = vqaddq_u8( ++ q8_inner_num_new, ++ vandq_u8(q8_cancel, ++ q8_early_cancellation_addition)); ++ q8_inner_new = vbslq_u8(q8_0x1f, q8_inner_num_new, ++ q8_inner_new); ++ uint8x16x3_t q8_ionp_new = {{q8_inner_new, q8_outer_new, q8_next_prev_new}}; ++ vst3q_u8(packed_inner_outer_nextprev_line, q8_ionp_new); ++ ++ uint8x16_t q8_phases = vshrq_n_u8(q8_inner_new, 6); ++ uint16x8_t q16_phases; ++ uint8x8_t q8s_phases1, q8s_phases; ++ ++#ifdef CONFIG_DRM_ROCKCHIP_EBC_3WIN_MODE ++ if (!direct_mode) { ++ vst1q_u8(phases_line, q8_phases); ++ } else ++#endif ++ { ++ q16_phases = vreinterpretq_u16_u8(q8_phases); ++ q16_phases = vorrq_u16( ++ q16_phases, vshrq_n_u16(q16_phases, 6)); ++ q8s_phases1 = vmovn_u16(q16_phases); ++ q16_phases = vreinterpretq_u16_u8( ++ vcombine_u8(q8s_phases1, q8s_phases1)); ++ q8s_phases = vmovn_u16( ++ vorrq_u16(q16_phases, ++ vshrq_n_u16(q16_phases, 4))); ++ vst1_lane_u32((u32 *)phases_line, ++ vreinterpret_u32_u8(q8s_phases), ++ 0); ++ __builtin_prefetch(phases_line + phases_prefetch_pitch, 1, 0); ++ } ++ ++ #ifdef ROCKCHIP_EBC_DEBUG_NEON ++ if (x == 928 && y == 702) { ++ NEON_PR_INFO(q8_inner); ++ NEON_PR_INFO(q8_outer); ++ NEON_PR_INFO(q8_next_prev); ++ NEON_PR_INFO(q8_prelim_target); ++ NEON_PR_INFO(q8_hints); ++ NEON_PR_INFO(q8_inner_num); ++ NEON_PR_INFO(q8_inner_is_last); ++ NEON_PR_INFO(q8_next); ++ NEON_PR_INFO(q8_prev); ++ NEON_PR_INFO(q8_prelim); ++ NEON_PR_INFO(q8_target); ++ NEON_PR_INFO(q8_wf_target); ++ NEON_PR_INFO(q8_hint_redraw); ++ NEON_PR_INFO(q8_hint_noredraw); ++ NEON_PR_INFO(q8_next_eq_target); ++ NEON_PR_INFO(q8_use_target); ++ NEON_PR_INFO(q8_target_or_prelim_new); ++ NEON_PR_INFO(q8_wf); ++ NEON_PR_INFO(q8_wf_is_du); ++ NEON_PR_INFO(q8_target_or_prelim_is_binary); ++ NEON_PR_INFO(q8_next_is_binary); ++ NEON_PR_INFO(q8_prev_is_binary); ++ NEON_PR_INFO(q8_next_and_prev_are_binary); ++ NEON_PR_INFO(q8_src_cancellable); ++ NEON_PR_INFO(q8_dst_cancellable); ++ NEON_PR_INFO(q8_inner_num_is_1); ++ NEON_PR_INFO(q8_inner_num_new); ++ NEON_PR_INFO(q8_inner_new); ++ NEON_PR_INFO(q8_finish); ++ NEON_PR_INFO(q8_waiting); ++ NEON_PR_INFO(q8_finish_waiting); ++ NEON_PR_INFO(q8_idle); ++ NEON_PR_INFO(q8_waiting_idle); ++ NEON_PR_INFO(q8_waiting_idle_finish); ++ NEON_PR_INFO(q8_can_cancel); ++ NEON_PR_INFO(q8_can_start_or_cancel); ++ NEON_PR_INFO(q8_start_scheduled); ++ NEON_PR_INFO(q8_next_new); ++ NEON_PR_INFO(q8_prev_new); ++ NEON_PR_INFO(q8_start_redraw); ++ NEON_PR_INFO(q8_start_scheduled_or_redraw); ++ NEON_PR_INFO(q8_no_start_scheduled_or_redraw); ++ NEON_PR_INFO(q8_finish_no_start_scheduled_or_redraw); ++ NEON_PR_INFO(q8_start_idle); ++ NEON_PR_INFO(q8_start_waiting); ++ NEON_PR_INFO(q8_start); ++ NEON_PR_INFO(q8_outer_new); ++ NEON_PR_INFO(q8_inner_new); ++ NEON_PR_INFO(q8_inner_new_from_lut); ++ NEON_PR_INFO(q8_cancel); ++ NEON_PR_INFO(q8_to_subtract); ++ NEON_PR_INFO(q8_inner_num_new); ++ NEON_PR_INFO(q8_inner_new); ++ NEON_PR_INFO(q8_phases); ++ NEON_PR_INFO(q16_phases); ++ NEON_PR_INFOS(q8s_phases1); ++ NEON_PR_INFOS(q8s_phases); ++ } ++#endif // ROCKCHIP_EBC_DEBUG_NEON ++ uint8x16_t q8_ongoing = vandq_u8( ++ q8_inner_new, ++ vcltq_u8(q8_outer_new, q8_offset_waiting)); ++ uint64x1_t q64s_any_ongoing_or_waiting = ++ vreinterpret_u64_u8( ++ vorr_u8(vget_high_u8(q8_inner_new), ++ vget_low_u8(q8_inner_new))); ++ uint64x1_t q64s_any_ongoing = vreinterpret_u64_u8( ++ vorr_u8(vget_high_u8(q8_ongoing), ++ vget_low_u8(q8_ongoing))); ++ q64s_any_ongoing_or_waiting = vcgt_u64( ++ q64s_any_ongoing_or_waiting, q64s_0x00); ++ q64s_any_ongoing = ++ vcgt_u64(q64s_any_ongoing, q64s_0x00); ++ uint32x2_t q32s_mask = vzip1_u32( ++ vreinterpret_u32_u64(q64s_any_ongoing), ++ vreinterpret_u32_u64( ++ q64s_any_ongoing_or_waiting)); ++ q16s_maxs = vmax_u16(q16s_maxs, ++ vreinterpret_u16_u32(vand_u32( ++ q32s_xy, q32s_mask))); ++ q16s_mins = vmin_u16( ++ q16s_mins, ++ vreinterpret_u16_u32(vorr_u32( ++ q32s_xy, vmvn_u32(q32s_mask)))); ++ ++ q32s_xy = vqadd_u32(q32s_xy, q32s_0x10); ++#ifdef ROCKCHIP_EBC_DEBUG_NEON ++ if (x == 928 && y == 702) { ++ NEON_PR_INFO(q8_inner_new); ++ NEON_PR_INFO(q8_outer_new); ++ NEON_PR_INFO(q8_ongoing); ++ NEON_PR_INFOS(q64s_any_ongoing_or_waiting); ++ NEON_PR_INFOS(q64s_any_ongoing); ++ NEON_PR_INFOS(q32s_mask); ++ NEON_PR_INFOS(q32s_maxs); ++ NEON_PR_INFOS(q32s_mins); ++ NEON_PR_INFOS(q32s_xy); ++ } ++#endif ++ } ++ } ++ ++ aligned_u64 u64_mins, u64_maxs; ++ u16 *mins = (u16 *)&u64_mins; ++ u16 *maxs = (u16 *)&u64_maxs; ++ vst1_lane_u64(&u64_mins, vreinterpret_u64_u16(q16s_mins), 0); ++ vst1_lane_u64(&u64_maxs, vreinterpret_u64_u16(q16s_maxs), 0); ++ clip_ongoing->x1 = mins[0]; ++ clip_ongoing->y1 = mins[1]; ++ clip_ongoing->x2 = maxs[0] + 16; ++ clip_ongoing->y2 = maxs[1] + 1; ++ clip_ongoing_or_waiting->x1 = mins[2]; ++ clip_ongoing_or_waiting->y1 = mins[3]; ++ clip_ongoing_or_waiting->x2 = maxs[2] + 16; ++ clip_ongoing_or_waiting->y2 = maxs[3] + 1; ++} ++EXPORT_SYMBOL(rockchip_ebc_schedule_advance_neon); ++ ++void rockchip_ebc_blit_y4_high_low_neon(u8 *dst, u8 *y4_high_src, ++ u8 *y4_low_src, unsigned int size) ++{ ++ for (unsigned int i = 0; i < size; ++ i += 16, dst += 16, y4_high_src += 16, y4_low_src += 16) { ++ vst1q_u8(dst, ++ vorrq_u8(vld1q_u8(y4_high_src), vld1q_u8(y4_low_src))); ++ } ++} ++EXPORT_SYMBOL(rockchip_ebc_blit_y4_high_low_neon); ++ ++/** ++ * rockchip_ebc_blit_y421_y4_neon - blit from y421 to y4 ++ */ ++void rockchip_ebc_blit_y421_y4_neon(const struct rockchip_ebc *ebc, u8 *y4_dst, ++ const u8 *y421_src, struct drm_rect *clip) ++{ ++ unsigned int y421_pitch = ebc->pixel_pitch; ++ unsigned int gray4_pitch = ebc->gray4_pitch; ++ ++ // 16 byte block size for NEON ++ unsigned int x_start = ++ max(0, min(clip->x1 & ~15, (int)ebc->pixel_pitch - 16)); ++ unsigned int x_end = ++ min((int)ebc->pixel_pitch, ((clip->x2 + 15) & ~15)); ++ unsigned int x, y; ++ ++ for (y = clip->y1; y < clip->y2; y++) { ++ const u8 *src_line = y421_src + y * y421_pitch + x_start; ++ u8 *dst_line = y4_dst + y * gray4_pitch + x_start / 2; ++ for (x = x_start; x < x_end; ++ x += 16, src_line += 16, dst_line += 8) { ++ // ?A ?B ?C ?D ++ uint8x16_t q8_y421 = vld1q_u8(src_line); ++ // AA BB CC DD duplicate lower bits by left shifting and insert immediately ++ uint8x16_t q8_y4 = vsliq_n_u8(q8_y421, q8_y421, 4); ++ // AABB CCDD ++ uint16x8_t q16_y4 = vreinterpretq_u16_u8(q8_y4); ++ // Insert AB CD ++ vst1_u8(dst_line, vshrn_n_u16(q16_y4, 4)); ++ } ++ } ++} ++EXPORT_SYMBOL(rockchip_ebc_blit_y421_y4_neon); ++ ++/** ++ * rockchip_ebc_blit_fb_rgb565_y4_hints_neon - blit from RGB565 FB ++ * ++ * Flips output horizontally. ++ * HHHH 4444 (hints in higher bits, Y4 in lower bits) ++ */ ++void rockchip_ebc_blit_fb_rgb565_y4_hints_neon(const struct rockchip_ebc *ebc, ++ struct drm_rect *dst_clip, ++ u8 *prelim_target_atomic_update, ++ u8 *hints_atomic_update, ++ const void *vaddr, ++ const struct drm_framebuffer *fb, ++ const struct drm_rect *src_clip) ++{ ++ unsigned int rgb_pitch = fb->pitches[0]; ++ unsigned int pixel_pitch = ebc->pixel_pitch; ++ unsigned int prefetch_pitch = (pixel_pitch << 6) >> 6; ++ ++ // 16 byte block size for NEON ++ unsigned int src_start_x = src_clip->x1; ++ unsigned int src_end_x = src_clip->x2; ++ unsigned int dst_start_x = dst_clip->x2 - 16; ++ unsigned int x, y; ++ ++ // Force horizontal reflection for simplicity ++ u8 *dst_prelim_target = prelim_target_atomic_update + ++ dst_clip->y1 * pixel_pitch + dst_start_x; ++ const u8 *src = vaddr + src_clip->y1 * rgb_pitch + ++ src_start_x * fb->format->cpp[0]; ++ const u8 *ioctl_hints = ++ ebc->hints_ioctl + src_clip->y1 * pixel_pitch + src_start_x; ++ u8 *hints = ++ hints_atomic_update + dst_clip->y1 * pixel_pitch + dst_start_x; ++ ++ // Thresholds and LUT for 4-level tresholding and 2-level threshold ++ uint8x16_t q8_thresholds_y2_table = vld1q_u8((u8 *)ebc->lut_y2_y4); ++ uint8x16x2_t q8_thresholds_y2_dither_table = ++ vld2q_u8((u8 *)ebc->lut_y2_y4_dithered); ++ uint8x16_t q8_threshold_y1 = vdupq_n_u8(ebc->y4_threshold_y1); ++ ++ const u8 *dithering_texture = ebc->dithering_texture; ++ u8 dithering_texture_size_hint = ebc->dithering_texture_size_hint; ++ ++ // (256 * (np.array([.299, .587, .114])) * [255/31, 255/63, 255/31] / [8, 4, 8]).round() ++ // Factors were modified to reduce number of shifting or masking operations required ++ uint8x8_t q8_yuv_r = vdup_n_u8(79); ++ uint8x8_t q8_yuv_g = vdup_n_u8(152); ++ uint8x8_t q8_yuv_b = vdup_n_u8(30); ++ ++ uint8x16_t q8_0x02 = vdupq_n_u8(0x02); ++ uint8x16_t q8_0x03 = vdupq_n_u8(0x03); ++ uint8x16_t q8_0x08 = vdupq_n_u8(0x08); ++ uint8x16_t q8_0x0f = vdupq_n_u8(0x0f); ++ uint8x16_t q8_0x40 = vdupq_n_u8(0x40); ++ uint8x8_t q8s_0xfc = vdup_n_u8(0xfc); ++ uint8x8_t q8s_0xf8 = vdup_n_u8(0xf8); ++ ++ for (y = src_clip->y1; y < src_clip->y2; y++) { ++ const u16 *fb_line = (const u16 *) src; ++ u8 *prelim_target_line = dst_prelim_target; ++ const u8 *ioctl_hints_line = ioctl_hints; ++ u8 *hints_line = hints; ++ ++ uint8x16_t q8_dither_pattern0, q8_dither_pattern1, q8_tmp; ++ if (dithering_texture_size_hint == 32) { ++ q8_dither_pattern0 = ++ vld1q_u8(dithering_texture + 32 * (y & 31)); ++ q8_dither_pattern1 = ++ vld1q_u8(dithering_texture + 32 * (y & 31)); ++ if (src_start_x & 16) { ++ q8_tmp = q8_dither_pattern0; ++ q8_dither_pattern0 = q8_dither_pattern1; ++ q8_dither_pattern1 = q8_tmp; ++ } ++ } else { ++ q8_dither_pattern0 = vld1q_u8( ++ dithering_texture + ++ 16 * (y & (dithering_texture_size_hint - 1))); ++ q8_dither_pattern1 = q8_dither_pattern0; ++ } ++ ++ for (x = src_start_x; x < src_end_x; x += 16, fb_line += 16, ++ prelim_target_line -= 16, ioctl_hints_line += 16, ++ hints_line -= 16) { ++ uint8x8_t q8s_gray1, q8s_gray2; ++ uint8x16_t q8_gray; ++ uint16x8_t q16_gray; ++ ++ // RGB -> Y8 using rounded YUV ++ // Load 8 RGB values ++ uint16x8_t q16_rgb = vld1q_u16(fb_line); ++ ++ q16_gray = vmull_u8(q8_yuv_b, ++ vmovn_u16(vshlq_n_u16(q16_rgb, 3))); ++ q16_gray = vmlal_u8(q16_gray, q8_yuv_g, ++ vand_u8(vshrn_n_u16(q16_rgb, 3), ++ q8s_0xfc)); ++ q16_gray = vmlal_u8(q16_gray, q8_yuv_r, ++ vand_u8(vshrn_n_u16(q16_rgb, 8), ++ q8s_0xf8)); ++ // Aa Bb Cc Dd lower-case bits still need to be discarded ++ q8s_gray1 = vshrn_n_u16(q16_gray, 8); ++ ++ // Same for the next 8 RGB pixels ++ q16_rgb = vld1q_u16(fb_line + 8); ++ /* __builtin_prefetch(fb_line + pixel_pitch, 0, 0); */ ++ ++ q16_gray = vmull_u8(q8_yuv_b, ++ vmovn_u16(vshlq_n_u16(q16_rgb, 3))); ++ q16_gray = vmlal_u8(q16_gray, q8_yuv_g, ++ vand_u8(vshrn_n_u16(q16_rgb, 3), ++ q8s_0xfc)); ++ q16_gray = vmlal_u8(q16_gray, q8_yuv_r, ++ vand_u8(vshrn_n_u16(q16_rgb, 8), ++ q8s_0xf8)); ++ q8s_gray2 = vshrn_n_u16(q16_gray, 8); ++ ++ // Combine into single vector, discard lower bits ++ // 0A 0B 0C 0D ++ q8_gray = vshrq_n_u8(vrev64q_u8(vcombine_u8(q8s_gray2, q8s_gray1)), ++ 4); ++ ++ uint64x1x2_t q64sx2_hint = vld2_u64((u64 *) ioctl_hints_line); ++ uint8x16_t q8_hint = vrev64q_u8(vreinterpretq_u8_u64(vcombine_u64(q64sx2_hint.val[1], q64sx2_hint.val[0]))); ++ /* __builtin_prefetch(ioctl_hints_line + pixel_pitch, 0, 0); */ ++ uint8x16_t q8_hint_dither = vtstq_u8(q8_hint, q8_0x40); ++ uint8x16_t q8_hint_waveform = ++ vandq_u8(vshrq_n_u8(q8_hint, 4), q8_0x03); ++ uint8x16_t q8_hint_gray = vorrq_u8(q8_gray, q8_hint); ++ ++ uint8x16_t q8_gray_dithered = vminq_u8( ++ vqsubq_u8(vqaddq_u8(q8_gray, ++ q8_dither_pattern0), ++ q8_0x08), ++ q8_0x0f); ++ uint8x16_t q8_gray_y2_dt = ++ vqtbl2q_u8(q8_thresholds_y2_dither_table, ++ q8_gray_dithered); ++ uint8x16_t q8_gray_y2_th = ++ vqtbl1q_u8(q8_thresholds_y2_table, q8_gray); ++ uint8x16_t q8_gray_y1_dt = vandq_u8( ++ vcgeq_u8(q8_gray_dithered, q8_threshold_y1), ++ q8_0x0f); ++ uint8x16_t q8_gray_y1_th = vandq_u8( ++ vcgeq_u8(q8_gray, q8_threshold_y1), q8_0x0f); ++ uint8x16_t q8_gray_y1 = vbslq_u8( ++ q8_hint_dither, q8_gray_y1_dt, q8_gray_y1_th); ++ uint8x16_t q8_gray_y2 = vbslq_u8( ++ q8_hint_dither, q8_gray_y2_dt, q8_gray_y2_th); ++ uint8x16_t q8_gray_y12 = ++ vbslq_u8(vceqzq_u8(q8_hint_waveform), ++ q8_gray_y1, q8_gray_y2); ++ ++ uint8x16_t q8_target = ++ vbslq_u8(vceqq_u8(q8_hint_waveform, q8_0x02), ++ q8_gray, q8_gray_y12); ++ // TODO: make dithering/thresholding configurable via a hint ++ uint8x16_t q8_prelim_target = vorrq_u8( ++ vshlq_n_u8(q8_gray_y1_dt, 4), q8_target); ++ ++ __builtin_prefetch(prelim_target_line + prefetch_pitch, 1, 0); // Maybe 1, 1 to keep in L3 cache ++ __builtin_prefetch(hints_line + prefetch_pitch, 1, 0); ++ vst1q_u8(hints_line, q8_hint_gray); ++ vst1q_u8(prelim_target_line, q8_prelim_target); ++ ++ // Swap to support 32x32 dithering textures ++ q8_tmp = q8_dither_pattern0; ++ q8_dither_pattern0 = q8_dither_pattern1; ++ q8_dither_pattern1 = q8_tmp; ++ } ++ ++ src += rgb_pitch; ++ dst_prelim_target += pixel_pitch; ++ ioctl_hints += pixel_pitch; ++ hints += pixel_pitch; ++ } ++} ++EXPORT_SYMBOL(rockchip_ebc_blit_fb_rgb565_y4_hints_neon); ++ ++/** ++ * rockchip_ebc_blit_fb_xrgb8888_y4_hints_neon - blit from XRGB8888 FB ++ * ++ * Flips output horizontally. ++ * HHHH 4444 (hints in higher bits, Y4 in lower bits) ++ */ ++void rockchip_ebc_blit_fb_xrgb8888_y4_hints_neon( ++ const struct rockchip_ebc *ebc, struct drm_rect *dst_clip, ++ u8 *prelim_target_atomic_update, u8 *hints_atomic_update, ++ const void *vaddr, const struct drm_framebuffer *fb, ++ const struct drm_rect *src_clip) ++{ ++ unsigned int rgba_pitch = fb->pitches[0]; ++ unsigned int pixel_pitch = ebc->pixel_pitch; ++ unsigned int prefetch_pitch = (pixel_pitch << 6) >> 6; ++ ++ // 16 byte block size for NEON ++ unsigned int src_start_x = src_clip->x1; ++ unsigned int src_end_x = src_clip->x2; ++ unsigned int dst_start_x = dst_clip->x2 - 16; ++ unsigned int x, y; ++ ++ // Force horizontal reflection for simplicity ++ u8 *dst_prelim_target = prelim_target_atomic_update + ++ dst_clip->y1 * pixel_pitch + dst_start_x; ++ const u8 *src = vaddr + src_clip->y1 * rgba_pitch + ++ src_start_x * fb->format->cpp[0]; ++ const u8 *ioctl_hints = ++ ebc->hints_ioctl + src_clip->y1 * pixel_pitch + src_start_x; ++ u8 *hints = ++ hints_atomic_update + dst_clip->y1 * pixel_pitch + dst_start_x; ++ ++ uint8x8_t q8_yuv_r = vdup_n_u8(76); ++ uint8x8_t q8_yuv_g = vdup_n_u8(150); ++ uint8x8_t q8_yuv_b = vdup_n_u8(29); ++ ++ // Thresholds and LUT for 4-level tresholding and 2-level threshold ++ uint8x16_t q8_thresholds_y2_table = vld1q_u8((u8 *)ebc->lut_y2_y4); ++ uint8x16x2_t q8_thresholds_y2_dither_table = ++ vld2q_u8((u8 *)ebc->lut_y2_y4_dithered); ++ uint8x16_t q8_threshold_y1 = vdupq_n_u8(ebc->y4_threshold_y1); ++ ++ const u8 *dithering_texture = ebc->dithering_texture; ++ u8 dithering_texture_size_hint = ebc->dithering_texture_size_hint; ++ ++ uint8x16_t q8_0x02 = vdupq_n_u8(0x02); ++ uint8x16_t q8_0x03 = vdupq_n_u8(0x03); ++ uint8x16_t q8_0x08 = vdupq_n_u8(0x08); ++ uint8x16_t q8_0x0f = vdupq_n_u8(0x0f); ++ uint8x16_t q8_0x40 = vdupq_n_u8(0x40); ++ ++ for (y = src_clip->y1; y < src_clip->y2; y++) { ++ const u32 *fb_line = (const u32 *) src; ++ u8 *prelim_target_line = dst_prelim_target; ++ const u8 *ioctl_hints_line = ioctl_hints; ++ u8 *hints_line = hints; ++ ++ uint8x16_t q8_dither_pattern0, q8_dither_pattern1, q8_tmp; ++ if (dithering_texture_size_hint == 32) { ++ q8_dither_pattern0 = ++ vld1q_u8(dithering_texture + 32 * (y & 31)); ++ q8_dither_pattern1 = ++ vld1q_u8(dithering_texture + 32 * (y & 31)); ++ if (src_start_x & 16) { ++ q8_tmp = q8_dither_pattern0; ++ q8_dither_pattern0 = q8_dither_pattern1; ++ q8_dither_pattern1 = q8_tmp; ++ } ++ } else { ++ q8_dither_pattern0 = vld1q_u8( ++ dithering_texture + ++ 16 * (y & (dithering_texture_size_hint - 1))); ++ q8_dither_pattern1 = q8_dither_pattern0; ++ } ++ ++ for (x = src_start_x; x < src_end_x; x += 16, fb_line += 16, ++ prelim_target_line -= 16, ioctl_hints_line += 16, ++ hints_line -= 16) { ++ uint8x8_t q8s_gray1, q8s_gray2; ++ uint8x16_t q8_gray; ++ uint16x8_t q16_gray; ++ ++ // RGB -> Y8 using rounded YUV ++ // Load 16 RGB values ++ uint8x16x4_t q8x4_rgba = vld4q_u8((const u8 *) fb_line); ++ q16_gray = vmull_u8(q8_yuv_r, vget_high_u8(q8x4_rgba.val[0])); ++ q16_gray = ++ vmlal_u8(q16_gray, q8_yuv_g, vget_high_u8(q8x4_rgba.val[1])); ++ q16_gray = vmlal_u8(q16_gray, q8_yuv_b, vget_high_u8(q8x4_rgba.val[2])); ++ // Aa Bb Cc Dd lower-case bits still need to be discarded ++ q8s_gray1 = vshrn_n_u16(q16_gray, 8); ++ ++ // Same for the next 8 RGB pixels ++ __builtin_prefetch(fb_line + pixel_pitch, 0, 0); ++ q16_gray = vmull_u8(q8_yuv_r, vget_low_u8(q8x4_rgba.val[0])); ++ q16_gray = ++ vmlal_u8(q16_gray, q8_yuv_g, vget_low_u8(q8x4_rgba.val[1])); ++ q16_gray = vmlal_u8(q16_gray, q8_yuv_b, vget_low_u8(q8x4_rgba.val[2])); ++ /* __builtin_prefetch(fb_line + pixel_pitch, 0, 0); */ ++ q8s_gray2 = vshrn_n_u16(q16_gray, 8); ++ ++ // Combine into single vector, discard lower bits ++ // 0A 0B 0C 0D ++ q8_gray = vshrq_n_u8(vrev64q_u8(vcombine_u8(q8s_gray1, q8s_gray2)), ++ 4); ++ ++ uint64x1x2_t q64sx2_hint = vld2_u64((u64 *) ioctl_hints_line); ++ uint8x16_t q8_hint = vrev64q_u8(vreinterpretq_u8_u64(vcombine_u64(q64sx2_hint.val[1], q64sx2_hint.val[0]))); ++ /* __builtin_prefetch(ioctl_hints_line + pixel_pitch, 0, 0); */ ++ uint8x16_t q8_hint_dither = vtstq_u8(q8_hint, q8_0x40); ++ uint8x16_t q8_hint_waveform = ++ vandq_u8(vshrq_n_u8(q8_hint, 4), q8_0x03); ++ uint8x16_t q8_hint_gray = vorrq_u8(q8_gray, q8_hint); ++ ++ uint8x16_t q8_gray_dithered = vminq_u8( ++ vqsubq_u8(vqaddq_u8(q8_gray, ++ q8_dither_pattern0), ++ q8_0x08), ++ q8_0x0f); ++ uint8x16_t q8_gray_y2_dt = ++ vqtbl2q_u8(q8_thresholds_y2_dither_table, ++ q8_gray_dithered); ++ uint8x16_t q8_gray_y2_th = ++ vqtbl1q_u8(q8_thresholds_y2_table, q8_gray); ++ uint8x16_t q8_gray_y1_dt = vandq_u8( ++ vcgeq_u8(q8_gray_dithered, q8_threshold_y1), ++ q8_0x0f); ++ uint8x16_t q8_gray_y1_th = vandq_u8( ++ vcgeq_u8(q8_gray, q8_threshold_y1), q8_0x0f); ++ uint8x16_t q8_gray_y1 = vbslq_u8( ++ q8_hint_dither, q8_gray_y1_dt, q8_gray_y1_th); ++ uint8x16_t q8_gray_y2 = vbslq_u8( ++ q8_hint_dither, q8_gray_y2_dt, q8_gray_y2_th); ++ uint8x16_t q8_gray_y12 = ++ vbslq_u8(vceqzq_u8(q8_hint_waveform), ++ q8_gray_y1, q8_gray_y2); ++ ++ uint8x16_t q8_target = ++ vbslq_u8(vceqq_u8(q8_hint_waveform, q8_0x02), ++ q8_gray, q8_gray_y12); ++ // TODO: make dithering/thresholding configurable via a hint ++ uint8x16_t q8_prelim_target = vorrq_u8( ++ vshlq_n_u8(q8_gray_y1_dt, 4), q8_target); ++ ++ __builtin_prefetch(prelim_target_line + prefetch_pitch, 1, 0); // Maybe 1, 1 to keep in L3 cache ++ __builtin_prefetch(hints_line + prefetch_pitch, 1, 0); ++ vst1q_u8(hints_line, q8_hint_gray); ++ vst1q_u8(prelim_target_line, q8_prelim_target); ++ ++ // Swap to support 32x32 dithering textures ++ q8_tmp = q8_dither_pattern0; ++ q8_dither_pattern0 = q8_dither_pattern1; ++ q8_dither_pattern1 = q8_tmp; ++ } ++ ++ src += rgba_pitch; ++ dst_prelim_target += pixel_pitch; ++ ioctl_hints += pixel_pitch; ++ hints += pixel_pitch; ++ } ++} ++EXPORT_SYMBOL(rockchip_ebc_blit_fb_xrgb8888_y4_hints_neon); ++ ++/** ++ * rockchip_ebc_blit_fb_r8_y4_hints_neon - blit from R8 FB ++ * ++ * Flips output horizontally. ++ * HHHH 4444 (hints in higher bits, Y4 in lower bits) ++ */ ++void rockchip_ebc_blit_fb_r8_y4_hints_neon(const struct rockchip_ebc *ebc, ++ struct drm_rect *dst_clip, ++ u8 *prelim_target_atomic_update, ++ u8 *hints_atomic_update, ++ const void *vaddr, ++ const struct drm_framebuffer *fb, ++ const struct drm_rect *src_clip) ++{ ++ unsigned int r8_pitch = fb->pitches[0]; ++ unsigned int pixel_pitch = ebc->pixel_pitch; ++ unsigned int prefetch_pitch = (pixel_pitch << 6) >> 6; ++ ++ // 16 byte block size for NEON ++ unsigned int src_start_x = src_clip->x1; ++ unsigned int src_end_x = src_clip->x2; ++ unsigned int dst_start_x = dst_clip->x2 - 16; ++ unsigned int x, y; ++ ++ // Force horizontal reflection for simplicity ++ u8 *dst_prelim_target = prelim_target_atomic_update + ++ dst_clip->y1 * pixel_pitch + dst_start_x; ++ const u8 *src = vaddr + src_clip->y1 * r8_pitch + ++ src_start_x * fb->format->cpp[0]; ++ const u8 *ioctl_hints = ++ ebc->hints_ioctl + src_clip->y1 * pixel_pitch + src_start_x; ++ u8 *hints = ++ hints_atomic_update + dst_clip->y1 * pixel_pitch + dst_start_x; ++ ++ // Thresholds and LUT for 4-level tresholding and 2-level threshold ++ uint8x16_t q8_thresholds_y2_table = vld1q_u8((u8 *)ebc->lut_y2_y4); ++ uint8x16x2_t q8_thresholds_y2_dither_table = ++ vld2q_u8((u8 *)ebc->lut_y2_y4_dithered); ++ uint8x16_t q8_threshold_y1 = vdupq_n_u8(ebc->y4_threshold_y1); ++ ++ const u8 *dithering_texture = ebc->dithering_texture; ++ u8 dithering_texture_size_hint = ebc->dithering_texture_size_hint; ++ ++ uint8x16_t q8_0x02 = vdupq_n_u8(0x02); ++ uint8x16_t q8_0x03 = vdupq_n_u8(0x03); ++ uint8x16_t q8_0x08 = vdupq_n_u8(0x08); ++ uint8x16_t q8_0x0f = vdupq_n_u8(0x0f); ++ uint8x16_t q8_0x40 = vdupq_n_u8(0x40); ++ ++ for (y = src_clip->y1; y < src_clip->y2; y++) { ++ const u8 *fb_line = src; ++ u8 *prelim_target_line = dst_prelim_target; ++ const u8 *ioctl_hints_line = ioctl_hints; ++ u8 *hints_line = hints; ++ ++ uint8x16_t q8_dither_pattern0, q8_dither_pattern1, q8_tmp; ++ if (dithering_texture_size_hint == 32) { ++ q8_dither_pattern0 = ++ vld1q_u8(dithering_texture + 32 * (y & 31)); ++ q8_dither_pattern1 = ++ vld1q_u8(dithering_texture + 32 * (y & 31)); ++ if (src_start_x & 16) { ++ q8_tmp = q8_dither_pattern0; ++ q8_dither_pattern0 = q8_dither_pattern1; ++ q8_dither_pattern1 = q8_tmp; ++ } ++ } else { ++ q8_dither_pattern0 = vld1q_u8( ++ dithering_texture + ++ 16 * (y & (dithering_texture_size_hint - 1))); ++ q8_dither_pattern1 = q8_dither_pattern0; ++ } ++ ++ for (x = src_start_x; x < src_end_x; x += 16, fb_line += 16, ++ prelim_target_line -= 16, ioctl_hints_line += 16, ++ hints_line -= 16) { ++ // Load 16 RGB values ++ uint64x1x2_t q64sx2_gray = vld2_u64((u64 *) fb_line); ++ /* __builtin_prefetch(fb_line + pixel_pitch, 0, 0); */ ++ ++ // Discard lower bits ++ // 0A 0B 0C 0D ++ uint8x16_t q8_gray = vshrq_n_u8(vrev64q_u8(vreinterpretq_u8_u64(vcombine_u64(q64sx2_gray.val[1], q64sx2_gray.val[0]))), 4); ++ ++ uint64x1x2_t q64sx2_hint = vld2_u64((u64 *) ioctl_hints_line); ++ uint8x16_t q8_hint = vrev64q_u8(vreinterpretq_u8_u64(vcombine_u64(q64sx2_hint.val[1], q64sx2_hint.val[0]))); ++ /* __builtin_prefetch(ioctl_hints_line + pixel_pitch, 0, 0); */ ++ uint8x16_t q8_hint_dither = vtstq_u8(q8_hint, q8_0x40); ++ uint8x16_t q8_hint_waveform = ++ vandq_u8(vshrq_n_u8(q8_hint, 4), q8_0x03); ++ uint8x16_t q8_hint_gray = vorrq_u8(q8_gray, q8_hint); ++ ++ uint8x16_t q8_gray_dithered = vminq_u8( ++ vqsubq_u8(vqaddq_u8(q8_gray, ++ q8_dither_pattern0), ++ q8_0x08), ++ q8_0x0f); ++ uint8x16_t q8_gray_y2_dt = ++ vqtbl2q_u8(q8_thresholds_y2_dither_table, ++ q8_gray_dithered); ++ uint8x16_t q8_gray_y2_th = ++ vqtbl1q_u8(q8_thresholds_y2_table, q8_gray); ++ uint8x16_t q8_gray_y1_dt = vandq_u8( ++ vcgeq_u8(q8_gray_dithered, q8_threshold_y1), ++ q8_0x0f); ++ uint8x16_t q8_gray_y1_th = vandq_u8( ++ vcgeq_u8(q8_gray, q8_threshold_y1), q8_0x0f); ++ uint8x16_t q8_gray_y1 = vbslq_u8( ++ q8_hint_dither, q8_gray_y1_dt, q8_gray_y1_th); ++ uint8x16_t q8_gray_y2 = vbslq_u8( ++ q8_hint_dither, q8_gray_y2_dt, q8_gray_y2_th); ++ uint8x16_t q8_gray_y12 = ++ vbslq_u8(vceqzq_u8(q8_hint_waveform), ++ q8_gray_y1, q8_gray_y2); ++ ++ uint8x16_t q8_target = ++ vbslq_u8(vceqq_u8(q8_hint_waveform, q8_0x02), ++ q8_gray, q8_gray_y12); ++ // TODO: make dithering/thresholding configurable via a hint ++ uint8x16_t q8_prelim_target = vorrq_u8( ++ vshlq_n_u8(q8_gray_y1_dt, 4), q8_target); ++ ++ __builtin_prefetch(prelim_target_line + prefetch_pitch, 1, 0); // Maybe 1, 1 to keep in L3 cache ++ __builtin_prefetch(hints_line + prefetch_pitch, 1, 0); ++ vst1q_u8(hints_line, q8_hint_gray); ++ vst1q_u8(prelim_target_line, q8_prelim_target); ++ ++ // Swap to support 32x32 dithering textures ++ q8_tmp = q8_dither_pattern0; ++ q8_dither_pattern0 = q8_dither_pattern1; ++ q8_dither_pattern1 = q8_tmp; ++ } ++ ++ src += r8_pitch; ++ dst_prelim_target += pixel_pitch; ++ ioctl_hints += pixel_pitch; ++ hints += pixel_pitch; ++ } ++} ++EXPORT_SYMBOL(rockchip_ebc_blit_fb_r8_y4_hints_neon); ++ ++void rockchip_ebc_reset_inner_outer_neon(const struct rockchip_ebc *ebc) ++{ ++ u8 *packed_inner_outer_nextprev_line = ebc->packed_inner_outer_nextprev; ++ uint8x16_t q8_0x00 = vdupq_n_u8(0x00); ++ for (int i = 0; i < ebc->num_pixels; i += 16) { ++ uint8x16x3_t q8_inner_outer_nextprev = vld3q_u8(packed_inner_outer_nextprev_line); ++ uint8x16x3_t q8_inner_outer_nextprev_new = { ++ { q8_0x00, q8_0x00, q8_inner_outer_nextprev.val[2] } ++ }; ++ vst3q_u8(packed_inner_outer_nextprev_line, ++ q8_inner_outer_nextprev_new); ++ packed_inner_outer_nextprev_line += 48; ++ } ++} ++EXPORT_SYMBOL(rockchip_ebc_reset_inner_outer_neon); ++ ++MODULE_AUTHOR("hrdl "); ++MODULE_DESCRIPTION("Rockchip EBC NEON functions"); ++MODULE_LICENSE("GPL v2"); +diff --git a/drivers/gpu/drm/rockchip/rockchip_ebc_blit_neon.h b/drivers/gpu/drm/rockchip/rockchip_ebc_blit_neon.h +new file mode 100644 +index 000000000..778e5736e +--- /dev/null ++++ b/drivers/gpu/drm/rockchip/rockchip_ebc_blit_neon.h +@@ -0,0 +1,62 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* ++ * Copyright (C) hrdl ++ * Author: hrdl ++ */ ++ ++#ifndef _ROCKCHIP_EBC_BLIT_NEON_H ++#define _ROCKCHIP_EBC_BLIT_NEON_H ++ ++void rockchip_ebc_blit_fb_xrgb8888_y4_neon(const struct rockchip_ebc_ctx *ctx, ++ struct drm_rect *dst_clip, ++ const void *vaddr, ++ const struct drm_framebuffer *fb, ++ const struct drm_rect *src_clip); ++ ++void rockchip_ebc_blit_fb_xrgb8888_y4_thresholded4_neon( ++ const struct rockchip_ebc_ctx *ctx, struct drm_rect *dst_clip, ++ const void *vaddr, const struct drm_framebuffer *fb, ++ const struct drm_rect *src_clip, u8 invert, u8 fourtone_low_threshold, ++ u8 fourtone_mid_threshold, u8 fourtone_hi_threshold, bool dither); ++ ++void rockchip_ebc_blit_fb_xrgb8888_y4_dithered2_texture_neon( ++ const struct rockchip_ebc_ctx *ctx, struct drm_rect *dst_clip, ++ const void *vaddr, const struct drm_framebuffer *fb, ++ const struct drm_rect *src_clip, u8 invert, ++ const u8 *dither_texture, int dither_texture_size); ++ ++void rockchip_ebc_blit_fb_xrgb8888_y4_dithered2_neon( ++ const struct rockchip_ebc_ctx *ctx, struct drm_rect *dst_clip, ++ const void *vaddr, const struct drm_framebuffer *fb, ++ const struct drm_rect *src_clip, u8 invert); ++ ++void rockchip_ebc_update_blit_fnum_prev_neon(const struct rockchip_ebc_ctx *ctx, ++ u8 *prev, u8 *next, u8 *fnum, ++ u8 *fnum_prev, ++ struct drm_rect *clip, ++ u8 last_phase); ++ ++void rockchip_ebc_blit_direct_fnum_a2_neon(const struct rockchip_ebc_ctx *ctx, ++ u8 *phase, u8 *frame_num, u8 *next, ++ u8 *prev, const struct drm_epd_lut *lut, ++ u8 a2_shorten_waveform, ++ const struct drm_rect *clip); ++ ++void rockchip_ebc_blit_frame_num_neon(const struct rockchip_ebc_ctx *ctx, ++ u8 *dst, u8 phase, ++ const struct drm_rect *clip, ++ u8 *other_buffer, int last_phase, ++ int frame, int check_blit_frame_num); ++ ++void rockchip_ebc_schedule_and_blit_neon( ++ const struct rockchip_ebc_ctx *ctx, u8 *frame_num, u8 *next, u8 *final, ++ struct drm_rect *clip_ongoing_new_areas, struct rockchip_ebc_area *area, ++ u32 frame, u8 last_phase); ++ ++void rockchip_ebc_schedule_cancel_blit_a2_neon( ++ const struct rockchip_ebc_ctx *ctx, u8 *frame_num, u8 *prev, u8 *next, ++ u8 *final, struct drm_rect *clip_ongoing_new_areas, ++ struct rockchip_ebc_area *area, u8 last_phase, ++ u8 early_cancellation_addition); ++ ++#endif /* _ROCKCHIP_EBC_BLIT_NEON_H */ +diff --git a/drivers/iio/accel/st_accel_i2c.c b/drivers/iio/accel/st_accel_i2c.c +index f24449500..5f6dd93c7 100644 +--- a/drivers/iio/accel/st_accel_i2c.c ++++ b/drivers/iio/accel/st_accel_i2c.c +@@ -202,11 +202,51 @@ static int st_accel_i2c_probe(struct i2c_client *client) + return st_accel_common_probe(indio_dev); + } + ++static int __maybe_unused st_accel_suspend(struct device *dev) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct iio_dev *indio_dev; ++ int ret; ++ struct st_sensor_data *sdata; ++ ++ indio_dev = i2c_get_clientdata(client); ++ sdata = iio_priv(indio_dev); ++ pr_info("%s", __func__); ++ st_sensors_set_enable(indio_dev, false); ++ st_sensors_set_dataready_irq(indio_dev, false); ++ disable_irq(sdata->irq); ++ ++ return 0; ++} ++ ++static int __maybe_unused st_accel_resume(struct device *dev) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct iio_dev *indio_dev; ++ struct st_sensor_data *sdata; ++ int ret; ++ ++ indio_dev = i2c_get_clientdata(client); ++ sdata = iio_priv(indio_dev); ++ ++ pr_info("%s", __func__); ++ st_sensors_set_enable(indio_dev, true); ++ st_sensors_set_dataready_irq(indio_dev, true); ++ // enabling the irq here will reduce the number of spurious irqs, and the ++ // device will keep on working after resume. ++ // Still not enough... ++ enable_irq(sdata->irq); ++ return 0; ++} ++ ++static SIMPLE_DEV_PM_OPS(st_accel_pm, st_accel_suspend, st_accel_resume); ++ + static struct i2c_driver st_accel_driver = { + .driver = { + .name = "st-accel-i2c", + .of_match_table = st_accel_of_match, + .acpi_match_table = st_accel_acpi_match, ++ .pm = &st_accel_pm, + }, + .probe = st_accel_i2c_probe, + .id_table = st_accel_id_table, +diff --git a/drivers/iio/common/st_sensors/st_sensors_trigger.c b/drivers/iio/common/st_sensors/st_sensors_trigger.c +index 8a8ab688d..d989f0e78 100644 +--- a/drivers/iio/common/st_sensors/st_sensors_trigger.c ++++ b/drivers/iio/common/st_sensors/st_sensors_trigger.c +@@ -87,6 +87,16 @@ static irqreturn_t st_sensors_irq_thread(int irq, void *p) + st_sensors_new_samples_available(indio_dev, sdata)) { + iio_trigger_poll_nested(p); + } else { ++ pr_info( ++ "st_sensors_irq_thread: %i returning IRQ_NONE (%i/%i/%i/%u) %s", ++ irq, ++ sdata->hw_irq_trigger, ++ st_sensors_new_samples_available(indio_dev, sdata), ++ sdata->num_data_channels, ++ sdata->odr, ++ trig->name ++ ); ++ /* return IRQ_HANDLED; */ + dev_dbg(indio_dev->dev.parent, "spurious IRQ\n"); + return IRQ_NONE; + } +diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig +index 94a753fcb..bb51f2772 100644 +--- a/drivers/input/misc/Kconfig ++++ b/drivers/input/misc/Kconfig +@@ -1003,4 +1003,12 @@ config INPUT_STPMIC1_ONKEY + To compile this driver as a module, choose M here: the + module will be called stpmic1_onkey. + ++config INPUT_WS8100_PEN ++ tristate "Wisesun WS8100 BLE Pen support" ++ depends on SPI ++ help ++ Say Y to enable support for the buttons on Bluetooth LE pens ++ connected via a Wisesun WS8100 BLE controller, such as used on the ++ Pine64 PineNote. ++ + endif +diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile +index 415fc4e29..7151713af 100644 +--- a/drivers/input/misc/Makefile ++++ b/drivers/input/misc/Makefile +@@ -93,6 +93,7 @@ obj-$(CONFIG_INPUT_TWL6040_VIBRA) += twl6040-vibra.o + obj-$(CONFIG_INPUT_UINPUT) += uinput.o + obj-$(CONFIG_INPUT_WISTRON_BTNS) += wistron_btns.o + obj-$(CONFIG_INPUT_WM831X_ON) += wm831x-on.o ++obj-$(CONFIG_INPUT_WS8100_PEN) += ws8100-pen.o + obj-$(CONFIG_INPUT_XEN_KBDDEV_FRONTEND) += xen-kbdfront.o + obj-$(CONFIG_INPUT_YEALINK) += yealink.o + obj-$(CONFIG_INPUT_IDEAPAD_SLIDEBAR) += ideapad_slidebar.o +diff --git a/drivers/input/misc/ws8100-pen.c b/drivers/input/misc/ws8100-pen.c +new file mode 100644 +index 000000000..ce6a31d10 +--- /dev/null ++++ b/drivers/input/misc/ws8100-pen.c +@@ -0,0 +1,801 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * WS8100 BLE Pen Driver ++ * ++ * Copyright (c) 2022 Samuel Holland ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define WS8100_IRQ_PEN_VERSION_INFO 0x12 ++#define WS8100_IRQ_PEN_BATTERY_INFO 0x13 ++#define WS8100_IRQ_PEN_OTA_STATUS 0x14 ++#define WS8100_IRQ_OTA_STATUS_A 0x45 ++#define WS8100_IRQ_OTA_STATUS_B 0x46 ++#define WS8100_IRQ_OTA_STATUS_C 0x47 ++#define WS8100_IRQ_INPUT 0x66 ++#define WS8100_IRQ_SCAN_COMPLETE 0x77 ++ ++#define WS8100_ADDRESS_LEN 6 ++#define WS8100_BATTERY_LEN 2 ++#define WS8100_COMMAND_LEN 3 ++#define WS8100_READ_STATUS_LEN 2 ++#define WS8100_VERSION_LEN 3 ++ ++#define WS8100_MAX_RX_LEN 10 ++#define WS8100_MAX_SCAN_RESULTS 50 ++#define WS8100_MAX_XFER_RETRY 3 ++ ++#define WS8100_RX_DATA_OFFSET 2 ++#define WS8100_RX_OVERHEAD 3 ++ ++struct ws8100_pen { ++ struct completion pen_battery_read_complete; ++ struct completion pen_version_read_complete; ++ struct completion scan_complete; ++ struct gpio_desc *cs_gpio; ++ struct gpio_desc *host_state_gpio; ++ struct gpio_desc *reset_gpio; ++ struct input_dev *input; ++ struct mutex scan_results_mutex; ++ struct mutex xfer_mutex; ++ ++ ktime_t pen_battery_time; ++ bool pen_present; ++ ++ u8 controller_version[WS8100_VERSION_LEN]; ++ u8 pen_address[WS8100_ADDRESS_LEN + 1]; ++ u8 pen_battery[WS8100_BATTERY_LEN]; ++ u8 pen_version[WS8100_VERSION_LEN]; ++ u8 scan_results[WS8100_MAX_SCAN_RESULTS][WS8100_ADDRESS_LEN]; ++ u8 scan_result_count; ++}; ++ ++/* ++ * All transfers follow the same pattern: ++ * 1) Command, always 3 bytes: ++ * [r/w] Always 0x50 for "writes", 0x51 for "reads" ++ * [op] Determines what the command does ++ * [sum] Sum to 0x00 ++ * 2) Transmitted data, optional, variable length: ++ * [data] Zero or more bytes, depending on the command ++ * [sum] Sum to 0x00, only present if there is data ++ * 3) Received data, variable length: ++ * [r/w] Copy of the byte from the command ++ * [op] Copy of the byte from the command ++ * [data] Zero or more bytes, depending on the command ++ * [sum] Sum to 0x00 ++ */ ++struct ws8100_cmd { ++ u8 command[WS8100_COMMAND_LEN]; ++ u8 rx_data_len; ++ u16 tx_data_len; ++}; ++ ++#define WS8100_CMD(_name, _cmd0, _cmd1, _tx, _rx) \ ++ static const struct ws8100_cmd _name = { \ ++ .command = { _cmd0, _cmd1, (0x100 - _cmd0 - _cmd1) }, \ ++ .tx_data_len = _tx, \ ++ .rx_data_len = _rx, \ ++ } ++ ++/* r/w op tx rx */ ++WS8100_CMD(ws8100_cmd_read_from_pen, 0x51, 0x11, 2, 2); ++WS8100_CMD(ws8100_cmd_get_pen_version, 0x51, 0x12, 0, 3); ++WS8100_CMD(ws8100_cmd_get_pen_battery, 0x51, 0x13, 0, 2); ++WS8100_CMD(ws8100_cmd_get_pen_ota_status, 0x51, 0x14, 0, 1); ++WS8100_CMD(ws8100_cmd_start_pen_ota, 0x50, 0x16, 0, 0); ++WS8100_CMD(ws8100_cmd_set_suspend_mode, 0x50, 0x22, 2, 1); ++WS8100_CMD(ws8100_cmd_get_controller_version, 0x51, 0x33, 0, 3); ++WS8100_CMD(ws8100_cmd_start_ota, 0x50, 0x44, 7, 1); ++WS8100_CMD(ws8100_cmd_write_ota_data, 0x50, 0x45, 504, 1); ++WS8100_CMD(ws8100_cmd_cancel_ota, 0x50, 0x46, 0, 0); ++WS8100_CMD(ws8100_cmd_get_ota_status, 0x51, 0x47, 0, 1); ++WS8100_CMD(ws8100_cmd_stop_ota, 0x50, 0x48, 0, 0); ++WS8100_CMD(ws8100_cmd_set_default_pen_address, 0x50, 0x55, 7, 1); ++WS8100_CMD(ws8100_cmd_get_default_pen_address, 0x51, 0x55, 0, 7); ++WS8100_CMD(ws8100_cmd_get_input, 0x51, 0x66, 0, 1); ++WS8100_CMD(ws8100_cmd_start_scan, 0x50, 0x77, 0, 0); ++WS8100_CMD(ws8100_cmd_get_scan_result_count, 0x51, 0x78, 0, 1); ++WS8100_CMD(ws8100_cmd_get_irq, 0x51, 0x88, 0, 1); ++WS8100_CMD(ws8100_cmd_get_scan_result, 0x51, 0xc0, 0, 6); ++ ++static int ws8100_pen_xfer(struct spi_device *spi, ++ const struct ws8100_cmd *c, ++ u8 *tx_data, u8 *rx_data) ++{ ++ struct ws8100_pen *pen = spi_get_drvdata(spi); ++ u8 rx_len = WS8100_RX_OVERHEAD + c->rx_data_len; ++ u8 rx_buf[WS8100_MAX_RX_LEN]; ++ int ret, try; ++ u8 i, sum; ++ struct spi_transfer xfers[3] = { ++ { ++ .tx_buf = c->command, ++ .len = WS8100_COMMAND_LEN, ++ .delay = { .value = c->tx_data_len ? 0 : 50 }, ++ }, ++ { ++ .tx_buf = tx_data, ++ .len = c->tx_data_len, ++ .delay = { .value = 50 }, ++ }, ++ { ++ .rx_buf = rx_buf, ++ .len = rx_len, ++ }, ++ }; ++ ++ if ((c->tx_data_len && !tx_data) || (c->rx_data_len && !rx_data)) ++ return -EINVAL; ++ if (rx_len > sizeof(rx_buf)) ++ return -EINVAL; ++ ++ mutex_lock(&pen->xfer_mutex); ++ ++ dev_dbg(&spi->dev, "send [%*ph] [%*ph]\n", ++ WS8100_COMMAND_LEN, c->command, c->tx_data_len, tx_data); ++ ++ for (try = 0; try < WS8100_MAX_XFER_RETRY; ++try) { ++ if (try) ++ msleep(50); ++ ++ gpiod_set_value_cansleep(pen->cs_gpio, 1); ++ msleep(10); ++ ret = spi_sync_transfer(spi, xfers, ARRAY_SIZE(xfers)); ++ gpiod_set_value_cansleep(pen->cs_gpio, 0); ++ if (ret) ++ goto out_unlock; ++ ++ dev_dbg(&spi->dev, "recv [%*ph]\n", rx_len, rx_buf); ++ ++ if (rx_buf[0] != c->command[0]) ++ continue; ++ for (i = 0, sum = 0; i < rx_len; ++i) ++ sum += rx_buf[i]; ++ if (sum) ++ continue; ++ ++ memcpy(rx_data, &rx_buf[WS8100_RX_DATA_OFFSET], c->rx_data_len); ++ goto out_unlock; ++ } ++ ++ ret = -EBADMSG; ++ ++out_unlock: ++ mutex_unlock(&pen->xfer_mutex); ++ ++ return ret; ++} ++ ++static void ws8100_pen_input(struct spi_device *spi) ++{ ++ struct ws8100_pen *pen = spi_get_drvdata(spi); ++ int code = 0, ret, tool = 0; ++ u8 input_status; ++ ++ ret = ws8100_pen_xfer(spi, &ws8100_cmd_get_input, NULL, &input_status); ++ if (ret) { ++ dev_warn(&spi->dev, "Failed to read input: %d\n", ret); ++ return; ++ } ++ ++ dev_dbg(&spi->dev, "Got input 0x%02x\n", input_status); ++ ++ switch (input_status) { ++ case 0x02: /* short press */ ++ code = BTN_STYLUS; ++ tool = BTN_TOOL_RUBBER; ++ break; ++ case 0x20: /* long press */ ++ code = KEY_BACK; ++ tool = BTN_TOOL_RUBBER; ++ break; ++ case 0x22: /* double press */ ++ code = KEY_MACRO1; ++ tool = BTN_TOOL_RUBBER; ++ break; ++ case 0x23: /* leading edge */ ++ tool = BTN_TOOL_RUBBER; ++ break; ++ ++ case 0x04: /* short press */ ++ code = BTN_STYLUS2; ++ tool = BTN_TOOL_PEN; ++ break; ++ case 0x40: /* long press */ ++ code = KEY_FORWARD; ++ tool = BTN_TOOL_PEN; ++ break; ++ case 0x42: /* double press */ ++ code = KEY_MACRO2; ++ tool = BTN_TOOL_PEN; ++ break; ++ case 0x43: /* leading edge */ ++ tool = BTN_TOOL_PEN; ++ break; ++ ++ case 0x01: /* short press */ ++ code = BTN_STYLUS3; ++ break; ++ case 0x10: /* long press */ ++ code = KEY_SLEEP; ++ break; ++ case 0x08: /* double press */ ++ code = KEY_MACRO3; ++ break; ++ ++ default: ++ dev_warn(&spi->dev, "Unknown input: 0x%02x\n", input_status); ++ break; ++ } ++ ++ if (code) { ++ input_report_key(pen->input, code, 1); ++ input_sync(pen->input); ++ input_report_key(pen->input, code, 0); ++ if (tool) ++ input_report_key(pen->input, tool, 0); ++ } else { ++ if (tool) ++ input_report_key(pen->input, tool, 1); ++ } ++ input_event(pen->input, EV_MSC, MSC_SCAN, input_status); ++ input_sync(pen->input); ++} ++ ++static void ws8100_pen_scan_complete(struct spi_device *spi) ++{ ++ struct ws8100_cmd result_cmd = ws8100_cmd_get_scan_result; ++ struct ws8100_pen *pen = spi_get_drvdata(spi); ++ u8 i, scan_result_count; ++ int ret; ++ ++ mutex_lock(&pen->scan_results_mutex); ++ ++ ret = ws8100_pen_xfer(spi, &ws8100_cmd_get_scan_result_count, ++ NULL, &scan_result_count); ++ if (ret) ++ goto out_unlock; ++ ++ pen->scan_result_count = min_t(u8, scan_result_count, WS8100_MAX_SCAN_RESULTS); ++ for (i = 0; i < pen->scan_result_count; ++i) { ++ ret = ws8100_pen_xfer(spi, &result_cmd, ++ NULL, pen->scan_results[i]); ++ if (ret) ++ goto out_unlock; ++ ++ result_cmd.command[1]++; /* increment the result index */ ++ result_cmd.command[2]--; /* decrement the checksum to match */ ++ } ++ ++out_unlock: ++ if (ret) { ++ dev_warn(&spi->dev, "Failed to read scan results: %d\n", ret); ++ pen->scan_result_count = 0; ++ } ++ ++ mutex_unlock(&pen->scan_results_mutex); ++} ++ ++static irqreturn_t ws8100_pen_irq(int irq, void *data) ++{ ++ struct spi_device *spi = data; ++ struct ws8100_pen *pen = spi_get_drvdata(spi); ++ u8 irq_status; ++ int ret; ++ ++ ret = ws8100_pen_xfer(spi, &ws8100_cmd_get_irq, NULL, &irq_status); ++ if (ret) { ++ dev_warn(&spi->dev, "Failed to read IRQ: %d\n", ret); ++ return IRQ_HANDLED; ++ } ++ ++ dev_dbg(&spi->dev, "Got IRQ 0x%02x\n", irq_status); ++ ++ switch (irq_status) { ++ case WS8100_IRQ_PEN_VERSION_INFO: ++ ws8100_pen_xfer(spi, &ws8100_cmd_get_pen_version, ++ NULL, pen->pen_version); ++ complete(&pen->pen_version_read_complete); ++ break; ++ case WS8100_IRQ_PEN_BATTERY_INFO: ++ ws8100_pen_xfer(spi, &ws8100_cmd_get_pen_battery, ++ NULL, pen->pen_battery); ++ complete(&pen->pen_battery_read_complete); ++ break; ++ case WS8100_IRQ_INPUT: ++ ws8100_pen_input(spi); ++ break; ++ case WS8100_IRQ_SCAN_COMPLETE: ++ ws8100_pen_scan_complete(spi); ++ complete(&pen->scan_complete); ++ break; ++ default: ++ dev_warn(&spi->dev, "Unknown IRQ: 0x%02x\n", irq_status); ++ break; ++ } ++ ++ return IRQ_HANDLED; ++} ++ ++static int ws8100_pen_get_controller_version(struct spi_device *spi) ++{ ++ struct ws8100_pen *pen = spi_get_drvdata(spi); ++ ++ return ws8100_pen_xfer(spi, &ws8100_cmd_get_controller_version, ++ NULL, pen->controller_version); ++} ++ ++static int ws8100_pen_get_pen_address(struct spi_device *spi) ++{ ++ struct ws8100_pen *pen = spi_get_drvdata(spi); ++ ++ return ws8100_pen_xfer(spi, &ws8100_cmd_get_default_pen_address, ++ NULL, pen->pen_address); ++} ++ ++static int ws8100_pen_get_pen_battery(struct spi_device *spi) ++{ ++ struct ws8100_pen *pen = spi_get_drvdata(spi); ++ u8 read_status[WS8100_READ_STATUS_LEN]; ++ u8 battery_data[2] = { 0x02, 0xfe }; ++ int ret; ++ ++ reinit_completion(&pen->pen_battery_read_complete); ++ ++ ret = ws8100_pen_xfer(spi, &ws8100_cmd_read_from_pen, ++ battery_data, read_status); ++ if (ret) ++ return ret; ++ ++ /* Fail early if no pen is connected. */ ++ if (!read_status[0]) ++ return -ENXIO; ++ ++ /* Usually takes 100-200 ms. */ ++ if (!wait_for_completion_timeout(&pen->pen_battery_read_complete, ++ msecs_to_jiffies(500))) ++ return -ETIMEDOUT; ++ ++ return 0; ++} ++ ++static int ws8100_pen_get_pen_version(struct spi_device *spi) ++{ ++ struct ws8100_pen *pen = spi_get_drvdata(spi); ++ u8 read_status[WS8100_READ_STATUS_LEN]; ++ u8 version_data[2] = { 0x01, 0xff }; ++ int ret; ++ ++ reinit_completion(&pen->pen_version_read_complete); ++ ++ ret = ws8100_pen_xfer(spi, &ws8100_cmd_read_from_pen, ++ version_data, read_status); ++ if (ret) ++ return ret; ++ ++ /* Fail early if no pen is connected. */ ++ if (!read_status[0]) ++ return -ENXIO; ++ ++ /* Usually takes 100-200 ms. */ ++ if (!wait_for_completion_timeout(&pen->pen_version_read_complete, ++ msecs_to_jiffies(500))) ++ return -ETIMEDOUT; ++ ++ return 0; ++} ++ ++static int ws8100_pen_scan(struct spi_device *spi) ++{ ++ struct ws8100_pen *pen = spi_get_drvdata(spi); ++ int ret; ++ ++ reinit_completion(&pen->scan_complete); ++ ++ ret = ws8100_pen_xfer(spi, &ws8100_cmd_start_scan, NULL, NULL); ++ if (ret) ++ return ret; ++ ++ /* Scans usually take about 12 seconds, but sometimes longer. */ ++ if (!wait_for_completion_interruptible_timeout(&pen->scan_complete, ++ msecs_to_jiffies(20000))) ++ return -ETIMEDOUT; ++ ++ return 0; ++} ++ ++static const enum power_supply_property ws8100_pen_psy_properties[] = { ++ POWER_SUPPLY_PROP_STATUS, ++ POWER_SUPPLY_PROP_PRESENT, ++ POWER_SUPPLY_PROP_CAPACITY, ++ POWER_SUPPLY_PROP_SCOPE, ++}; ++ ++static int ws8100_pen_psy_get_proprty(struct power_supply *psy, ++ enum power_supply_property psp, ++ union power_supply_propval *val) ++{ ++ struct spi_device *spi = power_supply_get_drvdata(psy); ++ struct ws8100_pen *pen = spi_get_drvdata(spi); ++ ktime_t now = ktime_get(); ++ int ret; ++ ++ ++ if (ktime_after(now, pen->pen_battery_time)) { ++ ret = ws8100_pen_get_pen_battery(spi); ++ if (ret && ret != -ENXIO) ++ return ret; ++ ++ pen->pen_battery_time = ktime_add_ns(now, NSEC_PER_SEC); ++ pen->pen_present = !ret; ++ } ++ ++ switch (psp) { ++ case POWER_SUPPLY_PROP_STATUS: ++ val->intval = pen->pen_battery[0] ++ ? POWER_SUPPLY_STATUS_CHARGING ++ : POWER_SUPPLY_STATUS_DISCHARGING; ++ return 0; ++ case POWER_SUPPLY_PROP_PRESENT: ++ val->intval = pen->pen_present; ++ return 0; ++ case POWER_SUPPLY_PROP_CAPACITY: ++ val->intval = pen->pen_battery[1]; ++ return 0; ++ case POWER_SUPPLY_PROP_SCOPE: ++ val->intval = POWER_SUPPLY_SCOPE_DEVICE; ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++static const struct power_supply_desc ws8100_pen_psy_desc = { ++ .name = "ws8100_pen", ++ .type = POWER_SUPPLY_TYPE_BATTERY, ++ .properties = ws8100_pen_psy_properties, ++ .num_properties = ARRAY_SIZE(ws8100_pen_psy_properties), ++ .get_property = ws8100_pen_psy_get_proprty, ++}; ++ ++static ssize_t controller_version_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct ws8100_pen *pen = dev_get_drvdata(dev); ++ struct spi_device *spi = to_spi_device(dev); ++ int ret; ++ ++ ret = ws8100_pen_get_controller_version(spi); ++ if (ret) ++ return ret; ++ ++ return sprintf(buf, "%.*s\n", WS8100_VERSION_LEN, ++ pen->controller_version); ++} ++ ++static DEVICE_ATTR_RO(controller_version); ++ ++static ssize_t host_state_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct ws8100_pen *pen = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "%d\n", ++ gpiod_get_value_cansleep(pen->host_state_gpio)); ++} ++ ++static ssize_t host_state_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct ws8100_pen *pen = dev_get_drvdata(dev); ++ bool state; ++ int ret; ++ ++ ret = kstrtobool(buf, &state); ++ if (ret) ++ return ret; ++ ++ gpiod_set_value_cansleep(pen->host_state_gpio, state); ++ ++ return count; ++} ++ ++static DEVICE_ATTR_RW(host_state); ++ ++static ssize_t pen_address_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct ws8100_pen *pen = dev_get_drvdata(dev); ++ struct spi_device *spi = to_spi_device(dev); ++ int ret; ++ ++ ret = ws8100_pen_get_pen_address(spi); ++ if (ret) ++ return ret; ++ ++ return sprintf(buf, "%pM\n", pen->pen_address); ++} ++ ++static ssize_t pen_address_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct spi_device *spi = to_spi_device(dev); ++ u8 address[WS8100_ADDRESS_LEN + 1]; ++ u8 status, sum; ++ int i, ret; ++ ++ ret = sscanf(buf, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", ++ &address[0], &address[1], &address[2], ++ &address[3], &address[4], &address[5]); ++ if (ret != WS8100_ADDRESS_LEN) ++ return -EINVAL; ++ ++ for (i = 0, sum = 0; i < WS8100_ADDRESS_LEN; ++i) ++ sum += address[i]; ++ address[WS8100_ADDRESS_LEN] = -sum; ++ ++ ret = ws8100_pen_xfer(spi, &ws8100_cmd_set_default_pen_address, ++ address, &status); ++ if (ret) ++ return ret; ++ ++ return count; ++} ++ ++static DEVICE_ATTR_RW(pen_address); ++ ++static ssize_t pen_battery_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct ws8100_pen *pen = dev_get_drvdata(dev); ++ struct spi_device *spi = to_spi_device(dev); ++ int ret; ++ ++ ret = ws8100_pen_get_pen_battery(spi); ++ if (ret) ++ return ret; ++ ++ return sprintf(buf, "%*ph\n", WS8100_BATTERY_LEN, pen->pen_battery); ++} ++ ++static DEVICE_ATTR_RO(pen_battery); ++ ++static ssize_t pen_version_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct ws8100_pen *pen = dev_get_drvdata(dev); ++ struct spi_device *spi = to_spi_device(dev); ++ int ret; ++ ++ ret = ws8100_pen_get_pen_version(spi); ++ if (ret) ++ return ret; ++ ++ return sprintf(buf, "%.*s\n", WS8100_VERSION_LEN, pen->pen_version); ++} ++ ++static DEVICE_ATTR_RO(pen_version); ++ ++static ssize_t scan_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct ws8100_pen *pen = dev_get_drvdata(dev); ++ ssize_t count; ++ u8 i; ++ ++ mutex_lock(&pen->scan_results_mutex); ++ ++ count = sprintf(buf, "Scan Results (%d)\n-----------------\n", ++ pen->scan_result_count); ++ ++ for (i = 0; i < pen->scan_result_count; ++i) ++ count = sprintf(buf, "%s%pM\n", buf, pen->scan_results[i]); ++ ++ mutex_unlock(&pen->scan_results_mutex); ++ ++ return count; ++} ++ ++static ssize_t scan_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct spi_device *spi = to_spi_device(dev); ++ int ret; ++ ++ ret = ws8100_pen_scan(spi); ++ if (ret) ++ return ret; ++ ++ return count; ++} ++ ++static DEVICE_ATTR_RW(scan); ++ ++static ssize_t suspend_mode_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct spi_device *spi = to_spi_device(dev); ++ u8 mode[2], status; ++ int ret; ++ ++ ret = kstrtou8(buf, 10, &mode[0]); ++ if (ret) ++ return ret; ++ mode[1] = -mode[0]; ++ ++ ret = ws8100_pen_xfer(spi, &ws8100_cmd_set_suspend_mode, mode, &status); ++ if (ret) ++ return ret; ++ ++ return count; ++} ++ ++static DEVICE_ATTR_WO(suspend_mode); ++ ++static struct attribute *ws8100_pen_attributes[] = { ++ &dev_attr_controller_version.attr, ++ &dev_attr_host_state.attr, ++ &dev_attr_pen_address.attr, ++ &dev_attr_pen_battery.attr, ++ &dev_attr_pen_version.attr, ++ &dev_attr_scan.attr, ++ &dev_attr_suspend_mode.attr, ++ NULL ++}; ++ ++static const struct attribute_group *ws8100_pen_groups[] = { ++ &(const struct attribute_group) { ++ .attrs = ws8100_pen_attributes, ++ }, ++ NULL ++}; ++ ++static int ws8100_pen_suspend(struct device *dev) ++{ ++ struct ws8100_pen *pen = dev_get_drvdata(dev); ++ ++ gpiod_set_value_cansleep(pen->host_state_gpio, 0); ++ ++ return 0; ++} ++ ++static int ws8100_pen_resume(struct device *dev) ++{ ++ struct ws8100_pen *pen = dev_get_drvdata(dev); ++ ++ gpiod_set_value_cansleep(pen->host_state_gpio, 1); ++ ++ return 0; ++} ++ ++static int ws8100_pen_probe(struct spi_device *spi) ++{ ++ struct power_supply_config psy_cfg = { .drv_data = spi }; ++ struct device *dev = &spi->dev; ++ struct power_supply *psy; ++ struct ws8100_pen *pen; ++ int code, ret; ++ ++ pen = devm_kzalloc(dev, sizeof(*pen), GFP_KERNEL); ++ if (!pen) ++ return -ENOMEM; ++ ++ spi_set_drvdata(spi, pen); ++ init_completion(&pen->pen_battery_read_complete); ++ init_completion(&pen->pen_version_read_complete); ++ init_completion(&pen->scan_complete); ++ mutex_init(&pen->scan_results_mutex); ++ mutex_init(&pen->xfer_mutex); ++ ++ pen->cs_gpio = devm_gpiod_get(dev, "cs", GPIOD_OUT_LOW); ++ if (IS_ERR(pen->cs_gpio)) ++ return dev_err_probe(dev, PTR_ERR(pen->cs_gpio), ++ "Failed to get %s GPIO\n", "cs"); ++ ++ pen->host_state_gpio = devm_gpiod_get(dev, "host_state", GPIOD_OUT_HIGH); ++ if (IS_ERR(pen->host_state_gpio)) ++ return dev_err_probe(dev, PTR_ERR(pen->host_state_gpio), ++ "Failed to get %s GPIO\n", "host_state"); ++ ++ pen->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); ++ if (IS_ERR(pen->reset_gpio)) ++ return dev_err_probe(dev, PTR_ERR(pen->reset_gpio), ++ "Failed to get %s GPIO\n", "reset"); ++ ++ pen->input = devm_input_allocate_device(dev); ++ if (!pen->input) ++ return -ENOMEM; ++ ++ input_set_drvdata(pen->input, spi); ++ ++ pen->input->name = "ws8100_pen"; ++ pen->input->phys = "ws8100_pen/input0"; ++ pen->input->id.bustype = BUS_BLUETOOTH; ++ ++ __set_bit(EV_KEY, pen->input->evbit); ++ __set_bit(KEY_SLEEP, pen->input->keybit); ++ __set_bit(KEY_BACK, pen->input->keybit); ++ __set_bit(KEY_FORWARD, pen->input->keybit); ++ __set_bit(BTN_TOOL_PEN, pen->input->keybit); ++ __set_bit(BTN_TOOL_RUBBER, pen->input->keybit); ++ __set_bit(BTN_STYLUS3, pen->input->keybit); ++ __set_bit(BTN_STYLUS, pen->input->keybit); ++ __set_bit(BTN_STYLUS2, pen->input->keybit); ++ for (code = KEY_MACRO1; code < KEY_MACRO4; ++code) ++ __set_bit(code, pen->input->keybit); ++ ++ __set_bit(EV_MSC, pen->input->evbit); ++ __set_bit(MSC_SCAN, pen->input->mscbit); ++ ++ ret = input_register_device(pen->input); ++ if (ret) ++ return dev_err_probe(dev, ret, "Failed to register input\n"); ++ ++ psy = devm_power_supply_register(dev, &ws8100_pen_psy_desc, &psy_cfg); ++ if (IS_ERR(psy)) ++ return PTR_ERR(psy); ++ ++ power_supply_powers(psy, &pen->input->dev); ++ ++ ret = devm_request_threaded_irq(dev, spi->irq, NULL, ws8100_pen_irq, ++ IRQF_ONESHOT, dev_name(dev), spi); ++ if (ret) ++ return dev_err_probe(dev, ret, "Failed to request IRQ\n"); ++ ++ /* Ensure the controller is fully reset. */ ++ msleep(50); ++ ++ gpiod_set_value_cansleep(pen->reset_gpio, 0); ++ ++ return 0; ++} ++ ++static void ws8100_pen_shutdown(struct spi_device *spi) ++{ ++ ws8100_pen_suspend(&spi->dev); ++} ++ ++static const struct spi_device_id ws8100_pen_id_table[] = { ++ { .name = "pinenote-ws8100-pen" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(spi, ws8100_pen_id_table); ++ ++static const struct of_device_id ws8100_pen_of_match[] = { ++ { .compatible = "pine64,pinenote-ws8100-pen" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, ws8100_pen_of_match); ++ ++static SIMPLE_DEV_PM_OPS(ws8100_pen_pm_ops, ++ ws8100_pen_suspend, ws8100_pen_resume); ++ ++static struct spi_driver ws8100_pen_driver = { ++ .id_table = ws8100_pen_id_table, ++ .probe = ws8100_pen_probe, ++ .shutdown = ws8100_pen_shutdown, ++ .driver = { ++ .name = "ws8100_pen", ++ .of_match_table = ws8100_pen_of_match, ++ .dev_groups = ws8100_pen_groups, ++ .pm = &ws8100_pen_pm_ops, ++ }, ++}; ++ ++module_spi_driver(ws8100_pen_driver); ++ ++MODULE_DESCRIPTION("WS8100 BLE Pen Driver"); ++MODULE_AUTHOR("Samuel Holland "); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/input/touchscreen/cyttsp5.c b/drivers/input/touchscreen/cyttsp5.c +index 47f427139..63e71f3b1 100644 +--- a/drivers/input/touchscreen/cyttsp5.c ++++ b/drivers/input/touchscreen/cyttsp5.c +@@ -525,15 +525,66 @@ static int cyttsp5_get_sysinfo_regs(struct cyttsp5 *ts) + struct cyttsp5_sensing_conf_data_dev *scd_dev = + (struct cyttsp5_sensing_conf_data_dev *) + &ts->response_buf[HID_SYSINFO_SENSING_OFFSET]; ++ u32 tmp; + + cyttsp5_si_get_btn_data(ts); + + scd->max_tch = scd_dev->max_num_of_tch_per_refresh_cycle; +- scd->res_x = get_unaligned_le16(&scd_dev->res_x); +- scd->res_y = get_unaligned_le16(&scd_dev->res_y); +- scd->max_z = get_unaligned_le16(&scd_dev->max_z); +- scd->len_x = get_unaligned_le16(&scd_dev->len_x); +- scd->len_y = get_unaligned_le16(&scd_dev->len_y); ++ ++ if (scd->max_tch == 0) { ++ dev_dbg(ts->dev, "Max touch points cannot be zero\n"); ++ scd->max_tch = 2; ++ } ++ ++ if(device_property_read_u32(ts->dev, "touchscreen-size-x", &tmp)) ++ scd->res_x = get_unaligned_le16(&scd_dev->res_x); ++ else ++ scd->res_x = tmp; ++ ++ if (scd->res_x == 0) { ++ dev_err(ts->dev, "ABS_X cannot be zero\n"); ++ return -ENODATA; ++ } ++ ++ if(device_property_read_u32(ts->dev, "touchscreen-size-y", &tmp)) ++ scd->res_y = get_unaligned_le16(&scd_dev->res_y); ++ else ++ scd->res_y = tmp; ++ ++ if (scd->res_y == 0) { ++ dev_err(ts->dev, "ABS_Y cannot be zero\n"); ++ return -ENODATA; ++ } ++ ++ if(device_property_read_u32(ts->dev, "touchscreen-max-pressure", &tmp)) ++ scd->max_z = get_unaligned_le16(&scd_dev->max_z); ++ else ++ scd->max_z = tmp; ++ ++ if (scd->max_z == 0) { ++ dev_err(ts->dev, "ABS_PRESSURE cannot be zero\n"); ++ return -ENODATA; ++ } ++ ++ if(device_property_read_u32(ts->dev, "touchscreen-x-mm", &tmp)) ++ scd->len_x = get_unaligned_le16(&scd_dev->len_x); ++ else ++ scd->len_x = tmp; ++ ++ if (scd->len_x == 0) { ++ dev_dbg(ts->dev, "Touchscreen size x cannot be zero\n"); ++ scd->len_x = scd->res_x + 1; ++ } ++ ++ if(device_property_read_u32(ts->dev, "touchscreen-y-mm", &tmp)) ++ scd->len_y = get_unaligned_le16(&scd_dev->len_y); ++ else ++ scd->len_y = tmp; ++ ++ if (scd->len_y == 0) { ++ dev_dbg(ts->dev, "Touchscreen size y cannot be zero\n"); ++ scd->len_y = scd->res_y + 1; ++ } + + return 0; + } +@@ -933,6 +984,7 @@ static int cyttsp5_i2c_probe(struct i2c_client *client) + + static const struct of_device_id cyttsp5_of_match[] = { + { .compatible = "cypress,tt21000", }, ++ { .compatible = "cypress,tma448", }, + { } + }; + MODULE_DEVICE_TABLE(of, cyttsp5_of_match); +diff --git a/drivers/media/platform/rockchip/rga/rga-hw.c b/drivers/media/platform/rockchip/rga/rga-hw.c +index 43ed742a1..1136fbf29 100644 +--- a/drivers/media/platform/rockchip/rga/rga-hw.c ++++ b/drivers/media/platform/rockchip/rga/rga-hw.c +@@ -213,6 +213,11 @@ static void rga_cmd_set_trans_info(struct rga_ctx *ctx) + } + } + ++ dst_info.data.sw_dst_fmt_yuv400_en = ctx->yuv400_enable; ++ dst_info.data.sw_dst_fmt_y4_en = ctx->y4_enable; ++ dst_info.data.dither_down_en = ctx->dither_down_enable; ++ dst_info.data.dither_down_mode = ctx->dither_down_mode; ++ + if (ctx->vflip) + src_info.data.mir_mode |= RGA_SRC_MIRR_MODE_X; + +@@ -234,6 +239,7 @@ static void rga_cmd_set_trans_info(struct rga_ctx *ctx) + break; + } + ++ + /* + * Calculate the up/down scaling mode/factor. + * +@@ -293,6 +299,7 @@ static void rga_cmd_set_trans_info(struct rga_ctx *ctx) + src_act_info.data.act_width = src_w - 1; + + dst_vir_info.data.vir_stride = ctx->out.stride >> 2; ++ + dst_act_info.data.act_height = dst_h - 1; + dst_act_info.data.act_width = dst_w - 1; + +@@ -407,6 +414,10 @@ static void rga_cmd_set_mode(struct rga_ctx *ctx) + dest[(RGA_ALPHA_CTRL1 - RGA_MODE_BASE_REG) >> 2] = alpha_ctrl1.val; + + dest[(RGA_MODE_CTRL - RGA_MODE_BASE_REG) >> 2] = mode.val; ++ ++ // LUTS Y4 ++ dest[(RGA_RST_Y4MAP_LUT0 - RGA_MODE_BASE_REG) >> 2] = ctx->y4map_lut0; ++ dest[(RGA_RST_Y4MAP_LUT1 - RGA_MODE_BASE_REG) >> 2] = ctx->y4map_lut1; + } + + static void rga_cmd_set(struct rga_ctx *ctx, +diff --git a/drivers/media/platform/rockchip/rga/rga-hw.h b/drivers/media/platform/rockchip/rga/rga-hw.h +index cc6bd7f5b..e451f5c7b 100644 +--- a/drivers/media/platform/rockchip/rga/rga-hw.h ++++ b/drivers/media/platform/rockchip/rga/rga-hw.h +@@ -59,6 +59,8 @@ + #define RGA_PAT_CON 0x015c + #define RGA_ROP_CON0 0x0160 + #define RGA_ROP_CON1 0x0164 ++#define RGA_RST_Y4MAP_LUT0 0x0160 ++#define RGA_RST_Y4MAP_LUT1 0x0164 + #define RGA_MASK_BASE 0x0168 + + #define RGA_MMU_CTRL1 0x016C +@@ -321,8 +323,15 @@ union rga_dst_info { + /* [16:18] */ + unsigned int csc_mode:2; + unsigned int csc_clip:1; ++ unsigned int sw_dst_csc_mode_2:1; ++ unsigned int sw_src1_csc_mode:2; ++ unsigned int sw_src1_csc_clip:1; ++ unsigned int reserved_1:1; ++ unsigned int sw_dst_fmt_yuv400_en:1; ++ unsigned int sw_dst_fmt_y4_en:1; ++ unsigned int sw_dst_nn_quantize_en:1; + /* [19:31] */ +- unsigned int reserved:13; ++ unsigned int reserved:6; + } data; + }; + +diff --git a/drivers/media/platform/rockchip/rga/rga.c b/drivers/media/platform/rockchip/rga/rga.c +index 43f6a8d99..66b6f30ba 100644 +--- a/drivers/media/platform/rockchip/rga/rga.c ++++ b/drivers/media/platform/rockchip/rga/rga.c +@@ -135,17 +135,63 @@ static int rga_s_ctrl(struct v4l2_ctrl *ctrl) + spin_lock_irqsave(&ctx->rga->ctrl_lock, flags); + switch (ctrl->id) { + case V4L2_CID_HFLIP: ++ printk(KERN_INFO "V4L2_CID_HFLIP: %i -> %i\n", ctx->hflip, ctrl->val); ++ if (ctx->dither_down_enable){ ++ // dither does not work with h/v-flip ++ spin_unlock_irqrestore(&ctx->rga->ctrl_lock, flags); ++ return -EINVAL; ++ } + ctx->hflip = ctrl->val; + break; + case V4L2_CID_VFLIP: ++ printk(KERN_INFO "V4L2_CID_VFLIP: %i -> %i\n", ctx->vflip, ctrl->val); ++ if (ctx->dither_down_enable){ ++ // dither does not work with h/v-flip ++ spin_unlock_irqrestore(&ctx->rga->ctrl_lock, flags); ++ return -EINVAL; ++ } + ctx->vflip = ctrl->val; + break; + case V4L2_CID_ROTATE: ++ if (ctx->dither_down_enable){ ++ // dither does not work with rotation ++ spin_unlock_irqrestore(&ctx->rga->ctrl_lock, flags); ++ return -EINVAL; ++ } + ctx->rotate = ctrl->val; + break; + case V4L2_CID_BG_COLOR: + ctx->fill_color = ctrl->val; + break; ++ case RGA_V4L2_CID_Y400_ENABLE: ++ printk(KERN_INFO "RGA_V4L2_CID_Y400_ENABLE: %i -> %i\n", ctx->yuv400_enable, ctrl->val); ++ ctx->yuv400_enable = ctrl->val; ++ break; ++ case RGA_V4L2_CID_DITHER_DOWN_ENABLE: ++ printk(KERN_INFO "RGA_V4L2_CID_DITHER_DOWN_ENABLE: %i -> %i\n", ctx->dither_down_enable, ctrl->val); ++ if (ctx->vflip || ctx->hflip || ctx->rotate != 0){ ++ // dither does not work with rotation or flips ++ spin_unlock_irqrestore(&ctx->rga->ctrl_lock, flags); ++ return -EINVAL; ++ } ++ ctx->dither_down_enable = ctrl->val; ++ break; ++ case RGA_V4L2_CID_DITHER_DOWN_MODE: ++ printk(KERN_INFO "RGA_V4L2_CID_DITHER_DOWN_MODE: %i->%i\n", ctx->dither_down_mode, ctrl->val); ++ ctx->dither_down_mode = ctrl->val; ++ break; ++ case RGA_V4L2_CID_Y4_ENABLE: ++ printk(KERN_INFO "RGA_V4L2_CID_Y4_ENABLE: %u\n", ctrl->val); ++ ctx->y4_enable = ctrl->val; ++ break; ++ case RGA_V4L2_CID_Y4MAP_LUT0: ++ printk(KERN_INFO "RGA_V4L2_CID_Y4MAP_LUT0: %u\n", ctrl->val); ++ ctx->y4map_lut0 = ctrl->val; ++ break; ++ case RGA_V4L2_CID_Y4MAP_LUT1: ++ printk(KERN_INFO "RGA_V4L2_CID_Y4MAP_LUT1: %u\n", ctrl->val); ++ ctx->y4map_lut1 = ctrl->val; ++ break; + } + spin_unlock_irqrestore(&ctx->rga->ctrl_lock, flags); + return 0; +@@ -155,11 +201,78 @@ static const struct v4l2_ctrl_ops rga_ctrl_ops = { + .s_ctrl = rga_s_ctrl, + }; + ++ ++static const struct v4l2_ctrl_config ctrl_y400_enable = { ++ .ops = &rga_ctrl_ops, ++ .id = RGA_V4L2_CID_Y400_ENABLE, ++ .name = "Enable Y400 conversion", ++ .type = V4L2_CTRL_TYPE_INTEGER, ++ .min = 0, ++ .max = 1, ++ .step = 1, ++ .def = 0, ++}; ++ ++static const struct v4l2_ctrl_config ctrl_dithering_down_enable = { ++ .ops = &rga_ctrl_ops, ++ .id = RGA_V4L2_CID_DITHER_DOWN_ENABLE, ++ .name = "Enable dither down", ++ .type = V4L2_CTRL_TYPE_INTEGER, ++ .min = 0, ++ .max = 1, ++ .step = 1, ++ .def = 0, ++}; ++ ++static const struct v4l2_ctrl_config ctrl_dithering_down_mode = { ++ .ops = &rga_ctrl_ops, ++ .id = RGA_V4L2_CID_DITHER_DOWN_MODE, ++ .name = "Dither down mode", ++ .type = V4L2_CTRL_TYPE_INTEGER, ++ .min = 0, ++ .max = 4, ++ .step = 1, ++ .def = 0, ++}; ++ ++static const struct v4l2_ctrl_config ctrl_y4_enable = { ++ .ops = &rga_ctrl_ops, ++ .id = RGA_V4L2_CID_Y4_ENABLE, ++ .name = "Enable Y4 conversion", ++ .type = V4L2_CTRL_TYPE_INTEGER, ++ .min = 0, ++ .max = 1, ++ .step = 1, ++ .def = 0, ++}; ++ ++static const struct v4l2_ctrl_config ctrl_y4_map_lut0 = { ++ .ops = &rga_ctrl_ops, ++ .id = RGA_V4L2_CID_Y4MAP_LUT0, ++ .name = "Set Y4MAP LUT0", ++ .type = V4L2_CTRL_TYPE_INTEGER, ++ .min = 0, ++ .max = 0xffffffff, ++ .step = 1, ++ .def = 0x76543210, ++}; ++ ++static const struct v4l2_ctrl_config ctrl_y4_map_lut1 = { ++ .ops = &rga_ctrl_ops, ++ .id = RGA_V4L2_CID_Y4MAP_LUT1, ++ .name = "Set Y4MAP LUT1", ++ .type = V4L2_CTRL_TYPE_INTEGER, ++ .min = 0, ++ .max = 0xffffffff, ++ .step = 1, ++ .def = 0xfedcba98, ++}; ++ + static int rga_setup_ctrls(struct rga_ctx *ctx) + { + struct rockchip_rga *rga = ctx->rga; + +- v4l2_ctrl_handler_init(&ctx->ctrl_handler, 4); ++ v4l2_ctrl_handler_init(&ctx->ctrl_handler, 10); + + v4l2_ctrl_new_std(&ctx->ctrl_handler, &rga_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); +@@ -173,6 +286,13 @@ static int rga_setup_ctrls(struct rga_ctx *ctx) + v4l2_ctrl_new_std(&ctx->ctrl_handler, &rga_ctrl_ops, + V4L2_CID_BG_COLOR, 0, 0xffffffff, 1, 0); + ++ v4l2_ctrl_new_custom(&ctx->ctrl_handler, &ctrl_y400_enable, NULL); ++ v4l2_ctrl_new_custom(&ctx->ctrl_handler, &ctrl_dithering_down_enable, NULL); ++ v4l2_ctrl_new_custom(&ctx->ctrl_handler, &ctrl_dithering_down_mode, NULL); ++ v4l2_ctrl_new_custom(&ctx->ctrl_handler, &ctrl_y4_enable, NULL); ++ v4l2_ctrl_new_custom(&ctx->ctrl_handler, &ctrl_y4_map_lut0, NULL); ++ v4l2_ctrl_new_custom(&ctx->ctrl_handler, &ctrl_y4_map_lut1, NULL); ++ + if (ctx->ctrl_handler.error) { + int err = ctx->ctrl_handler.error; + +@@ -311,6 +431,17 @@ static struct rga_fmt formats[] = { + .y_div = 2, + .x_div = 2, + }, ++ { ++ .fourcc = V4L2_PIX_FMT_Y4, ++ .color_swap = RGA_COLOR_NONE_SWAP, ++ /* hard will switch from yuv420P to yuv400 when y4 conversion is ++ * activated using the relevant registers*/ ++ .hw_format = RGA_COLOR_FMT_YUV420P, ++ .depth = 4, ++ .uv_factor = 1, ++ .y_div = 1, ++ .x_div = 1, ++ }, + { + .fourcc = V4L2_PIX_FMT_YUV422P, + .color_swap = RGA_COLOR_NONE_SWAP, +@@ -959,6 +1090,9 @@ static const struct of_device_id rockchip_rga_match[] = { + { + .compatible = "rockchip,rk3399-rga", + }, ++ { ++ .compatible = "rockchip,rk356x-rga", ++ }, + {}, + }; + +diff --git a/drivers/media/platform/rockchip/rga/rga.h b/drivers/media/platform/rockchip/rga/rga.h +index 72a28b120..dccec0405 100644 +--- a/drivers/media/platform/rockchip/rga/rga.h ++++ b/drivers/media/platform/rockchip/rga/rga.h +@@ -12,6 +12,14 @@ + #include + + #define RGA_NAME "rockchip-rga" ++// define custom v4l2 control ids so we can control the Y4/dithering options ++// via ioctl ++#define RGA_V4L2_CID_Y400_ENABLE (V4L2_CID_USER_RGA_BASE + 0) ++#define RGA_V4L2_CID_DITHER_DOWN_ENABLE (V4L2_CID_USER_RGA_BASE + 1) ++#define RGA_V4L2_CID_DITHER_DOWN_MODE (V4L2_CID_USER_RGA_BASE + 2) ++#define RGA_V4L2_CID_Y4_ENABLE (V4L2_CID_USER_RGA_BASE + 3) ++#define RGA_V4L2_CID_Y4MAP_LUT0 (V4L2_CID_USER_RGA_BASE + 4) ++#define RGA_V4L2_CID_Y4MAP_LUT1 (V4L2_CID_USER_RGA_BASE + 5) + + struct rga_fmt { + u32 fourcc; +@@ -66,6 +74,13 @@ struct rga_ctx { + u32 vflip; + u32 rotate; + u32 fill_color; ++ ++ u32 y4map_lut0; ++ u32 y4map_lut1; ++ u32 yuv400_enable; ++ u32 y4_enable; ++ u32 dither_down_enable; ++ u32 dither_down_mode; + }; + + static inline struct rga_ctx *file_to_rga_ctx(struct file *filp) +diff --git a/drivers/media/v4l2-core/v4l2-common.c b/drivers/media/v4l2-core/v4l2-common.c +index 554c591e1..f0279e86c 100644 +--- a/drivers/media/v4l2-core/v4l2-common.c ++++ b/drivers/media/v4l2-core/v4l2-common.c +@@ -305,6 +305,7 @@ const struct v4l2_format_info *v4l2_format_info(u32 format) + { .format = V4L2_PIX_FMT_YVU420, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 3, .bpp = { 1, 1, 1, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 2 }, + { .format = V4L2_PIX_FMT_YUV422P, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 3, .bpp = { 1, 1, 1, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 1 }, + { .format = V4L2_PIX_FMT_GREY, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 1, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, ++ { .format = V4L2_PIX_FMT_Y4, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 1, 0, 0, 0 }, .bpp_div = { 2, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, + + /* Tiled YUV formats */ + { .format = V4L2_PIX_FMT_NV12_4L4, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 2, .bpp = { 1, 2, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 2 }, +diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig +index d2335276c..f245177a0 100644 +--- a/drivers/regulator/Kconfig ++++ b/drivers/regulator/Kconfig +@@ -1690,6 +1690,15 @@ config REGULATOR_TPS65132 + This driver supports TPS65132 single inductor - dual output + power supply specifically designed for display panels. + ++config REGULATOR_TPS65185 ++ tristate "TI TPS65185 PMIC for E-Paper Display" ++ depends on GPIOLIB && I2C && IIO ++ select IIO_TRIGGERED_EVENT ++ select REGMAP_I2C ++ help ++ This driver supports the voltage regulators on the TPS65185 PMIC ++ specifically designed for e-paper display panels. ++ + config REGULATOR_TPS65217 + tristate "TI TPS65217 Power regulators" + depends on MFD_TPS65217 +diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile +index 1beba1493..018c559e1 100644 +--- a/drivers/regulator/Makefile ++++ b/drivers/regulator/Makefile +@@ -192,6 +192,7 @@ obj-$(CONFIG_REGULATOR_TPS65023) += tps65023-regulator.o + obj-$(CONFIG_REGULATOR_TPS6507X) += tps6507x-regulator.o + obj-$(CONFIG_REGULATOR_TPS65086) += tps65086-regulator.o + obj-$(CONFIG_REGULATOR_TPS65090) += tps65090-regulator.o ++obj-$(CONFIG_REGULATOR_TPS65185) += tps65185-regulator.o + obj-$(CONFIG_REGULATOR_TPS65217) += tps65217-regulator.o + obj-$(CONFIG_REGULATOR_TPS65218) += tps65218-regulator.o + obj-$(CONFIG_REGULATOR_TPS65219) += tps65219-regulator.o +diff --git a/drivers/regulator/tps65185-regulator.c b/drivers/regulator/tps65185-regulator.c +new file mode 100644 +index 000000000..a9de615e0 +--- /dev/null ++++ b/drivers/regulator/tps65185-regulator.c +@@ -0,0 +1,782 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// ++// Copyright (C) 2021 Samuel Holland ++// ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define TPS65185_REG_TMST_VALUE 0x00 ++#define TPS65185_REG_ENABLE 0x01 ++#define TPS65185_REG_VADJ 0x02 ++#define TPS65185_REG_VCOM1 0x03 ++#define TPS65185_REG_VCOM2 0x04 ++#define TPS65185_REG_INT_EN1 0x05 ++#define TPS65185_REG_INT_EN2 0x06 ++#define TPS65185_REG_INT1 0x07 ++#define TPS65185_REG_INT2 0x08 ++#define TPS65185_REG_UPSEQ0 0x09 ++#define TPS65185_REG_UPSEQ1 0x0a ++#define TPS65185_REG_DWNSEQ0 0x0b ++#define TPS65185_REG_DWNSEQ1 0x0c ++#define TPS65185_REG_TMST1 0x0d ++#define TPS65185_REG_TMST2 0x0e ++#define TPS65185_REG_PG 0x0f ++#define TPS65185_REG_REVID 0x10 ++ ++#define TPS65185_ENABLE_ACTIVE BIT(7) ++#define TPS65185_ENABLE_STANDBY BIT(6) ++#define TPS65185_ENABLE_V3P3_EN BIT(5) ++#define TPS65185_ENABLE_VCOM_EN BIT(4) ++#define TPS65185_ENABLE_VDDH_EN BIT(3) ++#define TPS65185_ENABLE_VPOS_EN BIT(2) ++#define TPS65185_ENABLE_VEE_EN BIT(1) ++#define TPS65185_ENABLE_VNEG_EN BIT(0) ++ ++#define TPS65185_VADJ_VSET (0x7 << 0) ++ ++#define TPS65185_VCOM2_VCOM_MSB BIT(0) ++ ++#define TPS65185_INT1_DTX BIT(7) ++#define TPS65185_INT1_TSD BIT(6) ++#define TPS65185_INT1_HOT BIT(5) ++#define TPS65185_INT1_TMST_HOT BIT(4) ++#define TPS65185_INT1_TMST_COLD BIT(3) ++#define TPS65185_INT1_UVLO BIT(2) ++#define TPS65185_INT1_ACQC BIT(1) ++#define TPS65185_INT1_PRGC BIT(0) ++ ++#define TPS65185_INT2_VB_UV BIT(7) ++#define TPS65185_INT2_VDDH_UV BIT(6) ++#define TPS65185_INT2_VN_UV BIT(5) ++#define TPS65185_INT2_VPOS_UV BIT(4) ++#define TPS65185_INT2_VEE_UV BIT(3) ++#define TPS65185_INT2_VCOMF BIT(2) ++#define TPS65185_INT2_VNEG_UV BIT(1) ++#define TPS65185_INT2_EOC BIT(0) ++ ++#define TPS65185_TMST1_READ_THERM BIT(7) ++#define TPS65185_TMST1_DT (0x3 << 0) ++ ++#define TPS65185_TMST2_TMST_COLD (0xf << 4) ++#define TPS65185_TMST2_TMST_COLD_SHIFT 4 ++#define TPS65185_TMST2_TMST_HOT (0xf << 0) ++#define TPS65185_TMST2_TMST_HOT_SHIFT 0 ++ ++#define TPS65185_PG_VB_PG BIT(7) ++#define TPS65185_PG_VDDH_PG BIT(6) ++#define TPS65185_PG_VN_PG BIT(5) ++#define TPS65185_PG_VPOS_PG BIT(4) ++#define TPS65185_PG_VEE_PG BIT(3) ++#define TPS65185_PG_VNEG_PG BIT(1) ++ ++#define TPS65185_TMST_DT_MIN_CELSIUS 2 ++#define TPS65185_TMST_DT_MAX_CELSIUS 5 ++ ++#define TPS65185_TMST_COLD_MIN_CELSIUS -7 ++#define TPS65185_TMST_HOT_MIN_CELSIUS 42 ++ ++/* ++ * After waking up from sleep, TPS65185 waits for VN to discharge and all ++ * voltage references to start up before loading settings from EEPROM. ++ * Accessing registers too early after WAKEUP could override the values ++ * stored in the EEPROM with the default values. ++ */ ++#define TPS65185_WAKEUP_DELAY_MS 50 ++ ++enum { ++ TPS65185_REGULATOR_V3P3, ++ TPS65185_REGULATOR_VCOM, ++ TPS65185_REGULATOR_VDRIVE, ++ TPS65185_NUM_REGULATORS ++}; ++ ++struct tps65185 { ++ struct gpio_desc *powerup_gpio; ++ struct gpio_desc *pwr_good_gpio; ++ struct gpio_desc *vcom_ctrl_gpio; ++ struct gpio_desc *wakeup_gpio; ++ struct regmap *regmap; ++ struct completion temp_completion; ++ u32 total_up_delay_us; ++ u32 total_down_delay_us; ++ u8 int_en1; ++ u8 int_en2; ++}; ++ ++static const struct regulator_ops tps65185_v3p3_ops = { ++ .enable = regulator_enable_regmap, ++ .disable = regulator_disable_regmap, ++ .is_enabled = regulator_is_enabled_regmap, ++}; ++ ++static const struct regulator_desc tps65185_v3p3_desc = { ++ .name = "v3p3", ++ .supply_name = "vin3p3", ++ .of_match = "v3p3", ++ .regulators_node = "regulators", ++ .ops = &tps65185_v3p3_ops, ++ .owner = THIS_MODULE, ++ .enable_reg = TPS65185_REG_ENABLE, ++ .enable_mask = TPS65185_ENABLE_V3P3_EN, ++}; ++ ++static int tps65185_vcom_set_voltage_sel(struct regulator_dev *rdev, ++ unsigned int sel) ++{ ++ int ret; ++ ++ ret = regmap_write(rdev->regmap, TPS65185_REG_VCOM1, sel); ++ if (ret) ++ return ret; ++ ++ return regmap_update_bits(rdev->regmap, TPS65185_REG_VCOM2, ++ TPS65185_VCOM2_VCOM_MSB, sel >> 8); ++} ++ ++static int tps65185_vcom_get_voltage_sel(struct regulator_dev *rdev) ++{ ++ int ret, sel, val; ++ ++ ret = regmap_read(rdev->regmap, TPS65185_REG_VCOM1, &sel); ++ if (ret) ++ return ret; ++ ++ ret = regmap_read(rdev->regmap, TPS65185_REG_VCOM2, &val); ++ if (ret) ++ return ret; ++ ++ sel |= (val & TPS65185_VCOM2_VCOM_MSB) << 8; ++ ++ return sel; ++} ++ ++static const struct regulator_ops tps65185_vcom_ops = { ++ .enable = regulator_enable_regmap, ++ .disable = regulator_disable_regmap, ++ .is_enabled = regulator_is_enabled_regmap, ++ .set_voltage_sel = tps65185_vcom_set_voltage_sel, ++ .map_voltage = regulator_map_voltage_linear, ++ .get_voltage_sel = tps65185_vcom_get_voltage_sel, ++ .list_voltage = regulator_list_voltage_linear, ++}; ++ ++static const struct regulator_desc tps65185_vcom_desc = { ++ .name = "vcom", ++ .supply_name = "vin", ++ .of_match = "vcom", ++ .regulators_node = "regulators", ++ .ops = &tps65185_vcom_ops, ++ .owner = THIS_MODULE, ++ .n_voltages = 0x200, ++ .min_uV = 0, ++ .uV_step = 10000, ++ .enable_reg = TPS65185_REG_ENABLE, ++ .enable_mask = TPS65185_ENABLE_VCOM_EN, ++}; ++ ++static int tps65185_vdrive_enable_time(struct regulator_dev *rdev) ++{ ++ struct tps65185 *tps = rdev_get_drvdata(rdev); ++ ++ return tps->total_down_delay_us + tps->total_up_delay_us + 50000 + 1000000; ++} ++ ++static int tps65185_vdrive_get_status(struct regulator_dev *rdev) ++{ ++ struct tps65185 *tps = rdev_get_drvdata(rdev); ++ int mask, ret, val; ++ ++ /* Are all rails reporting power good? */ ++ if (tps->pwr_good_gpio) { ++ ret = gpiod_get_value_cansleep(tps->pwr_good_gpio); ++ if (ret == 1) ++ return REGULATOR_STATUS_ON; ++ } else { ++ ret = regmap_read(rdev->regmap, TPS65185_REG_PG, &val); ++ if (ret) ++ return ret; ++ ++ mask = TPS65185_PG_VDDH_PG | TPS65185_PG_VPOS_PG | ++ TPS65185_PG_VEE_PG | TPS65185_PG_VNEG_PG; ++ if ((val & mask) == mask) ++ return REGULATOR_STATUS_ON; ++ } ++ ++ ret = regmap_read(rdev->regmap, TPS65185_REG_ENABLE, &val); ++ if (ret) ++ return ret; ++ ++ /* Are all rails disabled? */ ++ mask = TPS65185_ENABLE_VDDH_EN | TPS65185_ENABLE_VPOS_EN | ++ TPS65185_ENABLE_VEE_EN | TPS65185_ENABLE_VNEG_EN; ++ if (!(val & mask)) ++ return REGULATOR_STATUS_OFF; ++ ++ /* Any other combination is an error. */ ++ return REGULATOR_STATUS_ERROR; ++} ++ ++static const struct regulator_ops tps65185_vdrive_ops = { ++ .enable = regulator_enable_regmap, ++ .disable = regulator_disable_regmap, ++ .set_voltage_sel = regulator_set_voltage_sel_regmap, ++ .map_voltage = regulator_map_voltage_iterate, ++ .get_voltage_sel = regulator_get_voltage_sel_regmap, ++ .list_voltage = regulator_list_voltage_table, ++ .enable_time = tps65185_vdrive_enable_time, ++ .get_status = tps65185_vdrive_get_status, ++}; ++ ++static const struct regulator_desc tps65185_vdrive_desc = { ++ .name = "vdrive", ++ .supply_name = "vin", ++ .of_match = "vdrive", ++ .regulators_node = "regulators", ++ .ops = &tps65185_vdrive_ops, ++ .owner = THIS_MODULE, ++ .n_voltages = 7, ++ .linear_min_sel = 3, ++ .volt_table = (const unsigned int [7]) { ++ 0, 0, 0, ++ 15000000, 14750000, 14500000, 14250000 ++ }, ++ .vsel_reg = TPS65185_REG_VADJ, ++ .vsel_mask = TPS65185_VADJ_VSET, ++ .enable_reg = TPS65185_REG_ENABLE, ++ .enable_mask = TPS65185_ENABLE_ACTIVE | ++ TPS65185_ENABLE_STANDBY, ++ .enable_val = TPS65185_ENABLE_ACTIVE, ++ .disable_val = TPS65185_ENABLE_STANDBY, ++ .poll_enabled_time = 2500, ++}; ++ ++static const struct iio_event_spec tps65185_iio_temp_events[] = { ++ { ++ .type = IIO_EV_TYPE_THRESH_ADAPTIVE, ++ .dir = IIO_EV_DIR_EITHER, ++ .mask_separate = BIT(IIO_EV_INFO_VALUE) | ++ BIT(IIO_EV_INFO_ENABLE), ++ }, ++}; ++ ++static const struct iio_chan_spec tps65185_iio_channels[] = { ++ { ++ .type = IIO_TEMP, ++ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), ++ .event_spec = tps65185_iio_temp_events, ++ .num_event_specs = ARRAY_SIZE(tps65185_iio_temp_events), ++ }, ++}; ++ ++static int tps65185_iio_read_temp(struct tps65185 *tps) ++{ ++ reinit_completion(&tps->temp_completion); ++ ++ return regmap_update_bits(tps->regmap, TPS65185_REG_TMST1, ++ TPS65185_TMST1_READ_THERM, ++ TPS65185_TMST1_READ_THERM); ++} ++ ++static int tps65185_iio_read_raw(struct iio_dev *indio_dev, ++ const struct iio_chan_spec *chan, ++ int *val, int *val2, long mask) ++{ ++ struct tps65185 *tps = iio_device_get_drvdata(indio_dev); ++ unsigned int temp; ++ int ret; ++ ++ if (mask != IIO_CHAN_INFO_PROCESSED) ++ return -EINVAL; ++ ++ ret = tps65185_iio_read_temp(tps); ++ if (ret) ++ return ret; ++ ++ if (!wait_for_completion_timeout(&tps->temp_completion, ++ msecs_to_jiffies(20))) ++ return -ETIMEDOUT; ++ ++ ret = regmap_read(tps->regmap, TPS65185_REG_TMST_VALUE, &temp); ++ if (ret) ++ return ret; ++ ++ *val = sign_extend32(temp, 7) * 1000; ++ ++ return IIO_VAL_INT; ++} ++ ++static int tps65185_read_event_config(struct iio_dev *indio_dev, ++ const struct iio_chan_spec *chan, ++ enum iio_event_type type, ++ enum iio_event_direction dir) ++{ ++ struct tps65185 *tps = iio_device_get_drvdata(indio_dev); ++ u8 mask = TPS65185_INT1_DTX; ++ ++ return !!(tps->int_en1 & mask); ++} ++ ++static int tps65185_write_event_config(struct iio_dev *indio_dev, ++ const struct iio_chan_spec *chan, ++ enum iio_event_type type, ++ enum iio_event_direction dir, bool state) ++{ ++ struct tps65185 *tps = iio_device_get_drvdata(indio_dev); ++ u8 mask = TPS65185_INT1_DTX; ++ ++ if (state) ++ tps->int_en1 |= mask; ++ else ++ tps->int_en1 &= ~mask; ++ ++ return regmap_write(tps->regmap, TPS65185_REG_INT_EN1, tps->int_en1); ++} ++ ++static int tps65185_read_event_value(struct iio_dev *indio_dev, ++ const struct iio_chan_spec *chan, ++ enum iio_event_type type, ++ enum iio_event_direction dir, ++ enum iio_event_info info, ++ int *val, int *val2) ++{ ++ struct tps65185 *tps = iio_device_get_drvdata(indio_dev); ++ unsigned int dt; ++ int ret; ++ ++ ret = regmap_read(tps->regmap, TPS65185_REG_TMST1, &dt); ++ if (ret) ++ return ret; ++ ++ *val = TPS65185_TMST_DT_MIN_CELSIUS + (dt & TPS65185_TMST1_DT); ++ ++ return IIO_VAL_INT; ++} ++ ++static int tps65185_write_event_value(struct iio_dev *indio_dev, ++ const struct iio_chan_spec *chan, ++ enum iio_event_type type, ++ enum iio_event_direction dir, ++ enum iio_event_info info, ++ int val, int val2) ++{ ++ struct tps65185 *tps = iio_device_get_drvdata(indio_dev); ++ unsigned int dt; ++ ++ if (val < TPS65185_TMST_DT_MIN_CELSIUS || ++ val > TPS65185_TMST_DT_MAX_CELSIUS) ++ return -EINVAL; ++ ++ dt = val - TPS65185_TMST_DT_MIN_CELSIUS; ++ ++ return regmap_update_bits(tps->regmap, TPS65185_REG_TMST1, ++ TPS65185_TMST1_DT, dt); ++} ++ ++static const struct iio_info tps65185_iio_info = { ++ .read_raw = tps65185_iio_read_raw, ++ .read_event_config = tps65185_read_event_config, ++ .write_event_config = tps65185_write_event_config, ++ .read_event_value = tps65185_read_event_value, ++ .write_event_value = tps65185_write_event_value, ++}; ++ ++static irqreturn_t tps65185_iio_trigger_handler(int irq, void *p) ++{ ++ struct iio_poll_func *pf = p; ++ struct iio_dev *indio_dev = pf->indio_dev; ++ struct tps65185 *tps = iio_device_get_drvdata(indio_dev); ++ ++ tps65185_iio_read_temp(tps); ++ iio_trigger_notify_done(indio_dev->trig); ++ ++ return IRQ_HANDLED; ++} ++ ++static void tps65185_iio_triggered_event_cleanup(void *indio_dev) ++{ ++ iio_triggered_event_cleanup(indio_dev); ++} ++ ++static irqreturn_t tps65185_irq_handler(int irq, void *p) ++{ ++ struct iio_dev *indio_dev = p; ++ struct tps65185 *tps = iio_device_get_drvdata(indio_dev); ++ unsigned int status1, status2; ++ int ret; ++ ++ ret = regmap_read(tps->regmap, TPS65185_REG_INT1, &status1); ++ if (!ret && (status1 & TPS65185_INT1_DTX)) ++ iio_push_event(indio_dev, ++ IIO_UNMOD_EVENT_CODE(IIO_TEMP, 0, ++ IIO_EV_TYPE_THRESH_ADAPTIVE, ++ IIO_EV_DIR_EITHER), ++ iio_get_time_ns(indio_dev)); ++ ++ if (!ret && (status1 & TPS65185_INT1_TSD)) ++ printk(KERN_ERR "tps65185 TSD Thermal shutdown interrupt detected"); ++ if (!ret && (status1 & TPS65185_INT1_HOT)) ++ printk(KERN_ERR "tps65185 HOT Thermal shutdown early warning interrupt detected"); ++ if (!ret && (status1 & TPS65185_INT1_TMST_HOT)) ++ printk(KERN_ERR "tps65185 TMST HOT Thermistor hot interrupt detected"); ++ if (!ret && (status1 & TPS65185_INT1_TMST_COLD)) ++ printk(KERN_ERR "tps65185 TMST COLD Thermistor hot interrupt detected"); ++ if (!ret && (status1 & TPS65185_INT1_UVLO)) ++ printk(KERN_ERR "tps65185 UVLO VIN under voltage detect interrupt detected"); ++ // ACQC INT ++ // PRGC Int ++ ++ ret = regmap_read(tps->regmap, TPS65185_REG_INT2, &status2); ++ ++ if (!ret && (status2 & TPS65185_INT2_VB_UV)) ++ printk(KERN_ERR "tps65185 Positive Boost Converter (VB) undervoltage detected"); ++ if (!ret && (status2 & TPS65185_INT2_VDDH_UV)) ++ printk(KERN_ERR "tps65185 VDDH undervoltage detected"); ++ if (!ret && (status2 & TPS65185_INT2_VN_UV)) ++ printk(KERN_ERR "tps65185 Inverting Boost Converter (VB) undervoltage detected"); ++ if (!ret && (status2 & TPS65185_INT2_VPOS_UV)) ++ printk(KERN_ERR "tps65185 VPOS undervoltage detected"); ++ if (!ret && (status2 & TPS65185_INT2_VEE_UV)) ++ printk(KERN_ERR "tps65185 VEE undervoltage detected"); ++ if (!ret && (status2 & TPS65185_INT2_VCOMF)) ++ printk(KERN_ERR "tps65185 VCOM fault detection"); ++ if (!ret && (status2 & TPS65185_INT2_VNEG_UV)) ++ printk(KERN_ERR "tps65185 VNEG undervoltage detected"); ++ if (!ret && (status2 & TPS65185_INT2_EOC)) ++ complete(&tps->temp_completion); ++ ++ return IRQ_HANDLED; ++} ++ ++static const struct regmap_config tps65185_regmap_config = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ .max_register = TPS65185_REG_REVID, ++}; ++ ++static const struct { ++ u8 revid; ++ const char *name; ++} tps65185_revisions[] = { ++ { 0x45, "TPS65185 r1p0" }, ++ { 0x55, "TPS65185 r1p1" }, ++ { 0x65, "TPS65185 r1p2" }, ++ { 0x66, "TPS651851 r1p0" }, ++}; ++ ++static const u8 tps65185_up_delay_units[] = { 3, 3, 3, 3 }; ++static const u8 tps65185_down_delay_units[] = { 6, 6, 6, 2 }; ++ ++static int tps65185_set_delay(struct device *dev, const char *propname, ++ struct regmap *regmap, unsigned int reg, ++ const u8 *units, u32 *total) ++{ ++ u32 delay[4]; ++ int ret; ++ ++ ret = of_property_read_u32_array(dev->of_node, propname, delay, 4); ++ if (ret) ++ return ret; ++ ++ *total = (delay[0] + delay[1] + delay[2] + delay[3]) * USEC_PER_MSEC; ++ ++ return regmap_write(regmap, reg, ++ ((delay[0] / units[0] - 1) & 0x3) << 0 | ++ ((delay[1] / units[1] - 1) & 0x3) << 2 | ++ ((delay[2] / units[2] - 1) & 0x3) << 4 | ++ ((delay[3] / units[3] - 1) & 0x3) << 6); ++} ++ ++static int tps65185_set_sequence(struct device *dev, const char *propname, ++ struct regmap *regmap, unsigned int reg) ++{ ++ u32 sequence[4]; ++ int ret; ++ ++ ret = of_property_read_u32_array(dev->of_node, propname, sequence, 4); ++ if (ret) ++ return ret; ++ ++ return regmap_write(regmap, reg, ++ (sequence[0] & 0x3) << 0 | ++ (sequence[1] & 0x3) << 2 | ++ (sequence[2] & 0x3) << 4 | ++ (sequence[3] & 0x3) << 6); ++} ++ ++static int tps65185_set_config(struct device *dev, struct tps65185 *tps) ++{ ++ int ret; ++ ++ ret = tps65185_set_sequence(dev, "ti,up-sequence", ++ tps->regmap, TPS65185_REG_UPSEQ0); ++ if (ret) ++ return ret; ++ ++ ret = tps65185_set_delay(dev, "ti,up-delay-ms", ++ tps->regmap, TPS65185_REG_UPSEQ1, ++ tps65185_up_delay_units, ++ &tps->total_up_delay_us); ++ if (ret) ++ return ret; ++ ++ ret = tps65185_set_sequence(dev, "ti,down-sequence", ++ tps->regmap, TPS65185_REG_DWNSEQ0); ++ if (ret) ++ return ret; ++ ++ ret = tps65185_set_delay(dev, "ti,down-delay-ms", ++ tps->regmap, TPS65185_REG_DWNSEQ1, ++ tps65185_down_delay_units, ++ &tps->total_down_delay_us); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static int tps65185_probe(struct i2c_client *client) ++{ ++ struct regulator_config config = {}; ++ struct device *dev = &client->dev; ++ const char *revision = "unknown"; ++ struct iio_dev *indio_dev; ++ struct tps65185 *tps; ++ int i, ret, revid; ++ ++ tps = devm_kzalloc(dev, sizeof(*tps), GFP_KERNEL); ++ if (!tps) ++ return -ENOMEM; ++ ++ i2c_set_clientdata(client, tps); ++ init_completion(&tps->temp_completion); ++ ++ /* ++ * Drive POWERUP and VCOM_CTRL low before driving WAKEUP high to avoid ++ * powering up the output rails. ++ */ ++ tps->powerup_gpio = devm_gpiod_get_optional(dev, "powerup", GPIOD_OUT_LOW); ++ if (IS_ERR(tps->powerup_gpio)) ++ return dev_err_probe(dev, PTR_ERR(tps->powerup_gpio), ++ "Failed to get %s GPIO\n", "powerup"); ++ ++ tps->pwr_good_gpio = devm_gpiod_get_optional(dev, "pwr_good", GPIOD_IN); ++ if (IS_ERR(tps->pwr_good_gpio)) ++ return dev_err_probe(dev, PTR_ERR(tps->pwr_good_gpio), ++ "Failed to get %s GPIO\n", "pwr_good"); ++ ++ tps->vcom_ctrl_gpio = devm_gpiod_get_optional(dev, "vcom_ctrl", GPIOD_OUT_LOW); ++ if (IS_ERR(tps->vcom_ctrl_gpio)) ++ return dev_err_probe(dev, PTR_ERR(tps->vcom_ctrl_gpio), ++ "Failed to get %s GPIO\n", "vcom_ctrl"); ++ ++ tps->wakeup_gpio = devm_gpiod_get(dev, "wakeup", GPIOD_OUT_HIGH); ++ if (IS_ERR(tps->wakeup_gpio)) ++ return dev_err_probe(dev, PTR_ERR(tps->wakeup_gpio), ++ "Failed to get %s GPIO\n", "wakeup"); ++ ++ msleep(TPS65185_WAKEUP_DELAY_MS); ++ ++ tps->regmap = devm_regmap_init_i2c(client, &tps65185_regmap_config); ++ if (IS_ERR(tps->regmap)) ++ return PTR_ERR(tps->regmap); ++ ++ ret = regmap_read(tps->regmap, TPS65185_REG_REVID, &revid); ++ if (ret) ++ return ret; ++ ++ for (i = 0; i < ARRAY_SIZE(tps65185_revisions); ++i) { ++ if (revid == tps65185_revisions[i].revid) { ++ revision = tps65185_revisions[i].name; ++ break; ++ } ++ } ++ dev_info(dev, "detected %s\n", revision); ++ ++ ret = tps65185_set_config(dev, tps); ++ if (ret) ++ return dev_err_probe(dev, ret, "Failed to set config\n"); ++ ++ /* Power down all rails, but enable control by the powerup GPIO. */ ++ ret = regmap_write(tps->regmap, TPS65185_REG_ENABLE, ++ TPS65185_ENABLE_STANDBY | ++ TPS65185_ENABLE_VDDH_EN | ++ TPS65185_ENABLE_VPOS_EN | ++ TPS65185_ENABLE_VEE_EN | ++ TPS65185_ENABLE_VNEG_EN); ++ if (ret) ++ return ret; ++ ++ config.dev = dev; ++ config.driver_data = tps; ++ config.regmap = tps->regmap; ++ ++ for (i = TPS65185_REGULATOR_V3P3; i < TPS65185_NUM_REGULATORS; ++i) { ++ const struct regulator_desc *rdesc; ++ struct regulator_dev *rdev; ++ ++ switch (i) { ++ case TPS65185_REGULATOR_V3P3: ++ rdesc = &tps65185_v3p3_desc; ++ config.ena_gpiod = NULL; ++ break; ++ case TPS65185_REGULATOR_VCOM: ++ rdesc = &tps65185_vcom_desc; ++ config.ena_gpiod = tps->vcom_ctrl_gpio; ++ break; ++ case TPS65185_REGULATOR_VDRIVE: ++ rdesc = &tps65185_vdrive_desc; ++ config.ena_gpiod = tps->powerup_gpio; ++ break; ++ } ++ ++ rdev = devm_regulator_register(dev, rdesc, &config); ++ if (IS_ERR(rdev)) ++ return dev_err_probe(dev, PTR_ERR(rdev), ++ "Failed to register %s\n", ++ rdesc->name); ++ ++ devm_gpiod_unhinge(dev, config.ena_gpiod); ++ } ++ ++ indio_dev = devm_iio_device_alloc(dev, 0); ++ if (!indio_dev) ++ return -ENOMEM; ++ ++ iio_device_set_drvdata(indio_dev, tps); ++ ++ indio_dev->modes = INDIO_DIRECT_MODE; ++ indio_dev->channels = tps65185_iio_channels; ++ indio_dev->num_channels = ARRAY_SIZE(tps65185_iio_channels); ++ indio_dev->name = dev_name(dev); ++ indio_dev->info = &tps65185_iio_info; ++ ++ ret = iio_triggered_event_setup(indio_dev, NULL, ++ tps65185_iio_trigger_handler); ++ if (ret) ++ return dev_err_probe(dev, ret, "Failed to add IIO trigger\n"); ++ ++ ret = devm_add_action_or_reset(dev, tps65185_iio_triggered_event_cleanup, ++ indio_dev); ++ if (ret) ++ return ret; ++ ++ ret = devm_iio_device_register(dev, indio_dev); ++ if (ret) ++ return dev_err_probe(dev, ret, "Failed to register IIO device\n"); ++ ++ // disable all INT_EN1 interrupts ++ tps->int_en1 = 0xff; ++ ret = regmap_write(tps->regmap, TPS65185_REG_INT_EN1, tps->int_en1); ++ if (ret) ++ return ret; ++ ++ //tps->int_en2 = TPS65185_INT2_EOC; ++ tps->int_en2 = 0xff; ++ ret = regmap_write(tps->regmap, TPS65185_REG_INT_EN2, tps->int_en2); ++ if (ret) ++ return ret; ++ ++ ret = devm_request_threaded_irq(dev, client->irq, NULL, ++ tps65185_irq_handler, IRQF_ONESHOT, ++ dev_name(dev), indio_dev); ++ if (ret) ++ return dev_err_probe(dev, ret, "Failed to request IRQ\n"); ++ ++ return 0; ++} ++ ++static int __maybe_unused tps65185_suspend(struct device *dev) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct tps65185 *tps; ++ int ret; ++ ++ tps = i2c_get_clientdata(client); ++ // set STANDBY of ENABLE reg to 1 ++ // pull PWRUP pin low (falling edge) ++ // -> will go into either STANDBY or SLEEP, depending on WAKEUP pin ++ // we want STANDBY, so WAKUP=1 ++ gpiod_set_value(tps->wakeup_gpio, 1); ++ ++ // initiate standby ++ ret = regmap_write(tps->regmap, TPS65185_REG_ENABLE, ++ TPS65185_ENABLE_STANDBY); ++ if (ret) ++ printk(KERN_ERR "tps65185 suspend: could not set standby bit"); ++ ++ return 0; ++} ++ ++static int __maybe_unused tps65185_resume(struct device *dev) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ ++ struct tps65185 *tps; ++ int ret; ++ ++ tps = i2c_get_clientdata(client); ++ ++ // todo: is the location correct here? ++ msleep(TPS65185_WAKEUP_DELAY_MS); ++ ++ ret = tps65185_set_config(dev, tps); ++ if (ret) ++ return dev_err_probe(dev, ret, "Failed to set config at resume\n"); ++ ++ // disable all INT_EN1 interrupts ++ tps->int_en1 = 0xff; ++ ret = regmap_write(tps->regmap, TPS65185_REG_INT_EN1, tps->int_en1); ++ if (ret) ++ return ret; ++ ++ // only enable temperature-is-ready irq ++ tps->int_en2 = 0xff; ++ ret = regmap_write(tps->regmap, TPS65185_REG_INT_EN2, tps->int_en2); ++ if (ret) ++ return ret; ++ ++ // If I understand correctly regulator TPS65185_REGULATOR_VDRIVE is tied to ++ // the powerup pin - therefore when the ebc resumes the tps65185 is ++ // directly activated. I think its correct to do nothing here and wait. ++ // Similar to the probe function which also puts the pmic into standby ++ /* Power down all rails, but enable control by the powerup GPIO. */ ++ ret = regmap_write(tps->regmap, TPS65185_REG_ENABLE, ++ TPS65185_ENABLE_STANDBY | ++ TPS65185_ENABLE_VDDH_EN | ++ TPS65185_ENABLE_VPOS_EN | ++ TPS65185_ENABLE_VEE_EN | ++ TPS65185_ENABLE_VNEG_EN); ++ ++ return 0; ++} ++ ++static SIMPLE_DEV_PM_OPS(tps65185_pm, tps65185_suspend, tps65185_resume); ++ ++static const struct of_device_id tps65185_of_match[] = { ++ { .compatible = "ti,tps65185" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, tps65185_of_match); ++ ++static struct i2c_driver tps65185_driver = { ++ .probe = tps65185_probe, ++ .driver = { ++ .name = "tps65185", ++ .pm = &tps65185_pm, ++ .of_match_table = tps65185_of_match, ++ }, ++}; ++module_i2c_driver(tps65185_driver); ++ ++MODULE_AUTHOR("Samuel Holland "); ++MODULE_DESCRIPTION("TPS65185 PMIC driver"); ++MODULE_LICENSE("GPL v2"); +diff --git a/drivers/soc/rockchip/Kconfig b/drivers/soc/rockchip/Kconfig +index 785f60c6f..17ccb0fb1 100644 +--- a/drivers/soc/rockchip/Kconfig ++++ b/drivers/soc/rockchip/Kconfig +@@ -30,4 +30,10 @@ config ROCKCHIP_DTPM + on this platform. That will create all the power capping capable + devices. + ++config ROCKCHIP_SUSPEND_MODE ++ tristate "Rockchip suspend mode config" ++ depends on ROCKCHIP_SIP ++ help ++ Say Y here if you want to set the suspend mode to the ATF. ++ + endif +diff --git a/drivers/soc/rockchip/Makefile b/drivers/soc/rockchip/Makefile +index 23d414433..d62676283 100644 +--- a/drivers/soc/rockchip/Makefile ++++ b/drivers/soc/rockchip/Makefile +@@ -5,3 +5,4 @@ + obj-$(CONFIG_ROCKCHIP_GRF) += grf.o + obj-$(CONFIG_ROCKCHIP_IODOMAIN) += io-domain.o + obj-$(CONFIG_ROCKCHIP_DTPM) += dtpm.o ++obj-$(CONFIG_ROCKCHIP_SUSPEND_MODE) += rockchip_pm_config.o +diff --git a/drivers/soc/rockchip/rockchip_pm_config.c b/drivers/soc/rockchip/rockchip_pm_config.c +new file mode 100644 +index 000000000..655241816 +--- /dev/null ++++ b/drivers/soc/rockchip/rockchip_pm_config.c +@@ -0,0 +1,291 @@ ++/* ++ * Rockchip Generic power configuration support. ++ * ++ * Copyright (c) 2017 ROCKCHIP, Co. Ltd. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 as ++ * published by the Free Software Foundation. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include <../drivers/regulator/internal.h> ++ ++#define PM_INVALID_GPIO 0xffff ++#define MAX_ON_OFF_REG_NUM 30 ++#define MAX_ON_OFF_REG_PROP_NAME_LEN 60 ++ ++enum rk_pm_state { ++ RK_PM_MEM = 0, ++ RK_PM_MEM_LITE, ++ RK_PM_MEM_ULTRA, ++ RK_PM_STATE_MAX ++}; ++ ++static struct rk_on_off_regulator_list { ++ struct regulator_dev *on_reg_list[MAX_ON_OFF_REG_NUM]; ++ struct regulator_dev *off_reg_list[MAX_ON_OFF_REG_NUM]; ++} on_off_regs_list[RK_PM_STATE_MAX]; ++ ++static const struct of_device_id pm_match_table[] = { ++ { .compatible = "rockchip,pm-px30",}, ++ { .compatible = "rockchip,pm-rk1808",}, ++ { .compatible = "rockchip,pm-rk322x",}, ++ { .compatible = "rockchip,pm-rk3288",}, ++ { .compatible = "rockchip,pm-rk3308",}, ++ { .compatible = "rockchip,pm-rk3328",}, ++ { .compatible = "rockchip,pm-rk3368",}, ++ { .compatible = "rockchip,pm-rk3399",}, ++ { .compatible = "rockchip,pm-rk3568",}, ++ { .compatible = "rockchip,pm-rv1126",}, ++ { }, ++}; ++ ++static void rockchip_pm_virt_pwroff_prepare(void) ++{ ++ int error; ++ ++ regulator_suspend_prepare(PM_SUSPEND_MEM); ++ ++ error = suspend_disable_secondary_cpus(); ++ if (error) { ++ pr_err("Disable nonboot cpus failed!\n"); ++ return; ++ } ++ ++ sip_smc_set_suspend_mode(VIRTUAL_POWEROFF, 0, 1); ++ sip_smc_virtual_poweroff(); ++} ++ ++static int parse_on_off_regulator(struct device_node *node, enum rk_pm_state state) ++{ ++ char on_prop_name[MAX_ON_OFF_REG_PROP_NAME_LEN] = {0}; ++ char off_prop_name[MAX_ON_OFF_REG_PROP_NAME_LEN] = {0}; ++ int i, j; ++ struct device_node *dn; ++ struct regulator_dev *reg; ++ struct regulator_dev **on_list; ++ struct regulator_dev **off_list; ++ ++ switch (state) { ++ case RK_PM_MEM: ++ strncpy(on_prop_name, "rockchip,regulator-on-in-mem", ++ MAX_ON_OFF_REG_PROP_NAME_LEN); ++ strncpy(off_prop_name, "rockchip,regulator-off-in-mem", ++ MAX_ON_OFF_REG_PROP_NAME_LEN); ++ break; ++ ++ case RK_PM_MEM_LITE: ++ strncpy(on_prop_name, "rockchip,regulator-on-in-mem-lite", ++ MAX_ON_OFF_REG_PROP_NAME_LEN); ++ strncpy(off_prop_name, "rockchip,regulator-off-in-mem-lite", ++ MAX_ON_OFF_REG_PROP_NAME_LEN); ++ break; ++ ++ case RK_PM_MEM_ULTRA: ++ strncpy(on_prop_name, "rockchip,regulator-on-in-mem-ultra", ++ MAX_ON_OFF_REG_PROP_NAME_LEN); ++ strncpy(off_prop_name, "rockchip,regulator-off-in-mem-ultra", ++ MAX_ON_OFF_REG_PROP_NAME_LEN); ++ break; ++ ++ default: ++ return 0; ++ } ++ ++ on_list = on_off_regs_list[state].on_reg_list; ++ off_list = on_off_regs_list[state].off_reg_list; ++ ++ // if (of_find_property(node, on_prop_name, NULL)) { ++ // for (i = 0, j = 0; ++ // (dn = of_parse_phandle(node, on_prop_name, i)); ++ // i++) { ++ // reg = of_find_regulator_by_node(dn); ++ // if (reg == NULL) { ++ // pr_warn("failed to find regulator %s for %s\n", ++ // dn->name, on_prop_name); ++ // } else { ++ // pr_debug("%s on regulator=%s\n", __func__, ++ // reg->desc->name); ++ // on_list[j++] = reg; ++ // } ++ // of_node_put(dn); ++ ++ // if (j >= MAX_ON_OFF_REG_NUM) ++ // return 0; ++ // } ++ // } ++ ++ // if (of_find_property(node, off_prop_name, NULL)) { ++ // for (i = 0, j = 0; ++ // (dn = of_parse_phandle(node, off_prop_name, i)); ++ // i++) { ++ // reg = of_find_regulator_by_node(dn); ++ // if (reg == NULL) { ++ // pr_warn("failed to find regulator %s for %s\n", ++ // dn->name, off_prop_name); ++ // } else { ++ // pr_debug("%s off regulator=%s\n", __func__, ++ // reg->desc->name); ++ // off_list[j++] = reg; ++ // } ++ // of_node_put(dn); ++ ++ // if (j >= MAX_ON_OFF_REG_NUM) ++ // return 0; ++ // } ++ // } ++ ++ return 0; ++} ++ ++static int pm_config_probe(struct platform_device *pdev) ++{ ++ const struct of_device_id *match_id; ++ struct device_node *node; ++ u32 mode_config = 0; ++ u32 wakeup_config = 0; ++ u32 pwm_regulator_config = 0; ++ int gpio_temp[10]; ++ u32 sleep_debug_en = 0; ++ u32 apios_suspend = 0; ++ u32 virtual_poweroff_en = 0; ++ /* enum of_gpio_flags flags; */ ++ int i = 0; ++ int length; ++ ++ match_id = of_match_node(pm_match_table, pdev->dev.of_node); ++ if (!match_id) ++ return -ENODEV; ++ ++ node = of_find_node_by_name(NULL, "rockchip-suspend"); ++ ++ if (IS_ERR_OR_NULL(node)) { ++ dev_err(&pdev->dev, "%s dev node err\n", __func__); ++ return -ENODEV; ++ } ++ ++ if (of_property_read_u32_array(node, ++ "rockchip,sleep-mode-config", ++ &mode_config, 1)) ++ dev_warn(&pdev->dev, "not set sleep mode config\n"); ++ else ++ sip_smc_set_suspend_mode(SUSPEND_MODE_CONFIG, mode_config, 0); ++ ++ if (of_property_read_u32_array(node, ++ "rockchip,wakeup-config", ++ &wakeup_config, 1)) ++ dev_warn(&pdev->dev, "not set wakeup-config\n"); ++ else ++ sip_smc_set_suspend_mode(WKUP_SOURCE_CONFIG, wakeup_config, 0); ++ ++ if (of_property_read_u32_array(node, ++ "rockchip,pwm-regulator-config", ++ &pwm_regulator_config, 1)) ++ dev_warn(&pdev->dev, "not set pwm-regulator-config\n"); ++ else ++ sip_smc_set_suspend_mode(PWM_REGULATOR_CONFIG, ++ pwm_regulator_config, ++ 0); ++ ++ /* length = of_gpio_named_count(node, "rockchip,power-ctrl"); */ ++ ++ /* if (length > 0 && length < 10) { */ ++ /* for (i = 0; i < length; i++) { */ ++ /* gpio_temp[i] = of_get_named_gpio_flags(node, */ ++ /* "rockchip,power-ctrl", */ ++ /* i, */ ++ /* &flags); */ ++ /* if (!gpio_is_valid(gpio_temp[i])) */ ++ /* break; */ ++ /* sip_smc_set_suspend_mode(GPIO_POWER_CONFIG, */ ++ /* i, */ ++ /* gpio_temp[i]); */ ++ /* } */ ++ /* } */ ++ sip_smc_set_suspend_mode(GPIO_POWER_CONFIG, i, PM_INVALID_GPIO); ++ ++ if (!of_property_read_u32_array(node, ++ "rockchip,sleep-debug-en", ++ &sleep_debug_en, 1)) ++ sip_smc_set_suspend_mode(SUSPEND_DEBUG_ENABLE, ++ sleep_debug_en, ++ 0); ++ ++ if (!of_property_read_u32_array(node, ++ "rockchip,apios-suspend", ++ &apios_suspend, 1)) ++ sip_smc_set_suspend_mode(APIOS_SUSPEND_CONFIG, ++ apios_suspend, ++ 0); ++ ++ /* if (!of_property_read_u32_array(node, */ ++ /* "rockchip,virtual-poweroff", */ ++ /* &virtual_poweroff_en, 1) && */ ++ /* virtual_poweroff_en) */ ++ /* pm_power_off_prepare = rockchip_pm_virt_pwroff_prepare; */ ++ ++ for (i = RK_PM_MEM; i < RK_PM_STATE_MAX; i++) ++ parse_on_off_regulator(node, i); ++ ++ return 0; ++} ++ ++static int pm_config_prepare(struct device *dev) ++{ ++ int i; ++ suspend_state_t suspend_state = mem_sleep_current; ++ enum rk_pm_state state = suspend_state - PM_SUSPEND_MEM; ++ struct regulator_dev **on_list; ++ struct regulator_dev **off_list; ++ ++ sip_smc_set_suspend_mode(LINUX_PM_STATE, ++ suspend_state, ++ 0); ++ ++ if (state >= RK_PM_STATE_MAX) ++ return 0; ++ ++ on_list = on_off_regs_list[state].on_reg_list; ++ off_list = on_off_regs_list[state].off_reg_list; ++ ++ for (i = 0; i < MAX_ON_OFF_REG_NUM && on_list[i]; i++) ++ regulator_suspend_enable(on_list[i], PM_SUSPEND_MEM); ++ ++ for (i = 0; i < MAX_ON_OFF_REG_NUM && off_list[i]; i++) ++ regulator_suspend_disable(off_list[i], PM_SUSPEND_MEM); ++ ++ return 0; ++} ++ ++static const struct dev_pm_ops rockchip_pm_ops = { ++ .prepare = pm_config_prepare, ++}; ++ ++static struct platform_driver pm_driver = { ++ .probe = pm_config_probe, ++ .driver = { ++ .name = "rockchip-pm", ++ .of_match_table = pm_match_table, ++ .pm = &rockchip_pm_ops, ++ }, ++}; ++ ++static int __init rockchip_pm_drv_register(void) ++{ ++ return platform_driver_register(&pm_driver); ++} ++late_initcall_sync(rockchip_pm_drv_register); ++MODULE_DESCRIPTION("Rockchip suspend mode config"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/spi/spi-gpio.c b/drivers/spi/spi-gpio.c +index c8dadb532..658cad85c 100644 +--- a/drivers/spi/spi-gpio.c ++++ b/drivers/spi/spi-gpio.c +@@ -6,6 +6,7 @@ + * Copyright (C) 2017 Linus Walleij + */ + #include ++#include + #include + #include + #include +@@ -85,7 +86,7 @@ static inline int getmiso(const struct spi_device *spi) + * reaching even one Mbit/sec (except when we can inline bitops), so for now + * we'll just assume we never need additional per-bit slowdowns. + */ +-#define spidelay(nsecs) do {} while (0) ++#define spidelay(nsecs) ndelay(nsecs) + + #include "spi-bitbang-txrx.h" + +diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig +index 76521555e..6a765c899 100644 +--- a/drivers/usb/gadget/Kconfig ++++ b/drivers/usb/gadget/Kconfig +@@ -223,6 +223,9 @@ config USB_F_PRINTER + config USB_F_TCM + tristate + ++config USB_F_GUD ++ tristate ++ + # this first set of drivers all depend on bulk-capable hardware. + + config USB_CONFIGFS +@@ -506,6 +509,15 @@ config USB_CONFIGFS_F_TCM + Both protocols can work on USB2.0 and USB3.0. + UAS utilizes the USB 3.0 feature called streams support. + ++config USB_CONFIGFS_F_GUD ++ bool "GUD USB Display Gadget function" ++ depends on USB_CONFIGFS ++ depends on DRM ++ select DRM_GUD_GADGET ++ select USB_F_GUD ++ help ++ This presents a DRM display device as a GUD USB Display. ++ + source "drivers/usb/gadget/legacy/Kconfig" + + endif # USB_GADGET +diff --git a/drivers/usb/gadget/function/Makefile b/drivers/usb/gadget/function/Makefile +index 7ce163727..9e2c8ab5a 100644 +--- a/drivers/usb/gadget/function/Makefile ++++ b/drivers/usb/gadget/function/Makefile +@@ -56,3 +56,5 @@ usb_f_printer-y := f_printer.o + obj-$(CONFIG_USB_F_PRINTER) += usb_f_printer.o + usb_f_tcm-y := f_tcm.o + obj-$(CONFIG_USB_F_TCM) += usb_f_tcm.o ++usb_f_gud-y := f_gud.o ++obj-$(CONFIG_USB_F_GUD) += usb_f_gud.o +diff --git a/drivers/usb/gadget/function/f_gud.c b/drivers/usb/gadget/function/f_gud.c +new file mode 100644 +index 000000000..826d5582d +--- /dev/null ++++ b/drivers/usb/gadget/function/f_gud.c +@@ -0,0 +1,866 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (C) 2020 Noralf Trønnes ++ */ ++ ++//#define DEBUG ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++struct f_gud { ++ struct usb_function func; ++ struct work_struct worker; ++ size_t max_buffer_size; ++ ++ u8 interface_id; ++ struct usb_request *ctrl_req; ++ struct usb_request *status_req; ++ ++ struct usb_ep *bulk_ep; ++ struct usb_request *bulk_req; ++ ++ struct gud_gadget *gdg; ++ ++ spinlock_t lock; /* Protects the following members: */ ++ bool ctrl_pending; ++ bool status_pending; ++ bool bulk_pending; ++ bool disable_pending; ++ bool host_timeout; ++ int errno; ++ u16 request; ++ u16 value; ++}; ++ ++static inline struct f_gud *func_to_f_gud(struct usb_function *f) ++{ ++ return container_of(f, struct f_gud, func); ++} ++ ++struct f_gud_opts { ++ struct usb_function_instance func_inst; ++ struct mutex lock; ++ int refcnt; ++ ++ unsigned int drm_dev; ++ const char *backlight_dev; ++ u8 compression; ++ u32 connectors; ++ u8 formats[GUD_FORMATS_MAX_NUM]; ++}; ++ ++static inline struct f_gud_opts *fi_to_f_gud_opts(const struct usb_function_instance *fi) ++{ ++ return container_of(fi, struct f_gud_opts, func_inst); ++} ++ ++static inline struct f_gud_opts *ci_to_f_gud_opts(struct config_item *item) ++{ ++ return container_of(to_config_group(item), struct f_gud_opts, func_inst.group); ++} ++ ++#define F_MUD_DEFINE_BULK_ENDPOINT_DESCRIPTOR(name, addr, size) \ ++ static struct usb_endpoint_descriptor name = { \ ++ .bLength = USB_DT_ENDPOINT_SIZE, \ ++ .bDescriptorType = USB_DT_ENDPOINT, \ ++ .bEndpointAddress = addr, \ ++ .bmAttributes = USB_ENDPOINT_XFER_BULK, \ ++ .wMaxPacketSize = cpu_to_le16(size), \ ++ } ++ ++static struct usb_interface_descriptor f_gud_intf = { ++ .bLength = USB_DT_INTERFACE_SIZE, ++ .bDescriptorType = USB_DT_INTERFACE, ++ .bNumEndpoints = 1, ++ .bInterfaceClass = USB_CLASS_VENDOR_SPEC, ++}; ++ ++F_MUD_DEFINE_BULK_ENDPOINT_DESCRIPTOR(f_gud_fs_out_desc, USB_DIR_OUT, 0); ++ ++static struct usb_descriptor_header *f_gud_fs_function[] = { ++ (struct usb_descriptor_header *)&f_gud_intf, ++ (struct usb_descriptor_header *)&f_gud_fs_out_desc, ++ NULL, ++}; ++ ++F_MUD_DEFINE_BULK_ENDPOINT_DESCRIPTOR(f_gud_hs_out_desc, USB_DIR_OUT, 512); ++ ++static struct usb_descriptor_header *f_gud_hs_function[] = { ++ (struct usb_descriptor_header *)&f_gud_intf, ++ (struct usb_descriptor_header *)&f_gud_hs_out_desc, ++ NULL, ++}; ++ ++F_MUD_DEFINE_BULK_ENDPOINT_DESCRIPTOR(f_gud_ss_out_desc, USB_DIR_OUT, 1024); ++ ++static struct usb_ss_ep_comp_descriptor f_gud_ss_bulk_comp_desc = { ++ .bLength = USB_DT_SS_EP_COMP_SIZE, ++ .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, ++}; ++ ++static struct usb_descriptor_header *f_gud_ss_function[] = { ++ (struct usb_descriptor_header *)&f_gud_intf, ++ (struct usb_descriptor_header *)&f_gud_ss_out_desc, ++ (struct usb_descriptor_header *)&f_gud_ss_bulk_comp_desc, ++ NULL, ++}; ++ ++static struct usb_string f_gud_string_defs[] = { ++ [0].s = "GUD USB Display", ++ { } /* end of list */ ++}; ++ ++static struct usb_gadget_strings f_gud_string_table = { ++ .language = 0x0409, /* en-us */ ++ .strings = f_gud_string_defs, ++}; ++ ++static struct usb_gadget_strings *f_gud_strings[] = { ++ &f_gud_string_table, ++ NULL, ++}; ++ ++static void f_gud_bulk_complete(struct usb_ep *ep, struct usb_request *req) ++{ ++ struct f_gud *fgd = req->context; ++ unsigned long flags; ++ ++ if (req->status || req->actual != req->length) ++ return; ++ ++ spin_lock_irqsave(&fgd->lock, flags); ++ fgd->bulk_pending = true; ++ spin_unlock_irqrestore(&fgd->lock, flags); ++ ++ queue_work(system_long_wq, &fgd->worker); ++} ++ ++static int f_gud_ctrl_req_set_buffer(struct f_gud *fgd, void *buf, size_t len) ++{ ++ int ret; ++ ++ if (len != sizeof(struct gud_set_buffer_req)) ++ return -EINVAL; ++ ++ ret = gud_gadget_req_set_buffer(fgd->gdg, buf); ++ if (ret < 0) ++ return ret; ++ ++ if (ret > fgd->max_buffer_size) ++ return -EOVERFLOW; ++ ++ fgd->bulk_req->length = ret; ++ ++ return usb_ep_queue(fgd->bulk_ep, fgd->bulk_req, GFP_KERNEL); ++} ++ ++static void f_gud_status_req_complete(struct usb_ep *ep, struct usb_request *req) ++{ ++} ++ ++static int f_gud_status_req_queue(struct f_gud *fgd, int errno) ++{ ++ struct usb_composite_dev *cdev = fgd->func.config->cdev; ++ u8 *buf = fgd->status_req->buf; ++ int ret; ++ ++ //pr_debug("%s: errno=%d\n", __func__, errno); ++ ++ switch (errno) { ++ case 0: ++ *buf = GUD_STATUS_OK; ++ break; ++ case -EBUSY: ++ *buf = GUD_STATUS_BUSY; ++ break; ++ case -EOPNOTSUPP: ++ *buf = GUD_STATUS_REQUEST_NOT_SUPPORTED; ++ break; ++ case -EPROTO: ++ *buf = GUD_STATUS_PROTOCOL_ERROR; ++ break; ++ case -EINVAL: ++ *buf = GUD_STATUS_INVALID_PARAMETER; ++ break; ++ case -EOVERFLOW: ++ *buf = GUD_STATUS_PROTOCOL_ERROR; ++ break; ++ default: ++ *buf = GUD_STATUS_ERROR; ++ break; ++ } ++ ++ ret = usb_ep_queue(cdev->gadget->ep0, fgd->status_req, GFP_ATOMIC); ++ //if (ret) ++ // pr_debug("%s: ret=%d\n", __func__, ret); ++ ++ return ret; ++} ++ ++static void f_gud_worker(struct work_struct *work) ++{ ++ struct f_gud *fgd = container_of(work, struct f_gud, worker); ++ bool ctrl_pending, bulk_pending, disable_pending; ++ struct gud_gadget *gdg = fgd->gdg; ++ unsigned long flags; ++ u16 request, value; ++ int ret; ++ ++ spin_lock_irqsave(&fgd->lock, flags); ++ request = fgd->request; ++ value = fgd->value; ++ ctrl_pending = fgd->ctrl_pending; ++ bulk_pending = fgd->bulk_pending; ++ disable_pending = fgd->disable_pending; ++ spin_unlock_irqrestore(&fgd->lock, flags); ++ ++ pr_debug("%s: bulk_pending=%u ctrl_pending=%u disable_pending=%u\n", ++ __func__, bulk_pending, ctrl_pending, disable_pending); ++ ++ if (disable_pending) { ++ gud_gadget_disable_pipe(gdg); ++ ++ spin_lock_irqsave(&fgd->lock, flags); ++ fgd->disable_pending = false; ++ spin_unlock_irqrestore(&fgd->lock, flags); ++ return; ++ } ++ ++ if (bulk_pending) { ++ struct usb_request *req = fgd->bulk_req; ++ ++ ret = gud_gadget_write_buffer(gdg, req->buf, req->actual); ++ if (ret) ++ pr_err("%s: Failed to write buffer, error=%d\n", __func__, ret); ++ ++ spin_lock_irqsave(&fgd->lock, flags); ++ fgd->bulk_pending = false; ++ spin_unlock_irqrestore(&fgd->lock, flags); ++ } ++ ++ if (ctrl_pending) { ++ unsigned int length = fgd->ctrl_req->length; ++ void *buf = fgd->ctrl_req->buf; ++ ++ if (request == GUD_REQ_SET_BUFFER) ++ ret = f_gud_ctrl_req_set_buffer(fgd, buf, length); ++ else ++ ret = gud_gadget_req_set(gdg, request, value, buf, length); ++ ++ spin_lock_irqsave(&fgd->lock, flags); ++ fgd->ctrl_pending = false; ++ //pr_debug("%s: status_pending=%u\n", __func__, fgd->status_pending); ++ if (fgd->status_pending) { ++ fgd->status_pending = false; ++ f_gud_status_req_queue(fgd, ret); ++ } else { ++ fgd->errno = ret; ++ } ++ spin_unlock_irqrestore(&fgd->lock, flags); ++ } ++} ++ ++static void f_gud_ctrl_req_complete(struct usb_ep *ep, struct usb_request *req) ++{ ++ struct f_gud *fgd = req->context; ++ unsigned long flags; ++ int ret = 0; ++ ++ //pr_debug("%s: status=%d diff=%u\n", __func__, req->status, req->actual != req->length); ++ ++ spin_lock_irqsave(&fgd->lock, flags); ++ ++ if (req->status) ++ ret = req->status; ++ else if (req->actual != req->length) ++ ret = -EREMOTEIO; ++ if (ret) ++ fgd->errno = ret; ++ else ++ fgd->ctrl_pending = true; ++ ++ spin_unlock_irqrestore(&fgd->lock, flags); ++ ++ if (!ret) ++ queue_work(system_long_wq, &fgd->worker); ++} ++ ++static int f_gud_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) ++{ ++ struct usb_composite_dev *cdev = f->config->cdev; ++ struct f_gud *fgd = func_to_f_gud(f); ++ bool in = ctrl->bRequestType & USB_DIR_IN; ++ u16 length = le16_to_cpu(ctrl->wLength); ++ u16 value = le16_to_cpu(ctrl->wValue); ++ unsigned long flags; ++ int ret; ++ ++ spin_lock_irqsave(&fgd->lock, flags); ++ ++ pr_debug("%s: bRequest=0x%x length=%u ctrl_pending=%u status_pending=%u\n", ++ __func__, ctrl->bRequest, length, fgd->ctrl_pending, fgd->status_pending); ++ ++ if (fgd->status_pending) { ++ /* Host timed out on the previous status request, worker is running */ ++ pr_debug("EBUSY: status_pending\n"); ++ ret = -EBUSY; ++ fgd->status_pending = false; ++ fgd->host_timeout = true; ++ } else if (ctrl->bRequest == GUD_REQ_GET_STATUS) { ++ if (!in || length != sizeof(u8)) { ++ ret = -EINVAL; ++ } else if (fgd->ctrl_pending && !fgd->host_timeout) { ++ /* Worker isn't done yet, it will queue up status when done */ ++ ret = 0; ++ fgd->status_pending = true; ++ } else { ++ ret = f_gud_status_req_queue(fgd, fgd->errno); ++ } ++ } else if (fgd->ctrl_pending) { ++ /* Host timed out on the previous request, worker is running */ ++ pr_debug("EBUSY: ctrl_pending\n"); ++ ret = -EBUSY; ++ } else if (in) { ++ if (length > USB_COMP_EP0_BUFSIZ) { ++ ret = -EOVERFLOW; ++ } else { ++ ret = gud_gadget_req_get(fgd->gdg, ctrl->bRequest, value, ++ cdev->req->buf, length); ++ if (ret >= 0) { ++ cdev->req->length = ret; ++ cdev->req->zero = ret < length; ++ ret = usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC); ++ } ++ } ++ } else { ++ if (length > USB_COMP_EP0_BUFSIZ) { ++ ret = -EOVERFLOW; ++ } else { ++ fgd->host_timeout = false; ++ fgd->request = ctrl->bRequest; ++ fgd->value = value; ++ fgd->ctrl_req->length = length; ++ /* ->complete() can run inline and takes the lock. Preserve errno */ ++ fgd->errno = 0; ++ spin_unlock_irqrestore(&fgd->lock, flags); ++ ret = usb_ep_queue(cdev->gadget->ep0, fgd->ctrl_req, GFP_ATOMIC); ++ spin_lock_irqsave(&fgd->lock, flags); ++ if (!ret) ++ ret = fgd->errno; ++ } ++ } ++ ++ fgd->errno = ret < 0 ? ret : 0; ++ ++ spin_unlock_irqrestore(&fgd->lock, flags); ++ ++ pr_debug(" ret=%d\n", ret); ++ ++ return ret; ++} ++ ++static bool f_gud_req_match(struct usb_function *f, const struct usb_ctrlrequest *ctrl, ++ bool config0) ++{ ++ struct f_gud *fgd = func_to_f_gud(f); ++ ++ if (config0) ++ return false; ++ ++ if ((ctrl->bRequestType & USB_TYPE_MASK) != USB_TYPE_VENDOR) ++ return false; ++ ++ if ((ctrl->bRequestType & USB_RECIP_MASK) != USB_RECIP_INTERFACE) ++ return false; ++ ++ return fgd->interface_id == le16_to_cpu(ctrl->wIndex); ++} ++ ++static int f_gud_set_alt(struct usb_function *f, unsigned int intf, unsigned int alt) ++{ ++ struct usb_composite_dev *cdev = f->config->cdev; ++ struct f_gud *fgd = func_to_f_gud(f); ++ unsigned long flags; ++ ++ if (alt || intf != fgd->interface_id) ++ return -EINVAL; ++ ++ if (!fgd->bulk_ep->desc) { ++ pr_debug("%s: init\n", __func__); ++ if (config_ep_by_speed(cdev->gadget, f, fgd->bulk_ep)) { ++ fgd->bulk_ep->desc = NULL; ++ return -EINVAL; ++ } ++ } ++ ++ pr_debug("%s: reset\n", __func__); ++ ++ usb_ep_disable(fgd->bulk_ep); ++ usb_ep_enable(fgd->bulk_ep); ++ ++ spin_lock_irqsave(&fgd->lock, flags); ++ fgd->ctrl_pending = false; ++ fgd->bulk_pending = false; ++ fgd->disable_pending = false; ++ spin_unlock_irqrestore(&fgd->lock, flags); ++ ++ return 0; ++} ++ ++static void f_gud_disable(struct usb_function *f) ++{ ++ struct f_gud *fgd = func_to_f_gud(f); ++ unsigned long flags; ++ ++ pr_debug("%s\n", __func__); ++ ++ usb_ep_disable(fgd->bulk_ep); ++ ++ spin_lock_irqsave(&fgd->lock, flags); ++ fgd->ctrl_pending = false; ++ fgd->bulk_pending = false; ++ fgd->status_pending = false; ++ fgd->disable_pending = true; ++ fgd->errno = -ESHUTDOWN; ++ spin_unlock_irqrestore(&fgd->lock, flags); ++ ++ queue_work(system_long_wq, &fgd->worker); ++} ++ ++static struct usb_request *f_gud_alloc_request(struct usb_ep *ep, size_t length) ++{ ++ struct usb_request *req; ++ ++ req = usb_ep_alloc_request(ep, GFP_KERNEL); ++ if (!req) ++ return NULL; ++ ++ req->length = length; ++ req->buf = kmalloc(length, GFP_KERNEL); ++ if (!req->buf) { ++ usb_ep_free_request(ep, req); ++ return NULL; ++ } ++ ++ return req; ++} ++ ++static void f_gud_free_request(struct usb_ep *ep, struct usb_request **req) ++{ ++ if (!*req) ++ return; ++ ++ kfree((*req)->buf); ++ usb_ep_free_request(ep, *req); ++ *req = NULL; ++} ++ ++static void f_gud_unbind(struct usb_configuration *c, struct usb_function *f) ++{ ++ struct f_gud *fgd = func_to_f_gud(f); ++ struct usb_composite_dev *cdev = fgd->func.config->cdev; ++ ++ flush_work(&fgd->worker); ++ ++ gud_gadget_fini(fgd->gdg); ++ fgd->gdg = NULL; ++ ++ f_gud_free_request(fgd->bulk_ep, &fgd->bulk_req); ++ f_gud_free_request(cdev->gadget->ep0, &fgd->status_req); ++ f_gud_free_request(cdev->gadget->ep0, &fgd->ctrl_req); ++ fgd->bulk_ep = NULL; ++ ++ usb_free_all_descriptors(f); ++} ++ ++static int f_gud_bind(struct usb_configuration *c, struct usb_function *f) ++{ ++ struct f_gud_opts *opts = fi_to_f_gud_opts(f->fi); ++ struct usb_composite_dev *cdev = c->cdev; ++ struct f_gud *fgd = func_to_f_gud(f); ++ struct gud_gadget *gdg; ++ struct usb_string *us; ++ int ret; ++ ++ us = usb_gstrings_attach(cdev, f_gud_strings, ARRAY_SIZE(f_gud_string_defs)); ++ if (IS_ERR(us)) ++ return PTR_ERR(us); ++ ++ f_gud_intf.iInterface = us[0].id; ++ ++ ret = usb_interface_id(c, f); ++ if (ret < 0) ++ return ret; ++ ++ fgd->interface_id = ret; ++ f_gud_intf.bInterfaceNumber = fgd->interface_id; ++ ++ fgd->bulk_ep = usb_ep_autoconfig(cdev->gadget, &f_gud_fs_out_desc); ++ if (!fgd->bulk_ep) ++ return -ENODEV; ++ ++ f_gud_hs_out_desc.bEndpointAddress = f_gud_fs_out_desc.bEndpointAddress; ++ f_gud_ss_out_desc.bEndpointAddress = f_gud_fs_out_desc.bEndpointAddress; ++ ++ ret = usb_assign_descriptors(f, f_gud_fs_function, f_gud_hs_function, ++ f_gud_ss_function, NULL); ++ if (ret) ++ return ret; ++ ++ fgd->ctrl_req = f_gud_alloc_request(cdev->gadget->ep0, USB_COMP_EP0_BUFSIZ); ++ if (!fgd->ctrl_req) { ++ ret = -ENOMEM; ++ goto fail_free_descs; ++ } ++ ++ fgd->ctrl_req->complete = f_gud_ctrl_req_complete; ++ fgd->ctrl_req->context = fgd; ++ ++ fgd->status_req = f_gud_alloc_request(cdev->gadget->ep0, sizeof(u8)); ++ if (!fgd->status_req) { ++ ret = -ENOMEM; ++ goto fail_free_reqs; ++ } ++ ++ fgd->status_req->complete = f_gud_status_req_complete; ++ fgd->status_req->context = fgd; ++ ++ gdg = gud_gadget_init(opts->drm_dev, opts->backlight_dev, &fgd->max_buffer_size, ++ opts->compression, opts->formats, opts->connectors); ++ if (IS_ERR(gdg)) { ++ ret = PTR_ERR(gdg); ++ goto fail_free_reqs; ++ } ++ ++ fgd->bulk_req = f_gud_alloc_request(fgd->bulk_ep, fgd->max_buffer_size); ++ if (!fgd->bulk_req) { ++ ret = -ENOMEM; ++ goto fail_free_reqs; ++ } ++ ++ fgd->bulk_req->complete = f_gud_bulk_complete; ++ fgd->bulk_req->context = fgd; ++ ++ fgd->gdg = gdg; ++ ++ return 0; ++ ++fail_free_reqs: ++ f_gud_free_request(fgd->bulk_ep, &fgd->bulk_req); ++ f_gud_free_request(cdev->gadget->ep0, &fgd->ctrl_req); ++ f_gud_free_request(cdev->gadget->ep0, &fgd->status_req); ++fail_free_descs: ++ usb_free_all_descriptors(f); ++ ++ return ret; ++} ++ ++static void f_gud_free_func(struct usb_function *f) ++{ ++ struct f_gud_opts *opts = container_of(f->fi, struct f_gud_opts, func_inst); ++ struct f_gud *fgd = func_to_f_gud(f); ++ ++ mutex_lock(&opts->lock); ++ opts->refcnt--; ++ mutex_unlock(&opts->lock); ++ ++ kfree(fgd); ++} ++ ++static struct usb_function *f_gud_alloc_func(struct usb_function_instance *fi) ++{ ++ struct f_gud_opts *opts = fi_to_f_gud_opts(fi); ++ struct usb_function *func; ++ struct f_gud *fgd; ++ ++ fgd = kzalloc(sizeof(*fgd), GFP_KERNEL); ++ if (!fgd) ++ return ERR_PTR(-ENOMEM); ++ ++ spin_lock_init(&fgd->lock); ++ INIT_WORK(&fgd->worker, f_gud_worker); ++ ++ mutex_lock(&opts->lock); ++ opts->refcnt++; ++ mutex_unlock(&opts->lock); ++ ++ func = &fgd->func; ++ func->name = "gud"; ++ func->bind = f_gud_bind; ++ func->unbind = f_gud_unbind; ++ func->set_alt = f_gud_set_alt; ++ func->req_match = f_gud_req_match; ++ func->setup = f_gud_setup; ++ func->disable = f_gud_disable; ++ func->free_func = f_gud_free_func; ++ ++ return func; ++} ++ ++static ssize_t f_gud_opts_drm_dev_show(struct config_item *item, char *page) ++{ ++ struct f_gud_opts *opts = ci_to_f_gud_opts(item); ++ int result; ++ ++ mutex_lock(&opts->lock); ++ result = sprintf(page, "%u\n", opts->drm_dev); ++ mutex_unlock(&opts->lock); ++ ++ return result; ++} ++ ++static ssize_t f_gud_opts_drm_dev_store(struct config_item *item, const char *page, size_t len) ++{ ++ struct f_gud_opts *opts = ci_to_f_gud_opts(item); ++ unsigned int num; ++ int ret; ++ ++ mutex_lock(&opts->lock); ++ if (opts->refcnt) { ++ ret = -EBUSY; ++ goto unlock; ++ } ++ ++ ret = kstrtouint(page, 0, &num); ++ if (ret) ++ goto unlock; ++ ++ opts->drm_dev = num; ++ ret = len; ++unlock: ++ mutex_unlock(&opts->lock); ++ ++ return ret; ++} ++ ++CONFIGFS_ATTR(f_gud_opts_, drm_dev); ++ ++static ssize_t f_gud_opts_backlight_dev_show(struct config_item *item, char *page) ++{ ++ struct f_gud_opts *opts = ci_to_f_gud_opts(item); ++ ssize_t ret = 0; ++ ++ mutex_lock(&opts->lock); ++ if (opts->backlight_dev) ++ ret = strscpy(page, opts->backlight_dev, PAGE_SIZE); ++ else ++ page[0] = '\0'; ++ mutex_unlock(&opts->lock); ++ ++ return ret; ++} ++ ++static ssize_t f_gud_opts_backlight_dev_store(struct config_item *item, ++ const char *page, size_t len) ++{ ++ struct f_gud_opts *opts = ci_to_f_gud_opts(item); ++ ssize_t ret; ++ char *name; ++ ++ mutex_lock(&opts->lock); ++ if (opts->refcnt) { ++ ret = -EBUSY; ++ goto unlock; ++ } ++ ++ name = kstrndup(page, len, GFP_KERNEL); ++ if (!name) { ++ ret = -ENOMEM; ++ goto unlock; ++ } ++ ++ kfree(opts->backlight_dev); ++ opts->backlight_dev = name; ++ ret = len; ++unlock: ++ mutex_unlock(&opts->lock); ++ ++ return ret; ++} ++ ++CONFIGFS_ATTR(f_gud_opts_, backlight_dev); ++ ++static ssize_t f_gud_opts_compression_show(struct config_item *item, char *page) ++{ ++ struct f_gud_opts *opts = ci_to_f_gud_opts(item); ++ int ret; ++ ++ mutex_lock(&opts->lock); ++ ret = sprintf(page, "0x%02x\n", opts->compression); ++ mutex_unlock(&opts->lock); ++ ++ return ret; ++} ++ ++static ssize_t f_gud_opts_compression_store(struct config_item *item, ++ const char *page, size_t len) ++{ ++ struct f_gud_opts *opts = ci_to_f_gud_opts(item); ++ int ret; ++ ++ mutex_lock(&opts->lock); ++ if (opts->refcnt) { ++ ret = -EBUSY; ++ goto unlock; ++ } ++ ++ ret = kstrtou8(page, 0, &opts->compression); ++ if (ret) ++ goto unlock; ++ ++ ret = len; ++unlock: ++ mutex_unlock(&opts->lock); ++ ++ return ret; ++} ++ ++CONFIGFS_ATTR(f_gud_opts_, compression); ++ ++static ssize_t f_gud_opts_formats_show(struct config_item *item, ++ char *page) ++{ ++ struct f_gud_opts *opts = ci_to_f_gud_opts(item); ++ int i; ++ ++ mutex_lock(&opts->lock); ++ memcpy(page, opts->formats, GUD_FORMATS_MAX_NUM); ++ for (i = GUD_FORMATS_MAX_NUM; i > 0; i--) { ++ if (opts->formats[i - 1]) ++ break; ++ } ++ mutex_unlock(&opts->lock); ++ ++ return i; ++} ++ ++static ssize_t f_gud_opts_formats_store(struct config_item *item, ++ const char *page, size_t len) ++{ ++ struct f_gud_opts *opts = ci_to_f_gud_opts(item); ++ int ret; ++ ++ mutex_lock(&opts->lock); ++ if (opts->refcnt) { ++ ret = -EBUSY; ++ goto unlock; ++ } ++ ++ ret = len; ++ len = min_t(size_t, GUD_FORMATS_MAX_NUM, len); ++ memcpy(opts->formats, page, len); ++unlock: ++ mutex_unlock(&opts->lock); ++ ++ return ret; ++} ++ ++CONFIGFS_ATTR(f_gud_opts_, formats); ++ ++static ssize_t f_gud_opts_connectors_show(struct config_item *item, char *page) ++{ ++ struct f_gud_opts *opts = ci_to_f_gud_opts(item); ++ int ret; ++ ++ mutex_lock(&opts->lock); ++ ret = sprintf(page, "0x%08x\n", opts->connectors); ++ mutex_unlock(&opts->lock); ++ ++ return ret; ++} ++ ++static ssize_t f_gud_opts_connectors_store(struct config_item *item, ++ const char *page, size_t len) ++{ ++ struct f_gud_opts *opts = ci_to_f_gud_opts(item); ++ int ret; ++ ++ mutex_lock(&opts->lock); ++ if (opts->refcnt) { ++ ret = -EBUSY; ++ goto unlock; ++ } ++ ++ ret = kstrtou32(page, 0, &opts->connectors); ++ if (ret) ++ goto unlock; ++ ++ ret = len; ++unlock: ++ mutex_unlock(&opts->lock); ++ ++ return ret; ++} ++ ++CONFIGFS_ATTR(f_gud_opts_, connectors); ++ ++static struct configfs_attribute *f_gud_attrs[] = { ++ &f_gud_opts_attr_drm_dev, ++ &f_gud_opts_attr_backlight_dev, ++ &f_gud_opts_attr_compression, ++ &f_gud_opts_attr_formats, ++ &f_gud_opts_attr_connectors, ++ NULL, ++}; ++ ++static void f_gud_attr_release(struct config_item *item) ++{ ++ struct f_gud_opts *opts = ci_to_f_gud_opts(item); ++ ++ usb_put_function_instance(&opts->func_inst); ++} ++ ++static struct configfs_item_operations f_gud_item_ops = { ++ .release = f_gud_attr_release, ++}; ++ ++static const struct config_item_type f_gud_func_type = { ++ .ct_item_ops = &f_gud_item_ops, ++ .ct_attrs = f_gud_attrs, ++ .ct_owner = THIS_MODULE, ++}; ++ ++static void f_gud_free_func_inst(struct usb_function_instance *fi) ++{ ++ struct f_gud_opts *opts = fi_to_f_gud_opts(fi); ++ ++ mutex_destroy(&opts->lock); ++ kfree(opts->backlight_dev); ++ kfree(opts); ++} ++ ++static struct usb_function_instance *f_gud_alloc_func_inst(void) ++{ ++ struct f_gud_opts *opts; ++ ++ opts = kzalloc(sizeof(*opts), GFP_KERNEL); ++ if (!opts) ++ return ERR_PTR(-ENOMEM); ++ ++ mutex_init(&opts->lock); ++ opts->func_inst.free_func_inst = f_gud_free_func_inst; ++ opts->compression = ~0; ++ opts->connectors = ~0U; ++ ++ config_group_init_type_name(&opts->func_inst.group, "", &f_gud_func_type); ++ ++ return &opts->func_inst; ++} ++ ++DECLARE_USB_FUNCTION_INIT(gud, f_gud_alloc_func_inst, f_gud_alloc_func); ++ ++MODULE_DESCRIPTION("Generic USB Display Gadget"); ++MODULE_AUTHOR("Noralf Trønnes"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/video/backlight/lm3630a_bl.c b/drivers/video/backlight/lm3630a_bl.c +index 37651c2b9..1eecb0ca5 100644 +--- a/drivers/video/backlight/lm3630a_bl.c ++++ b/drivers/video/backlight/lm3630a_bl.c +@@ -85,18 +85,34 @@ static int lm3630a_chip_init(struct lm3630a_chip *pchip) + { + int rval; + struct lm3630a_platform_data *pdata = pchip->pdata; ++ int value; + + usleep_range(1000, 2000); + /* set Filter Strength Register */ + rval = lm3630a_write(pchip, REG_FILTER_STRENGTH, 0x03); + /* set Cofig. register */ + rval |= lm3630a_update(pchip, REG_CONFIG, 0x07, pdata->pwm_ctrl); ++ /* enable feedback on both strings */ ++ /* rval |= lm3630a_update(pchip, REG_CONFIG, 0x18, 0x18); */ ++ + /* set boost control */ +- rval |= lm3630a_write(pchip, REG_BOOST, 0x38); ++ value = pdata->boost_fmode + ++ (pdata->boost_shift << 1) + ++ (pdata->boost_slow_start << 2) + ++ (pdata->boost_ocp << 3) + ++ (pdata->boost_ovp << 5); ++ /* value = 0x38; // default */ ++ printk(KERN_INFO "lm3630a: REG_BOOST, writing value: %i", value); ++ rval |= lm3630a_write(pchip, REG_BOOST, value); ++ ++ pr_info("lm3630a: current_index_leda: %i", pdata->leda_current_index); ++ pr_info("lm3630a: current_index_ledb: %i", pdata->ledb_current_index); + /* set current A */ +- rval |= lm3630a_update(pchip, REG_I_A, 0x1F, 0x1F); ++ rval |= lm3630a_update(pchip, REG_I_A, 0x1F, pdata->leda_current_index); ++ /* rval |= lm3630a_update(pchip, REG_I_A, 0x1F, 0x1F); */ + /* set current B */ +- rval |= lm3630a_write(pchip, REG_I_B, 0x1F); ++ /* rval |= lm3630a_write(pchip, REG_I_B, 0x1F); */ ++ rval |= lm3630a_write(pchip, REG_I_B, pdata->ledb_current_index); + /* set control */ + rval |= lm3630a_update(pchip, REG_CTRL, 0x14, pdata->leda_ctrl); + rval |= lm3630a_update(pchip, REG_CTRL, 0x0B, pdata->ledb_ctrl); +@@ -408,6 +424,8 @@ static int lm3630a_parse_bank(struct lm3630a_platform_data *pdata, + const char *label; + u32 bank, val; + bool linear; ++ u32 current_ma; ++ u8 current_index; + + ret = fwnode_property_read_u32(node, "reg", &bank); + if (ret) +@@ -472,6 +490,27 @@ static int lm3630a_parse_bank(struct lm3630a_platform_data *pdata, + pdata->leda_max_brt = val; + } + ++ ret = fwnode_property_read_u32(node, "ti,current-microamp", ¤t_ma); ++ pr_info("current-microamp %i", current_ma); ++ // convert to 5-bit value ++ current_index = -1; ++ for (int i=0; i < 31; i++){ ++ pr_info(" checking %i = %i", i, 5000 + i * 750); ++ if (5000 + i * 750 == current_ma){ ++ current_index = i; ++ break; ++ } ++ } ++ if (current_index == -1){ ++ return -EINVAL; ++ } ++ if (!ret) { ++ if (bank) ++ pdata->leda_current_index = current_index; ++ else ++ pdata->ledb_current_index = current_index; ++ } ++ + return 0; + } + +@@ -480,7 +519,77 @@ static int lm3630a_parse_node(struct lm3630a_chip *pchip, + { + int ret = -ENODEV, seen_led_sources = 0; + struct fwnode_handle *node; ++ /* default reset values of the device */ ++ u32 boost_frequency = 500000; ++ u32 boost_ovp = 24000000; ++ u32 boost_ocp = 1200000; + ++ /* boost control */ ++ ret = fwnode_property_read_u32(dev_fwnode(pchip->dev), "ti,boost-frequency-hz", ++ &boost_frequency); ++ switch (boost_frequency) { ++ case 500000: ++ break; ++ case 560000: ++ pdata->boost_shift = true; ++ break; ++ case 1000000: ++ pdata->boost_fmode = true; ++ break; ++ case 1120000: ++ pdata->boost_fmode = true; ++ pdata->boost_shift = true; ++ break; ++ default: ++ dev_err(pchip->dev, "invalid boost frequency\n"); ++ return -EINVAL; ++ break; ++ } ++ ret = fwnode_property_read_u32(dev_fwnode(pchip->dev), "ti,boost-ocp-microamp", ++ &boost_ocp); ++ // todo: move values to header constants ++ switch(boost_ocp) { ++ case 600000: ++ pdata->boost_ocp = 0; ++ break; ++ case 800000: ++ pdata->boost_ocp = 1; ++ break; ++ case 1000000: ++ pdata->boost_ocp = 2; ++ break; ++ case 1200000: ++ pdata->boost_ocp = 3; ++ break; ++ default: ++ dev_err(pchip->dev, "invalid ocp value\n"); ++ return -EINVAL; ++ break; ++ } ++ ++ ret = fwnode_property_read_u32(dev_fwnode(pchip->dev), "ti,boost-ovp-microvolts", ++ &boost_ovp); ++ // todo: move values to header constants ++ switch(boost_ovp) { ++ case 16000000: ++ pdata->boost_ovp = 0; ++ break; ++ case 24000000: ++ pdata->boost_ovp = 1; ++ break; ++ case 32000000: ++ pdata->boost_ovp = 2; ++ break; ++ case 40000000: ++ pdata->boost_ovp = 3; ++ break; ++ default: ++ dev_err(pchip->dev, "invalid ovp value\n"); ++ return -EINVAL; ++ break; ++ } ++ ++ /* led strings */ + device_for_each_child_node(pchip->dev, node) { + ret = lm3630a_parse_bank(pdata, node, &seen_led_sources); + if (ret) { +@@ -530,6 +639,13 @@ static int lm3630a_probe(struct i2c_client *client) + pdata->leda_init_brt = LM3630A_MAX_BRIGHTNESS; + pdata->ledb_init_brt = LM3630A_MAX_BRIGHTNESS; + ++ // todo: use header constants ++ pdata->boost_ovp = 1; ++ pdata->boost_ocp = 3; ++ pdata->boost_slow_start = 0; ++ pdata->boost_shift = 0; ++ pdata->boost_fmode = 0; ++ + rval = lm3630a_parse_node(pchip, pdata); + if (rval) { + dev_err(&client->dev, "fail : parse node\n"); +diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h +index c972a8a33..d9316ec0c 100644 +--- a/include/drm/drm_client.h ++++ b/include/drm/drm_client.h +@@ -17,6 +17,7 @@ struct drm_file; + struct drm_framebuffer; + struct drm_gem_object; + struct drm_minor; ++struct drm_property; + struct module; + + /** +@@ -95,6 +96,26 @@ struct drm_client_funcs { + int (*resume)(struct drm_client_dev *client); + }; + ++/** ++ * struct drm_client_property - DRM client property ++ */ ++struct drm_client_property { ++ /** ++ * @obj: DRM Mode Object to which the property belongs. ++ */ ++ struct drm_mode_object *obj; ++ ++ /** ++ * @prop: DRM Property. ++ */ ++ struct drm_property *prop; ++ ++ /** ++ * @value: Property value. ++ */ ++ u64 value; ++}; ++ + /** + * struct drm_client_dev - DRM client instance + */ +@@ -128,7 +149,7 @@ struct drm_client_dev { + struct drm_file *file; + + /** +- * @modeset_mutex: Protects @modesets. ++ * @modeset_mutex: Protects @modesets and @properties. + */ + struct mutex modeset_mutex; + +@@ -152,6 +173,16 @@ struct drm_client_dev { + */ + bool hotplug_pending; + ++ /** ++ * @properties: DRM properties attached to the configuration. ++ */ ++ struct drm_client_property *properties; ++ ++ /** ++ * @num_properties: Number of attached properties. ++ */ ++ unsigned int num_properties; ++ + /** + * @hotplug_failed: + * +@@ -163,8 +194,10 @@ struct drm_client_dev { + + int drm_client_init(struct drm_device *dev, struct drm_client_dev *client, + const char *name, const struct drm_client_funcs *funcs); ++int drm_client_init_from_id(unsigned int minor_id, struct drm_client_dev *client, ++ const char *name, const struct drm_client_funcs *funcs); + void drm_client_release(struct drm_client_dev *client); +-void drm_client_register(struct drm_client_dev *client); ++bool drm_client_register(struct drm_client_dev *client); + + /** + * struct drm_client_buffer - DRM client buffer +@@ -209,12 +242,18 @@ void drm_client_buffer_vunmap(struct drm_client_buffer *buffer); + int drm_client_modeset_create(struct drm_client_dev *client); + void drm_client_modeset_free(struct drm_client_dev *client); + int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width, unsigned int height); ++int drm_client_modeset_set(struct drm_client_dev *client, struct drm_connector *connector, ++ struct drm_display_mode *mode, struct drm_framebuffer *fb); ++int drm_client_modeset_set_property(struct drm_client_dev *client, struct drm_mode_object *obj, ++ struct drm_property *prop, u64 value); ++int drm_client_modeset_set_rotation(struct drm_client_dev *client, u64 value); + bool drm_client_rotation(struct drm_mode_set *modeset, unsigned int *rotation); + int drm_client_modeset_check(struct drm_client_dev *client); + int drm_client_modeset_commit_locked(struct drm_client_dev *client); + int drm_client_modeset_commit(struct drm_client_dev *client); + int drm_client_modeset_dpms(struct drm_client_dev *client, int mode); + int drm_client_modeset_wait_for_vblank(struct drm_client_dev *client, unsigned int crtc_index); ++int drm_client_modeset_disable(struct drm_client_dev *client); + + /** + * drm_client_for_each_modeset() - Iterate over client modesets +diff --git a/include/drm/drm_epd_helper.h b/include/drm/drm_epd_helper.h +new file mode 100644 +index 000000000..6386fbce2 +--- /dev/null ++++ b/include/drm/drm_epd_helper.h +@@ -0,0 +1,107 @@ ++// SPDX-License-Identifier: GPL-2.0 ++ ++#ifndef __DRM_EPD_HELPER_H__ ++#define __DRM_EPD_HELPER_H__ ++ ++#define DRM_EPD_DEFAULT_TEMPERATURE 25 ++ ++struct drm_device; ++struct firmware; ++struct pvi_wbf_file_header; ++struct pvi_wbf_mode_info; ++ ++/** ++ * enum drm_epd_waveform - Identifiers for waveforms used to drive EPD pixels ++ * ++ * @DRM_EPD_WF_RESET: Used to initialize the panel, ends with white ++ * @DRM_EPD_WF_A2: Fast transitions between black and white only ++ * @DRM_EPD_WF_DU: Transitions 16-level grayscale to monochrome ++ * @DRM_EPD_WF_DU4: Transitions 16-level grayscale to 4-level grayscale ++ * @DRM_EPD_WF_GC16: High-quality but flashy 16-level grayscale ++ * @DRM_EPD_WF_GCC16: Less flashy 16-level grayscale ++ * @DRM_EPD_WF_GL16: Less flashy 16-level grayscale ++ * @DRM_EPD_WF_GLR16: Less flashy 16-level grayscale, plus anti-ghosting ++ * @DRM_EPD_WF_GLD16: Less flashy 16-level grayscale, plus anti-ghosting ++ */ ++enum drm_epd_waveform { ++ DRM_EPD_WF_RESET, ++ DRM_EPD_WF_A2, ++ DRM_EPD_WF_DU, ++ DRM_EPD_WF_DU4, ++ DRM_EPD_WF_GC16, ++ DRM_EPD_WF_GCC16, ++ DRM_EPD_WF_GL16, ++ DRM_EPD_WF_GLR16, ++ DRM_EPD_WF_GLD16, ++ DRM_EPD_WF_MAX ++}; ++ ++/** ++ * enum drm_epd_lut_format - EPD LUT buffer format ++ * ++ * @DRM_EPD_LUT_4BIT: 4-bit grayscale indexes, 1 byte per element ++ * @DRM_EPD_LUT_4BIT_PACKED: 4-bit grayscale indexes, 2 bits per element ++ * @DRM_EPD_LUT_5BIT: 5-bit grayscale indexes, 1 byte per element ++ * @DRM_EPD_LUT_5BIT_PACKED: 5-bit grayscale indexes, 2 bits per element ++ */ ++enum drm_epd_lut_format { ++ DRM_EPD_LUT_4BIT, ++ DRM_EPD_LUT_4BIT_PACKED, ++ DRM_EPD_LUT_5BIT, ++ DRM_EPD_LUT_5BIT_PACKED, ++}; ++ ++/** ++ * struct drm_epd_lut_file - Describes a file containing EPD LUTs ++ * ++ * @dev: The DRM device owning this LUT file ++ * @fw: The firmware object holding the raw file contents ++ * @header: Vendor-specific LUT file header ++ * @mode_info: Vendor-specific information about available waveforms ++ */ ++struct drm_epd_lut_file { ++ struct drm_device *dev; ++ const struct firmware *fw; ++ const struct pvi_wbf_file_header *header; ++ const struct pvi_wbf_mode_info *mode_info; ++}; ++ ++/** ++ * struct drm_epd_lut - Describes a particular LUT buffer ++ * ++ * @buf: The LUT, in the format requested by the driver ++ * @file: The file where this LUT was loaded from ++ * @format: The LUT buffer format needed by the driver ++ * @max_phases: The maximum number of waveform phases supported by the driver ++ * @num_phases: The number of waveform phases in the current LUT ++ * @mode_index: Private identifier for the current waveform ++ * @temp_index: Private identifier for the current temperature ++ */ ++struct drm_epd_lut { ++ u8 *buf; ++ const struct drm_epd_lut_file *file; ++ enum drm_epd_lut_format format; ++ unsigned int max_phases; ++ unsigned int num_phases; ++ int mode_index; ++ int temp_index; ++}; ++ ++int pvi_wbf_get_mode_index(const struct drm_epd_lut_file *file, ++ enum drm_epd_waveform waveform); ++ ++int drmm_epd_lut_file_init(struct drm_device *dev, ++ struct drm_epd_lut_file *file, ++ const char *file_name); ++ ++int drmm_epd_lut_init(struct drm_epd_lut_file *file, ++ struct drm_epd_lut *lut, ++ enum drm_epd_lut_format format, ++ unsigned int max_phases); ++ ++int drm_epd_lut_set_temperature(struct drm_epd_lut *lut, ++ int temperature); ++int drm_epd_lut_set_waveform(struct drm_epd_lut *lut, ++ enum drm_epd_waveform waveform); ++ ++#endif /* __DRM_EPD_HELPER_H__ */ +diff --git a/include/drm/gud.h b/include/drm/gud.h +index c52a8ba4a..0d3d57e53 100644 +--- a/include/drm/gud.h ++++ b/include/drm/gud.h +@@ -332,4 +332,16 @@ struct gud_state_req { + /* Enable/disable display/output (DPMS), value is u8: 0/1 */ + #define GUD_REQ_SET_DISPLAY_ENABLE 0x64 + ++struct gud_gadget; ++ ++struct gud_gadget *gud_gadget_init(unsigned int minor_id, const char *backlight, ++ size_t *max_buffer_size, u8 compression, u8 *formats, ++ u32 connectors); ++void gud_gadget_fini(struct gud_gadget *gdg); ++int gud_gadget_disable_pipe(struct gud_gadget *gdg); ++int gud_gadget_req_get(struct gud_gadget *gdg, u8 request, u16 index, void *data, size_t size); ++int gud_gadget_req_set(struct gud_gadget *gdg, u8 request, u16 index, const void *data, size_t size); ++int gud_gadget_req_set_buffer(struct gud_gadget *gdg, const struct gud_set_buffer_req *req); ++int gud_gadget_write_buffer(struct gud_gadget *gdg, const void *buf, size_t len); ++ + #endif +diff --git a/include/dt-bindings/suspend/rockchip-rk3568.h b/include/dt-bindings/suspend/rockchip-rk3568.h +new file mode 100644 +index 000000000..91e89f64a +--- /dev/null ++++ b/include/dt-bindings/suspend/rockchip-rk3568.h +@@ -0,0 +1,57 @@ ++/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */ ++/* ++ * Header providing constants for Rockchip suspend bindings. ++ * ++ * Copyright (C) 2021, Rockchip Electronics Co., Ltd. ++ * Author: XiaoDong.Huang ++ */ ++ ++#ifndef __DT_BINDINGS_SUSPEND_ROCKCHIP_RK3568_H__ ++#define __DT_BINDINGS_SUSPEND_ROCKCHIP_RK3568_H__ ++/******************************bits ops************************************/ ++ ++#ifndef BIT ++#define BIT(nr) (1 << (nr)) ++#endif ++ ++#define RKPM_SLP_WFI BIT(0) ++#define RKPM_SLP_ARMOFF BIT(1) ++#define RKPM_SLP_CENTER_OFF BIT(2) ++#define RKPM_SLP_ARMOFF_LOGOFF BIT(3) ++#define RKPM_SLP_FROM_UBOOT BIT(4) ++#define RKPM_SLP_PMIC_LP BIT(5) ++#define RKPM_SLP_HW_PLLS_OFF BIT(6) ++#define RKPM_SLP_PMUALIVE_32K BIT(7) ++#define RKPM_SLP_OSC_DIS BIT(8) ++#define RKPM_SLP_32K_EXT BIT(9) ++#define RKPM_SLP_32K_PVTM BIT(10) ++/* the wake up source */ ++#define RKPM_CPU0_WKUP_EN BIT(0) ++#define RKPM_CPU1_WKUP_EN BIT(1) ++#define RKPM_CPU2_WKUP_EN BIT(2) ++#define RKPM_CPU3_WKUP_EN BIT(3) ++#define RKPM_GPIO_WKUP_EN BIT(4) ++#define RKPM_UART0_WKUP_EN BIT(5) ++#define RKPM_SDMMC0_WKUP_EN BIT(6) ++#define RKPM_SDMMC1_WKUP_EN BIT(7) ++#define RKPM_SDMMC2_WKUP_EN BIT(8) ++#define RKPM_USB_WKUP_EN BIT(9) ++#define RKPM_PCIE_WKUP_EN BIT(10) ++#define RKPM_VAD_WKUP_EN BIT(11) ++#define RKPM_TIMER_WKUP_EN BIT(12) ++#define RKPM_PWM0_WKUP_EN BIT(13) ++#define RKPM_TIMEOUT_WKUP_EN BIT(14) ++#define RKPM_SFT_WKUP_EN BIT(15) ++#define RKPM_USB_LINESTATE_WKUP_EN BIT(16) ++ ++#define RKPM_SLP_LDO1_ON BIT(0) ++#define RKPM_SLP_LDO2_ON BIT(1) ++#define RKPM_SLP_LDO3_ON BIT(2) ++#define RKPM_SLP_LDO4_ON BIT(3) ++#define RKPM_SLP_LDO5_ON BIT(4) ++#define RKPM_SLP_LDO6_ON BIT(5) ++#define RKPM_SLP_LDO7_ON BIT(6) ++#define RKPM_SLP_LDO8_ON BIT(7) ++#define RKPM_SLP_LDO9_ON BIT(8) ++ ++#endif +diff --git a/include/linux/platform_data/lm3630a_bl.h b/include/linux/platform_data/lm3630a_bl.h +index 530be9318..921ccf5ab 100644 +--- a/include/linux/platform_data/lm3630a_bl.h ++++ b/include/linux/platform_data/lm3630a_bl.h +@@ -33,6 +33,8 @@ enum lm3630a_ledb_ctrl { + }; + + #define LM3630A_MAX_BRIGHTNESS 255 ++#define LM3630A_BOOST_USE_1MHZ BIT(0) ++#define LM3630A_BOOST_SHIFT BIT(1) + /* + *@leda_label : optional led a label. + *@leda_init_brt : led a init brightness. 4~255 +@@ -44,6 +46,8 @@ enum lm3630a_ledb_ctrl { + *@ledb_ctrl : led b disable, enable linear, enable exponential + *@pwm_period : pwm period + *@pwm_ctrl : pwm disable, bank a or b, active high or low ++ *@boost_shift_freq : activate the frequency shift of the boost converter ++ *@boost_use_1mhz : switch boost converter frequency from 500kHz to 1MHz + */ + struct lm3630a_platform_data { + +@@ -52,14 +56,22 @@ struct lm3630a_platform_data { + int leda_init_brt; + int leda_max_brt; + enum lm3630a_leda_ctrl leda_ctrl; ++ int leda_current_index; + /* led b config. */ + const char *ledb_label; + int ledb_init_brt; + int ledb_max_brt; + enum lm3630a_ledb_ctrl ledb_ctrl; ++ int ledb_current_index; + /* pwm config. */ + unsigned int pwm_period; + enum lm3630a_pwm_ctrl pwm_ctrl; ++ /* boost config */ ++ int boost_ovp; ++ int boost_ocp; ++ bool boost_slow_start; ++ bool boost_shift; ++ bool boost_fmode; + }; + + #endif /* __LINUX_LM3630A_H */ +diff --git a/include/linux/rockchip/rockchip_sip.h b/include/linux/rockchip/rockchip_sip.h +new file mode 100644 +index 000000000..c77bbb78c +--- /dev/null ++++ b/include/linux/rockchip/rockchip_sip.h +@@ -0,0 +1,333 @@ ++/* Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++#ifndef __ROCKCHIP_SIP_H ++#define __ROCKCHIP_SIP_H ++ ++#include ++#include ++ ++/* SMC function IDs for SiP Service queries, compatible with kernel-3.10 */ ++#define SIP_ATF_VERSION 0x82000001 ++#define SIP_ACCESS_REG 0x82000002 ++#define SIP_SUSPEND_MODE 0x82000003 ++#define SIP_PENDING_CPUS 0x82000004 ++#define SIP_UARTDBG_CFG 0x82000005 ++#define SIP_UARTDBG_CFG64 0xc2000005 ++#define SIP_MCU_EL3FIQ_CFG 0x82000006 ++#define SIP_ACCESS_CHIP_STATE64 0xc2000006 ++#define SIP_SECURE_MEM_CONFIG 0x82000007 ++#define SIP_ACCESS_CHIP_EXTRA_STATE64 0xc2000007 ++#define SIP_DRAM_CONFIG 0x82000008 ++#define SIP_SHARE_MEM 0x82000009 ++#define SIP_SIP_VERSION 0x8200000a ++#define SIP_REMOTECTL_CFG 0x8200000b ++#define PSCI_SIP_VPU_RESET 0x8200000c ++#define SIP_BUS_CFG 0x8200000d ++#define SIP_LAST_LOG 0x8200000e ++#define SIP_SCMI_AGENT0 0x82000010 ++#define SIP_SCMI_AGENT1 0x82000011 ++#define SIP_SCMI_AGENT2 0x82000012 ++#define SIP_SCMI_AGENT3 0x82000013 ++#define SIP_SCMI_AGENT4 0x82000014 ++#define SIP_SCMI_AGENT5 0x82000015 ++#define SIP_SCMI_AGENT6 0x82000016 ++#define SIP_SCMI_AGENT7 0x82000017 ++#define SIP_SCMI_AGENT8 0x82000018 ++#define SIP_SCMI_AGENT9 0x82000019 ++#define SIP_SCMI_AGENT10 0x8200001a ++#define SIP_SCMI_AGENT11 0x8200001b ++#define SIP_SCMI_AGENT12 0x8200001c ++#define SIP_SCMI_AGENT13 0x8200001d ++#define SIP_SCMI_AGENT14 0x8200001e ++#define SIP_SCMI_AGENT15 0x8200001f ++#define SIP_SDEI_FIQ_DBG_SWITCH_CPU 0x82000020 ++#define SIP_SDEI_FIQ_DBG_GET_EVENT_ID 0x82000021 ++ ++/* Rockchip Sip version */ ++#define SIP_IMPLEMENT_V1 (1) ++#define SIP_IMPLEMENT_V2 (2) ++ ++/* Trust firmware version */ ++#define ATF_VER_MAJOR(ver) (((ver) >> 16) & 0xffff) ++#define ATF_VER_MINOR(ver) (((ver) >> 0) & 0xffff) ++ ++/* SIP_ACCESS_REG: read or write */ ++#define SECURE_REG_RD 0x0 ++#define SECURE_REG_WR 0x1 ++ ++/* Fiq debugger share memory: 8KB enough */ ++#define FIQ_UARTDBG_PAGE_NUMS 2 ++#define FIQ_UARTDBG_SHARE_MEM_SIZE ((FIQ_UARTDBG_PAGE_NUMS) * 4096) ++ ++/* Error return code */ ++#define IS_SIP_ERROR(x) (!!(x)) ++ ++#define SIP_RET_SUCCESS 0 ++#define SIP_RET_SMC_UNKNOWN -1 ++#define SIP_RET_NOT_SUPPORTED -2 ++#define SIP_RET_INVALID_PARAMS -3 ++#define SIP_RET_INVALID_ADDRESS -4 ++#define SIP_RET_DENIED -5 ++#define SIP_RET_SET_RATE_TIMEOUT -6 ++ ++/* SIP_UARTDBG_CFG64 call types */ ++#define UARTDBG_CFG_INIT 0xf0 ++#define UARTDBG_CFG_OSHDL_TO_OS 0xf1 ++#define UARTDBG_CFG_OSHDL_CPUSW 0xf3 ++#define UARTDBG_CFG_OSHDL_DEBUG_ENABLE 0xf4 ++#define UARTDBG_CFG_OSHDL_DEBUG_DISABLE 0xf5 ++#define UARTDBG_CFG_PRINT_PORT 0xf7 ++#define UARTDBG_CFG_FIQ_ENABEL 0xf8 ++#define UARTDBG_CFG_FIQ_DISABEL 0xf9 ++ ++/* SIP_SUSPEND_MODE32 call types */ ++#define SUSPEND_MODE_CONFIG 0x01 ++#define WKUP_SOURCE_CONFIG 0x02 ++#define PWM_REGULATOR_CONFIG 0x03 ++#define GPIO_POWER_CONFIG 0x04 ++#define SUSPEND_DEBUG_ENABLE 0x05 ++#define APIOS_SUSPEND_CONFIG 0x06 ++#define VIRTUAL_POWEROFF 0x07 ++#define SUSPEND_WFI_TIME_MS 0x08 ++#define LINUX_PM_STATE 0x09 ++ ++/* SIP_REMOTECTL_CFG call types */ ++#define REMOTECTL_SET_IRQ 0xf0 ++#define REMOTECTL_SET_PWM_CH 0xf1 ++#define REMOTECTL_SET_PWRKEY 0xf2 ++#define REMOTECTL_GET_WAKEUP_STATE 0xf3 ++#define REMOTECTL_ENABLE 0xf4 ++/* wakeup state */ ++#define REMOTECTL_PWRKEY_WAKEUP 0xdeadbeaf ++ ++enum { ++ FIRMWARE_NONE, ++ FIRMWARE_TEE_32BIT, ++ FIRMWARE_ATF_32BIT, ++ FIRMWARE_ATF_64BIT, ++ FIRMWARE_END, ++}; ++ ++/* Share mem page types */ ++typedef enum { ++ SHARE_PAGE_TYPE_INVALID = 0, ++ SHARE_PAGE_TYPE_UARTDBG, ++ SHARE_PAGE_TYPE_DDR, ++ SHARE_PAGE_TYPE_DDRDBG, ++ SHARE_PAGE_TYPE_DDRECC, ++ SHARE_PAGE_TYPE_LAST_LOG, ++ SHARE_PAGE_TYPE_MAX, ++} share_page_type_t; ++ ++/* ++ * Rules: struct arm_smccc_res contains result and data, details: ++ * ++ * a0: error code(0: success, !0: error); ++ * a1~a3: data ++ */ ++#if IS_ENABLED(CONFIG_ROCKCHIP_SIP) ++struct arm_smccc_res sip_smc_get_atf_version(void); ++struct arm_smccc_res sip_smc_get_sip_version(void); ++struct arm_smccc_res sip_smc_dram(u32 arg0, u32 arg1, u32 arg2); ++struct arm_smccc_res sip_smc_request_share_mem(u32 page_num, ++ share_page_type_t page_type); ++struct arm_smccc_res sip_smc_mcu_el3fiq(u32 arg0, u32 arg1, u32 arg2); ++struct arm_smccc_res sip_smc_vpu_reset(u32 arg0, u32 arg1, u32 arg2); ++struct arm_smccc_res sip_smc_get_suspend_info(u32 info); ++struct arm_smccc_res sip_smc_lastlog_request(void); ++ ++int sip_smc_set_suspend_mode(u32 ctrl, u32 config1, u32 config2); ++int sip_smc_virtual_poweroff(void); ++int sip_smc_remotectl_config(u32 func, u32 data); ++ ++int sip_smc_secure_reg_write(u32 addr_phy, u32 val); ++u32 sip_smc_secure_reg_read(u32 addr_phy); ++struct arm_smccc_res sip_smc_bus_config(u32 arg0, u32 arg1, u32 arg2); ++ ++/***************************fiq debugger **************************************/ ++void sip_fiq_debugger_enable_fiq(bool enable, uint32_t tgt_cpu); ++void sip_fiq_debugger_enable_debug(bool enable); ++int sip_fiq_debugger_uart_irq_tf_init(u32 irq_id, void *callback_fn); ++int sip_fiq_debugger_set_print_port(u32 port_phyaddr, u32 baudrate); ++int sip_fiq_debugger_request_share_memory(void); ++int sip_fiq_debugger_get_target_cpu(void); ++int sip_fiq_debugger_switch_cpu(u32 cpu); ++int sip_fiq_debugger_sdei_switch_cpu(u32 cur_cpu, u32 target_cpu, u32 flag); ++int sip_fiq_debugger_is_enabled(void); ++int sip_fiq_debugger_sdei_get_event_id(u32 *fiq, u32 *sw_cpu, u32 *flag); ++#else ++static inline struct arm_smccc_res sip_smc_get_atf_version(void) ++{ ++ struct arm_smccc_res tmp = {0}; ++ return tmp; ++} ++ ++static inline struct arm_smccc_res sip_smc_get_sip_version(void) ++{ ++ struct arm_smccc_res tmp = {0}; ++ return tmp; ++} ++ ++static inline struct arm_smccc_res sip_smc_dram(u32 arg0, u32 arg1, u32 arg2) ++{ ++ struct arm_smccc_res tmp = {0}; ++ return tmp; ++} ++ ++static inline struct arm_smccc_res sip_smc_request_share_mem ++ (u32 page_num, share_page_type_t page_type) ++{ ++ struct arm_smccc_res tmp = {0}; ++ return tmp; ++} ++ ++static inline struct arm_smccc_res sip_smc_mcu_el3fiq ++ (u32 arg0, u32 arg1, u32 arg2) ++{ ++ struct arm_smccc_res tmp = {0}; ++ return tmp; ++} ++ ++static inline struct arm_smccc_res ++sip_smc_vpu_reset(u32 arg0, u32 arg1, u32 arg2) ++{ ++ struct arm_smccc_res tmp = {0}; ++ return tmp; ++} ++ ++static inline struct arm_smccc_res sip_smc_lastlog_request(void) ++{ ++ struct arm_smccc_res tmp = {0}; ++ return tmp; ++} ++ ++static inline int sip_smc_set_suspend_mode(u32 ctrl, u32 config1, u32 config2) ++{ ++ return 0; ++} ++ ++static inline int sip_smc_get_suspend_info(u32 info) ++{ ++ return 0; ++} ++ ++static inline int sip_smc_virtual_poweroff(void) { return 0; } ++static inline int sip_smc_remotectl_config(u32 func, u32 data) { return 0; } ++static inline u32 sip_smc_secure_reg_read(u32 addr_phy) { return 0; } ++static inline int sip_smc_secure_reg_write(u32 addr_phy, u32 val) { return 0; } ++static inline int sip_smc_soc_bus_div(u32 arg0, u32 arg1, u32 arg2) ++{ ++ return 0; ++} ++ ++/***************************fiq debugger **************************************/ ++static inline void sip_fiq_debugger_enable_fiq ++ (bool enable, uint32_t tgt_cpu) { return; } ++ ++static inline void sip_fiq_debugger_enable_debug(bool enable) { return; } ++static inline int sip_fiq_debugger_uart_irq_tf_init(u32 irq_id, ++ void *callback_fn) ++{ ++ return 0; ++} ++ ++static inline int sip_fiq_debugger_set_print_port(u32 port_phyaddr, ++ u32 baudrate) ++{ ++ return 0; ++} ++ ++static inline int sip_fiq_debugger_request_share_memory(void) { return 0; } ++static inline int sip_fiq_debugger_get_target_cpu(void) { return 0; } ++static inline int sip_fiq_debugger_switch_cpu(u32 cpu) { return 0; } ++static inline int sip_fiq_debugger_sdei_switch_cpu(u32 cur_cpu, u32 target_cpu, ++ u32 flag) { return 0; } ++static inline int sip_fiq_debugger_is_enabled(void) { return 0; } ++#endif ++ ++/* 32-bit OP-TEE context, never change order of members! */ ++struct sm_nsec_ctx { ++ u32 usr_sp; ++ u32 usr_lr; ++ u32 irq_spsr; ++ u32 irq_sp; ++ u32 irq_lr; ++ u32 fiq_spsr; ++ u32 fiq_sp; ++ u32 fiq_lr; ++ u32 svc_spsr; ++ u32 svc_sp; ++ u32 svc_lr; ++ u32 abt_spsr; ++ u32 abt_sp; ++ u32 abt_lr; ++ u32 und_spsr; ++ u32 und_sp; ++ u32 und_lr; ++ u32 mon_lr; ++ u32 mon_spsr; ++ u32 r4; ++ u32 r5; ++ u32 r6; ++ u32 r7; ++ u32 r8; ++ u32 r9; ++ u32 r10; ++ u32 r11; ++ u32 r12; ++ u32 r0; ++ u32 r1; ++ u32 r2; ++ u32 r3; ++}; ++ ++/* 64-bit ATF context, never change order of members! */ ++struct gp_regs_ctx { ++ u64 x0; ++ u64 x1; ++ u64 x2; ++ u64 x3; ++ u64 x4; ++ u64 x5; ++ u64 x6; ++ u64 x7; ++ u64 x8; ++ u64 x9; ++ u64 x10; ++ u64 x11; ++ u64 x12; ++ u64 x13; ++ u64 x14; ++ u64 x15; ++ u64 x16; ++ u64 x17; ++ u64 x18; ++ u64 x19; ++ u64 x20; ++ u64 x21; ++ u64 x22; ++ u64 x23; ++ u64 x24; ++ u64 x25; ++ u64 x26; ++ u64 x27; ++ u64 x28; ++ u64 x29; ++ u64 lr; ++ u64 sp_el0; ++ u64 scr_el3; ++ u64 runtime_sp; ++ u64 spsr_el3; ++ u64 elr_el3; ++}; ++ ++#endif +diff --git a/include/uapi/drm/rockchip_ebc_drm.h b/include/uapi/drm/rockchip_ebc_drm.h +new file mode 100644 +index 000000000..f9b27f30f +--- /dev/null ++++ b/include/uapi/drm/rockchip_ebc_drm.h +@@ -0,0 +1,172 @@ ++#ifndef __ROCKCHIP_EBC_DRM_H__ ++#define __ROCKCHIP_EBC_DRM_H__ ++ ++#include "drm.h" ++#include "drm/drm_mode.h" ++ ++#if defined(__cplusplus) ++extern "C" { ++#endif ++ ++#define ROCKCHIP_EBC_HINT_BIT_DEPTH_Y1 0 << 4 ++#define ROCKCHIP_EBC_HINT_BIT_DEPTH_Y2 1 << 4 ++#define ROCKCHIP_EBC_HINT_BIT_DEPTH_Y4 2 << 4 ++#define ROCKCHIP_EBC_HINT_BIT_DEPTH_MASK 3 << 4 ++#define ROCKCHIP_EBC_HINT_THRESHOLD 0 << 6 ++#define ROCKCHIP_EBC_HINT_DITHER 1 << 6 ++#define ROCKCHIP_EBC_HINT_REDRAW 1 << 7 ++#define ROCKCHIP_EBC_HINT_MASK 0xf0 ++ ++#define ROCKCHIP_EBC_DRIVER_MODE_NORMAL 0 ++#define ROCKCHIP_EBC_DRIVER_MODE_FAST 1 ++#define ROCKCHIP_EBC_DRIVER_MODE_ZERO_WAVEFORM 8 ++#define ROCKCHIP_EBC_DRIVER_MODE_PHASE_SEQUENCE 9 ++ ++#define ROCKCHIP_EBC_DITHER_MODE_BAYER 0 ++#define ROCKCHIP_EBC_DITHER_MODE_BLUE_NOISE_16 1 ++#define ROCKCHIP_EBC_DITHER_MODE_BLUE_NOISE_32 2 ++ ++struct drm_rockchip_ebc_trigger_global_refresh { ++ bool trigger_global_refresh; ++}; ++ ++/** ++ * struct drm_rockchip_ebc_off_screen - Pointer to userspace buffer. ++ * @info1: unused. ++ * @ptr_screen_content: pointer to width * height * buffer containing ++ * the horizontally flipped off screen. The highest four bits of each ++ * byte are ignored. ++ */ ++struct drm_rockchip_ebc_off_screen { ++ __u64 info1; ++ __u64 ptr_screen_content; ++}; ++ ++/** ++ * struct drm_rockchip_ebc_extract_fbs - Pointers to userspace buffers. Pointers ++ that are NULL are skipped without warning. ++ * @ptr_packed_inner_outer_nextprev: pointer to 3 * width * height buffer ++ * containing packed tuples of inner counters, outer counters, and a ++ * packed Y4 (next, prev) pairs. ++ * @ptr_hints: pointer to width * height buffer for hints. ++ * @ptr_prelim_target: pointer to width * height buffer containing packed Y4 ++ (prelim, target) pairs. ++ * @ptr_phase1: pointer to width * height / 4 buffer containing the first of ++ the double-buffered phase buffers. ++ * @ptr_phase2: pointer to width * height / 4 buffer containing the second of ++ the double-buffered phase buffers. ++ */ ++struct drm_rockchip_ebc_extract_fbs { ++ char *ptr_packed_inner_outer_nextprev; ++ char *ptr_hints; ++ char *ptr_prelim_target; ++ char *ptr_phase1; ++ char *ptr_phase2; ++}; ++ ++/** ++ * struct drm_rockchip_ebc_rect_hint - Pixels hint definition ++ * @hints: Pixel hints for all pixel in the rectangle. ++ * @padding: Padding for 64bit alignment. ++ * @rect: Rectangular region on which the hints should be applied. ++ */ ++struct drm_rockchip_ebc_rect_hint { ++ __u8 hints; ++ __u8 padding[7]; ++ struct drm_mode_rect rect; ++}; ++ ++/** ++ * struct drm_mode_rect_hints ++ * @set_default_hint: Reset hints to their default before applying new ones. ++ * @default_hint: Hint to use for uncovered pixels. ++ * @padding: 64bit alignment padding. ++ * @num_rects: Number of rectangles contained in rect_hints ++ * @rect_hints: Pointer to an array of struct drm_rockchip_ebc_rect_hint. ++ */ ++struct drm_rockchip_ebc_rect_hints { ++ __u8 set_default_hint; ++ __u8 default_hint; ++ __u8 padding[2]; ++ __u32 num_rects; ++ __u64 rect_hints; ++}; ++ ++/** ++ * struct drm_rockchip_ebc_mode - Query and set driver/dither modes and ++ * redraw delay ++ * @set_driver_mode: apply driver_mode instead of reading it to the same ++ * field. ++ * @driver_mode: one of ROCKCHIP_EBC_DRIVER_MODE_NORMAL or ++ * ROCKCHIP_EBC_DRIVER_MODE_FAST. ++ * @set_dither_mode: apply dither_mode instead of reading it to the same ++ * field. ++ * @dither_mode: one of ROCKCHIP_EBC_DITHER_MODE_BAYER, ++ * ROCKCHIP_EBC_DITHER_MODE_BLUE_NOISE_16, or ++ * ROCKCHIP_EBC_DITHER_MODE_BLUE_NOISE_32. ++ * @redraw_delay: number of hardware frames to delay redraws. ++ * @set_redraw_delay: apply redraw_delay instead of reading it to the same ++ * field. ++ */ ++struct drm_rockchip_ebc_mode { ++ __u8 set_driver_mode; ++ __u8 driver_mode; ++ __u8 set_dither_mode; ++ __u8 dither_mode; ++ __u16 redraw_delay; ++ __u8 set_redraw_delay; ++ __u8 _pad; ++}; ++ ++/** ++ * struct drm_rockchip_ebc_zero_waveform- Query and enable/disable zero ++ * waveform mode ++ * @set_zero_waveform_mode: apply zero_waveform_mode instead of reading it to ++ * the same field. ++ * @zero_waveform_mode: 0 for disable(d), 1 for enable(d) ++ */ ++struct drm_rockchip_ebc_zero_waveform { ++ __u8 set_zero_waveform_mode; ++ __u8 zero_waveform_mode; ++ __u8 _pad[6]; ++}; ++ ++#define ROCKCHIP_EBC_MAX_PHASE_SEQUENCES 96 ++#define ROCKCHIP_EBC_MAX_REGIONS 8 ++ ++struct drm_rockchip_ebc_phase_sequence_element { ++ __u32 delay_ms; ++ __u8 num_frames; ++ __u8 _pad[3]; ++ __u32 num_regions; ++ struct drm_mode_rect rect[ROCKCHIP_EBC_MAX_REGIONS]; ++ __u8 phase[ROCKCHIP_EBC_MAX_REGIONS]; ++}; ++ ++struct drm_rockchip_ebc_phase_sequence { ++ __u8 num_seqs; ++ __u8 do_init; ++ __u8 do_gc16; ++ __u8 gc_target; ++ __u8 do_force_temperature; ++ __u8 force_temperature; ++ __u8 _pad[6]; ++ __u32 delay_ms; ++ struct drm_rockchip_ebc_phase_sequence_element elms[ROCKCHIP_EBC_MAX_PHASE_SEQUENCES]; ++}; ++ ++#define DRM_ROCKCHIP_EBC_NUM_IOCTLS 0x07 ++ ++#define DRM_IOCTL_ROCKCHIP_EBC_GLOBAL_REFRESH DRM_IOWR(DRM_COMMAND_BASE + 0x00, struct drm_rockchip_ebc_trigger_global_refresh) ++#define DRM_IOCTL_ROCKCHIP_EBC_OFF_SCREEN DRM_IOW(DRM_COMMAND_BASE + 0x01, struct drm_rockchip_ebc_off_screen) ++#define DRM_IOCTL_ROCKCHIP_EBC_EXTRACT_FBS DRM_IOWR(DRM_COMMAND_BASE + 0x02, struct drm_rockchip_ebc_extract_fbs) ++#define DRM_IOCTL_ROCKCHIP_EBC_RECT_HINTS DRM_IOW(DRM_COMMAND_BASE + 0x03, struct drm_rockchip_ebc_rect_hints) ++#define DRM_IOCTL_ROCKCHIP_EBC_MODE DRM_IOWR(DRM_COMMAND_BASE + 0x04, struct drm_rockchip_ebc_mode) ++#define DRM_IOCTL_ROCKCHIP_EBC_ZERO_WAVEFORM DRM_IOWR(DRM_COMMAND_BASE + 0x05, struct drm_rockchip_ebc_zero_waveform) ++#define DRM_IOCTL_ROCKCHIP_EBC_PHASE_SEQUENCE DRM_IOW(DRM_COMMAND_BASE + 0x06, struct drm_rockchip_ebc_phase_sequence) ++ ++#if defined(__cplusplus) ++} ++#endif ++ ++#endif /* __ROCKCHIP_EBC_DRM_H__*/ +diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h +index f84ed133a..4378671ad 100644 +--- a/include/uapi/linux/v4l2-controls.h ++++ b/include/uapi/linux/v4l2-controls.h +@@ -204,16 +204,11 @@ enum v4l2_colorfx { + #define V4L2_CID_USER_ASPEED_BASE (V4L2_CID_USER_BASE + 0x11a0) + + /* +- * The base for Nuvoton NPCM driver controls. +- * We reserve 16 controls for this driver. +- */ +-#define V4L2_CID_USER_NPCM_BASE (V4L2_CID_USER_BASE + 0x11b0) +- +-/* +- * The base for THine THP7312 driver controls. ++ * The base for RGA driver controls. + * We reserve 32 controls for this driver. ++ * hex(int("0x11a0", 16) + 16) + */ +-#define V4L2_CID_USER_THP7312_BASE (V4L2_CID_USER_BASE + 0x11c0) ++#define V4L2_CID_USER_RGA_BASE (V4L2_CID_USER_BASE + 0x11b0) + + /* + * The base for the uvc driver controls. +diff --git a/scripts/package/debian/rules b/scripts/package/debian/rules +index a417a7f8b..5159f60e6 100755 +--- a/scripts/package/debian/rules ++++ b/scripts/package/debian/rules +@@ -56,7 +56,7 @@ define binary + $(Q)dh_installchangelogs $(DH_OPTIONS) + $(Q)dh_compress $(DH_OPTIONS) + $(Q)dh_fixperms $(DH_OPTIONS) +- $(Q)dh_gencontrol $(DH_OPTIONS) -- -f$(call mk-files,$@) ++ $(Q)dh_gencontrol $(DH_OPTIONS) -- -DProvides=wireguard-modules -f$(call mk-files,$@) + $(Q)dh_md5sums $(DH_OPTIONS) + $(Q)dh_builddeb $(DH_OPTIONS) -- $(addprefix -Z,$(KDEB_COMPRESS)) + endef diff --git a/device/community/linux-postmarketos-rockchip/rk3566-pinenote-ultra-suspend.patch b/device/community/linux-postmarketos-rockchip/rk3566-pinenote-ultra-suspend.patch new file mode 100644 index 000000000..a5334820d --- /dev/null +++ b/device/community/linux-postmarketos-rockchip/rk3566-pinenote-ultra-suspend.patch @@ -0,0 +1,160 @@ +Patch-Source: https://git.sr.ht/~hrdl/linux/log/v6.19_ultra_suspend +From af0f33629aefaa75ea6318352debdb8b64655c15 Mon Sep 17 00:00:00 2001 +From: hrdl +Date: Sun, 15 Mar 2026 21:35:31 +0100 +Subject: [PATCH] [rockchip suspend driver] allow overriding suspend_state + +Rockchip's bl31 behaves differently when configured with +suspend_state=5, which identifies rockchip's ultra suspend mode. This +is a prerequisite to achieve significant power savings in suspend mode +with rockchip's bl31. + +Signed-off-by: hrdl +--- + drivers/soc/rockchip/rockchip_pm_config.c | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/drivers/soc/rockchip/rockchip_pm_config.c b/drivers/soc/rockchip/rockchip_pm_config.c +index 65524181695fb8eafa0c09adf5d9c9860f1f6654..e5f982879c42b9f4ca5ac751cd973f880917b1b6 100644 +--- a/drivers/soc/rockchip/rockchip_pm_config.c ++++ b/drivers/soc/rockchip/rockchip_pm_config.c +@@ -38,6 +38,8 @@ static struct rk_on_off_regulator_list { + struct regulator_dev *off_reg_list[MAX_ON_OFF_REG_NUM]; + } on_off_regs_list[RK_PM_STATE_MAX]; + ++static int suspend_state_override = 0; ++ + static const struct of_device_id pm_match_table[] = { + { .compatible = "rockchip,pm-px30",}, + { .compatible = "rockchip,pm-rk1808",}, +@@ -190,6 +192,9 @@ static int pm_config_probe(struct platform_device *pdev) + else + sip_smc_set_suspend_mode(WKUP_SOURCE_CONFIG, wakeup_config, 0); + ++ of_property_read_u32(node, "rockchip,suspend-state-override", ++ &suspend_state_override); ++ + if (of_property_read_u32_array(node, + "rockchip,pwm-regulator-config", + &pwm_regulator_config, 1)) +@@ -250,6 +255,9 @@ static int pm_config_prepare(struct device *dev) + struct regulator_dev **on_list; + struct regulator_dev **off_list; + ++ if (suspend_state_override) ++ suspend_state = suspend_state_override; ++ + sip_smc_set_suspend_mode(LINUX_PM_STATE, + suspend_state, + 0); +From 693fee19336c7b348e30222a31de06eb34f70e25 Mon Sep 17 00:00:00 2001 +From: hrdl +Date: Sun, 15 Mar 2026 21:35:31 +0100 +Subject: [PATCH] [HACK]: Input: cyttsp5: support resume from ultra suspend + +Signed-off-by: hrdl +--- + drivers/input/touchscreen/cyttsp5.c | 19 +++++++++++++++---- + 1 file changed, 15 insertions(+), 4 deletions(-) + +diff --git a/drivers/input/touchscreen/cyttsp5.c b/drivers/input/touchscreen/cyttsp5.c +index 63e71f3b1ff2c40ec29d5c01a37afeee20512c33..c163ef2d87b80267b7cf28f7967162be8ac3177d 100644 +--- a/drivers/input/touchscreen/cyttsp5.c ++++ b/drivers/input/touchscreen/cyttsp5.c +@@ -1008,11 +1008,22 @@ static int __maybe_unused cyttsp5_suspend(struct device *dev) + static int __maybe_unused cyttsp5_resume(struct device *dev) + { + struct cyttsp5 *ts = dev_get_drvdata(dev); ++ int error = 0; ++ ++ if (!device_may_wakeup(dev)) { ++ error = cyttsp5_power_control(ts, true); ++ ++ if (error) { ++ gpiod_set_value_cansleep(ts->reset_gpio, 1); ++ msleep(10); ++ gpiod_set_value_cansleep(ts->reset_gpio, 0); ++ msleep(20); ++ error = 0; ++ // error = cyttsp5_startup(ts); ++ } ++ } + +- if (!device_may_wakeup(dev)) +- cyttsp5_power_control(ts, true); +- +- return 0; ++ return error; + } + + static SIMPLE_DEV_PM_OPS(cyttsp5_pm, cyttsp5_suspend, cyttsp5_resume); +From ee2c553f7893e82223e42436cebbcf8c4958464d Mon Sep 17 00:00:00 2001 +From: hrdl +Date: Sun, 15 Mar 2026 21:35:32 +0100 +Subject: [PATCH] arm64: dts: rockchip: pinenote: enable rockchip's ultra + suspend mode + +This reduces suspend power consumption from about 60 mW to about 11 +mW, extending the battery life in suspend mode from a week to about +1.5 months. This is achieved by + +1. Disabling the *_pmu regulators in suspend to save power. +2. Overriding the suspend mode to instruct bl31 to configure the + system for ultra suspend (suspend mode 5). This is needed for the + system to resume. +3. Indicate that the wifi mmc module will be powered off during + suspend. Without it the driver will not try to reinitialise it. + +Signed-off-by: hrdl +--- + arch/arm64/boot/dts/rockchip/rk3566-pinenote.dtsi | 9 +++++---- + 1 file changed, 5 insertions(+), 4 deletions(-) + +diff --git a/arch/arm64/boot/dts/rockchip/rk3566-pinenote.dtsi b/arch/arm64/boot/dts/rockchip/rk3566-pinenote.dtsi +index 15118c7ea8aa70f8ddcd26ebe4411dfe0b04e7b4..fe2219961b2420874626941ac56d42f7a444ce34 100644 +--- a/arch/arm64/boot/dts/rockchip/rk3566-pinenote.dtsi ++++ b/arch/arm64/boot/dts/rockchip/rk3566-pinenote.dtsi +@@ -137,6 +137,7 @@ + compatible = "rockchip,pm-rk3568"; + status = "okay"; + rockchip,sleep-debug-en = <0>; ++ rockchip,suspend-state-override = <5>; + rockchip,sleep-mode-config = < + (0 + | RKPM_SLP_CENTER_OFF +@@ -486,7 +487,7 @@ + regulator-max-microvolt = <1800000>; + + regulator-state-mem { +- regulator-on-in-suspend; ++ regulator-off-in-suspend; + }; + }; + +@@ -508,7 +509,7 @@ + regulator-max-microvolt = <900000>; + + regulator-state-mem { +- regulator-on-in-suspend; ++ regulator-off-in-suspend; + }; + }; + +@@ -541,7 +542,7 @@ + regulator-max-microvolt = <3300000>; + + regulator-state-mem { +- regulator-on-in-suspend; ++ regulator-off-in-suspend; + }; + }; + +@@ -981,7 +982,7 @@ + bus-width = <4>; + cap-sd-highspeed; + cap-sdio-irq; +- keep-power-in-suspend; ++ cap-power-off-card; + mmc-pwrseq = <&sdio_pwrseq>; + non-removable; + pinctrl-0 = <&sdmmc1_bus4>, <&sdmmc1_clk>, <&sdmmc1_cmd>;