diff options
64 files changed, 3982 insertions, 298 deletions
diff --git a/Documentation/devicetree/bindings/input/elan_i2c.txt b/Documentation/devicetree/bindings/input/elan_i2c.txt index 797607460735..9963247706f2 100644 --- a/Documentation/devicetree/bindings/input/elan_i2c.txt +++ b/Documentation/devicetree/bindings/input/elan_i2c.txt @@ -13,9 +13,20 @@ Optional properties: pinctrl binding [1]). - vcc-supply: a phandle for the regulator supplying 3.3V power. - elan,trackpoint: touchpad can support a trackpoint (boolean) +- elan,clickpad: touchpad is a clickpad (the entire surface is a button) +- elan,middle-button: touchpad has a physical middle button +- elan,x_traces: number of antennas on the x axis +- elan,y_traces: number of antennas on the y axis +- some generic touchscreen properties [2]: + * touchscreen-size-x + * touchscreen-size-y + * touchscreen-x-mm + * touchscreen-y-mm + [0]: Documentation/devicetree/bindings/interrupt-controller/interrupts.txt [1]: Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt +[2]: Documentation/devicetree/bindings/input/touchscreen/touchscreen.txt Example: &i2c1 { diff --git a/Documentation/devicetree/bindings/input/gpio-vibrator.yaml b/Documentation/devicetree/bindings/input/gpio-vibrator.yaml new file mode 100644 index 000000000000..903475f52dbd --- /dev/null +++ b/Documentation/devicetree/bindings/input/gpio-vibrator.yaml @@ -0,0 +1,37 @@ +# SPDX-License-Identifier: GPL-2.0 +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/bindings/input/gpio-vibrator.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: GPIO vibrator + +maintainers: + - Luca Weiss <luca@z3ntu.xyz> + +description: |+ + Registers a GPIO device as vibrator, where the on/off capability is controlled by a GPIO. + +properties: + compatible: + const: gpio-vibrator + + enable-gpios: + maxItems: 1 + + vcc-supply: + description: Regulator that provides power + +required: + - compatible + - enable-gpios + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + + vibrator { + compatible = "gpio-vibrator"; + enable-gpios = <&msmgpio 86 GPIO_ACTIVE_HIGH>; + vcc-supply = <&pm8941_l18>; + }; diff --git a/Documentation/devicetree/bindings/input/lpc32xx-key.txt b/Documentation/devicetree/bindings/input/lpc32xx-key.txt index bcf62f856358..2b075a080d30 100644 --- a/Documentation/devicetree/bindings/input/lpc32xx-key.txt +++ b/Documentation/devicetree/bindings/input/lpc32xx-key.txt @@ -8,6 +8,7 @@ Required Properties: - reg: Physical base address of the controller and length of memory mapped region. - interrupts: The interrupt number to the cpu. +- clocks: phandle to clock controller plus clock-specifier pair - nxp,debounce-delay-ms: Debounce delay in ms - nxp,scan-delay-ms: Repeated scan period in ms - linux,keymap: the key-code to be reported when the key is pressed @@ -22,7 +23,9 @@ Example: key@40050000 { compatible = "nxp,lpc3220-key"; reg = <0x40050000 0x1000>; - interrupts = <54 0>; + clocks = <&clk LPC32XX_CLK_KEY>; + interrupt-parent = <&sic1>; + interrupts = <22 IRQ_TYPE_LEVEL_HIGH>; keypad,num-rows = <1>; keypad,num-columns = <1>; nxp,debounce-delay-ms = <3>; diff --git a/Documentation/devicetree/bindings/input/max77650-onkey.txt b/Documentation/devicetree/bindings/input/max77650-onkey.txt new file mode 100644 index 000000000000..477dc74f452a --- /dev/null +++ b/Documentation/devicetree/bindings/input/max77650-onkey.txt @@ -0,0 +1,26 @@ +Onkey driver for MAX77650 PMIC from Maxim Integrated. + +This module is part of the MAX77650 MFD device. For more details +see Documentation/devicetree/bindings/mfd/max77650.txt. + +The onkey controller is represented as a sub-node of the PMIC node on +the device tree. + +Required properties: +-------------------- +- compatible: Must be "maxim,max77650-onkey". + +Optional properties: +- linux,code: The key-code to be reported when the key is pressed. + Defaults to KEY_POWER. +- maxim,onkey-slide: The system's button is a slide switch, not the default + push button. + +Example: +-------- + + onkey { + compatible = "maxim,max77650-onkey"; + linux,code = <KEY_END>; + maxim,onkey-slide; + }; diff --git a/Documentation/devicetree/bindings/input/microchip,qt1050.txt b/Documentation/devicetree/bindings/input/microchip,qt1050.txt new file mode 100644 index 000000000000..80e75f96252b --- /dev/null +++ b/Documentation/devicetree/bindings/input/microchip,qt1050.txt @@ -0,0 +1,78 @@ +Microchip AT42QT1050 Five-channel Touch Sensor IC + +The AT42QT1050 (QT1050) is a QTouchADC sensor device. The device can sense from +one to five keys, dependent on mode. The QT1050 includes all signal processing +functions necessary to provide stable sensing under a wide variety of changing +conditions, and the outputs are fully debounced. + +The touchkey device node should be placed inside an I2C bus node. + +Required properties: +- compatible: Must be "microchip,qt1050" +- reg: The I2C address of the device +- interrupts: The sink for the touchpad's IRQ output, + see ../interrupt-controller/interrupts.txt + +Optional properties: +- wakeup-source: touch keys can be used as a wakeup source + +Each button (key) is represented as a sub-node: + +Each not specified key or key with linux,code set to KEY_RESERVED gets disabled +in HW. + +Subnode properties: +- linux,code: Keycode to emit. +- reg: The key number. Valid values: 0, 1, 2, 3, 4. + +Optional subnode-properties: + +If a optional property is missing or has a invalid value the default value is +taken. + +- microchip,pre-charge-time-ns: + Each touchpad need some time to precharge. The value depends on the mechanical + layout. + Valid value range: 0 - 637500; values must be a multiple of 2500; + default is 0. +- microchip,average-samples: + Number of data samples which are averaged for each read. + Valid values: 1, 4, 16, 64, 256, 1024, 4096, 16384; default is 1. +- microchip,average-scaling: + The scaling factor which is used to scale the average-samples. + Valid values: 1, 2, 4, 8, 16, 32, 64, 128; default is 1. +- microchip,threshold: + Number of counts to register a touch detection. + Valid value range: 0 - 255; default is 20. + +Example: +QT1050 with 3 non continuous keys, key2 and key4 are disabled. + +touchkeys@41 { + compatible = "microchip,qt1050"; + reg = <0x41>; + interrupt-parent = <&gpio0>; + interrupts = <17 IRQ_TYPE_EDGE_FALLING>; + + up@0 { + reg = <0>; + linux,code = <KEY_UP>; + microchip,average-samples = <64>; + microchip,average-scaling = <16>; + microchip,pre-charge-time-ns = <10000>; + }; + + right@1 { + reg = <1>; + linux,code = <KEY_RIGHT>; + microchip,average-samples = <64>; + microchip,average-scaling = <8>; + }; + + down@3 { + reg = <3>; + linux,code = <KEY_DOWN>; + microchip,average-samples = <256>; + microchip,average-scaling = <16>; + }; +}; diff --git a/Documentation/devicetree/bindings/input/sun4i-lradc-keys.txt b/Documentation/devicetree/bindings/input/sun4i-lradc-keys.txt index 1458c3179a63..496125c6bfb7 100644 --- a/Documentation/devicetree/bindings/input/sun4i-lradc-keys.txt +++ b/Documentation/devicetree/bindings/input/sun4i-lradc-keys.txt @@ -2,12 +2,14 @@ Allwinner sun4i low res adc attached tablet keys ------------------------------------------------ Required properties: - - compatible: "allwinner,sun4i-a10-lradc-keys" + - compatible: should be one of the following string: + "allwinner,sun4i-a10-lradc-keys" + "allwinner,sun8i-a83t-r-lradc" - reg: mmio address range of the chip - interrupts: interrupt to which the chip is connected - vref-supply: powersupply for the lradc reference voltage -Each key is represented as a sub-node of "allwinner,sun4i-a10-lradc-keys": +Each key is represented as a sub-node of the compatible mentioned above: Required subnode-properties: - label: Descriptive name of the key. diff --git a/Documentation/devicetree/bindings/input/touchscreen/goodix.txt b/Documentation/devicetree/bindings/input/touchscreen/goodix.txt index 8cf0b4d38a7e..fc03ea4cf5ab 100644 --- a/Documentation/devicetree/bindings/input/touchscreen/goodix.txt +++ b/Documentation/devicetree/bindings/input/touchscreen/goodix.txt @@ -3,6 +3,7 @@ Device tree bindings for Goodix GT9xx series touchscreen controller Required properties: - compatible : Should be "goodix,gt1151" + or "goodix,gt5663" or "goodix,gt5688" or "goodix,gt911" or "goodix,gt9110" @@ -19,6 +20,8 @@ Optional properties: - irq-gpios : GPIO pin used for IRQ. The driver uses the interrupt gpio pin as output to reset the device. - reset-gpios : GPIO pin used for reset + - AVDD28-supply : Analog power supply regulator on AVDD28 pin + - VDDIO-supply : GPIO power supply regulator on VDDIO pin - touchscreen-inverted-x - touchscreen-inverted-y - touchscreen-size-x diff --git a/Documentation/devicetree/bindings/input/touchscreen/iqs5xx.txt b/Documentation/devicetree/bindings/input/touchscreen/iqs5xx.txt new file mode 100644 index 000000000000..efa0820e2469 --- /dev/null +++ b/Documentation/devicetree/bindings/input/touchscreen/iqs5xx.txt @@ -0,0 +1,80 @@ +Azoteq IQS550/572/525 Trackpad/Touchscreen Controller + +Required properties: + +- compatible : Must be equal to one of the following: + "azoteq,iqs550" + "azoteq,iqs572" + "azoteq,iqs525" + +- reg : I2C slave address for the device. + +- interrupts : GPIO to which the device's active-high RDY + output is connected (see [0]). + +- reset-gpios : GPIO to which the device's active-low NRST + input is connected (see [1]). + +Optional properties: + +- touchscreen-min-x : See [2]. + +- touchscreen-min-y : See [2]. + +- touchscreen-size-x : See [2]. If this property is omitted, the + maximum x-coordinate is specified by the + device's "X Resolution" register. + +- touchscreen-size-y : See [2]. If this property is omitted, the + maximum y-coordinate is specified by the + device's "Y Resolution" register. + +- touchscreen-max-pressure : See [2]. Pressure is expressed as the sum of + the deltas across all channels impacted by a + touch event. A channel's delta is calculated + as its count value minus a reference, where + the count value is inversely proportional to + the channel's capacitance. + +- touchscreen-fuzz-x : See [2]. + +- touchscreen-fuzz-y : See [2]. + +- touchscreen-fuzz-pressure : See [2]. + +- touchscreen-inverted-x : See [2]. Inversion is applied relative to that + which may already be specified by the device's + FLIP_X and FLIP_Y register fields. + +- touchscreen-inverted-y : See [2]. Inversion is applied relative to that + which may already be specified by the device's + FLIP_X and FLIP_Y register fields. + +- touchscreen-swapped-x-y : See [2]. Swapping is applied relative to that + which may already be specified by the device's + SWITCH_XY_AXIS register field. + +[0]: Documentation/devicetree/bindings/interrupt-controller/interrupts.txt +[1]: Documentation/devicetree/bindings/gpio/gpio.txt +[2]: Documentation/devicetree/bindings/input/touchscreen/touchscreen.txt + +Example: + + &i2c1 { + /* ... */ + + touchscreen@74 { + compatible = "azoteq,iqs550"; + reg = <0x74>; + interrupt-parent = <&gpio>; + interrupts = <17 4>; + reset-gpios = <&gpio 27 1>; + + touchscreen-size-x = <640>; + touchscreen-size-y = <480>; + + touchscreen-max-pressure = <16000>; + }; + + /* ... */ + }; diff --git a/Documentation/devicetree/bindings/leds/leds-max77650.txt b/Documentation/devicetree/bindings/leds/leds-max77650.txt new file mode 100644 index 000000000000..3a67115cc1da --- /dev/null +++ b/Documentation/devicetree/bindings/leds/leds-max77650.txt @@ -0,0 +1,57 @@ +LED driver for MAX77650 PMIC from Maxim Integrated. + +This module is part of the MAX77650 MFD device. For more details +see Documentation/devicetree/bindings/mfd/max77650.txt. + +The LED controller is represented as a sub-node of the PMIC node on +the device tree. + +This device has three current sinks. + +Required properties: +-------------------- +- compatible: Must be "maxim,max77650-led" +- #address-cells: Must be <1>. +- #size-cells: Must be <0>. + +Each LED is represented as a sub-node of the LED-controller node. Up to +three sub-nodes can be defined. + +Required properties of the sub-node: +------------------------------------ + +- reg: Must be <0>, <1> or <2>. + +Optional properties of the sub-node: +------------------------------------ + +- label: See Documentation/devicetree/bindings/leds/common.txt +- linux,default-trigger: See Documentation/devicetree/bindings/leds/common.txt + +For more details, please refer to the generic GPIO DT binding document +<devicetree/bindings/gpio/gpio.txt>. + +Example: +-------- + + leds { + compatible = "maxim,max77650-led"; + #address-cells = <1>; + #size-cells = <0>; + + led@0 { + reg = <0>; + label = "blue:usr0"; + }; + + led@1 { + reg = <1>; + label = "red:usr1"; + linux,default-trigger = "heartbeat"; + }; + + led@2 { + reg = <2>; + label = "green:usr2"; + }; + }; diff --git a/Documentation/devicetree/bindings/mfd/max77650.txt b/Documentation/devicetree/bindings/mfd/max77650.txt new file mode 100644 index 000000000000..b529d8d19335 --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/max77650.txt @@ -0,0 +1,46 @@ +MAX77650 ultra low-power PMIC from Maxim Integrated. + +Required properties: +------------------- +- compatible: Must be "maxim,max77650" +- reg: I2C device address. +- interrupts: The interrupt on the parent the controller is + connected to. +- interrupt-controller: Marks the device node as an interrupt controller. +- #interrupt-cells: Must be <2>. + +- gpio-controller: Marks the device node as a gpio controller. +- #gpio-cells: Must be <2>. The first cell is the pin number and + the second cell is used to specify the gpio active + state. + +Optional properties: +-------------------- +gpio-line-names: Single string containing the name of the GPIO line. + +The GPIO-controller module is represented as part of the top-level PMIC +node. The device exposes a single GPIO line. + +For device-tree bindings of other sub-modules (regulator, power supply, +LEDs and onkey) refer to the binding documents under the respective +sub-system directories. + +For more details on GPIO bindings, please refer to the generic GPIO DT +binding document <devicetree/bindings/gpio/gpio.txt>. + +Example: +-------- + + pmic@48 { + compatible = "maxim,max77650"; + reg = <0x48>; + + interrupt-controller; + interrupt-parent = <&gpio2>; + #interrupt-cells = <2>; + interrupts = <3 IRQ_TYPE_LEVEL_LOW>; + + gpio-controller; + #gpio-cells = <2>; + gpio-line-names = "max77650-charger"; + }; diff --git a/Documentation/devicetree/bindings/power/supply/max77650-charger.txt b/Documentation/devicetree/bindings/power/supply/max77650-charger.txt new file mode 100644 index 000000000000..e6d0fb6ff94e --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/max77650-charger.txt @@ -0,0 +1,28 @@ +Battery charger driver for MAX77650 PMIC from Maxim Integrated. + +This module is part of the MAX77650 MFD device. For more details +see Documentation/devicetree/bindings/mfd/max77650.txt. + +The charger is represented as a sub-node of the PMIC node on the device tree. + +Required properties: +-------------------- +- compatible: Must be "maxim,max77650-charger" + +Optional properties: +-------------------- +- input-voltage-min-microvolt: Minimum CHGIN regulation voltage. Must be one + of: 4000000, 4100000, 4200000, 4300000, + 4400000, 4500000, 4600000, 4700000. +- input-current-limit-microamp: CHGIN input current limit (in microamps). Must + be one of: 95000, 190000, 285000, 380000, + 475000. + +Example: +-------- + + charger { + compatible = "maxim,max77650-charger"; + input-voltage-min-microvolt = <4200000>; + input-current-limit-microamp = <285000>; + }; diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt index 8162b0eb4b50..804bce168f49 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.txt +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt @@ -54,6 +54,7 @@ avic Shanghai AVIC Optoelectronics Co., Ltd. avnet Avnet, Inc. axentia Axentia Technologies AB axis Axis Communications AB +azoteq Azoteq (Pty) Ltd bananapi BIPAI KEJI LIMITED bhf Beckhoff Automation GmbH & Co. KG bitmain Bitmain Technologies diff --git a/MAINTAINERS b/MAINTAINERS index 2c2fce72e694..1ab64e75b9ae 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9408,6 +9408,20 @@ S: Maintained F: Documentation/devicetree/bindings/sound/max9860.txt F: sound/soc/codecs/max9860.* +MAXIM MAX77650 PMIC MFD DRIVER +M: Bartosz Golaszewski <bgolaszewski@baylibre.com> +L: linux-kernel@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/*/*max77650.txt +F: Documentation/devicetree/bindings/*/max77650*.txt +F: include/linux/mfd/max77650.h +F: drivers/mfd/max77650.c +F: drivers/regulator/max77650-regulator.c +F: drivers/power/supply/max77650-charger.c +F: drivers/input/misc/max77650-onkey.c +F: drivers/leds/leds-max77650.c +F: drivers/gpio/gpio-max77650.c + MAXIM MAX77802 PMIC REGULATOR DEVICE DRIVER M: Javier Martinez Canillas <javier@dowhile0.org> L: linux-kernel@vger.kernel.org diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 3f50526a771f..c4f912104440 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1112,6 +1112,13 @@ config GPIO_MAX77620 driver also provides interrupt support for each of the gpios. Say yes here to enable the max77620 to be used as gpio controller. +config GPIO_MAX77650 + tristate "Maxim MAX77650/77651 GPIO support" + depends on MFD_MAX77650 + help + GPIO driver for MAX77650/77651 PMIC from Maxim Semiconductor. + These chips have a single pin that can be configured as GPIO. + config GPIO_MSIC bool "Intel MSIC mixed signal gpio support" depends on (X86 || COMPILE_TEST) && MFD_INTEL_MSIC diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 54d55274b93a..075722d8317d 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -80,6 +80,7 @@ obj-$(CONFIG_GPIO_MAX7300) += gpio-max7300.o obj-$(CONFIG_GPIO_MAX7301) += gpio-max7301.o obj-$(CONFIG_GPIO_MAX732X) += gpio-max732x.o obj-$(CONFIG_GPIO_MAX77620) += gpio-max77620.o +obj-$(CONFIG_GPIO_MAX77650) += gpio-max77650.o obj-$(CONFIG_GPIO_MB86S7X) += gpio-mb86s7x.o obj-$(CONFIG_GPIO_MENZ127) += gpio-menz127.o obj-$(CONFIG_GPIO_MERRIFIELD) += gpio-merrifield.o diff --git a/drivers/gpio/gpio-max77650.c b/drivers/gpio/gpio-max77650.c new file mode 100644 index 000000000000..3f03f4e8956c --- /dev/null +++ b/drivers/gpio/gpio-max77650.c @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (C) 2018 BayLibre SAS +// Author: Bartosz Golaszewski <bgolaszewski@baylibre.com> +// +// GPIO driver for MAXIM 77650/77651 charger/power-supply. + +#include <linux/gpio/driver.h> +#include <linux/i2c.h> +#include <linux/mfd/max77650.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#define MAX77650_GPIO_DIR_MASK BIT(0) +#define MAX77650_GPIO_INVAL_MASK BIT(1) +#define MAX77650_GPIO_DRV_MASK BIT(2) +#define MAX77650_GPIO_OUTVAL_MASK BIT(3) +#define MAX77650_GPIO_DEBOUNCE_MASK BIT(4) + +#define MAX77650_GPIO_DIR_OUT 0x00 +#define MAX77650_GPIO_DIR_IN BIT(0) +#define MAX77650_GPIO_OUT_LOW 0x00 +#define MAX77650_GPIO_OUT_HIGH BIT(3) +#define MAX77650_GPIO_DRV_OPEN_DRAIN 0x00 +#define MAX77650_GPIO_DRV_PUSH_PULL BIT(2) +#define MAX77650_GPIO_DEBOUNCE BIT(4) + +#define MAX77650_GPIO_DIR_BITS(_reg) \ + ((_reg) & MAX77650_GPIO_DIR_MASK) +#define MAX77650_GPIO_INVAL_BITS(_reg) \ + (((_reg) & MAX77650_GPIO_INVAL_MASK) >> 1) + +struct max77650_gpio_chip { + struct regmap *map; + struct gpio_chip gc; + int irq; +}; + +static int max77650_gpio_direction_input(struct gpio_chip *gc, + unsigned int offset) +{ + struct max77650_gpio_chip *chip = gpiochip_get_data(gc); + + return regmap_update_bits(chip->map, + MAX77650_REG_CNFG_GPIO, + MAX77650_GPIO_DIR_MASK, + MAX77650_GPIO_DIR_IN); +} + +static int max77650_gpio_direction_output(struct gpio_chip *gc, + unsigned int offset, int value) +{ + struct max77650_gpio_chip *chip = gpiochip_get_data(gc); + int mask, regval; + + mask = MAX77650_GPIO_DIR_MASK | MAX77650_GPIO_OUTVAL_MASK; + regval = value ? MAX77650_GPIO_OUT_HIGH : MAX77650_GPIO_OUT_LOW; + regval |= MAX77650_GPIO_DIR_OUT; + + return regmap_update_bits(chip->map, + MAX77650_REG_CNFG_GPIO, mask, regval); +} + +static void max77650_gpio_set_value(struct gpio_chip *gc, + unsigned int offset, int value) +{ + struct max77650_gpio_chip *chip = gpiochip_get_data(gc); + int rv, regval; + + regval = value ? MAX77650_GPIO_OUT_HIGH : MAX77650_GPIO_OUT_LOW; + + rv = regmap_update_bits(chip->map, MAX77650_REG_CNFG_GPIO, + MAX77650_GPIO_OUTVAL_MASK, regval); + if (rv) + dev_err(gc->parent, "cannot set GPIO value: %d\n", rv); +} + +static int max77650_gpio_get_value(struct gpio_chip *gc, + unsigned int offset) +{ + struct max77650_gpio_chip *chip = gpiochip_get_data(gc); + unsigned int val; + int rv; + + rv = regmap_read(chip->map, MAX77650_REG_CNFG_GPIO, &val); + if (rv) + return rv; + + return MAX77650_GPIO_INVAL_BITS(val); +} + +static int max77650_gpio_get_direction(struct gpio_chip *gc, + unsigned int offset) +{ + struct max77650_gpio_chip *chip = gpiochip_get_data(gc); + unsigned int val; + int rv; + + rv = regmap_read(chip->map, MAX77650_REG_CNFG_GPIO, &val); + if (rv) + return rv; + + return MAX77650_GPIO_DIR_BITS(val); +} + +static int max77650_gpio_set_config(struct gpio_chip *gc, + unsigned int offset, unsigned long cfg) +{ + struct max77650_gpio_chip *chip = gpiochip_get_data(gc); + + switch (pinconf_to_config_param(cfg)) { + case PIN_CONFIG_DRIVE_OPEN_DRAIN: + return regmap_update_bits(chip->map, + MAX77650_REG_CNFG_GPIO, + MAX77650_GPIO_DRV_MASK, + MAX77650_GPIO_DRV_OPEN_DRAIN); + case PIN_CONFIG_DRIVE_PUSH_PULL: + return regmap_update_bits(chip->map, + MAX77650_REG_CNFG_GPIO, + MAX77650_GPIO_DRV_MASK, + MAX77650_GPIO_DRV_PUSH_PULL); + case PIN_CONFIG_INPUT_DEBOUNCE: + return regmap_update_bits(chip->map, + MAX77650_REG_CNFG_GPIO, + MAX77650_GPIO_DEBOUNCE_MASK, + MAX77650_GPIO_DEBOUNCE); + default: + return -ENOTSUPP; + } +} + +static int max77650_gpio_to_irq(struct gpio_chip *gc, unsigned int offset) +{ + struct max77650_gpio_chip *chip = gpiochip_get_data(gc); + + return chip->irq; +} + +static int max77650_gpio_probe(struct platform_device *pdev) +{ + struct max77650_gpio_chip *chip; + struct device *dev, *parent; + struct i2c_client *i2c; + + dev = &pdev->dev; + parent = dev->parent; + i2c = to_i2c_client(parent); + + chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->map = dev_get_regmap(parent, NULL); + if (!chip->map) + return -ENODEV; + + chip->irq = platform_get_irq_byname(pdev, "GPI"); + if (chip->irq < 0) + return chip->irq; + + chip->gc.base = -1; + chip->gc.ngpio = 1; + chip->gc.label = i2c->name; + chip->gc.parent = dev; + chip->gc.owner = THIS_MODULE; + chip->gc.can_sleep = true; + + chip->gc.direction_input = max77650_gpio_direction_input; + chip->gc.direction_output = max77650_gpio_direction_output; + chip->gc.set = max77650_gpio_set_value; + chip->gc.get = max77650_gpio_get_value; + chip->gc.get_direction = max77650_gpio_get_direction; + chip->gc.set_config = max77650_gpio_set_config; + chip->gc.to_irq = max77650_gpio_to_irq; + + return devm_gpiochip_add_data(dev, &chip->gc, chip); +} + +static struct platform_driver max77650_gpio_driver = { + .driver = { + .name = "max77650-gpio", + }, + .probe = max77650_gpio_probe, +}; +module_platform_driver(max77650_gpio_driver); + +MODULE_DESCRIPTION("MAXIM 77650/77651 GPIO driver"); +MODULE_AUTHOR("Bartosz Golaszewski <bgolaszewski@baylibre.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index b607286a0bc8..0579b8d3f912 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -1051,6 +1051,8 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel case 0x28b: map_key_clear(KEY_FORWARDMAIL); break; case 0x28c: map_key_clear(KEY_SEND); break; + case 0x29d: map_key_clear(KEY_KBD_LAYOUT_NEXT); break; + case 0x2c7: map_key_clear(KEY_KBDINPUTASSIST_PREV); break; case 0x2c8: map_key_clear(KEY_KBDINPUTASSIST_NEXT); break; case 0x2c9: map_key_clear(KEY_KBDINPUTASSIST_PREVGROUP); break; diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c index f48369d6f3a0..ee8dd8b1b09e 100644 --- a/drivers/input/evdev.c +++ b/drivers/input/evdev.c @@ -503,14 +503,13 @@ static int evdev_open(struct inode *inode, struct file *file) { struct evdev *evdev = container_of(inode->i_cdev, struct evdev, cdev); unsigned int bufsize = evdev_compute_buffer_size(evdev->handle.dev); - unsigned int size = sizeof(struct evdev_client) + - bufsize * sizeof(struct input_event); struct evdev_client *client; int error; - client = kzalloc(size, GFP_KERNEL | __GFP_NOWARN); + client = kzalloc(struct_size(client, buffer, bufsize), + GFP_KERNEL | __GFP_NOWARN); if (!client) - client = vzalloc(size); + client = vzalloc(struct_size(client, buffer, bufsize)); if (!client) return -ENOMEM; diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index 52d7f55fca32..1fe039d7326b 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -137,6 +137,17 @@ config KEYBOARD_ATKBD_RDI_KEYCODES right-hand column will be interpreted as the key shown in the left-hand column. +config KEYBOARD_QT1050 + tristate "Microchip AT42QT1050 Touch Sensor Chip" + depends on I2C + select REGMAP_I2C + help + Say Y here if you want to use Microchip AT42QT1050 QTouch + Sensor chip as input device. + + To compile this driver as a module, choose M here: + the module will be called qt1050 + config KEYBOARD_QT1070 tristate "Atmel AT42QT1070 Touch Sensor Chip" depends on I2C diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index 182e92985dbf..f0291ca39f62 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -50,6 +50,7 @@ obj-$(CONFIG_KEYBOARD_OPENCORES) += opencores-kbd.o obj-$(CONFIG_KEYBOARD_PMIC8XXX) += pmic8xxx-keypad.o obj-$(CONFIG_KEYBOARD_PXA27x) += pxa27x_keypad.o obj-$(CONFIG_KEYBOARD_PXA930_ROTARY) += pxa930_rotary.o +obj-$(CONFIG_KEYBOARD_QT1050) += qt1050.o obj-$(CONFIG_KEYBOARD_QT1070) += qt1070.o obj-$(CONFIG_KEYBOARD_QT2160) += qt2160.o obj-$(CONFIG_KEYBOARD_SAMSUNG) += samsung-keypad.o diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c index 850bb259c20e..3ad93e3e2f4c 100644 --- a/drivers/input/keyboard/atkbd.c +++ b/drivers/input/keyboard/atkbd.c @@ -401,6 +401,8 @@ static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data, if (ps2_handle_response(&atkbd->ps2dev, data)) goto out; + pm_wakeup_event(&serio->dev, 0); + if (!atkbd->enabled) goto out; diff --git a/drivers/input/keyboard/gpio_keys.c b/drivers/input/keyboard/gpio_keys.c index 6cd199e8a370..c186c2552b04 100644 --- a/drivers/input/keyboard/gpio_keys.c +++ b/drivers/input/keyboard/gpio_keys.c @@ -774,7 +774,6 @@ static int gpio_keys_probe(struct platform_device *pdev) struct fwnode_handle *child = NULL; struct gpio_keys_drvdata *ddata; struct input_dev *input; - size_t size; int i, error; int wakeup = 0; @@ -784,9 +783,8 @@ static int gpio_keys_probe(struct platform_device *pdev) return PTR_ERR(pdata); } - size = sizeof(struct gpio_keys_drvdata) + - pdata->nbuttons * sizeof(struct gpio_button_data); - ddata = devm_kzalloc(dev, size, GFP_KERNEL); + ddata = devm_kzalloc(dev, struct_size(ddata, data, pdata->nbuttons), + GFP_KERNEL); if (!ddata) { dev_err(dev, "failed to allocate state\n"); return -ENOMEM; diff --git a/drivers/input/keyboard/gpio_keys_polled.c b/drivers/input/keyboard/gpio_keys_polled.c index edc7262103b9..c4087be0c2e0 100644 --- a/drivers/input/keyboard/gpio_keys_polled.c +++ b/drivers/input/keyboard/gpio_keys_polled.c @@ -235,7 +235,6 @@ static int gpio_keys_polled_probe(struct platform_device *pdev) struct gpio_keys_polled_dev *bdev; struct input_polled_dev *poll_dev; struct input_dev *input; - size_t size; int error; int i; @@ -250,9 +249,8 @@ static int gpio_keys_polled_probe(struct platform_device *pdev) return -EINVAL; } - size = sizeof(struct gpio_keys_polled_dev) + - pdata->nbuttons * sizeof(struct gpio_keys_button_data); - bdev = devm_kzalloc(dev, size, GFP_KERNEL); + bdev = devm_kzalloc(dev, struct_size(bdev, data, pdata->nbuttons), + GFP_KERNEL); if (!bdev) { dev_err(dev, "no memory for private data\n"); return -ENOMEM; diff --git a/drivers/input/keyboard/imx_keypad.c b/drivers/input/keyboard/imx_keypad.c index 539cb670de41..cf08f4acb191 100644 --- a/drivers/input/keyboard/imx_keypad.c +++ b/drivers/input/keyboard/imx_keypad.c @@ -422,7 +422,6 @@ static int imx_keypad_probe(struct platform_device *pdev) dev_get_platdata(&pdev->dev); struct imx_keypad *keypad; struct input_dev *input_dev; - struct resource *res; int irq, error, i, row, col; if (!keymap_data && !pdev->dev.of_node) { @@ -455,8 +454,7 @@ static int imx_keypad_probe(struct platform_device *pdev) timer_setup(&keypad->check_matrix_timer, imx_keypad_check_for_events, 0); - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - keypad->mmio_base = devm_ioremap_resource(&pdev->dev, res); + keypad->mmio_base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(keypad->mmio_base)) return PTR_ERR(keypad->mmio_base); diff --git a/drivers/input/keyboard/qt1050.c b/drivers/input/keyboard/qt1050.c new file mode 100644 index 000000000000..403060d05c3b --- /dev/null +++ b/drivers/input/keyboard/qt1050.c @@ -0,0 +1,598 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Microchip AT42QT1050 QTouch Sensor Controller + * + * Copyright (C) 2019 Pengutronix, Marco Felsch <kernel@pengutronix.de> + * + * Base on AT42QT1070 driver by: + * Bo Shen <voice.shen@atmel.com> + * Copyright (C) 2011 Atmel + */ + +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/log2.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regmap.h> + +/* Chip ID */ +#define QT1050_CHIP_ID 0x00 +#define QT1050_CHIP_ID_VER 0x46 + +/* Firmware version */ +#define QT1050_FW_VERSION 0x01 + +/* Detection status */ +#define QT1050_DET_STATUS 0x02 + +/* Key status */ +#define QT1050_KEY_STATUS 0x03 + +/* Key Signals */ +#define QT1050_KEY_SIGNAL_0_MSB 0x06 +#define QT1050_KEY_SIGNAL_0_LSB 0x07 +#define QT1050_KEY_SIGNAL_1_MSB 0x08 +#define QT1050_KEY_SIGNAL_1_LSB 0x09 +#define QT1050_KEY_SIGNAL_2_MSB 0x0c +#define QT1050_KEY_SIGNAL_2_LSB 0x0d +#define QT1050_KEY_SIGNAL_3_MSB 0x0e +#define QT1050_KEY_SIGNAL_3_LSB 0x0f +#define QT1050_KEY_SIGNAL_4_MSB 0x10 +#define QT1050_KEY_SIGNAL_4_LSB 0x11 + +/* Reference data */ +#define QT1050_REF_DATA_0_MSB 0x14 +#define QT1050_REF_DATA_0_LSB 0x15 +#define QT1050_REF_DATA_1_MSB 0x16 +#define QT1050_REF_DATA_1_LSB 0x17 +#define QT1050_REF_DATA_2_MSB 0x1a +#define QT1050_REF_DATA_2_LSB 0x1b +#define QT1050_REF_DATA_3_MSB 0x1c +#define QT1050_REF_DATA_3_LSB 0x1d +#define QT1050_REF_DATA_4_MSB 0x1e +#define QT1050_REF_DATA_4_LSB 0x1f + +/* Negative threshold level */ +#define QT1050_NTHR_0 0x21 +#define QT1050_NTHR_1 0x22 +#define QT1050_NTHR_2 0x24 +#define QT1050_NTHR_3 0x25 +#define QT1050_NTHR_4 0x26 + +/* Pulse / Scale */ +#define QT1050_PULSE_SCALE_0 0x28 +#define QT1050_PULSE_SCALE_1 0x29 +#define QT1050_PULSE_SCALE_2 0x2b +#define QT1050_PULSE_SCALE_3 0x2c +#define QT1050_PULSE_SCALE_4 0x2d + +/* Detection integrator counter / AKS */ +#define QT1050_DI_AKS_0 0x2f +#define QT1050_DI_AKS_1 0x30 +#define QT1050_DI_AKS_2 0x32 +#define QT1050_DI_AKS_3 0x33 +#define QT1050_DI_AKS_4 0x34 + +/* Charge Share Delay */ +#define QT1050_CSD_0 0x36 +#define QT1050_CSD_1 0x37 +#define QT1050_CSD_2 0x39 +#define QT1050_CSD_3 0x3a +#define QT1050_CSD_4 0x3b + +/* Low Power Mode */ +#define QT1050_LPMODE 0x3d + +/* Calibration and Reset */ +#define QT1050_RES_CAL 0x3f +#define QT1050_RES_CAL_RESET BIT(7) +#define QT1050_RES_CAL_CALIBRATE BIT(1) + +#define QT1050_MAX_KEYS 5 +#define QT1050_RESET_TIME 255 + +struct qt1050_key_regs { + unsigned int nthr; + unsigned int pulse_scale; + unsigned int di_aks; + unsigned int csd; +}; + +struct qt1050_key { + u32 num; + u32 charge_delay; + u32 thr_cnt; + u32 samples; + u32 scale; + u32 keycode; +}; + +struct qt1050_priv { + struct i2c_client *client; + struct input_dev *input; + struct regmap *regmap; + struct qt1050_key keys[QT1050_MAX_KEYS]; + unsigned short keycodes[QT1050_MAX_KEYS]; + u8 reg_keys; + u8 last_keys; +}; + +static const struct qt1050_key_regs qt1050_key_regs_data[] = { + { + .nthr = QT1050_NTHR_0, + .pulse_scale = QT1050_PULSE_SCALE_0, + .di_aks = QT1050_DI_AKS_0, + .csd = QT1050_CSD_0, + }, { + .nthr = QT1050_NTHR_1, + .pulse_scale = QT1050_PULSE_SCALE_1, + .di_aks = QT1050_DI_AKS_1, + .csd = QT1050_CSD_1, + }, { + .nthr = QT1050_NTHR_2, + .pulse_scale = QT1050_PULSE_SCALE_2, + .di_aks = QT1050_DI_AKS_2, + .csd = QT1050_CSD_2, + }, { + .nthr = QT1050_NTHR_3, + .pulse_scale = QT1050_PULSE_SCALE_3, + .di_aks = QT1050_DI_AKS_3, + .csd = QT1050_CSD_3, + }, { + .nthr = QT1050_NTHR_4, + .pulse_scale = QT1050_PULSE_SCALE_4, + .di_aks = QT1050_DI_AKS_4, + .csd = QT1050_CSD_4, + } +}; + +static bool qt1050_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case QT1050_DET_STATUS: + case QT1050_KEY_STATUS: + case QT1050_KEY_SIGNAL_0_MSB: + case QT1050_KEY_SIGNAL_0_LSB: + case QT1050_KEY_SIGNAL_1_MSB: + case QT1050_KEY_SIGNAL_1_LSB: + case QT1050_KEY_SIGNAL_2_MSB: + case QT1050_KEY_SIGNAL_2_LSB: + case QT1050_KEY_SIGNAL_3_MSB: + case QT1050_KEY_SIGNAL_3_LSB: + case QT1050_KEY_SIGNAL_4_MSB: + case QT1050_KEY_SIGNAL_4_LSB: + return true; + default: + return false; + } +} + +static const struct regmap_range qt1050_readable_ranges[] = { + regmap_reg_range(QT1050_CHIP_ID, QT1050_KEY_STATUS), + regmap_reg_range(QT1050_KEY_SIGNAL_0_MSB, QT1050_KEY_SIGNAL_1_LSB), + regmap_reg_range(QT1050_KEY_SIGNAL_2_MSB, QT1050_KEY_SIGNAL_4_LSB), + regmap_reg_range(QT1050_REF_DATA_0_MSB, QT1050_REF_DATA_1_LSB), + regmap_reg_range(QT1050_REF_DATA_2_MSB, QT1050_REF_DATA_4_LSB), + regmap_reg_range(QT1050_NTHR_0, QT1050_NTHR_1), + regmap_reg_range(QT1050_NTHR_2, QT1050_NTHR_4), + regmap_reg_range(QT1050_PULSE_SCALE_0, QT1050_PULSE_SCALE_1), + regmap_reg_range(QT1050_PULSE_SCALE_2, QT1050_PULSE_SCALE_4), + regmap_reg_range(QT1050_DI_AKS_0, QT1050_DI_AKS_1), + regmap_reg_range(QT1050_DI_AKS_2, QT1050_DI_AKS_4), + regmap_reg_range(QT1050_CSD_0, QT1050_CSD_1), + regmap_reg_range(QT1050_CSD_2, QT1050_RES_CAL), +}; + +static const struct regmap_access_table qt1050_readable_table = { + .yes_ranges = qt1050_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(qt1050_readable_ranges), +}; + +static const struct regmap_range qt1050_writeable_ranges[] = { + regmap_reg_range(QT1050_NTHR_0, QT1050_NTHR_1), + regmap_reg_range(QT1050_NTHR_2, QT1050_NTHR_4), + regmap_reg_range(QT1050_PULSE_SCALE_0, QT1050_PULSE_SCALE_1), + regmap_reg_range(QT1050_PULSE_SCALE_2, QT1050_PULSE_SCALE_4), + regmap_reg_range(QT1050_DI_AKS_0, QT1050_DI_AKS_1), + regmap_reg_range(QT1050_DI_AKS_2, QT1050_DI_AKS_4), + regmap_reg_range(QT1050_CSD_0, QT1050_CSD_1), + regmap_reg_range(QT1050_CSD_2, QT1050_RES_CAL), +}; + +static const struct regmap_access_table qt1050_writeable_table = { + .yes_ranges = qt1050_writeable_ranges, + .n_yes_ranges = ARRAY_SIZE(qt1050_writeable_ranges), +}; + +static struct regmap_config qt1050_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = QT1050_RES_CAL, + + .cache_type = REGCACHE_RBTREE, + + .wr_table = &qt1050_writeable_table, + .rd_table = &qt1050_readable_table, + .volatile_reg = qt1050_volatile_reg, +}; + +static bool qt1050_identify(struct qt1050_priv *ts) +{ + unsigned int val; + int err; + + /* Read Chip ID */ + regmap_read(ts->regmap, QT1050_CHIP_ID, &val); + if (val != QT1050_CHIP_ID_VER) { + dev_err(&ts->client->dev, "ID %d not supported\n", val); + return false; + } + + /* Read firmware version */ + err = regmap_read(ts->regmap, QT1050_FW_VERSION, &val); + if (err) { + dev_err(&ts->client->dev, "could not read the firmware version\n"); + return false; + } + + dev_info(&ts->client->dev, "AT42QT1050 firmware version %1d.%1d\n", + val >> 4, val & 0xf); + + return true; +} + +static irqreturn_t qt1050_irq_threaded(int irq, void *dev_id) +{ + struct qt1050_priv *ts = dev_id; + struct input_dev *input = ts->input; + unsigned long new_keys, changed; + unsigned int val; + int i, err; + + /* Read the detected status register, thus clearing interrupt */ + err = regmap_read(ts->regmap, QT1050_DET_STATUS, &val); + if (err) { + dev_err(&ts->client->dev, "Fail to read detection status: %d\n", + err); + return IRQ_NONE; + } + + /* Read which key changed, keys are not continuous */ + err = regmap_read(ts->regmap, QT1050_KEY_STATUS, &val); + if (err) { + dev_err(&ts->client->dev, + "Fail to determine the key status: %d\n", err); + return IRQ_NONE; + } + new_keys = (val & 0x70) >> 2 | (val & 0x6) >> 1; + changed = ts->last_keys ^ new_keys; + /* Report registered keys only */ + changed &= ts->reg_keys; + + for_each_set_bit(i, &changed, QT1050_MAX_KEYS) + input_report_key(input, ts->keys[i].keycode, + test_bit(i, &new_keys)); + + ts->last_keys = new_keys; + input_sync(input); + + return IRQ_HANDLED; +} + +static const struct qt1050_key_regs *qt1050_get_key_regs(int key_num) +{ + return &qt1050_key_regs_data[key_num]; +} + +static int qt1050_set_key(struct regmap *map, int number, int on) +{ + const struct qt1050_key_regs *key_regs; + + key_regs = qt1050_get_key_regs(number); + + return regmap_update_bits(map, key_regs->di_aks, 0xfc, + on ? BIT(4) : 0x00); +} + +static int qt1050_apply_fw_data(struct qt1050_priv *ts) +{ + struct regmap *map = ts->regmap; + struct qt1050_key *button = &ts->keys[0]; + const struct qt1050_key_regs *key_regs; + int i, err; + + /* Disable all keys and enable only the specified ones */ + for (i = 0; i < QT1050_MAX_KEYS; i++) { + err = qt1050_set_key(map, i, 0); + if (err) + return err; + } + + for (i = 0; i < QT1050_MAX_KEYS; i++, button++) { + /* Keep KEY_RESERVED keys off */ + if (button->keycode == KEY_RESERVED) + continue; + + err = qt1050_set_key(map, button->num, 1); + if (err) + return err; + + key_regs = qt1050_get_key_regs(button->num); + + err = regmap_write(map, key_regs->pulse_scale, + (button->samples << 4) | (button->scale)); + if (err) + return err; + err = regmap_write(map, key_regs->csd, button->charge_delay); + if (err) + return err; + err = regmap_write(map, key_regs->nthr, button->thr_cnt); + if (err) + return err; + } + + return 0; +} + +static int qt1050_parse_fw(struct qt1050_priv *ts) +{ + struct device *dev = &ts->client->dev; + struct fwnode_handle *child; + int nbuttons; + + nbuttons = device_get_child_node_count(dev); + if (nbuttons == 0 || nbuttons > QT1050_MAX_KEYS) + return -ENODEV; + + device_for_each_child_node(dev, child) { + struct qt1050_key button; + + /* Required properties */ + if (fwnode_property_read_u32(child, "linux,code", + &button.keycode)) { + dev_err(dev, "Button without keycode\n"); + goto err; + } + if (button.keycode >= KEY_MAX) { + dev_err(dev, "Invalid keycode 0x%x\n", + button.keycode); + goto err; + } + + if (fwnode_property_read_u32(child, "reg", + &button.num)) { + dev_err(dev, "Button without pad number\n"); + goto err; + } + if (button.num < 0 || button.num > QT1050_MAX_KEYS - 1) + goto err; + + ts->reg_keys |= BIT(button.num); + + /* Optional properties */ + if (fwnode_property_read_u32(child, + "microchip,pre-charge-time-ns", + &button.charge_delay)) { + button.charge_delay = 0; + } else { + if (button.charge_delay % 2500 == 0) + button.charge_delay = + button.charge_delay / 2500; + else + button.charge_delay = 0; + } + + if (fwnode_property_read_u32(child, "microchip,average-samples", + &button.samples)) { + button.samples = 0; + } else { + if (is_power_of_2(button.samples)) + button.samples = ilog2(button.samples); + else + button.samples = 0; + } + + if (fwnode_property_read_u32(child, "microchip,average-scaling", + &button.scale)) { + button.scale = 0; + } else { + if (is_power_of_2(button.scale)) + button.scale = ilog2(button.scale); + else + button.scale = 0; + + } + + if (fwnode_property_read_u32(child, "microchip,threshold", + &button.thr_cnt)) { + button.thr_cnt = 20; + } else { + if (button.thr_cnt > 255) + button.thr_cnt = 20; + } + + ts->keys[button.num] = button; + } + + return 0; + +err: + fwnode_handle_put(child); + return -EINVAL; +} + +static int qt1050_probe(struct i2c_client *client) +{ + struct qt1050_priv *ts; + struct input_dev *input; + struct device *dev = &client->dev; + struct regmap *map; + unsigned int status, i; + int err; + + /* Check basic functionality */ + err = i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE); + if (!err) { + dev_err(&client->dev, "%s adapter not supported\n", + dev_driver_string(&client->adapter->dev)); + return -ENODEV; + } + + if (!client->irq) { + dev_err(dev, "assign a irq line to this device\n"); + return -EINVAL; + } + + ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL); + if (!ts) + return -ENOMEM; + + input = devm_input_allocate_device(dev); + if (!input) + return -ENOMEM; + + map = devm_regmap_init_i2c(client, &qt1050_regmap_config); + if (IS_ERR(map)) + return PTR_ERR(map); + + ts->client = client; + ts->input = input; + ts->regmap = map; + + i2c_set_clientdata(client, ts); + + /* Identify the qt1050 chip */ + if (!qt1050_identify(ts)) + return -ENODEV; + + /* Get pdata */ + err = qt1050_parse_fw(ts); + if (err) { + dev_err(dev, "Failed to parse firmware: %d\n", err); + return err; + } + + input->name = "AT42QT1050 QTouch Sensor"; + input->dev.parent = &client->dev; + input->id.bustype = BUS_I2C; + + /* Add the keycode */ + input->keycode = ts->keycodes; + input->keycodesize = sizeof(ts->keycodes[0]); + input->keycodemax = QT1050_MAX_KEYS; + + __set_bit(EV_KEY, input->evbit); + for (i = 0; i < QT1050_MAX_KEYS; i++) { + ts->keycodes[i] = ts->keys[i].keycode; + __set_bit(ts->keycodes[i], input->keybit); + } + + /* Trigger re-calibration */ + err = regmap_update_bits(ts->regmap, QT1050_RES_CAL, 0x7f, + QT1050_RES_CAL_CALIBRATE); + if (err) { + dev_err(dev, "Trigger calibration failed: %d\n", err); + return err; + } + err = regmap_read_poll_timeout(ts->regmap, QT1050_DET_STATUS, status, + status >> 7 == 1, 10000, 200000); + if (err) { + dev_err(dev, "Calibration failed: %d\n", err); + return err; + } + + /* Soft reset to set defaults */ + err = regmap_update_bits(ts->regmap, QT1050_RES_CAL, + QT1050_RES_CAL_RESET, QT1050_RES_CAL_RESET); + if (err) { + dev_err(dev, "Trigger soft reset failed: %d\n", err); + return err; + } + msleep(QT1050_RESET_TIME); + + /* Set pdata */ + err = qt1050_apply_fw_data(ts); + if (err) { + dev_err(dev, "Failed to set firmware data: %d\n", err); + return err; + } + + err = devm_request_threaded_irq(dev, client->irq, NULL, + qt1050_irq_threaded, IRQF_ONESHOT, + "qt1050", ts); + if (err) { + dev_err(&client->dev, "Failed to request irq: %d\n", err); + return err; + } + + /* Clear #CHANGE line */ + err = regmap_read(ts->regmap, QT1050_DET_STATUS, &status); + if (err) { + dev_err(dev, "Failed to clear #CHANGE line level: %d\n", err); + return err; + } + + /* Register the input device */ + err = input_register_device(ts->input); + if (err) { + dev_err(&client->dev, "Failed to register input device: %d\n", + err); + return err; + } + + return 0; +} + +static int __maybe_unused qt1050_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct qt1050_priv *ts = i2c_get_clientdata(client); + + disable_irq(client->irq); + + /* + * Set measurement interval to 1s (125 x 8ms) if wakeup is allowed + * else turn off. The 1s interval seems to be a good compromise between + * low power and response time. + */ + return regmap_write(ts->regmap, QT1050_LPMODE, + device_may_wakeup(dev) ? 125 : 0); +} + +static int __maybe_unused qt1050_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct qt1050_priv *ts = i2c_get_clientdata(client); + + enable_irq(client->irq); + + /* Set measurement interval back to 16ms (2 x 8ms) */ + return regmap_write(ts->regmap, QT1050_LPMODE, 2); +} + +static SIMPLE_DEV_PM_OPS(qt1050_pm_ops, qt1050_suspend, qt1050_resume); + +static const struct of_device_id __maybe_unused qt1050_of_match[] = { + { .compatible = "microchip,qt1050", }, + { }, +}; +MODULE_DEVICE_TABLE(of, qt1050_of_match); + +static struct i2c_driver qt1050_driver = { + .driver = { + .name = "qt1050", + .of_match_table = of_match_ptr(qt1050_of_match), + .pm = &qt1050_pm_ops, + }, + .probe_new = qt1050_probe, +}; + +module_i2c_driver(qt1050_driver); + +MODULE_AUTHOR("Marco Felsch <kernel@pengutronix.de"); +MODULE_DESCRIPTION("Driver for AT42QT1050 QTouch sensor"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/keyboard/snvs_pwrkey.c b/drivers/input/keyboard/snvs_pwrkey.c index 4c67cf30a5d9..5342d8d45f81 100644 --- a/drivers/input/keyboard/snvs_pwrkey.c +++ b/drivers/input/keyboard/snvs_pwrkey.c @@ -15,6 +15,7 @@ #include <linux/of.h> #include <linux/of_address.h> #include <linux/platform_device.h> +#include <linux/pm_wakeirq.h> #include <linux/mfd/syscon.h> #include <linux/regmap.h> @@ -167,28 +168,9 @@ static int imx_snvs_pwrkey_probe(struct platform_device *pdev) } device_init_wakeup(&pdev->dev, pdata->wakeup); - - return 0; -} - -static int __maybe_unused imx_snvs_pwrkey_suspend(struct device *dev) -{ - struct platform_device *pdev = to_platform_device(dev); - struct pwrkey_drv_data *pdata = platform_get_drvdata(pdev); - - if (device_may_wakeup(&pdev->dev)) - enable_irq_wake(pdata->irq); - - return 0; -} - -static int __maybe_unused imx_snvs_pwrkey_resume(struct device *dev) -{ - struct platform_device *pdev = to_platform_device(dev); - struct pwrkey_drv_data *pdata = platform_get_drvdata(pdev); - - if (device_may_wakeup(&pdev->dev)) - disable_irq_wake(pdata->irq); + error = dev_pm_set_wake_irq(&pdev->dev, pdata->irq); + if (error) + dev_err(&pdev->dev, "irq wake enable failed.\n"); return 0; } @@ -199,13 +181,9 @@ static const struct of_device_id imx_snvs_pwrkey_ids[] = { }; MODULE_DEVICE_TABLE(of, imx_snvs_pwrkey_ids); -static SIMPLE_DEV_PM_OPS(imx_snvs_pwrkey_pm_ops, imx_snvs_pwrkey_suspend, - imx_snvs_pwrkey_resume); - static struct platform_driver imx_snvs_pwrkey_driver = { .driver = { .name = "snvs_pwrkey", - .pm = &imx_snvs_pwrkey_pm_ops, .of_match_table = imx_snvs_pwrkey_ids, }, .probe = imx_snvs_pwrkey_probe, diff --git a/drivers/input/keyboard/sun4i-lradc-keys.c b/drivers/input/keyboard/sun4i-lradc-keys.c index 57272df34cd5..df3eec72a9b2 100644 --- a/drivers/input/keyboard/sun4i-lradc-keys.c +++ b/drivers/input/keyboard/sun4i-lradc-keys.c @@ -46,6 +46,7 @@ #define CONTINUE_TIME_SEL(x) ((x) << 16) /* 4 bits */ #define KEY_MODE_SEL(x) ((x) << 12) /* 2 bits */ #define LEVELA_B_CNT(x) ((x) << 8) /* 4 bits */ +#define HOLD_KEY_EN(x) ((x) << 7) #define HOLD_EN(x) ((x) << 6) #define LEVELB_VOL(x) ((x) << 4) /* 2 bits */ #define SAMPLE_RATE(x) ((x) << 2) /* 2 bits */ @@ -63,6 +64,25 @@ #define CHAN0_KEYDOWN_IRQ BIT(1) #define CHAN0_DATA_IRQ BIT(0) +/* struct lradc_variant - Describe sun4i-a10-lradc-keys hardware variant + * @divisor_numerator: The numerator of lradc Vref internally divisor + * @divisor_denominator: The denominator of lradc Vref internally divisor + */ +struct lradc_variant { + u8 divisor_numerator; + u8 divisor_denominator; +}; + +static const struct lradc_variant lradc_variant_a10 = { + .divisor_numerator = 2, + .divisor_denominator = 3 +}; + +static const struct lradc_variant r_lradc_variant_a83t = { + .divisor_numerator = 3, + .divisor_denominator = 4 +}; + struct sun4i_lradc_keymap { u32 voltage; u32 keycode; @@ -74,6 +94,7 @@ struct sun4i_lradc_data { void __iomem *base; struct regulator *vref_supply; struct sun4i_lradc_keymap *chan0_map; + const struct lradc_variant *variant; u32 chan0_map_count; u32 chan0_keycode; u32 vref; @@ -128,9 +149,9 @@ static int sun4i_lradc_open(struct input_dev *dev) if (error) return error; - /* lradc Vref internally is divided by 2/3 */ - lradc->vref = regulator_get_voltage(lradc->vref_supply) * 2 / 3; - + lradc->vref = regulator_get_voltage(lradc->vref_supply) * + lradc->variant->divisor_numerator / + lradc->variant->divisor_denominator; /* * Set sample time to 4 ms / 250 Hz. Wait 2 * 4 ms for key to * stabilize on press, wait (1 + 1) * 4 ms for key release @@ -222,6 +243,12 @@ static int sun4i_lradc_probe(struct platform_device *pdev) if (error) return error; + lradc->variant = of_device_get_match_data(&pdev->dev); + if (!lradc->variant) { + dev_err(&pdev->dev, "Missing sun4i-a10-lradc-keys variant\n"); + return -EINVAL; + } + lradc->vref_supply = devm_regulator_get(dev, "vref"); if (IS_ERR(lradc->vref_supply)) return PTR_ERR(lradc->vref_supply); @@ -265,7 +292,10 @@ static int sun4i_lradc_probe(struct platform_device *pdev) } static const struct of_device_id sun4i_lradc_of_match[] = { - { .compatible = "allwinner,sun4i-a10-lradc-keys", }, + { .compatible = "allwinner,sun4i-a10-lradc-keys", + .data = &lradc_variant_a10 }, + { .compatible = "allwinner,sun8i-a83t-r-lradc", + .data = &r_lradc_variant_a83t }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, sun4i_lradc_of_match); diff --git a/drivers/input/keyboard/tca8418_keypad.c b/drivers/input/keyboard/tca8418_keypad.c index 6da607d3b811..3bbd7e652533 100644 --- a/drivers/input/keyboard/tca8418_keypad.c +++ b/drivers/input/keyboard/tca8418_keypad.c @@ -266,7 +266,7 @@ static int tca8418_keypad_probe(struct i2c_client *client, struct tca8418_keypad *keypad_data; struct input_dev *input; u32 rows = 0, cols = 0; - int error, row_shift, max_keys; + int error, row_shift; u8 reg; /* Check i2c driver capabilities */ @@ -291,7 +291,6 @@ static int tca8418_keypad_probe(struct i2c_client *client, } row_shift = get_count_order(cols); - max_keys = rows << row_shift; /* Allocate memory for keypad_data and input device */ keypad_data = devm_kzalloc(dev, sizeof(*keypad_data), GFP_KERNEL); diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index e15ed1bb8558..54d36f98b426 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -190,6 +190,15 @@ 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 @@ -290,6 +299,18 @@ config INPUT_GPIO_DECODER 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_IXP4XX_BEEPER tristate "IXP4XX Beeper support" depends on ARCH_IXP4XX diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index b936c5b1d4ac..8fd187f314bd 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -36,6 +36,7 @@ obj-$(CONFIG_INPUT_DRV2667_HAPTICS) += drv2667.o obj-$(CONFIG_INPUT_GP2A) += gp2ap002a00f.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_IMS_PCU) += ims-pcu.o @@ -43,6 +44,7 @@ obj-$(CONFIG_INPUT_IXP4XX_BEEPER) += ixp4xx-beeper.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 diff --git a/drivers/input/misc/da9063_onkey.c b/drivers/input/misc/da9063_onkey.c index 3e9c353d82ef..c06e067bd627 100644 --- a/drivers/input/misc/da9063_onkey.c +++ b/drivers/input/misc/da9063_onkey.c @@ -1,16 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0+ /* * OnKey device driver for DA9063, DA9062 and DA9061 PMICs * Copyright (C) 2015 Dialog Semiconductor Ltd. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #include <linux/module.h> @@ -22,7 +13,6 @@ #include <linux/regmap.h> #include <linux/of.h> #include <linux/mfd/da9063/core.h> -#include <linux/mfd/da9063/pdata.h> #include <linux/mfd/da9063/registers.h> #include <linux/mfd/da9062/core.h> #include <linux/mfd/da9062/registers.h> @@ -201,8 +191,6 @@ static void da9063_cancel_poll(void *data) static int da9063_onkey_probe(struct platform_device *pdev) { - struct da9063 *da9063 = dev_get_drvdata(pdev->dev.parent); - struct da9063_pdata *pdata = dev_get_platdata(da9063->dev); struct da9063_onkey *onkey; const struct of_device_id *match; int irq; @@ -229,12 +217,8 @@ static int da9063_onkey_probe(struct platform_device *pdev) return -ENXIO; } - if (pdata) - onkey->key_power = pdata->key_power; - else - onkey->key_power = - !of_property_read_bool(pdev->dev.of_node, - "dlg,disable-key-power"); + 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) { diff --git a/drivers/input/misc/gpio-vibra.c b/drivers/input/misc/gpio-vibra.c new file mode 100644 index 000000000000..f79f75595dd7 --- /dev/null +++ b/drivers/input/misc/gpio-vibra.c @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * GPIO vibrator driver + * + * Copyright (C) 2019 Luca Weiss <luca@z3ntu.xyz> + * + * Based on PWM vibrator driver: + * Copyright (C) 2017 Collabora Ltd. + * + * Based on previous work from: + * Copyright (C) 2012 Dmitry Torokhov <dmitry.torokhov@gmail.com> + * + * Based on PWM beeper driver: + * Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de> + */ + +#include <linux/gpio/consumer.h> +#include <linux/input.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> + +struct gpio_vibrator { + struct input_dev *input; + struct gpio_desc *gpio; + struct regulator *vcc; + + struct work_struct play_work; + bool running; + bool vcc_on; +}; + +static int gpio_vibrator_start(struct gpio_vibrator *vibrator) +{ + struct device *pdev = vibrator->input->dev.parent; + int err; + + if (!vibrator->vcc_on) { + err = regulator_enable(vibrator->vcc); + if (err) { + dev_err(pdev, "failed to enable regulator: %d\n", err); + return err; + } + vibrator->vcc_on = true; + } + + gpiod_set_value_cansleep(vibrator->gpio, 1); + + return 0; +} + +static void gpio_vibrator_stop(struct gpio_vibrator *vibrator) +{ + gpiod_set_value_cansleep(vibrator->gpio, 0); + + if (vibrator->vcc_on) { + regulator_disable(vibrator->vcc); + vibrator->vcc_on = false; + } +} + +static void gpio_vibrator_play_work(struct work_struct *work) +{ + struct gpio_vibrator *vibrator = + container_of(work, struct gpio_vibrator, play_work); + + if (vibrator->running) + gpio_vibrator_start(vibrator); + else + gpio_vibrator_stop(vibrator); +} + +static int gpio_vibrator_play_effect(struct input_dev *dev, void *data, + struct ff_effect *effect) +{ + struct gpio_vibrator *vibrator = input_get_drvdata(dev); + int level; + + level = effect->u.rumble.strong_magnitude; + if (!level) + level = effect->u.rumble.weak_magnitude; + + vibrator->running = level; + schedule_work(&vibrator->play_work); + + return 0; +} + +static void gpio_vibrator_close(struct input_dev *input) +{ + struct gpio_vibrator *vibrator = input_get_drvdata(input); + + cancel_work_sync(&vibrator->play_work); + gpio_vibrator_stop(vibrator); + vibrator->running = false; +} + +static int gpio_vibrator_probe(struct platform_device *pdev) +{ + struct gpio_vibrator *vibrator; + int err; + + vibrator = devm_kzalloc(&pdev->dev, sizeof(*vibrator), GFP_KERNEL); + if (!vibrator) + return -ENOMEM; + + vibrator->input = devm_input_allocate_device(&pdev->dev); + if (!vibrator->input) + return -ENOMEM; + + vibrator->vcc = devm_regulator_get(&pdev->dev, "vcc"); + err = PTR_ERR_OR_ZERO(vibrator->vcc); + if (err) { + if (err != -EPROBE_DEFER) + dev_err(&pdev->dev, "Failed to request regulator: %d\n", + err); + return err; + } + + vibrator->gpio = devm_gpiod_get(&pdev->dev, "enable", GPIOD_OUT_LOW); + err = PTR_ERR_OR_ZERO(vibrator->gpio); + if (err) { + if (err != -EPROBE_DEFER) + dev_err(&pdev->dev, "Failed to request main gpio: %d\n", + err); + return err; + } + + INIT_WORK(&vibrator->play_work, gpio_vibrator_play_work); + + vibrator->input->name = "gpio-vibrator"; + vibrator->input->id.bustype = BUS_HOST; + vibrator->input->close = gpio_vibrator_close; + + input_set_drvdata(vibrator->input, vibrator); + input_set_capability(vibrator->input, EV_FF, FF_RUMBLE); + + err = input_ff_create_memless(vibrator->input, NULL, + gpio_vibrator_play_effect); + if (err) { + dev_err(&pdev->dev, "Couldn't create FF dev: %d\n", err); + return err; + } + + err = input_register_device(vibrator->input); + if (err) { + dev_err(&pdev->dev, "Couldn't register input dev: %d\n", err); + return err; + } + + platform_set_drvdata(pdev, vibrator); + + return 0; +} + +static int __maybe_unused gpio_vibrator_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct gpio_vibrator *vibrator = platform_get_drvdata(pdev); + + cancel_work_sync(&vibrator->play_work); + if (vibrator->running) + gpio_vibrator_stop(vibrator); + + return 0; +} + +static int __maybe_unused gpio_vibrator_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct gpio_vibrator *vibrator = platform_get_drvdata(pdev); + + if (vibrator->running) + gpio_vibrator_start(vibrator); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(gpio_vibrator_pm_ops, + gpio_vibrator_suspend, gpio_vibrator_resume); + +#ifdef CONFIG_OF +static const struct of_device_id gpio_vibra_dt_match_table[] = { + { .compatible = "gpio-vibrator" }, + {} +}; +MODULE_DEVICE_TABLE(of, gpio_vibra_dt_match_table); +#endif + +static struct platform_driver gpio_vibrator_driver = { + .probe = gpio_vibrator_probe, + .driver = { + .name = "gpio-vibrator", + .pm = &gpio_vibrator_pm_ops, + .of_match_table = of_match_ptr(gpio_vibra_dt_match_table), + }, +}; +module_platform_driver(gpio_vibrator_driver); + +MODULE_AUTHOR("Luca Weiss <luca@z3ntu.xy>"); +MODULE_DESCRIPTION("GPIO vibrator driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:gpio-vibrator"); diff --git a/drivers/input/misc/max77650-onkey.c b/drivers/input/misc/max77650-onkey.c new file mode 100644 index 000000000000..fbf6caab7217 --- /dev/null +++ b/drivers/input/misc/max77650-onkey.c @@ -0,0 +1,121 @@ +// 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 struct platform_driver max77650_onkey_driver = { + .driver = { + .name = "max77650-onkey", + }, + .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"); diff --git a/drivers/input/misc/uinput.c b/drivers/input/misc/uinput.c index 26ec603fe220..83d1499fe021 100644 --- a/drivers/input/misc/uinput.c +++ b/drivers/input/misc/uinput.c @@ -1051,13 +1051,31 @@ static long uinput_ioctl(struct file *file, unsigned int cmd, unsigned long arg) #ifdef CONFIG_COMPAT -#define UI_SET_PHYS_COMPAT _IOW(UINPUT_IOCTL_BASE, 108, compat_uptr_t) +/* + * 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) { - if (cmd == UI_SET_PHYS_COMPAT) + 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)); } diff --git a/drivers/input/mouse/elan_i2c_core.c b/drivers/input/mouse/elan_i2c_core.c index f9525d6f0bfe..65cd325eabc3 100644 --- a/drivers/input/mouse/elan_i2c_core.c +++ b/drivers/input/mouse/elan_i2c_core.c @@ -99,6 +99,7 @@ struct elan_tp_data { u8 max_baseline; bool baseline_ready; u8 clickpad; + bool middle_button; }; static int elan_get_fwinfo(u16 ic_type, u16 *validpage_count, @@ -366,27 +367,62 @@ static unsigned int elan_convert_resolution(u8 val) static int elan_query_device_parameters(struct elan_tp_data *data) { + struct i2c_client *client = data->client; unsigned int x_traces, y_traces; + u32 x_mm, y_mm; u8 hw_x_res, hw_y_res; int error; - error = data->ops->get_max(data->client, &data->max_x, &data->max_y); - if (error) - return error; - - error = data->ops->get_num_traces(data->client, &x_traces, &y_traces); - if (error) - return error; + if (device_property_read_u32(&client->dev, + "touchscreen-size-x", &data->max_x) || + device_property_read_u32(&client->dev, + "touchscreen-size-y", &data->max_y)) { + error = data->ops->get_max(data->client, + &data->max_x, + &data->max_y); + if (error) + return error; + } else { + /* size is the maximum + 1 */ + --data->max_x; + --data->max_y; + } + if (device_property_read_u32(&client->dev, + "elan,x_traces", + &x_traces) || + device_property_read_u32(&client->dev, + "elan,y_traces", + &y_traces)) { + error = data->ops->get_num_traces(data->client, + &x_traces, &y_traces); + if (error) + return error; + } data->width_x = data->max_x / x_traces; data->width_y = data->max_y / y_traces; - error = data->ops->get_resolution(data->client, &hw_x_res, &hw_y_res); - if (error) - return error; + if (device_property_read_u32(&client->dev, + "touchscreen-x-mm", &x_mm) || + device_property_read_u32(&client->dev, + "touchscreen-y-mm", &y_mm)) { + error = data->ops->get_resolution(data->client, + &hw_x_res, &hw_y_res); + if (error) + return error; + + data->x_res = elan_convert_resolution(hw_x_res); + data->y_res = elan_convert_resolution(hw_y_res); + } else { + data->x_res = (data->max_x + 1) / x_mm; + data->y_res = (data->max_y + 1) / y_mm; + } + + if (device_property_read_bool(&client->dev, "elan,clickpad")) + data->clickpad = 1; - data->x_res = elan_convert_resolution(hw_x_res); - data->y_res = elan_convert_resolution(hw_y_res); + if (device_property_read_bool(&client->dev, "elan,middle-button")) + data->middle_button = true; return 0; } @@ -926,8 +962,9 @@ static void elan_report_absolute(struct elan_tp_data *data, u8 *packet) finger_data += ETP_FINGER_DATA_LEN; } - input_report_key(input, BTN_LEFT, tp_info & 0x01); - input_report_key(input, BTN_RIGHT, tp_info & 0x02); + input_report_key(input, BTN_LEFT, tp_info & BIT(0)); + input_report_key(input, BTN_MIDDLE, tp_info & BIT(2)); + input_report_key(input, BTN_RIGHT, tp_info & BIT(1)); input_report_abs(input, ABS_DISTANCE, hover_event != 0); input_mt_report_pointer_emulation(input, true); input_sync(input); @@ -981,6 +1018,8 @@ static irqreturn_t elan_isr(int irq, void *dev_id) if (error) goto out; + pm_wakeup_event(dev, 0); + switch (report[ETP_REPORT_ID_OFFSET]) { case ETP_REPORT_ID: elan_report_absolute(data, report); @@ -1059,10 +1098,13 @@ static int elan_setup_input_device(struct elan_tp_data *data) __set_bit(EV_ABS, input->evbit); __set_bit(INPUT_PROP_POINTER, input->propbit); - if (data->clickpad) + if (data->clickpad) { __set_bit(INPUT_PROP_BUTTONPAD, input->propbit); - else + } else { __set_bit(BTN_RIGHT, input->keybit); + if (data->middle_button) + __set_bit(BTN_MIDDLE, input->keybit); + } __set_bit(BTN_LEFT, input->keybit); /* Set up ST parameters */ diff --git a/drivers/input/mouse/elantech.c b/drivers/input/mouse/elantech.c index a7f8b1614559..ea1ee0f44a65 100644 --- a/drivers/input/mouse/elantech.c +++ b/drivers/input/mouse/elantech.c @@ -230,6 +230,52 @@ static void elantech_packet_dump(struct psmouse *psmouse) } /* + * Advertise INPUT_PROP_BUTTONPAD for clickpads. The testing of bit 12 in + * fw_version for this is based on the following fw_version & caps table: + * + * Laptop-model: fw_version: caps: buttons: + * Acer S3 0x461f00 10, 13, 0e clickpad + * Acer S7-392 0x581f01 50, 17, 0d clickpad + * Acer V5-131 0x461f02 01, 16, 0c clickpad + * Acer V5-551 0x461f00 ? clickpad + * Asus K53SV 0x450f01 78, 15, 0c 2 hw buttons + * Asus G46VW 0x460f02 00, 18, 0c 2 hw buttons + * Asus G750JX 0x360f00 00, 16, 0c 2 hw buttons + * Asus TP500LN 0x381f17 10, 14, 0e clickpad + * Asus X750JN 0x381f17 10, 14, 0e clickpad + * Asus UX31 0x361f00 20, 15, 0e clickpad + * Asus UX32VD 0x361f02 00, 15, 0e clickpad + * Avatar AVIU-145A2 0x361f00 ? clickpad + * Fujitsu CELSIUS H760 0x570f02 40, 14, 0c 3 hw buttons (**) + * Fujitsu CELSIUS H780 0x5d0f02 41, 16, 0d 3 hw buttons (**) + * Fujitsu LIFEBOOK E544 0x470f00 d0, 12, 09 2 hw buttons + * Fujitsu LIFEBOOK E546 0x470f00 50, 12, 09 2 hw buttons + * Fujitsu LIFEBOOK E547 0x470f00 50, 12, 09 2 hw buttons + * Fujitsu LIFEBOOK E554 0x570f01 40, 14, 0c 2 hw buttons + * Fujitsu LIFEBOOK E557 0x570f01 40, 14, 0c 2 hw buttons + * Fujitsu T725 0x470f01 05, 12, 09 2 hw buttons + * Fujitsu H730 0x570f00 c0, 14, 0c 3 hw buttons (**) + * Gigabyte U2442 0x450f01 58, 17, 0c 2 hw buttons + * Lenovo L430 0x350f02 b9, 15, 0c 2 hw buttons (*) + * Lenovo L530 0x350f02 b9, 15, 0c 2 hw buttons (*) + * Samsung NF210 0x150b00 78, 14, 0a 2 hw buttons + * Samsung NP770Z5E 0x575f01 10, 15, 0f clickpad + * Samsung NP700Z5B 0x361f06 21, 15, 0f clickpad + * Samsung NP900X3E-A02 0x575f03 ? clickpad + * Samsung NP-QX410 0x851b00 19, 14, 0c clickpad + * Samsung RC512 0x450f00 08, 15, 0c 2 hw buttons + * Samsung RF710 0x450f00 ? 2 hw buttons + * System76 Pangolin 0x250f01 ? 2 hw buttons + * (*) + 3 trackpoint buttons + * (**) + 0 trackpoint buttons + * Note: Lenovo L430 and Lenovo L530 have the same fw_version/caps + */ +static inline int elantech_is_buttonpad(struct elantech_device_info *info) +{ + return info->fw_version & 0x001000; +} + +/* * Interpret complete data packets and report absolute mode input events for * hardware version 1. (4 byte packets) */ @@ -526,7 +572,7 @@ static void elantech_report_absolute_v3(struct psmouse *psmouse, input_report_key(dev, BTN_TOOL_TRIPLETAP, fingers == 3); /* For clickpads map both buttons to BTN_LEFT */ - if (etd->info.fw_version & 0x001000) + if (elantech_is_buttonpad(&etd->info)) input_report_key(dev, BTN_LEFT, packet[0] & 0x03); else psmouse_report_standard_buttons(dev, packet[0]); @@ -544,7 +590,7 @@ static void elantech_input_sync_v4(struct psmouse *psmouse) unsigned char *packet = psmouse->packet; /* For clickpads map both buttons to BTN_LEFT */ - if (etd->info.fw_version & 0x001000) + if (elantech_is_buttonpad(&etd->info)) input_report_key(dev, BTN_LEFT, packet[0] & 0x03); else psmouse_report_standard_buttons(dev, packet[0]); @@ -994,88 +1040,6 @@ static int elantech_set_absolute_mode(struct psmouse *psmouse) return rc; } -static int elantech_set_range(struct psmouse *psmouse, - unsigned int *x_min, unsigned int *y_min, - unsigned int *x_max, unsigned int *y_max, - unsigned int *width) -{ - struct elantech_data *etd = psmouse->private; - struct elantech_device_info *info = &etd->info; - unsigned char param[3]; - unsigned char traces; - - switch (info->hw_version) { - case 1: - *x_min = ETP_XMIN_V1; - *y_min = ETP_YMIN_V1; - *x_max = ETP_XMAX_V1; - *y_max = ETP_YMAX_V1; - break; - - case 2: - if (info->fw_version == 0x020800 || - info->fw_version == 0x020b00 || - info->fw_version == 0x020030) { - *x_min = ETP_XMIN_V2; - *y_min = ETP_YMIN_V2; - *x_max = ETP_XMAX_V2; - *y_max = ETP_YMAX_V2; - } else { - int i; - int fixed_dpi; - - i = (info->fw_version > 0x020800 && - info->fw_version < 0x020900) ? 1 : 2; - - if (info->send_cmd(psmouse, ETP_FW_ID_QUERY, param)) - return -1; - - fixed_dpi = param[1] & 0x10; - - if (((info->fw_version >> 16) == 0x14) && fixed_dpi) { - if (info->send_cmd(psmouse, ETP_SAMPLE_QUERY, param)) - return -1; - - *x_max = (info->capabilities[1] - i) * param[1] / 2; - *y_max = (info->capabilities[2] - i) * param[2] / 2; - } else if (info->fw_version == 0x040216) { - *x_max = 819; - *y_max = 405; - } else if (info->fw_version == 0x040219 || info->fw_version == 0x040215) { - *x_max = 900; - *y_max = 500; - } else { - *x_max = (info->capabilities[1] - i) * 64; - *y_max = (info->capabilities[2] - i) * 64; - } - } - break; - - case 3: - if (info->send_cmd(psmouse, ETP_FW_ID_QUERY, param)) - return -1; - - *x_max = (0x0f & param[0]) << 8 | param[1]; - *y_max = (0xf0 & param[0]) << 4 | param[2]; - break; - - case 4: - if (info->send_cmd(psmouse, ETP_FW_ID_QUERY, param)) - return -1; - - *x_max = (0x0f & param[0]) << 8 | param[1]; - *y_max = (0xf0 & param[0]) << 4 | param[2]; - traces = info->capabilities[1]; - if ((traces < 2) || (traces > *x_max)) - return -1; - - *width = *x_max / (traces - 1); - break; - } - - return 0; -} - /* * (value from firmware) * 10 + 790 = dpi * we also have to convert dpi to dots/mm (*10/254 to avoid floating point) @@ -1102,53 +1066,12 @@ static int elantech_get_resolution_v4(struct psmouse *psmouse, return 0; } -/* - * Advertise INPUT_PROP_BUTTONPAD for clickpads. The testing of bit 12 in - * fw_version for this is based on the following fw_version & caps table: - * - * Laptop-model: fw_version: caps: buttons: - * Acer S3 0x461f00 10, 13, 0e clickpad - * Acer S7-392 0x581f01 50, 17, 0d clickpad - * Acer V5-131 0x461f02 01, 16, 0c clickpad - * Acer V5-551 0x461f00 ? clickpad - * Asus K53SV 0x450f01 78, 15, 0c 2 hw buttons - * Asus G46VW 0x460f02 00, 18, 0c 2 hw buttons - * Asus G750JX 0x360f00 00, 16, 0c 2 hw buttons - * Asus TP500LN 0x381f17 10, 14, 0e clickpad - * Asus X750JN 0x381f17 10, 14, 0e clickpad - * Asus UX31 0x361f00 20, 15, 0e clickpad - * Asus UX32VD 0x361f02 00, 15, 0e clickpad - * Avatar AVIU-145A2 0x361f00 ? clickpad - * Fujitsu CELSIUS H760 0x570f02 40, 14, 0c 3 hw buttons (**) - * Fujitsu CELSIUS H780 0x5d0f02 41, 16, 0d 3 hw buttons (**) - * Fujitsu LIFEBOOK E544 0x470f00 d0, 12, 09 2 hw buttons - * Fujitsu LIFEBOOK E546 0x470f00 50, 12, 09 2 hw buttons - * Fujitsu LIFEBOOK E547 0x470f00 50, 12, 09 2 hw buttons - * Fujitsu LIFEBOOK E554 0x570f01 40, 14, 0c 2 hw buttons - * Fujitsu LIFEBOOK E557 0x570f01 40, 14, 0c 2 hw buttons - * Fujitsu T725 0x470f01 05, 12, 09 2 hw buttons - * Fujitsu H730 0x570f00 c0, 14, 0c 3 hw buttons (**) - * Gigabyte U2442 0x450f01 58, 17, 0c 2 hw buttons - * Lenovo L430 0x350f02 b9, 15, 0c 2 hw buttons (*) - * Lenovo L530 0x350f02 b9, 15, 0c 2 hw buttons (*) - * Samsung NF210 0x150b00 78, 14, 0a 2 hw buttons - * Samsung NP770Z5E 0x575f01 10, 15, 0f clickpad - * Samsung NP700Z5B 0x361f06 21, 15, 0f clickpad - * Samsung NP900X3E-A02 0x575f03 ? clickpad - * Samsung NP-QX410 0x851b00 19, 14, 0c clickpad - * Samsung RC512 0x450f00 08, 15, 0c 2 hw buttons - * Samsung RF710 0x450f00 ? 2 hw buttons - * System76 Pangolin 0x250f01 ? 2 hw buttons - * (*) + 3 trackpoint buttons - * (**) + 0 trackpoint buttons - * Note: Lenovo L430 and Lenovo L530 have the same fw_version/caps - */ static void elantech_set_buttonpad_prop(struct psmouse *psmouse) { struct input_dev *dev = psmouse->dev; struct elantech_data *etd = psmouse->private; - if (etd->info.fw_version & 0x001000) { + if (elantech_is_buttonpad(&etd->info)) { __set_bit(INPUT_PROP_BUTTONPAD, dev->propbit); __clear_bit(BTN_RIGHT, dev->keybit); } @@ -1184,14 +1107,6 @@ static const struct dmi_system_id elantech_dmi_has_middle_button[] = { { } }; -static const char * const middle_button_pnp_ids[] = { - "LEN2131", /* ThinkPad P52 w/ NFC */ - "LEN2132", /* ThinkPad P52 */ - "LEN2133", /* ThinkPad P72 w/ NFC */ - "LEN2134", /* ThinkPad P72 */ - NULL -}; - /* * Set the appropriate event bits for the input subsystem */ @@ -1200,10 +1115,9 @@ static int elantech_set_input_params(struct psmouse *psmouse) struct input_dev *dev = psmouse->dev; struct elantech_data *etd = psmouse->private; struct elantech_device_info *info = &etd->info; - unsigned int x_min = 0, y_min = 0, x_max = 0, y_max = 0, width = 0; - - if (elantech_set_range(psmouse, &x_min, &y_min, &x_max, &y_max, &width)) - return -1; + unsigned int x_min = info->x_min, y_min = info->y_min, + x_max = info->x_max, y_max = info->y_max, + width = info->width; __set_bit(INPUT_PROP_POINTER, dev->propbit); __set_bit(EV_KEY, dev->evbit); @@ -1211,8 +1125,7 @@ static int elantech_set_input_params(struct psmouse *psmouse) __clear_bit(EV_REL, dev->evbit); __set_bit(BTN_LEFT, dev->keybit); - if (dmi_check_system(elantech_dmi_has_middle_button) || - psmouse_matches_pnp_id(psmouse, middle_button_pnp_ids)) + if (info->has_middle_button) __set_bit(BTN_MIDDLE, dev->keybit); __set_bit(BTN_RIGHT, dev->keybit); @@ -1687,6 +1600,7 @@ static int elantech_query_info(struct psmouse *psmouse, struct elantech_device_info *info) { unsigned char param[3]; + unsigned char traces; memset(info, 0, sizeof(*info)); @@ -1755,6 +1669,90 @@ static int elantech_query_info(struct psmouse *psmouse, } } + /* query range information */ + switch (info->hw_version) { + case 1: + info->x_min = ETP_XMIN_V1; + info->y_min = ETP_YMIN_V1; + info->x_max = ETP_XMAX_V1; + info->y_max = ETP_YMAX_V1; + break; + + case 2: + if (info->fw_version == 0x020800 || + info->fw_version == 0x020b00 || + info->fw_version == 0x020030) { + info->x_min = ETP_XMIN_V2; + info->y_min = ETP_YMIN_V2; + info->x_max = ETP_XMAX_V2; + info->y_max = ETP_YMAX_V2; + } else { + int i; + int fixed_dpi; + + i = (info->fw_version > 0x020800 && + info->fw_version < 0x020900) ? 1 : 2; + + if (info->send_cmd(psmouse, ETP_FW_ID_QUERY, param)) + return -EINVAL; + + fixed_dpi = param[1] & 0x10; + + if (((info->fw_version >> 16) == 0x14) && fixed_dpi) { + if (info->send_cmd(psmouse, ETP_SAMPLE_QUERY, param)) + return -EINVAL; + + info->x_max = (info->capabilities[1] - i) * param[1] / 2; + info->y_max = (info->capabilities[2] - i) * param[2] / 2; + } else if (info->fw_version == 0x040216) { + info->x_max = 819; + info->y_max = 405; + } else if (info->fw_version == 0x040219 || info->fw_version == 0x040215) { + info->x_max = 900; + info->y_max = 500; + } else { + info->x_max = (info->capabilities[1] - i) * 64; + info->y_max = (info->capabilities[2] - i) * 64; + } + } + break; + + case 3: + if (info->send_cmd(psmouse, ETP_FW_ID_QUERY, param)) + return -EINVAL; + + info->x_max = (0x0f & param[0]) << 8 | param[1]; + info->y_max = (0xf0 & param[0]) << 4 | param[2]; + break; + + case 4: + if (info->send_cmd(psmouse, ETP_FW_ID_QUERY, param)) + return -EINVAL; + + info->x_max = (0x0f & param[0]) << 8 | param[1]; + info->y_max = (0xf0 & param[0]) << 4 | param[2]; + traces = info->capabilities[1]; + if ((traces < 2) || (traces > info->x_max)) + return -EINVAL; + + info->width = info->x_max / (traces - 1); + + /* column number of traces */ + info->x_traces = traces; + + /* row number of traces */ + traces = info->capabilities[2]; + if ((traces >= 2) && (traces <= info->y_max)) + info->y_traces = traces; + + break; + } + + /* check for the middle button: DMI matching or new v4 firmwares */ + info->has_middle_button = dmi_check_system(elantech_dmi_has_middle_button) || + (ETP_NEW_IC_SMBUS_HOST_NOTIFY(info->fw_version) && + !elantech_is_buttonpad(info)); + return 0; } @@ -1781,10 +1779,6 @@ static const char * const i2c_blacklist_pnp_ids[] = { * These are known to not be working properly as bits are missing * in elan_i2c. */ - "LEN2131", /* ThinkPad P52 w/ NFC */ - "LEN2132", /* ThinkPad P52 */ - "LEN2133", /* ThinkPad P72 w/ NFC */ - "LEN2134", /* ThinkPad P72 */ NULL }; @@ -1792,17 +1786,45 @@ static int elantech_create_smbus(struct psmouse *psmouse, struct elantech_device_info *info, bool leave_breadcrumbs) { - const struct property_entry i2c_properties[] = { - PROPERTY_ENTRY_BOOL("elan,trackpoint"), - { }, - }; + struct property_entry i2c_props[11] = {}; struct i2c_board_info smbus_board = { I2C_BOARD_INFO("elan_i2c", 0x15), .flags = I2C_CLIENT_HOST_NOTIFY, }; + unsigned int idx = 0; + + smbus_board.properties = i2c_props; + + i2c_props[idx++] = PROPERTY_ENTRY_U32("touchscreen-size-x", + info->x_max + 1); + i2c_props[idx++] = PROPERTY_ENTRY_U32("touchscreen-size-y", + info->y_max + 1); + i2c_props[idx++] = PROPERTY_ENTRY_U32("touchscreen-min-x", + info->x_min); + i2c_props[idx++] = PROPERTY_ENTRY_U32("touchscreen-min-y", + info->y_min); + if (info->x_res) + i2c_props[idx++] = PROPERTY_ENTRY_U32("touchscreen-x-mm", + (info->x_max + 1) / info->x_res); + if (info->y_res) + i2c_props[idx++] = PROPERTY_ENTRY_U32("touchscreen-y-mm", + (info->y_max + 1) / info->y_res); if (info->has_trackpoint) - smbus_board.properties = i2c_properties; + i2c_props[idx++] = PROPERTY_ENTRY_BOOL("elan,trackpoint"); + + if (info->has_middle_button) + i2c_props[idx++] = PROPERTY_ENTRY_BOOL("elan,middle-button"); + + if (info->x_traces) + i2c_props[idx++] = PROPERTY_ENTRY_U32("elan,x_traces", + info->x_traces); + if (info->y_traces) + i2c_props[idx++] = PROPERTY_ENTRY_U32("elan,y_traces", + info->y_traces); + + if (elantech_is_buttonpad(info)) + i2c_props[idx++] = PROPERTY_ENTRY_BOOL("elan,clickpad"); return psmouse_smbus_init(psmouse, &smbus_board, NULL, 0, false, leave_breadcrumbs); diff --git a/drivers/input/mouse/elantech.h b/drivers/input/mouse/elantech.h index 119727085a60..a7eaa62af6a0 100644 --- a/drivers/input/mouse/elantech.h +++ b/drivers/input/mouse/elantech.h @@ -144,8 +144,15 @@ struct elantech_device_info { unsigned char debug; unsigned char hw_version; unsigned int fw_version; + unsigned int x_min; + unsigned int y_min; + unsigned int x_max; + unsigned int y_max; unsigned int x_res; unsigned int y_res; + unsigned int x_traces; + unsigned int y_traces; + unsigned int width; unsigned int bus; bool paritycheck; bool jumpy_cursor; @@ -153,6 +160,7 @@ struct elantech_device_info { bool crc_enabled; bool set_hw_resolution; bool has_trackpoint; + bool has_middle_button; int (*send_cmd)(struct psmouse *psmouse, unsigned char c, unsigned char *param); }; diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mouse/psmouse-base.c index d3ff1fc09af7..94f7ca5ad077 100644 --- a/drivers/input/mouse/psmouse-base.c +++ b/drivers/input/mouse/psmouse-base.c @@ -373,6 +373,8 @@ static irqreturn_t psmouse_interrupt(struct serio *serio, if (ps2_handle_response(&psmouse->ps2dev, data)) goto out; + pm_wakeup_event(&serio->dev, 0); + if (psmouse->state <= PSMOUSE_RESYNCING) goto out; diff --git a/drivers/input/rmi4/rmi_f12.c b/drivers/input/rmi4/rmi_f12.c index 5c7f48915779..3b7d7b940cab 100644 --- a/drivers/input/rmi4/rmi_f12.c +++ b/drivers/input/rmi4/rmi_f12.c @@ -73,7 +73,6 @@ static int rmi_f12_read_sensor_tuning(struct f12_data *f12) int pitch_y = 0; int rx_receivers = 0; int tx_receivers = 0; - int sensor_flags = 0; item = rmi_get_register_desc_item(&f12->control_reg_desc, 8); if (!item) { @@ -129,10 +128,9 @@ static int rmi_f12_read_sensor_tuning(struct f12_data *f12) offset += 2; } - if (rmi_register_desc_has_subpacket(item, 4)) { - sensor_flags = buf[offset]; + /* Skip over sensor flags */ + if (rmi_register_desc_has_subpacket(item, 4)) offset += 1; - } sensor->x_mm = (pitch_x * rx_receivers) >> 12; sensor->y_mm = (pitch_y * tx_receivers) >> 12; diff --git a/drivers/input/rmi4/rmi_f54.c b/drivers/input/rmi4/rmi_f54.c index a6f515bcab22..516fea06ed59 100644 --- a/drivers/input/rmi4/rmi_f54.c +++ b/drivers/input/rmi4/rmi_f54.c @@ -456,25 +456,15 @@ static int rmi_f54_vidioc_fmt(struct file *file, void *priv, static int rmi_f54_vidioc_enum_fmt(struct file *file, void *priv, struct v4l2_fmtdesc *fmt) { + struct f54_data *f54 = video_drvdata(file); + if (fmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; - switch (fmt->index) { - case 0: - fmt->pixelformat = V4L2_TCH_FMT_DELTA_TD16; - break; - - case 1: - fmt->pixelformat = V4L2_TCH_FMT_DELTA_TD08; - break; - - case 2: - fmt->pixelformat = V4L2_TCH_FMT_TU16; - break; - - default: + if (fmt->index) return -EINVAL; - } + + fmt->pixelformat = f54->format.pixelformat; return 0; } @@ -692,6 +682,7 @@ static int rmi_f54_probe(struct rmi_function *fn) return -ENOMEM; rmi_f54_create_input_map(f54); + rmi_f54_set_input(f54, 0); /* register video device */ strlcpy(f54->v4l2.name, F54_NAME, sizeof(f54->v4l2.name)); diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig index c9c7224d5ae0..bfe436ccb046 100644 --- a/drivers/input/serio/Kconfig +++ b/drivers/input/serio/Kconfig @@ -254,6 +254,7 @@ config SERIO_APBPS2 config SERIO_OLPC_APSP tristate "OLPC AP-SP input support" + depends on ARCH_MMP || COMPILE_TEST help Say Y here if you want support for the keyboard and touchpad included in the OLPC XO-1.75 and XO-4 laptops. diff --git a/drivers/input/serio/hyperv-keyboard.c b/drivers/input/serio/hyperv-keyboard.c index a8b9be3e28db..7935e52b5435 100644 --- a/drivers/input/serio/hyperv-keyboard.c +++ b/drivers/input/serio/hyperv-keyboard.c @@ -440,5 +440,7 @@ static void __exit hv_kbd_exit(void) } MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Microsoft Hyper-V Synthetic Keyboard Driver"); + module_init(hv_kbd_init); module_exit(hv_kbd_exit); diff --git a/drivers/input/serio/i8042.c b/drivers/input/serio/i8042.c index 95a78ccbd847..6462f1798fbb 100644 --- a/drivers/input/serio/i8042.c +++ b/drivers/input/serio/i8042.c @@ -573,9 +573,6 @@ static irqreturn_t i8042_interrupt(int irq, void *dev_id) port = &i8042_ports[port_no]; serio = port->exists ? port->serio : NULL; - if (irq && serio) - pm_wakeup_event(&serio->dev, 0); - filter_dbg(port->driver_bound, data, "<- i8042 (interrupt, %d, %d%s%s)\n", port_no, irq, dfl & SERIO_PARITY ? ", bad parity" : "", diff --git a/drivers/input/serio/libps2.c b/drivers/input/serio/libps2.c index e6a07e68d1ff..22b8e05aa36c 100644 --- a/drivers/input/serio/libps2.c +++ b/drivers/input/serio/libps2.c @@ -409,6 +409,7 @@ bool ps2_handle_ack(struct ps2dev *ps2dev, u8 data) ps2dev->nak = PS2_RET_ERR; break; } + /* Fall through */ /* * Workaround for mice which don't ACK the Get ID command. diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 7a4884ad198b..a2029c3235af 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -1312,4 +1312,14 @@ config TOUCHSCREEN_ROHM_BU21023 To compile this driver as a module, choose M here: the module will be called bu21023_ts. +config TOUCHSCREEN_IQS5XX + tristate "Azoteq IQS550/572/525 trackpad/touchscreen controller" + depends on I2C + help + Say Y to enable support for the Azoteq IQS550/572/525 + family of trackpad/touchscreen controllers. + + To compile this driver as a module, choose M here: the + module will be called iqs5xx. + endif diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index fcc7605fba8d..084a596a0c8b 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -110,3 +110,4 @@ obj-$(CONFIG_TOUCHSCREEN_ZFORCE) += zforce_ts.o obj-$(CONFIG_TOUCHSCREEN_COLIBRI_VF50) += colibri-vf50-ts.o obj-$(CONFIG_TOUCHSCREEN_ROHM_BU21023) += rohm_bu21023.o obj-$(CONFIG_TOUCHSCREEN_RASPBERRYPI_FW) += raspberrypi-ts.o +obj-$(CONFIG_TOUCHSCREEN_IQS5XX) += iqs5xx.o diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c index 702bfda7ee77..c639ebce914c 100644 --- a/drivers/input/touchscreen/edt-ft5x06.c +++ b/drivers/input/touchscreen/edt-ft5x06.c @@ -1,20 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2012 Simon Budig, <simon.budig@kernelconcepts.de> * Daniel Wagener <daniel.wagener@kernelconcepts.de> (M09 firmware support) * Lothar Waßmann <LW@KARO-electronics.de> (DT support) - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * 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 library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* @@ -39,7 +27,6 @@ #include <linux/gpio/consumer.h> #include <linux/input/mt.h> #include <linux/input/touchscreen.h> -#include <linux/of_device.h> #define WORK_REGISTER_THRESHOLD 0x00 #define WORK_REGISTER_REPORT_RATE 0x08 @@ -1073,7 +1060,7 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client, return -ENOMEM; } - chip_data = of_device_get_match_data(&client->dev); + chip_data = device_get_match_data(&client->dev); if (!chip_data) chip_data = (const struct edt_i2c_chip_data *)id->driver_data; if (!chip_data || !chip_data->max_support_points) { @@ -1254,7 +1241,6 @@ static const struct i2c_device_id edt_ft5x06_ts_id[] = { }; MODULE_DEVICE_TABLE(i2c, edt_ft5x06_ts_id); -#ifdef CONFIG_OF static const struct of_device_id edt_ft5x06_of_match[] = { { .compatible = "edt,edt-ft5206", .data = &edt_ft5x06_data }, { .compatible = "edt,edt-ft5306", .data = &edt_ft5x06_data }, @@ -1266,12 +1252,11 @@ static const struct of_device_id edt_ft5x06_of_match[] = { { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, edt_ft5x06_of_match); -#endif static struct i2c_driver edt_ft5x06_ts_driver = { .driver = { .name = "edt_ft5x06", - .of_match_table = of_match_ptr(edt_ft5x06_of_match), + .of_match_table = edt_ft5x06_of_match, .pm = &edt_ft5x06_ts_pm_ops, }, .id_table = edt_ft5x06_ts_id, @@ -1283,4 +1268,4 @@ module_i2c_driver(edt_ft5x06_ts_driver); MODULE_AUTHOR("Simon Budig <simon.budig@kernelconcepts.de>"); MODULE_DESCRIPTION("EDT FT5x06 I2C Touchscreen Driver"); -MODULE_LICENSE("GPL"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/eeti_ts.c b/drivers/input/touchscreen/eeti_ts.c index 7fe41965c5d1..b508d2693c0e 100644 --- a/drivers/input/touchscreen/eeti_ts.c +++ b/drivers/input/touchscreen/eeti_ts.c @@ -41,6 +41,7 @@ struct eeti_ts { struct input_dev *input; struct gpio_desc *attn_gpio; struct touchscreen_properties props; + struct mutex mutex; bool running; }; @@ -75,42 +76,80 @@ static void eeti_ts_report_event(struct eeti_ts *eeti, u8 *buf) input_sync(eeti->input); } +static int eeti_ts_read(struct eeti_ts *eeti) +{ + int len, error; + char buf[6]; + + len = i2c_master_recv(eeti->client, buf, sizeof(buf)); + if (len != sizeof(buf)) { + error = len < 0 ? len : -EIO; + dev_err(&eeti->client->dev, + "failed to read touchscreen data: %d\n", + error); + return error; + } + + /* Motion packet */ + if (buf[0] & 0x80) + eeti_ts_report_event(eeti, buf); + + return 0; +} + static irqreturn_t eeti_ts_isr(int irq, void *dev_id) { struct eeti_ts *eeti = dev_id; - int len; int error; - char buf[6]; + + mutex_lock(&eeti->mutex); do { - len = i2c_master_recv(eeti->client, buf, sizeof(buf)); - if (len != sizeof(buf)) { - error = len < 0 ? len : -EIO; - dev_err(&eeti->client->dev, - "failed to read touchscreen data: %d\n", - error); + /* + * If we have attention GPIO, trust it. Otherwise we'll read + * once and exit. We assume that in this case we are using + * level triggered interrupt and it will get raised again + * if/when there is more data. + */ + if (eeti->attn_gpio && + !gpiod_get_value_cansleep(eeti->attn_gpio)) { break; } - if (buf[0] & 0x80) { - /* Motion packet */ - eeti_ts_report_event(eeti, buf); - } - } while (eeti->running && - eeti->attn_gpio && gpiod_get_value_cansleep(eeti->attn_gpio)); + error = eeti_ts_read(eeti); + if (error) + break; + + } while (eeti->running && eeti->attn_gpio); + mutex_unlock(&eeti->mutex); return IRQ_HANDLED; } static void eeti_ts_start(struct eeti_ts *eeti) { + mutex_lock(&eeti->mutex); + eeti->running = true; - wmb(); enable_irq(eeti->client->irq); + + /* + * Kick the controller in case we are using edge interrupt and + * we missed our edge while interrupt was disabled. We expect + * the attention GPIO to be wired in this case. + */ + if (eeti->attn_gpio && gpiod_get_value_cansleep(eeti->attn_gpio)) + eeti_ts_read(eeti); + + mutex_unlock(&eeti->mutex); } static void eeti_ts_stop(struct eeti_ts *eeti) { + /* + * Not locking here, just setting a flag and expect that the + * interrupt thread will notice the flag eventually. + */ eeti->running = false; wmb(); disable_irq(eeti->client->irq); @@ -153,6 +192,8 @@ static int eeti_ts_probe(struct i2c_client *client, return -ENOMEM; } + mutex_init(&eeti->mutex); + input = devm_input_allocate_device(dev); if (!input) { dev_err(dev, "Failed to allocate input device.\n"); diff --git a/drivers/input/touchscreen/goodix.c b/drivers/input/touchscreen/goodix.c index f57d82220a88..f7c1d168dd89 100644 --- a/drivers/input/touchscreen/goodix.c +++ b/drivers/input/touchscreen/goodix.c @@ -27,6 +27,7 @@ #include <linux/delay.h> #include <linux/irq.h> #include <linux/interrupt.h> +#include <linux/regulator/consumer.h> #include <linux/slab.h> #include <linux/acpi.h> #include <linux/of.h> @@ -47,6 +48,8 @@ struct goodix_ts_data { struct touchscreen_properties prop; unsigned int max_touch_num; unsigned int int_trigger_type; + struct regulator *avdd28; + struct regulator *vddio; struct gpio_desc *gpiod_int; struct gpio_desc *gpiod_rst; u16 id; @@ -216,6 +219,7 @@ static const struct goodix_chip_data *goodix_get_chip_data(u16 id) { switch (id) { case 1151: + case 5663: case 5688: return >1x_chip_data; @@ -532,6 +536,24 @@ static int goodix_get_gpio_config(struct goodix_ts_data *ts) return -EINVAL; dev = &ts->client->dev; + ts->avdd28 = devm_regulator_get(dev, "AVDD28"); + if (IS_ERR(ts->avdd28)) { + error = PTR_ERR(ts->avdd28); + if (error != -EPROBE_DEFER) + dev_err(dev, + "Failed to get AVDD28 regulator: %d\n", error); + return error; + } + + ts->vddio = devm_regulator_get(dev, "VDDIO"); + if (IS_ERR(ts->vddio)) { + error = PTR_ERR(ts->vddio); + if (error != -EPROBE_DEFER) + dev_err(dev, + "Failed to get VDDIO regulator: %d\n", error); + return error; + } + /* Get the interrupt GPIO pin number */ gpiod = devm_gpiod_get_optional(dev, GOODIX_GPIO_INT_NAME, GPIOD_IN); if (IS_ERR(gpiod)) { @@ -764,6 +786,14 @@ err_release_cfg: complete_all(&ts->firmware_loading_complete); } +static void goodix_disable_regulators(void *arg) +{ + struct goodix_ts_data *ts = arg; + + regulator_disable(ts->vddio); + regulator_disable(ts->avdd28); +} + static int goodix_ts_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -789,6 +819,29 @@ static int goodix_ts_probe(struct i2c_client *client, if (error) return error; + /* power up the controller */ + error = regulator_enable(ts->avdd28); + if (error) { + dev_err(&client->dev, + "Failed to enable AVDD28 regulator: %d\n", + error); + return error; + } + + error = regulator_enable(ts->vddio); + if (error) { + dev_err(&client->dev, + "Failed to enable VDDIO regulator: %d\n", + error); + regulator_disable(ts->avdd28); + return error; + } + + error = devm_add_action_or_reset(&client->dev, + goodix_disable_regulators, ts); + if (error) + return error; + if (ts->gpiod_int && ts->gpiod_rst) { /* reset the controller */ error = goodix_reset(ts); @@ -945,6 +998,7 @@ MODULE_DEVICE_TABLE(acpi, goodix_acpi_match); #ifdef CONFIG_OF static const struct of_device_id goodix_of_match[] = { { .compatible = "goodix,gt1151" }, + { .compatible = "goodix,gt5663" }, { .compatible = "goodix,gt5688" }, { .compatible = "goodix,gt911" }, { .compatible = "goodix,gt9110" }, diff --git a/drivers/input/touchscreen/imx6ul_tsc.c b/drivers/input/touchscreen/imx6ul_tsc.c index c10fc594f94d..e04eecd65bbb 100644 --- a/drivers/input/touchscreen/imx6ul_tsc.c +++ b/drivers/input/touchscreen/imx6ul_tsc.c @@ -364,8 +364,6 @@ static int imx6ul_tsc_probe(struct platform_device *pdev) struct device_node *np = pdev->dev.of_node; struct imx6ul_tsc *tsc; struct input_dev *input_dev; - struct resource *tsc_mem; - struct resource *adc_mem; int err; int tsc_irq; int adc_irq; @@ -403,16 +401,14 @@ static int imx6ul_tsc_probe(struct platform_device *pdev) return err; } - tsc_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - tsc->tsc_regs = devm_ioremap_resource(&pdev->dev, tsc_mem); + tsc->tsc_regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(tsc->tsc_regs)) { err = PTR_ERR(tsc->tsc_regs); dev_err(&pdev->dev, "failed to remap tsc memory: %d\n", err); return err; } - adc_mem = platform_get_resource(pdev, IORESOURCE_MEM, 1); - tsc->adc_regs = devm_ioremap_resource(&pdev->dev, adc_mem); + tsc->adc_regs = devm_platform_ioremap_resource(pdev, 1); if (IS_ERR(tsc->adc_regs)) { err = PTR_ERR(tsc->adc_regs); dev_err(&pdev->dev, "failed to remap adc memory: %d\n", err); diff --git a/drivers/input/touchscreen/iqs5xx.c b/drivers/input/touchscreen/iqs5xx.c new file mode 100644 index 000000000000..158707897c2d --- /dev/null +++ b/drivers/input/touchscreen/iqs5xx.c @@ -0,0 +1,1131 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Azoteq IQS550/572/525 Trackpad/Touchscreen Controller + * + * Copyright (C) 2018 + * Author: Jeff LaBundy <jeff@labundy.com> + * + * These devices require firmware exported from a PC-based configuration tool + * made available by the vendor. Firmware files may be pushed to the device's + * nonvolatile memory by writing the filename to the 'fw_file' sysfs control. + * + * Link to PC-based configuration tool and data sheet: http://www.azoteq.com/ + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/firmware.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/input/mt.h> +#include <linux/input/touchscreen.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/slab.h> +#include <asm/unaligned.h> + +#define IQS5XX_FW_FILE_LEN 64 +#define IQS5XX_NUM_RETRIES 10 +#define IQS5XX_NUM_POINTS 256 +#define IQS5XX_NUM_CONTACTS 5 +#define IQS5XX_WR_BYTES_MAX 2 + +#define IQS5XX_PROD_NUM_IQS550 40 +#define IQS5XX_PROD_NUM_IQS572 58 +#define IQS5XX_PROD_NUM_IQS525 52 +#define IQS5XX_PROJ_NUM_A000 0 +#define IQS5XX_PROJ_NUM_B000 15 +#define IQS5XX_MAJOR_VER_MIN 2 + +#define IQS5XX_RESUME 0x00 +#define IQS5XX_SUSPEND 0x01 + +#define IQS5XX_SW_INPUT_EVENT 0x10 +#define IQS5XX_SETUP_COMPLETE 0x40 +#define IQS5XX_EVENT_MODE 0x01 +#define IQS5XX_TP_EVENT 0x04 + +#define IQS5XX_FLIP_X 0x01 +#define IQS5XX_FLIP_Y 0x02 +#define IQS5XX_SWITCH_XY_AXIS 0x04 + +#define IQS5XX_PROD_NUM 0x0000 +#define IQS5XX_ABS_X 0x0016 +#define IQS5XX_ABS_Y 0x0018 +#define IQS5XX_SYS_CTRL0 0x0431 +#define IQS5XX_SYS_CTRL1 0x0432 +#define IQS5XX_SYS_CFG0 0x058E +#define IQS5XX_SYS_CFG1 0x058F +#define IQS5XX_TOTAL_RX 0x063D +#define IQS5XX_TOTAL_TX 0x063E +#define IQS5XX_XY_CFG0 0x0669 +#define IQS5XX_X_RES 0x066E +#define IQS5XX_Y_RES 0x0670 +#define IQS5XX_CHKSM 0x83C0 +#define IQS5XX_APP 0x8400 +#define IQS5XX_CSTM 0xBE00 +#define IQS5XX_PMAP_END 0xBFFF +#define IQS5XX_END_COMM 0xEEEE + +#define IQS5XX_CHKSM_LEN (IQS5XX_APP - IQS5XX_CHKSM) +#define IQS5XX_APP_LEN (IQS5XX_CSTM - IQS5XX_APP) +#define IQS5XX_CSTM_LEN (IQS5XX_PMAP_END + 1 - IQS5XX_CSTM) +#define IQS5XX_PMAP_LEN (IQS5XX_PMAP_END + 1 - IQS5XX_CHKSM) + +#define IQS5XX_REC_HDR_LEN 4 +#define IQS5XX_REC_LEN_MAX 255 +#define IQS5XX_REC_TYPE_DATA 0x00 +#define IQS5XX_REC_TYPE_EOF 0x01 + +#define IQS5XX_BL_ADDR_MASK 0x40 +#define IQS5XX_BL_CMD_VER 0x00 +#define IQS5XX_BL_CMD_READ 0x01 +#define IQS5XX_BL_CMD_EXEC 0x02 +#define IQS5XX_BL_CMD_CRC 0x03 +#define IQS5XX_BL_BLK_LEN_MAX 64 +#define IQS5XX_BL_ID 0x0200 +#define IQS5XX_BL_STATUS_RESET 0x00 +#define IQS5XX_BL_STATUS_AVAIL 0xA5 +#define IQS5XX_BL_STATUS_NONE 0xEE +#define IQS5XX_BL_CRC_PASS 0x00 +#define IQS5XX_BL_CRC_FAIL 0x01 +#define IQS5XX_BL_ATTEMPTS 3 + +struct iqs5xx_private { + struct i2c_client *client; + struct input_dev *input; + struct gpio_desc *reset_gpio; + struct mutex lock; + u8 bl_status; +}; + +struct iqs5xx_dev_id_info { + __be16 prod_num; + __be16 proj_num; + u8 major_ver; + u8 minor_ver; + u8 bl_status; +} __packed; + +struct iqs5xx_ihex_rec { + char start; + char len[2]; + char addr[4]; + char type[2]; + char data[2]; +} __packed; + +struct iqs5xx_touch_data { + __be16 abs_x; + __be16 abs_y; + __be16 strength; + u8 area; +} __packed; + +static int iqs5xx_read_burst(struct i2c_client *client, + u16 reg, void *val, u16 len) +{ + __be16 reg_buf = cpu_to_be16(reg); + int ret, i; + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = 0, + .len = sizeof(reg_buf), + .buf = (u8 *)®_buf, + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = len, + .buf = (u8 *)val, + }, + }; + + /* + * The first addressing attempt outside of a communication window fails + * and must be retried, after which the device clock stretches until it + * is available. + */ + for (i = 0; i < IQS5XX_NUM_RETRIES; i++) { + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + if (ret == ARRAY_SIZE(msg)) + return 0; + + usleep_range(200, 300); + } + + if (ret >= 0) + ret = -EIO; + + dev_err(&client->dev, "Failed to read from address 0x%04X: %d\n", + reg, ret); + + return ret; +} + +static int iqs5xx_read_word(struct i2c_client *client, u16 reg, u16 *val) +{ + __be16 val_buf; + int error; + + error = iqs5xx_read_burst(client, reg, &val_buf, sizeof(val_buf)); + if (error) + return error; + + *val = be16_to_cpu(val_buf); + + return 0; +} + +static int iqs5xx_read_byte(struct i2c_client *client, u16 reg, u8 *val) +{ + return iqs5xx_read_burst(client, reg, val, sizeof(*val)); +} + +static int iqs5xx_write_burst(struct i2c_client *client, + u16 reg, const void *val, u16 len) +{ + int ret, i; + u16 mlen = sizeof(reg) + len; + u8 mbuf[sizeof(reg) + IQS5XX_WR_BYTES_MAX]; + + if (len > IQS5XX_WR_BYTES_MAX) + return -EINVAL; + + put_unaligned_be16(reg, mbuf); + memcpy(mbuf + sizeof(reg), val, len); + + /* + * The first addressing attempt outside of a communication window fails + * and must be retried, after which the device clock stretches until it + * is available. + */ + for (i = 0; i < IQS5XX_NUM_RETRIES; i++) { + ret = i2c_master_send(client, mbuf, mlen); + if (ret == mlen) + return 0; + + usleep_range(200, 300); + } + + if (ret >= 0) + ret = -EIO; + + dev_err(&client->dev, "Failed to write to address 0x%04X: %d\n", + reg, ret); + + return ret; +} + +static int iqs5xx_write_word(struct i2c_client *client, u16 reg, u16 val) +{ + __be16 val_buf = cpu_to_be16(val); + + return iqs5xx_write_burst(client, reg, &val_buf, sizeof(val_buf)); +} + +static int iqs5xx_write_byte(struct i2c_client *client, u16 reg, u8 val) +{ + return iqs5xx_write_burst(client, reg, &val, sizeof(val)); +} + +static void iqs5xx_reset(struct i2c_client *client) +{ + struct iqs5xx_private *iqs5xx = i2c_get_clientdata(client); + + gpiod_set_value_cansleep(iqs5xx->reset_gpio, 1); + usleep_range(200, 300); + + gpiod_set_value_cansleep(iqs5xx->reset_gpio, 0); +} + +static int iqs5xx_bl_cmd(struct i2c_client *client, u8 bl_cmd, u16 bl_addr) +{ + struct i2c_msg msg; + int ret; + u8 mbuf[sizeof(bl_cmd) + sizeof(bl_addr)]; + + msg.addr = client->addr ^ IQS5XX_BL_ADDR_MASK; + msg.flags = 0; + msg.len = sizeof(bl_cmd); + msg.buf = mbuf; + + *mbuf = bl_cmd; + + switch (bl_cmd) { + case IQS5XX_BL_CMD_VER: + case IQS5XX_BL_CMD_CRC: + case IQS5XX_BL_CMD_EXEC: + break; + case IQS5XX_BL_CMD_READ: + msg.len += sizeof(bl_addr); + put_unaligned_be16(bl_addr, mbuf + sizeof(bl_cmd)); + break; + default: + return -EINVAL; + } + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret != 1) + goto msg_fail; + + switch (bl_cmd) { + case IQS5XX_BL_CMD_VER: + msg.len = sizeof(u16); + break; + case IQS5XX_BL_CMD_CRC: + msg.len = sizeof(u8); + /* + * This delay saves the bus controller the trouble of having to + * tolerate a relatively long clock-stretching period while the + * CRC is calculated. + */ + msleep(50); + break; + case IQS5XX_BL_CMD_EXEC: + usleep_range(10000, 10100); + /* fall through */ + default: + return 0; + } + + msg.flags = I2C_M_RD; + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret != 1) + goto msg_fail; + + if (bl_cmd == IQS5XX_BL_CMD_VER && + get_unaligned_be16(mbuf) != IQS5XX_BL_ID) { + dev_err(&client->dev, "Unrecognized bootloader ID: 0x%04X\n", + get_unaligned_be16(mbuf)); + return -EINVAL; + } + + if (bl_cmd == IQS5XX_BL_CMD_CRC && *mbuf != IQS5XX_BL_CRC_PASS) { + dev_err(&client->dev, "Bootloader CRC failed\n"); + return -EIO; + } + + return 0; + +msg_fail: + if (ret >= 0) + ret = -EIO; + + if (bl_cmd != IQS5XX_BL_CMD_VER) + dev_err(&client->dev, + "Unsuccessful bootloader command 0x%02X: %d\n", + bl_cmd, ret); + + return ret; +} + +static int iqs5xx_bl_open(struct i2c_client *client) +{ + int error, i, j; + + /* + * The device opens a bootloader polling window for 2 ms following the + * release of reset. If the host cannot establish communication during + * this time frame, it must cycle reset again. + */ + for (i = 0; i < IQS5XX_BL_ATTEMPTS; i++) { + iqs5xx_reset(client); + + for (j = 0; j < IQS5XX_NUM_RETRIES; j++) { + error = iqs5xx_bl_cmd(client, IQS5XX_BL_CMD_VER, 0); + if (!error || error == -EINVAL) + return error; + } + } + + dev_err(&client->dev, "Failed to open bootloader: %d\n", error); + + return error; +} + +static int iqs5xx_bl_write(struct i2c_client *client, + u16 bl_addr, u8 *pmap_data, u16 pmap_len) +{ + struct i2c_msg msg; + int ret, i; + u8 mbuf[sizeof(bl_addr) + IQS5XX_BL_BLK_LEN_MAX]; + + if (pmap_len % IQS5XX_BL_BLK_LEN_MAX) + return -EINVAL; + + msg.addr = client->addr ^ IQS5XX_BL_ADDR_MASK; + msg.flags = 0; + msg.len = sizeof(mbuf); + msg.buf = mbuf; + + for (i = 0; i < pmap_len; i += IQS5XX_BL_BLK_LEN_MAX) { + put_unaligned_be16(bl_addr + i, mbuf); + memcpy(mbuf + sizeof(bl_addr), pmap_data + i, + sizeof(mbuf) - sizeof(bl_addr)); + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret != 1) + goto msg_fail; + + usleep_range(10000, 10100); + } + + return 0; + +msg_fail: + if (ret >= 0) + ret = -EIO; + + dev_err(&client->dev, "Failed to write block at address 0x%04X: %d\n", + bl_addr + i, ret); + + return ret; +} + +static int iqs5xx_bl_verify(struct i2c_client *client, + u16 bl_addr, u8 *pmap_data, u16 pmap_len) +{ + struct i2c_msg msg; + int ret, i; + u8 bl_data[IQS5XX_BL_BLK_LEN_MAX]; + + if (pmap_len % IQS5XX_BL_BLK_LEN_MAX) + return -EINVAL; + + msg.addr = client->addr ^ IQS5XX_BL_ADDR_MASK; + msg.flags = I2C_M_RD; + msg.len = sizeof(bl_data); + msg.buf = bl_data; + + for (i = 0; i < pmap_len; i += IQS5XX_BL_BLK_LEN_MAX) { + ret = iqs5xx_bl_cmd(client, IQS5XX_BL_CMD_READ, bl_addr + i); + if (ret) + return ret; + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret != 1) + goto msg_fail; + + if (memcmp(bl_data, pmap_data + i, sizeof(bl_data))) { + dev_err(&client->dev, + "Failed to verify block at address 0x%04X\n", + bl_addr + i); + return -EIO; + } + } + + return 0; + +msg_fail: + if (ret >= 0) + ret = -EIO; + + dev_err(&client->dev, "Failed to read block at address 0x%04X: %d\n", + bl_addr + i, ret); + + return ret; +} + +static int iqs5xx_set_state(struct i2c_client *client, u8 state) +{ + struct iqs5xx_private *iqs5xx = i2c_get_clientdata(client); + int error1, error2; + + if (iqs5xx->bl_status == IQS5XX_BL_STATUS_RESET) + return 0; + + mutex_lock(&iqs5xx->lock); + + /* + * Addressing the device outside of a communication window prompts it + * to assert the RDY output, so disable the interrupt line to prevent + * the handler from servicing a false interrupt. + */ + disable_irq(client->irq); + + error1 = iqs5xx_write_byte(client, IQS5XX_SYS_CTRL1, state); + error2 = iqs5xx_write_byte(client, IQS5XX_END_COMM, 0); + + usleep_range(50, 100); + enable_irq(client->irq); + + mutex_unlock(&iqs5xx->lock); + + if (error1) + return error1; + + return error2; +} + +static int iqs5xx_open(struct input_dev *input) +{ + struct iqs5xx_private *iqs5xx = input_get_drvdata(input); + + return iqs5xx_set_state(iqs5xx->client, IQS5XX_RESUME); +} + +static void iqs5xx_close(struct input_dev *input) +{ + struct iqs5xx_private *iqs5xx = input_get_drvdata(input); + + iqs5xx_set_state(iqs5xx->client, IQS5XX_SUSPEND); +} + +static int iqs5xx_axis_init(struct i2c_client *client) +{ + struct iqs5xx_private *iqs5xx = i2c_get_clientdata(client); + struct touchscreen_properties prop; + struct input_dev *input; + int error; + u16 max_x, max_x_hw; + u16 max_y, max_y_hw; + u8 val; + + if (!iqs5xx->input) { + input = devm_input_allocate_device(&client->dev); + if (!input) + return -ENOMEM; + + input->name = client->name; + input->id.bustype = BUS_I2C; + input->open = iqs5xx_open; + input->close = iqs5xx_close; + + input_set_capability(input, EV_ABS, ABS_MT_POSITION_X); + input_set_capability(input, EV_ABS, ABS_MT_POSITION_Y); + input_set_capability(input, EV_ABS, ABS_MT_PRESSURE); + + error = input_mt_init_slots(input, + IQS5XX_NUM_CONTACTS, INPUT_MT_DIRECT); + if (error) { + dev_err(&client->dev, + "Failed to initialize slots: %d\n", error); + return error; + } + + input_set_drvdata(input, iqs5xx); + iqs5xx->input = input; + } + + touchscreen_parse_properties(iqs5xx->input, true, &prop); + + error = iqs5xx_read_byte(client, IQS5XX_TOTAL_RX, &val); + if (error) + return error; + max_x_hw = (val - 1) * IQS5XX_NUM_POINTS; + + error = iqs5xx_read_byte(client, IQS5XX_TOTAL_TX, &val); + if (error) + return error; + max_y_hw = (val - 1) * IQS5XX_NUM_POINTS; + + error = iqs5xx_read_byte(client, IQS5XX_XY_CFG0, &val); + if (error) + return error; + + if (val & IQS5XX_SWITCH_XY_AXIS) + swap(max_x_hw, max_y_hw); + + if (prop.swap_x_y) + val ^= IQS5XX_SWITCH_XY_AXIS; + + if (prop.invert_x) + val ^= prop.swap_x_y ? IQS5XX_FLIP_Y : IQS5XX_FLIP_X; + + if (prop.invert_y) + val ^= prop.swap_x_y ? IQS5XX_FLIP_X : IQS5XX_FLIP_Y; + + error = iqs5xx_write_byte(client, IQS5XX_XY_CFG0, val); + if (error) + return error; + + if (prop.max_x > max_x_hw) { + dev_err(&client->dev, "Invalid maximum x-coordinate: %u > %u\n", + prop.max_x, max_x_hw); + return -EINVAL; + } else if (prop.max_x == 0) { + error = iqs5xx_read_word(client, IQS5XX_X_RES, &max_x); + if (error) + return error; + + input_abs_set_max(iqs5xx->input, + prop.swap_x_y ? ABS_MT_POSITION_Y : + ABS_MT_POSITION_X, + max_x); + } else { + max_x = (u16)prop.max_x; + } + + if (prop.max_y > max_y_hw) { + dev_err(&client->dev, "Invalid maximum y-coordinate: %u > %u\n", + prop.max_y, max_y_hw); + return -EINVAL; + } else if (prop.max_y == 0) { + error = iqs5xx_read_word(client, IQS5XX_Y_RES, &max_y); + if (error) + return error; + + input_abs_set_max(iqs5xx->input, + prop.swap_x_y ? ABS_MT_POSITION_X : + ABS_MT_POSITION_Y, + max_y); + } else { + max_y = (u16)prop.max_y; + } + + /* + * Write horizontal and vertical resolution to the device in case its + * original defaults were overridden or swapped as per the properties + * specified in the device tree. + */ + error = iqs5xx_write_word(client, + prop.swap_x_y ? IQS5XX_Y_RES : IQS5XX_X_RES, + max_x); + if (error) + return error; + + return iqs5xx_write_word(client, + prop.swap_x_y ? IQS5XX_X_RES : IQS5XX_Y_RES, + max_y); +} + +static int iqs5xx_dev_init(struct i2c_client *client) +{ + struct iqs5xx_private *iqs5xx = i2c_get_clientdata(client); + struct iqs5xx_dev_id_info *dev_id_info; + int error; + u8 val; + u8 buf[sizeof(*dev_id_info) + 1]; + + error = iqs5xx_read_burst(client, IQS5XX_PROD_NUM, + &buf[1], sizeof(*dev_id_info)); + if (error) + return iqs5xx_bl_open(client); + + /* + * A000 and B000 devices use 8-bit and 16-bit addressing, respectively. + * Querying an A000 device's version information with 16-bit addressing + * gives the appearance that the data is shifted by one byte; a nonzero + * leading array element suggests this could be the case (in which case + * the missing zero is prepended). + */ + buf[0] = 0; + dev_id_info = (struct iqs5xx_dev_id_info *)&buf[(buf[1] > 0) ? 0 : 1]; + + switch (be16_to_cpu(dev_id_info->prod_num)) { + case IQS5XX_PROD_NUM_IQS550: + case IQS5XX_PROD_NUM_IQS572: + case IQS5XX_PROD_NUM_IQS525: + break; + default: + dev_err(&client->dev, "Unrecognized product number: %u\n", + be16_to_cpu(dev_id_info->prod_num)); + return -EINVAL; + } + + switch (be16_to_cpu(dev_id_info->proj_num)) { + case IQS5XX_PROJ_NUM_A000: + dev_err(&client->dev, "Unsupported project number: %u\n", + be16_to_cpu(dev_id_info->proj_num)); + return iqs5xx_bl_open(client); + case IQS5XX_PROJ_NUM_B000: + break; + default: + dev_err(&client->dev, "Unrecognized project number: %u\n", + be16_to_cpu(dev_id_info->proj_num)); + return -EINVAL; + } + + if (dev_id_info->major_ver < IQS5XX_MAJOR_VER_MIN) { + dev_err(&client->dev, "Unsupported major version: %u\n", + dev_id_info->major_ver); + return iqs5xx_bl_open(client); + } + + switch (dev_id_info->bl_status) { + case IQS5XX_BL_STATUS_AVAIL: + case IQS5XX_BL_STATUS_NONE: + break; + default: + dev_err(&client->dev, + "Unrecognized bootloader status: 0x%02X\n", + dev_id_info->bl_status); + return -EINVAL; + } + + error = iqs5xx_axis_init(client); + if (error) + return error; + + error = iqs5xx_read_byte(client, IQS5XX_SYS_CFG0, &val); + if (error) + return error; + + val |= IQS5XX_SETUP_COMPLETE; + val &= ~IQS5XX_SW_INPUT_EVENT; + error = iqs5xx_write_byte(client, IQS5XX_SYS_CFG0, val); + if (error) + return error; + + val = IQS5XX_TP_EVENT | IQS5XX_EVENT_MODE; + error = iqs5xx_write_byte(client, IQS5XX_SYS_CFG1, val); + if (error) + return error; + + error = iqs5xx_write_byte(client, IQS5XX_END_COMM, 0); + if (error) + return error; + + iqs5xx->bl_status = dev_id_info->bl_status; + + /* + * Closure of the first communication window that appears following the + * release of reset appears to kick off an initialization period during + * which further communication is met with clock stretching. The return + * from this function is delayed so that further communication attempts + * avoid this period. + */ + msleep(100); + + return 0; +} + +static irqreturn_t iqs5xx_irq(int irq, void *data) +{ + struct iqs5xx_private *iqs5xx = data; + struct iqs5xx_touch_data touch_data[IQS5XX_NUM_CONTACTS]; + struct i2c_client *client = iqs5xx->client; + struct input_dev *input = iqs5xx->input; + int error, i; + + /* + * This check is purely a precaution, as the device does not assert the + * RDY output during bootloader mode. If the device operates outside of + * bootloader mode, the input device is guaranteed to be allocated. + */ + if (iqs5xx->bl_status == IQS5XX_BL_STATUS_RESET) + return IRQ_NONE; + + error = iqs5xx_read_burst(client, IQS5XX_ABS_X, + touch_data, sizeof(touch_data)); + if (error) + return IRQ_NONE; + + for (i = 0; i < ARRAY_SIZE(touch_data); i++) { + u16 pressure = be16_to_cpu(touch_data[i].strength); + + input_mt_slot(input, i); + if (input_mt_report_slot_state(input, MT_TOOL_FINGER, + pressure != 0)) { + input_report_abs(input, ABS_MT_POSITION_X, + be16_to_cpu(touch_data[i].abs_x)); + input_report_abs(input, ABS_MT_POSITION_Y, + be16_to_cpu(touch_data[i].abs_y)); + input_report_abs(input, ABS_MT_PRESSURE, pressure); + } + } + + input_mt_sync_frame(input); + input_sync(input); + + error = iqs5xx_write_byte(client, IQS5XX_END_COMM, 0); + if (error) + return IRQ_NONE; + + /* + * Once the communication window is closed, a small delay is added to + * ensure the device's RDY output has been deasserted by the time the + * interrupt handler returns. + */ + usleep_range(50, 100); + + return IRQ_HANDLED; +} + +static int iqs5xx_fw_file_parse(struct i2c_client *client, + const char *fw_file, u8 *pmap) +{ + const struct firmware *fw; + struct iqs5xx_ihex_rec *rec; + size_t pos = 0; + int error, i; + u16 rec_num = 1; + u16 rec_addr; + u8 rec_len, rec_type, rec_chksm, chksm; + u8 rec_hdr[IQS5XX_REC_HDR_LEN]; + u8 rec_data[IQS5XX_REC_LEN_MAX]; + + /* + * Firmware exported from the vendor's configuration tool deviates from + * standard ihex as follows: (1) the checksum for records corresponding + * to user-exported settings is not recalculated, and (2) an address of + * 0xFFFF is used for the EOF record. + * + * Because the ihex2fw tool tolerates neither (1) nor (2), the slightly + * nonstandard ihex firmware is parsed directly by the driver. + */ + error = request_firmware(&fw, fw_file, &client->dev); + if (error) { + dev_err(&client->dev, "Failed to request firmware %s: %d\n", + fw_file, error); + return error; + } + + do { + if (pos + sizeof(*rec) > fw->size) { + dev_err(&client->dev, "Insufficient firmware size\n"); + error = -EINVAL; + break; + } + rec = (struct iqs5xx_ihex_rec *)(fw->data + pos); + pos += sizeof(*rec); + + if (rec->start != ':') { + dev_err(&client->dev, "Invalid start at record %u\n", + rec_num); + error = -EINVAL; + break; + } + + error = hex2bin(rec_hdr, rec->len, sizeof(rec_hdr)); + if (error) { + dev_err(&client->dev, "Invalid header at record %u\n", + rec_num); + break; + } + + rec_len = *rec_hdr; + rec_addr = get_unaligned_be16(rec_hdr + sizeof(rec_len)); + rec_type = *(rec_hdr + sizeof(rec_len) + sizeof(rec_addr)); + + if (pos + rec_len * 2 > fw->size) { + dev_err(&client->dev, "Insufficient firmware size\n"); + error = -EINVAL; + break; + } + pos += (rec_len * 2); + + error = hex2bin(rec_data, rec->data, rec_len); + if (error) { + dev_err(&client->dev, "Invalid data at record %u\n", + rec_num); + break; + } + + error = hex2bin(&rec_chksm, + rec->data + rec_len * 2, sizeof(rec_chksm)); + if (error) { + dev_err(&client->dev, "Invalid checksum at record %u\n", + rec_num); + break; + } + + chksm = 0; + for (i = 0; i < sizeof(rec_hdr); i++) + chksm += rec_hdr[i]; + for (i = 0; i < rec_len; i++) + chksm += rec_data[i]; + chksm = ~chksm + 1; + + if (chksm != rec_chksm && rec_addr < IQS5XX_CSTM) { + dev_err(&client->dev, + "Incorrect checksum at record %u\n", + rec_num); + error = -EINVAL; + break; + } + + switch (rec_type) { + case IQS5XX_REC_TYPE_DATA: + if (rec_addr < IQS5XX_CHKSM || + rec_addr > IQS5XX_PMAP_END) { + dev_err(&client->dev, + "Invalid address at record %u\n", + rec_num); + error = -EINVAL; + } else { + memcpy(pmap + rec_addr - IQS5XX_CHKSM, + rec_data, rec_len); + } + break; + case IQS5XX_REC_TYPE_EOF: + break; + default: + dev_err(&client->dev, "Invalid type at record %u\n", + rec_num); + error = -EINVAL; + } + + if (error) + break; + + rec_num++; + while (pos < fw->size) { + if (*(fw->data + pos) == ':') + break; + pos++; + } + } while (rec_type != IQS5XX_REC_TYPE_EOF); + + release_firmware(fw); + + return error; +} + +static int iqs5xx_fw_file_write(struct i2c_client *client, const char *fw_file) +{ + struct iqs5xx_private *iqs5xx = i2c_get_clientdata(client); + int error; + u8 *pmap; + + if (iqs5xx->bl_status == IQS5XX_BL_STATUS_NONE) + return -EPERM; + + pmap = kzalloc(IQS5XX_PMAP_LEN, GFP_KERNEL); + if (!pmap) + return -ENOMEM; + + error = iqs5xx_fw_file_parse(client, fw_file, pmap); + if (error) + goto err_kfree; + + mutex_lock(&iqs5xx->lock); + + /* + * Disable the interrupt line in case the first attempt(s) to enter the + * bootloader don't happen quickly enough, in which case the device may + * assert the RDY output until the next attempt. + */ + disable_irq(client->irq); + + iqs5xx->bl_status = IQS5XX_BL_STATUS_RESET; + + error = iqs5xx_bl_cmd(client, IQS5XX_BL_CMD_VER, 0); + if (error) { + error = iqs5xx_bl_open(client); + if (error) + goto err_reset; + } + + error = iqs5xx_bl_write(client, IQS5XX_CHKSM, pmap, IQS5XX_PMAP_LEN); + if (error) + goto err_reset; + + error = iqs5xx_bl_cmd(client, IQS5XX_BL_CMD_CRC, 0); + if (error) + goto err_reset; + + error = iqs5xx_bl_verify(client, IQS5XX_CSTM, + pmap + IQS5XX_CHKSM_LEN + IQS5XX_APP_LEN, + IQS5XX_CSTM_LEN); + if (error) + goto err_reset; + + error = iqs5xx_bl_cmd(client, IQS5XX_BL_CMD_EXEC, 0); + +err_reset: + if (error) { + iqs5xx_reset(client); + usleep_range(10000, 10100); + } + + error = iqs5xx_dev_init(client); + if (!error && iqs5xx->bl_status == IQS5XX_BL_STATUS_RESET) + error = -EINVAL; + + enable_irq(client->irq); + + mutex_unlock(&iqs5xx->lock); + +err_kfree: + kfree(pmap); + + return error; +} + +static ssize_t fw_file_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct iqs5xx_private *iqs5xx = dev_get_drvdata(dev); + struct i2c_client *client = iqs5xx->client; + size_t len = count; + bool input_reg = !iqs5xx->input; + char fw_file[IQS5XX_FW_FILE_LEN + 1]; + int error; + + if (!len) + return -EINVAL; + + if (buf[len - 1] == '\n') + len--; + + if (len > IQS5XX_FW_FILE_LEN) + return -ENAMETOOLONG; + + memcpy(fw_file, buf, len); + fw_file[len] = '\0'; + + error = iqs5xx_fw_file_write(client, fw_file); + if (error) + return error; + + /* + * If the input device was not allocated already, it is guaranteed to + * be allocated by this point and can finally be registered. + */ + if (input_reg) { + error = input_register_device(iqs5xx->input); + if (error) { + dev_err(&client->dev, + "Failed to register device: %d\n", + error); + return error; + } + } + + return count; +} + +static DEVICE_ATTR_WO(fw_file); + +static struct attribute *iqs5xx_attrs[] = { + &dev_attr_fw_file.attr, + NULL, +}; + +static const struct attribute_group iqs5xx_attr_group = { + .attrs = iqs5xx_attrs, +}; + +static int __maybe_unused iqs5xx_suspend(struct device *dev) +{ + struct iqs5xx_private *iqs5xx = dev_get_drvdata(dev); + struct input_dev *input = iqs5xx->input; + int error = 0; + + if (!input) + return error; + + mutex_lock(&input->mutex); + + if (input->users) + error = iqs5xx_set_state(iqs5xx->client, IQS5XX_SUSPEND); + + mutex_unlock(&input->mutex); + + return error; +} + +static int __maybe_unused iqs5xx_resume(struct device *dev) +{ + struct iqs5xx_private *iqs5xx = dev_get_drvdata(dev); + struct input_dev *input = iqs5xx->input; + int error = 0; + + if (!input) + return error; + + mutex_lock(&input->mutex); + + if (input->users) + error = iqs5xx_set_state(iqs5xx->client, IQS5XX_RESUME); + + mutex_unlock(&input->mutex); + + return error; +} + +static SIMPLE_DEV_PM_OPS(iqs5xx_pm, iqs5xx_suspend, iqs5xx_resume); + +static int iqs5xx_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct iqs5xx_private *iqs5xx; + int error; + + iqs5xx = devm_kzalloc(&client->dev, sizeof(*iqs5xx), GFP_KERNEL); + if (!iqs5xx) + return -ENOMEM; + + i2c_set_clientdata(client, iqs5xx); + iqs5xx->client = client; + + iqs5xx->reset_gpio = devm_gpiod_get(&client->dev, + "reset", GPIOD_OUT_LOW); + if (IS_ERR(iqs5xx->reset_gpio)) { + error = PTR_ERR(iqs5xx->reset_gpio); + dev_err(&client->dev, "Failed to request GPIO: %d\n", error); + return error; + } + + mutex_init(&iqs5xx->lock); + + iqs5xx_reset(client); + usleep_range(10000, 10100); + + error = iqs5xx_dev_init(client); + if (error) + return error; + + error = devm_request_threaded_irq(&client->dev, client->irq, + NULL, iqs5xx_irq, IRQF_ONESHOT, + client->name, iqs5xx); + if (error) { + dev_err(&client->dev, "Failed to request IRQ: %d\n", error); + return error; + } + + error = devm_device_add_group(&client->dev, &iqs5xx_attr_group); + if (error) { + dev_err(&client->dev, "Failed to add attributes: %d\n", error); + return error; + } + + if (iqs5xx->input) { + error = input_register_device(iqs5xx->input); + if (error) + dev_err(&client->dev, + "Failed to register device: %d\n", + error); + } + + return error; +} + +static const struct i2c_device_id iqs5xx_id[] = { + { "iqs550", 0 }, + { "iqs572", 1 }, + { "iqs525", 2 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, iqs5xx_id); + +static const struct of_device_id iqs5xx_of_match[] = { + { .compatible = "azoteq,iqs550" }, + { .compatible = "azoteq,iqs572" }, + { .compatible = "azoteq,iqs525" }, + { } +}; +MODULE_DEVICE_TABLE(of, iqs5xx_of_match); + +static struct i2c_driver iqs5xx_i2c_driver = { + .driver = { + .name = "iqs5xx", + .of_match_table = iqs5xx_of_match, + .pm = &iqs5xx_pm, + }, + .id_table = iqs5xx_id, + .probe = iqs5xx_probe, +}; +module_i2c_driver(iqs5xx_i2c_driver); + +MODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>"); +MODULE_DESCRIPTION("Azoteq IQS550/572/525 Trackpad/Touchscreen Controller"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/silead.c b/drivers/input/touchscreen/silead.c index 09241d4cdebc..06f0eb04a8fd 100644 --- a/drivers/input/touchscreen/silead.c +++ b/drivers/input/touchscreen/silead.c @@ -617,6 +617,7 @@ static const struct acpi_device_id silead_ts_acpi_match[] = { { "MSSL1680", 0 }, { "MSSL0001", 0 }, { "MSSL0002", 0 }, + { "MSSL0017", 0 }, { } }; MODULE_DEVICE_TABLE(acpi, silead_ts_acpi_match); diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index a72f97fca57b..d8c70cc6a714 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -608,6 +608,12 @@ config LEDS_TLC591XX This option enables support for Texas Instruments TLC59108 and TLC59116 LED controllers. +config LEDS_MAX77650 + tristate "LED support for Maxim MAX77650 PMIC" + depends on LEDS_CLASS && MFD_MAX77650 + help + LEDs driver for MAX77650 family of PMICs from Maxim Integrated. + config LEDS_MAX77693 tristate "LED support for MAX77693 Flash" depends on LEDS_CLASS_FLASH diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 4c1b0054f379..f48b2404dbb7 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -61,6 +61,7 @@ obj-$(CONFIG_LEDS_MC13783) += leds-mc13783.o obj-$(CONFIG_LEDS_NS2) += leds-ns2.o obj-$(CONFIG_LEDS_NETXBIG) += leds-netxbig.o obj-$(CONFIG_LEDS_ASIC3) += leds-asic3.o +obj-$(CONFIG_LEDS_MAX77650) += leds-max77650.o obj-$(CONFIG_LEDS_MAX77693) += leds-max77693.o obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o obj-$(CONFIG_LEDS_LM355x) += leds-lm355x.o diff --git a/drivers/leds/leds-max77650.c b/drivers/leds/leds-max77650.c new file mode 100644 index 000000000000..6b74ce9cac12 --- /dev/null +++ b/drivers/leds/leds-max77650.c @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (C) 2018 BayLibre SAS +// Author: Bartosz Golaszewski <bgolaszewski@baylibre.com> +// +// LED driver for MAXIM 77650/77651 charger/power-supply. + +#include <linux/i2c.h> +#include <linux/leds.h> +#include <linux/mfd/max77650.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#define MAX77650_LED_NUM_LEDS 3 + +#define MAX77650_LED_A_BASE 0x40 +#define MAX77650_LED_B_BASE 0x43 + +#define MAX77650_LED_BR_MASK GENMASK(4, 0) +#define MAX77650_LED_EN_MASK GENMASK(7, 6) + +#define MAX77650_LED_MAX_BRIGHTNESS MAX77650_LED_BR_MASK + +/* Enable EN_LED_MSTR. */ +#define MAX77650_LED_TOP_DEFAULT BIT(0) + +#define MAX77650_LED_ENABLE GENMASK(7, 6) +#define MAX77650_LED_DISABLE 0x00 + +#define MAX77650_LED_A_DEFAULT MAX77650_LED_DISABLE +/* 100% on duty */ +#define MAX77650_LED_B_DEFAULT GENMASK(3, 0) + +struct max77650_led { + struct led_classdev cdev; + struct regmap *map; + unsigned int regA; + unsigned int regB; +}; + +static struct max77650_led *max77650_to_led(struct led_classdev *cdev) +{ + return container_of(cdev, struct max77650_led, cdev); +} + +static int max77650_led_brightness_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct max77650_led *led = max77650_to_led(cdev); + int val, mask; + + mask = MAX77650_LED_BR_MASK | MAX77650_LED_EN_MASK; + + if (brightness == LED_OFF) + val = MAX77650_LED_DISABLE; + else + val = MAX77650_LED_ENABLE | brightness; + + return regmap_update_bits(led->map, led->regA, mask, val); +} + +static int max77650_led_probe(struct platform_device *pdev) +{ + struct device_node *of_node, *child; + struct max77650_led *leds, *led; + struct device *parent; + struct device *dev; + struct regmap *map; + const char *label; + int rv, num_leds; + u32 reg; + + dev = &pdev->dev; + parent = dev->parent; + of_node = dev->of_node; + + if (!of_node) + return -ENODEV; + + leds = devm_kcalloc(dev, sizeof(*leds), + MAX77650_LED_NUM_LEDS, GFP_KERNEL); + if (!leds) + return -ENOMEM; + + map = dev_get_regmap(dev->parent, NULL); + if (!map) + return -ENODEV; + + num_leds = of_get_child_count(of_node); + if (!num_leds || num_leds > MAX77650_LED_NUM_LEDS) + return -ENODEV; + + for_each_child_of_node(of_node, child) { + rv = of_property_read_u32(child, "reg", ®); + if (rv || reg >= MAX77650_LED_NUM_LEDS) + return -EINVAL; + + led = &leds[reg]; + led->map = map; + led->regA = MAX77650_LED_A_BASE + reg; + led->regB = MAX77650_LED_B_BASE + reg; + led->cdev.brightness_set_blocking = max77650_led_brightness_set; + led->cdev.max_brightness = MAX77650_LED_MAX_BRIGHTNESS; + + label = of_get_property(child, "label", NULL); + if (!label) { + led->cdev.name = "max77650::"; + } else { + led->cdev.name = devm_kasprintf(dev, GFP_KERNEL, + "max77650:%s", label); + if (!led->cdev.name) + return -ENOMEM; + } + + of_property_read_string(child, "linux,default-trigger", + &led->cdev.default_trigger); + + rv = devm_of_led_classdev_register(dev, child, &led->cdev); + if (rv) + return rv; + + rv = regmap_write(map, led->regA, MAX77650_LED_A_DEFAULT); + if (rv) + return rv; + + rv = regmap_write(map, led->regB, MAX77650_LED_B_DEFAULT); + if (rv) + return rv; + } + + return regmap_write(map, + MAX77650_REG_CNFG_LED_TOP, + MAX77650_LED_TOP_DEFAULT); +} + +static struct platform_driver max77650_led_driver = { + .driver = { + .name = "max77650-led", + }, + .probe = max77650_led_probe, +}; +module_platform_driver(max77650_led_driver); + +MODULE_DESCRIPTION("MAXIM 77650/77651 LED driver"); +MODULE_AUTHOR("Bartosz Golaszewski <bgolaszewski@baylibre.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 26ad6468d13a..0f394f08db6f 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -733,6 +733,20 @@ config MFD_MAX77620 provides common support for accessing the device; additional drivers must be enabled in order to use the functionality of the device. +config MFD_MAX77650 + tristate "Maxim MAX77650/77651 PMIC Support" + depends on I2C + depends on OF || COMPILE_TEST + select MFD_CORE + select REGMAP_I2C + help + Say Y here to add support for Maxim Semiconductor MAX77650 and + MAX77651 Power Management ICs. This is the core multifunction + driver for interacting with the device. The module name is + 'max77650'. Additional drivers can be enabled in order to use + the following functionalities of the device: GPIO, regulator, + charger, LED, onkey. + config MFD_MAX77686 tristate "Maxim Semiconductor MAX77686/802 PMIC Support" depends on I2C diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index b4569ed7f3f3..5727d099c16f 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -155,6 +155,7 @@ obj-$(CONFIG_MFD_DA9150) += da9150-core.o obj-$(CONFIG_MFD_MAX14577) += max14577.o obj-$(CONFIG_MFD_MAX77620) += max77620.o +obj-$(CONFIG_MFD_MAX77650) += max77650.o obj-$(CONFIG_MFD_MAX77686) += max77686.o obj-$(CONFIG_MFD_MAX77693) += max77693.o obj-$(CONFIG_MFD_MAX77843) += max77843.o diff --git a/drivers/mfd/max77650.c b/drivers/mfd/max77650.c new file mode 100644 index 000000000000..60e07aca6ae5 --- /dev/null +++ b/drivers/mfd/max77650.c @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (C) 2018 BayLibre SAS +// Author: Bartosz Golaszewski <bgolaszewski@baylibre.com> +// +// Core MFD driver for MAXIM 77650/77651 charger/power-supply. +// Programming manual: https://pdfserv.maximintegrated.com/en/an/AN6428.pdf + +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/mfd/core.h> +#include <linux/mfd/max77650.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regmap.h> + +#define MAX77650_INT_GPI_F_MSK BIT(0) +#define MAX77650_INT_GPI_R_MSK BIT(1) +#define MAX77650_INT_GPI_MSK \ + (MAX77650_INT_GPI_F_MSK | MAX77650_INT_GPI_R_MSK) +#define MAX77650_INT_nEN_F_MSK BIT(2) +#define MAX77650_INT_nEN_R_MSK BIT(3) +#define MAX77650_INT_TJAL1_R_MSK BIT(4) +#define MAX77650_INT_TJAL2_R_MSK BIT(5) +#define MAX77650_INT_DOD_R_MSK BIT(6) + +#define MAX77650_INT_THM_MSK BIT(0) +#define MAX77650_INT_CHG_MSK BIT(1) +#define MAX77650_INT_CHGIN_MSK BIT(2) +#define MAX77650_INT_TJ_REG_MSK BIT(3) +#define MAX77650_INT_CHGIN_CTRL_MSK BIT(4) +#define MAX77650_INT_SYS_CTRL_MSK BIT(5) +#define MAX77650_INT_SYS_CNFG_MSK BIT(6) + +#define MAX77650_INT_GLBL_OFFSET 0 +#define MAX77650_INT_CHG_OFFSET 1 + +#define MAX77650_SBIA_LPM_MASK BIT(5) +#define MAX77650_SBIA_LPM_DISABLED 0x00 + +enum { + MAX77650_INT_GPI, + MAX77650_INT_nEN_F, + MAX77650_INT_nEN_R, + MAX77650_INT_TJAL1_R, + MAX77650_INT_TJAL2_R, + MAX77650_INT_DOD_R, + MAX77650_INT_THM, + MAX77650_INT_CHG, + MAX77650_INT_CHGIN, + MAX77650_INT_TJ_REG, + MAX77650_INT_CHGIN_CTRL, + MAX77650_INT_SYS_CTRL, + MAX77650_INT_SYS_CNFG, +}; + +static const struct resource max77650_charger_resources[] = { + DEFINE_RES_IRQ_NAMED(MAX77650_INT_CHG, "CHG"), + DEFINE_RES_IRQ_NAMED(MAX77650_INT_CHGIN, "CHGIN"), +}; + +static const struct resource max77650_gpio_resources[] = { + DEFINE_RES_IRQ_NAMED(MAX77650_INT_GPI, "GPI"), +}; + +static const struct resource max77650_onkey_resources[] = { + DEFINE_RES_IRQ_NAMED(MAX77650_INT_nEN_F, "nEN_F"), + DEFINE_RES_IRQ_NAMED(MAX77650_INT_nEN_R, "nEN_R"), +}; + +static const struct mfd_cell max77650_cells[] = { + { + .name = "max77650-regulator", + .of_compatible = "maxim,max77650-regulator", + }, { + .name = "max77650-charger", + .of_compatible = "maxim,max77650-charger", + .resources = max77650_charger_resources, + .num_resources = ARRAY_SIZE(max77650_charger_resources), + }, { + .name = "max77650-gpio", + .of_compatible = "maxim,max77650-gpio", + .resources = max77650_gpio_resources, + .num_resources = ARRAY_SIZE(max77650_gpio_resources), + }, { + .name = "max77650-led", + .of_compatible = "maxim,max77650-led", + }, { + .name = "max77650-onkey", + .of_compatible = "maxim,max77650-onkey", + .resources = max77650_onkey_resources, + .num_resources = ARRAY_SIZE(max77650_onkey_resources), + }, +}; + +static const struct regmap_irq max77650_irqs[] = { + [MAX77650_INT_GPI] = { + .reg_offset = MAX77650_INT_GLBL_OFFSET, + .mask = MAX77650_INT_GPI_MSK, + .type = { + .type_falling_val = MAX77650_INT_GPI_F_MSK, + .type_rising_val = MAX77650_INT_GPI_R_MSK, + .types_supported = IRQ_TYPE_EDGE_BOTH, + }, + }, + REGMAP_IRQ_REG(MAX77650_INT_nEN_F, + MAX77650_INT_GLBL_OFFSET, MAX77650_INT_nEN_F_MSK), + REGMAP_IRQ_REG(MAX77650_INT_nEN_R, + MAX77650_INT_GLBL_OFFSET, MAX77650_INT_nEN_R_MSK), + REGMAP_IRQ_REG(MAX77650_INT_TJAL1_R, + MAX77650_INT_GLBL_OFFSET, MAX77650_INT_TJAL1_R_MSK), + REGMAP_IRQ_REG(MAX77650_INT_TJAL2_R, + MAX77650_INT_GLBL_OFFSET, MAX77650_INT_TJAL2_R_MSK), + REGMAP_IRQ_REG(MAX77650_INT_DOD_R, + MAX77650_INT_GLBL_OFFSET, MAX77650_INT_DOD_R_MSK), + REGMAP_IRQ_REG(MAX77650_INT_THM, + MAX77650_INT_CHG_OFFSET, MAX77650_INT_THM_MSK), + REGMAP_IRQ_REG(MAX77650_INT_CHG, + MAX77650_INT_CHG_OFFSET, MAX77650_INT_CHG_MSK), + REGMAP_IRQ_REG(MAX77650_INT_CHGIN, + MAX77650_INT_CHG_OFFSET, MAX77650_INT_CHGIN_MSK), + REGMAP_IRQ_REG(MAX77650_INT_TJ_REG, + MAX77650_INT_CHG_OFFSET, MAX77650_INT_TJ_REG_MSK), + REGMAP_IRQ_REG(MAX77650_INT_CHGIN_CTRL, + MAX77650_INT_CHG_OFFSET, MAX77650_INT_CHGIN_CTRL_MSK), + REGMAP_IRQ_REG(MAX77650_INT_SYS_CTRL, + MAX77650_INT_CHG_OFFSET, MAX77650_INT_SYS_CTRL_MSK), + REGMAP_IRQ_REG(MAX77650_INT_SYS_CNFG, + MAX77650_INT_CHG_OFFSET, MAX77650_INT_SYS_CNFG_MSK), +}; + +static const struct regmap_irq_chip max77650_irq_chip = { + .name = "max77650-irq", + .irqs = max77650_irqs, + .num_irqs = ARRAY_SIZE(max77650_irqs), + .num_regs = 2, + .status_base = MAX77650_REG_INT_GLBL, + .mask_base = MAX77650_REG_INTM_GLBL, + .type_in_mask = true, + .type_invert = true, + .init_ack_masked = true, + .clear_on_unmask = true, +}; + +static const struct regmap_config max77650_regmap_config = { + .name = "max77650", + .reg_bits = 8, + .val_bits = 8, +}; + +static int max77650_i2c_probe(struct i2c_client *i2c) +{ + struct regmap_irq_chip_data *irq_data; + struct device *dev = &i2c->dev; + struct irq_domain *domain; + struct regmap *map; + unsigned int val; + int rv, id; + + map = devm_regmap_init_i2c(i2c, &max77650_regmap_config); + if (IS_ERR(map)) { + dev_err(dev, "Unable to initialise I2C Regmap\n"); + return PTR_ERR(map); + } + + rv = regmap_read(map, MAX77650_REG_CID, &val); + if (rv) { + dev_err(dev, "Unable to read Chip ID\n"); + return rv; + } + + id = MAX77650_CID_BITS(val); + switch (id) { + case MAX77650_CID_77650A: + case MAX77650_CID_77650C: + case MAX77650_CID_77651A: + case MAX77650_CID_77651B: + break; + default: + dev_err(dev, "Chip not supported - ID: 0x%02x\n", id); + return -ENODEV; + } + + /* + * This IC has a low-power mode which reduces the quiescent current + * consumption to ~5.6uA but is only suitable for systems consuming + * less than ~2mA. Since this is not likely the case even on + * linux-based wearables - keep the chip in normal power mode. + */ + rv = regmap_update_bits(map, + MAX77650_REG_CNFG_GLBL, + MAX77650_SBIA_LPM_MASK, + MAX77650_SBIA_LPM_DISABLED); + if (rv) { + dev_err(dev, "Unable to change the power mode\n"); + return rv; + } + + rv = devm_regmap_add_irq_chip(dev, map, i2c->irq, + IRQF_ONESHOT | IRQF_SHARED, 0, + &max77650_irq_chip, &irq_data); + if (rv) { + dev_err(dev, "Unable to add Regmap IRQ chip\n"); + return rv; + } + + domain = regmap_irq_get_domain(irq_data); + + return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, + max77650_cells, ARRAY_SIZE(max77650_cells), + NULL, 0, domain); +} + +static const struct of_device_id max77650_of_match[] = { + { .compatible = "maxim,max77650" }, + { } +}; +MODULE_DEVICE_TABLE(of, max77650_of_match); + +static struct i2c_driver max77650_i2c_driver = { + .driver = { + .name = "max77650", + .of_match_table = of_match_ptr(max77650_of_match), + }, + .probe_new = max77650_i2c_probe, +}; +module_i2c_driver(max77650_i2c_driver); + +MODULE_DESCRIPTION("MAXIM 77650/77651 multi-function core driver"); +MODULE_AUTHOR("Bartosz Golaszewski <bgolaszewski@baylibre.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mfd/mfd-core.c b/drivers/mfd/mfd-core.c index 94e3f32ce935..1ade4c8cc91f 100644 --- a/drivers/mfd/mfd-core.c +++ b/drivers/mfd/mfd-core.c @@ -269,6 +269,19 @@ fail_alloc: return ret; } +/** + * mfd_add_devices - register child devices + * + * @parent: Pointer to parent device. + * @id: Can be PLATFORM_DEVID_AUTO to let the Platform API take care + * of device numbering, or will be added to a device's cell_id. + * @cells: Array of (struct mfd_cell)s describing child devices. + * @n_devs: Number of child devices to register. + * @mem_base: Parent register range resource for child devices. + * @irq_base: Base of the range of virtual interrupt numbers allocated for + * this MFD device. Unused if @domain is specified. + * @domain: Interrupt domain to create mappings for hardware interrupts. + */ int mfd_add_devices(struct device *parent, int id, const struct mfd_cell *cells, int n_devs, struct resource *mem_base, diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index e901b9879e7e..0230c96fa94d 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -499,6 +499,13 @@ config CHARGER_DETECTOR_MAX14656 Revision 1.2 and can be found e.g. in Kindle 4/5th generation readers and certain LG devices. +config CHARGER_MAX77650 + tristate "Maxim MAX77650 battery charger driver" + depends on MFD_MAX77650 + help + Say Y to enable support for the battery charger control of MAX77650 + PMICs. + config CHARGER_MAX77693 tristate "Maxim MAX77693 battery charger driver" depends on MFD_MAX77693 diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index b731c2a9b695..b73eb8c5c1a9 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -70,6 +70,7 @@ obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o obj-$(CONFIG_CHARGER_LTC3651) += ltc3651-charger.o obj-$(CONFIG_CHARGER_MAX14577) += max14577_charger.o obj-$(CONFIG_CHARGER_DETECTOR_MAX14656) += max14656_charger_detector.o +obj-$(CONFIG_CHARGER_MAX77650) += max77650-charger.o obj-$(CONFIG_CHARGER_MAX77693) += max77693_charger.o obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o diff --git a/drivers/power/supply/max77650-charger.c b/drivers/power/supply/max77650-charger.c new file mode 100644 index 000000000000..e34714cb05ec --- /dev/null +++ b/drivers/power/supply/max77650-charger.c @@ -0,0 +1,368 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (C) 2018 BayLibre SAS +// Author: Bartosz Golaszewski <bgolaszewski@baylibre.com> +// +// Battery charger driver for MAXIM 77650/77651 charger/power-supply. + +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/mfd/max77650.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/regmap.h> + +#define MAX77650_CHARGER_ENABLED BIT(0) +#define MAX77650_CHARGER_DISABLED 0x00 +#define MAX77650_CHARGER_CHG_EN_MASK BIT(0) + +#define MAX77650_CHG_DETAILS_MASK GENMASK(7, 4) +#define MAX77650_CHG_DETAILS_BITS(_reg) \ + (((_reg) & MAX77650_CHG_DETAILS_MASK) >> 4) + +/* Charger is OFF. */ +#define MAX77650_CHG_OFF 0x00 +/* Charger is in prequalification mode. */ +#define MAX77650_CHG_PREQ 0x01 +/* Charger is in fast-charge constant current mode. */ +#define MAX77650_CHG_ON_CURR 0x02 +/* Charger is in JEITA modified fast-charge constant-current mode. */ +#define MAX77650_CHG_ON_CURR_JEITA 0x03 +/* Charger is in fast-charge constant-voltage mode. */ +#define MAX77650_CHG_ON_VOLT 0x04 +/* Charger is in JEITA modified fast-charge constant-voltage mode. */ +#define MAX77650_CHG_ON_VOLT_JEITA 0x05 +/* Charger is in top-off mode. */ +#define MAX77650_CHG_ON_TOPOFF 0x06 +/* Charger is in JEITA modified top-off mode. */ +#define MAX77650_CHG_ON_TOPOFF_JEITA 0x07 +/* Charger is done. */ +#define MAX77650_CHG_DONE 0x08 +/* Charger is JEITA modified done. */ +#define MAX77650_CHG_DONE_JEITA 0x09 +/* Charger is suspended due to a prequalification timer fault. */ +#define MAX77650_CHG_SUSP_PREQ_TIM_FAULT 0x0a +/* Charger is suspended due to a fast-charge timer fault. */ +#define MAX77650_CHG_SUSP_FAST_CHG_TIM_FAULT 0x0b +/* Charger is suspended due to a battery temperature fault. */ +#define MAX77650_CHG_SUSP_BATT_TEMP_FAULT 0x0c + +#define MAX77650_CHGIN_DETAILS_MASK GENMASK(3, 2) +#define MAX77650_CHGIN_DETAILS_BITS(_reg) \ + (((_reg) & MAX77650_CHGIN_DETAILS_MASK) >> 2) + +#define MAX77650_CHGIN_UNDERVOLTAGE_LOCKOUT 0x00 +#define MAX77650_CHGIN_OVERVOLTAGE_LOCKOUT 0x01 +#define MAX77650_CHGIN_OKAY 0x11 + +#define MAX77650_CHARGER_CHG_MASK BIT(1) +#define MAX77650_CHARGER_CHG_CHARGING(_reg) \ + (((_reg) & MAX77650_CHARGER_CHG_MASK) > 1) + +#define MAX77650_CHARGER_VCHGIN_MIN_MASK 0xc0 +#define MAX77650_CHARGER_VCHGIN_MIN_SHIFT(_val) ((_val) << 5) + +#define MAX77650_CHARGER_ICHGIN_LIM_MASK 0x1c +#define MAX77650_CHARGER_ICHGIN_LIM_SHIFT(_val) ((_val) << 2) + +struct max77650_charger_data { + struct regmap *map; + struct device *dev; +}; + +static enum power_supply_property max77650_charger_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CHARGE_TYPE +}; + +static const unsigned int max77650_charger_vchgin_min_table[] = { + 4000000, 4100000, 4200000, 4300000, 4400000, 4500000, 4600000, 4700000 +}; + +static const unsigned int max77650_charger_ichgin_lim_table[] = { + 95000, 190000, 285000, 380000, 475000 +}; + +static int max77650_charger_set_vchgin_min(struct max77650_charger_data *chg, + unsigned int val) +{ + int i, rv; + + for (i = 0; i < ARRAY_SIZE(max77650_charger_vchgin_min_table); i++) { + if (val == max77650_charger_vchgin_min_table[i]) { + rv = regmap_update_bits(chg->map, + MAX77650_REG_CNFG_CHG_B, + MAX77650_CHARGER_VCHGIN_MIN_MASK, + MAX77650_CHARGER_VCHGIN_MIN_SHIFT(i)); + if (rv) + return rv; + + return 0; + } + } + + return -EINVAL; +} + +static int max77650_charger_set_ichgin_lim(struct max77650_charger_data *chg, + unsigned int val) +{ + int i, rv; + + for (i = 0; i < ARRAY_SIZE(max77650_charger_ichgin_lim_table); i++) { + if (val == max77650_charger_ichgin_lim_table[i]) { + rv = regmap_update_bits(chg->map, + MAX77650_REG_CNFG_CHG_B, + MAX77650_CHARGER_ICHGIN_LIM_MASK, + MAX77650_CHARGER_ICHGIN_LIM_SHIFT(i)); + if (rv) + return rv; + + return 0; + } + } + + return -EINVAL; +} + +static int max77650_charger_enable(struct max77650_charger_data *chg) +{ + int rv; + + rv = regmap_update_bits(chg->map, + MAX77650_REG_CNFG_CHG_B, + MAX77650_CHARGER_CHG_EN_MASK, + MAX77650_CHARGER_ENABLED); + if (rv) + dev_err(chg->dev, "unable to enable the charger: %d\n", rv); + + return rv; +} + +static int max77650_charger_disable(struct max77650_charger_data *chg) +{ + int rv; + + rv = regmap_update_bits(chg->map, + MAX77650_REG_CNFG_CHG_B, + MAX77650_CHARGER_CHG_EN_MASK, + MAX77650_CHARGER_DISABLED); + if (rv) + dev_err(chg->dev, "unable to disable the charger: %d\n", rv); + + return rv; +} + +static irqreturn_t max77650_charger_check_status(int irq, void *data) +{ + struct max77650_charger_data *chg = data; + int rv, reg; + + rv = regmap_read(chg->map, MAX77650_REG_STAT_CHG_B, ®); + if (rv) { + dev_err(chg->dev, + "unable to read the charger status: %d\n", rv); + return IRQ_HANDLED; + } + + switch (MAX77650_CHGIN_DETAILS_BITS(reg)) { + case MAX77650_CHGIN_UNDERVOLTAGE_LOCKOUT: + dev_err(chg->dev, "undervoltage lockout detected, disabling charger\n"); + max77650_charger_disable(chg); + break; + case MAX77650_CHGIN_OVERVOLTAGE_LOCKOUT: + dev_err(chg->dev, "overvoltage lockout detected, disabling charger\n"); + max77650_charger_disable(chg); + break; + case MAX77650_CHGIN_OKAY: + max77650_charger_enable(chg); + break; + default: + /* May be 0x10 - debouncing */ + break; + } + + return IRQ_HANDLED; +} + +static int max77650_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max77650_charger_data *chg = power_supply_get_drvdata(psy); + int rv, reg; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + rv = regmap_read(chg->map, MAX77650_REG_STAT_CHG_B, ®); + if (rv) + return rv; + + if (MAX77650_CHARGER_CHG_CHARGING(reg)) { + val->intval = POWER_SUPPLY_STATUS_CHARGING; + break; + } + + switch (MAX77650_CHG_DETAILS_BITS(reg)) { + case MAX77650_CHG_OFF: + case MAX77650_CHG_SUSP_PREQ_TIM_FAULT: + case MAX77650_CHG_SUSP_FAST_CHG_TIM_FAULT: + case MAX77650_CHG_SUSP_BATT_TEMP_FAULT: + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case MAX77650_CHG_PREQ: + case MAX77650_CHG_ON_CURR: + case MAX77650_CHG_ON_CURR_JEITA: + case MAX77650_CHG_ON_VOLT: + case MAX77650_CHG_ON_VOLT_JEITA: + case MAX77650_CHG_ON_TOPOFF: + case MAX77650_CHG_ON_TOPOFF_JEITA: + val->intval = POWER_SUPPLY_STATUS_CHARGING; + break; + case MAX77650_CHG_DONE: + val->intval = POWER_SUPPLY_STATUS_FULL; + break; + default: + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + } + break; + case POWER_SUPPLY_PROP_ONLINE: + rv = regmap_read(chg->map, MAX77650_REG_STAT_CHG_B, ®); + if (rv) + return rv; + + val->intval = MAX77650_CHARGER_CHG_CHARGING(reg); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + rv = regmap_read(chg->map, MAX77650_REG_STAT_CHG_B, ®); + if (rv) + return rv; + + if (!MAX77650_CHARGER_CHG_CHARGING(reg)) { + val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; + break; + } + + switch (MAX77650_CHG_DETAILS_BITS(reg)) { + case MAX77650_CHG_PREQ: + case MAX77650_CHG_ON_CURR: + case MAX77650_CHG_ON_CURR_JEITA: + case MAX77650_CHG_ON_VOLT: + case MAX77650_CHG_ON_VOLT_JEITA: + val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + break; + case MAX77650_CHG_ON_TOPOFF: + case MAX77650_CHG_ON_TOPOFF_JEITA: + val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + default: + val->intval = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct power_supply_desc max77650_battery_desc = { + .name = "max77650", + .type = POWER_SUPPLY_TYPE_USB, + .get_property = max77650_charger_get_property, + .properties = max77650_charger_properties, + .num_properties = ARRAY_SIZE(max77650_charger_properties), +}; + +static int max77650_charger_probe(struct platform_device *pdev) +{ + struct power_supply_config pscfg = {}; + struct max77650_charger_data *chg; + struct power_supply *battery; + struct device *dev, *parent; + int rv, chg_irq, chgin_irq; + unsigned int prop; + + dev = &pdev->dev; + parent = dev->parent; + + chg = devm_kzalloc(dev, sizeof(*chg), GFP_KERNEL); + if (!chg) + return -ENOMEM; + + platform_set_drvdata(pdev, chg); + + chg->map = dev_get_regmap(parent, NULL); + if (!chg->map) + return -ENODEV; + + chg->dev = dev; + + pscfg.of_node = dev->of_node; + pscfg.drv_data = chg; + + chg_irq = platform_get_irq_byname(pdev, "CHG"); + if (chg_irq < 0) + return chg_irq; + + chgin_irq = platform_get_irq_byname(pdev, "CHGIN"); + if (chgin_irq < 0) + return chgin_irq; + + rv = devm_request_any_context_irq(dev, chg_irq, + max77650_charger_check_status, + IRQF_ONESHOT, "chg", chg); + if (rv < 0) + return rv; + + rv = devm_request_any_context_irq(dev, chgin_irq, + max77650_charger_check_status, + IRQF_ONESHOT, "chgin", chg); + if (rv < 0) + return rv; + + battery = devm_power_supply_register(dev, + &max77650_battery_desc, &pscfg); + if (IS_ERR(battery)) + return PTR_ERR(battery); + + rv = of_property_read_u32(dev->of_node, + "input-voltage-min-microvolt", &prop); + if (rv == 0) { + rv = max77650_charger_set_vchgin_min(chg, prop); + if (rv) + return rv; + } + + rv = of_property_read_u32(dev->of_node, + "input-current-limit-microamp", &prop); + if (rv == 0) { + rv = max77650_charger_set_ichgin_lim(chg, prop); + if (rv) + return rv; + } + + return max77650_charger_enable(chg); +} + +static int max77650_charger_remove(struct platform_device *pdev) +{ + struct max77650_charger_data *chg = platform_get_drvdata(pdev); + + return max77650_charger_disable(chg); +} + +static struct platform_driver max77650_charger_driver = { + .driver = { + .name = "max77650-charger", + }, + .probe = max77650_charger_probe, + .remove = max77650_charger_remove, +}; +module_platform_driver(max77650_charger_driver); + +MODULE_DESCRIPTION("MAXIM 77650/77651 charger driver"); +MODULE_AUTHOR("Bartosz Golaszewski <bgolaszewski@baylibre.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/mfd/max77650.h b/include/linux/mfd/max77650.h new file mode 100644 index 000000000000..c809e211a8cd --- /dev/null +++ b/include/linux/mfd/max77650.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2018 BayLibre SAS + * Author: Bartosz Golaszewski <bgolaszewski@baylibre.com> + * + * Common definitions for MAXIM 77650/77651 charger/power-supply. + */ + +#ifndef MAX77650_H +#define MAX77650_H + +#include <linux/bits.h> + +#define MAX77650_REG_INT_GLBL 0x00 +#define MAX77650_REG_INT_CHG 0x01 +#define MAX77650_REG_STAT_CHG_A 0x02 +#define MAX77650_REG_STAT_CHG_B 0x03 +#define MAX77650_REG_ERCFLAG 0x04 +#define MAX77650_REG_STAT_GLBL 0x05 +#define MAX77650_REG_INTM_GLBL 0x06 +#define MAX77650_REG_INTM_CHG 0x07 +#define MAX77650_REG_CNFG_GLBL 0x10 +#define MAX77650_REG_CID 0x11 +#define MAX77650_REG_CNFG_GPIO 0x12 +#define MAX77650_REG_CNFG_CHG_A 0x18 +#define MAX77650_REG_CNFG_CHG_B 0x19 +#define MAX77650_REG_CNFG_CHG_C 0x1a +#define MAX77650_REG_CNFG_CHG_D 0x1b +#define MAX77650_REG_CNFG_CHG_E 0x1c +#define MAX77650_REG_CNFG_CHG_F 0x1d +#define MAX77650_REG_CNFG_CHG_G 0x1e +#define MAX77650_REG_CNFG_CHG_H 0x1f +#define MAX77650_REG_CNFG_CHG_I 0x20 +#define MAX77650_REG_CNFG_SBB_TOP 0x28 +#define MAX77650_REG_CNFG_SBB0_A 0x29 +#define MAX77650_REG_CNFG_SBB0_B 0x2a +#define MAX77650_REG_CNFG_SBB1_A 0x2b +#define MAX77650_REG_CNFG_SBB1_B 0x2c +#define MAX77650_REG_CNFG_SBB2_A 0x2d +#define MAX77650_REG_CNFG_SBB2_B 0x2e +#define MAX77650_REG_CNFG_LDO_A 0x38 +#define MAX77650_REG_CNFG_LDO_B 0x39 +#define MAX77650_REG_CNFG_LED0_A 0x40 +#define MAX77650_REG_CNFG_LED1_A 0x41 +#define MAX77650_REG_CNFG_LED2_A 0x42 +#define MAX77650_REG_CNFG_LED0_B 0x43 +#define MAX77650_REG_CNFG_LED1_B 0x44 +#define MAX77650_REG_CNFG_LED2_B 0x45 +#define MAX77650_REG_CNFG_LED_TOP 0x46 + +#define MAX77650_CID_MASK GENMASK(3, 0) +#define MAX77650_CID_BITS(_reg) (_reg & MAX77650_CID_MASK) + +#define MAX77650_CID_77650A 0x03 +#define MAX77650_CID_77650C 0x0a +#define MAX77650_CID_77651A 0x06 +#define MAX77650_CID_77651B 0x08 + +#endif /* MAX77650_H */ diff --git a/include/uapi/linux/input-event-codes.h b/include/uapi/linux/input-event-codes.h index 64cee116928e..85387c76c24f 100644 --- a/include/uapi/linux/input-event-codes.h +++ b/include/uapi/linux/input-event-codes.h @@ -606,6 +606,7 @@ #define KEY_SCREENSAVER 0x245 /* AL Screen Saver */ #define KEY_VOICECOMMAND 0x246 /* Listening Voice Command */ #define KEY_ASSISTANT 0x247 /* AL Context-aware desktop assistant */ +#define KEY_KBD_LAYOUT_NEXT 0x248 /* AC Next Keyboard Layout Select */ #define KEY_BRIGHTNESS_MIN 0x250 /* Set Brightness to Minimum */ #define KEY_BRIGHTNESS_MAX 0x251 /* Set Brightness to Maximum */ |