Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input

Pull more input updates from Dmitry Torokhov:
 "Second round of updates for the input subsystem.

  This introduces two brand new touchscreen drivers (Colibri and
  imx6ul_tsc), some small driver fixes, and we are no longer report
  errors from evdev_flush() as users do not really have a way of
  handling errors, error codes that we were returning were not on the
  list of errors supposed to be returned by close(), and errors were
  causing issues with one of older versions of systemd"

* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input:
  Input: imx_keypad - remove obsolete comment
  Input: touchscreen - add imx6ul_tsc driver support
  Input: Add touchscreen support for Colibri VF50
  Input: i8042 - lower log level for "no controller" message
  Input: evdev - do not report errors form flush()
  Input: elants_i2c - extend the calibration timeout to 12 seconds
  Input: sparcspkr - fix module autoload for OF platform drivers
  Input: regulator-haptic - fix module autoload for OF platform driver
  Input: pwm-beeper - fix module autoload for OF platform driver
  Input: ab8500-ponkey - Fix module autoload for OF platform driver
  Input: cyttsp - remove unnecessary MODULE_ALIAS()
  Input: elan_i2c - add ACPI ID "ELAN1000"
This commit is contained in:
Linus Torvalds 2015-09-11 19:17:28 -07:00
commit f0c032d81f
17 changed files with 1019 additions and 15 deletions

View File

@ -0,0 +1,36 @@
* Toradex Colibri VF50 Touchscreen driver
Required Properties:
- compatible must be toradex,vf50-touchscreen
- io-channels: adc channels being used by the Colibri VF50 module
- xp-gpios: FET gate driver for input of X+
- xm-gpios: FET gate driver for input of X-
- yp-gpios: FET gate driver for input of Y+
- ym-gpios: FET gate driver for input of Y-
- interrupt-parent: phandle for the interrupt controller
- interrupts: pen irq interrupt for touch detection
- pinctrl-names: "idle", "default", "gpios"
- pinctrl-0: pinctrl node for pen/touch detection state pinmux
- pinctrl-1: pinctrl node for X/Y and pressure measurement (ADC) state pinmux
- pinctrl-2: pinctrl node for gpios functioning as FET gate drivers
- vf50-ts-min-pressure: pressure level at which to stop measuring X/Y values
Example:
touchctrl: vf50_touchctrl {
compatible = "toradex,vf50-touchscreen";
io-channels = <&adc1 0>,<&adc0 0>,
<&adc0 1>,<&adc1 2>;
xp-gpios = <&gpio0 13 GPIO_ACTIVE_LOW>;
xm-gpios = <&gpio2 29 GPIO_ACTIVE_HIGH>;
yp-gpios = <&gpio0 12 GPIO_ACTIVE_LOW>;
ym-gpios = <&gpio0 4 GPIO_ACTIVE_HIGH>;
interrupt-parent = <&gpio0>;
interrupts = <8 IRQ_TYPE_LEVEL_LOW>;
pinctrl-names = "idle","default","gpios";
pinctrl-0 = <&pinctrl_touchctrl_idle>;
pinctrl-1 = <&pinctrl_touchctrl_default>;
pinctrl-2 = <&pinctrl_touchctrl_gpios>;
vf50-ts-min-pressure = <200>;
status = "disabled";
};

View File

@ -0,0 +1,36 @@
* Freescale i.MX6UL Touch Controller
Required properties:
- compatible: must be "fsl,imx6ul-tsc".
- reg: this touch controller address and the ADC2 address.
- interrupts: the interrupt of this touch controller and ADC2.
- clocks: the root clock of touch controller and ADC2.
- clock-names; must be "tsc" and "adc".
- xnur-gpio: the X- gpio this controller connect to.
This xnur-gpio returns to low once the finger leave the touch screen (The
last touch event the touch controller capture).
Optional properties:
- measure-delay-time: the value of measure delay time.
Before X-axis or Y-axis measurement, the screen need some time before
even potential distribution ready.
This value depends on the touch screen.
- pre-charge-time: the touch screen need some time to precharge.
This value depends on the touch screen.
Example:
tsc: tsc@02040000 {
compatible = "fsl,imx6ul-tsc";
reg = <0x02040000 0x4000>, <0x0219c000 0x4000>;
interrupts = <GIC_SPI 3 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 101 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_IPG>,
<&clks IMX6UL_CLK_ADC2>;
clock-names = "tsc", "adc";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_tsc>;
xnur-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
measure-delay-time = <0xfff>;
pre-charge-time = <0xffff>;
status = "okay";
};

View File

@ -290,19 +290,14 @@ static int evdev_flush(struct file *file, fl_owner_t id)
{
struct evdev_client *client = file->private_data;
struct evdev *evdev = client->evdev;
int retval;
retval = mutex_lock_interruptible(&evdev->mutex);
if (retval)
return retval;
mutex_lock(&evdev->mutex);
if (!evdev->exist || client->revoked)
retval = -ENODEV;
else
retval = input_flush_device(&evdev->handle, file);
if (evdev->exist && !client->revoked)
input_flush_device(&evdev->handle, file);
mutex_unlock(&evdev->mutex);
return retval;
return 0;
}
static void evdev_free(struct device *dev)

View File

@ -5,8 +5,6 @@
* 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.
*
* <<Power management needs to be implemented>>.
*/
#include <linux/clk.h>

View File

@ -118,6 +118,7 @@ static const struct of_device_id ab8500_ponkey_match[] = {
{ .compatible = "stericsson,ab8500-ponkey", },
{}
};
MODULE_DEVICE_TABLE(of, ab8500_ponkey_match);
#endif
static struct platform_driver ab8500_ponkey_driver = {

View File

@ -173,6 +173,7 @@ static const struct of_device_id pwm_beeper_match[] = {
{ .compatible = "pwm-beeper", },
{ },
};
MODULE_DEVICE_TABLE(of, pwm_beeper_match);
#endif
static struct platform_driver pwm_beeper_driver = {

View File

@ -249,6 +249,7 @@ static const struct of_device_id regulator_haptic_dt_match[] = {
{ .compatible = "regulator-haptic" },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, regulator_haptic_dt_match);
static struct platform_driver regulator_haptic_driver = {
.probe = regulator_haptic_probe,

View File

@ -253,6 +253,7 @@ static const struct of_device_id bbc_beep_match[] = {
},
{},
};
MODULE_DEVICE_TABLE(of, bbc_beep_match);
static struct platform_driver bbc_beep_driver = {
.driver = {
@ -332,6 +333,7 @@ static const struct of_device_id grover_beep_match[] = {
},
{},
};
MODULE_DEVICE_TABLE(of, grover_beep_match);
static struct platform_driver grover_beep_driver = {
.driver = {

View File

@ -1170,6 +1170,7 @@ static const struct acpi_device_id elan_acpi_id[] = {
{ "ELAN0000", 0 },
{ "ELAN0100", 0 },
{ "ELAN0600", 0 },
{ "ELAN1000", 0 },
{ }
};
MODULE_DEVICE_TABLE(acpi, elan_acpi_id);

View File

@ -877,7 +877,7 @@ static int __init i8042_check_aux(void)
static int i8042_controller_check(void)
{
if (i8042_flush()) {
pr_err("No controller found\n");
pr_info("No controller found\n");
return -ENODEV;
}

View File

@ -479,6 +479,18 @@ config TOUCHSCREEN_MTOUCH
To compile this driver as a module, choose M here: the
module will be called mtouch.
config TOUCHSCREEN_IMX6UL_TSC
tristate "Freescale i.MX6UL touchscreen controller"
depends on (OF && GPIOLIB) || COMPILE_TEST
help
Say Y here if you have a Freescale i.MX6UL, and want to
use the internal touchscreen controller.
If unsure, say N.
To compile this driver as a module, choose M here: the
module will be called imx6ul_tsc.
config TOUCHSCREEN_INEXIO
tristate "iNexio serial touchscreens"
select SERIO
@ -1040,4 +1052,16 @@ config TOUCHSCREEN_ZFORCE
To compile this driver as a module, choose M here: the
module will be called zforce_ts.
config TOUCHSCREEN_COLIBRI_VF50
tristate "Toradex Colibri on board touchscreen driver"
depends on GPIOLIB && IIO && VF610_ADC
help
Say Y here if you have a Colibri VF50 and plan to use
the on-board provided 4-wire touchscreen driver.
If unsure, say N.
To compile this driver as a module, choose M here: the
module will be called colibri_vf50_ts.
endif

View File

@ -38,6 +38,7 @@ obj-$(CONFIG_TOUCHSCREEN_EGALAX) += egalax_ts.o
obj-$(CONFIG_TOUCHSCREEN_FUJITSU) += fujitsu_ts.o
obj-$(CONFIG_TOUCHSCREEN_GOODIX) += goodix.o
obj-$(CONFIG_TOUCHSCREEN_ILI210X) += ili210x.o
obj-$(CONFIG_TOUCHSCREEN_IMX6UL_TSC) += imx6ul_tsc.o
obj-$(CONFIG_TOUCHSCREEN_INEXIO) += inexio.o
obj-$(CONFIG_TOUCHSCREEN_INTEL_MID) += intel-mid-touch.o
obj-$(CONFIG_TOUCHSCREEN_IPROC) += bcm_iproc_tsc.o
@ -85,3 +86,4 @@ obj-$(CONFIG_TOUCHSCREEN_W90X900) += w90p910_ts.o
obj-$(CONFIG_TOUCHSCREEN_SX8654) += sx8654.o
obj-$(CONFIG_TOUCHSCREEN_TPS6507X) += tps6507x-ts.o
obj-$(CONFIG_TOUCHSCREEN_ZFORCE) += zforce_ts.o
obj-$(CONFIG_TOUCHSCREEN_COLIBRI_VF50) += colibri-vf50-ts.o

View File

@ -0,0 +1,386 @@
/*
* Toradex Colibri VF50 Touchscreen driver
*
* Copyright 2015 Toradex AG
*
* Originally authored by Stefan Agner for 3.0 kernel
*
* 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/delay.h>
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/iio/consumer.h>
#include <linux/iio/types.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pinctrl/consumer.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/types.h>
#define DRIVER_NAME "colibri-vf50-ts"
#define DRV_VERSION "1.0"
#define VF_ADC_MAX ((1 << 12) - 1)
#define COLI_TOUCH_MIN_DELAY_US 1000
#define COLI_TOUCH_MAX_DELAY_US 2000
#define COLI_PULLUP_MIN_DELAY_US 10000
#define COLI_PULLUP_MAX_DELAY_US 11000
#define COLI_TOUCH_NO_OF_AVGS 5
#define COLI_TOUCH_REQ_ADC_CHAN 4
struct vf50_touch_device {
struct platform_device *pdev;
struct input_dev *ts_input;
struct iio_channel *channels;
struct gpio_desc *gpio_xp;
struct gpio_desc *gpio_xm;
struct gpio_desc *gpio_yp;
struct gpio_desc *gpio_ym;
int pen_irq;
int min_pressure;
bool stop_touchscreen;
};
/*
* Enables given plates and measures touch parameters using ADC
*/
static int adc_ts_measure(struct iio_channel *channel,
struct gpio_desc *plate_p, struct gpio_desc *plate_m)
{
int i, value = 0, val = 0;
int error;
gpiod_set_value(plate_p, 1);
gpiod_set_value(plate_m, 1);
usleep_range(COLI_TOUCH_MIN_DELAY_US, COLI_TOUCH_MAX_DELAY_US);
for (i = 0; i < COLI_TOUCH_NO_OF_AVGS; i++) {
error = iio_read_channel_raw(channel, &val);
if (error < 0) {
value = error;
goto error_iio_read;
}
value += val;
}
value /= COLI_TOUCH_NO_OF_AVGS;
error_iio_read:
gpiod_set_value(plate_p, 0);
gpiod_set_value(plate_m, 0);
return value;
}
/*
* Enable touch detection using falling edge detection on XM
*/
static void vf50_ts_enable_touch_detection(struct vf50_touch_device *vf50_ts)
{
/* Enable plate YM (needs to be strong GND, high active) */
gpiod_set_value(vf50_ts->gpio_ym, 1);
/*
* Let the platform mux to idle state in order to enable
* Pull-Up on GPIO
*/
pinctrl_pm_select_idle_state(&vf50_ts->pdev->dev);
/* Wait for the pull-up to be stable on high */
usleep_range(COLI_PULLUP_MIN_DELAY_US, COLI_PULLUP_MAX_DELAY_US);
}
/*
* ADC touch screen sampling bottom half irq handler
*/
static irqreturn_t vf50_ts_irq_bh(int irq, void *private)
{
struct vf50_touch_device *vf50_ts = private;
struct device *dev = &vf50_ts->pdev->dev;
int val_x, val_y, val_z1, val_z2, val_p = 0;
bool discard_val_on_start = true;
/* Disable the touch detection plates */
gpiod_set_value(vf50_ts->gpio_ym, 0);
/* Let the platform mux to default state in order to mux as ADC */
pinctrl_pm_select_default_state(dev);
while (!vf50_ts->stop_touchscreen) {
/* X-Direction */
val_x = adc_ts_measure(&vf50_ts->channels[0],
vf50_ts->gpio_xp, vf50_ts->gpio_xm);
if (val_x < 0)
break;
/* Y-Direction */
val_y = adc_ts_measure(&vf50_ts->channels[1],
vf50_ts->gpio_yp, vf50_ts->gpio_ym);
if (val_y < 0)
break;
/*
* Touch pressure
* Measure on XP/YM
*/
val_z1 = adc_ts_measure(&vf50_ts->channels[2],
vf50_ts->gpio_yp, vf50_ts->gpio_xm);
if (val_z1 < 0)
break;
val_z2 = adc_ts_measure(&vf50_ts->channels[3],
vf50_ts->gpio_yp, vf50_ts->gpio_xm);
if (val_z2 < 0)
break;
/* Validate signal (avoid calculation using noise) */
if (val_z1 > 64 && val_x > 64) {
/*
* Calculate resistance between the plates
* lower resistance means higher pressure
*/
int r_x = (1000 * val_x) / VF_ADC_MAX;
val_p = (r_x * val_z2) / val_z1 - r_x;
} else {
val_p = 2000;
}
val_p = 2000 - val_p;
dev_dbg(dev,
"Measured values: x: %d, y: %d, z1: %d, z2: %d, p: %d\n",
val_x, val_y, val_z1, val_z2, val_p);
/*
* If touch pressure is too low, stop measuring and reenable
* touch detection
*/
if (val_p < vf50_ts->min_pressure || val_p > 2000)
break;
/*
* The pressure may not be enough for the first x and the
* second y measurement, but, the pressure is ok when the
* driver is doing the third and fourth measurement. To
* take care of this, we drop the first measurement always.
*/
if (discard_val_on_start) {
discard_val_on_start = false;
} else {
/*
* Report touch position and sleep for
* the next measurement.
*/
input_report_abs(vf50_ts->ts_input,
ABS_X, VF_ADC_MAX - val_x);
input_report_abs(vf50_ts->ts_input,
ABS_Y, VF_ADC_MAX - val_y);
input_report_abs(vf50_ts->ts_input,
ABS_PRESSURE, val_p);
input_report_key(vf50_ts->ts_input, BTN_TOUCH, 1);
input_sync(vf50_ts->ts_input);
}
usleep_range(COLI_PULLUP_MIN_DELAY_US,
COLI_PULLUP_MAX_DELAY_US);
}
/* Report no more touch, re-enable touch detection */
input_report_abs(vf50_ts->ts_input, ABS_PRESSURE, 0);
input_report_key(vf50_ts->ts_input, BTN_TOUCH, 0);
input_sync(vf50_ts->ts_input);
vf50_ts_enable_touch_detection(vf50_ts);
return IRQ_HANDLED;
}
static int vf50_ts_open(struct input_dev *dev_input)
{
struct vf50_touch_device *touchdev = input_get_drvdata(dev_input);
struct device *dev = &touchdev->pdev->dev;
dev_dbg(dev, "Input device %s opened, starting touch detection\n",
dev_input->name);
touchdev->stop_touchscreen = false;
/* Mux detection before request IRQ, wait for pull-up to settle */
vf50_ts_enable_touch_detection(touchdev);
return 0;
}
static void vf50_ts_close(struct input_dev *dev_input)
{
struct vf50_touch_device *touchdev = input_get_drvdata(dev_input);
struct device *dev = &touchdev->pdev->dev;
touchdev->stop_touchscreen = true;
/* Make sure IRQ is not running past close */
mb();
synchronize_irq(touchdev->pen_irq);
gpiod_set_value(touchdev->gpio_ym, 0);
pinctrl_pm_select_default_state(dev);
dev_dbg(dev, "Input device %s closed, disable touch detection\n",
dev_input->name);
}
static int vf50_ts_get_gpiod(struct device *dev, struct gpio_desc **gpio_d,
const char *con_id, enum gpiod_flags flags)
{
int error;
*gpio_d = devm_gpiod_get(dev, con_id, flags);
if (IS_ERR(*gpio_d)) {
error = PTR_ERR(*gpio_d);
dev_err(dev, "Could not get gpio_%s %d\n", con_id, error);
return error;
}
return 0;
}
static void vf50_ts_channel_release(void *data)
{
struct iio_channel *channels = data;
iio_channel_release_all(channels);
}
static int vf50_ts_probe(struct platform_device *pdev)
{
struct input_dev *input;
struct iio_channel *channels;
struct device *dev = &pdev->dev;
struct vf50_touch_device *touchdev;
int num_adc_channels;
int error;
channels = iio_channel_get_all(dev);
if (IS_ERR(channels))
return PTR_ERR(channels);
error = devm_add_action(dev, vf50_ts_channel_release, channels);
if (error) {
iio_channel_release_all(channels);
dev_err(dev, "Failed to register iio channel release action");
return error;
}
num_adc_channels = 0;
while (channels[num_adc_channels].indio_dev)
num_adc_channels++;
if (num_adc_channels != COLI_TOUCH_REQ_ADC_CHAN) {
dev_err(dev, "Inadequate ADC channels specified\n");
return -EINVAL;
}
touchdev = devm_kzalloc(dev, sizeof(*touchdev), GFP_KERNEL);
if (!touchdev)
return -ENOMEM;
touchdev->pdev = pdev;
touchdev->channels = channels;
error = of_property_read_u32(dev->of_node, "vf50-ts-min-pressure",
&touchdev->min_pressure);
if (error)
return error;
input = devm_input_allocate_device(dev);
if (!input) {
dev_err(dev, "Failed to allocate TS input device\n");
return -ENOMEM;
}
platform_set_drvdata(pdev, touchdev);
input->name = DRIVER_NAME;
input->id.bustype = BUS_HOST;
input->dev.parent = dev;
input->open = vf50_ts_open;
input->close = vf50_ts_close;
input_set_capability(input, EV_KEY, BTN_TOUCH);
input_set_abs_params(input, ABS_X, 0, VF_ADC_MAX, 0, 0);
input_set_abs_params(input, ABS_Y, 0, VF_ADC_MAX, 0, 0);
input_set_abs_params(input, ABS_PRESSURE, 0, VF_ADC_MAX, 0, 0);
touchdev->ts_input = input;
input_set_drvdata(input, touchdev);
error = input_register_device(input);
if (error) {
dev_err(dev, "Failed to register input device\n");
return error;
}
error = vf50_ts_get_gpiod(dev, &touchdev->gpio_xp, "xp", GPIOD_OUT_LOW);
if (error)
return error;
error = vf50_ts_get_gpiod(dev, &touchdev->gpio_xm,
"xm", GPIOD_OUT_LOW);
if (error)
return error;
error = vf50_ts_get_gpiod(dev, &touchdev->gpio_yp, "yp", GPIOD_OUT_LOW);
if (error)
return error;
error = vf50_ts_get_gpiod(dev, &touchdev->gpio_ym, "ym", GPIOD_OUT_LOW);
if (error)
return error;
touchdev->pen_irq = platform_get_irq(pdev, 0);
if (touchdev->pen_irq < 0)
return touchdev->pen_irq;
error = devm_request_threaded_irq(dev, touchdev->pen_irq,
NULL, vf50_ts_irq_bh, IRQF_ONESHOT,
"vf50 touch", touchdev);
if (error) {
dev_err(dev, "Failed to request IRQ %d: %d\n",
touchdev->pen_irq, error);
return error;
}
return 0;
}
static const struct of_device_id vf50_touch_of_match[] = {
{ .compatible = "toradex,vf50-touchscreen", },
{ }
};
MODULE_DEVICE_TABLE(of, vf50_touch_of_match);
static struct platform_driver vf50_touch_driver = {
.driver = {
.name = "toradex,vf50_touchctrl",
.of_match_table = vf50_touch_of_match,
},
.probe = vf50_ts_probe,
};
module_platform_driver(vf50_touch_driver);
MODULE_AUTHOR("Sanchayan Maity");
MODULE_DESCRIPTION("Colibri VF50 Touchscreen driver");
MODULE_LICENSE("GPL");
MODULE_VERSION(DRV_VERSION);

View File

@ -86,4 +86,3 @@ module_i2c_driver(cyttsp4_i2c_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Cypress TrueTouch(R) Standard Product (TTSP) I2C driver");
MODULE_AUTHOR("Cypress");
MODULE_ALIAS("i2c:cyttsp4");

View File

@ -86,4 +86,3 @@ module_i2c_driver(cyttsp_i2c_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Cypress TrueTouch(R) Standard Product (TTSP) I2C driver");
MODULE_AUTHOR("Cypress");
MODULE_ALIAS("i2c:cyttsp");

View File

@ -102,7 +102,7 @@
#define ELAN_FW_PAGESIZE 132
/* calibration timeout definition */
#define ELAN_CALI_TIMEOUT_MSEC 10000
#define ELAN_CALI_TIMEOUT_MSEC 12000
#define ELAN_POWERON_DELAY_USEC 500
#define ELAN_RESET_DELAY_MSEC 20

View File

@ -0,0 +1,523 @@
/*
* Freescale i.MX6UL touchscreen controller driver
*
* Copyright (C) 2015 Freescale Semiconductor, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/gpio/consumer.h>
#include <linux/input.h>
#include <linux/slab.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/of.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/io.h>
/* ADC configuration registers field define */
#define ADC_AIEN (0x1 << 7)
#define ADC_CONV_DISABLE 0x1F
#define ADC_CAL (0x1 << 7)
#define ADC_CALF 0x2
#define ADC_12BIT_MODE (0x2 << 2)
#define ADC_IPG_CLK 0x00
#define ADC_CLK_DIV_8 (0x03 << 5)
#define ADC_SHORT_SAMPLE_MODE (0x0 << 4)
#define ADC_HARDWARE_TRIGGER (0x1 << 13)
#define SELECT_CHANNEL_4 0x04
#define SELECT_CHANNEL_1 0x01
#define DISABLE_CONVERSION_INT (0x0 << 7)
/* ADC registers */
#define REG_ADC_HC0 0x00
#define REG_ADC_HC1 0x04
#define REG_ADC_HC2 0x08
#define REG_ADC_HC3 0x0C
#define REG_ADC_HC4 0x10
#define REG_ADC_HS 0x14
#define REG_ADC_R0 0x18
#define REG_ADC_CFG 0x2C
#define REG_ADC_GC 0x30
#define REG_ADC_GS 0x34
#define ADC_TIMEOUT msecs_to_jiffies(100)
/* TSC registers */
#define REG_TSC_BASIC_SETING 0x00
#define REG_TSC_PRE_CHARGE_TIME 0x10
#define REG_TSC_FLOW_CONTROL 0x20
#define REG_TSC_MEASURE_VALUE 0x30
#define REG_TSC_INT_EN 0x40
#define REG_TSC_INT_SIG_EN 0x50
#define REG_TSC_INT_STATUS 0x60
#define REG_TSC_DEBUG_MODE 0x70
#define REG_TSC_DEBUG_MODE2 0x80
/* TSC configuration registers field define */
#define DETECT_4_WIRE_MODE (0x0 << 4)
#define AUTO_MEASURE 0x1
#define MEASURE_SIGNAL 0x1
#define DETECT_SIGNAL (0x1 << 4)
#define VALID_SIGNAL (0x1 << 8)
#define MEASURE_INT_EN 0x1
#define MEASURE_SIG_EN 0x1
#define VALID_SIG_EN (0x1 << 8)
#define DE_GLITCH_2 (0x2 << 29)
#define START_SENSE (0x1 << 12)
#define TSC_DISABLE (0x1 << 16)
#define DETECT_MODE 0x2
struct imx6ul_tsc {
struct device *dev;
struct input_dev *input;
void __iomem *tsc_regs;
void __iomem *adc_regs;
struct clk *tsc_clk;
struct clk *adc_clk;
struct gpio_desc *xnur_gpio;
int measure_delay_time;
int pre_charge_time;
struct completion completion;
};
/*
* TSC module need ADC to get the measure value. So
* before config TSC, we should initialize ADC module.
*/
static void imx6ul_adc_init(struct imx6ul_tsc *tsc)
{
int adc_hc = 0;
int adc_gc;
int adc_gs;
int adc_cfg;
int timeout;
reinit_completion(&tsc->completion);
adc_cfg = readl(tsc->adc_regs + REG_ADC_CFG);
adc_cfg |= ADC_12BIT_MODE | ADC_IPG_CLK;
adc_cfg |= ADC_CLK_DIV_8 | ADC_SHORT_SAMPLE_MODE;
adc_cfg &= ~ADC_HARDWARE_TRIGGER;
writel(adc_cfg, tsc->adc_regs + REG_ADC_CFG);
/* enable calibration interrupt */
adc_hc |= ADC_AIEN;
adc_hc |= ADC_CONV_DISABLE;
writel(adc_hc, tsc->adc_regs + REG_ADC_HC0);
/* start ADC calibration */
adc_gc = readl(tsc->adc_regs + REG_ADC_GC);
adc_gc |= ADC_CAL;
writel(adc_gc, tsc->adc_regs + REG_ADC_GC);
timeout = wait_for_completion_timeout
(&tsc->completion, ADC_TIMEOUT);
if (timeout == 0)
dev_err(tsc->dev, "Timeout for adc calibration\n");
adc_gs = readl(tsc->adc_regs + REG_ADC_GS);
if (adc_gs & ADC_CALF)
dev_err(tsc->dev, "ADC calibration failed\n");
/* TSC need the ADC work in hardware trigger */
adc_cfg = readl(tsc->adc_regs + REG_ADC_CFG);
adc_cfg |= ADC_HARDWARE_TRIGGER;
writel(adc_cfg, tsc->adc_regs + REG_ADC_CFG);
}
/*
* This is a TSC workaround. Currently TSC misconnect two
* ADC channels, this function remap channel configure for
* hardware trigger.
*/
static void imx6ul_tsc_channel_config(struct imx6ul_tsc *tsc)
{
int adc_hc0, adc_hc1, adc_hc2, adc_hc3, adc_hc4;
adc_hc0 = DISABLE_CONVERSION_INT;
writel(adc_hc0, tsc->adc_regs + REG_ADC_HC0);
adc_hc1 = DISABLE_CONVERSION_INT | SELECT_CHANNEL_4;
writel(adc_hc1, tsc->adc_regs + REG_ADC_HC1);
adc_hc2 = DISABLE_CONVERSION_INT;
writel(adc_hc2, tsc->adc_regs + REG_ADC_HC2);
adc_hc3 = DISABLE_CONVERSION_INT | SELECT_CHANNEL_1;
writel(adc_hc3, tsc->adc_regs + REG_ADC_HC3);
adc_hc4 = DISABLE_CONVERSION_INT;
writel(adc_hc4, tsc->adc_regs + REG_ADC_HC4);
}
/*
* TSC setting, confige the pre-charge time and measure delay time.
* different touch screen may need different pre-charge time and
* measure delay time.
*/
static void imx6ul_tsc_set(struct imx6ul_tsc *tsc)
{
int basic_setting = 0;
int start;
basic_setting |= tsc->measure_delay_time << 8;
basic_setting |= DETECT_4_WIRE_MODE | AUTO_MEASURE;
writel(basic_setting, tsc->tsc_regs + REG_TSC_BASIC_SETING);
writel(DE_GLITCH_2, tsc->tsc_regs + REG_TSC_DEBUG_MODE2);
writel(tsc->pre_charge_time, tsc->tsc_regs + REG_TSC_PRE_CHARGE_TIME);
writel(MEASURE_INT_EN, tsc->tsc_regs + REG_TSC_INT_EN);
writel(MEASURE_SIG_EN | VALID_SIG_EN,
tsc->tsc_regs + REG_TSC_INT_SIG_EN);
/* start sense detection */
start = readl(tsc->tsc_regs + REG_TSC_FLOW_CONTROL);
start |= START_SENSE;
start &= ~TSC_DISABLE;
writel(start, tsc->tsc_regs + REG_TSC_FLOW_CONTROL);
}
static void imx6ul_tsc_init(struct imx6ul_tsc *tsc)
{
imx6ul_adc_init(tsc);
imx6ul_tsc_channel_config(tsc);
imx6ul_tsc_set(tsc);
}
static void imx6ul_tsc_disable(struct imx6ul_tsc *tsc)
{
int tsc_flow;
int adc_cfg;
/* TSC controller enters to idle status */
tsc_flow = readl(tsc->tsc_regs + REG_TSC_FLOW_CONTROL);
tsc_flow |= TSC_DISABLE;
writel(tsc_flow, tsc->tsc_regs + REG_TSC_FLOW_CONTROL);
/* ADC controller enters to stop mode */
adc_cfg = readl(tsc->adc_regs + REG_ADC_HC0);
adc_cfg |= ADC_CONV_DISABLE;
writel(adc_cfg, tsc->adc_regs + REG_ADC_HC0);
}
/* Delay some time (max 2ms), wait the pre-charge done. */
static bool tsc_wait_detect_mode(struct imx6ul_tsc *tsc)
{
unsigned long timeout = jiffies + msecs_to_jiffies(2);
int state_machine;
int debug_mode2;
do {
if (time_after(jiffies, timeout))
return false;
usleep_range(200, 400);
debug_mode2 = readl(tsc->tsc_regs + REG_TSC_DEBUG_MODE2);
state_machine = (debug_mode2 >> 20) & 0x7;
} while (state_machine != DETECT_MODE);
usleep_range(200, 400);
return true;
}
static irqreturn_t tsc_irq_fn(int irq, void *dev_id)
{
struct imx6ul_tsc *tsc = dev_id;
int status;
int value;
int x, y;
int start;
status = readl(tsc->tsc_regs + REG_TSC_INT_STATUS);
/* write 1 to clear the bit measure-signal */
writel(MEASURE_SIGNAL | DETECT_SIGNAL,
tsc->tsc_regs + REG_TSC_INT_STATUS);
/* It's a HW self-clean bit. Set this bit and start sense detection */
start = readl(tsc->tsc_regs + REG_TSC_FLOW_CONTROL);
start |= START_SENSE;
writel(start, tsc->tsc_regs + REG_TSC_FLOW_CONTROL);
if (status & MEASURE_SIGNAL) {
value = readl(tsc->tsc_regs + REG_TSC_MEASURE_VALUE);
x = (value >> 16) & 0x0fff;
y = value & 0x0fff;
/*
* In detect mode, we can get the xnur gpio value,
* otherwise assume contact is stiull active.
*/
if (!tsc_wait_detect_mode(tsc) ||
gpiod_get_value_cansleep(tsc->xnur_gpio)) {
input_report_key(tsc->input, BTN_TOUCH, 1);
input_report_abs(tsc->input, ABS_X, x);
input_report_abs(tsc->input, ABS_Y, y);
} else {
input_report_key(tsc->input, BTN_TOUCH, 0);
}
input_sync(tsc->input);
}
return IRQ_HANDLED;
}
static irqreturn_t adc_irq_fn(int irq, void *dev_id)
{
struct imx6ul_tsc *tsc = dev_id;
int coco;
int value;
coco = readl(tsc->adc_regs + REG_ADC_HS);
if (coco & 0x01) {
value = readl(tsc->adc_regs + REG_ADC_R0);
complete(&tsc->completion);
}
return IRQ_HANDLED;
}
static int imx6ul_tsc_open(struct input_dev *input_dev)
{
struct imx6ul_tsc *tsc = input_get_drvdata(input_dev);
int err;
err = clk_prepare_enable(tsc->adc_clk);
if (err) {
dev_err(tsc->dev,
"Could not prepare or enable the adc clock: %d\n",
err);
return err;
}
err = clk_prepare_enable(tsc->tsc_clk);
if (err) {
dev_err(tsc->dev,
"Could not prepare or enable the tsc clock: %d\n",
err);
clk_disable_unprepare(tsc->adc_clk);
return err;
}
imx6ul_tsc_init(tsc);
return 0;
}
static void imx6ul_tsc_close(struct input_dev *input_dev)
{
struct imx6ul_tsc *tsc = input_get_drvdata(input_dev);
imx6ul_tsc_disable(tsc);
clk_disable_unprepare(tsc->tsc_clk);
clk_disable_unprepare(tsc->adc_clk);
}
static int imx6ul_tsc_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct imx6ul_tsc *tsc;
struct input_dev *input_dev;
struct resource *tsc_mem;
struct resource *adc_mem;
int err;
int tsc_irq;
int adc_irq;
tsc = devm_kzalloc(&pdev->dev, sizeof(struct imx6ul_tsc), GFP_KERNEL);
if (!tsc)
return -ENOMEM;
input_dev = devm_input_allocate_device(&pdev->dev);
if (!input_dev)
return -ENOMEM;
input_dev->name = "iMX6UL TouchScreen Controller";
input_dev->id.bustype = BUS_HOST;
input_dev->open = imx6ul_tsc_open;
input_dev->close = imx6ul_tsc_close;
input_set_capability(input_dev, EV_KEY, BTN_TOUCH);
input_set_abs_params(input_dev, ABS_X, 0, 0xFFF, 0, 0);
input_set_abs_params(input_dev, ABS_Y, 0, 0xFFF, 0, 0);
input_set_drvdata(input_dev, tsc);
tsc->dev = &pdev->dev;
tsc->input = input_dev;
init_completion(&tsc->completion);
tsc->xnur_gpio = devm_gpiod_get(&pdev->dev, "xnur", GPIOD_IN);
if (IS_ERR(tsc->xnur_gpio)) {
err = PTR_ERR(tsc->xnur_gpio);
dev_err(&pdev->dev,
"failed to request GPIO tsc_X- (xnur): %d\n", err);
return err;
}
tsc_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
tsc->tsc_regs = devm_ioremap_resource(&pdev->dev, tsc_mem);
if (IS_ERR(tsc->tsc_regs)) {
err = PTR_ERR(tsc->tsc_regs);
dev_err(&pdev->dev, "failed to remap tsc memory: %d\n", err);
return err;
}
adc_mem = platform_get_resource(pdev, IORESOURCE_MEM, 1);
tsc->adc_regs = devm_ioremap_resource(&pdev->dev, adc_mem);
if (IS_ERR(tsc->adc_regs)) {
err = PTR_ERR(tsc->adc_regs);
dev_err(&pdev->dev, "failed to remap adc memory: %d\n", err);
return err;
}
tsc->tsc_clk = devm_clk_get(&pdev->dev, "tsc");
if (IS_ERR(tsc->tsc_clk)) {
err = PTR_ERR(tsc->tsc_clk);
dev_err(&pdev->dev, "failed getting tsc clock: %d\n", err);
return err;
}
tsc->adc_clk = devm_clk_get(&pdev->dev, "adc");
if (IS_ERR(tsc->adc_clk)) {
err = PTR_ERR(tsc->adc_clk);
dev_err(&pdev->dev, "failed getting adc clock: %d\n", err);
return err;
}
tsc_irq = platform_get_irq(pdev, 0);
if (tsc_irq < 0) {
dev_err(&pdev->dev, "no tsc irq resource?\n");
return tsc_irq;
}
adc_irq = platform_get_irq(pdev, 1);
if (adc_irq <= 0) {
dev_err(&pdev->dev, "no adc irq resource?\n");
return adc_irq;
}
err = devm_request_threaded_irq(tsc->dev, tsc_irq,
NULL, tsc_irq_fn, IRQF_ONESHOT,
dev_name(&pdev->dev), tsc);
if (err) {
dev_err(&pdev->dev,
"failed requesting tsc irq %d: %d\n",
tsc_irq, err);
return err;
}
err = devm_request_irq(tsc->dev, adc_irq, adc_irq_fn, 0,
dev_name(&pdev->dev), tsc);
if (err) {
dev_err(&pdev->dev,
"failed requesting adc irq %d: %d\n",
adc_irq, err);
return err;
}
err = of_property_read_u32(np, "measure-delay-time",
&tsc->measure_delay_time);
if (err)
tsc->measure_delay_time = 0xffff;
err = of_property_read_u32(np, "pre-charge-time",
&tsc->pre_charge_time);
if (err)
tsc->pre_charge_time = 0xfff;
err = input_register_device(tsc->input);
if (err) {
dev_err(&pdev->dev,
"failed to register input device: %d\n", err);
return err;
}
platform_set_drvdata(pdev, tsc);
return 0;
}
static int __maybe_unused imx6ul_tsc_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct imx6ul_tsc *tsc = platform_get_drvdata(pdev);
struct input_dev *input_dev = tsc->input;
mutex_lock(&input_dev->mutex);
if (input_dev->users) {
imx6ul_tsc_disable(tsc);
clk_disable_unprepare(tsc->tsc_clk);
clk_disable_unprepare(tsc->adc_clk);
}
mutex_unlock(&input_dev->mutex);
return 0;
}
static int __maybe_unused imx6ul_tsc_resume(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct imx6ul_tsc *tsc = platform_get_drvdata(pdev);
struct input_dev *input_dev = tsc->input;
int retval = 0;
mutex_lock(&input_dev->mutex);
if (input_dev->users) {
retval = clk_prepare_enable(tsc->adc_clk);
if (retval)
goto out;
retval = clk_prepare_enable(tsc->tsc_clk);
if (retval) {
clk_disable_unprepare(tsc->adc_clk);
goto out;
}
imx6ul_tsc_init(tsc);
}
out:
mutex_unlock(&input_dev->mutex);
return retval;
}
static SIMPLE_DEV_PM_OPS(imx6ul_tsc_pm_ops,
imx6ul_tsc_suspend, imx6ul_tsc_resume);
static const struct of_device_id imx6ul_tsc_match[] = {
{ .compatible = "fsl,imx6ul-tsc", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx6ul_tsc_match);
static struct platform_driver imx6ul_tsc_driver = {
.driver = {
.name = "imx6ul-tsc",
.of_match_table = imx6ul_tsc_match,
.pm = &imx6ul_tsc_pm_ops,
},
.probe = imx6ul_tsc_probe,
};
module_platform_driver(imx6ul_tsc_driver);
MODULE_AUTHOR("Haibo Chen <haibo.chen@freescale.com>");
MODULE_DESCRIPTION("Freescale i.MX6UL Touchscreen controller driver");
MODULE_LICENSE("GPL v2");