summaryrefslogtreecommitdiffstats
path: root/drivers/input/misc
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--drivers/input/misc/88pm80x_onkey.c165
-rw-r--r--drivers/input/misc/88pm860x_onkey.c145
-rw-r--r--drivers/input/misc/Kconfig932
-rw-r--r--drivers/input/misc/Makefile91
-rw-r--r--drivers/input/misc/ab8500-ponkey.c130
-rw-r--r--drivers/input/misc/ad714x-i2c.c110
-rw-r--r--drivers/input/misc/ad714x-spi.c115
-rw-r--r--drivers/input/misc/ad714x.c1209
-rw-r--r--drivers/input/misc/ad714x.h53
-rw-r--r--drivers/input/misc/adxl34x-i2c.c171
-rw-r--r--drivers/input/misc/adxl34x-spi.c133
-rw-r--r--drivers/input/misc/adxl34x.c910
-rw-r--r--drivers/input/misc/adxl34x.h30
-rw-r--r--drivers/input/misc/apanel.c305
-rw-r--r--drivers/input/misc/ariel-pwrbutton.c170
-rw-r--r--drivers/input/misc/arizona-haptics.c215
-rw-r--r--drivers/input/misc/atc260x-onkey.c305
-rw-r--r--drivers/input/misc/ati_remote2.c1035
-rw-r--r--drivers/input/misc/atlas_btns.c144
-rw-r--r--drivers/input/misc/atmel_captouch.c281
-rw-r--r--drivers/input/misc/axp20x-pek.c424
-rw-r--r--drivers/input/misc/bma150.c563
-rw-r--r--drivers/input/misc/cm109.c949
-rw-r--r--drivers/input/misc/cma3000_d0x.c388
-rw-r--r--drivers/input/misc/cma3000_d0x.h31
-rw-r--r--drivers/input/misc/cma3000_d0x_i2c.c118
-rw-r--r--drivers/input/misc/cobalt_btns.c128
-rw-r--r--drivers/input/misc/cpcap-pwrbutton.c120
-rw-r--r--drivers/input/misc/da7280.c1332
-rw-r--r--drivers/input/misc/da9052_onkey.c155
-rw-r--r--drivers/input/misc/da9055_onkey.c161
-rw-r--r--drivers/input/misc/da9063_onkey.c276
-rw-r--r--drivers/input/misc/dm355evm_keys.c238
-rw-r--r--drivers/input/misc/drv260x.c670
-rw-r--r--drivers/input/misc/drv2665.c313
-rw-r--r--drivers/input/misc/drv2667.c490
-rw-r--r--drivers/input/misc/e3x0-button.c135
-rw-r--r--drivers/input/misc/gpio-beeper.c114
-rw-r--r--drivers/input/misc/gpio-vibra.c207
-rw-r--r--drivers/input/misc/gpio_decoder.c132
-rw-r--r--drivers/input/misc/hisi_powerkey.c129
-rw-r--r--drivers/input/misc/hp_sdc_rtc.c377
-rw-r--r--drivers/input/misc/ibm-panel.c200
-rw-r--r--drivers/input/misc/ideapad_slidebar.c353
-rw-r--r--drivers/input/misc/ims-pcu.c2149
-rw-r--r--drivers/input/misc/iqs269a.c1826
-rw-r--r--drivers/input/misc/iqs626a.c1841
-rw-r--r--drivers/input/misc/iqs7222.c2602
-rw-r--r--drivers/input/misc/keyspan_remote.c590
-rw-r--r--drivers/input/misc/kxtj9.c550
-rw-r--r--drivers/input/misc/m68kspkr.c144
-rw-r--r--drivers/input/misc/max77650-onkey.c129
-rw-r--r--drivers/input/misc/max77693-haptic.c427
-rw-r--r--drivers/input/misc/max8925_onkey.c173
-rw-r--r--drivers/input/misc/max8997_haptic.c401
-rw-r--r--drivers/input/misc/mc13783-pwrbutton.c269
-rw-r--r--drivers/input/misc/mma8450.c214
-rw-r--r--drivers/input/misc/palmas-pwrbutton.c327
-rw-r--r--drivers/input/misc/pcap_keys.c127
-rw-r--r--drivers/input/misc/pcf50633-input.c115
-rw-r--r--drivers/input/misc/pcf8574_keypad.c221
-rw-r--r--drivers/input/misc/pcspkr.c136
-rw-r--r--drivers/input/misc/pm8941-pwrkey.c481
-rw-r--r--drivers/input/misc/pm8xxx-vibrator.c262
-rw-r--r--drivers/input/misc/pmic8xxx-pwrkey.c454
-rw-r--r--drivers/input/misc/powermate.c457
-rw-r--r--drivers/input/misc/pwm-beeper.c262
-rw-r--r--drivers/input/misc/pwm-vibra.c270
-rw-r--r--drivers/input/misc/rave-sp-pwrbutton.c94
-rw-r--r--drivers/input/misc/rb532_button.c94
-rw-r--r--drivers/input/misc/regulator-haptic.c264
-rw-r--r--drivers/input/misc/retu-pwrbutton.c92
-rw-r--r--drivers/input/misc/rk805-pwrkey.c104
-rw-r--r--drivers/input/misc/rotary_encoder.c370
-rw-r--r--drivers/input/misc/rt5120-pwrkey.c120
-rw-r--r--drivers/input/misc/sc27xx-vibra.c201
-rw-r--r--drivers/input/misc/sgi_btns.c131
-rw-r--r--drivers/input/misc/soc_button_array.c625
-rw-r--r--drivers/input/misc/sparcspkr.c366
-rw-r--r--drivers/input/misc/stpmic1_onkey.c192
-rw-r--r--drivers/input/misc/tps65218-pwrbutton.c160
-rw-r--r--drivers/input/misc/twl4030-pwrbutton.c115
-rw-r--r--drivers/input/misc/twl4030-vibra.c245
-rw-r--r--drivers/input/misc/twl6040-vibra.c366
-rw-r--r--drivers/input/misc/uinput.c1102
-rw-r--r--drivers/input/misc/wistron_btns.c1395
-rw-r--r--drivers/input/misc/wm831x-on.c150
-rw-r--r--drivers/input/misc/xen-kbdfront.c573
-rw-r--r--drivers/input/misc/yealink.c996
-rw-r--r--drivers/input/misc/yealink.h206
90 files changed, 37040 insertions, 0 deletions
diff --git a/drivers/input/misc/88pm80x_onkey.c b/drivers/input/misc/88pm80x_onkey.c
new file mode 100644
index 000000000..51c8a326f
--- /dev/null
+++ b/drivers/input/misc/88pm80x_onkey.c
@@ -0,0 +1,165 @@
+/*
+ * Marvell 88PM80x ONKEY driver
+ *
+ * Copyright (C) 2012 Marvell International Ltd.
+ * Haojian Zhuang <haojian.zhuang@marvell.com>
+ * Qiao Zhou <zhouqiao@marvell.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License. See the file "COPYING" in the main directory of this
+ * archive for more details.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/mfd/88pm80x.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#define PM800_LONG_ONKEY_EN (1 << 0)
+#define PM800_LONG_KEY_DELAY (8) /* 1 .. 16 seconds */
+#define PM800_LONKEY_PRESS_TIME ((PM800_LONG_KEY_DELAY-1) << 4)
+#define PM800_LONKEY_PRESS_TIME_MASK (0xF0)
+#define PM800_SW_PDOWN (1 << 5)
+
+struct pm80x_onkey_info {
+ struct input_dev *idev;
+ struct pm80x_chip *pm80x;
+ struct regmap *map;
+ int irq;
+};
+
+/* 88PM80x gives us an interrupt when ONKEY is held */
+static irqreturn_t pm80x_onkey_handler(int irq, void *data)
+{
+ struct pm80x_onkey_info *info = data;
+ int ret = 0;
+ unsigned int val;
+
+ ret = regmap_read(info->map, PM800_STATUS_1, &val);
+ if (ret < 0) {
+ dev_err(info->idev->dev.parent, "failed to read status: %d\n", ret);
+ return IRQ_NONE;
+ }
+ val &= PM800_ONKEY_STS1;
+
+ input_report_key(info->idev, KEY_POWER, val);
+ input_sync(info->idev);
+
+ return IRQ_HANDLED;
+}
+
+static SIMPLE_DEV_PM_OPS(pm80x_onkey_pm_ops, pm80x_dev_suspend,
+ pm80x_dev_resume);
+
+static int pm80x_onkey_probe(struct platform_device *pdev)
+{
+
+ struct pm80x_chip *chip = dev_get_drvdata(pdev->dev.parent);
+ struct pm80x_onkey_info *info;
+ int err;
+
+ info = kzalloc(sizeof(struct pm80x_onkey_info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ info->pm80x = chip;
+
+ info->irq = platform_get_irq(pdev, 0);
+ if (info->irq < 0) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ info->map = info->pm80x->regmap;
+ if (!info->map) {
+ dev_err(&pdev->dev, "no regmap!\n");
+ err = -EINVAL;
+ goto out;
+ }
+
+ info->idev = input_allocate_device();
+ if (!info->idev) {
+ dev_err(&pdev->dev, "Failed to allocate input dev\n");
+ err = -ENOMEM;
+ goto out;
+ }
+
+ info->idev->name = "88pm80x_on";
+ info->idev->phys = "88pm80x_on/input0";
+ info->idev->id.bustype = BUS_I2C;
+ info->idev->dev.parent = &pdev->dev;
+ info->idev->evbit[0] = BIT_MASK(EV_KEY);
+ __set_bit(KEY_POWER, info->idev->keybit);
+
+ err = pm80x_request_irq(info->pm80x, info->irq, pm80x_onkey_handler,
+ IRQF_ONESHOT, "onkey", info);
+ if (err < 0) {
+ dev_err(&pdev->dev, "Failed to request IRQ: #%d: %d\n",
+ info->irq, err);
+ goto out_reg;
+ }
+
+ err = input_register_device(info->idev);
+ if (err) {
+ dev_err(&pdev->dev, "Can't register input device: %d\n", err);
+ goto out_irq;
+ }
+
+ platform_set_drvdata(pdev, info);
+
+ /* Enable long onkey detection */
+ regmap_update_bits(info->map, PM800_RTC_MISC4, PM800_LONG_ONKEY_EN,
+ PM800_LONG_ONKEY_EN);
+ /* Set 8-second interval */
+ regmap_update_bits(info->map, PM800_RTC_MISC3,
+ PM800_LONKEY_PRESS_TIME_MASK,
+ PM800_LONKEY_PRESS_TIME);
+
+ device_init_wakeup(&pdev->dev, 1);
+ return 0;
+
+out_irq:
+ pm80x_free_irq(info->pm80x, info->irq, info);
+out_reg:
+ input_free_device(info->idev);
+out:
+ kfree(info);
+ return err;
+}
+
+static int pm80x_onkey_remove(struct platform_device *pdev)
+{
+ struct pm80x_onkey_info *info = platform_get_drvdata(pdev);
+
+ pm80x_free_irq(info->pm80x, info->irq, info);
+ input_unregister_device(info->idev);
+ kfree(info);
+ return 0;
+}
+
+static struct platform_driver pm80x_onkey_driver = {
+ .driver = {
+ .name = "88pm80x-onkey",
+ .pm = &pm80x_onkey_pm_ops,
+ },
+ .probe = pm80x_onkey_probe,
+ .remove = pm80x_onkey_remove,
+};
+
+module_platform_driver(pm80x_onkey_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Marvell 88PM80x ONKEY driver");
+MODULE_AUTHOR("Qiao Zhou <zhouqiao@marvell.com>");
+MODULE_ALIAS("platform:88pm80x-onkey");
diff --git a/drivers/input/misc/88pm860x_onkey.c b/drivers/input/misc/88pm860x_onkey.c
new file mode 100644
index 000000000..685995cad
--- /dev/null
+++ b/drivers/input/misc/88pm860x_onkey.c
@@ -0,0 +1,145 @@
+/*
+ * 88pm860x_onkey.c - Marvell 88PM860x ONKEY driver
+ *
+ * Copyright (C) 2009-2010 Marvell International Ltd.
+ * Haojian Zhuang <haojian.zhuang@marvell.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License. See the file "COPYING" in the main directory of this
+ * archive for more details.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/88pm860x.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+
+#define PM8607_WAKEUP 0x0b
+
+#define LONG_ONKEY_EN (1 << 1)
+#define ONKEY_STATUS (1 << 0)
+
+struct pm860x_onkey_info {
+ struct input_dev *idev;
+ struct pm860x_chip *chip;
+ struct i2c_client *i2c;
+ struct device *dev;
+ int irq;
+};
+
+/* 88PM860x gives us an interrupt when ONKEY is held */
+static irqreturn_t pm860x_onkey_handler(int irq, void *data)
+{
+ struct pm860x_onkey_info *info = data;
+ int ret;
+
+ ret = pm860x_reg_read(info->i2c, PM8607_STATUS_2);
+ ret &= ONKEY_STATUS;
+ input_report_key(info->idev, KEY_POWER, ret);
+ input_sync(info->idev);
+
+ /* Enable 8-second long onkey detection */
+ pm860x_set_bits(info->i2c, PM8607_WAKEUP, 3, LONG_ONKEY_EN);
+ return IRQ_HANDLED;
+}
+
+static int pm860x_onkey_probe(struct platform_device *pdev)
+{
+ struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent);
+ struct pm860x_onkey_info *info;
+ int irq, ret;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return -EINVAL;
+
+ info = devm_kzalloc(&pdev->dev, sizeof(struct pm860x_onkey_info),
+ GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+ info->chip = chip;
+ info->i2c = (chip->id == CHIP_PM8607) ? chip->client : chip->companion;
+ info->dev = &pdev->dev;
+ info->irq = irq;
+
+ info->idev = devm_input_allocate_device(&pdev->dev);
+ if (!info->idev) {
+ dev_err(chip->dev, "Failed to allocate input dev\n");
+ return -ENOMEM;
+ }
+
+ info->idev->name = "88pm860x_on";
+ info->idev->phys = "88pm860x_on/input0";
+ info->idev->id.bustype = BUS_I2C;
+ info->idev->dev.parent = &pdev->dev;
+ info->idev->evbit[0] = BIT_MASK(EV_KEY);
+ info->idev->keybit[BIT_WORD(KEY_POWER)] = BIT_MASK(KEY_POWER);
+
+ ret = input_register_device(info->idev);
+ if (ret) {
+ dev_err(chip->dev, "Can't register input device: %d\n", ret);
+ return ret;
+ }
+
+ ret = devm_request_threaded_irq(&pdev->dev, info->irq, NULL,
+ pm860x_onkey_handler, IRQF_ONESHOT,
+ "onkey", info);
+ if (ret < 0) {
+ dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n",
+ info->irq, ret);
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, info);
+ device_init_wakeup(&pdev->dev, 1);
+
+ return 0;
+}
+
+static int __maybe_unused pm860x_onkey_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent);
+
+ if (device_may_wakeup(dev))
+ chip->wakeup_flag |= 1 << PM8607_IRQ_ONKEY;
+ return 0;
+}
+static int __maybe_unused pm860x_onkey_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent);
+
+ if (device_may_wakeup(dev))
+ chip->wakeup_flag &= ~(1 << PM8607_IRQ_ONKEY);
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(pm860x_onkey_pm_ops, pm860x_onkey_suspend, pm860x_onkey_resume);
+
+static struct platform_driver pm860x_onkey_driver = {
+ .driver = {
+ .name = "88pm860x-onkey",
+ .pm = &pm860x_onkey_pm_ops,
+ },
+ .probe = pm860x_onkey_probe,
+};
+module_platform_driver(pm860x_onkey_driver);
+
+MODULE_DESCRIPTION("Marvell 88PM860x ONKEY driver");
+MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
new file mode 100644
index 000000000..fa9426516
--- /dev/null
+++ b/drivers/input/misc/Kconfig
@@ -0,0 +1,932 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Input misc drivers configuration
+#
+menuconfig INPUT_MISC
+ bool "Miscellaneous devices"
+ help
+ Say Y here, and a list of miscellaneous input drivers will be displayed.
+ Everything that didn't fit into the other categories is here. This option
+ doesn't affect the kernel.
+
+ If unsure, say Y.
+
+if INPUT_MISC
+
+config INPUT_88PM860X_ONKEY
+ tristate "88PM860x ONKEY support"
+ depends on MFD_88PM860X
+ help
+ Support the ONKEY of Marvell 88PM860x PMICs as an input device
+ reporting power button status.
+
+ To compile this driver as a module, choose M here: the module
+ will be called 88pm860x_onkey.
+
+config INPUT_88PM80X_ONKEY
+ tristate "88PM80x ONKEY support"
+ depends on MFD_88PM800
+ help
+ Support the ONKEY of Marvell 88PM80x PMICs as an input device
+ reporting power button status.
+
+ To compile this driver as a module, choose M here: the module
+ will be called 88pm80x_onkey.
+
+config INPUT_AB8500_PONKEY
+ tristate "AB8500 Pon (PowerOn) Key"
+ depends on AB8500_CORE
+ help
+ Say Y here to use the PowerOn Key for ST-Ericsson's AB8500
+ Mix-Sig PMIC.
+
+ To compile this driver as a module, choose M here: the module
+ will be called ab8500-ponkey.
+
+config INPUT_AD714X
+ tristate "Analog Devices AD714x Capacitance Touch Sensor"
+ help
+ Say Y here if you want to support an AD7142/3/7/8/7A touch sensor.
+
+ You should select a bus connection too.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ad714x.
+
+config INPUT_AD714X_I2C
+ tristate "support I2C bus connection"
+ depends on INPUT_AD714X && I2C
+ default y
+ help
+ Say Y here if you have AD7142/AD7147 hooked to an I2C bus.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ad714x-i2c.
+
+config INPUT_AD714X_SPI
+ tristate "support SPI bus connection"
+ depends on INPUT_AD714X && SPI
+ default y
+ help
+ Say Y here if you have AD7142/AD7147 hooked to a SPI bus.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ad714x-spi.
+
+config INPUT_ARIEL_PWRBUTTON
+ tristate "Dell Wyse 3020 Power Button Driver"
+ depends on SPI
+ depends on MACH_MMP3_DT || COMPILE_TEST
+ help
+ Say Y to enable support for reporting power button status on
+ on Dell Wyse 3020 ("Ariel") thin client.
+
+ To compile this driver as a module, choose M here: the module
+ will be called ariel-pwrbutton.
+
+config INPUT_ARIZONA_HAPTICS
+ tristate "Arizona haptics support"
+ depends on MFD_ARIZONA && SND_SOC
+ select INPUT_FF_MEMLESS
+ help
+ Say Y to enable support for the haptics module in Arizona CODECs.
+
+ To compile this driver as a module, choose M here: the
+ module will be called arizona-haptics.
+
+config INPUT_ATC260X_ONKEY
+ tristate "Actions Semi ATC260x PMIC ONKEY"
+ depends on MFD_ATC260X
+ help
+ Support the ONKEY of ATC260x PMICs as an input device reporting
+ power button status. ONKEY can be used to wakeup from low power
+ modes and force a reset on long press.
+
+ To compile this driver as a module, choose M here: the
+ module will be called atc260x-onkey.
+
+config INPUT_ATMEL_CAPTOUCH
+ tristate "Atmel Capacitive Touch Button Driver"
+ depends on OF || COMPILE_TEST
+ depends on I2C
+ help
+ Say Y here if an Atmel Capacitive Touch Button device which
+ implements "captouch" protocol is connected to I2C bus. Typically
+ this device consists of Atmel Touch sensor controlled by AtMegaXX
+ MCU running firmware based on Qtouch library.
+ One should find "atmel,captouch" node in the board specific DTS.
+
+ To compile this driver as a module, choose M here: the
+ module will be called atmel_captouch.
+
+config INPUT_BMA150
+ tristate "BMA150/SMB380 acceleration sensor support"
+ depends on I2C
+ help
+ Say Y here if you have Bosch Sensortec's BMA150 or SMB380
+ acceleration sensor hooked to an I2C bus.
+
+ To compile this driver as a module, choose M here: the
+ module will be called bma150.
+
+config INPUT_E3X0_BUTTON
+ tristate "NI Ettus Research USRP E3xx Button support."
+ default n
+ help
+ Say Y here to enable support for the NI Ettus Research
+ USRP E3xx Button.
+
+ To compile this driver as a module, choose M here: the
+ module will be called e3x0_button.
+
+config INPUT_PCSPKR
+ tristate "PC Speaker support"
+ depends on PCSPKR_PLATFORM
+ help
+ Say Y here if you want the standard PC Speaker to be used for
+ bells and whistles.
+
+ If unsure, say Y.
+
+ To compile this driver as a module, choose M here: the
+ module will be called pcspkr.
+
+config INPUT_PM8941_PWRKEY
+ tristate "Qualcomm PM8941 power key support"
+ depends on MFD_SPMI_PMIC
+ help
+ Say Y here if you want support for the power key usually found
+ on boards using a Qualcomm PM8941 compatible PMIC.
+
+ If unsure, say Y.
+
+ To compile this driver as a module, choose M here: the module
+ will be called pm8941-pwrkey.
+
+config INPUT_PM8XXX_VIBRATOR
+ tristate "Qualcomm PM8XXX vibrator support"
+ depends on MFD_PM8XXX || MFD_SPMI_PMIC
+ select INPUT_FF_MEMLESS
+ help
+ This option enables device driver support for the vibrator
+ on Qualcomm PM8xxx chip. This driver supports ff-memless interface
+ from input framework.
+
+ To compile this driver as module, choose M here: the
+ module will be called pm8xxx-vibrator.
+
+config INPUT_PMIC8XXX_PWRKEY
+ tristate "PMIC8XXX power key support"
+ depends on MFD_PM8XXX
+ help
+ Say Y here if you want support for the PMIC8XXX power key.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called pmic8xxx-pwrkey.
+
+config INPUT_SPARCSPKR
+ tristate "SPARC Speaker support"
+ depends on PCI && SPARC64
+ help
+ Say Y here if you want the standard Speaker on Sparc PCI systems
+ to be used for bells and whistles.
+
+ If unsure, say Y.
+
+ To compile this driver as a module, choose M here: the
+ module will be called sparcspkr.
+
+config INPUT_M68K_BEEP
+ tristate "M68k Beeper support"
+ depends on M68K
+
+config INPUT_MAX77650_ONKEY
+ tristate "Maxim MAX77650 ONKEY support"
+ depends on MFD_MAX77650
+ help
+ Support the ONKEY of the MAX77650 PMIC as an input device.
+
+ To compile this driver as a module, choose M here: the module
+ will be called max77650-onkey.
+
+config INPUT_MAX77693_HAPTIC
+ tristate "MAXIM MAX77693/MAX77843 haptic controller support"
+ depends on (MFD_MAX77693 || MFD_MAX77843) && PWM
+ select INPUT_FF_MEMLESS
+ help
+ This option enables support for the haptic controller on
+ MAXIM MAX77693 and MAX77843 chips.
+
+ To compile this driver as module, choose M here: the
+ module will be called max77693-haptic.
+
+config INPUT_MAX8925_ONKEY
+ tristate "MAX8925 ONKEY support"
+ depends on MFD_MAX8925
+ help
+ Support the ONKEY of MAX8925 PMICs as an input device
+ reporting power button status.
+
+ To compile this driver as a module, choose M here: the module
+ will be called max8925_onkey.
+
+config INPUT_MAX8997_HAPTIC
+ tristate "MAXIM MAX8997 haptic controller support"
+ depends on PWM && MFD_MAX8997
+ select INPUT_FF_MEMLESS
+ help
+ This option enables device driver support for the haptic controller
+ on MAXIM MAX8997 chip. This driver supports ff-memless interface
+ from input framework.
+
+ To compile this driver as module, choose M here: the
+ module will be called max8997-haptic.
+
+config INPUT_MC13783_PWRBUTTON
+ tristate "MC13783 ON buttons"
+ depends on MFD_MC13XXX
+ help
+ Support the ON buttons of MC13783 PMIC as an input device
+ reporting power button status.
+
+ To compile this driver as a module, choose M here: the module
+ will be called mc13783-pwrbutton.
+
+config INPUT_MMA8450
+ tristate "MMA8450 - Freescale's 3-Axis, 8/12-bit Digital Accelerometer"
+ depends on I2C
+ help
+ Say Y here if you want to support Freescale's MMA8450 Accelerometer
+ through I2C interface.
+
+ To compile this driver as a module, choose M here: the
+ module will be called mma8450.
+
+config INPUT_APANEL
+ tristate "Fujitsu Lifebook Application Panel buttons"
+ depends on X86 && I2C && LEDS_CLASS
+ select CHECK_SIGNATURE
+ help
+ Say Y here for support of the Application Panel buttons, used on
+ Fujitsu Lifebook. These are attached to the mainboard through
+ an SMBus interface managed by the I2C Intel ICH (i801) driver,
+ which you should also build for this kernel.
+
+ To compile this driver as a module, choose M here: the module will
+ be called apanel.
+
+config INPUT_GPIO_BEEPER
+ tristate "Generic GPIO Beeper support"
+ depends on GPIOLIB || COMPILE_TEST
+ help
+ Say Y here if you have a beeper connected to a GPIO pin.
+
+ To compile this driver as a module, choose M here: the
+ module will be called gpio-beeper.
+
+config INPUT_GPIO_DECODER
+ tristate "Polled GPIO Decoder Input driver"
+ depends on GPIOLIB || COMPILE_TEST
+ help
+ Say Y here if you want driver to read status of multiple GPIO
+ lines and report the encoded value as an absolute integer to
+ input subsystem.
+
+ To compile this driver as a module, choose M here: the module
+ will be called gpio_decoder.
+
+config INPUT_GPIO_VIBRA
+ tristate "GPIO vibrator support"
+ depends on GPIOLIB || COMPILE_TEST
+ select INPUT_FF_MEMLESS
+ help
+ Say Y here to get support for GPIO based vibrator devices.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the module will be
+ called gpio-vibra.
+
+config INPUT_COBALT_BTNS
+ tristate "Cobalt button interface"
+ depends on MIPS_COBALT
+ help
+ Say Y here if you want to support MIPS Cobalt button interface.
+
+ To compile this driver as a module, choose M here: the
+ module will be called cobalt_btns.
+
+config INPUT_CPCAP_PWRBUTTON
+ tristate "CPCAP OnKey"
+ depends on MFD_CPCAP
+ help
+ Say Y here if you want to enable power key reporting via the
+ Motorola CPCAP chip.
+
+ To compile this driver as a module, choose M here. The module will
+ be called cpcap-pwrbutton.
+
+config INPUT_WISTRON_BTNS
+ tristate "x86 Wistron laptop button interface"
+ depends on X86_32 && !UML
+ select INPUT_SPARSEKMAP
+ select NEW_LEDS
+ select LEDS_CLASS
+ select CHECK_SIGNATURE
+ help
+ Say Y here for support of Wistron laptop button interfaces, used on
+ laptops of various brands, including Acer and Fujitsu-Siemens. If
+ available, mail and wifi LEDs will be controllable via /sys/class/leds.
+
+ To compile this driver as a module, choose M here: the module will
+ be called wistron_btns.
+
+config INPUT_ATLAS_BTNS
+ tristate "x86 Atlas button interface"
+ depends on X86 && ACPI
+ help
+ Say Y here for support of Atlas wallmount touchscreen buttons.
+ The events will show up as scancodes F1 through F9 via evdev.
+
+ To compile this driver as a module, choose M here: the module will
+ be called atlas_btns.
+
+config INPUT_ATI_REMOTE2
+ tristate "ATI / Philips USB RF remote control"
+ depends on USB_ARCH_HAS_HCD
+ select USB
+ help
+ Say Y here if you want to use an ATI or Philips USB RF remote control.
+ These are RF remotes with USB receivers.
+ ATI Remote Wonder II comes with some ATI's All-In-Wonder video cards
+ and is also available as a separate product.
+ This driver provides mouse pointer, left and right mouse buttons,
+ and maps all the other remote buttons to keypress events.
+
+ To compile this driver as a module, choose M here: the module will be
+ called ati_remote2.
+
+config INPUT_KEYSPAN_REMOTE
+ tristate "Keyspan DMR USB remote control"
+ depends on USB_ARCH_HAS_HCD
+ select USB
+ help
+ Say Y here if you want to use a Keyspan DMR USB remote control.
+ Currently only the UIA-11 type of receiver has been tested. The tag
+ on the receiver that connects to the USB port should have a P/N that
+ will tell you what type of DMR you have. The UIA-10 type is not
+ supported at this time. This driver maps all buttons to keypress
+ events.
+
+ To compile this driver as a module, choose M here: the module will
+ be called keyspan_remote.
+
+config INPUT_KXTJ9
+ tristate "Kionix KXTJ9 tri-axis digital accelerometer"
+ depends on I2C
+ help
+ Say Y here to enable support for the Kionix KXTJ9 digital tri-axis
+ accelerometer.
+
+ To compile this driver as a module, choose M here: the module will
+ be called kxtj9.
+
+config INPUT_POWERMATE
+ tristate "Griffin PowerMate and Contour Jog support"
+ depends on USB_ARCH_HAS_HCD
+ select USB
+ help
+ Say Y here if you want to use Griffin PowerMate or Contour Jog devices.
+ These are aluminum dials which can measure clockwise and anticlockwise
+ rotation. The dial also acts as a pushbutton. The base contains an LED
+ which can be instructed to pulse or to switch to a particular intensity.
+
+ You can download userspace tools from
+ <http://sowerbutts.com/powermate/>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called powermate.
+
+config INPUT_YEALINK
+ tristate "Yealink usb-p1k voip phone"
+ depends on USB_ARCH_HAS_HCD
+ select USB
+ help
+ Say Y here if you want to enable keyboard and LCD functions of the
+ Yealink usb-p1k usb phones. The audio part is enabled by the generic
+ usb sound driver, so you might want to enable that as well.
+
+ For information about how to use these additional functions, see
+ <file:Documentation/input/devices/yealink.rst>.
+
+ To compile this driver as a module, choose M here: the module will be
+ called yealink.
+
+config INPUT_CM109
+ tristate "C-Media CM109 USB I/O Controller"
+ depends on USB_ARCH_HAS_HCD
+ select USB
+ help
+ Say Y here if you want to enable keyboard and buzzer functions of the
+ C-Media CM109 usb phones. The audio part is enabled by the generic
+ usb sound driver, so you might want to enable that as well.
+
+ To compile this driver as a module, choose M here: the module will be
+ called cm109.
+
+config INPUT_REGULATOR_HAPTIC
+ tristate "Regulator haptics support"
+ depends on REGULATOR
+ select INPUT_FF_MEMLESS
+ help
+ This option enables device driver support for the haptic controlled
+ by a regulator. This driver supports ff-memless interface
+ from input framework.
+
+ To compile this driver as a module, choose M here: the
+ module will be called regulator-haptic.
+
+config INPUT_RETU_PWRBUTTON
+ tristate "Retu Power button Driver"
+ depends on MFD_RETU
+ help
+ Say Y here if you want to enable power key reporting via the
+ Retu chips found in Nokia Internet Tablets (770, N800, N810).
+
+ To compile this driver as a module, choose M here. The module will
+ be called retu-pwrbutton.
+
+config INPUT_TPS65218_PWRBUTTON
+ tristate "TPS65218 Power button driver"
+ depends on (MFD_TPS65217 || MFD_TPS65218)
+ help
+ Say Y here if you want to enable power button reporting for
+ TPS65217 and TPS65218 Power Management IC devices.
+
+ To compile this driver as a module, choose M here. The module will
+ be called tps65218-pwrbutton.
+
+config INPUT_AXP20X_PEK
+ tristate "X-Powers AXP20X power button driver"
+ depends on MFD_AXP20X
+ help
+ Say Y here if you want to enable power key reporting via the
+ AXP20X PMIC.
+
+ To compile this driver as a module, choose M here. The module will
+ be called axp20x-pek.
+
+
+config INPUT_TWL4030_PWRBUTTON
+ tristate "TWL4030 Power button Driver"
+ depends on TWL4030_CORE
+ help
+ Say Y here if you want to enable power key reporting via the
+ TWL4030 family of chips.
+
+ To compile this driver as a module, choose M here. The module will
+ be called twl4030_pwrbutton.
+
+config INPUT_TWL4030_VIBRA
+ tristate "Support for TWL4030 Vibrator"
+ depends on TWL4030_CORE
+ select MFD_TWL4030_AUDIO
+ select INPUT_FF_MEMLESS
+ help
+ This option enables support for TWL4030 Vibrator Driver.
+
+ To compile this driver as a module, choose M here. The module will
+ be called twl4030_vibra.
+
+config INPUT_TWL6040_VIBRA
+ tristate "Support for TWL6040 Vibrator"
+ depends on TWL6040_CORE
+ select INPUT_FF_MEMLESS
+ help
+ This option enables support for TWL6040 Vibrator Driver.
+
+ To compile this driver as a module, choose M here. The module will
+ be called twl6040_vibra.
+
+config INPUT_UINPUT
+ tristate "User level driver support"
+ help
+ Say Y here if you want to support user level drivers for input
+ subsystem accessible under char device 10:223 - /dev/input/uinput.
+
+ To compile this driver as a module, choose M here: the
+ module will be called uinput.
+
+config INPUT_SGI_BTNS
+ tristate "SGI Indy/O2 volume button interface"
+ depends on SGI_IP22 || SGI_IP32
+ help
+ Say Y here if you want to support SGI Indy/O2 volume button interface.
+
+ To compile this driver as a module, choose M here: the
+ module will be called sgi_btns.
+
+config HP_SDC_RTC
+ tristate "HP SDC Real Time Clock"
+ depends on (GSC || HP300) && SERIO
+ select HP_SDC
+ help
+ Say Y here if you want to support the built-in real time clock
+ of the HP SDC controller.
+
+config INPUT_PALMAS_PWRBUTTON
+ tristate "Palmas Power button Driver"
+ depends on MFD_PALMAS
+ help
+ Say Y here if you want to enable power key reporting via the
+ Palmas family of PMICs.
+
+ To compile this driver as a module, choose M here. The module will
+ be called palmas_pwrbutton.
+
+config INPUT_PCF50633_PMU
+ tristate "PCF50633 PMU events"
+ depends on MFD_PCF50633
+ help
+ Say Y to include support for delivering PMU events via input
+ layer on NXP PCF50633.
+
+config INPUT_PCF8574
+ tristate "PCF8574 Keypad input device"
+ depends on I2C
+ help
+ Say Y here if you want to support a keypad connected via I2C
+ with a PCF8574.
+
+ To compile this driver as a module, choose M here: the
+ module will be called pcf8574_keypad.
+
+config INPUT_PWM_BEEPER
+ tristate "PWM beeper support"
+ depends on PWM
+ help
+ Say Y here to get support for PWM based beeper devices.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the module will be
+ called pwm-beeper.
+
+config INPUT_PWM_VIBRA
+ tristate "PWM vibrator support"
+ depends on PWM
+ select INPUT_FF_MEMLESS
+ help
+ Say Y here to get support for PWM based vibrator devices.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the module will be
+ called pwm-vibra.
+
+config INPUT_RK805_PWRKEY
+ tristate "Rockchip RK805 PMIC power key support"
+ depends on MFD_RK808
+ help
+ Select this option to enable power key driver for RK805.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the module will be
+ called rk805_pwrkey.
+
+config INPUT_GPIO_ROTARY_ENCODER
+ tristate "Rotary encoders connected to GPIO pins"
+ depends on GPIOLIB || COMPILE_TEST
+ help
+ Say Y here to add support for rotary encoders connected to GPIO lines.
+ Check file:Documentation/input/devices/rotary-encoder.rst for more
+ information.
+
+ To compile this driver as a module, choose M here: the
+ module will be called rotary_encoder.
+
+config INPUT_RB532_BUTTON
+ tristate "Mikrotik Routerboard 532 button interface"
+ depends on MIKROTIK_RB532
+ depends on GPIOLIB
+ help
+ Say Y here if you want support for the S1 button built into
+ Mikrotik's Routerboard 532.
+
+ To compile this driver as a module, choose M here: the
+ module will be called rb532_button.
+
+config INPUT_DA7280_HAPTICS
+ tristate "Dialog Semiconductor DA7280 haptics support"
+ depends on INPUT && I2C
+ select REGMAP_I2C
+ help
+ Say Y to enable support for the Dialog DA7280 haptics driver.
+ The haptics can be controlled by PWM or GPIO
+ with I2C communication.
+
+ To compile this driver as a module, choose M here: the
+ module will be called da7280.
+
+config INPUT_DA9052_ONKEY
+ tristate "Dialog DA9052/DA9053 Onkey"
+ depends on PMIC_DA9052
+ help
+ Support the ONKEY of Dialog DA9052 PMICs as an input device
+ reporting power button status.
+
+ To compile this driver as a module, choose M here: the
+ module will be called da9052_onkey.
+
+config INPUT_DA9055_ONKEY
+ tristate "Dialog Semiconductor DA9055 ONKEY"
+ depends on MFD_DA9055
+ help
+ Support the ONKEY of DA9055 PMICs as an input device
+ reporting power button status.
+
+ To compile this driver as a module, choose M here: the module
+ will be called da9055_onkey.
+
+config INPUT_DA9063_ONKEY
+ tristate "Dialog DA9063/62/61 OnKey"
+ depends on MFD_DA9063 || MFD_DA9062
+ help
+ Support the ONKEY of Dialog DA9063, DA9062 and DA9061 Power
+ Management ICs as an input device capable of reporting the
+ power button status.
+
+ To compile this driver as a module, choose M here: the module
+ will be called da9063_onkey.
+
+config INPUT_DM355EVM
+ tristate "TI DaVinci DM355 EVM Keypad and IR Remote"
+ depends on MFD_DM355EVM_MSP
+ select INPUT_SPARSEKMAP
+ help
+ Supports the pushbuttons and IR remote used with
+ the DM355 EVM board.
+
+ To compile this driver as a module, choose M here: the
+ module will be called dm355evm_keys.
+
+config INPUT_WM831X_ON
+ tristate "WM831X ON pin"
+ depends on MFD_WM831X
+ help
+ Support the ON pin of WM831X PMICs as an input device
+ reporting power button status.
+
+ To compile this driver as a module, choose M here: the module
+ will be called wm831x_on.
+
+config INPUT_PCAP
+ tristate "Motorola EZX PCAP misc input events"
+ depends on EZX_PCAP
+ help
+ Say Y here if you want to use Power key and Headphone button
+ on Motorola EZX phones.
+
+ To compile this driver as a module, choose M here: the
+ module will be called pcap_keys.
+
+config INPUT_ADXL34X
+ tristate "Analog Devices ADXL34x Three-Axis Digital Accelerometer"
+ default n
+ help
+ Say Y here if you have a Accelerometer interface using the
+ ADXL345/6 controller, and your board-specific initialization
+ code includes that in its table of devices.
+
+ This driver can use either I2C or SPI communication to the
+ ADXL345/6 controller. Select the appropriate method for
+ your system.
+
+ If unsure, say N (but it's safe to say "Y").
+
+ To compile this driver as a module, choose M here: the
+ module will be called adxl34x.
+
+config INPUT_ADXL34X_I2C
+ tristate "support I2C bus connection"
+ depends on INPUT_ADXL34X && I2C
+ default y
+ help
+ Say Y here if you have ADXL345/6 hooked to an I2C bus.
+
+ To compile this driver as a module, choose M here: the
+ module will be called adxl34x-i2c.
+
+config INPUT_ADXL34X_SPI
+ tristate "support SPI bus connection"
+ depends on INPUT_ADXL34X && SPI
+ default y
+ help
+ Say Y here if you have ADXL345/6 hooked to a SPI bus.
+
+ To compile this driver as a module, choose M here: the
+ module will be called adxl34x-spi.
+
+config INPUT_IBM_PANEL
+ tristate "IBM Operation Panel driver"
+ depends on I2C && I2C_SLAVE
+ help
+ Say Y here if you have an IBM Operation Panel connected to your system
+ over I2C. The panel is typically connected only to a system's service
+ processor (BMC).
+
+ If unsure, say N.
+
+ The Operation Panel is a controller with some buttons and an LCD
+ display that allows someone with physical access to the system to
+ perform various administrative tasks. This driver only supports the part
+ of the controller that sends commands to the system.
+
+ To compile this driver as a module, choose M here: the module will be
+ called ibm-panel.
+
+config INPUT_IMS_PCU
+ tristate "IMS Passenger Control Unit driver"
+ depends on USB
+ depends on LEDS_CLASS
+ help
+ Say Y here if you have system with IMS Rave Passenger Control Unit.
+
+ To compile this driver as a module, choose M here: the module will be
+ called ims_pcu.
+
+config INPUT_IQS269A
+ tristate "Azoteq IQS269A capacitive touch controller"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ Say Y to enable support for the Azoteq IQS269A capacitive
+ touch controller.
+
+ To compile this driver as a module, choose M here: the
+ module will be called iqs269a.
+
+config INPUT_IQS626A
+ tristate "Azoteq IQS626A capacitive touch controller"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ Say Y to enable support for the Azoteq IQS626A capacitive
+ touch controller.
+
+ To compile this driver as a module, choose M here: the
+ module will be called iqs626a.
+
+config INPUT_IQS7222
+ tristate "Azoteq IQS7222A/B/C capacitive touch controller"
+ depends on I2C
+ help
+ Say Y to enable support for the Azoteq IQS7222A/B/C family
+ of capacitive touch controllers.
+
+ To compile this driver as a module, choose M here: the
+ module will be called iqs7222.
+
+config INPUT_CMA3000
+ tristate "VTI CMA3000 Tri-axis accelerometer"
+ help
+ Say Y here if you want to use VTI CMA3000_D0x Accelerometer
+ driver
+
+ This driver currently only supports I2C interface to the
+ controller. Also select the I2C method.
+
+ If unsure, say N
+
+ To compile this driver as a module, choose M here: the
+ module will be called cma3000_d0x.
+
+config INPUT_CMA3000_I2C
+ tristate "Support I2C bus connection"
+ depends on INPUT_CMA3000 && I2C
+ help
+ Say Y here if you want to use VTI CMA3000_D0x Accelerometer
+ through I2C interface.
+
+ To compile this driver as a module, choose M here: the
+ module will be called cma3000_d0x_i2c.
+
+config INPUT_XEN_KBDDEV_FRONTEND
+ tristate "Xen virtual keyboard and mouse support"
+ depends on XEN
+ default y
+ select XEN_XENBUS_FRONTEND
+ help
+ This driver implements the front-end of the Xen virtual
+ keyboard and mouse device driver. It communicates with a back-end
+ in another domain.
+
+ To compile this driver as a module, choose M here: the
+ module will be called xen-kbdfront.
+
+config INPUT_IDEAPAD_SLIDEBAR
+ tristate "IdeaPad Laptop Slidebar"
+ depends on INPUT
+ depends on SERIO_I8042
+ help
+ Say Y here if you have an IdeaPad laptop with a slidebar.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ideapad_slidebar.
+
+config INPUT_SOC_BUTTON_ARRAY
+ tristate "Windows-compatible SoC Button Array"
+ depends on KEYBOARD_GPIO && ACPI
+ help
+ Say Y here if you have a SoC-based tablet that originally runs
+ Windows 8 or a Microsoft Surface Book 2, Pro 5, Laptop 1 or later.
+
+ To compile this driver as a module, choose M here: the
+ module will be called soc_button_array.
+
+config INPUT_DRV260X_HAPTICS
+ tristate "TI DRV260X haptics support"
+ depends on INPUT && I2C
+ depends on GPIOLIB || COMPILE_TEST
+ select INPUT_FF_MEMLESS
+ select REGMAP_I2C
+ help
+ Say Y to enable support for the TI DRV260X haptics driver.
+
+ To compile this driver as a module, choose M here: the
+ module will be called drv260x-haptics.
+
+config INPUT_DRV2665_HAPTICS
+ tristate "TI DRV2665 haptics support"
+ depends on INPUT && I2C
+ select INPUT_FF_MEMLESS
+ select REGMAP_I2C
+ help
+ Say Y to enable support for the TI DRV2665 haptics driver.
+
+ To compile this driver as a module, choose M here: the
+ module will be called drv2665-haptics.
+
+config INPUT_DRV2667_HAPTICS
+ tristate "TI DRV2667 haptics support"
+ depends on INPUT && I2C
+ select INPUT_FF_MEMLESS
+ select REGMAP_I2C
+ help
+ Say Y to enable support for the TI DRV2667 haptics driver.
+
+ To compile this driver as a module, choose M here: the
+ module will be called drv2667-haptics.
+
+config INPUT_HISI_POWERKEY
+ tristate "Hisilicon PMIC ONKEY support"
+ depends on ARCH_HISI || COMPILE_TEST
+ help
+ Say Y to enable support for PMIC ONKEY.
+
+ To compile this driver as a module, choose M here: the
+ module will be called hisi_powerkey.
+
+config INPUT_RAVE_SP_PWRBUTTON
+ tristate "RAVE SP Power button Driver"
+ depends on RAVE_SP_CORE
+ help
+ Say Y here if you want to enable power key reporting from RAVE SP
+
+ To compile this driver as a module, choose M here: the
+ module will be called rave-sp-pwrbutton.
+
+config INPUT_SC27XX_VIBRA
+ tristate "Spreadtrum sc27xx vibrator support"
+ depends on MFD_SC27XX_PMIC || COMPILE_TEST
+ select INPUT_FF_MEMLESS
+ help
+ This option enables support for Spreadtrum sc27xx vibrator driver.
+
+ To compile this driver as a module, choose M here. The module will
+ be called sc27xx_vibra.
+
+config INPUT_RT5120_PWRKEY
+ tristate "RT5120 PMIC power key support"
+ depends on MFD_RT5120 || COMPILE_TEST
+ help
+ This enables support for RT5120 PMIC power key driver.
+
+ To compile this driver as a module, choose M here. the module will
+ be called rt5120-pwrkey.
+
+config INPUT_STPMIC1_ONKEY
+ tristate "STPMIC1 PMIC Onkey support"
+ depends on MFD_STPMIC1
+ help
+ Say Y to enable support of onkey embedded into STPMIC1 PMIC. onkey
+ can be used to wakeup from low power modes and force a shut-down on
+ long press.
+
+ To compile this driver as a module, choose M here: the
+ module will be called stpmic1_onkey.
+
+endif
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
new file mode 100644
index 000000000..6abefc410
--- /dev/null
+++ b/drivers/input/misc/Makefile
@@ -0,0 +1,91 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the input misc drivers.
+#
+
+# Each configuration option enables a list of files.
+
+obj-$(CONFIG_INPUT_88PM860X_ONKEY) += 88pm860x_onkey.o
+obj-$(CONFIG_INPUT_88PM80X_ONKEY) += 88pm80x_onkey.o
+obj-$(CONFIG_INPUT_AB8500_PONKEY) += ab8500-ponkey.o
+obj-$(CONFIG_INPUT_AD714X) += ad714x.o
+obj-$(CONFIG_INPUT_AD714X_I2C) += ad714x-i2c.o
+obj-$(CONFIG_INPUT_AD714X_SPI) += ad714x-spi.o
+obj-$(CONFIG_INPUT_ADXL34X) += adxl34x.o
+obj-$(CONFIG_INPUT_ADXL34X_I2C) += adxl34x-i2c.o
+obj-$(CONFIG_INPUT_ADXL34X_SPI) += adxl34x-spi.o
+obj-$(CONFIG_INPUT_APANEL) += apanel.o
+obj-$(CONFIG_INPUT_ARIEL_PWRBUTTON) += ariel-pwrbutton.o
+obj-$(CONFIG_INPUT_ARIZONA_HAPTICS) += arizona-haptics.o
+obj-$(CONFIG_INPUT_ATC260X_ONKEY) += atc260x-onkey.o
+obj-$(CONFIG_INPUT_ATI_REMOTE2) += ati_remote2.o
+obj-$(CONFIG_INPUT_ATLAS_BTNS) += atlas_btns.o
+obj-$(CONFIG_INPUT_ATMEL_CAPTOUCH) += atmel_captouch.o
+obj-$(CONFIG_INPUT_BMA150) += bma150.o
+obj-$(CONFIG_INPUT_CM109) += cm109.o
+obj-$(CONFIG_INPUT_CMA3000) += cma3000_d0x.o
+obj-$(CONFIG_INPUT_CMA3000_I2C) += cma3000_d0x_i2c.o
+obj-$(CONFIG_INPUT_COBALT_BTNS) += cobalt_btns.o
+obj-$(CONFIG_INPUT_CPCAP_PWRBUTTON) += cpcap-pwrbutton.o
+obj-$(CONFIG_INPUT_DA7280_HAPTICS) += da7280.o
+obj-$(CONFIG_INPUT_DA9052_ONKEY) += da9052_onkey.o
+obj-$(CONFIG_INPUT_DA9055_ONKEY) += da9055_onkey.o
+obj-$(CONFIG_INPUT_DA9063_ONKEY) += da9063_onkey.o
+obj-$(CONFIG_INPUT_DM355EVM) += dm355evm_keys.o
+obj-$(CONFIG_INPUT_E3X0_BUTTON) += e3x0-button.o
+obj-$(CONFIG_INPUT_DRV260X_HAPTICS) += drv260x.o
+obj-$(CONFIG_INPUT_DRV2665_HAPTICS) += drv2665.o
+obj-$(CONFIG_INPUT_DRV2667_HAPTICS) += drv2667.o
+obj-$(CONFIG_INPUT_GPIO_BEEPER) += gpio-beeper.o
+obj-$(CONFIG_INPUT_GPIO_DECODER) += gpio_decoder.o
+obj-$(CONFIG_INPUT_GPIO_VIBRA) += gpio-vibra.o
+obj-$(CONFIG_INPUT_HISI_POWERKEY) += hisi_powerkey.o
+obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o
+obj-$(CONFIG_INPUT_IBM_PANEL) += ibm-panel.o
+obj-$(CONFIG_INPUT_IMS_PCU) += ims-pcu.o
+obj-$(CONFIG_INPUT_IQS269A) += iqs269a.o
+obj-$(CONFIG_INPUT_IQS626A) += iqs626a.o
+obj-$(CONFIG_INPUT_IQS7222) += iqs7222.o
+obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o
+obj-$(CONFIG_INPUT_KXTJ9) += kxtj9.o
+obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o
+obj-$(CONFIG_INPUT_MAX77650_ONKEY) += max77650-onkey.o
+obj-$(CONFIG_INPUT_MAX77693_HAPTIC) += max77693-haptic.o
+obj-$(CONFIG_INPUT_MAX8925_ONKEY) += max8925_onkey.o
+obj-$(CONFIG_INPUT_MAX8997_HAPTIC) += max8997_haptic.o
+obj-$(CONFIG_INPUT_MC13783_PWRBUTTON) += mc13783-pwrbutton.o
+obj-$(CONFIG_INPUT_MMA8450) += mma8450.o
+obj-$(CONFIG_INPUT_PALMAS_PWRBUTTON) += palmas-pwrbutton.o
+obj-$(CONFIG_INPUT_PCAP) += pcap_keys.o
+obj-$(CONFIG_INPUT_PCF50633_PMU) += pcf50633-input.o
+obj-$(CONFIG_INPUT_PCF8574) += pcf8574_keypad.o
+obj-$(CONFIG_INPUT_PCSPKR) += pcspkr.o
+obj-$(CONFIG_INPUT_PM8941_PWRKEY) += pm8941-pwrkey.o
+obj-$(CONFIG_INPUT_PM8XXX_VIBRATOR) += pm8xxx-vibrator.o
+obj-$(CONFIG_INPUT_PMIC8XXX_PWRKEY) += pmic8xxx-pwrkey.o
+obj-$(CONFIG_INPUT_POWERMATE) += powermate.o
+obj-$(CONFIG_INPUT_PWM_BEEPER) += pwm-beeper.o
+obj-$(CONFIG_INPUT_PWM_VIBRA) += pwm-vibra.o
+obj-$(CONFIG_INPUT_RAVE_SP_PWRBUTTON) += rave-sp-pwrbutton.o
+obj-$(CONFIG_INPUT_RB532_BUTTON) += rb532_button.o
+obj-$(CONFIG_INPUT_REGULATOR_HAPTIC) += regulator-haptic.o
+obj-$(CONFIG_INPUT_RETU_PWRBUTTON) += retu-pwrbutton.o
+obj-$(CONFIG_INPUT_RT5120_PWRKEY) += rt5120-pwrkey.o
+obj-$(CONFIG_INPUT_AXP20X_PEK) += axp20x-pek.o
+obj-$(CONFIG_INPUT_GPIO_ROTARY_ENCODER) += rotary_encoder.o
+obj-$(CONFIG_INPUT_RK805_PWRKEY) += rk805-pwrkey.o
+obj-$(CONFIG_INPUT_SC27XX_VIBRA) += sc27xx-vibra.o
+obj-$(CONFIG_INPUT_SGI_BTNS) += sgi_btns.o
+obj-$(CONFIG_INPUT_SOC_BUTTON_ARRAY) += soc_button_array.o
+obj-$(CONFIG_INPUT_SPARCSPKR) += sparcspkr.o
+obj-$(CONFIG_INPUT_STPMIC1_ONKEY) += stpmic1_onkey.o
+obj-$(CONFIG_INPUT_TPS65218_PWRBUTTON) += tps65218-pwrbutton.o
+obj-$(CONFIG_INPUT_TWL4030_PWRBUTTON) += twl4030-pwrbutton.o
+obj-$(CONFIG_INPUT_TWL4030_VIBRA) += twl4030-vibra.o
+obj-$(CONFIG_INPUT_TWL6040_VIBRA) += twl6040-vibra.o
+obj-$(CONFIG_INPUT_UINPUT) += uinput.o
+obj-$(CONFIG_INPUT_WISTRON_BTNS) += wistron_btns.o
+obj-$(CONFIG_INPUT_WM831X_ON) += wm831x-on.o
+obj-$(CONFIG_INPUT_XEN_KBDDEV_FRONTEND) += xen-kbdfront.o
+obj-$(CONFIG_INPUT_YEALINK) += yealink.o
+obj-$(CONFIG_INPUT_IDEAPAD_SLIDEBAR) += ideapad_slidebar.o
diff --git a/drivers/input/misc/ab8500-ponkey.c b/drivers/input/misc/ab8500-ponkey.c
new file mode 100644
index 000000000..a9b901368
--- /dev/null
+++ b/drivers/input/misc/ab8500-ponkey.c
@@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Sundar Iyer <sundar.iyer@stericsson.com> for ST-Ericsson
+ *
+ * AB8500 Power-On Key handler
+ */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/abx500/ab8500.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+
+/**
+ * struct ab8500_ponkey - ab8500 ponkey information
+ * @idev: pointer to input device
+ * @ab8500: ab8500 parent
+ * @irq_dbf: irq number for falling transition
+ * @irq_dbr: irq number for rising transition
+ */
+struct ab8500_ponkey {
+ struct input_dev *idev;
+ struct ab8500 *ab8500;
+ int irq_dbf;
+ int irq_dbr;
+};
+
+/* AB8500 gives us an interrupt when ONKEY is held */
+static irqreturn_t ab8500_ponkey_handler(int irq, void *data)
+{
+ struct ab8500_ponkey *ponkey = data;
+
+ if (irq == ponkey->irq_dbf)
+ input_report_key(ponkey->idev, KEY_POWER, true);
+ else if (irq == ponkey->irq_dbr)
+ input_report_key(ponkey->idev, KEY_POWER, false);
+
+ input_sync(ponkey->idev);
+
+ return IRQ_HANDLED;
+}
+
+static int ab8500_ponkey_probe(struct platform_device *pdev)
+{
+ struct ab8500 *ab8500 = dev_get_drvdata(pdev->dev.parent);
+ struct ab8500_ponkey *ponkey;
+ struct input_dev *input;
+ int irq_dbf, irq_dbr;
+ int error;
+
+ irq_dbf = platform_get_irq_byname(pdev, "ONKEY_DBF");
+ if (irq_dbf < 0)
+ return irq_dbf;
+
+ irq_dbr = platform_get_irq_byname(pdev, "ONKEY_DBR");
+ if (irq_dbr < 0)
+ return irq_dbr;
+
+ ponkey = devm_kzalloc(&pdev->dev, sizeof(struct ab8500_ponkey),
+ GFP_KERNEL);
+ if (!ponkey)
+ return -ENOMEM;
+
+ input = devm_input_allocate_device(&pdev->dev);
+ if (!input)
+ return -ENOMEM;
+
+ ponkey->idev = input;
+ ponkey->ab8500 = ab8500;
+ ponkey->irq_dbf = irq_dbf;
+ ponkey->irq_dbr = irq_dbr;
+
+ input->name = "AB8500 POn(PowerOn) Key";
+ input->dev.parent = &pdev->dev;
+
+ input_set_capability(input, EV_KEY, KEY_POWER);
+
+ error = devm_request_any_context_irq(&pdev->dev, ponkey->irq_dbf,
+ ab8500_ponkey_handler, 0,
+ "ab8500-ponkey-dbf", ponkey);
+ if (error < 0) {
+ dev_err(ab8500->dev, "Failed to request dbf IRQ#%d: %d\n",
+ ponkey->irq_dbf, error);
+ return error;
+ }
+
+ error = devm_request_any_context_irq(&pdev->dev, ponkey->irq_dbr,
+ ab8500_ponkey_handler, 0,
+ "ab8500-ponkey-dbr", ponkey);
+ if (error < 0) {
+ dev_err(ab8500->dev, "Failed to request dbr IRQ#%d: %d\n",
+ ponkey->irq_dbr, error);
+ return error;
+ }
+
+ error = input_register_device(ponkey->idev);
+ if (error) {
+ dev_err(ab8500->dev, "Can't register input device: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id ab8500_ponkey_match[] = {
+ { .compatible = "stericsson,ab8500-ponkey", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, ab8500_ponkey_match);
+#endif
+
+static struct platform_driver ab8500_ponkey_driver = {
+ .driver = {
+ .name = "ab8500-poweron-key",
+ .of_match_table = of_match_ptr(ab8500_ponkey_match),
+ },
+ .probe = ab8500_ponkey_probe,
+};
+module_platform_driver(ab8500_ponkey_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Sundar Iyer <sundar.iyer@stericsson.com>");
+MODULE_DESCRIPTION("ST-Ericsson AB8500 Power-ON(Pon) Key driver");
diff --git a/drivers/input/misc/ad714x-i2c.c b/drivers/input/misc/ad714x-i2c.c
new file mode 100644
index 000000000..efeef1350
--- /dev/null
+++ b/drivers/input/misc/ad714x-i2c.c
@@ -0,0 +1,110 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * AD714X CapTouch Programmable Controller driver (I2C bus)
+ *
+ * Copyright 2009-2011 Analog Devices Inc.
+ */
+
+#include <linux/input.h> /* BUS_I2C */
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/pm.h>
+#include "ad714x.h"
+
+static int __maybe_unused ad714x_i2c_suspend(struct device *dev)
+{
+ return ad714x_disable(i2c_get_clientdata(to_i2c_client(dev)));
+}
+
+static int __maybe_unused ad714x_i2c_resume(struct device *dev)
+{
+ return ad714x_enable(i2c_get_clientdata(to_i2c_client(dev)));
+}
+
+static SIMPLE_DEV_PM_OPS(ad714x_i2c_pm, ad714x_i2c_suspend, ad714x_i2c_resume);
+
+static int ad714x_i2c_write(struct ad714x_chip *chip,
+ unsigned short reg, unsigned short data)
+{
+ struct i2c_client *client = to_i2c_client(chip->dev);
+ int error;
+
+ chip->xfer_buf[0] = cpu_to_be16(reg);
+ chip->xfer_buf[1] = cpu_to_be16(data);
+
+ error = i2c_master_send(client, (u8 *)chip->xfer_buf,
+ 2 * sizeof(*chip->xfer_buf));
+ if (unlikely(error < 0)) {
+ dev_err(&client->dev, "I2C write error: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int ad714x_i2c_read(struct ad714x_chip *chip,
+ unsigned short reg, unsigned short *data, size_t len)
+{
+ struct i2c_client *client = to_i2c_client(chip->dev);
+ int i;
+ int error;
+
+ chip->xfer_buf[0] = cpu_to_be16(reg);
+
+ error = i2c_master_send(client, (u8 *)chip->xfer_buf,
+ sizeof(*chip->xfer_buf));
+ if (error >= 0)
+ error = i2c_master_recv(client, (u8 *)chip->xfer_buf,
+ len * sizeof(*chip->xfer_buf));
+
+ if (unlikely(error < 0)) {
+ dev_err(&client->dev, "I2C read error: %d\n", error);
+ return error;
+ }
+
+ for (i = 0; i < len; i++)
+ data[i] = be16_to_cpu(chip->xfer_buf[i]);
+
+ return 0;
+}
+
+static int ad714x_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct ad714x_chip *chip;
+
+ chip = ad714x_probe(&client->dev, BUS_I2C, client->irq,
+ ad714x_i2c_read, ad714x_i2c_write);
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+
+ i2c_set_clientdata(client, chip);
+
+ return 0;
+}
+
+static const struct i2c_device_id ad714x_id[] = {
+ { "ad7142_captouch", 0 },
+ { "ad7143_captouch", 0 },
+ { "ad7147_captouch", 0 },
+ { "ad7147a_captouch", 0 },
+ { "ad7148_captouch", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ad714x_id);
+
+static struct i2c_driver ad714x_i2c_driver = {
+ .driver = {
+ .name = "ad714x_captouch",
+ .pm = &ad714x_i2c_pm,
+ },
+ .probe = ad714x_i2c_probe,
+ .id_table = ad714x_id,
+};
+
+module_i2c_driver(ad714x_i2c_driver);
+
+MODULE_DESCRIPTION("Analog Devices AD714X Capacitance Touch Sensor I2C Bus Driver");
+MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/ad714x-spi.c b/drivers/input/misc/ad714x-spi.c
new file mode 100644
index 000000000..7d3bf4346
--- /dev/null
+++ b/drivers/input/misc/ad714x-spi.c
@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * AD714X CapTouch Programmable Controller driver (SPI bus)
+ *
+ * Copyright 2009-2011 Analog Devices Inc.
+ */
+
+#include <linux/input.h> /* BUS_SPI */
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+#include <linux/pm.h>
+#include <linux/types.h>
+#include "ad714x.h"
+
+#define AD714x_SPI_CMD_PREFIX 0xE000 /* bits 15:11 */
+#define AD714x_SPI_READ BIT(10)
+
+static int __maybe_unused ad714x_spi_suspend(struct device *dev)
+{
+ return ad714x_disable(spi_get_drvdata(to_spi_device(dev)));
+}
+
+static int __maybe_unused ad714x_spi_resume(struct device *dev)
+{
+ return ad714x_enable(spi_get_drvdata(to_spi_device(dev)));
+}
+
+static SIMPLE_DEV_PM_OPS(ad714x_spi_pm, ad714x_spi_suspend, ad714x_spi_resume);
+
+static int ad714x_spi_read(struct ad714x_chip *chip,
+ unsigned short reg, unsigned short *data, size_t len)
+{
+ struct spi_device *spi = to_spi_device(chip->dev);
+ struct spi_message message;
+ struct spi_transfer xfer[2];
+ int i;
+ int error;
+
+ spi_message_init(&message);
+ memset(xfer, 0, sizeof(xfer));
+
+ chip->xfer_buf[0] = cpu_to_be16(AD714x_SPI_CMD_PREFIX |
+ AD714x_SPI_READ | reg);
+ xfer[0].tx_buf = &chip->xfer_buf[0];
+ xfer[0].len = sizeof(chip->xfer_buf[0]);
+ spi_message_add_tail(&xfer[0], &message);
+
+ xfer[1].rx_buf = &chip->xfer_buf[1];
+ xfer[1].len = sizeof(chip->xfer_buf[1]) * len;
+ spi_message_add_tail(&xfer[1], &message);
+
+ error = spi_sync(spi, &message);
+ if (unlikely(error)) {
+ dev_err(chip->dev, "SPI read error: %d\n", error);
+ return error;
+ }
+
+ for (i = 0; i < len; i++)
+ data[i] = be16_to_cpu(chip->xfer_buf[i + 1]);
+
+ return 0;
+}
+
+static int ad714x_spi_write(struct ad714x_chip *chip,
+ unsigned short reg, unsigned short data)
+{
+ struct spi_device *spi = to_spi_device(chip->dev);
+ int error;
+
+ chip->xfer_buf[0] = cpu_to_be16(AD714x_SPI_CMD_PREFIX | reg);
+ chip->xfer_buf[1] = cpu_to_be16(data);
+
+ error = spi_write(spi, (u8 *)chip->xfer_buf,
+ 2 * sizeof(*chip->xfer_buf));
+ if (unlikely(error)) {
+ dev_err(chip->dev, "SPI write error: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int ad714x_spi_probe(struct spi_device *spi)
+{
+ struct ad714x_chip *chip;
+ int err;
+
+ spi->bits_per_word = 8;
+ err = spi_setup(spi);
+ if (err < 0)
+ return err;
+
+ chip = ad714x_probe(&spi->dev, BUS_SPI, spi->irq,
+ ad714x_spi_read, ad714x_spi_write);
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+
+ spi_set_drvdata(spi, chip);
+
+ return 0;
+}
+
+static struct spi_driver ad714x_spi_driver = {
+ .driver = {
+ .name = "ad714x_captouch",
+ .pm = &ad714x_spi_pm,
+ },
+ .probe = ad714x_spi_probe,
+};
+
+module_spi_driver(ad714x_spi_driver);
+
+MODULE_DESCRIPTION("Analog Devices AD714X Capacitance Touch Sensor SPI Bus Driver");
+MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/ad714x.c b/drivers/input/misc/ad714x.c
new file mode 100644
index 000000000..43132d98f
--- /dev/null
+++ b/drivers/input/misc/ad714x.c
@@ -0,0 +1,1209 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * AD714X CapTouch Programmable Controller driver supporting AD7142/3/7/8/7A
+ *
+ * Copyright 2009-2011 Analog Devices Inc.
+ */
+
+#include <linux/device.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/input/ad714x.h>
+#include <linux/module.h>
+#include "ad714x.h"
+
+#define AD714X_PWR_CTRL 0x0
+#define AD714X_STG_CAL_EN_REG 0x1
+#define AD714X_AMB_COMP_CTRL0_REG 0x2
+#define AD714X_PARTID_REG 0x17
+#define AD7142_PARTID 0xE620
+#define AD7143_PARTID 0xE630
+#define AD7147_PARTID 0x1470
+#define AD7148_PARTID 0x1480
+#define AD714X_STAGECFG_REG 0x80
+#define AD714X_SYSCFG_REG 0x0
+
+#define STG_LOW_INT_EN_REG 0x5
+#define STG_HIGH_INT_EN_REG 0x6
+#define STG_COM_INT_EN_REG 0x7
+#define STG_LOW_INT_STA_REG 0x8
+#define STG_HIGH_INT_STA_REG 0x9
+#define STG_COM_INT_STA_REG 0xA
+
+#define CDC_RESULT_S0 0xB
+#define CDC_RESULT_S1 0xC
+#define CDC_RESULT_S2 0xD
+#define CDC_RESULT_S3 0xE
+#define CDC_RESULT_S4 0xF
+#define CDC_RESULT_S5 0x10
+#define CDC_RESULT_S6 0x11
+#define CDC_RESULT_S7 0x12
+#define CDC_RESULT_S8 0x13
+#define CDC_RESULT_S9 0x14
+#define CDC_RESULT_S10 0x15
+#define CDC_RESULT_S11 0x16
+
+#define STAGE0_AMBIENT 0xF1
+#define STAGE1_AMBIENT 0x115
+#define STAGE2_AMBIENT 0x139
+#define STAGE3_AMBIENT 0x15D
+#define STAGE4_AMBIENT 0x181
+#define STAGE5_AMBIENT 0x1A5
+#define STAGE6_AMBIENT 0x1C9
+#define STAGE7_AMBIENT 0x1ED
+#define STAGE8_AMBIENT 0x211
+#define STAGE9_AMBIENT 0x234
+#define STAGE10_AMBIENT 0x259
+#define STAGE11_AMBIENT 0x27D
+
+#define PER_STAGE_REG_NUM 36
+#define STAGE_CFGREG_NUM 8
+#define SYS_CFGREG_NUM 8
+
+/*
+ * driver information which will be used to maintain the software flow
+ */
+enum ad714x_device_state { IDLE, JITTER, ACTIVE, SPACE };
+
+struct ad714x_slider_drv {
+ int highest_stage;
+ int abs_pos;
+ int flt_pos;
+ enum ad714x_device_state state;
+ struct input_dev *input;
+};
+
+struct ad714x_wheel_drv {
+ int abs_pos;
+ int flt_pos;
+ int pre_highest_stage;
+ int highest_stage;
+ enum ad714x_device_state state;
+ struct input_dev *input;
+};
+
+struct ad714x_touchpad_drv {
+ int x_highest_stage;
+ int x_flt_pos;
+ int x_abs_pos;
+ int y_highest_stage;
+ int y_flt_pos;
+ int y_abs_pos;
+ int left_ep;
+ int left_ep_val;
+ int right_ep;
+ int right_ep_val;
+ int top_ep;
+ int top_ep_val;
+ int bottom_ep;
+ int bottom_ep_val;
+ enum ad714x_device_state state;
+ struct input_dev *input;
+};
+
+struct ad714x_button_drv {
+ enum ad714x_device_state state;
+ /*
+ * Unlike slider/wheel/touchpad, all buttons point to
+ * same input_dev instance
+ */
+ struct input_dev *input;
+};
+
+struct ad714x_driver_data {
+ struct ad714x_slider_drv *slider;
+ struct ad714x_wheel_drv *wheel;
+ struct ad714x_touchpad_drv *touchpad;
+ struct ad714x_button_drv *button;
+};
+
+/*
+ * information to integrate all things which will be private data
+ * of spi/i2c device
+ */
+
+static void ad714x_use_com_int(struct ad714x_chip *ad714x,
+ int start_stage, int end_stage)
+{
+ unsigned short data;
+ unsigned short mask;
+
+ mask = ((1 << (end_stage + 1)) - 1) - ((1 << start_stage) - 1);
+
+ ad714x->read(ad714x, STG_COM_INT_EN_REG, &data, 1);
+ data |= 1 << end_stage;
+ ad714x->write(ad714x, STG_COM_INT_EN_REG, data);
+
+ ad714x->read(ad714x, STG_HIGH_INT_EN_REG, &data, 1);
+ data &= ~mask;
+ ad714x->write(ad714x, STG_HIGH_INT_EN_REG, data);
+}
+
+static void ad714x_use_thr_int(struct ad714x_chip *ad714x,
+ int start_stage, int end_stage)
+{
+ unsigned short data;
+ unsigned short mask;
+
+ mask = ((1 << (end_stage + 1)) - 1) - ((1 << start_stage) - 1);
+
+ ad714x->read(ad714x, STG_COM_INT_EN_REG, &data, 1);
+ data &= ~(1 << end_stage);
+ ad714x->write(ad714x, STG_COM_INT_EN_REG, data);
+
+ ad714x->read(ad714x, STG_HIGH_INT_EN_REG, &data, 1);
+ data |= mask;
+ ad714x->write(ad714x, STG_HIGH_INT_EN_REG, data);
+}
+
+static int ad714x_cal_highest_stage(struct ad714x_chip *ad714x,
+ int start_stage, int end_stage)
+{
+ int max_res = 0;
+ int max_idx = 0;
+ int i;
+
+ for (i = start_stage; i <= end_stage; i++) {
+ if (ad714x->sensor_val[i] > max_res) {
+ max_res = ad714x->sensor_val[i];
+ max_idx = i;
+ }
+ }
+
+ return max_idx;
+}
+
+static int ad714x_cal_abs_pos(struct ad714x_chip *ad714x,
+ int start_stage, int end_stage,
+ int highest_stage, int max_coord)
+{
+ int a_param, b_param;
+
+ if (highest_stage == start_stage) {
+ a_param = ad714x->sensor_val[start_stage + 1];
+ b_param = ad714x->sensor_val[start_stage] +
+ ad714x->sensor_val[start_stage + 1];
+ } else if (highest_stage == end_stage) {
+ a_param = ad714x->sensor_val[end_stage] *
+ (end_stage - start_stage) +
+ ad714x->sensor_val[end_stage - 1] *
+ (end_stage - start_stage - 1);
+ b_param = ad714x->sensor_val[end_stage] +
+ ad714x->sensor_val[end_stage - 1];
+ } else {
+ a_param = ad714x->sensor_val[highest_stage] *
+ (highest_stage - start_stage) +
+ ad714x->sensor_val[highest_stage - 1] *
+ (highest_stage - start_stage - 1) +
+ ad714x->sensor_val[highest_stage + 1] *
+ (highest_stage - start_stage + 1);
+ b_param = ad714x->sensor_val[highest_stage] +
+ ad714x->sensor_val[highest_stage - 1] +
+ ad714x->sensor_val[highest_stage + 1];
+ }
+
+ return (max_coord / (end_stage - start_stage)) * a_param / b_param;
+}
+
+/*
+ * One button can connect to multi positive and negative of CDCs
+ * Multi-buttons can connect to same positive/negative of one CDC
+ */
+static void ad714x_button_state_machine(struct ad714x_chip *ad714x, int idx)
+{
+ struct ad714x_button_plat *hw = &ad714x->hw->button[idx];
+ struct ad714x_button_drv *sw = &ad714x->sw->button[idx];
+
+ switch (sw->state) {
+ case IDLE:
+ if (((ad714x->h_state & hw->h_mask) == hw->h_mask) &&
+ ((ad714x->l_state & hw->l_mask) == hw->l_mask)) {
+ dev_dbg(ad714x->dev, "button %d touched\n", idx);
+ input_report_key(sw->input, hw->keycode, 1);
+ input_sync(sw->input);
+ sw->state = ACTIVE;
+ }
+ break;
+
+ case ACTIVE:
+ if (((ad714x->h_state & hw->h_mask) != hw->h_mask) ||
+ ((ad714x->l_state & hw->l_mask) != hw->l_mask)) {
+ dev_dbg(ad714x->dev, "button %d released\n", idx);
+ input_report_key(sw->input, hw->keycode, 0);
+ input_sync(sw->input);
+ sw->state = IDLE;
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+/*
+ * The response of a sensor is defined by the absolute number of codes
+ * between the current CDC value and the ambient value.
+ */
+static void ad714x_slider_cal_sensor_val(struct ad714x_chip *ad714x, int idx)
+{
+ struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx];
+ int i;
+
+ ad714x->read(ad714x, CDC_RESULT_S0 + hw->start_stage,
+ &ad714x->adc_reg[hw->start_stage],
+ hw->end_stage - hw->start_stage + 1);
+
+ for (i = hw->start_stage; i <= hw->end_stage; i++) {
+ ad714x->read(ad714x, STAGE0_AMBIENT + i * PER_STAGE_REG_NUM,
+ &ad714x->amb_reg[i], 1);
+
+ ad714x->sensor_val[i] =
+ abs(ad714x->adc_reg[i] - ad714x->amb_reg[i]);
+ }
+}
+
+static void ad714x_slider_cal_highest_stage(struct ad714x_chip *ad714x, int idx)
+{
+ struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx];
+ struct ad714x_slider_drv *sw = &ad714x->sw->slider[idx];
+
+ sw->highest_stage = ad714x_cal_highest_stage(ad714x, hw->start_stage,
+ hw->end_stage);
+
+ dev_dbg(ad714x->dev, "slider %d highest_stage:%d\n", idx,
+ sw->highest_stage);
+}
+
+/*
+ * The formulae are very straight forward. It uses the sensor with the
+ * highest response and the 2 adjacent ones.
+ * When Sensor 0 has the highest response, only sensor 0 and sensor 1
+ * are used in the calculations. Similarly when the last sensor has the
+ * highest response, only the last sensor and the second last sensors
+ * are used in the calculations.
+ *
+ * For i= idx_of_peak_Sensor-1 to i= idx_of_peak_Sensor+1
+ * v += Sensor response(i)*i
+ * w += Sensor response(i)
+ * POS=(Number_of_Positions_Wanted/(Number_of_Sensors_Used-1)) *(v/w)
+ */
+static void ad714x_slider_cal_abs_pos(struct ad714x_chip *ad714x, int idx)
+{
+ struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx];
+ struct ad714x_slider_drv *sw = &ad714x->sw->slider[idx];
+
+ sw->abs_pos = ad714x_cal_abs_pos(ad714x, hw->start_stage, hw->end_stage,
+ sw->highest_stage, hw->max_coord);
+
+ dev_dbg(ad714x->dev, "slider %d absolute position:%d\n", idx,
+ sw->abs_pos);
+}
+
+/*
+ * To minimise the Impact of the noise on the algorithm, ADI developed a
+ * routine that filters the CDC results after they have been read by the
+ * host processor.
+ * The filter used is an Infinite Input Response(IIR) filter implemented
+ * in firmware and attenuates the noise on the CDC results after they've
+ * been read by the host processor.
+ * Filtered_CDC_result = (Filtered_CDC_result * (10 - Coefficient) +
+ * Latest_CDC_result * Coefficient)/10
+ */
+static void ad714x_slider_cal_flt_pos(struct ad714x_chip *ad714x, int idx)
+{
+ struct ad714x_slider_drv *sw = &ad714x->sw->slider[idx];
+
+ sw->flt_pos = (sw->flt_pos * (10 - 4) +
+ sw->abs_pos * 4)/10;
+
+ dev_dbg(ad714x->dev, "slider %d filter position:%d\n", idx,
+ sw->flt_pos);
+}
+
+static void ad714x_slider_use_com_int(struct ad714x_chip *ad714x, int idx)
+{
+ struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx];
+
+ ad714x_use_com_int(ad714x, hw->start_stage, hw->end_stage);
+}
+
+static void ad714x_slider_use_thr_int(struct ad714x_chip *ad714x, int idx)
+{
+ struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx];
+
+ ad714x_use_thr_int(ad714x, hw->start_stage, hw->end_stage);
+}
+
+static void ad714x_slider_state_machine(struct ad714x_chip *ad714x, int idx)
+{
+ struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx];
+ struct ad714x_slider_drv *sw = &ad714x->sw->slider[idx];
+ unsigned short h_state, c_state;
+ unsigned short mask;
+
+ mask = ((1 << (hw->end_stage + 1)) - 1) - ((1 << hw->start_stage) - 1);
+
+ h_state = ad714x->h_state & mask;
+ c_state = ad714x->c_state & mask;
+
+ switch (sw->state) {
+ case IDLE:
+ if (h_state) {
+ sw->state = JITTER;
+ /* In End of Conversion interrupt mode, the AD714X
+ * continuously generates hardware interrupts.
+ */
+ ad714x_slider_use_com_int(ad714x, idx);
+ dev_dbg(ad714x->dev, "slider %d touched\n", idx);
+ }
+ break;
+
+ case JITTER:
+ if (c_state == mask) {
+ ad714x_slider_cal_sensor_val(ad714x, idx);
+ ad714x_slider_cal_highest_stage(ad714x, idx);
+ ad714x_slider_cal_abs_pos(ad714x, idx);
+ sw->flt_pos = sw->abs_pos;
+ sw->state = ACTIVE;
+ }
+ break;
+
+ case ACTIVE:
+ if (c_state == mask) {
+ if (h_state) {
+ ad714x_slider_cal_sensor_val(ad714x, idx);
+ ad714x_slider_cal_highest_stage(ad714x, idx);
+ ad714x_slider_cal_abs_pos(ad714x, idx);
+ ad714x_slider_cal_flt_pos(ad714x, idx);
+ input_report_abs(sw->input, ABS_X, sw->flt_pos);
+ input_report_key(sw->input, BTN_TOUCH, 1);
+ } else {
+ /* When the user lifts off the sensor, configure
+ * the AD714X back to threshold interrupt mode.
+ */
+ ad714x_slider_use_thr_int(ad714x, idx);
+ sw->state = IDLE;
+ input_report_key(sw->input, BTN_TOUCH, 0);
+ dev_dbg(ad714x->dev, "slider %d released\n",
+ idx);
+ }
+ input_sync(sw->input);
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+/*
+ * When the scroll wheel is activated, we compute the absolute position based
+ * on the sensor values. To calculate the position, we first determine the
+ * sensor that has the greatest response among the 8 sensors that constitutes
+ * the scrollwheel. Then we determined the 2 sensors on either sides of the
+ * sensor with the highest response and we apply weights to these sensors.
+ */
+static void ad714x_wheel_cal_highest_stage(struct ad714x_chip *ad714x, int idx)
+{
+ struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
+ struct ad714x_wheel_drv *sw = &ad714x->sw->wheel[idx];
+
+ sw->pre_highest_stage = sw->highest_stage;
+ sw->highest_stage = ad714x_cal_highest_stage(ad714x, hw->start_stage,
+ hw->end_stage);
+
+ dev_dbg(ad714x->dev, "wheel %d highest_stage:%d\n", idx,
+ sw->highest_stage);
+}
+
+static void ad714x_wheel_cal_sensor_val(struct ad714x_chip *ad714x, int idx)
+{
+ struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
+ int i;
+
+ ad714x->read(ad714x, CDC_RESULT_S0 + hw->start_stage,
+ &ad714x->adc_reg[hw->start_stage],
+ hw->end_stage - hw->start_stage + 1);
+
+ for (i = hw->start_stage; i <= hw->end_stage; i++) {
+ ad714x->read(ad714x, STAGE0_AMBIENT + i * PER_STAGE_REG_NUM,
+ &ad714x->amb_reg[i], 1);
+ if (ad714x->adc_reg[i] > ad714x->amb_reg[i])
+ ad714x->sensor_val[i] =
+ ad714x->adc_reg[i] - ad714x->amb_reg[i];
+ else
+ ad714x->sensor_val[i] = 0;
+ }
+}
+
+/*
+ * When the scroll wheel is activated, we compute the absolute position based
+ * on the sensor values. To calculate the position, we first determine the
+ * sensor that has the greatest response among the sensors that constitutes
+ * the scrollwheel. Then we determined the sensors on either sides of the
+ * sensor with the highest response and we apply weights to these sensors. The
+ * result of this computation gives us the mean value.
+ */
+
+static void ad714x_wheel_cal_abs_pos(struct ad714x_chip *ad714x, int idx)
+{
+ struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
+ struct ad714x_wheel_drv *sw = &ad714x->sw->wheel[idx];
+ int stage_num = hw->end_stage - hw->start_stage + 1;
+ int first_before, highest, first_after;
+ int a_param, b_param;
+
+ first_before = (sw->highest_stage + stage_num - 1) % stage_num;
+ highest = sw->highest_stage;
+ first_after = (sw->highest_stage + stage_num + 1) % stage_num;
+
+ a_param = ad714x->sensor_val[highest] *
+ (highest - hw->start_stage) +
+ ad714x->sensor_val[first_before] *
+ (highest - hw->start_stage - 1) +
+ ad714x->sensor_val[first_after] *
+ (highest - hw->start_stage + 1);
+ b_param = ad714x->sensor_val[highest] +
+ ad714x->sensor_val[first_before] +
+ ad714x->sensor_val[first_after];
+
+ sw->abs_pos = ((hw->max_coord / (hw->end_stage - hw->start_stage)) *
+ a_param) / b_param;
+
+ if (sw->abs_pos > hw->max_coord)
+ sw->abs_pos = hw->max_coord;
+ else if (sw->abs_pos < 0)
+ sw->abs_pos = 0;
+}
+
+static void ad714x_wheel_cal_flt_pos(struct ad714x_chip *ad714x, int idx)
+{
+ struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
+ struct ad714x_wheel_drv *sw = &ad714x->sw->wheel[idx];
+ if (((sw->pre_highest_stage == hw->end_stage) &&
+ (sw->highest_stage == hw->start_stage)) ||
+ ((sw->pre_highest_stage == hw->start_stage) &&
+ (sw->highest_stage == hw->end_stage)))
+ sw->flt_pos = sw->abs_pos;
+ else
+ sw->flt_pos = ((sw->flt_pos * 30) + (sw->abs_pos * 71)) / 100;
+
+ if (sw->flt_pos > hw->max_coord)
+ sw->flt_pos = hw->max_coord;
+}
+
+static void ad714x_wheel_use_com_int(struct ad714x_chip *ad714x, int idx)
+{
+ struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
+
+ ad714x_use_com_int(ad714x, hw->start_stage, hw->end_stage);
+}
+
+static void ad714x_wheel_use_thr_int(struct ad714x_chip *ad714x, int idx)
+{
+ struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
+
+ ad714x_use_thr_int(ad714x, hw->start_stage, hw->end_stage);
+}
+
+static void ad714x_wheel_state_machine(struct ad714x_chip *ad714x, int idx)
+{
+ struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
+ struct ad714x_wheel_drv *sw = &ad714x->sw->wheel[idx];
+ unsigned short h_state, c_state;
+ unsigned short mask;
+
+ mask = ((1 << (hw->end_stage + 1)) - 1) - ((1 << hw->start_stage) - 1);
+
+ h_state = ad714x->h_state & mask;
+ c_state = ad714x->c_state & mask;
+
+ switch (sw->state) {
+ case IDLE:
+ if (h_state) {
+ sw->state = JITTER;
+ /* In End of Conversion interrupt mode, the AD714X
+ * continuously generates hardware interrupts.
+ */
+ ad714x_wheel_use_com_int(ad714x, idx);
+ dev_dbg(ad714x->dev, "wheel %d touched\n", idx);
+ }
+ break;
+
+ case JITTER:
+ if (c_state == mask) {
+ ad714x_wheel_cal_sensor_val(ad714x, idx);
+ ad714x_wheel_cal_highest_stage(ad714x, idx);
+ ad714x_wheel_cal_abs_pos(ad714x, idx);
+ sw->flt_pos = sw->abs_pos;
+ sw->state = ACTIVE;
+ }
+ break;
+
+ case ACTIVE:
+ if (c_state == mask) {
+ if (h_state) {
+ ad714x_wheel_cal_sensor_val(ad714x, idx);
+ ad714x_wheel_cal_highest_stage(ad714x, idx);
+ ad714x_wheel_cal_abs_pos(ad714x, idx);
+ ad714x_wheel_cal_flt_pos(ad714x, idx);
+ input_report_abs(sw->input, ABS_WHEEL,
+ sw->flt_pos);
+ input_report_key(sw->input, BTN_TOUCH, 1);
+ } else {
+ /* When the user lifts off the sensor, configure
+ * the AD714X back to threshold interrupt mode.
+ */
+ ad714x_wheel_use_thr_int(ad714x, idx);
+ sw->state = IDLE;
+ input_report_key(sw->input, BTN_TOUCH, 0);
+
+ dev_dbg(ad714x->dev, "wheel %d released\n",
+ idx);
+ }
+ input_sync(sw->input);
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void touchpad_cal_sensor_val(struct ad714x_chip *ad714x, int idx)
+{
+ struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
+ int i;
+
+ ad714x->read(ad714x, CDC_RESULT_S0 + hw->x_start_stage,
+ &ad714x->adc_reg[hw->x_start_stage],
+ hw->x_end_stage - hw->x_start_stage + 1);
+
+ for (i = hw->x_start_stage; i <= hw->x_end_stage; i++) {
+ ad714x->read(ad714x, STAGE0_AMBIENT + i * PER_STAGE_REG_NUM,
+ &ad714x->amb_reg[i], 1);
+ if (ad714x->adc_reg[i] > ad714x->amb_reg[i])
+ ad714x->sensor_val[i] =
+ ad714x->adc_reg[i] - ad714x->amb_reg[i];
+ else
+ ad714x->sensor_val[i] = 0;
+ }
+}
+
+static void touchpad_cal_highest_stage(struct ad714x_chip *ad714x, int idx)
+{
+ struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
+ struct ad714x_touchpad_drv *sw = &ad714x->sw->touchpad[idx];
+
+ sw->x_highest_stage = ad714x_cal_highest_stage(ad714x,
+ hw->x_start_stage, hw->x_end_stage);
+ sw->y_highest_stage = ad714x_cal_highest_stage(ad714x,
+ hw->y_start_stage, hw->y_end_stage);
+
+ dev_dbg(ad714x->dev,
+ "touchpad %d x_highest_stage:%d, y_highest_stage:%d\n",
+ idx, sw->x_highest_stage, sw->y_highest_stage);
+}
+
+/*
+ * If 2 fingers are touching the sensor then 2 peaks can be observed in the
+ * distribution.
+ * The arithmetic doesn't support to get absolute coordinates for multi-touch
+ * yet.
+ */
+static int touchpad_check_second_peak(struct ad714x_chip *ad714x, int idx)
+{
+ struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
+ struct ad714x_touchpad_drv *sw = &ad714x->sw->touchpad[idx];
+ int i;
+
+ for (i = hw->x_start_stage; i < sw->x_highest_stage; i++) {
+ if ((ad714x->sensor_val[i] - ad714x->sensor_val[i + 1])
+ > (ad714x->sensor_val[i + 1] / 10))
+ return 1;
+ }
+
+ for (i = sw->x_highest_stage; i < hw->x_end_stage; i++) {
+ if ((ad714x->sensor_val[i + 1] - ad714x->sensor_val[i])
+ > (ad714x->sensor_val[i] / 10))
+ return 1;
+ }
+
+ for (i = hw->y_start_stage; i < sw->y_highest_stage; i++) {
+ if ((ad714x->sensor_val[i] - ad714x->sensor_val[i + 1])
+ > (ad714x->sensor_val[i + 1] / 10))
+ return 1;
+ }
+
+ for (i = sw->y_highest_stage; i < hw->y_end_stage; i++) {
+ if ((ad714x->sensor_val[i + 1] - ad714x->sensor_val[i])
+ > (ad714x->sensor_val[i] / 10))
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * If only one finger is used to activate the touch pad then only 1 peak will be
+ * registered in the distribution. This peak and the 2 adjacent sensors will be
+ * used in the calculation of the absolute position. This will prevent hand
+ * shadows to affect the absolute position calculation.
+ */
+static void touchpad_cal_abs_pos(struct ad714x_chip *ad714x, int idx)
+{
+ struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
+ struct ad714x_touchpad_drv *sw = &ad714x->sw->touchpad[idx];
+
+ sw->x_abs_pos = ad714x_cal_abs_pos(ad714x, hw->x_start_stage,
+ hw->x_end_stage, sw->x_highest_stage, hw->x_max_coord);
+ sw->y_abs_pos = ad714x_cal_abs_pos(ad714x, hw->y_start_stage,
+ hw->y_end_stage, sw->y_highest_stage, hw->y_max_coord);
+
+ dev_dbg(ad714x->dev, "touchpad %d absolute position:(%d, %d)\n", idx,
+ sw->x_abs_pos, sw->y_abs_pos);
+}
+
+static void touchpad_cal_flt_pos(struct ad714x_chip *ad714x, int idx)
+{
+ struct ad714x_touchpad_drv *sw = &ad714x->sw->touchpad[idx];
+
+ sw->x_flt_pos = (sw->x_flt_pos * (10 - 4) +
+ sw->x_abs_pos * 4)/10;
+ sw->y_flt_pos = (sw->y_flt_pos * (10 - 4) +
+ sw->y_abs_pos * 4)/10;
+
+ dev_dbg(ad714x->dev, "touchpad %d filter position:(%d, %d)\n",
+ idx, sw->x_flt_pos, sw->y_flt_pos);
+}
+
+/*
+ * To prevent distortion from showing in the absolute position, it is
+ * necessary to detect the end points. When endpoints are detected, the
+ * driver stops updating the status variables with absolute positions.
+ * End points are detected on the 4 edges of the touchpad sensor. The
+ * method to detect them is the same for all 4.
+ * To detect the end points, the firmware computes the difference in
+ * percent between the sensor on the edge and the adjacent one. The
+ * difference is calculated in percent in order to make the end point
+ * detection independent of the pressure.
+ */
+
+#define LEFT_END_POINT_DETECTION_LEVEL 550
+#define RIGHT_END_POINT_DETECTION_LEVEL 750
+#define LEFT_RIGHT_END_POINT_DEAVTIVALION_LEVEL 850
+#define TOP_END_POINT_DETECTION_LEVEL 550
+#define BOTTOM_END_POINT_DETECTION_LEVEL 950
+#define TOP_BOTTOM_END_POINT_DEAVTIVALION_LEVEL 700
+static int touchpad_check_endpoint(struct ad714x_chip *ad714x, int idx)
+{
+ struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
+ struct ad714x_touchpad_drv *sw = &ad714x->sw->touchpad[idx];
+ int percent_sensor_diff;
+
+ /* left endpoint detect */
+ percent_sensor_diff = (ad714x->sensor_val[hw->x_start_stage] -
+ ad714x->sensor_val[hw->x_start_stage + 1]) * 100 /
+ ad714x->sensor_val[hw->x_start_stage + 1];
+ if (!sw->left_ep) {
+ if (percent_sensor_diff >= LEFT_END_POINT_DETECTION_LEVEL) {
+ sw->left_ep = 1;
+ sw->left_ep_val =
+ ad714x->sensor_val[hw->x_start_stage + 1];
+ }
+ } else {
+ if ((percent_sensor_diff < LEFT_END_POINT_DETECTION_LEVEL) &&
+ (ad714x->sensor_val[hw->x_start_stage + 1] >
+ LEFT_RIGHT_END_POINT_DEAVTIVALION_LEVEL + sw->left_ep_val))
+ sw->left_ep = 0;
+ }
+
+ /* right endpoint detect */
+ percent_sensor_diff = (ad714x->sensor_val[hw->x_end_stage] -
+ ad714x->sensor_val[hw->x_end_stage - 1]) * 100 /
+ ad714x->sensor_val[hw->x_end_stage - 1];
+ if (!sw->right_ep) {
+ if (percent_sensor_diff >= RIGHT_END_POINT_DETECTION_LEVEL) {
+ sw->right_ep = 1;
+ sw->right_ep_val =
+ ad714x->sensor_val[hw->x_end_stage - 1];
+ }
+ } else {
+ if ((percent_sensor_diff < RIGHT_END_POINT_DETECTION_LEVEL) &&
+ (ad714x->sensor_val[hw->x_end_stage - 1] >
+ LEFT_RIGHT_END_POINT_DEAVTIVALION_LEVEL + sw->right_ep_val))
+ sw->right_ep = 0;
+ }
+
+ /* top endpoint detect */
+ percent_sensor_diff = (ad714x->sensor_val[hw->y_start_stage] -
+ ad714x->sensor_val[hw->y_start_stage + 1]) * 100 /
+ ad714x->sensor_val[hw->y_start_stage + 1];
+ if (!sw->top_ep) {
+ if (percent_sensor_diff >= TOP_END_POINT_DETECTION_LEVEL) {
+ sw->top_ep = 1;
+ sw->top_ep_val =
+ ad714x->sensor_val[hw->y_start_stage + 1];
+ }
+ } else {
+ if ((percent_sensor_diff < TOP_END_POINT_DETECTION_LEVEL) &&
+ (ad714x->sensor_val[hw->y_start_stage + 1] >
+ TOP_BOTTOM_END_POINT_DEAVTIVALION_LEVEL + sw->top_ep_val))
+ sw->top_ep = 0;
+ }
+
+ /* bottom endpoint detect */
+ percent_sensor_diff = (ad714x->sensor_val[hw->y_end_stage] -
+ ad714x->sensor_val[hw->y_end_stage - 1]) * 100 /
+ ad714x->sensor_val[hw->y_end_stage - 1];
+ if (!sw->bottom_ep) {
+ if (percent_sensor_diff >= BOTTOM_END_POINT_DETECTION_LEVEL) {
+ sw->bottom_ep = 1;
+ sw->bottom_ep_val =
+ ad714x->sensor_val[hw->y_end_stage - 1];
+ }
+ } else {
+ if ((percent_sensor_diff < BOTTOM_END_POINT_DETECTION_LEVEL) &&
+ (ad714x->sensor_val[hw->y_end_stage - 1] >
+ TOP_BOTTOM_END_POINT_DEAVTIVALION_LEVEL + sw->bottom_ep_val))
+ sw->bottom_ep = 0;
+ }
+
+ return sw->left_ep || sw->right_ep || sw->top_ep || sw->bottom_ep;
+}
+
+static void touchpad_use_com_int(struct ad714x_chip *ad714x, int idx)
+{
+ struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
+
+ ad714x_use_com_int(ad714x, hw->x_start_stage, hw->x_end_stage);
+}
+
+static void touchpad_use_thr_int(struct ad714x_chip *ad714x, int idx)
+{
+ struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
+
+ ad714x_use_thr_int(ad714x, hw->x_start_stage, hw->x_end_stage);
+ ad714x_use_thr_int(ad714x, hw->y_start_stage, hw->y_end_stage);
+}
+
+static void ad714x_touchpad_state_machine(struct ad714x_chip *ad714x, int idx)
+{
+ struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
+ struct ad714x_touchpad_drv *sw = &ad714x->sw->touchpad[idx];
+ unsigned short h_state, c_state;
+ unsigned short mask;
+
+ mask = (((1 << (hw->x_end_stage + 1)) - 1) -
+ ((1 << hw->x_start_stage) - 1)) +
+ (((1 << (hw->y_end_stage + 1)) - 1) -
+ ((1 << hw->y_start_stage) - 1));
+
+ h_state = ad714x->h_state & mask;
+ c_state = ad714x->c_state & mask;
+
+ switch (sw->state) {
+ case IDLE:
+ if (h_state) {
+ sw->state = JITTER;
+ /* In End of Conversion interrupt mode, the AD714X
+ * continuously generates hardware interrupts.
+ */
+ touchpad_use_com_int(ad714x, idx);
+ dev_dbg(ad714x->dev, "touchpad %d touched\n", idx);
+ }
+ break;
+
+ case JITTER:
+ if (c_state == mask) {
+ touchpad_cal_sensor_val(ad714x, idx);
+ touchpad_cal_highest_stage(ad714x, idx);
+ if ((!touchpad_check_second_peak(ad714x, idx)) &&
+ (!touchpad_check_endpoint(ad714x, idx))) {
+ dev_dbg(ad714x->dev,
+ "touchpad%d, 2 fingers or endpoint\n",
+ idx);
+ touchpad_cal_abs_pos(ad714x, idx);
+ sw->x_flt_pos = sw->x_abs_pos;
+ sw->y_flt_pos = sw->y_abs_pos;
+ sw->state = ACTIVE;
+ }
+ }
+ break;
+
+ case ACTIVE:
+ if (c_state == mask) {
+ if (h_state) {
+ touchpad_cal_sensor_val(ad714x, idx);
+ touchpad_cal_highest_stage(ad714x, idx);
+ if ((!touchpad_check_second_peak(ad714x, idx))
+ && (!touchpad_check_endpoint(ad714x, idx))) {
+ touchpad_cal_abs_pos(ad714x, idx);
+ touchpad_cal_flt_pos(ad714x, idx);
+ input_report_abs(sw->input, ABS_X,
+ sw->x_flt_pos);
+ input_report_abs(sw->input, ABS_Y,
+ sw->y_flt_pos);
+ input_report_key(sw->input, BTN_TOUCH,
+ 1);
+ }
+ } else {
+ /* When the user lifts off the sensor, configure
+ * the AD714X back to threshold interrupt mode.
+ */
+ touchpad_use_thr_int(ad714x, idx);
+ sw->state = IDLE;
+ input_report_key(sw->input, BTN_TOUCH, 0);
+ dev_dbg(ad714x->dev, "touchpad %d released\n",
+ idx);
+ }
+ input_sync(sw->input);
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+static int ad714x_hw_detect(struct ad714x_chip *ad714x)
+{
+ unsigned short data;
+
+ ad714x->read(ad714x, AD714X_PARTID_REG, &data, 1);
+ switch (data & 0xFFF0) {
+ case AD7142_PARTID:
+ ad714x->product = 0x7142;
+ ad714x->version = data & 0xF;
+ dev_info(ad714x->dev, "found AD7142 captouch, rev:%d\n",
+ ad714x->version);
+ return 0;
+
+ case AD7143_PARTID:
+ ad714x->product = 0x7143;
+ ad714x->version = data & 0xF;
+ dev_info(ad714x->dev, "found AD7143 captouch, rev:%d\n",
+ ad714x->version);
+ return 0;
+
+ case AD7147_PARTID:
+ ad714x->product = 0x7147;
+ ad714x->version = data & 0xF;
+ dev_info(ad714x->dev, "found AD7147(A) captouch, rev:%d\n",
+ ad714x->version);
+ return 0;
+
+ case AD7148_PARTID:
+ ad714x->product = 0x7148;
+ ad714x->version = data & 0xF;
+ dev_info(ad714x->dev, "found AD7148 captouch, rev:%d\n",
+ ad714x->version);
+ return 0;
+
+ default:
+ dev_err(ad714x->dev,
+ "fail to detect AD714X captouch, read ID is %04x\n",
+ data);
+ return -ENODEV;
+ }
+}
+
+static void ad714x_hw_init(struct ad714x_chip *ad714x)
+{
+ int i, j;
+ unsigned short reg_base;
+ unsigned short data;
+
+ /* configuration CDC and interrupts */
+
+ for (i = 0; i < STAGE_NUM; i++) {
+ reg_base = AD714X_STAGECFG_REG + i * STAGE_CFGREG_NUM;
+ for (j = 0; j < STAGE_CFGREG_NUM; j++)
+ ad714x->write(ad714x, reg_base + j,
+ ad714x->hw->stage_cfg_reg[i][j]);
+ }
+
+ for (i = 0; i < SYS_CFGREG_NUM; i++)
+ ad714x->write(ad714x, AD714X_SYSCFG_REG + i,
+ ad714x->hw->sys_cfg_reg[i]);
+ for (i = 0; i < SYS_CFGREG_NUM; i++)
+ ad714x->read(ad714x, AD714X_SYSCFG_REG + i, &data, 1);
+
+ ad714x->write(ad714x, AD714X_STG_CAL_EN_REG, 0xFFF);
+
+ /* clear all interrupts */
+ ad714x->read(ad714x, STG_LOW_INT_STA_REG, &ad714x->l_state, 3);
+}
+
+static irqreturn_t ad714x_interrupt_thread(int irq, void *data)
+{
+ struct ad714x_chip *ad714x = data;
+ int i;
+
+ mutex_lock(&ad714x->mutex);
+
+ ad714x->read(ad714x, STG_LOW_INT_STA_REG, &ad714x->l_state, 3);
+
+ for (i = 0; i < ad714x->hw->button_num; i++)
+ ad714x_button_state_machine(ad714x, i);
+ for (i = 0; i < ad714x->hw->slider_num; i++)
+ ad714x_slider_state_machine(ad714x, i);
+ for (i = 0; i < ad714x->hw->wheel_num; i++)
+ ad714x_wheel_state_machine(ad714x, i);
+ for (i = 0; i < ad714x->hw->touchpad_num; i++)
+ ad714x_touchpad_state_machine(ad714x, i);
+
+ mutex_unlock(&ad714x->mutex);
+
+ return IRQ_HANDLED;
+}
+
+struct ad714x_chip *ad714x_probe(struct device *dev, u16 bus_type, int irq,
+ ad714x_read_t read, ad714x_write_t write)
+{
+ int i;
+ int error;
+ struct input_dev *input;
+
+ struct ad714x_platform_data *plat_data = dev_get_platdata(dev);
+ struct ad714x_chip *ad714x;
+ void *drv_mem;
+ unsigned long irqflags;
+
+ struct ad714x_button_drv *bt_drv;
+ struct ad714x_slider_drv *sd_drv;
+ struct ad714x_wheel_drv *wl_drv;
+ struct ad714x_touchpad_drv *tp_drv;
+
+
+ if (irq <= 0) {
+ dev_err(dev, "IRQ not configured!\n");
+ error = -EINVAL;
+ return ERR_PTR(error);
+ }
+
+ if (dev_get_platdata(dev) == NULL) {
+ dev_err(dev, "platform data for ad714x doesn't exist\n");
+ error = -EINVAL;
+ return ERR_PTR(error);
+ }
+
+ ad714x = devm_kzalloc(dev, sizeof(*ad714x) + sizeof(*ad714x->sw) +
+ sizeof(*sd_drv) * plat_data->slider_num +
+ sizeof(*wl_drv) * plat_data->wheel_num +
+ sizeof(*tp_drv) * plat_data->touchpad_num +
+ sizeof(*bt_drv) * plat_data->button_num,
+ GFP_KERNEL);
+ if (!ad714x) {
+ error = -ENOMEM;
+ return ERR_PTR(error);
+ }
+ ad714x->hw = plat_data;
+
+ drv_mem = ad714x + 1;
+ ad714x->sw = drv_mem;
+ drv_mem += sizeof(*ad714x->sw);
+ ad714x->sw->slider = sd_drv = drv_mem;
+ drv_mem += sizeof(*sd_drv) * ad714x->hw->slider_num;
+ ad714x->sw->wheel = wl_drv = drv_mem;
+ drv_mem += sizeof(*wl_drv) * ad714x->hw->wheel_num;
+ ad714x->sw->touchpad = tp_drv = drv_mem;
+ drv_mem += sizeof(*tp_drv) * ad714x->hw->touchpad_num;
+ ad714x->sw->button = bt_drv = drv_mem;
+ drv_mem += sizeof(*bt_drv) * ad714x->hw->button_num;
+
+ ad714x->read = read;
+ ad714x->write = write;
+ ad714x->irq = irq;
+ ad714x->dev = dev;
+
+ error = ad714x_hw_detect(ad714x);
+ if (error)
+ return ERR_PTR(error);
+
+ /* initialize and request sw/hw resources */
+
+ ad714x_hw_init(ad714x);
+ mutex_init(&ad714x->mutex);
+
+ /* a slider uses one input_dev instance */
+ if (ad714x->hw->slider_num > 0) {
+ struct ad714x_slider_plat *sd_plat = ad714x->hw->slider;
+
+ for (i = 0; i < ad714x->hw->slider_num; i++) {
+ input = devm_input_allocate_device(dev);
+ if (!input)
+ return ERR_PTR(-ENOMEM);
+
+ __set_bit(EV_ABS, input->evbit);
+ __set_bit(EV_KEY, input->evbit);
+ __set_bit(ABS_X, input->absbit);
+ __set_bit(BTN_TOUCH, input->keybit);
+ input_set_abs_params(input,
+ ABS_X, 0, sd_plat->max_coord, 0, 0);
+
+ input->id.bustype = bus_type;
+ input->id.product = ad714x->product;
+ input->id.version = ad714x->version;
+ input->name = "ad714x_captouch_slider";
+ input->dev.parent = dev;
+
+ error = input_register_device(input);
+ if (error)
+ return ERR_PTR(error);
+
+ sd_drv[i].input = input;
+ }
+ }
+
+ /* a wheel uses one input_dev instance */
+ if (ad714x->hw->wheel_num > 0) {
+ struct ad714x_wheel_plat *wl_plat = ad714x->hw->wheel;
+
+ for (i = 0; i < ad714x->hw->wheel_num; i++) {
+ input = devm_input_allocate_device(dev);
+ if (!input)
+ return ERR_PTR(-ENOMEM);
+
+ __set_bit(EV_KEY, input->evbit);
+ __set_bit(EV_ABS, input->evbit);
+ __set_bit(ABS_WHEEL, input->absbit);
+ __set_bit(BTN_TOUCH, input->keybit);
+ input_set_abs_params(input,
+ ABS_WHEEL, 0, wl_plat->max_coord, 0, 0);
+
+ input->id.bustype = bus_type;
+ input->id.product = ad714x->product;
+ input->id.version = ad714x->version;
+ input->name = "ad714x_captouch_wheel";
+ input->dev.parent = dev;
+
+ error = input_register_device(input);
+ if (error)
+ return ERR_PTR(error);
+
+ wl_drv[i].input = input;
+ }
+ }
+
+ /* a touchpad uses one input_dev instance */
+ if (ad714x->hw->touchpad_num > 0) {
+ struct ad714x_touchpad_plat *tp_plat = ad714x->hw->touchpad;
+
+ for (i = 0; i < ad714x->hw->touchpad_num; i++) {
+ input = devm_input_allocate_device(dev);
+ if (!input)
+ return ERR_PTR(-ENOMEM);
+
+ __set_bit(EV_ABS, input->evbit);
+ __set_bit(EV_KEY, input->evbit);
+ __set_bit(ABS_X, input->absbit);
+ __set_bit(ABS_Y, input->absbit);
+ __set_bit(BTN_TOUCH, input->keybit);
+ input_set_abs_params(input,
+ ABS_X, 0, tp_plat->x_max_coord, 0, 0);
+ input_set_abs_params(input,
+ ABS_Y, 0, tp_plat->y_max_coord, 0, 0);
+
+ input->id.bustype = bus_type;
+ input->id.product = ad714x->product;
+ input->id.version = ad714x->version;
+ input->name = "ad714x_captouch_pad";
+ input->dev.parent = dev;
+
+ error = input_register_device(input);
+ if (error)
+ return ERR_PTR(error);
+
+ tp_drv[i].input = input;
+ }
+ }
+
+ /* all buttons use one input node */
+ if (ad714x->hw->button_num > 0) {
+ struct ad714x_button_plat *bt_plat = ad714x->hw->button;
+
+ input = devm_input_allocate_device(dev);
+ if (!input) {
+ error = -ENOMEM;
+ return ERR_PTR(error);
+ }
+
+ __set_bit(EV_KEY, input->evbit);
+ for (i = 0; i < ad714x->hw->button_num; i++) {
+ bt_drv[i].input = input;
+ __set_bit(bt_plat[i].keycode, input->keybit);
+ }
+
+ input->id.bustype = bus_type;
+ input->id.product = ad714x->product;
+ input->id.version = ad714x->version;
+ input->name = "ad714x_captouch_button";
+ input->dev.parent = dev;
+
+ error = input_register_device(input);
+ if (error)
+ return ERR_PTR(error);
+ }
+
+ irqflags = plat_data->irqflags ?: IRQF_TRIGGER_FALLING;
+ irqflags |= IRQF_ONESHOT;
+
+ error = devm_request_threaded_irq(dev, ad714x->irq, NULL,
+ ad714x_interrupt_thread,
+ irqflags, "ad714x_captouch", ad714x);
+ if (error) {
+ dev_err(dev, "can't allocate irq %d\n", ad714x->irq);
+ return ERR_PTR(error);
+ }
+
+ return ad714x;
+}
+EXPORT_SYMBOL(ad714x_probe);
+
+#ifdef CONFIG_PM
+int ad714x_disable(struct ad714x_chip *ad714x)
+{
+ unsigned short data;
+
+ dev_dbg(ad714x->dev, "%s enter\n", __func__);
+
+ mutex_lock(&ad714x->mutex);
+
+ data = ad714x->hw->sys_cfg_reg[AD714X_PWR_CTRL] | 0x3;
+ ad714x->write(ad714x, AD714X_PWR_CTRL, data);
+
+ mutex_unlock(&ad714x->mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL(ad714x_disable);
+
+int ad714x_enable(struct ad714x_chip *ad714x)
+{
+ dev_dbg(ad714x->dev, "%s enter\n", __func__);
+
+ mutex_lock(&ad714x->mutex);
+
+ /* resume to non-shutdown mode */
+
+ ad714x->write(ad714x, AD714X_PWR_CTRL,
+ ad714x->hw->sys_cfg_reg[AD714X_PWR_CTRL]);
+
+ /* make sure the interrupt output line is not low level after resume,
+ * otherwise we will get no chance to enter falling-edge irq again
+ */
+
+ ad714x->read(ad714x, STG_LOW_INT_STA_REG, &ad714x->l_state, 3);
+
+ mutex_unlock(&ad714x->mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL(ad714x_enable);
+#endif
+
+MODULE_DESCRIPTION("Analog Devices AD714X Capacitance Touch Sensor Driver");
+MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/ad714x.h b/drivers/input/misc/ad714x.h
new file mode 100644
index 000000000..af847b5f0
--- /dev/null
+++ b/drivers/input/misc/ad714x.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * AD714X CapTouch Programmable Controller driver (bus interfaces)
+ *
+ * Copyright 2009-2011 Analog Devices Inc.
+ */
+
+#ifndef _AD714X_H_
+#define _AD714X_H_
+
+#include <linux/types.h>
+
+#define STAGE_NUM 12
+
+struct device;
+struct ad714x_platform_data;
+struct ad714x_driver_data;
+struct ad714x_chip;
+
+typedef int (*ad714x_read_t)(struct ad714x_chip *, unsigned short, unsigned short *, size_t);
+typedef int (*ad714x_write_t)(struct ad714x_chip *, unsigned short, unsigned short);
+
+struct ad714x_chip {
+ unsigned short l_state;
+ unsigned short h_state;
+ unsigned short c_state;
+ unsigned short adc_reg[STAGE_NUM];
+ unsigned short amb_reg[STAGE_NUM];
+ unsigned short sensor_val[STAGE_NUM];
+
+ struct ad714x_platform_data *hw;
+ struct ad714x_driver_data *sw;
+
+ int irq;
+ struct device *dev;
+ ad714x_read_t read;
+ ad714x_write_t write;
+
+ struct mutex mutex;
+
+ unsigned product;
+ unsigned version;
+
+ __be16 xfer_buf[16] ____cacheline_aligned;
+
+};
+
+int ad714x_disable(struct ad714x_chip *ad714x);
+int ad714x_enable(struct ad714x_chip *ad714x);
+struct ad714x_chip *ad714x_probe(struct device *dev, u16 bus_type, int irq,
+ ad714x_read_t read, ad714x_write_t write);
+
+#endif
diff --git a/drivers/input/misc/adxl34x-i2c.c b/drivers/input/misc/adxl34x-i2c.c
new file mode 100644
index 000000000..5be636aaa
--- /dev/null
+++ b/drivers/input/misc/adxl34x-i2c.c
@@ -0,0 +1,171 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ADLX345/346 Three-Axis Digital Accelerometers (I2C Interface)
+ *
+ * Enter bugs at http://blackfin.uclinux.org/
+ *
+ * Copyright (C) 2009 Michael Hennerich, Analog Devices Inc.
+ */
+
+#include <linux/input.h> /* BUS_I2C */
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/types.h>
+#include <linux/pm.h>
+#include "adxl34x.h"
+
+static int adxl34x_smbus_read(struct device *dev, unsigned char reg)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+
+ return i2c_smbus_read_byte_data(client, reg);
+}
+
+static int adxl34x_smbus_write(struct device *dev,
+ unsigned char reg, unsigned char val)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+
+ return i2c_smbus_write_byte_data(client, reg, val);
+}
+
+static int adxl34x_smbus_read_block(struct device *dev,
+ unsigned char reg, int count,
+ void *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+
+ return i2c_smbus_read_i2c_block_data(client, reg, count, buf);
+}
+
+static int adxl34x_i2c_read_block(struct device *dev,
+ unsigned char reg, int count,
+ void *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ int ret;
+
+ ret = i2c_master_send(client, &reg, 1);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_master_recv(client, buf, count);
+ if (ret < 0)
+ return ret;
+
+ if (ret != count)
+ return -EIO;
+
+ return 0;
+}
+
+static const struct adxl34x_bus_ops adxl34x_smbus_bops = {
+ .bustype = BUS_I2C,
+ .write = adxl34x_smbus_write,
+ .read = adxl34x_smbus_read,
+ .read_block = adxl34x_smbus_read_block,
+};
+
+static const struct adxl34x_bus_ops adxl34x_i2c_bops = {
+ .bustype = BUS_I2C,
+ .write = adxl34x_smbus_write,
+ .read = adxl34x_smbus_read,
+ .read_block = adxl34x_i2c_read_block,
+};
+
+static int adxl34x_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct adxl34x *ac;
+ int error;
+
+ error = i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_BYTE_DATA);
+ if (!error) {
+ dev_err(&client->dev, "SMBUS Byte Data not Supported\n");
+ return -EIO;
+ }
+
+ ac = adxl34x_probe(&client->dev, client->irq, false,
+ i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_READ_I2C_BLOCK) ?
+ &adxl34x_smbus_bops : &adxl34x_i2c_bops);
+ if (IS_ERR(ac))
+ return PTR_ERR(ac);
+
+ i2c_set_clientdata(client, ac);
+
+ return 0;
+}
+
+static void adxl34x_i2c_remove(struct i2c_client *client)
+{
+ struct adxl34x *ac = i2c_get_clientdata(client);
+
+ adxl34x_remove(ac);
+}
+
+static int __maybe_unused adxl34x_i2c_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct adxl34x *ac = i2c_get_clientdata(client);
+
+ adxl34x_suspend(ac);
+
+ return 0;
+}
+
+static int __maybe_unused adxl34x_i2c_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct adxl34x *ac = i2c_get_clientdata(client);
+
+ adxl34x_resume(ac);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(adxl34x_i2c_pm, adxl34x_i2c_suspend,
+ adxl34x_i2c_resume);
+
+static const struct i2c_device_id adxl34x_id[] = {
+ { "adxl34x", 0 },
+ { }
+};
+
+MODULE_DEVICE_TABLE(i2c, adxl34x_id);
+
+static const struct of_device_id adxl34x_of_id[] = {
+ /*
+ * The ADXL346 is backward-compatible with the ADXL345. Differences are
+ * handled by runtime detection of the device model, there's thus no
+ * need for listing the "adi,adxl346" compatible value explicitly.
+ */
+ { .compatible = "adi,adxl345", },
+ /*
+ * Deprecated, DT nodes should use one or more of the device-specific
+ * compatible values "adi,adxl345" and "adi,adxl346".
+ */
+ { .compatible = "adi,adxl34x", },
+ { }
+};
+
+MODULE_DEVICE_TABLE(of, adxl34x_of_id);
+
+static struct i2c_driver adxl34x_driver = {
+ .driver = {
+ .name = "adxl34x",
+ .pm = &adxl34x_i2c_pm,
+ .of_match_table = adxl34x_of_id,
+ },
+ .probe = adxl34x_i2c_probe,
+ .remove = adxl34x_i2c_remove,
+ .id_table = adxl34x_id,
+};
+
+module_i2c_driver(adxl34x_driver);
+
+MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>");
+MODULE_DESCRIPTION("ADXL345/346 Three-Axis Digital Accelerometer I2C Bus Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/adxl34x-spi.c b/drivers/input/misc/adxl34x-spi.c
new file mode 100644
index 000000000..91e44d4c6
--- /dev/null
+++ b/drivers/input/misc/adxl34x-spi.c
@@ -0,0 +1,133 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ADLX345/346 Three-Axis Digital Accelerometers (SPI Interface)
+ *
+ * Enter bugs at http://blackfin.uclinux.org/
+ *
+ * Copyright (C) 2009 Michael Hennerich, Analog Devices Inc.
+ */
+
+#include <linux/input.h> /* BUS_SPI */
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+#include <linux/pm.h>
+#include <linux/types.h>
+#include "adxl34x.h"
+
+#define MAX_SPI_FREQ_HZ 5000000
+#define MAX_FREQ_NO_FIFODELAY 1500000
+#define ADXL34X_CMD_MULTB (1 << 6)
+#define ADXL34X_CMD_READ (1 << 7)
+#define ADXL34X_WRITECMD(reg) (reg & 0x3F)
+#define ADXL34X_READCMD(reg) (ADXL34X_CMD_READ | (reg & 0x3F))
+#define ADXL34X_READMB_CMD(reg) (ADXL34X_CMD_READ | ADXL34X_CMD_MULTB \
+ | (reg & 0x3F))
+
+static int adxl34x_spi_read(struct device *dev, unsigned char reg)
+{
+ struct spi_device *spi = to_spi_device(dev);
+ unsigned char cmd;
+
+ cmd = ADXL34X_READCMD(reg);
+
+ return spi_w8r8(spi, cmd);
+}
+
+static int adxl34x_spi_write(struct device *dev,
+ unsigned char reg, unsigned char val)
+{
+ struct spi_device *spi = to_spi_device(dev);
+ unsigned char buf[2];
+
+ buf[0] = ADXL34X_WRITECMD(reg);
+ buf[1] = val;
+
+ return spi_write(spi, buf, sizeof(buf));
+}
+
+static int adxl34x_spi_read_block(struct device *dev,
+ unsigned char reg, int count,
+ void *buf)
+{
+ struct spi_device *spi = to_spi_device(dev);
+ ssize_t status;
+
+ reg = ADXL34X_READMB_CMD(reg);
+ status = spi_write_then_read(spi, &reg, 1, buf, count);
+
+ return (status < 0) ? status : 0;
+}
+
+static const struct adxl34x_bus_ops adxl34x_spi_bops = {
+ .bustype = BUS_SPI,
+ .write = adxl34x_spi_write,
+ .read = adxl34x_spi_read,
+ .read_block = adxl34x_spi_read_block,
+};
+
+static int adxl34x_spi_probe(struct spi_device *spi)
+{
+ struct adxl34x *ac;
+
+ /* don't exceed max specified SPI CLK frequency */
+ if (spi->max_speed_hz > MAX_SPI_FREQ_HZ) {
+ dev_err(&spi->dev, "SPI CLK %d Hz too fast\n", spi->max_speed_hz);
+ return -EINVAL;
+ }
+
+ ac = adxl34x_probe(&spi->dev, spi->irq,
+ spi->max_speed_hz > MAX_FREQ_NO_FIFODELAY,
+ &adxl34x_spi_bops);
+
+ if (IS_ERR(ac))
+ return PTR_ERR(ac);
+
+ spi_set_drvdata(spi, ac);
+
+ return 0;
+}
+
+static void adxl34x_spi_remove(struct spi_device *spi)
+{
+ struct adxl34x *ac = spi_get_drvdata(spi);
+
+ adxl34x_remove(ac);
+}
+
+static int __maybe_unused adxl34x_spi_suspend(struct device *dev)
+{
+ struct spi_device *spi = to_spi_device(dev);
+ struct adxl34x *ac = spi_get_drvdata(spi);
+
+ adxl34x_suspend(ac);
+
+ return 0;
+}
+
+static int __maybe_unused adxl34x_spi_resume(struct device *dev)
+{
+ struct spi_device *spi = to_spi_device(dev);
+ struct adxl34x *ac = spi_get_drvdata(spi);
+
+ adxl34x_resume(ac);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(adxl34x_spi_pm, adxl34x_spi_suspend,
+ adxl34x_spi_resume);
+
+static struct spi_driver adxl34x_driver = {
+ .driver = {
+ .name = "adxl34x",
+ .pm = &adxl34x_spi_pm,
+ },
+ .probe = adxl34x_spi_probe,
+ .remove = adxl34x_spi_remove,
+};
+
+module_spi_driver(adxl34x_driver);
+
+MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>");
+MODULE_DESCRIPTION("ADXL345/346 Three-Axis Digital Accelerometer SPI Bus Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/adxl34x.c b/drivers/input/misc/adxl34x.c
new file mode 100644
index 000000000..69e359ff5
--- /dev/null
+++ b/drivers/input/misc/adxl34x.c
@@ -0,0 +1,910 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ADXL345/346 Three-Axis Digital Accelerometers
+ *
+ * Enter bugs at http://blackfin.uclinux.org/
+ *
+ * Copyright (C) 2009 Michael Hennerich, Analog Devices Inc.
+ */
+
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/input/adxl34x.h>
+#include <linux/module.h>
+
+#include "adxl34x.h"
+
+/* ADXL345/6 Register Map */
+#define DEVID 0x00 /* R Device ID */
+#define THRESH_TAP 0x1D /* R/W Tap threshold */
+#define OFSX 0x1E /* R/W X-axis offset */
+#define OFSY 0x1F /* R/W Y-axis offset */
+#define OFSZ 0x20 /* R/W Z-axis offset */
+#define DUR 0x21 /* R/W Tap duration */
+#define LATENT 0x22 /* R/W Tap latency */
+#define WINDOW 0x23 /* R/W Tap window */
+#define THRESH_ACT 0x24 /* R/W Activity threshold */
+#define THRESH_INACT 0x25 /* R/W Inactivity threshold */
+#define TIME_INACT 0x26 /* R/W Inactivity time */
+#define ACT_INACT_CTL 0x27 /* R/W Axis enable control for activity and */
+ /* inactivity detection */
+#define THRESH_FF 0x28 /* R/W Free-fall threshold */
+#define TIME_FF 0x29 /* R/W Free-fall time */
+#define TAP_AXES 0x2A /* R/W Axis control for tap/double tap */
+#define ACT_TAP_STATUS 0x2B /* R Source of tap/double tap */
+#define BW_RATE 0x2C /* R/W Data rate and power mode control */
+#define POWER_CTL 0x2D /* R/W Power saving features control */
+#define INT_ENABLE 0x2E /* R/W Interrupt enable control */
+#define INT_MAP 0x2F /* R/W Interrupt mapping control */
+#define INT_SOURCE 0x30 /* R Source of interrupts */
+#define DATA_FORMAT 0x31 /* R/W Data format control */
+#define DATAX0 0x32 /* R X-Axis Data 0 */
+#define DATAX1 0x33 /* R X-Axis Data 1 */
+#define DATAY0 0x34 /* R Y-Axis Data 0 */
+#define DATAY1 0x35 /* R Y-Axis Data 1 */
+#define DATAZ0 0x36 /* R Z-Axis Data 0 */
+#define DATAZ1 0x37 /* R Z-Axis Data 1 */
+#define FIFO_CTL 0x38 /* R/W FIFO control */
+#define FIFO_STATUS 0x39 /* R FIFO status */
+#define TAP_SIGN 0x3A /* R Sign and source for tap/double tap */
+/* Orientation ADXL346 only */
+#define ORIENT_CONF 0x3B /* R/W Orientation configuration */
+#define ORIENT 0x3C /* R Orientation status */
+
+/* DEVIDs */
+#define ID_ADXL345 0xE5
+#define ID_ADXL346 0xE6
+
+/* INT_ENABLE/INT_MAP/INT_SOURCE Bits */
+#define DATA_READY (1 << 7)
+#define SINGLE_TAP (1 << 6)
+#define DOUBLE_TAP (1 << 5)
+#define ACTIVITY (1 << 4)
+#define INACTIVITY (1 << 3)
+#define FREE_FALL (1 << 2)
+#define WATERMARK (1 << 1)
+#define OVERRUN (1 << 0)
+
+/* ACT_INACT_CONTROL Bits */
+#define ACT_ACDC (1 << 7)
+#define ACT_X_EN (1 << 6)
+#define ACT_Y_EN (1 << 5)
+#define ACT_Z_EN (1 << 4)
+#define INACT_ACDC (1 << 3)
+#define INACT_X_EN (1 << 2)
+#define INACT_Y_EN (1 << 1)
+#define INACT_Z_EN (1 << 0)
+
+/* TAP_AXES Bits */
+#define SUPPRESS (1 << 3)
+#define TAP_X_EN (1 << 2)
+#define TAP_Y_EN (1 << 1)
+#define TAP_Z_EN (1 << 0)
+
+/* ACT_TAP_STATUS Bits */
+#define ACT_X_SRC (1 << 6)
+#define ACT_Y_SRC (1 << 5)
+#define ACT_Z_SRC (1 << 4)
+#define ASLEEP (1 << 3)
+#define TAP_X_SRC (1 << 2)
+#define TAP_Y_SRC (1 << 1)
+#define TAP_Z_SRC (1 << 0)
+
+/* BW_RATE Bits */
+#define LOW_POWER (1 << 4)
+#define RATE(x) ((x) & 0xF)
+
+/* POWER_CTL Bits */
+#define PCTL_LINK (1 << 5)
+#define PCTL_AUTO_SLEEP (1 << 4)
+#define PCTL_MEASURE (1 << 3)
+#define PCTL_SLEEP (1 << 2)
+#define PCTL_WAKEUP(x) ((x) & 0x3)
+
+/* DATA_FORMAT Bits */
+#define SELF_TEST (1 << 7)
+#define SPI (1 << 6)
+#define INT_INVERT (1 << 5)
+#define FULL_RES (1 << 3)
+#define JUSTIFY (1 << 2)
+#define RANGE(x) ((x) & 0x3)
+#define RANGE_PM_2g 0
+#define RANGE_PM_4g 1
+#define RANGE_PM_8g 2
+#define RANGE_PM_16g 3
+
+/*
+ * Maximum value our axis may get in full res mode for the input device
+ * (signed 13 bits)
+ */
+#define ADXL_FULLRES_MAX_VAL 4096
+
+/*
+ * Maximum value our axis may get in fixed res mode for the input device
+ * (signed 10 bits)
+ */
+#define ADXL_FIXEDRES_MAX_VAL 512
+
+/* FIFO_CTL Bits */
+#define FIFO_MODE(x) (((x) & 0x3) << 6)
+#define FIFO_BYPASS 0
+#define FIFO_FIFO 1
+#define FIFO_STREAM 2
+#define FIFO_TRIGGER 3
+#define TRIGGER (1 << 5)
+#define SAMPLES(x) ((x) & 0x1F)
+
+/* FIFO_STATUS Bits */
+#define FIFO_TRIG (1 << 7)
+#define ENTRIES(x) ((x) & 0x3F)
+
+/* TAP_SIGN Bits ADXL346 only */
+#define XSIGN (1 << 6)
+#define YSIGN (1 << 5)
+#define ZSIGN (1 << 4)
+#define XTAP (1 << 3)
+#define YTAP (1 << 2)
+#define ZTAP (1 << 1)
+
+/* ORIENT_CONF ADXL346 only */
+#define ORIENT_DEADZONE(x) (((x) & 0x7) << 4)
+#define ORIENT_DIVISOR(x) ((x) & 0x7)
+
+/* ORIENT ADXL346 only */
+#define ADXL346_2D_VALID (1 << 6)
+#define ADXL346_2D_ORIENT(x) (((x) & 0x30) >> 4)
+#define ADXL346_3D_VALID (1 << 3)
+#define ADXL346_3D_ORIENT(x) ((x) & 0x7)
+#define ADXL346_2D_PORTRAIT_POS 0 /* +X */
+#define ADXL346_2D_PORTRAIT_NEG 1 /* -X */
+#define ADXL346_2D_LANDSCAPE_POS 2 /* +Y */
+#define ADXL346_2D_LANDSCAPE_NEG 3 /* -Y */
+
+#define ADXL346_3D_FRONT 3 /* +X */
+#define ADXL346_3D_BACK 4 /* -X */
+#define ADXL346_3D_RIGHT 2 /* +Y */
+#define ADXL346_3D_LEFT 5 /* -Y */
+#define ADXL346_3D_TOP 1 /* +Z */
+#define ADXL346_3D_BOTTOM 6 /* -Z */
+
+#undef ADXL_DEBUG
+
+#define ADXL_X_AXIS 0
+#define ADXL_Y_AXIS 1
+#define ADXL_Z_AXIS 2
+
+#define AC_READ(ac, reg) ((ac)->bops->read((ac)->dev, reg))
+#define AC_WRITE(ac, reg, val) ((ac)->bops->write((ac)->dev, reg, val))
+
+struct axis_triple {
+ int x;
+ int y;
+ int z;
+};
+
+struct adxl34x {
+ struct device *dev;
+ struct input_dev *input;
+ struct mutex mutex; /* reentrant protection for struct */
+ struct adxl34x_platform_data pdata;
+ struct axis_triple swcal;
+ struct axis_triple hwcal;
+ struct axis_triple saved;
+ char phys[32];
+ unsigned orient2d_saved;
+ unsigned orient3d_saved;
+ bool disabled; /* P: mutex */
+ bool opened; /* P: mutex */
+ bool suspended; /* P: mutex */
+ bool fifo_delay;
+ int irq;
+ unsigned model;
+ unsigned int_mask;
+
+ const struct adxl34x_bus_ops *bops;
+};
+
+static const struct adxl34x_platform_data adxl34x_default_init = {
+ .tap_threshold = 35,
+ .tap_duration = 3,
+ .tap_latency = 20,
+ .tap_window = 20,
+ .tap_axis_control = ADXL_TAP_X_EN | ADXL_TAP_Y_EN | ADXL_TAP_Z_EN,
+ .act_axis_control = 0xFF,
+ .activity_threshold = 6,
+ .inactivity_threshold = 4,
+ .inactivity_time = 3,
+ .free_fall_threshold = 8,
+ .free_fall_time = 0x20,
+ .data_rate = 8,
+ .data_range = ADXL_FULL_RES,
+
+ .ev_type = EV_ABS,
+ .ev_code_x = ABS_X, /* EV_REL */
+ .ev_code_y = ABS_Y, /* EV_REL */
+ .ev_code_z = ABS_Z, /* EV_REL */
+
+ .ev_code_tap = {BTN_TOUCH, BTN_TOUCH, BTN_TOUCH}, /* EV_KEY {x,y,z} */
+ .power_mode = ADXL_AUTO_SLEEP | ADXL_LINK,
+ .fifo_mode = ADXL_FIFO_STREAM,
+ .watermark = 0,
+};
+
+static void adxl34x_get_triple(struct adxl34x *ac, struct axis_triple *axis)
+{
+ __le16 buf[3];
+
+ ac->bops->read_block(ac->dev, DATAX0, DATAZ1 - DATAX0 + 1, buf);
+
+ mutex_lock(&ac->mutex);
+ ac->saved.x = (s16) le16_to_cpu(buf[0]);
+ axis->x = ac->saved.x;
+
+ ac->saved.y = (s16) le16_to_cpu(buf[1]);
+ axis->y = ac->saved.y;
+
+ ac->saved.z = (s16) le16_to_cpu(buf[2]);
+ axis->z = ac->saved.z;
+ mutex_unlock(&ac->mutex);
+}
+
+static void adxl34x_service_ev_fifo(struct adxl34x *ac)
+{
+ struct adxl34x_platform_data *pdata = &ac->pdata;
+ struct axis_triple axis;
+
+ adxl34x_get_triple(ac, &axis);
+
+ input_event(ac->input, pdata->ev_type, pdata->ev_code_x,
+ axis.x - ac->swcal.x);
+ input_event(ac->input, pdata->ev_type, pdata->ev_code_y,
+ axis.y - ac->swcal.y);
+ input_event(ac->input, pdata->ev_type, pdata->ev_code_z,
+ axis.z - ac->swcal.z);
+}
+
+static void adxl34x_report_key_single(struct input_dev *input, int key)
+{
+ input_report_key(input, key, true);
+ input_sync(input);
+ input_report_key(input, key, false);
+}
+
+static void adxl34x_send_key_events(struct adxl34x *ac,
+ struct adxl34x_platform_data *pdata, int status, int press)
+{
+ int i;
+
+ for (i = ADXL_X_AXIS; i <= ADXL_Z_AXIS; i++) {
+ if (status & (1 << (ADXL_Z_AXIS - i)))
+ input_report_key(ac->input,
+ pdata->ev_code_tap[i], press);
+ }
+}
+
+static void adxl34x_do_tap(struct adxl34x *ac,
+ struct adxl34x_platform_data *pdata, int status)
+{
+ adxl34x_send_key_events(ac, pdata, status, true);
+ input_sync(ac->input);
+ adxl34x_send_key_events(ac, pdata, status, false);
+}
+
+static irqreturn_t adxl34x_irq(int irq, void *handle)
+{
+ struct adxl34x *ac = handle;
+ struct adxl34x_platform_data *pdata = &ac->pdata;
+ int int_stat, tap_stat, samples, orient, orient_code;
+
+ /*
+ * ACT_TAP_STATUS should be read before clearing the interrupt
+ * Avoid reading ACT_TAP_STATUS in case TAP detection is disabled
+ */
+
+ if (pdata->tap_axis_control & (TAP_X_EN | TAP_Y_EN | TAP_Z_EN))
+ tap_stat = AC_READ(ac, ACT_TAP_STATUS);
+ else
+ tap_stat = 0;
+
+ int_stat = AC_READ(ac, INT_SOURCE);
+
+ if (int_stat & FREE_FALL)
+ adxl34x_report_key_single(ac->input, pdata->ev_code_ff);
+
+ if (int_stat & OVERRUN)
+ dev_dbg(ac->dev, "OVERRUN\n");
+
+ if (int_stat & (SINGLE_TAP | DOUBLE_TAP)) {
+ adxl34x_do_tap(ac, pdata, tap_stat);
+
+ if (int_stat & DOUBLE_TAP)
+ adxl34x_do_tap(ac, pdata, tap_stat);
+ }
+
+ if (pdata->ev_code_act_inactivity) {
+ if (int_stat & ACTIVITY)
+ input_report_key(ac->input,
+ pdata->ev_code_act_inactivity, 1);
+ if (int_stat & INACTIVITY)
+ input_report_key(ac->input,
+ pdata->ev_code_act_inactivity, 0);
+ }
+
+ /*
+ * ORIENTATION SENSING ADXL346 only
+ */
+ if (pdata->orientation_enable) {
+ orient = AC_READ(ac, ORIENT);
+ if ((pdata->orientation_enable & ADXL_EN_ORIENTATION_2D) &&
+ (orient & ADXL346_2D_VALID)) {
+
+ orient_code = ADXL346_2D_ORIENT(orient);
+ /* Report orientation only when it changes */
+ if (ac->orient2d_saved != orient_code) {
+ ac->orient2d_saved = orient_code;
+ adxl34x_report_key_single(ac->input,
+ pdata->ev_codes_orient_2d[orient_code]);
+ }
+ }
+
+ if ((pdata->orientation_enable & ADXL_EN_ORIENTATION_3D) &&
+ (orient & ADXL346_3D_VALID)) {
+
+ orient_code = ADXL346_3D_ORIENT(orient) - 1;
+ /* Report orientation only when it changes */
+ if (ac->orient3d_saved != orient_code) {
+ ac->orient3d_saved = orient_code;
+ adxl34x_report_key_single(ac->input,
+ pdata->ev_codes_orient_3d[orient_code]);
+ }
+ }
+ }
+
+ if (int_stat & (DATA_READY | WATERMARK)) {
+
+ if (pdata->fifo_mode)
+ samples = ENTRIES(AC_READ(ac, FIFO_STATUS)) + 1;
+ else
+ samples = 1;
+
+ for (; samples > 0; samples--) {
+ adxl34x_service_ev_fifo(ac);
+ /*
+ * To ensure that the FIFO has
+ * completely popped, there must be at least 5 us between
+ * the end of reading the data registers, signified by the
+ * transition to register 0x38 from 0x37 or the CS pin
+ * going high, and the start of new reads of the FIFO or
+ * reading the FIFO_STATUS register. For SPI operation at
+ * 1.5 MHz or lower, the register addressing portion of the
+ * transmission is sufficient delay to ensure the FIFO has
+ * completely popped. It is necessary for SPI operation
+ * greater than 1.5 MHz to de-assert the CS pin to ensure a
+ * total of 5 us, which is at most 3.4 us at 5 MHz
+ * operation.
+ */
+ if (ac->fifo_delay && (samples > 1))
+ udelay(3);
+ }
+ }
+
+ input_sync(ac->input);
+
+ return IRQ_HANDLED;
+}
+
+static void __adxl34x_disable(struct adxl34x *ac)
+{
+ /*
+ * A '0' places the ADXL34x into standby mode
+ * with minimum power consumption.
+ */
+ AC_WRITE(ac, POWER_CTL, 0);
+}
+
+static void __adxl34x_enable(struct adxl34x *ac)
+{
+ AC_WRITE(ac, POWER_CTL, ac->pdata.power_mode | PCTL_MEASURE);
+}
+
+void adxl34x_suspend(struct adxl34x *ac)
+{
+ mutex_lock(&ac->mutex);
+
+ if (!ac->suspended && !ac->disabled && ac->opened)
+ __adxl34x_disable(ac);
+
+ ac->suspended = true;
+
+ mutex_unlock(&ac->mutex);
+}
+EXPORT_SYMBOL_GPL(adxl34x_suspend);
+
+void adxl34x_resume(struct adxl34x *ac)
+{
+ mutex_lock(&ac->mutex);
+
+ if (ac->suspended && !ac->disabled && ac->opened)
+ __adxl34x_enable(ac);
+
+ ac->suspended = false;
+
+ mutex_unlock(&ac->mutex);
+}
+EXPORT_SYMBOL_GPL(adxl34x_resume);
+
+static ssize_t adxl34x_disable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct adxl34x *ac = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%u\n", ac->disabled);
+}
+
+static ssize_t adxl34x_disable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct adxl34x *ac = dev_get_drvdata(dev);
+ unsigned int val;
+ int error;
+
+ error = kstrtouint(buf, 10, &val);
+ if (error)
+ return error;
+
+ mutex_lock(&ac->mutex);
+
+ if (!ac->suspended && ac->opened) {
+ if (val) {
+ if (!ac->disabled)
+ __adxl34x_disable(ac);
+ } else {
+ if (ac->disabled)
+ __adxl34x_enable(ac);
+ }
+ }
+
+ ac->disabled = !!val;
+
+ mutex_unlock(&ac->mutex);
+
+ return count;
+}
+
+static DEVICE_ATTR(disable, 0664, adxl34x_disable_show, adxl34x_disable_store);
+
+static ssize_t adxl34x_calibrate_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct adxl34x *ac = dev_get_drvdata(dev);
+ ssize_t count;
+
+ mutex_lock(&ac->mutex);
+ count = sprintf(buf, "%d,%d,%d\n",
+ ac->hwcal.x * 4 + ac->swcal.x,
+ ac->hwcal.y * 4 + ac->swcal.y,
+ ac->hwcal.z * 4 + ac->swcal.z);
+ mutex_unlock(&ac->mutex);
+
+ return count;
+}
+
+static ssize_t adxl34x_calibrate_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct adxl34x *ac = dev_get_drvdata(dev);
+
+ /*
+ * Hardware offset calibration has a resolution of 15.6 mg/LSB.
+ * We use HW calibration and handle the remaining bits in SW. (4mg/LSB)
+ */
+
+ mutex_lock(&ac->mutex);
+ ac->hwcal.x -= (ac->saved.x / 4);
+ ac->swcal.x = ac->saved.x % 4;
+
+ ac->hwcal.y -= (ac->saved.y / 4);
+ ac->swcal.y = ac->saved.y % 4;
+
+ ac->hwcal.z -= (ac->saved.z / 4);
+ ac->swcal.z = ac->saved.z % 4;
+
+ AC_WRITE(ac, OFSX, (s8) ac->hwcal.x);
+ AC_WRITE(ac, OFSY, (s8) ac->hwcal.y);
+ AC_WRITE(ac, OFSZ, (s8) ac->hwcal.z);
+ mutex_unlock(&ac->mutex);
+
+ return count;
+}
+
+static DEVICE_ATTR(calibrate, 0664,
+ adxl34x_calibrate_show, adxl34x_calibrate_store);
+
+static ssize_t adxl34x_rate_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct adxl34x *ac = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%u\n", RATE(ac->pdata.data_rate));
+}
+
+static ssize_t adxl34x_rate_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct adxl34x *ac = dev_get_drvdata(dev);
+ unsigned char val;
+ int error;
+
+ error = kstrtou8(buf, 10, &val);
+ if (error)
+ return error;
+
+ mutex_lock(&ac->mutex);
+
+ ac->pdata.data_rate = RATE(val);
+ AC_WRITE(ac, BW_RATE,
+ ac->pdata.data_rate |
+ (ac->pdata.low_power_mode ? LOW_POWER : 0));
+
+ mutex_unlock(&ac->mutex);
+
+ return count;
+}
+
+static DEVICE_ATTR(rate, 0664, adxl34x_rate_show, adxl34x_rate_store);
+
+static ssize_t adxl34x_autosleep_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct adxl34x *ac = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%u\n",
+ ac->pdata.power_mode & (PCTL_AUTO_SLEEP | PCTL_LINK) ? 1 : 0);
+}
+
+static ssize_t adxl34x_autosleep_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct adxl34x *ac = dev_get_drvdata(dev);
+ unsigned int val;
+ int error;
+
+ error = kstrtouint(buf, 10, &val);
+ if (error)
+ return error;
+
+ mutex_lock(&ac->mutex);
+
+ if (val)
+ ac->pdata.power_mode |= (PCTL_AUTO_SLEEP | PCTL_LINK);
+ else
+ ac->pdata.power_mode &= ~(PCTL_AUTO_SLEEP | PCTL_LINK);
+
+ if (!ac->disabled && !ac->suspended && ac->opened)
+ AC_WRITE(ac, POWER_CTL, ac->pdata.power_mode | PCTL_MEASURE);
+
+ mutex_unlock(&ac->mutex);
+
+ return count;
+}
+
+static DEVICE_ATTR(autosleep, 0664,
+ adxl34x_autosleep_show, adxl34x_autosleep_store);
+
+static ssize_t adxl34x_position_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct adxl34x *ac = dev_get_drvdata(dev);
+ ssize_t count;
+
+ mutex_lock(&ac->mutex);
+ count = sprintf(buf, "(%d, %d, %d)\n",
+ ac->saved.x, ac->saved.y, ac->saved.z);
+ mutex_unlock(&ac->mutex);
+
+ return count;
+}
+
+static DEVICE_ATTR(position, S_IRUGO, adxl34x_position_show, NULL);
+
+#ifdef ADXL_DEBUG
+static ssize_t adxl34x_write_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct adxl34x *ac = dev_get_drvdata(dev);
+ unsigned int val;
+ int error;
+
+ /*
+ * This allows basic ADXL register write access for debug purposes.
+ */
+ error = kstrtouint(buf, 16, &val);
+ if (error)
+ return error;
+
+ mutex_lock(&ac->mutex);
+ AC_WRITE(ac, val >> 8, val & 0xFF);
+ mutex_unlock(&ac->mutex);
+
+ return count;
+}
+
+static DEVICE_ATTR(write, 0664, NULL, adxl34x_write_store);
+#endif
+
+static struct attribute *adxl34x_attributes[] = {
+ &dev_attr_disable.attr,
+ &dev_attr_calibrate.attr,
+ &dev_attr_rate.attr,
+ &dev_attr_autosleep.attr,
+ &dev_attr_position.attr,
+#ifdef ADXL_DEBUG
+ &dev_attr_write.attr,
+#endif
+ NULL
+};
+
+static const struct attribute_group adxl34x_attr_group = {
+ .attrs = adxl34x_attributes,
+};
+
+static int adxl34x_input_open(struct input_dev *input)
+{
+ struct adxl34x *ac = input_get_drvdata(input);
+
+ mutex_lock(&ac->mutex);
+
+ if (!ac->suspended && !ac->disabled)
+ __adxl34x_enable(ac);
+
+ ac->opened = true;
+
+ mutex_unlock(&ac->mutex);
+
+ return 0;
+}
+
+static void adxl34x_input_close(struct input_dev *input)
+{
+ struct adxl34x *ac = input_get_drvdata(input);
+
+ mutex_lock(&ac->mutex);
+
+ if (!ac->suspended && !ac->disabled)
+ __adxl34x_disable(ac);
+
+ ac->opened = false;
+
+ mutex_unlock(&ac->mutex);
+}
+
+struct adxl34x *adxl34x_probe(struct device *dev, int irq,
+ bool fifo_delay_default,
+ const struct adxl34x_bus_ops *bops)
+{
+ struct adxl34x *ac;
+ struct input_dev *input_dev;
+ const struct adxl34x_platform_data *pdata;
+ int err, range, i;
+ int revid;
+
+ if (!irq) {
+ dev_err(dev, "no IRQ?\n");
+ err = -ENODEV;
+ goto err_out;
+ }
+
+ ac = kzalloc(sizeof(*ac), GFP_KERNEL);
+ input_dev = input_allocate_device();
+ if (!ac || !input_dev) {
+ err = -ENOMEM;
+ goto err_free_mem;
+ }
+
+ ac->fifo_delay = fifo_delay_default;
+
+ pdata = dev_get_platdata(dev);
+ if (!pdata) {
+ dev_dbg(dev,
+ "No platform data: Using default initialization\n");
+ pdata = &adxl34x_default_init;
+ }
+
+ ac->pdata = *pdata;
+ pdata = &ac->pdata;
+
+ ac->input = input_dev;
+ ac->dev = dev;
+ ac->irq = irq;
+ ac->bops = bops;
+
+ mutex_init(&ac->mutex);
+
+ input_dev->name = "ADXL34x accelerometer";
+ revid = AC_READ(ac, DEVID);
+
+ switch (revid) {
+ case ID_ADXL345:
+ ac->model = 345;
+ break;
+ case ID_ADXL346:
+ ac->model = 346;
+ break;
+ default:
+ dev_err(dev, "Failed to probe %s\n", input_dev->name);
+ err = -ENODEV;
+ goto err_free_mem;
+ }
+
+ snprintf(ac->phys, sizeof(ac->phys), "%s/input0", dev_name(dev));
+
+ input_dev->phys = ac->phys;
+ input_dev->dev.parent = dev;
+ input_dev->id.product = ac->model;
+ input_dev->id.bustype = bops->bustype;
+ input_dev->open = adxl34x_input_open;
+ input_dev->close = adxl34x_input_close;
+
+ input_set_drvdata(input_dev, ac);
+
+ __set_bit(ac->pdata.ev_type, input_dev->evbit);
+
+ if (ac->pdata.ev_type == EV_REL) {
+ __set_bit(REL_X, input_dev->relbit);
+ __set_bit(REL_Y, input_dev->relbit);
+ __set_bit(REL_Z, input_dev->relbit);
+ } else {
+ /* EV_ABS */
+ __set_bit(ABS_X, input_dev->absbit);
+ __set_bit(ABS_Y, input_dev->absbit);
+ __set_bit(ABS_Z, input_dev->absbit);
+
+ if (pdata->data_range & FULL_RES)
+ range = ADXL_FULLRES_MAX_VAL; /* Signed 13-bit */
+ else
+ range = ADXL_FIXEDRES_MAX_VAL; /* Signed 10-bit */
+
+ input_set_abs_params(input_dev, ABS_X, -range, range, 3, 3);
+ input_set_abs_params(input_dev, ABS_Y, -range, range, 3, 3);
+ input_set_abs_params(input_dev, ABS_Z, -range, range, 3, 3);
+ }
+
+ __set_bit(EV_KEY, input_dev->evbit);
+ __set_bit(pdata->ev_code_tap[ADXL_X_AXIS], input_dev->keybit);
+ __set_bit(pdata->ev_code_tap[ADXL_Y_AXIS], input_dev->keybit);
+ __set_bit(pdata->ev_code_tap[ADXL_Z_AXIS], input_dev->keybit);
+
+ if (pdata->ev_code_ff) {
+ ac->int_mask = FREE_FALL;
+ __set_bit(pdata->ev_code_ff, input_dev->keybit);
+ }
+
+ if (pdata->ev_code_act_inactivity)
+ __set_bit(pdata->ev_code_act_inactivity, input_dev->keybit);
+
+ ac->int_mask |= ACTIVITY | INACTIVITY;
+
+ if (pdata->watermark) {
+ ac->int_mask |= WATERMARK;
+ if (FIFO_MODE(pdata->fifo_mode) == FIFO_BYPASS)
+ ac->pdata.fifo_mode |= FIFO_STREAM;
+ } else {
+ ac->int_mask |= DATA_READY;
+ }
+
+ if (pdata->tap_axis_control & (TAP_X_EN | TAP_Y_EN | TAP_Z_EN))
+ ac->int_mask |= SINGLE_TAP | DOUBLE_TAP;
+
+ if (FIFO_MODE(pdata->fifo_mode) == FIFO_BYPASS)
+ ac->fifo_delay = false;
+
+ AC_WRITE(ac, POWER_CTL, 0);
+
+ err = request_threaded_irq(ac->irq, NULL, adxl34x_irq,
+ IRQF_ONESHOT, dev_name(dev), ac);
+ if (err) {
+ dev_err(dev, "irq %d busy?\n", ac->irq);
+ goto err_free_mem;
+ }
+
+ err = sysfs_create_group(&dev->kobj, &adxl34x_attr_group);
+ if (err)
+ goto err_free_irq;
+
+ err = input_register_device(input_dev);
+ if (err)
+ goto err_remove_attr;
+
+ AC_WRITE(ac, OFSX, pdata->x_axis_offset);
+ ac->hwcal.x = pdata->x_axis_offset;
+ AC_WRITE(ac, OFSY, pdata->y_axis_offset);
+ ac->hwcal.y = pdata->y_axis_offset;
+ AC_WRITE(ac, OFSZ, pdata->z_axis_offset);
+ ac->hwcal.z = pdata->z_axis_offset;
+ AC_WRITE(ac, THRESH_TAP, pdata->tap_threshold);
+ AC_WRITE(ac, DUR, pdata->tap_duration);
+ AC_WRITE(ac, LATENT, pdata->tap_latency);
+ AC_WRITE(ac, WINDOW, pdata->tap_window);
+ AC_WRITE(ac, THRESH_ACT, pdata->activity_threshold);
+ AC_WRITE(ac, THRESH_INACT, pdata->inactivity_threshold);
+ AC_WRITE(ac, TIME_INACT, pdata->inactivity_time);
+ AC_WRITE(ac, THRESH_FF, pdata->free_fall_threshold);
+ AC_WRITE(ac, TIME_FF, pdata->free_fall_time);
+ AC_WRITE(ac, TAP_AXES, pdata->tap_axis_control);
+ AC_WRITE(ac, ACT_INACT_CTL, pdata->act_axis_control);
+ AC_WRITE(ac, BW_RATE, RATE(ac->pdata.data_rate) |
+ (pdata->low_power_mode ? LOW_POWER : 0));
+ AC_WRITE(ac, DATA_FORMAT, pdata->data_range);
+ AC_WRITE(ac, FIFO_CTL, FIFO_MODE(pdata->fifo_mode) |
+ SAMPLES(pdata->watermark));
+
+ if (pdata->use_int2) {
+ /* Map all INTs to INT2 */
+ AC_WRITE(ac, INT_MAP, ac->int_mask | OVERRUN);
+ } else {
+ /* Map all INTs to INT1 */
+ AC_WRITE(ac, INT_MAP, 0);
+ }
+
+ if (ac->model == 346 && ac->pdata.orientation_enable) {
+ AC_WRITE(ac, ORIENT_CONF,
+ ORIENT_DEADZONE(ac->pdata.deadzone_angle) |
+ ORIENT_DIVISOR(ac->pdata.divisor_length));
+
+ ac->orient2d_saved = 1234;
+ ac->orient3d_saved = 1234;
+
+ if (pdata->orientation_enable & ADXL_EN_ORIENTATION_3D)
+ for (i = 0; i < ARRAY_SIZE(pdata->ev_codes_orient_3d); i++)
+ __set_bit(pdata->ev_codes_orient_3d[i],
+ input_dev->keybit);
+
+ if (pdata->orientation_enable & ADXL_EN_ORIENTATION_2D)
+ for (i = 0; i < ARRAY_SIZE(pdata->ev_codes_orient_2d); i++)
+ __set_bit(pdata->ev_codes_orient_2d[i],
+ input_dev->keybit);
+ } else {
+ ac->pdata.orientation_enable = 0;
+ }
+
+ AC_WRITE(ac, INT_ENABLE, ac->int_mask | OVERRUN);
+
+ ac->pdata.power_mode &= (PCTL_AUTO_SLEEP | PCTL_LINK);
+
+ return ac;
+
+ err_remove_attr:
+ sysfs_remove_group(&dev->kobj, &adxl34x_attr_group);
+ err_free_irq:
+ free_irq(ac->irq, ac);
+ err_free_mem:
+ input_free_device(input_dev);
+ kfree(ac);
+ err_out:
+ return ERR_PTR(err);
+}
+EXPORT_SYMBOL_GPL(adxl34x_probe);
+
+void adxl34x_remove(struct adxl34x *ac)
+{
+ sysfs_remove_group(&ac->dev->kobj, &adxl34x_attr_group);
+ free_irq(ac->irq, ac);
+ input_unregister_device(ac->input);
+ dev_dbg(ac->dev, "unregistered accelerometer\n");
+ kfree(ac);
+}
+EXPORT_SYMBOL_GPL(adxl34x_remove);
+
+MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>");
+MODULE_DESCRIPTION("ADXL345/346 Three-Axis Digital Accelerometer Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/adxl34x.h b/drivers/input/misc/adxl34x.h
new file mode 100644
index 000000000..febf85270
--- /dev/null
+++ b/drivers/input/misc/adxl34x.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * ADXL345/346 Three-Axis Digital Accelerometers (I2C/SPI Interface)
+ *
+ * Enter bugs at http://blackfin.uclinux.org/
+ *
+ * Copyright (C) 2009 Michael Hennerich, Analog Devices Inc.
+ */
+
+#ifndef _ADXL34X_H_
+#define _ADXL34X_H_
+
+struct device;
+struct adxl34x;
+
+struct adxl34x_bus_ops {
+ u16 bustype;
+ int (*read)(struct device *, unsigned char);
+ int (*read_block)(struct device *, unsigned char, int, void *);
+ int (*write)(struct device *, unsigned char, unsigned char);
+};
+
+void adxl34x_suspend(struct adxl34x *ac);
+void adxl34x_resume(struct adxl34x *ac);
+struct adxl34x *adxl34x_probe(struct device *dev, int irq,
+ bool fifo_delay_default,
+ const struct adxl34x_bus_ops *bops);
+void adxl34x_remove(struct adxl34x *ac);
+
+#endif
diff --git a/drivers/input/misc/apanel.c b/drivers/input/misc/apanel.c
new file mode 100644
index 000000000..7276657ad
--- /dev/null
+++ b/drivers/input/misc/apanel.c
@@ -0,0 +1,305 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Fujitsu Lifebook Application Panel button drive
+ *
+ * Copyright (C) 2007 Stephen Hemminger <shemminger@linux-foundation.org>
+ * Copyright (C) 2001-2003 Jochen Eisinger <jochen@penguin-breeder.org>
+ *
+ * Many Fujitsu Lifebook laptops have a small panel of buttons that are
+ * accessible via the i2c/smbus interface. This driver polls those
+ * buttons and generates input events.
+ *
+ * For more details see:
+ * http://apanel.sourceforge.net/tech.php
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/ioport.h>
+#include <linux/io.h>
+#include <linux/input.h>
+#include <linux/i2c.h>
+#include <linux/leds.h>
+
+#define APANEL_NAME "Fujitsu Application Panel"
+#define APANEL "apanel"
+
+/* How often we poll keys - msecs */
+#define POLL_INTERVAL_DEFAULT 1000
+
+/* Magic constants in BIOS that tell about buttons */
+enum apanel_devid {
+ APANEL_DEV_NONE = 0,
+ APANEL_DEV_APPBTN = 1,
+ APANEL_DEV_CDBTN = 2,
+ APANEL_DEV_LCD = 3,
+ APANEL_DEV_LED = 4,
+
+ APANEL_DEV_MAX,
+};
+
+enum apanel_chip {
+ CHIP_NONE = 0,
+ CHIP_OZ992C = 1,
+ CHIP_OZ163T = 2,
+ CHIP_OZ711M3 = 4,
+};
+
+/* Result of BIOS snooping/probing -- what features are supported */
+static enum apanel_chip device_chip[APANEL_DEV_MAX];
+
+#define MAX_PANEL_KEYS 12
+
+struct apanel {
+ struct input_dev *idev;
+ struct i2c_client *client;
+ unsigned short keymap[MAX_PANEL_KEYS];
+ u16 nkeys;
+ struct led_classdev mail_led;
+};
+
+static const unsigned short apanel_keymap[MAX_PANEL_KEYS] = {
+ [0] = KEY_MAIL,
+ [1] = KEY_WWW,
+ [2] = KEY_PROG2,
+ [3] = KEY_PROG1,
+
+ [8] = KEY_FORWARD,
+ [9] = KEY_REWIND,
+ [10] = KEY_STOPCD,
+ [11] = KEY_PLAYPAUSE,
+};
+
+static void report_key(struct input_dev *input, unsigned keycode)
+{
+ dev_dbg(input->dev.parent, "report key %#x\n", keycode);
+ input_report_key(input, keycode, 1);
+ input_sync(input);
+
+ input_report_key(input, keycode, 0);
+ input_sync(input);
+}
+
+/* Poll for key changes
+ *
+ * Read Application keys via SMI
+ * A (0x4), B (0x8), Internet (0x2), Email (0x1).
+ *
+ * CD keys:
+ * Forward (0x100), Rewind (0x200), Stop (0x400), Pause (0x800)
+ */
+static void apanel_poll(struct input_dev *idev)
+{
+ struct apanel *ap = input_get_drvdata(idev);
+ u8 cmd = device_chip[APANEL_DEV_APPBTN] == CHIP_OZ992C ? 0 : 8;
+ s32 data;
+ int i;
+
+ data = i2c_smbus_read_word_data(ap->client, cmd);
+ if (data < 0)
+ return; /* ignore errors (due to ACPI??) */
+
+ /* write back to clear latch */
+ i2c_smbus_write_word_data(ap->client, cmd, 0);
+
+ if (!data)
+ return;
+
+ dev_dbg(&idev->dev, APANEL ": data %#x\n", data);
+ for (i = 0; i < idev->keycodemax; i++)
+ if ((1u << i) & data)
+ report_key(idev, ap->keymap[i]);
+}
+
+static int mail_led_set(struct led_classdev *led,
+ enum led_brightness value)
+{
+ struct apanel *ap = container_of(led, struct apanel, mail_led);
+ u16 led_bits = value != LED_OFF ? 0x8000 : 0x0000;
+
+ return i2c_smbus_write_word_data(ap->client, 0x10, led_bits);
+}
+
+static int apanel_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct apanel *ap;
+ struct input_dev *idev;
+ u8 cmd = device_chip[APANEL_DEV_APPBTN] == CHIP_OZ992C ? 0 : 8;
+ int i, err;
+
+ ap = devm_kzalloc(&client->dev, sizeof(*ap), GFP_KERNEL);
+ if (!ap)
+ return -ENOMEM;
+
+ idev = devm_input_allocate_device(&client->dev);
+ if (!idev)
+ return -ENOMEM;
+
+ ap->idev = idev;
+ ap->client = client;
+
+ i2c_set_clientdata(client, ap);
+
+ err = i2c_smbus_write_word_data(client, cmd, 0);
+ if (err) {
+ dev_warn(&client->dev, "smbus write error %d\n", err);
+ return err;
+ }
+
+ input_set_drvdata(idev, ap);
+
+ idev->name = APANEL_NAME " buttons";
+ idev->phys = "apanel/input0";
+ idev->id.bustype = BUS_HOST;
+
+ memcpy(ap->keymap, apanel_keymap, sizeof(apanel_keymap));
+ idev->keycode = ap->keymap;
+ idev->keycodesize = sizeof(ap->keymap[0]);
+ idev->keycodemax = (device_chip[APANEL_DEV_CDBTN] != CHIP_NONE) ? 12 : 4;
+
+ set_bit(EV_KEY, idev->evbit);
+ for (i = 0; i < idev->keycodemax; i++)
+ if (ap->keymap[i])
+ set_bit(ap->keymap[i], idev->keybit);
+
+ err = input_setup_polling(idev, apanel_poll);
+ if (err)
+ return err;
+
+ input_set_poll_interval(idev, POLL_INTERVAL_DEFAULT);
+
+ err = input_register_device(idev);
+ if (err)
+ return err;
+
+ if (device_chip[APANEL_DEV_LED] != CHIP_NONE) {
+ ap->mail_led.name = "mail:blue";
+ ap->mail_led.brightness_set_blocking = mail_led_set;
+ err = devm_led_classdev_register(&client->dev, &ap->mail_led);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static void apanel_shutdown(struct i2c_client *client)
+{
+ struct apanel *ap = i2c_get_clientdata(client);
+
+ if (device_chip[APANEL_DEV_LED] != CHIP_NONE)
+ led_set_brightness(&ap->mail_led, LED_OFF);
+}
+
+static const struct i2c_device_id apanel_id[] = {
+ { "fujitsu_apanel", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, apanel_id);
+
+static struct i2c_driver apanel_driver = {
+ .driver = {
+ .name = APANEL,
+ },
+ .probe = apanel_probe,
+ .shutdown = apanel_shutdown,
+ .id_table = apanel_id,
+};
+
+/* Scan the system ROM for the signature "FJKEYINF" */
+static __init const void __iomem *bios_signature(const void __iomem *bios)
+{
+ ssize_t offset;
+ const unsigned char signature[] = "FJKEYINF";
+
+ for (offset = 0; offset < 0x10000; offset += 0x10) {
+ if (check_signature(bios + offset, signature,
+ sizeof(signature)-1))
+ return bios + offset;
+ }
+ pr_notice(APANEL ": Fujitsu BIOS signature '%s' not found...\n",
+ signature);
+ return NULL;
+}
+
+static int __init apanel_init(void)
+{
+ void __iomem *bios;
+ const void __iomem *p;
+ u8 devno;
+ unsigned char i2c_addr;
+ int found = 0;
+
+ bios = ioremap(0xF0000, 0x10000); /* Can't fail */
+
+ p = bios_signature(bios);
+ if (!p) {
+ iounmap(bios);
+ return -ENODEV;
+ }
+
+ /* just use the first address */
+ p += 8;
+ i2c_addr = readb(p + 3) >> 1;
+
+ for ( ; (devno = readb(p)) & 0x7f; p += 4) {
+ unsigned char method, slave, chip;
+
+ method = readb(p + 1);
+ chip = readb(p + 2);
+ slave = readb(p + 3) >> 1;
+
+ if (slave != i2c_addr) {
+ pr_notice(APANEL ": only one SMBus slave "
+ "address supported, skipping device...\n");
+ continue;
+ }
+
+ /* translate alternative device numbers */
+ switch (devno) {
+ case 6:
+ devno = APANEL_DEV_APPBTN;
+ break;
+ case 7:
+ devno = APANEL_DEV_LED;
+ break;
+ }
+
+ if (devno >= APANEL_DEV_MAX)
+ pr_notice(APANEL ": unknown device %u found\n", devno);
+ else if (device_chip[devno] != CHIP_NONE)
+ pr_warn(APANEL ": duplicate entry for devno %u\n",
+ devno);
+
+ else if (method != 1 && method != 2 && method != 4) {
+ pr_notice(APANEL ": unknown method %u for devno %u\n",
+ method, devno);
+ } else {
+ device_chip[devno] = (enum apanel_chip) chip;
+ ++found;
+ }
+ }
+ iounmap(bios);
+
+ if (found == 0) {
+ pr_info(APANEL ": no input devices reported by BIOS\n");
+ return -EIO;
+ }
+
+ return i2c_add_driver(&apanel_driver);
+}
+module_init(apanel_init);
+
+static void __exit apanel_cleanup(void)
+{
+ i2c_del_driver(&apanel_driver);
+}
+module_exit(apanel_cleanup);
+
+MODULE_AUTHOR("Stephen Hemminger <shemminger@linux-foundation.org>");
+MODULE_DESCRIPTION(APANEL_NAME " driver");
+MODULE_LICENSE("GPL");
+
+MODULE_ALIAS("dmi:*:svnFUJITSU:pnLifeBook*:pvr*:rvnFUJITSU:*");
+MODULE_ALIAS("dmi:*:svnFUJITSU:pnLifebook*:pvr*:rvnFUJITSU:*");
diff --git a/drivers/input/misc/ariel-pwrbutton.c b/drivers/input/misc/ariel-pwrbutton.c
new file mode 100644
index 000000000..cdc80715b
--- /dev/null
+++ b/drivers/input/misc/ariel-pwrbutton.c
@@ -0,0 +1,170 @@
+// SPDX-License-Identifier: BSD-2-Clause OR GPL-2.0-or-later
+/*
+ * Dell Wyse 3020 a.k.a. "Ariel" Power Button Driver
+ *
+ * Copyright (C) 2020 Lubomir Rintel
+ */
+
+#include <linux/device.h>
+#include <linux/gfp.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+
+#define RESP_COUNTER(response) (response.header & 0x3)
+#define RESP_SIZE(response) ((response.header >> 2) & 0x3)
+#define RESP_TYPE(response) ((response.header >> 4) & 0xf)
+
+struct ec_input_response {
+ u8 reserved;
+ u8 header;
+ u8 data[3];
+} __packed;
+
+struct ariel_pwrbutton {
+ struct spi_device *client;
+ struct input_dev *input;
+ u8 msg_counter;
+};
+
+static int ec_input_read(struct ariel_pwrbutton *priv,
+ struct ec_input_response *response)
+{
+ u8 read_request[] = { 0x00, 0x5a, 0xa5, 0x00, 0x00 };
+ struct spi_device *spi = priv->client;
+ struct spi_transfer t = {
+ .tx_buf = read_request,
+ .rx_buf = response,
+ .len = sizeof(read_request),
+ };
+
+ compiletime_assert(sizeof(read_request) == sizeof(*response),
+ "SPI xfer request/response size mismatch");
+
+ return spi_sync_transfer(spi, &t, 1);
+}
+
+static irqreturn_t ec_input_interrupt(int irq, void *dev_id)
+{
+ struct ariel_pwrbutton *priv = dev_id;
+ struct spi_device *spi = priv->client;
+ struct ec_input_response response;
+ int error;
+ int i;
+
+ error = ec_input_read(priv, &response);
+ if (error < 0) {
+ dev_err(&spi->dev, "EC read failed: %d\n", error);
+ goto out;
+ }
+
+ if (priv->msg_counter == RESP_COUNTER(response)) {
+ dev_warn(&spi->dev, "No new data to read?\n");
+ goto out;
+ }
+
+ priv->msg_counter = RESP_COUNTER(response);
+
+ if (RESP_TYPE(response) != 0x3 && RESP_TYPE(response) != 0xc) {
+ dev_dbg(&spi->dev, "Ignoring message that's not kbd data\n");
+ goto out;
+ }
+
+ for (i = 0; i < RESP_SIZE(response); i++) {
+ switch (response.data[i]) {
+ case 0x74:
+ input_report_key(priv->input, KEY_POWER, 1);
+ input_sync(priv->input);
+ break;
+ case 0xf4:
+ input_report_key(priv->input, KEY_POWER, 0);
+ input_sync(priv->input);
+ break;
+ default:
+ dev_dbg(&spi->dev, "Unknown scan code: %02x\n",
+ response.data[i]);
+ }
+ }
+
+out:
+ return IRQ_HANDLED;
+}
+
+static int ariel_pwrbutton_probe(struct spi_device *spi)
+{
+ struct ec_input_response response;
+ struct ariel_pwrbutton *priv;
+ int error;
+
+ if (!spi->irq) {
+ dev_err(&spi->dev, "Missing IRQ.\n");
+ return -EINVAL;
+ }
+
+ priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->client = spi;
+ spi_set_drvdata(spi, priv);
+
+ priv->input = devm_input_allocate_device(&spi->dev);
+ if (!priv->input)
+ return -ENOMEM;
+ priv->input->name = "Power Button";
+ priv->input->dev.parent = &spi->dev;
+ input_set_capability(priv->input, EV_KEY, KEY_POWER);
+ error = input_register_device(priv->input);
+ if (error) {
+ dev_err(&spi->dev, "error registering input device: %d\n", error);
+ return error;
+ }
+
+ error = ec_input_read(priv, &response);
+ if (error < 0) {
+ dev_err(&spi->dev, "EC read failed: %d\n", error);
+ return error;
+ }
+ priv->msg_counter = RESP_COUNTER(response);
+
+ error = devm_request_threaded_irq(&spi->dev, spi->irq, NULL,
+ ec_input_interrupt,
+ IRQF_ONESHOT,
+ "Ariel EC Input", priv);
+
+ if (error) {
+ dev_err(&spi->dev, "Failed to request IRQ %d: %d\n",
+ spi->irq, error);
+ return error;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id ariel_pwrbutton_of_match[] = {
+ { .compatible = "dell,wyse-ariel-ec-input" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ariel_pwrbutton_of_match);
+
+static const struct spi_device_id ariel_pwrbutton_spi_ids[] = {
+ { .name = "wyse-ariel-ec-input" },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, ariel_pwrbutton_spi_ids);
+
+static struct spi_driver ariel_pwrbutton_driver = {
+ .driver = {
+ .name = "dell-wyse-ariel-ec-input",
+ .of_match_table = ariel_pwrbutton_of_match,
+ },
+ .probe = ariel_pwrbutton_probe,
+ .id_table = ariel_pwrbutton_spi_ids,
+};
+module_spi_driver(ariel_pwrbutton_driver);
+
+MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>");
+MODULE_DESCRIPTION("Dell Wyse 3020 Power Button Input Driver");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/input/misc/arizona-haptics.c b/drivers/input/misc/arizona-haptics.c
new file mode 100644
index 000000000..5fa1c9438
--- /dev/null
+++ b/drivers/input/misc/arizona-haptics.c
@@ -0,0 +1,215 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Arizona haptics driver
+ *
+ * Copyright 2012 Wolfson Microelectronics plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <linux/mfd/arizona/core.h>
+#include <linux/mfd/arizona/pdata.h>
+#include <linux/mfd/arizona/registers.h>
+
+struct arizona_haptics {
+ struct arizona *arizona;
+ struct input_dev *input_dev;
+ struct work_struct work;
+
+ struct mutex mutex;
+ u8 intensity;
+};
+
+static void arizona_haptics_work(struct work_struct *work)
+{
+ struct arizona_haptics *haptics = container_of(work,
+ struct arizona_haptics,
+ work);
+ struct arizona *arizona = haptics->arizona;
+ struct snd_soc_component *component =
+ snd_soc_dapm_to_component(arizona->dapm);
+ int ret;
+
+ if (!haptics->arizona->dapm) {
+ dev_err(arizona->dev, "No DAPM context\n");
+ return;
+ }
+
+ if (haptics->intensity) {
+ ret = regmap_update_bits(arizona->regmap,
+ ARIZONA_HAPTICS_PHASE_2_INTENSITY,
+ ARIZONA_PHASE2_INTENSITY_MASK,
+ haptics->intensity);
+ if (ret != 0) {
+ dev_err(arizona->dev, "Failed to set intensity: %d\n",
+ ret);
+ return;
+ }
+
+ /* This enable sequence will be a noop if already enabled */
+ ret = regmap_update_bits(arizona->regmap,
+ ARIZONA_HAPTICS_CONTROL_1,
+ ARIZONA_HAP_CTRL_MASK,
+ 1 << ARIZONA_HAP_CTRL_SHIFT);
+ if (ret != 0) {
+ dev_err(arizona->dev, "Failed to start haptics: %d\n",
+ ret);
+ return;
+ }
+
+ ret = snd_soc_component_enable_pin(component, "HAPTICS");
+ if (ret != 0) {
+ dev_err(arizona->dev, "Failed to start HAPTICS: %d\n",
+ ret);
+ return;
+ }
+
+ ret = snd_soc_dapm_sync(arizona->dapm);
+ if (ret != 0) {
+ dev_err(arizona->dev, "Failed to sync DAPM: %d\n",
+ ret);
+ return;
+ }
+ } else {
+ /* This disable sequence will be a noop if already enabled */
+ ret = snd_soc_component_disable_pin(component, "HAPTICS");
+ if (ret != 0) {
+ dev_err(arizona->dev, "Failed to disable HAPTICS: %d\n",
+ ret);
+ return;
+ }
+
+ ret = snd_soc_dapm_sync(arizona->dapm);
+ if (ret != 0) {
+ dev_err(arizona->dev, "Failed to sync DAPM: %d\n",
+ ret);
+ return;
+ }
+
+ ret = regmap_update_bits(arizona->regmap,
+ ARIZONA_HAPTICS_CONTROL_1,
+ ARIZONA_HAP_CTRL_MASK, 0);
+ if (ret != 0) {
+ dev_err(arizona->dev, "Failed to stop haptics: %d\n",
+ ret);
+ return;
+ }
+ }
+}
+
+static int arizona_haptics_play(struct input_dev *input, void *data,
+ struct ff_effect *effect)
+{
+ struct arizona_haptics *haptics = input_get_drvdata(input);
+ struct arizona *arizona = haptics->arizona;
+
+ if (!arizona->dapm) {
+ dev_err(arizona->dev, "No DAPM context\n");
+ return -EBUSY;
+ }
+
+ if (effect->u.rumble.strong_magnitude) {
+ /* Scale the magnitude into the range the device supports */
+ if (arizona->pdata.hap_act) {
+ haptics->intensity =
+ effect->u.rumble.strong_magnitude >> 9;
+ if (effect->direction < 0x8000)
+ haptics->intensity += 0x7f;
+ } else {
+ haptics->intensity =
+ effect->u.rumble.strong_magnitude >> 8;
+ }
+ } else {
+ haptics->intensity = 0;
+ }
+
+ schedule_work(&haptics->work);
+
+ return 0;
+}
+
+static void arizona_haptics_close(struct input_dev *input)
+{
+ struct arizona_haptics *haptics = input_get_drvdata(input);
+ struct snd_soc_component *component;
+
+ cancel_work_sync(&haptics->work);
+
+ if (haptics->arizona->dapm) {
+ component = snd_soc_dapm_to_component(haptics->arizona->dapm);
+ snd_soc_component_disable_pin(component, "HAPTICS");
+ }
+}
+
+static int arizona_haptics_probe(struct platform_device *pdev)
+{
+ struct arizona *arizona = dev_get_drvdata(pdev->dev.parent);
+ struct arizona_haptics *haptics;
+ int ret;
+
+ haptics = devm_kzalloc(&pdev->dev, sizeof(*haptics), GFP_KERNEL);
+ if (!haptics)
+ return -ENOMEM;
+
+ haptics->arizona = arizona;
+
+ ret = regmap_update_bits(arizona->regmap, ARIZONA_HAPTICS_CONTROL_1,
+ ARIZONA_HAP_ACT, arizona->pdata.hap_act);
+ if (ret != 0) {
+ dev_err(arizona->dev, "Failed to set haptics actuator: %d\n",
+ ret);
+ return ret;
+ }
+
+ INIT_WORK(&haptics->work, arizona_haptics_work);
+
+ haptics->input_dev = devm_input_allocate_device(&pdev->dev);
+ if (!haptics->input_dev) {
+ dev_err(arizona->dev, "Failed to allocate input device\n");
+ return -ENOMEM;
+ }
+
+ input_set_drvdata(haptics->input_dev, haptics);
+
+ haptics->input_dev->name = "arizona:haptics";
+ haptics->input_dev->close = arizona_haptics_close;
+ __set_bit(FF_RUMBLE, haptics->input_dev->ffbit);
+
+ ret = input_ff_create_memless(haptics->input_dev, NULL,
+ arizona_haptics_play);
+ if (ret < 0) {
+ dev_err(arizona->dev, "input_ff_create_memless() failed: %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = input_register_device(haptics->input_dev);
+ if (ret < 0) {
+ dev_err(arizona->dev, "couldn't register input device: %d\n",
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct platform_driver arizona_haptics_driver = {
+ .probe = arizona_haptics_probe,
+ .driver = {
+ .name = "arizona-haptics",
+ },
+};
+module_platform_driver(arizona_haptics_driver);
+
+MODULE_ALIAS("platform:arizona-haptics");
+MODULE_DESCRIPTION("Arizona haptics driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
diff --git a/drivers/input/misc/atc260x-onkey.c b/drivers/input/misc/atc260x-onkey.c
new file mode 100644
index 000000000..999aabf9d
--- /dev/null
+++ b/drivers/input/misc/atc260x-onkey.c
@@ -0,0 +1,305 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Onkey driver for Actions Semi ATC260x PMICs.
+ *
+ * Copyright (c) 2020 Cristian Ciocaltea <cristian.ciocaltea@gmail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/atc260x/core.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+/* <2s for short press, >2s for long press */
+#define KEY_PRESS_TIME_SEC 2
+
+/* Driver internals */
+enum atc260x_onkey_reset_status {
+ KEY_RESET_HW_DEFAULT,
+ KEY_RESET_DISABLED,
+ KEY_RESET_USER_SEL,
+};
+
+struct atc260x_onkey_params {
+ u32 reg_int_ctl;
+ u32 kdwn_state_bm;
+ u32 long_int_pnd_bm;
+ u32 short_int_pnd_bm;
+ u32 kdwn_int_pnd_bm;
+ u32 press_int_en_bm;
+ u32 kdwn_int_en_bm;
+ u32 press_time_bm;
+ u32 reset_en_bm;
+ u32 reset_time_bm;
+};
+
+struct atc260x_onkey {
+ struct atc260x *atc260x;
+ const struct atc260x_onkey_params *params;
+ struct input_dev *input_dev;
+ struct delayed_work work;
+ int irq;
+};
+
+static const struct atc260x_onkey_params atc2603c_onkey_params = {
+ .reg_int_ctl = ATC2603C_PMU_SYS_CTL2,
+ .long_int_pnd_bm = ATC2603C_PMU_SYS_CTL2_ONOFF_LONG_PRESS,
+ .short_int_pnd_bm = ATC2603C_PMU_SYS_CTL2_ONOFF_SHORT_PRESS,
+ .kdwn_int_pnd_bm = ATC2603C_PMU_SYS_CTL2_ONOFF_PRESS_PD,
+ .press_int_en_bm = ATC2603C_PMU_SYS_CTL2_ONOFF_INT_EN,
+ .kdwn_int_en_bm = ATC2603C_PMU_SYS_CTL2_ONOFF_PRESS_INT_EN,
+ .kdwn_state_bm = ATC2603C_PMU_SYS_CTL2_ONOFF_PRESS,
+ .press_time_bm = ATC2603C_PMU_SYS_CTL2_ONOFF_PRESS_TIME,
+ .reset_en_bm = ATC2603C_PMU_SYS_CTL2_ONOFF_PRESS_RESET_EN,
+ .reset_time_bm = ATC2603C_PMU_SYS_CTL2_ONOFF_RESET_TIME_SEL,
+};
+
+static const struct atc260x_onkey_params atc2609a_onkey_params = {
+ .reg_int_ctl = ATC2609A_PMU_SYS_CTL2,
+ .long_int_pnd_bm = ATC2609A_PMU_SYS_CTL2_ONOFF_LONG_PRESS,
+ .short_int_pnd_bm = ATC2609A_PMU_SYS_CTL2_ONOFF_SHORT_PRESS,
+ .kdwn_int_pnd_bm = ATC2609A_PMU_SYS_CTL2_ONOFF_PRESS_PD,
+ .press_int_en_bm = ATC2609A_PMU_SYS_CTL2_ONOFF_LSP_INT_EN,
+ .kdwn_int_en_bm = ATC2609A_PMU_SYS_CTL2_ONOFF_PRESS_INT_EN,
+ .kdwn_state_bm = ATC2609A_PMU_SYS_CTL2_ONOFF_PRESS,
+ .press_time_bm = ATC2609A_PMU_SYS_CTL2_ONOFF_PRESS_TIME,
+ .reset_en_bm = ATC2609A_PMU_SYS_CTL2_ONOFF_RESET_EN,
+ .reset_time_bm = ATC2609A_PMU_SYS_CTL2_ONOFF_RESET_TIME_SEL,
+};
+
+static int atc2603x_onkey_hw_init(struct atc260x_onkey *onkey,
+ enum atc260x_onkey_reset_status reset_status,
+ u32 reset_time, u32 press_time)
+{
+ u32 reg_bm, reg_val;
+
+ reg_bm = onkey->params->long_int_pnd_bm |
+ onkey->params->short_int_pnd_bm |
+ onkey->params->kdwn_int_pnd_bm |
+ onkey->params->press_int_en_bm |
+ onkey->params->kdwn_int_en_bm;
+
+ reg_val = reg_bm | press_time;
+ reg_bm |= onkey->params->press_time_bm;
+
+ if (reset_status == KEY_RESET_DISABLED) {
+ reg_bm |= onkey->params->reset_en_bm;
+ } else if (reset_status == KEY_RESET_USER_SEL) {
+ reg_bm |= onkey->params->reset_en_bm |
+ onkey->params->reset_time_bm;
+ reg_val |= onkey->params->reset_en_bm | reset_time;
+ }
+
+ return regmap_update_bits(onkey->atc260x->regmap,
+ onkey->params->reg_int_ctl, reg_bm, reg_val);
+}
+
+static void atc260x_onkey_query(struct atc260x_onkey *onkey)
+{
+ u32 reg_bits;
+ int ret, key_down;
+
+ ret = regmap_read(onkey->atc260x->regmap,
+ onkey->params->reg_int_ctl, &key_down);
+ if (ret) {
+ key_down = 1;
+ dev_err(onkey->atc260x->dev,
+ "Failed to read onkey status: %d\n", ret);
+ } else {
+ key_down &= onkey->params->kdwn_state_bm;
+ }
+
+ /*
+ * The hardware generates interrupt only when the onkey pin is
+ * asserted. Hence, the deassertion of the pin is simulated through
+ * work queue.
+ */
+ if (key_down) {
+ schedule_delayed_work(&onkey->work, msecs_to_jiffies(200));
+ return;
+ }
+
+ /*
+ * The key-down status bit is cleared when the On/Off button
+ * is released.
+ */
+ input_report_key(onkey->input_dev, KEY_POWER, 0);
+ input_sync(onkey->input_dev);
+
+ reg_bits = onkey->params->long_int_pnd_bm |
+ onkey->params->short_int_pnd_bm |
+ onkey->params->kdwn_int_pnd_bm |
+ onkey->params->press_int_en_bm |
+ onkey->params->kdwn_int_en_bm;
+
+ /* Clear key press pending events and enable key press interrupts. */
+ regmap_update_bits(onkey->atc260x->regmap, onkey->params->reg_int_ctl,
+ reg_bits, reg_bits);
+}
+
+static void atc260x_onkey_work(struct work_struct *work)
+{
+ struct atc260x_onkey *onkey = container_of(work, struct atc260x_onkey,
+ work.work);
+ atc260x_onkey_query(onkey);
+}
+
+static irqreturn_t atc260x_onkey_irq(int irq, void *data)
+{
+ struct atc260x_onkey *onkey = data;
+ int ret;
+
+ /* Disable key press interrupts. */
+ ret = regmap_update_bits(onkey->atc260x->regmap,
+ onkey->params->reg_int_ctl,
+ onkey->params->press_int_en_bm |
+ onkey->params->kdwn_int_en_bm, 0);
+ if (ret)
+ dev_err(onkey->atc260x->dev,
+ "Failed to disable interrupts: %d\n", ret);
+
+ input_report_key(onkey->input_dev, KEY_POWER, 1);
+ input_sync(onkey->input_dev);
+
+ atc260x_onkey_query(onkey);
+
+ return IRQ_HANDLED;
+}
+
+static int atc260x_onkey_open(struct input_dev *dev)
+{
+ struct atc260x_onkey *onkey = input_get_drvdata(dev);
+
+ enable_irq(onkey->irq);
+
+ return 0;
+}
+
+static void atc260x_onkey_close(struct input_dev *dev)
+{
+ struct atc260x_onkey *onkey = input_get_drvdata(dev);
+
+ disable_irq(onkey->irq);
+ cancel_delayed_work_sync(&onkey->work);
+}
+
+static int atc260x_onkey_probe(struct platform_device *pdev)
+{
+ struct atc260x *atc260x = dev_get_drvdata(pdev->dev.parent);
+ struct atc260x_onkey *onkey;
+ struct input_dev *input_dev;
+ enum atc260x_onkey_reset_status reset_status;
+ u32 press_time = KEY_PRESS_TIME_SEC, reset_time = 0;
+ int val, error;
+
+ onkey = devm_kzalloc(&pdev->dev, sizeof(*onkey), GFP_KERNEL);
+ if (!onkey)
+ return -ENOMEM;
+
+ error = device_property_read_u32(pdev->dev.parent,
+ "reset-time-sec", &val);
+ if (error) {
+ reset_status = KEY_RESET_HW_DEFAULT;
+ } else if (val) {
+ if (val < 6 || val > 12) {
+ dev_err(&pdev->dev, "reset-time-sec out of range\n");
+ return -EINVAL;
+ }
+
+ reset_status = KEY_RESET_USER_SEL;
+ reset_time = (val - 6) / 2;
+ } else {
+ reset_status = KEY_RESET_DISABLED;
+ dev_dbg(&pdev->dev, "Disabled reset on long-press\n");
+ }
+
+ switch (atc260x->ic_type) {
+ case ATC2603C:
+ onkey->params = &atc2603c_onkey_params;
+ press_time = FIELD_PREP(ATC2603C_PMU_SYS_CTL2_ONOFF_PRESS_TIME,
+ press_time);
+ reset_time = FIELD_PREP(ATC2603C_PMU_SYS_CTL2_ONOFF_RESET_TIME_SEL,
+ reset_time);
+ break;
+ case ATC2609A:
+ onkey->params = &atc2609a_onkey_params;
+ press_time = FIELD_PREP(ATC2609A_PMU_SYS_CTL2_ONOFF_PRESS_TIME,
+ press_time);
+ reset_time = FIELD_PREP(ATC2609A_PMU_SYS_CTL2_ONOFF_RESET_TIME_SEL,
+ reset_time);
+ break;
+ default:
+ dev_err(&pdev->dev,
+ "OnKey not supported for ATC260x PMIC type: %u\n",
+ atc260x->ic_type);
+ return -EINVAL;
+ }
+
+ input_dev = devm_input_allocate_device(&pdev->dev);
+ if (!input_dev) {
+ dev_err(&pdev->dev, "Failed to allocate input device\n");
+ return -ENOMEM;
+ }
+
+ onkey->input_dev = input_dev;
+ onkey->atc260x = atc260x;
+
+ input_dev->name = "atc260x-onkey";
+ input_dev->phys = "atc260x-onkey/input0";
+ input_dev->open = atc260x_onkey_open;
+ input_dev->close = atc260x_onkey_close;
+
+ input_set_capability(input_dev, EV_KEY, KEY_POWER);
+ input_set_drvdata(input_dev, onkey);
+
+ INIT_DELAYED_WORK(&onkey->work, atc260x_onkey_work);
+
+ onkey->irq = platform_get_irq(pdev, 0);
+ if (onkey->irq < 0)
+ return onkey->irq;
+
+ error = devm_request_threaded_irq(&pdev->dev, onkey->irq, NULL,
+ atc260x_onkey_irq, IRQF_ONESHOT,
+ dev_name(&pdev->dev), onkey);
+ if (error) {
+ dev_err(&pdev->dev,
+ "Failed to register IRQ %d: %d\n", onkey->irq, error);
+ return error;
+ }
+
+ /* Keep IRQ disabled until atc260x_onkey_open() is called. */
+ disable_irq(onkey->irq);
+
+ error = input_register_device(input_dev);
+ if (error) {
+ dev_err(&pdev->dev,
+ "Failed to register input device: %d\n", error);
+ return error;
+ }
+
+ error = atc2603x_onkey_hw_init(onkey, reset_status,
+ reset_time, press_time);
+ if (error)
+ return error;
+
+ device_init_wakeup(&pdev->dev, true);
+
+ return 0;
+}
+
+static struct platform_driver atc260x_onkey_driver = {
+ .probe = atc260x_onkey_probe,
+ .driver = {
+ .name = "atc260x-onkey",
+ },
+};
+
+module_platform_driver(atc260x_onkey_driver);
+
+MODULE_DESCRIPTION("Onkey driver for ATC260x PMICs");
+MODULE_AUTHOR("Cristian Ciocaltea <cristian.ciocaltea@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/ati_remote2.c b/drivers/input/misc/ati_remote2.c
new file mode 100644
index 000000000..946bf75aa
--- /dev/null
+++ b/drivers/input/misc/ati_remote2.c
@@ -0,0 +1,1035 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ati_remote2 - ATI/Philips USB RF remote driver
+ *
+ * Copyright (C) 2005-2008 Ville Syrjala <syrjala@sci.fi>
+ * Copyright (C) 2007-2008 Peter Stokes <linux@dadeos.co.uk>
+ */
+
+#include <linux/usb/input.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+
+#define DRIVER_DESC "ATI/Philips USB RF remote driver"
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_AUTHOR("Ville Syrjala <syrjala@sci.fi>");
+MODULE_LICENSE("GPL");
+
+/*
+ * ATI Remote Wonder II Channel Configuration
+ *
+ * The remote control can be assigned one of sixteen "channels" in order to facilitate
+ * the use of multiple remote controls within range of each other.
+ * A remote's "channel" may be altered by pressing and holding the "PC" button for
+ * approximately 3 seconds, after which the button will slowly flash the count of the
+ * currently configured "channel", using the numeric keypad enter a number between 1 and
+ * 16 and then press the "PC" button again, the button will slowly flash the count of the
+ * newly configured "channel".
+ */
+
+enum {
+ ATI_REMOTE2_MAX_CHANNEL_MASK = 0xFFFF,
+ ATI_REMOTE2_MAX_MODE_MASK = 0x1F,
+};
+
+static int ati_remote2_set_mask(const char *val,
+ const struct kernel_param *kp,
+ unsigned int max)
+{
+ unsigned int mask;
+ int ret;
+
+ if (!val)
+ return -EINVAL;
+
+ ret = kstrtouint(val, 0, &mask);
+ if (ret)
+ return ret;
+
+ if (mask & ~max)
+ return -EINVAL;
+
+ *(unsigned int *)kp->arg = mask;
+
+ return 0;
+}
+
+static int ati_remote2_set_channel_mask(const char *val,
+ const struct kernel_param *kp)
+{
+ pr_debug("%s()\n", __func__);
+
+ return ati_remote2_set_mask(val, kp, ATI_REMOTE2_MAX_CHANNEL_MASK);
+}
+
+static int ati_remote2_get_channel_mask(char *buffer,
+ const struct kernel_param *kp)
+{
+ pr_debug("%s()\n", __func__);
+
+ return sprintf(buffer, "0x%04x\n", *(unsigned int *)kp->arg);
+}
+
+static int ati_remote2_set_mode_mask(const char *val,
+ const struct kernel_param *kp)
+{
+ pr_debug("%s()\n", __func__);
+
+ return ati_remote2_set_mask(val, kp, ATI_REMOTE2_MAX_MODE_MASK);
+}
+
+static int ati_remote2_get_mode_mask(char *buffer,
+ const struct kernel_param *kp)
+{
+ pr_debug("%s()\n", __func__);
+
+ return sprintf(buffer, "0x%02x\n", *(unsigned int *)kp->arg);
+}
+
+static unsigned int channel_mask = ATI_REMOTE2_MAX_CHANNEL_MASK;
+#define param_check_channel_mask(name, p) __param_check(name, p, unsigned int)
+static const struct kernel_param_ops param_ops_channel_mask = {
+ .set = ati_remote2_set_channel_mask,
+ .get = ati_remote2_get_channel_mask,
+};
+module_param(channel_mask, channel_mask, 0644);
+MODULE_PARM_DESC(channel_mask, "Bitmask of channels to accept <15:Channel16>...<1:Channel2><0:Channel1>");
+
+static unsigned int mode_mask = ATI_REMOTE2_MAX_MODE_MASK;
+#define param_check_mode_mask(name, p) __param_check(name, p, unsigned int)
+static const struct kernel_param_ops param_ops_mode_mask = {
+ .set = ati_remote2_set_mode_mask,
+ .get = ati_remote2_get_mode_mask,
+};
+module_param(mode_mask, mode_mask, 0644);
+MODULE_PARM_DESC(mode_mask, "Bitmask of modes to accept <4:PC><3:AUX4><2:AUX3><1:AUX2><0:AUX1>");
+
+static const struct usb_device_id ati_remote2_id_table[] = {
+ { USB_DEVICE(0x0471, 0x0602) }, /* ATI Remote Wonder II */
+ { }
+};
+MODULE_DEVICE_TABLE(usb, ati_remote2_id_table);
+
+static DEFINE_MUTEX(ati_remote2_mutex);
+
+enum {
+ ATI_REMOTE2_OPENED = 0x1,
+ ATI_REMOTE2_SUSPENDED = 0x2,
+};
+
+enum {
+ ATI_REMOTE2_AUX1,
+ ATI_REMOTE2_AUX2,
+ ATI_REMOTE2_AUX3,
+ ATI_REMOTE2_AUX4,
+ ATI_REMOTE2_PC,
+ ATI_REMOTE2_MODES,
+};
+
+static const struct {
+ u8 hw_code;
+ u16 keycode;
+} ati_remote2_key_table[] = {
+ { 0x00, KEY_0 },
+ { 0x01, KEY_1 },
+ { 0x02, KEY_2 },
+ { 0x03, KEY_3 },
+ { 0x04, KEY_4 },
+ { 0x05, KEY_5 },
+ { 0x06, KEY_6 },
+ { 0x07, KEY_7 },
+ { 0x08, KEY_8 },
+ { 0x09, KEY_9 },
+ { 0x0c, KEY_POWER },
+ { 0x0d, KEY_MUTE },
+ { 0x10, KEY_VOLUMEUP },
+ { 0x11, KEY_VOLUMEDOWN },
+ { 0x20, KEY_CHANNELUP },
+ { 0x21, KEY_CHANNELDOWN },
+ { 0x28, KEY_FORWARD },
+ { 0x29, KEY_REWIND },
+ { 0x2c, KEY_PLAY },
+ { 0x30, KEY_PAUSE },
+ { 0x31, KEY_STOP },
+ { 0x37, KEY_RECORD },
+ { 0x38, KEY_DVD },
+ { 0x39, KEY_TV },
+ { 0x3f, KEY_PROG1 }, /* AUX1-AUX4 and PC */
+ { 0x54, KEY_MENU },
+ { 0x58, KEY_UP },
+ { 0x59, KEY_DOWN },
+ { 0x5a, KEY_LEFT },
+ { 0x5b, KEY_RIGHT },
+ { 0x5c, KEY_OK },
+ { 0x78, KEY_A },
+ { 0x79, KEY_B },
+ { 0x7a, KEY_C },
+ { 0x7b, KEY_D },
+ { 0x7c, KEY_E },
+ { 0x7d, KEY_F },
+ { 0x82, KEY_ENTER },
+ { 0x8e, KEY_VENDOR },
+ { 0x96, KEY_COFFEE },
+ { 0xa9, BTN_LEFT },
+ { 0xaa, BTN_RIGHT },
+ { 0xbe, KEY_QUESTION },
+ { 0xd0, KEY_EDIT },
+ { 0xd5, KEY_FRONT },
+ { 0xf9, KEY_INFO },
+};
+
+struct ati_remote2 {
+ struct input_dev *idev;
+ struct usb_device *udev;
+
+ struct usb_interface *intf[2];
+ struct usb_endpoint_descriptor *ep[2];
+ struct urb *urb[2];
+ void *buf[2];
+ dma_addr_t buf_dma[2];
+
+ unsigned long jiffies;
+ int mode;
+
+ char name[64];
+ char phys[64];
+
+ /* Each mode (AUX1-AUX4 and PC) can have an independent keymap. */
+ u16 keycode[ATI_REMOTE2_MODES][ARRAY_SIZE(ati_remote2_key_table)];
+
+ unsigned int flags;
+
+ unsigned int channel_mask;
+ unsigned int mode_mask;
+};
+
+static int ati_remote2_probe(struct usb_interface *interface, const struct usb_device_id *id);
+static void ati_remote2_disconnect(struct usb_interface *interface);
+static int ati_remote2_suspend(struct usb_interface *interface, pm_message_t message);
+static int ati_remote2_resume(struct usb_interface *interface);
+static int ati_remote2_reset_resume(struct usb_interface *interface);
+static int ati_remote2_pre_reset(struct usb_interface *interface);
+static int ati_remote2_post_reset(struct usb_interface *interface);
+
+static struct usb_driver ati_remote2_driver = {
+ .name = "ati_remote2",
+ .probe = ati_remote2_probe,
+ .disconnect = ati_remote2_disconnect,
+ .id_table = ati_remote2_id_table,
+ .suspend = ati_remote2_suspend,
+ .resume = ati_remote2_resume,
+ .reset_resume = ati_remote2_reset_resume,
+ .pre_reset = ati_remote2_pre_reset,
+ .post_reset = ati_remote2_post_reset,
+ .supports_autosuspend = 1,
+};
+
+static int ati_remote2_submit_urbs(struct ati_remote2 *ar2)
+{
+ int r;
+
+ r = usb_submit_urb(ar2->urb[0], GFP_KERNEL);
+ if (r) {
+ dev_err(&ar2->intf[0]->dev,
+ "%s(): usb_submit_urb() = %d\n", __func__, r);
+ return r;
+ }
+ r = usb_submit_urb(ar2->urb[1], GFP_KERNEL);
+ if (r) {
+ usb_kill_urb(ar2->urb[0]);
+ dev_err(&ar2->intf[1]->dev,
+ "%s(): usb_submit_urb() = %d\n", __func__, r);
+ return r;
+ }
+
+ return 0;
+}
+
+static void ati_remote2_kill_urbs(struct ati_remote2 *ar2)
+{
+ usb_kill_urb(ar2->urb[1]);
+ usb_kill_urb(ar2->urb[0]);
+}
+
+static int ati_remote2_open(struct input_dev *idev)
+{
+ struct ati_remote2 *ar2 = input_get_drvdata(idev);
+ int r;
+
+ dev_dbg(&ar2->intf[0]->dev, "%s()\n", __func__);
+
+ r = usb_autopm_get_interface(ar2->intf[0]);
+ if (r) {
+ dev_err(&ar2->intf[0]->dev,
+ "%s(): usb_autopm_get_interface() = %d\n", __func__, r);
+ goto fail1;
+ }
+
+ mutex_lock(&ati_remote2_mutex);
+
+ if (!(ar2->flags & ATI_REMOTE2_SUSPENDED)) {
+ r = ati_remote2_submit_urbs(ar2);
+ if (r)
+ goto fail2;
+ }
+
+ ar2->flags |= ATI_REMOTE2_OPENED;
+
+ mutex_unlock(&ati_remote2_mutex);
+
+ usb_autopm_put_interface(ar2->intf[0]);
+
+ return 0;
+
+ fail2:
+ mutex_unlock(&ati_remote2_mutex);
+ usb_autopm_put_interface(ar2->intf[0]);
+ fail1:
+ return r;
+}
+
+static void ati_remote2_close(struct input_dev *idev)
+{
+ struct ati_remote2 *ar2 = input_get_drvdata(idev);
+
+ dev_dbg(&ar2->intf[0]->dev, "%s()\n", __func__);
+
+ mutex_lock(&ati_remote2_mutex);
+
+ if (!(ar2->flags & ATI_REMOTE2_SUSPENDED))
+ ati_remote2_kill_urbs(ar2);
+
+ ar2->flags &= ~ATI_REMOTE2_OPENED;
+
+ mutex_unlock(&ati_remote2_mutex);
+}
+
+static void ati_remote2_input_mouse(struct ati_remote2 *ar2)
+{
+ struct input_dev *idev = ar2->idev;
+ u8 *data = ar2->buf[0];
+ int channel, mode;
+
+ channel = data[0] >> 4;
+
+ if (!((1 << channel) & ar2->channel_mask))
+ return;
+
+ mode = data[0] & 0x0F;
+
+ if (mode > ATI_REMOTE2_PC) {
+ dev_err(&ar2->intf[0]->dev,
+ "Unknown mode byte (%02x %02x %02x %02x)\n",
+ data[3], data[2], data[1], data[0]);
+ return;
+ }
+
+ if (!((1 << mode) & ar2->mode_mask))
+ return;
+
+ input_event(idev, EV_REL, REL_X, (s8) data[1]);
+ input_event(idev, EV_REL, REL_Y, (s8) data[2]);
+ input_sync(idev);
+}
+
+static int ati_remote2_lookup(unsigned int hw_code)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ati_remote2_key_table); i++)
+ if (ati_remote2_key_table[i].hw_code == hw_code)
+ return i;
+
+ return -1;
+}
+
+static void ati_remote2_input_key(struct ati_remote2 *ar2)
+{
+ struct input_dev *idev = ar2->idev;
+ u8 *data = ar2->buf[1];
+ int channel, mode, hw_code, index;
+
+ channel = data[0] >> 4;
+
+ if (!((1 << channel) & ar2->channel_mask))
+ return;
+
+ mode = data[0] & 0x0F;
+
+ if (mode > ATI_REMOTE2_PC) {
+ dev_err(&ar2->intf[1]->dev,
+ "Unknown mode byte (%02x %02x %02x %02x)\n",
+ data[3], data[2], data[1], data[0]);
+ return;
+ }
+
+ hw_code = data[2];
+ if (hw_code == 0x3f) {
+ /*
+ * For some incomprehensible reason the mouse pad generates
+ * events which look identical to the events from the last
+ * pressed mode key. Naturally we don't want to generate key
+ * events for the mouse pad so we filter out any subsequent
+ * events from the same mode key.
+ */
+ if (ar2->mode == mode)
+ return;
+
+ if (data[1] == 0)
+ ar2->mode = mode;
+ }
+
+ if (!((1 << mode) & ar2->mode_mask))
+ return;
+
+ index = ati_remote2_lookup(hw_code);
+ if (index < 0) {
+ dev_err(&ar2->intf[1]->dev,
+ "Unknown code byte (%02x %02x %02x %02x)\n",
+ data[3], data[2], data[1], data[0]);
+ return;
+ }
+
+ switch (data[1]) {
+ case 0: /* release */
+ break;
+ case 1: /* press */
+ ar2->jiffies = jiffies + msecs_to_jiffies(idev->rep[REP_DELAY]);
+ break;
+ case 2: /* repeat */
+
+ /* No repeat for mouse buttons. */
+ if (ar2->keycode[mode][index] == BTN_LEFT ||
+ ar2->keycode[mode][index] == BTN_RIGHT)
+ return;
+
+ if (!time_after_eq(jiffies, ar2->jiffies))
+ return;
+
+ ar2->jiffies = jiffies + msecs_to_jiffies(idev->rep[REP_PERIOD]);
+ break;
+ default:
+ dev_err(&ar2->intf[1]->dev,
+ "Unknown state byte (%02x %02x %02x %02x)\n",
+ data[3], data[2], data[1], data[0]);
+ return;
+ }
+
+ input_event(idev, EV_KEY, ar2->keycode[mode][index], data[1]);
+ input_sync(idev);
+}
+
+static void ati_remote2_complete_mouse(struct urb *urb)
+{
+ struct ati_remote2 *ar2 = urb->context;
+ int r;
+
+ switch (urb->status) {
+ case 0:
+ usb_mark_last_busy(ar2->udev);
+ ati_remote2_input_mouse(ar2);
+ break;
+ case -ENOENT:
+ case -EILSEQ:
+ case -ECONNRESET:
+ case -ESHUTDOWN:
+ dev_dbg(&ar2->intf[0]->dev,
+ "%s(): urb status = %d\n", __func__, urb->status);
+ return;
+ default:
+ usb_mark_last_busy(ar2->udev);
+ dev_err(&ar2->intf[0]->dev,
+ "%s(): urb status = %d\n", __func__, urb->status);
+ }
+
+ r = usb_submit_urb(urb, GFP_ATOMIC);
+ if (r)
+ dev_err(&ar2->intf[0]->dev,
+ "%s(): usb_submit_urb() = %d\n", __func__, r);
+}
+
+static void ati_remote2_complete_key(struct urb *urb)
+{
+ struct ati_remote2 *ar2 = urb->context;
+ int r;
+
+ switch (urb->status) {
+ case 0:
+ usb_mark_last_busy(ar2->udev);
+ ati_remote2_input_key(ar2);
+ break;
+ case -ENOENT:
+ case -EILSEQ:
+ case -ECONNRESET:
+ case -ESHUTDOWN:
+ dev_dbg(&ar2->intf[1]->dev,
+ "%s(): urb status = %d\n", __func__, urb->status);
+ return;
+ default:
+ usb_mark_last_busy(ar2->udev);
+ dev_err(&ar2->intf[1]->dev,
+ "%s(): urb status = %d\n", __func__, urb->status);
+ }
+
+ r = usb_submit_urb(urb, GFP_ATOMIC);
+ if (r)
+ dev_err(&ar2->intf[1]->dev,
+ "%s(): usb_submit_urb() = %d\n", __func__, r);
+}
+
+static int ati_remote2_getkeycode(struct input_dev *idev,
+ struct input_keymap_entry *ke)
+{
+ struct ati_remote2 *ar2 = input_get_drvdata(idev);
+ unsigned int mode;
+ int offset;
+ unsigned int index;
+ unsigned int scancode;
+
+ if (ke->flags & INPUT_KEYMAP_BY_INDEX) {
+ index = ke->index;
+ if (index >= ATI_REMOTE2_MODES *
+ ARRAY_SIZE(ati_remote2_key_table))
+ return -EINVAL;
+
+ mode = ke->index / ARRAY_SIZE(ati_remote2_key_table);
+ offset = ke->index % ARRAY_SIZE(ati_remote2_key_table);
+ scancode = (mode << 8) + ati_remote2_key_table[offset].hw_code;
+ } else {
+ if (input_scancode_to_scalar(ke, &scancode))
+ return -EINVAL;
+
+ mode = scancode >> 8;
+ if (mode > ATI_REMOTE2_PC)
+ return -EINVAL;
+
+ offset = ati_remote2_lookup(scancode & 0xff);
+ if (offset < 0)
+ return -EINVAL;
+
+ index = mode * ARRAY_SIZE(ati_remote2_key_table) + offset;
+ }
+
+ ke->keycode = ar2->keycode[mode][offset];
+ ke->len = sizeof(scancode);
+ memcpy(&ke->scancode, &scancode, sizeof(scancode));
+ ke->index = index;
+
+ return 0;
+}
+
+static int ati_remote2_setkeycode(struct input_dev *idev,
+ const struct input_keymap_entry *ke,
+ unsigned int *old_keycode)
+{
+ struct ati_remote2 *ar2 = input_get_drvdata(idev);
+ unsigned int mode;
+ int offset;
+ unsigned int index;
+ unsigned int scancode;
+
+ if (ke->flags & INPUT_KEYMAP_BY_INDEX) {
+ if (ke->index >= ATI_REMOTE2_MODES *
+ ARRAY_SIZE(ati_remote2_key_table))
+ return -EINVAL;
+
+ mode = ke->index / ARRAY_SIZE(ati_remote2_key_table);
+ offset = ke->index % ARRAY_SIZE(ati_remote2_key_table);
+ } else {
+ if (input_scancode_to_scalar(ke, &scancode))
+ return -EINVAL;
+
+ mode = scancode >> 8;
+ if (mode > ATI_REMOTE2_PC)
+ return -EINVAL;
+
+ offset = ati_remote2_lookup(scancode & 0xff);
+ if (offset < 0)
+ return -EINVAL;
+ }
+
+ *old_keycode = ar2->keycode[mode][offset];
+ ar2->keycode[mode][offset] = ke->keycode;
+ __set_bit(ke->keycode, idev->keybit);
+
+ for (mode = 0; mode < ATI_REMOTE2_MODES; mode++) {
+ for (index = 0; index < ARRAY_SIZE(ati_remote2_key_table); index++) {
+ if (ar2->keycode[mode][index] == *old_keycode)
+ return 0;
+ }
+ }
+
+ __clear_bit(*old_keycode, idev->keybit);
+
+ return 0;
+}
+
+static int ati_remote2_input_init(struct ati_remote2 *ar2)
+{
+ struct input_dev *idev;
+ int index, mode, retval;
+
+ idev = input_allocate_device();
+ if (!idev)
+ return -ENOMEM;
+
+ ar2->idev = idev;
+ input_set_drvdata(idev, ar2);
+
+ idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP) | BIT_MASK(EV_REL);
+ idev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) |
+ BIT_MASK(BTN_RIGHT);
+ idev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y);
+
+ for (mode = 0; mode < ATI_REMOTE2_MODES; mode++) {
+ for (index = 0; index < ARRAY_SIZE(ati_remote2_key_table); index++) {
+ ar2->keycode[mode][index] = ati_remote2_key_table[index].keycode;
+ __set_bit(ar2->keycode[mode][index], idev->keybit);
+ }
+ }
+
+ /* AUX1-AUX4 and PC generate the same scancode. */
+ index = ati_remote2_lookup(0x3f);
+ ar2->keycode[ATI_REMOTE2_AUX1][index] = KEY_PROG1;
+ ar2->keycode[ATI_REMOTE2_AUX2][index] = KEY_PROG2;
+ ar2->keycode[ATI_REMOTE2_AUX3][index] = KEY_PROG3;
+ ar2->keycode[ATI_REMOTE2_AUX4][index] = KEY_PROG4;
+ ar2->keycode[ATI_REMOTE2_PC][index] = KEY_PC;
+ __set_bit(KEY_PROG1, idev->keybit);
+ __set_bit(KEY_PROG2, idev->keybit);
+ __set_bit(KEY_PROG3, idev->keybit);
+ __set_bit(KEY_PROG4, idev->keybit);
+ __set_bit(KEY_PC, idev->keybit);
+
+ idev->rep[REP_DELAY] = 250;
+ idev->rep[REP_PERIOD] = 33;
+
+ idev->open = ati_remote2_open;
+ idev->close = ati_remote2_close;
+
+ idev->getkeycode = ati_remote2_getkeycode;
+ idev->setkeycode = ati_remote2_setkeycode;
+
+ idev->name = ar2->name;
+ idev->phys = ar2->phys;
+
+ usb_to_input_id(ar2->udev, &idev->id);
+ idev->dev.parent = &ar2->udev->dev;
+
+ retval = input_register_device(idev);
+ if (retval)
+ input_free_device(idev);
+
+ return retval;
+}
+
+static int ati_remote2_urb_init(struct ati_remote2 *ar2)
+{
+ struct usb_device *udev = ar2->udev;
+ int i, pipe, maxp;
+
+ for (i = 0; i < 2; i++) {
+ ar2->buf[i] = usb_alloc_coherent(udev, 4, GFP_KERNEL, &ar2->buf_dma[i]);
+ if (!ar2->buf[i])
+ return -ENOMEM;
+
+ ar2->urb[i] = usb_alloc_urb(0, GFP_KERNEL);
+ if (!ar2->urb[i])
+ return -ENOMEM;
+
+ pipe = usb_rcvintpipe(udev, ar2->ep[i]->bEndpointAddress);
+ maxp = usb_maxpacket(udev, pipe);
+ maxp = maxp > 4 ? 4 : maxp;
+
+ usb_fill_int_urb(ar2->urb[i], udev, pipe, ar2->buf[i], maxp,
+ i ? ati_remote2_complete_key : ati_remote2_complete_mouse,
+ ar2, ar2->ep[i]->bInterval);
+ ar2->urb[i]->transfer_dma = ar2->buf_dma[i];
+ ar2->urb[i]->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ }
+
+ return 0;
+}
+
+static void ati_remote2_urb_cleanup(struct ati_remote2 *ar2)
+{
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ usb_free_urb(ar2->urb[i]);
+ usb_free_coherent(ar2->udev, 4, ar2->buf[i], ar2->buf_dma[i]);
+ }
+}
+
+static int ati_remote2_setup(struct ati_remote2 *ar2, unsigned int ch_mask)
+{
+ int r, i, channel;
+
+ /*
+ * Configure receiver to only accept input from remote "channel"
+ * channel == 0 -> Accept input from any remote channel
+ * channel == 1 -> Only accept input from remote channel 1
+ * channel == 2 -> Only accept input from remote channel 2
+ * ...
+ * channel == 16 -> Only accept input from remote channel 16
+ */
+
+ channel = 0;
+ for (i = 0; i < 16; i++) {
+ if ((1 << i) & ch_mask) {
+ if (!(~(1 << i) & ch_mask))
+ channel = i + 1;
+ break;
+ }
+ }
+
+ r = usb_control_msg(ar2->udev, usb_sndctrlpipe(ar2->udev, 0),
+ 0x20,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
+ channel, 0x0, NULL, 0, USB_CTRL_SET_TIMEOUT);
+ if (r) {
+ dev_err(&ar2->udev->dev, "%s - failed to set channel due to error: %d\n",
+ __func__, r);
+ return r;
+ }
+
+ return 0;
+}
+
+static ssize_t ati_remote2_show_channel_mask(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct usb_device *udev = to_usb_device(dev);
+ struct usb_interface *intf = usb_ifnum_to_if(udev, 0);
+ struct ati_remote2 *ar2 = usb_get_intfdata(intf);
+
+ return sprintf(buf, "0x%04x\n", ar2->channel_mask);
+}
+
+static ssize_t ati_remote2_store_channel_mask(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct usb_device *udev = to_usb_device(dev);
+ struct usb_interface *intf = usb_ifnum_to_if(udev, 0);
+ struct ati_remote2 *ar2 = usb_get_intfdata(intf);
+ unsigned int mask;
+ int r;
+
+ r = kstrtouint(buf, 0, &mask);
+ if (r)
+ return r;
+
+ if (mask & ~ATI_REMOTE2_MAX_CHANNEL_MASK)
+ return -EINVAL;
+
+ r = usb_autopm_get_interface(ar2->intf[0]);
+ if (r) {
+ dev_err(&ar2->intf[0]->dev,
+ "%s(): usb_autopm_get_interface() = %d\n", __func__, r);
+ return r;
+ }
+
+ mutex_lock(&ati_remote2_mutex);
+
+ if (mask != ar2->channel_mask) {
+ r = ati_remote2_setup(ar2, mask);
+ if (!r)
+ ar2->channel_mask = mask;
+ }
+
+ mutex_unlock(&ati_remote2_mutex);
+
+ usb_autopm_put_interface(ar2->intf[0]);
+
+ return r ? r : count;
+}
+
+static ssize_t ati_remote2_show_mode_mask(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct usb_device *udev = to_usb_device(dev);
+ struct usb_interface *intf = usb_ifnum_to_if(udev, 0);
+ struct ati_remote2 *ar2 = usb_get_intfdata(intf);
+
+ return sprintf(buf, "0x%02x\n", ar2->mode_mask);
+}
+
+static ssize_t ati_remote2_store_mode_mask(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct usb_device *udev = to_usb_device(dev);
+ struct usb_interface *intf = usb_ifnum_to_if(udev, 0);
+ struct ati_remote2 *ar2 = usb_get_intfdata(intf);
+ unsigned int mask;
+ int err;
+
+ err = kstrtouint(buf, 0, &mask);
+ if (err)
+ return err;
+
+ if (mask & ~ATI_REMOTE2_MAX_MODE_MASK)
+ return -EINVAL;
+
+ ar2->mode_mask = mask;
+
+ return count;
+}
+
+static DEVICE_ATTR(channel_mask, 0644, ati_remote2_show_channel_mask,
+ ati_remote2_store_channel_mask);
+
+static DEVICE_ATTR(mode_mask, 0644, ati_remote2_show_mode_mask,
+ ati_remote2_store_mode_mask);
+
+static struct attribute *ati_remote2_attrs[] = {
+ &dev_attr_channel_mask.attr,
+ &dev_attr_mode_mask.attr,
+ NULL,
+};
+
+static struct attribute_group ati_remote2_attr_group = {
+ .attrs = ati_remote2_attrs,
+};
+
+static int ati_remote2_probe(struct usb_interface *interface, const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev(interface);
+ struct usb_host_interface *alt = interface->cur_altsetting;
+ struct ati_remote2 *ar2;
+ int r;
+
+ if (alt->desc.bInterfaceNumber)
+ return -ENODEV;
+
+ ar2 = kzalloc(sizeof (struct ati_remote2), GFP_KERNEL);
+ if (!ar2)
+ return -ENOMEM;
+
+ ar2->udev = udev;
+
+ /* Sanity check, first interface must have an endpoint */
+ if (alt->desc.bNumEndpoints < 1 || !alt->endpoint) {
+ dev_err(&interface->dev,
+ "%s(): interface 0 must have an endpoint\n", __func__);
+ r = -ENODEV;
+ goto fail1;
+ }
+ ar2->intf[0] = interface;
+ ar2->ep[0] = &alt->endpoint[0].desc;
+
+ /* Sanity check, the device must have two interfaces */
+ ar2->intf[1] = usb_ifnum_to_if(udev, 1);
+ if ((udev->actconfig->desc.bNumInterfaces < 2) || !ar2->intf[1]) {
+ dev_err(&interface->dev, "%s(): need 2 interfaces, found %d\n",
+ __func__, udev->actconfig->desc.bNumInterfaces);
+ r = -ENODEV;
+ goto fail1;
+ }
+
+ r = usb_driver_claim_interface(&ati_remote2_driver, ar2->intf[1], ar2);
+ if (r)
+ goto fail1;
+
+ /* Sanity check, second interface must have an endpoint */
+ alt = ar2->intf[1]->cur_altsetting;
+ if (alt->desc.bNumEndpoints < 1 || !alt->endpoint) {
+ dev_err(&interface->dev,
+ "%s(): interface 1 must have an endpoint\n", __func__);
+ r = -ENODEV;
+ goto fail2;
+ }
+ ar2->ep[1] = &alt->endpoint[0].desc;
+
+ r = ati_remote2_urb_init(ar2);
+ if (r)
+ goto fail3;
+
+ ar2->channel_mask = channel_mask;
+ ar2->mode_mask = mode_mask;
+
+ r = ati_remote2_setup(ar2, ar2->channel_mask);
+ if (r)
+ goto fail3;
+
+ usb_make_path(udev, ar2->phys, sizeof(ar2->phys));
+ strlcat(ar2->phys, "/input0", sizeof(ar2->phys));
+
+ strlcat(ar2->name, "ATI Remote Wonder II", sizeof(ar2->name));
+
+ r = sysfs_create_group(&udev->dev.kobj, &ati_remote2_attr_group);
+ if (r)
+ goto fail3;
+
+ r = ati_remote2_input_init(ar2);
+ if (r)
+ goto fail4;
+
+ usb_set_intfdata(interface, ar2);
+
+ interface->needs_remote_wakeup = 1;
+
+ return 0;
+
+ fail4:
+ sysfs_remove_group(&udev->dev.kobj, &ati_remote2_attr_group);
+ fail3:
+ ati_remote2_urb_cleanup(ar2);
+ fail2:
+ usb_driver_release_interface(&ati_remote2_driver, ar2->intf[1]);
+ fail1:
+ kfree(ar2);
+
+ return r;
+}
+
+static void ati_remote2_disconnect(struct usb_interface *interface)
+{
+ struct ati_remote2 *ar2;
+ struct usb_host_interface *alt = interface->cur_altsetting;
+
+ if (alt->desc.bInterfaceNumber)
+ return;
+
+ ar2 = usb_get_intfdata(interface);
+ usb_set_intfdata(interface, NULL);
+
+ input_unregister_device(ar2->idev);
+
+ sysfs_remove_group(&ar2->udev->dev.kobj, &ati_remote2_attr_group);
+
+ ati_remote2_urb_cleanup(ar2);
+
+ usb_driver_release_interface(&ati_remote2_driver, ar2->intf[1]);
+
+ kfree(ar2);
+}
+
+static int ati_remote2_suspend(struct usb_interface *interface,
+ pm_message_t message)
+{
+ struct ati_remote2 *ar2;
+ struct usb_host_interface *alt = interface->cur_altsetting;
+
+ if (alt->desc.bInterfaceNumber)
+ return 0;
+
+ ar2 = usb_get_intfdata(interface);
+
+ dev_dbg(&ar2->intf[0]->dev, "%s()\n", __func__);
+
+ mutex_lock(&ati_remote2_mutex);
+
+ if (ar2->flags & ATI_REMOTE2_OPENED)
+ ati_remote2_kill_urbs(ar2);
+
+ ar2->flags |= ATI_REMOTE2_SUSPENDED;
+
+ mutex_unlock(&ati_remote2_mutex);
+
+ return 0;
+}
+
+static int ati_remote2_resume(struct usb_interface *interface)
+{
+ struct ati_remote2 *ar2;
+ struct usb_host_interface *alt = interface->cur_altsetting;
+ int r = 0;
+
+ if (alt->desc.bInterfaceNumber)
+ return 0;
+
+ ar2 = usb_get_intfdata(interface);
+
+ dev_dbg(&ar2->intf[0]->dev, "%s()\n", __func__);
+
+ mutex_lock(&ati_remote2_mutex);
+
+ if (ar2->flags & ATI_REMOTE2_OPENED)
+ r = ati_remote2_submit_urbs(ar2);
+
+ if (!r)
+ ar2->flags &= ~ATI_REMOTE2_SUSPENDED;
+
+ mutex_unlock(&ati_remote2_mutex);
+
+ return r;
+}
+
+static int ati_remote2_reset_resume(struct usb_interface *interface)
+{
+ struct ati_remote2 *ar2;
+ struct usb_host_interface *alt = interface->cur_altsetting;
+ int r = 0;
+
+ if (alt->desc.bInterfaceNumber)
+ return 0;
+
+ ar2 = usb_get_intfdata(interface);
+
+ dev_dbg(&ar2->intf[0]->dev, "%s()\n", __func__);
+
+ mutex_lock(&ati_remote2_mutex);
+
+ r = ati_remote2_setup(ar2, ar2->channel_mask);
+ if (r)
+ goto out;
+
+ if (ar2->flags & ATI_REMOTE2_OPENED)
+ r = ati_remote2_submit_urbs(ar2);
+
+ if (!r)
+ ar2->flags &= ~ATI_REMOTE2_SUSPENDED;
+
+ out:
+ mutex_unlock(&ati_remote2_mutex);
+
+ return r;
+}
+
+static int ati_remote2_pre_reset(struct usb_interface *interface)
+{
+ struct ati_remote2 *ar2;
+ struct usb_host_interface *alt = interface->cur_altsetting;
+
+ if (alt->desc.bInterfaceNumber)
+ return 0;
+
+ ar2 = usb_get_intfdata(interface);
+
+ dev_dbg(&ar2->intf[0]->dev, "%s()\n", __func__);
+
+ mutex_lock(&ati_remote2_mutex);
+
+ if (ar2->flags == ATI_REMOTE2_OPENED)
+ ati_remote2_kill_urbs(ar2);
+
+ return 0;
+}
+
+static int ati_remote2_post_reset(struct usb_interface *interface)
+{
+ struct ati_remote2 *ar2;
+ struct usb_host_interface *alt = interface->cur_altsetting;
+ int r = 0;
+
+ if (alt->desc.bInterfaceNumber)
+ return 0;
+
+ ar2 = usb_get_intfdata(interface);
+
+ dev_dbg(&ar2->intf[0]->dev, "%s()\n", __func__);
+
+ if (ar2->flags == ATI_REMOTE2_OPENED)
+ r = ati_remote2_submit_urbs(ar2);
+
+ mutex_unlock(&ati_remote2_mutex);
+
+ return r;
+}
+
+module_usb_driver(ati_remote2_driver);
diff --git a/drivers/input/misc/atlas_btns.c b/drivers/input/misc/atlas_btns.c
new file mode 100644
index 000000000..0e77c40e1
--- /dev/null
+++ b/drivers/input/misc/atlas_btns.c
@@ -0,0 +1,144 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * atlas_btns.c - Atlas Wallmount Touchscreen ACPI Extras
+ *
+ * Copyright (C) 2006 Jaya Kumar
+ * Based on Toshiba ACPI by John Belmonte and ASUS ACPI
+ * This work was sponsored by CIS(M) Sdn Bhd.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/types.h>
+#include <linux/acpi.h>
+#include <linux/uaccess.h>
+
+#define ACPI_ATLAS_NAME "Atlas ACPI"
+#define ACPI_ATLAS_CLASS "Atlas"
+
+static unsigned short atlas_keymap[16];
+static struct input_dev *input_dev;
+
+/* button handling code */
+static acpi_status acpi_atlas_button_setup(acpi_handle region_handle,
+ u32 function, void *handler_context, void **return_context)
+{
+ *return_context =
+ (function != ACPI_REGION_DEACTIVATE) ? handler_context : NULL;
+
+ return AE_OK;
+}
+
+static acpi_status acpi_atlas_button_handler(u32 function,
+ acpi_physical_address address,
+ u32 bit_width, u64 *value,
+ void *handler_context, void *region_context)
+{
+ acpi_status status;
+
+ if (function == ACPI_WRITE) {
+ int code = address & 0x0f;
+ int key_down = !(address & 0x10);
+
+ input_event(input_dev, EV_MSC, MSC_SCAN, code);
+ input_report_key(input_dev, atlas_keymap[code], key_down);
+ input_sync(input_dev);
+
+ status = AE_OK;
+ } else {
+ pr_warn("shrugged on unexpected function: function=%x,address=%lx,value=%x\n",
+ function, (unsigned long)address, (u32)*value);
+ status = AE_BAD_PARAMETER;
+ }
+
+ return status;
+}
+
+static int atlas_acpi_button_add(struct acpi_device *device)
+{
+ acpi_status status;
+ int i;
+ int err;
+
+ input_dev = input_allocate_device();
+ if (!input_dev) {
+ pr_err("unable to allocate input device\n");
+ return -ENOMEM;
+ }
+
+ input_dev->name = "Atlas ACPI button driver";
+ input_dev->phys = "ASIM0000/atlas/input0";
+ input_dev->id.bustype = BUS_HOST;
+ input_dev->keycode = atlas_keymap;
+ input_dev->keycodesize = sizeof(unsigned short);
+ input_dev->keycodemax = ARRAY_SIZE(atlas_keymap);
+
+ input_set_capability(input_dev, EV_MSC, MSC_SCAN);
+ __set_bit(EV_KEY, input_dev->evbit);
+ for (i = 0; i < ARRAY_SIZE(atlas_keymap); i++) {
+ if (i < 9) {
+ atlas_keymap[i] = KEY_F1 + i;
+ __set_bit(KEY_F1 + i, input_dev->keybit);
+ } else
+ atlas_keymap[i] = KEY_RESERVED;
+ }
+
+ err = input_register_device(input_dev);
+ if (err) {
+ pr_err("couldn't register input device\n");
+ input_free_device(input_dev);
+ return err;
+ }
+
+ /* hookup button handler */
+ status = acpi_install_address_space_handler(device->handle,
+ 0x81, &acpi_atlas_button_handler,
+ &acpi_atlas_button_setup, device);
+ if (ACPI_FAILURE(status)) {
+ pr_err("error installing addr spc handler\n");
+ input_unregister_device(input_dev);
+ err = -EINVAL;
+ }
+
+ return err;
+}
+
+static int atlas_acpi_button_remove(struct acpi_device *device)
+{
+ acpi_status status;
+
+ status = acpi_remove_address_space_handler(device->handle,
+ 0x81, &acpi_atlas_button_handler);
+ if (ACPI_FAILURE(status))
+ pr_err("error removing addr spc handler\n");
+
+ input_unregister_device(input_dev);
+
+ return 0;
+}
+
+static const struct acpi_device_id atlas_device_ids[] = {
+ {"ASIM0000", 0},
+ {"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, atlas_device_ids);
+
+static struct acpi_driver atlas_acpi_driver = {
+ .name = ACPI_ATLAS_NAME,
+ .class = ACPI_ATLAS_CLASS,
+ .owner = THIS_MODULE,
+ .ids = atlas_device_ids,
+ .ops = {
+ .add = atlas_acpi_button_add,
+ .remove = atlas_acpi_button_remove,
+ },
+};
+module_acpi_driver(atlas_acpi_driver);
+
+MODULE_AUTHOR("Jaya Kumar");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Atlas button driver");
+
diff --git a/drivers/input/misc/atmel_captouch.c b/drivers/input/misc/atmel_captouch.c
new file mode 100644
index 000000000..051aded68
--- /dev/null
+++ b/drivers/input/misc/atmel_captouch.c
@@ -0,0 +1,281 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Atmel Atmegaxx Capacitive Touch Button Driver
+ *
+ * Copyright (C) 2016 Google, inc.
+ */
+
+/*
+ * It's irrelevant that the HW used to develop captouch driver is based
+ * on Atmega88PA part and uses QtouchADC parts for sensing touch.
+ * Calling this driver "captouch" is an arbitrary way to distinguish
+ * the protocol this driver supported by other atmel/qtouch drivers.
+ *
+ * Captouch driver supports a newer/different version of the I2C
+ * registers/commands than the qt1070.c driver.
+ * Don't let the similarity of the general driver structure fool you.
+ *
+ * For raw i2c access from userspace, use i2cset/i2cget
+ * to poke at /dev/i2c-N devices.
+ */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+
+/* Maximum number of buttons supported */
+#define MAX_NUM_OF_BUTTONS 8
+
+/* Registers */
+#define REG_KEY1_THRESHOLD 0x02
+#define REG_KEY2_THRESHOLD 0x03
+#define REG_KEY3_THRESHOLD 0x04
+#define REG_KEY4_THRESHOLD 0x05
+
+#define REG_KEY1_REF_H 0x20
+#define REG_KEY1_REF_L 0x21
+#define REG_KEY2_REF_H 0x22
+#define REG_KEY2_REF_L 0x23
+#define REG_KEY3_REF_H 0x24
+#define REG_KEY3_REF_L 0x25
+#define REG_KEY4_REF_H 0x26
+#define REG_KEY4_REF_L 0x27
+
+#define REG_KEY1_DLT_H 0x30
+#define REG_KEY1_DLT_L 0x31
+#define REG_KEY2_DLT_H 0x32
+#define REG_KEY2_DLT_L 0x33
+#define REG_KEY3_DLT_H 0x34
+#define REG_KEY3_DLT_L 0x35
+#define REG_KEY4_DLT_H 0x36
+#define REG_KEY4_DLT_L 0x37
+
+#define REG_KEY_STATE 0x3C
+
+/*
+ * @i2c_client: I2C slave device client pointer
+ * @input: Input device pointer
+ * @num_btn: Number of buttons
+ * @keycodes: map of button# to KeyCode
+ * @prev_btn: Previous key state to detect button "press" or "release"
+ * @xfer_buf: I2C transfer buffer
+ */
+struct atmel_captouch_device {
+ struct i2c_client *client;
+ struct input_dev *input;
+ u32 num_btn;
+ u32 keycodes[MAX_NUM_OF_BUTTONS];
+ u8 prev_btn;
+ u8 xfer_buf[8] ____cacheline_aligned;
+};
+
+/*
+ * Read from I2C slave device
+ * The protocol is that the client has to provide both the register address
+ * and the length, and while reading back the device would prepend the data
+ * with address and length for verification.
+ */
+static int atmel_read(struct atmel_captouch_device *capdev,
+ u8 reg, u8 *data, size_t len)
+{
+ struct i2c_client *client = capdev->client;
+ struct device *dev = &client->dev;
+ struct i2c_msg msg[2];
+ int err;
+
+ if (len > sizeof(capdev->xfer_buf) - 2)
+ return -EINVAL;
+
+ capdev->xfer_buf[0] = reg;
+ capdev->xfer_buf[1] = len;
+
+ msg[0].addr = client->addr;
+ msg[0].flags = 0;
+ msg[0].buf = capdev->xfer_buf;
+ msg[0].len = 2;
+
+ msg[1].addr = client->addr;
+ msg[1].flags = I2C_M_RD;
+ msg[1].buf = capdev->xfer_buf;
+ msg[1].len = len + 2;
+
+ err = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
+ if (err != ARRAY_SIZE(msg))
+ return err < 0 ? err : -EIO;
+
+ if (capdev->xfer_buf[0] != reg) {
+ dev_err(dev,
+ "I2C read error: register address does not match (%#02x vs %02x)\n",
+ capdev->xfer_buf[0], reg);
+ return -ECOMM;
+ }
+
+ memcpy(data, &capdev->xfer_buf[2], len);
+
+ return 0;
+}
+
+/*
+ * Handle interrupt and report the key changes to the input system.
+ * Multi-touch can be supported; however, it really depends on whether
+ * the device can multi-touch.
+ */
+static irqreturn_t atmel_captouch_isr(int irq, void *data)
+{
+ struct atmel_captouch_device *capdev = data;
+ struct device *dev = &capdev->client->dev;
+ int error;
+ int i;
+ u8 new_btn;
+ u8 changed_btn;
+
+ error = atmel_read(capdev, REG_KEY_STATE, &new_btn, 1);
+ if (error) {
+ dev_err(dev, "failed to read button state: %d\n", error);
+ goto out;
+ }
+
+ dev_dbg(dev, "%s: button state %#02x\n", __func__, new_btn);
+
+ changed_btn = new_btn ^ capdev->prev_btn;
+ capdev->prev_btn = new_btn;
+
+ for (i = 0; i < capdev->num_btn; i++) {
+ if (changed_btn & BIT(i))
+ input_report_key(capdev->input,
+ capdev->keycodes[i],
+ new_btn & BIT(i));
+ }
+
+ input_sync(capdev->input);
+
+out:
+ return IRQ_HANDLED;
+}
+
+/*
+ * Probe function to setup the device, input system and interrupt
+ */
+static int atmel_captouch_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct atmel_captouch_device *capdev;
+ struct device *dev = &client->dev;
+ struct device_node *node;
+ int i;
+ int err;
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_BYTE_DATA |
+ I2C_FUNC_SMBUS_WORD_DATA |
+ I2C_FUNC_SMBUS_I2C_BLOCK)) {
+ dev_err(dev, "needed i2c functionality is not supported\n");
+ return -EINVAL;
+ }
+
+ capdev = devm_kzalloc(dev, sizeof(*capdev), GFP_KERNEL);
+ if (!capdev)
+ return -ENOMEM;
+
+ capdev->client = client;
+
+ err = atmel_read(capdev, REG_KEY_STATE,
+ &capdev->prev_btn, sizeof(capdev->prev_btn));
+ if (err) {
+ dev_err(dev, "failed to read initial button state: %d\n", err);
+ return err;
+ }
+
+ capdev->input = devm_input_allocate_device(dev);
+ if (!capdev->input) {
+ dev_err(dev, "failed to allocate input device\n");
+ return -ENOMEM;
+ }
+
+ capdev->input->id.bustype = BUS_I2C;
+ capdev->input->id.product = 0x880A;
+ capdev->input->id.version = 0;
+ capdev->input->name = "ATMegaXX Capacitive Button Controller";
+ __set_bit(EV_KEY, capdev->input->evbit);
+
+ node = dev->of_node;
+ if (!node) {
+ dev_err(dev, "failed to find matching node in device tree\n");
+ return -EINVAL;
+ }
+
+ if (of_property_read_bool(node, "autorepeat"))
+ __set_bit(EV_REP, capdev->input->evbit);
+
+ capdev->num_btn = of_property_count_u32_elems(node, "linux,keymap");
+ if (capdev->num_btn > MAX_NUM_OF_BUTTONS)
+ capdev->num_btn = MAX_NUM_OF_BUTTONS;
+
+ err = of_property_read_u32_array(node, "linux,keycodes",
+ capdev->keycodes,
+ capdev->num_btn);
+ if (err) {
+ dev_err(dev,
+ "failed to read linux,keycode property: %d\n", err);
+ return err;
+ }
+
+ for (i = 0; i < capdev->num_btn; i++)
+ __set_bit(capdev->keycodes[i], capdev->input->keybit);
+
+ capdev->input->keycode = capdev->keycodes;
+ capdev->input->keycodesize = sizeof(capdev->keycodes[0]);
+ capdev->input->keycodemax = capdev->num_btn;
+
+ err = input_register_device(capdev->input);
+ if (err)
+ return err;
+
+ err = devm_request_threaded_irq(dev, client->irq,
+ NULL, atmel_captouch_isr,
+ IRQF_ONESHOT,
+ "atmel_captouch", capdev);
+ if (err) {
+ dev_err(dev, "failed to request irq %d: %d\n",
+ client->irq, err);
+ return err;
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id atmel_captouch_of_id[] = {
+ {
+ .compatible = "atmel,captouch",
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, atmel_captouch_of_id);
+#endif
+
+static const struct i2c_device_id atmel_captouch_id[] = {
+ { "atmel_captouch", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, atmel_captouch_id);
+
+static struct i2c_driver atmel_captouch_driver = {
+ .probe = atmel_captouch_probe,
+ .id_table = atmel_captouch_id,
+ .driver = {
+ .name = "atmel_captouch",
+ .of_match_table = of_match_ptr(atmel_captouch_of_id),
+ },
+};
+module_i2c_driver(atmel_captouch_driver);
+
+/* Module information */
+MODULE_AUTHOR("Hung-yu Wu <hywu@google.com>");
+MODULE_DESCRIPTION("Atmel ATmegaXX Capacitance Touch Sensor I2C Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/misc/axp20x-pek.c b/drivers/input/misc/axp20x-pek.c
new file mode 100644
index 000000000..04da7916e
--- /dev/null
+++ b/drivers/input/misc/axp20x-pek.c
@@ -0,0 +1,424 @@
+/*
+ * axp20x power button driver.
+ *
+ * Copyright (C) 2013 Carlo Caione <carlo@caione.org>
+ *
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License. See the file "COPYING" in the main directory of this
+ * archive for more details.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/acpi.h>
+#include <linux/errno.h>
+#include <linux/irq.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mfd/axp20x.h>
+#include <linux/module.h>
+#include <linux/platform_data/x86/soc.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#define AXP20X_PEK_STARTUP_MASK (0xc0)
+#define AXP20X_PEK_SHUTDOWN_MASK (0x03)
+
+struct axp20x_info {
+ const struct axp20x_time *startup_time;
+ unsigned int startup_mask;
+ const struct axp20x_time *shutdown_time;
+ unsigned int shutdown_mask;
+};
+
+struct axp20x_pek {
+ struct axp20x_dev *axp20x;
+ struct input_dev *input;
+ struct axp20x_info *info;
+ int irq_dbr;
+ int irq_dbf;
+};
+
+struct axp20x_time {
+ unsigned int time;
+ unsigned int idx;
+};
+
+static const struct axp20x_time startup_time[] = {
+ { .time = 128, .idx = 0 },
+ { .time = 1000, .idx = 2 },
+ { .time = 3000, .idx = 1 },
+ { .time = 2000, .idx = 3 },
+};
+
+static const struct axp20x_time axp221_startup_time[] = {
+ { .time = 128, .idx = 0 },
+ { .time = 1000, .idx = 1 },
+ { .time = 2000, .idx = 2 },
+ { .time = 3000, .idx = 3 },
+};
+
+static const struct axp20x_time shutdown_time[] = {
+ { .time = 4000, .idx = 0 },
+ { .time = 6000, .idx = 1 },
+ { .time = 8000, .idx = 2 },
+ { .time = 10000, .idx = 3 },
+};
+
+static const struct axp20x_info axp20x_info = {
+ .startup_time = startup_time,
+ .startup_mask = AXP20X_PEK_STARTUP_MASK,
+ .shutdown_time = shutdown_time,
+ .shutdown_mask = AXP20X_PEK_SHUTDOWN_MASK,
+};
+
+static const struct axp20x_info axp221_info = {
+ .startup_time = axp221_startup_time,
+ .startup_mask = AXP20X_PEK_STARTUP_MASK,
+ .shutdown_time = shutdown_time,
+ .shutdown_mask = AXP20X_PEK_SHUTDOWN_MASK,
+};
+
+static ssize_t axp20x_show_attr(struct device *dev,
+ const struct axp20x_time *time,
+ unsigned int mask, char *buf)
+{
+ struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev);
+ unsigned int val;
+ int ret, i;
+
+ ret = regmap_read(axp20x_pek->axp20x->regmap, AXP20X_PEK_KEY, &val);
+ if (ret != 0)
+ return ret;
+
+ val &= mask;
+ val >>= ffs(mask) - 1;
+
+ for (i = 0; i < 4; i++)
+ if (val == time[i].idx)
+ val = time[i].time;
+
+ return sprintf(buf, "%u\n", val);
+}
+
+static ssize_t axp20x_show_attr_startup(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev);
+
+ return axp20x_show_attr(dev, axp20x_pek->info->startup_time,
+ axp20x_pek->info->startup_mask, buf);
+}
+
+static ssize_t axp20x_show_attr_shutdown(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev);
+
+ return axp20x_show_attr(dev, axp20x_pek->info->shutdown_time,
+ axp20x_pek->info->shutdown_mask, buf);
+}
+
+static ssize_t axp20x_store_attr(struct device *dev,
+ const struct axp20x_time *time,
+ unsigned int mask, const char *buf,
+ size_t count)
+{
+ struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev);
+ char val_str[20];
+ size_t len;
+ int ret, i;
+ unsigned int val, idx = 0;
+ unsigned int best_err = UINT_MAX;
+
+ val_str[sizeof(val_str) - 1] = '\0';
+ strncpy(val_str, buf, sizeof(val_str) - 1);
+ len = strlen(val_str);
+
+ if (len && val_str[len - 1] == '\n')
+ val_str[len - 1] = '\0';
+
+ ret = kstrtouint(val_str, 10, &val);
+ if (ret)
+ return ret;
+
+ for (i = 3; i >= 0; i--) {
+ unsigned int err;
+
+ err = abs(time[i].time - val);
+ if (err < best_err) {
+ best_err = err;
+ idx = time[i].idx;
+ }
+
+ if (!err)
+ break;
+ }
+
+ idx <<= ffs(mask) - 1;
+ ret = regmap_update_bits(axp20x_pek->axp20x->regmap, AXP20X_PEK_KEY,
+ mask, idx);
+ if (ret != 0)
+ return -EINVAL;
+
+ return count;
+}
+
+static ssize_t axp20x_store_attr_startup(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev);
+
+ return axp20x_store_attr(dev, axp20x_pek->info->startup_time,
+ axp20x_pek->info->startup_mask, buf, count);
+}
+
+static ssize_t axp20x_store_attr_shutdown(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev);
+
+ return axp20x_store_attr(dev, axp20x_pek->info->shutdown_time,
+ axp20x_pek->info->shutdown_mask, buf, count);
+}
+
+static DEVICE_ATTR(startup, 0644, axp20x_show_attr_startup,
+ axp20x_store_attr_startup);
+static DEVICE_ATTR(shutdown, 0644, axp20x_show_attr_shutdown,
+ axp20x_store_attr_shutdown);
+
+static struct attribute *axp20x_attrs[] = {
+ &dev_attr_startup.attr,
+ &dev_attr_shutdown.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(axp20x);
+
+static irqreturn_t axp20x_pek_irq(int irq, void *pwr)
+{
+ struct input_dev *idev = pwr;
+ struct axp20x_pek *axp20x_pek = input_get_drvdata(idev);
+
+ /*
+ * The power-button is connected to ground so a falling edge (dbf)
+ * means it is pressed.
+ */
+ if (irq == axp20x_pek->irq_dbf)
+ input_report_key(idev, KEY_POWER, true);
+ else if (irq == axp20x_pek->irq_dbr)
+ input_report_key(idev, KEY_POWER, false);
+
+ input_sync(idev);
+
+ return IRQ_HANDLED;
+}
+
+static int axp20x_pek_probe_input_device(struct axp20x_pek *axp20x_pek,
+ struct platform_device *pdev)
+{
+ struct axp20x_dev *axp20x = axp20x_pek->axp20x;
+ struct input_dev *idev;
+ int error;
+
+ axp20x_pek->irq_dbr = platform_get_irq_byname(pdev, "PEK_DBR");
+ if (axp20x_pek->irq_dbr < 0)
+ return axp20x_pek->irq_dbr;
+ axp20x_pek->irq_dbr = regmap_irq_get_virq(axp20x->regmap_irqc,
+ axp20x_pek->irq_dbr);
+
+ axp20x_pek->irq_dbf = platform_get_irq_byname(pdev, "PEK_DBF");
+ if (axp20x_pek->irq_dbf < 0)
+ return axp20x_pek->irq_dbf;
+ axp20x_pek->irq_dbf = regmap_irq_get_virq(axp20x->regmap_irqc,
+ axp20x_pek->irq_dbf);
+
+ axp20x_pek->input = devm_input_allocate_device(&pdev->dev);
+ if (!axp20x_pek->input)
+ return -ENOMEM;
+
+ idev = axp20x_pek->input;
+
+ idev->name = "axp20x-pek";
+ idev->phys = "m1kbd/input2";
+ idev->dev.parent = &pdev->dev;
+
+ input_set_capability(idev, EV_KEY, KEY_POWER);
+
+ input_set_drvdata(idev, axp20x_pek);
+
+ error = devm_request_any_context_irq(&pdev->dev, axp20x_pek->irq_dbr,
+ axp20x_pek_irq, 0,
+ "axp20x-pek-dbr", idev);
+ if (error < 0) {
+ dev_err(&pdev->dev, "Failed to request dbr IRQ#%d: %d\n",
+ axp20x_pek->irq_dbr, error);
+ return error;
+ }
+
+ error = devm_request_any_context_irq(&pdev->dev, axp20x_pek->irq_dbf,
+ axp20x_pek_irq, 0,
+ "axp20x-pek-dbf", idev);
+ if (error < 0) {
+ dev_err(&pdev->dev, "Failed to request dbf IRQ#%d: %d\n",
+ axp20x_pek->irq_dbf, error);
+ return error;
+ }
+
+ error = input_register_device(idev);
+ if (error) {
+ dev_err(&pdev->dev, "Can't register input device: %d\n",
+ error);
+ return error;
+ }
+
+ device_init_wakeup(&pdev->dev, true);
+
+ return 0;
+}
+
+static bool axp20x_pek_should_register_input(struct axp20x_pek *axp20x_pek)
+{
+ if (IS_ENABLED(CONFIG_INPUT_SOC_BUTTON_ARRAY) &&
+ axp20x_pek->axp20x->variant == AXP288_ID) {
+ /*
+ * On Cherry Trail platforms (hrv == 3), do not register the
+ * input device if there is an "INTCFD9" or "ACPI0011" gpio
+ * button ACPI device, as that handles the power button too,
+ * and otherwise we end up reporting all presses twice.
+ */
+ if (soc_intel_is_cht() &&
+ (acpi_dev_present("INTCFD9", NULL, -1) ||
+ acpi_dev_present("ACPI0011", NULL, -1)))
+ return false;
+ }
+
+ return true;
+}
+
+static int axp20x_pek_probe(struct platform_device *pdev)
+{
+ struct axp20x_pek *axp20x_pek;
+ const struct platform_device_id *match = platform_get_device_id(pdev);
+ int error;
+
+ if (!match) {
+ dev_err(&pdev->dev, "Failed to get platform_device_id\n");
+ return -EINVAL;
+ }
+
+ axp20x_pek = devm_kzalloc(&pdev->dev, sizeof(struct axp20x_pek),
+ GFP_KERNEL);
+ if (!axp20x_pek)
+ return -ENOMEM;
+
+ axp20x_pek->axp20x = dev_get_drvdata(pdev->dev.parent);
+
+ if (axp20x_pek_should_register_input(axp20x_pek)) {
+ error = axp20x_pek_probe_input_device(axp20x_pek, pdev);
+ if (error)
+ return error;
+ }
+
+ axp20x_pek->info = (struct axp20x_info *)match->driver_data;
+
+ platform_set_drvdata(pdev, axp20x_pek);
+
+ return 0;
+}
+
+static int __maybe_unused axp20x_pek_suspend(struct device *dev)
+{
+ struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev);
+
+ /*
+ * As nested threaded IRQs are not automatically disabled during
+ * suspend, we must explicitly disable non-wakeup IRQs.
+ */
+ if (device_may_wakeup(dev)) {
+ enable_irq_wake(axp20x_pek->irq_dbf);
+ enable_irq_wake(axp20x_pek->irq_dbr);
+ } else {
+ disable_irq(axp20x_pek->irq_dbf);
+ disable_irq(axp20x_pek->irq_dbr);
+ }
+
+ return 0;
+}
+
+static int __maybe_unused axp20x_pek_resume(struct device *dev)
+{
+ struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev);
+
+ if (device_may_wakeup(dev)) {
+ disable_irq_wake(axp20x_pek->irq_dbf);
+ disable_irq_wake(axp20x_pek->irq_dbr);
+ } else {
+ enable_irq(axp20x_pek->irq_dbf);
+ enable_irq(axp20x_pek->irq_dbr);
+ }
+
+ return 0;
+}
+
+static int __maybe_unused axp20x_pek_resume_noirq(struct device *dev)
+{
+ struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev);
+
+ if (axp20x_pek->axp20x->variant != AXP288_ID)
+ return 0;
+
+ /*
+ * Clear interrupts from button presses during suspend, to avoid
+ * a wakeup power-button press getting reported to userspace.
+ */
+ regmap_write(axp20x_pek->axp20x->regmap,
+ AXP20X_IRQ1_STATE + AXP288_IRQ_POKN / 8,
+ BIT(AXP288_IRQ_POKN % 8));
+
+ return 0;
+}
+
+static const struct dev_pm_ops axp20x_pek_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(axp20x_pek_suspend, axp20x_pek_resume)
+#ifdef CONFIG_PM_SLEEP
+ .resume_noirq = axp20x_pek_resume_noirq,
+#endif
+};
+
+static const struct platform_device_id axp_pek_id_match[] = {
+ {
+ .name = "axp20x-pek",
+ .driver_data = (kernel_ulong_t)&axp20x_info,
+ },
+ {
+ .name = "axp221-pek",
+ .driver_data = (kernel_ulong_t)&axp221_info,
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(platform, axp_pek_id_match);
+
+static struct platform_driver axp20x_pek_driver = {
+ .probe = axp20x_pek_probe,
+ .id_table = axp_pek_id_match,
+ .driver = {
+ .name = "axp20x-pek",
+ .pm = &axp20x_pek_pm_ops,
+ .dev_groups = axp20x_groups,
+ },
+};
+module_platform_driver(axp20x_pek_driver);
+
+MODULE_DESCRIPTION("axp20x Power Button");
+MODULE_AUTHOR("Carlo Caione <carlo@caione.org>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/bma150.c b/drivers/input/misc/bma150.c
new file mode 100644
index 000000000..84fe394da
--- /dev/null
+++ b/drivers/input/misc/bma150.c
@@ -0,0 +1,563 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2011 Bosch Sensortec GmbH
+ * Copyright (c) 2011 Unixphere
+ *
+ * This driver adds support for Bosch Sensortec's digital acceleration
+ * sensors BMA150 and SMB380.
+ * The SMB380 is fully compatible with BMA150 and only differs in packaging.
+ *
+ * The datasheet for the BMA150 chip can be found here:
+ * http://www.bosch-sensortec.com/content/language1/downloads/BST-BMA150-DS000-07.pdf
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/bma150.h>
+
+#define ABSMAX_ACC_VAL 0x01FF
+#define ABSMIN_ACC_VAL -(ABSMAX_ACC_VAL)
+
+/* Each axis is represented by a 2-byte data word */
+#define BMA150_XYZ_DATA_SIZE 6
+
+/* Input poll interval in milliseconds */
+#define BMA150_POLL_INTERVAL 10
+#define BMA150_POLL_MAX 200
+#define BMA150_POLL_MIN 0
+
+#define BMA150_MODE_NORMAL 0
+#define BMA150_MODE_SLEEP 2
+#define BMA150_MODE_WAKE_UP 3
+
+/* Data register addresses */
+#define BMA150_DATA_0_REG 0x00
+#define BMA150_DATA_1_REG 0x01
+#define BMA150_DATA_2_REG 0x02
+
+/* Control register addresses */
+#define BMA150_CTRL_0_REG 0x0A
+#define BMA150_CTRL_1_REG 0x0B
+#define BMA150_CTRL_2_REG 0x14
+#define BMA150_CTRL_3_REG 0x15
+
+/* Configuration/Setting register addresses */
+#define BMA150_CFG_0_REG 0x0C
+#define BMA150_CFG_1_REG 0x0D
+#define BMA150_CFG_2_REG 0x0E
+#define BMA150_CFG_3_REG 0x0F
+#define BMA150_CFG_4_REG 0x10
+#define BMA150_CFG_5_REG 0x11
+
+#define BMA150_CHIP_ID 2
+#define BMA150_CHIP_ID_REG BMA150_DATA_0_REG
+
+#define BMA150_ACC_X_LSB_REG BMA150_DATA_2_REG
+
+#define BMA150_SLEEP_POS 0
+#define BMA150_SLEEP_MSK 0x01
+#define BMA150_SLEEP_REG BMA150_CTRL_0_REG
+
+#define BMA150_BANDWIDTH_POS 0
+#define BMA150_BANDWIDTH_MSK 0x07
+#define BMA150_BANDWIDTH_REG BMA150_CTRL_2_REG
+
+#define BMA150_RANGE_POS 3
+#define BMA150_RANGE_MSK 0x18
+#define BMA150_RANGE_REG BMA150_CTRL_2_REG
+
+#define BMA150_WAKE_UP_POS 0
+#define BMA150_WAKE_UP_MSK 0x01
+#define BMA150_WAKE_UP_REG BMA150_CTRL_3_REG
+
+#define BMA150_SW_RES_POS 1
+#define BMA150_SW_RES_MSK 0x02
+#define BMA150_SW_RES_REG BMA150_CTRL_0_REG
+
+/* Any-motion interrupt register fields */
+#define BMA150_ANY_MOTION_EN_POS 6
+#define BMA150_ANY_MOTION_EN_MSK 0x40
+#define BMA150_ANY_MOTION_EN_REG BMA150_CTRL_1_REG
+
+#define BMA150_ANY_MOTION_DUR_POS 6
+#define BMA150_ANY_MOTION_DUR_MSK 0xC0
+#define BMA150_ANY_MOTION_DUR_REG BMA150_CFG_5_REG
+
+#define BMA150_ANY_MOTION_THRES_REG BMA150_CFG_4_REG
+
+/* Advanced interrupt register fields */
+#define BMA150_ADV_INT_EN_POS 6
+#define BMA150_ADV_INT_EN_MSK 0x40
+#define BMA150_ADV_INT_EN_REG BMA150_CTRL_3_REG
+
+/* High-G interrupt register fields */
+#define BMA150_HIGH_G_EN_POS 1
+#define BMA150_HIGH_G_EN_MSK 0x02
+#define BMA150_HIGH_G_EN_REG BMA150_CTRL_1_REG
+
+#define BMA150_HIGH_G_HYST_POS 3
+#define BMA150_HIGH_G_HYST_MSK 0x38
+#define BMA150_HIGH_G_HYST_REG BMA150_CFG_5_REG
+
+#define BMA150_HIGH_G_DUR_REG BMA150_CFG_3_REG
+#define BMA150_HIGH_G_THRES_REG BMA150_CFG_2_REG
+
+/* Low-G interrupt register fields */
+#define BMA150_LOW_G_EN_POS 0
+#define BMA150_LOW_G_EN_MSK 0x01
+#define BMA150_LOW_G_EN_REG BMA150_CTRL_1_REG
+
+#define BMA150_LOW_G_HYST_POS 0
+#define BMA150_LOW_G_HYST_MSK 0x07
+#define BMA150_LOW_G_HYST_REG BMA150_CFG_5_REG
+
+#define BMA150_LOW_G_DUR_REG BMA150_CFG_1_REG
+#define BMA150_LOW_G_THRES_REG BMA150_CFG_0_REG
+
+struct bma150_data {
+ struct i2c_client *client;
+ struct input_dev *input;
+ u8 mode;
+};
+
+/*
+ * The settings for the given range, bandwidth and interrupt features
+ * are stated and verified by Bosch Sensortec where they are configured
+ * to provide a generic sensitivity performance.
+ */
+static const struct bma150_cfg default_cfg = {
+ .any_motion_int = 1,
+ .hg_int = 1,
+ .lg_int = 1,
+ .any_motion_dur = 0,
+ .any_motion_thres = 0,
+ .hg_hyst = 0,
+ .hg_dur = 150,
+ .hg_thres = 160,
+ .lg_hyst = 0,
+ .lg_dur = 150,
+ .lg_thres = 20,
+ .range = BMA150_RANGE_2G,
+ .bandwidth = BMA150_BW_50HZ
+};
+
+static int bma150_write_byte(struct i2c_client *client, u8 reg, u8 val)
+{
+ s32 ret;
+
+ /* As per specification, disable irq in between register writes */
+ if (client->irq)
+ disable_irq_nosync(client->irq);
+
+ ret = i2c_smbus_write_byte_data(client, reg, val);
+
+ if (client->irq)
+ enable_irq(client->irq);
+
+ return ret;
+}
+
+static int bma150_set_reg_bits(struct i2c_client *client,
+ int val, int shift, u8 mask, u8 reg)
+{
+ int data;
+
+ data = i2c_smbus_read_byte_data(client, reg);
+ if (data < 0)
+ return data;
+
+ data = (data & ~mask) | ((val << shift) & mask);
+ return bma150_write_byte(client, reg, data);
+}
+
+static int bma150_set_mode(struct bma150_data *bma150, u8 mode)
+{
+ int error;
+
+ error = bma150_set_reg_bits(bma150->client, mode, BMA150_WAKE_UP_POS,
+ BMA150_WAKE_UP_MSK, BMA150_WAKE_UP_REG);
+ if (error)
+ return error;
+
+ error = bma150_set_reg_bits(bma150->client, mode, BMA150_SLEEP_POS,
+ BMA150_SLEEP_MSK, BMA150_SLEEP_REG);
+ if (error)
+ return error;
+
+ if (mode == BMA150_MODE_NORMAL)
+ usleep_range(2000, 2100);
+
+ bma150->mode = mode;
+ return 0;
+}
+
+static int bma150_soft_reset(struct bma150_data *bma150)
+{
+ int error;
+
+ error = bma150_set_reg_bits(bma150->client, 1, BMA150_SW_RES_POS,
+ BMA150_SW_RES_MSK, BMA150_SW_RES_REG);
+ if (error)
+ return error;
+
+ usleep_range(2000, 2100);
+ return 0;
+}
+
+static int bma150_set_range(struct bma150_data *bma150, u8 range)
+{
+ return bma150_set_reg_bits(bma150->client, range, BMA150_RANGE_POS,
+ BMA150_RANGE_MSK, BMA150_RANGE_REG);
+}
+
+static int bma150_set_bandwidth(struct bma150_data *bma150, u8 bw)
+{
+ return bma150_set_reg_bits(bma150->client, bw, BMA150_BANDWIDTH_POS,
+ BMA150_BANDWIDTH_MSK, BMA150_BANDWIDTH_REG);
+}
+
+static int bma150_set_low_g_interrupt(struct bma150_data *bma150,
+ u8 enable, u8 hyst, u8 dur, u8 thres)
+{
+ int error;
+
+ error = bma150_set_reg_bits(bma150->client, hyst,
+ BMA150_LOW_G_HYST_POS, BMA150_LOW_G_HYST_MSK,
+ BMA150_LOW_G_HYST_REG);
+ if (error)
+ return error;
+
+ error = bma150_write_byte(bma150->client, BMA150_LOW_G_DUR_REG, dur);
+ if (error)
+ return error;
+
+ error = bma150_write_byte(bma150->client, BMA150_LOW_G_THRES_REG, thres);
+ if (error)
+ return error;
+
+ return bma150_set_reg_bits(bma150->client, !!enable,
+ BMA150_LOW_G_EN_POS, BMA150_LOW_G_EN_MSK,
+ BMA150_LOW_G_EN_REG);
+}
+
+static int bma150_set_high_g_interrupt(struct bma150_data *bma150,
+ u8 enable, u8 hyst, u8 dur, u8 thres)
+{
+ int error;
+
+ error = bma150_set_reg_bits(bma150->client, hyst,
+ BMA150_HIGH_G_HYST_POS, BMA150_HIGH_G_HYST_MSK,
+ BMA150_HIGH_G_HYST_REG);
+ if (error)
+ return error;
+
+ error = bma150_write_byte(bma150->client,
+ BMA150_HIGH_G_DUR_REG, dur);
+ if (error)
+ return error;
+
+ error = bma150_write_byte(bma150->client,
+ BMA150_HIGH_G_THRES_REG, thres);
+ if (error)
+ return error;
+
+ return bma150_set_reg_bits(bma150->client, !!enable,
+ BMA150_HIGH_G_EN_POS, BMA150_HIGH_G_EN_MSK,
+ BMA150_HIGH_G_EN_REG);
+}
+
+
+static int bma150_set_any_motion_interrupt(struct bma150_data *bma150,
+ u8 enable, u8 dur, u8 thres)
+{
+ int error;
+
+ error = bma150_set_reg_bits(bma150->client, dur,
+ BMA150_ANY_MOTION_DUR_POS,
+ BMA150_ANY_MOTION_DUR_MSK,
+ BMA150_ANY_MOTION_DUR_REG);
+ if (error)
+ return error;
+
+ error = bma150_write_byte(bma150->client,
+ BMA150_ANY_MOTION_THRES_REG, thres);
+ if (error)
+ return error;
+
+ error = bma150_set_reg_bits(bma150->client, !!enable,
+ BMA150_ADV_INT_EN_POS, BMA150_ADV_INT_EN_MSK,
+ BMA150_ADV_INT_EN_REG);
+ if (error)
+ return error;
+
+ return bma150_set_reg_bits(bma150->client, !!enable,
+ BMA150_ANY_MOTION_EN_POS,
+ BMA150_ANY_MOTION_EN_MSK,
+ BMA150_ANY_MOTION_EN_REG);
+}
+
+static void bma150_report_xyz(struct bma150_data *bma150)
+{
+ u8 data[BMA150_XYZ_DATA_SIZE];
+ s16 x, y, z;
+ s32 ret;
+
+ ret = i2c_smbus_read_i2c_block_data(bma150->client,
+ BMA150_ACC_X_LSB_REG, BMA150_XYZ_DATA_SIZE, data);
+ if (ret != BMA150_XYZ_DATA_SIZE)
+ return;
+
+ x = ((0xc0 & data[0]) >> 6) | (data[1] << 2);
+ y = ((0xc0 & data[2]) >> 6) | (data[3] << 2);
+ z = ((0xc0 & data[4]) >> 6) | (data[5] << 2);
+
+ x = sign_extend32(x, 9);
+ y = sign_extend32(y, 9);
+ z = sign_extend32(z, 9);
+
+ input_report_abs(bma150->input, ABS_X, x);
+ input_report_abs(bma150->input, ABS_Y, y);
+ input_report_abs(bma150->input, ABS_Z, z);
+ input_sync(bma150->input);
+}
+
+static irqreturn_t bma150_irq_thread(int irq, void *dev)
+{
+ bma150_report_xyz(dev);
+
+ return IRQ_HANDLED;
+}
+
+static void bma150_poll(struct input_dev *input)
+{
+ struct bma150_data *bma150 = input_get_drvdata(input);
+
+ bma150_report_xyz(bma150);
+}
+
+static int bma150_open(struct input_dev *input)
+{
+ struct bma150_data *bma150 = input_get_drvdata(input);
+ int error;
+
+ error = pm_runtime_get_sync(&bma150->client->dev);
+ if (error < 0 && error != -ENOSYS)
+ return error;
+
+ /*
+ * See if runtime PM woke up the device. If runtime PM
+ * is disabled we need to do it ourselves.
+ */
+ if (bma150->mode != BMA150_MODE_NORMAL) {
+ error = bma150_set_mode(bma150, BMA150_MODE_NORMAL);
+ if (error)
+ return error;
+ }
+
+ return 0;
+}
+
+static void bma150_close(struct input_dev *input)
+{
+ struct bma150_data *bma150 = input_get_drvdata(input);
+
+ pm_runtime_put_sync(&bma150->client->dev);
+
+ if (bma150->mode != BMA150_MODE_SLEEP)
+ bma150_set_mode(bma150, BMA150_MODE_SLEEP);
+}
+
+static int bma150_initialize(struct bma150_data *bma150,
+ const struct bma150_cfg *cfg)
+{
+ int error;
+
+ error = bma150_soft_reset(bma150);
+ if (error)
+ return error;
+
+ error = bma150_set_bandwidth(bma150, cfg->bandwidth);
+ if (error)
+ return error;
+
+ error = bma150_set_range(bma150, cfg->range);
+ if (error)
+ return error;
+
+ if (bma150->client->irq) {
+ error = bma150_set_any_motion_interrupt(bma150,
+ cfg->any_motion_int,
+ cfg->any_motion_dur,
+ cfg->any_motion_thres);
+ if (error)
+ return error;
+
+ error = bma150_set_high_g_interrupt(bma150,
+ cfg->hg_int, cfg->hg_hyst,
+ cfg->hg_dur, cfg->hg_thres);
+ if (error)
+ return error;
+
+ error = bma150_set_low_g_interrupt(bma150,
+ cfg->lg_int, cfg->lg_hyst,
+ cfg->lg_dur, cfg->lg_thres);
+ if (error)
+ return error;
+ }
+
+ return bma150_set_mode(bma150, BMA150_MODE_SLEEP);
+}
+
+static int bma150_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ const struct bma150_platform_data *pdata =
+ dev_get_platdata(&client->dev);
+ const struct bma150_cfg *cfg;
+ struct bma150_data *bma150;
+ struct input_dev *idev;
+ int chip_id;
+ int error;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(&client->dev, "i2c_check_functionality error\n");
+ return -EIO;
+ }
+
+ chip_id = i2c_smbus_read_byte_data(client, BMA150_CHIP_ID_REG);
+ if (chip_id != BMA150_CHIP_ID) {
+ dev_err(&client->dev, "BMA150 chip id error: %d\n", chip_id);
+ return -EINVAL;
+ }
+
+ bma150 = devm_kzalloc(&client->dev, sizeof(*bma150), GFP_KERNEL);
+ if (!bma150)
+ return -ENOMEM;
+
+ bma150->client = client;
+
+ if (pdata) {
+ if (pdata->irq_gpio_cfg) {
+ error = pdata->irq_gpio_cfg();
+ if (error) {
+ dev_err(&client->dev,
+ "IRQ GPIO conf. error %d, error %d\n",
+ client->irq, error);
+ return error;
+ }
+ }
+ cfg = &pdata->cfg;
+ } else {
+ cfg = &default_cfg;
+ }
+
+ error = bma150_initialize(bma150, cfg);
+ if (error)
+ return error;
+
+ idev = devm_input_allocate_device(&bma150->client->dev);
+ if (!idev)
+ return -ENOMEM;
+
+ input_set_drvdata(idev, bma150);
+ bma150->input = idev;
+
+ idev->name = BMA150_DRIVER;
+ idev->phys = BMA150_DRIVER "/input0";
+ idev->id.bustype = BUS_I2C;
+
+ idev->open = bma150_open;
+ idev->close = bma150_close;
+
+ input_set_abs_params(idev, ABS_X, ABSMIN_ACC_VAL, ABSMAX_ACC_VAL, 0, 0);
+ input_set_abs_params(idev, ABS_Y, ABSMIN_ACC_VAL, ABSMAX_ACC_VAL, 0, 0);
+ input_set_abs_params(idev, ABS_Z, ABSMIN_ACC_VAL, ABSMAX_ACC_VAL, 0, 0);
+
+ if (client->irq <= 0) {
+ error = input_setup_polling(idev, bma150_poll);
+ if (error)
+ return error;
+
+ input_set_poll_interval(idev, BMA150_POLL_INTERVAL);
+ input_set_min_poll_interval(idev, BMA150_POLL_MIN);
+ input_set_max_poll_interval(idev, BMA150_POLL_MAX);
+ }
+
+ error = input_register_device(idev);
+ if (error)
+ return error;
+
+ if (client->irq > 0) {
+ error = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, bma150_irq_thread,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ BMA150_DRIVER, bma150);
+ if (error) {
+ dev_err(&client->dev,
+ "irq request failed %d, error %d\n",
+ client->irq, error);
+ return error;
+ }
+ }
+
+ i2c_set_clientdata(client, bma150);
+
+ pm_runtime_enable(&client->dev);
+
+ return 0;
+}
+
+static void bma150_remove(struct i2c_client *client)
+{
+ pm_runtime_disable(&client->dev);
+}
+
+static int __maybe_unused bma150_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct bma150_data *bma150 = i2c_get_clientdata(client);
+
+ return bma150_set_mode(bma150, BMA150_MODE_SLEEP);
+}
+
+static int __maybe_unused bma150_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct bma150_data *bma150 = i2c_get_clientdata(client);
+
+ return bma150_set_mode(bma150, BMA150_MODE_NORMAL);
+}
+
+static UNIVERSAL_DEV_PM_OPS(bma150_pm, bma150_suspend, bma150_resume, NULL);
+
+static const struct i2c_device_id bma150_id[] = {
+ { "bma150", 0 },
+ { "smb380", 0 },
+ { "bma023", 0 },
+ { }
+};
+
+MODULE_DEVICE_TABLE(i2c, bma150_id);
+
+static struct i2c_driver bma150_driver = {
+ .driver = {
+ .name = BMA150_DRIVER,
+ .pm = &bma150_pm,
+ },
+ .class = I2C_CLASS_HWMON,
+ .id_table = bma150_id,
+ .probe = bma150_probe,
+ .remove = bma150_remove,
+};
+
+module_i2c_driver(bma150_driver);
+
+MODULE_AUTHOR("Albert Zhang <xu.zhang@bosch-sensortec.com>");
+MODULE_DESCRIPTION("BMA150 driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/cm109.c b/drivers/input/misc/cm109.c
new file mode 100644
index 000000000..728325a2d
--- /dev/null
+++ b/drivers/input/misc/cm109.c
@@ -0,0 +1,949 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for the VoIP USB phones with CM109 chipsets.
+ *
+ * Copyright (C) 2007 - 2008 Alfred E. Heggestad <aeh@db.org>
+ */
+
+/*
+ * Tested devices:
+ * - Komunikate KIP1000
+ * - Genius G-talk
+ * - Allied-Telesis Corega USBPH01
+ * - ...
+ *
+ * This driver is based on the yealink.c driver
+ *
+ * Thanks to:
+ * - Authors of yealink.c
+ * - Thomas Reitmayr
+ * - Oliver Neukum for good review comments and code
+ * - Shaun Jackman <sjackman@gmail.com> for Genius G-talk keymap
+ * - Dmitry Torokhov for valuable input and review
+ *
+ * Todo:
+ * - Read/write EEPROM
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/rwsem.h>
+#include <linux/usb/input.h>
+
+#define DRIVER_VERSION "20080805"
+#define DRIVER_AUTHOR "Alfred E. Heggestad"
+#define DRIVER_DESC "CM109 phone driver"
+
+static char *phone = "kip1000";
+module_param(phone, charp, S_IRUSR);
+MODULE_PARM_DESC(phone, "Phone name {kip1000, gtalk, usbph01, atcom}");
+
+enum {
+ /* HID Registers */
+ HID_IR0 = 0x00, /* Record/Playback-mute button, Volume up/down */
+ HID_IR1 = 0x01, /* GPI, generic registers or EEPROM_DATA0 */
+ HID_IR2 = 0x02, /* Generic registers or EEPROM_DATA1 */
+ HID_IR3 = 0x03, /* Generic registers or EEPROM_CTRL */
+ HID_OR0 = 0x00, /* Mapping control, buzzer, SPDIF (offset 0x04) */
+ HID_OR1 = 0x01, /* GPO - General Purpose Output */
+ HID_OR2 = 0x02, /* Set GPIO to input/output mode */
+ HID_OR3 = 0x03, /* SPDIF status channel or EEPROM_CTRL */
+
+ /* HID_IR0 */
+ RECORD_MUTE = 1 << 3,
+ PLAYBACK_MUTE = 1 << 2,
+ VOLUME_DOWN = 1 << 1,
+ VOLUME_UP = 1 << 0,
+
+ /* HID_OR0 */
+ /* bits 7-6
+ 0: HID_OR1-2 are used for GPO; HID_OR0, 3 are used for buzzer
+ and SPDIF
+ 1: HID_OR0-3 are used as generic HID registers
+ 2: Values written to HID_OR0-3 are also mapped to MCU_CTRL,
+ EEPROM_DATA0-1, EEPROM_CTRL (see Note)
+ 3: Reserved
+ */
+ HID_OR_GPO_BUZ_SPDIF = 0 << 6,
+ HID_OR_GENERIC_HID_REG = 1 << 6,
+ HID_OR_MAP_MCU_EEPROM = 2 << 6,
+
+ BUZZER_ON = 1 << 5,
+
+ /* up to 256 normal keys, up to 15 special key combinations */
+ KEYMAP_SIZE = 256 + 15,
+};
+
+/* CM109 protocol packet */
+struct cm109_ctl_packet {
+ u8 byte[4];
+} __attribute__ ((packed));
+
+enum { USB_PKT_LEN = sizeof(struct cm109_ctl_packet) };
+
+/* CM109 device structure */
+struct cm109_dev {
+ struct input_dev *idev; /* input device */
+ struct usb_device *udev; /* usb device */
+ struct usb_interface *intf;
+
+ /* irq input channel */
+ struct cm109_ctl_packet *irq_data;
+ dma_addr_t irq_dma;
+ struct urb *urb_irq;
+
+ /* control output channel */
+ struct cm109_ctl_packet *ctl_data;
+ dma_addr_t ctl_dma;
+ struct usb_ctrlrequest *ctl_req;
+ struct urb *urb_ctl;
+ /*
+ * The 3 bitfields below are protected by ctl_submit_lock.
+ * They have to be separate since they are accessed from IRQ
+ * context.
+ */
+ unsigned irq_urb_pending:1; /* irq_urb is in flight */
+ unsigned ctl_urb_pending:1; /* ctl_urb is in flight */
+ unsigned buzzer_pending:1; /* need to issue buzz command */
+ spinlock_t ctl_submit_lock;
+
+ unsigned char buzzer_state; /* on/off */
+
+ /* flags */
+ unsigned open:1;
+ unsigned resetting:1;
+ unsigned shutdown:1;
+
+ /* This mutex protects writes to the above flags */
+ struct mutex pm_mutex;
+
+ unsigned short keymap[KEYMAP_SIZE];
+
+ char phys[64]; /* physical device path */
+ int key_code; /* last reported key */
+ int keybit; /* 0=new scan 1,2,4,8=scan columns */
+ u8 gpi; /* Cached value of GPI (high nibble) */
+};
+
+/******************************************************************************
+ * CM109 key interface
+ *****************************************************************************/
+
+static unsigned short special_keymap(int code)
+{
+ if (code > 0xff) {
+ switch (code - 0xff) {
+ case RECORD_MUTE: return KEY_MICMUTE;
+ case PLAYBACK_MUTE: return KEY_MUTE;
+ case VOLUME_DOWN: return KEY_VOLUMEDOWN;
+ case VOLUME_UP: return KEY_VOLUMEUP;
+ }
+ }
+ return KEY_RESERVED;
+}
+
+/* Map device buttons to internal key events.
+ *
+ * The "up" and "down" keys, are symbolised by arrows on the button.
+ * The "pickup" and "hangup" keys are symbolised by a green and red phone
+ * on the button.
+
+ Komunikate KIP1000 Keyboard Matrix
+
+ -> -- 1 -- 2 -- 3 --> GPI pin 4 (0x10)
+ | | | |
+ <- -- 4 -- 5 -- 6 --> GPI pin 5 (0x20)
+ | | | |
+ END - 7 -- 8 -- 9 --> GPI pin 6 (0x40)
+ | | | |
+ OK -- * -- 0 -- # --> GPI pin 7 (0x80)
+ | | | |
+
+ /|\ /|\ /|\ /|\
+ | | | |
+GPO
+pin: 3 2 1 0
+ 0x8 0x4 0x2 0x1
+
+ */
+static unsigned short keymap_kip1000(int scancode)
+{
+ switch (scancode) { /* phone key: */
+ case 0x82: return KEY_NUMERIC_0; /* 0 */
+ case 0x14: return KEY_NUMERIC_1; /* 1 */
+ case 0x12: return KEY_NUMERIC_2; /* 2 */
+ case 0x11: return KEY_NUMERIC_3; /* 3 */
+ case 0x24: return KEY_NUMERIC_4; /* 4 */
+ case 0x22: return KEY_NUMERIC_5; /* 5 */
+ case 0x21: return KEY_NUMERIC_6; /* 6 */
+ case 0x44: return KEY_NUMERIC_7; /* 7 */
+ case 0x42: return KEY_NUMERIC_8; /* 8 */
+ case 0x41: return KEY_NUMERIC_9; /* 9 */
+ case 0x81: return KEY_NUMERIC_POUND; /* # */
+ case 0x84: return KEY_NUMERIC_STAR; /* * */
+ case 0x88: return KEY_ENTER; /* pickup */
+ case 0x48: return KEY_ESC; /* hangup */
+ case 0x28: return KEY_LEFT; /* IN */
+ case 0x18: return KEY_RIGHT; /* OUT */
+ default: return special_keymap(scancode);
+ }
+}
+
+/*
+ Contributed by Shaun Jackman <sjackman@gmail.com>
+
+ Genius G-Talk keyboard matrix
+ 0 1 2 3
+ 4: 0 4 8 Talk
+ 5: 1 5 9 End
+ 6: 2 6 # Up
+ 7: 3 7 * Down
+*/
+static unsigned short keymap_gtalk(int scancode)
+{
+ switch (scancode) {
+ case 0x11: return KEY_NUMERIC_0;
+ case 0x21: return KEY_NUMERIC_1;
+ case 0x41: return KEY_NUMERIC_2;
+ case 0x81: return KEY_NUMERIC_3;
+ case 0x12: return KEY_NUMERIC_4;
+ case 0x22: return KEY_NUMERIC_5;
+ case 0x42: return KEY_NUMERIC_6;
+ case 0x82: return KEY_NUMERIC_7;
+ case 0x14: return KEY_NUMERIC_8;
+ case 0x24: return KEY_NUMERIC_9;
+ case 0x44: return KEY_NUMERIC_POUND; /* # */
+ case 0x84: return KEY_NUMERIC_STAR; /* * */
+ case 0x18: return KEY_ENTER; /* Talk (green handset) */
+ case 0x28: return KEY_ESC; /* End (red handset) */
+ case 0x48: return KEY_UP; /* Menu up (rocker switch) */
+ case 0x88: return KEY_DOWN; /* Menu down (rocker switch) */
+ default: return special_keymap(scancode);
+ }
+}
+
+/*
+ * Keymap for Allied-Telesis Corega USBPH01
+ * http://www.alliedtelesis-corega.com/2/1344/1437/1360/chprd.html
+ *
+ * Contributed by july@nat.bg
+ */
+static unsigned short keymap_usbph01(int scancode)
+{
+ switch (scancode) {
+ case 0x11: return KEY_NUMERIC_0; /* 0 */
+ case 0x21: return KEY_NUMERIC_1; /* 1 */
+ case 0x41: return KEY_NUMERIC_2; /* 2 */
+ case 0x81: return KEY_NUMERIC_3; /* 3 */
+ case 0x12: return KEY_NUMERIC_4; /* 4 */
+ case 0x22: return KEY_NUMERIC_5; /* 5 */
+ case 0x42: return KEY_NUMERIC_6; /* 6 */
+ case 0x82: return KEY_NUMERIC_7; /* 7 */
+ case 0x14: return KEY_NUMERIC_8; /* 8 */
+ case 0x24: return KEY_NUMERIC_9; /* 9 */
+ case 0x44: return KEY_NUMERIC_POUND; /* # */
+ case 0x84: return KEY_NUMERIC_STAR; /* * */
+ case 0x18: return KEY_ENTER; /* pickup */
+ case 0x28: return KEY_ESC; /* hangup */
+ case 0x48: return KEY_LEFT; /* IN */
+ case 0x88: return KEY_RIGHT; /* OUT */
+ default: return special_keymap(scancode);
+ }
+}
+
+/*
+ * Keymap for ATCom AU-100
+ * http://www.atcom.cn/products.html
+ * http://www.packetizer.com/products/au100/
+ * http://www.voip-info.org/wiki/view/AU-100
+ *
+ * Contributed by daniel@gimpelevich.san-francisco.ca.us
+ */
+static unsigned short keymap_atcom(int scancode)
+{
+ switch (scancode) { /* phone key: */
+ case 0x82: return KEY_NUMERIC_0; /* 0 */
+ case 0x11: return KEY_NUMERIC_1; /* 1 */
+ case 0x12: return KEY_NUMERIC_2; /* 2 */
+ case 0x14: return KEY_NUMERIC_3; /* 3 */
+ case 0x21: return KEY_NUMERIC_4; /* 4 */
+ case 0x22: return KEY_NUMERIC_5; /* 5 */
+ case 0x24: return KEY_NUMERIC_6; /* 6 */
+ case 0x41: return KEY_NUMERIC_7; /* 7 */
+ case 0x42: return KEY_NUMERIC_8; /* 8 */
+ case 0x44: return KEY_NUMERIC_9; /* 9 */
+ case 0x84: return KEY_NUMERIC_POUND; /* # */
+ case 0x81: return KEY_NUMERIC_STAR; /* * */
+ case 0x18: return KEY_ENTER; /* pickup */
+ case 0x28: return KEY_ESC; /* hangup */
+ case 0x48: return KEY_LEFT; /* left arrow */
+ case 0x88: return KEY_RIGHT; /* right arrow */
+ default: return special_keymap(scancode);
+ }
+}
+
+static unsigned short (*keymap)(int) = keymap_kip1000;
+
+/*
+ * Completes a request by converting the data into events for the
+ * input subsystem.
+ */
+static void report_key(struct cm109_dev *dev, int key)
+{
+ struct input_dev *idev = dev->idev;
+
+ if (dev->key_code >= 0) {
+ /* old key up */
+ input_report_key(idev, dev->key_code, 0);
+ }
+
+ dev->key_code = key;
+ if (key >= 0) {
+ /* new valid key */
+ input_report_key(idev, key, 1);
+ }
+
+ input_sync(idev);
+}
+
+/*
+ * Converts data of special key presses (volume, mute) into events
+ * for the input subsystem, sends press-n-release for mute keys.
+ */
+static void cm109_report_special(struct cm109_dev *dev)
+{
+ static const u8 autorelease = RECORD_MUTE | PLAYBACK_MUTE;
+ struct input_dev *idev = dev->idev;
+ u8 data = dev->irq_data->byte[HID_IR0];
+ unsigned short keycode;
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ keycode = dev->keymap[0xff + BIT(i)];
+ if (keycode == KEY_RESERVED)
+ continue;
+
+ input_report_key(idev, keycode, data & BIT(i));
+ if (data & autorelease & BIT(i)) {
+ input_sync(idev);
+ input_report_key(idev, keycode, 0);
+ }
+ }
+ input_sync(idev);
+}
+
+/******************************************************************************
+ * CM109 usb communication interface
+ *****************************************************************************/
+
+static void cm109_submit_buzz_toggle(struct cm109_dev *dev)
+{
+ int error;
+
+ if (dev->buzzer_state)
+ dev->ctl_data->byte[HID_OR0] |= BUZZER_ON;
+ else
+ dev->ctl_data->byte[HID_OR0] &= ~BUZZER_ON;
+
+ error = usb_submit_urb(dev->urb_ctl, GFP_ATOMIC);
+ if (error)
+ dev_err(&dev->intf->dev,
+ "%s: usb_submit_urb (urb_ctl) failed %d\n",
+ __func__, error);
+}
+
+/*
+ * IRQ handler
+ */
+static void cm109_urb_irq_callback(struct urb *urb)
+{
+ struct cm109_dev *dev = urb->context;
+ const int status = urb->status;
+ int error;
+ unsigned long flags;
+
+ dev_dbg(&dev->intf->dev, "### URB IRQ: [0x%02x 0x%02x 0x%02x 0x%02x] keybit=0x%02x\n",
+ dev->irq_data->byte[0],
+ dev->irq_data->byte[1],
+ dev->irq_data->byte[2],
+ dev->irq_data->byte[3],
+ dev->keybit);
+
+ if (status) {
+ if (status == -ESHUTDOWN)
+ return;
+ dev_err_ratelimited(&dev->intf->dev, "%s: urb status %d\n",
+ __func__, status);
+ goto out;
+ }
+
+ /* Special keys */
+ cm109_report_special(dev);
+
+ /* Scan key column */
+ if (dev->keybit == 0xf) {
+
+ /* Any changes ? */
+ if ((dev->gpi & 0xf0) == (dev->irq_data->byte[HID_IR1] & 0xf0))
+ goto out;
+
+ dev->gpi = dev->irq_data->byte[HID_IR1] & 0xf0;
+ dev->keybit = 0x1;
+ } else {
+ report_key(dev, dev->keymap[dev->irq_data->byte[HID_IR1]]);
+
+ dev->keybit <<= 1;
+ if (dev->keybit > 0x8)
+ dev->keybit = 0xf;
+ }
+
+ out:
+
+ spin_lock_irqsave(&dev->ctl_submit_lock, flags);
+
+ dev->irq_urb_pending = 0;
+
+ if (likely(!dev->shutdown)) {
+
+ if (dev->buzzer_state)
+ dev->ctl_data->byte[HID_OR0] |= BUZZER_ON;
+ else
+ dev->ctl_data->byte[HID_OR0] &= ~BUZZER_ON;
+
+ dev->ctl_data->byte[HID_OR1] = dev->keybit;
+ dev->ctl_data->byte[HID_OR2] = dev->keybit;
+
+ dev->buzzer_pending = 0;
+ dev->ctl_urb_pending = 1;
+
+ error = usb_submit_urb(dev->urb_ctl, GFP_ATOMIC);
+ if (error)
+ dev_err(&dev->intf->dev,
+ "%s: usb_submit_urb (urb_ctl) failed %d\n",
+ __func__, error);
+ }
+
+ spin_unlock_irqrestore(&dev->ctl_submit_lock, flags);
+}
+
+static void cm109_urb_ctl_callback(struct urb *urb)
+{
+ struct cm109_dev *dev = urb->context;
+ const int status = urb->status;
+ int error;
+ unsigned long flags;
+
+ dev_dbg(&dev->intf->dev, "### URB CTL: [0x%02x 0x%02x 0x%02x 0x%02x]\n",
+ dev->ctl_data->byte[0],
+ dev->ctl_data->byte[1],
+ dev->ctl_data->byte[2],
+ dev->ctl_data->byte[3]);
+
+ if (status) {
+ if (status == -ESHUTDOWN)
+ return;
+ dev_err_ratelimited(&dev->intf->dev, "%s: urb status %d\n",
+ __func__, status);
+ }
+
+ spin_lock_irqsave(&dev->ctl_submit_lock, flags);
+
+ dev->ctl_urb_pending = 0;
+
+ if (likely(!dev->shutdown)) {
+
+ if (dev->buzzer_pending || status) {
+ dev->buzzer_pending = 0;
+ dev->ctl_urb_pending = 1;
+ cm109_submit_buzz_toggle(dev);
+ } else if (likely(!dev->irq_urb_pending)) {
+ /* ask for key data */
+ dev->irq_urb_pending = 1;
+ error = usb_submit_urb(dev->urb_irq, GFP_ATOMIC);
+ if (error)
+ dev_err(&dev->intf->dev,
+ "%s: usb_submit_urb (urb_irq) failed %d\n",
+ __func__, error);
+ }
+ }
+
+ spin_unlock_irqrestore(&dev->ctl_submit_lock, flags);
+}
+
+static void cm109_toggle_buzzer_async(struct cm109_dev *dev)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->ctl_submit_lock, flags);
+
+ if (dev->ctl_urb_pending) {
+ /* URB completion will resubmit */
+ dev->buzzer_pending = 1;
+ } else {
+ dev->ctl_urb_pending = 1;
+ cm109_submit_buzz_toggle(dev);
+ }
+
+ spin_unlock_irqrestore(&dev->ctl_submit_lock, flags);
+}
+
+static void cm109_toggle_buzzer_sync(struct cm109_dev *dev, int on)
+{
+ int error;
+
+ if (on)
+ dev->ctl_data->byte[HID_OR0] |= BUZZER_ON;
+ else
+ dev->ctl_data->byte[HID_OR0] &= ~BUZZER_ON;
+
+ error = usb_control_msg(dev->udev,
+ usb_sndctrlpipe(dev->udev, 0),
+ dev->ctl_req->bRequest,
+ dev->ctl_req->bRequestType,
+ le16_to_cpu(dev->ctl_req->wValue),
+ le16_to_cpu(dev->ctl_req->wIndex),
+ dev->ctl_data,
+ USB_PKT_LEN, USB_CTRL_SET_TIMEOUT);
+ if (error < 0 && error != -EINTR)
+ dev_err(&dev->intf->dev, "%s: usb_control_msg() failed %d\n",
+ __func__, error);
+}
+
+static void cm109_stop_traffic(struct cm109_dev *dev)
+{
+ dev->shutdown = 1;
+ /*
+ * Make sure other CPUs see this
+ */
+ smp_wmb();
+
+ usb_kill_urb(dev->urb_ctl);
+ usb_kill_urb(dev->urb_irq);
+
+ cm109_toggle_buzzer_sync(dev, 0);
+
+ dev->shutdown = 0;
+ smp_wmb();
+}
+
+static void cm109_restore_state(struct cm109_dev *dev)
+{
+ if (dev->open) {
+ /*
+ * Restore buzzer state.
+ * This will also kick regular URB submission
+ */
+ cm109_toggle_buzzer_async(dev);
+ }
+}
+
+/******************************************************************************
+ * input event interface
+ *****************************************************************************/
+
+static int cm109_input_open(struct input_dev *idev)
+{
+ struct cm109_dev *dev = input_get_drvdata(idev);
+ int error;
+
+ error = usb_autopm_get_interface(dev->intf);
+ if (error < 0) {
+ dev_err(&idev->dev, "%s - cannot autoresume, result %d\n",
+ __func__, error);
+ return error;
+ }
+
+ mutex_lock(&dev->pm_mutex);
+
+ dev->buzzer_state = 0;
+ dev->key_code = -1; /* no keys pressed */
+ dev->keybit = 0xf;
+
+ /* issue INIT */
+ dev->ctl_data->byte[HID_OR0] = HID_OR_GPO_BUZ_SPDIF;
+ dev->ctl_data->byte[HID_OR1] = dev->keybit;
+ dev->ctl_data->byte[HID_OR2] = dev->keybit;
+ dev->ctl_data->byte[HID_OR3] = 0x00;
+
+ dev->ctl_urb_pending = 1;
+ error = usb_submit_urb(dev->urb_ctl, GFP_KERNEL);
+ if (error) {
+ dev->ctl_urb_pending = 0;
+ dev_err(&dev->intf->dev, "%s: usb_submit_urb (urb_ctl) failed %d\n",
+ __func__, error);
+ } else {
+ dev->open = 1;
+ }
+
+ mutex_unlock(&dev->pm_mutex);
+
+ if (error)
+ usb_autopm_put_interface(dev->intf);
+
+ return error;
+}
+
+static void cm109_input_close(struct input_dev *idev)
+{
+ struct cm109_dev *dev = input_get_drvdata(idev);
+
+ mutex_lock(&dev->pm_mutex);
+
+ /*
+ * Once we are here event delivery is stopped so we
+ * don't need to worry about someone starting buzzer
+ * again
+ */
+ cm109_stop_traffic(dev);
+ dev->open = 0;
+
+ mutex_unlock(&dev->pm_mutex);
+
+ usb_autopm_put_interface(dev->intf);
+}
+
+static int cm109_input_ev(struct input_dev *idev, unsigned int type,
+ unsigned int code, int value)
+{
+ struct cm109_dev *dev = input_get_drvdata(idev);
+
+ dev_dbg(&dev->intf->dev,
+ "input_ev: type=%u code=%u value=%d\n", type, code, value);
+
+ if (type != EV_SND)
+ return -EINVAL;
+
+ switch (code) {
+ case SND_TONE:
+ case SND_BELL:
+ dev->buzzer_state = !!value;
+ if (!dev->resetting)
+ cm109_toggle_buzzer_async(dev);
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+
+/******************************************************************************
+ * Linux interface and usb initialisation
+ *****************************************************************************/
+
+struct driver_info {
+ char *name;
+};
+
+static const struct driver_info info_cm109 = {
+ .name = "CM109 USB driver",
+};
+
+enum {
+ VENDOR_ID = 0x0d8c, /* C-Media Electronics */
+ PRODUCT_ID_CM109 = 0x000e, /* CM109 defines range 0x0008 - 0x000f */
+};
+
+/* table of devices that work with this driver */
+static const struct usb_device_id cm109_usb_table[] = {
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+ USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = VENDOR_ID,
+ .idProduct = PRODUCT_ID_CM109,
+ .bInterfaceClass = USB_CLASS_HID,
+ .bInterfaceSubClass = 0,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t) &info_cm109
+ },
+ /* you can add more devices here with product ID 0x0008 - 0x000f */
+ { }
+};
+
+static void cm109_usb_cleanup(struct cm109_dev *dev)
+{
+ kfree(dev->ctl_req);
+ usb_free_coherent(dev->udev, USB_PKT_LEN, dev->ctl_data, dev->ctl_dma);
+ usb_free_coherent(dev->udev, USB_PKT_LEN, dev->irq_data, dev->irq_dma);
+
+ usb_free_urb(dev->urb_irq); /* parameter validation in core/urb */
+ usb_free_urb(dev->urb_ctl); /* parameter validation in core/urb */
+ kfree(dev);
+}
+
+static void cm109_usb_disconnect(struct usb_interface *interface)
+{
+ struct cm109_dev *dev = usb_get_intfdata(interface);
+
+ usb_set_intfdata(interface, NULL);
+ input_unregister_device(dev->idev);
+ cm109_usb_cleanup(dev);
+}
+
+static int cm109_usb_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev(intf);
+ struct driver_info *nfo = (struct driver_info *)id->driver_info;
+ struct usb_host_interface *interface;
+ struct usb_endpoint_descriptor *endpoint;
+ struct cm109_dev *dev;
+ struct input_dev *input_dev = NULL;
+ int ret, pipe, i;
+ int error = -ENOMEM;
+
+ interface = intf->cur_altsetting;
+
+ if (interface->desc.bNumEndpoints < 1)
+ return -ENODEV;
+
+ endpoint = &interface->endpoint[0].desc;
+
+ if (!usb_endpoint_is_int_in(endpoint))
+ return -ENODEV;
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ spin_lock_init(&dev->ctl_submit_lock);
+ mutex_init(&dev->pm_mutex);
+
+ dev->udev = udev;
+ dev->intf = intf;
+
+ dev->idev = input_dev = input_allocate_device();
+ if (!input_dev)
+ goto err_out;
+
+ /* allocate usb buffers */
+ dev->irq_data = usb_alloc_coherent(udev, USB_PKT_LEN,
+ GFP_KERNEL, &dev->irq_dma);
+ if (!dev->irq_data)
+ goto err_out;
+
+ dev->ctl_data = usb_alloc_coherent(udev, USB_PKT_LEN,
+ GFP_KERNEL, &dev->ctl_dma);
+ if (!dev->ctl_data)
+ goto err_out;
+
+ dev->ctl_req = kmalloc(sizeof(*(dev->ctl_req)), GFP_KERNEL);
+ if (!dev->ctl_req)
+ goto err_out;
+
+ /* allocate urb structures */
+ dev->urb_irq = usb_alloc_urb(0, GFP_KERNEL);
+ if (!dev->urb_irq)
+ goto err_out;
+
+ dev->urb_ctl = usb_alloc_urb(0, GFP_KERNEL);
+ if (!dev->urb_ctl)
+ goto err_out;
+
+ /* get a handle to the interrupt data pipe */
+ pipe = usb_rcvintpipe(udev, endpoint->bEndpointAddress);
+ ret = usb_maxpacket(udev, pipe);
+ if (ret != USB_PKT_LEN)
+ dev_err(&intf->dev, "invalid payload size %d, expected %d\n",
+ ret, USB_PKT_LEN);
+
+ /* initialise irq urb */
+ usb_fill_int_urb(dev->urb_irq, udev, pipe, dev->irq_data,
+ USB_PKT_LEN,
+ cm109_urb_irq_callback, dev, endpoint->bInterval);
+ dev->urb_irq->transfer_dma = dev->irq_dma;
+ dev->urb_irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ dev->urb_irq->dev = udev;
+
+ /* initialise ctl urb */
+ dev->ctl_req->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE |
+ USB_DIR_OUT;
+ dev->ctl_req->bRequest = USB_REQ_SET_CONFIGURATION;
+ dev->ctl_req->wValue = cpu_to_le16(0x200);
+ dev->ctl_req->wIndex = cpu_to_le16(interface->desc.bInterfaceNumber);
+ dev->ctl_req->wLength = cpu_to_le16(USB_PKT_LEN);
+
+ usb_fill_control_urb(dev->urb_ctl, udev, usb_sndctrlpipe(udev, 0),
+ (void *)dev->ctl_req, dev->ctl_data, USB_PKT_LEN,
+ cm109_urb_ctl_callback, dev);
+ dev->urb_ctl->transfer_dma = dev->ctl_dma;
+ dev->urb_ctl->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ dev->urb_ctl->dev = udev;
+
+ /* find out the physical bus location */
+ usb_make_path(udev, dev->phys, sizeof(dev->phys));
+ strlcat(dev->phys, "/input0", sizeof(dev->phys));
+
+ /* register settings for the input device */
+ input_dev->name = nfo->name;
+ input_dev->phys = dev->phys;
+ usb_to_input_id(udev, &input_dev->id);
+ input_dev->dev.parent = &intf->dev;
+
+ input_set_drvdata(input_dev, dev);
+ input_dev->open = cm109_input_open;
+ input_dev->close = cm109_input_close;
+ input_dev->event = cm109_input_ev;
+
+ input_dev->keycode = dev->keymap;
+ input_dev->keycodesize = sizeof(unsigned char);
+ input_dev->keycodemax = ARRAY_SIZE(dev->keymap);
+
+ input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_SND);
+ input_dev->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE);
+
+ /* register available key events */
+ for (i = 0; i < KEYMAP_SIZE; i++) {
+ unsigned short k = keymap(i);
+ dev->keymap[i] = k;
+ __set_bit(k, input_dev->keybit);
+ }
+ __clear_bit(KEY_RESERVED, input_dev->keybit);
+
+ error = input_register_device(dev->idev);
+ if (error)
+ goto err_out;
+
+ usb_set_intfdata(intf, dev);
+
+ return 0;
+
+ err_out:
+ input_free_device(input_dev);
+ cm109_usb_cleanup(dev);
+ return error;
+}
+
+static int cm109_usb_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ struct cm109_dev *dev = usb_get_intfdata(intf);
+
+ dev_info(&intf->dev, "cm109: usb_suspend (event=%d)\n", message.event);
+
+ mutex_lock(&dev->pm_mutex);
+ cm109_stop_traffic(dev);
+ mutex_unlock(&dev->pm_mutex);
+
+ return 0;
+}
+
+static int cm109_usb_resume(struct usb_interface *intf)
+{
+ struct cm109_dev *dev = usb_get_intfdata(intf);
+
+ dev_info(&intf->dev, "cm109: usb_resume\n");
+
+ mutex_lock(&dev->pm_mutex);
+ cm109_restore_state(dev);
+ mutex_unlock(&dev->pm_mutex);
+
+ return 0;
+}
+
+static int cm109_usb_pre_reset(struct usb_interface *intf)
+{
+ struct cm109_dev *dev = usb_get_intfdata(intf);
+
+ mutex_lock(&dev->pm_mutex);
+
+ /*
+ * Make sure input events don't try to toggle buzzer
+ * while we are resetting
+ */
+ dev->resetting = 1;
+ smp_wmb();
+
+ cm109_stop_traffic(dev);
+
+ return 0;
+}
+
+static int cm109_usb_post_reset(struct usb_interface *intf)
+{
+ struct cm109_dev *dev = usb_get_intfdata(intf);
+
+ dev->resetting = 0;
+ smp_wmb();
+
+ cm109_restore_state(dev);
+
+ mutex_unlock(&dev->pm_mutex);
+
+ return 0;
+}
+
+static struct usb_driver cm109_driver = {
+ .name = "cm109",
+ .probe = cm109_usb_probe,
+ .disconnect = cm109_usb_disconnect,
+ .suspend = cm109_usb_suspend,
+ .resume = cm109_usb_resume,
+ .reset_resume = cm109_usb_resume,
+ .pre_reset = cm109_usb_pre_reset,
+ .post_reset = cm109_usb_post_reset,
+ .id_table = cm109_usb_table,
+ .supports_autosuspend = 1,
+};
+
+static int __init cm109_select_keymap(void)
+{
+ /* Load the phone keymap */
+ if (!strcasecmp(phone, "kip1000")) {
+ keymap = keymap_kip1000;
+ printk(KERN_INFO KBUILD_MODNAME ": "
+ "Keymap for Komunikate KIP1000 phone loaded\n");
+ } else if (!strcasecmp(phone, "gtalk")) {
+ keymap = keymap_gtalk;
+ printk(KERN_INFO KBUILD_MODNAME ": "
+ "Keymap for Genius G-talk phone loaded\n");
+ } else if (!strcasecmp(phone, "usbph01")) {
+ keymap = keymap_usbph01;
+ printk(KERN_INFO KBUILD_MODNAME ": "
+ "Keymap for Allied-Telesis Corega USBPH01 phone loaded\n");
+ } else if (!strcasecmp(phone, "atcom")) {
+ keymap = keymap_atcom;
+ printk(KERN_INFO KBUILD_MODNAME ": "
+ "Keymap for ATCom AU-100 phone loaded\n");
+ } else {
+ printk(KERN_ERR KBUILD_MODNAME ": "
+ "Unsupported phone: %s\n", phone);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int __init cm109_init(void)
+{
+ int err;
+
+ err = cm109_select_keymap();
+ if (err)
+ return err;
+
+ err = usb_register(&cm109_driver);
+ if (err)
+ return err;
+
+ printk(KERN_INFO KBUILD_MODNAME ": "
+ DRIVER_DESC ": " DRIVER_VERSION " (C) " DRIVER_AUTHOR "\n");
+
+ return 0;
+}
+
+static void __exit cm109_exit(void)
+{
+ usb_deregister(&cm109_driver);
+}
+
+module_init(cm109_init);
+module_exit(cm109_exit);
+
+MODULE_DEVICE_TABLE(usb, cm109_usb_table);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/cma3000_d0x.c b/drivers/input/misc/cma3000_d0x.c
new file mode 100644
index 000000000..e6feb73bb
--- /dev/null
+++ b/drivers/input/misc/cma3000_d0x.c
@@ -0,0 +1,388 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * VTI CMA3000_D0x Accelerometer driver
+ *
+ * Copyright (C) 2010 Texas Instruments
+ * Author: Hemanth V <hemanthv@ti.com>
+ */
+
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/input/cma3000.h>
+#include <linux/module.h>
+
+#include "cma3000_d0x.h"
+
+#define CMA3000_WHOAMI 0x00
+#define CMA3000_REVID 0x01
+#define CMA3000_CTRL 0x02
+#define CMA3000_STATUS 0x03
+#define CMA3000_RSTR 0x04
+#define CMA3000_INTSTATUS 0x05
+#define CMA3000_DOUTX 0x06
+#define CMA3000_DOUTY 0x07
+#define CMA3000_DOUTZ 0x08
+#define CMA3000_MDTHR 0x09
+#define CMA3000_MDFFTMR 0x0A
+#define CMA3000_FFTHR 0x0B
+
+#define CMA3000_RANGE2G (1 << 7)
+#define CMA3000_RANGE8G (0 << 7)
+#define CMA3000_BUSI2C (0 << 4)
+#define CMA3000_MODEMASK (7 << 1)
+#define CMA3000_GRANGEMASK (1 << 7)
+
+#define CMA3000_STATUS_PERR 1
+#define CMA3000_INTSTATUS_FFDET (1 << 2)
+
+/* Settling time delay in ms */
+#define CMA3000_SETDELAY 30
+
+/* Delay for clearing interrupt in us */
+#define CMA3000_INTDELAY 44
+
+
+/*
+ * Bit weights in mg for bit 0, other bits need
+ * multiply factor 2^n. Eight bit is the sign bit.
+ */
+#define BIT_TO_2G 18
+#define BIT_TO_8G 71
+
+struct cma3000_accl_data {
+ const struct cma3000_bus_ops *bus_ops;
+ const struct cma3000_platform_data *pdata;
+
+ struct device *dev;
+ struct input_dev *input_dev;
+
+ int bit_to_mg;
+ int irq;
+
+ int g_range;
+ u8 mode;
+
+ struct mutex mutex;
+ bool opened;
+ bool suspended;
+};
+
+#define CMA3000_READ(data, reg, msg) \
+ (data->bus_ops->read(data->dev, reg, msg))
+#define CMA3000_SET(data, reg, val, msg) \
+ ((data)->bus_ops->write(data->dev, reg, val, msg))
+
+/*
+ * Conversion for each of the eight modes to g, depending
+ * on G range i.e 2G or 8G. Some modes always operate in
+ * 8G.
+ */
+
+static int mode_to_mg[8][2] = {
+ { 0, 0 },
+ { BIT_TO_8G, BIT_TO_2G },
+ { BIT_TO_8G, BIT_TO_2G },
+ { BIT_TO_8G, BIT_TO_8G },
+ { BIT_TO_8G, BIT_TO_8G },
+ { BIT_TO_8G, BIT_TO_2G },
+ { BIT_TO_8G, BIT_TO_2G },
+ { 0, 0},
+};
+
+static void decode_mg(struct cma3000_accl_data *data, int *datax,
+ int *datay, int *dataz)
+{
+ /* Data in 2's complement, convert to mg */
+ *datax = ((s8)*datax) * data->bit_to_mg;
+ *datay = ((s8)*datay) * data->bit_to_mg;
+ *dataz = ((s8)*dataz) * data->bit_to_mg;
+}
+
+static irqreturn_t cma3000_thread_irq(int irq, void *dev_id)
+{
+ struct cma3000_accl_data *data = dev_id;
+ int datax, datay, dataz, intr_status;
+ u8 ctrl, mode, range;
+
+ intr_status = CMA3000_READ(data, CMA3000_INTSTATUS, "interrupt status");
+ if (intr_status < 0)
+ return IRQ_NONE;
+
+ /* Check if free fall is detected, report immediately */
+ if (intr_status & CMA3000_INTSTATUS_FFDET) {
+ input_report_abs(data->input_dev, ABS_MISC, 1);
+ input_sync(data->input_dev);
+ } else {
+ input_report_abs(data->input_dev, ABS_MISC, 0);
+ }
+
+ datax = CMA3000_READ(data, CMA3000_DOUTX, "X");
+ datay = CMA3000_READ(data, CMA3000_DOUTY, "Y");
+ dataz = CMA3000_READ(data, CMA3000_DOUTZ, "Z");
+
+ ctrl = CMA3000_READ(data, CMA3000_CTRL, "ctrl");
+ mode = (ctrl & CMA3000_MODEMASK) >> 1;
+ range = (ctrl & CMA3000_GRANGEMASK) >> 7;
+
+ data->bit_to_mg = mode_to_mg[mode][range];
+
+ /* Interrupt not for this device */
+ if (data->bit_to_mg == 0)
+ return IRQ_NONE;
+
+ /* Decode register values to milli g */
+ decode_mg(data, &datax, &datay, &dataz);
+
+ input_report_abs(data->input_dev, ABS_X, datax);
+ input_report_abs(data->input_dev, ABS_Y, datay);
+ input_report_abs(data->input_dev, ABS_Z, dataz);
+ input_sync(data->input_dev);
+
+ return IRQ_HANDLED;
+}
+
+static int cma3000_reset(struct cma3000_accl_data *data)
+{
+ int val;
+
+ /* Reset sequence */
+ CMA3000_SET(data, CMA3000_RSTR, 0x02, "Reset");
+ CMA3000_SET(data, CMA3000_RSTR, 0x0A, "Reset");
+ CMA3000_SET(data, CMA3000_RSTR, 0x04, "Reset");
+
+ /* Settling time delay */
+ mdelay(10);
+
+ val = CMA3000_READ(data, CMA3000_STATUS, "Status");
+ if (val < 0) {
+ dev_err(data->dev, "Reset failed\n");
+ return val;
+ }
+
+ if (val & CMA3000_STATUS_PERR) {
+ dev_err(data->dev, "Parity Error\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int cma3000_poweron(struct cma3000_accl_data *data)
+{
+ const struct cma3000_platform_data *pdata = data->pdata;
+ u8 ctrl = 0;
+ int ret;
+
+ if (data->g_range == CMARANGE_2G) {
+ ctrl = (data->mode << 1) | CMA3000_RANGE2G;
+ } else if (data->g_range == CMARANGE_8G) {
+ ctrl = (data->mode << 1) | CMA3000_RANGE8G;
+ } else {
+ dev_info(data->dev,
+ "Invalid G range specified, assuming 8G\n");
+ ctrl = (data->mode << 1) | CMA3000_RANGE8G;
+ }
+
+ ctrl |= data->bus_ops->ctrl_mod;
+
+ CMA3000_SET(data, CMA3000_MDTHR, pdata->mdthr,
+ "Motion Detect Threshold");
+ CMA3000_SET(data, CMA3000_MDFFTMR, pdata->mdfftmr,
+ "Time register");
+ CMA3000_SET(data, CMA3000_FFTHR, pdata->ffthr,
+ "Free fall threshold");
+ ret = CMA3000_SET(data, CMA3000_CTRL, ctrl, "Mode setting");
+ if (ret < 0)
+ return -EIO;
+
+ msleep(CMA3000_SETDELAY);
+
+ return 0;
+}
+
+static int cma3000_poweroff(struct cma3000_accl_data *data)
+{
+ int ret;
+
+ ret = CMA3000_SET(data, CMA3000_CTRL, CMAMODE_POFF, "Mode setting");
+ msleep(CMA3000_SETDELAY);
+
+ return ret;
+}
+
+static int cma3000_open(struct input_dev *input_dev)
+{
+ struct cma3000_accl_data *data = input_get_drvdata(input_dev);
+
+ mutex_lock(&data->mutex);
+
+ if (!data->suspended)
+ cma3000_poweron(data);
+
+ data->opened = true;
+
+ mutex_unlock(&data->mutex);
+
+ return 0;
+}
+
+static void cma3000_close(struct input_dev *input_dev)
+{
+ struct cma3000_accl_data *data = input_get_drvdata(input_dev);
+
+ mutex_lock(&data->mutex);
+
+ if (!data->suspended)
+ cma3000_poweroff(data);
+
+ data->opened = false;
+
+ mutex_unlock(&data->mutex);
+}
+
+void cma3000_suspend(struct cma3000_accl_data *data)
+{
+ mutex_lock(&data->mutex);
+
+ if (!data->suspended && data->opened)
+ cma3000_poweroff(data);
+
+ data->suspended = true;
+
+ mutex_unlock(&data->mutex);
+}
+EXPORT_SYMBOL(cma3000_suspend);
+
+
+void cma3000_resume(struct cma3000_accl_data *data)
+{
+ mutex_lock(&data->mutex);
+
+ if (data->suspended && data->opened)
+ cma3000_poweron(data);
+
+ data->suspended = false;
+
+ mutex_unlock(&data->mutex);
+}
+EXPORT_SYMBOL(cma3000_resume);
+
+struct cma3000_accl_data *cma3000_init(struct device *dev, int irq,
+ const struct cma3000_bus_ops *bops)
+{
+ const struct cma3000_platform_data *pdata = dev_get_platdata(dev);
+ struct cma3000_accl_data *data;
+ struct input_dev *input_dev;
+ int rev;
+ int error;
+
+ if (!pdata) {
+ dev_err(dev, "platform data not found\n");
+ error = -EINVAL;
+ goto err_out;
+ }
+
+
+ /* if no IRQ return error */
+ if (irq == 0) {
+ error = -EINVAL;
+ goto err_out;
+ }
+
+ data = kzalloc(sizeof(struct cma3000_accl_data), GFP_KERNEL);
+ input_dev = input_allocate_device();
+ if (!data || !input_dev) {
+ error = -ENOMEM;
+ goto err_free_mem;
+ }
+
+ data->dev = dev;
+ data->input_dev = input_dev;
+ data->bus_ops = bops;
+ data->pdata = pdata;
+ data->irq = irq;
+ mutex_init(&data->mutex);
+
+ data->mode = pdata->mode;
+ if (data->mode > CMAMODE_POFF) {
+ data->mode = CMAMODE_MOTDET;
+ dev_warn(dev,
+ "Invalid mode specified, assuming Motion Detect\n");
+ }
+
+ data->g_range = pdata->g_range;
+ if (data->g_range != CMARANGE_2G && data->g_range != CMARANGE_8G) {
+ dev_info(dev,
+ "Invalid G range specified, assuming 8G\n");
+ data->g_range = CMARANGE_8G;
+ }
+
+ input_dev->name = "cma3000-accelerometer";
+ input_dev->id.bustype = bops->bustype;
+ input_dev->open = cma3000_open;
+ input_dev->close = cma3000_close;
+
+ __set_bit(EV_ABS, input_dev->evbit);
+
+ input_set_abs_params(input_dev, ABS_X,
+ -data->g_range, data->g_range, pdata->fuzz_x, 0);
+ input_set_abs_params(input_dev, ABS_Y,
+ -data->g_range, data->g_range, pdata->fuzz_y, 0);
+ input_set_abs_params(input_dev, ABS_Z,
+ -data->g_range, data->g_range, pdata->fuzz_z, 0);
+ input_set_abs_params(input_dev, ABS_MISC, 0, 1, 0, 0);
+
+ input_set_drvdata(input_dev, data);
+
+ error = cma3000_reset(data);
+ if (error)
+ goto err_free_mem;
+
+ rev = CMA3000_READ(data, CMA3000_REVID, "Revid");
+ if (rev < 0) {
+ error = rev;
+ goto err_free_mem;
+ }
+
+ pr_info("CMA3000 Accelerometer: Revision %x\n", rev);
+
+ error = request_threaded_irq(irq, NULL, cma3000_thread_irq,
+ pdata->irqflags | IRQF_ONESHOT,
+ "cma3000_d0x", data);
+ if (error) {
+ dev_err(dev, "request_threaded_irq failed\n");
+ goto err_free_mem;
+ }
+
+ error = input_register_device(data->input_dev);
+ if (error) {
+ dev_err(dev, "Unable to register input device\n");
+ goto err_free_irq;
+ }
+
+ return data;
+
+err_free_irq:
+ free_irq(irq, data);
+err_free_mem:
+ input_free_device(input_dev);
+ kfree(data);
+err_out:
+ return ERR_PTR(error);
+}
+EXPORT_SYMBOL(cma3000_init);
+
+void cma3000_exit(struct cma3000_accl_data *data)
+{
+ free_irq(data->irq, data);
+ input_unregister_device(data->input_dev);
+ kfree(data);
+}
+EXPORT_SYMBOL(cma3000_exit);
+
+MODULE_DESCRIPTION("CMA3000-D0x Accelerometer Driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Hemanth V <hemanthv@ti.com>");
diff --git a/drivers/input/misc/cma3000_d0x.h b/drivers/input/misc/cma3000_d0x.h
new file mode 100644
index 000000000..05ad42a56
--- /dev/null
+++ b/drivers/input/misc/cma3000_d0x.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * VTI CMA3000_D0x Accelerometer driver
+ *
+ * Copyright (C) 2010 Texas Instruments
+ * Author: Hemanth V <hemanthv@ti.com>
+ */
+
+#ifndef _INPUT_CMA3000_H
+#define _INPUT_CMA3000_H
+
+#include <linux/types.h>
+#include <linux/input.h>
+
+struct device;
+struct cma3000_accl_data;
+
+struct cma3000_bus_ops {
+ u16 bustype;
+ u8 ctrl_mod;
+ int (*read)(struct device *, u8, char *);
+ int (*write)(struct device *, u8, u8, char *);
+};
+
+struct cma3000_accl_data *cma3000_init(struct device *dev, int irq,
+ const struct cma3000_bus_ops *bops);
+void cma3000_exit(struct cma3000_accl_data *);
+void cma3000_suspend(struct cma3000_accl_data *);
+void cma3000_resume(struct cma3000_accl_data *);
+
+#endif
diff --git a/drivers/input/misc/cma3000_d0x_i2c.c b/drivers/input/misc/cma3000_d0x_i2c.c
new file mode 100644
index 000000000..3b23210c4
--- /dev/null
+++ b/drivers/input/misc/cma3000_d0x_i2c.c
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Implements I2C interface for VTI CMA300_D0x Accelerometer driver
+ *
+ * Copyright (C) 2010 Texas Instruments
+ * Author: Hemanth V <hemanthv@ti.com>
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/input/cma3000.h>
+#include "cma3000_d0x.h"
+
+static int cma3000_i2c_set(struct device *dev,
+ u8 reg, u8 val, char *msg)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, reg, val);
+ if (ret < 0)
+ dev_err(&client->dev,
+ "%s failed (%s, %d)\n", __func__, msg, ret);
+ return ret;
+}
+
+static int cma3000_i2c_read(struct device *dev, u8 reg, char *msg)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(client, reg);
+ if (ret < 0)
+ dev_err(&client->dev,
+ "%s failed (%s, %d)\n", __func__, msg, ret);
+ return ret;
+}
+
+static const struct cma3000_bus_ops cma3000_i2c_bops = {
+ .bustype = BUS_I2C,
+#define CMA3000_BUSI2C (0 << 4)
+ .ctrl_mod = CMA3000_BUSI2C,
+ .read = cma3000_i2c_read,
+ .write = cma3000_i2c_set,
+};
+
+static int cma3000_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct cma3000_accl_data *data;
+
+ data = cma3000_init(&client->dev, client->irq, &cma3000_i2c_bops);
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ i2c_set_clientdata(client, data);
+
+ return 0;
+}
+
+static void cma3000_i2c_remove(struct i2c_client *client)
+{
+ struct cma3000_accl_data *data = i2c_get_clientdata(client);
+
+ cma3000_exit(data);
+}
+
+#ifdef CONFIG_PM
+static int cma3000_i2c_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct cma3000_accl_data *data = i2c_get_clientdata(client);
+
+ cma3000_suspend(data);
+
+ return 0;
+}
+
+static int cma3000_i2c_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct cma3000_accl_data *data = i2c_get_clientdata(client);
+
+ cma3000_resume(data);
+
+ return 0;
+}
+
+static const struct dev_pm_ops cma3000_i2c_pm_ops = {
+ .suspend = cma3000_i2c_suspend,
+ .resume = cma3000_i2c_resume,
+};
+#endif
+
+static const struct i2c_device_id cma3000_i2c_id[] = {
+ { "cma3000_d01", 0 },
+ { },
+};
+
+MODULE_DEVICE_TABLE(i2c, cma3000_i2c_id);
+
+static struct i2c_driver cma3000_i2c_driver = {
+ .probe = cma3000_i2c_probe,
+ .remove = cma3000_i2c_remove,
+ .id_table = cma3000_i2c_id,
+ .driver = {
+ .name = "cma3000_i2c_accl",
+#ifdef CONFIG_PM
+ .pm = &cma3000_i2c_pm_ops,
+#endif
+ },
+};
+
+module_i2c_driver(cma3000_i2c_driver);
+
+MODULE_DESCRIPTION("CMA3000-D0x Accelerometer I2C Driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Hemanth V <hemanthv@ti.com>");
diff --git a/drivers/input/misc/cobalt_btns.c b/drivers/input/misc/cobalt_btns.c
new file mode 100644
index 000000000..b1624f541
--- /dev/null
+++ b/drivers/input/misc/cobalt_btns.c
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Cobalt button interface driver.
+ *
+ * Copyright (C) 2007-2008 Yoichi Yuasa <yuasa@linux-mips.org>
+ */
+#include <linux/input.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#define BUTTONS_POLL_INTERVAL 30 /* msec */
+#define BUTTONS_COUNT_THRESHOLD 3
+#define BUTTONS_STATUS_MASK 0xfe000000
+
+static const unsigned short cobalt_map[] = {
+ KEY_RESERVED,
+ KEY_RESTART,
+ KEY_LEFT,
+ KEY_UP,
+ KEY_DOWN,
+ KEY_RIGHT,
+ KEY_ENTER,
+ KEY_SELECT
+};
+
+struct buttons_dev {
+ unsigned short keymap[ARRAY_SIZE(cobalt_map)];
+ int count[ARRAY_SIZE(cobalt_map)];
+ void __iomem *reg;
+};
+
+static void handle_buttons(struct input_dev *input)
+{
+ struct buttons_dev *bdev = input_get_drvdata(input);
+ uint32_t status;
+ int i;
+
+ status = ~readl(bdev->reg) >> 24;
+
+ for (i = 0; i < ARRAY_SIZE(bdev->keymap); i++) {
+ if (status & (1U << i)) {
+ if (++bdev->count[i] == BUTTONS_COUNT_THRESHOLD) {
+ input_event(input, EV_MSC, MSC_SCAN, i);
+ input_report_key(input, bdev->keymap[i], 1);
+ input_sync(input);
+ }
+ } else {
+ if (bdev->count[i] >= BUTTONS_COUNT_THRESHOLD) {
+ input_event(input, EV_MSC, MSC_SCAN, i);
+ input_report_key(input, bdev->keymap[i], 0);
+ input_sync(input);
+ }
+ bdev->count[i] = 0;
+ }
+ }
+}
+
+static int cobalt_buttons_probe(struct platform_device *pdev)
+{
+ struct buttons_dev *bdev;
+ struct input_dev *input;
+ struct resource *res;
+ int error, i;
+
+ bdev = devm_kzalloc(&pdev->dev, sizeof(*bdev), GFP_KERNEL);
+ if (!bdev)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -EBUSY;
+
+ bdev->reg = devm_ioremap(&pdev->dev, res->start, resource_size(res));
+ if (!bdev->reg)
+ return -ENOMEM;
+
+ memcpy(bdev->keymap, cobalt_map, sizeof(bdev->keymap));
+
+ input = devm_input_allocate_device(&pdev->dev);
+ if (!input)
+ return -ENOMEM;
+
+ input_set_drvdata(input, bdev);
+
+ input->name = "Cobalt buttons";
+ input->phys = "cobalt/input0";
+ input->id.bustype = BUS_HOST;
+
+ input->keycode = bdev->keymap;
+ input->keycodemax = ARRAY_SIZE(bdev->keymap);
+ input->keycodesize = sizeof(unsigned short);
+
+ input_set_capability(input, EV_MSC, MSC_SCAN);
+ __set_bit(EV_KEY, input->evbit);
+ for (i = 0; i < ARRAY_SIZE(cobalt_map); i++)
+ __set_bit(bdev->keymap[i], input->keybit);
+ __clear_bit(KEY_RESERVED, input->keybit);
+
+
+ error = input_setup_polling(input, handle_buttons);
+ if (error)
+ return error;
+
+ input_set_poll_interval(input, BUTTONS_POLL_INTERVAL);
+
+ error = input_register_device(input);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+MODULE_AUTHOR("Yoichi Yuasa <yuasa@linux-mips.org>");
+MODULE_DESCRIPTION("Cobalt button interface driver");
+MODULE_LICENSE("GPL");
+/* work with hotplug and coldplug */
+MODULE_ALIAS("platform:Cobalt buttons");
+
+static struct platform_driver cobalt_buttons_driver = {
+ .probe = cobalt_buttons_probe,
+ .driver = {
+ .name = "Cobalt buttons",
+ },
+};
+module_platform_driver(cobalt_buttons_driver);
diff --git a/drivers/input/misc/cpcap-pwrbutton.c b/drivers/input/misc/cpcap-pwrbutton.c
new file mode 100644
index 000000000..879790bbf
--- /dev/null
+++ b/drivers/input/misc/cpcap-pwrbutton.c
@@ -0,0 +1,120 @@
+/**
+ * CPCAP Power Button Input Driver
+ *
+ * Copyright (C) 2017 Sebastian Reichel <sre@kernel.org>
+ *
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License. See the file "COPYING" in the main directory of this
+ * archive for more details.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/regmap.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/motorola-cpcap.h>
+
+#define CPCAP_IRQ_ON 23
+#define CPCAP_IRQ_ON_BITMASK (1 << (CPCAP_IRQ_ON % 16))
+
+struct cpcap_power_button {
+ struct regmap *regmap;
+ struct input_dev *idev;
+ struct device *dev;
+};
+
+static irqreturn_t powerbutton_irq(int irq, void *_button)
+{
+ struct cpcap_power_button *button = _button;
+ int val;
+
+ val = cpcap_sense_virq(button->regmap, irq);
+ if (val < 0) {
+ dev_err(button->dev, "irq read failed: %d", val);
+ return IRQ_HANDLED;
+ }
+
+ pm_wakeup_event(button->dev, 0);
+ input_report_key(button->idev, KEY_POWER, val);
+ input_sync(button->idev);
+
+ return IRQ_HANDLED;
+}
+
+static int cpcap_power_button_probe(struct platform_device *pdev)
+{
+ struct cpcap_power_button *button;
+ int irq;
+ int err;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ button = devm_kmalloc(&pdev->dev, sizeof(*button), GFP_KERNEL);
+ if (!button)
+ return -ENOMEM;
+
+ button->idev = devm_input_allocate_device(&pdev->dev);
+ if (!button->idev)
+ return -ENOMEM;
+
+ button->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!button->regmap)
+ return -ENODEV;
+
+ button->dev = &pdev->dev;
+
+ button->idev->name = "cpcap-pwrbutton";
+ button->idev->phys = "cpcap-pwrbutton/input0";
+ input_set_capability(button->idev, EV_KEY, KEY_POWER);
+
+ err = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+ powerbutton_irq, IRQF_ONESHOT, "cpcap_pwrbutton", button);
+ if (err < 0) {
+ dev_err(&pdev->dev, "IRQ request failed: %d\n", err);
+ return err;
+ }
+
+ err = input_register_device(button->idev);
+ if (err) {
+ dev_err(&pdev->dev, "Input register failed: %d\n", err);
+ return err;
+ }
+
+ device_init_wakeup(&pdev->dev, true);
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id cpcap_pwrbutton_dt_match_table[] = {
+ { .compatible = "motorola,cpcap-pwrbutton" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, cpcap_pwrbutton_dt_match_table);
+#endif
+
+static struct platform_driver cpcap_power_button_driver = {
+ .probe = cpcap_power_button_probe,
+ .driver = {
+ .name = "cpcap-pwrbutton",
+ .of_match_table = of_match_ptr(cpcap_pwrbutton_dt_match_table),
+ },
+};
+module_platform_driver(cpcap_power_button_driver);
+
+MODULE_ALIAS("platform:cpcap-pwrbutton");
+MODULE_DESCRIPTION("CPCAP Power Button");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Sebastian Reichel <sre@kernel.org>");
diff --git a/drivers/input/misc/da7280.c b/drivers/input/misc/da7280.c
new file mode 100644
index 000000000..b08610d6e
--- /dev/null
+++ b/drivers/input/misc/da7280.c
@@ -0,0 +1,1332 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * DA7280 Haptic device driver
+ *
+ * Copyright (c) 2020 Dialog Semiconductor.
+ * Author: Roy Im <Roy.Im.Opensource@diasemi.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/pwm.h>
+#include <linux/regmap.h>
+#include <linux/workqueue.h>
+#include <linux/uaccess.h>
+
+/* Registers */
+#define DA7280_IRQ_EVENT1 0x03
+#define DA7280_IRQ_EVENT_WARNING_DIAG 0x04
+#define DA7280_IRQ_EVENT_SEQ_DIAG 0x05
+#define DA7280_IRQ_STATUS1 0x06
+#define DA7280_IRQ_MASK1 0x07
+#define DA7280_FRQ_LRA_PER_H 0x0A
+#define DA7280_FRQ_LRA_PER_L 0x0B
+#define DA7280_ACTUATOR1 0x0C
+#define DA7280_ACTUATOR2 0x0D
+#define DA7280_ACTUATOR3 0x0E
+#define DA7280_CALIB_V2I_H 0x0F
+#define DA7280_CALIB_V2I_L 0x10
+#define DA7280_TOP_CFG1 0x13
+#define DA7280_TOP_CFG2 0x14
+#define DA7280_TOP_CFG4 0x16
+#define DA7280_TOP_INT_CFG1 0x17
+#define DA7280_TOP_CTL1 0x22
+#define DA7280_TOP_CTL2 0x23
+#define DA7280_SEQ_CTL2 0x28
+#define DA7280_GPI_0_CTL 0x29
+#define DA7280_GPI_1_CTL 0x2A
+#define DA7280_GPI_2_CTL 0x2B
+#define DA7280_MEM_CTL1 0x2C
+#define DA7280_MEM_CTL2 0x2D
+#define DA7280_TOP_CFG5 0x6E
+#define DA7280_IRQ_MASK2 0x83
+#define DA7280_SNP_MEM_99 0xE7
+
+/* Register field */
+
+/* DA7280_IRQ_EVENT1 (Address 0x03) */
+#define DA7280_E_SEQ_CONTINUE_MASK BIT(0)
+#define DA7280_E_UVLO_MASK BIT(1)
+#define DA7280_E_SEQ_DONE_MASK BIT(2)
+#define DA7280_E_OVERTEMP_CRIT_MASK BIT(3)
+#define DA7280_E_SEQ_FAULT_MASK BIT(4)
+#define DA7280_E_WARNING_MASK BIT(5)
+#define DA7280_E_ACTUATOR_FAULT_MASK BIT(6)
+#define DA7280_E_OC_FAULT_MASK BIT(7)
+
+/* DA7280_IRQ_EVENT_WARNING_DIAG (Address 0x04) */
+#define DA7280_E_OVERTEMP_WARN_MASK BIT(3)
+#define DA7280_E_MEM_TYPE_MASK BIT(4)
+#define DA7280_E_LIM_DRIVE_ACC_MASK BIT(6)
+#define DA7280_E_LIM_DRIVE_MASK BIT(7)
+
+/* DA7280_IRQ_EVENT_PAT_DIAG (Address 0x05) */
+#define DA7280_E_PWM_FAULT_MASK BIT(5)
+#define DA7280_E_MEM_FAULT_MASK BIT(6)
+#define DA7280_E_SEQ_ID_FAULT_MASK BIT(7)
+
+/* DA7280_IRQ_STATUS1 (Address 0x06) */
+#define DA7280_STA_SEQ_CONTINUE_MASK BIT(0)
+#define DA7280_STA_UVLO_VBAT_OK_MASK BIT(1)
+#define DA7280_STA_SEQ_DONE_MASK BIT(2)
+#define DA7280_STA_OVERTEMP_CRIT_MASK BIT(3)
+#define DA7280_STA_SEQ_FAULT_MASK BIT(4)
+#define DA7280_STA_WARNING_MASK BIT(5)
+#define DA7280_STA_ACTUATOR_MASK BIT(6)
+#define DA7280_STA_OC_MASK BIT(7)
+
+/* DA7280_IRQ_MASK1 (Address 0x07) */
+#define DA7280_SEQ_CONTINUE_M_MASK BIT(0)
+#define DA7280_E_UVLO_M_MASK BIT(1)
+#define DA7280_SEQ_DONE_M_MASK BIT(2)
+#define DA7280_OVERTEMP_CRIT_M_MASK BIT(3)
+#define DA7280_SEQ_FAULT_M_MASK BIT(4)
+#define DA7280_WARNING_M_MASK BIT(5)
+#define DA7280_ACTUATOR_M_MASK BIT(6)
+#define DA7280_OC_M_MASK BIT(7)
+
+/* DA7280_ACTUATOR3 (Address 0x0e) */
+#define DA7280_IMAX_MASK GENMASK(4, 0)
+
+/* DA7280_TOP_CFG1 (Address 0x13) */
+#define DA7280_AMP_PID_EN_MASK BIT(0)
+#define DA7280_RAPID_STOP_EN_MASK BIT(1)
+#define DA7280_ACCELERATION_EN_MASK BIT(2)
+#define DA7280_FREQ_TRACK_EN_MASK BIT(3)
+#define DA7280_BEMF_SENSE_EN_MASK BIT(4)
+#define DA7280_ACTUATOR_TYPE_MASK BIT(5)
+
+/* DA7280_TOP_CFG2 (Address 0x14) */
+#define DA7280_FULL_BRAKE_THR_MASK GENMASK(3, 0)
+#define DA7280_MEM_DATA_SIGNED_MASK BIT(4)
+
+/* DA7280_TOP_CFG4 (Address 0x16) */
+#define DA7280_TST_CALIB_IMPEDANCE_DIS_MASK BIT(6)
+#define DA7280_V2I_FACTOR_FREEZE_MASK BIT(7)
+
+/* DA7280_TOP_INT_CFG1 (Address 0x17) */
+#define DA7280_BEMF_FAULT_LIM_MASK GENMASK(1, 0)
+
+/* DA7280_TOP_CTL1 (Address 0x22) */
+#define DA7280_OPERATION_MODE_MASK GENMASK(2, 0)
+#define DA7280_STANDBY_EN_MASK BIT(3)
+#define DA7280_SEQ_START_MASK BIT(4)
+
+/* DA7280_SEQ_CTL2 (Address 0x28) */
+#define DA7280_PS_SEQ_ID_MASK GENMASK(3, 0)
+#define DA7280_PS_SEQ_LOOP_MASK GENMASK(7, 4)
+
+/* DA7280_GPIO_0_CTL (Address 0x29) */
+#define DA7280_GPI0_POLARITY_MASK GENMASK(1, 0)
+#define DA7280_GPI0_MODE_MASK BIT(2)
+#define DA7280_GPI0_SEQUENCE_ID_MASK GENMASK(6, 3)
+
+/* DA7280_GPIO_1_CTL (Address 0x2a) */
+#define DA7280_GPI1_POLARITY_MASK GENMASK(1, 0)
+#define DA7280_GPI1_MODE_MASK BIT(2)
+#define DA7280_GPI1_SEQUENCE_ID_MASK GENMASK(6, 3)
+
+/* DA7280_GPIO_2_CTL (Address 0x2b) */
+#define DA7280_GPI2_POLARITY_MASK GENMASK(1, 0)
+#define DA7280_GPI2_MODE_MASK BIT(2)
+#define DA7280_GPI2_SEQUENCE_ID_MASK GENMASK(6, 3)
+
+/* DA7280_MEM_CTL2 (Address 0x2d) */
+#define DA7280_WAV_MEM_LOCK_MASK BIT(7)
+
+/* DA7280_TOP_CFG5 (Address 0x6e) */
+#define DA7280_V2I_FACTOR_OFFSET_EN_MASK BIT(0)
+
+/* DA7280_IRQ_MASK2 (Address 0x83) */
+#define DA7280_ADC_SAT_M_MASK BIT(7)
+
+/* Controls */
+
+#define DA7280_VOLTAGE_RATE_MAX 6000000
+#define DA7280_VOLTAGE_RATE_STEP 23400
+#define DA7280_NOMMAX_DFT 0x6B
+#define DA7280_ABSMAX_DFT 0x78
+
+#define DA7280_IMPD_MAX 1500000000
+#define DA7280_IMPD_DEFAULT 22000000
+
+#define DA7280_IMAX_DEFAULT 0x0E
+#define DA7280_IMAX_STEP 7200
+#define DA7280_IMAX_LIMIT 252000
+
+#define DA7280_RESONT_FREQH_DFT 0x39
+#define DA7280_RESONT_FREQL_DFT 0x32
+#define DA7280_MIN_RESONAT_FREQ_HZ 50
+#define DA7280_MAX_RESONAT_FREQ_HZ 300
+
+#define DA7280_SEQ_ID_MAX 15
+#define DA7280_SEQ_LOOP_MAX 15
+#define DA7280_GPI_SEQ_ID_DFT 0
+#define DA7280_GPI_SEQ_ID_MAX 2
+
+#define DA7280_SNP_MEM_SIZE 100
+#define DA7280_SNP_MEM_MAX DA7280_SNP_MEM_99
+
+#define DA7280_IRQ_NUM 3
+
+#define DA7280_SKIP_INIT 0x100
+
+#define DA7280_FF_EFFECT_COUNT_MAX 15
+
+/* Maximum gain is 0x7fff for PWM mode */
+#define DA7280_MAX_MAGNITUDE_SHIFT 15
+
+enum da7280_haptic_dev_t {
+ DA7280_LRA = 0,
+ DA7280_ERM_BAR = 1,
+ DA7280_ERM_COIN = 2,
+ DA7280_DEV_MAX,
+};
+
+enum da7280_op_mode {
+ DA7280_INACTIVE = 0,
+ DA7280_DRO_MODE = 1,
+ DA7280_PWM_MODE = 2,
+ DA7280_RTWM_MODE = 3,
+ DA7280_ETWM_MODE = 4,
+ DA7280_OPMODE_MAX,
+};
+
+#define DA7280_FF_CONSTANT_DRO 1
+#define DA7280_FF_PERIODIC_PWM 2
+#define DA7280_FF_PERIODIC_RTWM 1
+#define DA7280_FF_PERIODIC_ETWM 2
+
+#define DA7280_FF_PERIODIC_MODE DA7280_RTWM_MODE
+#define DA7280_FF_CONSTANT_MODE DA7280_DRO_MODE
+
+enum da7280_custom_effect_param {
+ DA7280_CUSTOM_SEQ_ID_IDX = 0,
+ DA7280_CUSTOM_SEQ_LOOP_IDX = 1,
+ DA7280_CUSTOM_DATA_LEN = 2,
+};
+
+enum da7280_custom_gpi_effect_param {
+ DA7280_CUSTOM_GPI_SEQ_ID_IDX = 0,
+ DA7280_CUSTOM_GPI_NUM_IDX = 2,
+ DA7280_CUSTOM_GP_DATA_LEN = 3,
+};
+
+struct da7280_gpi_ctl {
+ u8 seq_id;
+ u8 mode;
+ u8 polarity;
+};
+
+struct da7280_haptic {
+ struct regmap *regmap;
+ struct input_dev *input_dev;
+ struct device *dev;
+ struct i2c_client *client;
+ struct pwm_device *pwm_dev;
+
+ bool legacy;
+ struct work_struct work;
+ int val;
+ u16 gain;
+ s16 level;
+
+ u8 dev_type;
+ u8 op_mode;
+ u8 const_op_mode;
+ u8 periodic_op_mode;
+ u16 nommax;
+ u16 absmax;
+ u32 imax;
+ u32 impd;
+ u32 resonant_freq_h;
+ u32 resonant_freq_l;
+ bool bemf_sense_en;
+ bool freq_track_en;
+ bool acc_en;
+ bool rapid_stop_en;
+ bool amp_pid_en;
+ u8 ps_seq_id;
+ u8 ps_seq_loop;
+ struct da7280_gpi_ctl gpi_ctl[3];
+ bool mem_update;
+ u8 snp_mem[DA7280_SNP_MEM_SIZE];
+ bool active;
+ bool suspended;
+};
+
+static bool da7280_volatile_register(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case DA7280_IRQ_EVENT1:
+ case DA7280_IRQ_EVENT_WARNING_DIAG:
+ case DA7280_IRQ_EVENT_SEQ_DIAG:
+ case DA7280_IRQ_STATUS1:
+ case DA7280_TOP_CTL1:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config da7280_haptic_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = DA7280_SNP_MEM_MAX,
+ .volatile_reg = da7280_volatile_register,
+};
+
+static int da7280_haptic_mem_update(struct da7280_haptic *haptics)
+{
+ unsigned int val;
+ int error;
+
+ /* The patterns should be updated when haptic is not working */
+ error = regmap_read(haptics->regmap, DA7280_IRQ_STATUS1, &val);
+ if (error)
+ return error;
+ if (val & DA7280_STA_WARNING_MASK) {
+ dev_warn(haptics->dev,
+ "Warning! Please check HAPTIC status.\n");
+ return -EBUSY;
+ }
+
+ /* Patterns are not updated if the lock bit is enabled */
+ val = 0;
+ error = regmap_read(haptics->regmap, DA7280_MEM_CTL2, &val);
+ if (error)
+ return error;
+ if (~val & DA7280_WAV_MEM_LOCK_MASK) {
+ dev_warn(haptics->dev, "Please unlock the bit first\n");
+ return -EACCES;
+ }
+
+ /* Set to Inactive mode to make sure safety */
+ error = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CTL1,
+ DA7280_OPERATION_MODE_MASK,
+ 0);
+ if (error)
+ return error;
+
+ error = regmap_read(haptics->regmap, DA7280_MEM_CTL1, &val);
+ if (error)
+ return error;
+
+ return regmap_bulk_write(haptics->regmap, val, haptics->snp_mem,
+ DA7280_SNP_MEM_MAX - val + 1);
+}
+
+static int da7280_haptic_set_pwm(struct da7280_haptic *haptics, bool enabled)
+{
+ struct pwm_state state;
+ u64 period_mag_multi;
+ int error;
+
+ if (!haptics->gain && enabled) {
+ dev_err(haptics->dev, "Unable to enable pwm with 0 gain\n");
+ return -EINVAL;
+ }
+
+ pwm_get_state(haptics->pwm_dev, &state);
+ state.enabled = enabled;
+ if (enabled) {
+ period_mag_multi = (u64)state.period * haptics->gain;
+ period_mag_multi >>= DA7280_MAX_MAGNITUDE_SHIFT;
+
+ /*
+ * The interpretation of duty cycle depends on the acc_en,
+ * it should be between 50% and 100% for acc_en = 0.
+ * See datasheet 'PWM mode' section.
+ */
+ if (!haptics->acc_en) {
+ period_mag_multi += state.period;
+ period_mag_multi /= 2;
+ }
+
+ state.duty_cycle = period_mag_multi;
+ }
+
+ error = pwm_apply_state(haptics->pwm_dev, &state);
+ if (error)
+ dev_err(haptics->dev, "Failed to apply pwm state: %d\n", error);
+
+ return error;
+}
+
+static void da7280_haptic_activate(struct da7280_haptic *haptics)
+{
+ int error;
+
+ if (haptics->active)
+ return;
+
+ switch (haptics->op_mode) {
+ case DA7280_DRO_MODE:
+ /* the valid range check when acc_en is enabled */
+ if (haptics->acc_en && haptics->level > 0x7F)
+ haptics->level = 0x7F;
+ else if (haptics->level > 0xFF)
+ haptics->level = 0xFF;
+
+ /* Set level as a % of ACTUATOR_NOMMAX (nommax) */
+ error = regmap_write(haptics->regmap, DA7280_TOP_CTL2,
+ haptics->level);
+ if (error) {
+ dev_err(haptics->dev,
+ "Failed to set level to %d: %d\n",
+ haptics->level, error);
+ return;
+ }
+ break;
+
+ case DA7280_PWM_MODE:
+ if (da7280_haptic_set_pwm(haptics, true))
+ return;
+ break;
+
+ case DA7280_RTWM_MODE:
+ /*
+ * The pattern will be played by the PS_SEQ_ID and the
+ * PS_SEQ_LOOP
+ */
+ break;
+
+ case DA7280_ETWM_MODE:
+ /*
+ * The pattern will be played by the GPI[N] state,
+ * GPI(N)_SEQUENCE_ID and the PS_SEQ_LOOP. See the
+ * datasheet for the details.
+ */
+ break;
+
+ default:
+ dev_err(haptics->dev, "Invalid op mode %d\n", haptics->op_mode);
+ return;
+ }
+
+ error = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CTL1,
+ DA7280_OPERATION_MODE_MASK,
+ haptics->op_mode);
+ if (error) {
+ dev_err(haptics->dev,
+ "Failed to set operation mode: %d", error);
+ return;
+ }
+
+ if (haptics->op_mode == DA7280_PWM_MODE ||
+ haptics->op_mode == DA7280_RTWM_MODE) {
+ error = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CTL1,
+ DA7280_SEQ_START_MASK,
+ DA7280_SEQ_START_MASK);
+ if (error) {
+ dev_err(haptics->dev,
+ "Failed to start sequence: %d\n", error);
+ return;
+ }
+ }
+
+ haptics->active = true;
+}
+
+static void da7280_haptic_deactivate(struct da7280_haptic *haptics)
+{
+ int error;
+
+ if (!haptics->active)
+ return;
+
+ /* Set to Inactive mode */
+ error = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CTL1,
+ DA7280_OPERATION_MODE_MASK, 0);
+ if (error) {
+ dev_err(haptics->dev,
+ "Failed to clear operation mode: %d", error);
+ return;
+ }
+
+ switch (haptics->op_mode) {
+ case DA7280_DRO_MODE:
+ error = regmap_write(haptics->regmap,
+ DA7280_TOP_CTL2, 0);
+ if (error) {
+ dev_err(haptics->dev,
+ "Failed to disable DRO mode: %d\n", error);
+ return;
+ }
+ break;
+
+ case DA7280_PWM_MODE:
+ if (da7280_haptic_set_pwm(haptics, false))
+ return;
+ break;
+
+ case DA7280_RTWM_MODE:
+ case DA7280_ETWM_MODE:
+ error = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CTL1,
+ DA7280_SEQ_START_MASK, 0);
+ if (error) {
+ dev_err(haptics->dev,
+ "Failed to disable RTWM/ETWM mode: %d\n",
+ error);
+ return;
+ }
+ break;
+
+ default:
+ dev_err(haptics->dev, "Invalid op mode %d\n", haptics->op_mode);
+ return;
+ }
+
+ haptics->active = false;
+}
+
+static void da7280_haptic_work(struct work_struct *work)
+{
+ struct da7280_haptic *haptics =
+ container_of(work, struct da7280_haptic, work);
+ int val = haptics->val;
+
+ if (val)
+ da7280_haptic_activate(haptics);
+ else
+ da7280_haptic_deactivate(haptics);
+}
+
+static int da7280_haptics_upload_effect(struct input_dev *dev,
+ struct ff_effect *effect,
+ struct ff_effect *old)
+{
+ struct da7280_haptic *haptics = input_get_drvdata(dev);
+ s16 data[DA7280_SNP_MEM_SIZE] = { 0 };
+ unsigned int val;
+ int tmp, i, num;
+ int error;
+
+ /* The effect should be uploaded when haptic is not working */
+ if (haptics->active)
+ return -EBUSY;
+
+ switch (effect->type) {
+ /* DRO/PWM modes support this type */
+ case FF_CONSTANT:
+ haptics->op_mode = haptics->const_op_mode;
+ if (haptics->op_mode == DA7280_DRO_MODE) {
+ tmp = effect->u.constant.level * 254;
+ haptics->level = tmp / 0x7FFF;
+ break;
+ }
+
+ haptics->gain = effect->u.constant.level <= 0 ?
+ 0 : effect->u.constant.level;
+ break;
+
+ /* RTWM/ETWM modes support this type */
+ case FF_PERIODIC:
+ if (effect->u.periodic.waveform != FF_CUSTOM) {
+ dev_err(haptics->dev,
+ "Device can only accept FF_CUSTOM waveform\n");
+ return -EINVAL;
+ }
+
+ /*
+ * Load the data and check the length.
+ * the data will be patterns in this case: 4 < X <= 100,
+ * and will be saved into the waveform memory inside DA728x.
+ * If X = 2, the data will be PS_SEQ_ID and PS_SEQ_LOOP.
+ * If X = 3, the 1st data will be GPIX_SEQUENCE_ID .
+ */
+ if (effect->u.periodic.custom_len == DA7280_CUSTOM_DATA_LEN)
+ goto set_seq_id_loop;
+
+ if (effect->u.periodic.custom_len == DA7280_CUSTOM_GP_DATA_LEN)
+ goto set_gpix_seq_id;
+
+ if (effect->u.periodic.custom_len < DA7280_CUSTOM_DATA_LEN ||
+ effect->u.periodic.custom_len > DA7280_SNP_MEM_SIZE) {
+ dev_err(haptics->dev, "Invalid waveform data size\n");
+ return -EINVAL;
+ }
+
+ if (copy_from_user(data, effect->u.periodic.custom_data,
+ sizeof(s16) *
+ effect->u.periodic.custom_len))
+ return -EFAULT;
+
+ memset(haptics->snp_mem, 0, DA7280_SNP_MEM_SIZE);
+
+ for (i = 0; i < effect->u.periodic.custom_len; i++) {
+ if (data[i] < 0 || data[i] > 0xff) {
+ dev_err(haptics->dev,
+ "Invalid waveform data %d at offset %d\n",
+ data[i], i);
+ return -EINVAL;
+ }
+ haptics->snp_mem[i] = (u8)data[i];
+ }
+
+ error = da7280_haptic_mem_update(haptics);
+ if (error) {
+ dev_err(haptics->dev,
+ "Failed to upload waveform: %d\n", error);
+ return error;
+ }
+ break;
+
+set_seq_id_loop:
+ if (copy_from_user(data, effect->u.periodic.custom_data,
+ sizeof(s16) * DA7280_CUSTOM_DATA_LEN))
+ return -EFAULT;
+
+ if (data[DA7280_CUSTOM_SEQ_ID_IDX] < 0 ||
+ data[DA7280_CUSTOM_SEQ_ID_IDX] > DA7280_SEQ_ID_MAX ||
+ data[DA7280_CUSTOM_SEQ_LOOP_IDX] < 0 ||
+ data[DA7280_CUSTOM_SEQ_LOOP_IDX] > DA7280_SEQ_LOOP_MAX) {
+ dev_err(haptics->dev,
+ "Invalid custom id (%d) or loop (%d)\n",
+ data[DA7280_CUSTOM_SEQ_ID_IDX],
+ data[DA7280_CUSTOM_SEQ_LOOP_IDX]);
+ return -EINVAL;
+ }
+
+ haptics->ps_seq_id = data[DA7280_CUSTOM_SEQ_ID_IDX] & 0x0f;
+ haptics->ps_seq_loop = data[DA7280_CUSTOM_SEQ_LOOP_IDX] & 0x0f;
+ haptics->op_mode = haptics->periodic_op_mode;
+
+ val = FIELD_PREP(DA7280_PS_SEQ_ID_MASK, haptics->ps_seq_id) |
+ FIELD_PREP(DA7280_PS_SEQ_LOOP_MASK,
+ haptics->ps_seq_loop);
+ error = regmap_write(haptics->regmap, DA7280_SEQ_CTL2, val);
+ if (error) {
+ dev_err(haptics->dev,
+ "Failed to update PS sequence: %d\n", error);
+ return error;
+ }
+ break;
+
+set_gpix_seq_id:
+ if (copy_from_user(data, effect->u.periodic.custom_data,
+ sizeof(s16) * DA7280_CUSTOM_GP_DATA_LEN))
+ return -EFAULT;
+
+ if (data[DA7280_CUSTOM_GPI_SEQ_ID_IDX] < 0 ||
+ data[DA7280_CUSTOM_GPI_SEQ_ID_IDX] > DA7280_SEQ_ID_MAX ||
+ data[DA7280_CUSTOM_GPI_NUM_IDX] < 0 ||
+ data[DA7280_CUSTOM_GPI_NUM_IDX] > DA7280_GPI_SEQ_ID_MAX) {
+ dev_err(haptics->dev,
+ "Invalid custom GPI id (%d) or num (%d)\n",
+ data[DA7280_CUSTOM_GPI_SEQ_ID_IDX],
+ data[DA7280_CUSTOM_GPI_NUM_IDX]);
+ return -EINVAL;
+ }
+
+ num = data[DA7280_CUSTOM_GPI_NUM_IDX] & 0x0f;
+ haptics->gpi_ctl[num].seq_id =
+ data[DA7280_CUSTOM_GPI_SEQ_ID_IDX] & 0x0f;
+ haptics->op_mode = haptics->periodic_op_mode;
+
+ val = FIELD_PREP(DA7280_GPI0_SEQUENCE_ID_MASK,
+ haptics->gpi_ctl[num].seq_id);
+ error = regmap_update_bits(haptics->regmap,
+ DA7280_GPI_0_CTL + num,
+ DA7280_GPI0_SEQUENCE_ID_MASK,
+ val);
+ if (error) {
+ dev_err(haptics->dev,
+ "Failed to update GPI sequence: %d\n", error);
+ return error;
+ }
+ break;
+
+ default:
+ dev_err(haptics->dev, "Unsupported effect type: %d\n",
+ effect->type);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int da7280_haptics_playback(struct input_dev *dev,
+ int effect_id, int val)
+{
+ struct da7280_haptic *haptics = input_get_drvdata(dev);
+
+ if (!haptics->op_mode) {
+ dev_warn(haptics->dev, "No effects have been uploaded\n");
+ return -EINVAL;
+ }
+
+ if (likely(!haptics->suspended)) {
+ haptics->val = val;
+ schedule_work(&haptics->work);
+ }
+
+ return 0;
+}
+
+static int da7280_haptic_start(struct da7280_haptic *haptics)
+{
+ int error;
+
+ error = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CTL1,
+ DA7280_STANDBY_EN_MASK,
+ DA7280_STANDBY_EN_MASK);
+ if (error) {
+ dev_err(haptics->dev, "Unable to enable device: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static void da7280_haptic_stop(struct da7280_haptic *haptics)
+{
+ int error;
+
+ cancel_work_sync(&haptics->work);
+
+
+ da7280_haptic_deactivate(haptics);
+
+ error = regmap_update_bits(haptics->regmap, DA7280_TOP_CTL1,
+ DA7280_STANDBY_EN_MASK, 0);
+ if (error)
+ dev_err(haptics->dev, "Failed to disable device: %d\n", error);
+}
+
+static int da7280_haptic_open(struct input_dev *dev)
+{
+ struct da7280_haptic *haptics = input_get_drvdata(dev);
+
+ return da7280_haptic_start(haptics);
+}
+
+static void da7280_haptic_close(struct input_dev *dev)
+{
+ struct da7280_haptic *haptics = input_get_drvdata(dev);
+
+ da7280_haptic_stop(haptics);
+}
+
+static u8 da7280_haptic_of_mode_str(struct device *dev,
+ const char *str)
+{
+ if (!strcmp(str, "LRA")) {
+ return DA7280_LRA;
+ } else if (!strcmp(str, "ERM-bar")) {
+ return DA7280_ERM_BAR;
+ } else if (!strcmp(str, "ERM-coin")) {
+ return DA7280_ERM_COIN;
+ } else {
+ dev_warn(dev, "Invalid string - set to LRA\n");
+ return DA7280_LRA;
+ }
+}
+
+static u8 da7280_haptic_of_gpi_mode_str(struct device *dev,
+ const char *str)
+{
+ if (!strcmp(str, "Single-pattern")) {
+ return 0;
+ } else if (!strcmp(str, "Multi-pattern")) {
+ return 1;
+ } else {
+ dev_warn(dev, "Invalid string - set to Single-pattern\n");
+ return 0;
+ }
+}
+
+static u8 da7280_haptic_of_gpi_pol_str(struct device *dev,
+ const char *str)
+{
+ if (!strcmp(str, "Rising-edge")) {
+ return 0;
+ } else if (!strcmp(str, "Falling-edge")) {
+ return 1;
+ } else if (!strcmp(str, "Both-edge")) {
+ return 2;
+ } else {
+ dev_warn(dev, "Invalid string - set to Rising-edge\n");
+ return 0;
+ }
+}
+
+static u8 da7280_haptic_of_volt_rating_set(u32 val)
+{
+ u32 voltage = val / DA7280_VOLTAGE_RATE_STEP + 1;
+
+ return min_t(u32, voltage, 0xff);
+}
+
+static void da7280_parse_properties(struct device *dev,
+ struct da7280_haptic *haptics)
+{
+ unsigned int i, mem[DA7280_SNP_MEM_SIZE];
+ char gpi_str1[] = "dlg,gpi0-seq-id";
+ char gpi_str2[] = "dlg,gpi0-mode";
+ char gpi_str3[] = "dlg,gpi0-polarity";
+ const char *str;
+ u32 val;
+ int error;
+
+ /*
+ * If there is no property, then use the mode programmed into the chip.
+ */
+ haptics->dev_type = DA7280_DEV_MAX;
+ error = device_property_read_string(dev, "dlg,actuator-type", &str);
+ if (!error)
+ haptics->dev_type = da7280_haptic_of_mode_str(dev, str);
+
+ haptics->const_op_mode = DA7280_DRO_MODE;
+ error = device_property_read_u32(dev, "dlg,const-op-mode", &val);
+ if (!error && val == DA7280_FF_PERIODIC_PWM)
+ haptics->const_op_mode = DA7280_PWM_MODE;
+
+ haptics->periodic_op_mode = DA7280_RTWM_MODE;
+ error = device_property_read_u32(dev, "dlg,periodic-op-mode", &val);
+ if (!error && val == DA7280_FF_PERIODIC_ETWM)
+ haptics->periodic_op_mode = DA7280_ETWM_MODE;
+
+ haptics->nommax = DA7280_SKIP_INIT;
+ error = device_property_read_u32(dev, "dlg,nom-microvolt", &val);
+ if (!error && val < DA7280_VOLTAGE_RATE_MAX)
+ haptics->nommax = da7280_haptic_of_volt_rating_set(val);
+
+ haptics->absmax = DA7280_SKIP_INIT;
+ error = device_property_read_u32(dev, "dlg,abs-max-microvolt", &val);
+ if (!error && val < DA7280_VOLTAGE_RATE_MAX)
+ haptics->absmax = da7280_haptic_of_volt_rating_set(val);
+
+ haptics->imax = DA7280_IMAX_DEFAULT;
+ error = device_property_read_u32(dev, "dlg,imax-microamp", &val);
+ if (!error && val < DA7280_IMAX_LIMIT)
+ haptics->imax = (val - 28600) / DA7280_IMAX_STEP + 1;
+
+ haptics->impd = DA7280_IMPD_DEFAULT;
+ error = device_property_read_u32(dev, "dlg,impd-micro-ohms", &val);
+ if (!error && val <= DA7280_IMPD_MAX)
+ haptics->impd = val;
+
+ haptics->resonant_freq_h = DA7280_SKIP_INIT;
+ haptics->resonant_freq_l = DA7280_SKIP_INIT;
+ error = device_property_read_u32(dev, "dlg,resonant-freq-hz", &val);
+ if (!error) {
+ if (val < DA7280_MAX_RESONAT_FREQ_HZ &&
+ val > DA7280_MIN_RESONAT_FREQ_HZ) {
+ haptics->resonant_freq_h =
+ ((1000000000 / (val * 1333)) >> 7) & 0xFF;
+ haptics->resonant_freq_l =
+ (1000000000 / (val * 1333)) & 0x7F;
+ } else {
+ haptics->resonant_freq_h = DA7280_RESONT_FREQH_DFT;
+ haptics->resonant_freq_l = DA7280_RESONT_FREQL_DFT;
+ }
+ }
+
+ /* If no property, set to zero as default is to do nothing. */
+ haptics->ps_seq_id = 0;
+ error = device_property_read_u32(dev, "dlg,ps-seq-id", &val);
+ if (!error && val <= DA7280_SEQ_ID_MAX)
+ haptics->ps_seq_id = val;
+
+ haptics->ps_seq_loop = 0;
+ error = device_property_read_u32(dev, "dlg,ps-seq-loop", &val);
+ if (!error && val <= DA7280_SEQ_LOOP_MAX)
+ haptics->ps_seq_loop = val;
+
+ /* GPI0~2 Control */
+ for (i = 0; i <= DA7280_GPI_SEQ_ID_MAX; i++) {
+ gpi_str1[7] = '0' + i;
+ haptics->gpi_ctl[i].seq_id = DA7280_GPI_SEQ_ID_DFT + i;
+ error = device_property_read_u32 (dev, gpi_str1, &val);
+ if (!error && val <= DA7280_SEQ_ID_MAX)
+ haptics->gpi_ctl[i].seq_id = val;
+
+ gpi_str2[7] = '0' + i;
+ haptics->gpi_ctl[i].mode = 0;
+ error = device_property_read_string(dev, gpi_str2, &str);
+ if (!error)
+ haptics->gpi_ctl[i].mode =
+ da7280_haptic_of_gpi_mode_str(dev, str);
+
+ gpi_str3[7] = '0' + i;
+ haptics->gpi_ctl[i].polarity = 0;
+ error = device_property_read_string(dev, gpi_str3, &str);
+ if (!error)
+ haptics->gpi_ctl[i].polarity =
+ da7280_haptic_of_gpi_pol_str(dev, str);
+ }
+
+ haptics->bemf_sense_en =
+ device_property_read_bool(dev, "dlg,bemf-sens-enable");
+ haptics->freq_track_en =
+ device_property_read_bool(dev, "dlg,freq-track-enable");
+ haptics->acc_en =
+ device_property_read_bool(dev, "dlg,acc-enable");
+ haptics->rapid_stop_en =
+ device_property_read_bool(dev, "dlg,rapid-stop-enable");
+ haptics->amp_pid_en =
+ device_property_read_bool(dev, "dlg,amp-pid-enable");
+
+ haptics->mem_update = false;
+ error = device_property_read_u32_array(dev, "dlg,mem-array",
+ &mem[0], DA7280_SNP_MEM_SIZE);
+ if (!error) {
+ haptics->mem_update = true;
+ memset(haptics->snp_mem, 0, DA7280_SNP_MEM_SIZE);
+ for (i = 0; i < DA7280_SNP_MEM_SIZE; i++) {
+ if (mem[i] <= 0xff) {
+ haptics->snp_mem[i] = (u8)mem[i];
+ } else {
+ dev_err(haptics->dev,
+ "Invalid data in mem-array at %d: %x\n",
+ i, mem[i]);
+ haptics->mem_update = false;
+ break;
+ }
+ }
+ }
+}
+
+static irqreturn_t da7280_irq_handler(int irq, void *data)
+{
+ struct da7280_haptic *haptics = data;
+ struct device *dev = haptics->dev;
+ u8 events[DA7280_IRQ_NUM];
+ int error;
+
+ /* Check what events have happened */
+ error = regmap_bulk_read(haptics->regmap, DA7280_IRQ_EVENT1,
+ events, sizeof(events));
+ if (error) {
+ dev_err(dev, "failed to read interrupt data: %d\n", error);
+ goto out;
+ }
+
+ /* Clear events */
+ error = regmap_write(haptics->regmap, DA7280_IRQ_EVENT1, events[0]);
+ if (error) {
+ dev_err(dev, "failed to clear interrupts: %d\n", error);
+ goto out;
+ }
+
+ if (events[0] & DA7280_E_SEQ_FAULT_MASK) {
+ /*
+ * Stop first if haptic is active, otherwise, the fault may
+ * happen continually even though the bit is cleared.
+ */
+ error = regmap_update_bits(haptics->regmap, DA7280_TOP_CTL1,
+ DA7280_OPERATION_MODE_MASK, 0);
+ if (error)
+ dev_err(dev, "failed to clear op mode on fault: %d\n",
+ error);
+ }
+
+ if (events[0] & DA7280_E_SEQ_DONE_MASK)
+ haptics->active = false;
+
+ if (events[0] & DA7280_E_WARNING_MASK) {
+ if (events[1] & DA7280_E_LIM_DRIVE_MASK ||
+ events[1] & DA7280_E_LIM_DRIVE_ACC_MASK)
+ dev_warn(dev, "Please reduce the driver level\n");
+ if (events[1] & DA7280_E_MEM_TYPE_MASK)
+ dev_warn(dev, "Please check the mem data format\n");
+ if (events[1] & DA7280_E_OVERTEMP_WARN_MASK)
+ dev_warn(dev, "Over-temperature warning\n");
+ }
+
+ if (events[0] & DA7280_E_SEQ_FAULT_MASK) {
+ if (events[2] & DA7280_E_SEQ_ID_FAULT_MASK)
+ dev_info(dev, "Please reload PS_SEQ_ID & mem data\n");
+ if (events[2] & DA7280_E_MEM_FAULT_MASK)
+ dev_info(dev, "Please reload the mem data\n");
+ if (events[2] & DA7280_E_PWM_FAULT_MASK)
+ dev_info(dev, "Please restart PWM interface\n");
+ }
+
+out:
+ return IRQ_HANDLED;
+}
+
+static int da7280_init(struct da7280_haptic *haptics)
+{
+ unsigned int val = 0;
+ u32 v2i_factor;
+ int error, i;
+ u8 mask = 0;
+
+ /*
+ * If device type is DA7280_DEV_MAX then simply use currently
+ * programmed mode.
+ */
+ if (haptics->dev_type == DA7280_DEV_MAX) {
+ error = regmap_read(haptics->regmap, DA7280_TOP_CFG1, &val);
+ if (error)
+ goto out_err;
+
+ haptics->dev_type = val & DA7280_ACTUATOR_TYPE_MASK ?
+ DA7280_ERM_COIN : DA7280_LRA;
+ }
+
+ /* Apply user settings */
+ if (haptics->dev_type == DA7280_LRA &&
+ haptics->resonant_freq_l != DA7280_SKIP_INIT) {
+ error = regmap_write(haptics->regmap, DA7280_FRQ_LRA_PER_H,
+ haptics->resonant_freq_h);
+ if (error)
+ goto out_err;
+ error = regmap_write(haptics->regmap, DA7280_FRQ_LRA_PER_L,
+ haptics->resonant_freq_l);
+ if (error)
+ goto out_err;
+ } else if (haptics->dev_type == DA7280_ERM_COIN) {
+ error = regmap_update_bits(haptics->regmap, DA7280_TOP_INT_CFG1,
+ DA7280_BEMF_FAULT_LIM_MASK, 0);
+ if (error)
+ goto out_err;
+
+ mask = DA7280_TST_CALIB_IMPEDANCE_DIS_MASK |
+ DA7280_V2I_FACTOR_FREEZE_MASK;
+ val = DA7280_TST_CALIB_IMPEDANCE_DIS_MASK |
+ DA7280_V2I_FACTOR_FREEZE_MASK;
+ error = regmap_update_bits(haptics->regmap, DA7280_TOP_CFG4,
+ mask, val);
+ if (error)
+ goto out_err;
+
+ haptics->acc_en = false;
+ haptics->rapid_stop_en = false;
+ haptics->amp_pid_en = false;
+ }
+
+ mask = DA7280_ACTUATOR_TYPE_MASK |
+ DA7280_BEMF_SENSE_EN_MASK |
+ DA7280_FREQ_TRACK_EN_MASK |
+ DA7280_ACCELERATION_EN_MASK |
+ DA7280_RAPID_STOP_EN_MASK |
+ DA7280_AMP_PID_EN_MASK;
+ val = FIELD_PREP(DA7280_ACTUATOR_TYPE_MASK,
+ (haptics->dev_type ? 1 : 0)) |
+ FIELD_PREP(DA7280_BEMF_SENSE_EN_MASK,
+ (haptics->bemf_sense_en ? 1 : 0)) |
+ FIELD_PREP(DA7280_FREQ_TRACK_EN_MASK,
+ (haptics->freq_track_en ? 1 : 0)) |
+ FIELD_PREP(DA7280_ACCELERATION_EN_MASK,
+ (haptics->acc_en ? 1 : 0)) |
+ FIELD_PREP(DA7280_RAPID_STOP_EN_MASK,
+ (haptics->rapid_stop_en ? 1 : 0)) |
+ FIELD_PREP(DA7280_AMP_PID_EN_MASK,
+ (haptics->amp_pid_en ? 1 : 0));
+
+ error = regmap_update_bits(haptics->regmap, DA7280_TOP_CFG1, mask, val);
+ if (error)
+ goto out_err;
+
+ error = regmap_update_bits(haptics->regmap, DA7280_TOP_CFG5,
+ DA7280_V2I_FACTOR_OFFSET_EN_MASK,
+ haptics->acc_en ?
+ DA7280_V2I_FACTOR_OFFSET_EN_MASK : 0);
+ if (error)
+ goto out_err;
+
+ error = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CFG2,
+ DA7280_MEM_DATA_SIGNED_MASK,
+ haptics->acc_en ?
+ 0 : DA7280_MEM_DATA_SIGNED_MASK);
+ if (error)
+ goto out_err;
+
+ if (haptics->nommax != DA7280_SKIP_INIT) {
+ error = regmap_write(haptics->regmap, DA7280_ACTUATOR1,
+ haptics->nommax);
+ if (error)
+ goto out_err;
+ }
+
+ if (haptics->absmax != DA7280_SKIP_INIT) {
+ error = regmap_write(haptics->regmap, DA7280_ACTUATOR2,
+ haptics->absmax);
+ if (error)
+ goto out_err;
+ }
+
+ error = regmap_update_bits(haptics->regmap, DA7280_ACTUATOR3,
+ DA7280_IMAX_MASK, haptics->imax);
+ if (error)
+ goto out_err;
+
+ v2i_factor = haptics->impd * (haptics->imax + 4) / 1610400;
+ error = regmap_write(haptics->regmap, DA7280_CALIB_V2I_L,
+ v2i_factor & 0xff);
+ if (error)
+ goto out_err;
+ error = regmap_write(haptics->regmap, DA7280_CALIB_V2I_H,
+ v2i_factor >> 8);
+ if (error)
+ goto out_err;
+
+ error = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CTL1,
+ DA7280_STANDBY_EN_MASK,
+ DA7280_STANDBY_EN_MASK);
+ if (error)
+ goto out_err;
+
+ if (haptics->mem_update) {
+ error = da7280_haptic_mem_update(haptics);
+ if (error)
+ goto out_err;
+ }
+
+ /* Set PS_SEQ_ID and PS_SEQ_LOOP */
+ val = FIELD_PREP(DA7280_PS_SEQ_ID_MASK, haptics->ps_seq_id) |
+ FIELD_PREP(DA7280_PS_SEQ_LOOP_MASK, haptics->ps_seq_loop);
+ error = regmap_write(haptics->regmap, DA7280_SEQ_CTL2, val);
+ if (error)
+ goto out_err;
+
+ /* GPI(N) CTL */
+ for (i = 0; i < 3; i++) {
+ val = FIELD_PREP(DA7280_GPI0_SEQUENCE_ID_MASK,
+ haptics->gpi_ctl[i].seq_id) |
+ FIELD_PREP(DA7280_GPI0_MODE_MASK,
+ haptics->gpi_ctl[i].mode) |
+ FIELD_PREP(DA7280_GPI0_POLARITY_MASK,
+ haptics->gpi_ctl[i].polarity);
+ error = regmap_write(haptics->regmap,
+ DA7280_GPI_0_CTL + i, val);
+ if (error)
+ goto out_err;
+ }
+
+ /* Mask ADC_SAT_M bit as default */
+ error = regmap_update_bits(haptics->regmap,
+ DA7280_IRQ_MASK2,
+ DA7280_ADC_SAT_M_MASK,
+ DA7280_ADC_SAT_M_MASK);
+ if (error)
+ goto out_err;
+
+ /* Clear Interrupts */
+ error = regmap_write(haptics->regmap, DA7280_IRQ_EVENT1, 0xff);
+ if (error)
+ goto out_err;
+
+ error = regmap_update_bits(haptics->regmap,
+ DA7280_IRQ_MASK1,
+ DA7280_SEQ_FAULT_M_MASK |
+ DA7280_SEQ_DONE_M_MASK,
+ 0);
+ if (error)
+ goto out_err;
+
+ haptics->active = false;
+ return 0;
+
+out_err:
+ dev_err(haptics->dev, "chip initialization error: %d\n", error);
+ return error;
+}
+
+static int da7280_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct device *dev = &client->dev;
+ struct da7280_haptic *haptics;
+ struct input_dev *input_dev;
+ struct pwm_state state;
+ struct ff_device *ff;
+ int error;
+
+ if (!client->irq) {
+ dev_err(dev, "No IRQ configured\n");
+ return -EINVAL;
+ }
+
+ haptics = devm_kzalloc(dev, sizeof(*haptics), GFP_KERNEL);
+ if (!haptics)
+ return -ENOMEM;
+
+ haptics->dev = dev;
+
+ da7280_parse_properties(dev, haptics);
+
+ if (haptics->const_op_mode == DA7280_PWM_MODE) {
+ haptics->pwm_dev = devm_pwm_get(dev, NULL);
+ error = PTR_ERR_OR_ZERO(haptics->pwm_dev);
+ if (error) {
+ if (error != -EPROBE_DEFER)
+ dev_err(dev, "Unable to request PWM: %d\n",
+ error);
+ return error;
+ }
+
+ /* Sync up PWM state and ensure it is off. */
+ pwm_init_state(haptics->pwm_dev, &state);
+ state.enabled = false;
+ error = pwm_apply_state(haptics->pwm_dev, &state);
+ if (error) {
+ dev_err(dev, "Failed to apply PWM state: %d\n", error);
+ return error;
+ }
+
+ /*
+ * Check PWM period, PWM freq = 1000000 / state.period.
+ * The valid PWM freq range: 10k ~ 250kHz.
+ */
+ if (state.period > 100000 || state.period < 4000) {
+ dev_err(dev, "Unsupported PWM period: %lld\n",
+ state.period);
+ return -EINVAL;
+ }
+ }
+
+ INIT_WORK(&haptics->work, da7280_haptic_work);
+
+ haptics->client = client;
+ i2c_set_clientdata(client, haptics);
+
+ haptics->regmap = devm_regmap_init_i2c(client,
+ &da7280_haptic_regmap_config);
+ error = PTR_ERR_OR_ZERO(haptics->regmap);
+ if (error) {
+ dev_err(dev, "Failed to allocate register map: %d\n", error);
+ return error;
+ }
+
+ error = da7280_init(haptics);
+ if (error) {
+ dev_err(dev, "Failed to initialize device: %d\n", error);
+ return error;
+ }
+
+ /* Initialize input device for haptic device */
+ input_dev = devm_input_allocate_device(dev);
+ if (!input_dev) {
+ dev_err(dev, "Failed to allocate input device\n");
+ return -ENOMEM;
+ }
+
+ input_dev->name = "da7280-haptic";
+ input_dev->dev.parent = client->dev.parent;
+ input_dev->open = da7280_haptic_open;
+ input_dev->close = da7280_haptic_close;
+ input_set_drvdata(input_dev, haptics);
+ haptics->input_dev = input_dev;
+
+ input_set_capability(haptics->input_dev, EV_FF, FF_PERIODIC);
+ input_set_capability(haptics->input_dev, EV_FF, FF_CUSTOM);
+ input_set_capability(haptics->input_dev, EV_FF, FF_CONSTANT);
+ input_set_capability(haptics->input_dev, EV_FF, FF_GAIN);
+
+ error = input_ff_create(haptics->input_dev,
+ DA7280_FF_EFFECT_COUNT_MAX);
+ if (error) {
+ dev_err(dev, "Failed to create FF input device: %d\n", error);
+ return error;
+ }
+
+ ff = input_dev->ff;
+ ff->upload = da7280_haptics_upload_effect;
+ ff->playback = da7280_haptics_playback;
+
+ error = input_register_device(input_dev);
+ if (error) {
+ dev_err(dev, "Failed to register input device: %d\n", error);
+ return error;
+ }
+
+ error = devm_request_threaded_irq(dev, client->irq,
+ NULL, da7280_irq_handler,
+ IRQF_ONESHOT,
+ "da7280-haptics", haptics);
+ if (error) {
+ dev_err(dev, "Failed to request IRQ %d: %d\n",
+ client->irq, error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused da7280_suspend(struct device *dev)
+{
+ struct da7280_haptic *haptics = dev_get_drvdata(dev);
+
+ mutex_lock(&haptics->input_dev->mutex);
+
+ /*
+ * Make sure no new requests will be submitted while device is
+ * suspended.
+ */
+ spin_lock_irq(&haptics->input_dev->event_lock);
+ haptics->suspended = true;
+ spin_unlock_irq(&haptics->input_dev->event_lock);
+
+ da7280_haptic_stop(haptics);
+
+ mutex_unlock(&haptics->input_dev->mutex);
+
+ return 0;
+}
+
+static int __maybe_unused da7280_resume(struct device *dev)
+{
+ struct da7280_haptic *haptics = dev_get_drvdata(dev);
+ int retval;
+
+ mutex_lock(&haptics->input_dev->mutex);
+
+ retval = da7280_haptic_start(haptics);
+ if (!retval) {
+ spin_lock_irq(&haptics->input_dev->event_lock);
+ haptics->suspended = false;
+ spin_unlock_irq(&haptics->input_dev->event_lock);
+ }
+
+ mutex_unlock(&haptics->input_dev->mutex);
+ return retval;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id da7280_of_match[] = {
+ { .compatible = "dlg,da7280", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, da7280_of_match);
+#endif
+
+static const struct i2c_device_id da7280_i2c_id[] = {
+ { "da7280", },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, da7280_i2c_id);
+
+static SIMPLE_DEV_PM_OPS(da7280_pm_ops, da7280_suspend, da7280_resume);
+
+static struct i2c_driver da7280_driver = {
+ .driver = {
+ .name = "da7280",
+ .of_match_table = of_match_ptr(da7280_of_match),
+ .pm = &da7280_pm_ops,
+ },
+ .probe = da7280_probe,
+ .id_table = da7280_i2c_id,
+};
+module_i2c_driver(da7280_driver);
+
+MODULE_DESCRIPTION("DA7280 haptics driver");
+MODULE_AUTHOR("Roy Im <Roy.Im.Opensource@diasemi.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/da9052_onkey.c b/drivers/input/misc/da9052_onkey.c
new file mode 100644
index 000000000..6d1152850
--- /dev/null
+++ b/drivers/input/misc/da9052_onkey.c
@@ -0,0 +1,155 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ON pin driver for Dialog DA9052 PMICs
+ *
+ * Copyright(c) 2012 Dialog Semiconductor Ltd.
+ *
+ * Author: David Dajun Chen <dchen@diasemi.com>
+ */
+
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/workqueue.h>
+
+#include <linux/mfd/da9052/da9052.h>
+#include <linux/mfd/da9052/reg.h>
+
+struct da9052_onkey {
+ struct da9052 *da9052;
+ struct input_dev *input;
+ struct delayed_work work;
+};
+
+static void da9052_onkey_query(struct da9052_onkey *onkey)
+{
+ int ret;
+
+ ret = da9052_reg_read(onkey->da9052, DA9052_STATUS_A_REG);
+ if (ret < 0) {
+ dev_err(onkey->da9052->dev,
+ "Failed to read onkey event err=%d\n", ret);
+ } else {
+ /*
+ * Since interrupt for deassertion of ONKEY pin is not
+ * generated, onkey event state determines the onkey
+ * button state.
+ */
+ bool pressed = !(ret & DA9052_STATUSA_NONKEY);
+
+ input_report_key(onkey->input, KEY_POWER, pressed);
+ input_sync(onkey->input);
+
+ /*
+ * Interrupt is generated only when the ONKEY pin
+ * is asserted. Hence the deassertion of the pin
+ * is simulated through work queue.
+ */
+ if (pressed)
+ schedule_delayed_work(&onkey->work,
+ msecs_to_jiffies(50));
+ }
+}
+
+static void da9052_onkey_work(struct work_struct *work)
+{
+ struct da9052_onkey *onkey = container_of(work, struct da9052_onkey,
+ work.work);
+
+ da9052_onkey_query(onkey);
+}
+
+static irqreturn_t da9052_onkey_irq(int irq, void *data)
+{
+ struct da9052_onkey *onkey = data;
+
+ da9052_onkey_query(onkey);
+
+ return IRQ_HANDLED;
+}
+
+static int da9052_onkey_probe(struct platform_device *pdev)
+{
+ struct da9052 *da9052 = dev_get_drvdata(pdev->dev.parent);
+ struct da9052_onkey *onkey;
+ struct input_dev *input_dev;
+ int error;
+
+ if (!da9052) {
+ dev_err(&pdev->dev, "Failed to get the driver's data\n");
+ return -EINVAL;
+ }
+
+ onkey = kzalloc(sizeof(*onkey), GFP_KERNEL);
+ input_dev = input_allocate_device();
+ if (!onkey || !input_dev) {
+ dev_err(&pdev->dev, "Failed to allocate memory\n");
+ error = -ENOMEM;
+ goto err_free_mem;
+ }
+
+ onkey->input = input_dev;
+ onkey->da9052 = da9052;
+ INIT_DELAYED_WORK(&onkey->work, da9052_onkey_work);
+
+ input_dev->name = "da9052-onkey";
+ input_dev->phys = "da9052-onkey/input0";
+ input_dev->dev.parent = &pdev->dev;
+
+ input_dev->evbit[0] = BIT_MASK(EV_KEY);
+ __set_bit(KEY_POWER, input_dev->keybit);
+
+ error = da9052_request_irq(onkey->da9052, DA9052_IRQ_NONKEY, "ONKEY",
+ da9052_onkey_irq, onkey);
+ if (error < 0) {
+ dev_err(onkey->da9052->dev,
+ "Failed to register ONKEY IRQ: %d\n", error);
+ goto err_free_mem;
+ }
+
+ error = input_register_device(onkey->input);
+ if (error) {
+ dev_err(&pdev->dev, "Unable to register input device, %d\n",
+ error);
+ goto err_free_irq;
+ }
+
+ platform_set_drvdata(pdev, onkey);
+ return 0;
+
+err_free_irq:
+ da9052_free_irq(onkey->da9052, DA9052_IRQ_NONKEY, onkey);
+ cancel_delayed_work_sync(&onkey->work);
+err_free_mem:
+ input_free_device(input_dev);
+ kfree(onkey);
+
+ return error;
+}
+
+static int da9052_onkey_remove(struct platform_device *pdev)
+{
+ struct da9052_onkey *onkey = platform_get_drvdata(pdev);
+
+ da9052_free_irq(onkey->da9052, DA9052_IRQ_NONKEY, onkey);
+ cancel_delayed_work_sync(&onkey->work);
+
+ input_unregister_device(onkey->input);
+ kfree(onkey);
+
+ return 0;
+}
+
+static struct platform_driver da9052_onkey_driver = {
+ .probe = da9052_onkey_probe,
+ .remove = da9052_onkey_remove,
+ .driver = {
+ .name = "da9052-onkey",
+ },
+};
+module_platform_driver(da9052_onkey_driver);
+
+MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>");
+MODULE_DESCRIPTION("Onkey driver for DA9052");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:da9052-onkey");
diff --git a/drivers/input/misc/da9055_onkey.c b/drivers/input/misc/da9055_onkey.c
new file mode 100644
index 000000000..7a0d3a1d5
--- /dev/null
+++ b/drivers/input/misc/da9055_onkey.c
@@ -0,0 +1,161 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ON pin driver for Dialog DA9055 PMICs
+ *
+ * Copyright(c) 2012 Dialog Semiconductor Ltd.
+ *
+ * Author: David Dajun Chen <dchen@diasemi.com>
+ */
+
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <linux/mfd/da9055/core.h>
+#include <linux/mfd/da9055/reg.h>
+
+struct da9055_onkey {
+ struct da9055 *da9055;
+ struct input_dev *input;
+ struct delayed_work work;
+};
+
+static void da9055_onkey_query(struct da9055_onkey *onkey)
+{
+ int key_stat;
+
+ key_stat = da9055_reg_read(onkey->da9055, DA9055_REG_STATUS_A);
+ if (key_stat < 0) {
+ dev_err(onkey->da9055->dev,
+ "Failed to read onkey event %d\n", key_stat);
+ } else {
+ key_stat &= DA9055_NOKEY_STS;
+ /*
+ * Onkey status bit is cleared when onkey button is released.
+ */
+ if (!key_stat) {
+ input_report_key(onkey->input, KEY_POWER, 0);
+ input_sync(onkey->input);
+ }
+ }
+
+ /*
+ * Interrupt is generated only when the ONKEY pin is asserted.
+ * Hence the deassertion of the pin is simulated through work queue.
+ */
+ if (key_stat)
+ schedule_delayed_work(&onkey->work, msecs_to_jiffies(10));
+
+}
+
+static void da9055_onkey_work(struct work_struct *work)
+{
+ struct da9055_onkey *onkey = container_of(work, struct da9055_onkey,
+ work.work);
+
+ da9055_onkey_query(onkey);
+}
+
+static irqreturn_t da9055_onkey_irq(int irq, void *data)
+{
+ struct da9055_onkey *onkey = data;
+
+ input_report_key(onkey->input, KEY_POWER, 1);
+ input_sync(onkey->input);
+
+ da9055_onkey_query(onkey);
+
+ return IRQ_HANDLED;
+}
+
+static int da9055_onkey_probe(struct platform_device *pdev)
+{
+ struct da9055 *da9055 = dev_get_drvdata(pdev->dev.parent);
+ struct da9055_onkey *onkey;
+ struct input_dev *input_dev;
+ int irq, err;
+
+ irq = platform_get_irq_byname(pdev, "ONKEY");
+ if (irq < 0)
+ return -EINVAL;
+
+ onkey = devm_kzalloc(&pdev->dev, sizeof(*onkey), GFP_KERNEL);
+ if (!onkey) {
+ dev_err(&pdev->dev, "Failed to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ input_dev = input_allocate_device();
+ if (!input_dev) {
+ dev_err(&pdev->dev, "Failed to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ onkey->input = input_dev;
+ onkey->da9055 = da9055;
+ input_dev->name = "da9055-onkey";
+ input_dev->phys = "da9055-onkey/input0";
+ input_dev->dev.parent = &pdev->dev;
+
+ input_dev->evbit[0] = BIT_MASK(EV_KEY);
+ __set_bit(KEY_POWER, input_dev->keybit);
+
+ INIT_DELAYED_WORK(&onkey->work, da9055_onkey_work);
+
+ err = request_threaded_irq(irq, NULL, da9055_onkey_irq,
+ IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
+ "ONKEY", onkey);
+ if (err < 0) {
+ dev_err(&pdev->dev,
+ "Failed to register ONKEY IRQ %d, error = %d\n",
+ irq, err);
+ goto err_free_input;
+ }
+
+ err = input_register_device(input_dev);
+ if (err) {
+ dev_err(&pdev->dev, "Unable to register input device, %d\n",
+ err);
+ goto err_free_irq;
+ }
+
+ platform_set_drvdata(pdev, onkey);
+
+ return 0;
+
+err_free_irq:
+ free_irq(irq, onkey);
+ cancel_delayed_work_sync(&onkey->work);
+err_free_input:
+ input_free_device(input_dev);
+
+ return err;
+}
+
+static int da9055_onkey_remove(struct platform_device *pdev)
+{
+ struct da9055_onkey *onkey = platform_get_drvdata(pdev);
+ int irq = platform_get_irq_byname(pdev, "ONKEY");
+
+ irq = regmap_irq_get_virq(onkey->da9055->irq_data, irq);
+ free_irq(irq, onkey);
+ cancel_delayed_work_sync(&onkey->work);
+ input_unregister_device(onkey->input);
+
+ return 0;
+}
+
+static struct platform_driver da9055_onkey_driver = {
+ .probe = da9055_onkey_probe,
+ .remove = da9055_onkey_remove,
+ .driver = {
+ .name = "da9055-onkey",
+ },
+};
+
+module_platform_driver(da9055_onkey_driver);
+
+MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>");
+MODULE_DESCRIPTION("Onkey driver for DA9055");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:da9055-onkey");
diff --git a/drivers/input/misc/da9063_onkey.c b/drivers/input/misc/da9063_onkey.c
new file mode 100644
index 000000000..b14a38960
--- /dev/null
+++ b/drivers/input/misc/da9063_onkey.c
@@ -0,0 +1,276 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * OnKey device driver for DA9063, DA9062 and DA9061 PMICs
+ * Copyright (C) 2015 Dialog Semiconductor Ltd.
+ */
+
+#include <linux/devm-helpers.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/workqueue.h>
+#include <linux/regmap.h>
+#include <linux/of.h>
+#include <linux/mfd/da9063/core.h>
+#include <linux/mfd/da9063/registers.h>
+#include <linux/mfd/da9062/core.h>
+#include <linux/mfd/da9062/registers.h>
+
+struct da906x_chip_config {
+ /* REGS */
+ int onkey_status;
+ int onkey_pwr_signalling;
+ int onkey_fault_log;
+ int onkey_shutdown;
+ /* MASKS */
+ int onkey_nonkey_mask;
+ int onkey_nonkey_lock_mask;
+ int onkey_key_reset_mask;
+ int onkey_shutdown_mask;
+ /* NAMES */
+ const char *name;
+};
+
+struct da9063_onkey {
+ struct delayed_work work;
+ struct input_dev *input;
+ struct device *dev;
+ struct regmap *regmap;
+ const struct da906x_chip_config *config;
+ char phys[32];
+ bool key_power;
+};
+
+static const struct da906x_chip_config da9063_regs = {
+ /* REGS */
+ .onkey_status = DA9063_REG_STATUS_A,
+ .onkey_pwr_signalling = DA9063_REG_CONTROL_B,
+ .onkey_fault_log = DA9063_REG_FAULT_LOG,
+ .onkey_shutdown = DA9063_REG_CONTROL_F,
+ /* MASKS */
+ .onkey_nonkey_mask = DA9063_NONKEY,
+ .onkey_nonkey_lock_mask = DA9063_NONKEY_LOCK,
+ .onkey_key_reset_mask = DA9063_KEY_RESET,
+ .onkey_shutdown_mask = DA9063_SHUTDOWN,
+ /* NAMES */
+ .name = DA9063_DRVNAME_ONKEY,
+};
+
+static const struct da906x_chip_config da9062_regs = {
+ /* REGS */
+ .onkey_status = DA9062AA_STATUS_A,
+ .onkey_pwr_signalling = DA9062AA_CONTROL_B,
+ .onkey_fault_log = DA9062AA_FAULT_LOG,
+ .onkey_shutdown = DA9062AA_CONTROL_F,
+ /* MASKS */
+ .onkey_nonkey_mask = DA9062AA_NONKEY_MASK,
+ .onkey_nonkey_lock_mask = DA9062AA_NONKEY_LOCK_MASK,
+ .onkey_key_reset_mask = DA9062AA_KEY_RESET_MASK,
+ .onkey_shutdown_mask = DA9062AA_SHUTDOWN_MASK,
+ /* NAMES */
+ .name = "da9062-onkey",
+};
+
+static const struct of_device_id da9063_compatible_reg_id_table[] = {
+ { .compatible = "dlg,da9063-onkey", .data = &da9063_regs },
+ { .compatible = "dlg,da9062-onkey", .data = &da9062_regs },
+ { },
+};
+MODULE_DEVICE_TABLE(of, da9063_compatible_reg_id_table);
+
+static void da9063_poll_on(struct work_struct *work)
+{
+ struct da9063_onkey *onkey = container_of(work,
+ struct da9063_onkey,
+ work.work);
+ const struct da906x_chip_config *config = onkey->config;
+ unsigned int val;
+ int fault_log = 0;
+ bool poll = true;
+ int error;
+
+ /* Poll to see when the pin is released */
+ error = regmap_read(onkey->regmap,
+ config->onkey_status,
+ &val);
+ if (error) {
+ dev_err(onkey->dev,
+ "Failed to read ON status: %d\n", error);
+ goto err_poll;
+ }
+
+ if (!(val & config->onkey_nonkey_mask)) {
+ error = regmap_update_bits(onkey->regmap,
+ config->onkey_pwr_signalling,
+ config->onkey_nonkey_lock_mask,
+ 0);
+ if (error) {
+ dev_err(onkey->dev,
+ "Failed to reset the Key Delay %d\n", error);
+ goto err_poll;
+ }
+
+ input_report_key(onkey->input, KEY_POWER, 0);
+ input_sync(onkey->input);
+
+ poll = false;
+ }
+
+ /*
+ * If the fault log KEY_RESET is detected, then clear it
+ * and shut down the system.
+ */
+ error = regmap_read(onkey->regmap,
+ config->onkey_fault_log,
+ &fault_log);
+ if (error) {
+ dev_warn(&onkey->input->dev,
+ "Cannot read FAULT_LOG: %d\n", error);
+ } else if (fault_log & config->onkey_key_reset_mask) {
+ error = regmap_write(onkey->regmap,
+ config->onkey_fault_log,
+ config->onkey_key_reset_mask);
+ if (error) {
+ dev_warn(&onkey->input->dev,
+ "Cannot reset KEY_RESET fault log: %d\n",
+ error);
+ } else {
+ /* at this point we do any S/W housekeeping
+ * and then send shutdown command
+ */
+ dev_dbg(&onkey->input->dev,
+ "Sending SHUTDOWN to PMIC ...\n");
+ error = regmap_write(onkey->regmap,
+ config->onkey_shutdown,
+ config->onkey_shutdown_mask);
+ if (error)
+ dev_err(&onkey->input->dev,
+ "Cannot SHUTDOWN PMIC: %d\n",
+ error);
+ }
+ }
+
+err_poll:
+ if (poll)
+ schedule_delayed_work(&onkey->work, msecs_to_jiffies(50));
+}
+
+static irqreturn_t da9063_onkey_irq_handler(int irq, void *data)
+{
+ struct da9063_onkey *onkey = data;
+ const struct da906x_chip_config *config = onkey->config;
+ unsigned int val;
+ int error;
+
+ error = regmap_read(onkey->regmap,
+ config->onkey_status,
+ &val);
+ if (onkey->key_power && !error && (val & config->onkey_nonkey_mask)) {
+ input_report_key(onkey->input, KEY_POWER, 1);
+ input_sync(onkey->input);
+ schedule_delayed_work(&onkey->work, 0);
+ dev_dbg(onkey->dev, "KEY_POWER long press.\n");
+ } else {
+ input_report_key(onkey->input, KEY_POWER, 1);
+ input_sync(onkey->input);
+ input_report_key(onkey->input, KEY_POWER, 0);
+ input_sync(onkey->input);
+ dev_dbg(onkey->dev, "KEY_POWER short press.\n");
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int da9063_onkey_probe(struct platform_device *pdev)
+{
+ struct da9063_onkey *onkey;
+ const struct of_device_id *match;
+ int irq;
+ int error;
+
+ match = of_match_node(da9063_compatible_reg_id_table,
+ pdev->dev.of_node);
+ if (!match)
+ return -ENXIO;
+
+ onkey = devm_kzalloc(&pdev->dev, sizeof(struct da9063_onkey),
+ GFP_KERNEL);
+ if (!onkey) {
+ dev_err(&pdev->dev, "Failed to allocate memory.\n");
+ return -ENOMEM;
+ }
+
+ onkey->config = match->data;
+ onkey->dev = &pdev->dev;
+
+ onkey->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!onkey->regmap) {
+ dev_err(&pdev->dev, "Parent regmap unavailable.\n");
+ return -ENXIO;
+ }
+
+ onkey->key_power = !of_property_read_bool(pdev->dev.of_node,
+ "dlg,disable-key-power");
+
+ onkey->input = devm_input_allocate_device(&pdev->dev);
+ if (!onkey->input) {
+ dev_err(&pdev->dev, "Failed to allocated input device.\n");
+ return -ENOMEM;
+ }
+
+ onkey->input->name = onkey->config->name;
+ snprintf(onkey->phys, sizeof(onkey->phys), "%s/input0",
+ onkey->config->name);
+ onkey->input->phys = onkey->phys;
+ onkey->input->dev.parent = &pdev->dev;
+
+ input_set_capability(onkey->input, EV_KEY, KEY_POWER);
+
+ error = devm_delayed_work_autocancel(&pdev->dev, &onkey->work,
+ da9063_poll_on);
+ if (error) {
+ dev_err(&pdev->dev,
+ "Failed to add cancel poll action: %d\n",
+ error);
+ return error;
+ }
+
+ irq = platform_get_irq_byname(pdev, "ONKEY");
+ if (irq < 0)
+ return irq;
+
+ error = devm_request_threaded_irq(&pdev->dev, irq,
+ NULL, da9063_onkey_irq_handler,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ "ONKEY", onkey);
+ if (error) {
+ dev_err(&pdev->dev,
+ "Failed to request IRQ %d: %d\n", irq, error);
+ return error;
+ }
+
+ error = input_register_device(onkey->input);
+ if (error) {
+ dev_err(&pdev->dev,
+ "Failed to register input device: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static struct platform_driver da9063_onkey_driver = {
+ .probe = da9063_onkey_probe,
+ .driver = {
+ .name = DA9063_DRVNAME_ONKEY,
+ .of_match_table = da9063_compatible_reg_id_table,
+ },
+};
+module_platform_driver(da9063_onkey_driver);
+
+MODULE_AUTHOR("S Twiss <stwiss.opensource@diasemi.com>");
+MODULE_DESCRIPTION("Onkey device driver for Dialog DA9063, DA9062 and DA9061");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DA9063_DRVNAME_ONKEY);
diff --git a/drivers/input/misc/dm355evm_keys.c b/drivers/input/misc/dm355evm_keys.c
new file mode 100644
index 000000000..397ca7c78
--- /dev/null
+++ b/drivers/input/misc/dm355evm_keys.c
@@ -0,0 +1,238 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * dm355evm_keys.c - support buttons and IR remote on DM355 EVM board
+ *
+ * Copyright (c) 2008 by David Brownell
+ */
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+
+#include <linux/mfd/dm355evm_msp.h>
+#include <linux/module.h>
+
+
+/*
+ * The MSP430 firmware on the DM355 EVM monitors on-board pushbuttons
+ * and an IR receptor used for the remote control. When any key is
+ * pressed, or its autorepeat kicks in, an event is sent. This driver
+ * read those events from the small (32 event) queue and reports them.
+ *
+ * Note that physically there can only be one of these devices.
+ *
+ * This driver was tested with firmware revision A4.
+ */
+struct dm355evm_keys {
+ struct input_dev *input;
+ struct device *dev;
+};
+
+/* These initial keycodes can be remapped */
+static const struct key_entry dm355evm_keys[] = {
+ /*
+ * Pushbuttons on the EVM board ... note that the labels for these
+ * are SW10/SW11/etc on the PC board. The left/right orientation
+ * comes only from the firmware's documentation, and presumes the
+ * power connector is immediately in front of you and the IR sensor
+ * is to the right. (That is, rotate the board counter-clockwise
+ * by 90 degrees from the SW10/etc and "DM355 EVM" labels.)
+ */
+ { KE_KEY, 0x00d8, { KEY_OK } }, /* SW12 */
+ { KE_KEY, 0x00b8, { KEY_UP } }, /* SW13 */
+ { KE_KEY, 0x00e8, { KEY_DOWN } }, /* SW11 */
+ { KE_KEY, 0x0078, { KEY_LEFT } }, /* SW14 */
+ { KE_KEY, 0x00f0, { KEY_RIGHT } }, /* SW10 */
+
+ /*
+ * IR buttons ... codes assigned to match the universal remote
+ * provided with the EVM (Philips PM4S) using DVD code 0020.
+ *
+ * These event codes match firmware documentation, but other
+ * remote controls could easily send more RC5-encoded events.
+ * The PM4S manual was used in several cases to help select
+ * a keycode reflecting the intended usage.
+ *
+ * RC5 codes are 14 bits, with two start bits (0x3 prefix)
+ * and a toggle bit (masked out below).
+ */
+ { KE_KEY, 0x300c, { KEY_POWER } }, /* NOTE: docs omit this */
+ { KE_KEY, 0x3000, { KEY_NUMERIC_0 } },
+ { KE_KEY, 0x3001, { KEY_NUMERIC_1 } },
+ { KE_KEY, 0x3002, { KEY_NUMERIC_2 } },
+ { KE_KEY, 0x3003, { KEY_NUMERIC_3 } },
+ { KE_KEY, 0x3004, { KEY_NUMERIC_4 } },
+ { KE_KEY, 0x3005, { KEY_NUMERIC_5 } },
+ { KE_KEY, 0x3006, { KEY_NUMERIC_6 } },
+ { KE_KEY, 0x3007, { KEY_NUMERIC_7 } },
+ { KE_KEY, 0x3008, { KEY_NUMERIC_8 } },
+ { KE_KEY, 0x3009, { KEY_NUMERIC_9 } },
+ { KE_KEY, 0x3022, { KEY_ENTER } },
+ { KE_KEY, 0x30ec, { KEY_MODE } }, /* "tv/vcr/..." */
+ { KE_KEY, 0x300f, { KEY_SELECT } }, /* "info" */
+ { KE_KEY, 0x3020, { KEY_CHANNELUP } }, /* "up" */
+ { KE_KEY, 0x302e, { KEY_MENU } }, /* "in/out" */
+ { KE_KEY, 0x3011, { KEY_VOLUMEDOWN } }, /* "left" */
+ { KE_KEY, 0x300d, { KEY_MUTE } }, /* "ok" */
+ { KE_KEY, 0x3010, { KEY_VOLUMEUP } }, /* "right" */
+ { KE_KEY, 0x301e, { KEY_SUBTITLE } }, /* "cc" */
+ { KE_KEY, 0x3021, { KEY_CHANNELDOWN } },/* "down" */
+ { KE_KEY, 0x3022, { KEY_PREVIOUS } },
+ { KE_KEY, 0x3026, { KEY_SLEEP } },
+ { KE_KEY, 0x3172, { KEY_REWIND } }, /* NOTE: docs wrongly say 0x30ca */
+ { KE_KEY, 0x3175, { KEY_PLAY } },
+ { KE_KEY, 0x3174, { KEY_FASTFORWARD } },
+ { KE_KEY, 0x3177, { KEY_RECORD } },
+ { KE_KEY, 0x3176, { KEY_STOP } },
+ { KE_KEY, 0x3169, { KEY_PAUSE } },
+};
+
+/*
+ * Because we communicate with the MSP430 using I2C, and all I2C calls
+ * in Linux sleep, we use a threaded IRQ handler. The IRQ itself is
+ * active low, but we go through the GPIO controller so we can trigger
+ * on falling edges and not worry about enabling/disabling the IRQ in
+ * the keypress handling path.
+ */
+static irqreturn_t dm355evm_keys_irq(int irq, void *_keys)
+{
+ static u16 last_event;
+ struct dm355evm_keys *keys = _keys;
+ const struct key_entry *ke;
+ unsigned int keycode;
+ int status;
+ u16 event;
+
+ /* For simplicity we ignore INPUT_COUNT and just read
+ * events until we get the "queue empty" indicator.
+ * Reading INPUT_LOW decrements the count.
+ */
+ for (;;) {
+ status = dm355evm_msp_read(DM355EVM_MSP_INPUT_HIGH);
+ if (status < 0) {
+ dev_dbg(keys->dev, "input high err %d\n",
+ status);
+ break;
+ }
+ event = status << 8;
+
+ status = dm355evm_msp_read(DM355EVM_MSP_INPUT_LOW);
+ if (status < 0) {
+ dev_dbg(keys->dev, "input low err %d\n",
+ status);
+ break;
+ }
+ event |= status;
+ if (event == 0xdead)
+ break;
+
+ /* Press and release a button: two events, same code.
+ * Press and hold (autorepeat), then release: N events
+ * (N > 2), same code. For RC5 buttons the toggle bits
+ * distinguish (for example) "1-autorepeat" from "1 1";
+ * but PCB buttons don't support that bit.
+ *
+ * So we must synthesize release events. We do that by
+ * mapping events to a press/release event pair; then
+ * to avoid adding extra events, skip the second event
+ * of each pair.
+ */
+ if (event == last_event) {
+ last_event = 0;
+ continue;
+ }
+ last_event = event;
+
+ /* ignore the RC5 toggle bit */
+ event &= ~0x0800;
+
+ /* find the key, or report it as unknown */
+ ke = sparse_keymap_entry_from_scancode(keys->input, event);
+ keycode = ke ? ke->keycode : KEY_UNKNOWN;
+ dev_dbg(keys->dev,
+ "input event 0x%04x--> keycode %d\n",
+ event, keycode);
+
+ /* report press + release */
+ input_report_key(keys->input, keycode, 1);
+ input_sync(keys->input);
+ input_report_key(keys->input, keycode, 0);
+ input_sync(keys->input);
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*----------------------------------------------------------------------*/
+
+static int dm355evm_keys_probe(struct platform_device *pdev)
+{
+ struct dm355evm_keys *keys;
+ struct input_dev *input;
+ int irq;
+ int error;
+
+ keys = devm_kzalloc(&pdev->dev, sizeof (*keys), GFP_KERNEL);
+ if (!keys)
+ return -ENOMEM;
+
+ input = devm_input_allocate_device(&pdev->dev);
+ if (!input)
+ return -ENOMEM;
+
+ keys->dev = &pdev->dev;
+ keys->input = input;
+
+ input->name = "DM355 EVM Controls";
+ input->phys = "dm355evm/input0";
+
+ input->id.bustype = BUS_I2C;
+ input->id.product = 0x0355;
+ input->id.version = dm355evm_msp_read(DM355EVM_MSP_FIRMREV);
+
+ error = sparse_keymap_setup(input, dm355evm_keys, NULL);
+ if (error)
+ return error;
+
+ /* REVISIT: flush the event queue? */
+
+ /* set up "threaded IRQ handler" */
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ error = devm_request_threaded_irq(&pdev->dev, irq,
+ NULL, dm355evm_keys_irq,
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ dev_name(&pdev->dev), keys);
+ if (error)
+ return error;
+
+ /* register */
+ error = input_register_device(input);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+/* REVISIT: add suspend/resume when DaVinci supports it. The IRQ should
+ * be able to wake up the system. When device_may_wakeup(&pdev->dev), call
+ * enable_irq_wake() on suspend, and disable_irq_wake() on resume.
+ */
+
+/*
+ * I2C is used to talk to the MSP430, but this platform device is
+ * exposed by an MFD driver that manages I2C communications.
+ */
+static struct platform_driver dm355evm_keys_driver = {
+ .probe = dm355evm_keys_probe,
+ .driver = {
+ .name = "dm355evm_keys",
+ },
+};
+module_platform_driver(dm355evm_keys_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/drv260x.c b/drivers/input/misc/drv260x.c
new file mode 100644
index 000000000..1923924fd
--- /dev/null
+++ b/drivers/input/misc/drv260x.c
@@ -0,0 +1,670 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * DRV260X haptics driver family
+ *
+ * Author: Dan Murphy <dmurphy@ti.com>
+ *
+ * Copyright: (C) 2014 Texas Instruments, Inc.
+ */
+
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/regulator/consumer.h>
+
+#include <dt-bindings/input/ti-drv260x.h>
+
+#define DRV260X_STATUS 0x0
+#define DRV260X_MODE 0x1
+#define DRV260X_RT_PB_IN 0x2
+#define DRV260X_LIB_SEL 0x3
+#define DRV260X_WV_SEQ_1 0x4
+#define DRV260X_WV_SEQ_2 0x5
+#define DRV260X_WV_SEQ_3 0x6
+#define DRV260X_WV_SEQ_4 0x7
+#define DRV260X_WV_SEQ_5 0x8
+#define DRV260X_WV_SEQ_6 0x9
+#define DRV260X_WV_SEQ_7 0xa
+#define DRV260X_WV_SEQ_8 0xb
+#define DRV260X_GO 0xc
+#define DRV260X_OVERDRIVE_OFF 0xd
+#define DRV260X_SUSTAIN_P_OFF 0xe
+#define DRV260X_SUSTAIN_N_OFF 0xf
+#define DRV260X_BRAKE_OFF 0x10
+#define DRV260X_A_TO_V_CTRL 0x11
+#define DRV260X_A_TO_V_MIN_INPUT 0x12
+#define DRV260X_A_TO_V_MAX_INPUT 0x13
+#define DRV260X_A_TO_V_MIN_OUT 0x14
+#define DRV260X_A_TO_V_MAX_OUT 0x15
+#define DRV260X_RATED_VOLT 0x16
+#define DRV260X_OD_CLAMP_VOLT 0x17
+#define DRV260X_CAL_COMP 0x18
+#define DRV260X_CAL_BACK_EMF 0x19
+#define DRV260X_FEEDBACK_CTRL 0x1a
+#define DRV260X_CTRL1 0x1b
+#define DRV260X_CTRL2 0x1c
+#define DRV260X_CTRL3 0x1d
+#define DRV260X_CTRL4 0x1e
+#define DRV260X_CTRL5 0x1f
+#define DRV260X_LRA_LOOP_PERIOD 0x20
+#define DRV260X_VBAT_MON 0x21
+#define DRV260X_LRA_RES_PERIOD 0x22
+#define DRV260X_MAX_REG 0x23
+
+#define DRV260X_GO_BIT 0x01
+
+/* Library Selection */
+#define DRV260X_LIB_SEL_MASK 0x07
+#define DRV260X_LIB_SEL_RAM 0x0
+#define DRV260X_LIB_SEL_OD 0x1
+#define DRV260X_LIB_SEL_40_60 0x2
+#define DRV260X_LIB_SEL_60_80 0x3
+#define DRV260X_LIB_SEL_100_140 0x4
+#define DRV260X_LIB_SEL_140_PLUS 0x5
+
+#define DRV260X_LIB_SEL_HIZ_MASK 0x10
+#define DRV260X_LIB_SEL_HIZ_EN 0x01
+#define DRV260X_LIB_SEL_HIZ_DIS 0
+
+/* Mode register */
+#define DRV260X_STANDBY (1 << 6)
+#define DRV260X_STANDBY_MASK 0x40
+#define DRV260X_INTERNAL_TRIGGER 0x00
+#define DRV260X_EXT_TRIGGER_EDGE 0x01
+#define DRV260X_EXT_TRIGGER_LEVEL 0x02
+#define DRV260X_PWM_ANALOG_IN 0x03
+#define DRV260X_AUDIOHAPTIC 0x04
+#define DRV260X_RT_PLAYBACK 0x05
+#define DRV260X_DIAGNOSTICS 0x06
+#define DRV260X_AUTO_CAL 0x07
+
+/* Audio to Haptics Control */
+#define DRV260X_AUDIO_HAPTICS_PEAK_10MS (0 << 2)
+#define DRV260X_AUDIO_HAPTICS_PEAK_20MS (1 << 2)
+#define DRV260X_AUDIO_HAPTICS_PEAK_30MS (2 << 2)
+#define DRV260X_AUDIO_HAPTICS_PEAK_40MS (3 << 2)
+
+#define DRV260X_AUDIO_HAPTICS_FILTER_100HZ 0x00
+#define DRV260X_AUDIO_HAPTICS_FILTER_125HZ 0x01
+#define DRV260X_AUDIO_HAPTICS_FILTER_150HZ 0x02
+#define DRV260X_AUDIO_HAPTICS_FILTER_200HZ 0x03
+
+/* Min/Max Input/Output Voltages */
+#define DRV260X_AUDIO_HAPTICS_MIN_IN_VOLT 0x19
+#define DRV260X_AUDIO_HAPTICS_MAX_IN_VOLT 0x64
+#define DRV260X_AUDIO_HAPTICS_MIN_OUT_VOLT 0x19
+#define DRV260X_AUDIO_HAPTICS_MAX_OUT_VOLT 0xFF
+
+/* Feedback register */
+#define DRV260X_FB_REG_ERM_MODE 0x7f
+#define DRV260X_FB_REG_LRA_MODE (1 << 7)
+
+#define DRV260X_BRAKE_FACTOR_MASK 0x1f
+#define DRV260X_BRAKE_FACTOR_2X (1 << 0)
+#define DRV260X_BRAKE_FACTOR_3X (2 << 4)
+#define DRV260X_BRAKE_FACTOR_4X (3 << 4)
+#define DRV260X_BRAKE_FACTOR_6X (4 << 4)
+#define DRV260X_BRAKE_FACTOR_8X (5 << 4)
+#define DRV260X_BRAKE_FACTOR_16 (6 << 4)
+#define DRV260X_BRAKE_FACTOR_DIS (7 << 4)
+
+#define DRV260X_LOOP_GAIN_LOW 0xf3
+#define DRV260X_LOOP_GAIN_MED (1 << 2)
+#define DRV260X_LOOP_GAIN_HIGH (2 << 2)
+#define DRV260X_LOOP_GAIN_VERY_HIGH (3 << 2)
+
+#define DRV260X_BEMF_GAIN_0 0xfc
+#define DRV260X_BEMF_GAIN_1 (1 << 0)
+#define DRV260X_BEMF_GAIN_2 (2 << 0)
+#define DRV260X_BEMF_GAIN_3 (3 << 0)
+
+/* Control 1 register */
+#define DRV260X_AC_CPLE_EN (1 << 5)
+#define DRV260X_STARTUP_BOOST (1 << 7)
+
+/* Control 2 register */
+
+#define DRV260X_IDISS_TIME_45 0
+#define DRV260X_IDISS_TIME_75 (1 << 0)
+#define DRV260X_IDISS_TIME_150 (1 << 1)
+#define DRV260X_IDISS_TIME_225 0x03
+
+#define DRV260X_BLANK_TIME_45 (0 << 2)
+#define DRV260X_BLANK_TIME_75 (1 << 2)
+#define DRV260X_BLANK_TIME_150 (2 << 2)
+#define DRV260X_BLANK_TIME_225 (3 << 2)
+
+#define DRV260X_SAMP_TIME_150 (0 << 4)
+#define DRV260X_SAMP_TIME_200 (1 << 4)
+#define DRV260X_SAMP_TIME_250 (2 << 4)
+#define DRV260X_SAMP_TIME_300 (3 << 4)
+
+#define DRV260X_BRAKE_STABILIZER (1 << 6)
+#define DRV260X_UNIDIR_IN (0 << 7)
+#define DRV260X_BIDIR_IN (1 << 7)
+
+/* Control 3 Register */
+#define DRV260X_LRA_OPEN_LOOP (1 << 0)
+#define DRV260X_ANANLOG_IN (1 << 1)
+#define DRV260X_LRA_DRV_MODE (1 << 2)
+#define DRV260X_RTP_UNSIGNED_DATA (1 << 3)
+#define DRV260X_SUPPLY_COMP_DIS (1 << 4)
+#define DRV260X_ERM_OPEN_LOOP (1 << 5)
+#define DRV260X_NG_THRESH_0 (0 << 6)
+#define DRV260X_NG_THRESH_2 (1 << 6)
+#define DRV260X_NG_THRESH_4 (2 << 6)
+#define DRV260X_NG_THRESH_8 (3 << 6)
+
+/* Control 4 Register */
+#define DRV260X_AUTOCAL_TIME_150MS (0 << 4)
+#define DRV260X_AUTOCAL_TIME_250MS (1 << 4)
+#define DRV260X_AUTOCAL_TIME_500MS (2 << 4)
+#define DRV260X_AUTOCAL_TIME_1000MS (3 << 4)
+
+/**
+ * struct drv260x_data -
+ * @input_dev: Pointer to the input device
+ * @client: Pointer to the I2C client
+ * @regmap: Register map of the device
+ * @work: Work item used to off load the enable/disable of the vibration
+ * @enable_gpio: Pointer to the gpio used for enable/disabling
+ * @regulator: Pointer to the regulator for the IC
+ * @magnitude: Magnitude of the vibration event
+ * @mode: The operating mode of the IC (LRA_NO_CAL, ERM or LRA)
+ * @library: The vibration library to be used
+ * @rated_voltage: The rated_voltage of the actuator
+ * @overdrive_voltage: The over drive voltage of the actuator
+**/
+struct drv260x_data {
+ struct input_dev *input_dev;
+ struct i2c_client *client;
+ struct regmap *regmap;
+ struct work_struct work;
+ struct gpio_desc *enable_gpio;
+ struct regulator *regulator;
+ u32 magnitude;
+ u32 mode;
+ u32 library;
+ int rated_voltage;
+ int overdrive_voltage;
+};
+
+static const struct reg_default drv260x_reg_defs[] = {
+ { DRV260X_STATUS, 0xe0 },
+ { DRV260X_MODE, 0x40 },
+ { DRV260X_RT_PB_IN, 0x00 },
+ { DRV260X_LIB_SEL, 0x00 },
+ { DRV260X_WV_SEQ_1, 0x01 },
+ { DRV260X_WV_SEQ_2, 0x00 },
+ { DRV260X_WV_SEQ_3, 0x00 },
+ { DRV260X_WV_SEQ_4, 0x00 },
+ { DRV260X_WV_SEQ_5, 0x00 },
+ { DRV260X_WV_SEQ_6, 0x00 },
+ { DRV260X_WV_SEQ_7, 0x00 },
+ { DRV260X_WV_SEQ_8, 0x00 },
+ { DRV260X_GO, 0x00 },
+ { DRV260X_OVERDRIVE_OFF, 0x00 },
+ { DRV260X_SUSTAIN_P_OFF, 0x00 },
+ { DRV260X_SUSTAIN_N_OFF, 0x00 },
+ { DRV260X_BRAKE_OFF, 0x00 },
+ { DRV260X_A_TO_V_CTRL, 0x05 },
+ { DRV260X_A_TO_V_MIN_INPUT, 0x19 },
+ { DRV260X_A_TO_V_MAX_INPUT, 0xff },
+ { DRV260X_A_TO_V_MIN_OUT, 0x19 },
+ { DRV260X_A_TO_V_MAX_OUT, 0xff },
+ { DRV260X_RATED_VOLT, 0x3e },
+ { DRV260X_OD_CLAMP_VOLT, 0x8c },
+ { DRV260X_CAL_COMP, 0x0c },
+ { DRV260X_CAL_BACK_EMF, 0x6c },
+ { DRV260X_FEEDBACK_CTRL, 0x36 },
+ { DRV260X_CTRL1, 0x93 },
+ { DRV260X_CTRL2, 0xfa },
+ { DRV260X_CTRL3, 0xa0 },
+ { DRV260X_CTRL4, 0x20 },
+ { DRV260X_CTRL5, 0x80 },
+ { DRV260X_LRA_LOOP_PERIOD, 0x33 },
+ { DRV260X_VBAT_MON, 0x00 },
+ { DRV260X_LRA_RES_PERIOD, 0x00 },
+};
+
+#define DRV260X_DEF_RATED_VOLT 0x90
+#define DRV260X_DEF_OD_CLAMP_VOLT 0x90
+
+/*
+ * Rated and Overdriver Voltages:
+ * Calculated using the formula r = v * 255 / 5.6
+ * where r is what will be written to the register
+ * and v is the rated or overdriver voltage of the actuator
+ */
+static int drv260x_calculate_voltage(unsigned int voltage)
+{
+ return (voltage * 255 / 5600);
+}
+
+static void drv260x_worker(struct work_struct *work)
+{
+ struct drv260x_data *haptics = container_of(work, struct drv260x_data, work);
+ int error;
+
+ gpiod_set_value(haptics->enable_gpio, 1);
+ /* Data sheet says to wait 250us before trying to communicate */
+ udelay(250);
+
+ error = regmap_write(haptics->regmap,
+ DRV260X_MODE, DRV260X_RT_PLAYBACK);
+ if (error) {
+ dev_err(&haptics->client->dev,
+ "Failed to write set mode: %d\n", error);
+ } else {
+ error = regmap_write(haptics->regmap,
+ DRV260X_RT_PB_IN, haptics->magnitude);
+ if (error)
+ dev_err(&haptics->client->dev,
+ "Failed to set magnitude: %d\n", error);
+ }
+}
+
+static int drv260x_haptics_play(struct input_dev *input, void *data,
+ struct ff_effect *effect)
+{
+ struct drv260x_data *haptics = input_get_drvdata(input);
+
+ haptics->mode = DRV260X_LRA_NO_CAL_MODE;
+
+ if (effect->u.rumble.strong_magnitude > 0)
+ haptics->magnitude = effect->u.rumble.strong_magnitude;
+ else if (effect->u.rumble.weak_magnitude > 0)
+ haptics->magnitude = effect->u.rumble.weak_magnitude;
+ else
+ haptics->magnitude = 0;
+
+ schedule_work(&haptics->work);
+
+ return 0;
+}
+
+static void drv260x_close(struct input_dev *input)
+{
+ struct drv260x_data *haptics = input_get_drvdata(input);
+ int error;
+
+ cancel_work_sync(&haptics->work);
+
+ error = regmap_write(haptics->regmap, DRV260X_MODE, DRV260X_STANDBY);
+ if (error)
+ dev_err(&haptics->client->dev,
+ "Failed to enter standby mode: %d\n", error);
+
+ gpiod_set_value(haptics->enable_gpio, 0);
+}
+
+static const struct reg_sequence drv260x_lra_cal_regs[] = {
+ { DRV260X_MODE, DRV260X_AUTO_CAL },
+ { DRV260X_CTRL3, DRV260X_NG_THRESH_2 },
+ { DRV260X_FEEDBACK_CTRL, DRV260X_FB_REG_LRA_MODE |
+ DRV260X_BRAKE_FACTOR_4X | DRV260X_LOOP_GAIN_HIGH },
+};
+
+static const struct reg_sequence drv260x_lra_init_regs[] = {
+ { DRV260X_MODE, DRV260X_RT_PLAYBACK },
+ { DRV260X_A_TO_V_CTRL, DRV260X_AUDIO_HAPTICS_PEAK_20MS |
+ DRV260X_AUDIO_HAPTICS_FILTER_125HZ },
+ { DRV260X_A_TO_V_MIN_INPUT, DRV260X_AUDIO_HAPTICS_MIN_IN_VOLT },
+ { DRV260X_A_TO_V_MAX_INPUT, DRV260X_AUDIO_HAPTICS_MAX_IN_VOLT },
+ { DRV260X_A_TO_V_MIN_OUT, DRV260X_AUDIO_HAPTICS_MIN_OUT_VOLT },
+ { DRV260X_A_TO_V_MAX_OUT, DRV260X_AUDIO_HAPTICS_MAX_OUT_VOLT },
+ { DRV260X_FEEDBACK_CTRL, DRV260X_FB_REG_LRA_MODE |
+ DRV260X_BRAKE_FACTOR_2X | DRV260X_LOOP_GAIN_MED |
+ DRV260X_BEMF_GAIN_3 },
+ { DRV260X_CTRL1, DRV260X_STARTUP_BOOST },
+ { DRV260X_CTRL2, DRV260X_SAMP_TIME_250 },
+ { DRV260X_CTRL3, DRV260X_NG_THRESH_2 | DRV260X_ANANLOG_IN },
+ { DRV260X_CTRL4, DRV260X_AUTOCAL_TIME_500MS },
+};
+
+static const struct reg_sequence drv260x_erm_cal_regs[] = {
+ { DRV260X_MODE, DRV260X_AUTO_CAL },
+ { DRV260X_A_TO_V_MIN_INPUT, DRV260X_AUDIO_HAPTICS_MIN_IN_VOLT },
+ { DRV260X_A_TO_V_MAX_INPUT, DRV260X_AUDIO_HAPTICS_MAX_IN_VOLT },
+ { DRV260X_A_TO_V_MIN_OUT, DRV260X_AUDIO_HAPTICS_MIN_OUT_VOLT },
+ { DRV260X_A_TO_V_MAX_OUT, DRV260X_AUDIO_HAPTICS_MAX_OUT_VOLT },
+ { DRV260X_FEEDBACK_CTRL, DRV260X_BRAKE_FACTOR_3X |
+ DRV260X_LOOP_GAIN_MED | DRV260X_BEMF_GAIN_2 },
+ { DRV260X_CTRL1, DRV260X_STARTUP_BOOST },
+ { DRV260X_CTRL2, DRV260X_SAMP_TIME_250 | DRV260X_BLANK_TIME_75 |
+ DRV260X_IDISS_TIME_75 },
+ { DRV260X_CTRL3, DRV260X_NG_THRESH_2 | DRV260X_ERM_OPEN_LOOP },
+ { DRV260X_CTRL4, DRV260X_AUTOCAL_TIME_500MS },
+};
+
+static int drv260x_init(struct drv260x_data *haptics)
+{
+ int error;
+ unsigned int cal_buf;
+
+ error = regmap_write(haptics->regmap,
+ DRV260X_RATED_VOLT, haptics->rated_voltage);
+ if (error) {
+ dev_err(&haptics->client->dev,
+ "Failed to write DRV260X_RATED_VOLT register: %d\n",
+ error);
+ return error;
+ }
+
+ error = regmap_write(haptics->regmap,
+ DRV260X_OD_CLAMP_VOLT, haptics->overdrive_voltage);
+ if (error) {
+ dev_err(&haptics->client->dev,
+ "Failed to write DRV260X_OD_CLAMP_VOLT register: %d\n",
+ error);
+ return error;
+ }
+
+ switch (haptics->mode) {
+ case DRV260X_LRA_MODE:
+ error = regmap_register_patch(haptics->regmap,
+ drv260x_lra_cal_regs,
+ ARRAY_SIZE(drv260x_lra_cal_regs));
+ if (error) {
+ dev_err(&haptics->client->dev,
+ "Failed to write LRA calibration registers: %d\n",
+ error);
+ return error;
+ }
+
+ break;
+
+ case DRV260X_ERM_MODE:
+ error = regmap_register_patch(haptics->regmap,
+ drv260x_erm_cal_regs,
+ ARRAY_SIZE(drv260x_erm_cal_regs));
+ if (error) {
+ dev_err(&haptics->client->dev,
+ "Failed to write ERM calibration registers: %d\n",
+ error);
+ return error;
+ }
+
+ error = regmap_update_bits(haptics->regmap, DRV260X_LIB_SEL,
+ DRV260X_LIB_SEL_MASK,
+ haptics->library);
+ if (error) {
+ dev_err(&haptics->client->dev,
+ "Failed to write DRV260X_LIB_SEL register: %d\n",
+ error);
+ return error;
+ }
+
+ break;
+
+ default:
+ error = regmap_register_patch(haptics->regmap,
+ drv260x_lra_init_regs,
+ ARRAY_SIZE(drv260x_lra_init_regs));
+ if (error) {
+ dev_err(&haptics->client->dev,
+ "Failed to write LRA init registers: %d\n",
+ error);
+ return error;
+ }
+
+ error = regmap_update_bits(haptics->regmap, DRV260X_LIB_SEL,
+ DRV260X_LIB_SEL_MASK,
+ haptics->library);
+ if (error) {
+ dev_err(&haptics->client->dev,
+ "Failed to write DRV260X_LIB_SEL register: %d\n",
+ error);
+ return error;
+ }
+
+ /* No need to set GO bit here */
+ return 0;
+ }
+
+ error = regmap_write(haptics->regmap, DRV260X_GO, DRV260X_GO_BIT);
+ if (error) {
+ dev_err(&haptics->client->dev,
+ "Failed to write GO register: %d\n",
+ error);
+ return error;
+ }
+
+ do {
+ usleep_range(15000, 15500);
+ error = regmap_read(haptics->regmap, DRV260X_GO, &cal_buf);
+ if (error) {
+ dev_err(&haptics->client->dev,
+ "Failed to read GO register: %d\n",
+ error);
+ return error;
+ }
+ } while (cal_buf == DRV260X_GO_BIT);
+
+ return 0;
+}
+
+static const struct regmap_config drv260x_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = DRV260X_MAX_REG,
+ .reg_defaults = drv260x_reg_defs,
+ .num_reg_defaults = ARRAY_SIZE(drv260x_reg_defs),
+ .cache_type = REGCACHE_NONE,
+};
+
+static int drv260x_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct device *dev = &client->dev;
+ struct drv260x_data *haptics;
+ u32 voltage;
+ int error;
+
+ haptics = devm_kzalloc(dev, sizeof(*haptics), GFP_KERNEL);
+ if (!haptics)
+ return -ENOMEM;
+
+ error = device_property_read_u32(dev, "mode", &haptics->mode);
+ if (error) {
+ dev_err(dev, "Can't fetch 'mode' property: %d\n", error);
+ return error;
+ }
+
+ if (haptics->mode < DRV260X_LRA_MODE ||
+ haptics->mode > DRV260X_ERM_MODE) {
+ dev_err(dev, "Vibrator mode is invalid: %i\n", haptics->mode);
+ return -EINVAL;
+ }
+
+ error = device_property_read_u32(dev, "library-sel", &haptics->library);
+ if (error) {
+ dev_err(dev, "Can't fetch 'library-sel' property: %d\n", error);
+ return error;
+ }
+
+ if (haptics->library < DRV260X_LIB_EMPTY ||
+ haptics->library > DRV260X_ERM_LIB_F) {
+ dev_err(dev,
+ "Library value is invalid: %i\n", haptics->library);
+ return -EINVAL;
+ }
+
+ if (haptics->mode == DRV260X_LRA_MODE &&
+ haptics->library != DRV260X_LIB_EMPTY &&
+ haptics->library != DRV260X_LIB_LRA) {
+ dev_err(dev, "LRA Mode with ERM Library mismatch\n");
+ return -EINVAL;
+ }
+
+ if (haptics->mode == DRV260X_ERM_MODE &&
+ (haptics->library == DRV260X_LIB_EMPTY ||
+ haptics->library == DRV260X_LIB_LRA)) {
+ dev_err(dev, "ERM Mode with LRA Library mismatch\n");
+ return -EINVAL;
+ }
+
+ error = device_property_read_u32(dev, "vib-rated-mv", &voltage);
+ haptics->rated_voltage = error ? DRV260X_DEF_RATED_VOLT :
+ drv260x_calculate_voltage(voltage);
+
+ error = device_property_read_u32(dev, "vib-overdrive-mv", &voltage);
+ haptics->overdrive_voltage = error ? DRV260X_DEF_OD_CLAMP_VOLT :
+ drv260x_calculate_voltage(voltage);
+
+ haptics->regulator = devm_regulator_get(dev, "vbat");
+ if (IS_ERR(haptics->regulator)) {
+ error = PTR_ERR(haptics->regulator);
+ dev_err(dev, "unable to get regulator, error: %d\n", error);
+ return error;
+ }
+
+ haptics->enable_gpio = devm_gpiod_get_optional(dev, "enable",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(haptics->enable_gpio))
+ return PTR_ERR(haptics->enable_gpio);
+
+ haptics->input_dev = devm_input_allocate_device(dev);
+ if (!haptics->input_dev) {
+ dev_err(dev, "Failed to allocate input device\n");
+ return -ENOMEM;
+ }
+
+ haptics->input_dev->name = "drv260x:haptics";
+ haptics->input_dev->close = drv260x_close;
+ input_set_drvdata(haptics->input_dev, haptics);
+ input_set_capability(haptics->input_dev, EV_FF, FF_RUMBLE);
+
+ error = input_ff_create_memless(haptics->input_dev, NULL,
+ drv260x_haptics_play);
+ if (error) {
+ dev_err(dev, "input_ff_create() failed: %d\n", error);
+ return error;
+ }
+
+ INIT_WORK(&haptics->work, drv260x_worker);
+
+ haptics->client = client;
+ i2c_set_clientdata(client, haptics);
+
+ haptics->regmap = devm_regmap_init_i2c(client, &drv260x_regmap_config);
+ if (IS_ERR(haptics->regmap)) {
+ error = PTR_ERR(haptics->regmap);
+ dev_err(dev, "Failed to allocate register map: %d\n", error);
+ return error;
+ }
+
+ error = drv260x_init(haptics);
+ if (error) {
+ dev_err(dev, "Device init failed: %d\n", error);
+ return error;
+ }
+
+ error = input_register_device(haptics->input_dev);
+ if (error) {
+ dev_err(dev, "couldn't register input device: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused drv260x_suspend(struct device *dev)
+{
+ struct drv260x_data *haptics = dev_get_drvdata(dev);
+ int ret = 0;
+
+ mutex_lock(&haptics->input_dev->mutex);
+
+ if (input_device_enabled(haptics->input_dev)) {
+ ret = regmap_update_bits(haptics->regmap,
+ DRV260X_MODE,
+ DRV260X_STANDBY_MASK,
+ DRV260X_STANDBY);
+ if (ret) {
+ dev_err(dev, "Failed to set standby mode\n");
+ goto out;
+ }
+
+ gpiod_set_value(haptics->enable_gpio, 0);
+
+ ret = regulator_disable(haptics->regulator);
+ if (ret) {
+ dev_err(dev, "Failed to disable regulator\n");
+ regmap_update_bits(haptics->regmap,
+ DRV260X_MODE,
+ DRV260X_STANDBY_MASK, 0);
+ }
+ }
+out:
+ mutex_unlock(&haptics->input_dev->mutex);
+ return ret;
+}
+
+static int __maybe_unused drv260x_resume(struct device *dev)
+{
+ struct drv260x_data *haptics = dev_get_drvdata(dev);
+ int ret = 0;
+
+ mutex_lock(&haptics->input_dev->mutex);
+
+ if (input_device_enabled(haptics->input_dev)) {
+ ret = regulator_enable(haptics->regulator);
+ if (ret) {
+ dev_err(dev, "Failed to enable regulator\n");
+ goto out;
+ }
+
+ ret = regmap_update_bits(haptics->regmap,
+ DRV260X_MODE,
+ DRV260X_STANDBY_MASK, 0);
+ if (ret) {
+ dev_err(dev, "Failed to unset standby mode\n");
+ regulator_disable(haptics->regulator);
+ goto out;
+ }
+
+ gpiod_set_value(haptics->enable_gpio, 1);
+ }
+
+out:
+ mutex_unlock(&haptics->input_dev->mutex);
+ return ret;
+}
+
+static SIMPLE_DEV_PM_OPS(drv260x_pm_ops, drv260x_suspend, drv260x_resume);
+
+static const struct i2c_device_id drv260x_id[] = {
+ { "drv2605l", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, drv260x_id);
+
+static const struct of_device_id drv260x_of_match[] = {
+ { .compatible = "ti,drv2604", },
+ { .compatible = "ti,drv2604l", },
+ { .compatible = "ti,drv2605", },
+ { .compatible = "ti,drv2605l", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, drv260x_of_match);
+
+static struct i2c_driver drv260x_driver = {
+ .probe = drv260x_probe,
+ .driver = {
+ .name = "drv260x-haptics",
+ .of_match_table = drv260x_of_match,
+ .pm = &drv260x_pm_ops,
+ },
+ .id_table = drv260x_id,
+};
+module_i2c_driver(drv260x_driver);
+
+MODULE_DESCRIPTION("TI DRV260x haptics driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>");
diff --git a/drivers/input/misc/drv2665.c b/drivers/input/misc/drv2665.c
new file mode 100644
index 000000000..21913e808
--- /dev/null
+++ b/drivers/input/misc/drv2665.c
@@ -0,0 +1,313 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * DRV2665 haptics driver family
+ *
+ * Author: Dan Murphy <dmurphy@ti.com>
+ *
+ * Copyright: (C) 2015 Texas Instruments, Inc.
+ */
+
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/regulator/consumer.h>
+
+/* Contol registers */
+#define DRV2665_STATUS 0x00
+#define DRV2665_CTRL_1 0x01
+#define DRV2665_CTRL_2 0x02
+#define DRV2665_FIFO 0x0b
+
+/* Status Register */
+#define DRV2665_FIFO_FULL BIT(0)
+#define DRV2665_FIFO_EMPTY BIT(1)
+
+/* Control 1 Register */
+#define DRV2665_25_VPP_GAIN 0x00
+#define DRV2665_50_VPP_GAIN 0x01
+#define DRV2665_75_VPP_GAIN 0x02
+#define DRV2665_100_VPP_GAIN 0x03
+#define DRV2665_DIGITAL_IN 0xfc
+#define DRV2665_ANALOG_IN BIT(2)
+
+/* Control 2 Register */
+#define DRV2665_BOOST_EN BIT(1)
+#define DRV2665_STANDBY BIT(6)
+#define DRV2665_DEV_RST BIT(7)
+#define DRV2665_5_MS_IDLE_TOUT 0x00
+#define DRV2665_10_MS_IDLE_TOUT 0x04
+#define DRV2665_15_MS_IDLE_TOUT 0x08
+#define DRV2665_20_MS_IDLE_TOUT 0x0c
+
+/**
+ * struct drv2665_data -
+ * @input_dev: Pointer to the input device
+ * @client: Pointer to the I2C client
+ * @regmap: Register map of the device
+ * @work: Work item used to off load the enable/disable of the vibration
+ * @regulator: Pointer to the regulator for the IC
+ */
+struct drv2665_data {
+ struct input_dev *input_dev;
+ struct i2c_client *client;
+ struct regmap *regmap;
+ struct work_struct work;
+ struct regulator *regulator;
+};
+
+/* 8kHz Sine wave to stream to the FIFO */
+static const u8 drv2665_sine_wave_form[] = {
+ 0x00, 0x10, 0x20, 0x2e, 0x3c, 0x48, 0x53, 0x5b, 0x61, 0x65, 0x66,
+ 0x65, 0x61, 0x5b, 0x53, 0x48, 0x3c, 0x2e, 0x20, 0x10,
+ 0x00, 0xf0, 0xe0, 0xd2, 0xc4, 0xb8, 0xad, 0xa5, 0x9f, 0x9b, 0x9a,
+ 0x9b, 0x9f, 0xa5, 0xad, 0xb8, 0xc4, 0xd2, 0xe0, 0xf0, 0x00,
+};
+
+static const struct reg_default drv2665_reg_defs[] = {
+ { DRV2665_STATUS, 0x02 },
+ { DRV2665_CTRL_1, 0x28 },
+ { DRV2665_CTRL_2, 0x40 },
+ { DRV2665_FIFO, 0x00 },
+};
+
+static void drv2665_worker(struct work_struct *work)
+{
+ struct drv2665_data *haptics =
+ container_of(work, struct drv2665_data, work);
+ unsigned int read_buf;
+ int error;
+
+ error = regmap_read(haptics->regmap, DRV2665_STATUS, &read_buf);
+ if (error) {
+ dev_err(&haptics->client->dev,
+ "Failed to read status: %d\n", error);
+ return;
+ }
+
+ if (read_buf & DRV2665_FIFO_EMPTY) {
+ error = regmap_bulk_write(haptics->regmap,
+ DRV2665_FIFO,
+ drv2665_sine_wave_form,
+ ARRAY_SIZE(drv2665_sine_wave_form));
+ if (error) {
+ dev_err(&haptics->client->dev,
+ "Failed to write FIFO: %d\n", error);
+ return;
+ }
+ }
+}
+
+static int drv2665_haptics_play(struct input_dev *input, void *data,
+ struct ff_effect *effect)
+{
+ struct drv2665_data *haptics = input_get_drvdata(input);
+
+ schedule_work(&haptics->work);
+
+ return 0;
+}
+
+static void drv2665_close(struct input_dev *input)
+{
+ struct drv2665_data *haptics = input_get_drvdata(input);
+ int error;
+
+ cancel_work_sync(&haptics->work);
+
+ error = regmap_update_bits(haptics->regmap, DRV2665_CTRL_2,
+ DRV2665_STANDBY, DRV2665_STANDBY);
+ if (error)
+ dev_err(&haptics->client->dev,
+ "Failed to enter standby mode: %d\n", error);
+}
+
+static const struct reg_sequence drv2665_init_regs[] = {
+ { DRV2665_CTRL_2, 0 | DRV2665_10_MS_IDLE_TOUT },
+ { DRV2665_CTRL_1, DRV2665_25_VPP_GAIN },
+};
+
+static int drv2665_init(struct drv2665_data *haptics)
+{
+ int error;
+
+ error = regmap_register_patch(haptics->regmap,
+ drv2665_init_regs,
+ ARRAY_SIZE(drv2665_init_regs));
+ if (error) {
+ dev_err(&haptics->client->dev,
+ "Failed to write init registers: %d\n",
+ error);
+ return error;
+ }
+
+ return 0;
+}
+
+static const struct regmap_config drv2665_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = DRV2665_FIFO,
+ .reg_defaults = drv2665_reg_defs,
+ .num_reg_defaults = ARRAY_SIZE(drv2665_reg_defs),
+ .cache_type = REGCACHE_NONE,
+};
+
+static int drv2665_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct drv2665_data *haptics;
+ int error;
+
+ haptics = devm_kzalloc(&client->dev, sizeof(*haptics), GFP_KERNEL);
+ if (!haptics)
+ return -ENOMEM;
+
+ haptics->regulator = devm_regulator_get(&client->dev, "vbat");
+ if (IS_ERR(haptics->regulator)) {
+ error = PTR_ERR(haptics->regulator);
+ dev_err(&client->dev,
+ "unable to get regulator, error: %d\n", error);
+ return error;
+ }
+
+ haptics->input_dev = devm_input_allocate_device(&client->dev);
+ if (!haptics->input_dev) {
+ dev_err(&client->dev, "Failed to allocate input device\n");
+ return -ENOMEM;
+ }
+
+ haptics->input_dev->name = "drv2665:haptics";
+ haptics->input_dev->dev.parent = client->dev.parent;
+ haptics->input_dev->close = drv2665_close;
+ input_set_drvdata(haptics->input_dev, haptics);
+ input_set_capability(haptics->input_dev, EV_FF, FF_RUMBLE);
+
+ error = input_ff_create_memless(haptics->input_dev, NULL,
+ drv2665_haptics_play);
+ if (error) {
+ dev_err(&client->dev, "input_ff_create() failed: %d\n",
+ error);
+ return error;
+ }
+
+ INIT_WORK(&haptics->work, drv2665_worker);
+
+ haptics->client = client;
+ i2c_set_clientdata(client, haptics);
+
+ haptics->regmap = devm_regmap_init_i2c(client, &drv2665_regmap_config);
+ if (IS_ERR(haptics->regmap)) {
+ error = PTR_ERR(haptics->regmap);
+ dev_err(&client->dev, "Failed to allocate register map: %d\n",
+ error);
+ return error;
+ }
+
+ error = drv2665_init(haptics);
+ if (error) {
+ dev_err(&client->dev, "Device init failed: %d\n", error);
+ return error;
+ }
+
+ error = input_register_device(haptics->input_dev);
+ if (error) {
+ dev_err(&client->dev, "couldn't register input device: %d\n",
+ error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused drv2665_suspend(struct device *dev)
+{
+ struct drv2665_data *haptics = dev_get_drvdata(dev);
+ int ret = 0;
+
+ mutex_lock(&haptics->input_dev->mutex);
+
+ if (input_device_enabled(haptics->input_dev)) {
+ ret = regmap_update_bits(haptics->regmap, DRV2665_CTRL_2,
+ DRV2665_STANDBY, DRV2665_STANDBY);
+ if (ret) {
+ dev_err(dev, "Failed to set standby mode\n");
+ regulator_disable(haptics->regulator);
+ goto out;
+ }
+
+ ret = regulator_disable(haptics->regulator);
+ if (ret) {
+ dev_err(dev, "Failed to disable regulator\n");
+ regmap_update_bits(haptics->regmap,
+ DRV2665_CTRL_2,
+ DRV2665_STANDBY, 0);
+ }
+ }
+out:
+ mutex_unlock(&haptics->input_dev->mutex);
+ return ret;
+}
+
+static int __maybe_unused drv2665_resume(struct device *dev)
+{
+ struct drv2665_data *haptics = dev_get_drvdata(dev);
+ int ret = 0;
+
+ mutex_lock(&haptics->input_dev->mutex);
+
+ if (input_device_enabled(haptics->input_dev)) {
+ ret = regulator_enable(haptics->regulator);
+ if (ret) {
+ dev_err(dev, "Failed to enable regulator\n");
+ goto out;
+ }
+
+ ret = regmap_update_bits(haptics->regmap, DRV2665_CTRL_2,
+ DRV2665_STANDBY, 0);
+ if (ret) {
+ dev_err(dev, "Failed to unset standby mode\n");
+ regulator_disable(haptics->regulator);
+ goto out;
+ }
+
+ }
+
+out:
+ mutex_unlock(&haptics->input_dev->mutex);
+ return ret;
+}
+
+static SIMPLE_DEV_PM_OPS(drv2665_pm_ops, drv2665_suspend, drv2665_resume);
+
+static const struct i2c_device_id drv2665_id[] = {
+ { "drv2665", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, drv2665_id);
+
+#ifdef CONFIG_OF
+static const struct of_device_id drv2665_of_match[] = {
+ { .compatible = "ti,drv2665", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, drv2665_of_match);
+#endif
+
+static struct i2c_driver drv2665_driver = {
+ .probe = drv2665_probe,
+ .driver = {
+ .name = "drv2665-haptics",
+ .of_match_table = of_match_ptr(drv2665_of_match),
+ .pm = &drv2665_pm_ops,
+ },
+ .id_table = drv2665_id,
+};
+module_i2c_driver(drv2665_driver);
+
+MODULE_DESCRIPTION("TI DRV2665 haptics driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>");
diff --git a/drivers/input/misc/drv2667.c b/drivers/input/misc/drv2667.c
new file mode 100644
index 000000000..3f67b9b01
--- /dev/null
+++ b/drivers/input/misc/drv2667.c
@@ -0,0 +1,490 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * DRV2667 haptics driver family
+ *
+ * Author: Dan Murphy <dmurphy@ti.com>
+ *
+ * Copyright: (C) 2014 Texas Instruments, Inc.
+ */
+
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/regulator/consumer.h>
+
+/* Contol registers */
+#define DRV2667_STATUS 0x00
+#define DRV2667_CTRL_1 0x01
+#define DRV2667_CTRL_2 0x02
+/* Waveform sequencer */
+#define DRV2667_WV_SEQ_0 0x03
+#define DRV2667_WV_SEQ_1 0x04
+#define DRV2667_WV_SEQ_2 0x05
+#define DRV2667_WV_SEQ_3 0x06
+#define DRV2667_WV_SEQ_4 0x07
+#define DRV2667_WV_SEQ_5 0x08
+#define DRV2667_WV_SEQ_6 0x09
+#define DRV2667_WV_SEQ_7 0x0A
+#define DRV2667_FIFO 0x0B
+#define DRV2667_PAGE 0xFF
+#define DRV2667_MAX_REG DRV2667_PAGE
+
+#define DRV2667_PAGE_0 0x00
+#define DRV2667_PAGE_1 0x01
+#define DRV2667_PAGE_2 0x02
+#define DRV2667_PAGE_3 0x03
+#define DRV2667_PAGE_4 0x04
+#define DRV2667_PAGE_5 0x05
+#define DRV2667_PAGE_6 0x06
+#define DRV2667_PAGE_7 0x07
+#define DRV2667_PAGE_8 0x08
+
+/* RAM fields */
+#define DRV2667_RAM_HDR_SZ 0x0
+/* RAM Header addresses */
+#define DRV2667_RAM_START_HI 0x01
+#define DRV2667_RAM_START_LO 0x02
+#define DRV2667_RAM_STOP_HI 0x03
+#define DRV2667_RAM_STOP_LO 0x04
+#define DRV2667_RAM_REPEAT_CT 0x05
+/* RAM data addresses */
+#define DRV2667_RAM_AMP 0x06
+#define DRV2667_RAM_FREQ 0x07
+#define DRV2667_RAM_DURATION 0x08
+#define DRV2667_RAM_ENVELOPE 0x09
+
+/* Control 1 Register */
+#define DRV2667_25_VPP_GAIN 0x00
+#define DRV2667_50_VPP_GAIN 0x01
+#define DRV2667_75_VPP_GAIN 0x02
+#define DRV2667_100_VPP_GAIN 0x03
+#define DRV2667_DIGITAL_IN 0xfc
+#define DRV2667_ANALOG_IN (1 << 2)
+
+/* Control 2 Register */
+#define DRV2667_GO (1 << 0)
+#define DRV2667_STANDBY (1 << 6)
+#define DRV2667_DEV_RST (1 << 7)
+
+/* RAM Envelope settings */
+#define DRV2667_NO_ENV 0x00
+#define DRV2667_32_MS_ENV 0x01
+#define DRV2667_64_MS_ENV 0x02
+#define DRV2667_96_MS_ENV 0x03
+#define DRV2667_128_MS_ENV 0x04
+#define DRV2667_160_MS_ENV 0x05
+#define DRV2667_192_MS_ENV 0x06
+#define DRV2667_224_MS_ENV 0x07
+#define DRV2667_256_MS_ENV 0x08
+#define DRV2667_512_MS_ENV 0x09
+#define DRV2667_768_MS_ENV 0x0a
+#define DRV2667_1024_MS_ENV 0x0b
+#define DRV2667_1280_MS_ENV 0x0c
+#define DRV2667_1536_MS_ENV 0x0d
+#define DRV2667_1792_MS_ENV 0x0e
+#define DRV2667_2048_MS_ENV 0x0f
+
+/**
+ * struct drv2667_data -
+ * @input_dev: Pointer to the input device
+ * @client: Pointer to the I2C client
+ * @regmap: Register map of the device
+ * @work: Work item used to off load the enable/disable of the vibration
+ * @regulator: Pointer to the regulator for the IC
+ * @page: Page number
+ * @magnitude: Magnitude of the vibration event
+ * @frequency: Frequency of the vibration event
+**/
+struct drv2667_data {
+ struct input_dev *input_dev;
+ struct i2c_client *client;
+ struct regmap *regmap;
+ struct work_struct work;
+ struct regulator *regulator;
+ u32 page;
+ u32 magnitude;
+ u32 frequency;
+};
+
+static const struct reg_default drv2667_reg_defs[] = {
+ { DRV2667_STATUS, 0x02 },
+ { DRV2667_CTRL_1, 0x28 },
+ { DRV2667_CTRL_2, 0x40 },
+ { DRV2667_WV_SEQ_0, 0x00 },
+ { DRV2667_WV_SEQ_1, 0x00 },
+ { DRV2667_WV_SEQ_2, 0x00 },
+ { DRV2667_WV_SEQ_3, 0x00 },
+ { DRV2667_WV_SEQ_4, 0x00 },
+ { DRV2667_WV_SEQ_5, 0x00 },
+ { DRV2667_WV_SEQ_6, 0x00 },
+ { DRV2667_WV_SEQ_7, 0x00 },
+ { DRV2667_FIFO, 0x00 },
+ { DRV2667_PAGE, 0x00 },
+};
+
+static int drv2667_set_waveform_freq(struct drv2667_data *haptics)
+{
+ unsigned int read_buf;
+ int freq;
+ int error;
+
+ /* Per the data sheet:
+ * Sinusoid Frequency (Hz) = 7.8125 x Frequency
+ */
+ freq = (haptics->frequency * 1000) / 78125;
+ if (freq <= 0) {
+ dev_err(&haptics->client->dev,
+ "ERROR: Frequency calculated to %i\n", freq);
+ return -EINVAL;
+ }
+
+ error = regmap_read(haptics->regmap, DRV2667_PAGE, &read_buf);
+ if (error) {
+ dev_err(&haptics->client->dev,
+ "Failed to read the page number: %d\n", error);
+ return -EIO;
+ }
+
+ if (read_buf == DRV2667_PAGE_0 ||
+ haptics->page != read_buf) {
+ error = regmap_write(haptics->regmap,
+ DRV2667_PAGE, haptics->page);
+ if (error) {
+ dev_err(&haptics->client->dev,
+ "Failed to set the page: %d\n", error);
+ return -EIO;
+ }
+ }
+
+ error = regmap_write(haptics->regmap, DRV2667_RAM_FREQ, freq);
+ if (error)
+ dev_err(&haptics->client->dev,
+ "Failed to set the frequency: %d\n", error);
+
+ /* Reset back to original page */
+ if (read_buf == DRV2667_PAGE_0 ||
+ haptics->page != read_buf) {
+ error = regmap_write(haptics->regmap, DRV2667_PAGE, read_buf);
+ if (error) {
+ dev_err(&haptics->client->dev,
+ "Failed to set the page: %d\n", error);
+ return -EIO;
+ }
+ }
+
+ return error;
+}
+
+static void drv2667_worker(struct work_struct *work)
+{
+ struct drv2667_data *haptics = container_of(work, struct drv2667_data, work);
+ int error;
+
+ if (haptics->magnitude) {
+ error = regmap_write(haptics->regmap,
+ DRV2667_PAGE, haptics->page);
+ if (error) {
+ dev_err(&haptics->client->dev,
+ "Failed to set the page: %d\n", error);
+ return;
+ }
+
+ error = regmap_write(haptics->regmap, DRV2667_RAM_AMP,
+ haptics->magnitude);
+ if (error) {
+ dev_err(&haptics->client->dev,
+ "Failed to set the amplitude: %d\n", error);
+ return;
+ }
+
+ error = regmap_write(haptics->regmap,
+ DRV2667_PAGE, DRV2667_PAGE_0);
+ if (error) {
+ dev_err(&haptics->client->dev,
+ "Failed to set the page: %d\n", error);
+ return;
+ }
+
+ error = regmap_write(haptics->regmap,
+ DRV2667_CTRL_2, DRV2667_GO);
+ if (error) {
+ dev_err(&haptics->client->dev,
+ "Failed to set the GO bit: %d\n", error);
+ }
+ } else {
+ error = regmap_update_bits(haptics->regmap, DRV2667_CTRL_2,
+ DRV2667_GO, 0);
+ if (error) {
+ dev_err(&haptics->client->dev,
+ "Failed to unset the GO bit: %d\n", error);
+ }
+ }
+}
+
+static int drv2667_haptics_play(struct input_dev *input, void *data,
+ struct ff_effect *effect)
+{
+ struct drv2667_data *haptics = input_get_drvdata(input);
+
+ if (effect->u.rumble.strong_magnitude > 0)
+ haptics->magnitude = effect->u.rumble.strong_magnitude;
+ else if (effect->u.rumble.weak_magnitude > 0)
+ haptics->magnitude = effect->u.rumble.weak_magnitude;
+ else
+ haptics->magnitude = 0;
+
+ schedule_work(&haptics->work);
+
+ return 0;
+}
+
+static void drv2667_close(struct input_dev *input)
+{
+ struct drv2667_data *haptics = input_get_drvdata(input);
+ int error;
+
+ cancel_work_sync(&haptics->work);
+
+ error = regmap_update_bits(haptics->regmap, DRV2667_CTRL_2,
+ DRV2667_STANDBY, DRV2667_STANDBY);
+ if (error)
+ dev_err(&haptics->client->dev,
+ "Failed to enter standby mode: %d\n", error);
+}
+
+static const struct reg_sequence drv2667_init_regs[] = {
+ { DRV2667_CTRL_2, 0 },
+ { DRV2667_CTRL_1, DRV2667_25_VPP_GAIN },
+ { DRV2667_WV_SEQ_0, 1 },
+ { DRV2667_WV_SEQ_1, 0 }
+};
+
+static const struct reg_sequence drv2667_page1_init[] = {
+ { DRV2667_RAM_HDR_SZ, 0x05 },
+ { DRV2667_RAM_START_HI, 0x80 },
+ { DRV2667_RAM_START_LO, 0x06 },
+ { DRV2667_RAM_STOP_HI, 0x00 },
+ { DRV2667_RAM_STOP_LO, 0x09 },
+ { DRV2667_RAM_REPEAT_CT, 0 },
+ { DRV2667_RAM_DURATION, 0x05 },
+ { DRV2667_RAM_ENVELOPE, DRV2667_NO_ENV },
+ { DRV2667_RAM_AMP, 0x60 },
+};
+
+static int drv2667_init(struct drv2667_data *haptics)
+{
+ int error;
+
+ /* Set default haptic frequency to 195Hz on Page 1*/
+ haptics->frequency = 195;
+ haptics->page = DRV2667_PAGE_1;
+
+ error = regmap_register_patch(haptics->regmap,
+ drv2667_init_regs,
+ ARRAY_SIZE(drv2667_init_regs));
+ if (error) {
+ dev_err(&haptics->client->dev,
+ "Failed to write init registers: %d\n",
+ error);
+ return error;
+ }
+
+ error = regmap_write(haptics->regmap, DRV2667_PAGE, haptics->page);
+ if (error) {
+ dev_err(&haptics->client->dev, "Failed to set page: %d\n",
+ error);
+ goto error_out;
+ }
+
+ error = drv2667_set_waveform_freq(haptics);
+ if (error)
+ goto error_page;
+
+ error = regmap_register_patch(haptics->regmap,
+ drv2667_page1_init,
+ ARRAY_SIZE(drv2667_page1_init));
+ if (error) {
+ dev_err(&haptics->client->dev,
+ "Failed to write page registers: %d\n",
+ error);
+ return error;
+ }
+
+ error = regmap_write(haptics->regmap, DRV2667_PAGE, DRV2667_PAGE_0);
+ return error;
+
+error_page:
+ regmap_write(haptics->regmap, DRV2667_PAGE, DRV2667_PAGE_0);
+error_out:
+ return error;
+}
+
+static const struct regmap_config drv2667_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = DRV2667_MAX_REG,
+ .reg_defaults = drv2667_reg_defs,
+ .num_reg_defaults = ARRAY_SIZE(drv2667_reg_defs),
+ .cache_type = REGCACHE_NONE,
+};
+
+static int drv2667_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct drv2667_data *haptics;
+ int error;
+
+ haptics = devm_kzalloc(&client->dev, sizeof(*haptics), GFP_KERNEL);
+ if (!haptics)
+ return -ENOMEM;
+
+ haptics->regulator = devm_regulator_get(&client->dev, "vbat");
+ if (IS_ERR(haptics->regulator)) {
+ error = PTR_ERR(haptics->regulator);
+ dev_err(&client->dev,
+ "unable to get regulator, error: %d\n", error);
+ return error;
+ }
+
+ haptics->input_dev = devm_input_allocate_device(&client->dev);
+ if (!haptics->input_dev) {
+ dev_err(&client->dev, "Failed to allocate input device\n");
+ return -ENOMEM;
+ }
+
+ haptics->input_dev->name = "drv2667:haptics";
+ haptics->input_dev->dev.parent = client->dev.parent;
+ haptics->input_dev->close = drv2667_close;
+ input_set_drvdata(haptics->input_dev, haptics);
+ input_set_capability(haptics->input_dev, EV_FF, FF_RUMBLE);
+
+ error = input_ff_create_memless(haptics->input_dev, NULL,
+ drv2667_haptics_play);
+ if (error) {
+ dev_err(&client->dev, "input_ff_create() failed: %d\n",
+ error);
+ return error;
+ }
+
+ INIT_WORK(&haptics->work, drv2667_worker);
+
+ haptics->client = client;
+ i2c_set_clientdata(client, haptics);
+
+ haptics->regmap = devm_regmap_init_i2c(client, &drv2667_regmap_config);
+ if (IS_ERR(haptics->regmap)) {
+ error = PTR_ERR(haptics->regmap);
+ dev_err(&client->dev, "Failed to allocate register map: %d\n",
+ error);
+ return error;
+ }
+
+ error = drv2667_init(haptics);
+ if (error) {
+ dev_err(&client->dev, "Device init failed: %d\n", error);
+ return error;
+ }
+
+ error = input_register_device(haptics->input_dev);
+ if (error) {
+ dev_err(&client->dev, "couldn't register input device: %d\n",
+ error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused drv2667_suspend(struct device *dev)
+{
+ struct drv2667_data *haptics = dev_get_drvdata(dev);
+ int ret = 0;
+
+ mutex_lock(&haptics->input_dev->mutex);
+
+ if (input_device_enabled(haptics->input_dev)) {
+ ret = regmap_update_bits(haptics->regmap, DRV2667_CTRL_2,
+ DRV2667_STANDBY, DRV2667_STANDBY);
+ if (ret) {
+ dev_err(dev, "Failed to set standby mode\n");
+ regulator_disable(haptics->regulator);
+ goto out;
+ }
+
+ ret = regulator_disable(haptics->regulator);
+ if (ret) {
+ dev_err(dev, "Failed to disable regulator\n");
+ regmap_update_bits(haptics->regmap,
+ DRV2667_CTRL_2,
+ DRV2667_STANDBY, 0);
+ }
+ }
+out:
+ mutex_unlock(&haptics->input_dev->mutex);
+ return ret;
+}
+
+static int __maybe_unused drv2667_resume(struct device *dev)
+{
+ struct drv2667_data *haptics = dev_get_drvdata(dev);
+ int ret = 0;
+
+ mutex_lock(&haptics->input_dev->mutex);
+
+ if (input_device_enabled(haptics->input_dev)) {
+ ret = regulator_enable(haptics->regulator);
+ if (ret) {
+ dev_err(dev, "Failed to enable regulator\n");
+ goto out;
+ }
+
+ ret = regmap_update_bits(haptics->regmap, DRV2667_CTRL_2,
+ DRV2667_STANDBY, 0);
+ if (ret) {
+ dev_err(dev, "Failed to unset standby mode\n");
+ regulator_disable(haptics->regulator);
+ goto out;
+ }
+
+ }
+
+out:
+ mutex_unlock(&haptics->input_dev->mutex);
+ return ret;
+}
+
+static SIMPLE_DEV_PM_OPS(drv2667_pm_ops, drv2667_suspend, drv2667_resume);
+
+static const struct i2c_device_id drv2667_id[] = {
+ { "drv2667", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, drv2667_id);
+
+#ifdef CONFIG_OF
+static const struct of_device_id drv2667_of_match[] = {
+ { .compatible = "ti,drv2667", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, drv2667_of_match);
+#endif
+
+static struct i2c_driver drv2667_driver = {
+ .probe = drv2667_probe,
+ .driver = {
+ .name = "drv2667-haptics",
+ .of_match_table = of_match_ptr(drv2667_of_match),
+ .pm = &drv2667_pm_ops,
+ },
+ .id_table = drv2667_id,
+};
+module_i2c_driver(drv2667_driver);
+
+MODULE_DESCRIPTION("TI DRV2667 haptics driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>");
diff --git a/drivers/input/misc/e3x0-button.c b/drivers/input/misc/e3x0-button.c
new file mode 100644
index 000000000..e2fde6e15
--- /dev/null
+++ b/drivers/input/misc/e3x0-button.c
@@ -0,0 +1,135 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2014, National Instruments Corp. All rights reserved.
+ *
+ * Driver for NI Ettus Research USRP E3x0 Button Driver
+ */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+
+static irqreturn_t e3x0_button_release_handler(int irq, void *data)
+{
+ struct input_dev *idev = data;
+
+ input_report_key(idev, KEY_POWER, 0);
+ input_sync(idev);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t e3x0_button_press_handler(int irq, void *data)
+{
+ struct input_dev *idev = data;
+
+ input_report_key(idev, KEY_POWER, 1);
+ pm_wakeup_event(idev->dev.parent, 0);
+ input_sync(idev);
+
+ return IRQ_HANDLED;
+}
+
+static int __maybe_unused e3x0_button_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+
+ if (device_may_wakeup(dev))
+ enable_irq_wake(platform_get_irq_byname(pdev, "press"));
+
+ return 0;
+}
+
+static int __maybe_unused e3x0_button_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+
+ if (device_may_wakeup(dev))
+ disable_irq_wake(platform_get_irq_byname(pdev, "press"));
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(e3x0_button_pm_ops,
+ e3x0_button_suspend, e3x0_button_resume);
+
+static int e3x0_button_probe(struct platform_device *pdev)
+{
+ struct input_dev *input;
+ int irq_press, irq_release;
+ int error;
+
+ irq_press = platform_get_irq_byname(pdev, "press");
+ if (irq_press < 0)
+ return irq_press;
+
+ irq_release = platform_get_irq_byname(pdev, "release");
+ if (irq_release < 0)
+ return irq_release;
+
+ input = devm_input_allocate_device(&pdev->dev);
+ if (!input)
+ return -ENOMEM;
+
+ input->name = "NI Ettus Research USRP E3x0 Button Driver";
+ input->phys = "e3x0_button/input0";
+ input->dev.parent = &pdev->dev;
+
+ input_set_capability(input, EV_KEY, KEY_POWER);
+
+ error = devm_request_irq(&pdev->dev, irq_press,
+ e3x0_button_press_handler, 0,
+ "e3x0-button", input);
+ if (error) {
+ dev_err(&pdev->dev, "Failed to request 'press' IRQ#%d: %d\n",
+ irq_press, error);
+ return error;
+ }
+
+ error = devm_request_irq(&pdev->dev, irq_release,
+ e3x0_button_release_handler, 0,
+ "e3x0-button", input);
+ if (error) {
+ dev_err(&pdev->dev, "Failed to request 'release' IRQ#%d: %d\n",
+ irq_release, error);
+ return error;
+ }
+
+ error = input_register_device(input);
+ if (error) {
+ dev_err(&pdev->dev, "Can't register input device: %d\n", error);
+ return error;
+ }
+
+ device_init_wakeup(&pdev->dev, 1);
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id e3x0_button_match[] = {
+ { .compatible = "ettus,e3x0-button", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, e3x0_button_match);
+#endif
+
+static struct platform_driver e3x0_button_driver = {
+ .driver = {
+ .name = "e3x0-button",
+ .of_match_table = of_match_ptr(e3x0_button_match),
+ .pm = &e3x0_button_pm_ops,
+ },
+ .probe = e3x0_button_probe,
+};
+
+module_platform_driver(e3x0_button_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Moritz Fischer <moritz.fischer@ettus.com>");
+MODULE_DESCRIPTION("NI Ettus Research USRP E3x0 Button driver");
+MODULE_ALIAS("platform:e3x0-button");
diff --git a/drivers/input/misc/gpio-beeper.c b/drivers/input/misc/gpio-beeper.c
new file mode 100644
index 000000000..d2d2954e2
--- /dev/null
+++ b/drivers/input/misc/gpio-beeper.c
@@ -0,0 +1,114 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Generic GPIO beeper driver
+ *
+ * Copyright (C) 2013-2014 Alexander Shiyan <shc_work@mail.ru>
+ */
+
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of.h>
+#include <linux/workqueue.h>
+#include <linux/platform_device.h>
+
+#define BEEPER_MODNAME "gpio-beeper"
+
+struct gpio_beeper {
+ struct work_struct work;
+ struct gpio_desc *desc;
+ bool beeping;
+};
+
+static void gpio_beeper_toggle(struct gpio_beeper *beep, bool on)
+{
+ gpiod_set_value_cansleep(beep->desc, on);
+}
+
+static void gpio_beeper_work(struct work_struct *work)
+{
+ struct gpio_beeper *beep = container_of(work, struct gpio_beeper, work);
+
+ gpio_beeper_toggle(beep, beep->beeping);
+}
+
+static int gpio_beeper_event(struct input_dev *dev, unsigned int type,
+ unsigned int code, int value)
+{
+ struct gpio_beeper *beep = input_get_drvdata(dev);
+
+ if (type != EV_SND || code != SND_BELL)
+ return -ENOTSUPP;
+
+ if (value < 0)
+ return -EINVAL;
+
+ beep->beeping = value;
+ /* Schedule work to actually turn the beeper on or off */
+ schedule_work(&beep->work);
+
+ return 0;
+}
+
+static void gpio_beeper_close(struct input_dev *input)
+{
+ struct gpio_beeper *beep = input_get_drvdata(input);
+
+ cancel_work_sync(&beep->work);
+ gpio_beeper_toggle(beep, false);
+}
+
+static int gpio_beeper_probe(struct platform_device *pdev)
+{
+ struct gpio_beeper *beep;
+ struct input_dev *input;
+
+ beep = devm_kzalloc(&pdev->dev, sizeof(*beep), GFP_KERNEL);
+ if (!beep)
+ return -ENOMEM;
+
+ beep->desc = devm_gpiod_get(&pdev->dev, NULL, GPIOD_OUT_LOW);
+ if (IS_ERR(beep->desc))
+ return PTR_ERR(beep->desc);
+
+ input = devm_input_allocate_device(&pdev->dev);
+ if (!input)
+ return -ENOMEM;
+
+ INIT_WORK(&beep->work, gpio_beeper_work);
+
+ input->name = pdev->name;
+ input->id.bustype = BUS_HOST;
+ input->id.vendor = 0x0001;
+ input->id.product = 0x0001;
+ input->id.version = 0x0100;
+ input->close = gpio_beeper_close;
+ input->event = gpio_beeper_event;
+
+ input_set_capability(input, EV_SND, SND_BELL);
+
+ input_set_drvdata(input, beep);
+
+ return input_register_device(input);
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id gpio_beeper_of_match[] = {
+ { .compatible = BEEPER_MODNAME, },
+ { }
+};
+MODULE_DEVICE_TABLE(of, gpio_beeper_of_match);
+#endif
+
+static struct platform_driver gpio_beeper_platform_driver = {
+ .driver = {
+ .name = BEEPER_MODNAME,
+ .of_match_table = of_match_ptr(gpio_beeper_of_match),
+ },
+ .probe = gpio_beeper_probe,
+};
+module_platform_driver(gpio_beeper_platform_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>");
+MODULE_DESCRIPTION("Generic GPIO beeper driver");
diff --git a/drivers/input/misc/gpio-vibra.c b/drivers/input/misc/gpio-vibra.c
new file mode 100644
index 000000000..f79f75595
--- /dev/null
+++ b/drivers/input/misc/gpio-vibra.c
@@ -0,0 +1,207 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * GPIO vibrator driver
+ *
+ * Copyright (C) 2019 Luca Weiss <luca@z3ntu.xyz>
+ *
+ * Based on PWM vibrator driver:
+ * Copyright (C) 2017 Collabora Ltd.
+ *
+ * Based on previous work from:
+ * Copyright (C) 2012 Dmitry Torokhov <dmitry.torokhov@gmail.com>
+ *
+ * Based on PWM beeper driver:
+ * Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de>
+ */
+
+#include <linux/gpio/consumer.h>
+#include <linux/input.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+struct gpio_vibrator {
+ struct input_dev *input;
+ struct gpio_desc *gpio;
+ struct regulator *vcc;
+
+ struct work_struct play_work;
+ bool running;
+ bool vcc_on;
+};
+
+static int gpio_vibrator_start(struct gpio_vibrator *vibrator)
+{
+ struct device *pdev = vibrator->input->dev.parent;
+ int err;
+
+ if (!vibrator->vcc_on) {
+ err = regulator_enable(vibrator->vcc);
+ if (err) {
+ dev_err(pdev, "failed to enable regulator: %d\n", err);
+ return err;
+ }
+ vibrator->vcc_on = true;
+ }
+
+ gpiod_set_value_cansleep(vibrator->gpio, 1);
+
+ return 0;
+}
+
+static void gpio_vibrator_stop(struct gpio_vibrator *vibrator)
+{
+ gpiod_set_value_cansleep(vibrator->gpio, 0);
+
+ if (vibrator->vcc_on) {
+ regulator_disable(vibrator->vcc);
+ vibrator->vcc_on = false;
+ }
+}
+
+static void gpio_vibrator_play_work(struct work_struct *work)
+{
+ struct gpio_vibrator *vibrator =
+ container_of(work, struct gpio_vibrator, play_work);
+
+ if (vibrator->running)
+ gpio_vibrator_start(vibrator);
+ else
+ gpio_vibrator_stop(vibrator);
+}
+
+static int gpio_vibrator_play_effect(struct input_dev *dev, void *data,
+ struct ff_effect *effect)
+{
+ struct gpio_vibrator *vibrator = input_get_drvdata(dev);
+ int level;
+
+ level = effect->u.rumble.strong_magnitude;
+ if (!level)
+ level = effect->u.rumble.weak_magnitude;
+
+ vibrator->running = level;
+ schedule_work(&vibrator->play_work);
+
+ return 0;
+}
+
+static void gpio_vibrator_close(struct input_dev *input)
+{
+ struct gpio_vibrator *vibrator = input_get_drvdata(input);
+
+ cancel_work_sync(&vibrator->play_work);
+ gpio_vibrator_stop(vibrator);
+ vibrator->running = false;
+}
+
+static int gpio_vibrator_probe(struct platform_device *pdev)
+{
+ struct gpio_vibrator *vibrator;
+ int err;
+
+ vibrator = devm_kzalloc(&pdev->dev, sizeof(*vibrator), GFP_KERNEL);
+ if (!vibrator)
+ return -ENOMEM;
+
+ vibrator->input = devm_input_allocate_device(&pdev->dev);
+ if (!vibrator->input)
+ return -ENOMEM;
+
+ vibrator->vcc = devm_regulator_get(&pdev->dev, "vcc");
+ err = PTR_ERR_OR_ZERO(vibrator->vcc);
+ if (err) {
+ if (err != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "Failed to request regulator: %d\n",
+ err);
+ return err;
+ }
+
+ vibrator->gpio = devm_gpiod_get(&pdev->dev, "enable", GPIOD_OUT_LOW);
+ err = PTR_ERR_OR_ZERO(vibrator->gpio);
+ if (err) {
+ if (err != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "Failed to request main gpio: %d\n",
+ err);
+ return err;
+ }
+
+ INIT_WORK(&vibrator->play_work, gpio_vibrator_play_work);
+
+ vibrator->input->name = "gpio-vibrator";
+ vibrator->input->id.bustype = BUS_HOST;
+ vibrator->input->close = gpio_vibrator_close;
+
+ input_set_drvdata(vibrator->input, vibrator);
+ input_set_capability(vibrator->input, EV_FF, FF_RUMBLE);
+
+ err = input_ff_create_memless(vibrator->input, NULL,
+ gpio_vibrator_play_effect);
+ if (err) {
+ dev_err(&pdev->dev, "Couldn't create FF dev: %d\n", err);
+ return err;
+ }
+
+ err = input_register_device(vibrator->input);
+ if (err) {
+ dev_err(&pdev->dev, "Couldn't register input dev: %d\n", err);
+ return err;
+ }
+
+ platform_set_drvdata(pdev, vibrator);
+
+ return 0;
+}
+
+static int __maybe_unused gpio_vibrator_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct gpio_vibrator *vibrator = platform_get_drvdata(pdev);
+
+ cancel_work_sync(&vibrator->play_work);
+ if (vibrator->running)
+ gpio_vibrator_stop(vibrator);
+
+ return 0;
+}
+
+static int __maybe_unused gpio_vibrator_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct gpio_vibrator *vibrator = platform_get_drvdata(pdev);
+
+ if (vibrator->running)
+ gpio_vibrator_start(vibrator);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(gpio_vibrator_pm_ops,
+ gpio_vibrator_suspend, gpio_vibrator_resume);
+
+#ifdef CONFIG_OF
+static const struct of_device_id gpio_vibra_dt_match_table[] = {
+ { .compatible = "gpio-vibrator" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, gpio_vibra_dt_match_table);
+#endif
+
+static struct platform_driver gpio_vibrator_driver = {
+ .probe = gpio_vibrator_probe,
+ .driver = {
+ .name = "gpio-vibrator",
+ .pm = &gpio_vibrator_pm_ops,
+ .of_match_table = of_match_ptr(gpio_vibra_dt_match_table),
+ },
+};
+module_platform_driver(gpio_vibrator_driver);
+
+MODULE_AUTHOR("Luca Weiss <luca@z3ntu.xy>");
+MODULE_DESCRIPTION("GPIO vibrator driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:gpio-vibrator");
diff --git a/drivers/input/misc/gpio_decoder.c b/drivers/input/misc/gpio_decoder.c
new file mode 100644
index 000000000..ee668eba3
--- /dev/null
+++ b/drivers/input/misc/gpio_decoder.c
@@ -0,0 +1,132 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2016 Texas Instruments Incorporated - http://www.ti.com/
+ *
+ * A generic driver to read multiple gpio lines and translate the
+ * encoded numeric value into an input event.
+ */
+
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/input.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+
+struct gpio_decoder {
+ struct gpio_descs *input_gpios;
+ struct device *dev;
+ u32 axis;
+ u32 last_stable;
+};
+
+static int gpio_decoder_get_gpios_state(struct gpio_decoder *decoder)
+{
+ struct gpio_descs *gpios = decoder->input_gpios;
+ unsigned int ret = 0;
+ int i, val;
+
+ for (i = 0; i < gpios->ndescs; i++) {
+ val = gpiod_get_value_cansleep(gpios->desc[i]);
+ if (val < 0) {
+ dev_err(decoder->dev,
+ "Error reading gpio %d: %d\n",
+ desc_to_gpio(gpios->desc[i]), val);
+ return val;
+ }
+
+ val = !!val;
+ ret = (ret << 1) | val;
+ }
+
+ return ret;
+}
+
+static void gpio_decoder_poll_gpios(struct input_dev *input)
+{
+ struct gpio_decoder *decoder = input_get_drvdata(input);
+ int state;
+
+ state = gpio_decoder_get_gpios_state(decoder);
+ if (state >= 0 && state != decoder->last_stable) {
+ input_report_abs(input, decoder->axis, state);
+ input_sync(input);
+ decoder->last_stable = state;
+ }
+}
+
+static int gpio_decoder_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct gpio_decoder *decoder;
+ struct input_dev *input;
+ u32 max;
+ int err;
+
+ decoder = devm_kzalloc(dev, sizeof(*decoder), GFP_KERNEL);
+ if (!decoder)
+ return -ENOMEM;
+
+ decoder->dev = dev;
+ device_property_read_u32(dev, "linux,axis", &decoder->axis);
+
+ decoder->input_gpios = devm_gpiod_get_array(dev, NULL, GPIOD_IN);
+ if (IS_ERR(decoder->input_gpios)) {
+ dev_err(dev, "unable to acquire input gpios\n");
+ return PTR_ERR(decoder->input_gpios);
+ }
+
+ if (decoder->input_gpios->ndescs < 2) {
+ dev_err(dev, "not enough gpios found\n");
+ return -EINVAL;
+ }
+
+ if (device_property_read_u32(dev, "decoder-max-value", &max))
+ max = (1U << decoder->input_gpios->ndescs) - 1;
+
+ input = devm_input_allocate_device(dev);
+ if (!input)
+ return -ENOMEM;
+
+ input_set_drvdata(input, decoder);
+
+ input->name = pdev->name;
+ input->id.bustype = BUS_HOST;
+ input_set_abs_params(input, decoder->axis, 0, max, 0, 0);
+
+ err = input_setup_polling(input, gpio_decoder_poll_gpios);
+ if (err) {
+ dev_err(dev, "failed to set up polling\n");
+ return err;
+ }
+
+ err = input_register_device(input);
+ if (err) {
+ dev_err(dev, "failed to register input device\n");
+ return err;
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id gpio_decoder_of_match[] = {
+ { .compatible = "gpio-decoder", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, gpio_decoder_of_match);
+#endif
+
+static struct platform_driver gpio_decoder_driver = {
+ .probe = gpio_decoder_probe,
+ .driver = {
+ .name = "gpio-decoder",
+ .of_match_table = of_match_ptr(gpio_decoder_of_match),
+ }
+};
+module_platform_driver(gpio_decoder_driver);
+
+MODULE_DESCRIPTION("GPIO decoder input driver");
+MODULE_AUTHOR("Vignesh R <vigneshr@ti.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/misc/hisi_powerkey.c b/drivers/input/misc/hisi_powerkey.c
new file mode 100644
index 000000000..d3c293a95
--- /dev/null
+++ b/drivers/input/misc/hisi_powerkey.c
@@ -0,0 +1,129 @@
+/*
+ * Hisilicon PMIC powerkey driver
+ *
+ * Copyright (C) 2013 Hisilicon Ltd.
+ * Copyright (C) 2015, 2016 Linaro Ltd.
+ *
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License. See the file "COPYING" in the main directory of this
+ * archive for more details.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/reboot.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_irq.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+
+/* the held interrupt will trigger after 4 seconds */
+#define MAX_HELD_TIME (4 * MSEC_PER_SEC)
+
+static irqreturn_t hi65xx_power_press_isr(int irq, void *q)
+{
+ struct input_dev *input = q;
+
+ pm_wakeup_event(input->dev.parent, MAX_HELD_TIME);
+ input_report_key(input, KEY_POWER, 1);
+ input_sync(input);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t hi65xx_power_release_isr(int irq, void *q)
+{
+ struct input_dev *input = q;
+
+ pm_wakeup_event(input->dev.parent, MAX_HELD_TIME);
+ input_report_key(input, KEY_POWER, 0);
+ input_sync(input);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t hi65xx_restart_toggle_isr(int irq, void *q)
+{
+ struct input_dev *input = q;
+ int value = test_bit(KEY_RESTART, input->key);
+
+ pm_wakeup_event(input->dev.parent, MAX_HELD_TIME);
+ input_report_key(input, KEY_RESTART, !value);
+ input_sync(input);
+
+ return IRQ_HANDLED;
+}
+
+static const struct {
+ const char *name;
+ irqreturn_t (*handler)(int irq, void *q);
+} hi65xx_irq_info[] = {
+ { "down", hi65xx_power_press_isr },
+ { "up", hi65xx_power_release_isr },
+ { "hold 4s", hi65xx_restart_toggle_isr },
+};
+
+static int hi65xx_powerkey_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct input_dev *input;
+ int irq, i, error;
+
+ input = devm_input_allocate_device(dev);
+ if (!input) {
+ dev_err(dev, "failed to allocate input device\n");
+ return -ENOMEM;
+ }
+
+ input->phys = "hisi_on/input0";
+ input->name = "HISI 65xx PowerOn Key";
+
+ input_set_capability(input, EV_KEY, KEY_POWER);
+ input_set_capability(input, EV_KEY, KEY_RESTART);
+
+ for (i = 0; i < ARRAY_SIZE(hi65xx_irq_info); i++) {
+
+ irq = platform_get_irq_byname(pdev, hi65xx_irq_info[i].name);
+ if (irq < 0)
+ return irq;
+
+ error = devm_request_any_context_irq(dev, irq,
+ hi65xx_irq_info[i].handler,
+ IRQF_ONESHOT,
+ hi65xx_irq_info[i].name,
+ input);
+ if (error < 0) {
+ dev_err(dev, "couldn't request irq %s: %d\n",
+ hi65xx_irq_info[i].name, error);
+ return error;
+ }
+ }
+
+ error = input_register_device(input);
+ if (error) {
+ dev_err(dev, "failed to register input device: %d\n", error);
+ return error;
+ }
+
+ device_init_wakeup(dev, 1);
+
+ return 0;
+}
+
+static struct platform_driver hi65xx_powerkey_driver = {
+ .driver = {
+ .name = "hi65xx-powerkey",
+ },
+ .probe = hi65xx_powerkey_probe,
+};
+module_platform_driver(hi65xx_powerkey_driver);
+
+MODULE_AUTHOR("Zhiliang Xue <xuezhiliang@huawei.com");
+MODULE_DESCRIPTION("Hisi PMIC Power key driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/misc/hp_sdc_rtc.c b/drivers/input/misc/hp_sdc_rtc.c
new file mode 100644
index 000000000..199bc17dd
--- /dev/null
+++ b/drivers/input/misc/hp_sdc_rtc.c
@@ -0,0 +1,377 @@
+/*
+ * HP i8042 SDC + MSM-58321 BBRTC driver.
+ *
+ * Copyright (c) 2001 Brian S. Julin
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions, and the following disclaimer,
+ * without modification.
+ * 2. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL").
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ *
+ * References:
+ * System Device Controller Microprocessor Firmware Theory of Operation
+ * for Part Number 1820-4784 Revision B. Dwg No. A-1820-4784-2
+ * efirtc.c by Stephane Eranian/Hewlett Packard
+ *
+ */
+
+#include <linux/hp_sdc.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/time.h>
+#include <linux/miscdevice.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/poll.h>
+#include <linux/rtc.h>
+#include <linux/mutex.h>
+#include <linux/semaphore.h>
+
+MODULE_AUTHOR("Brian S. Julin <bri@calyx.com>");
+MODULE_DESCRIPTION("HP i8042 SDC + MSM-58321 RTC Driver");
+MODULE_LICENSE("Dual BSD/GPL");
+
+#define RTC_VERSION "1.10d"
+
+static unsigned long epoch = 2000;
+
+static struct semaphore i8042tregs;
+
+static void hp_sdc_rtc_isr (int irq, void *dev_id,
+ uint8_t status, uint8_t data)
+{
+ return;
+}
+
+static int hp_sdc_rtc_do_read_bbrtc (struct rtc_time *rtctm)
+{
+ struct semaphore tsem;
+ hp_sdc_transaction t;
+ uint8_t tseq[91];
+ int i;
+
+ i = 0;
+ while (i < 91) {
+ tseq[i++] = HP_SDC_ACT_DATAREG |
+ HP_SDC_ACT_POSTCMD | HP_SDC_ACT_DATAIN;
+ tseq[i++] = 0x01; /* write i8042[0x70] */
+ tseq[i] = i / 7; /* BBRTC reg address */
+ i++;
+ tseq[i++] = HP_SDC_CMD_DO_RTCR; /* Trigger command */
+ tseq[i++] = 2; /* expect 1 stat/dat pair back. */
+ i++; i++; /* buffer for stat/dat pair */
+ }
+ tseq[84] |= HP_SDC_ACT_SEMAPHORE;
+ t.endidx = 91;
+ t.seq = tseq;
+ t.act.semaphore = &tsem;
+ sema_init(&tsem, 0);
+
+ if (hp_sdc_enqueue_transaction(&t)) return -1;
+
+ /* Put ourselves to sleep for results. */
+ if (WARN_ON(down_interruptible(&tsem)))
+ return -1;
+
+ /* Check for nonpresence of BBRTC */
+ if (!((tseq[83] | tseq[90] | tseq[69] | tseq[76] |
+ tseq[55] | tseq[62] | tseq[34] | tseq[41] |
+ tseq[20] | tseq[27] | tseq[6] | tseq[13]) & 0x0f))
+ return -1;
+
+ memset(rtctm, 0, sizeof(struct rtc_time));
+ rtctm->tm_year = (tseq[83] & 0x0f) + (tseq[90] & 0x0f) * 10;
+ rtctm->tm_mon = (tseq[69] & 0x0f) + (tseq[76] & 0x0f) * 10;
+ rtctm->tm_mday = (tseq[55] & 0x0f) + (tseq[62] & 0x0f) * 10;
+ rtctm->tm_wday = (tseq[48] & 0x0f);
+ rtctm->tm_hour = (tseq[34] & 0x0f) + (tseq[41] & 0x0f) * 10;
+ rtctm->tm_min = (tseq[20] & 0x0f) + (tseq[27] & 0x0f) * 10;
+ rtctm->tm_sec = (tseq[6] & 0x0f) + (tseq[13] & 0x0f) * 10;
+
+ return 0;
+}
+
+static int hp_sdc_rtc_read_bbrtc (struct rtc_time *rtctm)
+{
+ struct rtc_time tm, tm_last;
+ int i = 0;
+
+ /* MSM-58321 has no read latch, so must read twice and compare. */
+
+ if (hp_sdc_rtc_do_read_bbrtc(&tm_last)) return -1;
+ if (hp_sdc_rtc_do_read_bbrtc(&tm)) return -1;
+
+ while (memcmp(&tm, &tm_last, sizeof(struct rtc_time))) {
+ if (i++ > 4) return -1;
+ memcpy(&tm_last, &tm, sizeof(struct rtc_time));
+ if (hp_sdc_rtc_do_read_bbrtc(&tm)) return -1;
+ }
+
+ memcpy(rtctm, &tm, sizeof(struct rtc_time));
+
+ return 0;
+}
+
+
+static int64_t hp_sdc_rtc_read_i8042timer (uint8_t loadcmd, int numreg)
+{
+ hp_sdc_transaction t;
+ uint8_t tseq[26] = {
+ HP_SDC_ACT_PRECMD | HP_SDC_ACT_POSTCMD | HP_SDC_ACT_DATAIN,
+ 0,
+ HP_SDC_CMD_READ_T1, 2, 0, 0,
+ HP_SDC_ACT_POSTCMD | HP_SDC_ACT_DATAIN,
+ HP_SDC_CMD_READ_T2, 2, 0, 0,
+ HP_SDC_ACT_POSTCMD | HP_SDC_ACT_DATAIN,
+ HP_SDC_CMD_READ_T3, 2, 0, 0,
+ HP_SDC_ACT_POSTCMD | HP_SDC_ACT_DATAIN,
+ HP_SDC_CMD_READ_T4, 2, 0, 0,
+ HP_SDC_ACT_POSTCMD | HP_SDC_ACT_DATAIN,
+ HP_SDC_CMD_READ_T5, 2, 0, 0
+ };
+
+ t.endidx = numreg * 5;
+
+ tseq[1] = loadcmd;
+ tseq[t.endidx - 4] |= HP_SDC_ACT_SEMAPHORE; /* numreg assumed > 1 */
+
+ t.seq = tseq;
+ t.act.semaphore = &i8042tregs;
+
+ /* Sleep if output regs in use. */
+ if (WARN_ON(down_interruptible(&i8042tregs)))
+ return -1;
+
+ if (hp_sdc_enqueue_transaction(&t)) {
+ up(&i8042tregs);
+ return -1;
+ }
+
+ /* Sleep until results come back. */
+ if (WARN_ON(down_interruptible(&i8042tregs)))
+ return -1;
+
+ up(&i8042tregs);
+
+ return (tseq[5] |
+ ((uint64_t)(tseq[10]) << 8) | ((uint64_t)(tseq[15]) << 16) |
+ ((uint64_t)(tseq[20]) << 24) | ((uint64_t)(tseq[25]) << 32));
+}
+
+
+/* Read the i8042 real-time clock */
+static inline int hp_sdc_rtc_read_rt(struct timespec64 *res) {
+ int64_t raw;
+ uint32_t tenms;
+ unsigned int days;
+
+ raw = hp_sdc_rtc_read_i8042timer(HP_SDC_CMD_LOAD_RT, 5);
+ if (raw < 0) return -1;
+
+ tenms = (uint32_t)raw & 0xffffff;
+ days = (unsigned int)(raw >> 24) & 0xffff;
+
+ res->tv_nsec = (long)(tenms % 100) * 10000 * 1000;
+ res->tv_sec = (tenms / 100) + (time64_t)days * 86400;
+
+ return 0;
+}
+
+
+/* Read the i8042 fast handshake timer */
+static inline int hp_sdc_rtc_read_fhs(struct timespec64 *res) {
+ int64_t raw;
+ unsigned int tenms;
+
+ raw = hp_sdc_rtc_read_i8042timer(HP_SDC_CMD_LOAD_FHS, 2);
+ if (raw < 0) return -1;
+
+ tenms = (unsigned int)raw & 0xffff;
+
+ res->tv_nsec = (long)(tenms % 100) * 10000 * 1000;
+ res->tv_sec = (time64_t)(tenms / 100);
+
+ return 0;
+}
+
+
+/* Read the i8042 match timer (a.k.a. alarm) */
+static inline int hp_sdc_rtc_read_mt(struct timespec64 *res) {
+ int64_t raw;
+ uint32_t tenms;
+
+ raw = hp_sdc_rtc_read_i8042timer(HP_SDC_CMD_LOAD_MT, 3);
+ if (raw < 0) return -1;
+
+ tenms = (uint32_t)raw & 0xffffff;
+
+ res->tv_nsec = (long)(tenms % 100) * 10000 * 1000;
+ res->tv_sec = (time64_t)(tenms / 100);
+
+ return 0;
+}
+
+
+/* Read the i8042 delay timer */
+static inline int hp_sdc_rtc_read_dt(struct timespec64 *res) {
+ int64_t raw;
+ uint32_t tenms;
+
+ raw = hp_sdc_rtc_read_i8042timer(HP_SDC_CMD_LOAD_DT, 3);
+ if (raw < 0) return -1;
+
+ tenms = (uint32_t)raw & 0xffffff;
+
+ res->tv_nsec = (long)(tenms % 100) * 10000 * 1000;
+ res->tv_sec = (time64_t)(tenms / 100);
+
+ return 0;
+}
+
+
+/* Read the i8042 cycle timer (a.k.a. periodic) */
+static inline int hp_sdc_rtc_read_ct(struct timespec64 *res) {
+ int64_t raw;
+ uint32_t tenms;
+
+ raw = hp_sdc_rtc_read_i8042timer(HP_SDC_CMD_LOAD_CT, 3);
+ if (raw < 0) return -1;
+
+ tenms = (uint32_t)raw & 0xffffff;
+
+ res->tv_nsec = (long)(tenms % 100) * 10000 * 1000;
+ res->tv_sec = (time64_t)(tenms / 100);
+
+ return 0;
+}
+
+static int hp_sdc_rtc_proc_show(struct seq_file *m, void *v)
+{
+#define YN(bit) ("no")
+#define NY(bit) ("yes")
+ struct rtc_time tm;
+ struct timespec64 tv;
+
+ memset(&tm, 0, sizeof(struct rtc_time));
+
+ if (hp_sdc_rtc_read_bbrtc(&tm)) {
+ seq_puts(m, "BBRTC\t\t: READ FAILED!\n");
+ } else {
+ seq_printf(m,
+ "rtc_time\t: %ptRt\n"
+ "rtc_date\t: %ptRd\n"
+ "rtc_epoch\t: %04lu\n",
+ &tm, &tm, epoch);
+ }
+
+ if (hp_sdc_rtc_read_rt(&tv)) {
+ seq_puts(m, "i8042 rtc\t: READ FAILED!\n");
+ } else {
+ seq_printf(m, "i8042 rtc\t: %lld.%02ld seconds\n",
+ (s64)tv.tv_sec, (long)tv.tv_nsec/1000000L);
+ }
+
+ if (hp_sdc_rtc_read_fhs(&tv)) {
+ seq_puts(m, "handshake\t: READ FAILED!\n");
+ } else {
+ seq_printf(m, "handshake\t: %lld.%02ld seconds\n",
+ (s64)tv.tv_sec, (long)tv.tv_nsec/1000000L);
+ }
+
+ if (hp_sdc_rtc_read_mt(&tv)) {
+ seq_puts(m, "alarm\t\t: READ FAILED!\n");
+ } else {
+ seq_printf(m, "alarm\t\t: %lld.%02ld seconds\n",
+ (s64)tv.tv_sec, (long)tv.tv_nsec/1000000L);
+ }
+
+ if (hp_sdc_rtc_read_dt(&tv)) {
+ seq_puts(m, "delay\t\t: READ FAILED!\n");
+ } else {
+ seq_printf(m, "delay\t\t: %lld.%02ld seconds\n",
+ (s64)tv.tv_sec, (long)tv.tv_nsec/1000000L);
+ }
+
+ if (hp_sdc_rtc_read_ct(&tv)) {
+ seq_puts(m, "periodic\t: READ FAILED!\n");
+ } else {
+ seq_printf(m, "periodic\t: %lld.%02ld seconds\n",
+ (s64)tv.tv_sec, (long)tv.tv_nsec/1000000L);
+ }
+
+ seq_printf(m,
+ "DST_enable\t: %s\n"
+ "BCD\t\t: %s\n"
+ "24hr\t\t: %s\n"
+ "square_wave\t: %s\n"
+ "alarm_IRQ\t: %s\n"
+ "update_IRQ\t: %s\n"
+ "periodic_IRQ\t: %s\n"
+ "periodic_freq\t: %ld\n"
+ "batt_status\t: %s\n",
+ YN(RTC_DST_EN),
+ NY(RTC_DM_BINARY),
+ YN(RTC_24H),
+ YN(RTC_SQWE),
+ YN(RTC_AIE),
+ YN(RTC_UIE),
+ YN(RTC_PIE),
+ 1UL,
+ 1 ? "okay" : "dead");
+
+ return 0;
+#undef YN
+#undef NY
+}
+
+static int __init hp_sdc_rtc_init(void)
+{
+ int ret;
+
+#ifdef __mc68000__
+ if (!MACH_IS_HP300)
+ return -ENODEV;
+#endif
+
+ sema_init(&i8042tregs, 1);
+
+ if ((ret = hp_sdc_request_timer_irq(&hp_sdc_rtc_isr)))
+ return ret;
+
+ proc_create_single("driver/rtc", 0, NULL, hp_sdc_rtc_proc_show);
+
+ printk(KERN_INFO "HP i8042 SDC + MSM-58321 RTC support loaded "
+ "(RTC v " RTC_VERSION ")\n");
+
+ return 0;
+}
+
+static void __exit hp_sdc_rtc_exit(void)
+{
+ remove_proc_entry ("driver/rtc", NULL);
+ hp_sdc_release_timer_irq(hp_sdc_rtc_isr);
+ printk(KERN_INFO "HP i8042 SDC + MSM-58321 RTC support unloaded\n");
+}
+
+module_init(hp_sdc_rtc_init);
+module_exit(hp_sdc_rtc_exit);
diff --git a/drivers/input/misc/ibm-panel.c b/drivers/input/misc/ibm-panel.c
new file mode 100644
index 000000000..a8fba0054
--- /dev/null
+++ b/drivers/input/misc/ibm-panel.c
@@ -0,0 +1,200 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) IBM Corporation 2020
+ */
+
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/kernel.h>
+#include <linux/limits.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/spinlock.h>
+
+#define DEVICE_NAME "ibm-panel"
+#define PANEL_KEYCODES_COUNT 3
+
+struct ibm_panel {
+ u8 idx;
+ u8 command[11];
+ u32 keycodes[PANEL_KEYCODES_COUNT];
+ spinlock_t lock; /* protects writes to idx and command */
+ struct input_dev *input;
+};
+
+static u8 ibm_panel_calculate_checksum(struct ibm_panel *panel)
+{
+ u8 chksum;
+ u16 sum = 0;
+ unsigned int i;
+
+ for (i = 0; i < sizeof(panel->command) - 1; ++i) {
+ sum += panel->command[i];
+ if (sum & 0xff00) {
+ sum &= 0xff;
+ sum++;
+ }
+ }
+
+ chksum = sum & 0xff;
+ chksum = ~chksum;
+ chksum++;
+
+ return chksum;
+}
+
+static void ibm_panel_process_command(struct ibm_panel *panel)
+{
+ u8 button;
+ u8 chksum;
+
+ if (panel->command[0] != 0xff && panel->command[1] != 0xf0) {
+ dev_dbg(&panel->input->dev, "command invalid: %02x %02x\n",
+ panel->command[0], panel->command[1]);
+ return;
+ }
+
+ chksum = ibm_panel_calculate_checksum(panel);
+ if (chksum != panel->command[sizeof(panel->command) - 1]) {
+ dev_dbg(&panel->input->dev,
+ "command failed checksum: %u != %u\n", chksum,
+ panel->command[sizeof(panel->command) - 1]);
+ return;
+ }
+
+ button = panel->command[2] & 0xf;
+ if (button < PANEL_KEYCODES_COUNT) {
+ input_report_key(panel->input, panel->keycodes[button],
+ !(panel->command[2] & 0x80));
+ input_sync(panel->input);
+ } else {
+ dev_dbg(&panel->input->dev, "unknown button %u\n",
+ button);
+ }
+}
+
+static int ibm_panel_i2c_slave_cb(struct i2c_client *client,
+ enum i2c_slave_event event, u8 *val)
+{
+ unsigned long flags;
+ struct ibm_panel *panel = i2c_get_clientdata(client);
+
+ dev_dbg(&panel->input->dev, "event: %u data: %02x\n", event, *val);
+
+ spin_lock_irqsave(&panel->lock, flags);
+
+ switch (event) {
+ case I2C_SLAVE_STOP:
+ if (panel->idx == sizeof(panel->command))
+ ibm_panel_process_command(panel);
+ else
+ dev_dbg(&panel->input->dev,
+ "command incorrect size %u\n", panel->idx);
+ fallthrough;
+ case I2C_SLAVE_WRITE_REQUESTED:
+ panel->idx = 0;
+ break;
+ case I2C_SLAVE_WRITE_RECEIVED:
+ if (panel->idx < sizeof(panel->command))
+ panel->command[panel->idx++] = *val;
+ else
+ /*
+ * The command is too long and therefore invalid, so set the index
+ * to it's largest possible value. When a STOP is finally received,
+ * the command will be rejected upon processing.
+ */
+ panel->idx = U8_MAX;
+ break;
+ case I2C_SLAVE_READ_REQUESTED:
+ case I2C_SLAVE_READ_PROCESSED:
+ *val = 0xff;
+ break;
+ default:
+ break;
+ }
+
+ spin_unlock_irqrestore(&panel->lock, flags);
+
+ return 0;
+}
+
+static int ibm_panel_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct ibm_panel *panel;
+ int i;
+ int error;
+
+ panel = devm_kzalloc(&client->dev, sizeof(*panel), GFP_KERNEL);
+ if (!panel)
+ return -ENOMEM;
+
+ spin_lock_init(&panel->lock);
+
+ panel->input = devm_input_allocate_device(&client->dev);
+ if (!panel->input)
+ return -ENOMEM;
+
+ panel->input->name = client->name;
+ panel->input->id.bustype = BUS_I2C;
+
+ error = device_property_read_u32_array(&client->dev,
+ "linux,keycodes",
+ panel->keycodes,
+ PANEL_KEYCODES_COUNT);
+ if (error) {
+ /*
+ * Use gamepad buttons as defaults for compatibility with
+ * existing applications.
+ */
+ panel->keycodes[0] = BTN_NORTH;
+ panel->keycodes[1] = BTN_SOUTH;
+ panel->keycodes[2] = BTN_SELECT;
+ }
+
+ for (i = 0; i < PANEL_KEYCODES_COUNT; ++i)
+ input_set_capability(panel->input, EV_KEY, panel->keycodes[i]);
+
+ error = input_register_device(panel->input);
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to register input device: %d\n", error);
+ return error;
+ }
+
+ i2c_set_clientdata(client, panel);
+ error = i2c_slave_register(client, ibm_panel_i2c_slave_cb);
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to register as i2c slave: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static void ibm_panel_remove(struct i2c_client *client)
+{
+ i2c_slave_unregister(client);
+}
+
+static const struct of_device_id ibm_panel_match[] = {
+ { .compatible = "ibm,op-panel" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ibm_panel_match);
+
+static struct i2c_driver ibm_panel_driver = {
+ .driver = {
+ .name = DEVICE_NAME,
+ .of_match_table = ibm_panel_match,
+ },
+ .probe = ibm_panel_probe,
+ .remove = ibm_panel_remove,
+};
+module_i2c_driver(ibm_panel_driver);
+
+MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>");
+MODULE_DESCRIPTION("IBM Operation Panel Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/ideapad_slidebar.c b/drivers/input/misc/ideapad_slidebar.c
new file mode 100644
index 000000000..68f1c584d
--- /dev/null
+++ b/drivers/input/misc/ideapad_slidebar.c
@@ -0,0 +1,353 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Input driver for slidebars on some Lenovo IdeaPad laptops
+ *
+ * Copyright (C) 2013 Andrey Moiseev <o2g.org.ru@gmail.com>
+ *
+ * Reverse-engineered from Lenovo SlideNav software (SBarHook.dll).
+ *
+ * Trademarks are the property of their respective owners.
+ */
+
+/*
+ * Currently tested and works on:
+ * Lenovo IdeaPad Y550
+ * Lenovo IdeaPad Y550P
+ *
+ * Other models can be added easily. To test,
+ * load with 'force' parameter set 'true'.
+ *
+ * LEDs blinking and input mode are managed via sysfs,
+ * (hex, unsigned byte value):
+ * /sys/devices/platform/ideapad_slidebar/slidebar_mode
+ *
+ * The value is in byte range, however, I only figured out
+ * how bits 0b10011001 work. Some other bits, probably,
+ * are meaningfull too.
+ *
+ * Possible states:
+ *
+ * STD_INT, ONMOV_INT, OFF_INT, LAST_POLL, OFF_POLL
+ *
+ * Meaning:
+ * released touched
+ * STD 'heartbeat' lights follow the finger
+ * ONMOV no lights lights follow the finger
+ * LAST at last pos lights follow the finger
+ * OFF no lights no lights
+ *
+ * INT all input events are generated, interrupts are used
+ * POLL no input events by default, to get them,
+ * send 0b10000000 (read below)
+ *
+ * Commands: write
+ *
+ * All | 0b01001 -> STD_INT
+ * possible | 0b10001 -> ONMOV_INT
+ * states | 0b01000 -> OFF_INT
+ *
+ * | 0b0 -> LAST_POLL
+ * STD_INT or ONMOV_INT |
+ * | 0b1 -> STD_INT
+ *
+ * | 0b0 -> OFF_POLL
+ * OFF_INT or OFF_POLL |
+ * | 0b1 -> OFF_INT
+ *
+ * Any state | 0b10000000 -> if the slidebar has updated data,
+ * produce one input event (last position),
+ * switch to respective POLL mode
+ * (like 0x0), if not in POLL mode yet.
+ *
+ * Get current state: read
+ *
+ * masked by 0x11 read value means:
+ *
+ * 0x00 LAST
+ * 0x01 STD
+ * 0x10 OFF
+ * 0x11 ONMOV
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/dmi.h>
+#include <linux/spinlock.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/i8042.h>
+#include <linux/serio.h>
+
+#define IDEAPAD_BASE 0xff29
+
+static bool force;
+module_param(force, bool, 0);
+MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
+
+static DEFINE_SPINLOCK(io_lock);
+
+static struct input_dev *slidebar_input_dev;
+static struct platform_device *slidebar_platform_dev;
+
+static u8 slidebar_pos_get(void)
+{
+ u8 res;
+ unsigned long flags;
+
+ spin_lock_irqsave(&io_lock, flags);
+ outb(0xf4, 0xff29);
+ outb(0xbf, 0xff2a);
+ res = inb(0xff2b);
+ spin_unlock_irqrestore(&io_lock, flags);
+
+ return res;
+}
+
+static u8 slidebar_mode_get(void)
+{
+ u8 res;
+ unsigned long flags;
+
+ spin_lock_irqsave(&io_lock, flags);
+ outb(0xf7, 0xff29);
+ outb(0x8b, 0xff2a);
+ res = inb(0xff2b);
+ spin_unlock_irqrestore(&io_lock, flags);
+
+ return res;
+}
+
+static void slidebar_mode_set(u8 mode)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&io_lock, flags);
+ outb(0xf7, 0xff29);
+ outb(0x8b, 0xff2a);
+ outb(mode, 0xff2b);
+ spin_unlock_irqrestore(&io_lock, flags);
+}
+
+static bool slidebar_i8042_filter(unsigned char data, unsigned char str,
+ struct serio *port)
+{
+ static bool extended = false;
+
+ /* We are only interested in data coming form KBC port */
+ if (str & I8042_STR_AUXDATA)
+ return false;
+
+ /* Scancodes: e03b on move, e0bb on release. */
+ if (data == 0xe0) {
+ extended = true;
+ return true;
+ }
+
+ if (!extended)
+ return false;
+
+ extended = false;
+
+ if (likely((data & 0x7f) != 0x3b)) {
+ serio_interrupt(port, 0xe0, 0);
+ return false;
+ }
+
+ if (data & 0x80) {
+ input_report_key(slidebar_input_dev, BTN_TOUCH, 0);
+ } else {
+ input_report_key(slidebar_input_dev, BTN_TOUCH, 1);
+ input_report_abs(slidebar_input_dev, ABS_X, slidebar_pos_get());
+ }
+ input_sync(slidebar_input_dev);
+
+ return true;
+}
+
+static ssize_t show_slidebar_mode(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "%x\n", slidebar_mode_get());
+}
+
+static ssize_t store_slidebar_mode(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ u8 mode;
+ int error;
+
+ error = kstrtou8(buf, 0, &mode);
+ if (error)
+ return error;
+
+ slidebar_mode_set(mode);
+
+ return count;
+}
+
+static DEVICE_ATTR(slidebar_mode, S_IWUSR | S_IRUGO,
+ show_slidebar_mode, store_slidebar_mode);
+
+static struct attribute *ideapad_attrs[] = {
+ &dev_attr_slidebar_mode.attr,
+ NULL
+};
+
+static struct attribute_group ideapad_attr_group = {
+ .attrs = ideapad_attrs
+};
+
+static const struct attribute_group *ideapad_attr_groups[] = {
+ &ideapad_attr_group,
+ NULL
+};
+
+static int __init ideapad_probe(struct platform_device* pdev)
+{
+ int err;
+
+ if (!request_region(IDEAPAD_BASE, 3, "ideapad_slidebar")) {
+ dev_err(&pdev->dev, "IO ports are busy\n");
+ return -EBUSY;
+ }
+
+ slidebar_input_dev = input_allocate_device();
+ if (!slidebar_input_dev) {
+ dev_err(&pdev->dev, "Failed to allocate input device\n");
+ err = -ENOMEM;
+ goto err_release_ports;
+ }
+
+ slidebar_input_dev->name = "IdeaPad Slidebar";
+ slidebar_input_dev->id.bustype = BUS_HOST;
+ slidebar_input_dev->dev.parent = &pdev->dev;
+ input_set_capability(slidebar_input_dev, EV_KEY, BTN_TOUCH);
+ input_set_capability(slidebar_input_dev, EV_ABS, ABS_X);
+ input_set_abs_params(slidebar_input_dev, ABS_X, 0, 0xff, 0, 0);
+
+ err = i8042_install_filter(slidebar_i8042_filter);
+ if (err) {
+ dev_err(&pdev->dev,
+ "Failed to install i8042 filter: %d\n", err);
+ goto err_free_dev;
+ }
+
+ err = input_register_device(slidebar_input_dev);
+ if (err) {
+ dev_err(&pdev->dev,
+ "Failed to register input device: %d\n", err);
+ goto err_remove_filter;
+ }
+
+ return 0;
+
+err_remove_filter:
+ i8042_remove_filter(slidebar_i8042_filter);
+err_free_dev:
+ input_free_device(slidebar_input_dev);
+err_release_ports:
+ release_region(IDEAPAD_BASE, 3);
+ return err;
+}
+
+static int ideapad_remove(struct platform_device *pdev)
+{
+ i8042_remove_filter(slidebar_i8042_filter);
+ input_unregister_device(slidebar_input_dev);
+ release_region(IDEAPAD_BASE, 3);
+
+ return 0;
+}
+
+static struct platform_driver slidebar_drv = {
+ .driver = {
+ .name = "ideapad_slidebar",
+ },
+ .remove = ideapad_remove,
+};
+
+static int __init ideapad_dmi_check(const struct dmi_system_id *id)
+{
+ pr_info("Laptop model '%s'\n", id->ident);
+ return 1;
+}
+
+static const struct dmi_system_id ideapad_dmi[] __initconst = {
+ {
+ .ident = "Lenovo IdeaPad Y550",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "20017"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo IdeaPad Y550")
+ },
+ .callback = ideapad_dmi_check
+ },
+ {
+ .ident = "Lenovo IdeaPad Y550P",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "20035"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo IdeaPad Y550P")
+ },
+ .callback = ideapad_dmi_check
+ },
+ { NULL, }
+};
+MODULE_DEVICE_TABLE(dmi, ideapad_dmi);
+
+static int __init slidebar_init(void)
+{
+ int err;
+
+ if (!force && !dmi_check_system(ideapad_dmi)) {
+ pr_err("DMI does not match\n");
+ return -ENODEV;
+ }
+
+ slidebar_platform_dev = platform_device_alloc("ideapad_slidebar", -1);
+ if (!slidebar_platform_dev) {
+ pr_err("Not enough memory\n");
+ return -ENOMEM;
+ }
+
+ slidebar_platform_dev->dev.groups = ideapad_attr_groups;
+
+ err = platform_device_add(slidebar_platform_dev);
+ if (err) {
+ pr_err("Failed to register platform device\n");
+ goto err_free_dev;
+ }
+
+ err = platform_driver_probe(&slidebar_drv, ideapad_probe);
+ if (err) {
+ pr_err("Failed to register platform driver\n");
+ goto err_delete_dev;
+ }
+
+ return 0;
+
+err_delete_dev:
+ platform_device_del(slidebar_platform_dev);
+err_free_dev:
+ platform_device_put(slidebar_platform_dev);
+ return err;
+}
+
+static void __exit slidebar_exit(void)
+{
+ platform_device_unregister(slidebar_platform_dev);
+ platform_driver_unregister(&slidebar_drv);
+}
+
+module_init(slidebar_init);
+module_exit(slidebar_exit);
+
+MODULE_AUTHOR("Andrey Moiseev <o2g.org.ru@gmail.com>");
+MODULE_DESCRIPTION("Slidebar input support for some Lenovo IdeaPad laptops");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/ims-pcu.c b/drivers/input/misc/ims-pcu.c
new file mode 100644
index 000000000..b2f1292e2
--- /dev/null
+++ b/drivers/input/misc/ims-pcu.c
@@ -0,0 +1,2149 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for IMS Passenger Control Unit Devices
+ *
+ * Copyright (C) 2013 The IMS Company
+ */
+
+#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/ihex.h>
+#include <linux/input.h>
+#include <linux/kernel.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/usb/input.h>
+#include <linux/usb/cdc.h>
+#include <asm/unaligned.h>
+
+#define IMS_PCU_KEYMAP_LEN 32
+
+struct ims_pcu_buttons {
+ struct input_dev *input;
+ char name[32];
+ char phys[32];
+ unsigned short keymap[IMS_PCU_KEYMAP_LEN];
+};
+
+struct ims_pcu_gamepad {
+ struct input_dev *input;
+ char name[32];
+ char phys[32];
+};
+
+struct ims_pcu_backlight {
+ struct led_classdev cdev;
+ char name[32];
+};
+
+#define IMS_PCU_PART_NUMBER_LEN 15
+#define IMS_PCU_SERIAL_NUMBER_LEN 8
+#define IMS_PCU_DOM_LEN 8
+#define IMS_PCU_FW_VERSION_LEN (9 + 1)
+#define IMS_PCU_BL_VERSION_LEN (9 + 1)
+#define IMS_PCU_BL_RESET_REASON_LEN (2 + 1)
+
+#define IMS_PCU_PCU_B_DEVICE_ID 5
+
+#define IMS_PCU_BUF_SIZE 128
+
+struct ims_pcu {
+ struct usb_device *udev;
+ struct device *dev; /* control interface's device, used for logging */
+
+ unsigned int device_no;
+
+ bool bootloader_mode;
+
+ char part_number[IMS_PCU_PART_NUMBER_LEN];
+ char serial_number[IMS_PCU_SERIAL_NUMBER_LEN];
+ char date_of_manufacturing[IMS_PCU_DOM_LEN];
+ char fw_version[IMS_PCU_FW_VERSION_LEN];
+ char bl_version[IMS_PCU_BL_VERSION_LEN];
+ char reset_reason[IMS_PCU_BL_RESET_REASON_LEN];
+ int update_firmware_status;
+ u8 device_id;
+
+ u8 ofn_reg_addr;
+
+ struct usb_interface *ctrl_intf;
+
+ struct usb_endpoint_descriptor *ep_ctrl;
+ struct urb *urb_ctrl;
+ u8 *urb_ctrl_buf;
+ dma_addr_t ctrl_dma;
+ size_t max_ctrl_size;
+
+ struct usb_interface *data_intf;
+
+ struct usb_endpoint_descriptor *ep_in;
+ struct urb *urb_in;
+ u8 *urb_in_buf;
+ dma_addr_t read_dma;
+ size_t max_in_size;
+
+ struct usb_endpoint_descriptor *ep_out;
+ u8 *urb_out_buf;
+ size_t max_out_size;
+
+ u8 read_buf[IMS_PCU_BUF_SIZE];
+ u8 read_pos;
+ u8 check_sum;
+ bool have_stx;
+ bool have_dle;
+
+ u8 cmd_buf[IMS_PCU_BUF_SIZE];
+ u8 ack_id;
+ u8 expected_response;
+ u8 cmd_buf_len;
+ struct completion cmd_done;
+ struct mutex cmd_mutex;
+
+ u32 fw_start_addr;
+ u32 fw_end_addr;
+ struct completion async_firmware_done;
+
+ struct ims_pcu_buttons buttons;
+ struct ims_pcu_gamepad *gamepad;
+ struct ims_pcu_backlight backlight;
+
+ bool setup_complete; /* Input and LED devices have been created */
+};
+
+
+/*********************************************************************
+ * Buttons Input device support *
+ *********************************************************************/
+
+static const unsigned short ims_pcu_keymap_1[] = {
+ [1] = KEY_ATTENDANT_OFF,
+ [2] = KEY_ATTENDANT_ON,
+ [3] = KEY_LIGHTS_TOGGLE,
+ [4] = KEY_VOLUMEUP,
+ [5] = KEY_VOLUMEDOWN,
+ [6] = KEY_INFO,
+};
+
+static const unsigned short ims_pcu_keymap_2[] = {
+ [4] = KEY_VOLUMEUP,
+ [5] = KEY_VOLUMEDOWN,
+ [6] = KEY_INFO,
+};
+
+static const unsigned short ims_pcu_keymap_3[] = {
+ [1] = KEY_HOMEPAGE,
+ [2] = KEY_ATTENDANT_TOGGLE,
+ [3] = KEY_LIGHTS_TOGGLE,
+ [4] = KEY_VOLUMEUP,
+ [5] = KEY_VOLUMEDOWN,
+ [6] = KEY_DISPLAYTOGGLE,
+ [18] = KEY_PLAYPAUSE,
+};
+
+static const unsigned short ims_pcu_keymap_4[] = {
+ [1] = KEY_ATTENDANT_OFF,
+ [2] = KEY_ATTENDANT_ON,
+ [3] = KEY_LIGHTS_TOGGLE,
+ [4] = KEY_VOLUMEUP,
+ [5] = KEY_VOLUMEDOWN,
+ [6] = KEY_INFO,
+ [18] = KEY_PLAYPAUSE,
+};
+
+static const unsigned short ims_pcu_keymap_5[] = {
+ [1] = KEY_ATTENDANT_OFF,
+ [2] = KEY_ATTENDANT_ON,
+ [3] = KEY_LIGHTS_TOGGLE,
+};
+
+struct ims_pcu_device_info {
+ const unsigned short *keymap;
+ size_t keymap_len;
+ bool has_gamepad;
+};
+
+#define IMS_PCU_DEVINFO(_n, _gamepad) \
+ [_n] = { \
+ .keymap = ims_pcu_keymap_##_n, \
+ .keymap_len = ARRAY_SIZE(ims_pcu_keymap_##_n), \
+ .has_gamepad = _gamepad, \
+ }
+
+static const struct ims_pcu_device_info ims_pcu_device_info[] = {
+ IMS_PCU_DEVINFO(1, true),
+ IMS_PCU_DEVINFO(2, true),
+ IMS_PCU_DEVINFO(3, true),
+ IMS_PCU_DEVINFO(4, true),
+ IMS_PCU_DEVINFO(5, false),
+};
+
+static void ims_pcu_buttons_report(struct ims_pcu *pcu, u32 data)
+{
+ struct ims_pcu_buttons *buttons = &pcu->buttons;
+ struct input_dev *input = buttons->input;
+ int i;
+
+ for (i = 0; i < 32; i++) {
+ unsigned short keycode = buttons->keymap[i];
+
+ if (keycode != KEY_RESERVED)
+ input_report_key(input, keycode, data & (1UL << i));
+ }
+
+ input_sync(input);
+}
+
+static int ims_pcu_setup_buttons(struct ims_pcu *pcu,
+ const unsigned short *keymap,
+ size_t keymap_len)
+{
+ struct ims_pcu_buttons *buttons = &pcu->buttons;
+ struct input_dev *input;
+ int i;
+ int error;
+
+ input = input_allocate_device();
+ if (!input) {
+ dev_err(pcu->dev,
+ "Not enough memory for input input device\n");
+ return -ENOMEM;
+ }
+
+ snprintf(buttons->name, sizeof(buttons->name),
+ "IMS PCU#%d Button Interface", pcu->device_no);
+
+ usb_make_path(pcu->udev, buttons->phys, sizeof(buttons->phys));
+ strlcat(buttons->phys, "/input0", sizeof(buttons->phys));
+
+ memcpy(buttons->keymap, keymap, sizeof(*keymap) * keymap_len);
+
+ input->name = buttons->name;
+ input->phys = buttons->phys;
+ usb_to_input_id(pcu->udev, &input->id);
+ input->dev.parent = &pcu->ctrl_intf->dev;
+
+ input->keycode = buttons->keymap;
+ input->keycodemax = ARRAY_SIZE(buttons->keymap);
+ input->keycodesize = sizeof(buttons->keymap[0]);
+
+ __set_bit(EV_KEY, input->evbit);
+ for (i = 0; i < IMS_PCU_KEYMAP_LEN; i++)
+ __set_bit(buttons->keymap[i], input->keybit);
+ __clear_bit(KEY_RESERVED, input->keybit);
+
+ error = input_register_device(input);
+ if (error) {
+ dev_err(pcu->dev,
+ "Failed to register buttons input device: %d\n",
+ error);
+ input_free_device(input);
+ return error;
+ }
+
+ buttons->input = input;
+ return 0;
+}
+
+static void ims_pcu_destroy_buttons(struct ims_pcu *pcu)
+{
+ struct ims_pcu_buttons *buttons = &pcu->buttons;
+
+ input_unregister_device(buttons->input);
+}
+
+
+/*********************************************************************
+ * Gamepad Input device support *
+ *********************************************************************/
+
+static void ims_pcu_gamepad_report(struct ims_pcu *pcu, u32 data)
+{
+ struct ims_pcu_gamepad *gamepad = pcu->gamepad;
+ struct input_dev *input = gamepad->input;
+ int x, y;
+
+ x = !!(data & (1 << 14)) - !!(data & (1 << 13));
+ y = !!(data & (1 << 12)) - !!(data & (1 << 11));
+
+ input_report_abs(input, ABS_X, x);
+ input_report_abs(input, ABS_Y, y);
+
+ input_report_key(input, BTN_A, data & (1 << 7));
+ input_report_key(input, BTN_B, data & (1 << 8));
+ input_report_key(input, BTN_X, data & (1 << 9));
+ input_report_key(input, BTN_Y, data & (1 << 10));
+ input_report_key(input, BTN_START, data & (1 << 15));
+ input_report_key(input, BTN_SELECT, data & (1 << 16));
+
+ input_sync(input);
+}
+
+static int ims_pcu_setup_gamepad(struct ims_pcu *pcu)
+{
+ struct ims_pcu_gamepad *gamepad;
+ struct input_dev *input;
+ int error;
+
+ gamepad = kzalloc(sizeof(struct ims_pcu_gamepad), GFP_KERNEL);
+ input = input_allocate_device();
+ if (!gamepad || !input) {
+ dev_err(pcu->dev,
+ "Not enough memory for gamepad device\n");
+ error = -ENOMEM;
+ goto err_free_mem;
+ }
+
+ gamepad->input = input;
+
+ snprintf(gamepad->name, sizeof(gamepad->name),
+ "IMS PCU#%d Gamepad Interface", pcu->device_no);
+
+ usb_make_path(pcu->udev, gamepad->phys, sizeof(gamepad->phys));
+ strlcat(gamepad->phys, "/input1", sizeof(gamepad->phys));
+
+ input->name = gamepad->name;
+ input->phys = gamepad->phys;
+ usb_to_input_id(pcu->udev, &input->id);
+ input->dev.parent = &pcu->ctrl_intf->dev;
+
+ __set_bit(EV_KEY, input->evbit);
+ __set_bit(BTN_A, input->keybit);
+ __set_bit(BTN_B, input->keybit);
+ __set_bit(BTN_X, input->keybit);
+ __set_bit(BTN_Y, input->keybit);
+ __set_bit(BTN_START, input->keybit);
+ __set_bit(BTN_SELECT, input->keybit);
+
+ __set_bit(EV_ABS, input->evbit);
+ input_set_abs_params(input, ABS_X, -1, 1, 0, 0);
+ input_set_abs_params(input, ABS_Y, -1, 1, 0, 0);
+
+ error = input_register_device(input);
+ if (error) {
+ dev_err(pcu->dev,
+ "Failed to register gamepad input device: %d\n",
+ error);
+ goto err_free_mem;
+ }
+
+ pcu->gamepad = gamepad;
+ return 0;
+
+err_free_mem:
+ input_free_device(input);
+ kfree(gamepad);
+ return error;
+}
+
+static void ims_pcu_destroy_gamepad(struct ims_pcu *pcu)
+{
+ struct ims_pcu_gamepad *gamepad = pcu->gamepad;
+
+ input_unregister_device(gamepad->input);
+ kfree(gamepad);
+}
+
+
+/*********************************************************************
+ * PCU Communication protocol handling *
+ *********************************************************************/
+
+#define IMS_PCU_PROTOCOL_STX 0x02
+#define IMS_PCU_PROTOCOL_ETX 0x03
+#define IMS_PCU_PROTOCOL_DLE 0x10
+
+/* PCU commands */
+#define IMS_PCU_CMD_STATUS 0xa0
+#define IMS_PCU_CMD_PCU_RESET 0xa1
+#define IMS_PCU_CMD_RESET_REASON 0xa2
+#define IMS_PCU_CMD_SEND_BUTTONS 0xa3
+#define IMS_PCU_CMD_JUMP_TO_BTLDR 0xa4
+#define IMS_PCU_CMD_GET_INFO 0xa5
+#define IMS_PCU_CMD_SET_BRIGHTNESS 0xa6
+#define IMS_PCU_CMD_EEPROM 0xa7
+#define IMS_PCU_CMD_GET_FW_VERSION 0xa8
+#define IMS_PCU_CMD_GET_BL_VERSION 0xa9
+#define IMS_PCU_CMD_SET_INFO 0xab
+#define IMS_PCU_CMD_GET_BRIGHTNESS 0xac
+#define IMS_PCU_CMD_GET_DEVICE_ID 0xae
+#define IMS_PCU_CMD_SPECIAL_INFO 0xb0
+#define IMS_PCU_CMD_BOOTLOADER 0xb1 /* Pass data to bootloader */
+#define IMS_PCU_CMD_OFN_SET_CONFIG 0xb3
+#define IMS_PCU_CMD_OFN_GET_CONFIG 0xb4
+
+/* PCU responses */
+#define IMS_PCU_RSP_STATUS 0xc0
+#define IMS_PCU_RSP_PCU_RESET 0 /* Originally 0xc1 */
+#define IMS_PCU_RSP_RESET_REASON 0xc2
+#define IMS_PCU_RSP_SEND_BUTTONS 0xc3
+#define IMS_PCU_RSP_JUMP_TO_BTLDR 0 /* Originally 0xc4 */
+#define IMS_PCU_RSP_GET_INFO 0xc5
+#define IMS_PCU_RSP_SET_BRIGHTNESS 0xc6
+#define IMS_PCU_RSP_EEPROM 0xc7
+#define IMS_PCU_RSP_GET_FW_VERSION 0xc8
+#define IMS_PCU_RSP_GET_BL_VERSION 0xc9
+#define IMS_PCU_RSP_SET_INFO 0xcb
+#define IMS_PCU_RSP_GET_BRIGHTNESS 0xcc
+#define IMS_PCU_RSP_CMD_INVALID 0xcd
+#define IMS_PCU_RSP_GET_DEVICE_ID 0xce
+#define IMS_PCU_RSP_SPECIAL_INFO 0xd0
+#define IMS_PCU_RSP_BOOTLOADER 0xd1 /* Bootloader response */
+#define IMS_PCU_RSP_OFN_SET_CONFIG 0xd2
+#define IMS_PCU_RSP_OFN_GET_CONFIG 0xd3
+
+
+#define IMS_PCU_RSP_EVNT_BUTTONS 0xe0 /* Unsolicited, button state */
+#define IMS_PCU_GAMEPAD_MASK 0x0001ff80UL /* Bits 7 through 16 */
+
+
+#define IMS_PCU_MIN_PACKET_LEN 3
+#define IMS_PCU_DATA_OFFSET 2
+
+#define IMS_PCU_CMD_WRITE_TIMEOUT 100 /* msec */
+#define IMS_PCU_CMD_RESPONSE_TIMEOUT 500 /* msec */
+
+static void ims_pcu_report_events(struct ims_pcu *pcu)
+{
+ u32 data = get_unaligned_be32(&pcu->read_buf[3]);
+
+ ims_pcu_buttons_report(pcu, data & ~IMS_PCU_GAMEPAD_MASK);
+ if (pcu->gamepad)
+ ims_pcu_gamepad_report(pcu, data);
+}
+
+static void ims_pcu_handle_response(struct ims_pcu *pcu)
+{
+ switch (pcu->read_buf[0]) {
+ case IMS_PCU_RSP_EVNT_BUTTONS:
+ if (likely(pcu->setup_complete))
+ ims_pcu_report_events(pcu);
+ break;
+
+ default:
+ /*
+ * See if we got command completion.
+ * If both the sequence and response code match save
+ * the data and signal completion.
+ */
+ if (pcu->read_buf[0] == pcu->expected_response &&
+ pcu->read_buf[1] == pcu->ack_id - 1) {
+
+ memcpy(pcu->cmd_buf, pcu->read_buf, pcu->read_pos);
+ pcu->cmd_buf_len = pcu->read_pos;
+ complete(&pcu->cmd_done);
+ }
+ break;
+ }
+}
+
+static void ims_pcu_process_data(struct ims_pcu *pcu, struct urb *urb)
+{
+ int i;
+
+ for (i = 0; i < urb->actual_length; i++) {
+ u8 data = pcu->urb_in_buf[i];
+
+ /* Skip everything until we get Start Xmit */
+ if (!pcu->have_stx && data != IMS_PCU_PROTOCOL_STX)
+ continue;
+
+ if (pcu->have_dle) {
+ pcu->have_dle = false;
+ pcu->read_buf[pcu->read_pos++] = data;
+ pcu->check_sum += data;
+ continue;
+ }
+
+ switch (data) {
+ case IMS_PCU_PROTOCOL_STX:
+ if (pcu->have_stx)
+ dev_warn(pcu->dev,
+ "Unexpected STX at byte %d, discarding old data\n",
+ pcu->read_pos);
+ pcu->have_stx = true;
+ pcu->have_dle = false;
+ pcu->read_pos = 0;
+ pcu->check_sum = 0;
+ break;
+
+ case IMS_PCU_PROTOCOL_DLE:
+ pcu->have_dle = true;
+ break;
+
+ case IMS_PCU_PROTOCOL_ETX:
+ if (pcu->read_pos < IMS_PCU_MIN_PACKET_LEN) {
+ dev_warn(pcu->dev,
+ "Short packet received (%d bytes), ignoring\n",
+ pcu->read_pos);
+ } else if (pcu->check_sum != 0) {
+ dev_warn(pcu->dev,
+ "Invalid checksum in packet (%d bytes), ignoring\n",
+ pcu->read_pos);
+ } else {
+ ims_pcu_handle_response(pcu);
+ }
+
+ pcu->have_stx = false;
+ pcu->have_dle = false;
+ pcu->read_pos = 0;
+ break;
+
+ default:
+ pcu->read_buf[pcu->read_pos++] = data;
+ pcu->check_sum += data;
+ break;
+ }
+ }
+}
+
+static bool ims_pcu_byte_needs_escape(u8 byte)
+{
+ return byte == IMS_PCU_PROTOCOL_STX ||
+ byte == IMS_PCU_PROTOCOL_ETX ||
+ byte == IMS_PCU_PROTOCOL_DLE;
+}
+
+static int ims_pcu_send_cmd_chunk(struct ims_pcu *pcu,
+ u8 command, int chunk, int len)
+{
+ int error;
+
+ error = usb_bulk_msg(pcu->udev,
+ usb_sndbulkpipe(pcu->udev,
+ pcu->ep_out->bEndpointAddress),
+ pcu->urb_out_buf, len,
+ NULL, IMS_PCU_CMD_WRITE_TIMEOUT);
+ if (error < 0) {
+ dev_dbg(pcu->dev,
+ "Sending 0x%02x command failed at chunk %d: %d\n",
+ command, chunk, error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int ims_pcu_send_command(struct ims_pcu *pcu,
+ u8 command, const u8 *data, int len)
+{
+ int count = 0;
+ int chunk = 0;
+ int delta;
+ int i;
+ int error;
+ u8 csum = 0;
+ u8 ack_id;
+
+ pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_STX;
+
+ /* We know the command need not be escaped */
+ pcu->urb_out_buf[count++] = command;
+ csum += command;
+
+ ack_id = pcu->ack_id++;
+ if (ack_id == 0xff)
+ ack_id = pcu->ack_id++;
+
+ if (ims_pcu_byte_needs_escape(ack_id))
+ pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_DLE;
+
+ pcu->urb_out_buf[count++] = ack_id;
+ csum += ack_id;
+
+ for (i = 0; i < len; i++) {
+
+ delta = ims_pcu_byte_needs_escape(data[i]) ? 2 : 1;
+ if (count + delta >= pcu->max_out_size) {
+ error = ims_pcu_send_cmd_chunk(pcu, command,
+ ++chunk, count);
+ if (error)
+ return error;
+
+ count = 0;
+ }
+
+ if (delta == 2)
+ pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_DLE;
+
+ pcu->urb_out_buf[count++] = data[i];
+ csum += data[i];
+ }
+
+ csum = 1 + ~csum;
+
+ delta = ims_pcu_byte_needs_escape(csum) ? 3 : 2;
+ if (count + delta >= pcu->max_out_size) {
+ error = ims_pcu_send_cmd_chunk(pcu, command, ++chunk, count);
+ if (error)
+ return error;
+
+ count = 0;
+ }
+
+ if (delta == 3)
+ pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_DLE;
+
+ pcu->urb_out_buf[count++] = csum;
+ pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_ETX;
+
+ return ims_pcu_send_cmd_chunk(pcu, command, ++chunk, count);
+}
+
+static int __ims_pcu_execute_command(struct ims_pcu *pcu,
+ u8 command, const void *data, size_t len,
+ u8 expected_response, int response_time)
+{
+ int error;
+
+ pcu->expected_response = expected_response;
+ init_completion(&pcu->cmd_done);
+
+ error = ims_pcu_send_command(pcu, command, data, len);
+ if (error)
+ return error;
+
+ if (expected_response &&
+ !wait_for_completion_timeout(&pcu->cmd_done,
+ msecs_to_jiffies(response_time))) {
+ dev_dbg(pcu->dev, "Command 0x%02x timed out\n", command);
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+#define ims_pcu_execute_command(pcu, code, data, len) \
+ __ims_pcu_execute_command(pcu, \
+ IMS_PCU_CMD_##code, data, len, \
+ IMS_PCU_RSP_##code, \
+ IMS_PCU_CMD_RESPONSE_TIMEOUT)
+
+#define ims_pcu_execute_query(pcu, code) \
+ ims_pcu_execute_command(pcu, code, NULL, 0)
+
+/* Bootloader commands */
+#define IMS_PCU_BL_CMD_QUERY_DEVICE 0xa1
+#define IMS_PCU_BL_CMD_UNLOCK_CONFIG 0xa2
+#define IMS_PCU_BL_CMD_ERASE_APP 0xa3
+#define IMS_PCU_BL_CMD_PROGRAM_DEVICE 0xa4
+#define IMS_PCU_BL_CMD_PROGRAM_COMPLETE 0xa5
+#define IMS_PCU_BL_CMD_READ_APP 0xa6
+#define IMS_PCU_BL_CMD_RESET_DEVICE 0xa7
+#define IMS_PCU_BL_CMD_LAUNCH_APP 0xa8
+
+/* Bootloader commands */
+#define IMS_PCU_BL_RSP_QUERY_DEVICE 0xc1
+#define IMS_PCU_BL_RSP_UNLOCK_CONFIG 0xc2
+#define IMS_PCU_BL_RSP_ERASE_APP 0xc3
+#define IMS_PCU_BL_RSP_PROGRAM_DEVICE 0xc4
+#define IMS_PCU_BL_RSP_PROGRAM_COMPLETE 0xc5
+#define IMS_PCU_BL_RSP_READ_APP 0xc6
+#define IMS_PCU_BL_RSP_RESET_DEVICE 0 /* originally 0xa7 */
+#define IMS_PCU_BL_RSP_LAUNCH_APP 0 /* originally 0xa8 */
+
+#define IMS_PCU_BL_DATA_OFFSET 3
+
+static int __ims_pcu_execute_bl_command(struct ims_pcu *pcu,
+ u8 command, const void *data, size_t len,
+ u8 expected_response, int response_time)
+{
+ int error;
+
+ pcu->cmd_buf[0] = command;
+ if (data)
+ memcpy(&pcu->cmd_buf[1], data, len);
+
+ error = __ims_pcu_execute_command(pcu,
+ IMS_PCU_CMD_BOOTLOADER, pcu->cmd_buf, len + 1,
+ expected_response ? IMS_PCU_RSP_BOOTLOADER : 0,
+ response_time);
+ if (error) {
+ dev_err(pcu->dev,
+ "Failure when sending 0x%02x command to bootloader, error: %d\n",
+ pcu->cmd_buf[0], error);
+ return error;
+ }
+
+ if (expected_response && pcu->cmd_buf[2] != expected_response) {
+ dev_err(pcu->dev,
+ "Unexpected response from bootloader: 0x%02x, wanted 0x%02x\n",
+ pcu->cmd_buf[2], expected_response);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+#define ims_pcu_execute_bl_command(pcu, code, data, len, timeout) \
+ __ims_pcu_execute_bl_command(pcu, \
+ IMS_PCU_BL_CMD_##code, data, len, \
+ IMS_PCU_BL_RSP_##code, timeout) \
+
+#define IMS_PCU_INFO_PART_OFFSET 2
+#define IMS_PCU_INFO_DOM_OFFSET 17
+#define IMS_PCU_INFO_SERIAL_OFFSET 25
+
+#define IMS_PCU_SET_INFO_SIZE 31
+
+static int ims_pcu_get_info(struct ims_pcu *pcu)
+{
+ int error;
+
+ error = ims_pcu_execute_query(pcu, GET_INFO);
+ if (error) {
+ dev_err(pcu->dev,
+ "GET_INFO command failed, error: %d\n", error);
+ return error;
+ }
+
+ memcpy(pcu->part_number,
+ &pcu->cmd_buf[IMS_PCU_INFO_PART_OFFSET],
+ sizeof(pcu->part_number));
+ memcpy(pcu->date_of_manufacturing,
+ &pcu->cmd_buf[IMS_PCU_INFO_DOM_OFFSET],
+ sizeof(pcu->date_of_manufacturing));
+ memcpy(pcu->serial_number,
+ &pcu->cmd_buf[IMS_PCU_INFO_SERIAL_OFFSET],
+ sizeof(pcu->serial_number));
+
+ return 0;
+}
+
+static int ims_pcu_set_info(struct ims_pcu *pcu)
+{
+ int error;
+
+ memcpy(&pcu->cmd_buf[IMS_PCU_INFO_PART_OFFSET],
+ pcu->part_number, sizeof(pcu->part_number));
+ memcpy(&pcu->cmd_buf[IMS_PCU_INFO_DOM_OFFSET],
+ pcu->date_of_manufacturing, sizeof(pcu->date_of_manufacturing));
+ memcpy(&pcu->cmd_buf[IMS_PCU_INFO_SERIAL_OFFSET],
+ pcu->serial_number, sizeof(pcu->serial_number));
+
+ error = ims_pcu_execute_command(pcu, SET_INFO,
+ &pcu->cmd_buf[IMS_PCU_DATA_OFFSET],
+ IMS_PCU_SET_INFO_SIZE);
+ if (error) {
+ dev_err(pcu->dev,
+ "Failed to update device information, error: %d\n",
+ error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int ims_pcu_switch_to_bootloader(struct ims_pcu *pcu)
+{
+ int error;
+
+ /* Execute jump to the bootoloader */
+ error = ims_pcu_execute_command(pcu, JUMP_TO_BTLDR, NULL, 0);
+ if (error) {
+ dev_err(pcu->dev,
+ "Failure when sending JUMP TO BOOTLOADER command, error: %d\n",
+ error);
+ return error;
+ }
+
+ return 0;
+}
+
+/*********************************************************************
+ * Firmware Update handling *
+ *********************************************************************/
+
+#define IMS_PCU_FIRMWARE_NAME "imspcu.fw"
+
+struct ims_pcu_flash_fmt {
+ __le32 addr;
+ u8 len;
+ u8 data[];
+};
+
+static unsigned int ims_pcu_count_fw_records(const struct firmware *fw)
+{
+ const struct ihex_binrec *rec = (const struct ihex_binrec *)fw->data;
+ unsigned int count = 0;
+
+ while (rec) {
+ count++;
+ rec = ihex_next_binrec(rec);
+ }
+
+ return count;
+}
+
+static int ims_pcu_verify_block(struct ims_pcu *pcu,
+ u32 addr, u8 len, const u8 *data)
+{
+ struct ims_pcu_flash_fmt *fragment;
+ int error;
+
+ fragment = (void *)&pcu->cmd_buf[1];
+ put_unaligned_le32(addr, &fragment->addr);
+ fragment->len = len;
+
+ error = ims_pcu_execute_bl_command(pcu, READ_APP, NULL, 5,
+ IMS_PCU_CMD_RESPONSE_TIMEOUT);
+ if (error) {
+ dev_err(pcu->dev,
+ "Failed to retrieve block at 0x%08x, len %d, error: %d\n",
+ addr, len, error);
+ return error;
+ }
+
+ fragment = (void *)&pcu->cmd_buf[IMS_PCU_BL_DATA_OFFSET];
+ if (get_unaligned_le32(&fragment->addr) != addr ||
+ fragment->len != len) {
+ dev_err(pcu->dev,
+ "Wrong block when retrieving 0x%08x (0x%08x), len %d (%d)\n",
+ addr, get_unaligned_le32(&fragment->addr),
+ len, fragment->len);
+ return -EINVAL;
+ }
+
+ if (memcmp(fragment->data, data, len)) {
+ dev_err(pcu->dev,
+ "Mismatch in block at 0x%08x, len %d\n",
+ addr, len);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ims_pcu_flash_firmware(struct ims_pcu *pcu,
+ const struct firmware *fw,
+ unsigned int n_fw_records)
+{
+ const struct ihex_binrec *rec = (const struct ihex_binrec *)fw->data;
+ struct ims_pcu_flash_fmt *fragment;
+ unsigned int count = 0;
+ u32 addr;
+ u8 len;
+ int error;
+
+ error = ims_pcu_execute_bl_command(pcu, ERASE_APP, NULL, 0, 2000);
+ if (error) {
+ dev_err(pcu->dev,
+ "Failed to erase application image, error: %d\n",
+ error);
+ return error;
+ }
+
+ while (rec) {
+ /*
+ * The firmware format is messed up for some reason.
+ * The address twice that of what is needed for some
+ * reason and we end up overwriting half of the data
+ * with the next record.
+ */
+ addr = be32_to_cpu(rec->addr) / 2;
+ len = be16_to_cpu(rec->len);
+
+ fragment = (void *)&pcu->cmd_buf[1];
+ put_unaligned_le32(addr, &fragment->addr);
+ fragment->len = len;
+ memcpy(fragment->data, rec->data, len);
+
+ error = ims_pcu_execute_bl_command(pcu, PROGRAM_DEVICE,
+ NULL, len + 5,
+ IMS_PCU_CMD_RESPONSE_TIMEOUT);
+ if (error) {
+ dev_err(pcu->dev,
+ "Failed to write block at 0x%08x, len %d, error: %d\n",
+ addr, len, error);
+ return error;
+ }
+
+ if (addr >= pcu->fw_start_addr && addr < pcu->fw_end_addr) {
+ error = ims_pcu_verify_block(pcu, addr, len, rec->data);
+ if (error)
+ return error;
+ }
+
+ count++;
+ pcu->update_firmware_status = (count * 100) / n_fw_records;
+
+ rec = ihex_next_binrec(rec);
+ }
+
+ error = ims_pcu_execute_bl_command(pcu, PROGRAM_COMPLETE,
+ NULL, 0, 2000);
+ if (error)
+ dev_err(pcu->dev,
+ "Failed to send PROGRAM_COMPLETE, error: %d\n",
+ error);
+
+ return 0;
+}
+
+static int ims_pcu_handle_firmware_update(struct ims_pcu *pcu,
+ const struct firmware *fw)
+{
+ unsigned int n_fw_records;
+ int retval;
+
+ dev_info(pcu->dev, "Updating firmware %s, size: %zu\n",
+ IMS_PCU_FIRMWARE_NAME, fw->size);
+
+ n_fw_records = ims_pcu_count_fw_records(fw);
+
+ retval = ims_pcu_flash_firmware(pcu, fw, n_fw_records);
+ if (retval)
+ goto out;
+
+ retval = ims_pcu_execute_bl_command(pcu, LAUNCH_APP, NULL, 0, 0);
+ if (retval)
+ dev_err(pcu->dev,
+ "Failed to start application image, error: %d\n",
+ retval);
+
+out:
+ pcu->update_firmware_status = retval;
+ sysfs_notify(&pcu->dev->kobj, NULL, "update_firmware_status");
+ return retval;
+}
+
+static void ims_pcu_process_async_firmware(const struct firmware *fw,
+ void *context)
+{
+ struct ims_pcu *pcu = context;
+ int error;
+
+ if (!fw) {
+ dev_err(pcu->dev, "Failed to get firmware %s\n",
+ IMS_PCU_FIRMWARE_NAME);
+ goto out;
+ }
+
+ error = ihex_validate_fw(fw);
+ if (error) {
+ dev_err(pcu->dev, "Firmware %s is invalid\n",
+ IMS_PCU_FIRMWARE_NAME);
+ goto out;
+ }
+
+ mutex_lock(&pcu->cmd_mutex);
+ ims_pcu_handle_firmware_update(pcu, fw);
+ mutex_unlock(&pcu->cmd_mutex);
+
+ release_firmware(fw);
+
+out:
+ complete(&pcu->async_firmware_done);
+}
+
+/*********************************************************************
+ * Backlight LED device support *
+ *********************************************************************/
+
+#define IMS_PCU_MAX_BRIGHTNESS 31998
+
+static int ims_pcu_backlight_set_brightness(struct led_classdev *cdev,
+ enum led_brightness value)
+{
+ struct ims_pcu_backlight *backlight =
+ container_of(cdev, struct ims_pcu_backlight, cdev);
+ struct ims_pcu *pcu =
+ container_of(backlight, struct ims_pcu, backlight);
+ __le16 br_val = cpu_to_le16(value);
+ int error;
+
+ mutex_lock(&pcu->cmd_mutex);
+
+ error = ims_pcu_execute_command(pcu, SET_BRIGHTNESS,
+ &br_val, sizeof(br_val));
+ if (error && error != -ENODEV)
+ dev_warn(pcu->dev,
+ "Failed to set desired brightness %u, error: %d\n",
+ value, error);
+
+ mutex_unlock(&pcu->cmd_mutex);
+
+ return error;
+}
+
+static enum led_brightness
+ims_pcu_backlight_get_brightness(struct led_classdev *cdev)
+{
+ struct ims_pcu_backlight *backlight =
+ container_of(cdev, struct ims_pcu_backlight, cdev);
+ struct ims_pcu *pcu =
+ container_of(backlight, struct ims_pcu, backlight);
+ int brightness;
+ int error;
+
+ mutex_lock(&pcu->cmd_mutex);
+
+ error = ims_pcu_execute_query(pcu, GET_BRIGHTNESS);
+ if (error) {
+ dev_warn(pcu->dev,
+ "Failed to get current brightness, error: %d\n",
+ error);
+ /* Assume the LED is OFF */
+ brightness = LED_OFF;
+ } else {
+ brightness =
+ get_unaligned_le16(&pcu->cmd_buf[IMS_PCU_DATA_OFFSET]);
+ }
+
+ mutex_unlock(&pcu->cmd_mutex);
+
+ return brightness;
+}
+
+static int ims_pcu_setup_backlight(struct ims_pcu *pcu)
+{
+ struct ims_pcu_backlight *backlight = &pcu->backlight;
+ int error;
+
+ snprintf(backlight->name, sizeof(backlight->name),
+ "pcu%d::kbd_backlight", pcu->device_no);
+
+ backlight->cdev.name = backlight->name;
+ backlight->cdev.max_brightness = IMS_PCU_MAX_BRIGHTNESS;
+ backlight->cdev.brightness_get = ims_pcu_backlight_get_brightness;
+ backlight->cdev.brightness_set_blocking =
+ ims_pcu_backlight_set_brightness;
+
+ error = led_classdev_register(pcu->dev, &backlight->cdev);
+ if (error) {
+ dev_err(pcu->dev,
+ "Failed to register backlight LED device, error: %d\n",
+ error);
+ return error;
+ }
+
+ return 0;
+}
+
+static void ims_pcu_destroy_backlight(struct ims_pcu *pcu)
+{
+ struct ims_pcu_backlight *backlight = &pcu->backlight;
+
+ led_classdev_unregister(&backlight->cdev);
+}
+
+
+/*********************************************************************
+ * Sysfs attributes handling *
+ *********************************************************************/
+
+struct ims_pcu_attribute {
+ struct device_attribute dattr;
+ size_t field_offset;
+ int field_length;
+};
+
+static ssize_t ims_pcu_attribute_show(struct device *dev,
+ struct device_attribute *dattr,
+ char *buf)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct ims_pcu *pcu = usb_get_intfdata(intf);
+ struct ims_pcu_attribute *attr =
+ container_of(dattr, struct ims_pcu_attribute, dattr);
+ char *field = (char *)pcu + attr->field_offset;
+
+ return scnprintf(buf, PAGE_SIZE, "%.*s\n", attr->field_length, field);
+}
+
+static ssize_t ims_pcu_attribute_store(struct device *dev,
+ struct device_attribute *dattr,
+ const char *buf, size_t count)
+{
+
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct ims_pcu *pcu = usb_get_intfdata(intf);
+ struct ims_pcu_attribute *attr =
+ container_of(dattr, struct ims_pcu_attribute, dattr);
+ char *field = (char *)pcu + attr->field_offset;
+ size_t data_len;
+ int error;
+
+ if (count > attr->field_length)
+ return -EINVAL;
+
+ data_len = strnlen(buf, attr->field_length);
+ if (data_len > attr->field_length)
+ return -EINVAL;
+
+ error = mutex_lock_interruptible(&pcu->cmd_mutex);
+ if (error)
+ return error;
+
+ memset(field, 0, attr->field_length);
+ memcpy(field, buf, data_len);
+
+ error = ims_pcu_set_info(pcu);
+
+ /*
+ * Even if update failed, let's fetch the info again as we just
+ * clobbered one of the fields.
+ */
+ ims_pcu_get_info(pcu);
+
+ mutex_unlock(&pcu->cmd_mutex);
+
+ return error < 0 ? error : count;
+}
+
+#define IMS_PCU_ATTR(_field, _mode) \
+struct ims_pcu_attribute ims_pcu_attr_##_field = { \
+ .dattr = __ATTR(_field, _mode, \
+ ims_pcu_attribute_show, \
+ ims_pcu_attribute_store), \
+ .field_offset = offsetof(struct ims_pcu, _field), \
+ .field_length = sizeof(((struct ims_pcu *)NULL)->_field), \
+}
+
+#define IMS_PCU_RO_ATTR(_field) \
+ IMS_PCU_ATTR(_field, S_IRUGO)
+#define IMS_PCU_RW_ATTR(_field) \
+ IMS_PCU_ATTR(_field, S_IRUGO | S_IWUSR)
+
+static IMS_PCU_RW_ATTR(part_number);
+static IMS_PCU_RW_ATTR(serial_number);
+static IMS_PCU_RW_ATTR(date_of_manufacturing);
+
+static IMS_PCU_RO_ATTR(fw_version);
+static IMS_PCU_RO_ATTR(bl_version);
+static IMS_PCU_RO_ATTR(reset_reason);
+
+static ssize_t ims_pcu_reset_device(struct device *dev,
+ struct device_attribute *dattr,
+ const char *buf, size_t count)
+{
+ static const u8 reset_byte = 1;
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct ims_pcu *pcu = usb_get_intfdata(intf);
+ int value;
+ int error;
+
+ error = kstrtoint(buf, 0, &value);
+ if (error)
+ return error;
+
+ if (value != 1)
+ return -EINVAL;
+
+ dev_info(pcu->dev, "Attempting to reset device\n");
+
+ error = ims_pcu_execute_command(pcu, PCU_RESET, &reset_byte, 1);
+ if (error) {
+ dev_info(pcu->dev,
+ "Failed to reset device, error: %d\n",
+ error);
+ return error;
+ }
+
+ return count;
+}
+
+static DEVICE_ATTR(reset_device, S_IWUSR, NULL, ims_pcu_reset_device);
+
+static ssize_t ims_pcu_update_firmware_store(struct device *dev,
+ struct device_attribute *dattr,
+ const char *buf, size_t count)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct ims_pcu *pcu = usb_get_intfdata(intf);
+ const struct firmware *fw = NULL;
+ int value;
+ int error;
+
+ error = kstrtoint(buf, 0, &value);
+ if (error)
+ return error;
+
+ if (value != 1)
+ return -EINVAL;
+
+ error = mutex_lock_interruptible(&pcu->cmd_mutex);
+ if (error)
+ return error;
+
+ error = request_ihex_firmware(&fw, IMS_PCU_FIRMWARE_NAME, pcu->dev);
+ if (error) {
+ dev_err(pcu->dev, "Failed to request firmware %s, error: %d\n",
+ IMS_PCU_FIRMWARE_NAME, error);
+ goto out;
+ }
+
+ /*
+ * If we are already in bootloader mode we can proceed with
+ * flashing the firmware.
+ *
+ * If we are in application mode, then we need to switch into
+ * bootloader mode, which will cause the device to disconnect
+ * and reconnect as different device.
+ */
+ if (pcu->bootloader_mode)
+ error = ims_pcu_handle_firmware_update(pcu, fw);
+ else
+ error = ims_pcu_switch_to_bootloader(pcu);
+
+ release_firmware(fw);
+
+out:
+ mutex_unlock(&pcu->cmd_mutex);
+ return error ?: count;
+}
+
+static DEVICE_ATTR(update_firmware, S_IWUSR,
+ NULL, ims_pcu_update_firmware_store);
+
+static ssize_t
+ims_pcu_update_firmware_status_show(struct device *dev,
+ struct device_attribute *dattr,
+ char *buf)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct ims_pcu *pcu = usb_get_intfdata(intf);
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", pcu->update_firmware_status);
+}
+
+static DEVICE_ATTR(update_firmware_status, S_IRUGO,
+ ims_pcu_update_firmware_status_show, NULL);
+
+static struct attribute *ims_pcu_attrs[] = {
+ &ims_pcu_attr_part_number.dattr.attr,
+ &ims_pcu_attr_serial_number.dattr.attr,
+ &ims_pcu_attr_date_of_manufacturing.dattr.attr,
+ &ims_pcu_attr_fw_version.dattr.attr,
+ &ims_pcu_attr_bl_version.dattr.attr,
+ &ims_pcu_attr_reset_reason.dattr.attr,
+ &dev_attr_reset_device.attr,
+ &dev_attr_update_firmware.attr,
+ &dev_attr_update_firmware_status.attr,
+ NULL
+};
+
+static umode_t ims_pcu_is_attr_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct ims_pcu *pcu = usb_get_intfdata(intf);
+ umode_t mode = attr->mode;
+
+ if (pcu->bootloader_mode) {
+ if (attr != &dev_attr_update_firmware_status.attr &&
+ attr != &dev_attr_update_firmware.attr &&
+ attr != &dev_attr_reset_device.attr) {
+ mode = 0;
+ }
+ } else {
+ if (attr == &dev_attr_update_firmware_status.attr)
+ mode = 0;
+ }
+
+ return mode;
+}
+
+static const struct attribute_group ims_pcu_attr_group = {
+ .is_visible = ims_pcu_is_attr_visible,
+ .attrs = ims_pcu_attrs,
+};
+
+/* Support for a separate OFN attribute group */
+
+#define OFN_REG_RESULT_OFFSET 2
+
+static int ims_pcu_read_ofn_config(struct ims_pcu *pcu, u8 addr, u8 *data)
+{
+ int error;
+ s16 result;
+
+ error = ims_pcu_execute_command(pcu, OFN_GET_CONFIG,
+ &addr, sizeof(addr));
+ if (error)
+ return error;
+
+ result = (s16)get_unaligned_le16(pcu->cmd_buf + OFN_REG_RESULT_OFFSET);
+ if (result < 0)
+ return -EIO;
+
+ /* We only need LSB */
+ *data = pcu->cmd_buf[OFN_REG_RESULT_OFFSET];
+ return 0;
+}
+
+static int ims_pcu_write_ofn_config(struct ims_pcu *pcu, u8 addr, u8 data)
+{
+ u8 buffer[] = { addr, data };
+ int error;
+ s16 result;
+
+ error = ims_pcu_execute_command(pcu, OFN_SET_CONFIG,
+ &buffer, sizeof(buffer));
+ if (error)
+ return error;
+
+ result = (s16)get_unaligned_le16(pcu->cmd_buf + OFN_REG_RESULT_OFFSET);
+ if (result < 0)
+ return -EIO;
+
+ return 0;
+}
+
+static ssize_t ims_pcu_ofn_reg_data_show(struct device *dev,
+ struct device_attribute *dattr,
+ char *buf)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct ims_pcu *pcu = usb_get_intfdata(intf);
+ int error;
+ u8 data;
+
+ mutex_lock(&pcu->cmd_mutex);
+ error = ims_pcu_read_ofn_config(pcu, pcu->ofn_reg_addr, &data);
+ mutex_unlock(&pcu->cmd_mutex);
+
+ if (error)
+ return error;
+
+ return scnprintf(buf, PAGE_SIZE, "%x\n", data);
+}
+
+static ssize_t ims_pcu_ofn_reg_data_store(struct device *dev,
+ struct device_attribute *dattr,
+ const char *buf, size_t count)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct ims_pcu *pcu = usb_get_intfdata(intf);
+ int error;
+ u8 value;
+
+ error = kstrtou8(buf, 0, &value);
+ if (error)
+ return error;
+
+ mutex_lock(&pcu->cmd_mutex);
+ error = ims_pcu_write_ofn_config(pcu, pcu->ofn_reg_addr, value);
+ mutex_unlock(&pcu->cmd_mutex);
+
+ return error ?: count;
+}
+
+static DEVICE_ATTR(reg_data, S_IRUGO | S_IWUSR,
+ ims_pcu_ofn_reg_data_show, ims_pcu_ofn_reg_data_store);
+
+static ssize_t ims_pcu_ofn_reg_addr_show(struct device *dev,
+ struct device_attribute *dattr,
+ char *buf)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct ims_pcu *pcu = usb_get_intfdata(intf);
+ int error;
+
+ mutex_lock(&pcu->cmd_mutex);
+ error = scnprintf(buf, PAGE_SIZE, "%x\n", pcu->ofn_reg_addr);
+ mutex_unlock(&pcu->cmd_mutex);
+
+ return error;
+}
+
+static ssize_t ims_pcu_ofn_reg_addr_store(struct device *dev,
+ struct device_attribute *dattr,
+ const char *buf, size_t count)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct ims_pcu *pcu = usb_get_intfdata(intf);
+ int error;
+ u8 value;
+
+ error = kstrtou8(buf, 0, &value);
+ if (error)
+ return error;
+
+ mutex_lock(&pcu->cmd_mutex);
+ pcu->ofn_reg_addr = value;
+ mutex_unlock(&pcu->cmd_mutex);
+
+ return count;
+}
+
+static DEVICE_ATTR(reg_addr, S_IRUGO | S_IWUSR,
+ ims_pcu_ofn_reg_addr_show, ims_pcu_ofn_reg_addr_store);
+
+struct ims_pcu_ofn_bit_attribute {
+ struct device_attribute dattr;
+ u8 addr;
+ u8 nr;
+};
+
+static ssize_t ims_pcu_ofn_bit_show(struct device *dev,
+ struct device_attribute *dattr,
+ char *buf)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct ims_pcu *pcu = usb_get_intfdata(intf);
+ struct ims_pcu_ofn_bit_attribute *attr =
+ container_of(dattr, struct ims_pcu_ofn_bit_attribute, dattr);
+ int error;
+ u8 data;
+
+ mutex_lock(&pcu->cmd_mutex);
+ error = ims_pcu_read_ofn_config(pcu, attr->addr, &data);
+ mutex_unlock(&pcu->cmd_mutex);
+
+ if (error)
+ return error;
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", !!(data & (1 << attr->nr)));
+}
+
+static ssize_t ims_pcu_ofn_bit_store(struct device *dev,
+ struct device_attribute *dattr,
+ const char *buf, size_t count)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct ims_pcu *pcu = usb_get_intfdata(intf);
+ struct ims_pcu_ofn_bit_attribute *attr =
+ container_of(dattr, struct ims_pcu_ofn_bit_attribute, dattr);
+ int error;
+ int value;
+ u8 data;
+
+ error = kstrtoint(buf, 0, &value);
+ if (error)
+ return error;
+
+ if (value > 1)
+ return -EINVAL;
+
+ mutex_lock(&pcu->cmd_mutex);
+
+ error = ims_pcu_read_ofn_config(pcu, attr->addr, &data);
+ if (!error) {
+ if (value)
+ data |= 1U << attr->nr;
+ else
+ data &= ~(1U << attr->nr);
+
+ error = ims_pcu_write_ofn_config(pcu, attr->addr, data);
+ }
+
+ mutex_unlock(&pcu->cmd_mutex);
+
+ return error ?: count;
+}
+
+#define IMS_PCU_OFN_BIT_ATTR(_field, _addr, _nr) \
+struct ims_pcu_ofn_bit_attribute ims_pcu_ofn_attr_##_field = { \
+ .dattr = __ATTR(_field, S_IWUSR | S_IRUGO, \
+ ims_pcu_ofn_bit_show, ims_pcu_ofn_bit_store), \
+ .addr = _addr, \
+ .nr = _nr, \
+}
+
+static IMS_PCU_OFN_BIT_ATTR(engine_enable, 0x60, 7);
+static IMS_PCU_OFN_BIT_ATTR(speed_enable, 0x60, 6);
+static IMS_PCU_OFN_BIT_ATTR(assert_enable, 0x60, 5);
+static IMS_PCU_OFN_BIT_ATTR(xyquant_enable, 0x60, 4);
+static IMS_PCU_OFN_BIT_ATTR(xyscale_enable, 0x60, 1);
+
+static IMS_PCU_OFN_BIT_ATTR(scale_x2, 0x63, 6);
+static IMS_PCU_OFN_BIT_ATTR(scale_y2, 0x63, 7);
+
+static struct attribute *ims_pcu_ofn_attrs[] = {
+ &dev_attr_reg_data.attr,
+ &dev_attr_reg_addr.attr,
+ &ims_pcu_ofn_attr_engine_enable.dattr.attr,
+ &ims_pcu_ofn_attr_speed_enable.dattr.attr,
+ &ims_pcu_ofn_attr_assert_enable.dattr.attr,
+ &ims_pcu_ofn_attr_xyquant_enable.dattr.attr,
+ &ims_pcu_ofn_attr_xyscale_enable.dattr.attr,
+ &ims_pcu_ofn_attr_scale_x2.dattr.attr,
+ &ims_pcu_ofn_attr_scale_y2.dattr.attr,
+ NULL
+};
+
+static const struct attribute_group ims_pcu_ofn_attr_group = {
+ .name = "ofn",
+ .attrs = ims_pcu_ofn_attrs,
+};
+
+static void ims_pcu_irq(struct urb *urb)
+{
+ struct ims_pcu *pcu = urb->context;
+ int retval, status;
+
+ status = urb->status;
+
+ switch (status) {
+ case 0:
+ /* success */
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* this urb is terminated, clean up */
+ dev_dbg(pcu->dev, "%s - urb shutting down with status: %d\n",
+ __func__, status);
+ return;
+ default:
+ dev_dbg(pcu->dev, "%s - nonzero urb status received: %d\n",
+ __func__, status);
+ goto exit;
+ }
+
+ dev_dbg(pcu->dev, "%s: received %d: %*ph\n", __func__,
+ urb->actual_length, urb->actual_length, pcu->urb_in_buf);
+
+ if (urb == pcu->urb_in)
+ ims_pcu_process_data(pcu, urb);
+
+exit:
+ retval = usb_submit_urb(urb, GFP_ATOMIC);
+ if (retval && retval != -ENODEV)
+ dev_err(pcu->dev, "%s - usb_submit_urb failed with result %d\n",
+ __func__, retval);
+}
+
+static int ims_pcu_buffers_alloc(struct ims_pcu *pcu)
+{
+ int error;
+
+ pcu->urb_in_buf = usb_alloc_coherent(pcu->udev, pcu->max_in_size,
+ GFP_KERNEL, &pcu->read_dma);
+ if (!pcu->urb_in_buf) {
+ dev_err(pcu->dev,
+ "Failed to allocate memory for read buffer\n");
+ return -ENOMEM;
+ }
+
+ pcu->urb_in = usb_alloc_urb(0, GFP_KERNEL);
+ if (!pcu->urb_in) {
+ dev_err(pcu->dev, "Failed to allocate input URB\n");
+ error = -ENOMEM;
+ goto err_free_urb_in_buf;
+ }
+
+ pcu->urb_in->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ pcu->urb_in->transfer_dma = pcu->read_dma;
+
+ usb_fill_bulk_urb(pcu->urb_in, pcu->udev,
+ usb_rcvbulkpipe(pcu->udev,
+ pcu->ep_in->bEndpointAddress),
+ pcu->urb_in_buf, pcu->max_in_size,
+ ims_pcu_irq, pcu);
+
+ /*
+ * We are using usb_bulk_msg() for sending so there is no point
+ * in allocating memory with usb_alloc_coherent().
+ */
+ pcu->urb_out_buf = kmalloc(pcu->max_out_size, GFP_KERNEL);
+ if (!pcu->urb_out_buf) {
+ dev_err(pcu->dev, "Failed to allocate memory for write buffer\n");
+ error = -ENOMEM;
+ goto err_free_in_urb;
+ }
+
+ pcu->urb_ctrl_buf = usb_alloc_coherent(pcu->udev, pcu->max_ctrl_size,
+ GFP_KERNEL, &pcu->ctrl_dma);
+ if (!pcu->urb_ctrl_buf) {
+ dev_err(pcu->dev,
+ "Failed to allocate memory for read buffer\n");
+ error = -ENOMEM;
+ goto err_free_urb_out_buf;
+ }
+
+ pcu->urb_ctrl = usb_alloc_urb(0, GFP_KERNEL);
+ if (!pcu->urb_ctrl) {
+ dev_err(pcu->dev, "Failed to allocate input URB\n");
+ error = -ENOMEM;
+ goto err_free_urb_ctrl_buf;
+ }
+
+ pcu->urb_ctrl->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ pcu->urb_ctrl->transfer_dma = pcu->ctrl_dma;
+
+ usb_fill_int_urb(pcu->urb_ctrl, pcu->udev,
+ usb_rcvintpipe(pcu->udev,
+ pcu->ep_ctrl->bEndpointAddress),
+ pcu->urb_ctrl_buf, pcu->max_ctrl_size,
+ ims_pcu_irq, pcu, pcu->ep_ctrl->bInterval);
+
+ return 0;
+
+err_free_urb_ctrl_buf:
+ usb_free_coherent(pcu->udev, pcu->max_ctrl_size,
+ pcu->urb_ctrl_buf, pcu->ctrl_dma);
+err_free_urb_out_buf:
+ kfree(pcu->urb_out_buf);
+err_free_in_urb:
+ usb_free_urb(pcu->urb_in);
+err_free_urb_in_buf:
+ usb_free_coherent(pcu->udev, pcu->max_in_size,
+ pcu->urb_in_buf, pcu->read_dma);
+ return error;
+}
+
+static void ims_pcu_buffers_free(struct ims_pcu *pcu)
+{
+ usb_kill_urb(pcu->urb_in);
+ usb_free_urb(pcu->urb_in);
+
+ usb_free_coherent(pcu->udev, pcu->max_out_size,
+ pcu->urb_in_buf, pcu->read_dma);
+
+ kfree(pcu->urb_out_buf);
+
+ usb_kill_urb(pcu->urb_ctrl);
+ usb_free_urb(pcu->urb_ctrl);
+
+ usb_free_coherent(pcu->udev, pcu->max_ctrl_size,
+ pcu->urb_ctrl_buf, pcu->ctrl_dma);
+}
+
+static const struct usb_cdc_union_desc *
+ims_pcu_get_cdc_union_desc(struct usb_interface *intf)
+{
+ const void *buf = intf->altsetting->extra;
+ size_t buflen = intf->altsetting->extralen;
+ struct usb_cdc_union_desc *union_desc;
+
+ if (!buf) {
+ dev_err(&intf->dev, "Missing descriptor data\n");
+ return NULL;
+ }
+
+ if (!buflen) {
+ dev_err(&intf->dev, "Zero length descriptor\n");
+ return NULL;
+ }
+
+ while (buflen >= sizeof(*union_desc)) {
+ union_desc = (struct usb_cdc_union_desc *)buf;
+
+ if (union_desc->bLength > buflen) {
+ dev_err(&intf->dev, "Too large descriptor\n");
+ return NULL;
+ }
+
+ if (union_desc->bDescriptorType == USB_DT_CS_INTERFACE &&
+ union_desc->bDescriptorSubType == USB_CDC_UNION_TYPE) {
+ dev_dbg(&intf->dev, "Found union header\n");
+
+ if (union_desc->bLength >= sizeof(*union_desc))
+ return union_desc;
+
+ dev_err(&intf->dev,
+ "Union descriptor too short (%d vs %zd)\n",
+ union_desc->bLength, sizeof(*union_desc));
+ return NULL;
+ }
+
+ buflen -= union_desc->bLength;
+ buf += union_desc->bLength;
+ }
+
+ dev_err(&intf->dev, "Missing CDC union descriptor\n");
+ return NULL;
+}
+
+static int ims_pcu_parse_cdc_data(struct usb_interface *intf, struct ims_pcu *pcu)
+{
+ const struct usb_cdc_union_desc *union_desc;
+ struct usb_host_interface *alt;
+
+ union_desc = ims_pcu_get_cdc_union_desc(intf);
+ if (!union_desc)
+ return -EINVAL;
+
+ pcu->ctrl_intf = usb_ifnum_to_if(pcu->udev,
+ union_desc->bMasterInterface0);
+ if (!pcu->ctrl_intf)
+ return -EINVAL;
+
+ alt = pcu->ctrl_intf->cur_altsetting;
+
+ if (alt->desc.bNumEndpoints < 1)
+ return -ENODEV;
+
+ pcu->ep_ctrl = &alt->endpoint[0].desc;
+ pcu->max_ctrl_size = usb_endpoint_maxp(pcu->ep_ctrl);
+
+ pcu->data_intf = usb_ifnum_to_if(pcu->udev,
+ union_desc->bSlaveInterface0);
+ if (!pcu->data_intf)
+ return -EINVAL;
+
+ alt = pcu->data_intf->cur_altsetting;
+ if (alt->desc.bNumEndpoints != 2) {
+ dev_err(pcu->dev,
+ "Incorrect number of endpoints on data interface (%d)\n",
+ alt->desc.bNumEndpoints);
+ return -EINVAL;
+ }
+
+ pcu->ep_out = &alt->endpoint[0].desc;
+ if (!usb_endpoint_is_bulk_out(pcu->ep_out)) {
+ dev_err(pcu->dev,
+ "First endpoint on data interface is not BULK OUT\n");
+ return -EINVAL;
+ }
+
+ pcu->max_out_size = usb_endpoint_maxp(pcu->ep_out);
+ if (pcu->max_out_size < 8) {
+ dev_err(pcu->dev,
+ "Max OUT packet size is too small (%zd)\n",
+ pcu->max_out_size);
+ return -EINVAL;
+ }
+
+ pcu->ep_in = &alt->endpoint[1].desc;
+ if (!usb_endpoint_is_bulk_in(pcu->ep_in)) {
+ dev_err(pcu->dev,
+ "Second endpoint on data interface is not BULK IN\n");
+ return -EINVAL;
+ }
+
+ pcu->max_in_size = usb_endpoint_maxp(pcu->ep_in);
+ if (pcu->max_in_size < 8) {
+ dev_err(pcu->dev,
+ "Max IN packet size is too small (%zd)\n",
+ pcu->max_in_size);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ims_pcu_start_io(struct ims_pcu *pcu)
+{
+ int error;
+
+ error = usb_submit_urb(pcu->urb_ctrl, GFP_KERNEL);
+ if (error) {
+ dev_err(pcu->dev,
+ "Failed to start control IO - usb_submit_urb failed with result: %d\n",
+ error);
+ return -EIO;
+ }
+
+ error = usb_submit_urb(pcu->urb_in, GFP_KERNEL);
+ if (error) {
+ dev_err(pcu->dev,
+ "Failed to start IO - usb_submit_urb failed with result: %d\n",
+ error);
+ usb_kill_urb(pcu->urb_ctrl);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static void ims_pcu_stop_io(struct ims_pcu *pcu)
+{
+ usb_kill_urb(pcu->urb_in);
+ usb_kill_urb(pcu->urb_ctrl);
+}
+
+static int ims_pcu_line_setup(struct ims_pcu *pcu)
+{
+ struct usb_host_interface *interface = pcu->ctrl_intf->cur_altsetting;
+ struct usb_cdc_line_coding *line = (void *)pcu->cmd_buf;
+ int error;
+
+ memset(line, 0, sizeof(*line));
+ line->dwDTERate = cpu_to_le32(57600);
+ line->bDataBits = 8;
+
+ error = usb_control_msg(pcu->udev, usb_sndctrlpipe(pcu->udev, 0),
+ USB_CDC_REQ_SET_LINE_CODING,
+ USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0, interface->desc.bInterfaceNumber,
+ line, sizeof(struct usb_cdc_line_coding),
+ 5000);
+ if (error < 0) {
+ dev_err(pcu->dev, "Failed to set line coding, error: %d\n",
+ error);
+ return error;
+ }
+
+ error = usb_control_msg(pcu->udev, usb_sndctrlpipe(pcu->udev, 0),
+ USB_CDC_REQ_SET_CONTROL_LINE_STATE,
+ USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0x03, interface->desc.bInterfaceNumber,
+ NULL, 0, 5000);
+ if (error < 0) {
+ dev_err(pcu->dev, "Failed to set line state, error: %d\n",
+ error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int ims_pcu_get_device_info(struct ims_pcu *pcu)
+{
+ int error;
+
+ error = ims_pcu_get_info(pcu);
+ if (error)
+ return error;
+
+ error = ims_pcu_execute_query(pcu, GET_FW_VERSION);
+ if (error) {
+ dev_err(pcu->dev,
+ "GET_FW_VERSION command failed, error: %d\n", error);
+ return error;
+ }
+
+ snprintf(pcu->fw_version, sizeof(pcu->fw_version),
+ "%02d%02d%02d%02d.%c%c",
+ pcu->cmd_buf[2], pcu->cmd_buf[3], pcu->cmd_buf[4], pcu->cmd_buf[5],
+ pcu->cmd_buf[6], pcu->cmd_buf[7]);
+
+ error = ims_pcu_execute_query(pcu, GET_BL_VERSION);
+ if (error) {
+ dev_err(pcu->dev,
+ "GET_BL_VERSION command failed, error: %d\n", error);
+ return error;
+ }
+
+ snprintf(pcu->bl_version, sizeof(pcu->bl_version),
+ "%02d%02d%02d%02d.%c%c",
+ pcu->cmd_buf[2], pcu->cmd_buf[3], pcu->cmd_buf[4], pcu->cmd_buf[5],
+ pcu->cmd_buf[6], pcu->cmd_buf[7]);
+
+ error = ims_pcu_execute_query(pcu, RESET_REASON);
+ if (error) {
+ dev_err(pcu->dev,
+ "RESET_REASON command failed, error: %d\n", error);
+ return error;
+ }
+
+ snprintf(pcu->reset_reason, sizeof(pcu->reset_reason),
+ "%02x", pcu->cmd_buf[IMS_PCU_DATA_OFFSET]);
+
+ dev_dbg(pcu->dev,
+ "P/N: %s, MD: %s, S/N: %s, FW: %s, BL: %s, RR: %s\n",
+ pcu->part_number,
+ pcu->date_of_manufacturing,
+ pcu->serial_number,
+ pcu->fw_version,
+ pcu->bl_version,
+ pcu->reset_reason);
+
+ return 0;
+}
+
+static int ims_pcu_identify_type(struct ims_pcu *pcu, u8 *device_id)
+{
+ int error;
+
+ error = ims_pcu_execute_query(pcu, GET_DEVICE_ID);
+ if (error) {
+ dev_err(pcu->dev,
+ "GET_DEVICE_ID command failed, error: %d\n", error);
+ return error;
+ }
+
+ *device_id = pcu->cmd_buf[IMS_PCU_DATA_OFFSET];
+ dev_dbg(pcu->dev, "Detected device ID: %d\n", *device_id);
+
+ return 0;
+}
+
+static int ims_pcu_init_application_mode(struct ims_pcu *pcu)
+{
+ static atomic_t device_no = ATOMIC_INIT(-1);
+
+ const struct ims_pcu_device_info *info;
+ int error;
+
+ error = ims_pcu_get_device_info(pcu);
+ if (error) {
+ /* Device does not respond to basic queries, hopeless */
+ return error;
+ }
+
+ error = ims_pcu_identify_type(pcu, &pcu->device_id);
+ if (error) {
+ dev_err(pcu->dev,
+ "Failed to identify device, error: %d\n", error);
+ /*
+ * Do not signal error, but do not create input nor
+ * backlight devices either, let userspace figure this
+ * out (flash a new firmware?).
+ */
+ return 0;
+ }
+
+ if (pcu->device_id >= ARRAY_SIZE(ims_pcu_device_info) ||
+ !ims_pcu_device_info[pcu->device_id].keymap) {
+ dev_err(pcu->dev, "Device ID %d is not valid\n", pcu->device_id);
+ /* Same as above, punt to userspace */
+ return 0;
+ }
+
+ /* Device appears to be operable, complete initialization */
+ pcu->device_no = atomic_inc_return(&device_no);
+
+ /*
+ * PCU-B devices, both GEN_1 and GEN_2 do not have OFN sensor
+ */
+ if (pcu->device_id != IMS_PCU_PCU_B_DEVICE_ID) {
+ error = sysfs_create_group(&pcu->dev->kobj,
+ &ims_pcu_ofn_attr_group);
+ if (error)
+ return error;
+ }
+
+ error = ims_pcu_setup_backlight(pcu);
+ if (error)
+ return error;
+
+ info = &ims_pcu_device_info[pcu->device_id];
+ error = ims_pcu_setup_buttons(pcu, info->keymap, info->keymap_len);
+ if (error)
+ goto err_destroy_backlight;
+
+ if (info->has_gamepad) {
+ error = ims_pcu_setup_gamepad(pcu);
+ if (error)
+ goto err_destroy_buttons;
+ }
+
+ pcu->setup_complete = true;
+
+ return 0;
+
+err_destroy_buttons:
+ ims_pcu_destroy_buttons(pcu);
+err_destroy_backlight:
+ ims_pcu_destroy_backlight(pcu);
+ return error;
+}
+
+static void ims_pcu_destroy_application_mode(struct ims_pcu *pcu)
+{
+ if (pcu->setup_complete) {
+ pcu->setup_complete = false;
+ mb(); /* make sure flag setting is not reordered */
+
+ if (pcu->gamepad)
+ ims_pcu_destroy_gamepad(pcu);
+ ims_pcu_destroy_buttons(pcu);
+ ims_pcu_destroy_backlight(pcu);
+
+ if (pcu->device_id != IMS_PCU_PCU_B_DEVICE_ID)
+ sysfs_remove_group(&pcu->dev->kobj,
+ &ims_pcu_ofn_attr_group);
+ }
+}
+
+static int ims_pcu_init_bootloader_mode(struct ims_pcu *pcu)
+{
+ int error;
+
+ error = ims_pcu_execute_bl_command(pcu, QUERY_DEVICE, NULL, 0,
+ IMS_PCU_CMD_RESPONSE_TIMEOUT);
+ if (error) {
+ dev_err(pcu->dev, "Bootloader does not respond, aborting\n");
+ return error;
+ }
+
+ pcu->fw_start_addr =
+ get_unaligned_le32(&pcu->cmd_buf[IMS_PCU_DATA_OFFSET + 11]);
+ pcu->fw_end_addr =
+ get_unaligned_le32(&pcu->cmd_buf[IMS_PCU_DATA_OFFSET + 15]);
+
+ dev_info(pcu->dev,
+ "Device is in bootloader mode (addr 0x%08x-0x%08x), requesting firmware\n",
+ pcu->fw_start_addr, pcu->fw_end_addr);
+
+ error = request_firmware_nowait(THIS_MODULE, true,
+ IMS_PCU_FIRMWARE_NAME,
+ pcu->dev, GFP_KERNEL, pcu,
+ ims_pcu_process_async_firmware);
+ if (error) {
+ /* This error is not fatal, let userspace have another chance */
+ complete(&pcu->async_firmware_done);
+ }
+
+ return 0;
+}
+
+static void ims_pcu_destroy_bootloader_mode(struct ims_pcu *pcu)
+{
+ /* Make sure our initial firmware request has completed */
+ wait_for_completion(&pcu->async_firmware_done);
+}
+
+#define IMS_PCU_APPLICATION_MODE 0
+#define IMS_PCU_BOOTLOADER_MODE 1
+
+static struct usb_driver ims_pcu_driver;
+
+static int ims_pcu_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev(intf);
+ struct ims_pcu *pcu;
+ int error;
+
+ pcu = kzalloc(sizeof(struct ims_pcu), GFP_KERNEL);
+ if (!pcu)
+ return -ENOMEM;
+
+ pcu->dev = &intf->dev;
+ pcu->udev = udev;
+ pcu->bootloader_mode = id->driver_info == IMS_PCU_BOOTLOADER_MODE;
+ mutex_init(&pcu->cmd_mutex);
+ init_completion(&pcu->cmd_done);
+ init_completion(&pcu->async_firmware_done);
+
+ error = ims_pcu_parse_cdc_data(intf, pcu);
+ if (error)
+ goto err_free_mem;
+
+ error = usb_driver_claim_interface(&ims_pcu_driver,
+ pcu->data_intf, pcu);
+ if (error) {
+ dev_err(&intf->dev,
+ "Unable to claim corresponding data interface: %d\n",
+ error);
+ goto err_free_mem;
+ }
+
+ usb_set_intfdata(pcu->ctrl_intf, pcu);
+
+ error = ims_pcu_buffers_alloc(pcu);
+ if (error)
+ goto err_unclaim_intf;
+
+ error = ims_pcu_start_io(pcu);
+ if (error)
+ goto err_free_buffers;
+
+ error = ims_pcu_line_setup(pcu);
+ if (error)
+ goto err_stop_io;
+
+ error = sysfs_create_group(&intf->dev.kobj, &ims_pcu_attr_group);
+ if (error)
+ goto err_stop_io;
+
+ error = pcu->bootloader_mode ?
+ ims_pcu_init_bootloader_mode(pcu) :
+ ims_pcu_init_application_mode(pcu);
+ if (error)
+ goto err_remove_sysfs;
+
+ return 0;
+
+err_remove_sysfs:
+ sysfs_remove_group(&intf->dev.kobj, &ims_pcu_attr_group);
+err_stop_io:
+ ims_pcu_stop_io(pcu);
+err_free_buffers:
+ ims_pcu_buffers_free(pcu);
+err_unclaim_intf:
+ usb_driver_release_interface(&ims_pcu_driver, pcu->data_intf);
+err_free_mem:
+ kfree(pcu);
+ return error;
+}
+
+static void ims_pcu_disconnect(struct usb_interface *intf)
+{
+ struct ims_pcu *pcu = usb_get_intfdata(intf);
+ struct usb_host_interface *alt = intf->cur_altsetting;
+
+ usb_set_intfdata(intf, NULL);
+
+ /*
+ * See if we are dealing with control or data interface. The cleanup
+ * happens when we unbind primary (control) interface.
+ */
+ if (alt->desc.bInterfaceClass != USB_CLASS_COMM)
+ return;
+
+ sysfs_remove_group(&intf->dev.kobj, &ims_pcu_attr_group);
+
+ ims_pcu_stop_io(pcu);
+
+ if (pcu->bootloader_mode)
+ ims_pcu_destroy_bootloader_mode(pcu);
+ else
+ ims_pcu_destroy_application_mode(pcu);
+
+ ims_pcu_buffers_free(pcu);
+ kfree(pcu);
+}
+
+#ifdef CONFIG_PM
+static int ims_pcu_suspend(struct usb_interface *intf,
+ pm_message_t message)
+{
+ struct ims_pcu *pcu = usb_get_intfdata(intf);
+ struct usb_host_interface *alt = intf->cur_altsetting;
+
+ if (alt->desc.bInterfaceClass == USB_CLASS_COMM)
+ ims_pcu_stop_io(pcu);
+
+ return 0;
+}
+
+static int ims_pcu_resume(struct usb_interface *intf)
+{
+ struct ims_pcu *pcu = usb_get_intfdata(intf);
+ struct usb_host_interface *alt = intf->cur_altsetting;
+ int retval = 0;
+
+ if (alt->desc.bInterfaceClass == USB_CLASS_COMM) {
+ retval = ims_pcu_start_io(pcu);
+ if (retval == 0)
+ retval = ims_pcu_line_setup(pcu);
+ }
+
+ return retval;
+}
+#endif
+
+static const struct usb_device_id ims_pcu_id_table[] = {
+ {
+ USB_DEVICE_AND_INTERFACE_INFO(0x04d8, 0x0082,
+ USB_CLASS_COMM,
+ USB_CDC_SUBCLASS_ACM,
+ USB_CDC_ACM_PROTO_AT_V25TER),
+ .driver_info = IMS_PCU_APPLICATION_MODE,
+ },
+ {
+ USB_DEVICE_AND_INTERFACE_INFO(0x04d8, 0x0083,
+ USB_CLASS_COMM,
+ USB_CDC_SUBCLASS_ACM,
+ USB_CDC_ACM_PROTO_AT_V25TER),
+ .driver_info = IMS_PCU_BOOTLOADER_MODE,
+ },
+ { }
+};
+
+static struct usb_driver ims_pcu_driver = {
+ .name = "ims_pcu",
+ .id_table = ims_pcu_id_table,
+ .probe = ims_pcu_probe,
+ .disconnect = ims_pcu_disconnect,
+#ifdef CONFIG_PM
+ .suspend = ims_pcu_suspend,
+ .resume = ims_pcu_resume,
+ .reset_resume = ims_pcu_resume,
+#endif
+};
+
+module_usb_driver(ims_pcu_driver);
+
+MODULE_DESCRIPTION("IMS Passenger Control Unit driver");
+MODULE_AUTHOR("Dmitry Torokhov <dmitry.torokhov@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/iqs269a.c b/drivers/input/misc/iqs269a.c
new file mode 100644
index 000000000..a348247d3
--- /dev/null
+++ b/drivers/input/misc/iqs269a.c
@@ -0,0 +1,1826 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Azoteq IQS269A Capacitive Touch Controller
+ *
+ * Copyright (C) 2020 Jeff LaBundy <jeff@labundy.com>
+ *
+ * This driver registers up to 3 input devices: one representing capacitive or
+ * inductive keys as well as Hall-effect switches, and one for each of the two
+ * axial sliders presented by the device.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#define IQS269_VER_INFO 0x00
+#define IQS269_VER_INFO_PROD_NUM 0x4F
+
+#define IQS269_SYS_FLAGS 0x02
+#define IQS269_SYS_FLAGS_SHOW_RESET BIT(15)
+#define IQS269_SYS_FLAGS_PWR_MODE_MASK GENMASK(12, 11)
+#define IQS269_SYS_FLAGS_PWR_MODE_SHIFT 11
+#define IQS269_SYS_FLAGS_IN_ATI BIT(10)
+
+#define IQS269_CHx_COUNTS 0x08
+
+#define IQS269_SLIDER_X 0x30
+
+#define IQS269_CAL_DATA_A 0x35
+#define IQS269_CAL_DATA_A_HALL_BIN_L_MASK GENMASK(15, 12)
+#define IQS269_CAL_DATA_A_HALL_BIN_L_SHIFT 12
+#define IQS269_CAL_DATA_A_HALL_BIN_R_MASK GENMASK(11, 8)
+#define IQS269_CAL_DATA_A_HALL_BIN_R_SHIFT 8
+
+#define IQS269_SYS_SETTINGS 0x80
+#define IQS269_SYS_SETTINGS_CLK_DIV BIT(15)
+#define IQS269_SYS_SETTINGS_ULP_AUTO BIT(14)
+#define IQS269_SYS_SETTINGS_DIS_AUTO BIT(13)
+#define IQS269_SYS_SETTINGS_PWR_MODE_MASK GENMASK(12, 11)
+#define IQS269_SYS_SETTINGS_PWR_MODE_SHIFT 11
+#define IQS269_SYS_SETTINGS_PWR_MODE_MAX 3
+#define IQS269_SYS_SETTINGS_ULP_UPDATE_MASK GENMASK(10, 8)
+#define IQS269_SYS_SETTINGS_ULP_UPDATE_SHIFT 8
+#define IQS269_SYS_SETTINGS_ULP_UPDATE_MAX 7
+#define IQS269_SYS_SETTINGS_RESEED_OFFSET BIT(6)
+#define IQS269_SYS_SETTINGS_EVENT_MODE BIT(5)
+#define IQS269_SYS_SETTINGS_EVENT_MODE_LP BIT(4)
+#define IQS269_SYS_SETTINGS_REDO_ATI BIT(2)
+#define IQS269_SYS_SETTINGS_ACK_RESET BIT(0)
+
+#define IQS269_FILT_STR_LP_LTA_MASK GENMASK(7, 6)
+#define IQS269_FILT_STR_LP_LTA_SHIFT 6
+#define IQS269_FILT_STR_LP_CNT_MASK GENMASK(5, 4)
+#define IQS269_FILT_STR_LP_CNT_SHIFT 4
+#define IQS269_FILT_STR_NP_LTA_MASK GENMASK(3, 2)
+#define IQS269_FILT_STR_NP_LTA_SHIFT 2
+#define IQS269_FILT_STR_NP_CNT_MASK GENMASK(1, 0)
+#define IQS269_FILT_STR_MAX 3
+
+#define IQS269_EVENT_MASK_SYS BIT(6)
+#define IQS269_EVENT_MASK_DEEP BIT(2)
+#define IQS269_EVENT_MASK_TOUCH BIT(1)
+#define IQS269_EVENT_MASK_PROX BIT(0)
+
+#define IQS269_RATE_NP_MS_MAX 255
+#define IQS269_RATE_LP_MS_MAX 255
+#define IQS269_RATE_ULP_MS_MAX 4080
+#define IQS269_TIMEOUT_PWR_MS_MAX 130560
+#define IQS269_TIMEOUT_LTA_MS_MAX 130560
+
+#define IQS269_MISC_A_ATI_BAND_DISABLE BIT(15)
+#define IQS269_MISC_A_ATI_LP_ONLY BIT(14)
+#define IQS269_MISC_A_ATI_BAND_TIGHTEN BIT(13)
+#define IQS269_MISC_A_FILT_DISABLE BIT(12)
+#define IQS269_MISC_A_GPIO3_SELECT_MASK GENMASK(10, 8)
+#define IQS269_MISC_A_GPIO3_SELECT_SHIFT 8
+#define IQS269_MISC_A_DUAL_DIR BIT(6)
+#define IQS269_MISC_A_TX_FREQ_MASK GENMASK(5, 4)
+#define IQS269_MISC_A_TX_FREQ_SHIFT 4
+#define IQS269_MISC_A_TX_FREQ_MAX 3
+#define IQS269_MISC_A_GLOBAL_CAP_SIZE BIT(0)
+
+#define IQS269_MISC_B_RESEED_UI_SEL_MASK GENMASK(7, 6)
+#define IQS269_MISC_B_RESEED_UI_SEL_SHIFT 6
+#define IQS269_MISC_B_RESEED_UI_SEL_MAX 3
+#define IQS269_MISC_B_TRACKING_UI_ENABLE BIT(4)
+#define IQS269_MISC_B_FILT_STR_SLIDER GENMASK(1, 0)
+
+#define IQS269_CHx_SETTINGS 0x8C
+
+#define IQS269_CHx_ENG_A_MEAS_CAP_SIZE BIT(15)
+#define IQS269_CHx_ENG_A_RX_GND_INACTIVE BIT(13)
+#define IQS269_CHx_ENG_A_LOCAL_CAP_SIZE BIT(12)
+#define IQS269_CHx_ENG_A_ATI_MODE_MASK GENMASK(9, 8)
+#define IQS269_CHx_ENG_A_ATI_MODE_SHIFT 8
+#define IQS269_CHx_ENG_A_ATI_MODE_MAX 3
+#define IQS269_CHx_ENG_A_INV_LOGIC BIT(7)
+#define IQS269_CHx_ENG_A_PROJ_BIAS_MASK GENMASK(6, 5)
+#define IQS269_CHx_ENG_A_PROJ_BIAS_SHIFT 5
+#define IQS269_CHx_ENG_A_PROJ_BIAS_MAX 3
+#define IQS269_CHx_ENG_A_SENSE_MODE_MASK GENMASK(3, 0)
+#define IQS269_CHx_ENG_A_SENSE_MODE_MAX 15
+
+#define IQS269_CHx_ENG_B_LOCAL_CAP_ENABLE BIT(13)
+#define IQS269_CHx_ENG_B_SENSE_FREQ_MASK GENMASK(10, 9)
+#define IQS269_CHx_ENG_B_SENSE_FREQ_SHIFT 9
+#define IQS269_CHx_ENG_B_SENSE_FREQ_MAX 3
+#define IQS269_CHx_ENG_B_STATIC_ENABLE BIT(8)
+#define IQS269_CHx_ENG_B_ATI_BASE_MASK GENMASK(7, 6)
+#define IQS269_CHx_ENG_B_ATI_BASE_75 0x00
+#define IQS269_CHx_ENG_B_ATI_BASE_100 0x40
+#define IQS269_CHx_ENG_B_ATI_BASE_150 0x80
+#define IQS269_CHx_ENG_B_ATI_BASE_200 0xC0
+#define IQS269_CHx_ENG_B_ATI_TARGET_MASK GENMASK(5, 0)
+#define IQS269_CHx_ENG_B_ATI_TARGET_MAX 2016
+
+#define IQS269_CHx_WEIGHT_MAX 255
+#define IQS269_CHx_THRESH_MAX 255
+#define IQS269_CHx_HYST_DEEP_MASK GENMASK(7, 4)
+#define IQS269_CHx_HYST_DEEP_SHIFT 4
+#define IQS269_CHx_HYST_TOUCH_MASK GENMASK(3, 0)
+#define IQS269_CHx_HYST_MAX 15
+
+#define IQS269_CHx_HALL_INACTIVE 6
+#define IQS269_CHx_HALL_ACTIVE 7
+
+#define IQS269_HALL_PAD_R BIT(0)
+#define IQS269_HALL_PAD_L BIT(1)
+#define IQS269_HALL_PAD_INV BIT(6)
+
+#define IQS269_HALL_UI 0xF5
+#define IQS269_HALL_UI_ENABLE BIT(15)
+
+#define IQS269_MAX_REG 0xFF
+
+#define IQS269_NUM_CH 8
+#define IQS269_NUM_SL 2
+
+#define IQS269_ATI_POLL_SLEEP_US (iqs269->delay_mult * 10000)
+#define IQS269_ATI_POLL_TIMEOUT_US (iqs269->delay_mult * 500000)
+#define IQS269_ATI_STABLE_DELAY_MS (iqs269->delay_mult * 150)
+
+#define IQS269_PWR_MODE_POLL_SLEEP_US IQS269_ATI_POLL_SLEEP_US
+#define IQS269_PWR_MODE_POLL_TIMEOUT_US IQS269_ATI_POLL_TIMEOUT_US
+
+#define iqs269_irq_wait() usleep_range(100, 150)
+
+enum iqs269_local_cap_size {
+ IQS269_LOCAL_CAP_SIZE_0,
+ IQS269_LOCAL_CAP_SIZE_GLOBAL_ONLY,
+ IQS269_LOCAL_CAP_SIZE_GLOBAL_0pF5,
+};
+
+enum iqs269_st_offs {
+ IQS269_ST_OFFS_PROX,
+ IQS269_ST_OFFS_DIR,
+ IQS269_ST_OFFS_TOUCH,
+ IQS269_ST_OFFS_DEEP,
+};
+
+enum iqs269_th_offs {
+ IQS269_TH_OFFS_PROX,
+ IQS269_TH_OFFS_TOUCH,
+ IQS269_TH_OFFS_DEEP,
+};
+
+enum iqs269_event_id {
+ IQS269_EVENT_PROX_DN,
+ IQS269_EVENT_PROX_UP,
+ IQS269_EVENT_TOUCH_DN,
+ IQS269_EVENT_TOUCH_UP,
+ IQS269_EVENT_DEEP_DN,
+ IQS269_EVENT_DEEP_UP,
+};
+
+struct iqs269_switch_desc {
+ unsigned int code;
+ bool enabled;
+};
+
+struct iqs269_event_desc {
+ const char *name;
+ enum iqs269_st_offs st_offs;
+ enum iqs269_th_offs th_offs;
+ bool dir_up;
+ u8 mask;
+};
+
+static const struct iqs269_event_desc iqs269_events[] = {
+ [IQS269_EVENT_PROX_DN] = {
+ .name = "event-prox",
+ .st_offs = IQS269_ST_OFFS_PROX,
+ .th_offs = IQS269_TH_OFFS_PROX,
+ .mask = IQS269_EVENT_MASK_PROX,
+ },
+ [IQS269_EVENT_PROX_UP] = {
+ .name = "event-prox-alt",
+ .st_offs = IQS269_ST_OFFS_PROX,
+ .th_offs = IQS269_TH_OFFS_PROX,
+ .dir_up = true,
+ .mask = IQS269_EVENT_MASK_PROX,
+ },
+ [IQS269_EVENT_TOUCH_DN] = {
+ .name = "event-touch",
+ .st_offs = IQS269_ST_OFFS_TOUCH,
+ .th_offs = IQS269_TH_OFFS_TOUCH,
+ .mask = IQS269_EVENT_MASK_TOUCH,
+ },
+ [IQS269_EVENT_TOUCH_UP] = {
+ .name = "event-touch-alt",
+ .st_offs = IQS269_ST_OFFS_TOUCH,
+ .th_offs = IQS269_TH_OFFS_TOUCH,
+ .dir_up = true,
+ .mask = IQS269_EVENT_MASK_TOUCH,
+ },
+ [IQS269_EVENT_DEEP_DN] = {
+ .name = "event-deep",
+ .st_offs = IQS269_ST_OFFS_DEEP,
+ .th_offs = IQS269_TH_OFFS_DEEP,
+ .mask = IQS269_EVENT_MASK_DEEP,
+ },
+ [IQS269_EVENT_DEEP_UP] = {
+ .name = "event-deep-alt",
+ .st_offs = IQS269_ST_OFFS_DEEP,
+ .th_offs = IQS269_TH_OFFS_DEEP,
+ .dir_up = true,
+ .mask = IQS269_EVENT_MASK_DEEP,
+ },
+};
+
+struct iqs269_ver_info {
+ u8 prod_num;
+ u8 sw_num;
+ u8 hw_num;
+ u8 padding;
+} __packed;
+
+struct iqs269_sys_reg {
+ __be16 general;
+ u8 active;
+ u8 filter;
+ u8 reseed;
+ u8 event_mask;
+ u8 rate_np;
+ u8 rate_lp;
+ u8 rate_ulp;
+ u8 timeout_pwr;
+ u8 timeout_rdy;
+ u8 timeout_lta;
+ __be16 misc_a;
+ __be16 misc_b;
+ u8 blocking;
+ u8 padding;
+ u8 slider_select[IQS269_NUM_SL];
+ u8 timeout_tap;
+ u8 timeout_swipe;
+ u8 thresh_swipe;
+ u8 redo_ati;
+} __packed;
+
+struct iqs269_ch_reg {
+ u8 rx_enable;
+ u8 tx_enable;
+ __be16 engine_a;
+ __be16 engine_b;
+ __be16 ati_comp;
+ u8 thresh[3];
+ u8 hyst;
+ u8 assoc_select;
+ u8 assoc_weight;
+} __packed;
+
+struct iqs269_flags {
+ __be16 system;
+ u8 gesture;
+ u8 padding;
+ u8 states[4];
+} __packed;
+
+struct iqs269_private {
+ struct i2c_client *client;
+ struct regmap *regmap;
+ struct mutex lock;
+ struct iqs269_switch_desc switches[ARRAY_SIZE(iqs269_events)];
+ struct iqs269_ch_reg ch_reg[IQS269_NUM_CH];
+ struct iqs269_sys_reg sys_reg;
+ struct input_dev *keypad;
+ struct input_dev *slider[IQS269_NUM_SL];
+ unsigned int keycode[ARRAY_SIZE(iqs269_events) * IQS269_NUM_CH];
+ unsigned int suspend_mode;
+ unsigned int delay_mult;
+ unsigned int ch_num;
+ bool hall_enable;
+ bool ati_current;
+};
+
+static int iqs269_ati_mode_set(struct iqs269_private *iqs269,
+ unsigned int ch_num, unsigned int mode)
+{
+ u16 engine_a;
+
+ if (ch_num >= IQS269_NUM_CH)
+ return -EINVAL;
+
+ if (mode > IQS269_CHx_ENG_A_ATI_MODE_MAX)
+ return -EINVAL;
+
+ mutex_lock(&iqs269->lock);
+
+ engine_a = be16_to_cpu(iqs269->ch_reg[ch_num].engine_a);
+
+ engine_a &= ~IQS269_CHx_ENG_A_ATI_MODE_MASK;
+ engine_a |= (mode << IQS269_CHx_ENG_A_ATI_MODE_SHIFT);
+
+ iqs269->ch_reg[ch_num].engine_a = cpu_to_be16(engine_a);
+ iqs269->ati_current = false;
+
+ mutex_unlock(&iqs269->lock);
+
+ return 0;
+}
+
+static int iqs269_ati_mode_get(struct iqs269_private *iqs269,
+ unsigned int ch_num, unsigned int *mode)
+{
+ u16 engine_a;
+
+ if (ch_num >= IQS269_NUM_CH)
+ return -EINVAL;
+
+ mutex_lock(&iqs269->lock);
+ engine_a = be16_to_cpu(iqs269->ch_reg[ch_num].engine_a);
+ mutex_unlock(&iqs269->lock);
+
+ engine_a &= IQS269_CHx_ENG_A_ATI_MODE_MASK;
+ *mode = (engine_a >> IQS269_CHx_ENG_A_ATI_MODE_SHIFT);
+
+ return 0;
+}
+
+static int iqs269_ati_base_set(struct iqs269_private *iqs269,
+ unsigned int ch_num, unsigned int base)
+{
+ u16 engine_b;
+
+ if (ch_num >= IQS269_NUM_CH)
+ return -EINVAL;
+
+ switch (base) {
+ case 75:
+ base = IQS269_CHx_ENG_B_ATI_BASE_75;
+ break;
+
+ case 100:
+ base = IQS269_CHx_ENG_B_ATI_BASE_100;
+ break;
+
+ case 150:
+ base = IQS269_CHx_ENG_B_ATI_BASE_150;
+ break;
+
+ case 200:
+ base = IQS269_CHx_ENG_B_ATI_BASE_200;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ mutex_lock(&iqs269->lock);
+
+ engine_b = be16_to_cpu(iqs269->ch_reg[ch_num].engine_b);
+
+ engine_b &= ~IQS269_CHx_ENG_B_ATI_BASE_MASK;
+ engine_b |= base;
+
+ iqs269->ch_reg[ch_num].engine_b = cpu_to_be16(engine_b);
+ iqs269->ati_current = false;
+
+ mutex_unlock(&iqs269->lock);
+
+ return 0;
+}
+
+static int iqs269_ati_base_get(struct iqs269_private *iqs269,
+ unsigned int ch_num, unsigned int *base)
+{
+ u16 engine_b;
+
+ if (ch_num >= IQS269_NUM_CH)
+ return -EINVAL;
+
+ mutex_lock(&iqs269->lock);
+ engine_b = be16_to_cpu(iqs269->ch_reg[ch_num].engine_b);
+ mutex_unlock(&iqs269->lock);
+
+ switch (engine_b & IQS269_CHx_ENG_B_ATI_BASE_MASK) {
+ case IQS269_CHx_ENG_B_ATI_BASE_75:
+ *base = 75;
+ return 0;
+
+ case IQS269_CHx_ENG_B_ATI_BASE_100:
+ *base = 100;
+ return 0;
+
+ case IQS269_CHx_ENG_B_ATI_BASE_150:
+ *base = 150;
+ return 0;
+
+ case IQS269_CHx_ENG_B_ATI_BASE_200:
+ *base = 200;
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int iqs269_ati_target_set(struct iqs269_private *iqs269,
+ unsigned int ch_num, unsigned int target)
+{
+ u16 engine_b;
+
+ if (ch_num >= IQS269_NUM_CH)
+ return -EINVAL;
+
+ if (target > IQS269_CHx_ENG_B_ATI_TARGET_MAX)
+ return -EINVAL;
+
+ mutex_lock(&iqs269->lock);
+
+ engine_b = be16_to_cpu(iqs269->ch_reg[ch_num].engine_b);
+
+ engine_b &= ~IQS269_CHx_ENG_B_ATI_TARGET_MASK;
+ engine_b |= target / 32;
+
+ iqs269->ch_reg[ch_num].engine_b = cpu_to_be16(engine_b);
+ iqs269->ati_current = false;
+
+ mutex_unlock(&iqs269->lock);
+
+ return 0;
+}
+
+static int iqs269_ati_target_get(struct iqs269_private *iqs269,
+ unsigned int ch_num, unsigned int *target)
+{
+ u16 engine_b;
+
+ if (ch_num >= IQS269_NUM_CH)
+ return -EINVAL;
+
+ mutex_lock(&iqs269->lock);
+ engine_b = be16_to_cpu(iqs269->ch_reg[ch_num].engine_b);
+ mutex_unlock(&iqs269->lock);
+
+ *target = (engine_b & IQS269_CHx_ENG_B_ATI_TARGET_MASK) * 32;
+
+ return 0;
+}
+
+static int iqs269_parse_mask(const struct fwnode_handle *fwnode,
+ const char *propname, u8 *mask)
+{
+ unsigned int val[IQS269_NUM_CH];
+ int count, error, i;
+
+ count = fwnode_property_count_u32(fwnode, propname);
+ if (count < 0)
+ return 0;
+
+ if (count > IQS269_NUM_CH)
+ return -EINVAL;
+
+ error = fwnode_property_read_u32_array(fwnode, propname, val, count);
+ if (error)
+ return error;
+
+ *mask = 0;
+
+ for (i = 0; i < count; i++) {
+ if (val[i] >= IQS269_NUM_CH)
+ return -EINVAL;
+
+ *mask |= BIT(val[i]);
+ }
+
+ return 0;
+}
+
+static int iqs269_parse_chan(struct iqs269_private *iqs269,
+ const struct fwnode_handle *ch_node)
+{
+ struct i2c_client *client = iqs269->client;
+ struct fwnode_handle *ev_node;
+ struct iqs269_ch_reg *ch_reg;
+ u16 engine_a, engine_b;
+ unsigned int reg, val;
+ int error, i;
+
+ error = fwnode_property_read_u32(ch_node, "reg", &reg);
+ if (error) {
+ dev_err(&client->dev, "Failed to read channel number: %d\n",
+ error);
+ return error;
+ } else if (reg >= IQS269_NUM_CH) {
+ dev_err(&client->dev, "Invalid channel number: %u\n", reg);
+ return -EINVAL;
+ }
+
+ iqs269->sys_reg.active |= BIT(reg);
+ if (!fwnode_property_present(ch_node, "azoteq,reseed-disable"))
+ iqs269->sys_reg.reseed |= BIT(reg);
+
+ if (fwnode_property_present(ch_node, "azoteq,blocking-enable"))
+ iqs269->sys_reg.blocking |= BIT(reg);
+
+ if (fwnode_property_present(ch_node, "azoteq,slider0-select"))
+ iqs269->sys_reg.slider_select[0] |= BIT(reg);
+
+ if (fwnode_property_present(ch_node, "azoteq,slider1-select"))
+ iqs269->sys_reg.slider_select[1] |= BIT(reg);
+
+ ch_reg = &iqs269->ch_reg[reg];
+
+ error = regmap_raw_read(iqs269->regmap,
+ IQS269_CHx_SETTINGS + reg * sizeof(*ch_reg) / 2,
+ ch_reg, sizeof(*ch_reg));
+ if (error)
+ return error;
+
+ error = iqs269_parse_mask(ch_node, "azoteq,rx-enable",
+ &ch_reg->rx_enable);
+ if (error) {
+ dev_err(&client->dev, "Invalid channel %u RX enable mask: %d\n",
+ reg, error);
+ return error;
+ }
+
+ error = iqs269_parse_mask(ch_node, "azoteq,tx-enable",
+ &ch_reg->tx_enable);
+ if (error) {
+ dev_err(&client->dev, "Invalid channel %u TX enable mask: %d\n",
+ reg, error);
+ return error;
+ }
+
+ engine_a = be16_to_cpu(ch_reg->engine_a);
+ engine_b = be16_to_cpu(ch_reg->engine_b);
+
+ engine_a |= IQS269_CHx_ENG_A_MEAS_CAP_SIZE;
+ if (fwnode_property_present(ch_node, "azoteq,meas-cap-decrease"))
+ engine_a &= ~IQS269_CHx_ENG_A_MEAS_CAP_SIZE;
+
+ engine_a |= IQS269_CHx_ENG_A_RX_GND_INACTIVE;
+ if (fwnode_property_present(ch_node, "azoteq,rx-float-inactive"))
+ engine_a &= ~IQS269_CHx_ENG_A_RX_GND_INACTIVE;
+
+ engine_a &= ~IQS269_CHx_ENG_A_LOCAL_CAP_SIZE;
+ engine_b &= ~IQS269_CHx_ENG_B_LOCAL_CAP_ENABLE;
+ if (!fwnode_property_read_u32(ch_node, "azoteq,local-cap-size", &val)) {
+ switch (val) {
+ case IQS269_LOCAL_CAP_SIZE_0:
+ break;
+
+ case IQS269_LOCAL_CAP_SIZE_GLOBAL_0pF5:
+ engine_a |= IQS269_CHx_ENG_A_LOCAL_CAP_SIZE;
+ fallthrough;
+
+ case IQS269_LOCAL_CAP_SIZE_GLOBAL_ONLY:
+ engine_b |= IQS269_CHx_ENG_B_LOCAL_CAP_ENABLE;
+ break;
+
+ default:
+ dev_err(&client->dev,
+ "Invalid channel %u local cap. size: %u\n", reg,
+ val);
+ return -EINVAL;
+ }
+ }
+
+ engine_a &= ~IQS269_CHx_ENG_A_INV_LOGIC;
+ if (fwnode_property_present(ch_node, "azoteq,invert-enable"))
+ engine_a |= IQS269_CHx_ENG_A_INV_LOGIC;
+
+ if (!fwnode_property_read_u32(ch_node, "azoteq,proj-bias", &val)) {
+ if (val > IQS269_CHx_ENG_A_PROJ_BIAS_MAX) {
+ dev_err(&client->dev,
+ "Invalid channel %u bias current: %u\n", reg,
+ val);
+ return -EINVAL;
+ }
+
+ engine_a &= ~IQS269_CHx_ENG_A_PROJ_BIAS_MASK;
+ engine_a |= (val << IQS269_CHx_ENG_A_PROJ_BIAS_SHIFT);
+ }
+
+ if (!fwnode_property_read_u32(ch_node, "azoteq,sense-mode", &val)) {
+ if (val > IQS269_CHx_ENG_A_SENSE_MODE_MAX) {
+ dev_err(&client->dev,
+ "Invalid channel %u sensing mode: %u\n", reg,
+ val);
+ return -EINVAL;
+ }
+
+ engine_a &= ~IQS269_CHx_ENG_A_SENSE_MODE_MASK;
+ engine_a |= val;
+ }
+
+ if (!fwnode_property_read_u32(ch_node, "azoteq,sense-freq", &val)) {
+ if (val > IQS269_CHx_ENG_B_SENSE_FREQ_MAX) {
+ dev_err(&client->dev,
+ "Invalid channel %u sensing frequency: %u\n",
+ reg, val);
+ return -EINVAL;
+ }
+
+ engine_b &= ~IQS269_CHx_ENG_B_SENSE_FREQ_MASK;
+ engine_b |= (val << IQS269_CHx_ENG_B_SENSE_FREQ_SHIFT);
+ }
+
+ engine_b &= ~IQS269_CHx_ENG_B_STATIC_ENABLE;
+ if (fwnode_property_present(ch_node, "azoteq,static-enable"))
+ engine_b |= IQS269_CHx_ENG_B_STATIC_ENABLE;
+
+ ch_reg->engine_a = cpu_to_be16(engine_a);
+ ch_reg->engine_b = cpu_to_be16(engine_b);
+
+ if (!fwnode_property_read_u32(ch_node, "azoteq,ati-mode", &val)) {
+ error = iqs269_ati_mode_set(iqs269, reg, val);
+ if (error) {
+ dev_err(&client->dev,
+ "Invalid channel %u ATI mode: %u\n", reg, val);
+ return error;
+ }
+ }
+
+ if (!fwnode_property_read_u32(ch_node, "azoteq,ati-base", &val)) {
+ error = iqs269_ati_base_set(iqs269, reg, val);
+ if (error) {
+ dev_err(&client->dev,
+ "Invalid channel %u ATI base: %u\n", reg, val);
+ return error;
+ }
+ }
+
+ if (!fwnode_property_read_u32(ch_node, "azoteq,ati-target", &val)) {
+ error = iqs269_ati_target_set(iqs269, reg, val);
+ if (error) {
+ dev_err(&client->dev,
+ "Invalid channel %u ATI target: %u\n", reg,
+ val);
+ return error;
+ }
+ }
+
+ error = iqs269_parse_mask(ch_node, "azoteq,assoc-select",
+ &ch_reg->assoc_select);
+ if (error) {
+ dev_err(&client->dev, "Invalid channel %u association: %d\n",
+ reg, error);
+ return error;
+ }
+
+ if (!fwnode_property_read_u32(ch_node, "azoteq,assoc-weight", &val)) {
+ if (val > IQS269_CHx_WEIGHT_MAX) {
+ dev_err(&client->dev,
+ "Invalid channel %u associated weight: %u\n",
+ reg, val);
+ return -EINVAL;
+ }
+
+ ch_reg->assoc_weight = val;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(iqs269_events); i++) {
+ ev_node = fwnode_get_named_child_node(ch_node,
+ iqs269_events[i].name);
+ if (!ev_node)
+ continue;
+
+ if (!fwnode_property_read_u32(ev_node, "azoteq,thresh", &val)) {
+ if (val > IQS269_CHx_THRESH_MAX) {
+ dev_err(&client->dev,
+ "Invalid channel %u threshold: %u\n",
+ reg, val);
+ return -EINVAL;
+ }
+
+ ch_reg->thresh[iqs269_events[i].th_offs] = val;
+ }
+
+ if (!fwnode_property_read_u32(ev_node, "azoteq,hyst", &val)) {
+ u8 *hyst = &ch_reg->hyst;
+
+ if (val > IQS269_CHx_HYST_MAX) {
+ dev_err(&client->dev,
+ "Invalid channel %u hysteresis: %u\n",
+ reg, val);
+ return -EINVAL;
+ }
+
+ if (i == IQS269_EVENT_DEEP_DN ||
+ i == IQS269_EVENT_DEEP_UP) {
+ *hyst &= ~IQS269_CHx_HYST_DEEP_MASK;
+ *hyst |= (val << IQS269_CHx_HYST_DEEP_SHIFT);
+ } else if (i == IQS269_EVENT_TOUCH_DN ||
+ i == IQS269_EVENT_TOUCH_UP) {
+ *hyst &= ~IQS269_CHx_HYST_TOUCH_MASK;
+ *hyst |= val;
+ }
+ }
+
+ if (fwnode_property_read_u32(ev_node, "linux,code", &val))
+ continue;
+
+ switch (reg) {
+ case IQS269_CHx_HALL_ACTIVE:
+ if (iqs269->hall_enable) {
+ iqs269->switches[i].code = val;
+ iqs269->switches[i].enabled = true;
+ }
+ fallthrough;
+
+ case IQS269_CHx_HALL_INACTIVE:
+ if (iqs269->hall_enable)
+ break;
+ fallthrough;
+
+ default:
+ iqs269->keycode[i * IQS269_NUM_CH + reg] = val;
+ }
+
+ iqs269->sys_reg.event_mask &= ~iqs269_events[i].mask;
+ }
+
+ return 0;
+}
+
+static int iqs269_parse_prop(struct iqs269_private *iqs269)
+{
+ struct iqs269_sys_reg *sys_reg = &iqs269->sys_reg;
+ struct i2c_client *client = iqs269->client;
+ struct fwnode_handle *ch_node;
+ u16 general, misc_a, misc_b;
+ unsigned int val;
+ int error;
+
+ iqs269->hall_enable = device_property_present(&client->dev,
+ "azoteq,hall-enable");
+
+ if (!device_property_read_u32(&client->dev, "azoteq,suspend-mode",
+ &val)) {
+ if (val > IQS269_SYS_SETTINGS_PWR_MODE_MAX) {
+ dev_err(&client->dev, "Invalid suspend mode: %u\n",
+ val);
+ return -EINVAL;
+ }
+
+ iqs269->suspend_mode = val;
+ }
+
+ error = regmap_raw_read(iqs269->regmap, IQS269_SYS_SETTINGS, sys_reg,
+ sizeof(*sys_reg));
+ if (error)
+ return error;
+
+ if (!device_property_read_u32(&client->dev, "azoteq,filt-str-lp-lta",
+ &val)) {
+ if (val > IQS269_FILT_STR_MAX) {
+ dev_err(&client->dev, "Invalid filter strength: %u\n",
+ val);
+ return -EINVAL;
+ }
+
+ sys_reg->filter &= ~IQS269_FILT_STR_LP_LTA_MASK;
+ sys_reg->filter |= (val << IQS269_FILT_STR_LP_LTA_SHIFT);
+ }
+
+ if (!device_property_read_u32(&client->dev, "azoteq,filt-str-lp-cnt",
+ &val)) {
+ if (val > IQS269_FILT_STR_MAX) {
+ dev_err(&client->dev, "Invalid filter strength: %u\n",
+ val);
+ return -EINVAL;
+ }
+
+ sys_reg->filter &= ~IQS269_FILT_STR_LP_CNT_MASK;
+ sys_reg->filter |= (val << IQS269_FILT_STR_LP_CNT_SHIFT);
+ }
+
+ if (!device_property_read_u32(&client->dev, "azoteq,filt-str-np-lta",
+ &val)) {
+ if (val > IQS269_FILT_STR_MAX) {
+ dev_err(&client->dev, "Invalid filter strength: %u\n",
+ val);
+ return -EINVAL;
+ }
+
+ sys_reg->filter &= ~IQS269_FILT_STR_NP_LTA_MASK;
+ sys_reg->filter |= (val << IQS269_FILT_STR_NP_LTA_SHIFT);
+ }
+
+ if (!device_property_read_u32(&client->dev, "azoteq,filt-str-np-cnt",
+ &val)) {
+ if (val > IQS269_FILT_STR_MAX) {
+ dev_err(&client->dev, "Invalid filter strength: %u\n",
+ val);
+ return -EINVAL;
+ }
+
+ sys_reg->filter &= ~IQS269_FILT_STR_NP_CNT_MASK;
+ sys_reg->filter |= val;
+ }
+
+ if (!device_property_read_u32(&client->dev, "azoteq,rate-np-ms",
+ &val)) {
+ if (val > IQS269_RATE_NP_MS_MAX) {
+ dev_err(&client->dev, "Invalid report rate: %u\n", val);
+ return -EINVAL;
+ }
+
+ sys_reg->rate_np = val;
+ }
+
+ if (!device_property_read_u32(&client->dev, "azoteq,rate-lp-ms",
+ &val)) {
+ if (val > IQS269_RATE_LP_MS_MAX) {
+ dev_err(&client->dev, "Invalid report rate: %u\n", val);
+ return -EINVAL;
+ }
+
+ sys_reg->rate_lp = val;
+ }
+
+ if (!device_property_read_u32(&client->dev, "azoteq,rate-ulp-ms",
+ &val)) {
+ if (val > IQS269_RATE_ULP_MS_MAX) {
+ dev_err(&client->dev, "Invalid report rate: %u\n", val);
+ return -EINVAL;
+ }
+
+ sys_reg->rate_ulp = val / 16;
+ }
+
+ if (!device_property_read_u32(&client->dev, "azoteq,timeout-pwr-ms",
+ &val)) {
+ if (val > IQS269_TIMEOUT_PWR_MS_MAX) {
+ dev_err(&client->dev, "Invalid timeout: %u\n", val);
+ return -EINVAL;
+ }
+
+ sys_reg->timeout_pwr = val / 512;
+ }
+
+ if (!device_property_read_u32(&client->dev, "azoteq,timeout-lta-ms",
+ &val)) {
+ if (val > IQS269_TIMEOUT_LTA_MS_MAX) {
+ dev_err(&client->dev, "Invalid timeout: %u\n", val);
+ return -EINVAL;
+ }
+
+ sys_reg->timeout_lta = val / 512;
+ }
+
+ misc_a = be16_to_cpu(sys_reg->misc_a);
+ misc_b = be16_to_cpu(sys_reg->misc_b);
+
+ misc_a &= ~IQS269_MISC_A_ATI_BAND_DISABLE;
+ if (device_property_present(&client->dev, "azoteq,ati-band-disable"))
+ misc_a |= IQS269_MISC_A_ATI_BAND_DISABLE;
+
+ misc_a &= ~IQS269_MISC_A_ATI_LP_ONLY;
+ if (device_property_present(&client->dev, "azoteq,ati-lp-only"))
+ misc_a |= IQS269_MISC_A_ATI_LP_ONLY;
+
+ misc_a &= ~IQS269_MISC_A_ATI_BAND_TIGHTEN;
+ if (device_property_present(&client->dev, "azoteq,ati-band-tighten"))
+ misc_a |= IQS269_MISC_A_ATI_BAND_TIGHTEN;
+
+ misc_a &= ~IQS269_MISC_A_FILT_DISABLE;
+ if (device_property_present(&client->dev, "azoteq,filt-disable"))
+ misc_a |= IQS269_MISC_A_FILT_DISABLE;
+
+ if (!device_property_read_u32(&client->dev, "azoteq,gpio3-select",
+ &val)) {
+ if (val >= IQS269_NUM_CH) {
+ dev_err(&client->dev, "Invalid GPIO3 selection: %u\n",
+ val);
+ return -EINVAL;
+ }
+
+ misc_a &= ~IQS269_MISC_A_GPIO3_SELECT_MASK;
+ misc_a |= (val << IQS269_MISC_A_GPIO3_SELECT_SHIFT);
+ }
+
+ misc_a &= ~IQS269_MISC_A_DUAL_DIR;
+ if (device_property_present(&client->dev, "azoteq,dual-direction"))
+ misc_a |= IQS269_MISC_A_DUAL_DIR;
+
+ if (!device_property_read_u32(&client->dev, "azoteq,tx-freq", &val)) {
+ if (val > IQS269_MISC_A_TX_FREQ_MAX) {
+ dev_err(&client->dev,
+ "Invalid excitation frequency: %u\n", val);
+ return -EINVAL;
+ }
+
+ misc_a &= ~IQS269_MISC_A_TX_FREQ_MASK;
+ misc_a |= (val << IQS269_MISC_A_TX_FREQ_SHIFT);
+ }
+
+ misc_a &= ~IQS269_MISC_A_GLOBAL_CAP_SIZE;
+ if (device_property_present(&client->dev, "azoteq,global-cap-increase"))
+ misc_a |= IQS269_MISC_A_GLOBAL_CAP_SIZE;
+
+ if (!device_property_read_u32(&client->dev, "azoteq,reseed-select",
+ &val)) {
+ if (val > IQS269_MISC_B_RESEED_UI_SEL_MAX) {
+ dev_err(&client->dev, "Invalid reseed selection: %u\n",
+ val);
+ return -EINVAL;
+ }
+
+ misc_b &= ~IQS269_MISC_B_RESEED_UI_SEL_MASK;
+ misc_b |= (val << IQS269_MISC_B_RESEED_UI_SEL_SHIFT);
+ }
+
+ misc_b &= ~IQS269_MISC_B_TRACKING_UI_ENABLE;
+ if (device_property_present(&client->dev, "azoteq,tracking-enable"))
+ misc_b |= IQS269_MISC_B_TRACKING_UI_ENABLE;
+
+ if (!device_property_read_u32(&client->dev, "azoteq,filt-str-slider",
+ &val)) {
+ if (val > IQS269_FILT_STR_MAX) {
+ dev_err(&client->dev, "Invalid filter strength: %u\n",
+ val);
+ return -EINVAL;
+ }
+
+ misc_b &= ~IQS269_MISC_B_FILT_STR_SLIDER;
+ misc_b |= val;
+ }
+
+ sys_reg->misc_a = cpu_to_be16(misc_a);
+ sys_reg->misc_b = cpu_to_be16(misc_b);
+
+ sys_reg->active = 0;
+ sys_reg->reseed = 0;
+
+ sys_reg->blocking = 0;
+
+ sys_reg->slider_select[0] = 0;
+ sys_reg->slider_select[1] = 0;
+
+ sys_reg->event_mask = ~((u8)IQS269_EVENT_MASK_SYS);
+
+ device_for_each_child_node(&client->dev, ch_node) {
+ error = iqs269_parse_chan(iqs269, ch_node);
+ if (error) {
+ fwnode_handle_put(ch_node);
+ return error;
+ }
+ }
+
+ /*
+ * Volunteer all active channels to participate in ATI when REDO-ATI is
+ * manually triggered.
+ */
+ sys_reg->redo_ati = sys_reg->active;
+
+ general = be16_to_cpu(sys_reg->general);
+
+ if (device_property_present(&client->dev, "azoteq,clk-div")) {
+ general |= IQS269_SYS_SETTINGS_CLK_DIV;
+ iqs269->delay_mult = 4;
+ } else {
+ general &= ~IQS269_SYS_SETTINGS_CLK_DIV;
+ iqs269->delay_mult = 1;
+ }
+
+ /*
+ * Configure the device to automatically switch between normal and low-
+ * power modes as a function of sensing activity. Ultra-low-power mode,
+ * if enabled, is reserved for suspend.
+ */
+ general &= ~IQS269_SYS_SETTINGS_ULP_AUTO;
+ general &= ~IQS269_SYS_SETTINGS_DIS_AUTO;
+ general &= ~IQS269_SYS_SETTINGS_PWR_MODE_MASK;
+
+ if (!device_property_read_u32(&client->dev, "azoteq,ulp-update",
+ &val)) {
+ if (val > IQS269_SYS_SETTINGS_ULP_UPDATE_MAX) {
+ dev_err(&client->dev, "Invalid update rate: %u\n", val);
+ return -EINVAL;
+ }
+
+ general &= ~IQS269_SYS_SETTINGS_ULP_UPDATE_MASK;
+ general |= (val << IQS269_SYS_SETTINGS_ULP_UPDATE_SHIFT);
+ }
+
+ general &= ~IQS269_SYS_SETTINGS_RESEED_OFFSET;
+ if (device_property_present(&client->dev, "azoteq,reseed-offset"))
+ general |= IQS269_SYS_SETTINGS_RESEED_OFFSET;
+
+ general |= IQS269_SYS_SETTINGS_EVENT_MODE;
+
+ /*
+ * As per the datasheet, enable streaming during normal-power mode if
+ * either slider is in use. In that case, the device returns to event
+ * mode during low-power mode.
+ */
+ if (sys_reg->slider_select[0] || sys_reg->slider_select[1])
+ general |= IQS269_SYS_SETTINGS_EVENT_MODE_LP;
+
+ general |= IQS269_SYS_SETTINGS_REDO_ATI;
+ general |= IQS269_SYS_SETTINGS_ACK_RESET;
+
+ sys_reg->general = cpu_to_be16(general);
+
+ return 0;
+}
+
+static int iqs269_dev_init(struct iqs269_private *iqs269)
+{
+ struct iqs269_sys_reg *sys_reg = &iqs269->sys_reg;
+ struct iqs269_ch_reg *ch_reg;
+ unsigned int val;
+ int error, i;
+
+ mutex_lock(&iqs269->lock);
+
+ error = regmap_update_bits(iqs269->regmap, IQS269_HALL_UI,
+ IQS269_HALL_UI_ENABLE,
+ iqs269->hall_enable ? ~0 : 0);
+ if (error)
+ goto err_mutex;
+
+ for (i = 0; i < IQS269_NUM_CH; i++) {
+ if (!(sys_reg->active & BIT(i)))
+ continue;
+
+ ch_reg = &iqs269->ch_reg[i];
+
+ error = regmap_raw_write(iqs269->regmap,
+ IQS269_CHx_SETTINGS + i *
+ sizeof(*ch_reg) / 2, ch_reg,
+ sizeof(*ch_reg));
+ if (error)
+ goto err_mutex;
+ }
+
+ /*
+ * The REDO-ATI and ATI channel selection fields must be written in the
+ * same block write, so every field between registers 0x80 through 0x8B
+ * (inclusive) must be written as well.
+ */
+ error = regmap_raw_write(iqs269->regmap, IQS269_SYS_SETTINGS, sys_reg,
+ sizeof(*sys_reg));
+ if (error)
+ goto err_mutex;
+
+ error = regmap_read_poll_timeout(iqs269->regmap, IQS269_SYS_FLAGS, val,
+ !(val & IQS269_SYS_FLAGS_IN_ATI),
+ IQS269_ATI_POLL_SLEEP_US,
+ IQS269_ATI_POLL_TIMEOUT_US);
+ if (error)
+ goto err_mutex;
+
+ msleep(IQS269_ATI_STABLE_DELAY_MS);
+ iqs269->ati_current = true;
+
+err_mutex:
+ mutex_unlock(&iqs269->lock);
+
+ return error;
+}
+
+static int iqs269_input_init(struct iqs269_private *iqs269)
+{
+ struct i2c_client *client = iqs269->client;
+ struct iqs269_flags flags;
+ unsigned int sw_code, keycode;
+ int error, i, j;
+ u8 dir_mask, state;
+
+ iqs269->keypad = devm_input_allocate_device(&client->dev);
+ if (!iqs269->keypad)
+ return -ENOMEM;
+
+ iqs269->keypad->keycodemax = ARRAY_SIZE(iqs269->keycode);
+ iqs269->keypad->keycode = iqs269->keycode;
+ iqs269->keypad->keycodesize = sizeof(*iqs269->keycode);
+
+ iqs269->keypad->name = "iqs269a_keypad";
+ iqs269->keypad->id.bustype = BUS_I2C;
+
+ if (iqs269->hall_enable) {
+ error = regmap_raw_read(iqs269->regmap, IQS269_SYS_FLAGS,
+ &flags, sizeof(flags));
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to read initial status: %d\n", error);
+ return error;
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(iqs269_events); i++) {
+ dir_mask = flags.states[IQS269_ST_OFFS_DIR];
+ if (!iqs269_events[i].dir_up)
+ dir_mask = ~dir_mask;
+
+ state = flags.states[iqs269_events[i].st_offs] & dir_mask;
+
+ sw_code = iqs269->switches[i].code;
+
+ for (j = 0; j < IQS269_NUM_CH; j++) {
+ keycode = iqs269->keycode[i * IQS269_NUM_CH + j];
+
+ /*
+ * Hall-effect sensing repurposes a pair of dedicated
+ * channels, only one of which reports events.
+ */
+ switch (j) {
+ case IQS269_CHx_HALL_ACTIVE:
+ if (iqs269->hall_enable &&
+ iqs269->switches[i].enabled) {
+ input_set_capability(iqs269->keypad,
+ EV_SW, sw_code);
+ input_report_switch(iqs269->keypad,
+ sw_code,
+ state & BIT(j));
+ }
+ fallthrough;
+
+ case IQS269_CHx_HALL_INACTIVE:
+ if (iqs269->hall_enable)
+ continue;
+ fallthrough;
+
+ default:
+ if (keycode != KEY_RESERVED)
+ input_set_capability(iqs269->keypad,
+ EV_KEY, keycode);
+ }
+ }
+ }
+
+ input_sync(iqs269->keypad);
+
+ error = input_register_device(iqs269->keypad);
+ if (error) {
+ dev_err(&client->dev, "Failed to register keypad: %d\n", error);
+ return error;
+ }
+
+ for (i = 0; i < IQS269_NUM_SL; i++) {
+ if (!iqs269->sys_reg.slider_select[i])
+ continue;
+
+ iqs269->slider[i] = devm_input_allocate_device(&client->dev);
+ if (!iqs269->slider[i])
+ return -ENOMEM;
+
+ iqs269->slider[i]->name = i ? "iqs269a_slider_1"
+ : "iqs269a_slider_0";
+ iqs269->slider[i]->id.bustype = BUS_I2C;
+
+ input_set_capability(iqs269->slider[i], EV_KEY, BTN_TOUCH);
+ input_set_abs_params(iqs269->slider[i], ABS_X, 0, 255, 0, 0);
+
+ error = input_register_device(iqs269->slider[i]);
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to register slider %d: %d\n", i, error);
+ return error;
+ }
+ }
+
+ return 0;
+}
+
+static int iqs269_report(struct iqs269_private *iqs269)
+{
+ struct i2c_client *client = iqs269->client;
+ struct iqs269_flags flags;
+ unsigned int sw_code, keycode;
+ int error, i, j;
+ u8 slider_x[IQS269_NUM_SL];
+ u8 dir_mask, state;
+
+ error = regmap_raw_read(iqs269->regmap, IQS269_SYS_FLAGS, &flags,
+ sizeof(flags));
+ if (error) {
+ dev_err(&client->dev, "Failed to read device status: %d\n",
+ error);
+ return error;
+ }
+
+ /*
+ * The device resets itself if its own watchdog bites, which can happen
+ * in the event of an I2C communication error. In this case, the device
+ * asserts a SHOW_RESET interrupt and all registers must be restored.
+ */
+ if (be16_to_cpu(flags.system) & IQS269_SYS_FLAGS_SHOW_RESET) {
+ dev_err(&client->dev, "Unexpected device reset\n");
+
+ error = iqs269_dev_init(iqs269);
+ if (error)
+ dev_err(&client->dev,
+ "Failed to re-initialize device: %d\n", error);
+
+ return error;
+ }
+
+ error = regmap_raw_read(iqs269->regmap, IQS269_SLIDER_X, slider_x,
+ sizeof(slider_x));
+ if (error) {
+ dev_err(&client->dev, "Failed to read slider position: %d\n",
+ error);
+ return error;
+ }
+
+ for (i = 0; i < IQS269_NUM_SL; i++) {
+ if (!iqs269->sys_reg.slider_select[i])
+ continue;
+
+ /*
+ * Report BTN_TOUCH if any channel that participates in the
+ * slider is in a state of touch.
+ */
+ if (flags.states[IQS269_ST_OFFS_TOUCH] &
+ iqs269->sys_reg.slider_select[i]) {
+ input_report_key(iqs269->slider[i], BTN_TOUCH, 1);
+ input_report_abs(iqs269->slider[i], ABS_X, slider_x[i]);
+ } else {
+ input_report_key(iqs269->slider[i], BTN_TOUCH, 0);
+ }
+
+ input_sync(iqs269->slider[i]);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(iqs269_events); i++) {
+ dir_mask = flags.states[IQS269_ST_OFFS_DIR];
+ if (!iqs269_events[i].dir_up)
+ dir_mask = ~dir_mask;
+
+ state = flags.states[iqs269_events[i].st_offs] & dir_mask;
+
+ sw_code = iqs269->switches[i].code;
+
+ for (j = 0; j < IQS269_NUM_CH; j++) {
+ keycode = iqs269->keycode[i * IQS269_NUM_CH + j];
+
+ switch (j) {
+ case IQS269_CHx_HALL_ACTIVE:
+ if (iqs269->hall_enable &&
+ iqs269->switches[i].enabled)
+ input_report_switch(iqs269->keypad,
+ sw_code,
+ state & BIT(j));
+ fallthrough;
+
+ case IQS269_CHx_HALL_INACTIVE:
+ if (iqs269->hall_enable)
+ continue;
+ fallthrough;
+
+ default:
+ input_report_key(iqs269->keypad, keycode,
+ state & BIT(j));
+ }
+ }
+ }
+
+ input_sync(iqs269->keypad);
+
+ return 0;
+}
+
+static irqreturn_t iqs269_irq(int irq, void *context)
+{
+ struct iqs269_private *iqs269 = context;
+
+ if (iqs269_report(iqs269))
+ return IRQ_NONE;
+
+ /*
+ * The device does not deassert its interrupt (RDY) pin until shortly
+ * after receiving an I2C stop condition; the following delay ensures
+ * the interrupt handler does not return before this time.
+ */
+ iqs269_irq_wait();
+
+ return IRQ_HANDLED;
+}
+
+static ssize_t counts_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct iqs269_private *iqs269 = dev_get_drvdata(dev);
+ struct i2c_client *client = iqs269->client;
+ __le16 counts;
+ int error;
+
+ if (!iqs269->ati_current || iqs269->hall_enable)
+ return -EPERM;
+
+ /*
+ * Unsolicited I2C communication prompts the device to assert its RDY
+ * pin, so disable the interrupt line until the operation is finished
+ * and RDY has been deasserted.
+ */
+ disable_irq(client->irq);
+
+ error = regmap_raw_read(iqs269->regmap,
+ IQS269_CHx_COUNTS + iqs269->ch_num * 2,
+ &counts, sizeof(counts));
+
+ iqs269_irq_wait();
+ enable_irq(client->irq);
+
+ if (error)
+ return error;
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", le16_to_cpu(counts));
+}
+
+static ssize_t hall_bin_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct iqs269_private *iqs269 = dev_get_drvdata(dev);
+ struct i2c_client *client = iqs269->client;
+ unsigned int val;
+ int error;
+
+ disable_irq(client->irq);
+
+ error = regmap_read(iqs269->regmap, IQS269_CAL_DATA_A, &val);
+
+ iqs269_irq_wait();
+ enable_irq(client->irq);
+
+ if (error)
+ return error;
+
+ switch (iqs269->ch_reg[IQS269_CHx_HALL_ACTIVE].rx_enable &
+ iqs269->ch_reg[IQS269_CHx_HALL_INACTIVE].rx_enable) {
+ case IQS269_HALL_PAD_R:
+ val &= IQS269_CAL_DATA_A_HALL_BIN_R_MASK;
+ val >>= IQS269_CAL_DATA_A_HALL_BIN_R_SHIFT;
+ break;
+
+ case IQS269_HALL_PAD_L:
+ val &= IQS269_CAL_DATA_A_HALL_BIN_L_MASK;
+ val >>= IQS269_CAL_DATA_A_HALL_BIN_L_SHIFT;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t hall_enable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct iqs269_private *iqs269 = dev_get_drvdata(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", iqs269->hall_enable);
+}
+
+static ssize_t hall_enable_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ struct iqs269_private *iqs269 = dev_get_drvdata(dev);
+ unsigned int val;
+ int error;
+
+ error = kstrtouint(buf, 10, &val);
+ if (error)
+ return error;
+
+ mutex_lock(&iqs269->lock);
+
+ iqs269->hall_enable = val;
+ iqs269->ati_current = false;
+
+ mutex_unlock(&iqs269->lock);
+
+ return count;
+}
+
+static ssize_t ch_number_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct iqs269_private *iqs269 = dev_get_drvdata(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", iqs269->ch_num);
+}
+
+static ssize_t ch_number_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ struct iqs269_private *iqs269 = dev_get_drvdata(dev);
+ unsigned int val;
+ int error;
+
+ error = kstrtouint(buf, 10, &val);
+ if (error)
+ return error;
+
+ if (val >= IQS269_NUM_CH)
+ return -EINVAL;
+
+ iqs269->ch_num = val;
+
+ return count;
+}
+
+static ssize_t rx_enable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct iqs269_private *iqs269 = dev_get_drvdata(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n",
+ iqs269->ch_reg[iqs269->ch_num].rx_enable);
+}
+
+static ssize_t rx_enable_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ struct iqs269_private *iqs269 = dev_get_drvdata(dev);
+ unsigned int val;
+ int error;
+
+ error = kstrtouint(buf, 10, &val);
+ if (error)
+ return error;
+
+ if (val > 0xFF)
+ return -EINVAL;
+
+ mutex_lock(&iqs269->lock);
+
+ iqs269->ch_reg[iqs269->ch_num].rx_enable = val;
+ iqs269->ati_current = false;
+
+ mutex_unlock(&iqs269->lock);
+
+ return count;
+}
+
+static ssize_t ati_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct iqs269_private *iqs269 = dev_get_drvdata(dev);
+ unsigned int val;
+ int error;
+
+ error = iqs269_ati_mode_get(iqs269, iqs269->ch_num, &val);
+ if (error)
+ return error;
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t ati_mode_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ struct iqs269_private *iqs269 = dev_get_drvdata(dev);
+ unsigned int val;
+ int error;
+
+ error = kstrtouint(buf, 10, &val);
+ if (error)
+ return error;
+
+ error = iqs269_ati_mode_set(iqs269, iqs269->ch_num, val);
+ if (error)
+ return error;
+
+ return count;
+}
+
+static ssize_t ati_base_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct iqs269_private *iqs269 = dev_get_drvdata(dev);
+ unsigned int val;
+ int error;
+
+ error = iqs269_ati_base_get(iqs269, iqs269->ch_num, &val);
+ if (error)
+ return error;
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t ati_base_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ struct iqs269_private *iqs269 = dev_get_drvdata(dev);
+ unsigned int val;
+ int error;
+
+ error = kstrtouint(buf, 10, &val);
+ if (error)
+ return error;
+
+ error = iqs269_ati_base_set(iqs269, iqs269->ch_num, val);
+ if (error)
+ return error;
+
+ return count;
+}
+
+static ssize_t ati_target_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct iqs269_private *iqs269 = dev_get_drvdata(dev);
+ unsigned int val;
+ int error;
+
+ error = iqs269_ati_target_get(iqs269, iqs269->ch_num, &val);
+ if (error)
+ return error;
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t ati_target_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ struct iqs269_private *iqs269 = dev_get_drvdata(dev);
+ unsigned int val;
+ int error;
+
+ error = kstrtouint(buf, 10, &val);
+ if (error)
+ return error;
+
+ error = iqs269_ati_target_set(iqs269, iqs269->ch_num, val);
+ if (error)
+ return error;
+
+ return count;
+}
+
+static ssize_t ati_trigger_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct iqs269_private *iqs269 = dev_get_drvdata(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", iqs269->ati_current);
+}
+
+static ssize_t ati_trigger_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ struct iqs269_private *iqs269 = dev_get_drvdata(dev);
+ struct i2c_client *client = iqs269->client;
+ unsigned int val;
+ int error;
+
+ error = kstrtouint(buf, 10, &val);
+ if (error)
+ return error;
+
+ if (!val)
+ return count;
+
+ disable_irq(client->irq);
+
+ error = iqs269_dev_init(iqs269);
+
+ iqs269_irq_wait();
+ enable_irq(client->irq);
+
+ if (error)
+ return error;
+
+ return count;
+}
+
+static DEVICE_ATTR_RO(counts);
+static DEVICE_ATTR_RO(hall_bin);
+static DEVICE_ATTR_RW(hall_enable);
+static DEVICE_ATTR_RW(ch_number);
+static DEVICE_ATTR_RW(rx_enable);
+static DEVICE_ATTR_RW(ati_mode);
+static DEVICE_ATTR_RW(ati_base);
+static DEVICE_ATTR_RW(ati_target);
+static DEVICE_ATTR_RW(ati_trigger);
+
+static struct attribute *iqs269_attrs[] = {
+ &dev_attr_counts.attr,
+ &dev_attr_hall_bin.attr,
+ &dev_attr_hall_enable.attr,
+ &dev_attr_ch_number.attr,
+ &dev_attr_rx_enable.attr,
+ &dev_attr_ati_mode.attr,
+ &dev_attr_ati_base.attr,
+ &dev_attr_ati_target.attr,
+ &dev_attr_ati_trigger.attr,
+ NULL,
+};
+
+static const struct attribute_group iqs269_attr_group = {
+ .attrs = iqs269_attrs,
+};
+
+static const struct regmap_config iqs269_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 16,
+ .max_register = IQS269_MAX_REG,
+};
+
+static int iqs269_probe(struct i2c_client *client)
+{
+ struct iqs269_ver_info ver_info;
+ struct iqs269_private *iqs269;
+ int error;
+
+ iqs269 = devm_kzalloc(&client->dev, sizeof(*iqs269), GFP_KERNEL);
+ if (!iqs269)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, iqs269);
+ iqs269->client = client;
+
+ iqs269->regmap = devm_regmap_init_i2c(client, &iqs269_regmap_config);
+ if (IS_ERR(iqs269->regmap)) {
+ error = PTR_ERR(iqs269->regmap);
+ dev_err(&client->dev, "Failed to initialize register map: %d\n",
+ error);
+ return error;
+ }
+
+ mutex_init(&iqs269->lock);
+
+ error = regmap_raw_read(iqs269->regmap, IQS269_VER_INFO, &ver_info,
+ sizeof(ver_info));
+ if (error)
+ return error;
+
+ if (ver_info.prod_num != IQS269_VER_INFO_PROD_NUM) {
+ dev_err(&client->dev, "Unrecognized product number: 0x%02X\n",
+ ver_info.prod_num);
+ return -EINVAL;
+ }
+
+ error = iqs269_parse_prop(iqs269);
+ if (error)
+ return error;
+
+ error = iqs269_dev_init(iqs269);
+ if (error) {
+ dev_err(&client->dev, "Failed to initialize device: %d\n",
+ error);
+ return error;
+ }
+
+ error = iqs269_input_init(iqs269);
+ if (error)
+ return error;
+
+ error = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, iqs269_irq, IRQF_ONESHOT,
+ client->name, iqs269);
+ if (error) {
+ dev_err(&client->dev, "Failed to request IRQ: %d\n", error);
+ return error;
+ }
+
+ error = devm_device_add_group(&client->dev, &iqs269_attr_group);
+ if (error)
+ dev_err(&client->dev, "Failed to add attributes: %d\n", error);
+
+ return error;
+}
+
+static int __maybe_unused iqs269_suspend(struct device *dev)
+{
+ struct iqs269_private *iqs269 = dev_get_drvdata(dev);
+ struct i2c_client *client = iqs269->client;
+ unsigned int val;
+ int error;
+
+ if (!iqs269->suspend_mode)
+ return 0;
+
+ disable_irq(client->irq);
+
+ /*
+ * Automatic power mode switching must be disabled before the device is
+ * forced into any particular power mode. In this case, the device will
+ * transition into normal-power mode.
+ */
+ error = regmap_update_bits(iqs269->regmap, IQS269_SYS_SETTINGS,
+ IQS269_SYS_SETTINGS_DIS_AUTO, ~0);
+ if (error)
+ goto err_irq;
+
+ /*
+ * The following check ensures the device has completed its transition
+ * into normal-power mode before a manual mode switch is performed.
+ */
+ error = regmap_read_poll_timeout(iqs269->regmap, IQS269_SYS_FLAGS, val,
+ !(val & IQS269_SYS_FLAGS_PWR_MODE_MASK),
+ IQS269_PWR_MODE_POLL_SLEEP_US,
+ IQS269_PWR_MODE_POLL_TIMEOUT_US);
+ if (error)
+ goto err_irq;
+
+ error = regmap_update_bits(iqs269->regmap, IQS269_SYS_SETTINGS,
+ IQS269_SYS_SETTINGS_PWR_MODE_MASK,
+ iqs269->suspend_mode <<
+ IQS269_SYS_SETTINGS_PWR_MODE_SHIFT);
+ if (error)
+ goto err_irq;
+
+ /*
+ * This last check ensures the device has completed its transition into
+ * the desired power mode to prevent any spurious interrupts from being
+ * triggered after iqs269_suspend has already returned.
+ */
+ error = regmap_read_poll_timeout(iqs269->regmap, IQS269_SYS_FLAGS, val,
+ (val & IQS269_SYS_FLAGS_PWR_MODE_MASK)
+ == (iqs269->suspend_mode <<
+ IQS269_SYS_FLAGS_PWR_MODE_SHIFT),
+ IQS269_PWR_MODE_POLL_SLEEP_US,
+ IQS269_PWR_MODE_POLL_TIMEOUT_US);
+
+err_irq:
+ iqs269_irq_wait();
+ enable_irq(client->irq);
+
+ return error;
+}
+
+static int __maybe_unused iqs269_resume(struct device *dev)
+{
+ struct iqs269_private *iqs269 = dev_get_drvdata(dev);
+ struct i2c_client *client = iqs269->client;
+ unsigned int val;
+ int error;
+
+ if (!iqs269->suspend_mode)
+ return 0;
+
+ disable_irq(client->irq);
+
+ error = regmap_update_bits(iqs269->regmap, IQS269_SYS_SETTINGS,
+ IQS269_SYS_SETTINGS_PWR_MODE_MASK, 0);
+ if (error)
+ goto err_irq;
+
+ /*
+ * This check ensures the device has returned to normal-power mode
+ * before automatic power mode switching is re-enabled.
+ */
+ error = regmap_read_poll_timeout(iqs269->regmap, IQS269_SYS_FLAGS, val,
+ !(val & IQS269_SYS_FLAGS_PWR_MODE_MASK),
+ IQS269_PWR_MODE_POLL_SLEEP_US,
+ IQS269_PWR_MODE_POLL_TIMEOUT_US);
+ if (error)
+ goto err_irq;
+
+ error = regmap_update_bits(iqs269->regmap, IQS269_SYS_SETTINGS,
+ IQS269_SYS_SETTINGS_DIS_AUTO, 0);
+ if (error)
+ goto err_irq;
+
+ /*
+ * This step reports any events that may have been "swallowed" as a
+ * result of polling PWR_MODE (which automatically acknowledges any
+ * pending interrupts).
+ */
+ error = iqs269_report(iqs269);
+
+err_irq:
+ iqs269_irq_wait();
+ enable_irq(client->irq);
+
+ return error;
+}
+
+static SIMPLE_DEV_PM_OPS(iqs269_pm, iqs269_suspend, iqs269_resume);
+
+static const struct of_device_id iqs269_of_match[] = {
+ { .compatible = "azoteq,iqs269a" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, iqs269_of_match);
+
+static struct i2c_driver iqs269_i2c_driver = {
+ .driver = {
+ .name = "iqs269a",
+ .of_match_table = iqs269_of_match,
+ .pm = &iqs269_pm,
+ },
+ .probe_new = iqs269_probe,
+};
+module_i2c_driver(iqs269_i2c_driver);
+
+MODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>");
+MODULE_DESCRIPTION("Azoteq IQS269A Capacitive Touch Controller");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/iqs626a.c b/drivers/input/misc/iqs626a.c
new file mode 100644
index 000000000..23b5dd955
--- /dev/null
+++ b/drivers/input/misc/iqs626a.c
@@ -0,0 +1,1841 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Azoteq IQS626A Capacitive Touch Controller
+ *
+ * Copyright (C) 2020 Jeff LaBundy <jeff@labundy.com>
+ *
+ * This driver registers up to 2 input devices: one representing capacitive or
+ * inductive keys as well as Hall-effect switches, and one for a trackpad that
+ * can express various gestures.
+ */
+
+#include <linux/bits.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input/touchscreen.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#define IQS626_VER_INFO 0x00
+#define IQS626_VER_INFO_PROD_NUM 0x51
+
+#define IQS626_SYS_FLAGS 0x02
+#define IQS626_SYS_FLAGS_SHOW_RESET BIT(15)
+#define IQS626_SYS_FLAGS_IN_ATI BIT(12)
+#define IQS626_SYS_FLAGS_PWR_MODE_MASK GENMASK(9, 8)
+#define IQS626_SYS_FLAGS_PWR_MODE_SHIFT 8
+
+#define IQS626_HALL_OUTPUT 0x23
+
+#define IQS626_SYS_SETTINGS 0x80
+#define IQS626_SYS_SETTINGS_CLK_DIV BIT(15)
+#define IQS626_SYS_SETTINGS_ULP_AUTO BIT(14)
+#define IQS626_SYS_SETTINGS_DIS_AUTO BIT(13)
+#define IQS626_SYS_SETTINGS_PWR_MODE_MASK GENMASK(12, 11)
+#define IQS626_SYS_SETTINGS_PWR_MODE_SHIFT 11
+#define IQS626_SYS_SETTINGS_PWR_MODE_MAX 3
+#define IQS626_SYS_SETTINGS_ULP_UPDATE_MASK GENMASK(10, 8)
+#define IQS626_SYS_SETTINGS_ULP_UPDATE_SHIFT 8
+#define IQS626_SYS_SETTINGS_ULP_UPDATE_MAX 7
+#define IQS626_SYS_SETTINGS_EVENT_MODE BIT(5)
+#define IQS626_SYS_SETTINGS_EVENT_MODE_LP BIT(4)
+#define IQS626_SYS_SETTINGS_REDO_ATI BIT(2)
+#define IQS626_SYS_SETTINGS_ACK_RESET BIT(0)
+
+#define IQS626_MISC_A_ATI_BAND_DISABLE BIT(7)
+#define IQS626_MISC_A_TPx_LTA_UPDATE_MASK GENMASK(6, 4)
+#define IQS626_MISC_A_TPx_LTA_UPDATE_SHIFT 4
+#define IQS626_MISC_A_TPx_LTA_UPDATE_MAX 7
+#define IQS626_MISC_A_ATI_LP_ONLY BIT(3)
+#define IQS626_MISC_A_GPIO3_SELECT_MASK GENMASK(2, 0)
+#define IQS626_MISC_A_GPIO3_SELECT_MAX 7
+
+#define IQS626_EVENT_MASK_SYS BIT(6)
+#define IQS626_EVENT_MASK_GESTURE BIT(3)
+#define IQS626_EVENT_MASK_DEEP BIT(2)
+#define IQS626_EVENT_MASK_TOUCH BIT(1)
+#define IQS626_EVENT_MASK_PROX BIT(0)
+
+#define IQS626_RATE_NP_MS_MAX 255
+#define IQS626_RATE_LP_MS_MAX 255
+#define IQS626_RATE_ULP_MS_MAX 4080
+#define IQS626_TIMEOUT_PWR_MS_MAX 130560
+#define IQS626_TIMEOUT_LTA_MS_MAX 130560
+
+#define IQS626_MISC_B_RESEED_UI_SEL_MASK GENMASK(7, 6)
+#define IQS626_MISC_B_RESEED_UI_SEL_SHIFT 6
+#define IQS626_MISC_B_RESEED_UI_SEL_MAX 3
+#define IQS626_MISC_B_THRESH_EXTEND BIT(5)
+#define IQS626_MISC_B_TRACKING_UI_ENABLE BIT(4)
+#define IQS626_MISC_B_TPx_SWIPE BIT(3)
+#define IQS626_MISC_B_RESEED_OFFSET BIT(2)
+#define IQS626_MISC_B_FILT_STR_TPx GENMASK(1, 0)
+
+#define IQS626_THRESH_SWIPE_MAX 255
+#define IQS626_TIMEOUT_TAP_MS_MAX 4080
+#define IQS626_TIMEOUT_SWIPE_MS_MAX 4080
+
+#define IQS626_CHx_ENG_0_MEAS_CAP_SIZE BIT(7)
+#define IQS626_CHx_ENG_0_RX_TERM_VSS BIT(5)
+#define IQS626_CHx_ENG_0_LINEARIZE BIT(4)
+#define IQS626_CHx_ENG_0_DUAL_DIR BIT(3)
+#define IQS626_CHx_ENG_0_FILT_DISABLE BIT(2)
+#define IQS626_CHx_ENG_0_ATI_MODE_MASK GENMASK(1, 0)
+#define IQS626_CHx_ENG_0_ATI_MODE_MAX 3
+
+#define IQS626_CHx_ENG_1_CCT_HIGH_1 BIT(7)
+#define IQS626_CHx_ENG_1_CCT_HIGH_0 BIT(6)
+#define IQS626_CHx_ENG_1_PROJ_BIAS_MASK GENMASK(5, 4)
+#define IQS626_CHx_ENG_1_PROJ_BIAS_SHIFT 4
+#define IQS626_CHx_ENG_1_PROJ_BIAS_MAX 3
+#define IQS626_CHx_ENG_1_CCT_ENABLE BIT(3)
+#define IQS626_CHx_ENG_1_SENSE_FREQ_MASK GENMASK(2, 1)
+#define IQS626_CHx_ENG_1_SENSE_FREQ_SHIFT 1
+#define IQS626_CHx_ENG_1_SENSE_FREQ_MAX 3
+#define IQS626_CHx_ENG_1_ATI_BAND_TIGHTEN BIT(0)
+
+#define IQS626_CHx_ENG_2_LOCAL_CAP_MASK GENMASK(7, 6)
+#define IQS626_CHx_ENG_2_LOCAL_CAP_SHIFT 6
+#define IQS626_CHx_ENG_2_LOCAL_CAP_MAX 3
+#define IQS626_CHx_ENG_2_LOCAL_CAP_ENABLE BIT(5)
+#define IQS626_CHx_ENG_2_SENSE_MODE_MASK GENMASK(3, 0)
+#define IQS626_CHx_ENG_2_SENSE_MODE_MAX 15
+
+#define IQS626_CHx_ENG_3_TX_FREQ_MASK GENMASK(5, 4)
+#define IQS626_CHx_ENG_3_TX_FREQ_SHIFT 4
+#define IQS626_CHx_ENG_3_TX_FREQ_MAX 3
+#define IQS626_CHx_ENG_3_INV_LOGIC BIT(0)
+
+#define IQS626_CHx_ENG_4_RX_TERM_VREG BIT(6)
+#define IQS626_CHx_ENG_4_CCT_LOW_1 BIT(5)
+#define IQS626_CHx_ENG_4_CCT_LOW_0 BIT(4)
+#define IQS626_CHx_ENG_4_COMP_DISABLE BIT(1)
+#define IQS626_CHx_ENG_4_STATIC_ENABLE BIT(0)
+
+#define IQS626_TPx_ATI_BASE_MIN 45
+#define IQS626_TPx_ATI_BASE_MAX 300
+#define IQS626_CHx_ATI_BASE_MASK GENMASK(7, 6)
+#define IQS626_CHx_ATI_BASE_75 0x00
+#define IQS626_CHx_ATI_BASE_100 0x40
+#define IQS626_CHx_ATI_BASE_150 0x80
+#define IQS626_CHx_ATI_BASE_200 0xC0
+#define IQS626_CHx_ATI_TARGET_MASK GENMASK(5, 0)
+#define IQS626_CHx_ATI_TARGET_MAX 2016
+
+#define IQS626_CHx_THRESH_MAX 255
+#define IQS626_CHx_HYST_DEEP_MASK GENMASK(7, 4)
+#define IQS626_CHx_HYST_DEEP_SHIFT 4
+#define IQS626_CHx_HYST_TOUCH_MASK GENMASK(3, 0)
+#define IQS626_CHx_HYST_MAX 15
+
+#define IQS626_FILT_STR_NP_TPx_MASK GENMASK(7, 6)
+#define IQS626_FILT_STR_NP_TPx_SHIFT 6
+#define IQS626_FILT_STR_LP_TPx_MASK GENMASK(5, 4)
+#define IQS626_FILT_STR_LP_TPx_SHIFT 4
+
+#define IQS626_FILT_STR_NP_CNT_MASK GENMASK(7, 6)
+#define IQS626_FILT_STR_NP_CNT_SHIFT 6
+#define IQS626_FILT_STR_LP_CNT_MASK GENMASK(5, 4)
+#define IQS626_FILT_STR_LP_CNT_SHIFT 4
+#define IQS626_FILT_STR_NP_LTA_MASK GENMASK(3, 2)
+#define IQS626_FILT_STR_NP_LTA_SHIFT 2
+#define IQS626_FILT_STR_LP_LTA_MASK GENMASK(1, 0)
+#define IQS626_FILT_STR_MAX 3
+
+#define IQS626_ULP_PROJ_ENABLE BIT(4)
+#define IQS626_GEN_WEIGHT_MAX 255
+
+#define IQS626_MAX_REG 0xFF
+
+#define IQS626_NUM_CH_TP_3 9
+#define IQS626_NUM_CH_TP_2 6
+#define IQS626_NUM_CH_GEN 3
+#define IQS626_NUM_CRx_TX 8
+
+#define IQS626_PWR_MODE_POLL_SLEEP_US 50000
+#define IQS626_PWR_MODE_POLL_TIMEOUT_US 500000
+
+#define iqs626_irq_wait() usleep_range(350, 400)
+
+enum iqs626_ch_id {
+ IQS626_CH_ULP_0,
+ IQS626_CH_TP_2,
+ IQS626_CH_TP_3,
+ IQS626_CH_GEN_0,
+ IQS626_CH_GEN_1,
+ IQS626_CH_GEN_2,
+ IQS626_CH_HALL,
+};
+
+enum iqs626_rx_inactive {
+ IQS626_RX_INACTIVE_VSS,
+ IQS626_RX_INACTIVE_FLOAT,
+ IQS626_RX_INACTIVE_VREG,
+};
+
+enum iqs626_st_offs {
+ IQS626_ST_OFFS_PROX,
+ IQS626_ST_OFFS_DIR,
+ IQS626_ST_OFFS_TOUCH,
+ IQS626_ST_OFFS_DEEP,
+};
+
+enum iqs626_th_offs {
+ IQS626_TH_OFFS_PROX,
+ IQS626_TH_OFFS_TOUCH,
+ IQS626_TH_OFFS_DEEP,
+};
+
+enum iqs626_event_id {
+ IQS626_EVENT_PROX_DN,
+ IQS626_EVENT_PROX_UP,
+ IQS626_EVENT_TOUCH_DN,
+ IQS626_EVENT_TOUCH_UP,
+ IQS626_EVENT_DEEP_DN,
+ IQS626_EVENT_DEEP_UP,
+};
+
+enum iqs626_gesture_id {
+ IQS626_GESTURE_FLICK_X_POS,
+ IQS626_GESTURE_FLICK_X_NEG,
+ IQS626_GESTURE_FLICK_Y_POS,
+ IQS626_GESTURE_FLICK_Y_NEG,
+ IQS626_GESTURE_TAP,
+ IQS626_GESTURE_HOLD,
+ IQS626_NUM_GESTURES,
+};
+
+struct iqs626_event_desc {
+ const char *name;
+ enum iqs626_st_offs st_offs;
+ enum iqs626_th_offs th_offs;
+ bool dir_up;
+ u8 mask;
+};
+
+static const struct iqs626_event_desc iqs626_events[] = {
+ [IQS626_EVENT_PROX_DN] = {
+ .name = "event-prox",
+ .st_offs = IQS626_ST_OFFS_PROX,
+ .th_offs = IQS626_TH_OFFS_PROX,
+ .mask = IQS626_EVENT_MASK_PROX,
+ },
+ [IQS626_EVENT_PROX_UP] = {
+ .name = "event-prox-alt",
+ .st_offs = IQS626_ST_OFFS_PROX,
+ .th_offs = IQS626_TH_OFFS_PROX,
+ .dir_up = true,
+ .mask = IQS626_EVENT_MASK_PROX,
+ },
+ [IQS626_EVENT_TOUCH_DN] = {
+ .name = "event-touch",
+ .st_offs = IQS626_ST_OFFS_TOUCH,
+ .th_offs = IQS626_TH_OFFS_TOUCH,
+ .mask = IQS626_EVENT_MASK_TOUCH,
+ },
+ [IQS626_EVENT_TOUCH_UP] = {
+ .name = "event-touch-alt",
+ .st_offs = IQS626_ST_OFFS_TOUCH,
+ .th_offs = IQS626_TH_OFFS_TOUCH,
+ .dir_up = true,
+ .mask = IQS626_EVENT_MASK_TOUCH,
+ },
+ [IQS626_EVENT_DEEP_DN] = {
+ .name = "event-deep",
+ .st_offs = IQS626_ST_OFFS_DEEP,
+ .th_offs = IQS626_TH_OFFS_DEEP,
+ .mask = IQS626_EVENT_MASK_DEEP,
+ },
+ [IQS626_EVENT_DEEP_UP] = {
+ .name = "event-deep-alt",
+ .st_offs = IQS626_ST_OFFS_DEEP,
+ .th_offs = IQS626_TH_OFFS_DEEP,
+ .dir_up = true,
+ .mask = IQS626_EVENT_MASK_DEEP,
+ },
+};
+
+struct iqs626_ver_info {
+ u8 prod_num;
+ u8 sw_num;
+ u8 hw_num;
+ u8 padding;
+} __packed;
+
+struct iqs626_flags {
+ __be16 system;
+ u8 gesture;
+ u8 padding_a;
+ u8 states[4];
+ u8 ref_active;
+ u8 padding_b;
+ u8 comp_min;
+ u8 comp_max;
+ u8 trackpad_x;
+ u8 trackpad_y;
+} __packed;
+
+struct iqs626_ch_reg_ulp {
+ u8 thresh[2];
+ u8 hyst;
+ u8 filter;
+ u8 engine[2];
+ u8 ati_target;
+ u8 padding;
+ __be16 ati_comp;
+ u8 rx_enable;
+ u8 tx_enable;
+} __packed;
+
+struct iqs626_ch_reg_tp {
+ u8 thresh;
+ u8 ati_base;
+ __be16 ati_comp;
+} __packed;
+
+struct iqs626_tp_grp_reg {
+ u8 hyst;
+ u8 ati_target;
+ u8 engine[2];
+ struct iqs626_ch_reg_tp ch_reg_tp[IQS626_NUM_CH_TP_3];
+} __packed;
+
+struct iqs626_ch_reg_gen {
+ u8 thresh[3];
+ u8 padding;
+ u8 hyst;
+ u8 ati_target;
+ __be16 ati_comp;
+ u8 engine[5];
+ u8 filter;
+ u8 rx_enable;
+ u8 tx_enable;
+ u8 assoc_select;
+ u8 assoc_weight;
+} __packed;
+
+struct iqs626_ch_reg_hall {
+ u8 engine;
+ u8 thresh;
+ u8 hyst;
+ u8 ati_target;
+ __be16 ati_comp;
+} __packed;
+
+struct iqs626_sys_reg {
+ __be16 general;
+ u8 misc_a;
+ u8 event_mask;
+ u8 active;
+ u8 reseed;
+ u8 rate_np;
+ u8 rate_lp;
+ u8 rate_ulp;
+ u8 timeout_pwr;
+ u8 timeout_rdy;
+ u8 timeout_lta;
+ u8 misc_b;
+ u8 thresh_swipe;
+ u8 timeout_tap;
+ u8 timeout_swipe;
+ u8 redo_ati;
+ u8 padding;
+ struct iqs626_ch_reg_ulp ch_reg_ulp;
+ struct iqs626_tp_grp_reg tp_grp_reg;
+ struct iqs626_ch_reg_gen ch_reg_gen[IQS626_NUM_CH_GEN];
+ struct iqs626_ch_reg_hall ch_reg_hall;
+} __packed;
+
+struct iqs626_channel_desc {
+ const char *name;
+ int num_ch;
+ u8 active;
+ bool events[ARRAY_SIZE(iqs626_events)];
+};
+
+static const struct iqs626_channel_desc iqs626_channels[] = {
+ [IQS626_CH_ULP_0] = {
+ .name = "ulp-0",
+ .num_ch = 1,
+ .active = BIT(0),
+ .events = {
+ [IQS626_EVENT_PROX_DN] = true,
+ [IQS626_EVENT_PROX_UP] = true,
+ [IQS626_EVENT_TOUCH_DN] = true,
+ [IQS626_EVENT_TOUCH_UP] = true,
+ },
+ },
+ [IQS626_CH_TP_2] = {
+ .name = "trackpad-3x2",
+ .num_ch = IQS626_NUM_CH_TP_2,
+ .active = BIT(1),
+ .events = {
+ [IQS626_EVENT_TOUCH_DN] = true,
+ },
+ },
+ [IQS626_CH_TP_3] = {
+ .name = "trackpad-3x3",
+ .num_ch = IQS626_NUM_CH_TP_3,
+ .active = BIT(2) | BIT(1),
+ .events = {
+ [IQS626_EVENT_TOUCH_DN] = true,
+ },
+ },
+ [IQS626_CH_GEN_0] = {
+ .name = "generic-0",
+ .num_ch = 1,
+ .active = BIT(4),
+ .events = {
+ [IQS626_EVENT_PROX_DN] = true,
+ [IQS626_EVENT_PROX_UP] = true,
+ [IQS626_EVENT_TOUCH_DN] = true,
+ [IQS626_EVENT_TOUCH_UP] = true,
+ [IQS626_EVENT_DEEP_DN] = true,
+ [IQS626_EVENT_DEEP_UP] = true,
+ },
+ },
+ [IQS626_CH_GEN_1] = {
+ .name = "generic-1",
+ .num_ch = 1,
+ .active = BIT(5),
+ .events = {
+ [IQS626_EVENT_PROX_DN] = true,
+ [IQS626_EVENT_PROX_UP] = true,
+ [IQS626_EVENT_TOUCH_DN] = true,
+ [IQS626_EVENT_TOUCH_UP] = true,
+ [IQS626_EVENT_DEEP_DN] = true,
+ [IQS626_EVENT_DEEP_UP] = true,
+ },
+ },
+ [IQS626_CH_GEN_2] = {
+ .name = "generic-2",
+ .num_ch = 1,
+ .active = BIT(6),
+ .events = {
+ [IQS626_EVENT_PROX_DN] = true,
+ [IQS626_EVENT_PROX_UP] = true,
+ [IQS626_EVENT_TOUCH_DN] = true,
+ [IQS626_EVENT_TOUCH_UP] = true,
+ [IQS626_EVENT_DEEP_DN] = true,
+ [IQS626_EVENT_DEEP_UP] = true,
+ },
+ },
+ [IQS626_CH_HALL] = {
+ .name = "hall",
+ .num_ch = 1,
+ .active = BIT(7),
+ .events = {
+ [IQS626_EVENT_TOUCH_DN] = true,
+ [IQS626_EVENT_TOUCH_UP] = true,
+ },
+ },
+};
+
+struct iqs626_private {
+ struct i2c_client *client;
+ struct regmap *regmap;
+ struct iqs626_sys_reg sys_reg;
+ struct completion ati_done;
+ struct input_dev *keypad;
+ struct input_dev *trackpad;
+ struct touchscreen_properties prop;
+ unsigned int kp_type[ARRAY_SIZE(iqs626_channels)]
+ [ARRAY_SIZE(iqs626_events)];
+ unsigned int kp_code[ARRAY_SIZE(iqs626_channels)]
+ [ARRAY_SIZE(iqs626_events)];
+ unsigned int tp_code[IQS626_NUM_GESTURES];
+ unsigned int suspend_mode;
+};
+
+static noinline_for_stack int
+iqs626_parse_events(struct iqs626_private *iqs626,
+ const struct fwnode_handle *ch_node,
+ enum iqs626_ch_id ch_id)
+{
+ struct iqs626_sys_reg *sys_reg = &iqs626->sys_reg;
+ struct i2c_client *client = iqs626->client;
+ const struct fwnode_handle *ev_node;
+ const char *ev_name;
+ u8 *thresh, *hyst;
+ unsigned int thresh_tp[IQS626_NUM_CH_TP_3];
+ unsigned int val;
+ int num_ch = iqs626_channels[ch_id].num_ch;
+ int error, i, j;
+
+ switch (ch_id) {
+ case IQS626_CH_ULP_0:
+ thresh = sys_reg->ch_reg_ulp.thresh;
+ hyst = &sys_reg->ch_reg_ulp.hyst;
+ break;
+
+ case IQS626_CH_TP_2:
+ case IQS626_CH_TP_3:
+ thresh = &sys_reg->tp_grp_reg.ch_reg_tp[0].thresh;
+ hyst = &sys_reg->tp_grp_reg.hyst;
+ break;
+
+ case IQS626_CH_GEN_0:
+ case IQS626_CH_GEN_1:
+ case IQS626_CH_GEN_2:
+ i = ch_id - IQS626_CH_GEN_0;
+ thresh = sys_reg->ch_reg_gen[i].thresh;
+ hyst = &sys_reg->ch_reg_gen[i].hyst;
+ break;
+
+ case IQS626_CH_HALL:
+ thresh = &sys_reg->ch_reg_hall.thresh;
+ hyst = &sys_reg->ch_reg_hall.hyst;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(iqs626_events); i++) {
+ if (!iqs626_channels[ch_id].events[i])
+ continue;
+
+ if (ch_id == IQS626_CH_TP_2 || ch_id == IQS626_CH_TP_3) {
+ /*
+ * Trackpad touch events are simply described under the
+ * trackpad child node.
+ */
+ ev_node = ch_node;
+ } else {
+ ev_name = iqs626_events[i].name;
+ ev_node = fwnode_get_named_child_node(ch_node, ev_name);
+ if (!ev_node)
+ continue;
+
+ if (!fwnode_property_read_u32(ev_node, "linux,code",
+ &val)) {
+ iqs626->kp_code[ch_id][i] = val;
+
+ if (fwnode_property_read_u32(ev_node,
+ "linux,input-type",
+ &val)) {
+ if (ch_id == IQS626_CH_HALL)
+ val = EV_SW;
+ else
+ val = EV_KEY;
+ }
+
+ if (val != EV_KEY && val != EV_SW) {
+ dev_err(&client->dev,
+ "Invalid input type: %u\n",
+ val);
+ return -EINVAL;
+ }
+
+ iqs626->kp_type[ch_id][i] = val;
+
+ sys_reg->event_mask &= ~iqs626_events[i].mask;
+ }
+ }
+
+ if (!fwnode_property_read_u32(ev_node, "azoteq,hyst", &val)) {
+ if (val > IQS626_CHx_HYST_MAX) {
+ dev_err(&client->dev,
+ "Invalid %s channel hysteresis: %u\n",
+ fwnode_get_name(ch_node), val);
+ return -EINVAL;
+ }
+
+ if (i == IQS626_EVENT_DEEP_DN ||
+ i == IQS626_EVENT_DEEP_UP) {
+ *hyst &= ~IQS626_CHx_HYST_DEEP_MASK;
+ *hyst |= (val << IQS626_CHx_HYST_DEEP_SHIFT);
+ } else if (i == IQS626_EVENT_TOUCH_DN ||
+ i == IQS626_EVENT_TOUCH_UP) {
+ *hyst &= ~IQS626_CHx_HYST_TOUCH_MASK;
+ *hyst |= val;
+ }
+ }
+
+ if (ch_id != IQS626_CH_TP_2 && ch_id != IQS626_CH_TP_3 &&
+ !fwnode_property_read_u32(ev_node, "azoteq,thresh", &val)) {
+ if (val > IQS626_CHx_THRESH_MAX) {
+ dev_err(&client->dev,
+ "Invalid %s channel threshold: %u\n",
+ fwnode_get_name(ch_node), val);
+ return -EINVAL;
+ }
+
+ if (ch_id == IQS626_CH_HALL)
+ *thresh = val;
+ else
+ *(thresh + iqs626_events[i].th_offs) = val;
+
+ continue;
+ }
+
+ if (!fwnode_property_present(ev_node, "azoteq,thresh"))
+ continue;
+
+ error = fwnode_property_read_u32_array(ev_node, "azoteq,thresh",
+ thresh_tp, num_ch);
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to read %s channel thresholds: %d\n",
+ fwnode_get_name(ch_node), error);
+ return error;
+ }
+
+ for (j = 0; j < num_ch; j++) {
+ if (thresh_tp[j] > IQS626_CHx_THRESH_MAX) {
+ dev_err(&client->dev,
+ "Invalid %s channel threshold: %u\n",
+ fwnode_get_name(ch_node), thresh_tp[j]);
+ return -EINVAL;
+ }
+
+ sys_reg->tp_grp_reg.ch_reg_tp[j].thresh = thresh_tp[j];
+ }
+ }
+
+ return 0;
+}
+
+static noinline_for_stack int
+iqs626_parse_ati_target(struct iqs626_private *iqs626,
+ const struct fwnode_handle *ch_node,
+ enum iqs626_ch_id ch_id)
+{
+ struct iqs626_sys_reg *sys_reg = &iqs626->sys_reg;
+ struct i2c_client *client = iqs626->client;
+ unsigned int ati_base[IQS626_NUM_CH_TP_3];
+ unsigned int val;
+ u8 *ati_target;
+ int num_ch = iqs626_channels[ch_id].num_ch;
+ int error, i;
+
+ switch (ch_id) {
+ case IQS626_CH_ULP_0:
+ ati_target = &sys_reg->ch_reg_ulp.ati_target;
+ break;
+
+ case IQS626_CH_TP_2:
+ case IQS626_CH_TP_3:
+ ati_target = &sys_reg->tp_grp_reg.ati_target;
+ break;
+
+ case IQS626_CH_GEN_0:
+ case IQS626_CH_GEN_1:
+ case IQS626_CH_GEN_2:
+ i = ch_id - IQS626_CH_GEN_0;
+ ati_target = &sys_reg->ch_reg_gen[i].ati_target;
+ break;
+
+ case IQS626_CH_HALL:
+ ati_target = &sys_reg->ch_reg_hall.ati_target;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ if (!fwnode_property_read_u32(ch_node, "azoteq,ati-target", &val)) {
+ if (val > IQS626_CHx_ATI_TARGET_MAX) {
+ dev_err(&client->dev,
+ "Invalid %s channel ATI target: %u\n",
+ fwnode_get_name(ch_node), val);
+ return -EINVAL;
+ }
+
+ *ati_target &= ~IQS626_CHx_ATI_TARGET_MASK;
+ *ati_target |= (val / 32);
+ }
+
+ if (ch_id != IQS626_CH_TP_2 && ch_id != IQS626_CH_TP_3 &&
+ !fwnode_property_read_u32(ch_node, "azoteq,ati-base", &val)) {
+ switch (val) {
+ case 75:
+ val = IQS626_CHx_ATI_BASE_75;
+ break;
+
+ case 100:
+ val = IQS626_CHx_ATI_BASE_100;
+ break;
+
+ case 150:
+ val = IQS626_CHx_ATI_BASE_150;
+ break;
+
+ case 200:
+ val = IQS626_CHx_ATI_BASE_200;
+ break;
+
+ default:
+ dev_err(&client->dev,
+ "Invalid %s channel ATI base: %u\n",
+ fwnode_get_name(ch_node), val);
+ return -EINVAL;
+ }
+
+ *ati_target &= ~IQS626_CHx_ATI_BASE_MASK;
+ *ati_target |= val;
+
+ return 0;
+ }
+
+ if (!fwnode_property_present(ch_node, "azoteq,ati-base"))
+ return 0;
+
+ error = fwnode_property_read_u32_array(ch_node, "azoteq,ati-base",
+ ati_base, num_ch);
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to read %s channel ATI bases: %d\n",
+ fwnode_get_name(ch_node), error);
+ return error;
+ }
+
+ for (i = 0; i < num_ch; i++) {
+ if (ati_base[i] < IQS626_TPx_ATI_BASE_MIN ||
+ ati_base[i] > IQS626_TPx_ATI_BASE_MAX) {
+ dev_err(&client->dev,
+ "Invalid %s channel ATI base: %u\n",
+ fwnode_get_name(ch_node), ati_base[i]);
+ return -EINVAL;
+ }
+
+ ati_base[i] -= IQS626_TPx_ATI_BASE_MIN;
+ sys_reg->tp_grp_reg.ch_reg_tp[i].ati_base = ati_base[i];
+ }
+
+ return 0;
+}
+
+static int iqs626_parse_pins(struct iqs626_private *iqs626,
+ const struct fwnode_handle *ch_node,
+ const char *propname, u8 *enable)
+{
+ struct i2c_client *client = iqs626->client;
+ unsigned int val[IQS626_NUM_CRx_TX];
+ int error, count, i;
+
+ if (!fwnode_property_present(ch_node, propname))
+ return 0;
+
+ count = fwnode_property_count_u32(ch_node, propname);
+ if (count > IQS626_NUM_CRx_TX) {
+ dev_err(&client->dev,
+ "Too many %s channel CRX/TX pins present\n",
+ fwnode_get_name(ch_node));
+ return -EINVAL;
+ } else if (count < 0) {
+ dev_err(&client->dev,
+ "Failed to count %s channel CRX/TX pins: %d\n",
+ fwnode_get_name(ch_node), count);
+ return count;
+ }
+
+ error = fwnode_property_read_u32_array(ch_node, propname, val, count);
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to read %s channel CRX/TX pins: %d\n",
+ fwnode_get_name(ch_node), error);
+ return error;
+ }
+
+ *enable = 0;
+
+ for (i = 0; i < count; i++) {
+ if (val[i] >= IQS626_NUM_CRx_TX) {
+ dev_err(&client->dev,
+ "Invalid %s channel CRX/TX pin: %u\n",
+ fwnode_get_name(ch_node), val[i]);
+ return -EINVAL;
+ }
+
+ *enable |= BIT(val[i]);
+ }
+
+ return 0;
+}
+
+static int iqs626_parse_trackpad(struct iqs626_private *iqs626,
+ const struct fwnode_handle *ch_node)
+{
+ struct iqs626_sys_reg *sys_reg = &iqs626->sys_reg;
+ struct i2c_client *client = iqs626->client;
+ u8 *hyst = &sys_reg->tp_grp_reg.hyst;
+ unsigned int val;
+ int error, count;
+
+ if (!fwnode_property_read_u32(ch_node, "azoteq,lta-update", &val)) {
+ if (val > IQS626_MISC_A_TPx_LTA_UPDATE_MAX) {
+ dev_err(&client->dev,
+ "Invalid %s channel update rate: %u\n",
+ fwnode_get_name(ch_node), val);
+ return -EINVAL;
+ }
+
+ sys_reg->misc_a &= ~IQS626_MISC_A_TPx_LTA_UPDATE_MASK;
+ sys_reg->misc_a |= (val << IQS626_MISC_A_TPx_LTA_UPDATE_SHIFT);
+ }
+
+ if (!fwnode_property_read_u32(ch_node, "azoteq,filt-str-trackpad",
+ &val)) {
+ if (val > IQS626_FILT_STR_MAX) {
+ dev_err(&client->dev,
+ "Invalid %s channel filter strength: %u\n",
+ fwnode_get_name(ch_node), val);
+ return -EINVAL;
+ }
+
+ sys_reg->misc_b &= ~IQS626_MISC_B_FILT_STR_TPx;
+ sys_reg->misc_b |= val;
+ }
+
+ if (!fwnode_property_read_u32(ch_node, "azoteq,filt-str-np-cnt",
+ &val)) {
+ if (val > IQS626_FILT_STR_MAX) {
+ dev_err(&client->dev,
+ "Invalid %s channel filter strength: %u\n",
+ fwnode_get_name(ch_node), val);
+ return -EINVAL;
+ }
+
+ *hyst &= ~IQS626_FILT_STR_NP_TPx_MASK;
+ *hyst |= (val << IQS626_FILT_STR_NP_TPx_SHIFT);
+ }
+
+ if (!fwnode_property_read_u32(ch_node, "azoteq,filt-str-lp-cnt",
+ &val)) {
+ if (val > IQS626_FILT_STR_MAX) {
+ dev_err(&client->dev,
+ "Invalid %s channel filter strength: %u\n",
+ fwnode_get_name(ch_node), val);
+ return -EINVAL;
+ }
+
+ *hyst &= ~IQS626_FILT_STR_LP_TPx_MASK;
+ *hyst |= (val << IQS626_FILT_STR_LP_TPx_SHIFT);
+ }
+
+ if (!fwnode_property_present(ch_node, "linux,keycodes"))
+ return 0;
+
+ count = fwnode_property_count_u32(ch_node, "linux,keycodes");
+ if (count > IQS626_NUM_GESTURES) {
+ dev_err(&client->dev, "Too many keycodes present\n");
+ return -EINVAL;
+ } else if (count < 0) {
+ dev_err(&client->dev, "Failed to count keycodes: %d\n", count);
+ return count;
+ }
+
+ error = fwnode_property_read_u32_array(ch_node, "linux,keycodes",
+ iqs626->tp_code, count);
+ if (error) {
+ dev_err(&client->dev, "Failed to read keycodes: %d\n", error);
+ return error;
+ }
+
+ sys_reg->misc_b &= ~IQS626_MISC_B_TPx_SWIPE;
+ if (fwnode_property_present(ch_node, "azoteq,gesture-swipe"))
+ sys_reg->misc_b |= IQS626_MISC_B_TPx_SWIPE;
+
+ if (!fwnode_property_read_u32(ch_node, "azoteq,timeout-tap-ms",
+ &val)) {
+ if (val > IQS626_TIMEOUT_TAP_MS_MAX) {
+ dev_err(&client->dev,
+ "Invalid %s channel timeout: %u\n",
+ fwnode_get_name(ch_node), val);
+ return -EINVAL;
+ }
+
+ sys_reg->timeout_tap = val / 16;
+ }
+
+ if (!fwnode_property_read_u32(ch_node, "azoteq,timeout-swipe-ms",
+ &val)) {
+ if (val > IQS626_TIMEOUT_SWIPE_MS_MAX) {
+ dev_err(&client->dev,
+ "Invalid %s channel timeout: %u\n",
+ fwnode_get_name(ch_node), val);
+ return -EINVAL;
+ }
+
+ sys_reg->timeout_swipe = val / 16;
+ }
+
+ if (!fwnode_property_read_u32(ch_node, "azoteq,thresh-swipe",
+ &val)) {
+ if (val > IQS626_THRESH_SWIPE_MAX) {
+ dev_err(&client->dev,
+ "Invalid %s channel threshold: %u\n",
+ fwnode_get_name(ch_node), val);
+ return -EINVAL;
+ }
+
+ sys_reg->thresh_swipe = val;
+ }
+
+ sys_reg->event_mask &= ~IQS626_EVENT_MASK_GESTURE;
+
+ return 0;
+}
+
+static noinline_for_stack int
+iqs626_parse_channel(struct iqs626_private *iqs626,
+ const struct fwnode_handle *ch_node,
+ enum iqs626_ch_id ch_id)
+{
+ struct iqs626_sys_reg *sys_reg = &iqs626->sys_reg;
+ struct i2c_client *client = iqs626->client;
+ u8 *engine, *filter, *rx_enable, *tx_enable;
+ u8 *assoc_select, *assoc_weight;
+ unsigned int val;
+ int error, i;
+
+ switch (ch_id) {
+ case IQS626_CH_ULP_0:
+ engine = sys_reg->ch_reg_ulp.engine;
+ break;
+
+ case IQS626_CH_TP_2:
+ case IQS626_CH_TP_3:
+ engine = sys_reg->tp_grp_reg.engine;
+ break;
+
+ case IQS626_CH_GEN_0:
+ case IQS626_CH_GEN_1:
+ case IQS626_CH_GEN_2:
+ i = ch_id - IQS626_CH_GEN_0;
+ engine = sys_reg->ch_reg_gen[i].engine;
+ break;
+
+ case IQS626_CH_HALL:
+ engine = &sys_reg->ch_reg_hall.engine;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ *engine |= IQS626_CHx_ENG_0_MEAS_CAP_SIZE;
+ if (fwnode_property_present(ch_node, "azoteq,meas-cap-decrease"))
+ *engine &= ~IQS626_CHx_ENG_0_MEAS_CAP_SIZE;
+
+ *engine |= IQS626_CHx_ENG_0_RX_TERM_VSS;
+ if (!fwnode_property_read_u32(ch_node, "azoteq,rx-inactive", &val)) {
+ switch (val) {
+ case IQS626_RX_INACTIVE_VSS:
+ break;
+
+ case IQS626_RX_INACTIVE_FLOAT:
+ *engine &= ~IQS626_CHx_ENG_0_RX_TERM_VSS;
+ if (ch_id == IQS626_CH_GEN_0 ||
+ ch_id == IQS626_CH_GEN_1 ||
+ ch_id == IQS626_CH_GEN_2)
+ *(engine + 4) &= ~IQS626_CHx_ENG_4_RX_TERM_VREG;
+ break;
+
+ case IQS626_RX_INACTIVE_VREG:
+ if (ch_id == IQS626_CH_GEN_0 ||
+ ch_id == IQS626_CH_GEN_1 ||
+ ch_id == IQS626_CH_GEN_2) {
+ *engine &= ~IQS626_CHx_ENG_0_RX_TERM_VSS;
+ *(engine + 4) |= IQS626_CHx_ENG_4_RX_TERM_VREG;
+ break;
+ }
+ fallthrough;
+
+ default:
+ dev_err(&client->dev,
+ "Invalid %s channel CRX pin termination: %u\n",
+ fwnode_get_name(ch_node), val);
+ return -EINVAL;
+ }
+ }
+
+ *engine &= ~IQS626_CHx_ENG_0_LINEARIZE;
+ if (fwnode_property_present(ch_node, "azoteq,linearize"))
+ *engine |= IQS626_CHx_ENG_0_LINEARIZE;
+
+ *engine &= ~IQS626_CHx_ENG_0_DUAL_DIR;
+ if (fwnode_property_present(ch_node, "azoteq,dual-direction"))
+ *engine |= IQS626_CHx_ENG_0_DUAL_DIR;
+
+ *engine &= ~IQS626_CHx_ENG_0_FILT_DISABLE;
+ if (fwnode_property_present(ch_node, "azoteq,filt-disable"))
+ *engine |= IQS626_CHx_ENG_0_FILT_DISABLE;
+
+ if (!fwnode_property_read_u32(ch_node, "azoteq,ati-mode", &val)) {
+ if (val > IQS626_CHx_ENG_0_ATI_MODE_MAX) {
+ dev_err(&client->dev,
+ "Invalid %s channel ATI mode: %u\n",
+ fwnode_get_name(ch_node), val);
+ return -EINVAL;
+ }
+
+ *engine &= ~IQS626_CHx_ENG_0_ATI_MODE_MASK;
+ *engine |= val;
+ }
+
+ if (ch_id == IQS626_CH_HALL)
+ return 0;
+
+ *(engine + 1) &= ~IQS626_CHx_ENG_1_CCT_ENABLE;
+ if (!fwnode_property_read_u32(ch_node, "azoteq,cct-increase",
+ &val) && val) {
+ unsigned int orig_val = val--;
+
+ /*
+ * In the case of the generic channels, the charge cycle time
+ * field doubles in size and straddles two separate registers.
+ */
+ if (ch_id == IQS626_CH_GEN_0 ||
+ ch_id == IQS626_CH_GEN_1 ||
+ ch_id == IQS626_CH_GEN_2) {
+ *(engine + 4) &= ~IQS626_CHx_ENG_4_CCT_LOW_1;
+ if (val & BIT(1))
+ *(engine + 4) |= IQS626_CHx_ENG_4_CCT_LOW_1;
+
+ *(engine + 4) &= ~IQS626_CHx_ENG_4_CCT_LOW_0;
+ if (val & BIT(0))
+ *(engine + 4) |= IQS626_CHx_ENG_4_CCT_LOW_0;
+
+ val >>= 2;
+ }
+
+ if (val & ~GENMASK(1, 0)) {
+ dev_err(&client->dev,
+ "Invalid %s channel charge cycle time: %u\n",
+ fwnode_get_name(ch_node), orig_val);
+ return -EINVAL;
+ }
+
+ *(engine + 1) &= ~IQS626_CHx_ENG_1_CCT_HIGH_1;
+ if (val & BIT(1))
+ *(engine + 1) |= IQS626_CHx_ENG_1_CCT_HIGH_1;
+
+ *(engine + 1) &= ~IQS626_CHx_ENG_1_CCT_HIGH_0;
+ if (val & BIT(0))
+ *(engine + 1) |= IQS626_CHx_ENG_1_CCT_HIGH_0;
+
+ *(engine + 1) |= IQS626_CHx_ENG_1_CCT_ENABLE;
+ }
+
+ if (!fwnode_property_read_u32(ch_node, "azoteq,proj-bias", &val)) {
+ if (val > IQS626_CHx_ENG_1_PROJ_BIAS_MAX) {
+ dev_err(&client->dev,
+ "Invalid %s channel bias current: %u\n",
+ fwnode_get_name(ch_node), val);
+ return -EINVAL;
+ }
+
+ *(engine + 1) &= ~IQS626_CHx_ENG_1_PROJ_BIAS_MASK;
+ *(engine + 1) |= (val << IQS626_CHx_ENG_1_PROJ_BIAS_SHIFT);
+ }
+
+ if (!fwnode_property_read_u32(ch_node, "azoteq,sense-freq", &val)) {
+ if (val > IQS626_CHx_ENG_1_SENSE_FREQ_MAX) {
+ dev_err(&client->dev,
+ "Invalid %s channel sensing frequency: %u\n",
+ fwnode_get_name(ch_node), val);
+ return -EINVAL;
+ }
+
+ *(engine + 1) &= ~IQS626_CHx_ENG_1_SENSE_FREQ_MASK;
+ *(engine + 1) |= (val << IQS626_CHx_ENG_1_SENSE_FREQ_SHIFT);
+ }
+
+ *(engine + 1) &= ~IQS626_CHx_ENG_1_ATI_BAND_TIGHTEN;
+ if (fwnode_property_present(ch_node, "azoteq,ati-band-tighten"))
+ *(engine + 1) |= IQS626_CHx_ENG_1_ATI_BAND_TIGHTEN;
+
+ if (ch_id == IQS626_CH_TP_2 || ch_id == IQS626_CH_TP_3)
+ return iqs626_parse_trackpad(iqs626, ch_node);
+
+ if (ch_id == IQS626_CH_ULP_0) {
+ sys_reg->ch_reg_ulp.hyst &= ~IQS626_ULP_PROJ_ENABLE;
+ if (fwnode_property_present(ch_node, "azoteq,proj-enable"))
+ sys_reg->ch_reg_ulp.hyst |= IQS626_ULP_PROJ_ENABLE;
+
+ filter = &sys_reg->ch_reg_ulp.filter;
+
+ rx_enable = &sys_reg->ch_reg_ulp.rx_enable;
+ tx_enable = &sys_reg->ch_reg_ulp.tx_enable;
+ } else {
+ i = ch_id - IQS626_CH_GEN_0;
+ filter = &sys_reg->ch_reg_gen[i].filter;
+
+ rx_enable = &sys_reg->ch_reg_gen[i].rx_enable;
+ tx_enable = &sys_reg->ch_reg_gen[i].tx_enable;
+ }
+
+ if (!fwnode_property_read_u32(ch_node, "azoteq,filt-str-np-cnt",
+ &val)) {
+ if (val > IQS626_FILT_STR_MAX) {
+ dev_err(&client->dev,
+ "Invalid %s channel filter strength: %u\n",
+ fwnode_get_name(ch_node), val);
+ return -EINVAL;
+ }
+
+ *filter &= ~IQS626_FILT_STR_NP_CNT_MASK;
+ *filter |= (val << IQS626_FILT_STR_NP_CNT_SHIFT);
+ }
+
+ if (!fwnode_property_read_u32(ch_node, "azoteq,filt-str-lp-cnt",
+ &val)) {
+ if (val > IQS626_FILT_STR_MAX) {
+ dev_err(&client->dev,
+ "Invalid %s channel filter strength: %u\n",
+ fwnode_get_name(ch_node), val);
+ return -EINVAL;
+ }
+
+ *filter &= ~IQS626_FILT_STR_LP_CNT_MASK;
+ *filter |= (val << IQS626_FILT_STR_LP_CNT_SHIFT);
+ }
+
+ if (!fwnode_property_read_u32(ch_node, "azoteq,filt-str-np-lta",
+ &val)) {
+ if (val > IQS626_FILT_STR_MAX) {
+ dev_err(&client->dev,
+ "Invalid %s channel filter strength: %u\n",
+ fwnode_get_name(ch_node), val);
+ return -EINVAL;
+ }
+
+ *filter &= ~IQS626_FILT_STR_NP_LTA_MASK;
+ *filter |= (val << IQS626_FILT_STR_NP_LTA_SHIFT);
+ }
+
+ if (!fwnode_property_read_u32(ch_node, "azoteq,filt-str-lp-lta",
+ &val)) {
+ if (val > IQS626_FILT_STR_MAX) {
+ dev_err(&client->dev,
+ "Invalid %s channel filter strength: %u\n",
+ fwnode_get_name(ch_node), val);
+ return -EINVAL;
+ }
+
+ *filter &= ~IQS626_FILT_STR_LP_LTA_MASK;
+ *filter |= val;
+ }
+
+ error = iqs626_parse_pins(iqs626, ch_node, "azoteq,rx-enable",
+ rx_enable);
+ if (error)
+ return error;
+
+ error = iqs626_parse_pins(iqs626, ch_node, "azoteq,tx-enable",
+ tx_enable);
+ if (error)
+ return error;
+
+ if (ch_id == IQS626_CH_ULP_0)
+ return 0;
+
+ *(engine + 2) &= ~IQS626_CHx_ENG_2_LOCAL_CAP_ENABLE;
+ if (!fwnode_property_read_u32(ch_node, "azoteq,local-cap-size",
+ &val) && val) {
+ unsigned int orig_val = val--;
+
+ if (val > IQS626_CHx_ENG_2_LOCAL_CAP_MAX) {
+ dev_err(&client->dev,
+ "Invalid %s channel local cap. size: %u\n",
+ fwnode_get_name(ch_node), orig_val);
+ return -EINVAL;
+ }
+
+ *(engine + 2) &= ~IQS626_CHx_ENG_2_LOCAL_CAP_MASK;
+ *(engine + 2) |= (val << IQS626_CHx_ENG_2_LOCAL_CAP_SHIFT);
+
+ *(engine + 2) |= IQS626_CHx_ENG_2_LOCAL_CAP_ENABLE;
+ }
+
+ if (!fwnode_property_read_u32(ch_node, "azoteq,sense-mode", &val)) {
+ if (val > IQS626_CHx_ENG_2_SENSE_MODE_MAX) {
+ dev_err(&client->dev,
+ "Invalid %s channel sensing mode: %u\n",
+ fwnode_get_name(ch_node), val);
+ return -EINVAL;
+ }
+
+ *(engine + 2) &= ~IQS626_CHx_ENG_2_SENSE_MODE_MASK;
+ *(engine + 2) |= val;
+ }
+
+ if (!fwnode_property_read_u32(ch_node, "azoteq,tx-freq", &val)) {
+ if (val > IQS626_CHx_ENG_3_TX_FREQ_MAX) {
+ dev_err(&client->dev,
+ "Invalid %s channel excitation frequency: %u\n",
+ fwnode_get_name(ch_node), val);
+ return -EINVAL;
+ }
+
+ *(engine + 3) &= ~IQS626_CHx_ENG_3_TX_FREQ_MASK;
+ *(engine + 3) |= (val << IQS626_CHx_ENG_3_TX_FREQ_SHIFT);
+ }
+
+ *(engine + 3) &= ~IQS626_CHx_ENG_3_INV_LOGIC;
+ if (fwnode_property_present(ch_node, "azoteq,invert-enable"))
+ *(engine + 3) |= IQS626_CHx_ENG_3_INV_LOGIC;
+
+ *(engine + 4) &= ~IQS626_CHx_ENG_4_COMP_DISABLE;
+ if (fwnode_property_present(ch_node, "azoteq,comp-disable"))
+ *(engine + 4) |= IQS626_CHx_ENG_4_COMP_DISABLE;
+
+ *(engine + 4) &= ~IQS626_CHx_ENG_4_STATIC_ENABLE;
+ if (fwnode_property_present(ch_node, "azoteq,static-enable"))
+ *(engine + 4) |= IQS626_CHx_ENG_4_STATIC_ENABLE;
+
+ i = ch_id - IQS626_CH_GEN_0;
+ assoc_select = &sys_reg->ch_reg_gen[i].assoc_select;
+ assoc_weight = &sys_reg->ch_reg_gen[i].assoc_weight;
+
+ *assoc_select = 0;
+ if (!fwnode_property_present(ch_node, "azoteq,assoc-select"))
+ return 0;
+
+ for (i = 0; i < ARRAY_SIZE(iqs626_channels); i++) {
+ if (fwnode_property_match_string(ch_node, "azoteq,assoc-select",
+ iqs626_channels[i].name) < 0)
+ continue;
+
+ *assoc_select |= iqs626_channels[i].active;
+ }
+
+ if (fwnode_property_read_u32(ch_node, "azoteq,assoc-weight", &val))
+ return 0;
+
+ if (val > IQS626_GEN_WEIGHT_MAX) {
+ dev_err(&client->dev,
+ "Invalid %s channel associated weight: %u\n",
+ fwnode_get_name(ch_node), val);
+ return -EINVAL;
+ }
+
+ *assoc_weight = val;
+
+ return 0;
+}
+
+static int iqs626_parse_prop(struct iqs626_private *iqs626)
+{
+ struct iqs626_sys_reg *sys_reg = &iqs626->sys_reg;
+ struct i2c_client *client = iqs626->client;
+ struct fwnode_handle *ch_node;
+ unsigned int val;
+ int error, i;
+ u16 general;
+
+ if (!device_property_read_u32(&client->dev, "azoteq,suspend-mode",
+ &val)) {
+ if (val > IQS626_SYS_SETTINGS_PWR_MODE_MAX) {
+ dev_err(&client->dev, "Invalid suspend mode: %u\n",
+ val);
+ return -EINVAL;
+ }
+
+ iqs626->suspend_mode = val;
+ }
+
+ error = regmap_raw_read(iqs626->regmap, IQS626_SYS_SETTINGS, sys_reg,
+ sizeof(*sys_reg));
+ if (error)
+ return error;
+
+ general = be16_to_cpu(sys_reg->general);
+ general &= IQS626_SYS_SETTINGS_ULP_UPDATE_MASK;
+
+ if (device_property_present(&client->dev, "azoteq,clk-div"))
+ general |= IQS626_SYS_SETTINGS_CLK_DIV;
+
+ if (device_property_present(&client->dev, "azoteq,ulp-enable"))
+ general |= IQS626_SYS_SETTINGS_ULP_AUTO;
+
+ if (!device_property_read_u32(&client->dev, "azoteq,ulp-update",
+ &val)) {
+ if (val > IQS626_SYS_SETTINGS_ULP_UPDATE_MAX) {
+ dev_err(&client->dev, "Invalid update rate: %u\n", val);
+ return -EINVAL;
+ }
+
+ general &= ~IQS626_SYS_SETTINGS_ULP_UPDATE_MASK;
+ general |= (val << IQS626_SYS_SETTINGS_ULP_UPDATE_SHIFT);
+ }
+
+ sys_reg->misc_a &= ~IQS626_MISC_A_ATI_BAND_DISABLE;
+ if (device_property_present(&client->dev, "azoteq,ati-band-disable"))
+ sys_reg->misc_a |= IQS626_MISC_A_ATI_BAND_DISABLE;
+
+ sys_reg->misc_a &= ~IQS626_MISC_A_ATI_LP_ONLY;
+ if (device_property_present(&client->dev, "azoteq,ati-lp-only"))
+ sys_reg->misc_a |= IQS626_MISC_A_ATI_LP_ONLY;
+
+ if (!device_property_read_u32(&client->dev, "azoteq,gpio3-select",
+ &val)) {
+ if (val > IQS626_MISC_A_GPIO3_SELECT_MAX) {
+ dev_err(&client->dev, "Invalid GPIO3 selection: %u\n",
+ val);
+ return -EINVAL;
+ }
+
+ sys_reg->misc_a &= ~IQS626_MISC_A_GPIO3_SELECT_MASK;
+ sys_reg->misc_a |= val;
+ }
+
+ if (!device_property_read_u32(&client->dev, "azoteq,reseed-select",
+ &val)) {
+ if (val > IQS626_MISC_B_RESEED_UI_SEL_MAX) {
+ dev_err(&client->dev, "Invalid reseed selection: %u\n",
+ val);
+ return -EINVAL;
+ }
+
+ sys_reg->misc_b &= ~IQS626_MISC_B_RESEED_UI_SEL_MASK;
+ sys_reg->misc_b |= (val << IQS626_MISC_B_RESEED_UI_SEL_SHIFT);
+ }
+
+ sys_reg->misc_b &= ~IQS626_MISC_B_THRESH_EXTEND;
+ if (device_property_present(&client->dev, "azoteq,thresh-extend"))
+ sys_reg->misc_b |= IQS626_MISC_B_THRESH_EXTEND;
+
+ sys_reg->misc_b &= ~IQS626_MISC_B_TRACKING_UI_ENABLE;
+ if (device_property_present(&client->dev, "azoteq,tracking-enable"))
+ sys_reg->misc_b |= IQS626_MISC_B_TRACKING_UI_ENABLE;
+
+ sys_reg->misc_b &= ~IQS626_MISC_B_RESEED_OFFSET;
+ if (device_property_present(&client->dev, "azoteq,reseed-offset"))
+ sys_reg->misc_b |= IQS626_MISC_B_RESEED_OFFSET;
+
+ if (!device_property_read_u32(&client->dev, "azoteq,rate-np-ms",
+ &val)) {
+ if (val > IQS626_RATE_NP_MS_MAX) {
+ dev_err(&client->dev, "Invalid report rate: %u\n", val);
+ return -EINVAL;
+ }
+
+ sys_reg->rate_np = val;
+ }
+
+ if (!device_property_read_u32(&client->dev, "azoteq,rate-lp-ms",
+ &val)) {
+ if (val > IQS626_RATE_LP_MS_MAX) {
+ dev_err(&client->dev, "Invalid report rate: %u\n", val);
+ return -EINVAL;
+ }
+
+ sys_reg->rate_lp = val;
+ }
+
+ if (!device_property_read_u32(&client->dev, "azoteq,rate-ulp-ms",
+ &val)) {
+ if (val > IQS626_RATE_ULP_MS_MAX) {
+ dev_err(&client->dev, "Invalid report rate: %u\n", val);
+ return -EINVAL;
+ }
+
+ sys_reg->rate_ulp = val / 16;
+ }
+
+ if (!device_property_read_u32(&client->dev, "azoteq,timeout-pwr-ms",
+ &val)) {
+ if (val > IQS626_TIMEOUT_PWR_MS_MAX) {
+ dev_err(&client->dev, "Invalid timeout: %u\n", val);
+ return -EINVAL;
+ }
+
+ sys_reg->timeout_pwr = val / 512;
+ }
+
+ if (!device_property_read_u32(&client->dev, "azoteq,timeout-lta-ms",
+ &val)) {
+ if (val > IQS626_TIMEOUT_LTA_MS_MAX) {
+ dev_err(&client->dev, "Invalid timeout: %u\n", val);
+ return -EINVAL;
+ }
+
+ sys_reg->timeout_lta = val / 512;
+ }
+
+ sys_reg->event_mask = ~((u8)IQS626_EVENT_MASK_SYS);
+ sys_reg->redo_ati = 0;
+
+ sys_reg->reseed = 0;
+ sys_reg->active = 0;
+
+ for (i = 0; i < ARRAY_SIZE(iqs626_channels); i++) {
+ ch_node = device_get_named_child_node(&client->dev,
+ iqs626_channels[i].name);
+ if (!ch_node)
+ continue;
+
+ error = iqs626_parse_channel(iqs626, ch_node, i);
+ if (error)
+ return error;
+
+ error = iqs626_parse_ati_target(iqs626, ch_node, i);
+ if (error)
+ return error;
+
+ error = iqs626_parse_events(iqs626, ch_node, i);
+ if (error)
+ return error;
+
+ if (!fwnode_property_present(ch_node, "azoteq,ati-exclude"))
+ sys_reg->redo_ati |= iqs626_channels[i].active;
+
+ if (!fwnode_property_present(ch_node, "azoteq,reseed-disable"))
+ sys_reg->reseed |= iqs626_channels[i].active;
+
+ sys_reg->active |= iqs626_channels[i].active;
+ }
+
+ general |= IQS626_SYS_SETTINGS_EVENT_MODE;
+
+ /*
+ * Enable streaming during normal-power mode if the trackpad is used to
+ * report raw coordinates instead of gestures. In that case, the device
+ * returns to event mode during low-power mode.
+ */
+ if (sys_reg->active & iqs626_channels[IQS626_CH_TP_2].active &&
+ sys_reg->event_mask & IQS626_EVENT_MASK_GESTURE)
+ general |= IQS626_SYS_SETTINGS_EVENT_MODE_LP;
+
+ general |= IQS626_SYS_SETTINGS_REDO_ATI;
+ general |= IQS626_SYS_SETTINGS_ACK_RESET;
+
+ sys_reg->general = cpu_to_be16(general);
+
+ error = regmap_raw_write(iqs626->regmap, IQS626_SYS_SETTINGS,
+ &iqs626->sys_reg, sizeof(iqs626->sys_reg));
+ if (error)
+ return error;
+
+ iqs626_irq_wait();
+
+ return 0;
+}
+
+static int iqs626_input_init(struct iqs626_private *iqs626)
+{
+ struct iqs626_sys_reg *sys_reg = &iqs626->sys_reg;
+ struct i2c_client *client = iqs626->client;
+ int error, i, j;
+
+ iqs626->keypad = devm_input_allocate_device(&client->dev);
+ if (!iqs626->keypad)
+ return -ENOMEM;
+
+ iqs626->keypad->keycodemax = ARRAY_SIZE(iqs626->kp_code);
+ iqs626->keypad->keycode = iqs626->kp_code;
+ iqs626->keypad->keycodesize = sizeof(**iqs626->kp_code);
+
+ iqs626->keypad->name = "iqs626a_keypad";
+ iqs626->keypad->id.bustype = BUS_I2C;
+
+ for (i = 0; i < ARRAY_SIZE(iqs626_channels); i++) {
+ if (!(sys_reg->active & iqs626_channels[i].active))
+ continue;
+
+ for (j = 0; j < ARRAY_SIZE(iqs626_events); j++) {
+ if (!iqs626->kp_type[i][j])
+ continue;
+
+ input_set_capability(iqs626->keypad,
+ iqs626->kp_type[i][j],
+ iqs626->kp_code[i][j]);
+ }
+ }
+
+ if (!(sys_reg->active & iqs626_channels[IQS626_CH_TP_2].active))
+ return 0;
+
+ iqs626->trackpad = devm_input_allocate_device(&client->dev);
+ if (!iqs626->trackpad)
+ return -ENOMEM;
+
+ iqs626->trackpad->keycodemax = ARRAY_SIZE(iqs626->tp_code);
+ iqs626->trackpad->keycode = iqs626->tp_code;
+ iqs626->trackpad->keycodesize = sizeof(*iqs626->tp_code);
+
+ iqs626->trackpad->name = "iqs626a_trackpad";
+ iqs626->trackpad->id.bustype = BUS_I2C;
+
+ /*
+ * Present the trackpad as a traditional pointing device if no gestures
+ * have been mapped to a keycode.
+ */
+ if (sys_reg->event_mask & IQS626_EVENT_MASK_GESTURE) {
+ u8 tp_mask = iqs626_channels[IQS626_CH_TP_3].active;
+
+ input_set_capability(iqs626->trackpad, EV_KEY, BTN_TOUCH);
+ input_set_abs_params(iqs626->trackpad, ABS_Y, 0, 255, 0, 0);
+
+ if ((sys_reg->active & tp_mask) == tp_mask)
+ input_set_abs_params(iqs626->trackpad,
+ ABS_X, 0, 255, 0, 0);
+ else
+ input_set_abs_params(iqs626->trackpad,
+ ABS_X, 0, 128, 0, 0);
+
+ touchscreen_parse_properties(iqs626->trackpad, false,
+ &iqs626->prop);
+ } else {
+ for (i = 0; i < IQS626_NUM_GESTURES; i++)
+ if (iqs626->tp_code[i] != KEY_RESERVED)
+ input_set_capability(iqs626->trackpad, EV_KEY,
+ iqs626->tp_code[i]);
+ }
+
+ error = input_register_device(iqs626->trackpad);
+ if (error)
+ dev_err(&client->dev, "Failed to register trackpad: %d\n",
+ error);
+
+ return error;
+}
+
+static int iqs626_report(struct iqs626_private *iqs626)
+{
+ struct iqs626_sys_reg *sys_reg = &iqs626->sys_reg;
+ struct i2c_client *client = iqs626->client;
+ struct iqs626_flags flags;
+ __le16 hall_output;
+ int error, i, j;
+ u8 state;
+ u8 *dir_mask = &flags.states[IQS626_ST_OFFS_DIR];
+
+ error = regmap_raw_read(iqs626->regmap, IQS626_SYS_FLAGS, &flags,
+ sizeof(flags));
+ if (error) {
+ dev_err(&client->dev, "Failed to read device status: %d\n",
+ error);
+ return error;
+ }
+
+ /*
+ * The device resets itself if its own watchdog bites, which can happen
+ * in the event of an I2C communication error. In this case, the device
+ * asserts a SHOW_RESET interrupt and all registers must be restored.
+ */
+ if (be16_to_cpu(flags.system) & IQS626_SYS_FLAGS_SHOW_RESET) {
+ dev_err(&client->dev, "Unexpected device reset\n");
+
+ error = regmap_raw_write(iqs626->regmap, IQS626_SYS_SETTINGS,
+ sys_reg, sizeof(*sys_reg));
+ if (error)
+ dev_err(&client->dev,
+ "Failed to re-initialize device: %d\n", error);
+
+ return error;
+ }
+
+ if (be16_to_cpu(flags.system) & IQS626_SYS_FLAGS_IN_ATI)
+ return 0;
+
+ /*
+ * Unlike the ULP or generic channels, the Hall channel does not have a
+ * direction flag. Instead, the direction (i.e. magnet polarity) can be
+ * derived based on the sign of the 2's complement differential output.
+ */
+ if (sys_reg->active & iqs626_channels[IQS626_CH_HALL].active) {
+ error = regmap_raw_read(iqs626->regmap, IQS626_HALL_OUTPUT,
+ &hall_output, sizeof(hall_output));
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to read Hall output: %d\n", error);
+ return error;
+ }
+
+ *dir_mask &= ~iqs626_channels[IQS626_CH_HALL].active;
+ if (le16_to_cpu(hall_output) < 0x8000)
+ *dir_mask |= iqs626_channels[IQS626_CH_HALL].active;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(iqs626_channels); i++) {
+ if (!(sys_reg->active & iqs626_channels[i].active))
+ continue;
+
+ for (j = 0; j < ARRAY_SIZE(iqs626_events); j++) {
+ if (!iqs626->kp_type[i][j])
+ continue;
+
+ state = flags.states[iqs626_events[j].st_offs];
+ state &= iqs626_events[j].dir_up ? *dir_mask
+ : ~(*dir_mask);
+ state &= iqs626_channels[i].active;
+
+ input_event(iqs626->keypad, iqs626->kp_type[i][j],
+ iqs626->kp_code[i][j], !!state);
+ }
+ }
+
+ input_sync(iqs626->keypad);
+
+ /*
+ * The following completion signals that ATI has finished, any initial
+ * switch states have been reported and the keypad can be registered.
+ */
+ complete_all(&iqs626->ati_done);
+
+ if (!(sys_reg->active & iqs626_channels[IQS626_CH_TP_2].active))
+ return 0;
+
+ if (sys_reg->event_mask & IQS626_EVENT_MASK_GESTURE) {
+ state = flags.states[IQS626_ST_OFFS_TOUCH];
+ state &= iqs626_channels[IQS626_CH_TP_2].active;
+
+ input_report_key(iqs626->trackpad, BTN_TOUCH, state);
+
+ if (state)
+ touchscreen_report_pos(iqs626->trackpad, &iqs626->prop,
+ flags.trackpad_x,
+ flags.trackpad_y, false);
+ } else {
+ for (i = 0; i < IQS626_NUM_GESTURES; i++)
+ input_report_key(iqs626->trackpad, iqs626->tp_code[i],
+ flags.gesture & BIT(i));
+
+ if (flags.gesture & GENMASK(IQS626_GESTURE_TAP, 0)) {
+ input_sync(iqs626->trackpad);
+
+ /*
+ * Momentary gestures are followed by a complementary
+ * release cycle so as to emulate a full keystroke.
+ */
+ for (i = 0; i < IQS626_GESTURE_HOLD; i++)
+ input_report_key(iqs626->trackpad,
+ iqs626->tp_code[i], 0);
+ }
+ }
+
+ input_sync(iqs626->trackpad);
+
+ return 0;
+}
+
+static irqreturn_t iqs626_irq(int irq, void *context)
+{
+ struct iqs626_private *iqs626 = context;
+
+ if (iqs626_report(iqs626))
+ return IRQ_NONE;
+
+ /*
+ * The device does not deassert its interrupt (RDY) pin until shortly
+ * after receiving an I2C stop condition; the following delay ensures
+ * the interrupt handler does not return before this time.
+ */
+ iqs626_irq_wait();
+
+ return IRQ_HANDLED;
+}
+
+static const struct regmap_config iqs626_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 16,
+ .max_register = IQS626_MAX_REG,
+};
+
+static int iqs626_probe(struct i2c_client *client)
+{
+ struct iqs626_ver_info ver_info;
+ struct iqs626_private *iqs626;
+ int error;
+
+ iqs626 = devm_kzalloc(&client->dev, sizeof(*iqs626), GFP_KERNEL);
+ if (!iqs626)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, iqs626);
+ iqs626->client = client;
+
+ iqs626->regmap = devm_regmap_init_i2c(client, &iqs626_regmap_config);
+ if (IS_ERR(iqs626->regmap)) {
+ error = PTR_ERR(iqs626->regmap);
+ dev_err(&client->dev, "Failed to initialize register map: %d\n",
+ error);
+ return error;
+ }
+
+ init_completion(&iqs626->ati_done);
+
+ error = regmap_raw_read(iqs626->regmap, IQS626_VER_INFO, &ver_info,
+ sizeof(ver_info));
+ if (error)
+ return error;
+
+ if (ver_info.prod_num != IQS626_VER_INFO_PROD_NUM) {
+ dev_err(&client->dev, "Unrecognized product number: 0x%02X\n",
+ ver_info.prod_num);
+ return -EINVAL;
+ }
+
+ error = iqs626_parse_prop(iqs626);
+ if (error)
+ return error;
+
+ error = iqs626_input_init(iqs626);
+ if (error)
+ return error;
+
+ error = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, iqs626_irq, IRQF_ONESHOT,
+ client->name, iqs626);
+ if (error) {
+ dev_err(&client->dev, "Failed to request IRQ: %d\n", error);
+ return error;
+ }
+
+ if (!wait_for_completion_timeout(&iqs626->ati_done,
+ msecs_to_jiffies(2000))) {
+ dev_err(&client->dev, "Failed to complete ATI\n");
+ return -ETIMEDOUT;
+ }
+
+ /*
+ * The keypad may include one or more switches and is not registered
+ * until ATI is complete and the initial switch states are read.
+ */
+ error = input_register_device(iqs626->keypad);
+ if (error)
+ dev_err(&client->dev, "Failed to register keypad: %d\n", error);
+
+ return error;
+}
+
+static int __maybe_unused iqs626_suspend(struct device *dev)
+{
+ struct iqs626_private *iqs626 = dev_get_drvdata(dev);
+ struct i2c_client *client = iqs626->client;
+ unsigned int val;
+ int error;
+
+ if (!iqs626->suspend_mode)
+ return 0;
+
+ disable_irq(client->irq);
+
+ /*
+ * Automatic power mode switching must be disabled before the device is
+ * forced into any particular power mode. In this case, the device will
+ * transition into normal-power mode.
+ */
+ error = regmap_update_bits(iqs626->regmap, IQS626_SYS_SETTINGS,
+ IQS626_SYS_SETTINGS_DIS_AUTO, ~0);
+ if (error)
+ goto err_irq;
+
+ /*
+ * The following check ensures the device has completed its transition
+ * into normal-power mode before a manual mode switch is performed.
+ */
+ error = regmap_read_poll_timeout(iqs626->regmap, IQS626_SYS_FLAGS, val,
+ !(val & IQS626_SYS_FLAGS_PWR_MODE_MASK),
+ IQS626_PWR_MODE_POLL_SLEEP_US,
+ IQS626_PWR_MODE_POLL_TIMEOUT_US);
+ if (error)
+ goto err_irq;
+
+ error = regmap_update_bits(iqs626->regmap, IQS626_SYS_SETTINGS,
+ IQS626_SYS_SETTINGS_PWR_MODE_MASK,
+ iqs626->suspend_mode <<
+ IQS626_SYS_SETTINGS_PWR_MODE_SHIFT);
+ if (error)
+ goto err_irq;
+
+ /*
+ * This last check ensures the device has completed its transition into
+ * the desired power mode to prevent any spurious interrupts from being
+ * triggered after iqs626_suspend has already returned.
+ */
+ error = regmap_read_poll_timeout(iqs626->regmap, IQS626_SYS_FLAGS, val,
+ (val & IQS626_SYS_FLAGS_PWR_MODE_MASK)
+ == (iqs626->suspend_mode <<
+ IQS626_SYS_FLAGS_PWR_MODE_SHIFT),
+ IQS626_PWR_MODE_POLL_SLEEP_US,
+ IQS626_PWR_MODE_POLL_TIMEOUT_US);
+
+err_irq:
+ iqs626_irq_wait();
+ enable_irq(client->irq);
+
+ return error;
+}
+
+static int __maybe_unused iqs626_resume(struct device *dev)
+{
+ struct iqs626_private *iqs626 = dev_get_drvdata(dev);
+ struct i2c_client *client = iqs626->client;
+ unsigned int val;
+ int error;
+
+ if (!iqs626->suspend_mode)
+ return 0;
+
+ disable_irq(client->irq);
+
+ error = regmap_update_bits(iqs626->regmap, IQS626_SYS_SETTINGS,
+ IQS626_SYS_SETTINGS_PWR_MODE_MASK, 0);
+ if (error)
+ goto err_irq;
+
+ /*
+ * This check ensures the device has returned to normal-power mode
+ * before automatic power mode switching is re-enabled.
+ */
+ error = regmap_read_poll_timeout(iqs626->regmap, IQS626_SYS_FLAGS, val,
+ !(val & IQS626_SYS_FLAGS_PWR_MODE_MASK),
+ IQS626_PWR_MODE_POLL_SLEEP_US,
+ IQS626_PWR_MODE_POLL_TIMEOUT_US);
+ if (error)
+ goto err_irq;
+
+ error = regmap_update_bits(iqs626->regmap, IQS626_SYS_SETTINGS,
+ IQS626_SYS_SETTINGS_DIS_AUTO, 0);
+ if (error)
+ goto err_irq;
+
+ /*
+ * This step reports any events that may have been "swallowed" as a
+ * result of polling PWR_MODE (which automatically acknowledges any
+ * pending interrupts).
+ */
+ error = iqs626_report(iqs626);
+
+err_irq:
+ iqs626_irq_wait();
+ enable_irq(client->irq);
+
+ return error;
+}
+
+static SIMPLE_DEV_PM_OPS(iqs626_pm, iqs626_suspend, iqs626_resume);
+
+static const struct of_device_id iqs626_of_match[] = {
+ { .compatible = "azoteq,iqs626a" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, iqs626_of_match);
+
+static struct i2c_driver iqs626_i2c_driver = {
+ .driver = {
+ .name = "iqs626a",
+ .of_match_table = iqs626_of_match,
+ .pm = &iqs626_pm,
+ },
+ .probe_new = iqs626_probe,
+};
+module_i2c_driver(iqs626_i2c_driver);
+
+MODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>");
+MODULE_DESCRIPTION("Azoteq IQS626A Capacitive Touch Controller");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/iqs7222.c b/drivers/input/misc/iqs7222.c
new file mode 100644
index 000000000..f24b174c7
--- /dev/null
+++ b/drivers/input/misc/iqs7222.c
@@ -0,0 +1,2602 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Azoteq IQS7222A/B/C Capacitive Touch Controller
+ *
+ * Copyright (C) 2022 Jeff LaBundy <jeff@labundy.com>
+ */
+
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/ktime.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+#include <asm/unaligned.h>
+
+#define IQS7222_PROD_NUM 0x00
+#define IQS7222_PROD_NUM_A 840
+#define IQS7222_PROD_NUM_B 698
+#define IQS7222_PROD_NUM_C 863
+
+#define IQS7222_SYS_STATUS 0x10
+#define IQS7222_SYS_STATUS_RESET BIT(3)
+#define IQS7222_SYS_STATUS_ATI_ERROR BIT(1)
+#define IQS7222_SYS_STATUS_ATI_ACTIVE BIT(0)
+
+#define IQS7222_CHAN_SETUP_0_REF_MODE_MASK GENMASK(15, 14)
+#define IQS7222_CHAN_SETUP_0_REF_MODE_FOLLOW BIT(15)
+#define IQS7222_CHAN_SETUP_0_REF_MODE_REF BIT(14)
+#define IQS7222_CHAN_SETUP_0_CHAN_EN BIT(8)
+
+#define IQS7222_SLDR_SETUP_0_CHAN_CNT_MASK GENMASK(2, 0)
+#define IQS7222_SLDR_SETUP_2_RES_MASK GENMASK(15, 8)
+#define IQS7222_SLDR_SETUP_2_RES_SHIFT 8
+#define IQS7222_SLDR_SETUP_2_TOP_SPEED_MASK GENMASK(7, 0)
+
+#define IQS7222_GPIO_SETUP_0_GPIO_EN BIT(0)
+
+#define IQS7222_SYS_SETUP 0xD0
+#define IQS7222_SYS_SETUP_INTF_MODE_MASK GENMASK(7, 6)
+#define IQS7222_SYS_SETUP_INTF_MODE_TOUCH BIT(7)
+#define IQS7222_SYS_SETUP_INTF_MODE_EVENT BIT(6)
+#define IQS7222_SYS_SETUP_PWR_MODE_MASK GENMASK(5, 4)
+#define IQS7222_SYS_SETUP_PWR_MODE_AUTO IQS7222_SYS_SETUP_PWR_MODE_MASK
+#define IQS7222_SYS_SETUP_REDO_ATI BIT(2)
+#define IQS7222_SYS_SETUP_ACK_RESET BIT(0)
+
+#define IQS7222_EVENT_MASK_ATI BIT(12)
+#define IQS7222_EVENT_MASK_SLDR BIT(10)
+#define IQS7222_EVENT_MASK_TOUCH BIT(1)
+#define IQS7222_EVENT_MASK_PROX BIT(0)
+
+#define IQS7222_COMMS_HOLD BIT(0)
+#define IQS7222_COMMS_ERROR 0xEEEE
+#define IQS7222_COMMS_RETRY_MS 50
+#define IQS7222_COMMS_TIMEOUT_MS 100
+#define IQS7222_RESET_TIMEOUT_MS 250
+#define IQS7222_ATI_TIMEOUT_MS 2000
+
+#define IQS7222_MAX_COLS_STAT 8
+#define IQS7222_MAX_COLS_CYCLE 3
+#define IQS7222_MAX_COLS_GLBL 3
+#define IQS7222_MAX_COLS_BTN 3
+#define IQS7222_MAX_COLS_CHAN 6
+#define IQS7222_MAX_COLS_FILT 2
+#define IQS7222_MAX_COLS_SLDR 11
+#define IQS7222_MAX_COLS_GPIO 3
+#define IQS7222_MAX_COLS_SYS 13
+
+#define IQS7222_MAX_CHAN 20
+#define IQS7222_MAX_SLDR 2
+
+#define IQS7222_NUM_RETRIES 5
+#define IQS7222_REG_OFFSET 0x100
+
+enum iqs7222_reg_key_id {
+ IQS7222_REG_KEY_NONE,
+ IQS7222_REG_KEY_PROX,
+ IQS7222_REG_KEY_TOUCH,
+ IQS7222_REG_KEY_DEBOUNCE,
+ IQS7222_REG_KEY_TAP,
+ IQS7222_REG_KEY_TAP_LEGACY,
+ IQS7222_REG_KEY_AXIAL,
+ IQS7222_REG_KEY_AXIAL_LEGACY,
+ IQS7222_REG_KEY_WHEEL,
+ IQS7222_REG_KEY_NO_WHEEL,
+ IQS7222_REG_KEY_RESERVED
+};
+
+enum iqs7222_reg_grp_id {
+ IQS7222_REG_GRP_STAT,
+ IQS7222_REG_GRP_FILT,
+ IQS7222_REG_GRP_CYCLE,
+ IQS7222_REG_GRP_GLBL,
+ IQS7222_REG_GRP_BTN,
+ IQS7222_REG_GRP_CHAN,
+ IQS7222_REG_GRP_SLDR,
+ IQS7222_REG_GRP_GPIO,
+ IQS7222_REG_GRP_SYS,
+ IQS7222_NUM_REG_GRPS
+};
+
+static const char * const iqs7222_reg_grp_names[IQS7222_NUM_REG_GRPS] = {
+ [IQS7222_REG_GRP_CYCLE] = "cycle",
+ [IQS7222_REG_GRP_CHAN] = "channel",
+ [IQS7222_REG_GRP_SLDR] = "slider",
+ [IQS7222_REG_GRP_GPIO] = "gpio",
+};
+
+static const unsigned int iqs7222_max_cols[IQS7222_NUM_REG_GRPS] = {
+ [IQS7222_REG_GRP_STAT] = IQS7222_MAX_COLS_STAT,
+ [IQS7222_REG_GRP_CYCLE] = IQS7222_MAX_COLS_CYCLE,
+ [IQS7222_REG_GRP_GLBL] = IQS7222_MAX_COLS_GLBL,
+ [IQS7222_REG_GRP_BTN] = IQS7222_MAX_COLS_BTN,
+ [IQS7222_REG_GRP_CHAN] = IQS7222_MAX_COLS_CHAN,
+ [IQS7222_REG_GRP_FILT] = IQS7222_MAX_COLS_FILT,
+ [IQS7222_REG_GRP_SLDR] = IQS7222_MAX_COLS_SLDR,
+ [IQS7222_REG_GRP_GPIO] = IQS7222_MAX_COLS_GPIO,
+ [IQS7222_REG_GRP_SYS] = IQS7222_MAX_COLS_SYS,
+};
+
+static const unsigned int iqs7222_gpio_links[] = { 2, 5, 6, };
+
+struct iqs7222_event_desc {
+ const char *name;
+ u16 mask;
+ u16 val;
+ u16 enable;
+ enum iqs7222_reg_key_id reg_key;
+};
+
+static const struct iqs7222_event_desc iqs7222_kp_events[] = {
+ {
+ .name = "event-prox",
+ .enable = IQS7222_EVENT_MASK_PROX,
+ .reg_key = IQS7222_REG_KEY_PROX,
+ },
+ {
+ .name = "event-touch",
+ .enable = IQS7222_EVENT_MASK_TOUCH,
+ .reg_key = IQS7222_REG_KEY_TOUCH,
+ },
+};
+
+static const struct iqs7222_event_desc iqs7222_sl_events[] = {
+ { .name = "event-press", },
+ {
+ .name = "event-tap",
+ .mask = BIT(0),
+ .val = BIT(0),
+ .enable = BIT(0),
+ .reg_key = IQS7222_REG_KEY_TAP,
+ },
+ {
+ .name = "event-swipe-pos",
+ .mask = BIT(5) | BIT(1),
+ .val = BIT(1),
+ .enable = BIT(1),
+ .reg_key = IQS7222_REG_KEY_AXIAL,
+ },
+ {
+ .name = "event-swipe-neg",
+ .mask = BIT(5) | BIT(1),
+ .val = BIT(5) | BIT(1),
+ .enable = BIT(1),
+ .reg_key = IQS7222_REG_KEY_AXIAL,
+ },
+ {
+ .name = "event-flick-pos",
+ .mask = BIT(5) | BIT(2),
+ .val = BIT(2),
+ .enable = BIT(2),
+ .reg_key = IQS7222_REG_KEY_AXIAL,
+ },
+ {
+ .name = "event-flick-neg",
+ .mask = BIT(5) | BIT(2),
+ .val = BIT(5) | BIT(2),
+ .enable = BIT(2),
+ .reg_key = IQS7222_REG_KEY_AXIAL,
+ },
+};
+
+struct iqs7222_reg_grp_desc {
+ u16 base;
+ int num_row;
+ int num_col;
+};
+
+struct iqs7222_dev_desc {
+ u16 prod_num;
+ u16 fw_major;
+ u16 fw_minor;
+ u16 sldr_res;
+ u16 touch_link;
+ u16 wheel_enable;
+ int allow_offset;
+ int event_offset;
+ int comms_offset;
+ bool legacy_gesture;
+ struct iqs7222_reg_grp_desc reg_grps[IQS7222_NUM_REG_GRPS];
+};
+
+static const struct iqs7222_dev_desc iqs7222_devs[] = {
+ {
+ .prod_num = IQS7222_PROD_NUM_A,
+ .fw_major = 1,
+ .fw_minor = 13,
+ .sldr_res = U8_MAX * 16,
+ .touch_link = 1768,
+ .allow_offset = 9,
+ .event_offset = 10,
+ .comms_offset = 12,
+ .reg_grps = {
+ [IQS7222_REG_GRP_STAT] = {
+ .base = IQS7222_SYS_STATUS,
+ .num_row = 1,
+ .num_col = 8,
+ },
+ [IQS7222_REG_GRP_CYCLE] = {
+ .base = 0x8000,
+ .num_row = 7,
+ .num_col = 3,
+ },
+ [IQS7222_REG_GRP_GLBL] = {
+ .base = 0x8700,
+ .num_row = 1,
+ .num_col = 3,
+ },
+ [IQS7222_REG_GRP_BTN] = {
+ .base = 0x9000,
+ .num_row = 12,
+ .num_col = 3,
+ },
+ [IQS7222_REG_GRP_CHAN] = {
+ .base = 0xA000,
+ .num_row = 12,
+ .num_col = 6,
+ },
+ [IQS7222_REG_GRP_FILT] = {
+ .base = 0xAC00,
+ .num_row = 1,
+ .num_col = 2,
+ },
+ [IQS7222_REG_GRP_SLDR] = {
+ .base = 0xB000,
+ .num_row = 2,
+ .num_col = 11,
+ },
+ [IQS7222_REG_GRP_GPIO] = {
+ .base = 0xC000,
+ .num_row = 1,
+ .num_col = 3,
+ },
+ [IQS7222_REG_GRP_SYS] = {
+ .base = IQS7222_SYS_SETUP,
+ .num_row = 1,
+ .num_col = 13,
+ },
+ },
+ },
+ {
+ .prod_num = IQS7222_PROD_NUM_A,
+ .fw_major = 1,
+ .fw_minor = 12,
+ .sldr_res = U8_MAX * 16,
+ .touch_link = 1768,
+ .allow_offset = 9,
+ .event_offset = 10,
+ .comms_offset = 12,
+ .legacy_gesture = true,
+ .reg_grps = {
+ [IQS7222_REG_GRP_STAT] = {
+ .base = IQS7222_SYS_STATUS,
+ .num_row = 1,
+ .num_col = 8,
+ },
+ [IQS7222_REG_GRP_CYCLE] = {
+ .base = 0x8000,
+ .num_row = 7,
+ .num_col = 3,
+ },
+ [IQS7222_REG_GRP_GLBL] = {
+ .base = 0x8700,
+ .num_row = 1,
+ .num_col = 3,
+ },
+ [IQS7222_REG_GRP_BTN] = {
+ .base = 0x9000,
+ .num_row = 12,
+ .num_col = 3,
+ },
+ [IQS7222_REG_GRP_CHAN] = {
+ .base = 0xA000,
+ .num_row = 12,
+ .num_col = 6,
+ },
+ [IQS7222_REG_GRP_FILT] = {
+ .base = 0xAC00,
+ .num_row = 1,
+ .num_col = 2,
+ },
+ [IQS7222_REG_GRP_SLDR] = {
+ .base = 0xB000,
+ .num_row = 2,
+ .num_col = 11,
+ },
+ [IQS7222_REG_GRP_GPIO] = {
+ .base = 0xC000,
+ .num_row = 1,
+ .num_col = 3,
+ },
+ [IQS7222_REG_GRP_SYS] = {
+ .base = IQS7222_SYS_SETUP,
+ .num_row = 1,
+ .num_col = 13,
+ },
+ },
+ },
+ {
+ .prod_num = IQS7222_PROD_NUM_B,
+ .fw_major = 1,
+ .fw_minor = 43,
+ .event_offset = 10,
+ .comms_offset = 11,
+ .reg_grps = {
+ [IQS7222_REG_GRP_STAT] = {
+ .base = IQS7222_SYS_STATUS,
+ .num_row = 1,
+ .num_col = 6,
+ },
+ [IQS7222_REG_GRP_CYCLE] = {
+ .base = 0x8000,
+ .num_row = 10,
+ .num_col = 2,
+ },
+ [IQS7222_REG_GRP_GLBL] = {
+ .base = 0x8A00,
+ .num_row = 1,
+ .num_col = 3,
+ },
+ [IQS7222_REG_GRP_BTN] = {
+ .base = 0x9000,
+ .num_row = 20,
+ .num_col = 2,
+ },
+ [IQS7222_REG_GRP_CHAN] = {
+ .base = 0xB000,
+ .num_row = 20,
+ .num_col = 4,
+ },
+ [IQS7222_REG_GRP_FILT] = {
+ .base = 0xC400,
+ .num_row = 1,
+ .num_col = 2,
+ },
+ [IQS7222_REG_GRP_SYS] = {
+ .base = IQS7222_SYS_SETUP,
+ .num_row = 1,
+ .num_col = 13,
+ },
+ },
+ },
+ {
+ .prod_num = IQS7222_PROD_NUM_B,
+ .fw_major = 1,
+ .fw_minor = 27,
+ .reg_grps = {
+ [IQS7222_REG_GRP_STAT] = {
+ .base = IQS7222_SYS_STATUS,
+ .num_row = 1,
+ .num_col = 6,
+ },
+ [IQS7222_REG_GRP_CYCLE] = {
+ .base = 0x8000,
+ .num_row = 10,
+ .num_col = 2,
+ },
+ [IQS7222_REG_GRP_GLBL] = {
+ .base = 0x8A00,
+ .num_row = 1,
+ .num_col = 3,
+ },
+ [IQS7222_REG_GRP_BTN] = {
+ .base = 0x9000,
+ .num_row = 20,
+ .num_col = 2,
+ },
+ [IQS7222_REG_GRP_CHAN] = {
+ .base = 0xB000,
+ .num_row = 20,
+ .num_col = 4,
+ },
+ [IQS7222_REG_GRP_FILT] = {
+ .base = 0xC400,
+ .num_row = 1,
+ .num_col = 2,
+ },
+ [IQS7222_REG_GRP_SYS] = {
+ .base = IQS7222_SYS_SETUP,
+ .num_row = 1,
+ .num_col = 10,
+ },
+ },
+ },
+ {
+ .prod_num = IQS7222_PROD_NUM_C,
+ .fw_major = 2,
+ .fw_minor = 6,
+ .sldr_res = U16_MAX,
+ .touch_link = 1686,
+ .wheel_enable = BIT(3),
+ .event_offset = 9,
+ .comms_offset = 10,
+ .reg_grps = {
+ [IQS7222_REG_GRP_STAT] = {
+ .base = IQS7222_SYS_STATUS,
+ .num_row = 1,
+ .num_col = 6,
+ },
+ [IQS7222_REG_GRP_CYCLE] = {
+ .base = 0x8000,
+ .num_row = 5,
+ .num_col = 3,
+ },
+ [IQS7222_REG_GRP_GLBL] = {
+ .base = 0x8500,
+ .num_row = 1,
+ .num_col = 3,
+ },
+ [IQS7222_REG_GRP_BTN] = {
+ .base = 0x9000,
+ .num_row = 10,
+ .num_col = 3,
+ },
+ [IQS7222_REG_GRP_CHAN] = {
+ .base = 0xA000,
+ .num_row = 10,
+ .num_col = 6,
+ },
+ [IQS7222_REG_GRP_FILT] = {
+ .base = 0xAA00,
+ .num_row = 1,
+ .num_col = 2,
+ },
+ [IQS7222_REG_GRP_SLDR] = {
+ .base = 0xB000,
+ .num_row = 2,
+ .num_col = 10,
+ },
+ [IQS7222_REG_GRP_GPIO] = {
+ .base = 0xC000,
+ .num_row = 3,
+ .num_col = 3,
+ },
+ [IQS7222_REG_GRP_SYS] = {
+ .base = IQS7222_SYS_SETUP,
+ .num_row = 1,
+ .num_col = 12,
+ },
+ },
+ },
+ {
+ .prod_num = IQS7222_PROD_NUM_C,
+ .fw_major = 1,
+ .fw_minor = 13,
+ .sldr_res = U16_MAX,
+ .touch_link = 1674,
+ .wheel_enable = BIT(3),
+ .event_offset = 9,
+ .comms_offset = 10,
+ .reg_grps = {
+ [IQS7222_REG_GRP_STAT] = {
+ .base = IQS7222_SYS_STATUS,
+ .num_row = 1,
+ .num_col = 6,
+ },
+ [IQS7222_REG_GRP_CYCLE] = {
+ .base = 0x8000,
+ .num_row = 5,
+ .num_col = 3,
+ },
+ [IQS7222_REG_GRP_GLBL] = {
+ .base = 0x8500,
+ .num_row = 1,
+ .num_col = 3,
+ },
+ [IQS7222_REG_GRP_BTN] = {
+ .base = 0x9000,
+ .num_row = 10,
+ .num_col = 3,
+ },
+ [IQS7222_REG_GRP_CHAN] = {
+ .base = 0xA000,
+ .num_row = 10,
+ .num_col = 6,
+ },
+ [IQS7222_REG_GRP_FILT] = {
+ .base = 0xAA00,
+ .num_row = 1,
+ .num_col = 2,
+ },
+ [IQS7222_REG_GRP_SLDR] = {
+ .base = 0xB000,
+ .num_row = 2,
+ .num_col = 10,
+ },
+ [IQS7222_REG_GRP_GPIO] = {
+ .base = 0xC000,
+ .num_row = 1,
+ .num_col = 3,
+ },
+ [IQS7222_REG_GRP_SYS] = {
+ .base = IQS7222_SYS_SETUP,
+ .num_row = 1,
+ .num_col = 11,
+ },
+ },
+ },
+};
+
+struct iqs7222_prop_desc {
+ const char *name;
+ enum iqs7222_reg_grp_id reg_grp;
+ enum iqs7222_reg_key_id reg_key;
+ int reg_offset;
+ int reg_shift;
+ int reg_width;
+ int val_pitch;
+ int val_min;
+ int val_max;
+ bool invert;
+ const char *label;
+};
+
+static const struct iqs7222_prop_desc iqs7222_props[] = {
+ {
+ .name = "azoteq,conv-period",
+ .reg_grp = IQS7222_REG_GRP_CYCLE,
+ .reg_offset = 0,
+ .reg_shift = 8,
+ .reg_width = 8,
+ .label = "conversion period",
+ },
+ {
+ .name = "azoteq,conv-frac",
+ .reg_grp = IQS7222_REG_GRP_CYCLE,
+ .reg_offset = 0,
+ .reg_shift = 0,
+ .reg_width = 8,
+ .label = "conversion frequency fractional divider",
+ },
+ {
+ .name = "azoteq,rx-float-inactive",
+ .reg_grp = IQS7222_REG_GRP_CYCLE,
+ .reg_offset = 1,
+ .reg_shift = 6,
+ .reg_width = 1,
+ .invert = true,
+ },
+ {
+ .name = "azoteq,dead-time-enable",
+ .reg_grp = IQS7222_REG_GRP_CYCLE,
+ .reg_offset = 1,
+ .reg_shift = 5,
+ .reg_width = 1,
+ },
+ {
+ .name = "azoteq,tx-freq-fosc",
+ .reg_grp = IQS7222_REG_GRP_CYCLE,
+ .reg_offset = 1,
+ .reg_shift = 4,
+ .reg_width = 1,
+ },
+ {
+ .name = "azoteq,vbias-enable",
+ .reg_grp = IQS7222_REG_GRP_CYCLE,
+ .reg_offset = 1,
+ .reg_shift = 3,
+ .reg_width = 1,
+ },
+ {
+ .name = "azoteq,sense-mode",
+ .reg_grp = IQS7222_REG_GRP_CYCLE,
+ .reg_offset = 1,
+ .reg_shift = 0,
+ .reg_width = 3,
+ .val_max = 3,
+ .label = "sensing mode",
+ },
+ {
+ .name = "azoteq,iref-enable",
+ .reg_grp = IQS7222_REG_GRP_CYCLE,
+ .reg_offset = 2,
+ .reg_shift = 10,
+ .reg_width = 1,
+ },
+ {
+ .name = "azoteq,iref-level",
+ .reg_grp = IQS7222_REG_GRP_CYCLE,
+ .reg_offset = 2,
+ .reg_shift = 4,
+ .reg_width = 4,
+ .label = "current reference level",
+ },
+ {
+ .name = "azoteq,iref-trim",
+ .reg_grp = IQS7222_REG_GRP_CYCLE,
+ .reg_offset = 2,
+ .reg_shift = 0,
+ .reg_width = 4,
+ .label = "current reference trim",
+ },
+ {
+ .name = "azoteq,max-counts",
+ .reg_grp = IQS7222_REG_GRP_GLBL,
+ .reg_offset = 0,
+ .reg_shift = 13,
+ .reg_width = 2,
+ .label = "maximum counts",
+ },
+ {
+ .name = "azoteq,auto-mode",
+ .reg_grp = IQS7222_REG_GRP_GLBL,
+ .reg_offset = 0,
+ .reg_shift = 2,
+ .reg_width = 2,
+ .label = "number of conversions",
+ },
+ {
+ .name = "azoteq,ati-frac-div-fine",
+ .reg_grp = IQS7222_REG_GRP_GLBL,
+ .reg_offset = 1,
+ .reg_shift = 9,
+ .reg_width = 5,
+ .label = "ATI fine fractional divider",
+ },
+ {
+ .name = "azoteq,ati-frac-div-coarse",
+ .reg_grp = IQS7222_REG_GRP_GLBL,
+ .reg_offset = 1,
+ .reg_shift = 0,
+ .reg_width = 5,
+ .label = "ATI coarse fractional divider",
+ },
+ {
+ .name = "azoteq,ati-comp-select",
+ .reg_grp = IQS7222_REG_GRP_GLBL,
+ .reg_offset = 2,
+ .reg_shift = 0,
+ .reg_width = 10,
+ .label = "ATI compensation selection",
+ },
+ {
+ .name = "azoteq,ati-band",
+ .reg_grp = IQS7222_REG_GRP_CHAN,
+ .reg_offset = 0,
+ .reg_shift = 12,
+ .reg_width = 2,
+ .label = "ATI band",
+ },
+ {
+ .name = "azoteq,global-halt",
+ .reg_grp = IQS7222_REG_GRP_CHAN,
+ .reg_offset = 0,
+ .reg_shift = 11,
+ .reg_width = 1,
+ },
+ {
+ .name = "azoteq,invert-enable",
+ .reg_grp = IQS7222_REG_GRP_CHAN,
+ .reg_offset = 0,
+ .reg_shift = 10,
+ .reg_width = 1,
+ },
+ {
+ .name = "azoteq,dual-direction",
+ .reg_grp = IQS7222_REG_GRP_CHAN,
+ .reg_offset = 0,
+ .reg_shift = 9,
+ .reg_width = 1,
+ },
+ {
+ .name = "azoteq,samp-cap-double",
+ .reg_grp = IQS7222_REG_GRP_CHAN,
+ .reg_offset = 0,
+ .reg_shift = 3,
+ .reg_width = 1,
+ },
+ {
+ .name = "azoteq,vref-half",
+ .reg_grp = IQS7222_REG_GRP_CHAN,
+ .reg_offset = 0,
+ .reg_shift = 2,
+ .reg_width = 1,
+ },
+ {
+ .name = "azoteq,proj-bias",
+ .reg_grp = IQS7222_REG_GRP_CHAN,
+ .reg_offset = 0,
+ .reg_shift = 0,
+ .reg_width = 2,
+ .label = "projected bias current",
+ },
+ {
+ .name = "azoteq,ati-target",
+ .reg_grp = IQS7222_REG_GRP_CHAN,
+ .reg_offset = 1,
+ .reg_shift = 8,
+ .reg_width = 8,
+ .val_pitch = 8,
+ .label = "ATI target",
+ },
+ {
+ .name = "azoteq,ati-base",
+ .reg_grp = IQS7222_REG_GRP_CHAN,
+ .reg_offset = 1,
+ .reg_shift = 3,
+ .reg_width = 5,
+ .val_pitch = 16,
+ .label = "ATI base",
+ },
+ {
+ .name = "azoteq,ati-mode",
+ .reg_grp = IQS7222_REG_GRP_CHAN,
+ .reg_offset = 1,
+ .reg_shift = 0,
+ .reg_width = 3,
+ .val_max = 5,
+ .label = "ATI mode",
+ },
+ {
+ .name = "azoteq,ati-frac-div-fine",
+ .reg_grp = IQS7222_REG_GRP_CHAN,
+ .reg_offset = 2,
+ .reg_shift = 9,
+ .reg_width = 5,
+ .label = "ATI fine fractional divider",
+ },
+ {
+ .name = "azoteq,ati-frac-mult-coarse",
+ .reg_grp = IQS7222_REG_GRP_CHAN,
+ .reg_offset = 2,
+ .reg_shift = 5,
+ .reg_width = 4,
+ .label = "ATI coarse fractional multiplier",
+ },
+ {
+ .name = "azoteq,ati-frac-div-coarse",
+ .reg_grp = IQS7222_REG_GRP_CHAN,
+ .reg_offset = 2,
+ .reg_shift = 0,
+ .reg_width = 5,
+ .label = "ATI coarse fractional divider",
+ },
+ {
+ .name = "azoteq,ati-comp-div",
+ .reg_grp = IQS7222_REG_GRP_CHAN,
+ .reg_offset = 3,
+ .reg_shift = 11,
+ .reg_width = 5,
+ .label = "ATI compensation divider",
+ },
+ {
+ .name = "azoteq,ati-comp-select",
+ .reg_grp = IQS7222_REG_GRP_CHAN,
+ .reg_offset = 3,
+ .reg_shift = 0,
+ .reg_width = 10,
+ .label = "ATI compensation selection",
+ },
+ {
+ .name = "azoteq,debounce-exit",
+ .reg_grp = IQS7222_REG_GRP_BTN,
+ .reg_key = IQS7222_REG_KEY_DEBOUNCE,
+ .reg_offset = 0,
+ .reg_shift = 12,
+ .reg_width = 4,
+ .label = "debounce exit factor",
+ },
+ {
+ .name = "azoteq,debounce-enter",
+ .reg_grp = IQS7222_REG_GRP_BTN,
+ .reg_key = IQS7222_REG_KEY_DEBOUNCE,
+ .reg_offset = 0,
+ .reg_shift = 8,
+ .reg_width = 4,
+ .label = "debounce entrance factor",
+ },
+ {
+ .name = "azoteq,thresh",
+ .reg_grp = IQS7222_REG_GRP_BTN,
+ .reg_key = IQS7222_REG_KEY_PROX,
+ .reg_offset = 0,
+ .reg_shift = 0,
+ .reg_width = 8,
+ .val_max = 127,
+ .label = "threshold",
+ },
+ {
+ .name = "azoteq,thresh",
+ .reg_grp = IQS7222_REG_GRP_BTN,
+ .reg_key = IQS7222_REG_KEY_TOUCH,
+ .reg_offset = 1,
+ .reg_shift = 0,
+ .reg_width = 8,
+ .label = "threshold",
+ },
+ {
+ .name = "azoteq,hyst",
+ .reg_grp = IQS7222_REG_GRP_BTN,
+ .reg_key = IQS7222_REG_KEY_TOUCH,
+ .reg_offset = 1,
+ .reg_shift = 8,
+ .reg_width = 8,
+ .label = "hysteresis",
+ },
+ {
+ .name = "azoteq,lta-beta-lp",
+ .reg_grp = IQS7222_REG_GRP_FILT,
+ .reg_offset = 0,
+ .reg_shift = 12,
+ .reg_width = 4,
+ .label = "low-power mode long-term average beta",
+ },
+ {
+ .name = "azoteq,lta-beta-np",
+ .reg_grp = IQS7222_REG_GRP_FILT,
+ .reg_offset = 0,
+ .reg_shift = 8,
+ .reg_width = 4,
+ .label = "normal-power mode long-term average beta",
+ },
+ {
+ .name = "azoteq,counts-beta-lp",
+ .reg_grp = IQS7222_REG_GRP_FILT,
+ .reg_offset = 0,
+ .reg_shift = 4,
+ .reg_width = 4,
+ .label = "low-power mode counts beta",
+ },
+ {
+ .name = "azoteq,counts-beta-np",
+ .reg_grp = IQS7222_REG_GRP_FILT,
+ .reg_offset = 0,
+ .reg_shift = 0,
+ .reg_width = 4,
+ .label = "normal-power mode counts beta",
+ },
+ {
+ .name = "azoteq,lta-fast-beta-lp",
+ .reg_grp = IQS7222_REG_GRP_FILT,
+ .reg_offset = 1,
+ .reg_shift = 4,
+ .reg_width = 4,
+ .label = "low-power mode long-term average fast beta",
+ },
+ {
+ .name = "azoteq,lta-fast-beta-np",
+ .reg_grp = IQS7222_REG_GRP_FILT,
+ .reg_offset = 1,
+ .reg_shift = 0,
+ .reg_width = 4,
+ .label = "normal-power mode long-term average fast beta",
+ },
+ {
+ .name = "azoteq,lower-cal",
+ .reg_grp = IQS7222_REG_GRP_SLDR,
+ .reg_offset = 0,
+ .reg_shift = 8,
+ .reg_width = 8,
+ .label = "lower calibration",
+ },
+ {
+ .name = "azoteq,static-beta",
+ .reg_grp = IQS7222_REG_GRP_SLDR,
+ .reg_key = IQS7222_REG_KEY_NO_WHEEL,
+ .reg_offset = 0,
+ .reg_shift = 6,
+ .reg_width = 1,
+ },
+ {
+ .name = "azoteq,bottom-beta",
+ .reg_grp = IQS7222_REG_GRP_SLDR,
+ .reg_key = IQS7222_REG_KEY_NO_WHEEL,
+ .reg_offset = 0,
+ .reg_shift = 3,
+ .reg_width = 3,
+ .label = "bottom beta",
+ },
+ {
+ .name = "azoteq,static-beta",
+ .reg_grp = IQS7222_REG_GRP_SLDR,
+ .reg_key = IQS7222_REG_KEY_WHEEL,
+ .reg_offset = 0,
+ .reg_shift = 7,
+ .reg_width = 1,
+ },
+ {
+ .name = "azoteq,bottom-beta",
+ .reg_grp = IQS7222_REG_GRP_SLDR,
+ .reg_key = IQS7222_REG_KEY_WHEEL,
+ .reg_offset = 0,
+ .reg_shift = 4,
+ .reg_width = 3,
+ .label = "bottom beta",
+ },
+ {
+ .name = "azoteq,bottom-speed",
+ .reg_grp = IQS7222_REG_GRP_SLDR,
+ .reg_offset = 1,
+ .reg_shift = 8,
+ .reg_width = 8,
+ .label = "bottom speed",
+ },
+ {
+ .name = "azoteq,upper-cal",
+ .reg_grp = IQS7222_REG_GRP_SLDR,
+ .reg_offset = 1,
+ .reg_shift = 0,
+ .reg_width = 8,
+ .label = "upper calibration",
+ },
+ {
+ .name = "azoteq,gesture-max-ms",
+ .reg_grp = IQS7222_REG_GRP_SLDR,
+ .reg_key = IQS7222_REG_KEY_TAP,
+ .reg_offset = 9,
+ .reg_shift = 8,
+ .reg_width = 8,
+ .val_pitch = 16,
+ .label = "maximum gesture time",
+ },
+ {
+ .name = "azoteq,gesture-max-ms",
+ .reg_grp = IQS7222_REG_GRP_SLDR,
+ .reg_key = IQS7222_REG_KEY_TAP_LEGACY,
+ .reg_offset = 9,
+ .reg_shift = 8,
+ .reg_width = 8,
+ .val_pitch = 4,
+ .label = "maximum gesture time",
+ },
+ {
+ .name = "azoteq,gesture-min-ms",
+ .reg_grp = IQS7222_REG_GRP_SLDR,
+ .reg_key = IQS7222_REG_KEY_TAP,
+ .reg_offset = 9,
+ .reg_shift = 3,
+ .reg_width = 5,
+ .val_pitch = 16,
+ .label = "minimum gesture time",
+ },
+ {
+ .name = "azoteq,gesture-min-ms",
+ .reg_grp = IQS7222_REG_GRP_SLDR,
+ .reg_key = IQS7222_REG_KEY_TAP_LEGACY,
+ .reg_offset = 9,
+ .reg_shift = 3,
+ .reg_width = 5,
+ .val_pitch = 4,
+ .label = "minimum gesture time",
+ },
+ {
+ .name = "azoteq,gesture-dist",
+ .reg_grp = IQS7222_REG_GRP_SLDR,
+ .reg_key = IQS7222_REG_KEY_AXIAL,
+ .reg_offset = 10,
+ .reg_shift = 8,
+ .reg_width = 8,
+ .val_pitch = 16,
+ .label = "gesture distance",
+ },
+ {
+ .name = "azoteq,gesture-dist",
+ .reg_grp = IQS7222_REG_GRP_SLDR,
+ .reg_key = IQS7222_REG_KEY_AXIAL_LEGACY,
+ .reg_offset = 10,
+ .reg_shift = 8,
+ .reg_width = 8,
+ .val_pitch = 16,
+ .label = "gesture distance",
+ },
+ {
+ .name = "azoteq,gesture-max-ms",
+ .reg_grp = IQS7222_REG_GRP_SLDR,
+ .reg_key = IQS7222_REG_KEY_AXIAL,
+ .reg_offset = 10,
+ .reg_shift = 0,
+ .reg_width = 8,
+ .val_pitch = 16,
+ .label = "maximum gesture time",
+ },
+ {
+ .name = "azoteq,gesture-max-ms",
+ .reg_grp = IQS7222_REG_GRP_SLDR,
+ .reg_key = IQS7222_REG_KEY_AXIAL_LEGACY,
+ .reg_offset = 10,
+ .reg_shift = 0,
+ .reg_width = 8,
+ .val_pitch = 4,
+ .label = "maximum gesture time",
+ },
+ {
+ .name = "drive-open-drain",
+ .reg_grp = IQS7222_REG_GRP_GPIO,
+ .reg_offset = 0,
+ .reg_shift = 1,
+ .reg_width = 1,
+ },
+ {
+ .name = "azoteq,timeout-ati-ms",
+ .reg_grp = IQS7222_REG_GRP_SYS,
+ .reg_offset = 1,
+ .reg_shift = 0,
+ .reg_width = 16,
+ .val_pitch = 500,
+ .label = "ATI error timeout",
+ },
+ {
+ .name = "azoteq,rate-ati-ms",
+ .reg_grp = IQS7222_REG_GRP_SYS,
+ .reg_offset = 2,
+ .reg_shift = 0,
+ .reg_width = 16,
+ .label = "ATI report rate",
+ },
+ {
+ .name = "azoteq,timeout-np-ms",
+ .reg_grp = IQS7222_REG_GRP_SYS,
+ .reg_offset = 3,
+ .reg_shift = 0,
+ .reg_width = 16,
+ .label = "normal-power mode timeout",
+ },
+ {
+ .name = "azoteq,rate-np-ms",
+ .reg_grp = IQS7222_REG_GRP_SYS,
+ .reg_offset = 4,
+ .reg_shift = 0,
+ .reg_width = 16,
+ .val_max = 3000,
+ .label = "normal-power mode report rate",
+ },
+ {
+ .name = "azoteq,timeout-lp-ms",
+ .reg_grp = IQS7222_REG_GRP_SYS,
+ .reg_offset = 5,
+ .reg_shift = 0,
+ .reg_width = 16,
+ .label = "low-power mode timeout",
+ },
+ {
+ .name = "azoteq,rate-lp-ms",
+ .reg_grp = IQS7222_REG_GRP_SYS,
+ .reg_offset = 6,
+ .reg_shift = 0,
+ .reg_width = 16,
+ .val_max = 3000,
+ .label = "low-power mode report rate",
+ },
+ {
+ .name = "azoteq,timeout-ulp-ms",
+ .reg_grp = IQS7222_REG_GRP_SYS,
+ .reg_offset = 7,
+ .reg_shift = 0,
+ .reg_width = 16,
+ .label = "ultra-low-power mode timeout",
+ },
+ {
+ .name = "azoteq,rate-ulp-ms",
+ .reg_grp = IQS7222_REG_GRP_SYS,
+ .reg_offset = 8,
+ .reg_shift = 0,
+ .reg_width = 16,
+ .val_max = 3000,
+ .label = "ultra-low-power mode report rate",
+ },
+};
+
+struct iqs7222_private {
+ const struct iqs7222_dev_desc *dev_desc;
+ struct gpio_desc *reset_gpio;
+ struct gpio_desc *irq_gpio;
+ struct i2c_client *client;
+ struct input_dev *keypad;
+ unsigned int kp_type[IQS7222_MAX_CHAN][ARRAY_SIZE(iqs7222_kp_events)];
+ unsigned int kp_code[IQS7222_MAX_CHAN][ARRAY_SIZE(iqs7222_kp_events)];
+ unsigned int sl_code[IQS7222_MAX_SLDR][ARRAY_SIZE(iqs7222_sl_events)];
+ unsigned int sl_axis[IQS7222_MAX_SLDR];
+ u16 cycle_setup[IQS7222_MAX_CHAN / 2][IQS7222_MAX_COLS_CYCLE];
+ u16 glbl_setup[IQS7222_MAX_COLS_GLBL];
+ u16 btn_setup[IQS7222_MAX_CHAN][IQS7222_MAX_COLS_BTN];
+ u16 chan_setup[IQS7222_MAX_CHAN][IQS7222_MAX_COLS_CHAN];
+ u16 filt_setup[IQS7222_MAX_COLS_FILT];
+ u16 sldr_setup[IQS7222_MAX_SLDR][IQS7222_MAX_COLS_SLDR];
+ u16 gpio_setup[ARRAY_SIZE(iqs7222_gpio_links)][IQS7222_MAX_COLS_GPIO];
+ u16 sys_setup[IQS7222_MAX_COLS_SYS];
+};
+
+static u16 *iqs7222_setup(struct iqs7222_private *iqs7222,
+ enum iqs7222_reg_grp_id reg_grp, int row)
+{
+ switch (reg_grp) {
+ case IQS7222_REG_GRP_CYCLE:
+ return iqs7222->cycle_setup[row];
+
+ case IQS7222_REG_GRP_GLBL:
+ return iqs7222->glbl_setup;
+
+ case IQS7222_REG_GRP_BTN:
+ return iqs7222->btn_setup[row];
+
+ case IQS7222_REG_GRP_CHAN:
+ return iqs7222->chan_setup[row];
+
+ case IQS7222_REG_GRP_FILT:
+ return iqs7222->filt_setup;
+
+ case IQS7222_REG_GRP_SLDR:
+ return iqs7222->sldr_setup[row];
+
+ case IQS7222_REG_GRP_GPIO:
+ return iqs7222->gpio_setup[row];
+
+ case IQS7222_REG_GRP_SYS:
+ return iqs7222->sys_setup;
+
+ default:
+ return NULL;
+ }
+}
+
+static int iqs7222_irq_poll(struct iqs7222_private *iqs7222, u16 timeout_ms)
+{
+ ktime_t irq_timeout = ktime_add_ms(ktime_get(), timeout_ms);
+ int ret;
+
+ do {
+ usleep_range(1000, 1100);
+
+ ret = gpiod_get_value_cansleep(iqs7222->irq_gpio);
+ if (ret < 0)
+ return ret;
+ else if (ret > 0)
+ return 0;
+ } while (ktime_compare(ktime_get(), irq_timeout) < 0);
+
+ return -EBUSY;
+}
+
+static int iqs7222_hard_reset(struct iqs7222_private *iqs7222)
+{
+ struct i2c_client *client = iqs7222->client;
+ int error;
+
+ if (!iqs7222->reset_gpio)
+ return 0;
+
+ gpiod_set_value_cansleep(iqs7222->reset_gpio, 1);
+ usleep_range(1000, 1100);
+
+ gpiod_set_value_cansleep(iqs7222->reset_gpio, 0);
+
+ error = iqs7222_irq_poll(iqs7222, IQS7222_RESET_TIMEOUT_MS);
+ if (error)
+ dev_err(&client->dev, "Failed to reset device: %d\n", error);
+
+ return error;
+}
+
+static int iqs7222_force_comms(struct iqs7222_private *iqs7222)
+{
+ u8 msg_buf[] = { 0xFF, };
+ int ret;
+
+ /*
+ * The device cannot communicate until it asserts its interrupt (RDY)
+ * pin. Attempts to do so while RDY is deasserted return an ACK; how-
+ * ever all write data is ignored, and all read data returns 0xEE.
+ *
+ * Unsolicited communication must be preceded by a special force com-
+ * munication command, after which the device eventually asserts its
+ * RDY pin and agrees to communicate.
+ *
+ * Regardless of whether communication is forced or the result of an
+ * interrupt, the device automatically deasserts its RDY pin once it
+ * detects an I2C stop condition, or a timeout expires.
+ */
+ ret = gpiod_get_value_cansleep(iqs7222->irq_gpio);
+ if (ret < 0)
+ return ret;
+ else if (ret > 0)
+ return 0;
+
+ ret = i2c_master_send(iqs7222->client, msg_buf, sizeof(msg_buf));
+ if (ret < (int)sizeof(msg_buf)) {
+ if (ret >= 0)
+ ret = -EIO;
+
+ /*
+ * The datasheet states that the host must wait to retry any
+ * failed attempt to communicate over I2C.
+ */
+ msleep(IQS7222_COMMS_RETRY_MS);
+ return ret;
+ }
+
+ return iqs7222_irq_poll(iqs7222, IQS7222_COMMS_TIMEOUT_MS);
+}
+
+static int iqs7222_read_burst(struct iqs7222_private *iqs7222,
+ u16 reg, void *val, u16 num_val)
+{
+ u8 reg_buf[sizeof(__be16)];
+ int ret, i;
+ struct i2c_client *client = iqs7222->client;
+ struct i2c_msg msg[] = {
+ {
+ .addr = client->addr,
+ .flags = 0,
+ .len = reg > U8_MAX ? sizeof(reg) : sizeof(u8),
+ .buf = reg_buf,
+ },
+ {
+ .addr = client->addr,
+ .flags = I2C_M_RD,
+ .len = num_val * sizeof(__le16),
+ .buf = (u8 *)val,
+ },
+ };
+
+ if (reg > U8_MAX)
+ put_unaligned_be16(reg, reg_buf);
+ else
+ *reg_buf = (u8)reg;
+
+ /*
+ * The following loop protects against an edge case in which the RDY
+ * pin is automatically deasserted just as the read is initiated. In
+ * that case, the read must be retried using forced communication.
+ */
+ for (i = 0; i < IQS7222_NUM_RETRIES; i++) {
+ ret = iqs7222_force_comms(iqs7222);
+ if (ret < 0)
+ continue;
+
+ ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
+ if (ret < (int)ARRAY_SIZE(msg)) {
+ if (ret >= 0)
+ ret = -EIO;
+
+ msleep(IQS7222_COMMS_RETRY_MS);
+ continue;
+ }
+
+ if (get_unaligned_le16(msg[1].buf) == IQS7222_COMMS_ERROR) {
+ ret = -ENODATA;
+ continue;
+ }
+
+ ret = 0;
+ break;
+ }
+
+ /*
+ * The following delay ensures the device has deasserted the RDY pin
+ * following the I2C stop condition.
+ */
+ usleep_range(50, 100);
+
+ if (ret < 0)
+ dev_err(&client->dev,
+ "Failed to read from address 0x%04X: %d\n", reg, ret);
+
+ return ret;
+}
+
+static int iqs7222_read_word(struct iqs7222_private *iqs7222, u16 reg, u16 *val)
+{
+ __le16 val_buf;
+ int error;
+
+ error = iqs7222_read_burst(iqs7222, reg, &val_buf, 1);
+ if (error)
+ return error;
+
+ *val = le16_to_cpu(val_buf);
+
+ return 0;
+}
+
+static int iqs7222_write_burst(struct iqs7222_private *iqs7222,
+ u16 reg, const void *val, u16 num_val)
+{
+ int reg_len = reg > U8_MAX ? sizeof(reg) : sizeof(u8);
+ int val_len = num_val * sizeof(__le16);
+ int msg_len = reg_len + val_len;
+ int ret, i;
+ struct i2c_client *client = iqs7222->client;
+ u8 *msg_buf;
+
+ msg_buf = kzalloc(msg_len, GFP_KERNEL);
+ if (!msg_buf)
+ return -ENOMEM;
+
+ if (reg > U8_MAX)
+ put_unaligned_be16(reg, msg_buf);
+ else
+ *msg_buf = (u8)reg;
+
+ memcpy(msg_buf + reg_len, val, val_len);
+
+ /*
+ * The following loop protects against an edge case in which the RDY
+ * pin is automatically asserted just before the force communication
+ * command is sent.
+ *
+ * In that case, the subsequent I2C stop condition tricks the device
+ * into preemptively deasserting the RDY pin and the command must be
+ * sent again.
+ */
+ for (i = 0; i < IQS7222_NUM_RETRIES; i++) {
+ ret = iqs7222_force_comms(iqs7222);
+ if (ret < 0)
+ continue;
+
+ ret = i2c_master_send(client, msg_buf, msg_len);
+ if (ret < msg_len) {
+ if (ret >= 0)
+ ret = -EIO;
+
+ msleep(IQS7222_COMMS_RETRY_MS);
+ continue;
+ }
+
+ ret = 0;
+ break;
+ }
+
+ kfree(msg_buf);
+
+ usleep_range(50, 100);
+
+ if (ret < 0)
+ dev_err(&client->dev,
+ "Failed to write to address 0x%04X: %d\n", reg, ret);
+
+ return ret;
+}
+
+static int iqs7222_write_word(struct iqs7222_private *iqs7222, u16 reg, u16 val)
+{
+ __le16 val_buf = cpu_to_le16(val);
+
+ return iqs7222_write_burst(iqs7222, reg, &val_buf, 1);
+}
+
+static int iqs7222_ati_trigger(struct iqs7222_private *iqs7222)
+{
+ struct i2c_client *client = iqs7222->client;
+ ktime_t ati_timeout;
+ u16 sys_status = 0;
+ u16 sys_setup;
+ int error, i;
+
+ /*
+ * The reserved fields of the system setup register may have changed
+ * as a result of other registers having been written. As such, read
+ * the register's latest value to avoid unexpected behavior when the
+ * register is written in the loop that follows.
+ */
+ error = iqs7222_read_word(iqs7222, IQS7222_SYS_SETUP, &sys_setup);
+ if (error)
+ return error;
+
+ for (i = 0; i < IQS7222_NUM_RETRIES; i++) {
+ /*
+ * Trigger ATI from streaming and normal-power modes so that
+ * the RDY pin continues to be asserted during ATI.
+ */
+ error = iqs7222_write_word(iqs7222, IQS7222_SYS_SETUP,
+ sys_setup |
+ IQS7222_SYS_SETUP_REDO_ATI);
+ if (error)
+ return error;
+
+ ati_timeout = ktime_add_ms(ktime_get(), IQS7222_ATI_TIMEOUT_MS);
+
+ do {
+ error = iqs7222_irq_poll(iqs7222,
+ IQS7222_COMMS_TIMEOUT_MS);
+ if (error)
+ continue;
+
+ error = iqs7222_read_word(iqs7222, IQS7222_SYS_STATUS,
+ &sys_status);
+ if (error)
+ return error;
+
+ if (sys_status & IQS7222_SYS_STATUS_RESET)
+ return 0;
+
+ if (sys_status & IQS7222_SYS_STATUS_ATI_ERROR)
+ break;
+
+ if (sys_status & IQS7222_SYS_STATUS_ATI_ACTIVE)
+ continue;
+
+ /*
+ * Use stream-in-touch mode if either slider reports
+ * absolute position.
+ */
+ sys_setup |= test_bit(EV_ABS, iqs7222->keypad->evbit)
+ ? IQS7222_SYS_SETUP_INTF_MODE_TOUCH
+ : IQS7222_SYS_SETUP_INTF_MODE_EVENT;
+ sys_setup |= IQS7222_SYS_SETUP_PWR_MODE_AUTO;
+
+ return iqs7222_write_word(iqs7222, IQS7222_SYS_SETUP,
+ sys_setup);
+ } while (ktime_compare(ktime_get(), ati_timeout) < 0);
+
+ dev_err(&client->dev,
+ "ATI attempt %d of %d failed with status 0x%02X, %s\n",
+ i + 1, IQS7222_NUM_RETRIES, (u8)sys_status,
+ i + 1 < IQS7222_NUM_RETRIES ? "retrying" : "stopping");
+ }
+
+ return -ETIMEDOUT;
+}
+
+static int iqs7222_dev_init(struct iqs7222_private *iqs7222, int dir)
+{
+ const struct iqs7222_dev_desc *dev_desc = iqs7222->dev_desc;
+ int comms_offset = dev_desc->comms_offset;
+ int error, i, j, k;
+
+ /*
+ * Acknowledge reset before writing any registers in case the device
+ * suffers a spurious reset during initialization. Because this step
+ * may change the reserved fields of the second filter beta register,
+ * its cache must be updated.
+ *
+ * Writing the second filter beta register, in turn, may clobber the
+ * system status register. As such, the filter beta register pair is
+ * written first to protect against this hazard.
+ */
+ if (dir == WRITE) {
+ u16 reg = dev_desc->reg_grps[IQS7222_REG_GRP_FILT].base + 1;
+ u16 filt_setup;
+
+ error = iqs7222_write_word(iqs7222, IQS7222_SYS_SETUP,
+ iqs7222->sys_setup[0] |
+ IQS7222_SYS_SETUP_ACK_RESET);
+ if (error)
+ return error;
+
+ error = iqs7222_read_word(iqs7222, reg, &filt_setup);
+ if (error)
+ return error;
+
+ iqs7222->filt_setup[1] &= GENMASK(7, 0);
+ iqs7222->filt_setup[1] |= (filt_setup & ~GENMASK(7, 0));
+ }
+
+ /*
+ * Take advantage of the stop-bit disable function, if available, to
+ * save the trouble of having to reopen a communication window after
+ * each burst read or write.
+ */
+ if (comms_offset) {
+ u16 comms_setup;
+
+ error = iqs7222_read_word(iqs7222,
+ IQS7222_SYS_SETUP + comms_offset,
+ &comms_setup);
+ if (error)
+ return error;
+
+ error = iqs7222_write_word(iqs7222,
+ IQS7222_SYS_SETUP + comms_offset,
+ comms_setup | IQS7222_COMMS_HOLD);
+ if (error)
+ return error;
+ }
+
+ for (i = 0; i < IQS7222_NUM_REG_GRPS; i++) {
+ int num_row = dev_desc->reg_grps[i].num_row;
+ int num_col = dev_desc->reg_grps[i].num_col;
+ u16 reg = dev_desc->reg_grps[i].base;
+ __le16 *val_buf;
+ u16 *val;
+
+ if (!num_col)
+ continue;
+
+ val = iqs7222_setup(iqs7222, i, 0);
+ if (!val)
+ continue;
+
+ val_buf = kcalloc(num_col, sizeof(__le16), GFP_KERNEL);
+ if (!val_buf)
+ return -ENOMEM;
+
+ for (j = 0; j < num_row; j++) {
+ switch (dir) {
+ case READ:
+ error = iqs7222_read_burst(iqs7222, reg,
+ val_buf, num_col);
+ for (k = 0; k < num_col; k++)
+ val[k] = le16_to_cpu(val_buf[k]);
+ break;
+
+ case WRITE:
+ for (k = 0; k < num_col; k++)
+ val_buf[k] = cpu_to_le16(val[k]);
+ error = iqs7222_write_burst(iqs7222, reg,
+ val_buf, num_col);
+ break;
+
+ default:
+ error = -EINVAL;
+ }
+
+ if (error)
+ break;
+
+ reg += IQS7222_REG_OFFSET;
+ val += iqs7222_max_cols[i];
+ }
+
+ kfree(val_buf);
+
+ if (error)
+ return error;
+ }
+
+ if (comms_offset) {
+ u16 comms_setup;
+
+ error = iqs7222_read_word(iqs7222,
+ IQS7222_SYS_SETUP + comms_offset,
+ &comms_setup);
+ if (error)
+ return error;
+
+ error = iqs7222_write_word(iqs7222,
+ IQS7222_SYS_SETUP + comms_offset,
+ comms_setup & ~IQS7222_COMMS_HOLD);
+ if (error)
+ return error;
+ }
+
+ if (dir == READ) {
+ iqs7222->sys_setup[0] &= ~IQS7222_SYS_SETUP_INTF_MODE_MASK;
+ iqs7222->sys_setup[0] &= ~IQS7222_SYS_SETUP_PWR_MODE_MASK;
+ return 0;
+ }
+
+ return iqs7222_ati_trigger(iqs7222);
+}
+
+static int iqs7222_dev_info(struct iqs7222_private *iqs7222)
+{
+ struct i2c_client *client = iqs7222->client;
+ bool prod_num_valid = false;
+ __le16 dev_id[3];
+ int error, i;
+
+ error = iqs7222_read_burst(iqs7222, IQS7222_PROD_NUM, dev_id,
+ ARRAY_SIZE(dev_id));
+ if (error)
+ return error;
+
+ for (i = 0; i < ARRAY_SIZE(iqs7222_devs); i++) {
+ if (le16_to_cpu(dev_id[0]) != iqs7222_devs[i].prod_num)
+ continue;
+
+ prod_num_valid = true;
+
+ if (le16_to_cpu(dev_id[1]) < iqs7222_devs[i].fw_major)
+ continue;
+
+ if (le16_to_cpu(dev_id[2]) < iqs7222_devs[i].fw_minor)
+ continue;
+
+ iqs7222->dev_desc = &iqs7222_devs[i];
+ return 0;
+ }
+
+ if (prod_num_valid)
+ dev_err(&client->dev, "Unsupported firmware revision: %u.%u\n",
+ le16_to_cpu(dev_id[1]), le16_to_cpu(dev_id[2]));
+ else
+ dev_err(&client->dev, "Unrecognized product number: %u\n",
+ le16_to_cpu(dev_id[0]));
+
+ return -EINVAL;
+}
+
+static int iqs7222_gpio_select(struct iqs7222_private *iqs7222,
+ struct fwnode_handle *child_node,
+ int child_enable, u16 child_link)
+{
+ const struct iqs7222_dev_desc *dev_desc = iqs7222->dev_desc;
+ struct i2c_client *client = iqs7222->client;
+ int num_gpio = dev_desc->reg_grps[IQS7222_REG_GRP_GPIO].num_row;
+ int error, count, i;
+ unsigned int gpio_sel[ARRAY_SIZE(iqs7222_gpio_links)];
+
+ if (!num_gpio)
+ return 0;
+
+ if (!fwnode_property_present(child_node, "azoteq,gpio-select"))
+ return 0;
+
+ count = fwnode_property_count_u32(child_node, "azoteq,gpio-select");
+ if (count > num_gpio) {
+ dev_err(&client->dev, "Invalid number of %s GPIOs\n",
+ fwnode_get_name(child_node));
+ return -EINVAL;
+ } else if (count < 0) {
+ dev_err(&client->dev, "Failed to count %s GPIOs: %d\n",
+ fwnode_get_name(child_node), count);
+ return count;
+ }
+
+ error = fwnode_property_read_u32_array(child_node,
+ "azoteq,gpio-select",
+ gpio_sel, count);
+ if (error) {
+ dev_err(&client->dev, "Failed to read %s GPIOs: %d\n",
+ fwnode_get_name(child_node), error);
+ return error;
+ }
+
+ for (i = 0; i < count; i++) {
+ u16 *gpio_setup;
+
+ if (gpio_sel[i] >= num_gpio) {
+ dev_err(&client->dev, "Invalid %s GPIO: %u\n",
+ fwnode_get_name(child_node), gpio_sel[i]);
+ return -EINVAL;
+ }
+
+ gpio_setup = iqs7222->gpio_setup[gpio_sel[i]];
+
+ if (gpio_setup[2] && child_link != gpio_setup[2]) {
+ dev_err(&client->dev,
+ "Conflicting GPIO %u event types\n",
+ gpio_sel[i]);
+ return -EINVAL;
+ }
+
+ gpio_setup[0] |= IQS7222_GPIO_SETUP_0_GPIO_EN;
+ gpio_setup[1] |= child_enable;
+ gpio_setup[2] = child_link;
+ }
+
+ return 0;
+}
+
+static int iqs7222_parse_props(struct iqs7222_private *iqs7222,
+ struct fwnode_handle *reg_grp_node,
+ int reg_grp_index,
+ enum iqs7222_reg_grp_id reg_grp,
+ enum iqs7222_reg_key_id reg_key)
+{
+ u16 *setup = iqs7222_setup(iqs7222, reg_grp, reg_grp_index);
+ struct i2c_client *client = iqs7222->client;
+ int i;
+
+ if (!setup)
+ return 0;
+
+ for (i = 0; i < ARRAY_SIZE(iqs7222_props); i++) {
+ const char *name = iqs7222_props[i].name;
+ int reg_offset = iqs7222_props[i].reg_offset;
+ int reg_shift = iqs7222_props[i].reg_shift;
+ int reg_width = iqs7222_props[i].reg_width;
+ int val_pitch = iqs7222_props[i].val_pitch ? : 1;
+ int val_min = iqs7222_props[i].val_min;
+ int val_max = iqs7222_props[i].val_max;
+ bool invert = iqs7222_props[i].invert;
+ const char *label = iqs7222_props[i].label ? : name;
+ unsigned int val;
+ int error;
+
+ if (iqs7222_props[i].reg_grp != reg_grp ||
+ iqs7222_props[i].reg_key != reg_key)
+ continue;
+
+ /*
+ * Boolean register fields are one bit wide; they are forcibly
+ * reset to provide a means to undo changes by a bootloader if
+ * necessary.
+ *
+ * Scalar fields, on the other hand, are left untouched unless
+ * their corresponding properties are present.
+ */
+ if (reg_width == 1) {
+ if (invert)
+ setup[reg_offset] |= BIT(reg_shift);
+ else
+ setup[reg_offset] &= ~BIT(reg_shift);
+ }
+
+ if (!fwnode_property_present(reg_grp_node, name))
+ continue;
+
+ if (reg_width == 1) {
+ if (invert)
+ setup[reg_offset] &= ~BIT(reg_shift);
+ else
+ setup[reg_offset] |= BIT(reg_shift);
+
+ continue;
+ }
+
+ error = fwnode_property_read_u32(reg_grp_node, name, &val);
+ if (error) {
+ dev_err(&client->dev, "Failed to read %s %s: %d\n",
+ fwnode_get_name(reg_grp_node), label, error);
+ return error;
+ }
+
+ if (!val_max)
+ val_max = GENMASK(reg_width - 1, 0) * val_pitch;
+
+ if (val < val_min || val > val_max) {
+ dev_err(&client->dev, "Invalid %s %s: %u\n",
+ fwnode_get_name(reg_grp_node), label, val);
+ return -EINVAL;
+ }
+
+ setup[reg_offset] &= ~GENMASK(reg_shift + reg_width - 1,
+ reg_shift);
+ setup[reg_offset] |= (val / val_pitch << reg_shift);
+ }
+
+ return 0;
+}
+
+static int iqs7222_parse_event(struct iqs7222_private *iqs7222,
+ struct fwnode_handle *event_node,
+ int reg_grp_index,
+ enum iqs7222_reg_grp_id reg_grp,
+ enum iqs7222_reg_key_id reg_key,
+ u16 event_enable, u16 event_link,
+ unsigned int *event_type,
+ unsigned int *event_code)
+{
+ struct i2c_client *client = iqs7222->client;
+ int error;
+
+ error = iqs7222_parse_props(iqs7222, event_node, reg_grp_index,
+ reg_grp, reg_key);
+ if (error)
+ return error;
+
+ error = iqs7222_gpio_select(iqs7222, event_node, event_enable,
+ event_link);
+ if (error)
+ return error;
+
+ error = fwnode_property_read_u32(event_node, "linux,code", event_code);
+ if (error == -EINVAL) {
+ return 0;
+ } else if (error) {
+ dev_err(&client->dev, "Failed to read %s code: %d\n",
+ fwnode_get_name(event_node), error);
+ return error;
+ }
+
+ if (!event_type) {
+ input_set_capability(iqs7222->keypad, EV_KEY, *event_code);
+ return 0;
+ }
+
+ error = fwnode_property_read_u32(event_node, "linux,input-type",
+ event_type);
+ if (error == -EINVAL) {
+ *event_type = EV_KEY;
+ } else if (error) {
+ dev_err(&client->dev, "Failed to read %s input type: %d\n",
+ fwnode_get_name(event_node), error);
+ return error;
+ } else if (*event_type != EV_KEY && *event_type != EV_SW) {
+ dev_err(&client->dev, "Invalid %s input type: %d\n",
+ fwnode_get_name(event_node), *event_type);
+ return -EINVAL;
+ }
+
+ input_set_capability(iqs7222->keypad, *event_type, *event_code);
+
+ return 0;
+}
+
+static int iqs7222_parse_cycle(struct iqs7222_private *iqs7222,
+ struct fwnode_handle *cycle_node, int cycle_index)
+{
+ u16 *cycle_setup = iqs7222->cycle_setup[cycle_index];
+ struct i2c_client *client = iqs7222->client;
+ unsigned int pins[9];
+ int error, count, i;
+
+ /*
+ * Each channel shares a cycle with one other channel; the mapping of
+ * channels to cycles is fixed. Properties defined for a cycle impact
+ * both channels tied to the cycle.
+ *
+ * Unlike channels which are restricted to a select range of CRx pins
+ * based on channel number, any cycle can claim any of the device's 9
+ * CTx pins (CTx0-8).
+ */
+ if (!fwnode_property_present(cycle_node, "azoteq,tx-enable"))
+ return 0;
+
+ count = fwnode_property_count_u32(cycle_node, "azoteq,tx-enable");
+ if (count < 0) {
+ dev_err(&client->dev, "Failed to count %s CTx pins: %d\n",
+ fwnode_get_name(cycle_node), count);
+ return count;
+ } else if (count > ARRAY_SIZE(pins)) {
+ dev_err(&client->dev, "Invalid number of %s CTx pins\n",
+ fwnode_get_name(cycle_node));
+ return -EINVAL;
+ }
+
+ error = fwnode_property_read_u32_array(cycle_node, "azoteq,tx-enable",
+ pins, count);
+ if (error) {
+ dev_err(&client->dev, "Failed to read %s CTx pins: %d\n",
+ fwnode_get_name(cycle_node), error);
+ return error;
+ }
+
+ cycle_setup[1] &= ~GENMASK(7 + ARRAY_SIZE(pins) - 1, 7);
+
+ for (i = 0; i < count; i++) {
+ if (pins[i] > 8) {
+ dev_err(&client->dev, "Invalid %s CTx pin: %u\n",
+ fwnode_get_name(cycle_node), pins[i]);
+ return -EINVAL;
+ }
+
+ cycle_setup[1] |= BIT(pins[i] + 7);
+ }
+
+ return 0;
+}
+
+static int iqs7222_parse_chan(struct iqs7222_private *iqs7222,
+ struct fwnode_handle *chan_node, int chan_index)
+{
+ const struct iqs7222_dev_desc *dev_desc = iqs7222->dev_desc;
+ struct i2c_client *client = iqs7222->client;
+ int num_chan = dev_desc->reg_grps[IQS7222_REG_GRP_CHAN].num_row;
+ int ext_chan = rounddown(num_chan, 10);
+ int error, i;
+ u16 *chan_setup = iqs7222->chan_setup[chan_index];
+ u16 *sys_setup = iqs7222->sys_setup;
+ unsigned int val;
+
+ if (dev_desc->allow_offset &&
+ fwnode_property_present(chan_node, "azoteq,ulp-allow"))
+ sys_setup[dev_desc->allow_offset] &= ~BIT(chan_index);
+
+ chan_setup[0] |= IQS7222_CHAN_SETUP_0_CHAN_EN;
+
+ /*
+ * The reference channel function allows for differential measurements
+ * and is only available in the case of IQS7222A or IQS7222C.
+ */
+ if (dev_desc->reg_grps[IQS7222_REG_GRP_CHAN].num_col > 4 &&
+ fwnode_property_present(chan_node, "azoteq,ref-select")) {
+ u16 *ref_setup;
+
+ error = fwnode_property_read_u32(chan_node, "azoteq,ref-select",
+ &val);
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to read %s reference channel: %d\n",
+ fwnode_get_name(chan_node), error);
+ return error;
+ }
+
+ if (val >= ext_chan) {
+ dev_err(&client->dev,
+ "Invalid %s reference channel: %u\n",
+ fwnode_get_name(chan_node), val);
+ return -EINVAL;
+ }
+
+ ref_setup = iqs7222->chan_setup[val];
+
+ /*
+ * Configure the current channel as a follower of the selected
+ * reference channel.
+ */
+ chan_setup[0] |= IQS7222_CHAN_SETUP_0_REF_MODE_FOLLOW;
+ chan_setup[4] = val * 42 + 1048;
+
+ error = fwnode_property_read_u32(chan_node, "azoteq,ref-weight",
+ &val);
+ if (!error) {
+ if (val > U16_MAX) {
+ dev_err(&client->dev,
+ "Invalid %s reference weight: %u\n",
+ fwnode_get_name(chan_node), val);
+ return -EINVAL;
+ }
+
+ chan_setup[5] = val;
+ } else if (error != -EINVAL) {
+ dev_err(&client->dev,
+ "Failed to read %s reference weight: %d\n",
+ fwnode_get_name(chan_node), error);
+ return error;
+ }
+
+ /*
+ * Configure the selected channel as a reference channel which
+ * serves the current channel.
+ */
+ ref_setup[0] |= IQS7222_CHAN_SETUP_0_REF_MODE_REF;
+ ref_setup[5] |= BIT(chan_index);
+
+ ref_setup[4] = dev_desc->touch_link;
+ if (fwnode_property_present(chan_node, "azoteq,use-prox"))
+ ref_setup[4] -= 2;
+ }
+
+ if (fwnode_property_present(chan_node, "azoteq,rx-enable")) {
+ /*
+ * Each channel can claim up to 4 CRx pins. The first half of
+ * the channels can use CRx0-3, while the second half can use
+ * CRx4-7.
+ */
+ unsigned int pins[4];
+ int count;
+
+ count = fwnode_property_count_u32(chan_node,
+ "azoteq,rx-enable");
+ if (count < 0) {
+ dev_err(&client->dev,
+ "Failed to count %s CRx pins: %d\n",
+ fwnode_get_name(chan_node), count);
+ return count;
+ } else if (count > ARRAY_SIZE(pins)) {
+ dev_err(&client->dev,
+ "Invalid number of %s CRx pins\n",
+ fwnode_get_name(chan_node));
+ return -EINVAL;
+ }
+
+ error = fwnode_property_read_u32_array(chan_node,
+ "azoteq,rx-enable",
+ pins, count);
+ if (error) {
+ dev_err(&client->dev,
+ "Failed to read %s CRx pins: %d\n",
+ fwnode_get_name(chan_node), error);
+ return error;
+ }
+
+ chan_setup[0] &= ~GENMASK(4 + ARRAY_SIZE(pins) - 1, 4);
+
+ for (i = 0; i < count; i++) {
+ int min_crx = chan_index < ext_chan / 2 ? 0 : 4;
+
+ if (pins[i] < min_crx || pins[i] > min_crx + 3) {
+ dev_err(&client->dev,
+ "Invalid %s CRx pin: %u\n",
+ fwnode_get_name(chan_node), pins[i]);
+ return -EINVAL;
+ }
+
+ chan_setup[0] |= BIT(pins[i] + 4 - min_crx);
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(iqs7222_kp_events); i++) {
+ const char *event_name = iqs7222_kp_events[i].name;
+ u16 event_enable = iqs7222_kp_events[i].enable;
+ struct fwnode_handle *event_node;
+
+ event_node = fwnode_get_named_child_node(chan_node, event_name);
+ if (!event_node)
+ continue;
+
+ error = fwnode_property_read_u32(event_node,
+ "azoteq,timeout-press-ms",
+ &val);
+ if (!error) {
+ /*
+ * The IQS7222B employs a global pair of press timeout
+ * registers as opposed to channel-specific registers.
+ */
+ u16 *setup = dev_desc->reg_grps
+ [IQS7222_REG_GRP_BTN].num_col > 2 ?
+ &iqs7222->btn_setup[chan_index][2] :
+ &sys_setup[9];
+
+ if (val > U8_MAX * 500) {
+ dev_err(&client->dev,
+ "Invalid %s press timeout: %u\n",
+ fwnode_get_name(event_node), val);
+ fwnode_handle_put(event_node);
+ return -EINVAL;
+ }
+
+ *setup &= ~(U8_MAX << i * 8);
+ *setup |= (val / 500 << i * 8);
+ } else if (error != -EINVAL) {
+ dev_err(&client->dev,
+ "Failed to read %s press timeout: %d\n",
+ fwnode_get_name(event_node), error);
+ fwnode_handle_put(event_node);
+ return error;
+ }
+
+ error = iqs7222_parse_event(iqs7222, event_node, chan_index,
+ IQS7222_REG_GRP_BTN,
+ iqs7222_kp_events[i].reg_key,
+ BIT(chan_index),
+ dev_desc->touch_link - (i ? 0 : 2),
+ &iqs7222->kp_type[chan_index][i],
+ &iqs7222->kp_code[chan_index][i]);
+ fwnode_handle_put(event_node);
+ if (error)
+ return error;
+
+ if (!dev_desc->event_offset)
+ continue;
+
+ sys_setup[dev_desc->event_offset] |= event_enable;
+ }
+
+ /*
+ * The following call handles a special pair of properties that apply
+ * to a channel node, but reside within the button (event) group.
+ */
+ return iqs7222_parse_props(iqs7222, chan_node, chan_index,
+ IQS7222_REG_GRP_BTN,
+ IQS7222_REG_KEY_DEBOUNCE);
+}
+
+static int iqs7222_parse_sldr(struct iqs7222_private *iqs7222,
+ struct fwnode_handle *sldr_node, int sldr_index)
+{
+ const struct iqs7222_dev_desc *dev_desc = iqs7222->dev_desc;
+ struct i2c_client *client = iqs7222->client;
+ int num_chan = dev_desc->reg_grps[IQS7222_REG_GRP_CHAN].num_row;
+ int ext_chan = rounddown(num_chan, 10);
+ int count, error, reg_offset, i;
+ u16 *event_mask = &iqs7222->sys_setup[dev_desc->event_offset];
+ u16 *sldr_setup = iqs7222->sldr_setup[sldr_index];
+ unsigned int chan_sel[4], val;
+
+ /*
+ * Each slider can be spread across 3 to 4 channels. It is possible to
+ * select only 2 channels, but doing so prevents the slider from using
+ * the specified resolution.
+ */
+ count = fwnode_property_count_u32(sldr_node, "azoteq,channel-select");
+ if (count < 0) {
+ dev_err(&client->dev, "Failed to count %s channels: %d\n",
+ fwnode_get_name(sldr_node), count);
+ return count;
+ } else if (count < 3 || count > ARRAY_SIZE(chan_sel)) {
+ dev_err(&client->dev, "Invalid number of %s channels\n",
+ fwnode_get_name(sldr_node));
+ return -EINVAL;
+ }
+
+ error = fwnode_property_read_u32_array(sldr_node,
+ "azoteq,channel-select",
+ chan_sel, count);
+ if (error) {
+ dev_err(&client->dev, "Failed to read %s channels: %d\n",
+ fwnode_get_name(sldr_node), error);
+ return error;
+ }
+
+ /*
+ * Resolution and top speed, if small enough, are packed into a single
+ * register. Otherwise, each occupies its own register and the rest of
+ * the slider-related register addresses are offset by one.
+ */
+ reg_offset = dev_desc->sldr_res < U16_MAX ? 0 : 1;
+
+ sldr_setup[0] |= count;
+ sldr_setup[3 + reg_offset] &= ~GENMASK(ext_chan - 1, 0);
+
+ for (i = 0; i < ARRAY_SIZE(chan_sel); i++) {
+ sldr_setup[5 + reg_offset + i] = 0;
+ if (i >= count)
+ continue;
+
+ if (chan_sel[i] >= ext_chan) {
+ dev_err(&client->dev, "Invalid %s channel: %u\n",
+ fwnode_get_name(sldr_node), chan_sel[i]);
+ return -EINVAL;
+ }
+
+ /*
+ * The following fields indicate which channels participate in
+ * the slider, as well as each channel's relative placement.
+ */
+ sldr_setup[3 + reg_offset] |= BIT(chan_sel[i]);
+ sldr_setup[5 + reg_offset + i] = chan_sel[i] * 42 + 1080;
+ }
+
+ sldr_setup[4 + reg_offset] = dev_desc->touch_link;
+ if (fwnode_property_present(sldr_node, "azoteq,use-prox"))
+ sldr_setup[4 + reg_offset] -= 2;
+
+ error = fwnode_property_read_u32(sldr_node, "azoteq,slider-size", &val);
+ if (!error) {
+ if (val > dev_desc->sldr_res) {
+ dev_err(&client->dev, "Invalid %s size: %u\n",
+ fwnode_get_name(sldr_node), val);
+ return -EINVAL;
+ }
+
+ if (reg_offset) {
+ sldr_setup[3] = val;
+ } else {
+ sldr_setup[2] &= ~IQS7222_SLDR_SETUP_2_RES_MASK;
+ sldr_setup[2] |= (val / 16 <<
+ IQS7222_SLDR_SETUP_2_RES_SHIFT);
+ }
+ } else if (error != -EINVAL) {
+ dev_err(&client->dev, "Failed to read %s size: %d\n",
+ fwnode_get_name(sldr_node), error);
+ return error;
+ }
+
+ if (!(reg_offset ? sldr_setup[3]
+ : sldr_setup[2] & IQS7222_SLDR_SETUP_2_RES_MASK)) {
+ dev_err(&client->dev, "Undefined %s size\n",
+ fwnode_get_name(sldr_node));
+ return -EINVAL;
+ }
+
+ error = fwnode_property_read_u32(sldr_node, "azoteq,top-speed", &val);
+ if (!error) {
+ if (val > (reg_offset ? U16_MAX : U8_MAX * 4)) {
+ dev_err(&client->dev, "Invalid %s top speed: %u\n",
+ fwnode_get_name(sldr_node), val);
+ return -EINVAL;
+ }
+
+ if (reg_offset) {
+ sldr_setup[2] = val;
+ } else {
+ sldr_setup[2] &= ~IQS7222_SLDR_SETUP_2_TOP_SPEED_MASK;
+ sldr_setup[2] |= (val / 4);
+ }
+ } else if (error != -EINVAL) {
+ dev_err(&client->dev, "Failed to read %s top speed: %d\n",
+ fwnode_get_name(sldr_node), error);
+ return error;
+ }
+
+ error = fwnode_property_read_u32(sldr_node, "linux,axis", &val);
+ if (!error) {
+ u16 sldr_max = sldr_setup[3] - 1;
+
+ if (!reg_offset) {
+ sldr_max = sldr_setup[2];
+
+ sldr_max &= IQS7222_SLDR_SETUP_2_RES_MASK;
+ sldr_max >>= IQS7222_SLDR_SETUP_2_RES_SHIFT;
+
+ sldr_max = sldr_max * 16 - 1;
+ }
+
+ input_set_abs_params(iqs7222->keypad, val, 0, sldr_max, 0, 0);
+ iqs7222->sl_axis[sldr_index] = val;
+ } else if (error != -EINVAL) {
+ dev_err(&client->dev, "Failed to read %s axis: %d\n",
+ fwnode_get_name(sldr_node), error);
+ return error;
+ }
+
+ if (dev_desc->wheel_enable) {
+ sldr_setup[0] &= ~dev_desc->wheel_enable;
+ if (iqs7222->sl_axis[sldr_index] == ABS_WHEEL)
+ sldr_setup[0] |= dev_desc->wheel_enable;
+ }
+
+ /*
+ * The absence of a register offset makes it safe to assume the device
+ * supports gestures, each of which is first disabled until explicitly
+ * enabled.
+ */
+ if (!reg_offset)
+ for (i = 0; i < ARRAY_SIZE(iqs7222_sl_events); i++)
+ sldr_setup[9] &= ~iqs7222_sl_events[i].enable;
+
+ for (i = 0; i < ARRAY_SIZE(iqs7222_sl_events); i++) {
+ const char *event_name = iqs7222_sl_events[i].name;
+ struct fwnode_handle *event_node;
+ enum iqs7222_reg_key_id reg_key;
+
+ event_node = fwnode_get_named_child_node(sldr_node, event_name);
+ if (!event_node)
+ continue;
+
+ /*
+ * Depending on the device, gestures are either offered using
+ * one of two timing resolutions, or are not supported at all.
+ */
+ if (reg_offset)
+ reg_key = IQS7222_REG_KEY_RESERVED;
+ else if (dev_desc->legacy_gesture &&
+ iqs7222_sl_events[i].reg_key == IQS7222_REG_KEY_TAP)
+ reg_key = IQS7222_REG_KEY_TAP_LEGACY;
+ else if (dev_desc->legacy_gesture &&
+ iqs7222_sl_events[i].reg_key == IQS7222_REG_KEY_AXIAL)
+ reg_key = IQS7222_REG_KEY_AXIAL_LEGACY;
+ else
+ reg_key = iqs7222_sl_events[i].reg_key;
+
+ /*
+ * The press/release event does not expose a direct GPIO link,
+ * but one can be emulated by tying each of the participating
+ * channels to the same GPIO.
+ */
+ error = iqs7222_parse_event(iqs7222, event_node, sldr_index,
+ IQS7222_REG_GRP_SLDR, reg_key,
+ i ? iqs7222_sl_events[i].enable
+ : sldr_setup[3 + reg_offset],
+ i ? 1568 + sldr_index * 30
+ : sldr_setup[4 + reg_offset],
+ NULL,
+ &iqs7222->sl_code[sldr_index][i]);
+ fwnode_handle_put(event_node);
+ if (error)
+ return error;
+
+ if (!reg_offset)
+ sldr_setup[9] |= iqs7222_sl_events[i].enable;
+
+ if (!dev_desc->event_offset)
+ continue;
+
+ /*
+ * The press/release event is determined based on whether the
+ * coordinate field reports 0xFFFF and solely relies on touch
+ * or proximity interrupts to be unmasked.
+ */
+ if (i && !reg_offset)
+ *event_mask |= (IQS7222_EVENT_MASK_SLDR << sldr_index);
+ else if (sldr_setup[4 + reg_offset] == dev_desc->touch_link)
+ *event_mask |= IQS7222_EVENT_MASK_TOUCH;
+ else
+ *event_mask |= IQS7222_EVENT_MASK_PROX;
+ }
+
+ /*
+ * The following call handles a special pair of properties that shift
+ * to make room for a wheel enable control in the case of IQS7222C.
+ */
+ return iqs7222_parse_props(iqs7222, sldr_node, sldr_index,
+ IQS7222_REG_GRP_SLDR,
+ dev_desc->wheel_enable ?
+ IQS7222_REG_KEY_WHEEL :
+ IQS7222_REG_KEY_NO_WHEEL);
+}
+
+static int (*iqs7222_parse_extra[IQS7222_NUM_REG_GRPS])
+ (struct iqs7222_private *iqs7222,
+ struct fwnode_handle *reg_grp_node,
+ int reg_grp_index) = {
+ [IQS7222_REG_GRP_CYCLE] = iqs7222_parse_cycle,
+ [IQS7222_REG_GRP_CHAN] = iqs7222_parse_chan,
+ [IQS7222_REG_GRP_SLDR] = iqs7222_parse_sldr,
+};
+
+static int iqs7222_parse_reg_grp(struct iqs7222_private *iqs7222,
+ enum iqs7222_reg_grp_id reg_grp,
+ int reg_grp_index)
+{
+ struct i2c_client *client = iqs7222->client;
+ struct fwnode_handle *reg_grp_node;
+ int error;
+
+ if (iqs7222_reg_grp_names[reg_grp]) {
+ char reg_grp_name[16];
+
+ snprintf(reg_grp_name, sizeof(reg_grp_name), "%s-%d",
+ iqs7222_reg_grp_names[reg_grp], reg_grp_index);
+
+ reg_grp_node = device_get_named_child_node(&client->dev,
+ reg_grp_name);
+ } else {
+ reg_grp_node = fwnode_handle_get(dev_fwnode(&client->dev));
+ }
+
+ if (!reg_grp_node)
+ return 0;
+
+ error = iqs7222_parse_props(iqs7222, reg_grp_node, reg_grp_index,
+ reg_grp, IQS7222_REG_KEY_NONE);
+
+ if (!error && iqs7222_parse_extra[reg_grp])
+ error = iqs7222_parse_extra[reg_grp](iqs7222, reg_grp_node,
+ reg_grp_index);
+
+ fwnode_handle_put(reg_grp_node);
+
+ return error;
+}
+
+static int iqs7222_parse_all(struct iqs7222_private *iqs7222)
+{
+ const struct iqs7222_dev_desc *dev_desc = iqs7222->dev_desc;
+ const struct iqs7222_reg_grp_desc *reg_grps = dev_desc->reg_grps;
+ u16 *sys_setup = iqs7222->sys_setup;
+ int error, i, j;
+
+ if (dev_desc->allow_offset)
+ sys_setup[dev_desc->allow_offset] = U16_MAX;
+
+ if (dev_desc->event_offset)
+ sys_setup[dev_desc->event_offset] = IQS7222_EVENT_MASK_ATI;
+
+ for (i = 0; i < reg_grps[IQS7222_REG_GRP_GPIO].num_row; i++) {
+ u16 *gpio_setup = iqs7222->gpio_setup[i];
+
+ gpio_setup[0] &= ~IQS7222_GPIO_SETUP_0_GPIO_EN;
+ gpio_setup[1] = 0;
+ gpio_setup[2] = 0;
+
+ if (reg_grps[IQS7222_REG_GRP_GPIO].num_row == 1)
+ continue;
+
+ /*
+ * The IQS7222C exposes multiple GPIO and must be informed
+ * as to which GPIO this group represents.
+ */
+ for (j = 0; j < ARRAY_SIZE(iqs7222_gpio_links); j++)
+ gpio_setup[0] &= ~BIT(iqs7222_gpio_links[j]);
+
+ gpio_setup[0] |= BIT(iqs7222_gpio_links[i]);
+ }
+
+ for (i = 0; i < reg_grps[IQS7222_REG_GRP_CHAN].num_row; i++) {
+ u16 *chan_setup = iqs7222->chan_setup[i];
+
+ chan_setup[0] &= ~IQS7222_CHAN_SETUP_0_REF_MODE_MASK;
+ chan_setup[0] &= ~IQS7222_CHAN_SETUP_0_CHAN_EN;
+
+ chan_setup[5] = 0;
+ }
+
+ for (i = 0; i < reg_grps[IQS7222_REG_GRP_SLDR].num_row; i++) {
+ u16 *sldr_setup = iqs7222->sldr_setup[i];
+
+ sldr_setup[0] &= ~IQS7222_SLDR_SETUP_0_CHAN_CNT_MASK;
+ }
+
+ for (i = 0; i < IQS7222_NUM_REG_GRPS; i++) {
+ for (j = 0; j < reg_grps[i].num_row; j++) {
+ error = iqs7222_parse_reg_grp(iqs7222, i, j);
+ if (error)
+ return error;
+ }
+ }
+
+ return 0;
+}
+
+static int iqs7222_report(struct iqs7222_private *iqs7222)
+{
+ const struct iqs7222_dev_desc *dev_desc = iqs7222->dev_desc;
+ struct i2c_client *client = iqs7222->client;
+ int num_chan = dev_desc->reg_grps[IQS7222_REG_GRP_CHAN].num_row;
+ int num_stat = dev_desc->reg_grps[IQS7222_REG_GRP_STAT].num_col;
+ int error, i, j;
+ __le16 status[IQS7222_MAX_COLS_STAT];
+
+ error = iqs7222_read_burst(iqs7222, IQS7222_SYS_STATUS, status,
+ num_stat);
+ if (error)
+ return error;
+
+ if (le16_to_cpu(status[0]) & IQS7222_SYS_STATUS_RESET) {
+ dev_err(&client->dev, "Unexpected device reset\n");
+ return iqs7222_dev_init(iqs7222, WRITE);
+ }
+
+ if (le16_to_cpu(status[0]) & IQS7222_SYS_STATUS_ATI_ERROR) {
+ dev_err(&client->dev, "Unexpected ATI error\n");
+ return iqs7222_ati_trigger(iqs7222);
+ }
+
+ if (le16_to_cpu(status[0]) & IQS7222_SYS_STATUS_ATI_ACTIVE)
+ return 0;
+
+ for (i = 0; i < num_chan; i++) {
+ u16 *chan_setup = iqs7222->chan_setup[i];
+
+ if (!(chan_setup[0] & IQS7222_CHAN_SETUP_0_CHAN_EN))
+ continue;
+
+ for (j = 0; j < ARRAY_SIZE(iqs7222_kp_events); j++) {
+ /*
+ * Proximity state begins at offset 2 and spills into
+ * offset 3 for devices with more than 16 channels.
+ *
+ * Touch state begins at the first offset immediately
+ * following proximity state.
+ */
+ int k = 2 + j * (num_chan > 16 ? 2 : 1);
+ u16 state = le16_to_cpu(status[k + i / 16]);
+
+ if (!iqs7222->kp_type[i][j])
+ continue;
+
+ input_event(iqs7222->keypad,
+ iqs7222->kp_type[i][j],
+ iqs7222->kp_code[i][j],
+ !!(state & BIT(i % 16)));
+ }
+ }
+
+ for (i = 0; i < dev_desc->reg_grps[IQS7222_REG_GRP_SLDR].num_row; i++) {
+ u16 *sldr_setup = iqs7222->sldr_setup[i];
+ u16 sldr_pos = le16_to_cpu(status[4 + i]);
+ u16 state = le16_to_cpu(status[6 + i]);
+
+ if (!(sldr_setup[0] & IQS7222_SLDR_SETUP_0_CHAN_CNT_MASK))
+ continue;
+
+ if (sldr_pos < dev_desc->sldr_res)
+ input_report_abs(iqs7222->keypad, iqs7222->sl_axis[i],
+ sldr_pos);
+
+ input_report_key(iqs7222->keypad, iqs7222->sl_code[i][0],
+ sldr_pos < dev_desc->sldr_res);
+
+ /*
+ * A maximum resolution indicates the device does not support
+ * gestures, in which case the remaining fields are ignored.
+ */
+ if (dev_desc->sldr_res == U16_MAX)
+ continue;
+
+ if (!(le16_to_cpu(status[1]) & IQS7222_EVENT_MASK_SLDR << i))
+ continue;
+
+ /*
+ * Skip the press/release event, as it does not have separate
+ * status fields and is handled separately.
+ */
+ for (j = 1; j < ARRAY_SIZE(iqs7222_sl_events); j++) {
+ u16 mask = iqs7222_sl_events[j].mask;
+ u16 val = iqs7222_sl_events[j].val;
+
+ input_report_key(iqs7222->keypad,
+ iqs7222->sl_code[i][j],
+ (state & mask) == val);
+ }
+
+ input_sync(iqs7222->keypad);
+
+ for (j = 1; j < ARRAY_SIZE(iqs7222_sl_events); j++)
+ input_report_key(iqs7222->keypad,
+ iqs7222->sl_code[i][j], 0);
+ }
+
+ input_sync(iqs7222->keypad);
+
+ return 0;
+}
+
+static irqreturn_t iqs7222_irq(int irq, void *context)
+{
+ struct iqs7222_private *iqs7222 = context;
+
+ return iqs7222_report(iqs7222) ? IRQ_NONE : IRQ_HANDLED;
+}
+
+static int iqs7222_probe(struct i2c_client *client)
+{
+ struct iqs7222_private *iqs7222;
+ unsigned long irq_flags;
+ int error, irq;
+
+ iqs7222 = devm_kzalloc(&client->dev, sizeof(*iqs7222), GFP_KERNEL);
+ if (!iqs7222)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, iqs7222);
+ iqs7222->client = client;
+
+ iqs7222->keypad = devm_input_allocate_device(&client->dev);
+ if (!iqs7222->keypad)
+ return -ENOMEM;
+
+ iqs7222->keypad->name = client->name;
+ iqs7222->keypad->id.bustype = BUS_I2C;
+
+ /*
+ * The RDY pin behaves as an interrupt, but must also be polled ahead
+ * of unsolicited I2C communication. As such, it is first opened as a
+ * GPIO and then passed to gpiod_to_irq() to register the interrupt.
+ */
+ iqs7222->irq_gpio = devm_gpiod_get(&client->dev, "irq", GPIOD_IN);
+ if (IS_ERR(iqs7222->irq_gpio)) {
+ error = PTR_ERR(iqs7222->irq_gpio);
+ dev_err(&client->dev, "Failed to request IRQ GPIO: %d\n",
+ error);
+ return error;
+ }
+
+ iqs7222->reset_gpio = devm_gpiod_get_optional(&client->dev, "reset",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(iqs7222->reset_gpio)) {
+ error = PTR_ERR(iqs7222->reset_gpio);
+ dev_err(&client->dev, "Failed to request reset GPIO: %d\n",
+ error);
+ return error;
+ }
+
+ error = iqs7222_hard_reset(iqs7222);
+ if (error)
+ return error;
+
+ error = iqs7222_dev_info(iqs7222);
+ if (error)
+ return error;
+
+ error = iqs7222_dev_init(iqs7222, READ);
+ if (error)
+ return error;
+
+ error = iqs7222_parse_all(iqs7222);
+ if (error)
+ return error;
+
+ error = iqs7222_dev_init(iqs7222, WRITE);
+ if (error)
+ return error;
+
+ error = iqs7222_report(iqs7222);
+ if (error)
+ return error;
+
+ error = input_register_device(iqs7222->keypad);
+ if (error) {
+ dev_err(&client->dev, "Failed to register device: %d\n", error);
+ return error;
+ }
+
+ irq = gpiod_to_irq(iqs7222->irq_gpio);
+ if (irq < 0)
+ return irq;
+
+ irq_flags = gpiod_is_active_low(iqs7222->irq_gpio) ? IRQF_TRIGGER_LOW
+ : IRQF_TRIGGER_HIGH;
+ irq_flags |= IRQF_ONESHOT;
+
+ error = devm_request_threaded_irq(&client->dev, irq, NULL, iqs7222_irq,
+ irq_flags, client->name, iqs7222);
+ if (error)
+ dev_err(&client->dev, "Failed to request IRQ: %d\n", error);
+
+ return error;
+}
+
+static const struct of_device_id iqs7222_of_match[] = {
+ { .compatible = "azoteq,iqs7222a" },
+ { .compatible = "azoteq,iqs7222b" },
+ { .compatible = "azoteq,iqs7222c" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, iqs7222_of_match);
+
+static struct i2c_driver iqs7222_i2c_driver = {
+ .driver = {
+ .name = "iqs7222",
+ .of_match_table = iqs7222_of_match,
+ },
+ .probe_new = iqs7222_probe,
+};
+module_i2c_driver(iqs7222_i2c_driver);
+
+MODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>");
+MODULE_DESCRIPTION("Azoteq IQS7222A/B/C Capacitive Touch Controller");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/keyspan_remote.c b/drivers/input/misc/keyspan_remote.c
new file mode 100644
index 000000000..bee4b1376
--- /dev/null
+++ b/drivers/input/misc/keyspan_remote.c
@@ -0,0 +1,590 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * keyspan_remote: USB driver for the Keyspan DMR
+ *
+ * Copyright (C) 2005 Zymeta Corporation - Michael Downey (downey@zymeta.com)
+ *
+ * This driver has been put together with the support of Innosys, Inc.
+ * and Keyspan, Inc the manufacturers of the Keyspan USB DMR product.
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/usb/input.h>
+
+/* Parameters that can be passed to the driver. */
+static int debug;
+module_param(debug, int, 0444);
+MODULE_PARM_DESC(debug, "Enable extra debug messages and information");
+
+/* Vendor and product ids */
+#define USB_KEYSPAN_VENDOR_ID 0x06CD
+#define USB_KEYSPAN_PRODUCT_UIA11 0x0202
+
+/* Defines for converting the data from the remote. */
+#define ZERO 0x18
+#define ZERO_MASK 0x1F /* 5 bits for a 0 */
+#define ONE 0x3C
+#define ONE_MASK 0x3F /* 6 bits for a 1 */
+#define SYNC 0x3F80
+#define SYNC_MASK 0x3FFF /* 14 bits for a SYNC sequence */
+#define STOP 0x00
+#define STOP_MASK 0x1F /* 5 bits for the STOP sequence */
+#define GAP 0xFF
+
+#define RECV_SIZE 8 /* The UIA-11 type have a 8 byte limit. */
+
+/*
+ * Table that maps the 31 possible keycodes to input keys.
+ * Currently there are 15 and 17 button models so RESERVED codes
+ * are blank areas in the mapping.
+ */
+static const unsigned short keyspan_key_table[] = {
+ KEY_RESERVED, /* 0 is just a place holder. */
+ KEY_RESERVED,
+ KEY_STOP,
+ KEY_PLAYCD,
+ KEY_RESERVED,
+ KEY_PREVIOUSSONG,
+ KEY_REWIND,
+ KEY_FORWARD,
+ KEY_NEXTSONG,
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_PAUSE,
+ KEY_VOLUMEUP,
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_VOLUMEDOWN,
+ KEY_RESERVED,
+ KEY_UP,
+ KEY_RESERVED,
+ KEY_MUTE,
+ KEY_LEFT,
+ KEY_ENTER,
+ KEY_RIGHT,
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_DOWN,
+ KEY_RESERVED,
+ KEY_KPASTERISK,
+ KEY_RESERVED,
+ KEY_MENU
+};
+
+/* table of devices that work with this driver */
+static const struct usb_device_id keyspan_table[] = {
+ { USB_DEVICE(USB_KEYSPAN_VENDOR_ID, USB_KEYSPAN_PRODUCT_UIA11) },
+ { } /* Terminating entry */
+};
+
+/* Structure to store all the real stuff that a remote sends to us. */
+struct keyspan_message {
+ u16 system;
+ u8 button;
+ u8 toggle;
+};
+
+/* Structure used for all the bit testing magic needed to be done. */
+struct bit_tester {
+ u32 tester;
+ int len;
+ int pos;
+ int bits_left;
+ u8 buffer[32];
+};
+
+/* Structure to hold all of our driver specific stuff */
+struct usb_keyspan {
+ char name[128];
+ char phys[64];
+ unsigned short keymap[ARRAY_SIZE(keyspan_key_table)];
+ struct usb_device *udev;
+ struct input_dev *input;
+ struct usb_interface *interface;
+ struct usb_endpoint_descriptor *in_endpoint;
+ struct urb* irq_urb;
+ int open;
+ dma_addr_t in_dma;
+ unsigned char *in_buffer;
+
+ /* variables used to parse messages from remote. */
+ struct bit_tester data;
+ int stage;
+ int toggle;
+};
+
+static struct usb_driver keyspan_driver;
+
+/*
+ * Debug routine that prints out what we've received from the remote.
+ */
+static void keyspan_print(struct usb_keyspan* dev) /*unsigned char* data)*/
+{
+ char codes[4 * RECV_SIZE];
+ int i;
+
+ for (i = 0; i < RECV_SIZE; i++)
+ snprintf(codes + i * 3, 4, "%02x ", dev->in_buffer[i]);
+
+ dev_info(&dev->udev->dev, "%s\n", codes);
+}
+
+/*
+ * Routine that manages the bit_tester structure. It makes sure that there are
+ * at least bits_needed bits loaded into the tester.
+ */
+static int keyspan_load_tester(struct usb_keyspan* dev, int bits_needed)
+{
+ if (dev->data.bits_left >= bits_needed)
+ return 0;
+
+ /*
+ * Somehow we've missed the last message. The message will be repeated
+ * though so it's not too big a deal
+ */
+ if (dev->data.pos >= dev->data.len) {
+ dev_dbg(&dev->interface->dev,
+ "%s - Error ran out of data. pos: %d, len: %d\n",
+ __func__, dev->data.pos, dev->data.len);
+ return -1;
+ }
+
+ /* Load as much as we can into the tester. */
+ while ((dev->data.bits_left + 7 < (sizeof(dev->data.tester) * 8)) &&
+ (dev->data.pos < dev->data.len)) {
+ dev->data.tester += (dev->data.buffer[dev->data.pos++] << dev->data.bits_left);
+ dev->data.bits_left += 8;
+ }
+
+ return 0;
+}
+
+static void keyspan_report_button(struct usb_keyspan *remote, int button, int press)
+{
+ struct input_dev *input = remote->input;
+
+ input_event(input, EV_MSC, MSC_SCAN, button);
+ input_report_key(input, remote->keymap[button], press);
+ input_sync(input);
+}
+
+/*
+ * Routine that handles all the logic needed to parse out the message from the remote.
+ */
+static void keyspan_check_data(struct usb_keyspan *remote)
+{
+ int i;
+ int found = 0;
+ struct keyspan_message message;
+
+ switch(remote->stage) {
+ case 0:
+ /*
+ * In stage 0 we want to find the start of a message. The remote sends a 0xFF as filler.
+ * So the first byte that isn't a FF should be the start of a new message.
+ */
+ for (i = 0; i < RECV_SIZE && remote->in_buffer[i] == GAP; ++i);
+
+ if (i < RECV_SIZE) {
+ memcpy(remote->data.buffer, remote->in_buffer, RECV_SIZE);
+ remote->data.len = RECV_SIZE;
+ remote->data.pos = 0;
+ remote->data.tester = 0;
+ remote->data.bits_left = 0;
+ remote->stage = 1;
+ }
+ break;
+
+ case 1:
+ /*
+ * Stage 1 we should have 16 bytes and should be able to detect a
+ * SYNC. The SYNC is 14 bits, 7 0's and then 7 1's.
+ */
+ memcpy(remote->data.buffer + remote->data.len, remote->in_buffer, RECV_SIZE);
+ remote->data.len += RECV_SIZE;
+
+ found = 0;
+ while ((remote->data.bits_left >= 14 || remote->data.pos < remote->data.len) && !found) {
+ for (i = 0; i < 8; ++i) {
+ if (keyspan_load_tester(remote, 14) != 0) {
+ remote->stage = 0;
+ return;
+ }
+
+ if ((remote->data.tester & SYNC_MASK) == SYNC) {
+ remote->data.tester = remote->data.tester >> 14;
+ remote->data.bits_left -= 14;
+ found = 1;
+ break;
+ } else {
+ remote->data.tester = remote->data.tester >> 1;
+ --remote->data.bits_left;
+ }
+ }
+ }
+
+ if (!found) {
+ remote->stage = 0;
+ remote->data.len = 0;
+ } else {
+ remote->stage = 2;
+ }
+ break;
+
+ case 2:
+ /*
+ * Stage 2 we should have 24 bytes which will be enough for a full
+ * message. We need to parse out the system code, button code,
+ * toggle code, and stop.
+ */
+ memcpy(remote->data.buffer + remote->data.len, remote->in_buffer, RECV_SIZE);
+ remote->data.len += RECV_SIZE;
+
+ message.system = 0;
+ for (i = 0; i < 9; i++) {
+ keyspan_load_tester(remote, 6);
+
+ if ((remote->data.tester & ZERO_MASK) == ZERO) {
+ message.system = message.system << 1;
+ remote->data.tester = remote->data.tester >> 5;
+ remote->data.bits_left -= 5;
+ } else if ((remote->data.tester & ONE_MASK) == ONE) {
+ message.system = (message.system << 1) + 1;
+ remote->data.tester = remote->data.tester >> 6;
+ remote->data.bits_left -= 6;
+ } else {
+ dev_err(&remote->interface->dev,
+ "%s - Unknown sequence found in system data.\n",
+ __func__);
+ remote->stage = 0;
+ return;
+ }
+ }
+
+ message.button = 0;
+ for (i = 0; i < 5; i++) {
+ keyspan_load_tester(remote, 6);
+
+ if ((remote->data.tester & ZERO_MASK) == ZERO) {
+ message.button = message.button << 1;
+ remote->data.tester = remote->data.tester >> 5;
+ remote->data.bits_left -= 5;
+ } else if ((remote->data.tester & ONE_MASK) == ONE) {
+ message.button = (message.button << 1) + 1;
+ remote->data.tester = remote->data.tester >> 6;
+ remote->data.bits_left -= 6;
+ } else {
+ dev_err(&remote->interface->dev,
+ "%s - Unknown sequence found in button data.\n",
+ __func__);
+ remote->stage = 0;
+ return;
+ }
+ }
+
+ keyspan_load_tester(remote, 6);
+ if ((remote->data.tester & ZERO_MASK) == ZERO) {
+ message.toggle = 0;
+ remote->data.tester = remote->data.tester >> 5;
+ remote->data.bits_left -= 5;
+ } else if ((remote->data.tester & ONE_MASK) == ONE) {
+ message.toggle = 1;
+ remote->data.tester = remote->data.tester >> 6;
+ remote->data.bits_left -= 6;
+ } else {
+ dev_err(&remote->interface->dev,
+ "%s - Error in message, invalid toggle.\n",
+ __func__);
+ remote->stage = 0;
+ return;
+ }
+
+ keyspan_load_tester(remote, 5);
+ if ((remote->data.tester & STOP_MASK) == STOP) {
+ remote->data.tester = remote->data.tester >> 5;
+ remote->data.bits_left -= 5;
+ } else {
+ dev_err(&remote->interface->dev,
+ "Bad message received, no stop bit found.\n");
+ }
+
+ dev_dbg(&remote->interface->dev,
+ "%s found valid message: system: %d, button: %d, toggle: %d\n",
+ __func__, message.system, message.button, message.toggle);
+
+ if (message.toggle != remote->toggle) {
+ keyspan_report_button(remote, message.button, 1);
+ keyspan_report_button(remote, message.button, 0);
+ remote->toggle = message.toggle;
+ }
+
+ remote->stage = 0;
+ break;
+ }
+}
+
+/*
+ * Routine for sending all the initialization messages to the remote.
+ */
+static int keyspan_setup(struct usb_device* dev)
+{
+ int retval = 0;
+
+ retval = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+ 0x11, 0x40, 0x5601, 0x0, NULL, 0,
+ USB_CTRL_SET_TIMEOUT);
+ if (retval) {
+ dev_dbg(&dev->dev, "%s - failed to set bit rate due to error: %d\n",
+ __func__, retval);
+ return(retval);
+ }
+
+ retval = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+ 0x44, 0x40, 0x0, 0x0, NULL, 0,
+ USB_CTRL_SET_TIMEOUT);
+ if (retval) {
+ dev_dbg(&dev->dev, "%s - failed to set resume sensitivity due to error: %d\n",
+ __func__, retval);
+ return(retval);
+ }
+
+ retval = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+ 0x22, 0x40, 0x0, 0x0, NULL, 0,
+ USB_CTRL_SET_TIMEOUT);
+ if (retval) {
+ dev_dbg(&dev->dev, "%s - failed to turn receive on due to error: %d\n",
+ __func__, retval);
+ return(retval);
+ }
+
+ dev_dbg(&dev->dev, "%s - Setup complete.\n", __func__);
+ return(retval);
+}
+
+/*
+ * Routine used to handle a new message that has come in.
+ */
+static void keyspan_irq_recv(struct urb *urb)
+{
+ struct usb_keyspan *dev = urb->context;
+ int retval;
+
+ /* Check our status in case we need to bail out early. */
+ switch (urb->status) {
+ case 0:
+ break;
+
+ /* Device went away so don't keep trying to read from it. */
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ return;
+
+ default:
+ goto resubmit;
+ }
+
+ if (debug)
+ keyspan_print(dev);
+
+ keyspan_check_data(dev);
+
+resubmit:
+ retval = usb_submit_urb(urb, GFP_ATOMIC);
+ if (retval)
+ dev_err(&dev->interface->dev,
+ "%s - usb_submit_urb failed with result: %d\n",
+ __func__, retval);
+}
+
+static int keyspan_open(struct input_dev *dev)
+{
+ struct usb_keyspan *remote = input_get_drvdata(dev);
+
+ remote->irq_urb->dev = remote->udev;
+ if (usb_submit_urb(remote->irq_urb, GFP_KERNEL))
+ return -EIO;
+
+ return 0;
+}
+
+static void keyspan_close(struct input_dev *dev)
+{
+ struct usb_keyspan *remote = input_get_drvdata(dev);
+
+ usb_kill_urb(remote->irq_urb);
+}
+
+static struct usb_endpoint_descriptor *keyspan_get_in_endpoint(struct usb_host_interface *iface)
+{
+
+ struct usb_endpoint_descriptor *endpoint;
+ int i;
+
+ for (i = 0; i < iface->desc.bNumEndpoints; ++i) {
+ endpoint = &iface->endpoint[i].desc;
+
+ if (usb_endpoint_is_int_in(endpoint)) {
+ /* we found our interrupt in endpoint */
+ return endpoint;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Routine that sets up the driver to handle a specific USB device detected on the bus.
+ */
+static int keyspan_probe(struct usb_interface *interface, const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev(interface);
+ struct usb_endpoint_descriptor *endpoint;
+ struct usb_keyspan *remote;
+ struct input_dev *input_dev;
+ int i, error;
+
+ endpoint = keyspan_get_in_endpoint(interface->cur_altsetting);
+ if (!endpoint)
+ return -ENODEV;
+
+ remote = kzalloc(sizeof(*remote), GFP_KERNEL);
+ input_dev = input_allocate_device();
+ if (!remote || !input_dev) {
+ error = -ENOMEM;
+ goto fail1;
+ }
+
+ remote->udev = udev;
+ remote->input = input_dev;
+ remote->interface = interface;
+ remote->in_endpoint = endpoint;
+ remote->toggle = -1; /* Set to -1 so we will always not match the toggle from the first remote message. */
+
+ remote->in_buffer = usb_alloc_coherent(udev, RECV_SIZE, GFP_KERNEL, &remote->in_dma);
+ if (!remote->in_buffer) {
+ error = -ENOMEM;
+ goto fail1;
+ }
+
+ remote->irq_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!remote->irq_urb) {
+ error = -ENOMEM;
+ goto fail2;
+ }
+
+ error = keyspan_setup(udev);
+ if (error) {
+ error = -ENODEV;
+ goto fail3;
+ }
+
+ if (udev->manufacturer)
+ strscpy(remote->name, udev->manufacturer, sizeof(remote->name));
+
+ if (udev->product) {
+ if (udev->manufacturer)
+ strlcat(remote->name, " ", sizeof(remote->name));
+ strlcat(remote->name, udev->product, sizeof(remote->name));
+ }
+
+ if (!strlen(remote->name))
+ snprintf(remote->name, sizeof(remote->name),
+ "USB Keyspan Remote %04x:%04x",
+ le16_to_cpu(udev->descriptor.idVendor),
+ le16_to_cpu(udev->descriptor.idProduct));
+
+ usb_make_path(udev, remote->phys, sizeof(remote->phys));
+ strlcat(remote->phys, "/input0", sizeof(remote->phys));
+ memcpy(remote->keymap, keyspan_key_table, sizeof(remote->keymap));
+
+ input_dev->name = remote->name;
+ input_dev->phys = remote->phys;
+ usb_to_input_id(udev, &input_dev->id);
+ input_dev->dev.parent = &interface->dev;
+ input_dev->keycode = remote->keymap;
+ input_dev->keycodesize = sizeof(unsigned short);
+ input_dev->keycodemax = ARRAY_SIZE(remote->keymap);
+
+ input_set_capability(input_dev, EV_MSC, MSC_SCAN);
+ __set_bit(EV_KEY, input_dev->evbit);
+ for (i = 0; i < ARRAY_SIZE(keyspan_key_table); i++)
+ __set_bit(keyspan_key_table[i], input_dev->keybit);
+ __clear_bit(KEY_RESERVED, input_dev->keybit);
+
+ input_set_drvdata(input_dev, remote);
+
+ input_dev->open = keyspan_open;
+ input_dev->close = keyspan_close;
+
+ /*
+ * Initialize the URB to access the device.
+ * The urb gets sent to the device in keyspan_open()
+ */
+ usb_fill_int_urb(remote->irq_urb,
+ remote->udev,
+ usb_rcvintpipe(remote->udev, endpoint->bEndpointAddress),
+ remote->in_buffer, RECV_SIZE, keyspan_irq_recv, remote,
+ endpoint->bInterval);
+ remote->irq_urb->transfer_dma = remote->in_dma;
+ remote->irq_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+ /* we can register the device now, as it is ready */
+ error = input_register_device(remote->input);
+ if (error)
+ goto fail3;
+
+ /* save our data pointer in this interface device */
+ usb_set_intfdata(interface, remote);
+
+ return 0;
+
+ fail3: usb_free_urb(remote->irq_urb);
+ fail2: usb_free_coherent(udev, RECV_SIZE, remote->in_buffer, remote->in_dma);
+ fail1: kfree(remote);
+ input_free_device(input_dev);
+
+ return error;
+}
+
+/*
+ * Routine called when a device is disconnected from the USB.
+ */
+static void keyspan_disconnect(struct usb_interface *interface)
+{
+ struct usb_keyspan *remote;
+
+ remote = usb_get_intfdata(interface);
+ usb_set_intfdata(interface, NULL);
+
+ if (remote) { /* We have a valid driver structure so clean up everything we allocated. */
+ input_unregister_device(remote->input);
+ usb_kill_urb(remote->irq_urb);
+ usb_free_urb(remote->irq_urb);
+ usb_free_coherent(remote->udev, RECV_SIZE, remote->in_buffer, remote->in_dma);
+ kfree(remote);
+ }
+}
+
+/*
+ * Standard driver set up sections
+ */
+static struct usb_driver keyspan_driver =
+{
+ .name = "keyspan_remote",
+ .probe = keyspan_probe,
+ .disconnect = keyspan_disconnect,
+ .id_table = keyspan_table
+};
+
+module_usb_driver(keyspan_driver);
+
+MODULE_DEVICE_TABLE(usb, keyspan_table);
+MODULE_AUTHOR("Michael Downey <downey@zymeta.com>");
+MODULE_DESCRIPTION("Driver for the USB Keyspan remote control.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/kxtj9.c b/drivers/input/misc/kxtj9.c
new file mode 100644
index 000000000..bbb81617c
--- /dev/null
+++ b/drivers/input/misc/kxtj9.c
@@ -0,0 +1,550 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2011 Kionix, Inc.
+ * Written by Chris Hudson <chudson@kionix.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/input/kxtj9.h>
+
+#define NAME "kxtj9"
+#define G_MAX 8000
+/* OUTPUT REGISTERS */
+#define XOUT_L 0x06
+#define WHO_AM_I 0x0F
+/* CONTROL REGISTERS */
+#define INT_REL 0x1A
+#define CTRL_REG1 0x1B
+#define INT_CTRL1 0x1E
+#define DATA_CTRL 0x21
+/* CONTROL REGISTER 1 BITS */
+#define PC1_OFF 0x7F
+#define PC1_ON (1 << 7)
+/* Data ready funtion enable bit: set during probe if using irq mode */
+#define DRDYE (1 << 5)
+/* DATA CONTROL REGISTER BITS */
+#define ODR12_5F 0
+#define ODR25F 1
+#define ODR50F 2
+#define ODR100F 3
+#define ODR200F 4
+#define ODR400F 5
+#define ODR800F 6
+/* INTERRUPT CONTROL REGISTER 1 BITS */
+/* Set these during probe if using irq mode */
+#define KXTJ9_IEL (1 << 3)
+#define KXTJ9_IEA (1 << 4)
+#define KXTJ9_IEN (1 << 5)
+/* INPUT_ABS CONSTANTS */
+#define FUZZ 3
+#define FLAT 3
+/* RESUME STATE INDICES */
+#define RES_DATA_CTRL 0
+#define RES_CTRL_REG1 1
+#define RES_INT_CTRL1 2
+#define RESUME_ENTRIES 3
+
+/*
+ * The following table lists the maximum appropriate poll interval for each
+ * available output data rate.
+ */
+static const struct {
+ unsigned int cutoff;
+ u8 mask;
+} kxtj9_odr_table[] = {
+ { 3, ODR800F },
+ { 5, ODR400F },
+ { 10, ODR200F },
+ { 20, ODR100F },
+ { 40, ODR50F },
+ { 80, ODR25F },
+ { 0, ODR12_5F},
+};
+
+struct kxtj9_data {
+ struct i2c_client *client;
+ struct kxtj9_platform_data pdata;
+ struct input_dev *input_dev;
+ unsigned int last_poll_interval;
+ u8 shift;
+ u8 ctrl_reg1;
+ u8 data_ctrl;
+ u8 int_ctrl;
+};
+
+static int kxtj9_i2c_read(struct kxtj9_data *tj9, u8 addr, u8 *data, int len)
+{
+ struct i2c_msg msgs[] = {
+ {
+ .addr = tj9->client->addr,
+ .flags = tj9->client->flags,
+ .len = 1,
+ .buf = &addr,
+ },
+ {
+ .addr = tj9->client->addr,
+ .flags = tj9->client->flags | I2C_M_RD,
+ .len = len,
+ .buf = data,
+ },
+ };
+
+ return i2c_transfer(tj9->client->adapter, msgs, 2);
+}
+
+static void kxtj9_report_acceleration_data(struct kxtj9_data *tj9)
+{
+ s16 acc_data[3]; /* Data bytes from hardware xL, xH, yL, yH, zL, zH */
+ s16 x, y, z;
+ int err;
+
+ err = kxtj9_i2c_read(tj9, XOUT_L, (u8 *)acc_data, 6);
+ if (err < 0)
+ dev_err(&tj9->client->dev, "accelerometer data read failed\n");
+
+ x = le16_to_cpu(acc_data[tj9->pdata.axis_map_x]);
+ y = le16_to_cpu(acc_data[tj9->pdata.axis_map_y]);
+ z = le16_to_cpu(acc_data[tj9->pdata.axis_map_z]);
+
+ x >>= tj9->shift;
+ y >>= tj9->shift;
+ z >>= tj9->shift;
+
+ input_report_abs(tj9->input_dev, ABS_X, tj9->pdata.negate_x ? -x : x);
+ input_report_abs(tj9->input_dev, ABS_Y, tj9->pdata.negate_y ? -y : y);
+ input_report_abs(tj9->input_dev, ABS_Z, tj9->pdata.negate_z ? -z : z);
+ input_sync(tj9->input_dev);
+}
+
+static irqreturn_t kxtj9_isr(int irq, void *dev)
+{
+ struct kxtj9_data *tj9 = dev;
+ int err;
+
+ /* data ready is the only possible interrupt type */
+ kxtj9_report_acceleration_data(tj9);
+
+ err = i2c_smbus_read_byte_data(tj9->client, INT_REL);
+ if (err < 0)
+ dev_err(&tj9->client->dev,
+ "error clearing interrupt status: %d\n", err);
+
+ return IRQ_HANDLED;
+}
+
+static int kxtj9_update_g_range(struct kxtj9_data *tj9, u8 new_g_range)
+{
+ switch (new_g_range) {
+ case KXTJ9_G_2G:
+ tj9->shift = 4;
+ break;
+ case KXTJ9_G_4G:
+ tj9->shift = 3;
+ break;
+ case KXTJ9_G_8G:
+ tj9->shift = 2;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ tj9->ctrl_reg1 &= 0xe7;
+ tj9->ctrl_reg1 |= new_g_range;
+
+ return 0;
+}
+
+static int kxtj9_update_odr(struct kxtj9_data *tj9, unsigned int poll_interval)
+{
+ int err;
+ int i;
+
+ /* Use the lowest ODR that can support the requested poll interval */
+ for (i = 0; i < ARRAY_SIZE(kxtj9_odr_table); i++) {
+ tj9->data_ctrl = kxtj9_odr_table[i].mask;
+ if (poll_interval < kxtj9_odr_table[i].cutoff)
+ break;
+ }
+
+ err = i2c_smbus_write_byte_data(tj9->client, CTRL_REG1, 0);
+ if (err < 0)
+ return err;
+
+ err = i2c_smbus_write_byte_data(tj9->client, DATA_CTRL, tj9->data_ctrl);
+ if (err < 0)
+ return err;
+
+ err = i2c_smbus_write_byte_data(tj9->client, CTRL_REG1, tj9->ctrl_reg1);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static int kxtj9_device_power_on(struct kxtj9_data *tj9)
+{
+ if (tj9->pdata.power_on)
+ return tj9->pdata.power_on();
+
+ return 0;
+}
+
+static void kxtj9_device_power_off(struct kxtj9_data *tj9)
+{
+ int err;
+
+ tj9->ctrl_reg1 &= PC1_OFF;
+ err = i2c_smbus_write_byte_data(tj9->client, CTRL_REG1, tj9->ctrl_reg1);
+ if (err < 0)
+ dev_err(&tj9->client->dev, "soft power off failed\n");
+
+ if (tj9->pdata.power_off)
+ tj9->pdata.power_off();
+}
+
+static int kxtj9_enable(struct kxtj9_data *tj9)
+{
+ int err;
+
+ err = kxtj9_device_power_on(tj9);
+ if (err < 0)
+ return err;
+
+ /* ensure that PC1 is cleared before updating control registers */
+ err = i2c_smbus_write_byte_data(tj9->client, CTRL_REG1, 0);
+ if (err < 0)
+ return err;
+
+ /* only write INT_CTRL_REG1 if in irq mode */
+ if (tj9->client->irq) {
+ err = i2c_smbus_write_byte_data(tj9->client,
+ INT_CTRL1, tj9->int_ctrl);
+ if (err < 0)
+ return err;
+ }
+
+ err = kxtj9_update_g_range(tj9, tj9->pdata.g_range);
+ if (err < 0)
+ return err;
+
+ /* turn on outputs */
+ tj9->ctrl_reg1 |= PC1_ON;
+ err = i2c_smbus_write_byte_data(tj9->client, CTRL_REG1, tj9->ctrl_reg1);
+ if (err < 0)
+ return err;
+
+ err = kxtj9_update_odr(tj9, tj9->last_poll_interval);
+ if (err < 0)
+ return err;
+
+ /* clear initial interrupt if in irq mode */
+ if (tj9->client->irq) {
+ err = i2c_smbus_read_byte_data(tj9->client, INT_REL);
+ if (err < 0) {
+ dev_err(&tj9->client->dev,
+ "error clearing interrupt: %d\n", err);
+ goto fail;
+ }
+ }
+
+ return 0;
+
+fail:
+ kxtj9_device_power_off(tj9);
+ return err;
+}
+
+static void kxtj9_disable(struct kxtj9_data *tj9)
+{
+ kxtj9_device_power_off(tj9);
+}
+
+static int kxtj9_input_open(struct input_dev *input)
+{
+ struct kxtj9_data *tj9 = input_get_drvdata(input);
+
+ return kxtj9_enable(tj9);
+}
+
+static void kxtj9_input_close(struct input_dev *dev)
+{
+ struct kxtj9_data *tj9 = input_get_drvdata(dev);
+
+ kxtj9_disable(tj9);
+}
+
+/*
+ * When IRQ mode is selected, we need to provide an interface to allow the user
+ * to change the output data rate of the part. For consistency, we are using
+ * the set_poll method, which accepts a poll interval in milliseconds, and then
+ * calls update_odr() while passing this value as an argument. In IRQ mode, the
+ * data outputs will not be read AT the requested poll interval, rather, the
+ * lowest ODR that can support the requested interval. The client application
+ * will be responsible for retrieving data from the input node at the desired
+ * interval.
+ */
+
+/* Returns currently selected poll interval (in ms) */
+static ssize_t kxtj9_get_poll(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct kxtj9_data *tj9 = i2c_get_clientdata(client);
+
+ return sprintf(buf, "%d\n", tj9->last_poll_interval);
+}
+
+/* Allow users to select a new poll interval (in ms) */
+static ssize_t kxtj9_set_poll(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct kxtj9_data *tj9 = i2c_get_clientdata(client);
+ struct input_dev *input_dev = tj9->input_dev;
+ unsigned int interval;
+ int error;
+
+ error = kstrtouint(buf, 10, &interval);
+ if (error < 0)
+ return error;
+
+ /* Lock the device to prevent races with open/close (and itself) */
+ mutex_lock(&input_dev->mutex);
+
+ disable_irq(client->irq);
+
+ /*
+ * Set current interval to the greater of the minimum interval or
+ * the requested interval
+ */
+ tj9->last_poll_interval = max(interval, tj9->pdata.min_interval);
+
+ kxtj9_update_odr(tj9, tj9->last_poll_interval);
+
+ enable_irq(client->irq);
+ mutex_unlock(&input_dev->mutex);
+
+ return count;
+}
+
+static DEVICE_ATTR(poll, S_IRUGO|S_IWUSR, kxtj9_get_poll, kxtj9_set_poll);
+
+static struct attribute *kxtj9_attributes[] = {
+ &dev_attr_poll.attr,
+ NULL
+};
+
+static struct attribute_group kxtj9_attribute_group = {
+ .attrs = kxtj9_attributes
+};
+
+static void kxtj9_poll(struct input_dev *input)
+{
+ struct kxtj9_data *tj9 = input_get_drvdata(input);
+ unsigned int poll_interval = input_get_poll_interval(input);
+
+ kxtj9_report_acceleration_data(tj9);
+
+ if (poll_interval != tj9->last_poll_interval) {
+ kxtj9_update_odr(tj9, poll_interval);
+ tj9->last_poll_interval = poll_interval;
+ }
+}
+
+static void kxtj9_platform_exit(void *data)
+{
+ struct kxtj9_data *tj9 = data;
+
+ if (tj9->pdata.exit)
+ tj9->pdata.exit();
+}
+
+static int kxtj9_verify(struct kxtj9_data *tj9)
+{
+ int retval;
+
+ retval = kxtj9_device_power_on(tj9);
+ if (retval < 0)
+ return retval;
+
+ retval = i2c_smbus_read_byte_data(tj9->client, WHO_AM_I);
+ if (retval < 0) {
+ dev_err(&tj9->client->dev, "read err int source\n");
+ goto out;
+ }
+
+ retval = (retval != 0x07 && retval != 0x08) ? -EIO : 0;
+
+out:
+ kxtj9_device_power_off(tj9);
+ return retval;
+}
+
+static int kxtj9_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ const struct kxtj9_platform_data *pdata =
+ dev_get_platdata(&client->dev);
+ struct kxtj9_data *tj9;
+ struct input_dev *input_dev;
+ int err;
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_I2C | I2C_FUNC_SMBUS_BYTE_DATA)) {
+ dev_err(&client->dev, "client is not i2c capable\n");
+ return -ENXIO;
+ }
+
+ if (!pdata) {
+ dev_err(&client->dev, "platform data is NULL; exiting\n");
+ return -EINVAL;
+ }
+
+ tj9 = devm_kzalloc(&client->dev, sizeof(*tj9), GFP_KERNEL);
+ if (!tj9) {
+ dev_err(&client->dev,
+ "failed to allocate memory for module data\n");
+ return -ENOMEM;
+ }
+
+ tj9->client = client;
+ tj9->pdata = *pdata;
+
+ if (pdata->init) {
+ err = pdata->init();
+ if (err < 0)
+ return err;
+ }
+
+ err = devm_add_action_or_reset(&client->dev, kxtj9_platform_exit, tj9);
+ if (err)
+ return err;
+
+ err = kxtj9_verify(tj9);
+ if (err < 0) {
+ dev_err(&client->dev, "device not recognized\n");
+ return err;
+ }
+
+ i2c_set_clientdata(client, tj9);
+
+ tj9->ctrl_reg1 = tj9->pdata.res_12bit | tj9->pdata.g_range;
+ tj9->last_poll_interval = tj9->pdata.init_interval;
+
+ input_dev = devm_input_allocate_device(&client->dev);
+ if (!input_dev) {
+ dev_err(&client->dev, "input device allocate failed\n");
+ return -ENOMEM;
+ }
+
+ input_set_drvdata(input_dev, tj9);
+ tj9->input_dev = input_dev;
+
+ input_dev->name = "kxtj9_accel";
+ input_dev->id.bustype = BUS_I2C;
+
+ input_dev->open = kxtj9_input_open;
+ input_dev->close = kxtj9_input_close;
+
+ input_set_abs_params(input_dev, ABS_X, -G_MAX, G_MAX, FUZZ, FLAT);
+ input_set_abs_params(input_dev, ABS_Y, -G_MAX, G_MAX, FUZZ, FLAT);
+ input_set_abs_params(input_dev, ABS_Z, -G_MAX, G_MAX, FUZZ, FLAT);
+
+ if (client->irq <= 0) {
+ err = input_setup_polling(input_dev, kxtj9_poll);
+ if (err)
+ return err;
+ }
+
+ err = input_register_device(input_dev);
+ if (err) {
+ dev_err(&client->dev,
+ "unable to register input polled device %s: %d\n",
+ input_dev->name, err);
+ return err;
+ }
+
+ if (client->irq) {
+ /* If in irq mode, populate INT_CTRL_REG1 and enable DRDY. */
+ tj9->int_ctrl |= KXTJ9_IEN | KXTJ9_IEA | KXTJ9_IEL;
+ tj9->ctrl_reg1 |= DRDYE;
+
+ err = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, kxtj9_isr,
+ IRQF_TRIGGER_RISING |
+ IRQF_ONESHOT,
+ "kxtj9-irq", tj9);
+ if (err) {
+ dev_err(&client->dev, "request irq failed: %d\n", err);
+ return err;
+ }
+
+ err = devm_device_add_group(&client->dev,
+ &kxtj9_attribute_group);
+ if (err) {
+ dev_err(&client->dev, "sysfs create failed: %d\n", err);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+static int __maybe_unused kxtj9_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct kxtj9_data *tj9 = i2c_get_clientdata(client);
+ struct input_dev *input_dev = tj9->input_dev;
+
+ mutex_lock(&input_dev->mutex);
+
+ if (input_device_enabled(input_dev))
+ kxtj9_disable(tj9);
+
+ mutex_unlock(&input_dev->mutex);
+ return 0;
+}
+
+static int __maybe_unused kxtj9_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct kxtj9_data *tj9 = i2c_get_clientdata(client);
+ struct input_dev *input_dev = tj9->input_dev;
+
+ mutex_lock(&input_dev->mutex);
+
+ if (input_device_enabled(input_dev))
+ kxtj9_enable(tj9);
+
+ mutex_unlock(&input_dev->mutex);
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(kxtj9_pm_ops, kxtj9_suspend, kxtj9_resume);
+
+static const struct i2c_device_id kxtj9_id[] = {
+ { NAME, 0 },
+ { },
+};
+
+MODULE_DEVICE_TABLE(i2c, kxtj9_id);
+
+static struct i2c_driver kxtj9_driver = {
+ .driver = {
+ .name = NAME,
+ .pm = &kxtj9_pm_ops,
+ },
+ .probe = kxtj9_probe,
+ .id_table = kxtj9_id,
+};
+
+module_i2c_driver(kxtj9_driver);
+
+MODULE_DESCRIPTION("KXTJ9 accelerometer driver");
+MODULE_AUTHOR("Chris Hudson <chudson@kionix.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/m68kspkr.c b/drivers/input/misc/m68kspkr.c
new file mode 100644
index 000000000..25fcf1467
--- /dev/null
+++ b/drivers/input/misc/m68kspkr.c
@@ -0,0 +1,144 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * m68k beeper driver for Linux
+ *
+ * Copyright (c) 2002 Richard Zidlicky
+ * Copyright (c) 2002 Vojtech Pavlik
+ * Copyright (c) 1992 Orest Zborowski
+ */
+
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/platform_device.h>
+#include <asm/machdep.h>
+#include <asm/io.h>
+
+MODULE_AUTHOR("Richard Zidlicky <rz@linux-m68k.org>");
+MODULE_DESCRIPTION("m68k beeper driver");
+MODULE_LICENSE("GPL");
+
+static struct platform_device *m68kspkr_platform_device;
+
+static int m68kspkr_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
+{
+ unsigned int count = 0;
+
+ if (type != EV_SND)
+ return -1;
+
+ switch (code) {
+ case SND_BELL: if (value) value = 1000;
+ case SND_TONE: break;
+ default: return -1;
+ }
+
+ if (value > 20 && value < 32767)
+ count = 1193182 / value;
+
+ mach_beep(count, -1);
+
+ return 0;
+}
+
+static int m68kspkr_probe(struct platform_device *dev)
+{
+ struct input_dev *input_dev;
+ int err;
+
+ input_dev = input_allocate_device();
+ if (!input_dev)
+ return -ENOMEM;
+
+ input_dev->name = "m68k beeper";
+ input_dev->phys = "m68k/generic";
+ input_dev->id.bustype = BUS_HOST;
+ input_dev->id.vendor = 0x001f;
+ input_dev->id.product = 0x0001;
+ input_dev->id.version = 0x0100;
+ input_dev->dev.parent = &dev->dev;
+
+ input_dev->evbit[0] = BIT_MASK(EV_SND);
+ input_dev->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE);
+ input_dev->event = m68kspkr_event;
+
+ err = input_register_device(input_dev);
+ if (err) {
+ input_free_device(input_dev);
+ return err;
+ }
+
+ platform_set_drvdata(dev, input_dev);
+
+ return 0;
+}
+
+static int m68kspkr_remove(struct platform_device *dev)
+{
+ struct input_dev *input_dev = platform_get_drvdata(dev);
+
+ input_unregister_device(input_dev);
+ /* turn off the speaker */
+ m68kspkr_event(NULL, EV_SND, SND_BELL, 0);
+
+ return 0;
+}
+
+static void m68kspkr_shutdown(struct platform_device *dev)
+{
+ /* turn off the speaker */
+ m68kspkr_event(NULL, EV_SND, SND_BELL, 0);
+}
+
+static struct platform_driver m68kspkr_platform_driver = {
+ .driver = {
+ .name = "m68kspkr",
+ },
+ .probe = m68kspkr_probe,
+ .remove = m68kspkr_remove,
+ .shutdown = m68kspkr_shutdown,
+};
+
+static int __init m68kspkr_init(void)
+{
+ int err;
+
+ if (!mach_beep) {
+ printk(KERN_INFO "m68kspkr: no lowlevel beep support\n");
+ return -ENODEV;
+ }
+
+ err = platform_driver_register(&m68kspkr_platform_driver);
+ if (err)
+ return err;
+
+ m68kspkr_platform_device = platform_device_alloc("m68kspkr", -1);
+ if (!m68kspkr_platform_device) {
+ err = -ENOMEM;
+ goto err_unregister_driver;
+ }
+
+ err = platform_device_add(m68kspkr_platform_device);
+ if (err)
+ goto err_free_device;
+
+ return 0;
+
+ err_free_device:
+ platform_device_put(m68kspkr_platform_device);
+ err_unregister_driver:
+ platform_driver_unregister(&m68kspkr_platform_driver);
+
+ return err;
+}
+
+static void __exit m68kspkr_exit(void)
+{
+ platform_device_unregister(m68kspkr_platform_device);
+ platform_driver_unregister(&m68kspkr_platform_driver);
+}
+
+module_init(m68kspkr_init);
+module_exit(m68kspkr_exit);
diff --git a/drivers/input/misc/max77650-onkey.c b/drivers/input/misc/max77650-onkey.c
new file mode 100644
index 000000000..ee55f22db
--- /dev/null
+++ b/drivers/input/misc/max77650-onkey.c
@@ -0,0 +1,129 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (C) 2018 BayLibre SAS
+// Author: Bartosz Golaszewski <bgolaszewski@baylibre.com>
+//
+// ONKEY driver for MAXIM 77650/77651 charger/power-supply.
+
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/max77650.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define MAX77650_ONKEY_MODE_MASK BIT(3)
+#define MAX77650_ONKEY_MODE_PUSH 0x00
+#define MAX77650_ONKEY_MODE_SLIDE BIT(3)
+
+struct max77650_onkey {
+ struct input_dev *input;
+ unsigned int code;
+};
+
+static irqreturn_t max77650_onkey_falling(int irq, void *data)
+{
+ struct max77650_onkey *onkey = data;
+
+ input_report_key(onkey->input, onkey->code, 0);
+ input_sync(onkey->input);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t max77650_onkey_rising(int irq, void *data)
+{
+ struct max77650_onkey *onkey = data;
+
+ input_report_key(onkey->input, onkey->code, 1);
+ input_sync(onkey->input);
+
+ return IRQ_HANDLED;
+}
+
+static int max77650_onkey_probe(struct platform_device *pdev)
+{
+ int irq_r, irq_f, error, mode;
+ struct max77650_onkey *onkey;
+ struct device *dev, *parent;
+ struct regmap *map;
+ unsigned int type;
+
+ dev = &pdev->dev;
+ parent = dev->parent;
+
+ map = dev_get_regmap(parent, NULL);
+ if (!map)
+ return -ENODEV;
+
+ onkey = devm_kzalloc(dev, sizeof(*onkey), GFP_KERNEL);
+ if (!onkey)
+ return -ENOMEM;
+
+ error = device_property_read_u32(dev, "linux,code", &onkey->code);
+ if (error)
+ onkey->code = KEY_POWER;
+
+ if (device_property_read_bool(dev, "maxim,onkey-slide")) {
+ mode = MAX77650_ONKEY_MODE_SLIDE;
+ type = EV_SW;
+ } else {
+ mode = MAX77650_ONKEY_MODE_PUSH;
+ type = EV_KEY;
+ }
+
+ error = regmap_update_bits(map, MAX77650_REG_CNFG_GLBL,
+ MAX77650_ONKEY_MODE_MASK, mode);
+ if (error)
+ return error;
+
+ irq_f = platform_get_irq_byname(pdev, "nEN_F");
+ if (irq_f < 0)
+ return irq_f;
+
+ irq_r = platform_get_irq_byname(pdev, "nEN_R");
+ if (irq_r < 0)
+ return irq_r;
+
+ onkey->input = devm_input_allocate_device(dev);
+ if (!onkey->input)
+ return -ENOMEM;
+
+ onkey->input->name = "max77650_onkey";
+ onkey->input->phys = "max77650_onkey/input0";
+ onkey->input->id.bustype = BUS_I2C;
+ input_set_capability(onkey->input, type, onkey->code);
+
+ error = devm_request_any_context_irq(dev, irq_f, max77650_onkey_falling,
+ IRQF_ONESHOT, "onkey-down", onkey);
+ if (error < 0)
+ return error;
+
+ error = devm_request_any_context_irq(dev, irq_r, max77650_onkey_rising,
+ IRQF_ONESHOT, "onkey-up", onkey);
+ if (error < 0)
+ return error;
+
+ return input_register_device(onkey->input);
+}
+
+static const struct of_device_id max77650_onkey_of_match[] = {
+ { .compatible = "maxim,max77650-onkey" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, max77650_onkey_of_match);
+
+static struct platform_driver max77650_onkey_driver = {
+ .driver = {
+ .name = "max77650-onkey",
+ .of_match_table = max77650_onkey_of_match,
+ },
+ .probe = max77650_onkey_probe,
+};
+module_platform_driver(max77650_onkey_driver);
+
+MODULE_DESCRIPTION("MAXIM 77650/77651 ONKEY driver");
+MODULE_AUTHOR("Bartosz Golaszewski <bgolaszewski@baylibre.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:max77650-onkey");
diff --git a/drivers/input/misc/max77693-haptic.c b/drivers/input/misc/max77693-haptic.c
new file mode 100644
index 000000000..4369d3c04
--- /dev/null
+++ b/drivers/input/misc/max77693-haptic.c
@@ -0,0 +1,427 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * MAXIM MAX77693/MAX77843 Haptic device driver
+ *
+ * Copyright (C) 2014,2015 Samsung Electronics
+ * Jaewon Kim <jaewon02.kim@samsung.com>
+ * Krzysztof Kozlowski <krzk@kernel.org>
+ *
+ * This program is not provided / owned by Maxim Integrated Products.
+ */
+
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/regulator/consumer.h>
+#include <linux/mfd/max77693.h>
+#include <linux/mfd/max77693-common.h>
+#include <linux/mfd/max77693-private.h>
+#include <linux/mfd/max77843-private.h>
+
+#define MAX_MAGNITUDE_SHIFT 16
+
+enum max77693_haptic_motor_type {
+ MAX77693_HAPTIC_ERM = 0,
+ MAX77693_HAPTIC_LRA,
+};
+
+enum max77693_haptic_pulse_mode {
+ MAX77693_HAPTIC_EXTERNAL_MODE = 0,
+ MAX77693_HAPTIC_INTERNAL_MODE,
+};
+
+enum max77693_haptic_pwm_divisor {
+ MAX77693_HAPTIC_PWM_DIVISOR_32 = 0,
+ MAX77693_HAPTIC_PWM_DIVISOR_64,
+ MAX77693_HAPTIC_PWM_DIVISOR_128,
+ MAX77693_HAPTIC_PWM_DIVISOR_256,
+};
+
+struct max77693_haptic {
+ enum max77693_types dev_type;
+
+ struct regmap *regmap_pmic;
+ struct regmap *regmap_haptic;
+ struct device *dev;
+ struct input_dev *input_dev;
+ struct pwm_device *pwm_dev;
+ struct regulator *motor_reg;
+
+ bool enabled;
+ bool suspend_state;
+ unsigned int magnitude;
+ unsigned int pwm_duty;
+ enum max77693_haptic_motor_type type;
+ enum max77693_haptic_pulse_mode mode;
+
+ struct work_struct work;
+};
+
+static int max77693_haptic_set_duty_cycle(struct max77693_haptic *haptic)
+{
+ struct pwm_args pargs;
+ int delta;
+ int error;
+
+ pwm_get_args(haptic->pwm_dev, &pargs);
+ delta = (pargs.period + haptic->pwm_duty) / 2;
+ error = pwm_config(haptic->pwm_dev, delta, pargs.period);
+ if (error) {
+ dev_err(haptic->dev, "failed to configure pwm: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int max77843_haptic_bias(struct max77693_haptic *haptic, bool on)
+{
+ int error;
+
+ if (haptic->dev_type != TYPE_MAX77843)
+ return 0;
+
+ error = regmap_update_bits(haptic->regmap_haptic,
+ MAX77843_SYS_REG_MAINCTRL1,
+ MAX77843_MAINCTRL1_BIASEN_MASK,
+ on << MAINCTRL1_BIASEN_SHIFT);
+ if (error) {
+ dev_err(haptic->dev, "failed to %s bias: %d\n",
+ on ? "enable" : "disable", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int max77693_haptic_configure(struct max77693_haptic *haptic,
+ bool enable)
+{
+ unsigned int value, config_reg;
+ int error;
+
+ switch (haptic->dev_type) {
+ case TYPE_MAX77693:
+ value = ((haptic->type << MAX77693_CONFIG2_MODE) |
+ (enable << MAX77693_CONFIG2_MEN) |
+ (haptic->mode << MAX77693_CONFIG2_HTYP) |
+ MAX77693_HAPTIC_PWM_DIVISOR_128);
+ config_reg = MAX77693_HAPTIC_REG_CONFIG2;
+ break;
+ case TYPE_MAX77843:
+ value = (haptic->type << MCONFIG_MODE_SHIFT) |
+ (enable << MCONFIG_MEN_SHIFT) |
+ MAX77693_HAPTIC_PWM_DIVISOR_128;
+ config_reg = MAX77843_HAP_REG_MCONFIG;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ error = regmap_write(haptic->regmap_haptic,
+ config_reg, value);
+ if (error) {
+ dev_err(haptic->dev,
+ "failed to update haptic config: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int max77693_haptic_lowsys(struct max77693_haptic *haptic, bool enable)
+{
+ int error;
+
+ if (haptic->dev_type != TYPE_MAX77693)
+ return 0;
+
+ error = regmap_update_bits(haptic->regmap_pmic,
+ MAX77693_PMIC_REG_LSCNFG,
+ MAX77693_PMIC_LOW_SYS_MASK,
+ enable << MAX77693_PMIC_LOW_SYS_SHIFT);
+ if (error) {
+ dev_err(haptic->dev, "cannot update pmic regmap: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static void max77693_haptic_enable(struct max77693_haptic *haptic)
+{
+ int error;
+
+ if (haptic->enabled)
+ return;
+
+ error = pwm_enable(haptic->pwm_dev);
+ if (error) {
+ dev_err(haptic->dev,
+ "failed to enable haptic pwm device: %d\n", error);
+ return;
+ }
+
+ error = max77693_haptic_lowsys(haptic, true);
+ if (error)
+ goto err_enable_lowsys;
+
+ error = max77693_haptic_configure(haptic, true);
+ if (error)
+ goto err_enable_config;
+
+ haptic->enabled = true;
+
+ return;
+
+err_enable_config:
+ max77693_haptic_lowsys(haptic, false);
+err_enable_lowsys:
+ pwm_disable(haptic->pwm_dev);
+}
+
+static void max77693_haptic_disable(struct max77693_haptic *haptic)
+{
+ int error;
+
+ if (!haptic->enabled)
+ return;
+
+ error = max77693_haptic_configure(haptic, false);
+ if (error)
+ return;
+
+ error = max77693_haptic_lowsys(haptic, false);
+ if (error)
+ goto err_disable_lowsys;
+
+ pwm_disable(haptic->pwm_dev);
+ haptic->enabled = false;
+
+ return;
+
+err_disable_lowsys:
+ max77693_haptic_configure(haptic, true);
+}
+
+static void max77693_haptic_play_work(struct work_struct *work)
+{
+ struct max77693_haptic *haptic =
+ container_of(work, struct max77693_haptic, work);
+ int error;
+
+ error = max77693_haptic_set_duty_cycle(haptic);
+ if (error) {
+ dev_err(haptic->dev, "failed to set duty cycle: %d\n", error);
+ return;
+ }
+
+ if (haptic->magnitude)
+ max77693_haptic_enable(haptic);
+ else
+ max77693_haptic_disable(haptic);
+}
+
+static int max77693_haptic_play_effect(struct input_dev *dev, void *data,
+ struct ff_effect *effect)
+{
+ struct max77693_haptic *haptic = input_get_drvdata(dev);
+ struct pwm_args pargs;
+ u64 period_mag_multi;
+
+ haptic->magnitude = effect->u.rumble.strong_magnitude;
+ if (!haptic->magnitude)
+ haptic->magnitude = effect->u.rumble.weak_magnitude;
+
+ /*
+ * The magnitude comes from force-feedback interface.
+ * The formula to convert magnitude to pwm_duty as follows:
+ * - pwm_duty = (magnitude * pwm_period) / MAX_MAGNITUDE(0xFFFF)
+ */
+ pwm_get_args(haptic->pwm_dev, &pargs);
+ period_mag_multi = (u64)pargs.period * haptic->magnitude;
+ haptic->pwm_duty = (unsigned int)(period_mag_multi >>
+ MAX_MAGNITUDE_SHIFT);
+
+ schedule_work(&haptic->work);
+
+ return 0;
+}
+
+static int max77693_haptic_open(struct input_dev *dev)
+{
+ struct max77693_haptic *haptic = input_get_drvdata(dev);
+ int error;
+
+ error = max77843_haptic_bias(haptic, true);
+ if (error)
+ return error;
+
+ error = regulator_enable(haptic->motor_reg);
+ if (error) {
+ dev_err(haptic->dev,
+ "failed to enable regulator: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static void max77693_haptic_close(struct input_dev *dev)
+{
+ struct max77693_haptic *haptic = input_get_drvdata(dev);
+ int error;
+
+ cancel_work_sync(&haptic->work);
+ max77693_haptic_disable(haptic);
+
+ error = regulator_disable(haptic->motor_reg);
+ if (error)
+ dev_err(haptic->dev,
+ "failed to disable regulator: %d\n", error);
+
+ max77843_haptic_bias(haptic, false);
+}
+
+static int max77693_haptic_probe(struct platform_device *pdev)
+{
+ struct max77693_dev *max77693 = dev_get_drvdata(pdev->dev.parent);
+ struct max77693_haptic *haptic;
+ int error;
+
+ haptic = devm_kzalloc(&pdev->dev, sizeof(*haptic), GFP_KERNEL);
+ if (!haptic)
+ return -ENOMEM;
+
+ haptic->regmap_pmic = max77693->regmap;
+ haptic->dev = &pdev->dev;
+ haptic->type = MAX77693_HAPTIC_LRA;
+ haptic->mode = MAX77693_HAPTIC_EXTERNAL_MODE;
+ haptic->suspend_state = false;
+
+ /* Variant-specific init */
+ haptic->dev_type = platform_get_device_id(pdev)->driver_data;
+ switch (haptic->dev_type) {
+ case TYPE_MAX77693:
+ haptic->regmap_haptic = max77693->regmap_haptic;
+ break;
+ case TYPE_MAX77843:
+ haptic->regmap_haptic = max77693->regmap;
+ break;
+ default:
+ dev_err(&pdev->dev, "unsupported device type: %u\n",
+ haptic->dev_type);
+ return -EINVAL;
+ }
+
+ INIT_WORK(&haptic->work, max77693_haptic_play_work);
+
+ /* Get pwm and regulatot for haptic device */
+ haptic->pwm_dev = devm_pwm_get(&pdev->dev, NULL);
+ if (IS_ERR(haptic->pwm_dev)) {
+ dev_err(&pdev->dev, "failed to get pwm device\n");
+ return PTR_ERR(haptic->pwm_dev);
+ }
+
+ /*
+ * FIXME: pwm_apply_args() should be removed when switching to the
+ * atomic PWM API.
+ */
+ pwm_apply_args(haptic->pwm_dev);
+
+ haptic->motor_reg = devm_regulator_get(&pdev->dev, "haptic");
+ if (IS_ERR(haptic->motor_reg)) {
+ dev_err(&pdev->dev, "failed to get regulator\n");
+ return PTR_ERR(haptic->motor_reg);
+ }
+
+ /* Initialize input device for haptic device */
+ haptic->input_dev = devm_input_allocate_device(&pdev->dev);
+ if (!haptic->input_dev) {
+ dev_err(&pdev->dev, "failed to allocate input device\n");
+ return -ENOMEM;
+ }
+
+ haptic->input_dev->name = "max77693-haptic";
+ haptic->input_dev->id.version = 1;
+ haptic->input_dev->dev.parent = &pdev->dev;
+ haptic->input_dev->open = max77693_haptic_open;
+ haptic->input_dev->close = max77693_haptic_close;
+ input_set_drvdata(haptic->input_dev, haptic);
+ input_set_capability(haptic->input_dev, EV_FF, FF_RUMBLE);
+
+ error = input_ff_create_memless(haptic->input_dev, NULL,
+ max77693_haptic_play_effect);
+ if (error) {
+ dev_err(&pdev->dev, "failed to create force-feedback\n");
+ return error;
+ }
+
+ error = input_register_device(haptic->input_dev);
+ if (error) {
+ dev_err(&pdev->dev, "failed to register input device\n");
+ return error;
+ }
+
+ platform_set_drvdata(pdev, haptic);
+
+ return 0;
+}
+
+static int __maybe_unused max77693_haptic_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct max77693_haptic *haptic = platform_get_drvdata(pdev);
+
+ if (haptic->enabled) {
+ max77693_haptic_disable(haptic);
+ haptic->suspend_state = true;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused max77693_haptic_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct max77693_haptic *haptic = platform_get_drvdata(pdev);
+
+ if (haptic->suspend_state) {
+ max77693_haptic_enable(haptic);
+ haptic->suspend_state = false;
+ }
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(max77693_haptic_pm_ops,
+ max77693_haptic_suspend, max77693_haptic_resume);
+
+static const struct platform_device_id max77693_haptic_id[] = {
+ { "max77693-haptic", TYPE_MAX77693 },
+ { "max77843-haptic", TYPE_MAX77843 },
+ {},
+};
+MODULE_DEVICE_TABLE(platform, max77693_haptic_id);
+
+static struct platform_driver max77693_haptic_driver = {
+ .driver = {
+ .name = "max77693-haptic",
+ .pm = &max77693_haptic_pm_ops,
+ },
+ .probe = max77693_haptic_probe,
+ .id_table = max77693_haptic_id,
+};
+module_platform_driver(max77693_haptic_driver);
+
+MODULE_AUTHOR("Jaewon Kim <jaewon02.kim@samsung.com>");
+MODULE_AUTHOR("Krzysztof Kozlowski <krzk@kernel.org>");
+MODULE_DESCRIPTION("MAXIM 77693/77843 Haptic driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/max8925_onkey.c b/drivers/input/misc/max8925_onkey.c
new file mode 100644
index 000000000..4770cb556
--- /dev/null
+++ b/drivers/input/misc/max8925_onkey.c
@@ -0,0 +1,173 @@
+/*
+ * MAX8925 ONKEY driver
+ *
+ * Copyright (C) 2009 Marvell International Ltd.
+ * Haojian Zhuang <haojian.zhuang@marvell.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License. See the file "COPYING" in the main directory of this
+ * archive for more details.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/max8925.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+
+#define SW_INPUT (1 << 7) /* 0/1 -- up/down */
+#define HARDRESET_EN (1 << 7)
+#define PWREN_EN (1 << 7)
+
+struct max8925_onkey_info {
+ struct input_dev *idev;
+ struct i2c_client *i2c;
+ struct device *dev;
+ unsigned int irq[2];
+};
+
+/*
+ * MAX8925 gives us an interrupt when ONKEY is pressed or released.
+ * max8925_set_bits() operates I2C bus and may sleep. So implement
+ * it in thread IRQ handler.
+ */
+static irqreturn_t max8925_onkey_handler(int irq, void *data)
+{
+ struct max8925_onkey_info *info = data;
+ int state;
+
+ state = max8925_reg_read(info->i2c, MAX8925_ON_OFF_STATUS);
+
+ input_report_key(info->idev, KEY_POWER, state & SW_INPUT);
+ input_sync(info->idev);
+
+ dev_dbg(info->dev, "onkey state:%d\n", state);
+
+ /* Enable hardreset to halt if system isn't shutdown on time */
+ max8925_set_bits(info->i2c, MAX8925_SYSENSEL,
+ HARDRESET_EN, HARDRESET_EN);
+
+ return IRQ_HANDLED;
+}
+
+static int max8925_onkey_probe(struct platform_device *pdev)
+{
+ struct max8925_chip *chip = dev_get_drvdata(pdev->dev.parent);
+ struct max8925_onkey_info *info;
+ struct input_dev *input;
+ int irq[2], error;
+
+ irq[0] = platform_get_irq(pdev, 0);
+ if (irq[0] < 0)
+ return -EINVAL;
+
+ irq[1] = platform_get_irq(pdev, 1);
+ if (irq[1] < 0)
+ return -EINVAL;
+
+ info = devm_kzalloc(&pdev->dev, sizeof(struct max8925_onkey_info),
+ GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ input = devm_input_allocate_device(&pdev->dev);
+ if (!input)
+ return -ENOMEM;
+
+ info->idev = input;
+ info->i2c = chip->i2c;
+ info->dev = &pdev->dev;
+ info->irq[0] = irq[0];
+ info->irq[1] = irq[1];
+
+ input->name = "max8925_on";
+ input->phys = "max8925_on/input0";
+ input->id.bustype = BUS_I2C;
+ input->dev.parent = &pdev->dev;
+ input_set_capability(input, EV_KEY, KEY_POWER);
+
+ error = devm_request_threaded_irq(&pdev->dev, irq[0], NULL,
+ max8925_onkey_handler, IRQF_ONESHOT,
+ "onkey-down", info);
+ if (error < 0) {
+ dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n",
+ irq[0], error);
+ return error;
+ }
+
+ error = devm_request_threaded_irq(&pdev->dev, irq[1], NULL,
+ max8925_onkey_handler, IRQF_ONESHOT,
+ "onkey-up", info);
+ if (error < 0) {
+ dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n",
+ irq[1], error);
+ return error;
+ }
+
+ error = input_register_device(info->idev);
+ if (error) {
+ dev_err(chip->dev, "Can't register input device: %d\n", error);
+ return error;
+ }
+
+ platform_set_drvdata(pdev, info);
+ device_init_wakeup(&pdev->dev, 1);
+
+ return 0;
+}
+
+static int __maybe_unused max8925_onkey_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct max8925_onkey_info *info = platform_get_drvdata(pdev);
+ struct max8925_chip *chip = dev_get_drvdata(pdev->dev.parent);
+
+ if (device_may_wakeup(dev)) {
+ chip->wakeup_flag |= 1 << info->irq[0];
+ chip->wakeup_flag |= 1 << info->irq[1];
+ }
+
+ return 0;
+}
+
+static int __maybe_unused max8925_onkey_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct max8925_onkey_info *info = platform_get_drvdata(pdev);
+ struct max8925_chip *chip = dev_get_drvdata(pdev->dev.parent);
+
+ if (device_may_wakeup(dev)) {
+ chip->wakeup_flag &= ~(1 << info->irq[0]);
+ chip->wakeup_flag &= ~(1 << info->irq[1]);
+ }
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(max8925_onkey_pm_ops, max8925_onkey_suspend, max8925_onkey_resume);
+
+static struct platform_driver max8925_onkey_driver = {
+ .driver = {
+ .name = "max8925-onkey",
+ .pm = &max8925_onkey_pm_ops,
+ },
+ .probe = max8925_onkey_probe,
+};
+module_platform_driver(max8925_onkey_driver);
+
+MODULE_DESCRIPTION("Maxim MAX8925 ONKEY driver");
+MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/max8997_haptic.c b/drivers/input/misc/max8997_haptic.c
new file mode 100644
index 000000000..cd5e99ec1
--- /dev/null
+++ b/drivers/input/misc/max8997_haptic.c
@@ -0,0 +1,401 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * MAX8997-haptic controller driver
+ *
+ * Copyright (C) 2012 Samsung Electronics
+ * Donggeun Kim <dg77.kim@samsung.com>
+ *
+ * This program is not provided / owned by Maxim Integrated Products.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/pwm.h>
+#include <linux/input.h>
+#include <linux/mfd/max8997-private.h>
+#include <linux/mfd/max8997.h>
+#include <linux/regulator/consumer.h>
+
+/* Haptic configuration 2 register */
+#define MAX8997_MOTOR_TYPE_SHIFT 7
+#define MAX8997_ENABLE_SHIFT 6
+#define MAX8997_MODE_SHIFT 5
+
+/* Haptic driver configuration register */
+#define MAX8997_CYCLE_SHIFT 6
+#define MAX8997_SIG_PERIOD_SHIFT 4
+#define MAX8997_SIG_DUTY_SHIFT 2
+#define MAX8997_PWM_DUTY_SHIFT 0
+
+struct max8997_haptic {
+ struct device *dev;
+ struct i2c_client *client;
+ struct input_dev *input_dev;
+ struct regulator *regulator;
+
+ struct work_struct work;
+ struct mutex mutex;
+
+ bool enabled;
+ unsigned int level;
+
+ struct pwm_device *pwm;
+ unsigned int pwm_period;
+ enum max8997_haptic_pwm_divisor pwm_divisor;
+
+ enum max8997_haptic_motor_type type;
+ enum max8997_haptic_pulse_mode mode;
+
+ unsigned int internal_mode_pattern;
+ unsigned int pattern_cycle;
+ unsigned int pattern_signal_period;
+};
+
+static int max8997_haptic_set_duty_cycle(struct max8997_haptic *chip)
+{
+ int ret = 0;
+
+ if (chip->mode == MAX8997_EXTERNAL_MODE) {
+ unsigned int duty = chip->pwm_period * chip->level / 100;
+ ret = pwm_config(chip->pwm, duty, chip->pwm_period);
+ } else {
+ u8 duty_index = 0;
+
+ duty_index = DIV_ROUND_UP(chip->level * 64, 100);
+
+ switch (chip->internal_mode_pattern) {
+ case 0:
+ max8997_write_reg(chip->client,
+ MAX8997_HAPTIC_REG_SIGPWMDC1, duty_index);
+ break;
+ case 1:
+ max8997_write_reg(chip->client,
+ MAX8997_HAPTIC_REG_SIGPWMDC2, duty_index);
+ break;
+ case 2:
+ max8997_write_reg(chip->client,
+ MAX8997_HAPTIC_REG_SIGPWMDC3, duty_index);
+ break;
+ case 3:
+ max8997_write_reg(chip->client,
+ MAX8997_HAPTIC_REG_SIGPWMDC4, duty_index);
+ break;
+ default:
+ break;
+ }
+ }
+ return ret;
+}
+
+static void max8997_haptic_configure(struct max8997_haptic *chip)
+{
+ u8 value;
+
+ value = chip->type << MAX8997_MOTOR_TYPE_SHIFT |
+ chip->enabled << MAX8997_ENABLE_SHIFT |
+ chip->mode << MAX8997_MODE_SHIFT | chip->pwm_divisor;
+ max8997_write_reg(chip->client, MAX8997_HAPTIC_REG_CONF2, value);
+
+ if (chip->mode == MAX8997_INTERNAL_MODE && chip->enabled) {
+ value = chip->internal_mode_pattern << MAX8997_CYCLE_SHIFT |
+ chip->internal_mode_pattern << MAX8997_SIG_PERIOD_SHIFT |
+ chip->internal_mode_pattern << MAX8997_SIG_DUTY_SHIFT |
+ chip->internal_mode_pattern << MAX8997_PWM_DUTY_SHIFT;
+ max8997_write_reg(chip->client,
+ MAX8997_HAPTIC_REG_DRVCONF, value);
+
+ switch (chip->internal_mode_pattern) {
+ case 0:
+ value = chip->pattern_cycle << 4;
+ max8997_write_reg(chip->client,
+ MAX8997_HAPTIC_REG_CYCLECONF1, value);
+ value = chip->pattern_signal_period;
+ max8997_write_reg(chip->client,
+ MAX8997_HAPTIC_REG_SIGCONF1, value);
+ break;
+
+ case 1:
+ value = chip->pattern_cycle;
+ max8997_write_reg(chip->client,
+ MAX8997_HAPTIC_REG_CYCLECONF1, value);
+ value = chip->pattern_signal_period;
+ max8997_write_reg(chip->client,
+ MAX8997_HAPTIC_REG_SIGCONF2, value);
+ break;
+
+ case 2:
+ value = chip->pattern_cycle << 4;
+ max8997_write_reg(chip->client,
+ MAX8997_HAPTIC_REG_CYCLECONF2, value);
+ value = chip->pattern_signal_period;
+ max8997_write_reg(chip->client,
+ MAX8997_HAPTIC_REG_SIGCONF3, value);
+ break;
+
+ case 3:
+ value = chip->pattern_cycle;
+ max8997_write_reg(chip->client,
+ MAX8997_HAPTIC_REG_CYCLECONF2, value);
+ value = chip->pattern_signal_period;
+ max8997_write_reg(chip->client,
+ MAX8997_HAPTIC_REG_SIGCONF4, value);
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+static void max8997_haptic_enable(struct max8997_haptic *chip)
+{
+ int error;
+
+ mutex_lock(&chip->mutex);
+
+ error = max8997_haptic_set_duty_cycle(chip);
+ if (error) {
+ dev_err(chip->dev, "set_pwm_cycle failed, error: %d\n", error);
+ goto out;
+ }
+
+ if (!chip->enabled) {
+ error = regulator_enable(chip->regulator);
+ if (error) {
+ dev_err(chip->dev, "Failed to enable regulator\n");
+ goto out;
+ }
+ max8997_haptic_configure(chip);
+ if (chip->mode == MAX8997_EXTERNAL_MODE) {
+ error = pwm_enable(chip->pwm);
+ if (error) {
+ dev_err(chip->dev, "Failed to enable PWM\n");
+ regulator_disable(chip->regulator);
+ goto out;
+ }
+ }
+ chip->enabled = true;
+ }
+
+out:
+ mutex_unlock(&chip->mutex);
+}
+
+static void max8997_haptic_disable(struct max8997_haptic *chip)
+{
+ mutex_lock(&chip->mutex);
+
+ if (chip->enabled) {
+ chip->enabled = false;
+ max8997_haptic_configure(chip);
+ if (chip->mode == MAX8997_EXTERNAL_MODE)
+ pwm_disable(chip->pwm);
+ regulator_disable(chip->regulator);
+ }
+
+ mutex_unlock(&chip->mutex);
+}
+
+static void max8997_haptic_play_effect_work(struct work_struct *work)
+{
+ struct max8997_haptic *chip =
+ container_of(work, struct max8997_haptic, work);
+
+ if (chip->level)
+ max8997_haptic_enable(chip);
+ else
+ max8997_haptic_disable(chip);
+}
+
+static int max8997_haptic_play_effect(struct input_dev *dev, void *data,
+ struct ff_effect *effect)
+{
+ struct max8997_haptic *chip = input_get_drvdata(dev);
+
+ chip->level = effect->u.rumble.strong_magnitude;
+ if (!chip->level)
+ chip->level = effect->u.rumble.weak_magnitude;
+
+ schedule_work(&chip->work);
+
+ return 0;
+}
+
+static void max8997_haptic_close(struct input_dev *dev)
+{
+ struct max8997_haptic *chip = input_get_drvdata(dev);
+
+ cancel_work_sync(&chip->work);
+ max8997_haptic_disable(chip);
+}
+
+static int max8997_haptic_probe(struct platform_device *pdev)
+{
+ struct max8997_dev *iodev = dev_get_drvdata(pdev->dev.parent);
+ const struct max8997_platform_data *pdata =
+ dev_get_platdata(iodev->dev);
+ const struct max8997_haptic_platform_data *haptic_pdata = NULL;
+ struct max8997_haptic *chip;
+ struct input_dev *input_dev;
+ int error;
+
+ if (pdata)
+ haptic_pdata = pdata->haptic_pdata;
+
+ if (!haptic_pdata) {
+ dev_err(&pdev->dev, "no haptic platform data\n");
+ return -EINVAL;
+ }
+
+ chip = kzalloc(sizeof(struct max8997_haptic), GFP_KERNEL);
+ input_dev = input_allocate_device();
+ if (!chip || !input_dev) {
+ dev_err(&pdev->dev, "unable to allocate memory\n");
+ error = -ENOMEM;
+ goto err_free_mem;
+ }
+
+ INIT_WORK(&chip->work, max8997_haptic_play_effect_work);
+ mutex_init(&chip->mutex);
+
+ chip->client = iodev->haptic;
+ chip->dev = &pdev->dev;
+ chip->input_dev = input_dev;
+ chip->pwm_period = haptic_pdata->pwm_period;
+ chip->type = haptic_pdata->type;
+ chip->mode = haptic_pdata->mode;
+ chip->pwm_divisor = haptic_pdata->pwm_divisor;
+
+ switch (chip->mode) {
+ case MAX8997_INTERNAL_MODE:
+ chip->internal_mode_pattern =
+ haptic_pdata->internal_mode_pattern;
+ chip->pattern_cycle = haptic_pdata->pattern_cycle;
+ chip->pattern_signal_period =
+ haptic_pdata->pattern_signal_period;
+ break;
+
+ case MAX8997_EXTERNAL_MODE:
+ chip->pwm = pwm_request(haptic_pdata->pwm_channel_id,
+ "max8997-haptic");
+ if (IS_ERR(chip->pwm)) {
+ error = PTR_ERR(chip->pwm);
+ dev_err(&pdev->dev,
+ "unable to request PWM for haptic, error: %d\n",
+ error);
+ goto err_free_mem;
+ }
+
+ /*
+ * FIXME: pwm_apply_args() should be removed when switching to
+ * the atomic PWM API.
+ */
+ pwm_apply_args(chip->pwm);
+ break;
+
+ default:
+ dev_err(&pdev->dev,
+ "Invalid chip mode specified (%d)\n", chip->mode);
+ error = -EINVAL;
+ goto err_free_mem;
+ }
+
+ chip->regulator = regulator_get(&pdev->dev, "inmotor");
+ if (IS_ERR(chip->regulator)) {
+ error = PTR_ERR(chip->regulator);
+ dev_err(&pdev->dev,
+ "unable to get regulator, error: %d\n",
+ error);
+ goto err_free_pwm;
+ }
+
+ input_dev->name = "max8997-haptic";
+ input_dev->id.version = 1;
+ input_dev->dev.parent = &pdev->dev;
+ input_dev->close = max8997_haptic_close;
+ input_set_drvdata(input_dev, chip);
+ input_set_capability(input_dev, EV_FF, FF_RUMBLE);
+
+ error = input_ff_create_memless(input_dev, NULL,
+ max8997_haptic_play_effect);
+ if (error) {
+ dev_err(&pdev->dev,
+ "unable to create FF device, error: %d\n",
+ error);
+ goto err_put_regulator;
+ }
+
+ error = input_register_device(input_dev);
+ if (error) {
+ dev_err(&pdev->dev,
+ "unable to register input device, error: %d\n",
+ error);
+ goto err_destroy_ff;
+ }
+
+ platform_set_drvdata(pdev, chip);
+ return 0;
+
+err_destroy_ff:
+ input_ff_destroy(input_dev);
+err_put_regulator:
+ regulator_put(chip->regulator);
+err_free_pwm:
+ if (chip->mode == MAX8997_EXTERNAL_MODE)
+ pwm_free(chip->pwm);
+err_free_mem:
+ input_free_device(input_dev);
+ kfree(chip);
+
+ return error;
+}
+
+static int max8997_haptic_remove(struct platform_device *pdev)
+{
+ struct max8997_haptic *chip = platform_get_drvdata(pdev);
+
+ input_unregister_device(chip->input_dev);
+ regulator_put(chip->regulator);
+
+ if (chip->mode == MAX8997_EXTERNAL_MODE)
+ pwm_free(chip->pwm);
+
+ kfree(chip);
+
+ return 0;
+}
+
+static int __maybe_unused max8997_haptic_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct max8997_haptic *chip = platform_get_drvdata(pdev);
+
+ max8997_haptic_disable(chip);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(max8997_haptic_pm_ops, max8997_haptic_suspend, NULL);
+
+static const struct platform_device_id max8997_haptic_id[] = {
+ { "max8997-haptic", 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(platform, max8997_haptic_id);
+
+static struct platform_driver max8997_haptic_driver = {
+ .driver = {
+ .name = "max8997-haptic",
+ .pm = &max8997_haptic_pm_ops,
+ },
+ .probe = max8997_haptic_probe,
+ .remove = max8997_haptic_remove,
+ .id_table = max8997_haptic_id,
+};
+module_platform_driver(max8997_haptic_driver);
+
+MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>");
+MODULE_DESCRIPTION("max8997_haptic driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/mc13783-pwrbutton.c b/drivers/input/misc/mc13783-pwrbutton.c
new file mode 100644
index 000000000..0636eee4b
--- /dev/null
+++ b/drivers/input/misc/mc13783-pwrbutton.c
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2011 Philippe Rétornaz
+ *
+ * Based on twl4030-pwrbutton driver by:
+ * Peter De Schrijver <peter.de-schrijver@nokia.com>
+ * Felipe Balbi <felipe.balbi@nokia.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License. See the file "COPYING" in the main directory of this
+ * archive for more details.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/mc13783.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+
+struct mc13783_pwrb {
+ struct input_dev *pwr;
+ struct mc13xxx *mc13783;
+#define MC13783_PWRB_B1_POL_INVERT (1 << 0)
+#define MC13783_PWRB_B2_POL_INVERT (1 << 1)
+#define MC13783_PWRB_B3_POL_INVERT (1 << 2)
+ int flags;
+ unsigned short keymap[3];
+};
+
+#define MC13783_REG_INTERRUPT_SENSE_1 5
+#define MC13783_IRQSENSE1_ONOFD1S (1 << 3)
+#define MC13783_IRQSENSE1_ONOFD2S (1 << 4)
+#define MC13783_IRQSENSE1_ONOFD3S (1 << 5)
+
+#define MC13783_REG_POWER_CONTROL_2 15
+#define MC13783_POWER_CONTROL_2_ON1BDBNC 4
+#define MC13783_POWER_CONTROL_2_ON2BDBNC 6
+#define MC13783_POWER_CONTROL_2_ON3BDBNC 8
+#define MC13783_POWER_CONTROL_2_ON1BRSTEN (1 << 1)
+#define MC13783_POWER_CONTROL_2_ON2BRSTEN (1 << 2)
+#define MC13783_POWER_CONTROL_2_ON3BRSTEN (1 << 3)
+
+static irqreturn_t button_irq(int irq, void *_priv)
+{
+ struct mc13783_pwrb *priv = _priv;
+ int val;
+
+ mc13xxx_irq_ack(priv->mc13783, irq);
+ mc13xxx_reg_read(priv->mc13783, MC13783_REG_INTERRUPT_SENSE_1, &val);
+
+ switch (irq) {
+ case MC13783_IRQ_ONOFD1:
+ val = val & MC13783_IRQSENSE1_ONOFD1S ? 1 : 0;
+ if (priv->flags & MC13783_PWRB_B1_POL_INVERT)
+ val ^= 1;
+ input_report_key(priv->pwr, priv->keymap[0], val);
+ break;
+
+ case MC13783_IRQ_ONOFD2:
+ val = val & MC13783_IRQSENSE1_ONOFD2S ? 1 : 0;
+ if (priv->flags & MC13783_PWRB_B2_POL_INVERT)
+ val ^= 1;
+ input_report_key(priv->pwr, priv->keymap[1], val);
+ break;
+
+ case MC13783_IRQ_ONOFD3:
+ val = val & MC13783_IRQSENSE1_ONOFD3S ? 1 : 0;
+ if (priv->flags & MC13783_PWRB_B3_POL_INVERT)
+ val ^= 1;
+ input_report_key(priv->pwr, priv->keymap[2], val);
+ break;
+ }
+
+ input_sync(priv->pwr);
+
+ return IRQ_HANDLED;
+}
+
+static int mc13783_pwrbutton_probe(struct platform_device *pdev)
+{
+ const struct mc13xxx_buttons_platform_data *pdata;
+ struct mc13xxx *mc13783 = dev_get_drvdata(pdev->dev.parent);
+ struct input_dev *pwr;
+ struct mc13783_pwrb *priv;
+ int err = 0;
+ int reg = 0;
+
+ pdata = dev_get_platdata(&pdev->dev);
+ if (!pdata) {
+ dev_err(&pdev->dev, "missing platform data\n");
+ return -ENODEV;
+ }
+
+ pwr = input_allocate_device();
+ if (!pwr) {
+ dev_dbg(&pdev->dev, "Can't allocate power button\n");
+ return -ENOMEM;
+ }
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv) {
+ err = -ENOMEM;
+ dev_dbg(&pdev->dev, "Can't allocate power button\n");
+ goto free_input_dev;
+ }
+
+ reg |= (pdata->b1on_flags & 0x3) << MC13783_POWER_CONTROL_2_ON1BDBNC;
+ reg |= (pdata->b2on_flags & 0x3) << MC13783_POWER_CONTROL_2_ON2BDBNC;
+ reg |= (pdata->b3on_flags & 0x3) << MC13783_POWER_CONTROL_2_ON3BDBNC;
+
+ priv->pwr = pwr;
+ priv->mc13783 = mc13783;
+
+ mc13xxx_lock(mc13783);
+
+ if (pdata->b1on_flags & MC13783_BUTTON_ENABLE) {
+ priv->keymap[0] = pdata->b1on_key;
+ if (pdata->b1on_key != KEY_RESERVED)
+ __set_bit(pdata->b1on_key, pwr->keybit);
+
+ if (pdata->b1on_flags & MC13783_BUTTON_POL_INVERT)
+ priv->flags |= MC13783_PWRB_B1_POL_INVERT;
+
+ if (pdata->b1on_flags & MC13783_BUTTON_RESET_EN)
+ reg |= MC13783_POWER_CONTROL_2_ON1BRSTEN;
+
+ err = mc13xxx_irq_request(mc13783, MC13783_IRQ_ONOFD1,
+ button_irq, "b1on", priv);
+ if (err) {
+ dev_dbg(&pdev->dev, "Can't request irq\n");
+ goto free_priv;
+ }
+ }
+
+ if (pdata->b2on_flags & MC13783_BUTTON_ENABLE) {
+ priv->keymap[1] = pdata->b2on_key;
+ if (pdata->b2on_key != KEY_RESERVED)
+ __set_bit(pdata->b2on_key, pwr->keybit);
+
+ if (pdata->b2on_flags & MC13783_BUTTON_POL_INVERT)
+ priv->flags |= MC13783_PWRB_B2_POL_INVERT;
+
+ if (pdata->b2on_flags & MC13783_BUTTON_RESET_EN)
+ reg |= MC13783_POWER_CONTROL_2_ON2BRSTEN;
+
+ err = mc13xxx_irq_request(mc13783, MC13783_IRQ_ONOFD2,
+ button_irq, "b2on", priv);
+ if (err) {
+ dev_dbg(&pdev->dev, "Can't request irq\n");
+ goto free_irq_b1;
+ }
+ }
+
+ if (pdata->b3on_flags & MC13783_BUTTON_ENABLE) {
+ priv->keymap[2] = pdata->b3on_key;
+ if (pdata->b3on_key != KEY_RESERVED)
+ __set_bit(pdata->b3on_key, pwr->keybit);
+
+ if (pdata->b3on_flags & MC13783_BUTTON_POL_INVERT)
+ priv->flags |= MC13783_PWRB_B3_POL_INVERT;
+
+ if (pdata->b3on_flags & MC13783_BUTTON_RESET_EN)
+ reg |= MC13783_POWER_CONTROL_2_ON3BRSTEN;
+
+ err = mc13xxx_irq_request(mc13783, MC13783_IRQ_ONOFD3,
+ button_irq, "b3on", priv);
+ if (err) {
+ dev_dbg(&pdev->dev, "Can't request irq: %d\n", err);
+ goto free_irq_b2;
+ }
+ }
+
+ mc13xxx_reg_rmw(mc13783, MC13783_REG_POWER_CONTROL_2, 0x3FE, reg);
+
+ mc13xxx_unlock(mc13783);
+
+ pwr->name = "mc13783_pwrbutton";
+ pwr->phys = "mc13783_pwrbutton/input0";
+ pwr->dev.parent = &pdev->dev;
+
+ pwr->keycode = priv->keymap;
+ pwr->keycodemax = ARRAY_SIZE(priv->keymap);
+ pwr->keycodesize = sizeof(priv->keymap[0]);
+ __set_bit(EV_KEY, pwr->evbit);
+
+ err = input_register_device(pwr);
+ if (err) {
+ dev_dbg(&pdev->dev, "Can't register power button: %d\n", err);
+ goto free_irq;
+ }
+
+ platform_set_drvdata(pdev, priv);
+
+ return 0;
+
+free_irq:
+ mc13xxx_lock(mc13783);
+
+ if (pdata->b3on_flags & MC13783_BUTTON_ENABLE)
+ mc13xxx_irq_free(mc13783, MC13783_IRQ_ONOFD3, priv);
+
+free_irq_b2:
+ if (pdata->b2on_flags & MC13783_BUTTON_ENABLE)
+ mc13xxx_irq_free(mc13783, MC13783_IRQ_ONOFD2, priv);
+
+free_irq_b1:
+ if (pdata->b1on_flags & MC13783_BUTTON_ENABLE)
+ mc13xxx_irq_free(mc13783, MC13783_IRQ_ONOFD1, priv);
+
+free_priv:
+ mc13xxx_unlock(mc13783);
+ kfree(priv);
+
+free_input_dev:
+ input_free_device(pwr);
+
+ return err;
+}
+
+static int mc13783_pwrbutton_remove(struct platform_device *pdev)
+{
+ struct mc13783_pwrb *priv = platform_get_drvdata(pdev);
+ const struct mc13xxx_buttons_platform_data *pdata;
+
+ pdata = dev_get_platdata(&pdev->dev);
+
+ mc13xxx_lock(priv->mc13783);
+
+ if (pdata->b3on_flags & MC13783_BUTTON_ENABLE)
+ mc13xxx_irq_free(priv->mc13783, MC13783_IRQ_ONOFD3, priv);
+ if (pdata->b2on_flags & MC13783_BUTTON_ENABLE)
+ mc13xxx_irq_free(priv->mc13783, MC13783_IRQ_ONOFD2, priv);
+ if (pdata->b1on_flags & MC13783_BUTTON_ENABLE)
+ mc13xxx_irq_free(priv->mc13783, MC13783_IRQ_ONOFD1, priv);
+
+ mc13xxx_unlock(priv->mc13783);
+
+ input_unregister_device(priv->pwr);
+ kfree(priv);
+
+ return 0;
+}
+
+static struct platform_driver mc13783_pwrbutton_driver = {
+ .probe = mc13783_pwrbutton_probe,
+ .remove = mc13783_pwrbutton_remove,
+ .driver = {
+ .name = "mc13783-pwrbutton",
+ },
+};
+
+module_platform_driver(mc13783_pwrbutton_driver);
+
+MODULE_ALIAS("platform:mc13783-pwrbutton");
+MODULE_DESCRIPTION("MC13783 Power Button");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Philippe Retornaz");
diff --git a/drivers/input/misc/mma8450.c b/drivers/input/misc/mma8450.c
new file mode 100644
index 000000000..1b5a5e192
--- /dev/null
+++ b/drivers/input/misc/mma8450.c
@@ -0,0 +1,214 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Freescale's 3-Axis Accelerometer MMA8450
+ *
+ * Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/of_device.h>
+
+#define MMA8450_DRV_NAME "mma8450"
+
+#define MODE_CHANGE_DELAY_MS 100
+#define POLL_INTERVAL 100
+#define POLL_INTERVAL_MAX 500
+
+/* register definitions */
+#define MMA8450_STATUS 0x00
+#define MMA8450_STATUS_ZXYDR 0x08
+
+#define MMA8450_OUT_X8 0x01
+#define MMA8450_OUT_Y8 0x02
+#define MMA8450_OUT_Z8 0x03
+
+#define MMA8450_OUT_X_LSB 0x05
+#define MMA8450_OUT_X_MSB 0x06
+#define MMA8450_OUT_Y_LSB 0x07
+#define MMA8450_OUT_Y_MSB 0x08
+#define MMA8450_OUT_Z_LSB 0x09
+#define MMA8450_OUT_Z_MSB 0x0a
+
+#define MMA8450_XYZ_DATA_CFG 0x16
+
+#define MMA8450_CTRL_REG1 0x38
+#define MMA8450_CTRL_REG2 0x39
+
+static int mma8450_read(struct i2c_client *c, unsigned int off)
+{
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(c, off);
+ if (ret < 0)
+ dev_err(&c->dev,
+ "failed to read register 0x%02x, error %d\n",
+ off, ret);
+
+ return ret;
+}
+
+static int mma8450_write(struct i2c_client *c, unsigned int off, u8 v)
+{
+ int error;
+
+ error = i2c_smbus_write_byte_data(c, off, v);
+ if (error < 0) {
+ dev_err(&c->dev,
+ "failed to write to register 0x%02x, error %d\n",
+ off, error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int mma8450_read_block(struct i2c_client *c, unsigned int off,
+ u8 *buf, size_t size)
+{
+ int err;
+
+ err = i2c_smbus_read_i2c_block_data(c, off, size, buf);
+ if (err < 0) {
+ dev_err(&c->dev,
+ "failed to read block data at 0x%02x, error %d\n",
+ MMA8450_OUT_X_LSB, err);
+ return err;
+ }
+
+ return 0;
+}
+
+static void mma8450_poll(struct input_dev *input)
+{
+ struct i2c_client *c = input_get_drvdata(input);
+ int x, y, z;
+ int ret;
+ u8 buf[6];
+
+ ret = mma8450_read(c, MMA8450_STATUS);
+ if (ret < 0)
+ return;
+
+ if (!(ret & MMA8450_STATUS_ZXYDR))
+ return;
+
+ ret = mma8450_read_block(c, MMA8450_OUT_X_LSB, buf, sizeof(buf));
+ if (ret < 0)
+ return;
+
+ x = ((int)(s8)buf[1] << 4) | (buf[0] & 0xf);
+ y = ((int)(s8)buf[3] << 4) | (buf[2] & 0xf);
+ z = ((int)(s8)buf[5] << 4) | (buf[4] & 0xf);
+
+ input_report_abs(input, ABS_X, x);
+ input_report_abs(input, ABS_Y, y);
+ input_report_abs(input, ABS_Z, z);
+ input_sync(input);
+}
+
+/* Initialize the MMA8450 chip */
+static int mma8450_open(struct input_dev *input)
+{
+ struct i2c_client *c = input_get_drvdata(input);
+ int err;
+
+ /* enable all events from X/Y/Z, no FIFO */
+ err = mma8450_write(c, MMA8450_XYZ_DATA_CFG, 0x07);
+ if (err)
+ return err;
+
+ /*
+ * Sleep mode poll rate - 50Hz
+ * System output data rate - 400Hz
+ * Full scale selection - Active, +/- 2G
+ */
+ err = mma8450_write(c, MMA8450_CTRL_REG1, 0x01);
+ if (err)
+ return err;
+
+ msleep(MODE_CHANGE_DELAY_MS);
+ return 0;
+}
+
+static void mma8450_close(struct input_dev *input)
+{
+ struct i2c_client *c = input_get_drvdata(input);
+
+ mma8450_write(c, MMA8450_CTRL_REG1, 0x00);
+ mma8450_write(c, MMA8450_CTRL_REG2, 0x01);
+}
+
+/*
+ * I2C init/probing/exit functions
+ */
+static int mma8450_probe(struct i2c_client *c,
+ const struct i2c_device_id *id)
+{
+ struct input_dev *input;
+ int err;
+
+ input = devm_input_allocate_device(&c->dev);
+ if (!input)
+ return -ENOMEM;
+
+ input_set_drvdata(input, c);
+
+ input->name = MMA8450_DRV_NAME;
+ input->id.bustype = BUS_I2C;
+
+ input->open = mma8450_open;
+ input->close = mma8450_close;
+
+ input_set_abs_params(input, ABS_X, -2048, 2047, 32, 32);
+ input_set_abs_params(input, ABS_Y, -2048, 2047, 32, 32);
+ input_set_abs_params(input, ABS_Z, -2048, 2047, 32, 32);
+
+ err = input_setup_polling(input, mma8450_poll);
+ if (err) {
+ dev_err(&c->dev, "failed to set up polling\n");
+ return err;
+ }
+
+ input_set_poll_interval(input, POLL_INTERVAL);
+ input_set_max_poll_interval(input, POLL_INTERVAL_MAX);
+
+ err = input_register_device(input);
+ if (err) {
+ dev_err(&c->dev, "failed to register input device\n");
+ return err;
+ }
+
+ return 0;
+}
+
+static const struct i2c_device_id mma8450_id[] = {
+ { MMA8450_DRV_NAME, 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, mma8450_id);
+
+static const struct of_device_id mma8450_dt_ids[] = {
+ { .compatible = "fsl,mma8450", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mma8450_dt_ids);
+
+static struct i2c_driver mma8450_driver = {
+ .driver = {
+ .name = MMA8450_DRV_NAME,
+ .of_match_table = mma8450_dt_ids,
+ },
+ .probe = mma8450_probe,
+ .id_table = mma8450_id,
+};
+
+module_i2c_driver(mma8450_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("MMA8450 3-Axis Accelerometer Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/palmas-pwrbutton.c b/drivers/input/misc/palmas-pwrbutton.c
new file mode 100644
index 000000000..465e66930
--- /dev/null
+++ b/drivers/input/misc/palmas-pwrbutton.c
@@ -0,0 +1,327 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Texas Instruments' Palmas Power Button Input Driver
+ *
+ * Copyright (C) 2012-2014 Texas Instruments Incorporated - http://www.ti.com/
+ * Girish S Ghongdemath
+ * Nishanth Menon
+ */
+
+#include <linux/bitfield.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mfd/palmas.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#define PALMAS_LPK_TIME_MASK 0x0c
+#define PALMAS_PWRON_DEBOUNCE_MASK 0x03
+#define PALMAS_PWR_KEY_Q_TIME_MS 20
+
+/**
+ * struct palmas_pwron - Palmas power on data
+ * @palmas: pointer to palmas device
+ * @input_dev: pointer to input device
+ * @input_work: work for detecting release of key
+ * @irq: irq that we are hooked on to
+ */
+struct palmas_pwron {
+ struct palmas *palmas;
+ struct input_dev *input_dev;
+ struct delayed_work input_work;
+ int irq;
+};
+
+/**
+ * struct palmas_pwron_config - configuration of palmas power on
+ * @long_press_time_val: value for long press h/w shutdown event
+ * @pwron_debounce_val: value for debounce of power button
+ */
+struct palmas_pwron_config {
+ u8 long_press_time_val;
+ u8 pwron_debounce_val;
+};
+
+/**
+ * palmas_power_button_work() - Detects the button release event
+ * @work: work item to detect button release
+ */
+static void palmas_power_button_work(struct work_struct *work)
+{
+ struct palmas_pwron *pwron = container_of(work,
+ struct palmas_pwron,
+ input_work.work);
+ struct input_dev *input_dev = pwron->input_dev;
+ unsigned int reg;
+ int error;
+
+ error = palmas_read(pwron->palmas, PALMAS_INTERRUPT_BASE,
+ PALMAS_INT1_LINE_STATE, &reg);
+ if (error) {
+ dev_err(input_dev->dev.parent,
+ "Cannot read palmas PWRON status: %d\n", error);
+ } else if (reg & BIT(1)) {
+ /* The button is released, report event. */
+ input_report_key(input_dev, KEY_POWER, 0);
+ input_sync(input_dev);
+ } else {
+ /* The button is still depressed, keep checking. */
+ schedule_delayed_work(&pwron->input_work,
+ msecs_to_jiffies(PALMAS_PWR_KEY_Q_TIME_MS));
+ }
+}
+
+/**
+ * pwron_irq() - button press isr
+ * @irq: irq
+ * @palmas_pwron: pwron struct
+ *
+ * Return: IRQ_HANDLED
+ */
+static irqreturn_t pwron_irq(int irq, void *palmas_pwron)
+{
+ struct palmas_pwron *pwron = palmas_pwron;
+ struct input_dev *input_dev = pwron->input_dev;
+
+ input_report_key(input_dev, KEY_POWER, 1);
+ pm_wakeup_event(input_dev->dev.parent, 0);
+ input_sync(input_dev);
+
+ mod_delayed_work(system_wq, &pwron->input_work,
+ msecs_to_jiffies(PALMAS_PWR_KEY_Q_TIME_MS));
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * palmas_pwron_params_ofinit() - device tree parameter parser
+ * @dev: palmas button device
+ * @config: configuration params that this fills up
+ */
+static void palmas_pwron_params_ofinit(struct device *dev,
+ struct palmas_pwron_config *config)
+{
+ struct device_node *np;
+ u32 val;
+ int i, error;
+ static const u8 lpk_times[] = { 6, 8, 10, 12 };
+ static const int pwr_on_deb_ms[] = { 15, 100, 500, 1000 };
+
+ memset(config, 0, sizeof(*config));
+
+ /* Default config parameters */
+ config->long_press_time_val = ARRAY_SIZE(lpk_times) - 1;
+
+ np = dev->of_node;
+ if (!np)
+ return;
+
+ error = of_property_read_u32(np, "ti,palmas-long-press-seconds", &val);
+ if (!error) {
+ for (i = 0; i < ARRAY_SIZE(lpk_times); i++) {
+ if (val <= lpk_times[i]) {
+ config->long_press_time_val = i;
+ break;
+ }
+ }
+ }
+
+ error = of_property_read_u32(np,
+ "ti,palmas-pwron-debounce-milli-seconds",
+ &val);
+ if (!error) {
+ for (i = 0; i < ARRAY_SIZE(pwr_on_deb_ms); i++) {
+ if (val <= pwr_on_deb_ms[i]) {
+ config->pwron_debounce_val = i;
+ break;
+ }
+ }
+ }
+
+ dev_info(dev, "h/w controlled shutdown duration=%d seconds\n",
+ lpk_times[config->long_press_time_val]);
+}
+
+/**
+ * palmas_pwron_probe() - probe
+ * @pdev: platform device for the button
+ *
+ * Return: 0 for successful probe else appropriate error
+ */
+static int palmas_pwron_probe(struct platform_device *pdev)
+{
+ struct palmas *palmas = dev_get_drvdata(pdev->dev.parent);
+ struct device *dev = &pdev->dev;
+ struct input_dev *input_dev;
+ struct palmas_pwron *pwron;
+ struct palmas_pwron_config config;
+ int val;
+ int error;
+
+ palmas_pwron_params_ofinit(dev, &config);
+
+ pwron = kzalloc(sizeof(*pwron), GFP_KERNEL);
+ if (!pwron)
+ return -ENOMEM;
+
+ input_dev = input_allocate_device();
+ if (!input_dev) {
+ dev_err(dev, "Can't allocate power button\n");
+ error = -ENOMEM;
+ goto err_free_mem;
+ }
+
+ input_dev->name = "palmas_pwron";
+ input_dev->phys = "palmas_pwron/input0";
+ input_dev->dev.parent = dev;
+
+ input_set_capability(input_dev, EV_KEY, KEY_POWER);
+
+ /*
+ * Setup default hardware shutdown option (long key press)
+ * and debounce.
+ */
+ val = FIELD_PREP(PALMAS_LPK_TIME_MASK, config.long_press_time_val) |
+ FIELD_PREP(PALMAS_PWRON_DEBOUNCE_MASK, config.pwron_debounce_val);
+ error = palmas_update_bits(palmas, PALMAS_PMU_CONTROL_BASE,
+ PALMAS_LONG_PRESS_KEY,
+ PALMAS_LPK_TIME_MASK |
+ PALMAS_PWRON_DEBOUNCE_MASK,
+ val);
+ if (error) {
+ dev_err(dev, "LONG_PRESS_KEY_UPDATE failed: %d\n", error);
+ goto err_free_input;
+ }
+
+ pwron->palmas = palmas;
+ pwron->input_dev = input_dev;
+
+ INIT_DELAYED_WORK(&pwron->input_work, palmas_power_button_work);
+
+ pwron->irq = platform_get_irq(pdev, 0);
+ if (pwron->irq < 0) {
+ error = pwron->irq;
+ goto err_free_input;
+ }
+
+ error = request_threaded_irq(pwron->irq, NULL, pwron_irq,
+ IRQF_TRIGGER_HIGH |
+ IRQF_TRIGGER_LOW |
+ IRQF_ONESHOT,
+ dev_name(dev), pwron);
+ if (error) {
+ dev_err(dev, "Can't get IRQ for pwron: %d\n", error);
+ goto err_free_input;
+ }
+
+ error = input_register_device(input_dev);
+ if (error) {
+ dev_err(dev, "Can't register power button: %d\n", error);
+ goto err_free_irq;
+ }
+
+ platform_set_drvdata(pdev, pwron);
+ device_init_wakeup(dev, true);
+
+ return 0;
+
+err_free_irq:
+ cancel_delayed_work_sync(&pwron->input_work);
+ free_irq(pwron->irq, pwron);
+err_free_input:
+ input_free_device(input_dev);
+err_free_mem:
+ kfree(pwron);
+ return error;
+}
+
+/**
+ * palmas_pwron_remove() - Cleanup on removal
+ * @pdev: platform device for the button
+ *
+ * Return: 0
+ */
+static int palmas_pwron_remove(struct platform_device *pdev)
+{
+ struct palmas_pwron *pwron = platform_get_drvdata(pdev);
+
+ free_irq(pwron->irq, pwron);
+ cancel_delayed_work_sync(&pwron->input_work);
+
+ input_unregister_device(pwron->input_dev);
+ kfree(pwron);
+
+ return 0;
+}
+
+/**
+ * palmas_pwron_suspend() - suspend handler
+ * @dev: power button device
+ *
+ * Cancel all pending work items for the power button, setup irq for wakeup
+ *
+ * Return: 0
+ */
+static int __maybe_unused palmas_pwron_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct palmas_pwron *pwron = platform_get_drvdata(pdev);
+
+ cancel_delayed_work_sync(&pwron->input_work);
+
+ if (device_may_wakeup(dev))
+ enable_irq_wake(pwron->irq);
+
+ return 0;
+}
+
+/**
+ * palmas_pwron_resume() - resume handler
+ * @dev: power button device
+ *
+ * Just disable the wakeup capability of irq here.
+ *
+ * Return: 0
+ */
+static int __maybe_unused palmas_pwron_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct palmas_pwron *pwron = platform_get_drvdata(pdev);
+
+ if (device_may_wakeup(dev))
+ disable_irq_wake(pwron->irq);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(palmas_pwron_pm,
+ palmas_pwron_suspend, palmas_pwron_resume);
+
+#ifdef CONFIG_OF
+static const struct of_device_id of_palmas_pwr_match[] = {
+ { .compatible = "ti,palmas-pwrbutton" },
+ { },
+};
+
+MODULE_DEVICE_TABLE(of, of_palmas_pwr_match);
+#endif
+
+static struct platform_driver palmas_pwron_driver = {
+ .probe = palmas_pwron_probe,
+ .remove = palmas_pwron_remove,
+ .driver = {
+ .name = "palmas_pwrbutton",
+ .of_match_table = of_match_ptr(of_palmas_pwr_match),
+ .pm = &palmas_pwron_pm,
+ },
+};
+module_platform_driver(palmas_pwron_driver);
+
+MODULE_ALIAS("platform:palmas-pwrbutton");
+MODULE_DESCRIPTION("Palmas Power Button");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Texas Instruments Inc.");
diff --git a/drivers/input/misc/pcap_keys.c b/drivers/input/misc/pcap_keys.c
new file mode 100644
index 000000000..b5a53636d
--- /dev/null
+++ b/drivers/input/misc/pcap_keys.c
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Input driver for PCAP events:
+ * * Power key
+ * * Headphone button
+ *
+ * Copyright (c) 2008,2009 Ilya Petrov <ilya.muromec@gmail.com>
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/mfd/ezx-pcap.h>
+#include <linux/slab.h>
+
+struct pcap_keys {
+ struct pcap_chip *pcap;
+ struct input_dev *input;
+};
+
+/* PCAP2 interrupts us on keypress */
+static irqreturn_t pcap_keys_handler(int irq, void *_pcap_keys)
+{
+ struct pcap_keys *pcap_keys = _pcap_keys;
+ int pirq = irq_to_pcap(pcap_keys->pcap, irq);
+ u32 pstat;
+
+ ezx_pcap_read(pcap_keys->pcap, PCAP_REG_PSTAT, &pstat);
+ pstat &= 1 << pirq;
+
+ switch (pirq) {
+ case PCAP_IRQ_ONOFF:
+ input_report_key(pcap_keys->input, KEY_POWER, !pstat);
+ break;
+ case PCAP_IRQ_MIC:
+ input_report_key(pcap_keys->input, KEY_HP, !pstat);
+ break;
+ }
+
+ input_sync(pcap_keys->input);
+
+ return IRQ_HANDLED;
+}
+
+static int pcap_keys_probe(struct platform_device *pdev)
+{
+ int err = -ENOMEM;
+ struct pcap_keys *pcap_keys;
+ struct input_dev *input_dev;
+
+ pcap_keys = kmalloc(sizeof(struct pcap_keys), GFP_KERNEL);
+ if (!pcap_keys)
+ return err;
+
+ pcap_keys->pcap = dev_get_drvdata(pdev->dev.parent);
+
+ input_dev = input_allocate_device();
+ if (!input_dev)
+ goto fail;
+
+ pcap_keys->input = input_dev;
+
+ platform_set_drvdata(pdev, pcap_keys);
+ input_dev->name = pdev->name;
+ input_dev->phys = "pcap-keys/input0";
+ input_dev->id.bustype = BUS_HOST;
+ input_dev->dev.parent = &pdev->dev;
+
+ __set_bit(EV_KEY, input_dev->evbit);
+ __set_bit(KEY_POWER, input_dev->keybit);
+ __set_bit(KEY_HP, input_dev->keybit);
+
+ err = input_register_device(input_dev);
+ if (err)
+ goto fail_allocate;
+
+ err = request_irq(pcap_to_irq(pcap_keys->pcap, PCAP_IRQ_ONOFF),
+ pcap_keys_handler, 0, "Power key", pcap_keys);
+ if (err)
+ goto fail_register;
+
+ err = request_irq(pcap_to_irq(pcap_keys->pcap, PCAP_IRQ_MIC),
+ pcap_keys_handler, 0, "Headphone button", pcap_keys);
+ if (err)
+ goto fail_pwrkey;
+
+ return 0;
+
+fail_pwrkey:
+ free_irq(pcap_to_irq(pcap_keys->pcap, PCAP_IRQ_ONOFF), pcap_keys);
+fail_register:
+ input_unregister_device(input_dev);
+ goto fail;
+fail_allocate:
+ input_free_device(input_dev);
+fail:
+ kfree(pcap_keys);
+ return err;
+}
+
+static int pcap_keys_remove(struct platform_device *pdev)
+{
+ struct pcap_keys *pcap_keys = platform_get_drvdata(pdev);
+
+ free_irq(pcap_to_irq(pcap_keys->pcap, PCAP_IRQ_ONOFF), pcap_keys);
+ free_irq(pcap_to_irq(pcap_keys->pcap, PCAP_IRQ_MIC), pcap_keys);
+
+ input_unregister_device(pcap_keys->input);
+ kfree(pcap_keys);
+
+ return 0;
+}
+
+static struct platform_driver pcap_keys_device_driver = {
+ .probe = pcap_keys_probe,
+ .remove = pcap_keys_remove,
+ .driver = {
+ .name = "pcap-keys",
+ }
+};
+module_platform_driver(pcap_keys_device_driver);
+
+MODULE_DESCRIPTION("Motorola PCAP2 input events driver");
+MODULE_AUTHOR("Ilya Petrov <ilya.muromec@gmail.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:pcap_keys");
diff --git a/drivers/input/misc/pcf50633-input.c b/drivers/input/misc/pcf50633-input.c
new file mode 100644
index 000000000..4c60c70c4
--- /dev/null
+++ b/drivers/input/misc/pcf50633-input.c
@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* NXP PCF50633 Input Driver
+ *
+ * (C) 2006-2008 by Openmoko, Inc.
+ * Author: Balaji Rao <balajirrao@openmoko.org>
+ * All rights reserved.
+ *
+ * Broken down from monstrous PCF50633 driver mainly by
+ * Harald Welte, Andy Green and Werner Almesberger
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+
+#include <linux/mfd/pcf50633/core.h>
+
+#define PCF50633_OOCSTAT_ONKEY 0x01
+#define PCF50633_REG_OOCSTAT 0x12
+#define PCF50633_REG_OOCMODE 0x10
+
+struct pcf50633_input {
+ struct pcf50633 *pcf;
+ struct input_dev *input_dev;
+};
+
+static void
+pcf50633_input_irq(int irq, void *data)
+{
+ struct pcf50633_input *input;
+ int onkey_released;
+
+ input = data;
+
+ /* We report only one event depending on the key press status */
+ onkey_released = pcf50633_reg_read(input->pcf, PCF50633_REG_OOCSTAT)
+ & PCF50633_OOCSTAT_ONKEY;
+
+ if (irq == PCF50633_IRQ_ONKEYF && !onkey_released)
+ input_report_key(input->input_dev, KEY_POWER, 1);
+ else if (irq == PCF50633_IRQ_ONKEYR && onkey_released)
+ input_report_key(input->input_dev, KEY_POWER, 0);
+
+ input_sync(input->input_dev);
+}
+
+static int pcf50633_input_probe(struct platform_device *pdev)
+{
+ struct pcf50633_input *input;
+ struct input_dev *input_dev;
+ int ret;
+
+
+ input = kzalloc(sizeof(*input), GFP_KERNEL);
+ if (!input)
+ return -ENOMEM;
+
+ input_dev = input_allocate_device();
+ if (!input_dev) {
+ kfree(input);
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(pdev, input);
+ input->pcf = dev_to_pcf50633(pdev->dev.parent);
+ input->input_dev = input_dev;
+
+ input_dev->name = "PCF50633 PMU events";
+ input_dev->id.bustype = BUS_I2C;
+ input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_PWR);
+ set_bit(KEY_POWER, input_dev->keybit);
+
+ ret = input_register_device(input_dev);
+ if (ret) {
+ input_free_device(input_dev);
+ kfree(input);
+ return ret;
+ }
+ pcf50633_register_irq(input->pcf, PCF50633_IRQ_ONKEYR,
+ pcf50633_input_irq, input);
+ pcf50633_register_irq(input->pcf, PCF50633_IRQ_ONKEYF,
+ pcf50633_input_irq, input);
+
+ return 0;
+}
+
+static int pcf50633_input_remove(struct platform_device *pdev)
+{
+ struct pcf50633_input *input = platform_get_drvdata(pdev);
+
+ pcf50633_free_irq(input->pcf, PCF50633_IRQ_ONKEYR);
+ pcf50633_free_irq(input->pcf, PCF50633_IRQ_ONKEYF);
+
+ input_unregister_device(input->input_dev);
+ kfree(input);
+
+ return 0;
+}
+
+static struct platform_driver pcf50633_input_driver = {
+ .driver = {
+ .name = "pcf50633-input",
+ },
+ .probe = pcf50633_input_probe,
+ .remove = pcf50633_input_remove,
+};
+module_platform_driver(pcf50633_input_driver);
+
+MODULE_AUTHOR("Balaji Rao <balajirrao@openmoko.org>");
+MODULE_DESCRIPTION("PCF50633 input driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:pcf50633-input");
diff --git a/drivers/input/misc/pcf8574_keypad.c b/drivers/input/misc/pcf8574_keypad.c
new file mode 100644
index 000000000..cfd6640e4
--- /dev/null
+++ b/drivers/input/misc/pcf8574_keypad.c
@@ -0,0 +1,221 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for a keypad w/16 buttons connected to a PCF8574 I2C I/O expander
+ *
+ * Copyright 2005-2008 Analog Devices Inc.
+ */
+
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#define DRV_NAME "pcf8574_keypad"
+
+static const unsigned char pcf8574_kp_btncode[] = {
+ [0] = KEY_RESERVED,
+ [1] = KEY_ENTER,
+ [2] = KEY_BACKSLASH,
+ [3] = KEY_0,
+ [4] = KEY_RIGHTBRACE,
+ [5] = KEY_C,
+ [6] = KEY_9,
+ [7] = KEY_8,
+ [8] = KEY_7,
+ [9] = KEY_B,
+ [10] = KEY_6,
+ [11] = KEY_5,
+ [12] = KEY_4,
+ [13] = KEY_A,
+ [14] = KEY_3,
+ [15] = KEY_2,
+ [16] = KEY_1
+};
+
+struct kp_data {
+ unsigned short btncode[ARRAY_SIZE(pcf8574_kp_btncode)];
+ struct input_dev *idev;
+ struct i2c_client *client;
+ char name[64];
+ char phys[32];
+ unsigned char laststate;
+};
+
+static short read_state(struct kp_data *lp)
+{
+ unsigned char x, y, a, b;
+
+ i2c_smbus_write_byte(lp->client, 240);
+ x = 0xF & (~(i2c_smbus_read_byte(lp->client) >> 4));
+
+ i2c_smbus_write_byte(lp->client, 15);
+ y = 0xF & (~i2c_smbus_read_byte(lp->client));
+
+ for (a = 0; x > 0; a++)
+ x = x >> 1;
+ for (b = 0; y > 0; b++)
+ y = y >> 1;
+
+ return ((a - 1) * 4) + b;
+}
+
+static irqreturn_t pcf8574_kp_irq_handler(int irq, void *dev_id)
+{
+ struct kp_data *lp = dev_id;
+ unsigned char nextstate = read_state(lp);
+
+ if (lp->laststate != nextstate) {
+ int key_down = nextstate < ARRAY_SIZE(lp->btncode);
+ unsigned short keycode = key_down ?
+ lp->btncode[nextstate] : lp->btncode[lp->laststate];
+
+ input_report_key(lp->idev, keycode, key_down);
+ input_sync(lp->idev);
+
+ lp->laststate = nextstate;
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int pcf8574_kp_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+ int i, ret;
+ struct input_dev *idev;
+ struct kp_data *lp;
+
+ if (i2c_smbus_write_byte(client, 240) < 0) {
+ dev_err(&client->dev, "probe: write fail\n");
+ return -ENODEV;
+ }
+
+ lp = kzalloc(sizeof(*lp), GFP_KERNEL);
+ if (!lp)
+ return -ENOMEM;
+
+ idev = input_allocate_device();
+ if (!idev) {
+ dev_err(&client->dev, "Can't allocate input device\n");
+ ret = -ENOMEM;
+ goto fail_allocate;
+ }
+
+ lp->idev = idev;
+ lp->client = client;
+
+ idev->evbit[0] = BIT_MASK(EV_KEY);
+ idev->keycode = lp->btncode;
+ idev->keycodesize = sizeof(lp->btncode[0]);
+ idev->keycodemax = ARRAY_SIZE(lp->btncode);
+
+ for (i = 0; i < ARRAY_SIZE(pcf8574_kp_btncode); i++) {
+ if (lp->btncode[i] <= KEY_MAX) {
+ lp->btncode[i] = pcf8574_kp_btncode[i];
+ __set_bit(lp->btncode[i], idev->keybit);
+ }
+ }
+ __clear_bit(KEY_RESERVED, idev->keybit);
+
+ sprintf(lp->name, DRV_NAME);
+ sprintf(lp->phys, "kp_data/input0");
+
+ idev->name = lp->name;
+ idev->phys = lp->phys;
+ idev->id.bustype = BUS_I2C;
+ idev->id.vendor = 0x0001;
+ idev->id.product = 0x0001;
+ idev->id.version = 0x0100;
+
+ lp->laststate = read_state(lp);
+
+ ret = request_threaded_irq(client->irq, NULL, pcf8574_kp_irq_handler,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ DRV_NAME, lp);
+ if (ret) {
+ dev_err(&client->dev, "IRQ %d is not free\n", client->irq);
+ goto fail_free_device;
+ }
+
+ ret = input_register_device(idev);
+ if (ret) {
+ dev_err(&client->dev, "input_register_device() failed\n");
+ goto fail_free_irq;
+ }
+
+ i2c_set_clientdata(client, lp);
+ return 0;
+
+ fail_free_irq:
+ free_irq(client->irq, lp);
+ fail_free_device:
+ input_free_device(idev);
+ fail_allocate:
+ kfree(lp);
+
+ return ret;
+}
+
+static void pcf8574_kp_remove(struct i2c_client *client)
+{
+ struct kp_data *lp = i2c_get_clientdata(client);
+
+ free_irq(client->irq, lp);
+
+ input_unregister_device(lp->idev);
+ kfree(lp);
+}
+
+#ifdef CONFIG_PM
+static int pcf8574_kp_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+
+ enable_irq(client->irq);
+
+ return 0;
+}
+
+static int pcf8574_kp_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+
+ disable_irq(client->irq);
+
+ return 0;
+}
+
+static const struct dev_pm_ops pcf8574_kp_pm_ops = {
+ .suspend = pcf8574_kp_suspend,
+ .resume = pcf8574_kp_resume,
+};
+
+#else
+# define pcf8574_kp_resume NULL
+# define pcf8574_kp_suspend NULL
+#endif
+
+static const struct i2c_device_id pcf8574_kp_id[] = {
+ { DRV_NAME, 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, pcf8574_kp_id);
+
+static struct i2c_driver pcf8574_kp_driver = {
+ .driver = {
+ .name = DRV_NAME,
+#ifdef CONFIG_PM
+ .pm = &pcf8574_kp_pm_ops,
+#endif
+ },
+ .probe = pcf8574_kp_probe,
+ .remove = pcf8574_kp_remove,
+ .id_table = pcf8574_kp_id,
+};
+
+module_i2c_driver(pcf8574_kp_driver);
+
+MODULE_AUTHOR("Michael Hennerich");
+MODULE_DESCRIPTION("Keypad input driver for 16 keys connected to PCF8574");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/pcspkr.c b/drivers/input/misc/pcspkr.c
new file mode 100644
index 000000000..9c666b2f1
--- /dev/null
+++ b/drivers/input/misc/pcspkr.c
@@ -0,0 +1,136 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * PC Speaker beeper driver for Linux
+ *
+ * Copyright (c) 2002 Vojtech Pavlik
+ * Copyright (c) 1992 Orest Zborowski
+ */
+
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i8253.h>
+#include <linux/input.h>
+#include <linux/platform_device.h>
+#include <linux/timex.h>
+#include <linux/io.h>
+
+MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
+MODULE_DESCRIPTION("PC Speaker beeper driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:pcspkr");
+
+static int pcspkr_event(struct input_dev *dev, unsigned int type,
+ unsigned int code, int value)
+{
+ unsigned int count = 0;
+ unsigned long flags;
+
+ if (type != EV_SND)
+ return -EINVAL;
+
+ switch (code) {
+ case SND_BELL:
+ if (value)
+ value = 1000;
+ break;
+ case SND_TONE:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (value > 20 && value < 32767)
+ count = PIT_TICK_RATE / value;
+
+ raw_spin_lock_irqsave(&i8253_lock, flags);
+
+ if (count) {
+ /* set command for counter 2, 2 byte write */
+ outb_p(0xB6, 0x43);
+ /* select desired HZ */
+ outb_p(count & 0xff, 0x42);
+ outb((count >> 8) & 0xff, 0x42);
+ /* enable counter 2 */
+ outb_p(inb_p(0x61) | 3, 0x61);
+ } else {
+ /* disable counter 2 */
+ outb(inb_p(0x61) & 0xFC, 0x61);
+ }
+
+ raw_spin_unlock_irqrestore(&i8253_lock, flags);
+
+ return 0;
+}
+
+static int pcspkr_probe(struct platform_device *dev)
+{
+ struct input_dev *pcspkr_dev;
+ int err;
+
+ pcspkr_dev = input_allocate_device();
+ if (!pcspkr_dev)
+ return -ENOMEM;
+
+ pcspkr_dev->name = "PC Speaker";
+ pcspkr_dev->phys = "isa0061/input0";
+ pcspkr_dev->id.bustype = BUS_ISA;
+ pcspkr_dev->id.vendor = 0x001f;
+ pcspkr_dev->id.product = 0x0001;
+ pcspkr_dev->id.version = 0x0100;
+ pcspkr_dev->dev.parent = &dev->dev;
+
+ pcspkr_dev->evbit[0] = BIT_MASK(EV_SND);
+ pcspkr_dev->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE);
+ pcspkr_dev->event = pcspkr_event;
+
+ err = input_register_device(pcspkr_dev);
+ if (err) {
+ input_free_device(pcspkr_dev);
+ return err;
+ }
+
+ platform_set_drvdata(dev, pcspkr_dev);
+
+ return 0;
+}
+
+static int pcspkr_remove(struct platform_device *dev)
+{
+ struct input_dev *pcspkr_dev = platform_get_drvdata(dev);
+
+ input_unregister_device(pcspkr_dev);
+ /* turn off the speaker */
+ pcspkr_event(NULL, EV_SND, SND_BELL, 0);
+
+ return 0;
+}
+
+static int pcspkr_suspend(struct device *dev)
+{
+ pcspkr_event(NULL, EV_SND, SND_BELL, 0);
+
+ return 0;
+}
+
+static void pcspkr_shutdown(struct platform_device *dev)
+{
+ /* turn off the speaker */
+ pcspkr_event(NULL, EV_SND, SND_BELL, 0);
+}
+
+static const struct dev_pm_ops pcspkr_pm_ops = {
+ .suspend = pcspkr_suspend,
+};
+
+static struct platform_driver pcspkr_platform_driver = {
+ .driver = {
+ .name = "pcspkr",
+ .pm = &pcspkr_pm_ops,
+ },
+ .probe = pcspkr_probe,
+ .remove = pcspkr_remove,
+ .shutdown = pcspkr_shutdown,
+};
+module_platform_driver(pcspkr_platform_driver);
+
diff --git a/drivers/input/misc/pm8941-pwrkey.c b/drivers/input/misc/pm8941-pwrkey.c
new file mode 100644
index 000000000..5dd68a02c
--- /dev/null
+++ b/drivers/input/misc/pm8941-pwrkey.c
@@ -0,0 +1,481 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2010-2011, 2020-2021, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2014, Sony Mobile Communications Inc.
+ */
+
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/ktime.h>
+#include <linux/log2.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <linux/regmap.h>
+
+#define PON_REV2 0x01
+
+#define PON_SUBTYPE 0x05
+
+#define PON_SUBTYPE_PRIMARY 0x01
+#define PON_SUBTYPE_SECONDARY 0x02
+#define PON_SUBTYPE_1REG 0x03
+#define PON_SUBTYPE_GEN2_PRIMARY 0x04
+#define PON_SUBTYPE_GEN2_SECONDARY 0x05
+#define PON_SUBTYPE_GEN3_PBS 0x08
+#define PON_SUBTYPE_GEN3_HLOS 0x09
+
+#define PON_RT_STS 0x10
+#define PON_KPDPWR_N_SET BIT(0)
+#define PON_RESIN_N_SET BIT(1)
+#define PON_GEN3_RESIN_N_SET BIT(6)
+#define PON_GEN3_KPDPWR_N_SET BIT(7)
+
+#define PON_PS_HOLD_RST_CTL 0x5a
+#define PON_PS_HOLD_RST_CTL2 0x5b
+#define PON_PS_HOLD_ENABLE BIT(7)
+#define PON_PS_HOLD_TYPE_MASK 0x0f
+#define PON_PS_HOLD_TYPE_WARM_RESET 1
+#define PON_PS_HOLD_TYPE_SHUTDOWN 4
+#define PON_PS_HOLD_TYPE_HARD_RESET 7
+
+#define PON_PULL_CTL 0x70
+#define PON_KPDPWR_PULL_UP BIT(1)
+#define PON_RESIN_PULL_UP BIT(0)
+
+#define PON_DBC_CTL 0x71
+#define PON_DBC_DELAY_MASK_GEN1 0x7
+#define PON_DBC_DELAY_MASK_GEN2 0xf
+#define PON_DBC_SHIFT_GEN1 6
+#define PON_DBC_SHIFT_GEN2 14
+
+struct pm8941_data {
+ unsigned int pull_up_bit;
+ unsigned int status_bit;
+ bool supports_ps_hold_poff_config;
+ bool supports_debounce_config;
+ bool has_pon_pbs;
+ const char *name;
+ const char *phys;
+};
+
+struct pm8941_pwrkey {
+ struct device *dev;
+ int irq;
+ u32 baseaddr;
+ u32 pon_pbs_baseaddr;
+ struct regmap *regmap;
+ struct input_dev *input;
+
+ unsigned int revision;
+ unsigned int subtype;
+ struct notifier_block reboot_notifier;
+
+ u32 code;
+ u32 sw_debounce_time_us;
+ ktime_t sw_debounce_end_time;
+ bool last_status;
+ const struct pm8941_data *data;
+};
+
+static int pm8941_reboot_notify(struct notifier_block *nb,
+ unsigned long code, void *unused)
+{
+ struct pm8941_pwrkey *pwrkey = container_of(nb, struct pm8941_pwrkey,
+ reboot_notifier);
+ unsigned int enable_reg;
+ unsigned int reset_type;
+ int error;
+
+ /* PMICs with revision 0 have the enable bit in same register as ctrl */
+ if (pwrkey->revision == 0)
+ enable_reg = PON_PS_HOLD_RST_CTL;
+ else
+ enable_reg = PON_PS_HOLD_RST_CTL2;
+
+ error = regmap_update_bits(pwrkey->regmap,
+ pwrkey->baseaddr + enable_reg,
+ PON_PS_HOLD_ENABLE,
+ 0);
+ if (error)
+ dev_err(pwrkey->dev,
+ "unable to clear ps hold reset enable: %d\n",
+ error);
+
+ /*
+ * Updates of PON_PS_HOLD_ENABLE requires 3 sleep cycles between
+ * writes.
+ */
+ usleep_range(100, 1000);
+
+ switch (code) {
+ case SYS_HALT:
+ case SYS_POWER_OFF:
+ reset_type = PON_PS_HOLD_TYPE_SHUTDOWN;
+ break;
+ case SYS_RESTART:
+ default:
+ if (reboot_mode == REBOOT_WARM)
+ reset_type = PON_PS_HOLD_TYPE_WARM_RESET;
+ else
+ reset_type = PON_PS_HOLD_TYPE_HARD_RESET;
+ break;
+ }
+
+ error = regmap_update_bits(pwrkey->regmap,
+ pwrkey->baseaddr + PON_PS_HOLD_RST_CTL,
+ PON_PS_HOLD_TYPE_MASK,
+ reset_type);
+ if (error)
+ dev_err(pwrkey->dev, "unable to set ps hold reset type: %d\n",
+ error);
+
+ error = regmap_update_bits(pwrkey->regmap,
+ pwrkey->baseaddr + enable_reg,
+ PON_PS_HOLD_ENABLE,
+ PON_PS_HOLD_ENABLE);
+ if (error)
+ dev_err(pwrkey->dev, "unable to re-set enable: %d\n", error);
+
+ return NOTIFY_DONE;
+}
+
+static irqreturn_t pm8941_pwrkey_irq(int irq, void *_data)
+{
+ struct pm8941_pwrkey *pwrkey = _data;
+ unsigned int sts;
+ int err;
+
+ if (pwrkey->sw_debounce_time_us) {
+ if (ktime_before(ktime_get(), pwrkey->sw_debounce_end_time)) {
+ dev_dbg(pwrkey->dev,
+ "ignoring key event received before debounce end %llu us\n",
+ pwrkey->sw_debounce_end_time);
+ return IRQ_HANDLED;
+ }
+ }
+
+ err = regmap_read(pwrkey->regmap, pwrkey->baseaddr + PON_RT_STS, &sts);
+ if (err)
+ return IRQ_HANDLED;
+
+ sts &= pwrkey->data->status_bit;
+
+ if (pwrkey->sw_debounce_time_us && !sts)
+ pwrkey->sw_debounce_end_time = ktime_add_us(ktime_get(),
+ pwrkey->sw_debounce_time_us);
+
+ /*
+ * Simulate a press event in case a release event occurred without a
+ * corresponding press event.
+ */
+ if (!pwrkey->last_status && !sts) {
+ input_report_key(pwrkey->input, pwrkey->code, 1);
+ input_sync(pwrkey->input);
+ }
+ pwrkey->last_status = sts;
+
+ input_report_key(pwrkey->input, pwrkey->code, sts);
+ input_sync(pwrkey->input);
+
+ return IRQ_HANDLED;
+}
+
+static int pm8941_pwrkey_sw_debounce_init(struct pm8941_pwrkey *pwrkey)
+{
+ unsigned int val, addr, mask;
+ int error;
+
+ if (pwrkey->data->has_pon_pbs && !pwrkey->pon_pbs_baseaddr) {
+ dev_err(pwrkey->dev,
+ "PON_PBS address missing, can't read HW debounce time\n");
+ return 0;
+ }
+
+ if (pwrkey->pon_pbs_baseaddr)
+ addr = pwrkey->pon_pbs_baseaddr + PON_DBC_CTL;
+ else
+ addr = pwrkey->baseaddr + PON_DBC_CTL;
+ error = regmap_read(pwrkey->regmap, addr, &val);
+ if (error)
+ return error;
+
+ if (pwrkey->subtype >= PON_SUBTYPE_GEN2_PRIMARY)
+ mask = 0xf;
+ else
+ mask = 0x7;
+
+ pwrkey->sw_debounce_time_us =
+ 2 * USEC_PER_SEC / (1 << (mask - (val & mask)));
+
+ dev_dbg(pwrkey->dev, "SW debounce time = %u us\n",
+ pwrkey->sw_debounce_time_us);
+
+ return 0;
+}
+
+static int __maybe_unused pm8941_pwrkey_suspend(struct device *dev)
+{
+ struct pm8941_pwrkey *pwrkey = dev_get_drvdata(dev);
+
+ if (device_may_wakeup(dev))
+ enable_irq_wake(pwrkey->irq);
+
+ return 0;
+}
+
+static int __maybe_unused pm8941_pwrkey_resume(struct device *dev)
+{
+ struct pm8941_pwrkey *pwrkey = dev_get_drvdata(dev);
+
+ if (device_may_wakeup(dev))
+ disable_irq_wake(pwrkey->irq);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(pm8941_pwr_key_pm_ops,
+ pm8941_pwrkey_suspend, pm8941_pwrkey_resume);
+
+static int pm8941_pwrkey_probe(struct platform_device *pdev)
+{
+ struct pm8941_pwrkey *pwrkey;
+ bool pull_up;
+ struct device *parent;
+ struct device_node *regmap_node;
+ const __be32 *addr;
+ u32 req_delay, mask, delay_shift;
+ int error;
+
+ if (of_property_read_u32(pdev->dev.of_node, "debounce", &req_delay))
+ req_delay = 15625;
+
+ if (req_delay > 2000000 || req_delay == 0) {
+ dev_err(&pdev->dev, "invalid debounce time: %u\n", req_delay);
+ return -EINVAL;
+ }
+
+ pull_up = of_property_read_bool(pdev->dev.of_node, "bias-pull-up");
+
+ pwrkey = devm_kzalloc(&pdev->dev, sizeof(*pwrkey), GFP_KERNEL);
+ if (!pwrkey)
+ return -ENOMEM;
+
+ pwrkey->dev = &pdev->dev;
+ pwrkey->data = of_device_get_match_data(&pdev->dev);
+
+ parent = pdev->dev.parent;
+ regmap_node = pdev->dev.of_node;
+ pwrkey->regmap = dev_get_regmap(parent, NULL);
+ if (!pwrkey->regmap) {
+ regmap_node = parent->of_node;
+ /*
+ * We failed to get regmap for parent. Let's see if we are
+ * a child of pon node and read regmap and reg from its
+ * parent.
+ */
+ pwrkey->regmap = dev_get_regmap(parent->parent, NULL);
+ if (!pwrkey->regmap) {
+ dev_err(&pdev->dev, "failed to locate regmap\n");
+ return -ENODEV;
+ }
+ }
+
+ addr = of_get_address(regmap_node, 0, NULL, NULL);
+ if (!addr) {
+ dev_err(&pdev->dev, "reg property missing\n");
+ return -EINVAL;
+ }
+ pwrkey->baseaddr = be32_to_cpup(addr);
+
+ if (pwrkey->data->has_pon_pbs) {
+ /* PON_PBS base address is optional */
+ addr = of_get_address(regmap_node, 1, NULL, NULL);
+ if (addr)
+ pwrkey->pon_pbs_baseaddr = be32_to_cpup(addr);
+ }
+
+ pwrkey->irq = platform_get_irq(pdev, 0);
+ if (pwrkey->irq < 0)
+ return pwrkey->irq;
+
+ error = regmap_read(pwrkey->regmap, pwrkey->baseaddr + PON_REV2,
+ &pwrkey->revision);
+ if (error) {
+ dev_err(&pdev->dev, "failed to read revision: %d\n", error);
+ return error;
+ }
+
+ error = regmap_read(pwrkey->regmap, pwrkey->baseaddr + PON_SUBTYPE,
+ &pwrkey->subtype);
+ if (error) {
+ dev_err(&pdev->dev, "failed to read subtype: %d\n", error);
+ return error;
+ }
+
+ error = of_property_read_u32(pdev->dev.of_node, "linux,code",
+ &pwrkey->code);
+ if (error) {
+ dev_dbg(&pdev->dev,
+ "no linux,code assuming power (%d)\n", error);
+ pwrkey->code = KEY_POWER;
+ }
+
+ pwrkey->input = devm_input_allocate_device(&pdev->dev);
+ if (!pwrkey->input) {
+ dev_dbg(&pdev->dev, "unable to allocate input device\n");
+ return -ENOMEM;
+ }
+
+ input_set_capability(pwrkey->input, EV_KEY, pwrkey->code);
+
+ pwrkey->input->name = pwrkey->data->name;
+ pwrkey->input->phys = pwrkey->data->phys;
+
+ if (pwrkey->data->supports_debounce_config) {
+ if (pwrkey->subtype >= PON_SUBTYPE_GEN2_PRIMARY) {
+ mask = PON_DBC_DELAY_MASK_GEN2;
+ delay_shift = PON_DBC_SHIFT_GEN2;
+ } else {
+ mask = PON_DBC_DELAY_MASK_GEN1;
+ delay_shift = PON_DBC_SHIFT_GEN1;
+ }
+
+ req_delay = (req_delay << delay_shift) / USEC_PER_SEC;
+ req_delay = ilog2(req_delay);
+
+ error = regmap_update_bits(pwrkey->regmap,
+ pwrkey->baseaddr + PON_DBC_CTL,
+ mask,
+ req_delay);
+ if (error) {
+ dev_err(&pdev->dev, "failed to set debounce: %d\n",
+ error);
+ return error;
+ }
+ }
+
+ error = pm8941_pwrkey_sw_debounce_init(pwrkey);
+ if (error)
+ return error;
+
+ if (pwrkey->data->pull_up_bit) {
+ error = regmap_update_bits(pwrkey->regmap,
+ pwrkey->baseaddr + PON_PULL_CTL,
+ pwrkey->data->pull_up_bit,
+ pull_up ? pwrkey->data->pull_up_bit :
+ 0);
+ if (error) {
+ dev_err(&pdev->dev, "failed to set pull: %d\n", error);
+ return error;
+ }
+ }
+
+ error = devm_request_threaded_irq(&pdev->dev, pwrkey->irq,
+ NULL, pm8941_pwrkey_irq,
+ IRQF_ONESHOT,
+ pwrkey->data->name, pwrkey);
+ if (error) {
+ dev_err(&pdev->dev, "failed requesting IRQ: %d\n", error);
+ return error;
+ }
+
+ error = input_register_device(pwrkey->input);
+ if (error) {
+ dev_err(&pdev->dev, "failed to register input device: %d\n",
+ error);
+ return error;
+ }
+
+ if (pwrkey->data->supports_ps_hold_poff_config) {
+ pwrkey->reboot_notifier.notifier_call = pm8941_reboot_notify;
+ error = register_reboot_notifier(&pwrkey->reboot_notifier);
+ if (error) {
+ dev_err(&pdev->dev, "failed to register reboot notifier: %d\n",
+ error);
+ return error;
+ }
+ }
+
+ platform_set_drvdata(pdev, pwrkey);
+ device_init_wakeup(&pdev->dev, 1);
+
+ return 0;
+}
+
+static int pm8941_pwrkey_remove(struct platform_device *pdev)
+{
+ struct pm8941_pwrkey *pwrkey = platform_get_drvdata(pdev);
+
+ if (pwrkey->data->supports_ps_hold_poff_config)
+ unregister_reboot_notifier(&pwrkey->reboot_notifier);
+
+ return 0;
+}
+
+static const struct pm8941_data pwrkey_data = {
+ .pull_up_bit = PON_KPDPWR_PULL_UP,
+ .status_bit = PON_KPDPWR_N_SET,
+ .name = "pm8941_pwrkey",
+ .phys = "pm8941_pwrkey/input0",
+ .supports_ps_hold_poff_config = true,
+ .supports_debounce_config = true,
+ .has_pon_pbs = false,
+};
+
+static const struct pm8941_data resin_data = {
+ .pull_up_bit = PON_RESIN_PULL_UP,
+ .status_bit = PON_RESIN_N_SET,
+ .name = "pm8941_resin",
+ .phys = "pm8941_resin/input0",
+ .supports_ps_hold_poff_config = true,
+ .supports_debounce_config = true,
+ .has_pon_pbs = false,
+};
+
+static const struct pm8941_data pon_gen3_pwrkey_data = {
+ .status_bit = PON_GEN3_KPDPWR_N_SET,
+ .name = "pmic_pwrkey",
+ .phys = "pmic_pwrkey/input0",
+ .supports_ps_hold_poff_config = false,
+ .supports_debounce_config = false,
+ .has_pon_pbs = true,
+};
+
+static const struct pm8941_data pon_gen3_resin_data = {
+ .status_bit = PON_GEN3_RESIN_N_SET,
+ .name = "pmic_resin",
+ .phys = "pmic_resin/input0",
+ .supports_ps_hold_poff_config = false,
+ .supports_debounce_config = false,
+ .has_pon_pbs = true,
+};
+
+static const struct of_device_id pm8941_pwr_key_id_table[] = {
+ { .compatible = "qcom,pm8941-pwrkey", .data = &pwrkey_data },
+ { .compatible = "qcom,pm8941-resin", .data = &resin_data },
+ { .compatible = "qcom,pmk8350-pwrkey", .data = &pon_gen3_pwrkey_data },
+ { .compatible = "qcom,pmk8350-resin", .data = &pon_gen3_resin_data },
+ { }
+};
+MODULE_DEVICE_TABLE(of, pm8941_pwr_key_id_table);
+
+static struct platform_driver pm8941_pwrkey_driver = {
+ .probe = pm8941_pwrkey_probe,
+ .remove = pm8941_pwrkey_remove,
+ .driver = {
+ .name = "pm8941-pwrkey",
+ .pm = &pm8941_pwr_key_pm_ops,
+ .of_match_table = of_match_ptr(pm8941_pwr_key_id_table),
+ },
+};
+module_platform_driver(pm8941_pwrkey_driver);
+
+MODULE_DESCRIPTION("PM8941 Power Key driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/misc/pm8xxx-vibrator.c b/drivers/input/misc/pm8xxx-vibrator.c
new file mode 100644
index 000000000..53ad25eaf
--- /dev/null
+++ b/drivers/input/misc/pm8xxx-vibrator.c
@@ -0,0 +1,262 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+ */
+
+#include <linux/errno.h>
+#include <linux/input.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#define VIB_MAX_LEVEL_mV (3100)
+#define VIB_MIN_LEVEL_mV (1200)
+#define VIB_MAX_LEVELS (VIB_MAX_LEVEL_mV - VIB_MIN_LEVEL_mV)
+
+#define MAX_FF_SPEED 0xff
+
+struct pm8xxx_regs {
+ unsigned int enable_addr;
+ unsigned int enable_mask;
+
+ unsigned int drv_addr;
+ unsigned int drv_mask;
+ unsigned int drv_shift;
+ unsigned int drv_en_manual_mask;
+};
+
+static const struct pm8xxx_regs pm8058_regs = {
+ .drv_addr = 0x4A,
+ .drv_mask = 0xf8,
+ .drv_shift = 3,
+ .drv_en_manual_mask = 0xfc,
+};
+
+static struct pm8xxx_regs pm8916_regs = {
+ .enable_addr = 0xc046,
+ .enable_mask = BIT(7),
+ .drv_addr = 0xc041,
+ .drv_mask = 0x1F,
+ .drv_shift = 0,
+ .drv_en_manual_mask = 0,
+};
+
+/**
+ * struct pm8xxx_vib - structure to hold vibrator data
+ * @vib_input_dev: input device supporting force feedback
+ * @work: work structure to set the vibration parameters
+ * @regmap: regmap for register read/write
+ * @regs: registers' info
+ * @speed: speed of vibration set from userland
+ * @active: state of vibrator
+ * @level: level of vibration to set in the chip
+ * @reg_vib_drv: regs->drv_addr register value
+ */
+struct pm8xxx_vib {
+ struct input_dev *vib_input_dev;
+ struct work_struct work;
+ struct regmap *regmap;
+ const struct pm8xxx_regs *regs;
+ int speed;
+ int level;
+ bool active;
+ u8 reg_vib_drv;
+};
+
+/**
+ * pm8xxx_vib_set - handler to start/stop vibration
+ * @vib: pointer to vibrator structure
+ * @on: state to set
+ */
+static int pm8xxx_vib_set(struct pm8xxx_vib *vib, bool on)
+{
+ int rc;
+ unsigned int val = vib->reg_vib_drv;
+ const struct pm8xxx_regs *regs = vib->regs;
+
+ if (on)
+ val |= (vib->level << regs->drv_shift) & regs->drv_mask;
+ else
+ val &= ~regs->drv_mask;
+
+ rc = regmap_write(vib->regmap, regs->drv_addr, val);
+ if (rc < 0)
+ return rc;
+
+ vib->reg_vib_drv = val;
+
+ if (regs->enable_mask)
+ rc = regmap_update_bits(vib->regmap, regs->enable_addr,
+ regs->enable_mask, on ? ~0 : 0);
+
+ return rc;
+}
+
+/**
+ * pm8xxx_work_handler - worker to set vibration level
+ * @work: pointer to work_struct
+ */
+static void pm8xxx_work_handler(struct work_struct *work)
+{
+ struct pm8xxx_vib *vib = container_of(work, struct pm8xxx_vib, work);
+ const struct pm8xxx_regs *regs = vib->regs;
+ int rc;
+ unsigned int val;
+
+ rc = regmap_read(vib->regmap, regs->drv_addr, &val);
+ if (rc < 0)
+ return;
+
+ /*
+ * pmic vibrator supports voltage ranges from 1.2 to 3.1V, so
+ * scale the level to fit into these ranges.
+ */
+ if (vib->speed) {
+ vib->active = true;
+ vib->level = ((VIB_MAX_LEVELS * vib->speed) / MAX_FF_SPEED) +
+ VIB_MIN_LEVEL_mV;
+ vib->level /= 100;
+ } else {
+ vib->active = false;
+ vib->level = VIB_MIN_LEVEL_mV / 100;
+ }
+
+ pm8xxx_vib_set(vib, vib->active);
+}
+
+/**
+ * pm8xxx_vib_close - callback of input close callback
+ * @dev: input device pointer
+ *
+ * Turns off the vibrator.
+ */
+static void pm8xxx_vib_close(struct input_dev *dev)
+{
+ struct pm8xxx_vib *vib = input_get_drvdata(dev);
+
+ cancel_work_sync(&vib->work);
+ if (vib->active)
+ pm8xxx_vib_set(vib, false);
+}
+
+/**
+ * pm8xxx_vib_play_effect - function to handle vib effects.
+ * @dev: input device pointer
+ * @data: data of effect
+ * @effect: effect to play
+ *
+ * Currently this driver supports only rumble effects.
+ */
+static int pm8xxx_vib_play_effect(struct input_dev *dev, void *data,
+ struct ff_effect *effect)
+{
+ struct pm8xxx_vib *vib = input_get_drvdata(dev);
+
+ vib->speed = effect->u.rumble.strong_magnitude >> 8;
+ if (!vib->speed)
+ vib->speed = effect->u.rumble.weak_magnitude >> 9;
+
+ schedule_work(&vib->work);
+
+ return 0;
+}
+
+static int pm8xxx_vib_probe(struct platform_device *pdev)
+{
+ struct pm8xxx_vib *vib;
+ struct input_dev *input_dev;
+ int error;
+ unsigned int val;
+ const struct pm8xxx_regs *regs;
+
+ vib = devm_kzalloc(&pdev->dev, sizeof(*vib), GFP_KERNEL);
+ if (!vib)
+ return -ENOMEM;
+
+ vib->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!vib->regmap)
+ return -ENODEV;
+
+ input_dev = devm_input_allocate_device(&pdev->dev);
+ if (!input_dev)
+ return -ENOMEM;
+
+ INIT_WORK(&vib->work, pm8xxx_work_handler);
+ vib->vib_input_dev = input_dev;
+
+ regs = of_device_get_match_data(&pdev->dev);
+
+ /* operate in manual mode */
+ error = regmap_read(vib->regmap, regs->drv_addr, &val);
+ if (error < 0)
+ return error;
+
+ val &= regs->drv_en_manual_mask;
+ error = regmap_write(vib->regmap, regs->drv_addr, val);
+ if (error < 0)
+ return error;
+
+ vib->regs = regs;
+ vib->reg_vib_drv = val;
+
+ input_dev->name = "pm8xxx_vib_ffmemless";
+ input_dev->id.version = 1;
+ input_dev->close = pm8xxx_vib_close;
+ input_set_drvdata(input_dev, vib);
+ input_set_capability(vib->vib_input_dev, EV_FF, FF_RUMBLE);
+
+ error = input_ff_create_memless(input_dev, NULL,
+ pm8xxx_vib_play_effect);
+ if (error) {
+ dev_err(&pdev->dev,
+ "couldn't register vibrator as FF device\n");
+ return error;
+ }
+
+ error = input_register_device(input_dev);
+ if (error) {
+ dev_err(&pdev->dev, "couldn't register input device\n");
+ return error;
+ }
+
+ platform_set_drvdata(pdev, vib);
+ return 0;
+}
+
+static int __maybe_unused pm8xxx_vib_suspend(struct device *dev)
+{
+ struct pm8xxx_vib *vib = dev_get_drvdata(dev);
+
+ /* Turn off the vibrator */
+ pm8xxx_vib_set(vib, false);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(pm8xxx_vib_pm_ops, pm8xxx_vib_suspend, NULL);
+
+static const struct of_device_id pm8xxx_vib_id_table[] = {
+ { .compatible = "qcom,pm8058-vib", .data = &pm8058_regs },
+ { .compatible = "qcom,pm8921-vib", .data = &pm8058_regs },
+ { .compatible = "qcom,pm8916-vib", .data = &pm8916_regs },
+ { }
+};
+MODULE_DEVICE_TABLE(of, pm8xxx_vib_id_table);
+
+static struct platform_driver pm8xxx_vib_driver = {
+ .probe = pm8xxx_vib_probe,
+ .driver = {
+ .name = "pm8xxx-vib",
+ .pm = &pm8xxx_vib_pm_ops,
+ .of_match_table = pm8xxx_vib_id_table,
+ },
+};
+module_platform_driver(pm8xxx_vib_driver);
+
+MODULE_ALIAS("platform:pm8xxx_vib");
+MODULE_DESCRIPTION("PMIC8xxx vibrator driver based on ff-memless framework");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Amy Maloche <amaloche@codeaurora.org>");
diff --git a/drivers/input/misc/pmic8xxx-pwrkey.c b/drivers/input/misc/pmic8xxx-pwrkey.c
new file mode 100644
index 000000000..0e818a3d2
--- /dev/null
+++ b/drivers/input/misc/pmic8xxx-pwrkey.c
@@ -0,0 +1,454 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/log2.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+
+#define PON_CNTL_1 0x1C
+#define PON_CNTL_PULL_UP BIT(7)
+#define PON_CNTL_TRIG_DELAY_MASK (0x7)
+#define PON_CNTL_1_PULL_UP_EN 0xe0
+#define PON_CNTL_1_USB_PWR_EN 0x10
+#define PON_CNTL_1_WD_EN_RESET 0x08
+
+#define PM8058_SLEEP_CTRL 0x02b
+#define PM8921_SLEEP_CTRL 0x10a
+
+#define SLEEP_CTRL_SMPL_EN_RESET 0x04
+
+/* Regulator master enable addresses */
+#define REG_PM8058_VREG_EN_MSM 0x018
+#define REG_PM8058_VREG_EN_GRP_5_4 0x1c8
+
+/* Regulator control registers for shutdown/reset */
+#define PM8058_S0_CTRL 0x004
+#define PM8058_S1_CTRL 0x005
+#define PM8058_S3_CTRL 0x111
+#define PM8058_L21_CTRL 0x120
+#define PM8058_L22_CTRL 0x121
+
+#define PM8058_REGULATOR_ENABLE_MASK 0x80
+#define PM8058_REGULATOR_ENABLE 0x80
+#define PM8058_REGULATOR_DISABLE 0x00
+#define PM8058_REGULATOR_PULL_DOWN_MASK 0x40
+#define PM8058_REGULATOR_PULL_DOWN_EN 0x40
+
+/* Buck CTRL register */
+#define PM8058_SMPS_LEGACY_VREF_SEL 0x20
+#define PM8058_SMPS_LEGACY_VPROG_MASK 0x1f
+#define PM8058_SMPS_ADVANCED_BAND_MASK 0xC0
+#define PM8058_SMPS_ADVANCED_BAND_SHIFT 6
+#define PM8058_SMPS_ADVANCED_VPROG_MASK 0x3f
+
+/* Buck TEST2 registers for shutdown/reset */
+#define PM8058_S0_TEST2 0x084
+#define PM8058_S1_TEST2 0x085
+#define PM8058_S3_TEST2 0x11a
+
+#define PM8058_REGULATOR_BANK_WRITE 0x80
+#define PM8058_REGULATOR_BANK_MASK 0x70
+#define PM8058_REGULATOR_BANK_SHIFT 4
+#define PM8058_REGULATOR_BANK_SEL(n) ((n) << PM8058_REGULATOR_BANK_SHIFT)
+
+/* Buck TEST2 register bank 1 */
+#define PM8058_SMPS_LEGACY_VLOW_SEL 0x01
+
+/* Buck TEST2 register bank 7 */
+#define PM8058_SMPS_ADVANCED_MODE_MASK 0x02
+#define PM8058_SMPS_ADVANCED_MODE 0x02
+#define PM8058_SMPS_LEGACY_MODE 0x00
+
+/**
+ * struct pmic8xxx_pwrkey - pmic8xxx pwrkey information
+ * @key_press_irq: key press irq number
+ * @regmap: device regmap
+ * @shutdown_fn: shutdown configuration function
+ */
+struct pmic8xxx_pwrkey {
+ int key_press_irq;
+ struct regmap *regmap;
+ int (*shutdown_fn)(struct pmic8xxx_pwrkey *, bool);
+};
+
+static irqreturn_t pwrkey_press_irq(int irq, void *_pwr)
+{
+ struct input_dev *pwr = _pwr;
+
+ input_report_key(pwr, KEY_POWER, 1);
+ input_sync(pwr);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t pwrkey_release_irq(int irq, void *_pwr)
+{
+ struct input_dev *pwr = _pwr;
+
+ input_report_key(pwr, KEY_POWER, 0);
+ input_sync(pwr);
+
+ return IRQ_HANDLED;
+}
+
+static int __maybe_unused pmic8xxx_pwrkey_suspend(struct device *dev)
+{
+ struct pmic8xxx_pwrkey *pwrkey = dev_get_drvdata(dev);
+
+ if (device_may_wakeup(dev))
+ enable_irq_wake(pwrkey->key_press_irq);
+
+ return 0;
+}
+
+static int __maybe_unused pmic8xxx_pwrkey_resume(struct device *dev)
+{
+ struct pmic8xxx_pwrkey *pwrkey = dev_get_drvdata(dev);
+
+ if (device_may_wakeup(dev))
+ disable_irq_wake(pwrkey->key_press_irq);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(pm8xxx_pwr_key_pm_ops,
+ pmic8xxx_pwrkey_suspend, pmic8xxx_pwrkey_resume);
+
+static void pmic8xxx_pwrkey_shutdown(struct platform_device *pdev)
+{
+ struct pmic8xxx_pwrkey *pwrkey = platform_get_drvdata(pdev);
+ int error;
+ u8 mask, val;
+ bool reset = system_state == SYSTEM_RESTART;
+
+ if (pwrkey->shutdown_fn) {
+ error = pwrkey->shutdown_fn(pwrkey, reset);
+ if (error)
+ return;
+ }
+
+ /*
+ * Select action to perform (reset or shutdown) when PS_HOLD goes low.
+ * Also ensure that KPD, CBL0, and CBL1 pull ups are enabled and that
+ * USB charging is enabled.
+ */
+ mask = PON_CNTL_1_PULL_UP_EN | PON_CNTL_1_USB_PWR_EN;
+ mask |= PON_CNTL_1_WD_EN_RESET;
+ val = mask;
+ if (!reset)
+ val &= ~PON_CNTL_1_WD_EN_RESET;
+
+ regmap_update_bits(pwrkey->regmap, PON_CNTL_1, mask, val);
+}
+
+/*
+ * Set an SMPS regulator to be disabled in its CTRL register, but enabled
+ * in the master enable register. Also set it's pull down enable bit.
+ * Take care to make sure that the output voltage doesn't change if switching
+ * from advanced mode to legacy mode.
+ */
+static int pm8058_disable_smps_locally_set_pull_down(struct regmap *regmap,
+ u16 ctrl_addr, u16 test2_addr, u16 master_enable_addr,
+ u8 master_enable_bit)
+{
+ int error;
+ u8 vref_sel, vlow_sel, band, vprog, bank;
+ unsigned int reg;
+
+ bank = PM8058_REGULATOR_BANK_SEL(7);
+ error = regmap_write(regmap, test2_addr, bank);
+ if (error)
+ return error;
+
+ error = regmap_read(regmap, test2_addr, &reg);
+ if (error)
+ return error;
+
+ reg &= PM8058_SMPS_ADVANCED_MODE_MASK;
+ /* Check if in advanced mode. */
+ if (reg == PM8058_SMPS_ADVANCED_MODE) {
+ /* Determine current output voltage. */
+ error = regmap_read(regmap, ctrl_addr, &reg);
+ if (error)
+ return error;
+
+ band = reg & PM8058_SMPS_ADVANCED_BAND_MASK;
+ band >>= PM8058_SMPS_ADVANCED_BAND_SHIFT;
+ switch (band) {
+ case 3:
+ vref_sel = 0;
+ vlow_sel = 0;
+ break;
+ case 2:
+ vref_sel = PM8058_SMPS_LEGACY_VREF_SEL;
+ vlow_sel = 0;
+ break;
+ case 1:
+ vref_sel = PM8058_SMPS_LEGACY_VREF_SEL;
+ vlow_sel = PM8058_SMPS_LEGACY_VLOW_SEL;
+ break;
+ default:
+ pr_err("%s: regulator already disabled\n", __func__);
+ return -EPERM;
+ }
+ vprog = reg & PM8058_SMPS_ADVANCED_VPROG_MASK;
+ /* Round up if fine step is in use. */
+ vprog = (vprog + 1) >> 1;
+ if (vprog > PM8058_SMPS_LEGACY_VPROG_MASK)
+ vprog = PM8058_SMPS_LEGACY_VPROG_MASK;
+
+ /* Set VLOW_SEL bit. */
+ bank = PM8058_REGULATOR_BANK_SEL(1);
+ error = regmap_write(regmap, test2_addr, bank);
+ if (error)
+ return error;
+
+ error = regmap_update_bits(regmap, test2_addr,
+ PM8058_REGULATOR_BANK_WRITE | PM8058_REGULATOR_BANK_MASK
+ | PM8058_SMPS_LEGACY_VLOW_SEL,
+ PM8058_REGULATOR_BANK_WRITE |
+ PM8058_REGULATOR_BANK_SEL(1) | vlow_sel);
+ if (error)
+ return error;
+
+ /* Switch to legacy mode */
+ bank = PM8058_REGULATOR_BANK_SEL(7);
+ error = regmap_write(regmap, test2_addr, bank);
+ if (error)
+ return error;
+
+ error = regmap_update_bits(regmap, test2_addr,
+ PM8058_REGULATOR_BANK_WRITE |
+ PM8058_REGULATOR_BANK_MASK |
+ PM8058_SMPS_ADVANCED_MODE_MASK,
+ PM8058_REGULATOR_BANK_WRITE |
+ PM8058_REGULATOR_BANK_SEL(7) |
+ PM8058_SMPS_LEGACY_MODE);
+ if (error)
+ return error;
+
+ /* Enable locally, enable pull down, keep voltage the same. */
+ error = regmap_update_bits(regmap, ctrl_addr,
+ PM8058_REGULATOR_ENABLE_MASK |
+ PM8058_REGULATOR_PULL_DOWN_MASK |
+ PM8058_SMPS_LEGACY_VREF_SEL |
+ PM8058_SMPS_LEGACY_VPROG_MASK,
+ PM8058_REGULATOR_ENABLE | PM8058_REGULATOR_PULL_DOWN_EN
+ | vref_sel | vprog);
+ if (error)
+ return error;
+ }
+
+ /* Enable in master control register. */
+ error = regmap_update_bits(regmap, master_enable_addr,
+ master_enable_bit, master_enable_bit);
+ if (error)
+ return error;
+
+ /* Disable locally and enable pull down. */
+ return regmap_update_bits(regmap, ctrl_addr,
+ PM8058_REGULATOR_ENABLE_MASK | PM8058_REGULATOR_PULL_DOWN_MASK,
+ PM8058_REGULATOR_DISABLE | PM8058_REGULATOR_PULL_DOWN_EN);
+}
+
+static int pm8058_disable_ldo_locally_set_pull_down(struct regmap *regmap,
+ u16 ctrl_addr, u16 master_enable_addr, u8 master_enable_bit)
+{
+ int error;
+
+ /* Enable LDO in master control register. */
+ error = regmap_update_bits(regmap, master_enable_addr,
+ master_enable_bit, master_enable_bit);
+ if (error)
+ return error;
+
+ /* Disable LDO in CTRL register and set pull down */
+ return regmap_update_bits(regmap, ctrl_addr,
+ PM8058_REGULATOR_ENABLE_MASK | PM8058_REGULATOR_PULL_DOWN_MASK,
+ PM8058_REGULATOR_DISABLE | PM8058_REGULATOR_PULL_DOWN_EN);
+}
+
+static int pm8058_pwrkey_shutdown(struct pmic8xxx_pwrkey *pwrkey, bool reset)
+{
+ int error;
+ struct regmap *regmap = pwrkey->regmap;
+ u8 mask, val;
+
+ /* When shutting down, enable active pulldowns on important rails. */
+ if (!reset) {
+ /* Disable SMPS's 0,1,3 locally and set pulldown enable bits. */
+ pm8058_disable_smps_locally_set_pull_down(regmap,
+ PM8058_S0_CTRL, PM8058_S0_TEST2,
+ REG_PM8058_VREG_EN_MSM, BIT(7));
+ pm8058_disable_smps_locally_set_pull_down(regmap,
+ PM8058_S1_CTRL, PM8058_S1_TEST2,
+ REG_PM8058_VREG_EN_MSM, BIT(6));
+ pm8058_disable_smps_locally_set_pull_down(regmap,
+ PM8058_S3_CTRL, PM8058_S3_TEST2,
+ REG_PM8058_VREG_EN_GRP_5_4, BIT(7) | BIT(4));
+ /* Disable LDO 21 locally and set pulldown enable bit. */
+ pm8058_disable_ldo_locally_set_pull_down(regmap,
+ PM8058_L21_CTRL, REG_PM8058_VREG_EN_GRP_5_4,
+ BIT(1));
+ }
+
+ /*
+ * Fix-up: Set regulator LDO22 to 1.225 V in high power mode. Leave its
+ * pull-down state intact. This ensures a safe shutdown.
+ */
+ error = regmap_update_bits(regmap, PM8058_L22_CTRL, 0xbf, 0x93);
+ if (error)
+ return error;
+
+ /* Enable SMPL if resetting is desired */
+ mask = SLEEP_CTRL_SMPL_EN_RESET;
+ val = 0;
+ if (reset)
+ val = mask;
+ return regmap_update_bits(regmap, PM8058_SLEEP_CTRL, mask, val);
+}
+
+static int pm8921_pwrkey_shutdown(struct pmic8xxx_pwrkey *pwrkey, bool reset)
+{
+ struct regmap *regmap = pwrkey->regmap;
+ u8 mask = SLEEP_CTRL_SMPL_EN_RESET;
+ u8 val = 0;
+
+ /* Enable SMPL if resetting is desired */
+ if (reset)
+ val = mask;
+ return regmap_update_bits(regmap, PM8921_SLEEP_CTRL, mask, val);
+}
+
+static int pmic8xxx_pwrkey_probe(struct platform_device *pdev)
+{
+ struct input_dev *pwr;
+ int key_release_irq = platform_get_irq(pdev, 0);
+ int key_press_irq = platform_get_irq(pdev, 1);
+ int err;
+ unsigned int delay;
+ unsigned int pon_cntl;
+ struct regmap *regmap;
+ struct pmic8xxx_pwrkey *pwrkey;
+ u32 kpd_delay;
+ bool pull_up;
+
+ if (of_property_read_u32(pdev->dev.of_node, "debounce", &kpd_delay))
+ kpd_delay = 15625;
+
+ /* Valid range of pwr key trigger delay is 1/64 sec to 2 seconds. */
+ if (kpd_delay > USEC_PER_SEC * 2 || kpd_delay < USEC_PER_SEC / 64) {
+ dev_err(&pdev->dev, "invalid power key trigger delay\n");
+ return -EINVAL;
+ }
+
+ pull_up = of_property_read_bool(pdev->dev.of_node, "pull-up");
+
+ regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!regmap) {
+ dev_err(&pdev->dev, "failed to locate regmap for the device\n");
+ return -ENODEV;
+ }
+
+ pwrkey = devm_kzalloc(&pdev->dev, sizeof(*pwrkey), GFP_KERNEL);
+ if (!pwrkey)
+ return -ENOMEM;
+
+ pwrkey->shutdown_fn = of_device_get_match_data(&pdev->dev);
+ pwrkey->regmap = regmap;
+ pwrkey->key_press_irq = key_press_irq;
+
+ pwr = devm_input_allocate_device(&pdev->dev);
+ if (!pwr) {
+ dev_dbg(&pdev->dev, "Can't allocate power button\n");
+ return -ENOMEM;
+ }
+
+ input_set_capability(pwr, EV_KEY, KEY_POWER);
+
+ pwr->name = "pmic8xxx_pwrkey";
+ pwr->phys = "pmic8xxx_pwrkey/input0";
+
+ delay = (kpd_delay << 6) / USEC_PER_SEC;
+ delay = ilog2(delay);
+
+ err = regmap_read(regmap, PON_CNTL_1, &pon_cntl);
+ if (err < 0) {
+ dev_err(&pdev->dev, "failed reading PON_CNTL_1 err=%d\n", err);
+ return err;
+ }
+
+ pon_cntl &= ~PON_CNTL_TRIG_DELAY_MASK;
+ pon_cntl |= (delay & PON_CNTL_TRIG_DELAY_MASK);
+ if (pull_up)
+ pon_cntl |= PON_CNTL_PULL_UP;
+ else
+ pon_cntl &= ~PON_CNTL_PULL_UP;
+
+ err = regmap_write(regmap, PON_CNTL_1, pon_cntl);
+ if (err < 0) {
+ dev_err(&pdev->dev, "failed writing PON_CNTL_1 err=%d\n", err);
+ return err;
+ }
+
+ err = devm_request_irq(&pdev->dev, key_press_irq, pwrkey_press_irq,
+ IRQF_TRIGGER_RISING,
+ "pmic8xxx_pwrkey_press", pwr);
+ if (err) {
+ dev_err(&pdev->dev, "Can't get %d IRQ for pwrkey: %d\n",
+ key_press_irq, err);
+ return err;
+ }
+
+ err = devm_request_irq(&pdev->dev, key_release_irq, pwrkey_release_irq,
+ IRQF_TRIGGER_RISING,
+ "pmic8xxx_pwrkey_release", pwr);
+ if (err) {
+ dev_err(&pdev->dev, "Can't get %d IRQ for pwrkey: %d\n",
+ key_release_irq, err);
+ return err;
+ }
+
+ err = input_register_device(pwr);
+ if (err) {
+ dev_err(&pdev->dev, "Can't register power key: %d\n", err);
+ return err;
+ }
+
+ platform_set_drvdata(pdev, pwrkey);
+ device_init_wakeup(&pdev->dev, 1);
+
+ return 0;
+}
+
+static const struct of_device_id pm8xxx_pwr_key_id_table[] = {
+ { .compatible = "qcom,pm8058-pwrkey", .data = &pm8058_pwrkey_shutdown },
+ { .compatible = "qcom,pm8921-pwrkey", .data = &pm8921_pwrkey_shutdown },
+ { }
+};
+MODULE_DEVICE_TABLE(of, pm8xxx_pwr_key_id_table);
+
+static struct platform_driver pmic8xxx_pwrkey_driver = {
+ .probe = pmic8xxx_pwrkey_probe,
+ .shutdown = pmic8xxx_pwrkey_shutdown,
+ .driver = {
+ .name = "pm8xxx-pwrkey",
+ .pm = &pm8xxx_pwr_key_pm_ops,
+ .of_match_table = pm8xxx_pwr_key_id_table,
+ },
+};
+module_platform_driver(pmic8xxx_pwrkey_driver);
+
+MODULE_ALIAS("platform:pmic8xxx_pwrkey");
+MODULE_DESCRIPTION("PMIC8XXX Power Key driver");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Trilok Soni <tsoni@codeaurora.org>");
diff --git a/drivers/input/misc/powermate.c b/drivers/input/misc/powermate.c
new file mode 100644
index 000000000..db2ba89ad
--- /dev/null
+++ b/drivers/input/misc/powermate.c
@@ -0,0 +1,457 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * A driver for the Griffin Technology, Inc. "PowerMate" USB controller dial.
+ *
+ * v1.1, (c)2002 William R Sowerbutts <will@sowerbutts.com>
+ *
+ * This device is a anodised aluminium knob which connects over USB. It can measure
+ * clockwise and anticlockwise rotation. The dial also acts as a pushbutton with
+ * a spring for automatic release. The base contains a pair of LEDs which illuminate
+ * the translucent base. It rotates without limit and reports its relative rotation
+ * back to the host when polled by the USB controller.
+ *
+ * Testing with the knob I have has shown that it measures approximately 94 "clicks"
+ * for one full rotation. Testing with my High Speed Rotation Actuator (ok, it was
+ * a variable speed cordless electric drill) has shown that the device can measure
+ * speeds of up to 7 clicks either clockwise or anticlockwise between pollings from
+ * the host. If it counts more than 7 clicks before it is polled, it will wrap back
+ * to zero and start counting again. This was at quite high speed, however, almost
+ * certainly faster than the human hand could turn it. Griffin say that it loses a
+ * pulse or two on a direction change; the granularity is so fine that I never
+ * noticed this in practice.
+ *
+ * The device's microcontroller can be programmed to set the LED to either a constant
+ * intensity, or to a rhythmic pulsing. Several patterns and speeds are available.
+ *
+ * Griffin were very happy to provide documentation and free hardware for development.
+ *
+ * Some userspace tools are available on the web: http://sowerbutts.com/powermate/
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/usb/input.h>
+
+#define POWERMATE_VENDOR 0x077d /* Griffin Technology, Inc. */
+#define POWERMATE_PRODUCT_NEW 0x0410 /* Griffin PowerMate */
+#define POWERMATE_PRODUCT_OLD 0x04AA /* Griffin soundKnob */
+
+#define CONTOUR_VENDOR 0x05f3 /* Contour Design, Inc. */
+#define CONTOUR_JOG 0x0240 /* Jog and Shuttle */
+
+/* these are the command codes we send to the device */
+#define SET_STATIC_BRIGHTNESS 0x01
+#define SET_PULSE_ASLEEP 0x02
+#define SET_PULSE_AWAKE 0x03
+#define SET_PULSE_MODE 0x04
+
+/* these refer to bits in the powermate_device's requires_update field. */
+#define UPDATE_STATIC_BRIGHTNESS (1<<0)
+#define UPDATE_PULSE_ASLEEP (1<<1)
+#define UPDATE_PULSE_AWAKE (1<<2)
+#define UPDATE_PULSE_MODE (1<<3)
+
+/* at least two versions of the hardware exist, with differing payload
+ sizes. the first three bytes always contain the "interesting" data in
+ the relevant format. */
+#define POWERMATE_PAYLOAD_SIZE_MAX 6
+#define POWERMATE_PAYLOAD_SIZE_MIN 3
+struct powermate_device {
+ signed char *data;
+ dma_addr_t data_dma;
+ struct urb *irq, *config;
+ struct usb_ctrlrequest *configcr;
+ struct usb_device *udev;
+ struct usb_interface *intf;
+ struct input_dev *input;
+ spinlock_t lock;
+ int static_brightness;
+ int pulse_speed;
+ int pulse_table;
+ int pulse_asleep;
+ int pulse_awake;
+ int requires_update; // physical settings which are out of sync
+ char phys[64];
+};
+
+static char pm_name_powermate[] = "Griffin PowerMate";
+static char pm_name_soundknob[] = "Griffin SoundKnob";
+
+static void powermate_config_complete(struct urb *urb);
+
+/* Callback for data arriving from the PowerMate over the USB interrupt pipe */
+static void powermate_irq(struct urb *urb)
+{
+ struct powermate_device *pm = urb->context;
+ struct device *dev = &pm->intf->dev;
+ int retval;
+
+ switch (urb->status) {
+ case 0:
+ /* success */
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* this urb is terminated, clean up */
+ dev_dbg(dev, "%s - urb shutting down with status: %d\n",
+ __func__, urb->status);
+ return;
+ default:
+ dev_dbg(dev, "%s - nonzero urb status received: %d\n",
+ __func__, urb->status);
+ goto exit;
+ }
+
+ /* handle updates to device state */
+ input_report_key(pm->input, BTN_0, pm->data[0] & 0x01);
+ input_report_rel(pm->input, REL_DIAL, pm->data[1]);
+ input_sync(pm->input);
+
+exit:
+ retval = usb_submit_urb (urb, GFP_ATOMIC);
+ if (retval)
+ dev_err(dev, "%s - usb_submit_urb failed with result: %d\n",
+ __func__, retval);
+}
+
+/* Decide if we need to issue a control message and do so. Must be called with pm->lock taken */
+static void powermate_sync_state(struct powermate_device *pm)
+{
+ if (pm->requires_update == 0)
+ return; /* no updates are required */
+ if (pm->config->status == -EINPROGRESS)
+ return; /* an update is already in progress; it'll issue this update when it completes */
+
+ if (pm->requires_update & UPDATE_PULSE_ASLEEP){
+ pm->configcr->wValue = cpu_to_le16( SET_PULSE_ASLEEP );
+ pm->configcr->wIndex = cpu_to_le16( pm->pulse_asleep ? 1 : 0 );
+ pm->requires_update &= ~UPDATE_PULSE_ASLEEP;
+ }else if (pm->requires_update & UPDATE_PULSE_AWAKE){
+ pm->configcr->wValue = cpu_to_le16( SET_PULSE_AWAKE );
+ pm->configcr->wIndex = cpu_to_le16( pm->pulse_awake ? 1 : 0 );
+ pm->requires_update &= ~UPDATE_PULSE_AWAKE;
+ }else if (pm->requires_update & UPDATE_PULSE_MODE){
+ int op, arg;
+ /* the powermate takes an operation and an argument for its pulse algorithm.
+ the operation can be:
+ 0: divide the speed
+ 1: pulse at normal speed
+ 2: multiply the speed
+ the argument only has an effect for operations 0 and 2, and ranges between
+ 1 (least effect) to 255 (maximum effect).
+
+ thus, several states are equivalent and are coalesced into one state.
+
+ we map this onto a range from 0 to 510, with:
+ 0 -- 254 -- use divide (0 = slowest)
+ 255 -- use normal speed
+ 256 -- 510 -- use multiple (510 = fastest).
+
+ Only values of 'arg' quite close to 255 are particularly useful/spectacular.
+ */
+ if (pm->pulse_speed < 255) {
+ op = 0; // divide
+ arg = 255 - pm->pulse_speed;
+ } else if (pm->pulse_speed > 255) {
+ op = 2; // multiply
+ arg = pm->pulse_speed - 255;
+ } else {
+ op = 1; // normal speed
+ arg = 0; // can be any value
+ }
+ pm->configcr->wValue = cpu_to_le16( (pm->pulse_table << 8) | SET_PULSE_MODE );
+ pm->configcr->wIndex = cpu_to_le16( (arg << 8) | op );
+ pm->requires_update &= ~UPDATE_PULSE_MODE;
+ } else if (pm->requires_update & UPDATE_STATIC_BRIGHTNESS) {
+ pm->configcr->wValue = cpu_to_le16( SET_STATIC_BRIGHTNESS );
+ pm->configcr->wIndex = cpu_to_le16( pm->static_brightness );
+ pm->requires_update &= ~UPDATE_STATIC_BRIGHTNESS;
+ } else {
+ printk(KERN_ERR "powermate: unknown update required");
+ pm->requires_update = 0; /* fudge the bug */
+ return;
+ }
+
+/* printk("powermate: %04x %04x\n", pm->configcr->wValue, pm->configcr->wIndex); */
+
+ pm->configcr->bRequestType = 0x41; /* vendor request */
+ pm->configcr->bRequest = 0x01;
+ pm->configcr->wLength = 0;
+
+ usb_fill_control_urb(pm->config, pm->udev, usb_sndctrlpipe(pm->udev, 0),
+ (void *) pm->configcr, NULL, 0,
+ powermate_config_complete, pm);
+
+ if (usb_submit_urb(pm->config, GFP_ATOMIC))
+ printk(KERN_ERR "powermate: usb_submit_urb(config) failed");
+}
+
+/* Called when our asynchronous control message completes. We may need to issue another immediately */
+static void powermate_config_complete(struct urb *urb)
+{
+ struct powermate_device *pm = urb->context;
+ unsigned long flags;
+
+ if (urb->status)
+ printk(KERN_ERR "powermate: config urb returned %d\n", urb->status);
+
+ spin_lock_irqsave(&pm->lock, flags);
+ powermate_sync_state(pm);
+ spin_unlock_irqrestore(&pm->lock, flags);
+}
+
+/* Set the LED up as described and begin the sync with the hardware if required */
+static void powermate_pulse_led(struct powermate_device *pm, int static_brightness, int pulse_speed,
+ int pulse_table, int pulse_asleep, int pulse_awake)
+{
+ unsigned long flags;
+
+ if (pulse_speed < 0)
+ pulse_speed = 0;
+ if (pulse_table < 0)
+ pulse_table = 0;
+ if (pulse_speed > 510)
+ pulse_speed = 510;
+ if (pulse_table > 2)
+ pulse_table = 2;
+
+ pulse_asleep = !!pulse_asleep;
+ pulse_awake = !!pulse_awake;
+
+
+ spin_lock_irqsave(&pm->lock, flags);
+
+ /* mark state updates which are required */
+ if (static_brightness != pm->static_brightness) {
+ pm->static_brightness = static_brightness;
+ pm->requires_update |= UPDATE_STATIC_BRIGHTNESS;
+ }
+ if (pulse_asleep != pm->pulse_asleep) {
+ pm->pulse_asleep = pulse_asleep;
+ pm->requires_update |= (UPDATE_PULSE_ASLEEP | UPDATE_STATIC_BRIGHTNESS);
+ }
+ if (pulse_awake != pm->pulse_awake) {
+ pm->pulse_awake = pulse_awake;
+ pm->requires_update |= (UPDATE_PULSE_AWAKE | UPDATE_STATIC_BRIGHTNESS);
+ }
+ if (pulse_speed != pm->pulse_speed || pulse_table != pm->pulse_table) {
+ pm->pulse_speed = pulse_speed;
+ pm->pulse_table = pulse_table;
+ pm->requires_update |= UPDATE_PULSE_MODE;
+ }
+
+ powermate_sync_state(pm);
+
+ spin_unlock_irqrestore(&pm->lock, flags);
+}
+
+/* Callback from the Input layer when an event arrives from userspace to configure the LED */
+static int powermate_input_event(struct input_dev *dev, unsigned int type, unsigned int code, int _value)
+{
+ unsigned int command = (unsigned int)_value;
+ struct powermate_device *pm = input_get_drvdata(dev);
+
+ if (type == EV_MSC && code == MSC_PULSELED){
+ /*
+ bits 0- 7: 8 bits: LED brightness
+ bits 8-16: 9 bits: pulsing speed modifier (0 ... 510); 0-254 = slower, 255 = standard, 256-510 = faster.
+ bits 17-18: 2 bits: pulse table (0, 1, 2 valid)
+ bit 19: 1 bit : pulse whilst asleep?
+ bit 20: 1 bit : pulse constantly?
+ */
+ int static_brightness = command & 0xFF; // bits 0-7
+ int pulse_speed = (command >> 8) & 0x1FF; // bits 8-16
+ int pulse_table = (command >> 17) & 0x3; // bits 17-18
+ int pulse_asleep = (command >> 19) & 0x1; // bit 19
+ int pulse_awake = (command >> 20) & 0x1; // bit 20
+
+ powermate_pulse_led(pm, static_brightness, pulse_speed, pulse_table, pulse_asleep, pulse_awake);
+ }
+
+ return 0;
+}
+
+static int powermate_alloc_buffers(struct usb_device *udev, struct powermate_device *pm)
+{
+ pm->data = usb_alloc_coherent(udev, POWERMATE_PAYLOAD_SIZE_MAX,
+ GFP_KERNEL, &pm->data_dma);
+ if (!pm->data)
+ return -1;
+
+ pm->configcr = kmalloc(sizeof(*(pm->configcr)), GFP_KERNEL);
+ if (!pm->configcr)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static void powermate_free_buffers(struct usb_device *udev, struct powermate_device *pm)
+{
+ usb_free_coherent(udev, POWERMATE_PAYLOAD_SIZE_MAX,
+ pm->data, pm->data_dma);
+ kfree(pm->configcr);
+}
+
+/* Called whenever a USB device matching one in our supported devices table is connected */
+static int powermate_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev (intf);
+ struct usb_host_interface *interface;
+ struct usb_endpoint_descriptor *endpoint;
+ struct powermate_device *pm;
+ struct input_dev *input_dev;
+ int pipe, maxp;
+ int error = -ENOMEM;
+
+ interface = intf->cur_altsetting;
+ if (interface->desc.bNumEndpoints < 1)
+ return -EINVAL;
+
+ endpoint = &interface->endpoint[0].desc;
+ if (!usb_endpoint_is_int_in(endpoint))
+ return -EIO;
+
+ usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ 0x0a, USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0, interface->desc.bInterfaceNumber, NULL, 0,
+ USB_CTRL_SET_TIMEOUT);
+
+ pm = kzalloc(sizeof(struct powermate_device), GFP_KERNEL);
+ input_dev = input_allocate_device();
+ if (!pm || !input_dev)
+ goto fail1;
+
+ if (powermate_alloc_buffers(udev, pm))
+ goto fail2;
+
+ pm->irq = usb_alloc_urb(0, GFP_KERNEL);
+ if (!pm->irq)
+ goto fail2;
+
+ pm->config = usb_alloc_urb(0, GFP_KERNEL);
+ if (!pm->config)
+ goto fail3;
+
+ pm->udev = udev;
+ pm->intf = intf;
+ pm->input = input_dev;
+
+ usb_make_path(udev, pm->phys, sizeof(pm->phys));
+ strlcat(pm->phys, "/input0", sizeof(pm->phys));
+
+ spin_lock_init(&pm->lock);
+
+ switch (le16_to_cpu(udev->descriptor.idProduct)) {
+ case POWERMATE_PRODUCT_NEW:
+ input_dev->name = pm_name_powermate;
+ break;
+ case POWERMATE_PRODUCT_OLD:
+ input_dev->name = pm_name_soundknob;
+ break;
+ default:
+ input_dev->name = pm_name_soundknob;
+ printk(KERN_WARNING "powermate: unknown product id %04x\n",
+ le16_to_cpu(udev->descriptor.idProduct));
+ }
+
+ input_dev->phys = pm->phys;
+ usb_to_input_id(udev, &input_dev->id);
+ input_dev->dev.parent = &intf->dev;
+
+ input_set_drvdata(input_dev, pm);
+
+ input_dev->event = powermate_input_event;
+
+ input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL) |
+ BIT_MASK(EV_MSC);
+ input_dev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0);
+ input_dev->relbit[BIT_WORD(REL_DIAL)] = BIT_MASK(REL_DIAL);
+ input_dev->mscbit[BIT_WORD(MSC_PULSELED)] = BIT_MASK(MSC_PULSELED);
+
+ /* get a handle to the interrupt data pipe */
+ pipe = usb_rcvintpipe(udev, endpoint->bEndpointAddress);
+ maxp = usb_maxpacket(udev, pipe);
+
+ if (maxp < POWERMATE_PAYLOAD_SIZE_MIN || maxp > POWERMATE_PAYLOAD_SIZE_MAX) {
+ printk(KERN_WARNING "powermate: Expected payload of %d--%d bytes, found %d bytes!\n",
+ POWERMATE_PAYLOAD_SIZE_MIN, POWERMATE_PAYLOAD_SIZE_MAX, maxp);
+ maxp = POWERMATE_PAYLOAD_SIZE_MAX;
+ }
+
+ usb_fill_int_urb(pm->irq, udev, pipe, pm->data,
+ maxp, powermate_irq,
+ pm, endpoint->bInterval);
+ pm->irq->transfer_dma = pm->data_dma;
+ pm->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+ /* register our interrupt URB with the USB system */
+ if (usb_submit_urb(pm->irq, GFP_KERNEL)) {
+ error = -EIO;
+ goto fail4;
+ }
+
+ error = input_register_device(pm->input);
+ if (error)
+ goto fail5;
+
+
+ /* force an update of everything */
+ pm->requires_update = UPDATE_PULSE_ASLEEP | UPDATE_PULSE_AWAKE | UPDATE_PULSE_MODE | UPDATE_STATIC_BRIGHTNESS;
+ powermate_pulse_led(pm, 0x80, 255, 0, 1, 0); // set default pulse parameters
+
+ usb_set_intfdata(intf, pm);
+ return 0;
+
+ fail5: usb_kill_urb(pm->irq);
+ fail4: usb_free_urb(pm->config);
+ fail3: usb_free_urb(pm->irq);
+ fail2: powermate_free_buffers(udev, pm);
+ fail1: input_free_device(input_dev);
+ kfree(pm);
+ return error;
+}
+
+/* Called when a USB device we've accepted ownership of is removed */
+static void powermate_disconnect(struct usb_interface *intf)
+{
+ struct powermate_device *pm = usb_get_intfdata (intf);
+
+ usb_set_intfdata(intf, NULL);
+ if (pm) {
+ pm->requires_update = 0;
+ usb_kill_urb(pm->irq);
+ input_unregister_device(pm->input);
+ usb_kill_urb(pm->config);
+ usb_free_urb(pm->irq);
+ usb_free_urb(pm->config);
+ powermate_free_buffers(interface_to_usbdev(intf), pm);
+
+ kfree(pm);
+ }
+}
+
+static const struct usb_device_id powermate_devices[] = {
+ { USB_DEVICE(POWERMATE_VENDOR, POWERMATE_PRODUCT_NEW) },
+ { USB_DEVICE(POWERMATE_VENDOR, POWERMATE_PRODUCT_OLD) },
+ { USB_DEVICE(CONTOUR_VENDOR, CONTOUR_JOG) },
+ { } /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE (usb, powermate_devices);
+
+static struct usb_driver powermate_driver = {
+ .name = "powermate",
+ .probe = powermate_probe,
+ .disconnect = powermate_disconnect,
+ .id_table = powermate_devices,
+};
+
+module_usb_driver(powermate_driver);
+
+MODULE_AUTHOR( "William R Sowerbutts" );
+MODULE_DESCRIPTION( "Griffin Technology, Inc PowerMate driver" );
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/pwm-beeper.c b/drivers/input/misc/pwm-beeper.c
new file mode 100644
index 000000000..d6b124777
--- /dev/null
+++ b/drivers/input/misc/pwm-beeper.c
@@ -0,0 +1,262 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de>
+ * PWM beeper driver
+ */
+
+#include <linux/input.h>
+#include <linux/regulator/consumer.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/pwm.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+struct pwm_beeper {
+ struct input_dev *input;
+ struct pwm_device *pwm;
+ struct regulator *amplifier;
+ struct work_struct work;
+ unsigned long period;
+ unsigned int bell_frequency;
+ bool suspended;
+ bool amplifier_on;
+};
+
+#define HZ_TO_NANOSECONDS(x) (1000000000UL/(x))
+
+static int pwm_beeper_on(struct pwm_beeper *beeper, unsigned long period)
+{
+ struct pwm_state state;
+ int error;
+
+ pwm_get_state(beeper->pwm, &state);
+
+ state.enabled = true;
+ state.period = period;
+ pwm_set_relative_duty_cycle(&state, 50, 100);
+
+ error = pwm_apply_state(beeper->pwm, &state);
+ if (error)
+ return error;
+
+ if (!beeper->amplifier_on) {
+ error = regulator_enable(beeper->amplifier);
+ if (error) {
+ pwm_disable(beeper->pwm);
+ return error;
+ }
+
+ beeper->amplifier_on = true;
+ }
+
+ return 0;
+}
+
+static void pwm_beeper_off(struct pwm_beeper *beeper)
+{
+ if (beeper->amplifier_on) {
+ regulator_disable(beeper->amplifier);
+ beeper->amplifier_on = false;
+ }
+
+ pwm_disable(beeper->pwm);
+}
+
+static void pwm_beeper_work(struct work_struct *work)
+{
+ struct pwm_beeper *beeper = container_of(work, struct pwm_beeper, work);
+ unsigned long period = READ_ONCE(beeper->period);
+
+ if (period)
+ pwm_beeper_on(beeper, period);
+ else
+ pwm_beeper_off(beeper);
+}
+
+static int pwm_beeper_event(struct input_dev *input,
+ unsigned int type, unsigned int code, int value)
+{
+ struct pwm_beeper *beeper = input_get_drvdata(input);
+
+ if (type != EV_SND || value < 0)
+ return -EINVAL;
+
+ switch (code) {
+ case SND_BELL:
+ value = value ? beeper->bell_frequency : 0;
+ break;
+ case SND_TONE:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (value == 0)
+ beeper->period = 0;
+ else
+ beeper->period = HZ_TO_NANOSECONDS(value);
+
+ if (!beeper->suspended)
+ schedule_work(&beeper->work);
+
+ return 0;
+}
+
+static void pwm_beeper_stop(struct pwm_beeper *beeper)
+{
+ cancel_work_sync(&beeper->work);
+ pwm_beeper_off(beeper);
+}
+
+static void pwm_beeper_close(struct input_dev *input)
+{
+ struct pwm_beeper *beeper = input_get_drvdata(input);
+
+ pwm_beeper_stop(beeper);
+}
+
+static int pwm_beeper_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct pwm_beeper *beeper;
+ struct pwm_state state;
+ u32 bell_frequency;
+ int error;
+
+ beeper = devm_kzalloc(dev, sizeof(*beeper), GFP_KERNEL);
+ if (!beeper)
+ return -ENOMEM;
+
+ beeper->pwm = devm_pwm_get(dev, NULL);
+ if (IS_ERR(beeper->pwm)) {
+ error = PTR_ERR(beeper->pwm);
+ if (error != -EPROBE_DEFER)
+ dev_err(dev, "Failed to request PWM device: %d\n",
+ error);
+ return error;
+ }
+
+ /* Sync up PWM state and ensure it is off. */
+ pwm_init_state(beeper->pwm, &state);
+ state.enabled = false;
+ error = pwm_apply_state(beeper->pwm, &state);
+ if (error) {
+ dev_err(dev, "failed to apply initial PWM state: %d\n",
+ error);
+ return error;
+ }
+
+ beeper->amplifier = devm_regulator_get(dev, "amp");
+ if (IS_ERR(beeper->amplifier)) {
+ error = PTR_ERR(beeper->amplifier);
+ if (error != -EPROBE_DEFER)
+ dev_err(dev, "Failed to get 'amp' regulator: %d\n",
+ error);
+ return error;
+ }
+
+ INIT_WORK(&beeper->work, pwm_beeper_work);
+
+ error = device_property_read_u32(dev, "beeper-hz", &bell_frequency);
+ if (error) {
+ bell_frequency = 1000;
+ dev_dbg(dev,
+ "failed to parse 'beeper-hz' property, using default: %uHz\n",
+ bell_frequency);
+ }
+
+ beeper->bell_frequency = bell_frequency;
+
+ beeper->input = devm_input_allocate_device(dev);
+ if (!beeper->input) {
+ dev_err(dev, "Failed to allocate input device\n");
+ return -ENOMEM;
+ }
+
+ beeper->input->name = "pwm-beeper";
+ beeper->input->phys = "pwm/input0";
+ beeper->input->id.bustype = BUS_HOST;
+ beeper->input->id.vendor = 0x001f;
+ beeper->input->id.product = 0x0001;
+ beeper->input->id.version = 0x0100;
+
+ input_set_capability(beeper->input, EV_SND, SND_TONE);
+ input_set_capability(beeper->input, EV_SND, SND_BELL);
+
+ beeper->input->event = pwm_beeper_event;
+ beeper->input->close = pwm_beeper_close;
+
+ input_set_drvdata(beeper->input, beeper);
+
+ error = input_register_device(beeper->input);
+ if (error) {
+ dev_err(dev, "Failed to register input device: %d\n", error);
+ return error;
+ }
+
+ platform_set_drvdata(pdev, beeper);
+
+ return 0;
+}
+
+static int __maybe_unused pwm_beeper_suspend(struct device *dev)
+{
+ struct pwm_beeper *beeper = dev_get_drvdata(dev);
+
+ /*
+ * Spinlock is taken here is not to protect write to
+ * beeper->suspended, but to ensure that pwm_beeper_event
+ * does not re-submit work once flag is set.
+ */
+ spin_lock_irq(&beeper->input->event_lock);
+ beeper->suspended = true;
+ spin_unlock_irq(&beeper->input->event_lock);
+
+ pwm_beeper_stop(beeper);
+
+ return 0;
+}
+
+static int __maybe_unused pwm_beeper_resume(struct device *dev)
+{
+ struct pwm_beeper *beeper = dev_get_drvdata(dev);
+
+ spin_lock_irq(&beeper->input->event_lock);
+ beeper->suspended = false;
+ spin_unlock_irq(&beeper->input->event_lock);
+
+ /* Let worker figure out if we should resume beeping */
+ schedule_work(&beeper->work);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(pwm_beeper_pm_ops,
+ pwm_beeper_suspend, pwm_beeper_resume);
+
+#ifdef CONFIG_OF
+static const struct of_device_id pwm_beeper_match[] = {
+ { .compatible = "pwm-beeper", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, pwm_beeper_match);
+#endif
+
+static struct platform_driver pwm_beeper_driver = {
+ .probe = pwm_beeper_probe,
+ .driver = {
+ .name = "pwm-beeper",
+ .pm = &pwm_beeper_pm_ops,
+ .of_match_table = of_match_ptr(pwm_beeper_match),
+ },
+};
+module_platform_driver(pwm_beeper_driver);
+
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
+MODULE_DESCRIPTION("PWM beeper driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:pwm-beeper");
diff --git a/drivers/input/misc/pwm-vibra.c b/drivers/input/misc/pwm-vibra.c
new file mode 100644
index 000000000..81e777a04
--- /dev/null
+++ b/drivers/input/misc/pwm-vibra.c
@@ -0,0 +1,270 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * PWM vibrator driver
+ *
+ * Copyright (C) 2017 Collabora Ltd.
+ *
+ * Based on previous work from:
+ * Copyright (C) 2012 Dmitry Torokhov <dmitry.torokhov@gmail.com>
+ *
+ * Based on PWM beeper driver:
+ * Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de>
+ */
+
+#include <linux/input.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/pwm.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+struct pwm_vibrator {
+ struct input_dev *input;
+ struct pwm_device *pwm;
+ struct pwm_device *pwm_dir;
+ struct regulator *vcc;
+
+ struct work_struct play_work;
+ u16 level;
+ u32 direction_duty_cycle;
+ bool vcc_on;
+};
+
+static int pwm_vibrator_start(struct pwm_vibrator *vibrator)
+{
+ struct device *pdev = vibrator->input->dev.parent;
+ struct pwm_state state;
+ int err;
+
+ if (!vibrator->vcc_on) {
+ err = regulator_enable(vibrator->vcc);
+ if (err) {
+ dev_err(pdev, "failed to enable regulator: %d", err);
+ return err;
+ }
+ vibrator->vcc_on = true;
+ }
+
+ pwm_get_state(vibrator->pwm, &state);
+ pwm_set_relative_duty_cycle(&state, vibrator->level, 0xffff);
+ state.enabled = true;
+
+ err = pwm_apply_state(vibrator->pwm, &state);
+ if (err) {
+ dev_err(pdev, "failed to apply pwm state: %d", err);
+ return err;
+ }
+
+ if (vibrator->pwm_dir) {
+ pwm_get_state(vibrator->pwm_dir, &state);
+ state.duty_cycle = vibrator->direction_duty_cycle;
+ state.enabled = true;
+
+ err = pwm_apply_state(vibrator->pwm_dir, &state);
+ if (err) {
+ dev_err(pdev, "failed to apply dir-pwm state: %d", err);
+ pwm_disable(vibrator->pwm);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+static void pwm_vibrator_stop(struct pwm_vibrator *vibrator)
+{
+ if (vibrator->pwm_dir)
+ pwm_disable(vibrator->pwm_dir);
+ pwm_disable(vibrator->pwm);
+
+ if (vibrator->vcc_on) {
+ regulator_disable(vibrator->vcc);
+ vibrator->vcc_on = false;
+ }
+}
+
+static void pwm_vibrator_play_work(struct work_struct *work)
+{
+ struct pwm_vibrator *vibrator = container_of(work,
+ struct pwm_vibrator, play_work);
+
+ if (vibrator->level)
+ pwm_vibrator_start(vibrator);
+ else
+ pwm_vibrator_stop(vibrator);
+}
+
+static int pwm_vibrator_play_effect(struct input_dev *dev, void *data,
+ struct ff_effect *effect)
+{
+ struct pwm_vibrator *vibrator = input_get_drvdata(dev);
+
+ vibrator->level = effect->u.rumble.strong_magnitude;
+ if (!vibrator->level)
+ vibrator->level = effect->u.rumble.weak_magnitude;
+
+ schedule_work(&vibrator->play_work);
+
+ return 0;
+}
+
+static void pwm_vibrator_close(struct input_dev *input)
+{
+ struct pwm_vibrator *vibrator = input_get_drvdata(input);
+
+ cancel_work_sync(&vibrator->play_work);
+ pwm_vibrator_stop(vibrator);
+}
+
+static int pwm_vibrator_probe(struct platform_device *pdev)
+{
+ struct pwm_vibrator *vibrator;
+ struct pwm_state state;
+ int err;
+
+ vibrator = devm_kzalloc(&pdev->dev, sizeof(*vibrator), GFP_KERNEL);
+ if (!vibrator)
+ return -ENOMEM;
+
+ vibrator->input = devm_input_allocate_device(&pdev->dev);
+ if (!vibrator->input)
+ return -ENOMEM;
+
+ vibrator->vcc = devm_regulator_get(&pdev->dev, "vcc");
+ err = PTR_ERR_OR_ZERO(vibrator->vcc);
+ if (err) {
+ if (err != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "Failed to request regulator: %d",
+ err);
+ return err;
+ }
+
+ vibrator->pwm = devm_pwm_get(&pdev->dev, "enable");
+ err = PTR_ERR_OR_ZERO(vibrator->pwm);
+ if (err) {
+ if (err != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "Failed to request main pwm: %d",
+ err);
+ return err;
+ }
+
+ INIT_WORK(&vibrator->play_work, pwm_vibrator_play_work);
+
+ /* Sync up PWM state and ensure it is off. */
+ pwm_init_state(vibrator->pwm, &state);
+ state.enabled = false;
+ err = pwm_apply_state(vibrator->pwm, &state);
+ if (err) {
+ dev_err(&pdev->dev, "failed to apply initial PWM state: %d",
+ err);
+ return err;
+ }
+
+ vibrator->pwm_dir = devm_pwm_get(&pdev->dev, "direction");
+ err = PTR_ERR_OR_ZERO(vibrator->pwm_dir);
+ switch (err) {
+ case 0:
+ /* Sync up PWM state and ensure it is off. */
+ pwm_init_state(vibrator->pwm_dir, &state);
+ state.enabled = false;
+ err = pwm_apply_state(vibrator->pwm_dir, &state);
+ if (err) {
+ dev_err(&pdev->dev, "failed to apply initial PWM state: %d",
+ err);
+ return err;
+ }
+
+ vibrator->direction_duty_cycle =
+ pwm_get_period(vibrator->pwm_dir) / 2;
+ device_property_read_u32(&pdev->dev, "direction-duty-cycle-ns",
+ &vibrator->direction_duty_cycle);
+ break;
+
+ case -ENODATA:
+ /* Direction PWM is optional */
+ vibrator->pwm_dir = NULL;
+ break;
+
+ default:
+ dev_err(&pdev->dev, "Failed to request direction pwm: %d", err);
+ fallthrough;
+
+ case -EPROBE_DEFER:
+ return err;
+ }
+
+ vibrator->input->name = "pwm-vibrator";
+ vibrator->input->id.bustype = BUS_HOST;
+ vibrator->input->dev.parent = &pdev->dev;
+ vibrator->input->close = pwm_vibrator_close;
+
+ input_set_drvdata(vibrator->input, vibrator);
+ input_set_capability(vibrator->input, EV_FF, FF_RUMBLE);
+
+ err = input_ff_create_memless(vibrator->input, NULL,
+ pwm_vibrator_play_effect);
+ if (err) {
+ dev_err(&pdev->dev, "Couldn't create FF dev: %d", err);
+ return err;
+ }
+
+ err = input_register_device(vibrator->input);
+ if (err) {
+ dev_err(&pdev->dev, "Couldn't register input dev: %d", err);
+ return err;
+ }
+
+ platform_set_drvdata(pdev, vibrator);
+
+ return 0;
+}
+
+static int __maybe_unused pwm_vibrator_suspend(struct device *dev)
+{
+ struct pwm_vibrator *vibrator = dev_get_drvdata(dev);
+
+ cancel_work_sync(&vibrator->play_work);
+ if (vibrator->level)
+ pwm_vibrator_stop(vibrator);
+
+ return 0;
+}
+
+static int __maybe_unused pwm_vibrator_resume(struct device *dev)
+{
+ struct pwm_vibrator *vibrator = dev_get_drvdata(dev);
+
+ if (vibrator->level)
+ pwm_vibrator_start(vibrator);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(pwm_vibrator_pm_ops,
+ pwm_vibrator_suspend, pwm_vibrator_resume);
+
+#ifdef CONFIG_OF
+static const struct of_device_id pwm_vibra_dt_match_table[] = {
+ { .compatible = "pwm-vibrator" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, pwm_vibra_dt_match_table);
+#endif
+
+static struct platform_driver pwm_vibrator_driver = {
+ .probe = pwm_vibrator_probe,
+ .driver = {
+ .name = "pwm-vibrator",
+ .pm = &pwm_vibrator_pm_ops,
+ .of_match_table = of_match_ptr(pwm_vibra_dt_match_table),
+ },
+};
+module_platform_driver(pwm_vibrator_driver);
+
+MODULE_AUTHOR("Sebastian Reichel <sre@kernel.org>");
+MODULE_DESCRIPTION("PWM vibrator driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:pwm-vibrator");
diff --git a/drivers/input/misc/rave-sp-pwrbutton.c b/drivers/input/misc/rave-sp-pwrbutton.c
new file mode 100644
index 000000000..bcab3cdb7
--- /dev/null
+++ b/drivers/input/misc/rave-sp-pwrbutton.c
@@ -0,0 +1,94 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// Power Button driver for RAVE SP
+//
+// Copyright (C) 2017 Zodiac Inflight Innovations
+//
+//
+
+#include <linux/input.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mfd/rave-sp.h>
+#include <linux/platform_device.h>
+
+#define RAVE_SP_EVNT_BUTTON_PRESS (RAVE_SP_EVNT_BASE + 0x00)
+
+struct rave_sp_power_button {
+ struct input_dev *idev;
+ struct notifier_block nb;
+};
+
+static int rave_sp_power_button_event(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct rave_sp_power_button *pb =
+ container_of(nb, struct rave_sp_power_button, nb);
+ const u8 event = rave_sp_action_unpack_event(action);
+ const u8 value = rave_sp_action_unpack_value(action);
+ struct input_dev *idev = pb->idev;
+
+ if (event == RAVE_SP_EVNT_BUTTON_PRESS) {
+ input_report_key(idev, KEY_POWER, value);
+ input_sync(idev);
+
+ return NOTIFY_STOP;
+ }
+
+ return NOTIFY_DONE;
+}
+
+static int rave_sp_pwrbutton_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rave_sp_power_button *pb;
+ struct input_dev *idev;
+ int error;
+
+ pb = devm_kzalloc(dev, sizeof(*pb), GFP_KERNEL);
+ if (!pb)
+ return -ENOMEM;
+
+ idev = devm_input_allocate_device(dev);
+ if (!idev)
+ return -ENOMEM;
+
+ idev->name = pdev->name;
+
+ input_set_capability(idev, EV_KEY, KEY_POWER);
+
+ error = input_register_device(idev);
+ if (error)
+ return error;
+
+ pb->idev = idev;
+ pb->nb.notifier_call = rave_sp_power_button_event;
+ pb->nb.priority = 128;
+
+ error = devm_rave_sp_register_event_notifier(dev, &pb->nb);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static const struct of_device_id rave_sp_pwrbutton_of_match[] = {
+ { .compatible = "zii,rave-sp-pwrbutton" },
+ {}
+};
+
+static struct platform_driver rave_sp_pwrbutton_driver = {
+ .probe = rave_sp_pwrbutton_probe,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = rave_sp_pwrbutton_of_match,
+ },
+};
+module_platform_driver(rave_sp_pwrbutton_driver);
+
+MODULE_DEVICE_TABLE(of, rave_sp_pwrbutton_of_match);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Andrey Vostrikov <andrey.vostrikov@cogentembedded.com>");
+MODULE_AUTHOR("Nikita Yushchenko <nikita.yoush@cogentembedded.com>");
+MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>");
+MODULE_DESCRIPTION("RAVE SP Power Button driver");
diff --git a/drivers/input/misc/rb532_button.c b/drivers/input/misc/rb532_button.c
new file mode 100644
index 000000000..190a80e1e
--- /dev/null
+++ b/drivers/input/misc/rb532_button.c
@@ -0,0 +1,94 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Support for the S1 button on Routerboard 532
+ *
+ * Copyright (C) 2009 Phil Sutter <n0-1@freewrt.org>
+ */
+
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+
+#include <asm/mach-rc32434/gpio.h>
+#include <asm/mach-rc32434/rb.h>
+
+#define DRV_NAME "rb532-button"
+
+#define RB532_BTN_RATE 100 /* msec */
+#define RB532_BTN_KSYM BTN_0
+
+/* The S1 button state is provided by GPIO pin 1. But as this
+ * pin is also used for uart input as alternate function, the
+ * operational modes must be switched first:
+ * 1) disable uart using set_latch_u5()
+ * 2) turn off alternate function implicitly through
+ * gpio_direction_input()
+ * 3) read the GPIO's current value
+ * 4) undo step 2 by enabling alternate function (in this
+ * mode the GPIO direction is fixed, so no change needed)
+ * 5) turn on uart again
+ * The GPIO value occurs to be inverted, so pin high means
+ * button is not pressed.
+ */
+static bool rb532_button_pressed(void)
+{
+ int val;
+
+ set_latch_u5(0, LO_FOFF);
+ gpio_direction_input(GPIO_BTN_S1);
+
+ val = gpio_get_value(GPIO_BTN_S1);
+
+ rb532_gpio_set_func(GPIO_BTN_S1);
+ set_latch_u5(LO_FOFF, 0);
+
+ return !val;
+}
+
+static void rb532_button_poll(struct input_dev *input)
+{
+ input_report_key(input, RB532_BTN_KSYM, rb532_button_pressed());
+ input_sync(input);
+}
+
+static int rb532_button_probe(struct platform_device *pdev)
+{
+ struct input_dev *input;
+ int error;
+
+ input = devm_input_allocate_device(&pdev->dev);
+ if (!input)
+ return -ENOMEM;
+
+ input->name = "rb532 button";
+ input->phys = "rb532/button0";
+ input->id.bustype = BUS_HOST;
+
+ input_set_capability(input, EV_KEY, RB532_BTN_KSYM);
+
+ error = input_setup_polling(input, rb532_button_poll);
+ if (error)
+ return error;
+
+ input_set_poll_interval(input, RB532_BTN_RATE);
+
+ error = input_register_device(input);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static struct platform_driver rb532_button_driver = {
+ .probe = rb532_button_probe,
+ .driver = {
+ .name = DRV_NAME,
+ },
+};
+module_platform_driver(rb532_button_driver);
+
+MODULE_AUTHOR("Phil Sutter <n0-1@freewrt.org>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Support for S1 button on Routerboard 532");
+MODULE_ALIAS("platform:" DRV_NAME);
diff --git a/drivers/input/misc/regulator-haptic.c b/drivers/input/misc/regulator-haptic.c
new file mode 100644
index 000000000..a661e7754
--- /dev/null
+++ b/drivers/input/misc/regulator-haptic.c
@@ -0,0 +1,264 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Regulator haptic driver
+ *
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ * Author: Jaewon Kim <jaewon02.kim@samsung.com>
+ * Author: Hyunhee Kim <hyunhee.kim@samsung.com>
+ */
+
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_data/regulator-haptic.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+#define MAX_MAGNITUDE_SHIFT 16
+
+struct regulator_haptic {
+ struct device *dev;
+ struct input_dev *input_dev;
+ struct regulator *regulator;
+
+ struct work_struct work;
+ struct mutex mutex;
+
+ bool active;
+ bool suspended;
+
+ unsigned int max_volt;
+ unsigned int min_volt;
+ unsigned int magnitude;
+};
+
+static int regulator_haptic_toggle(struct regulator_haptic *haptic, bool on)
+{
+ int error;
+
+ if (haptic->active != on) {
+
+ error = on ? regulator_enable(haptic->regulator) :
+ regulator_disable(haptic->regulator);
+ if (error) {
+ dev_err(haptic->dev,
+ "failed to switch regulator %s: %d\n",
+ on ? "on" : "off", error);
+ return error;
+ }
+
+ haptic->active = on;
+ }
+
+ return 0;
+}
+
+static int regulator_haptic_set_voltage(struct regulator_haptic *haptic,
+ unsigned int magnitude)
+{
+ u64 volt_mag_multi;
+ unsigned int intensity;
+ int error;
+
+ volt_mag_multi = (u64)(haptic->max_volt - haptic->min_volt) * magnitude;
+ intensity = (unsigned int)(volt_mag_multi >> MAX_MAGNITUDE_SHIFT);
+
+ error = regulator_set_voltage(haptic->regulator,
+ intensity + haptic->min_volt,
+ haptic->max_volt);
+ if (error) {
+ dev_err(haptic->dev, "cannot set regulator voltage to %d: %d\n",
+ intensity + haptic->min_volt, error);
+ return error;
+ }
+
+ regulator_haptic_toggle(haptic, !!magnitude);
+
+ return 0;
+}
+
+static void regulator_haptic_work(struct work_struct *work)
+{
+ struct regulator_haptic *haptic = container_of(work,
+ struct regulator_haptic, work);
+
+ mutex_lock(&haptic->mutex);
+
+ if (!haptic->suspended)
+ regulator_haptic_set_voltage(haptic, haptic->magnitude);
+
+ mutex_unlock(&haptic->mutex);
+}
+
+static int regulator_haptic_play_effect(struct input_dev *input, void *data,
+ struct ff_effect *effect)
+{
+ struct regulator_haptic *haptic = input_get_drvdata(input);
+
+ haptic->magnitude = effect->u.rumble.strong_magnitude;
+ if (!haptic->magnitude)
+ haptic->magnitude = effect->u.rumble.weak_magnitude;
+
+ schedule_work(&haptic->work);
+
+ return 0;
+}
+
+static void regulator_haptic_close(struct input_dev *input)
+{
+ struct regulator_haptic *haptic = input_get_drvdata(input);
+
+ cancel_work_sync(&haptic->work);
+ regulator_haptic_set_voltage(haptic, 0);
+}
+
+static int __maybe_unused
+regulator_haptic_parse_dt(struct device *dev, struct regulator_haptic *haptic)
+{
+ struct device_node *node;
+ int error;
+
+ node = dev->of_node;
+ if(!node) {
+ dev_err(dev, "Missing device tree data\n");
+ return -EINVAL;
+ }
+
+ error = of_property_read_u32(node, "max-microvolt", &haptic->max_volt);
+ if (error) {
+ dev_err(dev, "cannot parse max-microvolt\n");
+ return error;
+ }
+
+ error = of_property_read_u32(node, "min-microvolt", &haptic->min_volt);
+ if (error) {
+ dev_err(dev, "cannot parse min-microvolt\n");
+ return error;
+ }
+
+ return 0;
+}
+
+static int regulator_haptic_probe(struct platform_device *pdev)
+{
+ const struct regulator_haptic_data *pdata = dev_get_platdata(&pdev->dev);
+ struct regulator_haptic *haptic;
+ struct input_dev *input_dev;
+ int error;
+
+ haptic = devm_kzalloc(&pdev->dev, sizeof(*haptic), GFP_KERNEL);
+ if (!haptic)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, haptic);
+ haptic->dev = &pdev->dev;
+ mutex_init(&haptic->mutex);
+ INIT_WORK(&haptic->work, regulator_haptic_work);
+
+ if (pdata) {
+ haptic->max_volt = pdata->max_volt;
+ haptic->min_volt = pdata->min_volt;
+ } else if (IS_ENABLED(CONFIG_OF)) {
+ error = regulator_haptic_parse_dt(&pdev->dev, haptic);
+ if (error)
+ return error;
+ } else {
+ dev_err(&pdev->dev, "Missing platform data\n");
+ return -EINVAL;
+ }
+
+ haptic->regulator = devm_regulator_get_exclusive(&pdev->dev, "haptic");
+ if (IS_ERR(haptic->regulator)) {
+ dev_err(&pdev->dev, "failed to get regulator\n");
+ return PTR_ERR(haptic->regulator);
+ }
+
+ input_dev = devm_input_allocate_device(&pdev->dev);
+ if (!input_dev)
+ return -ENOMEM;
+
+ haptic->input_dev = input_dev;
+ haptic->input_dev->name = "regulator-haptic";
+ haptic->input_dev->dev.parent = &pdev->dev;
+ haptic->input_dev->close = regulator_haptic_close;
+ input_set_drvdata(haptic->input_dev, haptic);
+ input_set_capability(haptic->input_dev, EV_FF, FF_RUMBLE);
+
+ error = input_ff_create_memless(input_dev, NULL,
+ regulator_haptic_play_effect);
+ if (error) {
+ dev_err(&pdev->dev, "failed to create force-feedback\n");
+ return error;
+ }
+
+ error = input_register_device(haptic->input_dev);
+ if (error) {
+ dev_err(&pdev->dev, "failed to register input device\n");
+ return error;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused regulator_haptic_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct regulator_haptic *haptic = platform_get_drvdata(pdev);
+ int error;
+
+ error = mutex_lock_interruptible(&haptic->mutex);
+ if (error)
+ return error;
+
+ regulator_haptic_set_voltage(haptic, 0);
+
+ haptic->suspended = true;
+
+ mutex_unlock(&haptic->mutex);
+
+ return 0;
+}
+
+static int __maybe_unused regulator_haptic_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct regulator_haptic *haptic = platform_get_drvdata(pdev);
+ unsigned int magnitude;
+
+ mutex_lock(&haptic->mutex);
+
+ haptic->suspended = false;
+
+ magnitude = READ_ONCE(haptic->magnitude);
+ if (magnitude)
+ regulator_haptic_set_voltage(haptic, magnitude);
+
+ mutex_unlock(&haptic->mutex);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(regulator_haptic_pm_ops,
+ regulator_haptic_suspend, regulator_haptic_resume);
+
+static const struct of_device_id regulator_haptic_dt_match[] = {
+ { .compatible = "regulator-haptic" },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, regulator_haptic_dt_match);
+
+static struct platform_driver regulator_haptic_driver = {
+ .probe = regulator_haptic_probe,
+ .driver = {
+ .name = "regulator-haptic",
+ .of_match_table = regulator_haptic_dt_match,
+ .pm = &regulator_haptic_pm_ops,
+ },
+};
+module_platform_driver(regulator_haptic_driver);
+
+MODULE_AUTHOR("Jaewon Kim <jaewon02.kim@samsung.com>");
+MODULE_AUTHOR("Hyunhee Kim <hyunhee.kim@samsung.com>");
+MODULE_DESCRIPTION("Regulator haptic driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/retu-pwrbutton.c b/drivers/input/misc/retu-pwrbutton.c
new file mode 100644
index 000000000..64023ac08
--- /dev/null
+++ b/drivers/input/misc/retu-pwrbutton.c
@@ -0,0 +1,92 @@
+/*
+ * Retu power button driver.
+ *
+ * Copyright (C) 2004-2010 Nokia Corporation
+ *
+ * Original code written by Ari Saastamoinen, Juha Yrjölä and Felipe Balbi.
+ * Rewritten by Aaro Koskinen.
+ *
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License. See the file "COPYING" in the main directory of this
+ * archive for more details.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/irq.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/input.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mfd/retu.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+
+#define RETU_STATUS_PWRONX (1 << 5)
+
+static irqreturn_t retu_pwrbutton_irq(int irq, void *_pwr)
+{
+ struct input_dev *idev = _pwr;
+ struct retu_dev *rdev = input_get_drvdata(idev);
+ bool state;
+
+ state = !(retu_read(rdev, RETU_REG_STATUS) & RETU_STATUS_PWRONX);
+ input_report_key(idev, KEY_POWER, state);
+ input_sync(idev);
+
+ return IRQ_HANDLED;
+}
+
+static int retu_pwrbutton_probe(struct platform_device *pdev)
+{
+ struct retu_dev *rdev = dev_get_drvdata(pdev->dev.parent);
+ struct input_dev *idev;
+ int irq;
+ int error;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ idev = devm_input_allocate_device(&pdev->dev);
+ if (!idev)
+ return -ENOMEM;
+
+ idev->name = "retu-pwrbutton";
+ idev->dev.parent = &pdev->dev;
+
+ input_set_capability(idev, EV_KEY, KEY_POWER);
+ input_set_drvdata(idev, rdev);
+
+ error = devm_request_threaded_irq(&pdev->dev, irq,
+ NULL, retu_pwrbutton_irq,
+ IRQF_ONESHOT,
+ "retu-pwrbutton", idev);
+ if (error)
+ return error;
+
+ error = input_register_device(idev);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static struct platform_driver retu_pwrbutton_driver = {
+ .probe = retu_pwrbutton_probe,
+ .driver = {
+ .name = "retu-pwrbutton",
+ },
+};
+module_platform_driver(retu_pwrbutton_driver);
+
+MODULE_ALIAS("platform:retu-pwrbutton");
+MODULE_DESCRIPTION("Retu Power Button");
+MODULE_AUTHOR("Ari Saastamoinen");
+MODULE_AUTHOR("Felipe Balbi");
+MODULE_AUTHOR("Aaro Koskinen <aaro.koskinen@iki.fi>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/rk805-pwrkey.c b/drivers/input/misc/rk805-pwrkey.c
new file mode 100644
index 000000000..76873aa00
--- /dev/null
+++ b/drivers/input/misc/rk805-pwrkey.c
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Rockchip RK805 PMIC Power Key driver
+ *
+ * Copyright (c) 2017, Fuzhou Rockchip Electronics Co., Ltd
+ *
+ * Author: Joseph Chen <chenjh@rock-chips.com>
+ */
+
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+static irqreturn_t pwrkey_fall_irq(int irq, void *_pwr)
+{
+ struct input_dev *pwr = _pwr;
+
+ input_report_key(pwr, KEY_POWER, 1);
+ input_sync(pwr);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t pwrkey_rise_irq(int irq, void *_pwr)
+{
+ struct input_dev *pwr = _pwr;
+
+ input_report_key(pwr, KEY_POWER, 0);
+ input_sync(pwr);
+
+ return IRQ_HANDLED;
+}
+
+static int rk805_pwrkey_probe(struct platform_device *pdev)
+{
+ struct input_dev *pwr;
+ int fall_irq, rise_irq;
+ int err;
+
+ pwr = devm_input_allocate_device(&pdev->dev);
+ if (!pwr) {
+ dev_err(&pdev->dev, "Can't allocate power button\n");
+ return -ENOMEM;
+ }
+
+ pwr->name = "rk805 pwrkey";
+ pwr->phys = "rk805_pwrkey/input0";
+ pwr->id.bustype = BUS_HOST;
+ input_set_capability(pwr, EV_KEY, KEY_POWER);
+
+ fall_irq = platform_get_irq(pdev, 0);
+ if (fall_irq < 0)
+ return fall_irq;
+
+ rise_irq = platform_get_irq(pdev, 1);
+ if (rise_irq < 0)
+ return rise_irq;
+
+ err = devm_request_any_context_irq(&pwr->dev, fall_irq,
+ pwrkey_fall_irq,
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ "rk805_pwrkey_fall", pwr);
+ if (err < 0) {
+ dev_err(&pdev->dev, "Can't register fall irq: %d\n", err);
+ return err;
+ }
+
+ err = devm_request_any_context_irq(&pwr->dev, rise_irq,
+ pwrkey_rise_irq,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ "rk805_pwrkey_rise", pwr);
+ if (err < 0) {
+ dev_err(&pdev->dev, "Can't register rise irq: %d\n", err);
+ return err;
+ }
+
+ err = input_register_device(pwr);
+ if (err) {
+ dev_err(&pdev->dev, "Can't register power button: %d\n", err);
+ return err;
+ }
+
+ platform_set_drvdata(pdev, pwr);
+ device_init_wakeup(&pdev->dev, true);
+
+ return 0;
+}
+
+static struct platform_driver rk805_pwrkey_driver = {
+ .probe = rk805_pwrkey_probe,
+ .driver = {
+ .name = "rk805-pwrkey",
+ },
+};
+module_platform_driver(rk805_pwrkey_driver);
+
+MODULE_ALIAS("platform:rk805-pwrkey");
+MODULE_AUTHOR("Joseph Chen <chenjh@rock-chips.com>");
+MODULE_DESCRIPTION("RK805 PMIC Power Key driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/rotary_encoder.c b/drivers/input/misc/rotary_encoder.c
new file mode 100644
index 000000000..6d613f2a0
--- /dev/null
+++ b/drivers/input/misc/rotary_encoder.c
@@ -0,0 +1,370 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * rotary_encoder.c
+ *
+ * (c) 2009 Daniel Mack <daniel@caiaq.de>
+ * Copyright (C) 2011 Johan Hovold <jhovold@gmail.com>
+ *
+ * state machine code inspired by code from Tim Ruetz
+ *
+ * A generic driver for rotary encoders connected to GPIO lines.
+ * See file:Documentation/input/devices/rotary-encoder.rst for more information
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/input.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/pm.h>
+#include <linux/property.h>
+
+#define DRV_NAME "rotary-encoder"
+
+enum rotary_encoder_encoding {
+ ROTENC_GRAY,
+ ROTENC_BINARY,
+};
+
+struct rotary_encoder {
+ struct input_dev *input;
+
+ struct mutex access_mutex;
+
+ u32 steps;
+ u32 axis;
+ bool relative_axis;
+ bool rollover;
+ enum rotary_encoder_encoding encoding;
+
+ unsigned int pos;
+
+ struct gpio_descs *gpios;
+
+ unsigned int *irq;
+
+ bool armed;
+ signed char dir; /* 1 - clockwise, -1 - CCW */
+
+ unsigned int last_stable;
+};
+
+static unsigned int rotary_encoder_get_state(struct rotary_encoder *encoder)
+{
+ int i;
+ unsigned int ret = 0;
+
+ for (i = 0; i < encoder->gpios->ndescs; ++i) {
+ int val = gpiod_get_value_cansleep(encoder->gpios->desc[i]);
+
+ /* convert from gray encoding to normal */
+ if (encoder->encoding == ROTENC_GRAY && ret & 1)
+ val = !val;
+
+ ret = ret << 1 | val;
+ }
+
+ return ret & 3;
+}
+
+static void rotary_encoder_report_event(struct rotary_encoder *encoder)
+{
+ if (encoder->relative_axis) {
+ input_report_rel(encoder->input,
+ encoder->axis, encoder->dir);
+ } else {
+ unsigned int pos = encoder->pos;
+
+ if (encoder->dir < 0) {
+ /* turning counter-clockwise */
+ if (encoder->rollover)
+ pos += encoder->steps;
+ if (pos)
+ pos--;
+ } else {
+ /* turning clockwise */
+ if (encoder->rollover || pos < encoder->steps)
+ pos++;
+ }
+
+ if (encoder->rollover)
+ pos %= encoder->steps;
+
+ encoder->pos = pos;
+ input_report_abs(encoder->input, encoder->axis, encoder->pos);
+ }
+
+ input_sync(encoder->input);
+}
+
+static irqreturn_t rotary_encoder_irq(int irq, void *dev_id)
+{
+ struct rotary_encoder *encoder = dev_id;
+ unsigned int state;
+
+ mutex_lock(&encoder->access_mutex);
+
+ state = rotary_encoder_get_state(encoder);
+
+ switch (state) {
+ case 0x0:
+ if (encoder->armed) {
+ rotary_encoder_report_event(encoder);
+ encoder->armed = false;
+ }
+ break;
+
+ case 0x1:
+ case 0x3:
+ if (encoder->armed)
+ encoder->dir = 2 - state;
+ break;
+
+ case 0x2:
+ encoder->armed = true;
+ break;
+ }
+
+ mutex_unlock(&encoder->access_mutex);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t rotary_encoder_half_period_irq(int irq, void *dev_id)
+{
+ struct rotary_encoder *encoder = dev_id;
+ unsigned int state;
+
+ mutex_lock(&encoder->access_mutex);
+
+ state = rotary_encoder_get_state(encoder);
+
+ if (state & 1) {
+ encoder->dir = ((encoder->last_stable - state + 1) % 4) - 1;
+ } else {
+ if (state != encoder->last_stable) {
+ rotary_encoder_report_event(encoder);
+ encoder->last_stable = state;
+ }
+ }
+
+ mutex_unlock(&encoder->access_mutex);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t rotary_encoder_quarter_period_irq(int irq, void *dev_id)
+{
+ struct rotary_encoder *encoder = dev_id;
+ unsigned int state;
+
+ mutex_lock(&encoder->access_mutex);
+
+ state = rotary_encoder_get_state(encoder);
+
+ if ((encoder->last_stable + 1) % 4 == state)
+ encoder->dir = 1;
+ else if (encoder->last_stable == (state + 1) % 4)
+ encoder->dir = -1;
+ else
+ goto out;
+
+ rotary_encoder_report_event(encoder);
+
+out:
+ encoder->last_stable = state;
+ mutex_unlock(&encoder->access_mutex);
+
+ return IRQ_HANDLED;
+}
+
+static int rotary_encoder_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rotary_encoder *encoder;
+ struct input_dev *input;
+ irq_handler_t handler;
+ u32 steps_per_period;
+ unsigned int i;
+ int err;
+
+ encoder = devm_kzalloc(dev, sizeof(struct rotary_encoder), GFP_KERNEL);
+ if (!encoder)
+ return -ENOMEM;
+
+ mutex_init(&encoder->access_mutex);
+
+ device_property_read_u32(dev, "rotary-encoder,steps", &encoder->steps);
+
+ err = device_property_read_u32(dev, "rotary-encoder,steps-per-period",
+ &steps_per_period);
+ if (err) {
+ /*
+ * The 'half-period' property has been deprecated, you must
+ * use 'steps-per-period' and set an appropriate value, but
+ * we still need to parse it to maintain compatibility. If
+ * neither property is present we fall back to the one step
+ * per period behavior.
+ */
+ steps_per_period = device_property_read_bool(dev,
+ "rotary-encoder,half-period") ? 2 : 1;
+ }
+
+ encoder->rollover =
+ device_property_read_bool(dev, "rotary-encoder,rollover");
+
+ if (!device_property_present(dev, "rotary-encoder,encoding") ||
+ !device_property_match_string(dev, "rotary-encoder,encoding",
+ "gray")) {
+ dev_info(dev, "gray");
+ encoder->encoding = ROTENC_GRAY;
+ } else if (!device_property_match_string(dev, "rotary-encoder,encoding",
+ "binary")) {
+ dev_info(dev, "binary");
+ encoder->encoding = ROTENC_BINARY;
+ } else {
+ dev_err(dev, "unknown encoding setting\n");
+ return -EINVAL;
+ }
+
+ device_property_read_u32(dev, "linux,axis", &encoder->axis);
+ encoder->relative_axis =
+ device_property_read_bool(dev, "rotary-encoder,relative-axis");
+
+ encoder->gpios = devm_gpiod_get_array(dev, NULL, GPIOD_IN);
+ if (IS_ERR(encoder->gpios)) {
+ err = PTR_ERR(encoder->gpios);
+ if (err != -EPROBE_DEFER)
+ dev_err(dev, "unable to get gpios: %d\n", err);
+ return err;
+ }
+ if (encoder->gpios->ndescs < 2) {
+ dev_err(dev, "not enough gpios found\n");
+ return -EINVAL;
+ }
+
+ input = devm_input_allocate_device(dev);
+ if (!input)
+ return -ENOMEM;
+
+ encoder->input = input;
+
+ input->name = pdev->name;
+ input->id.bustype = BUS_HOST;
+ input->dev.parent = dev;
+
+ if (encoder->relative_axis)
+ input_set_capability(input, EV_REL, encoder->axis);
+ else
+ input_set_abs_params(input,
+ encoder->axis, 0, encoder->steps, 0, 1);
+
+ switch (steps_per_period >> (encoder->gpios->ndescs - 2)) {
+ case 4:
+ handler = &rotary_encoder_quarter_period_irq;
+ encoder->last_stable = rotary_encoder_get_state(encoder);
+ break;
+ case 2:
+ handler = &rotary_encoder_half_period_irq;
+ encoder->last_stable = rotary_encoder_get_state(encoder);
+ break;
+ case 1:
+ handler = &rotary_encoder_irq;
+ break;
+ default:
+ dev_err(dev, "'%d' is not a valid steps-per-period value\n",
+ steps_per_period);
+ return -EINVAL;
+ }
+
+ encoder->irq =
+ devm_kcalloc(dev,
+ encoder->gpios->ndescs, sizeof(*encoder->irq),
+ GFP_KERNEL);
+ if (!encoder->irq)
+ return -ENOMEM;
+
+ for (i = 0; i < encoder->gpios->ndescs; ++i) {
+ encoder->irq[i] = gpiod_to_irq(encoder->gpios->desc[i]);
+
+ err = devm_request_threaded_irq(dev, encoder->irq[i],
+ NULL, handler,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT,
+ DRV_NAME, encoder);
+ if (err) {
+ dev_err(dev, "unable to request IRQ %d (gpio#%d)\n",
+ encoder->irq[i], i);
+ return err;
+ }
+ }
+
+ err = input_register_device(input);
+ if (err) {
+ dev_err(dev, "failed to register input device\n");
+ return err;
+ }
+
+ device_init_wakeup(dev,
+ device_property_read_bool(dev, "wakeup-source"));
+
+ platform_set_drvdata(pdev, encoder);
+
+ return 0;
+}
+
+static int __maybe_unused rotary_encoder_suspend(struct device *dev)
+{
+ struct rotary_encoder *encoder = dev_get_drvdata(dev);
+ unsigned int i;
+
+ if (device_may_wakeup(dev)) {
+ for (i = 0; i < encoder->gpios->ndescs; ++i)
+ enable_irq_wake(encoder->irq[i]);
+ }
+
+ return 0;
+}
+
+static int __maybe_unused rotary_encoder_resume(struct device *dev)
+{
+ struct rotary_encoder *encoder = dev_get_drvdata(dev);
+ unsigned int i;
+
+ if (device_may_wakeup(dev)) {
+ for (i = 0; i < encoder->gpios->ndescs; ++i)
+ disable_irq_wake(encoder->irq[i]);
+ }
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(rotary_encoder_pm_ops,
+ rotary_encoder_suspend, rotary_encoder_resume);
+
+#ifdef CONFIG_OF
+static const struct of_device_id rotary_encoder_of_match[] = {
+ { .compatible = "rotary-encoder", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, rotary_encoder_of_match);
+#endif
+
+static struct platform_driver rotary_encoder_driver = {
+ .probe = rotary_encoder_probe,
+ .driver = {
+ .name = DRV_NAME,
+ .pm = &rotary_encoder_pm_ops,
+ .of_match_table = of_match_ptr(rotary_encoder_of_match),
+ }
+};
+module_platform_driver(rotary_encoder_driver);
+
+MODULE_ALIAS("platform:" DRV_NAME);
+MODULE_DESCRIPTION("GPIO rotary encoder driver");
+MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>, Johan Hovold");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/misc/rt5120-pwrkey.c b/drivers/input/misc/rt5120-pwrkey.c
new file mode 100644
index 000000000..8a8c1aeee
--- /dev/null
+++ b/drivers/input/misc/rt5120-pwrkey.c
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2022 Richtek Technology Corp.
+ * Author: ChiYuan Huang <cy_huang@richtek.com>
+ */
+
+#include <linux/bits.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define RT5120_REG_INTSTAT 0x1E
+#define RT5120_PWRKEYSTAT_MASK BIT(7)
+
+struct rt5120_priv {
+ struct regmap *regmap;
+ struct input_dev *input;
+};
+
+static irqreturn_t rt5120_pwrkey_handler(int irq, void *devid)
+{
+ struct rt5120_priv *priv = devid;
+ unsigned int stat;
+ int error;
+
+ error = regmap_read(priv->regmap, RT5120_REG_INTSTAT, &stat);
+ if (error)
+ return IRQ_NONE;
+
+ input_report_key(priv->input, KEY_POWER,
+ !(stat & RT5120_PWRKEYSTAT_MASK));
+ input_sync(priv->input);
+
+ return IRQ_HANDLED;
+}
+
+static int rt5120_pwrkey_probe(struct platform_device *pdev)
+{
+ struct rt5120_priv *priv;
+ struct device *dev = &pdev->dev;
+ int press_irq, release_irq;
+ int error;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->regmap = dev_get_regmap(dev->parent, NULL);
+ if (!priv->regmap) {
+ dev_err(dev, "Failed to init regmap\n");
+ return -ENODEV;
+ }
+
+ press_irq = platform_get_irq_byname(pdev, "pwrkey-press");
+ if (press_irq < 0)
+ return press_irq;
+
+ release_irq = platform_get_irq_byname(pdev, "pwrkey-release");
+ if (release_irq < 0)
+ return release_irq;
+
+ /* Make input device be device resource managed */
+ priv->input = devm_input_allocate_device(dev);
+ if (!priv->input)
+ return -ENOMEM;
+
+ priv->input->name = "rt5120_pwrkey";
+ priv->input->phys = "rt5120_pwrkey/input0";
+ priv->input->id.bustype = BUS_I2C;
+ input_set_capability(priv->input, EV_KEY, KEY_POWER);
+
+ error = input_register_device(priv->input);
+ if (error) {
+ dev_err(dev, "Failed to register input device: %d\n", error);
+ return error;
+ }
+
+ error = devm_request_threaded_irq(dev, press_irq,
+ NULL, rt5120_pwrkey_handler,
+ 0, "pwrkey-press", priv);
+ if (error) {
+ dev_err(dev,
+ "Failed to register pwrkey press irq: %d\n", error);
+ return error;
+ }
+
+ error = devm_request_threaded_irq(dev, release_irq,
+ NULL, rt5120_pwrkey_handler,
+ 0, "pwrkey-release", priv);
+ if (error) {
+ dev_err(dev,
+ "Failed to register pwrkey release irq: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id r5120_pwrkey_match_table[] = {
+ { .compatible = "richtek,rt5120-pwrkey" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, r5120_pwrkey_match_table);
+
+static struct platform_driver rt5120_pwrkey_driver = {
+ .driver = {
+ .name = "rt5120-pwrkey",
+ .of_match_table = r5120_pwrkey_match_table,
+ },
+ .probe = rt5120_pwrkey_probe,
+};
+module_platform_driver(rt5120_pwrkey_driver);
+
+MODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>");
+MODULE_DESCRIPTION("Richtek RT5120 power key driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/sc27xx-vibra.c b/drivers/input/misc/sc27xx-vibra.c
new file mode 100644
index 000000000..1478017f0
--- /dev/null
+++ b/drivers/input/misc/sc27xx-vibra.c
@@ -0,0 +1,201 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Spreadtrum Communications Inc.
+ */
+
+#include <linux/device.h>
+#include <linux/input.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/workqueue.h>
+
+#define CUR_DRV_CAL_SEL GENMASK(13, 12)
+#define SLP_LDOVIBR_PD_EN BIT(9)
+#define LDO_VIBR_PD BIT(8)
+#define SC2730_CUR_DRV_CAL_SEL 0
+#define SC2730_SLP_LDOVIBR_PD_EN BIT(14)
+#define SC2730_LDO_VIBR_PD BIT(13)
+
+struct sc27xx_vibra_data {
+ u32 cur_drv_cal_sel;
+ u32 slp_pd_en;
+ u32 ldo_pd;
+};
+
+struct vibra_info {
+ struct input_dev *input_dev;
+ struct work_struct play_work;
+ struct regmap *regmap;
+ const struct sc27xx_vibra_data *data;
+ u32 base;
+ u32 strength;
+ bool enabled;
+};
+
+static const struct sc27xx_vibra_data sc2731_data = {
+ .cur_drv_cal_sel = CUR_DRV_CAL_SEL,
+ .slp_pd_en = SLP_LDOVIBR_PD_EN,
+ .ldo_pd = LDO_VIBR_PD,
+};
+
+static const struct sc27xx_vibra_data sc2730_data = {
+ .cur_drv_cal_sel = SC2730_CUR_DRV_CAL_SEL,
+ .slp_pd_en = SC2730_SLP_LDOVIBR_PD_EN,
+ .ldo_pd = SC2730_LDO_VIBR_PD,
+};
+
+static const struct sc27xx_vibra_data sc2721_data = {
+ .cur_drv_cal_sel = CUR_DRV_CAL_SEL,
+ .slp_pd_en = SLP_LDOVIBR_PD_EN,
+ .ldo_pd = LDO_VIBR_PD,
+};
+
+static void sc27xx_vibra_set(struct vibra_info *info, bool on)
+{
+ const struct sc27xx_vibra_data *data = info->data;
+ if (on) {
+ regmap_update_bits(info->regmap, info->base, data->ldo_pd, 0);
+ regmap_update_bits(info->regmap, info->base,
+ data->slp_pd_en, 0);
+ info->enabled = true;
+ } else {
+ regmap_update_bits(info->regmap, info->base, data->ldo_pd,
+ data->ldo_pd);
+ regmap_update_bits(info->regmap, info->base,
+ data->slp_pd_en, data->slp_pd_en);
+ info->enabled = false;
+ }
+}
+
+static int sc27xx_vibra_hw_init(struct vibra_info *info)
+{
+ const struct sc27xx_vibra_data *data = info->data;
+
+ if (!data->cur_drv_cal_sel)
+ return 0;
+
+ return regmap_update_bits(info->regmap, info->base,
+ data->cur_drv_cal_sel, 0);
+}
+
+static void sc27xx_vibra_play_work(struct work_struct *work)
+{
+ struct vibra_info *info = container_of(work, struct vibra_info,
+ play_work);
+
+ if (info->strength && !info->enabled)
+ sc27xx_vibra_set(info, true);
+ else if (info->strength == 0 && info->enabled)
+ sc27xx_vibra_set(info, false);
+}
+
+static int sc27xx_vibra_play(struct input_dev *input, void *data,
+ struct ff_effect *effect)
+{
+ struct vibra_info *info = input_get_drvdata(input);
+
+ info->strength = effect->u.rumble.weak_magnitude;
+ schedule_work(&info->play_work);
+
+ return 0;
+}
+
+static void sc27xx_vibra_close(struct input_dev *input)
+{
+ struct vibra_info *info = input_get_drvdata(input);
+
+ cancel_work_sync(&info->play_work);
+ if (info->enabled)
+ sc27xx_vibra_set(info, false);
+}
+
+static int sc27xx_vibra_probe(struct platform_device *pdev)
+{
+ struct vibra_info *info;
+ const struct sc27xx_vibra_data *data;
+ int error;
+
+ data = device_get_match_data(&pdev->dev);
+ if (!data) {
+ dev_err(&pdev->dev, "no matching driver data found\n");
+ return -EINVAL;
+ }
+
+ info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ info->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!info->regmap) {
+ dev_err(&pdev->dev, "failed to get vibrator regmap.\n");
+ return -ENODEV;
+ }
+
+ error = device_property_read_u32(&pdev->dev, "reg", &info->base);
+ if (error) {
+ dev_err(&pdev->dev, "failed to get vibrator base address.\n");
+ return error;
+ }
+
+ info->input_dev = devm_input_allocate_device(&pdev->dev);
+ if (!info->input_dev) {
+ dev_err(&pdev->dev, "failed to allocate input device.\n");
+ return -ENOMEM;
+ }
+
+ info->input_dev->name = "sc27xx:vibrator";
+ info->input_dev->id.version = 0;
+ info->input_dev->close = sc27xx_vibra_close;
+ info->data = data;
+
+ input_set_drvdata(info->input_dev, info);
+ input_set_capability(info->input_dev, EV_FF, FF_RUMBLE);
+ INIT_WORK(&info->play_work, sc27xx_vibra_play_work);
+ info->enabled = false;
+
+ error = sc27xx_vibra_hw_init(info);
+ if (error) {
+ dev_err(&pdev->dev, "failed to initialize the vibrator.\n");
+ return error;
+ }
+
+ error = input_ff_create_memless(info->input_dev, NULL,
+ sc27xx_vibra_play);
+ if (error) {
+ dev_err(&pdev->dev, "failed to register vibrator to FF.\n");
+ return error;
+ }
+
+ error = input_register_device(info->input_dev);
+ if (error) {
+ dev_err(&pdev->dev, "failed to register input device.\n");
+ return error;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id sc27xx_vibra_of_match[] = {
+ { .compatible = "sprd,sc2721-vibrator", .data = &sc2721_data },
+ { .compatible = "sprd,sc2730-vibrator", .data = &sc2730_data },
+ { .compatible = "sprd,sc2731-vibrator", .data = &sc2731_data },
+ {}
+};
+MODULE_DEVICE_TABLE(of, sc27xx_vibra_of_match);
+
+static struct platform_driver sc27xx_vibra_driver = {
+ .driver = {
+ .name = "sc27xx-vibrator",
+ .of_match_table = sc27xx_vibra_of_match,
+ },
+ .probe = sc27xx_vibra_probe,
+};
+
+module_platform_driver(sc27xx_vibra_driver);
+
+MODULE_DESCRIPTION("Spreadtrum SC27xx Vibrator Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Xiaotong Lu <xiaotong.lu@spreadtrum.com>");
diff --git a/drivers/input/misc/sgi_btns.c b/drivers/input/misc/sgi_btns.c
new file mode 100644
index 000000000..0657d785b
--- /dev/null
+++ b/drivers/input/misc/sgi_btns.c
@@ -0,0 +1,131 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * SGI Volume Button interface driver
+ *
+ * Copyright (C) 2008 Thomas Bogendoerfer <tsbogend@alpha.franken.de>
+ */
+#include <linux/input.h>
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#ifdef CONFIG_SGI_IP22
+#include <asm/sgi/ioc.h>
+
+static inline u8 button_status(void)
+{
+ u8 status;
+
+ status = readb(&sgioc->panel) ^ 0xa0;
+ return ((status & 0x80) >> 6) | ((status & 0x20) >> 5);
+}
+#endif
+
+#ifdef CONFIG_SGI_IP32
+#include <asm/ip32/mace.h>
+
+static inline u8 button_status(void)
+{
+ u64 status;
+
+ status = readq(&mace->perif.audio.control);
+ writeq(status & ~(3U << 23), &mace->perif.audio.control);
+
+ return (status >> 23) & 3;
+}
+#endif
+
+#define BUTTONS_POLL_INTERVAL 30 /* msec */
+#define BUTTONS_COUNT_THRESHOLD 3
+
+static const unsigned short sgi_map[] = {
+ KEY_VOLUMEDOWN,
+ KEY_VOLUMEUP
+};
+
+struct buttons_dev {
+ unsigned short keymap[ARRAY_SIZE(sgi_map)];
+ int count[ARRAY_SIZE(sgi_map)];
+};
+
+static void handle_buttons(struct input_dev *input)
+{
+ struct buttons_dev *bdev = input_get_drvdata(input);
+ u8 status;
+ int i;
+
+ status = button_status();
+
+ for (i = 0; i < ARRAY_SIZE(bdev->keymap); i++) {
+ if (status & (1U << i)) {
+ if (++bdev->count[i] == BUTTONS_COUNT_THRESHOLD) {
+ input_event(input, EV_MSC, MSC_SCAN, i);
+ input_report_key(input, bdev->keymap[i], 1);
+ input_sync(input);
+ }
+ } else {
+ if (bdev->count[i] >= BUTTONS_COUNT_THRESHOLD) {
+ input_event(input, EV_MSC, MSC_SCAN, i);
+ input_report_key(input, bdev->keymap[i], 0);
+ input_sync(input);
+ }
+ bdev->count[i] = 0;
+ }
+ }
+}
+
+static int sgi_buttons_probe(struct platform_device *pdev)
+{
+ struct buttons_dev *bdev;
+ struct input_dev *input;
+ int error, i;
+
+ bdev = devm_kzalloc(&pdev->dev, sizeof(*bdev), GFP_KERNEL);
+ if (!bdev)
+ return -ENOMEM;
+
+ input = devm_input_allocate_device(&pdev->dev);
+ if (!input)
+ return -ENOMEM;
+
+ memcpy(bdev->keymap, sgi_map, sizeof(bdev->keymap));
+
+ input_set_drvdata(input, bdev);
+
+ input->name = "SGI buttons";
+ input->phys = "sgi/input0";
+ input->id.bustype = BUS_HOST;
+
+ input->keycode = bdev->keymap;
+ input->keycodemax = ARRAY_SIZE(bdev->keymap);
+ input->keycodesize = sizeof(unsigned short);
+
+ input_set_capability(input, EV_MSC, MSC_SCAN);
+ __set_bit(EV_KEY, input->evbit);
+ for (i = 0; i < ARRAY_SIZE(sgi_map); i++)
+ __set_bit(bdev->keymap[i], input->keybit);
+ __clear_bit(KEY_RESERVED, input->keybit);
+
+ error = input_setup_polling(input, handle_buttons);
+ if (error)
+ return error;
+
+ input_set_poll_interval(input, BUTTONS_POLL_INTERVAL);
+
+ error = input_register_device(input);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static struct platform_driver sgi_buttons_driver = {
+ .probe = sgi_buttons_probe,
+ .driver = {
+ .name = "sgibtns",
+ },
+};
+module_platform_driver(sgi_buttons_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c
new file mode 100644
index 000000000..9116f4248
--- /dev/null
+++ b/drivers/input/misc/soc_button_array.c
@@ -0,0 +1,625 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Supports for the button array on SoC tablets originally running
+ * Windows 8.
+ *
+ * (C) Copyright 2014 Intel Corporation
+ */
+
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/init.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/acpi.h>
+#include <linux/dmi.h>
+#include <linux/gpio/consumer.h>
+#include <linux/gpio_keys.h>
+#include <linux/gpio.h>
+#include <linux/platform_device.h>
+
+static bool use_low_level_irq;
+module_param(use_low_level_irq, bool, 0444);
+MODULE_PARM_DESC(use_low_level_irq, "Use low-level triggered IRQ instead of edge triggered");
+
+struct soc_button_info {
+ const char *name;
+ int acpi_index;
+ unsigned int event_type;
+ unsigned int event_code;
+ bool autorepeat;
+ bool wakeup;
+ bool active_low;
+};
+
+struct soc_device_data {
+ const struct soc_button_info *button_info;
+ int (*check)(struct device *dev);
+};
+
+/*
+ * Some of the buttons like volume up/down are auto repeat, while others
+ * are not. To support both, we register two platform devices, and put
+ * buttons into them based on whether the key should be auto repeat.
+ */
+#define BUTTON_TYPES 2
+
+struct soc_button_data {
+ struct platform_device *children[BUTTON_TYPES];
+};
+
+/*
+ * Some 2-in-1s which use the soc_button_array driver have this ugly issue in
+ * their DSDT where the _LID method modifies the irq-type settings of the GPIOs
+ * used for the power and home buttons. The intend of this AML code is to
+ * disable these buttons when the lid is closed.
+ * The AML does this by directly poking the GPIO controllers registers. This is
+ * problematic because when re-enabling the irq, which happens whenever _LID
+ * gets called with the lid open (e.g. on boot and on resume), it sets the
+ * irq-type to IRQ_TYPE_LEVEL_LOW. Where as the gpio-keys driver programs the
+ * type to, and expects it to be, IRQ_TYPE_EDGE_BOTH.
+ * To work around this we don't set gpio_keys_button.gpio on these 2-in-1s,
+ * instead we get the irq for the GPIO ourselves, configure it as
+ * IRQ_TYPE_LEVEL_LOW (to match how the _LID AML code configures it) and pass
+ * the irq in gpio_keys_button.irq. Below is a list of affected devices.
+ */
+static const struct dmi_system_id dmi_use_low_level_irq[] = {
+ {
+ /*
+ * Acer Switch 10 SW5-012. _LID method messes with home- and
+ * power-button GPIO IRQ settings. When (re-)enabling the irq
+ * it ors in its own flags without clearing the previous set
+ * ones, leading to an irq-type of IRQ_TYPE_LEVEL_LOW |
+ * IRQ_TYPE_LEVEL_HIGH causing a continuous interrupt storm.
+ */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire SW5-012"),
+ },
+ },
+ {
+ /* Acer Switch V 10 SW5-017, same issue as Acer Switch 10 SW5-012. */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "SW5-017"),
+ },
+ },
+ {
+ /*
+ * Acer One S1003. _LID method messes with power-button GPIO
+ * IRQ settings, leading to a non working power-button.
+ */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "One S1003"),
+ },
+ },
+ {
+ /*
+ * Lenovo Yoga Tab2 1051F/1051L, something messes with the home-button
+ * IRQ settings, leading to a non working home-button.
+ */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "60073"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "1051"),
+ },
+ },
+ {} /* Terminating entry */
+};
+
+/*
+ * Some devices have a wrong entry which points to a GPIO which is
+ * required in another driver, so this driver must not claim it.
+ */
+static const struct dmi_system_id dmi_invalid_acpi_index[] = {
+ {
+ /*
+ * Lenovo Yoga Book X90F / X90L, the PNP0C40 home button entry
+ * points to a GPIO which is not a home button and which is
+ * required by the lenovo-yogabook driver.
+ */
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel Corporation"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "CHERRYVIEW D1 PLATFORM"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "YETI-11"),
+ },
+ .driver_data = (void *)1l,
+ },
+ {} /* Terminating entry */
+};
+
+/*
+ * Get the Nth GPIO number from the ACPI object.
+ */
+static int soc_button_lookup_gpio(struct device *dev, int acpi_index,
+ int *gpio_ret, int *irq_ret)
+{
+ struct gpio_desc *desc;
+
+ desc = gpiod_get_index(dev, NULL, acpi_index, GPIOD_ASIS);
+ if (IS_ERR(desc))
+ return PTR_ERR(desc);
+
+ *gpio_ret = desc_to_gpio(desc);
+ *irq_ret = gpiod_to_irq(desc);
+
+ gpiod_put(desc);
+
+ return 0;
+}
+
+static struct platform_device *
+soc_button_device_create(struct platform_device *pdev,
+ const struct soc_button_info *button_info,
+ bool autorepeat)
+{
+ const struct soc_button_info *info;
+ struct platform_device *pd;
+ struct gpio_keys_button *gpio_keys;
+ struct gpio_keys_platform_data *gpio_keys_pdata;
+ const struct dmi_system_id *dmi_id;
+ int invalid_acpi_index = -1;
+ int error, gpio, irq;
+ int n_buttons = 0;
+
+ for (info = button_info; info->name; info++)
+ if (info->autorepeat == autorepeat)
+ n_buttons++;
+
+ gpio_keys_pdata = devm_kzalloc(&pdev->dev,
+ sizeof(*gpio_keys_pdata) +
+ sizeof(*gpio_keys) * n_buttons,
+ GFP_KERNEL);
+ if (!gpio_keys_pdata)
+ return ERR_PTR(-ENOMEM);
+
+ gpio_keys = (void *)(gpio_keys_pdata + 1);
+ n_buttons = 0;
+
+ dmi_id = dmi_first_match(dmi_invalid_acpi_index);
+ if (dmi_id)
+ invalid_acpi_index = (long)dmi_id->driver_data;
+
+ for (info = button_info; info->name; info++) {
+ if (info->autorepeat != autorepeat)
+ continue;
+
+ if (info->acpi_index == invalid_acpi_index)
+ continue;
+
+ error = soc_button_lookup_gpio(&pdev->dev, info->acpi_index, &gpio, &irq);
+ if (error || irq < 0) {
+ /*
+ * Skip GPIO if not present. Note we deliberately
+ * ignore -EPROBE_DEFER errors here. On some devices
+ * Intel is using so called virtual GPIOs which are not
+ * GPIOs at all but some way for AML code to check some
+ * random status bits without need a custom opregion.
+ * In some cases the resources table we parse points to
+ * such a virtual GPIO, since these are not real GPIOs
+ * we do not have a driver for these so they will never
+ * show up, therefore we ignore -EPROBE_DEFER.
+ */
+ continue;
+ }
+
+ /* See dmi_use_low_level_irq[] comment */
+ if (!autorepeat && (use_low_level_irq ||
+ dmi_check_system(dmi_use_low_level_irq))) {
+ irq_set_irq_type(irq, IRQ_TYPE_LEVEL_LOW);
+ gpio_keys[n_buttons].irq = irq;
+ gpio_keys[n_buttons].gpio = -ENOENT;
+ } else {
+ gpio_keys[n_buttons].gpio = gpio;
+ }
+
+ gpio_keys[n_buttons].type = info->event_type;
+ gpio_keys[n_buttons].code = info->event_code;
+ gpio_keys[n_buttons].active_low = info->active_low;
+ gpio_keys[n_buttons].desc = info->name;
+ gpio_keys[n_buttons].wakeup = info->wakeup;
+ /* These devices often use cheap buttons, use 50 ms debounce */
+ gpio_keys[n_buttons].debounce_interval = 50;
+ n_buttons++;
+ }
+
+ if (n_buttons == 0) {
+ error = -ENODEV;
+ goto err_free_mem;
+ }
+
+ gpio_keys_pdata->buttons = gpio_keys;
+ gpio_keys_pdata->nbuttons = n_buttons;
+ gpio_keys_pdata->rep = autorepeat;
+
+ pd = platform_device_register_resndata(&pdev->dev, "gpio-keys",
+ PLATFORM_DEVID_AUTO, NULL, 0,
+ gpio_keys_pdata,
+ sizeof(*gpio_keys_pdata));
+ error = PTR_ERR_OR_ZERO(pd);
+ if (error) {
+ dev_err(&pdev->dev,
+ "failed registering gpio-keys: %d\n", error);
+ goto err_free_mem;
+ }
+
+ return pd;
+
+err_free_mem:
+ devm_kfree(&pdev->dev, gpio_keys_pdata);
+ return ERR_PTR(error);
+}
+
+static int soc_button_get_acpi_object_int(const union acpi_object *obj)
+{
+ if (obj->type != ACPI_TYPE_INTEGER)
+ return -1;
+
+ return obj->integer.value;
+}
+
+/* Parse a single ACPI0011 _DSD button descriptor */
+static int soc_button_parse_btn_desc(struct device *dev,
+ const union acpi_object *desc,
+ int collection_uid,
+ struct soc_button_info *info)
+{
+ int upage, usage;
+
+ if (desc->type != ACPI_TYPE_PACKAGE ||
+ desc->package.count != 5 ||
+ /* First byte should be 1 (control) */
+ soc_button_get_acpi_object_int(&desc->package.elements[0]) != 1 ||
+ /* Third byte should be collection uid */
+ soc_button_get_acpi_object_int(&desc->package.elements[2]) !=
+ collection_uid) {
+ dev_err(dev, "Invalid ACPI Button Descriptor\n");
+ return -ENODEV;
+ }
+
+ info->event_type = EV_KEY;
+ info->active_low = true;
+ info->acpi_index =
+ soc_button_get_acpi_object_int(&desc->package.elements[1]);
+ upage = soc_button_get_acpi_object_int(&desc->package.elements[3]);
+ usage = soc_button_get_acpi_object_int(&desc->package.elements[4]);
+
+ /*
+ * The UUID: fa6bd625-9ce8-470d-a2c7-b3ca36c4282e descriptors use HID
+ * usage page and usage codes, but otherwise the device is not HID
+ * compliant: it uses one irq per button instead of generating HID
+ * input reports and some buttons should generate wakeups where as
+ * others should not, so we cannot use the HID subsystem.
+ *
+ * Luckily all devices only use a few usage page + usage combinations,
+ * so we can simply check for the known combinations here.
+ */
+ if (upage == 0x01 && usage == 0x81) {
+ info->name = "power";
+ info->event_code = KEY_POWER;
+ info->wakeup = true;
+ } else if (upage == 0x01 && usage == 0xc6) {
+ info->name = "airplane mode switch";
+ info->event_type = EV_SW;
+ info->event_code = SW_RFKILL_ALL;
+ info->active_low = false;
+ } else if (upage == 0x01 && usage == 0xca) {
+ info->name = "rotation lock switch";
+ info->event_type = EV_SW;
+ info->event_code = SW_ROTATE_LOCK;
+ } else if (upage == 0x07 && usage == 0xe3) {
+ info->name = "home";
+ info->event_code = KEY_LEFTMETA;
+ info->wakeup = true;
+ } else if (upage == 0x0c && usage == 0xe9) {
+ info->name = "volume_up";
+ info->event_code = KEY_VOLUMEUP;
+ info->autorepeat = true;
+ } else if (upage == 0x0c && usage == 0xea) {
+ info->name = "volume_down";
+ info->event_code = KEY_VOLUMEDOWN;
+ info->autorepeat = true;
+ } else {
+ dev_warn(dev, "Unknown button index %d upage %02x usage %02x, ignoring\n",
+ info->acpi_index, upage, usage);
+ info->name = "unknown";
+ info->event_code = KEY_RESERVED;
+ }
+
+ return 0;
+}
+
+/* ACPI0011 _DSD btns descriptors UUID: fa6bd625-9ce8-470d-a2c7-b3ca36c4282e */
+static const u8 btns_desc_uuid[16] = {
+ 0x25, 0xd6, 0x6b, 0xfa, 0xe8, 0x9c, 0x0d, 0x47,
+ 0xa2, 0xc7, 0xb3, 0xca, 0x36, 0xc4, 0x28, 0x2e
+};
+
+/* Parse ACPI0011 _DSD button descriptors */
+static struct soc_button_info *soc_button_get_button_info(struct device *dev)
+{
+ struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER };
+ const union acpi_object *desc, *el0, *uuid, *btns_desc = NULL;
+ struct soc_button_info *button_info;
+ acpi_status status;
+ int i, btn, collection_uid = -1;
+
+ status = acpi_evaluate_object_typed(ACPI_HANDLE(dev), "_DSD", NULL,
+ &buf, ACPI_TYPE_PACKAGE);
+ if (ACPI_FAILURE(status)) {
+ dev_err(dev, "ACPI _DSD object not found\n");
+ return ERR_PTR(-ENODEV);
+ }
+
+ /* Look for the Button Descriptors UUID */
+ desc = buf.pointer;
+ for (i = 0; (i + 1) < desc->package.count; i += 2) {
+ uuid = &desc->package.elements[i];
+
+ if (uuid->type != ACPI_TYPE_BUFFER ||
+ uuid->buffer.length != 16 ||
+ desc->package.elements[i + 1].type != ACPI_TYPE_PACKAGE) {
+ break;
+ }
+
+ if (memcmp(uuid->buffer.pointer, btns_desc_uuid, 16) == 0) {
+ btns_desc = &desc->package.elements[i + 1];
+ break;
+ }
+ }
+
+ if (!btns_desc) {
+ dev_err(dev, "ACPI Button Descriptors not found\n");
+ button_info = ERR_PTR(-ENODEV);
+ goto out;
+ }
+
+ /* The first package describes the collection */
+ el0 = &btns_desc->package.elements[0];
+ if (el0->type == ACPI_TYPE_PACKAGE &&
+ el0->package.count == 5 &&
+ /* First byte should be 0 (collection) */
+ soc_button_get_acpi_object_int(&el0->package.elements[0]) == 0 &&
+ /* Third byte should be 0 (top level collection) */
+ soc_button_get_acpi_object_int(&el0->package.elements[2]) == 0) {
+ collection_uid = soc_button_get_acpi_object_int(
+ &el0->package.elements[1]);
+ }
+ if (collection_uid == -1) {
+ dev_err(dev, "Invalid Button Collection Descriptor\n");
+ button_info = ERR_PTR(-ENODEV);
+ goto out;
+ }
+
+ /* There are package.count - 1 buttons + 1 terminating empty entry */
+ button_info = devm_kcalloc(dev, btns_desc->package.count,
+ sizeof(*button_info), GFP_KERNEL);
+ if (!button_info) {
+ button_info = ERR_PTR(-ENOMEM);
+ goto out;
+ }
+
+ /* Parse the button descriptors */
+ for (i = 1, btn = 0; i < btns_desc->package.count; i++, btn++) {
+ if (soc_button_parse_btn_desc(dev,
+ &btns_desc->package.elements[i],
+ collection_uid,
+ &button_info[btn])) {
+ button_info = ERR_PTR(-ENODEV);
+ goto out;
+ }
+ }
+
+out:
+ kfree(buf.pointer);
+ return button_info;
+}
+
+static int soc_button_remove(struct platform_device *pdev)
+{
+ struct soc_button_data *priv = platform_get_drvdata(pdev);
+
+ int i;
+
+ for (i = 0; i < BUTTON_TYPES; i++)
+ if (priv->children[i])
+ platform_device_unregister(priv->children[i]);
+
+ return 0;
+}
+
+static int soc_button_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const struct soc_device_data *device_data;
+ const struct soc_button_info *button_info;
+ struct soc_button_data *priv;
+ struct platform_device *pd;
+ int i;
+ int error;
+
+ device_data = acpi_device_get_match_data(dev);
+ if (device_data && device_data->check) {
+ error = device_data->check(dev);
+ if (error)
+ return error;
+ }
+
+ if (device_data && device_data->button_info) {
+ button_info = device_data->button_info;
+ } else {
+ button_info = soc_button_get_button_info(dev);
+ if (IS_ERR(button_info))
+ return PTR_ERR(button_info);
+ }
+
+ error = gpiod_count(dev, NULL);
+ if (error < 0) {
+ dev_dbg(dev, "no GPIO attached, ignoring...\n");
+ return -ENODEV;
+ }
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, priv);
+
+ for (i = 0; i < BUTTON_TYPES; i++) {
+ pd = soc_button_device_create(pdev, button_info, i == 0);
+ if (IS_ERR(pd)) {
+ error = PTR_ERR(pd);
+ if (error != -ENODEV) {
+ soc_button_remove(pdev);
+ return error;
+ }
+ continue;
+ }
+
+ priv->children[i] = pd;
+ }
+
+ if (!priv->children[0] && !priv->children[1])
+ return -ENODEV;
+
+ if (!device_data || !device_data->button_info)
+ devm_kfree(dev, button_info);
+
+ return 0;
+}
+
+/*
+ * Definition of buttons on the tablet. The ACPI index of each button
+ * is defined in section 2.8.7.2 of "Windows ACPI Design Guide for SoC
+ * Platforms"
+ */
+static const struct soc_button_info soc_button_PNP0C40[] = {
+ { "power", 0, EV_KEY, KEY_POWER, false, true, true },
+ { "home", 1, EV_KEY, KEY_LEFTMETA, false, true, true },
+ { "volume_up", 2, EV_KEY, KEY_VOLUMEUP, true, false, true },
+ { "volume_down", 3, EV_KEY, KEY_VOLUMEDOWN, true, false, true },
+ { "rotation_lock", 4, EV_KEY, KEY_ROTATE_LOCK_TOGGLE, false, false, true },
+ { }
+};
+
+static const struct soc_device_data soc_device_PNP0C40 = {
+ .button_info = soc_button_PNP0C40,
+};
+
+static const struct soc_button_info soc_button_INT33D3[] = {
+ { "tablet_mode", 0, EV_SW, SW_TABLET_MODE, false, false, false },
+ { }
+};
+
+static const struct soc_device_data soc_device_INT33D3 = {
+ .button_info = soc_button_INT33D3,
+};
+
+/*
+ * Button info for Microsoft Surface 3 (non pro), this is indentical to
+ * the PNP0C40 info except that the home button is active-high.
+ *
+ * The Surface 3 Pro also has a MSHW0028 ACPI device, but that uses a custom
+ * version of the drivers/platform/x86/intel/hid.c 5 button array ACPI API
+ * instead. A check() callback is not necessary though as the Surface 3 Pro
+ * MSHW0028 ACPI device's resource table does not contain any GPIOs.
+ */
+static const struct soc_button_info soc_button_MSHW0028[] = {
+ { "power", 0, EV_KEY, KEY_POWER, false, true, true },
+ { "home", 1, EV_KEY, KEY_LEFTMETA, false, true, false },
+ { "volume_up", 2, EV_KEY, KEY_VOLUMEUP, true, false, true },
+ { "volume_down", 3, EV_KEY, KEY_VOLUMEDOWN, true, false, true },
+ { }
+};
+
+static const struct soc_device_data soc_device_MSHW0028 = {
+ .button_info = soc_button_MSHW0028,
+};
+
+/*
+ * Special device check for Surface Book 2 and Surface Pro (2017).
+ * Both, the Surface Pro 4 (surfacepro3_button.c) and the above mentioned
+ * devices use MSHW0040 for power and volume buttons, however the way they
+ * have to be addressed differs. Make sure that we only load this drivers
+ * for the correct devices by checking the OEM Platform Revision provided by
+ * the _DSM method.
+ */
+#define MSHW0040_DSM_REVISION 0x01
+#define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision
+static const guid_t MSHW0040_DSM_UUID =
+ GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, 0x95, 0xed, 0xab, 0x16, 0x65,
+ 0x49, 0x80, 0x35);
+
+static int soc_device_check_MSHW0040(struct device *dev)
+{
+ acpi_handle handle = ACPI_HANDLE(dev);
+ union acpi_object *result;
+ u64 oem_platform_rev = 0; // valid revisions are nonzero
+
+ // get OEM platform revision
+ result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID,
+ MSHW0040_DSM_REVISION,
+ MSHW0040_DSM_GET_OMPR, NULL,
+ ACPI_TYPE_INTEGER);
+
+ if (result) {
+ oem_platform_rev = result->integer.value;
+ ACPI_FREE(result);
+ }
+
+ /*
+ * If the revision is zero here, the _DSM evaluation has failed. This
+ * indicates that we have a Pro 4 or Book 1 and this driver should not
+ * be used.
+ */
+ if (oem_platform_rev == 0)
+ return -ENODEV;
+
+ dev_dbg(dev, "OEM Platform Revision %llu\n", oem_platform_rev);
+
+ return 0;
+}
+
+/*
+ * Button infos for Microsoft Surface Book 2 and Surface Pro (2017).
+ * Obtained from DSDT/testing.
+ */
+static const struct soc_button_info soc_button_MSHW0040[] = {
+ { "power", 0, EV_KEY, KEY_POWER, false, true, true },
+ { "volume_up", 2, EV_KEY, KEY_VOLUMEUP, true, false, true },
+ { "volume_down", 4, EV_KEY, KEY_VOLUMEDOWN, true, false, true },
+ { }
+};
+
+static const struct soc_device_data soc_device_MSHW0040 = {
+ .button_info = soc_button_MSHW0040,
+ .check = soc_device_check_MSHW0040,
+};
+
+static const struct acpi_device_id soc_button_acpi_match[] = {
+ { "PNP0C40", (unsigned long)&soc_device_PNP0C40 },
+ { "INT33D3", (unsigned long)&soc_device_INT33D3 },
+ { "ID9001", (unsigned long)&soc_device_INT33D3 },
+ { "ACPI0011", 0 },
+
+ /* Microsoft Surface Devices (3th, 5th and 6th generation) */
+ { "MSHW0028", (unsigned long)&soc_device_MSHW0028 },
+ { "MSHW0040", (unsigned long)&soc_device_MSHW0040 },
+
+ { }
+};
+
+MODULE_DEVICE_TABLE(acpi, soc_button_acpi_match);
+
+static struct platform_driver soc_button_driver = {
+ .probe = soc_button_probe,
+ .remove = soc_button_remove,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .acpi_match_table = ACPI_PTR(soc_button_acpi_match),
+ },
+};
+module_platform_driver(soc_button_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/sparcspkr.c b/drivers/input/misc/sparcspkr.c
new file mode 100644
index 000000000..cdcb7737c
--- /dev/null
+++ b/drivers/input/misc/sparcspkr.c
@@ -0,0 +1,366 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for PC-speaker like devices found on various Sparc systems.
+ *
+ * Copyright (c) 2002 Vojtech Pavlik
+ * Copyright (c) 2002, 2006, 2008 David S. Miller (davem@davemloft.net)
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/of_device.h>
+#include <linux/slab.h>
+
+#include <asm/io.h>
+
+MODULE_AUTHOR("David S. Miller <davem@davemloft.net>");
+MODULE_DESCRIPTION("Sparc Speaker beeper driver");
+MODULE_LICENSE("GPL");
+
+struct grover_beep_info {
+ void __iomem *freq_regs;
+ void __iomem *enable_reg;
+};
+
+struct bbc_beep_info {
+ u32 clock_freq;
+ void __iomem *regs;
+};
+
+struct sparcspkr_state {
+ const char *name;
+ int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
+ spinlock_t lock;
+ struct input_dev *input_dev;
+ union {
+ struct grover_beep_info grover;
+ struct bbc_beep_info bbc;
+ } u;
+};
+
+static u32 bbc_count_to_reg(struct bbc_beep_info *info, unsigned int count)
+{
+ u32 val, clock_freq = info->clock_freq;
+ int i;
+
+ if (!count)
+ return 0;
+
+ if (count <= clock_freq >> 20)
+ return 1 << 18;
+
+ if (count >= clock_freq >> 12)
+ return 1 << 10;
+
+ val = 1 << 18;
+ for (i = 19; i >= 11; i--) {
+ val >>= 1;
+ if (count <= clock_freq >> i)
+ break;
+ }
+
+ return val;
+}
+
+static int bbc_spkr_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
+{
+ struct sparcspkr_state *state = dev_get_drvdata(dev->dev.parent);
+ struct bbc_beep_info *info = &state->u.bbc;
+ unsigned int count = 0;
+ unsigned long flags;
+
+ if (type != EV_SND)
+ return -1;
+
+ switch (code) {
+ case SND_BELL: if (value) value = 1000;
+ case SND_TONE: break;
+ default: return -1;
+ }
+
+ if (value > 20 && value < 32767)
+ count = 1193182 / value;
+
+ count = bbc_count_to_reg(info, count);
+
+ spin_lock_irqsave(&state->lock, flags);
+
+ if (count) {
+ sbus_writeb(0x01, info->regs + 0);
+ sbus_writeb(0x00, info->regs + 2);
+ sbus_writeb((count >> 16) & 0xff, info->regs + 3);
+ sbus_writeb((count >> 8) & 0xff, info->regs + 4);
+ sbus_writeb(0x00, info->regs + 5);
+ } else {
+ sbus_writeb(0x00, info->regs + 0);
+ }
+
+ spin_unlock_irqrestore(&state->lock, flags);
+
+ return 0;
+}
+
+static int grover_spkr_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
+{
+ struct sparcspkr_state *state = dev_get_drvdata(dev->dev.parent);
+ struct grover_beep_info *info = &state->u.grover;
+ unsigned int count = 0;
+ unsigned long flags;
+
+ if (type != EV_SND)
+ return -1;
+
+ switch (code) {
+ case SND_BELL: if (value) value = 1000;
+ case SND_TONE: break;
+ default: return -1;
+ }
+
+ if (value > 20 && value < 32767)
+ count = 1193182 / value;
+
+ spin_lock_irqsave(&state->lock, flags);
+
+ if (count) {
+ /* enable counter 2 */
+ sbus_writeb(sbus_readb(info->enable_reg) | 3, info->enable_reg);
+ /* set command for counter 2, 2 byte write */
+ sbus_writeb(0xB6, info->freq_regs + 1);
+ /* select desired HZ */
+ sbus_writeb(count & 0xff, info->freq_regs + 0);
+ sbus_writeb((count >> 8) & 0xff, info->freq_regs + 0);
+ } else {
+ /* disable counter 2 */
+ sbus_writeb(sbus_readb(info->enable_reg) & 0xFC, info->enable_reg);
+ }
+
+ spin_unlock_irqrestore(&state->lock, flags);
+
+ return 0;
+}
+
+static int sparcspkr_probe(struct device *dev)
+{
+ struct sparcspkr_state *state = dev_get_drvdata(dev);
+ struct input_dev *input_dev;
+ int error;
+
+ input_dev = input_allocate_device();
+ if (!input_dev)
+ return -ENOMEM;
+
+ input_dev->name = state->name;
+ input_dev->phys = "sparc/input0";
+ input_dev->id.bustype = BUS_ISA;
+ input_dev->id.vendor = 0x001f;
+ input_dev->id.product = 0x0001;
+ input_dev->id.version = 0x0100;
+ input_dev->dev.parent = dev;
+
+ input_dev->evbit[0] = BIT_MASK(EV_SND);
+ input_dev->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE);
+
+ input_dev->event = state->event;
+
+ error = input_register_device(input_dev);
+ if (error) {
+ input_free_device(input_dev);
+ return error;
+ }
+
+ state->input_dev = input_dev;
+
+ return 0;
+}
+
+static void sparcspkr_shutdown(struct platform_device *dev)
+{
+ struct sparcspkr_state *state = platform_get_drvdata(dev);
+ struct input_dev *input_dev = state->input_dev;
+
+ /* turn off the speaker */
+ state->event(input_dev, EV_SND, SND_BELL, 0);
+}
+
+static int bbc_beep_probe(struct platform_device *op)
+{
+ struct sparcspkr_state *state;
+ struct bbc_beep_info *info;
+ struct device_node *dp;
+ int err = -ENOMEM;
+
+ state = kzalloc(sizeof(*state), GFP_KERNEL);
+ if (!state)
+ goto out_err;
+
+ state->name = "Sparc BBC Speaker";
+ state->event = bbc_spkr_event;
+ spin_lock_init(&state->lock);
+
+ dp = of_find_node_by_path("/");
+ err = -ENODEV;
+ if (!dp)
+ goto out_free;
+
+ info = &state->u.bbc;
+ info->clock_freq = of_getintprop_default(dp, "clock-frequency", 0);
+ of_node_put(dp);
+ if (!info->clock_freq)
+ goto out_free;
+
+ info->regs = of_ioremap(&op->resource[0], 0, 6, "bbc beep");
+ if (!info->regs)
+ goto out_free;
+
+ platform_set_drvdata(op, state);
+
+ err = sparcspkr_probe(&op->dev);
+ if (err)
+ goto out_clear_drvdata;
+
+ return 0;
+
+out_clear_drvdata:
+ of_iounmap(&op->resource[0], info->regs, 6);
+
+out_free:
+ kfree(state);
+out_err:
+ return err;
+}
+
+static int bbc_remove(struct platform_device *op)
+{
+ struct sparcspkr_state *state = platform_get_drvdata(op);
+ struct input_dev *input_dev = state->input_dev;
+ struct bbc_beep_info *info = &state->u.bbc;
+
+ /* turn off the speaker */
+ state->event(input_dev, EV_SND, SND_BELL, 0);
+
+ input_unregister_device(input_dev);
+
+ of_iounmap(&op->resource[0], info->regs, 6);
+
+ kfree(state);
+
+ return 0;
+}
+
+static const struct of_device_id bbc_beep_match[] = {
+ {
+ .name = "beep",
+ .compatible = "SUNW,bbc-beep",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, bbc_beep_match);
+
+static struct platform_driver bbc_beep_driver = {
+ .driver = {
+ .name = "bbcbeep",
+ .of_match_table = bbc_beep_match,
+ },
+ .probe = bbc_beep_probe,
+ .remove = bbc_remove,
+ .shutdown = sparcspkr_shutdown,
+};
+
+static int grover_beep_probe(struct platform_device *op)
+{
+ struct sparcspkr_state *state;
+ struct grover_beep_info *info;
+ int err = -ENOMEM;
+
+ state = kzalloc(sizeof(*state), GFP_KERNEL);
+ if (!state)
+ goto out_err;
+
+ state->name = "Sparc Grover Speaker";
+ state->event = grover_spkr_event;
+ spin_lock_init(&state->lock);
+
+ info = &state->u.grover;
+ info->freq_regs = of_ioremap(&op->resource[2], 0, 2, "grover beep freq");
+ if (!info->freq_regs)
+ goto out_free;
+
+ info->enable_reg = of_ioremap(&op->resource[3], 0, 1, "grover beep enable");
+ if (!info->enable_reg)
+ goto out_unmap_freq_regs;
+
+ platform_set_drvdata(op, state);
+
+ err = sparcspkr_probe(&op->dev);
+ if (err)
+ goto out_clear_drvdata;
+
+ return 0;
+
+out_clear_drvdata:
+ of_iounmap(&op->resource[3], info->enable_reg, 1);
+
+out_unmap_freq_regs:
+ of_iounmap(&op->resource[2], info->freq_regs, 2);
+out_free:
+ kfree(state);
+out_err:
+ return err;
+}
+
+static int grover_remove(struct platform_device *op)
+{
+ struct sparcspkr_state *state = platform_get_drvdata(op);
+ struct grover_beep_info *info = &state->u.grover;
+ struct input_dev *input_dev = state->input_dev;
+
+ /* turn off the speaker */
+ state->event(input_dev, EV_SND, SND_BELL, 0);
+
+ input_unregister_device(input_dev);
+
+ of_iounmap(&op->resource[3], info->enable_reg, 1);
+ of_iounmap(&op->resource[2], info->freq_regs, 2);
+
+ kfree(state);
+
+ return 0;
+}
+
+static const struct of_device_id grover_beep_match[] = {
+ {
+ .name = "beep",
+ .compatible = "SUNW,smbus-beep",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, grover_beep_match);
+
+static struct platform_driver grover_beep_driver = {
+ .driver = {
+ .name = "groverbeep",
+ .of_match_table = grover_beep_match,
+ },
+ .probe = grover_beep_probe,
+ .remove = grover_remove,
+ .shutdown = sparcspkr_shutdown,
+};
+
+static struct platform_driver * const drivers[] = {
+ &bbc_beep_driver,
+ &grover_beep_driver,
+};
+
+static int __init sparcspkr_init(void)
+{
+ return platform_register_drivers(drivers, ARRAY_SIZE(drivers));
+}
+
+static void __exit sparcspkr_exit(void)
+{
+ platform_unregister_drivers(drivers, ARRAY_SIZE(drivers));
+}
+
+module_init(sparcspkr_init);
+module_exit(sparcspkr_exit);
diff --git a/drivers/input/misc/stpmic1_onkey.c b/drivers/input/misc/stpmic1_onkey.c
new file mode 100644
index 000000000..d8dc2f2f8
--- /dev/null
+++ b/drivers/input/misc/stpmic1_onkey.c
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) STMicroelectronics 2018
+// Author: Pascal Paillet <p.paillet@st.com> for STMicroelectronics.
+
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/stpmic1.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+/**
+ * struct stpmic1_onkey - OnKey data
+ * @input_dev: pointer to input device
+ * @irq_falling: irq that we are hooked on to
+ * @irq_rising: irq that we are hooked on to
+ */
+struct stpmic1_onkey {
+ struct input_dev *input_dev;
+ int irq_falling;
+ int irq_rising;
+};
+
+static irqreturn_t onkey_falling_irq(int irq, void *ponkey)
+{
+ struct stpmic1_onkey *onkey = ponkey;
+ struct input_dev *input_dev = onkey->input_dev;
+
+ input_report_key(input_dev, KEY_POWER, 1);
+ pm_wakeup_event(input_dev->dev.parent, 0);
+ input_sync(input_dev);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t onkey_rising_irq(int irq, void *ponkey)
+{
+ struct stpmic1_onkey *onkey = ponkey;
+ struct input_dev *input_dev = onkey->input_dev;
+
+ input_report_key(input_dev, KEY_POWER, 0);
+ pm_wakeup_event(input_dev->dev.parent, 0);
+ input_sync(input_dev);
+
+ return IRQ_HANDLED;
+}
+
+static int stpmic1_onkey_probe(struct platform_device *pdev)
+{
+ struct stpmic1 *pmic = dev_get_drvdata(pdev->dev.parent);
+ struct device *dev = &pdev->dev;
+ struct input_dev *input_dev;
+ struct stpmic1_onkey *onkey;
+ unsigned int val, reg = 0;
+ int error;
+
+ onkey = devm_kzalloc(dev, sizeof(*onkey), GFP_KERNEL);
+ if (!onkey)
+ return -ENOMEM;
+
+ onkey->irq_falling = platform_get_irq_byname(pdev, "onkey-falling");
+ if (onkey->irq_falling < 0)
+ return onkey->irq_falling;
+
+ onkey->irq_rising = platform_get_irq_byname(pdev, "onkey-rising");
+ if (onkey->irq_rising < 0)
+ return onkey->irq_rising;
+
+ if (!device_property_read_u32(dev, "power-off-time-sec", &val)) {
+ if (val > 0 && val <= 16) {
+ dev_dbg(dev, "power-off-time=%d seconds\n", val);
+ reg |= PONKEY_PWR_OFF;
+ reg |= ((16 - val) & PONKEY_TURNOFF_TIMER_MASK);
+ } else {
+ dev_err(dev, "power-off-time-sec out of range\n");
+ return -EINVAL;
+ }
+ }
+
+ if (device_property_present(dev, "st,onkey-clear-cc-flag"))
+ reg |= PONKEY_CC_FLAG_CLEAR;
+
+ error = regmap_update_bits(pmic->regmap, PKEY_TURNOFF_CR,
+ PONKEY_TURNOFF_MASK, reg);
+ if (error) {
+ dev_err(dev, "PKEY_TURNOFF_CR write failed: %d\n", error);
+ return error;
+ }
+
+ if (device_property_present(dev, "st,onkey-pu-inactive")) {
+ error = regmap_update_bits(pmic->regmap, PADS_PULL_CR,
+ PONKEY_PU_INACTIVE,
+ PONKEY_PU_INACTIVE);
+ if (error) {
+ dev_err(dev, "ONKEY Pads configuration failed: %d\n",
+ error);
+ return error;
+ }
+ }
+
+ input_dev = devm_input_allocate_device(dev);
+ if (!input_dev) {
+ dev_err(dev, "Can't allocate Pwr Onkey Input Device\n");
+ return -ENOMEM;
+ }
+
+ input_dev->name = "pmic_onkey";
+ input_dev->phys = "pmic_onkey/input0";
+
+ input_set_capability(input_dev, EV_KEY, KEY_POWER);
+
+ onkey->input_dev = input_dev;
+
+ /* interrupt is nested in a thread */
+ error = devm_request_threaded_irq(dev, onkey->irq_falling, NULL,
+ onkey_falling_irq, IRQF_ONESHOT,
+ dev_name(dev), onkey);
+ if (error) {
+ dev_err(dev, "Can't get IRQ Onkey Falling: %d\n", error);
+ return error;
+ }
+
+ error = devm_request_threaded_irq(dev, onkey->irq_rising, NULL,
+ onkey_rising_irq, IRQF_ONESHOT,
+ dev_name(dev), onkey);
+ if (error) {
+ dev_err(dev, "Can't get IRQ Onkey Rising: %d\n", error);
+ return error;
+ }
+
+ error = input_register_device(input_dev);
+ if (error) {
+ dev_err(dev, "Can't register power button: %d\n", error);
+ return error;
+ }
+
+ platform_set_drvdata(pdev, onkey);
+ device_init_wakeup(dev, true);
+
+ return 0;
+}
+
+static int __maybe_unused stpmic1_onkey_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct stpmic1_onkey *onkey = platform_get_drvdata(pdev);
+
+ if (device_may_wakeup(dev)) {
+ enable_irq_wake(onkey->irq_falling);
+ enable_irq_wake(onkey->irq_rising);
+ }
+ return 0;
+}
+
+static int __maybe_unused stpmic1_onkey_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct stpmic1_onkey *onkey = platform_get_drvdata(pdev);
+
+ if (device_may_wakeup(dev)) {
+ disable_irq_wake(onkey->irq_falling);
+ disable_irq_wake(onkey->irq_rising);
+ }
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(stpmic1_onkey_pm,
+ stpmic1_onkey_suspend,
+ stpmic1_onkey_resume);
+
+static const struct of_device_id of_stpmic1_onkey_match[] = {
+ { .compatible = "st,stpmic1-onkey" },
+ { },
+};
+
+MODULE_DEVICE_TABLE(of, of_stpmic1_onkey_match);
+
+static struct platform_driver stpmic1_onkey_driver = {
+ .probe = stpmic1_onkey_probe,
+ .driver = {
+ .name = "stpmic1_onkey",
+ .of_match_table = of_match_ptr(of_stpmic1_onkey_match),
+ .pm = &stpmic1_onkey_pm,
+ },
+};
+module_platform_driver(stpmic1_onkey_driver);
+
+MODULE_DESCRIPTION("Onkey driver for STPMIC1");
+MODULE_AUTHOR("Pascal Paillet <p.paillet@st.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/misc/tps65218-pwrbutton.c b/drivers/input/misc/tps65218-pwrbutton.c
new file mode 100644
index 000000000..fc450fce0
--- /dev/null
+++ b/drivers/input/misc/tps65218-pwrbutton.c
@@ -0,0 +1,160 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Texas Instruments' TPS65217 and TPS65218 Power Button Input Driver
+ *
+ * Copyright (C) 2014 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Felipe Balbi <balbi@ti.com>
+ * Author: Marcin Niestroj <m.niestroj@grinn-global.com>
+ */
+
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mfd/tps65217.h>
+#include <linux/mfd/tps65218.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+struct tps6521x_data {
+ unsigned int reg_status;
+ unsigned int pb_mask;
+ const char *name;
+};
+
+static const struct tps6521x_data tps65217_data = {
+ .reg_status = TPS65217_REG_STATUS,
+ .pb_mask = TPS65217_STATUS_PB,
+ .name = "tps65217_pwrbutton",
+};
+
+static const struct tps6521x_data tps65218_data = {
+ .reg_status = TPS65218_REG_STATUS,
+ .pb_mask = TPS65218_STATUS_PB_STATE,
+ .name = "tps65218_pwrbutton",
+};
+
+struct tps6521x_pwrbutton {
+ struct device *dev;
+ struct regmap *regmap;
+ struct input_dev *idev;
+ const struct tps6521x_data *data;
+ char phys[32];
+};
+
+static const struct of_device_id of_tps6521x_pb_match[] = {
+ { .compatible = "ti,tps65217-pwrbutton", .data = &tps65217_data },
+ { .compatible = "ti,tps65218-pwrbutton", .data = &tps65218_data },
+ { },
+};
+MODULE_DEVICE_TABLE(of, of_tps6521x_pb_match);
+
+static irqreturn_t tps6521x_pb_irq(int irq, void *_pwr)
+{
+ struct tps6521x_pwrbutton *pwr = _pwr;
+ const struct tps6521x_data *tps_data = pwr->data;
+ unsigned int reg;
+ int error;
+
+ error = regmap_read(pwr->regmap, tps_data->reg_status, &reg);
+ if (error) {
+ dev_err(pwr->dev, "can't read register: %d\n", error);
+ goto out;
+ }
+
+ if (reg & tps_data->pb_mask) {
+ input_report_key(pwr->idev, KEY_POWER, 1);
+ pm_wakeup_event(pwr->dev, 0);
+ } else {
+ input_report_key(pwr->idev, KEY_POWER, 0);
+ }
+
+ input_sync(pwr->idev);
+
+out:
+ return IRQ_HANDLED;
+}
+
+static int tps6521x_pb_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct tps6521x_pwrbutton *pwr;
+ struct input_dev *idev;
+ const struct of_device_id *match;
+ int error;
+ int irq;
+
+ match = of_match_node(of_tps6521x_pb_match, dev->of_node);
+ if (!match)
+ return -ENXIO;
+
+ pwr = devm_kzalloc(dev, sizeof(*pwr), GFP_KERNEL);
+ if (!pwr)
+ return -ENOMEM;
+
+ pwr->data = match->data;
+
+ idev = devm_input_allocate_device(dev);
+ if (!idev)
+ return -ENOMEM;
+
+ idev->name = pwr->data->name;
+ snprintf(pwr->phys, sizeof(pwr->phys), "%s/input0",
+ pwr->data->name);
+ idev->phys = pwr->phys;
+ idev->dev.parent = dev;
+ idev->id.bustype = BUS_I2C;
+
+ input_set_capability(idev, EV_KEY, KEY_POWER);
+
+ pwr->regmap = dev_get_regmap(dev->parent, NULL);
+ pwr->dev = dev;
+ pwr->idev = idev;
+ device_init_wakeup(dev, true);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return -EINVAL;
+
+ error = devm_request_threaded_irq(dev, irq, NULL, tps6521x_pb_irq,
+ IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT,
+ pwr->data->name, pwr);
+ if (error) {
+ dev_err(dev, "failed to request IRQ #%d: %d\n", irq, error);
+ return error;
+ }
+
+ error= input_register_device(idev);
+ if (error) {
+ dev_err(dev, "Can't register power button: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static const struct platform_device_id tps6521x_pwrbtn_id_table[] = {
+ { "tps65218-pwrbutton", },
+ { "tps65217-pwrbutton", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(platform, tps6521x_pwrbtn_id_table);
+
+static struct platform_driver tps6521x_pb_driver = {
+ .probe = tps6521x_pb_probe,
+ .driver = {
+ .name = "tps6521x_pwrbutton",
+ .of_match_table = of_tps6521x_pb_match,
+ },
+ .id_table = tps6521x_pwrbtn_id_table,
+};
+module_platform_driver(tps6521x_pb_driver);
+
+MODULE_DESCRIPTION("TPS6521X Power Button");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Felipe Balbi <balbi@ti.com>");
diff --git a/drivers/input/misc/twl4030-pwrbutton.c b/drivers/input/misc/twl4030-pwrbutton.c
new file mode 100644
index 000000000..e3ee0638f
--- /dev/null
+++ b/drivers/input/misc/twl4030-pwrbutton.c
@@ -0,0 +1,115 @@
+/**
+ * twl4030-pwrbutton.c - TWL4030 Power Button Input Driver
+ *
+ * Copyright (C) 2008-2009 Nokia Corporation
+ *
+ * Written by Peter De Schrijver <peter.de-schrijver@nokia.com>
+ * Several fixes by Felipe Balbi <felipe.balbi@nokia.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License. See the file "COPYING" in the main directory of this
+ * archive for more details.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/twl.h>
+
+#define PWR_PWRON_IRQ (1 << 0)
+
+#define STS_HW_CONDITIONS 0xf
+
+static irqreturn_t powerbutton_irq(int irq, void *_pwr)
+{
+ struct input_dev *pwr = _pwr;
+ int err;
+ u8 value;
+
+ err = twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &value, STS_HW_CONDITIONS);
+ if (!err) {
+ pm_wakeup_event(pwr->dev.parent, 0);
+ input_report_key(pwr, KEY_POWER, value & PWR_PWRON_IRQ);
+ input_sync(pwr);
+ } else {
+ dev_err(pwr->dev.parent, "twl4030: i2c error %d while reading"
+ " TWL4030 PM_MASTER STS_HW_CONDITIONS register\n", err);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int twl4030_pwrbutton_probe(struct platform_device *pdev)
+{
+ struct input_dev *pwr;
+ int irq = platform_get_irq(pdev, 0);
+ int err;
+
+ pwr = devm_input_allocate_device(&pdev->dev);
+ if (!pwr) {
+ dev_err(&pdev->dev, "Can't allocate power button\n");
+ return -ENOMEM;
+ }
+
+ input_set_capability(pwr, EV_KEY, KEY_POWER);
+ pwr->name = "twl4030_pwrbutton";
+ pwr->phys = "twl4030_pwrbutton/input0";
+ pwr->dev.parent = &pdev->dev;
+
+ err = devm_request_threaded_irq(&pdev->dev, irq, NULL, powerbutton_irq,
+ IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING |
+ IRQF_ONESHOT,
+ "twl4030_pwrbutton", pwr);
+ if (err < 0) {
+ dev_err(&pdev->dev, "Can't get IRQ for pwrbutton: %d\n", err);
+ return err;
+ }
+
+ err = input_register_device(pwr);
+ if (err) {
+ dev_err(&pdev->dev, "Can't register power button: %d\n", err);
+ return err;
+ }
+
+ device_init_wakeup(&pdev->dev, true);
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id twl4030_pwrbutton_dt_match_table[] = {
+ { .compatible = "ti,twl4030-pwrbutton" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, twl4030_pwrbutton_dt_match_table);
+#endif
+
+static struct platform_driver twl4030_pwrbutton_driver = {
+ .probe = twl4030_pwrbutton_probe,
+ .driver = {
+ .name = "twl4030_pwrbutton",
+ .of_match_table = of_match_ptr(twl4030_pwrbutton_dt_match_table),
+ },
+};
+module_platform_driver(twl4030_pwrbutton_driver);
+
+MODULE_ALIAS("platform:twl4030_pwrbutton");
+MODULE_DESCRIPTION("Triton2 Power Button");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Peter De Schrijver <peter.de-schrijver@nokia.com>");
+MODULE_AUTHOR("Felipe Balbi <felipe.balbi@nokia.com>");
+
diff --git a/drivers/input/misc/twl4030-vibra.c b/drivers/input/misc/twl4030-vibra.c
new file mode 100644
index 000000000..5619996da
--- /dev/null
+++ b/drivers/input/misc/twl4030-vibra.c
@@ -0,0 +1,245 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * twl4030-vibra.c - TWL4030 Vibrator driver
+ *
+ * Copyright (C) 2008-2010 Nokia Corporation
+ *
+ * Written by Henrik Saari <henrik.saari@nokia.com>
+ * Updates by Felipe Balbi <felipe.balbi@nokia.com>
+ * Input by Jari Vanhala <ext-jari.vanhala@nokia.com>
+ */
+
+#include <linux/module.h>
+#include <linux/jiffies.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/workqueue.h>
+#include <linux/mfd/twl.h>
+#include <linux/mfd/twl4030-audio.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+
+/* MODULE ID2 */
+#define LEDEN 0x00
+
+/* ForceFeedback */
+#define EFFECT_DIR_180_DEG 0x8000 /* range is 0 - 0xFFFF */
+
+struct vibra_info {
+ struct device *dev;
+ struct input_dev *input_dev;
+
+ struct work_struct play_work;
+
+ bool enabled;
+ int speed;
+ int direction;
+
+ bool coexist;
+};
+
+static void vibra_disable_leds(void)
+{
+ u8 reg;
+
+ /* Disable LEDA & LEDB, cannot be used with vibra (PWM) */
+ twl_i2c_read_u8(TWL4030_MODULE_LED, &reg, LEDEN);
+ reg &= ~0x03;
+ twl_i2c_write_u8(TWL4030_MODULE_LED, LEDEN, reg);
+}
+
+/* Powers H-Bridge and enables audio clk */
+static void vibra_enable(struct vibra_info *info)
+{
+ u8 reg;
+
+ twl4030_audio_enable_resource(TWL4030_AUDIO_RES_POWER);
+
+ /* turn H-Bridge on */
+ twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE,
+ &reg, TWL4030_REG_VIBRA_CTL);
+ twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
+ (reg | TWL4030_VIBRA_EN), TWL4030_REG_VIBRA_CTL);
+
+ twl4030_audio_enable_resource(TWL4030_AUDIO_RES_APLL);
+
+ info->enabled = true;
+}
+
+static void vibra_disable(struct vibra_info *info)
+{
+ u8 reg;
+
+ /* Power down H-Bridge */
+ twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE,
+ &reg, TWL4030_REG_VIBRA_CTL);
+ twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
+ (reg & ~TWL4030_VIBRA_EN), TWL4030_REG_VIBRA_CTL);
+
+ twl4030_audio_disable_resource(TWL4030_AUDIO_RES_APLL);
+ twl4030_audio_disable_resource(TWL4030_AUDIO_RES_POWER);
+
+ info->enabled = false;
+}
+
+static void vibra_play_work(struct work_struct *work)
+{
+ struct vibra_info *info = container_of(work,
+ struct vibra_info, play_work);
+ int dir;
+ int pwm;
+ u8 reg;
+
+ dir = info->direction;
+ pwm = info->speed;
+
+ twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE,
+ &reg, TWL4030_REG_VIBRA_CTL);
+ if (pwm && (!info->coexist || !(reg & TWL4030_VIBRA_SEL))) {
+
+ if (!info->enabled)
+ vibra_enable(info);
+
+ /* set vibra rotation direction */
+ twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE,
+ &reg, TWL4030_REG_VIBRA_CTL);
+ reg = (dir) ? (reg | TWL4030_VIBRA_DIR) :
+ (reg & ~TWL4030_VIBRA_DIR);
+ twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
+ reg, TWL4030_REG_VIBRA_CTL);
+
+ /* set PWM, 1 = max, 255 = min */
+ twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
+ 256 - pwm, TWL4030_REG_VIBRA_SET);
+ } else {
+ if (info->enabled)
+ vibra_disable(info);
+ }
+}
+
+/*** Input/ForceFeedback ***/
+
+static int vibra_play(struct input_dev *input, void *data,
+ struct ff_effect *effect)
+{
+ struct vibra_info *info = input_get_drvdata(input);
+
+ info->speed = effect->u.rumble.strong_magnitude >> 8;
+ if (!info->speed)
+ info->speed = effect->u.rumble.weak_magnitude >> 9;
+ info->direction = effect->direction < EFFECT_DIR_180_DEG ? 0 : 1;
+ schedule_work(&info->play_work);
+ return 0;
+}
+
+static void twl4030_vibra_close(struct input_dev *input)
+{
+ struct vibra_info *info = input_get_drvdata(input);
+
+ cancel_work_sync(&info->play_work);
+
+ if (info->enabled)
+ vibra_disable(info);
+}
+
+/*** Module ***/
+static int __maybe_unused twl4030_vibra_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct vibra_info *info = platform_get_drvdata(pdev);
+
+ if (info->enabled)
+ vibra_disable(info);
+
+ return 0;
+}
+
+static int __maybe_unused twl4030_vibra_resume(struct device *dev)
+{
+ vibra_disable_leds();
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(twl4030_vibra_pm_ops,
+ twl4030_vibra_suspend, twl4030_vibra_resume);
+
+static bool twl4030_vibra_check_coexist(struct device_node *parent)
+{
+ struct device_node *node;
+
+ node = of_get_child_by_name(parent, "codec");
+ if (node) {
+ of_node_put(node);
+ return true;
+ }
+
+ return false;
+}
+
+static int twl4030_vibra_probe(struct platform_device *pdev)
+{
+ struct device_node *twl4030_core_node = pdev->dev.parent->of_node;
+ struct vibra_info *info;
+ int ret;
+
+ if (!twl4030_core_node) {
+ dev_dbg(&pdev->dev, "twl4030 OF node is missing\n");
+ return -EINVAL;
+ }
+
+ info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ info->dev = &pdev->dev;
+ info->coexist = twl4030_vibra_check_coexist(twl4030_core_node);
+ INIT_WORK(&info->play_work, vibra_play_work);
+
+ info->input_dev = devm_input_allocate_device(&pdev->dev);
+ if (info->input_dev == NULL) {
+ dev_err(&pdev->dev, "couldn't allocate input device\n");
+ return -ENOMEM;
+ }
+
+ input_set_drvdata(info->input_dev, info);
+
+ info->input_dev->name = "twl4030:vibrator";
+ info->input_dev->id.version = 1;
+ info->input_dev->close = twl4030_vibra_close;
+ __set_bit(FF_RUMBLE, info->input_dev->ffbit);
+
+ ret = input_ff_create_memless(info->input_dev, NULL, vibra_play);
+ if (ret < 0) {
+ dev_dbg(&pdev->dev, "couldn't register vibrator to FF\n");
+ return ret;
+ }
+
+ ret = input_register_device(info->input_dev);
+ if (ret < 0) {
+ dev_dbg(&pdev->dev, "couldn't register input device\n");
+ goto err_iff;
+ }
+
+ vibra_disable_leds();
+
+ platform_set_drvdata(pdev, info);
+ return 0;
+
+err_iff:
+ input_ff_destroy(info->input_dev);
+ return ret;
+}
+
+static struct platform_driver twl4030_vibra_driver = {
+ .probe = twl4030_vibra_probe,
+ .driver = {
+ .name = "twl4030-vibra",
+ .pm = &twl4030_vibra_pm_ops,
+ },
+};
+module_platform_driver(twl4030_vibra_driver);
+
+MODULE_ALIAS("platform:twl4030-vibra");
+MODULE_DESCRIPTION("TWL4030 Vibra driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Nokia Corporation");
diff --git a/drivers/input/misc/twl6040-vibra.c b/drivers/input/misc/twl6040-vibra.c
new file mode 100644
index 000000000..bf6644927
--- /dev/null
+++ b/drivers/input/misc/twl6040-vibra.c
@@ -0,0 +1,366 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * twl6040-vibra.c - TWL6040 Vibrator driver
+ *
+ * Author: Jorge Eduardo Candelaria <jorge.candelaria@ti.com>
+ * Author: Misael Lopez Cruz <misael.lopez@ti.com>
+ *
+ * Copyright: (C) 2011 Texas Instruments, Inc.
+ *
+ * Based on twl4030-vibra.c by Henrik Saari <henrik.saari@nokia.com>
+ * Felipe Balbi <felipe.balbi@nokia.com>
+ * Jari Vanhala <ext-javi.vanhala@nokia.com>
+ */
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/workqueue.h>
+#include <linux/input.h>
+#include <linux/mfd/twl6040.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/regulator/consumer.h>
+
+#define EFFECT_DIR_180_DEG 0x8000
+
+/* Recommended modulation index 85% */
+#define TWL6040_VIBRA_MOD 85
+
+#define TWL6040_NUM_SUPPLIES 2
+
+struct vibra_info {
+ struct device *dev;
+ struct input_dev *input_dev;
+ struct work_struct play_work;
+
+ int irq;
+
+ bool enabled;
+ int weak_speed;
+ int strong_speed;
+ int direction;
+
+ unsigned int vibldrv_res;
+ unsigned int vibrdrv_res;
+ unsigned int viblmotor_res;
+ unsigned int vibrmotor_res;
+
+ struct regulator_bulk_data supplies[TWL6040_NUM_SUPPLIES];
+
+ struct twl6040 *twl6040;
+};
+
+static irqreturn_t twl6040_vib_irq_handler(int irq, void *data)
+{
+ struct vibra_info *info = data;
+ struct twl6040 *twl6040 = info->twl6040;
+ u8 status;
+
+ status = twl6040_reg_read(twl6040, TWL6040_REG_STATUS);
+ if (status & TWL6040_VIBLOCDET) {
+ dev_warn(info->dev, "Left Vibrator overcurrent detected\n");
+ twl6040_clear_bits(twl6040, TWL6040_REG_VIBCTLL,
+ TWL6040_VIBENA);
+ }
+ if (status & TWL6040_VIBROCDET) {
+ dev_warn(info->dev, "Right Vibrator overcurrent detected\n");
+ twl6040_clear_bits(twl6040, TWL6040_REG_VIBCTLR,
+ TWL6040_VIBENA);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void twl6040_vibra_enable(struct vibra_info *info)
+{
+ struct twl6040 *twl6040 = info->twl6040;
+ int ret;
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(info->supplies), info->supplies);
+ if (ret) {
+ dev_err(info->dev, "failed to enable regulators %d\n", ret);
+ return;
+ }
+
+ twl6040_power(info->twl6040, 1);
+ if (twl6040_get_revid(twl6040) <= TWL6040_REV_ES1_1) {
+ /*
+ * ERRATA: Disable overcurrent protection for at least
+ * 3ms when enabling vibrator drivers to avoid false
+ * overcurrent detection
+ */
+ twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLL,
+ TWL6040_VIBENA | TWL6040_VIBCTRL);
+ twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLR,
+ TWL6040_VIBENA | TWL6040_VIBCTRL);
+ usleep_range(3000, 3500);
+ }
+
+ twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLL,
+ TWL6040_VIBENA);
+ twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLR,
+ TWL6040_VIBENA);
+
+ info->enabled = true;
+}
+
+static void twl6040_vibra_disable(struct vibra_info *info)
+{
+ struct twl6040 *twl6040 = info->twl6040;
+
+ twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLL, 0x00);
+ twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLR, 0x00);
+ twl6040_power(info->twl6040, 0);
+
+ regulator_bulk_disable(ARRAY_SIZE(info->supplies), info->supplies);
+
+ info->enabled = false;
+}
+
+static u8 twl6040_vibra_code(int vddvib, int vibdrv_res, int motor_res,
+ int speed, int direction)
+{
+ int vpk, max_code;
+ u8 vibdat;
+
+ /* output swing */
+ vpk = (vddvib * motor_res * TWL6040_VIBRA_MOD) /
+ (100 * (vibdrv_res + motor_res));
+
+ /* 50mV per VIBDAT code step */
+ max_code = vpk / 50;
+ if (max_code > TWL6040_VIBDAT_MAX)
+ max_code = TWL6040_VIBDAT_MAX;
+
+ /* scale speed to max allowed code */
+ vibdat = (u8)((speed * max_code) / USHRT_MAX);
+
+ /* 2's complement for direction > 180 degrees */
+ vibdat *= direction;
+
+ return vibdat;
+}
+
+static void twl6040_vibra_set_effect(struct vibra_info *info)
+{
+ struct twl6040 *twl6040 = info->twl6040;
+ u8 vibdatl, vibdatr;
+ int volt;
+
+ /* weak motor */
+ volt = regulator_get_voltage(info->supplies[0].consumer) / 1000;
+ vibdatl = twl6040_vibra_code(volt, info->vibldrv_res,
+ info->viblmotor_res,
+ info->weak_speed, info->direction);
+
+ /* strong motor */
+ volt = regulator_get_voltage(info->supplies[1].consumer) / 1000;
+ vibdatr = twl6040_vibra_code(volt, info->vibrdrv_res,
+ info->vibrmotor_res,
+ info->strong_speed, info->direction);
+
+ twl6040_reg_write(twl6040, TWL6040_REG_VIBDATL, vibdatl);
+ twl6040_reg_write(twl6040, TWL6040_REG_VIBDATR, vibdatr);
+}
+
+static void vibra_play_work(struct work_struct *work)
+{
+ struct vibra_info *info = container_of(work,
+ struct vibra_info, play_work);
+ int ret;
+
+ /* Do not allow effect, while the routing is set to use audio */
+ ret = twl6040_get_vibralr_status(info->twl6040);
+ if (ret & TWL6040_VIBSEL) {
+ dev_info(info->dev, "Vibra is configured for audio\n");
+ return;
+ }
+
+ if (info->weak_speed || info->strong_speed) {
+ if (!info->enabled)
+ twl6040_vibra_enable(info);
+
+ twl6040_vibra_set_effect(info);
+ } else if (info->enabled)
+ twl6040_vibra_disable(info);
+
+}
+
+static int vibra_play(struct input_dev *input, void *data,
+ struct ff_effect *effect)
+{
+ struct vibra_info *info = input_get_drvdata(input);
+
+ info->weak_speed = effect->u.rumble.weak_magnitude;
+ info->strong_speed = effect->u.rumble.strong_magnitude;
+ info->direction = effect->direction < EFFECT_DIR_180_DEG ? 1 : -1;
+
+ schedule_work(&info->play_work);
+
+ return 0;
+}
+
+static void twl6040_vibra_close(struct input_dev *input)
+{
+ struct vibra_info *info = input_get_drvdata(input);
+
+ cancel_work_sync(&info->play_work);
+
+ if (info->enabled)
+ twl6040_vibra_disable(info);
+}
+
+static int __maybe_unused twl6040_vibra_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct vibra_info *info = platform_get_drvdata(pdev);
+
+ cancel_work_sync(&info->play_work);
+
+ if (info->enabled)
+ twl6040_vibra_disable(info);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(twl6040_vibra_pm_ops, twl6040_vibra_suspend, NULL);
+
+static int twl6040_vibra_probe(struct platform_device *pdev)
+{
+ struct device *twl6040_core_dev = pdev->dev.parent;
+ struct device_node *twl6040_core_node;
+ struct vibra_info *info;
+ int vddvibl_uV = 0;
+ int vddvibr_uV = 0;
+ int error;
+
+ twl6040_core_node = of_get_child_by_name(twl6040_core_dev->of_node,
+ "vibra");
+ if (!twl6040_core_node) {
+ dev_err(&pdev->dev, "parent of node is missing?\n");
+ return -EINVAL;
+ }
+
+ info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ of_node_put(twl6040_core_node);
+ dev_err(&pdev->dev, "couldn't allocate memory\n");
+ return -ENOMEM;
+ }
+
+ info->dev = &pdev->dev;
+
+ info->twl6040 = dev_get_drvdata(pdev->dev.parent);
+
+ of_property_read_u32(twl6040_core_node, "ti,vibldrv-res",
+ &info->vibldrv_res);
+ of_property_read_u32(twl6040_core_node, "ti,vibrdrv-res",
+ &info->vibrdrv_res);
+ of_property_read_u32(twl6040_core_node, "ti,viblmotor-res",
+ &info->viblmotor_res);
+ of_property_read_u32(twl6040_core_node, "ti,vibrmotor-res",
+ &info->vibrmotor_res);
+ of_property_read_u32(twl6040_core_node, "ti,vddvibl-uV", &vddvibl_uV);
+ of_property_read_u32(twl6040_core_node, "ti,vddvibr-uV", &vddvibr_uV);
+
+ of_node_put(twl6040_core_node);
+
+ if ((!info->vibldrv_res && !info->viblmotor_res) ||
+ (!info->vibrdrv_res && !info->vibrmotor_res)) {
+ dev_err(info->dev, "invalid vibra driver/motor resistance\n");
+ return -EINVAL;
+ }
+
+ info->irq = platform_get_irq(pdev, 0);
+ if (info->irq < 0)
+ return -EINVAL;
+
+ error = devm_request_threaded_irq(&pdev->dev, info->irq, NULL,
+ twl6040_vib_irq_handler,
+ IRQF_ONESHOT,
+ "twl6040_irq_vib", info);
+ if (error) {
+ dev_err(info->dev, "VIB IRQ request failed: %d\n", error);
+ return error;
+ }
+
+ info->supplies[0].supply = "vddvibl";
+ info->supplies[1].supply = "vddvibr";
+ /*
+ * When booted with Device tree the regulators are attached to the
+ * parent device (twl6040 MFD core)
+ */
+ error = devm_regulator_bulk_get(twl6040_core_dev,
+ ARRAY_SIZE(info->supplies),
+ info->supplies);
+ if (error) {
+ dev_err(info->dev, "couldn't get regulators %d\n", error);
+ return error;
+ }
+
+ if (vddvibl_uV) {
+ error = regulator_set_voltage(info->supplies[0].consumer,
+ vddvibl_uV, vddvibl_uV);
+ if (error) {
+ dev_err(info->dev, "failed to set VDDVIBL volt %d\n",
+ error);
+ return error;
+ }
+ }
+
+ if (vddvibr_uV) {
+ error = regulator_set_voltage(info->supplies[1].consumer,
+ vddvibr_uV, vddvibr_uV);
+ if (error) {
+ dev_err(info->dev, "failed to set VDDVIBR volt %d\n",
+ error);
+ return error;
+ }
+ }
+
+ INIT_WORK(&info->play_work, vibra_play_work);
+
+ info->input_dev = devm_input_allocate_device(&pdev->dev);
+ if (!info->input_dev) {
+ dev_err(info->dev, "couldn't allocate input device\n");
+ return -ENOMEM;
+ }
+
+ input_set_drvdata(info->input_dev, info);
+
+ info->input_dev->name = "twl6040:vibrator";
+ info->input_dev->id.version = 1;
+ info->input_dev->close = twl6040_vibra_close;
+ __set_bit(FF_RUMBLE, info->input_dev->ffbit);
+
+ error = input_ff_create_memless(info->input_dev, NULL, vibra_play);
+ if (error) {
+ dev_err(info->dev, "couldn't register vibrator to FF\n");
+ return error;
+ }
+
+ error = input_register_device(info->input_dev);
+ if (error) {
+ dev_err(info->dev, "couldn't register input device\n");
+ return error;
+ }
+
+ platform_set_drvdata(pdev, info);
+
+ return 0;
+}
+
+static struct platform_driver twl6040_vibra_driver = {
+ .probe = twl6040_vibra_probe,
+ .driver = {
+ .name = "twl6040-vibra",
+ .pm = &twl6040_vibra_pm_ops,
+ },
+};
+module_platform_driver(twl6040_vibra_driver);
+
+MODULE_ALIAS("platform:twl6040-vibra");
+MODULE_DESCRIPTION("TWL6040 Vibra driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jorge Eduardo Candelaria <jorge.candelaria@ti.com>");
+MODULE_AUTHOR("Misael Lopez Cruz <misael.lopez@ti.com>");
diff --git a/drivers/input/misc/uinput.c b/drivers/input/misc/uinput.c
new file mode 100644
index 000000000..f2593133e
--- /dev/null
+++ b/drivers/input/misc/uinput.c
@@ -0,0 +1,1102 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * User level driver support for input subsystem
+ *
+ * Heavily based on evdev.c by Vojtech Pavlik
+ *
+ * Author: Aristeu Sergio Rozanski Filho <aris@cathedrallabs.org>
+ *
+ * Changes/Revisions:
+ * 0.4 01/09/2014 (Benjamin Tissoires <benjamin.tissoires@redhat.com>)
+ * - add UI_GET_SYSNAME ioctl
+ * 0.3 09/04/2006 (Anssi Hannula <anssi.hannula@gmail.com>)
+ * - updated ff support for the changes in kernel interface
+ * - added MODULE_VERSION
+ * 0.2 16/10/2004 (Micah Dowty <micah@navi.cx>)
+ * - added force feedback support
+ * - added UI_SET_PHYS
+ * 0.1 20/06/2002
+ * - first public version
+ */
+#include <uapi/linux/uinput.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/overflow.h>
+#include <linux/input/mt.h>
+#include "../input-compat.h"
+
+#define UINPUT_NAME "uinput"
+#define UINPUT_BUFFER_SIZE 16
+#define UINPUT_NUM_REQUESTS 16
+
+enum uinput_state { UIST_NEW_DEVICE, UIST_SETUP_COMPLETE, UIST_CREATED };
+
+struct uinput_request {
+ unsigned int id;
+ unsigned int code; /* UI_FF_UPLOAD, UI_FF_ERASE */
+
+ int retval;
+ struct completion done;
+
+ union {
+ unsigned int effect_id;
+ struct {
+ struct ff_effect *effect;
+ struct ff_effect *old;
+ } upload;
+ } u;
+};
+
+struct uinput_device {
+ struct input_dev *dev;
+ struct mutex mutex;
+ enum uinput_state state;
+ wait_queue_head_t waitq;
+ unsigned char ready;
+ unsigned char head;
+ unsigned char tail;
+ struct input_event buff[UINPUT_BUFFER_SIZE];
+ unsigned int ff_effects_max;
+
+ struct uinput_request *requests[UINPUT_NUM_REQUESTS];
+ wait_queue_head_t requests_waitq;
+ spinlock_t requests_lock;
+};
+
+static int uinput_dev_event(struct input_dev *dev,
+ unsigned int type, unsigned int code, int value)
+{
+ struct uinput_device *udev = input_get_drvdata(dev);
+ struct timespec64 ts;
+
+ ktime_get_ts64(&ts);
+
+ udev->buff[udev->head] = (struct input_event) {
+ .input_event_sec = ts.tv_sec,
+ .input_event_usec = ts.tv_nsec / NSEC_PER_USEC,
+ .type = type,
+ .code = code,
+ .value = value,
+ };
+
+ udev->head = (udev->head + 1) % UINPUT_BUFFER_SIZE;
+
+ wake_up_interruptible(&udev->waitq);
+
+ return 0;
+}
+
+/* Atomically allocate an ID for the given request. Returns 0 on success. */
+static bool uinput_request_alloc_id(struct uinput_device *udev,
+ struct uinput_request *request)
+{
+ unsigned int id;
+ bool reserved = false;
+
+ spin_lock(&udev->requests_lock);
+
+ for (id = 0; id < UINPUT_NUM_REQUESTS; id++) {
+ if (!udev->requests[id]) {
+ request->id = id;
+ udev->requests[id] = request;
+ reserved = true;
+ break;
+ }
+ }
+
+ spin_unlock(&udev->requests_lock);
+ return reserved;
+}
+
+static struct uinput_request *uinput_request_find(struct uinput_device *udev,
+ unsigned int id)
+{
+ /* Find an input request, by ID. Returns NULL if the ID isn't valid. */
+ if (id >= UINPUT_NUM_REQUESTS)
+ return NULL;
+
+ return udev->requests[id];
+}
+
+static int uinput_request_reserve_slot(struct uinput_device *udev,
+ struct uinput_request *request)
+{
+ /* Allocate slot. If none are available right away, wait. */
+ return wait_event_interruptible(udev->requests_waitq,
+ uinput_request_alloc_id(udev, request));
+}
+
+static void uinput_request_release_slot(struct uinput_device *udev,
+ unsigned int id)
+{
+ /* Mark slot as available */
+ spin_lock(&udev->requests_lock);
+ udev->requests[id] = NULL;
+ spin_unlock(&udev->requests_lock);
+
+ wake_up(&udev->requests_waitq);
+}
+
+static int uinput_request_send(struct uinput_device *udev,
+ struct uinput_request *request)
+{
+ int retval;
+
+ retval = mutex_lock_interruptible(&udev->mutex);
+ if (retval)
+ return retval;
+
+ if (udev->state != UIST_CREATED) {
+ retval = -ENODEV;
+ goto out;
+ }
+
+ init_completion(&request->done);
+
+ /*
+ * Tell our userspace application about this new request
+ * by queueing an input event.
+ */
+ uinput_dev_event(udev->dev, EV_UINPUT, request->code, request->id);
+
+ out:
+ mutex_unlock(&udev->mutex);
+ return retval;
+}
+
+static int uinput_request_submit(struct uinput_device *udev,
+ struct uinput_request *request)
+{
+ int retval;
+
+ retval = uinput_request_reserve_slot(udev, request);
+ if (retval)
+ return retval;
+
+ retval = uinput_request_send(udev, request);
+ if (retval)
+ goto out;
+
+ if (!wait_for_completion_timeout(&request->done, 30 * HZ)) {
+ retval = -ETIMEDOUT;
+ goto out;
+ }
+
+ retval = request->retval;
+
+ out:
+ uinput_request_release_slot(udev, request->id);
+ return retval;
+}
+
+/*
+ * Fail all outstanding requests so handlers don't wait for the userspace
+ * to finish processing them.
+ */
+static void uinput_flush_requests(struct uinput_device *udev)
+{
+ struct uinput_request *request;
+ int i;
+
+ spin_lock(&udev->requests_lock);
+
+ for (i = 0; i < UINPUT_NUM_REQUESTS; i++) {
+ request = udev->requests[i];
+ if (request) {
+ request->retval = -ENODEV;
+ complete(&request->done);
+ }
+ }
+
+ spin_unlock(&udev->requests_lock);
+}
+
+static void uinput_dev_set_gain(struct input_dev *dev, u16 gain)
+{
+ uinput_dev_event(dev, EV_FF, FF_GAIN, gain);
+}
+
+static void uinput_dev_set_autocenter(struct input_dev *dev, u16 magnitude)
+{
+ uinput_dev_event(dev, EV_FF, FF_AUTOCENTER, magnitude);
+}
+
+static int uinput_dev_playback(struct input_dev *dev, int effect_id, int value)
+{
+ return uinput_dev_event(dev, EV_FF, effect_id, value);
+}
+
+static int uinput_dev_upload_effect(struct input_dev *dev,
+ struct ff_effect *effect,
+ struct ff_effect *old)
+{
+ struct uinput_device *udev = input_get_drvdata(dev);
+ struct uinput_request request;
+
+ /*
+ * uinput driver does not currently support periodic effects with
+ * custom waveform since it does not have a way to pass buffer of
+ * samples (custom_data) to userspace. If ever there is a device
+ * supporting custom waveforms we would need to define an additional
+ * ioctl (UI_UPLOAD_SAMPLES) but for now we just bail out.
+ */
+ if (effect->type == FF_PERIODIC &&
+ effect->u.periodic.waveform == FF_CUSTOM)
+ return -EINVAL;
+
+ request.code = UI_FF_UPLOAD;
+ request.u.upload.effect = effect;
+ request.u.upload.old = old;
+
+ return uinput_request_submit(udev, &request);
+}
+
+static int uinput_dev_erase_effect(struct input_dev *dev, int effect_id)
+{
+ struct uinput_device *udev = input_get_drvdata(dev);
+ struct uinput_request request;
+
+ if (!test_bit(EV_FF, dev->evbit))
+ return -ENOSYS;
+
+ request.code = UI_FF_ERASE;
+ request.u.effect_id = effect_id;
+
+ return uinput_request_submit(udev, &request);
+}
+
+static int uinput_dev_flush(struct input_dev *dev, struct file *file)
+{
+ /*
+ * If we are called with file == NULL that means we are tearing
+ * down the device, and therefore we can not handle FF erase
+ * requests: either we are handling UI_DEV_DESTROY (and holding
+ * the udev->mutex), or the file descriptor is closed and there is
+ * nobody on the other side anymore.
+ */
+ return file ? input_ff_flush(dev, file) : 0;
+}
+
+static void uinput_destroy_device(struct uinput_device *udev)
+{
+ const char *name, *phys;
+ struct input_dev *dev = udev->dev;
+ enum uinput_state old_state = udev->state;
+
+ udev->state = UIST_NEW_DEVICE;
+
+ if (dev) {
+ name = dev->name;
+ phys = dev->phys;
+ if (old_state == UIST_CREATED) {
+ uinput_flush_requests(udev);
+ input_unregister_device(dev);
+ } else {
+ input_free_device(dev);
+ }
+ kfree(name);
+ kfree(phys);
+ udev->dev = NULL;
+ }
+}
+
+static int uinput_create_device(struct uinput_device *udev)
+{
+ struct input_dev *dev = udev->dev;
+ int error, nslot;
+
+ if (udev->state != UIST_SETUP_COMPLETE) {
+ printk(KERN_DEBUG "%s: write device info first\n", UINPUT_NAME);
+ return -EINVAL;
+ }
+
+ if (test_bit(EV_ABS, dev->evbit)) {
+ input_alloc_absinfo(dev);
+ if (!dev->absinfo) {
+ error = -EINVAL;
+ goto fail1;
+ }
+
+ if (test_bit(ABS_MT_SLOT, dev->absbit)) {
+ nslot = input_abs_get_max(dev, ABS_MT_SLOT) + 1;
+ error = input_mt_init_slots(dev, nslot, 0);
+ if (error)
+ goto fail1;
+ } else if (test_bit(ABS_MT_POSITION_X, dev->absbit)) {
+ input_set_events_per_packet(dev, 60);
+ }
+ }
+
+ if (test_bit(EV_FF, dev->evbit) && !udev->ff_effects_max) {
+ printk(KERN_DEBUG "%s: ff_effects_max should be non-zero when FF_BIT is set\n",
+ UINPUT_NAME);
+ error = -EINVAL;
+ goto fail1;
+ }
+
+ if (udev->ff_effects_max) {
+ error = input_ff_create(dev, udev->ff_effects_max);
+ if (error)
+ goto fail1;
+
+ dev->ff->upload = uinput_dev_upload_effect;
+ dev->ff->erase = uinput_dev_erase_effect;
+ dev->ff->playback = uinput_dev_playback;
+ dev->ff->set_gain = uinput_dev_set_gain;
+ dev->ff->set_autocenter = uinput_dev_set_autocenter;
+ /*
+ * The standard input_ff_flush() implementation does
+ * not quite work for uinput as we can't reasonably
+ * handle FF requests during device teardown.
+ */
+ dev->flush = uinput_dev_flush;
+ }
+
+ dev->event = uinput_dev_event;
+
+ input_set_drvdata(udev->dev, udev);
+
+ error = input_register_device(udev->dev);
+ if (error)
+ goto fail2;
+
+ udev->state = UIST_CREATED;
+
+ return 0;
+
+ fail2: input_ff_destroy(dev);
+ fail1: uinput_destroy_device(udev);
+ return error;
+}
+
+static int uinput_open(struct inode *inode, struct file *file)
+{
+ struct uinput_device *newdev;
+
+ newdev = kzalloc(sizeof(struct uinput_device), GFP_KERNEL);
+ if (!newdev)
+ return -ENOMEM;
+
+ mutex_init(&newdev->mutex);
+ spin_lock_init(&newdev->requests_lock);
+ init_waitqueue_head(&newdev->requests_waitq);
+ init_waitqueue_head(&newdev->waitq);
+ newdev->state = UIST_NEW_DEVICE;
+
+ file->private_data = newdev;
+ stream_open(inode, file);
+
+ return 0;
+}
+
+static int uinput_validate_absinfo(struct input_dev *dev, unsigned int code,
+ const struct input_absinfo *abs)
+{
+ int min, max, range;
+
+ min = abs->minimum;
+ max = abs->maximum;
+
+ if ((min != 0 || max != 0) && max < min) {
+ printk(KERN_DEBUG
+ "%s: invalid abs[%02x] min:%d max:%d\n",
+ UINPUT_NAME, code, min, max);
+ return -EINVAL;
+ }
+
+ if (!check_sub_overflow(max, min, &range) && abs->flat > range) {
+ printk(KERN_DEBUG
+ "%s: abs_flat #%02x out of range: %d (min:%d/max:%d)\n",
+ UINPUT_NAME, code, abs->flat, min, max);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int uinput_validate_absbits(struct input_dev *dev)
+{
+ unsigned int cnt;
+ int error;
+
+ if (!test_bit(EV_ABS, dev->evbit))
+ return 0;
+
+ /*
+ * Check if absmin/absmax/absfuzz/absflat are sane.
+ */
+
+ for_each_set_bit(cnt, dev->absbit, ABS_CNT) {
+ if (!dev->absinfo)
+ return -EINVAL;
+
+ error = uinput_validate_absinfo(dev, cnt, &dev->absinfo[cnt]);
+ if (error)
+ return error;
+ }
+
+ return 0;
+}
+
+static int uinput_dev_setup(struct uinput_device *udev,
+ struct uinput_setup __user *arg)
+{
+ struct uinput_setup setup;
+ struct input_dev *dev;
+
+ if (udev->state == UIST_CREATED)
+ return -EINVAL;
+
+ if (copy_from_user(&setup, arg, sizeof(setup)))
+ return -EFAULT;
+
+ if (!setup.name[0])
+ return -EINVAL;
+
+ dev = udev->dev;
+ dev->id = setup.id;
+ udev->ff_effects_max = setup.ff_effects_max;
+
+ kfree(dev->name);
+ dev->name = kstrndup(setup.name, UINPUT_MAX_NAME_SIZE, GFP_KERNEL);
+ if (!dev->name)
+ return -ENOMEM;
+
+ udev->state = UIST_SETUP_COMPLETE;
+ return 0;
+}
+
+static int uinput_abs_setup(struct uinput_device *udev,
+ struct uinput_setup __user *arg, size_t size)
+{
+ struct uinput_abs_setup setup = {};
+ struct input_dev *dev;
+ int error;
+
+ if (size > sizeof(setup))
+ return -E2BIG;
+
+ if (udev->state == UIST_CREATED)
+ return -EINVAL;
+
+ if (copy_from_user(&setup, arg, size))
+ return -EFAULT;
+
+ if (setup.code > ABS_MAX)
+ return -ERANGE;
+
+ dev = udev->dev;
+
+ error = uinput_validate_absinfo(dev, setup.code, &setup.absinfo);
+ if (error)
+ return error;
+
+ input_alloc_absinfo(dev);
+ if (!dev->absinfo)
+ return -ENOMEM;
+
+ set_bit(setup.code, dev->absbit);
+ dev->absinfo[setup.code] = setup.absinfo;
+ return 0;
+}
+
+/* legacy setup via write() */
+static int uinput_setup_device_legacy(struct uinput_device *udev,
+ const char __user *buffer, size_t count)
+{
+ struct uinput_user_dev *user_dev;
+ struct input_dev *dev;
+ int i;
+ int retval;
+
+ if (count != sizeof(struct uinput_user_dev))
+ return -EINVAL;
+
+ if (!udev->dev) {
+ udev->dev = input_allocate_device();
+ if (!udev->dev)
+ return -ENOMEM;
+ }
+
+ dev = udev->dev;
+
+ user_dev = memdup_user(buffer, sizeof(struct uinput_user_dev));
+ if (IS_ERR(user_dev))
+ return PTR_ERR(user_dev);
+
+ udev->ff_effects_max = user_dev->ff_effects_max;
+
+ /* Ensure name is filled in */
+ if (!user_dev->name[0]) {
+ retval = -EINVAL;
+ goto exit;
+ }
+
+ kfree(dev->name);
+ dev->name = kstrndup(user_dev->name, UINPUT_MAX_NAME_SIZE,
+ GFP_KERNEL);
+ if (!dev->name) {
+ retval = -ENOMEM;
+ goto exit;
+ }
+
+ dev->id.bustype = user_dev->id.bustype;
+ dev->id.vendor = user_dev->id.vendor;
+ dev->id.product = user_dev->id.product;
+ dev->id.version = user_dev->id.version;
+
+ for (i = 0; i < ABS_CNT; i++) {
+ input_abs_set_max(dev, i, user_dev->absmax[i]);
+ input_abs_set_min(dev, i, user_dev->absmin[i]);
+ input_abs_set_fuzz(dev, i, user_dev->absfuzz[i]);
+ input_abs_set_flat(dev, i, user_dev->absflat[i]);
+ }
+
+ retval = uinput_validate_absbits(dev);
+ if (retval < 0)
+ goto exit;
+
+ udev->state = UIST_SETUP_COMPLETE;
+ retval = count;
+
+ exit:
+ kfree(user_dev);
+ return retval;
+}
+
+static ssize_t uinput_inject_events(struct uinput_device *udev,
+ const char __user *buffer, size_t count)
+{
+ struct input_event ev;
+ size_t bytes = 0;
+
+ if (count != 0 && count < input_event_size())
+ return -EINVAL;
+
+ while (bytes + input_event_size() <= count) {
+ /*
+ * Note that even if some events were fetched successfully
+ * we are still going to return EFAULT instead of partial
+ * count to let userspace know that it got it's buffers
+ * all wrong.
+ */
+ if (input_event_from_user(buffer + bytes, &ev))
+ return -EFAULT;
+
+ input_event(udev->dev, ev.type, ev.code, ev.value);
+ bytes += input_event_size();
+ cond_resched();
+ }
+
+ return bytes;
+}
+
+static ssize_t uinput_write(struct file *file, const char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ struct uinput_device *udev = file->private_data;
+ int retval;
+
+ if (count == 0)
+ return 0;
+
+ retval = mutex_lock_interruptible(&udev->mutex);
+ if (retval)
+ return retval;
+
+ retval = udev->state == UIST_CREATED ?
+ uinput_inject_events(udev, buffer, count) :
+ uinput_setup_device_legacy(udev, buffer, count);
+
+ mutex_unlock(&udev->mutex);
+
+ return retval;
+}
+
+static bool uinput_fetch_next_event(struct uinput_device *udev,
+ struct input_event *event)
+{
+ bool have_event;
+
+ spin_lock_irq(&udev->dev->event_lock);
+
+ have_event = udev->head != udev->tail;
+ if (have_event) {
+ *event = udev->buff[udev->tail];
+ udev->tail = (udev->tail + 1) % UINPUT_BUFFER_SIZE;
+ }
+
+ spin_unlock_irq(&udev->dev->event_lock);
+
+ return have_event;
+}
+
+static ssize_t uinput_events_to_user(struct uinput_device *udev,
+ char __user *buffer, size_t count)
+{
+ struct input_event event;
+ size_t read = 0;
+
+ while (read + input_event_size() <= count &&
+ uinput_fetch_next_event(udev, &event)) {
+
+ if (input_event_to_user(buffer + read, &event))
+ return -EFAULT;
+
+ read += input_event_size();
+ }
+
+ return read;
+}
+
+static ssize_t uinput_read(struct file *file, char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ struct uinput_device *udev = file->private_data;
+ ssize_t retval;
+
+ if (count != 0 && count < input_event_size())
+ return -EINVAL;
+
+ do {
+ retval = mutex_lock_interruptible(&udev->mutex);
+ if (retval)
+ return retval;
+
+ if (udev->state != UIST_CREATED)
+ retval = -ENODEV;
+ else if (udev->head == udev->tail &&
+ (file->f_flags & O_NONBLOCK))
+ retval = -EAGAIN;
+ else
+ retval = uinput_events_to_user(udev, buffer, count);
+
+ mutex_unlock(&udev->mutex);
+
+ if (retval || count == 0)
+ break;
+
+ if (!(file->f_flags & O_NONBLOCK))
+ retval = wait_event_interruptible(udev->waitq,
+ udev->head != udev->tail ||
+ udev->state != UIST_CREATED);
+ } while (retval == 0);
+
+ return retval;
+}
+
+static __poll_t uinput_poll(struct file *file, poll_table *wait)
+{
+ struct uinput_device *udev = file->private_data;
+ __poll_t mask = EPOLLOUT | EPOLLWRNORM; /* uinput is always writable */
+
+ poll_wait(file, &udev->waitq, wait);
+
+ if (udev->head != udev->tail)
+ mask |= EPOLLIN | EPOLLRDNORM;
+
+ return mask;
+}
+
+static int uinput_release(struct inode *inode, struct file *file)
+{
+ struct uinput_device *udev = file->private_data;
+
+ uinput_destroy_device(udev);
+ kfree(udev);
+
+ return 0;
+}
+
+#ifdef CONFIG_COMPAT
+struct uinput_ff_upload_compat {
+ __u32 request_id;
+ __s32 retval;
+ struct ff_effect_compat effect;
+ struct ff_effect_compat old;
+};
+
+static int uinput_ff_upload_to_user(char __user *buffer,
+ const struct uinput_ff_upload *ff_up)
+{
+ if (in_compat_syscall()) {
+ struct uinput_ff_upload_compat ff_up_compat;
+
+ ff_up_compat.request_id = ff_up->request_id;
+ ff_up_compat.retval = ff_up->retval;
+ /*
+ * It so happens that the pointer that gives us the trouble
+ * is the last field in the structure. Since we don't support
+ * custom waveforms in uinput anyway we can just copy the whole
+ * thing (to the compat size) and ignore the pointer.
+ */
+ memcpy(&ff_up_compat.effect, &ff_up->effect,
+ sizeof(struct ff_effect_compat));
+ memcpy(&ff_up_compat.old, &ff_up->old,
+ sizeof(struct ff_effect_compat));
+
+ if (copy_to_user(buffer, &ff_up_compat,
+ sizeof(struct uinput_ff_upload_compat)))
+ return -EFAULT;
+ } else {
+ if (copy_to_user(buffer, ff_up,
+ sizeof(struct uinput_ff_upload)))
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+static int uinput_ff_upload_from_user(const char __user *buffer,
+ struct uinput_ff_upload *ff_up)
+{
+ if (in_compat_syscall()) {
+ struct uinput_ff_upload_compat ff_up_compat;
+
+ if (copy_from_user(&ff_up_compat, buffer,
+ sizeof(struct uinput_ff_upload_compat)))
+ return -EFAULT;
+
+ ff_up->request_id = ff_up_compat.request_id;
+ ff_up->retval = ff_up_compat.retval;
+ memcpy(&ff_up->effect, &ff_up_compat.effect,
+ sizeof(struct ff_effect_compat));
+ memcpy(&ff_up->old, &ff_up_compat.old,
+ sizeof(struct ff_effect_compat));
+
+ } else {
+ if (copy_from_user(ff_up, buffer,
+ sizeof(struct uinput_ff_upload)))
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+#else
+
+static int uinput_ff_upload_to_user(char __user *buffer,
+ const struct uinput_ff_upload *ff_up)
+{
+ if (copy_to_user(buffer, ff_up, sizeof(struct uinput_ff_upload)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int uinput_ff_upload_from_user(const char __user *buffer,
+ struct uinput_ff_upload *ff_up)
+{
+ if (copy_from_user(ff_up, buffer, sizeof(struct uinput_ff_upload)))
+ return -EFAULT;
+
+ return 0;
+}
+
+#endif
+
+#define uinput_set_bit(_arg, _bit, _max) \
+({ \
+ int __ret = 0; \
+ if (udev->state == UIST_CREATED) \
+ __ret = -EINVAL; \
+ else if ((_arg) > (_max)) \
+ __ret = -EINVAL; \
+ else set_bit((_arg), udev->dev->_bit); \
+ __ret; \
+})
+
+static int uinput_str_to_user(void __user *dest, const char *str,
+ unsigned int maxlen)
+{
+ char __user *p = dest;
+ int len, ret;
+
+ if (!str)
+ return -ENOENT;
+
+ if (maxlen == 0)
+ return -EINVAL;
+
+ len = strlen(str) + 1;
+ if (len > maxlen)
+ len = maxlen;
+
+ ret = copy_to_user(p, str, len);
+ if (ret)
+ return -EFAULT;
+
+ /* force terminating '\0' */
+ ret = put_user(0, p + len - 1);
+ return ret ? -EFAULT : len;
+}
+
+static long uinput_ioctl_handler(struct file *file, unsigned int cmd,
+ unsigned long arg, void __user *p)
+{
+ int retval;
+ struct uinput_device *udev = file->private_data;
+ struct uinput_ff_upload ff_up;
+ struct uinput_ff_erase ff_erase;
+ struct uinput_request *req;
+ char *phys;
+ const char *name;
+ unsigned int size;
+
+ retval = mutex_lock_interruptible(&udev->mutex);
+ if (retval)
+ return retval;
+
+ if (!udev->dev) {
+ udev->dev = input_allocate_device();
+ if (!udev->dev) {
+ retval = -ENOMEM;
+ goto out;
+ }
+ }
+
+ switch (cmd) {
+ case UI_GET_VERSION:
+ if (put_user(UINPUT_VERSION, (unsigned int __user *)p))
+ retval = -EFAULT;
+ goto out;
+
+ case UI_DEV_CREATE:
+ retval = uinput_create_device(udev);
+ goto out;
+
+ case UI_DEV_DESTROY:
+ uinput_destroy_device(udev);
+ goto out;
+
+ case UI_DEV_SETUP:
+ retval = uinput_dev_setup(udev, p);
+ goto out;
+
+ /* UI_ABS_SETUP is handled in the variable size ioctls */
+
+ case UI_SET_EVBIT:
+ retval = uinput_set_bit(arg, evbit, EV_MAX);
+ goto out;
+
+ case UI_SET_KEYBIT:
+ retval = uinput_set_bit(arg, keybit, KEY_MAX);
+ goto out;
+
+ case UI_SET_RELBIT:
+ retval = uinput_set_bit(arg, relbit, REL_MAX);
+ goto out;
+
+ case UI_SET_ABSBIT:
+ retval = uinput_set_bit(arg, absbit, ABS_MAX);
+ goto out;
+
+ case UI_SET_MSCBIT:
+ retval = uinput_set_bit(arg, mscbit, MSC_MAX);
+ goto out;
+
+ case UI_SET_LEDBIT:
+ retval = uinput_set_bit(arg, ledbit, LED_MAX);
+ goto out;
+
+ case UI_SET_SNDBIT:
+ retval = uinput_set_bit(arg, sndbit, SND_MAX);
+ goto out;
+
+ case UI_SET_FFBIT:
+ retval = uinput_set_bit(arg, ffbit, FF_MAX);
+ goto out;
+
+ case UI_SET_SWBIT:
+ retval = uinput_set_bit(arg, swbit, SW_MAX);
+ goto out;
+
+ case UI_SET_PROPBIT:
+ retval = uinput_set_bit(arg, propbit, INPUT_PROP_MAX);
+ goto out;
+
+ case UI_SET_PHYS:
+ if (udev->state == UIST_CREATED) {
+ retval = -EINVAL;
+ goto out;
+ }
+
+ phys = strndup_user(p, 1024);
+ if (IS_ERR(phys)) {
+ retval = PTR_ERR(phys);
+ goto out;
+ }
+
+ kfree(udev->dev->phys);
+ udev->dev->phys = phys;
+ goto out;
+
+ case UI_BEGIN_FF_UPLOAD:
+ retval = uinput_ff_upload_from_user(p, &ff_up);
+ if (retval)
+ goto out;
+
+ req = uinput_request_find(udev, ff_up.request_id);
+ if (!req || req->code != UI_FF_UPLOAD ||
+ !req->u.upload.effect) {
+ retval = -EINVAL;
+ goto out;
+ }
+
+ ff_up.retval = 0;
+ ff_up.effect = *req->u.upload.effect;
+ if (req->u.upload.old)
+ ff_up.old = *req->u.upload.old;
+ else
+ memset(&ff_up.old, 0, sizeof(struct ff_effect));
+
+ retval = uinput_ff_upload_to_user(p, &ff_up);
+ goto out;
+
+ case UI_BEGIN_FF_ERASE:
+ if (copy_from_user(&ff_erase, p, sizeof(ff_erase))) {
+ retval = -EFAULT;
+ goto out;
+ }
+
+ req = uinput_request_find(udev, ff_erase.request_id);
+ if (!req || req->code != UI_FF_ERASE) {
+ retval = -EINVAL;
+ goto out;
+ }
+
+ ff_erase.retval = 0;
+ ff_erase.effect_id = req->u.effect_id;
+ if (copy_to_user(p, &ff_erase, sizeof(ff_erase))) {
+ retval = -EFAULT;
+ goto out;
+ }
+
+ goto out;
+
+ case UI_END_FF_UPLOAD:
+ retval = uinput_ff_upload_from_user(p, &ff_up);
+ if (retval)
+ goto out;
+
+ req = uinput_request_find(udev, ff_up.request_id);
+ if (!req || req->code != UI_FF_UPLOAD ||
+ !req->u.upload.effect) {
+ retval = -EINVAL;
+ goto out;
+ }
+
+ req->retval = ff_up.retval;
+ complete(&req->done);
+ goto out;
+
+ case UI_END_FF_ERASE:
+ if (copy_from_user(&ff_erase, p, sizeof(ff_erase))) {
+ retval = -EFAULT;
+ goto out;
+ }
+
+ req = uinput_request_find(udev, ff_erase.request_id);
+ if (!req || req->code != UI_FF_ERASE) {
+ retval = -EINVAL;
+ goto out;
+ }
+
+ req->retval = ff_erase.retval;
+ complete(&req->done);
+ goto out;
+ }
+
+ size = _IOC_SIZE(cmd);
+
+ /* Now check variable-length commands */
+ switch (cmd & ~IOCSIZE_MASK) {
+ case UI_GET_SYSNAME(0):
+ if (udev->state != UIST_CREATED) {
+ retval = -ENOENT;
+ goto out;
+ }
+ name = dev_name(&udev->dev->dev);
+ retval = uinput_str_to_user(p, name, size);
+ goto out;
+
+ case UI_ABS_SETUP & ~IOCSIZE_MASK:
+ retval = uinput_abs_setup(udev, p, size);
+ goto out;
+ }
+
+ retval = -EINVAL;
+ out:
+ mutex_unlock(&udev->mutex);
+ return retval;
+}
+
+static long uinput_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ return uinput_ioctl_handler(file, cmd, arg, (void __user *)arg);
+}
+
+#ifdef CONFIG_COMPAT
+
+/*
+ * These IOCTLs change their size and thus their numbers between
+ * 32 and 64 bits.
+ */
+#define UI_SET_PHYS_COMPAT \
+ _IOW(UINPUT_IOCTL_BASE, 108, compat_uptr_t)
+#define UI_BEGIN_FF_UPLOAD_COMPAT \
+ _IOWR(UINPUT_IOCTL_BASE, 200, struct uinput_ff_upload_compat)
+#define UI_END_FF_UPLOAD_COMPAT \
+ _IOW(UINPUT_IOCTL_BASE, 201, struct uinput_ff_upload_compat)
+
+static long uinput_compat_ioctl(struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ switch (cmd) {
+ case UI_SET_PHYS_COMPAT:
+ cmd = UI_SET_PHYS;
+ break;
+ case UI_BEGIN_FF_UPLOAD_COMPAT:
+ cmd = UI_BEGIN_FF_UPLOAD;
+ break;
+ case UI_END_FF_UPLOAD_COMPAT:
+ cmd = UI_END_FF_UPLOAD;
+ break;
+ }
+
+ return uinput_ioctl_handler(file, cmd, arg, compat_ptr(arg));
+}
+#endif
+
+static const struct file_operations uinput_fops = {
+ .owner = THIS_MODULE,
+ .open = uinput_open,
+ .release = uinput_release,
+ .read = uinput_read,
+ .write = uinput_write,
+ .poll = uinput_poll,
+ .unlocked_ioctl = uinput_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = uinput_compat_ioctl,
+#endif
+ .llseek = no_llseek,
+};
+
+static struct miscdevice uinput_misc = {
+ .fops = &uinput_fops,
+ .minor = UINPUT_MINOR,
+ .name = UINPUT_NAME,
+};
+module_misc_device(uinput_misc);
+
+MODULE_ALIAS_MISCDEV(UINPUT_MINOR);
+MODULE_ALIAS("devname:" UINPUT_NAME);
+
+MODULE_AUTHOR("Aristeu Sergio Rozanski Filho");
+MODULE_DESCRIPTION("User level driver support for input subsystem");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/wistron_btns.c b/drivers/input/misc/wistron_btns.c
new file mode 100644
index 000000000..80dfd72a0
--- /dev/null
+++ b/drivers/input/misc/wistron_btns.c
@@ -0,0 +1,1395 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Wistron laptop button driver
+ * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz>
+ * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org>
+ * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru>
+ */
+#include <linux/io.h>
+#include <linux/dmi.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/interrupt.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/mc146818rtc.h>
+#include <linux/module.h>
+#include <linux/preempt.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/platform_device.h>
+#include <linux/leds.h>
+
+/* How often we poll keys - msecs */
+#define POLL_INTERVAL_DEFAULT 500 /* when idle */
+#define POLL_INTERVAL_BURST 100 /* when a key was recently pressed */
+
+/* BIOS subsystem IDs */
+#define WIFI 0x35
+#define BLUETOOTH 0x34
+#define MAIL_LED 0x31
+
+MODULE_AUTHOR("Miloslav Trmac <mitr@volny.cz>");
+MODULE_DESCRIPTION("Wistron laptop button driver");
+MODULE_LICENSE("GPL v2");
+
+static bool force; /* = 0; */
+module_param(force, bool, 0);
+MODULE_PARM_DESC(force, "Load even if computer is not in database");
+
+static char *keymap_name; /* = NULL; */
+module_param_named(keymap, keymap_name, charp, 0);
+MODULE_PARM_DESC(keymap, "Keymap name, if it can't be autodetected [generic, 1557/MS2141]");
+
+static struct platform_device *wistron_device;
+
+ /* BIOS interface implementation */
+
+static void __iomem *bios_entry_point; /* BIOS routine entry point */
+static void __iomem *bios_code_map_base;
+static void __iomem *bios_data_map_base;
+
+static u8 cmos_address;
+
+struct regs {
+ u32 eax, ebx, ecx;
+};
+
+static void call_bios(struct regs *regs)
+{
+ unsigned long flags;
+
+ preempt_disable();
+ local_irq_save(flags);
+ asm volatile ("pushl %%ebp;"
+ "movl %7, %%ebp;"
+ "call *%6;"
+ "popl %%ebp"
+ : "=a" (regs->eax), "=b" (regs->ebx), "=c" (regs->ecx)
+ : "0" (regs->eax), "1" (regs->ebx), "2" (regs->ecx),
+ "m" (bios_entry_point), "m" (bios_data_map_base)
+ : "edx", "edi", "esi", "memory");
+ local_irq_restore(flags);
+ preempt_enable();
+}
+
+static ssize_t __init locate_wistron_bios(void __iomem *base)
+{
+ static unsigned char __initdata signature[] =
+ { 0x42, 0x21, 0x55, 0x30 };
+ ssize_t offset;
+
+ for (offset = 0; offset < 0x10000; offset += 0x10) {
+ if (check_signature(base + offset, signature,
+ sizeof(signature)) != 0)
+ return offset;
+ }
+ return -1;
+}
+
+static int __init map_bios(void)
+{
+ void __iomem *base;
+ ssize_t offset;
+ u32 entry_point;
+
+ base = ioremap(0xF0000, 0x10000); /* Can't fail */
+ offset = locate_wistron_bios(base);
+ if (offset < 0) {
+ printk(KERN_ERR "wistron_btns: BIOS entry point not found\n");
+ iounmap(base);
+ return -ENODEV;
+ }
+
+ entry_point = readl(base + offset + 5);
+ printk(KERN_DEBUG
+ "wistron_btns: BIOS signature found at %p, entry point %08X\n",
+ base + offset, entry_point);
+
+ if (entry_point >= 0xF0000) {
+ bios_code_map_base = base;
+ bios_entry_point = bios_code_map_base + (entry_point & 0xFFFF);
+ } else {
+ iounmap(base);
+ bios_code_map_base = ioremap(entry_point & ~0x3FFF, 0x4000);
+ if (bios_code_map_base == NULL) {
+ printk(KERN_ERR
+ "wistron_btns: Can't map BIOS code at %08X\n",
+ entry_point & ~0x3FFF);
+ goto err;
+ }
+ bios_entry_point = bios_code_map_base + (entry_point & 0x3FFF);
+ }
+ /* The Windows driver maps 0x10000 bytes, we keep only one page... */
+ bios_data_map_base = ioremap(0x400, 0xc00);
+ if (bios_data_map_base == NULL) {
+ printk(KERN_ERR "wistron_btns: Can't map BIOS data\n");
+ goto err_code;
+ }
+ return 0;
+
+err_code:
+ iounmap(bios_code_map_base);
+err:
+ return -ENOMEM;
+}
+
+static inline void unmap_bios(void)
+{
+ iounmap(bios_code_map_base);
+ iounmap(bios_data_map_base);
+}
+
+ /* BIOS calls */
+
+static u16 bios_pop_queue(void)
+{
+ struct regs regs;
+
+ memset(&regs, 0, sizeof (regs));
+ regs.eax = 0x9610;
+ regs.ebx = 0x061C;
+ regs.ecx = 0x0000;
+ call_bios(&regs);
+
+ return regs.eax;
+}
+
+static void bios_attach(void)
+{
+ struct regs regs;
+
+ memset(&regs, 0, sizeof (regs));
+ regs.eax = 0x9610;
+ regs.ebx = 0x012E;
+ call_bios(&regs);
+}
+
+static void bios_detach(void)
+{
+ struct regs regs;
+
+ memset(&regs, 0, sizeof (regs));
+ regs.eax = 0x9610;
+ regs.ebx = 0x002E;
+ call_bios(&regs);
+}
+
+static u8 bios_get_cmos_address(void)
+{
+ struct regs regs;
+
+ memset(&regs, 0, sizeof (regs));
+ regs.eax = 0x9610;
+ regs.ebx = 0x051C;
+ call_bios(&regs);
+
+ return regs.ecx;
+}
+
+static u16 bios_get_default_setting(u8 subsys)
+{
+ struct regs regs;
+
+ memset(&regs, 0, sizeof (regs));
+ regs.eax = 0x9610;
+ regs.ebx = 0x0200 | subsys;
+ call_bios(&regs);
+
+ return regs.eax;
+}
+
+static void bios_set_state(u8 subsys, int enable)
+{
+ struct regs regs;
+
+ memset(&regs, 0, sizeof (regs));
+ regs.eax = 0x9610;
+ regs.ebx = (enable ? 0x0100 : 0x0000) | subsys;
+ call_bios(&regs);
+}
+
+/* Hardware database */
+
+#define KE_WIFI (KE_LAST + 1)
+#define KE_BLUETOOTH (KE_LAST + 2)
+
+#define FE_MAIL_LED 0x01
+#define FE_WIFI_LED 0x02
+#define FE_UNTESTED 0x80
+
+static struct key_entry *keymap; /* = NULL; Current key map */
+static bool have_wifi;
+static bool have_bluetooth;
+static int leds_present; /* bitmask of leds present */
+
+static int __init dmi_matched(const struct dmi_system_id *dmi)
+{
+ const struct key_entry *key;
+
+ keymap = dmi->driver_data;
+ for (key = keymap; key->type != KE_END; key++) {
+ if (key->type == KE_WIFI)
+ have_wifi = true;
+ else if (key->type == KE_BLUETOOTH)
+ have_bluetooth = true;
+ }
+ leds_present = key->code & (FE_MAIL_LED | FE_WIFI_LED);
+
+ return 1;
+}
+
+static struct key_entry keymap_empty[] __initdata = {
+ { KE_END, 0 }
+};
+
+static struct key_entry keymap_fs_amilo_pro_v2000[] __initdata = {
+ { KE_KEY, 0x01, {KEY_HELP} },
+ { KE_KEY, 0x11, {KEY_PROG1} },
+ { KE_KEY, 0x12, {KEY_PROG2} },
+ { KE_WIFI, 0x30 },
+ { KE_KEY, 0x31, {KEY_MAIL} },
+ { KE_KEY, 0x36, {KEY_WWW} },
+ { KE_END, 0 }
+};
+
+static struct key_entry keymap_fs_amilo_pro_v3505[] __initdata = {
+ { KE_KEY, 0x01, {KEY_HELP} }, /* Fn+F1 */
+ { KE_KEY, 0x06, {KEY_DISPLAYTOGGLE} }, /* Fn+F4 */
+ { KE_BLUETOOTH, 0x30 }, /* Fn+F10 */
+ { KE_KEY, 0x31, {KEY_MAIL} }, /* mail button */
+ { KE_KEY, 0x36, {KEY_WWW} }, /* www button */
+ { KE_WIFI, 0x78 }, /* satellite dish button */
+ { KE_END, 0 }
+};
+
+static struct key_entry keymap_fs_amilo_pro_v8210[] __initdata = {
+ { KE_KEY, 0x01, {KEY_HELP} }, /* Fn+F1 */
+ { KE_KEY, 0x06, {KEY_DISPLAYTOGGLE} }, /* Fn+F4 */
+ { KE_BLUETOOTH, 0x30 }, /* Fn+F10 */
+ { KE_KEY, 0x31, {KEY_MAIL} }, /* mail button */
+ { KE_KEY, 0x36, {KEY_WWW} }, /* www button */
+ { KE_WIFI, 0x78 }, /* satelite dish button */
+ { KE_END, FE_WIFI_LED }
+};
+
+static struct key_entry keymap_fujitsu_n3510[] __initdata = {
+ { KE_KEY, 0x11, {KEY_PROG1} },
+ { KE_KEY, 0x12, {KEY_PROG2} },
+ { KE_KEY, 0x36, {KEY_WWW} },
+ { KE_KEY, 0x31, {KEY_MAIL} },
+ { KE_KEY, 0x71, {KEY_STOPCD} },
+ { KE_KEY, 0x72, {KEY_PLAYPAUSE} },
+ { KE_KEY, 0x74, {KEY_REWIND} },
+ { KE_KEY, 0x78, {KEY_FORWARD} },
+ { KE_END, 0 }
+};
+
+static struct key_entry keymap_wistron_ms2111[] __initdata = {
+ { KE_KEY, 0x11, {KEY_PROG1} },
+ { KE_KEY, 0x12, {KEY_PROG2} },
+ { KE_KEY, 0x13, {KEY_PROG3} },
+ { KE_KEY, 0x31, {KEY_MAIL} },
+ { KE_KEY, 0x36, {KEY_WWW} },
+ { KE_END, FE_MAIL_LED }
+};
+
+static struct key_entry keymap_wistron_md40100[] __initdata = {
+ { KE_KEY, 0x01, {KEY_HELP} },
+ { KE_KEY, 0x02, {KEY_CONFIG} },
+ { KE_KEY, 0x31, {KEY_MAIL} },
+ { KE_KEY, 0x36, {KEY_WWW} },
+ { KE_KEY, 0x37, {KEY_DISPLAYTOGGLE} }, /* Display on/off */
+ { KE_END, FE_MAIL_LED | FE_WIFI_LED | FE_UNTESTED }
+};
+
+static struct key_entry keymap_wistron_ms2141[] __initdata = {
+ { KE_KEY, 0x11, {KEY_PROG1} },
+ { KE_KEY, 0x12, {KEY_PROG2} },
+ { KE_WIFI, 0x30 },
+ { KE_KEY, 0x22, {KEY_REWIND} },
+ { KE_KEY, 0x23, {KEY_FORWARD} },
+ { KE_KEY, 0x24, {KEY_PLAYPAUSE} },
+ { KE_KEY, 0x25, {KEY_STOPCD} },
+ { KE_KEY, 0x31, {KEY_MAIL} },
+ { KE_KEY, 0x36, {KEY_WWW} },
+ { KE_END, 0 }
+};
+
+static struct key_entry keymap_acer_aspire_1500[] __initdata = {
+ { KE_KEY, 0x01, {KEY_HELP} },
+ { KE_KEY, 0x03, {KEY_POWER} },
+ { KE_KEY, 0x11, {KEY_PROG1} },
+ { KE_KEY, 0x12, {KEY_PROG2} },
+ { KE_WIFI, 0x30 },
+ { KE_KEY, 0x31, {KEY_MAIL} },
+ { KE_KEY, 0x36, {KEY_WWW} },
+ { KE_KEY, 0x49, {KEY_CONFIG} },
+ { KE_BLUETOOTH, 0x44 },
+ { KE_END, FE_UNTESTED }
+};
+
+static struct key_entry keymap_acer_aspire_1600[] __initdata = {
+ { KE_KEY, 0x01, {KEY_HELP} },
+ { KE_KEY, 0x03, {KEY_POWER} },
+ { KE_KEY, 0x08, {KEY_MUTE} },
+ { KE_KEY, 0x11, {KEY_PROG1} },
+ { KE_KEY, 0x12, {KEY_PROG2} },
+ { KE_KEY, 0x13, {KEY_PROG3} },
+ { KE_KEY, 0x31, {KEY_MAIL} },
+ { KE_KEY, 0x36, {KEY_WWW} },
+ { KE_KEY, 0x49, {KEY_CONFIG} },
+ { KE_WIFI, 0x30 },
+ { KE_BLUETOOTH, 0x44 },
+ { KE_END, FE_MAIL_LED | FE_UNTESTED }
+};
+
+/* 3020 has been tested */
+static struct key_entry keymap_acer_aspire_5020[] __initdata = {
+ { KE_KEY, 0x01, {KEY_HELP} },
+ { KE_KEY, 0x03, {KEY_POWER} },
+ { KE_KEY, 0x05, {KEY_SWITCHVIDEOMODE} }, /* Display selection */
+ { KE_KEY, 0x11, {KEY_PROG1} },
+ { KE_KEY, 0x12, {KEY_PROG2} },
+ { KE_KEY, 0x31, {KEY_MAIL} },
+ { KE_KEY, 0x36, {KEY_WWW} },
+ { KE_KEY, 0x6a, {KEY_CONFIG} },
+ { KE_WIFI, 0x30 },
+ { KE_BLUETOOTH, 0x44 },
+ { KE_END, FE_MAIL_LED | FE_UNTESTED }
+};
+
+static struct key_entry keymap_acer_travelmate_2410[] __initdata = {
+ { KE_KEY, 0x01, {KEY_HELP} },
+ { KE_KEY, 0x6d, {KEY_POWER} },
+ { KE_KEY, 0x11, {KEY_PROG1} },
+ { KE_KEY, 0x12, {KEY_PROG2} },
+ { KE_KEY, 0x31, {KEY_MAIL} },
+ { KE_KEY, 0x36, {KEY_WWW} },
+ { KE_KEY, 0x6a, {KEY_CONFIG} },
+ { KE_WIFI, 0x30 },
+ { KE_BLUETOOTH, 0x44 },
+ { KE_END, FE_MAIL_LED | FE_UNTESTED }
+};
+
+static struct key_entry keymap_acer_travelmate_110[] __initdata = {
+ { KE_KEY, 0x01, {KEY_HELP} },
+ { KE_KEY, 0x02, {KEY_CONFIG} },
+ { KE_KEY, 0x03, {KEY_POWER} },
+ { KE_KEY, 0x08, {KEY_MUTE} },
+ { KE_KEY, 0x11, {KEY_PROG1} },
+ { KE_KEY, 0x12, {KEY_PROG2} },
+ { KE_KEY, 0x20, {KEY_VOLUMEUP} },
+ { KE_KEY, 0x21, {KEY_VOLUMEDOWN} },
+ { KE_KEY, 0x31, {KEY_MAIL} },
+ { KE_KEY, 0x36, {KEY_WWW} },
+ { KE_SW, 0x4a, {.sw = {SW_LID, 1}} }, /* lid close */
+ { KE_SW, 0x4b, {.sw = {SW_LID, 0}} }, /* lid open */
+ { KE_WIFI, 0x30 },
+ { KE_END, FE_MAIL_LED | FE_UNTESTED }
+};
+
+static struct key_entry keymap_acer_travelmate_300[] __initdata = {
+ { KE_KEY, 0x01, {KEY_HELP} },
+ { KE_KEY, 0x02, {KEY_CONFIG} },
+ { KE_KEY, 0x03, {KEY_POWER} },
+ { KE_KEY, 0x08, {KEY_MUTE} },
+ { KE_KEY, 0x11, {KEY_PROG1} },
+ { KE_KEY, 0x12, {KEY_PROG2} },
+ { KE_KEY, 0x20, {KEY_VOLUMEUP} },
+ { KE_KEY, 0x21, {KEY_VOLUMEDOWN} },
+ { KE_KEY, 0x31, {KEY_MAIL} },
+ { KE_KEY, 0x36, {KEY_WWW} },
+ { KE_WIFI, 0x30 },
+ { KE_BLUETOOTH, 0x44 },
+ { KE_END, FE_MAIL_LED | FE_UNTESTED }
+};
+
+static struct key_entry keymap_acer_travelmate_380[] __initdata = {
+ { KE_KEY, 0x01, {KEY_HELP} },
+ { KE_KEY, 0x02, {KEY_CONFIG} },
+ { KE_KEY, 0x03, {KEY_POWER} }, /* not 370 */
+ { KE_KEY, 0x11, {KEY_PROG1} },
+ { KE_KEY, 0x12, {KEY_PROG2} },
+ { KE_KEY, 0x13, {KEY_PROG3} },
+ { KE_KEY, 0x31, {KEY_MAIL} },
+ { KE_KEY, 0x36, {KEY_WWW} },
+ { KE_WIFI, 0x30 },
+ { KE_END, FE_MAIL_LED | FE_UNTESTED }
+};
+
+/* unusual map */
+static struct key_entry keymap_acer_travelmate_220[] __initdata = {
+ { KE_KEY, 0x01, {KEY_HELP} },
+ { KE_KEY, 0x02, {KEY_CONFIG} },
+ { KE_KEY, 0x11, {KEY_MAIL} },
+ { KE_KEY, 0x12, {KEY_WWW} },
+ { KE_KEY, 0x13, {KEY_PROG2} },
+ { KE_KEY, 0x31, {KEY_PROG1} },
+ { KE_END, FE_WIFI_LED | FE_UNTESTED }
+};
+
+static struct key_entry keymap_acer_travelmate_230[] __initdata = {
+ { KE_KEY, 0x01, {KEY_HELP} },
+ { KE_KEY, 0x02, {KEY_CONFIG} },
+ { KE_KEY, 0x11, {KEY_PROG1} },
+ { KE_KEY, 0x12, {KEY_PROG2} },
+ { KE_KEY, 0x31, {KEY_MAIL} },
+ { KE_KEY, 0x36, {KEY_WWW} },
+ { KE_END, FE_WIFI_LED | FE_UNTESTED }
+};
+
+static struct key_entry keymap_acer_travelmate_240[] __initdata = {
+ { KE_KEY, 0x01, {KEY_HELP} },
+ { KE_KEY, 0x02, {KEY_CONFIG} },
+ { KE_KEY, 0x03, {KEY_POWER} },
+ { KE_KEY, 0x08, {KEY_MUTE} },
+ { KE_KEY, 0x31, {KEY_MAIL} },
+ { KE_KEY, 0x36, {KEY_WWW} },
+ { KE_KEY, 0x11, {KEY_PROG1} },
+ { KE_KEY, 0x12, {KEY_PROG2} },
+ { KE_BLUETOOTH, 0x44 },
+ { KE_WIFI, 0x30 },
+ { KE_END, FE_UNTESTED }
+};
+
+static struct key_entry keymap_acer_travelmate_350[] __initdata = {
+ { KE_KEY, 0x01, {KEY_HELP} },
+ { KE_KEY, 0x02, {KEY_CONFIG} },
+ { KE_KEY, 0x11, {KEY_PROG1} },
+ { KE_KEY, 0x12, {KEY_PROG2} },
+ { KE_KEY, 0x13, {KEY_MAIL} },
+ { KE_KEY, 0x14, {KEY_PROG3} },
+ { KE_KEY, 0x15, {KEY_WWW} },
+ { KE_END, FE_MAIL_LED | FE_WIFI_LED | FE_UNTESTED }
+};
+
+static struct key_entry keymap_acer_travelmate_360[] __initdata = {
+ { KE_KEY, 0x01, {KEY_HELP} },
+ { KE_KEY, 0x02, {KEY_CONFIG} },
+ { KE_KEY, 0x11, {KEY_PROG1} },
+ { KE_KEY, 0x12, {KEY_PROG2} },
+ { KE_KEY, 0x13, {KEY_MAIL} },
+ { KE_KEY, 0x14, {KEY_PROG3} },
+ { KE_KEY, 0x15, {KEY_WWW} },
+ { KE_KEY, 0x40, {KEY_WLAN} },
+ { KE_END, FE_WIFI_LED | FE_UNTESTED } /* no mail led */
+};
+
+/* Wifi subsystem only activates the led. Therefore we need to pass
+ * wifi event as a normal key, then userspace can really change the wifi state.
+ * TODO we need to export led state to userspace (wifi and mail) */
+static struct key_entry keymap_acer_travelmate_610[] __initdata = {
+ { KE_KEY, 0x01, {KEY_HELP} },
+ { KE_KEY, 0x02, {KEY_CONFIG} },
+ { KE_KEY, 0x11, {KEY_PROG1} },
+ { KE_KEY, 0x12, {KEY_PROG2} },
+ { KE_KEY, 0x13, {KEY_PROG3} },
+ { KE_KEY, 0x14, {KEY_MAIL} },
+ { KE_KEY, 0x15, {KEY_WWW} },
+ { KE_KEY, 0x40, {KEY_WLAN} },
+ { KE_END, FE_MAIL_LED | FE_WIFI_LED }
+};
+
+static struct key_entry keymap_acer_travelmate_630[] __initdata = {
+ { KE_KEY, 0x01, {KEY_HELP} },
+ { KE_KEY, 0x02, {KEY_CONFIG} },
+ { KE_KEY, 0x03, {KEY_POWER} },
+ { KE_KEY, 0x08, {KEY_MUTE} }, /* not 620 */
+ { KE_KEY, 0x11, {KEY_PROG1} },
+ { KE_KEY, 0x12, {KEY_PROG2} },
+ { KE_KEY, 0x13, {KEY_PROG3} },
+ { KE_KEY, 0x20, {KEY_VOLUMEUP} },
+ { KE_KEY, 0x21, {KEY_VOLUMEDOWN} },
+ { KE_KEY, 0x31, {KEY_MAIL} },
+ { KE_KEY, 0x36, {KEY_WWW} },
+ { KE_WIFI, 0x30 },
+ { KE_END, FE_MAIL_LED | FE_UNTESTED }
+};
+
+static struct key_entry keymap_aopen_1559as[] __initdata = {
+ { KE_KEY, 0x01, {KEY_HELP} },
+ { KE_KEY, 0x06, {KEY_PROG3} },
+ { KE_KEY, 0x11, {KEY_PROG1} },
+ { KE_KEY, 0x12, {KEY_PROG2} },
+ { KE_WIFI, 0x30 },
+ { KE_KEY, 0x31, {KEY_MAIL} },
+ { KE_KEY, 0x36, {KEY_WWW} },
+ { KE_END, 0 },
+};
+
+static struct key_entry keymap_fs_amilo_d88x0[] __initdata = {
+ { KE_KEY, 0x01, {KEY_HELP} },
+ { KE_KEY, 0x08, {KEY_MUTE} },
+ { KE_KEY, 0x31, {KEY_MAIL} },
+ { KE_KEY, 0x36, {KEY_WWW} },
+ { KE_KEY, 0x11, {KEY_PROG1} },
+ { KE_KEY, 0x12, {KEY_PROG2} },
+ { KE_KEY, 0x13, {KEY_PROG3} },
+ { KE_END, FE_MAIL_LED | FE_WIFI_LED | FE_UNTESTED }
+};
+
+static struct key_entry keymap_wistron_md2900[] __initdata = {
+ { KE_KEY, 0x01, {KEY_HELP} },
+ { KE_KEY, 0x02, {KEY_CONFIG} },
+ { KE_KEY, 0x11, {KEY_PROG1} },
+ { KE_KEY, 0x12, {KEY_PROG2} },
+ { KE_KEY, 0x31, {KEY_MAIL} },
+ { KE_KEY, 0x36, {KEY_WWW} },
+ { KE_WIFI, 0x30 },
+ { KE_END, FE_MAIL_LED | FE_UNTESTED }
+};
+
+static struct key_entry keymap_wistron_md96500[] __initdata = {
+ { KE_KEY, 0x01, {KEY_HELP} },
+ { KE_KEY, 0x02, {KEY_CONFIG} },
+ { KE_KEY, 0x05, {KEY_SWITCHVIDEOMODE} }, /* Display selection */
+ { KE_KEY, 0x06, {KEY_DISPLAYTOGGLE} }, /* Display on/off */
+ { KE_KEY, 0x08, {KEY_MUTE} },
+ { KE_KEY, 0x11, {KEY_PROG1} },
+ { KE_KEY, 0x12, {KEY_PROG2} },
+ { KE_KEY, 0x20, {KEY_VOLUMEUP} },
+ { KE_KEY, 0x21, {KEY_VOLUMEDOWN} },
+ { KE_KEY, 0x22, {KEY_REWIND} },
+ { KE_KEY, 0x23, {KEY_FORWARD} },
+ { KE_KEY, 0x24, {KEY_PLAYPAUSE} },
+ { KE_KEY, 0x25, {KEY_STOPCD} },
+ { KE_KEY, 0x31, {KEY_MAIL} },
+ { KE_KEY, 0x36, {KEY_WWW} },
+ { KE_WIFI, 0x30 },
+ { KE_BLUETOOTH, 0x44 },
+ { KE_END, 0 }
+};
+
+static struct key_entry keymap_wistron_generic[] __initdata = {
+ { KE_KEY, 0x01, {KEY_HELP} },
+ { KE_KEY, 0x02, {KEY_CONFIG} },
+ { KE_KEY, 0x03, {KEY_POWER} },
+ { KE_KEY, 0x05, {KEY_SWITCHVIDEOMODE} }, /* Display selection */
+ { KE_KEY, 0x06, {KEY_DISPLAYTOGGLE} }, /* Display on/off */
+ { KE_KEY, 0x08, {KEY_MUTE} },
+ { KE_KEY, 0x11, {KEY_PROG1} },
+ { KE_KEY, 0x12, {KEY_PROG2} },
+ { KE_KEY, 0x13, {KEY_PROG3} },
+ { KE_KEY, 0x14, {KEY_MAIL} },
+ { KE_KEY, 0x15, {KEY_WWW} },
+ { KE_KEY, 0x20, {KEY_VOLUMEUP} },
+ { KE_KEY, 0x21, {KEY_VOLUMEDOWN} },
+ { KE_KEY, 0x22, {KEY_REWIND} },
+ { KE_KEY, 0x23, {KEY_FORWARD} },
+ { KE_KEY, 0x24, {KEY_PLAYPAUSE} },
+ { KE_KEY, 0x25, {KEY_STOPCD} },
+ { KE_KEY, 0x31, {KEY_MAIL} },
+ { KE_KEY, 0x36, {KEY_WWW} },
+ { KE_KEY, 0x37, {KEY_DISPLAYTOGGLE} }, /* Display on/off */
+ { KE_KEY, 0x40, {KEY_WLAN} },
+ { KE_KEY, 0x49, {KEY_CONFIG} },
+ { KE_SW, 0x4a, {.sw = {SW_LID, 1}} }, /* lid close */
+ { KE_SW, 0x4b, {.sw = {SW_LID, 0}} }, /* lid open */
+ { KE_KEY, 0x6a, {KEY_CONFIG} },
+ { KE_KEY, 0x6d, {KEY_POWER} },
+ { KE_KEY, 0x71, {KEY_STOPCD} },
+ { KE_KEY, 0x72, {KEY_PLAYPAUSE} },
+ { KE_KEY, 0x74, {KEY_REWIND} },
+ { KE_KEY, 0x78, {KEY_FORWARD} },
+ { KE_WIFI, 0x30 },
+ { KE_BLUETOOTH, 0x44 },
+ { KE_END, 0 }
+};
+
+static struct key_entry keymap_aopen_1557[] __initdata = {
+ { KE_KEY, 0x01, {KEY_HELP} },
+ { KE_KEY, 0x11, {KEY_PROG1} },
+ { KE_KEY, 0x12, {KEY_PROG2} },
+ { KE_WIFI, 0x30 },
+ { KE_KEY, 0x22, {KEY_REWIND} },
+ { KE_KEY, 0x23, {KEY_FORWARD} },
+ { KE_KEY, 0x24, {KEY_PLAYPAUSE} },
+ { KE_KEY, 0x25, {KEY_STOPCD} },
+ { KE_KEY, 0x31, {KEY_MAIL} },
+ { KE_KEY, 0x36, {KEY_WWW} },
+ { KE_END, 0 }
+};
+
+static struct key_entry keymap_prestigio[] __initdata = {
+ { KE_KEY, 0x11, {KEY_PROG1} },
+ { KE_KEY, 0x12, {KEY_PROG2} },
+ { KE_WIFI, 0x30 },
+ { KE_KEY, 0x22, {KEY_REWIND} },
+ { KE_KEY, 0x23, {KEY_FORWARD} },
+ { KE_KEY, 0x24, {KEY_PLAYPAUSE} },
+ { KE_KEY, 0x25, {KEY_STOPCD} },
+ { KE_KEY, 0x31, {KEY_MAIL} },
+ { KE_KEY, 0x36, {KEY_WWW} },
+ { KE_END, 0 }
+};
+
+
+/*
+ * If your machine is not here (which is currently rather likely), please send
+ * a list of buttons and their key codes (reported when loading this module
+ * with force=1) and the output of dmidecode to $MODULE_AUTHOR.
+ */
+static const struct dmi_system_id dmi_ids[] __initconst = {
+ {
+ /* Fujitsu-Siemens Amilo Pro V2000 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "AMILO Pro V2000"),
+ },
+ .driver_data = keymap_fs_amilo_pro_v2000
+ },
+ {
+ /* Fujitsu-Siemens Amilo Pro Edition V3505 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "AMILO Pro Edition V3505"),
+ },
+ .driver_data = keymap_fs_amilo_pro_v3505
+ },
+ {
+ /* Fujitsu-Siemens Amilo Pro Edition V8210 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "AMILO Pro Series V8210"),
+ },
+ .driver_data = keymap_fs_amilo_pro_v8210
+ },
+ {
+ /* Fujitsu-Siemens Amilo M7400 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "AMILO M "),
+ },
+ .driver_data = keymap_fs_amilo_pro_v2000
+ },
+ {
+ /* Maxdata Pro 7000 DX */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "MAXDATA"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Pro 7000"),
+ },
+ .driver_data = keymap_fs_amilo_pro_v2000
+ },
+ {
+ /* Fujitsu N3510 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "N3510"),
+ },
+ .driver_data = keymap_fujitsu_n3510
+ },
+ {
+ /* Acer Aspire 1500 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 1500"),
+ },
+ .driver_data = keymap_acer_aspire_1500
+ },
+ {
+ /* Acer Aspire 1600 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 1600"),
+ },
+ .driver_data = keymap_acer_aspire_1600
+ },
+ {
+ /* Acer Aspire 3020 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 3020"),
+ },
+ .driver_data = keymap_acer_aspire_5020
+ },
+ {
+ /* Acer Aspire 5020 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5020"),
+ },
+ .driver_data = keymap_acer_aspire_5020
+ },
+ {
+ /* Acer TravelMate 2100 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 2100"),
+ },
+ .driver_data = keymap_acer_aspire_5020
+ },
+ {
+ /* Acer TravelMate 2410 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 2410"),
+ },
+ .driver_data = keymap_acer_travelmate_2410
+ },
+ {
+ /* Acer TravelMate C300 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate C300"),
+ },
+ .driver_data = keymap_acer_travelmate_300
+ },
+ {
+ /* Acer TravelMate C100 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate C100"),
+ },
+ .driver_data = keymap_acer_travelmate_300
+ },
+ {
+ /* Acer TravelMate C110 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate C110"),
+ },
+ .driver_data = keymap_acer_travelmate_110
+ },
+ {
+ /* Acer TravelMate 380 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 380"),
+ },
+ .driver_data = keymap_acer_travelmate_380
+ },
+ {
+ /* Acer TravelMate 370 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 370"),
+ },
+ .driver_data = keymap_acer_travelmate_380 /* keyboard minus 1 key */
+ },
+ {
+ /* Acer TravelMate 220 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 220"),
+ },
+ .driver_data = keymap_acer_travelmate_220
+ },
+ {
+ /* Acer TravelMate 260 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 260"),
+ },
+ .driver_data = keymap_acer_travelmate_220
+ },
+ {
+ /* Acer TravelMate 230 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 230"),
+ /* acerhk looks for "TravelMate F4..." ?! */
+ },
+ .driver_data = keymap_acer_travelmate_230
+ },
+ {
+ /* Acer TravelMate 280 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 280"),
+ },
+ .driver_data = keymap_acer_travelmate_230
+ },
+ {
+ /* Acer TravelMate 240 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 240"),
+ },
+ .driver_data = keymap_acer_travelmate_240
+ },
+ {
+ /* Acer TravelMate 250 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 250"),
+ },
+ .driver_data = keymap_acer_travelmate_240
+ },
+ {
+ /* Acer TravelMate 2424NWXCi */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 2420"),
+ },
+ .driver_data = keymap_acer_travelmate_240
+ },
+ {
+ /* Acer TravelMate 350 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 350"),
+ },
+ .driver_data = keymap_acer_travelmate_350
+ },
+ {
+ /* Acer TravelMate 360 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 360"),
+ },
+ .driver_data = keymap_acer_travelmate_360
+ },
+ {
+ /* Acer TravelMate 610 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ACER"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 610"),
+ },
+ .driver_data = keymap_acer_travelmate_610
+ },
+ {
+ /* Acer TravelMate 620 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 620"),
+ },
+ .driver_data = keymap_acer_travelmate_630
+ },
+ {
+ /* Acer TravelMate 630 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 630"),
+ },
+ .driver_data = keymap_acer_travelmate_630
+ },
+ {
+ /* AOpen 1559AS */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "E2U"),
+ DMI_MATCH(DMI_BOARD_NAME, "E2U"),
+ },
+ .driver_data = keymap_aopen_1559as
+ },
+ {
+ /* Medion MD 9783 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "MEDIONNB"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "MD 9783"),
+ },
+ .driver_data = keymap_wistron_ms2111
+ },
+ {
+ /* Medion MD 40100 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "MEDIONNB"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "WID2000"),
+ },
+ .driver_data = keymap_wistron_md40100
+ },
+ {
+ /* Medion MD 2900 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "MEDIONNB"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "WIM 2000"),
+ },
+ .driver_data = keymap_wistron_md2900
+ },
+ {
+ /* Medion MD 42200 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Medion"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "WIM 2030"),
+ },
+ .driver_data = keymap_fs_amilo_pro_v2000
+ },
+ {
+ /* Medion MD 96500 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "MEDIONPC"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "WIM 2040"),
+ },
+ .driver_data = keymap_wistron_md96500
+ },
+ {
+ /* Medion MD 95400 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "MEDIONPC"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "WIM 2050"),
+ },
+ .driver_data = keymap_wistron_md96500
+ },
+ {
+ /* Fujitsu Siemens Amilo D7820 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"), /* not sure */
+ DMI_MATCH(DMI_PRODUCT_NAME, "Amilo D"),
+ },
+ .driver_data = keymap_fs_amilo_d88x0
+ },
+ {
+ /* Fujitsu Siemens Amilo D88x0 */
+ .callback = dmi_matched,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "AMILO D"),
+ },
+ .driver_data = keymap_fs_amilo_d88x0
+ },
+ { NULL, }
+};
+MODULE_DEVICE_TABLE(dmi, dmi_ids);
+
+/* Copy the good keymap, as the original ones are free'd */
+static int __init copy_keymap(void)
+{
+ const struct key_entry *key;
+ struct key_entry *new_keymap;
+ unsigned int length = 1;
+
+ for (key = keymap; key->type != KE_END; key++)
+ length++;
+
+ new_keymap = kmemdup(keymap, length * sizeof(struct key_entry),
+ GFP_KERNEL);
+ if (!new_keymap)
+ return -ENOMEM;
+
+ keymap = new_keymap;
+
+ return 0;
+}
+
+static int __init select_keymap(void)
+{
+ dmi_check_system(dmi_ids);
+ if (keymap_name != NULL) {
+ if (strcmp (keymap_name, "1557/MS2141") == 0)
+ keymap = keymap_wistron_ms2141;
+ else if (strcmp (keymap_name, "aopen1557") == 0)
+ keymap = keymap_aopen_1557;
+ else if (strcmp (keymap_name, "prestigio") == 0)
+ keymap = keymap_prestigio;
+ else if (strcmp (keymap_name, "generic") == 0)
+ keymap = keymap_wistron_generic;
+ else {
+ printk(KERN_ERR "wistron_btns: Keymap unknown\n");
+ return -EINVAL;
+ }
+ }
+ if (keymap == NULL) {
+ if (!force) {
+ printk(KERN_ERR "wistron_btns: System unknown\n");
+ return -ENODEV;
+ }
+ keymap = keymap_empty;
+ }
+
+ return copy_keymap();
+}
+
+ /* Input layer interface */
+
+static struct input_dev *wistron_idev;
+static unsigned long jiffies_last_press;
+static bool wifi_enabled;
+static bool bluetooth_enabled;
+
+ /* led management */
+static void wistron_mail_led_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ bios_set_state(MAIL_LED, (value != LED_OFF) ? 1 : 0);
+}
+
+/* same as setting up wifi card, but for laptops on which the led is managed */
+static void wistron_wifi_led_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ bios_set_state(WIFI, (value != LED_OFF) ? 1 : 0);
+}
+
+static struct led_classdev wistron_mail_led = {
+ .name = "wistron:green:mail",
+ .brightness_set = wistron_mail_led_set,
+};
+
+static struct led_classdev wistron_wifi_led = {
+ .name = "wistron:red:wifi",
+ .brightness_set = wistron_wifi_led_set,
+};
+
+static void wistron_led_init(struct device *parent)
+{
+ if (leds_present & FE_WIFI_LED) {
+ u16 wifi = bios_get_default_setting(WIFI);
+ if (wifi & 1) {
+ wistron_wifi_led.brightness = (wifi & 2) ? LED_FULL : LED_OFF;
+ if (led_classdev_register(parent, &wistron_wifi_led))
+ leds_present &= ~FE_WIFI_LED;
+ else
+ bios_set_state(WIFI, wistron_wifi_led.brightness);
+
+ } else
+ leds_present &= ~FE_WIFI_LED;
+ }
+
+ if (leds_present & FE_MAIL_LED) {
+ /* bios_get_default_setting(MAIL) always retuns 0, so just turn the led off */
+ wistron_mail_led.brightness = LED_OFF;
+ if (led_classdev_register(parent, &wistron_mail_led))
+ leds_present &= ~FE_MAIL_LED;
+ else
+ bios_set_state(MAIL_LED, wistron_mail_led.brightness);
+ }
+}
+
+static void wistron_led_remove(void)
+{
+ if (leds_present & FE_MAIL_LED)
+ led_classdev_unregister(&wistron_mail_led);
+
+ if (leds_present & FE_WIFI_LED)
+ led_classdev_unregister(&wistron_wifi_led);
+}
+
+static inline void wistron_led_suspend(void)
+{
+ if (leds_present & FE_MAIL_LED)
+ led_classdev_suspend(&wistron_mail_led);
+
+ if (leds_present & FE_WIFI_LED)
+ led_classdev_suspend(&wistron_wifi_led);
+}
+
+static inline void wistron_led_resume(void)
+{
+ if (leds_present & FE_MAIL_LED)
+ led_classdev_resume(&wistron_mail_led);
+
+ if (leds_present & FE_WIFI_LED)
+ led_classdev_resume(&wistron_wifi_led);
+}
+
+static void handle_key(u8 code)
+{
+ const struct key_entry *key =
+ sparse_keymap_entry_from_scancode(wistron_idev, code);
+
+ if (key) {
+ switch (key->type) {
+ case KE_WIFI:
+ if (have_wifi) {
+ wifi_enabled = !wifi_enabled;
+ bios_set_state(WIFI, wifi_enabled);
+ }
+ break;
+
+ case KE_BLUETOOTH:
+ if (have_bluetooth) {
+ bluetooth_enabled = !bluetooth_enabled;
+ bios_set_state(BLUETOOTH, bluetooth_enabled);
+ }
+ break;
+
+ default:
+ sparse_keymap_report_entry(wistron_idev, key, 1, true);
+ break;
+ }
+ jiffies_last_press = jiffies;
+ } else {
+ printk(KERN_NOTICE
+ "wistron_btns: Unknown key code %02X\n", code);
+ }
+}
+
+static void poll_bios(bool discard)
+{
+ u8 qlen;
+ u16 val;
+
+ for (;;) {
+ qlen = CMOS_READ(cmos_address);
+ if (qlen == 0)
+ break;
+ val = bios_pop_queue();
+ if (val != 0 && !discard)
+ handle_key((u8)val);
+ }
+}
+
+static int wistron_flush(struct input_dev *dev)
+{
+ /* Flush stale event queue */
+ poll_bios(true);
+
+ return 0;
+}
+
+static void wistron_poll(struct input_dev *dev)
+{
+ poll_bios(false);
+
+ /* Increase poll frequency if user is currently pressing keys (< 2s ago) */
+ if (time_before(jiffies, jiffies_last_press + 2 * HZ))
+ input_set_poll_interval(dev, POLL_INTERVAL_BURST);
+ else
+ input_set_poll_interval(dev, POLL_INTERVAL_DEFAULT);
+}
+
+static int wistron_setup_keymap(struct input_dev *dev,
+ struct key_entry *entry)
+{
+ switch (entry->type) {
+
+ /* if wifi or bluetooth are not available, create normal keys */
+ case KE_WIFI:
+ if (!have_wifi) {
+ entry->type = KE_KEY;
+ entry->keycode = KEY_WLAN;
+ }
+ break;
+
+ case KE_BLUETOOTH:
+ if (!have_bluetooth) {
+ entry->type = KE_KEY;
+ entry->keycode = KEY_BLUETOOTH;
+ }
+ break;
+
+ case KE_END:
+ if (entry->code & FE_UNTESTED)
+ printk(KERN_WARNING "Untested laptop multimedia keys, "
+ "please report success or failure to "
+ "eric.piel@tremplin-utc.net\n");
+ break;
+ }
+
+ return 0;
+}
+
+static int setup_input_dev(void)
+{
+ int error;
+
+ wistron_idev = input_allocate_device();
+ if (!wistron_idev)
+ return -ENOMEM;
+
+ wistron_idev->name = "Wistron laptop buttons";
+ wistron_idev->phys = "wistron/input0";
+ wistron_idev->id.bustype = BUS_HOST;
+ wistron_idev->dev.parent = &wistron_device->dev;
+
+ wistron_idev->open = wistron_flush;
+
+ error = sparse_keymap_setup(wistron_idev, keymap, wistron_setup_keymap);
+ if (error)
+ goto err_free_dev;
+
+ error = input_setup_polling(wistron_idev, wistron_poll);
+ if (error)
+ goto err_free_dev;
+
+ input_set_poll_interval(wistron_idev, POLL_INTERVAL_DEFAULT);
+
+ error = input_register_device(wistron_idev);
+ if (error)
+ goto err_free_dev;
+
+ return 0;
+
+ err_free_dev:
+ input_free_device(wistron_idev);
+ return error;
+}
+
+/* Driver core */
+
+static int wistron_probe(struct platform_device *dev)
+{
+ int err;
+
+ bios_attach();
+ cmos_address = bios_get_cmos_address();
+
+ if (have_wifi) {
+ u16 wifi = bios_get_default_setting(WIFI);
+ if (wifi & 1)
+ wifi_enabled = wifi & 2;
+ else
+ have_wifi = 0;
+
+ if (have_wifi)
+ bios_set_state(WIFI, wifi_enabled);
+ }
+
+ if (have_bluetooth) {
+ u16 bt = bios_get_default_setting(BLUETOOTH);
+ if (bt & 1)
+ bluetooth_enabled = bt & 2;
+ else
+ have_bluetooth = false;
+
+ if (have_bluetooth)
+ bios_set_state(BLUETOOTH, bluetooth_enabled);
+ }
+
+ wistron_led_init(&dev->dev);
+
+ err = setup_input_dev();
+ if (err) {
+ bios_detach();
+ return err;
+ }
+
+ return 0;
+}
+
+static int wistron_remove(struct platform_device *dev)
+{
+ wistron_led_remove();
+ input_unregister_device(wistron_idev);
+ bios_detach();
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int wistron_suspend(struct device *dev)
+{
+ if (have_wifi)
+ bios_set_state(WIFI, 0);
+
+ if (have_bluetooth)
+ bios_set_state(BLUETOOTH, 0);
+
+ wistron_led_suspend();
+
+ return 0;
+}
+
+static int wistron_resume(struct device *dev)
+{
+ if (have_wifi)
+ bios_set_state(WIFI, wifi_enabled);
+
+ if (have_bluetooth)
+ bios_set_state(BLUETOOTH, bluetooth_enabled);
+
+ wistron_led_resume();
+
+ poll_bios(true);
+
+ return 0;
+}
+
+static const struct dev_pm_ops wistron_pm_ops = {
+ .suspend = wistron_suspend,
+ .resume = wistron_resume,
+ .poweroff = wistron_suspend,
+ .restore = wistron_resume,
+};
+#endif
+
+static struct platform_driver wistron_driver = {
+ .driver = {
+ .name = "wistron-bios",
+#ifdef CONFIG_PM
+ .pm = &wistron_pm_ops,
+#endif
+ },
+ .probe = wistron_probe,
+ .remove = wistron_remove,
+};
+
+static int __init wb_module_init(void)
+{
+ int err;
+
+ err = select_keymap();
+ if (err)
+ return err;
+
+ err = map_bios();
+ if (err)
+ goto err_free_keymap;
+
+ err = platform_driver_register(&wistron_driver);
+ if (err)
+ goto err_unmap_bios;
+
+ wistron_device = platform_device_alloc("wistron-bios", -1);
+ if (!wistron_device) {
+ err = -ENOMEM;
+ goto err_unregister_driver;
+ }
+
+ err = platform_device_add(wistron_device);
+ if (err)
+ goto err_free_device;
+
+ return 0;
+
+ err_free_device:
+ platform_device_put(wistron_device);
+ err_unregister_driver:
+ platform_driver_unregister(&wistron_driver);
+ err_unmap_bios:
+ unmap_bios();
+ err_free_keymap:
+ kfree(keymap);
+
+ return err;
+}
+
+static void __exit wb_module_exit(void)
+{
+ platform_device_unregister(wistron_device);
+ platform_driver_unregister(&wistron_driver);
+ unmap_bios();
+ kfree(keymap);
+}
+
+module_init(wb_module_init);
+module_exit(wb_module_exit);
diff --git a/drivers/input/misc/wm831x-on.c b/drivers/input/misc/wm831x-on.c
new file mode 100644
index 000000000..a42fe041b
--- /dev/null
+++ b/drivers/input/misc/wm831x-on.c
@@ -0,0 +1,150 @@
+/*
+ * wm831x-on.c - WM831X ON pin driver
+ *
+ * Copyright (C) 2009 Wolfson Microelectronics plc
+ *
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License. See the file "COPYING" in the main directory of this
+ * archive for more details.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/workqueue.h>
+#include <linux/mfd/wm831x/core.h>
+
+struct wm831x_on {
+ struct input_dev *dev;
+ struct delayed_work work;
+ struct wm831x *wm831x;
+};
+
+/*
+ * The chip gives us an interrupt when the ON pin is asserted but we
+ * then need to poll to see when the pin is deasserted.
+ */
+static void wm831x_poll_on(struct work_struct *work)
+{
+ struct wm831x_on *wm831x_on = container_of(work, struct wm831x_on,
+ work.work);
+ struct wm831x *wm831x = wm831x_on->wm831x;
+ int poll, ret;
+
+ ret = wm831x_reg_read(wm831x, WM831X_ON_PIN_CONTROL);
+ if (ret >= 0) {
+ poll = !(ret & WM831X_ON_PIN_STS);
+
+ input_report_key(wm831x_on->dev, KEY_POWER, poll);
+ input_sync(wm831x_on->dev);
+ } else {
+ dev_err(wm831x->dev, "Failed to read ON status: %d\n", ret);
+ poll = 1;
+ }
+
+ if (poll)
+ schedule_delayed_work(&wm831x_on->work, 100);
+}
+
+static irqreturn_t wm831x_on_irq(int irq, void *data)
+{
+ struct wm831x_on *wm831x_on = data;
+
+ schedule_delayed_work(&wm831x_on->work, 0);
+
+ return IRQ_HANDLED;
+}
+
+static int wm831x_on_probe(struct platform_device *pdev)
+{
+ struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
+ struct wm831x_on *wm831x_on;
+ int irq = wm831x_irq(wm831x, platform_get_irq(pdev, 0));
+ int ret;
+
+ wm831x_on = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_on),
+ GFP_KERNEL);
+ if (!wm831x_on) {
+ dev_err(&pdev->dev, "Can't allocate data\n");
+ return -ENOMEM;
+ }
+
+ wm831x_on->wm831x = wm831x;
+ INIT_DELAYED_WORK(&wm831x_on->work, wm831x_poll_on);
+
+ wm831x_on->dev = devm_input_allocate_device(&pdev->dev);
+ if (!wm831x_on->dev) {
+ dev_err(&pdev->dev, "Can't allocate input dev\n");
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ wm831x_on->dev->evbit[0] = BIT_MASK(EV_KEY);
+ wm831x_on->dev->keybit[BIT_WORD(KEY_POWER)] = BIT_MASK(KEY_POWER);
+ wm831x_on->dev->name = "wm831x_on";
+ wm831x_on->dev->phys = "wm831x_on/input0";
+ wm831x_on->dev->dev.parent = &pdev->dev;
+
+ ret = request_threaded_irq(irq, NULL, wm831x_on_irq,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ "wm831x_on",
+ wm831x_on);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Unable to request IRQ: %d\n", ret);
+ goto err_input_dev;
+ }
+ ret = input_register_device(wm831x_on->dev);
+ if (ret) {
+ dev_dbg(&pdev->dev, "Can't register input device: %d\n", ret);
+ goto err_irq;
+ }
+
+ platform_set_drvdata(pdev, wm831x_on);
+
+ return 0;
+
+err_irq:
+ free_irq(irq, wm831x_on);
+err_input_dev:
+err:
+ return ret;
+}
+
+static int wm831x_on_remove(struct platform_device *pdev)
+{
+ struct wm831x_on *wm831x_on = platform_get_drvdata(pdev);
+ int irq = platform_get_irq(pdev, 0);
+
+ free_irq(irq, wm831x_on);
+ cancel_delayed_work_sync(&wm831x_on->work);
+
+ return 0;
+}
+
+static struct platform_driver wm831x_on_driver = {
+ .probe = wm831x_on_probe,
+ .remove = wm831x_on_remove,
+ .driver = {
+ .name = "wm831x-on",
+ },
+};
+module_platform_driver(wm831x_on_driver);
+
+MODULE_ALIAS("platform:wm831x-on");
+MODULE_DESCRIPTION("WM831x ON pin");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+
diff --git a/drivers/input/misc/xen-kbdfront.c b/drivers/input/misc/xen-kbdfront.c
new file mode 100644
index 000000000..8d8ebdc20
--- /dev/null
+++ b/drivers/input/misc/xen-kbdfront.c
@@ -0,0 +1,573 @@
+/*
+ * Xen para-virtual input device
+ *
+ * Copyright (C) 2005 Anthony Liguori <aliguori@us.ibm.com>
+ * Copyright (C) 2006-2008 Red Hat, Inc., Markus Armbruster <armbru@redhat.com>
+ *
+ * Based on linux/drivers/input/mouse/sermouse.c
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive for
+ * more details.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/slab.h>
+
+#include <asm/xen/hypervisor.h>
+
+#include <xen/xen.h>
+#include <xen/events.h>
+#include <xen/page.h>
+#include <xen/grant_table.h>
+#include <xen/interface/grant_table.h>
+#include <xen/interface/io/fbif.h>
+#include <xen/interface/io/kbdif.h>
+#include <xen/xenbus.h>
+#include <xen/platform_pci.h>
+
+struct xenkbd_info {
+ struct input_dev *kbd;
+ struct input_dev *ptr;
+ struct input_dev *mtouch;
+ struct xenkbd_page *page;
+ int gref;
+ int irq;
+ struct xenbus_device *xbdev;
+ char phys[32];
+ /* current MT slot/contact ID we are injecting events in */
+ int mtouch_cur_contact_id;
+};
+
+enum { KPARAM_X, KPARAM_Y, KPARAM_CNT };
+static int ptr_size[KPARAM_CNT] = { XENFB_WIDTH, XENFB_HEIGHT };
+module_param_array(ptr_size, int, NULL, 0444);
+MODULE_PARM_DESC(ptr_size,
+ "Pointing device width, height in pixels (default 800,600)");
+
+static int xenkbd_remove(struct xenbus_device *);
+static int xenkbd_connect_backend(struct xenbus_device *, struct xenkbd_info *);
+static void xenkbd_disconnect_backend(struct xenkbd_info *);
+
+/*
+ * Note: if you need to send out events, see xenfb_do_update() for how
+ * to do that.
+ */
+
+static void xenkbd_handle_motion_event(struct xenkbd_info *info,
+ struct xenkbd_motion *motion)
+{
+ if (unlikely(!info->ptr))
+ return;
+
+ input_report_rel(info->ptr, REL_X, motion->rel_x);
+ input_report_rel(info->ptr, REL_Y, motion->rel_y);
+ if (motion->rel_z)
+ input_report_rel(info->ptr, REL_WHEEL, -motion->rel_z);
+ input_sync(info->ptr);
+}
+
+static void xenkbd_handle_position_event(struct xenkbd_info *info,
+ struct xenkbd_position *pos)
+{
+ if (unlikely(!info->ptr))
+ return;
+
+ input_report_abs(info->ptr, ABS_X, pos->abs_x);
+ input_report_abs(info->ptr, ABS_Y, pos->abs_y);
+ if (pos->rel_z)
+ input_report_rel(info->ptr, REL_WHEEL, -pos->rel_z);
+ input_sync(info->ptr);
+}
+
+static void xenkbd_handle_key_event(struct xenkbd_info *info,
+ struct xenkbd_key *key)
+{
+ struct input_dev *dev;
+ int value = key->pressed;
+
+ if (test_bit(key->keycode, info->ptr->keybit)) {
+ dev = info->ptr;
+ } else if (test_bit(key->keycode, info->kbd->keybit)) {
+ dev = info->kbd;
+ if (key->pressed && test_bit(key->keycode, info->kbd->key))
+ value = 2; /* Mark as autorepeat */
+ } else {
+ pr_warn("unhandled keycode 0x%x\n", key->keycode);
+ return;
+ }
+
+ if (unlikely(!dev))
+ return;
+
+ input_event(dev, EV_KEY, key->keycode, value);
+ input_sync(dev);
+}
+
+static void xenkbd_handle_mt_event(struct xenkbd_info *info,
+ struct xenkbd_mtouch *mtouch)
+{
+ if (unlikely(!info->mtouch))
+ return;
+
+ if (mtouch->contact_id != info->mtouch_cur_contact_id) {
+ info->mtouch_cur_contact_id = mtouch->contact_id;
+ input_mt_slot(info->mtouch, mtouch->contact_id);
+ }
+
+ switch (mtouch->event_type) {
+ case XENKBD_MT_EV_DOWN:
+ input_mt_report_slot_state(info->mtouch, MT_TOOL_FINGER, true);
+ fallthrough;
+
+ case XENKBD_MT_EV_MOTION:
+ input_report_abs(info->mtouch, ABS_MT_POSITION_X,
+ mtouch->u.pos.abs_x);
+ input_report_abs(info->mtouch, ABS_MT_POSITION_Y,
+ mtouch->u.pos.abs_y);
+ break;
+
+ case XENKBD_MT_EV_SHAPE:
+ input_report_abs(info->mtouch, ABS_MT_TOUCH_MAJOR,
+ mtouch->u.shape.major);
+ input_report_abs(info->mtouch, ABS_MT_TOUCH_MINOR,
+ mtouch->u.shape.minor);
+ break;
+
+ case XENKBD_MT_EV_ORIENT:
+ input_report_abs(info->mtouch, ABS_MT_ORIENTATION,
+ mtouch->u.orientation);
+ break;
+
+ case XENKBD_MT_EV_UP:
+ input_mt_report_slot_inactive(info->mtouch);
+ break;
+
+ case XENKBD_MT_EV_SYN:
+ input_mt_sync_frame(info->mtouch);
+ input_sync(info->mtouch);
+ break;
+ }
+}
+
+static void xenkbd_handle_event(struct xenkbd_info *info,
+ union xenkbd_in_event *event)
+{
+ switch (event->type) {
+ case XENKBD_TYPE_MOTION:
+ xenkbd_handle_motion_event(info, &event->motion);
+ break;
+
+ case XENKBD_TYPE_KEY:
+ xenkbd_handle_key_event(info, &event->key);
+ break;
+
+ case XENKBD_TYPE_POS:
+ xenkbd_handle_position_event(info, &event->pos);
+ break;
+
+ case XENKBD_TYPE_MTOUCH:
+ xenkbd_handle_mt_event(info, &event->mtouch);
+ break;
+ }
+}
+
+static irqreturn_t input_handler(int rq, void *dev_id)
+{
+ struct xenkbd_info *info = dev_id;
+ struct xenkbd_page *page = info->page;
+ __u32 cons, prod;
+
+ prod = page->in_prod;
+ if (prod == page->in_cons)
+ return IRQ_HANDLED;
+ rmb(); /* ensure we see ring contents up to prod */
+ for (cons = page->in_cons; cons != prod; cons++)
+ xenkbd_handle_event(info, &XENKBD_IN_RING_REF(page, cons));
+ mb(); /* ensure we got ring contents */
+ page->in_cons = cons;
+ notify_remote_via_irq(info->irq);
+
+ return IRQ_HANDLED;
+}
+
+static int xenkbd_probe(struct xenbus_device *dev,
+ const struct xenbus_device_id *id)
+{
+ int ret, i;
+ bool with_mtouch, with_kbd, with_ptr;
+ struct xenkbd_info *info;
+ struct input_dev *kbd, *ptr, *mtouch;
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ xenbus_dev_fatal(dev, -ENOMEM, "allocating info structure");
+ return -ENOMEM;
+ }
+ dev_set_drvdata(&dev->dev, info);
+ info->xbdev = dev;
+ info->irq = -1;
+ info->gref = -1;
+ snprintf(info->phys, sizeof(info->phys), "xenbus/%s", dev->nodename);
+
+ info->page = (void *)__get_free_page(GFP_KERNEL | __GFP_ZERO);
+ if (!info->page)
+ goto error_nomem;
+
+ /*
+ * The below are reverse logic, e.g. if the feature is set, then
+ * do not expose the corresponding virtual device.
+ */
+ with_kbd = !xenbus_read_unsigned(dev->otherend,
+ XENKBD_FIELD_FEAT_DSBL_KEYBRD, 0);
+
+ with_ptr = !xenbus_read_unsigned(dev->otherend,
+ XENKBD_FIELD_FEAT_DSBL_POINTER, 0);
+
+ /* Direct logic: if set, then create multi-touch device. */
+ with_mtouch = xenbus_read_unsigned(dev->otherend,
+ XENKBD_FIELD_FEAT_MTOUCH, 0);
+ if (with_mtouch) {
+ ret = xenbus_write(XBT_NIL, dev->nodename,
+ XENKBD_FIELD_REQ_MTOUCH, "1");
+ if (ret) {
+ pr_warn("xenkbd: can't request multi-touch");
+ with_mtouch = 0;
+ }
+ }
+
+ /* keyboard */
+ if (with_kbd) {
+ kbd = input_allocate_device();
+ if (!kbd)
+ goto error_nomem;
+ kbd->name = "Xen Virtual Keyboard";
+ kbd->phys = info->phys;
+ kbd->id.bustype = BUS_PCI;
+ kbd->id.vendor = 0x5853;
+ kbd->id.product = 0xffff;
+
+ __set_bit(EV_KEY, kbd->evbit);
+ for (i = KEY_ESC; i < KEY_UNKNOWN; i++)
+ __set_bit(i, kbd->keybit);
+ for (i = KEY_OK; i < KEY_MAX; i++)
+ __set_bit(i, kbd->keybit);
+
+ ret = input_register_device(kbd);
+ if (ret) {
+ input_free_device(kbd);
+ xenbus_dev_fatal(dev, ret,
+ "input_register_device(kbd)");
+ goto error;
+ }
+ info->kbd = kbd;
+ }
+
+ /* pointing device */
+ if (with_ptr) {
+ unsigned int abs;
+
+ /* Set input abs params to match backend screen res */
+ abs = xenbus_read_unsigned(dev->otherend,
+ XENKBD_FIELD_FEAT_ABS_POINTER, 0);
+ ptr_size[KPARAM_X] = xenbus_read_unsigned(dev->otherend,
+ XENKBD_FIELD_WIDTH,
+ ptr_size[KPARAM_X]);
+ ptr_size[KPARAM_Y] = xenbus_read_unsigned(dev->otherend,
+ XENKBD_FIELD_HEIGHT,
+ ptr_size[KPARAM_Y]);
+ if (abs) {
+ ret = xenbus_write(XBT_NIL, dev->nodename,
+ XENKBD_FIELD_REQ_ABS_POINTER, "1");
+ if (ret) {
+ pr_warn("xenkbd: can't request abs-pointer\n");
+ abs = 0;
+ }
+ }
+
+ ptr = input_allocate_device();
+ if (!ptr)
+ goto error_nomem;
+ ptr->name = "Xen Virtual Pointer";
+ ptr->phys = info->phys;
+ ptr->id.bustype = BUS_PCI;
+ ptr->id.vendor = 0x5853;
+ ptr->id.product = 0xfffe;
+
+ if (abs) {
+ __set_bit(EV_ABS, ptr->evbit);
+ input_set_abs_params(ptr, ABS_X, 0,
+ ptr_size[KPARAM_X], 0, 0);
+ input_set_abs_params(ptr, ABS_Y, 0,
+ ptr_size[KPARAM_Y], 0, 0);
+ } else {
+ input_set_capability(ptr, EV_REL, REL_X);
+ input_set_capability(ptr, EV_REL, REL_Y);
+ }
+ input_set_capability(ptr, EV_REL, REL_WHEEL);
+
+ __set_bit(EV_KEY, ptr->evbit);
+ for (i = BTN_LEFT; i <= BTN_TASK; i++)
+ __set_bit(i, ptr->keybit);
+
+ ret = input_register_device(ptr);
+ if (ret) {
+ input_free_device(ptr);
+ xenbus_dev_fatal(dev, ret,
+ "input_register_device(ptr)");
+ goto error;
+ }
+ info->ptr = ptr;
+ }
+
+ /* multi-touch device */
+ if (with_mtouch) {
+ int num_cont, width, height;
+
+ mtouch = input_allocate_device();
+ if (!mtouch)
+ goto error_nomem;
+
+ num_cont = xenbus_read_unsigned(info->xbdev->otherend,
+ XENKBD_FIELD_MT_NUM_CONTACTS,
+ 1);
+ width = xenbus_read_unsigned(info->xbdev->otherend,
+ XENKBD_FIELD_MT_WIDTH,
+ XENFB_WIDTH);
+ height = xenbus_read_unsigned(info->xbdev->otherend,
+ XENKBD_FIELD_MT_HEIGHT,
+ XENFB_HEIGHT);
+
+ mtouch->name = "Xen Virtual Multi-touch";
+ mtouch->phys = info->phys;
+ mtouch->id.bustype = BUS_PCI;
+ mtouch->id.vendor = 0x5853;
+ mtouch->id.product = 0xfffd;
+
+ input_set_abs_params(mtouch, ABS_MT_TOUCH_MAJOR,
+ 0, 255, 0, 0);
+ input_set_abs_params(mtouch, ABS_MT_POSITION_X,
+ 0, width, 0, 0);
+ input_set_abs_params(mtouch, ABS_MT_POSITION_Y,
+ 0, height, 0, 0);
+
+ ret = input_mt_init_slots(mtouch, num_cont, INPUT_MT_DIRECT);
+ if (ret) {
+ input_free_device(mtouch);
+ xenbus_dev_fatal(info->xbdev, ret,
+ "input_mt_init_slots");
+ goto error;
+ }
+
+ ret = input_register_device(mtouch);
+ if (ret) {
+ input_free_device(mtouch);
+ xenbus_dev_fatal(info->xbdev, ret,
+ "input_register_device(mtouch)");
+ goto error;
+ }
+ info->mtouch_cur_contact_id = -1;
+ info->mtouch = mtouch;
+ }
+
+ if (!(with_kbd || with_ptr || with_mtouch)) {
+ ret = -ENXIO;
+ goto error;
+ }
+
+ ret = xenkbd_connect_backend(dev, info);
+ if (ret < 0)
+ goto error;
+
+ return 0;
+
+ error_nomem:
+ ret = -ENOMEM;
+ xenbus_dev_fatal(dev, ret, "allocating device memory");
+ error:
+ xenkbd_remove(dev);
+ return ret;
+}
+
+static int xenkbd_resume(struct xenbus_device *dev)
+{
+ struct xenkbd_info *info = dev_get_drvdata(&dev->dev);
+
+ xenkbd_disconnect_backend(info);
+ memset(info->page, 0, PAGE_SIZE);
+ return xenkbd_connect_backend(dev, info);
+}
+
+static int xenkbd_remove(struct xenbus_device *dev)
+{
+ struct xenkbd_info *info = dev_get_drvdata(&dev->dev);
+
+ xenkbd_disconnect_backend(info);
+ if (info->kbd)
+ input_unregister_device(info->kbd);
+ if (info->ptr)
+ input_unregister_device(info->ptr);
+ if (info->mtouch)
+ input_unregister_device(info->mtouch);
+ free_page((unsigned long)info->page);
+ kfree(info);
+ return 0;
+}
+
+static int xenkbd_connect_backend(struct xenbus_device *dev,
+ struct xenkbd_info *info)
+{
+ int ret, evtchn;
+ struct xenbus_transaction xbt;
+
+ ret = gnttab_grant_foreign_access(dev->otherend_id,
+ virt_to_gfn(info->page), 0);
+ if (ret < 0)
+ return ret;
+ info->gref = ret;
+
+ ret = xenbus_alloc_evtchn(dev, &evtchn);
+ if (ret)
+ goto error_grant;
+ ret = bind_evtchn_to_irqhandler(evtchn, input_handler,
+ 0, dev->devicetype, info);
+ if (ret < 0) {
+ xenbus_dev_fatal(dev, ret, "bind_evtchn_to_irqhandler");
+ goto error_evtchan;
+ }
+ info->irq = ret;
+
+ again:
+ ret = xenbus_transaction_start(&xbt);
+ if (ret) {
+ xenbus_dev_fatal(dev, ret, "starting transaction");
+ goto error_irqh;
+ }
+ ret = xenbus_printf(xbt, dev->nodename, XENKBD_FIELD_RING_REF, "%lu",
+ virt_to_gfn(info->page));
+ if (ret)
+ goto error_xenbus;
+ ret = xenbus_printf(xbt, dev->nodename, XENKBD_FIELD_RING_GREF,
+ "%u", info->gref);
+ if (ret)
+ goto error_xenbus;
+ ret = xenbus_printf(xbt, dev->nodename, XENKBD_FIELD_EVT_CHANNEL, "%u",
+ evtchn);
+ if (ret)
+ goto error_xenbus;
+ ret = xenbus_transaction_end(xbt, 0);
+ if (ret) {
+ if (ret == -EAGAIN)
+ goto again;
+ xenbus_dev_fatal(dev, ret, "completing transaction");
+ goto error_irqh;
+ }
+
+ xenbus_switch_state(dev, XenbusStateInitialised);
+ return 0;
+
+ error_xenbus:
+ xenbus_transaction_end(xbt, 1);
+ xenbus_dev_fatal(dev, ret, "writing xenstore");
+ error_irqh:
+ unbind_from_irqhandler(info->irq, info);
+ info->irq = -1;
+ error_evtchan:
+ xenbus_free_evtchn(dev, evtchn);
+ error_grant:
+ gnttab_end_foreign_access(info->gref, NULL);
+ info->gref = -1;
+ return ret;
+}
+
+static void xenkbd_disconnect_backend(struct xenkbd_info *info)
+{
+ if (info->irq >= 0)
+ unbind_from_irqhandler(info->irq, info);
+ info->irq = -1;
+ if (info->gref >= 0)
+ gnttab_end_foreign_access(info->gref, NULL);
+ info->gref = -1;
+}
+
+static void xenkbd_backend_changed(struct xenbus_device *dev,
+ enum xenbus_state backend_state)
+{
+ switch (backend_state) {
+ case XenbusStateInitialising:
+ case XenbusStateInitialised:
+ case XenbusStateReconfiguring:
+ case XenbusStateReconfigured:
+ case XenbusStateUnknown:
+ break;
+
+ case XenbusStateInitWait:
+ xenbus_switch_state(dev, XenbusStateConnected);
+ break;
+
+ case XenbusStateConnected:
+ /*
+ * Work around xenbus race condition: If backend goes
+ * through InitWait to Connected fast enough, we can
+ * get Connected twice here.
+ */
+ if (dev->state != XenbusStateConnected)
+ xenbus_switch_state(dev, XenbusStateConnected);
+ break;
+
+ case XenbusStateClosed:
+ if (dev->state == XenbusStateClosed)
+ break;
+ fallthrough; /* Missed the backend's CLOSING state */
+ case XenbusStateClosing:
+ xenbus_frontend_closed(dev);
+ break;
+ }
+}
+
+static const struct xenbus_device_id xenkbd_ids[] = {
+ { XENKBD_DRIVER_NAME },
+ { "" }
+};
+
+static struct xenbus_driver xenkbd_driver = {
+ .ids = xenkbd_ids,
+ .probe = xenkbd_probe,
+ .remove = xenkbd_remove,
+ .resume = xenkbd_resume,
+ .otherend_changed = xenkbd_backend_changed,
+ .not_essential = true,
+};
+
+static int __init xenkbd_init(void)
+{
+ if (!xen_domain())
+ return -ENODEV;
+
+ /* Nothing to do if running in dom0. */
+ if (xen_initial_domain())
+ return -ENODEV;
+
+ if (!xen_has_pv_devices())
+ return -ENODEV;
+
+ return xenbus_register_frontend(&xenkbd_driver);
+}
+
+static void __exit xenkbd_cleanup(void)
+{
+ xenbus_unregister_driver(&xenkbd_driver);
+}
+
+module_init(xenkbd_init);
+module_exit(xenkbd_cleanup);
+
+MODULE_DESCRIPTION("Xen virtual keyboard/pointer device frontend");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("xen:" XENKBD_DRIVER_NAME);
diff --git a/drivers/input/misc/yealink.c b/drivers/input/misc/yealink.c
new file mode 100644
index 000000000..69420781d
--- /dev/null
+++ b/drivers/input/misc/yealink.c
@@ -0,0 +1,996 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * drivers/usb/input/yealink.c
+ *
+ * Copyright (c) 2005 Henk Vergonet <Henk.Vergonet@gmail.com>
+ */
+/*
+ * Description:
+ * Driver for the USB-P1K voip usb phone.
+ * This device is produced by Yealink Network Technology Co Ltd
+ * but may be branded under several names:
+ * - Yealink usb-p1k
+ * - Tiptel 115
+ * - ...
+ *
+ * This driver is based on:
+ * - the usbb2k-api http://savannah.nongnu.org/projects/usbb2k-api/
+ * - information from http://memeteau.free.fr/usbb2k
+ * - the xpad-driver drivers/input/joystick/xpad.c
+ *
+ * Thanks to:
+ * - Olivier Vandorpe, for providing the usbb2k-api.
+ * - Martin Diehl, for spotting my memory allocation bug.
+ *
+ * History:
+ * 20050527 henk First version, functional keyboard. Keyboard events
+ * will pop-up on the ../input/eventX bus.
+ * 20050531 henk Added led, LCD, dialtone and sysfs interface.
+ * 20050610 henk Cleanups, make it ready for public consumption.
+ * 20050630 henk Cleanups, fixes in response to comments.
+ * 20050701 henk sysfs write serialisation, fix potential unload races
+ * 20050801 henk Added ringtone, restructure USB
+ * 20050816 henk Merge 2.6.13-rc6
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/rwsem.h>
+#include <linux/usb/input.h>
+#include <linux/map_to_7segment.h>
+
+#include "yealink.h"
+
+#define DRIVER_VERSION "yld-20051230"
+
+#define YEALINK_POLLING_FREQUENCY 10 /* in [Hz] */
+
+struct yld_status {
+ u8 lcd[24];
+ u8 led;
+ u8 dialtone;
+ u8 ringtone;
+ u8 keynum;
+} __attribute__ ((packed));
+
+/*
+ * Register the LCD segment and icon map
+ */
+#define _LOC(k,l) { .a = (k), .m = (l) }
+#define _SEG(t, a, am, b, bm, c, cm, d, dm, e, em, f, fm, g, gm) \
+ { .type = (t), \
+ .u = { .s = { _LOC(a, am), _LOC(b, bm), _LOC(c, cm), \
+ _LOC(d, dm), _LOC(e, em), _LOC(g, gm), \
+ _LOC(f, fm) } } }
+#define _PIC(t, h, hm, n) \
+ { .type = (t), \
+ .u = { .p = { .name = (n), .a = (h), .m = (hm) } } }
+
+static const struct lcd_segment_map {
+ char type;
+ union {
+ struct pictogram_map {
+ u8 a,m;
+ char name[10];
+ } p;
+ struct segment_map {
+ u8 a,m;
+ } s[7];
+ } u;
+} lcdMap[] = {
+#include "yealink.h"
+};
+
+struct yealink_dev {
+ struct input_dev *idev; /* input device */
+ struct usb_device *udev; /* usb device */
+ struct usb_interface *intf; /* usb interface */
+
+ /* irq input channel */
+ struct yld_ctl_packet *irq_data;
+ dma_addr_t irq_dma;
+ struct urb *urb_irq;
+
+ /* control output channel */
+ struct yld_ctl_packet *ctl_data;
+ dma_addr_t ctl_dma;
+ struct usb_ctrlrequest *ctl_req;
+ struct urb *urb_ctl;
+
+ char phys[64]; /* physical device path */
+
+ u8 lcdMap[ARRAY_SIZE(lcdMap)]; /* state of LCD, LED ... */
+ int key_code; /* last reported key */
+
+ unsigned int shutdown:1;
+
+ int stat_ix;
+ union {
+ struct yld_status s;
+ u8 b[sizeof(struct yld_status)];
+ } master, copy;
+};
+
+
+/*******************************************************************************
+ * Yealink lcd interface
+ ******************************************************************************/
+
+/*
+ * Register a default 7 segment character set
+ */
+static SEG7_DEFAULT_MAP(map_seg7);
+
+ /* Display a char,
+ * char '\9' and '\n' are placeholders and do not overwrite the original text.
+ * A space will always hide an icon.
+ */
+static int setChar(struct yealink_dev *yld, int el, int chr)
+{
+ int i, a, m, val;
+
+ if (el >= ARRAY_SIZE(lcdMap))
+ return -EINVAL;
+
+ if (chr == '\t' || chr == '\n')
+ return 0;
+
+ yld->lcdMap[el] = chr;
+
+ if (lcdMap[el].type == '.') {
+ a = lcdMap[el].u.p.a;
+ m = lcdMap[el].u.p.m;
+ if (chr != ' ')
+ yld->master.b[a] |= m;
+ else
+ yld->master.b[a] &= ~m;
+ return 0;
+ }
+
+ val = map_to_seg7(&map_seg7, chr);
+ for (i = 0; i < ARRAY_SIZE(lcdMap[0].u.s); i++) {
+ m = lcdMap[el].u.s[i].m;
+
+ if (m == 0)
+ continue;
+
+ a = lcdMap[el].u.s[i].a;
+ if (val & 1)
+ yld->master.b[a] |= m;
+ else
+ yld->master.b[a] &= ~m;
+ val = val >> 1;
+ }
+ return 0;
+};
+
+/*******************************************************************************
+ * Yealink key interface
+ ******************************************************************************/
+
+/* Map device buttons to internal key events.
+ *
+ * USB-P1K button layout:
+ *
+ * up
+ * IN OUT
+ * down
+ *
+ * pickup C hangup
+ * 1 2 3
+ * 4 5 6
+ * 7 8 9
+ * * 0 #
+ *
+ * The "up" and "down" keys, are symbolised by arrows on the button.
+ * The "pickup" and "hangup" keys are symbolised by a green and red phone
+ * on the button.
+ */
+static int map_p1k_to_key(int scancode)
+{
+ switch(scancode) { /* phone key: */
+ case 0x23: return KEY_LEFT; /* IN */
+ case 0x33: return KEY_UP; /* up */
+ case 0x04: return KEY_RIGHT; /* OUT */
+ case 0x24: return KEY_DOWN; /* down */
+ case 0x03: return KEY_ENTER; /* pickup */
+ case 0x14: return KEY_BACKSPACE; /* C */
+ case 0x13: return KEY_ESC; /* hangup */
+ case 0x00: return KEY_1; /* 1 */
+ case 0x01: return KEY_2; /* 2 */
+ case 0x02: return KEY_3; /* 3 */
+ case 0x10: return KEY_4; /* 4 */
+ case 0x11: return KEY_5; /* 5 */
+ case 0x12: return KEY_6; /* 6 */
+ case 0x20: return KEY_7; /* 7 */
+ case 0x21: return KEY_8; /* 8 */
+ case 0x22: return KEY_9; /* 9 */
+ case 0x30: return KEY_KPASTERISK; /* * */
+ case 0x31: return KEY_0; /* 0 */
+ case 0x32: return KEY_LEFTSHIFT |
+ KEY_3 << 8; /* # */
+ }
+ return -EINVAL;
+}
+
+/* Completes a request by converting the data into events for the
+ * input subsystem.
+ *
+ * The key parameter can be cascaded: key2 << 8 | key1
+ */
+static void report_key(struct yealink_dev *yld, int key)
+{
+ struct input_dev *idev = yld->idev;
+
+ if (yld->key_code >= 0) {
+ /* old key up */
+ input_report_key(idev, yld->key_code & 0xff, 0);
+ if (yld->key_code >> 8)
+ input_report_key(idev, yld->key_code >> 8, 0);
+ }
+
+ yld->key_code = key;
+ if (key >= 0) {
+ /* new valid key */
+ input_report_key(idev, key & 0xff, 1);
+ if (key >> 8)
+ input_report_key(idev, key >> 8, 1);
+ }
+ input_sync(idev);
+}
+
+/*******************************************************************************
+ * Yealink usb communication interface
+ ******************************************************************************/
+
+static int yealink_cmd(struct yealink_dev *yld, struct yld_ctl_packet *p)
+{
+ u8 *buf = (u8 *)p;
+ int i;
+ u8 sum = 0;
+
+ for(i=0; i<USB_PKT_LEN-1; i++)
+ sum -= buf[i];
+ p->sum = sum;
+ return usb_control_msg(yld->udev,
+ usb_sndctrlpipe(yld->udev, 0),
+ USB_REQ_SET_CONFIGURATION,
+ USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT,
+ 0x200, 3,
+ p, sizeof(*p),
+ USB_CTRL_SET_TIMEOUT);
+}
+
+static u8 default_ringtone[] = {
+ 0xEF, /* volume [0-255] */
+ 0xFB, 0x1E, 0x00, 0x0C, /* 1250 [hz], 12/100 [s] */
+ 0xFC, 0x18, 0x00, 0x0C, /* 1000 [hz], 12/100 [s] */
+ 0xFB, 0x1E, 0x00, 0x0C,
+ 0xFC, 0x18, 0x00, 0x0C,
+ 0xFB, 0x1E, 0x00, 0x0C,
+ 0xFC, 0x18, 0x00, 0x0C,
+ 0xFB, 0x1E, 0x00, 0x0C,
+ 0xFC, 0x18, 0x00, 0x0C,
+ 0xFF, 0xFF, 0x01, 0x90, /* silent, 400/100 [s] */
+ 0x00, 0x00 /* end of sequence */
+};
+
+static int yealink_set_ringtone(struct yealink_dev *yld, u8 *buf, size_t size)
+{
+ struct yld_ctl_packet *p = yld->ctl_data;
+ int ix, len;
+
+ if (size <= 0)
+ return -EINVAL;
+
+ /* Set the ringtone volume */
+ memset(yld->ctl_data, 0, sizeof(*(yld->ctl_data)));
+ yld->ctl_data->cmd = CMD_RING_VOLUME;
+ yld->ctl_data->size = 1;
+ yld->ctl_data->data[0] = buf[0];
+ yealink_cmd(yld, p);
+
+ buf++;
+ size--;
+
+ p->cmd = CMD_RING_NOTE;
+ ix = 0;
+ while (size != ix) {
+ len = size - ix;
+ if (len > sizeof(p->data))
+ len = sizeof(p->data);
+ p->size = len;
+ p->offset = cpu_to_be16(ix);
+ memcpy(p->data, &buf[ix], len);
+ yealink_cmd(yld, p);
+ ix += len;
+ }
+ return 0;
+}
+
+/* keep stat_master & stat_copy in sync.
+ */
+static int yealink_do_idle_tasks(struct yealink_dev *yld)
+{
+ u8 val;
+ int i, ix, len;
+
+ ix = yld->stat_ix;
+
+ memset(yld->ctl_data, 0, sizeof(*(yld->ctl_data)));
+ yld->ctl_data->cmd = CMD_KEYPRESS;
+ yld->ctl_data->size = 1;
+ yld->ctl_data->sum = 0xff - CMD_KEYPRESS;
+
+ /* If state update pointer wraps do a KEYPRESS first. */
+ if (ix >= sizeof(yld->master)) {
+ yld->stat_ix = 0;
+ return 0;
+ }
+
+ /* find update candidates: copy != master */
+ do {
+ val = yld->master.b[ix];
+ if (val != yld->copy.b[ix])
+ goto send_update;
+ } while (++ix < sizeof(yld->master));
+
+ /* nothing todo, wait a bit and poll for a KEYPRESS */
+ yld->stat_ix = 0;
+ /* TODO how can we wait abit. ??
+ * msleep_interruptible(1000 / YEALINK_POLLING_FREQUENCY);
+ */
+ return 0;
+
+send_update:
+
+ /* Setup an appropriate update request */
+ yld->copy.b[ix] = val;
+ yld->ctl_data->data[0] = val;
+
+ switch(ix) {
+ case offsetof(struct yld_status, led):
+ yld->ctl_data->cmd = CMD_LED;
+ yld->ctl_data->sum = -1 - CMD_LED - val;
+ break;
+ case offsetof(struct yld_status, dialtone):
+ yld->ctl_data->cmd = CMD_DIALTONE;
+ yld->ctl_data->sum = -1 - CMD_DIALTONE - val;
+ break;
+ case offsetof(struct yld_status, ringtone):
+ yld->ctl_data->cmd = CMD_RINGTONE;
+ yld->ctl_data->sum = -1 - CMD_RINGTONE - val;
+ break;
+ case offsetof(struct yld_status, keynum):
+ val--;
+ val &= 0x1f;
+ yld->ctl_data->cmd = CMD_SCANCODE;
+ yld->ctl_data->offset = cpu_to_be16(val);
+ yld->ctl_data->data[0] = 0;
+ yld->ctl_data->sum = -1 - CMD_SCANCODE - val;
+ break;
+ default:
+ len = sizeof(yld->master.s.lcd) - ix;
+ if (len > sizeof(yld->ctl_data->data))
+ len = sizeof(yld->ctl_data->data);
+
+ /* Combine up to <len> consecutive LCD bytes in a singe request
+ */
+ yld->ctl_data->cmd = CMD_LCD;
+ yld->ctl_data->offset = cpu_to_be16(ix);
+ yld->ctl_data->size = len;
+ yld->ctl_data->sum = -CMD_LCD - ix - val - len;
+ for(i=1; i<len; i++) {
+ ix++;
+ val = yld->master.b[ix];
+ yld->copy.b[ix] = val;
+ yld->ctl_data->data[i] = val;
+ yld->ctl_data->sum -= val;
+ }
+ }
+ yld->stat_ix = ix + 1;
+ return 1;
+}
+
+/* Decide on how to handle responses
+ *
+ * The state transition diagram is somethhing like:
+ *
+ * syncState<--+
+ * | |
+ * | idle
+ * \|/ |
+ * init --ok--> waitForKey --ok--> getKey
+ * ^ ^ |
+ * | +-------ok-------+
+ * error,start
+ *
+ */
+static void urb_irq_callback(struct urb *urb)
+{
+ struct yealink_dev *yld = urb->context;
+ int ret, status = urb->status;
+
+ if (status)
+ dev_err(&yld->intf->dev, "%s - urb status %d\n",
+ __func__, status);
+
+ switch (yld->irq_data->cmd) {
+ case CMD_KEYPRESS:
+
+ yld->master.s.keynum = yld->irq_data->data[0];
+ break;
+
+ case CMD_SCANCODE:
+ dev_dbg(&yld->intf->dev, "get scancode %x\n",
+ yld->irq_data->data[0]);
+
+ report_key(yld, map_p1k_to_key(yld->irq_data->data[0]));
+ break;
+
+ default:
+ dev_err(&yld->intf->dev, "unexpected response %x\n",
+ yld->irq_data->cmd);
+ }
+
+ yealink_do_idle_tasks(yld);
+
+ if (!yld->shutdown) {
+ ret = usb_submit_urb(yld->urb_ctl, GFP_ATOMIC);
+ if (ret && ret != -EPERM)
+ dev_err(&yld->intf->dev,
+ "%s - usb_submit_urb failed %d\n",
+ __func__, ret);
+ }
+}
+
+static void urb_ctl_callback(struct urb *urb)
+{
+ struct yealink_dev *yld = urb->context;
+ int ret = 0, status = urb->status;
+
+ if (status)
+ dev_err(&yld->intf->dev, "%s - urb status %d\n",
+ __func__, status);
+
+ switch (yld->ctl_data->cmd) {
+ case CMD_KEYPRESS:
+ case CMD_SCANCODE:
+ /* ask for a response */
+ if (!yld->shutdown)
+ ret = usb_submit_urb(yld->urb_irq, GFP_ATOMIC);
+ break;
+ default:
+ /* send new command */
+ yealink_do_idle_tasks(yld);
+ if (!yld->shutdown)
+ ret = usb_submit_urb(yld->urb_ctl, GFP_ATOMIC);
+ break;
+ }
+
+ if (ret && ret != -EPERM)
+ dev_err(&yld->intf->dev, "%s - usb_submit_urb failed %d\n",
+ __func__, ret);
+}
+
+/*******************************************************************************
+ * input event interface
+ ******************************************************************************/
+
+/* TODO should we issue a ringtone on a SND_BELL event?
+static int input_ev(struct input_dev *dev, unsigned int type,
+ unsigned int code, int value)
+{
+
+ if (type != EV_SND)
+ return -EINVAL;
+
+ switch (code) {
+ case SND_BELL:
+ case SND_TONE:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+*/
+
+static int input_open(struct input_dev *dev)
+{
+ struct yealink_dev *yld = input_get_drvdata(dev);
+ int i, ret;
+
+ dev_dbg(&yld->intf->dev, "%s\n", __func__);
+
+ /* force updates to device */
+ for (i = 0; i<sizeof(yld->master); i++)
+ yld->copy.b[i] = ~yld->master.b[i];
+ yld->key_code = -1; /* no keys pressed */
+
+ yealink_set_ringtone(yld, default_ringtone, sizeof(default_ringtone));
+
+ /* issue INIT */
+ memset(yld->ctl_data, 0, sizeof(*(yld->ctl_data)));
+ yld->ctl_data->cmd = CMD_INIT;
+ yld->ctl_data->size = 10;
+ yld->ctl_data->sum = 0x100-CMD_INIT-10;
+ if ((ret = usb_submit_urb(yld->urb_ctl, GFP_KERNEL)) != 0) {
+ dev_dbg(&yld->intf->dev,
+ "%s - usb_submit_urb failed with result %d\n",
+ __func__, ret);
+ return ret;
+ }
+ return 0;
+}
+
+static void input_close(struct input_dev *dev)
+{
+ struct yealink_dev *yld = input_get_drvdata(dev);
+
+ yld->shutdown = 1;
+ /*
+ * Make sure the flag is seen by other CPUs before we start
+ * killing URBs so new URBs won't be submitted
+ */
+ smp_wmb();
+
+ usb_kill_urb(yld->urb_ctl);
+ usb_kill_urb(yld->urb_irq);
+
+ yld->shutdown = 0;
+ smp_wmb();
+}
+
+/*******************************************************************************
+ * sysfs interface
+ ******************************************************************************/
+
+static DECLARE_RWSEM(sysfs_rwsema);
+
+/* Interface to the 7-segments translation table aka. char set.
+ */
+static ssize_t show_map(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ memcpy(buf, &map_seg7, sizeof(map_seg7));
+ return sizeof(map_seg7);
+}
+
+static ssize_t store_map(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t cnt)
+{
+ if (cnt != sizeof(map_seg7))
+ return -EINVAL;
+ memcpy(&map_seg7, buf, sizeof(map_seg7));
+ return sizeof(map_seg7);
+}
+
+/* Interface to the LCD.
+ */
+
+/* Reading /sys/../lineX will return the format string with its settings:
+ *
+ * Example:
+ * cat ./line3
+ * 888888888888
+ * Linux Rocks!
+ */
+static ssize_t show_line(struct device *dev, char *buf, int a, int b)
+{
+ struct yealink_dev *yld;
+ int i;
+
+ down_read(&sysfs_rwsema);
+ yld = dev_get_drvdata(dev);
+ if (yld == NULL) {
+ up_read(&sysfs_rwsema);
+ return -ENODEV;
+ }
+
+ for (i = a; i < b; i++)
+ *buf++ = lcdMap[i].type;
+ *buf++ = '\n';
+ for (i = a; i < b; i++)
+ *buf++ = yld->lcdMap[i];
+ *buf++ = '\n';
+ *buf = 0;
+
+ up_read(&sysfs_rwsema);
+ return 3 + ((b - a) << 1);
+}
+
+static ssize_t show_line1(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ return show_line(dev, buf, LCD_LINE1_OFFSET, LCD_LINE2_OFFSET);
+}
+
+static ssize_t show_line2(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ return show_line(dev, buf, LCD_LINE2_OFFSET, LCD_LINE3_OFFSET);
+}
+
+static ssize_t show_line3(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ return show_line(dev, buf, LCD_LINE3_OFFSET, LCD_LINE4_OFFSET);
+}
+
+/* Writing to /sys/../lineX will set the coresponding LCD line.
+ * - Excess characters are ignored.
+ * - If less characters are written than allowed, the remaining digits are
+ * unchanged.
+ * - The '\n' or '\t' char is a placeholder, it does not overwrite the
+ * original content.
+ */
+static ssize_t store_line(struct device *dev, const char *buf, size_t count,
+ int el, size_t len)
+{
+ struct yealink_dev *yld;
+ int i;
+
+ down_write(&sysfs_rwsema);
+ yld = dev_get_drvdata(dev);
+ if (yld == NULL) {
+ up_write(&sysfs_rwsema);
+ return -ENODEV;
+ }
+
+ if (len > count)
+ len = count;
+ for (i = 0; i < len; i++)
+ setChar(yld, el++, buf[i]);
+
+ up_write(&sysfs_rwsema);
+ return count;
+}
+
+static ssize_t store_line1(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ return store_line(dev, buf, count, LCD_LINE1_OFFSET, LCD_LINE1_SIZE);
+}
+
+static ssize_t store_line2(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ return store_line(dev, buf, count, LCD_LINE2_OFFSET, LCD_LINE2_SIZE);
+}
+
+static ssize_t store_line3(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ return store_line(dev, buf, count, LCD_LINE3_OFFSET, LCD_LINE3_SIZE);
+}
+
+/* Interface to visible and audible "icons", these include:
+ * pictures on the LCD, the LED, and the dialtone signal.
+ */
+
+/* Get a list of "switchable elements" with their current state. */
+static ssize_t get_icons(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct yealink_dev *yld;
+ int i, ret = 1;
+
+ down_read(&sysfs_rwsema);
+ yld = dev_get_drvdata(dev);
+ if (yld == NULL) {
+ up_read(&sysfs_rwsema);
+ return -ENODEV;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(lcdMap); i++) {
+ if (lcdMap[i].type != '.')
+ continue;
+ ret += sprintf(&buf[ret], "%s %s\n",
+ yld->lcdMap[i] == ' ' ? " " : "on",
+ lcdMap[i].u.p.name);
+ }
+ up_read(&sysfs_rwsema);
+ return ret;
+}
+
+/* Change the visibility of a particular element. */
+static ssize_t set_icon(struct device *dev, const char *buf, size_t count,
+ int chr)
+{
+ struct yealink_dev *yld;
+ int i;
+
+ down_write(&sysfs_rwsema);
+ yld = dev_get_drvdata(dev);
+ if (yld == NULL) {
+ up_write(&sysfs_rwsema);
+ return -ENODEV;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(lcdMap); i++) {
+ if (lcdMap[i].type != '.')
+ continue;
+ if (strncmp(buf, lcdMap[i].u.p.name, count) == 0) {
+ setChar(yld, i, chr);
+ break;
+ }
+ }
+
+ up_write(&sysfs_rwsema);
+ return count;
+}
+
+static ssize_t show_icon(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ return set_icon(dev, buf, count, buf[0]);
+}
+
+static ssize_t hide_icon(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ return set_icon(dev, buf, count, ' ');
+}
+
+/* Upload a ringtone to the device.
+ */
+
+/* Stores raw ringtone data in the phone */
+static ssize_t store_ringtone(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct yealink_dev *yld;
+
+ down_write(&sysfs_rwsema);
+ yld = dev_get_drvdata(dev);
+ if (yld == NULL) {
+ up_write(&sysfs_rwsema);
+ return -ENODEV;
+ }
+
+ /* TODO locking with async usb control interface??? */
+ yealink_set_ringtone(yld, (char *)buf, count);
+ up_write(&sysfs_rwsema);
+ return count;
+}
+
+#define _M444 S_IRUGO
+#define _M664 S_IRUGO|S_IWUSR|S_IWGRP
+#define _M220 S_IWUSR|S_IWGRP
+
+static DEVICE_ATTR(map_seg7 , _M664, show_map , store_map );
+static DEVICE_ATTR(line1 , _M664, show_line1 , store_line1 );
+static DEVICE_ATTR(line2 , _M664, show_line2 , store_line2 );
+static DEVICE_ATTR(line3 , _M664, show_line3 , store_line3 );
+static DEVICE_ATTR(get_icons , _M444, get_icons , NULL );
+static DEVICE_ATTR(show_icon , _M220, NULL , show_icon );
+static DEVICE_ATTR(hide_icon , _M220, NULL , hide_icon );
+static DEVICE_ATTR(ringtone , _M220, NULL , store_ringtone);
+
+static struct attribute *yld_attributes[] = {
+ &dev_attr_line1.attr,
+ &dev_attr_line2.attr,
+ &dev_attr_line3.attr,
+ &dev_attr_get_icons.attr,
+ &dev_attr_show_icon.attr,
+ &dev_attr_hide_icon.attr,
+ &dev_attr_map_seg7.attr,
+ &dev_attr_ringtone.attr,
+ NULL
+};
+
+static const struct attribute_group yld_attr_group = {
+ .attrs = yld_attributes
+};
+
+/*******************************************************************************
+ * Linux interface and usb initialisation
+ ******************************************************************************/
+
+struct driver_info {
+ char *name;
+};
+
+static const struct driver_info info_P1K = {
+ .name = "Yealink usb-p1k",
+};
+
+static const struct usb_device_id usb_table [] = {
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+ USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x6993,
+ .idProduct = 0xb001,
+ .bInterfaceClass = USB_CLASS_HID,
+ .bInterfaceSubClass = 0,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&info_P1K
+ },
+ { }
+};
+
+static int usb_cleanup(struct yealink_dev *yld, int err)
+{
+ if (yld == NULL)
+ return err;
+
+ if (yld->idev) {
+ if (err)
+ input_free_device(yld->idev);
+ else
+ input_unregister_device(yld->idev);
+ }
+
+ usb_free_urb(yld->urb_irq);
+ usb_free_urb(yld->urb_ctl);
+
+ kfree(yld->ctl_req);
+ usb_free_coherent(yld->udev, USB_PKT_LEN, yld->ctl_data, yld->ctl_dma);
+ usb_free_coherent(yld->udev, USB_PKT_LEN, yld->irq_data, yld->irq_dma);
+
+ kfree(yld);
+ return err;
+}
+
+static void usb_disconnect(struct usb_interface *intf)
+{
+ struct yealink_dev *yld;
+
+ down_write(&sysfs_rwsema);
+ yld = usb_get_intfdata(intf);
+ sysfs_remove_group(&intf->dev.kobj, &yld_attr_group);
+ usb_set_intfdata(intf, NULL);
+ up_write(&sysfs_rwsema);
+
+ usb_cleanup(yld, 0);
+}
+
+static int usb_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev (intf);
+ struct driver_info *nfo = (struct driver_info *)id->driver_info;
+ struct usb_host_interface *interface;
+ struct usb_endpoint_descriptor *endpoint;
+ struct yealink_dev *yld;
+ struct input_dev *input_dev;
+ int ret, pipe, i;
+
+ interface = intf->cur_altsetting;
+
+ if (interface->desc.bNumEndpoints < 1)
+ return -ENODEV;
+
+ endpoint = &interface->endpoint[0].desc;
+ if (!usb_endpoint_is_int_in(endpoint))
+ return -ENODEV;
+
+ yld = kzalloc(sizeof(struct yealink_dev), GFP_KERNEL);
+ if (!yld)
+ return -ENOMEM;
+
+ yld->udev = udev;
+ yld->intf = intf;
+
+ yld->idev = input_dev = input_allocate_device();
+ if (!input_dev)
+ return usb_cleanup(yld, -ENOMEM);
+
+ /* allocate usb buffers */
+ yld->irq_data = usb_alloc_coherent(udev, USB_PKT_LEN,
+ GFP_KERNEL, &yld->irq_dma);
+ if (yld->irq_data == NULL)
+ return usb_cleanup(yld, -ENOMEM);
+
+ yld->ctl_data = usb_alloc_coherent(udev, USB_PKT_LEN,
+ GFP_KERNEL, &yld->ctl_dma);
+ if (!yld->ctl_data)
+ return usb_cleanup(yld, -ENOMEM);
+
+ yld->ctl_req = kmalloc(sizeof(*(yld->ctl_req)), GFP_KERNEL);
+ if (yld->ctl_req == NULL)
+ return usb_cleanup(yld, -ENOMEM);
+
+ /* allocate urb structures */
+ yld->urb_irq = usb_alloc_urb(0, GFP_KERNEL);
+ if (yld->urb_irq == NULL)
+ return usb_cleanup(yld, -ENOMEM);
+
+ yld->urb_ctl = usb_alloc_urb(0, GFP_KERNEL);
+ if (yld->urb_ctl == NULL)
+ return usb_cleanup(yld, -ENOMEM);
+
+ /* get a handle to the interrupt data pipe */
+ pipe = usb_rcvintpipe(udev, endpoint->bEndpointAddress);
+ ret = usb_maxpacket(udev, pipe);
+ if (ret != USB_PKT_LEN)
+ dev_err(&intf->dev, "invalid payload size %d, expected %zd\n",
+ ret, USB_PKT_LEN);
+
+ /* initialise irq urb */
+ usb_fill_int_urb(yld->urb_irq, udev, pipe, yld->irq_data,
+ USB_PKT_LEN,
+ urb_irq_callback,
+ yld, endpoint->bInterval);
+ yld->urb_irq->transfer_dma = yld->irq_dma;
+ yld->urb_irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ yld->urb_irq->dev = udev;
+
+ /* initialise ctl urb */
+ yld->ctl_req->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE |
+ USB_DIR_OUT;
+ yld->ctl_req->bRequest = USB_REQ_SET_CONFIGURATION;
+ yld->ctl_req->wValue = cpu_to_le16(0x200);
+ yld->ctl_req->wIndex = cpu_to_le16(interface->desc.bInterfaceNumber);
+ yld->ctl_req->wLength = cpu_to_le16(USB_PKT_LEN);
+
+ usb_fill_control_urb(yld->urb_ctl, udev, usb_sndctrlpipe(udev, 0),
+ (void *)yld->ctl_req, yld->ctl_data, USB_PKT_LEN,
+ urb_ctl_callback, yld);
+ yld->urb_ctl->transfer_dma = yld->ctl_dma;
+ yld->urb_ctl->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ yld->urb_ctl->dev = udev;
+
+ /* find out the physical bus location */
+ usb_make_path(udev, yld->phys, sizeof(yld->phys));
+ strlcat(yld->phys, "/input0", sizeof(yld->phys));
+
+ /* register settings for the input device */
+ input_dev->name = nfo->name;
+ input_dev->phys = yld->phys;
+ usb_to_input_id(udev, &input_dev->id);
+ input_dev->dev.parent = &intf->dev;
+
+ input_set_drvdata(input_dev, yld);
+
+ input_dev->open = input_open;
+ input_dev->close = input_close;
+ /* input_dev->event = input_ev; TODO */
+
+ /* register available key events */
+ input_dev->evbit[0] = BIT_MASK(EV_KEY);
+ for (i = 0; i < 256; i++) {
+ int k = map_p1k_to_key(i);
+ if (k >= 0) {
+ set_bit(k & 0xff, input_dev->keybit);
+ if (k >> 8)
+ set_bit(k >> 8, input_dev->keybit);
+ }
+ }
+
+ ret = input_register_device(yld->idev);
+ if (ret)
+ return usb_cleanup(yld, ret);
+
+ usb_set_intfdata(intf, yld);
+
+ /* clear visible elements */
+ for (i = 0; i < ARRAY_SIZE(lcdMap); i++)
+ setChar(yld, i, ' ');
+
+ /* display driver version on LCD line 3 */
+ store_line3(&intf->dev, NULL,
+ DRIVER_VERSION, sizeof(DRIVER_VERSION));
+
+ /* Register sysfs hooks (don't care about failure) */
+ ret = sysfs_create_group(&intf->dev.kobj, &yld_attr_group);
+ return 0;
+}
+
+static struct usb_driver yealink_driver = {
+ .name = "yealink",
+ .probe = usb_probe,
+ .disconnect = usb_disconnect,
+ .id_table = usb_table,
+};
+
+module_usb_driver(yealink_driver);
+
+MODULE_DEVICE_TABLE (usb, usb_table);
+
+MODULE_AUTHOR("Henk Vergonet");
+MODULE_DESCRIPTION("Yealink phone driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/yealink.h b/drivers/input/misc/yealink.h
new file mode 100644
index 000000000..69f700431
--- /dev/null
+++ b/drivers/input/misc/yealink.h
@@ -0,0 +1,206 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * drivers/usb/input/yealink.h
+ *
+ * Copyright (c) 2005 Henk Vergonet <Henk.Vergonet@gmail.com>
+ */
+#ifndef INPUT_YEALINK_H
+#define INPUT_YEALINK_H
+
+/* Using the control channel on interface 3 various aspects of the phone
+ * can be controlled like LCD, LED, dialtone and the ringtone.
+ */
+
+struct yld_ctl_packet {
+ u8 cmd; /* command code, see below */
+ u8 size; /* 1-11, size of used data bytes. */
+ __be16 offset; /* internal packet offset */
+ u8 data[11];
+ s8 sum; /* negative sum of 15 preceding bytes */
+} __attribute__ ((packed));
+
+#define USB_PKT_LEN sizeof(struct yld_ctl_packet)
+
+/* The following yld_ctl_packet's are available: */
+
+/* Init registers
+ *
+ * cmd 0x8e
+ * size 10
+ * offset 0
+ * data 0,0,0,0....
+ */
+#define CMD_INIT 0x8e
+
+/* Request key scan
+ *
+ * cmd 0x80
+ * size 1
+ * offset 0
+ * data[0] on return returns the key number, if it changes there's a new
+ * key pressed.
+ */
+#define CMD_KEYPRESS 0x80
+
+/* Request scancode
+ *
+ * cmd 0x81
+ * size 1
+ * offset key number [0-1f]
+ * data[0] on return returns the scancode
+ */
+#define CMD_SCANCODE 0x81
+
+/* Set LCD
+ *
+ * cmd 0x04
+ * size 1-11
+ * offset 0-23
+ * data segment bits
+ */
+#define CMD_LCD 0x04
+
+/* Set led
+ *
+ * cmd 0x05
+ * size 1
+ * offset 0
+ * data[0] 0 OFF / 1 ON
+ */
+#define CMD_LED 0x05
+
+/* Set ringtone volume
+ *
+ * cmd 0x11
+ * size 1
+ * offset 0
+ * data[0] 0-0xff volume
+ */
+#define CMD_RING_VOLUME 0x11
+
+/* Set ringtone notes
+ *
+ * cmd 0x02
+ * size 1-11
+ * offset 0->
+ * data binary representation LE16(-freq), LE16(duration) ....
+ */
+#define CMD_RING_NOTE 0x02
+
+/* Sound ringtone via the speaker on the back
+ *
+ * cmd 0x03
+ * size 1
+ * offset 0
+ * data[0] 0 OFF / 0x24 ON
+ */
+#define CMD_RINGTONE 0x03
+
+/* Sound dial tone via the ear speaker
+ *
+ * cmd 0x09
+ * size 1
+ * offset 0
+ * data[0] 0 OFF / 1 ON
+ */
+#define CMD_DIALTONE 0x09
+
+#endif /* INPUT_YEALINK_H */
+
+
+#if defined(_SEG) && defined(_PIC)
+/* This table maps the LCD segments onto individual bit positions in the
+ * yld_status struct.
+ */
+
+/* LCD, each segment must be driven separately.
+ *
+ * Layout:
+ *
+ * |[] [][] [][] [][] in |[][]
+ * |[] M [][] D [][] : [][] out |[][]
+ * store
+ *
+ * NEW REP SU MO TU WE TH FR SA
+ *
+ * [] [] [] [] [] [] [] [] [] [] [] []
+ * [] [] [] [] [] [] [] [] [] [] [] []
+ */
+
+/* Line 1
+ * Format : 18.e8.M8.88...188
+ * Icon names : M D : IN OUT STORE
+ */
+#define LCD_LINE1_OFFSET 0
+#define LCD_LINE1_SIZE 17
+
+/* Note: first g then f => ! ! */
+/* _SEG( type a b c d e g f ) */
+ _SEG('1', 0,0 , 22,2 , 22,2 , 0,0 , 0,0 , 0,0 , 0,0 ),
+ _SEG('8', 20,1 , 20,2 , 20,4 , 20,8 , 21,4 , 21,2 , 21,1 ),
+ _PIC('.', 22,1 , "M" ),
+ _SEG('e', 18,1 , 18,2 , 18,4 , 18,1 , 19,2 , 18,1 , 19,1 ),
+ _SEG('8', 16,1 , 16,2 , 16,4 , 16,8 , 17,4 , 17,2 , 17,1 ),
+ _PIC('.', 15,8 , "D" ),
+ _SEG('M', 14,1 , 14,2 , 14,4 , 14,1 , 15,4 , 15,2 , 15,1 ),
+ _SEG('8', 12,1 , 12,2 , 12,4 , 12,8 , 13,4 , 13,2 , 13,1 ),
+ _PIC('.', 11,8 , ":" ),
+ _SEG('8', 10,1 , 10,2 , 10,4 , 10,8 , 11,4 , 11,2 , 11,1 ),
+ _SEG('8', 8,1 , 8,2 , 8,4 , 8,8 , 9,4 , 9,2 , 9,1 ),
+ _PIC('.', 7,1 , "IN" ),
+ _PIC('.', 7,2 , "OUT" ),
+ _PIC('.', 7,4 , "STORE" ),
+ _SEG('1', 0,0 , 5,1 , 5,1 , 0,0 , 0,0 , 0,0 , 0,0 ),
+ _SEG('8', 4,1 , 4,2 , 4,4 , 4,8 , 5,8 , 5,4 , 5,2 ),
+ _SEG('8', 2,1 , 2,2 , 2,4 , 2,8 , 3,4 , 3,2 , 3,1 ),
+
+/* Line 2
+ * Format : .........
+ * Pict. name : NEW REP SU MO TU WE TH FR SA
+ */
+#define LCD_LINE2_OFFSET LCD_LINE1_OFFSET + LCD_LINE1_SIZE
+#define LCD_LINE2_SIZE 9
+
+ _PIC('.', 23,2 , "NEW" ),
+ _PIC('.', 23,4 , "REP" ),
+ _PIC('.', 1,8 , "SU" ),
+ _PIC('.', 1,4 , "MO" ),
+ _PIC('.', 1,2 , "TU" ),
+ _PIC('.', 1,1 , "WE" ),
+ _PIC('.', 0,1 , "TH" ),
+ _PIC('.', 0,2 , "FR" ),
+ _PIC('.', 0,4 , "SA" ),
+
+/* Line 3
+ * Format : 888888888888
+ */
+#define LCD_LINE3_OFFSET LCD_LINE2_OFFSET + LCD_LINE2_SIZE
+#define LCD_LINE3_SIZE 12
+
+ _SEG('8', 22,16, 22,32, 22,64, 22,128, 23,128, 23,64, 23,32 ),
+ _SEG('8', 20,16, 20,32, 20,64, 20,128, 21,128, 21,64, 21,32 ),
+ _SEG('8', 18,16, 18,32, 18,64, 18,128, 19,128, 19,64, 19,32 ),
+ _SEG('8', 16,16, 16,32, 16,64, 16,128, 17,128, 17,64, 17,32 ),
+ _SEG('8', 14,16, 14,32, 14,64, 14,128, 15,128, 15,64, 15,32 ),
+ _SEG('8', 12,16, 12,32, 12,64, 12,128, 13,128, 13,64, 13,32 ),
+ _SEG('8', 10,16, 10,32, 10,64, 10,128, 11,128, 11,64, 11,32 ),
+ _SEG('8', 8,16, 8,32, 8,64, 8,128, 9,128, 9,64, 9,32 ),
+ _SEG('8', 6,16, 6,32, 6,64, 6,128, 7,128, 7,64, 7,32 ),
+ _SEG('8', 4,16, 4,32, 4,64, 4,128, 5,128, 5,64, 5,32 ),
+ _SEG('8', 2,16, 2,32, 2,64, 2,128, 3,128, 3,64, 3,32 ),
+ _SEG('8', 0,16, 0,32, 0,64, 0,128, 1,128, 1,64, 1,32 ),
+
+/* Line 4
+ *
+ * The LED, DIALTONE and RINGTONE are implemented as icons and use the same
+ * sysfs interface.
+ */
+#define LCD_LINE4_OFFSET LCD_LINE3_OFFSET + LCD_LINE3_SIZE
+
+ _PIC('.', offsetof(struct yld_status, led) , 0x01, "LED" ),
+ _PIC('.', offsetof(struct yld_status, dialtone) , 0x01, "DIALTONE" ),
+ _PIC('.', offsetof(struct yld_status, ringtone) , 0x24, "RINGTONE" ),
+
+#undef _SEG
+#undef _PIC
+#endif /* _SEG && _PIC */