2019-06-04 10:11:33 +02:00
// SPDX-License-Identifier: GPL-2.0-only
2010-04-28 01:10:50 +00:00
/*
* Amstrad E3 ( Delta ) keyboard port driver
*
* Copyright ( c ) 2006 Matt Callow
* Copyright ( c ) 2010 Janusz Krzysztofik
*
* Thanks to Cliff Lawson for his help
*
* The Amstrad Delta keyboard ( aka mailboard ) uses normal PC - AT style serial
* transmission . The keyboard port is formed of two GPIO lines , for clock
* and data . Due to strict timing requirements of the interface ,
* the serial data stream is read and processed by a FIQ handler .
* The resulting words are fetched by this driver from a circular buffer .
*
* Standard AT keyboard driver ( atkbd ) is used for handling the keyboard data .
* However , when used with the E3 mailboard that producecs non - standard
* scancodes , a custom key table must be prepared and loaded from userspace .
*/
# include <linux/irq.h>
2018-06-22 00:41:26 +02:00
# include <linux/platform_data/ams-delta-fiq.h>
2018-06-22 00:41:20 +02:00
# include <linux/platform_device.h>
2018-06-22 00:41:22 +02:00
# include <linux/regulator/consumer.h>
2010-04-28 01:10:50 +00:00
# include <linux/serio.h>
# include <linux/slab.h>
2011-11-09 10:13:33 -08:00
# include <linux/module.h>
2010-04-28 01:10:50 +00:00
2018-06-22 00:41:20 +02:00
# define DRIVER_NAME "ams-delta-serio"
2010-04-28 01:10:50 +00:00
MODULE_AUTHOR ( " Matt Callow " ) ;
MODULE_DESCRIPTION ( " AMS Delta (E3) keyboard port driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
2018-06-22 00:41:21 +02:00
struct ams_delta_serio {
struct serio * serio ;
2018-06-22 00:41:22 +02:00
struct regulator * vcc ;
2018-06-22 00:41:28 +02:00
unsigned int * fiq_buffer ;
2018-06-22 00:41:21 +02:00
} ;
2010-04-28 01:10:50 +00:00
2018-06-22 00:41:21 +02:00
static int check_data ( struct serio * serio , int data )
2010-04-28 01:10:50 +00:00
{
int i , parity = 0 ;
/* check valid stop bit */
if ( ! ( data & 0x400 ) ) {
2018-06-22 00:41:21 +02:00
dev_warn ( & serio - > dev , " invalid stop bit, data=0x%X \n " , data ) ;
2010-04-28 01:10:50 +00:00
return SERIO_FRAME ;
}
/* calculate the parity */
for ( i = 1 ; i < 10 ; i + + ) {
if ( data & ( 1 < < i ) )
parity + + ;
}
/* it should be odd */
if ( ! ( parity & 0x01 ) ) {
2018-06-22 00:41:21 +02:00
dev_warn ( & serio - > dev ,
" parity check failed, data=0x%X parity=0x%X \n " , data ,
parity ) ;
2010-04-28 01:10:50 +00:00
return SERIO_PARITY ;
}
return 0 ;
}
static irqreturn_t ams_delta_serio_interrupt ( int irq , void * dev_id )
{
2018-06-22 00:41:21 +02:00
struct ams_delta_serio * priv = dev_id ;
2018-06-22 00:41:28 +02:00
int * circ_buff = & priv - > fiq_buffer [ FIQ_CIRC_BUFF ] ;
2010-04-28 01:10:50 +00:00
int data , dfl ;
u8 scancode ;
2018-06-22 00:41:28 +02:00
priv - > fiq_buffer [ FIQ_IRQ_PEND ] = 0 ;
2010-04-28 01:10:50 +00:00
/*
* Read data from the circular buffer , check it
* and then pass it on the serio
*/
2018-06-22 00:41:28 +02:00
while ( priv - > fiq_buffer [ FIQ_KEYS_CNT ] > 0 ) {
2010-04-28 01:10:50 +00:00
2018-06-22 00:41:28 +02:00
data = circ_buff [ priv - > fiq_buffer [ FIQ_HEAD_OFFSET ] + + ] ;
priv - > fiq_buffer [ FIQ_KEYS_CNT ] - - ;
if ( priv - > fiq_buffer [ FIQ_HEAD_OFFSET ] = =
priv - > fiq_buffer [ FIQ_BUF_LEN ] )
priv - > fiq_buffer [ FIQ_HEAD_OFFSET ] = 0 ;
2010-04-28 01:10:50 +00:00
2018-06-22 00:41:21 +02:00
dfl = check_data ( priv - > serio , data ) ;
2010-04-28 01:10:50 +00:00
scancode = ( u8 ) ( data > > 1 ) & 0xFF ;
2018-06-22 00:41:21 +02:00
serio_interrupt ( priv - > serio , scancode , dfl ) ;
2010-04-28 01:10:50 +00:00
}
return IRQ_HANDLED ;
}
static int ams_delta_serio_open ( struct serio * serio )
{
2018-06-22 00:41:22 +02:00
struct ams_delta_serio * priv = serio - > port_data ;
2010-04-28 01:10:50 +00:00
2018-06-22 00:41:22 +02:00
/* enable keyboard */
return regulator_enable ( priv - > vcc ) ;
2010-04-28 01:10:50 +00:00
}
static void ams_delta_serio_close ( struct serio * serio )
{
2018-06-22 00:41:22 +02:00
struct ams_delta_serio * priv = serio - > port_data ;
2010-04-28 01:10:50 +00:00
/* disable keyboard */
2018-06-22 00:41:22 +02:00
regulator_disable ( priv - > vcc ) ;
2010-04-28 01:10:50 +00:00
}
2018-06-22 00:41:20 +02:00
static int ams_delta_serio_init ( struct platform_device * pdev )
2010-04-28 01:10:50 +00:00
{
2018-06-22 00:41:21 +02:00
struct ams_delta_serio * priv ;
struct serio * serio ;
2018-06-22 00:41:27 +02:00
int irq , err ;
2010-04-28 01:10:50 +00:00
2018-06-22 00:41:21 +02:00
priv = devm_kzalloc ( & pdev - > dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
2010-04-28 01:10:50 +00:00
return - ENOMEM ;
2018-06-22 00:41:28 +02:00
priv - > fiq_buffer = pdev - > dev . platform_data ;
if ( ! priv - > fiq_buffer )
return - EINVAL ;
2018-06-22 00:41:22 +02:00
priv - > vcc = devm_regulator_get ( & pdev - > dev , " vcc " ) ;
if ( IS_ERR ( priv - > vcc ) ) {
err = PTR_ERR ( priv - > vcc ) ;
dev_err ( & pdev - > dev , " regulator request failed (%d) \n " , err ) ;
/*
* When running on a non - dt platform and requested regulator
* is not available , devm_regulator_get ( ) never returns
* - EPROBE_DEFER as it is not able to justify if the regulator
* may still appear later . On the other hand , the board can
* still set full constriants flag at late_initcall in order
* to instruct devm_regulator_get ( ) to returnn a dummy one
* if sufficient . Hence , if we get - ENODEV here , let ' s convert
* it to - EPROBE_DEFER and wait for the board to decide or
* let Deferred Probe infrastructure handle this error .
*/
if ( err = = - ENODEV )
err = - EPROBE_DEFER ;
2018-06-22 00:41:25 +02:00
return err ;
2018-06-22 00:41:22 +02:00
}
2018-06-22 00:41:27 +02:00
irq = platform_get_irq ( pdev , 0 ) ;
if ( irq < 0 )
return - ENXIO ;
err = devm_request_irq ( & pdev - > dev , irq , ams_delta_serio_interrupt ,
IRQ_TYPE_EDGE_RISING , DRIVER_NAME , priv ) ;
2010-04-28 01:10:50 +00:00
if ( err < 0 ) {
2018-06-22 00:41:20 +02:00
dev_err ( & pdev - > dev , " IRQ request failed (%d) \n " , err ) ;
2018-06-22 00:41:25 +02:00
return err ;
2010-04-28 01:10:50 +00:00
}
2018-06-22 00:41:21 +02:00
serio = kzalloc ( sizeof ( * serio ) , GFP_KERNEL ) ;
2018-06-22 00:41:27 +02:00
if ( ! serio )
return - ENOMEM ;
2018-06-22 00:41:21 +02:00
priv - > serio = serio ;
serio - > id . type = SERIO_8042 ;
serio - > open = ams_delta_serio_open ;
serio - > close = ams_delta_serio_close ;
strlcpy ( serio - > name , " AMS DELTA keyboard adapter " , sizeof ( serio - > name ) ) ;
strlcpy ( serio - > phys , dev_name ( & pdev - > dev ) , sizeof ( serio - > phys ) ) ;
serio - > dev . parent = & pdev - > dev ;
serio - > port_data = priv ;
serio_register_port ( serio ) ;
platform_set_drvdata ( pdev , priv ) ;
dev_info ( & serio - > dev , " %s \n " , serio - > name ) ;
2010-04-28 01:10:50 +00:00
return 0 ;
}
2018-06-22 00:41:20 +02:00
static int ams_delta_serio_exit ( struct platform_device * pdev )
2010-04-28 01:10:50 +00:00
{
2018-06-22 00:41:21 +02:00
struct ams_delta_serio * priv = platform_get_drvdata ( pdev ) ;
serio_unregister_port ( priv - > serio ) ;
2018-06-22 00:41:20 +02:00
return 0 ;
2010-04-28 01:10:50 +00:00
}
2018-06-22 00:41:20 +02:00
static struct platform_driver ams_delta_serio_driver = {
. probe = ams_delta_serio_init ,
. remove = ams_delta_serio_exit ,
. driver = {
. name = DRIVER_NAME
} ,
} ;
module_platform_driver ( ams_delta_serio_driver ) ;