diff --git a/target/linux/ramips/modules.mk b/target/linux/ramips/modules.mk index 175ba94eff9..22a9660b0f3 100644 --- a/target/linux/ramips/modules.mk +++ b/target/linux/ramips/modules.mk @@ -152,3 +152,21 @@ define KernelPackage/sound-mt7620/description endef $(eval $(call KernelPackage,sound-mt7620)) + + +define KernelPackage/keyboard-sx951x + SUBMENU:=Other modules + TITLE:=Semtech SX9512/SX9513 + DEPENDS:=@TARGET_ramips_mt7621 +kmod-input-core + KCONFIG:= \ + CONFIG_KEYBOARD_SX951X \ + CONFIG_INPUT_KEYBOARD=y + FILES:=$(LINUX_DIR)/drivers/input/keyboard/sx951x.ko + AUTOLOAD:=$(call AutoProbe,sx951x) +endef + +define KernelPackage/keyboard-sx951x/description + Enable support for SX9512/SX9513 capacitive touch controllers +endef + +$(eval $(call KernelPackage,keyboard-sx951x)) diff --git a/target/linux/ramips/patches-6.6/870-Input-sx951x-add-Semtech-SX9512-SX9513-driver.patch b/target/linux/ramips/patches-6.6/870-Input-sx951x-add-Semtech-SX9512-SX9513-driver.patch new file mode 100644 index 00000000000..340fb3c3426 --- /dev/null +++ b/target/linux/ramips/patches-6.6/870-Input-sx951x-add-Semtech-SX9512-SX9513-driver.patch @@ -0,0 +1,560 @@ +From ba92c0187006e2a6eae9573a569d275b0bd31732 Mon Sep 17 00:00:00 2001 +From: David Bauer +Date: Fri, 2 May 2025 23:04:27 +0200 +Subject: [PATCH] Input sx951x: add Semtech SX9512/SX9513 driver + +The Semtech SX9512/SX9513 is a family of capacitive touch-keyboard +controllers. + +All chips offer 8 channel touch sensitive inputs with one LED driver per +output channel. + +The also SX9512 supports proximity detection which is currently not +supported with the driver. + +This chip can be found on the Genexis Pulse EX400 repeater platform. + +Link: https://www.mouser.com/datasheet/2/761/SEMTS05226_1-2575172.pdf +Link: https://www.spinics.net/lists/kernel/msg5669349.html + +Signed-off-by: David Bauer +--- + drivers/input/keyboard/Kconfig | 11 + + drivers/input/keyboard/Makefile | 1 + + drivers/input/keyboard/sx951x.c | 490 ++++++++++++++++++++++++++++++++ + 3 files changed, 502 insertions(+) + create mode 100644 drivers/input/keyboard/sx951x.c + +diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig +index 1d0c5f4c0f99..6dc397389c64 100644 +--- a/drivers/input/keyboard/Kconfig ++++ b/drivers/input/keyboard/Kconfig +@@ -616,6 +616,17 @@ config KEYBOARD_SUNKBD + To compile this driver as a module, choose M here: the + module will be called sunkbd. + ++config KEYBOARD_SX951X ++ tristate "Semtech SX951X capacitive touch controller" ++ depends on OF && I2C ++ select REGMAP_I2C ++ help ++ Say Y here to enable the Semtech SX9512/SX9153 capacitive ++ touch controller driver. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called sx951x. ++ + config KEYBOARD_SH_KEYSC + tristate "SuperH KEYSC keypad support" + depends on ARCH_SHMOBILE || COMPILE_TEST +diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile +index aecef00c5d09..e59ca83c30ec 100644 +--- a/drivers/input/keyboard/Makefile ++++ b/drivers/input/keyboard/Makefile +@@ -66,6 +66,7 @@ obj-$(CONFIG_KEYBOARD_STOWAWAY) += stowaway.o + obj-$(CONFIG_KEYBOARD_ST_KEYSCAN) += st-keyscan.o + obj-$(CONFIG_KEYBOARD_SUN4I_LRADC) += sun4i-lradc-keys.o + obj-$(CONFIG_KEYBOARD_SUNKBD) += sunkbd.o ++obj-$(CONFIG_KEYBOARD_SX951X) += sx951x.o + obj-$(CONFIG_KEYBOARD_TC3589X) += tc3589x-keypad.o + obj-$(CONFIG_KEYBOARD_TEGRA) += tegra-kbc.o + obj-$(CONFIG_KEYBOARD_TM2_TOUCHKEY) += tm2-touchkey.o +diff --git a/drivers/input/keyboard/sx951x.c b/drivers/input/keyboard/sx951x.c +new file mode 100644 +index 000000000000..66355036aa95 +--- /dev/null ++++ b/drivers/input/keyboard/sx951x.c +@@ -0,0 +1,490 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Input driver for Semtech SX9512/SX9513 capacitive touch sensors. ++ * ++ * The difference between SX9512 and SX9513 is the presence of proximity ++ * sensing capabilities on the SX9512. ++ * ++ * SX951xB is the identical chip but with a different I2C address. ++ * ++ * (c) 2025 David Bauer ++ */ ++ ++ #include ++ #include ++ #include ++ #include ++ #include ++ #include ++ #include ++ #include ++ #include ++ ++ /* Generic properties */ ++#define SX951X_I2C_ADDRESS 0x2b ++#define SX951XB_I2C_ADDRESS_ 0x2d ++#define SX951X_NUM_CHANNELS 8 ++#define SX951X_POLL_INTERVAL 100 ++ ++/* Registers*/ ++#define SX951X_REG_IRQ_SRC 0x00 ++#define SX951X_REG_TOUCH_STATUS 0x01 ++#define SX951X_REG_PROXIMITY_STATUS 0x02 ++#define SX951X_REG_COMPENSATION_STATUS 0x03 ++#define SX951X_REG_IRQ_NVM_CTRL 0x04 ++#define SX951X_REG_SPO2_MODE_CTRL 0x07 ++#define SX951X_REG_PWR_KEY_CTRL 0x08 ++#define SX951X_REG_IRQ_MASK 0x09 ++ ++/* LED registers */ ++#define SX951X_REG_LED_MAP_ENG1 0x0c ++#define SX951X_REG_LED_MAP_ENG2 0x0d ++#define SX951X_REG_LED_PWM_FREQ 0x0e ++#define SX951X_REG_LED_MODE 0x0f ++#define SX951X_REG_LED_IDLE 0x10 ++#define SX951X_REG_LED_OFF_DELAY 0x11 ++#define SX951X_REG_LED_ON_ENG1 0x12 ++#define SX951X_REG_LED_FADE_ENG1 0x13 ++#define SX951X_REG_LED_ON_ENG2 0x14 ++#define SX951X_REG_LED_FADE_ENG2 0x15 ++#define SX951X_REG_LED_POWER_IDLE 0x16 ++#define SX951X_REG_LED_POWER_ON 0x17 ++#define SX951X_REG_LED_POWER_OFF 0x18 ++#define SX951X_REG_LED_POWER_FADE 0x19 ++#define SX951X_REG_LED_POWER_ON_PULSE 0x1a ++#define SX951X_REG_LED_POWER_MODE 0x1b ++ ++/* Capacitive touch sensing registers*/ ++#define SX951X_REG_CAP_SENSE_ENABLE 0x1e ++ ++#define SX951X_REG_CAP_SENSE_RANGE(x) (0x1f + (x)) ++#define SX951X_REG_CAP_SENSE_RANGE_CIN_DELTA_MASK GENMASK(1, 0) ++ ++#define SX951X_REG_CAP_SENSE_THRESH(x) (0x28 + (x)) ++#define SX951X_REG_CAP_SENSE_THRESH_ALL 0x30 ++ ++#define SX951X_REG_CAP_SENSE_OP 0x31 ++#define SX951X_REG_CAP_SENSE_MODE 0x32 ++#define SX951X_REG_CAP_SENSE_DEBOUNCE 0x33 ++ ++/* Reset register*/ ++#define SX951X_REG_SOFT_RESET 0xff ++ ++/* Default properties (keys)*/ ++#define SX951X_KEY_DEFAULT_CIN_DELTA 0x03 ++#define SX951X_KEY_DEFAULT_SENSE_THRESHOLD 0x04 ++ ++struct sx951x_key_data { ++ u32 cin_delta; ++ u32 sense_threshold; ++}; ++ ++struct sx951x_led { ++#ifdef CONFIG_LEDS_CLASS ++ struct led_classdev cdev; ++ struct sx951x_priv *priv; ++ ++ u32 reg; ++ bool registered; ++#endif ++}; ++ ++struct sx951x_priv { ++ struct regmap *regmap; ++ struct device *dev; ++ struct input_dev *idev; ++ const struct sx951x_hw_data *hw; ++ ++ struct sx951x_led leds[SX951X_NUM_CHANNELS]; ++ ++ /* device-config */ ++ u32 poll_interval; ++ ++ /* key-config */ ++ u32 keycodes[SX951X_NUM_CHANNELS]; ++ struct sx951x_key_data key_data[SX951X_NUM_CHANNELS]; ++}; ++ ++struct sx951x_hw_data { ++ bool has_proximity_sensing; ++}; ++ ++static const struct reg_default sx951x_reg_defaults[] = { ++ { SX951X_REG_LED_MAP_ENG1, 0x00 }, ++ { SX951X_REG_LED_MAP_ENG2, 0x00 }, ++ { SX951X_REG_LED_PWM_FREQ, 0x10 }, ++ { SX951X_REG_LED_IDLE, 0xff }, ++ { SX951X_REG_LED_ON_ENG1, 0xff }, ++ { SX951X_REG_LED_ON_ENG2, 0xff }, ++ { SX951X_REG_LED_POWER_IDLE, 0xff }, ++ { SX951X_REG_LED_POWER_ON, 0xff }, ++ { SX951X_REG_CAP_SENSE_ENABLE, 0x00 }, ++ { SX951X_REG_CAP_SENSE_RANGE(0), 0x40 }, ++ { SX951X_REG_CAP_SENSE_RANGE(1), 0x40 }, ++ { SX951X_REG_CAP_SENSE_RANGE(2), 0x40 }, ++ { SX951X_REG_CAP_SENSE_RANGE(3), 0x40 }, ++ { SX951X_REG_CAP_SENSE_RANGE(4), 0x40 }, ++ { SX951X_REG_CAP_SENSE_RANGE(5), 0x40 }, ++ { SX951X_REG_CAP_SENSE_RANGE(6), 0x40 }, ++ { SX951X_REG_CAP_SENSE_RANGE(7), 0x40 }, ++ { SX951X_REG_CAP_SENSE_THRESH(0), 0x0f }, ++ { SX951X_REG_CAP_SENSE_THRESH(1), 0x0f }, ++ { SX951X_REG_CAP_SENSE_THRESH(2), 0x0f }, ++ { SX951X_REG_CAP_SENSE_THRESH(3), 0x0f }, ++ { SX951X_REG_CAP_SENSE_THRESH(4), 0x0f }, ++ { SX951X_REG_CAP_SENSE_THRESH(5), 0x0f }, ++ { SX951X_REG_CAP_SENSE_THRESH(6), 0x0f }, ++ { SX951X_REG_CAP_SENSE_THRESH(7), 0x0f }, ++ { SX951X_REG_CAP_SENSE_THRESH_ALL, 0x0f }, ++ { SX951X_REG_CAP_SENSE_OP, 0x14 }, ++ { SX951X_REG_CAP_SENSE_MODE, 0x70 }, ++ { SX951X_REG_CAP_SENSE_DEBOUNCE, 0xff }, ++}; ++ ++static bool sx951x_volatile_reg(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case SX951X_REG_TOUCH_STATUS: ++ return true; ++ default: ++ return false; ++ } ++} ++ ++static const struct regmap_config sx951x_regmap_config = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ ++ .max_register = SX951X_REG_SOFT_RESET, ++ ++ .reg_defaults = sx951x_reg_defaults, ++ .num_reg_defaults = ARRAY_SIZE(sx951x_reg_defaults), ++ ++ .cache_type = REGCACHE_MAPLE, ++ .volatile_reg = sx951x_volatile_reg, ++}; ++ ++#ifdef CONFIG_LEDS_CLASS ++static int sx951x_led_set(struct led_classdev *cdev, enum led_brightness value) ++{ ++ struct sx951x_led *led = container_of(cdev, struct sx951x_led, cdev); ++ struct sx951x_priv *priv = led->priv; ++ ++ return regmap_update_bits(priv->regmap, ++ SX951X_REG_LED_MAP_ENG2, ++ BIT(led->reg), ++ value ? BIT(led->reg) : 0); ++} ++ ++static int sx951x_led_init(struct sx951x_priv *priv, ++ struct device_node *channel_node, u32 reg) ++{ ++ struct device_node *led_node; ++ struct sx951x_led *led = &priv->leds[reg]; ++ struct led_init_data init_data = {}; ++ int error; ++ ++ if (led->registered) { ++ dev_err(priv->dev, ++ "LED %d already registered\n", reg); ++ return -EINVAL; ++ } ++ ++ led_node = of_get_child_by_name(channel_node, "led"); ++ if (!led_node) { ++ /* No LED */ ++ return 0; ++ } ++ ++ led->cdev.flags = 0; ++ led->cdev.brightness_set_blocking = sx951x_led_set; ++ led->cdev.max_brightness = 1; ++ led->cdev.brightness = LED_OFF; ++ ++ init_data.default_label = of_get_property(led_node, "label", NULL); ++ init_data.fwnode = of_fwnode_handle(led_node); ++ ++ led->reg = reg; ++ led->priv = priv; ++ ++ error = devm_led_classdev_register_ext(priv->dev, &led->cdev, &init_data); ++ if (error) ++ return error; ++ ++ return 0; ++} ++#endif ++ ++static void sx951x_poll(struct input_dev *input) ++{ ++ struct sx951x_priv *priv = input_get_drvdata(input); ++ struct device *dev = priv->dev; ++ unsigned int val; ++ int error; ++ int i; ++ ++ error = regmap_read(priv->regmap, SX951X_REG_TOUCH_STATUS, &val); ++ if (error) { ++ dev_err(dev, "Failed to read touch status: %d\n", error); ++ return; ++ } ++ ++ for (i = 0; i < SX951X_NUM_CHANNELS; i++) { ++ if (priv->keycodes[i] == KEY_RESERVED) ++ continue; ++ ++ input_report_key(input, priv->keycodes[i], !!(val & BIT(i))); ++ input_sync(input); ++ } ++} ++ ++static int sx951x_channel_init(struct sx951x_priv *priv, struct device_node *of_node, ++ u32 chan_idx) ++{ ++ struct sx951x_key_data *key_data; ++ struct device *dev = priv->dev; ++ int error; ++ ++ key_data = &priv->key_data[chan_idx]; ++ ++ /* Defaults */ ++ key_data->cin_delta = SX951X_KEY_DEFAULT_CIN_DELTA; ++ key_data->sense_threshold = SX951X_KEY_DEFAULT_SENSE_THRESHOLD; ++ ++ error = of_property_read_u32(of_node, "linux,keycodes", ++ &priv->keycodes[chan_idx]); ++ if (error) { ++ /* Not configured */ ++ return 0; ++ } ++ ++ error = of_property_read_u32(of_node, "semtech,cin-delta", ++ &key_data->cin_delta); ++ if (key_data->cin_delta > 0x03) { ++ dev_err(dev, "Failed to read cin-delta for channel %d: %d\n", ++ chan_idx, error); ++ return error; ++ } ++ ++ error = of_property_read_u32(of_node, "semtech,sense-threshold", ++ &key_data->sense_threshold); ++ if (key_data->sense_threshold > 0xff) { ++ dev_err(dev, "Failed to read sense-threshold for channel %d: %d\n", ++ chan_idx, error); ++ return error; ++ } ++ ++ error = regmap_update_bits(priv->regmap, ++ SX951X_REG_CAP_SENSE_RANGE(chan_idx), ++ SX951X_REG_CAP_SENSE_RANGE_CIN_DELTA_MASK, ++ key_data->cin_delta); ++ ++ if (error) { ++ dev_err(dev, "Failed to set cin-delta for channel %d: %d\n", ++ chan_idx, error); ++ return error; ++ } ++ ++ error = regmap_write(priv->regmap, ++ SX951X_REG_CAP_SENSE_THRESH(chan_idx), ++ key_data->sense_threshold); ++ if (error) { ++ dev_err(dev, "Failed to set sense-threshold for channel %d: %d\n", ++ chan_idx, error); ++ return error; ++ } ++ ++ return 0; ++} ++ ++static int sx951x_channels_init(struct sx951x_priv *priv) ++{ ++ struct device *dev = priv->dev; ++ unsigned int channels = 0; ++ int error; ++ u32 reg; ++ ++ for_each_child_of_node_scoped(dev->of_node, child) { ++ error = of_property_read_u32(child, "reg", ®); ++ if (error != 0 || reg >= SX951X_NUM_CHANNELS) { ++ dev_err(dev, "Invalid channel %d\n", reg); ++ return -EINVAL; ++ } ++ ++ priv->keycodes[reg] = KEY_RESERVED; ++ ++ error = sx951x_channel_init(priv, child, reg); ++ if (error) { ++ dev_err(dev, "Failed to initialize channel %d: %d\n", ++ reg, error); ++ return error; ++ } ++ ++ if (priv->keycodes[reg] != KEY_RESERVED) ++ channels |= BIT(reg); ++ ++#ifdef CONFIG_LEDS_CLASS ++ error = sx951x_led_init(priv, child, reg); ++ if (error) { ++ dev_err(dev, "Failed to initialize LED %d: %d\n", ++ reg, error); ++ return error; ++ } ++#endif ++ } ++ ++ /* Enable sensing on channels with keycode configured */ ++ error = regmap_write(priv->regmap, ++ SX951X_REG_CAP_SENSE_ENABLE, ++ channels); ++ ++ return 0; ++} ++ ++static int sx951x_input_init(struct sx951x_priv *priv) ++{ ++ struct device *dev = priv->dev; ++ int i, error; ++ ++ priv->idev = devm_input_allocate_device(dev); ++ if (!priv->idev) ++ return -ENOMEM; ++ ++ priv->idev->name = "SX9512/SX9513 capacitive touch sensor"; ++ priv->idev->id.bustype = BUS_I2C; ++ __set_bit(EV_KEY, priv->idev->evbit); ++ ++ for (i = 0; i < SX951X_NUM_CHANNELS; i++) ++ __set_bit(priv->keycodes[i], priv->idev->keybit); ++ ++ __clear_bit(KEY_RESERVED, priv->idev->keybit); ++ ++ priv->idev->keycode = priv->keycodes; ++ priv->idev->keycodesize = sizeof(priv->keycodes[0]); ++ priv->idev->keycodemax = SX951X_NUM_CHANNELS; ++ ++ input_set_drvdata(priv->idev, priv); ++ ++ error = input_setup_polling(priv->idev, sx951x_poll); ++ if (error) { ++ dev_err(dev, "Unable to set up polling: %d\n", error); ++ return error; ++ } ++ ++ input_set_poll_interval(priv->idev, priv->poll_interval); ++ ++ error = input_register_device(priv->idev); ++ if (error) { ++ dev_err(dev, "Unable to register polled device: %d\n", ++ error); ++ return error; ++ } ++ ++ return 0; ++} ++ ++static int sx951x_probe(struct i2c_client *i2c_client) ++{ ++ const struct i2c_device_id *id; ++ const struct sx951x_hw_data *hw; ++ struct device *dev = &i2c_client->dev; ++ struct sx951x_priv *priv; ++ int error; ++ ++ if (i2c_client->addr != SX951X_I2C_ADDRESS && ++ i2c_client->addr != SX951XB_I2C_ADDRESS_) { ++ dev_err(dev, "Invalid I2C address: 0x%02x\n", ++ i2c_client->addr); ++ return -ENODEV; ++ } ++ ++ id = i2c_client_get_device_id(i2c_client); ++ hw = i2c_get_match_data(i2c_client); ++ if (!id || !hw) { ++ dev_err(dev, "Invalid device configuration\n"); ++ return -EINVAL; ++ } ++ ++ priv = devm_kzalloc(dev, ++ sizeof(struct sx951x_priv), ++ GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ priv->dev = dev; ++ priv->hw = hw; ++ ++ priv->regmap = devm_regmap_init_i2c(i2c_client, &sx951x_regmap_config); ++ if (IS_ERR(priv->regmap)) ++ return PTR_ERR(priv->regmap); ++ ++ /* Parse device configuration */ ++ if (of_property_read_u32(dev->of_node, "poll-interval", ++ &priv->poll_interval)) ++ priv->poll_interval = SX951X_POLL_INTERVAL; ++ ++ /* Register LED and input channels */ ++ error = sx951x_channels_init(priv); ++ if (error) { ++ dev_err(dev, "Failed to initialize channels: %d\n", error); ++ return error; ++ } ++ ++ /* Register input device */ ++ error = sx951x_input_init(priv); ++ if (error) { ++ dev_err(dev, "Failed to register input device: %d\n", error); ++ return error; ++ } ++ ++ return 0; ++} ++ ++static void sx951x_remove(struct i2c_client *i2c_client) ++{ ++ struct sx951x_priv *priv = i2c_get_clientdata(i2c_client); ++ ++ /* Disable sensing */ ++ regmap_write(priv->regmap, SX951X_REG_CAP_SENSE_ENABLE, 0x00); ++ ++ /* Turn off all LEDs */ ++ regmap_write(priv->regmap, SX951X_REG_LED_MAP_ENG2, 0x00); ++} ++ ++static const struct sx951x_hw_data sx9512_hw_data = { ++ .has_proximity_sensing = true, ++}; ++ ++static const struct sx951x_hw_data sx9513_hw_data = { ++ .has_proximity_sensing = false, ++}; ++ ++static const struct of_device_id sx951x_dt_ids[] = { ++ { .compatible = "semtech,sx9512", .data = &sx9512_hw_data }, ++ { .compatible = "semtech,sx9513", .data = &sx9513_hw_data }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, sx951x_dt_ids); ++ ++static const struct i2c_device_id sx951x_i2c_ids[] = { ++ { "sx9512", (kernel_ulong_t)&sx9512_hw_data }, ++ { "sx9513", (kernel_ulong_t)&sx9513_hw_data }, ++ { } ++}; ++MODULE_DEVICE_TABLE(i2c, sx951x_i2c_ids); ++ ++static struct i2c_driver sx951x_i2c_driver = { ++ .driver = { ++ .name = "sx951x", ++ .of_match_table = sx951x_dt_ids, ++ }, ++ .id_table = sx951x_i2c_ids, ++ .probe = sx951x_probe, ++ .remove = sx951x_remove, ++}; ++ ++module_i2c_driver(sx951x_i2c_driver); ++ ++MODULE_DESCRIPTION("Semtech SX9512/SX9513 driver"); ++MODULE_AUTHOR("David Bauer "); ++MODULE_LICENSE("GPL"); +-- +2.47.2 +