mirror of
https://github.com/torvalds/linux.git
synced 2024-11-28 07:01:32 +00:00
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input
Pull input updates from Dmitry Torokhov: "The first round of updates for the input subsystem. Just new drivers and existing driver fixes, no core changes except for the new uinput IOCTL to allow userspace to fetch sysfs name of the input device that was created" * 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input: (43 commits) Input: edt-ft5x06 - add a missing condition Input: appletouch - fix jumps when additional fingers are detected Input: appletouch - implement sensor data smoothing Input: add driver for SOC button array Input: pm8xxx-vibrator - add DT match table Input: pmic8xxx-pwrkey - migrate to DT Input: pmic8xxx-keypad - migrate to DT Input: pmic8xxx-keypad - migrate to regmap APIs Input: pmic8xxx-keypad - migrate to devm_* APIs Input: pmic8xxx-keypad - fix build by removing gpio configuration Input: add new driver for ARM CLPS711X keypad Input: edt-ft5x06 - add support for M09 firmware version Input: edt-ft5x06 - ignore touchdown events Input: edt-ft5x06 - adjust delays to conform datasheet Input: edt-ft5x06 - add DT support Input: edt-ft5x06 - several cleanups; no functional change Input: appletouch - dial back fuzz setting Input: remove obsolete tnetv107x drivers Input: sirfsoc-onkey - set the capability of reporting KEY_POWER Input: da9052_onkey - use correct register bit for key status ...
This commit is contained in:
commit
73f10274a6
27
Documentation/devicetree/bindings/input/clps711x-keypad.txt
Normal file
27
Documentation/devicetree/bindings/input/clps711x-keypad.txt
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
* Cirrus Logic CLPS711X matrix keypad device tree bindings
|
||||||
|
|
||||||
|
Required Properties:
|
||||||
|
- compatible: Shall contain "cirrus,clps711x-keypad".
|
||||||
|
- row-gpios: List of GPIOs used as row lines.
|
||||||
|
- poll-interval: Poll interval time in milliseconds.
|
||||||
|
- linux,keymap: The definition can be found at
|
||||||
|
bindings/input/matrix-keymap.txt.
|
||||||
|
|
||||||
|
Optional Properties:
|
||||||
|
- autorepeat: Enable autorepeat feature.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
keypad {
|
||||||
|
compatible = "cirrus,ep7312-keypad", "cirrus,clps711x-keypad";
|
||||||
|
autorepeat;
|
||||||
|
poll-interval = <120>;
|
||||||
|
row-gpios = <&porta 0 0>,
|
||||||
|
<&porta 1 0>;
|
||||||
|
|
||||||
|
linux,keymap = <
|
||||||
|
MATRIX_KEY(0, 0, KEY_UP)
|
||||||
|
MATRIX_KEY(0, 1, KEY_DOWN)
|
||||||
|
MATRIX_KEY(1, 0, KEY_LEFT)
|
||||||
|
MATRIX_KEY(1, 1, KEY_RIGHT)
|
||||||
|
>;
|
||||||
|
};
|
@ -0,0 +1,89 @@
|
|||||||
|
Qualcomm PM8xxx PMIC Keypad
|
||||||
|
|
||||||
|
PROPERTIES
|
||||||
|
|
||||||
|
- compatible:
|
||||||
|
Usage: required
|
||||||
|
Value type: <string>
|
||||||
|
Definition: must be one of:
|
||||||
|
"qcom,pm8058-keypad"
|
||||||
|
"qcom,pm8921-keypad"
|
||||||
|
|
||||||
|
- reg:
|
||||||
|
Usage: required
|
||||||
|
Value type: <prop-encoded-array>
|
||||||
|
Definition: address of keypad control register
|
||||||
|
|
||||||
|
- interrupts:
|
||||||
|
Usage: required
|
||||||
|
Value type: <prop-encoded-array>
|
||||||
|
Definition: the first interrupt specifies the key sense interrupt
|
||||||
|
and the second interrupt specifies the key stuck interrupt.
|
||||||
|
The format of the specifier is defined by the binding
|
||||||
|
document describing the node's interrupt parent.
|
||||||
|
|
||||||
|
- linux,keymap:
|
||||||
|
Usage: required
|
||||||
|
Value type: <prop-encoded-array>
|
||||||
|
Definition: the linux keymap. More information can be found in
|
||||||
|
input/matrix-keymap.txt.
|
||||||
|
|
||||||
|
- linux,keypad-no-autorepeat:
|
||||||
|
Usage: optional
|
||||||
|
Value type: <bool>
|
||||||
|
Definition: don't enable autorepeat feature.
|
||||||
|
|
||||||
|
- linux,keypad-wakeup:
|
||||||
|
Usage: optional
|
||||||
|
Value type: <bool>
|
||||||
|
Definition: use any event on keypad as wakeup event.
|
||||||
|
|
||||||
|
- keypad,num-rows:
|
||||||
|
Usage: required
|
||||||
|
Value type: <u32>
|
||||||
|
Definition: number of rows in the keymap. More information can be found
|
||||||
|
in input/matrix-keymap.txt.
|
||||||
|
|
||||||
|
- keypad,num-columns:
|
||||||
|
Usage: required
|
||||||
|
Value type: <u32>
|
||||||
|
Definition: number of columns in the keymap. More information can be
|
||||||
|
found in input/matrix-keymap.txt.
|
||||||
|
|
||||||
|
- debounce:
|
||||||
|
Usage: optional
|
||||||
|
Value type: <u32>
|
||||||
|
Definition: time in microseconds that key must be pressed or release
|
||||||
|
for key sense interrupt to trigger.
|
||||||
|
|
||||||
|
- scan-delay:
|
||||||
|
Usage: optional
|
||||||
|
Value type: <u32>
|
||||||
|
Definition: time in microseconds to pause between successive scans
|
||||||
|
of the matrix array.
|
||||||
|
|
||||||
|
- row-hold:
|
||||||
|
Usage: optional
|
||||||
|
Value type: <u32>
|
||||||
|
Definition: time in nanoseconds to pause between scans of each row in
|
||||||
|
the matrix array.
|
||||||
|
|
||||||
|
EXAMPLE
|
||||||
|
|
||||||
|
keypad@148 {
|
||||||
|
compatible = "qcom,pm8921-keypad";
|
||||||
|
reg = <0x148>;
|
||||||
|
interrupt-parent = <&pmicintc>;
|
||||||
|
interrupts = <74 1>, <75 1>;
|
||||||
|
linux,keymap = <
|
||||||
|
MATRIX_KEY(0, 0, KEY_VOLUMEUP)
|
||||||
|
MATRIX_KEY(0, 1, KEY_VOLUMEDOWN)
|
||||||
|
MATRIX_KEY(0, 2, KEY_CAMERA_FOCUS)
|
||||||
|
MATRIX_KEY(0, 3, KEY_CAMERA)
|
||||||
|
>;
|
||||||
|
keypad,num-rows = <1>;
|
||||||
|
keypad,num-columns = <5>;
|
||||||
|
debounce = <15>;
|
||||||
|
scan-delay = <32>;
|
||||||
|
row-hold = <91500>;
|
||||||
|
};
|
@ -0,0 +1,46 @@
|
|||||||
|
Qualcomm PM8xxx PMIC Power Key
|
||||||
|
|
||||||
|
PROPERTIES
|
||||||
|
|
||||||
|
- compatible:
|
||||||
|
Usage: required
|
||||||
|
Value type: <string>
|
||||||
|
Definition: must be one of:
|
||||||
|
"qcom,pm8058-pwrkey"
|
||||||
|
"qcom,pm8921-pwrkey"
|
||||||
|
|
||||||
|
- reg:
|
||||||
|
Usage: required
|
||||||
|
Value type: <prop-encoded-array>
|
||||||
|
Definition: address of power key control register
|
||||||
|
|
||||||
|
- interrupts:
|
||||||
|
Usage: required
|
||||||
|
Value type: <prop-encoded-array>
|
||||||
|
Definition: the first interrupt specifies the key release interrupt
|
||||||
|
and the second interrupt specifies the key press interrupt.
|
||||||
|
The format of the specifier is defined by the binding
|
||||||
|
document describing the node's interrupt parent.
|
||||||
|
|
||||||
|
- debounce:
|
||||||
|
Usage: optional
|
||||||
|
Value type: <u32>
|
||||||
|
Definition: time in microseconds that key must be pressed or release
|
||||||
|
for state change interrupt to trigger.
|
||||||
|
|
||||||
|
- pull-up:
|
||||||
|
Usage: optional
|
||||||
|
Value type: <empty>
|
||||||
|
Definition: presence of this property indicates that the KPDPWR_N pin
|
||||||
|
should be configured for pull up.
|
||||||
|
|
||||||
|
EXAMPLE
|
||||||
|
|
||||||
|
pwrkey@1c {
|
||||||
|
compatible = "qcom,pm8921-pwrkey";
|
||||||
|
reg = <0x1c>;
|
||||||
|
interrupt-parent = <&pmicintc>;
|
||||||
|
interrupts = <50 1>, <51 1>;
|
||||||
|
debounce = <15625>;
|
||||||
|
pull-up;
|
||||||
|
};
|
22
Documentation/devicetree/bindings/input/qcom,pm8xxx-vib.txt
Normal file
22
Documentation/devicetree/bindings/input/qcom,pm8xxx-vib.txt
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
Qualcomm PM8xxx PMIC Vibrator
|
||||||
|
|
||||||
|
PROPERTIES
|
||||||
|
|
||||||
|
- compatible:
|
||||||
|
Usage: required
|
||||||
|
Value type: <string>
|
||||||
|
Definition: must be one of:
|
||||||
|
"qcom,pm8058-vib"
|
||||||
|
"qcom,pm8921-vib"
|
||||||
|
|
||||||
|
- reg:
|
||||||
|
Usage: required
|
||||||
|
Value type: <prop-encoded-array>
|
||||||
|
Definition: address of vibration control register
|
||||||
|
|
||||||
|
EXAMPLE
|
||||||
|
|
||||||
|
vibrator@4a {
|
||||||
|
compatible = "qcom,pm8058-vib";
|
||||||
|
reg = <0x4a>;
|
||||||
|
};
|
@ -0,0 +1,55 @@
|
|||||||
|
FocalTech EDT-FT5x06 Polytouch driver
|
||||||
|
=====================================
|
||||||
|
|
||||||
|
There are 3 variants of the chip for various touch panel sizes
|
||||||
|
FT5206GE1 2.8" .. 3.8"
|
||||||
|
FT5306DE4 4.3" .. 7"
|
||||||
|
FT5406EE8 7" .. 8.9"
|
||||||
|
|
||||||
|
The software interface is identical for all those chips, so that
|
||||||
|
currently there is no need for the driver to distinguish between the
|
||||||
|
different chips. Nevertheless distinct compatible strings are used so
|
||||||
|
that a distinction can be added if necessary without changing the DT
|
||||||
|
bindings.
|
||||||
|
|
||||||
|
|
||||||
|
Required properties:
|
||||||
|
- compatible: "edt,edt-ft5206"
|
||||||
|
or: "edt,edt-ft5306"
|
||||||
|
or: "edt,edt-ft5406"
|
||||||
|
|
||||||
|
- reg: I2C slave address of the chip (0x38)
|
||||||
|
- interrupt-parent: a phandle pointing to the interrupt controller
|
||||||
|
serving the interrupt for this chip
|
||||||
|
- interrupts: interrupt specification for the touchdetect
|
||||||
|
interrupt
|
||||||
|
|
||||||
|
Optional properties:
|
||||||
|
- reset-gpios: GPIO specification for the RESET input
|
||||||
|
- wake-gpios: GPIO specification for the WAKE input
|
||||||
|
|
||||||
|
- pinctrl-names: should be "default"
|
||||||
|
- pinctrl-0: a phandle pointing to the pin settings for the
|
||||||
|
control gpios
|
||||||
|
|
||||||
|
- threshold: allows setting the "click"-threshold in the range
|
||||||
|
from 20 to 80.
|
||||||
|
|
||||||
|
- gain: allows setting the sensitivity in the range from 0 to
|
||||||
|
31. Note that lower values indicate higher
|
||||||
|
sensitivity.
|
||||||
|
|
||||||
|
- offset: allows setting the edge compensation in the range from
|
||||||
|
0 to 31.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
polytouch: edt-ft5x06@38 {
|
||||||
|
compatible = "edt,edt-ft5406", "edt,edt-ft5x06";
|
||||||
|
reg = <0x38>;
|
||||||
|
pinctrl-names = "default";
|
||||||
|
pinctrl-0 = <&edt_ft5x06_pins>;
|
||||||
|
interrupt-parent = <&gpio2>;
|
||||||
|
interrupts = <5 0>;
|
||||||
|
reset-gpios = <&gpio2 6 1>;
|
||||||
|
wake-gpios = <&gpio4 9 0>;
|
||||||
|
};
|
@ -0,0 +1,30 @@
|
|||||||
|
* Neonode infrared touchscreen controller
|
||||||
|
|
||||||
|
Required properties:
|
||||||
|
- compatible: must be "neonode,zforce"
|
||||||
|
- reg: I2C address of the chip
|
||||||
|
- interrupts: interrupt to which the chip is connected
|
||||||
|
- gpios: gpios the chip is connected to
|
||||||
|
first one is the interrupt gpio and second one the reset gpio
|
||||||
|
- x-size: horizontal resolution of touchscreen
|
||||||
|
- y-size: vertical resolution of touchscreen
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
i2c@00000000 {
|
||||||
|
/* ... */
|
||||||
|
|
||||||
|
zforce_ts@50 {
|
||||||
|
compatible = "neonode,zforce";
|
||||||
|
reg = <0x50>;
|
||||||
|
interrupts = <2 0>;
|
||||||
|
|
||||||
|
gpios = <&gpio5 6 0>, /* INT */
|
||||||
|
<&gpio5 9 0>; /* RST */
|
||||||
|
|
||||||
|
x-size = <800>;
|
||||||
|
y-size = <600>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ... */
|
||||||
|
};
|
@ -151,6 +151,18 @@ config KEYBOARD_BFIN
|
|||||||
To compile this driver as a module, choose M here: the
|
To compile this driver as a module, choose M here: the
|
||||||
module will be called bf54x-keys.
|
module will be called bf54x-keys.
|
||||||
|
|
||||||
|
config KEYBOARD_CLPS711X
|
||||||
|
tristate "CLPS711X Keypad support"
|
||||||
|
depends on OF_GPIO && (ARCH_CLPS711X || COMPILE_TEST)
|
||||||
|
select INPUT_MATRIXKMAP
|
||||||
|
select INPUT_POLLDEV
|
||||||
|
help
|
||||||
|
Say Y here to enable the matrix keypad on the Cirrus Logic
|
||||||
|
CLPS711X CPUs.
|
||||||
|
|
||||||
|
To compile this driver as a module, choose M here: the
|
||||||
|
module will be called clps711x-keypad.
|
||||||
|
|
||||||
config KEYBOARD_LKKBD
|
config KEYBOARD_LKKBD
|
||||||
tristate "DECstation/VAXstation LK201/LK401 keyboard"
|
tristate "DECstation/VAXstation LK201/LK401 keyboard"
|
||||||
select SERIO
|
select SERIO
|
||||||
@ -595,16 +607,6 @@ config KEYBOARD_TC3589X
|
|||||||
To compile this driver as a module, choose M here: the
|
To compile this driver as a module, choose M here: the
|
||||||
module will be called tc3589x-keypad.
|
module will be called tc3589x-keypad.
|
||||||
|
|
||||||
config KEYBOARD_TNETV107X
|
|
||||||
tristate "TI TNETV107X keypad support"
|
|
||||||
depends on ARCH_DAVINCI_TNETV107X
|
|
||||||
select INPUT_MATRIXKMAP
|
|
||||||
help
|
|
||||||
Say Y here if you want to use the TNETV107X keypad.
|
|
||||||
|
|
||||||
To compile this driver as a module, choose M here: the
|
|
||||||
module will be called tnetv107x-keypad.
|
|
||||||
|
|
||||||
config KEYBOARD_TWL4030
|
config KEYBOARD_TWL4030
|
||||||
tristate "TI TWL4030/TWL5030/TPS659x0 keypad support"
|
tristate "TI TWL4030/TWL5030/TPS659x0 keypad support"
|
||||||
depends on TWL4030_CORE
|
depends on TWL4030_CORE
|
||||||
|
@ -11,6 +11,7 @@ obj-$(CONFIG_KEYBOARD_AMIGA) += amikbd.o
|
|||||||
obj-$(CONFIG_KEYBOARD_ATARI) += atakbd.o
|
obj-$(CONFIG_KEYBOARD_ATARI) += atakbd.o
|
||||||
obj-$(CONFIG_KEYBOARD_ATKBD) += atkbd.o
|
obj-$(CONFIG_KEYBOARD_ATKBD) += atkbd.o
|
||||||
obj-$(CONFIG_KEYBOARD_BFIN) += bf54x-keys.o
|
obj-$(CONFIG_KEYBOARD_BFIN) += bf54x-keys.o
|
||||||
|
obj-$(CONFIG_KEYBOARD_CLPS711X) += clps711x-keypad.o
|
||||||
obj-$(CONFIG_KEYBOARD_CROS_EC) += cros_ec_keyb.o
|
obj-$(CONFIG_KEYBOARD_CROS_EC) += cros_ec_keyb.o
|
||||||
obj-$(CONFIG_KEYBOARD_DAVINCI) += davinci_keyscan.o
|
obj-$(CONFIG_KEYBOARD_DAVINCI) += davinci_keyscan.o
|
||||||
obj-$(CONFIG_KEYBOARD_EP93XX) += ep93xx_keypad.o
|
obj-$(CONFIG_KEYBOARD_EP93XX) += ep93xx_keypad.o
|
||||||
@ -53,7 +54,6 @@ obj-$(CONFIG_KEYBOARD_STOWAWAY) += stowaway.o
|
|||||||
obj-$(CONFIG_KEYBOARD_SUNKBD) += sunkbd.o
|
obj-$(CONFIG_KEYBOARD_SUNKBD) += sunkbd.o
|
||||||
obj-$(CONFIG_KEYBOARD_TC3589X) += tc3589x-keypad.o
|
obj-$(CONFIG_KEYBOARD_TC3589X) += tc3589x-keypad.o
|
||||||
obj-$(CONFIG_KEYBOARD_TEGRA) += tegra-kbc.o
|
obj-$(CONFIG_KEYBOARD_TEGRA) += tegra-kbc.o
|
||||||
obj-$(CONFIG_KEYBOARD_TNETV107X) += tnetv107x-keypad.o
|
|
||||||
obj-$(CONFIG_KEYBOARD_TWL4030) += twl4030_keypad.o
|
obj-$(CONFIG_KEYBOARD_TWL4030) += twl4030_keypad.o
|
||||||
obj-$(CONFIG_KEYBOARD_XTKBD) += xtkbd.o
|
obj-$(CONFIG_KEYBOARD_XTKBD) += xtkbd.o
|
||||||
obj-$(CONFIG_KEYBOARD_W90P910) += w90p910_keypad.o
|
obj-$(CONFIG_KEYBOARD_W90P910) += w90p910_keypad.o
|
||||||
|
207
drivers/input/keyboard/clps711x-keypad.c
Normal file
207
drivers/input/keyboard/clps711x-keypad.c
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
/*
|
||||||
|
* Cirrus Logic CLPS711X Keypad driver
|
||||||
|
*
|
||||||
|
* Copyright (C) 2014 Alexander Shiyan <shc_work@mail.ru>
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/input.h>
|
||||||
|
#include <linux/input-polldev.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/of_gpio.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/regmap.h>
|
||||||
|
#include <linux/sched.h>
|
||||||
|
#include <linux/input/matrix_keypad.h>
|
||||||
|
#include <linux/mfd/syscon.h>
|
||||||
|
#include <linux/mfd/syscon/clps711x.h>
|
||||||
|
|
||||||
|
#define CLPS711X_KEYPAD_COL_COUNT 8
|
||||||
|
|
||||||
|
struct clps711x_gpio_data {
|
||||||
|
struct gpio_desc *desc;
|
||||||
|
DECLARE_BITMAP(last_state, CLPS711X_KEYPAD_COL_COUNT);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct clps711x_keypad_data {
|
||||||
|
struct regmap *syscon;
|
||||||
|
int row_count;
|
||||||
|
unsigned int row_shift;
|
||||||
|
struct clps711x_gpio_data *gpio_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void clps711x_keypad_poll(struct input_polled_dev *dev)
|
||||||
|
{
|
||||||
|
const unsigned short *keycodes = dev->input->keycode;
|
||||||
|
struct clps711x_keypad_data *priv = dev->private;
|
||||||
|
bool sync = false;
|
||||||
|
int col, row;
|
||||||
|
|
||||||
|
for (col = 0; col < CLPS711X_KEYPAD_COL_COUNT; col++) {
|
||||||
|
/* Assert column */
|
||||||
|
regmap_update_bits(priv->syscon, SYSCON_OFFSET,
|
||||||
|
SYSCON1_KBDSCAN_MASK,
|
||||||
|
SYSCON1_KBDSCAN(8 + col));
|
||||||
|
|
||||||
|
/* Scan rows */
|
||||||
|
for (row = 0; row < priv->row_count; row++) {
|
||||||
|
struct clps711x_gpio_data *data = &priv->gpio_data[row];
|
||||||
|
bool state, state1;
|
||||||
|
|
||||||
|
/* Read twice for protection against fluctuations */
|
||||||
|
do {
|
||||||
|
state = gpiod_get_value_cansleep(data->desc);
|
||||||
|
cond_resched();
|
||||||
|
state1 = gpiod_get_value_cansleep(data->desc);
|
||||||
|
} while (state != state1);
|
||||||
|
|
||||||
|
if (test_bit(col, data->last_state) != state) {
|
||||||
|
int code = MATRIX_SCAN_CODE(row, col,
|
||||||
|
priv->row_shift);
|
||||||
|
|
||||||
|
if (state) {
|
||||||
|
set_bit(col, data->last_state);
|
||||||
|
input_event(dev->input, EV_MSC,
|
||||||
|
MSC_SCAN, code);
|
||||||
|
} else {
|
||||||
|
clear_bit(col, data->last_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keycodes[code])
|
||||||
|
input_report_key(dev->input,
|
||||||
|
keycodes[code], state);
|
||||||
|
sync = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set all columns to low */
|
||||||
|
regmap_update_bits(priv->syscon, SYSCON_OFFSET,
|
||||||
|
SYSCON1_KBDSCAN_MASK, SYSCON1_KBDSCAN(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sync)
|
||||||
|
input_sync(dev->input);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int clps711x_keypad_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct clps711x_keypad_data *priv;
|
||||||
|
struct device *dev = &pdev->dev;
|
||||||
|
struct device_node *np = dev->of_node;
|
||||||
|
struct input_polled_dev *poll_dev;
|
||||||
|
u32 poll_interval;
|
||||||
|
int i, err;
|
||||||
|
|
||||||
|
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||||
|
if (!priv)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
priv->syscon =
|
||||||
|
syscon_regmap_lookup_by_compatible("cirrus,clps711x-syscon1");
|
||||||
|
if (IS_ERR(priv->syscon))
|
||||||
|
return PTR_ERR(priv->syscon);
|
||||||
|
|
||||||
|
priv->row_count = of_gpio_named_count(np, "row-gpios");
|
||||||
|
if (priv->row_count < 1)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
priv->gpio_data = devm_kzalloc(dev,
|
||||||
|
sizeof(*priv->gpio_data) * priv->row_count,
|
||||||
|
GFP_KERNEL);
|
||||||
|
if (!priv->gpio_data)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
priv->row_shift = get_count_order(CLPS711X_KEYPAD_COL_COUNT);
|
||||||
|
|
||||||
|
for (i = 0; i < priv->row_count; i++) {
|
||||||
|
struct clps711x_gpio_data *data = &priv->gpio_data[i];
|
||||||
|
|
||||||
|
data->desc = devm_gpiod_get_index(dev, "row", i);
|
||||||
|
if (!data->desc)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (IS_ERR(data->desc))
|
||||||
|
return PTR_ERR(data->desc);
|
||||||
|
|
||||||
|
gpiod_direction_input(data->desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
err = of_property_read_u32(np, "poll-interval", &poll_interval);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
poll_dev = input_allocate_polled_device();
|
||||||
|
if (!poll_dev)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
poll_dev->private = priv;
|
||||||
|
poll_dev->poll = clps711x_keypad_poll;
|
||||||
|
poll_dev->poll_interval = poll_interval;
|
||||||
|
poll_dev->input->name = pdev->name;
|
||||||
|
poll_dev->input->dev.parent = dev;
|
||||||
|
poll_dev->input->id.bustype = BUS_HOST;
|
||||||
|
poll_dev->input->id.vendor = 0x0001;
|
||||||
|
poll_dev->input->id.product = 0x0001;
|
||||||
|
poll_dev->input->id.version = 0x0100;
|
||||||
|
|
||||||
|
err = matrix_keypad_build_keymap(NULL, NULL, priv->row_count,
|
||||||
|
CLPS711X_KEYPAD_COL_COUNT,
|
||||||
|
NULL, poll_dev->input);
|
||||||
|
if (err)
|
||||||
|
goto out_err;
|
||||||
|
|
||||||
|
input_set_capability(poll_dev->input, EV_MSC, MSC_SCAN);
|
||||||
|
if (of_property_read_bool(np, "autorepeat"))
|
||||||
|
__set_bit(EV_REP, poll_dev->input->evbit);
|
||||||
|
|
||||||
|
platform_set_drvdata(pdev, poll_dev);
|
||||||
|
|
||||||
|
/* Set all columns to low */
|
||||||
|
regmap_update_bits(priv->syscon, SYSCON_OFFSET, SYSCON1_KBDSCAN_MASK,
|
||||||
|
SYSCON1_KBDSCAN(1));
|
||||||
|
|
||||||
|
err = input_register_polled_device(poll_dev);
|
||||||
|
if (err)
|
||||||
|
goto out_err;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
out_err:
|
||||||
|
input_free_polled_device(poll_dev);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int clps711x_keypad_remove(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct input_polled_dev *poll_dev = platform_get_drvdata(pdev);
|
||||||
|
|
||||||
|
input_unregister_polled_device(poll_dev);
|
||||||
|
input_free_polled_device(poll_dev);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct of_device_id clps711x_keypad_of_match[] = {
|
||||||
|
{ .compatible = "cirrus,clps711x-keypad", },
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, clps711x_keypad_of_match);
|
||||||
|
|
||||||
|
static struct platform_driver clps711x_keypad_driver = {
|
||||||
|
.driver = {
|
||||||
|
.name = "clps711x-keypad",
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.of_match_table = clps711x_keypad_of_match,
|
||||||
|
},
|
||||||
|
.probe = clps711x_keypad_probe,
|
||||||
|
.remove = clps711x_keypad_remove,
|
||||||
|
};
|
||||||
|
module_platform_driver(clps711x_keypad_driver);
|
||||||
|
|
||||||
|
MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>");
|
||||||
|
MODULE_DESCRIPTION("Cirrus Logic CLPS711X Keypad driver");
|
||||||
|
MODULE_LICENSE("GPL");
|
@ -439,7 +439,7 @@ static int imx_keypad_probe(struct platform_device *pdev)
|
|||||||
irq = platform_get_irq(pdev, 0);
|
irq = platform_get_irq(pdev, 0);
|
||||||
if (irq < 0) {
|
if (irq < 0) {
|
||||||
dev_err(&pdev->dev, "no irq defined in platform data\n");
|
dev_err(&pdev->dev, "no irq defined in platform data\n");
|
||||||
return -EINVAL;
|
return irq;
|
||||||
}
|
}
|
||||||
|
|
||||||
input_dev = devm_input_allocate_device(&pdev->dev);
|
input_dev = devm_input_allocate_device(&pdev->dev);
|
||||||
@ -449,7 +449,7 @@ static int imx_keypad_probe(struct platform_device *pdev)
|
|||||||
}
|
}
|
||||||
|
|
||||||
keypad = devm_kzalloc(&pdev->dev, sizeof(struct imx_keypad),
|
keypad = devm_kzalloc(&pdev->dev, sizeof(struct imx_keypad),
|
||||||
GFP_KERNEL);
|
GFP_KERNEL);
|
||||||
if (!keypad) {
|
if (!keypad) {
|
||||||
dev_err(&pdev->dev, "not enough memory for driver data\n");
|
dev_err(&pdev->dev, "not enough memory for driver data\n");
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
@ -19,10 +19,9 @@
|
|||||||
#include <linux/bitops.h>
|
#include <linux/bitops.h>
|
||||||
#include <linux/delay.h>
|
#include <linux/delay.h>
|
||||||
#include <linux/mutex.h>
|
#include <linux/mutex.h>
|
||||||
|
#include <linux/regmap.h>
|
||||||
#include <linux/mfd/pm8xxx/core.h>
|
#include <linux/of.h>
|
||||||
#include <linux/mfd/pm8xxx/gpio.h>
|
#include <linux/input/matrix_keypad.h>
|
||||||
#include <linux/input/pmic8xxx-keypad.h>
|
|
||||||
|
|
||||||
#define PM8XXX_MAX_ROWS 18
|
#define PM8XXX_MAX_ROWS 18
|
||||||
#define PM8XXX_MAX_COLS 8
|
#define PM8XXX_MAX_COLS 8
|
||||||
@ -85,8 +84,10 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* struct pmic8xxx_kp - internal keypad data structure
|
* struct pmic8xxx_kp - internal keypad data structure
|
||||||
* @pdata - keypad platform data pointer
|
* @num_cols - number of columns of keypad
|
||||||
|
* @num_rows - number of row of keypad
|
||||||
* @input - input device pointer for keypad
|
* @input - input device pointer for keypad
|
||||||
|
* @regmap - regmap handle
|
||||||
* @key_sense_irq - key press/release irq number
|
* @key_sense_irq - key press/release irq number
|
||||||
* @key_stuck_irq - key stuck notification irq number
|
* @key_stuck_irq - key stuck notification irq number
|
||||||
* @keycodes - array to hold the key codes
|
* @keycodes - array to hold the key codes
|
||||||
@ -96,8 +97,10 @@
|
|||||||
* @ctrl_reg - control register value
|
* @ctrl_reg - control register value
|
||||||
*/
|
*/
|
||||||
struct pmic8xxx_kp {
|
struct pmic8xxx_kp {
|
||||||
const struct pm8xxx_keypad_platform_data *pdata;
|
unsigned int num_rows;
|
||||||
|
unsigned int num_cols;
|
||||||
struct input_dev *input;
|
struct input_dev *input;
|
||||||
|
struct regmap *regmap;
|
||||||
int key_sense_irq;
|
int key_sense_irq;
|
||||||
int key_stuck_irq;
|
int key_stuck_irq;
|
||||||
|
|
||||||
@ -110,40 +113,13 @@ struct pmic8xxx_kp {
|
|||||||
u8 ctrl_reg;
|
u8 ctrl_reg;
|
||||||
};
|
};
|
||||||
|
|
||||||
static int pmic8xxx_kp_write_u8(struct pmic8xxx_kp *kp,
|
|
||||||
u8 data, u16 reg)
|
|
||||||
{
|
|
||||||
int rc;
|
|
||||||
|
|
||||||
rc = pm8xxx_writeb(kp->dev->parent, reg, data);
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int pmic8xxx_kp_read(struct pmic8xxx_kp *kp,
|
|
||||||
u8 *data, u16 reg, unsigned num_bytes)
|
|
||||||
{
|
|
||||||
int rc;
|
|
||||||
|
|
||||||
rc = pm8xxx_read_buf(kp->dev->parent, reg, data, num_bytes);
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int pmic8xxx_kp_read_u8(struct pmic8xxx_kp *kp,
|
|
||||||
u8 *data, u16 reg)
|
|
||||||
{
|
|
||||||
int rc;
|
|
||||||
|
|
||||||
rc = pmic8xxx_kp_read(kp, data, reg, 1);
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
static u8 pmic8xxx_col_state(struct pmic8xxx_kp *kp, u8 col)
|
static u8 pmic8xxx_col_state(struct pmic8xxx_kp *kp, u8 col)
|
||||||
{
|
{
|
||||||
/* all keys pressed on that particular row? */
|
/* all keys pressed on that particular row? */
|
||||||
if (col == 0x00)
|
if (col == 0x00)
|
||||||
return 1 << kp->pdata->num_cols;
|
return 1 << kp->num_cols;
|
||||||
else
|
else
|
||||||
return col & ((1 << kp->pdata->num_cols) - 1);
|
return col & ((1 << kp->num_cols) - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -161,9 +137,9 @@ static u8 pmic8xxx_col_state(struct pmic8xxx_kp *kp, u8 col)
|
|||||||
static int pmic8xxx_chk_sync_read(struct pmic8xxx_kp *kp)
|
static int pmic8xxx_chk_sync_read(struct pmic8xxx_kp *kp)
|
||||||
{
|
{
|
||||||
int rc;
|
int rc;
|
||||||
u8 scan_val;
|
unsigned int scan_val;
|
||||||
|
|
||||||
rc = pmic8xxx_kp_read_u8(kp, &scan_val, KEYP_SCAN);
|
rc = regmap_read(kp->regmap, KEYP_SCAN, &scan_val);
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
dev_err(kp->dev, "Error reading KEYP_SCAN reg, rc=%d\n", rc);
|
dev_err(kp->dev, "Error reading KEYP_SCAN reg, rc=%d\n", rc);
|
||||||
return rc;
|
return rc;
|
||||||
@ -171,7 +147,7 @@ static int pmic8xxx_chk_sync_read(struct pmic8xxx_kp *kp)
|
|||||||
|
|
||||||
scan_val |= 0x1;
|
scan_val |= 0x1;
|
||||||
|
|
||||||
rc = pmic8xxx_kp_write_u8(kp, scan_val, KEYP_SCAN);
|
rc = regmap_write(kp->regmap, KEYP_SCAN, scan_val);
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
dev_err(kp->dev, "Error writing KEYP_SCAN reg, rc=%d\n", rc);
|
dev_err(kp->dev, "Error writing KEYP_SCAN reg, rc=%d\n", rc);
|
||||||
return rc;
|
return rc;
|
||||||
@ -187,31 +163,29 @@ static int pmic8xxx_kp_read_data(struct pmic8xxx_kp *kp, u16 *state,
|
|||||||
u16 data_reg, int read_rows)
|
u16 data_reg, int read_rows)
|
||||||
{
|
{
|
||||||
int rc, row;
|
int rc, row;
|
||||||
u8 new_data[PM8XXX_MAX_ROWS];
|
unsigned int val;
|
||||||
|
|
||||||
rc = pmic8xxx_kp_read(kp, new_data, data_reg, read_rows);
|
for (row = 0; row < read_rows; row++) {
|
||||||
if (rc)
|
rc = regmap_read(kp->regmap, data_reg, &val);
|
||||||
return rc;
|
if (rc)
|
||||||
|
return rc;
|
||||||
for (row = 0; row < kp->pdata->num_rows; row++) {
|
dev_dbg(kp->dev, "%d = %d\n", row, val);
|
||||||
dev_dbg(kp->dev, "new_data[%d] = %d\n", row,
|
state[row] = pmic8xxx_col_state(kp, val);
|
||||||
new_data[row]);
|
|
||||||
state[row] = pmic8xxx_col_state(kp, new_data[row]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return rc;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int pmic8xxx_kp_read_matrix(struct pmic8xxx_kp *kp, u16 *new_state,
|
static int pmic8xxx_kp_read_matrix(struct pmic8xxx_kp *kp, u16 *new_state,
|
||||||
u16 *old_state)
|
u16 *old_state)
|
||||||
{
|
{
|
||||||
int rc, read_rows;
|
int rc, read_rows;
|
||||||
u8 scan_val;
|
unsigned int scan_val;
|
||||||
|
|
||||||
if (kp->pdata->num_rows < PM8XXX_MIN_ROWS)
|
if (kp->num_rows < PM8XXX_MIN_ROWS)
|
||||||
read_rows = PM8XXX_MIN_ROWS;
|
read_rows = PM8XXX_MIN_ROWS;
|
||||||
else
|
else
|
||||||
read_rows = kp->pdata->num_rows;
|
read_rows = kp->num_rows;
|
||||||
|
|
||||||
pmic8xxx_chk_sync_read(kp);
|
pmic8xxx_chk_sync_read(kp);
|
||||||
|
|
||||||
@ -236,14 +210,14 @@ static int pmic8xxx_kp_read_matrix(struct pmic8xxx_kp *kp, u16 *new_state,
|
|||||||
/* 4 * 32KHz clocks */
|
/* 4 * 32KHz clocks */
|
||||||
udelay((4 * DIV_ROUND_UP(USEC_PER_SEC, KEYP_CLOCK_FREQ)) + 1);
|
udelay((4 * DIV_ROUND_UP(USEC_PER_SEC, KEYP_CLOCK_FREQ)) + 1);
|
||||||
|
|
||||||
rc = pmic8xxx_kp_read_u8(kp, &scan_val, KEYP_SCAN);
|
rc = regmap_read(kp->regmap, KEYP_SCAN, &scan_val);
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
dev_err(kp->dev, "Error reading KEYP_SCAN reg, rc=%d\n", rc);
|
dev_err(kp->dev, "Error reading KEYP_SCAN reg, rc=%d\n", rc);
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
scan_val &= 0xFE;
|
scan_val &= 0xFE;
|
||||||
rc = pmic8xxx_kp_write_u8(kp, scan_val, KEYP_SCAN);
|
rc = regmap_write(kp->regmap, KEYP_SCAN, scan_val);
|
||||||
if (rc < 0)
|
if (rc < 0)
|
||||||
dev_err(kp->dev, "Error writing KEYP_SCAN reg, rc=%d\n", rc);
|
dev_err(kp->dev, "Error writing KEYP_SCAN reg, rc=%d\n", rc);
|
||||||
|
|
||||||
@ -255,13 +229,13 @@ static void __pmic8xxx_kp_scan_matrix(struct pmic8xxx_kp *kp, u16 *new_state,
|
|||||||
{
|
{
|
||||||
int row, col, code;
|
int row, col, code;
|
||||||
|
|
||||||
for (row = 0; row < kp->pdata->num_rows; row++) {
|
for (row = 0; row < kp->num_rows; row++) {
|
||||||
int bits_changed = new_state[row] ^ old_state[row];
|
int bits_changed = new_state[row] ^ old_state[row];
|
||||||
|
|
||||||
if (!bits_changed)
|
if (!bits_changed)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
for (col = 0; col < kp->pdata->num_cols; col++) {
|
for (col = 0; col < kp->num_cols; col++) {
|
||||||
if (!(bits_changed & (1 << col)))
|
if (!(bits_changed & (1 << col)))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@ -287,9 +261,9 @@ static bool pmic8xxx_detect_ghost_keys(struct pmic8xxx_kp *kp, u16 *new_state)
|
|||||||
u16 check, row_state;
|
u16 check, row_state;
|
||||||
|
|
||||||
check = 0;
|
check = 0;
|
||||||
for (row = 0; row < kp->pdata->num_rows; row++) {
|
for (row = 0; row < kp->num_rows; row++) {
|
||||||
row_state = (~new_state[row]) &
|
row_state = (~new_state[row]) &
|
||||||
((1 << kp->pdata->num_cols) - 1);
|
((1 << kp->num_cols) - 1);
|
||||||
|
|
||||||
if (hweight16(row_state) > 1) {
|
if (hweight16(row_state) > 1) {
|
||||||
if (found_first == -1)
|
if (found_first == -1)
|
||||||
@ -379,10 +353,10 @@ static irqreturn_t pmic8xxx_kp_stuck_irq(int irq, void *data)
|
|||||||
static irqreturn_t pmic8xxx_kp_irq(int irq, void *data)
|
static irqreturn_t pmic8xxx_kp_irq(int irq, void *data)
|
||||||
{
|
{
|
||||||
struct pmic8xxx_kp *kp = data;
|
struct pmic8xxx_kp *kp = data;
|
||||||
u8 ctrl_val, events;
|
unsigned int ctrl_val, events;
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
rc = pmic8xxx_kp_read(kp, &ctrl_val, KEYP_CTRL, 1);
|
rc = regmap_read(kp->regmap, KEYP_CTRL, &ctrl_val);
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
dev_err(kp->dev, "failed to read keyp_ctrl register\n");
|
dev_err(kp->dev, "failed to read keyp_ctrl register\n");
|
||||||
return IRQ_HANDLED;
|
return IRQ_HANDLED;
|
||||||
@ -397,8 +371,13 @@ static irqreturn_t pmic8xxx_kp_irq(int irq, void *data)
|
|||||||
return IRQ_HANDLED;
|
return IRQ_HANDLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int pmic8xxx_kpd_init(struct pmic8xxx_kp *kp)
|
static int pmic8xxx_kpd_init(struct pmic8xxx_kp *kp,
|
||||||
|
struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
|
const struct device_node *of_node = pdev->dev.of_node;
|
||||||
|
unsigned int scan_delay_ms;
|
||||||
|
unsigned int row_hold_ns;
|
||||||
|
unsigned int debounce_ms;
|
||||||
int bits, rc, cycles;
|
int bits, rc, cycles;
|
||||||
u8 scan_val = 0, ctrl_val = 0;
|
u8 scan_val = 0, ctrl_val = 0;
|
||||||
static const u8 row_bits[] = {
|
static const u8 row_bits[] = {
|
||||||
@ -406,40 +385,69 @@ static int pmic8xxx_kpd_init(struct pmic8xxx_kp *kp)
|
|||||||
};
|
};
|
||||||
|
|
||||||
/* Find column bits */
|
/* Find column bits */
|
||||||
if (kp->pdata->num_cols < KEYP_CTRL_SCAN_COLS_MIN)
|
if (kp->num_cols < KEYP_CTRL_SCAN_COLS_MIN)
|
||||||
bits = 0;
|
bits = 0;
|
||||||
else
|
else
|
||||||
bits = kp->pdata->num_cols - KEYP_CTRL_SCAN_COLS_MIN;
|
bits = kp->num_cols - KEYP_CTRL_SCAN_COLS_MIN;
|
||||||
ctrl_val = (bits & KEYP_CTRL_SCAN_COLS_BITS) <<
|
ctrl_val = (bits & KEYP_CTRL_SCAN_COLS_BITS) <<
|
||||||
KEYP_CTRL_SCAN_COLS_SHIFT;
|
KEYP_CTRL_SCAN_COLS_SHIFT;
|
||||||
|
|
||||||
/* Find row bits */
|
/* Find row bits */
|
||||||
if (kp->pdata->num_rows < KEYP_CTRL_SCAN_ROWS_MIN)
|
if (kp->num_rows < KEYP_CTRL_SCAN_ROWS_MIN)
|
||||||
bits = 0;
|
bits = 0;
|
||||||
else
|
else
|
||||||
bits = row_bits[kp->pdata->num_rows - KEYP_CTRL_SCAN_ROWS_MIN];
|
bits = row_bits[kp->num_rows - KEYP_CTRL_SCAN_ROWS_MIN];
|
||||||
|
|
||||||
ctrl_val |= (bits << KEYP_CTRL_SCAN_ROWS_SHIFT);
|
ctrl_val |= (bits << KEYP_CTRL_SCAN_ROWS_SHIFT);
|
||||||
|
|
||||||
rc = pmic8xxx_kp_write_u8(kp, ctrl_val, KEYP_CTRL);
|
rc = regmap_write(kp->regmap, KEYP_CTRL, ctrl_val);
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
dev_err(kp->dev, "Error writing KEYP_CTRL reg, rc=%d\n", rc);
|
dev_err(kp->dev, "Error writing KEYP_CTRL reg, rc=%d\n", rc);
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
bits = (kp->pdata->debounce_ms / 5) - 1;
|
if (of_property_read_u32(of_node, "scan-delay", &scan_delay_ms))
|
||||||
|
scan_delay_ms = MIN_SCAN_DELAY;
|
||||||
|
|
||||||
|
if (scan_delay_ms > MAX_SCAN_DELAY || scan_delay_ms < MIN_SCAN_DELAY ||
|
||||||
|
!is_power_of_2(scan_delay_ms)) {
|
||||||
|
dev_err(&pdev->dev, "invalid keypad scan time supplied\n");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (of_property_read_u32(of_node, "row-hold", &row_hold_ns))
|
||||||
|
row_hold_ns = MIN_ROW_HOLD_DELAY;
|
||||||
|
|
||||||
|
if (row_hold_ns > MAX_ROW_HOLD_DELAY ||
|
||||||
|
row_hold_ns < MIN_ROW_HOLD_DELAY ||
|
||||||
|
((row_hold_ns % MIN_ROW_HOLD_DELAY) != 0)) {
|
||||||
|
dev_err(&pdev->dev, "invalid keypad row hold time supplied\n");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (of_property_read_u32(of_node, "debounce", &debounce_ms))
|
||||||
|
debounce_ms = MIN_DEBOUNCE_TIME;
|
||||||
|
|
||||||
|
if (((debounce_ms % 5) != 0) ||
|
||||||
|
debounce_ms > MAX_DEBOUNCE_TIME ||
|
||||||
|
debounce_ms < MIN_DEBOUNCE_TIME) {
|
||||||
|
dev_err(&pdev->dev, "invalid debounce time supplied\n");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bits = (debounce_ms / 5) - 1;
|
||||||
|
|
||||||
scan_val |= (bits << KEYP_SCAN_DBOUNCE_SHIFT);
|
scan_val |= (bits << KEYP_SCAN_DBOUNCE_SHIFT);
|
||||||
|
|
||||||
bits = fls(kp->pdata->scan_delay_ms) - 1;
|
bits = fls(scan_delay_ms) - 1;
|
||||||
scan_val |= (bits << KEYP_SCAN_PAUSE_SHIFT);
|
scan_val |= (bits << KEYP_SCAN_PAUSE_SHIFT);
|
||||||
|
|
||||||
/* Row hold time is a multiple of 32KHz cycles. */
|
/* Row hold time is a multiple of 32KHz cycles. */
|
||||||
cycles = (kp->pdata->row_hold_ns * KEYP_CLOCK_FREQ) / NSEC_PER_SEC;
|
cycles = (row_hold_ns * KEYP_CLOCK_FREQ) / NSEC_PER_SEC;
|
||||||
|
|
||||||
scan_val |= (cycles << KEYP_SCAN_ROW_HOLD_SHIFT);
|
scan_val |= (cycles << KEYP_SCAN_ROW_HOLD_SHIFT);
|
||||||
|
|
||||||
rc = pmic8xxx_kp_write_u8(kp, scan_val, KEYP_SCAN);
|
rc = regmap_write(kp->regmap, KEYP_SCAN, scan_val);
|
||||||
if (rc)
|
if (rc)
|
||||||
dev_err(kp->dev, "Error writing KEYP_SCAN reg, rc=%d\n", rc);
|
dev_err(kp->dev, "Error writing KEYP_SCAN reg, rc=%d\n", rc);
|
||||||
|
|
||||||
@ -447,34 +455,13 @@ static int pmic8xxx_kpd_init(struct pmic8xxx_kp *kp)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int pmic8xxx_kp_config_gpio(int gpio_start, int num_gpios,
|
|
||||||
struct pmic8xxx_kp *kp, struct pm_gpio *gpio_config)
|
|
||||||
{
|
|
||||||
int rc, i;
|
|
||||||
|
|
||||||
if (gpio_start < 0 || num_gpios < 0)
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
for (i = 0; i < num_gpios; i++) {
|
|
||||||
rc = pm8xxx_gpio_config(gpio_start + i, gpio_config);
|
|
||||||
if (rc) {
|
|
||||||
dev_err(kp->dev, "%s: FAIL pm8xxx_gpio_config():"
|
|
||||||
"for PM GPIO [%d] rc=%d.\n",
|
|
||||||
__func__, gpio_start + i, rc);
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int pmic8xxx_kp_enable(struct pmic8xxx_kp *kp)
|
static int pmic8xxx_kp_enable(struct pmic8xxx_kp *kp)
|
||||||
{
|
{
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
kp->ctrl_reg |= KEYP_CTRL_KEYP_EN;
|
kp->ctrl_reg |= KEYP_CTRL_KEYP_EN;
|
||||||
|
|
||||||
rc = pmic8xxx_kp_write_u8(kp, kp->ctrl_reg, KEYP_CTRL);
|
rc = regmap_write(kp->regmap, KEYP_CTRL, kp->ctrl_reg);
|
||||||
if (rc < 0)
|
if (rc < 0)
|
||||||
dev_err(kp->dev, "Error writing KEYP_CTRL reg, rc=%d\n", rc);
|
dev_err(kp->dev, "Error writing KEYP_CTRL reg, rc=%d\n", rc);
|
||||||
|
|
||||||
@ -487,7 +474,7 @@ static int pmic8xxx_kp_disable(struct pmic8xxx_kp *kp)
|
|||||||
|
|
||||||
kp->ctrl_reg &= ~KEYP_CTRL_KEYP_EN;
|
kp->ctrl_reg &= ~KEYP_CTRL_KEYP_EN;
|
||||||
|
|
||||||
rc = pmic8xxx_kp_write_u8(kp, kp->ctrl_reg, KEYP_CTRL);
|
rc = regmap_write(kp->regmap, KEYP_CTRL, kp->ctrl_reg);
|
||||||
if (rc < 0)
|
if (rc < 0)
|
||||||
return rc;
|
return rc;
|
||||||
|
|
||||||
@ -520,106 +507,62 @@ static void pmic8xxx_kp_close(struct input_dev *dev)
|
|||||||
*/
|
*/
|
||||||
static int pmic8xxx_kp_probe(struct platform_device *pdev)
|
static int pmic8xxx_kp_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
const struct pm8xxx_keypad_platform_data *pdata =
|
unsigned int rows, cols;
|
||||||
dev_get_platdata(&pdev->dev);
|
bool repeat;
|
||||||
const struct matrix_keymap_data *keymap_data;
|
bool wakeup;
|
||||||
struct pmic8xxx_kp *kp;
|
struct pmic8xxx_kp *kp;
|
||||||
int rc;
|
int rc;
|
||||||
u8 ctrl_val;
|
unsigned int ctrl_val;
|
||||||
|
|
||||||
struct pm_gpio kypd_drv = {
|
rc = matrix_keypad_parse_of_params(&pdev->dev, &rows, &cols);
|
||||||
.direction = PM_GPIO_DIR_OUT,
|
if (rc)
|
||||||
.output_buffer = PM_GPIO_OUT_BUF_OPEN_DRAIN,
|
return rc;
|
||||||
.output_value = 0,
|
|
||||||
.pull = PM_GPIO_PULL_NO,
|
|
||||||
.vin_sel = PM_GPIO_VIN_S3,
|
|
||||||
.out_strength = PM_GPIO_STRENGTH_LOW,
|
|
||||||
.function = PM_GPIO_FUNC_1,
|
|
||||||
.inv_int_pol = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct pm_gpio kypd_sns = {
|
if (cols > PM8XXX_MAX_COLS || rows > PM8XXX_MAX_ROWS ||
|
||||||
.direction = PM_GPIO_DIR_IN,
|
cols < PM8XXX_MIN_COLS) {
|
||||||
.pull = PM_GPIO_PULL_UP_31P5,
|
|
||||||
.vin_sel = PM_GPIO_VIN_S3,
|
|
||||||
.out_strength = PM_GPIO_STRENGTH_NO,
|
|
||||||
.function = PM_GPIO_FUNC_NORMAL,
|
|
||||||
.inv_int_pol = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
if (!pdata || !pdata->num_cols || !pdata->num_rows ||
|
|
||||||
pdata->num_cols > PM8XXX_MAX_COLS ||
|
|
||||||
pdata->num_rows > PM8XXX_MAX_ROWS ||
|
|
||||||
pdata->num_cols < PM8XXX_MIN_COLS) {
|
|
||||||
dev_err(&pdev->dev, "invalid platform data\n");
|
dev_err(&pdev->dev, "invalid platform data\n");
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pdata->scan_delay_ms ||
|
repeat = !of_property_read_bool(pdev->dev.of_node,
|
||||||
pdata->scan_delay_ms > MAX_SCAN_DELAY ||
|
"linux,input-no-autorepeat");
|
||||||
pdata->scan_delay_ms < MIN_SCAN_DELAY ||
|
wakeup = of_property_read_bool(pdev->dev.of_node,
|
||||||
!is_power_of_2(pdata->scan_delay_ms)) {
|
"linux,keypad-wakeup");
|
||||||
dev_err(&pdev->dev, "invalid keypad scan time supplied\n");
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!pdata->row_hold_ns ||
|
kp = devm_kzalloc(&pdev->dev, sizeof(*kp), GFP_KERNEL);
|
||||||
pdata->row_hold_ns > MAX_ROW_HOLD_DELAY ||
|
|
||||||
pdata->row_hold_ns < MIN_ROW_HOLD_DELAY ||
|
|
||||||
((pdata->row_hold_ns % MIN_ROW_HOLD_DELAY) != 0)) {
|
|
||||||
dev_err(&pdev->dev, "invalid keypad row hold time supplied\n");
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!pdata->debounce_ms ||
|
|
||||||
((pdata->debounce_ms % 5) != 0) ||
|
|
||||||
pdata->debounce_ms > MAX_DEBOUNCE_TIME ||
|
|
||||||
pdata->debounce_ms < MIN_DEBOUNCE_TIME) {
|
|
||||||
dev_err(&pdev->dev, "invalid debounce time supplied\n");
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
keymap_data = pdata->keymap_data;
|
|
||||||
if (!keymap_data) {
|
|
||||||
dev_err(&pdev->dev, "no keymap data supplied\n");
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
kp = kzalloc(sizeof(*kp), GFP_KERNEL);
|
|
||||||
if (!kp)
|
if (!kp)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
|
kp->regmap = dev_get_regmap(pdev->dev.parent, NULL);
|
||||||
|
if (!kp->regmap)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
platform_set_drvdata(pdev, kp);
|
platform_set_drvdata(pdev, kp);
|
||||||
|
|
||||||
kp->pdata = pdata;
|
kp->num_rows = rows;
|
||||||
|
kp->num_cols = cols;
|
||||||
kp->dev = &pdev->dev;
|
kp->dev = &pdev->dev;
|
||||||
|
|
||||||
kp->input = input_allocate_device();
|
kp->input = devm_input_allocate_device(&pdev->dev);
|
||||||
if (!kp->input) {
|
if (!kp->input) {
|
||||||
dev_err(&pdev->dev, "unable to allocate input device\n");
|
dev_err(&pdev->dev, "unable to allocate input device\n");
|
||||||
rc = -ENOMEM;
|
return -ENOMEM;
|
||||||
goto err_alloc_device;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
kp->key_sense_irq = platform_get_irq(pdev, 0);
|
kp->key_sense_irq = platform_get_irq(pdev, 0);
|
||||||
if (kp->key_sense_irq < 0) {
|
if (kp->key_sense_irq < 0) {
|
||||||
dev_err(&pdev->dev, "unable to get keypad sense irq\n");
|
dev_err(&pdev->dev, "unable to get keypad sense irq\n");
|
||||||
rc = -ENXIO;
|
return kp->key_sense_irq;
|
||||||
goto err_get_irq;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
kp->key_stuck_irq = platform_get_irq(pdev, 1);
|
kp->key_stuck_irq = platform_get_irq(pdev, 1);
|
||||||
if (kp->key_stuck_irq < 0) {
|
if (kp->key_stuck_irq < 0) {
|
||||||
dev_err(&pdev->dev, "unable to get keypad stuck irq\n");
|
dev_err(&pdev->dev, "unable to get keypad stuck irq\n");
|
||||||
rc = -ENXIO;
|
return kp->key_stuck_irq;
|
||||||
goto err_get_irq;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
kp->input->name = pdata->input_name ? : "PMIC8XXX keypad";
|
kp->input->name = "PMIC8XXX keypad";
|
||||||
kp->input->phys = pdata->input_phys_device ? : "pmic8xxx_keypad/input0";
|
kp->input->phys = "pmic8xxx_keypad/input0";
|
||||||
|
|
||||||
kp->input->dev.parent = &pdev->dev;
|
|
||||||
|
|
||||||
kp->input->id.bustype = BUS_I2C;
|
kp->input->id.bustype = BUS_I2C;
|
||||||
kp->input->id.version = 0x0001;
|
kp->input->id.version = 0x0001;
|
||||||
@ -629,15 +572,15 @@ static int pmic8xxx_kp_probe(struct platform_device *pdev)
|
|||||||
kp->input->open = pmic8xxx_kp_open;
|
kp->input->open = pmic8xxx_kp_open;
|
||||||
kp->input->close = pmic8xxx_kp_close;
|
kp->input->close = pmic8xxx_kp_close;
|
||||||
|
|
||||||
rc = matrix_keypad_build_keymap(keymap_data, NULL,
|
rc = matrix_keypad_build_keymap(NULL, NULL,
|
||||||
PM8XXX_MAX_ROWS, PM8XXX_MAX_COLS,
|
PM8XXX_MAX_ROWS, PM8XXX_MAX_COLS,
|
||||||
kp->keycodes, kp->input);
|
kp->keycodes, kp->input);
|
||||||
if (rc) {
|
if (rc) {
|
||||||
dev_err(&pdev->dev, "failed to build keymap\n");
|
dev_err(&pdev->dev, "failed to build keymap\n");
|
||||||
goto err_get_irq;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pdata->rep)
|
if (repeat)
|
||||||
__set_bit(EV_REP, kp->input->evbit);
|
__set_bit(EV_REP, kp->input->evbit);
|
||||||
input_set_capability(kp->input, EV_MSC, MSC_SCAN);
|
input_set_capability(kp->input, EV_MSC, MSC_SCAN);
|
||||||
|
|
||||||
@ -647,44 +590,32 @@ static int pmic8xxx_kp_probe(struct platform_device *pdev)
|
|||||||
memset(kp->keystate, 0xff, sizeof(kp->keystate));
|
memset(kp->keystate, 0xff, sizeof(kp->keystate));
|
||||||
memset(kp->stuckstate, 0xff, sizeof(kp->stuckstate));
|
memset(kp->stuckstate, 0xff, sizeof(kp->stuckstate));
|
||||||
|
|
||||||
rc = pmic8xxx_kpd_init(kp);
|
rc = pmic8xxx_kpd_init(kp, pdev);
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
dev_err(&pdev->dev, "unable to initialize keypad controller\n");
|
dev_err(&pdev->dev, "unable to initialize keypad controller\n");
|
||||||
goto err_get_irq;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
rc = pmic8xxx_kp_config_gpio(pdata->cols_gpio_start,
|
rc = devm_request_any_context_irq(&pdev->dev, kp->key_sense_irq,
|
||||||
pdata->num_cols, kp, &kypd_sns);
|
pmic8xxx_kp_irq, IRQF_TRIGGER_RISING, "pmic-keypad",
|
||||||
if (rc < 0) {
|
kp);
|
||||||
dev_err(&pdev->dev, "unable to configure keypad sense lines\n");
|
|
||||||
goto err_gpio_config;
|
|
||||||
}
|
|
||||||
|
|
||||||
rc = pmic8xxx_kp_config_gpio(pdata->rows_gpio_start,
|
|
||||||
pdata->num_rows, kp, &kypd_drv);
|
|
||||||
if (rc < 0) {
|
|
||||||
dev_err(&pdev->dev, "unable to configure keypad drive lines\n");
|
|
||||||
goto err_gpio_config;
|
|
||||||
}
|
|
||||||
|
|
||||||
rc = request_any_context_irq(kp->key_sense_irq, pmic8xxx_kp_irq,
|
|
||||||
IRQF_TRIGGER_RISING, "pmic-keypad", kp);
|
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
dev_err(&pdev->dev, "failed to request keypad sense irq\n");
|
dev_err(&pdev->dev, "failed to request keypad sense irq\n");
|
||||||
goto err_get_irq;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
rc = request_any_context_irq(kp->key_stuck_irq, pmic8xxx_kp_stuck_irq,
|
rc = devm_request_any_context_irq(&pdev->dev, kp->key_stuck_irq,
|
||||||
IRQF_TRIGGER_RISING, "pmic-keypad-stuck", kp);
|
pmic8xxx_kp_stuck_irq, IRQF_TRIGGER_RISING,
|
||||||
|
"pmic-keypad-stuck", kp);
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
dev_err(&pdev->dev, "failed to request keypad stuck irq\n");
|
dev_err(&pdev->dev, "failed to request keypad stuck irq\n");
|
||||||
goto err_req_stuck_irq;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
rc = pmic8xxx_kp_read_u8(kp, &ctrl_val, KEYP_CTRL);
|
rc = regmap_read(kp->regmap, KEYP_CTRL, &ctrl_val);
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
dev_err(&pdev->dev, "failed to read KEYP_CTRL register\n");
|
dev_err(&pdev->dev, "failed to read KEYP_CTRL register\n");
|
||||||
goto err_pmic_reg_read;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
kp->ctrl_reg = ctrl_val;
|
kp->ctrl_reg = ctrl_val;
|
||||||
@ -692,34 +623,10 @@ static int pmic8xxx_kp_probe(struct platform_device *pdev)
|
|||||||
rc = input_register_device(kp->input);
|
rc = input_register_device(kp->input);
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
dev_err(&pdev->dev, "unable to register keypad input device\n");
|
dev_err(&pdev->dev, "unable to register keypad input device\n");
|
||||||
goto err_pmic_reg_read;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
device_init_wakeup(&pdev->dev, pdata->wakeup);
|
device_init_wakeup(&pdev->dev, wakeup);
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
err_pmic_reg_read:
|
|
||||||
free_irq(kp->key_stuck_irq, kp);
|
|
||||||
err_req_stuck_irq:
|
|
||||||
free_irq(kp->key_sense_irq, kp);
|
|
||||||
err_gpio_config:
|
|
||||||
err_get_irq:
|
|
||||||
input_free_device(kp->input);
|
|
||||||
err_alloc_device:
|
|
||||||
kfree(kp);
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int pmic8xxx_kp_remove(struct platform_device *pdev)
|
|
||||||
{
|
|
||||||
struct pmic8xxx_kp *kp = platform_get_drvdata(pdev);
|
|
||||||
|
|
||||||
device_init_wakeup(&pdev->dev, 0);
|
|
||||||
free_irq(kp->key_stuck_irq, kp);
|
|
||||||
free_irq(kp->key_sense_irq, kp);
|
|
||||||
input_unregister_device(kp->input);
|
|
||||||
kfree(kp);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -769,13 +676,20 @@ static int pmic8xxx_kp_resume(struct device *dev)
|
|||||||
static SIMPLE_DEV_PM_OPS(pm8xxx_kp_pm_ops,
|
static SIMPLE_DEV_PM_OPS(pm8xxx_kp_pm_ops,
|
||||||
pmic8xxx_kp_suspend, pmic8xxx_kp_resume);
|
pmic8xxx_kp_suspend, pmic8xxx_kp_resume);
|
||||||
|
|
||||||
|
static const struct of_device_id pm8xxx_match_table[] = {
|
||||||
|
{ .compatible = "qcom,pm8058-keypad" },
|
||||||
|
{ .compatible = "qcom,pm8921-keypad" },
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, pm8xxx_match_table);
|
||||||
|
|
||||||
static struct platform_driver pmic8xxx_kp_driver = {
|
static struct platform_driver pmic8xxx_kp_driver = {
|
||||||
.probe = pmic8xxx_kp_probe,
|
.probe = pmic8xxx_kp_probe,
|
||||||
.remove = pmic8xxx_kp_remove,
|
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = PM8XXX_KEYPAD_DEV_NAME,
|
.name = "pm8xxx-keypad",
|
||||||
.owner = THIS_MODULE,
|
.owner = THIS_MODULE,
|
||||||
.pm = &pm8xxx_kp_pm_ops,
|
.pm = &pm8xxx_kp_pm_ops,
|
||||||
|
.of_match_table = pm8xxx_match_table,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
module_platform_driver(pmic8xxx_kp_driver);
|
module_platform_driver(pmic8xxx_kp_driver);
|
||||||
|
@ -1,329 +0,0 @@
|
|||||||
/*
|
|
||||||
* Texas Instruments TNETV107X Keypad Driver
|
|
||||||
*
|
|
||||||
* Copyright (C) 2010 Texas Instruments
|
|
||||||
*
|
|
||||||
* 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 version 2.
|
|
||||||
*
|
|
||||||
* This program is distributed "as is" WITHOUT ANY WARRANTY of any
|
|
||||||
* kind, whether express or implied; without even the implied warranty
|
|
||||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <linux/kernel.h>
|
|
||||||
#include <linux/err.h>
|
|
||||||
#include <linux/errno.h>
|
|
||||||
#include <linux/input.h>
|
|
||||||
#include <linux/platform_device.h>
|
|
||||||
#include <linux/interrupt.h>
|
|
||||||
#include <linux/slab.h>
|
|
||||||
#include <linux/delay.h>
|
|
||||||
#include <linux/io.h>
|
|
||||||
#include <linux/clk.h>
|
|
||||||
#include <linux/input/matrix_keypad.h>
|
|
||||||
#include <linux/module.h>
|
|
||||||
|
|
||||||
#define BITS(x) (BIT(x) - 1)
|
|
||||||
|
|
||||||
#define KEYPAD_ROWS 9
|
|
||||||
#define KEYPAD_COLS 9
|
|
||||||
|
|
||||||
#define DEBOUNCE_MIN 0x400ul
|
|
||||||
#define DEBOUNCE_MAX 0x3ffffffful
|
|
||||||
|
|
||||||
struct keypad_regs {
|
|
||||||
u32 rev;
|
|
||||||
u32 mode;
|
|
||||||
u32 mask;
|
|
||||||
u32 pol;
|
|
||||||
u32 dclock;
|
|
||||||
u32 rclock;
|
|
||||||
u32 stable_cnt;
|
|
||||||
u32 in_en;
|
|
||||||
u32 out;
|
|
||||||
u32 out_en;
|
|
||||||
u32 in;
|
|
||||||
u32 lock;
|
|
||||||
u32 pres[3];
|
|
||||||
};
|
|
||||||
|
|
||||||
#define keypad_read(kp, reg) __raw_readl(&(kp)->regs->reg)
|
|
||||||
#define keypad_write(kp, reg, val) __raw_writel(val, &(kp)->regs->reg)
|
|
||||||
|
|
||||||
struct keypad_data {
|
|
||||||
struct input_dev *input_dev;
|
|
||||||
struct resource *res;
|
|
||||||
struct keypad_regs __iomem *regs;
|
|
||||||
struct clk *clk;
|
|
||||||
struct device *dev;
|
|
||||||
spinlock_t lock;
|
|
||||||
int irq_press;
|
|
||||||
int irq_release;
|
|
||||||
int rows, cols, row_shift;
|
|
||||||
int debounce_ms, active_low;
|
|
||||||
u32 prev_keys[3];
|
|
||||||
unsigned short keycodes[];
|
|
||||||
};
|
|
||||||
|
|
||||||
static irqreturn_t keypad_irq(int irq, void *data)
|
|
||||||
{
|
|
||||||
struct keypad_data *kp = data;
|
|
||||||
int i, bit, val, row, col, code;
|
|
||||||
unsigned long flags;
|
|
||||||
u32 curr_keys[3];
|
|
||||||
u32 change;
|
|
||||||
|
|
||||||
spin_lock_irqsave(&kp->lock, flags);
|
|
||||||
|
|
||||||
memset(curr_keys, 0, sizeof(curr_keys));
|
|
||||||
if (irq == kp->irq_press)
|
|
||||||
for (i = 0; i < 3; i++)
|
|
||||||
curr_keys[i] = keypad_read(kp, pres[i]);
|
|
||||||
|
|
||||||
for (i = 0; i < 3; i++) {
|
|
||||||
change = curr_keys[i] ^ kp->prev_keys[i];
|
|
||||||
|
|
||||||
while (change) {
|
|
||||||
bit = fls(change) - 1;
|
|
||||||
change ^= BIT(bit);
|
|
||||||
val = curr_keys[i] & BIT(bit);
|
|
||||||
bit += i * 32;
|
|
||||||
row = bit / KEYPAD_COLS;
|
|
||||||
col = bit % KEYPAD_COLS;
|
|
||||||
|
|
||||||
code = MATRIX_SCAN_CODE(row, col, kp->row_shift);
|
|
||||||
input_event(kp->input_dev, EV_MSC, MSC_SCAN, code);
|
|
||||||
input_report_key(kp->input_dev, kp->keycodes[code],
|
|
||||||
val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
input_sync(kp->input_dev);
|
|
||||||
memcpy(kp->prev_keys, curr_keys, sizeof(curr_keys));
|
|
||||||
|
|
||||||
if (irq == kp->irq_press)
|
|
||||||
keypad_write(kp, lock, 0); /* Allow hardware updates */
|
|
||||||
|
|
||||||
spin_unlock_irqrestore(&kp->lock, flags);
|
|
||||||
|
|
||||||
return IRQ_HANDLED;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int keypad_start(struct input_dev *dev)
|
|
||||||
{
|
|
||||||
struct keypad_data *kp = input_get_drvdata(dev);
|
|
||||||
unsigned long mask, debounce, clk_rate_khz;
|
|
||||||
unsigned long flags;
|
|
||||||
|
|
||||||
clk_enable(kp->clk);
|
|
||||||
clk_rate_khz = clk_get_rate(kp->clk) / 1000;
|
|
||||||
|
|
||||||
spin_lock_irqsave(&kp->lock, flags);
|
|
||||||
|
|
||||||
/* Initialize device registers */
|
|
||||||
keypad_write(kp, mode, 0);
|
|
||||||
|
|
||||||
mask = BITS(kp->rows) << KEYPAD_COLS;
|
|
||||||
mask |= BITS(kp->cols);
|
|
||||||
keypad_write(kp, mask, ~mask);
|
|
||||||
|
|
||||||
keypad_write(kp, pol, kp->active_low ? 0 : 0x3ffff);
|
|
||||||
keypad_write(kp, stable_cnt, 3);
|
|
||||||
|
|
||||||
debounce = kp->debounce_ms * clk_rate_khz;
|
|
||||||
debounce = clamp(debounce, DEBOUNCE_MIN, DEBOUNCE_MAX);
|
|
||||||
keypad_write(kp, dclock, debounce);
|
|
||||||
keypad_write(kp, rclock, 4 * debounce);
|
|
||||||
|
|
||||||
keypad_write(kp, in_en, 1);
|
|
||||||
|
|
||||||
spin_unlock_irqrestore(&kp->lock, flags);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void keypad_stop(struct input_dev *dev)
|
|
||||||
{
|
|
||||||
struct keypad_data *kp = input_get_drvdata(dev);
|
|
||||||
|
|
||||||
synchronize_irq(kp->irq_press);
|
|
||||||
synchronize_irq(kp->irq_release);
|
|
||||||
clk_disable(kp->clk);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int keypad_probe(struct platform_device *pdev)
|
|
||||||
{
|
|
||||||
const struct matrix_keypad_platform_data *pdata;
|
|
||||||
const struct matrix_keymap_data *keymap_data;
|
|
||||||
struct device *dev = &pdev->dev;
|
|
||||||
struct keypad_data *kp;
|
|
||||||
int error = 0, sz, row_shift;
|
|
||||||
u32 rev = 0;
|
|
||||||
|
|
||||||
pdata = dev_get_platdata(&pdev->dev);
|
|
||||||
if (!pdata) {
|
|
||||||
dev_err(dev, "cannot find device data\n");
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
keymap_data = pdata->keymap_data;
|
|
||||||
if (!keymap_data) {
|
|
||||||
dev_err(dev, "cannot find keymap data\n");
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
row_shift = get_count_order(pdata->num_col_gpios);
|
|
||||||
sz = offsetof(struct keypad_data, keycodes);
|
|
||||||
sz += (pdata->num_row_gpios << row_shift) * sizeof(kp->keycodes[0]);
|
|
||||||
kp = kzalloc(sz, GFP_KERNEL);
|
|
||||||
if (!kp) {
|
|
||||||
dev_err(dev, "cannot allocate device info\n");
|
|
||||||
return -ENOMEM;
|
|
||||||
}
|
|
||||||
|
|
||||||
kp->dev = dev;
|
|
||||||
kp->rows = pdata->num_row_gpios;
|
|
||||||
kp->cols = pdata->num_col_gpios;
|
|
||||||
kp->row_shift = row_shift;
|
|
||||||
platform_set_drvdata(pdev, kp);
|
|
||||||
spin_lock_init(&kp->lock);
|
|
||||||
|
|
||||||
kp->irq_press = platform_get_irq_byname(pdev, "press");
|
|
||||||
kp->irq_release = platform_get_irq_byname(pdev, "release");
|
|
||||||
if (kp->irq_press < 0 || kp->irq_release < 0) {
|
|
||||||
dev_err(dev, "cannot determine device interrupts\n");
|
|
||||||
error = -ENODEV;
|
|
||||||
goto error_res;
|
|
||||||
}
|
|
||||||
|
|
||||||
kp->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
||||||
if (!kp->res) {
|
|
||||||
dev_err(dev, "cannot determine register area\n");
|
|
||||||
error = -ENODEV;
|
|
||||||
goto error_res;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!request_mem_region(kp->res->start, resource_size(kp->res),
|
|
||||||
pdev->name)) {
|
|
||||||
dev_err(dev, "cannot claim register memory\n");
|
|
||||||
kp->res = NULL;
|
|
||||||
error = -EINVAL;
|
|
||||||
goto error_res;
|
|
||||||
}
|
|
||||||
|
|
||||||
kp->regs = ioremap(kp->res->start, resource_size(kp->res));
|
|
||||||
if (!kp->regs) {
|
|
||||||
dev_err(dev, "cannot map register memory\n");
|
|
||||||
error = -ENOMEM;
|
|
||||||
goto error_map;
|
|
||||||
}
|
|
||||||
|
|
||||||
kp->clk = clk_get(dev, NULL);
|
|
||||||
if (IS_ERR(kp->clk)) {
|
|
||||||
dev_err(dev, "cannot claim device clock\n");
|
|
||||||
error = PTR_ERR(kp->clk);
|
|
||||||
goto error_clk;
|
|
||||||
}
|
|
||||||
|
|
||||||
error = request_threaded_irq(kp->irq_press, NULL, keypad_irq,
|
|
||||||
IRQF_ONESHOT, dev_name(dev), kp);
|
|
||||||
if (error < 0) {
|
|
||||||
dev_err(kp->dev, "Could not allocate keypad press key irq\n");
|
|
||||||
goto error_irq_press;
|
|
||||||
}
|
|
||||||
|
|
||||||
error = request_threaded_irq(kp->irq_release, NULL, keypad_irq,
|
|
||||||
IRQF_ONESHOT, dev_name(dev), kp);
|
|
||||||
if (error < 0) {
|
|
||||||
dev_err(kp->dev, "Could not allocate keypad release key irq\n");
|
|
||||||
goto error_irq_release;
|
|
||||||
}
|
|
||||||
|
|
||||||
kp->input_dev = input_allocate_device();
|
|
||||||
if (!kp->input_dev) {
|
|
||||||
dev_err(dev, "cannot allocate input device\n");
|
|
||||||
error = -ENOMEM;
|
|
||||||
goto error_input;
|
|
||||||
}
|
|
||||||
|
|
||||||
kp->input_dev->name = pdev->name;
|
|
||||||
kp->input_dev->dev.parent = &pdev->dev;
|
|
||||||
kp->input_dev->open = keypad_start;
|
|
||||||
kp->input_dev->close = keypad_stop;
|
|
||||||
|
|
||||||
clk_enable(kp->clk);
|
|
||||||
rev = keypad_read(kp, rev);
|
|
||||||
kp->input_dev->id.bustype = BUS_HOST;
|
|
||||||
kp->input_dev->id.product = ((rev >> 8) & 0x07);
|
|
||||||
kp->input_dev->id.version = ((rev >> 16) & 0xfff);
|
|
||||||
clk_disable(kp->clk);
|
|
||||||
|
|
||||||
error = matrix_keypad_build_keymap(keymap_data, NULL,
|
|
||||||
kp->rows, kp->cols,
|
|
||||||
kp->keycodes, kp->input_dev);
|
|
||||||
if (error) {
|
|
||||||
dev_err(dev, "Failed to build keymap\n");
|
|
||||||
goto error_reg;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!pdata->no_autorepeat)
|
|
||||||
kp->input_dev->evbit[0] |= BIT_MASK(EV_REP);
|
|
||||||
input_set_capability(kp->input_dev, EV_MSC, MSC_SCAN);
|
|
||||||
|
|
||||||
input_set_drvdata(kp->input_dev, kp);
|
|
||||||
|
|
||||||
error = input_register_device(kp->input_dev);
|
|
||||||
if (error < 0) {
|
|
||||||
dev_err(dev, "Could not register input device\n");
|
|
||||||
goto error_reg;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
|
|
||||||
error_reg:
|
|
||||||
input_free_device(kp->input_dev);
|
|
||||||
error_input:
|
|
||||||
free_irq(kp->irq_release, kp);
|
|
||||||
error_irq_release:
|
|
||||||
free_irq(kp->irq_press, kp);
|
|
||||||
error_irq_press:
|
|
||||||
clk_put(kp->clk);
|
|
||||||
error_clk:
|
|
||||||
iounmap(kp->regs);
|
|
||||||
error_map:
|
|
||||||
release_mem_region(kp->res->start, resource_size(kp->res));
|
|
||||||
error_res:
|
|
||||||
kfree(kp);
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int keypad_remove(struct platform_device *pdev)
|
|
||||||
{
|
|
||||||
struct keypad_data *kp = platform_get_drvdata(pdev);
|
|
||||||
|
|
||||||
free_irq(kp->irq_press, kp);
|
|
||||||
free_irq(kp->irq_release, kp);
|
|
||||||
input_unregister_device(kp->input_dev);
|
|
||||||
clk_put(kp->clk);
|
|
||||||
iounmap(kp->regs);
|
|
||||||
release_mem_region(kp->res->start, resource_size(kp->res));
|
|
||||||
kfree(kp);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct platform_driver keypad_driver = {
|
|
||||||
.probe = keypad_probe,
|
|
||||||
.remove = keypad_remove,
|
|
||||||
.driver.name = "tnetv107x-keypad",
|
|
||||||
.driver.owner = THIS_MODULE,
|
|
||||||
};
|
|
||||||
module_platform_driver(keypad_driver);
|
|
||||||
|
|
||||||
MODULE_AUTHOR("Cyril Chemparathy");
|
|
||||||
MODULE_DESCRIPTION("TNETV107X Keypad Driver");
|
|
||||||
MODULE_ALIAS("platform:tnetv107x-keypad");
|
|
||||||
MODULE_LICENSE("GPL");
|
|
@ -269,7 +269,7 @@ config INPUT_COBALT_BTNS
|
|||||||
|
|
||||||
config INPUT_WISTRON_BTNS
|
config INPUT_WISTRON_BTNS
|
||||||
tristate "x86 Wistron laptop button interface"
|
tristate "x86 Wistron laptop button interface"
|
||||||
depends on X86 && !X86_64
|
depends on X86_32
|
||||||
select INPUT_POLLDEV
|
select INPUT_POLLDEV
|
||||||
select INPUT_SPARSEKMAP
|
select INPUT_SPARSEKMAP
|
||||||
select NEW_LEDS
|
select NEW_LEDS
|
||||||
@ -666,4 +666,14 @@ config INPUT_IDEAPAD_SLIDEBAR
|
|||||||
To compile this driver as a module, choose M here: the
|
To compile this driver as a module, choose M here: the
|
||||||
module will be called ideapad_slidebar.
|
module will be called ideapad_slidebar.
|
||||||
|
|
||||||
|
config INPUT_SOC_BUTTON_ARRAY
|
||||||
|
tristate "Windows-compatible SoC Button Array"
|
||||||
|
depends on KEYBOARD_GPIO
|
||||||
|
help
|
||||||
|
Say Y here if you have a SoC-based tablet that originally
|
||||||
|
runs Windows 8.
|
||||||
|
|
||||||
|
To compile this driver as a module, choose M here: the
|
||||||
|
module will be called soc_button_array.
|
||||||
|
|
||||||
endif
|
endif
|
||||||
|
@ -53,6 +53,7 @@ obj-$(CONFIG_INPUT_RETU_PWRBUTTON) += retu-pwrbutton.o
|
|||||||
obj-$(CONFIG_INPUT_GPIO_ROTARY_ENCODER) += rotary_encoder.o
|
obj-$(CONFIG_INPUT_GPIO_ROTARY_ENCODER) += rotary_encoder.o
|
||||||
obj-$(CONFIG_INPUT_SGI_BTNS) += sgi_btns.o
|
obj-$(CONFIG_INPUT_SGI_BTNS) += sgi_btns.o
|
||||||
obj-$(CONFIG_INPUT_SIRFSOC_ONKEY) += sirfsoc-onkey.o
|
obj-$(CONFIG_INPUT_SIRFSOC_ONKEY) += sirfsoc-onkey.o
|
||||||
|
obj-$(CONFIG_INPUT_SOC_BUTTON_ARRAY) += soc_button_array.o
|
||||||
obj-$(CONFIG_INPUT_SPARCSPKR) += sparcspkr.o
|
obj-$(CONFIG_INPUT_SPARCSPKR) += sparcspkr.o
|
||||||
obj-$(CONFIG_INPUT_TWL4030_PWRBUTTON) += twl4030-pwrbutton.o
|
obj-$(CONFIG_INPUT_TWL4030_PWRBUTTON) += twl4030-pwrbutton.o
|
||||||
obj-$(CONFIG_INPUT_TWL4030_VIBRA) += twl4030-vibra.o
|
obj-$(CONFIG_INPUT_TWL4030_VIBRA) += twl4030-vibra.o
|
||||||
|
@ -51,6 +51,8 @@ struct ims_pcu_backlight {
|
|||||||
#define IMS_PCU_BL_VERSION_LEN (9 + 1)
|
#define IMS_PCU_BL_VERSION_LEN (9 + 1)
|
||||||
#define IMS_PCU_BL_RESET_REASON_LEN (2 + 1)
|
#define IMS_PCU_BL_RESET_REASON_LEN (2 + 1)
|
||||||
|
|
||||||
|
#define IMS_PCU_PCU_B_DEVICE_ID 5
|
||||||
|
|
||||||
#define IMS_PCU_BUF_SIZE 128
|
#define IMS_PCU_BUF_SIZE 128
|
||||||
|
|
||||||
struct ims_pcu {
|
struct ims_pcu {
|
||||||
@ -68,6 +70,9 @@ struct ims_pcu {
|
|||||||
char bl_version[IMS_PCU_BL_VERSION_LEN];
|
char bl_version[IMS_PCU_BL_VERSION_LEN];
|
||||||
char reset_reason[IMS_PCU_BL_RESET_REASON_LEN];
|
char reset_reason[IMS_PCU_BL_RESET_REASON_LEN];
|
||||||
int update_firmware_status;
|
int update_firmware_status;
|
||||||
|
u8 device_id;
|
||||||
|
|
||||||
|
u8 ofn_reg_addr;
|
||||||
|
|
||||||
struct usb_interface *ctrl_intf;
|
struct usb_interface *ctrl_intf;
|
||||||
|
|
||||||
@ -371,6 +376,8 @@ static void ims_pcu_destroy_gamepad(struct ims_pcu *pcu)
|
|||||||
#define IMS_PCU_CMD_GET_DEVICE_ID 0xae
|
#define IMS_PCU_CMD_GET_DEVICE_ID 0xae
|
||||||
#define IMS_PCU_CMD_SPECIAL_INFO 0xb0
|
#define IMS_PCU_CMD_SPECIAL_INFO 0xb0
|
||||||
#define IMS_PCU_CMD_BOOTLOADER 0xb1 /* Pass data to bootloader */
|
#define IMS_PCU_CMD_BOOTLOADER 0xb1 /* Pass data to bootloader */
|
||||||
|
#define IMS_PCU_CMD_OFN_SET_CONFIG 0xb3
|
||||||
|
#define IMS_PCU_CMD_OFN_GET_CONFIG 0xb4
|
||||||
|
|
||||||
/* PCU responses */
|
/* PCU responses */
|
||||||
#define IMS_PCU_RSP_STATUS 0xc0
|
#define IMS_PCU_RSP_STATUS 0xc0
|
||||||
@ -389,6 +396,9 @@ static void ims_pcu_destroy_gamepad(struct ims_pcu *pcu)
|
|||||||
#define IMS_PCU_RSP_GET_DEVICE_ID 0xce
|
#define IMS_PCU_RSP_GET_DEVICE_ID 0xce
|
||||||
#define IMS_PCU_RSP_SPECIAL_INFO 0xd0
|
#define IMS_PCU_RSP_SPECIAL_INFO 0xd0
|
||||||
#define IMS_PCU_RSP_BOOTLOADER 0xd1 /* Bootloader response */
|
#define IMS_PCU_RSP_BOOTLOADER 0xd1 /* Bootloader response */
|
||||||
|
#define IMS_PCU_RSP_OFN_SET_CONFIG 0xd2
|
||||||
|
#define IMS_PCU_RSP_OFN_GET_CONFIG 0xd3
|
||||||
|
|
||||||
|
|
||||||
#define IMS_PCU_RSP_EVNT_BUTTONS 0xe0 /* Unsolicited, button state */
|
#define IMS_PCU_RSP_EVNT_BUTTONS 0xe0 /* Unsolicited, button state */
|
||||||
#define IMS_PCU_GAMEPAD_MASK 0x0001ff80UL /* Bits 7 through 16 */
|
#define IMS_PCU_GAMEPAD_MASK 0x0001ff80UL /* Bits 7 through 16 */
|
||||||
@ -1256,6 +1266,225 @@ static struct attribute_group ims_pcu_attr_group = {
|
|||||||
.attrs = ims_pcu_attrs,
|
.attrs = ims_pcu_attrs,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Support for a separate OFN attribute group */
|
||||||
|
|
||||||
|
#define OFN_REG_RESULT_OFFSET 2
|
||||||
|
|
||||||
|
static int ims_pcu_read_ofn_config(struct ims_pcu *pcu, u8 addr, u8 *data)
|
||||||
|
{
|
||||||
|
int error;
|
||||||
|
s16 result;
|
||||||
|
|
||||||
|
error = ims_pcu_execute_command(pcu, OFN_GET_CONFIG,
|
||||||
|
&addr, sizeof(addr));
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
result = (s16)get_unaligned_le16(pcu->cmd_buf + OFN_REG_RESULT_OFFSET);
|
||||||
|
if (result < 0)
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
|
/* We only need LSB */
|
||||||
|
*data = pcu->cmd_buf[OFN_REG_RESULT_OFFSET];
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ims_pcu_write_ofn_config(struct ims_pcu *pcu, u8 addr, u8 data)
|
||||||
|
{
|
||||||
|
u8 buffer[] = { addr, data };
|
||||||
|
int error;
|
||||||
|
s16 result;
|
||||||
|
|
||||||
|
error = ims_pcu_execute_command(pcu, OFN_SET_CONFIG,
|
||||||
|
&buffer, sizeof(buffer));
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
result = (s16)get_unaligned_le16(pcu->cmd_buf + OFN_REG_RESULT_OFFSET);
|
||||||
|
if (result < 0)
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t ims_pcu_ofn_reg_data_show(struct device *dev,
|
||||||
|
struct device_attribute *dattr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
struct usb_interface *intf = to_usb_interface(dev);
|
||||||
|
struct ims_pcu *pcu = usb_get_intfdata(intf);
|
||||||
|
int error;
|
||||||
|
u8 data;
|
||||||
|
|
||||||
|
mutex_lock(&pcu->cmd_mutex);
|
||||||
|
error = ims_pcu_read_ofn_config(pcu, pcu->ofn_reg_addr, &data);
|
||||||
|
mutex_unlock(&pcu->cmd_mutex);
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
return scnprintf(buf, PAGE_SIZE, "%x\n", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t ims_pcu_ofn_reg_data_store(struct device *dev,
|
||||||
|
struct device_attribute *dattr,
|
||||||
|
const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
struct usb_interface *intf = to_usb_interface(dev);
|
||||||
|
struct ims_pcu *pcu = usb_get_intfdata(intf);
|
||||||
|
int error;
|
||||||
|
u8 value;
|
||||||
|
|
||||||
|
error = kstrtou8(buf, 0, &value);
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
mutex_lock(&pcu->cmd_mutex);
|
||||||
|
error = ims_pcu_write_ofn_config(pcu, pcu->ofn_reg_addr, value);
|
||||||
|
mutex_unlock(&pcu->cmd_mutex);
|
||||||
|
|
||||||
|
return error ?: count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DEVICE_ATTR(reg_data, S_IRUGO | S_IWUSR,
|
||||||
|
ims_pcu_ofn_reg_data_show, ims_pcu_ofn_reg_data_store);
|
||||||
|
|
||||||
|
static ssize_t ims_pcu_ofn_reg_addr_show(struct device *dev,
|
||||||
|
struct device_attribute *dattr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
struct usb_interface *intf = to_usb_interface(dev);
|
||||||
|
struct ims_pcu *pcu = usb_get_intfdata(intf);
|
||||||
|
int error;
|
||||||
|
|
||||||
|
mutex_lock(&pcu->cmd_mutex);
|
||||||
|
error = scnprintf(buf, PAGE_SIZE, "%x\n", pcu->ofn_reg_addr);
|
||||||
|
mutex_unlock(&pcu->cmd_mutex);
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t ims_pcu_ofn_reg_addr_store(struct device *dev,
|
||||||
|
struct device_attribute *dattr,
|
||||||
|
const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
struct usb_interface *intf = to_usb_interface(dev);
|
||||||
|
struct ims_pcu *pcu = usb_get_intfdata(intf);
|
||||||
|
int error;
|
||||||
|
u8 value;
|
||||||
|
|
||||||
|
error = kstrtou8(buf, 0, &value);
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
mutex_lock(&pcu->cmd_mutex);
|
||||||
|
pcu->ofn_reg_addr = value;
|
||||||
|
mutex_unlock(&pcu->cmd_mutex);
|
||||||
|
|
||||||
|
return error ?: count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DEVICE_ATTR(reg_addr, S_IRUGO | S_IWUSR,
|
||||||
|
ims_pcu_ofn_reg_addr_show, ims_pcu_ofn_reg_addr_store);
|
||||||
|
|
||||||
|
struct ims_pcu_ofn_bit_attribute {
|
||||||
|
struct device_attribute dattr;
|
||||||
|
u8 addr;
|
||||||
|
u8 nr;
|
||||||
|
};
|
||||||
|
|
||||||
|
static ssize_t ims_pcu_ofn_bit_show(struct device *dev,
|
||||||
|
struct device_attribute *dattr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
struct usb_interface *intf = to_usb_interface(dev);
|
||||||
|
struct ims_pcu *pcu = usb_get_intfdata(intf);
|
||||||
|
struct ims_pcu_ofn_bit_attribute *attr =
|
||||||
|
container_of(dattr, struct ims_pcu_ofn_bit_attribute, dattr);
|
||||||
|
int error;
|
||||||
|
u8 data;
|
||||||
|
|
||||||
|
mutex_lock(&pcu->cmd_mutex);
|
||||||
|
error = ims_pcu_read_ofn_config(pcu, attr->addr, &data);
|
||||||
|
mutex_unlock(&pcu->cmd_mutex);
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
return scnprintf(buf, PAGE_SIZE, "%d\n", !!(data & (1 << attr->nr)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t ims_pcu_ofn_bit_store(struct device *dev,
|
||||||
|
struct device_attribute *dattr,
|
||||||
|
const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
struct usb_interface *intf = to_usb_interface(dev);
|
||||||
|
struct ims_pcu *pcu = usb_get_intfdata(intf);
|
||||||
|
struct ims_pcu_ofn_bit_attribute *attr =
|
||||||
|
container_of(dattr, struct ims_pcu_ofn_bit_attribute, dattr);
|
||||||
|
int error;
|
||||||
|
int value;
|
||||||
|
u8 data;
|
||||||
|
|
||||||
|
error = kstrtoint(buf, 0, &value);
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
if (value > 1)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
mutex_lock(&pcu->cmd_mutex);
|
||||||
|
|
||||||
|
error = ims_pcu_read_ofn_config(pcu, attr->addr, &data);
|
||||||
|
if (!error) {
|
||||||
|
if (value)
|
||||||
|
data |= 1U << attr->nr;
|
||||||
|
else
|
||||||
|
data &= ~(1U << attr->nr);
|
||||||
|
|
||||||
|
error = ims_pcu_write_ofn_config(pcu, attr->addr, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_unlock(&pcu->cmd_mutex);
|
||||||
|
|
||||||
|
return error ?: count;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define IMS_PCU_OFN_BIT_ATTR(_field, _addr, _nr) \
|
||||||
|
struct ims_pcu_ofn_bit_attribute ims_pcu_ofn_attr_##_field = { \
|
||||||
|
.dattr = __ATTR(_field, S_IWUSR | S_IRUGO, \
|
||||||
|
ims_pcu_ofn_bit_show, ims_pcu_ofn_bit_store), \
|
||||||
|
.addr = _addr, \
|
||||||
|
.nr = _nr, \
|
||||||
|
}
|
||||||
|
|
||||||
|
static IMS_PCU_OFN_BIT_ATTR(engine_enable, 0x60, 7);
|
||||||
|
static IMS_PCU_OFN_BIT_ATTR(speed_enable, 0x60, 6);
|
||||||
|
static IMS_PCU_OFN_BIT_ATTR(assert_enable, 0x60, 5);
|
||||||
|
static IMS_PCU_OFN_BIT_ATTR(xyquant_enable, 0x60, 4);
|
||||||
|
static IMS_PCU_OFN_BIT_ATTR(xyscale_enable, 0x60, 1);
|
||||||
|
|
||||||
|
static IMS_PCU_OFN_BIT_ATTR(scale_x2, 0x63, 6);
|
||||||
|
static IMS_PCU_OFN_BIT_ATTR(scale_y2, 0x63, 7);
|
||||||
|
|
||||||
|
static struct attribute *ims_pcu_ofn_attrs[] = {
|
||||||
|
&dev_attr_reg_data.attr,
|
||||||
|
&dev_attr_reg_addr.attr,
|
||||||
|
&ims_pcu_ofn_attr_engine_enable.dattr.attr,
|
||||||
|
&ims_pcu_ofn_attr_speed_enable.dattr.attr,
|
||||||
|
&ims_pcu_ofn_attr_assert_enable.dattr.attr,
|
||||||
|
&ims_pcu_ofn_attr_xyquant_enable.dattr.attr,
|
||||||
|
&ims_pcu_ofn_attr_xyscale_enable.dattr.attr,
|
||||||
|
&ims_pcu_ofn_attr_scale_x2.dattr.attr,
|
||||||
|
&ims_pcu_ofn_attr_scale_y2.dattr.attr,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct attribute_group ims_pcu_ofn_attr_group = {
|
||||||
|
.name = "ofn",
|
||||||
|
.attrs = ims_pcu_ofn_attrs,
|
||||||
|
};
|
||||||
|
|
||||||
static void ims_pcu_irq(struct urb *urb)
|
static void ims_pcu_irq(struct urb *urb)
|
||||||
{
|
{
|
||||||
struct ims_pcu *pcu = urb->context;
|
struct ims_pcu *pcu = urb->context;
|
||||||
@ -1624,7 +1853,6 @@ static int ims_pcu_init_application_mode(struct ims_pcu *pcu)
|
|||||||
static atomic_t device_no = ATOMIC_INIT(0);
|
static atomic_t device_no = ATOMIC_INIT(0);
|
||||||
|
|
||||||
const struct ims_pcu_device_info *info;
|
const struct ims_pcu_device_info *info;
|
||||||
u8 device_id;
|
|
||||||
int error;
|
int error;
|
||||||
|
|
||||||
error = ims_pcu_get_device_info(pcu);
|
error = ims_pcu_get_device_info(pcu);
|
||||||
@ -1633,7 +1861,7 @@ static int ims_pcu_init_application_mode(struct ims_pcu *pcu)
|
|||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
error = ims_pcu_identify_type(pcu, &device_id);
|
error = ims_pcu_identify_type(pcu, &pcu->device_id);
|
||||||
if (error) {
|
if (error) {
|
||||||
dev_err(pcu->dev,
|
dev_err(pcu->dev,
|
||||||
"Failed to identify device, error: %d\n", error);
|
"Failed to identify device, error: %d\n", error);
|
||||||
@ -1645,9 +1873,9 @@ static int ims_pcu_init_application_mode(struct ims_pcu *pcu)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (device_id >= ARRAY_SIZE(ims_pcu_device_info) ||
|
if (pcu->device_id >= ARRAY_SIZE(ims_pcu_device_info) ||
|
||||||
!ims_pcu_device_info[device_id].keymap) {
|
!ims_pcu_device_info[pcu->device_id].keymap) {
|
||||||
dev_err(pcu->dev, "Device ID %d is not valid\n", device_id);
|
dev_err(pcu->dev, "Device ID %d is not valid\n", pcu->device_id);
|
||||||
/* Same as above, punt to userspace */
|
/* Same as above, punt to userspace */
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -1655,11 +1883,21 @@ static int ims_pcu_init_application_mode(struct ims_pcu *pcu)
|
|||||||
/* Device appears to be operable, complete initialization */
|
/* Device appears to be operable, complete initialization */
|
||||||
pcu->device_no = atomic_inc_return(&device_no) - 1;
|
pcu->device_no = atomic_inc_return(&device_no) - 1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PCU-B devices, both GEN_1 and GEN_2 do not have OFN sensor
|
||||||
|
*/
|
||||||
|
if (pcu->device_id != IMS_PCU_PCU_B_DEVICE_ID) {
|
||||||
|
error = sysfs_create_group(&pcu->dev->kobj,
|
||||||
|
&ims_pcu_ofn_attr_group);
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
error = ims_pcu_setup_backlight(pcu);
|
error = ims_pcu_setup_backlight(pcu);
|
||||||
if (error)
|
if (error)
|
||||||
return error;
|
return error;
|
||||||
|
|
||||||
info = &ims_pcu_device_info[device_id];
|
info = &ims_pcu_device_info[pcu->device_id];
|
||||||
error = ims_pcu_setup_buttons(pcu, info->keymap, info->keymap_len);
|
error = ims_pcu_setup_buttons(pcu, info->keymap, info->keymap_len);
|
||||||
if (error)
|
if (error)
|
||||||
goto err_destroy_backlight;
|
goto err_destroy_backlight;
|
||||||
@ -1674,10 +1912,10 @@ static int ims_pcu_init_application_mode(struct ims_pcu *pcu)
|
|||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
err_destroy_backlight:
|
|
||||||
ims_pcu_destroy_backlight(pcu);
|
|
||||||
err_destroy_buttons:
|
err_destroy_buttons:
|
||||||
ims_pcu_destroy_buttons(pcu);
|
ims_pcu_destroy_buttons(pcu);
|
||||||
|
err_destroy_backlight:
|
||||||
|
ims_pcu_destroy_backlight(pcu);
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1691,6 +1929,10 @@ static void ims_pcu_destroy_application_mode(struct ims_pcu *pcu)
|
|||||||
ims_pcu_destroy_gamepad(pcu);
|
ims_pcu_destroy_gamepad(pcu);
|
||||||
ims_pcu_destroy_buttons(pcu);
|
ims_pcu_destroy_buttons(pcu);
|
||||||
ims_pcu_destroy_backlight(pcu);
|
ims_pcu_destroy_backlight(pcu);
|
||||||
|
|
||||||
|
if (pcu->device_id != IMS_PCU_PCU_B_DEVICE_ID)
|
||||||
|
sysfs_remove_group(&pcu->dev->kobj,
|
||||||
|
&ims_pcu_ofn_attr_group);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,7 +142,6 @@ static int pm8xxx_vib_play_effect(struct input_dev *dev, void *data,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int pm8xxx_vib_probe(struct platform_device *pdev)
|
static int pm8xxx_vib_probe(struct platform_device *pdev)
|
||||||
|
|
||||||
{
|
{
|
||||||
struct pm8xxx_vib *vib;
|
struct pm8xxx_vib *vib;
|
||||||
struct input_dev *input_dev;
|
struct input_dev *input_dev;
|
||||||
@ -214,12 +213,20 @@ static int pm8xxx_vib_suspend(struct device *dev)
|
|||||||
|
|
||||||
static SIMPLE_DEV_PM_OPS(pm8xxx_vib_pm_ops, pm8xxx_vib_suspend, NULL);
|
static SIMPLE_DEV_PM_OPS(pm8xxx_vib_pm_ops, pm8xxx_vib_suspend, NULL);
|
||||||
|
|
||||||
|
static const struct of_device_id pm8xxx_vib_id_table[] = {
|
||||||
|
{ .compatible = "qcom,pm8058-vib" },
|
||||||
|
{ .compatible = "qcom,pm8921-vib" },
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, pm8xxx_vib_id_table);
|
||||||
|
|
||||||
static struct platform_driver pm8xxx_vib_driver = {
|
static struct platform_driver pm8xxx_vib_driver = {
|
||||||
.probe = pm8xxx_vib_probe,
|
.probe = pm8xxx_vib_probe,
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = "pm8xxx-vib",
|
.name = "pm8xxx-vib",
|
||||||
.owner = THIS_MODULE,
|
.owner = THIS_MODULE,
|
||||||
.pm = &pm8xxx_vib_pm_ops,
|
.pm = &pm8xxx_vib_pm_ops,
|
||||||
|
.of_match_table = pm8xxx_vib_id_table,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
module_platform_driver(pm8xxx_vib_driver);
|
module_platform_driver(pm8xxx_vib_driver);
|
||||||
|
@ -19,8 +19,7 @@
|
|||||||
#include <linux/platform_device.h>
|
#include <linux/platform_device.h>
|
||||||
#include <linux/regmap.h>
|
#include <linux/regmap.h>
|
||||||
#include <linux/log2.h>
|
#include <linux/log2.h>
|
||||||
|
#include <linux/of.h>
|
||||||
#include <linux/input/pmic8xxx-pwrkey.h>
|
|
||||||
|
|
||||||
#define PON_CNTL_1 0x1C
|
#define PON_CNTL_1 0x1C
|
||||||
#define PON_CNTL_PULL_UP BIT(7)
|
#define PON_CNTL_PULL_UP BIT(7)
|
||||||
@ -89,15 +88,15 @@ static int pmic8xxx_pwrkey_probe(struct platform_device *pdev)
|
|||||||
unsigned int pon_cntl;
|
unsigned int pon_cntl;
|
||||||
struct regmap *regmap;
|
struct regmap *regmap;
|
||||||
struct pmic8xxx_pwrkey *pwrkey;
|
struct pmic8xxx_pwrkey *pwrkey;
|
||||||
const struct pm8xxx_pwrkey_platform_data *pdata =
|
u32 kpd_delay;
|
||||||
dev_get_platdata(&pdev->dev);
|
bool pull_up;
|
||||||
|
|
||||||
if (!pdata) {
|
if (of_property_read_u32(pdev->dev.of_node, "debounce", &kpd_delay))
|
||||||
dev_err(&pdev->dev, "power key platform data not supplied\n");
|
kpd_delay = 0;
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pdata->kpd_trigger_delay_us > 62500) {
|
pull_up = of_property_read_bool(pdev->dev.of_node, "pull-up");
|
||||||
|
|
||||||
|
if (kpd_delay > 62500) {
|
||||||
dev_err(&pdev->dev, "invalid power key trigger delay\n");
|
dev_err(&pdev->dev, "invalid power key trigger delay\n");
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
@ -125,7 +124,7 @@ static int pmic8xxx_pwrkey_probe(struct platform_device *pdev)
|
|||||||
pwr->name = "pmic8xxx_pwrkey";
|
pwr->name = "pmic8xxx_pwrkey";
|
||||||
pwr->phys = "pmic8xxx_pwrkey/input0";
|
pwr->phys = "pmic8xxx_pwrkey/input0";
|
||||||
|
|
||||||
delay = (pdata->kpd_trigger_delay_us << 10) / USEC_PER_SEC;
|
delay = (kpd_delay << 10) / USEC_PER_SEC;
|
||||||
delay = 1 + ilog2(delay);
|
delay = 1 + ilog2(delay);
|
||||||
|
|
||||||
err = regmap_read(regmap, PON_CNTL_1, &pon_cntl);
|
err = regmap_read(regmap, PON_CNTL_1, &pon_cntl);
|
||||||
@ -136,7 +135,7 @@ static int pmic8xxx_pwrkey_probe(struct platform_device *pdev)
|
|||||||
|
|
||||||
pon_cntl &= ~PON_CNTL_TRIG_DELAY_MASK;
|
pon_cntl &= ~PON_CNTL_TRIG_DELAY_MASK;
|
||||||
pon_cntl |= (delay & PON_CNTL_TRIG_DELAY_MASK);
|
pon_cntl |= (delay & PON_CNTL_TRIG_DELAY_MASK);
|
||||||
if (pdata->pull_up)
|
if (pull_up)
|
||||||
pon_cntl |= PON_CNTL_PULL_UP;
|
pon_cntl |= PON_CNTL_PULL_UP;
|
||||||
else
|
else
|
||||||
pon_cntl &= ~PON_CNTL_PULL_UP;
|
pon_cntl &= ~PON_CNTL_PULL_UP;
|
||||||
@ -172,7 +171,7 @@ static int pmic8xxx_pwrkey_probe(struct platform_device *pdev)
|
|||||||
}
|
}
|
||||||
|
|
||||||
platform_set_drvdata(pdev, pwrkey);
|
platform_set_drvdata(pdev, pwrkey);
|
||||||
device_init_wakeup(&pdev->dev, pdata->wakeup);
|
device_init_wakeup(&pdev->dev, 1);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -184,13 +183,21 @@ static int pmic8xxx_pwrkey_remove(struct platform_device *pdev)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id pm8xxx_pwr_key_id_table[] = {
|
||||||
|
{ .compatible = "qcom,pm8058-pwrkey" },
|
||||||
|
{ .compatible = "qcom,pm8921-pwrkey" },
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, pm8xxx_pwr_key_id_table);
|
||||||
|
|
||||||
static struct platform_driver pmic8xxx_pwrkey_driver = {
|
static struct platform_driver pmic8xxx_pwrkey_driver = {
|
||||||
.probe = pmic8xxx_pwrkey_probe,
|
.probe = pmic8xxx_pwrkey_probe,
|
||||||
.remove = pmic8xxx_pwrkey_remove,
|
.remove = pmic8xxx_pwrkey_remove,
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = PM8XXX_PWRKEY_DEV_NAME,
|
.name = "pm8xxx-pwrkey",
|
||||||
.owner = THIS_MODULE,
|
.owner = THIS_MODULE,
|
||||||
.pm = &pm8xxx_pwr_key_pm_ops,
|
.pm = &pm8xxx_pwr_key_pm_ops,
|
||||||
|
.of_match_table = pm8xxx_pwr_key_id_table,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
module_platform_driver(pmic8xxx_pwrkey_driver);
|
module_platform_driver(pmic8xxx_pwrkey_driver);
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* Power key driver for SiRF PrimaII
|
* Power key driver for SiRF PrimaII
|
||||||
*
|
*
|
||||||
* Copyright (c) 2013 Cambridge Silicon Radio Limited, a CSR plc group company.
|
* Copyright (c) 2013 - 2014 Cambridge Silicon Radio Limited, a CSR plc group
|
||||||
|
* company.
|
||||||
*
|
*
|
||||||
* Licensed under GPLv2 or later.
|
* Licensed under GPLv2 or later.
|
||||||
*/
|
*/
|
||||||
@ -13,16 +14,41 @@
|
|||||||
#include <linux/input.h>
|
#include <linux/input.h>
|
||||||
#include <linux/rtc/sirfsoc_rtciobrg.h>
|
#include <linux/rtc/sirfsoc_rtciobrg.h>
|
||||||
#include <linux/of.h>
|
#include <linux/of.h>
|
||||||
|
#include <linux/workqueue.h>
|
||||||
|
|
||||||
struct sirfsoc_pwrc_drvdata {
|
struct sirfsoc_pwrc_drvdata {
|
||||||
u32 pwrc_base;
|
u32 pwrc_base;
|
||||||
struct input_dev *input;
|
struct input_dev *input;
|
||||||
|
struct delayed_work work;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define PWRC_ON_KEY_BIT (1 << 0)
|
#define PWRC_ON_KEY_BIT (1 << 0)
|
||||||
|
|
||||||
#define PWRC_INT_STATUS 0xc
|
#define PWRC_INT_STATUS 0xc
|
||||||
#define PWRC_INT_MASK 0x10
|
#define PWRC_INT_MASK 0x10
|
||||||
|
#define PWRC_PIN_STATUS 0x14
|
||||||
|
#define PWRC_KEY_DETECT_UP_TIME 20 /* ms*/
|
||||||
|
|
||||||
|
static int sirfsoc_pwrc_is_on_key_down(struct sirfsoc_pwrc_drvdata *pwrcdrv)
|
||||||
|
{
|
||||||
|
u32 state = sirfsoc_rtc_iobrg_readl(pwrcdrv->pwrc_base +
|
||||||
|
PWRC_PIN_STATUS);
|
||||||
|
return !(state & PWRC_ON_KEY_BIT); /* ON_KEY is active low */
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sirfsoc_pwrc_report_event(struct work_struct *work)
|
||||||
|
{
|
||||||
|
struct sirfsoc_pwrc_drvdata *pwrcdrv =
|
||||||
|
container_of(work, struct sirfsoc_pwrc_drvdata, work.work);
|
||||||
|
|
||||||
|
if (sirfsoc_pwrc_is_on_key_down(pwrcdrv)) {
|
||||||
|
schedule_delayed_work(&pwrcdrv->work,
|
||||||
|
msecs_to_jiffies(PWRC_KEY_DETECT_UP_TIME));
|
||||||
|
} else {
|
||||||
|
input_event(pwrcdrv->input, EV_KEY, KEY_POWER, 0);
|
||||||
|
input_sync(pwrcdrv->input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static irqreturn_t sirfsoc_pwrc_isr(int irq, void *dev_id)
|
static irqreturn_t sirfsoc_pwrc_isr(int irq, void *dev_id)
|
||||||
{
|
{
|
||||||
@ -34,21 +60,44 @@ static irqreturn_t sirfsoc_pwrc_isr(int irq, void *dev_id)
|
|||||||
sirfsoc_rtc_iobrg_writel(int_status & ~PWRC_ON_KEY_BIT,
|
sirfsoc_rtc_iobrg_writel(int_status & ~PWRC_ON_KEY_BIT,
|
||||||
pwrcdrv->pwrc_base + PWRC_INT_STATUS);
|
pwrcdrv->pwrc_base + PWRC_INT_STATUS);
|
||||||
|
|
||||||
/*
|
input_event(pwrcdrv->input, EV_KEY, KEY_POWER, 1);
|
||||||
* For a typical Linux system, we report KEY_SUSPEND to trigger apm-power.c
|
|
||||||
* to queue a SUSPEND APM event
|
|
||||||
*/
|
|
||||||
input_event(pwrcdrv->input, EV_PWR, KEY_SUSPEND, 1);
|
|
||||||
input_sync(pwrcdrv->input);
|
input_sync(pwrcdrv->input);
|
||||||
|
schedule_delayed_work(&pwrcdrv->work,
|
||||||
/*
|
msecs_to_jiffies(PWRC_KEY_DETECT_UP_TIME));
|
||||||
* Todo: report KEY_POWER event for Android platforms, Android PowerManager
|
|
||||||
* will handle the suspend and powerdown/hibernation
|
|
||||||
*/
|
|
||||||
|
|
||||||
return IRQ_HANDLED;
|
return IRQ_HANDLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void sirfsoc_pwrc_toggle_interrupts(struct sirfsoc_pwrc_drvdata *pwrcdrv,
|
||||||
|
bool enable)
|
||||||
|
{
|
||||||
|
u32 int_mask;
|
||||||
|
|
||||||
|
int_mask = sirfsoc_rtc_iobrg_readl(pwrcdrv->pwrc_base + PWRC_INT_MASK);
|
||||||
|
if (enable)
|
||||||
|
int_mask |= PWRC_ON_KEY_BIT;
|
||||||
|
else
|
||||||
|
int_mask &= ~PWRC_ON_KEY_BIT;
|
||||||
|
sirfsoc_rtc_iobrg_writel(int_mask, pwrcdrv->pwrc_base + PWRC_INT_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sirfsoc_pwrc_open(struct input_dev *input)
|
||||||
|
{
|
||||||
|
struct sirfsoc_pwrc_drvdata *pwrcdrv = input_get_drvdata(input);
|
||||||
|
|
||||||
|
sirfsoc_pwrc_toggle_interrupts(pwrcdrv, true);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sirfsoc_pwrc_close(struct input_dev *input)
|
||||||
|
{
|
||||||
|
struct sirfsoc_pwrc_drvdata *pwrcdrv = input_get_drvdata(input);
|
||||||
|
|
||||||
|
sirfsoc_pwrc_toggle_interrupts(pwrcdrv, false);
|
||||||
|
cancel_delayed_work_sync(&pwrcdrv->work);
|
||||||
|
}
|
||||||
|
|
||||||
static const struct of_device_id sirfsoc_pwrc_of_match[] = {
|
static const struct of_device_id sirfsoc_pwrc_of_match[] = {
|
||||||
{ .compatible = "sirf,prima2-pwrc" },
|
{ .compatible = "sirf,prima2-pwrc" },
|
||||||
{},
|
{},
|
||||||
@ -70,7 +119,7 @@ static int sirfsoc_pwrc_probe(struct platform_device *pdev)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* we can't use of_iomap because pwrc is not mapped in memory,
|
* We can't use of_iomap because pwrc is not mapped in memory,
|
||||||
* the so-called base address is only offset in rtciobrg
|
* the so-called base address is only offset in rtciobrg
|
||||||
*/
|
*/
|
||||||
error = of_property_read_u32(np, "reg", &pwrcdrv->pwrc_base);
|
error = of_property_read_u32(np, "reg", &pwrcdrv->pwrc_base);
|
||||||
@ -86,11 +135,22 @@ static int sirfsoc_pwrc_probe(struct platform_device *pdev)
|
|||||||
|
|
||||||
pwrcdrv->input->name = "sirfsoc pwrckey";
|
pwrcdrv->input->name = "sirfsoc pwrckey";
|
||||||
pwrcdrv->input->phys = "pwrc/input0";
|
pwrcdrv->input->phys = "pwrc/input0";
|
||||||
pwrcdrv->input->evbit[0] = BIT_MASK(EV_PWR);
|
pwrcdrv->input->evbit[0] = BIT_MASK(EV_KEY);
|
||||||
|
input_set_capability(pwrcdrv->input, EV_KEY, KEY_POWER);
|
||||||
|
|
||||||
|
INIT_DELAYED_WORK(&pwrcdrv->work, sirfsoc_pwrc_report_event);
|
||||||
|
|
||||||
|
pwrcdrv->input->open = sirfsoc_pwrc_open;
|
||||||
|
pwrcdrv->input->close = sirfsoc_pwrc_close;
|
||||||
|
|
||||||
|
input_set_drvdata(pwrcdrv->input, pwrcdrv);
|
||||||
|
|
||||||
|
/* Make sure the device is quiesced */
|
||||||
|
sirfsoc_pwrc_toggle_interrupts(pwrcdrv, false);
|
||||||
|
|
||||||
irq = platform_get_irq(pdev, 0);
|
irq = platform_get_irq(pdev, 0);
|
||||||
error = devm_request_irq(&pdev->dev, irq,
|
error = devm_request_irq(&pdev->dev, irq,
|
||||||
sirfsoc_pwrc_isr, IRQF_SHARED,
|
sirfsoc_pwrc_isr, 0,
|
||||||
"sirfsoc_pwrc_int", pwrcdrv);
|
"sirfsoc_pwrc_int", pwrcdrv);
|
||||||
if (error) {
|
if (error) {
|
||||||
dev_err(&pdev->dev, "unable to claim irq %d, error: %d\n",
|
dev_err(&pdev->dev, "unable to claim irq %d, error: %d\n",
|
||||||
@ -98,11 +158,6 @@ static int sirfsoc_pwrc_probe(struct platform_device *pdev)
|
|||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
sirfsoc_rtc_iobrg_writel(
|
|
||||||
sirfsoc_rtc_iobrg_readl(pwrcdrv->pwrc_base + PWRC_INT_MASK) |
|
|
||||||
PWRC_ON_KEY_BIT,
|
|
||||||
pwrcdrv->pwrc_base + PWRC_INT_MASK);
|
|
||||||
|
|
||||||
error = input_register_device(pwrcdrv->input);
|
error = input_register_device(pwrcdrv->input);
|
||||||
if (error) {
|
if (error) {
|
||||||
dev_err(&pdev->dev,
|
dev_err(&pdev->dev,
|
||||||
@ -111,7 +166,7 @@ static int sirfsoc_pwrc_probe(struct platform_device *pdev)
|
|||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
platform_set_drvdata(pdev, pwrcdrv);
|
dev_set_drvdata(&pdev->dev, pwrcdrv);
|
||||||
device_init_wakeup(&pdev->dev, 1);
|
device_init_wakeup(&pdev->dev, 1);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@ -125,25 +180,25 @@ static int sirfsoc_pwrc_remove(struct platform_device *pdev)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_PM_SLEEP
|
#ifdef CONFIG_PM_SLEEP
|
||||||
static int pwrc_resume(struct device *dev)
|
static int sirfsoc_pwrc_resume(struct device *dev)
|
||||||
{
|
{
|
||||||
struct platform_device *pdev = to_platform_device(dev);
|
struct sirfsoc_pwrc_drvdata *pwrcdrv = dev_get_drvdata(dev);
|
||||||
struct sirfsoc_pwrc_drvdata *pwrcdrv = platform_get_drvdata(pdev);
|
struct input_dev *input = pwrcdrv->input;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Do not mask pwrc interrupt as we want pwrc work as a wakeup source
|
* Do not mask pwrc interrupt as we want pwrc work as a wakeup source
|
||||||
* if users touch X_ONKEY_B, see arch/arm/mach-prima2/pm.c
|
* if users touch X_ONKEY_B, see arch/arm/mach-prima2/pm.c
|
||||||
*/
|
*/
|
||||||
sirfsoc_rtc_iobrg_writel(
|
mutex_lock(&input->mutex);
|
||||||
sirfsoc_rtc_iobrg_readl(
|
if (input->users)
|
||||||
pwrcdrv->pwrc_base + PWRC_INT_MASK) | PWRC_ON_KEY_BIT,
|
sirfsoc_pwrc_toggle_interrupts(pwrcdrv, true);
|
||||||
pwrcdrv->pwrc_base + PWRC_INT_MASK);
|
mutex_unlock(&input->mutex);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static SIMPLE_DEV_PM_OPS(sirfsoc_pwrc_pm_ops, NULL, pwrc_resume);
|
static SIMPLE_DEV_PM_OPS(sirfsoc_pwrc_pm_ops, NULL, sirfsoc_pwrc_resume);
|
||||||
|
|
||||||
static struct platform_driver sirfsoc_pwrc_driver = {
|
static struct platform_driver sirfsoc_pwrc_driver = {
|
||||||
.probe = sirfsoc_pwrc_probe,
|
.probe = sirfsoc_pwrc_probe,
|
||||||
|
218
drivers/input/misc/soc_button_array.c
Normal file
218
drivers/input/misc/soc_button_array.c
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
/*
|
||||||
|
* Supports for the button array on SoC tablets originally running
|
||||||
|
* Windows 8.
|
||||||
|
*
|
||||||
|
* (C) Copyright 2014 Intel Corporation
|
||||||
|
*
|
||||||
|
* 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; version 2
|
||||||
|
* of the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/input.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/acpi.h>
|
||||||
|
#include <linux/gpio/consumer.h>
|
||||||
|
#include <linux/gpio_keys.h>
|
||||||
|
#include <linux/input.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/pnp.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Definition of buttons on the tablet. The ACPI index of each button
|
||||||
|
* is defined in section 2.8.7.2 of "Windows ACPI Design Guide for SoC
|
||||||
|
* Platforms"
|
||||||
|
*/
|
||||||
|
#define MAX_NBUTTONS 5
|
||||||
|
|
||||||
|
struct soc_button_info {
|
||||||
|
const char *name;
|
||||||
|
int acpi_index;
|
||||||
|
unsigned int event_type;
|
||||||
|
unsigned int event_code;
|
||||||
|
bool autorepeat;
|
||||||
|
bool wakeup;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Some of the buttons like volume up/down are auto repeat, while others
|
||||||
|
* are not. To support both, we register two platform devices, and put
|
||||||
|
* buttons into them based on whether the key should be auto repeat.
|
||||||
|
*/
|
||||||
|
#define BUTTON_TYPES 2
|
||||||
|
|
||||||
|
struct soc_button_data {
|
||||||
|
struct platform_device *children[BUTTON_TYPES];
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the Nth GPIO number from the ACPI object.
|
||||||
|
*/
|
||||||
|
static int soc_button_lookup_gpio(struct device *dev, int acpi_index)
|
||||||
|
{
|
||||||
|
struct gpio_desc *desc;
|
||||||
|
int gpio;
|
||||||
|
|
||||||
|
desc = gpiod_get_index(dev, KBUILD_MODNAME, acpi_index);
|
||||||
|
if (IS_ERR(desc))
|
||||||
|
return PTR_ERR(desc);
|
||||||
|
|
||||||
|
gpio = desc_to_gpio(desc);
|
||||||
|
|
||||||
|
gpiod_put(desc);
|
||||||
|
|
||||||
|
return gpio;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct platform_device *
|
||||||
|
soc_button_device_create(struct pnp_dev *pdev,
|
||||||
|
const struct soc_button_info *button_info,
|
||||||
|
bool autorepeat)
|
||||||
|
{
|
||||||
|
const struct soc_button_info *info;
|
||||||
|
struct platform_device *pd;
|
||||||
|
struct gpio_keys_button *gpio_keys;
|
||||||
|
struct gpio_keys_platform_data *gpio_keys_pdata;
|
||||||
|
int n_buttons = 0;
|
||||||
|
int gpio;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
gpio_keys_pdata = devm_kzalloc(&pdev->dev,
|
||||||
|
sizeof(*gpio_keys_pdata) +
|
||||||
|
sizeof(*gpio_keys) * MAX_NBUTTONS,
|
||||||
|
GFP_KERNEL);
|
||||||
|
gpio_keys = (void *)(gpio_keys_pdata + 1);
|
||||||
|
|
||||||
|
for (info = button_info; info->name; info++) {
|
||||||
|
if (info->autorepeat != autorepeat)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
gpio = soc_button_lookup_gpio(&pdev->dev, info->acpi_index);
|
||||||
|
if (gpio < 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
gpio_keys[n_buttons].type = info->event_type;
|
||||||
|
gpio_keys[n_buttons].code = info->event_code;
|
||||||
|
gpio_keys[n_buttons].gpio = gpio;
|
||||||
|
gpio_keys[n_buttons].active_low = 1;
|
||||||
|
gpio_keys[n_buttons].desc = info->name;
|
||||||
|
gpio_keys[n_buttons].wakeup = info->wakeup;
|
||||||
|
n_buttons++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n_buttons == 0) {
|
||||||
|
error = -ENODEV;
|
||||||
|
goto err_free_mem;
|
||||||
|
}
|
||||||
|
|
||||||
|
gpio_keys_pdata->buttons = gpio_keys;
|
||||||
|
gpio_keys_pdata->nbuttons = n_buttons;
|
||||||
|
gpio_keys_pdata->rep = autorepeat;
|
||||||
|
|
||||||
|
pd = platform_device_alloc("gpio-keys", PLATFORM_DEVID_AUTO);
|
||||||
|
if (!pd) {
|
||||||
|
error = -ENOMEM;
|
||||||
|
goto err_free_mem;
|
||||||
|
}
|
||||||
|
|
||||||
|
error = platform_device_add_data(pd, gpio_keys_pdata,
|
||||||
|
sizeof(*gpio_keys_pdata));
|
||||||
|
if (error)
|
||||||
|
goto err_free_pdev;
|
||||||
|
|
||||||
|
error = platform_device_add(pd);
|
||||||
|
if (error)
|
||||||
|
goto err_free_pdev;
|
||||||
|
|
||||||
|
return pd;
|
||||||
|
|
||||||
|
err_free_pdev:
|
||||||
|
platform_device_put(pd);
|
||||||
|
err_free_mem:
|
||||||
|
devm_kfree(&pdev->dev, gpio_keys_pdata);
|
||||||
|
return ERR_PTR(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void soc_button_remove(struct pnp_dev *pdev)
|
||||||
|
{
|
||||||
|
struct soc_button_data *priv = pnp_get_drvdata(pdev);
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < BUTTON_TYPES; i++)
|
||||||
|
if (priv->children[i])
|
||||||
|
platform_device_unregister(priv->children[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int soc_button_pnp_probe(struct pnp_dev *pdev,
|
||||||
|
const struct pnp_device_id *id)
|
||||||
|
{
|
||||||
|
const struct soc_button_info *button_info = (void *)id->driver_data;
|
||||||
|
struct soc_button_data *priv;
|
||||||
|
struct platform_device *pd;
|
||||||
|
int i;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
|
||||||
|
if (!priv)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
pnp_set_drvdata(pdev, priv);
|
||||||
|
|
||||||
|
for (i = 0; i < BUTTON_TYPES; i++) {
|
||||||
|
pd = soc_button_device_create(pdev, button_info, i == 0);
|
||||||
|
if (IS_ERR(pd)) {
|
||||||
|
error = PTR_ERR(pd);
|
||||||
|
if (error != -ENODEV) {
|
||||||
|
soc_button_remove(pdev);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
priv->children[i] = pd;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!priv->children[0] && !priv->children[1])
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct soc_button_info soc_button_PNP0C40[] = {
|
||||||
|
{ "power", 0, EV_KEY, KEY_POWER, false, true },
|
||||||
|
{ "home", 1, EV_KEY, KEY_HOME, false, true },
|
||||||
|
{ "volume_up", 2, EV_KEY, KEY_VOLUMEUP, true, false },
|
||||||
|
{ "volume_down", 3, EV_KEY, KEY_VOLUMEDOWN, true, false },
|
||||||
|
{ "rotation_lock", 4, EV_SW, SW_ROTATE_LOCK, false, false },
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct pnp_device_id soc_button_pnp_match[] = {
|
||||||
|
{ .id = "PNP0C40", .driver_data = (long)soc_button_PNP0C40 },
|
||||||
|
{ .id = "" }
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(pnp, soc_button_pnp_match);
|
||||||
|
|
||||||
|
static struct pnp_driver soc_button_pnp_driver = {
|
||||||
|
.name = KBUILD_MODNAME,
|
||||||
|
.id_table = soc_button_pnp_match,
|
||||||
|
.probe = soc_button_pnp_probe,
|
||||||
|
.remove = soc_button_remove,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int __init soc_button_init(void)
|
||||||
|
{
|
||||||
|
return pnp_register_driver(&soc_button_pnp_driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __exit soc_button_exit(void)
|
||||||
|
{
|
||||||
|
pnp_unregister_driver(&soc_button_pnp_driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(soc_button_init);
|
||||||
|
module_exit(soc_button_exit);
|
||||||
|
|
||||||
|
MODULE_LICENSE("GPL");
|
@ -20,6 +20,8 @@
|
|||||||
* Author: Aristeu Sergio Rozanski Filho <aris@cathedrallabs.org>
|
* Author: Aristeu Sergio Rozanski Filho <aris@cathedrallabs.org>
|
||||||
*
|
*
|
||||||
* Changes/Revisions:
|
* Changes/Revisions:
|
||||||
|
* 0.4 01/09/2014 (Benjamin Tissoires <benjamin.tissoires@redhat.com>)
|
||||||
|
* - add UI_GET_SYSNAME ioctl
|
||||||
* 0.3 09/04/2006 (Anssi Hannula <anssi.hannula@gmail.com>)
|
* 0.3 09/04/2006 (Anssi Hannula <anssi.hannula@gmail.com>)
|
||||||
* - updated ff support for the changes in kernel interface
|
* - updated ff support for the changes in kernel interface
|
||||||
* - added MODULE_VERSION
|
* - added MODULE_VERSION
|
||||||
@ -670,6 +672,31 @@ static int uinput_ff_upload_from_user(const char __user *buffer,
|
|||||||
__ret; \
|
__ret; \
|
||||||
})
|
})
|
||||||
|
|
||||||
|
static int uinput_str_to_user(void __user *dest, const char *str,
|
||||||
|
unsigned int maxlen)
|
||||||
|
{
|
||||||
|
char __user *p = dest;
|
||||||
|
int len, ret;
|
||||||
|
|
||||||
|
if (!str)
|
||||||
|
return -ENOENT;
|
||||||
|
|
||||||
|
if (maxlen == 0)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
len = strlen(str) + 1;
|
||||||
|
if (len > maxlen)
|
||||||
|
len = maxlen;
|
||||||
|
|
||||||
|
ret = copy_to_user(p, str, len);
|
||||||
|
if (ret)
|
||||||
|
return -EFAULT;
|
||||||
|
|
||||||
|
/* force terminating '\0' */
|
||||||
|
ret = put_user(0, p + len - 1);
|
||||||
|
return ret ? -EFAULT : len;
|
||||||
|
}
|
||||||
|
|
||||||
static long uinput_ioctl_handler(struct file *file, unsigned int cmd,
|
static long uinput_ioctl_handler(struct file *file, unsigned int cmd,
|
||||||
unsigned long arg, void __user *p)
|
unsigned long arg, void __user *p)
|
||||||
{
|
{
|
||||||
@ -679,6 +706,8 @@ static long uinput_ioctl_handler(struct file *file, unsigned int cmd,
|
|||||||
struct uinput_ff_erase ff_erase;
|
struct uinput_ff_erase ff_erase;
|
||||||
struct uinput_request *req;
|
struct uinput_request *req;
|
||||||
char *phys;
|
char *phys;
|
||||||
|
const char *name;
|
||||||
|
unsigned int size;
|
||||||
|
|
||||||
retval = mutex_lock_interruptible(&udev->mutex);
|
retval = mutex_lock_interruptible(&udev->mutex);
|
||||||
if (retval)
|
if (retval)
|
||||||
@ -693,51 +722,51 @@ static long uinput_ioctl_handler(struct file *file, unsigned int cmd,
|
|||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case UI_DEV_CREATE:
|
case UI_DEV_CREATE:
|
||||||
retval = uinput_create_device(udev);
|
retval = uinput_create_device(udev);
|
||||||
break;
|
goto out;
|
||||||
|
|
||||||
case UI_DEV_DESTROY:
|
case UI_DEV_DESTROY:
|
||||||
uinput_destroy_device(udev);
|
uinput_destroy_device(udev);
|
||||||
break;
|
goto out;
|
||||||
|
|
||||||
case UI_SET_EVBIT:
|
case UI_SET_EVBIT:
|
||||||
retval = uinput_set_bit(arg, evbit, EV_MAX);
|
retval = uinput_set_bit(arg, evbit, EV_MAX);
|
||||||
break;
|
goto out;
|
||||||
|
|
||||||
case UI_SET_KEYBIT:
|
case UI_SET_KEYBIT:
|
||||||
retval = uinput_set_bit(arg, keybit, KEY_MAX);
|
retval = uinput_set_bit(arg, keybit, KEY_MAX);
|
||||||
break;
|
goto out;
|
||||||
|
|
||||||
case UI_SET_RELBIT:
|
case UI_SET_RELBIT:
|
||||||
retval = uinput_set_bit(arg, relbit, REL_MAX);
|
retval = uinput_set_bit(arg, relbit, REL_MAX);
|
||||||
break;
|
goto out;
|
||||||
|
|
||||||
case UI_SET_ABSBIT:
|
case UI_SET_ABSBIT:
|
||||||
retval = uinput_set_bit(arg, absbit, ABS_MAX);
|
retval = uinput_set_bit(arg, absbit, ABS_MAX);
|
||||||
break;
|
goto out;
|
||||||
|
|
||||||
case UI_SET_MSCBIT:
|
case UI_SET_MSCBIT:
|
||||||
retval = uinput_set_bit(arg, mscbit, MSC_MAX);
|
retval = uinput_set_bit(arg, mscbit, MSC_MAX);
|
||||||
break;
|
goto out;
|
||||||
|
|
||||||
case UI_SET_LEDBIT:
|
case UI_SET_LEDBIT:
|
||||||
retval = uinput_set_bit(arg, ledbit, LED_MAX);
|
retval = uinput_set_bit(arg, ledbit, LED_MAX);
|
||||||
break;
|
goto out;
|
||||||
|
|
||||||
case UI_SET_SNDBIT:
|
case UI_SET_SNDBIT:
|
||||||
retval = uinput_set_bit(arg, sndbit, SND_MAX);
|
retval = uinput_set_bit(arg, sndbit, SND_MAX);
|
||||||
break;
|
goto out;
|
||||||
|
|
||||||
case UI_SET_FFBIT:
|
case UI_SET_FFBIT:
|
||||||
retval = uinput_set_bit(arg, ffbit, FF_MAX);
|
retval = uinput_set_bit(arg, ffbit, FF_MAX);
|
||||||
break;
|
goto out;
|
||||||
|
|
||||||
case UI_SET_SWBIT:
|
case UI_SET_SWBIT:
|
||||||
retval = uinput_set_bit(arg, swbit, SW_MAX);
|
retval = uinput_set_bit(arg, swbit, SW_MAX);
|
||||||
break;
|
goto out;
|
||||||
|
|
||||||
case UI_SET_PROPBIT:
|
case UI_SET_PROPBIT:
|
||||||
retval = uinput_set_bit(arg, propbit, INPUT_PROP_MAX);
|
retval = uinput_set_bit(arg, propbit, INPUT_PROP_MAX);
|
||||||
break;
|
goto out;
|
||||||
|
|
||||||
case UI_SET_PHYS:
|
case UI_SET_PHYS:
|
||||||
if (udev->state == UIST_CREATED) {
|
if (udev->state == UIST_CREATED) {
|
||||||
@ -753,18 +782,18 @@ static long uinput_ioctl_handler(struct file *file, unsigned int cmd,
|
|||||||
|
|
||||||
kfree(udev->dev->phys);
|
kfree(udev->dev->phys);
|
||||||
udev->dev->phys = phys;
|
udev->dev->phys = phys;
|
||||||
break;
|
goto out;
|
||||||
|
|
||||||
case UI_BEGIN_FF_UPLOAD:
|
case UI_BEGIN_FF_UPLOAD:
|
||||||
retval = uinput_ff_upload_from_user(p, &ff_up);
|
retval = uinput_ff_upload_from_user(p, &ff_up);
|
||||||
if (retval)
|
if (retval)
|
||||||
break;
|
goto out;
|
||||||
|
|
||||||
req = uinput_request_find(udev, ff_up.request_id);
|
req = uinput_request_find(udev, ff_up.request_id);
|
||||||
if (!req || req->code != UI_FF_UPLOAD ||
|
if (!req || req->code != UI_FF_UPLOAD ||
|
||||||
!req->u.upload.effect) {
|
!req->u.upload.effect) {
|
||||||
retval = -EINVAL;
|
retval = -EINVAL;
|
||||||
break;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
ff_up.retval = 0;
|
ff_up.retval = 0;
|
||||||
@ -775,65 +804,77 @@ static long uinput_ioctl_handler(struct file *file, unsigned int cmd,
|
|||||||
memset(&ff_up.old, 0, sizeof(struct ff_effect));
|
memset(&ff_up.old, 0, sizeof(struct ff_effect));
|
||||||
|
|
||||||
retval = uinput_ff_upload_to_user(p, &ff_up);
|
retval = uinput_ff_upload_to_user(p, &ff_up);
|
||||||
break;
|
goto out;
|
||||||
|
|
||||||
case UI_BEGIN_FF_ERASE:
|
case UI_BEGIN_FF_ERASE:
|
||||||
if (copy_from_user(&ff_erase, p, sizeof(ff_erase))) {
|
if (copy_from_user(&ff_erase, p, sizeof(ff_erase))) {
|
||||||
retval = -EFAULT;
|
retval = -EFAULT;
|
||||||
break;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
req = uinput_request_find(udev, ff_erase.request_id);
|
req = uinput_request_find(udev, ff_erase.request_id);
|
||||||
if (!req || req->code != UI_FF_ERASE) {
|
if (!req || req->code != UI_FF_ERASE) {
|
||||||
retval = -EINVAL;
|
retval = -EINVAL;
|
||||||
break;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
ff_erase.retval = 0;
|
ff_erase.retval = 0;
|
||||||
ff_erase.effect_id = req->u.effect_id;
|
ff_erase.effect_id = req->u.effect_id;
|
||||||
if (copy_to_user(p, &ff_erase, sizeof(ff_erase))) {
|
if (copy_to_user(p, &ff_erase, sizeof(ff_erase))) {
|
||||||
retval = -EFAULT;
|
retval = -EFAULT;
|
||||||
break;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
goto out;
|
||||||
|
|
||||||
case UI_END_FF_UPLOAD:
|
case UI_END_FF_UPLOAD:
|
||||||
retval = uinput_ff_upload_from_user(p, &ff_up);
|
retval = uinput_ff_upload_from_user(p, &ff_up);
|
||||||
if (retval)
|
if (retval)
|
||||||
break;
|
goto out;
|
||||||
|
|
||||||
req = uinput_request_find(udev, ff_up.request_id);
|
req = uinput_request_find(udev, ff_up.request_id);
|
||||||
if (!req || req->code != UI_FF_UPLOAD ||
|
if (!req || req->code != UI_FF_UPLOAD ||
|
||||||
!req->u.upload.effect) {
|
!req->u.upload.effect) {
|
||||||
retval = -EINVAL;
|
retval = -EINVAL;
|
||||||
break;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
req->retval = ff_up.retval;
|
req->retval = ff_up.retval;
|
||||||
uinput_request_done(udev, req);
|
uinput_request_done(udev, req);
|
||||||
break;
|
goto out;
|
||||||
|
|
||||||
case UI_END_FF_ERASE:
|
case UI_END_FF_ERASE:
|
||||||
if (copy_from_user(&ff_erase, p, sizeof(ff_erase))) {
|
if (copy_from_user(&ff_erase, p, sizeof(ff_erase))) {
|
||||||
retval = -EFAULT;
|
retval = -EFAULT;
|
||||||
break;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
req = uinput_request_find(udev, ff_erase.request_id);
|
req = uinput_request_find(udev, ff_erase.request_id);
|
||||||
if (!req || req->code != UI_FF_ERASE) {
|
if (!req || req->code != UI_FF_ERASE) {
|
||||||
retval = -EINVAL;
|
retval = -EINVAL;
|
||||||
break;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
req->retval = ff_erase.retval;
|
req->retval = ff_erase.retval;
|
||||||
uinput_request_done(udev, req);
|
uinput_request_done(udev, req);
|
||||||
break;
|
goto out;
|
||||||
|
|
||||||
default:
|
|
||||||
retval = -EINVAL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size = _IOC_SIZE(cmd);
|
||||||
|
|
||||||
|
/* Now check variable-length commands */
|
||||||
|
switch (cmd & ~IOCSIZE_MASK) {
|
||||||
|
case UI_GET_SYSNAME(0):
|
||||||
|
if (udev->state != UIST_CREATED) {
|
||||||
|
retval = -ENOENT;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
name = dev_name(&udev->dev->dev);
|
||||||
|
retval = uinput_str_to_user(p, name, size);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
retval = -EINVAL;
|
||||||
out:
|
out:
|
||||||
mutex_unlock(&udev->mutex);
|
mutex_unlock(&udev->mutex);
|
||||||
return retval;
|
return retval;
|
||||||
|
@ -277,6 +277,16 @@ static struct key_entry keymap_fs_amilo_pro_v3505[] __initdata = {
|
|||||||
{ KE_END, 0 }
|
{ KE_END, 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static struct key_entry keymap_fs_amilo_pro_v8210[] __initdata = {
|
||||||
|
{ KE_KEY, 0x01, {KEY_HELP} }, /* Fn+F1 */
|
||||||
|
{ KE_KEY, 0x06, {KEY_DISPLAYTOGGLE} }, /* Fn+F4 */
|
||||||
|
{ KE_BLUETOOTH, 0x30 }, /* Fn+F10 */
|
||||||
|
{ KE_KEY, 0x31, {KEY_MAIL} }, /* mail button */
|
||||||
|
{ KE_KEY, 0x36, {KEY_WWW} }, /* www button */
|
||||||
|
{ KE_WIFI, 0x78 }, /* satelite dish button */
|
||||||
|
{ KE_END, FE_WIFI_LED }
|
||||||
|
};
|
||||||
|
|
||||||
static struct key_entry keymap_fujitsu_n3510[] __initdata = {
|
static struct key_entry keymap_fujitsu_n3510[] __initdata = {
|
||||||
{ KE_KEY, 0x11, {KEY_PROG1} },
|
{ KE_KEY, 0x11, {KEY_PROG1} },
|
||||||
{ KE_KEY, 0x12, {KEY_PROG2} },
|
{ KE_KEY, 0x12, {KEY_PROG2} },
|
||||||
@ -653,6 +663,15 @@ static const struct dmi_system_id dmi_ids[] __initconst = {
|
|||||||
},
|
},
|
||||||
.driver_data = keymap_fs_amilo_pro_v3505
|
.driver_data = keymap_fs_amilo_pro_v3505
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
/* Fujitsu-Siemens Amilo Pro Edition V8210 */
|
||||||
|
.callback = dmi_matched,
|
||||||
|
.matches = {
|
||||||
|
DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
|
||||||
|
DMI_MATCH(DMI_PRODUCT_NAME, "AMILO Pro Series V8210"),
|
||||||
|
},
|
||||||
|
.driver_data = keymap_fs_amilo_pro_v8210
|
||||||
|
},
|
||||||
{
|
{
|
||||||
/* Fujitsu-Siemens Amilo M7400 */
|
/* Fujitsu-Siemens Amilo M7400 */
|
||||||
.callback = dmi_matched,
|
.callback = dmi_matched,
|
||||||
|
@ -48,6 +48,7 @@ struct atp_info {
|
|||||||
int yfact; /* Y multiplication factor */
|
int yfact; /* Y multiplication factor */
|
||||||
int datalen; /* size of USB transfers */
|
int datalen; /* size of USB transfers */
|
||||||
void (*callback)(struct urb *); /* callback function */
|
void (*callback)(struct urb *); /* callback function */
|
||||||
|
int fuzz; /* fuzz touchpad generates */
|
||||||
};
|
};
|
||||||
|
|
||||||
static void atp_complete_geyser_1_2(struct urb *urb);
|
static void atp_complete_geyser_1_2(struct urb *urb);
|
||||||
@ -61,6 +62,7 @@ static const struct atp_info fountain_info = {
|
|||||||
.yfact = 43,
|
.yfact = 43,
|
||||||
.datalen = 81,
|
.datalen = 81,
|
||||||
.callback = atp_complete_geyser_1_2,
|
.callback = atp_complete_geyser_1_2,
|
||||||
|
.fuzz = 16,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct atp_info geyser1_info = {
|
static const struct atp_info geyser1_info = {
|
||||||
@ -71,6 +73,7 @@ static const struct atp_info geyser1_info = {
|
|||||||
.yfact = 43,
|
.yfact = 43,
|
||||||
.datalen = 81,
|
.datalen = 81,
|
||||||
.callback = atp_complete_geyser_1_2,
|
.callback = atp_complete_geyser_1_2,
|
||||||
|
.fuzz = 16,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct atp_info geyser2_info = {
|
static const struct atp_info geyser2_info = {
|
||||||
@ -81,6 +84,7 @@ static const struct atp_info geyser2_info = {
|
|||||||
.yfact = 43,
|
.yfact = 43,
|
||||||
.datalen = 64,
|
.datalen = 64,
|
||||||
.callback = atp_complete_geyser_1_2,
|
.callback = atp_complete_geyser_1_2,
|
||||||
|
.fuzz = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct atp_info geyser3_info = {
|
static const struct atp_info geyser3_info = {
|
||||||
@ -90,6 +94,7 @@ static const struct atp_info geyser3_info = {
|
|||||||
.yfact = 64,
|
.yfact = 64,
|
||||||
.datalen = 64,
|
.datalen = 64,
|
||||||
.callback = atp_complete_geyser_3_4,
|
.callback = atp_complete_geyser_3_4,
|
||||||
|
.fuzz = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct atp_info geyser4_info = {
|
static const struct atp_info geyser4_info = {
|
||||||
@ -99,6 +104,7 @@ static const struct atp_info geyser4_info = {
|
|||||||
.yfact = 64,
|
.yfact = 64,
|
||||||
.datalen = 64,
|
.datalen = 64,
|
||||||
.callback = atp_complete_geyser_3_4,
|
.callback = atp_complete_geyser_3_4,
|
||||||
|
.fuzz = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
#define ATP_DEVICE(prod, info) \
|
#define ATP_DEVICE(prod, info) \
|
||||||
@ -155,8 +161,11 @@ MODULE_DEVICE_TABLE(usb, atp_table);
|
|||||||
#define ATP_XSENSORS 26
|
#define ATP_XSENSORS 26
|
||||||
#define ATP_YSENSORS 16
|
#define ATP_YSENSORS 16
|
||||||
|
|
||||||
/* amount of fuzz this touchpad generates */
|
/*
|
||||||
#define ATP_FUZZ 16
|
* The largest possible bank of sensors with additional buffer of 4 extra values
|
||||||
|
* on either side, for an array of smoothed sensor values.
|
||||||
|
*/
|
||||||
|
#define ATP_SMOOTHSIZE 34
|
||||||
|
|
||||||
/* maximum pressure this driver will report */
|
/* maximum pressure this driver will report */
|
||||||
#define ATP_PRESSURE 300
|
#define ATP_PRESSURE 300
|
||||||
@ -165,7 +174,13 @@ MODULE_DEVICE_TABLE(usb, atp_table);
|
|||||||
* Threshold for the touchpad sensors. Any change less than ATP_THRESHOLD is
|
* Threshold for the touchpad sensors. Any change less than ATP_THRESHOLD is
|
||||||
* ignored.
|
* ignored.
|
||||||
*/
|
*/
|
||||||
#define ATP_THRESHOLD 5
|
#define ATP_THRESHOLD 5
|
||||||
|
|
||||||
|
/*
|
||||||
|
* How far we'll bitshift our sensor values before averaging them. Mitigates
|
||||||
|
* rounding errors.
|
||||||
|
*/
|
||||||
|
#define ATP_SCALE 12
|
||||||
|
|
||||||
/* Geyser initialization constants */
|
/* Geyser initialization constants */
|
||||||
#define ATP_GEYSER_MODE_READ_REQUEST_ID 1
|
#define ATP_GEYSER_MODE_READ_REQUEST_ID 1
|
||||||
@ -203,11 +218,14 @@ struct atp {
|
|||||||
bool valid; /* are the samples valid? */
|
bool valid; /* are the samples valid? */
|
||||||
bool size_detect_done;
|
bool size_detect_done;
|
||||||
bool overflow_warned;
|
bool overflow_warned;
|
||||||
|
int fingers_old; /* last reported finger count */
|
||||||
int x_old; /* last reported x/y, */
|
int x_old; /* last reported x/y, */
|
||||||
int y_old; /* used for smoothing */
|
int y_old; /* used for smoothing */
|
||||||
signed char xy_cur[ATP_XSENSORS + ATP_YSENSORS];
|
signed char xy_cur[ATP_XSENSORS + ATP_YSENSORS];
|
||||||
signed char xy_old[ATP_XSENSORS + ATP_YSENSORS];
|
signed char xy_old[ATP_XSENSORS + ATP_YSENSORS];
|
||||||
int xy_acc[ATP_XSENSORS + ATP_YSENSORS];
|
int xy_acc[ATP_XSENSORS + ATP_YSENSORS];
|
||||||
|
int smooth[ATP_SMOOTHSIZE];
|
||||||
|
int smooth_tmp[ATP_SMOOTHSIZE];
|
||||||
int idlecount; /* number of empty packets */
|
int idlecount; /* number of empty packets */
|
||||||
struct work_struct work;
|
struct work_struct work;
|
||||||
};
|
};
|
||||||
@ -326,10 +344,17 @@ static void atp_reinit(struct work_struct *work)
|
|||||||
retval);
|
retval);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int atp_calculate_abs(int *xy_sensors, int nb_sensors, int fact,
|
static int atp_calculate_abs(struct atp *dev, int offset, int nb_sensors,
|
||||||
int *z, int *fingers)
|
int fact, int *z, int *fingers)
|
||||||
{
|
{
|
||||||
int i;
|
int i, pass;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Use offset to point xy_sensors at the first value in dev->xy_acc
|
||||||
|
* for whichever dimension we're looking at this particular go-round.
|
||||||
|
*/
|
||||||
|
int *xy_sensors = dev->xy_acc + offset;
|
||||||
|
|
||||||
/* values to calculate mean */
|
/* values to calculate mean */
|
||||||
int pcum = 0, psum = 0;
|
int pcum = 0, psum = 0;
|
||||||
int is_increasing = 0;
|
int is_increasing = 0;
|
||||||
@ -341,9 +366,6 @@ static int atp_calculate_abs(int *xy_sensors, int nb_sensors, int fact,
|
|||||||
if (is_increasing)
|
if (is_increasing)
|
||||||
is_increasing = 0;
|
is_increasing = 0;
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Makes the finger detection more versatile. For example,
|
* Makes the finger detection more versatile. For example,
|
||||||
* two fingers with no gap will be detected. Also, my
|
* two fingers with no gap will be detected. Also, my
|
||||||
@ -358,27 +380,63 @@ static int atp_calculate_abs(int *xy_sensors, int nb_sensors, int fact,
|
|||||||
*
|
*
|
||||||
* - Jason Parekh <jasonparekh@gmail.com>
|
* - Jason Parekh <jasonparekh@gmail.com>
|
||||||
*/
|
*/
|
||||||
if (i < 1 ||
|
|
||||||
|
} else if (i < 1 ||
|
||||||
(!is_increasing && xy_sensors[i - 1] < xy_sensors[i])) {
|
(!is_increasing && xy_sensors[i - 1] < xy_sensors[i])) {
|
||||||
(*fingers)++;
|
(*fingers)++;
|
||||||
is_increasing = 1;
|
is_increasing = 1;
|
||||||
} else if (i > 0 && (xy_sensors[i - 1] - xy_sensors[i] > threshold)) {
|
} else if (i > 0 && (xy_sensors[i - 1] - xy_sensors[i] > threshold)) {
|
||||||
is_increasing = 0;
|
is_increasing = 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*fingers < 1) /* No need to continue if no fingers are found. */
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Use a smoothed version of sensor data for movement calculations, to
|
||||||
|
* combat noise without needing to rely so heavily on a threshold.
|
||||||
|
* This improves tracking.
|
||||||
|
*
|
||||||
|
* The smoothed array is bigger than the original so that the smoothing
|
||||||
|
* doesn't result in edge values being truncated.
|
||||||
|
*/
|
||||||
|
|
||||||
|
memset(dev->smooth, 0, 4 * sizeof(dev->smooth[0]));
|
||||||
|
/* Pull base values, scaled up to help avoid truncation errors. */
|
||||||
|
for (i = 0; i < nb_sensors; i++)
|
||||||
|
dev->smooth[i + 4] = xy_sensors[i] << ATP_SCALE;
|
||||||
|
memset(&dev->smooth[nb_sensors + 4], 0, 4 * sizeof(dev->smooth[0]));
|
||||||
|
|
||||||
|
for (pass = 0; pass < 4; pass++) {
|
||||||
|
/* Handle edge. */
|
||||||
|
dev->smooth_tmp[0] = (dev->smooth[0] + dev->smooth[1]) / 2;
|
||||||
|
|
||||||
|
/* Average values with neighbors. */
|
||||||
|
for (i = 1; i < nb_sensors + 7; i++)
|
||||||
|
dev->smooth_tmp[i] = (dev->smooth[i - 1] +
|
||||||
|
dev->smooth[i] * 2 +
|
||||||
|
dev->smooth[i + 1]) / 4;
|
||||||
|
|
||||||
|
/* Handle other edge. */
|
||||||
|
dev->smooth_tmp[i] = (dev->smooth[i - 1] + dev->smooth[i]) / 2;
|
||||||
|
|
||||||
|
memcpy(dev->smooth, dev->smooth_tmp, sizeof(dev->smooth));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < nb_sensors + 8; i++) {
|
||||||
/*
|
/*
|
||||||
* Subtracts threshold so a high sensor that just passes the
|
* Skip values if they're small enough to be truncated to 0
|
||||||
* threshold won't skew the calculated absolute coordinate.
|
* by scale. Mostly noise.
|
||||||
* Fixes an issue where slowly moving the mouse would
|
|
||||||
* occasionally jump a number of pixels (slowly moving the
|
|
||||||
* finger makes this issue most apparent.)
|
|
||||||
*/
|
*/
|
||||||
pcum += (xy_sensors[i] - threshold) * i;
|
if ((dev->smooth[i] >> ATP_SCALE) > 0) {
|
||||||
psum += (xy_sensors[i] - threshold);
|
pcum += dev->smooth[i] * i;
|
||||||
|
psum += dev->smooth[i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (psum > 0) {
|
if (psum > 0) {
|
||||||
*z = psum;
|
*z = psum >> ATP_SCALE; /* Scale down pressure output. */
|
||||||
return pcum * fact / psum;
|
return pcum * fact / psum;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -455,7 +513,7 @@ static void atp_detect_size(struct atp *dev)
|
|||||||
input_set_abs_params(dev->input, ABS_X, 0,
|
input_set_abs_params(dev->input, ABS_X, 0,
|
||||||
(dev->info->xsensors_17 - 1) *
|
(dev->info->xsensors_17 - 1) *
|
||||||
dev->info->xfact - 1,
|
dev->info->xfact - 1,
|
||||||
ATP_FUZZ, 0);
|
dev->info->fuzz, 0);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -471,7 +529,7 @@ static void atp_complete_geyser_1_2(struct urb *urb)
|
|||||||
{
|
{
|
||||||
int x, y, x_z, y_z, x_f, y_f;
|
int x, y, x_z, y_z, x_f, y_f;
|
||||||
int retval, i, j;
|
int retval, i, j;
|
||||||
int key;
|
int key, fingers;
|
||||||
struct atp *dev = urb->context;
|
struct atp *dev = urb->context;
|
||||||
int status = atp_status_check(urb);
|
int status = atp_status_check(urb);
|
||||||
|
|
||||||
@ -548,16 +606,18 @@ static void atp_complete_geyser_1_2(struct urb *urb)
|
|||||||
|
|
||||||
dbg_dump("accumulator", dev->xy_acc);
|
dbg_dump("accumulator", dev->xy_acc);
|
||||||
|
|
||||||
x = atp_calculate_abs(dev->xy_acc, ATP_XSENSORS,
|
x = atp_calculate_abs(dev, 0, ATP_XSENSORS,
|
||||||
dev->info->xfact, &x_z, &x_f);
|
dev->info->xfact, &x_z, &x_f);
|
||||||
y = atp_calculate_abs(dev->xy_acc + ATP_XSENSORS, ATP_YSENSORS,
|
y = atp_calculate_abs(dev, ATP_XSENSORS, ATP_YSENSORS,
|
||||||
dev->info->yfact, &y_z, &y_f);
|
dev->info->yfact, &y_z, &y_f);
|
||||||
key = dev->data[dev->info->datalen - 1] & ATP_STATUS_BUTTON;
|
key = dev->data[dev->info->datalen - 1] & ATP_STATUS_BUTTON;
|
||||||
|
|
||||||
if (x && y) {
|
fingers = max(x_f, y_f);
|
||||||
|
|
||||||
|
if (x && y && fingers == dev->fingers_old) {
|
||||||
if (dev->x_old != -1) {
|
if (dev->x_old != -1) {
|
||||||
x = (dev->x_old * 3 + x) >> 2;
|
x = (dev->x_old * 7 + x) >> 3;
|
||||||
y = (dev->y_old * 3 + y) >> 2;
|
y = (dev->y_old * 7 + y) >> 3;
|
||||||
dev->x_old = x;
|
dev->x_old = x;
|
||||||
dev->y_old = y;
|
dev->y_old = y;
|
||||||
|
|
||||||
@ -571,7 +631,7 @@ static void atp_complete_geyser_1_2(struct urb *urb)
|
|||||||
input_report_abs(dev->input, ABS_Y, y);
|
input_report_abs(dev->input, ABS_Y, y);
|
||||||
input_report_abs(dev->input, ABS_PRESSURE,
|
input_report_abs(dev->input, ABS_PRESSURE,
|
||||||
min(ATP_PRESSURE, x_z + y_z));
|
min(ATP_PRESSURE, x_z + y_z));
|
||||||
atp_report_fingers(dev->input, max(x_f, y_f));
|
atp_report_fingers(dev->input, fingers);
|
||||||
}
|
}
|
||||||
dev->x_old = x;
|
dev->x_old = x;
|
||||||
dev->y_old = y;
|
dev->y_old = y;
|
||||||
@ -579,6 +639,7 @@ static void atp_complete_geyser_1_2(struct urb *urb)
|
|||||||
} else if (!x && !y) {
|
} else if (!x && !y) {
|
||||||
|
|
||||||
dev->x_old = dev->y_old = -1;
|
dev->x_old = dev->y_old = -1;
|
||||||
|
dev->fingers_old = 0;
|
||||||
input_report_key(dev->input, BTN_TOUCH, 0);
|
input_report_key(dev->input, BTN_TOUCH, 0);
|
||||||
input_report_abs(dev->input, ABS_PRESSURE, 0);
|
input_report_abs(dev->input, ABS_PRESSURE, 0);
|
||||||
atp_report_fingers(dev->input, 0);
|
atp_report_fingers(dev->input, 0);
|
||||||
@ -587,6 +648,10 @@ static void atp_complete_geyser_1_2(struct urb *urb)
|
|||||||
memset(dev->xy_acc, 0, sizeof(dev->xy_acc));
|
memset(dev->xy_acc, 0, sizeof(dev->xy_acc));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fingers != dev->fingers_old)
|
||||||
|
dev->x_old = dev->y_old = -1;
|
||||||
|
dev->fingers_old = fingers;
|
||||||
|
|
||||||
input_report_key(dev->input, BTN_LEFT, key);
|
input_report_key(dev->input, BTN_LEFT, key);
|
||||||
input_sync(dev->input);
|
input_sync(dev->input);
|
||||||
|
|
||||||
@ -604,7 +669,7 @@ static void atp_complete_geyser_3_4(struct urb *urb)
|
|||||||
{
|
{
|
||||||
int x, y, x_z, y_z, x_f, y_f;
|
int x, y, x_z, y_z, x_f, y_f;
|
||||||
int retval, i, j;
|
int retval, i, j;
|
||||||
int key;
|
int key, fingers;
|
||||||
struct atp *dev = urb->context;
|
struct atp *dev = urb->context;
|
||||||
int status = atp_status_check(urb);
|
int status = atp_status_check(urb);
|
||||||
|
|
||||||
@ -660,16 +725,19 @@ static void atp_complete_geyser_3_4(struct urb *urb)
|
|||||||
|
|
||||||
dbg_dump("accumulator", dev->xy_acc);
|
dbg_dump("accumulator", dev->xy_acc);
|
||||||
|
|
||||||
x = atp_calculate_abs(dev->xy_acc, ATP_XSENSORS,
|
x = atp_calculate_abs(dev, 0, ATP_XSENSORS,
|
||||||
dev->info->xfact, &x_z, &x_f);
|
dev->info->xfact, &x_z, &x_f);
|
||||||
y = atp_calculate_abs(dev->xy_acc + ATP_XSENSORS, ATP_YSENSORS,
|
y = atp_calculate_abs(dev, ATP_XSENSORS, ATP_YSENSORS,
|
||||||
dev->info->yfact, &y_z, &y_f);
|
dev->info->yfact, &y_z, &y_f);
|
||||||
|
|
||||||
key = dev->data[dev->info->datalen - 1] & ATP_STATUS_BUTTON;
|
key = dev->data[dev->info->datalen - 1] & ATP_STATUS_BUTTON;
|
||||||
|
|
||||||
if (x && y) {
|
fingers = max(x_f, y_f);
|
||||||
|
|
||||||
|
if (x && y && fingers == dev->fingers_old) {
|
||||||
if (dev->x_old != -1) {
|
if (dev->x_old != -1) {
|
||||||
x = (dev->x_old * 3 + x) >> 2;
|
x = (dev->x_old * 7 + x) >> 3;
|
||||||
y = (dev->y_old * 3 + y) >> 2;
|
y = (dev->y_old * 7 + y) >> 3;
|
||||||
dev->x_old = x;
|
dev->x_old = x;
|
||||||
dev->y_old = y;
|
dev->y_old = y;
|
||||||
|
|
||||||
@ -683,7 +751,7 @@ static void atp_complete_geyser_3_4(struct urb *urb)
|
|||||||
input_report_abs(dev->input, ABS_Y, y);
|
input_report_abs(dev->input, ABS_Y, y);
|
||||||
input_report_abs(dev->input, ABS_PRESSURE,
|
input_report_abs(dev->input, ABS_PRESSURE,
|
||||||
min(ATP_PRESSURE, x_z + y_z));
|
min(ATP_PRESSURE, x_z + y_z));
|
||||||
atp_report_fingers(dev->input, max(x_f, y_f));
|
atp_report_fingers(dev->input, fingers);
|
||||||
}
|
}
|
||||||
dev->x_old = x;
|
dev->x_old = x;
|
||||||
dev->y_old = y;
|
dev->y_old = y;
|
||||||
@ -691,6 +759,7 @@ static void atp_complete_geyser_3_4(struct urb *urb)
|
|||||||
} else if (!x && !y) {
|
} else if (!x && !y) {
|
||||||
|
|
||||||
dev->x_old = dev->y_old = -1;
|
dev->x_old = dev->y_old = -1;
|
||||||
|
dev->fingers_old = 0;
|
||||||
input_report_key(dev->input, BTN_TOUCH, 0);
|
input_report_key(dev->input, BTN_TOUCH, 0);
|
||||||
input_report_abs(dev->input, ABS_PRESSURE, 0);
|
input_report_abs(dev->input, ABS_PRESSURE, 0);
|
||||||
atp_report_fingers(dev->input, 0);
|
atp_report_fingers(dev->input, 0);
|
||||||
@ -699,6 +768,10 @@ static void atp_complete_geyser_3_4(struct urb *urb)
|
|||||||
memset(dev->xy_acc, 0, sizeof(dev->xy_acc));
|
memset(dev->xy_acc, 0, sizeof(dev->xy_acc));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fingers != dev->fingers_old)
|
||||||
|
dev->x_old = dev->y_old = -1;
|
||||||
|
dev->fingers_old = fingers;
|
||||||
|
|
||||||
input_report_key(dev->input, BTN_LEFT, key);
|
input_report_key(dev->input, BTN_LEFT, key);
|
||||||
input_sync(dev->input);
|
input_sync(dev->input);
|
||||||
|
|
||||||
@ -843,10 +916,10 @@ static int atp_probe(struct usb_interface *iface,
|
|||||||
|
|
||||||
input_set_abs_params(input_dev, ABS_X, 0,
|
input_set_abs_params(input_dev, ABS_X, 0,
|
||||||
(dev->info->xsensors - 1) * dev->info->xfact - 1,
|
(dev->info->xsensors - 1) * dev->info->xfact - 1,
|
||||||
ATP_FUZZ, 0);
|
dev->info->fuzz, 0);
|
||||||
input_set_abs_params(input_dev, ABS_Y, 0,
|
input_set_abs_params(input_dev, ABS_Y, 0,
|
||||||
(dev->info->ysensors - 1) * dev->info->yfact - 1,
|
(dev->info->ysensors - 1) * dev->info->yfact - 1,
|
||||||
ATP_FUZZ, 0);
|
dev->info->fuzz, 0);
|
||||||
input_set_abs_params(input_dev, ABS_PRESSURE, 0, ATP_PRESSURE, 0, 0);
|
input_set_abs_params(input_dev, ABS_PRESSURE, 0, ATP_PRESSURE, 0, 0);
|
||||||
|
|
||||||
set_bit(EV_KEY, input_dev->evbit);
|
set_bit(EV_KEY, input_dev->evbit);
|
||||||
|
@ -263,7 +263,7 @@ config SERIO_APBPS2
|
|||||||
|
|
||||||
config SERIO_OLPC_APSP
|
config SERIO_OLPC_APSP
|
||||||
tristate "OLPC AP-SP input support"
|
tristate "OLPC AP-SP input support"
|
||||||
depends on OF
|
depends on OLPC || COMPILE_TEST
|
||||||
help
|
help
|
||||||
Say Y here if you want support for the keyboard and touchpad included
|
Say Y here if you want support for the keyboard and touchpad included
|
||||||
in the OLPC XO-1.75 and XO-4 laptops.
|
in the OLPC XO-1.75 and XO-4 laptops.
|
||||||
|
@ -984,7 +984,7 @@ static void hp_sdc_exit(void)
|
|||||||
free_irq(hp_sdc.irq, &hp_sdc);
|
free_irq(hp_sdc.irq, &hp_sdc);
|
||||||
write_unlock_irq(&hp_sdc.lock);
|
write_unlock_irq(&hp_sdc.lock);
|
||||||
|
|
||||||
del_timer(&hp_sdc.kicker);
|
del_timer_sync(&hp_sdc.kicker);
|
||||||
|
|
||||||
tasklet_kill(&hp_sdc.task);
|
tasklet_kill(&hp_sdc.task);
|
||||||
|
|
||||||
|
@ -848,7 +848,7 @@ static int gtco_probe(struct usb_interface *usbinterface,
|
|||||||
gtco->inputdevice = input_dev;
|
gtco->inputdevice = input_dev;
|
||||||
|
|
||||||
/* Save interface information */
|
/* Save interface information */
|
||||||
gtco->usbdev = usb_get_dev(interface_to_usbdev(usbinterface));
|
gtco->usbdev = interface_to_usbdev(usbinterface);
|
||||||
gtco->intf = usbinterface;
|
gtco->intf = usbinterface;
|
||||||
|
|
||||||
/* Allocate some data for incoming reports */
|
/* Allocate some data for incoming reports */
|
||||||
|
@ -514,15 +514,6 @@ config TOUCHSCREEN_MIGOR
|
|||||||
To compile this driver as a module, choose M here: the
|
To compile this driver as a module, choose M here: the
|
||||||
module will be called migor_ts.
|
module will be called migor_ts.
|
||||||
|
|
||||||
config TOUCHSCREEN_TNETV107X
|
|
||||||
tristate "TI TNETV107X touchscreen support"
|
|
||||||
depends on ARCH_DAVINCI_TNETV107X
|
|
||||||
help
|
|
||||||
Say Y here if you want to use the TNETV107X touchscreen.
|
|
||||||
|
|
||||||
To compile this driver as a module, choose M here: the
|
|
||||||
module will be called tnetv107x-ts.
|
|
||||||
|
|
||||||
config TOUCHSCREEN_TOUCHRIGHT
|
config TOUCHSCREEN_TOUCHRIGHT
|
||||||
tristate "Touchright serial touchscreen"
|
tristate "Touchright serial touchscreen"
|
||||||
select SERIO
|
select SERIO
|
||||||
|
@ -56,7 +56,6 @@ obj-$(CONFIG_TOUCHSCREEN_ST1232) += st1232.o
|
|||||||
obj-$(CONFIG_TOUCHSCREEN_STMPE) += stmpe-ts.o
|
obj-$(CONFIG_TOUCHSCREEN_STMPE) += stmpe-ts.o
|
||||||
obj-$(CONFIG_TOUCHSCREEN_SUR40) += sur40.o
|
obj-$(CONFIG_TOUCHSCREEN_SUR40) += sur40.o
|
||||||
obj-$(CONFIG_TOUCHSCREEN_TI_AM335X_TSC) += ti_am335x_tsc.o
|
obj-$(CONFIG_TOUCHSCREEN_TI_AM335X_TSC) += ti_am335x_tsc.o
|
||||||
obj-$(CONFIG_TOUCHSCREEN_TNETV107X) += tnetv107x-ts.o
|
|
||||||
obj-$(CONFIG_TOUCHSCREEN_TOUCHIT213) += touchit213.o
|
obj-$(CONFIG_TOUCHSCREEN_TOUCHIT213) += touchit213.o
|
||||||
obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT) += touchright.o
|
obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT) += touchright.o
|
||||||
obj-$(CONFIG_TOUCHSCREEN_TOUCHWIN) += touchwin.o
|
obj-$(CONFIG_TOUCHSCREEN_TOUCHWIN) += touchwin.o
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2012 Simon Budig, <simon.budig@kernelconcepts.de>
|
* 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
|
* This software is licensed under the terms of the GNU General Public
|
||||||
* License version 2, as published by the Free Software Foundation, and
|
* License version 2, as published by the Free Software Foundation, and
|
||||||
@ -33,6 +35,7 @@
|
|||||||
#include <linux/debugfs.h>
|
#include <linux/debugfs.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/gpio.h>
|
#include <linux/gpio.h>
|
||||||
|
#include <linux/of_gpio.h>
|
||||||
#include <linux/input/mt.h>
|
#include <linux/input/mt.h>
|
||||||
#include <linux/input/edt-ft5x06.h>
|
#include <linux/input/edt-ft5x06.h>
|
||||||
|
|
||||||
@ -45,6 +48,14 @@
|
|||||||
#define WORK_REGISTER_NUM_X 0x33
|
#define WORK_REGISTER_NUM_X 0x33
|
||||||
#define WORK_REGISTER_NUM_Y 0x34
|
#define WORK_REGISTER_NUM_Y 0x34
|
||||||
|
|
||||||
|
#define M09_REGISTER_THRESHOLD 0x80
|
||||||
|
#define M09_REGISTER_GAIN 0x92
|
||||||
|
#define M09_REGISTER_OFFSET 0x93
|
||||||
|
#define M09_REGISTER_NUM_X 0x94
|
||||||
|
#define M09_REGISTER_NUM_Y 0x95
|
||||||
|
|
||||||
|
#define NO_REGISTER 0xff
|
||||||
|
|
||||||
#define WORK_REGISTER_OPMODE 0x3c
|
#define WORK_REGISTER_OPMODE 0x3c
|
||||||
#define FACTORY_REGISTER_OPMODE 0x01
|
#define FACTORY_REGISTER_OPMODE 0x01
|
||||||
|
|
||||||
@ -59,12 +70,30 @@
|
|||||||
#define EDT_RAW_DATA_RETRIES 100
|
#define EDT_RAW_DATA_RETRIES 100
|
||||||
#define EDT_RAW_DATA_DELAY 1 /* msec */
|
#define EDT_RAW_DATA_DELAY 1 /* msec */
|
||||||
|
|
||||||
|
enum edt_ver {
|
||||||
|
M06,
|
||||||
|
M09,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct edt_reg_addr {
|
||||||
|
int reg_threshold;
|
||||||
|
int reg_report_rate;
|
||||||
|
int reg_gain;
|
||||||
|
int reg_offset;
|
||||||
|
int reg_num_x;
|
||||||
|
int reg_num_y;
|
||||||
|
};
|
||||||
|
|
||||||
struct edt_ft5x06_ts_data {
|
struct edt_ft5x06_ts_data {
|
||||||
struct i2c_client *client;
|
struct i2c_client *client;
|
||||||
struct input_dev *input;
|
struct input_dev *input;
|
||||||
u16 num_x;
|
u16 num_x;
|
||||||
u16 num_y;
|
u16 num_y;
|
||||||
|
|
||||||
|
int reset_pin;
|
||||||
|
int irq_pin;
|
||||||
|
int wake_pin;
|
||||||
|
|
||||||
#if defined(CONFIG_DEBUG_FS)
|
#if defined(CONFIG_DEBUG_FS)
|
||||||
struct dentry *debug_dir;
|
struct dentry *debug_dir;
|
||||||
u8 *raw_buffer;
|
u8 *raw_buffer;
|
||||||
@ -79,6 +108,9 @@ struct edt_ft5x06_ts_data {
|
|||||||
int report_rate;
|
int report_rate;
|
||||||
|
|
||||||
char name[EDT_NAME_LEN];
|
char name[EDT_NAME_LEN];
|
||||||
|
|
||||||
|
struct edt_reg_addr reg_addr;
|
||||||
|
enum edt_ver version;
|
||||||
};
|
};
|
||||||
|
|
||||||
static int edt_ft5x06_ts_readwrite(struct i2c_client *client,
|
static int edt_ft5x06_ts_readwrite(struct i2c_client *client,
|
||||||
@ -136,33 +168,58 @@ static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id)
|
|||||||
{
|
{
|
||||||
struct edt_ft5x06_ts_data *tsdata = dev_id;
|
struct edt_ft5x06_ts_data *tsdata = dev_id;
|
||||||
struct device *dev = &tsdata->client->dev;
|
struct device *dev = &tsdata->client->dev;
|
||||||
u8 cmd = 0xf9;
|
u8 cmd;
|
||||||
u8 rdbuf[26];
|
u8 rdbuf[29];
|
||||||
int i, type, x, y, id;
|
int i, type, x, y, id;
|
||||||
|
int offset, tplen, datalen;
|
||||||
int error;
|
int error;
|
||||||
|
|
||||||
|
switch (tsdata->version) {
|
||||||
|
case M06:
|
||||||
|
cmd = 0xf9; /* tell the controller to send touch data */
|
||||||
|
offset = 5; /* where the actual touch data starts */
|
||||||
|
tplen = 4; /* data comes in so called frames */
|
||||||
|
datalen = 26; /* how much bytes to listen for */
|
||||||
|
break;
|
||||||
|
|
||||||
|
case M09:
|
||||||
|
cmd = 0x02;
|
||||||
|
offset = 1;
|
||||||
|
tplen = 6;
|
||||||
|
datalen = 29;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
memset(rdbuf, 0, sizeof(rdbuf));
|
memset(rdbuf, 0, sizeof(rdbuf));
|
||||||
|
|
||||||
error = edt_ft5x06_ts_readwrite(tsdata->client,
|
error = edt_ft5x06_ts_readwrite(tsdata->client,
|
||||||
sizeof(cmd), &cmd,
|
sizeof(cmd), &cmd,
|
||||||
sizeof(rdbuf), rdbuf);
|
datalen, rdbuf);
|
||||||
if (error) {
|
if (error) {
|
||||||
dev_err_ratelimited(dev, "Unable to fetch data, error: %d\n",
|
dev_err_ratelimited(dev, "Unable to fetch data, error: %d\n",
|
||||||
error);
|
error);
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rdbuf[0] != 0xaa || rdbuf[1] != 0xaa || rdbuf[2] != 26) {
|
/* M09 does not send header or CRC */
|
||||||
dev_err_ratelimited(dev, "Unexpected header: %02x%02x%02x!\n",
|
if (tsdata->version == M06) {
|
||||||
rdbuf[0], rdbuf[1], rdbuf[2]);
|
if (rdbuf[0] != 0xaa || rdbuf[1] != 0xaa ||
|
||||||
goto out;
|
rdbuf[2] != datalen) {
|
||||||
|
dev_err_ratelimited(dev,
|
||||||
|
"Unexpected header: %02x%02x%02x!\n",
|
||||||
|
rdbuf[0], rdbuf[1], rdbuf[2]);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!edt_ft5x06_ts_check_crc(tsdata, rdbuf, datalen))
|
||||||
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!edt_ft5x06_ts_check_crc(tsdata, rdbuf, 26))
|
|
||||||
goto out;
|
|
||||||
|
|
||||||
for (i = 0; i < MAX_SUPPORT_POINTS; i++) {
|
for (i = 0; i < MAX_SUPPORT_POINTS; i++) {
|
||||||
u8 *buf = &rdbuf[i * 4 + 5];
|
u8 *buf = &rdbuf[i * tplen + offset];
|
||||||
bool down;
|
bool down;
|
||||||
|
|
||||||
type = buf[0] >> 6;
|
type = buf[0] >> 6;
|
||||||
@ -170,10 +227,14 @@ static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id)
|
|||||||
if (type == TOUCH_EVENT_RESERVED)
|
if (type == TOUCH_EVENT_RESERVED)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
/* M06 sometimes sends bogus coordinates in TOUCH_DOWN */
|
||||||
|
if (tsdata->version == M06 && type == TOUCH_EVENT_DOWN)
|
||||||
|
continue;
|
||||||
|
|
||||||
x = ((buf[0] << 8) | buf[1]) & 0x0fff;
|
x = ((buf[0] << 8) | buf[1]) & 0x0fff;
|
||||||
y = ((buf[2] << 8) | buf[3]) & 0x0fff;
|
y = ((buf[2] << 8) | buf[3]) & 0x0fff;
|
||||||
id = (buf[2] >> 4) & 0x0f;
|
id = (buf[2] >> 4) & 0x0f;
|
||||||
down = (type != TOUCH_EVENT_UP);
|
down = type != TOUCH_EVENT_UP;
|
||||||
|
|
||||||
input_mt_slot(tsdata->input, id);
|
input_mt_slot(tsdata->input, id);
|
||||||
input_mt_report_slot_state(tsdata->input, MT_TOOL_FINGER, down);
|
input_mt_report_slot_state(tsdata->input, MT_TOOL_FINGER, down);
|
||||||
@ -197,12 +258,25 @@ static int edt_ft5x06_register_write(struct edt_ft5x06_ts_data *tsdata,
|
|||||||
{
|
{
|
||||||
u8 wrbuf[4];
|
u8 wrbuf[4];
|
||||||
|
|
||||||
wrbuf[0] = tsdata->factory_mode ? 0xf3 : 0xfc;
|
switch (tsdata->version) {
|
||||||
wrbuf[1] = tsdata->factory_mode ? addr & 0x7f : addr & 0x3f;
|
case M06:
|
||||||
wrbuf[2] = value;
|
wrbuf[0] = tsdata->factory_mode ? 0xf3 : 0xfc;
|
||||||
wrbuf[3] = wrbuf[0] ^ wrbuf[1] ^ wrbuf[2];
|
wrbuf[1] = tsdata->factory_mode ? addr & 0x7f : addr & 0x3f;
|
||||||
|
wrbuf[1] = tsdata->factory_mode ? addr & 0x7f : addr & 0x3f;
|
||||||
|
wrbuf[2] = value;
|
||||||
|
wrbuf[3] = wrbuf[0] ^ wrbuf[1] ^ wrbuf[2];
|
||||||
|
return edt_ft5x06_ts_readwrite(tsdata->client, 4,
|
||||||
|
wrbuf, 0, NULL);
|
||||||
|
case M09:
|
||||||
|
wrbuf[0] = addr;
|
||||||
|
wrbuf[1] = value;
|
||||||
|
|
||||||
return edt_ft5x06_ts_readwrite(tsdata->client, 4, wrbuf, 0, NULL);
|
return edt_ft5x06_ts_readwrite(tsdata->client, 3,
|
||||||
|
wrbuf, 0, NULL);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int edt_ft5x06_register_read(struct edt_ft5x06_ts_data *tsdata,
|
static int edt_ft5x06_register_read(struct edt_ft5x06_ts_data *tsdata,
|
||||||
@ -211,19 +285,36 @@ static int edt_ft5x06_register_read(struct edt_ft5x06_ts_data *tsdata,
|
|||||||
u8 wrbuf[2], rdbuf[2];
|
u8 wrbuf[2], rdbuf[2];
|
||||||
int error;
|
int error;
|
||||||
|
|
||||||
wrbuf[0] = tsdata->factory_mode ? 0xf3 : 0xfc;
|
switch (tsdata->version) {
|
||||||
wrbuf[1] = tsdata->factory_mode ? addr & 0x7f : addr & 0x3f;
|
case M06:
|
||||||
wrbuf[1] |= tsdata->factory_mode ? 0x80 : 0x40;
|
wrbuf[0] = tsdata->factory_mode ? 0xf3 : 0xfc;
|
||||||
|
wrbuf[1] = tsdata->factory_mode ? addr & 0x7f : addr & 0x3f;
|
||||||
|
wrbuf[1] |= tsdata->factory_mode ? 0x80 : 0x40;
|
||||||
|
|
||||||
error = edt_ft5x06_ts_readwrite(tsdata->client, 2, wrbuf, 2, rdbuf);
|
error = edt_ft5x06_ts_readwrite(tsdata->client, 2, wrbuf, 2,
|
||||||
if (error)
|
rdbuf);
|
||||||
return error;
|
if (error)
|
||||||
|
return error;
|
||||||
|
|
||||||
if ((wrbuf[0] ^ wrbuf[1] ^ rdbuf[0]) != rdbuf[1]) {
|
if ((wrbuf[0] ^ wrbuf[1] ^ rdbuf[0]) != rdbuf[1]) {
|
||||||
dev_err(&tsdata->client->dev,
|
dev_err(&tsdata->client->dev,
|
||||||
"crc error: 0x%02x expected, got 0x%02x\n",
|
"crc error: 0x%02x expected, got 0x%02x\n",
|
||||||
wrbuf[0] ^ wrbuf[1] ^ rdbuf[0], rdbuf[1]);
|
wrbuf[0] ^ wrbuf[1] ^ rdbuf[0],
|
||||||
return -EIO;
|
rdbuf[1]);
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case M09:
|
||||||
|
wrbuf[0] = addr;
|
||||||
|
error = edt_ft5x06_ts_readwrite(tsdata->client, 1,
|
||||||
|
wrbuf, 1, rdbuf);
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return rdbuf[0];
|
return rdbuf[0];
|
||||||
@ -234,19 +325,21 @@ struct edt_ft5x06_attribute {
|
|||||||
size_t field_offset;
|
size_t field_offset;
|
||||||
u8 limit_low;
|
u8 limit_low;
|
||||||
u8 limit_high;
|
u8 limit_high;
|
||||||
u8 addr;
|
u8 addr_m06;
|
||||||
|
u8 addr_m09;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define EDT_ATTR(_field, _mode, _addr, _limit_low, _limit_high) \
|
#define EDT_ATTR(_field, _mode, _addr_m06, _addr_m09, \
|
||||||
|
_limit_low, _limit_high) \
|
||||||
struct edt_ft5x06_attribute edt_ft5x06_attr_##_field = { \
|
struct edt_ft5x06_attribute edt_ft5x06_attr_##_field = { \
|
||||||
.dattr = __ATTR(_field, _mode, \
|
.dattr = __ATTR(_field, _mode, \
|
||||||
edt_ft5x06_setting_show, \
|
edt_ft5x06_setting_show, \
|
||||||
edt_ft5x06_setting_store), \
|
edt_ft5x06_setting_store), \
|
||||||
.field_offset = \
|
.field_offset = offsetof(struct edt_ft5x06_ts_data, _field), \
|
||||||
offsetof(struct edt_ft5x06_ts_data, _field), \
|
.addr_m06 = _addr_m06, \
|
||||||
|
.addr_m09 = _addr_m09, \
|
||||||
.limit_low = _limit_low, \
|
.limit_low = _limit_low, \
|
||||||
.limit_high = _limit_high, \
|
.limit_high = _limit_high, \
|
||||||
.addr = _addr, \
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t edt_ft5x06_setting_show(struct device *dev,
|
static ssize_t edt_ft5x06_setting_show(struct device *dev,
|
||||||
@ -257,10 +350,11 @@ static ssize_t edt_ft5x06_setting_show(struct device *dev,
|
|||||||
struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client);
|
struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client);
|
||||||
struct edt_ft5x06_attribute *attr =
|
struct edt_ft5x06_attribute *attr =
|
||||||
container_of(dattr, struct edt_ft5x06_attribute, dattr);
|
container_of(dattr, struct edt_ft5x06_attribute, dattr);
|
||||||
u8 *field = (u8 *)((char *)tsdata + attr->field_offset);
|
u8 *field = (u8 *)tsdata + attr->field_offset;
|
||||||
int val;
|
int val;
|
||||||
size_t count = 0;
|
size_t count = 0;
|
||||||
int error = 0;
|
int error = 0;
|
||||||
|
u8 addr;
|
||||||
|
|
||||||
mutex_lock(&tsdata->mutex);
|
mutex_lock(&tsdata->mutex);
|
||||||
|
|
||||||
@ -269,15 +363,33 @@ static ssize_t edt_ft5x06_setting_show(struct device *dev,
|
|||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
val = edt_ft5x06_register_read(tsdata, attr->addr);
|
switch (tsdata->version) {
|
||||||
if (val < 0) {
|
case M06:
|
||||||
error = val;
|
addr = attr->addr_m06;
|
||||||
dev_err(&tsdata->client->dev,
|
break;
|
||||||
"Failed to fetch attribute %s, error %d\n",
|
|
||||||
dattr->attr.name, error);
|
case M09:
|
||||||
|
addr = attr->addr_m09;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
error = -ENODEV;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (addr != NO_REGISTER) {
|
||||||
|
val = edt_ft5x06_register_read(tsdata, addr);
|
||||||
|
if (val < 0) {
|
||||||
|
error = val;
|
||||||
|
dev_err(&tsdata->client->dev,
|
||||||
|
"Failed to fetch attribute %s, error %d\n",
|
||||||
|
dattr->attr.name, error);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val = *field;
|
||||||
|
}
|
||||||
|
|
||||||
if (val != *field) {
|
if (val != *field) {
|
||||||
dev_warn(&tsdata->client->dev,
|
dev_warn(&tsdata->client->dev,
|
||||||
"%s: read (%d) and stored value (%d) differ\n",
|
"%s: read (%d) and stored value (%d) differ\n",
|
||||||
@ -299,9 +411,10 @@ static ssize_t edt_ft5x06_setting_store(struct device *dev,
|
|||||||
struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client);
|
struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client);
|
||||||
struct edt_ft5x06_attribute *attr =
|
struct edt_ft5x06_attribute *attr =
|
||||||
container_of(dattr, struct edt_ft5x06_attribute, dattr);
|
container_of(dattr, struct edt_ft5x06_attribute, dattr);
|
||||||
u8 *field = (u8 *)((char *)tsdata + attr->field_offset);
|
u8 *field = (u8 *)tsdata + attr->field_offset;
|
||||||
unsigned int val;
|
unsigned int val;
|
||||||
int error;
|
int error;
|
||||||
|
u8 addr;
|
||||||
|
|
||||||
mutex_lock(&tsdata->mutex);
|
mutex_lock(&tsdata->mutex);
|
||||||
|
|
||||||
@ -319,14 +432,29 @@ static ssize_t edt_ft5x06_setting_store(struct device *dev,
|
|||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
error = edt_ft5x06_register_write(tsdata, attr->addr, val);
|
switch (tsdata->version) {
|
||||||
if (error) {
|
case M06:
|
||||||
dev_err(&tsdata->client->dev,
|
addr = attr->addr_m06;
|
||||||
"Failed to update attribute %s, error: %d\n",
|
break;
|
||||||
dattr->attr.name, error);
|
|
||||||
|
case M09:
|
||||||
|
addr = attr->addr_m09;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
error = -ENODEV;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (addr != NO_REGISTER) {
|
||||||
|
error = edt_ft5x06_register_write(tsdata, addr, val);
|
||||||
|
if (error) {
|
||||||
|
dev_err(&tsdata->client->dev,
|
||||||
|
"Failed to update attribute %s, error: %d\n",
|
||||||
|
dattr->attr.name, error);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
*field = val;
|
*field = val;
|
||||||
|
|
||||||
out:
|
out:
|
||||||
@ -334,12 +462,14 @@ out:
|
|||||||
return error ?: count;
|
return error ?: count;
|
||||||
}
|
}
|
||||||
|
|
||||||
static EDT_ATTR(gain, S_IWUSR | S_IRUGO, WORK_REGISTER_GAIN, 0, 31);
|
static EDT_ATTR(gain, S_IWUSR | S_IRUGO, WORK_REGISTER_GAIN,
|
||||||
static EDT_ATTR(offset, S_IWUSR | S_IRUGO, WORK_REGISTER_OFFSET, 0, 31);
|
M09_REGISTER_GAIN, 0, 31);
|
||||||
static EDT_ATTR(threshold, S_IWUSR | S_IRUGO,
|
static EDT_ATTR(offset, S_IWUSR | S_IRUGO, WORK_REGISTER_OFFSET,
|
||||||
WORK_REGISTER_THRESHOLD, 20, 80);
|
M09_REGISTER_OFFSET, 0, 31);
|
||||||
static EDT_ATTR(report_rate, S_IWUSR | S_IRUGO,
|
static EDT_ATTR(threshold, S_IWUSR | S_IRUGO, WORK_REGISTER_THRESHOLD,
|
||||||
WORK_REGISTER_REPORT_RATE, 3, 14);
|
M09_REGISTER_THRESHOLD, 20, 80);
|
||||||
|
static EDT_ATTR(report_rate, S_IWUSR | S_IRUGO, WORK_REGISTER_REPORT_RATE,
|
||||||
|
NO_REGISTER, 3, 14);
|
||||||
|
|
||||||
static struct attribute *edt_ft5x06_attrs[] = {
|
static struct attribute *edt_ft5x06_attrs[] = {
|
||||||
&edt_ft5x06_attr_gain.dattr.attr,
|
&edt_ft5x06_attr_gain.dattr.attr,
|
||||||
@ -374,6 +504,9 @@ static int edt_ft5x06_factory_mode(struct edt_ft5x06_ts_data *tsdata)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* mode register is 0x3c when in the work mode */
|
/* mode register is 0x3c when in the work mode */
|
||||||
|
if (tsdata->version == M09)
|
||||||
|
goto m09_out;
|
||||||
|
|
||||||
error = edt_ft5x06_register_write(tsdata, WORK_REGISTER_OPMODE, 0x03);
|
error = edt_ft5x06_register_write(tsdata, WORK_REGISTER_OPMODE, 0x03);
|
||||||
if (error) {
|
if (error) {
|
||||||
dev_err(&client->dev,
|
dev_err(&client->dev,
|
||||||
@ -406,12 +539,18 @@ err_out:
|
|||||||
enable_irq(client->irq);
|
enable_irq(client->irq);
|
||||||
|
|
||||||
return error;
|
return error;
|
||||||
|
|
||||||
|
m09_out:
|
||||||
|
dev_err(&client->dev, "No factory mode support for M09\n");
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int edt_ft5x06_work_mode(struct edt_ft5x06_ts_data *tsdata)
|
static int edt_ft5x06_work_mode(struct edt_ft5x06_ts_data *tsdata)
|
||||||
{
|
{
|
||||||
struct i2c_client *client = tsdata->client;
|
struct i2c_client *client = tsdata->client;
|
||||||
int retries = EDT_SWITCH_MODE_RETRIES;
|
int retries = EDT_SWITCH_MODE_RETRIES;
|
||||||
|
struct edt_reg_addr *reg_addr = &tsdata->reg_addr;
|
||||||
int ret;
|
int ret;
|
||||||
int error;
|
int error;
|
||||||
|
|
||||||
@ -444,13 +583,14 @@ static int edt_ft5x06_work_mode(struct edt_ft5x06_ts_data *tsdata)
|
|||||||
tsdata->raw_buffer = NULL;
|
tsdata->raw_buffer = NULL;
|
||||||
|
|
||||||
/* restore parameters */
|
/* restore parameters */
|
||||||
edt_ft5x06_register_write(tsdata, WORK_REGISTER_THRESHOLD,
|
edt_ft5x06_register_write(tsdata, reg_addr->reg_threshold,
|
||||||
tsdata->threshold);
|
tsdata->threshold);
|
||||||
edt_ft5x06_register_write(tsdata, WORK_REGISTER_GAIN,
|
edt_ft5x06_register_write(tsdata, reg_addr->reg_gain,
|
||||||
tsdata->gain);
|
tsdata->gain);
|
||||||
edt_ft5x06_register_write(tsdata, WORK_REGISTER_OFFSET,
|
edt_ft5x06_register_write(tsdata, reg_addr->reg_offset,
|
||||||
tsdata->offset);
|
tsdata->offset);
|
||||||
edt_ft5x06_register_write(tsdata, WORK_REGISTER_REPORT_RATE,
|
if (reg_addr->reg_report_rate)
|
||||||
|
edt_ft5x06_register_write(tsdata, reg_addr->reg_report_rate,
|
||||||
tsdata->report_rate);
|
tsdata->report_rate);
|
||||||
|
|
||||||
enable_irq(client->irq);
|
enable_irq(client->irq);
|
||||||
@ -479,7 +619,7 @@ static int edt_ft5x06_debugfs_mode_set(void *data, u64 mode)
|
|||||||
|
|
||||||
if (mode != tsdata->factory_mode) {
|
if (mode != tsdata->factory_mode) {
|
||||||
retval = mode ? edt_ft5x06_factory_mode(tsdata) :
|
retval = mode ? edt_ft5x06_factory_mode(tsdata) :
|
||||||
edt_ft5x06_work_mode(tsdata);
|
edt_ft5x06_work_mode(tsdata);
|
||||||
}
|
}
|
||||||
|
|
||||||
mutex_unlock(&tsdata->mutex);
|
mutex_unlock(&tsdata->mutex);
|
||||||
@ -568,7 +708,6 @@ out:
|
|||||||
return error ?: read;
|
return error ?: read;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
static const struct file_operations debugfs_raw_data_fops = {
|
static const struct file_operations debugfs_raw_data_fops = {
|
||||||
.open = simple_open,
|
.open = simple_open,
|
||||||
.read = edt_ft5x06_debugfs_raw_data_read,
|
.read = edt_ft5x06_debugfs_raw_data_read,
|
||||||
@ -614,58 +753,100 @@ edt_ft5x06_ts_teardown_debugfs(struct edt_ft5x06_ts_data *tsdata)
|
|||||||
|
|
||||||
#endif /* CONFIG_DEBUGFS */
|
#endif /* CONFIG_DEBUGFS */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static int edt_ft5x06_ts_reset(struct i2c_client *client,
|
static int edt_ft5x06_ts_reset(struct i2c_client *client,
|
||||||
int reset_pin)
|
struct edt_ft5x06_ts_data *tsdata)
|
||||||
{
|
{
|
||||||
int error;
|
int error;
|
||||||
|
|
||||||
if (gpio_is_valid(reset_pin)) {
|
if (gpio_is_valid(tsdata->wake_pin)) {
|
||||||
/* this pulls reset down, enabling the low active reset */
|
error = devm_gpio_request_one(&client->dev,
|
||||||
error = devm_gpio_request_one(&client->dev, reset_pin,
|
tsdata->wake_pin, GPIOF_OUT_INIT_LOW,
|
||||||
GPIOF_OUT_INIT_LOW,
|
"edt-ft5x06 wake");
|
||||||
"edt-ft5x06 reset");
|
|
||||||
if (error) {
|
if (error) {
|
||||||
dev_err(&client->dev,
|
dev_err(&client->dev,
|
||||||
"Failed to request GPIO %d as reset pin, error %d\n",
|
"Failed to request GPIO %d as wake pin, error %d\n",
|
||||||
reset_pin, error);
|
tsdata->wake_pin, error);
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
mdelay(50);
|
msleep(5);
|
||||||
gpio_set_value(reset_pin, 1);
|
gpio_set_value(tsdata->wake_pin, 1);
|
||||||
mdelay(100);
|
}
|
||||||
|
if (gpio_is_valid(tsdata->reset_pin)) {
|
||||||
|
/* this pulls reset down, enabling the low active reset */
|
||||||
|
error = devm_gpio_request_one(&client->dev,
|
||||||
|
tsdata->reset_pin, GPIOF_OUT_INIT_LOW,
|
||||||
|
"edt-ft5x06 reset");
|
||||||
|
if (error) {
|
||||||
|
dev_err(&client->dev,
|
||||||
|
"Failed to request GPIO %d as reset pin, error %d\n",
|
||||||
|
tsdata->reset_pin, error);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
msleep(5);
|
||||||
|
gpio_set_value(tsdata->reset_pin, 1);
|
||||||
|
msleep(300);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int edt_ft5x06_ts_identify(struct i2c_client *client,
|
static int edt_ft5x06_ts_identify(struct i2c_client *client,
|
||||||
char *model_name,
|
struct edt_ft5x06_ts_data *tsdata,
|
||||||
char *fw_version)
|
char *fw_version)
|
||||||
{
|
{
|
||||||
u8 rdbuf[EDT_NAME_LEN];
|
u8 rdbuf[EDT_NAME_LEN];
|
||||||
char *p;
|
char *p;
|
||||||
int error;
|
int error;
|
||||||
|
char *model_name = tsdata->name;
|
||||||
|
|
||||||
|
/* see what we find if we assume it is a M06 *
|
||||||
|
* if we get less than EDT_NAME_LEN, we don't want
|
||||||
|
* to have garbage in there
|
||||||
|
*/
|
||||||
|
memset(rdbuf, 0, sizeof(rdbuf));
|
||||||
error = edt_ft5x06_ts_readwrite(client, 1, "\xbb",
|
error = edt_ft5x06_ts_readwrite(client, 1, "\xbb",
|
||||||
EDT_NAME_LEN - 1, rdbuf);
|
EDT_NAME_LEN - 1, rdbuf);
|
||||||
if (error)
|
if (error)
|
||||||
return error;
|
return error;
|
||||||
|
|
||||||
/* remove last '$' end marker */
|
/* if we find something consistent, stay with that assumption
|
||||||
rdbuf[EDT_NAME_LEN - 1] = '\0';
|
* at least M09 won't send 3 bytes here
|
||||||
if (rdbuf[EDT_NAME_LEN - 2] == '$')
|
*/
|
||||||
rdbuf[EDT_NAME_LEN - 2] = '\0';
|
if (!(strnicmp(rdbuf + 1, "EP0", 3))) {
|
||||||
|
tsdata->version = M06;
|
||||||
|
|
||||||
/* look for Model/Version separator */
|
/* remove last '$' end marker */
|
||||||
p = strchr(rdbuf, '*');
|
rdbuf[EDT_NAME_LEN - 1] = '\0';
|
||||||
if (p)
|
if (rdbuf[EDT_NAME_LEN - 2] == '$')
|
||||||
*p++ = '\0';
|
rdbuf[EDT_NAME_LEN - 2] = '\0';
|
||||||
|
|
||||||
strlcpy(model_name, rdbuf + 1, EDT_NAME_LEN);
|
/* look for Model/Version separator */
|
||||||
strlcpy(fw_version, p ? p : "", EDT_NAME_LEN);
|
p = strchr(rdbuf, '*');
|
||||||
|
if (p)
|
||||||
|
*p++ = '\0';
|
||||||
|
strlcpy(model_name, rdbuf + 1, EDT_NAME_LEN);
|
||||||
|
strlcpy(fw_version, p ? p : "", EDT_NAME_LEN);
|
||||||
|
} else {
|
||||||
|
/* since there are only two versions around (M06, M09) */
|
||||||
|
tsdata->version = M09;
|
||||||
|
|
||||||
|
error = edt_ft5x06_ts_readwrite(client, 1, "\xA6",
|
||||||
|
2, rdbuf);
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
strlcpy(fw_version, rdbuf, 2);
|
||||||
|
|
||||||
|
error = edt_ft5x06_ts_readwrite(client, 1, "\xA8",
|
||||||
|
1, rdbuf);
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
snprintf(model_name, EDT_NAME_LEN, "EP0%i%i0M09",
|
||||||
|
rdbuf[0] >> 4, rdbuf[0] & 0x0F);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -675,33 +856,104 @@ static int edt_ft5x06_ts_identify(struct i2c_client *client,
|
|||||||
pdata->name <= edt_ft5x06_attr_##name.limit_high) \
|
pdata->name <= edt_ft5x06_attr_##name.limit_high) \
|
||||||
edt_ft5x06_register_write(tsdata, reg, pdata->name)
|
edt_ft5x06_register_write(tsdata, reg, pdata->name)
|
||||||
|
|
||||||
|
#define EDT_GET_PROP(name, reg) { \
|
||||||
|
u32 val; \
|
||||||
|
if (of_property_read_u32(np, #name, &val) == 0) \
|
||||||
|
edt_ft5x06_register_write(tsdata, reg, val); \
|
||||||
|
}
|
||||||
|
|
||||||
|
static void edt_ft5x06_ts_get_dt_defaults(struct device_node *np,
|
||||||
|
struct edt_ft5x06_ts_data *tsdata)
|
||||||
|
{
|
||||||
|
struct edt_reg_addr *reg_addr = &tsdata->reg_addr;
|
||||||
|
|
||||||
|
EDT_GET_PROP(threshold, reg_addr->reg_threshold);
|
||||||
|
EDT_GET_PROP(gain, reg_addr->reg_gain);
|
||||||
|
EDT_GET_PROP(offset, reg_addr->reg_offset);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
edt_ft5x06_ts_get_defaults(struct edt_ft5x06_ts_data *tsdata,
|
edt_ft5x06_ts_get_defaults(struct edt_ft5x06_ts_data *tsdata,
|
||||||
const struct edt_ft5x06_platform_data *pdata)
|
const struct edt_ft5x06_platform_data *pdata)
|
||||||
{
|
{
|
||||||
|
struct edt_reg_addr *reg_addr = &tsdata->reg_addr;
|
||||||
|
|
||||||
if (!pdata->use_parameters)
|
if (!pdata->use_parameters)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/* pick up defaults from the platform data */
|
/* pick up defaults from the platform data */
|
||||||
EDT_ATTR_CHECKSET(threshold, WORK_REGISTER_THRESHOLD);
|
EDT_ATTR_CHECKSET(threshold, reg_addr->reg_threshold);
|
||||||
EDT_ATTR_CHECKSET(gain, WORK_REGISTER_GAIN);
|
EDT_ATTR_CHECKSET(gain, reg_addr->reg_gain);
|
||||||
EDT_ATTR_CHECKSET(offset, WORK_REGISTER_OFFSET);
|
EDT_ATTR_CHECKSET(offset, reg_addr->reg_offset);
|
||||||
EDT_ATTR_CHECKSET(report_rate, WORK_REGISTER_REPORT_RATE);
|
if (reg_addr->reg_report_rate != NO_REGISTER)
|
||||||
|
EDT_ATTR_CHECKSET(report_rate, reg_addr->reg_report_rate);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
edt_ft5x06_ts_get_parameters(struct edt_ft5x06_ts_data *tsdata)
|
edt_ft5x06_ts_get_parameters(struct edt_ft5x06_ts_data *tsdata)
|
||||||
{
|
{
|
||||||
|
struct edt_reg_addr *reg_addr = &tsdata->reg_addr;
|
||||||
|
|
||||||
tsdata->threshold = edt_ft5x06_register_read(tsdata,
|
tsdata->threshold = edt_ft5x06_register_read(tsdata,
|
||||||
WORK_REGISTER_THRESHOLD);
|
reg_addr->reg_threshold);
|
||||||
tsdata->gain = edt_ft5x06_register_read(tsdata, WORK_REGISTER_GAIN);
|
tsdata->gain = edt_ft5x06_register_read(tsdata, reg_addr->reg_gain);
|
||||||
tsdata->offset = edt_ft5x06_register_read(tsdata, WORK_REGISTER_OFFSET);
|
tsdata->offset = edt_ft5x06_register_read(tsdata, reg_addr->reg_offset);
|
||||||
tsdata->report_rate = edt_ft5x06_register_read(tsdata,
|
if (reg_addr->reg_report_rate != NO_REGISTER)
|
||||||
WORK_REGISTER_REPORT_RATE);
|
tsdata->report_rate = edt_ft5x06_register_read(tsdata,
|
||||||
tsdata->num_x = edt_ft5x06_register_read(tsdata, WORK_REGISTER_NUM_X);
|
reg_addr->reg_report_rate);
|
||||||
tsdata->num_y = edt_ft5x06_register_read(tsdata, WORK_REGISTER_NUM_Y);
|
tsdata->num_x = edt_ft5x06_register_read(tsdata, reg_addr->reg_num_x);
|
||||||
|
tsdata->num_y = edt_ft5x06_register_read(tsdata, reg_addr->reg_num_y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
edt_ft5x06_ts_set_regs(struct edt_ft5x06_ts_data *tsdata)
|
||||||
|
{
|
||||||
|
struct edt_reg_addr *reg_addr = &tsdata->reg_addr;
|
||||||
|
|
||||||
|
switch (tsdata->version) {
|
||||||
|
case M06:
|
||||||
|
reg_addr->reg_threshold = WORK_REGISTER_THRESHOLD;
|
||||||
|
reg_addr->reg_report_rate = WORK_REGISTER_REPORT_RATE;
|
||||||
|
reg_addr->reg_gain = WORK_REGISTER_GAIN;
|
||||||
|
reg_addr->reg_offset = WORK_REGISTER_OFFSET;
|
||||||
|
reg_addr->reg_num_x = WORK_REGISTER_NUM_X;
|
||||||
|
reg_addr->reg_num_y = WORK_REGISTER_NUM_Y;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case M09:
|
||||||
|
reg_addr->reg_threshold = M09_REGISTER_THRESHOLD;
|
||||||
|
reg_addr->reg_gain = M09_REGISTER_GAIN;
|
||||||
|
reg_addr->reg_offset = M09_REGISTER_OFFSET;
|
||||||
|
reg_addr->reg_num_x = M09_REGISTER_NUM_X;
|
||||||
|
reg_addr->reg_num_y = M09_REGISTER_NUM_Y;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_OF
|
||||||
|
static int edt_ft5x06_i2c_ts_probe_dt(struct device *dev,
|
||||||
|
struct edt_ft5x06_ts_data *tsdata)
|
||||||
|
{
|
||||||
|
struct device_node *np = dev->of_node;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* irq_pin is not needed for DT setup.
|
||||||
|
* irq is associated via 'interrupts' property in DT
|
||||||
|
*/
|
||||||
|
tsdata->irq_pin = -EINVAL;
|
||||||
|
tsdata->reset_pin = of_get_named_gpio(np, "reset-gpios", 0);
|
||||||
|
tsdata->wake_pin = of_get_named_gpio(np, "wake-gpios", 0);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
static inline int edt_ft5x06_i2c_ts_probe_dt(struct device *dev,
|
||||||
|
struct edt_ft5x06_ts_data *tsdata)
|
||||||
|
{
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static int edt_ft5x06_ts_probe(struct i2c_client *client,
|
static int edt_ft5x06_ts_probe(struct i2c_client *client,
|
||||||
const struct i2c_device_id *id)
|
const struct i2c_device_id *id)
|
||||||
{
|
{
|
||||||
@ -714,32 +966,40 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client,
|
|||||||
|
|
||||||
dev_dbg(&client->dev, "probing for EDT FT5x06 I2C\n");
|
dev_dbg(&client->dev, "probing for EDT FT5x06 I2C\n");
|
||||||
|
|
||||||
if (!pdata) {
|
|
||||||
dev_err(&client->dev, "no platform data?\n");
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
error = edt_ft5x06_ts_reset(client, pdata->reset_pin);
|
|
||||||
if (error)
|
|
||||||
return error;
|
|
||||||
|
|
||||||
if (gpio_is_valid(pdata->irq_pin)) {
|
|
||||||
error = devm_gpio_request_one(&client->dev, pdata->irq_pin,
|
|
||||||
GPIOF_IN, "edt-ft5x06 irq");
|
|
||||||
if (error) {
|
|
||||||
dev_err(&client->dev,
|
|
||||||
"Failed to request GPIO %d, error %d\n",
|
|
||||||
pdata->irq_pin, error);
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tsdata = devm_kzalloc(&client->dev, sizeof(*tsdata), GFP_KERNEL);
|
tsdata = devm_kzalloc(&client->dev, sizeof(*tsdata), GFP_KERNEL);
|
||||||
if (!tsdata) {
|
if (!tsdata) {
|
||||||
dev_err(&client->dev, "failed to allocate driver data.\n");
|
dev_err(&client->dev, "failed to allocate driver data.\n");
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!pdata) {
|
||||||
|
error = edt_ft5x06_i2c_ts_probe_dt(&client->dev, tsdata);
|
||||||
|
if (error) {
|
||||||
|
dev_err(&client->dev,
|
||||||
|
"DT probe failed and no platform data present\n");
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tsdata->reset_pin = pdata->reset_pin;
|
||||||
|
tsdata->irq_pin = pdata->irq_pin;
|
||||||
|
tsdata->wake_pin = -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
error = edt_ft5x06_ts_reset(client, tsdata);
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
if (gpio_is_valid(tsdata->irq_pin)) {
|
||||||
|
error = devm_gpio_request_one(&client->dev, tsdata->irq_pin,
|
||||||
|
GPIOF_IN, "edt-ft5x06 irq");
|
||||||
|
if (error) {
|
||||||
|
dev_err(&client->dev,
|
||||||
|
"Failed to request GPIO %d, error %d\n",
|
||||||
|
tsdata->irq_pin, error);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
input = devm_input_allocate_device(&client->dev);
|
input = devm_input_allocate_device(&client->dev);
|
||||||
if (!input) {
|
if (!input) {
|
||||||
dev_err(&client->dev, "failed to allocate input device.\n");
|
dev_err(&client->dev, "failed to allocate input device.\n");
|
||||||
@ -751,13 +1011,19 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client,
|
|||||||
tsdata->input = input;
|
tsdata->input = input;
|
||||||
tsdata->factory_mode = false;
|
tsdata->factory_mode = false;
|
||||||
|
|
||||||
error = edt_ft5x06_ts_identify(client, tsdata->name, fw_version);
|
error = edt_ft5x06_ts_identify(client, tsdata, fw_version);
|
||||||
if (error) {
|
if (error) {
|
||||||
dev_err(&client->dev, "touchscreen probe failed\n");
|
dev_err(&client->dev, "touchscreen probe failed\n");
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
edt_ft5x06_ts_get_defaults(tsdata, pdata);
|
edt_ft5x06_ts_set_regs(tsdata);
|
||||||
|
|
||||||
|
if (!pdata)
|
||||||
|
edt_ft5x06_ts_get_dt_defaults(client->dev.of_node, tsdata);
|
||||||
|
else
|
||||||
|
edt_ft5x06_ts_get_defaults(tsdata, pdata);
|
||||||
|
|
||||||
edt_ft5x06_ts_get_parameters(tsdata);
|
edt_ft5x06_ts_get_parameters(tsdata);
|
||||||
|
|
||||||
dev_dbg(&client->dev,
|
dev_dbg(&client->dev,
|
||||||
@ -787,10 +1053,10 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client,
|
|||||||
input_set_drvdata(input, tsdata);
|
input_set_drvdata(input, tsdata);
|
||||||
i2c_set_clientdata(client, tsdata);
|
i2c_set_clientdata(client, tsdata);
|
||||||
|
|
||||||
error = devm_request_threaded_irq(&client->dev, client->irq,
|
error = devm_request_threaded_irq(&client->dev, client->irq, NULL,
|
||||||
NULL, edt_ft5x06_ts_isr,
|
edt_ft5x06_ts_isr,
|
||||||
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
||||||
client->name, tsdata);
|
client->name, tsdata);
|
||||||
if (error) {
|
if (error) {
|
||||||
dev_err(&client->dev, "Unable to request touchscreen IRQ.\n");
|
dev_err(&client->dev, "Unable to request touchscreen IRQ.\n");
|
||||||
return error;
|
return error;
|
||||||
@ -801,19 +1067,21 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client,
|
|||||||
return error;
|
return error;
|
||||||
|
|
||||||
error = input_register_device(input);
|
error = input_register_device(input);
|
||||||
if (error) {
|
if (error)
|
||||||
sysfs_remove_group(&client->dev.kobj, &edt_ft5x06_attr_group);
|
goto err_remove_attrs;
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
edt_ft5x06_ts_prepare_debugfs(tsdata, dev_driver_string(&client->dev));
|
edt_ft5x06_ts_prepare_debugfs(tsdata, dev_driver_string(&client->dev));
|
||||||
device_init_wakeup(&client->dev, 1);
|
device_init_wakeup(&client->dev, 1);
|
||||||
|
|
||||||
dev_dbg(&client->dev,
|
dev_dbg(&client->dev,
|
||||||
"EDT FT5x06 initialized: IRQ pin %d, Reset pin %d.\n",
|
"EDT FT5x06 initialized: IRQ %d, WAKE pin %d, Reset pin %d.\n",
|
||||||
pdata->irq_pin, pdata->reset_pin);
|
client->irq, tsdata->wake_pin, tsdata->reset_pin);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
err_remove_attrs:
|
||||||
|
sysfs_remove_group(&client->dev.kobj, &edt_ft5x06_attr_group);
|
||||||
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int edt_ft5x06_ts_remove(struct i2c_client *client)
|
static int edt_ft5x06_ts_remove(struct i2c_client *client)
|
||||||
@ -852,15 +1120,26 @@ static SIMPLE_DEV_PM_OPS(edt_ft5x06_ts_pm_ops,
|
|||||||
edt_ft5x06_ts_suspend, edt_ft5x06_ts_resume);
|
edt_ft5x06_ts_suspend, edt_ft5x06_ts_resume);
|
||||||
|
|
||||||
static const struct i2c_device_id edt_ft5x06_ts_id[] = {
|
static const struct i2c_device_id edt_ft5x06_ts_id[] = {
|
||||||
{ "edt-ft5x06", 0 },
|
{ "edt-ft5x06", 0, },
|
||||||
{ }
|
{ /* sentinel */ }
|
||||||
};
|
};
|
||||||
MODULE_DEVICE_TABLE(i2c, 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", },
|
||||||
|
{ .compatible = "edt,edt-ft5306", },
|
||||||
|
{ .compatible = "edt,edt-ft5406", },
|
||||||
|
{ /* sentinel */ }
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, edt_ft5x06_of_match);
|
||||||
|
#endif
|
||||||
|
|
||||||
static struct i2c_driver edt_ft5x06_ts_driver = {
|
static struct i2c_driver edt_ft5x06_ts_driver = {
|
||||||
.driver = {
|
.driver = {
|
||||||
.owner = THIS_MODULE,
|
.owner = THIS_MODULE,
|
||||||
.name = "edt_ft5x06",
|
.name = "edt_ft5x06",
|
||||||
|
.of_match_table = of_match_ptr(edt_ft5x06_of_match),
|
||||||
.pm = &edt_ft5x06_ts_pm_ops,
|
.pm = &edt_ft5x06_ts_pm_ops,
|
||||||
},
|
},
|
||||||
.id_table = edt_ft5x06_ts_id,
|
.id_table = edt_ft5x06_ts_id,
|
||||||
|
@ -1,384 +0,0 @@
|
|||||||
/*
|
|
||||||
* Texas Instruments TNETV107X Touchscreen Driver
|
|
||||||
*
|
|
||||||
* Copyright (C) 2010 Texas Instruments
|
|
||||||
*
|
|
||||||
* 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 version 2.
|
|
||||||
*
|
|
||||||
* This program is distributed "as is" WITHOUT ANY WARRANTY of any
|
|
||||||
* kind, whether express or implied; without even the implied warranty
|
|
||||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <linux/module.h>
|
|
||||||
#include <linux/kernel.h>
|
|
||||||
#include <linux/err.h>
|
|
||||||
#include <linux/errno.h>
|
|
||||||
#include <linux/input.h>
|
|
||||||
#include <linux/platform_device.h>
|
|
||||||
#include <linux/interrupt.h>
|
|
||||||
#include <linux/slab.h>
|
|
||||||
#include <linux/delay.h>
|
|
||||||
#include <linux/ctype.h>
|
|
||||||
#include <linux/io.h>
|
|
||||||
#include <linux/clk.h>
|
|
||||||
|
|
||||||
#include <mach/tnetv107x.h>
|
|
||||||
|
|
||||||
#define TSC_PENUP_POLL (HZ / 5)
|
|
||||||
#define IDLE_TIMEOUT 100 /* msec */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The first and last samples of a touch interval are usually garbage and need
|
|
||||||
* to be filtered out with these devices. The following definitions control
|
|
||||||
* the number of samples skipped.
|
|
||||||
*/
|
|
||||||
#define TSC_HEAD_SKIP 1
|
|
||||||
#define TSC_TAIL_SKIP 1
|
|
||||||
#define TSC_SKIP (TSC_HEAD_SKIP + TSC_TAIL_SKIP + 1)
|
|
||||||
#define TSC_SAMPLES (TSC_SKIP + 1)
|
|
||||||
|
|
||||||
/* Register Offsets */
|
|
||||||
struct tsc_regs {
|
|
||||||
u32 rev;
|
|
||||||
u32 tscm;
|
|
||||||
u32 bwcm;
|
|
||||||
u32 swc;
|
|
||||||
u32 adcchnl;
|
|
||||||
u32 adcdata;
|
|
||||||
u32 chval[4];
|
|
||||||
};
|
|
||||||
|
|
||||||
/* TSC Mode Configuration Register (tscm) bits */
|
|
||||||
#define WMODE BIT(0)
|
|
||||||
#define TSKIND BIT(1)
|
|
||||||
#define ZMEASURE_EN BIT(2)
|
|
||||||
#define IDLE BIT(3)
|
|
||||||
#define TSC_EN BIT(4)
|
|
||||||
#define STOP BIT(5)
|
|
||||||
#define ONE_SHOT BIT(6)
|
|
||||||
#define SINGLE BIT(7)
|
|
||||||
#define AVG BIT(8)
|
|
||||||
#define AVGNUM(x) (((x) & 0x03) << 9)
|
|
||||||
#define PVSTC(x) (((x) & 0x07) << 11)
|
|
||||||
#define PON BIT(14)
|
|
||||||
#define PONBG BIT(15)
|
|
||||||
#define AFERST BIT(16)
|
|
||||||
|
|
||||||
/* ADC DATA Capture Register bits */
|
|
||||||
#define DATA_VALID BIT(16)
|
|
||||||
|
|
||||||
/* Register Access Macros */
|
|
||||||
#define tsc_read(ts, reg) __raw_readl(&(ts)->regs->reg)
|
|
||||||
#define tsc_write(ts, reg, val) __raw_writel(val, &(ts)->regs->reg);
|
|
||||||
#define tsc_set_bits(ts, reg, val) \
|
|
||||||
tsc_write(ts, reg, tsc_read(ts, reg) | (val))
|
|
||||||
#define tsc_clr_bits(ts, reg, val) \
|
|
||||||
tsc_write(ts, reg, tsc_read(ts, reg) & ~(val))
|
|
||||||
|
|
||||||
struct sample {
|
|
||||||
int x, y, p;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct tsc_data {
|
|
||||||
struct input_dev *input_dev;
|
|
||||||
struct resource *res;
|
|
||||||
struct tsc_regs __iomem *regs;
|
|
||||||
struct timer_list timer;
|
|
||||||
spinlock_t lock;
|
|
||||||
struct clk *clk;
|
|
||||||
struct device *dev;
|
|
||||||
int sample_count;
|
|
||||||
struct sample samples[TSC_SAMPLES];
|
|
||||||
int tsc_irq;
|
|
||||||
};
|
|
||||||
|
|
||||||
static int tsc_read_sample(struct tsc_data *ts, struct sample* sample)
|
|
||||||
{
|
|
||||||
int x, y, z1, z2, t, p = 0;
|
|
||||||
u32 val;
|
|
||||||
|
|
||||||
val = tsc_read(ts, chval[0]);
|
|
||||||
if (val & DATA_VALID)
|
|
||||||
x = val & 0xffff;
|
|
||||||
else
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
y = tsc_read(ts, chval[1]) & 0xffff;
|
|
||||||
z1 = tsc_read(ts, chval[2]) & 0xffff;
|
|
||||||
z2 = tsc_read(ts, chval[3]) & 0xffff;
|
|
||||||
|
|
||||||
if (z1) {
|
|
||||||
t = ((600 * x) * (z2 - z1));
|
|
||||||
p = t / (u32) (z1 << 12);
|
|
||||||
if (p < 0)
|
|
||||||
p = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
sample->x = x;
|
|
||||||
sample->y = y;
|
|
||||||
sample->p = p;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void tsc_poll(unsigned long data)
|
|
||||||
{
|
|
||||||
struct tsc_data *ts = (struct tsc_data *)data;
|
|
||||||
unsigned long flags;
|
|
||||||
int i, val, x, y, p;
|
|
||||||
|
|
||||||
spin_lock_irqsave(&ts->lock, flags);
|
|
||||||
|
|
||||||
if (ts->sample_count >= TSC_SKIP) {
|
|
||||||
input_report_abs(ts->input_dev, ABS_PRESSURE, 0);
|
|
||||||
input_report_key(ts->input_dev, BTN_TOUCH, 0);
|
|
||||||
input_sync(ts->input_dev);
|
|
||||||
} else if (ts->sample_count > 0) {
|
|
||||||
/*
|
|
||||||
* A touch event lasted less than our skip count. Salvage and
|
|
||||||
* report anyway.
|
|
||||||
*/
|
|
||||||
for (i = 0, val = 0; i < ts->sample_count; i++)
|
|
||||||
val += ts->samples[i].x;
|
|
||||||
x = val / ts->sample_count;
|
|
||||||
|
|
||||||
for (i = 0, val = 0; i < ts->sample_count; i++)
|
|
||||||
val += ts->samples[i].y;
|
|
||||||
y = val / ts->sample_count;
|
|
||||||
|
|
||||||
for (i = 0, val = 0; i < ts->sample_count; i++)
|
|
||||||
val += ts->samples[i].p;
|
|
||||||
p = val / ts->sample_count;
|
|
||||||
|
|
||||||
input_report_abs(ts->input_dev, ABS_X, x);
|
|
||||||
input_report_abs(ts->input_dev, ABS_Y, y);
|
|
||||||
input_report_abs(ts->input_dev, ABS_PRESSURE, p);
|
|
||||||
input_report_key(ts->input_dev, BTN_TOUCH, 1);
|
|
||||||
input_sync(ts->input_dev);
|
|
||||||
}
|
|
||||||
|
|
||||||
ts->sample_count = 0;
|
|
||||||
|
|
||||||
spin_unlock_irqrestore(&ts->lock, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
static irqreturn_t tsc_irq(int irq, void *dev_id)
|
|
||||||
{
|
|
||||||
struct tsc_data *ts = (struct tsc_data *)dev_id;
|
|
||||||
struct sample *sample;
|
|
||||||
int index;
|
|
||||||
|
|
||||||
spin_lock(&ts->lock);
|
|
||||||
|
|
||||||
index = ts->sample_count % TSC_SAMPLES;
|
|
||||||
sample = &ts->samples[index];
|
|
||||||
if (tsc_read_sample(ts, sample) < 0)
|
|
||||||
goto out;
|
|
||||||
|
|
||||||
if (++ts->sample_count >= TSC_SKIP) {
|
|
||||||
index = (ts->sample_count - TSC_TAIL_SKIP - 1) % TSC_SAMPLES;
|
|
||||||
sample = &ts->samples[index];
|
|
||||||
|
|
||||||
input_report_abs(ts->input_dev, ABS_X, sample->x);
|
|
||||||
input_report_abs(ts->input_dev, ABS_Y, sample->y);
|
|
||||||
input_report_abs(ts->input_dev, ABS_PRESSURE, sample->p);
|
|
||||||
if (ts->sample_count == TSC_SKIP)
|
|
||||||
input_report_key(ts->input_dev, BTN_TOUCH, 1);
|
|
||||||
input_sync(ts->input_dev);
|
|
||||||
}
|
|
||||||
mod_timer(&ts->timer, jiffies + TSC_PENUP_POLL);
|
|
||||||
out:
|
|
||||||
spin_unlock(&ts->lock);
|
|
||||||
return IRQ_HANDLED;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int tsc_start(struct input_dev *dev)
|
|
||||||
{
|
|
||||||
struct tsc_data *ts = input_get_drvdata(dev);
|
|
||||||
unsigned long timeout = jiffies + msecs_to_jiffies(IDLE_TIMEOUT);
|
|
||||||
u32 val;
|
|
||||||
|
|
||||||
clk_enable(ts->clk);
|
|
||||||
|
|
||||||
/* Go to idle mode, before any initialization */
|
|
||||||
while (time_after(timeout, jiffies)) {
|
|
||||||
if (tsc_read(ts, tscm) & IDLE)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (time_before(timeout, jiffies)) {
|
|
||||||
dev_warn(ts->dev, "timeout waiting for idle\n");
|
|
||||||
clk_disable(ts->clk);
|
|
||||||
return -EIO;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Configure TSC Control register*/
|
|
||||||
val = (PONBG | PON | PVSTC(4) | ONE_SHOT | ZMEASURE_EN);
|
|
||||||
tsc_write(ts, tscm, val);
|
|
||||||
|
|
||||||
/* Bring TSC out of reset: Clear AFE reset bit */
|
|
||||||
val &= ~(AFERST);
|
|
||||||
tsc_write(ts, tscm, val);
|
|
||||||
|
|
||||||
/* Configure all pins for hardware control*/
|
|
||||||
tsc_write(ts, bwcm, 0);
|
|
||||||
|
|
||||||
/* Finally enable the TSC */
|
|
||||||
tsc_set_bits(ts, tscm, TSC_EN);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void tsc_stop(struct input_dev *dev)
|
|
||||||
{
|
|
||||||
struct tsc_data *ts = input_get_drvdata(dev);
|
|
||||||
|
|
||||||
tsc_clr_bits(ts, tscm, TSC_EN);
|
|
||||||
synchronize_irq(ts->tsc_irq);
|
|
||||||
del_timer_sync(&ts->timer);
|
|
||||||
clk_disable(ts->clk);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int tsc_probe(struct platform_device *pdev)
|
|
||||||
{
|
|
||||||
struct device *dev = &pdev->dev;
|
|
||||||
struct tsc_data *ts;
|
|
||||||
int error = 0;
|
|
||||||
u32 rev = 0;
|
|
||||||
|
|
||||||
ts = kzalloc(sizeof(struct tsc_data), GFP_KERNEL);
|
|
||||||
if (!ts) {
|
|
||||||
dev_err(dev, "cannot allocate device info\n");
|
|
||||||
return -ENOMEM;
|
|
||||||
}
|
|
||||||
|
|
||||||
ts->dev = dev;
|
|
||||||
spin_lock_init(&ts->lock);
|
|
||||||
setup_timer(&ts->timer, tsc_poll, (unsigned long)ts);
|
|
||||||
platform_set_drvdata(pdev, ts);
|
|
||||||
|
|
||||||
ts->tsc_irq = platform_get_irq(pdev, 0);
|
|
||||||
if (ts->tsc_irq < 0) {
|
|
||||||
dev_err(dev, "cannot determine device interrupt\n");
|
|
||||||
error = -ENODEV;
|
|
||||||
goto error_res;
|
|
||||||
}
|
|
||||||
|
|
||||||
ts->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
||||||
if (!ts->res) {
|
|
||||||
dev_err(dev, "cannot determine register area\n");
|
|
||||||
error = -ENODEV;
|
|
||||||
goto error_res;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!request_mem_region(ts->res->start, resource_size(ts->res),
|
|
||||||
pdev->name)) {
|
|
||||||
dev_err(dev, "cannot claim register memory\n");
|
|
||||||
ts->res = NULL;
|
|
||||||
error = -EINVAL;
|
|
||||||
goto error_res;
|
|
||||||
}
|
|
||||||
|
|
||||||
ts->regs = ioremap(ts->res->start, resource_size(ts->res));
|
|
||||||
if (!ts->regs) {
|
|
||||||
dev_err(dev, "cannot map register memory\n");
|
|
||||||
error = -ENOMEM;
|
|
||||||
goto error_map;
|
|
||||||
}
|
|
||||||
|
|
||||||
ts->clk = clk_get(dev, NULL);
|
|
||||||
if (IS_ERR(ts->clk)) {
|
|
||||||
dev_err(dev, "cannot claim device clock\n");
|
|
||||||
error = PTR_ERR(ts->clk);
|
|
||||||
goto error_clk;
|
|
||||||
}
|
|
||||||
|
|
||||||
error = request_threaded_irq(ts->tsc_irq, NULL, tsc_irq, IRQF_ONESHOT,
|
|
||||||
dev_name(dev), ts);
|
|
||||||
if (error < 0) {
|
|
||||||
dev_err(ts->dev, "Could not allocate ts irq\n");
|
|
||||||
goto error_irq;
|
|
||||||
}
|
|
||||||
|
|
||||||
ts->input_dev = input_allocate_device();
|
|
||||||
if (!ts->input_dev) {
|
|
||||||
dev_err(dev, "cannot allocate input device\n");
|
|
||||||
error = -ENOMEM;
|
|
||||||
goto error_input;
|
|
||||||
}
|
|
||||||
input_set_drvdata(ts->input_dev, ts);
|
|
||||||
|
|
||||||
ts->input_dev->name = pdev->name;
|
|
||||||
ts->input_dev->id.bustype = BUS_HOST;
|
|
||||||
ts->input_dev->dev.parent = &pdev->dev;
|
|
||||||
ts->input_dev->open = tsc_start;
|
|
||||||
ts->input_dev->close = tsc_stop;
|
|
||||||
|
|
||||||
clk_enable(ts->clk);
|
|
||||||
rev = tsc_read(ts, rev);
|
|
||||||
ts->input_dev->id.product = ((rev >> 8) & 0x07);
|
|
||||||
ts->input_dev->id.version = ((rev >> 16) & 0xfff);
|
|
||||||
clk_disable(ts->clk);
|
|
||||||
|
|
||||||
__set_bit(EV_KEY, ts->input_dev->evbit);
|
|
||||||
__set_bit(EV_ABS, ts->input_dev->evbit);
|
|
||||||
__set_bit(BTN_TOUCH, ts->input_dev->keybit);
|
|
||||||
|
|
||||||
input_set_abs_params(ts->input_dev, ABS_X, 0, 0xffff, 5, 0);
|
|
||||||
input_set_abs_params(ts->input_dev, ABS_Y, 0, 0xffff, 5, 0);
|
|
||||||
input_set_abs_params(ts->input_dev, ABS_PRESSURE, 0, 4095, 128, 0);
|
|
||||||
|
|
||||||
error = input_register_device(ts->input_dev);
|
|
||||||
if (error < 0) {
|
|
||||||
dev_err(dev, "failed input device registration\n");
|
|
||||||
goto error_reg;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
error_reg:
|
|
||||||
input_free_device(ts->input_dev);
|
|
||||||
error_input:
|
|
||||||
free_irq(ts->tsc_irq, ts);
|
|
||||||
error_irq:
|
|
||||||
clk_put(ts->clk);
|
|
||||||
error_clk:
|
|
||||||
iounmap(ts->regs);
|
|
||||||
error_map:
|
|
||||||
release_mem_region(ts->res->start, resource_size(ts->res));
|
|
||||||
error_res:
|
|
||||||
kfree(ts);
|
|
||||||
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int tsc_remove(struct platform_device *pdev)
|
|
||||||
{
|
|
||||||
struct tsc_data *ts = platform_get_drvdata(pdev);
|
|
||||||
|
|
||||||
input_unregister_device(ts->input_dev);
|
|
||||||
free_irq(ts->tsc_irq, ts);
|
|
||||||
clk_put(ts->clk);
|
|
||||||
iounmap(ts->regs);
|
|
||||||
release_mem_region(ts->res->start, resource_size(ts->res));
|
|
||||||
kfree(ts);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct platform_driver tsc_driver = {
|
|
||||||
.probe = tsc_probe,
|
|
||||||
.remove = tsc_remove,
|
|
||||||
.driver.name = "tnetv107x-ts",
|
|
||||||
.driver.owner = THIS_MODULE,
|
|
||||||
};
|
|
||||||
module_platform_driver(tsc_driver);
|
|
||||||
|
|
||||||
MODULE_AUTHOR("Cyril Chemparathy");
|
|
||||||
MODULE_DESCRIPTION("TNETV107X Touchscreen Driver");
|
|
||||||
MODULE_ALIAS("platform:tnetv107x-ts");
|
|
||||||
MODULE_LICENSE("GPL");
|
|
@ -29,10 +29,13 @@
|
|||||||
#include <linux/sysfs.h>
|
#include <linux/sysfs.h>
|
||||||
#include <linux/input/mt.h>
|
#include <linux/input/mt.h>
|
||||||
#include <linux/platform_data/zforce_ts.h>
|
#include <linux/platform_data/zforce_ts.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
#include <linux/of_gpio.h>
|
||||||
|
|
||||||
#define WAIT_TIMEOUT msecs_to_jiffies(1000)
|
#define WAIT_TIMEOUT msecs_to_jiffies(1000)
|
||||||
|
|
||||||
#define FRAME_START 0xee
|
#define FRAME_START 0xee
|
||||||
|
#define FRAME_MAXSIZE 257
|
||||||
|
|
||||||
/* Offsets of the different parts of the payload the controller sends */
|
/* Offsets of the different parts of the payload the controller sends */
|
||||||
#define PAYLOAD_HEADER 0
|
#define PAYLOAD_HEADER 0
|
||||||
@ -64,7 +67,7 @@
|
|||||||
#define RESPONSE_STATUS 0X1e
|
#define RESPONSE_STATUS 0X1e
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Notifications are send by the touch controller without
|
* Notifications are sent by the touch controller without
|
||||||
* being requested by the driver and include for example
|
* being requested by the driver and include for example
|
||||||
* touch indications
|
* touch indications
|
||||||
*/
|
*/
|
||||||
@ -103,8 +106,8 @@ struct zforce_point {
|
|||||||
* @suspended device suspended
|
* @suspended device suspended
|
||||||
* @access_mutex serialize i2c-access, to keep multipart reads together
|
* @access_mutex serialize i2c-access, to keep multipart reads together
|
||||||
* @command_done completion to wait for the command result
|
* @command_done completion to wait for the command result
|
||||||
* @command_mutex serialize commands send to the ic
|
* @command_mutex serialize commands sent to the ic
|
||||||
* @command_waiting the id of the command that that is currently waiting
|
* @command_waiting the id of the command that is currently waiting
|
||||||
* for a result
|
* for a result
|
||||||
* @command_result returned result of the command
|
* @command_result returned result of the command
|
||||||
*/
|
*/
|
||||||
@ -235,7 +238,8 @@ static int zforce_scan_frequency(struct zforce_ts *ts, u16 idle, u16 finger,
|
|||||||
(finger & 0xff), ((finger >> 8) & 0xff),
|
(finger & 0xff), ((finger >> 8) & 0xff),
|
||||||
(stylus & 0xff), ((stylus >> 8) & 0xff) };
|
(stylus & 0xff), ((stylus >> 8) & 0xff) };
|
||||||
|
|
||||||
dev_dbg(&client->dev, "set scan frequency to (idle: %d, finger: %d, stylus: %d)\n",
|
dev_dbg(&client->dev,
|
||||||
|
"set scan frequency to (idle: %d, finger: %d, stylus: %d)\n",
|
||||||
idle, finger, stylus);
|
idle, finger, stylus);
|
||||||
|
|
||||||
return zforce_send_wait(ts, &buf[0], ARRAY_SIZE(buf));
|
return zforce_send_wait(ts, &buf[0], ARRAY_SIZE(buf));
|
||||||
@ -255,7 +259,7 @@ static int zforce_setconfig(struct zforce_ts *ts, char b1)
|
|||||||
static int zforce_start(struct zforce_ts *ts)
|
static int zforce_start(struct zforce_ts *ts)
|
||||||
{
|
{
|
||||||
struct i2c_client *client = ts->client;
|
struct i2c_client *client = ts->client;
|
||||||
const struct zforce_ts_platdata *pdata = dev_get_platdata(&client->dev);
|
const struct zforce_ts_platdata *pdata = ts->pdata;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
dev_dbg(&client->dev, "starting device\n");
|
dev_dbg(&client->dev, "starting device\n");
|
||||||
@ -326,13 +330,14 @@ static int zforce_stop(struct zforce_ts *ts)
|
|||||||
static int zforce_touch_event(struct zforce_ts *ts, u8 *payload)
|
static int zforce_touch_event(struct zforce_ts *ts, u8 *payload)
|
||||||
{
|
{
|
||||||
struct i2c_client *client = ts->client;
|
struct i2c_client *client = ts->client;
|
||||||
const struct zforce_ts_platdata *pdata = dev_get_platdata(&client->dev);
|
const struct zforce_ts_platdata *pdata = ts->pdata;
|
||||||
struct zforce_point point;
|
struct zforce_point point;
|
||||||
int count, i, num = 0;
|
int count, i, num = 0;
|
||||||
|
|
||||||
count = payload[0];
|
count = payload[0];
|
||||||
if (count > ZFORCE_REPORT_POINTS) {
|
if (count > ZFORCE_REPORT_POINTS) {
|
||||||
dev_warn(&client->dev, "to many coordinates %d, expected max %d\n",
|
dev_warn(&client->dev,
|
||||||
|
"too many coordinates %d, expected max %d\n",
|
||||||
count, ZFORCE_REPORT_POINTS);
|
count, ZFORCE_REPORT_POINTS);
|
||||||
count = ZFORCE_REPORT_POINTS;
|
count = ZFORCE_REPORT_POINTS;
|
||||||
}
|
}
|
||||||
@ -421,7 +426,7 @@ static int zforce_read_packet(struct zforce_ts *ts, u8 *buf)
|
|||||||
goto unlock;
|
goto unlock;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buf[PAYLOAD_LENGTH] <= 0 || buf[PAYLOAD_LENGTH] > 255) {
|
if (buf[PAYLOAD_LENGTH] == 0) {
|
||||||
dev_err(&client->dev, "invalid payload length: %d\n",
|
dev_err(&client->dev, "invalid payload length: %d\n",
|
||||||
buf[PAYLOAD_LENGTH]);
|
buf[PAYLOAD_LENGTH]);
|
||||||
ret = -EIO;
|
ret = -EIO;
|
||||||
@ -471,9 +476,9 @@ static irqreturn_t zforce_irq_thread(int irq, void *dev_id)
|
|||||||
{
|
{
|
||||||
struct zforce_ts *ts = dev_id;
|
struct zforce_ts *ts = dev_id;
|
||||||
struct i2c_client *client = ts->client;
|
struct i2c_client *client = ts->client;
|
||||||
const struct zforce_ts_platdata *pdata = dev_get_platdata(&client->dev);
|
const struct zforce_ts_platdata *pdata = ts->pdata;
|
||||||
int ret;
|
int ret;
|
||||||
u8 payload_buffer[512];
|
u8 payload_buffer[FRAME_MAXSIZE];
|
||||||
u8 *payload;
|
u8 *payload;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -494,8 +499,8 @@ static irqreturn_t zforce_irq_thread(int irq, void *dev_id)
|
|||||||
while (!gpio_get_value(pdata->gpio_int)) {
|
while (!gpio_get_value(pdata->gpio_int)) {
|
||||||
ret = zforce_read_packet(ts, payload_buffer);
|
ret = zforce_read_packet(ts, payload_buffer);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
dev_err(&client->dev, "could not read packet, ret: %d\n",
|
dev_err(&client->dev,
|
||||||
ret);
|
"could not read packet, ret: %d\n", ret);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -539,7 +544,8 @@ static irqreturn_t zforce_irq_thread(int irq, void *dev_id)
|
|||||||
payload[RESPONSE_DATA + 4];
|
payload[RESPONSE_DATA + 4];
|
||||||
ts->version_rev = (payload[RESPONSE_DATA + 7] << 8) |
|
ts->version_rev = (payload[RESPONSE_DATA + 7] << 8) |
|
||||||
payload[RESPONSE_DATA + 6];
|
payload[RESPONSE_DATA + 6];
|
||||||
dev_dbg(&ts->client->dev, "Firmware Version %04x:%04x %04x:%04x\n",
|
dev_dbg(&ts->client->dev,
|
||||||
|
"Firmware Version %04x:%04x %04x:%04x\n",
|
||||||
ts->version_major, ts->version_minor,
|
ts->version_major, ts->version_minor,
|
||||||
ts->version_build, ts->version_rev);
|
ts->version_build, ts->version_rev);
|
||||||
|
|
||||||
@ -552,7 +558,8 @@ static irqreturn_t zforce_irq_thread(int irq, void *dev_id)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
dev_err(&ts->client->dev, "unrecognized response id: 0x%x\n",
|
dev_err(&ts->client->dev,
|
||||||
|
"unrecognized response id: 0x%x\n",
|
||||||
payload[RESPONSE_ID]);
|
payload[RESPONSE_ID]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -618,7 +625,8 @@ static int zforce_suspend(struct device *dev)
|
|||||||
|
|
||||||
enable_irq_wake(client->irq);
|
enable_irq_wake(client->irq);
|
||||||
} else if (input->users) {
|
} else if (input->users) {
|
||||||
dev_dbg(&client->dev, "suspend without being a wakeup source\n");
|
dev_dbg(&client->dev,
|
||||||
|
"suspend without being a wakeup source\n");
|
||||||
|
|
||||||
ret = zforce_stop(ts);
|
ret = zforce_stop(ts);
|
||||||
if (ret)
|
if (ret)
|
||||||
@ -684,6 +692,45 @@ static void zforce_reset(void *data)
|
|||||||
gpio_set_value(ts->pdata->gpio_rst, 0);
|
gpio_set_value(ts->pdata->gpio_rst, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static struct zforce_ts_platdata *zforce_parse_dt(struct device *dev)
|
||||||
|
{
|
||||||
|
struct zforce_ts_platdata *pdata;
|
||||||
|
struct device_node *np = dev->of_node;
|
||||||
|
|
||||||
|
if (!np)
|
||||||
|
return ERR_PTR(-ENOENT);
|
||||||
|
|
||||||
|
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
|
||||||
|
if (!pdata) {
|
||||||
|
dev_err(dev, "failed to allocate platform data\n");
|
||||||
|
return ERR_PTR(-ENOMEM);
|
||||||
|
}
|
||||||
|
|
||||||
|
pdata->gpio_int = of_get_gpio(np, 0);
|
||||||
|
if (!gpio_is_valid(pdata->gpio_int)) {
|
||||||
|
dev_err(dev, "failed to get interrupt gpio\n");
|
||||||
|
return ERR_PTR(-EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
pdata->gpio_rst = of_get_gpio(np, 1);
|
||||||
|
if (!gpio_is_valid(pdata->gpio_rst)) {
|
||||||
|
dev_err(dev, "failed to get reset gpio\n");
|
||||||
|
return ERR_PTR(-EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (of_property_read_u32(np, "x-size", &pdata->x_max)) {
|
||||||
|
dev_err(dev, "failed to get x-size property\n");
|
||||||
|
return ERR_PTR(-EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (of_property_read_u32(np, "y-size", &pdata->y_max)) {
|
||||||
|
dev_err(dev, "failed to get y-size property\n");
|
||||||
|
return ERR_PTR(-EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pdata;
|
||||||
|
}
|
||||||
|
|
||||||
static int zforce_probe(struct i2c_client *client,
|
static int zforce_probe(struct i2c_client *client,
|
||||||
const struct i2c_device_id *id)
|
const struct i2c_device_id *id)
|
||||||
{
|
{
|
||||||
@ -692,8 +739,11 @@ static int zforce_probe(struct i2c_client *client,
|
|||||||
struct input_dev *input_dev;
|
struct input_dev *input_dev;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
if (!pdata)
|
if (!pdata) {
|
||||||
return -EINVAL;
|
pdata = zforce_parse_dt(&client->dev);
|
||||||
|
if (IS_ERR(pdata))
|
||||||
|
return PTR_ERR(pdata);
|
||||||
|
}
|
||||||
|
|
||||||
ts = devm_kzalloc(&client->dev, sizeof(struct zforce_ts), GFP_KERNEL);
|
ts = devm_kzalloc(&client->dev, sizeof(struct zforce_ts), GFP_KERNEL);
|
||||||
if (!ts)
|
if (!ts)
|
||||||
@ -798,7 +848,7 @@ static int zforce_probe(struct i2c_client *client,
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* this gets the firmware version among other informations */
|
/* this gets the firmware version among other information */
|
||||||
ret = zforce_command_wait(ts, COMMAND_STATUS);
|
ret = zforce_command_wait(ts, COMMAND_STATUS);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
dev_err(&client->dev, "couldn't get status, %d\n", ret);
|
dev_err(&client->dev, "couldn't get status, %d\n", ret);
|
||||||
@ -829,11 +879,20 @@ static struct i2c_device_id zforce_idtable[] = {
|
|||||||
};
|
};
|
||||||
MODULE_DEVICE_TABLE(i2c, zforce_idtable);
|
MODULE_DEVICE_TABLE(i2c, zforce_idtable);
|
||||||
|
|
||||||
|
#ifdef CONFIG_OF
|
||||||
|
static struct of_device_id zforce_dt_idtable[] = {
|
||||||
|
{ .compatible = "neonode,zforce" },
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, zforce_dt_idtable);
|
||||||
|
#endif
|
||||||
|
|
||||||
static struct i2c_driver zforce_driver = {
|
static struct i2c_driver zforce_driver = {
|
||||||
.driver = {
|
.driver = {
|
||||||
.owner = THIS_MODULE,
|
.owner = THIS_MODULE,
|
||||||
.name = "zforce-ts",
|
.name = "zforce-ts",
|
||||||
.pm = &zforce_pm_ops,
|
.pm = &zforce_pm_ops,
|
||||||
|
.of_match_table = of_match_ptr(zforce_dt_idtable),
|
||||||
},
|
},
|
||||||
.probe = zforce_probe,
|
.probe = zforce_probe,
|
||||||
.id_table = zforce_idtable,
|
.id_table = zforce_idtable,
|
||||||
|
@ -1,52 +0,0 @@
|
|||||||
/* Copyright (c) 2011, Code Aurora Forum. All rights reserved.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License version 2 and
|
|
||||||
* only version 2 as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef __PMIC8XXX_KEYPAD_H__
|
|
||||||
#define __PMIC8XXX_KEYPAD_H__
|
|
||||||
|
|
||||||
#include <linux/input/matrix_keypad.h>
|
|
||||||
|
|
||||||
#define PM8XXX_KEYPAD_DEV_NAME "pm8xxx-keypad"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* struct pm8xxx_keypad_platform_data - platform data for keypad
|
|
||||||
* @keymap_data - matrix keymap data
|
|
||||||
* @input_name - input device name
|
|
||||||
* @input_phys_device - input device name
|
|
||||||
* @num_cols - number of columns of keypad
|
|
||||||
* @num_rows - number of row of keypad
|
|
||||||
* @debounce_ms - debounce period in milliseconds
|
|
||||||
* @scan_delay_ms - scan delay in milliseconds
|
|
||||||
* @row_hold_ns - row hold period in nanoseconds
|
|
||||||
* @wakeup - configure keypad as wakeup
|
|
||||||
* @rep - enable or disable key repeat bit
|
|
||||||
*/
|
|
||||||
struct pm8xxx_keypad_platform_data {
|
|
||||||
const struct matrix_keymap_data *keymap_data;
|
|
||||||
|
|
||||||
const char *input_name;
|
|
||||||
const char *input_phys_device;
|
|
||||||
|
|
||||||
unsigned int num_cols;
|
|
||||||
unsigned int num_rows;
|
|
||||||
unsigned int rows_gpio_start;
|
|
||||||
unsigned int cols_gpio_start;
|
|
||||||
|
|
||||||
unsigned int debounce_ms;
|
|
||||||
unsigned int scan_delay_ms;
|
|
||||||
unsigned int row_hold_ns;
|
|
||||||
|
|
||||||
bool wakeup;
|
|
||||||
bool rep;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /*__PMIC8XXX_KEYPAD_H__ */
|
|
@ -1,31 +0,0 @@
|
|||||||
/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License version 2 and
|
|
||||||
* only version 2 as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef __PMIC8XXX_PWRKEY_H__
|
|
||||||
#define __PMIC8XXX_PWRKEY_H__
|
|
||||||
|
|
||||||
#define PM8XXX_PWRKEY_DEV_NAME "pm8xxx-pwrkey"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* struct pm8xxx_pwrkey_platform_data - platform data for pwrkey driver
|
|
||||||
* @pull up: power on register control for pull up/down configuration
|
|
||||||
* @kpd_trigger_delay_us: time delay for power key state change interrupt
|
|
||||||
* trigger.
|
|
||||||
* @wakeup: configure power key as wakeup source
|
|
||||||
*/
|
|
||||||
struct pm8xxx_pwrkey_platform_data {
|
|
||||||
bool pull_up;
|
|
||||||
u32 kpd_trigger_delay_us;
|
|
||||||
u32 wakeup;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /* __PMIC8XXX_PWRKEY_H__ */
|
|
@ -1,5 +1,4 @@
|
|||||||
/* arch/arm/plat-samsung/include/plat/ts.h
|
/*
|
||||||
*
|
|
||||||
* Copyright (c) 2005 Arnaud Patard <arnaud.patard@rtp-net.org>
|
* Copyright (c) 2005 Arnaud Patard <arnaud.patard@rtp-net.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@ -7,14 +6,14 @@
|
|||||||
* published by the Free Software Foundation.
|
* published by the Free Software Foundation.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __ASM_ARM_TS_H
|
#ifndef __TOUCHSCREEN_S3C2410_H
|
||||||
#define __ASM_ARM_TS_H
|
#define __TOUCHSCREEN_S3C2410_H
|
||||||
|
|
||||||
struct s3c2410_ts_mach_info {
|
struct s3c2410_ts_mach_info {
|
||||||
int delay;
|
int delay;
|
||||||
int presc;
|
int presc;
|
||||||
int oversampling_shift;
|
int oversampling_shift;
|
||||||
void (*cfg_gpio)(struct platform_device *dev);
|
void (*cfg_gpio)(struct platform_device *dev);
|
||||||
};
|
};
|
||||||
|
|
||||||
extern void s3c24xx_ts_set_platdata(struct s3c2410_ts_mach_info *);
|
extern void s3c24xx_ts_set_platdata(struct s3c2410_ts_mach_info *);
|
||||||
@ -22,4 +21,4 @@ extern void s3c24xx_ts_set_platdata(struct s3c2410_ts_mach_info *);
|
|||||||
/* defined by architecture to configure gpio */
|
/* defined by architecture to configure gpio */
|
||||||
extern void s3c24xx_ts_cfg_gpio(struct platform_device *dev);
|
extern void s3c24xx_ts_cfg_gpio(struct platform_device *dev);
|
||||||
|
|
||||||
#endif /* __ASM_ARM_TS_H */
|
#endif /*__TOUCHSCREEN_S3C2410_H */
|
||||||
|
@ -20,6 +20,8 @@
|
|||||||
* Author: Aristeu Sergio Rozanski Filho <aris@cathedrallabs.org>
|
* Author: Aristeu Sergio Rozanski Filho <aris@cathedrallabs.org>
|
||||||
*
|
*
|
||||||
* Changes/Revisions:
|
* Changes/Revisions:
|
||||||
|
* 0.4 01/09/2014 (Benjamin Tissoires <benjamin.tissoires@redhat.com>)
|
||||||
|
* - add UI_GET_SYSNAME ioctl
|
||||||
* 0.3 24/05/2006 (Anssi Hannula <anssi.hannulagmail.com>)
|
* 0.3 24/05/2006 (Anssi Hannula <anssi.hannulagmail.com>)
|
||||||
* - update ff support for the changes in kernel interface
|
* - update ff support for the changes in kernel interface
|
||||||
* - add UINPUT_VERSION
|
* - add UINPUT_VERSION
|
||||||
|
@ -20,6 +20,8 @@
|
|||||||
* Author: Aristeu Sergio Rozanski Filho <aris@cathedrallabs.org>
|
* Author: Aristeu Sergio Rozanski Filho <aris@cathedrallabs.org>
|
||||||
*
|
*
|
||||||
* Changes/Revisions:
|
* Changes/Revisions:
|
||||||
|
* 0.4 01/09/2014 (Benjamin Tissoires <benjamin.tissoires@redhat.com>)
|
||||||
|
* - add UI_GET_SYSNAME ioctl
|
||||||
* 0.3 24/05/2006 (Anssi Hannula <anssi.hannulagmail.com>)
|
* 0.3 24/05/2006 (Anssi Hannula <anssi.hannulagmail.com>)
|
||||||
* - update ff support for the changes in kernel interface
|
* - update ff support for the changes in kernel interface
|
||||||
* - add UINPUT_VERSION
|
* - add UINPUT_VERSION
|
||||||
@ -35,7 +37,7 @@
|
|||||||
#include <linux/types.h>
|
#include <linux/types.h>
|
||||||
#include <linux/input.h>
|
#include <linux/input.h>
|
||||||
|
|
||||||
#define UINPUT_VERSION 3
|
#define UINPUT_VERSION 4
|
||||||
|
|
||||||
|
|
||||||
struct uinput_ff_upload {
|
struct uinput_ff_upload {
|
||||||
@ -73,6 +75,15 @@ struct uinput_ff_erase {
|
|||||||
#define UI_BEGIN_FF_ERASE _IOWR(UINPUT_IOCTL_BASE, 202, struct uinput_ff_erase)
|
#define UI_BEGIN_FF_ERASE _IOWR(UINPUT_IOCTL_BASE, 202, struct uinput_ff_erase)
|
||||||
#define UI_END_FF_ERASE _IOW(UINPUT_IOCTL_BASE, 203, struct uinput_ff_erase)
|
#define UI_END_FF_ERASE _IOW(UINPUT_IOCTL_BASE, 203, struct uinput_ff_erase)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UI_GET_SYSNAME - get the sysfs name of the created uinput device
|
||||||
|
*
|
||||||
|
* @return the sysfs name of the created virtual input device.
|
||||||
|
* The complete sysfs path is then /sys/devices/virtual/input/--NAME--
|
||||||
|
* Usually, it is in the form "inputN"
|
||||||
|
*/
|
||||||
|
#define UI_GET_SYSNAME(len) _IOC(_IOC_READ, UINPUT_IOCTL_BASE, 300, len)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* To write a force-feedback-capable driver, the upload_effect
|
* To write a force-feedback-capable driver, the upload_effect
|
||||||
* and erase_effect callbacks in input_dev must be implemented.
|
* and erase_effect callbacks in input_dev must be implemented.
|
||||||
|
Loading…
Reference in New Issue
Block a user