Commit aa6a0f45 authored by Andrzej Zaborowski's avatar Andrzej Zaborowski Committed by Tony Lindgren

TSC210x driver using the SPI framework.

This is a rework of the TSC2102 driver from linux-omap-2.6 taking TSC2101
into account with the goal of using only a single driver.
Signed-off-by: default avatarTony Lindgren <tony@atomide.com>
parent 1f77fbae
......@@ -684,6 +684,18 @@ config SENSORS_APPLESMC
Say Y here if you have an applicable laptop and want to experience
the awesome power of applesmc.
config SENSORS_TSC210X
tristate "TI TSC2101/2102 battery & temperature sensors"
depends on HWMON && SPI_MASTER
select SPI_TSC210X
help
Say Y if your board has a TSC210X chip and you want to
have its battery state, auxiliary input and/or temperature
sensors exported through hwmon.
This driver can also be built as a module. In this case
the module will be called tsc210x_sensors.
config HWMON_DEBUG_CHIP
bool "Hardware Monitoring Chip debugging messages"
default n
......
......@@ -62,6 +62,7 @@ obj-$(CONFIG_SENSORS_VT1211) += vt1211.o
obj-$(CONFIG_SENSORS_VT8231) += vt8231.o
obj-$(CONFIG_SENSORS_W83627EHF) += w83627ehf.o
obj-$(CONFIG_SENSORS_W83L785TS) += w83l785ts.o
obj-$(CONFIG_SENSORS_TSC210X) += tsc210x_sensors.o
ifeq ($(CONFIG_HWMON_DEBUG_CHIP),y)
EXTRA_CFLAGS += -DDEBUG
......
/*
* drivers/hwmon/tsc210x_sensors.c
*
* hwmon interface for TSC210X sensors
*
* Copyright (c) 2005-2007 Andrzej Zaborowski <balrog@zabor.org>
*
* This package 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.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this package; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <linux/init.h>
#include <linux/err.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#include <linux/autoconf.h>
#ifdef CONFIG_APM
# include <linux/apm-emulation.h>
#endif
#include <linux/spi/tsc210x.h>
struct tsc210x_hwmon {
int bat[2], aux[2], temp[2];
struct class_device *dev;
struct tsc210x_config *pdata;
#ifdef CONFIG_APM
spinlock_t apm_lock;
#endif
};
#ifdef CONFIG_APM
# define apm_lock() spin_lock(&hwmon->apm_lock)
# define apm_unlock() spin_unlock(&hwmon->apm_lock)
#else
# define apm_lock()
# define apm_unlock()
#endif
static void tsc210x_ports(struct tsc210x_hwmon *hwmon, int bat[], int aux[])
{
apm_lock();
hwmon->bat[0] = bat[0];
hwmon->bat[1] = bat[1];
hwmon->aux[0] = aux[0];
hwmon->aux[1] = aux[1];
apm_unlock();
}
static void tsc210x_temp1(struct tsc210x_hwmon *hwmon, int temp)
{
apm_lock();
hwmon->temp[0] = temp;
apm_unlock();
}
static void tsc210x_temp2(struct tsc210x_hwmon *hwmon, int temp)
{
apm_lock();
hwmon->temp[1] = temp;
apm_unlock();
}
#define TSC210X_INPUT(devname, field) \
static ssize_t tsc_show_ ## devname(struct device *dev, \
struct device_attribute *devattr, char *buf) \
{ \
struct tsc210x_hwmon *hwmon = (struct tsc210x_hwmon *) \
platform_get_drvdata(to_platform_device(dev)); \
return sprintf(buf, "%i\n", hwmon->field); \
} \
static DEVICE_ATTR(devname ## _input, S_IRUGO, tsc_show_ ## devname, NULL);
TSC210X_INPUT(in0, bat[0])
TSC210X_INPUT(in1, bat[1])
TSC210X_INPUT(in2, aux[0])
TSC210X_INPUT(in3, aux[1])
TSC210X_INPUT(in4, temp[0])
TSC210X_INPUT(in5, temp[1])
static ssize_t tsc_show_temp1(struct device *dev,
struct device_attribute *devattr, char *buf)
{
struct tsc210x_hwmon *hwmon = (struct tsc210x_hwmon *)
platform_get_drvdata(to_platform_device(dev));
int t1, t2;
int diff, value;
t1 = hwmon->temp[0];
t2 = hwmon->temp[1];
/*
* Use method #2 (differential) to calculate current temperature.
* The difference between TEMP2 and TEMP1 input values is
* multiplied by a constant to obtain current temperature.
* To find this constant we use the values measured at 25 C as
* thermometer calibration data.
*
* 298150 is 25 degrees Celcius represented in Kelvins and
* multiplied by 1000 for fixed point precision (273.15 + 25).
* 273150 is zero degrees Celcius.
*/
diff = hwmon->pdata->temp_at25c[1] - hwmon->pdata->temp_at25c[0];
BUG_ON(diff == 0);
value = (t2 - t1) * 298150 / diff; /* This is in Kelvins now */
value -= 273150; /* Celcius millidegree */
return sprintf(buf, "%i\n", value);
}
static DEVICE_ATTR(temp1_input, S_IRUGO, tsc_show_temp1, NULL);
#ifdef CONFIG_APM
static struct tsc210x_hwmon *apm_hwmon;
static void tsc210x_get_power_status(struct apm_power_info *info)
{
struct tsc210x_hwmon *hwmon = apm_hwmon;
apm_lock();
hwmon->pdata->apm_report(info, hwmon->bat);
apm_unlock();
}
#endif
static int tsc210x_hwmon_probe(struct platform_device *pdev)
{
struct tsc210x_hwmon *hwmon;
struct tsc210x_config *pdata = pdev->dev.platform_data;
int status = 0;
hwmon = (struct tsc210x_hwmon *)
kzalloc(sizeof(struct tsc210x_hwmon), GFP_KERNEL);
if (!hwmon) {
printk(KERN_ERR "%s: allocation failed\n", __FUNCTION__);
return -ENOMEM;
}
hwmon->dev = hwmon_device_register(&pdev->dev);
if (IS_ERR(hwmon->dev)) {
kfree(hwmon);
printk(KERN_ERR "%s: Class registration failed\n",
__FUNCTION__);
return PTR_ERR(hwmon->dev);
}
hwmon->pdata = pdata;
#ifdef CONFIG_APM
spin_lock_init(&hwmon->apm_lock);
if (pdata->apm_report) {
apm_hwmon = hwmon;
apm_get_power_status = tsc210x_get_power_status;
}
#endif
platform_set_drvdata(pdev, hwmon);
if (pdata->monitor & (TSC_BAT1 | TSC_BAT2 | TSC_AUX1 | TSC_AUX2))
status |= tsc210x_ports_cb(pdev->dev.parent,
(tsc210x_ports_t) tsc210x_ports, hwmon);
if (pdata->monitor & TSC_TEMP) {
status |= tsc210x_temp1_cb(pdev->dev.parent,
(tsc210x_temp_t) tsc210x_temp1, hwmon);
status |= tsc210x_temp2_cb(pdev->dev.parent,
(tsc210x_temp_t) tsc210x_temp2, hwmon);
}
if (status) {
tsc210x_ports_cb(pdev->dev.parent, 0, 0);
tsc210x_temp1_cb(pdev->dev.parent, 0, 0);
tsc210x_temp2_cb(pdev->dev.parent, 0, 0);
platform_set_drvdata(pdev, 0);
#ifdef CONFIG_APM
if (pdata->apm_report)
apm_get_power_status = 0;
#endif
hwmon_device_unregister(hwmon->dev);
kfree(hwmon);
return status;
}
if (pdata->monitor & TSC_BAT1)
status |= device_create_file(&pdev->dev, &dev_attr_in0_input);
if (pdata->monitor & TSC_BAT2)
status |= device_create_file(&pdev->dev, &dev_attr_in1_input);
if (pdata->monitor & TSC_AUX1)
status |= device_create_file(&pdev->dev, &dev_attr_in2_input);
if (pdata->monitor & TSC_AUX2)
status |= device_create_file(&pdev->dev, &dev_attr_in3_input);
if (pdata->monitor & TSC_TEMP) {
status |= device_create_file(&pdev->dev, &dev_attr_in4_input);
status |= device_create_file(&pdev->dev, &dev_attr_in5_input);
status |= device_create_file(&pdev->dev, &dev_attr_temp1_input);
}
if (status) /* Not fatal */
printk(KERN_ERR "%s: Creating one or more "
"attribute files failed\n", __FUNCTION__);
return 0;
}
static int tsc210x_hwmon_remove(struct platform_device *pdev)
{
struct tsc210x_hwmon *dev = platform_get_drvdata(pdev);
tsc210x_ports_cb(pdev->dev.parent, 0, 0);
tsc210x_temp1_cb(pdev->dev.parent, 0, 0);
tsc210x_temp2_cb(pdev->dev.parent, 0, 0);
platform_set_drvdata(pdev, 0);
#ifdef CONFIG_APM
if (dev->pdata->apm_report)
apm_get_power_status = 0;
#endif
hwmon_device_unregister(dev->dev);
kfree(dev);
return 0;
}
static struct platform_driver tsc210x_hwmon_driver = {
.probe = tsc210x_hwmon_probe,
.remove = tsc210x_hwmon_remove,
/* Nothing to do on suspend/resume */
.driver = {
.name = "tsc210x-hwmon",
},
};
static int __init tsc210x_hwmon_init(void)
{
return platform_driver_register(&tsc210x_hwmon_driver);
}
static void __exit tsc210x_hwmon_exit(void)
{
platform_driver_unregister(&tsc210x_hwmon_driver);
}
module_init(tsc210x_hwmon_init);
module_exit(tsc210x_hwmon_exit);
MODULE_AUTHOR("Andrzej Zaborowski");
MODULE_DESCRIPTION("hwmon driver for TI TSC210x-connected sensors.");
MODULE_LICENSE("GPL");
......@@ -192,6 +192,20 @@ config TOUCHSCREEN_TSC2102
To compile this driver as a module, choose M here: the
module will be called tsc2102_ts.
config TOUCHSCREEN_TSC210X
tristate "TSC 2101/2102 based touchscreens"
depends on SPI_MASTER
select SPI_TSC210X
help
Say Y here if you have a touchscreen interface using a
TI TSC 210x controller, and your board-specific initialisation
code includes that in its table of SPI devices.
If unsure, say N (but it's safe to say "Y").
To compile this driver as a module, choose M here: the
module will be called tsc210x_ts.
config TOUCHSCREEN_TSC2301
tristate "TSC2301 touchscreen support"
depends on SPI_TSC2301
......
......@@ -20,4 +20,5 @@ obj-$(CONFIG_TOUCHSCREEN_TOUCHWIN) += touchwin.o
obj-$(CONFIG_TOUCHSCREEN_UCB1400) += ucb1400_ts.o
obj-$(CONFIG_TOUCHSCREEN_TSC2102) += tsc2102_ts.o
obj-$(CONFIG_TOUCHSCREEN_OMAP) += omap/
obj-$(CONFIG_TOUCHSCREEN_TSC210X) += tsc210x_ts.o
obj-$(CONFIG_TOUCHSCREEN_TSC2301) += tsc2301_ts.o
/*
* input/touchscreen/tsc210x_ts.c
*
* Touchscreen input device driver for the TSC 2101/2102 chips.
*
* Copyright (c) 2006-2007 Andrzej Zaborowski <balrog@zabor.org>
*
* This package 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.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this package; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/input.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/spi/tsc210x.h>
static void tsc210x_touch(struct input_dev *dev, int touching)
{
if (!touching) {
input_report_abs(dev, ABS_X, 0);
input_report_abs(dev, ABS_Y, 0);
input_report_abs(dev, ABS_PRESSURE, 0);
input_sync(dev);
}
input_report_key(dev, BTN_TOUCH, touching);
}
static void tsc210x_coords(struct input_dev *dev, int x, int y, int z1, int z2)
{
int p;
/* Calculate the touch resistance a la equation #1 */
if (z1 != 0)
p = x * (z2 - z1) / (z1 << 4);
else
p = 1;
input_report_abs(dev, ABS_X, x);
input_report_abs(dev, ABS_Y, y);
input_report_abs(dev, ABS_PRESSURE, p);
input_sync(dev);
}
static int tsc210x_ts_probe(struct platform_device *pdev)
{
int status;
struct input_dev *dev;
dev = input_allocate_device();
if (!dev)
return -ENOMEM;
status = tsc210x_touch_cb(pdev->dev.parent,
(tsc210x_touch_t) tsc210x_touch, dev);
if (status) {
input_free_device(dev);
return status;
}
status = tsc210x_coords_cb(pdev->dev.parent,
(tsc210x_coords_t) tsc210x_coords, dev);
if (status) {
tsc210x_touch_cb(pdev->dev.parent, 0, 0);
input_free_device(dev);
return status;
}
dev->name = "TSC210x Touchscreen";
dev->cdev.dev = &pdev->dev;
dev->evbit[0] = BIT(EV_KEY) | BIT(EV_ABS);
dev->keybit[LONG(BTN_TOUCH)] |= BIT(BTN_TOUCH);
dev->absbit[0] = BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_PRESSURE);
dev->phys = "tsc210x/input0";
dev->id.bustype = BUS_HOST;
dev->id.vendor = 0x0001;
dev->id.product = 0x2100;
dev->id.version = 0x0001;
status = input_register_device(dev);
if (status) {
tsc210x_coords_cb(pdev->dev.parent, 0, 0);
tsc210x_touch_cb(pdev->dev.parent, 0, 0);
input_free_device(dev);
return status;
}
platform_set_drvdata(pdev, dev);
printk(KERN_INFO "TSC210x touchscreen initialised\n");
return 0;
}
static int tsc210x_ts_remove(struct platform_device *pdev)
{
struct input_dev *dev = (struct input_dev *)
platform_get_drvdata(pdev);
tsc210x_touch_cb(pdev->dev.parent, 0, 0);
tsc210x_coords_cb(pdev->dev.parent, 0, 0);
platform_set_drvdata(pdev, 0);
input_unregister_device(dev);
input_free_device(dev);
return 0;
}
static struct platform_driver tsc210x_ts_driver = {
.probe = tsc210x_ts_probe,
.remove = tsc210x_ts_remove,
/* Nothing to do on suspend/resume */
.driver = {
.name = "tsc210x-ts",
.owner = THIS_MODULE,
},
};
static int __init tsc210x_ts_init(void)
{
int ret;
ret = platform_driver_register(&tsc210x_ts_driver);
if (ret)
return -ENODEV;
return 0;
}
static void __exit tsc210x_ts_exit(void)
{
platform_driver_unregister(&tsc210x_ts_driver);
}
module_init(tsc210x_ts_init);
module_exit(tsc210x_ts_exit);
MODULE_AUTHOR("Andrzej Zaborowski");
MODULE_DESCRIPTION("Touchscreen input driver for TI TSC2101/2102.");
MODULE_LICENSE("GPL");
......@@ -227,6 +227,18 @@ config SPI_TSC2102
Say Y here if you want support for the TSC2102 chip. It
will be needed for the touchscreen driver on some boards.
config SPI_TSC210X
depends on SPI_MASTER && EXPERIMENTAL
tristate "TSC2101/TSC2102 chips support"
help
Say Y here if you want support for the TSC210x chips. It
will be needed for the touchscreen driver on some boards.
Note that the device has to be present in the board's SPI
devices table for this driver to load. This driver doesn't
automatically enable touchscreen, sensors or audio
functionality - enable these in their respective menus.
config SPI_TSC2301
tristate "TSC2301 driver"
depends on SPI_MASTER
......
......@@ -35,6 +35,7 @@ obj-$(CONFIG_SPI_SPIDEV) += spidev.o
obj-$(CONFIG_SPI_TLE62X0) += tle62x0.o
obj-$(CONFIG_SPI_TSC2101) += tsc2101.o
obj-$(CONFIG_SPI_TSC2102) += tsc2102.o
obj-$(CONFIG_SPI_TSC210X) += tsc210x.o
obj-$(CONFIG_SPI_TSC2301) += tsc2301.o
tsc2301-objs := tsc2301-core.o
tsc2301-$(CONFIG_SPI_TSC2301_AUDIO) += tsc2301-mixer.o
......
/*
* drivers/spi/tsc210x.c
*
* TSC2101/2102 interface driver.
*
* Copyright (c) 2005-2007 Andrzej Zaborowski <balrog@zabor.org>
*
* This package 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.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this package; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/platform_device.h>
#include <linux/suspend.h>
#include <linux/interrupt.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/spinlock.h>
#include <linux/workqueue.h>
#include <linux/completion.h>
#include <linux/autoconf.h>
#include <linux/spi/spi.h>
#include <linux/spi/tsc210x.h>
/* Bit field definitions for chip registers */
/* Scan X, Y, Z1, Z2, chip controlled, 12-bit, 16 samples, 500 usec */
#define TSC210X_ADC_TS_CONTROL 0x8bf4
/* Scan BAT1, BAT2, AUX1, AUX2, 12-bit, 16 samples, 500 usec */
#define TSC210X_ADC_SCAN_CONTROL 0x2ff4
/* Scan TEMP1, 12-bit, 16 samples, 500 usec */
#define TSC210X_ADC_T1_CONTROL 0x2bf4
/* Scan TEMP2, 12-bit, 16 samples, 500 usec */
#define TSC210X_ADC_T2_CONTROL 0x33f4
/* PINT/DAV acts as DAV */
#define TSC210X_ADC_DAV 0x4000
/* Internal reference, 100 usec delay, 1.25 V reference */
#define TSC210X_ADC_INT_REF 0x0016
/* External reference, 100 usec delay, 1.25 V reference */
#define TSC210X_ADC_EXT_REF 0x0002
/* 84 usec precharge time, 32 usec sense time */
#define TSC210X_CONFIG_TIMES 0x0008
/* The reset sequence */
#define TSC210X_RESET 0xbb00
/* Pen Status bit */
#define TSC210X_ADC_PSTCM (1 << 15)
/* A/D Status bit */
#define TSC210X_ADC_ADST (1 << 14)
/* (At least) One of X, Y, Z1, Z2 contains data */
#define TSC210X_TS_DAV 0x0780
/* (At least) One of BAT1, BAT2, AUX1, AUX2 contains data */
#define TSC210X_PS_DAV 0x0078
/* TEMP1 contains data */
#define TSC210X_T1_DAV 0x0004
/* TEMP2 contains data */
#define TSC210X_T2_DAV 0x0002
#define TSC2101_DAC_ON 0x0000
#define TSC2101_DAC_OFF 0xe7fc
#define TSC2102_DAC_ON 0x3ba0
#define TSC2102_DAC_OFF 0xafa0
#define TSC210X_FS44K (1 << 13)
#define TSC210X_PLL1_OFF 0x0000
#define TSC210X_PLL1_44K 0x811c
#define TSC210X_PLL1_48K 0x8120
#define TSC210X_PLL2_44K (5462 << 2)
#define TSC210X_PLL2_48K (1920 << 2)
#define TSC210X_SLVMS (1 << 11)
#define TSC210X_DEEMPF (1 << 0)
#define TSC2102_BASSBC (1 << 1)
#define TSC210X_KEYCLICK_OFF 0x0000
#define CS_CHANGE(val) 0
struct tsc210x_spi_req {
struct spi_device *dev;
uint16_t command, data;
struct spi_message message;
};
struct tsc210x_dev {
enum tsc_type {
tsc2101,
tsc2102,
} kind;
struct tsc210x_config *pdata;
struct clk *bclk_ck;
struct workqueue_struct *queue;
struct delayed_work ts_worker; /* Poll-wait for PEN UP */
struct delayed_work sensor_worker; /* Scan the ADC inputs */
spinlock_t queue_lock;
struct completion data_avail;
tsc210x_touch_t touch_cb;
void *touch_cb_ctx;
tsc210x_coords_t coords_cb;
void *coords_cb_ctx;
tsc210x_ports_t ports_cb;
void *ports_cb_ctx;
tsc210x_temp_t temp1_cb;
void *temp2_cb_ctx;
tsc210x_temp_t temp2_cb;
void *temp1_cb_ctx;
struct spi_device *spi;
struct spi_transfer *transfers;
struct tsc210x_spi_req req_adc;
struct tsc210x_spi_req req_status;
struct tsc210x_spi_req req_mode;
struct tsc210x_spi_req req_stop;
int pendown;
int flushing; /* Queue flush in progress */
uint16_t status, adc_data[4];
int bat[2], aux[2], temp[2];
};
static struct {
unsigned int ts_msecs; /* Interval for .ts_timer */
unsigned int mode_msecs; /* Interval for .mode_timer */
} settings;
module_param_named(touch_check_msecs, settings.ts_msecs, uint, 0);
MODULE_PARM_DESC(touch_check_msecs, "Pen-up polling interval in msecs");
module_param_named(sensor_scan_msecs, settings.mode_msecs, uint, 0);
MODULE_PARM_DESC(sensor_scan_msecs, "Temperature & battery scan interval");
void tsc210x_write_sync(struct tsc210x_dev *dev,
int page, u8 address, u16 data)
{
static struct tsc210x_spi_req req;
static struct spi_transfer transfer[2];
int ret;
spi_message_init(&req.message);
/* Address */
req.command = (page << 11) | (address << 5);
transfer[0].tx_buf = &req.command;
transfer[0].rx_buf = 0;
transfer[0].len = 2;
spi_message_add_tail(&transfer[0], &req.message);
/* Data */
transfer[1].tx_buf = &data;
transfer[1].rx_buf = 0;
transfer[1].len = 2;
transfer[1].cs_change = CS_CHANGE(1);
spi_message_add_tail(&transfer[1], &req.message);
ret = spi_sync(dev->spi, &req.message);
if (!ret && req.message.status)
ret = req.message.status;
if (ret)
printk(KERN_ERR "%s: error %i in SPI request\n",
__FUNCTION__, ret);
}
void tsc210x_reads_sync(struct tsc210x_dev *dev,
int page, u8 startaddress, u16 *data, int numregs)
{
static struct tsc210x_spi_req req;
static struct spi_transfer transfer[6];
int ret, i, j;
BUG_ON(numregs + 1 > ARRAY_SIZE(transfer));
spi_message_init(&req.message);
i = 0;
j = 0;
/* Address */
req.command = 0x8000 | (page << 11) | (startaddress << 5);
transfer[i].tx_buf = &req.command;
transfer[i].rx_buf = 0;
transfer[i].len = 2;
spi_message_add_tail(&transfer[i ++], &req.message);
/* Data */
while (j < numregs) {
transfer[i].tx_buf = 0;
transfer[i].rx_buf = &data[j ++];
transfer[i].len = 2;
transfer[i].cs_change = CS_CHANGE(j == numregs);
spi_message_add_tail(&transfer[i ++], &req.message);
}
ret = spi_sync(dev->spi, &req.message);
if (!ret && req.message.status)
ret = req.message.status;
if (ret)
printk(KERN_ERR "%s: error %i in SPI request\n",
__FUNCTION__, ret);
}
uint16_t tsc210x_read_sync(struct tsc210x_dev *dev, int page, uint8_t address)
{
uint16_t ret;
tsc210x_reads_sync(dev, page, address, &ret, 1);
return ret;
}
static void tsc210x_submit_async(struct tsc210x_spi_req *spi)
{
int ret;
ret = spi_async(spi->dev, &spi->message);
if (ret)
printk(KERN_ERR "%s: error %i in SPI request\n",
__FUNCTION__, ret);
}
static void tsc210x_request_alloc(struct tsc210x_dev *dev,
struct tsc210x_spi_req *spi, int direction,
int page, u8 startaddress, int numregs, uint16_t *data,
void (*complete)(struct tsc210x_dev *context),
struct spi_transfer **transfer)
{
spi->dev = dev->spi;
if (direction == 1) /* Write */
numregs = 2;
else /* Read */
numregs += 1;
spi_message_init(&spi->message);
spi->message.complete = (void (*)(void *)) complete;
spi->message.context = dev;
/* Address */
spi->command = (page << 11) | (startaddress << 5);
if (direction != 1)
spi->command |= 1 << 15;
(*transfer)->tx_buf = &spi->command;
(*transfer)->rx_buf = 0;
(*transfer)->len = 2;
spi_message_add_tail((*transfer) ++, &spi->message);
/* Data */
while (-- numregs) {
if (direction == 1)
(*transfer)->tx_buf = &spi->data;
else
(*transfer)->rx_buf = data ++;
(*transfer)->len = 2;
(*transfer)->cs_change = CS_CHANGE(numregs != 1);
spi_message_add_tail((*transfer) ++, &spi->message);
}
}
#define tsc210x_cb_register_func(cb, cb_t) \
int tsc210x_ ## cb(struct device *dev, cb_t handler, void *context) \
{ \
struct tsc210x_dev *tsc = (struct tsc210x_dev *) \
platform_get_drvdata(to_platform_device(dev)); \
\
/* Lock the module */ \
if (handler && !tsc->cb) \
if (!try_module_get(THIS_MODULE)) { \
printk(KERN_INFO "Failed to get TSC module\n"); \
} \
if (!handler && tsc->cb) \
module_put(THIS_MODULE); \
\
tsc->cb = handler; \
tsc->cb ## _ctx = context; \
return 0; \
}
tsc210x_cb_register_func(touch_cb, tsc210x_touch_t)
tsc210x_cb_register_func(coords_cb, tsc210x_coords_t)
tsc210x_cb_register_func(ports_cb, tsc210x_ports_t)
tsc210x_cb_register_func(temp1_cb, tsc210x_temp_t)
tsc210x_cb_register_func(temp2_cb, tsc210x_temp_t)
#ifdef DEBUG
static void tsc210x_print_dav(void)
{
u16 status = tsc210x_read_sync(dev, TSC210X_TS_STATUS_CTRL);
if (status & 0x0fff)
printk("TSC210x: data in");
if (status & 0x0400)
printk(" X");
if (status & 0x0200)
printk(" Y");
if (status & 0x0100)
printk(" Z1");
if (status & 0x0080)
printk(" Z2");
if (status & 0x0040)
printk(" BAT1");
if (status & 0x0020)
printk(" BAT2");
if (status & 0x0010)
printk(" AUX1");
if (status & 0x0008)
printk(" AUX2");
if (status & 0x0004)
printk(" TEMP1");
if (status & 0x0002)
printk(" TEMP2");
if (status & 0x0001)
printk(" KP");
if (status & 0x0fff)
printk(".\n");
}
#endif
static void tsc210x_complete_dummy(struct tsc210x_dev *dev)
{
}
static inline void tsc210x_touchscreen_mode(struct tsc210x_dev *dev)
{
/* Scan X, Y, Z1, Z2, chip controlled, 12-bit, 16 samples, 500 usec */
dev->req_mode.data = TSC210X_ADC_TS_CONTROL;
tsc210x_submit_async(&dev->req_mode);
}
static inline void tsc210x_portscan_mode(struct tsc210x_dev *dev)
{
/* Scan BAT1, BAT2, AUX1, AUX2, 12-bit, 16 samples, 500 usec */
dev->req_mode.data = TSC210X_ADC_SCAN_CONTROL;
tsc210x_submit_async(&dev->req_mode);
}
static inline void tsc210x_temp1_mode(struct tsc210x_dev *dev)
{
/* Scan TEMP1, 12-bit, 16 samples, 500 usec */
dev->req_mode.data = TSC210X_ADC_T1_CONTROL;
tsc210x_submit_async(&dev->req_mode);
}
static inline void tsc210x_temp2_mode(struct tsc210x_dev *dev)
{
/* Scan TEMP2, 12-bit, 16 samples, 500 usec */
dev->req_mode.data = TSC210X_ADC_T2_CONTROL;
tsc210x_submit_async(&dev->req_mode);
}
/* Abort current conversion if any */
static void tsc210x_new_mode(struct tsc210x_dev *dev)
{
dev->req_stop.data = TSC210X_ADC_ADST;
tsc210x_submit_async(&dev->req_stop);
}
static void tsc210x_queue_scan(struct tsc210x_dev *dev)
{
if (dev->pdata->monitor)
if (!queue_delayed_work(dev->queue,
&dev->sensor_worker,
msecs_to_jiffies(settings.mode_msecs)))
printk(KERN_ERR "%s: can't queue measurements\n",
__FUNCTION__);
}
static void tsc210x_queue_penup(struct tsc210x_dev *dev)
{
if (!queue_delayed_work(dev->queue,
&dev->ts_worker,
msecs_to_jiffies(settings.ts_msecs)))
printk(KERN_ERR "%s: can't queue pen-up poll\n",
__FUNCTION__);
}
static void tsc210x_status_report(struct tsc210x_dev *dev)
{
/*
* Read all converted data from corresponding registers
* so that the ADC can move on to a new conversion.
*/
if (dev->status & TSC210X_TS_DAV) {
if (!dev->pendown && !dev->flushing) {
dev->pendown = 1;
if (dev->touch_cb)
dev->touch_cb(dev->touch_cb_ctx, 1);
tsc210x_queue_penup(dev);
}
tsc210x_submit_async(&dev->req_adc);
}
if (dev->status & (TSC210X_PS_DAV | TSC210X_T1_DAV |TSC210X_T2_DAV))
complete(&dev->data_avail);
}
static void tsc210x_data_report(struct tsc210x_dev *dev)
{
uint16_t adc_data[4];
if (dev->status & TSC210X_PS_DAV) {
tsc210x_reads_sync(dev, TSC210X_TS_BAT1, adc_data, 4);
dev->bat[0] = adc_data[0];
dev->bat[1] = adc_data[1];
dev->aux[0] = adc_data[2];
dev->aux[1] = adc_data[3];
if (dev->ports_cb)
dev->ports_cb(dev->ports_cb_ctx, dev->bat, dev->aux);
}
if (dev->status & TSC210X_T1_DAV) {
dev->temp[0] = tsc210x_read_sync(dev, TSC210X_TS_TEMP1);
if (dev->temp1_cb)
dev->temp1_cb(dev->temp1_cb_ctx, dev->temp[0]);
}
if (dev->status & TSC210X_T2_DAV) {
dev->temp[1] = tsc210x_read_sync(dev, TSC210X_TS_TEMP2);
if (dev->temp2_cb)
dev->temp2_cb(dev->temp2_cb_ctx, dev->temp[1]);
}
}
static void tsc210x_coords_report(struct tsc210x_dev *dev)
{
if (dev->coords_cb)
dev->coords_cb(dev->coords_cb_ctx,
dev->adc_data[0], dev->adc_data[1],
dev->adc_data[2], dev->adc_data[3]);
}
/*
* There are at least three ways to check for pen-up:
* - the PINT/DAV pin state,
* - reading PSTCM bit in ADC Control register (D15, offset 0x00),
* - reading ADST bit in ADC Control register (D14, offset 0x00),
* ADC idle would indicate no screen touch.
* Unfortunately none of them seems to be 100% accurate and you will
* find they are totally inconsistent, i.e. you get to see any arbitrary
* combination of values in these three bits. So we will busy-wait
* for a moment when the latter two indicate a pen-up, using a queue,
* before we report a pen-up.
*/
static void tsc210x_pressure(struct work_struct *work)
{
struct tsc210x_dev *dev =
container_of(work, struct tsc210x_dev, ts_worker.work);
uint16_t adc_status;
BUG_ON(!dev->pendown);
adc_status = tsc210x_read_sync(dev, TSC210X_TS_ADC_CTRL);
if ((adc_status & TSC210X_ADC_PSTCM) ||
!(adc_status & TSC210X_ADC_ADST)) {
tsc210x_queue_penup(dev);
} else {
dev->pendown = 0;
if (dev->touch_cb)
dev->touch_cb(dev->touch_cb_ctx, 0);
}
}
static void tsc210x_wait_data(struct tsc210x_dev *dev)
{
wait_for_completion(&dev->data_avail);
tsc210x_data_report(dev);
}
static void tsc210x_input_scan(struct work_struct *work)
{
struct tsc210x_dev *dev = (struct tsc210x_dev *)
container_of(work, struct tsc210x_dev, sensor_worker.work);
tsc210x_new_mode(dev);
if (dev->pdata->monitor &
(TSC_BAT1 | TSC_BAT2 | TSC_AUX1 | TSC_AUX2)) {
tsc210x_portscan_mode(dev);
tsc210x_wait_data(dev);
}
if (dev->pdata->monitor & TSC_TEMP) {
tsc210x_temp1_mode(dev);
tsc210x_wait_data(dev);
tsc210x_temp2_mode(dev);
tsc210x_wait_data(dev);
}
tsc210x_touchscreen_mode(dev);
spin_lock(&dev->queue_lock);
if (!dev->flushing)
tsc210x_queue_scan(dev);
spin_unlock(&dev->queue_lock);
}
/* ADC has finished a new conversion for us. */
static irqreturn_t tsc210x_handler(int irq, void *dev_id)
{
struct tsc210x_dev *dev = (struct tsc210x_dev *) dev_id;
/* See what data became available. */
tsc210x_submit_async(&dev->req_status);
return IRQ_HANDLED;
}
#ifdef CONFIG_SOUND
/*
* Volume level values should be in the range [0, 127].
* Higher values mean lower volume.
*/
void tsc210x_set_dac_volume(struct device *dev,
uint8_t left_ch, uint8_t right_ch)
{
struct tsc210x_dev *tsc = (struct tsc210x_dev *)
platform_get_drvdata(to_platform_device(dev));
u16 val;
if (tsc->kind == tsc2102) {
/* All 0's or all 1's */
if (left_ch == 0x00 || left_ch == 0x7f)
left_ch ^= 0x7f;
if (right_ch == 0x00 || right_ch == 0x7f)
right_ch ^= 0x7f;
}
val = tsc210x_read_sync(tsc, TSC210X_DAC_GAIN_CTRL);
val &= 0x8080; /* Preserve mute-bits */
val |= (left_ch << 8) | right_ch;
tsc210x_write_sync(tsc, TSC210X_DAC_GAIN_CTRL, val);
}
void tsc210x_set_dac_mute(struct device *dev, int left_ch, int right_ch)
{
struct tsc210x_dev *tsc = (struct tsc210x_dev *)
platform_get_drvdata(to_platform_device(dev));
u16 val;
val = tsc210x_read_sync(tsc, TSC210X_DAC_GAIN_CTRL);
val &= 0x7f7f; /* Preserve volume settings */
val |= (left_ch << 15) | (right_ch << 7);
tsc210x_write_sync(tsc, TSC210X_DAC_GAIN_CTRL, val);
}
void tsc210x_get_dac_mute(struct device *dev, int *left_ch, int *right_ch)
{
struct tsc210x_dev *tsc = (struct tsc210x_dev *)
platform_get_drvdata(to_platform_device(dev));
u16 val;
val = tsc210x_read_sync(tsc, TSC210X_DAC_GAIN_CTRL);
*left_ch = !!(val & (1 << 15));
*right_ch = !!(val & (1 << 7));
}
void tsc210x_set_deemphasis(struct device *dev, int enable)
{
struct tsc210x_dev *tsc = (struct tsc210x_dev *)
platform_get_drvdata(to_platform_device(dev));
u16 val;
val = tsc210x_read_sync(tsc, TSC210X_POWER_CTRL);
if (enable)
val &= ~TSC210X_DEEMPF;
else
val |= TSC210X_DEEMPF;
tsc210x_write_sync(tsc, TSC210X_POWER_CTRL, val);
}
void tsc2102_set_bassboost(struct device *dev, int enable)
{
struct tsc210x_dev *tsc = (struct tsc210x_dev *)
platform_get_drvdata(to_platform_device(dev));
u16 val;
val = tsc210x_read_sync(tsc, TSC210X_POWER_CTRL);
if (enable)
val &= ~TSC2102_BASSBC;
else
val |= TSC2102_BASSBC;
tsc210x_write_sync(tsc, TSC210X_POWER_CTRL, val);
}
/* {rate, dsor, fsref} */
static const struct tsc210x_rate_info_s tsc2101_rates[] = {
/* Fsref / 6.0 */
{7350, 7, 1},
{8000, 7, 0},
/* Fsref / 5.5 */
{8018, 6, 1},
{8727, 6, 0},
/* Fsref / 5.0 */
{8820, 5, 1},
{9600, 5, 0},
/* Fsref / 4.0 */
{11025, 4, 1},
{12000, 4, 0},
/* Fsref / 3.0 */
{14700, 3, 1},
{16000, 3, 0},
/* Fsref / 2.0 */
{22050, 2, 1},
{24000, 2, 0},
/* Fsref / 1.5 */
{29400, 1, 1},
{32000, 1, 0},
/* Fsref */
{44100, 0, 1},
{48000, 0, 0},
{0, 0, 0},
};
/* {rate, dsor, fsref} */
static const struct tsc210x_rate_info_s tsc2102_rates[] = {
/* Fsref / 6.0 */
{7350, 63, 1},
{8000, 63, 0},
/* Fsref / 6.0 */
{7350, 54, 1},
{8000, 54, 0},
/* Fsref / 5.0 */
{8820, 45, 1},
{9600, 45, 0},
/* Fsref / 4.0 */
{11025, 36, 1},
{12000, 36, 0},
/* Fsref / 3.0 */
{14700, 27, 1},
{16000, 27, 0},
/* Fsref / 2.0 */
{22050, 18, 1},
{24000, 18, 0},
/* Fsref / 1.5 */
{29400, 9, 1},
{32000, 9, 0},
/* Fsref */
{44100, 0, 1},
{48000, 0, 0},
{0, 0, 0},
};
int tsc210x_set_rate(struct device *dev, int rate)
{
struct tsc210x_dev *tsc = (struct tsc210x_dev *)
platform_get_drvdata(to_platform_device(dev));
int i;
uint16_t val;
const struct tsc210x_rate_info_s *rates;
if (tsc->kind == tsc2101)
rates = tsc2101_rates;
else
rates = tsc2102_rates;
for (i = 0; rates[i].sample_rate; i ++)
if (rates[i].sample_rate == rate)
break;
if (rates[i].sample_rate == 0) {
printk(KERN_ERR "Unknown sampling rate %i.0 Hz\n", rate);
return -EINVAL;
}
if (tsc->kind == tsc2101) {
val = tsc210x_read_sync(tsc, TSC210X_AUDIO1_CTRL) &
~((7 << 3) | (7 << 0));
val |= rates[i].divisor << 3;
val |= rates[i].divisor << 0;
} else
val = rates[i].divisor;
tsc210x_write_sync(tsc, TSC210X_AUDIO1_CTRL, val);
val = tsc210x_read_sync(tsc, TSC210X_AUDIO3_CTRL);
if (tsc2102_rates[i].fs_44k) {
tsc210x_write_sync(tsc,
TSC210X_AUDIO3_CTRL, val | TSC210X_FS44K);
/* Enable Phase-locked-loop, set up clock dividers */
tsc210x_write_sync(tsc, TSC210X_PLL1_CTRL, TSC210X_PLL1_44K);
tsc210x_write_sync(tsc, TSC210X_PLL2_CTRL, TSC210X_PLL2_44K);
} else {
tsc210x_write_sync(tsc,
TSC210X_AUDIO3_CTRL, val & ~TSC210X_FS44K);
/* Enable Phase-locked-loop, set up clock dividers */
tsc210x_write_sync(tsc, TSC210X_PLL1_CTRL, TSC210X_PLL1_48K);
tsc210x_write_sync(tsc, TSC210X_PLL2_CTRL, TSC210X_PLL2_48K);
}
return 0;
}
/*
* Perform basic set-up with default values and power the DAC/ADC on.
*/
void tsc210x_dac_power(struct device *dev, int on)
{
struct tsc210x_dev *tsc = (struct tsc210x_dev *)
platform_get_drvdata(to_platform_device(dev));
if (on) {
/* 16-bit words, DSP mode, sample at Fsref */
tsc210x_write_sync(tsc,
TSC210X_AUDIO1_CTRL, 0x0100);
/* Keyclicks off, soft-stepping at normal rate */
tsc210x_write_sync(tsc,
TSC210X_AUDIO2_CTRL, TSC210X_KEYCLICK_OFF);
/* 44.1 kHz Fsref, continuous transfer mode, master DAC */
tsc210x_write_sync(tsc,
TSC210X_AUDIO3_CTRL, 0x2000);
/* Soft-stepping enabled, 1 dB MIX AGC hysteresis */
tsc210x_write_sync(tsc,
TSC210X_AUDIO4_CTRL, 0x0000);
/* PLL generates 44.1 kHz */
tsc210x_write_sync(tsc,
TSC210X_PLL1_CTRL, TSC210X_PLL1_44K);
tsc210x_write_sync(tsc,
TSC210X_PLL2_CTRL, TSC210X_PLL2_44K);
/* Codec & DAC power up, virtual ground disabled */
tsc210x_write_sync(tsc,
TSC210X_POWER_CTRL, (tsc->kind == tsc2101) ?
TSC2101_DAC_ON : TSC2102_DAC_ON);
} else {
/* All off */
tsc210x_write_sync(tsc,
TSC210X_AUDIO2_CTRL, TSC210X_KEYCLICK_OFF);
tsc210x_write_sync(tsc,
TSC210X_PLL1_CTRL, TSC210X_PLL1_OFF);
#if 0
tsc210x_write_sync(tsc,
TSC210X_POWER_CTRL, (tsc->kind == tsc2101) ?
TSC2102_DAC_OFF : TSC2102_DAC_OFF);
#endif
}
}
void tsc210x_set_i2s_master(struct device *dev, int state)
{
struct tsc210x_dev *tsc = (struct tsc210x_dev *)
platform_get_drvdata(to_platform_device(dev));
uint16_t val;
val = tsc210x_read_sync(tsc, TSC210X_AUDIO3_CTRL);
if (state)
tsc210x_write_sync(tsc, TSC210X_AUDIO3_CTRL,
val | TSC210X_SLVMS);
else
tsc210x_write_sync(tsc, TSC210X_AUDIO3_CTRL,
val & ~TSC210X_SLVMS);
}
#endif /* CONFIG_SOUND */
static int tsc210x_configure(struct tsc210x_dev *dev)
{
/* Reset the chip */
tsc210x_write_sync(dev, TSC210X_TS_RESET_CTRL, TSC210X_RESET);
/* Reference mode */
if (dev->pdata->use_internal)
tsc210x_write_sync(dev,
TSC210X_TS_REF_CTRL, TSC210X_ADC_INT_REF);
else
tsc210x_write_sync(dev,
TSC210X_TS_REF_CTRL, TSC210X_ADC_EXT_REF);
/* Precharge and sense delays, pen touch detection on */
tsc210x_write_sync(dev, TSC210X_TS_CONFIG_CTRL, TSC210X_CONFIG_TIMES);
/* PINT/DAV acts as DAV */
tsc210x_write_sync(dev, TSC210X_TS_STATUS_CTRL, TSC210X_ADC_DAV);
tsc210x_queue_scan(dev);
return 0;
}
/*
* Retrieves chip revision. Should be always 1.
*/
int tsc210x_get_revision(struct tsc210x_dev *dev)
{
return tsc210x_read_sync(dev, TSC210X_AUDIO3_CTRL) & 7;
}
void tsc210x_keyclick(struct tsc210x_dev *dev,
int amplitude, int freq, int length)
{
u16 val;
val = tsc210x_read_sync(dev, TSC210X_AUDIO2_CTRL);
val &= 0x800f;
/* Set amplitude */
switch (amplitude) {
case 1:
val |= 4 << 12;
break;
case 2:
val |= 7 << 12;
break;
default:
break;
}
/* Frequency */
val |= (freq & 0x7) << 8;
/* Round to nearest supported length */
if (dev->kind == tsc2101)
val = (min(length - 1, 31) >> 1) << 4;
else {
if (length > 20)
val |= 4 << 4;
else if (length > 6)
val |= 3 << 4;
else if (length > 4)
val |= 2 << 4;
else if (length > 2)
val |= 1 << 4;
}
/* Enable keyclick */
val |= 0x8000;
tsc210x_write_sync(dev, TSC210X_AUDIO2_CTRL, val);
}
#ifdef CONFIG_PM
/*
* Suspend the chip.
*/
static int
tsc210x_suspend(struct spi_device *spi, pm_message_t state)
{
struct tsc210x_dev *dev = dev_get_drvdata(&spi->dev);
if (!dev)
return -ENODEV;
/* Stop the inputs scan loop */
spin_lock(&dev->queue_lock);
dev->flushing = 1;
cancel_delayed_work(&dev->sensor_worker);
spin_unlock(&dev->queue_lock);
flush_workqueue(dev->queue);
/* Wait until pen-up happens */
while (dev->pendown)
flush_workqueue(dev->queue);
/* Abort current conversion and power down the ADC */
tsc210x_write_sync(dev, TSC210X_TS_ADC_CTRL, TSC210X_ADC_ADST);
dev->spi->dev.power.power_state = state;
return 0;
}
/*
* Resume chip operation.
*/
static int tsc210x_resume(struct spi_device *spi)
{
struct tsc210x_dev *dev = dev_get_drvdata(&spi->dev);
int err;
if (!dev)
return 0;
dev->spi->dev.power.power_state = PMSG_ON;
spin_lock(&dev->queue_lock);
err = tsc210x_configure(dev);
dev->flushing = 0;
spin_unlock(&dev->queue_lock);
return err;
}
#else
#define tsc210x_suspend NULL
#define tsc210x_resume NULL
#endif
static struct platform_device tsc210x_ts_device = {
.name = "tsc210x-ts",
.id = -1,
};
static struct platform_device tsc210x_hwmon_device = {
.name = "tsc210x-hwmon",
.id = -1,
};
static struct platform_device tsc210x_alsa_device = {
.name = "tsc210x-alsa",
.id = -1,
};
static int tsc210x_probe(struct spi_device *spi, enum tsc_type type)
{
struct tsc210x_config *pdata = spi->dev.platform_data;
struct spi_transfer *spi_buffer;
struct tsc210x_dev *dev;
int err = 0;
if (!pdata) {
printk(KERN_ERR "TSC210x: Platform data not supplied\n");
return -ENOENT;
}
if (!spi->irq) {
printk(KERN_ERR "TSC210x: Invalid irq value\n");
return -EINVAL;
}
dev = (struct tsc210x_dev *)
kzalloc(sizeof(struct tsc210x_dev), GFP_KERNEL);
if (!dev) {
printk(KERN_ERR "TSC210x: No memory\n");
return -ENOMEM;
}
dev->pdata = pdata;
dev->pendown = 0;
dev->spi = spi;
dev->kind = type;
dev->queue = create_singlethread_workqueue(spi->dev.driver->name);
if (!dev->queue) {
printk(KERN_ERR "TSC210x: Can't make a workqueue\n");
err = -ENOMEM;
goto err_queue;
}
spin_lock_init(&dev->queue_lock);
init_completion(&dev->data_avail);
/* Allocate enough struct spi_transfer's for all requests */
spi_buffer = kzalloc(sizeof(struct spi_transfer) * 16, GFP_KERNEL);
if (!spi_buffer) {
printk(KERN_ERR "TSC210x: No memory for SPI buffers\n");
err = -ENOMEM;
goto err_buffers;
}
dev->transfers = spi_buffer;
tsc210x_request_alloc(dev, &dev->req_adc, 0,
TSC210X_TS_X, 4, dev->adc_data,
tsc210x_coords_report, &spi_buffer);
tsc210x_request_alloc(dev, &dev->req_status, 0,
TSC210X_TS_STATUS_CTRL, 1, &dev->status,
tsc210x_status_report, &spi_buffer);
tsc210x_request_alloc(dev, &dev->req_mode, 1,
TSC210X_TS_ADC_CTRL, 1, 0,
tsc210x_complete_dummy, &spi_buffer);
tsc210x_request_alloc(dev, &dev->req_stop, 1,
TSC210X_TS_ADC_CTRL, 1, 0,
tsc210x_complete_dummy, &spi_buffer);
if (pdata->bclk) {
/* Get the BCLK */
dev->bclk_ck = clk_get(&spi->dev, pdata->bclk);
if (IS_ERR(dev->bclk_ck)) {
err = PTR_ERR(dev->bclk_ck);
printk(KERN_ERR "Unable to get '%s': %i\n",
pdata->bclk, err);
goto err_clk;
}
clk_enable(dev->bclk_ck);
}
INIT_DELAYED_WORK(&dev->ts_worker, tsc210x_pressure);
INIT_DELAYED_WORK(&dev->sensor_worker, tsc210x_input_scan);
/* Setup the communication bus */
dev_set_drvdata(&spi->dev, dev);
spi->dev.power.power_state = PMSG_ON;
spi->mode = SPI_MODE_1;
spi->bits_per_word = 16;
err = spi_setup(spi);
if (err)
goto err_spi;
/* Now try to detect the chip, make first contact */
if (tsc210x_get_revision(dev) != 0x1) {
printk(KERN_ERR "No TI %s chip found!\n",
spi->dev.driver->name);
err = -ENODEV;
goto err_spi;
}
err = tsc210x_configure(dev);
if (err)
goto err_spi;
/* We want no interrupts before configuration succeeds. */
spin_lock(&dev->queue_lock);
dev->flushing = 1;
if (request_irq(spi->irq, tsc210x_handler, IRQF_SAMPLE_RANDOM |
IRQF_TRIGGER_FALLING, spi->dev.driver->name,
dev)) {
printk(KERN_ERR "Could not allocate touchscreen IRQ!\n");
err = -EINVAL;
goto err_irq;
}
/* Register subdevices controlled by the TSC 2101/2102 */
tsc210x_ts_device.dev.platform_data = dev;
tsc210x_ts_device.dev.parent = &spi->dev;
err = platform_device_register(&tsc210x_ts_device);
if (err)
goto err_irq;
tsc210x_hwmon_device.dev.platform_data = pdata;
tsc210x_hwmon_device.dev.parent = &spi->dev;
err = platform_device_register(&tsc210x_hwmon_device);
if (err)
goto err_hwmon;
tsc210x_alsa_device.dev.platform_data = pdata->alsa_config;
tsc210x_alsa_device.dev.parent = &spi->dev;
err = platform_device_register(&tsc210x_alsa_device);
if (err)
goto err_alsa;
dev->flushing = 0;
spin_unlock(&dev->queue_lock);
return 0;
err_alsa:
platform_device_unregister(&tsc210x_hwmon_device);
err_hwmon:
platform_device_unregister(&tsc210x_ts_device);
err_irq:
spin_unlock(&dev->queue_lock);
err_spi:
dev_set_drvdata(&spi->dev, NULL);
clk_disable(dev->bclk_ck);
clk_put(dev->bclk_ck);
err_clk:
kfree(dev->transfers);
err_buffers:
destroy_workqueue(dev->queue);
err_queue:
kfree(dev);
return err;
}
static int tsc2101_probe(struct spi_device *spi)
{
return tsc210x_probe(spi, tsc2101);
}
static int tsc2102_probe(struct spi_device *spi)
{
return tsc210x_probe(spi, tsc2102);
}
static int tsc210x_remove(struct spi_device *spi)
{
struct tsc210x_dev *dev = dev_get_drvdata(&spi->dev);
/* Stop the inputs scan loop */
spin_lock(&dev->queue_lock);
dev->flushing = 1;
cancel_delayed_work(&dev->sensor_worker);
spin_unlock(&dev->queue_lock);
flush_workqueue(dev->queue);
/* Wait for pen-up */
while (dev->pendown)
flush_workqueue(dev->queue);
/* Abort current conversion and power down the ADC */
tsc210x_write_sync(dev, TSC210X_TS_ADC_CTRL, TSC210X_ADC_ADST);
destroy_workqueue(dev->queue);
platform_device_unregister(&tsc210x_ts_device);
platform_device_unregister(&tsc210x_hwmon_device);
platform_device_unregister(&tsc210x_alsa_device);
dev_set_drvdata(&spi->dev, NULL);
/* Release the BCLK */
clk_disable(dev->bclk_ck);
clk_put(dev->bclk_ck);
kfree(dev->transfers);
kfree(dev);
return 0;
}
static struct spi_driver tsc2101_driver = {
.probe = tsc2101_probe,
.remove = tsc210x_remove,
.suspend = tsc210x_suspend,
.resume = tsc210x_resume,
.driver = {
.name = "tsc2101",
.owner = THIS_MODULE,
.bus = &spi_bus_type,
},
};
static struct spi_driver tsc2102_driver = {
.probe = tsc2102_probe,
.remove = tsc210x_remove,
.suspend = tsc210x_suspend,
.resume = tsc210x_resume,
.driver = {
.name = "tsc2102",
.owner = THIS_MODULE,
.bus = &spi_bus_type,
},
};
static char __initdata banner[] = KERN_INFO "TI TSC210x driver initializing\n";
static int __init tsc210x_init(void)
{
int err;
printk(banner);
settings.ts_msecs = 20;
settings.mode_msecs = 1000;
err = spi_register_driver(&tsc2101_driver);
if (err != 0)
return err;
err = spi_register_driver(&tsc2102_driver);
if (err != 0)
spi_unregister_driver(&tsc2101_driver);
return err;
}
static void __exit tsc210x_exit(void)
{
spi_unregister_driver(&tsc2101_driver);
spi_unregister_driver(&tsc2102_driver);
}
module_init(tsc210x_init);
module_exit(tsc210x_exit);
EXPORT_SYMBOL(tsc210x_read_sync);
EXPORT_SYMBOL(tsc210x_reads_sync);
EXPORT_SYMBOL(tsc210x_write_sync);
EXPORT_SYMBOL(tsc210x_keyclick);
MODULE_AUTHOR("Andrzej Zaborowski");
MODULE_DESCRIPTION("Interface driver for TI TSC210x chips.");
MODULE_LICENSE("GPL");
/*
* include/linux/spi/tsc2102.h
*
* TI TSC2101/2102 control register definitions.
*
* Copyright (c) 2005-2007 Andrzej Zaborowski <balrog@zabor.org>
*
* This package 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.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this package; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef __LINUX_SPI_TSC210X_H
#define __LINUX_SPI_TSC210X_H
struct apm_power_info;
struct tsc210x_config {
int use_internal; /* Use internal reference voltage */
uint32_t monitor; /* What inputs are relevant */
int temp_at25c[2]; /* Thermometer calibration data */
void (*apm_report)(struct apm_power_info *info, int battery[]);
/* Report status to APM based on battery[] */
void *alsa_config; /* .platform_data for the ALSA device */
const char *mclk; /* Optional: bclk name */
const char *bclk; /* Optional: mclk name */
};
#define TSC_BAT1 (1 << 0)
#define TSC_BAT2 (1 << 1)
#define TSC_AUX1 (1 << 2)
#define TSC_AUX2 (1 << 3)
#define TSC_TEMP (1 << 4)
#define TSC_AUX TSC_AUX1
#define TSC_VBAT TSC_BAT1
struct tsc210x_dev;
extern u16 tsc210x_read_sync(struct tsc210x_dev *dev, int page, u8 address);
extern void tsc210x_reads_sync(struct tsc210x_dev *dev, int page,
u8 startaddress, u16 *data, int numregs);
extern void tsc210x_write_sync(struct tsc210x_dev *dev, int page,
u8 address, u16 data);
typedef void (*tsc210x_touch_t)(void *context, int touching);
typedef void (*tsc210x_coords_t)(void *context, int x, int y, int z1, int z2);
typedef void (*tsc210x_ports_t)(void *context, int bat[], int aux[]);
typedef void (*tsc210x_temp_t)(void *context, int temp);
extern int tsc210x_touch_cb(struct device *dev,
tsc210x_touch_t handler, void *context);
extern int tsc210x_coords_cb(struct device *dev,
tsc210x_coords_t handler, void *context);
extern int tsc210x_ports_cb(struct device *dev,
tsc210x_ports_t handler, void *context);
extern int tsc210x_temp1_cb(struct device *dev,
tsc210x_temp_t handler, void *context);
extern int tsc210x_temp2_cb(struct device *dev,
tsc210x_temp_t handler, void *context);
#ifdef CONFIG_SOUND
extern void tsc210x_set_dac_volume(struct device *dev,
uint8_t left_ch, uint8_t right_ch);
extern void tsc210x_set_dac_mute(struct device *dev,
int left_ch, int right_ch);
extern void tsc210x_get_dac_mute(struct device *dev,
int *left_ch, int *right_ch);
extern void tsc210x_dac_power(struct device *dev, int on);
extern int tsc210x_set_rate(struct device *dev, int rate);
extern void tsc210x_set_i2s_master(struct device *dev, int state);
extern void tsc210x_set_deemphasis(struct device *dev, int enable);
extern void tsc2102_set_bassboost(struct device *dev, int enable);
#endif
/*
* Emit a short keyclick typically in order to give feedback to
* user on specific events.
*
* amplitude must be between 0 (lowest) and 2 (highest).
* freq must be between 0 (corresponds to 62.5 Hz) and 7 (8 kHz).
* length should be between 2 and 32 periods.
*
* This function sleeps but for a period unrelated to the length of
* the sound, i.e. returning doesn't indicate that the sound has
* finished.
*/
extern void tsc210x_keyclick(struct tsc210x_dev *dev,
int amplitude, int freq, int length);
/* Page 0, Touch Screen & Keypad Data registers */
#define TSC210X_TS_X 0, 0x00
#define TSC210X_TS_Y 0, 0x01
#define TSC210X_TS_Z1 0, 0x02
#define TSC210X_TS_Z2 0, 0x03
#define TSC210X_TS_BAT1 0, 0x05
#define TSC2102_TS_BAT2 0, 0x06
#define TSC210X_TS_AUX1 0, 0x07
#define TSC2101_TS_AUX2 0, 0x08
#define TSC210X_TS_TEMP1 0, 0x09
#define TSC210X_TS_TEMP2 0, 0x0a
/* Page 1, Touch Screen & Keypad Control registers */
#define TSC210X_TS_ADC_CTRL 1, 0x00
#define TSC210X_TS_STATUS_CTRL 1, 0x01
#define TSC2101_TS_BUFFER_CTRL 1, 0x02
#define TSC210X_TS_REF_CTRL 1, 0x03
#define TSC210X_TS_RESET_CTRL 1, 0x04
#define TSC210X_TS_CONFIG_CTRL 1, 0x05
#define TSC2101_TS_TEMPMAX_CTRL 1, 0x06
#define TSC2101_TS_TEMPMIN_CTRL 1, 0x07
#define TSC2101_TS_AUX1MAX_CTRL 1, 0x08
#define TSC2101_TS_AUX1MIN_CTRL 1, 0x09
#define TSC2101_TS_AUX2MAX_CTRL 1, 0x0a
#define TSC2101_TS_AUX2MIN_CTRL 1, 0x0b
#define TSC2101_TS_MCONFIG_CTRL 1, 0x0c
#define TSC2101_TS_DELAY_CTRL 1, 0x0d
/* Page 2, Audio Control registers */
#define TSC210X_AUDIO1_CTRL 2, 0x00
#define TSC2101_HEADSET_GAIN_CTRL 2, 0x01
#define TSC210X_DAC_GAIN_CTRL 2, 0x02
#define TSC2101_MIXER_GAIN_CTRL 2, 0x03
#define TSC210X_AUDIO2_CTRL 2, 0x04
#define TSC210X_POWER_CTRL 2, 0x05
#define TSC210X_AUDIO3_CTRL 2, 0x06
#define TSC210X_LCH_BASS_BOOST_N0 2, 0x07
#define TSC210X_LCH_BASS_BOOST_N1 2, 0x08
#define TSC210X_LCH_BASS_BOOST_N2 2, 0x09
#define TSC210X_LCH_BASS_BOOST_N3 2, 0x0a
#define TSC210X_LCH_BASS_BOOST_N4 2, 0x0b
#define TSC210X_LCH_BASS_BOOST_N5 2, 0x0c
#define TSC210X_LCH_BASS_BOOST_D1 2, 0x0d
#define TSC210X_LCH_BASS_BOOST_D2 2, 0x0e
#define TSC210X_LCH_BASS_BOOST_D4 2, 0x0f
#define TSC210X_LCH_BASS_BOOST_D5 2, 0x10
#define TSC210X_RCH_BASS_BOOST_N0 2, 0x11
#define TSC210X_RCH_BASS_BOOST_N1 2, 0x12
#define TSC210X_RCH_BASS_BOOST_N2 2, 0x13
#define TSC210X_RCH_BASS_BOOST_N3 2, 0x14
#define TSC210X_RCH_BASS_BOOST_N4 2, 0x15
#define TSC210X_RCH_BASS_BOOST_N5 2, 0x16
#define TSC210X_RCH_BASS_BOOST_D1 2, 0x17
#define TSC210X_RCH_BASS_BOOST_D2 2, 0x18
#define TSC210X_RCH_BASS_BOOST_D4 2, 0x19
#define TSC210X_RCH_BASS_BOOST_D5 2, 0x1a
#define TSC210X_PLL1_CTRL 2, 0x1b
#define TSC210X_PLL2_CTRL 2, 0x1c
#define TSC210X_AUDIO4_CTRL 2, 0x1d
#define TSC2101_HANDSET_GAIN_CTRL 2, 0x1e
#define TSC2101_CELL_GAIN_CTRL 2, 0x1f
#define TSC2101_AUIDO5_CTRL 2, 0x20
#define TSC2101_AUDIO6_CTRL 2, 0x21
#define TSC2101_AUDIO7_CTRL 2, 0x22
#define TSC2101_GPIO_CTRL 2, 0x23
#define TSC2101_IN_AGC_CTRL 2, 0x24
#define TSC2101_POWER_STATUS 2, 0x25
#define TSC2101_MIX_AGC_CTRL 2, 0x26
#define TSC2101_CELL_AGC_CTRL 2, 0x27
/* Field masks for Audio Control 1 */
#define AC1_WLEN(ARG) (((ARG) & 0x03) << 10)
#define AC1_DATFM(ARG) (((ARG) & 0x03) << 8)
#define AC1_DACFS(ARG) ((ARG) & 0x3f)
/* Field masks for TSC2102_DAC_GAIN_CTRL */
#define DGC_DALMU (1 << 15)
#define DGC_DALVL(ARG) (((ARG) & 0x7f) << 8)
#define DGC_DARMU (1 << 7)
#define DGC_DARVL(ARG) (((ARG) & 0x7f))
/* Field formats for TSC210X_AUDIO2_CTRL */
#define AC2_KCLEN (1 << 15)
#define AC2_KCLAC(ARG) (((ARG) & 0x07) << 12)
#define AC2_KCLFRQ(ARG) (((ARG) & 0x07) << 8)
#define AC2_KCLLN(ARG) (((ARG) & 0x0f) << 4)
#define AC2_DLGAF (1 << 3)
#define AC2_DRGAF (1 << 2)
#define AC2_DASTC (1 << 1)
/* Field masks for TSC210X_DAC_POWER_CTRL */
#define CPC_PWDNC (1 << 15)
#define CPC_DAODRC (1 << 12)
#define CPC_DAPWDN (1 << 10)
#define CPC_VGPWDN (1 << 8)
#define CPC_DAPWDF (1 << 6)
#define CPC_BASSBC (1 << 1)
#define CPC_DEEMPF (0x01)
/* Field masks for TSC210X_AUDIO3_CTRL */
#define AC3_DMSVOL(ARG) (((ARG) & 0x03) << 14)
#define AC3_REFFS (1 << 13)
#define AC3_DAXFM (1 << 12)
#define AC3_SLVMS (1 << 11)
#define AC3_DALOVF (1 << 7)
#define AC3_DAROVF (1 << 6)
#define AC3_REVID(ARG) (((ARG) & 0x07))
/* Field masks for TSC210X_PLL1_CTRL */
#define PLL1_PLLEN (1 << 15)
#define PLL1_Q_VAL(ARG) (((ARG) & 0x0f) << 11)
#define PLL1_P_VAL(ARG) (((ARG) & 0x07) << 8)
#define PLL1_I_VAL(ARG) (((ARG) & 0x3f) << 2)
/* Field masks for TSC210X_PLL2_CTRL */
#define PLL2_D_VAL(ARG) (((ARG) & 0x3fff) << 2)
/* Field masks for TSC210X_AUDIO4_CTRL */
#define AC4_DASTPD (1 << 14)
struct tsc210x_rate_info_s {
u16 sample_rate;
u8 divisor;
u8 fs_44k; /* 44.1 kHz Fsref if 1, 48 kHz if 0 */
};
#endif /* __LINUX_SPI_TSC210X_H */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment