sm8550: Add patches for 6.18

Signed-off-by: Alex Ling <ling_kasim@hotmail.com>
This commit is contained in:
Alex Ling 2026-01-23 10:24:10 +08:00 committed by Igor
parent 6b1f235243
commit b9b5fb8b0c
19 changed files with 11821 additions and 41 deletions

View File

@ -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

View File

@ -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

View File

@ -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, &current_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

View File

@ -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

View File

@ -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", &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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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, &reg_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

View File

@ -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