From: Mike Rapoport <mike@compulab.co.il>
To: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Cc: Jean Delvare <khali@linux-fr.org>,
grinberg <grinberg@compulab.co.il>,
linux-input <linux-input@vger.kernel.org>
Subject: Re: [PATCH v2] input: add synaptics_i2c driver
Date: Thu, 11 Jun 2009 08:52:41 +0300 [thread overview]
Message-ID: <4A309BA9.8030002@compulab.co.il> (raw)
In-Reply-To: <4A262D5F.1070803@compulab.co.il>
Hi Dmitry,
Any updates on this? I'd be really happy if it can go to 2.6.31...
Mike Rapoport wrote:
> Hi Dmitry,
>
> Dmitry Torokhov wrote:
>> Hi Mike,
>>
>> On Wednesday 20 May 2009 05:36:17 Mike Rapoport wrote:
>>> This patch adds support for Synaptics i2c touchpad controller found on
>>> eXeda machine.
>>>
>>> Signed-off-by: Igor Grinberg <grinberg@compulab.co.il>
>>> Signed-off-by: Mike Rapoport <mike@compulab.co.il>
>>> CC: Jean Delvare <khali@linux-fr.org>
>>>
>>> ---
>>> v2 changelog:
>>> - fix bugs and issues pointed out by Jean and Dmitry
>>> - use workqueue instead of kthread
>>> - reorganize methods order to something more logical
>>>
>>> ---
>>> drivers/input/mouse/Kconfig | 18 +
>>> drivers/input/mouse/Makefile | 1 +
>>> drivers/input/mouse/synaptics_i2c.c | 700
>>> +++++++++++++++++++++++++++++++++++ 3 files changed, 719 insertions(+), 0
>>> deletions(-)
>>> create mode 100644 drivers/input/mouse/synaptics_i2c.c
>>>
>>> diff --git a/drivers/input/mouse/Kconfig b/drivers/input/mouse/Kconfig
>>> index c66cc3d..8a2c5b1 100644
>>> --- a/drivers/input/mouse/Kconfig
>>> +++ b/drivers/input/mouse/Kconfig
>>> @@ -303,4 +303,22 @@ config MOUSE_MAPLE
>>> To compile this driver as a module choose M here: the module will be
>>> called maplemouse.
>>>
>>> +config MOUSE_SYNAPTICS_I2C
>>> + tristate "Synaptics I2C Touchpad support"
>>> + depends on I2C
>>> + help
>>> + This driver supports Synaptics I2C touchpad controller on eXeda
>>> + mobile device.
>>> + The device will not work the synaptics X11 driver because
>>> + (i) it reports only relative coordinates and has no capabilities
>>> + to report absolute coordinates
>>> + (ii) the eXeda device itself uses Xfbdev as X Server and it does
>>> + not allow using xf86-input-* drivers.
>>> +
>>> + Say y here if you have eXeda device and want to use a Synaptics
>>> + I2C Touchpad.
>>> +
>>> + To compile this driver as a module, choose M here: the
>>> + module will be called synaptics_i2c.
>>> +
>>> endif
>>> diff --git a/drivers/input/mouse/Makefile b/drivers/input/mouse/Makefile
>>> index 4721894..010f265 100644
>>> --- a/drivers/input/mouse/Makefile
>>> +++ b/drivers/input/mouse/Makefile
>>> @@ -18,6 +18,7 @@ obj-$(CONFIG_MOUSE_PS2) += psmouse.o
>>> obj-$(CONFIG_MOUSE_PXA930_TRKBALL) += pxa930_trkball.o
>>> obj-$(CONFIG_MOUSE_RISCPC) += rpcmouse.o
>>> obj-$(CONFIG_MOUSE_SERIAL) += sermouse.o
>>> +obj-$(CONFIG_MOUSE_SYNAPTICS_I2C) += synaptics_i2c.o
>>> obj-$(CONFIG_MOUSE_VSXXXAA) += vsxxxaa.o
>>>
>>> psmouse-objs := psmouse-base.o synaptics.o
>>> diff --git a/drivers/input/mouse/synaptics_i2c.c
>>> b/drivers/input/mouse/synaptics_i2c.c new file mode 100644
>>> index 0000000..82d90f3
>>> --- /dev/null
>>> +++ b/drivers/input/mouse/synaptics_i2c.c
>>> @@ -0,0 +1,700 @@
>>> +/*
>>> + * Synaptics touchpad with I2C interface
>>> + *
>>> + * Copyright (C) 2009 Compulab, Ltd.
>>> + * Mike Rapoport <mike@compulab.co.il>
>>> + * Igor Grinberg <grinberg@compulab.co.il>
>>> + *
>>> + * This file is subject to the terms and conditions of the GNU General
>>> Public + * License. See the file COPYING in the main directory of this
>>> archive for + * more details.
>>> + */
>>> +
>>> +#include <linux/module.h>
>>> +#include <linux/i2c.h>
>>> +#include <linux/irq.h>
>>> +#include <linux/interrupt.h>
>>> +#include <linux/input.h>
>>> +#include <linux/delay.h>
>>> +#include <linux/workqueue.h>
>>> +
>>> +#define DRIVER_NAME "synaptics_i2c"
>>> +/* maximum product id is 15 characters */
>>> +#define PRODUCT_ID_LENGTH 15
>>> +#define REGISTER_LENGTH 8
>>> +
>>> +/*
>>> + * after soft reset, we should wait for 1 ms
>>> + * before the device becomes operational
>>> + */
>>> +#define SOFT_RESET_DELAY_MS 3
>>> +/* and after hard reset, we should wait for max 500ms */
>>> +#define HARD_RESET_DELAY_MS 500
>>> +
>>> +/* Registers by SMBus address */
>>> +#define PAGE_SEL_REG 0xff
>>> +#define DEVICE_STATUS_REG 0x09
>>> +
>>> +/* Registers by RMI address */
>>> +#define DEV_CONTROL_REG 0x0000
>>> +#define INTERRUPT_EN_REG 0x0001
>>> +#define ERR_STAT_REG 0x0002
>>> +#define INT_REQ_STAT_REG 0x0003
>>> +#define DEV_COMMAND_REG 0x0004
>>> +
>>> +#define RMI_PROT_VER_REG 0x0200
>>> +#define MANUFACT_ID_REG 0x0201
>>> +#define PHYS_INT_VER_REG 0x0202
>>> +#define PROD_PROPERTY_REG 0x0203
>>> +#define INFO_QUERY_REG0 0x0204
>>> +#define INFO_QUERY_REG1 (INFO_QUERY_REG0 + 1)
>>> +#define INFO_QUERY_REG2 (INFO_QUERY_REG0 + 2)
>>> +#define INFO_QUERY_REG3 (INFO_QUERY_REG0 + 3)
>>> +
>>> +#define PRODUCT_ID_REG0 0x0210
>>> +#define PRODUCT_ID_REG1 (PRODUCT_ID_REG0 + 1)
>>> +#define PRODUCT_ID_REG2 (PRODUCT_ID_REG0 + 2)
>>> +#define PRODUCT_ID_REG3 (PRODUCT_ID_REG0 + 3)
>>> +#define PRODUCT_ID_REG4 (PRODUCT_ID_REG0 + 4)
>>> +#define PRODUCT_ID_REG5 (PRODUCT_ID_REG0 + 5)
>>> +#define PRODUCT_ID_REG6 (PRODUCT_ID_REG0 + 6)
>>> +#define PRODUCT_ID_REG7 (PRODUCT_ID_REG0 + 7)
>>> +#define PRODUCT_ID_REG8 (PRODUCT_ID_REG0 + 8)
>>> +#define PRODUCT_ID_REG9 (PRODUCT_ID_REG0 + 9)
>>> +#define PRODUCT_ID_REG10 (PRODUCT_ID_REG0 + 10)
>>> +#define PRODUCT_ID_REG11 (PRODUCT_ID_REG0 + 11)
>>> +#define PRODUCT_ID_REG12 (PRODUCT_ID_REG0 + 12)
>>> +#define PRODUCT_ID_REG13 (PRODUCT_ID_REG0 + 13)
>>> +#define PRODUCT_ID_REG14 (PRODUCT_ID_REG0 + 14)
>>> +#define PRODUCT_ID_REG15 (PRODUCT_ID_REG0 + 15)
>>> +
>>> +#define DATA_REG0 0x0400
>>> +#define ABS_PRESSURE_REG 0x0401
>>> +#define ABS_MSB_X_REG 0x0402
>>> +#define ABS_LSB_X_REG (ABS_MSB_X_REG + 1)
>>> +#define ABS_MSB_Y_REG 0x0404
>>> +#define ABS_LSB_Y_REG (ABS_MSB_Y_REG + 1)
>>> +#define REL_X_REG 0x0406
>>> +#define REL_Y_REG 0x0407
>>> +
>>> +#define DEV_QUERY_REG0 0x1000
>>> +#define DEV_QUERY_REG1 (DEV_QUERY_REG0 + 1)
>>> +#define DEV_QUERY_REG2 (DEV_QUERY_REG0 + 2)
>>> +#define DEV_QUERY_REG3 (DEV_QUERY_REG0 + 3)
>>> +#define DEV_QUERY_REG4 (DEV_QUERY_REG0 + 4)
>>> +#define DEV_QUERY_REG5 (DEV_QUERY_REG0 + 5)
>>> +#define DEV_QUERY_REG6 (DEV_QUERY_REG0 + 6)
>>> +#define DEV_QUERY_REG7 (DEV_QUERY_REG0 + 7)
>>> +#define DEV_QUERY_REG8 (DEV_QUERY_REG0 + 8)
>>> +
>>> +#define GENERAL_2D_CONTROL_REG 0x1041
>>> +#define SENSOR_SENSITIVITY_REG 0x1044
>>> +#define SENS_MAX_POS_MSB_REG 0x1046
>>> +#define SENS_MAX_POS_LSB_REG (SENS_MAX_POS_UPPER_REG + 1)
>>> +
>>> +/* Register bits */
>>> +/* Device Control Register Bits */
>>> +#define REPORT_RATE_1ST_BIT 6
>>> +
>>> +/* Interrupt Enable Register Bits (INTERRUPT_EN_REG) */
>>> +#define F10_ABS_INT_ENA 0
>>> +#define F10_REL_INT_ENA 1
>>> +#define F20_INT_ENA 2
>>> +
>>> +/* Interrupt Request Register Bits (INT_REQ_STAT_REG | DEVICE_STATUS_REG)
>>> */ +#define F10_ABS_INT_REQ 0
>>> +#define F10_REL_INT_REQ 1
>>> +#define F20_INT_REQ 2
>>> +/* Device Status Register Bits (DEVICE_STATUS_REG) */
>>> +#define STAT_CONFIGURED 6
>>> +#define STAT_ERROR 7
>>> +
>>> +/* Device Command Register Bits (DEV_COMMAND_REG) */
>>> +#define RESET_COMMAND 0x01
>>> +#define REZERO_COMMAND 0x02
>>> +
>>> +/* Data Register 0 Bits (DATA_REG0) */
>>> +#define GESTURE 3
>>> +
>>> +/* Device Query Registers Bits */
>>> +/* DEV_QUERY_REG3 */
>>> +#define HAS_PALM_DETECT 1
>>> +#define HAS_MULTI_FING 2
>>> +#define HAS_SCROLLER 4
>>> +#define HAS_2D_SCROLL 5
>>> +
>>> +/* General 2D Control Register Bits (GENERAL_2D_CONTROL_REG) */
>>> +#define NO_DECELERATION 1
>>> +#define REDUCE_REPORTING 3
>>> +#define NO_FILTER 5
>>> +
>>> +#define GET_BIT(bit, val) (((val) >> (bit)) & 0x01)
>> We have test_bit() if you don't want to open-code this.
>
> Fixed
>
>>> +
>>> +/* Function Masks */
>>> +/* Device Control Register Masks (DEV_CONTROL_REG) */
>>> +#define REPORT_RATE_MSK 0xc0
>>> +#define SLEEP_MODE_MSK 0x07
>>> +
>>> +/* Device Sleep Modes */
>>> +#define FULL_AWAKE 0x0
>>> +#define NORMAL_OP 0x1
>>> +#define LOW_PWR_OP 0x2
>>> +#define VERY_LOW_PWR_OP 0x3
>>> +#define SENS_SLEEP 0x4
>>> +#define SLEEP_MOD 0x5
>>> +#define DEEP_SLEEP 0x6
>>> +#define HIBERNATE 0x7
>>> +
>>> +/* Interrupt Register Mask */
>>> +/* (INT_REQ_STAT_REG | DEVICE_STATUS_REG | INTERRUPT_EN_REG) */
>>> +#define INT_ENA_REQ_MSK 0x07
>>> +#define INT_ENA_ABS_MSK 0x01
>>> +#define INT_ENA_REL_MSK 0x02
>>> +#define INT_ENA_F20_MSK 0x04
>>> +
>>> +/* Device Status Register Masks (DEVICE_STATUS_REG) */
>>> +#define CONFIGURED_MSK 0x40
>>> +#define ERROR_MSK 0x80
>>> +
>>> +/* Data Register 0 Masks */
>>> +#define FINGER_WIDTH_MSK 0xf0
>>> +#define GESTURE_MSK 0x08
>>> +#define SENSOR_STATUS_MSK 0x07
>>> +
>>> +/*
>>> + * MSB Position Register Masks
>>> + * ABS_MSB_X_REG | ABS_MSB_Y_REG | SENS_MAX_POS_MSB_REG |
>>> + * DEV_QUERY_REG3 | DEV_QUERY_REG5
>>> + */
>>> +#define MSB_POSITION_MSK 0x1f
>>> +
>>> +/* Device Query Registers Masks */
>>> +
>>> +/* DEV_QUERY_REG2 */
>>> +#define NUM_EXTRA_POS_MSK 0x07
>>> +
>>> +/* When in IRQ mode read the device every THREAD_IRQ_SLEEP_SECS */
>>> +#define THREAD_IRQ_SLEEP_SECS 2
>>> +#define THREAD_IRQ_SLEEP_MSECS (THREAD_IRQ_SLEEP_SECS * MSEC_PER_SEC)
>>> +
>>> +/*
>>> + * When in Polling mode and no data received for NO_DATA_THRES msecs
>>> + * reduce the polling rate to NO_DATA_SLEEP_MSECS
>>> + */
>>> +#define NO_DATA_THRES (MSEC_PER_SEC)
>>> +#define NO_DATA_SLEEP_MSECS (MSEC_PER_SEC / 4)
>>> +
>>> +/*
>>> + * The Acceleration Factor:
>>> + * from 0.5 (1) to 4 (8) in dozes of 0.5
>>> + * the transformation is done in order to get 0.5 resolution.
>>> + * values above 8 will affect poor performance without proper smoothing.
>>> + */
>>> +#define ACCEL_DIVIDER 10
>>> +#define ACCEL_DOZE 2
>>> +static int accel = 4;
>>> +module_param(accel, int, 0644);
>>> +MODULE_PARM_DESC(accel, "Set Acceleration parameter 1..8. Default = 4");
>>> +
>>> +/* Control touchpad's No Deceleration option */
>>> +static int no_decel = 1;
>>> +module_param(no_decel, bool, 0644);
>>> +MODULE_PARM_DESC(no_decel, "No Deceleration. Default = 1 (on)");
>>> +
>> The same objection as before - does not belong in the kernel.
>
> Acceleration handling is removed from the driver.
> As for the 'no_decel' parameter, it does not define a policy in the driver but
> rather the hardware operation mode, so we've keep it along with others. The
> parameters are directly translated into the device control register bits.
>
>>> +/* Control touchpad's Reduced Reporting option */
>>> +static int reduce_report;
>>> +module_param(reduce_report, bool, 0644);
>>> +MODULE_PARM_DESC(reduce_report, "Reduced Reporting. Default = 0 (off)");
>>> +
>>> +/* Control touchpad's No Filter option */
>>> +static int no_filter;
>>> +module_param(no_filter, bool, 0644);
>>> +MODULE_PARM_DESC(no_filter, "No Filter. Default = 0 (off)");
>>> +
>>> +/*
>>> + * touchpad Attention line is Active Low and Open Drain,
>>> + * therefore should be connected to pulled up line
>>> + * and the irq configuration should be set to Falling Edge Trigger
>>> + */
>>> +/* Control IRQ / Polling option */
>>> +static int polling_req;
>>> +module_param(polling_req, bool, 0444);
>>> +MODULE_PARM_DESC(polling_req, "Request Polling. Default = 0 (use irq)");
>>> +
>>> +/* Control Polling Rate */
>>> +static int scan_rate = 80;
>>> +module_param(scan_rate, int, 0644);
>>> +MODULE_PARM_DESC(scan_rate, "Polling rate in times/sec. Default = 80");
>>> +
>>> +/* The main device structure */
>>> +struct synaptics_i2c {
>>> + struct i2c_client *client;
>>> + struct input_dev *input;
>>> + struct mutex mutex;
>>> + struct delayed_work dwork;
>>> + int no_data_count;
>>> + int no_decel_param;
>>> + int reduce_report_param;
>>> + int no_filter_param;
>>> + int scan_rate_param;
>>> + int accel_param;
>>> + int accel; /* Transformed acceleration */
>>> + int scan_ms;
>>> +};
>>> +
>>> +static inline void set_acceleration(struct synaptics_i2c *touch, int
>>> accel) +{
>>> + int acc = (accel < 1) ? 1 : ((accel > 8) ? 8 : accel);
>>> + touch->accel = (acc * ACCEL_DIVIDER) / ACCEL_DOZE;
>>> + touch->accel_param = accel;
>>> +}
>>> +
>>> +static inline void set_scan_rate(struct synaptics_i2c *touch, int
>>> scan_rate) +{
>>> + touch->scan_ms = MSEC_PER_SEC / scan_rate;
>>> + touch->scan_rate_param = scan_rate;
>>> +}
>>> +
>>> +static s32 synaptics_i2c_reg_get(struct i2c_client *client, u16 reg)
>>> +{
>>> + struct synaptics_i2c *touch = i2c_get_clientdata(client);
>>> + int ret;
>>> +
>>> + mutex_lock(&touch->mutex);
>> Why do you need to take this mutex? I don't see you doing simultaneous
>> access to the device, but if you do then I'd think you need to protect
>> larger blocks, like the read-modify-write chunks, not individual read/
>> write operations.
>
> The driver indeed has no concurrent accesses to the device, so we've removed the
> locks and added appropriate comment.
>
>>> +
>>> + ret = i2c_smbus_write_byte_data(client, PAGE_SEL_REG, reg >> 8);
>>> + if (!ret)
>>> + ret = i2c_smbus_read_byte_data(client, reg & 0xff);
>>> +
>>> + mutex_unlock(&touch->mutex);
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static s32 synaptics_i2c_reg_set(struct i2c_client *client, u16 reg, u8
>>> val) +{
>>> + struct synaptics_i2c *touch = i2c_get_clientdata(client);
>>> + int ret;
>>> +
>>> + mutex_lock(&touch->mutex);
>>> +
>>> + ret = i2c_smbus_write_byte_data(client, PAGE_SEL_REG, reg >> 8);
>>> + if (!ret)
>>> + ret = i2c_smbus_write_byte_data(client, reg & 0xff, val);
>>> +
>>> + mutex_unlock(&touch->mutex);
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static s32 synaptics_i2c_word_get(struct i2c_client *client, u16 reg)
>>> +{
>>> + struct synaptics_i2c *touch = i2c_get_clientdata(client);
>>> + int ret;
>>> +
>>> + mutex_lock(&touch->mutex);
>>> +
>>> + ret = i2c_smbus_write_byte_data(client, PAGE_SEL_REG, reg >> 8);
>>> + if (!ret)
>>> + ret = i2c_smbus_read_word_data(client, reg & 0xff);
>>> +
>>> + mutex_unlock(&touch->mutex);
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static int synaptics_i2c_config(struct i2c_client *client)
>>> +{
>>> + int ret, control;
>>> + u8 int_en;
>>> +
>>> + /* set Report Rate to Device Highest (>=80) and Sleep to normal */
>>> + ret = synaptics_i2c_reg_set(client, DEV_CONTROL_REG, 0xc1);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + /* set Interrupt Disable to Func20 / Enable to Func10) */
>>> + int_en = (polling_req) ? 0 : INT_ENA_ABS_MSK | INT_ENA_REL_MSK;
>>> + ret = synaptics_i2c_reg_set(client, INTERRUPT_EN_REG, int_en);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + control = synaptics_i2c_reg_get(client, GENERAL_2D_CONTROL_REG);
>>> + /* No Deceleration */
>>> + control |= (no_decel) ? 1 << NO_DECELERATION : 0;
>>> + /* Reduced Reporting */
>>> + control |= (reduce_report) ? 1 << REDUCE_REPORTING : 0;
>>> + /* No Filter */
>>> + control |= (no_filter) ? 1 << NO_FILTER : 0;
>>> + ret = synaptics_i2c_reg_set(client, GENERAL_2D_CONTROL_REG, control);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int synaptics_i2c_reset_config(struct i2c_client *client)
>>> +{
>>> + int ret;
>>> + /* Reset the Touchpad */
>>> + ret = synaptics_i2c_reg_set(client, DEV_COMMAND_REG, RESET_COMMAND);
>>> + if (ret) {
>>> + dev_err(&client->dev, "Unable to reset device\n");
>>> + } else {
>>> + msleep(SOFT_RESET_DELAY_MS);
>>> + ret = synaptics_i2c_config(client);
>>> + if (ret)
>>> + dev_err(&client->dev, "Unable to config device\n");
>>> + }
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static int synaptics_i2c_check_error(struct i2c_client *client)
>>> +{
>>> + int status, ret = 0;
>>> + struct synaptics_i2c *touch = i2c_get_clientdata(client);
>>> +
>>> + mutex_lock(&touch->mutex);
>>> +
>>> + status = i2c_smbus_read_byte_data(client, DEVICE_STATUS_REG) &
>>> + (CONFIGURED_MSK | ERROR_MSK);
>>> +
>>> + mutex_unlock(&touch->mutex);
>>> +
>>> + if (status != CONFIGURED_MSK)
>>> + ret = synaptics_i2c_reset_config(client);
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static int synaptics_i2c_get_input(struct synaptics_i2c *touch)
>>> +{
>>> + struct input_dev *input = touch->input;
>>> + int xy_delta, gesture;
>>> + s8 x_delta, y_delta;
>>> +
>>> + /* Deal with spontanious resets and errors */
>>> + if (synaptics_i2c_check_error(touch->client))
>>> + return 0;
>>> +
>>> + /* Get Gesture Bit */
>>> + gesture = GET_BIT(GESTURE,
>>> + synaptics_i2c_reg_get(touch->client, DATA_REG0));
>>> +
>>> + /*
>>> + * Get Relative axes. we have to get them in one shot,
>>> + * so we get 2 bytes starting from REL_X_REG.
>>> + */
>>> + xy_delta = synaptics_i2c_word_get(touch->client, REL_X_REG) & 0xffff;
>>> +
>>> + /* Separate X from Y */
>>> + x_delta = xy_delta & 0xff;
>>> + y_delta = (xy_delta >> REGISTER_LENGTH) & 0xff;
>>> +
>>> + /* Report the button event */
>>> + input_report_key(input, BTN_LEFT, gesture);
>>> +
>>> + /* Report the deltas multiplied by acceleration factor */
>>> + input_report_rel(input, REL_X,
>>> + (touch->accel * (s16) x_delta) / ACCEL_DIVIDER);
>>> + input_report_rel(input, REL_Y,
>>> + -((touch->accel * (s16) y_delta) / ACCEL_DIVIDER));
>>> + input_sync(input);
>>> +
>>> + return (xy_delta || gesture) ? 1 : 0;
>>> +}
>>> +
>>> +static irqreturn_t synaptics_i2c_irq(int irq, void *dev_id)
>>> +{
>>> + struct synaptics_i2c *touch = dev_id;
>>> +
>>> + cancel_delayed_work(&touch->dwork);
>> Why do we need to cancel?
>
> An explanation is below.
>
>>> + schedule_work(&touch->dwork.work);
>> Do not poke inside delayed work structure, just do
>>
>> schedule_delayed_work(&touch->dwork, 0);
>>
>> to schedule it immediately.
>
> Fixed.
>
>>> +
>>> + return IRQ_HANDLED;
>>> +}
>>> +
>>> +static void synaptics_i2c_check_params(struct synaptics_i2c *touch)
>>> +{
>>> + int reset = 0;
>>> + if (accel != touch->accel_param)
>>> + set_acceleration(touch, accel);
>>> +
>>> + if (scan_rate != touch->scan_rate_param)
>>> + set_scan_rate(touch, scan_rate);
>>> +
>>> + if (no_decel != touch->no_decel_param) {
>>> + touch->no_decel_param = no_decel;
>>> + reset = 1;
>>> + }
>>> + if (no_filter != touch->no_filter_param) {
>>> + touch->no_filter_param = no_filter;
>>> + reset = 1;
>>> + }
>>> + if (reduce_report != touch->reduce_report_param) {
>>> + touch->reduce_report_param = reduce_report;
>>> + reset = 1;
>>> + }
>>> + if (reset)
>>> + synaptics_i2c_reset_config(touch->client);
>>> +}
>>> +
>>> +/* Control the Device polling rate / Work Handler sleep time */
>>> +unsigned long synaptics_i2c_fix_delay(struct synaptics_i2c *touch, int
>>> data) +{
>>> + int delay, nodata_count_thres;
>>> +
>>> + if (polling_req) {
>>> + delay = touch->scan_ms;
>>> + if (data) {
>>> + touch->no_data_count = 0;
>>> + } else {
>>> + nodata_count_thres = NO_DATA_THRES / touch->scan_ms;
>>> + if (touch->no_data_count < nodata_count_thres)
>>> + touch->no_data_count++;
>>> + else
>>> + delay = NO_DATA_SLEEP_MSECS;
>>> + }
>>> + } else
>>> + delay = THREAD_IRQ_SLEEP_MSECS;
>>> +
>>> + return msecs_to_jiffies(delay);
>>> +}
>>> +
>>> +/* Work Handler */
>>> +static void synaptics_i2c_work_handler(struct work_struct *work)
>>> +{
>>> + int data = 1;
>>> + struct synaptics_i2c *touch =
>>> + container_of(work, struct synaptics_i2c, dwork.work);
>>> + unsigned long delay;
>>> +
>>> + synaptics_i2c_check_params(touch);
>>> +
>>> + do {
>>> + data = synaptics_i2c_get_input(touch);
>>> + delay = synaptics_i2c_fix_delay(touch, data);
>>> + } while (data);
>>> +
>>> + schedule_delayed_work(&touch->dwork, delay);
>> Do we need to re-schedule the work even if we are in interrupt-driven
>> mode?
>
> We've added the following comment explaining why we re-schedule the work even if
> we are in interrupt-driven mode:
> /*
> While interrupt driven, there is no real need to poll the device.
> But touchpads are very sensitive, so there could be errors
> related to physical environment and the attention line isn't
> neccesarily asserted. In such case we can lose the touchpad.
> We poll the device once in THREAD_IRQ_SLEEP_SECS and
> if error is detected, we try to reset and reconfigure the touchpad.
> */
>
>>> +}
>>> +
>>> +static int synaptics_i2c_open(struct input_dev *input)
>>> +{
>>> + int ret = 0;
>>> + struct synaptics_i2c *touch = input_get_drvdata(input);
>>> +
>>> + ret = synaptics_i2c_reset_config(touch->client);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + if (polling_req)
>>> + schedule_delayed_work(&touch->dwork,
>>> + msecs_to_jiffies(NO_DATA_SLEEP_MSECS));
>>> + else
>>> + enable_irq(touch->client->irq);
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static void synaptics_i2c_close(struct input_dev *input)
>>> +{
>>> + struct synaptics_i2c *touch = input_get_drvdata(input);
>>> +
>>> + if (!polling_req)
>>> + disable_irq(touch->client->irq);
>> Just disabling IRQ is pretty heavy-handed. Can't it ever be shared with
>> another device?
>
> Fixed.
>
>>> +
>>> + cancel_delayed_work_sync(&touch->dwork);
>>> +
>>> + /* Save some power */
>>> + synaptics_i2c_reg_set(touch->client, DEV_CONTROL_REG, DEEP_SLEEP);
>>> +}
>>> +
>>> +static void synaptics_i2c_set_input_params(struct synaptics_i2c *touch)
>>> +{
>>> + struct input_dev *input = touch->input;
>>> +
>>> + input->name = touch->client->name;
>>> + input->phys = touch->client->adapter->name;
>>> + input->id.bustype = BUS_I2C;
>>> + input->id.version = synaptics_i2c_word_get(touch->client,
>>> + INFO_QUERY_REG0);
>>> + input->dev.parent = &touch->client->dev;
>>> + input->open = synaptics_i2c_open;
>>> + input->close = synaptics_i2c_close;
>>> + input_set_drvdata(input, touch);
>>> +
>>> + /* Register the device as mouse */
>>> + set_bit(EV_REL, input->evbit);
>>> + set_bit(REL_X, input->relbit);
>>> + set_bit(REL_Y, input->relbit);
>>> +
>>> + /* Register device's buttons and keys */
>>> + set_bit(EV_KEY, input->evbit);
>>> + set_bit(BTN_LEFT, input->keybit);
>>> +}
>>> +
>>> +struct synaptics_i2c *synaptics_i2c_touch_create(struct i2c_client
>>> *client) +{
>>> + struct synaptics_i2c *touch;
>>> +
>>> + touch = kzalloc(sizeof(struct synaptics_i2c), GFP_KERNEL);
>>> + if (!touch)
>>> + return NULL;
>>> +
>>> + touch->client = client;
>>> + touch->no_decel_param = no_decel;
>>> + touch->scan_rate_param = scan_rate;
>>> + set_scan_rate(touch, scan_rate);
>>> + touch->accel_param = accel;
>>> + set_acceleration(touch, accel);
>>> + INIT_DELAYED_WORK(&touch->dwork, synaptics_i2c_work_handler);
>>> + mutex_init(&touch->mutex);
>>> +
>>> + return touch;
>>> +}
>>> +
>>> +static int __devinit synaptics_i2c_probe(struct i2c_client *client,
>>> + const struct i2c_device_id *dev_id)
>>> +{
>>> + int ret;
>>> + struct synaptics_i2c *touch;
>>> +
>>> + touch = synaptics_i2c_touch_create(client);
>>> + if (!touch)
>>> + return -ENOMEM;
>>> +
>>> + i2c_set_clientdata(client, touch);
>>> +
>>> + ret = synaptics_i2c_reset_config(client);
>>> + if (ret)
>>> + goto err_mem_free;
>>> +
>>> + if (client->irq < 1)
>>> + polling_req = 1;
>>> +
>>> + touch->input = input_allocate_device();
>>> + if (!touch->input) {
>>> + ret = -ENOMEM;
>>> + goto err_mem_free;
>>> + }
>>> +
>>> + synaptics_i2c_set_input_params(touch);
>>> +
>>> + if (!polling_req) {
>>> + dev_dbg(&touch->client->dev,
>>> + "Requesting IRQ: %d\n", touch->client->irq);
>>> +
>>> + ret = request_irq(touch->client->irq, synaptics_i2c_irq,
>>> + IRQF_DISABLED|IRQ_TYPE_EDGE_FALLING,
>>> + DRIVER_NAME, touch);
>>> + if (ret) {
>>> + dev_warn(&touch->client->dev,
>>> + "IRQ request failed: %d, "
>>> + "falling back to polling\n", ret);
>>> + polling_req = 1;
>>> + } else
>>> + disable_irq(touch->client->irq);
>>> + }
>>> +
>>> + if (polling_req)
>>> + dev_dbg(&touch->client->dev,
>>> + "Using polling at rate: %d times/sec\n", scan_rate);
>>> +
>>> + /* Register the device in input subsystem */
>>> + ret = input_register_device(touch->input);
>>> + if (ret) {
>>> + dev_err(&client->dev,
>>> + "Input device register failed: %d\n", ret);
>>> + goto err_input_free;
>>> + }
>>> + return 0;
>>> +
>>> +err_input_free:
>>> + input_free_device(touch->input);
>>> +err_mem_free:
>>> + i2c_set_clientdata(client, NULL);
>>> + kfree(touch);
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static int __devexit synaptics_i2c_remove(struct i2c_client *client)
>>> +{
>>> + struct synaptics_i2c *touch = i2c_get_clientdata(client);
>>> +
>>> + if (!polling_req)
>>> + free_irq(touch->client->irq, touch);
>>> +
>>> + input_unregister_device(touch->input);
>>> + i2c_set_clientdata(client, NULL);
>>> + kfree(touch);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int synaptics_i2c_suspend(struct i2c_client *client, pm_message_t
>>> mesg) +{
>>> + struct synaptics_i2c *touch = i2c_get_clientdata(client);
>>> +
>>> + cancel_delayed_work_sync(&touch->dwork);
>>> +
>>> + /* Save some power */
>>> + synaptics_i2c_reg_set(touch->client, DEV_CONTROL_REG, DEEP_SLEEP);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int synaptics_i2c_resume(struct i2c_client *client)
>>> +{
>>> + int ret;
>>> + struct synaptics_i2c *touch = i2c_get_clientdata(client);
>>> +
>>> + ret = synaptics_i2c_reset_config(client);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + schedule_delayed_work(&touch->dwork,
>>> + msecs_to_jiffies(NO_DATA_SLEEP_MSECS));
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static const struct i2c_device_id synaptics_i2c_id_table[] = {
>>> + { "synaptics_i2c", 0 },
>>> + { },
>>> +};
>>> +MODULE_DEVICE_TABLE(i2c, synaptics_i2c_id_table);
>>> +
>>> +static struct i2c_driver synaptics_i2c_driver = {
>>> + .driver = {
>>> + .name = DRIVER_NAME,
>>> + .owner = THIS_MODULE,
>>> + },
>>> +
>>> + .probe = synaptics_i2c_probe,
>>> + .remove = __devexit_p(synaptics_i2c_remove),
>>> +
>>> + .suspend = synaptics_i2c_suspend,
>>> + .resume = synaptics_i2c_resume,
>>> + .id_table = synaptics_i2c_id_table,
>>> +};
>>> +
>>> +static int __init synaptics_i2c_init(void)
>>> +{
>>> + return i2c_add_driver(&synaptics_i2c_driver);
>>> +}
>>> +
>>> +static void __exit synaptics_i2c_exit(void)
>>> +{
>>> + i2c_del_driver(&synaptics_i2c_driver);
>>> +}
>>> +
>>> +module_init(synaptics_i2c_init);
>>> +module_exit(synaptics_i2c_exit);
>>> +
>>> +MODULE_DESCRIPTION("Synaptics I2C touchpad driver");
>>> +MODULE_AUTHOR("Mike Rapoport, Igor Grinberg, Compulab");
>>> +MODULE_LICENSE("GPL");
>>> +
>> Thanks.
>>
>
> This driver supports Synaptics I2C touchpad controller on eXeda
> mobile device.
>
> Signed-off-by: Igor Grinberg <grinberg@compulab.co.il>
> Signed-off-by: Mike Rapoport <mike@compulab.co.il>
> ---
> drivers/input/mouse/Kconfig | 18 +
> drivers/input/mouse/Makefile | 1 +
> drivers/input/mouse/synaptics_i2c.c | 664 +++++++++++++++++++++++++++++++++++
> 3 files changed, 683 insertions(+), 0 deletions(-)
> create mode 100644 drivers/input/mouse/synaptics_i2c.c
>
> diff --git a/drivers/input/mouse/Kconfig b/drivers/input/mouse/Kconfig
> index c66cc3d..8a2c5b1 100644
> --- a/drivers/input/mouse/Kconfig
> +++ b/drivers/input/mouse/Kconfig
> @@ -303,4 +303,22 @@ config MOUSE_MAPLE
> To compile this driver as a module choose M here: the module will be
> called maplemouse.
>
> +config MOUSE_SYNAPTICS_I2C
> + tristate "Synaptics I2C Touchpad support"
> + depends on I2C
> + help
> + This driver supports Synaptics I2C touchpad controller on eXeda
> + mobile device.
> + The device will not work the synaptics X11 driver because
> + (i) it reports only relative coordinates and has no capabilities
> + to report absolute coordinates
> + (ii) the eXeda device itself uses Xfbdev as X Server and it does
> + not allow using xf86-input-* drivers.
> +
> + Say y here if you have eXeda device and want to use a Synaptics
> + I2C Touchpad.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called synaptics_i2c.
> +
> endif
> diff --git a/drivers/input/mouse/Makefile b/drivers/input/mouse/Makefile
> index 4721894..010f265 100644
> --- a/drivers/input/mouse/Makefile
> +++ b/drivers/input/mouse/Makefile
> @@ -18,6 +18,7 @@ obj-$(CONFIG_MOUSE_PS2) += psmouse.o
> obj-$(CONFIG_MOUSE_PXA930_TRKBALL) += pxa930_trkball.o
> obj-$(CONFIG_MOUSE_RISCPC) += rpcmouse.o
> obj-$(CONFIG_MOUSE_SERIAL) += sermouse.o
> +obj-$(CONFIG_MOUSE_SYNAPTICS_I2C) += synaptics_i2c.o
> obj-$(CONFIG_MOUSE_VSXXXAA) += vsxxxaa.o
>
> psmouse-objs := psmouse-base.o synaptics.o
> diff --git a/drivers/input/mouse/synaptics_i2c.c
> b/drivers/input/mouse/synaptics_i2c.c
> new file mode 100644
> index 0000000..32a5d87
> --- /dev/null
> +++ b/drivers/input/mouse/synaptics_i2c.c
> @@ -0,0 +1,664 @@
> +/*
> + * Synaptics touchpad with I2C interface
> + *
> + * Copyright (C) 2009 Compulab, Ltd.
> + * Mike Rapoport <mike@compulab.co.il>
> + * Igor Grinberg <grinberg@compulab.co.il>
> + *
> + * This file is subject to the terms and conditions of the GNU General Public
> + * License. See the file COPYING in the main directory of this archive for
> + * more details.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/i2c.h>
> +#include <linux/irq.h>
> +#include <linux/interrupt.h>
> +#include <linux/input.h>
> +#include <linux/delay.h>
> +#include <linux/workqueue.h>
> +
> +#define DRIVER_NAME "synaptics_i2c"
> +/* maximum product id is 15 characters */
> +#define PRODUCT_ID_LENGTH 15
> +#define REGISTER_LENGTH 8
> +
> +/*
> + * after soft reset, we should wait for 1 ms
> + * before the device becomes operational
> + */
> +#define SOFT_RESET_DELAY_MS 3
> +/* and after hard reset, we should wait for max 500ms */
> +#define HARD_RESET_DELAY_MS 500
> +
> +/* Registers by SMBus address */
> +#define PAGE_SEL_REG 0xff
> +#define DEVICE_STATUS_REG 0x09
> +
> +/* Registers by RMI address */
> +#define DEV_CONTROL_REG 0x0000
> +#define INTERRUPT_EN_REG 0x0001
> +#define ERR_STAT_REG 0x0002
> +#define INT_REQ_STAT_REG 0x0003
> +#define DEV_COMMAND_REG 0x0004
> +
> +#define RMI_PROT_VER_REG 0x0200
> +#define MANUFACT_ID_REG 0x0201
> +#define PHYS_INT_VER_REG 0x0202
> +#define PROD_PROPERTY_REG 0x0203
> +#define INFO_QUERY_REG0 0x0204
> +#define INFO_QUERY_REG1 (INFO_QUERY_REG0 + 1)
> +#define INFO_QUERY_REG2 (INFO_QUERY_REG0 + 2)
> +#define INFO_QUERY_REG3 (INFO_QUERY_REG0 + 3)
> +
> +#define PRODUCT_ID_REG0 0x0210
> +#define PRODUCT_ID_REG1 (PRODUCT_ID_REG0 + 1)
> +#define PRODUCT_ID_REG2 (PRODUCT_ID_REG0 + 2)
> +#define PRODUCT_ID_REG3 (PRODUCT_ID_REG0 + 3)
> +#define PRODUCT_ID_REG4 (PRODUCT_ID_REG0 + 4)
> +#define PRODUCT_ID_REG5 (PRODUCT_ID_REG0 + 5)
> +#define PRODUCT_ID_REG6 (PRODUCT_ID_REG0 + 6)
> +#define PRODUCT_ID_REG7 (PRODUCT_ID_REG0 + 7)
> +#define PRODUCT_ID_REG8 (PRODUCT_ID_REG0 + 8)
> +#define PRODUCT_ID_REG9 (PRODUCT_ID_REG0 + 9)
> +#define PRODUCT_ID_REG10 (PRODUCT_ID_REG0 + 10)
> +#define PRODUCT_ID_REG11 (PRODUCT_ID_REG0 + 11)
> +#define PRODUCT_ID_REG12 (PRODUCT_ID_REG0 + 12)
> +#define PRODUCT_ID_REG13 (PRODUCT_ID_REG0 + 13)
> +#define PRODUCT_ID_REG14 (PRODUCT_ID_REG0 + 14)
> +#define PRODUCT_ID_REG15 (PRODUCT_ID_REG0 + 15)
> +
> +#define DATA_REG0 0x0400
> +#define ABS_PRESSURE_REG 0x0401
> +#define ABS_MSB_X_REG 0x0402
> +#define ABS_LSB_X_REG (ABS_MSB_X_REG + 1)
> +#define ABS_MSB_Y_REG 0x0404
> +#define ABS_LSB_Y_REG (ABS_MSB_Y_REG + 1)
> +#define REL_X_REG 0x0406
> +#define REL_Y_REG 0x0407
> +
> +#define DEV_QUERY_REG0 0x1000
> +#define DEV_QUERY_REG1 (DEV_QUERY_REG0 + 1)
> +#define DEV_QUERY_REG2 (DEV_QUERY_REG0 + 2)
> +#define DEV_QUERY_REG3 (DEV_QUERY_REG0 + 3)
> +#define DEV_QUERY_REG4 (DEV_QUERY_REG0 + 4)
> +#define DEV_QUERY_REG5 (DEV_QUERY_REG0 + 5)
> +#define DEV_QUERY_REG6 (DEV_QUERY_REG0 + 6)
> +#define DEV_QUERY_REG7 (DEV_QUERY_REG0 + 7)
> +#define DEV_QUERY_REG8 (DEV_QUERY_REG0 + 8)
> +
> +#define GENERAL_2D_CONTROL_REG 0x1041
> +#define SENSOR_SENSITIVITY_REG 0x1044
> +#define SENS_MAX_POS_MSB_REG 0x1046
> +#define SENS_MAX_POS_LSB_REG (SENS_MAX_POS_UPPER_REG + 1)
> +
> +/* Register bits */
> +/* Device Control Register Bits */
> +#define REPORT_RATE_1ST_BIT 6
> +
> +/* Interrupt Enable Register Bits (INTERRUPT_EN_REG) */
> +#define F10_ABS_INT_ENA 0
> +#define F10_REL_INT_ENA 1
> +#define F20_INT_ENA 2
> +
> +/* Interrupt Request Register Bits (INT_REQ_STAT_REG | DEVICE_STATUS_REG) */
> +#define F10_ABS_INT_REQ 0
> +#define F10_REL_INT_REQ 1
> +#define F20_INT_REQ 2
> +/* Device Status Register Bits (DEVICE_STATUS_REG) */
> +#define STAT_CONFIGURED 6
> +#define STAT_ERROR 7
> +
> +/* Device Command Register Bits (DEV_COMMAND_REG) */
> +#define RESET_COMMAND 0x01
> +#define REZERO_COMMAND 0x02
> +
> +/* Data Register 0 Bits (DATA_REG0) */
> +#define GESTURE 3
> +
> +/* Device Query Registers Bits */
> +/* DEV_QUERY_REG3 */
> +#define HAS_PALM_DETECT 1
> +#define HAS_MULTI_FING 2
> +#define HAS_SCROLLER 4
> +#define HAS_2D_SCROLL 5
> +
> +/* General 2D Control Register Bits (GENERAL_2D_CONTROL_REG) */
> +#define NO_DECELERATION 1
> +#define REDUCE_REPORTING 3
> +#define NO_FILTER 5
> +
> +/* Function Masks */
> +/* Device Control Register Masks (DEV_CONTROL_REG) */
> +#define REPORT_RATE_MSK 0xc0
> +#define SLEEP_MODE_MSK 0x07
> +
> +/* Device Sleep Modes */
> +#define FULL_AWAKE 0x0
> +#define NORMAL_OP 0x1
> +#define LOW_PWR_OP 0x2
> +#define VERY_LOW_PWR_OP 0x3
> +#define SENS_SLEEP 0x4
> +#define SLEEP_MOD 0x5
> +#define DEEP_SLEEP 0x6
> +#define HIBERNATE 0x7
> +
> +/* Interrupt Register Mask */
> +/* (INT_REQ_STAT_REG | DEVICE_STATUS_REG | INTERRUPT_EN_REG) */
> +#define INT_ENA_REQ_MSK 0x07
> +#define INT_ENA_ABS_MSK 0x01
> +#define INT_ENA_REL_MSK 0x02
> +#define INT_ENA_F20_MSK 0x04
> +
> +/* Device Status Register Masks (DEVICE_STATUS_REG) */
> +#define CONFIGURED_MSK 0x40
> +#define ERROR_MSK 0x80
> +
> +/* Data Register 0 Masks */
> +#define FINGER_WIDTH_MSK 0xf0
> +#define GESTURE_MSK 0x08
> +#define SENSOR_STATUS_MSK 0x07
> +
> +/*
> + * MSB Position Register Masks
> + * ABS_MSB_X_REG | ABS_MSB_Y_REG | SENS_MAX_POS_MSB_REG |
> + * DEV_QUERY_REG3 | DEV_QUERY_REG5
> + */
> +#define MSB_POSITION_MSK 0x1f
> +
> +/* Device Query Registers Masks */
> +
> +/* DEV_QUERY_REG2 */
> +#define NUM_EXTRA_POS_MSK 0x07
> +
> +/* When in IRQ mode read the device every THREAD_IRQ_SLEEP_SECS */
> +#define THREAD_IRQ_SLEEP_SECS 2
> +#define THREAD_IRQ_SLEEP_MSECS (THREAD_IRQ_SLEEP_SECS * MSEC_PER_SEC)
> +
> +/*
> + * When in Polling mode and no data received for NO_DATA_THRES msecs
> + * reduce the polling rate to NO_DATA_SLEEP_MSECS
> + */
> +#define NO_DATA_THRES (MSEC_PER_SEC)
> +#define NO_DATA_SLEEP_MSECS (MSEC_PER_SEC / 4)
> +
> +/* Control touchpad's No Deceleration option */
> +static int no_decel = 1;
> +module_param(no_decel, bool, 0644);
> +MODULE_PARM_DESC(no_decel, "No Deceleration. Default = 1 (on)");
> +
> +/* Control touchpad's Reduced Reporting option */
> +static int reduce_report;
> +module_param(reduce_report, bool, 0644);
> +MODULE_PARM_DESC(reduce_report, "Reduced Reporting. Default = 0 (off)");
> +
> +/* Control touchpad's No Filter option */
> +static int no_filter;
> +module_param(no_filter, bool, 0644);
> +MODULE_PARM_DESC(no_filter, "No Filter. Default = 0 (off)");
> +
> +/*
> + * touchpad Attention line is Active Low and Open Drain,
> + * therefore should be connected to pulled up line
> + * and the irq configuration should be set to Falling Edge Trigger
> + */
> +/* Control IRQ / Polling option */
> +static int polling_req;
> +module_param(polling_req, bool, 0444);
> +MODULE_PARM_DESC(polling_req, "Request Polling. Default = 0 (use irq)");
> +
> +/* Control Polling Rate */
> +static int scan_rate = 80;
> +module_param(scan_rate, int, 0644);
> +MODULE_PARM_DESC(scan_rate, "Polling rate in times/sec. Default = 80");
> +
> +/* The main device structure */
> +struct synaptics_i2c {
> + struct i2c_client *client;
> + struct input_dev *input;
> + struct delayed_work dwork;
> + int no_data_count;
> + int no_decel_param;
> + int reduce_report_param;
> + int no_filter_param;
> + int scan_rate_param;
> + int scan_ms;
> +};
> +
> +static inline void set_scan_rate(struct synaptics_i2c *touch, int scan_rate)
> +{
> + touch->scan_ms = MSEC_PER_SEC / scan_rate;
> + touch->scan_rate_param = scan_rate;
> +}
> +
> +/*
> + * Driver's initial design makes no race condition possible on i2c bus,
> + * so there is no need in any locking.
> + * Keep it in mind, while playing with the code.
> + */
> +static s32 synaptics_i2c_reg_get(struct i2c_client *client, u16 reg)
> +{
> + int ret;
> +
> + ret = i2c_smbus_write_byte_data(client, PAGE_SEL_REG, reg >> 8);
> + if (ret)
> + return ret;
> +
> + return i2c_smbus_read_byte_data(client, reg & 0xff);
> +}
> +
> +static s32 synaptics_i2c_reg_set(struct i2c_client *client, u16 reg, u8 val)
> +{
> + int ret;
> +
> + ret = i2c_smbus_write_byte_data(client, PAGE_SEL_REG, reg >> 8);
> + if (ret)
> + return ret;
> +
> + return i2c_smbus_write_byte_data(client, reg & 0xff, val);
> +}
> +
> +static s32 synaptics_i2c_word_get(struct i2c_client *client, u16 reg)
> +{
> + int ret;
> +
> + ret = i2c_smbus_write_byte_data(client, PAGE_SEL_REG, reg >> 8);
> + if (ret)
> + return ret;
> +
> + return i2c_smbus_read_word_data(client, reg & 0xff);
> +}
> +
> +static int synaptics_i2c_config(struct i2c_client *client)
> +{
> + int ret, control;
> + u8 int_en;
> +
> + /* set Report Rate to Device Highest (>=80) and Sleep to normal */
> + ret = synaptics_i2c_reg_set(client, DEV_CONTROL_REG, 0xc1);
> + if (ret)
> + return ret;
> +
> + /* set Interrupt Disable to Func20 / Enable to Func10) */
> + int_en = (polling_req) ? 0 : INT_ENA_ABS_MSK | INT_ENA_REL_MSK;
> + ret = synaptics_i2c_reg_set(client, INTERRUPT_EN_REG, int_en);
> + if (ret)
> + return ret;
> +
> + control = synaptics_i2c_reg_get(client, GENERAL_2D_CONTROL_REG);
> + /* No Deceleration */
> + control |= (no_decel) ? 1 << NO_DECELERATION : 0;
> + /* Reduced Reporting */
> + control |= (reduce_report) ? 1 << REDUCE_REPORTING : 0;
> + /* No Filter */
> + control |= (no_filter) ? 1 << NO_FILTER : 0;
> + ret = synaptics_i2c_reg_set(client, GENERAL_2D_CONTROL_REG, control);
> + if (ret)
> + return ret;
> +
> + return 0;
> +}
> +
> +static int synaptics_i2c_reset_config(struct i2c_client *client)
> +{
> + int ret;
> + /* Reset the Touchpad */
> + ret = synaptics_i2c_reg_set(client, DEV_COMMAND_REG, RESET_COMMAND);
> + if (ret) {
> + dev_err(&client->dev, "Unable to reset device\n");
> + } else {
> + msleep(SOFT_RESET_DELAY_MS);
> + ret = synaptics_i2c_config(client);
> + if (ret)
> + dev_err(&client->dev, "Unable to config device\n");
> + }
> +
> + return ret;
> +}
> +
> +static int synaptics_i2c_check_error(struct i2c_client *client)
> +{
> + int status, ret = 0;
> +
> + status = i2c_smbus_read_byte_data(client, DEVICE_STATUS_REG) &
> + (CONFIGURED_MSK | ERROR_MSK);
> +
> + if (status != CONFIGURED_MSK)
> + ret = synaptics_i2c_reset_config(client);
> +
> + return ret;
> +}
> +
> +static int synaptics_i2c_get_input(struct synaptics_i2c *touch)
> +{
> + struct input_dev *input = touch->input;
> + int xy_delta, gesture;
> + s8 x_delta, y_delta;
> +
> + /* Deal with spontanious resets and errors */
> + if (synaptics_i2c_check_error(touch->client))
> + return 0;
> +
> + /* Get Gesture Bit */
> + gesture = (synaptics_i2c_reg_get(touch->client, DATA_REG0)
> + >> GESTURE) & 0x1;
> +
> + /*
> + * Get Relative axes. we have to get them in one shot,
> + * so we get 2 bytes starting from REL_X_REG.
> + */
> + xy_delta = synaptics_i2c_word_get(touch->client, REL_X_REG) & 0xffff;
> +
> + /* Separate X from Y */
> + x_delta = xy_delta & 0xff;
> + y_delta = (xy_delta >> REGISTER_LENGTH) & 0xff;
> +
> + /* Report the button event */
> + input_report_key(input, BTN_LEFT, gesture);
> +
> + /* Report the deltas */
> + input_report_rel(input, REL_X, x_delta);
> + input_report_rel(input, REL_Y, -y_delta);
> + input_sync(input);
> +
> + return (xy_delta || gesture) ? 1 : 0;
> +}
> +
> +static irqreturn_t synaptics_i2c_irq(int irq, void *dev_id)
> +{
> + struct synaptics_i2c *touch = dev_id;
> +
> + /*
> + * This cancels the work scheduled in work handler.
> + * See explanation on this in work handler func.
> + */
> + cancel_delayed_work(&touch->dwork);
> + schedule_delayed_work(&touch->dwork, 0);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static void synaptics_i2c_check_params(struct synaptics_i2c *touch)
> +{
> + int reset = 0;
> + if (scan_rate != touch->scan_rate_param)
> + set_scan_rate(touch, scan_rate);
> +
> + if (no_decel != touch->no_decel_param) {
> + touch->no_decel_param = no_decel;
> + reset = 1;
> + }
> + if (no_filter != touch->no_filter_param) {
> + touch->no_filter_param = no_filter;
> + reset = 1;
> + }
> + if (reduce_report != touch->reduce_report_param) {
> + touch->reduce_report_param = reduce_report;
> + reset = 1;
> + }
> + if (reset)
> + synaptics_i2c_reset_config(touch->client);
> +}
> +
> +/* Control the Device polling rate / Work Handler sleep time */
> +unsigned long synaptics_i2c_fix_delay(struct synaptics_i2c *touch, int data)
> +{
> + int delay, nodata_count_thres;
> +
> + if (polling_req) {
> + delay = touch->scan_ms;
> + if (data) {
> + touch->no_data_count = 0;
> + } else {
> + nodata_count_thres = NO_DATA_THRES / touch->scan_ms;
> + if (touch->no_data_count < nodata_count_thres)
> + touch->no_data_count++;
> + else
> + delay = NO_DATA_SLEEP_MSECS;
> + }
> + } else
> + delay = THREAD_IRQ_SLEEP_MSECS;
> +
> + return msecs_to_jiffies(delay);
> +}
> +
> +/* Work Handler */
> +static void synaptics_i2c_work_handler(struct work_struct *work)
> +{
> + int data = 1;
> + struct synaptics_i2c *touch =
> + container_of(work, struct synaptics_i2c, dwork.work);
> + unsigned long delay;
> +
> + synaptics_i2c_check_params(touch);
> +
> + do {
> + data = synaptics_i2c_get_input(touch);
> + delay = synaptics_i2c_fix_delay(touch, data);
> + } while (data);
> +
> + /*
> + * While interrupt driven, there is no real need to poll the device.
> + * But touchpads are very sensitive, so there could be errors
> + * related to physical environment and the attention line isn't
> + * neccesarily asserted. In such case we can lose the touchpad.
> + * We poll the device once in THREAD_IRQ_SLEEP_SECS and
> + * if error is detected, we try to reset and reconfigure the touchpad.
> + */
> + schedule_delayed_work(&touch->dwork, delay);
> +}
> +
> +static int synaptics_i2c_open(struct input_dev *input)
> +{
> + int ret = 0;
> + struct synaptics_i2c *touch = input_get_drvdata(input);
> +
> + ret = synaptics_i2c_reset_config(touch->client);
> + if (ret)
> + return ret;
> +
> + if (polling_req)
> + schedule_delayed_work(&touch->dwork,
> + msecs_to_jiffies(NO_DATA_SLEEP_MSECS));
> +
> + return ret;
> +}
> +
> +static void synaptics_i2c_close(struct input_dev *input)
> +{
> + struct synaptics_i2c *touch = input_get_drvdata(input);
> +
> + if (!polling_req)
> + synaptics_i2c_reg_set(touch->client, INTERRUPT_EN_REG, 0);
> +
> + cancel_delayed_work_sync(&touch->dwork);
> +
> + /* Save some power */
> + synaptics_i2c_reg_set(touch->client, DEV_CONTROL_REG, DEEP_SLEEP);
> +}
> +
> +static void synaptics_i2c_set_input_params(struct synaptics_i2c *touch)
> +{
> + struct input_dev *input = touch->input;
> +
> + input->name = touch->client->name;
> + input->phys = touch->client->adapter->name;
> + input->id.bustype = BUS_I2C;
> + input->id.version = synaptics_i2c_word_get(touch->client,
> + INFO_QUERY_REG0);
> + input->dev.parent = &touch->client->dev;
> + input->open = synaptics_i2c_open;
> + input->close = synaptics_i2c_close;
> + input_set_drvdata(input, touch);
> +
> + /* Register the device as mouse */
> + set_bit(EV_REL, input->evbit);
> + set_bit(REL_X, input->relbit);
> + set_bit(REL_Y, input->relbit);
> +
> + /* Register device's buttons and keys */
> + set_bit(EV_KEY, input->evbit);
> + set_bit(BTN_LEFT, input->keybit);
> +}
> +
> +struct synaptics_i2c *synaptics_i2c_touch_create(struct i2c_client *client)
> +{
> + struct synaptics_i2c *touch;
> +
> + touch = kzalloc(sizeof(struct synaptics_i2c), GFP_KERNEL);
> + if (!touch)
> + return NULL;
> +
> + touch->client = client;
> + touch->no_decel_param = no_decel;
> + touch->scan_rate_param = scan_rate;
> + set_scan_rate(touch, scan_rate);
> + INIT_DELAYED_WORK(&touch->dwork, synaptics_i2c_work_handler);
> +
> + return touch;
> +}
> +
> +static int __devinit synaptics_i2c_probe(struct i2c_client *client,
> + const struct i2c_device_id *dev_id)
> +{
> + int ret;
> + struct synaptics_i2c *touch;
> +
> + touch = synaptics_i2c_touch_create(client);
> + if (!touch)
> + return -ENOMEM;
> +
> + i2c_set_clientdata(client, touch);
> +
> + ret = synaptics_i2c_reset_config(client);
> + if (ret)
> + goto err_mem_free;
> +
> + if (client->irq < 1)
> + polling_req = 1;
> +
> + touch->input = input_allocate_device();
> + if (!touch->input) {
> + ret = -ENOMEM;
> + goto err_mem_free;
> + }
> +
> + synaptics_i2c_set_input_params(touch);
> +
> + if (!polling_req) {
> + dev_dbg(&touch->client->dev,
> + "Requesting IRQ: %d\n", touch->client->irq);
> +
> + ret = request_irq(touch->client->irq, synaptics_i2c_irq,
> + IRQF_DISABLED|IRQ_TYPE_EDGE_FALLING,
> + DRIVER_NAME, touch);
> + if (ret) {
> + dev_warn(&touch->client->dev,
> + "IRQ request failed: %d, "
> + "falling back to polling\n", ret);
> + polling_req = 1;
> + synaptics_i2c_reg_set(touch->client,
> + INTERRUPT_EN_REG, 0);
> + }
> + }
> +
> + if (polling_req)
> + dev_dbg(&touch->client->dev,
> + "Using polling at rate: %d times/sec\n", scan_rate);
> +
> + /* Register the device in input subsystem */
> + ret = input_register_device(touch->input);
> + if (ret) {
> + dev_err(&client->dev,
> + "Input device register failed: %d\n", ret);
> + goto err_input_free;
> + }
> + return 0;
> +
> +err_input_free:
> + input_free_device(touch->input);
> +err_mem_free:
> + i2c_set_clientdata(client, NULL);
> + kfree(touch);
> +
> + return ret;
> +}
> +
> +static int __devexit synaptics_i2c_remove(struct i2c_client *client)
> +{
> + struct synaptics_i2c *touch = i2c_get_clientdata(client);
> +
> + if (!polling_req)
> + free_irq(touch->client->irq, touch);
> +
> + input_unregister_device(touch->input);
> + i2c_set_clientdata(client, NULL);
> + kfree(touch);
> +
> + return 0;
> +}
> +
> +static int synaptics_i2c_suspend(struct i2c_client *client, pm_message_t mesg)
> +{
> + struct synaptics_i2c *touch = i2c_get_clientdata(client);
> +
> + cancel_delayed_work_sync(&touch->dwork);
> +
> + /* Save some power */
> + synaptics_i2c_reg_set(touch->client, DEV_CONTROL_REG, DEEP_SLEEP);
> +
> + return 0;
> +}
> +
> +static int synaptics_i2c_resume(struct i2c_client *client)
> +{
> + int ret;
> + struct synaptics_i2c *touch = i2c_get_clientdata(client);
> +
> + ret = synaptics_i2c_reset_config(client);
> + if (ret)
> + return ret;
> +
> + schedule_delayed_work(&touch->dwork,
> + msecs_to_jiffies(NO_DATA_SLEEP_MSECS));
> +
> + return 0;
> +}
> +
> +static const struct i2c_device_id synaptics_i2c_id_table[] = {
> + { "synaptics_i2c", 0 },
> + { },
> +};
> +MODULE_DEVICE_TABLE(i2c, synaptics_i2c_id_table);
> +
> +static struct i2c_driver synaptics_i2c_driver = {
> + .driver = {
> + .name = DRIVER_NAME,
> + .owner = THIS_MODULE,
> + },
> +
> + .probe = synaptics_i2c_probe,
> + .remove = __devexit_p(synaptics_i2c_remove),
> +
> + .suspend = synaptics_i2c_suspend,
> + .resume = synaptics_i2c_resume,
> + .id_table = synaptics_i2c_id_table,
> +};
> +
> +static int __init synaptics_i2c_init(void)
> +{
> + return i2c_add_driver(&synaptics_i2c_driver);
> +}
> +
> +static void __exit synaptics_i2c_exit(void)
> +{
> + i2c_del_driver(&synaptics_i2c_driver);
> +}
> +
> +module_init(synaptics_i2c_init);
> +module_exit(synaptics_i2c_exit);
> +
> +MODULE_DESCRIPTION("Synaptics I2C touchpad driver");
> +MODULE_AUTHOR("Mike Rapoport, Igor Grinberg, Compulab");
> +MODULE_LICENSE("GPL");
> +
--
Sincerely yours,
Mike.
next prev parent reply other threads:[~2009-06-11 5:52 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2009-05-20 12:36 [PATCH v2] input: add synaptics_i2c driver Mike Rapoport
2009-05-20 12:49 ` Jean Delvare
2009-06-02 5:40 ` Mike Rapoport
2009-06-02 11:58 ` Dmitry Torokhov
2009-06-03 7:59 ` Mike Rapoport
2009-06-11 5:52 ` Mike Rapoport [this message]
2009-06-11 7:14 ` Dmitry Torokhov
2009-06-11 10:35 ` Mike Rapoport
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=4A309BA9.8030002@compulab.co.il \
--to=mike@compulab.co.il \
--cc=dmitry.torokhov@gmail.com \
--cc=grinberg@compulab.co.il \
--cc=khali@linux-fr.org \
--cc=linux-input@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).