Re: [PATCH] Input: Add driver for Microchip's CAP1106

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi

On Fri, Jul 11, 2014 at 9:42 AM, Daniel Mack <zonque@xxxxxxxxx> wrote:
> This patch adds a driver for Microchips CAP1106, an I2C driven, 6-channel
> capacitive touch sensor.
>
> For now, only the capacitive buttons are supported, and no specific
> settings that can be tweaked for individual channels, except for the
> device-wide sensitivity gain. The defaults seem to work just fine out of
> the box, so I'll leave configurable parameters for someone who's in need
> of them and who can actually measure the impact. All registers are
> prepared, however. Many of them are just not used for now.
>
> The implementation does not make any attempt to be compatible to platform
> data driven boards, but fully depends on CONFIG_OF.
>
> Power management functions are also left for volounteers with the ability
> to actually test them.
>
> Signed-off-by: Daniel Mack <zonque@xxxxxxxxx>
> ---
>  .../devicetree/bindings/input/cap1106.txt          |  63 ++++
>  drivers/input/keyboard/Kconfig                     |  10 +
>  drivers/input/keyboard/Makefile                    |   1 +
>  drivers/input/keyboard/cap1106.c                   | 376 +++++++++++++++++++++
>  4 files changed, 450 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/input/cap1106.txt
>  create mode 100644 drivers/input/keyboard/cap1106.c
>
> diff --git a/Documentation/devicetree/bindings/input/cap1106.txt b/Documentation/devicetree/bindings/input/cap1106.txt
> new file mode 100644
> index 0000000..57f5af3
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/input/cap1106.txt
> @@ -0,0 +1,63 @@
> +Device tree bindings for Microchip CAP1106, 6 channel capacitive touch sensor
> +
> +The node for this driver must be a child of a I2C controller node, as the
> +device communication via I2C only.
> +
> +Required properties:
> +
> +       compatible:             Must be "microchip,cap1106"
> +       reg:                    The I2C slave address of the device.
> +                               Only 0x28 is valid.
> +       interrupt:              Node describing the interrupt line the device's
> +                               ALERT#/CM_IRQ# pin is connected to.
> +
> +Optional properties:
> +
> +       autorepeat:             Enables the Linux input system's autorepeat
> +                               feature on the input device.
> +       microchip,sensor-gain:  Defines the gain of the sensor circuitry. This
> +                               effectively controls the sensitivity, as a
> +                               smaller delta capacitance is required to
> +                               generate the same delta count values.
> +                               Valid values are 1, 2, 4, and 8.
> +                               By default, a gain of 1 is set.
> +
> +To define details of each individual button channel, six subnodes can be
> +specified. Inside each of those, the following property is valid:
> +
> +       linux,keycode:  Specify the numeric identifier of the keycode to be
> +                       generated when this channel is activated. If this
> +                       property is omitted, KEY_A, KEY_B, etc are used as
> +                       defaults.
> +
> +Example:
> +
> +i2c_controller {
> +       cap1106@28 {
> +               compatible = "microchip,cap1106";
> +               interrupt-parent = <&gpio1>;
> +               interrupts = <0 0>;
> +               reg = <0x28>;
> +               autorepeat;
> +               microchip,sensor-gain = <2>;
> +
> +               up {
> +                       linux,keycode = <103>; /* KEY_UP */
> +               };
> +               right {
> +                       linux,keycode = <106>; /* KEY_RIGHT */
> +               };
> +               down {
> +                       linux,keycode = <108>; /* KEY_DOWN */
> +               };
> +               left {
> +                       linux,keycode = <105>; /* KEY_LEFT */
> +               };
> +               pagedown {
> +                       linux,keycode = <109>; /* KEY_PAGEDOWN */
> +               };
> +               pageup {
> +                       linux,keycode = <104>; /* KEY_PAGEUP */
> +               };
> +       };
> +}
> diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
> index f7e79b4..a3958c6 100644
> --- a/drivers/input/keyboard/Kconfig
> +++ b/drivers/input/keyboard/Kconfig
> @@ -665,4 +665,14 @@ config KEYBOARD_CROS_EC
>           To compile this driver as a module, choose M here: the
>           module will be called cros_ec_keyb.
>
> +config KEYBOARD_CAP1106
> +       tristate "Microchip CAP1106 touch sensor"
> +       depends on OF && I2C
> +       select REGMAP_I2C
> +       help
> +         Say Y here to enable the CAP1106 touch sensor driver.
> +
> +         To compile this driver as a module, choose M here: the
> +         module will be called cap1106.
> +
>  endif
> diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
> index 7504ae1..0a33456 100644
> --- a/drivers/input/keyboard/Makefile
> +++ b/drivers/input/keyboard/Makefile
> @@ -11,6 +11,7 @@ obj-$(CONFIG_KEYBOARD_AMIGA)          += amikbd.o
>  obj-$(CONFIG_KEYBOARD_ATARI)           += atakbd.o
>  obj-$(CONFIG_KEYBOARD_ATKBD)           += atkbd.o
>  obj-$(CONFIG_KEYBOARD_BFIN)            += bf54x-keys.o
> +obj-$(CONFIG_KEYBOARD_CAP1106)         += cap1106.o
>  obj-$(CONFIG_KEYBOARD_CLPS711X)                += clps711x-keypad.o
>  obj-$(CONFIG_KEYBOARD_CROS_EC)         += cros_ec_keyb.o
>  obj-$(CONFIG_KEYBOARD_DAVINCI)         += davinci_keyscan.o
> diff --git a/drivers/input/keyboard/cap1106.c b/drivers/input/keyboard/cap1106.c
> new file mode 100644
> index 0000000..ad0f0fb
> --- /dev/null
> +++ b/drivers/input/keyboard/cap1106.c
> @@ -0,0 +1,376 @@
> +/*
> + * Input driver for Microchip CAP1106, 6 channel capacitive touch sensor
> + *
> + * http://www.microchip.com/wwwproducts/Devices.aspx?product=CAP1106
> + *
> + * (c) 2014 Daniel Mack <linux@xxxxxxxxxx>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/interrupt.h>
> +#include <linux/input.h>
> +#include <linux/of_irq.h>
> +#include <linux/regmap.h>
> +#include <linux/i2c.h>
> +#include <linux/gpio/consumer.h>
> +
> +#define CAP1106_REG_MAIN_CONTROL       0x00
> +#define CAP1106_REG_MAIN_CONTROL_GAIN_SHIFT    (6)
> +#define CAP1106_REG_MAIN_CONTROL_GAIN_MASK     (0xc0)
> +#define CAP1106_REG_MAIN_CONTROL_DLSEEP                BIT(4)
> +#define CAP1106_REG_GENERAL_STATUS     0x02
> +#define CAP1106_REG_SENSOR_INPUT       0x03
> +#define CAP1106_REG_NOISE_FLAG_STATUS  0x0a
> +#define CAP1106_REG_SENOR_DELTA(X)     (0x10 + (X))
> +#define CAP1106_REG_SENSITIVITY_CONTROL        0x1f
> +#define CAP1106_REG_CONFIG             0x20
> +#define CAP1106_REG_SENSOR_ENABLE      0x21
> +#define CAP1106_REG_SENSOR_CONFIG      0x22
> +#define CAP1106_REG_SENSOR_CONFIG2     0x23
> +#define CAP1106_REG_SAMPLING_CONFIG    0x24
> +#define CAP1106_REG_CALIBRATION                0x25
> +#define CAP1106_REG_INT_ENABLE         0x26
> +#define CAP1106_REG_REPEAT_RATE                0x28
> +#define CAP1106_REG_MT_CONFIG          0x2a
> +#define CAP1106_REG_MT_PATTERN_CONFIG  0x2b
> +#define CAP1106_REG_MT_PATTERN         0x2d
> +#define CAP1106_REG_RECALIB_CONFIG     0x2f
> +#define CAP1106_REG_SENSOR_THRESH(X)   (0x30 + (X))
> +#define CAP1106_REG_SENSOR_NOISE_THRESH        0x38
> +#define CAP1106_REG_STANDBY_CHANNEL    0x40
> +#define CAP1106_REG_STANDBY_CONFIG     0x41
> +#define CAP1106_REG_STANDBY_SENSITIVITY        0x42
> +#define CAP1106_REG_STANDBY_THRESH     0x43
> +#define CAP1106_REG_CONFIG2            0x44
> +#define CAP1106_REG_SENSOR_BASE_CNT(X) (0x50 + (X))
> +#define CAP1106_REG_SENSOR_CALIB       (0xb1 + (X))
> +#define CAP1106_REG_SENSOR_CALIB_LSB1  0xb9
> +#define CAP1106_REG_SENSOR_CALIB_LSB2  0xba
> +#define CAP1106_REG_PRODUCT_ID         0xfd
> +#define CAP1106_REG_MANUFACTURER_ID    0xfe
> +#define CAP1106_REG_REVISION           0xff
> +
> +#define CAP1106_NUM_CHN 6
> +#define CAP1106_PRODUCT_ID     0x55
> +#define CAP1106_MANUFACTURER_ID        0x5d
> +
> +struct cap1106_priv {
> +       struct regmap *regmap;
> +       struct input_dev *idev;
> +       struct work_struct work;
> +       spinlock_t lock;
> +       int irq;
> +
> +       /* config */
> +       unsigned int keycodes[CAP1106_NUM_CHN];
> +};
> +
> +static const struct reg_default cap1106_reg_defaults[] = {
> +       { CAP1106_REG_MAIN_CONTROL,             0x00 },
> +       { CAP1106_REG_GENERAL_STATUS,           0x00 },
> +       { CAP1106_REG_SENSOR_INPUT,             0x00 },
> +       { CAP1106_REG_NOISE_FLAG_STATUS,        0x00 },
> +       { CAP1106_REG_SENSITIVITY_CONTROL,      0x2f },
> +       { CAP1106_REG_CONFIG,                   0x20 },
> +       { CAP1106_REG_SENSOR_ENABLE,            0x3f },
> +       { CAP1106_REG_SENSOR_CONFIG,            0xa4 },
> +       { CAP1106_REG_SENSOR_CONFIG2,           0x07 },
> +       { CAP1106_REG_SAMPLING_CONFIG,          0x39 },
> +       { CAP1106_REG_CALIBRATION,              0x00 },
> +       { CAP1106_REG_INT_ENABLE,               0x3f },
> +       { CAP1106_REG_REPEAT_RATE,              0x3f },
> +       { CAP1106_REG_MT_CONFIG,                0x80 },
> +       { CAP1106_REG_MT_PATTERN_CONFIG,        0x00 },
> +       { CAP1106_REG_MT_PATTERN,               0x3f },
> +       { CAP1106_REG_RECALIB_CONFIG,           0x8a },
> +       { CAP1106_REG_SENSOR_THRESH(0),         0x40 },
> +       { CAP1106_REG_SENSOR_THRESH(1),         0x40 },
> +       { CAP1106_REG_SENSOR_THRESH(2),         0x40 },
> +       { CAP1106_REG_SENSOR_THRESH(3),         0x40 },
> +       { CAP1106_REG_SENSOR_THRESH(4),         0x40 },
> +       { CAP1106_REG_SENSOR_THRESH(5),         0x40 },
> +       { CAP1106_REG_SENSOR_NOISE_THRESH,      0x01 },
> +       { CAP1106_REG_STANDBY_CHANNEL,          0x00 },
> +       { CAP1106_REG_STANDBY_CONFIG,           0x39 },
> +       { CAP1106_REG_STANDBY_SENSITIVITY,      0x02 },
> +       { CAP1106_REG_STANDBY_THRESH,           0x40 },
> +       { CAP1106_REG_CONFIG2,                  0x40 },
> +       { CAP1106_REG_SENSOR_CALIB_LSB1,        0x00 },
> +       { CAP1106_REG_SENSOR_CALIB_LSB2,        0x00 },
> +};
> +
> +static bool cap1106_volatile_reg(struct device *dev, unsigned int reg)
> +{
> +       switch (reg) {
> +       case CAP1106_REG_MAIN_CONTROL:
> +       case CAP1106_REG_SENSOR_INPUT:
> +       case CAP1106_REG_SENOR_DELTA(0):
> +       case CAP1106_REG_SENOR_DELTA(1):
> +       case CAP1106_REG_SENOR_DELTA(2):
> +       case CAP1106_REG_SENOR_DELTA(3):
> +       case CAP1106_REG_SENOR_DELTA(4):
> +       case CAP1106_REG_SENOR_DELTA(5):
> +       case CAP1106_REG_PRODUCT_ID:
> +       case CAP1106_REG_MANUFACTURER_ID:
> +       case CAP1106_REG_REVISION:
> +               return true;
> +       }
> +
> +       return false;
> +}
> +
> +static const struct regmap_config cap1106_regmap_config = {
> +       .reg_bits = 8,
> +       .val_bits = 8,
> +
> +       .max_register = CAP1106_REG_REVISION,
> +       .reg_defaults = cap1106_reg_defaults,
> +
> +       .num_reg_defaults = ARRAY_SIZE(cap1106_reg_defaults),
> +       .cache_type = REGCACHE_RBTREE,
> +       .volatile_reg = cap1106_volatile_reg,
> +};
> +
> +static void cap1106_work(struct work_struct *w)
> +{
> +       struct cap1106_priv *priv = container_of(w, struct cap1106_priv, work);
> +       unsigned int status;
> +       int ret, i;
> +
> +       spin_lock(&priv->lock);

If your worker started and is _about_ to take the spinlock, you might
dead-lock with cap1106_close(). They might already hold the spinlock
and then call cancel_work_sync(), thus waiting for this task to take
the lock end exit..

How about you add "bool closed : 1;" to your cap1106_priv structure
and drop the lock here, but..

> +
> +       /*
> +        * Deassert interrupt. This needs to be done before reading the status
> +        * registers, which will not carry valid values otherwise.
> +        */
> +       ret = regmap_update_bits(priv->regmap, CAP1106_REG_MAIN_CONTROL, 1, 0);
> +       if (ret < 0)
> +               goto out;
> +
> +       ret = regmap_read(priv->regmap, CAP1106_REG_SENSOR_INPUT, &status);
> +       if (ret < 0)
> +               goto out;
> +
> +       for (i = 0; i < CAP1106_NUM_CHN; i++)
> +               input_report_key(priv->idev, priv->keycodes[i],
> +                                status & (1 << i));
> +
> +       input_sync(priv->idev);
> +
> +out:
> +       enable_irq(priv->irq);
> +       spin_unlock(&priv->lock);

...change this to:

spin_lock(&priv->lock);
if (!priv->closed)
        enable_irq(priv->irq);
spin_unlock(&priv->lock);

> +}
> +
> +static irqreturn_t cap1106_isr(int irq_num, void *data)
> +{
> +       struct cap1106_priv *priv = data;
> +
> +       spin_lock(&priv->lock);
> +       disable_irq_nosync(priv->irq);
> +       schedule_work(&priv->work);
> +       spin_unlock(&priv->lock);

Locks here can be simply dropped. The IRQ is always stopped
synchronously before any worker is stopped, so this cannot race.

> +
> +       return IRQ_HANDLED;
> +}
> +
> +static int cap1106_open(struct input_dev *input_dev)
> +{
> +       struct cap1106_priv *priv = input_get_drvdata(input_dev);
> +       int ret;
> +
> +       /* disable deep sleep */
> +       ret = regmap_update_bits(priv->regmap, CAP1106_REG_MAIN_CONTROL,
> +                                CAP1106_REG_MAIN_CONTROL_DLSEEP, 0);
> +

You'd have to add:
priv->closed = false;

No need for locking as enable_irq and work-structs work as barriers.

> +       enable_irq(priv->irq);
> +
> +       return ret;
> +}
> +
> +static void cap1106_close(struct input_dev *input_dev)
> +{
> +       struct cap1106_priv *priv = input_get_drvdata(input_dev);
> +       unsigned long flags;
> +
> +       spin_lock_irqsave(&priv->lock, flags);
> +       disable_irq(priv->irq);
> +       cancel_work_sync(&priv->work);
> +       spin_unlock_irqrestore(&priv->lock, flags);

This would become:

spin_lock(&priv->lock);
priv->closed = true;
disable_irq(priv->irq);
spin_unlock(&priv->lock);

cancel_work_sync(&priv->work);

Btw., no need for irqsave/irqrestore here, ->closed is called in user-context.
Everything else looks good to me:

Thanks
David

> +
> +       /* enable deep sleep */
> +       regmap_update_bits(priv->regmap, CAP1106_REG_MAIN_CONTROL,
> +                          CAP1106_REG_MAIN_CONTROL_DLSEEP,
> +                          CAP1106_REG_MAIN_CONTROL_DLSEEP);
> +}
> +
> +static int cap1106_i2c_probe(struct i2c_client *i2c_client,
> +                            const struct i2c_device_id *id)
> +{
> +       struct device *dev = &i2c_client->dev;
> +       struct device_node *child, *node;
> +       struct cap1106_priv *priv;
> +       unsigned int val, rev;
> +       u8 gain = 0;
> +       int i, ret;
> +       u32 tmp32;
> +
> +       priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +       if (!priv)
> +               return -ENOMEM;
> +
> +       priv->regmap = devm_regmap_init_i2c(i2c_client, &cap1106_regmap_config);
> +       if (IS_ERR(priv->regmap))
> +               return PTR_ERR(priv->regmap);
> +
> +       ret = regmap_read(priv->regmap, CAP1106_REG_PRODUCT_ID, &val);
> +       if (ret < 0)
> +               return ret;
> +
> +       if (val != CAP1106_PRODUCT_ID) {
> +               dev_err(dev, "Product ID: Got 0x%02x, expected 0x%02x\n",
> +                       val, CAP1106_PRODUCT_ID);
> +               return -ENODEV;
> +       }
> +
> +       ret = regmap_read(priv->regmap, CAP1106_REG_MANUFACTURER_ID, &val);
> +       if (ret < 0)
> +               return ret;
> +
> +       if (val != CAP1106_MANUFACTURER_ID) {
> +               dev_err(dev, "Manufacturer ID: Got 0x%02x, expected 0x%02x\n",
> +                       val, CAP1106_MANUFACTURER_ID);
> +               return -ENODEV;
> +       }
> +
> +       ret = regmap_read(priv->regmap, CAP1106_REG_REVISION, &rev);
> +       if (ret < 0)
> +               return ret;
> +
> +       dev_info(dev, "CAP1106 detected, revision 0x%02x\n", rev);
> +       i2c_set_clientdata(i2c_client, priv);
> +       INIT_WORK(&priv->work, cap1106_work);
> +       spin_lock_init(&priv->lock);
> +       node = dev->of_node;
> +
> +       if (!of_property_read_u32(node, "microchip,sensor-gain", &tmp32)) {
> +               if (is_power_of_2(tmp32) && tmp32 <= 8)
> +                       gain = ilog2(tmp32);
> +               else
> +                       dev_err(dev, "Invalid sensor-gain value %d\n", tmp32);
> +       }
> +
> +       ret = regmap_update_bits(priv->regmap, CAP1106_REG_MAIN_CONTROL,
> +                                CAP1106_REG_MAIN_CONTROL_GAIN_MASK,
> +                                gain << CAP1106_REG_MAIN_CONTROL_GAIN_SHIFT);
> +       if (ret < 0)
> +               return ret;
> +
> +       /* disable autorepeat. The Linux input system has its own handling. */
> +       ret = regmap_write(priv->regmap, CAP1106_REG_REPEAT_RATE, 0);
> +       if (ret < 0)
> +               return ret;
> +
> +       /* set the defaults */
> +       for (i = 0; i < CAP1106_NUM_CHN; i++)
> +               priv->keycodes[i] = KEY_A + i;
> +
> +       i = 0;
> +       for_each_child_of_node(node, child) {
> +               if (i == CAP1106_NUM_CHN) {
> +                       dev_err(dev, "Too many button nodes.\n");
> +                       return -EINVAL;
> +               }
> +
> +               if (!of_property_read_u32(child, "linux,keycode", &tmp32))
> +                       priv->keycodes[i] = tmp32;
> +
> +               i++;
> +       }
> +
> +       priv->idev = devm_input_allocate_device(dev);
> +       if (!priv->idev)
> +               return -ENOMEM;
> +
> +       priv->idev->name = "CAP1106 capacitive touch sensor";
> +       priv->idev->id.bustype = BUS_I2C;
> +       priv->idev->evbit[0] = BIT_MASK(EV_KEY);
> +
> +       if (of_get_property(node, "autorepeat", NULL))
> +               __set_bit(EV_REP, priv->idev->evbit);
> +
> +       for (i = 0; i < CAP1106_NUM_CHN; i++) {
> +               unsigned int code = priv->keycodes[i];
> +               priv->idev->keybit[BIT_WORD(code)] |= BIT_MASK(code);
> +       }
> +
> +       priv->idev->id.vendor = CAP1106_MANUFACTURER_ID;
> +       priv->idev->id.product = CAP1106_PRODUCT_ID;
> +       priv->idev->id.version = rev;
> +
> +       priv->idev->open = cap1106_open;
> +       priv->idev->close = cap1106_close;
> +
> +       input_set_drvdata(priv->idev, priv);
> +
> +       ret = input_register_device(priv->idev);
> +       if (ret < 0)
> +               return ret;
> +
> +       priv->irq = irq_of_parse_and_map(node, 0);
> +       if (!priv->irq) {
> +               dev_err(dev, "Unable to parse or map IRQ\n");
> +               return -ENXIO;
> +       }
> +
> +       ret = devm_request_irq(dev, priv->irq, cap1106_isr, IRQF_DISABLED,
> +                              dev_name(dev), priv);
> +       if (ret < 0)
> +               return ret;
> +
> +       return 0;
> +}
> +
> +static int cap1106_i2c_remove(struct i2c_client *i2c_client)
> +{
> +       struct cap1106_priv *priv = i2c_get_clientdata(i2c_client);
> +
> +       input_unregister_device(priv->idev);
> +
> +       return 0;
> +}
> +
> +static const struct of_device_id cap1106_dt_ids[] = {
> +       { .compatible = "microchip,cap1106", },
> +       {}
> +};
> +MODULE_DEVICE_TABLE(of, cap1106_dt_ids);
> +
> +static const struct i2c_device_id cap1106_i2c_ids[] = {
> +       { "cap1106", 0 },
> +       {}
> +};
> +MODULE_DEVICE_TABLE(i2c, cap1106_i2c_ids);
> +
> +static struct i2c_driver cap1106_i2c_driver = {
> +       .driver = {
> +               .name   = "cap1106",
> +               .owner  = THIS_MODULE,
> +               .of_match_table = cap1106_dt_ids,
> +       },
> +       .id_table       = cap1106_i2c_ids,
> +       .probe          = cap1106_i2c_probe,
> +       .remove         = cap1106_i2c_remove,
> +};
> +
> +module_i2c_driver(cap1106_i2c_driver);
> +
> +MODULE_ALIAS("platform:" DRV_NAME);
> +MODULE_DESCRIPTION("Microchip CAP1106 driver");
> +MODULE_AUTHOR("Daniel Mack <linux@xxxxxxxxxx>");
> +MODULE_LICENSE("GPL v2");
> --
> 1.9.3
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-input" in
> the body of a message to majordomo@xxxxxxxxxxxxxxx
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux Media Devel]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Linux Wireless Networking]     [Linux Omap]

  Powered by Linux