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

SPI: Add TSC2102 main SPI driver

This adds a driver for the TSC2102 chip by Texas Instruments. This
driver is responsible for all communication with the chip and doesn't
export anything for user-space, but for other drivers controlling
devices connected to the chip.
Signed-off-by: default avatarAndrzej Zaborowski <balrog@zabor.org>
parent d58eb964
......@@ -166,6 +166,13 @@ config SPI_TSC2101
At the moment it provides basic register read / write interface
as well as a way to enable the MCLK clock.
config SPI_TSC2102
depends on SPI_MASTER
tristate "TSC2102 codec support"
---help---
Say Y here if you want support for the TSC2102 chip. It
will be needed for the touchscreen driver on some boards.
#
# Add new SPI protocol masters in alphabetical order above this line
#
......
......@@ -26,6 +26,7 @@ obj-$(CONFIG_SPI_S3C24XX) += spi_s3c24xx.o
# SPI protocol drivers (device/link on bus)
obj-$(CONFIG_SPI_AT25) += at25.o
obj-$(CONFIG_SPI_TSC2101) += tsc2101.o
obj-$(CONFIG_SPI_TSC2102) += tsc2102.o
# ... add above this line ...
# SPI slave controller drivers (upstream link)
......
/*
* drivers/spi/tsc2102.c
*
* TSC2102 interface driver.
*
* Copyright (c) 2005 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/sched.h>
#include <linux/errno.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/suspend.h>
#include <linux/interrupt.h>
#include <linux/clk.h>
#include <linux/spi/spi.h>
#include <linux/spi/tsc2102.h>
#include <linux/err.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <asm/system.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/mach-types.h>
#include <asm/hardware.h>
#ifdef CONFIG_APM
#include <asm/apm.h>
#endif
/* Bit field definitions for chip registers */
#define TSC2102_ADC_TS_CONTROL 0x8bf4
#define TSC2102_ADC_SCAN_CONTROL 0x2ff4
#define TSC2102_ADC_T1_CONTROL 0x2bf4
#define TSC2102_ADC_T2_CONTROL 0x33f4
#define TSC2102_ADC_DAV 0x4000
#define TSC2102_ADC_INT_REF 0x0016
#define TSC2102_ADC_EXT_REF 0x0002
#define TSC2102_CONFIG_TIMES 0x0008
#define TSC2102_RESET 0xbb00
#define TSC2102_ADC_PSTCM (1 << 15)
#define TSC2102_ADC_ADST (1 << 14)
#define TSC2102_TS_DAV 0x0780
#define TSC2102_PS_DAV 0x0078
#define TSC2102_T1_DAV 0x0004
#define TSC2102_T2_DAV 0x0002
#define TSC2102_DAC_ON 0x3ba0
#define TSC2102_DAC_OFF 0xafa0
#define TSC2102_FS44K (1 << 13)
#define TSC2102_PLL1_OFF 0x0000
#define TSC2102_PLL1_44K 0x811c
#define TSC2102_PLL1_48K 0x8120
#define TSC2102_PLL2_44K (5462 << 2)
#define TSC2102_PLL2_48K (1920 << 2)
#define TSC2102_SLVMS (1 << 11)
#define TSC2102_DEEMPF (1 << 0)
#define TSC2102_BASSBC (1 << 1)
#define TSC2102_KEYCLICK_OFF 0x0000
#define CS_CHANGE(val) 0
struct tsc2102_spi_req {
struct spi_device *dev;
uint16_t command;
uint16_t data;
struct spi_transfer *transfer;
struct spi_message message;
};
struct tsc2102_dev {
struct tsc2102_config *pdata;
spinlock_t lock, lock_sync;
struct clk *bclk_ck;
int state; /* 0: TS, 1: Portscan, 2-3: Temps */
struct timer_list ts_timer; /* Busy-wait for PEN UP */
struct timer_list mode_timer; /* Change .state every some time */
int pendown;
int data_pending;
uint16_t status, adc_status, adc_data[4];
tsc2102_touch_t touch_cb;
tsc2102_coords_t coords_cb;
tsc2102_ports_t ports_cb;
tsc2102_temp_t temp1_cb;
tsc2102_temp_t temp2_cb;
unsigned int ts_msecs; /* Interval for .ts_timer */
unsigned int mode_msecs; /* Interval for .mode_timer */
struct spi_device *spi;
struct spi_transfer *transfers;
struct tsc2102_spi_req req_adc;
struct tsc2102_spi_req req_status;
struct tsc2102_spi_req req_pressure;
struct tsc2102_spi_req req_stopadc;
struct tsc2102_spi_req req_mode;
int bat[2], aux[1], temp[2];
struct class_device *hwmondev;
};
static struct tsc2102_dev tsc;
module_param_named(touch_check_msecs, tsc.ts_msecs, uint, 0);
MODULE_PARM_DESC(touch_check_msecs, "Pen-up polling interval in msecs");
module_param_named(sensor_scan_msecs, tsc.mode_msecs, uint, 0);
MODULE_PARM_DESC(sensor_scan_msecs, "Temperature & battery scan interval");
void tsc2102_write_sync(int page, u8 address, u16 data)
{
static struct tsc2102_spi_req req;
static struct spi_transfer transfer[2];
int ret;
spi_message_init(&req.message);
req.transfer = transfer;
/* Address */
req.command = (page << 11) | (address << 5);
req.transfer[0].tx_buf = &req.command;
req.transfer[0].rx_buf = 0;
req.transfer[0].len = 2;
spi_message_add_tail(&req.transfer[0], &req.message);
/* Data */
req.transfer[1].tx_buf = &data;
req.transfer[1].rx_buf = 0;
req.transfer[1].len = 2;
req.transfer[1].cs_change = CS_CHANGE(1);
spi_message_add_tail(&req.transfer[1], &req.message);
ret = spi_sync(tsc.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 tsc2102_reads_sync(int page, u8 startaddress, u16 *data, int numregs)
{
static struct tsc2102_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);
req.transfer = transfer;
i = 0;
j = 0;
/* Address */
req.command = 0x8000 | (page << 11) | (startaddress << 5);
req.transfer[i].tx_buf = &req.command;
req.transfer[i].rx_buf = 0;
req.transfer[i].len = 2;
spi_message_add_tail(&req.transfer[i ++], &req.message);
/* Data */
while (j < numregs) {
req.transfer[i].tx_buf = 0;
req.transfer[i].rx_buf = &data[j ++];
req.transfer[i].len = 2;
req.transfer[i].cs_change = CS_CHANGE(j == numregs);
spi_message_add_tail(&req.transfer[i ++], &req.message);
}
ret = spi_sync(tsc.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);
}
u16 tsc2102_read_sync(int page, u8 address)
{
u16 ret;
tsc2102_reads_sync(page, address, &ret, 1);
return ret;
}
static void tsc2102_write_async(
struct tsc2102_spi_req *spi, int page, u8 address, u16 data,
void (*complete)(struct tsc2102_dev *context))
{
int ret;
spi_message_init(&spi->message);
spi->message.complete = (void (*)(void *)) complete;
spi->message.context = &tsc;
/* Address */
spi->command = (page << 11) | (address << 5);
spi->transfer[0].tx_buf = &spi->command;
spi->transfer[0].rx_buf = 0;
spi->transfer[0].len = 2;
spi_message_add_tail(&spi->transfer[0], &spi->message);
/* Data */
spi->data = data;
spi->transfer[1].tx_buf = &spi->data;
spi->transfer[1].rx_buf = 0;
spi->transfer[1].len = 2;
spi->transfer[1].cs_change = CS_CHANGE(1);
spi_message_add_tail(&spi->transfer[1], &spi->message);
ret = spi_async(spi->dev, &spi->message);
if (ret)
printk(KERN_ERR "%s: error %i in SPI request\n",
__FUNCTION__, ret);
}
static void tsc2102_reads_async(struct tsc2102_spi_req *spi,
int page, u8 startaddress, u16 *data, int numregs,
void (*complete)(struct tsc2102_dev *context))
{
int ret, i, j;
spi_message_init(&spi->message);
spi->message.complete = (void (*)(void *)) complete;
spi->message.context = &tsc;
i = 0;
j = 0;
/* Address */
spi->command = 0x8000 | (page << 11) | (startaddress << 5);
spi->transfer[i].tx_buf = &spi->command;
spi->transfer[i].rx_buf = 0;
spi->transfer[i].len = 2;
spi_message_add_tail(&spi->transfer[i ++], &spi->message);
/* Data */
while (j < numregs) {
spi->transfer[i].tx_buf = 0;
spi->transfer[i].rx_buf = &data[j ++];
spi->transfer[i].len = 2;
spi->transfer[i].cs_change = CS_CHANGE(j == numregs);
spi_message_add_tail(&spi->transfer[i ++], &spi->message);
}
ret = spi_async(spi->dev, &spi->message);
if (ret)
printk(KERN_ERR "%s: error %i in SPI request\n",
__FUNCTION__, ret);
}
static void tsc2102_read_async(struct tsc2102_spi_req *spi,
int page, u8 address, u16 *ret,
void (*complete)(struct tsc2102_dev *context))
{
tsc2102_reads_async(spi, page, address, ret, 1, complete);
}
static void tsc2102_request_alloc(struct tsc2102_dev *dev,
struct tsc2102_spi_req *spi, int direction, int numregs,
struct spi_transfer **buffer)
{
spi->dev = dev->spi;
if (direction == 1) /* Write */
numregs = 2;
else /* Read */
numregs += 1;
spi->transfer = *buffer;
*buffer += numregs;
}
#define tsc2102_cb_register_func(cb, cb_t) \
int tsc2102_ ## cb(cb_t handler) \
{ \
spin_lock(&tsc.lock); \
\
/* 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; \
\
spin_unlock(&tsc.lock); \
return 0; \
}
tsc2102_cb_register_func(touch_cb, tsc2102_touch_t)
tsc2102_cb_register_func(coords_cb, tsc2102_coords_t)
tsc2102_cb_register_func(ports_cb, tsc2102_ports_t)
tsc2102_cb_register_func(temp1_cb, tsc2102_temp_t)
tsc2102_cb_register_func(temp2_cb, tsc2102_temp_t)
#ifdef DEBUG
static void tsc2102_print_dav(void)
{
u16 status = tsc2102_read_sync(TSC2102_TS_STATUS_CTRL);
if (status & 0x0fff)
printk("TSC2102: 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 tsc2102_complete_dummy(struct tsc2102_dev *dev)
{
}
static inline void tsc2102_touchscreen_mode(struct tsc2102_dev *dev)
{
/* Scan X, Y, Z1, Z2, chip controlled, 12-bit, 16 samples, 500 usec */
tsc2102_write_async(&dev->req_mode,
TSC2102_TS_ADC_CTRL, TSC2102_ADC_TS_CONTROL,
tsc2102_complete_dummy);
}
static inline void tsc2102_portscan_mode(struct tsc2102_dev *dev)
{
/* Scan BAT1, BAT2, AUX, 12-bit, 16 samples, 500 usec */
tsc2102_write_async(&dev->req_mode,
TSC2102_TS_ADC_CTRL, TSC2102_ADC_SCAN_CONTROL,
tsc2102_complete_dummy);
}
static inline void tsc2102_temp1_mode(struct tsc2102_dev *dev)
{
/* Scan TEMP1, 12-bit, 16 samples, 500 usec */
tsc2102_write_async(&dev->req_mode,
TSC2102_TS_ADC_CTRL, TSC2102_ADC_T1_CONTROL,
tsc2102_complete_dummy);
}
static inline void tsc2102_temp2_mode(struct tsc2102_dev *dev)
{
/* Scan TEMP2, 12-bit, 16 samples, 500 usec */
tsc2102_write_async(&dev->req_mode,
TSC2102_TS_ADC_CTRL, TSC2102_ADC_T2_CONTROL,
tsc2102_complete_dummy);
}
static void tsc2102_mode(struct tsc2102_dev *dev)
{
switch (dev->state) {
case 0:
tsc2102_touchscreen_mode(dev);
break;
case 1:
tsc2102_portscan_mode(dev);
break;
case 2:
tsc2102_temp1_mode(dev);
break;
case 3:
tsc2102_temp2_mode(dev);
break;
default:
dev->state = 0;
tsc2102_touchscreen_mode(dev);
break;
}
}
/* Lock is held when this is called. */
static void tsc2102_new_mode(struct tsc2102_dev *dev)
{
/* Abort current conversion if any */
tsc2102_write_async(&dev->req_stopadc,
TSC2102_TS_ADC_CTRL, TSC2102_ADC_ADST,
tsc2102_complete_dummy);
dev->state ++;
tsc2102_mode(dev);
}
static void tsc2102_check_status(struct tsc2102_dev *dev);
/* TSC has new data for us availiable. */
static irqreturn_t tsc2102_handler(int irq, void *dev_id)
{
struct tsc2102_dev *dev = (struct tsc2102_dev *) dev_id;
spin_lock_irq(&dev->lock);
if (!dev->data_pending)
tsc2102_check_status(dev);
dev->data_pending ++;
spin_unlock_irq(&dev->lock);
return IRQ_HANDLED;
}
static void tsc2102_data_report(struct tsc2102_dev *dev)
{
if (dev->status & TSC2102_TS_DAV) {
if (dev->coords_cb)
dev->coords_cb(
dev->adc_data[0], dev->adc_data[1],
dev->adc_data[2], dev->adc_data[3]);
}
if (dev->status & TSC2102_PS_DAV) {
if (dev->ports_cb)
dev->ports_cb(dev->adc_data[0],
dev->adc_data[1], dev->adc_data[2]);
dev->bat[0] = dev->adc_data[0];
dev->bat[1] = dev->adc_data[1];
dev->aux[0] = dev->adc_data[2];
}
if (dev->status & TSC2102_T1_DAV) {
if (dev->temp1_cb)
dev->temp1_cb(*dev->adc_data);
dev->temp[0] = *dev->adc_data;
}
if (dev->status & TSC2102_T2_DAV) {
if (dev->temp2_cb)
dev->temp2_cb(*dev->adc_data);
dev->temp[1] = *dev->adc_data;
}
spin_lock_irq(&dev->lock);
dev->data_pending --;
/*
* This may happen if the registers were successfully read and a
* new conversion was started and completed by the TSC before the
* completion for SPI read was called.
*/
if (dev->data_pending)
tsc2102_check_status(dev);
if (dev->status & (TSC2102_PS_DAV | TSC2102_T1_DAV | TSC2102_T2_DAV))
tsc2102_new_mode(dev);
spin_unlock_irq(&dev->lock);
}
static void tsc2102_status_report(struct tsc2102_dev *dev)
{
/*
* Read all converted data from corresponding registers
* so that the ADC can move on to a new conversion.
*/
if (dev->status & TSC2102_TS_DAV) {
tsc2102_reads_async(&dev->req_adc, TSC2102_TS_X,
dev->adc_data, 4, tsc2102_data_report);
if (!dev->pendown) {
dev->pendown = 1;
if (dev->touch_cb)
dev->touch_cb(1);
mod_timer(&dev->ts_timer, jiffies +
msecs_to_jiffies(dev->ts_msecs));
}
}
if (dev->status & TSC2102_PS_DAV) {
tsc2102_reads_async(&dev->req_adc, TSC2102_TS_BAT1,
dev->adc_data, 3, tsc2102_data_report);
}
if (dev->status & TSC2102_T1_DAV) {
tsc2102_read_async(&dev->req_adc, TSC2102_TS_TEMP1,
dev->adc_data, tsc2102_data_report);
}
if (dev->status & TSC2102_T2_DAV) {
tsc2102_read_async(&dev->req_adc, TSC2102_TS_TEMP2,
dev->adc_data, tsc2102_data_report);
}
if (!(dev->status & (TSC2102_TS_DAV | TSC2102_PS_DAV |
TSC2102_T1_DAV | TSC2102_T2_DAV))) {
spin_lock_irq(&dev->lock);
dev->data_pending --;
spin_unlock_irq(&dev->lock);
WARN_ON(!dev->state);
}
}
static void tsc2102_check_status(struct tsc2102_dev *dev)
{
tsc2102_read_async(&dev->req_status, TSC2102_TS_STATUS_CTRL,
&dev->status, tsc2102_status_report);
}
static void tsc2102_mode_timer(unsigned long data)
{
struct tsc2102_dev *dev = (struct tsc2102_dev *) data;
spin_lock_irq(&dev->lock);
BUG_ON(dev->state);
tsc2102_new_mode(dev);
mod_timer(&dev->mode_timer, jiffies +
msecs_to_jiffies(dev->mode_msecs));
spin_unlock_irq(&dev->lock);
}
/*
* 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 all three indicate a pen-up, using a timer, before
* we report a pen-up.
*/
static void tsc2102_pressure_report(struct tsc2102_dev *dev)
{
if (!dev->pendown)
return;
if (dev->state ||
(dev->adc_status & TSC2102_ADC_PSTCM) ||
!(dev->adc_status & TSC2102_ADC_ADST)) {
mod_timer(&dev->ts_timer, jiffies +
msecs_to_jiffies(dev->ts_msecs));
} else {
dev->pendown = 0;
if (dev->touch_cb)
dev->touch_cb(0);
}
}
static void tsc2102_pressure(unsigned long data)
{
struct tsc2102_dev *dev = (struct tsc2102_dev *) data;
BUG_ON(!dev->pendown);
tsc2102_read_async(&dev->req_pressure, TSC2102_TS_ADC_CTRL,
&dev->adc_status, tsc2102_pressure_report);
}
#ifdef CONFIG_SOUND
/*
* Volume level values should be in the range [0, 127].
* Higher values mean lower volume.
*/
void tsc2102_set_volume(uint8_t left_ch, uint8_t right_ch)
{
u16 val;
if (left_ch == 0x00 || left_ch == 0x7f) /* All 0's or all 1's */
left_ch ^= 0x7f;
if (right_ch == 0x00 || right_ch == 0x7f)
right_ch ^= 0x7f;
spin_lock(&tsc.lock_sync);
val = tsc2102_read_sync(TSC2102_DAC_GAIN_CTRL);
val &= 0x8080; /* Preserve mute-bits */
val |= (left_ch << 8) | right_ch;
tsc2102_write_sync(TSC2102_DAC_GAIN_CTRL, val);
spin_unlock(&tsc.lock_sync);
}
void tsc2102_set_mute(int left_ch, int right_ch)
{
u16 val;
spin_lock(&tsc.lock_sync);
val = tsc2102_read_sync(TSC2102_DAC_GAIN_CTRL);
val &= 0x7f7f; /* Preserve volume settings */
val |= (left_ch << 15) | (right_ch << 7);
tsc2102_write_sync(TSC2102_DAC_GAIN_CTRL, val);
spin_unlock(&tsc.lock_sync);
}
void tsc2102_get_mute(int *left_ch, int *right_ch)
{
u16 val;
spin_lock(&tsc.lock_sync);
val = tsc2102_read_sync(TSC2102_DAC_GAIN_CTRL);
spin_unlock(&tsc.lock_sync);
*left_ch = !!(val & (1 << 15));
*right_ch = !!(val & (1 << 7));
}
void tsc2102_set_deemphasis(int enable)
{
u16 val;
spin_lock(&tsc.lock_sync);
val = tsc2102_read_sync(TSC2102_DAC_POWER_CTRL);
if (enable)
val &= ~TSC2102_DEEMPF;
else
val |= TSC2102_DEEMPF;
tsc2102_write_sync(TSC2102_DAC_POWER_CTRL, val);
spin_unlock(&tsc.lock_sync);
}
void tsc2102_set_bassboost(int enable)
{
u16 val;
spin_lock(&tsc.lock_sync);
val = tsc2102_read_sync(TSC2102_DAC_POWER_CTRL);
if (enable)
val &= ~TSC2102_BASSBC;
else
val |= TSC2102_BASSBC;
tsc2102_write_sync(TSC2102_DAC_POWER_CTRL, val);
spin_unlock(&tsc.lock_sync);
}
/* {rate, dsor, fsref} */
static const struct tsc2102_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 tsc2102_set_rate(int rate)
{
int i;
uint16_t val;
for (i = 0; tsc2102_rates[i].sample_rate; i ++)
if (tsc2102_rates[i].sample_rate == rate)
break;
if (tsc2102_rates[i].sample_rate == 0) {
printk(KERN_ERR "Unknown sampling rate %i.0 Hz\n", rate);
return -EINVAL;
}
spin_lock(&tsc.lock_sync);
tsc2102_write_sync(TSC2102_AUDIO1_CTRL, tsc2102_rates[i].divisor);
val = tsc2102_read_sync(TSC2102_AUDIO3_CTRL);
if (tsc2102_rates[i].fs_44k) {
tsc2102_write_sync(TSC2102_AUDIO3_CTRL, val | TSC2102_FS44K);
/* Enable Phase-locked-loop, set up clock dividers */
tsc2102_write_sync(TSC2102_PLL1_CTRL, TSC2102_PLL1_44K);
tsc2102_write_sync(TSC2102_PLL2_CTRL, TSC2102_PLL2_44K);
} else {
tsc2102_write_sync(TSC2102_AUDIO3_CTRL, val & ~TSC2102_FS44K);
/* Enable Phase-locked-loop, set up clock dividers */
tsc2102_write_sync(TSC2102_PLL1_CTRL, TSC2102_PLL1_48K);
tsc2102_write_sync(TSC2102_PLL2_CTRL, TSC2102_PLL2_48K);
}
spin_unlock(&tsc.lock_sync);
return 0;
}
/*
* Perform basic set-up with default values and power the DAC on.
*/
void tsc2102_dac_power(int state)
{
spin_lock(&tsc.lock_sync);
if (state) {
/* 16-bit words, DSP mode, sample at Fsref */
tsc2102_write_sync(TSC2102_AUDIO1_CTRL, 0x0100);
/* Keyclicks off, soft-stepping at normal rate */
tsc2102_write_sync(TSC2102_AUDIO2_CTRL, TSC2102_KEYCLICK_OFF);
/* 44.1 kHz Fsref, continuous transfer mode, master DAC */
tsc2102_write_sync(TSC2102_AUDIO3_CTRL, 0x2800);
/* Soft-stepping enabled */
tsc2102_write_sync(TSC2102_AUDIO4_CTRL, 0x0000);
/* PLL generates 44.1 kHz */
tsc2102_write_sync(TSC2102_PLL1_CTRL, TSC2102_PLL1_44K);
tsc2102_write_sync(TSC2102_PLL2_CTRL, TSC2102_PLL2_44K);
/* Codec & DAC power up, virtual ground disabled */
tsc2102_write_sync(TSC2102_DAC_POWER_CTRL, TSC2102_DAC_ON);
} else {
/* All off */
tsc2102_write_sync(TSC2102_AUDIO4_CTRL, TSC2102_KEYCLICK_OFF);
tsc2102_write_sync(TSC2102_PLL1_CTRL, TSC2102_PLL1_OFF);
}
spin_unlock(&tsc.lock_sync);
}
void tsc2102_set_i2s_master(int state)
{
uint16_t val;
spin_lock(&tsc.lock_sync);
val = tsc2102_read_sync(TSC2102_AUDIO3_CTRL);
if (state)
tsc2102_write_sync(TSC2102_AUDIO3_CTRL, val | TSC2102_SLVMS);
else
tsc2102_write_sync(TSC2102_AUDIO3_CTRL, val & ~TSC2102_SLVMS);
spin_unlock(&tsc.lock_sync);
}
#endif /* CONFIG_SOUND */
static int tsc2102_configure(struct tsc2102_dev *dev)
{
/* Reset the chip */
tsc2102_write_sync(TSC2102_TS_RESET_CTRL, TSC2102_RESET);
/* Reference mode, 100 usec delay, 1.25 V reference */
if (dev->pdata->use_internal)
tsc2102_write_sync(TSC2102_TS_REF_CTRL, TSC2102_ADC_INT_REF);
else
tsc2102_write_sync(TSC2102_TS_REF_CTRL, TSC2102_ADC_EXT_REF);
/* 84 usec precharge time, 32 usec sense time */
tsc2102_write_sync(TSC2102_TS_CONFIG_CTRL, TSC2102_CONFIG_TIMES);
/* PINT/DAV acts as DAV */
tsc2102_write_sync(TSC2102_TS_STATUS_CTRL, TSC2102_ADC_DAV);
tsc2102_mode(dev);
mod_timer(&dev->mode_timer, jiffies +
msecs_to_jiffies(dev->mode_msecs));
return 0;
}
/*
* Retrieves chip revision. Should be always 1.
*/
int tsc2102_get_revision(void)
{
return tsc2102_read_sync(TSC2102_AUDIO3_CTRL) & 7;
}
/*
* 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 doesn't sleep until the sound has
* finished.
*/
void tsc2102_keyclick(int amplitude, int freq, int length)
{
u16 val;
spin_lock(&tsc.lock_sync);
val = tsc2102_read_sync(TSC2102_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 (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;
tsc2102_write_sync(TSC2102_AUDIO2_CTRL, val);
spin_unlock(&tsc.lock_sync);
}
#ifdef CONFIG_HWMON
#define TSC2102_INPUT(devname, field) \
static ssize_t show_ ## devname(struct device *dev, \
struct device_attribute *devattr, char *buf) \
{ \
struct tsc2102_dev *devhwmon = dev_get_drvdata(dev); \
int value = devhwmon->field; \
return sprintf(buf, "%i\n", value); \
} \
static DEVICE_ATTR(devname ## _input, S_IRUGO, show_ ## devname, NULL);
TSC2102_INPUT(in0, bat[0])
TSC2102_INPUT(in1, bat[1])
TSC2102_INPUT(in2, aux[0])
TSC2102_INPUT(in3, temp[0])
TSC2102_INPUT(in4, temp[1])
static ssize_t show_temp1(struct device *dev,
struct device_attribute *devattr, char *buf)
{
struct tsc2102_dev *devhwmon = dev_get_drvdata(dev);
int t1, t2;
int value, diff;
t1 = devhwmon->temp[0];
t2 = devhwmon->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 = devhwmon->pdata->temp_at25c[1] - devhwmon->pdata->temp_at25c[0];
BUG_ON(diff == 0);
value = (t2 - t1) * 298150 / diff; /* This is in Kelvins now */
t1 = value - 273150; /* Celcius millidegree */
return sprintf(buf, "%i\n", t1);
}
static DEVICE_ATTR(temp1_input, S_IRUGO, show_temp1, NULL);
#endif /* CONFIG_HWMON */
#ifdef CONFIG_APM
static void tsc2102_get_power_status(struct apm_power_info *info)
{
tsc.pdata->apm_report(info, tsc.bat);
}
#endif
#ifdef CONFIG_PM
/*
* Suspend the chip.
*/
static int
tsc2102_suspend(struct spi_device *spi, pm_message_t state)
{
struct tsc2102_dev *dev = dev_get_drvdata(&spi->dev);
if (!dev)
return 0;
spin_lock(&dev->lock_sync);
del_timer(&dev->mode_timer);
del_timer(&dev->ts_timer);
if (dev->pendown && dev->touch_cb)
dev->touch_cb(0);
/* Abort current conversion and power down the ADC */
tsc2102_write_sync(TSC2102_TS_ADC_CTRL, TSC2102_ADC_ADST);
dev->spi->dev.power.power_state = state;
spin_unlock(&dev->lock_sync);
return 0;
}
/*
* Resume chip operation.
*/
static int tsc2102_resume(struct spi_device *spi)
{
struct tsc2102_dev *dev = dev_get_drvdata(&spi->dev);
int err;
if (!dev)
return 0;
spin_lock(&dev->lock_sync);
dev->spi->dev.power.power_state = PMSG_ON;
dev->state = 0;
dev->pendown = 0;
err = tsc2102_configure(dev);
spin_unlock(&dev->lock_sync);
return err;
}
#else
#define tsc2102_suspend NULL
#define tsc2102_resume NULL
#endif
static struct platform_device tsc2102_ts_device = {
.name = "tsc2102-ts",
.id = -1,
};
static struct platform_device tsc2102_alsa_device = {
.name = "tsc2102-alsa",
.id = -1,
};
static int tsc2102_probe(struct spi_device *spi)
{
struct tsc2102_config *pdata = spi->dev.platform_data;
struct spi_transfer *spi_buffer;
int err = 0;
if (!pdata) {
printk(KERN_ERR "TSC2102: Platform data not supplied\n");
return -ENOENT;
}
if (!spi->irq) {
printk(KERN_ERR "TSC2102: Invalid irq value\n");
return -ENOENT;
}
tsc.pdata = pdata;
tsc.state = 0;
tsc.pendown = 0;
tsc.data_pending = 0;
tsc.ts_msecs = 20;
tsc.mode_msecs = 1000;
tsc.spi = spi;
/* 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 "TSC2102: No memory for SPI buffers\n");
return -ENOMEM;
}
tsc.transfers = spi_buffer;
tsc2102_request_alloc(&tsc, &tsc.req_adc, 0, 4, &spi_buffer);
tsc2102_request_alloc(&tsc, &tsc.req_status, 0, 1, &spi_buffer);
tsc2102_request_alloc(&tsc, &tsc.req_pressure, 0, 1, &spi_buffer);
tsc2102_request_alloc(&tsc, &tsc.req_stopadc, 1, 1, &spi_buffer);
tsc2102_request_alloc(&tsc, &tsc.req_mode, 1, 1, &spi_buffer);
spin_lock_init(&tsc.lock);
spin_lock(&tsc.lock_sync);
/* Get the BCLK - assuming the rate is at 12000000 */
tsc.bclk_ck = clk_get(0, "bclk");
if (!tsc.bclk_ck) {
printk(KERN_ERR "Unable to get the clock BCLK\n");
err = -EPERM;
goto done;
}
clk_enable(tsc.bclk_ck);
if (request_irq(spi->irq, tsc2102_handler, IRQF_SAMPLE_RANDOM |
IRQF_TRIGGER_FALLING, "tsc2102", &tsc)) {
printk(KERN_ERR "Could not allocate touchscreen IRQ!\n");
err = -EINVAL;
goto err_clk;
}
setup_timer(&tsc.ts_timer,
tsc2102_pressure, (unsigned long) &tsc);
setup_timer(&tsc.mode_timer,
tsc2102_mode_timer, (unsigned long) &tsc);
/* Set up the communication bus */
dev_set_drvdata(&spi->dev, &tsc);
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_timer;
/* Now try to detect the chip, make first contact */
if (tsc2102_get_revision() != 0x1) {
printk(KERN_ERR "No TI TSC2102 chip found!\n");
goto err_timer;
}
err = tsc2102_configure(&tsc);
if (err)
goto err_timer;
/* Register devices controlled by TSC 2102 */
tsc2102_ts_device.dev.platform_data = pdata;
tsc2102_ts_device.dev.parent = &spi->dev;
err = platform_device_register(&tsc2102_ts_device);
if (err)
goto err_timer;
tsc2102_alsa_device.dev.platform_data = pdata->alsa_config;
tsc2102_alsa_device.dev.parent = &spi->dev;
err = platform_device_register(&tsc2102_alsa_device);
if (err)
goto err_ts;
#ifdef CONFIG_HWMON
tsc.hwmondev = hwmon_device_register(&spi->dev);
if (IS_ERR(tsc.hwmondev)) {
printk(KERN_ERR "tsc2102_hwmon: Device registration failed\n");
err = PTR_ERR(tsc.hwmondev);
goto err_alsa;
}
if (pdata->monitor & TSC_BAT1)
err |= device_create_file(&spi->dev, &dev_attr_in0_input);
if (pdata->monitor & TSC_BAT2)
err |= device_create_file(&spi->dev, &dev_attr_in1_input);
if (pdata->monitor & TSC_AUX)
err |= device_create_file(&spi->dev, &dev_attr_in2_input);
if (pdata->monitor & TSC_TEMP) {
err |= device_create_file(&spi->dev, &dev_attr_temp1_input);
err |= device_create_file(&spi->dev, &dev_attr_in3_input);
err |= device_create_file(&spi->dev, &dev_attr_in4_input);
}
if (err)
printk(KERN_ERR "tsc2102_hwmon: Creating one or more "
"attribute files failed\n");
err = 0; /* Not fatal */
#endif
#ifdef CONFIG_APM
if (pdata->apm_report)
apm_get_power_status = tsc2102_get_power_status;
#endif
if (!err)
goto done;
err_alsa:
platform_device_unregister(&tsc2102_alsa_device);
err_ts:
platform_device_unregister(&tsc2102_ts_device);
err_timer:
del_timer(&tsc.ts_timer);
del_timer(&tsc.mode_timer);
dev_set_drvdata(&spi->dev, NULL);
err_clk:
clk_disable(tsc.bclk_ck);
clk_put(tsc.bclk_ck);
done:
spin_unlock(&tsc.lock_sync);
return err;
}
static int tsc2102_remove(struct spi_device *spi)
{
struct tsc2102_dev *dev = dev_get_drvdata(&spi->dev);
spin_lock(&dev->lock_sync);
platform_device_unregister(&tsc2102_ts_device);
platform_device_unregister(&tsc2102_alsa_device);
dev_set_drvdata(&spi->dev, NULL);
/* Release the BCLK */
clk_disable(dev->bclk_ck);
clk_put(dev->bclk_ck);
del_timer(&tsc.mode_timer);
del_timer(&tsc.ts_timer);
kfree(tsc.transfers);
#ifdef CONFIG_HWMON
hwmon_device_unregister(dev->hwmondev);
#endif
#ifdef CONFIG_APM
apm_get_power_status = 0;
#endif
spin_unlock(&dev->lock_sync);
return 0;
}
static struct spi_driver tsc2102_driver = {
.probe = tsc2102_probe,
.remove = tsc2102_remove,
.suspend = tsc2102_suspend,
.resume = tsc2102_resume,
.driver = {
.name = "tsc2102",
.owner = THIS_MODULE,
.bus = &spi_bus_type,
},
};
static char __initdata banner[] = KERN_INFO "TI TSC2102 driver initializing\n";
static int __init tsc2102_init(void)
{
printk(banner);
return spi_register_driver(&tsc2102_driver);
}
static void __exit tsc2102_exit(void)
{
spi_unregister_driver(&tsc2102_driver);
}
module_init(tsc2102_init);
module_exit(tsc2102_exit);
EXPORT_SYMBOL(tsc2102_read_sync);
EXPORT_SYMBOL(tsc2102_reads_sync);
EXPORT_SYMBOL(tsc2102_write_sync);
EXPORT_SYMBOL(tsc2102_keyclick);
MODULE_AUTHOR("Andrzej Zaborowski");
MODULE_DESCRIPTION("Interface driver for TI TSC2102 chips.");
MODULE_LICENSE("GPL");
/*
* include/linux/spi/tsc2102.h
*
* TI TSC2102 Touchscreen, Audio and Battery control register definitions
*
* Copyright (c) 2005 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_TSC2102_H
#define __LINUX_SPI_TSC2102_H
struct apm_power_info;
struct tsc2102_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 */
};
#define TSC_BAT1 (1 << 0)
#define TSC_BAT2 (1 << 1)
#define TSC_AUX (1 << 2)
#define TSC_TEMP (1 << 4)
extern u16 tsc2102_read_sync(int page, u8 address);
extern void tsc2102_reads_sync(int page, u8 startaddress, u16 *data,
int numregs);
extern void tsc2102_write_sync(int page, u8 address, u16 data);
typedef void (*tsc2102_touch_t)(int touching);
typedef void (*tsc2102_coords_t)(int x, int y, int z1, int z2);
typedef void (*tsc2102_ports_t)(int bat1, int bat2, int aux);
typedef void (*tsc2102_temp_t)(int temp);
extern int tsc2102_touch_cb(tsc2102_touch_t handler);
extern int tsc2102_coords_cb(tsc2102_coords_t handler);
extern int tsc2102_ports_cb(tsc2102_ports_t handler);
extern int tsc2102_temp1_cb(tsc2102_temp_t handler);
extern int tsc2102_temp2_cb(tsc2102_temp_t handler);
#ifdef CONFIG_SOUND
extern void tsc2102_set_volume(uint8_t left_ch, uint8_t right_ch);
extern void tsc2102_set_mute(int left_ch, int right_ch);
extern void tsc2102_get_mute(int *left_ch, int *right_ch);
extern void tsc2102_dac_power(int state);
extern int tsc2102_set_rate(int rate);
extern void tsc2102_set_i2s_master(int state);
extern void tsc2102_set_deemphasis(int enable);
extern void tsc2102_set_bassboost(int enable);
#endif
extern void tsc2102_keyclick(int amplitude, int freq, int length);
#define TSC2102_REG(pg, addr) pg, addr
/* Page 0, Touch Screen & Keypad Data registers */
#define TSC2102_TS_X TSC2102_REG(0, 0x00)
#define TSC2102_TS_Y TSC2102_REG(0, 0x01)
#define TSC2102_TS_Z1 TSC2102_REG(0, 0x02)
#define TSC2102_TS_Z2 TSC2102_REG(0, 0x03)
#define TSC2102_TS_BAT1 TSC2102_REG(0, 0x05)
#define TSC2102_TS_BAT2 TSC2102_REG(0, 0x06)
#define TSC2102_TS_AUX TSC2102_REG(0, 0x07)
#define TSC2102_TS_TEMP1 TSC2102_REG(0, 0x09)
#define TSC2102_TS_TEMP2 TSC2102_REG(0, 0x0a)
/* Page 1, Touch Screen & Keypad Control registers */
#define TSC2102_TS_ADC_CTRL TSC2102_REG(1, 0x00)
#define TSC2102_TS_STATUS_CTRL TSC2102_REG(1, 0x01)
#define TSC2102_TS_REF_CTRL TSC2102_REG(1, 0x03)
#define TSC2102_TS_RESET_CTRL TSC2102_REG(1, 0x04)
#define TSC2102_TS_CONFIG_CTRL TSC2102_REG(1, 0x05)
/* Page 2, Audio Control registers */
#define TSC2102_AUDIO1_CTRL TSC2102_REG(2, 0x00)
#define TSC2102_DAC_GAIN_CTRL TSC2102_REG(2, 0x02)
#define TSC2102_AUDIO2_CTRL TSC2102_REG(2, 0x04)
#define TSC2102_DAC_POWER_CTRL TSC2102_REG(2, 0x05)
#define TSC2102_AUDIO3_CTRL TSC2102_REG(2, 0x06)
#define TSC2102_LCH_BASS_BOOST_N0 TSC2102_REG(2, 0x07)
#define TSC2102_LCH_BASS_BOOST_N1 TSC2102_REG(2, 0x08)
#define TSC2102_LCH_BASS_BOOST_N2 TSC2102_REG(2, 0x09)
#define TSC2102_LCH_BASS_BOOST_N3 TSC2102_REG(2, 0x0a)
#define TSC2102_LCH_BASS_BOOST_N4 TSC2102_REG(2, 0x0b)
#define TSC2102_LCH_BASS_BOOST_N5 TSC2102_REG(2, 0x0c)
#define TSC2102_LCH_BASS_BOOST_D1 TSC2102_REG(2, 0x0d)
#define TSC2102_LCH_BASS_BOOST_D2 TSC2102_REG(2, 0x0e)
#define TSC2102_LCH_BASS_BOOST_D4 TSC2102_REG(2, 0x0f)
#define TSC2102_LCH_BASS_BOOST_D5 TSC2102_REG(2, 0x10)
#define TSC2102_RCH_BASS_BOOST_N0 TSC2102_REG(2, 0x11)
#define TSC2102_RCH_BASS_BOOST_N1 TSC2102_REG(2, 0x12)
#define TSC2102_RCH_BASS_BOOST_N2 TSC2102_REG(2, 0x13)
#define TSC2102_RCH_BASS_BOOST_N3 TSC2102_REG(2, 0x14)
#define TSC2102_RCH_BASS_BOOST_N4 TSC2102_REG(2, 0x15)
#define TSC2102_RCH_BASS_BOOST_N5 TSC2102_REG(2, 0x16)
#define TSC2102_RCH_BASS_BOOST_D1 TSC2102_REG(2, 0x17)
#define TSC2102_RCH_BASS_BOOST_D2 TSC2102_REG(2, 0x18)
#define TSC2102_RCH_BASS_BOOST_D4 TSC2102_REG(2, 0x19)
#define TSC2102_RCH_BASS_BOOST_D5 TSC2102_REG(2, 0x1a)
#define TSC2102_PLL1_CTRL TSC2102_REG(2, 0x1b)
#define TSC2102_PLL2_CTRL TSC2102_REG(2, 0x1c)
#define TSC2102_AUDIO4_CTRL TSC2102_REG(2, 0x1d)
/* 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 TSC2102_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 TSC2102_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 TSC2101_AUDIO_CTRL_3 */
#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 TSC2102_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 TSC2102_PLL2_CTRL */
#define PLL2_D_VAL(ARG) (((ARG) & 0x3fff) << 2)
/* Field masks for TSC2101_AUDIO_CTRL_4 */
#define AC4_DASTPD (1 << 14)
struct tsc2102_rate_info_s {
u16 sample_rate;
u8 divisor;
u8 fs_44k; /* 44.1 kHz Fsref if 1, 48 kHz if 0 */
};
#endif /* __LINUX_SPI_TSC2102_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