From patchwork Fri Jul 11 10:06:33 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Mack X-Patchwork-Id: 4532531 Return-Path: X-Original-To: patchwork-linux-input@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 7D4259F36A for ; Fri, 11 Jul 2014 10:06:56 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 1E684201D3 for ; Fri, 11 Jul 2014 10:06:55 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 8F51A201D5 for ; Fri, 11 Jul 2014 10:06:53 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753177AbaGKKGw (ORCPT ); Fri, 11 Jul 2014 06:06:52 -0400 Received: from mail-wi0-f173.google.com ([209.85.212.173]:42204 "EHLO mail-wi0-f173.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753095AbaGKKGu (ORCPT ); Fri, 11 Jul 2014 06:06:50 -0400 Received: by mail-wi0-f173.google.com with SMTP id cc10so5988511wib.6 for ; Fri, 11 Jul 2014 03:06:44 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:cc:subject:date:message-id; bh=zna693c24CGSwRBgc9HRB6OpbrB7XdPkulhgd+Uy18c=; b=zVQ1jw8ZA/tlLneR4DQjKgsh+epOlw3rUKiY7GQV1CZFnXBk8LVy/ZGsXozyIOLl8F xI9MF1RpUrkWDlm58mAyub2CVr/ycTEotHAr40aqIOYvh5oay7dYgXEmd1cQt40LX+4Y veHB2ZSL2HGkYqZBya9ji87Z2UbeMH26IrDn8mO3UNFw1OtqF172V5fxaaK5hA85EUNw XnVF/MZAH3scBGmoQDdwP0ptuaqaH2BrV2E4TGX1wKW877Tq5J+y07JYwRu/ptYME4M1 act4EOxCznV95DYGq9l9HjkIii8p0QkHtGWYLFyCSdwbgymZIadEtPmiSsBevlhZciZ7 SmxQ== X-Received: by 10.194.87.97 with SMTP id w1mr2882696wjz.42.1405073203070; Fri, 11 Jul 2014 03:06:43 -0700 (PDT) Received: from tamtam.fritz.box ([2001:4dd0:ff00:9394:224:d7ff:fec6:a0ec]) by mx.google.com with ESMTPSA id cd1sm4180811wjc.19.2014.07.11.03.06.41 for (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 11 Jul 2014 03:06:42 -0700 (PDT) From: Daniel Mack To: dtor@mail.ru Cc: linux-input@vger.kernel.org, broonie@kernel.org, dh.herrmann@gmail.com, devicetree@vger.kernel.org, Daniel Mack Subject: [PATCH v2] Input: Add driver for Microchip's CAP1106 Date: Fri, 11 Jul 2014 12:06:33 +0200 Message-Id: <1405073193-21448-1-git-send-email-zonque@gmail.com> X-Mailer: git-send-email 1.9.3 Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org X-Spam-Status: No, score=-7.4 required=5.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED, DKIM_SIGNED, FREEMAIL_FROM, RCVD_IN_DNSWL_HI, RP_MATCHES_RCVD, T_DKIM_INVALID, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP 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 --- v2: * Fix potential deadlocks pointed out by David. .../devicetree/bindings/input/cap1106.txt | 63 ++++ drivers/input/keyboard/Kconfig | 10 + drivers/input/keyboard/Makefile | 1 + drivers/input/keyboard/cap1106.c | 378 +++++++++++++++++++++ 4 files changed, 452 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..b4741c5 --- /dev/null +++ b/drivers/input/keyboard/cap1106.c @@ -0,0 +1,378 @@ +/* + * Input driver for Microchip CAP1106, 6 channel capacitive touch sensor + * + * http://www.microchip.com/wwwproducts/Devices.aspx?product=CAP1106 + * + * (c) 2014 Daniel Mack + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#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; + bool closed:1; + 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; + + /* + * 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: + 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; + + disable_irq_nosync(priv->irq); + schedule_work(&priv->work); + + 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); + + priv->closed = false; + enable_irq(priv->irq); + + return ret; +} + +static void cap1106_close(struct input_dev *input_dev) +{ + struct cap1106_priv *priv = input_get_drvdata(input_dev); + + spin_lock(&priv->lock); + priv->closed = true; + disable_irq(priv->irq); + spin_unlock(&priv->lock); + + cancel_work_sync(&priv->work); + + /* 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); + priv->closed = true; + + 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 "); +MODULE_LICENSE("GPL v2");