diff options
Diffstat (limited to 'drivers/input/misc')
91 files changed, 37403 insertions, 0 deletions
diff --git a/drivers/input/misc/88pm80x_onkey.c b/drivers/input/misc/88pm80x_onkey.c new file mode 100644 index 0000000000..51c8a326fd --- /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 0000000000..0f8b7ffb3a --- /dev/null +++ b/drivers/input/misc/88pm860x_onkey.c @@ -0,0 +1,146 @@ +/* + * 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 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 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 DEFINE_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 = pm_sleep_ptr(&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 0000000000..6ba984d7f0 --- /dev/null +++ b/drivers/input/misc/Kconfig @@ -0,0 +1,942 @@ +# 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 + 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_BBNSM_PWRKEY + tristate "NXP BBNSM Power Key Driver" + depends on ARCH_MXC || COMPILE_TEST + depends on OF + help + This is the bbnsm powerkey driver for the NXP i.MX application + processors. + + To compile this driver as a module, choose M here; the + module will be called bbnsm_pwrkey. + +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_TPS65219_PWRBUTTON + tristate "TPS65219 Power button driver" + depends on MFD_TPS65219 + help + Say Y here if you want to enable power button reporting for + TPS65219 Power Management IC devices. + + To compile this driver as a module, choose M here. The module will + be called tps65219-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_RK8XX + 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_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/D capacitive touch controller" + depends on I2C + help + Say Y to enable support for the Azoteq IQS7222A/B/C/D 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 0000000000..04296a4abe --- /dev/null +++ b/drivers/input/misc/Makefile @@ -0,0 +1,92 @@ +# 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_BBNSM_PWRKEY) += nxp-bbnsm-pwrkey.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_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_TPS65219_PWRBUTTON) += tps65219-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 0000000000..a9b9013680 --- /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 0000000000..679fcfea94 --- /dev/null +++ b/drivers/input/misc/ad714x-i2c.c @@ -0,0 +1,97 @@ +// 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 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) +{ + 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 = pm_sleep_ptr(&ad714x_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 0000000000..eb13b4cd65 --- /dev/null +++ b/drivers/input/misc/ad714x-spi.c @@ -0,0 +1,103 @@ +// 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 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 = pm_sleep_ptr(&ad714x_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 0000000000..1acd8429c5 --- /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); + +static int ad714x_suspend(struct device *dev) +{ + struct ad714x_chip *ad714x = dev_get_drvdata(dev); + 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; +} + +static int ad714x_resume(struct device *dev) +{ + struct ad714x_chip *ad714x = dev_get_drvdata(dev); + 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_SIMPLE_DEV_PM_OPS(ad714x_pm, ad714x_suspend, ad714x_resume); + +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 0000000000..dafa12325f --- /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/pm.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; + +}; + +extern const struct dev_pm_ops ad714x_pm; +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 0000000000..6b880e282d --- /dev/null +++ b/drivers/input/misc/adxl34x-i2c.c @@ -0,0 +1,147 @@ +// 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, ®, 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) +{ + 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 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 = pm_sleep_ptr(&adxl34x_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 0000000000..f1094a8ccd --- /dev/null +++ b/drivers/input/misc/adxl34x-spi.c @@ -0,0 +1,110 @@ +// 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, ®, 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 struct spi_driver adxl34x_driver = { + .driver = { + .name = "adxl34x", + .pm = pm_sleep_ptr(&adxl34x_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 0000000000..a3f45e0ee0 --- /dev/null +++ b/drivers/input/misc/adxl34x.c @@ -0,0 +1,918 @@ +// 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); +} + +static int adxl34x_suspend(struct device *dev) +{ + struct adxl34x *ac = dev_get_drvdata(dev); + + mutex_lock(&ac->mutex); + + if (!ac->suspended && !ac->disabled && ac->opened) + __adxl34x_disable(ac); + + ac->suspended = true; + + mutex_unlock(&ac->mutex); + + return 0; +} + +static int adxl34x_resume(struct device *dev) +{ + struct adxl34x *ac = dev_get_drvdata(dev); + + mutex_lock(&ac->mutex); + + if (ac->suspended && !ac->disabled && ac->opened) + __adxl34x_enable(ac); + + ac->suspended = false; + + mutex_unlock(&ac->mutex); + + return 0; +} + +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); + +EXPORT_GPL_SIMPLE_DEV_PM_OPS(adxl34x_pm, adxl34x_suspend, adxl34x_resume); + +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 0000000000..f9272a2e7a --- /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); +}; + +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); + +extern const struct dev_pm_ops adxl34x_pm; + +#endif diff --git a/drivers/input/misc/apanel.c b/drivers/input/misc/apanel.c new file mode 100644 index 0000000000..b5219bbe85 --- /dev/null +++ b/drivers/input/misc/apanel.c @@ -0,0 +1,304 @@ +// 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) +{ + 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 0000000000..cdc80715b5 --- /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 0000000000..5fa1c9438a --- /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 0000000000..999aabf9dc --- /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 0000000000..946bf75aa1 --- /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 0000000000..3c9bbd04e1 --- /dev/null +++ b/drivers/input/misc/atlas_btns.c @@ -0,0 +1,142 @@ +// 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 void 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); +} + +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 0000000000..b6a30044e8 --- /dev/null +++ b/drivers/input/misc/atmel_captouch.c @@ -0,0 +1,278 @@ +// 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) +{ + 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; +} + +static const struct of_device_id atmel_captouch_of_id[] = { + { + .compatible = "atmel,captouch", + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, atmel_captouch_of_id); + +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 = 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 0000000000..4581606a28 --- /dev/null +++ b/drivers/input/misc/axp20x-pek.c @@ -0,0 +1,422 @@ +/* + * 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 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 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 = { + SYSTEM_SLEEP_PM_OPS(axp20x_pek_suspend, axp20x_pek_resume) + .resume_noirq = pm_sleep_ptr(axp20x_pek_resume_noirq), +}; + +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 = pm_sleep_ptr(&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 0000000000..0fb4cc628f --- /dev/null +++ b/drivers/input/misc/bma150.c @@ -0,0 +1,562 @@ +// 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 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 0000000000..728325a2d5 --- /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 0000000000..1772846708 --- /dev/null +++ b/drivers/input/misc/cma3000_d0x.c @@ -0,0 +1,386 @@ +// 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; + + 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 0000000000..05ad42a564 --- /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 0000000000..a4dfb3052d --- /dev/null +++ b/drivers/input/misc/cma3000_d0x_i2c.c @@ -0,0 +1,113 @@ +// 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) +{ + 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); +} + +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, +}; + +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", + .pm = pm_sleep_ptr(&cma3000_i2c_pm_ops), + }, +}; + +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 0000000000..b1624f5414 --- /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 0000000000..85cddb8471 --- /dev/null +++ b/drivers/input/misc/cpcap-pwrbutton.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * CPCAP Power Button Input Driver + * + * Copyright (C) 2017 Sebastian Reichel <sre@kernel.org> + */ + +#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 0000000000..ce82548916 --- /dev/null +++ b/drivers/input/misc/da7280.c @@ -0,0 +1,1331 @@ +// 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) +{ + 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 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 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 DEFINE_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 = pm_sleep_ptr(&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 0000000000..6d1152850a --- /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 0000000000..7a0d3a1d50 --- /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 0000000000..74808bae32 --- /dev/null +++ b/drivers/input/misc/da9063_onkey.c @@ -0,0 +1,285 @@ +// 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/pm_wakeirq.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 = dev_pm_set_wake_irq(&pdev->dev, irq); + if (error) + dev_warn(&pdev->dev, + "Failed to set IRQ %d as a wake IRQ: %d\n", + irq, error); + else + device_init_wakeup(&pdev->dev, true); + + 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/drv260x.c b/drivers/input/misc/drv260x.c new file mode 100644 index 0000000000..6717e3c954 --- /dev/null +++ b/drivers/input/misc/drv260x.c @@ -0,0 +1,630 @@ +// 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_ANALOG_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; + u8 magnitude; + u32 mode; + u32 library; + int rated_voltage; + int overdrive_voltage; +}; + +#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; + + /* Scale u16 magnitude into u8 register value */ + if (effect->u.rumble.strong_magnitude > 0) + haptics->magnitude = effect->u.rumble.strong_magnitude >> 8; + else if (effect->u.rumble.weak_magnitude > 0) + haptics->magnitude = effect->u.rumble.weak_magnitude >> 8; + 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_RTP_UNSIGNED_DATA }, + { 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_RTP_UNSIGNED_DATA | DRV260X_ANALOG_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_RTP_UNSIGNED_DATA }, + { 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, + .cache_type = REGCACHE_NONE, +}; + +static int drv260x_probe(struct i2c_client *client) +{ + 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 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 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 DEFINE_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 = pm_sleep_ptr(&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 0000000000..de27e6079d --- /dev/null +++ b/drivers/input/misc/drv2665.c @@ -0,0 +1,312 @@ +// 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) +{ + 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 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 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 DEFINE_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 = pm_sleep_ptr(&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 0000000000..11c5855256 --- /dev/null +++ b/drivers/input/misc/drv2667.c @@ -0,0 +1,489 @@ +// 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) +{ + 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 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 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 DEFINE_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 = pm_sleep_ptr(&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 0000000000..5bd5271174 --- /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 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 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 DEFINE_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 = pm_sleep_ptr(&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 0000000000..d2d2954e2f --- /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 0000000000..ad44b4d18a --- /dev/null +++ b/drivers/input/misc/gpio-vibra.c @@ -0,0 +1,199 @@ +// 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.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"); + if (IS_ERR(vibrator->vcc)) + return dev_err_probe(&pdev->dev, PTR_ERR(vibrator->vcc), + "Failed to request regulator\n"); + + vibrator->gpio = devm_gpiod_get(&pdev->dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(vibrator->gpio)) + return dev_err_probe(&pdev->dev, PTR_ERR(vibrator->gpio), + "Failed to request main gpio\n"); + + 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 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 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 DEFINE_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 = pm_sleep_ptr(&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 0000000000..ee668eba30 --- /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 0000000000..d3c293a95d --- /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 0000000000..afc0d6dc57 --- /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 __maybe_unused 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 0000000000..867ac7aa10 --- /dev/null +++ b/drivers/input/misc/ibm-panel.c @@ -0,0 +1,199 @@ +// 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) +{ + 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 0000000000..68f1c584da --- /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 0000000000..b2f1292e27 --- /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 0000000000..c0a0856398 --- /dev/null +++ b/drivers/input/misc/iqs269a.c @@ -0,0 +1,1755 @@ +// 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/completion.h> +#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/mod_devicetable.h> +#include <linux/module.h> +#include <linux/mutex.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_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_irq_wait() usleep_range(200, 250) + +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_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_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; + struct iqs269_ch_reg ch_reg[IQS269_NUM_CH]; +} __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_sys_reg sys_reg; + struct completion ati_done; + struct input_dev *keypad; + struct input_dev *slider[IQS269_NUM_SL]; + unsigned int keycode[ARRAY_SIZE(iqs269_events) * IQS269_NUM_CH]; + 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) +{ + struct iqs269_ch_reg *ch_reg = iqs269->sys_reg.ch_reg; + 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(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); + + 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) +{ + struct iqs269_ch_reg *ch_reg = iqs269->sys_reg.ch_reg; + u16 engine_a; + + if (ch_num >= IQS269_NUM_CH) + return -EINVAL; + + mutex_lock(&iqs269->lock); + engine_a = be16_to_cpu(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) +{ + struct iqs269_ch_reg *ch_reg = iqs269->sys_reg.ch_reg; + 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(ch_reg[ch_num].engine_b); + + engine_b &= ~IQS269_CHx_ENG_B_ATI_BASE_MASK; + engine_b |= base; + + 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) +{ + struct iqs269_ch_reg *ch_reg = iqs269->sys_reg.ch_reg; + u16 engine_b; + + if (ch_num >= IQS269_NUM_CH) + return -EINVAL; + + mutex_lock(&iqs269->lock); + engine_b = be16_to_cpu(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) +{ + struct iqs269_ch_reg *ch_reg = iqs269->sys_reg.ch_reg; + 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(ch_reg[ch_num].engine_b); + + engine_b &= ~IQS269_CHx_ENG_B_ATI_TARGET_MASK; + engine_b |= target / 32; + + 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) +{ + struct iqs269_ch_reg *ch_reg = iqs269->sys_reg.ch_reg; + u16 engine_b; + + if (ch_num >= IQS269_NUM_CH) + return -EINVAL; + + mutex_lock(&iqs269->lock); + engine_b = be16_to_cpu(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", ®); + 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->sys_reg.ch_reg[reg]; + + 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); + fwnode_handle_put(ev_node); + 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); + fwnode_handle_put(ev_node); + 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; + } + } + + error = fwnode_property_read_u32(ev_node, "linux,code", &val); + fwnode_handle_put(ev_node); + if (error == -EINVAL) { + continue; + } else if (error) { + dev_err(&client->dev, + "Failed to read channel %u code: %d\n", reg, + error); + return error; + } + + 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"); + + 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; + + /* + * 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,suspend-mode", + &val)) { + if (val > IQS269_SYS_SETTINGS_PWR_MODE_MAX) { + dev_err(&client->dev, "Invalid suspend mode: %u\n", + val); + return -EINVAL; + } + + general |= (val << IQS269_SYS_SETTINGS_PWR_MODE_SHIFT); + } + + 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) +{ + int error; + + 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; + + error = regmap_raw_write(iqs269->regmap, IQS269_SYS_SETTINGS, + &iqs269->sys_reg, sizeof(iqs269->sys_reg)); + if (error) + goto err_mutex; + + /* + * The following delay gives the device time to deassert its RDY output + * so as to prevent an interrupt from being serviced prematurely. + */ + usleep_range(2000, 2100); + + 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; + unsigned int sw_code, keycode; + int error, i, j; + + 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; + + for (i = 0; i < ARRAY_SIZE(iqs269_events); i++) { + 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); + 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); + } + } + } + + 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; + } + + if (be16_to_cpu(flags.system) & IQS269_SYS_FLAGS_IN_ATI) + return 0; + + 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); + + /* + * The following completion signals that ATI has finished, any initial + * switch states have been reported and the keypad can be registered. + */ + complete_all(&iqs269->ati_done); + + 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; + + if (!completion_done(&iqs269->ati_done)) + return -EBUSY; + + /* + * 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 iqs269_ch_reg *ch_reg = iqs269->sys_reg.ch_reg; + 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 (ch_reg[IQS269_CHx_HALL_ACTIVE].rx_enable & + 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); + struct iqs269_ch_reg *ch_reg = iqs269->sys_reg.ch_reg; + + return scnprintf(buf, PAGE_SIZE, "%u\n", + 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); + struct iqs269_ch_reg *ch_reg = iqs269->sys_reg.ch_reg; + unsigned int val; + int error; + + error = kstrtouint(buf, 10, &val); + if (error) + return error; + + if (val > 0xFF) + return -EINVAL; + + mutex_lock(&iqs269->lock); + + 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 && + completion_done(&iqs269->ati_done)); +} + +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); + reinit_completion(&iqs269->ati_done); + + error = iqs269_dev_init(iqs269); + + iqs269_irq_wait(); + enable_irq(client->irq); + + if (error) + return error; + + if (!wait_for_completion_timeout(&iqs269->ati_done, + msecs_to_jiffies(2000))) + return -ETIMEDOUT; + + 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); + init_completion(&iqs269->ati_done); + + 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; + } + + if (!wait_for_completion_timeout(&iqs269->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(iqs269->keypad); + if (error) { + dev_err(&client->dev, "Failed to register keypad: %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 u16 iqs269_general_get(struct iqs269_private *iqs269) +{ + u16 general = be16_to_cpu(iqs269->sys_reg.general); + + general &= ~IQS269_SYS_SETTINGS_REDO_ATI; + general &= ~IQS269_SYS_SETTINGS_ACK_RESET; + + return general | IQS269_SYS_SETTINGS_DIS_AUTO; +} + +static int iqs269_suspend(struct device *dev) +{ + struct iqs269_private *iqs269 = dev_get_drvdata(dev); + struct i2c_client *client = iqs269->client; + int error; + u16 general = iqs269_general_get(iqs269); + + if (!(general & IQS269_SYS_SETTINGS_PWR_MODE_MASK)) + return 0; + + disable_irq(client->irq); + + error = regmap_write(iqs269->regmap, IQS269_SYS_SETTINGS, general); + + iqs269_irq_wait(); + enable_irq(client->irq); + + return error; +} + +static int iqs269_resume(struct device *dev) +{ + struct iqs269_private *iqs269 = dev_get_drvdata(dev); + struct i2c_client *client = iqs269->client; + int error; + u16 general = iqs269_general_get(iqs269); + + if (!(general & IQS269_SYS_SETTINGS_PWR_MODE_MASK)) + return 0; + + disable_irq(client->irq); + + error = regmap_write(iqs269->regmap, IQS269_SYS_SETTINGS, + general & ~IQS269_SYS_SETTINGS_PWR_MODE_MASK); + if (!error) + error = regmap_write(iqs269->regmap, IQS269_SYS_SETTINGS, + general & ~IQS269_SYS_SETTINGS_DIS_AUTO); + + iqs269_irq_wait(); + enable_irq(client->irq); + + return error; +} + +static DEFINE_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 = pm_sleep_ptr(&iqs269_pm), + }, + .probe = 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 0000000000..0dab54d3a0 --- /dev/null +++ b/drivers/input/misc/iqs626a.c @@ -0,0 +1,1831 @@ +// 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/mod_devicetable.h> +#include <linux/module.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, + 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; + struct fwnode_handle *ev_node; + const char *ev_name; + u8 *thresh, *hyst; + unsigned int val; + int i; + + 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 = fwnode_handle_get(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); + fwnode_handle_put(ev_node); + 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); + fwnode_handle_put(ev_node); + 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); + fwnode_handle_put(ev_node); + return -EINVAL; + } + + if (ch_id == IQS626_CH_HALL) + *thresh = val; + else + *(thresh + iqs626_events[i].th_offs) = val; + } + + fwnode_handle_put(ev_node); + } + + return 0; +} + +static noinline_for_stack int +iqs626_parse_ati_target(struct iqs626_private *iqs626, + 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 val; + u8 *ati_target; + int 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; +} + +static int iqs626_parse_pins(struct iqs626_private *iqs626, + 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, + 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 *hyst = &sys_reg->tp_grp_reg.hyst; + int error, count, i; + unsigned int val; + + 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); + } + + for (i = 0; i < iqs626_channels[ch_id].num_ch; i++) { + u8 *ati_base = &sys_reg->tp_grp_reg.ch_reg_tp[i].ati_base; + u8 *thresh = &sys_reg->tp_grp_reg.ch_reg_tp[i].thresh; + struct fwnode_handle *tc_node; + char tc_name[10]; + + snprintf(tc_name, sizeof(tc_name), "channel-%d", i); + + tc_node = fwnode_get_named_child_node(ch_node, tc_name); + if (!tc_node) + continue; + + if (!fwnode_property_read_u32(tc_node, "azoteq,ati-base", + &val)) { + if (val < IQS626_TPx_ATI_BASE_MIN || + val > IQS626_TPx_ATI_BASE_MAX) { + dev_err(&client->dev, + "Invalid %s %s ATI base: %u\n", + fwnode_get_name(ch_node), tc_name, val); + fwnode_handle_put(tc_node); + return -EINVAL; + } + + *ati_base = val - IQS626_TPx_ATI_BASE_MIN; + } + + if (!fwnode_property_read_u32(tc_node, "azoteq,thresh", + &val)) { + if (val > IQS626_CHx_THRESH_MAX) { + dev_err(&client->dev, + "Invalid %s %s threshold: %u\n", + fwnode_get_name(ch_node), tc_name, val); + fwnode_handle_put(tc_node); + return -EINVAL; + } + + *thresh = val; + } + + fwnode_handle_put(tc_node); + } + + 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, + 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; + } + + error = iqs626_parse_ati_target(iqs626, ch_node, ch_id); + if (error) + return error; + + error = iqs626_parse_events(iqs626, ch_node, ch_id); + if (error) + return error; + + if (!fwnode_property_present(ch_node, "azoteq,ati-exclude")) + sys_reg->redo_ati |= iqs626_channels[ch_id].active; + + if (!fwnode_property_present(ch_node, "azoteq,reseed-disable")) + sys_reg->reseed |= iqs626_channels[ch_id].active; + + *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, ch_id); + + 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); + fwnode_handle_put(ch_node); + if (error) + return error; + + 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 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 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 DEFINE_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 = pm_sleep_ptr(&iqs626_pm), + }, + .probe = 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 0000000000..36aeeae776 --- /dev/null +++ b/drivers/input/misc/iqs7222.c @@ -0,0 +1,3052 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Azoteq IQS7222A/B/C/D 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/input/touchscreen.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/ktime.h> +#include <linux/mod_devicetable.h> +#include <linux/module.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_PROD_NUM_D 1046 + +#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_TPAD IQS7222_EVENT_MASK_SLDR +#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_TPAD 24 +#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_TPAD, + 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-%d", + [IQS7222_REG_GRP_CHAN] = "channel-%d", + [IQS7222_REG_GRP_SLDR] = "slider-%d", + [IQS7222_REG_GRP_TPAD] = "trackpad", + [IQS7222_REG_GRP_GPIO] = "gpio-%d", +}; + +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_TPAD] = IQS7222_MAX_COLS_TPAD, + [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 link; + u16 mask; + u16 val; + u16 strict; + 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, + }, +}; + +static const struct iqs7222_event_desc iqs7222_tp_events[] = { + { + .name = "event-press", + .link = BIT(7), + }, + { + .name = "event-tap", + .link = BIT(0), + .mask = BIT(0), + .val = BIT(0), + .enable = BIT(0), + .reg_key = IQS7222_REG_KEY_TAP, + }, + { + .name = "event-swipe-x-pos", + .link = BIT(2), + .mask = BIT(2) | BIT(1), + .val = BIT(2), + .strict = BIT(4), + .enable = BIT(1), + .reg_key = IQS7222_REG_KEY_AXIAL, + }, + { + .name = "event-swipe-y-pos", + .link = BIT(3), + .mask = BIT(3) | BIT(1), + .val = BIT(3), + .strict = BIT(3), + .enable = BIT(1), + .reg_key = IQS7222_REG_KEY_AXIAL, + }, + { + .name = "event-swipe-x-neg", + .link = BIT(4), + .mask = BIT(4) | BIT(1), + .val = BIT(4), + .strict = BIT(4), + .enable = BIT(1), + .reg_key = IQS7222_REG_KEY_AXIAL, + }, + { + .name = "event-swipe-y-neg", + .link = BIT(5), + .mask = BIT(5) | BIT(1), + .val = BIT(5), + .strict = BIT(3), + .enable = BIT(1), + .reg_key = IQS7222_REG_KEY_AXIAL, + }, + { + .name = "event-flick-x-pos", + .link = BIT(2), + .mask = BIT(2) | BIT(1), + .val = BIT(2) | BIT(1), + .strict = BIT(4), + .enable = BIT(2), + .reg_key = IQS7222_REG_KEY_AXIAL, + }, + { + .name = "event-flick-y-pos", + .link = BIT(3), + .mask = BIT(3) | BIT(1), + .val = BIT(3) | BIT(1), + .strict = BIT(3), + .enable = BIT(2), + .reg_key = IQS7222_REG_KEY_AXIAL, + }, + { + .name = "event-flick-x-neg", + .link = BIT(4), + .mask = BIT(4) | BIT(1), + .val = BIT(4) | BIT(1), + .strict = BIT(4), + .enable = BIT(2), + .reg_key = IQS7222_REG_KEY_AXIAL, + }, + { + .name = "event-flick-y-neg", + .link = BIT(5), + .mask = BIT(5) | BIT(1), + .val = BIT(5) | BIT(1), + .strict = BIT(3), + .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, + }, + }, + }, + { + .prod_num = IQS7222_PROD_NUM_D, + .fw_major = 0, + .fw_minor = 37, + .touch_link = 1770, + .allow_offset = 9, + .event_offset = 10, + .comms_offset = 11, + .reg_grps = { + [IQS7222_REG_GRP_STAT] = { + .base = IQS7222_SYS_STATUS, + .num_row = 1, + .num_col = 7, + }, + [IQS7222_REG_GRP_CYCLE] = { + .base = 0x8000, + .num_row = 7, + .num_col = 2, + }, + [IQS7222_REG_GRP_GLBL] = { + .base = 0x8700, + .num_row = 1, + .num_col = 3, + }, + [IQS7222_REG_GRP_BTN] = { + .base = 0x9000, + .num_row = 14, + .num_col = 3, + }, + [IQS7222_REG_GRP_CHAN] = { + .base = 0xA000, + .num_row = 14, + .num_col = 4, + }, + [IQS7222_REG_GRP_FILT] = { + .base = 0xAE00, + .num_row = 1, + .num_col = 2, + }, + [IQS7222_REG_GRP_TPAD] = { + .base = 0xB000, + .num_row = 1, + .num_col = 24, + }, + [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, + }, + }, + }, +}; + +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 = "azoteq,num-rows", + .reg_grp = IQS7222_REG_GRP_TPAD, + .reg_offset = 0, + .reg_shift = 4, + .reg_width = 4, + .val_min = 1, + .val_max = 12, + .label = "number of rows", + }, + { + .name = "azoteq,num-cols", + .reg_grp = IQS7222_REG_GRP_TPAD, + .reg_offset = 0, + .reg_shift = 0, + .reg_width = 4, + .val_min = 1, + .val_max = 12, + .label = "number of columns", + }, + { + .name = "azoteq,lower-cal-y", + .reg_grp = IQS7222_REG_GRP_TPAD, + .reg_offset = 1, + .reg_shift = 8, + .reg_width = 8, + .label = "lower vertical calibration", + }, + { + .name = "azoteq,lower-cal-x", + .reg_grp = IQS7222_REG_GRP_TPAD, + .reg_offset = 1, + .reg_shift = 0, + .reg_width = 8, + .label = "lower horizontal calibration", + }, + { + .name = "azoteq,upper-cal-y", + .reg_grp = IQS7222_REG_GRP_TPAD, + .reg_offset = 2, + .reg_shift = 8, + .reg_width = 8, + .label = "upper vertical calibration", + }, + { + .name = "azoteq,upper-cal-x", + .reg_grp = IQS7222_REG_GRP_TPAD, + .reg_offset = 2, + .reg_shift = 0, + .reg_width = 8, + .label = "upper horizontal calibration", + }, + { + .name = "azoteq,top-speed", + .reg_grp = IQS7222_REG_GRP_TPAD, + .reg_offset = 3, + .reg_shift = 8, + .reg_width = 8, + .val_pitch = 4, + .label = "top speed", + }, + { + .name = "azoteq,bottom-speed", + .reg_grp = IQS7222_REG_GRP_TPAD, + .reg_offset = 3, + .reg_shift = 0, + .reg_width = 8, + .label = "bottom speed", + }, + { + .name = "azoteq,gesture-min-ms", + .reg_grp = IQS7222_REG_GRP_TPAD, + .reg_key = IQS7222_REG_KEY_TAP, + .reg_offset = 20, + .reg_shift = 8, + .reg_width = 8, + .val_pitch = 16, + .label = "minimum gesture time", + }, + { + .name = "azoteq,gesture-max-ms", + .reg_grp = IQS7222_REG_GRP_TPAD, + .reg_key = IQS7222_REG_KEY_AXIAL, + .reg_offset = 21, + .reg_shift = 8, + .reg_width = 8, + .val_pitch = 16, + .label = "maximum gesture time", + }, + { + .name = "azoteq,gesture-max-ms", + .reg_grp = IQS7222_REG_GRP_TPAD, + .reg_key = IQS7222_REG_KEY_TAP, + .reg_offset = 21, + .reg_shift = 0, + .reg_width = 8, + .val_pitch = 16, + .label = "maximum gesture time", + }, + { + .name = "azoteq,gesture-dist", + .reg_grp = IQS7222_REG_GRP_TPAD, + .reg_key = IQS7222_REG_KEY_TAP, + .reg_offset = 22, + .reg_shift = 0, + .reg_width = 16, + .label = "gesture distance", + }, + { + .name = "azoteq,gesture-dist", + .reg_grp = IQS7222_REG_GRP_TPAD, + .reg_key = IQS7222_REG_KEY_AXIAL, + .reg_offset = 23, + .reg_shift = 0, + .reg_width = 16, + .label = "gesture distance", + }, + { + .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; + struct touchscreen_properties prop; + 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]; + unsigned int tp_code[ARRAY_SIZE(iqs7222_tp_events)]; + 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 tpad_setup[IQS7222_MAX_COLS_TPAD]; + 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_TPAD: + return iqs7222->tpad_setup; + + 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; + } else if (dev_desc->reg_grps[IQS7222_REG_GRP_TPAD].num_row && + fwnode_property_present(chan_node, + "azoteq,counts-filt-enable")) { + /* + * In the case of IQS7222D, however, the reference mode field + * is partially repurposed as a counts filter enable control. + */ + chan_setup[0] |= IQS7222_CHAN_SETUP_0_REF_MODE_REF; + } + + 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_tpad(struct iqs7222_private *iqs7222, + struct fwnode_handle *tpad_node, int tpad_index) +{ + const struct iqs7222_dev_desc *dev_desc = iqs7222->dev_desc; + struct touchscreen_properties *prop = &iqs7222->prop; + struct i2c_client *client = iqs7222->client; + int num_chan = dev_desc->reg_grps[IQS7222_REG_GRP_CHAN].num_row; + int count, error, i; + u16 *event_mask = &iqs7222->sys_setup[dev_desc->event_offset]; + u16 *tpad_setup = iqs7222->tpad_setup; + unsigned int chan_sel[12]; + + error = iqs7222_parse_props(iqs7222, tpad_node, tpad_index, + IQS7222_REG_GRP_TPAD, + IQS7222_REG_KEY_NONE); + if (error) + return error; + + count = fwnode_property_count_u32(tpad_node, "azoteq,channel-select"); + if (count < 0) { + dev_err(&client->dev, "Failed to count %s channels: %d\n", + fwnode_get_name(tpad_node), count); + return count; + } else if (!count || count > ARRAY_SIZE(chan_sel)) { + dev_err(&client->dev, "Invalid number of %s channels\n", + fwnode_get_name(tpad_node)); + return -EINVAL; + } + + error = fwnode_property_read_u32_array(tpad_node, + "azoteq,channel-select", + chan_sel, count); + if (error) { + dev_err(&client->dev, "Failed to read %s channels: %d\n", + fwnode_get_name(tpad_node), error); + return error; + } + + tpad_setup[6] &= ~GENMASK(num_chan - 1, 0); + + for (i = 0; i < ARRAY_SIZE(chan_sel); i++) { + tpad_setup[8 + i] = 0; + if (i >= count || chan_sel[i] == U8_MAX) + continue; + + if (chan_sel[i] >= num_chan) { + dev_err(&client->dev, "Invalid %s channel: %u\n", + fwnode_get_name(tpad_node), chan_sel[i]); + return -EINVAL; + } + + /* + * The following fields indicate which channels participate in + * the trackpad, as well as each channel's relative placement. + */ + tpad_setup[6] |= BIT(chan_sel[i]); + tpad_setup[8 + i] = chan_sel[i] * 34 + 1072; + } + + tpad_setup[7] = dev_desc->touch_link; + if (fwnode_property_present(tpad_node, "azoteq,use-prox")) + tpad_setup[7] -= 2; + + for (i = 0; i < ARRAY_SIZE(iqs7222_tp_events); i++) + tpad_setup[20] &= ~(iqs7222_tp_events[i].strict | + iqs7222_tp_events[i].enable); + + for (i = 0; i < ARRAY_SIZE(iqs7222_tp_events); i++) { + const char *event_name = iqs7222_tp_events[i].name; + struct fwnode_handle *event_node; + + event_node = fwnode_get_named_child_node(tpad_node, event_name); + if (!event_node) + continue; + + if (fwnode_property_present(event_node, + "azoteq,gesture-angle-tighten")) + tpad_setup[20] |= iqs7222_tp_events[i].strict; + + tpad_setup[20] |= iqs7222_tp_events[i].enable; + + error = iqs7222_parse_event(iqs7222, event_node, tpad_index, + IQS7222_REG_GRP_TPAD, + iqs7222_tp_events[i].reg_key, + iqs7222_tp_events[i].link, 1566, + NULL, + &iqs7222->tp_code[i]); + fwnode_handle_put(event_node); + if (error) + return error; + + if (!dev_desc->event_offset) + continue; + + /* + * The press/release event is determined based on whether the + * coordinate fields report 0xFFFF and solely relies on touch + * or proximity interrupts to be unmasked. + */ + if (i) + *event_mask |= IQS7222_EVENT_MASK_TPAD; + else if (tpad_setup[7] == dev_desc->touch_link) + *event_mask |= IQS7222_EVENT_MASK_TOUCH; + else + *event_mask |= IQS7222_EVENT_MASK_PROX; + } + + if (!iqs7222->tp_code[0]) + return 0; + + input_set_abs_params(iqs7222->keypad, ABS_X, + 0, (tpad_setup[4] ? : 1) - 1, 0, 0); + + input_set_abs_params(iqs7222->keypad, ABS_Y, + 0, (tpad_setup[5] ? : 1) - 1, 0, 0); + + touchscreen_parse_properties(iqs7222->keypad, false, prop); + + if (prop->max_x >= U16_MAX || prop->max_y >= U16_MAX) { + dev_err(&client->dev, "Invalid trackpad size: %u*%u\n", + prop->max_x, prop->max_y); + return -EINVAL; + } + + tpad_setup[4] = prop->max_x + 1; + tpad_setup[5] = prop->max_y + 1; + + return 0; +} + +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, + [IQS7222_REG_GRP_TPAD] = iqs7222_parse_tpad, +}; + +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), + 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 and IQS7222D expose 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); + } + + for (i = 0; i < dev_desc->reg_grps[IQS7222_REG_GRP_TPAD].num_row; i++) { + u16 tpad_pos_x = le16_to_cpu(status[4]); + u16 tpad_pos_y = le16_to_cpu(status[5]); + u16 state = le16_to_cpu(status[6]); + + input_report_key(iqs7222->keypad, iqs7222->tp_code[0], + tpad_pos_x < U16_MAX); + + if (tpad_pos_x < U16_MAX) + touchscreen_report_pos(iqs7222->keypad, &iqs7222->prop, + tpad_pos_x, tpad_pos_y, false); + + if (!(le16_to_cpu(status[1]) & IQS7222_EVENT_MASK_TPAD)) + 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_tp_events); j++) { + u16 mask = iqs7222_tp_events[j].mask; + u16 val = iqs7222_tp_events[j].val; + + input_report_key(iqs7222->keypad, + iqs7222->tp_code[j], + (state & mask) == val); + } + + input_sync(iqs7222->keypad); + + for (j = 1; j < ARRAY_SIZE(iqs7222_tp_events); j++) + input_report_key(iqs7222->keypad, + iqs7222->tp_code[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" }, + { .compatible = "azoteq,iqs7222d" }, + { } +}; +MODULE_DEVICE_TABLE(of, iqs7222_of_match); + +static struct i2c_driver iqs7222_i2c_driver = { + .driver = { + .name = "iqs7222", + .of_match_table = iqs7222_of_match, + }, + .probe = iqs7222_probe, +}; +module_i2c_driver(iqs7222_i2c_driver); + +MODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>"); +MODULE_DESCRIPTION("Azoteq IQS7222A/B/C/D 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 0000000000..bee4b13764 --- /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 0000000000..912e614d03 --- /dev/null +++ b/drivers/input/misc/kxtj9.c @@ -0,0 +1,549 @@ +// 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 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 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 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 DEFINE_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 = pm_sleep_ptr(&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 0000000000..25fcf14671 --- /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 0000000000..ee55f22dbc --- /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 0000000000..80f4416ffe --- /dev/null +++ b/drivers/input/misc/max77693-haptic.c @@ -0,0 +1,428 @@ +// 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 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 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 DEFINE_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 = pm_sleep_ptr(&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 0000000000..62619e4fed --- /dev/null +++ b/drivers/input/misc/max8925_onkey.c @@ -0,0 +1,174 @@ +/* + * 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 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 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 DEFINE_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 = pm_sleep_ptr(&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 0000000000..c4dff476d4 --- /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_get(&pdev->dev, NULL); + 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_put(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_put(chip->pwm); + + kfree(chip); + + return 0; +} + +static int 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 DEFINE_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 = pm_sleep_ptr(&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 0000000000..0636eee4bb --- /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 0000000000..662b436d76 --- /dev/null +++ b/drivers/input/misc/mma8450.c @@ -0,0 +1,213 @@ +// 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/mod_devicetable.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) +{ + 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/nxp-bbnsm-pwrkey.c b/drivers/input/misc/nxp-bbnsm-pwrkey.c new file mode 100644 index 0000000000..1d99206dd3 --- /dev/null +++ b/drivers/input/misc/nxp-bbnsm-pwrkey.c @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// Copyright 2022 NXP. + +#include <linux/device.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/pm_wakeirq.h> +#include <linux/regmap.h> + +#define BBNSM_CTRL 0x8 +#define BBNSM_INT_EN 0x10 +#define BBNSM_EVENTS 0x14 +#define BBNSM_PAD_CTRL 0x24 + +#define BBNSM_BTN_PRESSED BIT(7) +#define BBNSM_PWR_ON BIT(6) +#define BBNSM_BTN_OFF BIT(5) +#define BBNSM_EMG_OFF BIT(4) +#define BBNSM_PWRKEY_EVENTS (BBNSM_PWR_ON | BBNSM_BTN_OFF | BBNSM_EMG_OFF) +#define BBNSM_DP_EN BIT(24) + +#define DEBOUNCE_TIME 30 +#define REPEAT_INTERVAL 60 + +struct bbnsm_pwrkey { + struct regmap *regmap; + int irq; + int keycode; + int keystate; /* 1:pressed */ + struct timer_list check_timer; + struct input_dev *input; +}; + +static void bbnsm_pwrkey_check_for_events(struct timer_list *t) +{ + struct bbnsm_pwrkey *bbnsm = from_timer(bbnsm, t, check_timer); + struct input_dev *input = bbnsm->input; + u32 state; + + regmap_read(bbnsm->regmap, BBNSM_EVENTS, &state); + + state = state & BBNSM_BTN_PRESSED ? 1 : 0; + + /* only report new event if status changed */ + if (state ^ bbnsm->keystate) { + bbnsm->keystate = state; + input_event(input, EV_KEY, bbnsm->keycode, state); + input_sync(input); + pm_relax(bbnsm->input->dev.parent); + } + + /* repeat check if pressed long */ + if (state) + mod_timer(&bbnsm->check_timer, + jiffies + msecs_to_jiffies(REPEAT_INTERVAL)); +} + +static irqreturn_t bbnsm_pwrkey_interrupt(int irq, void *dev_id) +{ + struct platform_device *pdev = dev_id; + struct bbnsm_pwrkey *bbnsm = platform_get_drvdata(pdev); + u32 event; + + regmap_read(bbnsm->regmap, BBNSM_EVENTS, &event); + if (!(event & BBNSM_BTN_OFF)) + return IRQ_NONE; + + pm_wakeup_event(bbnsm->input->dev.parent, 0); + + mod_timer(&bbnsm->check_timer, + jiffies + msecs_to_jiffies(DEBOUNCE_TIME)); + + /* clear PWR OFF */ + regmap_write(bbnsm->regmap, BBNSM_EVENTS, BBNSM_BTN_OFF); + + return IRQ_HANDLED; +} + +static void bbnsm_pwrkey_act(void *pdata) +{ + struct bbnsm_pwrkey *bbnsm = pdata; + + timer_shutdown_sync(&bbnsm->check_timer); +} + +static int bbnsm_pwrkey_probe(struct platform_device *pdev) +{ + struct bbnsm_pwrkey *bbnsm; + struct input_dev *input; + struct device_node *np = pdev->dev.of_node; + int error; + + bbnsm = devm_kzalloc(&pdev->dev, sizeof(*bbnsm), GFP_KERNEL); + if (!bbnsm) + return -ENOMEM; + + bbnsm->regmap = syscon_node_to_regmap(np->parent); + if (IS_ERR(bbnsm->regmap)) { + dev_err(&pdev->dev, "bbnsm pwerkey get regmap failed\n"); + return PTR_ERR(bbnsm->regmap); + } + + if (device_property_read_u32(&pdev->dev, "linux,code", + &bbnsm->keycode)) { + bbnsm->keycode = KEY_POWER; + dev_warn(&pdev->dev, "key code is not specified, using default KEY_POWER\n"); + } + + bbnsm->irq = platform_get_irq(pdev, 0); + if (bbnsm->irq < 0) + return -EINVAL; + + /* config the BBNSM power related register */ + regmap_update_bits(bbnsm->regmap, BBNSM_CTRL, BBNSM_DP_EN, BBNSM_DP_EN); + + /* clear the unexpected interrupt before driver ready */ + regmap_write_bits(bbnsm->regmap, BBNSM_EVENTS, BBNSM_PWRKEY_EVENTS, + BBNSM_PWRKEY_EVENTS); + + timer_setup(&bbnsm->check_timer, bbnsm_pwrkey_check_for_events, 0); + + input = devm_input_allocate_device(&pdev->dev); + if (!input) { + dev_err(&pdev->dev, "failed to allocate the input device\n"); + return -ENOMEM; + } + + input->name = pdev->name; + input->phys = "bbnsm-pwrkey/input0"; + input->id.bustype = BUS_HOST; + + input_set_capability(input, EV_KEY, bbnsm->keycode); + + /* input customer action to cancel release timer */ + error = devm_add_action(&pdev->dev, bbnsm_pwrkey_act, bbnsm); + if (error) { + dev_err(&pdev->dev, "failed to register remove action\n"); + return error; + } + + bbnsm->input = input; + platform_set_drvdata(pdev, bbnsm); + + error = devm_request_irq(&pdev->dev, bbnsm->irq, bbnsm_pwrkey_interrupt, + IRQF_SHARED, pdev->name, pdev); + if (error) { + dev_err(&pdev->dev, "interrupt not available.\n"); + return error; + } + + error = input_register_device(input); + if (error) { + dev_err(&pdev->dev, "failed to register input device\n"); + return error; + } + + device_init_wakeup(&pdev->dev, true); + error = dev_pm_set_wake_irq(&pdev->dev, bbnsm->irq); + if (error) + dev_warn(&pdev->dev, "irq wake enable failed.\n"); + + return 0; +} + +static const struct of_device_id bbnsm_pwrkey_ids[] = { + { .compatible = "nxp,imx93-bbnsm-pwrkey" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, bbnsm_pwrkey_ids); + +static struct platform_driver bbnsm_pwrkey_driver = { + .driver = { + .name = "bbnsm_pwrkey", + .of_match_table = bbnsm_pwrkey_ids, + }, + .probe = bbnsm_pwrkey_probe, +}; +module_platform_driver(bbnsm_pwrkey_driver); + +MODULE_AUTHOR("Jacky Bai <ping.bai@nxp.com>"); +MODULE_DESCRIPTION("NXP bbnsm power key 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 0000000000..7e361727b0 --- /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, ®); + 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 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 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 DEFINE_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 = pm_sleep_ptr(&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 0000000000..b5a53636d7 --- /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 0000000000..4c60c70c4c --- /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 0000000000..536cedeb38 --- /dev/null +++ b/drivers/input/misc/pcf8574_keypad.c @@ -0,0 +1,211 @@ +// 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) +{ + 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); +} + +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 DEFINE_SIMPLE_DEV_PM_OPS(pcf8574_kp_pm_ops, + pcf8574_kp_suspend, pcf8574_kp_resume); + +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, + .pm = pm_sleep_ptr(&pcf8574_kp_pm_ops), + }, + .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 0000000000..9c666b2f14 --- /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 0000000000..ba747c5b2b --- /dev/null +++ b/drivers/input/misc/pm8941-pwrkey.c @@ -0,0 +1,480 @@ +// 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/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 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 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 DEFINE_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 = pm_sleep_ptr(&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 0000000000..5c288fe7ac --- /dev/null +++ b/drivers/input/misc/pm8xxx-vibrator.c @@ -0,0 +1,261 @@ +// 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/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 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 DEFINE_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 = pm_sleep_ptr(&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 0000000000..c406a1cca5 --- /dev/null +++ b/drivers/input/misc/pmic8xxx-pwrkey.c @@ -0,0 +1,453 @@ +// 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> + +#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 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 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 DEFINE_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, ®); + 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, ®); + 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 = pm_sleep_ptr(&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 0000000000..db2ba89ada --- /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 0000000000..1e731d8397 --- /dev/null +++ b/drivers/input/misc/pwm-beeper.c @@ -0,0 +1,253 @@ +// 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)) + return dev_err_probe(dev, PTR_ERR(beeper->pwm), "Failed to request PWM device\n"); + + /* 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)) + return dev_err_probe(dev, PTR_ERR(beeper->amplifier), + "Failed to get 'amp' regulator\n"); + + 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 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 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 DEFINE_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 = pm_sleep_ptr(&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 0000000000..acac79c488 --- /dev/null +++ b/drivers/input/misc/pwm-vibra.c @@ -0,0 +1,274 @@ +// 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/gpio/consumer.h> +#include <linux/input.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.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 gpio_desc *enable_gpio; + 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\n", err); + return err; + } + vibrator->vcc_on = true; + } + + gpiod_set_value_cansleep(vibrator->enable_gpio, 1); + + 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\n", 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\n", 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); + + gpiod_set_value_cansleep(vibrator->enable_gpio, 0); + + 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"); + if (IS_ERR(vibrator->vcc)) + return dev_err_probe(&pdev->dev, PTR_ERR(vibrator->vcc), + "Failed to request regulator\n"); + + vibrator->enable_gpio = devm_gpiod_get_optional(&pdev->dev, "enable", + GPIOD_OUT_LOW); + if (IS_ERR(vibrator->enable_gpio)) + return dev_err_probe(&pdev->dev, PTR_ERR(vibrator->enable_gpio), + "Failed to request enable gpio\n"); + + vibrator->pwm = devm_pwm_get(&pdev->dev, "enable"); + if (IS_ERR(vibrator->pwm)) + return dev_err_probe(&pdev->dev, PTR_ERR(vibrator->pwm), + "Failed to request main pwm\n"); + + 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\n", + 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\n", + 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\n", 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\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 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 pwm_vibrator_resume(struct device *dev) +{ + struct pwm_vibrator *vibrator = dev_get_drvdata(dev); + + if (vibrator->level) + pwm_vibrator_start(vibrator); + + return 0; +} + +static DEFINE_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 = pm_sleep_ptr(&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 0000000000..bcab3cdb7e --- /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 0000000000..190a80e1e2 --- /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 0000000000..02f73b7c04 --- /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 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 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 DEFINE_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 = pm_sleep_ptr(®ulator_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 0000000000..64023ac08e --- /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 0000000000..76873aa005 --- /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 0000000000..e94cab8133 --- /dev/null +++ b/drivers/input/misc/rotary_encoder.c @@ -0,0 +1,365 @@ +// 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)) + return dev_err_probe(dev, PTR_ERR(encoder->gpios), "unable to get gpios\n"); + 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; + + 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 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 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 DEFINE_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 = pm_sleep_ptr(&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 0000000000..8a8c1aeeed --- /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 0000000000..1478017f09 --- /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 0000000000..0657d785b3 --- /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 0000000000..9116f4248f --- /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 0000000000..e5dd84725c --- /dev/null +++ b/drivers/input/misc/sparcspkr.c @@ -0,0 +1,367 @@ +// 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.h> +#include <linux/platform_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 0000000000..d5ebca7b90 --- /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 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 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 DEFINE_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 = pm_sleep_ptr(&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 0000000000..fc450fce09 --- /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, ®); + 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/tps65219-pwrbutton.c b/drivers/input/misc/tps65219-pwrbutton.c new file mode 100644 index 0000000000..eeb9f2334a --- /dev/null +++ b/drivers/input/misc/tps65219-pwrbutton.c @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Driver for TPS65219 Push Button +// +// Copyright (C) 2022 BayLibre Incorporated - https://www.baylibre.com/ + +#include <linux/init.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/mfd/tps65219.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +struct tps65219_pwrbutton { + struct device *dev; + struct input_dev *idev; + char phys[32]; +}; + +static irqreturn_t tps65219_pb_push_irq(int irq, void *_pwr) +{ + struct tps65219_pwrbutton *pwr = _pwr; + + input_report_key(pwr->idev, KEY_POWER, 1); + pm_wakeup_event(pwr->dev, 0); + input_sync(pwr->idev); + + return IRQ_HANDLED; +} + +static irqreturn_t tps65219_pb_release_irq(int irq, void *_pwr) +{ + struct tps65219_pwrbutton *pwr = _pwr; + + input_report_key(pwr->idev, KEY_POWER, 0); + input_sync(pwr->idev); + + return IRQ_HANDLED; +} + +static int tps65219_pb_probe(struct platform_device *pdev) +{ + struct tps65219 *tps = dev_get_drvdata(pdev->dev.parent); + struct device *dev = &pdev->dev; + struct tps65219_pwrbutton *pwr; + struct input_dev *idev; + int error; + int push_irq; + int release_irq; + + pwr = devm_kzalloc(dev, sizeof(*pwr), GFP_KERNEL); + if (!pwr) + return -ENOMEM; + + idev = devm_input_allocate_device(dev); + if (!idev) + return -ENOMEM; + + idev->name = pdev->name; + snprintf(pwr->phys, sizeof(pwr->phys), "%s/input0", + pdev->name); + idev->phys = pwr->phys; + idev->id.bustype = BUS_I2C; + + input_set_capability(idev, EV_KEY, KEY_POWER); + + pwr->dev = dev; + pwr->idev = idev; + device_init_wakeup(dev, true); + + push_irq = platform_get_irq(pdev, 0); + if (push_irq < 0) + return -EINVAL; + + release_irq = platform_get_irq(pdev, 1); + if (release_irq < 0) + return -EINVAL; + + error = devm_request_threaded_irq(dev, push_irq, NULL, + tps65219_pb_push_irq, + IRQF_ONESHOT, + dev->init_name, pwr); + if (error) { + dev_err(dev, "failed to request push IRQ #%d: %d\n", push_irq, + error); + return error; + } + + error = devm_request_threaded_irq(dev, release_irq, NULL, + tps65219_pb_release_irq, + IRQF_ONESHOT, + dev->init_name, pwr); + if (error) { + dev_err(dev, "failed to request release IRQ #%d: %d\n", + release_irq, error); + return error; + } + + error = input_register_device(idev); + if (error) { + dev_err(dev, "Can't register power button: %d\n", error); + return error; + } + + /* Enable interrupts for the pushbutton */ + regmap_clear_bits(tps->regmap, TPS65219_REG_MASK_CONFIG, + TPS65219_REG_MASK_INT_FOR_PB_MASK); + + /* Set PB/EN/VSENSE pin to be a pushbutton */ + regmap_update_bits(tps->regmap, TPS65219_REG_MFP_2_CONFIG, + TPS65219_MFP_2_EN_PB_VSENSE_MASK, TPS65219_MFP_2_PB); + + return 0; +} + +static void tps65219_pb_remove(struct platform_device *pdev) +{ + struct tps65219 *tps = dev_get_drvdata(pdev->dev.parent); + int ret; + + /* Disable interrupt for the pushbutton */ + ret = regmap_set_bits(tps->regmap, TPS65219_REG_MASK_CONFIG, + TPS65219_REG_MASK_INT_FOR_PB_MASK); + if (ret) + dev_warn(&pdev->dev, "Failed to disable irq (%pe)\n", ERR_PTR(ret)); +} + +static const struct platform_device_id tps65219_pwrbtn_id_table[] = { + { "tps65219-pwrbutton", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, tps65219_pwrbtn_id_table); + +static struct platform_driver tps65219_pb_driver = { + .probe = tps65219_pb_probe, + .remove_new = tps65219_pb_remove, + .driver = { + .name = "tps65219_pwrbutton", + }, + .id_table = tps65219_pwrbtn_id_table, +}; +module_platform_driver(tps65219_pb_driver); + +MODULE_DESCRIPTION("TPS65219 Power Button"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Markus Schneider-Pargmann <msp@baylibre.com"); diff --git a/drivers/input/misc/twl4030-pwrbutton.c b/drivers/input/misc/twl4030-pwrbutton.c new file mode 100644 index 0000000000..e3ee0638ff --- /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 0000000000..101548b35e --- /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, ®, 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, + ®, 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, + ®, 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, + ®, 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, + ®, 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 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 twl4030_vibra_resume(struct device *dev) +{ + vibra_disable_leds(); + return 0; +} + +static DEFINE_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 = pm_sleep_ptr(&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 0000000000..78f0b63e5c --- /dev/null +++ b/drivers/input/misc/twl6040-vibra.c @@ -0,0 +1,367 @@ +// 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 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 DEFINE_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 = pm_sleep_ptr(&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 0000000000..d98212d551 --- /dev/null +++ b/drivers/input/misc/uinput.c @@ -0,0 +1,1136 @@ +// 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 +#define UINPUT_TIMESTAMP_ALLOWED_OFFSET_SECS 10 + +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; +} + +/* + * Returns true if the given timestamp is valid (i.e., if all the following + * conditions are satisfied), false otherwise. + * 1) given timestamp is positive + * 2) it's within the allowed offset before the current time + * 3) it's not in the future + */ +static bool is_valid_timestamp(const ktime_t timestamp) +{ + ktime_t zero_time; + ktime_t current_time; + ktime_t min_time; + ktime_t offset; + + zero_time = ktime_set(0, 0); + if (ktime_compare(zero_time, timestamp) >= 0) + return false; + + current_time = ktime_get(); + offset = ktime_set(UINPUT_TIMESTAMP_ALLOWED_OFFSET_SECS, 0); + min_time = ktime_sub(current_time, offset); + + if (ktime_after(min_time, timestamp) || ktime_after(timestamp, current_time)) + return false; + + return true; +} + +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; + ktime_t timestamp; + + 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; + + timestamp = ktime_set(ev.input_event_sec, ev.input_event_usec * NSEC_PER_USEC); + if (is_valid_timestamp(timestamp)) + input_set_timestamp(udev->dev, timestamp); + + 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 0000000000..111cb70cde --- /dev/null +++ b/drivers/input/misc/wistron_btns.c @@ -0,0 +1,1391 @@ +// 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(®s, 0, sizeof (regs)); + regs.eax = 0x9610; + regs.ebx = 0x061C; + regs.ecx = 0x0000; + call_bios(®s); + + return regs.eax; +} + +static void bios_attach(void) +{ + struct regs regs; + + memset(®s, 0, sizeof (regs)); + regs.eax = 0x9610; + regs.ebx = 0x012E; + call_bios(®s); +} + +static void bios_detach(void) +{ + struct regs regs; + + memset(®s, 0, sizeof (regs)); + regs.eax = 0x9610; + regs.ebx = 0x002E; + call_bios(®s); +} + +static u8 bios_get_cmos_address(void) +{ + struct regs regs; + + memset(®s, 0, sizeof (regs)); + regs.eax = 0x9610; + regs.ebx = 0x051C; + call_bios(®s); + + return regs.ecx; +} + +static u16 bios_get_default_setting(u8 subsys) +{ + struct regs regs; + + memset(®s, 0, sizeof (regs)); + regs.eax = 0x9610; + regs.ebx = 0x0200 | subsys; + call_bios(®s); + + return regs.eax; +} + +static void bios_set_state(u8 subsys, int enable) +{ + struct regs regs; + + memset(®s, 0, sizeof (regs)); + regs.eax = 0x9610; + regs.ebx = (enable ? 0x0100 : 0x0000) | subsys; + call_bios(®s); +} + +/* 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; +} + +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, +}; + +static struct platform_driver wistron_driver = { + .driver = { + .name = "wistron-bios", + .pm = pm_sleep_ptr(&wistron_pm_ops), + }, + .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 0000000000..a42fe041b7 --- /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 0000000000..67f1c7364c --- /dev/null +++ b/drivers/input/misc/xen-kbdfront.c @@ -0,0 +1,572 @@ +/* + * 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 void 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 void 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); +} + +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 0000000000..69420781db --- /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 0000000000..69f700431f --- /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 */ |