From patchwork Fri Apr 26 08:30:17 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?b?TWljaGFsIFZva8OhxI0=?= X-Patchwork-Id: 10918877 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id ED8711575 for ; Fri, 26 Apr 2019 08:31:44 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id DD6D228B0A for ; Fri, 26 Apr 2019 08:31:44 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id D14D928CA4; Fri, 26 Apr 2019 08:31:44 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-8.0 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id BB72F28B1E for ; Fri, 26 Apr 2019 08:31:43 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726036AbfDZIbm (ORCPT ); Fri, 26 Apr 2019 04:31:42 -0400 Received: from uho.ysoft.cz ([81.19.3.130]:56107 "EHLO uho.ysoft.cz" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1725947AbfDZIbm (ORCPT ); Fri, 26 Apr 2019 04:31:42 -0400 Received: from iota-build.ysoft.local (unknown [10.1.5.151]) by uho.ysoft.cz (Postfix) with ESMTP id 3C7E5A6C69; Fri, 26 Apr 2019 10:31:39 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=ysoft.com; s=20160406-ysoft-com; t=1556267499; bh=Is9qQ6K+sh46hK3N5KZM07kXt89OjVvh92tHpZRwnN4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=SWEyh+e25VNPstPk8VUyeWrxZGZv4VyqsJ1KdHWwRmkJDJSOO52wOvIR+8TBvU/St mgOo7Etf8TZNHE681DUjFCpAmaWlAezIaxgneWRRcRnoD2VYPH5Izp3DzmlSehwm08 0/cfw+Ue3F1ZduP8ilbQIjYubjrfG/8j6knJhwwg= From: =?utf-8?b?TWljaGFsIFZva8OhxI0=?= To: Dmitry Torokhov , Rob Herring Cc: Mark Rutland , Shawn Guo , Sascha Hauer , Fabio Estevam , linux-input@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Pengutronix Kernel Team , =?utf-8?b?TWljaGFsIFZva8Oh?= =?utf-8?b?xI0=?= Subject: [RFC PATCH 1/4] dt-bindings: input: Add support for the MPR121 without interrupt line Date: Fri, 26 Apr 2019 10:30:17 +0200 Message-Id: <1556267420-93219-2-git-send-email-michal.vokac@ysoft.com> X-Mailer: git-send-email 2.1.4 In-Reply-To: <1556267420-93219-1-git-send-email-michal.vokac@ysoft.com> References: <1556267420-93219-1-git-send-email-michal.vokac@ysoft.com> MIME-Version: 1.0 Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Normally, the MPR121 controller uses separate interrupt line to notify the I2C host that a key was touched/released. To support platforms that can not use the interrupt line, polling of the MPR121 registers can be used. Signed-off-by: Michal Vokáč --- .../bindings/input/mpr121-touchkey-polled.txt | 26 ++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 Documentation/devicetree/bindings/input/mpr121-touchkey-polled.txt diff --git a/Documentation/devicetree/bindings/input/mpr121-touchkey-polled.txt b/Documentation/devicetree/bindings/input/mpr121-touchkey-polled.txt new file mode 100644 index 000000000000..6bb1d312614c --- /dev/null +++ b/Documentation/devicetree/bindings/input/mpr121-touchkey-polled.txt @@ -0,0 +1,26 @@ +* Freescale MPR121 Controller without interrupt line + +Required Properties: +- compatible: Should be "fsl,mpr121-touchkey-polled" +- reg: The I2C slave address of the device. +- vdd-supply: Phandle to the Vdd power supply. +- linux,keycodes: Specifies an array of numeric keycode values to + be used for reporting button presses. The array can + contain up to 12 entries. + +Optional Properties: +- autorepeat: Enable autorepeat feature. + +Example: + +#include "dt-bindings/input/input.h" + + touchkeys: keys@5a { + compatible = "fsl,mpr121-touchkey-polled"; + reg = <0x5a>; + autorepeat; + vdd-supply = <&ldo4_reg>; + linux,keycodes = , , , , + , , , + , , , ; + }; From patchwork Fri Apr 26 08:30:18 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?b?TWljaGFsIFZva8OhxI0=?= X-Patchwork-Id: 10918885 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 600B51515 for ; Fri, 26 Apr 2019 08:32:13 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 4FD4F28B0A for ; Fri, 26 Apr 2019 08:32:13 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 432A928CA1; Fri, 26 Apr 2019 08:32:13 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-8.0 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 0E3D828B0A for ; Fri, 26 Apr 2019 08:32:12 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1725923AbfDZIcL (ORCPT ); Fri, 26 Apr 2019 04:32:11 -0400 Received: from uho.ysoft.cz ([81.19.3.130]:56113 "EHLO uho.ysoft.cz" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1725933AbfDZIbm (ORCPT ); Fri, 26 Apr 2019 04:31:42 -0400 Received: from iota-build.ysoft.local (unknown [10.1.5.151]) by uho.ysoft.cz (Postfix) with ESMTP id 999DAA6C6A; Fri, 26 Apr 2019 10:31:39 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=ysoft.com; s=20160406-ysoft-com; t=1556267499; bh=jXRslSbwwjjXRQeea8zfHvPYrElHJKxjhz5k3sVETGE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=TpJ3HqGlR1LJdaAjIlqfl2UD6c4Jk0jJpULyPJXNbzKDXaAYomFFiu9MrDGSULo5m nm3AX1UrKNkLFaLD5u3C9SYRHZEsOi7/9O8XJMU/1FzrOPk4uKQcvV+rPNp3U4SQwS /dxqwnXX8/WV6Rmrhi98gMly0O2Jd5FpA4XLVrGs= From: =?utf-8?b?TWljaGFsIFZva8OhxI0=?= To: Dmitry Torokhov , Rob Herring Cc: Mark Rutland , Shawn Guo , Sascha Hauer , Fabio Estevam , linux-input@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Pengutronix Kernel Team , =?utf-8?b?TWljaGFsIFZva8Oh?= =?utf-8?b?xI0=?= Subject: [RFC PATCH 2/4] Input: mpr121-polled: Add polling variant of the MPR121 touchkey driver Date: Fri, 26 Apr 2019 10:30:18 +0200 Message-Id: <1556267420-93219-3-git-send-email-michal.vokac@ysoft.com> X-Mailer: git-send-email 2.1.4 In-Reply-To: <1556267420-93219-1-git-send-email-michal.vokac@ysoft.com> References: <1556267420-93219-1-git-send-email-michal.vokac@ysoft.com> MIME-Version: 1.0 Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP This driver is based on the original driver with interrupts. Polling driver may be used in cases where the MPR121 chip is connected using only the I2C interface and the interrupt line is not available. Signed-off-by: Michal Vokáč --- drivers/input/keyboard/Kconfig | 13 + drivers/input/keyboard/Makefile | 1 + drivers/input/keyboard/mpr121_touchkey_polled.c | 417 ++++++++++++++++++++++++ 3 files changed, 431 insertions(+) create mode 100644 drivers/input/keyboard/mpr121_touchkey_polled.c diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index a878351f1643..a674e72cdeef 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -418,6 +418,19 @@ config KEYBOARD_MPR121 To compile this driver as a module, choose M here: the module will be called mpr121_touchkey. +config KEYBOARD_MPR121_POLLED + tristate "Polled Freescale MPR121 Touchkey" + depends on I2C + help + Say Y here if you have Freescale MPR121 touchkey controller + chip in your system connected only using the I2C line without + the interrupt line. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called mpr121_touchkey_polled. + config KEYBOARD_SNVS_PWRKEY tristate "IMX SNVS Power Key Driver" depends on SOC_IMX6SX || SOC_IMX7D diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index 182e92985dbf..903f50842844 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -40,6 +40,7 @@ obj-$(CONFIG_KEYBOARD_MATRIX) += matrix_keypad.o obj-$(CONFIG_KEYBOARD_MAX7359) += max7359_keypad.o obj-$(CONFIG_KEYBOARD_MCS) += mcs_touchkey.o obj-$(CONFIG_KEYBOARD_MPR121) += mpr121_touchkey.o +obj-$(CONFIG_KEYBOARD_MPR121_POLLED) += mpr121_touchkey_polled.o obj-$(CONFIG_KEYBOARD_MTK_PMIC) += mtk-pmic-keys.o obj-$(CONFIG_KEYBOARD_NEWTON) += newtonkbd.o obj-$(CONFIG_KEYBOARD_NOMADIK) += nomadik-ske-keypad.o diff --git a/drivers/input/keyboard/mpr121_touchkey_polled.c b/drivers/input/keyboard/mpr121_touchkey_polled.c new file mode 100644 index 000000000000..e5e80530c9d8 --- /dev/null +++ b/drivers/input/keyboard/mpr121_touchkey_polled.c @@ -0,0 +1,417 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Touchkey driver for Freescale MPR121 Controllor +// +// Copyright (C) 2011 Freescale Semiconductor, Inc. +// Author: Zhang Jiejing +// +// Based on mcs_touchkey.c +// +// Copyright (C) 2019 Y Soft Corporation, a.s. +// Author: Pavel Staněk +// Author: Michal Vokáč +// +// Reworked into polled driver based on mpr121_touchkey.c + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Register definitions */ +#define ELE_TOUCH_STATUS_0_ADDR 0x0 +#define ELE_TOUCH_STATUS_1_ADDR 0X1 +#define MHD_RISING_ADDR 0x2b +#define NHD_RISING_ADDR 0x2c +#define NCL_RISING_ADDR 0x2d +#define FDL_RISING_ADDR 0x2e +#define MHD_FALLING_ADDR 0x2f +#define NHD_FALLING_ADDR 0x30 +#define NCL_FALLING_ADDR 0x31 +#define FDL_FALLING_ADDR 0x32 +#define ELE0_TOUCH_THRESHOLD_ADDR 0x41 +#define ELE0_RELEASE_THRESHOLD_ADDR 0x42 +#define AFE_CONF_ADDR 0x5c +#define FILTER_CONF_ADDR 0x5d + +/* + * ELECTRODE_CONF_ADDR: This register configures the number of + * enabled capacitance sensing inputs and its run/suspend mode. + */ +#define ELECTRODE_CONF_ADDR 0x5e +#define ELECTRODE_CONF_QUICK_CHARGE 0x80 +#define AUTO_CONFIG_CTRL_ADDR 0x7b +#define AUTO_CONFIG_USL_ADDR 0x7d +#define AUTO_CONFIG_LSL_ADDR 0x7e +#define AUTO_CONFIG_TL_ADDR 0x7f + +/* Threshold of touch/release trigger */ +#define TOUCH_THRESHOLD 0x08 +#define RELEASE_THRESHOLD 0x05 +/* Masks for touch and release triggers */ +#define TOUCH_STATUS_MASK 0xfff +/* MPR121 has 12 keys */ +#define MPR121_MAX_KEY_COUNT 12 + +#define MPR121_POLL_INTERVAL 50 +#define MPR121_POLL_INTERVAL_MIN 10 +#define MPR121_POLL_INTERVAL_MAX 200 +#define MPR121_POLL_INTERVAL_REINIT 500 +#define MPR121_POLL_RETRY_MAX 4 + +struct mpr121_polled { + struct i2c_client *client; + struct input_dev *input_dev; + struct input_polled_dev *poll_dev; + unsigned int statusbits; + unsigned int keycount; + u32 keycodes[MPR121_MAX_KEY_COUNT]; + u8 read_errors; + int vdd_uv; +}; + +struct mpr121_polled_init_register { + int addr; + u8 val; +}; + +static const struct mpr121_polled_init_register init_reg_table[] = { + { MHD_RISING_ADDR, 0x1 }, + { NHD_RISING_ADDR, 0x1 }, + { MHD_FALLING_ADDR, 0x1 }, + { NHD_FALLING_ADDR, 0x1 }, + { NCL_FALLING_ADDR, 0xff }, + { FDL_FALLING_ADDR, 0x02 }, + { FILTER_CONF_ADDR, 0x04 }, + { AFE_CONF_ADDR, 0x0b }, + { AUTO_CONFIG_CTRL_ADDR, 0x0b }, +}; + +static void mpr121_polled_vdd_supply_disable(void *data) +{ + struct regulator *vdd_supply = data; + + regulator_disable(vdd_supply); +} + +static struct regulator *mpr121_polled_vdd_supply_init(struct device *dev) +{ + struct regulator *vdd_supply; + int err; + + vdd_supply = devm_regulator_get(dev, "vdd"); + if (IS_ERR(vdd_supply)) { + dev_err(dev, "failed to get vdd regulator: %ld\n", + PTR_ERR(vdd_supply)); + return vdd_supply; + } + + err = regulator_enable(vdd_supply); + if (err) { + dev_err(dev, "failed to enable vdd regulator: %d\n", err); + return ERR_PTR(err); + } + + err = devm_add_action(dev, mpr121_polled_vdd_supply_disable, + vdd_supply); + if (err) { + regulator_disable(vdd_supply); + dev_err(dev, "failed to add disable regulator action: %d\n", + err); + return ERR_PTR(err); + } + + return vdd_supply; +} + +static int mpr121_polled_phys_init(struct mpr121_polled *mpr121, + struct i2c_client *client) +{ + const struct mpr121_polled_init_register *reg; + unsigned char usl, lsl, tl, eleconf; + int i, t, vdd, ret; + + /* Set stop mode prior to writing any register */ + ret = i2c_smbus_write_byte_data(client, ELECTRODE_CONF_ADDR, 0x00); + if (ret < 0) + goto err_i2c_write; + + /* Set up touch/release threshold for ele0-ele11 */ + for (i = 0; i <= MPR121_MAX_KEY_COUNT; i++) { + t = ELE0_TOUCH_THRESHOLD_ADDR + (i * 2); + ret = i2c_smbus_write_byte_data(client, t, TOUCH_THRESHOLD); + if (ret < 0) + goto err_i2c_write; + ret = i2c_smbus_write_byte_data(client, t + 1, + RELEASE_THRESHOLD); + if (ret < 0) + goto err_i2c_write; + } + + /* Set up init register */ + for (i = 0; i < ARRAY_SIZE(init_reg_table); i++) { + reg = &init_reg_table[i]; + ret = i2c_smbus_write_byte_data(client, reg->addr, reg->val); + if (ret < 0) + goto err_i2c_write; + } + + /* + * Capacitance on sensing input varies and needs to be compensated. + * The internal MPR121-auto-configuration can do this if it's + * registers are set properly (based on vdd_uv). + */ + vdd = mpr121->vdd_uv / 1000; + usl = ((vdd - 700) * 256) / vdd; + lsl = (usl * 65) / 100; + tl = (usl * 90) / 100; + ret = i2c_smbus_write_byte_data(client, AUTO_CONFIG_USL_ADDR, usl); + ret |= i2c_smbus_write_byte_data(client, AUTO_CONFIG_LSL_ADDR, lsl); + ret |= i2c_smbus_write_byte_data(client, AUTO_CONFIG_TL_ADDR, tl); + + /* + * Quick charge bit will let the capacitive charge to ready + * state quickly, or the buttons may not function after system + * boot. + */ + eleconf = mpr121->keycount | ELECTRODE_CONF_QUICK_CHARGE; + ret |= i2c_smbus_write_byte_data(client, ELECTRODE_CONF_ADDR, + eleconf); + if (ret != 0) + goto err_i2c_write; + + dev_dbg(&client->dev, "set up with %x keys.\n", mpr121->keycount); + + return 0; + +err_i2c_write: + dev_err(&client->dev, "i2c write error: %d\n", ret); + return ret; +} + +static void mpr121_polled_release_keys(struct mpr121_polled *mpr121) +{ + struct input_dev *input = mpr121->input_dev; + struct i2c_client *client = mpr121->client; + unsigned long statusbits; + unsigned int key_num; + + if (!mpr121->statusbits) + return; + + statusbits = mpr121->statusbits; + mpr121->statusbits = 0; + for_each_set_bit(key_num, &statusbits, mpr121->keycount) { + unsigned int key_val; + + key_val = mpr121->keycodes[key_num]; + + input_event(input, EV_MSC, MSC_SCAN, key_num); + input_report_key(input, key_val, 0); + + dev_dbg(&client->dev, "key %d %d %s\n", key_num, key_val, + "released"); + } + input_sync(input); +} + +static int mpr121_polled_process_keys(struct mpr121_polled *mpr121) +{ + struct input_dev *input = mpr121->input_dev; + struct i2c_client *client = mpr121->client; + unsigned long bit_changed; + unsigned int key_num; + int reg; + + reg = i2c_smbus_read_word_data(client, ELE_TOUCH_STATUS_0_ADDR); + if (reg < 0) { + dev_err(&client->dev, "i2c read error: %d\n", reg); + return reg; + } + + reg &= TOUCH_STATUS_MASK; + bit_changed = reg ^ mpr121->statusbits; + mpr121->statusbits = reg; + for_each_set_bit(key_num, &bit_changed, mpr121->keycount) { + unsigned int key_val, pressed; + + pressed = reg & BIT(key_num); + key_val = mpr121->keycodes[key_num]; + + input_event(input, EV_MSC, MSC_SCAN, key_num); + input_report_key(input, key_val, pressed); + + dev_dbg(&client->dev, "key %d %d %s\n", key_num, key_val, + pressed ? "pressed" : "released"); + } + input_sync(input); + + return 0; +} +static void mpr121_poll(struct input_polled_dev *dev) +{ + struct mpr121_polled *mpr121 = dev->private; + struct i2c_client *client = mpr121->client; + int ret; + + if (mpr121->read_errors > MPR121_POLL_RETRY_MAX) { + dev_warn(&client->dev, + "device does not respond, re-initializing\n"); + mpr121_polled_release_keys(mpr121); + ret = mpr121_polled_phys_init(mpr121, client); + if (ret >= 0) { + mpr121->read_errors = 0; + dev->poll_interval = MPR121_POLL_INTERVAL; + } else { + dev->poll_interval = MPR121_POLL_INTERVAL_REINIT; + } + } + + ret = mpr121_polled_process_keys(mpr121); + if (ret < 0) { + mpr121->read_errors++; + return; + } + + mpr121->read_errors = 0; +} + +static int mpr121_polled_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct regulator *vdd_supply; + struct mpr121_polled *mpr121; + struct input_dev *input_dev; + struct input_polled_dev *poll_dev; + int error; + int i; + + vdd_supply = mpr121_polled_vdd_supply_init(dev); + if (IS_ERR(vdd_supply)) + return PTR_ERR(vdd_supply); + + mpr121 = devm_kzalloc(dev, sizeof(*mpr121), GFP_KERNEL); + if (!mpr121) + return -ENOMEM; + + poll_dev = devm_input_allocate_polled_device(dev); + if (!poll_dev) + return -ENOMEM; + + mpr121->vdd_uv = regulator_get_voltage(vdd_supply); + mpr121->client = client; + mpr121->input_dev = poll_dev->input; + mpr121->poll_dev = poll_dev; + mpr121->keycount = device_property_read_u32_array(dev, "linux,keycodes", + NULL, 0); + if (mpr121->keycount > MPR121_MAX_KEY_COUNT) { + dev_err(dev, "too many keys defined (%d)\n", mpr121->keycount); + return -EINVAL; + } + + error = device_property_read_u32_array(dev, "linux,keycodes", + mpr121->keycodes, + mpr121->keycount); + if (error) { + dev_err(dev, + "failed to read linux,keycode property: %d\n", error); + return error; + } + + poll_dev->private = mpr121; + poll_dev->poll = mpr121_poll; + poll_dev->poll_interval = MPR121_POLL_INTERVAL; + poll_dev->poll_interval_max = MPR121_POLL_INTERVAL_MAX; + poll_dev->poll_interval_min = MPR121_POLL_INTERVAL_MIN; + + input_dev = poll_dev->input; + input_dev->name = "Freescale MPR121 Polled Touchkey"; + input_dev->id.bustype = BUS_I2C; + input_dev->dev.parent = dev; + if (device_property_read_bool(dev, "autorepeat")) + __set_bit(EV_REP, input_dev->evbit); + input_set_capability(input_dev, EV_MSC, MSC_SCAN); + + input_dev->keycode = mpr121->keycodes; + input_dev->keycodesize = sizeof(mpr121->keycodes[0]); + input_dev->keycodemax = mpr121->keycount; + + for (i = 0; i < mpr121->keycount; i++) + input_set_capability(input_dev, EV_KEY, mpr121->keycodes[i]); + + error = mpr121_polled_phys_init(mpr121, client); + if (error) { + dev_err(dev, "Failed to init register\n"); + return error; + } + + error = input_register_polled_device(poll_dev); + if (error) + return error; + + i2c_set_clientdata(client, mpr121); + + return 0; +} + +static int __maybe_unused mpr121_polled_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + + i2c_smbus_write_byte_data(client, ELECTRODE_CONF_ADDR, 0x00); + + return 0; +} + +static int __maybe_unused mpr121_polled_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct mpr121_polled *mpr121 = i2c_get_clientdata(client); + + i2c_smbus_write_byte_data(client, ELECTRODE_CONF_ADDR, + mpr121->keycount); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(mpr121_polled_pm_ops, mpr121_polled_suspend, + mpr121_polled_resume); + +static const struct i2c_device_id mpr121_polled_id[] = { + { "mpr121_polled", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mpr121_polled_id); + +#ifdef CONFIG_OF +static const struct of_device_id mpr121_polled_dt_match_table[] = { + { .compatible = "fsl,mpr121-touchkey-polled" }, + { }, +}; +MODULE_DEVICE_TABLE(of, mpr121_polled_dt_match_table); +#endif + +static struct i2c_driver mpr121_polled_driver = { + .driver = { + .name = "mpr121-polled", + .pm = &mpr121_polled_pm_ops, + .of_match_table = of_match_ptr(mpr121_polled_dt_match_table), + }, + .id_table = mpr121_polled_id, + .probe = mpr121_polled_probe, +}; + +module_i2c_driver(mpr121_polled_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Michal Vokáč "); +MODULE_DESCRIPTION("Polled driver for Freescale MPR121 chip"); From patchwork Fri Apr 26 08:30:19 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?b?TWljaGFsIFZva8OhxI0=?= X-Patchwork-Id: 10918883 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 4647F1515 for ; Fri, 26 Apr 2019 08:32:11 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 363B128B0A for ; Fri, 26 Apr 2019 08:32:11 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 2912A28CA1; Fri, 26 Apr 2019 08:32:11 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-8.0 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 86FC228B0A for ; Fri, 26 Apr 2019 08:32:10 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726069AbfDZIbn (ORCPT ); Fri, 26 Apr 2019 04:31:43 -0400 Received: from uho.ysoft.cz ([81.19.3.130]:56119 "EHLO uho.ysoft.cz" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1725959AbfDZIbm (ORCPT ); Fri, 26 Apr 2019 04:31:42 -0400 Received: from iota-build.ysoft.local (unknown [10.1.5.151]) by uho.ysoft.cz (Postfix) with ESMTP id D8F16A6C6B; Fri, 26 Apr 2019 10:31:39 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=ysoft.com; s=20160406-ysoft-com; t=1556267500; bh=Hb5jFQIPCxZTBWzwQ8QSsG9NLvE8GRE3e3X2L85MB0U=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=rQu/87PbQUcu33MZXWOKX5Zd3JsDjWa2s86gI1ETggef7uz8I2RkfDFN9vs1/3zFq waSI2imqGsahDLVG9LoFHrz7UwicJsjEP7fK7w6HLA+RNqrWRYgqHERTC0Hif/JCJz cCgovvW7hsR7KVBrMH2NbO5arLaS+nTheBJTIm6E= From: =?utf-8?b?TWljaGFsIFZva8OhxI0=?= To: Dmitry Torokhov , Rob Herring Cc: Mark Rutland , Shawn Guo , Sascha Hauer , Fabio Estevam , linux-input@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Pengutronix Kernel Team , =?utf-8?b?TWljaGFsIFZva8Oh?= =?utf-8?b?xI0=?= Subject: [RFC PATCH 3/4] Input: mpr121-polled: Add write-through cache to detect corrupted registers Date: Fri, 26 Apr 2019 10:30:19 +0200 Message-Id: <1556267420-93219-4-git-send-email-michal.vokac@ysoft.com> X-Mailer: git-send-email 2.1.4 In-Reply-To: <1556267420-93219-1-git-send-email-michal.vokac@ysoft.com> References: <1556267420-93219-1-git-send-email-michal.vokac@ysoft.com> MIME-Version: 1.0 Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP The MPR121 chip (and I2C bus in general) is quite sensitive to ESD. An electrostatic discharge can easily cause a reset of the MPR121 chip. Even though the chip then recovers and respond to read/write commands, it is not properly initialized. This state can be detected using a write-through cache of the internal registers. Each time a register is written to, its value is stored in the cache and marked as valid. Once per MPR121_REG_CACHE_CHECK_LIMIT polls one valid cache value is compared with its corresponding register value. In case of difference an error counter is increased. If the error counter limit is exceeded, the chip is re-initialized. Signed-off-by: Michal Vokáč --- drivers/input/keyboard/mpr121_touchkey_polled.c | 100 +++++++++++++++++++++--- 1 file changed, 88 insertions(+), 12 deletions(-) diff --git a/drivers/input/keyboard/mpr121_touchkey_polled.c b/drivers/input/keyboard/mpr121_touchkey_polled.c index e5e80530c9d8..6536d9b2eeb8 100644 --- a/drivers/input/keyboard/mpr121_touchkey_polled.c +++ b/drivers/input/keyboard/mpr121_touchkey_polled.c @@ -67,6 +67,19 @@ #define MPR121_POLL_INTERVAL_REINIT 500 #define MPR121_POLL_RETRY_MAX 4 +#define MPR121_REG_CACHE_MIN_ADDR 0x2b +#define MPR121_REG_CACHE_MAX_ADDR 0x7f +#define MPR121_REG_CACHE_SIZE \ + (MPR121_REG_CACHE_MAX_ADDR - MPR121_REG_CACHE_MIN_ADDR + 1) +#define MPR121_REG_CACHE_CHECK_LIMIT 8 +#define mpr121_addr_to_cache_idx(addr) (addr - MPR121_REG_CACHE_MIN_ADDR) +#define mpr121_cache_idx_to_addr(idx) (idx + MPR121_REG_CACHE_MIN_ADDR) + +struct mpr121_polled_reg_cache { + bool valid; + u8 value; +}; + struct mpr121_polled { struct i2c_client *client; struct input_dev *input_dev; @@ -76,6 +89,9 @@ struct mpr121_polled { u32 keycodes[MPR121_MAX_KEY_COUNT]; u8 read_errors; int vdd_uv; + struct mpr121_polled_reg_cache reg_cache[MPR121_REG_CACHE_SIZE]; + u8 reg_cache_check_count; + u8 reg_cache_next_check_item; }; struct mpr121_polled_init_register { @@ -95,6 +111,29 @@ static const struct mpr121_polled_init_register init_reg_table[] = { { AUTO_CONFIG_CTRL_ADDR, 0x0b }, }; +static int mpr121_polled_write_reg(struct mpr121_polled *mpr121, u8 addr, + u8 value) +{ + struct i2c_client *client = mpr121->client; + int ret; + + ret = i2c_smbus_write_byte_data(client, addr, value); + if (ret < 0) { + dev_err(&client->dev, "i2c write error: %d\n", ret); + return ret; + } + + if (addr >= MPR121_REG_CACHE_MIN_ADDR && + addr <= MPR121_REG_CACHE_MAX_ADDR) { + u8 i = mpr121_addr_to_cache_idx(addr); + + mpr121->reg_cache[i].valid = 1; + mpr121->reg_cache[i].value = value; + } + + return 0; +} + static void mpr121_polled_vdd_supply_disable(void *data) { struct regulator *vdd_supply = data; @@ -140,18 +179,18 @@ static int mpr121_polled_phys_init(struct mpr121_polled *mpr121, int i, t, vdd, ret; /* Set stop mode prior to writing any register */ - ret = i2c_smbus_write_byte_data(client, ELECTRODE_CONF_ADDR, 0x00); + ret = mpr121_polled_write_reg(mpr121, ELECTRODE_CONF_ADDR, 0x00); if (ret < 0) goto err_i2c_write; /* Set up touch/release threshold for ele0-ele11 */ for (i = 0; i <= MPR121_MAX_KEY_COUNT; i++) { t = ELE0_TOUCH_THRESHOLD_ADDR + (i * 2); - ret = i2c_smbus_write_byte_data(client, t, TOUCH_THRESHOLD); + ret = mpr121_polled_write_reg(mpr121, t, TOUCH_THRESHOLD); if (ret < 0) goto err_i2c_write; - ret = i2c_smbus_write_byte_data(client, t + 1, - RELEASE_THRESHOLD); + ret = mpr121_polled_write_reg(mpr121, t + 1, + RELEASE_THRESHOLD); if (ret < 0) goto err_i2c_write; } @@ -159,7 +198,7 @@ static int mpr121_polled_phys_init(struct mpr121_polled *mpr121, /* Set up init register */ for (i = 0; i < ARRAY_SIZE(init_reg_table); i++) { reg = &init_reg_table[i]; - ret = i2c_smbus_write_byte_data(client, reg->addr, reg->val); + ret = mpr121_polled_write_reg(mpr121, reg->addr, reg->val); if (ret < 0) goto err_i2c_write; } @@ -173,9 +212,9 @@ static int mpr121_polled_phys_init(struct mpr121_polled *mpr121, usl = ((vdd - 700) * 256) / vdd; lsl = (usl * 65) / 100; tl = (usl * 90) / 100; - ret = i2c_smbus_write_byte_data(client, AUTO_CONFIG_USL_ADDR, usl); - ret |= i2c_smbus_write_byte_data(client, AUTO_CONFIG_LSL_ADDR, lsl); - ret |= i2c_smbus_write_byte_data(client, AUTO_CONFIG_TL_ADDR, tl); + ret = mpr121_polled_write_reg(mpr121, AUTO_CONFIG_USL_ADDR, usl); + ret |= mpr121_polled_write_reg(mpr121, AUTO_CONFIG_LSL_ADDR, lsl); + ret |= mpr121_polled_write_reg(mpr121, AUTO_CONFIG_TL_ADDR, tl); /* * Quick charge bit will let the capacitive charge to ready @@ -183,7 +222,7 @@ static int mpr121_polled_phys_init(struct mpr121_polled *mpr121, * boot. */ eleconf = mpr121->keycount | ELECTRODE_CONF_QUICK_CHARGE; - ret |= i2c_smbus_write_byte_data(client, ELECTRODE_CONF_ADDR, + ret |= mpr121_polled_write_reg(mpr121, ELECTRODE_CONF_ADDR, eleconf); if (ret != 0) goto err_i2c_write; @@ -256,6 +295,36 @@ static int mpr121_polled_process_keys(struct mpr121_polled *mpr121) return 0; } + +static int mpr121_polled_check_regs(struct mpr121_polled *mpr121) +{ + struct i2c_client *client = mpr121->client; + int i, reg; + + /* Skip registers that were never written to (have invalid cache) */ + i = mpr121->reg_cache_next_check_item; + for (; i < MPR121_REG_CACHE_SIZE; i++) + if (mpr121->reg_cache[i].valid) + break; + + if (i == MPR121_REG_CACHE_SIZE) { + mpr121->reg_cache_next_check_item = 0; + return 0; + } + + reg = i2c_smbus_read_byte_data(client, mpr121_cache_idx_to_addr(i)); + if (reg < 0) { + dev_err(&client->dev, "i2c read error: %d\n", reg); + return -1; + } + + if (reg != mpr121->reg_cache[i].value) + return -1; + + mpr121->reg_cache_next_check_item = i + 1; + return 0; +} + static void mpr121_poll(struct input_polled_dev *dev) { struct mpr121_polled *mpr121 = dev->private; @@ -282,6 +351,13 @@ static void mpr121_poll(struct input_polled_dev *dev) } mpr121->read_errors = 0; + mpr121->reg_cache_check_count++; + if (mpr121->reg_cache_check_count > MPR121_REG_CACHE_CHECK_LIMIT) { + mpr121->reg_cache_check_count = 0; + ret = mpr121_polled_check_regs(mpr121); + if (ret < 0) + mpr121->read_errors++; + } } static int mpr121_polled_probe(struct i2c_client *client, @@ -366,8 +442,9 @@ static int mpr121_polled_probe(struct i2c_client *client, static int __maybe_unused mpr121_polled_suspend(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); + struct mpr121_polled *mpr121 = i2c_get_clientdata(client); - i2c_smbus_write_byte_data(client, ELECTRODE_CONF_ADDR, 0x00); + mpr121_polled_write_reg(mpr121, ELECTRODE_CONF_ADDR, 0x00); return 0; } @@ -377,8 +454,7 @@ static int __maybe_unused mpr121_polled_resume(struct device *dev) struct i2c_client *client = to_i2c_client(dev); struct mpr121_polled *mpr121 = i2c_get_clientdata(client); - i2c_smbus_write_byte_data(client, ELECTRODE_CONF_ADDR, - mpr121->keycount); + mpr121_polled_write_reg(mpr121, ELECTRODE_CONF_ADDR, mpr121->keycount); return 0; } From patchwork Fri Apr 26 08:30:20 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?b?TWljaGFsIFZva8OhxI0=?= X-Patchwork-Id: 10918879 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 22CD792A for ; Fri, 26 Apr 2019 08:31:46 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 130A028B0A for ; Fri, 26 Apr 2019 08:31:46 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 06E6E28B1E; Fri, 26 Apr 2019 08:31:46 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-8.0 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id DBC6328CA1 for ; Fri, 26 Apr 2019 08:31:43 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726089AbfDZIbn (ORCPT ); Fri, 26 Apr 2019 04:31:43 -0400 Received: from uho.ysoft.cz ([81.19.3.130]:56125 "EHLO uho.ysoft.cz" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1725993AbfDZIbm (ORCPT ); Fri, 26 Apr 2019 04:31:42 -0400 Received: from iota-build.ysoft.local (unknown [10.1.5.151]) by uho.ysoft.cz (Postfix) with ESMTP id 45B54A6C6C; Fri, 26 Apr 2019 10:31:40 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=ysoft.com; s=20160406-ysoft-com; t=1556267500; bh=3fqHTQWwFFxlsRLtnrTf/2ecqWmAHakKFLqkKHlonlU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=mz545dlfPtJHx7U/umxGLUgQhr8HJpGFC3qEpTym3UyB0bvCp4ArTWOvELl/uOnw4 uIhyD7gd2ZFYXQENGJWOZEKRBxZeuxG0YXdfLKFGFcrXrVQPSNjIJvgD4dmVAyhbIC yEYggcs8FlsIyByhgxFWzFQ0glxTMvAsuaU/Ka/s= From: =?utf-8?b?TWljaGFsIFZva8OhxI0=?= To: Dmitry Torokhov , Rob Herring Cc: Mark Rutland , Shawn Guo , Sascha Hauer , Fabio Estevam , linux-input@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Pengutronix Kernel Team , =?utf-8?b?TWljaGFsIFZva8Oh?= =?utf-8?b?xI0=?= Subject: [RFC PATCH 4/4] ARM: dts: imx6dl-yapp4: Enable MPR121 touch keypad on Hydra Date: Fri, 26 Apr 2019 10:30:20 +0200 Message-Id: <1556267420-93219-5-git-send-email-michal.vokac@ysoft.com> X-Mailer: git-send-email 2.1.4 In-Reply-To: <1556267420-93219-1-git-send-email-michal.vokac@ysoft.com> References: <1556267420-93219-1-git-send-email-michal.vokac@ysoft.com> MIME-Version: 1.0 Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Enable the I2C connected touch keypad on Hydra board. Use the polled binding as the interrupt line is not available. Signed-off-by: Michal Vokáč --- arch/arm/boot/dts/imx6dl-yapp4-common.dtsi | 12 ++++++++++++ arch/arm/boot/dts/imx6dl-yapp4-hydra.dts | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/arch/arm/boot/dts/imx6dl-yapp4-common.dtsi b/arch/arm/boot/dts/imx6dl-yapp4-common.dtsi index e8d800fec637..65a670e5bd4f 100644 --- a/arch/arm/boot/dts/imx6dl-yapp4-common.dtsi +++ b/arch/arm/boot/dts/imx6dl-yapp4-common.dtsi @@ -4,6 +4,7 @@ #include #include +#include #include / { @@ -330,6 +331,17 @@ vcc-supply = <&sw2_reg>; status = "disabled"; }; + + touchkeys: keys@5a { + compatible = "fsl,mpr121-touchkey-polled"; + reg = <0x5a>; + vdd-supply = <&sw2_reg>; + autorepeat; + linux,keycodes = , , , , , + , , , , + , , ; + status = "disabled"; + }; }; &iomuxc { diff --git a/arch/arm/boot/dts/imx6dl-yapp4-hydra.dts b/arch/arm/boot/dts/imx6dl-yapp4-hydra.dts index f97927064750..84c275bfdd38 100644 --- a/arch/arm/boot/dts/imx6dl-yapp4-hydra.dts +++ b/arch/arm/boot/dts/imx6dl-yapp4-hydra.dts @@ -45,6 +45,10 @@ status = "okay"; }; +&touchkeys { + status = "okay"; +}; + &usdhc3 { status = "okay"; };