linux-input.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v3] add analog devices AD714X captouch input driver
@ 2009-09-11  6:53 Barry Song
  2009-09-11 13:19 ` [Uclinux-dist-devel] [PATCH v3] add analog devices AD714X captouchinput driver Hennerich, Michael
                   ` (2 more replies)
  0 siblings, 3 replies; 21+ messages in thread
From: Barry Song @ 2009-09-11  6:53 UTC (permalink / raw)
  To: dmitry.torokhov; +Cc: linux-input, uclinux-dist-devel, Barry Song

AD7142 and AD7147 are integrated capacitance-to-digital converters
(CDCs) with on-chip environmental calibration for use in systems
requiring a novel user input method. The AD7142 and AD7147 can interface
to external capacitance sensors implementing functions such as buttons,
scrollwheels, sliders, touchpads and so on.

The chips don't restrict the specific usage. Depending on the hardware
connection, one special target board can include one or several these
components. The platform_data for the device's "struct device" holds
these information. The data-struct defined in head file descript the
hardware feature of button/scrollwheel/slider/touchpad components on
target boards, which need be filled in the arch/mach-/.

As the result, the driver is independent of boards. It gets the
components layout from the platform_data, registers related devices,
fullfills the algorithms and state machines for these components and
report related input events to up level.

Signed-off-by: Barry Song <21cnbao@gmail.com>
---
 drivers/input/misc/Kconfig   |   23 +
 drivers/input/misc/Makefile  |    1 +
 drivers/input/misc/ad714x.c  | 1652 ++++++++++++++++++++++++++++++++++++++++++
 include/linux/input.h        |    1 +
 include/linux/input/ad714x.h |   62 ++
 5 files changed, 1739 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/misc/ad714x.c
 create mode 100644 include/linux/input/ad714x.h

diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index cbe21bc..da83556 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -260,6 +260,29 @@ config INPUT_RB532_BUTTON
 	  To compile this driver as a module, choose M here: the
 	  module will be called rb532_button.
 
+config INPUT_AD714X
+	tristate "Analog Devices AD7142/7147 Capacitance Touch Sensor"
+	depends on SPI || I2C
+	help
+	  Say Y here if you want to support an AD7142/7147 touch sensor.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called ad714x.
+
+config INPUT_EVAL_AD7147EBZ
+	bool "ADI EVAL-AD7147EBZ board"
+	depends on INPUT_AD714X && SPI
+	help
+	  Select this if you are using ADI EVAL-AD7147EBZ board, there are
+	  five buttons and one slider on this board.
+
+config INPUT_EVAL_AD7142EB
+	bool "ADI EVAL-AD7142EB board"
+	depends on INPUT_AD714X && I2C
+	help
+	  Select this if you are using ADI EVAL-AD7142EB board, there are
+	  four buttons and two sliders on this board.
+
 config INPUT_DM355EVM
 	tristate "TI DaVinci DM355 EVM Keypad and IR Remote"
 	depends on MFD_DM355EVM_MSP
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 79c1e9a..39e0ae9 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -4,6 +4,7 @@
 
 # Each configuration option enables a list of files.
 
+obj-$(CONFIG_INPUT_AD714X)		+= ad714x.o
 obj-$(CONFIG_INPUT_APANEL)		+= apanel.o
 obj-$(CONFIG_INPUT_ATI_REMOTE)		+= ati_remote.o
 obj-$(CONFIG_INPUT_ATI_REMOTE2)		+= ati_remote2.o
diff --git a/drivers/input/misc/ad714x.c b/drivers/input/misc/ad714x.c
new file mode 100644
index 0000000..505ffaf
--- /dev/null
+++ b/drivers/input/misc/ad714x.c
@@ -0,0 +1,1652 @@
+/*
+ * AD714X CapTouch Programmable Controller driver
+ *
+ * Copyright 2009 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+#define pr_fmt(fmt) "ad714x: " fmt
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/spi/spi.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/input/ad714x.h>
+
+#define AD714x_SPI_CMD_PREFIX      0xE000   /* bits 15:11 */
+#define AD714x_SPI_READ            BIT(10)
+
+#define AD714X_PWR_CTRL           0x0
+#define AD714X_STG_CAL_EN_REG     0x1
+#define AD714X_AMB_COMP_CTRL0_REG 0x2
+#define AD714X_PARTID_REG         0x17
+#define AD7147_PARTID             0x1470
+#define AD7142_PARTID             0xE620
+#define AD714X_STAGECFG_REG       0x80
+#define AD714X_SYSCFG_REG         0x0
+
+#define STG_LOW_INT_EN_REG     0x5
+#define STG_HIGH_INT_EN_REG    0x6
+#define STG_COM_INT_EN_REG     0x7
+#define STG_LOW_INT_STA_REG    0x8
+#define STG_HIGH_INT_STA_REG   0x9
+#define STG_COM_INT_STA_REG    0xA
+
+#define CDC_RESULT_S0          0xB
+#define CDC_RESULT_S1          0xC
+#define CDC_RESULT_S2          0xD
+#define CDC_RESULT_S3          0xE
+#define CDC_RESULT_S4          0xF
+#define CDC_RESULT_S5          0x10
+#define CDC_RESULT_S6          0x11
+#define CDC_RESULT_S7          0x12
+#define CDC_RESULT_S8          0x13
+#define CDC_RESULT_S9          0x14
+#define CDC_RESULT_S10         0x15
+#define CDC_RESULT_S11         0x16
+
+#define STAGE0_AMBIENT		0xF1
+#define STAGE1_AMBIENT		0x115
+#define STAGE2_AMBIENT		0x139
+#define STAGE3_AMBIENT		0x15D
+#define STAGE4_AMBIENT		0x181
+#define STAGE5_AMBIENT		0x1A5
+#define STAGE6_AMBIENT		0x1C9
+#define STAGE7_AMBIENT		0x1ED
+#define STAGE8_AMBIENT		0x211
+#define STAGE9_AMBIENT		0x234
+#define STAGE10_AMBIENT		0x259
+#define STAGE11_AMBIENT		0x27D
+
+#define PER_STAGE_REG_NUM      36
+#define STAGE_NUM              12
+#define STAGE_CFGREG_NUM       8
+#define SYS_CFGREG_NUM         8
+
+/*
+ * driver information which will be used to maintain the software flow
+ */
+typedef enum {IDLE, JITTER, ACTIVE, SPACE} ad714x_device_state;
+
+struct ad714x_slider_drv {
+	int highest_stage;
+	int abs_pos;
+	int flt_pos;
+	ad714x_device_state state;
+	struct input_dev *input;
+};
+
+struct ad714x_wheel_drv {
+	int abs_pos;
+	int flt_pos;
+	int pre_mean_value;
+	int pre_highest_stage;
+	int pre_mean_value_no_offset;
+	int mean_value;
+	int mean_value_no_offset;
+	int pos_offset;
+	int pos_ratio;
+	int highest_stage;
+	ad714x_device_state state;
+	struct input_dev *input;
+};
+
+struct ad714x_touchpad_drv {
+	int x_highest_stage;
+	int x_flt_pos;
+	int x_abs_pos;
+	int y_highest_stage;
+	int y_flt_pos;
+	int y_abs_pos;
+	int left_ep;
+	int left_ep_val;
+	int right_ep;
+	int right_ep_val;
+	int top_ep;
+	int top_ep_val;
+	int bottom_ep;
+	int bottom_ep_val;
+	ad714x_device_state state;
+	struct input_dev *input;
+};
+
+struct ad714x_button_drv {
+	ad714x_device_state state;
+	/* Unlike slider/wheel/touchpad, all buttons point to
+	 * same input_dev instance
+	 */
+	struct input_dev *input;
+};
+
+struct ad714x_driver_data {
+	struct ad714x_slider_drv *slider;
+	struct ad714x_wheel_drv *wheel;
+	struct ad714x_touchpad_drv *touchpad;
+	struct ad714x_button_drv *button;
+};
+
+/* information to integrate all things which will be private data
+ * of spi/i2c device
+ */
+struct ad714x_chip {
+	unsigned short h_state;
+	unsigned short l_state;
+	unsigned short c_state;
+	unsigned short adc_reg[STAGE_NUM];
+	unsigned short amb_reg[STAGE_NUM];
+	unsigned short sensor_val[STAGE_NUM];
+
+	struct ad714x_platform_data *hw;
+	struct ad714x_driver_data *sw;
+
+	int irq;
+	struct device *dev;
+	int (*read) (struct device *, unsigned short, unsigned short *);
+	int (*write) (struct device *, unsigned short, unsigned short);
+
+	struct mutex mutex;
+
+	unsigned product;
+	unsigned version;
+};
+
+static void ad714x_use_com_int(struct ad714x_chip *ad714x, int start_stage,
+		int end_stage)
+{
+	unsigned short data;
+	unsigned short mask;
+
+	mask = ((1 << (end_stage + 1)) - 1) - (1 << start_stage);
+
+	ad714x->read(ad714x->dev, STG_COM_INT_EN_REG, &data);
+	data |= 1 << start_stage;
+	ad714x->write(ad714x->dev, STG_COM_INT_EN_REG, data);
+
+	ad714x->read(ad714x->dev, STG_HIGH_INT_EN_REG, &data);
+	data &= ~mask;
+	ad714x->write(ad714x->dev, STG_HIGH_INT_EN_REG, data);
+}
+
+static void ad714x_use_thr_int(struct ad714x_chip *ad714x, int start_stage,
+		int end_stage)
+{
+	unsigned short data;
+	unsigned short mask;
+
+	mask = ((1 << (end_stage + 1)) - 1) - (1 << start_stage);
+
+	ad714x->read(ad714x->dev, STG_COM_INT_EN_REG, &data);
+	data &= ~(1 << start_stage);
+	ad714x->write(ad714x->dev, STG_COM_INT_EN_REG, data);
+
+	ad714x->read(ad714x->dev, STG_HIGH_INT_EN_REG, &data);
+	data |= mask;
+	ad714x->write(ad714x->dev, STG_HIGH_INT_EN_REG, data);
+}
+
+static int ad714x_cal_highest_stage(struct ad714x_chip *ad714x, int start_stage,
+		int end_stage)
+{
+	int max_res = 0;
+	int max_idx = 0;
+	int i;
+
+	for (i = start_stage; i <= end_stage; i++) {
+		if (ad714x->sensor_val[i] > max_res) {
+			max_res = ad714x->sensor_val[i];
+			max_idx = i;
+		}
+	}
+
+	return max_idx;
+}
+
+static int ad714x_cal_abs_pos(struct ad714x_chip *ad714x, int start_stage,
+		int end_stage, int highest_stage, int max_coord)
+{
+	int a_param, b_param;
+
+	if (highest_stage == start_stage) {
+		a_param = ad714x->sensor_val[start_stage + 1];
+		b_param = ad714x->sensor_val[start_stage] +
+			ad714x->sensor_val[start_stage + 1];
+	} else if (highest_stage == end_stage) {
+		a_param = ad714x->sensor_val[end_stage] *
+			(end_stage - start_stage) +
+			ad714x->sensor_val[end_stage - 1] *
+			(end_stage - start_stage - 1);
+		b_param = ad714x->sensor_val[end_stage] +
+			ad714x->sensor_val[end_stage - 1];
+	} else {
+		a_param = ad714x->sensor_val[highest_stage] *
+			(highest_stage - start_stage) +
+			ad714x->sensor_val[highest_stage - 1] *
+			(highest_stage - start_stage - 1) +
+			ad714x->sensor_val[highest_stage + 1] *
+			(highest_stage - start_stage + 1);
+		b_param = ad714x->sensor_val[highest_stage] +
+			ad714x->sensor_val[highest_stage - 1] +
+			ad714x->sensor_val[highest_stage + 1];
+	}
+
+	return (max_coord / (end_stage - start_stage)) * a_param / b_param;
+}
+
+
+/* One button can connect to multi positive and negative of CDCs
+ * Multi-buttons can connect to same positive/negative of one CDC
+ */
+static void ad714x_button_state_machine(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_button_plat *hw = &ad714x->hw->button[idx];
+	struct ad714x_button_drv *sw = &ad714x->sw->button[idx];
+
+	switch (sw->state) {
+	case IDLE:
+		if (((ad714x->h_state & hw->h_mask) == hw->h_mask) &&
+			((ad714x->l_state & hw->l_mask) == hw->l_mask)) {
+			dev_dbg(ad714x->dev, "button %d touched\n", idx);
+			input_report_key(sw->input, hw->keycode, 1);
+			input_sync(sw->input);
+			sw->state = ACTIVE;
+		}
+		break;
+	case ACTIVE:
+		if (((ad714x->h_state & hw->h_mask) != hw->h_mask) ||
+			((ad714x->l_state & hw->l_mask) != hw->l_mask)) {
+			dev_dbg(ad714x->dev, "button %d released\n", idx);
+			input_report_key(sw->input, hw->keycode, 0);
+			input_sync(sw->input);
+			sw->state = IDLE;
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+/* The response of a sensor is defined by the absolute number of codes
+ * between the current CDC value and the ambient value.
+ */
+void ad714x_slider_cal_sensor_val(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx];
+	int i;
+
+	for (i = hw->start_stage; i <= hw->end_stage; i++) {
+		ad714x->read(ad714x->dev, CDC_RESULT_S0 + i,
+			&ad714x->adc_reg[i]);
+		ad714x->read(ad714x->dev,
+				STAGE0_AMBIENT + i * PER_STAGE_REG_NUM,
+				&ad714x->amb_reg[i]);
+
+		ad714x->sensor_val[i] = abs(ad714x->adc_reg[i] -
+				ad714x->amb_reg[i]);
+	}
+}
+
+void ad714x_slider_cal_highest_stage(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx];
+	struct ad714x_slider_drv *sw = &ad714x->sw->slider[idx];
+
+	sw->highest_stage = ad714x_cal_highest_stage(ad714x, hw->start_stage,
+			hw->end_stage);
+
+	dev_dbg(ad714x->dev, "slider %d highest_stage:%d\n", idx,
+		sw->highest_stage);
+}
+
+/* The formulae are very straight forward. It uses the sensor with the
+ * highest response and the 2 adjacent ones.
+ * When Sensor 0 has the highest response, only sensor 0 and sensor 1
+ * are used in the calculations. Similarly when the last sensor has the
+ * highest response, only the last sensor and the second last sensors
+ * are used in the calculations.
+ *
+ * For i= idx_of_peak_Sensor-1 to i= idx_of_peak_Sensor+1
+ *         v += Sensor response(i)*i
+ *         w += Sensor response(i)
+ * POS=(Number_of_Positions_Wanted/(Number_of_Sensors_Used-1)) *(v/w)
+ */
+void ad714x_slider_cal_abs_pos(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx];
+	struct ad714x_slider_drv *sw = &ad714x->sw->slider[idx];
+
+	sw->abs_pos = ad714x_cal_abs_pos(ad714x, hw->start_stage, hw->end_stage,
+		sw->highest_stage, hw->max_coord);
+
+	dev_dbg(ad714x->dev, "slider %d absolute position:%d\n", idx,
+		sw->abs_pos);
+}
+
+/*
+ * To minimise the Impact of the noise on the algorithm, ADI developed a
+ * routine that filters the CDC results after they have been read by the
+ * host processor.
+ * The filter used is an Infinite Input Response(IIR) filter implemented
+ * in firmware and attenuates the noise on the CDC results after they've
+ * been read by the host processor.
+ * Filtered_CDC_result = (Filtered_CDC_result * (10 - Coefficient) +
+ * 				Latest_CDC_result * Coefficient)/10
+ */
+void ad714x_slider_cal_flt_pos(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_slider_drv *sw = &ad714x->sw->slider[idx];
+
+	sw->flt_pos = (sw->flt_pos * (10 - 4) +
+			sw->abs_pos * 4)/10;
+
+	dev_dbg(ad714x->dev, "slider %d filter position:%d\n", idx,
+		sw->flt_pos);
+}
+
+static void ad714x_slider_use_com_int(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx];
+	ad714x_use_com_int(ad714x, hw->start_stage, hw->end_stage);
+}
+
+static void ad714x_slider_use_thr_int(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx];
+	ad714x_use_thr_int(ad714x, hw->start_stage, hw->end_stage);
+}
+
+static void ad714x_slider_state_machine(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx];
+	struct ad714x_slider_drv *sw = &ad714x->sw->slider[idx];
+	unsigned short h_state, c_state;
+	unsigned short mask;
+
+	mask = ((1 << (hw->end_stage + 1)) - 1) - ((1 << hw->start_stage) - 1);
+
+	h_state = ad714x->h_state & mask;
+	c_state = ad714x->c_state & mask;
+
+	switch (sw->state) {
+	case IDLE:
+		if (h_state) {
+			sw->state = JITTER;
+			/* In End of Conversion interrupt mode, the AD714X
+			 * continuously generates hardware interrupts.
+			 */
+			ad714x_slider_use_com_int(ad714x, idx);
+			dev_dbg(ad714x->dev, "slider %d touched\n", idx);
+		}
+		break;
+	case JITTER:
+		if (c_state == mask) {
+			ad714x_slider_cal_sensor_val(ad714x, idx);
+			ad714x_slider_cal_highest_stage(ad714x, idx);
+			ad714x_slider_cal_abs_pos(ad714x, idx);
+			sw->flt_pos = sw->abs_pos;
+			sw->state = ACTIVE;
+		}
+		break;
+	case ACTIVE:
+		if (c_state == mask) {
+			if (h_state) {
+				ad714x_slider_cal_sensor_val(ad714x, idx);
+				ad714x_slider_cal_highest_stage(ad714x, idx);
+				ad714x_slider_cal_abs_pos(ad714x, idx);
+				ad714x_slider_cal_flt_pos(ad714x, idx);
+
+				input_report_abs(sw->input, ABS_X, sw->flt_pos);
+				input_report_key(sw->input, BTN_TOUCH, 1);
+			} else {
+				/* When the user lifts off the sensor, configure
+				 * the AD714X back to threshold interrupt mode.
+				 */
+				ad714x_slider_use_thr_int(ad714x, idx);
+				sw->state = IDLE;
+				input_report_key(sw->input, BTN_TOUCH, 0);
+				dev_dbg(ad714x->dev, "slider %d released\n",
+					idx);
+			}
+			input_sync(sw->input);
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+/* When the scroll wheel is activated, we compute the absolute position based
+ * on the sensor values. To calculate the position, we first determine the
+ * sensor that has the greatest response among the 8 sensors that constitutes
+ * the scrollwheel. Then we determined the 2 sensors on either sides of the
+ * sensor with the highest response and we apply weights to these sensors.
+ */
+static void ad714x_wheel_cal_highest_stage(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
+	struct ad714x_wheel_drv *sw = &ad714x->sw->wheel[idx];
+
+	sw->pre_highest_stage = sw->highest_stage;
+	sw->highest_stage = ad714x_cal_highest_stage(ad714x, hw->start_stage,
+			hw->end_stage);
+
+	dev_dbg(ad714x->dev, "wheel %d highest_stage:%d\n", idx,
+		sw->highest_stage);
+}
+
+static void ad714x_wheel_cal_sensor_val(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
+	int i;
+
+	for (i = hw->start_stage; i <= hw->end_stage; i++) {
+		ad714x->read(ad714x->dev, CDC_RESULT_S0 + i,
+			&ad714x->adc_reg[i]);
+		ad714x->read(ad714x->dev,
+				STAGE0_AMBIENT + i * PER_STAGE_REG_NUM,
+				&ad714x->amb_reg[i]);
+		if (ad714x->adc_reg[i] > ad714x->amb_reg[i])
+			ad714x->sensor_val[i] = ad714x->adc_reg[i] -
+				ad714x->amb_reg[i];
+		else
+			ad714x->sensor_val[i] = 0;
+	}
+}
+
+/* When the scroll wheel is activated, we compute the absolute position based
+ * on the sensor values. To calculate the position, we first determine the
+ * sensor that has the greatest response among the 8 sensors that constitutes
+ * the scrollwheel. Then we determined the 2 sensors on either sides of the
+ * sensor with the highest response and we apply weights to these sensors. The
+ * result of this computation gives us the mean value which defined by the
+ * following formula:
+ * For i= second_before_highest_stage to i= second_after_highest_stage
+ *         v += Sensor response(i)*WEIGHT*(i+3)
+ *         w += Sensor response(i)
+ * Mean_Value=v/w
+ * pos_on_scrollwheel = (Mean_Value - position_offset) / position_ratio
+ *
+ */
+
+#define WEIGHT_FACTOR 30
+/* This constant prevents the "PositionOffset" from reaching a big value */
+#define OFFSET_POSITION_CLAMP	120
+static void ad714x_wheel_cal_abs_pos(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
+	struct ad714x_wheel_drv *sw = &ad714x->sw->wheel[idx];
+	int stage_num = hw->end_stage - hw->start_stage + 1;
+	int second_before, first_before, highest, first_after, second_after;
+	int a_param, b_param;
+
+	/* Calculate Mean value */
+
+	second_before = (sw->highest_stage + stage_num - 2) % stage_num;
+	first_before = (sw->highest_stage + stage_num - 1) % stage_num;
+	highest = sw->highest_stage;
+	first_after = (sw->highest_stage + stage_num + 1) % stage_num;
+	second_after = (sw->highest_stage + stage_num + 2) % stage_num;
+
+	if (((sw->highest_stage - hw->start_stage) > 1) &&
+			((hw->end_stage - sw->highest_stage) > 1)) {
+		a_param = ad714x->sensor_val[second_before] *
+			(second_before - hw->start_stage + 3) +
+			ad714x->sensor_val[first_before] *
+			(second_before - hw->start_stage + 3) +
+			ad714x->sensor_val[highest] *
+			(second_before - hw->start_stage + 3) +
+			ad714x->sensor_val[first_after] *
+			(first_after - hw->start_stage + 3) +
+			ad714x->sensor_val[second_after] *
+			(second_after - hw->start_stage + 3);
+	} else {
+		a_param = ad714x->sensor_val[second_before] *
+			(second_before - hw->start_stage + 1) +
+			ad714x->sensor_val[first_before] *
+			(second_before - hw->start_stage + 2) +
+			ad714x->sensor_val[highest] *
+			(second_before - hw->start_stage + 3) +
+			ad714x->sensor_val[first_after] *
+			(first_after - hw->start_stage + 4) +
+			ad714x->sensor_val[second_after] *
+			(second_after - hw->start_stage + 5);
+	}
+	a_param *= WEIGHT_FACTOR;
+
+	b_param = ad714x->sensor_val[second_before] +
+		ad714x->sensor_val[first_before] +
+		ad714x->sensor_val[highest] +
+		ad714x->sensor_val[first_after] +
+		ad714x->sensor_val[second_after];
+
+	sw->pre_mean_value = sw->mean_value;
+	sw->mean_value = a_param / b_param;
+
+	/* Calculate the offset */
+
+	if ((sw->pre_highest_stage == hw->end_stage) &&
+			(sw->highest_stage == hw->start_stage))
+		sw->pos_offset = sw->mean_value;
+	else if ((sw->pre_highest_stage == hw->start_stage) &&
+			(sw->highest_stage == hw->end_stage))
+		sw->pos_offset = sw->pre_mean_value;
+	if (sw->pos_offset > OFFSET_POSITION_CLAMP)
+		sw->pos_offset = OFFSET_POSITION_CLAMP;
+
+	/* Calculate the mean value without the offset */
+
+	sw->pre_mean_value_no_offset = sw->mean_value_no_offset;
+	sw->mean_value_no_offset = sw->mean_value - sw->pos_offset;
+	if (sw->mean_value_no_offset < 0)
+		sw->mean_value_no_offset = 0;
+
+	/* Calculate ratio to scale down to NUMBER_OF_WANTED_POSITIONS */
+
+	if ((sw->pre_highest_stage == hw->end_stage) &&
+			(sw->highest_stage == hw->start_stage))
+		sw->pos_ratio = (sw->pre_mean_value_no_offset * 100) /
+			hw->max_coord;
+	else if ((sw->pre_highest_stage == hw->start_stage) &&
+			(sw->highest_stage == hw->end_stage))
+		sw->pos_ratio = (sw->mean_value_no_offset * 100) /
+			hw->max_coord;
+	sw->abs_pos = (sw->mean_value_no_offset * 100) / sw->pos_ratio;
+	if (sw->abs_pos > hw->max_coord)
+		sw->abs_pos = hw->max_coord;
+}
+
+static void ad714x_wheel_cal_flt_pos(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
+	struct ad714x_wheel_drv *sw = &ad714x->sw->wheel[idx];
+	if (((sw->pre_highest_stage == hw->end_stage) &&
+				(sw->highest_stage == hw->start_stage)) ||
+			((sw->pre_highest_stage == hw->start_stage) &&
+			 (sw->highest_stage == hw->end_stage)))
+		sw->flt_pos = sw->abs_pos;
+	else
+		sw->flt_pos = ((sw->flt_pos * 30) + (sw->abs_pos * 71)) / 100;
+
+	if (sw->flt_pos > hw->max_coord)
+		sw->flt_pos = hw->max_coord;
+}
+
+static void ad714x_wheel_use_com_int(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
+	ad714x_use_com_int(ad714x, hw->start_stage, hw->end_stage);
+}
+
+static void ad714x_wheel_use_thr_int(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
+	ad714x_use_thr_int(ad714x, hw->start_stage, hw->end_stage);
+}
+
+static void ad714x_wheel_state_machine(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
+	struct ad714x_wheel_drv *sw = &ad714x->sw->wheel[idx];
+	unsigned short h_state, c_state;
+	unsigned short mask;
+
+	mask = ((1 << (hw->end_stage + 1)) - 1) - ((1 << hw->start_stage) - 1);
+
+	h_state = ad714x->h_state & mask;
+	c_state = ad714x->c_state & mask;
+
+	switch (sw->state) {
+	case IDLE:
+		if (h_state) {
+			sw->state = JITTER;
+			/* In End of Conversion interrupt mode, the AD714X
+			 * continuously generates hardware interrupts.
+			 */
+			ad714x_wheel_use_com_int(ad714x, idx);
+			dev_dbg(ad714x->dev, "wheel %d touched\n", idx);
+		}
+		break;
+	case JITTER:
+		if (c_state == mask)	{
+			ad714x_wheel_cal_sensor_val(ad714x, idx);
+			ad714x_wheel_cal_highest_stage(ad714x, idx);
+			ad714x_wheel_cal_abs_pos(ad714x, idx);
+			sw->flt_pos = sw->abs_pos;
+			sw->state = ACTIVE;
+		}
+		break;
+	case ACTIVE:
+		if (c_state == mask) {
+			if (h_state) {
+				ad714x_wheel_cal_sensor_val(ad714x, idx);
+				ad714x_wheel_cal_highest_stage(ad714x, idx);
+				ad714x_wheel_cal_abs_pos(ad714x, idx);
+				ad714x_wheel_cal_flt_pos(ad714x, idx);
+
+				input_report_abs(sw->input, ABS_WHEEL,
+					sw->abs_pos);
+				input_report_key(sw->input, BTN_TOUCH, 1);
+			} else {
+				/* When the user lifts off the sensor, configure
+				 * the AD714X back to threshold interrupt mode.
+				 */
+				ad714x_wheel_use_thr_int(ad714x, idx);
+				sw->state = IDLE;
+				input_report_key(sw->input, BTN_TOUCH, 0);
+
+				dev_dbg(ad714x->dev, "wheel %d released\n",
+					idx);
+			}
+			input_sync(sw->input);
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+static void touchpad_cal_sensor_val(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
+	int i;
+
+	for (i = hw->x_start_stage; i <= hw->x_end_stage; i++) {
+		ad714x->read(ad714x->dev, CDC_RESULT_S0 + i,
+				&ad714x->adc_reg[i]);
+		ad714x->read(ad714x->dev,
+				STAGE0_AMBIENT + i * PER_STAGE_REG_NUM,
+				&ad714x->amb_reg[i]);
+		if (ad714x->adc_reg[i] > ad714x->amb_reg[i])
+			ad714x->sensor_val[i] = ad714x->adc_reg[i] -
+				ad714x->amb_reg[i];
+		else
+			ad714x->sensor_val[i] = 0;
+	}
+}
+
+static void touchpad_cal_highest_stage(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
+	struct ad714x_touchpad_drv *sw = &ad714x->sw->touchpad[idx];
+
+	sw->x_highest_stage = ad714x_cal_highest_stage(ad714x,
+		hw->x_start_stage, hw->x_end_stage);
+	sw->y_highest_stage = ad714x_cal_highest_stage(ad714x,
+		hw->y_start_stage, hw->y_end_stage);
+
+	dev_dbg(ad714x->dev,
+		"touchpad %d x_highest_stage:%d, y_highest_stage:%d\n",
+		idx, sw->x_highest_stage, sw->y_highest_stage);
+}
+
+/* If 2 fingers are touching the sensor then 2 peaks can be observed in the
+ * distribution.
+ * The arithmetic doesn't support to get absolute coordinates for multi-touch
+ * yet.
+ */
+static int touchpad_check_second_peak(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
+	struct ad714x_touchpad_drv *sw = &ad714x->sw->touchpad[idx];
+	int i;
+
+	for (i = hw->x_start_stage; i < sw->x_highest_stage; i++) {
+		if ((ad714x->sensor_val[i] - ad714x->sensor_val[i + 1])
+			> (ad714x->sensor_val[i + 1] / 10))
+			return 1;
+	}
+
+	for (i = sw->x_highest_stage; i < hw->x_end_stage; i++) {
+		if ((ad714x->sensor_val[i + 1] - ad714x->sensor_val[i])
+			> (ad714x->sensor_val[i] / 10))
+			return 1;
+	}
+
+	for (i = hw->y_start_stage; i < sw->y_highest_stage; i++) {
+		if ((ad714x->sensor_val[i] - ad714x->sensor_val[i + 1])
+			> (ad714x->sensor_val[i + 1] / 10))
+			return 1;
+	}
+
+	for (i = sw->y_highest_stage; i < hw->y_end_stage; i++) {
+		if ((ad714x->sensor_val[i + 1] - ad714x->sensor_val[i])
+			> (ad714x->sensor_val[i] / 10))
+			return 1;
+	}
+
+	return 0;
+}
+
+/* If only one finger is used to activate the touch pad then only 1 peak will be
+ * registered in the distribution. This peak and the 2 adjacent sensors will be
+ * used in the calculation of the absolute position. This will prevent hand
+ * shadows to affect the absolute position calculation.
+ */
+static void touchpad_cal_abs_pos(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
+	struct ad714x_touchpad_drv *sw = &ad714x->sw->touchpad[idx];
+
+	sw->x_abs_pos = ad714x_cal_abs_pos(ad714x, hw->x_start_stage,
+			hw->x_end_stage, sw->x_highest_stage, hw->x_max_coord);
+	sw->y_abs_pos = ad714x_cal_abs_pos(ad714x, hw->y_start_stage,
+			hw->y_end_stage, sw->y_highest_stage, hw->y_max_coord);
+
+	dev_dbg(ad714x->dev, "touchpad %d absolute position:(%d, %d)\n", idx,
+			sw->x_abs_pos, sw->y_abs_pos);
+}
+
+static void touchpad_cal_flt_pos(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_touchpad_drv *sw = &ad714x->sw->touchpad[idx];
+
+	sw->x_flt_pos = (sw->x_flt_pos * (10 - 4) +
+			sw->x_abs_pos * 4)/10;
+	sw->y_flt_pos = (sw->y_flt_pos * (10 - 4) +
+			sw->y_abs_pos * 4)/10;
+
+	dev_dbg(ad714x->dev, "touchpad %d filter position:(%d, %d)\n",
+			idx, sw->x_flt_pos, sw->y_flt_pos);
+}
+
+/* To prevent distortion from showing in the absolute position, it is
+ * necessary to detect the end points. When endpoints are detected, the
+ * driver stops updating the status variables with absolute positions.
+ * End points are detected on the 4 edges of the touchpad sensor. The
+ * method to detect them is the same for all 4.
+ * To detect the end points, the firmware computes the difference in
+ * percent between the sensor on the edge and the adjacent one. The
+ * difference is calculated in percent in order to make the end point
+ * detection independent of the pressure.
+ */
+
+#define LEFT_END_POINT_DETECTION_LEVEL                  550
+#define RIGHT_END_POINT_DETECTION_LEVEL                 750
+#define LEFT_RIGHT_END_POINT_DEAVTIVALION_LEVEL         850
+#define TOP_END_POINT_DETECTION_LEVEL                   550
+#define BOTTOM_END_POINT_DETECTION_LEVEL                950
+#define TOP_BOTTOM_END_POINT_DEAVTIVALION_LEVEL         700
+static int touchpad_check_endpoint(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
+	struct ad714x_touchpad_drv *sw  = &ad714x->sw->touchpad[idx];
+	int percent_sensor_diff;
+
+	/* left endpoint detect */
+	percent_sensor_diff = (ad714x->sensor_val[hw->x_start_stage] -
+			ad714x->sensor_val[hw->x_start_stage + 1]) * 100 /
+			ad714x->sensor_val[hw->x_start_stage + 1];
+	if (!sw->left_ep) {
+		if (percent_sensor_diff >= LEFT_END_POINT_DETECTION_LEVEL)  {
+			sw->left_ep = 1;
+			sw->left_ep_val =
+				ad714x->sensor_val[hw->x_start_stage + 1];
+		}
+	} else {
+		if ((percent_sensor_diff < LEFT_END_POINT_DETECTION_LEVEL) &&
+		(ad714x->sensor_val[hw->x_start_stage + 1] >
+		LEFT_RIGHT_END_POINT_DEAVTIVALION_LEVEL + sw->left_ep_val))
+			sw->left_ep = 0;
+	}
+
+	/* right endpoint detect */
+	percent_sensor_diff = (ad714x->sensor_val[hw->x_end_stage] -
+			ad714x->sensor_val[hw->x_end_stage - 1]) * 100 /
+			ad714x->sensor_val[hw->x_end_stage - 1];
+	if (!sw->right_ep) {
+		if (percent_sensor_diff >= RIGHT_END_POINT_DETECTION_LEVEL)  {
+			sw->right_ep = 1;
+			sw->right_ep_val =
+				ad714x->sensor_val[hw->x_end_stage - 1];
+		}
+	} else {
+		if ((percent_sensor_diff < RIGHT_END_POINT_DETECTION_LEVEL) &&
+		(ad714x->sensor_val[hw->x_end_stage - 1] >
+		LEFT_RIGHT_END_POINT_DEAVTIVALION_LEVEL + sw->right_ep_val))
+			sw->right_ep = 0;
+	}
+
+	/* top endpoint detect */
+	percent_sensor_diff = (ad714x->sensor_val[hw->y_start_stage] -
+			ad714x->sensor_val[hw->y_start_stage + 1]) * 100 /
+			ad714x->sensor_val[hw->y_start_stage + 1];
+	if (!sw->top_ep) {
+		if (percent_sensor_diff >= TOP_END_POINT_DETECTION_LEVEL)  {
+			sw->top_ep = 1;
+			sw->top_ep_val =
+				ad714x->sensor_val[hw->y_start_stage + 1];
+		}
+	} else {
+		if ((percent_sensor_diff < TOP_END_POINT_DETECTION_LEVEL) &&
+		(ad714x->sensor_val[hw->y_start_stage + 1] >
+		TOP_BOTTOM_END_POINT_DEAVTIVALION_LEVEL + sw->top_ep_val))
+			sw->top_ep = 0;
+	}
+
+	/* bottom endpoint detect */
+	percent_sensor_diff = (ad714x->sensor_val[hw->y_end_stage] -
+		ad714x->sensor_val[hw->y_end_stage - 1]) * 100 /
+		ad714x->sensor_val[hw->y_end_stage - 1];
+	if (!sw->bottom_ep) {
+		if (percent_sensor_diff >= BOTTOM_END_POINT_DETECTION_LEVEL)  {
+			sw->bottom_ep = 1;
+			sw->bottom_ep_val =
+				ad714x->sensor_val[hw->y_end_stage - 1];
+		}
+	} else {
+		if ((percent_sensor_diff < BOTTOM_END_POINT_DETECTION_LEVEL) &&
+		(ad714x->sensor_val[hw->y_end_stage - 1] >
+		 TOP_BOTTOM_END_POINT_DEAVTIVALION_LEVEL + sw->bottom_ep_val))
+			sw->bottom_ep = 0;
+	}
+
+	return sw->left_ep || sw->right_ep || sw->top_ep || sw->bottom_ep;
+}
+
+static void touchpad_use_com_int(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
+	ad714x_use_com_int(ad714x, hw->x_start_stage, hw->x_end_stage);
+}
+
+static void touchpad_use_thr_int(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
+	ad714x_use_thr_int(ad714x, hw->x_start_stage, hw->x_end_stage);
+	ad714x_use_thr_int(ad714x, hw->y_start_stage, hw->y_end_stage);
+}
+
+static void ad714x_touchpad_state_machine(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
+	struct ad714x_touchpad_drv *sw = &ad714x->sw->touchpad[idx];
+	unsigned short h_state, c_state;
+	unsigned short mask;
+
+	mask = (((1 << (hw->x_end_stage + 1)) - 1) -
+		((1 << hw->x_start_stage) - 1)) +
+		(((1 << (hw->y_end_stage + 1)) - 1) -
+		((1 << hw->y_start_stage) - 1));
+
+	h_state = ad714x->h_state & mask;
+	c_state = ad714x->c_state & mask;
+
+	switch (sw->state) {
+	case IDLE:
+		if (h_state) {
+			sw->state = JITTER;
+			/* In End of Conversion interrupt mode, the AD714X
+			 * continuously generates hardware interrupts.
+			 */
+			touchpad_use_com_int(ad714x, idx);
+			dev_dbg(ad714x->dev, "touchpad %d touched\n", idx);
+		}
+		break;
+	case JITTER:
+		if (c_state == mask) {
+			touchpad_cal_sensor_val(ad714x, idx);
+			touchpad_cal_highest_stage(ad714x, idx);
+			if ((!touchpad_check_second_peak(ad714x, idx)) &&
+				(!touchpad_check_endpoint(ad714x, idx))) {
+				dev_dbg(ad714x->dev,
+					"touchpad%d, 2 fingers or endpoint\n",
+					idx);
+				touchpad_cal_abs_pos(ad714x, idx);
+				sw->x_flt_pos = sw->x_abs_pos;
+				sw->y_flt_pos = sw->y_abs_pos;
+				sw->state = ACTIVE;
+			}
+		}
+		break;
+	case ACTIVE:
+		if (c_state == mask) {
+			if (h_state) {
+				touchpad_cal_sensor_val(ad714x, idx);
+				touchpad_cal_highest_stage(ad714x, idx);
+				if ((!touchpad_check_second_peak(ad714x, idx))
+				  && (!touchpad_check_endpoint(ad714x, idx))) {
+					touchpad_cal_abs_pos(ad714x, idx);
+					touchpad_cal_flt_pos(ad714x, idx);
+					input_report_abs(sw->input, ABS_X,
+						sw->x_flt_pos);
+					input_report_abs(sw->input, ABS_Y,
+						sw->y_flt_pos);
+					input_report_key(sw->input, BTN_TOUCH,
+						1);
+				}
+			} else {
+				/* When the user lifts off the sensor, configure
+				 * the AD714X back to threshold interrupt mode.
+				 */
+				touchpad_use_thr_int(ad714x, idx);
+				sw->state = IDLE;
+				input_report_key(sw->input, BTN_TOUCH, 0);
+				dev_dbg(ad714x->dev, "touchpad %d released\n",
+					idx);
+			}
+			input_sync(sw->input);
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+static int ad714x_hw_detect(struct ad714x_chip *ad714x)
+{
+	unsigned short data;
+
+	ad714x->read(ad714x->dev, AD714X_PARTID_REG, &data);
+	switch (data & 0xFFF0) {
+	case AD7147_PARTID:
+		ad714x->product = 0x7147;
+		ad714x->version = data & 0xF;
+		dev_info(ad714x->dev, "Found AD7147 captouch, rev:%d\n",
+				ad714x->version);
+		return 0;
+	case AD7142_PARTID:
+		ad714x->product = 0x7142;
+		ad714x->version = data & 0xF;
+		dev_info(ad714x->dev, "Found AD7142 captouch, rev:%d\n",
+				ad714x->version);
+		return 0;
+	default:
+		dev_err(ad714x->dev,
+			"Fail to detect AD714X captouch, read ID is %04x\n",
+			data);
+		return -ENODEV;
+	}
+}
+
+static void __devinit ad714x_hw_init(struct ad714x_chip *ad714x)
+{
+	int i, j;
+	unsigned short reg_base;
+	unsigned short data;
+
+	/* configuration CDC and interrupts*/
+
+	for (i = 0; i < STAGE_NUM; i++) {
+		reg_base = AD714X_STAGECFG_REG + i * STAGE_CFGREG_NUM;
+		for (j = 0; j < STAGE_CFGREG_NUM; j++)
+			ad714x->write(ad714x->dev, reg_base + j,
+					ad714x->hw->stage_cfg_reg[i][j]);
+	}
+
+	for (i = 0; i < SYS_CFGREG_NUM; i++)
+		ad714x->write(ad714x->dev, AD714X_SYSCFG_REG + i,
+			ad714x->hw->sys_cfg_reg[i]);
+	for (i = 0; i < SYS_CFGREG_NUM; i++)
+		ad714x->read(ad714x->dev, AD714X_SYSCFG_REG + i,
+			&data);
+
+	ad714x->write(ad714x->dev, AD714X_STG_CAL_EN_REG, 0xFFF);
+
+	/* clear all interrupts */
+	ad714x->read(ad714x->dev, STG_LOW_INT_STA_REG, &data);
+	ad714x->read(ad714x->dev, STG_HIGH_INT_STA_REG, &data);
+	ad714x->read(ad714x->dev, STG_COM_INT_STA_REG, &data);
+}
+
+static irqreturn_t ad714x_interrupt_thread(int irq, void *data)
+{
+	struct ad714x_chip *ad714x = data;
+	int i;
+
+	mutex_lock(&ad714x->mutex);
+
+	ad714x->read(ad714x->dev, STG_LOW_INT_STA_REG, &ad714x->l_state);
+	ad714x->read(ad714x->dev, STG_HIGH_INT_STA_REG, &ad714x->h_state);
+	ad714x->read(ad714x->dev, STG_COM_INT_STA_REG, &ad714x->c_state);
+
+	for (i = 0; i < ad714x->hw->button_num; i++)
+		ad714x_button_state_machine(ad714x, i);
+	for (i = 0; i < ad714x->hw->slider_num; i++)
+		ad714x_slider_state_machine(ad714x, i);
+	for (i = 0; i < ad714x->hw->wheel_num; i++)
+		ad714x_wheel_state_machine(ad714x, i);
+	for (i = 0; i < ad714x->hw->touchpad_num; i++)
+		ad714x_touchpad_state_machine(ad714x, i);
+
+	mutex_unlock(&ad714x->mutex);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t ad714x_interrupt(int irq, void *data)
+{
+	return IRQ_WAKE_THREAD;
+}
+
+#define MAX_DEVICE_NUM 8
+static int __devinit ad714x_probe(struct ad714x_chip *ad714x, u16 bus_type)
+{
+	int ret = 0;
+	struct input_dev *input[MAX_DEVICE_NUM];
+
+	struct ad714x_driver_data *drv_data = NULL;
+
+	struct ad714x_button_plat *bt_plat   = ad714x->hw->button;
+	struct ad714x_slider_plat *sd_plat   = ad714x->hw->slider;
+	struct ad714x_wheel_plat *wl_plat    = ad714x->hw->wheel;
+	struct ad714x_touchpad_plat *tp_plat = ad714x->hw->touchpad;
+
+	struct ad714x_button_drv *bt_drv   = NULL;
+	struct ad714x_slider_drv *sd_drv   = NULL;
+	struct ad714x_wheel_drv *wl_drv    = NULL;
+	struct ad714x_touchpad_drv *tp_drv = NULL;
+
+	int alloc_idx = 0, reg_idx = 0;
+	int i;
+
+	ret = ad714x_hw_detect(ad714x);
+	if (ret)
+		goto det_err;
+
+	/* initilize and request sw/hw resources */
+
+	ad714x_hw_init(ad714x);
+	mutex_init(&ad714x->mutex);
+
+	if (ad714x->irq > 0) {
+		ret = request_threaded_irq(ad714x->irq, ad714x_interrupt,
+				ad714x_interrupt_thread, IRQF_TRIGGER_FALLING,
+				"ad714x_captouch", ad714x);
+		if (ret) {
+			dev_err(ad714x->dev, "Can't allocate irq %d\n",
+					ad714x->irq);
+			goto fail_irq;
+		}
+	} else
+		dev_err(ad714x->dev, "IRQ not configured!\n");
+
+	/*
+	 * Allocate and register AD714X input device
+	 */
+
+	drv_data = kzalloc(sizeof(struct ad714x_driver_data), GFP_KERNEL);
+	if (!drv_data) {
+		dev_err(ad714x->dev,
+			"Can't allocate memory for ad714x driver info\n");
+		ret = -ENOMEM;
+		goto fail_alloc_reg;
+	}
+	ad714x->sw = drv_data;
+
+	/* a slider uses one input_dev instance */
+	if (ad714x->hw->slider_num > 0) {
+		sd_drv = kzalloc(sizeof(struct ad714x_slider_drv) *
+				ad714x->hw->slider_num, GFP_KERNEL);
+		if (!sd_drv) {
+			dev_err(ad714x->dev,
+				"Can't allocate memory for slider info\n");
+			ret = -ENOMEM;
+			goto fail_alloc_reg;
+		}
+
+		for (i = 0; i < ad714x->hw->slider_num; i++) {
+			input[alloc_idx] = input_allocate_device();
+			if (!input[alloc_idx]) {
+				dev_err(ad714x->dev,
+				"Can't allocate input device %d\n", alloc_idx);
+				ret = -ENOMEM;
+				goto fail_alloc_reg;
+			}
+			alloc_idx++;
+
+			__set_bit(EV_ABS, input[alloc_idx-1]->evbit);
+			__set_bit(EV_KEY, input[alloc_idx-1]->evbit);
+			__set_bit(ABS_X, input[alloc_idx-1]->absbit);
+			__set_bit(BTN_TOUCH, input[alloc_idx-1]->keybit);
+			input_set_abs_params(input[alloc_idx-1], ABS_X, 0,
+					sd_plat->max_coord, 0, 0);
+
+			input[alloc_idx-1]->id.bustype = bus_type;
+			input[alloc_idx-1]->id.product = ad714x->product;
+			input[alloc_idx-1]->id.version = ad714x->version;
+
+			ret = input_register_device(input[reg_idx]);
+			if (ret) {
+				dev_err(ad714x->dev,
+				"Failed to register AD714X input device!\n");
+				goto fail_alloc_reg;
+			}
+			reg_idx++;
+
+			sd_drv[i].input = input[alloc_idx-1];
+			ad714x->sw->slider = sd_drv;
+		}
+	}
+
+	/* a wheel uses one input_dev instance */
+	if (ad714x->hw->wheel_num > 0) {
+		wl_drv = kzalloc(sizeof(struct ad714x_wheel_drv) *
+				ad714x->hw->wheel_num, GFP_KERNEL);
+		if (!wl_drv) {
+			dev_err(ad714x->dev,
+				"Can't allocate memory for wheel info\n");
+			ret = -ENOMEM;
+			goto fail_alloc_reg;
+		}
+
+		for (i = 0; i < ad714x->hw->wheel_num; i++) {
+			input[alloc_idx] = input_allocate_device();
+			if (!input[alloc_idx]) {
+				dev_err(ad714x->dev,
+				"Can't allocate input device %d\n", alloc_idx);
+				ret = -ENOMEM;
+				goto fail_alloc_reg;
+			}
+			alloc_idx++;
+
+			__set_bit(EV_KEY, input[alloc_idx-1]->evbit);
+			__set_bit(EV_ABS, input[alloc_idx-1]->evbit);
+			__set_bit(ABS_WHEEL, input[alloc_idx-1]->absbit);
+			__set_bit(BTN_TOUCH, input[alloc_idx-1]->keybit);
+			input_set_abs_params(input[alloc_idx-1], ABS_WHEEL, 0,
+					wl_plat->max_coord, 0, 0);
+
+			input[alloc_idx-1]->id.bustype = bus_type;
+			input[alloc_idx-1]->id.product = ad714x->product;
+			input[alloc_idx-1]->id.version = ad714x->version;
+
+			ret = input_register_device(input[reg_idx]);
+			if (ret) {
+				dev_err(ad714x->dev,
+				"Failed to register AD714X input device!\n");
+				goto fail_alloc_reg;
+			}
+			reg_idx++;
+
+			wl_drv[i].input = input[alloc_idx-1];
+			ad714x->sw->wheel = wl_drv;
+		}
+	}
+
+	/* a touchpad uses one input_dev instance */
+	if (ad714x->hw->touchpad_num > 0) {
+		tp_drv = kzalloc(sizeof(struct ad714x_touchpad_drv) *
+				ad714x->hw->touchpad_num, GFP_KERNEL);
+		if (!tp_drv) {
+			dev_err(ad714x->dev,
+				"Can't allocate memory for touchpad info\n");
+			ret = -ENOMEM;
+			goto fail_alloc_reg;
+		}
+
+		for (i = 0; i < ad714x->hw->touchpad_num; i++) {
+			input[alloc_idx] = input_allocate_device();
+			if (!input[alloc_idx]) {
+				dev_err(ad714x->dev,
+					"Can't allocate input device %d\n",
+					alloc_idx);
+				ret = -ENOMEM;
+				goto fail_alloc_reg;
+			}
+			alloc_idx++;
+
+			__set_bit(EV_ABS, input[alloc_idx-1]->evbit);
+			__set_bit(EV_KEY, input[alloc_idx-1]->evbit);
+			__set_bit(ABS_X, input[alloc_idx-1]->absbit);
+			__set_bit(ABS_Y, input[alloc_idx-1]->absbit);
+			__set_bit(BTN_TOUCH, input[alloc_idx-1]->keybit);
+			input_set_abs_params(input[alloc_idx-1], ABS_X, 0,
+					tp_plat->x_max_coord, 0, 0);
+			input_set_abs_params(input[alloc_idx-1], ABS_Y, 0,
+					tp_plat->y_max_coord, 0, 0);
+
+			input[alloc_idx-1]->id.bustype = bus_type;
+			input[alloc_idx-1]->id.product = ad714x->product;
+			input[alloc_idx-1]->id.version = ad714x->version;
+
+			ret = input_register_device(input[reg_idx]);
+			if (ret) {
+				dev_err(ad714x->dev,
+				"Failed to register AD714X input device!\n");
+				goto fail_alloc_reg;
+			}
+			reg_idx++;
+
+			tp_drv[i].input = input[alloc_idx-1];
+			ad714x->sw->touchpad = tp_drv;
+		}
+	}
+
+	/* all buttons use one input node */
+	if (ad714x->hw->button_num > 0) {
+		bt_drv = kzalloc(sizeof(struct ad714x_button_drv) *
+				ad714x->hw->button_num, GFP_KERNEL);
+		if (!bt_drv) {
+			dev_err(ad714x->dev,
+				"Can't allocate memory for button info\n");
+			ret = -ENOMEM;
+			goto fail_alloc_reg;
+		}
+
+		input[alloc_idx] = input_allocate_device();
+		if (!input[alloc_idx]) {
+			dev_err(ad714x->dev,
+					"Can't allocate input device %d\n",
+					alloc_idx);
+			ret = -ENOMEM;
+			goto fail_alloc_reg;
+		}
+		alloc_idx++;
+
+		__set_bit(EV_KEY, input[alloc_idx-1]->evbit);
+		for (i = 0; i < ad714x->hw->button_num; i++) {
+			__set_bit(bt_plat[i].keycode,
+				input[alloc_idx-1]->keybit);
+		}
+
+		input[alloc_idx-1]->id.bustype = bus_type;
+		input[alloc_idx-1]->id.product = ad714x->product;
+		input[alloc_idx-1]->id.version = ad714x->version;
+
+		ret = input_register_device(input[reg_idx]);
+		if (ret) {
+			dev_err(ad714x->dev,
+				"Failed to register AD714X input device!\n");
+			goto fail_alloc_reg;
+		}
+		reg_idx++;
+
+		for (i = 0; i < ad714x->hw->button_num; i++)
+			bt_drv[i].input = input[alloc_idx-1];
+		ad714x->sw->button = bt_drv;
+	}
+
+
+	return 0;
+
+fail_alloc_reg:
+	for (i = 0; i < reg_idx; i++)
+		input_unregister_device(input[i]);
+	for (i = reg_idx; i < alloc_idx; i++)
+		input_free_device(input[i]);
+
+	kfree(bt_drv);
+	kfree(sd_drv);
+	kfree(wl_drv);
+	kfree(tp_drv);
+	kfree(drv_data);
+
+	free_irq(ad714x->irq, ad714x);
+fail_irq:
+det_err:
+	return ret;
+}
+
+static int __devexit ad714x_remove(struct ad714x_chip *ad714x)
+{
+	int i;
+
+	struct ad714x_driver_data *drv_data = ad714x->sw;
+	struct ad714x_button_drv *bt_drv   = ad714x->sw->button;
+	struct ad714x_slider_drv *sd_drv   = ad714x->sw->slider;
+	struct ad714x_wheel_drv *wl_drv    = ad714x->sw->wheel;
+	struct ad714x_touchpad_drv *tp_drv = ad714x->sw->touchpad;
+
+
+	/* unregister and free all input devices */
+
+	for (i = 0; i < ad714x->hw->slider_num; i++)
+		input_unregister_device(ad714x->sw->slider[i].input);
+
+	for (i = 0; i < ad714x->hw->wheel_num; i++)
+		input_unregister_device(ad714x->sw->wheel[i].input);
+
+	for (i = 0; i < ad714x->hw->touchpad_num; i++)
+		input_unregister_device(ad714x->sw->touchpad[i].input);
+
+	input_unregister_device(ad714x->sw->button[0].input);
+
+	/* free all memories for software flow */
+
+	kfree(bt_drv);
+	kfree(sd_drv);
+	kfree(wl_drv);
+	kfree(tp_drv);
+	kfree(drv_data);
+
+	/* free irq hardware resource */
+
+	free_irq(ad714x->irq, ad714x);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int ad714x_disable(struct ad714x_chip *ad714x)
+{
+	unsigned short data;
+
+	dev_dbg(ad714x->dev, "%s enter\n", __func__);
+
+	mutex_lock(&ad714x->mutex);
+
+	data = ad714x->hw->sys_cfg_reg[AD714X_PWR_CTRL] | 0x3;
+	ad714x->write(ad714x->dev, AD714X_PWR_CTRL, data);
+
+	mutex_unlock(&ad714x->mutex);
+
+	return 0;
+}
+
+static int ad714x_enable(struct ad714x_chip *ad714x)
+{
+	unsigned short data;
+
+	dev_dbg(ad714x->dev, "%s enter\n", __func__);
+
+	mutex_lock(&ad714x->mutex);
+
+	/* resume to non-shutdown mode */
+
+	ad714x->write(ad714x->dev, AD714X_PWR_CTRL,
+			ad714x->hw->sys_cfg_reg[AD714X_PWR_CTRL]);
+
+	/* make sure the interrupt output line is not low level after resume,
+	 * otherwise we will get no chance to enter falling-edge irq again
+	 */
+
+	ad714x->read(ad714x->dev, STG_LOW_INT_STA_REG, &data);
+	ad714x->read(ad714x->dev, STG_HIGH_INT_STA_REG, &data);
+	ad714x->read(ad714x->dev, STG_COM_INT_STA_REG, &data);
+
+	mutex_unlock(&ad714x->mutex);
+
+	return 0;
+}
+
+#if defined(CONFIG_SPI) || defined(CONFIG_SPI_MODULE)
+static int ad714x_spi_suspend(struct spi_device *spi, pm_message_t message)
+{
+	struct ad714x_chip *ad714x = spi_get_drvdata(spi);
+
+	ad714x_disable(ad714x);
+
+	return 0;
+}
+
+static int ad714x_spi_resume(struct spi_device *spi)
+{
+	struct ad714x_chip *ad714x = spi_get_drvdata(spi);
+
+	ad714x_enable(ad714x);
+
+	return 0;
+}
+#endif
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static int ad714x_i2c_suspend(struct i2c_client *client, pm_message_t message)
+{
+	struct ad714x_chip *ad714x = i2c_get_clientdata(client);
+
+	ad714x_disable(ad714x);
+
+	return 0;
+}
+
+static int ad714x_i2c_resume(struct i2c_client *client)
+{
+	struct ad714x_chip *ad714x = i2c_get_clientdata(client);
+
+	ad714x_enable(ad714x);
+
+	return 0;
+}
+#endif
+
+#else
+#define ad714x_spi_suspend NULL
+#define ad714x_spi_resume  NULL
+#define ad714x_i2c_suspend NULL
+#define ad714x_i2c_resume  NULL
+#endif
+
+#if defined(CONFIG_SPI) || defined(CONFIG_SPI_MODULE)
+static int ad714x_spi_read(struct device *dev, unsigned short reg,
+		unsigned short *data)
+{
+	struct spi_device *spi = to_spi_device(dev);
+	unsigned short tx = AD714x_SPI_CMD_PREFIX | AD714x_SPI_READ | reg;
+
+	return spi_write_then_read(spi, (u8 *)&tx, 2, (u8 *)data, 2);
+}
+
+static int ad714x_spi_write(struct device *dev, unsigned short reg,
+		unsigned short data)
+{
+	struct spi_device *spi = to_spi_device(dev);
+	unsigned short tx[2] = {
+		AD714x_SPI_CMD_PREFIX | reg,
+		data
+	};
+
+	return spi_write(spi, (u8 *)tx, 4);
+}
+
+static int __devinit ad714x_spi_probe(struct spi_device *spi)
+{
+	int ret = 0;
+	struct ad714x_chip *chip;
+
+	if (spi->dev.platform_data == NULL) {
+		dev_err(&spi->dev, "platform data for ad714x doesn't exist\n");
+		return -ENODEV;
+	}
+
+	chip = kzalloc(sizeof(struct ad714x_chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	chip->read = ad714x_spi_read;
+	chip->write = ad714x_spi_write;
+	chip->hw = spi->dev.platform_data;
+	chip->irq = spi->irq;
+	chip->dev = &spi->dev;
+	spi_set_drvdata(spi, chip);
+
+	/* common probe not related with spi/i2c */
+	ret = ad714x_probe(chip, BUS_SPI);
+	if (ret)
+		kfree(chip);
+
+	return ret;
+}
+
+static int __devexit ad714x_spi_remove(struct spi_device *spi)
+{
+	struct ad714x_chip *chip = spi_get_drvdata(spi);
+
+	ad714x_remove(chip);
+
+	kfree(chip);
+
+	return 0;
+}
+
+static struct spi_driver ad714x_spi_driver = {
+	.driver = {
+		.name	= "ad714x_captouch",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= ad714x_spi_probe,
+	.remove		= __devexit_p(ad714x_spi_remove),
+	.suspend	= ad714x_spi_suspend,
+	.resume		= ad714x_spi_resume,
+};
+
+static inline int ad714x_spi_register_driver(struct spi_driver *spi_drv)
+{
+	return spi_register_driver(spi_drv);
+}
+
+static inline void ad714x_spi_unregister_driver(struct spi_driver *spi_drv)
+{
+	spi_unregister_driver(spi_drv);
+}
+
+#else
+#define ad714x_spi_register_driver(p) 0
+#define ad714x_spi_unregister_driver(p)
+#endif
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static int ad714x_i2c_write(struct device *dev, unsigned short reg,
+		unsigned short data)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	int ret = 0;
+	u8 *_reg = (u8 *)&reg;
+	u8 *_data = (u8 *)&data;
+
+	u8 tx[4] = {
+		_reg[1],
+		_reg[0],
+		_data[1],
+		_data[0]
+	};
+
+	ret = i2c_master_send(client, tx, 4);
+	if (ret < 0)
+		dev_err(&client->dev, "I2C write error\n");
+
+	return ret;
+}
+
+static int ad714x_i2c_read(struct device *dev, unsigned short reg,
+		unsigned short *data)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	int ret = 0;
+	u8 *_reg = (u8 *)&reg;
+	u8 *_data = (u8 *)data;
+
+	u8 tx[2] = {
+		_reg[1],
+		_reg[0]
+	};
+	u8 rx[2];
+
+	ret = i2c_master_send(client, tx, 2);
+	if (ret < 0) {
+		dev_err(&client->dev, "I2C read error\n");
+		return ret;
+	}
+
+	ret = i2c_master_recv(client, rx, 2);
+	if (ret < 0) {
+		dev_err(&client->dev, "I2C read error\n");
+		return ret;
+	}
+
+	_data[0] = rx[1];
+	_data[1] = rx[0];
+
+	return ret;
+}
+
+static int __devinit ad714x_i2c_probe(struct i2c_client *client,
+		const struct i2c_device_id *id)
+{
+	int ret = 0;
+	struct ad714x_chip *chip;
+
+	if (client->dev.platform_data == NULL) {
+		dev_err(&client->dev,
+			"platform data for ad714x doesn't exist\n");
+		return -ENODEV;
+	}
+
+	chip = kzalloc(sizeof(struct ad714x_chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	chip->read = ad714x_i2c_read;
+	chip->write = ad714x_i2c_write;
+	chip->hw = client->dev.platform_data;
+	chip->irq = client->irq;
+	chip->dev = &client->dev;
+	i2c_set_clientdata(client, chip);
+
+	/* common probe not related with spi/i2c */
+	ret = ad714x_probe(chip, BUS_I2C);
+	if (ret)
+		kfree(chip);
+
+	return ret;
+}
+
+static int __devexit ad714x_i2c_remove(struct i2c_client *client)
+{
+	struct ad714x_chip *chip = i2c_get_clientdata(client);
+
+	ad714x_remove(chip);
+
+	kfree(chip);
+
+	return 0;
+}
+
+static const struct i2c_device_id ad714x_id[] = {
+	{ "ad7142_captouch", 0 },
+	{ "ad7147_captouch", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, ad714x_id);
+
+static struct i2c_driver ad714x_i2c_driver = {
+	.driver = {
+		.name = "ad714x_captouch",
+	},
+	.probe    = ad714x_i2c_probe,
+	.remove   = __devexit_p(ad714x_i2c_remove),
+	.suspend  = ad714x_i2c_suspend,
+	.resume	  = ad714x_i2c_resume,
+	.id_table = ad714x_id,
+};
+
+static inline int ad714x_i2c_add_driver(struct i2c_driver *i2c_drv)
+{
+	return i2c_add_driver(i2c_drv);
+}
+
+static inline void ad714x_i2c_del_driver(struct i2c_driver *i2c_drv)
+{
+	i2c_del_driver(i2c_drv);
+}
+
+#else
+#define ad714x_i2c_add_driver(p) 0
+#define ad714x_i2c_del_driver(p)
+#endif
+
+static int __init ad714x_init(void)
+{
+
+	int ret = 0;
+	ret = ad714x_spi_register_driver(&ad714x_spi_driver);
+	if (ret)
+		goto err;
+	ret = ad714x_i2c_add_driver(&ad714x_i2c_driver);
+	if (ret)
+		ad714x_spi_unregister_driver(&ad714x_spi_driver);
+err:
+	return ret;
+}
+
+static void __exit ad714x_exit(void)
+{
+	ad714x_spi_unregister_driver(&ad714x_spi_driver);
+	ad714x_i2c_del_driver(&ad714x_i2c_driver);
+}
+
+module_init(ad714x_init);
+module_exit(ad714x_exit);
+
+MODULE_DESCRIPTION("Analog Devices AD714X Capacitance Touch Sensor Driver");
+MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/input.h b/include/linux/input.h
index 8b3bc3e..3836e7c 100644
--- a/include/linux/input.h
+++ b/include/linux/input.h
@@ -756,6 +756,7 @@ struct input_absinfo {
 #define BUS_HOST		0x19
 #define BUS_GSC			0x1A
 #define BUS_ATARI		0x1B
+#define BUS_SPI			0x1C
 
 /*
  * MT_TOOL types
diff --git a/include/linux/input/ad714x.h b/include/linux/input/ad714x.h
new file mode 100644
index 0000000..bacc02d
--- /dev/null
+++ b/include/linux/input/ad714x.h
@@ -0,0 +1,62 @@
+/*
+ * include/linux/input/ad714x.h
+ *
+ * AD714x is very flexible, it can be used as buttons, scrollwheel,
+ * slider, touchpad at the same time. That depends on the boards.
+ * The platform_data for the device's "struct device" holds this
+ * information.
+ * Copyright 2009 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#ifndef __LINUX_INPUT_AD714X_H__
+#define __LINUX_INPUT_AD714X_H__
+
+#define STAGE_NUM              12
+#define STAGE_CFGREG_NUM       8
+#define SYS_CFGREG_NUM         8
+
+/* board information which need be initialized in arch/mach... */
+struct ad714x_slider_plat {
+	int start_stage;
+	int end_stage;
+	int max_coord;
+};
+
+struct ad714x_wheel_plat {
+	int start_stage;
+	int end_stage;
+	int max_coord;
+};
+
+struct ad714x_touchpad_plat {
+	int x_start_stage;
+	int x_end_stage;
+	int x_max_coord;
+
+	int y_start_stage;
+	int y_end_stage;
+	int y_max_coord;
+};
+
+struct ad714x_button_plat {
+	int keycode;
+	unsigned short l_mask;
+	unsigned short h_mask;
+};
+
+struct ad714x_platform_data {
+	int slider_num;
+	int wheel_num;
+	int touchpad_num;
+	int button_num;
+	struct ad714x_slider_plat *slider;
+	struct ad714x_wheel_plat *wheel;
+	struct ad714x_touchpad_plat *touchpad;
+	struct ad714x_button_plat *button;
+	unsigned short stage_cfg_reg[STAGE_NUM][STAGE_CFGREG_NUM];
+	unsigned short sys_cfg_reg[SYS_CFGREG_NUM];
+};
+
+#endif
-- 
1.5.6.3


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* RE: [Uclinux-dist-devel] [PATCH v3] add analog devices AD714X captouchinput driver
  2009-09-11  6:53 [PATCH v3] add analog devices AD714X captouch input driver Barry Song
@ 2009-09-11 13:19 ` Hennerich, Michael
  2009-09-11 13:28   ` [Uclinux-dist-devel] [PATCH v3] add analog devices AD714Xcaptouchinput driver Hennerich, Michael
       [not found]   ` <8A42379416420646B9BFAC9682273B6D0DC3BD95-pcKY8lWzTjquVPpjEGsWsTcYPEmu4y7e@public.gmane.org>
  2009-09-14  3:36 ` [Uclinux-dist-devel] [PATCH v3] add analog devices AD714X captouch input driver Mike Frysinger
       [not found] ` <1252652006-5270-1-git-send-email-21cnbao-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
  2 siblings, 2 replies; 21+ messages in thread
From: Hennerich, Michael @ 2009-09-11 13:19 UTC (permalink / raw)
  To: Barry Song, dmitry.torokhov
  Cc: uclinux-dist-devel, linux-input, David Brownell

On Friday 11 September 2009, Song, Barry wrote:
+static int __init ad714x_init(void)
+{
+
+	int ret = 0;
+	ret = ad714x_spi_register_driver(&ad714x_spi_driver);
+	if (ret)
+		goto err;
+	ret = ad714x_i2c_add_driver(&ad714x_i2c_driver);
+	if (ret)
+		ad714x_spi_unregister_driver(&ad714x_spi_driver);
+err:
+	return ret;
+}
+
+static void __exit ad714x_exit(void)
+{
+	ad714x_spi_unregister_driver(&ad714x_spi_driver);
+	ad714x_i2c_del_driver(&ad714x_i2c_driver);
+}

This doesn't make much sense!
Assuming we have two AD714x in a system. One connected by SPI the other
by I2C.
Why would I remove the SPI one in case the other I2C fails, or not even
try the SPI one if the I2C fails?

Who says that a driver can't have two module_init()?

Use two entry points and let them individually return its status.

Signed-off-by: Michael Hennerich <michael.hennerich@analog.com>

Index: drivers/input/misc/ad714x.c

===================================================================

--- drivers/input/misc/ad714x.c (revision 7296)

+++ drivers/input/misc/ad714x.c (working copy)

@@ -1479,19 +1479,18 @@

        .resume         = ad714x_spi_resume,

 };

 

-static inline int ad714x_spi_register_driver(struct spi_driver
*spi_drv)                              
+static inline int ad714x_spi_init(struct spi_driver *spi_drv)

 {

        return spi_register_driver(spi_drv);

 }

 

-static inline void ad714x_spi_unregister_driver(struct spi_driver
*spi_drv)                           
+static inline void ad714x_spi_exit(struct spi_driver *spi_drv)

 {

        spi_unregister_driver(spi_drv);

 }

 

-#else

-#define ad714x_spi_register_driver(p) 0

-#define ad714x_spi_unregister_driver(p)

+module_init(ad714x_spi_init);

+module_exit(ad714x_spi_exit);

 #endif

 

 #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)

@@ -1609,44 +1608,20 @@

        .id_table = ad714x_id,

 };

 

-static inline int ad714x_i2c_add_driver(struct i2c_driver *i2c_drv)

+static __init int ad714x_i2c_init(struct i2c_driver *i2c_drv)

 {

        return i2c_add_driver(i2c_drv);
 }

-static inline void ad714x_i2c_del_driver(struct i2c_driver *i2c_drv)
+static __init void ad714x_i2c_exit(struct i2c_driver *i2c_drv)
 {
        i2c_del_driver(i2c_drv);
 }

-#else
-#define ad714x_i2c_add_driver(p) 0
-#define ad714x_i2c_del_driver(p)
+module_init(ad714x_i2c_init);
+module_exit(ad714x_i2c_exit);
 #endif

-static int __init ad714x_init(void)
-{
-
-       int ret = 0;
-       ret = ad714x_spi_register_driver(&ad714x_spi_driver);
-       if (ret)
-               goto err;
-       ret = ad714x_i2c_add_driver(&ad714x_i2c_driver);
-       if (ret)
-               ad714x_spi_unregister_driver(&ad714x_spi_driver);
-err:
-       return ret;
-}
-
-static void __exit ad714x_exit(void)
-{
-       ad714x_spi_unregister_driver(&ad714x_spi_driver);
-       ad714x_i2c_del_driver(&ad714x_i2c_driver);
-}
-
-module_init(ad714x_init);
-module_exit(ad714x_exit);
-
 MODULE_DESCRIPTION("Analog Devices AD714X Capacitance Touch Sensor
Driver");
 MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
 MODULE_LICENSE("GPL");
 


^ permalink raw reply	[flat|nested] 21+ messages in thread

* RE: [Uclinux-dist-devel] [PATCH v3] add analog devices AD714Xcaptouchinput driver
  2009-09-11 13:19 ` [Uclinux-dist-devel] [PATCH v3] add analog devices AD714X captouchinput driver Hennerich, Michael
@ 2009-09-11 13:28   ` Hennerich, Michael
       [not found]   ` <8A42379416420646B9BFAC9682273B6D0DC3BD95-pcKY8lWzTjquVPpjEGsWsTcYPEmu4y7e@public.gmane.org>
  1 sibling, 0 replies; 21+ messages in thread
From: Hennerich, Michael @ 2009-09-11 13:28 UTC (permalink / raw)
  To: Hennerich, Michael, Barry Song, dmitry.torokhov
  Cc: David Brownell, uclinux-dist-devel, linux-input


On Friday 11 September 2009, Hennerich, Michael wrote:
>On Friday 11 September 2009, Song, Barry wrote:
>+static int __init ad714x_init(void)
>+{
>+
>+	int ret = 0;
>+	ret = ad714x_spi_register_driver(&ad714x_spi_driver);
>+	if (ret)
>+		goto err;
>+	ret = ad714x_i2c_add_driver(&ad714x_i2c_driver);
>+	if (ret)
>+		ad714x_spi_unregister_driver(&ad714x_spi_driver);
>+err:
>+	return ret;
>+}
>+
>+static void __exit ad714x_exit(void)
>+{
>+	ad714x_spi_unregister_driver(&ad714x_spi_driver);
>+	ad714x_i2c_del_driver(&ad714x_i2c_driver);
>+}
>
>This doesn't make much sense!
>Assuming we have two AD714x in a system. One connected by SPI the other
>by I2C.
>Why would I remove the SPI one in case the other I2C fails, or not even
>try the SPI one if the I2C fails?
>
>Who says that a driver can't have two module_init()?
>
>Use two entry points and let them individually return its status.
>
>Signed-off-by: Michael Hennerich <michael.hennerich@analog.com>
>
>Index: drivers/input/misc/ad714x.c
>
>===================================================================
>
>--- drivers/input/misc/ad714x.c (revision 7296)
>
>+++ drivers/input/misc/ad714x.c (working copy)
>
>@@ -1479,19 +1479,18 @@
>
>        .resume         = ad714x_spi_resume,
>
> };
>
>
>
>-static inline int ad714x_spi_register_driver(struct spi_driver
>*spi_drv)
>+static inline int ad714x_spi_init(struct spi_driver *spi_drv)
>
> {
>
>        return spi_register_driver(spi_drv);
>
> }
>
>
>
>-static inline void ad714x_spi_unregister_driver(struct spi_driver
>*spi_drv)
>+static inline void ad714x_spi_exit(struct spi_driver *spi_drv)
>
> {
>
>        spi_unregister_driver(spi_drv);
>
> }
>
>
>
>-#else
>
>-#define ad714x_spi_register_driver(p) 0
>
>-#define ad714x_spi_unregister_driver(p)
>
>+module_init(ad714x_spi_init);
>
>+module_exit(ad714x_spi_exit);
>
> #endif
>
>
>
> #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
>
>@@ -1609,44 +1608,20 @@
>
>        .id_table = ad714x_id,
>
> };
>
>
>
>-static inline int ad714x_i2c_add_driver(struct i2c_driver *i2c_drv)
>
>+static __init int ad714x_i2c_init(struct i2c_driver *i2c_drv)
>
> {
>
>        return i2c_add_driver(i2c_drv);
> }
>
>-static inline void ad714x_i2c_del_driver(struct i2c_driver *i2c_drv)
>+static __init void ad714x_i2c_exit(struct i2c_driver *i2c_drv)
> {
>        i2c_del_driver(i2c_drv);
> }
>
>-#else
>-#define ad714x_i2c_add_driver(p) 0
>-#define ad714x_i2c_del_driver(p)
>+module_init(ad714x_i2c_init);
>+module_exit(ad714x_i2c_exit);
> #endif
>
>-static int __init ad714x_init(void)
>-{
>-
>-       int ret = 0;
>-       ret = ad714x_spi_register_driver(&ad714x_spi_driver);
>-       if (ret)
>-               goto err;
>-       ret = ad714x_i2c_add_driver(&ad714x_i2c_driver);
>-       if (ret)
>-               ad714x_spi_unregister_driver(&ad714x_spi_driver);
>-err:
>-       return ret;
>-}
>-
>-static void __exit ad714x_exit(void)
>-{
>-       ad714x_spi_unregister_driver(&ad714x_spi_driver);
>-       ad714x_i2c_del_driver(&ad714x_i2c_driver);
>-}
>-
>-module_init(ad714x_init);
>-module_exit(ad714x_exit);
>-
> MODULE_DESCRIPTION("Analog Devices AD714X Capacitance Touch Sensor
>Driver");
> MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
> MODULE_LICENSE("GPL");

Sorry for the noise - wrong patch attached.
This one should work better... 

Index: drivers/input/misc/ad714x.c

===================================================================

--- drivers/input/misc/ad714x.c (revision 7296)

+++ drivers/input/misc/ad714x.c (working copy)

@@ -1479,19 +1479,18 @@

        .resume         = ad714x_spi_resume,

 };

 

-static inline int ad714x_spi_register_driver(struct spi_driver
*spi_drv)                              
+static __init int ad714x_spi_init(void)

 {

-       return spi_register_driver(spi_drv);

+       return spi_register_driver(&ad714x_spi_driver);

 }

 

-static inline void ad714x_spi_unregister_driver(struct spi_driver
*spi_drv)                           
+static __init void ad714x_spi_exit(void)

 {

-       spi_unregister_driver(spi_drv);

+       spi_unregister_driver(&ad714x_spi_driver);

 }

 

-#else

-#define ad714x_spi_register_driver(p) 0

-#define ad714x_spi_unregister_driver(p)

+module_init(ad714x_spi_init);

+module_exit(ad714x_spi_exit);

 #endif

 

 #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)

@@ -1609,44 +1608,20 @@

        .id_table = ad714x_id,

 };

 

-static inline int ad714x_i2c_add_driver(struct i2c_driver *i2c_drv)

+static __init int ad714x_i2c_init(void)

 {

-       return i2c_add_driver(i2c_drv);

+       return i2c_add_driver(&ad714x_i2c_driver);

 }

-static inline void ad714x_i2c_del_driver(struct i2c_driver *i2c_drv)
+static __init void ad714x_i2c_exit(void)
 {
-       i2c_del_driver(i2c_drv);
+       i2c_del_driver(&ad714x_i2c_driver);
 }

-#else
-#define ad714x_i2c_add_driver(p) 0
-#define ad714x_i2c_del_driver(p)
+module_init(ad714x_i2c_init);
+module_exit(ad714x_i2c_exit);
 #endif

-static int __init ad714x_init(void)
-{
-
-       int ret = 0;
-       ret = ad714x_spi_register_driver(&ad714x_spi_driver);
-       if (ret)
-               goto err;
-       ret = ad714x_i2c_add_driver(&ad714x_i2c_driver);
-       if (ret)
-               ad714x_spi_unregister_driver(&ad714x_spi_driver);
-err:
-       return ret;
-}
-
-static void __exit ad714x_exit(void)
-{
-       ad714x_spi_unregister_driver(&ad714x_spi_driver);
-       ad714x_i2c_del_driver(&ad714x_i2c_driver);
-}
-
-module_init(ad714x_init);
-module_exit(ad714x_exit);
-
 MODULE_DESCRIPTION("Analog Devices AD714X Capacitance Touch Sensor
Driver");
 MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
 MODULE_LICENSE("GPL");


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [Uclinux-dist-devel] [PATCH v3] add analog devices AD714X captouch input driver
  2009-09-11  6:53 [PATCH v3] add analog devices AD714X captouch input driver Barry Song
  2009-09-11 13:19 ` [Uclinux-dist-devel] [PATCH v3] add analog devices AD714X captouchinput driver Hennerich, Michael
@ 2009-09-14  3:36 ` Mike Frysinger
       [not found] ` <1252652006-5270-1-git-send-email-21cnbao-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
  2 siblings, 0 replies; 21+ messages in thread
From: Mike Frysinger @ 2009-09-14  3:36 UTC (permalink / raw)
  To: Barry Song; +Cc: dmitry.torokhov, uclinux-dist-devel, linux-input

On Fri, Sep 11, 2009 at 02:53, Barry Song wrote:

subject should really be "Analog Devices" ...

> Signed-off-by: Barry Song <21cnbao@gmail.com>

you've trimmed off s-o-b from people who have done significant earlier work ...
Signed-off-by: Bryan Wu <cooloney@kernel.org>
Signed-off-by: Michael Hennerich <michael.hennerich@analog.com>
-mike

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v3] add analog devices AD714X captouchinput driver
       [not found]   ` <8A42379416420646B9BFAC9682273B6D0DC3BD95-pcKY8lWzTjquVPpjEGsWsTcYPEmu4y7e@public.gmane.org>
@ 2009-10-09  8:56     ` Mike Frysinger
       [not found]       ` <8bd0f97a0910090156g2a8fba58r4085422f3a79c892-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
  0 siblings, 1 reply; 21+ messages in thread
From: Mike Frysinger @ 2009-10-09  8:56 UTC (permalink / raw)
  To: Hennerich, Michael
  Cc: David Brownell,
	uclinux-dist-devel-ZG0+EudsQA8dtHy/vicBwGD2FQJk+8+b,
	dmitry.torokhov-Re5JQEeQqe8AvxtiuMwx3w,
	linux-input-u79uwXL29TY76Z2rM5mHXA

On Fri, Sep 11, 2009 at 09:19, Hennerich, Michael wrote:
> On Friday 11 September 2009, Song, Barry wrote:
> +static int __init ad714x_init(void)
> +{
> +
> +       int ret = 0;
> +       ret = ad714x_spi_register_driver(&ad714x_spi_driver);
> +       if (ret)
> +               goto err;
> +       ret = ad714x_i2c_add_driver(&ad714x_i2c_driver);
> +       if (ret)
> +               ad714x_spi_unregister_driver(&ad714x_spi_driver);
> +err:
> +       return ret;
> +}
> +
> +static void __exit ad714x_exit(void)
> +{
> +       ad714x_spi_unregister_driver(&ad714x_spi_driver);
> +       ad714x_i2c_del_driver(&ad714x_i2c_driver);
> +}
>
> This doesn't make much sense!
> Assuming we have two AD714x in a system. One connected by SPI the other
> by I2C.
> Why would I remove the SPI one in case the other I2C fails, or not even
> try the SPI one if the I2C fails?

is this a realistic problem ?  we arent talking about failure to probe
the device, we're talking about failure to register the driver as
available.  i.e. the only way the module_init step should fail is if
i2c_add_driver() or spi_register_driver() fails.  and if either of
those fail, it's most likely a pretty bad situation.

failure to probe/detect/talk to the device are a different issue and a
problem with one interface will not affect the other.

> Who says that a driver can't have two module_init()?

Mr. C Code says it wont work.  if ad714x is built as a module and both
I2C and SPI support are enabled, we get:
drivers/input/misc/ad714x.c:1621: error: redefinition of ‘__inittest’
drivers/input/misc/ad714x.c:1492: error: previous definition of
‘__inittest’ was here

these lines correspond to the module_init() macro

linux/init.h says:
/* Each module must use one module_init(). */
#define module_init(initfn)                 \
    static inline initcall_t __inittest(void)       \
    { return initfn; }                  \
    int init_module(void) __attribute__((alias(#initfn)));

and indeed, the error makes sense -- you cant have multiple functions
named the same thing (in this case, we'd have two __inittest() and two
init_module())
-mike
_______________________________________________
Uclinux-dist-devel mailing list
Uclinux-dist-devel@blackfin.uclinux.org
https://blackfin.uclinux.org/mailman/listinfo/uclinux-dist-devel

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v3] add analog devices AD714X captouchinput driver
       [not found]       ` <8bd0f97a0910090156g2a8fba58r4085422f3a79c892-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
@ 2009-10-09  9:03         ` David Woodhouse
       [not found]           ` <1255079007.8362.70.camel-uXGAPMMVk8bAQYKIod7YupZV94DADvEd@public.gmane.org>
  2009-10-09 10:34         ` [PATCH v3] add analog devices AD714Xcaptouchinput driver Song, Barry
  1 sibling, 1 reply; 21+ messages in thread
From: David Woodhouse @ 2009-10-09  9:03 UTC (permalink / raw)
  To: Mike Frysinger
  Cc: David Brownell,
	uclinux-dist-devel-ZG0+EudsQA8dtHy/vicBwGD2FQJk+8+b,
	dmitry.torokhov-Re5JQEeQqe8AvxtiuMwx3w, Hennerich, Michael,
	linux-input-u79uwXL29TY76Z2rM5mHXA

On Fri, 2009-10-09 at 04:56 -0400, Mike Frysinger wrote:
> 
> > Who says that a driver can't have two module_init()?
> 
> Mr. C Code says it wont work. 

It could be fixed -- it works when the module is built-in; it could be
made to work in the way way when it's a module.

How did I get subscribed to this list, anyway?

-- 
David Woodhouse                            Open Source Technology Centre
David.Woodhouse-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org                              Intel Corporation

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v3] add analog devices AD714X captouchinput driver
       [not found]           ` <1255079007.8362.70.camel-uXGAPMMVk8bAQYKIod7YupZV94DADvEd@public.gmane.org>
@ 2009-10-09  9:18             ` Mike Frysinger
       [not found]               ` <8bd0f97a0910090218j6a2aa6aaq497fba3f04d7b19f-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
  0 siblings, 1 reply; 21+ messages in thread
From: Mike Frysinger @ 2009-10-09  9:18 UTC (permalink / raw)
  To: David Woodhouse
  Cc: David Brownell,
	uclinux-dist-devel-ZG0+EudsQA8dtHy/vicBwGD2FQJk+8+b,
	dmitry.torokhov-Re5JQEeQqe8AvxtiuMwx3w, Hennerich, Michael,
	linux-input-u79uwXL29TY76Z2rM5mHXA

On Fri, Oct 9, 2009 at 05:03, David Woodhouse wrote:
> On Fri, 2009-10-09 at 04:56 -0400, Mike Frysinger wrote:
>> > Who says that a driver can't have two module_init()?
>>
>> Mr. C Code says it wont work.
>
> It could be fixed -- it works when the module is built-in; it could be
> made to work in the way way when it's a module.

probably, but it opens a fairly large can of worms that until now, no
one has really complained as a real problem.  if all the module_init
functions do not return the same thing (i.e. all pass or all fail),
what do you do ?
 - return an error to userspace an unload module (this happens today)
 - return an error to userspace but leave module loaded
 - do not return an error and leave the module loaded

if you leave the module partially loaded, how do you know which fini
functions to run ?  in this case, we have an init/fini func for spi
and an init/fini func for i2c.  if the spi worked but i2c failed, we
obviously dont want to run the fini i2c func with a partially loaded
module.

today, modules that lack a fini func are not allowed to be unloaded.
how do you know in a multi init/fini setup which ones have a
corresponding fini ?  presumably the answer here would be resolved
naturally if the previous question is answered.

i also imagine the rest of the module framework in the kernel will
need significant changes all over the place to handle an array of
init/fini functions.  this is a pretty hard sell when the answer could
simply be: make more damned modules in your driver.  we could have
three modules here: ad714x, ad714x-i2c, ad714x-spi.  then all of the
issues raised by Michael and the things i raise above all magically
resolve themselves with the framework we have today.

personally, i think catering to this highly unlikely edge case is time
that could be spent on other much more realistic cases

> How did I get subscribed to this list, anyway?

i think i might have accidentally added you to the subscribed list
while processing moderation requests when i meant to add you to just
the approved list.  i'll fix it up of course, sorry about that.
-mike

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v3] add analog devices AD714X captouchinput driver
       [not found]               ` <8bd0f97a0910090218j6a2aa6aaq497fba3f04d7b19f-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
@ 2009-10-09  9:20                 ` David Woodhouse
  2009-10-09 10:11                 ` Mike Frysinger
  2009-10-09 21:33                 ` Jiri Kosina
  2 siblings, 0 replies; 21+ messages in thread
From: David Woodhouse @ 2009-10-09  9:20 UTC (permalink / raw)
  To: Mike Frysinger
  Cc: David Brownell,
	uclinux-dist-devel-ZG0+EudsQA8dtHy/vicBwGD2FQJk+8+b,
	dmitry.torokhov-Re5JQEeQqe8AvxtiuMwx3w, Hennerich, Michael,
	linux-input-u79uwXL29TY76Z2rM5mHXA

On Fri, 2009-10-09 at 05:18 -0400, Mike Frysinger wrote:
> personally, i think catering to this highly unlikely edge case is time
> that could be spent on other much more realistic cases

Yeah, you're probably right.

> > How did I get subscribed to this list, anyway?
> 
> i think i might have accidentally added you to the subscribed list
> while processing moderation requests when i meant to add you to just
> the approved list.  i'll fix it up of course, sorry about that. 

No problem. It's just that it's arriving in my INBOX rather than a
list-specific folder. I'll fix that; no need to unsubscribe me.

-- 
David Woodhouse                            Open Source Technology Centre
David.Woodhouse-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org                              Intel Corporation

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v3] add analog devices AD714X captouchinput driver
       [not found]               ` <8bd0f97a0910090218j6a2aa6aaq497fba3f04d7b19f-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
  2009-10-09  9:20                 ` David Woodhouse
@ 2009-10-09 10:11                 ` Mike Frysinger
       [not found]                   ` <8bd0f97a0910090311t5f5167c8r336afaa6f790e1a9-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
  2009-10-09 21:33                 ` Jiri Kosina
  2 siblings, 1 reply; 21+ messages in thread
From: Mike Frysinger @ 2009-10-09 10:11 UTC (permalink / raw)
  To: dmitry.torokhov-Re5JQEeQqe8AvxtiuMwx3w
  Cc: David Brownell,
	uclinux-dist-devel-ZG0+EudsQA8dtHy/vicBwGD2FQJk+8+b, Hennerich,
	Michael, linux-input-u79uwXL29TY76Z2rM5mHXA

On Fri, Oct 9, 2009 at 05:18, Mike Frysinger <vapier.adi-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:
> we could have three modules here: ad714x, ad714x-i2c, ad714x-spi

are you ok with this approach Dmitry ?  this way people can
load/unload a specific bus without affecting the core or the other
bus, and errors with loading one bus type wouldnt affect loading of
the other.
-mike

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v3] add analog devices AD714Xcaptouchinput driver
       [not found]       ` <8bd0f97a0910090156g2a8fba58r4085422f3a79c892-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
  2009-10-09  9:03         ` David Woodhouse
@ 2009-10-09 10:34         ` Song, Barry
  1 sibling, 0 replies; 21+ messages in thread
From: Song, Barry @ 2009-10-09 10:34 UTC (permalink / raw)
  To: Mike Frysinger, Hennerich, Michael
  Cc: David Brownell,
	uclinux-dist-devel-ZG0+EudsQA8dtHy/vicBwGD2FQJk+8+b,
	dmitry.torokhov-Re5JQEeQqe8AvxtiuMwx3w,
	linux-input-u79uwXL29TY76Z2rM5mHXA

 

>-----Original Message-----
>From: uclinux-dist-devel-bounces-ZG0+EudsQA8dtHy/vicBwGD2FQJk+8+b@public.gmane.org 
>[mailto:uclinux-dist-devel-bounces-ZG0+EudsQA8dtHy/vicBwGD2FQJk+8+b@public.gmane.org] On 
>Behalf Of Mike Frysinger
>Sent: Friday, October 09, 2009 4:56 PM
>To: Hennerich, Michael
>Cc: David Brownell; uclinux-dist-devel-ZG0+EudsQA8dtHy/vicBwGD2FQJk+8+b@public.gmane.org; 
>dmitry.torokhov-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org; linux-input-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
>Subject: Re: [Uclinux-dist-devel] [PATCH v3] add analog 
>devices AD714Xcaptouchinput driver
>
>On Fri, Sep 11, 2009 at 09:19, Hennerich, Michael wrote:
>> On Friday 11 September 2009, Song, Barry wrote:
>> +static int __init ad714x_init(void)
>> +{
>> +
>> +       int ret = 0;
>> +       ret = ad714x_spi_register_driver(&ad714x_spi_driver);
>> +       if (ret)
>> +               goto err;
>> +       ret = ad714x_i2c_add_driver(&ad714x_i2c_driver);
>> +       if (ret)
>> +               ad714x_spi_unregister_driver(&ad714x_spi_driver);
>> +err:
>> +       return ret;
>> +}
>> +
>> +static void __exit ad714x_exit(void)
>> +{
>> +       ad714x_spi_unregister_driver(&ad714x_spi_driver);
>> +       ad714x_i2c_del_driver(&ad714x_i2c_driver);
>> +}
>>
>> This doesn't make much sense!
>> Assuming we have two AD714x in a system. One connected by 
>SPI the other
>> by I2C.
>> Why would I remove the SPI one in case the other I2C fails, 
>or not even
>> try the SPI one if the I2C fails?
>
>is this a realistic problem ?  we arent talking about failure to probe
>the device, we're talking about failure to register the driver as
>available.  i.e. the only way the module_init step should fail is if
>i2c_add_driver() or spi_register_driver() fails.  and if either of
>those fail, it's most likely a pretty bad situation.

I agree with this. It is clear the failure of spi_register_driver or i2c_add_driver means a kernel issue not a device probe issue. That maybe means no memory or other serious problems.
But anyway, I check in some codes to support the capacity that the other bus can still continue to register even after the first one fails, maybe the new codes are ugly.
static int spi_sta = -1, i2c_sta = -1;

static __init int ad714x_init(void)
{
#if defined(CONFIG_AD714X_SCAN_SPI)
        spi_sta = spi_register_driver(&ad714x_spi_driver);
#endif

#if defined(CONFIG_AD714X_SCAN_I2C)
        i2c_sta = i2c_add_driver(&ad714x_i2c_driver);
#endif

        /* If anyone of spi and i2c init successfully, we permit it to work */
        if ((spi_sta && i2c_sta) == 0)
                return 0;
        else 
                return -ENODEV;
}

static __init void ad714x_exit(void)
{
#if defined(CONFIG_AD714X_SCAN_SPI)
        if (!spi_sta)
                spi_unregister_driver(&ad714x_spi_driver);
#endif

#if defined(CONFIG_AD714X_SCAN_I2C)
        if (!i2c_sta)
                i2c_del_driver(&ad714x_i2c_driver);
#endif
}

>
>failure to probe/detect/talk to the device are a different issue and a
>problem with one interface will not affect the other.
>
>> Who says that a driver can't have two module_init()?
>
>Mr. C Code says it wont work.  if ad714x is built as a module and both
>I2C and SPI support are enabled, we get:
>drivers/input/misc/ad714x.c:1621: error: redefinition of '__inittest'
>drivers/input/misc/ad714x.c:1492: error: previous definition of
>'__inittest' was here
>
>these lines correspond to the module_init() macro
>
>linux/init.h says:
>/* Each module must use one module_init(). */
>#define module_init(initfn)                 \
>    static inline initcall_t __inittest(void)       \
>    { return initfn; }                  \
>    int init_module(void) __attribute__((alias(#initfn)));
>
>and indeed, the error makes sense -- you cant have multiple functions
>named the same thing (in this case, we'd have two __inittest() and two
>init_module())
>-mike
>_______________________________________________
>Uclinux-dist-devel mailing list
>Uclinux-dist-devel-ZG0+EudsQA8dtHy/vicBwGD2FQJk+8+b@public.gmane.org
>https://blackfin.uclinux.org/mailman/listinfo/uclinux-dist-devel
>

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v3] add analog devices AD714X captouchinput driver
       [not found]                   ` <8bd0f97a0910090311t5f5167c8r336afaa6f790e1a9-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
@ 2009-10-09 16:22                     ` Dmitry Torokhov
       [not found]                       ` <20091009162219.GB1092-WlK9ik9hQGAhIp7JRqBPierSzoNAToWh@public.gmane.org>
  0 siblings, 1 reply; 21+ messages in thread
From: Dmitry Torokhov @ 2009-10-09 16:22 UTC (permalink / raw)
  To: Mike Frysinger
  Cc: David Brownell,
	uclinux-dist-devel-ZG0+EudsQA8dtHy/vicBwGD2FQJk+8+b, Hennerich,
	Michael, linux-input-u79uwXL29TY76Z2rM5mHXA

On Fri, Oct 09, 2009 at 06:11:45AM -0400, Mike Frysinger wrote:
> On Fri, Oct 9, 2009 at 05:18, Mike Frysinger <vapier.adi-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:
> > we could have three modules here: ad714x, ad714x-i2c, ad714x-spi
> 
> are you ok with this approach Dmitry ?  this way people can
> load/unload a specific bus without affecting the core or the other
> bus, and errors with loading one bus type wouldnt affect loading of
> the other.

Yes, I think this is the most clean way.

-- 
Dmitry

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v3] add analog devices AD714X captouchinput driver
       [not found]               ` <8bd0f97a0910090218j6a2aa6aaq497fba3f04d7b19f-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
  2009-10-09  9:20                 ` David Woodhouse
  2009-10-09 10:11                 ` Mike Frysinger
@ 2009-10-09 21:33                 ` Jiri Kosina
       [not found]                   ` <alpine.LRH.2.00.0910092331040.12171-1ReQVI26iDCaZKY3DrU6dA@public.gmane.org>
  2 siblings, 1 reply; 21+ messages in thread
From: Jiri Kosina @ 2009-10-09 21:33 UTC (permalink / raw)
  To: Mike Frysinger
  Cc: Hennerich, Michael, dmitry.torokhov-Re5JQEeQqe8AvxtiuMwx3w,
	David Brownell, linux-input-u79uwXL29TY76Z2rM5mHXA,
	uclinux-dist-devel-ZG0+EudsQA8dtHy/vicBwGD2FQJk+8+b

On Fri, 9 Oct 2009, Mike Frysinger wrote:

> > How did I get subscribed to this list, anyway?
> 
> i think i might have accidentally added you to the subscribed list
> while processing moderation requests when i meant to add you to just
> the approved list.  i'll fix it up of course, sorry about that.

BTW you should rather check your processess, this happened to me a few 
weeks ago as well -- I had to unsubscribe manually after sending some 
message to uclinux list and getting automatically subscribed afterwards.

-- 
Jiri Kosina
SUSE Labs, Novell Inc.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v3] add analog devices AD714X captouchinput driver
       [not found]                   ` <alpine.LRH.2.00.0910092331040.12171-1ReQVI26iDCaZKY3DrU6dA@public.gmane.org>
@ 2009-10-09 21:36                     ` Mike Frysinger
  0 siblings, 0 replies; 21+ messages in thread
From: Mike Frysinger @ 2009-10-09 21:36 UTC (permalink / raw)
  To: Jiri Kosina
  Cc: Hennerich, Michael, dmitry.torokhov-Re5JQEeQqe8AvxtiuMwx3w,
	David Brownell, linux-input-u79uwXL29TY76Z2rM5mHXA,
	uclinux-dist-devel-ZG0+EudsQA8dtHy/vicBwGD2FQJk+8+b

On Fri, Oct 9, 2009 at 17:33, Jiri Kosina wrote:
> On Fri, 9 Oct 2009, Mike Frysinger wrote:
>> > How did I get subscribed to this list, anyway?
>>
>> i think i might have accidentally added you to the subscribed list
>> while processing moderation requests when i meant to add you to just
>> the approved list.  i'll fix it up of course, sorry about that.
>
> BTW you should rather check your processess, this happened to me a few
> weeks ago as well -- I had to unsubscribe manually after sending some
> message to uclinux list and getting automatically subscribed afterwards.

i think it's because of a new script that we've started using recently
that manages moderations via the command line rather than web
interface.  it subscribes people instead of adding them to the
approved list.  i vaguely recall this applying to about 3 or 4 people.
-mike
_______________________________________________
Uclinux-dist-devel mailing list
Uclinux-dist-devel@blackfin.uclinux.org
https://blackfin.uclinux.org/mailman/listinfo/uclinux-dist-devel

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v3] add analog devices AD714X captouchinput driver
       [not found]                       ` <20091009162219.GB1092-WlK9ik9hQGAhIp7JRqBPierSzoNAToWh@public.gmane.org>
@ 2009-10-10  2:24                         ` Barry Song
       [not found]                           ` <3c17e3570910091924p68a248e6q3dd2640acc17c90d-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
  0 siblings, 1 reply; 21+ messages in thread
From: Barry Song @ 2009-10-10  2:24 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: David Brownell,
	uclinux-dist-devel-ZG0+EudsQA8dtHy/vicBwGD2FQJk+8+b, Hennerich,
	Michael, linux-input-u79uwXL29TY76Z2rM5mHXA

On Sat, Oct 10, 2009 at 12:22 AM, Dmitry Torokhov
<dmitry.torokhov@gmail.com> wrote:
> On Fri, Oct 09, 2009 at 06:11:45AM -0400, Mike Frysinger wrote:
>> On Fri, Oct 9, 2009 at 05:18, Mike Frysinger <vapier.adi@gmail.com> wrote:
>> > we could have three modules here: ad714x, ad714x-i2c, ad714x-spi
>>
>> are you ok with this approach Dmitry ?  this way people can
>> load/unload a specific bus without affecting the core or the other
>> bus, and errors with loading one bus type wouldnt affect loading of
>> the other.
>
> Yes, I think this is the most clean way.
That will decrease the cohesion and increase coupling. It's tedious
for one driver with single feature to have three modules. To fulfill
that, ad714x need to expose data structures and export symbols to
ad714x-i2c and ad714x-spi. But those data structures and symbols are
more like local stuff. And I2C and SPI are just bottom level
communication ways, they should be the bottom level of ad714x, but
after splitting to 3 modules, it seems they become the top level of
ad714x.
How about we still have one module, but let users to select which bus
should be probed according to their boards.

config INPUT_AD714X
        tristate "Analog Devices AD7142/7147 Capacitance Touch Sensor"
        help
          Say Y here if you want to support an AD7142/7147 touch sensor.

          To compile this driver as a module, choose M here: the
          module will be called ad714x.

if INPUT_AD714X

config AD714X_SCAN_SPI
        bool "scan AD7142/7147 devices on SPI bus"
        default y
        select SPI
        ---help---
          Say Y here if you have AD7142/AD7147 hooked on SPI bus.
          If unsure, say N.


config AD714X_SCAN_I2C
        bool "scan AD7142/7147 devices on I2C bus"
        default y
        select I2C
        ---help---
          Say Y here if you have AD7142/AD7147 hooked on I2C bus.
          If unsure, say N.

endif

While AD714X_SCAN_SPI or AD714X_SCAN_I2C is selected, we let
corresponding codes compiled.  And we use variables to maintain the
registers of SPI/I2C to permit the other one working even after one
fails to register.

static int spi_sta = -1, i2c_sta = -1;

static __init int ad714x_init(void)
{
#if defined(CONFIG_AD714X_SCAN_SPI)
        spi_sta = spi_register_driver(&ad714x_spi_driver);
#endif

#if defined(CONFIG_AD714X_SCAN_I2C)
        i2c_sta = i2c_add_driver(&ad714x_i2c_driver);
#endif

        /* If anyone of spi and i2c init successfully, we permit it to work */
        if ((spi_sta && i2c_sta) == 0)
                return 0;
        else
                return -ENODEV;
}

static __init void ad714x_exit(void)
{
#if defined(CONFIG_AD714X_SCAN_SPI)
        if (!spi_sta)
                spi_unregister_driver(&ad714x_spi_driver);
#endif

#if defined(CONFIG_AD714X_SCAN_I2C)
        if (!i2c_sta)
                i2c_del_driver(&ad714x_i2c_driver);
#endif
}
module_init(ad714x_init);
module_exit(ad714x_exit);

>
> --
> Dmitry
> _______________________________________________
> Uclinux-dist-devel mailing list
> Uclinux-dist-devel@blackfin.uclinux.org
> https://blackfin.uclinux.org/mailman/listinfo/uclinux-dist-devel
>
_______________________________________________
Uclinux-dist-devel mailing list
Uclinux-dist-devel@blackfin.uclinux.org
https://blackfin.uclinux.org/mailman/listinfo/uclinux-dist-devel

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v3] add analog devices AD714X captouchinput driver
       [not found]                           ` <3c17e3570910091924p68a248e6q3dd2640acc17c90d-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
@ 2009-10-10 20:38                             ` Mike Frysinger
       [not found]                               ` <8bd0f97a0910101338y46cb227hdc6d83c3c6622a21-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
  0 siblings, 1 reply; 21+ messages in thread
From: Mike Frysinger @ 2009-10-10 20:38 UTC (permalink / raw)
  To: Barry Song
  Cc: David Brownell,
	uclinux-dist-devel-ZG0+EudsQA8dtHy/vicBwGD2FQJk+8+b,
	Dmitry Torokhov, Hennerich, Michael,
	linux-input-u79uwXL29TY76Z2rM5mHXA

On Fri, Oct 9, 2009 at 22:24, Barry Song wrote:
> On Sat, Oct 10, 2009 at 12:22 AM, Dmitry Torokhov wrote:
>> On Fri, Oct 09, 2009 at 06:11:45AM -0400, Mike Frysinger wrote:
>>> On Fri, Oct 9, 2009 at 05:18, Mike Frysinger <vapier.adi@gmail.com> wrote:
>>> > we could have three modules here: ad714x, ad714x-i2c, ad714x-spi
>>>
>>> are you ok with this approach Dmitry ?  this way people can
>>> load/unload a specific bus without affecting the core or the other
>>> bus, and errors with loading one bus type wouldnt affect loading of
>>> the other.
>>
>> Yes, I think this is the most clean way.
>
> That will decrease the cohesion and increase coupling. It's tedious
> for one driver with single feature to have three modules. To fulfill
> that, ad714x need to expose data structures and export symbols to
> ad714x-i2c and ad714x-spi. But those data structures and symbols are
> more like local stuff. And I2C and SPI are just bottom level
> communication ways, they should be the bottom level of ad714x, but
> after splitting to 3 modules, it seems they become the top level of
> ad714x.

there should already be a clear split between the bus and the driver
core, otherwise crap is being duplicated between the two interfaces.
i just tested splitting things and each bus code needs to know very
few things:
 - probe/remove for initialization
 - enable/disable for pm

the only downside i see here is that we have to remove
__devinit/__devexit markings from the common probe/remove functions
and those weigh a hefty 2406/218 bytes a piece.

> How about we still have one module, but let users to select which bus
> should be probed according to their boards.

we do that now in ad7879 and it's a bad idea -- the situation is worse
than what we have now with the ad714x as it means we cant have
multiple devices on different busses.
-mike
_______________________________________________
Uclinux-dist-devel mailing list
Uclinux-dist-devel@blackfin.uclinux.org
https://blackfin.uclinux.org/mailman/listinfo/uclinux-dist-devel

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v3] add analog devices AD714X captouchinput driver
       [not found]                               ` <8bd0f97a0910101338y46cb227hdc6d83c3c6622a21-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
@ 2009-10-12 17:08                                 ` Mike Frysinger
       [not found]                                   ` <8bd0f97a0910121008l1563803bu766e7d0868254a9f-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
  0 siblings, 1 reply; 21+ messages in thread
From: Mike Frysinger @ 2009-10-12 17:08 UTC (permalink / raw)
  To: Barry Song
  Cc: David Brownell,
	uclinux-dist-devel-ZG0+EudsQA8dtHy/vicBwGD2FQJk+8+b,
	Dmitry Torokhov, Hennerich, Michael,
	linux-input-u79uwXL29TY76Z2rM5mHXA

On Sat, Oct 10, 2009 at 16:38, Mike Frysinger wrote:
> On Fri, Oct 9, 2009 at 22:24, Barry Song wrote:
>> On Sat, Oct 10, 2009 at 12:22 AM, Dmitry Torokhov wrote:
>>> On Fri, Oct 09, 2009 at 06:11:45AM -0400, Mike Frysinger wrote:
>>>> On Fri, Oct 9, 2009 at 05:18, Mike Frysinger <vapier.adi@gmail.com> wrote:
>>>> > we could have three modules here: ad714x, ad714x-i2c, ad714x-spi
>>>>
>>>> are you ok with this approach Dmitry ?  this way people can
>>>> load/unload a specific bus without affecting the core or the other
>>>> bus, and errors with loading one bus type wouldnt affect loading of
>>>> the other.
>>>
>>> Yes, I think this is the most clean way.
>>
>> That will decrease the cohesion and increase coupling. It's tedious
>> for one driver with single feature to have three modules. To fulfill
>> that, ad714x need to expose data structures and export symbols to
>> ad714x-i2c and ad714x-spi. But those data structures and symbols are
>> more like local stuff. And I2C and SPI are just bottom level
>> communication ways, they should be the bottom level of ad714x, but
>> after splitting to 3 modules, it seems they become the top level of
>> ad714x.
>
> there should already be a clear split between the bus and the driver
> core, otherwise crap is being duplicated between the two interfaces.
> i just tested splitting things and each bus code needs to know very
> few things:
>  - probe/remove for initialization
>  - enable/disable for pm
>
> the only downside i see here is that we have to remove
> __devinit/__devexit markings from the common probe/remove functions
> and those weigh a hefty 2406/218 bytes a piece.

i spent time packing common stuff in the probe code and cut it down by
~1k, so i'm fine with this little bit of overhead.

Barry: can you test out the latest ad714x driver and make sure i didnt
screw anything up ?  i dont have any hardware to actually test these
guys out ...
-mike
_______________________________________________
Uclinux-dist-devel mailing list
Uclinux-dist-devel@blackfin.uclinux.org
https://blackfin.uclinux.org/mailman/listinfo/uclinux-dist-devel

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v3] add analog devices AD714X captouchinput driver
       [not found]                                   ` <8bd0f97a0910121008l1563803bu766e7d0868254a9f-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
@ 2009-10-13  3:05                                     ` Barry Song
       [not found]                                       ` <3c17e3570910122005j16c5e0fcgc542172ca9c4a0d2-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
  0 siblings, 1 reply; 21+ messages in thread
From: Barry Song @ 2009-10-13  3:05 UTC (permalink / raw)
  To: Mike Frysinger
  Cc: David Brownell,
	uclinux-dist-devel-ZG0+EudsQA8dtHy/vicBwGD2FQJk+8+b,
	Dmitry Torokhov, Hennerich, Michael,
	linux-input-u79uwXL29TY76Z2rM5mHXA

On Tue, Oct 13, 2009 at 1:08 AM, Mike Frysinger <vapier.adi@gmail.com> wrote:
> On Sat, Oct 10, 2009 at 16:38, Mike Frysinger wrote:
>> On Fri, Oct 9, 2009 at 22:24, Barry Song wrote:
>>> On Sat, Oct 10, 2009 at 12:22 AM, Dmitry Torokhov wrote:
>>>> On Fri, Oct 09, 2009 at 06:11:45AM -0400, Mike Frysinger wrote:
>>>>> On Fri, Oct 9, 2009 at 05:18, Mike Frysinger <vapier.adi@gmail.com> wrote:
>>>>> > we could have three modules here: ad714x, ad714x-i2c, ad714x-spi
>>>>>
>>>>> are you ok with this approach Dmitry ?  this way people can
>>>>> load/unload a specific bus without affecting the core or the other
>>>>> bus, and errors with loading one bus type wouldnt affect loading of
>>>>> the other.
>>>>
>>>> Yes, I think this is the most clean way.
>>>
>>> That will decrease the cohesion and increase coupling. It's tedious
>>> for one driver with single feature to have three modules. To fulfill
>>> that, ad714x need to expose data structures and export symbols to
>>> ad714x-i2c and ad714x-spi. But those data structures and symbols are
>>> more like local stuff. And I2C and SPI are just bottom level
>>> communication ways, they should be the bottom level of ad714x, but
>>> after splitting to 3 modules, it seems they become the top level of
>>> ad714x.
>>
>> there should already be a clear split between the bus and the driver
>> core, otherwise crap is being duplicated between the two interfaces.
>> i just tested splitting things and each bus code needs to know very
>> few things:
>>  - probe/remove for initialization
>>  - enable/disable for pm
>>
>> the only downside i see here is that we have to remove
>> __devinit/__devexit markings from the common probe/remove functions
>> and those weigh a hefty 2406/218 bytes a piece.
>
> i spent time packing common stuff in the probe code and cut it down by
> ~1k, so i'm fine with this little bit of overhead.
>
> Barry: can you test out the latest ad714x driver and make sure i didnt
> screw anything up ?  i dont have any hardware to actually test these
> guys out ...
It can't work now. I will figure out the problems and fix it.
> -mike
>
_______________________________________________
Uclinux-dist-devel mailing list
Uclinux-dist-devel@blackfin.uclinux.org
https://blackfin.uclinux.org/mailman/listinfo/uclinux-dist-devel

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v3] add analog devices AD714X captouchinput driver
       [not found]                                       ` <3c17e3570910122005j16c5e0fcgc542172ca9c4a0d2-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
@ 2009-10-13  4:26                                         ` Barry Song
  0 siblings, 0 replies; 21+ messages in thread
From: Barry Song @ 2009-10-13  4:26 UTC (permalink / raw)
  To: Mike Frysinger
  Cc: David Brownell,
	uclinux-dist-devel-ZG0+EudsQA8dtHy/vicBwGD2FQJk+8+b,
	Dmitry Torokhov, Hennerich, Michael,
	linux-input-u79uwXL29TY76Z2rM5mHXA

On Tue, Oct 13, 2009 at 11:05 AM, Barry Song <21cnbao@gmail.com> wrote:
> On Tue, Oct 13, 2009 at 1:08 AM, Mike Frysinger <vapier.adi@gmail.com> wrote:
>> On Sat, Oct 10, 2009 at 16:38, Mike Frysinger wrote:
>>> On Fri, Oct 9, 2009 at 22:24, Barry Song wrote:
>>>> On Sat, Oct 10, 2009 at 12:22 AM, Dmitry Torokhov wrote:
>>>>> On Fri, Oct 09, 2009 at 06:11:45AM -0400, Mike Frysinger wrote:
>>>>>> On Fri, Oct 9, 2009 at 05:18, Mike Frysinger <vapier.adi@gmail.com> wrote:
>>>>>> > we could have three modules here: ad714x, ad714x-i2c, ad714x-spi
>>>>>>
>>>>>> are you ok with this approach Dmitry ?  this way people can
>>>>>> load/unload a specific bus without affecting the core or the other
>>>>>> bus, and errors with loading one bus type wouldnt affect loading of
>>>>>> the other.
>>>>>
>>>>> Yes, I think this is the most clean way.
>>>>
>>>> That will decrease the cohesion and increase coupling. It's tedious
>>>> for one driver with single feature to have three modules. To fulfill
>>>> that, ad714x need to expose data structures and export symbols to
>>>> ad714x-i2c and ad714x-spi. But those data structures and symbols are
>>>> more like local stuff. And I2C and SPI are just bottom level
>>>> communication ways, they should be the bottom level of ad714x, but
>>>> after splitting to 3 modules, it seems they become the top level of
>>>> ad714x.
>>>
>>> there should already be a clear split between the bus and the driver
>>> core, otherwise crap is being duplicated between the two interfaces.
>>> i just tested splitting things and each bus code needs to know very
>>> few things:
>>>  - probe/remove for initialization
>>>  - enable/disable for pm
>>>
>>> the only downside i see here is that we have to remove
>>> __devinit/__devexit markings from the common probe/remove functions
>>> and those weigh a hefty 2406/218 bytes a piece.
>>
>> i spent time packing common stuff in the probe code and cut it down by
>> ~1k, so i'm fine with this little bit of overhead.
>>
>> Barry: can you test out the latest ad714x driver and make sure i didnt
>> screw anything up ?  i dont have any hardware to actually test these
>> guys out ...
> It can't work now. I will figure out the problems and fix it.
Done.
>> -mike
>>
>
_______________________________________________
Uclinux-dist-devel mailing list
Uclinux-dist-devel@blackfin.uclinux.org
https://blackfin.uclinux.org/mailman/listinfo/uclinux-dist-devel

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v4] input/misc: add Analog Devices AD714x captouch input driver
       [not found] ` <1252652006-5270-1-git-send-email-21cnbao-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
@ 2009-10-13  6:02   ` Mike Frysinger
  2009-10-20  8:37     ` [PATCH v5] " Mike Frysinger
  0 siblings, 1 reply; 21+ messages in thread
From: Mike Frysinger @ 2009-10-13  6:02 UTC (permalink / raw)
  To: linux-input-u79uwXL29TY76Z2rM5mHXA, Dmitry Torokhov
  Cc: uclinux-dist-devel-ZG0+EudsQA8dtHy/vicBwGD2FQJk+8+b, Michael Hennerich

From: Bryan Wu <cooloney-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>

AD7142 and AD7147 are integrated capacitance-to-digital converters
(CDCs) with on-chip environmental calibration for use in systems
requiring a novel user input method. The AD7142 and AD7147 can interface
to external capacitance sensors implementing functions such as buttons,
scrollwheels, sliders, touchpads and so on.

The chips don't restrict the specific usage. Depending on the hardware
connection, one special target board can include one or several these
components. The platform_data for the device's "struct device" holds
these information. The data-struct defined in head file descript the
hardware feature of button/scrollwheel/slider/touchpad components on
target boards, which need be filled in the arch/mach-/.

As the result, the driver is independent of boards. It gets the
components layout from the platform_data, registers related devices,
fullfills the algorithms and state machines for these components and
report related input events to up level.

Signed-off-by: Bryan Wu <cooloney-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
Signed-off-by: Michael Hennerich <michael.hennerich-OyLXuOCK7orQT0dZR+AlfA@public.gmane.org>
Signed-off-by: Barry Song <21cnbao-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
Signed-off-by: Mike Frysinger <vapier-aBrp7R+bbdUdnm+yROfE0A@public.gmane.org>
---
v4
	- unify common code between busses
	- simplify/cleanup common probe/remove code
	- split bus code into dedicated modules

 drivers/input/misc/Kconfig      |   30 +
 drivers/input/misc/Makefile     |    3 +
 drivers/input/misc/ad714x-i2c.c |  134 ++++
 drivers/input/misc/ad714x-spi.c |   96 +++
 drivers/input/misc/ad714x.c     | 1317 +++++++++++++++++++++++++++++++++++++++
 drivers/input/misc/ad714x.h     |   25 +
 include/linux/input.h           |    1 +
 include/linux/input/ad714x.h    |   63 ++
 8 files changed, 1669 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/misc/ad714x-i2c.c
 create mode 100644 drivers/input/misc/ad714x-spi.c
 create mode 100644 drivers/input/misc/ad714x.c
 create mode 100644 drivers/input/misc/ad714x.h
 create mode 100644 include/linux/input/ad714x.h

diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 02f4f8f..fc4b0b9 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -316,4 +316,34 @@ config INPUT_PCAP
 	  To compile this driver as a module, choose M here: the
 	  module will be called pcap_keys.
 
+config INPUT_AD714X
+	tristate "Analog Devices AD714x Capacitance Touch Sensor"
+	help
+	  Say Y here if you want to support an AD7142/AD7147 touch sensor.
+
+	  You should select a bus connection too.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called ad714x.
+
+config INPUT_AD714X_I2C
+	tristate "support I2C bus connection"
+	depends on INPUT_AD714X && I2C
+	default y
+	help
+	  Say Y here if you have AD7142/AD7147 hooked to an I2C bus.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called ad714x-i2c.
+
+config INPUT_AD714X_SPI
+	tristate "support SPI bus connection"
+	depends on INPUT_AD714X && SPI
+	default y
+	help
+	  Say Y here if you have AD7142/AD7147 hooked to a SPI bus.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called ad714x-spi.
+
 endif
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index a8b8485..16db64a 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -4,6 +4,9 @@
 
 # Each configuration option enables a list of files.
 
+obj-$(CONFIG_INPUT_AD714X)		+= ad714x.o
+obj-$(CONFIG_INPUT_AD714X_I2C)		+= ad714x-i2c.o
+obj-$(CONFIG_INPUT_AD714X_SPI)		+= ad714x-spi.o
 obj-$(CONFIG_INPUT_APANEL)		+= apanel.o
 obj-$(CONFIG_INPUT_ATI_REMOTE)		+= ati_remote.o
 obj-$(CONFIG_INPUT_ATI_REMOTE2)		+= ati_remote2.o
diff --git a/drivers/input/misc/ad714x-i2c.c b/drivers/input/misc/ad714x-i2c.c
new file mode 100644
index 0000000..596c199
--- /dev/null
+++ b/drivers/input/misc/ad714x-i2c.c
@@ -0,0 +1,134 @@
+/*
+ * AD714X CapTouch Programmable Controller driver (I2C bus)
+ *
+ * Copyright 2009 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/input.h>	/* BUS_I2C */
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include "ad714x.h"
+
+#ifdef CONFIG_PM
+static int ad714x_i2c_suspend(struct i2c_client *client, pm_message_t message)
+{
+	return ad714x_disable(i2c_get_clientdata(client));
+}
+
+static int ad714x_i2c_resume(struct i2c_client *client)
+{
+	return ad714x_enable(i2c_get_clientdata(client));
+}
+#else
+# define ad714x_i2c_suspend NULL
+# define ad714x_i2c_resume  NULL
+#endif
+
+static int ad714x_i2c_write(struct device *dev, unsigned short reg,
+		unsigned short data)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	int ret = 0;
+	u8 *_reg = (u8 *)&reg;
+	u8 *_data = (u8 *)&data;
+
+	u8 tx[4] = {
+		_reg[1],
+		_reg[0],
+		_data[1],
+		_data[0]
+	};
+
+	ret = i2c_master_send(client, tx, 4);
+	if (ret < 0)
+		dev_err(&client->dev, "I2C write error\n");
+
+	return ret;
+}
+
+static int ad714x_i2c_read(struct device *dev, unsigned short reg,
+		unsigned short *data)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	int ret = 0;
+	u8 *_reg = (u8 *)&reg;
+	u8 *_data = (u8 *)data;
+
+	u8 tx[2] = {
+		_reg[1],
+		_reg[0]
+	};
+	u8 rx[2];
+
+	ret = i2c_master_send(client, tx, 2);
+	if (ret < 0) {
+		dev_err(&client->dev, "I2C read error\n");
+		return ret;
+	}
+
+	ret = i2c_master_recv(client, rx, 2);
+	if (ret < 0) {
+		dev_err(&client->dev, "I2C read error\n");
+		return ret;
+	}
+
+	_data[0] = rx[1];
+	_data[1] = rx[0];
+
+	return ret;
+}
+
+static int __devinit ad714x_i2c_probe(struct i2c_client *client,
+		const struct i2c_device_id *id)
+{
+	int ret = 0;
+	struct ad714x_chip *chip = NULL;
+
+	ret = ad714x_probe(&chip, &client->dev, BUS_I2C, client->irq,
+		ad714x_i2c_read, ad714x_i2c_write);
+	i2c_set_clientdata(client, chip);
+
+	return ret;
+}
+
+static int __devexit ad714x_i2c_remove(struct i2c_client *client)
+{
+	return ad714x_remove(i2c_get_clientdata(client));
+}
+
+static const struct i2c_device_id ad714x_id[] = {
+	{ "ad7142_captouch", 0 },
+	{ "ad7147_captouch", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, ad714x_id);
+
+static struct i2c_driver ad714x_i2c_driver = {
+	.driver = {
+		.name = "ad714x_captouch",
+	},
+	.probe    = ad714x_i2c_probe,
+	.remove   = __devexit_p(ad714x_i2c_remove),
+	.suspend  = ad714x_i2c_suspend,
+	.resume	  = ad714x_i2c_resume,
+	.id_table = ad714x_id,
+};
+
+static __init int ad714x_i2c_init(void)
+{
+	return i2c_add_driver(&ad714x_i2c_driver);
+}
+module_init(ad714x_i2c_init);
+
+static __exit void ad714x_i2c_exit(void)
+{
+	i2c_del_driver(&ad714x_i2c_driver);
+}
+module_exit(ad714x_i2c_exit);
+
+MODULE_DESCRIPTION("Analog Devices AD714X Capacitance Touch Sensor I2C Bus Driver");
+MODULE_AUTHOR("Barry Song <21cnbao-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/ad714x-spi.c b/drivers/input/misc/ad714x-spi.c
new file mode 100644
index 0000000..2cea1e4
--- /dev/null
+++ b/drivers/input/misc/ad714x-spi.c
@@ -0,0 +1,96 @@
+/*
+ * AD714X CapTouch Programmable Controller driver (SPI bus)
+ *
+ * Copyright 2009 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/input.h>	/* BUS_I2C */
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+#include <linux/types.h>
+#include "ad714x.h"
+
+#define AD714x_SPI_CMD_PREFIX      0xE000   /* bits 15:11 */
+#define AD714x_SPI_READ            BIT(10)
+
+#ifdef CONFIG_PM
+static int ad714x_spi_suspend(struct spi_device *spi, pm_message_t message)
+{
+	return ad714x_disable(spi_get_drvdata(spi));
+}
+
+static int ad714x_spi_resume(struct spi_device *spi)
+{
+	return ad714x_enable(spi_get_drvdata(spi));
+}
+#else
+# define ad714x_spi_suspend NULL
+# define ad714x_spi_resume  NULL
+#endif
+
+static int ad714x_spi_read(struct device *dev, unsigned short reg,
+		unsigned short *data)
+{
+	struct spi_device *spi = to_spi_device(dev);
+	unsigned short tx = AD714x_SPI_CMD_PREFIX | AD714x_SPI_READ | reg;
+
+	return spi_write_then_read(spi, (u8 *)&tx, 2, (u8 *)data, 2);
+}
+
+static int ad714x_spi_write(struct device *dev, unsigned short reg,
+		unsigned short data)
+{
+	struct spi_device *spi = to_spi_device(dev);
+	unsigned short tx[2] = {
+		AD714x_SPI_CMD_PREFIX | reg,
+		data
+	};
+
+	return spi_write(spi, (u8 *)tx, 4);
+}
+
+static int __devinit ad714x_spi_probe(struct spi_device *spi)
+{
+	int ret;
+	struct ad714x_chip *chip = NULL;
+
+	ret = ad714x_probe(&chip, &spi->dev, BUS_SPI, spi->irq,
+		ad714x_spi_read, ad714x_spi_write);
+	spi_set_drvdata(spi, chip);
+
+	return ret;
+}
+
+static int __devexit ad714x_spi_remove(struct spi_device *spi)
+{
+	return ad714x_remove(spi_get_drvdata(spi));
+}
+
+static struct spi_driver ad714x_spi_driver = {
+	.driver = {
+		.name	= "ad714x_captouch",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= ad714x_spi_probe,
+	.remove		= __devexit_p(ad714x_spi_remove),
+	.suspend	= ad714x_spi_suspend,
+	.resume		= ad714x_spi_resume,
+};
+
+static __init int ad714x_spi_init(void)
+{
+	return spi_register_driver(&ad714x_spi_driver);
+}
+module_init(ad714x_spi_init);
+
+static __exit void ad714x_spi_exit(void)
+{
+	spi_unregister_driver(&ad714x_spi_driver);
+}
+module_exit(ad714x_spi_exit);
+
+MODULE_DESCRIPTION("Analog Devices AD714X Capacitance Touch Sensor SPI Bus Driver");
+MODULE_AUTHOR("Barry Song <21cnbao-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/ad714x.c b/drivers/input/misc/ad714x.c
new file mode 100644
index 0000000..bc4f042
--- /dev/null
+++ b/drivers/input/misc/ad714x.c
@@ -0,0 +1,1317 @@
+/*
+ * AD714X CapTouch Programmable Controller driver
+ *
+ * Copyright 2009 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/input/ad714x.h>
+#include "ad714x.h"
+
+#define AD714X_PWR_CTRL           0x0
+#define AD714X_STG_CAL_EN_REG     0x1
+#define AD714X_AMB_COMP_CTRL0_REG 0x2
+#define AD714X_PARTID_REG         0x17
+#define AD7147_PARTID             0x1470
+#define AD7142_PARTID             0xE620
+#define AD714X_STAGECFG_REG       0x80
+#define AD714X_SYSCFG_REG         0x0
+
+#define STG_LOW_INT_EN_REG     0x5
+#define STG_HIGH_INT_EN_REG    0x6
+#define STG_COM_INT_EN_REG     0x7
+#define STG_LOW_INT_STA_REG    0x8
+#define STG_HIGH_INT_STA_REG   0x9
+#define STG_COM_INT_STA_REG    0xA
+
+#define CDC_RESULT_S0          0xB
+#define CDC_RESULT_S1          0xC
+#define CDC_RESULT_S2          0xD
+#define CDC_RESULT_S3          0xE
+#define CDC_RESULT_S4          0xF
+#define CDC_RESULT_S5          0x10
+#define CDC_RESULT_S6          0x11
+#define CDC_RESULT_S7          0x12
+#define CDC_RESULT_S8          0x13
+#define CDC_RESULT_S9          0x14
+#define CDC_RESULT_S10         0x15
+#define CDC_RESULT_S11         0x16
+
+#define STAGE0_AMBIENT		0xF1
+#define STAGE1_AMBIENT		0x115
+#define STAGE2_AMBIENT		0x139
+#define STAGE3_AMBIENT		0x15D
+#define STAGE4_AMBIENT		0x181
+#define STAGE5_AMBIENT		0x1A5
+#define STAGE6_AMBIENT		0x1C9
+#define STAGE7_AMBIENT		0x1ED
+#define STAGE8_AMBIENT		0x211
+#define STAGE9_AMBIENT		0x234
+#define STAGE10_AMBIENT		0x259
+#define STAGE11_AMBIENT		0x27D
+
+#define PER_STAGE_REG_NUM      36
+#define STAGE_NUM              12
+#define STAGE_CFGREG_NUM       8
+#define SYS_CFGREG_NUM         8
+
+/*
+ * driver information which will be used to maintain the software flow
+ */
+typedef enum {IDLE, JITTER, ACTIVE, SPACE} ad714x_device_state;
+
+struct ad714x_slider_drv {
+	int highest_stage;
+	int abs_pos;
+	int flt_pos;
+	ad714x_device_state state;
+	struct input_dev *input;
+};
+
+struct ad714x_wheel_drv {
+	int abs_pos;
+	int flt_pos;
+	int pre_mean_value;
+	int pre_highest_stage;
+	int pre_mean_value_no_offset;
+	int mean_value;
+	int mean_value_no_offset;
+	int pos_offset;
+	int pos_ratio;
+	int highest_stage;
+	ad714x_device_state state;
+	struct input_dev *input;
+};
+
+struct ad714x_touchpad_drv {
+	int x_highest_stage;
+	int x_flt_pos;
+	int x_abs_pos;
+	int y_highest_stage;
+	int y_flt_pos;
+	int y_abs_pos;
+	int left_ep;
+	int left_ep_val;
+	int right_ep;
+	int right_ep_val;
+	int top_ep;
+	int top_ep_val;
+	int bottom_ep;
+	int bottom_ep_val;
+	ad714x_device_state state;
+	struct input_dev *input;
+};
+
+struct ad714x_button_drv {
+	ad714x_device_state state;
+	/* Unlike slider/wheel/touchpad, all buttons point to
+	 * same input_dev instance
+	 */
+	struct input_dev *input;
+};
+
+struct ad714x_driver_data {
+	struct ad714x_slider_drv *slider;
+	struct ad714x_wheel_drv *wheel;
+	struct ad714x_touchpad_drv *touchpad;
+	struct ad714x_button_drv *button;
+};
+
+/* information to integrate all things which will be private data
+ * of spi/i2c device
+ */
+struct ad714x_chip {
+	unsigned short h_state;
+	unsigned short l_state;
+	unsigned short c_state;
+	unsigned short adc_reg[STAGE_NUM];
+	unsigned short amb_reg[STAGE_NUM];
+	unsigned short sensor_val[STAGE_NUM];
+
+	struct ad714x_platform_data *hw;
+	struct ad714x_driver_data *sw;
+
+	int irq;
+	struct device *dev;
+	ad714x_read_t *read;
+	ad714x_write_t *write;
+
+	struct mutex mutex;
+
+	unsigned product;
+	unsigned version;
+};
+
+static void ad714x_use_com_int(struct ad714x_chip *ad714x, int start_stage,
+		int end_stage)
+{
+	unsigned short data;
+	unsigned short mask;
+
+	mask = ((1 << (end_stage + 1)) - 1) - (1 << start_stage);
+
+	ad714x->read(ad714x->dev, STG_COM_INT_EN_REG, &data);
+	data |= 1 << start_stage;
+	ad714x->write(ad714x->dev, STG_COM_INT_EN_REG, data);
+
+	ad714x->read(ad714x->dev, STG_HIGH_INT_EN_REG, &data);
+	data &= ~mask;
+	ad714x->write(ad714x->dev, STG_HIGH_INT_EN_REG, data);
+}
+
+static void ad714x_use_thr_int(struct ad714x_chip *ad714x, int start_stage,
+		int end_stage)
+{
+	unsigned short data;
+	unsigned short mask;
+
+	mask = ((1 << (end_stage + 1)) - 1) - (1 << start_stage);
+
+	ad714x->read(ad714x->dev, STG_COM_INT_EN_REG, &data);
+	data &= ~(1 << start_stage);
+	ad714x->write(ad714x->dev, STG_COM_INT_EN_REG, data);
+
+	ad714x->read(ad714x->dev, STG_HIGH_INT_EN_REG, &data);
+	data |= mask;
+	ad714x->write(ad714x->dev, STG_HIGH_INT_EN_REG, data);
+}
+
+static int ad714x_cal_highest_stage(struct ad714x_chip *ad714x, int start_stage,
+		int end_stage)
+{
+	int max_res = 0;
+	int max_idx = 0;
+	int i;
+
+	for (i = start_stage; i <= end_stage; i++) {
+		if (ad714x->sensor_val[i] > max_res) {
+			max_res = ad714x->sensor_val[i];
+			max_idx = i;
+		}
+	}
+
+	return max_idx;
+}
+
+static int ad714x_cal_abs_pos(struct ad714x_chip *ad714x, int start_stage,
+		int end_stage, int highest_stage, int max_coord)
+{
+	int a_param, b_param;
+
+	if (highest_stage == start_stage) {
+		a_param = ad714x->sensor_val[start_stage + 1];
+		b_param = ad714x->sensor_val[start_stage] +
+			ad714x->sensor_val[start_stage + 1];
+	} else if (highest_stage == end_stage) {
+		a_param = ad714x->sensor_val[end_stage] *
+			(end_stage - start_stage) +
+			ad714x->sensor_val[end_stage - 1] *
+			(end_stage - start_stage - 1);
+		b_param = ad714x->sensor_val[end_stage] +
+			ad714x->sensor_val[end_stage - 1];
+	} else {
+		a_param = ad714x->sensor_val[highest_stage] *
+			(highest_stage - start_stage) +
+			ad714x->sensor_val[highest_stage - 1] *
+			(highest_stage - start_stage - 1) +
+			ad714x->sensor_val[highest_stage + 1] *
+			(highest_stage - start_stage + 1);
+		b_param = ad714x->sensor_val[highest_stage] +
+			ad714x->sensor_val[highest_stage - 1] +
+			ad714x->sensor_val[highest_stage + 1];
+	}
+
+	return (max_coord / (end_stage - start_stage)) * a_param / b_param;
+}
+
+
+/* One button can connect to multi positive and negative of CDCs
+ * Multi-buttons can connect to same positive/negative of one CDC
+ */
+static void ad714x_button_state_machine(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_button_plat *hw = &ad714x->hw->button[idx];
+	struct ad714x_button_drv *sw = &ad714x->sw->button[idx];
+
+	switch (sw->state) {
+	case IDLE:
+		if (((ad714x->h_state & hw->h_mask) == hw->h_mask) &&
+			((ad714x->l_state & hw->l_mask) == hw->l_mask)) {
+			dev_dbg(ad714x->dev, "button %d touched\n", idx);
+			input_report_key(sw->input, hw->keycode, 1);
+			input_sync(sw->input);
+			sw->state = ACTIVE;
+		}
+		break;
+	case ACTIVE:
+		if (((ad714x->h_state & hw->h_mask) != hw->h_mask) ||
+			((ad714x->l_state & hw->l_mask) != hw->l_mask)) {
+			dev_dbg(ad714x->dev, "button %d released\n", idx);
+			input_report_key(sw->input, hw->keycode, 0);
+			input_sync(sw->input);
+			sw->state = IDLE;
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+/* The response of a sensor is defined by the absolute number of codes
+ * between the current CDC value and the ambient value.
+ */
+void ad714x_slider_cal_sensor_val(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx];
+	int i;
+
+	for (i = hw->start_stage; i <= hw->end_stage; i++) {
+		ad714x->read(ad714x->dev, CDC_RESULT_S0 + i,
+			&ad714x->adc_reg[i]);
+		ad714x->read(ad714x->dev,
+				STAGE0_AMBIENT + i * PER_STAGE_REG_NUM,
+				&ad714x->amb_reg[i]);
+
+		ad714x->sensor_val[i] = abs(ad714x->adc_reg[i] -
+				ad714x->amb_reg[i]);
+	}
+}
+
+void ad714x_slider_cal_highest_stage(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx];
+	struct ad714x_slider_drv *sw = &ad714x->sw->slider[idx];
+
+	sw->highest_stage = ad714x_cal_highest_stage(ad714x, hw->start_stage,
+			hw->end_stage);
+
+	dev_dbg(ad714x->dev, "slider %d highest_stage:%d\n", idx,
+		sw->highest_stage);
+}
+
+/* The formulae are very straight forward. It uses the sensor with the
+ * highest response and the 2 adjacent ones.
+ * When Sensor 0 has the highest response, only sensor 0 and sensor 1
+ * are used in the calculations. Similarly when the last sensor has the
+ * highest response, only the last sensor and the second last sensors
+ * are used in the calculations.
+ *
+ * For i= idx_of_peak_Sensor-1 to i= idx_of_peak_Sensor+1
+ *         v += Sensor response(i)*i
+ *         w += Sensor response(i)
+ * POS=(Number_of_Positions_Wanted/(Number_of_Sensors_Used-1)) *(v/w)
+ */
+void ad714x_slider_cal_abs_pos(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx];
+	struct ad714x_slider_drv *sw = &ad714x->sw->slider[idx];
+
+	sw->abs_pos = ad714x_cal_abs_pos(ad714x, hw->start_stage, hw->end_stage,
+		sw->highest_stage, hw->max_coord);
+
+	dev_dbg(ad714x->dev, "slider %d absolute position:%d\n", idx,
+		sw->abs_pos);
+}
+
+/*
+ * To minimise the Impact of the noise on the algorithm, ADI developed a
+ * routine that filters the CDC results after they have been read by the
+ * host processor.
+ * The filter used is an Infinite Input Response(IIR) filter implemented
+ * in firmware and attenuates the noise on the CDC results after they've
+ * been read by the host processor.
+ * Filtered_CDC_result = (Filtered_CDC_result * (10 - Coefficient) +
+ * 				Latest_CDC_result * Coefficient)/10
+ */
+void ad714x_slider_cal_flt_pos(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_slider_drv *sw = &ad714x->sw->slider[idx];
+
+	sw->flt_pos = (sw->flt_pos * (10 - 4) +
+			sw->abs_pos * 4)/10;
+
+	dev_dbg(ad714x->dev, "slider %d filter position:%d\n", idx,
+		sw->flt_pos);
+}
+
+static void ad714x_slider_use_com_int(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx];
+	ad714x_use_com_int(ad714x, hw->start_stage, hw->end_stage);
+}
+
+static void ad714x_slider_use_thr_int(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx];
+	ad714x_use_thr_int(ad714x, hw->start_stage, hw->end_stage);
+}
+
+static void ad714x_slider_state_machine(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx];
+	struct ad714x_slider_drv *sw = &ad714x->sw->slider[idx];
+	unsigned short h_state, c_state;
+	unsigned short mask;
+
+	mask = ((1 << (hw->end_stage + 1)) - 1) - ((1 << hw->start_stage) - 1);
+
+	h_state = ad714x->h_state & mask;
+	c_state = ad714x->c_state & mask;
+
+	switch (sw->state) {
+	case IDLE:
+		if (h_state) {
+			sw->state = JITTER;
+			/* In End of Conversion interrupt mode, the AD714X
+			 * continuously generates hardware interrupts.
+			 */
+			ad714x_slider_use_com_int(ad714x, idx);
+			dev_dbg(ad714x->dev, "slider %d touched\n", idx);
+		}
+		break;
+	case JITTER:
+		if (c_state == mask) {
+			ad714x_slider_cal_sensor_val(ad714x, idx);
+			ad714x_slider_cal_highest_stage(ad714x, idx);
+			ad714x_slider_cal_abs_pos(ad714x, idx);
+			sw->flt_pos = sw->abs_pos;
+			sw->state = ACTIVE;
+		}
+		break;
+	case ACTIVE:
+		if (c_state == mask) {
+			if (h_state) {
+				ad714x_slider_cal_sensor_val(ad714x, idx);
+				ad714x_slider_cal_highest_stage(ad714x, idx);
+				ad714x_slider_cal_abs_pos(ad714x, idx);
+				ad714x_slider_cal_flt_pos(ad714x, idx);
+
+				input_report_abs(sw->input, ABS_X, sw->flt_pos);
+				input_report_key(sw->input, BTN_TOUCH, 1);
+			} else {
+				/* When the user lifts off the sensor, configure
+				 * the AD714X back to threshold interrupt mode.
+				 */
+				ad714x_slider_use_thr_int(ad714x, idx);
+				sw->state = IDLE;
+				input_report_key(sw->input, BTN_TOUCH, 0);
+				dev_dbg(ad714x->dev, "slider %d released\n",
+					idx);
+			}
+			input_sync(sw->input);
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+/* When the scroll wheel is activated, we compute the absolute position based
+ * on the sensor values. To calculate the position, we first determine the
+ * sensor that has the greatest response among the 8 sensors that constitutes
+ * the scrollwheel. Then we determined the 2 sensors on either sides of the
+ * sensor with the highest response and we apply weights to these sensors.
+ */
+static void ad714x_wheel_cal_highest_stage(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
+	struct ad714x_wheel_drv *sw = &ad714x->sw->wheel[idx];
+
+	sw->pre_highest_stage = sw->highest_stage;
+	sw->highest_stage = ad714x_cal_highest_stage(ad714x, hw->start_stage,
+			hw->end_stage);
+
+	dev_dbg(ad714x->dev, "wheel %d highest_stage:%d\n", idx,
+		sw->highest_stage);
+}
+
+static void ad714x_wheel_cal_sensor_val(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
+	int i;
+
+	for (i = hw->start_stage; i <= hw->end_stage; i++) {
+		ad714x->read(ad714x->dev, CDC_RESULT_S0 + i,
+			&ad714x->adc_reg[i]);
+		ad714x->read(ad714x->dev,
+				STAGE0_AMBIENT + i * PER_STAGE_REG_NUM,
+				&ad714x->amb_reg[i]);
+		if (ad714x->adc_reg[i] > ad714x->amb_reg[i])
+			ad714x->sensor_val[i] = ad714x->adc_reg[i] -
+				ad714x->amb_reg[i];
+		else
+			ad714x->sensor_val[i] = 0;
+	}
+}
+
+/* When the scroll wheel is activated, we compute the absolute position based
+ * on the sensor values. To calculate the position, we first determine the
+ * sensor that has the greatest response among the 8 sensors that constitutes
+ * the scrollwheel. Then we determined the 2 sensors on either sides of the
+ * sensor with the highest response and we apply weights to these sensors. The
+ * result of this computation gives us the mean value which defined by the
+ * following formula:
+ * For i= second_before_highest_stage to i= second_after_highest_stage
+ *         v += Sensor response(i)*WEIGHT*(i+3)
+ *         w += Sensor response(i)
+ * Mean_Value=v/w
+ * pos_on_scrollwheel = (Mean_Value - position_offset) / position_ratio
+ *
+ */
+
+#define WEIGHT_FACTOR 30
+/* This constant prevents the "PositionOffset" from reaching a big value */
+#define OFFSET_POSITION_CLAMP	120
+static void ad714x_wheel_cal_abs_pos(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
+	struct ad714x_wheel_drv *sw = &ad714x->sw->wheel[idx];
+	int stage_num = hw->end_stage - hw->start_stage + 1;
+	int second_before, first_before, highest, first_after, second_after;
+	int a_param, b_param;
+
+	/* Calculate Mean value */
+
+	second_before = (sw->highest_stage + stage_num - 2) % stage_num;
+	first_before = (sw->highest_stage + stage_num - 1) % stage_num;
+	highest = sw->highest_stage;
+	first_after = (sw->highest_stage + stage_num + 1) % stage_num;
+	second_after = (sw->highest_stage + stage_num + 2) % stage_num;
+
+	if (((sw->highest_stage - hw->start_stage) > 1) &&
+			((hw->end_stage - sw->highest_stage) > 1)) {
+		a_param = ad714x->sensor_val[second_before] *
+			(second_before - hw->start_stage + 3) +
+			ad714x->sensor_val[first_before] *
+			(second_before - hw->start_stage + 3) +
+			ad714x->sensor_val[highest] *
+			(second_before - hw->start_stage + 3) +
+			ad714x->sensor_val[first_after] *
+			(first_after - hw->start_stage + 3) +
+			ad714x->sensor_val[second_after] *
+			(second_after - hw->start_stage + 3);
+	} else {
+		a_param = ad714x->sensor_val[second_before] *
+			(second_before - hw->start_stage + 1) +
+			ad714x->sensor_val[first_before] *
+			(second_before - hw->start_stage + 2) +
+			ad714x->sensor_val[highest] *
+			(second_before - hw->start_stage + 3) +
+			ad714x->sensor_val[first_after] *
+			(first_after - hw->start_stage + 4) +
+			ad714x->sensor_val[second_after] *
+			(second_after - hw->start_stage + 5);
+	}
+	a_param *= WEIGHT_FACTOR;
+
+	b_param = ad714x->sensor_val[second_before] +
+		ad714x->sensor_val[first_before] +
+		ad714x->sensor_val[highest] +
+		ad714x->sensor_val[first_after] +
+		ad714x->sensor_val[second_after];
+
+	sw->pre_mean_value = sw->mean_value;
+	sw->mean_value = a_param / b_param;
+
+	/* Calculate the offset */
+
+	if ((sw->pre_highest_stage == hw->end_stage) &&
+			(sw->highest_stage == hw->start_stage))
+		sw->pos_offset = sw->mean_value;
+	else if ((sw->pre_highest_stage == hw->start_stage) &&
+			(sw->highest_stage == hw->end_stage))
+		sw->pos_offset = sw->pre_mean_value;
+	if (sw->pos_offset > OFFSET_POSITION_CLAMP)
+		sw->pos_offset = OFFSET_POSITION_CLAMP;
+
+	/* Calculate the mean value without the offset */
+
+	sw->pre_mean_value_no_offset = sw->mean_value_no_offset;
+	sw->mean_value_no_offset = sw->mean_value - sw->pos_offset;
+	if (sw->mean_value_no_offset < 0)
+		sw->mean_value_no_offset = 0;
+
+	/* Calculate ratio to scale down to NUMBER_OF_WANTED_POSITIONS */
+
+	if ((sw->pre_highest_stage == hw->end_stage) &&
+			(sw->highest_stage == hw->start_stage))
+		sw->pos_ratio = (sw->pre_mean_value_no_offset * 100) /
+			hw->max_coord;
+	else if ((sw->pre_highest_stage == hw->start_stage) &&
+			(sw->highest_stage == hw->end_stage))
+		sw->pos_ratio = (sw->mean_value_no_offset * 100) /
+			hw->max_coord;
+	sw->abs_pos = (sw->mean_value_no_offset * 100) / sw->pos_ratio;
+	if (sw->abs_pos > hw->max_coord)
+		sw->abs_pos = hw->max_coord;
+}
+
+static void ad714x_wheel_cal_flt_pos(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
+	struct ad714x_wheel_drv *sw = &ad714x->sw->wheel[idx];
+	if (((sw->pre_highest_stage == hw->end_stage) &&
+				(sw->highest_stage == hw->start_stage)) ||
+			((sw->pre_highest_stage == hw->start_stage) &&
+			 (sw->highest_stage == hw->end_stage)))
+		sw->flt_pos = sw->abs_pos;
+	else
+		sw->flt_pos = ((sw->flt_pos * 30) + (sw->abs_pos * 71)) / 100;
+
+	if (sw->flt_pos > hw->max_coord)
+		sw->flt_pos = hw->max_coord;
+}
+
+static void ad714x_wheel_use_com_int(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
+	ad714x_use_com_int(ad714x, hw->start_stage, hw->end_stage);
+}
+
+static void ad714x_wheel_use_thr_int(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
+	ad714x_use_thr_int(ad714x, hw->start_stage, hw->end_stage);
+}
+
+static void ad714x_wheel_state_machine(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
+	struct ad714x_wheel_drv *sw = &ad714x->sw->wheel[idx];
+	unsigned short h_state, c_state;
+	unsigned short mask;
+
+	mask = ((1 << (hw->end_stage + 1)) - 1) - ((1 << hw->start_stage) - 1);
+
+	h_state = ad714x->h_state & mask;
+	c_state = ad714x->c_state & mask;
+
+	switch (sw->state) {
+	case IDLE:
+		if (h_state) {
+			sw->state = JITTER;
+			/* In End of Conversion interrupt mode, the AD714X
+			 * continuously generates hardware interrupts.
+			 */
+			ad714x_wheel_use_com_int(ad714x, idx);
+			dev_dbg(ad714x->dev, "wheel %d touched\n", idx);
+		}
+		break;
+	case JITTER:
+		if (c_state == mask)	{
+			ad714x_wheel_cal_sensor_val(ad714x, idx);
+			ad714x_wheel_cal_highest_stage(ad714x, idx);
+			ad714x_wheel_cal_abs_pos(ad714x, idx);
+			sw->flt_pos = sw->abs_pos;
+			sw->state = ACTIVE;
+		}
+		break;
+	case ACTIVE:
+		if (c_state == mask) {
+			if (h_state) {
+				ad714x_wheel_cal_sensor_val(ad714x, idx);
+				ad714x_wheel_cal_highest_stage(ad714x, idx);
+				ad714x_wheel_cal_abs_pos(ad714x, idx);
+				ad714x_wheel_cal_flt_pos(ad714x, idx);
+
+				input_report_abs(sw->input, ABS_WHEEL,
+					sw->abs_pos);
+				input_report_key(sw->input, BTN_TOUCH, 1);
+			} else {
+				/* When the user lifts off the sensor, configure
+				 * the AD714X back to threshold interrupt mode.
+				 */
+				ad714x_wheel_use_thr_int(ad714x, idx);
+				sw->state = IDLE;
+				input_report_key(sw->input, BTN_TOUCH, 0);
+
+				dev_dbg(ad714x->dev, "wheel %d released\n",
+					idx);
+			}
+			input_sync(sw->input);
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+static void touchpad_cal_sensor_val(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
+	int i;
+
+	for (i = hw->x_start_stage; i <= hw->x_end_stage; i++) {
+		ad714x->read(ad714x->dev, CDC_RESULT_S0 + i,
+				&ad714x->adc_reg[i]);
+		ad714x->read(ad714x->dev,
+				STAGE0_AMBIENT + i * PER_STAGE_REG_NUM,
+				&ad714x->amb_reg[i]);
+		if (ad714x->adc_reg[i] > ad714x->amb_reg[i])
+			ad714x->sensor_val[i] = ad714x->adc_reg[i] -
+				ad714x->amb_reg[i];
+		else
+			ad714x->sensor_val[i] = 0;
+	}
+}
+
+static void touchpad_cal_highest_stage(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
+	struct ad714x_touchpad_drv *sw = &ad714x->sw->touchpad[idx];
+
+	sw->x_highest_stage = ad714x_cal_highest_stage(ad714x,
+		hw->x_start_stage, hw->x_end_stage);
+	sw->y_highest_stage = ad714x_cal_highest_stage(ad714x,
+		hw->y_start_stage, hw->y_end_stage);
+
+	dev_dbg(ad714x->dev,
+		"touchpad %d x_highest_stage:%d, y_highest_stage:%d\n",
+		idx, sw->x_highest_stage, sw->y_highest_stage);
+}
+
+/* If 2 fingers are touching the sensor then 2 peaks can be observed in the
+ * distribution.
+ * The arithmetic doesn't support to get absolute coordinates for multi-touch
+ * yet.
+ */
+static int touchpad_check_second_peak(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
+	struct ad714x_touchpad_drv *sw = &ad714x->sw->touchpad[idx];
+	int i;
+
+	for (i = hw->x_start_stage; i < sw->x_highest_stage; i++) {
+		if ((ad714x->sensor_val[i] - ad714x->sensor_val[i + 1])
+			> (ad714x->sensor_val[i + 1] / 10))
+			return 1;
+	}
+
+	for (i = sw->x_highest_stage; i < hw->x_end_stage; i++) {
+		if ((ad714x->sensor_val[i + 1] - ad714x->sensor_val[i])
+			> (ad714x->sensor_val[i] / 10))
+			return 1;
+	}
+
+	for (i = hw->y_start_stage; i < sw->y_highest_stage; i++) {
+		if ((ad714x->sensor_val[i] - ad714x->sensor_val[i + 1])
+			> (ad714x->sensor_val[i + 1] / 10))
+			return 1;
+	}
+
+	for (i = sw->y_highest_stage; i < hw->y_end_stage; i++) {
+		if ((ad714x->sensor_val[i + 1] - ad714x->sensor_val[i])
+			> (ad714x->sensor_val[i] / 10))
+			return 1;
+	}
+
+	return 0;
+}
+
+/* If only one finger is used to activate the touch pad then only 1 peak will be
+ * registered in the distribution. This peak and the 2 adjacent sensors will be
+ * used in the calculation of the absolute position. This will prevent hand
+ * shadows to affect the absolute position calculation.
+ */
+static void touchpad_cal_abs_pos(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
+	struct ad714x_touchpad_drv *sw = &ad714x->sw->touchpad[idx];
+
+	sw->x_abs_pos = ad714x_cal_abs_pos(ad714x, hw->x_start_stage,
+			hw->x_end_stage, sw->x_highest_stage, hw->x_max_coord);
+	sw->y_abs_pos = ad714x_cal_abs_pos(ad714x, hw->y_start_stage,
+			hw->y_end_stage, sw->y_highest_stage, hw->y_max_coord);
+
+	dev_dbg(ad714x->dev, "touchpad %d absolute position:(%d, %d)\n", idx,
+			sw->x_abs_pos, sw->y_abs_pos);
+}
+
+static void touchpad_cal_flt_pos(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_touchpad_drv *sw = &ad714x->sw->touchpad[idx];
+
+	sw->x_flt_pos = (sw->x_flt_pos * (10 - 4) +
+			sw->x_abs_pos * 4)/10;
+	sw->y_flt_pos = (sw->y_flt_pos * (10 - 4) +
+			sw->y_abs_pos * 4)/10;
+
+	dev_dbg(ad714x->dev, "touchpad %d filter position:(%d, %d)\n",
+			idx, sw->x_flt_pos, sw->y_flt_pos);
+}
+
+/* To prevent distortion from showing in the absolute position, it is
+ * necessary to detect the end points. When endpoints are detected, the
+ * driver stops updating the status variables with absolute positions.
+ * End points are detected on the 4 edges of the touchpad sensor. The
+ * method to detect them is the same for all 4.
+ * To detect the end points, the firmware computes the difference in
+ * percent between the sensor on the edge and the adjacent one. The
+ * difference is calculated in percent in order to make the end point
+ * detection independent of the pressure.
+ */
+
+#define LEFT_END_POINT_DETECTION_LEVEL                  550
+#define RIGHT_END_POINT_DETECTION_LEVEL                 750
+#define LEFT_RIGHT_END_POINT_DEAVTIVALION_LEVEL         850
+#define TOP_END_POINT_DETECTION_LEVEL                   550
+#define BOTTOM_END_POINT_DETECTION_LEVEL                950
+#define TOP_BOTTOM_END_POINT_DEAVTIVALION_LEVEL         700
+static int touchpad_check_endpoint(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
+	struct ad714x_touchpad_drv *sw  = &ad714x->sw->touchpad[idx];
+	int percent_sensor_diff;
+
+	/* left endpoint detect */
+	percent_sensor_diff = (ad714x->sensor_val[hw->x_start_stage] -
+			ad714x->sensor_val[hw->x_start_stage + 1]) * 100 /
+			ad714x->sensor_val[hw->x_start_stage + 1];
+	if (!sw->left_ep) {
+		if (percent_sensor_diff >= LEFT_END_POINT_DETECTION_LEVEL)  {
+			sw->left_ep = 1;
+			sw->left_ep_val =
+				ad714x->sensor_val[hw->x_start_stage + 1];
+		}
+	} else {
+		if ((percent_sensor_diff < LEFT_END_POINT_DETECTION_LEVEL) &&
+		(ad714x->sensor_val[hw->x_start_stage + 1] >
+		LEFT_RIGHT_END_POINT_DEAVTIVALION_LEVEL + sw->left_ep_val))
+			sw->left_ep = 0;
+	}
+
+	/* right endpoint detect */
+	percent_sensor_diff = (ad714x->sensor_val[hw->x_end_stage] -
+			ad714x->sensor_val[hw->x_end_stage - 1]) * 100 /
+			ad714x->sensor_val[hw->x_end_stage - 1];
+	if (!sw->right_ep) {
+		if (percent_sensor_diff >= RIGHT_END_POINT_DETECTION_LEVEL)  {
+			sw->right_ep = 1;
+			sw->right_ep_val =
+				ad714x->sensor_val[hw->x_end_stage - 1];
+		}
+	} else {
+		if ((percent_sensor_diff < RIGHT_END_POINT_DETECTION_LEVEL) &&
+		(ad714x->sensor_val[hw->x_end_stage - 1] >
+		LEFT_RIGHT_END_POINT_DEAVTIVALION_LEVEL + sw->right_ep_val))
+			sw->right_ep = 0;
+	}
+
+	/* top endpoint detect */
+	percent_sensor_diff = (ad714x->sensor_val[hw->y_start_stage] -
+			ad714x->sensor_val[hw->y_start_stage + 1]) * 100 /
+			ad714x->sensor_val[hw->y_start_stage + 1];
+	if (!sw->top_ep) {
+		if (percent_sensor_diff >= TOP_END_POINT_DETECTION_LEVEL)  {
+			sw->top_ep = 1;
+			sw->top_ep_val =
+				ad714x->sensor_val[hw->y_start_stage + 1];
+		}
+	} else {
+		if ((percent_sensor_diff < TOP_END_POINT_DETECTION_LEVEL) &&
+		(ad714x->sensor_val[hw->y_start_stage + 1] >
+		TOP_BOTTOM_END_POINT_DEAVTIVALION_LEVEL + sw->top_ep_val))
+			sw->top_ep = 0;
+	}
+
+	/* bottom endpoint detect */
+	percent_sensor_diff = (ad714x->sensor_val[hw->y_end_stage] -
+		ad714x->sensor_val[hw->y_end_stage - 1]) * 100 /
+		ad714x->sensor_val[hw->y_end_stage - 1];
+	if (!sw->bottom_ep) {
+		if (percent_sensor_diff >= BOTTOM_END_POINT_DETECTION_LEVEL)  {
+			sw->bottom_ep = 1;
+			sw->bottom_ep_val =
+				ad714x->sensor_val[hw->y_end_stage - 1];
+		}
+	} else {
+		if ((percent_sensor_diff < BOTTOM_END_POINT_DETECTION_LEVEL) &&
+		(ad714x->sensor_val[hw->y_end_stage - 1] >
+		 TOP_BOTTOM_END_POINT_DEAVTIVALION_LEVEL + sw->bottom_ep_val))
+			sw->bottom_ep = 0;
+	}
+
+	return sw->left_ep || sw->right_ep || sw->top_ep || sw->bottom_ep;
+}
+
+static void touchpad_use_com_int(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
+	ad714x_use_com_int(ad714x, hw->x_start_stage, hw->x_end_stage);
+}
+
+static void touchpad_use_thr_int(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
+	ad714x_use_thr_int(ad714x, hw->x_start_stage, hw->x_end_stage);
+	ad714x_use_thr_int(ad714x, hw->y_start_stage, hw->y_end_stage);
+}
+
+static void ad714x_touchpad_state_machine(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
+	struct ad714x_touchpad_drv *sw = &ad714x->sw->touchpad[idx];
+	unsigned short h_state, c_state;
+	unsigned short mask;
+
+	mask = (((1 << (hw->x_end_stage + 1)) - 1) -
+		((1 << hw->x_start_stage) - 1)) +
+		(((1 << (hw->y_end_stage + 1)) - 1) -
+		((1 << hw->y_start_stage) - 1));
+
+	h_state = ad714x->h_state & mask;
+	c_state = ad714x->c_state & mask;
+
+	switch (sw->state) {
+	case IDLE:
+		if (h_state) {
+			sw->state = JITTER;
+			/* In End of Conversion interrupt mode, the AD714X
+			 * continuously generates hardware interrupts.
+			 */
+			touchpad_use_com_int(ad714x, idx);
+			dev_dbg(ad714x->dev, "touchpad %d touched\n", idx);
+		}
+		break;
+	case JITTER:
+		if (c_state == mask) {
+			touchpad_cal_sensor_val(ad714x, idx);
+			touchpad_cal_highest_stage(ad714x, idx);
+			if ((!touchpad_check_second_peak(ad714x, idx)) &&
+				(!touchpad_check_endpoint(ad714x, idx))) {
+				dev_dbg(ad714x->dev,
+					"touchpad%d, 2 fingers or endpoint\n",
+					idx);
+				touchpad_cal_abs_pos(ad714x, idx);
+				sw->x_flt_pos = sw->x_abs_pos;
+				sw->y_flt_pos = sw->y_abs_pos;
+				sw->state = ACTIVE;
+			}
+		}
+		break;
+	case ACTIVE:
+		if (c_state == mask) {
+			if (h_state) {
+				touchpad_cal_sensor_val(ad714x, idx);
+				touchpad_cal_highest_stage(ad714x, idx);
+				if ((!touchpad_check_second_peak(ad714x, idx))
+				  && (!touchpad_check_endpoint(ad714x, idx))) {
+					touchpad_cal_abs_pos(ad714x, idx);
+					touchpad_cal_flt_pos(ad714x, idx);
+					input_report_abs(sw->input, ABS_X,
+						sw->x_flt_pos);
+					input_report_abs(sw->input, ABS_Y,
+						sw->y_flt_pos);
+					input_report_key(sw->input, BTN_TOUCH,
+						1);
+				}
+			} else {
+				/* When the user lifts off the sensor, configure
+				 * the AD714X back to threshold interrupt mode.
+				 */
+				touchpad_use_thr_int(ad714x, idx);
+				sw->state = IDLE;
+				input_report_key(sw->input, BTN_TOUCH, 0);
+				dev_dbg(ad714x->dev, "touchpad %d released\n",
+					idx);
+			}
+			input_sync(sw->input);
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+static int ad714x_hw_detect(struct ad714x_chip *ad714x)
+{
+	unsigned short data;
+
+	ad714x->read(ad714x->dev, AD714X_PARTID_REG, &data);
+	switch (data & 0xFFF0) {
+	case AD7147_PARTID:
+		ad714x->product = 0x7147;
+		ad714x->version = data & 0xF;
+		dev_info(ad714x->dev, "found AD7147 captouch, rev:%d\n",
+				ad714x->version);
+		return 0;
+	case AD7142_PARTID:
+		ad714x->product = 0x7142;
+		ad714x->version = data & 0xF;
+		dev_info(ad714x->dev, "found AD7142 captouch, rev:%d\n",
+				ad714x->version);
+		return 0;
+	default:
+		dev_err(ad714x->dev,
+			"fail to detect AD714X captouch, read ID is %04x\n",
+			data);
+		return -ENODEV;
+	}
+}
+
+static void ad714x_hw_init(struct ad714x_chip *ad714x)
+{
+	int i, j;
+	unsigned short reg_base;
+	unsigned short data;
+
+	/* configuration CDC and interrupts*/
+
+	for (i = 0; i < STAGE_NUM; i++) {
+		reg_base = AD714X_STAGECFG_REG + i * STAGE_CFGREG_NUM;
+		for (j = 0; j < STAGE_CFGREG_NUM; j++)
+			ad714x->write(ad714x->dev, reg_base + j,
+					ad714x->hw->stage_cfg_reg[i][j]);
+	}
+
+	for (i = 0; i < SYS_CFGREG_NUM; i++)
+		ad714x->write(ad714x->dev, AD714X_SYSCFG_REG + i,
+			ad714x->hw->sys_cfg_reg[i]);
+	for (i = 0; i < SYS_CFGREG_NUM; i++)
+		ad714x->read(ad714x->dev, AD714X_SYSCFG_REG + i,
+			&data);
+
+	ad714x->write(ad714x->dev, AD714X_STG_CAL_EN_REG, 0xFFF);
+
+	/* clear all interrupts */
+	ad714x->read(ad714x->dev, STG_LOW_INT_STA_REG, &data);
+	ad714x->read(ad714x->dev, STG_HIGH_INT_STA_REG, &data);
+	ad714x->read(ad714x->dev, STG_COM_INT_STA_REG, &data);
+}
+
+static irqreturn_t ad714x_interrupt_thread(int irq, void *data)
+{
+	struct ad714x_chip *ad714x = data;
+	int i;
+
+	mutex_lock(&ad714x->mutex);
+
+	ad714x->read(ad714x->dev, STG_LOW_INT_STA_REG, &ad714x->l_state);
+	ad714x->read(ad714x->dev, STG_HIGH_INT_STA_REG, &ad714x->h_state);
+	ad714x->read(ad714x->dev, STG_COM_INT_STA_REG, &ad714x->c_state);
+
+	for (i = 0; i < ad714x->hw->button_num; i++)
+		ad714x_button_state_machine(ad714x, i);
+	for (i = 0; i < ad714x->hw->slider_num; i++)
+		ad714x_slider_state_machine(ad714x, i);
+	for (i = 0; i < ad714x->hw->wheel_num; i++)
+		ad714x_wheel_state_machine(ad714x, i);
+	for (i = 0; i < ad714x->hw->touchpad_num; i++)
+		ad714x_touchpad_state_machine(ad714x, i);
+
+	mutex_unlock(&ad714x->mutex);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t ad714x_interrupt(int irq, void *data)
+{
+	return IRQ_WAKE_THREAD;
+}
+
+#define MAX_DEVICE_NUM 8
+int ad714x_probe(struct ad714x_chip **pad714x, struct device *dev,
+	u16 bus_type, int irq, ad714x_read_t read, ad714x_write_t write)
+{
+	int ret, i, alloc_idx;
+	struct input_dev *input[MAX_DEVICE_NUM];
+
+	struct ad714x_platform_data *plat_data;
+	struct ad714x_chip *ad714x;
+	void *drv_mem;
+
+	struct ad714x_button_drv *bt_drv;
+	struct ad714x_slider_drv *sd_drv;
+	struct ad714x_wheel_drv *wl_drv;
+	struct ad714x_touchpad_drv *tp_drv;
+
+	plat_data = dev->platform_data;
+	if (dev->platform_data == NULL) {
+		dev_err(dev, "platform data for ad714x doesn't exist\n");
+		return -ENODEV;
+	}
+
+	*pad714x = ad714x = kzalloc(sizeof(*ad714x) + sizeof(*ad714x->sw) +
+			sizeof(*sd_drv) * plat_data->slider_num +
+			sizeof(*wl_drv) * plat_data->wheel_num +
+			sizeof(*tp_drv) * plat_data->touchpad_num +
+			sizeof(*bt_drv) * plat_data->button_num, GFP_KERNEL);
+	if (!ad714x)
+		return -ENOMEM;
+	ad714x->hw = plat_data;
+
+	drv_mem = ad714x + 1;
+	ad714x->sw = drv_mem;
+	drv_mem += sizeof(*ad714x->sw);
+	ad714x->sw->slider = sd_drv = drv_mem;
+	drv_mem += sizeof(*sd_drv) * ad714x->hw->slider_num;
+	ad714x->sw->wheel = wl_drv = drv_mem;
+	drv_mem += sizeof(*wl_drv) * ad714x->hw->wheel_num;
+	ad714x->sw->touchpad = tp_drv = drv_mem;
+	drv_mem += sizeof(*tp_drv) * ad714x->hw->touchpad_num;
+	ad714x->sw->button = bt_drv = drv_mem;
+	drv_mem += sizeof(*bt_drv) * ad714x->hw->button_num;
+
+	ad714x->read = read;
+	ad714x->write = write;
+	ad714x->irq = irq;
+	ad714x->dev = dev;
+
+	ret = ad714x_hw_detect(ad714x);
+	if (ret)
+		goto det_err;
+
+	/* initilize and request sw/hw resources */
+
+	ad714x_hw_init(ad714x);
+	mutex_init(&ad714x->mutex);
+
+	if (ad714x->irq > 0) {
+		ret = request_threaded_irq(ad714x->irq, ad714x_interrupt,
+				ad714x_interrupt_thread, IRQF_TRIGGER_FALLING,
+				"ad714x_captouch", ad714x);
+		if (ret) {
+			dev_err(dev, "can't allocate irq %d\n",
+					ad714x->irq);
+			goto fail_irq;
+		}
+	} else {
+		dev_err(dev, "IRQ not configured!\n");
+		ret = -EINVAL;
+		goto det_err;
+	}
+
+	/*
+	 * Allocate and register AD714X input device
+	 */
+	alloc_idx = 0;
+
+	/* a slider uses one input_dev instance */
+	if (ad714x->hw->slider_num > 0) {
+		struct ad714x_slider_plat *sd_plat = ad714x->hw->slider;
+
+		for (i = 0; i < ad714x->hw->slider_num; i++) {
+			sd_drv[i].input = input[alloc_idx] = input_allocate_device();
+			if (!input[alloc_idx]) {
+				ret = -ENOMEM;
+				goto fail_alloc_reg;
+			}
+
+			__set_bit(EV_ABS, input[alloc_idx]->evbit);
+			__set_bit(EV_KEY, input[alloc_idx]->evbit);
+			__set_bit(ABS_X, input[alloc_idx]->absbit);
+			__set_bit(BTN_TOUCH, input[alloc_idx]->keybit);
+			input_set_abs_params(input[alloc_idx], ABS_X, 0,
+					sd_plat->max_coord, 0, 0);
+
+			input[alloc_idx]->id.bustype = bus_type;
+			input[alloc_idx]->id.product = ad714x->product;
+			input[alloc_idx]->id.version = ad714x->version;
+
+			ret = input_register_device(input[alloc_idx]);
+			if (ret)
+				goto fail_alloc_reg;
+
+			alloc_idx++;
+		}
+	}
+
+	/* a wheel uses one input_dev instance */
+	if (ad714x->hw->wheel_num > 0) {
+		struct ad714x_wheel_plat *wl_plat = ad714x->hw->wheel;
+
+		for (i = 0; i < ad714x->hw->wheel_num; i++) {
+			wl_drv[i].input = input[alloc_idx] = input_allocate_device();
+			if (!input[alloc_idx]) {
+				ret = -ENOMEM;
+				goto fail_alloc_reg;
+			}
+
+			__set_bit(EV_KEY, input[alloc_idx]->evbit);
+			__set_bit(EV_ABS, input[alloc_idx]->evbit);
+			__set_bit(ABS_WHEEL, input[alloc_idx]->absbit);
+			__set_bit(BTN_TOUCH, input[alloc_idx]->keybit);
+			input_set_abs_params(input[alloc_idx], ABS_WHEEL, 0,
+					wl_plat->max_coord, 0, 0);
+
+			input[alloc_idx]->id.bustype = bus_type;
+			input[alloc_idx]->id.product = ad714x->product;
+			input[alloc_idx]->id.version = ad714x->version;
+
+			ret = input_register_device(input[alloc_idx]);
+			if (ret)
+				goto fail_alloc_reg;
+
+			alloc_idx++;
+		}
+	}
+
+	/* a touchpad uses one input_dev instance */
+	if (ad714x->hw->touchpad_num > 0) {
+		struct ad714x_touchpad_plat *tp_plat = ad714x->hw->touchpad;
+
+		for (i = 0; i < ad714x->hw->touchpad_num; i++) {
+			tp_drv[i].input = input[alloc_idx] = input_allocate_device();
+			if (!input[alloc_idx]) {
+				ret = -ENOMEM;
+				goto fail_alloc_reg;
+			}
+
+			__set_bit(EV_ABS, input[alloc_idx]->evbit);
+			__set_bit(EV_KEY, input[alloc_idx]->evbit);
+			__set_bit(ABS_X, input[alloc_idx]->absbit);
+			__set_bit(ABS_Y, input[alloc_idx]->absbit);
+			__set_bit(BTN_TOUCH, input[alloc_idx]->keybit);
+			input_set_abs_params(input[alloc_idx], ABS_X, 0,
+					tp_plat->x_max_coord, 0, 0);
+			input_set_abs_params(input[alloc_idx], ABS_Y, 0,
+					tp_plat->y_max_coord, 0, 0);
+
+			input[alloc_idx]->id.bustype = bus_type;
+			input[alloc_idx]->id.product = ad714x->product;
+			input[alloc_idx]->id.version = ad714x->version;
+
+			ret = input_register_device(input[alloc_idx]);
+			if (ret)
+				goto fail_alloc_reg;
+
+			alloc_idx++;
+		}
+	}
+
+	/* all buttons use one input node */
+	if (ad714x->hw->button_num > 0) {
+		struct ad714x_button_plat *bt_plat = ad714x->hw->button;
+
+		input[alloc_idx] = input_allocate_device();
+		if (!input[alloc_idx]) {
+			ret = -ENOMEM;
+			goto fail_alloc_reg;
+		}
+
+		__set_bit(EV_KEY, input[alloc_idx]->evbit);
+		for (i = 0; i < ad714x->hw->button_num; i++) {
+			bt_drv[i].input = input[alloc_idx];
+			__set_bit(bt_plat[i].keycode, input[alloc_idx]->keybit);
+		}
+
+		input[alloc_idx]->id.bustype = bus_type;
+		input[alloc_idx]->id.product = ad714x->product;
+		input[alloc_idx]->id.version = ad714x->version;
+
+		ret = input_register_device(input[alloc_idx]);
+		if (ret)
+			goto fail_alloc_reg;
+
+		alloc_idx++;
+	}
+
+
+	return 0;
+
+ fail_alloc_reg:
+	dev_err(dev, "failed to setup AD714x input device %i\n", alloc_idx);
+	for (i = 0; i < alloc_idx - 1; i++)
+		input_unregister_device(input[i]);
+	input_free_device(input[alloc_idx]);
+
+	free_irq(ad714x->irq, ad714x);
+ fail_irq:
+ det_err:
+	kfree(ad714x);
+	return ret;
+}
+EXPORT_SYMBOL(ad714x_probe);
+
+int ad714x_remove(struct ad714x_chip *ad714x)
+{
+	int i;
+
+	/* unregister and free all input devices */
+
+	for (i = 0; i < ad714x->hw->slider_num; i++)
+		input_unregister_device(ad714x->sw->slider[i].input);
+
+	for (i = 0; i < ad714x->hw->wheel_num; i++)
+		input_unregister_device(ad714x->sw->wheel[i].input);
+
+	for (i = 0; i < ad714x->hw->touchpad_num; i++)
+		input_unregister_device(ad714x->sw->touchpad[i].input);
+
+	if (ad714x->hw->button_num)
+		input_unregister_device(ad714x->sw->button[0].input);
+
+	free_irq(ad714x->irq, ad714x);
+
+	kfree(ad714x);
+
+	return 0;
+}
+EXPORT_SYMBOL(ad714x_remove);
+
+#ifdef CONFIG_PM
+int ad714x_disable(struct ad714x_chip *ad714x)
+{
+	unsigned short data;
+
+	dev_dbg(ad714x->dev, "%s enter\n", __func__);
+
+	mutex_lock(&ad714x->mutex);
+
+	data = ad714x->hw->sys_cfg_reg[AD714X_PWR_CTRL] | 0x3;
+	ad714x->write(ad714x->dev, AD714X_PWR_CTRL, data);
+
+	mutex_unlock(&ad714x->mutex);
+
+	return 0;
+}
+EXPORT_SYMBOL(ad714x_disable);
+
+int ad714x_enable(struct ad714x_chip *ad714x)
+{
+	unsigned short data;
+
+	dev_dbg(ad714x->dev, "%s enter\n", __func__);
+
+	mutex_lock(&ad714x->mutex);
+
+	/* resume to non-shutdown mode */
+
+	ad714x->write(ad714x->dev, AD714X_PWR_CTRL,
+			ad714x->hw->sys_cfg_reg[AD714X_PWR_CTRL]);
+
+	/* make sure the interrupt output line is not low level after resume,
+	 * otherwise we will get no chance to enter falling-edge irq again
+	 */
+
+	ad714x->read(ad714x->dev, STG_LOW_INT_STA_REG, &data);
+	ad714x->read(ad714x->dev, STG_HIGH_INT_STA_REG, &data);
+	ad714x->read(ad714x->dev, STG_COM_INT_STA_REG, &data);
+
+	mutex_unlock(&ad714x->mutex);
+
+	return 0;
+}
+EXPORT_SYMBOL(ad714x_enable);
+#endif
+
+/* Stub functions so we can load/unload the module */
+static __init int ad714x_init(void)
+{
+	return 0;
+}
+module_init(ad714x_init);
+
+static __exit void ad714x_exit(void)
+{
+}
+module_exit(ad714x_exit);
+
+MODULE_DESCRIPTION("Analog Devices AD714X Capacitance Touch Sensor Driver");
+MODULE_AUTHOR("Barry Song <21cnbao-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/ad714x.h b/drivers/input/misc/ad714x.h
new file mode 100644
index 0000000..3fc5a16
--- /dev/null
+++ b/drivers/input/misc/ad714x.h
@@ -0,0 +1,25 @@
+/*
+ * AD714X CapTouch Programmable Controller driver (bus interfaces)
+ *
+ * Copyright 2009 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#ifndef _AD714X_H_
+#define _AD714X_H_
+
+#include <linux/types.h>
+
+struct device;
+struct ad714x_chip;
+typedef int (ad714x_read_t) (struct device *, unsigned short, unsigned short *);
+typedef int (ad714x_write_t) (struct device *, unsigned short, unsigned short);
+
+int ad714x_disable(struct ad714x_chip *ad714x);
+int ad714x_enable(struct ad714x_chip *ad714x);
+int ad714x_probe(struct ad714x_chip **pad714x, struct device *dev,
+	u16 bus_type, int irq, ad714x_read_t read, ad714x_write_t write);
+int ad714x_remove(struct ad714x_chip *ad714x);
+
+#endif
diff --git a/include/linux/input.h b/include/linux/input.h
index 0ccfc30..8b38cfa 100644
--- a/include/linux/input.h
+++ b/include/linux/input.h
@@ -756,6 +756,7 @@ struct input_absinfo {
 #define BUS_HOST		0x19
 #define BUS_GSC			0x1A
 #define BUS_ATARI		0x1B
+#define BUS_SPI			0x1C
 
 /*
  * MT_TOOL types
diff --git a/include/linux/input/ad714x.h b/include/linux/input/ad714x.h
new file mode 100644
index 0000000..0cbe5e8
--- /dev/null
+++ b/include/linux/input/ad714x.h
@@ -0,0 +1,63 @@
+/*
+ * include/linux/input/ad714x.h
+ *
+ * AD714x is very flexible, it can be used as buttons, scrollwheel,
+ * slider, touchpad at the same time. That depends on the boards.
+ * The platform_data for the device's "struct device" holds this
+ * information.
+ *
+ * Copyright 2009 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#ifndef __LINUX_INPUT_AD714X_H__
+#define __LINUX_INPUT_AD714X_H__
+
+#define STAGE_NUM              12
+#define STAGE_CFGREG_NUM       8
+#define SYS_CFGREG_NUM         8
+
+/* board information which need be initialized in arch/mach... */
+struct ad714x_slider_plat {
+	int start_stage;
+	int end_stage;
+	int max_coord;
+};
+
+struct ad714x_wheel_plat {
+	int start_stage;
+	int end_stage;
+	int max_coord;
+};
+
+struct ad714x_touchpad_plat {
+	int x_start_stage;
+	int x_end_stage;
+	int x_max_coord;
+
+	int y_start_stage;
+	int y_end_stage;
+	int y_max_coord;
+};
+
+struct ad714x_button_plat {
+	int keycode;
+	unsigned short l_mask;
+	unsigned short h_mask;
+};
+
+struct ad714x_platform_data {
+	int slider_num;
+	int wheel_num;
+	int touchpad_num;
+	int button_num;
+	struct ad714x_slider_plat *slider;
+	struct ad714x_wheel_plat *wheel;
+	struct ad714x_touchpad_plat *touchpad;
+	struct ad714x_button_plat *button;
+	unsigned short stage_cfg_reg[STAGE_NUM][STAGE_CFGREG_NUM];
+	unsigned short sys_cfg_reg[SYS_CFGREG_NUM];
+};
+
+#endif
-- 
1.6.5

^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v5] input/misc: add Analog Devices AD714x captouch input driver
  2009-10-13  6:02   ` [PATCH v4] input/misc: add Analog Devices AD714x " Mike Frysinger
@ 2009-10-20  8:37     ` Mike Frysinger
       [not found]       ` <1256027864-26126-1-git-send-email-vapier-aBrp7R+bbdUdnm+yROfE0A@public.gmane.org>
  0 siblings, 1 reply; 21+ messages in thread
From: Mike Frysinger @ 2009-10-20  8:37 UTC (permalink / raw)
  To: linux-input, Dmitry Torokhov
  Cc: uclinux-dist-devel, Bryan Wu, Michael Hennerich, Barry Song

From: Bryan Wu <cooloney@kernel.org>

AD7142 and AD7147 are integrated capacitance-to-digital converters
(CDCs) with on-chip environmental calibration for use in systems
requiring a novel user input method. The AD7142 and AD7147 can interface
to external capacitance sensors implementing functions such as buttons,
scrollwheels, sliders, touchpads and so on.

The chips don't restrict the specific usage. Depending on the hardware
connection, one special target board can include one or several these
components. The platform_data for the device's "struct device" holds
these information. The data-struct defined in head file descript the
hardware feature of button/scrollwheel/slider/touchpad components on
target boards, which need be filled in the arch/mach-/.

As the result, the driver is independent of boards. It gets the
components layout from the platform_data, registers related devices,
fullfills the algorithms and state machines for these components and
report related input events to up level.

Signed-off-by: Bryan Wu <cooloney@kernel.org>
Signed-off-by: Michael Hennerich <michael.hennerich@analog.com>
Signed-off-by: Barry Song <21cnbao@gmail.com>
Signed-off-by: Mike Frysinger <vapier@gentoo.org>
---
v5
	- remove dummy module entry points

 drivers/input/misc/Kconfig      |   30 +
 drivers/input/misc/Makefile     |    3 +
 drivers/input/misc/ad714x-i2c.c |  134 ++++
 drivers/input/misc/ad714x-spi.c |   96 +++
 drivers/input/misc/ad714x.c     | 1305 +++++++++++++++++++++++++++++++++++++++
 drivers/input/misc/ad714x.h     |   25 +
 include/linux/input.h           |    1 +
 include/linux/input/ad714x.h    |   63 ++
 8 files changed, 1657 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/misc/ad714x-i2c.c
 create mode 100644 drivers/input/misc/ad714x-spi.c
 create mode 100644 drivers/input/misc/ad714x.c
 create mode 100644 drivers/input/misc/ad714x.h
 create mode 100644 include/linux/input/ad714x.h

diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 02f4f8f..fc4b0b9 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -316,4 +316,34 @@ config INPUT_PCAP
 	  To compile this driver as a module, choose M here: the
 	  module will be called pcap_keys.
 
+config INPUT_AD714X
+	tristate "Analog Devices AD714x Capacitance Touch Sensor"
+	help
+	  Say Y here if you want to support an AD7142/AD7147 touch sensor.
+
+	  You should select a bus connection too.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called ad714x.
+
+config INPUT_AD714X_I2C
+	tristate "support I2C bus connection"
+	depends on INPUT_AD714X && I2C
+	default y
+	help
+	  Say Y here if you have AD7142/AD7147 hooked to an I2C bus.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called ad714x-i2c.
+
+config INPUT_AD714X_SPI
+	tristate "support SPI bus connection"
+	depends on INPUT_AD714X && SPI
+	default y
+	help
+	  Say Y here if you have AD7142/AD7147 hooked to a SPI bus.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called ad714x-spi.
+
 endif
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index a8b8485..16db64a 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -4,6 +4,9 @@
 
 # Each configuration option enables a list of files.
 
+obj-$(CONFIG_INPUT_AD714X)		+= ad714x.o
+obj-$(CONFIG_INPUT_AD714X_I2C)		+= ad714x-i2c.o
+obj-$(CONFIG_INPUT_AD714X_SPI)		+= ad714x-spi.o
 obj-$(CONFIG_INPUT_APANEL)		+= apanel.o
 obj-$(CONFIG_INPUT_ATI_REMOTE)		+= ati_remote.o
 obj-$(CONFIG_INPUT_ATI_REMOTE2)		+= ati_remote2.o
diff --git a/drivers/input/misc/ad714x-i2c.c b/drivers/input/misc/ad714x-i2c.c
new file mode 100644
index 0000000..596c199
--- /dev/null
+++ b/drivers/input/misc/ad714x-i2c.c
@@ -0,0 +1,134 @@
+/*
+ * AD714X CapTouch Programmable Controller driver (I2C bus)
+ *
+ * Copyright 2009 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/input.h>	/* BUS_I2C */
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include "ad714x.h"
+
+#ifdef CONFIG_PM
+static int ad714x_i2c_suspend(struct i2c_client *client, pm_message_t message)
+{
+	return ad714x_disable(i2c_get_clientdata(client));
+}
+
+static int ad714x_i2c_resume(struct i2c_client *client)
+{
+	return ad714x_enable(i2c_get_clientdata(client));
+}
+#else
+# define ad714x_i2c_suspend NULL
+# define ad714x_i2c_resume  NULL
+#endif
+
+static int ad714x_i2c_write(struct device *dev, unsigned short reg,
+		unsigned short data)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	int ret = 0;
+	u8 *_reg = (u8 *)&reg;
+	u8 *_data = (u8 *)&data;
+
+	u8 tx[4] = {
+		_reg[1],
+		_reg[0],
+		_data[1],
+		_data[0]
+	};
+
+	ret = i2c_master_send(client, tx, 4);
+	if (ret < 0)
+		dev_err(&client->dev, "I2C write error\n");
+
+	return ret;
+}
+
+static int ad714x_i2c_read(struct device *dev, unsigned short reg,
+		unsigned short *data)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	int ret = 0;
+	u8 *_reg = (u8 *)&reg;
+	u8 *_data = (u8 *)data;
+
+	u8 tx[2] = {
+		_reg[1],
+		_reg[0]
+	};
+	u8 rx[2];
+
+	ret = i2c_master_send(client, tx, 2);
+	if (ret < 0) {
+		dev_err(&client->dev, "I2C read error\n");
+		return ret;
+	}
+
+	ret = i2c_master_recv(client, rx, 2);
+	if (ret < 0) {
+		dev_err(&client->dev, "I2C read error\n");
+		return ret;
+	}
+
+	_data[0] = rx[1];
+	_data[1] = rx[0];
+
+	return ret;
+}
+
+static int __devinit ad714x_i2c_probe(struct i2c_client *client,
+		const struct i2c_device_id *id)
+{
+	int ret = 0;
+	struct ad714x_chip *chip = NULL;
+
+	ret = ad714x_probe(&chip, &client->dev, BUS_I2C, client->irq,
+		ad714x_i2c_read, ad714x_i2c_write);
+	i2c_set_clientdata(client, chip);
+
+	return ret;
+}
+
+static int __devexit ad714x_i2c_remove(struct i2c_client *client)
+{
+	return ad714x_remove(i2c_get_clientdata(client));
+}
+
+static const struct i2c_device_id ad714x_id[] = {
+	{ "ad7142_captouch", 0 },
+	{ "ad7147_captouch", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, ad714x_id);
+
+static struct i2c_driver ad714x_i2c_driver = {
+	.driver = {
+		.name = "ad714x_captouch",
+	},
+	.probe    = ad714x_i2c_probe,
+	.remove   = __devexit_p(ad714x_i2c_remove),
+	.suspend  = ad714x_i2c_suspend,
+	.resume	  = ad714x_i2c_resume,
+	.id_table = ad714x_id,
+};
+
+static __init int ad714x_i2c_init(void)
+{
+	return i2c_add_driver(&ad714x_i2c_driver);
+}
+module_init(ad714x_i2c_init);
+
+static __exit void ad714x_i2c_exit(void)
+{
+	i2c_del_driver(&ad714x_i2c_driver);
+}
+module_exit(ad714x_i2c_exit);
+
+MODULE_DESCRIPTION("Analog Devices AD714X Capacitance Touch Sensor I2C Bus Driver");
+MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/ad714x-spi.c b/drivers/input/misc/ad714x-spi.c
new file mode 100644
index 0000000..2cea1e4
--- /dev/null
+++ b/drivers/input/misc/ad714x-spi.c
@@ -0,0 +1,96 @@
+/*
+ * AD714X CapTouch Programmable Controller driver (SPI bus)
+ *
+ * Copyright 2009 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/input.h>	/* BUS_I2C */
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+#include <linux/types.h>
+#include "ad714x.h"
+
+#define AD714x_SPI_CMD_PREFIX      0xE000   /* bits 15:11 */
+#define AD714x_SPI_READ            BIT(10)
+
+#ifdef CONFIG_PM
+static int ad714x_spi_suspend(struct spi_device *spi, pm_message_t message)
+{
+	return ad714x_disable(spi_get_drvdata(spi));
+}
+
+static int ad714x_spi_resume(struct spi_device *spi)
+{
+	return ad714x_enable(spi_get_drvdata(spi));
+}
+#else
+# define ad714x_spi_suspend NULL
+# define ad714x_spi_resume  NULL
+#endif
+
+static int ad714x_spi_read(struct device *dev, unsigned short reg,
+		unsigned short *data)
+{
+	struct spi_device *spi = to_spi_device(dev);
+	unsigned short tx = AD714x_SPI_CMD_PREFIX | AD714x_SPI_READ | reg;
+
+	return spi_write_then_read(spi, (u8 *)&tx, 2, (u8 *)data, 2);
+}
+
+static int ad714x_spi_write(struct device *dev, unsigned short reg,
+		unsigned short data)
+{
+	struct spi_device *spi = to_spi_device(dev);
+	unsigned short tx[2] = {
+		AD714x_SPI_CMD_PREFIX | reg,
+		data
+	};
+
+	return spi_write(spi, (u8 *)tx, 4);
+}
+
+static int __devinit ad714x_spi_probe(struct spi_device *spi)
+{
+	int ret;
+	struct ad714x_chip *chip = NULL;
+
+	ret = ad714x_probe(&chip, &spi->dev, BUS_SPI, spi->irq,
+		ad714x_spi_read, ad714x_spi_write);
+	spi_set_drvdata(spi, chip);
+
+	return ret;
+}
+
+static int __devexit ad714x_spi_remove(struct spi_device *spi)
+{
+	return ad714x_remove(spi_get_drvdata(spi));
+}
+
+static struct spi_driver ad714x_spi_driver = {
+	.driver = {
+		.name	= "ad714x_captouch",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= ad714x_spi_probe,
+	.remove		= __devexit_p(ad714x_spi_remove),
+	.suspend	= ad714x_spi_suspend,
+	.resume		= ad714x_spi_resume,
+};
+
+static __init int ad714x_spi_init(void)
+{
+	return spi_register_driver(&ad714x_spi_driver);
+}
+module_init(ad714x_spi_init);
+
+static __exit void ad714x_spi_exit(void)
+{
+	spi_unregister_driver(&ad714x_spi_driver);
+}
+module_exit(ad714x_spi_exit);
+
+MODULE_DESCRIPTION("Analog Devices AD714X Capacitance Touch Sensor SPI Bus Driver");
+MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/ad714x.c b/drivers/input/misc/ad714x.c
new file mode 100644
index 0000000..d22509d
--- /dev/null
+++ b/drivers/input/misc/ad714x.c
@@ -0,0 +1,1305 @@
+/*
+ * AD714X CapTouch Programmable Controller driver
+ *
+ * Copyright 2009 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/input/ad714x.h>
+#include "ad714x.h"
+
+#define AD714X_PWR_CTRL           0x0
+#define AD714X_STG_CAL_EN_REG     0x1
+#define AD714X_AMB_COMP_CTRL0_REG 0x2
+#define AD714X_PARTID_REG         0x17
+#define AD7147_PARTID             0x1470
+#define AD7142_PARTID             0xE620
+#define AD714X_STAGECFG_REG       0x80
+#define AD714X_SYSCFG_REG         0x0
+
+#define STG_LOW_INT_EN_REG     0x5
+#define STG_HIGH_INT_EN_REG    0x6
+#define STG_COM_INT_EN_REG     0x7
+#define STG_LOW_INT_STA_REG    0x8
+#define STG_HIGH_INT_STA_REG   0x9
+#define STG_COM_INT_STA_REG    0xA
+
+#define CDC_RESULT_S0          0xB
+#define CDC_RESULT_S1          0xC
+#define CDC_RESULT_S2          0xD
+#define CDC_RESULT_S3          0xE
+#define CDC_RESULT_S4          0xF
+#define CDC_RESULT_S5          0x10
+#define CDC_RESULT_S6          0x11
+#define CDC_RESULT_S7          0x12
+#define CDC_RESULT_S8          0x13
+#define CDC_RESULT_S9          0x14
+#define CDC_RESULT_S10         0x15
+#define CDC_RESULT_S11         0x16
+
+#define STAGE0_AMBIENT		0xF1
+#define STAGE1_AMBIENT		0x115
+#define STAGE2_AMBIENT		0x139
+#define STAGE3_AMBIENT		0x15D
+#define STAGE4_AMBIENT		0x181
+#define STAGE5_AMBIENT		0x1A5
+#define STAGE6_AMBIENT		0x1C9
+#define STAGE7_AMBIENT		0x1ED
+#define STAGE8_AMBIENT		0x211
+#define STAGE9_AMBIENT		0x234
+#define STAGE10_AMBIENT		0x259
+#define STAGE11_AMBIENT		0x27D
+
+#define PER_STAGE_REG_NUM      36
+#define STAGE_NUM              12
+#define STAGE_CFGREG_NUM       8
+#define SYS_CFGREG_NUM         8
+
+/*
+ * driver information which will be used to maintain the software flow
+ */
+typedef enum {IDLE, JITTER, ACTIVE, SPACE} ad714x_device_state;
+
+struct ad714x_slider_drv {
+	int highest_stage;
+	int abs_pos;
+	int flt_pos;
+	ad714x_device_state state;
+	struct input_dev *input;
+};
+
+struct ad714x_wheel_drv {
+	int abs_pos;
+	int flt_pos;
+	int pre_mean_value;
+	int pre_highest_stage;
+	int pre_mean_value_no_offset;
+	int mean_value;
+	int mean_value_no_offset;
+	int pos_offset;
+	int pos_ratio;
+	int highest_stage;
+	ad714x_device_state state;
+	struct input_dev *input;
+};
+
+struct ad714x_touchpad_drv {
+	int x_highest_stage;
+	int x_flt_pos;
+	int x_abs_pos;
+	int y_highest_stage;
+	int y_flt_pos;
+	int y_abs_pos;
+	int left_ep;
+	int left_ep_val;
+	int right_ep;
+	int right_ep_val;
+	int top_ep;
+	int top_ep_val;
+	int bottom_ep;
+	int bottom_ep_val;
+	ad714x_device_state state;
+	struct input_dev *input;
+};
+
+struct ad714x_button_drv {
+	ad714x_device_state state;
+	/* Unlike slider/wheel/touchpad, all buttons point to
+	 * same input_dev instance
+	 */
+	struct input_dev *input;
+};
+
+struct ad714x_driver_data {
+	struct ad714x_slider_drv *slider;
+	struct ad714x_wheel_drv *wheel;
+	struct ad714x_touchpad_drv *touchpad;
+	struct ad714x_button_drv *button;
+};
+
+/* information to integrate all things which will be private data
+ * of spi/i2c device
+ */
+struct ad714x_chip {
+	unsigned short h_state;
+	unsigned short l_state;
+	unsigned short c_state;
+	unsigned short adc_reg[STAGE_NUM];
+	unsigned short amb_reg[STAGE_NUM];
+	unsigned short sensor_val[STAGE_NUM];
+
+	struct ad714x_platform_data *hw;
+	struct ad714x_driver_data *sw;
+
+	int irq;
+	struct device *dev;
+	ad714x_read_t *read;
+	ad714x_write_t *write;
+
+	struct mutex mutex;
+
+	unsigned product;
+	unsigned version;
+};
+
+static void ad714x_use_com_int(struct ad714x_chip *ad714x, int start_stage,
+		int end_stage)
+{
+	unsigned short data;
+	unsigned short mask;
+
+	mask = ((1 << (end_stage + 1)) - 1) - (1 << start_stage);
+
+	ad714x->read(ad714x->dev, STG_COM_INT_EN_REG, &data);
+	data |= 1 << start_stage;
+	ad714x->write(ad714x->dev, STG_COM_INT_EN_REG, data);
+
+	ad714x->read(ad714x->dev, STG_HIGH_INT_EN_REG, &data);
+	data &= ~mask;
+	ad714x->write(ad714x->dev, STG_HIGH_INT_EN_REG, data);
+}
+
+static void ad714x_use_thr_int(struct ad714x_chip *ad714x, int start_stage,
+		int end_stage)
+{
+	unsigned short data;
+	unsigned short mask;
+
+	mask = ((1 << (end_stage + 1)) - 1) - (1 << start_stage);
+
+	ad714x->read(ad714x->dev, STG_COM_INT_EN_REG, &data);
+	data &= ~(1 << start_stage);
+	ad714x->write(ad714x->dev, STG_COM_INT_EN_REG, data);
+
+	ad714x->read(ad714x->dev, STG_HIGH_INT_EN_REG, &data);
+	data |= mask;
+	ad714x->write(ad714x->dev, STG_HIGH_INT_EN_REG, data);
+}
+
+static int ad714x_cal_highest_stage(struct ad714x_chip *ad714x, int start_stage,
+		int end_stage)
+{
+	int max_res = 0;
+	int max_idx = 0;
+	int i;
+
+	for (i = start_stage; i <= end_stage; i++) {
+		if (ad714x->sensor_val[i] > max_res) {
+			max_res = ad714x->sensor_val[i];
+			max_idx = i;
+		}
+	}
+
+	return max_idx;
+}
+
+static int ad714x_cal_abs_pos(struct ad714x_chip *ad714x, int start_stage,
+		int end_stage, int highest_stage, int max_coord)
+{
+	int a_param, b_param;
+
+	if (highest_stage == start_stage) {
+		a_param = ad714x->sensor_val[start_stage + 1];
+		b_param = ad714x->sensor_val[start_stage] +
+			ad714x->sensor_val[start_stage + 1];
+	} else if (highest_stage == end_stage) {
+		a_param = ad714x->sensor_val[end_stage] *
+			(end_stage - start_stage) +
+			ad714x->sensor_val[end_stage - 1] *
+			(end_stage - start_stage - 1);
+		b_param = ad714x->sensor_val[end_stage] +
+			ad714x->sensor_val[end_stage - 1];
+	} else {
+		a_param = ad714x->sensor_val[highest_stage] *
+			(highest_stage - start_stage) +
+			ad714x->sensor_val[highest_stage - 1] *
+			(highest_stage - start_stage - 1) +
+			ad714x->sensor_val[highest_stage + 1] *
+			(highest_stage - start_stage + 1);
+		b_param = ad714x->sensor_val[highest_stage] +
+			ad714x->sensor_val[highest_stage - 1] +
+			ad714x->sensor_val[highest_stage + 1];
+	}
+
+	return (max_coord / (end_stage - start_stage)) * a_param / b_param;
+}
+
+
+/* One button can connect to multi positive and negative of CDCs
+ * Multi-buttons can connect to same positive/negative of one CDC
+ */
+static void ad714x_button_state_machine(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_button_plat *hw = &ad714x->hw->button[idx];
+	struct ad714x_button_drv *sw = &ad714x->sw->button[idx];
+
+	switch (sw->state) {
+	case IDLE:
+		if (((ad714x->h_state & hw->h_mask) == hw->h_mask) &&
+			((ad714x->l_state & hw->l_mask) == hw->l_mask)) {
+			dev_dbg(ad714x->dev, "button %d touched\n", idx);
+			input_report_key(sw->input, hw->keycode, 1);
+			input_sync(sw->input);
+			sw->state = ACTIVE;
+		}
+		break;
+	case ACTIVE:
+		if (((ad714x->h_state & hw->h_mask) != hw->h_mask) ||
+			((ad714x->l_state & hw->l_mask) != hw->l_mask)) {
+			dev_dbg(ad714x->dev, "button %d released\n", idx);
+			input_report_key(sw->input, hw->keycode, 0);
+			input_sync(sw->input);
+			sw->state = IDLE;
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+/* The response of a sensor is defined by the absolute number of codes
+ * between the current CDC value and the ambient value.
+ */
+void ad714x_slider_cal_sensor_val(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx];
+	int i;
+
+	for (i = hw->start_stage; i <= hw->end_stage; i++) {
+		ad714x->read(ad714x->dev, CDC_RESULT_S0 + i,
+			&ad714x->adc_reg[i]);
+		ad714x->read(ad714x->dev,
+				STAGE0_AMBIENT + i * PER_STAGE_REG_NUM,
+				&ad714x->amb_reg[i]);
+
+		ad714x->sensor_val[i] = abs(ad714x->adc_reg[i] -
+				ad714x->amb_reg[i]);
+	}
+}
+
+void ad714x_slider_cal_highest_stage(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx];
+	struct ad714x_slider_drv *sw = &ad714x->sw->slider[idx];
+
+	sw->highest_stage = ad714x_cal_highest_stage(ad714x, hw->start_stage,
+			hw->end_stage);
+
+	dev_dbg(ad714x->dev, "slider %d highest_stage:%d\n", idx,
+		sw->highest_stage);
+}
+
+/* The formulae are very straight forward. It uses the sensor with the
+ * highest response and the 2 adjacent ones.
+ * When Sensor 0 has the highest response, only sensor 0 and sensor 1
+ * are used in the calculations. Similarly when the last sensor has the
+ * highest response, only the last sensor and the second last sensors
+ * are used in the calculations.
+ *
+ * For i= idx_of_peak_Sensor-1 to i= idx_of_peak_Sensor+1
+ *         v += Sensor response(i)*i
+ *         w += Sensor response(i)
+ * POS=(Number_of_Positions_Wanted/(Number_of_Sensors_Used-1)) *(v/w)
+ */
+void ad714x_slider_cal_abs_pos(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx];
+	struct ad714x_slider_drv *sw = &ad714x->sw->slider[idx];
+
+	sw->abs_pos = ad714x_cal_abs_pos(ad714x, hw->start_stage, hw->end_stage,
+		sw->highest_stage, hw->max_coord);
+
+	dev_dbg(ad714x->dev, "slider %d absolute position:%d\n", idx,
+		sw->abs_pos);
+}
+
+/*
+ * To minimise the Impact of the noise on the algorithm, ADI developed a
+ * routine that filters the CDC results after they have been read by the
+ * host processor.
+ * The filter used is an Infinite Input Response(IIR) filter implemented
+ * in firmware and attenuates the noise on the CDC results after they've
+ * been read by the host processor.
+ * Filtered_CDC_result = (Filtered_CDC_result * (10 - Coefficient) +
+ * 				Latest_CDC_result * Coefficient)/10
+ */
+void ad714x_slider_cal_flt_pos(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_slider_drv *sw = &ad714x->sw->slider[idx];
+
+	sw->flt_pos = (sw->flt_pos * (10 - 4) +
+			sw->abs_pos * 4)/10;
+
+	dev_dbg(ad714x->dev, "slider %d filter position:%d\n", idx,
+		sw->flt_pos);
+}
+
+static void ad714x_slider_use_com_int(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx];
+	ad714x_use_com_int(ad714x, hw->start_stage, hw->end_stage);
+}
+
+static void ad714x_slider_use_thr_int(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx];
+	ad714x_use_thr_int(ad714x, hw->start_stage, hw->end_stage);
+}
+
+static void ad714x_slider_state_machine(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx];
+	struct ad714x_slider_drv *sw = &ad714x->sw->slider[idx];
+	unsigned short h_state, c_state;
+	unsigned short mask;
+
+	mask = ((1 << (hw->end_stage + 1)) - 1) - ((1 << hw->start_stage) - 1);
+
+	h_state = ad714x->h_state & mask;
+	c_state = ad714x->c_state & mask;
+
+	switch (sw->state) {
+	case IDLE:
+		if (h_state) {
+			sw->state = JITTER;
+			/* In End of Conversion interrupt mode, the AD714X
+			 * continuously generates hardware interrupts.
+			 */
+			ad714x_slider_use_com_int(ad714x, idx);
+			dev_dbg(ad714x->dev, "slider %d touched\n", idx);
+		}
+		break;
+	case JITTER:
+		if (c_state == mask) {
+			ad714x_slider_cal_sensor_val(ad714x, idx);
+			ad714x_slider_cal_highest_stage(ad714x, idx);
+			ad714x_slider_cal_abs_pos(ad714x, idx);
+			sw->flt_pos = sw->abs_pos;
+			sw->state = ACTIVE;
+		}
+		break;
+	case ACTIVE:
+		if (c_state == mask) {
+			if (h_state) {
+				ad714x_slider_cal_sensor_val(ad714x, idx);
+				ad714x_slider_cal_highest_stage(ad714x, idx);
+				ad714x_slider_cal_abs_pos(ad714x, idx);
+				ad714x_slider_cal_flt_pos(ad714x, idx);
+
+				input_report_abs(sw->input, ABS_X, sw->flt_pos);
+				input_report_key(sw->input, BTN_TOUCH, 1);
+			} else {
+				/* When the user lifts off the sensor, configure
+				 * the AD714X back to threshold interrupt mode.
+				 */
+				ad714x_slider_use_thr_int(ad714x, idx);
+				sw->state = IDLE;
+				input_report_key(sw->input, BTN_TOUCH, 0);
+				dev_dbg(ad714x->dev, "slider %d released\n",
+					idx);
+			}
+			input_sync(sw->input);
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+/* When the scroll wheel is activated, we compute the absolute position based
+ * on the sensor values. To calculate the position, we first determine the
+ * sensor that has the greatest response among the 8 sensors that constitutes
+ * the scrollwheel. Then we determined the 2 sensors on either sides of the
+ * sensor with the highest response and we apply weights to these sensors.
+ */
+static void ad714x_wheel_cal_highest_stage(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
+	struct ad714x_wheel_drv *sw = &ad714x->sw->wheel[idx];
+
+	sw->pre_highest_stage = sw->highest_stage;
+	sw->highest_stage = ad714x_cal_highest_stage(ad714x, hw->start_stage,
+			hw->end_stage);
+
+	dev_dbg(ad714x->dev, "wheel %d highest_stage:%d\n", idx,
+		sw->highest_stage);
+}
+
+static void ad714x_wheel_cal_sensor_val(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
+	int i;
+
+	for (i = hw->start_stage; i <= hw->end_stage; i++) {
+		ad714x->read(ad714x->dev, CDC_RESULT_S0 + i,
+			&ad714x->adc_reg[i]);
+		ad714x->read(ad714x->dev,
+				STAGE0_AMBIENT + i * PER_STAGE_REG_NUM,
+				&ad714x->amb_reg[i]);
+		if (ad714x->adc_reg[i] > ad714x->amb_reg[i])
+			ad714x->sensor_val[i] = ad714x->adc_reg[i] -
+				ad714x->amb_reg[i];
+		else
+			ad714x->sensor_val[i] = 0;
+	}
+}
+
+/* When the scroll wheel is activated, we compute the absolute position based
+ * on the sensor values. To calculate the position, we first determine the
+ * sensor that has the greatest response among the 8 sensors that constitutes
+ * the scrollwheel. Then we determined the 2 sensors on either sides of the
+ * sensor with the highest response and we apply weights to these sensors. The
+ * result of this computation gives us the mean value which defined by the
+ * following formula:
+ * For i= second_before_highest_stage to i= second_after_highest_stage
+ *         v += Sensor response(i)*WEIGHT*(i+3)
+ *         w += Sensor response(i)
+ * Mean_Value=v/w
+ * pos_on_scrollwheel = (Mean_Value - position_offset) / position_ratio
+ *
+ */
+
+#define WEIGHT_FACTOR 30
+/* This constant prevents the "PositionOffset" from reaching a big value */
+#define OFFSET_POSITION_CLAMP	120
+static void ad714x_wheel_cal_abs_pos(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
+	struct ad714x_wheel_drv *sw = &ad714x->sw->wheel[idx];
+	int stage_num = hw->end_stage - hw->start_stage + 1;
+	int second_before, first_before, highest, first_after, second_after;
+	int a_param, b_param;
+
+	/* Calculate Mean value */
+
+	second_before = (sw->highest_stage + stage_num - 2) % stage_num;
+	first_before = (sw->highest_stage + stage_num - 1) % stage_num;
+	highest = sw->highest_stage;
+	first_after = (sw->highest_stage + stage_num + 1) % stage_num;
+	second_after = (sw->highest_stage + stage_num + 2) % stage_num;
+
+	if (((sw->highest_stage - hw->start_stage) > 1) &&
+			((hw->end_stage - sw->highest_stage) > 1)) {
+		a_param = ad714x->sensor_val[second_before] *
+			(second_before - hw->start_stage + 3) +
+			ad714x->sensor_val[first_before] *
+			(second_before - hw->start_stage + 3) +
+			ad714x->sensor_val[highest] *
+			(second_before - hw->start_stage + 3) +
+			ad714x->sensor_val[first_after] *
+			(first_after - hw->start_stage + 3) +
+			ad714x->sensor_val[second_after] *
+			(second_after - hw->start_stage + 3);
+	} else {
+		a_param = ad714x->sensor_val[second_before] *
+			(second_before - hw->start_stage + 1) +
+			ad714x->sensor_val[first_before] *
+			(second_before - hw->start_stage + 2) +
+			ad714x->sensor_val[highest] *
+			(second_before - hw->start_stage + 3) +
+			ad714x->sensor_val[first_after] *
+			(first_after - hw->start_stage + 4) +
+			ad714x->sensor_val[second_after] *
+			(second_after - hw->start_stage + 5);
+	}
+	a_param *= WEIGHT_FACTOR;
+
+	b_param = ad714x->sensor_val[second_before] +
+		ad714x->sensor_val[first_before] +
+		ad714x->sensor_val[highest] +
+		ad714x->sensor_val[first_after] +
+		ad714x->sensor_val[second_after];
+
+	sw->pre_mean_value = sw->mean_value;
+	sw->mean_value = a_param / b_param;
+
+	/* Calculate the offset */
+
+	if ((sw->pre_highest_stage == hw->end_stage) &&
+			(sw->highest_stage == hw->start_stage))
+		sw->pos_offset = sw->mean_value;
+	else if ((sw->pre_highest_stage == hw->start_stage) &&
+			(sw->highest_stage == hw->end_stage))
+		sw->pos_offset = sw->pre_mean_value;
+	if (sw->pos_offset > OFFSET_POSITION_CLAMP)
+		sw->pos_offset = OFFSET_POSITION_CLAMP;
+
+	/* Calculate the mean value without the offset */
+
+	sw->pre_mean_value_no_offset = sw->mean_value_no_offset;
+	sw->mean_value_no_offset = sw->mean_value - sw->pos_offset;
+	if (sw->mean_value_no_offset < 0)
+		sw->mean_value_no_offset = 0;
+
+	/* Calculate ratio to scale down to NUMBER_OF_WANTED_POSITIONS */
+
+	if ((sw->pre_highest_stage == hw->end_stage) &&
+			(sw->highest_stage == hw->start_stage))
+		sw->pos_ratio = (sw->pre_mean_value_no_offset * 100) /
+			hw->max_coord;
+	else if ((sw->pre_highest_stage == hw->start_stage) &&
+			(sw->highest_stage == hw->end_stage))
+		sw->pos_ratio = (sw->mean_value_no_offset * 100) /
+			hw->max_coord;
+	sw->abs_pos = (sw->mean_value_no_offset * 100) / sw->pos_ratio;
+	if (sw->abs_pos > hw->max_coord)
+		sw->abs_pos = hw->max_coord;
+}
+
+static void ad714x_wheel_cal_flt_pos(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
+	struct ad714x_wheel_drv *sw = &ad714x->sw->wheel[idx];
+	if (((sw->pre_highest_stage == hw->end_stage) &&
+				(sw->highest_stage == hw->start_stage)) ||
+			((sw->pre_highest_stage == hw->start_stage) &&
+			 (sw->highest_stage == hw->end_stage)))
+		sw->flt_pos = sw->abs_pos;
+	else
+		sw->flt_pos = ((sw->flt_pos * 30) + (sw->abs_pos * 71)) / 100;
+
+	if (sw->flt_pos > hw->max_coord)
+		sw->flt_pos = hw->max_coord;
+}
+
+static void ad714x_wheel_use_com_int(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
+	ad714x_use_com_int(ad714x, hw->start_stage, hw->end_stage);
+}
+
+static void ad714x_wheel_use_thr_int(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
+	ad714x_use_thr_int(ad714x, hw->start_stage, hw->end_stage);
+}
+
+static void ad714x_wheel_state_machine(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
+	struct ad714x_wheel_drv *sw = &ad714x->sw->wheel[idx];
+	unsigned short h_state, c_state;
+	unsigned short mask;
+
+	mask = ((1 << (hw->end_stage + 1)) - 1) - ((1 << hw->start_stage) - 1);
+
+	h_state = ad714x->h_state & mask;
+	c_state = ad714x->c_state & mask;
+
+	switch (sw->state) {
+	case IDLE:
+		if (h_state) {
+			sw->state = JITTER;
+			/* In End of Conversion interrupt mode, the AD714X
+			 * continuously generates hardware interrupts.
+			 */
+			ad714x_wheel_use_com_int(ad714x, idx);
+			dev_dbg(ad714x->dev, "wheel %d touched\n", idx);
+		}
+		break;
+	case JITTER:
+		if (c_state == mask)	{
+			ad714x_wheel_cal_sensor_val(ad714x, idx);
+			ad714x_wheel_cal_highest_stage(ad714x, idx);
+			ad714x_wheel_cal_abs_pos(ad714x, idx);
+			sw->flt_pos = sw->abs_pos;
+			sw->state = ACTIVE;
+		}
+		break;
+	case ACTIVE:
+		if (c_state == mask) {
+			if (h_state) {
+				ad714x_wheel_cal_sensor_val(ad714x, idx);
+				ad714x_wheel_cal_highest_stage(ad714x, idx);
+				ad714x_wheel_cal_abs_pos(ad714x, idx);
+				ad714x_wheel_cal_flt_pos(ad714x, idx);
+
+				input_report_abs(sw->input, ABS_WHEEL,
+					sw->abs_pos);
+				input_report_key(sw->input, BTN_TOUCH, 1);
+			} else {
+				/* When the user lifts off the sensor, configure
+				 * the AD714X back to threshold interrupt mode.
+				 */
+				ad714x_wheel_use_thr_int(ad714x, idx);
+				sw->state = IDLE;
+				input_report_key(sw->input, BTN_TOUCH, 0);
+
+				dev_dbg(ad714x->dev, "wheel %d released\n",
+					idx);
+			}
+			input_sync(sw->input);
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+static void touchpad_cal_sensor_val(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
+	int i;
+
+	for (i = hw->x_start_stage; i <= hw->x_end_stage; i++) {
+		ad714x->read(ad714x->dev, CDC_RESULT_S0 + i,
+				&ad714x->adc_reg[i]);
+		ad714x->read(ad714x->dev,
+				STAGE0_AMBIENT + i * PER_STAGE_REG_NUM,
+				&ad714x->amb_reg[i]);
+		if (ad714x->adc_reg[i] > ad714x->amb_reg[i])
+			ad714x->sensor_val[i] = ad714x->adc_reg[i] -
+				ad714x->amb_reg[i];
+		else
+			ad714x->sensor_val[i] = 0;
+	}
+}
+
+static void touchpad_cal_highest_stage(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
+	struct ad714x_touchpad_drv *sw = &ad714x->sw->touchpad[idx];
+
+	sw->x_highest_stage = ad714x_cal_highest_stage(ad714x,
+		hw->x_start_stage, hw->x_end_stage);
+	sw->y_highest_stage = ad714x_cal_highest_stage(ad714x,
+		hw->y_start_stage, hw->y_end_stage);
+
+	dev_dbg(ad714x->dev,
+		"touchpad %d x_highest_stage:%d, y_highest_stage:%d\n",
+		idx, sw->x_highest_stage, sw->y_highest_stage);
+}
+
+/* If 2 fingers are touching the sensor then 2 peaks can be observed in the
+ * distribution.
+ * The arithmetic doesn't support to get absolute coordinates for multi-touch
+ * yet.
+ */
+static int touchpad_check_second_peak(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
+	struct ad714x_touchpad_drv *sw = &ad714x->sw->touchpad[idx];
+	int i;
+
+	for (i = hw->x_start_stage; i < sw->x_highest_stage; i++) {
+		if ((ad714x->sensor_val[i] - ad714x->sensor_val[i + 1])
+			> (ad714x->sensor_val[i + 1] / 10))
+			return 1;
+	}
+
+	for (i = sw->x_highest_stage; i < hw->x_end_stage; i++) {
+		if ((ad714x->sensor_val[i + 1] - ad714x->sensor_val[i])
+			> (ad714x->sensor_val[i] / 10))
+			return 1;
+	}
+
+	for (i = hw->y_start_stage; i < sw->y_highest_stage; i++) {
+		if ((ad714x->sensor_val[i] - ad714x->sensor_val[i + 1])
+			> (ad714x->sensor_val[i + 1] / 10))
+			return 1;
+	}
+
+	for (i = sw->y_highest_stage; i < hw->y_end_stage; i++) {
+		if ((ad714x->sensor_val[i + 1] - ad714x->sensor_val[i])
+			> (ad714x->sensor_val[i] / 10))
+			return 1;
+	}
+
+	return 0;
+}
+
+/* If only one finger is used to activate the touch pad then only 1 peak will be
+ * registered in the distribution. This peak and the 2 adjacent sensors will be
+ * used in the calculation of the absolute position. This will prevent hand
+ * shadows to affect the absolute position calculation.
+ */
+static void touchpad_cal_abs_pos(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
+	struct ad714x_touchpad_drv *sw = &ad714x->sw->touchpad[idx];
+
+	sw->x_abs_pos = ad714x_cal_abs_pos(ad714x, hw->x_start_stage,
+			hw->x_end_stage, sw->x_highest_stage, hw->x_max_coord);
+	sw->y_abs_pos = ad714x_cal_abs_pos(ad714x, hw->y_start_stage,
+			hw->y_end_stage, sw->y_highest_stage, hw->y_max_coord);
+
+	dev_dbg(ad714x->dev, "touchpad %d absolute position:(%d, %d)\n", idx,
+			sw->x_abs_pos, sw->y_abs_pos);
+}
+
+static void touchpad_cal_flt_pos(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_touchpad_drv *sw = &ad714x->sw->touchpad[idx];
+
+	sw->x_flt_pos = (sw->x_flt_pos * (10 - 4) +
+			sw->x_abs_pos * 4)/10;
+	sw->y_flt_pos = (sw->y_flt_pos * (10 - 4) +
+			sw->y_abs_pos * 4)/10;
+
+	dev_dbg(ad714x->dev, "touchpad %d filter position:(%d, %d)\n",
+			idx, sw->x_flt_pos, sw->y_flt_pos);
+}
+
+/* To prevent distortion from showing in the absolute position, it is
+ * necessary to detect the end points. When endpoints are detected, the
+ * driver stops updating the status variables with absolute positions.
+ * End points are detected on the 4 edges of the touchpad sensor. The
+ * method to detect them is the same for all 4.
+ * To detect the end points, the firmware computes the difference in
+ * percent between the sensor on the edge and the adjacent one. The
+ * difference is calculated in percent in order to make the end point
+ * detection independent of the pressure.
+ */
+
+#define LEFT_END_POINT_DETECTION_LEVEL                  550
+#define RIGHT_END_POINT_DETECTION_LEVEL                 750
+#define LEFT_RIGHT_END_POINT_DEAVTIVALION_LEVEL         850
+#define TOP_END_POINT_DETECTION_LEVEL                   550
+#define BOTTOM_END_POINT_DETECTION_LEVEL                950
+#define TOP_BOTTOM_END_POINT_DEAVTIVALION_LEVEL         700
+static int touchpad_check_endpoint(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
+	struct ad714x_touchpad_drv *sw  = &ad714x->sw->touchpad[idx];
+	int percent_sensor_diff;
+
+	/* left endpoint detect */
+	percent_sensor_diff = (ad714x->sensor_val[hw->x_start_stage] -
+			ad714x->sensor_val[hw->x_start_stage + 1]) * 100 /
+			ad714x->sensor_val[hw->x_start_stage + 1];
+	if (!sw->left_ep) {
+		if (percent_sensor_diff >= LEFT_END_POINT_DETECTION_LEVEL)  {
+			sw->left_ep = 1;
+			sw->left_ep_val =
+				ad714x->sensor_val[hw->x_start_stage + 1];
+		}
+	} else {
+		if ((percent_sensor_diff < LEFT_END_POINT_DETECTION_LEVEL) &&
+		(ad714x->sensor_val[hw->x_start_stage + 1] >
+		LEFT_RIGHT_END_POINT_DEAVTIVALION_LEVEL + sw->left_ep_val))
+			sw->left_ep = 0;
+	}
+
+	/* right endpoint detect */
+	percent_sensor_diff = (ad714x->sensor_val[hw->x_end_stage] -
+			ad714x->sensor_val[hw->x_end_stage - 1]) * 100 /
+			ad714x->sensor_val[hw->x_end_stage - 1];
+	if (!sw->right_ep) {
+		if (percent_sensor_diff >= RIGHT_END_POINT_DETECTION_LEVEL)  {
+			sw->right_ep = 1;
+			sw->right_ep_val =
+				ad714x->sensor_val[hw->x_end_stage - 1];
+		}
+	} else {
+		if ((percent_sensor_diff < RIGHT_END_POINT_DETECTION_LEVEL) &&
+		(ad714x->sensor_val[hw->x_end_stage - 1] >
+		LEFT_RIGHT_END_POINT_DEAVTIVALION_LEVEL + sw->right_ep_val))
+			sw->right_ep = 0;
+	}
+
+	/* top endpoint detect */
+	percent_sensor_diff = (ad714x->sensor_val[hw->y_start_stage] -
+			ad714x->sensor_val[hw->y_start_stage + 1]) * 100 /
+			ad714x->sensor_val[hw->y_start_stage + 1];
+	if (!sw->top_ep) {
+		if (percent_sensor_diff >= TOP_END_POINT_DETECTION_LEVEL)  {
+			sw->top_ep = 1;
+			sw->top_ep_val =
+				ad714x->sensor_val[hw->y_start_stage + 1];
+		}
+	} else {
+		if ((percent_sensor_diff < TOP_END_POINT_DETECTION_LEVEL) &&
+		(ad714x->sensor_val[hw->y_start_stage + 1] >
+		TOP_BOTTOM_END_POINT_DEAVTIVALION_LEVEL + sw->top_ep_val))
+			sw->top_ep = 0;
+	}
+
+	/* bottom endpoint detect */
+	percent_sensor_diff = (ad714x->sensor_val[hw->y_end_stage] -
+		ad714x->sensor_val[hw->y_end_stage - 1]) * 100 /
+		ad714x->sensor_val[hw->y_end_stage - 1];
+	if (!sw->bottom_ep) {
+		if (percent_sensor_diff >= BOTTOM_END_POINT_DETECTION_LEVEL)  {
+			sw->bottom_ep = 1;
+			sw->bottom_ep_val =
+				ad714x->sensor_val[hw->y_end_stage - 1];
+		}
+	} else {
+		if ((percent_sensor_diff < BOTTOM_END_POINT_DETECTION_LEVEL) &&
+		(ad714x->sensor_val[hw->y_end_stage - 1] >
+		 TOP_BOTTOM_END_POINT_DEAVTIVALION_LEVEL + sw->bottom_ep_val))
+			sw->bottom_ep = 0;
+	}
+
+	return sw->left_ep || sw->right_ep || sw->top_ep || sw->bottom_ep;
+}
+
+static void touchpad_use_com_int(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
+	ad714x_use_com_int(ad714x, hw->x_start_stage, hw->x_end_stage);
+}
+
+static void touchpad_use_thr_int(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
+	ad714x_use_thr_int(ad714x, hw->x_start_stage, hw->x_end_stage);
+	ad714x_use_thr_int(ad714x, hw->y_start_stage, hw->y_end_stage);
+}
+
+static void ad714x_touchpad_state_machine(struct ad714x_chip *ad714x, int idx)
+{
+	struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
+	struct ad714x_touchpad_drv *sw = &ad714x->sw->touchpad[idx];
+	unsigned short h_state, c_state;
+	unsigned short mask;
+
+	mask = (((1 << (hw->x_end_stage + 1)) - 1) -
+		((1 << hw->x_start_stage) - 1)) +
+		(((1 << (hw->y_end_stage + 1)) - 1) -
+		((1 << hw->y_start_stage) - 1));
+
+	h_state = ad714x->h_state & mask;
+	c_state = ad714x->c_state & mask;
+
+	switch (sw->state) {
+	case IDLE:
+		if (h_state) {
+			sw->state = JITTER;
+			/* In End of Conversion interrupt mode, the AD714X
+			 * continuously generates hardware interrupts.
+			 */
+			touchpad_use_com_int(ad714x, idx);
+			dev_dbg(ad714x->dev, "touchpad %d touched\n", idx);
+		}
+		break;
+	case JITTER:
+		if (c_state == mask) {
+			touchpad_cal_sensor_val(ad714x, idx);
+			touchpad_cal_highest_stage(ad714x, idx);
+			if ((!touchpad_check_second_peak(ad714x, idx)) &&
+				(!touchpad_check_endpoint(ad714x, idx))) {
+				dev_dbg(ad714x->dev,
+					"touchpad%d, 2 fingers or endpoint\n",
+					idx);
+				touchpad_cal_abs_pos(ad714x, idx);
+				sw->x_flt_pos = sw->x_abs_pos;
+				sw->y_flt_pos = sw->y_abs_pos;
+				sw->state = ACTIVE;
+			}
+		}
+		break;
+	case ACTIVE:
+		if (c_state == mask) {
+			if (h_state) {
+				touchpad_cal_sensor_val(ad714x, idx);
+				touchpad_cal_highest_stage(ad714x, idx);
+				if ((!touchpad_check_second_peak(ad714x, idx))
+				  && (!touchpad_check_endpoint(ad714x, idx))) {
+					touchpad_cal_abs_pos(ad714x, idx);
+					touchpad_cal_flt_pos(ad714x, idx);
+					input_report_abs(sw->input, ABS_X,
+						sw->x_flt_pos);
+					input_report_abs(sw->input, ABS_Y,
+						sw->y_flt_pos);
+					input_report_key(sw->input, BTN_TOUCH,
+						1);
+				}
+			} else {
+				/* When the user lifts off the sensor, configure
+				 * the AD714X back to threshold interrupt mode.
+				 */
+				touchpad_use_thr_int(ad714x, idx);
+				sw->state = IDLE;
+				input_report_key(sw->input, BTN_TOUCH, 0);
+				dev_dbg(ad714x->dev, "touchpad %d released\n",
+					idx);
+			}
+			input_sync(sw->input);
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+static int ad714x_hw_detect(struct ad714x_chip *ad714x)
+{
+	unsigned short data;
+
+	ad714x->read(ad714x->dev, AD714X_PARTID_REG, &data);
+	switch (data & 0xFFF0) {
+	case AD7147_PARTID:
+		ad714x->product = 0x7147;
+		ad714x->version = data & 0xF;
+		dev_info(ad714x->dev, "found AD7147 captouch, rev:%d\n",
+				ad714x->version);
+		return 0;
+	case AD7142_PARTID:
+		ad714x->product = 0x7142;
+		ad714x->version = data & 0xF;
+		dev_info(ad714x->dev, "found AD7142 captouch, rev:%d\n",
+				ad714x->version);
+		return 0;
+	default:
+		dev_err(ad714x->dev,
+			"fail to detect AD714X captouch, read ID is %04x\n",
+			data);
+		return -ENODEV;
+	}
+}
+
+static void ad714x_hw_init(struct ad714x_chip *ad714x)
+{
+	int i, j;
+	unsigned short reg_base;
+	unsigned short data;
+
+	/* configuration CDC and interrupts*/
+
+	for (i = 0; i < STAGE_NUM; i++) {
+		reg_base = AD714X_STAGECFG_REG + i * STAGE_CFGREG_NUM;
+		for (j = 0; j < STAGE_CFGREG_NUM; j++)
+			ad714x->write(ad714x->dev, reg_base + j,
+					ad714x->hw->stage_cfg_reg[i][j]);
+	}
+
+	for (i = 0; i < SYS_CFGREG_NUM; i++)
+		ad714x->write(ad714x->dev, AD714X_SYSCFG_REG + i,
+			ad714x->hw->sys_cfg_reg[i]);
+	for (i = 0; i < SYS_CFGREG_NUM; i++)
+		ad714x->read(ad714x->dev, AD714X_SYSCFG_REG + i,
+			&data);
+
+	ad714x->write(ad714x->dev, AD714X_STG_CAL_EN_REG, 0xFFF);
+
+	/* clear all interrupts */
+	ad714x->read(ad714x->dev, STG_LOW_INT_STA_REG, &data);
+	ad714x->read(ad714x->dev, STG_HIGH_INT_STA_REG, &data);
+	ad714x->read(ad714x->dev, STG_COM_INT_STA_REG, &data);
+}
+
+static irqreturn_t ad714x_interrupt_thread(int irq, void *data)
+{
+	struct ad714x_chip *ad714x = data;
+	int i;
+
+	mutex_lock(&ad714x->mutex);
+
+	ad714x->read(ad714x->dev, STG_LOW_INT_STA_REG, &ad714x->l_state);
+	ad714x->read(ad714x->dev, STG_HIGH_INT_STA_REG, &ad714x->h_state);
+	ad714x->read(ad714x->dev, STG_COM_INT_STA_REG, &ad714x->c_state);
+
+	for (i = 0; i < ad714x->hw->button_num; i++)
+		ad714x_button_state_machine(ad714x, i);
+	for (i = 0; i < ad714x->hw->slider_num; i++)
+		ad714x_slider_state_machine(ad714x, i);
+	for (i = 0; i < ad714x->hw->wheel_num; i++)
+		ad714x_wheel_state_machine(ad714x, i);
+	for (i = 0; i < ad714x->hw->touchpad_num; i++)
+		ad714x_touchpad_state_machine(ad714x, i);
+
+	mutex_unlock(&ad714x->mutex);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t ad714x_interrupt(int irq, void *data)
+{
+	return IRQ_WAKE_THREAD;
+}
+
+#define MAX_DEVICE_NUM 8
+int ad714x_probe(struct ad714x_chip **pad714x, struct device *dev,
+	u16 bus_type, int irq, ad714x_read_t read, ad714x_write_t write)
+{
+	int ret, i, alloc_idx;
+	struct input_dev *input[MAX_DEVICE_NUM];
+
+	struct ad714x_platform_data *plat_data;
+	struct ad714x_chip *ad714x;
+	void *drv_mem;
+
+	struct ad714x_button_drv *bt_drv;
+	struct ad714x_slider_drv *sd_drv;
+	struct ad714x_wheel_drv *wl_drv;
+	struct ad714x_touchpad_drv *tp_drv;
+
+	plat_data = dev->platform_data;
+	if (dev->platform_data == NULL) {
+		dev_err(dev, "platform data for ad714x doesn't exist\n");
+		return -ENODEV;
+	}
+
+	*pad714x = ad714x = kzalloc(sizeof(*ad714x) + sizeof(*ad714x->sw) +
+			sizeof(*sd_drv) * plat_data->slider_num +
+			sizeof(*wl_drv) * plat_data->wheel_num +
+			sizeof(*tp_drv) * plat_data->touchpad_num +
+			sizeof(*bt_drv) * plat_data->button_num, GFP_KERNEL);
+	if (!ad714x)
+		return -ENOMEM;
+	ad714x->hw = plat_data;
+
+	drv_mem = ad714x + 1;
+	ad714x->sw = drv_mem;
+	drv_mem += sizeof(*ad714x->sw);
+	ad714x->sw->slider = sd_drv = drv_mem;
+	drv_mem += sizeof(*sd_drv) * ad714x->hw->slider_num;
+	ad714x->sw->wheel = wl_drv = drv_mem;
+	drv_mem += sizeof(*wl_drv) * ad714x->hw->wheel_num;
+	ad714x->sw->touchpad = tp_drv = drv_mem;
+	drv_mem += sizeof(*tp_drv) * ad714x->hw->touchpad_num;
+	ad714x->sw->button = bt_drv = drv_mem;
+	drv_mem += sizeof(*bt_drv) * ad714x->hw->button_num;
+
+	ad714x->read = read;
+	ad714x->write = write;
+	ad714x->irq = irq;
+	ad714x->dev = dev;
+
+	ret = ad714x_hw_detect(ad714x);
+	if (ret)
+		goto det_err;
+
+	/* initilize and request sw/hw resources */
+
+	ad714x_hw_init(ad714x);
+	mutex_init(&ad714x->mutex);
+
+	if (ad714x->irq > 0) {
+		ret = request_threaded_irq(ad714x->irq, ad714x_interrupt,
+				ad714x_interrupt_thread, IRQF_TRIGGER_FALLING,
+				"ad714x_captouch", ad714x);
+		if (ret) {
+			dev_err(dev, "can't allocate irq %d\n",
+					ad714x->irq);
+			goto fail_irq;
+		}
+	} else {
+		dev_err(dev, "IRQ not configured!\n");
+		ret = -EINVAL;
+		goto det_err;
+	}
+
+	/*
+	 * Allocate and register AD714X input device
+	 */
+	alloc_idx = 0;
+
+	/* a slider uses one input_dev instance */
+	if (ad714x->hw->slider_num > 0) {
+		struct ad714x_slider_plat *sd_plat = ad714x->hw->slider;
+
+		for (i = 0; i < ad714x->hw->slider_num; i++) {
+			sd_drv[i].input = input[alloc_idx] = input_allocate_device();
+			if (!input[alloc_idx]) {
+				ret = -ENOMEM;
+				goto fail_alloc_reg;
+			}
+
+			__set_bit(EV_ABS, input[alloc_idx]->evbit);
+			__set_bit(EV_KEY, input[alloc_idx]->evbit);
+			__set_bit(ABS_X, input[alloc_idx]->absbit);
+			__set_bit(BTN_TOUCH, input[alloc_idx]->keybit);
+			input_set_abs_params(input[alloc_idx], ABS_X, 0,
+					sd_plat->max_coord, 0, 0);
+
+			input[alloc_idx]->id.bustype = bus_type;
+			input[alloc_idx]->id.product = ad714x->product;
+			input[alloc_idx]->id.version = ad714x->version;
+
+			ret = input_register_device(input[alloc_idx]);
+			if (ret)
+				goto fail_alloc_reg;
+
+			alloc_idx++;
+		}
+	}
+
+	/* a wheel uses one input_dev instance */
+	if (ad714x->hw->wheel_num > 0) {
+		struct ad714x_wheel_plat *wl_plat = ad714x->hw->wheel;
+
+		for (i = 0; i < ad714x->hw->wheel_num; i++) {
+			wl_drv[i].input = input[alloc_idx] = input_allocate_device();
+			if (!input[alloc_idx]) {
+				ret = -ENOMEM;
+				goto fail_alloc_reg;
+			}
+
+			__set_bit(EV_KEY, input[alloc_idx]->evbit);
+			__set_bit(EV_ABS, input[alloc_idx]->evbit);
+			__set_bit(ABS_WHEEL, input[alloc_idx]->absbit);
+			__set_bit(BTN_TOUCH, input[alloc_idx]->keybit);
+			input_set_abs_params(input[alloc_idx], ABS_WHEEL, 0,
+					wl_plat->max_coord, 0, 0);
+
+			input[alloc_idx]->id.bustype = bus_type;
+			input[alloc_idx]->id.product = ad714x->product;
+			input[alloc_idx]->id.version = ad714x->version;
+
+			ret = input_register_device(input[alloc_idx]);
+			if (ret)
+				goto fail_alloc_reg;
+
+			alloc_idx++;
+		}
+	}
+
+	/* a touchpad uses one input_dev instance */
+	if (ad714x->hw->touchpad_num > 0) {
+		struct ad714x_touchpad_plat *tp_plat = ad714x->hw->touchpad;
+
+		for (i = 0; i < ad714x->hw->touchpad_num; i++) {
+			tp_drv[i].input = input[alloc_idx] = input_allocate_device();
+			if (!input[alloc_idx]) {
+				ret = -ENOMEM;
+				goto fail_alloc_reg;
+			}
+
+			__set_bit(EV_ABS, input[alloc_idx]->evbit);
+			__set_bit(EV_KEY, input[alloc_idx]->evbit);
+			__set_bit(ABS_X, input[alloc_idx]->absbit);
+			__set_bit(ABS_Y, input[alloc_idx]->absbit);
+			__set_bit(BTN_TOUCH, input[alloc_idx]->keybit);
+			input_set_abs_params(input[alloc_idx], ABS_X, 0,
+					tp_plat->x_max_coord, 0, 0);
+			input_set_abs_params(input[alloc_idx], ABS_Y, 0,
+					tp_plat->y_max_coord, 0, 0);
+
+			input[alloc_idx]->id.bustype = bus_type;
+			input[alloc_idx]->id.product = ad714x->product;
+			input[alloc_idx]->id.version = ad714x->version;
+
+			ret = input_register_device(input[alloc_idx]);
+			if (ret)
+				goto fail_alloc_reg;
+
+			alloc_idx++;
+		}
+	}
+
+	/* all buttons use one input node */
+	if (ad714x->hw->button_num > 0) {
+		struct ad714x_button_plat *bt_plat = ad714x->hw->button;
+
+		input[alloc_idx] = input_allocate_device();
+		if (!input[alloc_idx]) {
+			ret = -ENOMEM;
+			goto fail_alloc_reg;
+		}
+
+		__set_bit(EV_KEY, input[alloc_idx]->evbit);
+		for (i = 0; i < ad714x->hw->button_num; i++) {
+			bt_drv[i].input = input[alloc_idx];
+			__set_bit(bt_plat[i].keycode, input[alloc_idx]->keybit);
+		}
+
+		input[alloc_idx]->id.bustype = bus_type;
+		input[alloc_idx]->id.product = ad714x->product;
+		input[alloc_idx]->id.version = ad714x->version;
+
+		ret = input_register_device(input[alloc_idx]);
+		if (ret)
+			goto fail_alloc_reg;
+
+		alloc_idx++;
+	}
+
+
+	return 0;
+
+ fail_alloc_reg:
+	dev_err(dev, "failed to setup AD714x input device %i\n", alloc_idx);
+	for (i = 0; i < alloc_idx - 1; i++)
+		input_unregister_device(input[i]);
+	input_free_device(input[alloc_idx]);
+
+	free_irq(ad714x->irq, ad714x);
+ fail_irq:
+ det_err:
+	kfree(ad714x);
+	return ret;
+}
+EXPORT_SYMBOL(ad714x_probe);
+
+int ad714x_remove(struct ad714x_chip *ad714x)
+{
+	int i;
+
+	/* unregister and free all input devices */
+
+	for (i = 0; i < ad714x->hw->slider_num; i++)
+		input_unregister_device(ad714x->sw->slider[i].input);
+
+	for (i = 0; i < ad714x->hw->wheel_num; i++)
+		input_unregister_device(ad714x->sw->wheel[i].input);
+
+	for (i = 0; i < ad714x->hw->touchpad_num; i++)
+		input_unregister_device(ad714x->sw->touchpad[i].input);
+
+	if (ad714x->hw->button_num)
+		input_unregister_device(ad714x->sw->button[0].input);
+
+	free_irq(ad714x->irq, ad714x);
+
+	kfree(ad714x);
+
+	return 0;
+}
+EXPORT_SYMBOL(ad714x_remove);
+
+#ifdef CONFIG_PM
+int ad714x_disable(struct ad714x_chip *ad714x)
+{
+	unsigned short data;
+
+	dev_dbg(ad714x->dev, "%s enter\n", __func__);
+
+	mutex_lock(&ad714x->mutex);
+
+	data = ad714x->hw->sys_cfg_reg[AD714X_PWR_CTRL] | 0x3;
+	ad714x->write(ad714x->dev, AD714X_PWR_CTRL, data);
+
+	mutex_unlock(&ad714x->mutex);
+
+	return 0;
+}
+EXPORT_SYMBOL(ad714x_disable);
+
+int ad714x_enable(struct ad714x_chip *ad714x)
+{
+	unsigned short data;
+
+	dev_dbg(ad714x->dev, "%s enter\n", __func__);
+
+	mutex_lock(&ad714x->mutex);
+
+	/* resume to non-shutdown mode */
+
+	ad714x->write(ad714x->dev, AD714X_PWR_CTRL,
+			ad714x->hw->sys_cfg_reg[AD714X_PWR_CTRL]);
+
+	/* make sure the interrupt output line is not low level after resume,
+	 * otherwise we will get no chance to enter falling-edge irq again
+	 */
+
+	ad714x->read(ad714x->dev, STG_LOW_INT_STA_REG, &data);
+	ad714x->read(ad714x->dev, STG_HIGH_INT_STA_REG, &data);
+	ad714x->read(ad714x->dev, STG_COM_INT_STA_REG, &data);
+
+	mutex_unlock(&ad714x->mutex);
+
+	return 0;
+}
+EXPORT_SYMBOL(ad714x_enable);
+#endif
+
+MODULE_DESCRIPTION("Analog Devices AD714X Capacitance Touch Sensor Driver");
+MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/ad714x.h b/drivers/input/misc/ad714x.h
new file mode 100644
index 0000000..3fc5a16
--- /dev/null
+++ b/drivers/input/misc/ad714x.h
@@ -0,0 +1,25 @@
+/*
+ * AD714X CapTouch Programmable Controller driver (bus interfaces)
+ *
+ * Copyright 2009 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#ifndef _AD714X_H_
+#define _AD714X_H_
+
+#include <linux/types.h>
+
+struct device;
+struct ad714x_chip;
+typedef int (ad714x_read_t) (struct device *, unsigned short, unsigned short *);
+typedef int (ad714x_write_t) (struct device *, unsigned short, unsigned short);
+
+int ad714x_disable(struct ad714x_chip *ad714x);
+int ad714x_enable(struct ad714x_chip *ad714x);
+int ad714x_probe(struct ad714x_chip **pad714x, struct device *dev,
+	u16 bus_type, int irq, ad714x_read_t read, ad714x_write_t write);
+int ad714x_remove(struct ad714x_chip *ad714x);
+
+#endif
diff --git a/include/linux/input.h b/include/linux/input.h
index 0ccfc30..8b38cfa 100644
--- a/include/linux/input.h
+++ b/include/linux/input.h
@@ -756,6 +756,7 @@ struct input_absinfo {
 #define BUS_HOST		0x19
 #define BUS_GSC			0x1A
 #define BUS_ATARI		0x1B
+#define BUS_SPI			0x1C
 
 /*
  * MT_TOOL types
diff --git a/include/linux/input/ad714x.h b/include/linux/input/ad714x.h
new file mode 100644
index 0000000..0cbe5e8
--- /dev/null
+++ b/include/linux/input/ad714x.h
@@ -0,0 +1,63 @@
+/*
+ * include/linux/input/ad714x.h
+ *
+ * AD714x is very flexible, it can be used as buttons, scrollwheel,
+ * slider, touchpad at the same time. That depends on the boards.
+ * The platform_data for the device's "struct device" holds this
+ * information.
+ *
+ * Copyright 2009 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#ifndef __LINUX_INPUT_AD714X_H__
+#define __LINUX_INPUT_AD714X_H__
+
+#define STAGE_NUM              12
+#define STAGE_CFGREG_NUM       8
+#define SYS_CFGREG_NUM         8
+
+/* board information which need be initialized in arch/mach... */
+struct ad714x_slider_plat {
+	int start_stage;
+	int end_stage;
+	int max_coord;
+};
+
+struct ad714x_wheel_plat {
+	int start_stage;
+	int end_stage;
+	int max_coord;
+};
+
+struct ad714x_touchpad_plat {
+	int x_start_stage;
+	int x_end_stage;
+	int x_max_coord;
+
+	int y_start_stage;
+	int y_end_stage;
+	int y_max_coord;
+};
+
+struct ad714x_button_plat {
+	int keycode;
+	unsigned short l_mask;
+	unsigned short h_mask;
+};
+
+struct ad714x_platform_data {
+	int slider_num;
+	int wheel_num;
+	int touchpad_num;
+	int button_num;
+	struct ad714x_slider_plat *slider;
+	struct ad714x_wheel_plat *wheel;
+	struct ad714x_touchpad_plat *touchpad;
+	struct ad714x_button_plat *button;
+	unsigned short stage_cfg_reg[STAGE_NUM][STAGE_CFGREG_NUM];
+	unsigned short sys_cfg_reg[SYS_CFGREG_NUM];
+};
+
+#endif
-- 
1.6.5.1


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* Re: [PATCH v5] input/misc: add Analog Devices AD714x captouch input driver
       [not found]       ` <1256027864-26126-1-git-send-email-vapier-aBrp7R+bbdUdnm+yROfE0A@public.gmane.org>
@ 2009-12-15  4:52         ` Barry Song
  0 siblings, 0 replies; 21+ messages in thread
From: Barry Song @ 2009-12-15  4:52 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: uclinux-dist-devel-ZG0+EudsQA8dtHy/vicBwGD2FQJk+8+b,
	Mike Frysinger, Michael Hennerich,
	linux-input-u79uwXL29TY76Z2rM5mHXA

Hi Dmitry,
Is there any update for AD714X and ADXL345/346 patch?
Thanks
Barry

On Tue, Oct 20, 2009 at 4:37 PM, Mike Frysinger <vapier@gentoo.org> wrote:
> From: Bryan Wu <cooloney@kernel.org>
>
> AD7142 and AD7147 are integrated capacitance-to-digital converters
> (CDCs) with on-chip environmental calibration for use in systems
> requiring a novel user input method. The AD7142 and AD7147 can interface
> to external capacitance sensors implementing functions such as buttons,
> scrollwheels, sliders, touchpads and so on.
>
> The chips don't restrict the specific usage. Depending on the hardware
> connection, one special target board can include one or several these
> components. The platform_data for the device's "struct device" holds
> these information. The data-struct defined in head file descript the
> hardware feature of button/scrollwheel/slider/touchpad components on
> target boards, which need be filled in the arch/mach-/.
>
> As the result, the driver is independent of boards. It gets the
> components layout from the platform_data, registers related devices,
> fullfills the algorithms and state machines for these components and
> report related input events to up level.
>
> Signed-off-by: Bryan Wu <cooloney@kernel.org>
> Signed-off-by: Michael Hennerich <michael.hennerich@analog.com>
> Signed-off-by: Barry Song <21cnbao@gmail.com>
> Signed-off-by: Mike Frysinger <vapier@gentoo.org>
> ---
> v5
>        - remove dummy module entry points
>
>  drivers/input/misc/Kconfig      |   30 +
>  drivers/input/misc/Makefile     |    3 +
>  drivers/input/misc/ad714x-i2c.c |  134 ++++
>  drivers/input/misc/ad714x-spi.c |   96 +++
>  drivers/input/misc/ad714x.c     | 1305 +++++++++++++++++++++++++++++++++++++++
>  drivers/input/misc/ad714x.h     |   25 +
>  include/linux/input.h           |    1 +
>  include/linux/input/ad714x.h    |   63 ++
>  8 files changed, 1657 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/input/misc/ad714x-i2c.c
>  create mode 100644 drivers/input/misc/ad714x-spi.c
>  create mode 100644 drivers/input/misc/ad714x.c
>  create mode 100644 drivers/input/misc/ad714x.h
>  create mode 100644 include/linux/input/ad714x.h
>
> diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
> index 02f4f8f..fc4b0b9 100644
> --- a/drivers/input/misc/Kconfig
> +++ b/drivers/input/misc/Kconfig
> @@ -316,4 +316,34 @@ config INPUT_PCAP
>          To compile this driver as a module, choose M here: the
>          module will be called pcap_keys.
>
> +config INPUT_AD714X
> +       tristate "Analog Devices AD714x Capacitance Touch Sensor"
> +       help
> +         Say Y here if you want to support an AD7142/AD7147 touch sensor.
> +
> +         You should select a bus connection too.
> +
> +         To compile this driver as a module, choose M here: the
> +         module will be called ad714x.
> +
> +config INPUT_AD714X_I2C
> +       tristate "support I2C bus connection"
> +       depends on INPUT_AD714X && I2C
> +       default y
> +       help
> +         Say Y here if you have AD7142/AD7147 hooked to an I2C bus.
> +
> +         To compile this driver as a module, choose M here: the
> +         module will be called ad714x-i2c.
> +
> +config INPUT_AD714X_SPI
> +       tristate "support SPI bus connection"
> +       depends on INPUT_AD714X && SPI
> +       default y
> +       help
> +         Say Y here if you have AD7142/AD7147 hooked to a SPI bus.
> +
> +         To compile this driver as a module, choose M here: the
> +         module will be called ad714x-spi.
> +
>  endif
> diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
> index a8b8485..16db64a 100644
> --- a/drivers/input/misc/Makefile
> +++ b/drivers/input/misc/Makefile
> @@ -4,6 +4,9 @@
>
>  # Each configuration option enables a list of files.
>
> +obj-$(CONFIG_INPUT_AD714X)             += ad714x.o
> +obj-$(CONFIG_INPUT_AD714X_I2C)         += ad714x-i2c.o
> +obj-$(CONFIG_INPUT_AD714X_SPI)         += ad714x-spi.o
>  obj-$(CONFIG_INPUT_APANEL)             += apanel.o
>  obj-$(CONFIG_INPUT_ATI_REMOTE)         += ati_remote.o
>  obj-$(CONFIG_INPUT_ATI_REMOTE2)                += ati_remote2.o
> diff --git a/drivers/input/misc/ad714x-i2c.c b/drivers/input/misc/ad714x-i2c.c
> new file mode 100644
> index 0000000..596c199
> --- /dev/null
> +++ b/drivers/input/misc/ad714x-i2c.c
> @@ -0,0 +1,134 @@
> +/*
> + * AD714X CapTouch Programmable Controller driver (I2C bus)
> + *
> + * Copyright 2009 Analog Devices Inc.
> + *
> + * Licensed under the GPL-2 or later.
> + */
> +
> +#include <linux/input.h>       /* BUS_I2C */
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/types.h>
> +#include "ad714x.h"
> +
> +#ifdef CONFIG_PM
> +static int ad714x_i2c_suspend(struct i2c_client *client, pm_message_t message)
> +{
> +       return ad714x_disable(i2c_get_clientdata(client));
> +}
> +
> +static int ad714x_i2c_resume(struct i2c_client *client)
> +{
> +       return ad714x_enable(i2c_get_clientdata(client));
> +}
> +#else
> +# define ad714x_i2c_suspend NULL
> +# define ad714x_i2c_resume  NULL
> +#endif
> +
> +static int ad714x_i2c_write(struct device *dev, unsigned short reg,
> +               unsigned short data)
> +{
> +       struct i2c_client *client = to_i2c_client(dev);
> +       int ret = 0;
> +       u8 *_reg = (u8 *)&reg;
> +       u8 *_data = (u8 *)&data;
> +
> +       u8 tx[4] = {
> +               _reg[1],
> +               _reg[0],
> +               _data[1],
> +               _data[0]
> +       };
> +
> +       ret = i2c_master_send(client, tx, 4);
> +       if (ret < 0)
> +               dev_err(&client->dev, "I2C write error\n");
> +
> +       return ret;
> +}
> +
> +static int ad714x_i2c_read(struct device *dev, unsigned short reg,
> +               unsigned short *data)
> +{
> +       struct i2c_client *client = to_i2c_client(dev);
> +       int ret = 0;
> +       u8 *_reg = (u8 *)&reg;
> +       u8 *_data = (u8 *)data;
> +
> +       u8 tx[2] = {
> +               _reg[1],
> +               _reg[0]
> +       };
> +       u8 rx[2];
> +
> +       ret = i2c_master_send(client, tx, 2);
> +       if (ret < 0) {
> +               dev_err(&client->dev, "I2C read error\n");
> +               return ret;
> +       }
> +
> +       ret = i2c_master_recv(client, rx, 2);
> +       if (ret < 0) {
> +               dev_err(&client->dev, "I2C read error\n");
> +               return ret;
> +       }
> +
> +       _data[0] = rx[1];
> +       _data[1] = rx[0];
> +
> +       return ret;
> +}
> +
> +static int __devinit ad714x_i2c_probe(struct i2c_client *client,
> +               const struct i2c_device_id *id)
> +{
> +       int ret = 0;
> +       struct ad714x_chip *chip = NULL;
> +
> +       ret = ad714x_probe(&chip, &client->dev, BUS_I2C, client->irq,
> +               ad714x_i2c_read, ad714x_i2c_write);
> +       i2c_set_clientdata(client, chip);
> +
> +       return ret;
> +}
> +
> +static int __devexit ad714x_i2c_remove(struct i2c_client *client)
> +{
> +       return ad714x_remove(i2c_get_clientdata(client));
> +}
> +
> +static const struct i2c_device_id ad714x_id[] = {
> +       { "ad7142_captouch", 0 },
> +       { "ad7147_captouch", 0 },
> +       { }
> +};
> +MODULE_DEVICE_TABLE(i2c, ad714x_id);
> +
> +static struct i2c_driver ad714x_i2c_driver = {
> +       .driver = {
> +               .name = "ad714x_captouch",
> +       },
> +       .probe    = ad714x_i2c_probe,
> +       .remove   = __devexit_p(ad714x_i2c_remove),
> +       .suspend  = ad714x_i2c_suspend,
> +       .resume   = ad714x_i2c_resume,
> +       .id_table = ad714x_id,
> +};
> +
> +static __init int ad714x_i2c_init(void)
> +{
> +       return i2c_add_driver(&ad714x_i2c_driver);
> +}
> +module_init(ad714x_i2c_init);
> +
> +static __exit void ad714x_i2c_exit(void)
> +{
> +       i2c_del_driver(&ad714x_i2c_driver);
> +}
> +module_exit(ad714x_i2c_exit);
> +
> +MODULE_DESCRIPTION("Analog Devices AD714X Capacitance Touch Sensor I2C Bus Driver");
> +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/input/misc/ad714x-spi.c b/drivers/input/misc/ad714x-spi.c
> new file mode 100644
> index 0000000..2cea1e4
> --- /dev/null
> +++ b/drivers/input/misc/ad714x-spi.c
> @@ -0,0 +1,96 @@
> +/*
> + * AD714X CapTouch Programmable Controller driver (SPI bus)
> + *
> + * Copyright 2009 Analog Devices Inc.
> + *
> + * Licensed under the GPL-2 or later.
> + */
> +
> +#include <linux/input.h>       /* BUS_I2C */
> +#include <linux/module.h>
> +#include <linux/spi/spi.h>
> +#include <linux/types.h>
> +#include "ad714x.h"
> +
> +#define AD714x_SPI_CMD_PREFIX      0xE000   /* bits 15:11 */
> +#define AD714x_SPI_READ            BIT(10)
> +
> +#ifdef CONFIG_PM
> +static int ad714x_spi_suspend(struct spi_device *spi, pm_message_t message)
> +{
> +       return ad714x_disable(spi_get_drvdata(spi));
> +}
> +
> +static int ad714x_spi_resume(struct spi_device *spi)
> +{
> +       return ad714x_enable(spi_get_drvdata(spi));
> +}
> +#else
> +# define ad714x_spi_suspend NULL
> +# define ad714x_spi_resume  NULL
> +#endif
> +
> +static int ad714x_spi_read(struct device *dev, unsigned short reg,
> +               unsigned short *data)
> +{
> +       struct spi_device *spi = to_spi_device(dev);
> +       unsigned short tx = AD714x_SPI_CMD_PREFIX | AD714x_SPI_READ | reg;
> +
> +       return spi_write_then_read(spi, (u8 *)&tx, 2, (u8 *)data, 2);
> +}
> +
> +static int ad714x_spi_write(struct device *dev, unsigned short reg,
> +               unsigned short data)
> +{
> +       struct spi_device *spi = to_spi_device(dev);
> +       unsigned short tx[2] = {
> +               AD714x_SPI_CMD_PREFIX | reg,
> +               data
> +       };
> +
> +       return spi_write(spi, (u8 *)tx, 4);
> +}
> +
> +static int __devinit ad714x_spi_probe(struct spi_device *spi)
> +{
> +       int ret;
> +       struct ad714x_chip *chip = NULL;
> +
> +       ret = ad714x_probe(&chip, &spi->dev, BUS_SPI, spi->irq,
> +               ad714x_spi_read, ad714x_spi_write);
> +       spi_set_drvdata(spi, chip);
> +
> +       return ret;
> +}
> +
> +static int __devexit ad714x_spi_remove(struct spi_device *spi)
> +{
> +       return ad714x_remove(spi_get_drvdata(spi));
> +}
> +
> +static struct spi_driver ad714x_spi_driver = {
> +       .driver = {
> +               .name   = "ad714x_captouch",
> +               .owner  = THIS_MODULE,
> +       },
> +       .probe          = ad714x_spi_probe,
> +       .remove         = __devexit_p(ad714x_spi_remove),
> +       .suspend        = ad714x_spi_suspend,
> +       .resume         = ad714x_spi_resume,
> +};
> +
> +static __init int ad714x_spi_init(void)
> +{
> +       return spi_register_driver(&ad714x_spi_driver);
> +}
> +module_init(ad714x_spi_init);
> +
> +static __exit void ad714x_spi_exit(void)
> +{
> +       spi_unregister_driver(&ad714x_spi_driver);
> +}
> +module_exit(ad714x_spi_exit);
> +
> +MODULE_DESCRIPTION("Analog Devices AD714X Capacitance Touch Sensor SPI Bus Driver");
> +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/input/misc/ad714x.c b/drivers/input/misc/ad714x.c
> new file mode 100644
> index 0000000..d22509d
> --- /dev/null
> +++ b/drivers/input/misc/ad714x.c
> @@ -0,0 +1,1305 @@
> +/*
> + * AD714X CapTouch Programmable Controller driver
> + *
> + * Copyright 2009 Analog Devices Inc.
> + *
> + * Licensed under the GPL-2 or later.
> + */
> +
> +#include <linux/device.h>
> +#include <linux/init.h>
> +#include <linux/input.h>
> +#include <linux/interrupt.h>
> +#include <linux/input/ad714x.h>
> +#include "ad714x.h"
> +
> +#define AD714X_PWR_CTRL           0x0
> +#define AD714X_STG_CAL_EN_REG     0x1
> +#define AD714X_AMB_COMP_CTRL0_REG 0x2
> +#define AD714X_PARTID_REG         0x17
> +#define AD7147_PARTID             0x1470
> +#define AD7142_PARTID             0xE620
> +#define AD714X_STAGECFG_REG       0x80
> +#define AD714X_SYSCFG_REG         0x0
> +
> +#define STG_LOW_INT_EN_REG     0x5
> +#define STG_HIGH_INT_EN_REG    0x6
> +#define STG_COM_INT_EN_REG     0x7
> +#define STG_LOW_INT_STA_REG    0x8
> +#define STG_HIGH_INT_STA_REG   0x9
> +#define STG_COM_INT_STA_REG    0xA
> +
> +#define CDC_RESULT_S0          0xB
> +#define CDC_RESULT_S1          0xC
> +#define CDC_RESULT_S2          0xD
> +#define CDC_RESULT_S3          0xE
> +#define CDC_RESULT_S4          0xF
> +#define CDC_RESULT_S5          0x10
> +#define CDC_RESULT_S6          0x11
> +#define CDC_RESULT_S7          0x12
> +#define CDC_RESULT_S8          0x13
> +#define CDC_RESULT_S9          0x14
> +#define CDC_RESULT_S10         0x15
> +#define CDC_RESULT_S11         0x16
> +
> +#define STAGE0_AMBIENT         0xF1
> +#define STAGE1_AMBIENT         0x115
> +#define STAGE2_AMBIENT         0x139
> +#define STAGE3_AMBIENT         0x15D
> +#define STAGE4_AMBIENT         0x181
> +#define STAGE5_AMBIENT         0x1A5
> +#define STAGE6_AMBIENT         0x1C9
> +#define STAGE7_AMBIENT         0x1ED
> +#define STAGE8_AMBIENT         0x211
> +#define STAGE9_AMBIENT         0x234
> +#define STAGE10_AMBIENT                0x259
> +#define STAGE11_AMBIENT                0x27D
> +
> +#define PER_STAGE_REG_NUM      36
> +#define STAGE_NUM              12
> +#define STAGE_CFGREG_NUM       8
> +#define SYS_CFGREG_NUM         8
> +
> +/*
> + * driver information which will be used to maintain the software flow
> + */
> +typedef enum {IDLE, JITTER, ACTIVE, SPACE} ad714x_device_state;
> +
> +struct ad714x_slider_drv {
> +       int highest_stage;
> +       int abs_pos;
> +       int flt_pos;
> +       ad714x_device_state state;
> +       struct input_dev *input;
> +};
> +
> +struct ad714x_wheel_drv {
> +       int abs_pos;
> +       int flt_pos;
> +       int pre_mean_value;
> +       int pre_highest_stage;
> +       int pre_mean_value_no_offset;
> +       int mean_value;
> +       int mean_value_no_offset;
> +       int pos_offset;
> +       int pos_ratio;
> +       int highest_stage;
> +       ad714x_device_state state;
> +       struct input_dev *input;
> +};
> +
> +struct ad714x_touchpad_drv {
> +       int x_highest_stage;
> +       int x_flt_pos;
> +       int x_abs_pos;
> +       int y_highest_stage;
> +       int y_flt_pos;
> +       int y_abs_pos;
> +       int left_ep;
> +       int left_ep_val;
> +       int right_ep;
> +       int right_ep_val;
> +       int top_ep;
> +       int top_ep_val;
> +       int bottom_ep;
> +       int bottom_ep_val;
> +       ad714x_device_state state;
> +       struct input_dev *input;
> +};
> +
> +struct ad714x_button_drv {
> +       ad714x_device_state state;
> +       /* Unlike slider/wheel/touchpad, all buttons point to
> +        * same input_dev instance
> +        */
> +       struct input_dev *input;
> +};
> +
> +struct ad714x_driver_data {
> +       struct ad714x_slider_drv *slider;
> +       struct ad714x_wheel_drv *wheel;
> +       struct ad714x_touchpad_drv *touchpad;
> +       struct ad714x_button_drv *button;
> +};
> +
> +/* information to integrate all things which will be private data
> + * of spi/i2c device
> + */
> +struct ad714x_chip {
> +       unsigned short h_state;
> +       unsigned short l_state;
> +       unsigned short c_state;
> +       unsigned short adc_reg[STAGE_NUM];
> +       unsigned short amb_reg[STAGE_NUM];
> +       unsigned short sensor_val[STAGE_NUM];
> +
> +       struct ad714x_platform_data *hw;
> +       struct ad714x_driver_data *sw;
> +
> +       int irq;
> +       struct device *dev;
> +       ad714x_read_t *read;
> +       ad714x_write_t *write;
> +
> +       struct mutex mutex;
> +
> +       unsigned product;
> +       unsigned version;
> +};
> +
> +static void ad714x_use_com_int(struct ad714x_chip *ad714x, int start_stage,
> +               int end_stage)
> +{
> +       unsigned short data;
> +       unsigned short mask;
> +
> +       mask = ((1 << (end_stage + 1)) - 1) - (1 << start_stage);
> +
> +       ad714x->read(ad714x->dev, STG_COM_INT_EN_REG, &data);
> +       data |= 1 << start_stage;
> +       ad714x->write(ad714x->dev, STG_COM_INT_EN_REG, data);
> +
> +       ad714x->read(ad714x->dev, STG_HIGH_INT_EN_REG, &data);
> +       data &= ~mask;
> +       ad714x->write(ad714x->dev, STG_HIGH_INT_EN_REG, data);
> +}
> +
> +static void ad714x_use_thr_int(struct ad714x_chip *ad714x, int start_stage,
> +               int end_stage)
> +{
> +       unsigned short data;
> +       unsigned short mask;
> +
> +       mask = ((1 << (end_stage + 1)) - 1) - (1 << start_stage);
> +
> +       ad714x->read(ad714x->dev, STG_COM_INT_EN_REG, &data);
> +       data &= ~(1 << start_stage);
> +       ad714x->write(ad714x->dev, STG_COM_INT_EN_REG, data);
> +
> +       ad714x->read(ad714x->dev, STG_HIGH_INT_EN_REG, &data);
> +       data |= mask;
> +       ad714x->write(ad714x->dev, STG_HIGH_INT_EN_REG, data);
> +}
> +
> +static int ad714x_cal_highest_stage(struct ad714x_chip *ad714x, int start_stage,
> +               int end_stage)
> +{
> +       int max_res = 0;
> +       int max_idx = 0;
> +       int i;
> +
> +       for (i = start_stage; i <= end_stage; i++) {
> +               if (ad714x->sensor_val[i] > max_res) {
> +                       max_res = ad714x->sensor_val[i];
> +                       max_idx = i;
> +               }
> +       }
> +
> +       return max_idx;
> +}
> +
> +static int ad714x_cal_abs_pos(struct ad714x_chip *ad714x, int start_stage,
> +               int end_stage, int highest_stage, int max_coord)
> +{
> +       int a_param, b_param;
> +
> +       if (highest_stage == start_stage) {
> +               a_param = ad714x->sensor_val[start_stage + 1];
> +               b_param = ad714x->sensor_val[start_stage] +
> +                       ad714x->sensor_val[start_stage + 1];
> +       } else if (highest_stage == end_stage) {
> +               a_param = ad714x->sensor_val[end_stage] *
> +                       (end_stage - start_stage) +
> +                       ad714x->sensor_val[end_stage - 1] *
> +                       (end_stage - start_stage - 1);
> +               b_param = ad714x->sensor_val[end_stage] +
> +                       ad714x->sensor_val[end_stage - 1];
> +       } else {
> +               a_param = ad714x->sensor_val[highest_stage] *
> +                       (highest_stage - start_stage) +
> +                       ad714x->sensor_val[highest_stage - 1] *
> +                       (highest_stage - start_stage - 1) +
> +                       ad714x->sensor_val[highest_stage + 1] *
> +                       (highest_stage - start_stage + 1);
> +               b_param = ad714x->sensor_val[highest_stage] +
> +                       ad714x->sensor_val[highest_stage - 1] +
> +                       ad714x->sensor_val[highest_stage + 1];
> +       }
> +
> +       return (max_coord / (end_stage - start_stage)) * a_param / b_param;
> +}
> +
> +
> +/* One button can connect to multi positive and negative of CDCs
> + * Multi-buttons can connect to same positive/negative of one CDC
> + */
> +static void ad714x_button_state_machine(struct ad714x_chip *ad714x, int idx)
> +{
> +       struct ad714x_button_plat *hw = &ad714x->hw->button[idx];
> +       struct ad714x_button_drv *sw = &ad714x->sw->button[idx];
> +
> +       switch (sw->state) {
> +       case IDLE:
> +               if (((ad714x->h_state & hw->h_mask) == hw->h_mask) &&
> +                       ((ad714x->l_state & hw->l_mask) == hw->l_mask)) {
> +                       dev_dbg(ad714x->dev, "button %d touched\n", idx);
> +                       input_report_key(sw->input, hw->keycode, 1);
> +                       input_sync(sw->input);
> +                       sw->state = ACTIVE;
> +               }
> +               break;
> +       case ACTIVE:
> +               if (((ad714x->h_state & hw->h_mask) != hw->h_mask) ||
> +                       ((ad714x->l_state & hw->l_mask) != hw->l_mask)) {
> +                       dev_dbg(ad714x->dev, "button %d released\n", idx);
> +                       input_report_key(sw->input, hw->keycode, 0);
> +                       input_sync(sw->input);
> +                       sw->state = IDLE;
> +               }
> +               break;
> +       default:
> +               break;
> +       }
> +}
> +
> +/* The response of a sensor is defined by the absolute number of codes
> + * between the current CDC value and the ambient value.
> + */
> +void ad714x_slider_cal_sensor_val(struct ad714x_chip *ad714x, int idx)
> +{
> +       struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx];
> +       int i;
> +
> +       for (i = hw->start_stage; i <= hw->end_stage; i++) {
> +               ad714x->read(ad714x->dev, CDC_RESULT_S0 + i,
> +                       &ad714x->adc_reg[i]);
> +               ad714x->read(ad714x->dev,
> +                               STAGE0_AMBIENT + i * PER_STAGE_REG_NUM,
> +                               &ad714x->amb_reg[i]);
> +
> +               ad714x->sensor_val[i] = abs(ad714x->adc_reg[i] -
> +                               ad714x->amb_reg[i]);
> +       }
> +}
> +
> +void ad714x_slider_cal_highest_stage(struct ad714x_chip *ad714x, int idx)
> +{
> +       struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx];
> +       struct ad714x_slider_drv *sw = &ad714x->sw->slider[idx];
> +
> +       sw->highest_stage = ad714x_cal_highest_stage(ad714x, hw->start_stage,
> +                       hw->end_stage);
> +
> +       dev_dbg(ad714x->dev, "slider %d highest_stage:%d\n", idx,
> +               sw->highest_stage);
> +}
> +
> +/* The formulae are very straight forward. It uses the sensor with the
> + * highest response and the 2 adjacent ones.
> + * When Sensor 0 has the highest response, only sensor 0 and sensor 1
> + * are used in the calculations. Similarly when the last sensor has the
> + * highest response, only the last sensor and the second last sensors
> + * are used in the calculations.
> + *
> + * For i= idx_of_peak_Sensor-1 to i= idx_of_peak_Sensor+1
> + *         v += Sensor response(i)*i
> + *         w += Sensor response(i)
> + * POS=(Number_of_Positions_Wanted/(Number_of_Sensors_Used-1)) *(v/w)
> + */
> +void ad714x_slider_cal_abs_pos(struct ad714x_chip *ad714x, int idx)
> +{
> +       struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx];
> +       struct ad714x_slider_drv *sw = &ad714x->sw->slider[idx];
> +
> +       sw->abs_pos = ad714x_cal_abs_pos(ad714x, hw->start_stage, hw->end_stage,
> +               sw->highest_stage, hw->max_coord);
> +
> +       dev_dbg(ad714x->dev, "slider %d absolute position:%d\n", idx,
> +               sw->abs_pos);
> +}
> +
> +/*
> + * To minimise the Impact of the noise on the algorithm, ADI developed a
> + * routine that filters the CDC results after they have been read by the
> + * host processor.
> + * The filter used is an Infinite Input Response(IIR) filter implemented
> + * in firmware and attenuates the noise on the CDC results after they've
> + * been read by the host processor.
> + * Filtered_CDC_result = (Filtered_CDC_result * (10 - Coefficient) +
> + *                             Latest_CDC_result * Coefficient)/10
> + */
> +void ad714x_slider_cal_flt_pos(struct ad714x_chip *ad714x, int idx)
> +{
> +       struct ad714x_slider_drv *sw = &ad714x->sw->slider[idx];
> +
> +       sw->flt_pos = (sw->flt_pos * (10 - 4) +
> +                       sw->abs_pos * 4)/10;
> +
> +       dev_dbg(ad714x->dev, "slider %d filter position:%d\n", idx,
> +               sw->flt_pos);
> +}
> +
> +static void ad714x_slider_use_com_int(struct ad714x_chip *ad714x, int idx)
> +{
> +       struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx];
> +       ad714x_use_com_int(ad714x, hw->start_stage, hw->end_stage);
> +}
> +
> +static void ad714x_slider_use_thr_int(struct ad714x_chip *ad714x, int idx)
> +{
> +       struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx];
> +       ad714x_use_thr_int(ad714x, hw->start_stage, hw->end_stage);
> +}
> +
> +static void ad714x_slider_state_machine(struct ad714x_chip *ad714x, int idx)
> +{
> +       struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx];
> +       struct ad714x_slider_drv *sw = &ad714x->sw->slider[idx];
> +       unsigned short h_state, c_state;
> +       unsigned short mask;
> +
> +       mask = ((1 << (hw->end_stage + 1)) - 1) - ((1 << hw->start_stage) - 1);
> +
> +       h_state = ad714x->h_state & mask;
> +       c_state = ad714x->c_state & mask;
> +
> +       switch (sw->state) {
> +       case IDLE:
> +               if (h_state) {
> +                       sw->state = JITTER;
> +                       /* In End of Conversion interrupt mode, the AD714X
> +                        * continuously generates hardware interrupts.
> +                        */
> +                       ad714x_slider_use_com_int(ad714x, idx);
> +                       dev_dbg(ad714x->dev, "slider %d touched\n", idx);
> +               }
> +               break;
> +       case JITTER:
> +               if (c_state == mask) {
> +                       ad714x_slider_cal_sensor_val(ad714x, idx);
> +                       ad714x_slider_cal_highest_stage(ad714x, idx);
> +                       ad714x_slider_cal_abs_pos(ad714x, idx);
> +                       sw->flt_pos = sw->abs_pos;
> +                       sw->state = ACTIVE;
> +               }
> +               break;
> +       case ACTIVE:
> +               if (c_state == mask) {
> +                       if (h_state) {
> +                               ad714x_slider_cal_sensor_val(ad714x, idx);
> +                               ad714x_slider_cal_highest_stage(ad714x, idx);
> +                               ad714x_slider_cal_abs_pos(ad714x, idx);
> +                               ad714x_slider_cal_flt_pos(ad714x, idx);
> +
> +                               input_report_abs(sw->input, ABS_X, sw->flt_pos);
> +                               input_report_key(sw->input, BTN_TOUCH, 1);
> +                       } else {
> +                               /* When the user lifts off the sensor, configure
> +                                * the AD714X back to threshold interrupt mode.
> +                                */
> +                               ad714x_slider_use_thr_int(ad714x, idx);
> +                               sw->state = IDLE;
> +                               input_report_key(sw->input, BTN_TOUCH, 0);
> +                               dev_dbg(ad714x->dev, "slider %d released\n",
> +                                       idx);
> +                       }
> +                       input_sync(sw->input);
> +               }
> +               break;
> +       default:
> +               break;
> +       }
> +}
> +
> +/* When the scroll wheel is activated, we compute the absolute position based
> + * on the sensor values. To calculate the position, we first determine the
> + * sensor that has the greatest response among the 8 sensors that constitutes
> + * the scrollwheel. Then we determined the 2 sensors on either sides of the
> + * sensor with the highest response and we apply weights to these sensors.
> + */
> +static void ad714x_wheel_cal_highest_stage(struct ad714x_chip *ad714x, int idx)
> +{
> +       struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
> +       struct ad714x_wheel_drv *sw = &ad714x->sw->wheel[idx];
> +
> +       sw->pre_highest_stage = sw->highest_stage;
> +       sw->highest_stage = ad714x_cal_highest_stage(ad714x, hw->start_stage,
> +                       hw->end_stage);
> +
> +       dev_dbg(ad714x->dev, "wheel %d highest_stage:%d\n", idx,
> +               sw->highest_stage);
> +}
> +
> +static void ad714x_wheel_cal_sensor_val(struct ad714x_chip *ad714x, int idx)
> +{
> +       struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
> +       int i;
> +
> +       for (i = hw->start_stage; i <= hw->end_stage; i++) {
> +               ad714x->read(ad714x->dev, CDC_RESULT_S0 + i,
> +                       &ad714x->adc_reg[i]);
> +               ad714x->read(ad714x->dev,
> +                               STAGE0_AMBIENT + i * PER_STAGE_REG_NUM,
> +                               &ad714x->amb_reg[i]);
> +               if (ad714x->adc_reg[i] > ad714x->amb_reg[i])
> +                       ad714x->sensor_val[i] = ad714x->adc_reg[i] -
> +                               ad714x->amb_reg[i];
> +               else
> +                       ad714x->sensor_val[i] = 0;
> +       }
> +}
> +
> +/* When the scroll wheel is activated, we compute the absolute position based
> + * on the sensor values. To calculate the position, we first determine the
> + * sensor that has the greatest response among the 8 sensors that constitutes
> + * the scrollwheel. Then we determined the 2 sensors on either sides of the
> + * sensor with the highest response and we apply weights to these sensors. The
> + * result of this computation gives us the mean value which defined by the
> + * following formula:
> + * For i= second_before_highest_stage to i= second_after_highest_stage
> + *         v += Sensor response(i)*WEIGHT*(i+3)
> + *         w += Sensor response(i)
> + * Mean_Value=v/w
> + * pos_on_scrollwheel = (Mean_Value - position_offset) / position_ratio
> + *
> + */
> +
> +#define WEIGHT_FACTOR 30
> +/* This constant prevents the "PositionOffset" from reaching a big value */
> +#define OFFSET_POSITION_CLAMP  120
> +static void ad714x_wheel_cal_abs_pos(struct ad714x_chip *ad714x, int idx)
> +{
> +       struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
> +       struct ad714x_wheel_drv *sw = &ad714x->sw->wheel[idx];
> +       int stage_num = hw->end_stage - hw->start_stage + 1;
> +       int second_before, first_before, highest, first_after, second_after;
> +       int a_param, b_param;
> +
> +       /* Calculate Mean value */
> +
> +       second_before = (sw->highest_stage + stage_num - 2) % stage_num;
> +       first_before = (sw->highest_stage + stage_num - 1) % stage_num;
> +       highest = sw->highest_stage;
> +       first_after = (sw->highest_stage + stage_num + 1) % stage_num;
> +       second_after = (sw->highest_stage + stage_num + 2) % stage_num;
> +
> +       if (((sw->highest_stage - hw->start_stage) > 1) &&
> +                       ((hw->end_stage - sw->highest_stage) > 1)) {
> +               a_param = ad714x->sensor_val[second_before] *
> +                       (second_before - hw->start_stage + 3) +
> +                       ad714x->sensor_val[first_before] *
> +                       (second_before - hw->start_stage + 3) +
> +                       ad714x->sensor_val[highest] *
> +                       (second_before - hw->start_stage + 3) +
> +                       ad714x->sensor_val[first_after] *
> +                       (first_after - hw->start_stage + 3) +
> +                       ad714x->sensor_val[second_after] *
> +                       (second_after - hw->start_stage + 3);
> +       } else {
> +               a_param = ad714x->sensor_val[second_before] *
> +                       (second_before - hw->start_stage + 1) +
> +                       ad714x->sensor_val[first_before] *
> +                       (second_before - hw->start_stage + 2) +
> +                       ad714x->sensor_val[highest] *
> +                       (second_before - hw->start_stage + 3) +
> +                       ad714x->sensor_val[first_after] *
> +                       (first_after - hw->start_stage + 4) +
> +                       ad714x->sensor_val[second_after] *
> +                       (second_after - hw->start_stage + 5);
> +       }
> +       a_param *= WEIGHT_FACTOR;
> +
> +       b_param = ad714x->sensor_val[second_before] +
> +               ad714x->sensor_val[first_before] +
> +               ad714x->sensor_val[highest] +
> +               ad714x->sensor_val[first_after] +
> +               ad714x->sensor_val[second_after];
> +
> +       sw->pre_mean_value = sw->mean_value;
> +       sw->mean_value = a_param / b_param;
> +
> +       /* Calculate the offset */
> +
> +       if ((sw->pre_highest_stage == hw->end_stage) &&
> +                       (sw->highest_stage == hw->start_stage))
> +               sw->pos_offset = sw->mean_value;
> +       else if ((sw->pre_highest_stage == hw->start_stage) &&
> +                       (sw->highest_stage == hw->end_stage))
> +               sw->pos_offset = sw->pre_mean_value;
> +       if (sw->pos_offset > OFFSET_POSITION_CLAMP)
> +               sw->pos_offset = OFFSET_POSITION_CLAMP;
> +
> +       /* Calculate the mean value without the offset */
> +
> +       sw->pre_mean_value_no_offset = sw->mean_value_no_offset;
> +       sw->mean_value_no_offset = sw->mean_value - sw->pos_offset;
> +       if (sw->mean_value_no_offset < 0)
> +               sw->mean_value_no_offset = 0;
> +
> +       /* Calculate ratio to scale down to NUMBER_OF_WANTED_POSITIONS */
> +
> +       if ((sw->pre_highest_stage == hw->end_stage) &&
> +                       (sw->highest_stage == hw->start_stage))
> +               sw->pos_ratio = (sw->pre_mean_value_no_offset * 100) /
> +                       hw->max_coord;
> +       else if ((sw->pre_highest_stage == hw->start_stage) &&
> +                       (sw->highest_stage == hw->end_stage))
> +               sw->pos_ratio = (sw->mean_value_no_offset * 100) /
> +                       hw->max_coord;
> +       sw->abs_pos = (sw->mean_value_no_offset * 100) / sw->pos_ratio;
> +       if (sw->abs_pos > hw->max_coord)
> +               sw->abs_pos = hw->max_coord;
> +}
> +
> +static void ad714x_wheel_cal_flt_pos(struct ad714x_chip *ad714x, int idx)
> +{
> +       struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
> +       struct ad714x_wheel_drv *sw = &ad714x->sw->wheel[idx];
> +       if (((sw->pre_highest_stage == hw->end_stage) &&
> +                               (sw->highest_stage == hw->start_stage)) ||
> +                       ((sw->pre_highest_stage == hw->start_stage) &&
> +                        (sw->highest_stage == hw->end_stage)))
> +               sw->flt_pos = sw->abs_pos;
> +       else
> +               sw->flt_pos = ((sw->flt_pos * 30) + (sw->abs_pos * 71)) / 100;
> +
> +       if (sw->flt_pos > hw->max_coord)
> +               sw->flt_pos = hw->max_coord;
> +}
> +
> +static void ad714x_wheel_use_com_int(struct ad714x_chip *ad714x, int idx)
> +{
> +       struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
> +       ad714x_use_com_int(ad714x, hw->start_stage, hw->end_stage);
> +}
> +
> +static void ad714x_wheel_use_thr_int(struct ad714x_chip *ad714x, int idx)
> +{
> +       struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
> +       ad714x_use_thr_int(ad714x, hw->start_stage, hw->end_stage);
> +}
> +
> +static void ad714x_wheel_state_machine(struct ad714x_chip *ad714x, int idx)
> +{
> +       struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx];
> +       struct ad714x_wheel_drv *sw = &ad714x->sw->wheel[idx];
> +       unsigned short h_state, c_state;
> +       unsigned short mask;
> +
> +       mask = ((1 << (hw->end_stage + 1)) - 1) - ((1 << hw->start_stage) - 1);
> +
> +       h_state = ad714x->h_state & mask;
> +       c_state = ad714x->c_state & mask;
> +
> +       switch (sw->state) {
> +       case IDLE:
> +               if (h_state) {
> +                       sw->state = JITTER;
> +                       /* In End of Conversion interrupt mode, the AD714X
> +                        * continuously generates hardware interrupts.
> +                        */
> +                       ad714x_wheel_use_com_int(ad714x, idx);
> +                       dev_dbg(ad714x->dev, "wheel %d touched\n", idx);
> +               }
> +               break;
> +       case JITTER:
> +               if (c_state == mask)    {
> +                       ad714x_wheel_cal_sensor_val(ad714x, idx);
> +                       ad714x_wheel_cal_highest_stage(ad714x, idx);
> +                       ad714x_wheel_cal_abs_pos(ad714x, idx);
> +                       sw->flt_pos = sw->abs_pos;
> +                       sw->state = ACTIVE;
> +               }
> +               break;
> +       case ACTIVE:
> +               if (c_state == mask) {
> +                       if (h_state) {
> +                               ad714x_wheel_cal_sensor_val(ad714x, idx);
> +                               ad714x_wheel_cal_highest_stage(ad714x, idx);
> +                               ad714x_wheel_cal_abs_pos(ad714x, idx);
> +                               ad714x_wheel_cal_flt_pos(ad714x, idx);
> +
> +                               input_report_abs(sw->input, ABS_WHEEL,
> +                                       sw->abs_pos);
> +                               input_report_key(sw->input, BTN_TOUCH, 1);
> +                       } else {
> +                               /* When the user lifts off the sensor, configure
> +                                * the AD714X back to threshold interrupt mode.
> +                                */
> +                               ad714x_wheel_use_thr_int(ad714x, idx);
> +                               sw->state = IDLE;
> +                               input_report_key(sw->input, BTN_TOUCH, 0);
> +
> +                               dev_dbg(ad714x->dev, "wheel %d released\n",
> +                                       idx);
> +                       }
> +                       input_sync(sw->input);
> +               }
> +               break;
> +       default:
> +               break;
> +       }
> +}
> +
> +static void touchpad_cal_sensor_val(struct ad714x_chip *ad714x, int idx)
> +{
> +       struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
> +       int i;
> +
> +       for (i = hw->x_start_stage; i <= hw->x_end_stage; i++) {
> +               ad714x->read(ad714x->dev, CDC_RESULT_S0 + i,
> +                               &ad714x->adc_reg[i]);
> +               ad714x->read(ad714x->dev,
> +                               STAGE0_AMBIENT + i * PER_STAGE_REG_NUM,
> +                               &ad714x->amb_reg[i]);
> +               if (ad714x->adc_reg[i] > ad714x->amb_reg[i])
> +                       ad714x->sensor_val[i] = ad714x->adc_reg[i] -
> +                               ad714x->amb_reg[i];
> +               else
> +                       ad714x->sensor_val[i] = 0;
> +       }
> +}
> +
> +static void touchpad_cal_highest_stage(struct ad714x_chip *ad714x, int idx)
> +{
> +       struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
> +       struct ad714x_touchpad_drv *sw = &ad714x->sw->touchpad[idx];
> +
> +       sw->x_highest_stage = ad714x_cal_highest_stage(ad714x,
> +               hw->x_start_stage, hw->x_end_stage);
> +       sw->y_highest_stage = ad714x_cal_highest_stage(ad714x,
> +               hw->y_start_stage, hw->y_end_stage);
> +
> +       dev_dbg(ad714x->dev,
> +               "touchpad %d x_highest_stage:%d, y_highest_stage:%d\n",
> +               idx, sw->x_highest_stage, sw->y_highest_stage);
> +}
> +
> +/* If 2 fingers are touching the sensor then 2 peaks can be observed in the
> + * distribution.
> + * The arithmetic doesn't support to get absolute coordinates for multi-touch
> + * yet.
> + */
> +static int touchpad_check_second_peak(struct ad714x_chip *ad714x, int idx)
> +{
> +       struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
> +       struct ad714x_touchpad_drv *sw = &ad714x->sw->touchpad[idx];
> +       int i;
> +
> +       for (i = hw->x_start_stage; i < sw->x_highest_stage; i++) {
> +               if ((ad714x->sensor_val[i] - ad714x->sensor_val[i + 1])
> +                       > (ad714x->sensor_val[i + 1] / 10))
> +                       return 1;
> +       }
> +
> +       for (i = sw->x_highest_stage; i < hw->x_end_stage; i++) {
> +               if ((ad714x->sensor_val[i + 1] - ad714x->sensor_val[i])
> +                       > (ad714x->sensor_val[i] / 10))
> +                       return 1;
> +       }
> +
> +       for (i = hw->y_start_stage; i < sw->y_highest_stage; i++) {
> +               if ((ad714x->sensor_val[i] - ad714x->sensor_val[i + 1])
> +                       > (ad714x->sensor_val[i + 1] / 10))
> +                       return 1;
> +       }
> +
> +       for (i = sw->y_highest_stage; i < hw->y_end_stage; i++) {
> +               if ((ad714x->sensor_val[i + 1] - ad714x->sensor_val[i])
> +                       > (ad714x->sensor_val[i] / 10))
> +                       return 1;
> +       }
> +
> +       return 0;
> +}
> +
> +/* If only one finger is used to activate the touch pad then only 1 peak will be
> + * registered in the distribution. This peak and the 2 adjacent sensors will be
> + * used in the calculation of the absolute position. This will prevent hand
> + * shadows to affect the absolute position calculation.
> + */
> +static void touchpad_cal_abs_pos(struct ad714x_chip *ad714x, int idx)
> +{
> +       struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
> +       struct ad714x_touchpad_drv *sw = &ad714x->sw->touchpad[idx];
> +
> +       sw->x_abs_pos = ad714x_cal_abs_pos(ad714x, hw->x_start_stage,
> +                       hw->x_end_stage, sw->x_highest_stage, hw->x_max_coord);
> +       sw->y_abs_pos = ad714x_cal_abs_pos(ad714x, hw->y_start_stage,
> +                       hw->y_end_stage, sw->y_highest_stage, hw->y_max_coord);
> +
> +       dev_dbg(ad714x->dev, "touchpad %d absolute position:(%d, %d)\n", idx,
> +                       sw->x_abs_pos, sw->y_abs_pos);
> +}
> +
> +static void touchpad_cal_flt_pos(struct ad714x_chip *ad714x, int idx)
> +{
> +       struct ad714x_touchpad_drv *sw = &ad714x->sw->touchpad[idx];
> +
> +       sw->x_flt_pos = (sw->x_flt_pos * (10 - 4) +
> +                       sw->x_abs_pos * 4)/10;
> +       sw->y_flt_pos = (sw->y_flt_pos * (10 - 4) +
> +                       sw->y_abs_pos * 4)/10;
> +
> +       dev_dbg(ad714x->dev, "touchpad %d filter position:(%d, %d)\n",
> +                       idx, sw->x_flt_pos, sw->y_flt_pos);
> +}
> +
> +/* To prevent distortion from showing in the absolute position, it is
> + * necessary to detect the end points. When endpoints are detected, the
> + * driver stops updating the status variables with absolute positions.
> + * End points are detected on the 4 edges of the touchpad sensor. The
> + * method to detect them is the same for all 4.
> + * To detect the end points, the firmware computes the difference in
> + * percent between the sensor on the edge and the adjacent one. The
> + * difference is calculated in percent in order to make the end point
> + * detection independent of the pressure.
> + */
> +
> +#define LEFT_END_POINT_DETECTION_LEVEL                  550
> +#define RIGHT_END_POINT_DETECTION_LEVEL                 750
> +#define LEFT_RIGHT_END_POINT_DEAVTIVALION_LEVEL         850
> +#define TOP_END_POINT_DETECTION_LEVEL                   550
> +#define BOTTOM_END_POINT_DETECTION_LEVEL                950
> +#define TOP_BOTTOM_END_POINT_DEAVTIVALION_LEVEL         700
> +static int touchpad_check_endpoint(struct ad714x_chip *ad714x, int idx)
> +{
> +       struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
> +       struct ad714x_touchpad_drv *sw  = &ad714x->sw->touchpad[idx];
> +       int percent_sensor_diff;
> +
> +       /* left endpoint detect */
> +       percent_sensor_diff = (ad714x->sensor_val[hw->x_start_stage] -
> +                       ad714x->sensor_val[hw->x_start_stage + 1]) * 100 /
> +                       ad714x->sensor_val[hw->x_start_stage + 1];
> +       if (!sw->left_ep) {
> +               if (percent_sensor_diff >= LEFT_END_POINT_DETECTION_LEVEL)  {
> +                       sw->left_ep = 1;
> +                       sw->left_ep_val =
> +                               ad714x->sensor_val[hw->x_start_stage + 1];
> +               }
> +       } else {
> +               if ((percent_sensor_diff < LEFT_END_POINT_DETECTION_LEVEL) &&
> +               (ad714x->sensor_val[hw->x_start_stage + 1] >
> +               LEFT_RIGHT_END_POINT_DEAVTIVALION_LEVEL + sw->left_ep_val))
> +                       sw->left_ep = 0;
> +       }
> +
> +       /* right endpoint detect */
> +       percent_sensor_diff = (ad714x->sensor_val[hw->x_end_stage] -
> +                       ad714x->sensor_val[hw->x_end_stage - 1]) * 100 /
> +                       ad714x->sensor_val[hw->x_end_stage - 1];
> +       if (!sw->right_ep) {
> +               if (percent_sensor_diff >= RIGHT_END_POINT_DETECTION_LEVEL)  {
> +                       sw->right_ep = 1;
> +                       sw->right_ep_val =
> +                               ad714x->sensor_val[hw->x_end_stage - 1];
> +               }
> +       } else {
> +               if ((percent_sensor_diff < RIGHT_END_POINT_DETECTION_LEVEL) &&
> +               (ad714x->sensor_val[hw->x_end_stage - 1] >
> +               LEFT_RIGHT_END_POINT_DEAVTIVALION_LEVEL + sw->right_ep_val))
> +                       sw->right_ep = 0;
> +       }
> +
> +       /* top endpoint detect */
> +       percent_sensor_diff = (ad714x->sensor_val[hw->y_start_stage] -
> +                       ad714x->sensor_val[hw->y_start_stage + 1]) * 100 /
> +                       ad714x->sensor_val[hw->y_start_stage + 1];
> +       if (!sw->top_ep) {
> +               if (percent_sensor_diff >= TOP_END_POINT_DETECTION_LEVEL)  {
> +                       sw->top_ep = 1;
> +                       sw->top_ep_val =
> +                               ad714x->sensor_val[hw->y_start_stage + 1];
> +               }
> +       } else {
> +               if ((percent_sensor_diff < TOP_END_POINT_DETECTION_LEVEL) &&
> +               (ad714x->sensor_val[hw->y_start_stage + 1] >
> +               TOP_BOTTOM_END_POINT_DEAVTIVALION_LEVEL + sw->top_ep_val))
> +                       sw->top_ep = 0;
> +       }
> +
> +       /* bottom endpoint detect */
> +       percent_sensor_diff = (ad714x->sensor_val[hw->y_end_stage] -
> +               ad714x->sensor_val[hw->y_end_stage - 1]) * 100 /
> +               ad714x->sensor_val[hw->y_end_stage - 1];
> +       if (!sw->bottom_ep) {
> +               if (percent_sensor_diff >= BOTTOM_END_POINT_DETECTION_LEVEL)  {
> +                       sw->bottom_ep = 1;
> +                       sw->bottom_ep_val =
> +                               ad714x->sensor_val[hw->y_end_stage - 1];
> +               }
> +       } else {
> +               if ((percent_sensor_diff < BOTTOM_END_POINT_DETECTION_LEVEL) &&
> +               (ad714x->sensor_val[hw->y_end_stage - 1] >
> +                TOP_BOTTOM_END_POINT_DEAVTIVALION_LEVEL + sw->bottom_ep_val))
> +                       sw->bottom_ep = 0;
> +       }
> +
> +       return sw->left_ep || sw->right_ep || sw->top_ep || sw->bottom_ep;
> +}
> +
> +static void touchpad_use_com_int(struct ad714x_chip *ad714x, int idx)
> +{
> +       struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
> +       ad714x_use_com_int(ad714x, hw->x_start_stage, hw->x_end_stage);
> +}
> +
> +static void touchpad_use_thr_int(struct ad714x_chip *ad714x, int idx)
> +{
> +       struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
> +       ad714x_use_thr_int(ad714x, hw->x_start_stage, hw->x_end_stage);
> +       ad714x_use_thr_int(ad714x, hw->y_start_stage, hw->y_end_stage);
> +}
> +
> +static void ad714x_touchpad_state_machine(struct ad714x_chip *ad714x, int idx)
> +{
> +       struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx];
> +       struct ad714x_touchpad_drv *sw = &ad714x->sw->touchpad[idx];
> +       unsigned short h_state, c_state;
> +       unsigned short mask;
> +
> +       mask = (((1 << (hw->x_end_stage + 1)) - 1) -
> +               ((1 << hw->x_start_stage) - 1)) +
> +               (((1 << (hw->y_end_stage + 1)) - 1) -
> +               ((1 << hw->y_start_stage) - 1));
> +
> +       h_state = ad714x->h_state & mask;
> +       c_state = ad714x->c_state & mask;
> +
> +       switch (sw->state) {
> +       case IDLE:
> +               if (h_state) {
> +                       sw->state = JITTER;
> +                       /* In End of Conversion interrupt mode, the AD714X
> +                        * continuously generates hardware interrupts.
> +                        */
> +                       touchpad_use_com_int(ad714x, idx);
> +                       dev_dbg(ad714x->dev, "touchpad %d touched\n", idx);
> +               }
> +               break;
> +       case JITTER:
> +               if (c_state == mask) {
> +                       touchpad_cal_sensor_val(ad714x, idx);
> +                       touchpad_cal_highest_stage(ad714x, idx);
> +                       if ((!touchpad_check_second_peak(ad714x, idx)) &&
> +                               (!touchpad_check_endpoint(ad714x, idx))) {
> +                               dev_dbg(ad714x->dev,
> +                                       "touchpad%d, 2 fingers or endpoint\n",
> +                                       idx);
> +                               touchpad_cal_abs_pos(ad714x, idx);
> +                               sw->x_flt_pos = sw->x_abs_pos;
> +                               sw->y_flt_pos = sw->y_abs_pos;
> +                               sw->state = ACTIVE;
> +                       }
> +               }
> +               break;
> +       case ACTIVE:
> +               if (c_state == mask) {
> +                       if (h_state) {
> +                               touchpad_cal_sensor_val(ad714x, idx);
> +                               touchpad_cal_highest_stage(ad714x, idx);
> +                               if ((!touchpad_check_second_peak(ad714x, idx))
> +                                 && (!touchpad_check_endpoint(ad714x, idx))) {
> +                                       touchpad_cal_abs_pos(ad714x, idx);
> +                                       touchpad_cal_flt_pos(ad714x, idx);
> +                                       input_report_abs(sw->input, ABS_X,
> +                                               sw->x_flt_pos);
> +                                       input_report_abs(sw->input, ABS_Y,
> +                                               sw->y_flt_pos);
> +                                       input_report_key(sw->input, BTN_TOUCH,
> +                                               1);
> +                               }
> +                       } else {
> +                               /* When the user lifts off the sensor, configure
> +                                * the AD714X back to threshold interrupt mode.
> +                                */
> +                               touchpad_use_thr_int(ad714x, idx);
> +                               sw->state = IDLE;
> +                               input_report_key(sw->input, BTN_TOUCH, 0);
> +                               dev_dbg(ad714x->dev, "touchpad %d released\n",
> +                                       idx);
> +                       }
> +                       input_sync(sw->input);
> +               }
> +               break;
> +       default:
> +               break;
> +       }
> +}
> +
> +static int ad714x_hw_detect(struct ad714x_chip *ad714x)
> +{
> +       unsigned short data;
> +
> +       ad714x->read(ad714x->dev, AD714X_PARTID_REG, &data);
> +       switch (data & 0xFFF0) {
> +       case AD7147_PARTID:
> +               ad714x->product = 0x7147;
> +               ad714x->version = data & 0xF;
> +               dev_info(ad714x->dev, "found AD7147 captouch, rev:%d\n",
> +                               ad714x->version);
> +               return 0;
> +       case AD7142_PARTID:
> +               ad714x->product = 0x7142;
> +               ad714x->version = data & 0xF;
> +               dev_info(ad714x->dev, "found AD7142 captouch, rev:%d\n",
> +                               ad714x->version);
> +               return 0;
> +       default:
> +               dev_err(ad714x->dev,
> +                       "fail to detect AD714X captouch, read ID is %04x\n",
> +                       data);
> +               return -ENODEV;
> +       }
> +}
> +
> +static void ad714x_hw_init(struct ad714x_chip *ad714x)
> +{
> +       int i, j;
> +       unsigned short reg_base;
> +       unsigned short data;
> +
> +       /* configuration CDC and interrupts*/
> +
> +       for (i = 0; i < STAGE_NUM; i++) {
> +               reg_base = AD714X_STAGECFG_REG + i * STAGE_CFGREG_NUM;
> +               for (j = 0; j < STAGE_CFGREG_NUM; j++)
> +                       ad714x->write(ad714x->dev, reg_base + j,
> +                                       ad714x->hw->stage_cfg_reg[i][j]);
> +       }
> +
> +       for (i = 0; i < SYS_CFGREG_NUM; i++)
> +               ad714x->write(ad714x->dev, AD714X_SYSCFG_REG + i,
> +                       ad714x->hw->sys_cfg_reg[i]);
> +       for (i = 0; i < SYS_CFGREG_NUM; i++)
> +               ad714x->read(ad714x->dev, AD714X_SYSCFG_REG + i,
> +                       &data);
> +
> +       ad714x->write(ad714x->dev, AD714X_STG_CAL_EN_REG, 0xFFF);
> +
> +       /* clear all interrupts */
> +       ad714x->read(ad714x->dev, STG_LOW_INT_STA_REG, &data);
> +       ad714x->read(ad714x->dev, STG_HIGH_INT_STA_REG, &data);
> +       ad714x->read(ad714x->dev, STG_COM_INT_STA_REG, &data);
> +}
> +
> +static irqreturn_t ad714x_interrupt_thread(int irq, void *data)
> +{
> +       struct ad714x_chip *ad714x = data;
> +       int i;
> +
> +       mutex_lock(&ad714x->mutex);
> +
> +       ad714x->read(ad714x->dev, STG_LOW_INT_STA_REG, &ad714x->l_state);
> +       ad714x->read(ad714x->dev, STG_HIGH_INT_STA_REG, &ad714x->h_state);
> +       ad714x->read(ad714x->dev, STG_COM_INT_STA_REG, &ad714x->c_state);
> +
> +       for (i = 0; i < ad714x->hw->button_num; i++)
> +               ad714x_button_state_machine(ad714x, i);
> +       for (i = 0; i < ad714x->hw->slider_num; i++)
> +               ad714x_slider_state_machine(ad714x, i);
> +       for (i = 0; i < ad714x->hw->wheel_num; i++)
> +               ad714x_wheel_state_machine(ad714x, i);
> +       for (i = 0; i < ad714x->hw->touchpad_num; i++)
> +               ad714x_touchpad_state_machine(ad714x, i);
> +
> +       mutex_unlock(&ad714x->mutex);
> +
> +       return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t ad714x_interrupt(int irq, void *data)
> +{
> +       return IRQ_WAKE_THREAD;
> +}
> +
> +#define MAX_DEVICE_NUM 8
> +int ad714x_probe(struct ad714x_chip **pad714x, struct device *dev,
> +       u16 bus_type, int irq, ad714x_read_t read, ad714x_write_t write)
> +{
> +       int ret, i, alloc_idx;
> +       struct input_dev *input[MAX_DEVICE_NUM];
> +
> +       struct ad714x_platform_data *plat_data;
> +       struct ad714x_chip *ad714x;
> +       void *drv_mem;
> +
> +       struct ad714x_button_drv *bt_drv;
> +       struct ad714x_slider_drv *sd_drv;
> +       struct ad714x_wheel_drv *wl_drv;
> +       struct ad714x_touchpad_drv *tp_drv;
> +
> +       plat_data = dev->platform_data;
> +       if (dev->platform_data == NULL) {
> +               dev_err(dev, "platform data for ad714x doesn't exist\n");
> +               return -ENODEV;
> +       }
> +
> +       *pad714x = ad714x = kzalloc(sizeof(*ad714x) + sizeof(*ad714x->sw) +
> +                       sizeof(*sd_drv) * plat_data->slider_num +
> +                       sizeof(*wl_drv) * plat_data->wheel_num +
> +                       sizeof(*tp_drv) * plat_data->touchpad_num +
> +                       sizeof(*bt_drv) * plat_data->button_num, GFP_KERNEL);
> +       if (!ad714x)
> +               return -ENOMEM;
> +       ad714x->hw = plat_data;
> +
> +       drv_mem = ad714x + 1;
> +       ad714x->sw = drv_mem;
> +       drv_mem += sizeof(*ad714x->sw);
> +       ad714x->sw->slider = sd_drv = drv_mem;
> +       drv_mem += sizeof(*sd_drv) * ad714x->hw->slider_num;
> +       ad714x->sw->wheel = wl_drv = drv_mem;
> +       drv_mem += sizeof(*wl_drv) * ad714x->hw->wheel_num;
> +       ad714x->sw->touchpad = tp_drv = drv_mem;
> +       drv_mem += sizeof(*tp_drv) * ad714x->hw->touchpad_num;
> +       ad714x->sw->button = bt_drv = drv_mem;
> +       drv_mem += sizeof(*bt_drv) * ad714x->hw->button_num;
> +
> +       ad714x->read = read;
> +       ad714x->write = write;
> +       ad714x->irq = irq;
> +       ad714x->dev = dev;
> +
> +       ret = ad714x_hw_detect(ad714x);
> +       if (ret)
> +               goto det_err;
> +
> +       /* initilize and request sw/hw resources */
> +
> +       ad714x_hw_init(ad714x);
> +       mutex_init(&ad714x->mutex);
> +
> +       if (ad714x->irq > 0) {
> +               ret = request_threaded_irq(ad714x->irq, ad714x_interrupt,
> +                               ad714x_interrupt_thread, IRQF_TRIGGER_FALLING,
> +                               "ad714x_captouch", ad714x);
> +               if (ret) {
> +                       dev_err(dev, "can't allocate irq %d\n",
> +                                       ad714x->irq);
> +                       goto fail_irq;
> +               }
> +       } else {
> +               dev_err(dev, "IRQ not configured!\n");
> +               ret = -EINVAL;
> +               goto det_err;
> +       }
> +
> +       /*
> +        * Allocate and register AD714X input device
> +        */
> +       alloc_idx = 0;
> +
> +       /* a slider uses one input_dev instance */
> +       if (ad714x->hw->slider_num > 0) {
> +               struct ad714x_slider_plat *sd_plat = ad714x->hw->slider;
> +
> +               for (i = 0; i < ad714x->hw->slider_num; i++) {
> +                       sd_drv[i].input = input[alloc_idx] = input_allocate_device();
> +                       if (!input[alloc_idx]) {
> +                               ret = -ENOMEM;
> +                               goto fail_alloc_reg;
> +                       }
> +
> +                       __set_bit(EV_ABS, input[alloc_idx]->evbit);
> +                       __set_bit(EV_KEY, input[alloc_idx]->evbit);
> +                       __set_bit(ABS_X, input[alloc_idx]->absbit);
> +                       __set_bit(BTN_TOUCH, input[alloc_idx]->keybit);
> +                       input_set_abs_params(input[alloc_idx], ABS_X, 0,
> +                                       sd_plat->max_coord, 0, 0);
> +
> +                       input[alloc_idx]->id.bustype = bus_type;
> +                       input[alloc_idx]->id.product = ad714x->product;
> +                       input[alloc_idx]->id.version = ad714x->version;
> +
> +                       ret = input_register_device(input[alloc_idx]);
> +                       if (ret)
> +                               goto fail_alloc_reg;
> +
> +                       alloc_idx++;
> +               }
> +       }
> +
> +       /* a wheel uses one input_dev instance */
> +       if (ad714x->hw->wheel_num > 0) {
> +               struct ad714x_wheel_plat *wl_plat = ad714x->hw->wheel;
> +
> +               for (i = 0; i < ad714x->hw->wheel_num; i++) {
> +                       wl_drv[i].input = input[alloc_idx] = input_allocate_device();
> +                       if (!input[alloc_idx]) {
> +                               ret = -ENOMEM;
> +                               goto fail_alloc_reg;
> +                       }
> +
> +                       __set_bit(EV_KEY, input[alloc_idx]->evbit);
> +                       __set_bit(EV_ABS, input[alloc_idx]->evbit);
> +                       __set_bit(ABS_WHEEL, input[alloc_idx]->absbit);
> +                       __set_bit(BTN_TOUCH, input[alloc_idx]->keybit);
> +                       input_set_abs_params(input[alloc_idx], ABS_WHEEL, 0,
> +                                       wl_plat->max_coord, 0, 0);
> +
> +                       input[alloc_idx]->id.bustype = bus_type;
> +                       input[alloc_idx]->id.product = ad714x->product;
> +                       input[alloc_idx]->id.version = ad714x->version;
> +
> +                       ret = input_register_device(input[alloc_idx]);
> +                       if (ret)
> +                               goto fail_alloc_reg;
> +
> +                       alloc_idx++;
> +               }
> +       }
> +
> +       /* a touchpad uses one input_dev instance */
> +       if (ad714x->hw->touchpad_num > 0) {
> +               struct ad714x_touchpad_plat *tp_plat = ad714x->hw->touchpad;
> +
> +               for (i = 0; i < ad714x->hw->touchpad_num; i++) {
> +                       tp_drv[i].input = input[alloc_idx] = input_allocate_device();
> +                       if (!input[alloc_idx]) {
> +                               ret = -ENOMEM;
> +                               goto fail_alloc_reg;
> +                       }
> +
> +                       __set_bit(EV_ABS, input[alloc_idx]->evbit);
> +                       __set_bit(EV_KEY, input[alloc_idx]->evbit);
> +                       __set_bit(ABS_X, input[alloc_idx]->absbit);
> +                       __set_bit(ABS_Y, input[alloc_idx]->absbit);
> +                       __set_bit(BTN_TOUCH, input[alloc_idx]->keybit);
> +                       input_set_abs_params(input[alloc_idx], ABS_X, 0,
> +                                       tp_plat->x_max_coord, 0, 0);
> +                       input_set_abs_params(input[alloc_idx], ABS_Y, 0,
> +                                       tp_plat->y_max_coord, 0, 0);
> +
> +                       input[alloc_idx]->id.bustype = bus_type;
> +                       input[alloc_idx]->id.product = ad714x->product;
> +                       input[alloc_idx]->id.version = ad714x->version;
> +
> +                       ret = input_register_device(input[alloc_idx]);
> +                       if (ret)
> +                               goto fail_alloc_reg;
> +
> +                       alloc_idx++;
> +               }
> +       }
> +
> +       /* all buttons use one input node */
> +       if (ad714x->hw->button_num > 0) {
> +               struct ad714x_button_plat *bt_plat = ad714x->hw->button;
> +
> +               input[alloc_idx] = input_allocate_device();
> +               if (!input[alloc_idx]) {
> +                       ret = -ENOMEM;
> +                       goto fail_alloc_reg;
> +               }
> +
> +               __set_bit(EV_KEY, input[alloc_idx]->evbit);
> +               for (i = 0; i < ad714x->hw->button_num; i++) {
> +                       bt_drv[i].input = input[alloc_idx];
> +                       __set_bit(bt_plat[i].keycode, input[alloc_idx]->keybit);
> +               }
> +
> +               input[alloc_idx]->id.bustype = bus_type;
> +               input[alloc_idx]->id.product = ad714x->product;
> +               input[alloc_idx]->id.version = ad714x->version;
> +
> +               ret = input_register_device(input[alloc_idx]);
> +               if (ret)
> +                       goto fail_alloc_reg;
> +
> +               alloc_idx++;
> +       }
> +
> +
> +       return 0;
> +
> + fail_alloc_reg:
> +       dev_err(dev, "failed to setup AD714x input device %i\n", alloc_idx);
> +       for (i = 0; i < alloc_idx - 1; i++)
> +               input_unregister_device(input[i]);
> +       input_free_device(input[alloc_idx]);
> +
> +       free_irq(ad714x->irq, ad714x);
> + fail_irq:
> + det_err:
> +       kfree(ad714x);
> +       return ret;
> +}
> +EXPORT_SYMBOL(ad714x_probe);
> +
> +int ad714x_remove(struct ad714x_chip *ad714x)
> +{
> +       int i;
> +
> +       /* unregister and free all input devices */
> +
> +       for (i = 0; i < ad714x->hw->slider_num; i++)
> +               input_unregister_device(ad714x->sw->slider[i].input);
> +
> +       for (i = 0; i < ad714x->hw->wheel_num; i++)
> +               input_unregister_device(ad714x->sw->wheel[i].input);
> +
> +       for (i = 0; i < ad714x->hw->touchpad_num; i++)
> +               input_unregister_device(ad714x->sw->touchpad[i].input);
> +
> +       if (ad714x->hw->button_num)
> +               input_unregister_device(ad714x->sw->button[0].input);
> +
> +       free_irq(ad714x->irq, ad714x);
> +
> +       kfree(ad714x);
> +
> +       return 0;
> +}
> +EXPORT_SYMBOL(ad714x_remove);
> +
> +#ifdef CONFIG_PM
> +int ad714x_disable(struct ad714x_chip *ad714x)
> +{
> +       unsigned short data;
> +
> +       dev_dbg(ad714x->dev, "%s enter\n", __func__);
> +
> +       mutex_lock(&ad714x->mutex);
> +
> +       data = ad714x->hw->sys_cfg_reg[AD714X_PWR_CTRL] | 0x3;
> +       ad714x->write(ad714x->dev, AD714X_PWR_CTRL, data);
> +
> +       mutex_unlock(&ad714x->mutex);
> +
> +       return 0;
> +}
> +EXPORT_SYMBOL(ad714x_disable);
> +
> +int ad714x_enable(struct ad714x_chip *ad714x)
> +{
> +       unsigned short data;
> +
> +       dev_dbg(ad714x->dev, "%s enter\n", __func__);
> +
> +       mutex_lock(&ad714x->mutex);
> +
> +       /* resume to non-shutdown mode */
> +
> +       ad714x->write(ad714x->dev, AD714X_PWR_CTRL,
> +                       ad714x->hw->sys_cfg_reg[AD714X_PWR_CTRL]);
> +
> +       /* make sure the interrupt output line is not low level after resume,
> +        * otherwise we will get no chance to enter falling-edge irq again
> +        */
> +
> +       ad714x->read(ad714x->dev, STG_LOW_INT_STA_REG, &data);
> +       ad714x->read(ad714x->dev, STG_HIGH_INT_STA_REG, &data);
> +       ad714x->read(ad714x->dev, STG_COM_INT_STA_REG, &data);
> +
> +       mutex_unlock(&ad714x->mutex);
> +
> +       return 0;
> +}
> +EXPORT_SYMBOL(ad714x_enable);
> +#endif
> +
> +MODULE_DESCRIPTION("Analog Devices AD714X Capacitance Touch Sensor Driver");
> +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/input/misc/ad714x.h b/drivers/input/misc/ad714x.h
> new file mode 100644
> index 0000000..3fc5a16
> --- /dev/null
> +++ b/drivers/input/misc/ad714x.h
> @@ -0,0 +1,25 @@
> +/*
> + * AD714X CapTouch Programmable Controller driver (bus interfaces)
> + *
> + * Copyright 2009 Analog Devices Inc.
> + *
> + * Licensed under the GPL-2 or later.
> + */
> +
> +#ifndef _AD714X_H_
> +#define _AD714X_H_
> +
> +#include <linux/types.h>
> +
> +struct device;
> +struct ad714x_chip;
> +typedef int (ad714x_read_t) (struct device *, unsigned short, unsigned short *);
> +typedef int (ad714x_write_t) (struct device *, unsigned short, unsigned short);
> +
> +int ad714x_disable(struct ad714x_chip *ad714x);
> +int ad714x_enable(struct ad714x_chip *ad714x);
> +int ad714x_probe(struct ad714x_chip **pad714x, struct device *dev,
> +       u16 bus_type, int irq, ad714x_read_t read, ad714x_write_t write);
> +int ad714x_remove(struct ad714x_chip *ad714x);
> +
> +#endif
> diff --git a/include/linux/input.h b/include/linux/input.h
> index 0ccfc30..8b38cfa 100644
> --- a/include/linux/input.h
> +++ b/include/linux/input.h
> @@ -756,6 +756,7 @@ struct input_absinfo {
>  #define BUS_HOST               0x19
>  #define BUS_GSC                        0x1A
>  #define BUS_ATARI              0x1B
> +#define BUS_SPI                        0x1C
>
>  /*
>  * MT_TOOL types
> diff --git a/include/linux/input/ad714x.h b/include/linux/input/ad714x.h
> new file mode 100644
> index 0000000..0cbe5e8
> --- /dev/null
> +++ b/include/linux/input/ad714x.h
> @@ -0,0 +1,63 @@
> +/*
> + * include/linux/input/ad714x.h
> + *
> + * AD714x is very flexible, it can be used as buttons, scrollwheel,
> + * slider, touchpad at the same time. That depends on the boards.
> + * The platform_data for the device's "struct device" holds this
> + * information.
> + *
> + * Copyright 2009 Analog Devices Inc.
> + *
> + * Licensed under the GPL-2 or later.
> + */
> +
> +#ifndef __LINUX_INPUT_AD714X_H__
> +#define __LINUX_INPUT_AD714X_H__
> +
> +#define STAGE_NUM              12
> +#define STAGE_CFGREG_NUM       8
> +#define SYS_CFGREG_NUM         8
> +
> +/* board information which need be initialized in arch/mach... */
> +struct ad714x_slider_plat {
> +       int start_stage;
> +       int end_stage;
> +       int max_coord;
> +};
> +
> +struct ad714x_wheel_plat {
> +       int start_stage;
> +       int end_stage;
> +       int max_coord;
> +};
> +
> +struct ad714x_touchpad_plat {
> +       int x_start_stage;
> +       int x_end_stage;
> +       int x_max_coord;
> +
> +       int y_start_stage;
> +       int y_end_stage;
> +       int y_max_coord;
> +};
> +
> +struct ad714x_button_plat {
> +       int keycode;
> +       unsigned short l_mask;
> +       unsigned short h_mask;
> +};
> +
> +struct ad714x_platform_data {
> +       int slider_num;
> +       int wheel_num;
> +       int touchpad_num;
> +       int button_num;
> +       struct ad714x_slider_plat *slider;
> +       struct ad714x_wheel_plat *wheel;
> +       struct ad714x_touchpad_plat *touchpad;
> +       struct ad714x_button_plat *button;
> +       unsigned short stage_cfg_reg[STAGE_NUM][STAGE_CFGREG_NUM];
> +       unsigned short sys_cfg_reg[SYS_CFGREG_NUM];
> +};
> +
> +#endif
> --
> 1.6.5.1
>
>
_______________________________________________
Uclinux-dist-devel mailing list
Uclinux-dist-devel@blackfin.uclinux.org
https://blackfin.uclinux.org/mailman/listinfo/uclinux-dist-devel

^ permalink raw reply	[flat|nested] 21+ messages in thread

end of thread, other threads:[~2009-12-15  4:52 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2009-09-11  6:53 [PATCH v3] add analog devices AD714X captouch input driver Barry Song
2009-09-11 13:19 ` [Uclinux-dist-devel] [PATCH v3] add analog devices AD714X captouchinput driver Hennerich, Michael
2009-09-11 13:28   ` [Uclinux-dist-devel] [PATCH v3] add analog devices AD714Xcaptouchinput driver Hennerich, Michael
     [not found]   ` <8A42379416420646B9BFAC9682273B6D0DC3BD95-pcKY8lWzTjquVPpjEGsWsTcYPEmu4y7e@public.gmane.org>
2009-10-09  8:56     ` [PATCH v3] add analog devices AD714X captouchinput driver Mike Frysinger
     [not found]       ` <8bd0f97a0910090156g2a8fba58r4085422f3a79c892-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2009-10-09  9:03         ` David Woodhouse
     [not found]           ` <1255079007.8362.70.camel-uXGAPMMVk8bAQYKIod7YupZV94DADvEd@public.gmane.org>
2009-10-09  9:18             ` Mike Frysinger
     [not found]               ` <8bd0f97a0910090218j6a2aa6aaq497fba3f04d7b19f-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2009-10-09  9:20                 ` David Woodhouse
2009-10-09 10:11                 ` Mike Frysinger
     [not found]                   ` <8bd0f97a0910090311t5f5167c8r336afaa6f790e1a9-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2009-10-09 16:22                     ` Dmitry Torokhov
     [not found]                       ` <20091009162219.GB1092-WlK9ik9hQGAhIp7JRqBPierSzoNAToWh@public.gmane.org>
2009-10-10  2:24                         ` Barry Song
     [not found]                           ` <3c17e3570910091924p68a248e6q3dd2640acc17c90d-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2009-10-10 20:38                             ` Mike Frysinger
     [not found]                               ` <8bd0f97a0910101338y46cb227hdc6d83c3c6622a21-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2009-10-12 17:08                                 ` Mike Frysinger
     [not found]                                   ` <8bd0f97a0910121008l1563803bu766e7d0868254a9f-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2009-10-13  3:05                                     ` Barry Song
     [not found]                                       ` <3c17e3570910122005j16c5e0fcgc542172ca9c4a0d2-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2009-10-13  4:26                                         ` Barry Song
2009-10-09 21:33                 ` Jiri Kosina
     [not found]                   ` <alpine.LRH.2.00.0910092331040.12171-1ReQVI26iDCaZKY3DrU6dA@public.gmane.org>
2009-10-09 21:36                     ` Mike Frysinger
2009-10-09 10:34         ` [PATCH v3] add analog devices AD714Xcaptouchinput driver Song, Barry
2009-09-14  3:36 ` [Uclinux-dist-devel] [PATCH v3] add analog devices AD714X captouch input driver Mike Frysinger
     [not found] ` <1252652006-5270-1-git-send-email-21cnbao-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
2009-10-13  6:02   ` [PATCH v4] input/misc: add Analog Devices AD714x " Mike Frysinger
2009-10-20  8:37     ` [PATCH v5] " Mike Frysinger
     [not found]       ` <1256027864-26126-1-git-send-email-vapier-aBrp7R+bbdUdnm+yROfE0A@public.gmane.org>
2009-12-15  4:52         ` Barry Song

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).