|
|
@@ -0,0 +1,560 @@
|
|
|
+From ba92c0187006e2a6eae9573a569d275b0bd31732 Mon Sep 17 00:00:00 2001
|
|
|
+From: David Bauer <[email protected]>
|
|
|
+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 <[email protected]>
|
|
|
+---
|
|
|
+ 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 <[email protected]>
|
|
|
++ */
|
|
|
++
|
|
|
++ #include <linux/kernel.h>
|
|
|
++ #include <linux/module.h>
|
|
|
++ #include <linux/input.h>
|
|
|
++ #include <linux/leds.h>
|
|
|
++ #include <linux/of.h>
|
|
|
++ #include <linux/regmap.h>
|
|
|
++ #include <linux/i2c.h>
|
|
|
++ #include <linux/gpio/consumer.h>
|
|
|
++ #include <linux/bitfield.h>
|
|
|
++
|
|
|
++ /* 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 <[email protected]>");
|
|
|
++MODULE_LICENSE("GPL");
|
|
|
+--
|
|
|
+2.47.2
|
|
|
+
|