2008-03-04 15:23:45 -08:00
/*
* SuperH KEYSC Keypad Driver
*
* Copyright ( C ) 2008 Magnus Damm
*
* Based on gpio_keys . c , Copyright 2005 Phil Blundell
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/init.h>
# include <linux/interrupt.h>
# include <linux/irq.h>
# include <linux/delay.h>
# include <linux/platform_device.h>
# include <linux/input.h>
2009-11-27 07:32:24 +00:00
# include <linux/input/sh_keysc.h>
2010-02-10 22:13:21 -08:00
# include <linux/bitmap.h>
2008-10-31 20:21:23 +09:00
# include <linux/clk.h>
2008-03-04 15:23:45 -08:00
# include <linux/io.h>
static const struct {
unsigned char kymd , keyout , keyin ;
} sh_keysc_mode [ ] = {
[ SH_KEYSC_MODE_1 ] = { 0 , 6 , 5 } ,
[ SH_KEYSC_MODE_2 ] = { 1 , 5 , 6 } ,
[ SH_KEYSC_MODE_3 ] = { 2 , 4 , 7 } ,
2010-01-21 00:02:36 -08:00
[ SH_KEYSC_MODE_4 ] = { 3 , 6 , 6 } ,
[ SH_KEYSC_MODE_5 ] = { 4 , 6 , 7 } ,
2008-03-04 15:23:45 -08:00
} ;
struct sh_keysc_priv {
void __iomem * iomem_base ;
2008-10-31 20:21:23 +09:00
struct clk * clk ;
2010-02-10 22:13:21 -08:00
DECLARE_BITMAP ( last_keys , SH_KEYSC_MAXKEYS ) ;
2008-03-04 15:23:45 -08:00
struct input_dev * input ;
struct sh_keysc_info pdata ;
} ;
2010-02-10 22:13:21 -08:00
# define KYCR1 0
# define KYCR2 1
# define KYINDR 2
# define KYOUTDR 3
# define KYCR2_IRQ_LEVEL 0x10
# define KYCR2_IRQ_DISABLED 0x00
static unsigned long sh_keysc_read ( struct sh_keysc_priv * p , int reg_nr )
{
return ioread16 ( p - > iomem_base + ( reg_nr < < 2 ) ) ;
}
static void sh_keysc_write ( struct sh_keysc_priv * p , int reg_nr ,
unsigned long value )
{
iowrite16 ( value , p - > iomem_base + ( reg_nr < < 2 ) ) ;
}
static void sh_keysc_level_mode ( struct sh_keysc_priv * p ,
unsigned long keys_set )
{
struct sh_keysc_info * pdata = & p - > pdata ;
sh_keysc_write ( p , KYOUTDR , 0 ) ;
sh_keysc_write ( p , KYCR2 , KYCR2_IRQ_LEVEL | ( keys_set < < 8 ) ) ;
if ( pdata - > kycr2_delay )
udelay ( pdata - > kycr2_delay ) ;
}
2010-02-10 22:13:21 -08:00
static void sh_keysc_map_dbg ( struct device * dev , unsigned long * map ,
const char * str )
{
int k ;
for ( k = 0 ; k < BITS_TO_LONGS ( SH_KEYSC_MAXKEYS ) ; k + + )
dev_dbg ( dev , " %s[%d] 0x%lx \n " , str , k , map [ k ] ) ;
}
2008-03-04 15:23:45 -08:00
static irqreturn_t sh_keysc_isr ( int irq , void * dev_id )
{
struct platform_device * pdev = dev_id ;
struct sh_keysc_priv * priv = platform_get_drvdata ( pdev ) ;
struct sh_keysc_info * pdata = & priv - > pdata ;
2010-02-10 22:13:21 -08:00
int keyout_nr = sh_keysc_mode [ pdata - > mode ] . keyout ;
int keyin_nr = sh_keysc_mode [ pdata - > mode ] . keyin ;
DECLARE_BITMAP ( keys , SH_KEYSC_MAXKEYS ) ;
DECLARE_BITMAP ( keys0 , SH_KEYSC_MAXKEYS ) ;
DECLARE_BITMAP ( keys1 , SH_KEYSC_MAXKEYS ) ;
2008-03-04 15:23:45 -08:00
unsigned char keyin_set , tmp ;
2010-02-10 22:13:21 -08:00
int i , k , n ;
2008-03-04 15:23:45 -08:00
dev_dbg ( & pdev - > dev , " isr! \n " ) ;
2010-02-10 22:13:21 -08:00
bitmap_fill ( keys1 , SH_KEYSC_MAXKEYS ) ;
bitmap_zero ( keys0 , SH_KEYSC_MAXKEYS ) ;
2008-03-04 15:23:45 -08:00
do {
2010-02-10 22:13:21 -08:00
bitmap_zero ( keys , SH_KEYSC_MAXKEYS ) ;
2008-03-04 15:23:45 -08:00
keyin_set = 0 ;
2010-02-10 22:13:21 -08:00
sh_keysc_write ( priv , KYCR2 , KYCR2_IRQ_DISABLED ) ;
2008-03-04 15:23:45 -08:00
2010-02-10 22:13:21 -08:00
for ( i = 0 ; i < keyout_nr ; i + + ) {
n = keyin_nr * i ;
/* drive one KEYOUT pin low, read KEYIN pins */
2010-02-10 22:13:21 -08:00
sh_keysc_write ( priv , KYOUTDR , 0xfff ^ ( 3 < < ( i * 2 ) ) ) ;
2008-03-04 15:23:45 -08:00
udelay ( pdata - > delay ) ;
2010-02-10 22:13:21 -08:00
tmp = sh_keysc_read ( priv , KYINDR ) ;
2010-02-10 22:13:21 -08:00
/* set bit if key press has been detected */
for ( k = 0 ; k < keyin_nr ; k + + ) {
if ( tmp & ( 1 < < k ) )
__set_bit ( n + k , keys ) ;
}
/* keep track of which KEYIN bits that have been set */
keyin_set | = tmp ^ ( ( 1 < < keyin_nr ) - 1 ) ;
2008-03-04 15:23:45 -08:00
}
2010-02-10 22:13:21 -08:00
sh_keysc_level_mode ( priv , keyin_set ) ;
2009-09-15 00:21:34 +00:00
2010-02-10 22:13:21 -08:00
bitmap_complement ( keys , keys , SH_KEYSC_MAXKEYS ) ;
bitmap_and ( keys1 , keys1 , keys , SH_KEYSC_MAXKEYS ) ;
bitmap_or ( keys0 , keys0 , keys , SH_KEYSC_MAXKEYS ) ;
2008-03-04 15:23:45 -08:00
2010-02-10 22:13:21 -08:00
sh_keysc_map_dbg ( & pdev - > dev , keys , " keys " ) ;
2008-03-04 15:23:45 -08:00
2010-02-10 22:13:21 -08:00
} while ( sh_keysc_read ( priv , KYCR2 ) & 0x01 ) ;
2008-03-04 15:23:45 -08:00
2010-02-10 22:13:21 -08:00
sh_keysc_map_dbg ( & pdev - > dev , priv - > last_keys , " last_keys " ) ;
sh_keysc_map_dbg ( & pdev - > dev , keys0 , " keys0 " ) ;
sh_keysc_map_dbg ( & pdev - > dev , keys1 , " keys1 " ) ;
2008-03-04 15:23:45 -08:00
for ( i = 0 ; i < SH_KEYSC_MAXKEYS ; i + + ) {
k = pdata - > keycodes [ i ] ;
if ( ! k )
continue ;
2010-02-10 22:13:21 -08:00
if ( test_bit ( i , keys0 ) = = test_bit ( i , priv - > last_keys ) )
2008-03-04 15:23:45 -08:00
continue ;
2010-02-10 22:13:21 -08:00
if ( test_bit ( i , keys1 ) | | test_bit ( i , keys0 ) ) {
2008-03-04 15:23:45 -08:00
input_event ( priv - > input , EV_KEY , k , 1 ) ;
2010-02-10 22:13:21 -08:00
__set_bit ( i , priv - > last_keys ) ;
2008-03-04 15:23:45 -08:00
}
2010-02-10 22:13:21 -08:00
if ( ! test_bit ( i , keys1 ) ) {
2008-03-04 15:23:45 -08:00
input_event ( priv - > input , EV_KEY , k , 0 ) ;
2010-02-10 22:13:21 -08:00
__clear_bit ( i , priv - > last_keys ) ;
2008-03-04 15:23:45 -08:00
}
}
input_sync ( priv - > input ) ;
return IRQ_HANDLED ;
}
static int __devinit sh_keysc_probe ( struct platform_device * pdev )
{
struct sh_keysc_priv * priv ;
struct sh_keysc_info * pdata ;
struct resource * res ;
struct input_dev * input ;
2008-10-31 20:21:23 +09:00
char clk_name [ 8 ] ;
2009-07-21 01:12:12 -07:00
int i ;
2008-03-04 15:23:45 -08:00
int irq , error ;
if ( ! pdev - > dev . platform_data ) {
dev_err ( & pdev - > dev , " no platform data defined \n " ) ;
error = - EINVAL ;
goto err0 ;
}
error = - ENXIO ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( res = = NULL ) {
dev_err ( & pdev - > dev , " failed to get I/O memory \n " ) ;
goto err0 ;
}
irq = platform_get_irq ( pdev , 0 ) ;
if ( irq < 0 ) {
dev_err ( & pdev - > dev , " failed to get irq \n " ) ;
goto err0 ;
}
priv = kzalloc ( sizeof ( * priv ) , GFP_KERNEL ) ;
if ( priv = = NULL ) {
dev_err ( & pdev - > dev , " failed to allocate driver data \n " ) ;
error = - ENOMEM ;
goto err0 ;
}
platform_set_drvdata ( pdev , priv ) ;
memcpy ( & priv - > pdata , pdev - > dev . platform_data , sizeof ( priv - > pdata ) ) ;
pdata = & priv - > pdata ;
2010-01-21 00:02:36 -08:00
priv - > iomem_base = ioremap_nocache ( res - > start , resource_size ( res ) ) ;
2008-03-04 15:23:45 -08:00
if ( priv - > iomem_base = = NULL ) {
dev_err ( & pdev - > dev , " failed to remap I/O memory \n " ) ;
error = - ENXIO ;
2008-07-19 07:46:53 +09:00
goto err1 ;
2008-03-04 15:23:45 -08:00
}
2008-10-31 20:21:23 +09:00
snprintf ( clk_name , sizeof ( clk_name ) , " keysc%d " , pdev - > id ) ;
priv - > clk = clk_get ( & pdev - > dev , clk_name ) ;
if ( IS_ERR ( priv - > clk ) ) {
dev_err ( & pdev - > dev , " cannot get clock \" %s \" \n " , clk_name ) ;
error = PTR_ERR ( priv - > clk ) ;
goto err2 ;
}
2008-03-04 15:23:45 -08:00
priv - > input = input_allocate_device ( ) ;
if ( ! priv - > input ) {
dev_err ( & pdev - > dev , " failed to allocate input device \n " ) ;
error = - ENOMEM ;
2008-10-31 20:21:23 +09:00
goto err3 ;
2008-03-04 15:23:45 -08:00
}
input = priv - > input ;
input - > evbit [ 0 ] = BIT_MASK ( EV_KEY ) ;
input - > name = pdev - > name ;
input - > phys = " sh-keysc-keys/input0 " ;
input - > dev . parent = & pdev - > dev ;
input - > id . bustype = BUS_HOST ;
input - > id . vendor = 0x0001 ;
input - > id . product = 0x0001 ;
input - > id . version = 0x0100 ;
2009-07-21 01:12:12 -07:00
input - > keycode = pdata - > keycodes ;
input - > keycodesize = sizeof ( pdata - > keycodes [ 0 ] ) ;
input - > keycodemax = ARRAY_SIZE ( pdata - > keycodes ) ;
2008-03-04 15:23:45 -08:00
error = request_irq ( irq , sh_keysc_isr , 0 , pdev - > name , pdev ) ;
if ( error ) {
dev_err ( & pdev - > dev , " failed to request IRQ \n " ) ;
2008-10-31 20:21:23 +09:00
goto err4 ;
2008-03-04 15:23:45 -08:00
}
2009-07-21 01:12:12 -07:00
for ( i = 0 ; i < SH_KEYSC_MAXKEYS ; i + + )
__set_bit ( pdata - > keycodes [ i ] , input - > keybit ) ;
__clear_bit ( KEY_RESERVED , input - > keybit ) ;
2008-03-04 15:23:45 -08:00
error = input_register_device ( input ) ;
if ( error ) {
dev_err ( & pdev - > dev , " failed to register input device \n " ) ;
2008-10-31 20:21:23 +09:00
goto err5 ;
2008-03-04 15:23:45 -08:00
}
2008-10-31 20:21:23 +09:00
clk_enable ( priv - > clk ) ;
2010-02-10 22:13:21 -08:00
sh_keysc_write ( priv , KYCR1 , ( sh_keysc_mode [ pdata - > mode ] . kymd < < 8 ) |
pdata - > scan_timing ) ;
sh_keysc_level_mode ( priv , 0 ) ;
2009-03-10 06:24:21 +00:00
device_init_wakeup ( & pdev - > dev , 1 ) ;
2009-07-21 01:12:12 -07:00
2008-03-04 15:23:45 -08:00
return 0 ;
2009-07-21 01:12:12 -07:00
2008-10-31 20:21:23 +09:00
err5 :
2008-07-19 07:46:53 +09:00
free_irq ( irq , pdev ) ;
2008-10-31 20:21:23 +09:00
err4 :
2008-07-19 07:46:53 +09:00
input_free_device ( input ) ;
2008-10-31 20:21:23 +09:00
err3 :
clk_put ( priv - > clk ) ;
2008-03-04 15:23:45 -08:00
err2 :
2008-07-19 07:46:53 +09:00
iounmap ( priv - > iomem_base ) ;
2008-03-04 15:23:45 -08:00
err1 :
platform_set_drvdata ( pdev , NULL ) ;
kfree ( priv ) ;
err0 :
return error ;
}
static int __devexit sh_keysc_remove ( struct platform_device * pdev )
{
struct sh_keysc_priv * priv = platform_get_drvdata ( pdev ) ;
2010-02-10 22:13:21 -08:00
sh_keysc_write ( priv , KYCR2 , KYCR2_IRQ_DISABLED ) ;
2008-03-04 15:23:45 -08:00
input_unregister_device ( priv - > input ) ;
free_irq ( platform_get_irq ( pdev , 0 ) , pdev ) ;
iounmap ( priv - > iomem_base ) ;
2008-10-31 20:21:23 +09:00
clk_disable ( priv - > clk ) ;
clk_put ( priv - > clk ) ;
2008-03-04 15:23:45 -08:00
platform_set_drvdata ( pdev , NULL ) ;
kfree ( priv ) ;
2009-07-21 01:12:12 -07:00
2008-03-04 15:23:45 -08:00
return 0 ;
}
2009-03-10 06:24:21 +00:00
static int sh_keysc_suspend ( struct device * dev )
{
2009-03-11 08:04:23 +00:00
struct platform_device * pdev = to_platform_device ( dev ) ;
struct sh_keysc_priv * priv = platform_get_drvdata ( pdev ) ;
2009-04-01 14:39:20 +00:00
int irq = platform_get_irq ( pdev , 0 ) ;
2009-03-10 06:24:21 +00:00
unsigned short value ;
2010-02-10 22:13:21 -08:00
value = sh_keysc_read ( priv , KYCR1 ) ;
2008-03-04 15:23:45 -08:00
2009-04-01 14:39:20 +00:00
if ( device_may_wakeup ( dev ) ) {
2009-03-10 06:24:21 +00:00
value | = 0x80 ;
2009-04-01 14:39:20 +00:00
enable_irq_wake ( irq ) ;
2009-07-21 01:12:12 -07:00
} else {
2009-03-10 06:24:21 +00:00
value & = ~ 0x80 ;
2009-07-21 01:12:12 -07:00
}
2009-03-10 06:24:21 +00:00
2010-02-10 22:13:21 -08:00
sh_keysc_write ( priv , KYCR1 , value ) ;
2009-07-21 01:12:12 -07:00
2009-03-10 06:24:21 +00:00
return 0 ;
}
2009-04-01 14:39:20 +00:00
static int sh_keysc_resume ( struct device * dev )
{
struct platform_device * pdev = to_platform_device ( dev ) ;
int irq = platform_get_irq ( pdev , 0 ) ;
if ( device_may_wakeup ( dev ) )
disable_irq_wake ( irq ) ;
return 0 ;
}
2009-12-14 18:00:08 -08:00
static const struct dev_pm_ops sh_keysc_dev_pm_ops = {
2009-03-10 06:24:21 +00:00
. suspend = sh_keysc_suspend ,
2009-04-01 14:39:20 +00:00
. resume = sh_keysc_resume ,
2009-03-10 06:24:21 +00:00
} ;
2008-03-04 15:23:45 -08:00
struct platform_driver sh_keysc_device_driver = {
. probe = sh_keysc_probe ,
. remove = __devexit_p ( sh_keysc_remove ) ,
. driver = {
. name = " sh_keysc " ,
2009-03-10 06:24:21 +00:00
. pm = & sh_keysc_dev_pm_ops ,
2008-03-04 15:23:45 -08:00
}
} ;
static int __init sh_keysc_init ( void )
{
return platform_driver_register ( & sh_keysc_device_driver ) ;
}
static void __exit sh_keysc_exit ( void )
{
platform_driver_unregister ( & sh_keysc_device_driver ) ;
}
module_init ( sh_keysc_init ) ;
module_exit ( sh_keysc_exit ) ;
MODULE_AUTHOR ( " Magnus Damm " ) ;
MODULE_DESCRIPTION ( " SuperH KEYSC Keypad Driver " ) ;
MODULE_LICENSE ( " GPL " ) ;