sm8550: Add patches for 6.18
Signed-off-by: Alex Ling <ling_kasim@hotmail.com>
This commit is contained in:
parent
6b1f235243
commit
b9b5fb8b0c
@ -1,4 +1,4 @@
|
||||
# Armbian defconfig generated with 6.12
|
||||
# Armbian defconfig generated with 6.18
|
||||
# CONFIG_LOCALVERSION_AUTO is not set
|
||||
CONFIG_DEFAULT_HOSTNAME="@DEVICENAME@"
|
||||
CONFIG_SYSVIPC=y
|
||||
@ -42,8 +42,6 @@ CONFIG_ARCH_QCOM=y
|
||||
# CONFIG_NVIDIA_CARMEL_CNP_ERRATUM is not set
|
||||
# CONFIG_ROCKCHIP_ERRATUM_3588001 is not set
|
||||
CONFIG_ARM64_VA_BITS_48=y
|
||||
CONFIG_SCHED_MC=y
|
||||
CONFIG_SCHED_SMT=y
|
||||
CONFIG_NUMA=y
|
||||
CONFIG_PARAVIRT=y
|
||||
CONFIG_COMPAT=y
|
||||
@ -74,7 +72,6 @@ CONFIG_ACPI_APEI_MEMORY_FAILURE=y
|
||||
CONFIG_ACPI_APEI_EINJ=y
|
||||
CONFIG_VIRTUALIZATION=y
|
||||
CONFIG_KVM=y
|
||||
CONFIG_JUMP_LABEL=y
|
||||
CONFIG_MODULES=y
|
||||
CONFIG_MODULE_UNLOAD=y
|
||||
CONFIG_BLK_DEV_INTEGRITY=y
|
||||
@ -83,7 +80,6 @@ CONFIG_PARTITION_ADVANCED=y
|
||||
# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set
|
||||
CONFIG_BINFMT_MISC=y
|
||||
CONFIG_ZSWAP=y
|
||||
CONFIG_ZSWAP_ZPOOL_DEFAULT_ZBUD=y
|
||||
# CONFIG_COMPAT_BRK is not set
|
||||
CONFIG_MEMORY_HOTPLUG=y
|
||||
CONFIG_MEMORY_HOTREMOVE=y
|
||||
@ -145,18 +141,22 @@ CONFIG_NETFILTER_XT_TARGET_AUDIT=m
|
||||
CONFIG_NETFILTER_XT_TARGET_CHECKSUM=m
|
||||
CONFIG_NETFILTER_XT_TARGET_CLASSIFY=m
|
||||
CONFIG_NETFILTER_XT_TARGET_CONNMARK=m
|
||||
CONFIG_NETFILTER_XT_TARGET_CT=m
|
||||
CONFIG_NETFILTER_XT_TARGET_DSCP=m
|
||||
CONFIG_NETFILTER_XT_TARGET_HL=m
|
||||
CONFIG_NETFILTER_XT_TARGET_HMARK=m
|
||||
CONFIG_NETFILTER_XT_TARGET_IDLETIMER=m
|
||||
CONFIG_NETFILTER_XT_TARGET_LED=m
|
||||
CONFIG_NETFILTER_XT_TARGET_LOG=m
|
||||
CONFIG_NETFILTER_XT_TARGET_MARK=m
|
||||
CONFIG_NETFILTER_XT_NAT=m
|
||||
CONFIG_NETFILTER_XT_TARGET_NETMAP=m
|
||||
CONFIG_NETFILTER_XT_TARGET_NFLOG=m
|
||||
CONFIG_NETFILTER_XT_TARGET_NFQUEUE=m
|
||||
CONFIG_NETFILTER_XT_TARGET_NOTRACK=m
|
||||
CONFIG_NETFILTER_XT_TARGET_REDIRECT=m
|
||||
CONFIG_NETFILTER_XT_TARGET_MASQUERADE=m
|
||||
CONFIG_NETFILTER_XT_TARGET_TEE=m
|
||||
CONFIG_NETFILTER_XT_TARGET_TPROXY=m
|
||||
CONFIG_NETFILTER_XT_TARGET_TRACE=m
|
||||
CONFIG_NETFILTER_XT_TARGET_TCPMSS=m
|
||||
CONFIG_NETFILTER_XT_TARGET_TCPOPTSTRIP=m
|
||||
CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=m
|
||||
@ -221,15 +221,7 @@ CONFIG_NFT_DUP_IPV4=m
|
||||
CONFIG_NFT_FIB_IPV4=m
|
||||
CONFIG_NF_TABLES_ARP=y
|
||||
CONFIG_IP_NF_IPTABLES=m
|
||||
CONFIG_IP_NF_FILTER=m
|
||||
CONFIG_IP_NF_TARGET_REJECT=m
|
||||
CONFIG_IP_NF_NAT=m
|
||||
CONFIG_IP_NF_TARGET_MASQUERADE=m
|
||||
CONFIG_IP_NF_TARGET_NETMAP=m
|
||||
CONFIG_IP_NF_TARGET_REDIRECT=m
|
||||
CONFIG_IP_NF_MANGLE=m
|
||||
CONFIG_IP_NF_RAW=m
|
||||
CONFIG_IP_NF_SECURITY=m
|
||||
CONFIG_NFT_DUP_IPV6=m
|
||||
CONFIG_NFT_FIB_IPV6=m
|
||||
CONFIG_IP6_NF_IPTABLES=m
|
||||
@ -243,15 +235,8 @@ CONFIG_IP6_NF_MATCH_MH=m
|
||||
CONFIG_IP6_NF_MATCH_RPFILTER=m
|
||||
CONFIG_IP6_NF_MATCH_RT=m
|
||||
CONFIG_IP6_NF_MATCH_SRH=m
|
||||
CONFIG_IP6_NF_TARGET_HL=m
|
||||
CONFIG_IP6_NF_FILTER=m
|
||||
CONFIG_IP6_NF_TARGET_REJECT=m
|
||||
CONFIG_IP6_NF_TARGET_SYNPROXY=m
|
||||
CONFIG_IP6_NF_MANGLE=m
|
||||
CONFIG_IP6_NF_RAW=m
|
||||
CONFIG_IP6_NF_SECURITY=m
|
||||
CONFIG_IP6_NF_NAT=m
|
||||
CONFIG_IP6_NF_TARGET_MASQUERADE=m
|
||||
CONFIG_IP6_NF_TARGET_NPT=m
|
||||
CONFIG_NF_TABLES_BRIDGE=m
|
||||
CONFIG_NFT_BRIDGE_META=m
|
||||
@ -289,7 +274,9 @@ CONFIG_BT_HCIUART=m
|
||||
CONFIG_BT_HCIUART_LL=y
|
||||
CONFIG_BT_HCIUART_QCA=y
|
||||
CONFIG_CFG80211=m
|
||||
CONFIG_CFG80211_WEXT=y
|
||||
CONFIG_MAC80211=m
|
||||
CONFIG_MAC80211_MESH=y
|
||||
CONFIG_MAC80211_LEDS=y
|
||||
CONFIG_RFKILL=m
|
||||
CONFIG_RFKILL_INPUT=y
|
||||
@ -364,15 +351,14 @@ CONFIG_QCOM_COINCELL=m
|
||||
CONFIG_QCOM_FASTRPC=y
|
||||
CONFIG_SRAM=y
|
||||
CONFIG_PCI_ENDPOINT_TEST=m
|
||||
CONFIG_NTSYNC=m
|
||||
CONFIG_EEPROM_AT24=m
|
||||
CONFIG_EEPROM_AT25=m
|
||||
CONFIG_EEPROM_93CX6=y
|
||||
CONFIG_UACCE=m
|
||||
CONFIG_SCSI=y
|
||||
# CONFIG_SCSI_PROC_FS is not set
|
||||
CONFIG_BLK_DEV_SD=y
|
||||
CONFIG_MD=y
|
||||
# CONFIG_MD_BITMAP_FILE is not set
|
||||
CONFIG_NETDEVICES=y
|
||||
CONFIG_DUMMY=m
|
||||
CONFIG_WIREGUARD=m
|
||||
@ -608,9 +594,7 @@ CONFIG_VIDEO_QCOM_VENUS=m
|
||||
CONFIG_DRM=y
|
||||
CONFIG_DRM_LOAD_EDID_FIRMWARE=y
|
||||
CONFIG_DRM_DISPLAY_DP_AUX_CHARDEV=y
|
||||
CONFIG_DRM_I2C_CH7006=m
|
||||
CONFIG_DRM_I2C_SIL164=m
|
||||
CONFIG_DRM_I2C_NXP_TDA998X=m
|
||||
CONFIG_DRM_SIMPLEDRM=y
|
||||
CONFIG_DRM_MSM=y
|
||||
CONFIG_DRM_PANEL_CHIPONE_ICNA3512=y
|
||||
CONFIG_DRM_PANEL_LVDS=m
|
||||
@ -618,8 +602,8 @@ CONFIG_DRM_PANEL_EDP=y
|
||||
CONFIG_DRM_PANEL_SIMPLE=m
|
||||
CONFIG_DRM_PANEL_SYNAPTICS_TD4328=y
|
||||
CONFIG_DRM_DISPLAY_CONNECTOR=y
|
||||
CONFIG_DRM_I2C_NXP_TDA998X=m
|
||||
CONFIG_DRM_SIMPLE_BRIDGE=m
|
||||
CONFIG_DRM_SIMPLEDRM=y
|
||||
CONFIG_FB=y
|
||||
CONFIG_FB_EFI=y
|
||||
CONFIG_FB_MODE_HELPERS=y
|
||||
@ -681,10 +665,10 @@ CONFIG_HID_PLAYSTATION=y
|
||||
CONFIG_PLAYSTATION_FF=y
|
||||
CONFIG_HID_SONY=y
|
||||
CONFIG_SONY_FF=y
|
||||
CONFIG_USB_HIDDEV=y
|
||||
CONFIG_I2C_HID_ACPI=m
|
||||
CONFIG_I2C_HID_OF=m
|
||||
CONFIG_I2C_HID_OF_ELAN=m
|
||||
CONFIG_USB_HIDDEV=y
|
||||
CONFIG_USB_ULPI_BUS=y
|
||||
CONFIG_USB_CONN_GPIO=y
|
||||
CONFIG_USB_OTG=y
|
||||
@ -852,12 +836,13 @@ CONFIG_IIO_TRIGGERED_BUFFER=m
|
||||
CONFIG_QCOM_SPMI_VADC=y
|
||||
CONFIG_QCOM_SPMI_ADC5=y
|
||||
CONFIG_PWM=y
|
||||
CONFIG_PWM_SN3112=y
|
||||
CONFIG_PWM_SN3112=m
|
||||
CONFIG_QCOM_PDC=y
|
||||
CONFIG_QCOM_MPM=y
|
||||
CONFIG_RESET_GPIO=m
|
||||
CONFIG_RESET_QCOM_AOSS=y
|
||||
CONFIG_RESET_QCOM_PDC=y
|
||||
CONFIG_PHY_SNPS_EUSB2=y
|
||||
CONFIG_PHY_CAN_TRANSCEIVER=m
|
||||
CONFIG_PHY_CADENCE_TORRENT=m
|
||||
CONFIG_PHY_CADENCE_DPHY_RX=m
|
||||
@ -868,7 +853,6 @@ CONFIG_PHY_QCOM_PCIE2=m
|
||||
CONFIG_PHY_QCOM_QMP=y
|
||||
CONFIG_PHY_QCOM_QMP_PCIE_8996=m
|
||||
CONFIG_PHY_QCOM_QUSB2=m
|
||||
CONFIG_PHY_QCOM_SNPS_EUSB2=y
|
||||
CONFIG_PHY_QCOM_EUSB2_REPEATER=y
|
||||
CONFIG_PHY_QCOM_M31_USB=m
|
||||
CONFIG_PHY_QCOM_USB_HS=m
|
||||
@ -898,7 +882,6 @@ CONFIG_TEE=y
|
||||
CONFIG_OPTEE=y
|
||||
CONFIG_MUX_GPIO=m
|
||||
CONFIG_MUX_MMIO=m
|
||||
CONFIG_SLIM_QCOM_CTRL=m
|
||||
CONFIG_SLIM_QCOM_NGD_CTRL=m
|
||||
CONFIG_INTERCONNECT_QCOM=y
|
||||
CONFIG_INTERCONNECT_QCOM_OSM_L3=y
|
||||
@ -952,7 +935,6 @@ CONFIG_SECURITY=y
|
||||
CONFIG_SECURITY_APPARMOR=y
|
||||
CONFIG_LSM="landlock,lockdown,yama,loadpin,safesetid,bpf"
|
||||
CONFIG_CRYPTO_USER=y
|
||||
CONFIG_CRYPTO_TEST=m
|
||||
CONFIG_CRYPTO_ECDH=y
|
||||
CONFIG_CRYPTO_DES=m
|
||||
CONFIG_CRYPTO_ARC4=m
|
||||
@ -963,14 +945,10 @@ CONFIG_CRYPTO_MICHAEL_MIC=y
|
||||
CONFIG_CRYPTO_ANSI_CPRNG=y
|
||||
CONFIG_CRYPTO_USER_API_RNG=m
|
||||
CONFIG_CRYPTO_GHASH_ARM64_CE=y
|
||||
CONFIG_CRYPTO_SHA1_ARM64_CE=y
|
||||
CONFIG_CRYPTO_SHA2_ARM64_CE=y
|
||||
CONFIG_CRYPTO_SHA512_ARM64_CE=m
|
||||
CONFIG_CRYPTO_SHA3_ARM64=m
|
||||
CONFIG_CRYPTO_SM3_ARM64_CE=m
|
||||
CONFIG_CRYPTO_AES_ARM64_BS=m
|
||||
CONFIG_CRYPTO_AES_ARM64_CE_CCM=y
|
||||
CONFIG_CRYPTO_CRCT10DIF_ARM64_CE=y
|
||||
CONFIG_CRYPTO_DEV_QCE=y
|
||||
CONFIG_CRYPTO_DEV_QCOM_RNG=y
|
||||
CONFIG_CRYPTO_DEV_CCREE=m
|
||||
@ -981,7 +959,6 @@ CONFIG_CRYPTO_DEV_HISI_TRNG=m
|
||||
CONFIG_CRYPTO_DEV_AMLOGIC_GXL=m
|
||||
CONFIG_PACKING=y
|
||||
CONFIG_INDIRECT_PIO=y
|
||||
CONFIG_CRC_CCITT=m
|
||||
CONFIG_DMA_RESTRICTED_POOL=y
|
||||
CONFIG_DMA_CMA=y
|
||||
CONFIG_CMA_SIZE_MBYTES=32
|
||||
@ -991,11 +968,8 @@ CONFIG_CONSOLE_LOGLEVEL_DEFAULT=4
|
||||
CONFIG_CONSOLE_LOGLEVEL_QUIET=1
|
||||
CONFIG_BOOT_PRINTK_DELAY=y
|
||||
CONFIG_DYNAMIC_DEBUG=y
|
||||
CONFIG_DEBUG_INFO_DWARF5=y
|
||||
CONFIG_DEBUG_INFO_REDUCED=y
|
||||
CONFIG_MAGIC_SYSRQ=y
|
||||
CONFIG_DEBUG_FS=y
|
||||
CONFIG_DEBUG_MEMORY_INIT=y
|
||||
# CONFIG_SCHED_DEBUG is not set
|
||||
# CONFIG_FTRACE is not set
|
||||
CONFIG_MEMTEST=y
|
||||
|
||||
@ -0,0 +1,53 @@
|
||||
From df25170e42b1b5a20e5e2d1489bcffe5d6aee336 Mon Sep 17 00:00:00 2001
|
||||
From: Teguh Sobirin <teguh@sobir.in>
|
||||
Date: Wed, 12 Feb 2025 17:53:48 +0800
|
||||
Subject: [PATCH 01/18] arm64: dts: qcom: sm8550: add UART15
|
||||
|
||||
Signed-off-by: Teguh Sobirin <teguh@sobir.in>
|
||||
---
|
||||
arch/arm64/boot/dts/qcom/sm8550.dtsi | 22 ++++++++++++++++++++++
|
||||
1 file changed, 22 insertions(+)
|
||||
|
||||
diff --git a/arch/arm64/boot/dts/qcom/sm8550.dtsi b/arch/arm64/boot/dts/qcom/sm8550.dtsi
|
||||
index 7724dba75..ac0698c01 100644
|
||||
--- a/arch/arm64/boot/dts/qcom/sm8550.dtsi
|
||||
+++ b/arch/arm64/boot/dts/qcom/sm8550.dtsi
|
||||
@@ -1251,6 +1251,20 @@ &config_noc SLAVE_QUP_2 QCOM_ICC_TAG_ACTIVE_ONLY>,
|
||||
#size-cells = <0>;
|
||||
status = "disabled";
|
||||
};
|
||||
+
|
||||
+ uart15: serial@89c000 {
|
||||
+ compatible = "qcom,geni-uart";
|
||||
+ reg = <0 0x89c000 0 0x4000>;
|
||||
+ clock-names = "se";
|
||||
+ clocks = <&gcc GCC_QUPV3_WRAP2_S7_CLK>;
|
||||
+ pinctrl-names = "default";
|
||||
+ pinctrl-0 = <&qup_uart15_default>;
|
||||
+ interrupts = <GIC_SPI 462 IRQ_TYPE_LEVEL_HIGH 0>;
|
||||
+ interconnects = <&clk_virt MASTER_QUP_CORE_2 0 &clk_virt SLAVE_QUP_CORE_2 0>,
|
||||
+ <&gem_noc MASTER_APPSS_PROC 0 &config_noc SLAVE_QUP_2 0>;
|
||||
+ interconnect-names = "qup-core", "qup-config";
|
||||
+ status = "disabled";
|
||||
+ };
|
||||
};
|
||||
|
||||
i2c_master_hub_0: geniqup@9c0000 {
|
||||
@@ -4917,6 +4931,14 @@ qup_uart14_cts_rts: qup-uart14-cts-rts-state {
|
||||
bias-pull-down;
|
||||
};
|
||||
|
||||
+ qup_uart15_default: qup-uart15-default-state {
|
||||
+ /* TX, RX */
|
||||
+ pins = "gpio74", "gpio75";
|
||||
+ function = "qup2_se7";
|
||||
+ drive-strength = <2>;
|
||||
+ bias-pull-up;
|
||||
+ };
|
||||
+
|
||||
sdc2_sleep: sdc2-sleep-state {
|
||||
clk-pins {
|
||||
pins = "sdc2_clk";
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@ -0,0 +1,472 @@
|
||||
From 223437dc502f36021f3131bf8ba3a0b2a3dcee0c Mon Sep 17 00:00:00 2001
|
||||
From: Teguh Sobirin <teguh@sobir.in>
|
||||
Date: Thu, 20 Feb 2025 14:50:30 +0800
|
||||
Subject: [PATCH 02/18] input: Add driver for RSInput Gamepad
|
||||
|
||||
Signed-off-by: Teguh Sobirin <teguh@sobir.in>
|
||||
---
|
||||
drivers/input/joystick/Kconfig | 4 +
|
||||
drivers/input/joystick/Makefile | 1 +
|
||||
drivers/input/joystick/rsinput.c | 423 +++++++++++++++++++++++++++++++
|
||||
3 files changed, 428 insertions(+)
|
||||
create mode 100644 drivers/input/joystick/rsinput.c
|
||||
|
||||
diff --git a/drivers/input/joystick/Kconfig b/drivers/input/joystick/Kconfig
|
||||
index 7755e5b45..0da3c0f44 100644
|
||||
--- a/drivers/input/joystick/Kconfig
|
||||
+++ b/drivers/input/joystick/Kconfig
|
||||
@@ -383,6 +383,10 @@ config JOYSTICK_QWIIC
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called qwiic-joystick.
|
||||
|
||||
+config JOYSTICK_RSINPUT
|
||||
+ tristate "UART Based gamepad driver that found in AYN and Retroid Pocket products"
|
||||
+ depends on SERIAL_DEV_BUS
|
||||
+
|
||||
config JOYSTICK_FSIA6B
|
||||
tristate "FlySky FS-iA6B RC Receiver"
|
||||
select SERIO
|
||||
diff --git a/drivers/input/joystick/Makefile b/drivers/input/joystick/Makefile
|
||||
index 9976f596a..3de503e29 100644
|
||||
--- a/drivers/input/joystick/Makefile
|
||||
+++ b/drivers/input/joystick/Makefile
|
||||
@@ -28,6 +28,7 @@ obj-$(CONFIG_JOYSTICK_N64) += n64joy.o
|
||||
obj-$(CONFIG_JOYSTICK_PSXPAD_SPI) += psxpad-spi.o
|
||||
obj-$(CONFIG_JOYSTICK_PXRC) += pxrc.o
|
||||
obj-$(CONFIG_JOYSTICK_QWIIC) += qwiic-joystick.o
|
||||
+obj-$(CONFIG_JOYSTICK_RSINPUT) += rsinput.o
|
||||
obj-$(CONFIG_JOYSTICK_SEESAW) += adafruit-seesaw.o
|
||||
obj-$(CONFIG_JOYSTICK_SENSEHAT) += sensehat-joystick.o
|
||||
obj-$(CONFIG_JOYSTICK_SIDEWINDER) += sidewinder.o
|
||||
diff --git a/drivers/input/joystick/rsinput.c b/drivers/input/joystick/rsinput.c
|
||||
new file mode 100644
|
||||
index 000000000..8fc0c6ac3
|
||||
--- /dev/null
|
||||
+++ b/drivers/input/joystick/rsinput.c
|
||||
@@ -0,0 +1,423 @@
|
||||
+// SPDX-License-Identifier: GPL-2.0-only
|
||||
+/*
|
||||
+ * RSInput Gamepad Driver
|
||||
+ *
|
||||
+ * Copyright (C) 2024 Teguh Sobirin <teguh@sobir.in>
|
||||
+ *
|
||||
+ */
|
||||
+#define DEBUG
|
||||
+
|
||||
+#include <linux/errno.h>
|
||||
+#include <linux/gpio/consumer.h>
|
||||
+#include <linux/init.h>
|
||||
+#include <linux/input.h>
|
||||
+#include <linux/kernel.h>
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/of.h>
|
||||
+#include <linux/serdev.h>
|
||||
+#include <linux/slab.h>
|
||||
+#include <uapi/linux/sched/types.h>
|
||||
+
|
||||
+#define FRAME_HEAD_1 0xA5
|
||||
+#define FRAME_HEAD_2 0xD3
|
||||
+#define FRAME_HEAD_3 0x5A
|
||||
+#define FRAME_HEAD_4 0x3D
|
||||
+
|
||||
+#define CMD_COMMOD 0x01
|
||||
+#define CMD_STATUS 0x02
|
||||
+
|
||||
+#define DATA_COMMOD_VERSION 0x02
|
||||
+#define DATA_COMMOD_SET_PAR 0x05
|
||||
+
|
||||
+#define FRAME_POS_SEQ 4
|
||||
+#define FRAME_POS_CMD 5
|
||||
+#define FRAME_POS_LEN_L 6
|
||||
+#define FRAME_POS_LEN_H 7
|
||||
+#define FRAME_POS_DATA_1 8
|
||||
+#define FRAME_POS_DATA_2 9
|
||||
+#define FRAME_POS_DATA_3 10
|
||||
+#define FRAME_POS_DATA_4 11
|
||||
+#define FRAME_POS_DATA_5 12
|
||||
+#define FRAME_POS_DATA_6 13
|
||||
+#define FRAME_POS_DATA_7 14
|
||||
+#define FRAME_POS_DATA_8 15
|
||||
+#define FRAME_POS_DATA_9 16
|
||||
+#define FRAME_POS_DATA_10 17
|
||||
+#define FRAME_POS_DATA_11 18
|
||||
+#define FRAME_POS_DATA_12 19
|
||||
+#define FRAME_POS_DATA_13 20
|
||||
+#define FRAME_POS_DATA_14 21
|
||||
+
|
||||
+#define MCU_PKT_SIZE_MIN 9
|
||||
+
|
||||
+#define MCU_VERSION_MAX_LEN 64
|
||||
+
|
||||
+struct rsinput_driver {
|
||||
+ struct serdev_device *serdev;
|
||||
+ struct input_dev *input;
|
||||
+ struct gpio_desc *boot_gpio;
|
||||
+ struct gpio_desc *enable_gpio;
|
||||
+ struct gpio_desc *reset_gpio;
|
||||
+ uint8_t rx_buf[256];
|
||||
+ uint8_t sequence_number;
|
||||
+};
|
||||
+
|
||||
+static const unsigned int keymap[] = {
|
||||
+ BTN_DPAD_UP, BTN_DPAD_DOWN, BTN_DPAD_LEFT, BTN_DPAD_RIGHT,
|
||||
+ BTN_NORTH, BTN_WEST, BTN_EAST, BTN_SOUTH,
|
||||
+ BTN_TL, BTN_TR, BTN_SELECT, BTN_START,
|
||||
+ BTN_THUMBL, BTN_THUMBR, BTN_MODE, BTN_BACK
|
||||
+};
|
||||
+
|
||||
+static uint8_t compute_checksum(const uint8_t *data, size_t len) {
|
||||
+ uint8_t checksum = 0;
|
||||
+
|
||||
+ for (size_t i = FRAME_POS_SEQ; i < len - 1; i++) {
|
||||
+ checksum ^= data[i];
|
||||
+ }
|
||||
+
|
||||
+ return checksum;
|
||||
+}
|
||||
+
|
||||
+static int rsinput_send_command(struct rsinput_driver *drv, uint8_t cmd, const uint8_t *data, size_t len) {
|
||||
+ uint8_t frame[256];
|
||||
+ uint8_t checksum = 0;
|
||||
+ size_t frame_len = 0;
|
||||
+
|
||||
+ frame[frame_len++] = FRAME_HEAD_1;
|
||||
+ frame[frame_len++] = FRAME_HEAD_2;
|
||||
+ frame[frame_len++] = FRAME_HEAD_3;
|
||||
+ frame[frame_len++] = FRAME_HEAD_4;
|
||||
+
|
||||
+ frame[frame_len++] = drv->sequence_number;
|
||||
+ drv->sequence_number++;
|
||||
+
|
||||
+ frame[frame_len++] = cmd;
|
||||
+
|
||||
+ frame[frame_len++] = len & 0xFF;
|
||||
+ frame[frame_len++] = (len >> 8) & 0xFF;
|
||||
+
|
||||
+ if (data && len) {
|
||||
+ memcpy(&frame[frame_len], data, len);
|
||||
+ frame_len += len;
|
||||
+ }
|
||||
+
|
||||
+ checksum = compute_checksum(frame, frame_len + 1);
|
||||
+ frame[frame_len++] = checksum;
|
||||
+
|
||||
+ return serdev_device_write_buf(drv->serdev, frame, frame_len);
|
||||
+}
|
||||
+
|
||||
+static int rsinput_init_commands(struct rsinput_driver *drv) {
|
||||
+ int error;
|
||||
+
|
||||
+ msleep(100);
|
||||
+ uint8_t version_request[] = {DATA_COMMOD_VERSION};
|
||||
+ error = rsinput_send_command(drv, CMD_COMMOD, version_request, sizeof(version_request));
|
||||
+ if (error < 0) {
|
||||
+ dev_err(&drv->serdev->dev, "Failed to request MCU version: %d\n", error);
|
||||
+ return error;
|
||||
+ }
|
||||
+
|
||||
+ msleep(100);
|
||||
+ uint8_t mcu_params[] = {
|
||||
+ DATA_COMMOD_SET_PAR,
|
||||
+ 0x01,
|
||||
+ 0x00, 0x00, 0x00, 0x28,
|
||||
+ 0x00, 0x00, 0x00, 0x07
|
||||
+ };
|
||||
+ error = rsinput_send_command(drv, CMD_COMMOD, mcu_params, sizeof(mcu_params));
|
||||
+ if (error < 0) {
|
||||
+ dev_err(&drv->serdev->dev, "Failed to set MCU parameters: %d\n", error);
|
||||
+ return error;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static void handle_cmd_commod(struct rsinput_driver *drv, const uint8_t *data, size_t payload_length) {
|
||||
+ switch (data[FRAME_POS_DATA_1]) {
|
||||
+ case DATA_COMMOD_VERSION:
|
||||
+ if (payload_length >= 1) {
|
||||
+ char mcu_version[MCU_VERSION_MAX_LEN] = {0};
|
||||
+ size_t version_length = payload_length;
|
||||
+ if (version_length > MCU_VERSION_MAX_LEN - 1) {
|
||||
+ version_length = MCU_VERSION_MAX_LEN - 1;
|
||||
+ }
|
||||
+ memcpy(mcu_version, &data[FRAME_POS_DATA_1], version_length);
|
||||
+ mcu_version[version_length] = '\0';
|
||||
+ dev_info(&drv->serdev->dev, "MCU Version: %s\n", mcu_version);
|
||||
+ } else {
|
||||
+ dev_err(&drv->serdev->dev, "Invalid MCU version response length\n");
|
||||
+ }
|
||||
+ break;
|
||||
+ case DATA_COMMOD_SET_PAR:
|
||||
+ dev_info(&drv->serdev->dev, "MCU parameters set successfully\n");
|
||||
+ break;
|
||||
+ default:
|
||||
+ dev_warn(&drv->serdev->dev, "Unhandled CMD_COMMOD sub-command: 0x%02x\n", data[FRAME_POS_DATA_1]);
|
||||
+ break;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+static void handle_cmd_status(struct rsinput_driver *drv, const uint8_t *data, size_t payload_length) {
|
||||
+ if (payload_length >= 6) {
|
||||
+ static unsigned long prev_states;
|
||||
+ unsigned long keys = data[FRAME_POS_DATA_1] | (data[FRAME_POS_DATA_2] << 8);
|
||||
+ unsigned long current_states = keys, changes;
|
||||
+ int i;
|
||||
+
|
||||
+ bitmap_xor(&changes, ¤t_states, &prev_states, ARRAY_SIZE(keymap));
|
||||
+
|
||||
+ for_each_set_bit(i, &changes, ARRAY_SIZE(keymap)) {
|
||||
+ input_report_key(drv->input, keymap[i], (current_states & BIT(i)));
|
||||
+ }
|
||||
+
|
||||
+ input_report_abs(drv->input, ABS_HAT2X,
|
||||
+ 0x650 - (data[FRAME_POS_DATA_3] | (data[FRAME_POS_DATA_4] << 8)));
|
||||
+ input_report_abs(drv->input, ABS_HAT2Y,
|
||||
+ 0x650 - (data[FRAME_POS_DATA_5] | (data[FRAME_POS_DATA_6] << 8)));
|
||||
+ input_report_abs(drv->input, ABS_X,
|
||||
+ -(int16_t)(data[FRAME_POS_DATA_7] | (data[FRAME_POS_DATA_8] << 8)));
|
||||
+ input_report_abs(drv->input, ABS_Y,
|
||||
+ -(int16_t)(data[FRAME_POS_DATA_9] | (data[FRAME_POS_DATA_10] << 8)));
|
||||
+ input_report_abs(drv->input, ABS_RX,
|
||||
+ -(int16_t)(data[FRAME_POS_DATA_11] | (data[FRAME_POS_DATA_12] << 8)));
|
||||
+ input_report_abs(drv->input, ABS_RY,
|
||||
+ -(int16_t)(data[FRAME_POS_DATA_13] | (data[FRAME_POS_DATA_14] << 8)));
|
||||
+
|
||||
+ input_sync(drv->input);
|
||||
+ prev_states = keys;
|
||||
+
|
||||
+ } else {
|
||||
+ dev_warn(&drv->serdev->dev, "Invalid CMD_STATUS response length\n");
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+static void rsinput_process_data(struct rsinput_driver *drv, const uint8_t *data, size_t len) {
|
||||
+ while (len >= MCU_PKT_SIZE_MIN) {
|
||||
+ uint16_t payload_length = data[FRAME_POS_LEN_L] | (data[FRAME_POS_LEN_H] << 8);
|
||||
+ size_t frame_length = MCU_PKT_SIZE_MIN + payload_length;
|
||||
+
|
||||
+ if (len < frame_length) {
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ uint8_t received_checksum = data[frame_length - 1];
|
||||
+ uint8_t computed_checksum = compute_checksum(data, frame_length);
|
||||
+ if (computed_checksum != received_checksum) {
|
||||
+ data += frame_length;
|
||||
+ len -= frame_length;
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ switch (data[FRAME_POS_CMD]) {
|
||||
+ case CMD_COMMOD:
|
||||
+ handle_cmd_commod(drv, data, payload_length);
|
||||
+ break;
|
||||
+ case CMD_STATUS:
|
||||
+ handle_cmd_status(drv, data, payload_length);
|
||||
+ break;
|
||||
+ default:
|
||||
+ dev_warn(&drv->serdev->dev, "Unhandled command: 0x%02X\n", data[FRAME_POS_CMD]);
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
+ data += frame_length;
|
||||
+ len -= frame_length;
|
||||
+ }
|
||||
+
|
||||
+ if (len > 0) {
|
||||
+ dev_warn(&drv->serdev->dev, "Trailing bytes after processing: %zu\n", len);
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+static size_t rsinput_rx(struct serdev_device *serdev, const u8 *data, size_t count) {
|
||||
+ struct rsinput_driver *drv = serdev_device_get_drvdata(serdev);
|
||||
+ uint8_t received_checksum, computed_checksum;
|
||||
+
|
||||
+ if (!drv || !data || count == 0) {
|
||||
+ dev_warn_ratelimited(&serdev->dev, "Invalid RX data\n");
|
||||
+ goto error;
|
||||
+ }
|
||||
+
|
||||
+ if (count > sizeof(drv->rx_buf)) {
|
||||
+ dev_warn_ratelimited(&serdev->dev, "RX buffer overflow\n");
|
||||
+ goto error;
|
||||
+ }
|
||||
+
|
||||
+ memcpy(drv->rx_buf, data, count);
|
||||
+
|
||||
+ if (count < MCU_PKT_SIZE_MIN) {
|
||||
+ dev_warn_ratelimited(&serdev->dev, "Frame too short for checksum validation\n");
|
||||
+ goto error;
|
||||
+ }
|
||||
+
|
||||
+ received_checksum = drv->rx_buf[count - 1];
|
||||
+
|
||||
+ computed_checksum = compute_checksum(drv->rx_buf, count);
|
||||
+
|
||||
+ if (computed_checksum != received_checksum) {
|
||||
+ rsinput_init_commands(drv);
|
||||
+ goto error;
|
||||
+ }
|
||||
+
|
||||
+ rsinput_process_data(drv, drv->rx_buf, count);
|
||||
+
|
||||
+error:
|
||||
+ return count;
|
||||
+}
|
||||
+
|
||||
+static const struct serdev_device_ops rsinput_rx_ops = {
|
||||
+ .receive_buf = rsinput_rx,
|
||||
+};
|
||||
+
|
||||
+static int rsinput_probe(struct serdev_device *serdev) {
|
||||
+ struct rsinput_driver *drv;
|
||||
+ u32 gamepad_bus = 0;
|
||||
+ u32 gamepad_vid = 0;
|
||||
+ u32 gamepad_pid = 0;
|
||||
+ u32 gamepad_rev = 0;
|
||||
+ int error;
|
||||
+
|
||||
+ drv = devm_kzalloc(&serdev->dev, sizeof(*drv), GFP_KERNEL);
|
||||
+ if (!drv)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ drv->boot_gpio =
|
||||
+ devm_gpiod_get_optional(&serdev->dev, "boot", GPIOD_OUT_HIGH);
|
||||
+ if (IS_ERR(drv->boot_gpio)) {
|
||||
+ error = PTR_ERR(drv->boot_gpio);
|
||||
+ dev_warn(&serdev->dev, "Unable to get boot gpio: %d\n", error);
|
||||
+ }
|
||||
+
|
||||
+ drv->enable_gpio =
|
||||
+ devm_gpiod_get_optional(&serdev->dev, "enable", GPIOD_OUT_HIGH);
|
||||
+ if (IS_ERR(drv->enable_gpio)) {
|
||||
+ error = PTR_ERR(drv->enable_gpio);
|
||||
+ dev_warn(&serdev->dev, "Unable to get enable gpio: %d\n", error);
|
||||
+ }
|
||||
+
|
||||
+ drv->reset_gpio =
|
||||
+ devm_gpiod_get_optional(&serdev->dev, "reset", GPIOD_OUT_HIGH);
|
||||
+ if (IS_ERR(drv->reset_gpio)) {
|
||||
+ error = PTR_ERR(drv->reset_gpio);
|
||||
+ dev_warn(&serdev->dev, "Unable to get reset gpio: %d\n", error);
|
||||
+ }
|
||||
+
|
||||
+ if (drv->boot_gpio)
|
||||
+ gpiod_set_value_cansleep(drv->boot_gpio, 0);
|
||||
+
|
||||
+ if (drv->reset_gpio)
|
||||
+ gpiod_set_value_cansleep(drv->reset_gpio, 0);
|
||||
+
|
||||
+ msleep(100);
|
||||
+
|
||||
+ if (drv->enable_gpio)
|
||||
+ gpiod_set_value_cansleep(drv->enable_gpio, 1);
|
||||
+
|
||||
+ if (drv->reset_gpio)
|
||||
+ gpiod_set_value_cansleep(drv->reset_gpio, 1);
|
||||
+
|
||||
+ msleep(100);
|
||||
+
|
||||
+ serdev_device_set_client_ops(serdev, &rsinput_rx_ops);
|
||||
+ error = serdev_device_open(serdev);
|
||||
+ if (error)
|
||||
+ return dev_err_probe(&serdev->dev, error, "Unable to open UART device\n");
|
||||
+
|
||||
+ drv->serdev = serdev;
|
||||
+ drv->sequence_number = 0;
|
||||
+
|
||||
+ serdev_device_set_drvdata(serdev, drv);
|
||||
+
|
||||
+ error = serdev_device_set_baudrate(serdev, 115200);
|
||||
+ if (error < 0)
|
||||
+ goto err_close;
|
||||
+
|
||||
+ serdev_device_set_flow_control(serdev, false);
|
||||
+
|
||||
+ drv->input = devm_input_allocate_device(&serdev->dev);
|
||||
+ if (!drv->input) {
|
||||
+ error = -ENOMEM;
|
||||
+ goto err_close;
|
||||
+ }
|
||||
+
|
||||
+ drv->input->phys = "rsinput-gamepad/input0";
|
||||
+
|
||||
+ error = device_property_read_string(&serdev->dev, "gamepad-name", &drv->input->name);
|
||||
+ if (error) {
|
||||
+ drv->input->name = "RSInput Gamepad";
|
||||
+ }
|
||||
+
|
||||
+ device_property_read_u32(&serdev->dev, "gamepad-bus", &gamepad_bus);
|
||||
+ device_property_read_u32(&serdev->dev, "gamepad-vid", &gamepad_vid);
|
||||
+ device_property_read_u32(&serdev->dev, "gamepad-pid", &gamepad_pid);
|
||||
+ device_property_read_u32(&serdev->dev, "gamepad-rev", &gamepad_rev);
|
||||
+
|
||||
+ drv->input->id.bustype = (u16)gamepad_bus;
|
||||
+ drv->input->id.vendor = (u16)gamepad_vid;
|
||||
+ drv->input->id.product = (u16)gamepad_pid;
|
||||
+ drv->input->id.version = (u16)gamepad_rev;
|
||||
+
|
||||
+ __set_bit(EV_KEY, drv->input->evbit);
|
||||
+ for (int i = 0; i < ARRAY_SIZE(keymap); i++)
|
||||
+ input_set_capability(drv->input, EV_KEY, keymap[i]);
|
||||
+
|
||||
+ __set_bit(EV_ABS, drv->input->evbit);
|
||||
+ for (int i = ABS_X; i <= ABS_RZ; i++)
|
||||
+ input_set_abs_params(drv->input, i, -0x580, 0x580,
|
||||
+ 0, 0);
|
||||
+
|
||||
+ input_set_abs_params(drv->input, ABS_HAT2X, 0, 1830, 0, 30);
|
||||
+ input_set_abs_params(drv->input, ABS_HAT2Y, 0, 1830, 0, 30);
|
||||
+
|
||||
+ error = input_register_device(drv->input);
|
||||
+ if (error) {
|
||||
+ goto err_close;
|
||||
+ }
|
||||
+
|
||||
+ error = rsinput_init_commands(drv);
|
||||
+ if (error < 0) {
|
||||
+ goto err_close;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+
|
||||
+err_close:
|
||||
+ serdev_device_close(serdev);
|
||||
+ return error;
|
||||
+}
|
||||
+
|
||||
+static void rsinput_remove(struct serdev_device *serdev) {
|
||||
+ struct rsinput_driver *drv = serdev_device_get_drvdata(serdev);
|
||||
+
|
||||
+ serdev_device_close(serdev);
|
||||
+ input_unregister_device(drv->input);
|
||||
+ if (drv->enable_gpio)
|
||||
+ gpiod_set_value_cansleep(drv->enable_gpio, 0);
|
||||
+
|
||||
+ if (drv->reset_gpio)
|
||||
+ gpiod_set_value_cansleep(drv->reset_gpio, 0);
|
||||
+}
|
||||
+
|
||||
+static const struct of_device_id rsinput_of_match[] = {
|
||||
+ { .compatible = "gamepad,rsinput" },
|
||||
+ { /* sentinel */ }
|
||||
+};
|
||||
+MODULE_DEVICE_TABLE(of, rsinput_of_match);
|
||||
+
|
||||
+static struct serdev_device_driver rsinput_driver = {
|
||||
+ .probe = rsinput_probe,
|
||||
+ .remove = rsinput_remove,
|
||||
+ .driver = {
|
||||
+ .name = "rsinput",
|
||||
+ .of_match_table = rsinput_of_match,
|
||||
+ },
|
||||
+};
|
||||
+
|
||||
+module_serdev_device_driver(rsinput_driver);
|
||||
+
|
||||
+MODULE_LICENSE("GPL");
|
||||
+MODULE_DESCRIPTION("RSInput Gamepad Driver");
|
||||
+MODULE_AUTHOR("Teguh Sobirin <teguh@sobir.in>");
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@ -0,0 +1,565 @@
|
||||
From e795cc2b164408cc92c11f96be268a689c7b4c1a Mon Sep 17 00:00:00 2001
|
||||
From: Teguh Sobirin <teguh@sobir.in>
|
||||
Date: Thu, 13 Feb 2025 18:25:19 +0800
|
||||
Subject: [PATCH 03/18] drm/panel: Add panel driver for Chipone ICNA3512 based
|
||||
panels
|
||||
|
||||
Signed-off-by: Teguh Sobirin <teguh@sobir.in>
|
||||
---
|
||||
drivers/gpu/drm/panel/Kconfig | 11 +
|
||||
drivers/gpu/drm/panel/Makefile | 1 +
|
||||
.../gpu/drm/panel/panel-chipone-icna3512.c | 473 ++++++++++++++++++
|
||||
include/drm/drm_mipi_dsi.h | 22 +
|
||||
4 files changed, 507 insertions(+)
|
||||
create mode 100644 drivers/gpu/drm/panel/panel-chipone-icna3512.c
|
||||
|
||||
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
|
||||
index 407c5f6a2..d8cf15b0b 100644
|
||||
--- a/drivers/gpu/drm/panel/Kconfig
|
||||
+++ b/drivers/gpu/drm/panel/Kconfig
|
||||
@@ -105,6 +105,17 @@ config DRM_PANEL_BOE_TV101WUM_LL2
|
||||
Say Y here if you want to support for BOE TV101WUM-LL2
|
||||
WUXGA PANEL DSI Video Mode panel
|
||||
|
||||
+config DRM_PANEL_CHIPONE_ICNA3512
|
||||
+ tristate "Chipone ICNA3512 panel driver"
|
||||
+ depends on OF
|
||||
+ depends on DRM_MIPI_DSI
|
||||
+ depends on BACKLIGHT_CLASS_DEVICE
|
||||
+ select DRM_DISPLAY_HELPER
|
||||
+ help
|
||||
+ Say Y here if you want to enable support for the panels built
|
||||
+ around the Chipone ICNA3512 display controller, such as some
|
||||
+ Tianma panels used in AYN Odin2 Portal.
|
||||
+
|
||||
config DRM_PANEL_EBBG_FT8719
|
||||
tristate "EBBG FT8719 panel driver"
|
||||
depends on OF
|
||||
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
|
||||
index 3615a761b..171843de9 100644
|
||||
--- a/drivers/gpu/drm/panel/Makefile
|
||||
+++ b/drivers/gpu/drm/panel/Makefile
|
||||
@@ -9,6 +9,7 @@ obj-$(CONFIG_DRM_PANEL_BOE_TD4320) += panel-boe-td4320.o
|
||||
obj-$(CONFIG_DRM_PANEL_BOE_TH101MB31UIG002_28A) += panel-boe-th101mb31ig002-28a.o
|
||||
obj-$(CONFIG_DRM_PANEL_BOE_TV101WUM_LL2) += panel-boe-tv101wum-ll2.o
|
||||
obj-$(CONFIG_DRM_PANEL_BOE_TV101WUM_NL6) += panel-boe-tv101wum-nl6.o
|
||||
+obj-$(CONFIG_DRM_PANEL_CHIPONE_ICNA3512) += panel-chipone-icna3512.o
|
||||
obj-$(CONFIG_DRM_PANEL_DSI_CM) += panel-dsi-cm.o
|
||||
obj-$(CONFIG_DRM_PANEL_LVDS) += panel-lvds.o
|
||||
obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.o
|
||||
diff --git a/drivers/gpu/drm/panel/panel-chipone-icna3512.c b/drivers/gpu/drm/panel/panel-chipone-icna3512.c
|
||||
new file mode 100644
|
||||
index 000000000..cbda976df
|
||||
--- /dev/null
|
||||
+++ b/drivers/gpu/drm/panel/panel-chipone-icna3512.c
|
||||
@@ -0,0 +1,473 @@
|
||||
+// SPDX-License-Identifier: GPL-2.0-only
|
||||
+/*
|
||||
+ * Chipone ICNA3512 Driver IC panels driver
|
||||
+ *
|
||||
+ * Copyright (c) 2025 Teguh Sobirin <teguh@sobir.in>
|
||||
+ */
|
||||
+
|
||||
+#include <linux/backlight.h>
|
||||
+#include <linux/delay.h>
|
||||
+#include <linux/gpio/consumer.h>
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/of.h>
|
||||
+#include <linux/of_graph.h>
|
||||
+#include <linux/regulator/consumer.h>
|
||||
+
|
||||
+#include <video/mipi_display.h>
|
||||
+
|
||||
+#include <drm/display/drm_dsc.h>
|
||||
+#include <drm/display/drm_dsc_helper.h>
|
||||
+#include <drm/drm_connector.h>
|
||||
+#include <drm/drm_crtc.h>
|
||||
+#include <drm/drm_mipi_dsi.h>
|
||||
+#include <drm/drm_modes.h>
|
||||
+#include <drm/drm_panel.h>
|
||||
+
|
||||
+struct panel_info {
|
||||
+ struct drm_panel panel;
|
||||
+ struct drm_connector *connector;
|
||||
+ struct mipi_dsi_device *dsi;
|
||||
+ struct panel_desc *desc;
|
||||
+ enum drm_panel_orientation orientation;
|
||||
+
|
||||
+ struct gpio_desc *reset_gpio;
|
||||
+ struct regulator_bulk_data supplies[3];
|
||||
+};
|
||||
+
|
||||
+struct panel_desc {
|
||||
+ unsigned int width_mm;
|
||||
+ unsigned int height_mm;
|
||||
+
|
||||
+ unsigned int bpc;
|
||||
+ unsigned int lanes;
|
||||
+ unsigned long mode_flags;
|
||||
+ enum mipi_dsi_pixel_format format;
|
||||
+
|
||||
+ const struct drm_display_mode *modes;
|
||||
+ unsigned int num_modes;
|
||||
+ int (*init_sequence)(struct panel_info *pinfo);
|
||||
+
|
||||
+ struct drm_dsc_config dsc;
|
||||
+};
|
||||
+
|
||||
+static inline struct panel_info *to_panel_info(struct drm_panel *panel)
|
||||
+{
|
||||
+ return container_of(panel, struct panel_info, panel);
|
||||
+}
|
||||
+
|
||||
+static int icna3512_get_current_mode(struct panel_info *pinfo)
|
||||
+{
|
||||
+ struct drm_connector *connector = pinfo->connector;
|
||||
+ struct drm_crtc_state *crtc_state;
|
||||
+ int i;
|
||||
+
|
||||
+ /* Return the default (first) mode if no info available yet */
|
||||
+ if (!connector->state || !connector->state->crtc)
|
||||
+ return 0;
|
||||
+
|
||||
+ crtc_state = connector->state->crtc->state;
|
||||
+
|
||||
+ for (i = 0; i < pinfo->desc->num_modes; i++) {
|
||||
+ if (drm_mode_match(&crtc_state->mode,
|
||||
+ &pinfo->desc->modes[i],
|
||||
+ DRM_MODE_MATCH_TIMINGS | DRM_MODE_MATCH_CLOCK))
|
||||
+ return i;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int icna3512_init_sequence(struct panel_info *pinfo)
|
||||
+{
|
||||
+ struct mipi_dsi_device *dsi = pinfo->dsi;
|
||||
+ struct device *dev = &dsi->dev;
|
||||
+ int ret;
|
||||
+
|
||||
+ int cur_mode = icna3512_get_current_mode(pinfo);
|
||||
+ int cur_vrefresh = drm_mode_vrefresh(&pinfo->desc->modes[cur_mode]);
|
||||
+
|
||||
+ mipi_dsi_dcs_write_seq(dsi, 0x9F, 0x01);
|
||||
+ if (cur_vrefresh == 120) {
|
||||
+
|
||||
+ mipi_dsi_dcs_write_seq(dsi, 0xB3,
|
||||
+ 0x00, 0xE0, 0xA0, 0x10, 0xC8, 0x00, 0x02, 0x83,
|
||||
+ 0x00, 0x10, 0x14, 0x00, 0x00, 0xC3, 0x00, 0x10,
|
||||
+ 0x14, 0x00, 0x00, 0xE0, 0x10, 0x10, 0x9C, 0x00,
|
||||
+ 0x00, 0xE0, 0xA0, 0x10, 0xC8, 0x22, 0x18, 0x18,
|
||||
+ 0x18, 0x18, 0x18);
|
||||
+ mipi_dsi_dcs_write_seq(dsi, 0x9F, 0x07);
|
||||
+ mipi_dsi_dcs_write_seq(dsi, 0xB5,
|
||||
+ 0x04, 0x0C, 0x08, 0x0C, 0x04, 0x00, 0xC4);
|
||||
+ mipi_dsi_dcs_write_seq(dsi, 0xD9,
|
||||
+ 0x88, 0x40, 0x40, 0x88, 0x40, 0x40, 0x00, 0xEB,
|
||||
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
|
||||
+ mipi_dsi_dcs_write_seq(dsi, 0xCE,
|
||||
+ 0x01, 0x01, 0x01, 0x01, 0x04, 0x09, 0x2C);
|
||||
+ mipi_dsi_dcs_write_seq(dsi, 0x48, 0x00);
|
||||
+ mipi_dsi_dcs_write_seq(dsi, 0x48, 0x30);
|
||||
+ }
|
||||
+ else {
|
||||
+ mipi_dsi_dcs_write_seq(dsi, 0xB3,
|
||||
+ 0x00, 0xE0, 0xA0, 0x10, 0xC8, 0x00);
|
||||
+ mipi_dsi_dcs_write_seq(dsi, 0x9F, 0x07);
|
||||
+ mipi_dsi_dcs_write_seq(dsi, 0xB2,
|
||||
+ 0x04, 0x18, 0x08, 0x0C, 0x02, 0x00, 0xC4);
|
||||
+ mipi_dsi_dcs_write_seq(dsi, 0xD3,
|
||||
+ 0x88, 0x4A, 0x4A, 0x88, 0x4A, 0x4A, 0x00, 0xEB,
|
||||
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
|
||||
+ mipi_dsi_dcs_write_seq(dsi, 0xCB,
|
||||
+ 0x01, 0x01, 0x01, 0x01, 0x04, 0x09, 0x2C);
|
||||
+ mipi_dsi_dcs_write_seq(dsi, 0x48, 0x30);
|
||||
+ mipi_dsi_dcs_write_seq(dsi, 0x48, 0x00);
|
||||
+ }
|
||||
+
|
||||
+ mipi_dsi_dcs_write_seq(dsi, 0x9C, 0xA5, 0xA5);
|
||||
+ mipi_dsi_dcs_write_seq(dsi, 0xFD, 0x5A, 0x5A);
|
||||
+ mipi_dsi_dcs_write_seq(dsi, 0x48, 0x00);
|
||||
+ mipi_dsi_dcs_write_seq(dsi, 0x53, 0xE0);
|
||||
+ mipi_dsi_dcs_write_seq(dsi, 0x35, 0x00);
|
||||
+
|
||||
+ ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
|
||||
+ if (ret < 0) {
|
||||
+ dev_err(dev, "failed to exit sleep mode: %d\n", ret);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ mipi_dsi_dcs_write_seq(dsi, 0x51, 0x0D, 0xBB);
|
||||
+ mipi_dsi_dcs_write_seq(dsi, 0x9F, 0x0F);
|
||||
+ mipi_dsi_dcs_write_seq(dsi, 0xCE, 0x22);
|
||||
+
|
||||
+ msleep(120);
|
||||
+
|
||||
+ ret = mipi_dsi_dcs_set_display_on(dsi);
|
||||
+ if (ret < 0) {
|
||||
+ dev_err(dev, "failed to set display on: %d\n", ret);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static const struct drm_display_mode icna3512_modes[] = {
|
||||
+ {
|
||||
+ /* 120Hz */
|
||||
+ .clock = (1080 + 156 + 1 + 23) * (1920 + 412 + 1 + 15) * 120 / 1000,
|
||||
+ .hdisplay = 1080,
|
||||
+ .hsync_start = 1080 + 156,
|
||||
+ .hsync_end = 1080 + 156 + 1,
|
||||
+ .htotal = 1080 + 156 + 1 + 23,
|
||||
+ .vdisplay = 1920,
|
||||
+ .vsync_start = 1920 + 412,
|
||||
+ .vsync_end = 1920 + 412 + 1,
|
||||
+ .vtotal = 1920 + 412 + 1 + 15,
|
||||
+ },
|
||||
+ {
|
||||
+ /* 60Hz */
|
||||
+ .clock = (1080 + 156 + 1 + 23) * (1920 + 2760 + 1 + 15) * 60 / 1000,
|
||||
+ .hdisplay = 1080,
|
||||
+ .hsync_start = 1080 + 156,
|
||||
+ .hsync_end = 1080 + 156 + 1,
|
||||
+ .htotal = 1080 + 156 + 1 + 23,
|
||||
+ .vdisplay = 1920,
|
||||
+ .vsync_start = 1920 + 2760,
|
||||
+ .vsync_end = 1920 + 2760 + 1,
|
||||
+ .vtotal = 1920 + 2760 + 1 + 15,
|
||||
+ }
|
||||
+};
|
||||
+
|
||||
+static struct panel_desc icna3512_desc = {
|
||||
+ .modes = icna3512_modes,
|
||||
+ .num_modes = ARRAY_SIZE(icna3512_modes),
|
||||
+ .width_mm = 160,
|
||||
+ .height_mm = 89,
|
||||
+ .bpc = 8,
|
||||
+ .lanes = 4,
|
||||
+ .format = MIPI_DSI_FMT_RGB888,
|
||||
+ .mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS | MIPI_DSI_MODE_LPM,
|
||||
+ .init_sequence = icna3512_init_sequence,
|
||||
+ .dsc = {
|
||||
+ .dsc_version_major = 0x1,
|
||||
+ .dsc_version_minor = 0x1,
|
||||
+ .slice_height = 20,
|
||||
+ .slice_width = 540,
|
||||
+ .slice_count = 2,
|
||||
+ .bits_per_component = 8,
|
||||
+ .bits_per_pixel = 8 << 4,
|
||||
+ .block_pred_enable = true,
|
||||
+ },
|
||||
+};
|
||||
+
|
||||
+static void icna3512_reset(struct panel_info *pinfo)
|
||||
+{
|
||||
+ gpiod_set_value_cansleep(pinfo->reset_gpio, 0);
|
||||
+ usleep_range(10000, 11000);
|
||||
+ gpiod_set_value_cansleep(pinfo->reset_gpio, 1);
|
||||
+ usleep_range(10000, 11000);
|
||||
+ gpiod_set_value_cansleep(pinfo->reset_gpio, 0);
|
||||
+ usleep_range(10000, 11000);
|
||||
+}
|
||||
+
|
||||
+static int icna3512_prepare(struct drm_panel *panel)
|
||||
+{
|
||||
+ struct panel_info *pinfo = to_panel_info(panel);
|
||||
+ struct drm_dsc_picture_parameter_set pps;
|
||||
+ int ret;
|
||||
+
|
||||
+ ret = regulator_bulk_enable(ARRAY_SIZE(pinfo->supplies), pinfo->supplies);
|
||||
+ if (ret < 0) {
|
||||
+ dev_err(panel->dev, "failed to enable regulators: %d\n", ret);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ icna3512_reset(pinfo);
|
||||
+
|
||||
+ ret = pinfo->desc->init_sequence(pinfo);
|
||||
+ if (ret < 0) {
|
||||
+ regulator_bulk_disable(ARRAY_SIZE(pinfo->supplies), pinfo->supplies);
|
||||
+ dev_err(panel->dev, "failed to initialize panel: %d\n", ret);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ drm_dsc_pps_payload_pack(&pps, &pinfo->desc->dsc);
|
||||
+
|
||||
+ ret = mipi_dsi_picture_parameter_set(pinfo->dsi, &pps);
|
||||
+ if (ret < 0) {
|
||||
+ dev_err(panel->dev, "failed to transmit PPS: %d\n", ret);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ /* Not required, ICNA3512 has DSC always enabled. */
|
||||
+ ret = mipi_dsi_compression_mode(pinfo->dsi, true);
|
||||
+ if (ret < 0) {
|
||||
+ dev_err(panel->dev, "failed to enable compression mode: %d\n", ret);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int icna3512_disable(struct drm_panel *panel)
|
||||
+{
|
||||
+ struct panel_info *pinfo = to_panel_info(panel);
|
||||
+ int ret;
|
||||
+
|
||||
+ ret = mipi_dsi_dcs_set_display_off(pinfo->dsi);
|
||||
+ if (ret < 0)
|
||||
+ dev_err(&pinfo->dsi->dev, "failed to set display off: %d\n", ret);
|
||||
+
|
||||
+ msleep(50);
|
||||
+
|
||||
+ ret = mipi_dsi_dcs_enter_sleep_mode(pinfo->dsi);
|
||||
+ if (ret < 0)
|
||||
+ dev_err(&pinfo->dsi->dev, "failed to enter sleep mode: %d\n", ret);
|
||||
+
|
||||
+ msleep(120);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int icna3512_unprepare(struct drm_panel *panel)
|
||||
+{
|
||||
+ struct panel_info *pinfo = to_panel_info(panel);
|
||||
+
|
||||
+ gpiod_set_value_cansleep(pinfo->reset_gpio, 1);
|
||||
+ regulator_bulk_disable(ARRAY_SIZE(pinfo->supplies), pinfo->supplies);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static void icna3512_remove(struct mipi_dsi_device *dsi)
|
||||
+{
|
||||
+ struct panel_info *pinfo = mipi_dsi_get_drvdata(dsi);
|
||||
+ int ret;
|
||||
+
|
||||
+ ret = mipi_dsi_detach(pinfo->dsi);
|
||||
+ if (ret < 0)
|
||||
+ dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", ret);
|
||||
+
|
||||
+ drm_panel_remove(&pinfo->panel);
|
||||
+}
|
||||
+
|
||||
+static int icna3512_get_modes(struct drm_panel *panel,
|
||||
+ struct drm_connector *connector)
|
||||
+{
|
||||
+ struct panel_info *pinfo = to_panel_info(panel);
|
||||
+ int i;
|
||||
+
|
||||
+ for (i = 0; i < pinfo->desc->num_modes; i++) {
|
||||
+ const struct drm_display_mode *m = &pinfo->desc->modes[i];
|
||||
+ struct drm_display_mode *mode;
|
||||
+
|
||||
+ mode = drm_mode_duplicate(connector->dev, m);
|
||||
+ if (!mode) {
|
||||
+ dev_err(panel->dev, "failed to add mode %ux%u@%u\n",
|
||||
+ m->hdisplay, m->vdisplay, drm_mode_vrefresh(m));
|
||||
+ return -ENOMEM;
|
||||
+ }
|
||||
+
|
||||
+ mode->type = DRM_MODE_TYPE_DRIVER;
|
||||
+ if (i == 0)
|
||||
+ mode->type |= DRM_MODE_TYPE_PREFERRED;
|
||||
+
|
||||
+ drm_mode_set_name(mode);
|
||||
+ drm_mode_probed_add(connector, mode);
|
||||
+ }
|
||||
+
|
||||
+ connector->display_info.width_mm = pinfo->desc->width_mm;
|
||||
+ connector->display_info.height_mm = pinfo->desc->height_mm;
|
||||
+ connector->display_info.bpc = pinfo->desc->bpc;
|
||||
+ pinfo->connector = connector;
|
||||
+
|
||||
+ return pinfo->desc->num_modes;
|
||||
+}
|
||||
+
|
||||
+static enum drm_panel_orientation icna3512_get_orientation(struct drm_panel *panel)
|
||||
+{
|
||||
+ struct panel_info *pinfo = to_panel_info(panel);
|
||||
+
|
||||
+ return pinfo->orientation;
|
||||
+}
|
||||
+
|
||||
+static const struct drm_panel_funcs icna3512_panel_funcs = {
|
||||
+ .disable = icna3512_disable,
|
||||
+ .prepare = icna3512_prepare,
|
||||
+ .unprepare = icna3512_unprepare,
|
||||
+ .get_modes = icna3512_get_modes,
|
||||
+ .get_orientation = icna3512_get_orientation,
|
||||
+};
|
||||
+
|
||||
+static int icna3512_bl_update_status(struct backlight_device *bl)
|
||||
+{
|
||||
+ struct mipi_dsi_device *dsi = bl_get_data(bl);
|
||||
+ u16 brightness = backlight_get_brightness(bl);
|
||||
+ int ret;
|
||||
+
|
||||
+ dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
|
||||
+
|
||||
+ ret = mipi_dsi_dcs_set_display_brightness_large(dsi, brightness);
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+
|
||||
+ dsi->mode_flags |= MIPI_DSI_MODE_LPM;
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int icna3512_bl_get_brightness(struct backlight_device *bl)
|
||||
+{
|
||||
+ struct mipi_dsi_device *dsi = bl_get_data(bl);
|
||||
+ u16 brightness;
|
||||
+ int ret;
|
||||
+
|
||||
+ dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
|
||||
+
|
||||
+ ret = mipi_dsi_dcs_get_display_brightness_large(dsi, &brightness);
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+
|
||||
+ dsi->mode_flags |= MIPI_DSI_MODE_LPM;
|
||||
+
|
||||
+ return brightness;
|
||||
+}
|
||||
+
|
||||
+static const struct backlight_ops icna3512_bl_ops = {
|
||||
+ .update_status = icna3512_bl_update_status,
|
||||
+ .get_brightness = icna3512_bl_get_brightness,
|
||||
+};
|
||||
+
|
||||
+static struct backlight_device *icna3512_create_backlight(struct mipi_dsi_device *dsi)
|
||||
+{
|
||||
+ struct device *dev = &dsi->dev;
|
||||
+ const struct backlight_properties props = {
|
||||
+ .type = BACKLIGHT_RAW,
|
||||
+ .brightness = 4096,
|
||||
+ .max_brightness = 4096,
|
||||
+ };
|
||||
+
|
||||
+ return devm_backlight_device_register(dev, dev_name(dev), dev, dsi,
|
||||
+ &icna3512_bl_ops, &props);
|
||||
+}
|
||||
+
|
||||
+static int icna3512_probe(struct mipi_dsi_device *dsi)
|
||||
+{
|
||||
+ struct device *dev = &dsi->dev;
|
||||
+ struct panel_info *pinfo;
|
||||
+ int ret;
|
||||
+
|
||||
+ pinfo = devm_kzalloc(dev, sizeof(*pinfo), GFP_KERNEL);
|
||||
+ if (!pinfo)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ pinfo->supplies[0].supply = "blvdd";
|
||||
+ pinfo->supplies[1].supply = "iovdd";
|
||||
+ pinfo->supplies[2].supply = "vdd";
|
||||
+
|
||||
+ ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(pinfo->supplies),
|
||||
+ pinfo->supplies);
|
||||
+ if (ret < 0)
|
||||
+ return dev_err_probe(dev, ret, "failed to get regulators\n");
|
||||
+
|
||||
+ pinfo->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
|
||||
+ if (IS_ERR(pinfo->reset_gpio))
|
||||
+ return dev_err_probe(dev, PTR_ERR(pinfo->reset_gpio), "failed to get reset gpio\n");
|
||||
+
|
||||
+ pinfo->desc = (struct panel_desc *)of_device_get_match_data(dev);
|
||||
+ if (!pinfo->desc)
|
||||
+ return -ENODEV;
|
||||
+
|
||||
+ pinfo->dsi = dsi;
|
||||
+ mipi_dsi_set_drvdata(dsi, pinfo);
|
||||
+ drm_panel_init(&pinfo->panel, dev, &icna3512_panel_funcs, DRM_MODE_CONNECTOR_DSI);
|
||||
+
|
||||
+ ret = of_drm_get_panel_orientation(dev->of_node, &pinfo->orientation);
|
||||
+ if (ret < 0) {
|
||||
+ dev_err(dev, "%pOF: failed to get orientation %d\n", dev->of_node, ret);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ pinfo->panel.prepare_prev_first = true;
|
||||
+
|
||||
+ pinfo->panel.backlight = icna3512_create_backlight(dsi);
|
||||
+ if (IS_ERR(pinfo->panel.backlight))
|
||||
+ return dev_err_probe(dev, PTR_ERR(pinfo->panel.backlight),
|
||||
+ "Failed to create backlight\n");
|
||||
+
|
||||
+ drm_panel_add(&pinfo->panel);
|
||||
+
|
||||
+ pinfo->dsi->lanes = pinfo->desc->lanes;
|
||||
+ pinfo->dsi->format = pinfo->desc->format;
|
||||
+ pinfo->dsi->mode_flags = pinfo->desc->mode_flags;
|
||||
+ pinfo->dsi->dsc = &pinfo->desc->dsc;
|
||||
+
|
||||
+ ret = mipi_dsi_attach(pinfo->dsi);
|
||||
+ if (ret < 0){
|
||||
+ dev_err_probe(dev, ret, "Failed to attach to DSI host\n");
|
||||
+ drm_panel_remove(&pinfo->panel);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static const struct of_device_id icna3512_of_match[] = {
|
||||
+ {
|
||||
+ .compatible = "chipone,icna3512",
|
||||
+ .data = &icna3512_desc,
|
||||
+ },
|
||||
+ {},
|
||||
+};
|
||||
+MODULE_DEVICE_TABLE(of, icna3512_of_match);
|
||||
+
|
||||
+static struct mipi_dsi_driver icna3512_driver = {
|
||||
+ .probe = icna3512_probe,
|
||||
+ .remove = icna3512_remove,
|
||||
+ .driver = {
|
||||
+ .name = "panel-chipone-icna3512",
|
||||
+ .of_match_table = icna3512_of_match,
|
||||
+ },
|
||||
+};
|
||||
+module_mipi_dsi_driver(icna3512_driver);
|
||||
+
|
||||
+MODULE_AUTHOR("Teguh Sobirin <teguh@sobir.in>");
|
||||
+MODULE_DESCRIPTION("DRM driver for Chipone ICNA3512 based MIPI DSI panels");
|
||||
+MODULE_LICENSE("GPL");
|
||||
\ No newline at end of file
|
||||
diff --git a/include/drm/drm_mipi_dsi.h b/include/drm/drm_mipi_dsi.h
|
||||
index 3aba7b380..979bd874a 100644
|
||||
--- a/include/drm/drm_mipi_dsi.h
|
||||
+++ b/include/drm/drm_mipi_dsi.h
|
||||
@@ -421,6 +421,28 @@ void mipi_dsi_dcs_set_tear_off_multi(struct mipi_dsi_multi_context *ctx);
|
||||
mipi_dsi_generic_write_multi(ctx, d, ARRAY_SIZE(d)); \
|
||||
} while (0)
|
||||
|
||||
+/**
|
||||
+ * mipi_dsi_dcs_write_seq - transmit a DCS command with payload
|
||||
+ *
|
||||
+ * This macro will print errors for you and will RETURN FROM THE CALLING
|
||||
+ * FUNCTION (yes this is non-intuitive) upon error.
|
||||
+ *
|
||||
+ * Because of the non-intuitive return behavior, THIS MACRO IS DEPRECATED.
|
||||
+ * Please replace calls of it with mipi_dsi_dcs_write_seq_multi().
|
||||
+ *
|
||||
+ * @dsi: DSI peripheral device
|
||||
+ * @cmd: Command
|
||||
+ * @seq: buffer containing data to be transmitted
|
||||
+ */
|
||||
+#define mipi_dsi_dcs_write_seq(dsi, cmd, seq...) \
|
||||
+ do { \
|
||||
+ static const u8 d[] = { cmd, seq }; \
|
||||
+ int ret; \
|
||||
+ ret = mipi_dsi_dcs_write_buffer_chatty(dsi, d, ARRAY_SIZE(d)); \
|
||||
+ if (ret < 0) \
|
||||
+ return ret; \
|
||||
+ } while (0)
|
||||
+
|
||||
/**
|
||||
* mipi_dsi_dcs_write_seq_multi - transmit a DCS command with payload
|
||||
*
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@ -0,0 +1,353 @@
|
||||
From b105642e6a3e41b2853ca688c25682d9c934d056 Mon Sep 17 00:00:00 2001
|
||||
From: Teguh Sobirin <teguh@sobir.in>
|
||||
Date: Wed, 12 Feb 2025 15:29:16 +0800
|
||||
Subject: [PATCH 04/18] leds: Add driver for HEROIC HTR3212
|
||||
|
||||
Signed-off-by: Teguh Sobirin <teguh@sobir.in>
|
||||
---
|
||||
drivers/leds/Kconfig | 10 ++
|
||||
drivers/leds/Makefile | 1 +
|
||||
drivers/leds/leds-htr3212.c | 298 ++++++++++++++++++++++++++++++++++++
|
||||
3 files changed, 309 insertions(+)
|
||||
create mode 100644 drivers/leds/leds-htr3212.c
|
||||
|
||||
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
|
||||
index 06e6291be..8e5c6ca7d 100644
|
||||
--- a/drivers/leds/Kconfig
|
||||
+++ b/drivers/leds/Kconfig
|
||||
@@ -218,6 +218,16 @@ config LEDS_EXPRESSWIRE
|
||||
bool
|
||||
depends on GPIOLIB
|
||||
|
||||
+config LEDS_HTR3212
|
||||
+ tristate "HEROIC HTR3212 LED driver"
|
||||
+ depends on LEDS_CLASS && I2C
|
||||
+ select REGMAP_I2C
|
||||
+ help
|
||||
+ Driver for HEROIC HTR3212 LED controller.
|
||||
+
|
||||
+ To compile this driver as a module, choose M here: the module
|
||||
+ will be called leds-htr3212.
|
||||
+
|
||||
config LEDS_TURRIS_OMNIA
|
||||
tristate "LED support for CZ.NIC's Turris Omnia"
|
||||
depends on LEDS_CLASS_MULTICOLOR
|
||||
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
|
||||
index 9a0333ec1..36815a752 100644
|
||||
--- a/drivers/leds/Makefile
|
||||
+++ b/drivers/leds/Makefile
|
||||
@@ -33,6 +33,7 @@ obj-$(CONFIG_LEDS_DA9052) += leds-da9052.o
|
||||
obj-$(CONFIG_LEDS_GPIO) += leds-gpio.o
|
||||
obj-$(CONFIG_LEDS_GPIO_REGISTER) += leds-gpio-register.o
|
||||
obj-$(CONFIG_LEDS_HP6XX) += leds-hp6xx.o
|
||||
+obj-$(CONFIG_LEDS_HTR3212) += leds-htr3212.o
|
||||
obj-$(CONFIG_LEDS_INTEL_SS4200) += leds-ss4200.o
|
||||
obj-$(CONFIG_LEDS_IP30) += leds-ip30.o
|
||||
obj-$(CONFIG_LEDS_IPAQ_MICRO) += leds-ipaq-micro.o
|
||||
diff --git a/drivers/leds/leds-htr3212.c b/drivers/leds/leds-htr3212.c
|
||||
new file mode 100644
|
||||
index 000000000..bbb4b2fc1
|
||||
--- /dev/null
|
||||
+++ b/drivers/leds/leds-htr3212.c
|
||||
@@ -0,0 +1,298 @@
|
||||
+// SPDX-License-Identifier: GPL-2.0-only
|
||||
+/*
|
||||
+ * Driver for HEROIC HTR3212 12-channel 8-bit PWM LED controller
|
||||
+ *
|
||||
+ * Copyright (c) 2024 Teguh Sobirin <teguh@sobir.in>
|
||||
+ *
|
||||
+ */
|
||||
+
|
||||
+#include <linux/delay.h>
|
||||
+#include <linux/device.h>
|
||||
+#include <linux/gpio/consumer.h>
|
||||
+#include <linux/i2c.h>
|
||||
+#include <linux/kernel.h>
|
||||
+#include <linux/leds.h>
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/of.h>
|
||||
+
|
||||
+#define HTR3212_CHANNELS 12
|
||||
+#define HTR3212_ENABLE_BIT 1
|
||||
+#define HTR3212_SHUTDOWN 0x00
|
||||
+#define HTR3212_PWM_REGISTER_BASE 0x0d
|
||||
+#define HTR3212_PWM_UPDATE 0x25
|
||||
+#define HTR3212_LED_CONTROL_BASE 0x32
|
||||
+#define HTR3212_GLOBAL_CONTROL 0x4a
|
||||
+#define HTR3212_OUTPUT_FREQ 0x4b
|
||||
+#define HTR3212_RESET 0x4f
|
||||
+
|
||||
+struct htr3212_priv;
|
||||
+
|
||||
+struct htr3212_led_data {
|
||||
+ struct led_classdev cdev;
|
||||
+ struct htr3212_priv *priv;
|
||||
+ u8 channel;
|
||||
+};
|
||||
+
|
||||
+struct htr3212_priv {
|
||||
+ struct i2c_client *client;
|
||||
+ unsigned int num_leds;
|
||||
+ struct gpio_desc *sdb;
|
||||
+ struct regulator *vdd;
|
||||
+ struct htr3212_led_data leds[];
|
||||
+};
|
||||
+
|
||||
+static int htr3212_write(struct htr3212_priv *priv, u8 reg, u8 val)
|
||||
+{
|
||||
+ int ret;
|
||||
+
|
||||
+ dev_dbg(&priv->client->dev, "writing register 0x%02X=0x%02X", reg, val);
|
||||
+
|
||||
+ ret = i2c_smbus_write_byte_data(priv->client, reg, val);
|
||||
+ if (ret) {
|
||||
+ dev_err(&priv->client->dev,
|
||||
+ "register write to 0x%02X failed (error %d)",
|
||||
+ reg, ret);
|
||||
+ }
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static int htr3212_brightness_set(struct led_classdev *led_cdev,
|
||||
+ enum led_brightness brightness)
|
||||
+{
|
||||
+ const struct htr3212_led_data *led_data =
|
||||
+ container_of(led_cdev, struct htr3212_led_data, cdev);
|
||||
+ u8 pwm_register_offset;
|
||||
+ int ret;
|
||||
+
|
||||
+ dev_dbg(led_cdev->dev, "%s: %d\n", __func__, brightness);
|
||||
+
|
||||
+ pwm_register_offset = led_data->channel - 1;
|
||||
+
|
||||
+ ret = htr3212_write(led_data->priv,
|
||||
+ HTR3212_PWM_REGISTER_BASE + pwm_register_offset,
|
||||
+ brightness);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ return htr3212_write(led_data->priv, HTR3212_PWM_UPDATE, 0x00);
|
||||
+}
|
||||
+
|
||||
+static int htr3212_reset_regs(struct htr3212_priv *priv)
|
||||
+{
|
||||
+ int ret;
|
||||
+
|
||||
+ ret = htr3212_write(priv, HTR3212_RESET, 0x00);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int htr3212_init_regs(struct htr3212_priv *priv)
|
||||
+{
|
||||
+ int ret;
|
||||
+
|
||||
+ ret = htr3212_reset_regs(priv);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ u8 value = GENMASK(HTR3212_ENABLE_BIT, 0);
|
||||
+ u8 num_regs = HTR3212_CHANNELS / HTR3212_ENABLE_BIT;
|
||||
+
|
||||
+ int i;
|
||||
+
|
||||
+ for (i = 0; i < num_regs; i++) {
|
||||
+ ret = htr3212_write(priv,
|
||||
+ HTR3212_LED_CONTROL_BASE + i, value);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ ret = htr3212_write(priv, HTR3212_SHUTDOWN, 0x01);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = htr3212_write(priv, HTR3212_OUTPUT_FREQ, 0x01);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = htr3212_write(priv, HTR3212_GLOBAL_CONTROL, 0x00);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int htr3212_parse_child_dt(const struct device *dev,
|
||||
+ const struct device_node *child,
|
||||
+ struct htr3212_led_data *led_data)
|
||||
+{
|
||||
+ struct led_classdev *cdev = &led_data->cdev;
|
||||
+ int ret = 0;
|
||||
+ u32 reg;
|
||||
+
|
||||
+ ret = of_property_read_u32(child, "reg", ®);
|
||||
+ if (ret || reg < 1 || reg > HTR3212_CHANNELS) {
|
||||
+ dev_err(dev,
|
||||
+ "Child node %pOF does not have a valid reg property\n",
|
||||
+ child);
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+ led_data->channel = reg;
|
||||
+
|
||||
+ cdev->brightness_set_blocking = htr3212_brightness_set;
|
||||
+
|
||||
+ if (!device_property_read_bool(dev, "always-on"))
|
||||
+ cdev->flags |= LED_CORE_SUSPENDRESUME;
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static struct htr3212_led_data *htr3212_find_led_data(
|
||||
+ struct htr3212_priv *priv,
|
||||
+ u8 channel)
|
||||
+{
|
||||
+ size_t i;
|
||||
+
|
||||
+ for (i = 0; i < priv->num_leds; i++) {
|
||||
+ if (priv->leds[i].channel == channel)
|
||||
+ return &priv->leds[i];
|
||||
+ }
|
||||
+
|
||||
+ return NULL;
|
||||
+}
|
||||
+
|
||||
+static int htr3212_parse_dt(struct device *dev,
|
||||
+ struct htr3212_priv *priv)
|
||||
+{
|
||||
+ int ret = 0;
|
||||
+
|
||||
+ for_each_available_child_of_node_scoped(dev_of_node(dev), child) {
|
||||
+ struct led_init_data init_data = {};
|
||||
+ struct htr3212_led_data *led_data =
|
||||
+ &priv->leds[priv->num_leds];
|
||||
+ const struct htr3212_led_data *other_led_data;
|
||||
+
|
||||
+ led_data->priv = priv;
|
||||
+
|
||||
+ ret = htr3212_parse_child_dt(dev, child, led_data);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ other_led_data = htr3212_find_led_data(priv,
|
||||
+ led_data->channel);
|
||||
+ if (other_led_data) {
|
||||
+ dev_err(dev,
|
||||
+ "Node %pOF 'reg' conflicts with another LED\n",
|
||||
+ child);
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+
|
||||
+ init_data.fwnode = of_fwnode_handle(child);
|
||||
+
|
||||
+ ret = devm_led_classdev_register_ext(dev, &led_data->cdev,
|
||||
+ &init_data);
|
||||
+ if (ret) {
|
||||
+ dev_err(dev, "Failed to register LED for %pOF: %d\n",
|
||||
+ child, ret);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ priv->num_leds++;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static const struct of_device_id of_htr3212_match[] = {
|
||||
+ { .compatible = "heroic,htr3212", },
|
||||
+ { /* sentinel */ }
|
||||
+};
|
||||
+
|
||||
+MODULE_DEVICE_TABLE(of, of_htr3212_match);
|
||||
+
|
||||
+static int htr3212_probe(struct i2c_client *client)
|
||||
+{
|
||||
+ struct device *dev = &client->dev;
|
||||
+ struct htr3212_priv *priv;
|
||||
+ int count;
|
||||
+ int ret = 0;
|
||||
+
|
||||
+ count = of_get_available_child_count(dev_of_node(dev));
|
||||
+ if (!count)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ priv = devm_kzalloc(dev, struct_size(priv, leds, count),
|
||||
+ GFP_KERNEL);
|
||||
+ if (!priv)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ priv->sdb = devm_gpiod_get(dev, "sdb", GPIOD_OUT_HIGH);
|
||||
+ if (IS_ERR(priv->sdb))
|
||||
+ return PTR_ERR(priv->sdb);
|
||||
+
|
||||
+ priv->vdd = devm_regulator_get(dev, "vdd");
|
||||
+ if (IS_ERR(priv->vdd)) {
|
||||
+ ret = PTR_ERR(priv->vdd);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ ret = regulator_enable(priv->vdd);
|
||||
+ if (ret < 0) {
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ gpiod_set_value_cansleep(priv->sdb, 1);
|
||||
+ usleep_range(10000, 11000);
|
||||
+
|
||||
+ priv->client = client;
|
||||
+ i2c_set_clientdata(client, priv);
|
||||
+
|
||||
+ ret = htr3212_init_regs(priv);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = htr3212_parse_dt(dev, priv);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static void htr3212_remove(struct i2c_client *client)
|
||||
+{
|
||||
+ struct htr3212_priv *priv = i2c_get_clientdata(client);
|
||||
+ int ret;
|
||||
+
|
||||
+ ret = htr3212_reset_regs(priv);
|
||||
+ if (ret)
|
||||
+ dev_err(&client->dev, "Failed to reset registers on removal (%pe)\n",
|
||||
+ ERR_PTR(ret));
|
||||
+
|
||||
+ gpiod_set_value_cansleep(priv->sdb, 0);
|
||||
+
|
||||
+ regulator_disable(priv->vdd);
|
||||
+}
|
||||
+
|
||||
+static const struct i2c_device_id htr3212_id[] = {
|
||||
+ { "htr3212" },
|
||||
+ {},
|
||||
+};
|
||||
+
|
||||
+MODULE_DEVICE_TABLE(i2c, htr3212_id);
|
||||
+
|
||||
+static struct i2c_driver htr3212_driver = {
|
||||
+ .driver = {
|
||||
+ .name = "htr3212",
|
||||
+ .of_match_table = of_htr3212_match,
|
||||
+ },
|
||||
+ .probe = htr3212_probe,
|
||||
+ .remove = htr3212_remove,
|
||||
+ .id_table = htr3212_id,
|
||||
+};
|
||||
+
|
||||
+module_i2c_driver(htr3212_driver);
|
||||
+
|
||||
+MODULE_AUTHOR("Teguh Sobirin <teguh@sobir.in");
|
||||
+MODULE_DESCRIPTION("HEROIC HTR3212 LED Driver");
|
||||
+MODULE_LICENSE("GPL v2");
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@ -0,0 +1,98 @@
|
||||
From 10d9493e70a541d909494556d7d28451e4c17f0d Mon Sep 17 00:00:00 2001
|
||||
From: Teguh Sobirin <teguh@sobir.in>
|
||||
Date: Fri, 23 Jan 2026 09:15:25 +0800
|
||||
Subject: [PATCH 05/18] ASoC: qcom: sc8280xp Add support for Primary I2S
|
||||
|
||||
---
|
||||
sound/soc/qcom/sc8280xp.c | 40 ++++++++++++++++++++++++++++++++++++++-
|
||||
1 file changed, 39 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/sound/soc/qcom/sc8280xp.c b/sound/soc/qcom/sc8280xp.c
|
||||
index ed8b04c60..01017d05a 100644
|
||||
--- a/sound/soc/qcom/sc8280xp.c
|
||||
+++ b/sound/soc/qcom/sc8280xp.c
|
||||
@@ -2,6 +2,7 @@
|
||||
// Copyright (c) 2022, Linaro Limited
|
||||
|
||||
#include <dt-bindings/sound/qcom,q6afe.h>
|
||||
+#include <linux/clk.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <sound/soc.h>
|
||||
@@ -21,6 +22,7 @@ struct sc8280xp_snd_data {
|
||||
struct sdw_stream_runtime *sruntime[AFE_PORT_MAX];
|
||||
struct snd_soc_jack jack;
|
||||
struct snd_soc_jack dp_jack[8];
|
||||
+ struct clk *i2s_clk;
|
||||
bool jack_setup;
|
||||
};
|
||||
|
||||
@@ -68,6 +70,29 @@ static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd)
|
||||
return qcom_snd_wcd_jack_setup(rtd, &data->jack, &data->jack_setup);
|
||||
}
|
||||
|
||||
+static int sc8280xp_snd_startup(struct snd_pcm_substream *substream)
|
||||
+{
|
||||
+ unsigned int fmt = SND_SOC_DAIFMT_BP_FP;
|
||||
+ unsigned int codec_dai_fmt = SND_SOC_DAIFMT_BC_FC | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_I2S;
|
||||
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
+ struct sc8280xp_snd_data *pdata = snd_soc_card_get_drvdata(rtd->card);
|
||||
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
|
||||
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
|
||||
+
|
||||
+ switch (cpu_dai->id) {
|
||||
+ case PRIMARY_MI2S_RX:
|
||||
+ if (pdata->i2s_clk)
|
||||
+ clk_prepare_enable(pdata->i2s_clk);
|
||||
+ snd_soc_dai_set_fmt(cpu_dai, fmt);
|
||||
+ snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt);
|
||||
+ break;
|
||||
+ default:
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
+ return qcom_snd_sdw_startup(substream);
|
||||
+}
|
||||
+
|
||||
static void sc8280xp_snd_shutdown(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
|
||||
@@ -75,6 +100,15 @@ static void sc8280xp_snd_shutdown(struct snd_pcm_substream *substream)
|
||||
struct sc8280xp_snd_data *pdata = snd_soc_card_get_drvdata(rtd->card);
|
||||
struct sdw_stream_runtime *sruntime = qcom_snd_sdw_get_stream(substream);
|
||||
|
||||
+ switch (cpu_dai->id) {
|
||||
+ case PRIMARY_MI2S_RX:
|
||||
+ if (pdata->i2s_clk)
|
||||
+ clk_disable_unprepare(pdata->i2s_clk);
|
||||
+ break;
|
||||
+ default:
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
pdata->sruntime[cpu_dai->id] = NULL;
|
||||
sdw_release_stream(sruntime);
|
||||
}
|
||||
@@ -141,7 +175,7 @@ static int sc8280xp_snd_hw_free(struct snd_pcm_substream *substream)
|
||||
}
|
||||
|
||||
static const struct snd_soc_ops sc8280xp_be_ops = {
|
||||
- .startup = qcom_snd_sdw_startup,
|
||||
+ .startup = sc8280xp_snd_startup,
|
||||
.shutdown = sc8280xp_snd_shutdown,
|
||||
.hw_params = sc8280xp_snd_hw_params,
|
||||
.hw_free = sc8280xp_snd_hw_free,
|
||||
@@ -185,6 +219,10 @@ static int sc8280xp_platform_probe(struct platform_device *pdev)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
+ data->i2s_clk = devm_clk_get_optional(dev, "i2s_clk");
|
||||
+ if (IS_ERR(data->i2s_clk))
|
||||
+ return dev_err_probe(dev, PTR_ERR(data->i2s_clk), "unable to get i2s clock\n");
|
||||
+
|
||||
card->driver_name = of_device_get_match_data(dev);
|
||||
sc8280xp_add_be_ops(card);
|
||||
return devm_snd_soc_register_card(dev, card);
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@ -0,0 +1,39 @@
|
||||
From 8127a1d15b63b1f582be9c212472b8d50bcb3b9d Mon Sep 17 00:00:00 2001
|
||||
From: Sarthak Garg <quic_sartgarg@quicinc.com>
|
||||
Date: Thu, 7 Nov 2024 13:35:03 +0530
|
||||
Subject: [PATCH 06/18] dt-bindings: mmc: qcom: Document level shifter flag for
|
||||
SD card
|
||||
|
||||
Introduce a flag to indicate if the Qualcomm platform has a level
|
||||
shifter for SD cards. With level shifter addition some extra delay is
|
||||
seen on RX data path leading to CRC errors. To compensate these delays
|
||||
and avoid CRC errors below things needs to be done:
|
||||
|
||||
1) Enable tuning for SDR50 mode
|
||||
2) Limit HS mode frequency to 37.5MHz from 50MHz
|
||||
|
||||
Add this flag for all targets with a level shifter to handle these
|
||||
issues for SD card.
|
||||
|
||||
Signed-off-by: Sarthak Garg <quic_sartgarg@quicinc.com>
|
||||
---
|
||||
Documentation/devicetree/bindings/mmc/sdhci-msm.yaml | 3 +++
|
||||
1 file changed, 3 insertions(+)
|
||||
|
||||
diff --git a/Documentation/devicetree/bindings/mmc/sdhci-msm.yaml b/Documentation/devicetree/bindings/mmc/sdhci-msm.yaml
|
||||
index 594bd174f..14b91538a 100644
|
||||
--- a/Documentation/devicetree/bindings/mmc/sdhci-msm.yaml
|
||||
+++ b/Documentation/devicetree/bindings/mmc/sdhci-msm.yaml
|
||||
@@ -138,6 +138,9 @@ properties:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
description: platform specific settings for DLL_CONFIG reg.
|
||||
|
||||
+ qcom,use-level-shifter:
|
||||
+ description: Flag to indicate if platform has level shifter for SD card.
|
||||
+
|
||||
iommus:
|
||||
minItems: 1
|
||||
maxItems: 8
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@ -0,0 +1,62 @@
|
||||
From 59ca09bcdfb968d7da2ad65fc51127a6aec1183c Mon Sep 17 00:00:00 2001
|
||||
From: Sarthak Garg <quic_sartgarg@quicinc.com>
|
||||
Date: Fri, 23 Jan 2026 09:17:32 +0800
|
||||
Subject: [PATCH 07/18] mmc: sdhci-msm: Limit HS mode frequency to 37.5MHz
|
||||
|
||||
For Qualcomm SoCs with level shifter delays are seen on receivers data
|
||||
path due to latency added by level shifter.
|
||||
|
||||
To bring these delays in normal range and avoid CMD CRC errors
|
||||
reduce frequency for HS mode SD cards to 37.5MHz for targets which has
|
||||
level shifter.
|
||||
|
||||
Signed-off-by: Sarthak Garg <quic_sartgarg@quicinc.com>
|
||||
---
|
||||
drivers/mmc/host/sdhci-msm.c | 10 ++++++++++
|
||||
1 file changed, 10 insertions(+)
|
||||
|
||||
diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c
|
||||
index 3b8523313..ea5531a97 100644
|
||||
--- a/drivers/mmc/host/sdhci-msm.c
|
||||
+++ b/drivers/mmc/host/sdhci-msm.c
|
||||
@@ -147,6 +147,8 @@
|
||||
/* Max load for SD Vdd-io supply */
|
||||
#define SD_VQMMC_MAX_LOAD_UA 22000
|
||||
|
||||
+#define LEVEL_SHIFTER_HIGH_SPEED_FREQ 37500000
|
||||
+
|
||||
#define msm_host_readl(msm_host, host, offset) \
|
||||
msm_host->var_ops->msm_readl_relaxed(host, offset)
|
||||
|
||||
@@ -296,6 +298,7 @@ struct sdhci_msm_host {
|
||||
bool use_cdr;
|
||||
u32 transfer_mode;
|
||||
bool updated_ddr_cfg;
|
||||
+ bool uses_level_shifter;
|
||||
bool uses_tassadar_dll;
|
||||
u32 dll_config;
|
||||
u32 ddr_config;
|
||||
@@ -377,6 +380,11 @@ static void msm_set_clock_rate_for_bus_mode(struct sdhci_host *host,
|
||||
|
||||
mult = msm_get_clock_mult_for_bus_mode(host, clock, timing);
|
||||
desired_rate = clock * mult;
|
||||
+
|
||||
+ if (timing == MMC_TIMING_SD_HS && desired_rate == 50000000
|
||||
+ && msm_host->uses_level_shifter)
|
||||
+ desired_rate = LEVEL_SHIFTER_HIGH_SPEED_FREQ;
|
||||
+
|
||||
rc = dev_pm_opp_set_rate(mmc_dev(host->mmc), desired_rate);
|
||||
if (rc) {
|
||||
pr_err("%s: Failed to set clock at rate %u at timing %d\n",
|
||||
@@ -2490,6 +2498,8 @@ static inline void sdhci_msm_get_of_property(struct platform_device *pdev,
|
||||
|
||||
of_property_read_u32(node, "qcom,dll-config", &msm_host->dll_config);
|
||||
|
||||
+ msm_host->uses_level_shifter = of_property_read_bool(node, "qcom,use-level-shifter");
|
||||
+
|
||||
if (of_device_is_compatible(node, "qcom,msm8916-sdhci"))
|
||||
host->quirks2 |= SDHCI_QUIRK2_BROKEN_64_BIT_DMA;
|
||||
}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@ -0,0 +1,100 @@
|
||||
From a55ef657ce7ac5c14a09d6f361d6caa62a2a8bcb Mon Sep 17 00:00:00 2001
|
||||
From: Ram Prakash Gupta <quic_rampraka@quicinc.com>
|
||||
Date: Tue, 22 Oct 2024 16:40:25 +0530
|
||||
Subject: [PATCH 08/18] mmc: sdhci-msm: Toggle the FIFO write clock after
|
||||
ungate
|
||||
|
||||
For Qualcomm SoCs with sdcc minor version 6B and more, command path
|
||||
state machine is getting corrupted post clock ungate which is leading
|
||||
to software timeout.
|
||||
|
||||
Toggle the write fifo clock to reset the async fifo to fix this issue.
|
||||
|
||||
Signed-off-by: Ram Prakash Gupta <quic_rampraka@quicinc.com>
|
||||
---
|
||||
drivers/mmc/host/sdhci-msm.c | 41 ++++++++++++++++++++++++++++++++++++
|
||||
1 file changed, 41 insertions(+)
|
||||
|
||||
diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c
|
||||
index ea5531a97..5be69346b 100644
|
||||
--- a/drivers/mmc/host/sdhci-msm.c
|
||||
+++ b/drivers/mmc/host/sdhci-msm.c
|
||||
@@ -158,6 +158,7 @@
|
||||
/* CQHCI vendor specific registers */
|
||||
#define CQHCI_VENDOR_CFG1 0xA00
|
||||
#define CQHCI_VENDOR_DIS_RST_ON_CQ_EN (0x3 << 13)
|
||||
+#define RCLK_TOGGLE BIT(1)
|
||||
|
||||
struct sdhci_msm_offset {
|
||||
u32 core_hc_mode;
|
||||
@@ -303,6 +304,7 @@ struct sdhci_msm_host {
|
||||
u32 dll_config;
|
||||
u32 ddr_config;
|
||||
bool vqmmc_enabled;
|
||||
+ bool toggle_fifo_clk;
|
||||
};
|
||||
|
||||
static const struct sdhci_msm_offset *sdhci_priv_msm_offset(struct sdhci_host *host)
|
||||
@@ -1186,6 +1188,39 @@ static int sdhci_msm_restore_sdr_dll_config(struct sdhci_host *host)
|
||||
return ret;
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * After MCLK ugating, toggle the FIFO write clock to get
|
||||
+ * the FIFO pointers and flags to valid state.
|
||||
+ */
|
||||
+static void sdhci_msm_toggle_fifo_write_clk(struct sdhci_host *host)
|
||||
+{
|
||||
+ u32 config;
|
||||
+ struct mmc_ios ios = host->mmc->ios;
|
||||
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
+ struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host);
|
||||
+ const struct sdhci_msm_offset *msm_offset = msm_host->offset;
|
||||
+
|
||||
+ if ((msm_host->tuning_done || ios.enhanced_strobe) &&
|
||||
+ host->mmc->ios.timing == MMC_TIMING_MMC_HS400) {
|
||||
+ /*
|
||||
+ * Select MCLK as DLL input clock.
|
||||
+ */
|
||||
+ config = readl_relaxed(host->ioaddr + msm_offset->core_dll_config_3);
|
||||
+ config |= RCLK_TOGGLE;
|
||||
+ writel_relaxed(config, host->ioaddr + msm_offset->core_dll_config_3);
|
||||
+
|
||||
+ /* ensure above write as toggling same bit quickly */
|
||||
+ wmb();
|
||||
+ udelay(2);
|
||||
+
|
||||
+ /*
|
||||
+ * Select RCLK as DLL input clock
|
||||
+ */
|
||||
+ config &= ~RCLK_TOGGLE;
|
||||
+ writel_relaxed(config, host->ioaddr + msm_offset->core_dll_config_3);
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
static void sdhci_msm_set_cdr(struct sdhci_host *host, bool enable)
|
||||
{
|
||||
const struct sdhci_msm_offset *msm_offset = sdhci_priv_msm_offset(host);
|
||||
@@ -2731,6 +2766,9 @@ static int sdhci_msm_probe(struct platform_device *pdev)
|
||||
if (core_major == 1 && core_minor >= 0x71)
|
||||
msm_host->uses_tassadar_dll = true;
|
||||
|
||||
+ if (core_major == 1 && core_minor >= 0x6B)
|
||||
+ msm_host->toggle_fifo_clk = true;
|
||||
+
|
||||
ret = sdhci_msm_register_vreg(msm_host);
|
||||
if (ret)
|
||||
goto clk_disable;
|
||||
@@ -2860,6 +2898,9 @@ static int sdhci_msm_runtime_resume(struct device *dev)
|
||||
msm_host->bulk_clks);
|
||||
if (ret)
|
||||
return ret;
|
||||
+
|
||||
+ if (msm_host->toggle_fifo_clk)
|
||||
+ sdhci_msm_toggle_fifo_write_clk(host);
|
||||
/*
|
||||
* Whenever core-clock is gated dynamically, it's needed to
|
||||
* restore the SDR DLL settings when the clock is ungated.
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@ -0,0 +1,53 @@
|
||||
From d5271df380a8407f0e0db336f4eba34ce587f135 Mon Sep 17 00:00:00 2001
|
||||
From: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
|
||||
Date: Thu, 7 Nov 2024 11:58:09 +0000
|
||||
Subject: [PATCH 09/18] clk: qcom: gcc-sm8550: Keep UFS PHY GDSCs ALWAYS_ON
|
||||
|
||||
Starting from SM8550, UFS PHY GDSCs doesn't support hardware retention. So
|
||||
using RETAIN_FF_ENABLE is wrong. Moreover, without ALWAYS_ON flag, GDSCs
|
||||
will get powered down during suspend, causing the UFS PHY to loose its
|
||||
state. And this will lead to below UFS error during resume as observed on
|
||||
SM8550-QRD:
|
||||
|
||||
ufshcd-qcom 1d84000.ufs: ufshcd_uic_hibern8_exit: hibern8 exit failed. ret = 5
|
||||
ufshcd-qcom 1d84000.ufs: __ufshcd_wl_resume: hibern8 exit failed 5
|
||||
ufs_device_wlun 0:0:0:49488: ufshcd_wl_resume failed: 5
|
||||
ufs_device_wlun 0:0:0:49488: PM: dpm_run_callback(): scsi_bus_resume+0x0/0x84 returns 5
|
||||
ufs_device_wlun 0:0:0:49488: PM: failed to resume async: error 5
|
||||
|
||||
Cc: stable@vger.kernel.org # 6.8
|
||||
Fixes: 1fe8273c8d40 ("clk: qcom: gcc-sm8550: Add the missing RETAIN_FF_ENABLE GDSC flag")
|
||||
Reported-by: Neil Armstrong <neil.armstrong@linaro.org>
|
||||
Suggested-by: Nitin Rawat <quic_nitirawa@quicinc.com>
|
||||
Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
|
||||
Tested-by: Neil Armstrong <neil.armstrong@linaro.org> # on SM8550-QRD
|
||||
Tested-by: Neil Armstrong <neil.armstrong@linaro.org> # on SM8550-HDK
|
||||
---
|
||||
drivers/clk/qcom/gcc-sm8550.c | 4 ++--
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/drivers/clk/qcom/gcc-sm8550.c b/drivers/clk/qcom/gcc-sm8550.c
|
||||
index 862a9bf73..2803c0855 100644
|
||||
--- a/drivers/clk/qcom/gcc-sm8550.c
|
||||
+++ b/drivers/clk/qcom/gcc-sm8550.c
|
||||
@@ -3046,7 +3046,7 @@ static struct gdsc ufs_phy_gdsc = {
|
||||
.name = "ufs_phy_gdsc",
|
||||
},
|
||||
.pwrsts = PWRSTS_OFF_ON,
|
||||
- .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE,
|
||||
+ .flags = POLL_CFG_GDSCR | ALWAYS_ON,
|
||||
};
|
||||
|
||||
static struct gdsc ufs_mem_phy_gdsc = {
|
||||
@@ -3055,7 +3055,7 @@ static struct gdsc ufs_mem_phy_gdsc = {
|
||||
.name = "ufs_mem_phy_gdsc",
|
||||
},
|
||||
.pwrsts = PWRSTS_OFF_ON,
|
||||
- .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE,
|
||||
+ .flags = POLL_CFG_GDSCR | ALWAYS_ON,
|
||||
};
|
||||
|
||||
static struct gdsc usb30_prim_gdsc = {
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@ -0,0 +1,307 @@
|
||||
From 512a16bf06286e0ce6d7ff03c419464c298e0fe2 Mon Sep 17 00:00:00 2001
|
||||
From: Xilin Wu <wuxilin123@gmail.com>
|
||||
Date: Fri, 23 Jan 2026 09:21:13 +0800
|
||||
Subject: [PATCH 10/18] drm/panel: Add WIP panel driver for AYN Odin 2
|
||||
|
||||
Signed-off-by: Xilin Wu <wuxilin123@gmail.com>
|
||||
---
|
||||
drivers/gpu/drm/panel/Kconfig | 11 +
|
||||
drivers/gpu/drm/panel/Makefile | 1 +
|
||||
.../gpu/drm/panel/panel-synaptics-td4328.c | 246 ++++++++++++++++++
|
||||
3 files changed, 258 insertions(+)
|
||||
create mode 100644 drivers/gpu/drm/panel/panel-synaptics-td4328.c
|
||||
|
||||
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
|
||||
index d8cf15b0b..d34351f27 100644
|
||||
--- a/drivers/gpu/drm/panel/Kconfig
|
||||
+++ b/drivers/gpu/drm/panel/Kconfig
|
||||
@@ -1056,6 +1056,16 @@ config DRM_PANEL_SYNAPTICS_R63353
|
||||
Say Y if you want to enable support for panels based on the
|
||||
Synaptics R63353 controller.
|
||||
|
||||
+config DRM_PANEL_SYNAPTICS_TD4328
|
||||
+ tristate "Synaptics TD4328-based panels"
|
||||
+ depends on OF
|
||||
+ depends on DRM_MIPI_DSI
|
||||
+ depends on BACKLIGHT_CLASS_DEVICE
|
||||
+ select DRM_KMS_HELPER
|
||||
+ help
|
||||
+ Say Y if you want to enable support for panels based on the
|
||||
+ Synaptics TD4328 controller.
|
||||
+
|
||||
config DRM_PANEL_TDO_TL070WSH30
|
||||
tristate "TDO TL070WSH30 DSI panel"
|
||||
depends on OF
|
||||
@@ -1167,4 +1177,5 @@ config DRM_PANEL_XINPENG_XPP055C272
|
||||
Say Y here if you want to enable support for the Xinpeng
|
||||
XPP055C272 controller for 720x1280 LCD panels with MIPI/RGB/SPI
|
||||
system interfaces.
|
||||
+
|
||||
endmenu
|
||||
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
|
||||
index 171843de9..d8e9d6ac1 100644
|
||||
--- a/drivers/gpu/drm/panel/Makefile
|
||||
+++ b/drivers/gpu/drm/panel/Makefile
|
||||
@@ -101,6 +101,7 @@ obj-$(CONFIG_DRM_PANEL_SITRONIX_ST7703) += panel-sitronix-st7703.o
|
||||
obj-$(CONFIG_DRM_PANEL_SITRONIX_ST7789V) += panel-sitronix-st7789v.o
|
||||
obj-$(CONFIG_DRM_PANEL_SUMMIT) += panel-summit.o
|
||||
obj-$(CONFIG_DRM_PANEL_SYNAPTICS_R63353) += panel-synaptics-r63353.o
|
||||
+obj-$(CONFIG_DRM_PANEL_SYNAPTICS_TD4328) += panel-synaptics-td4328.o
|
||||
obj-$(CONFIG_DRM_PANEL_SONY_ACX565AKM) += panel-sony-acx565akm.o
|
||||
obj-$(CONFIG_DRM_PANEL_SONY_TD4353_JDI) += panel-sony-td4353-jdi.o
|
||||
obj-$(CONFIG_DRM_PANEL_SONY_TULIP_TRULY_NT35521) += panel-sony-tulip-truly-nt35521.o
|
||||
diff --git a/drivers/gpu/drm/panel/panel-synaptics-td4328.c b/drivers/gpu/drm/panel/panel-synaptics-td4328.c
|
||||
new file mode 100644
|
||||
index 000000000..0fb0ddd93
|
||||
--- /dev/null
|
||||
+++ b/drivers/gpu/drm/panel/panel-synaptics-td4328.c
|
||||
@@ -0,0 +1,246 @@
|
||||
+// SPDX-License-Identifier: GPL-2.0-only
|
||||
+/*
|
||||
+ * Generated with linux-mdss-dsi-panel-driver-generator from vendor device tree.
|
||||
+ * Copyright (c) 2024 Xilin Wu <wuxilin123@gmail.com>
|
||||
+ * Copyright (c) 2024 Junhao Xie <bigfoot@classfun.cn>
|
||||
+ */
|
||||
+
|
||||
+#include <linux/delay.h>
|
||||
+#include <linux/gpio/consumer.h>
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/of.h>
|
||||
+#include <linux/regulator/consumer.h>
|
||||
+
|
||||
+#include <drm/drm_mipi_dsi.h>
|
||||
+#include <drm/drm_modes.h>
|
||||
+#include <drm/drm_panel.h>
|
||||
+#include <drm/drm_probe_helper.h>
|
||||
+
|
||||
+struct td4328 {
|
||||
+ struct drm_panel panel;
|
||||
+ struct mipi_dsi_device *dsi;
|
||||
+ struct regulator_bulk_data supplies[2];
|
||||
+ struct gpio_desc *reset_gpio;
|
||||
+};
|
||||
+
|
||||
+static inline struct td4328 *to_td4328(struct drm_panel *panel)
|
||||
+{
|
||||
+ return container_of(panel, struct td4328, panel);
|
||||
+}
|
||||
+
|
||||
+static void td4328_reset(struct td4328 *ctx)
|
||||
+{
|
||||
+ gpiod_set_value_cansleep(ctx->reset_gpio, 0);
|
||||
+ usleep_range(10000, 11000);
|
||||
+ gpiod_set_value_cansleep(ctx->reset_gpio, 1);
|
||||
+ usleep_range(10000, 11000);
|
||||
+ gpiod_set_value_cansleep(ctx->reset_gpio, 0);
|
||||
+ usleep_range(10000, 11000);
|
||||
+}
|
||||
+
|
||||
+static int td4328_on(struct td4328 *ctx)
|
||||
+{
|
||||
+ struct mipi_dsi_device *dsi = ctx->dsi;
|
||||
+ struct device *dev = &dsi->dev;
|
||||
+ int ret;
|
||||
+
|
||||
+ dsi->mode_flags |= MIPI_DSI_MODE_LPM;
|
||||
+
|
||||
+ mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x00);
|
||||
+ mipi_dsi_dcs_write_seq(dsi, 0xb3, 0x31);
|
||||
+ mipi_dsi_dcs_write_seq(dsi, 0xd6, 0x00);
|
||||
+
|
||||
+ ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
|
||||
+ if (ret < 0) {
|
||||
+ dev_err(dev, "Failed to exit sleep mode: %d\n", ret);
|
||||
+ return ret;
|
||||
+ }
|
||||
+ msleep(70);
|
||||
+
|
||||
+ ret = mipi_dsi_dcs_set_display_on(dsi);
|
||||
+ if (ret < 0) {
|
||||
+ dev_err(dev, "Failed to set display on: %d\n", ret);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int td4328_off(struct td4328 *ctx)
|
||||
+{
|
||||
+ struct mipi_dsi_device *dsi = ctx->dsi;
|
||||
+ struct device *dev = &dsi->dev;
|
||||
+ int ret;
|
||||
+
|
||||
+ dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
|
||||
+
|
||||
+ ret = mipi_dsi_dcs_set_display_off(dsi);
|
||||
+ if (ret < 0) {
|
||||
+ dev_err(dev, "Failed to set display off: %d\n", ret);
|
||||
+ return ret;
|
||||
+ }
|
||||
+ msleep(50);
|
||||
+
|
||||
+ ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
|
||||
+ if (ret < 0) {
|
||||
+ dev_err(dev, "Failed to enter sleep mode: %d\n", ret);
|
||||
+ return ret;
|
||||
+ }
|
||||
+ msleep(120);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int td4328_prepare(struct drm_panel *panel)
|
||||
+{
|
||||
+ struct td4328 *ctx = to_td4328(panel);
|
||||
+ struct device *dev = &ctx->dsi->dev;
|
||||
+ int ret;
|
||||
+
|
||||
+ ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
|
||||
+ if (ret < 0) {
|
||||
+ dev_err(dev, "Failed to enable regulators: %d\n", ret);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ td4328_reset(ctx);
|
||||
+
|
||||
+ ret = td4328_on(ctx);
|
||||
+ if (ret < 0) {
|
||||
+ dev_err(dev, "Failed to initialize panel: %d\n", ret);
|
||||
+ gpiod_set_value_cansleep(ctx->reset_gpio, 1);
|
||||
+ regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int td4328_unprepare(struct drm_panel *panel)
|
||||
+{
|
||||
+ struct td4328 *ctx = to_td4328(panel);
|
||||
+ struct device *dev = &ctx->dsi->dev;
|
||||
+ int ret;
|
||||
+
|
||||
+ ret = td4328_off(ctx);
|
||||
+ if (ret < 0)
|
||||
+ dev_err(dev, "Failed to un-initialize panel: %d\n", ret);
|
||||
+
|
||||
+ regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static const struct drm_display_mode td4328_mode = {
|
||||
+ .clock = (1080 + 93 + 1 + 47) * (1920 + 40 + 2 + 60) * 60 / 1000,
|
||||
+ .hdisplay = 1080,
|
||||
+ .hsync_start = 1080 + 93,
|
||||
+ .hsync_end = 1080 + 93 + 1,
|
||||
+ .htotal = 1080 + 93 + 1 + 47,
|
||||
+ .vdisplay = 1920,
|
||||
+ .vsync_start = 1920 + 40,
|
||||
+ .vsync_end = 1920 + 40 + 2,
|
||||
+ .vtotal = 1920 + 40 + 2 + 60,
|
||||
+ .width_mm = 75,
|
||||
+ .height_mm = 133,
|
||||
+ .type = DRM_MODE_TYPE_DRIVER,
|
||||
+};
|
||||
+
|
||||
+static int td4328_get_modes(struct drm_panel *panel,
|
||||
+ struct drm_connector *connector)
|
||||
+{
|
||||
+ return drm_connector_helper_get_modes_fixed(connector, &td4328_mode);
|
||||
+}
|
||||
+
|
||||
+static enum drm_panel_orientation td4328_get_orientation(struct drm_panel *panel)
|
||||
+{
|
||||
+ return DRM_MODE_PANEL_ORIENTATION_RIGHT_UP;
|
||||
+}
|
||||
+
|
||||
+static const struct drm_panel_funcs td4328_panel_funcs = {
|
||||
+ .prepare = td4328_prepare,
|
||||
+ .disable = td4328_unprepare,
|
||||
+ .get_modes = td4328_get_modes,
|
||||
+ .get_orientation = td4328_get_orientation,
|
||||
+};
|
||||
+
|
||||
+static int td4328_probe(struct mipi_dsi_device *dsi)
|
||||
+{
|
||||
+ struct device *dev = &dsi->dev;
|
||||
+ struct td4328 *ctx;
|
||||
+ int ret;
|
||||
+
|
||||
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
|
||||
+ if (!ctx)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ ctx->supplies[0].supply = "vddio";
|
||||
+ ctx->supplies[1].supply = "vdd";
|
||||
+ ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies),
|
||||
+ ctx->supplies);
|
||||
+ if (ret < 0)
|
||||
+ return dev_err_probe(dev, ret, "Failed to get regulators\n");
|
||||
+
|
||||
+ ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
|
||||
+ if (IS_ERR(ctx->reset_gpio))
|
||||
+ return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio),
|
||||
+ "Failed to get reset-gpios\n");
|
||||
+
|
||||
+ ctx->dsi = dsi;
|
||||
+ mipi_dsi_set_drvdata(dsi, ctx);
|
||||
+
|
||||
+ dsi->lanes = 4;
|
||||
+ dsi->format = MIPI_DSI_FMT_RGB888;
|
||||
+ dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
|
||||
+ MIPI_DSI_CLOCK_NON_CONTINUOUS;
|
||||
+
|
||||
+ drm_panel_init(&ctx->panel, dev, &td4328_panel_funcs,
|
||||
+ DRM_MODE_CONNECTOR_DSI);
|
||||
+ ctx->panel.prepare_prev_first = true;
|
||||
+
|
||||
+ ret = drm_panel_of_backlight(&ctx->panel);
|
||||
+ if (ret)
|
||||
+ return dev_err_probe(dev, ret, "Failed to get backlight\n");
|
||||
+
|
||||
+ drm_panel_add(&ctx->panel);
|
||||
+
|
||||
+ ret = mipi_dsi_attach(dsi);
|
||||
+ if (ret < 0) {
|
||||
+ dev_err_probe(dev, ret, "Failed to attach to DSI host\n");
|
||||
+ drm_panel_remove(&ctx->panel);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static void td4328_remove(struct mipi_dsi_device *dsi)
|
||||
+{
|
||||
+ struct td4328 *ctx = mipi_dsi_get_drvdata(dsi);
|
||||
+ int ret;
|
||||
+
|
||||
+ ret = mipi_dsi_detach(dsi);
|
||||
+ if (ret < 0)
|
||||
+ dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret);
|
||||
+
|
||||
+ drm_panel_remove(&ctx->panel);
|
||||
+}
|
||||
+
|
||||
+static const struct of_device_id td4328_of_match[] = {
|
||||
+ { .compatible = "syna,td4328" },
|
||||
+ { /* sentinel */ }
|
||||
+};
|
||||
+MODULE_DEVICE_TABLE(of, td4328_of_match);
|
||||
+
|
||||
+static struct mipi_dsi_driver td4328_driver = {
|
||||
+ .probe = td4328_probe,
|
||||
+ .remove = td4328_remove,
|
||||
+ .driver = {
|
||||
+ .name = "panel-td4328",
|
||||
+ .of_match_table = td4328_of_match,
|
||||
+ },
|
||||
+};
|
||||
+module_mipi_dsi_driver(td4328_driver);
|
||||
+
|
||||
+MODULE_DESCRIPTION("DRM driver for td4328-equipped DSI panels");
|
||||
+MODULE_LICENSE("GPL");
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
From 9e66f088c6907dfa6fa91be93d035098653b9619 Mon Sep 17 00:00:00 2001
|
||||
From: Alex Ling <ling_kasim@hotmail.com>
|
||||
Date: Fri, 23 Jan 2026 09:24:14 +0800
|
||||
Subject: [PATCH 11/18] arm64: dts: qcom: Added pmk8550 pwm support
|
||||
|
||||
Signed-off-by: Alex Ling <ling_kasim@hotmail.com>
|
||||
---
|
||||
arch/arm64/boot/dts/qcom/pmk8550.dtsi | 10 ++++++++++
|
||||
1 file changed, 10 insertions(+)
|
||||
|
||||
diff --git a/arch/arm64/boot/dts/qcom/pmk8550.dtsi b/arch/arm64/boot/dts/qcom/pmk8550.dtsi
|
||||
index 583f61fc1..3049eb6b4 100644
|
||||
--- a/arch/arm64/boot/dts/qcom/pmk8550.dtsi
|
||||
+++ b/arch/arm64/boot/dts/qcom/pmk8550.dtsi
|
||||
@@ -73,5 +73,15 @@ pmk8550_gpios: gpio@b800 {
|
||||
interrupt-controller;
|
||||
#interrupt-cells = <2>;
|
||||
};
|
||||
+
|
||||
+ pmk8550_pwm: pwm {
|
||||
+ compatible = "qcom,pmk8550-pwm";
|
||||
+
|
||||
+ #address-cells = <1>;
|
||||
+ #size-cells = <0>;
|
||||
+ #pwm-cells = <2>;
|
||||
+
|
||||
+ status = "disabled";
|
||||
+ };
|
||||
};
|
||||
};
|
||||
--
|
||||
2.43.0
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,35 @@
|
||||
From ab22f8aadb8e3345416f9e934a48b8a59748173d Mon Sep 17 00:00:00 2001
|
||||
From: Alex Ling <ling_kasim@hotmail.com>
|
||||
Date: Fri, 23 Jan 2026 09:31:36 +0800
|
||||
Subject: [PATCH 13/18] drivers: power: Fix name for sc8280xp battery
|
||||
|
||||
Signed-off-by: Alex Ling <ling_kasim@hotmail.com>
|
||||
---
|
||||
drivers/power/supply/qcom_battmgr.c | 4 ++--
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/drivers/power/supply/qcom_battmgr.c b/drivers/power/supply/qcom_battmgr.c
|
||||
index e6f01e012..21019949e 100644
|
||||
--- a/drivers/power/supply/qcom_battmgr.c
|
||||
+++ b/drivers/power/supply/qcom_battmgr.c
|
||||
@@ -822,7 +822,7 @@ static const enum power_supply_property sc8280xp_bat_props[] = {
|
||||
};
|
||||
|
||||
static const struct power_supply_desc sc8280xp_bat_psy_desc = {
|
||||
- .name = "qcom-battmgr-bat",
|
||||
+ .name = "battery",
|
||||
.type = POWER_SUPPLY_TYPE_BATTERY,
|
||||
.properties = sc8280xp_bat_props,
|
||||
.num_properties = ARRAY_SIZE(sc8280xp_bat_props),
|
||||
@@ -891,7 +891,7 @@ static const enum power_supply_property sm8350_bat_props[] = {
|
||||
};
|
||||
|
||||
static const struct power_supply_desc sm8350_bat_psy_desc = {
|
||||
- .name = "qcom-battmgr-bat",
|
||||
+ .name = "battery",
|
||||
.type = POWER_SUPPLY_TYPE_BATTERY,
|
||||
.properties = sm8350_bat_props,
|
||||
.num_properties = ARRAY_SIZE(sm8350_bat_props),
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
From f46b5f9b1c1157a835a1a08252cd891687ac0376 Mon Sep 17 00:00:00 2001
|
||||
From: Alex Ling <ling_kasim@hotmail.com>
|
||||
Date: Fri, 23 Jan 2026 09:32:30 +0800
|
||||
Subject: [PATCH 14/18] HACK: fix usb boot hang
|
||||
|
||||
Signed-off-by: Alex Ling <ling_kasim@hotmail.com>
|
||||
---
|
||||
drivers/usb/host/pci-quirks.c | 13 -------------
|
||||
1 file changed, 13 deletions(-)
|
||||
|
||||
diff --git a/drivers/usb/host/pci-quirks.c b/drivers/usb/host/pci-quirks.c
|
||||
index 0404489c2..12e1167e2 100644
|
||||
--- a/drivers/usb/host/pci-quirks.c
|
||||
+++ b/drivers/usb/host/pci-quirks.c
|
||||
@@ -1226,19 +1226,6 @@ static void quirk_usb_handoff_xhci(struct pci_dev *pdev)
|
||||
|
||||
op_reg_base = base + XHCI_HC_LENGTH(readl(base));
|
||||
|
||||
- /* Wait for the host controller to be ready before writing any
|
||||
- * operational or runtime registers. Wait 5 seconds and no more.
|
||||
- */
|
||||
- timeout = handshake(op_reg_base + XHCI_STS_OFFSET, XHCI_STS_CNR, 0,
|
||||
- 5000000, 10);
|
||||
- /* Assume a buggy HC and start HC initialization anyway */
|
||||
- if (timeout) {
|
||||
- val = readl(op_reg_base + XHCI_STS_OFFSET);
|
||||
- dev_warn(&pdev->dev,
|
||||
- "xHCI HW not ready after 5 sec (HC bug?) status = 0x%x\n",
|
||||
- val);
|
||||
- }
|
||||
-
|
||||
/* Send the halt and disable interrupts command */
|
||||
val = readl(op_reg_base + XHCI_CMD_OFFSET);
|
||||
val &= ~(XHCI_CMD_RUN | XHCI_IRQS);
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@ -0,0 +1,301 @@
|
||||
From 59250424d761009af70ce8079169d8e6081acf26 Mon Sep 17 00:00:00 2001
|
||||
From: spycat88 <spycat88@users.noreply.github.com>
|
||||
Date: Thu, 23 Jan 2025 15:18:25 +0000
|
||||
Subject: [PATCH 15/18] drivers: use soc serial for wifi and bluetooth
|
||||
|
||||
---
|
||||
drivers/bluetooth/btqca.c | 94 ++++++++++++++++++++++--
|
||||
drivers/net/wireless/ath/ath12k/mac.c | 100 ++++++++++++++++++++++++--
|
||||
drivers/soc/qcom/socinfo.c | 10 +++
|
||||
3 files changed, 196 insertions(+), 8 deletions(-)
|
||||
|
||||
diff --git a/drivers/bluetooth/btqca.c b/drivers/bluetooth/btqca.c
|
||||
index 7c958d606..d71871f94 100644
|
||||
--- a/drivers/bluetooth/btqca.c
|
||||
+++ b/drivers/bluetooth/btqca.c
|
||||
@@ -12,6 +12,74 @@
|
||||
#include <net/bluetooth/hci_core.h>
|
||||
|
||||
#include "btqca.h"
|
||||
+#include <linux/ctype.h>
|
||||
+
|
||||
+extern const char *qcom_serial_number;
|
||||
+
|
||||
+/**
|
||||
+ * generate_bdaddr_from_serial - Generates a BD_ADDR using the serial number
|
||||
+ * @bdaddr: Pointer to bdaddr_t structure to populate
|
||||
+ *
|
||||
+ * This function sets the first 3 bytes to 00:03:7F and the last 3 bytes
|
||||
+ * are derived from the last 6 characters of qcom_serial_number in reversed order.
|
||||
+ *
|
||||
+ * Returns 0 on success, negative error code on failure.
|
||||
+ */
|
||||
+static int generate_bdaddr_from_serial(struct hci_dev *hdev, bdaddr_t *bdaddr)
|
||||
+{
|
||||
+ size_t serial_len;
|
||||
+ const char *serial = qcom_serial_number;
|
||||
+ char last6[7] = {0}; // 6 characters + null terminator
|
||||
+ int i;
|
||||
+ int ret;
|
||||
+
|
||||
+ if (!serial) {
|
||||
+ bt_dev_err(hdev, "qcom_serial_number is NULL");
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+
|
||||
+ serial_len = strlen(serial);
|
||||
+ if (serial_len < 6) {
|
||||
+ bt_dev_err(hdev, "qcom_serial_number is too short: %zu characters", serial_len);
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+
|
||||
+ // Extract the last 6 characters
|
||||
+ strncpy(last6, serial + serial_len - 6, 6);
|
||||
+
|
||||
+ // Initialize the first 3 bytes
|
||||
+ bdaddr->b[5] = 0x00;
|
||||
+ bdaddr->b[4] = 0x03;
|
||||
+ bdaddr->b[3] = 0x7F;
|
||||
+
|
||||
+ // Convert the last 6 characters into 3 bytes in reversed order
|
||||
+ for (i = 0; i < 3; i++) {
|
||||
+ char byte_str[3] = {0};
|
||||
+ u8 byte_val;
|
||||
+
|
||||
+ byte_str[0] = last6[i * 2];
|
||||
+ byte_str[1] = last6[i * 2 + 1];
|
||||
+
|
||||
+ if (!isxdigit(byte_str[0]) || !isxdigit(byte_str[1])) {
|
||||
+ bt_dev_err(hdev, "Invalid hex characters in serial number: %c%c",
|
||||
+ byte_str[0], byte_str[1]);
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+
|
||||
+ ret = kstrtou8(byte_str, 16, &byte_val);
|
||||
+ if (ret < 0) {
|
||||
+ bt_dev_err(hdev, "Failed to convert hex string to u8: %c%c",
|
||||
+ byte_str[0], byte_str[1]);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ bdaddr->b[i] = byte_val; // Assign to bytes 0,1,2 (reversed order)
|
||||
+ }
|
||||
+
|
||||
+ bt_dev_info(hdev, "Generated BD_ADDR: %pMR", bdaddr);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
|
||||
int qca_read_soc_version(struct hci_dev *hdev, struct qca_btsoc_version *ver,
|
||||
enum qca_btsoc_type soc_type)
|
||||
@@ -714,7 +782,7 @@ int qca_set_bdaddr_rome(struct hci_dev *hdev, const bdaddr_t *bdaddr)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(qca_set_bdaddr_rome);
|
||||
|
||||
-static int qca_check_bdaddr(struct hci_dev *hdev, const struct qca_fw_config *config)
|
||||
+static int __maybe_unused qca_check_bdaddr(struct hci_dev *hdev, const struct qca_fw_config *config)
|
||||
{
|
||||
struct hci_rp_read_bd_addr *bda;
|
||||
struct sk_buff *skb;
|
||||
@@ -790,6 +858,7 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate,
|
||||
u8 rom_ver = 0;
|
||||
u32 soc_ver;
|
||||
u16 boardid = 0;
|
||||
+ bdaddr_t generated_bdaddr;
|
||||
|
||||
bt_dev_dbg(hdev, "QCA setup on UART");
|
||||
|
||||
@@ -993,9 +1062,26 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate,
|
||||
break;
|
||||
}
|
||||
|
||||
- err = qca_check_bdaddr(hdev, &config);
|
||||
- if (err)
|
||||
- return err;
|
||||
+ /* Generate BD_ADDR from qcom_serial_number */
|
||||
+ err = generate_bdaddr_from_serial(hdev, &generated_bdaddr);
|
||||
+ if (err) {
|
||||
+ bt_dev_warn(hdev, "Failed to generate BD_ADDR from serial number, falling back to NVM");
|
||||
+ err = qca_check_bdaddr(hdev, &config);
|
||||
+ if (err)
|
||||
+ return err;
|
||||
+ } else {
|
||||
+ /* Set the generated BD_ADDR */
|
||||
+ err = qca_set_bdaddr(hdev, &generated_bdaddr);
|
||||
+ if (err) {
|
||||
+ bt_dev_err(hdev, "Failed to set the generated BD_ADDR from serial number");
|
||||
+ return err;
|
||||
+ }
|
||||
+
|
||||
+ /* Update hdev->public_addr and hdev->bdaddr */
|
||||
+ bacpy(&hdev->public_addr, &generated_bdaddr);
|
||||
+ bacpy(&hdev->bdaddr, &generated_bdaddr);
|
||||
+ bt_dev_info(hdev, "BD_ADDR set to %pMR", &hdev->public_addr);
|
||||
+ }
|
||||
|
||||
bt_dev_info(hdev, "QCA setup on UART is completed");
|
||||
|
||||
diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c
|
||||
index 095b49a39..45ad5cbd2 100644
|
||||
--- a/drivers/net/wireless/ath/ath12k/mac.c
|
||||
+++ b/drivers/net/wireless/ath/ath12k/mac.c
|
||||
@@ -162,6 +162,91 @@ static const struct ieee80211_channel ath12k_6ghz_channels[] = {
|
||||
CHAN6G(233, 7115, 0),
|
||||
};
|
||||
|
||||
+extern const char *qcom_serial_number;
|
||||
+
|
||||
+/* Define a small struct for storing a 6-byte MAC address array */
|
||||
+struct macaddr_t {
|
||||
+ u8 b[ETH_ALEN];
|
||||
+};
|
||||
+
|
||||
+/* Define a static, predefined MAC_ADDR structure */
|
||||
+static const struct macaddr_t static_macaddr = {
|
||||
+ .b = { 0x00, 0x03, 0x7F, 0x11, 0x22, 0x33 }
|
||||
+};
|
||||
+
|
||||
+/**
|
||||
+ * generate_macaddr_from_serial - Generates a MAC_ADDR using the serial number
|
||||
+ * @macaddr: Pointer to macaddr_t structure to populate
|
||||
+ *
|
||||
+ * This function sets the first 3 bytes to 00:03:7F and the last 3 bytes
|
||||
+ * are derived from the last 6 characters of qcom_serial_number.
|
||||
+ *
|
||||
+ * Returns 0 on success, negative error code on failure.
|
||||
+ */
|
||||
+static int generate_macaddr_from_serial(struct ath12k *ar, struct macaddr_t *macaddr)
|
||||
+{
|
||||
+ size_t serial_len;
|
||||
+ const char *serial = qcom_serial_number;
|
||||
+ char last6[7] = {0}; // 6 characters + null terminator
|
||||
+ int i;
|
||||
+ int ret;
|
||||
+
|
||||
+ if (!serial) {
|
||||
+ ath12k_err(ar->ab, "qcom_serial_number is NULL");
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+
|
||||
+ serial_len = strlen(serial);
|
||||
+ if (serial_len < 6) {
|
||||
+ ath12k_err(ar->ab, "qcom_serial_number is too short: %zu characters", serial_len);
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+
|
||||
+ // Extract the last 6 characters
|
||||
+ strncpy(last6, serial + serial_len - 6, 6);
|
||||
+
|
||||
+ // Initialize the first 3 bytes
|
||||
+ macaddr->b[5] = 0x00;
|
||||
+ macaddr->b[4] = 0x03;
|
||||
+ macaddr->b[3] = 0x7F;
|
||||
+
|
||||
+ // Convert the last 6 characters into 3 bytes
|
||||
+ for (i = 0; i < 3; i++) {
|
||||
+ char byte_str[3] = {0};
|
||||
+ u8 byte_val;
|
||||
+
|
||||
+ byte_str[0] = last6[i * 2];
|
||||
+ byte_str[1] = last6[i * 2 + 1];
|
||||
+
|
||||
+ if (!isxdigit(byte_str[0]) || !isxdigit(byte_str[1])) {
|
||||
+ ath12k_err(ar->ab, "Invalid hex characters in serial number: %c%c",
|
||||
+ byte_str[0], byte_str[1]);
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+
|
||||
+ ret = kstrtou8(byte_str, 16, &byte_val);
|
||||
+ if (ret < 0) {
|
||||
+ ath12k_err(ar->ab, "Failed to convert hex string to u8: %c%c",
|
||||
+ byte_str[0], byte_str[1]);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ macaddr->b[2 - i] = byte_val; // Assign to bytes 2,1,0
|
||||
+ }
|
||||
+
|
||||
+ ath12k_info(ar->ab, "Generated MAC_ADDR: %pMR", macaddr);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static void ath12k_reverse_mac(u8 *dst, const u8 *src)
|
||||
+{
|
||||
+ int i;
|
||||
+
|
||||
+ for (i = 0; i < ETH_ALEN; i++)
|
||||
+ dst[i] = src[ETH_ALEN - 1 - i];
|
||||
+}
|
||||
+
|
||||
static struct ieee80211_rate ath12k_legacy_rates[] = {
|
||||
{ .bitrate = 10,
|
||||
.hw_value = ATH12K_HW_RATE_CCK_LP_1M },
|
||||
@@ -13723,6 +13808,7 @@ static int ath12k_mac_hw_register(struct ath12k_hw *ah)
|
||||
struct ath12k_base *ab = ar->ab;
|
||||
struct ath12k_pdev *pdev;
|
||||
struct ath12k_pdev_cap *cap;
|
||||
+ struct macaddr_t generated_macaddr;
|
||||
static const u32 cipher_suites[] = {
|
||||
WLAN_CIPHER_SUITE_TKIP,
|
||||
WLAN_CIPHER_SUITE_CCMP,
|
||||
@@ -13746,11 +13832,17 @@ static int ath12k_mac_hw_register(struct ath12k_hw *ah)
|
||||
u32 ht_cap_info = 0;
|
||||
|
||||
pdev = ar->pdev;
|
||||
- if (ar->ab->pdevs_macaddr_valid) {
|
||||
- ether_addr_copy(ar->mac_addr, pdev->mac_addr);
|
||||
+ ret = generate_macaddr_from_serial(ar, &generated_macaddr);
|
||||
+ if (ret) {
|
||||
+ if (ar->ab->pdevs_macaddr_valid) {
|
||||
+ ether_addr_copy(ar->mac_addr, pdev->mac_addr);
|
||||
+ } else {
|
||||
+ ether_addr_copy(ar->mac_addr, ar->ab->mac_addr);
|
||||
+ ar->mac_addr[4] += ar->pdev_idx;
|
||||
+ }
|
||||
} else {
|
||||
- ether_addr_copy(ar->mac_addr, ar->ab->mac_addr);
|
||||
- ar->mac_addr[4] += ar->pdev_idx;
|
||||
+ ath12k_reverse_mac(ar->mac_addr, generated_macaddr.b);
|
||||
+ ath12k_info(ab, "MAC_ADDR set to %pMR", generated_macaddr.b);
|
||||
}
|
||||
|
||||
ret = ath12k_mac_setup_register(ar, &ht_cap_info, hw->wiphy->bands);
|
||||
diff --git a/drivers/soc/qcom/socinfo.c b/drivers/soc/qcom/socinfo.c
|
||||
index 963772f45..b69c976da 100644
|
||||
--- a/drivers/soc/qcom/socinfo.c
|
||||
+++ b/drivers/soc/qcom/socinfo.c
|
||||
@@ -171,6 +171,10 @@ struct smem_image_version {
|
||||
};
|
||||
#endif /* CONFIG_DEBUG_FS */
|
||||
|
||||
+/* Global variable to hold the serial number */
|
||||
+const char *qcom_serial_number;
|
||||
+EXPORT_SYMBOL(qcom_serial_number);
|
||||
+
|
||||
struct qcom_socinfo {
|
||||
struct soc_device *soc_dev;
|
||||
struct soc_device_attribute attr;
|
||||
@@ -817,6 +821,9 @@ static int qcom_socinfo_probe(struct platform_device *pdev)
|
||||
le32_to_cpu(info->serial_num));
|
||||
if (!qs->attr.serial_number)
|
||||
return -ENOMEM;
|
||||
+
|
||||
+ /* Assign the serial number to the global variable */
|
||||
+ qcom_serial_number = qs->attr.serial_number;
|
||||
}
|
||||
|
||||
qs->soc_dev = soc_device_register(&qs->attr);
|
||||
@@ -840,6 +847,9 @@ static void qcom_socinfo_remove(struct platform_device *pdev)
|
||||
soc_device_unregister(qs->soc_dev);
|
||||
|
||||
socinfo_debugfs_exit(qs);
|
||||
+
|
||||
+ /* Clear the global serial number */
|
||||
+ qcom_serial_number = NULL;
|
||||
}
|
||||
|
||||
static struct platform_driver qcom_socinfo_driver = {
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@ -0,0 +1,540 @@
|
||||
From f3406cb7cea1b173c4e4c3bb1e8f23cda63ba03c Mon Sep 17 00:00:00 2001
|
||||
From: Alex Ling <ling_kasim@hotmail.com>
|
||||
Date: Sun, 18 Jan 2026 20:37:39 +0800
|
||||
Subject: [PATCH 16/18] HACK: Revert back to 6.12 aw88166 driver
|
||||
|
||||
Signed-off-by: Alex Ling <ling_kasim@hotmail.com>
|
||||
---
|
||||
sound/soc/codecs/aw88166.c | 378 ++++++++++---------------------------
|
||||
sound/soc/codecs/aw88166.h | 2 +-
|
||||
2 files changed, 105 insertions(+), 275 deletions(-)
|
||||
|
||||
diff --git a/sound/soc/codecs/aw88166.c b/sound/soc/codecs/aw88166.c
|
||||
index 28f62b991..e2636bde6 100644
|
||||
--- a/sound/soc/codecs/aw88166.c
|
||||
+++ b/sound/soc/codecs/aw88166.c
|
||||
@@ -11,8 +11,8 @@
|
||||
#include <linux/firmware.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/i2c.h>
|
||||
-#include <linux/minmax.h>
|
||||
#include <linux/regmap.h>
|
||||
+#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include "aw88166.h"
|
||||
#include "aw88395/aw88395_device.h"
|
||||
@@ -123,12 +123,14 @@ static int aw_dev_dsp_read(struct aw_device *aw_dev,
|
||||
case AW88166_DSP_16_DATA:
|
||||
ret = aw_dev_dsp_read_16bit(aw_dev, dsp_addr, dsp_data);
|
||||
if (ret)
|
||||
- dev_err(aw_dev->dev, "read dsp_addr[0x%x] 16-bit failed", (u32)dsp_addr);
|
||||
+ dev_err(aw_dev->dev, "read dsp_addr[0x%x] 16-bit dsp_data[0x%x] failed",
|
||||
+ (u32)dsp_addr, *dsp_data);
|
||||
break;
|
||||
case AW88166_DSP_32_DATA:
|
||||
ret = aw_dev_dsp_read_32bit(aw_dev, dsp_addr, dsp_data);
|
||||
if (ret)
|
||||
- dev_err(aw_dev->dev, "read dsp_addr[0x%x] 32-bit failed", (u32)dsp_addr);
|
||||
+ dev_err(aw_dev->dev, "read dsp_addr[0x%x] 32r-bit dsp_data[0x%x] failed",
|
||||
+ (u32)dsp_addr, *dsp_data);
|
||||
break;
|
||||
default:
|
||||
dev_err(aw_dev->dev, "data type[%d] unsupported", data_type);
|
||||
@@ -901,22 +903,45 @@ static int aw88166_dev_start(struct aw88166 *aw88166)
|
||||
static int aw_dev_dsp_update_container(struct aw_device *aw_dev,
|
||||
unsigned char *data, unsigned int len, unsigned short base)
|
||||
{
|
||||
- u32 tmp_len;
|
||||
int i, ret;
|
||||
|
||||
+#ifdef AW88166_DSP_I2C_WRITES
|
||||
+ u32 tmp_len;
|
||||
+
|
||||
mutex_lock(&aw_dev->dsp_lock);
|
||||
ret = regmap_write(aw_dev->regmap, AW88166_DSPMADD_REG, base);
|
||||
if (ret)
|
||||
goto error_operation;
|
||||
|
||||
for (i = 0; i < len; i += AW88166_MAX_RAM_WRITE_BYTE_SIZE) {
|
||||
- tmp_len = min(len - i, AW88166_MAX_RAM_WRITE_BYTE_SIZE);
|
||||
+ if ((len - i) < AW88166_MAX_RAM_WRITE_BYTE_SIZE)
|
||||
+ tmp_len = len - i;
|
||||
+ else
|
||||
+ tmp_len = AW88166_MAX_RAM_WRITE_BYTE_SIZE;
|
||||
+
|
||||
ret = regmap_raw_write(aw_dev->regmap, AW88166_DSPMDAT_REG,
|
||||
&data[i], tmp_len);
|
||||
if (ret)
|
||||
goto error_operation;
|
||||
}
|
||||
mutex_unlock(&aw_dev->dsp_lock);
|
||||
+#else
|
||||
+ __be16 reg_val;
|
||||
+
|
||||
+ mutex_lock(&aw_dev->dsp_lock);
|
||||
+ /* i2c write */
|
||||
+ ret = regmap_write(aw_dev->regmap, AW88166_DSPMADD_REG, base);
|
||||
+ if (ret)
|
||||
+ goto error_operation;
|
||||
+ for (i = 0; i < len; i += 2) {
|
||||
+ reg_val = cpu_to_be16p((u16 *)(data + i));
|
||||
+ ret = regmap_write(aw_dev->regmap, AW88166_DSPMDAT_REG,
|
||||
+ (u16)reg_val);
|
||||
+ if (ret)
|
||||
+ goto error_operation;
|
||||
+ }
|
||||
+ mutex_unlock(&aw_dev->dsp_lock);
|
||||
+#endif
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -1321,7 +1346,7 @@ static int aw_dev_check_sysint(struct aw_device *aw_dev)
|
||||
|
||||
aw_dev_get_int_status(aw_dev, ®_val);
|
||||
if (reg_val & AW88166_BIT_SYSINT_CHECK) {
|
||||
- dev_err(aw_dev->dev, "pa stop check fail:0x%04x\n", reg_val);
|
||||
+ dev_dbg(aw_dev->dev, "pa stop check fail:0x%04x\n", reg_val);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
@@ -1367,284 +1392,97 @@ static int aw88166_stop(struct aw_device *aw_dev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
-static struct snd_soc_dai_driver aw88166_dai[] = {
|
||||
- {
|
||||
- .name = "aw88166-aif",
|
||||
- .id = 1,
|
||||
- .playback = {
|
||||
- .stream_name = "Speaker_Playback",
|
||||
- .channels_min = 1,
|
||||
- .channels_max = 2,
|
||||
- .rates = AW88166_RATES,
|
||||
- .formats = AW88166_FORMATS,
|
||||
- },
|
||||
- .capture = {
|
||||
- .stream_name = "Speaker_Capture",
|
||||
- .channels_min = 1,
|
||||
- .channels_max = 2,
|
||||
- .rates = AW88166_RATES,
|
||||
- .formats = AW88166_FORMATS,
|
||||
- },
|
||||
- },
|
||||
-};
|
||||
-
|
||||
-static int aw88166_get_fade_in_time(struct snd_kcontrol *kcontrol,
|
||||
- struct snd_ctl_elem_value *ucontrol)
|
||||
-{
|
||||
- struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
|
||||
- struct aw88166 *aw88166 = snd_soc_component_get_drvdata(component);
|
||||
- struct aw_device *aw_dev = aw88166->aw_pa;
|
||||
-
|
||||
- ucontrol->value.integer.value[0] = aw_dev->fade_in_time;
|
||||
-
|
||||
- return 0;
|
||||
-}
|
||||
-
|
||||
-static int aw88166_set_fade_in_time(struct snd_kcontrol *kcontrol,
|
||||
- struct snd_ctl_elem_value *ucontrol)
|
||||
+static int aw88166_startup(struct snd_pcm_substream *substream,
|
||||
+ struct snd_soc_dai *dai)
|
||||
{
|
||||
- struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
|
||||
- struct aw88166 *aw88166 = snd_soc_component_get_drvdata(component);
|
||||
- struct soc_mixer_control *mc =
|
||||
- (struct soc_mixer_control *)kcontrol->private_value;
|
||||
- struct aw_device *aw_dev = aw88166->aw_pa;
|
||||
- int time;
|
||||
-
|
||||
- time = ucontrol->value.integer.value[0];
|
||||
-
|
||||
- if (time < mc->min || time > mc->max)
|
||||
- return -EINVAL;
|
||||
-
|
||||
- if (time != aw_dev->fade_in_time) {
|
||||
- aw_dev->fade_in_time = time;
|
||||
- return 1;
|
||||
- }
|
||||
-
|
||||
- return 0;
|
||||
-}
|
||||
-
|
||||
-static int aw88166_get_fade_out_time(struct snd_kcontrol *kcontrol,
|
||||
- struct snd_ctl_elem_value *ucontrol)
|
||||
-{
|
||||
- struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
|
||||
- struct aw88166 *aw88166 = snd_soc_component_get_drvdata(component);
|
||||
- struct aw_device *aw_dev = aw88166->aw_pa;
|
||||
-
|
||||
- ucontrol->value.integer.value[0] = aw_dev->fade_out_time;
|
||||
-
|
||||
- return 0;
|
||||
-}
|
||||
-
|
||||
-static int aw88166_set_fade_out_time(struct snd_kcontrol *kcontrol,
|
||||
- struct snd_ctl_elem_value *ucontrol)
|
||||
-{
|
||||
- struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
|
||||
- struct aw88166 *aw88166 = snd_soc_component_get_drvdata(component);
|
||||
- struct soc_mixer_control *mc =
|
||||
- (struct soc_mixer_control *)kcontrol->private_value;
|
||||
- struct aw_device *aw_dev = aw88166->aw_pa;
|
||||
- int time;
|
||||
-
|
||||
- time = ucontrol->value.integer.value[0];
|
||||
- if (time < mc->min || time > mc->max)
|
||||
- return -EINVAL;
|
||||
-
|
||||
- if (time != aw_dev->fade_out_time) {
|
||||
- aw_dev->fade_out_time = time;
|
||||
- return 1;
|
||||
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
+ dev_dbg(dai->dev, "STREAM_PLAYBACK start");
|
||||
+ } else {
|
||||
+ dev_dbg(dai->dev, "STREAM_CAPTURE start");
|
||||
}
|
||||
-
|
||||
- return 0;
|
||||
-}
|
||||
-
|
||||
-static int aw88166_dev_set_profile_index(struct aw_device *aw_dev, int index)
|
||||
-{
|
||||
- /* check the index whether is valid */
|
||||
- if ((index >= aw_dev->prof_info.count) || (index < 0))
|
||||
- return -EINVAL;
|
||||
- /* check the index whether change */
|
||||
- if (aw_dev->prof_index == index)
|
||||
- return -EINVAL;
|
||||
-
|
||||
- aw_dev->prof_index = index;
|
||||
- dev_dbg(aw_dev->dev, "set prof[%s]",
|
||||
- aw_dev->prof_info.prof_name_list[aw_dev->prof_info.prof_desc[index].id]);
|
||||
-
|
||||
return 0;
|
||||
}
|
||||
|
||||
-static int aw88166_profile_info(struct snd_kcontrol *kcontrol,
|
||||
- struct snd_ctl_elem_info *uinfo)
|
||||
+static int aw88166_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
|
||||
{
|
||||
- struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol);
|
||||
- struct aw88166 *aw88166 = snd_soc_component_get_drvdata(codec);
|
||||
- char *prof_name;
|
||||
- int count, ret;
|
||||
-
|
||||
- uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
|
||||
- uinfo->count = 1;
|
||||
-
|
||||
- count = aw88166->aw_pa->prof_info.count;
|
||||
- if (count <= 0) {
|
||||
- uinfo->value.enumerated.items = 0;
|
||||
- return 0;
|
||||
- }
|
||||
-
|
||||
- uinfo->value.enumerated.items = count;
|
||||
-
|
||||
- if (uinfo->value.enumerated.item >= count)
|
||||
- uinfo->value.enumerated.item = count - 1;
|
||||
-
|
||||
- count = uinfo->value.enumerated.item;
|
||||
-
|
||||
- ret = aw88166_dev_get_prof_name(aw88166->aw_pa, count, &prof_name);
|
||||
- if (ret) {
|
||||
- strscpy(uinfo->value.enumerated.name, "null");
|
||||
- return 0;
|
||||
- }
|
||||
-
|
||||
- strscpy(uinfo->value.enumerated.name, prof_name);
|
||||
+ dev_dbg(dai->dev, "fmt=0x%x", fmt);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
-static int aw88166_profile_get(struct snd_kcontrol *kcontrol,
|
||||
- struct snd_ctl_elem_value *ucontrol)
|
||||
+static int aw88166_set_dai_sysclk(struct snd_soc_dai *dai,
|
||||
+ int clk_id, unsigned int freq, int dir)
|
||||
{
|
||||
- struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol);
|
||||
- struct aw88166 *aw88166 = snd_soc_component_get_drvdata(codec);
|
||||
-
|
||||
- ucontrol->value.integer.value[0] = aw88166->aw_pa->prof_index;
|
||||
-
|
||||
+ dev_dbg(dai->dev, "freq=%d", freq);
|
||||
return 0;
|
||||
}
|
||||
|
||||
-static int aw88166_profile_set(struct snd_kcontrol *kcontrol,
|
||||
- struct snd_ctl_elem_value *ucontrol)
|
||||
+static int aw88166_hw_params(struct snd_pcm_substream *substream,
|
||||
+ struct snd_pcm_hw_params *params,
|
||||
+ struct snd_soc_dai *dai)
|
||||
{
|
||||
- struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol);
|
||||
- struct aw88166 *aw88166 = snd_soc_component_get_drvdata(codec);
|
||||
- int ret;
|
||||
-
|
||||
- mutex_lock(&aw88166->lock);
|
||||
- ret = aw88166_dev_set_profile_index(aw88166->aw_pa, ucontrol->value.integer.value[0]);
|
||||
- if (ret) {
|
||||
- dev_dbg(codec->dev, "profile index does not change");
|
||||
- mutex_unlock(&aw88166->lock);
|
||||
- return 0;
|
||||
- }
|
||||
-
|
||||
- if (aw88166->aw_pa->status) {
|
||||
- aw88166_stop(aw88166->aw_pa);
|
||||
- aw88166_start(aw88166, AW88166_SYNC_START);
|
||||
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
|
||||
+ dev_dbg(dai->dev,
|
||||
+ "STREAM_CAPTURE requested rate: %d, width = %d",
|
||||
+ params_rate(params), params_width(params));
|
||||
}
|
||||
|
||||
- mutex_unlock(&aw88166->lock);
|
||||
-
|
||||
- return 1;
|
||||
-}
|
||||
-
|
||||
-static int aw88166_volume_get(struct snd_kcontrol *kcontrol,
|
||||
- struct snd_ctl_elem_value *ucontrol)
|
||||
-{
|
||||
- struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol);
|
||||
- struct aw88166 *aw88166 = snd_soc_component_get_drvdata(codec);
|
||||
- struct aw_volume_desc *vol_desc = &aw88166->aw_pa->volume_desc;
|
||||
-
|
||||
- ucontrol->value.integer.value[0] = vol_desc->ctl_volume;
|
||||
-
|
||||
- return 0;
|
||||
-}
|
||||
-
|
||||
-static int aw88166_volume_set(struct snd_kcontrol *kcontrol,
|
||||
- struct snd_ctl_elem_value *ucontrol)
|
||||
-{
|
||||
- struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol);
|
||||
- struct aw88166 *aw88166 = snd_soc_component_get_drvdata(codec);
|
||||
- struct aw_volume_desc *vol_desc = &aw88166->aw_pa->volume_desc;
|
||||
- struct soc_mixer_control *mc =
|
||||
- (struct soc_mixer_control *)kcontrol->private_value;
|
||||
- int value;
|
||||
-
|
||||
- value = ucontrol->value.integer.value[0];
|
||||
- if (value < mc->min || value > mc->max)
|
||||
- return -EINVAL;
|
||||
-
|
||||
- if (vol_desc->ctl_volume != value) {
|
||||
- vol_desc->ctl_volume = value;
|
||||
- aw_dev_set_volume(aw88166->aw_pa, vol_desc->ctl_volume);
|
||||
-
|
||||
- return 1;
|
||||
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
+ dev_dbg(dai->dev,
|
||||
+ "STREAM_PLAYBACK requested rate: %d, width = %d",
|
||||
+ params_rate(params), params_width(params));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
-static int aw88166_get_fade_step(struct snd_kcontrol *kcontrol,
|
||||
- struct snd_ctl_elem_value *ucontrol)
|
||||
+static int aw88166_mute(struct snd_soc_dai *dai, int mute, int stream)
|
||||
{
|
||||
- struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol);
|
||||
- struct aw88166 *aw88166 = snd_soc_component_get_drvdata(codec);
|
||||
-
|
||||
- ucontrol->value.integer.value[0] = aw88166->aw_pa->fade_step;
|
||||
+ dev_dbg(dai->dev, "mute state=%d", mute);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
-static int aw88166_set_fade_step(struct snd_kcontrol *kcontrol,
|
||||
- struct snd_ctl_elem_value *ucontrol)
|
||||
+static void aw88166_shutdown(struct snd_pcm_substream *substream,
|
||||
+ struct snd_soc_dai *dai)
|
||||
{
|
||||
- struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol);
|
||||
- struct aw88166 *aw88166 = snd_soc_component_get_drvdata(codec);
|
||||
- struct soc_mixer_control *mc =
|
||||
- (struct soc_mixer_control *)kcontrol->private_value;
|
||||
- int value;
|
||||
-
|
||||
- value = ucontrol->value.integer.value[0];
|
||||
- if (value < mc->min || value > mc->max)
|
||||
- return -EINVAL;
|
||||
-
|
||||
- if (aw88166->aw_pa->fade_step != value) {
|
||||
- aw88166->aw_pa->fade_step = value;
|
||||
- return 1;
|
||||
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK){
|
||||
+ dev_dbg(dai->dev, "STREAM_PLAYBACK stop");
|
||||
+ } else {
|
||||
+ dev_dbg(dai->dev, "STREAM_CAPTURE stop");
|
||||
}
|
||||
-
|
||||
- return 0;
|
||||
}
|
||||
|
||||
-static int aw88166_re_get(struct snd_kcontrol *kcontrol,
|
||||
- struct snd_ctl_elem_value *ucontrol)
|
||||
-{
|
||||
- struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol);
|
||||
- struct aw88166 *aw88166 = snd_soc_component_get_drvdata(codec);
|
||||
- struct aw_device *aw_dev = aw88166->aw_pa;
|
||||
-
|
||||
- ucontrol->value.integer.value[0] = aw_dev->cali_desc.cali_re;
|
||||
-
|
||||
- return 0;
|
||||
-}
|
||||
-
|
||||
-static int aw88166_re_set(struct snd_kcontrol *kcontrol,
|
||||
- struct snd_ctl_elem_value *ucontrol)
|
||||
-{
|
||||
- struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol);
|
||||
- struct aw88166 *aw88166 = snd_soc_component_get_drvdata(codec);
|
||||
- struct soc_mixer_control *mc =
|
||||
- (struct soc_mixer_control *)kcontrol->private_value;
|
||||
- struct aw_device *aw_dev = aw88166->aw_pa;
|
||||
- int value;
|
||||
-
|
||||
- value = ucontrol->value.integer.value[0];
|
||||
- if (value < mc->min || value > mc->max)
|
||||
- return -EINVAL;
|
||||
-
|
||||
- if (aw_dev->cali_desc.cali_re != value) {
|
||||
- aw_dev->cali_desc.cali_re = value;
|
||||
- return 1;
|
||||
- }
|
||||
+static const struct snd_soc_dai_ops aw88166_dai_ops = {
|
||||
+ .startup = aw88166_startup,
|
||||
+ .set_fmt = aw88166_set_fmt,
|
||||
+ .set_sysclk = aw88166_set_dai_sysclk,
|
||||
+ .hw_params = aw88166_hw_params,
|
||||
+ .mute_stream = aw88166_mute,
|
||||
+ .shutdown = aw88166_shutdown,
|
||||
+};
|
||||
|
||||
- return 0;
|
||||
-}
|
||||
+static struct snd_soc_dai_driver aw88166_dai[] = {
|
||||
+ {
|
||||
+ .name = "aw88166-aif",
|
||||
+ .id = 1,
|
||||
+ .playback = {
|
||||
+ .stream_name = "Speaker_Playback",
|
||||
+ .channels_min = 1,
|
||||
+ .channels_max = 2,
|
||||
+ .rates = AW88166_RATES,
|
||||
+ .formats = AW88166_FORMATS,
|
||||
+ },
|
||||
+ .capture = {
|
||||
+ .stream_name = "Speaker_Capture",
|
||||
+ .channels_min = 1,
|
||||
+ .channels_max = 2,
|
||||
+ .rates = AW88166_RATES,
|
||||
+ .formats = AW88166_FORMATS,
|
||||
+ },
|
||||
+ .ops = &aw88166_dai_ops,
|
||||
+ },
|
||||
+};
|
||||
|
||||
static int aw88166_dev_init(struct aw88166 *aw88166, struct aw_container *aw_cfg)
|
||||
{
|
||||
@@ -1686,19 +1524,27 @@ static int aw88166_dev_init(struct aw88166 *aw88166, struct aw_container *aw_cfg
|
||||
|
||||
static int aw88166_request_firmware_file(struct aw88166 *aw88166)
|
||||
{
|
||||
+ struct aw_device *aw_dev = aw88166->aw_pa;
|
||||
+ struct device_node *np = aw_dev->dev->of_node;
|
||||
const struct firmware *cont = NULL;
|
||||
+ const char *fw_name;
|
||||
int ret;
|
||||
|
||||
aw88166->aw_pa->fw_status = AW88166_DEV_FW_FAILED;
|
||||
|
||||
- ret = request_firmware(&cont, AW88166_ACF_FILE, aw88166->aw_pa->dev);
|
||||
+ ret = of_property_read_string(np, "firmware-name", &fw_name);
|
||||
+ if (ret < 0) {
|
||||
+ fw_name = AW88166_ACF_FILE;
|
||||
+ }
|
||||
+
|
||||
+ ret = request_firmware(&cont, fw_name, aw88166->aw_pa->dev);
|
||||
if (ret) {
|
||||
- dev_err(aw88166->aw_pa->dev, "request [%s] failed!\n", AW88166_ACF_FILE);
|
||||
+ dev_err(aw88166->aw_pa->dev, "request [%s] failed!\n", fw_name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dev_dbg(aw88166->aw_pa->dev, "loaded %s - size: %zu\n",
|
||||
- AW88166_ACF_FILE, cont ? cont->size : 0);
|
||||
+ fw_name, cont ? cont->size : 0);
|
||||
|
||||
aw88166->aw_cfg = devm_kzalloc(aw88166->aw_pa->dev,
|
||||
struct_size(aw88166->aw_cfg, data, cont->size), GFP_KERNEL);
|
||||
@@ -1712,7 +1558,7 @@ static int aw88166_request_firmware_file(struct aw88166 *aw88166)
|
||||
|
||||
ret = aw88395_dev_load_acf_check(aw88166->aw_pa, aw88166->aw_cfg);
|
||||
if (ret) {
|
||||
- dev_err(aw88166->aw_pa->dev, "load [%s] failed!\n", AW88166_ACF_FILE);
|
||||
+ dev_err(aw88166->aw_pa->dev, "load [%s] failed!\n", fw_name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1726,22 +1572,6 @@ static int aw88166_request_firmware_file(struct aw88166 *aw88166)
|
||||
return ret;
|
||||
}
|
||||
|
||||
-static const struct snd_kcontrol_new aw88166_controls[] = {
|
||||
- SOC_SINGLE_EXT("PCM Playback Volume", AW88166_SYSCTRL2_REG,
|
||||
- 6, AW88166_MUTE_VOL, 0, aw88166_volume_get,
|
||||
- aw88166_volume_set),
|
||||
- SOC_SINGLE_EXT("Fade Step", 0, 0, AW88166_MUTE_VOL, 0,
|
||||
- aw88166_get_fade_step, aw88166_set_fade_step),
|
||||
- SOC_SINGLE_EXT("Volume Ramp Up Step", 0, 0, FADE_TIME_MAX, FADE_TIME_MIN,
|
||||
- aw88166_get_fade_in_time, aw88166_set_fade_in_time),
|
||||
- SOC_SINGLE_EXT("Volume Ramp Down Step", 0, 0, FADE_TIME_MAX, FADE_TIME_MIN,
|
||||
- aw88166_get_fade_out_time, aw88166_set_fade_out_time),
|
||||
- SOC_SINGLE_EXT("Calib", 0, 0, AW88166_CALI_RE_MAX, 0,
|
||||
- aw88166_re_get, aw88166_re_set),
|
||||
- AW88166_PROFILE_EXT("AW88166 Profile Set", aw88166_profile_info,
|
||||
- aw88166_profile_get, aw88166_profile_set),
|
||||
-};
|
||||
-
|
||||
static int aw88166_playback_event(struct snd_soc_dapm_widget *w,
|
||||
struct snd_kcontrol *k, int event)
|
||||
{
|
||||
@@ -1809,8 +1639,6 @@ static const struct snd_soc_component_driver soc_codec_dev_aw88166 = {
|
||||
.num_dapm_widgets = ARRAY_SIZE(aw88166_dapm_widgets),
|
||||
.dapm_routes = aw88166_audio_map,
|
||||
.num_dapm_routes = ARRAY_SIZE(aw88166_audio_map),
|
||||
- .controls = aw88166_controls,
|
||||
- .num_controls = ARRAY_SIZE(aw88166_controls),
|
||||
};
|
||||
|
||||
static void aw88166_hw_reset(struct aw88166 *aw88166)
|
||||
@@ -1820,6 +1648,8 @@ static void aw88166_hw_reset(struct aw88166 *aw88166)
|
||||
usleep_range(AW88166_1000_US, AW88166_1000_US + 10);
|
||||
gpiod_set_value_cansleep(aw88166->reset_gpio, 0);
|
||||
usleep_range(AW88166_1000_US, AW88166_1000_US + 10);
|
||||
+ gpiod_set_value_cansleep(aw88166->reset_gpio, 1);
|
||||
+ usleep_range(AW88166_1000_US, AW88166_1000_US + 10);
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/sound/soc/codecs/aw88166.h b/sound/soc/codecs/aw88166.h
|
||||
index 3a53ba0ac..8b5c83a48 100644
|
||||
--- a/sound/soc/codecs/aw88166.h
|
||||
+++ b/sound/soc/codecs/aw88166.h
|
||||
@@ -387,7 +387,7 @@
|
||||
|
||||
#define AW88166_NOISE_GATE_EN_START_BIT (13)
|
||||
#define AW88166_NOISE_GATE_EN_BITS_LEN (1)
|
||||
-#define AW88166_NOISE_GATE_EN_MASK \
|
||||
+#define AW88166_NOISE_GATE_EN_MASK \
|
||||
(~(((1<<AW88166_NOISE_GATE_EN_BITS_LEN)-1) << AW88166_NOISE_GATE_EN_START_BIT))
|
||||
|
||||
#define AW88166_WDT_CNT_START_BIT (0)
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@ -0,0 +1,407 @@
|
||||
From 46a8c2454f5e228718a36f046d0e42156ded57e6 Mon Sep 17 00:00:00 2001
|
||||
From: BigfootACA <bigfoot@classfun.cn>
|
||||
Date: Sat, 24 Jan 2026 19:45:33 +0800
|
||||
Subject: [PATCH 17/18] pwm: Add SI-EN SN3112 PWM support
|
||||
|
||||
Add a new driver for the SI-EN SN3112 12-channel 8-bit PWM LED controller.
|
||||
|
||||
Signed-off-by: Alex Ling <ling_kasim@hotmail.com>
|
||||
---
|
||||
drivers/pwm/Kconfig | 10 ++
|
||||
drivers/pwm/Makefile | 1 +
|
||||
drivers/pwm/pwm-sn3112.c | 350 +++++++++++++++++++++++++++++++++++++++
|
||||
3 files changed, 361 insertions(+)
|
||||
create mode 100644 drivers/pwm/pwm-sn3112.c
|
||||
|
||||
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
|
||||
index c2fd3f4b6..c05ac85c4 100644
|
||||
--- a/drivers/pwm/Kconfig
|
||||
+++ b/drivers/pwm/Kconfig
|
||||
@@ -658,6 +658,16 @@ config PWM_SOPHGO_SG2042
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called pwm_sophgo_sg2042.
|
||||
|
||||
+config PWM_SN3112
|
||||
+ tristate "SI-EN SN3112 PWM driver"
|
||||
+ depends on I2C
|
||||
+ select REGMAP_I2C
|
||||
+ help
|
||||
+ Generic PWM framework driver for SI-EN SN3112 LED controller.
|
||||
+
|
||||
+ To compile this driver as a module, choose M here: the module
|
||||
+ will be called pwm-sn3112.
|
||||
+
|
||||
config PWM_SPEAR
|
||||
tristate "STMicroelectronics SPEAr PWM support"
|
||||
depends on PLAT_SPEAR || COMPILE_TEST
|
||||
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
|
||||
index dfa8b4966..c507bebf8 100644
|
||||
--- a/drivers/pwm/Makefile
|
||||
+++ b/drivers/pwm/Makefile
|
||||
@@ -58,6 +58,7 @@ obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o
|
||||
obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o
|
||||
obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o
|
||||
obj-$(CONFIG_PWM_SL28CPLD) += pwm-sl28cpld.o
|
||||
+obj-$(CONFIG_PWM_SN3112) += pwm-sn3112.o
|
||||
obj-$(CONFIG_PWM_SOPHGO_SG2042) += pwm-sophgo-sg2042.o
|
||||
obj-$(CONFIG_PWM_SPEAR) += pwm-spear.o
|
||||
obj-$(CONFIG_PWM_SPRD) += pwm-sprd.o
|
||||
diff --git a/drivers/pwm/pwm-sn3112.c b/drivers/pwm/pwm-sn3112.c
|
||||
new file mode 100644
|
||||
index 000000000..18345b19b
|
||||
--- /dev/null
|
||||
+++ b/drivers/pwm/pwm-sn3112.c
|
||||
@@ -0,0 +1,350 @@
|
||||
+// SPDX-License-Identifier: GPL-2.0-only
|
||||
+/*
|
||||
+ * Driver for SN3112 12-channel 8-bit PWM LED controller
|
||||
+ *
|
||||
+ * Copyright (c) 2024 Junhao Xie <bigfoot@classfun.cn>
|
||||
+ *
|
||||
+ */
|
||||
+
|
||||
+#include <linux/i2c.h>
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/mutex.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/pwm.h>
|
||||
+#include <linux/regmap.h>
|
||||
+#include <linux/delay.h>
|
||||
+#include <linux/gpio/consumer.h>
|
||||
+#include <linux/regulator/consumer.h>
|
||||
+
|
||||
+#define SN3112_CHANNELS 12
|
||||
+#define SN3112_REG_ENABLE 0x00
|
||||
+#define SN3112_REG_PWM_VAL 0x04
|
||||
+#define SN3112_REG_PWM_EN 0x13
|
||||
+#define SN3112_REG_APPLY 0x16
|
||||
+#define SN3112_REG_RESET 0x17
|
||||
+
|
||||
+struct sn3112 {
|
||||
+ struct device *pdev;
|
||||
+ struct regmap *regmap;
|
||||
+ struct mutex lock;
|
||||
+ struct regulator *vdd;
|
||||
+ uint8_t pwm_val[SN3112_CHANNELS];
|
||||
+ uint8_t pwm_en_reg[3];
|
||||
+ bool pwm_en[SN3112_CHANNELS];
|
||||
+#if IS_ENABLED(CONFIG_GPIOLIB)
|
||||
+ struct gpio_desc *sdb;
|
||||
+#endif
|
||||
+};
|
||||
+
|
||||
+static int sn3112_write_reg(struct sn3112 *priv, unsigned int reg,
|
||||
+ unsigned int val)
|
||||
+{
|
||||
+ int err;
|
||||
+
|
||||
+ dev_dbg(priv->pdev, "request regmap_write 0x%x 0x%x\n", reg, val);
|
||||
+ err = regmap_write(priv->regmap, reg, val);
|
||||
+ if (err)
|
||||
+ dev_warn_ratelimited(
|
||||
+ priv->pdev,
|
||||
+ "regmap_write to register 0x%x failed: %pe\n", reg,
|
||||
+ ERR_PTR(err));
|
||||
+
|
||||
+ return err;
|
||||
+}
|
||||
+
|
||||
+static int sn3112_set_en_reg(struct sn3112 *priv, unsigned int channel,
|
||||
+ bool enabled, bool write)
|
||||
+{
|
||||
+ unsigned int reg, bit;
|
||||
+
|
||||
+ if (channel >= SN3112_CHANNELS)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ /* LED_EN1: BIT5:BIT3 = OUT3:OUT1 */
|
||||
+ if (channel >= 0 && channel <= 2)
|
||||
+ reg = 0, bit = channel + 3;
|
||||
+ /* LED_EN2: BIT5:BIT0 = OUT9:OUT4 */
|
||||
+ else if (channel >= 3 && channel <= 8)
|
||||
+ reg = 1, bit = channel - 3;
|
||||
+ /* LED_EN3: BIT2:BIT0 = OUT12:OUT10 */
|
||||
+ else if (channel >= 9 && channel <= 11)
|
||||
+ reg = 2, bit = channel - 9;
|
||||
+ else
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ dev_dbg(priv->pdev, "channel %u enabled %u\n", channel, enabled);
|
||||
+ dev_dbg(priv->pdev, "reg %u bit %u\n", reg, bit);
|
||||
+ if (enabled)
|
||||
+ priv->pwm_en_reg[reg] |= BIT(bit);
|
||||
+ else
|
||||
+ priv->pwm_en_reg[reg] &= ~BIT(bit);
|
||||
+ dev_dbg(priv->pdev, "set enable reg %u to %u\n", reg,
|
||||
+ priv->pwm_en_reg[reg]);
|
||||
+
|
||||
+ if (!write)
|
||||
+ return 0;
|
||||
+ return sn3112_write_reg(priv, SN3112_REG_PWM_EN + reg,
|
||||
+ priv->pwm_en_reg[reg]);
|
||||
+}
|
||||
+
|
||||
+static int sn3112_set_val_reg(struct sn3112 *priv, unsigned int channel,
|
||||
+ uint8_t val, bool write)
|
||||
+{
|
||||
+ if (channel >= SN3112_CHANNELS)
|
||||
+ return -EINVAL;
|
||||
+ priv->pwm_val[channel] = val;
|
||||
+ dev_dbg(priv->pdev, "set value reg %u to %u\n", channel,
|
||||
+ priv->pwm_val[channel]);
|
||||
+
|
||||
+ if (!write)
|
||||
+ return 0;
|
||||
+ return sn3112_write_reg(priv, SN3112_REG_PWM_VAL + channel,
|
||||
+ priv->pwm_val[channel]);
|
||||
+}
|
||||
+
|
||||
+static int sn3112_write_all(struct sn3112 *priv)
|
||||
+{
|
||||
+ int i, ret;
|
||||
+
|
||||
+ /* regenerate enable register values */
|
||||
+ for (i = 0; i < SN3112_CHANNELS; i++) {
|
||||
+ ret = sn3112_set_en_reg(priv, i, priv->pwm_en[i], false);
|
||||
+ if (ret != 0)
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ /* use random value to clear all registers */
|
||||
+ ret = sn3112_write_reg(priv, SN3112_REG_RESET, 0x66);
|
||||
+ if (ret != 0)
|
||||
+ return ret;
|
||||
+
|
||||
+ /* set software enable register */
|
||||
+ ret = sn3112_write_reg(priv, SN3112_REG_ENABLE, 1);
|
||||
+ if (ret != 0)
|
||||
+ return ret;
|
||||
+
|
||||
+ /* rewrite pwm value register */
|
||||
+ for (i = 0; i < SN3112_CHANNELS; i++) {
|
||||
+ ret = sn3112_write_reg(priv, SN3112_REG_PWM_VAL + i,
|
||||
+ priv->pwm_val[i]);
|
||||
+ if (ret != 0)
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ /* rewrite pwm enable register */
|
||||
+ for (i = 0; i < 3; i++) {
|
||||
+ ret = sn3112_write_reg(priv, SN3112_REG_PWM_EN + i,
|
||||
+ priv->pwm_en_reg[i]);
|
||||
+ if (ret != 0)
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ /* use random value to apply changes */
|
||||
+ ret = sn3112_write_reg(priv, SN3112_REG_APPLY, 0x66);
|
||||
+ if (ret != 0)
|
||||
+ return ret;
|
||||
+
|
||||
+ dev_dbg(priv->pdev, "reinitialized\n");
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int sn3112_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||
+{
|
||||
+ struct sn3112 *priv = pwmchip_get_drvdata(chip);
|
||||
+
|
||||
+ if (pwm->hwpwm >= SN3112_CHANNELS)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ dev_dbg(priv->pdev, "sn3112 request channel %u\n", pwm->hwpwm);
|
||||
+ pwm->args.period = 1000000;
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int sn3112_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
+ const struct pwm_state *state)
|
||||
+{
|
||||
+ int ret;
|
||||
+ u64 val = 0;
|
||||
+ struct sn3112 *priv = pwmchip_get_drvdata(chip);
|
||||
+
|
||||
+ if (pwm->hwpwm >= SN3112_CHANNELS)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ if (state->polarity != PWM_POLARITY_NORMAL)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ if (state->period <= 0)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ val = mul_u64_u64_div_u64(state->duty_cycle, 0xff, state->period);
|
||||
+ dev_dbg(priv->pdev, "duty_cycle %llu period %llu\n", state->duty_cycle,
|
||||
+ state->period);
|
||||
+ dev_dbg(priv->pdev, "set channel %u value to %llu\n", pwm->hwpwm, val);
|
||||
+ dev_dbg(priv->pdev, "set channel %u enabled to %u\n", pwm->hwpwm,
|
||||
+ state->enabled);
|
||||
+
|
||||
+ mutex_lock(&priv->lock);
|
||||
+ ret = sn3112_set_en_reg(priv, pwm->hwpwm, state->enabled, true);
|
||||
+ if (ret)
|
||||
+ goto out;
|
||||
+ ret = sn3112_set_val_reg(priv, pwm->hwpwm, val, true);
|
||||
+ if (ret)
|
||||
+ goto out;
|
||||
+ ret = sn3112_write_reg(priv, SN3112_REG_APPLY, 0x66);
|
||||
+ if (ret)
|
||||
+ goto out;
|
||||
+out:
|
||||
+ mutex_unlock(&priv->lock);
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static const struct pwm_ops sn3112_pwm_ops = {
|
||||
+ .apply = sn3112_pwm_apply,
|
||||
+ .request = sn3112_pwm_request,
|
||||
+};
|
||||
+
|
||||
+static const struct regmap_config sn3112_regmap_i2c_config = {
|
||||
+ .reg_bits = 8,
|
||||
+ .val_bits = 8,
|
||||
+ .max_register = 24,
|
||||
+ .cache_type = REGCACHE_NONE,
|
||||
+};
|
||||
+
|
||||
+static int sn3112_pwm_probe(struct i2c_client *client)
|
||||
+{
|
||||
+ struct pwm_chip *chip;
|
||||
+ struct sn3112 *priv;
|
||||
+ int ret, i;
|
||||
+
|
||||
+ dev_dbg(&client->dev, "probing\n");
|
||||
+ chip = devm_pwmchip_alloc(&client->dev, SN3112_CHANNELS, sizeof(*priv));
|
||||
+ if (IS_ERR(chip))
|
||||
+ return PTR_ERR(chip);
|
||||
+ priv = pwmchip_get_drvdata(chip);
|
||||
+ priv->pdev = &client->dev;
|
||||
+
|
||||
+ /* initialize sn3112 (chip does not support read command) */
|
||||
+ for (i = 0; i < SN3112_CHANNELS; i++)
|
||||
+ priv->pwm_en[i] = false;
|
||||
+ for (i = 0; i < SN3112_CHANNELS; i++)
|
||||
+ priv->pwm_val[i] = 0;
|
||||
+ for (i = 0; i < 3; i++)
|
||||
+ priv->pwm_en_reg[i] = 0;
|
||||
+
|
||||
+ /* enable sn5112 power vdd */
|
||||
+ priv->vdd = devm_regulator_get(priv->pdev, "vdd");
|
||||
+ if (IS_ERR(priv->vdd)) {
|
||||
+ ret = PTR_ERR(priv->vdd);
|
||||
+ dev_err(priv->pdev, "Unable to get vdd regulator: %d\n", ret);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+#if IS_ENABLED(CONFIG_GPIOLIB)
|
||||
+ /* sn5112 hardware shutdown pin */
|
||||
+ priv->sdb = devm_gpiod_get_optional(priv->pdev, "sdb", GPIOD_OUT_LOW);
|
||||
+ if (IS_ERR(priv->sdb))
|
||||
+ return dev_err_probe(priv->pdev, PTR_ERR(priv->sdb),
|
||||
+ "Unable to get sdb gpio\n");;
|
||||
+#endif
|
||||
+
|
||||
+ /* enable sn5112 power vdd */
|
||||
+ ret = regulator_enable(priv->vdd);
|
||||
+ if (ret < 0) {
|
||||
+ dev_err(priv->pdev, "Unable to enable regulator: %d\n", ret);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ priv->regmap = devm_regmap_init_i2c(client, &sn3112_regmap_i2c_config);
|
||||
+ if (IS_ERR(priv->regmap)) {
|
||||
+ ret = PTR_ERR(priv->regmap);
|
||||
+ dev_err(priv->pdev, "Failed to initialize register map: %d\n",
|
||||
+ ret);
|
||||
+ goto err_disable_vdd;
|
||||
+ }
|
||||
+
|
||||
+ i2c_set_clientdata(client, chip);
|
||||
+ mutex_init(&priv->lock);
|
||||
+
|
||||
+ chip->ops = &sn3112_pwm_ops;
|
||||
+ ret = pwmchip_add(chip);
|
||||
+ if (ret < 0)
|
||||
+ goto err_disable_vdd;
|
||||
+
|
||||
+#if IS_ENABLED(CONFIG_GPIOLIB)
|
||||
+ /* disable hardware shutdown pin */
|
||||
+ if (priv->sdb)
|
||||
+ gpiod_set_value(priv->sdb, 0);
|
||||
+#endif
|
||||
+
|
||||
+ /* initialize registers */
|
||||
+ ret = sn3112_write_all(priv);
|
||||
+ if (ret != 0) {
|
||||
+ dev_err(priv->pdev, "Failed to initialize sn3112: %d\n", ret);
|
||||
+ goto err_remove_chip;
|
||||
+ }
|
||||
+
|
||||
+ dev_info(&client->dev,
|
||||
+ "Found SI-EN SN3112 12-channel 8-bit PWM LED controller\n");
|
||||
+ return 0;
|
||||
+
|
||||
+err_remove_chip:
|
||||
+ pwmchip_remove(chip);
|
||||
+err_disable_vdd:
|
||||
+ regulator_disable(priv->vdd);
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static void sn3112_pwm_remove(struct i2c_client *client)
|
||||
+{
|
||||
+ struct pwm_chip *chip = i2c_get_clientdata(client);
|
||||
+ struct sn3112 *priv = pwmchip_get_drvdata(chip);
|
||||
+
|
||||
+ dev_dbg(priv->pdev, "remove\n");
|
||||
+
|
||||
+ /* set software enable register */
|
||||
+ sn3112_write_reg(priv, SN3112_REG_ENABLE, 0);
|
||||
+
|
||||
+ /* use random value to apply changes */
|
||||
+ sn3112_write_reg(priv, SN3112_REG_APPLY, 0x66);
|
||||
+
|
||||
+#if IS_ENABLED(CONFIG_GPIOLIB)
|
||||
+ /* enable hardware shutdown pin */
|
||||
+ if (priv->sdb)
|
||||
+ gpiod_set_value(priv->sdb, 1);
|
||||
+#endif
|
||||
+
|
||||
+ /* power-off sn5112 power vdd */
|
||||
+ regulator_disable(priv->vdd);
|
||||
+
|
||||
+ pwmchip_remove(chip);
|
||||
+}
|
||||
+
|
||||
+static const struct i2c_device_id sn3112_id[] = {
|
||||
+ { "sn3112", 0 },
|
||||
+ { /* sentinel */ },
|
||||
+};
|
||||
+MODULE_DEVICE_TABLE(i2c, sn3112_id);
|
||||
+
|
||||
+#ifdef CONFIG_OF
|
||||
+static const struct of_device_id sn3112_dt_ids[] = {
|
||||
+ { .compatible = "si-en,sn3112-pwm", },
|
||||
+ { /* sentinel */ }
|
||||
+};
|
||||
+MODULE_DEVICE_TABLE(of, sn3112_dt_ids);
|
||||
+#endif
|
||||
+
|
||||
+static struct i2c_driver sn3112_i2c_driver = {
|
||||
+ .driver = {
|
||||
+ .name = "sn3112-pwm",
|
||||
+ .of_match_table = of_match_ptr(sn3112_dt_ids),
|
||||
+ },
|
||||
+ .probe = sn3112_pwm_probe,
|
||||
+ .remove = sn3112_pwm_remove,
|
||||
+ .id_table = sn3112_id,
|
||||
+};
|
||||
+
|
||||
+module_i2c_driver(sn3112_i2c_driver);
|
||||
+
|
||||
+MODULE_AUTHOR("BigfootACA <bigfoot@classfun.cn>");
|
||||
+MODULE_DESCRIPTION("PWM driver for SI-EN SN3112");
|
||||
+MODULE_LICENSE("GPL");
|
||||
--
|
||||
2.43.0
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user