Hi,
the following patch serie adds support to the STMPE811 device by ST Micro.
STMPE811 is a multifunction device, which contains a GPIO controller,
a Touchscreen controller, an ADC and a temperature sensor. The following
patch serie provides a core driver, a driver for the GPIO controller and a
driver for the touch screen controller.
The documentation for the chip is, well, let's say not quite accurate.
Especially the touch screen controller crashes under conditions, which are not
documented any where. (did not manage to find any erratum also) The reference
code by st is crappy and also leads to crashes. I made some workarounds for
these troubles and documented them in the comments in the driver. So pls check
the comment if you spot any strange stuff.
Tested on a board with a i.MX27 SOC.
cheers
Luotao Fu
This one adds a driver for STMPE811 GPIO controller. STMPE811 is a
multifunction device. Hence this driver depends on stmpe811_core
driver for core functionalities. The gpio chip is registered to
gpiolib. Interrupts are currently not supported.
Signed-off-by: Luotao Fu <[email protected]>
---
drivers/gpio/Kconfig | 10 ++
drivers/gpio/Makefile | 1 +
drivers/gpio/stmpe811_gpio.c | 236 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 247 insertions(+), 0 deletions(-)
create mode 100644 drivers/gpio/stmpe811_gpio.c
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 724038d..38307e5 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -249,6 +249,16 @@ config GPIO_ADP5588
To compile this driver as a module, choose M here: the module will be
called adp5588-gpio.
+config GPIO_STMPE811
+ tristate "STMPE811 I2C MFD GPIO expander"
+ depends on I2C
+ depends on MFD_STMPE811
+ help
+ This option enables support for 8 GPIOs found
+ on STMicroelectronics STMPE811 multifunction devices.
+ To compile this driver as a module, choose M here: the module will be
+ called stmpe811_gpio.
+
comment "PCI GPIO expanders:"
config GPIO_CS5535
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 51c3cdd..7b184aa 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -31,3 +31,4 @@ obj-$(CONFIG_GPIO_WM8994) += wm8994-gpio.o
obj-$(CONFIG_GPIO_SCH) += sch_gpio.o
obj-$(CONFIG_GPIO_RDC321X) += rdc321x-gpio.o
obj-$(CONFIG_GPIO_JANZ_TTL) += janz-ttl.o
+obj-$(CONFIG_GPIO_STMPE811) += stmpe811_gpio.o
diff --git a/drivers/gpio/stmpe811_gpio.c b/drivers/gpio/stmpe811_gpio.c
new file mode 100644
index 0000000..54da187
--- /dev/null
+++ b/drivers/gpio/stmpe811_gpio.c
@@ -0,0 +1,236 @@
+/*
+ * stmpe811_gpio.c -- gpiolib support for STMicroelectronics STMPE811 chip.
+ *
+ * (c)2010 Luotao Fu <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/platform_device.h>
+
+#include <linux/mfd/stmpe811.h>
+
+#define STMPE811_GPIO_NAME "stmpe811-gpio"
+
+struct stmpe811_gpio {
+ struct stmpe811 *stm;
+ struct gpio_chip gpio;
+};
+
+static inline struct stmpe811_gpio *to_stmpe811_gpio(struct gpio_chip *chip)
+{
+ return container_of(chip, struct stmpe811_gpio, gpio);
+}
+
+static int stmpe811_gpio_direction_in(struct gpio_chip *chip, unsigned offset)
+{
+ struct stmpe811_gpio *stm_gpio = to_stmpe811_gpio(chip);
+
+ return stmpe811_reg_clear_bits(stm_gpio->stm,
+ STMPE811_REG_GPIO_DIR, BIT(offset));
+}
+
+static int stmpe811_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct stmpe811_gpio *stm_gpio = to_stmpe811_gpio(chip);
+ uint8_t gpio_sta;
+ int ret;
+
+ ret = stmpe811_reg_read(stm_gpio->stm,
+ STMPE811_REG_GPIO_MP_STA, &gpio_sta);
+ if (ret)
+ return ret;
+
+ if (gpio_sta & BIT(offset))
+ return 1;
+ else
+ return 0;
+}
+
+static inline int __stmpe811_gpio_set(struct stmpe811 *stm,
+ unsigned offset, int value)
+{
+ int ret;
+
+ if (value)
+ ret = stmpe811_reg_write(stm, STMPE811_REG_GPIO_SET_PIN,
+ BIT(offset));
+ else
+ ret = stmpe811_reg_write(stm, STMPE811_REG_GPIO_CLR_PIN,
+ BIT(offset));
+
+ return ret;
+}
+
+static int stmpe811_gpio_direction_out(struct gpio_chip *chip, unsigned offset,
+ int value)
+{
+ struct stmpe811_gpio *stm_gpio = to_stmpe811_gpio(chip);
+ struct stmpe811 *stm = stm_gpio->stm;
+ int ret;
+
+ ret = stmpe811_reg_set_bits(stm, STMPE811_REG_GPIO_DIR, BIT(offset));
+ if (ret)
+ goto out;
+
+ ret = __stmpe811_gpio_set(stm, offset, value);
+out:
+ return ret;
+}
+
+static void stmpe811_gpio_set(struct gpio_chip *chip, unsigned offset,
+ int value)
+{
+ struct stmpe811_gpio *stm_gpio = to_stmpe811_gpio(chip);
+
+ __stmpe811_gpio_set(stm_gpio->stm, offset, value);
+}
+
+static int stmpe811_gpio_request(struct gpio_chip *chip, unsigned offset)
+{
+ struct stmpe811_gpio *stm_gpio = to_stmpe811_gpio(chip);
+ struct stmpe811 *stm = stm_gpio->stm;
+
+ if (offset > 3 && (stm->active_flag & STMPE811_USE_TS))
+ return 1;
+ else
+ return stmpe811_reg_set_bits(stm, STMPE811_REG_GPIO_AF,
+ BIT(offset));
+}
+
+static struct gpio_chip gpio_chip = {
+ .label = STMPE811_GPIO_NAME,
+ .owner = THIS_MODULE,
+ .direction_input = stmpe811_gpio_direction_in,
+ .get = stmpe811_gpio_get,
+ .direction_output = stmpe811_gpio_direction_out,
+ .set = stmpe811_gpio_set,
+ .request = stmpe811_gpio_request,
+ .can_sleep = 1,
+};
+
+static int __devinit stmpe811_gpio_probe(struct platform_device *pdev)
+{
+ struct stmpe811 *stm = dev_get_drvdata(pdev->dev.parent);
+ struct stmpe811_platform_data *pdata = stm->pdata;
+ struct stmpe811_gpio_platform_data *gpio_pdata = NULL;
+ struct stmpe811_gpio *stm_gpio;
+
+ int ret, tmp;
+
+ stm_gpio = kzalloc(sizeof(*stm_gpio), GFP_KERNEL);
+ if (!stm_gpio)
+ return -ENOMEM;
+
+ stm_gpio->stm = stm;
+ stm_gpio->gpio = gpio_chip;
+ stm_gpio->gpio.ngpio = 8;
+ stm_gpio->gpio.dev = &pdev->dev;
+
+ if (pdata)
+ gpio_pdata = pdata->gpio_pdata;
+
+ if (gpio_pdata && gpio_pdata->gpio_base)
+ stm_gpio->gpio.base = gpio_pdata->gpio_base;
+ else
+ stm_gpio->gpio.base = -1;
+
+ ret = gpiochip_add(&stm_gpio->gpio);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Could not register gpiochip, %d\n", ret);
+ goto err_free_stmgpio;
+ }
+ /* enable clock supply */
+ ret = stmpe811_reg_clear_bits(stm, STMPE811_REG_SYS_CTRL2,
+ STMPE811_SYS_CTRL2_GPIO_OFF);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not enable clock for gpio\n");
+ goto err_free_gpiochip;
+ }
+
+ if (gpio_pdata && gpio_pdata->setup) {
+ ret = gpio_pdata->setup(stm);
+
+ if (ret != 0) {
+ dev_err(&pdev->dev, "setup hook failed, %d\n",
+ ret);
+ goto err_free_gpiochip;
+ }
+ }
+
+ dev_info(&pdev->dev, "registerd gpios %d..%d on a stmpe811\n",
+ stm_gpio->gpio.base,
+ stm_gpio->gpio.base + stm_gpio->gpio.ngpio - 1);
+ platform_set_drvdata(pdev, stm_gpio);
+
+ return ret;
+
+err_free_gpiochip:
+ tmp = gpiochip_remove(&stm_gpio->gpio);
+ if (tmp) {
+ dev_err(&pdev->dev, "Could net remove gpio chip\n");
+ return ret;
+ }
+err_free_stmgpio:
+ kfree(stm_gpio);
+ return ret;
+}
+
+static int __devexit stmpe811_gpio_remove(struct platform_device *pdev)
+{
+ struct stmpe811_gpio *stm_gpio = platform_get_drvdata(pdev);
+ struct stmpe811_platform_data *pdata = stm_gpio->stm->pdata;
+ struct stmpe811_gpio_platform_data *gpio_pdata = NULL;
+ int ret;
+
+ if (pdata)
+ gpio_pdata = pdata->gpio_pdata;
+
+ if (gpio_pdata && gpio_pdata->remove)
+ gpio_pdata->remove(stm_gpio->stm);
+
+ /* disable clock supply */
+ stmpe811_reg_set_bits(stm_gpio->stm, STMPE811_REG_SYS_CTRL2,
+ STMPE811_SYS_CTRL2_GPIO_OFF);
+
+ ret = gpiochip_remove(&stm_gpio->gpio);
+ if (ret == 0)
+ kfree(stm_gpio);
+
+ return ret;
+}
+
+static struct platform_driver stmpe811_gpio_driver = {
+ .driver.name = STMPE811_GPIO_NAME,
+ .driver.owner = THIS_MODULE,
+ .probe = stmpe811_gpio_probe,
+ .remove = __devexit_p(stmpe811_gpio_remove),
+};
+
+static int __init stmpe811_gpio_init(void)
+{
+ return platform_driver_register(&stmpe811_gpio_driver);
+}
+
+subsys_initcall(stmpe811_gpio_init);
+
+static void __exit stmpe811_gpio_exit(void)
+{
+ platform_driver_unregister(&stmpe811_gpio_driver);
+}
+
+module_exit(stmpe811_gpio_exit);
+
+MODULE_AUTHOR("Luotao Fu <[email protected]>");
+MODULE_DESCRIPTION("GPIO interface for STMicroelectronics STMPE811");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" STMPE811_GPIO_NAME);
--
1.7.1
This one adds a driver for STMPE811 4-wire resistive touchscreen
controller. STMPE811 is a multifunction device. Hence this driver
depends on stmpe811_core driver for core functionalities.
Signed-off-by: Luotao Fu <[email protected]>
---
drivers/input/touchscreen/Kconfig | 10 +
drivers/input/touchscreen/Makefile | 1 +
drivers/input/touchscreen/stmpe811_ts.c | 403 +++++++++++++++++++++++++++++++
3 files changed, 414 insertions(+), 0 deletions(-)
create mode 100644 drivers/input/touchscreen/stmpe811_ts.c
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 3b9d5e2..059b82b 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -603,4 +603,14 @@ config TOUCHSCREEN_TPS6507X
To compile this driver as a module, choose M here: the
module will be called tps6507x_ts.
+config TOUCHSCREEN_STMPE811
+ tristate "STMicroelectronics STMPE811 touchscreen"
+ depends on MFD_STMPE811
+ help
+ Say Y here if you want support for STMicroelectronics
+ STMPE811 based touchscreen controller.
+
+ To compile this driver as a module, choose M here: the
+ module will be called stmpe811_ts.
+
endif
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 497964a..9da8948 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -47,3 +47,4 @@ obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE) += mainstone-wm97xx.o
obj-$(CONFIG_TOUCHSCREEN_WM97XX_ZYLONITE) += zylonite-wm97xx.o
obj-$(CONFIG_TOUCHSCREEN_W90X900) += w90p910_ts.o
obj-$(CONFIG_TOUCHSCREEN_TPS6507X) += tps6507x-ts.o
+obj-$(CONFIG_TOUCHSCREEN_STMPE811) += stmpe811_ts.o
diff --git a/drivers/input/touchscreen/stmpe811_ts.c b/drivers/input/touchscreen/stmpe811_ts.c
new file mode 100644
index 0000000..3fed0ba
--- /dev/null
+++ b/drivers/input/touchscreen/stmpe811_ts.c
@@ -0,0 +1,403 @@
+/* STMicroelectronics STMPE811 Touchscreen Driver
+ *
+ * (C) 2010 Luotao Fu <[email protected]>
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+
+#include <linux/mfd/stmpe811.h>
+
+#define STMPE811_TSC_CTRL_OP_MOD_XYZ (0<<1)
+#define STMPE811_TSC_CTRL_OP_MOD_XY (1<<1)
+#define STMPE811_TSC_CTRL_OP_MOD_X (2<<1)
+#define STMPE811_TSC_CTRL_OP_MOD_Y (3<<1)
+#define STMPE811_TSC_CTRL_OP_MOD_Z (4<<1)
+
+#define STMPE811_TSC_CTRL_TSC_STA (1<<7)
+#define STMPE811_TSC_CTRL_TSC_EN (1<<0)
+
+#define STMPE811_TS_NAME "stmpe811-ts"
+#define XY_MASK 0xfff
+
+/*
+ * Module parameters
+ */
+
+/*
+ * Sample time:
+ *
+ * Set sample_time = 0 for 36 clocks
+ * sample_time = 1 for 44 clocks
+ * sample_time = 2 for 56 clocks
+ * sample_time = 3 for 64 clocks
+ * sample_time = 4 for 80 clocks
+ * sample_time = 5 for 96 clocks
+ * sample_time = 6 for 144 clocks
+ * This one defines ADC converstion time in number of clock.
+ */
+static int sample_time = 4;
+module_param(sample_time, int, 0444);
+MODULE_PARM_DESC(sample_time,
+ "Set ADC conversion time. Default is 4 (80 clocks)");
+
+/*
+ * ADC Bit mode:
+ *
+ * Set mod_12b = 0 for 10bit ADC
+ * mod_12b = 1 for 12bit ADC
+ */
+static int mod_12b = 1;
+module_param(mod_12b, int, 0444);
+MODULE_PARM_DESC(mod_12b, "Set ADC Bit mode. Default is 1 (12bit)");
+
+/*
+ * reference source:
+ *
+ * Set ref_sel = 0 for internal reference
+ * ref_sel = 1 for external reference
+ */
+static int ref_sel;
+module_param(ref_sel, int, 0444);
+MODULE_PARM_DESC(ref_sel,
+ "Set ADC reference source. Default is 0 (internal reference)");
+
+/*
+ * ADC Clock speed:
+ *
+ * Set adc_freq = 0 for 1.625 MHz
+ * adc_freq = 1 for 3.25 MHz
+ * adc_freq = 2 | adc_freq = 3 for 6.5 MHz
+ */
+static int adc_freq = 1;
+module_param(adc_freq, int, 0444);
+MODULE_PARM_DESC(adc_freq, "Set ADC clock speed. Default is 1 (3.25MHz)");
+
+/*
+ * Sample average control:
+ *
+ * Set ave_ctrl = 0 for 1 sample
+ * ave_ctrl = 1 for 2 samples
+ * ave_ctrl = 2 for 4 samples
+ * ave_ctrl = 3 for 8 samples
+ */
+static int ave_ctrl = 3;
+module_param(ave_ctrl, int, 0444);
+MODULE_PARM_DESC(adc_freq,
+ "Set average sample counts. Default is 3 (8 samples)");
+
+/*
+ * Touch detect interrupt delay:
+ *
+ * Set touch_det_delay = 0 for 10 us
+ * touch_det_delay = 1 for 50 us
+ * touch_det_delay = 2 for 100 us
+ * touch_det_delay = 3 for 500 us
+ * touch_det_delay = 4 for 1 ms
+ * touch_det_delay = 5 for 5 ms
+ * touch_det_delay = 6 for 10 ms
+ * touch_det_delay = 7 for 50 ms
+ * This one defines the delay for signaling a touch detection interrupt after
+ * the actual event
+ */
+static int touch_det_delay = 3;
+module_param(touch_det_delay, int, 0444);
+MODULE_PARM_DESC(touch_det_delay,
+ "Set touch detect delay. Default is 3 (500 us)");
+
+/*
+ * Panel driver settling time
+ *
+ * Set settling = 0 for 10 us
+ * settling = 1 for 100 us
+ * settling = 2 for 500 us
+ * settling = 3 for 1 ms
+ * settling = 4 for 5 ms
+ * settling = 5 for 10 ms
+ * settling = 6 for 50 ms
+ * settling = 7 for 100 ms
+ *
+ * This one defines the delay time the ADC shall give the pannel to settle its
+ * voltage for stable measurement.
+ */
+static int settling = 2;
+module_param(settling, int, 0444);
+MODULE_PARM_DESC(settling,
+ "Set panel driver settling time. Default is 2 (500 us)");
+
+/*
+ * Length of the fractional part in z
+ *
+ * value coding is quite identical with touch_det_delay above, only
+ * fraction_z ([0..7]) = Count of the fractional part
+ *
+ * This one allows to select range and accuracy of the pressure measurement
+ */
+static int fraction_z = 7;
+module_param(fraction_z, int, 0444);
+MODULE_PARM_DESC(fraction_z,
+ "Set fractional part of z. Default is 7 (7 fractional, 1 whole)");
+
+struct stmpe811_touch {
+ struct stmpe811 *stm;
+ struct input_dev *idev;
+ struct delayed_work work;
+};
+
+static void stmpe811_work(struct work_struct *work)
+{
+ u8 int_sta;
+ u32 timeout = 40;
+
+ struct stmpe811_touch *ts =
+ container_of(work, struct stmpe811_touch, work.work);
+
+ stmpe811_reg_read(ts->stm, STMPE811_REG_INT_STA, &int_sta);
+
+ /* touch_det sometimes get desasserted or just get stuck. This appears
+ * to be a silicon bug, We still have to clearify this with the
+ * manufacture. As a workaround We release the key anyway if the
+ * touch_det keeps coming in after 4ms, while the FIFO contains no value
+ * during the whole time. */
+ while ((int_sta & (1 << STMPE811_IRQ_TOUCH_DET)) && (timeout > 0)) {
+ timeout--;
+ stmpe811_reg_read(ts->stm, STMPE811_REG_INT_STA, &int_sta);
+ udelay(100);
+ }
+
+ input_report_abs(ts->idev, ABS_PRESSURE, 0);
+ input_sync(ts->idev);
+}
+
+static irqreturn_t stmpe811_ts_handler(int irq, void *data)
+{
+ u8 data_set[4];
+ int x, y, z;
+ struct stmpe811_touch *ts = data;
+
+ /* Cancel polling for release if we have new value available. */
+ cancel_delayed_work(&ts->work);
+
+ /*
+ * The FIFO sometimes just crashes and stops generating interrupts. This
+ * appears to be a silicon bug. We still have to clearify this with
+ * the manufacture. As a workaround we disable the TSC while we are
+ * collecting data and flush the FIFO after reading
+ */
+ stmpe811_reg_clear_bits(ts->stm, STMPE811_REG_TSC_CTRL,
+ STMPE811_TSC_CTRL_TSC_EN);
+
+ stmpe811_block_read(ts->stm, STMPE811_REG_TSC_DATA_XYZ, 4, data_set);
+
+ x = (data_set[0] << 4) | (data_set[1] >> 4);
+ y = ((data_set[1] & 0xf) << 8) | data_set[2];
+ z = data_set[3];
+
+ input_report_abs(ts->idev, ABS_X, x);
+ input_report_abs(ts->idev, ABS_Y, y);
+ input_report_abs(ts->idev, ABS_PRESSURE, z);
+ input_sync(ts->idev);
+
+ /* flush the FIFO after we have read out our values. */
+ stmpe811_reg_set_bits(ts->stm, STMPE811_REG_FIFO_STA,
+ STMPE811_FIFO_STA_RESET);
+ stmpe811_reg_clear_bits(ts->stm, STMPE811_REG_FIFO_STA,
+ STMPE811_FIFO_STA_RESET);
+
+ /* reenable the tsc */
+ stmpe811_reg_set_bits(ts->stm, STMPE811_REG_TSC_CTRL,
+ STMPE811_TSC_CTRL_TSC_EN);
+
+ /* start polling for touch_det to detect release */
+ schedule_delayed_work(&ts->work, HZ / 50);
+
+ return IRQ_HANDLED;
+}
+
+static int stmpe811_ts_open(struct input_dev *dev)
+{
+ struct stmpe811_touch *ts = input_get_drvdata(dev);
+
+ return stmpe811_reg_set_bits(ts->stm, STMPE811_REG_TSC_CTRL,
+ STMPE811_TSC_CTRL_TSC_EN);
+}
+
+static void stmpe811_ts_close(struct input_dev *dev)
+{
+ struct stmpe811_touch *ts = input_get_drvdata(dev);
+
+ stmpe811_reg_clear_bits(ts->stm, STMPE811_REG_TSC_CTRL,
+ STMPE811_TSC_CTRL_TSC_EN);
+}
+
+static int __devinit stmpe811_input_probe(struct platform_device *pdev)
+{
+ struct stmpe811_touch *ts;
+ struct input_dev *idev;
+ int ret = 0;
+
+ ts = kzalloc(sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ goto err_out;
+
+ idev = input_allocate_device();
+ if (!idev)
+ goto err_free_ts;
+
+ platform_set_drvdata(pdev, ts);
+ ts->stm = dev_get_drvdata(pdev->dev.parent);
+ ts->idev = idev;
+
+ INIT_DELAYED_WORK(&ts->work, stmpe811_work);
+
+ stmpe811_register_irq(ts->stm, STMPE811_IRQ_FIFO_TH,
+ stmpe811_ts_handler, ts);
+
+ ret = stmpe811_reg_clear_bits(ts->stm, STMPE811_REG_SYS_CTRL2,
+ (STMPE811_SYS_CTRL2_ADC_OFF |
+ STMPE811_SYS_CTRL2_TSC_OFF));
+ if (ret) {
+ dev_err(&pdev->dev, "Could not enable clock for ADC and TS\n");
+ goto err_free_plat;
+ }
+
+ ret = stmpe811_reg_set_bits(ts->stm, STMPE811_REG_ADC_CTRL1,
+ (sample_time << 4) | (mod_12b << 3) | (ref_sel << 1));
+ if (ret) {
+ dev_err(&pdev->dev, "Could not setup ADC\n");
+ goto err_free_plat;
+ }
+
+ ret = stmpe811_reg_set_bits(ts->stm, STMPE811_REG_ADC_CTRL2, adc_freq);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not setup ADC\n");
+ goto err_free_plat;
+ }
+
+ ret = stmpe811_reg_set_bits(ts->stm, STMPE811_REG_TSC_CFG,
+ (ave_ctrl << 6) | (touch_det_delay << 3) | settling);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not config touch\n");
+ goto err_free_plat;
+ }
+
+ ret = stmpe811_reg_set_bits(ts->stm,
+ STMPE811_REG_TSC_FRACTION_Z, fraction_z);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not config touch\n");
+ goto err_free_plat;
+ }
+
+ /* set FIFO to 1 for single point reading */
+ ret = stmpe811_reg_write(ts->stm, STMPE811_REG_FIFO_TH, 1);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not set FIFO\n");
+ goto err_free_plat;
+ }
+
+ ret = stmpe811_reg_set_bits(ts->stm, STMPE811_REG_TSC_CTRL,
+ STMPE811_TSC_CTRL_OP_MOD_XYZ);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not set mode\n");
+ goto err_free_plat;
+ }
+
+ idev->name = STMPE811_TS_NAME;
+ idev->id.bustype = BUS_I2C;
+ idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ idev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+
+ idev->open = stmpe811_ts_open;
+ idev->close = stmpe811_ts_close;
+
+ input_set_drvdata(idev, ts);
+
+ ret = input_register_device(idev);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not register input device\n");
+ goto err_free_plat;
+ }
+
+ ts->stm->active_flag |= STMPE811_USE_TS;
+
+ input_set_abs_params(idev, ABS_X, 0, XY_MASK, 0, 0);
+ input_set_abs_params(idev, ABS_Y, 0, XY_MASK, 0, 0);
+ input_set_abs_params(idev, ABS_PRESSURE, 0x0, 0xff, 0, 0);
+
+ return ret;
+
+err_free_plat:
+ platform_set_drvdata(pdev, NULL);
+ input_free_device(idev);
+err_free_ts:
+ kfree(ts);
+err_out:
+ return ret;
+}
+
+static int __devexit stmpe811_ts_remove(struct platform_device *pdev)
+{
+ struct stmpe811_touch *ts = platform_get_drvdata(pdev);
+
+ cancel_delayed_work(&ts->work);
+
+ stmpe811_free_irq(ts->stm, STMPE811_IRQ_FIFO_TH);
+ /*disable FIFO TH */
+ stmpe811_reg_write(ts->stm, STMPE811_REG_FIFO_TH, 0);
+
+ stmpe811_reg_set_bits(ts->stm, STMPE811_REG_SYS_CTRL2,
+ (STMPE811_SYS_CTRL2_ADC_OFF |
+ STMPE811_SYS_CTRL2_ADC_OFF));
+
+ ts->stm->active_flag &= ~STMPE811_USE_TS;
+ platform_set_drvdata(pdev, NULL);
+
+ input_unregister_device(ts->idev);
+ input_free_device(ts->idev);
+
+ kfree(ts);
+
+ return 0;
+}
+
+static struct platform_driver stmpe811_input_driver = {
+ .driver = {
+ .name = "stmpe811-ts",
+ },
+ .probe = stmpe811_input_probe,
+ .remove = __devexit_p(stmpe811_ts_remove),
+};
+
+static int __init stmpe811_input_init(void)
+{
+ return platform_driver_register(&stmpe811_input_driver);
+}
+
+module_init(stmpe811_input_init);
+
+static void __exit stmpe811_input_exit(void)
+{
+ platform_driver_unregister(&stmpe811_input_driver);
+}
+
+module_exit(stmpe811_input_exit);
+
+MODULE_AUTHOR("Luotao Fu <[email protected]>");
+MODULE_DESCRIPTION("STMPE811 touchscreen driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" STMPE811_TS_NAME);
--
1.7.1
STMPE811 is a multifunction device, which contains a GPIO controller, a
Touchscreen controller, an ADC and a temperature sensor. This patch adds a core
driver for this device. The driver provides core functionalities like accessing
the registers and management of subdevices. The device supports communication
through SPI and I2C interface. Currently we only support I2C.
Signed-off-by: Luotao Fu <[email protected]>
---
drivers/mfd/Kconfig | 8 +
drivers/mfd/Makefile | 1 +
drivers/mfd/stmpe811-core.c | 369 ++++++++++++++++++++++++++++++++++++++++++
include/linux/mfd/stmpe811.h | 126 ++++++++++++++
4 files changed, 504 insertions(+), 0 deletions(-)
create mode 100644 drivers/mfd/stmpe811-core.c
create mode 100644 include/linux/mfd/stmpe811.h
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 9da0e50..2c5388b 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -482,6 +482,14 @@ config MFD_JANZ_CMODIO
host many different types of MODULbus daughterboards, including
CAN and GPIO controllers.
+config MFD_STMPE811
+ tristate "STMicroelectronics STMPE811"
+ depends on I2C
+ select MFD_CORE
+ help
+ This is the core driver for the stmpe811 GPIO expander with touchscreen
+ controller, ADC and temperature sensor.
+
endif # MFD_SUPPORT
menu "Multimedia Capabilities Port drivers"
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index fb503e7..6a7ad83 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -71,3 +71,4 @@ obj-$(CONFIG_PMIC_ADP5520) += adp5520.o
obj-$(CONFIG_LPC_SCH) += lpc_sch.o
obj-$(CONFIG_MFD_RDC321X) += rdc321x-southbridge.o
obj-$(CONFIG_MFD_JANZ_CMODIO) += janz-cmodio.o
+obj-$(CONFIG_MFD_STMPE811) += stmpe811-core.o
diff --git a/drivers/mfd/stmpe811-core.c b/drivers/mfd/stmpe811-core.c
new file mode 100644
index 0000000..080b3d7
--- /dev/null
+++ b/drivers/mfd/stmpe811-core.c
@@ -0,0 +1,369 @@
+/*
+ * stmpe811-core.c -- core support for STMicroelectronics STMPE811 chip.
+ *
+ * based on pcf50633-core.c by Harald Welte <[email protected]> and
+ * Balaji Rao <[email protected]>
+ *
+ * (c)2010 Luotao Fu <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/stmpe811.h>
+
+static inline int __stmpe811_i2c_reads(struct i2c_client *client, int reg,
+ int len, u8 *val)
+{
+ int ret;
+
+ ret = i2c_smbus_read_i2c_block_data(client, reg, len, val);
+ if (ret < 0) {
+ dev_err(&client->dev, "failed reading from 0x%02x\n", reg);
+ return ret;
+ }
+ return 0;
+}
+
+static struct mfd_cell stmpe811_ts_dev = {
+ .name = "stmpe811-ts",
+};
+
+static struct mfd_cell stmpe811_gpio_dev = {
+ .name = "stmpe811-gpio",
+};
+
+static void stmpe811_irq_worker(struct work_struct *work)
+{
+ struct stmpe811 *stm;
+ int ret, bit;
+ u8 int_stat;
+ irq_handler_t handler;
+ void *data;
+
+ stm = container_of(work, struct stmpe811, irq_work);
+
+ ret = stmpe811_reg_read(stm, STMPE811_REG_INT_STA, &int_stat);
+ if (ret) {
+ dev_err(stm->dev, "Error reading INT registers\n");
+ goto out;
+ }
+
+ dev_dbg(stm->dev, "%s int_stat 0x%02x\n", __func__, int_stat);
+
+ for_each_set_bit(bit, (unsigned long *)&int_stat, STMPE811_NUM_IRQ) {
+ handler = stm->irqhandler[bit];
+ data = stm->irqdata[bit];
+ if (handler) {
+ /* mask the interrupt while calling handler */
+ stmpe811_reg_clear_bits(stm, STMPE811_REG_INT_EN,
+ 1 << bit);
+ /* call handler and acknowledge the interrupt */
+ handler(bit, data);
+ stmpe811_reg_set_bits(stm, STMPE811_REG_INT_STA,
+ (1 << bit));
+ /* demask the interrupt */
+ stmpe811_reg_set_bits(stm, STMPE811_REG_INT_EN,
+ 1 << bit);
+ }
+ }
+out:
+ put_device(stm->dev);
+ enable_irq(stm->irq);
+
+}
+
+static irqreturn_t stmpe811_irq(int irq, void *data)
+{
+ struct stmpe811 *stm = data;
+
+ get_device(stm->dev);
+ disable_irq_nosync(stm->irq);
+ queue_work(stm->work_queue, &stm->irq_work);
+
+ return IRQ_HANDLED;
+}
+
+static inline int __stmpe811_i2c_read(struct i2c_client *client,
+ int reg, u8 *val)
+{
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(client, reg);
+ if (ret < 0) {
+ dev_err(&client->dev, "failed reading at 0x%02x\n", reg);
+ return ret;
+ }
+ dev_dbg(&client->dev, "%s: value 0x%x from 0x%x\n", __func__, ret, reg);
+
+ *val = (u8) ret;
+ return 0;
+}
+
+static inline int __stmpe811_i2c_write(struct i2c_client *client,
+ int reg, u8 val)
+{
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, reg, val);
+ if (ret < 0) {
+ dev_err(&client->dev, "failed writing 0x%02x to 0x%02x\n",
+ val, reg);
+ return ret;
+ }
+ return 0;
+}
+
+int stmpe811_block_read(struct stmpe811 *stm, u8 reg, uint len, u8 *val)
+{
+ int ret;
+
+ mutex_lock(&stm->io_lock);
+ ret = __stmpe811_i2c_reads(stm->i2c_client, reg, len, val);
+ mutex_unlock(&stm->io_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe811_block_read);
+
+int stmpe811_reg_read(struct stmpe811 *stm, u8 reg, u8 *val)
+{
+ int ret;
+
+ mutex_lock(&stm->io_lock);
+ ret = __stmpe811_i2c_read(stm->i2c_client, reg, val);
+ mutex_unlock(&stm->io_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe811_reg_read);
+
+int stmpe811_reg_write(struct stmpe811 *stm, u8 reg, u8 val)
+{
+ int ret;
+
+ mutex_lock(&stm->io_lock);
+ ret = __stmpe811_i2c_write(stm->i2c_client, reg, val);
+ mutex_unlock(&stm->io_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe811_reg_write);
+
+int stmpe811_reg_set_bits(struct stmpe811 *stm, u8 reg, u8 val)
+{
+ int ret;
+ u8 tmp;
+
+ mutex_lock(&stm->io_lock);
+ ret = __stmpe811_i2c_read(stm->i2c_client, reg, &tmp);
+ if (ret < 0)
+ goto out;
+
+ tmp |= val;
+ ret = __stmpe811_i2c_write(stm->i2c_client, reg, tmp);
+
+out:
+ mutex_unlock(&stm->io_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe811_reg_set_bits);
+
+int stmpe811_reg_clear_bits(struct stmpe811 *stm, u8 reg, u8 val)
+{
+ int ret;
+ u8 tmp;
+
+ mutex_lock(&stm->io_lock);
+ ret = __stmpe811_i2c_read(stm->i2c_client, reg, &tmp);
+ if (ret < 0)
+ goto out;
+
+ tmp &= ~val;
+ ret = __stmpe811_i2c_write(stm->i2c_client, reg, tmp);
+
+out:
+ mutex_unlock(&stm->io_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe811_reg_clear_bits);
+
+int stmpe811_register_irq(struct stmpe811 *stm, int irq,
+ irq_handler_t handler, void *data)
+{
+ if (irq < 0 || irq > STMPE811_NUM_IRQ || !handler)
+ return -EINVAL;
+
+ if (WARN_ON(stm->irqhandler[irq]))
+ return -EBUSY;
+
+ stm->irqhandler[irq] = handler;
+ stm->irqdata[irq] = data;
+ /* unmask the irq */
+ return stmpe811_reg_set_bits(stm, STMPE811_REG_INT_EN, 1 << irq);
+}
+EXPORT_SYMBOL_GPL(stmpe811_register_irq);
+
+int stmpe811_free_irq(struct stmpe811 *stm, int irq)
+{
+ if (irq < 0 || irq > STMPE811_NUM_IRQ)
+ return -EINVAL;
+
+ stm->irqhandler[irq] = NULL;
+
+ return stmpe811_reg_clear_bits(stm, STMPE811_REG_INT_EN, 1 << irq);
+}
+EXPORT_SYMBOL_GPL(stmpe811_free_irq);
+
+static int __devinit stmpe811_probe(struct i2c_client *client,
+ const struct i2c_device_id *ids)
+{
+ struct stmpe811 *stm;
+ struct stmpe811_platform_data *pdata = client->dev.platform_data;
+ int ret = 0;
+ u8 chip_id[2], chip_ver = 0;
+
+ if (!client->irq) {
+ dev_err(&client->dev, "Missing IRQ\n");
+ return -ENOENT;
+ }
+
+ stm = kzalloc(sizeof(*stm), GFP_KERNEL);
+ if (!stm)
+ return -ENOMEM;
+
+ stm->pdata = pdata;
+
+ mutex_init(&stm->io_lock);
+
+ i2c_set_clientdata(client, stm);
+ stm->dev = &client->dev;
+ stm->i2c_client = client;
+ stm->irq = client->irq;
+ stm->work_queue = create_singlethread_workqueue("stmpe811");
+
+ if (!stm->work_queue) {
+ dev_err(&client->dev, "Failed to alloc workqueue\n");
+ ret = -ENOMEM;
+ goto err_free;
+ }
+
+ INIT_WORK(&stm->irq_work, stmpe811_irq_worker);
+
+ ret = stmpe811_block_read(stm, STMPE811_REG_CHIP_ID, 2, chip_id);
+ if ((ret < 0) || (((chip_id[0] << 8) | chip_id[1]) != 0x811)) {
+ dev_err(&client->dev, "could not verify stmpe811 chip id\n");
+ ret = -ENODEV;
+ goto err_destroy_workqueue;
+ }
+
+ stmpe811_reg_read(stm, STMPE811_REG_ID_VER, &chip_ver);
+ dev_info(&client->dev, "found stmpe811 chip id 0x%x version 0x%x\n",
+ (chip_id[0] << 8) | chip_id[1], chip_ver);
+
+ /* reset the device */
+ stmpe811_reg_write(stm, STMPE811_REG_SYS_CTRL1,
+ STMPE811_SYS_CTRL1_SOFT_RESET);
+
+ /* setup platform specific interrupt control flags and enable global
+ * interrupt */
+ stmpe811_reg_write(stm, STMPE811_REG_INT_CTRL,
+ STMPE811_INT_CTRL_GLOBAL_INT | pdata->int_conf);
+ ret =
+ request_irq(client->irq, stmpe811_irq, pdata->irq_flags, "stmpe811",
+ stm);
+ if (ret) {
+ dev_err(stm->dev, "Failed to request IRQ %d\n", ret);
+ goto err_destroy_workqueue;
+ }
+
+ if (pdata->flags & STMPE811_USE_TS)
+ ret =
+ mfd_add_devices(&client->dev, -1, &stmpe811_ts_dev, 1, NULL,
+ 0);
+ if (ret != 0)
+ goto err_release_irq;
+
+ if (pdata->flags & STMPE811_USE_GPIO)
+ ret =
+ mfd_add_devices(&client->dev, -1, &stmpe811_gpio_dev, 1,
+ NULL, 0);
+ if (ret != 0)
+ goto err_remove_mfd_devices;
+
+ return ret;
+
+err_remove_mfd_devices:
+ mfd_remove_devices(&client->dev);
+err_release_irq:
+ free_irq(client->irq, stm);
+err_destroy_workqueue:
+ destroy_workqueue(stm->work_queue);
+err_free:
+ mutex_destroy(&stm->io_lock);
+ i2c_set_clientdata(client, NULL);
+ kfree(stm);
+
+ return ret;
+}
+
+static int __devexit stmpe811_remove(struct i2c_client *client)
+{
+ struct stmpe811 *stm = i2c_get_clientdata(client);
+
+ stmpe811_reg_write(stm, STMPE811_REG_SYS_CTRL1,
+ STMPE811_SYS_CTRL1_HIBERNATE);
+
+ free_irq(stm->irq, stm);
+ destroy_workqueue(stm->work_queue);
+
+ mfd_remove_devices(&client->dev);
+
+ kfree(stm);
+
+ return 0;
+}
+
+static struct i2c_device_id stmpe811_id_table[] = {
+ {"stmpe811", 0x88},
+ { /* end of list */ }
+};
+
+static struct i2c_driver stmpe811_driver = {
+ .driver = {
+ .name = "stmpe811",
+ .owner = THIS_MODULE,
+ },
+ .probe = stmpe811_probe,
+ .remove = __devexit_p(stmpe811_remove),
+ .id_table = stmpe811_id_table,
+};
+
+static int __init stmpe811_init(void)
+{
+ return i2c_add_driver(&stmpe811_driver);
+}
+
+subsys_initcall(stmpe811_init);
+
+static void __exit stmpe811_exit(void)
+{
+ i2c_del_driver(&stmpe811_driver);
+}
+
+module_exit(stmpe811_exit);
+
+MODULE_DESCRIPTION
+ ("CORE Driver for STMPE811 Touch screen controller with GPIO expander");
+MODULE_AUTHOR("Luotao Fu <[email protected]>");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/stmpe811.h b/include/linux/mfd/stmpe811.h
new file mode 100644
index 0000000..ab2808a
--- /dev/null
+++ b/include/linux/mfd/stmpe811.h
@@ -0,0 +1,126 @@
+#ifndef __LINUX_MFD_STMPE811_H
+#define __LINUX_MFD_STMPE811_H
+
+#include <linux/i2c.h>
+#include <linux/workqueue.h>
+
+#define STMPE811_REG_CHIP_ID 0x00
+#define STMPE811_REG_ID_VER 0x02
+#define STMPE811_REG_SYS_CTRL1 0x03
+#define STMPE811_REG_SYS_CTRL2 0x04
+#define STMPE811_REG_SPI_CFG 0x08
+#define STMPE811_REG_INT_CTRL 0x09
+#define STMPE811_REG_INT_EN 0x0A
+#define STMPE811_REG_INT_STA 0x0B
+#define STMPE811_REG_GPIO_EN 0x0C
+#define STMPE811_REG_GPIO_INT_STA 0x0D
+#define STMPE811_REG_ADC_INT_EN 0x0E
+#define STMPE811_REG_ADC_INT_STA 0x0F
+#define STMPE811_REG_GPIO_SET_PIN 0x10
+#define STMPE811_REG_GPIO_CLR_PIN 0x11
+#define STMPE811_REG_GPIO_MP_STA 0x12
+#define STMPE811_REG_GPIO_DIR 0x13
+#define STMPE811_REG_GPIO_ED 0x14
+#define STMPE811_REG_GPIO_RE 0x15
+#define STMPE811_REG_GPIO_FE 0x16
+#define STMPE811_REG_GPIO_AF 0x17
+#define STMPE811_REG_ADC_CTRL1 0x20
+#define STMPE811_REG_ADC_CTRL2 0x21
+#define STMPE811_REG_ADC_CAPT 0x22
+#define STMPE811_REG_ADC_DATA_CH0 0x30
+#define STMPE811_REG_ADC_DATA_CH1 0x32
+#define STMPE811_REG_ADC_DATA_CH2 0x34
+#define STMPE811_REG_ADC_DATA_CH3 0x36
+#define STMPE811_REG_ADC_DATA_CH4 0x38
+#define STMPE811_REG_ADC_DATA_CH5 0x3A
+#define STMPE811_REG_ADC_DATA_CH6 0x3C
+#define STMPE811_REG_ADC_DATA_CH7 0x3E
+#define STMPE811_REG_TSC_CTRL 0x40
+#define STMPE811_REG_TSC_CFG 0x41
+#define STMPE811_REG_WDW_TR_X 0x42
+#define STMPE811_REG_WDW_TR_Y 0x44
+#define STMPE811_REG_WDW_BL_X 0x46
+#define STMPE811_REG_WDW_BL_Y 0x48
+#define STMPE811_REG_FIFO_TH 0x4A
+#define STMPE811_REG_FIFO_STA 0x4B
+#define STMPE811_REG_FIFO_SIZE 0x4C
+#define STMPE811_REG_TSC_DATA_X 0x4D
+#define STMPE811_REG_TSC_DATA_Y 0x4F
+#define STMPE811_REG_TSC_DATA_Z 0x51
+#define STMPE811_REG_TSC_DATA_XYZ 0x52
+#define STMPE811_REG_TSC_FRACTION_Z 0x56
+#define STMPE811_REG_TSC_DATA 0x57
+#define STMPE811_REG_TSC_DATA_SINGLE 0xD7
+#define STMPE811_REG_TSC_I_DRIVE 0x58
+#define STMPE811_REG_TSC_SHIELD 0x59
+#define STMPE811_REG_TEMP_CTRL 0x60
+
+#define STMPE811_IRQ_TOUCH_DET 0
+#define STMPE811_IRQ_FIFO_TH 1
+#define STMPE811_IRQ_FIFO_OFLOW 2
+#define STMPE811_IRQ_FIFO_FULL 3
+#define STMPE811_IRQ_FIFO_EMPTY 4
+#define STMPE811_IRQ_TEMP_SENS 5
+#define STMPE811_IRQ_ADC 6
+#define STMPE811_IRQ_GPIO 7
+#define STMPE811_NUM_IRQ 8
+
+#define STMPE811_SYS_CTRL1_HIBERNATE (1<<0)
+#define STMPE811_SYS_CTRL1_SOFT_RESET (1<<1)
+
+#define STMPE811_SYS_CTRL2_ADC_OFF (1<<0)
+#define STMPE811_SYS_CTRL2_TSC_OFF (1<<1)
+#define STMPE811_SYS_CTRL2_GPIO_OFF (1<<2)
+#define STMPE811_SYS_CTRL2_TS_OFF (1<<3)
+
+#define STMPE811_INT_CTRL_GLOBAL_INT (1<<0)
+#define STMPE811_INT_CTRL_INT_TYPE (1<<1)
+#define STMPE811_INT_CTRL_INT_POLARITY (1<<2)
+
+#define STMPE811_FIFO_STA_OFLOW (1<<7)
+#define STMPE811_FIFO_STA_FULL (1<<6)
+#define STMPE811_FIFO_STA_EMPTY (1<<5)
+#define STMPE811_FIFO_STA_TH_TRIG (1<<4)
+#define STMPE811_FIFO_STA_RESET (1<<0)
+
+#define STMPE811_USE_TS (1<<0)
+#define STMPE811_USE_GPIO (1<<1)
+
+struct stmpe811 {
+ struct device *dev;
+ struct i2c_client *i2c_client;
+
+ struct stmpe811_platform_data *pdata;
+ int irq;
+ irq_handler_t irqhandler[STMPE811_NUM_IRQ];
+ void *irqdata[STMPE811_NUM_IRQ];
+ struct work_struct irq_work;
+ struct workqueue_struct *work_queue;
+ struct mutex io_lock;
+ u8 active_flag;
+};
+
+struct stmpe811_gpio_platform_data {
+ unsigned int gpio_base;
+ int (*setup) (struct stmpe811 *stm);
+ void (*remove) (struct stmpe811 *stm);
+};
+
+struct stmpe811_platform_data {
+ unsigned int flags;
+ unsigned int irq_flags;
+ unsigned int int_conf;
+ struct stmpe811_gpio_platform_data *gpio_pdata;
+};
+
+int stmpe811_block_read(struct stmpe811 *stm, u8 reg, uint len, u8 *val);
+int stmpe811_reg_read(struct stmpe811 *pcf, u8 reg, u8 *val);
+int stmpe811_reg_write(struct stmpe811 *stm, u8 reg, u8 val);
+int stmpe811_reg_set_bits(struct stmpe811 *stm, u8 reg, u8 val);
+int stmpe811_reg_clear_bits(struct stmpe811 *stm, u8 reg, u8 val);
+
+int stmpe811_register_irq(struct stmpe811 *stm, int irq,
+ irq_handler_t handler, void *data);
+int stmpe811_free_irq(struct stmpe811 *stm, int irq);
+
+#endif
--
1.7.1
On 06/11/10 11:13, Luotao Fu wrote:
Hi couple of really minor comments inline.
Looks good to me,
Acked-by: Jonathan Cameron<[email protected]>
> STMPE811 is a multifunction device, which contains a GPIO controller, a
> Touchscreen controller, an ADC and a temperature sensor. This patch adds a core
> driver for this device. The driver provides core functionalities like accessing
> the registers and management of subdevices. The device supports communication
> through SPI and I2C interface. Currently we only support I2C.
>
> Signed-off-by: Luotao Fu <[email protected]>
> ---
> drivers/mfd/Kconfig | 8 +
> drivers/mfd/Makefile | 1 +
> drivers/mfd/stmpe811-core.c | 369 ++++++++++++++++++++++++++++++++++++++++++
> include/linux/mfd/stmpe811.h | 126 ++++++++++++++
> 4 files changed, 504 insertions(+), 0 deletions(-)
> create mode 100644 drivers/mfd/stmpe811-core.c
> create mode 100644 include/linux/mfd/stmpe811.h
>
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index 9da0e50..2c5388b 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -482,6 +482,14 @@ config MFD_JANZ_CMODIO
> host many different types of MODULbus daughterboards, including
> CAN and GPIO controllers.
>
> +config MFD_STMPE811
> + tristate "STMicroelectronics STMPE811"
> + depends on I2C
> + select MFD_CORE
> + help
> + This is the core driver for the stmpe811 GPIO expander with touchscreen
> + controller, ADC and temperature sensor.
> +
> endif # MFD_SUPPORT
>
> menu "Multimedia Capabilities Port drivers"
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index fb503e7..6a7ad83 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -71,3 +71,4 @@ obj-$(CONFIG_PMIC_ADP5520) += adp5520.o
> obj-$(CONFIG_LPC_SCH) += lpc_sch.o
> obj-$(CONFIG_MFD_RDC321X) += rdc321x-southbridge.o
> obj-$(CONFIG_MFD_JANZ_CMODIO) += janz-cmodio.o
> +obj-$(CONFIG_MFD_STMPE811) += stmpe811-core.o
> diff --git a/drivers/mfd/stmpe811-core.c b/drivers/mfd/stmpe811-core.c
> new file mode 100644
> index 0000000..080b3d7
> --- /dev/null
> +++ b/drivers/mfd/stmpe811-core.c
> @@ -0,0 +1,369 @@
> +/*
> + * stmpe811-core.c -- core support for STMicroelectronics STMPE811 chip.
> + *
> + * based on pcf50633-core.c by Harald Welte <[email protected]> and
> + * Balaji Rao <[email protected]>
> + *
> + * (c)2010 Luotao Fu <[email protected]>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/interrupt.h>
> +#include <linux/slab.h>
> +#include <linux/platform_device.h>
> +#include <linux/i2c.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/stmpe811.h>
> +
> +static inline int __stmpe811_i2c_reads(struct i2c_client *client, int reg,
> + int len, u8 *val)
> +{
> + int ret;
> +
> + ret = i2c_smbus_read_i2c_block_data(client, reg, len, val);
> + if (ret < 0) {
> + dev_err(&client->dev, "failed reading from 0x%02x\n", reg);
> + return ret;
> + }
> + return 0;
> +}
> +
> +static struct mfd_cell stmpe811_ts_dev = {
> + .name = "stmpe811-ts",
> +};
> +
> +static struct mfd_cell stmpe811_gpio_dev = {
> + .name = "stmpe811-gpio",
> +};
> +
> +static void stmpe811_irq_worker(struct work_struct *work)
> +{
> + struct stmpe811 *stm;
> + int ret, bit;
> + u8 int_stat;
> + irq_handler_t handler;
> + void *data;
> +
> + stm = container_of(work, struct stmpe811, irq_work);
> +
> + ret = stmpe811_reg_read(stm, STMPE811_REG_INT_STA, &int_stat);
> + if (ret) {
> + dev_err(stm->dev, "Error reading INT registers\n");
> + goto out;
> + }
> +
> + dev_dbg(stm->dev, "%s int_stat 0x%02x\n", __func__, int_stat);
> +
> + for_each_set_bit(bit, (unsigned long *)&int_stat, STMPE811_NUM_IRQ) {
> + handler = stm->irqhandler[bit];
> + data = stm->irqdata[bit];
> + if (handler) {
> + /* mask the interrupt while calling handler */
> + stmpe811_reg_clear_bits(stm, STMPE811_REG_INT_EN,
> + 1 << bit);
> + /* call handler and acknowledge the interrupt */
> + handler(bit, data);
> + stmpe811_reg_set_bits(stm, STMPE811_REG_INT_STA,
> + (1 << bit));
small formatting quirk. Why the brackets in the above?
> + /* demask the interrupt */
> + stmpe811_reg_set_bits(stm, STMPE811_REG_INT_EN,
> + 1 << bit);
> + }
> + }
> +out:
> + put_device(stm->dev);
> + enable_irq(stm->irq);
> +
bonus white line.
> +}
> +
> +static irqreturn_t stmpe811_irq(int irq, void *data)
> +{
> + struct stmpe811 *stm = data;
> +
> + get_device(stm->dev);
> + disable_irq_nosync(stm->irq);
> + queue_work(stm->work_queue, &stm->irq_work);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static inline int __stmpe811_i2c_read(struct i2c_client *client,
> + int reg, u8 *val)
> +{
> + int ret;
> +
> + ret = i2c_smbus_read_byte_data(client, reg);
> + if (ret < 0) {
> + dev_err(&client->dev, "failed reading at 0x%02x\n", reg);
> + return ret;
> + }
> + dev_dbg(&client->dev, "%s: value 0x%x from 0x%x\n", __func__, ret, reg);
> +
> + *val = (u8) ret;
> + return 0;
> +}
> +
> +static inline int __stmpe811_i2c_write(struct i2c_client *client,
> + int reg, u8 val)
> +{
> + int ret;
> +
> + ret = i2c_smbus_write_byte_data(client, reg, val);
> + if (ret < 0) {
> + dev_err(&client->dev, "failed writing 0x%02x to 0x%02x\n",
> + val, reg);
> + return ret;
> + }
> + return 0;
> +}
> +
> +int stmpe811_block_read(struct stmpe811 *stm, u8 reg, uint len, u8 *val)
> +{
> + int ret;
> +
> + mutex_lock(&stm->io_lock);
> + ret = __stmpe811_i2c_reads(stm->i2c_client, reg, len, val);
> + mutex_unlock(&stm->io_lock);
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(stmpe811_block_read);
> +
> +int stmpe811_reg_read(struct stmpe811 *stm, u8 reg, u8 *val)
> +{
> + int ret;
> +
> + mutex_lock(&stm->io_lock);
> + ret = __stmpe811_i2c_read(stm->i2c_client, reg, val);
> + mutex_unlock(&stm->io_lock);
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(stmpe811_reg_read);
> +
> +int stmpe811_reg_write(struct stmpe811 *stm, u8 reg, u8 val)
> +{
> + int ret;
> +
> + mutex_lock(&stm->io_lock);
> + ret = __stmpe811_i2c_write(stm->i2c_client, reg, val);
> + mutex_unlock(&stm->io_lock);
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(stmpe811_reg_write);
> +
> +int stmpe811_reg_set_bits(struct stmpe811 *stm, u8 reg, u8 val)
> +{
> + int ret;
> + u8 tmp;
> +
> + mutex_lock(&stm->io_lock);
> + ret = __stmpe811_i2c_read(stm->i2c_client, reg, &tmp);
> + if (ret < 0)
> + goto out;
> +
> + tmp |= val;
> + ret = __stmpe811_i2c_write(stm->i2c_client, reg, tmp);
> +
> +out:
> + mutex_unlock(&stm->io_lock);
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(stmpe811_reg_set_bits);
> +
> +int stmpe811_reg_clear_bits(struct stmpe811 *stm, u8 reg, u8 val)
> +{
> + int ret;
> + u8 tmp;
> +
> + mutex_lock(&stm->io_lock);
> + ret = __stmpe811_i2c_read(stm->i2c_client, reg, &tmp);
> + if (ret < 0)
> + goto out;
> +
> + tmp &= ~val;
> + ret = __stmpe811_i2c_write(stm->i2c_client, reg, tmp);
> +
> +out:
> + mutex_unlock(&stm->io_lock);
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(stmpe811_reg_clear_bits);
> +
> +int stmpe811_register_irq(struct stmpe811 *stm, int irq,
> + irq_handler_t handler, void *data)
> +{
> + if (irq < 0 || irq > STMPE811_NUM_IRQ || !handler)
> + return -EINVAL;
> +
> + if (WARN_ON(stm->irqhandler[irq]))
> + return -EBUSY;
> +
> + stm->irqhandler[irq] = handler;
> + stm->irqdata[irq] = data;
> + /* unmask the irq */
> + return stmpe811_reg_set_bits(stm, STMPE811_REG_INT_EN, 1 << irq);
> +}
> +EXPORT_SYMBOL_GPL(stmpe811_register_irq);
> +
> +int stmpe811_free_irq(struct stmpe811 *stm, int irq)
> +{
> + if (irq < 0 || irq > STMPE811_NUM_IRQ)
> + return -EINVAL;
> +
> + stm->irqhandler[irq] = NULL;
> +
> + return stmpe811_reg_clear_bits(stm, STMPE811_REG_INT_EN, 1 << irq);
> +}
> +EXPORT_SYMBOL_GPL(stmpe811_free_irq);
> +
> +static int __devinit stmpe811_probe(struct i2c_client *client,
> + const struct i2c_device_id *ids)
> +{
> + struct stmpe811 *stm;
> + struct stmpe811_platform_data *pdata = client->dev.platform_data;
> + int ret = 0;
> + u8 chip_id[2], chip_ver = 0;
> +
> + if (!client->irq) {
> + dev_err(&client->dev, "Missing IRQ\n");
> + return -ENOENT;
> + }
> +
> + stm = kzalloc(sizeof(*stm), GFP_KERNEL);
> + if (!stm)
> + return -ENOMEM;
> +
> + stm->pdata = pdata;
> +
> + mutex_init(&stm->io_lock);
> +
> + i2c_set_clientdata(client, stm);
> + stm->dev = &client->dev;
> + stm->i2c_client = client;
> + stm->irq = client->irq;
> + stm->work_queue = create_singlethread_workqueue("stmpe811");
> +
> + if (!stm->work_queue) {
> + dev_err(&client->dev, "Failed to alloc workqueue\n");
> + ret = -ENOMEM;
> + goto err_free;
> + }
> +
> + INIT_WORK(&stm->irq_work, stmpe811_irq_worker);
> +
> + ret = stmpe811_block_read(stm, STMPE811_REG_CHIP_ID, 2, chip_id);
> + if ((ret < 0) || (((chip_id[0] << 8) | chip_id[1]) != 0x811)) {
> + dev_err(&client->dev, "could not verify stmpe811 chip id\n");
> + ret = -ENODEV;
> + goto err_destroy_workqueue;
> + }
> +
> + stmpe811_reg_read(stm, STMPE811_REG_ID_VER, &chip_ver);
> + dev_info(&client->dev, "found stmpe811 chip id 0x%x version 0x%x\n",
> + (chip_id[0] << 8) | chip_id[1], chip_ver);
> +
> + /* reset the device */
> + stmpe811_reg_write(stm, STMPE811_REG_SYS_CTRL1,
> + STMPE811_SYS_CTRL1_SOFT_RESET);
> +
> + /* setup platform specific interrupt control flags and enable global
> + * interrupt */
> + stmpe811_reg_write(stm, STMPE811_REG_INT_CTRL,
> + STMPE811_INT_CTRL_GLOBAL_INT | pdata->int_conf);
> + ret =
> + request_irq(client->irq, stmpe811_irq, pdata->irq_flags, "stmpe811",
> + stm);
> + if (ret) {
> + dev_err(stm->dev, "Failed to request IRQ %d\n", ret);
> + goto err_destroy_workqueue;
> + }
> +
> + if (pdata->flags & STMPE811_USE_TS)
> + ret =
> + mfd_add_devices(&client->dev, -1, &stmpe811_ts_dev, 1, NULL,
> + 0);
> + if (ret != 0)
> + goto err_release_irq;
> +
> + if (pdata->flags & STMPE811_USE_GPIO)
> + ret =
> + mfd_add_devices(&client->dev, -1, &stmpe811_gpio_dev, 1,
> + NULL, 0);
> + if (ret != 0)
> + goto err_remove_mfd_devices;
> +
> + return ret;
> +
> +err_remove_mfd_devices:
> + mfd_remove_devices(&client->dev);
> +err_release_irq:
> + free_irq(client->irq, stm);
> +err_destroy_workqueue:
> + destroy_workqueue(stm->work_queue);
> +err_free:
> + mutex_destroy(&stm->io_lock);
> + i2c_set_clientdata(client, NULL);
> + kfree(stm);
> +
> + return ret;
> +}
> +
> +static int __devexit stmpe811_remove(struct i2c_client *client)
> +{
> + struct stmpe811 *stm = i2c_get_clientdata(client);
> +
> + stmpe811_reg_write(stm, STMPE811_REG_SYS_CTRL1,
> + STMPE811_SYS_CTRL1_HIBERNATE);
> +
> + free_irq(stm->irq, stm);
> + destroy_workqueue(stm->work_queue);
> +
> + mfd_remove_devices(&client->dev);
> +
> + kfree(stm);
> +
> + return 0;
> +}
> +
> +static struct i2c_device_id stmpe811_id_table[] = {
> + {"stmpe811", 0x88},
> + { /* end of list */ }
> +};
> +
> +static struct i2c_driver stmpe811_driver = {
> + .driver = {
> + .name = "stmpe811",
> + .owner = THIS_MODULE,
> + },
> + .probe = stmpe811_probe,
> + .remove = __devexit_p(stmpe811_remove),
> + .id_table = stmpe811_id_table,
> +};
> +
> +static int __init stmpe811_init(void)
> +{
> + return i2c_add_driver(&stmpe811_driver);
> +}
> +
> +subsys_initcall(stmpe811_init);
> +
> +static void __exit stmpe811_exit(void)
> +{
> + i2c_del_driver(&stmpe811_driver);
> +}
> +
> +module_exit(stmpe811_exit);
> +
> +MODULE_DESCRIPTION
> + ("CORE Driver for STMPE811 Touch screen controller with GPIO expander");
> +MODULE_AUTHOR("Luotao Fu <[email protected]>");
> +MODULE_LICENSE("GPL");
> diff --git a/include/linux/mfd/stmpe811.h b/include/linux/mfd/stmpe811.h
> new file mode 100644
> index 0000000..ab2808a
> --- /dev/null
> +++ b/include/linux/mfd/stmpe811.h
> @@ -0,0 +1,126 @@
> +#ifndef __LINUX_MFD_STMPE811_H
> +#define __LINUX_MFD_STMPE811_H
> +
> +#include <linux/i2c.h>
> +#include <linux/workqueue.h>
> +
Personally I'd be inclined to put these in the
subdevice drivers that care about them.
Just keep the ones used by multiple subdevices
in here...
> +#define STMPE811_REG_CHIP_ID 0x00
> +#define STMPE811_REG_ID_VER 0x02
> +#define STMPE811_REG_SYS_CTRL1 0x03
> +#define STMPE811_REG_SYS_CTRL2 0x04
> +#define STMPE811_REG_SPI_CFG 0x08
> +#define STMPE811_REG_INT_CTRL 0x09
> +#define STMPE811_REG_INT_EN 0x0A
> +#define STMPE811_REG_INT_STA 0x0B
> +#define STMPE811_REG_GPIO_EN 0x0C
> +#define STMPE811_REG_GPIO_INT_STA 0x0D
> +#define STMPE811_REG_ADC_INT_EN 0x0E
> +#define STMPE811_REG_ADC_INT_STA 0x0F
> +#define STMPE811_REG_GPIO_SET_PIN 0x10
> +#define STMPE811_REG_GPIO_CLR_PIN 0x11
> +#define STMPE811_REG_GPIO_MP_STA 0x12
> +#define STMPE811_REG_GPIO_DIR 0x13
> +#define STMPE811_REG_GPIO_ED 0x14
> +#define STMPE811_REG_GPIO_RE 0x15
> +#define STMPE811_REG_GPIO_FE 0x16
> +#define STMPE811_REG_GPIO_AF 0x17
> +#define STMPE811_REG_ADC_CTRL1 0x20
> +#define STMPE811_REG_ADC_CTRL2 0x21
> +#define STMPE811_REG_ADC_CAPT 0x22
> +#define STMPE811_REG_ADC_DATA_CH0 0x30
> +#define STMPE811_REG_ADC_DATA_CH1 0x32
> +#define STMPE811_REG_ADC_DATA_CH2 0x34
> +#define STMPE811_REG_ADC_DATA_CH3 0x36
> +#define STMPE811_REG_ADC_DATA_CH4 0x38
> +#define STMPE811_REG_ADC_DATA_CH5 0x3A
> +#define STMPE811_REG_ADC_DATA_CH6 0x3C
> +#define STMPE811_REG_ADC_DATA_CH7 0x3E
> +#define STMPE811_REG_TSC_CTRL 0x40
> +#define STMPE811_REG_TSC_CFG 0x41
> +#define STMPE811_REG_WDW_TR_X 0x42
> +#define STMPE811_REG_WDW_TR_Y 0x44
> +#define STMPE811_REG_WDW_BL_X 0x46
> +#define STMPE811_REG_WDW_BL_Y 0x48
> +#define STMPE811_REG_FIFO_TH 0x4A
> +#define STMPE811_REG_FIFO_STA 0x4B
> +#define STMPE811_REG_FIFO_SIZE 0x4C
> +#define STMPE811_REG_TSC_DATA_X 0x4D
> +#define STMPE811_REG_TSC_DATA_Y 0x4F
> +#define STMPE811_REG_TSC_DATA_Z 0x51
> +#define STMPE811_REG_TSC_DATA_XYZ 0x52
> +#define STMPE811_REG_TSC_FRACTION_Z 0x56
> +#define STMPE811_REG_TSC_DATA 0x57
> +#define STMPE811_REG_TSC_DATA_SINGLE 0xD7
> +#define STMPE811_REG_TSC_I_DRIVE 0x58
> +#define STMPE811_REG_TSC_SHIELD 0x59
> +#define STMPE811_REG_TEMP_CTRL 0x60
> +
> +#define STMPE811_IRQ_TOUCH_DET 0
> +#define STMPE811_IRQ_FIFO_TH 1
> +#define STMPE811_IRQ_FIFO_OFLOW 2
> +#define STMPE811_IRQ_FIFO_FULL 3
> +#define STMPE811_IRQ_FIFO_EMPTY 4
> +#define STMPE811_IRQ_TEMP_SENS 5
> +#define STMPE811_IRQ_ADC 6
> +#define STMPE811_IRQ_GPIO 7
> +#define STMPE811_NUM_IRQ 8
> +
> +#define STMPE811_SYS_CTRL1_HIBERNATE (1<<0)
> +#define STMPE811_SYS_CTRL1_SOFT_RESET (1<<1)
> +
> +#define STMPE811_SYS_CTRL2_ADC_OFF (1<<0)
> +#define STMPE811_SYS_CTRL2_TSC_OFF (1<<1)
> +#define STMPE811_SYS_CTRL2_GPIO_OFF (1<<2)
> +#define STMPE811_SYS_CTRL2_TS_OFF (1<<3)
> +
> +#define STMPE811_INT_CTRL_GLOBAL_INT (1<<0)
> +#define STMPE811_INT_CTRL_INT_TYPE (1<<1)
> +#define STMPE811_INT_CTRL_INT_POLARITY (1<<2)
> +
> +#define STMPE811_FIFO_STA_OFLOW (1<<7)
> +#define STMPE811_FIFO_STA_FULL (1<<6)
> +#define STMPE811_FIFO_STA_EMPTY (1<<5)
> +#define STMPE811_FIFO_STA_TH_TRIG (1<<4)
> +#define STMPE811_FIFO_STA_RESET (1<<0)
> +
> +#define STMPE811_USE_TS (1<<0)
> +#define STMPE811_USE_GPIO (1<<1)
> +
> +struct stmpe811 {
> + struct device *dev;
> + struct i2c_client *i2c_client;
> +
> + struct stmpe811_platform_data *pdata;
> + int irq;
> + irq_handler_t irqhandler[STMPE811_NUM_IRQ];
> + void *irqdata[STMPE811_NUM_IRQ];
> + struct work_struct irq_work;
> + struct workqueue_struct *work_queue;
> + struct mutex io_lock;
> + u8 active_flag;
> +};
> +
> +struct stmpe811_gpio_platform_data {
> + unsigned int gpio_base;
> + int (*setup) (struct stmpe811 *stm);
> + void (*remove) (struct stmpe811 *stm);
> +};
> +
> +struct stmpe811_platform_data {
> + unsigned int flags;
> + unsigned int irq_flags;
> + unsigned int int_conf;
> + struct stmpe811_gpio_platform_data *gpio_pdata;
> +};
> +
> +int stmpe811_block_read(struct stmpe811 *stm, u8 reg, uint len, u8 *val);
> +int stmpe811_reg_read(struct stmpe811 *pcf, u8 reg, u8 *val);
> +int stmpe811_reg_write(struct stmpe811 *stm, u8 reg, u8 val);
> +int stmpe811_reg_set_bits(struct stmpe811 *stm, u8 reg, u8 val);
> +int stmpe811_reg_clear_bits(struct stmpe811 *stm, u8 reg, u8 val);
> +
> +int stmpe811_register_irq(struct stmpe811 *stm, int irq,
> + irq_handler_t handler, void *data);
> +int stmpe811_free_irq(struct stmpe811 *stm, int irq);
> +
> +#endif
On Fri, Jun 11, 2010 at 12:13:13PM +0200, Luotao Fu wrote:
> + for_each_set_bit(bit, (unsigned long *)&int_stat, STMPE811_NUM_IRQ) {
> + handler = stm->irqhandler[bit];
> + data = stm->irqdata[bit];
> + if (handler) {
You should be using genirq here - just call handle_nested_irq() if the
IRQ is asserted and let genirq manage the handler for you.
> +static irqreturn_t stmpe811_irq(int irq, void *data)
> +{
> + struct stmpe811 *stm = data;
> +
> + get_device(stm->dev);
> + disable_irq_nosync(stm->irq);
> + queue_work(stm->work_queue, &stm->irq_work);
> +
> + return IRQ_HANDLED;
> +}
You should use request_threaded_irq() for the main IRQ - it will take
care of all this for you.
> +static struct i2c_device_id stmpe811_id_table[] = {
> + {"stmpe811", 0x88},
Any reason for the 0x88 - you don't seem to use it anywhere?
> +int stmpe811_register_irq(struct stmpe811 *stm, int irq,
> + irq_handler_t handler, void *data);
> +int stmpe811_free_irq(struct stmpe811 *stm, int irq);
If you use genirq these can be dropped - this will also mean that
existing generic drivers using the standard GPIO and IRQ frameworks will
be able to use the chip without modification.
On Fri, Jun 11, 2010 at 12:13:15PM +0200, Luotao Fu wrote:
> +/*
> + * reference source:
> + *
> + * Set ref_sel = 0 for internal reference
> + * ref_sel = 1 for external reference
> + */
> +static int ref_sel;
> +module_param(ref_sel, int, 0444);
> +MODULE_PARM_DESC(ref_sel,
> + "Set ADC reference source. Default is 0 (internal reference)");
This looks like something that ought to be configured via platform data
since the value that should be used is going to be determined by the
board schematic. Similar comments apply to most of the other module
parameters, though in some cases sysfs files might be more appropriate
as well, or in addition.
Hi,
On Fri, Jun 11, 2010 at 12:03:05PM +0100, Jonathan Cameron wrote:
> On 06/11/10 11:13, Luotao Fu wrote:
>
> Hi couple of really minor comments inline.
>
> Looks good to me,
>
> Acked-by: Jonathan Cameron<[email protected]>
Thanks for reviewing the code. Will fix the stuff you pointed out in V2.
cheers
Luotao Fu
--
Pengutronix e.K. | Dipl.-Ing. Luotao Fu |
Industrial Linux Solutions | http://www.pengutronix.de/ |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
Hi,
On Fri, Jun 11, 2010 at 12:13:26PM +0100, Mark Brown wrote:
> On Fri, Jun 11, 2010 at 12:13:13PM +0200, Luotao Fu wrote:
>
> > + for_each_set_bit(bit, (unsigned long *)&int_stat, STMPE811_NUM_IRQ) {
> > + handler = stm->irqhandler[bit];
> > + data = stm->irqdata[bit];
> > + if (handler) {
>
> You should be using genirq here - just call handle_nested_irq() if the
> IRQ is asserted and let genirq manage the handler for you.
>
ah OK, I did think about using nested irq, was however too lazy to find
out how it works. ;-). Will fix in V2.
> > +static irqreturn_t stmpe811_irq(int irq, void *data)
> > +{
> > + struct stmpe811 *stm = data;
> > +
> > + get_device(stm->dev);
> > + disable_irq_nosync(stm->irq);
> > + queue_work(stm->work_queue, &stm->irq_work);
> > +
> > + return IRQ_HANDLED;
> > +}
>
> You should use request_threaded_irq() for the main IRQ - it will take
> care of all this for you.
>
>
> > +static struct i2c_device_id stmpe811_id_table[] = {
> > + {"stmpe811", 0x88},
>
> Any reason for the 0x88 - you don't seem to use it anywhere?
>
doh, this is bogus. will fix in the next round.
> > +int stmpe811_register_irq(struct stmpe811 *stm, int irq,
> > + irq_handler_t handler, void *data);
> > +int stmpe811_free_irq(struct stmpe811 *stm, int irq);
>
> If you use genirq these can be dropped - this will also mean that
> existing generic drivers using the standard GPIO and IRQ frameworks will
> be able to use the chip without modification.
all right.
Thanks
cheers
Luotao Fu
--
Pengutronix e.K. | Dipl.-Ing. Luotao Fu |
Industrial Linux Solutions | http://www.pengutronix.de/ |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
Hi,
On Fri, Jun 11, 2010 at 12:21:02PM +0100, Mark Brown wrote:
> On Fri, Jun 11, 2010 at 12:13:15PM +0200, Luotao Fu wrote:
>
> > +/*
> > + * reference source:
> > + *
> > + * Set ref_sel = 0 for internal reference
> > + * ref_sel = 1 for external reference
> > + */
> > +static int ref_sel;
> > +module_param(ref_sel, int, 0444);
> > +MODULE_PARM_DESC(ref_sel,
> > + "Set ADC reference source. Default is 0 (internal reference)");
>
> This looks like something that ought to be configured via platform data
> since the value that should be used is going to be determined by the
> board schematic. Similar comments apply to most of the other module
> parameters, though in some cases sysfs files might be more appropriate
> as well, or in addition.
OK. These stuffs are indeed platform dependent. I'll create a platform
data for it than.
Thanks
Luotao Fu
--
Pengutronix e.K. | Dipl.-Ing. Luotao Fu |
Industrial Linux Solutions | http://www.pengutronix.de/ |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
Hi Fu,
> +err_free:
> + mutex_destroy(&stm->io_lock);
> + i2c_set_clientdata(client, NULL);
Please drop the clientdata line; the core clears the pointer now.
Regards,
Wolfram
--
Pengutronix e.K. | Wolfram Sang |
Industrial Linux Solutions | http://www.pengutronix.de/ |
Hi,
in the following is the V2 patch serie for the stmpe811 chip. Changes are made
all over the place:
* irq management of the core
* configuration parameter passing in the touch screen driver.
* Some additional locking stuff
* i2c client stuff
* formating fix
etc.
See the individual patch header for more details.
thanks
cheers
Luotao Fu
This one adds a driver for STMPE811 4-wire resistive touchscreen
controller. STMPE811 is a multifunction device. Hence this driver
depends on stmpe811_core driver for core functionalities.
Signed-off-by: Luotao Fu <[email protected]>
---
V2 Changes:
* include subsystem headers since they are now remove from the mfd
core header file
* use genirq to register threaded isr. The stmpe811 own irq callbacks
are dropped in the core driver.
* use STMPE811_TS_NAME for name all over the place
* wait for completion of core IO operation before cancelling polling of
release event.
* switch to platform data for configuration parameters.
* add FIFO reset to open callback, also reset FIFO before reporting
release.
drivers/input/touchscreen/Kconfig | 10 +
drivers/input/touchscreen/Makefile | 1 +
drivers/input/touchscreen/stmpe811_ts.c | 361 +++++++++++++++++++++++++++++++
include/linux/mfd/stmpe811.h | 35 +++
4 files changed, 407 insertions(+), 0 deletions(-)
create mode 100644 drivers/input/touchscreen/stmpe811_ts.c
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 3b9d5e2..059b82b 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -603,4 +603,14 @@ config TOUCHSCREEN_TPS6507X
To compile this driver as a module, choose M here: the
module will be called tps6507x_ts.
+config TOUCHSCREEN_STMPE811
+ tristate "STMicroelectronics STMPE811 touchscreen"
+ depends on MFD_STMPE811
+ help
+ Say Y here if you want support for STMicroelectronics
+ STMPE811 based touchscreen controller.
+
+ To compile this driver as a module, choose M here: the
+ module will be called stmpe811_ts.
+
endif
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 497964a..9da8948 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -47,3 +47,4 @@ obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE) += mainstone-wm97xx.o
obj-$(CONFIG_TOUCHSCREEN_WM97XX_ZYLONITE) += zylonite-wm97xx.o
obj-$(CONFIG_TOUCHSCREEN_W90X900) += w90p910_ts.o
obj-$(CONFIG_TOUCHSCREEN_TPS6507X) += tps6507x-ts.o
+obj-$(CONFIG_TOUCHSCREEN_STMPE811) += stmpe811_ts.o
diff --git a/drivers/input/touchscreen/stmpe811_ts.c b/drivers/input/touchscreen/stmpe811_ts.c
new file mode 100644
index 0000000..ee6a547
--- /dev/null
+++ b/drivers/input/touchscreen/stmpe811_ts.c
@@ -0,0 +1,361 @@
+/* STMicroelectronics STMPE811 Touchscreen Driver
+ *
+ * (C) 2010 Luotao Fu <[email protected]>
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/workqueue.h>
+
+#include <linux/mfd/stmpe811.h>
+
+#define STMPE811_TSC_CTRL_OP_MOD_XYZ (0<<1)
+#define STMPE811_TSC_CTRL_OP_MOD_XY (1<<1)
+#define STMPE811_TSC_CTRL_OP_MOD_X (2<<1)
+#define STMPE811_TSC_CTRL_OP_MOD_Y (3<<1)
+#define STMPE811_TSC_CTRL_OP_MOD_Z (4<<1)
+
+#define STMPE811_TSC_CTRL_TSC_STA (1<<7)
+#define STMPE811_TSC_CTRL_TSC_EN (1<<0)
+
+#define SAMPLE_TIME(x) ((x & 0xf) << 4)
+#define MOD_12B(x) ((x & 0x1) << 3)
+#define REF_SEL(x) ((x & 0x1) << 1)
+#define ADC_FREQ(x) (x & 0x3)
+#define AVE_CTRL(x) ((x & 0x3) << 6)
+#define DET_DELAY(x) ((x & 0x7) << 3)
+#define SETTLING(x) ((x & 0x7))
+#define FRACTION_Z(x) ((x & 0x7))
+
+#define STMPE811_TS_NAME "stmpe811-ts"
+#define XY_MASK 0xfff
+
+struct stmpe811_touch {
+ struct stmpe811 *stm;
+ struct input_dev *idev;
+ struct delayed_work work;
+ u8 sample_time;
+ u8 mod_12b;
+ u8 ref_sel;
+ u8 adc_freq;
+ u8 ave_ctrl;
+ u8 touch_det_delay;
+ u8 settling;
+ u8 fraction_z;
+};
+
+static void stmpe811_work(struct work_struct *work)
+{
+ u8 int_sta;
+ u32 timeout = 40;
+
+ struct stmpe811_touch *ts =
+ container_of(work, struct stmpe811_touch, work.work);
+
+ stmpe811_reg_read(ts->stm, STMPE811_REG_INT_STA, &int_sta);
+
+ /* touch_det sometimes get desasserted or just get stuck. This appears
+ * to be a silicon bug, We still have to clearify this with the
+ * manufacture. As a workaround We release the key anyway if the
+ * touch_det keeps coming in after 4ms, while the FIFO contains no value
+ * during the whole time. */
+ while ((int_sta & (1 << STMPE811_IRQ_TOUCH_DET)) && (timeout > 0)) {
+ timeout--;
+ stmpe811_reg_read(ts->stm, STMPE811_REG_INT_STA, &int_sta);
+ udelay(100);
+ }
+
+ /* reset the FIFO before we report release event */
+ stmpe811_reg_set_bits(ts->stm, STMPE811_REG_FIFO_STA,
+ STMPE811_FIFO_STA_RESET);
+ stmpe811_reg_clear_bits(ts->stm, STMPE811_REG_FIFO_STA,
+ STMPE811_FIFO_STA_RESET);
+
+ input_report_abs(ts->idev, ABS_PRESSURE, 0);
+ input_sync(ts->idev);
+}
+
+static irqreturn_t stmpe811_ts_handler(int irq, void *data)
+{
+ u8 data_set[4];
+ int x, y, z;
+ struct stmpe811_touch *ts = data;
+
+ /* Cancel polling for release if we have new value available. Wait for
+ * canceling till io operation in the work is finished. */
+ mutex_lock(&ts->stm->io_lock);
+ cancel_delayed_work(&ts->work);
+ mutex_unlock(&ts->stm->io_lock);
+
+ /*
+ * The FIFO sometimes just crashes and stops generating interrupts. This
+ * appears to be a silicon bug. We still have to clearify this with
+ * the manufacture. As a workaround we disable the TSC while we are
+ * collecting data and flush the FIFO after reading
+ */
+ stmpe811_reg_clear_bits(ts->stm, STMPE811_REG_TSC_CTRL,
+ STMPE811_TSC_CTRL_TSC_EN);
+
+ stmpe811_block_read(ts->stm, STMPE811_REG_TSC_DATA_XYZ, 4, data_set);
+
+ x = (data_set[0] << 4) | (data_set[1] >> 4);
+ y = ((data_set[1] & 0xf) << 8) | data_set[2];
+ z = data_set[3];
+
+ input_report_abs(ts->idev, ABS_X, x);
+ input_report_abs(ts->idev, ABS_Y, y);
+ input_report_abs(ts->idev, ABS_PRESSURE, z);
+ input_sync(ts->idev);
+
+ /* flush the FIFO after we have read out our values. */
+ stmpe811_reg_set_bits(ts->stm, STMPE811_REG_FIFO_STA,
+ STMPE811_FIFO_STA_RESET);
+ stmpe811_reg_clear_bits(ts->stm, STMPE811_REG_FIFO_STA,
+ STMPE811_FIFO_STA_RESET);
+
+ /* reenable the tsc */
+ stmpe811_reg_set_bits(ts->stm, STMPE811_REG_TSC_CTRL,
+ STMPE811_TSC_CTRL_TSC_EN);
+
+ /* start polling for touch_det to detect release */
+ schedule_delayed_work(&ts->work, HZ / 50);
+
+ return IRQ_HANDLED;
+}
+
+static int stmpe811_ts_open(struct input_dev *dev)
+{
+ struct stmpe811_touch *ts = input_get_drvdata(dev);
+ int ret = 0;
+
+ ret = stmpe811_reg_set_bits(ts->stm, STMPE811_REG_FIFO_STA,
+ STMPE811_FIFO_STA_RESET);
+ if (ret)
+ goto out;
+
+ ret = stmpe811_reg_clear_bits(ts->stm, STMPE811_REG_FIFO_STA,
+ STMPE811_FIFO_STA_RESET);
+ if (ret)
+ goto out;
+
+ ret = stmpe811_reg_set_bits(ts->stm, STMPE811_REG_TSC_CTRL,
+ STMPE811_TSC_CTRL_TSC_EN);
+ if (ret)
+ goto out;
+
+out:
+ return ret;
+}
+
+static void stmpe811_ts_close(struct input_dev *dev)
+{
+ struct stmpe811_touch *ts = input_get_drvdata(dev);
+
+ stmpe811_reg_clear_bits(ts->stm, STMPE811_REG_TSC_CTRL,
+ STMPE811_TSC_CTRL_TSC_EN);
+}
+
+static int __devinit stmpe811_input_probe(struct platform_device *pdev)
+{
+ struct stmpe811 *stm = dev_get_drvdata(pdev->dev.parent);
+ struct stmpe811_platform_data *pdata = stm->pdata;
+ struct stmpe811_touch *ts;
+ struct input_dev *idev;
+ struct stmpe811_ts_platform_data *ts_pdata = NULL;
+
+ int ret = 0;
+ unsigned int ts_irq;
+
+ ts = kzalloc(sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ goto err_out;
+
+ idev = input_allocate_device();
+ if (!idev)
+ goto err_free_ts;
+
+ platform_set_drvdata(pdev, ts);
+ ts->stm = stm;
+ ts->idev = idev;
+
+ if (pdata)
+ ts_pdata = pdata->ts_pdata;
+
+ if (ts_pdata) {
+ ts->sample_time = ts_pdata->sample_time;
+ ts->mod_12b = ts_pdata->mod_12b;
+ ts->ref_sel = ts_pdata->ref_sel;
+ ts->adc_freq = ts_pdata->adc_freq;
+ ts->ave_ctrl = ts_pdata->ave_ctrl;
+ ts->touch_det_delay = ts_pdata->touch_det_delay;
+ ts->settling = ts_pdata->settling;
+ ts->fraction_z = ts_pdata->fraction_z;
+ }
+
+ INIT_DELAYED_WORK(&ts->work, stmpe811_work);
+
+ ts_irq = stm->irq_base + STMPE811_IRQ_FIFO_TH;
+ ret = request_threaded_irq(ts_irq, NULL, stmpe811_ts_handler,
+ IRQF_ONESHOT, STMPE811_TS_NAME, ts);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to request IRQ %d\n", ts_irq);
+ goto err_free_input;
+ }
+
+ ret = stmpe811_reg_clear_bits(stm, STMPE811_REG_SYS_CTRL2,
+ (STMPE811_SYS_CTRL2_ADC_OFF |
+ STMPE811_SYS_CTRL2_TSC_OFF));
+ if (ret) {
+ dev_err(&pdev->dev, "Could not enable clock for ADC and TS\n");
+ goto err_free_irq;
+ }
+
+ ret = stmpe811_reg_set_bits(stm, STMPE811_REG_ADC_CTRL1,
+ SAMPLE_TIME(ts->sample_time) |
+ MOD_12B(ts->mod_12b) | REF_SEL(ts->ref_sel));
+ if (ret) {
+ dev_err(&pdev->dev, "Could not setup ADC\n");
+ goto err_free_irq;
+ }
+
+ ret = stmpe811_reg_set_bits(stm, STMPE811_REG_ADC_CTRL2,
+ ADC_FREQ(ts->adc_freq));
+ if (ret) {
+ dev_err(&pdev->dev, "Could not setup ADC\n");
+ goto err_free_irq;
+ }
+
+ ret = stmpe811_reg_set_bits(stm, STMPE811_REG_TSC_CFG,
+ AVE_CTRL(ts->ave_ctrl) |
+ DET_DELAY(ts->touch_det_delay) |
+ SETTLING(ts->settling));
+ if (ret) {
+ dev_err(&pdev->dev, "Could not config touch\n");
+ goto err_free_irq;
+ }
+
+ ret = stmpe811_reg_set_bits(stm,
+ STMPE811_REG_TSC_FRACTION_Z,
+ FRACTION_Z(ts->fraction_z));
+ if (ret) {
+ dev_err(&pdev->dev, "Could not config touch\n");
+ goto err_free_irq;
+ }
+
+ /* set FIFO to 1 for single point reading */
+ ret = stmpe811_reg_write(stm, STMPE811_REG_FIFO_TH, 1);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not set FIFO\n");
+ goto err_free_irq;
+ }
+
+ ret = stmpe811_reg_set_bits(stm, STMPE811_REG_TSC_CTRL,
+ STMPE811_TSC_CTRL_OP_MOD_XYZ);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not set mode\n");
+ goto err_free_irq;
+ }
+
+ idev->name = STMPE811_TS_NAME;
+ idev->id.bustype = BUS_I2C;
+ idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ idev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+
+ idev->open = stmpe811_ts_open;
+ idev->close = stmpe811_ts_close;
+
+ input_set_drvdata(idev, ts);
+
+ ret = input_register_device(idev);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not register input device\n");
+ goto err_free_irq;
+ }
+
+ stm->active_flag |= STMPE811_USE_TS;
+
+ input_set_abs_params(idev, ABS_X, 0, XY_MASK, 0, 0);
+ input_set_abs_params(idev, ABS_Y, 0, XY_MASK, 0, 0);
+ input_set_abs_params(idev, ABS_PRESSURE, 0x0, 0xff, 0, 0);
+
+ return ret;
+
+err_free_irq:
+ free_irq(ts_irq, ts);
+err_free_input:
+ input_free_device(idev);
+ platform_set_drvdata(pdev, NULL);
+err_free_ts:
+ kfree(ts);
+err_out:
+ return ret;
+}
+
+static int __devexit stmpe811_ts_remove(struct platform_device *pdev)
+{
+ struct stmpe811_touch *ts = platform_get_drvdata(pdev);
+ unsigned int ts_irq = ts->stm->irq_base + STMPE811_IRQ_FIFO_TH;
+
+ cancel_delayed_work(&ts->work);
+
+ stmpe811_reg_write(ts->stm, STMPE811_REG_FIFO_TH, 0);
+
+ stmpe811_reg_set_bits(ts->stm, STMPE811_REG_SYS_CTRL2,
+ (STMPE811_SYS_CTRL2_ADC_OFF |
+ STMPE811_SYS_CTRL2_ADC_OFF));
+
+ free_irq(ts_irq, ts);
+
+ ts->stm->active_flag &= ~STMPE811_USE_TS;
+ platform_set_drvdata(pdev, NULL);
+
+ input_unregister_device(ts->idev);
+ input_free_device(ts->idev);
+
+ kfree(ts);
+
+ return 0;
+}
+
+static struct platform_driver stmpe811_input_driver = {
+ .driver = {
+ .name = STMPE811_TS_NAME,
+ },
+ .probe = stmpe811_input_probe,
+ .remove = __devexit_p(stmpe811_ts_remove),
+};
+
+static int __init stmpe811_input_init(void)
+{
+ return platform_driver_register(&stmpe811_input_driver);
+}
+
+module_init(stmpe811_input_init);
+
+static void __exit stmpe811_input_exit(void)
+{
+ platform_driver_unregister(&stmpe811_input_driver);
+}
+
+module_exit(stmpe811_input_exit);
+
+MODULE_AUTHOR("Luotao Fu <[email protected]>");
+MODULE_DESCRIPTION("STMPE811 touchscreen driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" STMPE811_TS_NAME);
diff --git a/include/linux/mfd/stmpe811.h b/include/linux/mfd/stmpe811.h
index acd5aaf..b8a75d8 100644
--- a/include/linux/mfd/stmpe811.h
+++ b/include/linux/mfd/stmpe811.h
@@ -96,6 +96,40 @@ struct stmpe811 {
u8 active_flag;
};
+struct stmpe811_ts_platform_data {
+ /* ADC converstion time in number of clock.
+ * 0 -> 36 clocks, 1 -> 44 clocks, 2 -> 56 clocks, 3 -> 64 clocks
+ * 4 -> 80 clocks, 5 -> 96 clocks, 6 -> 144 clocks
+ * recommended is 4 */
+ u8 sample_time;
+ /* ADC Bit mode:
+ * 0 -> 10bit ADC, 1 -> 12bit ADC */
+ u8 mod_12b;
+ /* ADC reference source:
+ * 0 -> internal reference, 1 -> external reference */
+ u8 ref_sel;
+ /* ADC Clock speed:
+ * 0 -> 1.625 MHz, 1 -> 3.25 MHz, 2 || 3 -> 6.5 MHz */
+ u8 adc_freq;
+ /* Sample average control:
+ * 0 -> 1 sample, 1 -> 2 samples, 2 -> 4 samples, 3 -> 8 samples */
+ u8 ave_ctrl;
+ /* Touch detect interrupt delay:
+ * 0 -> 10 us, 1 -> 50 us, 2 -> 100 us, 3 -> 500 us
+ * 4 -> 1 ms, 5 -> 5 ms, 6 -> 10 ms, 7 -> 50 ms
+ * recommended is 3 */
+ u8 touch_det_delay;
+ /* Panel driver settling time
+ * 0 -> 10 us, 1 -> 100 us, 2 -> 500 us, 3 -> 1 ms,
+ * 4 -> 5 ms, 5 -> 10 ms, 6 for 50 ms, 7 -> 100 ms
+ * recommended is 2 */
+ u8 settling;
+ /* Length of the fractional part in z
+ * fraction_z ([0..7]) = Count of the fractional part
+ * recommended is 7 */
+ u8 fraction_z;
+};
+
struct stmpe811_gpio_platform_data {
unsigned int gpio_base;
int (*setup) (struct stmpe811 *stm);
@@ -108,6 +142,7 @@ struct stmpe811_platform_data {
unsigned int int_conf;
int irq_base;
struct stmpe811_gpio_platform_data *gpio_pdata;
+ struct stmpe811_ts_platform_data *ts_pdata;
};
int stmpe811_block_read(struct stmpe811 *stm, u8 reg, uint len, u8 *val);
--
1.7.1
This one adds a driver for STMPE811 GPIO controller. STMPE811 is a
multifunction device. Hence this driver depends on stmpe811_core
driver for core functionalities. The gpio chip is registered to
gpiolib. Interrupts are currently not supported.
Signed-off-by: Luotao Fu <[email protected]>
---
V2 changes:
* include subsystem headers since they are now removed from the mfd
core header file
drivers/gpio/Kconfig | 10 ++
drivers/gpio/Makefile | 1 +
drivers/gpio/stmpe811_gpio.c | 238 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 249 insertions(+), 0 deletions(-)
create mode 100644 drivers/gpio/stmpe811_gpio.c
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 724038d..38307e5 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -249,6 +249,16 @@ config GPIO_ADP5588
To compile this driver as a module, choose M here: the module will be
called adp5588-gpio.
+config GPIO_STMPE811
+ tristate "STMPE811 I2C MFD GPIO expander"
+ depends on I2C
+ depends on MFD_STMPE811
+ help
+ This option enables support for 8 GPIOs found
+ on STMicroelectronics STMPE811 multifunction devices.
+ To compile this driver as a module, choose M here: the module will be
+ called stmpe811_gpio.
+
comment "PCI GPIO expanders:"
config GPIO_CS5535
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 51c3cdd..7b184aa 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -31,3 +31,4 @@ obj-$(CONFIG_GPIO_WM8994) += wm8994-gpio.o
obj-$(CONFIG_GPIO_SCH) += sch_gpio.o
obj-$(CONFIG_GPIO_RDC321X) += rdc321x-gpio.o
obj-$(CONFIG_GPIO_JANZ_TTL) += janz-ttl.o
+obj-$(CONFIG_GPIO_STMPE811) += stmpe811_gpio.o
diff --git a/drivers/gpio/stmpe811_gpio.c b/drivers/gpio/stmpe811_gpio.c
new file mode 100644
index 0000000..507fb5b
--- /dev/null
+++ b/drivers/gpio/stmpe811_gpio.c
@@ -0,0 +1,238 @@
+/*
+ * stmpe811_gpio.c -- gpiolib support for STMicroelectronics STMPE811 chip.
+ *
+ * (c)2010 Luotao Fu <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/workqueue.h>
+
+#include <linux/mfd/stmpe811.h>
+
+#define STMPE811_GPIO_NAME "stmpe811-gpio"
+
+struct stmpe811_gpio {
+ struct stmpe811 *stm;
+ struct gpio_chip gpio;
+};
+
+static inline struct stmpe811_gpio *to_stmpe811_gpio(struct gpio_chip *chip)
+{
+ return container_of(chip, struct stmpe811_gpio, gpio);
+}
+
+static int stmpe811_gpio_direction_in(struct gpio_chip *chip, unsigned offset)
+{
+ struct stmpe811_gpio *stm_gpio = to_stmpe811_gpio(chip);
+
+ return stmpe811_reg_clear_bits(stm_gpio->stm,
+ STMPE811_REG_GPIO_DIR, BIT(offset));
+}
+
+static int stmpe811_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct stmpe811_gpio *stm_gpio = to_stmpe811_gpio(chip);
+ uint8_t gpio_sta;
+ int ret;
+
+ ret = stmpe811_reg_read(stm_gpio->stm,
+ STMPE811_REG_GPIO_MP_STA, &gpio_sta);
+ if (ret)
+ return ret;
+
+ if (gpio_sta & BIT(offset))
+ return 1;
+ else
+ return 0;
+}
+
+static inline int __stmpe811_gpio_set(struct stmpe811 *stm,
+ unsigned offset, int value)
+{
+ int ret;
+
+ if (value)
+ ret = stmpe811_reg_write(stm, STMPE811_REG_GPIO_SET_PIN,
+ BIT(offset));
+ else
+ ret = stmpe811_reg_write(stm, STMPE811_REG_GPIO_CLR_PIN,
+ BIT(offset));
+
+ return ret;
+}
+
+static int stmpe811_gpio_direction_out(struct gpio_chip *chip, unsigned offset,
+ int value)
+{
+ struct stmpe811_gpio *stm_gpio = to_stmpe811_gpio(chip);
+ struct stmpe811 *stm = stm_gpio->stm;
+ int ret;
+
+ ret = stmpe811_reg_set_bits(stm, STMPE811_REG_GPIO_DIR, BIT(offset));
+ if (ret)
+ goto out;
+
+ ret = __stmpe811_gpio_set(stm, offset, value);
+out:
+ return ret;
+}
+
+static void stmpe811_gpio_set(struct gpio_chip *chip, unsigned offset,
+ int value)
+{
+ struct stmpe811_gpio *stm_gpio = to_stmpe811_gpio(chip);
+
+ __stmpe811_gpio_set(stm_gpio->stm, offset, value);
+}
+
+static int stmpe811_gpio_request(struct gpio_chip *chip, unsigned offset)
+{
+ struct stmpe811_gpio *stm_gpio = to_stmpe811_gpio(chip);
+ struct stmpe811 *stm = stm_gpio->stm;
+
+ if (offset > 3 && (stm->active_flag & STMPE811_USE_TS))
+ return 1;
+ else
+ return stmpe811_reg_set_bits(stm, STMPE811_REG_GPIO_AF,
+ BIT(offset));
+}
+
+static struct gpio_chip gpio_chip = {
+ .label = STMPE811_GPIO_NAME,
+ .owner = THIS_MODULE,
+ .direction_input = stmpe811_gpio_direction_in,
+ .get = stmpe811_gpio_get,
+ .direction_output = stmpe811_gpio_direction_out,
+ .set = stmpe811_gpio_set,
+ .request = stmpe811_gpio_request,
+ .can_sleep = 1,
+};
+
+static int __devinit stmpe811_gpio_probe(struct platform_device *pdev)
+{
+ struct stmpe811 *stm = dev_get_drvdata(pdev->dev.parent);
+ struct stmpe811_platform_data *pdata = stm->pdata;
+ struct stmpe811_gpio_platform_data *gpio_pdata = NULL;
+ struct stmpe811_gpio *stm_gpio;
+
+ int ret, tmp;
+
+ stm_gpio = kzalloc(sizeof(*stm_gpio), GFP_KERNEL);
+ if (!stm_gpio)
+ return -ENOMEM;
+
+ stm_gpio->stm = stm;
+ stm_gpio->gpio = gpio_chip;
+ stm_gpio->gpio.ngpio = 8;
+ stm_gpio->gpio.dev = &pdev->dev;
+
+ if (pdata)
+ gpio_pdata = pdata->gpio_pdata;
+
+ if (gpio_pdata && gpio_pdata->gpio_base)
+ stm_gpio->gpio.base = gpio_pdata->gpio_base;
+ else
+ stm_gpio->gpio.base = -1;
+
+ ret = gpiochip_add(&stm_gpio->gpio);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Could not register gpiochip, %d\n", ret);
+ goto err_free_stmgpio;
+ }
+ /* enable clock supply */
+ ret = stmpe811_reg_clear_bits(stm, STMPE811_REG_SYS_CTRL2,
+ STMPE811_SYS_CTRL2_GPIO_OFF);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not enable clock for gpio\n");
+ goto err_free_gpiochip;
+ }
+
+ if (gpio_pdata && gpio_pdata->setup) {
+ ret = gpio_pdata->setup(stm);
+
+ if (ret != 0) {
+ dev_err(&pdev->dev, "setup hook failed, %d\n",
+ ret);
+ goto err_free_gpiochip;
+ }
+ }
+
+ dev_info(&pdev->dev, "registerd gpios %d..%d on a stmpe811\n",
+ stm_gpio->gpio.base,
+ stm_gpio->gpio.base + stm_gpio->gpio.ngpio - 1);
+ platform_set_drvdata(pdev, stm_gpio);
+
+ return ret;
+
+err_free_gpiochip:
+ tmp = gpiochip_remove(&stm_gpio->gpio);
+ if (tmp) {
+ dev_err(&pdev->dev, "Could net remove gpio chip\n");
+ return ret;
+ }
+err_free_stmgpio:
+ kfree(stm_gpio);
+ return ret;
+}
+
+static int __devexit stmpe811_gpio_remove(struct platform_device *pdev)
+{
+ struct stmpe811_gpio *stm_gpio = platform_get_drvdata(pdev);
+ struct stmpe811_platform_data *pdata = stm_gpio->stm->pdata;
+ struct stmpe811_gpio_platform_data *gpio_pdata = NULL;
+ int ret;
+
+ if (pdata)
+ gpio_pdata = pdata->gpio_pdata;
+
+ if (gpio_pdata && gpio_pdata->remove)
+ gpio_pdata->remove(stm_gpio->stm);
+
+ /* disable clock supply */
+ stmpe811_reg_set_bits(stm_gpio->stm, STMPE811_REG_SYS_CTRL2,
+ STMPE811_SYS_CTRL2_GPIO_OFF);
+
+ ret = gpiochip_remove(&stm_gpio->gpio);
+ if (ret == 0)
+ kfree(stm_gpio);
+
+ return ret;
+}
+
+static struct platform_driver stmpe811_gpio_driver = {
+ .driver.name = STMPE811_GPIO_NAME,
+ .driver.owner = THIS_MODULE,
+ .probe = stmpe811_gpio_probe,
+ .remove = __devexit_p(stmpe811_gpio_remove),
+};
+
+static int __init stmpe811_gpio_init(void)
+{
+ return platform_driver_register(&stmpe811_gpio_driver);
+}
+
+subsys_initcall(stmpe811_gpio_init);
+
+static void __exit stmpe811_gpio_exit(void)
+{
+ platform_driver_unregister(&stmpe811_gpio_driver);
+}
+
+module_exit(stmpe811_gpio_exit);
+
+MODULE_AUTHOR("Luotao Fu <[email protected]>");
+MODULE_DESCRIPTION("GPIO interface for STMicroelectronics STMPE811");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" STMPE811_GPIO_NAME);
--
1.7.1
STMPE811 is a multifunction device, which contains a GPIO controller, a
Touchscreen controller, an ADC and a temperature sensor. This patch adds a core
driver for this device. The driver provides core functionalities like accessing
the registers and management of subdevices. The device supports communication
through SPI and I2C interface. Currently we only support I2C.
Signed-off-by: Luotao Fu <[email protected]>
Acked-by: Jonathan Cameron<[email protected]>
---
V2 Changes:
* remove inculding subsysstem headers from the device header file
* use genirq for irq management:
* use request_threaded_irq for the main isr
* register own irq chip and use nested irq callback for subdevice irqs.
* drop i2c client data cleaning up to let the i2c framework do the job.
* fix bogus usage of driver data in i2c device id table
* some formating fixes
drivers/mfd/Kconfig | 8 +
drivers/mfd/Makefile | 1 +
drivers/mfd/stmpe811-core.c | 393 ++++++++++++++++++++++++++++++++++++++++++
include/linux/mfd/stmpe811.h | 119 +++++++++++++
4 files changed, 521 insertions(+), 0 deletions(-)
create mode 100644 drivers/mfd/stmpe811-core.c
create mode 100644 include/linux/mfd/stmpe811.h
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 9da0e50..2c5388b 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -482,6 +482,14 @@ config MFD_JANZ_CMODIO
host many different types of MODULbus daughterboards, including
CAN and GPIO controllers.
+config MFD_STMPE811
+ tristate "STMicroelectronics STMPE811"
+ depends on I2C
+ select MFD_CORE
+ help
+ This is the core driver for the stmpe811 GPIO expander with touchscreen
+ controller, ADC and temperature sensor.
+
endif # MFD_SUPPORT
menu "Multimedia Capabilities Port drivers"
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index fb503e7..6a7ad83 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -71,3 +71,4 @@ obj-$(CONFIG_PMIC_ADP5520) += adp5520.o
obj-$(CONFIG_LPC_SCH) += lpc_sch.o
obj-$(CONFIG_MFD_RDC321X) += rdc321x-southbridge.o
obj-$(CONFIG_MFD_JANZ_CMODIO) += janz-cmodio.o
+obj-$(CONFIG_MFD_STMPE811) += stmpe811-core.o
diff --git a/drivers/mfd/stmpe811-core.c b/drivers/mfd/stmpe811-core.c
new file mode 100644
index 0000000..73840ae
--- /dev/null
+++ b/drivers/mfd/stmpe811-core.c
@@ -0,0 +1,393 @@
+/*
+ * stmpe811-core.c -- core support for STMicroelectronics STMPE811 chip.
+ *
+ * based on pcf50633-core.c by Harald Welte <[email protected]> and
+ * Balaji Rao <[email protected]>
+ *
+ * (c)2010 Luotao Fu <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/workqueue.h>
+#include <linux/irq.h>
+
+#include <linux/mfd/core.h>
+#include <linux/mfd/stmpe811.h>
+
+static struct mfd_cell stmpe811_ts_dev = {
+ .name = "stmpe811-ts",
+};
+
+static struct mfd_cell stmpe811_gpio_dev = {
+ .name = "stmpe811-gpio",
+};
+
+static void stmpe811_mask_work(struct work_struct *work)
+{
+ struct stmpe811 *stm = container_of(work, struct stmpe811, mask_work);
+
+ stmpe811_reg_write(stm, STMPE811_REG_INT_EN, stm->int_en_mask);
+
+ mutex_unlock(&stm->irq_mask_lock);
+}
+
+static void stmpe811_irq_mask(unsigned int irq)
+{
+ struct stmpe811 *stm = get_irq_chip_data(irq);
+ irq -= stm->irq_base;
+
+ mutex_lock(&stm->irq_mask_lock);
+
+ clear_bit(irq, &stm->int_en_mask);
+ schedule_work(&stm->mask_work);
+}
+
+static void stmpe811_irq_unmask(unsigned int irq)
+{
+ struct stmpe811 *stm = get_irq_chip_data(irq);
+ irq -= stm->irq_base;
+
+ mutex_lock(&stm->irq_mask_lock);
+
+ set_bit(irq, &stm->int_en_mask);
+ schedule_work(&stm->mask_work);
+}
+
+static struct irq_chip stmpe811_irq_chip = {
+ .name = "stmpe811",
+ .mask = stmpe811_irq_mask,
+ .unmask = stmpe811_irq_unmask,
+};
+
+static int stmpe811_irq_init(struct stmpe811 *stm)
+{
+ struct stmpe811_platform_data *pdata = stm->pdata;
+ int base = stm->irq_base;
+ int irq;
+
+ for (irq = base; irq < base + STMPE811_NUM_IRQ; irq++) {
+ set_irq_chip_data(irq, stm);
+ set_irq_chip_and_handler(irq, &stmpe811_irq_chip,
+ handle_edge_irq);
+ set_irq_nested_thread(irq, 1);
+#ifdef CONFIG_ARM
+ set_irq_flags(irq, IRQF_VALID);
+#else
+ set_irq_noprobe(irq);
+#endif
+ }
+
+ /* setup platform specific interrupt control flags and enable global
+ * interrupt */
+ stmpe811_reg_write(stm, STMPE811_REG_INT_CTRL,
+ STMPE811_INT_CTRL_GLOBAL_INT | pdata->int_conf);
+
+ return 0;
+}
+
+static void stmpe811_irq_remove(struct stmpe811 *stm)
+{
+ int base = stm->irq_base;
+ int irq;
+
+ for (irq = base; irq < base + STMPE811_NUM_IRQ; irq++) {
+#ifdef CONFIG_ARM
+ set_irq_flags(irq, 0);
+#endif
+ set_irq_chip_and_handler(irq, NULL, NULL);
+ set_irq_chip_data(irq, NULL);
+ }
+}
+
+static irqreturn_t stmpe811_irq(int irq, void *data)
+{
+ struct stmpe811 *stm = data;
+ int ret, bit;
+ u8 int_stat;
+
+ ret = stmpe811_reg_read(stm, STMPE811_REG_INT_STA, &int_stat);
+ if (ret) {
+ dev_err(stm->dev, "Error reading INT registers\n");
+ return IRQ_NONE;
+ }
+
+ dev_dbg(stm->dev, "%s int_stat 0x%02x\n", __func__, int_stat);
+
+ for_each_set_bit(bit, (unsigned long *)&int_stat, STMPE811_NUM_IRQ) {
+ /* mask the interrupt while calling handler */
+ stmpe811_reg_clear_bits(stm, STMPE811_REG_INT_EN, 1 << bit);
+
+ handle_nested_irq(stm->irq_base + bit);
+
+ /* acknowledge the interrupt */
+ stmpe811_reg_set_bits(stm, STMPE811_REG_INT_STA, 1 << bit);
+ /* demask the interrupt */
+ stmpe811_reg_set_bits(stm, STMPE811_REG_INT_EN, 1 << bit);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static inline int __stmpe811_i2c_reads(struct i2c_client *client, int reg,
+ int len, u8 *val)
+{
+ int ret;
+
+ ret = i2c_smbus_read_i2c_block_data(client, reg, len, val);
+ if (ret < 0) {
+ dev_err(&client->dev, "failed reading from 0x%02x\n", reg);
+ return ret;
+ }
+ return 0;
+}
+
+static inline int __stmpe811_i2c_read(struct i2c_client *client,
+ int reg, u8 *val)
+{
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(client, reg);
+ if (ret < 0) {
+ dev_err(&client->dev, "failed reading at 0x%02x\n", reg);
+ return ret;
+ }
+ dev_dbg(&client->dev, "%s: value 0x%x from 0x%x\n", __func__, ret, reg);
+
+ *val = (u8) ret;
+ return 0;
+}
+
+static inline int __stmpe811_i2c_write(struct i2c_client *client,
+ int reg, u8 val)
+{
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, reg, val);
+ if (ret < 0) {
+ dev_err(&client->dev, "failed writing 0x%02x to 0x%02x\n",
+ val, reg);
+ return ret;
+ }
+ return 0;
+}
+
+int stmpe811_block_read(struct stmpe811 *stm, u8 reg, uint len, u8 *val)
+{
+ int ret;
+
+ mutex_lock(&stm->io_lock);
+ ret = __stmpe811_i2c_reads(stm->i2c_client, reg, len, val);
+ mutex_unlock(&stm->io_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe811_block_read);
+
+int stmpe811_reg_read(struct stmpe811 *stm, u8 reg, u8 *val)
+{
+ int ret;
+
+ mutex_lock(&stm->io_lock);
+ ret = __stmpe811_i2c_read(stm->i2c_client, reg, val);
+ mutex_unlock(&stm->io_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe811_reg_read);
+
+int stmpe811_reg_write(struct stmpe811 *stm, u8 reg, u8 val)
+{
+ int ret;
+
+ mutex_lock(&stm->io_lock);
+ ret = __stmpe811_i2c_write(stm->i2c_client, reg, val);
+ mutex_unlock(&stm->io_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe811_reg_write);
+
+int stmpe811_reg_set_bits(struct stmpe811 *stm, u8 reg, u8 val)
+{
+ int ret;
+ u8 tmp;
+
+ mutex_lock(&stm->io_lock);
+ ret = __stmpe811_i2c_read(stm->i2c_client, reg, &tmp);
+ if (ret < 0)
+ goto out;
+
+ tmp |= val;
+ ret = __stmpe811_i2c_write(stm->i2c_client, reg, tmp);
+
+out:
+ mutex_unlock(&stm->io_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe811_reg_set_bits);
+
+int stmpe811_reg_clear_bits(struct stmpe811 *stm, u8 reg, u8 val)
+{
+ int ret;
+ u8 tmp;
+
+ mutex_lock(&stm->io_lock);
+ ret = __stmpe811_i2c_read(stm->i2c_client, reg, &tmp);
+ if (ret < 0)
+ goto out;
+
+ tmp &= ~val;
+ ret = __stmpe811_i2c_write(stm->i2c_client, reg, tmp);
+
+out:
+ mutex_unlock(&stm->io_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe811_reg_clear_bits);
+
+static int __devinit stmpe811_probe(struct i2c_client *client,
+ const struct i2c_device_id *ids)
+{
+ struct stmpe811 *stm;
+ struct stmpe811_platform_data *pdata = client->dev.platform_data;
+ int ret = 0;
+ u8 chip_id[2], chip_ver = 0;
+
+ if (!client->irq) {
+ dev_err(&client->dev, "Missing IRQ\n");
+ return -ENOENT;
+ }
+
+ stm = kzalloc(sizeof(*stm), GFP_KERNEL);
+ if (!stm)
+ return -ENOMEM;
+
+ stm->pdata = pdata;
+
+ mutex_init(&stm->io_lock);
+ mutex_init(&stm->irq_mask_lock);
+
+ i2c_set_clientdata(client, stm);
+ stm->dev = &client->dev;
+ stm->i2c_client = client;
+ stm->irq = client->irq;
+ stm->irq_base = pdata->irq_base;
+
+ INIT_WORK(&stm->mask_work, stmpe811_mask_work);
+
+ ret = stmpe811_block_read(stm, STMPE811_REG_CHIP_ID, 2, chip_id);
+ if ((ret < 0) || (((chip_id[0] << 8) | chip_id[1]) != 0x811)) {
+ dev_err(&client->dev, "could not verify stmpe811 chip id\n");
+ ret = -ENODEV;
+ goto err_free;
+ }
+
+ stmpe811_reg_read(stm, STMPE811_REG_ID_VER, &chip_ver);
+ dev_info(&client->dev, "found stmpe811 chip id 0x%x version 0x%x\n",
+ (chip_id[0] << 8) | chip_id[1], chip_ver);
+
+ /* reset the device */
+ stmpe811_reg_write(stm, STMPE811_REG_SYS_CTRL1,
+ STMPE811_SYS_CTRL1_SOFT_RESET);
+
+ stmpe811_irq_init(stm);
+
+ ret = request_threaded_irq(client->irq, NULL, stmpe811_irq,
+ pdata->irq_flags, "stmpe811", stm);
+ if (ret) {
+ dev_err(&client->dev, "failed to request IRQ: %d\n", ret);
+ goto err_free;
+ }
+
+ if (pdata->flags & STMPE811_USE_TS)
+ ret =
+ mfd_add_devices(&client->dev, -1, &stmpe811_ts_dev, 1, NULL,
+ 0);
+ if (ret != 0)
+ goto err_release_irq;
+
+ if (pdata->flags & STMPE811_USE_GPIO)
+ ret =
+ mfd_add_devices(&client->dev, -1, &stmpe811_gpio_dev, 1,
+ NULL, 0);
+ if (ret != 0)
+ dev_err(&client->dev, "Unable to add mfd subdevices\n");
+
+ return ret;
+
+err_release_irq:
+ free_irq(client->irq, stm);
+err_free:
+ mutex_destroy(&stm->io_lock);
+ mutex_destroy(&stm->irq_mask_lock);
+
+ kfree(stm);
+
+ return ret;
+}
+
+static int __devexit stmpe811_remove(struct i2c_client *client)
+{
+ struct stmpe811 *stm = i2c_get_clientdata(client);
+
+ flush_work(&stm->mask_work);
+ stmpe811_reg_write(stm, STMPE811_REG_SYS_CTRL1,
+ STMPE811_SYS_CTRL1_HIBERNATE);
+
+ stmpe811_irq_remove(stm);
+ free_irq(stm->irq, stm);
+
+ mutex_destroy(&stm->io_lock);
+ mutex_destroy(&stm->irq_mask_lock);
+
+ mfd_remove_devices(&client->dev);
+
+ kfree(stm);
+
+ return 0;
+}
+
+static struct i2c_device_id stmpe811_id_table[] = {
+ {"stmpe811", 0},
+ { /* end of list */ }
+};
+
+static struct i2c_driver stmpe811_driver = {
+ .driver = {
+ .name = "stmpe811",
+ .owner = THIS_MODULE,
+ },
+ .probe = stmpe811_probe,
+ .remove = __devexit_p(stmpe811_remove),
+ .id_table = stmpe811_id_table,
+};
+
+static int __init stmpe811_init(void)
+{
+ return i2c_add_driver(&stmpe811_driver);
+}
+
+subsys_initcall(stmpe811_init);
+
+static void __exit stmpe811_exit(void)
+{
+ i2c_del_driver(&stmpe811_driver);
+}
+
+module_exit(stmpe811_exit);
+
+MODULE_DESCRIPTION
+ ("CORE Driver for STMPE811 Touch screen controller with GPIO expander");
+MODULE_AUTHOR("Luotao Fu <[email protected]>");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/stmpe811.h b/include/linux/mfd/stmpe811.h
new file mode 100644
index 0000000..acd5aaf
--- /dev/null
+++ b/include/linux/mfd/stmpe811.h
@@ -0,0 +1,119 @@
+#ifndef __LINUX_MFD_STMPE811_H
+#define __LINUX_MFD_STMPE811_H
+
+#define STMPE811_REG_CHIP_ID 0x00
+#define STMPE811_REG_ID_VER 0x02
+#define STMPE811_REG_SYS_CTRL1 0x03
+#define STMPE811_REG_SYS_CTRL2 0x04
+#define STMPE811_REG_SPI_CFG 0x08
+#define STMPE811_REG_INT_CTRL 0x09
+#define STMPE811_REG_INT_EN 0x0A
+#define STMPE811_REG_INT_STA 0x0B
+#define STMPE811_REG_GPIO_EN 0x0C
+#define STMPE811_REG_GPIO_INT_STA 0x0D
+#define STMPE811_REG_ADC_INT_EN 0x0E
+#define STMPE811_REG_ADC_INT_STA 0x0F
+#define STMPE811_REG_GPIO_SET_PIN 0x10
+#define STMPE811_REG_GPIO_CLR_PIN 0x11
+#define STMPE811_REG_GPIO_MP_STA 0x12
+#define STMPE811_REG_GPIO_DIR 0x13
+#define STMPE811_REG_GPIO_ED 0x14
+#define STMPE811_REG_GPIO_RE 0x15
+#define STMPE811_REG_GPIO_FE 0x16
+#define STMPE811_REG_GPIO_AF 0x17
+#define STMPE811_REG_ADC_CTRL1 0x20
+#define STMPE811_REG_ADC_CTRL2 0x21
+#define STMPE811_REG_ADC_CAPT 0x22
+#define STMPE811_REG_ADC_DATA_CH0 0x30
+#define STMPE811_REG_ADC_DATA_CH1 0x32
+#define STMPE811_REG_ADC_DATA_CH2 0x34
+#define STMPE811_REG_ADC_DATA_CH3 0x36
+#define STMPE811_REG_ADC_DATA_CH4 0x38
+#define STMPE811_REG_ADC_DATA_CH5 0x3A
+#define STMPE811_REG_ADC_DATA_CH6 0x3C
+#define STMPE811_REG_ADC_DATA_CH7 0x3E
+#define STMPE811_REG_TSC_CTRL 0x40
+#define STMPE811_REG_TSC_CFG 0x41
+#define STMPE811_REG_WDW_TR_X 0x42
+#define STMPE811_REG_WDW_TR_Y 0x44
+#define STMPE811_REG_WDW_BL_X 0x46
+#define STMPE811_REG_WDW_BL_Y 0x48
+#define STMPE811_REG_FIFO_TH 0x4A
+#define STMPE811_REG_FIFO_STA 0x4B
+#define STMPE811_REG_FIFO_SIZE 0x4C
+#define STMPE811_REG_TSC_DATA_X 0x4D
+#define STMPE811_REG_TSC_DATA_Y 0x4F
+#define STMPE811_REG_TSC_DATA_Z 0x51
+#define STMPE811_REG_TSC_DATA_XYZ 0x52
+#define STMPE811_REG_TSC_FRACTION_Z 0x56
+#define STMPE811_REG_TSC_DATA 0x57
+#define STMPE811_REG_TSC_DATA_SINGLE 0xD7
+#define STMPE811_REG_TSC_I_DRIVE 0x58
+#define STMPE811_REG_TSC_SHIELD 0x59
+#define STMPE811_REG_TEMP_CTRL 0x60
+
+#define STMPE811_SYS_CTRL1_HIBERNATE (1<<0)
+#define STMPE811_SYS_CTRL1_SOFT_RESET (1<<1)
+
+#define STMPE811_SYS_CTRL2_ADC_OFF (1<<0)
+#define STMPE811_SYS_CTRL2_TSC_OFF (1<<1)
+#define STMPE811_SYS_CTRL2_GPIO_OFF (1<<2)
+#define STMPE811_SYS_CTRL2_TS_OFF (1<<3)
+
+#define STMPE811_INT_CTRL_GLOBAL_INT (1<<0)
+#define STMPE811_INT_CTRL_INT_TYPE (1<<1)
+#define STMPE811_INT_CTRL_INT_POLARITY (1<<2)
+
+#define STMPE811_FIFO_STA_OFLOW (1<<7)
+#define STMPE811_FIFO_STA_FULL (1<<6)
+#define STMPE811_FIFO_STA_EMPTY (1<<5)
+#define STMPE811_FIFO_STA_TH_TRIG (1<<4)
+#define STMPE811_FIFO_STA_RESET (1<<0)
+
+#define STMPE811_USE_TS (1<<0)
+#define STMPE811_USE_GPIO (1<<1)
+
+#define STMPE811_IRQ_TOUCH_DET 0
+#define STMPE811_IRQ_FIFO_TH 1
+#define STMPE811_IRQ_FIFO_OFLOW 2
+#define STMPE811_IRQ_FIFO_FULL 3
+#define STMPE811_IRQ_FIFO_EMPTY 4
+#define STMPE811_IRQ_TEMP_SENS 5
+#define STMPE811_IRQ_ADC 6
+#define STMPE811_IRQ_GPIO 7
+#define STMPE811_NUM_IRQ 8
+
+struct stmpe811 {
+ struct device *dev;
+ struct i2c_client *i2c_client;
+ struct work_struct mask_work;
+ struct stmpe811_platform_data *pdata;
+ struct mutex io_lock;
+ struct mutex irq_mask_lock;
+ unsigned int irq;
+ int irq_base;
+ unsigned long int_en_mask;
+ u8 active_flag;
+};
+
+struct stmpe811_gpio_platform_data {
+ unsigned int gpio_base;
+ int (*setup) (struct stmpe811 *stm);
+ void (*remove) (struct stmpe811 *stm);
+};
+
+struct stmpe811_platform_data {
+ unsigned int flags;
+ unsigned int irq_flags;
+ unsigned int int_conf;
+ int irq_base;
+ struct stmpe811_gpio_platform_data *gpio_pdata;
+};
+
+int stmpe811_block_read(struct stmpe811 *stm, u8 reg, uint len, u8 *val);
+int stmpe811_reg_read(struct stmpe811 *pcf, u8 reg, u8 *val);
+int stmpe811_reg_write(struct stmpe811 *stm, u8 reg, u8 val);
+int stmpe811_reg_set_bits(struct stmpe811 *stm, u8 reg, u8 val);
+int stmpe811_reg_clear_bits(struct stmpe811 *stm, u8 reg, u8 val);
+
+#endif
--
1.7.1
On Mon, Jun 14, 2010 at 12:32:36PM +0200, Luotao Fu wrote:
> +static void stmpe811_mask_work(struct work_struct *work)
> +{
> + struct stmpe811 *stm = container_of(work, struct stmpe811, mask_work);
> +
> + stmpe811_reg_write(stm, STMPE811_REG_INT_EN, stm->int_en_mask);
> +
> + mutex_unlock(&stm->irq_mask_lock);
> +}
Why are you doing this in a workqueue? You shouldn't need to do this -
you should implement the bus_lock() and bus_unlock() callbacks instead.
> + ret = request_threaded_irq(client->irq, NULL, stmpe811_irq,
> + pdata->irq_flags, "stmpe811", stm);
> + if (ret) {
> + dev_err(&client->dev, "failed to request IRQ: %d\n", ret);
> + goto err_free;
> + }
I suspect you should be unconditionally putting IRQF_ONESHOT here since
the threaded IRQ requires it.
On Mon, Jun 14, 2010 at 12:32:37PM +0200, Luotao Fu wrote:
> +static struct gpio_chip gpio_chip = {
> + .label = STMPE811_GPIO_NAME,
> + .owner = THIS_MODULE,
> + .direction_input = stmpe811_gpio_direction_in,
> + .get = stmpe811_gpio_get,
> + .direction_output = stmpe811_gpio_direction_out,
> + .set = stmpe811_gpio_set,
> + .request = stmpe811_gpio_request,
> + .can_sleep = 1,
> +};
Assuming you're able to do interrupts on the GPIOs you should now be
able to implement to_irq() as well.
Hi Mark,
thanks for the review.
On Mon, Jun 14, 2010 at 12:50:32PM +0100, Mark Brown wrote:
> On Mon, Jun 14, 2010 at 12:32:36PM +0200, Luotao Fu wrote:
>
> > +static void stmpe811_mask_work(struct work_struct *work)
> > +{
> > + struct stmpe811 *stm = container_of(work, struct stmpe811, mask_work);
> > +
> > + stmpe811_reg_write(stm, STMPE811_REG_INT_EN, stm->int_en_mask);
> > +
> > + mutex_unlock(&stm->irq_mask_lock);
> > +}
>
> Why are you doing this in a workqueue? You shouldn't need to do this -
> you should implement the bus_lock() and bus_unlock() callbacks instead.
Right, will replace the stuff with bus_lock/unlock
>
> > + ret = request_threaded_irq(client->irq, NULL, stmpe811_irq,
> > + pdata->irq_flags, "stmpe811", stm);
> > + if (ret) {
> > + dev_err(&client->dev, "failed to request IRQ: %d\n", ret);
> > + goto err_free;
> > + }
>
> I suspect you should be unconditionally putting IRQF_ONESHOT here since
> the threaded IRQ requires it.
oh, oversaw this one, will fix.
Thanks
regards
Luotao Fu
--
Pengutronix e.K. | Dipl.-Ing. Luotao Fu |
Industrial Linux Solutions | http://www.pengutronix.de/ |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
Hi Mark,
thaks for the review.
On Mon, Jun 14, 2010 at 12:51:23PM +0100, Mark Brown wrote:
> On Mon, Jun 14, 2010 at 12:32:37PM +0200, Luotao Fu wrote:
>
> > +static struct gpio_chip gpio_chip = {
> > + .label = STMPE811_GPIO_NAME,
> > + .owner = THIS_MODULE,
> > + .direction_input = stmpe811_gpio_direction_in,
> > + .get = stmpe811_gpio_get,
> > + .direction_output = stmpe811_gpio_direction_out,
> > + .set = stmpe811_gpio_set,
> > + .request = stmpe811_gpio_request,
> > + .can_sleep = 1,
> > +};
>
> Assuming you're able to do interrupts on the GPIOs you should now be
> able to implement to_irq() as well.
The chip can indeed generate irqs on the GPIO line. I, however, kept out
the irq stuff for now because i want to get the core functionalies done
for this turn. I'd like to add the irq stuff with a separate patch later.
Thanks
cheers
Luotao Fu
--
Pengutronix e.K. | Dipl.-Ing. Luotao Fu |
Industrial Linux Solutions | http://www.pengutronix.de/ |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
On Mon, Jun 14, 2010 at 02:09:43PM +0200, Luotao Fu wrote:
> On Mon, Jun 14, 2010 at 12:51:23PM +0100, Mark Brown wrote:
> > Assuming you're able to do interrupts on the GPIOs you should now be
> > able to implement to_irq() as well.
> The chip can indeed generate irqs on the GPIO line. I, however, kept out
> the irq stuff for now because i want to get the core functionalies done
> for this turn. I'd like to add the irq stuff with a separate patch later.
It should be a trivial function - it just does a mapping from a GPIO
number to an IRQ number.
Hi Mark,
On Mon, Jun 14, 2010 at 01:13:49PM +0100, Mark Brown wrote:
> On Mon, Jun 14, 2010 at 02:09:43PM +0200, Luotao Fu wrote:
> > On Mon, Jun 14, 2010 at 12:51:23PM +0100, Mark Brown wrote:
>
> > > Assuming you're able to do interrupts on the GPIOs you should now be
> > > able to implement to_irq() as well.
>
> > The chip can indeed generate irqs on the GPIO line. I, however, kept out
> > the irq stuff for now because i want to get the core functionalies done
> > for this turn. I'd like to add the irq stuff with a separate patch later.
>
> It should be a trivial function - it just does a mapping from a GPIO
> number to an IRQ number.
right, to_irq is simple. However, there're some more stuff necessary for
gpio irq on this chip, since it has separate registers for GPIO
interrupt control and status. A further gpio chip might be needed
needed to get the stuff done. Hence I'd like to push out the most
importatn core funcationalities out for the first turn before I take a
deeper look into other stuffs.
thanks
cheers
Luotao Fu
--
Pengutronix e.K. | Dipl.-Ing. Luotao Fu |
Industrial Linux Solutions | http://www.pengutronix.de/ |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
On Mon, Jun 14, 2010 at 02:28:53PM +0200, Luotao Fu wrote:
> right, to_irq is simple. However, there're some more stuff necessary for
> gpio irq on this chip, since it has separate registers for GPIO
> interrupt control and status. A further gpio chip might be needed
> needed to get the stuff done. Hence I'd like to push out the most
> importatn core funcationalities out for the first turn before I take a
> deeper look into other stuffs.
Ah, right - that makes sense. I'd have expected it to have just flopped
out of haiving working IRQ support in the core.
Hi,
in the following is the V3 patch serie for the stmpe811 chip. Main changes
are:
* reformat platform data comments to kernel-doc style
* add bus_lock/unlock callbacks to the core irq_chip.
* move declaration of gpio platform data into the gpio subdevice patch.
* new irq flags in the core platform data.
etc.
Please see the individual patch header for more details.
thanks
cheers
Luotao Fu
This one adds a driver for STMPE811 GPIO controller. STMPE811 is a
multifunction device. Hence this driver depends on stmpe811_core
driver for core functionalities. The gpio chip is registered to
gpiolib. Interrupts are currently not supported.
Signed-off-by: Luotao Fu <[email protected]>
---
V2 changes:
* include subsystem headers since they are now removed from the mfd
core header file
V3 Changes:
* reformated platform data comments to kernel-doc style
* move platform data stuff from core patch to herein.
drivers/gpio/Kconfig | 10 ++
drivers/gpio/Makefile | 1 +
drivers/gpio/stmpe811_gpio.c | 238 ++++++++++++++++++++++++++++++++++++++++++
include/linux/mfd/stmpe811.h | 16 +++
4 files changed, 265 insertions(+), 0 deletions(-)
create mode 100644 drivers/gpio/stmpe811_gpio.c
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 724038d..38307e5 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -249,6 +249,16 @@ config GPIO_ADP5588
To compile this driver as a module, choose M here: the module will be
called adp5588-gpio.
+config GPIO_STMPE811
+ tristate "STMPE811 I2C MFD GPIO expander"
+ depends on I2C
+ depends on MFD_STMPE811
+ help
+ This option enables support for 8 GPIOs found
+ on STMicroelectronics STMPE811 multifunction devices.
+ To compile this driver as a module, choose M here: the module will be
+ called stmpe811_gpio.
+
comment "PCI GPIO expanders:"
config GPIO_CS5535
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 51c3cdd..7b184aa 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -31,3 +31,4 @@ obj-$(CONFIG_GPIO_WM8994) += wm8994-gpio.o
obj-$(CONFIG_GPIO_SCH) += sch_gpio.o
obj-$(CONFIG_GPIO_RDC321X) += rdc321x-gpio.o
obj-$(CONFIG_GPIO_JANZ_TTL) += janz-ttl.o
+obj-$(CONFIG_GPIO_STMPE811) += stmpe811_gpio.o
diff --git a/drivers/gpio/stmpe811_gpio.c b/drivers/gpio/stmpe811_gpio.c
new file mode 100644
index 0000000..507fb5b
--- /dev/null
+++ b/drivers/gpio/stmpe811_gpio.c
@@ -0,0 +1,238 @@
+/*
+ * stmpe811_gpio.c -- gpiolib support for STMicroelectronics STMPE811 chip.
+ *
+ * (c)2010 Luotao Fu <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/workqueue.h>
+
+#include <linux/mfd/stmpe811.h>
+
+#define STMPE811_GPIO_NAME "stmpe811-gpio"
+
+struct stmpe811_gpio {
+ struct stmpe811 *stm;
+ struct gpio_chip gpio;
+};
+
+static inline struct stmpe811_gpio *to_stmpe811_gpio(struct gpio_chip *chip)
+{
+ return container_of(chip, struct stmpe811_gpio, gpio);
+}
+
+static int stmpe811_gpio_direction_in(struct gpio_chip *chip, unsigned offset)
+{
+ struct stmpe811_gpio *stm_gpio = to_stmpe811_gpio(chip);
+
+ return stmpe811_reg_clear_bits(stm_gpio->stm,
+ STMPE811_REG_GPIO_DIR, BIT(offset));
+}
+
+static int stmpe811_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct stmpe811_gpio *stm_gpio = to_stmpe811_gpio(chip);
+ uint8_t gpio_sta;
+ int ret;
+
+ ret = stmpe811_reg_read(stm_gpio->stm,
+ STMPE811_REG_GPIO_MP_STA, &gpio_sta);
+ if (ret)
+ return ret;
+
+ if (gpio_sta & BIT(offset))
+ return 1;
+ else
+ return 0;
+}
+
+static inline int __stmpe811_gpio_set(struct stmpe811 *stm,
+ unsigned offset, int value)
+{
+ int ret;
+
+ if (value)
+ ret = stmpe811_reg_write(stm, STMPE811_REG_GPIO_SET_PIN,
+ BIT(offset));
+ else
+ ret = stmpe811_reg_write(stm, STMPE811_REG_GPIO_CLR_PIN,
+ BIT(offset));
+
+ return ret;
+}
+
+static int stmpe811_gpio_direction_out(struct gpio_chip *chip, unsigned offset,
+ int value)
+{
+ struct stmpe811_gpio *stm_gpio = to_stmpe811_gpio(chip);
+ struct stmpe811 *stm = stm_gpio->stm;
+ int ret;
+
+ ret = stmpe811_reg_set_bits(stm, STMPE811_REG_GPIO_DIR, BIT(offset));
+ if (ret)
+ goto out;
+
+ ret = __stmpe811_gpio_set(stm, offset, value);
+out:
+ return ret;
+}
+
+static void stmpe811_gpio_set(struct gpio_chip *chip, unsigned offset,
+ int value)
+{
+ struct stmpe811_gpio *stm_gpio = to_stmpe811_gpio(chip);
+
+ __stmpe811_gpio_set(stm_gpio->stm, offset, value);
+}
+
+static int stmpe811_gpio_request(struct gpio_chip *chip, unsigned offset)
+{
+ struct stmpe811_gpio *stm_gpio = to_stmpe811_gpio(chip);
+ struct stmpe811 *stm = stm_gpio->stm;
+
+ if (offset > 3 && (stm->active_flag & STMPE811_USE_TS))
+ return 1;
+ else
+ return stmpe811_reg_set_bits(stm, STMPE811_REG_GPIO_AF,
+ BIT(offset));
+}
+
+static struct gpio_chip gpio_chip = {
+ .label = STMPE811_GPIO_NAME,
+ .owner = THIS_MODULE,
+ .direction_input = stmpe811_gpio_direction_in,
+ .get = stmpe811_gpio_get,
+ .direction_output = stmpe811_gpio_direction_out,
+ .set = stmpe811_gpio_set,
+ .request = stmpe811_gpio_request,
+ .can_sleep = 1,
+};
+
+static int __devinit stmpe811_gpio_probe(struct platform_device *pdev)
+{
+ struct stmpe811 *stm = dev_get_drvdata(pdev->dev.parent);
+ struct stmpe811_platform_data *pdata = stm->pdata;
+ struct stmpe811_gpio_platform_data *gpio_pdata = NULL;
+ struct stmpe811_gpio *stm_gpio;
+
+ int ret, tmp;
+
+ stm_gpio = kzalloc(sizeof(*stm_gpio), GFP_KERNEL);
+ if (!stm_gpio)
+ return -ENOMEM;
+
+ stm_gpio->stm = stm;
+ stm_gpio->gpio = gpio_chip;
+ stm_gpio->gpio.ngpio = 8;
+ stm_gpio->gpio.dev = &pdev->dev;
+
+ if (pdata)
+ gpio_pdata = pdata->gpio_pdata;
+
+ if (gpio_pdata && gpio_pdata->gpio_base)
+ stm_gpio->gpio.base = gpio_pdata->gpio_base;
+ else
+ stm_gpio->gpio.base = -1;
+
+ ret = gpiochip_add(&stm_gpio->gpio);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Could not register gpiochip, %d\n", ret);
+ goto err_free_stmgpio;
+ }
+ /* enable clock supply */
+ ret = stmpe811_reg_clear_bits(stm, STMPE811_REG_SYS_CTRL2,
+ STMPE811_SYS_CTRL2_GPIO_OFF);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not enable clock for gpio\n");
+ goto err_free_gpiochip;
+ }
+
+ if (gpio_pdata && gpio_pdata->setup) {
+ ret = gpio_pdata->setup(stm);
+
+ if (ret != 0) {
+ dev_err(&pdev->dev, "setup hook failed, %d\n",
+ ret);
+ goto err_free_gpiochip;
+ }
+ }
+
+ dev_info(&pdev->dev, "registerd gpios %d..%d on a stmpe811\n",
+ stm_gpio->gpio.base,
+ stm_gpio->gpio.base + stm_gpio->gpio.ngpio - 1);
+ platform_set_drvdata(pdev, stm_gpio);
+
+ return ret;
+
+err_free_gpiochip:
+ tmp = gpiochip_remove(&stm_gpio->gpio);
+ if (tmp) {
+ dev_err(&pdev->dev, "Could net remove gpio chip\n");
+ return ret;
+ }
+err_free_stmgpio:
+ kfree(stm_gpio);
+ return ret;
+}
+
+static int __devexit stmpe811_gpio_remove(struct platform_device *pdev)
+{
+ struct stmpe811_gpio *stm_gpio = platform_get_drvdata(pdev);
+ struct stmpe811_platform_data *pdata = stm_gpio->stm->pdata;
+ struct stmpe811_gpio_platform_data *gpio_pdata = NULL;
+ int ret;
+
+ if (pdata)
+ gpio_pdata = pdata->gpio_pdata;
+
+ if (gpio_pdata && gpio_pdata->remove)
+ gpio_pdata->remove(stm_gpio->stm);
+
+ /* disable clock supply */
+ stmpe811_reg_set_bits(stm_gpio->stm, STMPE811_REG_SYS_CTRL2,
+ STMPE811_SYS_CTRL2_GPIO_OFF);
+
+ ret = gpiochip_remove(&stm_gpio->gpio);
+ if (ret == 0)
+ kfree(stm_gpio);
+
+ return ret;
+}
+
+static struct platform_driver stmpe811_gpio_driver = {
+ .driver.name = STMPE811_GPIO_NAME,
+ .driver.owner = THIS_MODULE,
+ .probe = stmpe811_gpio_probe,
+ .remove = __devexit_p(stmpe811_gpio_remove),
+};
+
+static int __init stmpe811_gpio_init(void)
+{
+ return platform_driver_register(&stmpe811_gpio_driver);
+}
+
+subsys_initcall(stmpe811_gpio_init);
+
+static void __exit stmpe811_gpio_exit(void)
+{
+ platform_driver_unregister(&stmpe811_gpio_driver);
+}
+
+module_exit(stmpe811_gpio_exit);
+
+MODULE_AUTHOR("Luotao Fu <[email protected]>");
+MODULE_DESCRIPTION("GPIO interface for STMicroelectronics STMPE811");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" STMPE811_GPIO_NAME);
diff --git a/include/linux/mfd/stmpe811.h b/include/linux/mfd/stmpe811.h
index 8e2b55e..736b4eb 100644
--- a/include/linux/mfd/stmpe811.h
+++ b/include/linux/mfd/stmpe811.h
@@ -94,12 +94,27 @@ struct stmpe811 {
};
/**
+ * struct stmpe811_gpio_platform_data - stmpe811 gpio controller platform data
+ *
+ * @gpio_base: number of the chip's first GPIO
+ * @setup: optional callback issued once the GPIOs are valid
+ * @remove: optional callback issued before the GPIOs are invalidated
+ *
+ * */
+struct stmpe811_gpio_platform_data {
+ unsigned int gpio_base;
+ int (*setup) (struct stmpe811 *stm);
+ void (*remove) (struct stmpe811 *stm);
+};
+
+/**
* struct stmpe811_platform_data - stmpe811 core platform data
*
* @flags: define which subdevices should be supported
* @irq_high: IRQ is active high.
* @irq_rev_pol: IRQ line is connected with reversed polarity
* @irq_base: board dependt irq number of the first irq for the irq chip
+ * @gpio_pdata: pointer to gpio controller platform data
* registered by the core.
*
* */
@@ -110,6 +125,7 @@ struct stmpe811_platform_data {
unsigned int irq_high;
unsigned int irq_rev_pol;
int irq_base;
+ struct stmpe811_gpio_platform_data *gpio_pdata;
};
int stmpe811_block_read(struct stmpe811 *stm, u8 reg, uint len, u8 *val);
--
1.7.1
This one adds a driver for STMPE811 4-wire resistive touchscreen
controller. STMPE811 is a multifunction device. Hence this driver
depends on stmpe811_core driver for core functionalities.
Signed-off-by: Luotao Fu <[email protected]>
---
V2 Changes:
* include subsystem headers since they are now remove from the mfd
core header file
* use genirq to register threaded isr. The stmpe811 own irq callbacks
are dropped in the core driver.
* use STMPE811_TS_NAME for name all over the place
* wait for completion of core IO operation before cancelling polling of
release event.
* switch to platform data for configuration parameters.
* add FIFO reset to open callback, also reset FIFO before reporting
release.
V3 Changes:
* reformated platform data comments to kernel-doc style
drivers/input/touchscreen/Kconfig | 10 +
drivers/input/touchscreen/Makefile | 1 +
drivers/input/touchscreen/stmpe811_ts.c | 361 +++++++++++++++++++++++++++++++
include/linux/mfd/stmpe811.h | 40 ++++
4 files changed, 412 insertions(+), 0 deletions(-)
create mode 100644 drivers/input/touchscreen/stmpe811_ts.c
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 3b9d5e2..059b82b 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -603,4 +603,14 @@ config TOUCHSCREEN_TPS6507X
To compile this driver as a module, choose M here: the
module will be called tps6507x_ts.
+config TOUCHSCREEN_STMPE811
+ tristate "STMicroelectronics STMPE811 touchscreen"
+ depends on MFD_STMPE811
+ help
+ Say Y here if you want support for STMicroelectronics
+ STMPE811 based touchscreen controller.
+
+ To compile this driver as a module, choose M here: the
+ module will be called stmpe811_ts.
+
endif
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 497964a..9da8948 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -47,3 +47,4 @@ obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE) += mainstone-wm97xx.o
obj-$(CONFIG_TOUCHSCREEN_WM97XX_ZYLONITE) += zylonite-wm97xx.o
obj-$(CONFIG_TOUCHSCREEN_W90X900) += w90p910_ts.o
obj-$(CONFIG_TOUCHSCREEN_TPS6507X) += tps6507x-ts.o
+obj-$(CONFIG_TOUCHSCREEN_STMPE811) += stmpe811_ts.o
diff --git a/drivers/input/touchscreen/stmpe811_ts.c b/drivers/input/touchscreen/stmpe811_ts.c
new file mode 100644
index 0000000..ee6a547
--- /dev/null
+++ b/drivers/input/touchscreen/stmpe811_ts.c
@@ -0,0 +1,361 @@
+/* STMicroelectronics STMPE811 Touchscreen Driver
+ *
+ * (C) 2010 Luotao Fu <[email protected]>
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/workqueue.h>
+
+#include <linux/mfd/stmpe811.h>
+
+#define STMPE811_TSC_CTRL_OP_MOD_XYZ (0<<1)
+#define STMPE811_TSC_CTRL_OP_MOD_XY (1<<1)
+#define STMPE811_TSC_CTRL_OP_MOD_X (2<<1)
+#define STMPE811_TSC_CTRL_OP_MOD_Y (3<<1)
+#define STMPE811_TSC_CTRL_OP_MOD_Z (4<<1)
+
+#define STMPE811_TSC_CTRL_TSC_STA (1<<7)
+#define STMPE811_TSC_CTRL_TSC_EN (1<<0)
+
+#define SAMPLE_TIME(x) ((x & 0xf) << 4)
+#define MOD_12B(x) ((x & 0x1) << 3)
+#define REF_SEL(x) ((x & 0x1) << 1)
+#define ADC_FREQ(x) (x & 0x3)
+#define AVE_CTRL(x) ((x & 0x3) << 6)
+#define DET_DELAY(x) ((x & 0x7) << 3)
+#define SETTLING(x) ((x & 0x7))
+#define FRACTION_Z(x) ((x & 0x7))
+
+#define STMPE811_TS_NAME "stmpe811-ts"
+#define XY_MASK 0xfff
+
+struct stmpe811_touch {
+ struct stmpe811 *stm;
+ struct input_dev *idev;
+ struct delayed_work work;
+ u8 sample_time;
+ u8 mod_12b;
+ u8 ref_sel;
+ u8 adc_freq;
+ u8 ave_ctrl;
+ u8 touch_det_delay;
+ u8 settling;
+ u8 fraction_z;
+};
+
+static void stmpe811_work(struct work_struct *work)
+{
+ u8 int_sta;
+ u32 timeout = 40;
+
+ struct stmpe811_touch *ts =
+ container_of(work, struct stmpe811_touch, work.work);
+
+ stmpe811_reg_read(ts->stm, STMPE811_REG_INT_STA, &int_sta);
+
+ /* touch_det sometimes get desasserted or just get stuck. This appears
+ * to be a silicon bug, We still have to clearify this with the
+ * manufacture. As a workaround We release the key anyway if the
+ * touch_det keeps coming in after 4ms, while the FIFO contains no value
+ * during the whole time. */
+ while ((int_sta & (1 << STMPE811_IRQ_TOUCH_DET)) && (timeout > 0)) {
+ timeout--;
+ stmpe811_reg_read(ts->stm, STMPE811_REG_INT_STA, &int_sta);
+ udelay(100);
+ }
+
+ /* reset the FIFO before we report release event */
+ stmpe811_reg_set_bits(ts->stm, STMPE811_REG_FIFO_STA,
+ STMPE811_FIFO_STA_RESET);
+ stmpe811_reg_clear_bits(ts->stm, STMPE811_REG_FIFO_STA,
+ STMPE811_FIFO_STA_RESET);
+
+ input_report_abs(ts->idev, ABS_PRESSURE, 0);
+ input_sync(ts->idev);
+}
+
+static irqreturn_t stmpe811_ts_handler(int irq, void *data)
+{
+ u8 data_set[4];
+ int x, y, z;
+ struct stmpe811_touch *ts = data;
+
+ /* Cancel polling for release if we have new value available. Wait for
+ * canceling till io operation in the work is finished. */
+ mutex_lock(&ts->stm->io_lock);
+ cancel_delayed_work(&ts->work);
+ mutex_unlock(&ts->stm->io_lock);
+
+ /*
+ * The FIFO sometimes just crashes and stops generating interrupts. This
+ * appears to be a silicon bug. We still have to clearify this with
+ * the manufacture. As a workaround we disable the TSC while we are
+ * collecting data and flush the FIFO after reading
+ */
+ stmpe811_reg_clear_bits(ts->stm, STMPE811_REG_TSC_CTRL,
+ STMPE811_TSC_CTRL_TSC_EN);
+
+ stmpe811_block_read(ts->stm, STMPE811_REG_TSC_DATA_XYZ, 4, data_set);
+
+ x = (data_set[0] << 4) | (data_set[1] >> 4);
+ y = ((data_set[1] & 0xf) << 8) | data_set[2];
+ z = data_set[3];
+
+ input_report_abs(ts->idev, ABS_X, x);
+ input_report_abs(ts->idev, ABS_Y, y);
+ input_report_abs(ts->idev, ABS_PRESSURE, z);
+ input_sync(ts->idev);
+
+ /* flush the FIFO after we have read out our values. */
+ stmpe811_reg_set_bits(ts->stm, STMPE811_REG_FIFO_STA,
+ STMPE811_FIFO_STA_RESET);
+ stmpe811_reg_clear_bits(ts->stm, STMPE811_REG_FIFO_STA,
+ STMPE811_FIFO_STA_RESET);
+
+ /* reenable the tsc */
+ stmpe811_reg_set_bits(ts->stm, STMPE811_REG_TSC_CTRL,
+ STMPE811_TSC_CTRL_TSC_EN);
+
+ /* start polling for touch_det to detect release */
+ schedule_delayed_work(&ts->work, HZ / 50);
+
+ return IRQ_HANDLED;
+}
+
+static int stmpe811_ts_open(struct input_dev *dev)
+{
+ struct stmpe811_touch *ts = input_get_drvdata(dev);
+ int ret = 0;
+
+ ret = stmpe811_reg_set_bits(ts->stm, STMPE811_REG_FIFO_STA,
+ STMPE811_FIFO_STA_RESET);
+ if (ret)
+ goto out;
+
+ ret = stmpe811_reg_clear_bits(ts->stm, STMPE811_REG_FIFO_STA,
+ STMPE811_FIFO_STA_RESET);
+ if (ret)
+ goto out;
+
+ ret = stmpe811_reg_set_bits(ts->stm, STMPE811_REG_TSC_CTRL,
+ STMPE811_TSC_CTRL_TSC_EN);
+ if (ret)
+ goto out;
+
+out:
+ return ret;
+}
+
+static void stmpe811_ts_close(struct input_dev *dev)
+{
+ struct stmpe811_touch *ts = input_get_drvdata(dev);
+
+ stmpe811_reg_clear_bits(ts->stm, STMPE811_REG_TSC_CTRL,
+ STMPE811_TSC_CTRL_TSC_EN);
+}
+
+static int __devinit stmpe811_input_probe(struct platform_device *pdev)
+{
+ struct stmpe811 *stm = dev_get_drvdata(pdev->dev.parent);
+ struct stmpe811_platform_data *pdata = stm->pdata;
+ struct stmpe811_touch *ts;
+ struct input_dev *idev;
+ struct stmpe811_ts_platform_data *ts_pdata = NULL;
+
+ int ret = 0;
+ unsigned int ts_irq;
+
+ ts = kzalloc(sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ goto err_out;
+
+ idev = input_allocate_device();
+ if (!idev)
+ goto err_free_ts;
+
+ platform_set_drvdata(pdev, ts);
+ ts->stm = stm;
+ ts->idev = idev;
+
+ if (pdata)
+ ts_pdata = pdata->ts_pdata;
+
+ if (ts_pdata) {
+ ts->sample_time = ts_pdata->sample_time;
+ ts->mod_12b = ts_pdata->mod_12b;
+ ts->ref_sel = ts_pdata->ref_sel;
+ ts->adc_freq = ts_pdata->adc_freq;
+ ts->ave_ctrl = ts_pdata->ave_ctrl;
+ ts->touch_det_delay = ts_pdata->touch_det_delay;
+ ts->settling = ts_pdata->settling;
+ ts->fraction_z = ts_pdata->fraction_z;
+ }
+
+ INIT_DELAYED_WORK(&ts->work, stmpe811_work);
+
+ ts_irq = stm->irq_base + STMPE811_IRQ_FIFO_TH;
+ ret = request_threaded_irq(ts_irq, NULL, stmpe811_ts_handler,
+ IRQF_ONESHOT, STMPE811_TS_NAME, ts);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to request IRQ %d\n", ts_irq);
+ goto err_free_input;
+ }
+
+ ret = stmpe811_reg_clear_bits(stm, STMPE811_REG_SYS_CTRL2,
+ (STMPE811_SYS_CTRL2_ADC_OFF |
+ STMPE811_SYS_CTRL2_TSC_OFF));
+ if (ret) {
+ dev_err(&pdev->dev, "Could not enable clock for ADC and TS\n");
+ goto err_free_irq;
+ }
+
+ ret = stmpe811_reg_set_bits(stm, STMPE811_REG_ADC_CTRL1,
+ SAMPLE_TIME(ts->sample_time) |
+ MOD_12B(ts->mod_12b) | REF_SEL(ts->ref_sel));
+ if (ret) {
+ dev_err(&pdev->dev, "Could not setup ADC\n");
+ goto err_free_irq;
+ }
+
+ ret = stmpe811_reg_set_bits(stm, STMPE811_REG_ADC_CTRL2,
+ ADC_FREQ(ts->adc_freq));
+ if (ret) {
+ dev_err(&pdev->dev, "Could not setup ADC\n");
+ goto err_free_irq;
+ }
+
+ ret = stmpe811_reg_set_bits(stm, STMPE811_REG_TSC_CFG,
+ AVE_CTRL(ts->ave_ctrl) |
+ DET_DELAY(ts->touch_det_delay) |
+ SETTLING(ts->settling));
+ if (ret) {
+ dev_err(&pdev->dev, "Could not config touch\n");
+ goto err_free_irq;
+ }
+
+ ret = stmpe811_reg_set_bits(stm,
+ STMPE811_REG_TSC_FRACTION_Z,
+ FRACTION_Z(ts->fraction_z));
+ if (ret) {
+ dev_err(&pdev->dev, "Could not config touch\n");
+ goto err_free_irq;
+ }
+
+ /* set FIFO to 1 for single point reading */
+ ret = stmpe811_reg_write(stm, STMPE811_REG_FIFO_TH, 1);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not set FIFO\n");
+ goto err_free_irq;
+ }
+
+ ret = stmpe811_reg_set_bits(stm, STMPE811_REG_TSC_CTRL,
+ STMPE811_TSC_CTRL_OP_MOD_XYZ);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not set mode\n");
+ goto err_free_irq;
+ }
+
+ idev->name = STMPE811_TS_NAME;
+ idev->id.bustype = BUS_I2C;
+ idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ idev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+
+ idev->open = stmpe811_ts_open;
+ idev->close = stmpe811_ts_close;
+
+ input_set_drvdata(idev, ts);
+
+ ret = input_register_device(idev);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not register input device\n");
+ goto err_free_irq;
+ }
+
+ stm->active_flag |= STMPE811_USE_TS;
+
+ input_set_abs_params(idev, ABS_X, 0, XY_MASK, 0, 0);
+ input_set_abs_params(idev, ABS_Y, 0, XY_MASK, 0, 0);
+ input_set_abs_params(idev, ABS_PRESSURE, 0x0, 0xff, 0, 0);
+
+ return ret;
+
+err_free_irq:
+ free_irq(ts_irq, ts);
+err_free_input:
+ input_free_device(idev);
+ platform_set_drvdata(pdev, NULL);
+err_free_ts:
+ kfree(ts);
+err_out:
+ return ret;
+}
+
+static int __devexit stmpe811_ts_remove(struct platform_device *pdev)
+{
+ struct stmpe811_touch *ts = platform_get_drvdata(pdev);
+ unsigned int ts_irq = ts->stm->irq_base + STMPE811_IRQ_FIFO_TH;
+
+ cancel_delayed_work(&ts->work);
+
+ stmpe811_reg_write(ts->stm, STMPE811_REG_FIFO_TH, 0);
+
+ stmpe811_reg_set_bits(ts->stm, STMPE811_REG_SYS_CTRL2,
+ (STMPE811_SYS_CTRL2_ADC_OFF |
+ STMPE811_SYS_CTRL2_ADC_OFF));
+
+ free_irq(ts_irq, ts);
+
+ ts->stm->active_flag &= ~STMPE811_USE_TS;
+ platform_set_drvdata(pdev, NULL);
+
+ input_unregister_device(ts->idev);
+ input_free_device(ts->idev);
+
+ kfree(ts);
+
+ return 0;
+}
+
+static struct platform_driver stmpe811_input_driver = {
+ .driver = {
+ .name = STMPE811_TS_NAME,
+ },
+ .probe = stmpe811_input_probe,
+ .remove = __devexit_p(stmpe811_ts_remove),
+};
+
+static int __init stmpe811_input_init(void)
+{
+ return platform_driver_register(&stmpe811_input_driver);
+}
+
+module_init(stmpe811_input_init);
+
+static void __exit stmpe811_input_exit(void)
+{
+ platform_driver_unregister(&stmpe811_input_driver);
+}
+
+module_exit(stmpe811_input_exit);
+
+MODULE_AUTHOR("Luotao Fu <[email protected]>");
+MODULE_DESCRIPTION("STMPE811 touchscreen driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" STMPE811_TS_NAME);
diff --git a/include/linux/mfd/stmpe811.h b/include/linux/mfd/stmpe811.h
index 736b4eb..68bcdac 100644
--- a/include/linux/mfd/stmpe811.h
+++ b/include/linux/mfd/stmpe811.h
@@ -94,6 +94,44 @@ struct stmpe811 {
};
/**
+ * struct stmpe811_ts_platform_data - stmpe811 touch screen controller platform
+ * data
+ * @sample_time: ADC converstion time in number of clock.
+ * (0 -> 36 clocks, 1 -> 44 clocks, 2 -> 56 clocks, 3 -> 64 clocks,
+ * 4 -> 80 clocks, 5 -> 96 clocks, 6 -> 144 clocks),
+ * recommended is 4.
+ * @mod_12b: ADC Bit mode (0 -> 10bit ADC, 1 -> 12bit ADC)
+ * @ref_sel: ADC reference source
+ * (0 -> internal reference, 1 -> external reference)
+ * @adc_freq: ADC Clock speed
+ * (0 -> 1.625 MHz, 1 -> 3.25 MHz, 2 || 3 -> 6.5 MHz)
+ * @ave_ctrl: Sample average control
+ * (0 -> 1 sample, 1 -> 2 samples, 2 -> 4 samples, 3 -> 8 samples)
+ * @touch_det_delay: Touch detect interrupt delay
+ * (0 -> 10 us, 1 -> 50 us, 2 -> 100 us, 3 -> 500 us,
+ * 4-> 1 ms, 5 -> 5 ms, 6 -> 10 ms, 7 -> 50 ms)
+ * recommended is 3
+ * @settling: Panel driver settling time
+ * (0 -> 10 us, 1 -> 100 us, 2 -> 500 us, 3 -> 1 ms,
+ * 4 -> 5 ms, 5 -> 10 ms, 6 for 50 ms, 7 -> 100 ms)
+ * recommended is 2
+ * @fraction_z: Length of the fractional part in z
+ * (fraction_z ([0..7]) = Count of the fractional part)
+ * recommended is 7
+ *
+ * */
+struct stmpe811_ts_platform_data {
+ u8 sample_time;
+ u8 mod_12b;
+ u8 ref_sel;
+ u8 adc_freq;
+ u8 ave_ctrl;
+ u8 touch_det_delay;
+ u8 settling;
+ u8 fraction_z;
+};
+
+/**
* struct stmpe811_gpio_platform_data - stmpe811 gpio controller platform data
*
* @gpio_base: number of the chip's first GPIO
@@ -116,6 +154,7 @@ struct stmpe811_gpio_platform_data {
* @irq_base: board dependt irq number of the first irq for the irq chip
* @gpio_pdata: pointer to gpio controller platform data
* registered by the core.
+ * @ts_pdata: pointer to touch screen controller platform data
*
* */
struct stmpe811_platform_data {
@@ -126,6 +165,7 @@ struct stmpe811_platform_data {
unsigned int irq_rev_pol;
int irq_base;
struct stmpe811_gpio_platform_data *gpio_pdata;
+ struct stmpe811_ts_platform_data *ts_pdata;
};
int stmpe811_block_read(struct stmpe811 *stm, u8 reg, uint len, u8 *val);
--
1.7.1
STMPE811 is a multifunction device, which contains a GPIO controller, a
Touchscreen controller, an ADC and a temperature sensor. This patch adds a core
driver for this device. The driver provides core functionalities like accessing
the registers and management of subdevices. The device supports communication
through SPI and I2C interface. Currently we only support I2C.
Signed-off-by: Luotao Fu <[email protected]>
Acked-by: Jonathan Cameron<[email protected]>
---
V2 Changes:
* remove inculding subsysstem headers from the device header file
* use genirq for irq management:
* use request_threaded_irq for the main isr
* register own irq chip and use nested irq callback for subdevice irqs.
* drop i2c client data cleaning up to let the i2c framework do the job.
* fix bogus usage of driver data in i2c device id table
* some formating fixes
V3 Changes
* reformated platform data comment to with kernel-doc style
* reranged some macro defines to make the usage clear
* add bus_lock/unlock to the irq chip, remove manual locking stuff from de/masking
callbacks
* move gpio subdevice platform data stuff into the gpio subdevice patch, where
it belongs to.
* reworked irq control flags in the platform data, add unconditional IRQF_ONESHOT
to the threaded irq.
drivers/mfd/Kconfig | 8 +
drivers/mfd/Makefile | 1 +
drivers/mfd/stmpe811-core.c | 407 ++++++++++++++++++++++++++++++++++++++++++
include/linux/mfd/stmpe811.h | 121 +++++++++++++
4 files changed, 537 insertions(+), 0 deletions(-)
create mode 100644 drivers/mfd/stmpe811-core.c
create mode 100644 include/linux/mfd/stmpe811.h
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 9da0e50..2c5388b 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -482,6 +482,14 @@ config MFD_JANZ_CMODIO
host many different types of MODULbus daughterboards, including
CAN and GPIO controllers.
+config MFD_STMPE811
+ tristate "STMicroelectronics STMPE811"
+ depends on I2C
+ select MFD_CORE
+ help
+ This is the core driver for the stmpe811 GPIO expander with touchscreen
+ controller, ADC and temperature sensor.
+
endif # MFD_SUPPORT
menu "Multimedia Capabilities Port drivers"
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index fb503e7..6a7ad83 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -71,3 +71,4 @@ obj-$(CONFIG_PMIC_ADP5520) += adp5520.o
obj-$(CONFIG_LPC_SCH) += lpc_sch.o
obj-$(CONFIG_MFD_RDC321X) += rdc321x-southbridge.o
obj-$(CONFIG_MFD_JANZ_CMODIO) += janz-cmodio.o
+obj-$(CONFIG_MFD_STMPE811) += stmpe811-core.o
diff --git a/drivers/mfd/stmpe811-core.c b/drivers/mfd/stmpe811-core.c
new file mode 100644
index 0000000..86c928f
--- /dev/null
+++ b/drivers/mfd/stmpe811-core.c
@@ -0,0 +1,407 @@
+/*
+ * stmpe811-core.c -- core support for STMicroelectronics STMPE811 chip.
+ *
+ * based on pcf50633-core.c by Harald Welte <[email protected]> and
+ * Balaji Rao <[email protected]>
+ *
+ * (c)2010 Luotao Fu <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/irq.h>
+
+#include <linux/mfd/core.h>
+#include <linux/mfd/stmpe811.h>
+
+static struct mfd_cell stmpe811_ts_dev = {
+ .name = "stmpe811-ts",
+};
+
+static struct mfd_cell stmpe811_gpio_dev = {
+ .name = "stmpe811-gpio",
+};
+
+static irqreturn_t stmpe811_irq(int irq, void *data)
+{
+ struct stmpe811 *stm = data;
+ int ret, bit;
+ u8 int_stat;
+
+ ret = stmpe811_reg_read(stm, STMPE811_REG_INT_STA, &int_stat);
+ if (ret) {
+ dev_err(stm->dev, "Error reading INT registers\n");
+ return IRQ_NONE;
+ }
+
+ dev_dbg(stm->dev, "%s int_stat 0x%02x\n", __func__, int_stat);
+
+ for_each_set_bit(bit, (unsigned long *)&int_stat, STMPE811_NUM_IRQ) {
+ /* mask the interrupt while calling handler */
+ stmpe811_reg_clear_bits(stm, STMPE811_REG_INT_EN, 1 << bit);
+
+ handle_nested_irq(stm->irq_base + bit);
+
+ /* acknowledge the interrupt */
+ stmpe811_reg_set_bits(stm, STMPE811_REG_INT_STA, 1 << bit);
+ /* demask the interrupt */
+ stmpe811_reg_set_bits(stm, STMPE811_REG_INT_EN, 1 << bit);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void stmpe811_irq_mask(unsigned int irq)
+{
+ struct stmpe811 *stm = get_irq_chip_data(irq);
+ irq -= stm->irq_base;
+
+ clear_bit(irq, &stm->int_en_cur);
+}
+
+static void stmpe811_irq_unmask(unsigned int irq)
+{
+ struct stmpe811 *stm = get_irq_chip_data(irq);
+ irq -= stm->irq_base;
+
+ set_bit(irq, &stm->int_en_cur);
+}
+
+static void stmpe811_irq_lock(unsigned int irq)
+{
+ struct stmpe811 *stm = get_irq_chip_data(irq);
+
+ mutex_lock(&stm->irq_lock);
+}
+
+static void stmpe811_irq_sync_unlock(unsigned int irq)
+{
+ struct stmpe811 *stm = get_irq_chip_data(irq);
+
+ if (stm->int_en_cur != stm->int_en_cache) {
+ stm->int_en_cache = stm->int_en_cur;
+ stmpe811_reg_write(stm, STMPE811_REG_INT_EN, stm->int_en_cur);
+ }
+
+ mutex_unlock(&stm->irq_lock);
+}
+
+static struct irq_chip stmpe811_irq_chip = {
+ .name = "stmpe811",
+ .mask = stmpe811_irq_mask,
+ .unmask = stmpe811_irq_unmask,
+ .bus_lock = stmpe811_irq_lock,
+ .bus_sync_unlock = stmpe811_irq_sync_unlock,
+};
+
+static int stmpe811_irq_init(struct stmpe811 *stm)
+{
+ struct stmpe811_platform_data *pdata = stm->pdata;
+ unsigned long irq_flag = IRQF_ONESHOT;
+ unsigned long int_ctrl = STMPE811_INT_CTRL_GLOBAL_INT;
+ int base = stm->irq_base;
+ int irq, ret = 0;
+
+ if (pdata->irq_high) {
+ irq_flag |= IRQF_TRIGGER_HIGH;
+ int_ctrl |= STMPE811_INT_CTRL_INT_POLARITY;
+ } else
+ irq_flag |= IRQF_TRIGGER_LOW;
+
+ if (pdata->irq_rev_pol)
+ change_bit(__fls(STMPE811_INT_CTRL_INT_POLARITY), &int_ctrl);
+
+ for (irq = base; irq < base + STMPE811_NUM_IRQ; irq++) {
+ set_irq_chip_data(irq, stm);
+ set_irq_chip_and_handler(irq, &stmpe811_irq_chip,
+ handle_edge_irq);
+ set_irq_nested_thread(irq, 1);
+#ifdef CONFIG_ARM
+ set_irq_flags(irq, IRQF_VALID);
+#else
+ set_irq_noprobe(irq);
+#endif
+ }
+
+ ret = request_threaded_irq(stm->irq, NULL, stmpe811_irq, irq_flag,
+ "stmpe811", stm);
+ if (ret)
+ goto out;
+
+ ret = stmpe811_reg_write(stm, STMPE811_REG_INT_CTRL, int_ctrl);
+
+out:
+ return ret;
+}
+
+static void stmpe811_irq_remove(struct stmpe811 *stm)
+{
+ int base = stm->irq_base;
+ int irq;
+
+ for (irq = base; irq < base + STMPE811_NUM_IRQ; irq++) {
+#ifdef CONFIG_ARM
+ set_irq_flags(irq, 0);
+#endif
+ set_irq_chip_and_handler(irq, NULL, NULL);
+ set_irq_chip_data(irq, NULL);
+ }
+
+ free_irq(stm->irq, stm);
+}
+
+static inline int __stmpe811_i2c_reads(struct i2c_client *client, int reg,
+ int len, u8 *val)
+{
+ int ret;
+
+ ret = i2c_smbus_read_i2c_block_data(client, reg, len, val);
+ if (ret < 0) {
+ dev_err(&client->dev, "failed reading from 0x%02x\n", reg);
+ return ret;
+ }
+ return 0;
+}
+
+static inline int __stmpe811_i2c_read(struct i2c_client *client,
+ int reg, u8 *val)
+{
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(client, reg);
+ if (ret < 0) {
+ dev_err(&client->dev, "failed reading at 0x%02x\n", reg);
+ return ret;
+ }
+ dev_dbg(&client->dev, "%s: value 0x%x from 0x%x\n", __func__, ret, reg);
+
+ *val = (u8) ret;
+ return 0;
+}
+
+static inline int __stmpe811_i2c_write(struct i2c_client *client,
+ int reg, u8 val)
+{
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, reg, val);
+ if (ret < 0) {
+ dev_err(&client->dev, "failed writing 0x%02x to 0x%02x\n",
+ val, reg);
+ return ret;
+ }
+ return 0;
+}
+
+int stmpe811_block_read(struct stmpe811 *stm, u8 reg, uint len, u8 *val)
+{
+ int ret;
+
+ mutex_lock(&stm->io_lock);
+ ret = __stmpe811_i2c_reads(stm->i2c_client, reg, len, val);
+ mutex_unlock(&stm->io_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe811_block_read);
+
+int stmpe811_reg_read(struct stmpe811 *stm, u8 reg, u8 *val)
+{
+ int ret;
+
+ mutex_lock(&stm->io_lock);
+ ret = __stmpe811_i2c_read(stm->i2c_client, reg, val);
+ mutex_unlock(&stm->io_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe811_reg_read);
+
+int stmpe811_reg_write(struct stmpe811 *stm, u8 reg, u8 val)
+{
+ int ret;
+
+ mutex_lock(&stm->io_lock);
+ ret = __stmpe811_i2c_write(stm->i2c_client, reg, val);
+ mutex_unlock(&stm->io_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe811_reg_write);
+
+int stmpe811_reg_set_bits(struct stmpe811 *stm, u8 reg, u8 val)
+{
+ int ret;
+ u8 tmp;
+
+ mutex_lock(&stm->io_lock);
+ ret = __stmpe811_i2c_read(stm->i2c_client, reg, &tmp);
+ if (ret < 0)
+ goto out;
+
+ tmp |= val;
+ ret = __stmpe811_i2c_write(stm->i2c_client, reg, tmp);
+
+out:
+ mutex_unlock(&stm->io_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe811_reg_set_bits);
+
+int stmpe811_reg_clear_bits(struct stmpe811 *stm, u8 reg, u8 val)
+{
+ int ret;
+ u8 tmp;
+
+ mutex_lock(&stm->io_lock);
+ ret = __stmpe811_i2c_read(stm->i2c_client, reg, &tmp);
+ if (ret < 0)
+ goto out;
+
+ tmp &= ~val;
+ ret = __stmpe811_i2c_write(stm->i2c_client, reg, tmp);
+
+out:
+ mutex_unlock(&stm->io_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe811_reg_clear_bits);
+
+static int __devinit stmpe811_probe(struct i2c_client *client,
+ const struct i2c_device_id *ids)
+{
+ struct stmpe811 *stm;
+ struct stmpe811_platform_data *pdata = client->dev.platform_data;
+ int ret = 0;
+ u8 chip_id[2], chip_ver = 0;
+
+ if (!client->irq) {
+ dev_err(&client->dev, "Missing IRQ\n");
+ return -ENOENT;
+ }
+
+ stm = kzalloc(sizeof(*stm), GFP_KERNEL);
+ if (!stm)
+ return -ENOMEM;
+
+ stm->pdata = pdata;
+
+ mutex_init(&stm->io_lock);
+ mutex_init(&stm->irq_lock);
+
+ i2c_set_clientdata(client, stm);
+ stm->dev = &client->dev;
+ stm->i2c_client = client;
+ stm->irq = client->irq;
+ stm->irq_base = pdata->irq_base;
+
+ ret = stmpe811_block_read(stm, STMPE811_REG_CHIP_ID, 2, chip_id);
+ if ((ret < 0) || (((chip_id[0] << 8) | chip_id[1]) != 0x811)) {
+ dev_err(&client->dev, "could not verify stmpe811 chip id\n");
+ ret = -ENODEV;
+ goto err_free;
+ }
+
+ stmpe811_reg_read(stm, STMPE811_REG_ID_VER, &chip_ver);
+ dev_info(&client->dev, "found stmpe811 chip id 0x%x version 0x%x\n",
+ (chip_id[0] << 8) | chip_id[1], chip_ver);
+
+ /* reset the device */
+ stmpe811_reg_write(stm, STMPE811_REG_SYS_CTRL1,
+ STMPE811_SYS_CTRL1_SOFT_RESET);
+
+ ret = stmpe811_irq_init(stm);
+ if (ret) {
+ dev_err(&client->dev, "failed to initialize IRQ: %d\n", ret);
+ goto err_free;
+ }
+
+ if (pdata->flags & STMPE811_USE_TS)
+ ret =
+ mfd_add_devices(&client->dev, -1, &stmpe811_ts_dev, 1, NULL,
+ 0);
+ if (ret != 0)
+ goto err_release_irq;
+
+ if (pdata->flags & STMPE811_USE_GPIO)
+ ret =
+ mfd_add_devices(&client->dev, -1, &stmpe811_gpio_dev, 1,
+ NULL, 0);
+ if (ret != 0)
+ dev_err(&client->dev, "Unable to add mfd subdevices\n");
+
+ return ret;
+
+err_release_irq:
+ free_irq(client->irq, stm);
+err_free:
+ mutex_destroy(&stm->io_lock);
+ mutex_destroy(&stm->irq_lock);
+
+ kfree(stm);
+
+ return ret;
+}
+
+static int __devexit stmpe811_remove(struct i2c_client *client)
+{
+ struct stmpe811 *stm = i2c_get_clientdata(client);
+
+ stmpe811_reg_write(stm, STMPE811_REG_SYS_CTRL1,
+ STMPE811_SYS_CTRL1_HIBERNATE);
+
+ stmpe811_irq_remove(stm);
+
+ mutex_destroy(&stm->io_lock);
+ mutex_destroy(&stm->irq_lock);
+
+ mfd_remove_devices(&client->dev);
+
+ kfree(stm);
+
+ return 0;
+}
+
+static struct i2c_device_id stmpe811_id_table[] = {
+ {"stmpe811", 0},
+ { /* end of list */ }
+};
+
+static struct i2c_driver stmpe811_driver = {
+ .driver = {
+ .name = "stmpe811",
+ .owner = THIS_MODULE,
+ },
+ .probe = stmpe811_probe,
+ .remove = __devexit_p(stmpe811_remove),
+ .id_table = stmpe811_id_table,
+};
+
+static int __init stmpe811_init(void)
+{
+ return i2c_add_driver(&stmpe811_driver);
+}
+
+subsys_initcall(stmpe811_init);
+
+static void __exit stmpe811_exit(void)
+{
+ i2c_del_driver(&stmpe811_driver);
+}
+
+module_exit(stmpe811_exit);
+
+MODULE_DESCRIPTION
+ ("CORE Driver for STMPE811 Touch screen controller with GPIO expander");
+MODULE_AUTHOR("Luotao Fu <[email protected]>");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/stmpe811.h b/include/linux/mfd/stmpe811.h
new file mode 100644
index 0000000..8e2b55e
--- /dev/null
+++ b/include/linux/mfd/stmpe811.h
@@ -0,0 +1,121 @@
+#ifndef __LINUX_MFD_STMPE811_H
+#define __LINUX_MFD_STMPE811_H
+
+#define STMPE811_REG_CHIP_ID 0x00
+#define STMPE811_REG_ID_VER 0x02
+#define STMPE811_REG_SYS_CTRL1 0x03
+#define STMPE811_REG_SYS_CTRL2 0x04
+#define STMPE811_REG_SPI_CFG 0x08
+#define STMPE811_REG_INT_CTRL 0x09
+#define STMPE811_REG_INT_EN 0x0A
+#define STMPE811_REG_INT_STA 0x0B
+#define STMPE811_REG_GPIO_EN 0x0C
+#define STMPE811_REG_GPIO_INT_STA 0x0D
+#define STMPE811_REG_ADC_INT_EN 0x0E
+#define STMPE811_REG_ADC_INT_STA 0x0F
+#define STMPE811_REG_GPIO_SET_PIN 0x10
+#define STMPE811_REG_GPIO_CLR_PIN 0x11
+#define STMPE811_REG_GPIO_MP_STA 0x12
+#define STMPE811_REG_GPIO_DIR 0x13
+#define STMPE811_REG_GPIO_ED 0x14
+#define STMPE811_REG_GPIO_RE 0x15
+#define STMPE811_REG_GPIO_FE 0x16
+#define STMPE811_REG_GPIO_AF 0x17
+#define STMPE811_REG_ADC_CTRL1 0x20
+#define STMPE811_REG_ADC_CTRL2 0x21
+#define STMPE811_REG_ADC_CAPT 0x22
+#define STMPE811_REG_ADC_DATA_CH0 0x30
+#define STMPE811_REG_ADC_DATA_CH1 0x32
+#define STMPE811_REG_ADC_DATA_CH2 0x34
+#define STMPE811_REG_ADC_DATA_CH3 0x36
+#define STMPE811_REG_ADC_DATA_CH4 0x38
+#define STMPE811_REG_ADC_DATA_CH5 0x3A
+#define STMPE811_REG_ADC_DATA_CH6 0x3C
+#define STMPE811_REG_ADC_DATA_CH7 0x3E
+#define STMPE811_REG_TSC_CTRL 0x40
+#define STMPE811_REG_TSC_CFG 0x41
+#define STMPE811_REG_WDW_TR_X 0x42
+#define STMPE811_REG_WDW_TR_Y 0x44
+#define STMPE811_REG_WDW_BL_X 0x46
+#define STMPE811_REG_WDW_BL_Y 0x48
+#define STMPE811_REG_FIFO_TH 0x4A
+#define STMPE811_REG_FIFO_STA 0x4B
+#define STMPE811_REG_FIFO_SIZE 0x4C
+#define STMPE811_REG_TSC_DATA_X 0x4D
+#define STMPE811_REG_TSC_DATA_Y 0x4F
+#define STMPE811_REG_TSC_DATA_Z 0x51
+#define STMPE811_REG_TSC_DATA_XYZ 0x52
+#define STMPE811_REG_TSC_FRACTION_Z 0x56
+#define STMPE811_REG_TSC_DATA 0x57
+#define STMPE811_REG_TSC_DATA_SINGLE 0xD7
+#define STMPE811_REG_TSC_I_DRIVE 0x58
+#define STMPE811_REG_TSC_SHIELD 0x59
+#define STMPE811_REG_TEMP_CTRL 0x60
+
+#define STMPE811_SYS_CTRL1_HIBERNATE (1<<0)
+#define STMPE811_SYS_CTRL1_SOFT_RESET (1<<1)
+
+#define STMPE811_SYS_CTRL2_ADC_OFF (1<<0)
+#define STMPE811_SYS_CTRL2_TSC_OFF (1<<1)
+#define STMPE811_SYS_CTRL2_GPIO_OFF (1<<2)
+#define STMPE811_SYS_CTRL2_TS_OFF (1<<3)
+
+#define STMPE811_INT_CTRL_GLOBAL_INT (1<<0)
+#define STMPE811_INT_CTRL_INT_TYPE (1<<1)
+#define STMPE811_INT_CTRL_INT_POLARITY (1<<2)
+
+#define STMPE811_FIFO_STA_OFLOW (1<<7)
+#define STMPE811_FIFO_STA_FULL (1<<6)
+#define STMPE811_FIFO_STA_EMPTY (1<<5)
+#define STMPE811_FIFO_STA_TH_TRIG (1<<4)
+#define STMPE811_FIFO_STA_RESET (1<<0)
+
+#define STMPE811_IRQ_TOUCH_DET 0
+#define STMPE811_IRQ_FIFO_TH 1
+#define STMPE811_IRQ_FIFO_OFLOW 2
+#define STMPE811_IRQ_FIFO_FULL 3
+#define STMPE811_IRQ_FIFO_EMPTY 4
+#define STMPE811_IRQ_TEMP_SENS 5
+#define STMPE811_IRQ_ADC 6
+#define STMPE811_IRQ_GPIO 7
+#define STMPE811_NUM_IRQ 8
+
+struct stmpe811 {
+ struct device *dev;
+ struct i2c_client *i2c_client;
+ struct stmpe811_platform_data *pdata;
+ struct mutex io_lock;
+ struct mutex irq_lock;
+ unsigned int irq;
+ int irq_base;
+ unsigned long int_en_cache;
+ unsigned long int_en_cur;
+ u8 active_flag;
+};
+
+/**
+ * struct stmpe811_platform_data - stmpe811 core platform data
+ *
+ * @flags: define which subdevices should be supported
+ * @irq_high: IRQ is active high.
+ * @irq_rev_pol: IRQ line is connected with reversed polarity
+ * @irq_base: board dependt irq number of the first irq for the irq chip
+ * registered by the core.
+ *
+ * */
+struct stmpe811_platform_data {
+#define STMPE811_USE_TS (1<<0)
+#define STMPE811_USE_GPIO (1<<1)
+ unsigned int flags;
+ unsigned int irq_high;
+ unsigned int irq_rev_pol;
+ int irq_base;
+};
+
+int stmpe811_block_read(struct stmpe811 *stm, u8 reg, uint len, u8 *val);
+int stmpe811_reg_read(struct stmpe811 *pcf, u8 reg, u8 *val);
+int stmpe811_reg_write(struct stmpe811 *stm, u8 reg, u8 val);
+int stmpe811_reg_set_bits(struct stmpe811 *stm, u8 reg, u8 val);
+int stmpe811_reg_clear_bits(struct stmpe811 *stm, u8 reg, u8 val);
+
+#endif
--
1.7.1
On Tue, Jun 15, 2010 at 11:00:44AM +0200, Luotao Fu wrote:
> STMPE811 is a multifunction device, which contains a GPIO controller, a
> Touchscreen controller, an ADC and a temperature sensor. This patch adds a core
> driver for this device. The driver provides core functionalities like accessing
> the registers and management of subdevices. The device supports communication
> through SPI and I2C interface. Currently we only support I2C.
>
> Signed-off-by: Luotao Fu <[email protected]>
> Acked-by: Jonathan Cameron<[email protected]>
Looks good.
Reviewed-by: Mark Brown <[email protected]>