Pull input subsystem updates from Dmitry Torokhov: "You will get the following new drivers: - Qualcomm PM8941 power key drver - ChipOne icn8318 touchscreen controller driver - Broadcom iProc touchscreen and keypad drivers - Semtech SX8654 I2C touchscreen controller driver ALPS driver now supports newer SS4 devices; Elantech got a fix that should make it work on some ASUS laptops; and a slew of other enhancements and random fixes" * 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input: (51 commits) Input: alps - non interleaved V2 dualpoint has separate stick button bits Input: alps - fix touchpad buttons getting stuck when used with trackpoint Input: atkbd - document "no new force-release quirks" policy Input: ALPS - make alps_get_pkt_id_ss4_v2() and others static Input: ALPS - V7 devices can report 5-finger taps Input: ALPS - add support for SS4 touchpad devices Input: ALPS - refactor alps_set_abs_params_mt() Input: elantech - fix absolute mode setting on some ASUS laptops Input: atmel_mxt_ts - split out touchpad initialisation logic Input: atmel_mxt_ts - implement support for T100 touch object Input: cros_ec_keyb - fix clearing keyboard state on wakeup Input: gscps2 - drop pci_ids dependency Input: synaptics - allocate 3 slots to keep stability in image sensors Input: Revert "Revert "synaptics - use dmax in input_mt_assign_slots"" Input: MT - make slot assignment work for overcovered solutions mfd: tc3589x: enforce device-tree only mode Input: tc3589x - localize platform data Input: tsc2007 - Convert msecs to jiffies only once Input: edt-ft5x06 - remove EV_SYN event report Input: edt-ft5x06 - allow to setting the maximum axes value through the DT ...tirimbino
commit
8691c130fa
@ -0,0 +1,108 @@ |
||||
* Broadcom Keypad Controller device tree bindings |
||||
|
||||
Broadcom Keypad controller is used to interface a SoC with a matrix-type |
||||
keypad device. The keypad controller supports multiple row and column lines. |
||||
A key can be placed at each intersection of a unique row and a unique column. |
||||
The keypad controller can sense a key-press and key-release and report the |
||||
event using a interrupt to the cpu. |
||||
|
||||
This binding is based on the matrix-keymap binding with the following |
||||
changes: |
||||
|
||||
keypad,num-rows and keypad,num-columns are required. |
||||
|
||||
Required SoC Specific Properties: |
||||
- compatible: should be "brcm,bcm-keypad" |
||||
|
||||
- reg: physical base address of the controller and length of memory mapped |
||||
region. |
||||
|
||||
- interrupts: The interrupt number to the cpu. |
||||
|
||||
Board Specific Properties: |
||||
- keypad,num-rows: Number of row lines connected to the keypad |
||||
controller. |
||||
|
||||
- keypad,num-columns: Number of column lines connected to the |
||||
keypad controller. |
||||
|
||||
- col-debounce-filter-period: The debounce period for the Column filter. |
||||
|
||||
KEYPAD_DEBOUNCE_1_ms = 0 |
||||
KEYPAD_DEBOUNCE_2_ms = 1 |
||||
KEYPAD_DEBOUNCE_4_ms = 2 |
||||
KEYPAD_DEBOUNCE_8_ms = 3 |
||||
KEYPAD_DEBOUNCE_16_ms = 4 |
||||
KEYPAD_DEBOUNCE_32_ms = 5 |
||||
KEYPAD_DEBOUNCE_64_ms = 6 |
||||
KEYPAD_DEBOUNCE_128_ms = 7 |
||||
|
||||
- status-debounce-filter-period: The debounce period for the Status filter. |
||||
|
||||
KEYPAD_DEBOUNCE_1_ms = 0 |
||||
KEYPAD_DEBOUNCE_2_ms = 1 |
||||
KEYPAD_DEBOUNCE_4_ms = 2 |
||||
KEYPAD_DEBOUNCE_8_ms = 3 |
||||
KEYPAD_DEBOUNCE_16_ms = 4 |
||||
KEYPAD_DEBOUNCE_32_ms = 5 |
||||
KEYPAD_DEBOUNCE_64_ms = 6 |
||||
KEYPAD_DEBOUNCE_128_ms = 7 |
||||
|
||||
- row-output-enabled: An optional property indicating whether the row or |
||||
column is being used as output. If specified the row is being used |
||||
as the output. Else defaults to column. |
||||
|
||||
- pull-up-enabled: An optional property indicating the Keypad scan mode. |
||||
If specified implies the keypad scan pull-up has been enabled. |
||||
|
||||
- autorepeat: Boolean, Enable auto repeat feature of Linux input |
||||
subsystem (optional). |
||||
|
||||
- linux,keymap: The keymap for keys as described in the binding document |
||||
devicetree/bindings/input/matrix-keymap.txt. |
||||
|
||||
Example: |
||||
#include "dt-bindings/input/input.h" |
||||
|
||||
/ { |
||||
keypad: keypad@180ac000 { |
||||
/* Required SoC specific properties */ |
||||
compatible = "brcm,bcm-keypad"; |
||||
|
||||
/* Required Board specific properties */ |
||||
keypad,num-rows = <5>; |
||||
keypad,num-columns = <5>; |
||||
status = "okay"; |
||||
|
||||
linux,keymap = <MATRIX_KEY(0x00, 0x02, KEY_F) /* key_forward */ |
||||
MATRIX_KEY(0x00, 0x03, KEY_HOME) /* key_home */ |
||||
MATRIX_KEY(0x00, 0x04, KEY_M) /* key_message */ |
||||
MATRIX_KEY(0x01, 0x00, KEY_A) /* key_contacts */ |
||||
MATRIX_KEY(0x01, 0x01, KEY_1) /* key_1 */ |
||||
MATRIX_KEY(0x01, 0x02, KEY_2) /* key_2 */ |
||||
MATRIX_KEY(0x01, 0x03, KEY_3) /* key_3 */ |
||||
MATRIX_KEY(0x01, 0x04, KEY_S) /* key_speaker */ |
||||
MATRIX_KEY(0x02, 0x00, KEY_P) /* key_phone */ |
||||
MATRIX_KEY(0x02, 0x01, KEY_4) /* key_4 */ |
||||
MATRIX_KEY(0x02, 0x02, KEY_5) /* key_5 */ |
||||
MATRIX_KEY(0x02, 0x03, KEY_6) /* key_6 */ |
||||
MATRIX_KEY(0x02, 0x04, KEY_VOLUMEUP) /* key_vol_up */ |
||||
MATRIX_KEY(0x03, 0x00, KEY_C) /* key_call_log */ |
||||
MATRIX_KEY(0x03, 0x01, KEY_7) /* key_7 */ |
||||
MATRIX_KEY(0x03, 0x02, KEY_8) /* key_8 */ |
||||
MATRIX_KEY(0x03, 0x03, KEY_9) /* key_9 */ |
||||
MATRIX_KEY(0x03, 0x04, KEY_VOLUMEDOWN) /* key_vol_down */ |
||||
MATRIX_KEY(0x04, 0x00, KEY_H) /* key_headset */ |
||||
MATRIX_KEY(0x04, 0x01, KEY_KPASTERISK) /* key_* */ |
||||
MATRIX_KEY(0x04, 0x02, KEY_0) /* key_0 */ |
||||
MATRIX_KEY(0x04, 0x03, KEY_GRAVE) /* key_# */ |
||||
MATRIX_KEY(0x04, 0x04, KEY_MUTE) /* key_mute */ |
||||
>; |
||||
|
||||
/* Optional board specific properties */ |
||||
col-debounce-filter-period = <5>; |
||||
row-output-enabled; |
||||
pull-up-enabled; |
||||
|
||||
}; |
||||
}; |
@ -0,0 +1,43 @@ |
||||
Qualcomm PM8941 PMIC Power Key |
||||
|
||||
PROPERTIES |
||||
|
||||
- compatible: |
||||
Usage: required |
||||
Value type: <string> |
||||
Definition: must be one of: |
||||
"qcom,pm8941-pwrkey" |
||||
|
||||
- reg: |
||||
Usage: required |
||||
Value type: <prop-encoded-array> |
||||
Definition: base address of registers for block |
||||
|
||||
- interrupts: |
||||
Usage: required |
||||
Value type: <prop-encoded-array> |
||||
Definition: key change 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 released |
||||
for state change interrupt to trigger. |
||||
|
||||
- bias-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@800 { |
||||
compatible = "qcom,pm8941-pwrkey"; |
||||
reg = <0x800>; |
||||
interrupts = <0x0 0x8 0 IRQ_TYPE_EDGE_BOTH>; |
||||
debounce = <15625>; |
||||
bias-pull-up; |
||||
}; |
@ -0,0 +1,76 @@ |
||||
* Broadcom's IPROC Touchscreen Controller |
||||
|
||||
Required properties: |
||||
- compatible: must be "brcm,iproc-touchscreen" |
||||
- reg: physical base address of the controller and length of memory mapped |
||||
region. |
||||
- clocks: The clock provided by the SOC to driver the tsc |
||||
- clock-name: name for the clock |
||||
- interrupts: The touchscreen controller's interrupt |
||||
|
||||
Optional properties: |
||||
- scanning_period: Time between scans. Each step is 1024 us. Valid 1-256. |
||||
- debounce_timeout: Each step is 512 us. Valid 0-255 |
||||
- settling_timeout: The settling duration (in ms) is the amount of time |
||||
the tsc waits to allow the voltage to settle after |
||||
turning on the drivers in detection mode. |
||||
Valid values: 0-11 |
||||
0 = 0.008 ms |
||||
1 = 0.01 ms |
||||
2 = 0.02 ms |
||||
3 = 0.04 ms |
||||
4 = 0.08 ms |
||||
5 = 0.16 ms |
||||
6 = 0.32 ms |
||||
7 = 0.64 ms |
||||
8 = 1.28 ms |
||||
9 = 2.56 ms |
||||
10 = 5.12 ms |
||||
11 = 10.24 ms |
||||
- touch_timeout: The continuous number of scan periods in which touch is |
||||
not detected before the controller returns to idle state. |
||||
Valid values 0-255. |
||||
- average_data: Number of data samples which are averaged before a final |
||||
data point is placed into the FIFO |
||||
Valid values 0-7 |
||||
0 = 1 sample |
||||
1 = 2 samples |
||||
2 = 4 samples |
||||
3 = 8 samples |
||||
4 = 16 samples |
||||
5 = 32 samples |
||||
6 = 64 samples |
||||
7 = 128 samples |
||||
- fifo_threshold: Interrupt is generated whenever the number of fifo |
||||
entries exceeds this value |
||||
Valid values 0-31 |
||||
- touchscreen-size-x: horizontal resolution of touchscreen (in pixels) |
||||
- touchscreen-size-y: vertical resolution of touchscreen (in pixels) |
||||
- touchscreen-fuzz-x: horizontal noise value of the absolute input |
||||
device (in pixels) |
||||
- touchscreen-fuzz-y: vertical noise value of the absolute input |
||||
device (in pixels) |
||||
- touchscreen-inverted-x: X axis is inverted (boolean) |
||||
- touchscreen-inverted-y: Y axis is inverted (boolean) |
||||
|
||||
Example: |
||||
|
||||
touchscreen: tsc@0x180A6000 { |
||||
compatible = "brcm,iproc-touchscreen"; |
||||
#address-cells = <1>; |
||||
#size-cells = <1>; |
||||
reg = <0x180A6000 0x40>; |
||||
clocks = <&adc_clk>; |
||||
clock-names = "tsc_clk"; |
||||
interrupts = <GIC_SPI 164 IRQ_TYPE_LEVEL_HIGH>; |
||||
|
||||
scanning_period = <5>; |
||||
debounce_timeout = <40>; |
||||
settling_timeout = <7>; |
||||
touch_timeout = <10>; |
||||
average_data = <5>; |
||||
fifo_threshold = <1>; |
||||
/* Touchscreen is rotated 180 degrees. */ |
||||
touchscreen-inverted-x; |
||||
touchscreen-inverted-y; |
||||
}; |
@ -0,0 +1,46 @@ |
||||
* ChipOne icn8318 I2C touchscreen controller |
||||
|
||||
Required properties: |
||||
- compatible : "chipone,icn8318" |
||||
- reg : I2C slave address of the chip (0x40) |
||||
- interrupt-parent : a phandle pointing to the interrupt controller |
||||
serving the interrupt for this chip |
||||
- interrupts : interrupt specification for the icn8318 interrupt |
||||
- wake-gpios : GPIO specification for the WAKE input |
||||
- touchscreen-size-x : horizontal resolution of touchscreen (in pixels) |
||||
- touchscreen-size-y : vertical resolution of touchscreen (in pixels) |
||||
|
||||
Optional properties: |
||||
- pinctrl-names : should be "default" |
||||
- pinctrl-0: : a phandle pointing to the pin settings for the |
||||
control gpios |
||||
- touchscreen-fuzz-x : horizontal noise value of the absolute input |
||||
device (in pixels) |
||||
- touchscreen-fuzz-y : vertical noise value of the absolute input |
||||
device (in pixels) |
||||
- touchscreen-inverted-x : X axis is inverted (boolean) |
||||
- touchscreen-inverted-y : Y axis is inverted (boolean) |
||||
- touchscreen-swapped-x-y : X and Y axis are swapped (boolean) |
||||
Swapping is done after inverting the axis |
||||
|
||||
Example: |
||||
|
||||
i2c@00000000 { |
||||
/* ... */ |
||||
|
||||
chipone_icn8318@40 { |
||||
compatible = "chipone,icn8318"; |
||||
reg = <0x40>; |
||||
interrupt-parent = <&pio>; |
||||
interrupts = <9 IRQ_TYPE_EDGE_FALLING>; /* EINT9 (PG9) */ |
||||
pinctrl-names = "default"; |
||||
pinctrl-0 = <&ts_wake_pin_p66>; |
||||
wake-gpios = <&pio 1 3 GPIO_ACTIVE_HIGH>; /* PB3 */ |
||||
touchscreen-size-x = <800>; |
||||
touchscreen-size-y = <480>; |
||||
touchscreen-inverted-x; |
||||
touchscreen-swapped-x-y; |
||||
}; |
||||
|
||||
/* ... */ |
||||
}; |
@ -0,0 +1,29 @@ |
||||
Device tree bindings for Goodix GT9xx series touchscreen controller |
||||
|
||||
Required properties: |
||||
|
||||
- compatible : Should be "goodix,gt911" |
||||
or "goodix,gt9110" |
||||
or "goodix,gt912" |
||||
or "goodix,gt927" |
||||
or "goodix,gt9271" |
||||
or "goodix,gt928" |
||||
or "goodix,gt967" |
||||
- reg : I2C address of the chip. Should be 0x5d or 0x14 |
||||
- interrupt-parent : Interrupt controller to which the chip is connected |
||||
- interrupts : Interrupt to which the chip is connected |
||||
|
||||
Example: |
||||
|
||||
i2c@00000000 { |
||||
/* ... */ |
||||
|
||||
gt928@5d { |
||||
compatible = "goodix,gt928"; |
||||
reg = <0x5d>; |
||||
interrupt-parent = <&gpio>; |
||||
interrupts = <0 0>; |
||||
}; |
||||
|
||||
/* ... */ |
||||
}; |
@ -0,0 +1,16 @@ |
||||
* Semtech SX8654 I2C Touchscreen Controller |
||||
|
||||
Required properties: |
||||
- compatible: must be "semtech,sx8654" |
||||
- reg: i2c slave address |
||||
- interrupt-parent: the phandle for the interrupt controller |
||||
- interrupts: touch controller interrupt |
||||
|
||||
Example: |
||||
|
||||
sx8654@48 { |
||||
compatible = "semtech,sx8654"; |
||||
reg = <0x48>; |
||||
interrupt-parent = <&gpio6>; |
||||
interrupts = <3 IRQ_TYPE_EDGE_FALLING>; |
||||
}; |
@ -0,0 +1,458 @@ |
||||
/*
|
||||
* Copyright (C) 2014 Broadcom 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. |
||||
* |
||||
* 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/bitops.h> |
||||
#include <linux/clk.h> |
||||
#include <linux/gfp.h> |
||||
#include <linux/io.h> |
||||
#include <linux/input.h> |
||||
#include <linux/input/matrix_keypad.h> |
||||
#include <linux/interrupt.h> |
||||
#include <linux/module.h> |
||||
#include <linux/of.h> |
||||
#include <linux/platform_device.h> |
||||
#include <linux/stddef.h> |
||||
#include <linux/types.h> |
||||
|
||||
#define DEFAULT_CLK_HZ 31250 |
||||
#define MAX_ROWS 8 |
||||
#define MAX_COLS 8 |
||||
|
||||
/* Register/field definitions */ |
||||
#define KPCR_OFFSET 0x00000080 |
||||
#define KPCR_MODE 0x00000002 |
||||
#define KPCR_MODE_SHIFT 1 |
||||
#define KPCR_MODE_MASK 1 |
||||
#define KPCR_ENABLE 0x00000001 |
||||
#define KPCR_STATUSFILTERENABLE 0x00008000 |
||||
#define KPCR_STATUSFILTERTYPE_SHIFT 12 |
||||
#define KPCR_COLFILTERENABLE 0x00000800 |
||||
#define KPCR_COLFILTERTYPE_SHIFT 8 |
||||
#define KPCR_ROWWIDTH_SHIFT 20 |
||||
#define KPCR_COLUMNWIDTH_SHIFT 16 |
||||
|
||||
#define KPIOR_OFFSET 0x00000084 |
||||
#define KPIOR_ROWOCONTRL_SHIFT 24 |
||||
#define KPIOR_ROWOCONTRL_MASK 0xFF000000 |
||||
#define KPIOR_COLUMNOCONTRL_SHIFT 16 |
||||
#define KPIOR_COLUMNOCONTRL_MASK 0x00FF0000 |
||||
#define KPIOR_COLUMN_IO_DATA_SHIFT 0 |
||||
|
||||
#define KPEMR0_OFFSET 0x00000090 |
||||
#define KPEMR1_OFFSET 0x00000094 |
||||
#define KPEMR2_OFFSET 0x00000098 |
||||
#define KPEMR3_OFFSET 0x0000009C |
||||
#define KPEMR_EDGETYPE_BOTH 3 |
||||
|
||||
#define KPSSR0_OFFSET 0x000000A0 |
||||
#define KPSSR1_OFFSET 0x000000A4 |
||||
#define KPSSRN_OFFSET(reg_n) (KPSSR0_OFFSET + 4 * (reg_n)) |
||||
#define KPIMR0_OFFSET 0x000000B0 |
||||
#define KPIMR1_OFFSET 0x000000B4 |
||||
#define KPICR0_OFFSET 0x000000B8 |
||||
#define KPICR1_OFFSET 0x000000BC |
||||
#define KPICRN_OFFSET(reg_n) (KPICR0_OFFSET + 4 * (reg_n)) |
||||
#define KPISR0_OFFSET 0x000000C0 |
||||
#define KPISR1_OFFSET 0x000000C4 |
||||
|
||||
#define KPCR_STATUSFILTERTYPE_MAX 7 |
||||
#define KPCR_COLFILTERTYPE_MAX 7 |
||||
|
||||
/* Macros to determine the row/column from a bit that is set in SSR0/1. */ |
||||
#define BIT_TO_ROW_SSRN(bit_nr, reg_n) (((bit_nr) >> 3) + 4 * (reg_n)) |
||||
#define BIT_TO_COL(bit_nr) ((bit_nr) % 8) |
||||
|
||||
/* Structure representing various run-time entities */ |
||||
struct bcm_kp { |
||||
void __iomem *base; |
||||
int irq; |
||||
struct clk *clk; |
||||
struct input_dev *input_dev; |
||||
unsigned long last_state[2]; |
||||
unsigned int n_rows; |
||||
unsigned int n_cols; |
||||
u32 kpcr; |
||||
u32 kpior; |
||||
u32 kpemr; |
||||
u32 imr0_val; |
||||
u32 imr1_val; |
||||
}; |
||||
|
||||
/*
|
||||
* Returns the keycode from the input device keymap given the row and |
||||
* column. |
||||
*/ |
||||
static int bcm_kp_get_keycode(struct bcm_kp *kp, int row, int col) |
||||
{ |
||||
unsigned int row_shift = get_count_order(kp->n_cols); |
||||
unsigned short *keymap = kp->input_dev->keycode; |
||||
|
||||
return keymap[MATRIX_SCAN_CODE(row, col, row_shift)]; |
||||
} |
||||
|
||||
static void bcm_kp_report_keys(struct bcm_kp *kp, int reg_num, int pull_mode) |
||||
{ |
||||
unsigned long state, change; |
||||
int bit_nr; |
||||
int key_press; |
||||
int row, col; |
||||
unsigned int keycode; |
||||
|
||||
/* Clear interrupts */ |
||||
writel(0xFFFFFFFF, kp->base + KPICRN_OFFSET(reg_num)); |
||||
|
||||
state = readl(kp->base + KPSSRN_OFFSET(reg_num)); |
||||
change = kp->last_state[reg_num] ^ state; |
||||
kp->last_state[reg_num] = state; |
||||
|
||||
for_each_set_bit(bit_nr, &change, BITS_PER_LONG) { |
||||
key_press = state & BIT(bit_nr); |
||||
/* The meaning of SSR register depends on pull mode. */ |
||||
key_press = pull_mode ? !key_press : key_press; |
||||
row = BIT_TO_ROW_SSRN(bit_nr, reg_num); |
||||
col = BIT_TO_COL(bit_nr); |
||||
keycode = bcm_kp_get_keycode(kp, row, col); |
||||
input_report_key(kp->input_dev, keycode, key_press); |
||||
} |
||||
} |
||||
|
||||
static irqreturn_t bcm_kp_isr_thread(int irq, void *dev_id) |
||||
{ |
||||
struct bcm_kp *kp = dev_id; |
||||
int pull_mode = (kp->kpcr >> KPCR_MODE_SHIFT) & KPCR_MODE_MASK; |
||||
int reg_num; |
||||
|
||||
for (reg_num = 0; reg_num <= 1; reg_num++) |
||||
bcm_kp_report_keys(kp, reg_num, pull_mode); |
||||
|
||||
input_sync(kp->input_dev); |
||||
|
||||
return IRQ_HANDLED; |
||||
} |
||||
|
||||
static int bcm_kp_start(struct bcm_kp *kp) |
||||
{ |
||||
int error; |
||||
|
||||
if (kp->clk) { |
||||
error = clk_prepare_enable(kp->clk); |
||||
if (error) |
||||
return error; |
||||
} |
||||
|
||||
writel(kp->kpior, kp->base + KPIOR_OFFSET); |
||||
|
||||
writel(kp->imr0_val, kp->base + KPIMR0_OFFSET); |
||||
writel(kp->imr1_val, kp->base + KPIMR1_OFFSET); |
||||
|
||||
writel(kp->kpemr, kp->base + KPEMR0_OFFSET); |
||||
writel(kp->kpemr, kp->base + KPEMR1_OFFSET); |
||||
writel(kp->kpemr, kp->base + KPEMR2_OFFSET); |
||||
writel(kp->kpemr, kp->base + KPEMR3_OFFSET); |
||||
|
||||
writel(0xFFFFFFFF, kp->base + KPICR0_OFFSET); |
||||
writel(0xFFFFFFFF, kp->base + KPICR1_OFFSET); |
||||
|
||||
kp->last_state[0] = readl(kp->base + KPSSR0_OFFSET); |
||||
kp->last_state[0] = readl(kp->base + KPSSR1_OFFSET); |
||||
|
||||
writel(kp->kpcr | KPCR_ENABLE, kp->base + KPCR_OFFSET); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static void bcm_kp_stop(const struct bcm_kp *kp) |
||||
{ |
||||
u32 val; |
||||
|
||||
val = readl(kp->base + KPCR_OFFSET); |
||||
val &= ~KPCR_ENABLE; |
||||
writel(0, kp->base + KPCR_OFFSET); |
||||
writel(0, kp->base + KPIMR0_OFFSET); |
||||
writel(0, kp->base + KPIMR1_OFFSET); |
||||
writel(0xFFFFFFFF, kp->base + KPICR0_OFFSET); |
||||
writel(0xFFFFFFFF, kp->base + KPICR1_OFFSET); |
||||
|
||||
if (kp->clk) |
||||
clk_disable_unprepare(kp->clk); |
||||
} |
||||
|
||||
static int bcm_kp_open(struct input_dev *dev) |
||||
{ |
||||
struct bcm_kp *kp = input_get_drvdata(dev); |
||||
|
||||
return bcm_kp_start(kp); |
||||
} |
||||
|
||||
static void bcm_kp_close(struct input_dev *dev) |
||||
{ |
||||
struct bcm_kp *kp = input_get_drvdata(dev); |
||||
|
||||
bcm_kp_stop(kp); |
||||
} |
||||
|
||||
static int bcm_kp_matrix_key_parse_dt(struct bcm_kp *kp) |
||||
{ |
||||
struct device *dev = kp->input_dev->dev.parent; |
||||
struct device_node *np = dev->of_node; |
||||
int error; |
||||
unsigned int dt_val; |
||||
unsigned int i; |
||||
unsigned int num_rows, col_mask, rows_set; |
||||
|
||||
/* Initialize the KPCR Keypad Configuration Register */ |
||||
kp->kpcr = KPCR_STATUSFILTERENABLE | KPCR_COLFILTERENABLE; |
||||
|
||||
error = matrix_keypad_parse_of_params(dev, &kp->n_rows, &kp->n_cols); |
||||
if (error) { |
||||
dev_err(dev, "failed to parse kp params\n"); |
||||
return error; |
||||
} |
||||
|
||||
/* Set row width for the ASIC block. */ |
||||
kp->kpcr |= (kp->n_rows - 1) << KPCR_ROWWIDTH_SHIFT; |
||||
|
||||
/* Set column width for the ASIC block. */ |
||||
kp->kpcr |= (kp->n_cols - 1) << KPCR_COLUMNWIDTH_SHIFT; |
||||
|
||||
/* Configure the IMR registers */ |
||||
|
||||
/*
|
||||
* IMR registers contain interrupt enable bits for 8x8 matrix |
||||
* IMR0 register format: <row3> <row2> <row1> <row0> |
||||
* IMR1 register format: <row7> <row6> <row5> <row4> |
||||
*/ |
||||
col_mask = (1 << (kp->n_cols)) - 1; |
||||
num_rows = kp->n_rows; |
||||
|
||||
/* Set column bits in rows 0 to 3 in IMR0 */ |
||||
kp->imr0_val = col_mask; |
||||
|
||||
rows_set = 1; |
||||
while (--num_rows && rows_set++ < 4) |
||||
kp->imr0_val |= kp->imr0_val << MAX_COLS; |
||||
|
||||
/* Set column bits in rows 4 to 7 in IMR1 */ |
||||
kp->imr1_val = 0; |
||||
if (num_rows) { |
||||
kp->imr1_val = col_mask; |
||||
while (--num_rows) |
||||
kp->imr1_val |= kp->imr1_val << MAX_COLS; |
||||
} |
||||
|
||||
/* Initialize the KPEMR Keypress Edge Mode Registers */ |
||||
/* Trigger on both edges */ |
||||
kp->kpemr = 0; |
||||
for (i = 0; i <= 30; i += 2) |
||||
kp->kpemr |= (KPEMR_EDGETYPE_BOTH << i); |
||||
|
||||
/*
|
||||
* Obtain the Status filter debounce value and verify against the |
||||
* possible values specified in the DT binding. |
||||
*/ |
||||
of_property_read_u32(np, "status-debounce-filter-period", &dt_val); |
||||
|
||||
if (dt_val > KPCR_STATUSFILTERTYPE_MAX) { |
||||
dev_err(dev, "Invalid Status filter debounce value %d\n", |
||||
dt_val); |
||||
return -EINVAL; |
||||
} |
||||
|
||||
kp->kpcr |= dt_val << KPCR_STATUSFILTERTYPE_SHIFT; |
||||
|
||||
/*
|
||||
* Obtain the Column filter debounce value and verify against the |
||||
* possible values specified in the DT binding. |
||||
*/ |
||||
of_property_read_u32(np, "col-debounce-filter-period", &dt_val); |
||||
|
||||
if (dt_val > KPCR_COLFILTERTYPE_MAX) { |
||||
dev_err(dev, "Invalid Column filter debounce value %d\n", |
||||
dt_val); |
||||
return -EINVAL; |
||||
} |
||||
|
||||
kp->kpcr |= dt_val << KPCR_COLFILTERTYPE_SHIFT; |
||||
|
||||
/*
|
||||
* Determine between the row and column, |
||||
* which should be configured as output. |
||||
*/ |
||||
if (of_property_read_bool(np, "row-output-enabled")) { |
||||
/*
|
||||
* Set RowOContrl or ColumnOContrl in KPIOR |
||||
* to the number of pins to drive as outputs |
||||
*/ |
||||
kp->kpior = ((1 << kp->n_rows) - 1) << |
||||
KPIOR_ROWOCONTRL_SHIFT; |
||||
} else { |
||||
kp->kpior = ((1 << kp->n_cols) - 1) << |
||||
KPIOR_COLUMNOCONTRL_SHIFT; |
||||
} |
||||
|
||||
/*
|
||||
* Determine if the scan pull up needs to be enabled |
||||
*/ |
||||
if (of_property_read_bool(np, "pull-up-enabled")) |
||||
kp->kpcr |= KPCR_MODE; |
||||
|
||||
dev_dbg(dev, "n_rows=%d n_col=%d kpcr=%x kpior=%x kpemr=%x\n", |
||||
kp->n_rows, kp->n_cols, |
||||
kp->kpcr, kp->kpior, kp->kpemr); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
|
||||
static int bcm_kp_probe(struct platform_device *pdev) |
||||
{ |
||||
struct bcm_kp *kp; |
||||
struct input_dev *input_dev; |
||||
struct resource *res; |
||||
int error; |
||||
|
||||
kp = devm_kzalloc(&pdev->dev, sizeof(*kp), GFP_KERNEL); |
||||
if (!kp) |
||||
return -ENOMEM; |
||||
|
||||
input_dev = devm_input_allocate_device(&pdev->dev); |
||||
if (!input_dev) { |
||||
dev_err(&pdev->dev, "failed to allocate the input device\n"); |
||||
return -ENOMEM; |
||||
} |
||||
|
||||
__set_bit(EV_KEY, input_dev->evbit); |
||||
|
||||
/* Enable auto repeat feature of Linux input subsystem */ |
||||
if (of_property_read_bool(pdev->dev.of_node, "autorepeat")) |
||||
__set_bit(EV_REP, input_dev->evbit); |
||||
|
||||
input_dev->name = pdev->name; |
||||
input_dev->phys = "keypad/input0"; |
||||
input_dev->dev.parent = &pdev->dev; |
||||
input_dev->open = bcm_kp_open; |
||||
input_dev->close = bcm_kp_close; |
||||
|
||||
input_dev->id.bustype = BUS_HOST; |
||||
input_dev->id.vendor = 0x0001; |
||||
input_dev->id.product = 0x0001; |
||||
input_dev->id.version = 0x0100; |
||||
|
||||
input_set_drvdata(input_dev, kp); |
||||
|
||||
kp->input_dev = input_dev; |
||||
|
||||
platform_set_drvdata(pdev, kp); |
||||
|
||||
error = bcm_kp_matrix_key_parse_dt(kp); |
||||
if (error) |
||||
return error; |
||||
|
||||
error = matrix_keypad_build_keymap(NULL, NULL, |
||||
kp->n_rows, kp->n_cols, |
||||
NULL, input_dev); |
||||
if (error) { |
||||
dev_err(&pdev->dev, "failed to build keymap\n"); |
||||
return error; |
||||
} |
||||
|
||||
/* Get the KEYPAD base address */ |
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
||||
if (!res) { |
||||
dev_err(&pdev->dev, "Missing keypad base address resource\n"); |
||||
return -ENODEV; |
||||
} |
||||
|
||||
kp->base = devm_ioremap_resource(&pdev->dev, res); |
||||
if (IS_ERR(kp->base)) |
||||
return PTR_ERR(kp->base); |
||||
|
||||
/* Enable clock */ |
||||
kp->clk = devm_clk_get(&pdev->dev, "peri_clk"); |
||||
if (IS_ERR(kp->clk)) { |
||||
error = PTR_ERR(kp->clk); |
||||
if (error != -ENOENT) { |
||||
if (error != -EPROBE_DEFER) |
||||
dev_err(&pdev->dev, "Failed to get clock\n"); |
||||
return error; |
||||
} |
||||
dev_dbg(&pdev->dev, |
||||
"No clock specified. Assuming it's enabled\n"); |
||||
kp->clk = NULL; |
||||
} else { |
||||
unsigned int desired_rate; |
||||
long actual_rate; |
||||
|
||||
error = of_property_read_u32(pdev->dev.of_node, |
||||
"clock-frequency", &desired_rate); |
||||
if (error < 0) |
||||
desired_rate = DEFAULT_CLK_HZ; |
||||
|
||||
actual_rate = clk_round_rate(kp->clk, desired_rate); |
||||
if (actual_rate <= 0) |
||||
return -EINVAL; |
||||
|
||||
error = clk_set_rate(kp->clk, actual_rate); |
||||
if (error) |
||||
return error; |
||||
|
||||
error = clk_prepare_enable(kp->clk); |
||||
if (error) |
||||
return error; |
||||
} |
||||
|
||||
/* Put the kp into a known sane state */ |
||||
bcm_kp_stop(kp); |
||||
|
||||
kp->irq = platform_get_irq(pdev, 0); |
||||
if (kp->irq < 0) { |
||||
dev_err(&pdev->dev, "no IRQ specified\n"); |
||||
return -EINVAL; |
||||
} |
||||
|
||||
error = devm_request_threaded_irq(&pdev->dev, kp->irq, |
||||
NULL, bcm_kp_isr_thread, |
||||
IRQF_ONESHOT, pdev->name, kp); |
||||
if (error) { |
||||
dev_err(&pdev->dev, "failed to request IRQ\n"); |
||||
return error; |
||||
} |
||||
|
||||
error = input_register_device(input_dev); |
||||
if (error) { |
||||
dev_err(&pdev->dev, "failed to register input device\n"); |
||||
return error; |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static const struct of_device_id bcm_kp_of_match[] = { |
||||
{ .compatible = "brcm,bcm-keypad" }, |
||||
{ }, |
||||
}; |
||||
MODULE_DEVICE_TABLE(of, bcm_kp_of_match); |
||||
|
||||
static struct platform_driver bcm_kp_device_driver = { |
||||
.probe = bcm_kp_probe, |
||||
.driver = { |
||||
.name = "bcm-keypad", |
||||
.of_match_table = of_match_ptr(bcm_kp_of_match), |
||||
} |
||||
}; |
||||
|
||||
module_platform_driver(bcm_kp_device_driver); |
||||
|
||||
MODULE_AUTHOR("Broadcom Corporation"); |
||||
MODULE_DESCRIPTION("BCM Keypad Driver"); |
||||
MODULE_LICENSE("GPL v2"); |
@ -0,0 +1,168 @@ |
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License version 2 as |
||||
* published by the Free Software Foundation. |
||||
* |
||||
* h3600 atmel micro companion support, key subdevice |
||||
* based on previous kernel 2.4 version |
||||
* Author : Alessandro Gardich <gremlin@gremlin.it> |
||||
* Author : Linus Walleij <linus.walleij@linaro.org> |
||||
* |
||||
*/ |
||||
#include <linux/module.h> |
||||
#include <linux/init.h> |
||||
#include <linux/fs.h> |
||||
#include <linux/interrupt.h> |
||||
#include <linux/sched.h> |
||||
#include <linux/pm.h> |
||||
#include <linux/sysctl.h> |
||||
#include <linux/proc_fs.h> |
||||
#include <linux/delay.h> |
||||
#include <linux/device.h> |
||||
#include <linux/input.h> |
||||
#include <linux/platform_device.h> |
||||
#include <linux/mfd/ipaq-micro.h> |
||||
|
||||
struct ipaq_micro_keys { |
||||
struct ipaq_micro *micro; |
||||
struct input_dev *input; |
||||
u16 *codes; |
||||
}; |
||||
|
||||
static const u16 micro_keycodes[] = { |
||||
KEY_RECORD, /* 1: Record button */ |
||||
KEY_CALENDAR, /* 2: Calendar */ |
||||
KEY_ADDRESSBOOK, /* 3: Contacts (looks like Outlook) */ |
||||
KEY_MAIL, /* 4: Envelope (Q on older iPAQs) */ |
||||
KEY_HOMEPAGE, /* 5: Start (looks like swoopy arrow) */ |
||||
KEY_UP, /* 6: Up */ |
||||
KEY_RIGHT, /* 7: Right */ |
||||
KEY_LEFT, /* 8: Left */ |
||||
KEY_DOWN, /* 9: Down */ |
||||
}; |
||||
|
||||
static void micro_key_receive(void *data, int len, unsigned char *msg) |
||||
{ |
||||
struct ipaq_micro_keys *keys = data; |
||||
int key, down; |
||||
|
||||
down = 0x80 & msg[0]; |
||||
key = 0x7f & msg[0]; |
||||
|
||||
if (key < ARRAY_SIZE(micro_keycodes)) { |
||||
input_report_key(keys->input, keys->codes[key], down); |
||||
input_sync(keys->input); |
||||
} |
||||
} |
||||
|
||||
static void micro_key_start(struct ipaq_micro_keys *keys) |
||||
{ |
||||
spin_lock(&keys->micro->lock); |
||||
keys->micro->key = micro_key_receive; |
||||
keys->micro->key_data = keys; |
||||
spin_unlock(&keys->micro->lock); |
||||
} |
||||
|
||||
static void micro_key_stop(struct ipaq_micro_keys *keys) |
||||
{ |
||||
spin_lock(&keys->micro->lock); |
||||
keys->micro->key = NULL; |
||||
keys->micro->key_data = NULL; |
||||
spin_unlock(&keys->micro->lock); |
||||
} |
||||
|
||||
static int micro_key_open(struct input_dev *input) |
||||
{ |
||||
struct ipaq_micro_keys *keys = input_get_drvdata(input); |
||||
|
||||
micro_key_start(keys); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static void micro_key_close(struct input_dev *input) |
||||
{ |
||||
struct ipaq_micro_keys *keys = input_get_drvdata(input); |
||||
|
||||
micro_key_stop(keys); |
||||
} |
||||
|
||||
static int micro_key_probe(struct platform_device *pdev) |
||||
{ |
||||
struct ipaq_micro_keys *keys; |
||||
int error; |
||||
int i; |
||||
|
||||
keys = devm_kzalloc(&pdev->dev, sizeof(*keys), GFP_KERNEL); |
||||
if (!keys) |
||||
return -ENOMEM; |
||||
|
||||
keys->micro = dev_get_drvdata(pdev->dev.parent); |
||||
|
||||
keys->input = devm_input_allocate_device(&pdev->dev); |
||||
if (!keys->input) |
||||
return -ENOMEM; |
||||
|
||||
keys->input->keycodesize = sizeof(micro_keycodes[0]); |
||||
keys->input->keycodemax = ARRAY_SIZE(micro_keycodes); |
||||
keys->codes = devm_kmemdup(&pdev->dev, micro_keycodes, |
||||
keys->input->keycodesize * keys->input->keycodemax, |
||||
GFP_KERNEL); |
||||
keys->input->keycode = keys->codes; |
||||
|
||||
__set_bit(EV_KEY, keys->input->evbit); |
||||
for (i = 0; i < ARRAY_SIZE(micro_keycodes); i++) |
||||
__set_bit(micro_keycodes[i], keys->input->keybit); |
||||
|
||||
keys->input->name = "h3600 micro keys"; |
||||
keys->input->open = micro_key_open; |
||||
keys->input->close = micro_key_close; |
||||
input_set_drvdata(keys->input, keys); |
||||
|
||||
error = input_register_device(keys->input); |
||||
if (error) |
||||
return error; |
||||
|
||||
platform_set_drvdata(pdev, keys); |
||||
return 0; |
||||
} |
||||
|
||||
static int __maybe_unused micro_key_suspend(struct device *dev) |
||||
{ |
||||
struct ipaq_micro_keys *keys = dev_get_drvdata(dev); |
||||
|
||||
micro_key_stop(keys); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static int __maybe_unused micro_key_resume(struct device *dev) |
||||
{ |
||||
struct ipaq_micro_keys *keys = dev_get_drvdata(dev); |
||||
struct input_dev *input = keys->input; |
||||
|
||||
mutex_lock(&input->mutex); |
||||
|
||||
if (input->users) |
||||
micro_key_start(keys); |
||||
|
||||
mutex_unlock(&input->mutex); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static SIMPLE_DEV_PM_OPS(micro_key_dev_pm_ops, |
||||
micro_key_suspend, micro_key_resume); |
||||
|
||||
static struct platform_driver micro_key_device_driver = { |
||||
.driver = { |
||||
.name = "ipaq-micro-keys", |
||||
.pm = µ_key_dev_pm_ops, |
||||
}, |
||||
.probe = micro_key_probe, |
||||
}; |
||||
module_platform_driver(micro_key_device_driver); |
||||
|
||||
MODULE_LICENSE("GPL"); |
||||
MODULE_DESCRIPTION("driver for iPAQ Atmel micro keys"); |
||||
MODULE_ALIAS("platform:ipaq-micro-keys"); |
@ -0,0 +1,358 @@ |
||||
/*
|
||||
* MAXIM MAX77693 Haptic device driver |
||||
* |
||||
* Copyright (C) 2015 Samsung Electronics |
||||
* Author: Jaewon Kim <jaewon02.kim@samsung.com> |
||||
* |
||||
* 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/err.h> |
||||
#include <linux/i2c.h> |
||||
#include <linux/init.h> |
||||
#include <linux/input.h> |
||||
#include <linux/mfd/max77843-private.h> |
||||
#include <linux/module.h> |
||||
#include <linux/platform_device.h> |
||||
#include <linux/pwm.h> |
||||
#include <linux/regmap.h> |
||||
#include <linux/regulator/consumer.h> |
||||
#include <linux/slab.h> |
||||
#include <linux/workqueue.h> |
||||
|
||||
#define MAX_MAGNITUDE_SHIFT 16 |
||||
|
||||
enum max77843_haptic_motor_type { |
||||
MAX77843_HAPTIC_ERM = 0, |
||||
MAX77843_HAPTIC_LRA, |
||||
}; |
||||
|
||||
enum max77843_haptic_pwm_divisor { |
||||
MAX77843_HAPTIC_PWM_DIVISOR_32 = 0, |
||||
MAX77843_HAPTIC_PWM_DIVISOR_64, |
||||
MAX77843_HAPTIC_PWM_DIVISOR_128, |
||||
MAX77843_HAPTIC_PWM_DIVISOR_256, |
||||
}; |
||||
|
||||
struct max77843_haptic { |
||||
struct regmap *regmap_haptic; |
||||
struct device *dev; |
||||
struct input_dev *input_dev; |
||||
struct pwm_device *pwm_dev; |
||||
struct regulator *motor_reg; |
||||
struct work_struct work; |
||||
struct mutex mutex; |
||||
|
||||
unsigned int magnitude; |
||||
unsigned int pwm_duty; |
||||
|
||||
bool active; |
||||
bool suspended; |
||||
|
||||
enum max77843_haptic_motor_type type; |
||||
enum max77843_haptic_pwm_divisor pwm_divisor; |
||||
}; |
||||
|
||||
static int max77843_haptic_set_duty_cycle(struct max77843_haptic *haptic) |
||||
{ |
||||
int delta = (haptic->pwm_dev->period + haptic->pwm_duty) / 2; |
||||
int error; |
||||
|
||||
error = pwm_config(haptic->pwm_dev, delta, haptic->pwm_dev->period); |
||||
if (error) { |
||||
dev_err(haptic->dev, "failed to configure pwm: %d\n", error); |
||||
return error; |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static int max77843_haptic_bias(struct max77843_haptic *haptic, bool on) |
||||
{ |
||||
int error; |
||||
|
||||
error = regmap_update_bits(haptic->regmap_haptic, |
||||
MAX77843_SYS_REG_MAINCTRL1, |
||||
MAX77843_MAINCTRL1_BIASEN_MASK, |
||||
on << MAINCTRL1_BIASEN_SHIFT); |
||||
if (error) { |
||||
dev_err(haptic->dev, "failed to %s bias: %d\n", |
||||
on ? "enable" : "disable", error); |
||||
return error; |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static int max77843_haptic_config(struct max77843_haptic *haptic, bool enable) |
||||
{ |
||||
unsigned int value; |
||||
int error; |
||||
|
||||
value = (haptic->type << MCONFIG_MODE_SHIFT) | |
||||
(enable << MCONFIG_MEN_SHIFT) | |
||||
(haptic->pwm_divisor << MCONFIG_PDIV_SHIFT); |
||||
|
||||
error = regmap_write(haptic->regmap_haptic, |
||||
MAX77843_HAP_REG_MCONFIG, value); |
||||
if (error) { |
||||
dev_err(haptic->dev, |
||||
"failed to update haptic config: %d\n", error); |
||||
return error; |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static int max77843_haptic_enable(struct max77843_haptic *haptic) |
||||
{ |
||||
int error; |
||||
|
||||
if (haptic->active) |
||||
return 0; |
||||
|
||||
error = pwm_enable(haptic->pwm_dev); |
||||
if (error) { |
||||
dev_err(haptic->dev, |
||||
"failed to enable pwm device: %d\n", error); |
||||
return error; |
||||
} |
||||
|
||||
error = max77843_haptic_config(haptic, true); |
||||
if (error) |
||||
goto err_config; |
||||
|
||||
haptic->active = true; |
||||
|
||||
return 0; |
||||
|
||||
err_config: |
||||
pwm_disable(haptic->pwm_dev); |
||||
|
||||
return error; |
||||
} |
||||
|
||||
static int max77843_haptic_disable(struct max77843_haptic *haptic) |
||||
{ |
||||
int error; |
||||
|
||||
if (!haptic->active) |
||||
return 0; |
||||
|
||||
error = max77843_haptic_config(haptic, false); |
||||
if (error) |
||||
return error; |
||||
|
||||
pwm_disable(haptic->pwm_dev); |
||||
|
||||
haptic->active = false; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static void max77843_haptic_play_work(struct work_struct *work) |
||||
{ |
||||
struct max77843_haptic *haptic = |
||||
container_of(work, struct max77843_haptic, work); |
||||
int error; |
||||
|
||||
mutex_lock(&haptic->mutex); |
||||
|
||||
if (haptic->suspended) |
||||
goto out_unlock; |
||||
|
||||
if (haptic->magnitude) { |
||||
error = max77843_haptic_set_duty_cycle(haptic); |
||||
if (error) { |
||||
dev_err(haptic->dev, |
||||
"failed to set duty cycle: %d\n", error); |
||||
goto out_unlock; |
||||
} |
||||
|
||||
error = max77843_haptic_enable(haptic); |
||||
if (error) |
||||
dev_err(haptic->dev, |
||||
"cannot enable haptic: %d\n", error); |
||||
} else { |
||||
error = max77843_haptic_disable(haptic); |
||||
if (error) |
||||
dev_err(haptic->dev, |
||||
"cannot disable haptic: %d\n", error); |
||||
} |
||||
|
||||
out_unlock: |
||||
mutex_unlock(&haptic->mutex); |
||||
} |
||||
|
||||
static int max77843_haptic_play_effect(struct input_dev *dev, void *data, |
||||
struct ff_effect *effect) |
||||
{ |
||||
struct max77843_haptic *haptic = input_get_drvdata(dev); |
||||
u64 period_mag_multi; |
||||
|
||||
haptic->magnitude = effect->u.rumble.strong_magnitude; |
||||
if (!haptic->magnitude) |
||||
haptic->magnitude = effect->u.rumble.weak_magnitude; |
||||
|
||||
period_mag_multi = (u64)haptic->pwm_dev->period * haptic->magnitude; |
||||
haptic->pwm_duty = (unsigned int)(period_mag_multi >> |
||||
MAX_MAGNITUDE_SHIFT); |
||||
|
||||
schedule_work(&haptic->work); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static int max77843_haptic_open(struct input_dev *dev) |
||||
{ |
||||
struct max77843_haptic *haptic = input_get_drvdata(dev); |
||||
int error; |
||||
|
||||
error = max77843_haptic_bias(haptic, true); |
||||
if (error) |
||||
return error; |
||||
|
||||
error = regulator_enable(haptic->motor_reg); |
||||
if (error) { |
||||
dev_err(haptic->dev, |
||||
"failed to enable regulator: %d\n", error); |
||||
return error; |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static void max77843_haptic_close(struct input_dev *dev) |
||||
{ |
||||
struct max77843_haptic *haptic = input_get_drvdata(dev); |
||||
int error; |
||||
|
||||
cancel_work_sync(&haptic->work); |
||||
max77843_haptic_disable(haptic); |
||||
|
||||
error = regulator_disable(haptic->motor_reg); |
||||
if (error) |
||||
dev_err(haptic->dev, |
||||
"failed to disable regulator: %d\n", error); |
||||
|
||||
max77843_haptic_bias(haptic, false); |
||||
} |
||||
|
||||
static int max77843_haptic_probe(struct platform_device *pdev) |
||||
{ |
||||
struct max77843 *max77843 = dev_get_drvdata(pdev->dev.parent); |
||||
struct max77843_haptic *haptic; |
||||
int error; |
||||
|
||||
haptic = devm_kzalloc(&pdev->dev, sizeof(*haptic), GFP_KERNEL); |
||||
if (!haptic) |
||||
return -ENOMEM; |
||||
|
||||
haptic->regmap_haptic = max77843->regmap; |
||||
haptic->dev = &pdev->dev; |
||||
haptic->type = MAX77843_HAPTIC_LRA; |
||||
haptic->pwm_divisor = MAX77843_HAPTIC_PWM_DIVISOR_128; |
||||
|
||||
INIT_WORK(&haptic->work, max77843_haptic_play_work); |
||||
mutex_init(&haptic->mutex); |
||||
|
||||
haptic->pwm_dev = devm_pwm_get(&pdev->dev, NULL); |
||||
if (IS_ERR(haptic->pwm_dev)) { |
||||
dev_err(&pdev->dev, "failed to get pwm device\n"); |
||||
return PTR_ERR(haptic->pwm_dev); |
||||
} |
||||
|
||||
haptic->motor_reg = devm_regulator_get_exclusive(&pdev->dev, "haptic"); |
||||
if (IS_ERR(haptic->motor_reg)) { |
||||
dev_err(&pdev->dev, "failed to get regulator\n"); |
||||
return PTR_ERR(haptic->motor_reg); |
||||
} |
||||
|
||||
haptic->input_dev = devm_input_allocate_device(&pdev->dev); |
||||
if (!haptic->input_dev) { |
||||
dev_err(&pdev->dev, "failed to allocate input device\n"); |
||||
return -ENOMEM; |
||||
} |
||||
|
||||
haptic->input_dev->name = "max77843-haptic"; |
||||
haptic->input_dev->id.version = 1; |
||||
haptic->input_dev->dev.parent = &pdev->dev; |
||||
haptic->input_dev->open = max77843_haptic_open; |
||||
haptic->input_dev->close = max77843_haptic_close; |
||||
input_set_drvdata(haptic->input_dev, haptic); |
||||
input_set_capability(haptic->input_dev, EV_FF, FF_RUMBLE); |
||||
|
||||
error = input_ff_create_memless(haptic->input_dev, NULL, |
||||
max77843_haptic_play_effect); |
||||
if (error) { |
||||
dev_err(&pdev->dev, "failed to create force-feedback\n"); |
||||
return error; |
||||
} |
||||
|
||||
error = input_register_device(haptic->input_dev); |
||||
if (error) { |
||||
dev_err(&pdev->dev, "failed to register input device\n"); |
||||
return error; |
||||
} |
||||
|
||||
platform_set_drvdata(pdev, haptic); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static int __maybe_unused max77843_haptic_suspend(struct device *dev) |
||||
{ |
||||
struct platform_device *pdev = to_platform_device(dev); |
||||
struct max77843_haptic *haptic = platform_get_drvdata(pdev); |
||||
int error; |
||||
|
||||
error = mutex_lock_interruptible(&haptic->mutex); |
||||
if (error) |
||||
return error; |
||||
|
||||
max77843_haptic_disable(haptic); |
||||
|
||||
haptic->suspended = true; |
||||
|
||||
mutex_unlock(&haptic->mutex); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static int __maybe_unused max77843_haptic_resume(struct device *dev) |
||||
{ |
||||
struct platform_device *pdev = to_platform_device(dev); |
||||
struct max77843_haptic *haptic = platform_get_drvdata(pdev); |
||||
unsigned int magnitude; |
||||
|
||||
mutex_lock(&haptic->mutex); |
||||
|
||||
haptic->suspended = false; |
||||
|
||||
magnitude = ACCESS_ONCE(haptic->magnitude); |
||||
if (magnitude) |
||||
max77843_haptic_enable(haptic); |
||||
|
||||
mutex_unlock(&haptic->mutex); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static SIMPLE_DEV_PM_OPS(max77843_haptic_pm_ops, |
||||
max77843_haptic_suspend, max77843_haptic_resume); |
||||
|
||||
static struct platform_driver max77843_haptic_driver = { |
||||
.driver = { |
||||
.name = "max77843-haptic", |
||||
.pm = &max77843_haptic_pm_ops, |
||||
}, |
||||
.probe = max77843_haptic_probe, |
||||
}; |
||||
module_platform_driver(max77843_haptic_driver); |
||||
|
||||
MODULE_AUTHOR("Jaewon Kim <jaewon02.kim@samsung.com>"); |
||||
MODULE_DESCRIPTION("MAXIM MAX77843 Haptic driver"); |
||||
MODULE_LICENSE("GPL"); |
@ -0,0 +1,293 @@ |
||||
/*
|
||||
* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved. |
||||
* Copyright (c) 2014, Sony Mobile Communications Inc. |
||||
* |
||||
* 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. |
||||
*/ |
||||
|
||||
#include <linux/delay.h> |
||||
#include <linux/errno.h> |
||||
#include <linux/input.h> |
||||
#include <linux/interrupt.h> |
||||
#include <linux/kernel.h> |
||||
#include <linux/log2.h> |
||||
#include <linux/module.h> |
||||
#include <linux/of.h> |
||||
#include <linux/platform_device.h> |
||||
#include <linux/reboot.h> |
||||
#include <linux/regmap.h> |
||||
|
||||
#define PON_REV2 0x01 |
||||
|
||||
#define PON_RT_STS 0x10 |
||||
#define PON_KPDPWR_N_SET BIT(0) |
||||
|
||||
#define PON_PS_HOLD_RST_CTL 0x5a |
||||
#define PON_PS_HOLD_RST_CTL2 0x5b |
||||
#define PON_PS_HOLD_ENABLE BIT(7) |
||||
#define PON_PS_HOLD_TYPE_MASK 0x0f |
||||
#define PON_PS_HOLD_TYPE_SHUTDOWN 4 |
||||
#define PON_PS_HOLD_TYPE_HARD_RESET 7 |
||||
|
||||
#define PON_PULL_CTL 0x70 |
||||
#define PON_KPDPWR_PULL_UP BIT(1) |
||||
|
||||
#define PON_DBC_CTL 0x71 |
||||
#define PON_DBC_DELAY_MASK 0x7 |
||||
|
||||
|
||||
struct pm8941_pwrkey { |
||||
struct device *dev; |
||||
int irq; |
||||
u32 baseaddr; |
||||
struct regmap *regmap; |
||||
struct input_dev *input; |
||||
|
||||
unsigned int revision; |
||||
struct notifier_block reboot_notifier; |
||||
}; |
||||
|
||||
static int pm8941_reboot_notify(struct notifier_block *nb, |
||||
unsigned long code, void *unused) |
||||
{ |
||||
struct pm8941_pwrkey *pwrkey = container_of(nb, struct pm8941_pwrkey, |
||||
reboot_notifier); |
||||
unsigned int enable_reg; |
||||
unsigned int reset_type; |
||||
int error; |
||||
|
||||
/* PMICs with revision 0 have the enable bit in same register as ctrl */ |
||||
if (pwrkey->revision == 0) |
||||
enable_reg = PON_PS_HOLD_RST_CTL; |
||||
else |
||||
enable_reg = PON_PS_HOLD_RST_CTL2; |
||||
|
||||
error = regmap_update_bits(pwrkey->regmap, |
||||
pwrkey->baseaddr + enable_reg, |
||||
PON_PS_HOLD_ENABLE, |
||||
0); |
||||
if (error) |
||||
dev_err(pwrkey->dev, |
||||
"unable to clear ps hold reset enable: %d\n", |
||||
error); |
||||
|
||||
/*
|
||||
* Updates of PON_PS_HOLD_ENABLE requires 3 sleep cycles between |
||||
* writes. |
||||
*/ |
||||
usleep_range(100, 1000); |
||||
|
||||
switch (code) { |
||||
case SYS_HALT: |
||||
case SYS_POWER_OFF: |
||||
reset_type = PON_PS_HOLD_TYPE_SHUTDOWN; |
||||
break; |
||||
case SYS_RESTART: |
||||
default: |
||||
reset_type = PON_PS_HOLD_TYPE_HARD_RESET; |
||||
break; |
||||
}; |
||||
|
||||
error = regmap_update_bits(pwrkey->regmap, |
||||
pwrkey->baseaddr + PON_PS_HOLD_RST_CTL, |
||||
PON_PS_HOLD_TYPE_MASK, |
||||
reset_type); |
||||
if (error) |
||||
dev_err(pwrkey->dev, "unable to set ps hold reset type: %d\n", |
||||
error); |
||||
|
||||
error = regmap_update_bits(pwrkey->regmap, |
||||
pwrkey->baseaddr + enable_reg, |
||||
PON_PS_HOLD_ENABLE, |
||||
PON_PS_HOLD_ENABLE); |
||||
if (error) |
||||
dev_err(pwrkey->dev, "unable to re-set enable: %d\n", error); |
||||
|
||||
return NOTIFY_DONE; |
||||
} |
||||
|
||||
static irqreturn_t pm8941_pwrkey_irq(int irq, void *_data) |
||||
{ |
||||
struct pm8941_pwrkey *pwrkey = _data; |
||||
unsigned int sts; |
||||
int error; |
||||
|
||||
error = regmap_read(pwrkey->regmap, |
||||
pwrkey->baseaddr + PON_RT_STS, &sts); |
||||
if (error) |
||||
return IRQ_HANDLED; |
||||
|
||||
input_report_key(pwrkey->input, KEY_POWER, !!(sts & PON_KPDPWR_N_SET)); |
||||
input_sync(pwrkey->input); |
||||
|
||||
return IRQ_HANDLED; |
||||
} |
||||
|
||||
static int __maybe_unused pm8941_pwrkey_suspend(struct device *dev) |
||||
{ |
||||
struct pm8941_pwrkey *pwrkey = dev_get_drvdata(dev); |
||||
|
||||
if (device_may_wakeup(dev)) |
||||
enable_irq_wake(pwrkey->irq); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static int __maybe_unused pm8941_pwrkey_resume(struct device *dev) |
||||
{ |
||||
struct pm8941_pwrkey *pwrkey = dev_get_drvdata(dev); |
||||
|
||||
if (device_may_wakeup(dev)) |
||||
disable_irq_wake(pwrkey->irq); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static SIMPLE_DEV_PM_OPS(pm8941_pwr_key_pm_ops, |
||||
pm8941_pwrkey_suspend, pm8941_pwrkey_resume); |
||||
|
||||
static int pm8941_pwrkey_probe(struct platform_device *pdev) |
||||
{ |
||||
struct pm8941_pwrkey *pwrkey; |
||||
bool pull_up; |
||||
u32 req_delay; |
||||
int error; |
||||
|
||||
if (of_property_read_u32(pdev->dev.of_node, "debounce", &req_delay)) |
||||
req_delay = 15625; |
||||
|
||||
if (req_delay > 2000000 || req_delay == 0) { |
||||
dev_err(&pdev->dev, "invalid debounce time: %u\n", req_delay); |
||||
return -EINVAL; |
||||
} |
||||
|
||||
pull_up = of_property_read_bool(pdev->dev.of_node, "bias-pull-up"); |
||||
|
||||
pwrkey = devm_kzalloc(&pdev->dev, sizeof(*pwrkey), GFP_KERNEL); |
||||
if (!pwrkey) |
||||
return -ENOMEM; |
||||
|
||||
pwrkey->dev = &pdev->dev; |
||||
|
||||
pwrkey->regmap = dev_get_regmap(pdev->dev.parent, NULL); |
||||
if (!pwrkey->regmap) { |
||||
dev_err(&pdev->dev, "failed to locate regmap\n"); |
||||
return -ENODEV; |
||||
} |
||||
|
||||
pwrkey->irq = platform_get_irq(pdev, 0); |
||||
if (pwrkey->irq < 0) { |
||||
dev_err(&pdev->dev, "failed to get irq\n"); |
||||
return pwrkey->irq; |
||||
} |
||||
|
||||
error = of_property_read_u32(pdev->dev.of_node, "reg", |
||||
&pwrkey->baseaddr); |
||||
if (error) |
||||
return error; |
||||
|
||||
error = regmap_read(pwrkey->regmap, pwrkey->baseaddr + PON_REV2, |
||||
&pwrkey->revision); |
||||
if (error) { |
||||
dev_err(&pdev->dev, "failed to set debounce: %d\n", error); |
||||
return error; |
||||
} |
||||
|
||||
pwrkey->input = devm_input_allocate_device(&pdev->dev); |
||||
if (!pwrkey->input) { |
||||
dev_dbg(&pdev->dev, "unable to allocate input device\n"); |
||||
return -ENOMEM; |
||||
} |
||||
|
||||
input_set_capability(pwrkey->input, EV_KEY, KEY_POWER); |
||||
|
||||
pwrkey->input->name = "pm8941_pwrkey"; |
||||
pwrkey->input->phys = "pm8941_pwrkey/input0"; |
||||
|
||||
req_delay = (req_delay << 6) / USEC_PER_SEC; |
||||
req_delay = ilog2(req_delay); |
||||
|
||||
error = regmap_update_bits(pwrkey->regmap, |
||||
pwrkey->baseaddr + PON_DBC_CTL, |
||||
PON_DBC_DELAY_MASK, |
||||
req_delay); |
||||
if (error) { |
||||
dev_err(&pdev->dev, "failed to set debounce: %d\n", error); |
||||
return error; |
||||
} |
||||
|
||||
error = regmap_update_bits(pwrkey->regmap, |
||||
pwrkey->baseaddr + PON_PULL_CTL, |
||||
PON_KPDPWR_PULL_UP, |
||||
pull_up ? PON_KPDPWR_PULL_UP : 0); |
||||
if (error) { |
||||
dev_err(&pdev->dev, "failed to set pull: %d\n", error); |
||||
return error; |
||||
} |
||||
|
||||
error = devm_request_threaded_irq(&pdev->dev, pwrkey->irq, |
||||
NULL, pm8941_pwrkey_irq, |
||||
IRQF_ONESHOT, |
||||
"pm8941_pwrkey", pwrkey); |
||||
if (error) { |
||||
dev_err(&pdev->dev, "failed requesting IRQ: %d\n", error); |
||||
return error; |
||||
} |
||||
|
||||
error = input_register_device(pwrkey->input); |
||||
if (error) { |
||||
dev_err(&pdev->dev, "failed to register input device: %d\n", |
||||
error); |
||||
return error; |
||||
} |
||||
|
||||
pwrkey->reboot_notifier.notifier_call = pm8941_reboot_notify, |
||||
error = register_reboot_notifier(&pwrkey->reboot_notifier); |
||||
if (error) { |
||||
dev_err(&pdev->dev, "failed to register reboot notifier: %d\n", |
||||
error); |
||||
return error; |
||||
} |
||||
|
||||
platform_set_drvdata(pdev, pwrkey); |
||||
device_init_wakeup(&pdev->dev, 1); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static int pm8941_pwrkey_remove(struct platform_device *pdev) |
||||
{ |
||||
struct pm8941_pwrkey *pwrkey = platform_get_drvdata(pdev); |
||||
|
||||
device_init_wakeup(&pdev->dev, 0); |
||||
unregister_reboot_notifier(&pwrkey->reboot_notifier); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static const struct of_device_id pm8941_pwr_key_id_table[] = { |
||||
{ .compatible = "qcom,pm8941-pwrkey" }, |
||||
{ } |
||||
}; |
||||
MODULE_DEVICE_TABLE(of, pm8941_pwr_key_id_table); |
||||
|
||||
static struct platform_driver pm8941_pwrkey_driver = { |
||||
.probe = pm8941_pwrkey_probe, |
||||
.remove = pm8941_pwrkey_remove, |
||||
.driver = { |
||||
.name = "pm8941-pwrkey", |
||||
.pm = &pm8941_pwr_key_pm_ops, |
||||
.of_match_table = of_match_ptr(pm8941_pwr_key_id_table), |
||||
}, |
||||
}; |
||||
module_platform_driver(pm8941_pwrkey_driver); |
||||
|
||||
MODULE_DESCRIPTION("PM8941 Power Key driver"); |
||||
MODULE_LICENSE("GPL v2"); |
@ -0,0 +1,522 @@ |
||||
/*
|
||||
* Copyright (C) 2015 Broadcom 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. |
||||
* |
||||
* 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/init.h> |
||||
#include <linux/input.h> |
||||
#include <linux/delay.h> |
||||
#include <linux/interrupt.h> |
||||
#include <linux/keyboard.h> |
||||
#include <linux/platform_device.h> |
||||
#include <linux/slab.h> |
||||
#include <linux/of.h> |
||||
#include <asm/irq.h> |
||||
#include <linux/io.h> |
||||
#include <linux/clk.h> |
||||
#include <linux/serio.h> |
||||
|
||||
#define IPROC_TS_NAME "iproc-ts" |
||||
|
||||
#define PEN_DOWN_STATUS 1 |
||||
#define PEN_UP_STATUS 0 |
||||
|
||||
#define X_MIN 0 |
||||
#define Y_MIN 0 |
||||
#define X_MAX 0xFFF |
||||
#define Y_MAX 0xFFF |
||||
|
||||
/* Value given by controller for invalid coordinate. */ |
||||
#define INVALID_COORD 0xFFFFFFFF |
||||
|
||||
/* Register offsets */ |
||||
#define REGCTL1 0x00 |
||||
#define REGCTL2 0x04 |
||||
#define INTERRUPT_THRES 0x08 |
||||
#define INTERRUPT_MASK 0x0c |
||||
|
||||
#define INTERRUPT_STATUS 0x10 |
||||
#define CONTROLLER_STATUS 0x14 |
||||
#define FIFO_DATA 0x18 |
||||
#define FIFO_DATA_X_Y_MASK 0xFFFF |
||||
#define ANALOG_CONTROL 0x1c |
||||
|
||||
#define AUX_DATA 0x20 |
||||
#define DEBOUNCE_CNTR_STAT 0x24 |
||||
#define SCAN_CNTR_STAT 0x28 |
||||
#define REM_CNTR_STAT 0x2c |
||||
|
||||
#define SETTLING_TIMER_STAT 0x30 |
||||
#define SPARE_REG 0x34 |
||||
#define SOFT_BYPASS_CONTROL 0x38 |
||||
#define SOFT_BYPASS_DATA 0x3c |
||||
|
||||
|
||||
/* Bit values for INTERRUPT_MASK and INTERRUPT_STATUS regs */ |
||||
#define TS_PEN_INTR_MASK BIT(0) |
||||
#define TS_FIFO_INTR_MASK BIT(2) |
||||
|
||||
/* Bit values for CONTROLLER_STATUS reg1 */ |
||||
#define TS_PEN_DOWN BIT(0) |
||||
|
||||
/* Shift values for control reg1 */ |
||||
#define SCANNING_PERIOD_SHIFT 24 |
||||
#define DEBOUNCE_TIMEOUT_SHIFT 16 |
||||
#define SETTLING_TIMEOUT_SHIFT 8 |
||||
#define TOUCH_TIMEOUT_SHIFT 0 |
||||
|
||||
/* Shift values for coordinates from fifo */ |
||||
#define X_COORD_SHIFT 0 |
||||
#define Y_COORD_SHIFT 16 |
||||
|
||||
/* Bit values for REGCTL2 */ |
||||
#define TS_CONTROLLER_EN_BIT BIT(16) |
||||
#define TS_CONTROLLER_AVGDATA_SHIFT 8 |
||||
#define TS_CONTROLLER_AVGDATA_MASK (0x7 << TS_CONTROLLER_AVGDATA_SHIFT) |
||||
#define TS_CONTROLLER_PWR_LDO BIT(5) |
||||
#define TS_CONTROLLER_PWR_ADC BIT(4) |
||||
#define TS_CONTROLLER_PWR_BGP BIT(3) |
||||
#define TS_CONTROLLER_PWR_TS BIT(2) |
||||
#define TS_WIRE_MODE_BIT BIT(1) |
||||
|
||||
#define dbg_reg(dev, priv, reg) \ |
||||
dev_dbg(dev, "%20s= 0x%08x\n", #reg, readl((priv)->regs + reg)) |
||||
|
||||
struct tsc_param { |
||||
/* Each step is 1024 us. Valid 1-256 */ |
||||
u32 scanning_period; |
||||
|
||||
/* Each step is 512 us. Valid 0-255 */ |
||||
u32 debounce_timeout; |
||||
|
||||
/*
|
||||
* The settling duration (in ms) is the amount of time the tsc |
||||
* waits to allow the voltage to settle after turning on the |
||||
* drivers in detection mode. Valid values: 0-11 |
||||
* 0 = 0.008 ms |
||||
* 1 = 0.01 ms |
||||
* 2 = 0.02 ms |
||||
* 3 = 0.04 ms |
||||
* 4 = 0.08 ms |
||||
* 5 = 0.16 ms |
||||
* 6 = 0.32 ms |
||||
* 7 = 0.64 ms |
||||
* 8 = 1.28 ms |
||||
* 9 = 2.56 ms |
||||
* 10 = 5.12 ms |
||||
* 11 = 10.24 ms |
||||
*/ |
||||
u32 settling_timeout; |
||||
|
||||
/* touch timeout in sample counts */ |
||||
u32 touch_timeout; |
||||
|
||||
/*
|
||||
* Number of data samples which are averaged before a final data point |
||||
* is placed into the FIFO |
||||
*/ |
||||
u32 average_data; |
||||
|
||||
/* FIFO threshold */ |
||||
u32 fifo_threshold; |
||||
|
||||
/* Optional standard touchscreen properties. */ |
||||
u32 max_x; |
||||
u32 max_y; |
||||
u32 fuzz_x; |
||||
u32 fuzz_y; |
||||
bool invert_x; |
||||
bool invert_y; |
||||
}; |
||||
|
||||
struct iproc_ts_priv { |
||||
struct platform_device *pdev; |
||||
struct input_dev *idev; |
||||
|
||||
void __iomem *regs; |
||||
struct clk *tsc_clk; |
||||
|
||||
int pen_status; |
||||
struct tsc_param cfg_params; |
||||
}; |
||||
|
||||
/*
|
||||
* Set default values the same as hardware reset values |
||||
* except for fifo_threshold with is set to 1. |
||||
*/ |
||||
static const struct tsc_param iproc_default_config = { |
||||
.scanning_period = 0x5, /* 1 to 256 */ |
||||
.debounce_timeout = 0x28, /* 0 to 255 */ |
||||
.settling_timeout = 0x7, /* 0 to 11 */ |
||||
.touch_timeout = 0xa, /* 0 to 255 */ |
||||
.average_data = 5, /* entry 5 = 32 pts */ |
||||
.fifo_threshold = 1, /* 0 to 31 */ |
||||
.max_x = X_MAX, |
||||
.max_y = Y_MAX, |
||||
}; |
||||
|
||||
static void ts_reg_dump(struct iproc_ts_priv *priv) |
||||
{ |
||||
struct device *dev = &priv->pdev->dev; |
||||
|
||||
dbg_reg(dev, priv, REGCTL1); |
||||
dbg_reg(dev, priv, REGCTL2); |
||||
dbg_reg(dev, priv, INTERRUPT_THRES); |
||||
dbg_reg(dev, priv, INTERRUPT_MASK); |
||||
dbg_reg(dev, priv, INTERRUPT_STATUS); |
||||
dbg_reg(dev, priv, CONTROLLER_STATUS); |
||||
dbg_reg(dev, priv, FIFO_DATA); |
||||
dbg_reg(dev, priv, ANALOG_CONTROL); |
||||
dbg_reg(dev, priv, AUX_DATA); |
||||
dbg_reg(dev, priv, DEBOUNCE_CNTR_STAT); |
||||
dbg_reg(dev, priv, SCAN_CNTR_STAT); |
||||
dbg_reg(dev, priv, REM_CNTR_STAT); |
||||
dbg_reg(dev, priv, SETTLING_TIMER_STAT); |
||||
dbg_reg(dev, priv, SPARE_REG); |
||||
dbg_reg(dev, priv, SOFT_BYPASS_CONTROL); |
||||
dbg_reg(dev, priv, SOFT_BYPASS_DATA); |
||||
} |
||||
|
||||
static irqreturn_t iproc_touchscreen_interrupt(int irq, void *data) |
||||
{ |
||||
struct platform_device *pdev = data; |
||||
struct iproc_ts_priv *priv = platform_get_drvdata(pdev); |
||||
u32 intr_status; |
||||
u32 raw_coordinate; |
||||
u16 x; |
||||
u16 y; |
||||
int i; |
||||
bool needs_sync = false; |
||||
|
||||
intr_status = readl(priv->regs + INTERRUPT_STATUS); |
||||
intr_status &= TS_PEN_INTR_MASK | TS_FIFO_INTR_MASK; |
||||
if (intr_status == 0) |
||||
return IRQ_NONE; |
||||
|
||||
/* Clear all interrupt status bits, write-1-clear */ |
||||
writel(intr_status, priv->regs + INTERRUPT_STATUS); |
||||
|
||||
/* Pen up/down */ |
||||
if (intr_status & TS_PEN_INTR_MASK) { |
||||
if (readl(priv->regs + CONTROLLER_STATUS) & TS_PEN_DOWN) |
||||
priv->pen_status = PEN_DOWN_STATUS; |
||||
else |
||||
priv->pen_status = PEN_UP_STATUS; |
||||
|
||||
input_report_key(priv->idev, BTN_TOUCH, priv->pen_status); |
||||
needs_sync = true; |
||||
|
||||
dev_dbg(&priv->pdev->dev, |
||||
"pen up-down (%d)\n", priv->pen_status); |
||||
} |
||||
|
||||
/* coordinates in FIFO exceed the theshold */ |
||||
if (intr_status & TS_FIFO_INTR_MASK) { |
||||
for (i = 0; i < priv->cfg_params.fifo_threshold; i++) { |
||||
raw_coordinate = readl(priv->regs + FIFO_DATA); |
||||
if (raw_coordinate == INVALID_COORD) |
||||
continue; |
||||
|
||||
/*
|
||||
* The x and y coordinate are 16 bits each |
||||
* with the x in the lower 16 bits and y in the |
||||
* upper 16 bits. |
||||
*/ |
||||
x = (raw_coordinate >> X_COORD_SHIFT) & |
||||
FIFO_DATA_X_Y_MASK; |
||||
y = (raw_coordinate >> Y_COORD_SHIFT) & |
||||
FIFO_DATA_X_Y_MASK; |
||||
|
||||
/* We only want to retain the 12 msb of the 16 */ |
||||
x = (x >> 4) & 0x0FFF; |
||||
y = (y >> 4) & 0x0FFF; |
||||
|
||||
/* adjust x y according to lcd tsc mount angle */ |
||||
if (priv->cfg_params.invert_x) |
||||
x = priv->cfg_params.max_x - x; |
||||
|
||||
if (priv->cfg_params.invert_y) |
||||
y = priv->cfg_params.max_y - y; |
||||
|
||||
input_report_abs(priv->idev, ABS_X, x); |
||||
input_report_abs(priv->idev, ABS_Y, y); |
||||
needs_sync = true; |
||||
|
||||
dev_dbg(&priv->pdev->dev, "xy (0x%x 0x%x)\n", x, y); |
||||
} |
||||
} |
||||
|
||||
if (needs_sync) |
||||
input_sync(priv->idev); |
||||
|
||||
return IRQ_HANDLED; |
||||
} |
||||
|
||||
static int iproc_ts_start(struct input_dev *idev) |
||||
{ |
||||
struct iproc_ts_priv *priv = input_get_drvdata(idev); |
||||
u32 val; |
||||
int error; |
||||
|
||||
/* Enable clock */ |
||||
error = clk_prepare_enable(priv->tsc_clk); |
||||
if (error) { |
||||
dev_err(&priv->pdev->dev, "%s clk_prepare_enable failed %d\n", |
||||
__func__, error); |
||||
return error; |
||||
} |
||||
|
||||
/*
|
||||
* Interrupt is generated when: |
||||
* FIFO reaches the int_th value, and pen event(up/down) |
||||
*/ |
||||
val = TS_PEN_INTR_MASK | TS_FIFO_INTR_MASK; |
||||
writel(val, priv->regs + INTERRUPT_MASK); |
||||
|
||||
writel(priv->cfg_params.fifo_threshold, priv->regs + INTERRUPT_THRES); |
||||
|
||||
/* Initialize control reg1 */ |
||||
val = 0; |
||||
val |= priv->cfg_params.scanning_period << SCANNING_PERIOD_SHIFT; |
||||
val |= priv->cfg_params.debounce_timeout << DEBOUNCE_TIMEOUT_SHIFT; |
||||
val |= priv->cfg_params.settling_timeout << SETTLING_TIMEOUT_SHIFT; |
||||
val |= priv->cfg_params.touch_timeout << TOUCH_TIMEOUT_SHIFT; |
||||
writel(val, priv->regs + REGCTL1); |
||||
|
||||
/* Try to clear all interrupt status */ |
||||
val = readl(priv->regs + INTERRUPT_STATUS); |
||||
val |= TS_FIFO_INTR_MASK | TS_PEN_INTR_MASK; |
||||
writel(val, priv->regs + INTERRUPT_STATUS); |
||||
|
||||
/* Initialize control reg2 */ |
||||
val = readl(priv->regs + REGCTL2); |
||||
val |= TS_CONTROLLER_EN_BIT | TS_WIRE_MODE_BIT; |
||||
|
||||
val &= ~TS_CONTROLLER_AVGDATA_MASK; |
||||
val |= priv->cfg_params.average_data << TS_CONTROLLER_AVGDATA_SHIFT; |
||||
|
||||
val &= ~(TS_CONTROLLER_PWR_LDO | /* PWR up LDO */ |
||||
TS_CONTROLLER_PWR_ADC | /* PWR up ADC */ |
||||
TS_CONTROLLER_PWR_BGP | /* PWR up BGP */ |
||||
TS_CONTROLLER_PWR_TS); /* PWR up TS */ |
||||
|
||||
writel(val, priv->regs + REGCTL2); |
||||
|
||||
ts_reg_dump(priv); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static void iproc_ts_stop(struct input_dev *dev) |
||||
{ |
||||
u32 val; |
||||
struct iproc_ts_priv *priv = input_get_drvdata(dev); |
||||
|
||||
writel(0, priv->regs + INTERRUPT_MASK); /* Disable all interrupts */ |
||||
|
||||
/* Only power down touch screen controller */ |
||||
val = readl(priv->regs + REGCTL2); |
||||
val |= TS_CONTROLLER_PWR_TS; |
||||
writel(val, priv->regs + REGCTL2); |
||||
|
||||
clk_disable(priv->tsc_clk); |
||||
} |
||||
|
||||
static int iproc_get_tsc_config(struct device *dev, struct iproc_ts_priv *priv) |
||||
{ |
||||
struct device_node *np = dev->of_node; |
||||
u32 val; |
||||
|
||||
priv->cfg_params = iproc_default_config; |
||||
|
||||
if (!np) |
||||
return 0; |
||||
|
||||
if (of_property_read_u32(np, "scanning_period", &val) >= 0) { |
||||
if (val < 1 || val > 256) { |
||||
dev_err(dev, "scanning_period (%u) must be [1-256]\n", |
||||
val); |
||||
return -EINVAL; |
||||
} |
||||
priv->cfg_params.scanning_period = val; |
||||
} |
||||
|
||||
if (of_property_read_u32(np, "debounce_timeout", &val) >= 0) { |
||||
if (val > 255) { |
||||
dev_err(dev, "debounce_timeout (%u) must be [0-255]\n", |
||||
val); |
||||
return -EINVAL; |
||||
} |
||||
priv->cfg_params.debounce_timeout = val; |
||||
} |
||||
|
||||
if (of_property_read_u32(np, "settling_timeout", &val) >= 0) { |
||||
if (val > 11) { |
||||
dev_err(dev, "settling_timeout (%u) must be [0-11]\n", |
||||
val); |
||||
return -EINVAL; |
||||
} |
||||
priv->cfg_params.settling_timeout = val; |
||||
} |
||||
|
||||
if (of_property_read_u32(np, "touch_timeout", &val) >= 0) { |
||||
if (val > 255) { |
||||
dev_err(dev, "touch_timeout (%u) must be [0-255]\n", |
||||
val); |
||||
return -EINVAL; |
||||
} |
||||
priv->cfg_params.touch_timeout = val; |
||||
} |
||||
|
||||
if (of_property_read_u32(np, "average_data", &val) >= 0) { |
||||
if (val > 8) { |
||||
dev_err(dev, "average_data (%u) must be [0-8]\n", val); |
||||
return -EINVAL; |
||||
} |
||||
priv->cfg_params.average_data = val; |
||||
} |
||||
|
||||
if (of_property_read_u32(np, "fifo_threshold", &val) >= 0) { |
||||
if (val > 31) { |
||||
dev_err(dev, "fifo_threshold (%u)) must be [0-31]\n", |
||||
val); |
||||
return -EINVAL; |
||||
} |
||||
priv->cfg_params.fifo_threshold = val; |
||||
} |
||||
|
||||
/* Parse optional properties. */ |
||||
of_property_read_u32(np, "touchscreen-size-x", &priv->cfg_params.max_x); |
||||
of_property_read_u32(np, "touchscreen-size-y", &priv->cfg_params.max_y); |
||||
|
||||
of_property_read_u32(np, "touchscreen-fuzz-x", |
||||
&priv->cfg_params.fuzz_x); |
||||
of_property_read_u32(np, "touchscreen-fuzz-y", |
||||
&priv->cfg_params.fuzz_y); |
||||
|
||||
priv->cfg_params.invert_x = |
||||
of_property_read_bool(np, "touchscreen-inverted-x"); |
||||
priv->cfg_params.invert_y = |
||||
of_property_read_bool(np, "touchscreen-inverted-y"); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static int iproc_ts_probe(struct platform_device *pdev) |
||||
{ |
||||
struct iproc_ts_priv *priv; |
||||
struct input_dev *idev; |
||||
struct resource *res; |
||||
int irq; |
||||
int error; |
||||
|
||||
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); |
||||
if (!priv) |
||||
return -ENOMEM; |
||||
|
||||
/* touchscreen controller memory mapped regs */ |
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
||||
priv->regs = devm_ioremap_resource(&pdev->dev, res); |
||||
if (IS_ERR(priv->regs)) { |
||||
error = PTR_ERR(priv->regs); |
||||
dev_err(&pdev->dev, "unable to map I/O memory: %d\n", error); |
||||
return error; |
||||
} |
||||
|
||||
priv->tsc_clk = devm_clk_get(&pdev->dev, "tsc_clk"); |
||||
if (IS_ERR(priv->tsc_clk)) { |
||||
error = PTR_ERR(priv->tsc_clk); |
||||
dev_err(&pdev->dev, |
||||
"failed getting clock tsc_clk: %d\n", error); |
||||
return error; |
||||
} |
||||
|
||||
priv->pdev = pdev; |
||||
error = iproc_get_tsc_config(&pdev->dev, priv); |
||||
if (error) { |
||||
dev_err(&pdev->dev, "get_tsc_config failed: %d\n", error); |
||||
return error; |
||||
} |
||||
|
||||
idev = devm_input_allocate_device(&pdev->dev); |
||||
if (!idev) { |
||||
dev_err(&pdev->dev, "failed to allocate input device\n"); |
||||
return -ENOMEM; |
||||
} |
||||
|
||||
priv->idev = idev; |
||||
priv->pen_status = PEN_UP_STATUS; |
||||
|
||||
/* Set input device info */ |
||||
idev->name = IPROC_TS_NAME; |
||||
idev->dev.parent = &pdev->dev; |
||||
|
||||
idev->id.bustype = BUS_HOST; |
||||
idev->id.vendor = SERIO_UNKNOWN; |
||||
idev->id.product = 0; |
||||
idev->id.version = 0; |
||||
|
||||
idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); |
||||
__set_bit(BTN_TOUCH, idev->keybit); |
||||
|
||||
input_set_abs_params(idev, ABS_X, X_MIN, priv->cfg_params.max_x, |
||||
priv->cfg_params.fuzz_x, 0); |
||||
input_set_abs_params(idev, ABS_Y, Y_MIN, priv->cfg_params.max_y, |
||||
priv->cfg_params.fuzz_y, 0); |
||||
|
||||
idev->open = iproc_ts_start; |
||||
idev->close = iproc_ts_stop; |
||||
|
||||
input_set_drvdata(idev, priv); |
||||
platform_set_drvdata(pdev, priv); |
||||
|
||||
/* get interrupt */ |
||||
irq = platform_get_irq(pdev, 0); |
||||
if (irq < 0) { |
||||
dev_err(&pdev->dev, "platform_get_irq failed: %d\n", irq); |
||||
return irq; |
||||
} |
||||
|
||||
error = devm_request_irq(&pdev->dev, irq, |
||||
iproc_touchscreen_interrupt, |
||||
IRQF_SHARED, IPROC_TS_NAME, pdev); |
||||
if (error) |
||||
return error; |
||||
|
||||
error = input_register_device(priv->idev); |
||||
if (error) { |
||||
dev_err(&pdev->dev, |
||||
"failed to register input device: %d\n", error); |
||||
return error; |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static const struct of_device_id iproc_ts_of_match[] = { |
||||
{.compatible = "brcm,iproc-touchscreen", }, |
||||
{ }, |
||||
}; |
||||
MODULE_DEVICE_TABLE(of, iproc_ts_of_match); |
||||
|
||||
static struct platform_driver iproc_ts_driver = { |
||||
.probe = iproc_ts_probe, |
||||
.driver = { |
||||
.name = IPROC_TS_NAME, |
||||
.of_match_table = of_match_ptr(iproc_ts_of_match), |
||||
}, |
||||
}; |
||||
|
||||
module_platform_driver(iproc_ts_driver); |
||||
|
||||
MODULE_DESCRIPTION("IPROC Touchscreen driver"); |
||||
MODULE_AUTHOR("Broadcom"); |
||||
MODULE_LICENSE("GPL v2"); |
@ -0,0 +1,316 @@ |
||||
/*
|
||||
* Driver for ChipOne icn8318 i2c touchscreen controller |
||||
* |
||||
* Copyright (c) 2015 Red Hat Inc. |
||||
* |
||||
* 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. |
||||
* |
||||
* Red Hat authors: |
||||
* Hans de Goede <hdegoede@redhat.com> |
||||
*/ |
||||
|
||||
#include <linux/gpio/consumer.h> |
||||
#include <linux/interrupt.h> |
||||
#include <linux/i2c.h> |
||||
#include <linux/input.h> |
||||
#include <linux/input/mt.h> |
||||
#include <linux/module.h> |
||||
#include <linux/of.h> |
||||
|
||||
#define ICN8318_REG_POWER 4 |
||||
#define ICN8318_REG_TOUCHDATA 16 |
||||
|
||||
#define ICN8318_POWER_ACTIVE 0 |
||||
#define ICN8318_POWER_MONITOR 1 |
||||
#define ICN8318_POWER_HIBERNATE 2 |
||||
|
||||
#define ICN8318_MAX_TOUCHES 5 |
||||
|
||||
struct icn8318_touch { |
||||
__u8 slot; |
||||
__be16 x; |
||||
__be16 y; |
||||
__u8 pressure; /* Seems more like finger width then pressure really */ |
||||
__u8 event; |
||||
/* The difference between 2 and 3 is unclear */ |
||||
#define ICN8318_EVENT_NO_DATA 1 /* No finger seen yet since wakeup */ |
||||
#define ICN8318_EVENT_UPDATE1 2 /* New or updated coordinates */ |
||||
#define ICN8318_EVENT_UPDATE2 3 /* New or updated coordinates */ |
||||
#define ICN8318_EVENT_END 4 /* Finger lifted */ |
||||
} __packed; |
||||
|
||||
struct icn8318_touch_data { |
||||
__u8 softbutton; |
||||
__u8 touch_count; |
||||
struct icn8318_touch touches[ICN8318_MAX_TOUCHES]; |
||||
} __packed; |
||||
|
||||
struct icn8318_data { |
||||
struct i2c_client *client; |
||||
struct input_dev *input; |
||||
struct gpio_desc *wake_gpio; |
||||
u32 max_x; |
||||
u32 max_y; |
||||
bool invert_x; |
||||
bool invert_y; |
||||
bool swap_x_y; |
||||
}; |
||||
|
||||
static int icn8318_read_touch_data(struct i2c_client *client, |
||||
struct icn8318_touch_data *touch_data) |
||||
{ |
||||
u8 reg = ICN8318_REG_TOUCHDATA; |
||||
struct i2c_msg msg[2] = { |
||||
{ |
||||
.addr = client->addr, |
||||
.len = 1, |
||||
.buf = ® |
||||
}, |
||||
{ |
||||
.addr = client->addr, |
||||
.flags = I2C_M_RD, |
||||
.len = sizeof(struct icn8318_touch_data), |
||||
.buf = (u8 *)touch_data |
||||
} |
||||
}; |
||||
|
||||
return i2c_transfer(client->adapter, msg, 2); |
||||
} |
||||
|
||||
static inline bool icn8318_touch_active(u8 event) |
||||
{ |
||||
return (event == ICN8318_EVENT_UPDATE1) || |
||||
(event == ICN8318_EVENT_UPDATE2); |
||||
} |
||||
|
||||
static irqreturn_t icn8318_irq(int irq, void *dev_id) |
||||
{ |
||||
struct icn8318_data *data = dev_id; |
||||
struct device *dev = &data->client->dev; |
||||
struct icn8318_touch_data touch_data; |
||||
int i, ret, x, y; |
||||
|
||||
ret = icn8318_read_touch_data(data->client, &touch_data); |
||||
if (ret < 0) { |
||||
dev_err(dev, "Error reading touch data: %d\n", ret); |
||||
return IRQ_HANDLED; |
||||
} |
||||
|
||||
if (touch_data.softbutton) { |
||||
/*
|
||||
* Other data is invalid when a softbutton is pressed. |
||||
* This needs some extra devicetree bindings to map the icn8318 |
||||
* softbutton codes to evdev codes. Currently no known devices |
||||
* use this. |
||||
*/ |
||||
return IRQ_HANDLED; |
||||
} |
||||
|
||||
if (touch_data.touch_count > ICN8318_MAX_TOUCHES) { |
||||
dev_warn(dev, "Too much touches %d > %d\n", |
||||
touch_data.touch_count, ICN8318_MAX_TOUCHES); |
||||
touch_data.touch_count = ICN8318_MAX_TOUCHES; |
||||
} |
||||
|
||||
for (i = 0; i < touch_data.touch_count; i++) { |
||||
struct icn8318_touch *touch = &touch_data.touches[i]; |
||||
bool act = icn8318_touch_active(touch->event); |
||||
|
||||
input_mt_slot(data->input, touch->slot); |
||||
input_mt_report_slot_state(data->input, MT_TOOL_FINGER, act); |
||||
if (!act) |
||||
continue; |
||||
|
||||
x = be16_to_cpu(touch->x); |
||||
y = be16_to_cpu(touch->y); |
||||
|
||||
if (data->invert_x) |
||||
x = data->max_x - x; |
||||
|
||||
if (data->invert_y) |
||||
y = data->max_y - y; |
||||
|
||||
if (!data->swap_x_y) { |
||||
input_event(data->input, EV_ABS, ABS_MT_POSITION_X, x); |
||||
input_event(data->input, EV_ABS, ABS_MT_POSITION_Y, y); |
||||
} else { |
||||
input_event(data->input, EV_ABS, ABS_MT_POSITION_X, y); |
||||
input_event(data->input, EV_ABS, ABS_MT_POSITION_Y, x); |
||||
} |
||||
} |
||||
|
||||
input_mt_sync_frame(data->input); |
||||
input_sync(data->input); |
||||
|
||||
return IRQ_HANDLED; |
||||
} |
||||
|
||||
static int icn8318_start(struct input_dev *dev) |
||||
{ |
||||
struct icn8318_data *data = input_get_drvdata(dev); |
||||
|
||||
enable_irq(data->client->irq); |
||||
gpiod_set_value_cansleep(data->wake_gpio, 1); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static void icn8318_stop(struct input_dev *dev) |
||||
{ |
||||
struct icn8318_data *data = input_get_drvdata(dev); |
||||
|
||||
disable_irq(data->client->irq); |
||||
i2c_smbus_write_byte_data(data->client, ICN8318_REG_POWER, |
||||
ICN8318_POWER_HIBERNATE); |
||||
gpiod_set_value_cansleep(data->wake_gpio, 0); |
||||
} |
||||
|
||||
#ifdef CONFIG_PM_SLEEP |
||||
static int icn8318_suspend(struct device *dev) |
||||
{ |
||||
struct icn8318_data *data = i2c_get_clientdata(to_i2c_client(dev)); |
||||
|
||||
mutex_lock(&data->input->mutex); |
||||
if (data->input->users) |
||||
icn8318_stop(data->input); |
||||
mutex_unlock(&data->input->mutex); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static int icn8318_resume(struct device *dev) |
||||
{ |
||||
struct icn8318_data *data = i2c_get_clientdata(to_i2c_client(dev)); |
||||
|
||||
mutex_lock(&data->input->mutex); |
||||
if (data->input->users) |
||||
icn8318_start(data->input); |
||||
mutex_unlock(&data->input->mutex); |
||||
|
||||
return 0; |
||||
} |
||||
#endif |
||||
|
||||
static SIMPLE_DEV_PM_OPS(icn8318_pm_ops, icn8318_suspend, icn8318_resume); |
||||
|
||||
static int icn8318_probe(struct i2c_client *client, |
||||
const struct i2c_device_id *id) |
||||
{ |
||||
struct device *dev = &client->dev; |
||||
struct device_node *np = dev->of_node; |
||||
struct icn8318_data *data; |
||||
struct input_dev *input; |
||||
u32 fuzz_x = 0, fuzz_y = 0; |
||||
int error; |
||||
|
||||
if (!client->irq) { |
||||
dev_err(dev, "Error no irq specified\n"); |
||||
return -EINVAL; |
||||
} |
||||
|
||||
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); |
||||
if (!data) |
||||
return -ENOMEM; |
||||
|
||||
data->wake_gpio = devm_gpiod_get(dev, "wake", GPIOD_OUT_LOW); |
||||
if (IS_ERR(data->wake_gpio)) { |
||||
error = PTR_ERR(data->wake_gpio); |
||||
if (error != -EPROBE_DEFER) |
||||
dev_err(dev, "Error getting wake gpio: %d\n", error); |
||||
return error; |
||||
} |
||||
|
||||
if (of_property_read_u32(np, "touchscreen-size-x", &data->max_x) || |
||||
of_property_read_u32(np, "touchscreen-size-y", &data->max_y)) { |
||||
dev_err(dev, "Error touchscreen-size-x and/or -y missing\n"); |
||||
return -EINVAL; |
||||
} |
||||
|
||||
/* Optional */ |
||||
of_property_read_u32(np, "touchscreen-fuzz-x", &fuzz_x); |
||||
of_property_read_u32(np, "touchscreen-fuzz-y", &fuzz_y); |
||||
data->invert_x = of_property_read_bool(np, "touchscreen-inverted-x"); |
||||
data->invert_y = of_property_read_bool(np, "touchscreen-inverted-y"); |
||||
data->swap_x_y = of_property_read_bool(np, "touchscreen-swapped-x-y"); |
||||
|
||||
input = devm_input_allocate_device(dev); |
||||
if (!input) |
||||
return -ENOMEM; |
||||
|
||||
input->name = client->name; |
||||
input->id.bustype = BUS_I2C; |
||||
input->open = icn8318_start; |
||||
input->close = icn8318_stop; |
||||
input->dev.parent = dev; |
||||
|
||||
if (!data->swap_x_y) { |
||||
input_set_abs_params(input, ABS_MT_POSITION_X, 0, |
||||
data->max_x, fuzz_x, 0); |
||||
input_set_abs_params(input, ABS_MT_POSITION_Y, 0, |
||||
data->max_y, fuzz_y, 0); |
||||
} else { |
||||
input_set_abs_params(input, ABS_MT_POSITION_X, 0, |
||||
data->max_y, fuzz_y, 0); |
||||
input_set_abs_params(input, ABS_MT_POSITION_Y, 0, |
||||
data->max_x, fuzz_x, 0); |
||||
} |
||||
|
||||
error = input_mt_init_slots(input, ICN8318_MAX_TOUCHES, |
||||
INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED); |
||||
if (error) |
||||
return error; |
||||
|
||||
data->client = client; |
||||
data->input = input; |
||||
input_set_drvdata(input, data); |
||||
|
||||
error = devm_request_threaded_irq(dev, client->irq, NULL, icn8318_irq, |
||||
IRQF_ONESHOT, client->name, data); |
||||
if (error) { |
||||
dev_err(dev, "Error requesting irq: %d\n", error); |
||||
return error; |
||||
} |
||||
|
||||
/* Stop device till opened */ |
||||
icn8318_stop(data->input); |
||||
|
||||
error = input_register_device(input); |
||||
if (error) |
||||
return error; |
||||
|
||||
i2c_set_clientdata(client, data); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static const struct of_device_id icn8318_of_match[] = { |
||||
{ .compatible = "chipone,icn8318" }, |
||||
{ } |
||||
}; |
||||
MODULE_DEVICE_TABLE(of, icn8318_of_match); |
||||
|
||||
/* This is useless for OF-enabled devices, but it is needed by I2C subsystem */ |
||||
static const struct i2c_device_id icn8318_i2c_id[] = { |
||||
{ }, |
||||
}; |
||||
MODULE_DEVICE_TABLE(i2c, icn8318_i2c_id); |
||||
|
||||
static struct i2c_driver icn8318_driver = { |
||||
.driver = { |
||||
.owner = THIS_MODULE, |
||||
.name = "chipone_icn8318", |
||||
.pm = &icn8318_pm_ops, |
||||
.of_match_table = icn8318_of_match, |
||||
}, |
||||
.probe = icn8318_probe, |
||||
.id_table = icn8318_i2c_id, |
||||
}; |
||||
|
||||
module_i2c_driver(icn8318_driver); |
||||
|
||||
MODULE_DESCRIPTION("ChipOne icn8318 I2C Touchscreen Driver"); |
||||
MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); |
||||
MODULE_LICENSE("GPL"); |
@ -0,0 +1,286 @@ |
||||
/*
|
||||
* Driver for Semtech SX8654 I2C touchscreen controller. |
||||
* |
||||
* Copyright (c) 2015 Armadeus Systems |
||||
* Sébastien Szymanski <sebastien.szymanski@armadeus.com> |
||||
* |
||||
* Using code from: |
||||
* - sx865x.c |
||||
* Copyright (c) 2013 U-MoBo Srl |
||||
* Pierluigi Passaro <p.passaro@u-mobo.com> |
||||
* - sx8650.c |
||||
* Copyright (c) 2009 Wayne Roberts |
||||
* - tsc2007.c |
||||
* Copyright (c) 2008 Kwangwoo Lee |
||||
* - ads7846.c |
||||
* Copyright (c) 2005 David Brownell |
||||
* Copyright (c) 2006 Nokia Corporation |
||||
* - corgi_ts.c |
||||
* Copyright (C) 2004-2005 Richard Purdie |
||||
* - omap_ts.[hc], ads7846.h, ts_osk.c |
||||
* Copyright (C) 2002 MontaVista Software |
||||
* Copyright (C) 2004 Texas Instruments |
||||
* Copyright (C) 2005 Dirk Behme |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License version 2 as |
||||
* published by the Free Software Foundation. |
||||
*/ |
||||
|
||||
#include <linux/input.h> |
||||
#include <linux/module.h> |
||||
#include <linux/of.h> |
||||
#include <linux/i2c.h> |
||||
#include <linux/interrupt.h> |
||||
#include <linux/irq.h> |
||||
|
||||
/* register addresses */ |
||||
#define I2C_REG_TOUCH0 0x00 |
||||
#define I2C_REG_TOUCH1 0x01 |
||||
#define I2C_REG_CHANMASK 0x04 |
||||
#define I2C_REG_IRQMASK 0x22 |
||||
#define I2C_REG_IRQSRC 0x23 |
||||
#define I2C_REG_SOFTRESET 0x3f |
||||
|
||||
/* commands */ |
||||
#define CMD_READ_REGISTER 0x40 |
||||
#define CMD_MANUAL 0xc0 |
||||
#define CMD_PENTRG 0xe0 |
||||
|
||||
/* value for I2C_REG_SOFTRESET */ |
||||
#define SOFTRESET_VALUE 0xde |
||||
|
||||
/* bits for I2C_REG_IRQSRC */ |
||||
#define IRQ_PENTOUCH_TOUCHCONVDONE 0x08 |
||||
#define IRQ_PENRELEASE 0x04 |
||||
|
||||
/* bits for RegTouch1 */ |
||||
#define CONDIRQ 0x20 |
||||
#define FILT_7SA 0x03 |
||||
|
||||
/* bits for I2C_REG_CHANMASK */ |
||||
#define CONV_X 0x80 |
||||
#define CONV_Y 0x40 |
||||
|
||||
/* coordinates rate: higher nibble of CTRL0 register */ |
||||
#define RATE_MANUAL 0x00 |
||||
#define RATE_5000CPS 0xf0 |
||||
|
||||
/* power delay: lower nibble of CTRL0 register */ |
||||
#define POWDLY_1_1MS 0x0b |
||||
|
||||
#define MAX_12BIT ((1 << 12) - 1) |
||||
|
||||
struct sx8654 { |
||||
struct input_dev *input; |
||||
struct i2c_client *client; |
||||
}; |
||||
|
||||
static irqreturn_t sx8654_irq(int irq, void *handle) |
||||
{ |
||||
struct sx8654 *sx8654 = handle; |
||||
int irqsrc; |
||||
u8 data[4]; |
||||
unsigned int x, y; |
||||
int retval; |
||||
|
||||
irqsrc = i2c_smbus_read_byte_data(sx8654->client, |
||||
CMD_READ_REGISTER | I2C_REG_IRQSRC); |
||||
dev_dbg(&sx8654->client->dev, "irqsrc = 0x%x", irqsrc); |
||||
|
||||
if (irqsrc < 0) |
||||
goto out; |
||||
|
||||
if (irqsrc & IRQ_PENRELEASE) { |
||||
dev_dbg(&sx8654->client->dev, "pen release interrupt"); |
||||
|
||||
input_report_key(sx8654->input, BTN_TOUCH, 0); |
||||
input_sync(sx8654->input); |
||||
} |
||||
|
||||
if (irqsrc & IRQ_PENTOUCH_TOUCHCONVDONE) { |
||||
dev_dbg(&sx8654->client->dev, "pen touch interrupt"); |
||||
|
||||
retval = i2c_master_recv(sx8654->client, data, sizeof(data)); |
||||
if (retval != sizeof(data)) |
||||
goto out; |
||||
|
||||
/* invalid data */ |
||||
if (unlikely(data[0] & 0x80 || data[2] & 0x80)) |
||||
goto out; |
||||
|
||||
x = ((data[0] & 0xf) << 8) | (data[1]); |
||||
y = ((data[2] & 0xf) << 8) | (data[3]); |
||||
|
||||
input_report_abs(sx8654->input, ABS_X, x); |
||||
input_report_abs(sx8654->input, ABS_Y, y); |
||||
input_report_key(sx8654->input, BTN_TOUCH, 1); |
||||
input_sync(sx8654->input); |
||||
|
||||
dev_dbg(&sx8654->client->dev, "point(%4d,%4d)\n", x, y); |
||||
} |
||||
|
||||
out: |
||||
return IRQ_HANDLED; |
||||
} |
||||
|
||||
static int sx8654_open(struct input_dev *dev) |
||||
{ |
||||
struct sx8654 *sx8654 = input_get_drvdata(dev); |
||||
struct i2c_client *client = sx8654->client; |
||||
int error; |
||||
|
||||
/* enable pen trigger mode */ |
||||
error = i2c_smbus_write_byte_data(client, I2C_REG_TOUCH0, |
||||
RATE_5000CPS | POWDLY_1_1MS); |
||||
if (error) { |
||||
dev_err(&client->dev, "writing to I2C_REG_TOUCH0 failed"); |
||||
return error; |
||||
} |
||||
|
||||
error = i2c_smbus_write_byte(client, CMD_PENTRG); |
||||
if (error) { |
||||
dev_err(&client->dev, "writing command CMD_PENTRG failed"); |
||||
return error; |
||||
} |
||||
|
||||
enable_irq(client->irq); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static void sx8654_close(struct input_dev *dev) |
||||
{ |
||||
struct sx8654 *sx8654 = input_get_drvdata(dev); |
||||
struct i2c_client *client = sx8654->client; |
||||
int error; |
||||
|
||||
disable_irq(client->irq); |
||||
|
||||
/* enable manual mode mode */ |
||||
error = i2c_smbus_write_byte(client, CMD_MANUAL); |
||||
if (error) { |
||||
dev_err(&client->dev, "writing command CMD_MANUAL failed"); |
||||
return; |
||||
} |
||||
|
||||
error = i2c_smbus_write_byte_data(client, I2C_REG_TOUCH0, 0); |
||||
if (error) { |
||||
dev_err(&client->dev, "writing to I2C_REG_TOUCH0 failed"); |
||||
return; |
||||
} |
||||
} |
||||
|
||||
static int sx8654_probe(struct i2c_client *client, |
||||
const struct i2c_device_id *id) |
||||
{ |
||||
struct sx8654 *sx8654; |
||||
struct input_dev *input; |
||||
int error; |
||||
|
||||
if (!i2c_check_functionality(client->adapter, |
||||
I2C_FUNC_SMBUS_READ_WORD_DATA)) |
||||
return -ENXIO; |
||||
|
||||
sx8654 = devm_kzalloc(&client->dev, sizeof(*sx8654), GFP_KERNEL); |
||||
if (!sx8654) |
||||
return -ENOMEM; |
||||
|
||||
input = devm_input_allocate_device(&client->dev); |
||||
if (!sx8654) |
||||
return -ENOMEM; |
||||
|
||||
input->name = "SX8654 I2C Touchscreen"; |
||||
input->id.bustype = BUS_I2C; |
||||
input->dev.parent = &client->dev; |
||||
input->open = sx8654_open; |
||||
input->close = sx8654_close; |
||||
|
||||
__set_bit(INPUT_PROP_DIRECT, input->propbit); |
||||
input_set_capability(input, EV_KEY, BTN_TOUCH); |
||||
input_set_abs_params(input, ABS_X, 0, MAX_12BIT, 0, 0); |
||||
input_set_abs_params(input, ABS_Y, 0, MAX_12BIT, 0, 0); |
||||
|
||||
sx8654->client = client; |
||||
sx8654->input = input; |
||||
|
||||
input_set_drvdata(sx8654->input, sx8654); |
||||
|
||||
error = i2c_smbus_write_byte_data(client, I2C_REG_SOFTRESET, |
||||
SOFTRESET_VALUE); |
||||
if (error) { |
||||
dev_err(&client->dev, "writing softreset value failed"); |
||||
return error; |
||||
} |
||||
|
||||
error = i2c_smbus_write_byte_data(client, I2C_REG_CHANMASK, |
||||
CONV_X | CONV_Y); |
||||
if (error) { |
||||
dev_err(&client->dev, "writing to I2C_REG_CHANMASK failed"); |
||||
return error; |
||||
} |
||||
|
||||
error = i2c_smbus_write_byte_data(client, I2C_REG_IRQMASK, |
||||
IRQ_PENTOUCH_TOUCHCONVDONE | |
||||
IRQ_PENRELEASE); |
||||
if (error) { |
||||
dev_err(&client->dev, "writing to I2C_REG_IRQMASK failed"); |
||||
return error; |
||||
} |
||||
|
||||
error = i2c_smbus_write_byte_data(client, I2C_REG_TOUCH1, |
||||
CONDIRQ | FILT_7SA); |
||||
if (error) { |
||||
dev_err(&client->dev, "writing to I2C_REG_TOUCH1 failed"); |
||||
return error; |
||||
} |
||||
|
||||
error = devm_request_threaded_irq(&client->dev, client->irq, |
||||
NULL, sx8654_irq, |
||||
IRQF_TRIGGER_FALLING | IRQF_ONESHOT, |
||||
client->name, sx8654); |
||||
if (error) { |
||||
dev_err(&client->dev, |
||||
"Failed to enable IRQ %d, error: %d\n", |
||||
client->irq, error); |
||||
return error; |
||||
} |
||||
|
||||
/* Disable the IRQ, we'll enable it in sx8654_open() */ |
||||
disable_irq(client->irq); |
||||
|
||||
error = input_register_device(sx8654->input); |
||||
if (error) |
||||
return error; |
||||
|
||||
i2c_set_clientdata(client, sx8654); |
||||
return 0; |
||||
} |
||||
|
||||
#ifdef CONFIG_OF |
||||
static const struct of_device_id sx8654_of_match[] = { |
||||
{ .compatible = "semtech,sx8654", }, |
||||
{ }, |
||||
}; |
||||
MODULE_DEVICE_TABLE(of, sx8654_of_match); |
||||
#endif |
||||
|
||||
static const struct i2c_device_id sx8654_id_table[] = { |
||||
{ "semtech_sx8654", 0 }, |
||||
{ }, |
||||
}; |
||||
MODULE_DEVICE_TABLE(i2c, sx8654_id_table); |
||||
|
||||
static struct i2c_driver sx8654_driver = { |
||||
.driver = { |
||||
.name = "sx8654", |
||||
.of_match_table = of_match_ptr(sx8654_of_match), |
||||
}, |
||||
.id_table = sx8654_id_table, |
||||
.probe = sx8654_probe, |
||||
}; |
||||
module_i2c_driver(sx8654_driver); |
||||
|
||||
MODULE_AUTHOR("Sébastien Szymanski <sebastien.szymanski@armadeus.com>"); |
||||
MODULE_DESCRIPTION("Semtech SX8654 I2C Touchscreen Driver"); |
||||
MODULE_LICENSE("GPL"); |
Loading…
Reference in new issue