2011-01-30 23:31:30 +03:00
/*
* Touchscreen driver for WM831x PMICs
*
* Copyright 2011 Wolfson Microelectronics plc .
* Author : Mark Brown < broonie @ opensource . wolfsonmicro . com >
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation ; either version 2 of the License , or ( at your
* option ) any later version .
*/
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/string.h>
# include <linux/pm.h>
# include <linux/input.h>
# include <linux/interrupt.h>
# include <linux/io.h>
# include <linux/mfd/wm831x/core.h>
# include <linux/mfd/wm831x/irq.h>
# include <linux/mfd/wm831x/pdata.h>
# include <linux/platform_device.h>
# include <linux/slab.h>
# include <linux/types.h>
/*
* R16424 ( 0x4028 ) - Touch Control 1
*/
# define WM831X_TCH_ENA 0x8000 /* TCH_ENA */
# define WM831X_TCH_CVT_ENA 0x4000 /* TCH_CVT_ENA */
# define WM831X_TCH_SLPENA 0x1000 /* TCH_SLPENA */
# define WM831X_TCH_Z_ENA 0x0400 /* TCH_Z_ENA */
# define WM831X_TCH_Y_ENA 0x0200 /* TCH_Y_ENA */
# define WM831X_TCH_X_ENA 0x0100 /* TCH_X_ENA */
# define WM831X_TCH_DELAY_MASK 0x00E0 /* TCH_DELAY - [7:5] */
# define WM831X_TCH_DELAY_SHIFT 5 /* TCH_DELAY - [7:5] */
# define WM831X_TCH_DELAY_WIDTH 3 /* TCH_DELAY - [7:5] */
# define WM831X_TCH_RATE_MASK 0x001F /* TCH_RATE - [4:0] */
# define WM831X_TCH_RATE_SHIFT 0 /* TCH_RATE - [4:0] */
# define WM831X_TCH_RATE_WIDTH 5 /* TCH_RATE - [4:0] */
/*
* R16425 ( 0x4029 ) - Touch Control 2
*/
# define WM831X_TCH_PD_WK 0x2000 /* TCH_PD_WK */
# define WM831X_TCH_5WIRE 0x1000 /* TCH_5WIRE */
# define WM831X_TCH_PDONLY 0x0800 /* TCH_PDONLY */
# define WM831X_TCH_ISEL 0x0100 /* TCH_ISEL */
# define WM831X_TCH_RPU_MASK 0x000F /* TCH_RPU - [3:0] */
# define WM831X_TCH_RPU_SHIFT 0 /* TCH_RPU - [3:0] */
# define WM831X_TCH_RPU_WIDTH 4 /* TCH_RPU - [3:0] */
/*
* R16426 - 8 ( 0x402A - C ) - Touch Data X / Y / X
*/
# define WM831X_TCH_PD 0x8000 /* TCH_PD1 */
# define WM831X_TCH_DATA_MASK 0x0FFF /* TCH_DATA - [11:0] */
# define WM831X_TCH_DATA_SHIFT 0 /* TCH_DATA - [11:0] */
# define WM831X_TCH_DATA_WIDTH 12 /* TCH_DATA - [11:0] */
struct wm831x_ts {
struct input_dev * input_dev ;
struct wm831x * wm831x ;
unsigned int data_irq ;
unsigned int pd_irq ;
bool pressure ;
bool pen_down ;
2011-04-28 10:08:34 +04:00
struct work_struct pd_data_work ;
2011-01-30 23:31:30 +03:00
} ;
2011-04-28 10:08:34 +04:00
static void wm831x_pd_data_work ( struct work_struct * work )
{
struct wm831x_ts * wm831x_ts =
container_of ( work , struct wm831x_ts , pd_data_work ) ;
if ( wm831x_ts - > pen_down ) {
enable_irq ( wm831x_ts - > data_irq ) ;
dev_dbg ( wm831x_ts - > wm831x - > dev , " IRQ PD->DATA done \n " ) ;
} else {
enable_irq ( wm831x_ts - > pd_irq ) ;
dev_dbg ( wm831x_ts - > wm831x - > dev , " IRQ DATA->PD done \n " ) ;
}
}
2011-01-30 23:31:30 +03:00
static irqreturn_t wm831x_ts_data_irq ( int irq , void * irq_data )
{
struct wm831x_ts * wm831x_ts = irq_data ;
struct wm831x * wm831x = wm831x_ts - > wm831x ;
static int data_types [ ] = { ABS_X , ABS_Y , ABS_PRESSURE } ;
u16 data [ 3 ] ;
2011-02-01 08:09:25 +03:00
int count ;
2011-01-30 23:31:30 +03:00
int i , ret ;
2011-02-01 08:09:25 +03:00
if ( wm831x_ts - > pressure )
count = 3 ;
else
count = 2 ;
2011-01-30 23:31:30 +03:00
wm831x_set_bits ( wm831x , WM831X_INTERRUPT_STATUS_1 ,
WM831X_TCHDATA_EINT , WM831X_TCHDATA_EINT ) ;
ret = wm831x_bulk_read ( wm831x , WM831X_TOUCH_DATA_X , count ,
data ) ;
if ( ret ! = 0 ) {
dev_err ( wm831x - > dev , " Failed to read touch data: %d \n " ,
ret ) ;
return IRQ_NONE ;
}
/*
* We get a pen down reading on every reading , report pen up if any
* individual reading does so .
*/
wm831x_ts - > pen_down = true ;
for ( i = 0 ; i < count ; i + + ) {
if ( ! ( data [ i ] & WM831X_TCH_PD ) ) {
wm831x_ts - > pen_down = false ;
continue ;
}
input_report_abs ( wm831x_ts - > input_dev , data_types [ i ] ,
data [ i ] & WM831X_TCH_DATA_MASK ) ;
}
if ( ! wm831x_ts - > pen_down ) {
2011-04-28 10:08:34 +04:00
/* Switch from data to pen down */
dev_dbg ( wm831x - > dev , " IRQ DATA->PD \n " ) ;
2011-01-30 23:31:30 +03:00
disable_irq_nosync ( wm831x_ts - > data_irq ) ;
/* Don't need data any more */
wm831x_set_bits ( wm831x , WM831X_TOUCH_CONTROL_1 ,
WM831X_TCH_X_ENA | WM831X_TCH_Y_ENA |
WM831X_TCH_Z_ENA , 0 ) ;
/* Flush any final samples that arrived while reading */
wm831x_set_bits ( wm831x , WM831X_INTERRUPT_STATUS_1 ,
WM831X_TCHDATA_EINT , WM831X_TCHDATA_EINT ) ;
wm831x_bulk_read ( wm831x , WM831X_TOUCH_DATA_X , count , data ) ;
if ( wm831x_ts - > pressure )
input_report_abs ( wm831x_ts - > input_dev ,
ABS_PRESSURE , 0 ) ;
input_report_key ( wm831x_ts - > input_dev , BTN_TOUCH , 0 ) ;
2011-04-28 10:08:34 +04:00
schedule_work ( & wm831x_ts - > pd_data_work ) ;
2011-04-28 10:09:26 +04:00
} else {
input_report_key ( wm831x_ts - > input_dev , BTN_TOUCH , 1 ) ;
2011-01-30 23:31:30 +03:00
}
input_sync ( wm831x_ts - > input_dev ) ;
return IRQ_HANDLED ;
}
static irqreturn_t wm831x_ts_pen_down_irq ( int irq , void * irq_data )
{
struct wm831x_ts * wm831x_ts = irq_data ;
struct wm831x * wm831x = wm831x_ts - > wm831x ;
2011-02-01 08:09:25 +03:00
int ena = 0 ;
2011-01-30 23:31:30 +03:00
2011-04-28 10:08:34 +04:00
if ( wm831x_ts - > pen_down )
return IRQ_HANDLED ;
disable_irq_nosync ( wm831x_ts - > pd_irq ) ;
2011-01-30 23:31:30 +03:00
/* Start collecting data */
2011-02-01 08:09:25 +03:00
if ( wm831x_ts - > pressure )
ena | = WM831X_TCH_Z_ENA ;
2011-01-30 23:31:30 +03:00
wm831x_set_bits ( wm831x , WM831X_TOUCH_CONTROL_1 ,
WM831X_TCH_X_ENA | WM831X_TCH_Y_ENA | WM831X_TCH_Z_ENA ,
WM831X_TCH_X_ENA | WM831X_TCH_Y_ENA | ena ) ;
wm831x_set_bits ( wm831x , WM831X_INTERRUPT_STATUS_1 ,
WM831X_TCHPD_EINT , WM831X_TCHPD_EINT ) ;
wm831x_ts - > pen_down = true ;
2011-04-28 10:08:34 +04:00
/* Switch from pen down to data */
dev_dbg ( wm831x - > dev , " IRQ PD->DATA \n " ) ;
schedule_work ( & wm831x_ts - > pd_data_work ) ;
2011-01-30 23:31:30 +03:00
return IRQ_HANDLED ;
}
static int wm831x_ts_input_open ( struct input_dev * idev )
{
struct wm831x_ts * wm831x_ts = input_get_drvdata ( idev ) ;
struct wm831x * wm831x = wm831x_ts - > wm831x ;
wm831x_set_bits ( wm831x , WM831X_TOUCH_CONTROL_1 ,
2011-03-15 07:39:09 +03:00
WM831X_TCH_ENA | WM831X_TCH_CVT_ENA |
WM831X_TCH_X_ENA | WM831X_TCH_Y_ENA |
WM831X_TCH_Z_ENA , WM831X_TCH_ENA ) ;
2011-01-30 23:31:30 +03:00
wm831x_set_bits ( wm831x , WM831X_TOUCH_CONTROL_1 ,
WM831X_TCH_CVT_ENA , WM831X_TCH_CVT_ENA ) ;
return 0 ;
}
static void wm831x_ts_input_close ( struct input_dev * idev )
{
struct wm831x_ts * wm831x_ts = input_get_drvdata ( idev ) ;
struct wm831x * wm831x = wm831x_ts - > wm831x ;
2011-04-28 10:08:34 +04:00
/* Shut the controller down, disabling all other functionality too */
2011-01-30 23:31:30 +03:00
wm831x_set_bits ( wm831x , WM831X_TOUCH_CONTROL_1 ,
2011-04-28 10:08:34 +04:00
WM831X_TCH_ENA | WM831X_TCH_X_ENA |
WM831X_TCH_Y_ENA | WM831X_TCH_Z_ENA , 0 ) ;
2011-01-30 23:31:30 +03:00
2011-04-28 10:08:34 +04:00
/* Make sure any pending IRQs are done, the above will prevent
* new ones firing .
*/
synchronize_irq ( wm831x_ts - > data_irq ) ;
synchronize_irq ( wm831x_ts - > pd_irq ) ;
/* Make sure the IRQ completion work is quiesced */
flush_work_sync ( & wm831x_ts - > pd_data_work ) ;
/* If we ended up with the pen down then make sure we revert back
* to pen detection state for the next time we start up .
*/
if ( wm831x_ts - > pen_down ) {
2011-01-30 23:31:30 +03:00
disable_irq ( wm831x_ts - > data_irq ) ;
2011-04-28 10:08:34 +04:00
enable_irq ( wm831x_ts - > pd_irq ) ;
wm831x_ts - > pen_down = false ;
}
2011-01-30 23:31:30 +03:00
}
static __devinit int wm831x_ts_probe ( struct platform_device * pdev )
{
struct wm831x_ts * wm831x_ts ;
struct wm831x * wm831x = dev_get_drvdata ( pdev - > dev . parent ) ;
struct wm831x_pdata * core_pdata = dev_get_platdata ( pdev - > dev . parent ) ;
2011-02-01 08:09:25 +03:00
struct wm831x_touch_pdata * pdata = NULL ;
2011-01-30 23:31:30 +03:00
struct input_dev * input_dev ;
2011-04-28 10:08:51 +04:00
int error , irqf ;
2011-01-30 23:31:30 +03:00
2011-02-01 08:09:25 +03:00
if ( core_pdata )
pdata = core_pdata - > touch ;
2011-01-30 23:31:30 +03:00
wm831x_ts = kzalloc ( sizeof ( struct wm831x_ts ) , GFP_KERNEL ) ;
input_dev = input_allocate_device ( ) ;
if ( ! wm831x_ts | | ! input_dev ) {
error = - ENOMEM ;
goto err_alloc ;
}
wm831x_ts - > wm831x = wm831x ;
wm831x_ts - > input_dev = input_dev ;
2011-04-28 10:08:34 +04:00
INIT_WORK ( & wm831x_ts - > pd_data_work , wm831x_pd_data_work ) ;
2011-01-30 23:31:30 +03:00
/*
* If we have a direct IRQ use it , otherwise use the interrupt
* from the WM831x IRQ controller .
*/
if ( pdata & & pdata - > data_irq )
wm831x_ts - > data_irq = pdata - > data_irq ;
else
wm831x_ts - > data_irq = platform_get_irq_byname ( pdev , " TCHDATA " ) ;
if ( pdata & & pdata - > pd_irq )
wm831x_ts - > pd_irq = pdata - > pd_irq ;
else
wm831x_ts - > pd_irq = platform_get_irq_byname ( pdev , " TCHPD " ) ;
2011-03-13 07:48:34 +03:00
if ( pdata )
wm831x_ts - > pressure = pdata - > pressure ;
else
wm831x_ts - > pressure = true ;
2011-01-30 23:31:30 +03:00
/* Five wire touchscreens can't report pressure */
if ( pdata & & pdata - > fivewire ) {
wm831x_set_bits ( wm831x , WM831X_TOUCH_CONTROL_2 ,
WM831X_TCH_5WIRE , WM831X_TCH_5WIRE ) ;
/* Pressure measurements are not possible for five wire mode */
WARN_ON ( pdata - > pressure & & pdata - > fivewire ) ;
wm831x_ts - > pressure = false ;
} else {
wm831x_set_bits ( wm831x , WM831X_TOUCH_CONTROL_2 ,
WM831X_TCH_5WIRE , 0 ) ;
}
if ( pdata ) {
switch ( pdata - > isel ) {
default :
dev_err ( & pdev - > dev , " Unsupported ISEL setting: %d \n " ,
pdata - > isel ) ;
/* Fall through */
case 200 :
case 0 :
wm831x_set_bits ( wm831x , WM831X_TOUCH_CONTROL_2 ,
WM831X_TCH_ISEL , 0 ) ;
break ;
case 400 :
wm831x_set_bits ( wm831x , WM831X_TOUCH_CONTROL_2 ,
WM831X_TCH_ISEL , WM831X_TCH_ISEL ) ;
break ;
}
}
wm831x_set_bits ( wm831x , WM831X_TOUCH_CONTROL_2 ,
WM831X_TCH_PDONLY , 0 ) ;
/* Default to 96 samples/sec */
wm831x_set_bits ( wm831x , WM831X_TOUCH_CONTROL_1 ,
WM831X_TCH_RATE_MASK , 6 ) ;
2011-04-28 10:08:51 +04:00
if ( pdata & & pdata - > data_irqf )
irqf = pdata - > data_irqf ;
else
irqf = IRQF_TRIGGER_HIGH ;
2011-01-30 23:31:30 +03:00
error = request_threaded_irq ( wm831x_ts - > data_irq ,
NULL , wm831x_ts_data_irq ,
2011-04-28 10:08:51 +04:00
irqf | IRQF_ONESHOT ,
2011-01-30 23:31:30 +03:00
" Touchscreen data " , wm831x_ts ) ;
if ( error ) {
dev_err ( & pdev - > dev , " Failed to request data IRQ %d: %d \n " ,
wm831x_ts - > data_irq , error ) ;
goto err_alloc ;
}
disable_irq ( wm831x_ts - > data_irq ) ;
2011-04-28 10:08:51 +04:00
if ( pdata & & pdata - > pd_irqf )
irqf = pdata - > pd_irqf ;
else
irqf = IRQF_TRIGGER_HIGH ;
2011-01-30 23:31:30 +03:00
error = request_threaded_irq ( wm831x_ts - > pd_irq ,
NULL , wm831x_ts_pen_down_irq ,
2011-04-28 10:08:51 +04:00
irqf | IRQF_ONESHOT ,
2011-01-30 23:31:30 +03:00
" Touchscreen pen down " , wm831x_ts ) ;
if ( error ) {
dev_err ( & pdev - > dev , " Failed to request pen down IRQ %d: %d \n " ,
wm831x_ts - > pd_irq , error ) ;
goto err_data_irq ;
}
/* set up touch configuration */
input_dev - > name = " WM831x touchscreen " ;
input_dev - > phys = " wm831x " ;
input_dev - > open = wm831x_ts_input_open ;
input_dev - > close = wm831x_ts_input_close ;
__set_bit ( EV_ABS , input_dev - > evbit ) ;
__set_bit ( EV_KEY , input_dev - > evbit ) ;
__set_bit ( BTN_TOUCH , input_dev - > keybit ) ;
input_set_abs_params ( input_dev , ABS_X , 0 , 4095 , 5 , 0 ) ;
input_set_abs_params ( input_dev , ABS_Y , 0 , 4095 , 5 , 0 ) ;
if ( wm831x_ts - > pressure )
input_set_abs_params ( input_dev , ABS_PRESSURE , 0 , 4095 , 5 , 0 ) ;
input_set_drvdata ( input_dev , wm831x_ts ) ;
input_dev - > dev . parent = & pdev - > dev ;
error = input_register_device ( input_dev ) ;
if ( error )
goto err_pd_irq ;
platform_set_drvdata ( pdev , wm831x_ts ) ;
return 0 ;
err_pd_irq :
free_irq ( wm831x_ts - > pd_irq , wm831x_ts ) ;
err_data_irq :
free_irq ( wm831x_ts - > data_irq , wm831x_ts ) ;
err_alloc :
input_free_device ( input_dev ) ;
kfree ( wm831x_ts ) ;
return error ;
}
static __devexit int wm831x_ts_remove ( struct platform_device * pdev )
{
struct wm831x_ts * wm831x_ts = platform_get_drvdata ( pdev ) ;
free_irq ( wm831x_ts - > pd_irq , wm831x_ts ) ;
free_irq ( wm831x_ts - > data_irq , wm831x_ts ) ;
input_unregister_device ( wm831x_ts - > input_dev ) ;
kfree ( wm831x_ts ) ;
platform_set_drvdata ( pdev , NULL ) ;
return 0 ;
}
static struct platform_driver wm831x_ts_driver = {
. driver = {
. name = " wm831x-touch " ,
. owner = THIS_MODULE ,
} ,
. probe = wm831x_ts_probe ,
. remove = __devexit_p ( wm831x_ts_remove ) ,
} ;
2011-11-29 23:14:13 +04:00
module_platform_driver ( wm831x_ts_driver ) ;
2011-01-30 23:31:30 +03:00
/* Module information */
MODULE_AUTHOR ( " Mark Brown <broonie@opensource.wolfsonmicro.com> " ) ;
MODULE_DESCRIPTION ( " WM831x PMIC touchscreen driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS ( " platform:wm831x-touch " ) ;