2008-01-21 01:04:20 -05:00
/*
* Keyboard driver for Sharp Tosa models ( SL - 6000 x )
*
* Copyright ( c ) 2005 Dirk Opfer
* Copyright ( c ) 2007 Dmitry Baryshkov
*
* Based on xtkbd . c / locomkbd . c / corgikbd . c
*
* 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/platform_device.h>
# include <linux/input.h>
# include <linux/delay.h>
# include <linux/interrupt.h>
2008-08-05 16:14:15 +01:00
# include <mach/gpio.h>
# include <mach/tosa.h>
2008-01-21 01:04:20 -05:00
# define KB_ROWMASK(r) (1 << (r))
# define SCANCODE(r, c) (((r)<<4) + (c) + 1)
# define NR_SCANCODES SCANCODE(TOSA_KEY_SENSE_NUM - 1, TOSA_KEY_STROBE_NUM - 1) + 1
# define SCAN_INTERVAL (HZ / 10)
# define KB_DISCHARGE_DELAY 10
# define KB_ACTIVATE_DELAY 10
static unsigned int tosakbd_keycode [ NR_SCANCODES ] = {
0 ,
0 , KEY_W , 0 , 0 , 0 , KEY_K , KEY_BACKSPACE , KEY_P ,
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
KEY_Q , KEY_E , KEY_T , KEY_Y , 0 , KEY_O , KEY_I , KEY_COMMA ,
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
KEY_A , KEY_D , KEY_G , KEY_U , 0 , KEY_L , KEY_ENTER , KEY_DOT ,
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
KEY_Z , KEY_C , KEY_V , KEY_J , TOSA_KEY_ADDRESSBOOK , TOSA_KEY_CANCEL , TOSA_KEY_CENTER , TOSA_KEY_OK ,
KEY_LEFTSHIFT , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
KEY_S , KEY_R , KEY_B , KEY_N , TOSA_KEY_CALENDAR , TOSA_KEY_HOMEPAGE , KEY_LEFTCTRL , TOSA_KEY_LIGHT ,
0 , KEY_RIGHTSHIFT , 0 , 0 , 0 , 0 , 0 , 0 ,
KEY_TAB , KEY_SLASH , KEY_H , KEY_M , TOSA_KEY_MENU , 0 , KEY_UP , 0 ,
0 , 0 , TOSA_KEY_FN , 0 , 0 , 0 , 0 , 0 ,
KEY_X , KEY_F , KEY_SPACE , KEY_APOSTROPHE , TOSA_KEY_MAIL , KEY_LEFT , KEY_DOWN , KEY_RIGHT ,
0 , 0 , 0 ,
} ;
struct tosakbd {
unsigned int keycode [ ARRAY_SIZE ( tosakbd_keycode ) ] ;
struct input_dev * input ;
2008-04-02 11:22:51 -04:00
int suspended ;
2008-01-21 01:04:20 -05:00
spinlock_t lock ; /* protect kbd scanning */
struct timer_list timer ;
} ;
/* Helper functions for reading the keyboard matrix
2008-09-25 13:35:27 +01:00
* Note : We should really be using the generic gpio functions to alter
* GPDR but it requires a function call per GPIO bit which is
* excessive when we need to access 12 bits at once , multiple times .
2008-01-21 01:04:20 -05:00
* These functions must be called within local_irq_save ( ) / local_irq_restore ( )
* or similar .
*/
# define GET_ROWS_STATUS(c) ((GPLR2 & TOSA_GPIO_ALL_SENSE_BIT) >> TOSA_GPIO_ALL_SENSE_RSHIFT)
static inline void tosakbd_discharge_all ( void )
{
/* STROBE All HiZ */
GPCR1 = TOSA_GPIO_HIGH_STROBE_BIT ;
GPDR1 & = ~ TOSA_GPIO_HIGH_STROBE_BIT ;
GPCR2 = TOSA_GPIO_LOW_STROBE_BIT ;
GPDR2 & = ~ TOSA_GPIO_LOW_STROBE_BIT ;
}
static inline void tosakbd_activate_all ( void )
{
/* STROBE ALL -> High */
GPSR1 = TOSA_GPIO_HIGH_STROBE_BIT ;
GPDR1 | = TOSA_GPIO_HIGH_STROBE_BIT ;
GPSR2 = TOSA_GPIO_LOW_STROBE_BIT ;
GPDR2 | = TOSA_GPIO_LOW_STROBE_BIT ;
udelay ( KB_DISCHARGE_DELAY ) ;
/* STATE CLEAR */
GEDR2 | = TOSA_GPIO_ALL_SENSE_BIT ;
}
static inline void tosakbd_activate_col ( int col )
{
if ( col < = 5 ) {
/* STROBE col -> High, not col -> HiZ */
GPSR1 = TOSA_GPIO_STROBE_BIT ( col ) ;
GPDR1 = ( GPDR1 & ~ TOSA_GPIO_HIGH_STROBE_BIT ) | TOSA_GPIO_STROBE_BIT ( col ) ;
} else {
/* STROBE col -> High, not col -> HiZ */
GPSR2 = TOSA_GPIO_STROBE_BIT ( col ) ;
GPDR2 = ( GPDR2 & ~ TOSA_GPIO_LOW_STROBE_BIT ) | TOSA_GPIO_STROBE_BIT ( col ) ;
}
}
static inline void tosakbd_reset_col ( int col )
{
if ( col < = 5 ) {
/* STROBE col -> Low */
GPCR1 = TOSA_GPIO_STROBE_BIT ( col ) ;
/* STROBE col -> out, not col -> HiZ */
GPDR1 = ( GPDR1 & ~ TOSA_GPIO_HIGH_STROBE_BIT ) | TOSA_GPIO_STROBE_BIT ( col ) ;
} else {
/* STROBE col -> Low */
GPCR2 = TOSA_GPIO_STROBE_BIT ( col ) ;
/* STROBE col -> out, not col -> HiZ */
GPDR2 = ( GPDR2 & ~ TOSA_GPIO_LOW_STROBE_BIT ) | TOSA_GPIO_STROBE_BIT ( col ) ;
}
}
/*
* The tosa keyboard only generates interrupts when a key is pressed .
* So when a key is pressed , we enable a timer . This timer scans the
* keyboard , and this is how we detect when the key is released .
*/
/* Scan the hardware keyboard and push any changes up through the input layer */
static void tosakbd_scankeyboard ( struct platform_device * dev )
{
struct tosakbd * tosakbd = platform_get_drvdata ( dev ) ;
unsigned int row , col , rowd ;
unsigned long flags ;
unsigned int num_pressed = 0 ;
spin_lock_irqsave ( & tosakbd - > lock , flags ) ;
2008-04-02 11:22:51 -04:00
if ( tosakbd - > suspended )
goto out ;
2008-01-21 01:04:20 -05:00
for ( col = 0 ; col < TOSA_KEY_STROBE_NUM ; col + + ) {
/*
* Discharge the output driver capacitatance
* in the keyboard matrix . ( Yes it is significant . . )
*/
tosakbd_discharge_all ( ) ;
udelay ( KB_DISCHARGE_DELAY ) ;
tosakbd_activate_col ( col ) ;
udelay ( KB_ACTIVATE_DELAY ) ;
rowd = GET_ROWS_STATUS ( col ) ;
for ( row = 0 ; row < TOSA_KEY_SENSE_NUM ; row + + ) {
unsigned int scancode , pressed ;
scancode = SCANCODE ( row , col ) ;
pressed = rowd & KB_ROWMASK ( row ) ;
if ( pressed & & ! tosakbd - > keycode [ scancode ] )
dev_warn ( & dev - > dev ,
" unhandled scancode: 0x%02x \n " ,
scancode ) ;
input_report_key ( tosakbd - > input ,
tosakbd - > keycode [ scancode ] ,
pressed ) ;
if ( pressed )
num_pressed + + ;
}
tosakbd_reset_col ( col ) ;
}
tosakbd_activate_all ( ) ;
input_sync ( tosakbd - > input ) ;
/* if any keys are pressed, enable the timer */
if ( num_pressed )
mod_timer ( & tosakbd - > timer , jiffies + SCAN_INTERVAL ) ;
2008-04-02 11:22:51 -04:00
out :
2008-01-21 01:04:20 -05:00
spin_unlock_irqrestore ( & tosakbd - > lock , flags ) ;
}
/*
* tosa keyboard interrupt handler .
*/
static irqreturn_t tosakbd_interrupt ( int irq , void * __dev )
{
struct platform_device * dev = __dev ;
struct tosakbd * tosakbd = platform_get_drvdata ( dev ) ;
if ( ! timer_pending ( & tosakbd - > timer ) ) {
/** wait chattering delay **/
udelay ( 20 ) ;
tosakbd_scankeyboard ( dev ) ;
}
return IRQ_HANDLED ;
}
/*
* tosa timer checking for released keys
*/
static void tosakbd_timer_callback ( unsigned long __dev )
{
struct platform_device * dev = ( struct platform_device * ) __dev ;
2008-04-02 11:22:51 -04:00
2008-01-21 01:04:20 -05:00
tosakbd_scankeyboard ( dev ) ;
}
# ifdef CONFIG_PM
static int tosakbd_suspend ( struct platform_device * dev , pm_message_t state )
{
struct tosakbd * tosakbd = platform_get_drvdata ( dev ) ;
2008-04-02 11:22:51 -04:00
unsigned long flags ;
spin_lock_irqsave ( & tosakbd - > lock , flags ) ;
tosakbd - > suspended = 1 ;
spin_unlock_irqrestore ( & tosakbd - > lock , flags ) ;
2008-01-21 01:04:20 -05:00
del_timer_sync ( & tosakbd - > timer ) ;
return 0 ;
}
static int tosakbd_resume ( struct platform_device * dev )
{
2008-04-02 11:22:51 -04:00
struct tosakbd * tosakbd = platform_get_drvdata ( dev ) ;
tosakbd - > suspended = 0 ;
2008-01-21 01:04:20 -05:00
tosakbd_scankeyboard ( dev ) ;
return 0 ;
}
# else
# define tosakbd_suspend NULL
# define tosakbd_resume NULL
# endif
static int __devinit tosakbd_probe ( struct platform_device * pdev ) {
int i ;
struct tosakbd * tosakbd ;
struct input_dev * input_dev ;
int error ;
tosakbd = kzalloc ( sizeof ( struct tosakbd ) , GFP_KERNEL ) ;
if ( ! tosakbd )
return - ENOMEM ;
input_dev = input_allocate_device ( ) ;
if ( ! input_dev ) {
kfree ( tosakbd ) ;
return - ENOMEM ;
}
platform_set_drvdata ( pdev , tosakbd ) ;
spin_lock_init ( & tosakbd - > lock ) ;
/* Init Keyboard rescan timer */
init_timer ( & tosakbd - > timer ) ;
tosakbd - > timer . function = tosakbd_timer_callback ;
tosakbd - > timer . data = ( unsigned long ) pdev ;
tosakbd - > input = input_dev ;
input_set_drvdata ( input_dev , tosakbd ) ;
input_dev - > name = " Tosa Keyboard " ;
input_dev - > phys = " tosakbd/input0 " ;
input_dev - > dev . parent = & pdev - > dev ;
input_dev - > id . bustype = BUS_HOST ;
input_dev - > id . vendor = 0x0001 ;
input_dev - > id . product = 0x0001 ;
input_dev - > id . version = 0x0100 ;
input_dev - > evbit [ 0 ] = BIT ( EV_KEY ) | BIT ( EV_REP ) ;
input_dev - > keycode = tosakbd - > keycode ;
input_dev - > keycodesize = sizeof ( unsigned int ) ;
input_dev - > keycodemax = ARRAY_SIZE ( tosakbd_keycode ) ;
memcpy ( tosakbd - > keycode , tosakbd_keycode , sizeof ( tosakbd_keycode ) ) ;
for ( i = 0 ; i < ARRAY_SIZE ( tosakbd_keycode ) ; i + + )
__set_bit ( tosakbd - > keycode [ i ] , input_dev - > keybit ) ;
clear_bit ( 0 , input_dev - > keybit ) ;
/* Setup sense interrupts - RisingEdge Detect, sense lines as inputs */
for ( i = 0 ; i < TOSA_KEY_SENSE_NUM ; i + + ) {
int gpio = TOSA_GPIO_KEY_SENSE ( i ) ;
int irq ;
error = gpio_request ( gpio , " tosakbd " ) ;
if ( error < 0 ) {
printk ( KERN_ERR " tosakbd: failed to request GPIO %d, "
" error %d \n " , gpio , error ) ;
goto fail ;
}
error = gpio_direction_input ( TOSA_GPIO_KEY_SENSE ( i ) ) ;
if ( error < 0 ) {
printk ( KERN_ERR " tosakbd: failed to configure input "
" direction for GPIO %d, error %d \n " ,
gpio , error ) ;
gpio_free ( gpio ) ;
goto fail ;
}
irq = gpio_to_irq ( gpio ) ;
if ( irq < 0 ) {
error = irq ;
printk ( KERN_ERR " gpio-keys: Unable to get irq number "
" for GPIO %d, error %d \n " ,
gpio , error ) ;
gpio_free ( gpio ) ;
goto fail ;
}
error = request_irq ( irq , tosakbd_interrupt ,
IRQF_DISABLED | IRQF_TRIGGER_RISING ,
" tosakbd " , pdev ) ;
if ( error ) {
printk ( " tosakbd: Can't get IRQ: %d: error %d! \n " ,
irq , error ) ;
gpio_free ( gpio ) ;
goto fail ;
}
}
/* Set Strobe lines as outputs - set high */
for ( i = 0 ; i < TOSA_KEY_STROBE_NUM ; i + + ) {
int gpio = TOSA_GPIO_KEY_STROBE ( i ) ;
error = gpio_request ( gpio , " tosakbd " ) ;
if ( error < 0 ) {
printk ( KERN_ERR " tosakbd: failed to request GPIO %d, "
" error %d \n " , gpio , error ) ;
goto fail2 ;
}
error = gpio_direction_output ( gpio , 1 ) ;
if ( error < 0 ) {
printk ( KERN_ERR " tosakbd: failed to configure input "
" direction for GPIO %d, error %d \n " ,
gpio , error ) ;
gpio_free ( gpio ) ;
goto fail ;
}
}
error = input_register_device ( input_dev ) ;
if ( error ) {
printk ( KERN_ERR " tosakbd: Unable to register input device, "
" error: %d \n " , error ) ;
goto fail ;
}
printk ( KERN_INFO " input: Tosa Keyboard Registered \n " ) ;
return 0 ;
fail2 :
while ( - - i > = 0 )
gpio_free ( TOSA_GPIO_KEY_STROBE ( i ) ) ;
i = TOSA_KEY_SENSE_NUM ;
fail :
while ( - - i > = 0 ) {
free_irq ( gpio_to_irq ( TOSA_GPIO_KEY_SENSE ( i ) ) , pdev ) ;
gpio_free ( TOSA_GPIO_KEY_SENSE ( i ) ) ;
}
platform_set_drvdata ( pdev , NULL ) ;
input_free_device ( input_dev ) ;
kfree ( tosakbd ) ;
return error ;
}
2008-04-02 11:22:51 -04:00
static int __devexit tosakbd_remove ( struct platform_device * dev )
{
2008-01-21 01:04:20 -05:00
int i ;
struct tosakbd * tosakbd = platform_get_drvdata ( dev ) ;
for ( i = 0 ; i < TOSA_KEY_STROBE_NUM ; i + + )
gpio_free ( TOSA_GPIO_KEY_STROBE ( i ) ) ;
for ( i = 0 ; i < TOSA_KEY_SENSE_NUM ; i + + ) {
free_irq ( gpio_to_irq ( TOSA_GPIO_KEY_SENSE ( i ) ) , dev ) ;
gpio_free ( TOSA_GPIO_KEY_SENSE ( i ) ) ;
}
del_timer_sync ( & tosakbd - > timer ) ;
input_unregister_device ( tosakbd - > input ) ;
kfree ( tosakbd ) ;
return 0 ;
}
static struct platform_driver tosakbd_driver = {
. probe = tosakbd_probe ,
. remove = __devexit_p ( tosakbd_remove ) ,
. suspend = tosakbd_suspend ,
. resume = tosakbd_resume ,
. driver = {
. name = " tosa-keyboard " ,
2008-04-18 00:24:42 -04:00
. owner = THIS_MODULE ,
2008-01-21 01:04:20 -05:00
} ,
} ;
static int __devinit tosakbd_init ( void )
{
return platform_driver_register ( & tosakbd_driver ) ;
}
static void __exit tosakbd_exit ( void )
{
platform_driver_unregister ( & tosakbd_driver ) ;
}
module_init ( tosakbd_init ) ;
module_exit ( tosakbd_exit ) ;
MODULE_AUTHOR ( " Dirk Opfer <Dirk@Opfer-Online.de> " ) ;
MODULE_DESCRIPTION ( " Tosa Keyboard Driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
2008-04-18 00:24:42 -04:00
MODULE_ALIAS ( " platform:tosa-keyboard " ) ;