2014-05-14 11:20:45 -07:00
/*
* Allwinner sunxi resistive touchscreen controller driver
*
* Copyright ( C ) 2013 - 2014 Hans de Goede < hdegoede @ redhat . com >
*
2014-05-14 11:22:09 -07:00
* The hwmon parts are based on work by Corentin LABBE which is :
* Copyright ( C ) 2013 Corentin LABBE < clabbe . montjoie @ gmail . com >
*
2014-05-14 11:20:45 -07:00
* 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 .
*
* This program 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 .
*/
/*
* The sun4i - ts controller is capable of detecting a second touch , but when a
* second touch is present then the accuracy becomes so bad the reported touch
* location is not useable .
*
* The original android driver contains some complicated heuristics using the
* aprox . distance between the 2 touches to see if the user is making a pinch
* open / close movement , and then reports emulated multi - touch events around
* the last touch coordinate ( as the dual - touch coordinates are worthless ) .
*
* These kinds of heuristics are just asking for trouble ( and don ' t belong
* in the kernel ) . So this driver offers straight forward , reliable single
* touch functionality only .
2015-03-23 09:04:56 -07:00
*
* s . a . A20 User Manual " 1.15 TP " ( Documentation / arm / sunxi / README )
* ( looks like the description in the A20 User Manual v1 .3 is better
* than the one in the A10 User Manual v .1 .5 )
2014-05-14 11:20:45 -07:00
*/
# include <linux/err.h>
2014-05-14 11:22:09 -07:00
# include <linux/hwmon.h>
2015-01-15 10:26:32 -08:00
# include <linux/thermal.h>
2014-05-14 11:20:45 -07:00
# include <linux/init.h>
# include <linux/input.h>
# include <linux/interrupt.h>
# include <linux/io.h>
# include <linux/module.h>
# include <linux/of_platform.h>
# include <linux/platform_device.h>
# include <linux/slab.h>
# define TP_CTRL0 0x00
# define TP_CTRL1 0x04
# define TP_CTRL2 0x08
# define TP_CTRL3 0x0c
# define TP_INT_FIFOC 0x10
# define TP_INT_FIFOS 0x14
# define TP_TPR 0x18
# define TP_CDAT 0x1c
# define TEMP_DATA 0x20
# define TP_DATA 0x24
/* TP_CTRL0 bits */
# define ADC_FIRST_DLY(x) ((x) << 24) /* 8 bits */
# define ADC_FIRST_DLY_MODE(x) ((x) << 23)
# define ADC_CLK_SEL(x) ((x) << 22)
# define ADC_CLK_DIV(x) ((x) << 20) /* 3 bits */
# define FS_DIV(x) ((x) << 16) /* 4 bits */
# define T_ACQ(x) ((x) << 0) /* 16 bits */
/* TP_CTRL1 bits */
# define STYLUS_UP_DEBOUN(x) ((x) << 12) /* 8 bits */
# define STYLUS_UP_DEBOUN_EN(x) ((x) << 9)
# define TOUCH_PAN_CALI_EN(x) ((x) << 6)
# define TP_DUAL_EN(x) ((x) << 5)
# define TP_MODE_EN(x) ((x) << 4)
# define TP_ADC_SELECT(x) ((x) << 3)
# define ADC_CHAN_SELECT(x) ((x) << 0) /* 3 bits */
2015-01-26 23:57:00 -08:00
/* on sun6i, bits 3~6 are left shifted by 1 to 4~7 */
# define SUN6I_TP_MODE_EN(x) ((x) << 5)
2014-05-14 11:20:45 -07:00
/* TP_CTRL2 bits */
# define TP_SENSITIVE_ADJUST(x) ((x) << 28) /* 4 bits */
# define TP_MODE_SELECT(x) ((x) << 26) /* 2 bits */
# define PRE_MEA_EN(x) ((x) << 24)
# define PRE_MEA_THRE_CNT(x) ((x) << 0) /* 24 bits */
/* TP_CTRL3 bits */
# define FILTER_EN(x) ((x) << 2)
# define FILTER_TYPE(x) ((x) << 0) /* 2 bits */
/* TP_INT_FIFOC irq and fifo mask / control bits */
# define TEMP_IRQ_EN(x) ((x) << 18)
# define OVERRUN_IRQ_EN(x) ((x) << 17)
# define DATA_IRQ_EN(x) ((x) << 16)
# define TP_DATA_XY_CHANGE(x) ((x) << 13)
# define FIFO_TRIG(x) ((x) << 8) /* 5 bits */
# define DATA_DRQ_EN(x) ((x) << 7)
# define FIFO_FLUSH(x) ((x) << 4)
# define TP_UP_IRQ_EN(x) ((x) << 1)
# define TP_DOWN_IRQ_EN(x) ((x) << 0)
/* TP_INT_FIFOS irq and fifo status bits */
# define TEMP_DATA_PENDING BIT(18)
# define FIFO_OVERRUN_PENDING BIT(17)
# define FIFO_DATA_PENDING BIT(16)
# define TP_IDLE_FLG BIT(2)
# define TP_UP_PENDING BIT(1)
# define TP_DOWN_PENDING BIT(0)
/* TP_TPR bits */
# define TEMP_ENABLE(x) ((x) << 16)
# define TEMP_PERIOD(x) ((x) << 0) /* t = x * 256 * 16 / clkin */
struct sun4i_ts_data {
struct device * dev ;
struct input_dev * input ;
void __iomem * base ;
unsigned int irq ;
bool ignore_fifo_data ;
2014-05-14 11:22:09 -07:00
int temp_data ;
2015-01-26 23:57:00 -08:00
int temp_offset ;
int temp_step ;
2014-05-14 11:20:45 -07:00
} ;
2014-05-14 11:22:09 -07:00
static void sun4i_ts_irq_handle_input ( struct sun4i_ts_data * ts , u32 reg_val )
2014-05-14 11:20:45 -07:00
{
2014-05-14 11:22:09 -07:00
u32 x , y ;
2014-05-14 11:20:45 -07:00
if ( reg_val & FIFO_DATA_PENDING ) {
x = readl ( ts - > base + TP_DATA ) ;
y = readl ( ts - > base + TP_DATA ) ;
/* The 1st location reported after an up event is unreliable */
if ( ! ts - > ignore_fifo_data ) {
input_report_abs ( ts - > input , ABS_X , x ) ;
input_report_abs ( ts - > input , ABS_Y , y ) ;
/*
* The hardware has a separate down status bit , but
* that gets set before we get the first location ,
* resulting in reporting a click on the old location .
*/
input_report_key ( ts - > input , BTN_TOUCH , 1 ) ;
input_sync ( ts - > input ) ;
} else {
ts - > ignore_fifo_data = false ;
}
}
if ( reg_val & TP_UP_PENDING ) {
ts - > ignore_fifo_data = true ;
input_report_key ( ts - > input , BTN_TOUCH , 0 ) ;
input_sync ( ts - > input ) ;
}
2014-05-14 11:22:09 -07:00
}
static irqreturn_t sun4i_ts_irq ( int irq , void * dev_id )
{
struct sun4i_ts_data * ts = dev_id ;
u32 reg_val ;
reg_val = readl ( ts - > base + TP_INT_FIFOS ) ;
if ( reg_val & TEMP_DATA_PENDING )
ts - > temp_data = readl ( ts - > base + TEMP_DATA ) ;
if ( ts - > input )
sun4i_ts_irq_handle_input ( ts , reg_val ) ;
2014-05-14 11:20:45 -07:00
writel ( reg_val , ts - > base + TP_INT_FIFOS ) ;
return IRQ_HANDLED ;
}
static int sun4i_ts_open ( struct input_dev * dev )
{
struct sun4i_ts_data * ts = input_get_drvdata ( dev ) ;
2014-05-14 11:22:09 -07:00
/* Flush, set trig level to 1, enable temp, data and up irqs */
writel ( TEMP_IRQ_EN ( 1 ) | DATA_IRQ_EN ( 1 ) | FIFO_TRIG ( 1 ) | FIFO_FLUSH ( 1 ) |
TP_UP_IRQ_EN ( 1 ) , ts - > base + TP_INT_FIFOC ) ;
2014-05-14 11:20:45 -07:00
return 0 ;
}
static void sun4i_ts_close ( struct input_dev * dev )
{
struct sun4i_ts_data * ts = input_get_drvdata ( dev ) ;
2014-05-14 11:22:09 -07:00
/* Deactivate all input IRQs */
writel ( TEMP_IRQ_EN ( 1 ) , ts - > base + TP_INT_FIFOC ) ;
}
2015-07-24 08:12:54 +02:00
static int sun4i_get_temp ( const struct sun4i_ts_data * ts , int * temp )
2015-01-15 10:26:32 -08:00
{
/* No temp_data until the first irq */
if ( ts - > temp_data = = - 1 )
return - EAGAIN ;
2015-03-12 14:50:36 -07:00
* temp = ts - > temp_data * ts - > temp_step - ts - > temp_offset ;
2015-01-15 10:26:32 -08:00
return 0 ;
}
2015-07-24 08:12:54 +02:00
static int sun4i_get_tz_temp ( void * data , int * temp )
2015-01-15 10:26:32 -08:00
{
return sun4i_get_temp ( data , temp ) ;
}
static struct thermal_zone_of_device_ops sun4i_ts_tz_ops = {
. get_temp = sun4i_get_tz_temp ,
} ;
2014-05-14 11:22:09 -07:00
static ssize_t show_temp ( struct device * dev , struct device_attribute * devattr ,
char * buf )
{
struct sun4i_ts_data * ts = dev_get_drvdata ( dev ) ;
2015-07-24 08:12:54 +02:00
int temp ;
2015-01-15 10:26:32 -08:00
int error ;
2014-05-14 11:22:09 -07:00
2015-01-15 10:26:32 -08:00
error = sun4i_get_temp ( ts , & temp ) ;
if ( error )
return error ;
2014-05-14 11:22:09 -07:00
2015-07-24 08:12:54 +02:00
return sprintf ( buf , " %d \n " , temp ) ;
2014-05-14 11:22:09 -07:00
}
static ssize_t show_temp_label ( struct device * dev ,
struct device_attribute * devattr , char * buf )
{
return sprintf ( buf , " SoC temperature \n " ) ;
2014-05-14 11:20:45 -07:00
}
2014-05-14 11:22:09 -07:00
static DEVICE_ATTR ( temp1_input , S_IRUGO , show_temp , NULL ) ;
static DEVICE_ATTR ( temp1_label , S_IRUGO , show_temp_label , NULL ) ;
static struct attribute * sun4i_ts_attrs [ ] = {
& dev_attr_temp1_input . attr ,
& dev_attr_temp1_label . attr ,
NULL
} ;
ATTRIBUTE_GROUPS ( sun4i_ts ) ;
2014-05-14 11:20:45 -07:00
static int sun4i_ts_probe ( struct platform_device * pdev )
{
struct sun4i_ts_data * ts ;
struct device * dev = & pdev - > dev ;
2014-05-14 11:22:09 -07:00
struct device_node * np = dev - > of_node ;
struct device * hwmon ;
2014-05-14 11:20:45 -07:00
int error ;
2015-01-26 23:57:00 -08:00
u32 reg ;
2014-05-14 11:22:09 -07:00
bool ts_attached ;
2015-03-23 09:04:56 -07:00
u32 tp_sensitive_adjust = 15 ;
u32 filter_type = 1 ;
2014-05-14 11:20:45 -07:00
ts = devm_kzalloc ( dev , sizeof ( struct sun4i_ts_data ) , GFP_KERNEL ) ;
if ( ! ts )
return - ENOMEM ;
ts - > dev = dev ;
ts - > ignore_fifo_data = true ;
2014-05-14 11:22:09 -07:00
ts - > temp_data = - 1 ;
2015-01-26 23:57:00 -08:00
if ( of_device_is_compatible ( np , " allwinner,sun6i-a31-ts " ) ) {
2015-03-12 14:50:36 -07:00
/* Allwinner SDK has temperature (C) = (value / 6) - 271 */
ts - > temp_offset = 271000 ;
2015-01-26 23:57:00 -08:00
ts - > temp_step = 167 ;
2015-03-08 14:12:41 -07:00
} else if ( of_device_is_compatible ( np , " allwinner,sun4i-a10-ts " ) ) {
/*
* The A10 temperature sensor has quite a wide spread , these
* parameters are based on the averaging of the calibration
* results of 4 completely different boards , with a spread of
2015-03-12 14:50:36 -07:00
* temp_step from 0.096 - 0.170 and temp_offset from 176 - 331.
2015-03-08 14:12:41 -07:00
*/
2015-03-12 14:50:36 -07:00
ts - > temp_offset = 257000 ;
2015-03-08 14:12:41 -07:00
ts - > temp_step = 133 ;
2015-01-26 23:57:00 -08:00
} else {
/*
* The user manuals do not contain the formula for calculating
* the temperature . The formula used here is from the AXP209 ,
* which is designed by X - Powers , an affiliate of Allwinner :
*
2015-03-12 14:50:36 -07:00
* temperature ( C ) = ( value * 0.1 ) - 144.7
2015-01-26 23:57:00 -08:00
*
* Allwinner does not have any documentation whatsoever for
* this hardware . Moreover , it is claimed that the sensor
* is inaccurate and cannot work properly .
*/
2015-03-12 14:50:36 -07:00
ts - > temp_offset = 144700 ;
2015-01-26 23:57:00 -08:00
ts - > temp_step = 100 ;
}
2014-05-14 11:22:09 -07:00
ts_attached = of_property_read_bool ( np , " allwinner,ts-attached " ) ;
if ( ts_attached ) {
ts - > input = devm_input_allocate_device ( dev ) ;
if ( ! ts - > input )
return - ENOMEM ;
ts - > input - > name = pdev - > name ;
ts - > input - > phys = " sun4i_ts/input0 " ;
ts - > input - > open = sun4i_ts_open ;
ts - > input - > close = sun4i_ts_close ;
ts - > input - > id . bustype = BUS_HOST ;
ts - > input - > id . vendor = 0x0001 ;
ts - > input - > id . product = 0x0001 ;
ts - > input - > id . version = 0x0100 ;
ts - > input - > evbit [ 0 ] = BIT ( EV_SYN ) | BIT ( EV_KEY ) | BIT ( EV_ABS ) ;
__set_bit ( BTN_TOUCH , ts - > input - > keybit ) ;
input_set_abs_params ( ts - > input , ABS_X , 0 , 4095 , 0 , 0 ) ;
input_set_abs_params ( ts - > input , ABS_Y , 0 , 4095 , 0 , 0 ) ;
input_set_drvdata ( ts - > input , ts ) ;
}
2014-05-14 11:20:45 -07:00
ts - > base = devm_ioremap_resource ( dev ,
platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ) ;
if ( IS_ERR ( ts - > base ) )
return PTR_ERR ( ts - > base ) ;
ts - > irq = platform_get_irq ( pdev , 0 ) ;
error = devm_request_irq ( dev , ts - > irq , sun4i_ts_irq , 0 , " sun4i-ts " , ts ) ;
if ( error )
return error ;
/*
* Select HOSC clk , clkin = clk / 6 , adc samplefreq = clkin / 8192 ,
* t_acq = clkin / ( 16 * 64 )
*/
writel ( ADC_CLK_SEL ( 0 ) | ADC_CLK_DIV ( 2 ) | FS_DIV ( 7 ) | T_ACQ ( 63 ) ,
ts - > base + TP_CTRL0 ) ;
/*
2015-03-23 09:04:56 -07:00
* tp_sensitive_adjust is an optional property
2014-05-14 11:20:45 -07:00
* tp_mode = 0 : only x and y coordinates , as we don ' t use dual touch
*/
2015-03-23 09:04:56 -07:00
of_property_read_u32 ( np , " allwinner,tp-sensitive-adjust " ,
& tp_sensitive_adjust ) ;
writel ( TP_SENSITIVE_ADJUST ( tp_sensitive_adjust ) | TP_MODE_SELECT ( 0 ) ,
2014-05-14 11:20:45 -07:00
ts - > base + TP_CTRL2 ) ;
2015-03-23 09:04:56 -07:00
/*
* Enable median and averaging filter , optional property for
* filter type .
*/
of_property_read_u32 ( np , " allwinner,filter-type " , & filter_type ) ;
writel ( FILTER_EN ( 1 ) | FILTER_TYPE ( filter_type ) , ts - > base + TP_CTRL3 ) ;
2014-05-14 11:20:45 -07:00
/* Enable temperature measurement, period 1953 (2 seconds) */
writel ( TEMP_ENABLE ( 1 ) | TEMP_PERIOD ( 1953 ) , ts - > base + TP_TPR ) ;
/*
* Set stylus up debounce to aprox 10 ms , enable debounce , and
* finally enable tp mode .
*/
2015-01-26 23:57:00 -08:00
reg = STYLUS_UP_DEBOUN ( 5 ) | STYLUS_UP_DEBOUN_EN ( 1 ) ;
2015-03-08 14:12:41 -07:00
if ( of_device_is_compatible ( np , " allwinner,sun6i-a31-ts " ) )
2015-01-26 23:57:00 -08:00
reg | = SUN6I_TP_MODE_EN ( 1 ) ;
2015-03-08 14:12:41 -07:00
else
reg | = TP_MODE_EN ( 1 ) ;
2015-01-26 23:57:00 -08:00
writel ( reg , ts - > base + TP_CTRL1 ) ;
2014-05-14 11:20:45 -07:00
2015-01-15 10:26:32 -08:00
/*
* The thermal core does not register hwmon devices for DT - based
* thermal zone sensors , such as this one .
*/
2014-05-14 11:22:09 -07:00
hwmon = devm_hwmon_device_register_with_groups ( ts - > dev , " sun4i_ts " ,
ts , sun4i_ts_groups ) ;
if ( IS_ERR ( hwmon ) )
return PTR_ERR ( hwmon ) ;
2016-03-09 13:05:42 -08:00
devm_thermal_zone_of_sensor_register ( ts - > dev , 0 , ts , & sun4i_ts_tz_ops ) ;
2015-01-15 10:26:32 -08:00
2014-05-14 11:22:09 -07:00
writel ( TEMP_IRQ_EN ( 1 ) , ts - > base + TP_INT_FIFOC ) ;
if ( ts_attached ) {
error = input_register_device ( ts - > input ) ;
if ( error ) {
writel ( 0 , ts - > base + TP_INT_FIFOC ) ;
return error ;
}
}
2014-05-14 11:20:45 -07:00
platform_set_drvdata ( pdev , ts ) ;
return 0 ;
}
2014-05-14 11:22:09 -07:00
static int sun4i_ts_remove ( struct platform_device * pdev )
{
struct sun4i_ts_data * ts = platform_get_drvdata ( pdev ) ;
/* Explicit unregister to avoid open/close changing the imask later */
if ( ts - > input )
input_unregister_device ( ts - > input ) ;
/* Deactivate all IRQs */
writel ( 0 , ts - > base + TP_INT_FIFOC ) ;
return 0 ;
}
2014-05-14 11:20:45 -07:00
static const struct of_device_id sun4i_ts_of_match [ ] = {
{ . compatible = " allwinner,sun4i-a10-ts " , } ,
2015-03-08 14:12:41 -07:00
{ . compatible = " allwinner,sun5i-a13-ts " , } ,
2015-01-26 23:57:00 -08:00
{ . compatible = " allwinner,sun6i-a31-ts " , } ,
2014-05-14 11:20:45 -07:00
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , sun4i_ts_of_match ) ;
static struct platform_driver sun4i_ts_driver = {
. driver = {
. name = " sun4i-ts " ,
. of_match_table = of_match_ptr ( sun4i_ts_of_match ) ,
} ,
. probe = sun4i_ts_probe ,
2014-05-14 11:22:09 -07:00
. remove = sun4i_ts_remove ,
2014-05-14 11:20:45 -07:00
} ;
module_platform_driver ( sun4i_ts_driver ) ;
MODULE_DESCRIPTION ( " Allwinner sun4i resistive touchscreen controller driver " ) ;
MODULE_AUTHOR ( " Hans de Goede <hdegoede@redhat.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;