579 lines
16 KiB
C

// ===========================================================================
// orizon_ts.c
// Copyright (C) 2003-2010 Bookeen - All rights reserved
// ===========================================================================
/* TODO: Verify if all this includes are necessary */
#include <linux/kernel.h>
#include <linux/module.h>
#include <asm/atomic.h>
#include <asm/io.h>
#include <asm/arch/irqs.h>
#include <asm/arch/gpio.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/proc_fs.h>
#include <linux/kthread.h>
#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/ctype.h>
#include <cybook.h>
#include <linux/cyio.h>
#define DEBUG_MESSAGES
//#define DEBUG_TRACEFUNC
#define MODULE_NAME "ORIZON-TS"
#define FINGER_NUM 0x00
#define INT_DELAY 0x64 /* Default 0x64 */
#define INT_MODE 0x01
#define X_SENS 0x22 /* Default 0x14 */
#define Y_SENS 0x22 /* Default 0x14 */
#ifdef DEBUG_MESSAGES
enum InfoLevel
{
INFO_ERROR = 0,
INFO_WARNING,
INFO_NORMAL,
INFO_DEBUG,
INFO_VERBOSE,
};
# ifndef VERBOSE_LEVEL
# define VERBOSE_LEVEL INFO_VERBOSE
# endif
# ifdef DEBUG_TRACEFUNC
static int _dbg_FunctionLevel = 0;
# define MSG(str) {\
int __i;\
printk(KERN_ALERT "+");\
for (__i = 0; __i < _dbg_FunctionLevel; __i++)\
printk("-");\
printk("||" str "\n");\
}
# define DBG(str, ...) {\
int __i;\
printk(KERN_ALERT "+");\
for (__i = 0; __i < _dbg_FunctionLevel; __i++)\
printk("-");\
printk("||" str "\n", __VA_ARGS__);\
}
# define INFOL(level, s) do {\
if (level <= VERBOSE_LEVEL) {\
int __i;\
printk(KERN_ALERT "+");\
for (__i = 0; __i < _dbg_FunctionLevel; __i++)\
printk("-");\
printk("<%d>%s:%s(): ", level, __FILE__, __func__); printk s; printk("\n");\
}\
} while(0)
# define FUNC_IN() {\
int __i;\
_dbg_FunctionLevel++;\
printk(KERN_ALERT "+");\
for (__i = 0; __i < _dbg_FunctionLevel; __i++)\
printk("-");\
printk(">> %s() >>\n", __func__);\
}
# define FUNC_OUT() {\
int __i;\
printk(KERN_ALERT "+");\
for (__i = 0; __i < _dbg_FunctionLevel; __i++)\
printk("-");\
printk("<< %s() <<\n", __func__);\
_dbg_FunctionLevel--;\
}
# define FUNC_OUTR(val) {\
int __i;\
printk(KERN_ALERT "+");\
for (__i = 0; __i < _dbg_FunctionLevel; __i++)\
printk("-");\
printk("<< %s() = %d <<\n", __func__, val);\
_dbg_FunctionLevel--;\
}
# else /* DEBUG_TRACEFUNC */
# define MSG(str) do {\
printk(KERN_ALERT MODULE_NAME ": " str "\n");\
} while(0)
# define DBG(str, ...) do {\
printk(KERN_ALERT MODULE_NAME ": " str "\n", __VA_ARGS__);\
} while(0)
# define FUNC_IN() do {\
} while(0)
# define FUNC_OUT() do {\
} while(0)
# define FUNC_OUTR(val) do {\
printk(KERN_ALERT MODULE_NAME ": %s() return %d\n", __func__, val);\
} while(0)
# define INFOL(level, s) do {\
if (level <= VERBOSE_LEVEL) {\
printk("<%d>%s:%s(): ", level, __FILE__, __func__); printk s; printk("\n");\
}\
} while(0)
# endif /* DEBUG_TRACEFUNC */
#else /* DEBUG_MESSAGES */
# define MSG(str)
# define DBG(str, ...)
# define FUNC_IN()
# define FUNC_OUT()
# define FUNC_OUTR(val)
# define INFOL(level, s)
# define INFO(s)
#endif /* DEBUG_MESSAGES */
typedef enum
{
POWER_UNDEFINED = -1,
POWER_OFF = 0,
POWER_ON,
POWER_ONAUTOSTANDBY,
POWER_DEEPSLEEP,
} Ots_PowerModes;
/*============================================================================*/
/*============================= Prototypes ===================================*/
/*============================================================================*/
/**************************** i2c functions ***********************************/
static int ots_attachAdapter (struct i2c_adapter *adapter);
static int ots_detect (struct i2c_adapter *adapter, int address, int kind);
static int ots_detachClient (struct i2c_client *client);
static int ots_suspend (struct device *dev, pm_message_t state);
static int ots_resume (struct device *dev);
//static void ots_dumpI2C(void);
/**************************** irq functions ***********************************/
static void ots_checkWorkFunction (struct work_struct *work);
static irqreturn_t ots_interrupt (int irq, void *dev_id);
/******************************* Chip functions *******************************/
static void ots_setPowerMode (Ots_PowerModes power);
static void ots_setDeviceParameters (unsigned char int_mode,
unsigned char x_sensitivity,
unsigned char y_sensitivity);
static void ots_ackInterrupt (void);
/****************************** Module functions ******************************/
static int __init ots_init (void);
static void __exit ots_exit (void);
/*============================= End of prototypes ============================*/
/*============================================================================*/
/*============================= Variables ====================================*/
/*============================================================================*/
struct workqueue_struct *ots_check_workqueue;
struct work_struct ots_check_work;
static Ots_PowerModes ots_currentPowerMode = POWER_UNDEFINED;
/****************************** i2c configuration *****************************/
#define OTS_ADDR_I2C 0x5C
static unsigned short normal_i2c[] = { OTS_ADDR_I2C,
I2C_CLIENT_END };
/* Insmod parameters */
I2C_CLIENT_INSMOD_1 (ots);
struct i2c_client *ots_client;
/* Each client has this additional data */
struct ots_data
{
struct i2c_client client;
};
/* This is the I2C driver that will be inserted */
static struct i2c_driver ots_driver ={
.driver =
{
.name = "orizon_ts",
.suspend = ots_suspend,
.resume = ots_resume,
},
.id = I2C_DRIVERID_EEPROM,
.attach_adapter = ots_attachAdapter,
.detach_client = ots_detachClient,
};
/******************************************************************************/
/**************************** i2c functions ***********************************/
/******************************************************************************/
#if 0
static void ots_dumpI2C(void)
{
int Addr = 0;
unsigned char value;
int I,J;
printk("-------------------------------------------------------------------------------\n");
printk("-------------------------------------I²C TS------------------------------------\n");
for ( J = 0; J < 16; J++ )
{
printk("%04X: ", Addr);
for ( I = 0; I < 16; I++, Addr++ )
{
value = i2c_smbus_read_byte_data(ots_client, Addr);
mdelay(4);
printk("%02X ", value);
}
printk(" | ");
Addr -= 16;
for ( I = 0; I < 16; I++, Addr++ )
{
value = i2c_smbus_read_byte_data(ots_client, Addr);
mdelay(4);
printk("%c", isprint(value) ? value : '.');
}
printk("\n");
}
printk("-------------------------------------------------------------------------------\n");
printk("-------------------------------------------------------------------------------\n");
}
#endif
static int ots_attachAdapter (struct i2c_adapter *adapter)
{
return i2c_probe(adapter, &addr_data, ots_detect);
}
/* This function is called by i2c_probe */
static int ots_detect (struct i2c_adapter *adapter, int address, int kind)
{
struct ots_data *data = NULL;
int err = 0;
FUNC_IN();
//DBG(">>%s(%p, 0x%X, 0x%X)", __func__, adapter, address, kind);
if ( !i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA
| I2C_FUNC_SMBUS_BYTE) )
goto exit;
if ( address == OTS_ADDR_I2C )
{
if ( !(data = kzalloc(sizeof (struct ots_data), GFP_KERNEL)) )
{
err = -ENOMEM;
goto exit;
}
ots_client = &data->client;
i2c_set_clientdata(ots_client, data);
ots_client->addr = address;
ots_client->adapter = adapter;
ots_client->driver = &ots_driver;
ots_client->flags = 0;
/* Fill in the remaining client fields */
strlcpy(ots_client->name, "orizon_ts", I2C_NAME_SIZE);
/* Tell the I2C layer a new client has arrived */
if ( (err = i2c_attach_client(ots_client)) )
{
goto exit_kfree;
}
ots_setPowerMode(POWER_ONAUTOSTANDBY);
ots_setDeviceParameters(INT_MODE, X_SENS, Y_SENS);
/* Now we are sure that the driver init successfully, then aquire the IRQ */
set_irq_type(IRQ_EINT2, IRQT_FALLING);
if ( request_irq(IRQ_EINT2, ots_interrupt, SA_SHIRQ, "orizon_ts", &ots_client) )
{
printk(KERN_ERR "failed to get interrupt resouce at IRQ_EINT2.\n");
goto exit_kfree;
}
}
goto exit;
exit_kfree:
kfree(data);
exit:
FUNC_OUTR(err);
return err;
}
static int ots_detachClient (struct i2c_client *client)
{
int err = 0;
FUNC_IN();
err = i2c_detach_client(client);
if ( err )
goto exit;
kfree(i2c_get_clientdata(client));
exit:
FUNC_OUTR(err);
return err;
}
static int ots_suspend (struct device *dev, pm_message_t state)
{
int ret = 0;
FUNC_IN();
FUNC_OUTR(ret);
return ret;
}
static int ots_resume (struct device *dev)
{
FUNC_IN();
FUNC_OUT();
return 0;
}
/******************************************************************************/
/********************** Interrupt Related functions ***************************/
/******************************************************************************/
static void ots_checkWorkFunction (struct work_struct *work)
{
unsigned long x1, y1, x2, y2;
FUNC_IN();
/* Here do what the interrupt should... (ie read touch values) */
x1 = i2c_smbus_read_byte_data(ots_client, 0);
x1 |= i2c_smbus_read_byte_data(ots_client, 1) << 8;
y1 = i2c_smbus_read_byte_data(ots_client, 2);
y1 |= i2c_smbus_read_byte_data(ots_client, 3) << 8;
x2 = i2c_smbus_read_byte_data(ots_client, 4);
x2 |= i2c_smbus_read_byte_data(ots_client, 5) << 8;
y2 = i2c_smbus_read_byte_data(ots_client, 6);
y2 |= i2c_smbus_read_byte_data(ots_client, 7) << 8;
DBG("x1: %lu\ty1: %lu\ty1: %lu\ty2: %lu", x1, y1, x2, y2);
/* Say I get the data */
ots_ackInterrupt();
FUNC_OUT();
}
static irqreturn_t ots_interrupt (int irq, void *dev_id)
{
irqreturn_t ret = IRQ_HANDLED;
static int initialised = 0;
FUNC_IN();
if (initialised == 0)
{
INIT_WORK(&ots_check_work, ots_checkWorkFunction);
initialised = 1;
}
else
{
PREPARE_WORK(&ots_check_work, ots_checkWorkFunction);
}
schedule_work(&ots_check_work);
//FUNC_OUTR((int)ret);
return ret;
}
/******************************************************************************/
/******************************* Chip functions *******************************/
/******************************************************************************/
static void ots_setPowerMode (Ots_PowerModes power)
{
unsigned char tmpReg;
FUNC_IN();
if (power == ots_currentPowerMode)
return; /* No need to do anything, we are in the same power mode... */
switch(power)
{
default:
case POWER_UNDEFINED:
/* Error */
DBG("Error: invalid power mode #%d", power);
break;
case POWER_ON:
/* Set the power to on */
s3c2410_gpio_setpin(S3C2410_GPD10, 1);
msleep(100);
/* TODO: Does we need to set chip settings? */
tmpReg = 0x0; /* Set in "Active Mode" */
i2c_smbus_write_byte_data(ots_client, 0x73, tmpReg);
msleep(400);
break;
case POWER_OFF:
/* Set the power to off */
s3c2410_gpio_setpin(S3C2410_GPD10, 0);
msleep(10);
break;
case POWER_ONAUTOSTANDBY:
if ( (ots_currentPowerMode != POWER_ON) &&
(ots_currentPowerMode != POWER_DEEPSLEEP) )
ots_setPowerMode(POWER_ON); /* Set myself as Power ON before anything */
//tmpReg = i2c_smbus_read_byte_data(ots_client, 0x24);
tmpReg = (((50) /* timeout in ms for auto sleep */ ) & 0x0F) << 4;
tmpReg |= (1<< 2); /* Activate auto sleep mode */
tmpReg |= 0x1; /* Set in "Sleep Mode" */
i2c_smbus_write_byte_data(ots_client, 0x73, tmpReg);
msleep(4);
break;
case POWER_DEEPSLEEP:
if ( (ots_currentPowerMode != POWER_ON) &&
(ots_currentPowerMode != POWER_ONAUTOSTANDBY) )
ots_setPowerMode(POWER_ON); /* Set myself as Power ON before anything */
tmpReg = 0x02;
i2c_smbus_write_byte_data(ots_client, 0x73, tmpReg);
msleep(4);
break;
}
ots_currentPowerMode = power;
FUNC_OUT();
}
static void ots_setDeviceParameters (unsigned char int_mode,
unsigned char x_sensitivity,
unsigned char y_sensitivity)
{
unsigned char tmpReg;
FUNC_IN();
/* Then set INT_WIDTH */
/*
* Interrupt width register @114 [0x72]
* b7-b0: int width
* Default: 0x64
*/
i2c_smbus_write_byte_data(ots_client, 0x72, INT_DELAY);
mdelay(4);
/* Now set sensitivity */
i2c_smbus_write_byte_data(ots_client, 0x6F, x_sensitivity);
mdelay(4);
i2c_smbus_write_byte_data(ots_client, 0x70, y_sensitivity);
mdelay(4);
i2c_smbus_write_byte_data(ots_client, 0xBE, 0x01); // TEST
tmpReg = i2c_smbus_read_byte_data(ots_client, 0xBE); // TEST
DBG("0xBE = %02X", tmpReg);
DBG("Firmware Version =0x%x\n", i2c_smbus_read_byte_data(ots_client, 0x77));
/* Activate the device ! */
/*
* Interrupt mode Setting register @113 [0x71]
* b7-b6-b5: TP_NUM : How many finger, 000: none, 001: One, 010: Two
* b4: INT_RELEASE: used to ack (1) IRQ in periodical mode
* b3: EN_INT: Enable int (1: enabled, 0: disable)
* b2: INT_POL: 0: active low, 1: active high
* b1-b0: INT_MODE: 00: INT periodicaly, 01: INT assert when coord changes
* 10: TOuch indicate, 11: INT assert when INT_RELEASE modified
*
* Default 0x0C [000 0 1 1 00]
* Qisda: 0x0A [000 0 1 0 10]
* Int periodicaly, Active High, and INT enabled
*/
/* 2 Finger Active High Mode */
tmpReg = (FINGER_NUM << 5) | (1 << 3) | (0 << 2) | ((int_mode & 0x03) << 0);
DBG("Settings: 0x%02X", tmpReg);
i2c_smbus_write_byte_data(ots_client, 0x71, tmpReg);
mdelay(4);
tmpReg = i2c_smbus_read_byte_data(ots_client, 0x71); // TEST
DBG("[0x71] Settings = %02X", tmpReg);
FUNC_OUT();
}
static void ots_ackInterrupt (void)
{
unsigned char tmpReg;
FUNC_IN();
tmpReg = i2c_smbus_read_byte_data(ots_client, 0x71);
tmpReg |= (1<<4);
i2c_smbus_write_byte_data(ots_client, 0x71, tmpReg);
FUNC_OUT();
}
/******************************************************************************/
/****************************** Module functions ******************************/
/******************************************************************************/
static int __init ots_init (void)
{
FUNC_IN();
/* if (GET_CAPABILITY(PLAT_CAP_GSENSOR) == PLAT_CAP_GMMA7660)
{
}*/
/* Init GPIOs */
s3c2410_gpio_cfgpin(S3C2410_GPD10, S3C2410_GPD10_OUTP);
s3c2410_gpio_pullup(S3C2410_GPD10, 2);
i2c_add_driver(&ots_driver);
FUNC_OUT();
return 0;
}
// ---------------------------------------------------------------------------
static void __exit ots_exit (void)
{
FUNC_IN();
/*if (GET_CAPABILITY(PLAT_CAP_GSENSOR) == PLAT_CAP_GMMA7660)
{
}*/
/* Deinit GPIOs */
/* Nothing to do, leave it as output is good enought, just set the
* device as power off */
ots_setPowerMode(POWER_OFF);
i2c_del_driver(&ots_driver);
FUNC_OUT();
}
// ---------------------------------------------------------------------------
module_init (ots_init);
module_exit (ots_exit);
// ---------------------------------------------------------------------------
MODULE_LICENSE ("GPL");
MODULE_AUTHOR ("Bookeen <developers@bookeen.com>");
MODULE_VERSION ("1.0");
MODULE_DESCRIPTION ("Cybook Orizon Touchpanel driver");
// ===========================================================================