2010-07-27 03:32:03 +04:00
/*
2017-03-07 22:13:15 +03:00
* IR SIR driver , ( C ) 2000 Milan Pikula < www @ fornax . sk >
2010-07-27 03:32:03 +04:00
*
2016-12-20 01:07:08 +03:00
* sir_ir - Device driver for use with SIR ( serial infra red )
2010-07-27 03:32:03 +04:00
* mode of IrDA on many notebooks .
*
* 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 .
*/
2012-11-08 22:53:37 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2010-07-27 03:32:03 +04:00
# include <linux/module.h>
# include <linux/interrupt.h>
# include <linux/kernel.h>
# include <linux/serial_reg.h>
2015-05-22 18:58:42 +03:00
# include <linux/ktime.h>
2010-07-27 03:32:03 +04:00
# include <linux/delay.h>
2012-06-04 20:05:24 +04:00
# include <linux/platform_device.h>
2010-07-27 03:32:03 +04:00
2016-12-20 01:07:08 +03:00
# include <media/rc-core.h>
2010-07-27 03:32:03 +04:00
/* SECTION: Definitions */
# define PULSE '['
/* 9bit * 1s/115200bit in milli seconds = 78.125ms*/
2017-03-07 22:25:45 +03:00
# define TIME_CONST (9000000ul / 115200ul)
2010-07-27 03:32:03 +04:00
/* timeout for sequences in jiffies (=5/100s), must be longer than TIME_CONST */
2017-03-07 22:25:45 +03:00
# define SIR_TIMEOUT (HZ * 5 / 100)
2010-07-27 03:32:03 +04:00
/* onboard sir ports are typically com3 */
2017-03-07 22:13:15 +03:00
static int io = 0x3e8 ;
static int irq = 4 ;
2010-07-27 03:32:03 +04:00
static int threshold = 3 ;
static DEFINE_SPINLOCK ( timer_lock ) ;
static struct timer_list timerlist ;
/* time of last signal change detected */
2015-05-22 18:58:42 +03:00
static ktime_t last ;
2010-07-27 03:32:03 +04:00
/* time of last UART data ready interrupt */
2015-05-22 18:58:42 +03:00
static ktime_t last_intr_time ;
2010-07-27 03:32:03 +04:00
static int last_value ;
2016-12-20 01:07:08 +03:00
static struct rc_dev * rcdev ;
2010-07-27 03:32:03 +04:00
2016-12-20 01:07:08 +03:00
static struct platform_device * sir_ir_dev ;
2010-07-27 03:32:03 +04:00
static DEFINE_SPINLOCK ( hardware_lock ) ;
/* SECTION: Prototypes */
/* Communication with user-space */
static void add_read_queue ( int flag , unsigned long val ) ;
/* Hardware */
static irqreturn_t sir_interrupt ( int irq , void * dev_id ) ;
static void send_space ( unsigned long len ) ;
static void send_pulse ( unsigned long len ) ;
2017-05-17 20:32:52 +03:00
static void init_hardware ( void ) ;
2010-07-27 03:32:03 +04:00
static void drop_hardware ( void ) ;
/* Initialisation */
static inline unsigned int sinp ( int offset )
{
return inb ( io + offset ) ;
}
static inline void soutp ( int offset , int value )
{
outb ( value , io + offset ) ;
}
/* SECTION: Communication with user-space */
2016-12-20 01:07:08 +03:00
static int sir_tx_ir ( struct rc_dev * dev , unsigned int * tx_buf ,
unsigned int count )
2010-07-27 03:32:03 +04:00
{
unsigned long flags ;
2016-12-20 01:07:08 +03:00
int i ;
2010-07-27 03:32:03 +04:00
local_irq_save ( flags ) ;
2016-12-20 01:07:08 +03:00
for ( i = 0 ; i < count ; ) {
2010-07-27 03:32:03 +04:00
if ( tx_buf [ i ] )
send_pulse ( tx_buf [ i ] ) ;
i + + ;
if ( i > = count )
break ;
if ( tx_buf [ i ] )
send_space ( tx_buf [ i ] ) ;
i + + ;
}
local_irq_restore ( flags ) ;
2016-12-20 01:07:08 +03:00
return count ;
2010-07-27 03:32:03 +04:00
}
static void add_read_queue ( int flag , unsigned long val )
{
2016-12-20 01:07:08 +03:00
DEFINE_IR_RAW_EVENT ( ev ) ;
2010-07-27 03:32:03 +04:00
2014-10-26 20:46:00 +03:00
pr_debug ( " add flag %d with val %lu \n " , flag , val ) ;
2010-07-27 03:32:03 +04:00
/*
* statistically , pulses are ~ TIME_CONST / 2 too long . we could
* maybe make this more exact , but this is good enough
*/
if ( flag ) {
/* pulse */
2016-12-20 01:07:08 +03:00
if ( val > TIME_CONST / 2 )
val - = TIME_CONST / 2 ;
2010-07-27 03:32:03 +04:00
else /* should not ever happen */
2016-12-20 01:07:08 +03:00
val = 1 ;
ev . pulse = true ;
2010-07-27 03:32:03 +04:00
} else {
2016-12-20 01:07:08 +03:00
val + = TIME_CONST / 2 ;
2010-07-27 03:32:03 +04:00
}
2016-12-20 01:07:08 +03:00
ev . duration = US_TO_NS ( val ) ;
2010-07-27 03:32:03 +04:00
2016-12-20 01:07:08 +03:00
ir_raw_event_store_with_filter ( rcdev , & ev ) ;
2010-07-27 03:32:03 +04:00
}
/* SECTION: Hardware */
static void sir_timeout ( unsigned long data )
{
/*
* if last received signal was a pulse , but receiving stopped
* within the 9 bit frame , we need to finish this pulse and
* simulate a signal change to from pulse to space . Otherwise
* upper layers will receive two sequences next time .
*/
unsigned long flags ;
unsigned long pulse_end ;
/* avoid interference with interrupt */
spin_lock_irqsave ( & timer_lock , flags ) ;
if ( last_value ) {
/* clear unread bits in UART and restart */
outb ( UART_FCR_CLEAR_RCVR , io + UART_FCR ) ;
/* determine 'virtual' pulse end: */
2015-05-22 18:58:42 +03:00
pulse_end = min_t ( unsigned long ,
ktime_us_delta ( last , last_intr_time ) ,
2016-12-20 01:07:08 +03:00
IR_MAX_DURATION ) ;
dev_dbg ( & sir_ir_dev - > dev , " timeout add %d for %lu usec \n " ,
last_value , pulse_end ) ;
2010-07-27 03:32:03 +04:00
add_read_queue ( last_value , pulse_end ) ;
last_value = 0 ;
2015-05-22 18:58:42 +03:00
last = last_intr_time ;
2010-07-27 03:32:03 +04:00
}
spin_unlock_irqrestore ( & timer_lock , flags ) ;
2016-12-20 01:07:08 +03:00
ir_raw_event_handle ( rcdev ) ;
2010-07-27 03:32:03 +04:00
}
static irqreturn_t sir_interrupt ( int irq , void * dev_id )
{
unsigned char data ;
2015-05-22 18:58:42 +03:00
ktime_t curr_time ;
static unsigned long delt ;
unsigned long deltintr ;
2010-07-27 03:32:03 +04:00
unsigned long flags ;
2017-05-16 10:56:14 +03:00
int counter = 0 ;
2010-07-27 03:32:03 +04:00
int iir , lsr ;
while ( ( iir = inb ( io + UART_IIR ) & UART_IIR_ID ) ) {
2017-05-16 10:56:14 +03:00
if ( + + counter > 256 ) {
dev_err ( & sir_ir_dev - > dev , " Trapped in interrupt " ) ;
break ;
}
2017-03-07 22:25:45 +03:00
switch ( iir & UART_IIR_ID ) { /* FIXME toto treba preriedit */
2010-07-27 03:32:03 +04:00
case UART_IIR_MSI :
2017-03-07 22:25:45 +03:00
( void ) inb ( io + UART_MSR ) ;
2010-07-27 03:32:03 +04:00
break ;
case UART_IIR_RLSI :
case UART_IIR_THRI :
2017-03-07 22:25:45 +03:00
( void ) inb ( io + UART_LSR ) ;
2010-07-27 03:32:03 +04:00
break ;
case UART_IIR_RDI :
/* avoid interference with timer */
spin_lock_irqsave ( & timer_lock , flags ) ;
do {
del_timer ( & timerlist ) ;
data = inb ( io + UART_RX ) ;
2015-05-22 18:58:42 +03:00
curr_time = ktime_get ( ) ;
delt = min_t ( unsigned long ,
ktime_us_delta ( last , curr_time ) ,
2016-12-20 01:07:08 +03:00
IR_MAX_DURATION ) ;
2015-05-22 18:58:42 +03:00
deltintr = min_t ( unsigned long ,
ktime_us_delta ( last_intr_time ,
curr_time ) ,
2016-12-20 01:07:08 +03:00
IR_MAX_DURATION ) ;
dev_dbg ( & sir_ir_dev - > dev , " t %lu, d %d \n " ,
deltintr , ( int ) data ) ;
2010-07-27 03:32:03 +04:00
/*
* if nothing came in last X cycles ,
* it was gap
*/
2015-05-22 18:58:42 +03:00
if ( deltintr > TIME_CONST * threshold ) {
2010-07-27 03:32:03 +04:00
if ( last_value ) {
2016-12-20 01:07:08 +03:00
dev_dbg ( & sir_ir_dev - > dev , " GAP \n " ) ;
2010-07-27 03:32:03 +04:00
/* simulate signal change */
add_read_queue ( last_value ,
2015-05-22 18:58:42 +03:00
delt -
deltintr ) ;
2010-07-27 03:32:03 +04:00
last_value = 0 ;
2015-05-22 18:58:42 +03:00
last = last_intr_time ;
delt = deltintr ;
2010-07-27 03:32:03 +04:00
}
}
data = 1 ;
if ( data ^ last_value ) {
/*
2015-05-22 18:58:42 +03:00
* deltintr > 2 * TIME_CONST , remember ?
2010-07-27 03:32:03 +04:00
* the other case is timeout
*/
add_read_queue ( last_value ,
2017-03-07 22:25:45 +03:00
delt - TIME_CONST ) ;
2010-07-27 03:32:03 +04:00
last_value = data ;
2015-05-22 18:58:42 +03:00
last = curr_time ;
last = ktime_sub_us ( last ,
TIME_CONST ) ;
2010-07-27 03:32:03 +04:00
}
2015-05-22 18:58:42 +03:00
last_intr_time = curr_time ;
2010-07-27 03:32:03 +04:00
if ( data ) {
/*
* start timer for end of
* sequence detection
*/
timerlist . expires = jiffies +
SIR_TIMEOUT ;
add_timer ( & timerlist ) ;
}
lsr = inb ( io + UART_LSR ) ;
} while ( lsr & UART_LSR_DR ) ; /* data ready */
spin_unlock_irqrestore ( & timer_lock , flags ) ;
break ;
default :
break ;
}
}
2016-12-20 01:07:08 +03:00
ir_raw_event_handle ( rcdev ) ;
2010-07-27 03:32:03 +04:00
return IRQ_RETVAL ( IRQ_HANDLED ) ;
}
static void send_space ( unsigned long len )
{
2017-03-07 23:01:48 +03:00
usleep_range ( len , len + 25 ) ;
2010-07-27 03:32:03 +04:00
}
static void send_pulse ( unsigned long len )
{
long bytes_out = len / TIME_CONST ;
2011-05-27 22:46:19 +04:00
if ( bytes_out = = 0 )
2010-07-27 03:32:03 +04:00
bytes_out + + ;
2011-05-27 22:46:19 +04:00
2010-07-27 03:32:03 +04:00
while ( bytes_out - - ) {
outb ( PULSE , io + UART_TX ) ;
/* FIXME treba seriozne cakanie z char/serial.c */
while ( ! ( inb ( io + UART_LSR ) & UART_LSR_THRE ) )
;
}
}
2017-05-17 20:32:52 +03:00
static void init_hardware ( void )
2010-07-27 03:32:03 +04:00
{
unsigned long flags ;
spin_lock_irqsave ( & hardware_lock , flags ) ;
/* reset UART */
outb ( 0 , io + UART_MCR ) ;
outb ( 0 , io + UART_IER ) ;
/* init UART */
/* set DLAB, speed = 115200 */
outb ( UART_LCR_DLAB | UART_LCR_WLEN7 , io + UART_LCR ) ;
outb ( 1 , io + UART_DLL ) ; outb ( 0 , io + UART_DLM ) ;
/* 7N1+start = 9 bits at 115200 ~ 3 bits at 44000 */
outb ( UART_LCR_WLEN7 , io + UART_LCR ) ;
/* FIFO operation */
outb ( UART_FCR_ENABLE_FIFO , io + UART_FCR ) ;
/* interrupts */
/* outb(UART_IER_RLSI|UART_IER_RDI|UART_IER_THRI, io + UART_IER); */
outb ( UART_IER_RDI , io + UART_IER ) ;
/* turn on UART */
2017-03-07 22:25:45 +03:00
outb ( UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2 , io + UART_MCR ) ;
2010-07-27 03:32:03 +04:00
spin_unlock_irqrestore ( & hardware_lock , flags ) ;
}
static void drop_hardware ( void )
{
unsigned long flags ;
spin_lock_irqsave ( & hardware_lock , flags ) ;
/* turn off interrupts */
outb ( 0 , io + UART_IER ) ;
2014-06-06 00:48:11 +04:00
2010-07-27 03:32:03 +04:00
spin_unlock_irqrestore ( & hardware_lock , flags ) ;
}
/* SECTION: Initialisation */
2017-05-17 20:32:53 +03:00
static int sir_ir_probe ( struct platform_device * dev )
2010-07-27 03:32:03 +04:00
{
int retval ;
2017-05-17 20:32:53 +03:00
rcdev = devm_rc_allocate_device ( & sir_ir_dev - > dev , RC_DRIVER_IR_RAW ) ;
if ( ! rcdev )
return - ENOMEM ;
rcdev - > input_name = " SIR IrDA port " ;
rcdev - > input_phys = KBUILD_MODNAME " /input0 " ;
rcdev - > input_id . bustype = BUS_HOST ;
rcdev - > input_id . vendor = 0x0001 ;
rcdev - > input_id . product = 0x0001 ;
rcdev - > input_id . version = 0x0100 ;
rcdev - > tx_ir = sir_tx_ir ;
rcdev - > allowed_protocols = RC_BIT_ALL_IR_DECODER ;
rcdev - > driver_name = KBUILD_MODNAME ;
rcdev - > map_name = RC_MAP_RC6_MCE ;
rcdev - > timeout = IR_DEFAULT_TIMEOUT ;
rcdev - > dev . parent = & sir_ir_dev - > dev ;
2017-03-25 14:45:38 +03:00
setup_timer ( & timerlist , sir_timeout , 0 ) ;
2010-07-27 03:32:03 +04:00
/* get I/O port access and IRQ line */
2017-05-17 20:32:51 +03:00
if ( ! devm_request_region ( & sir_ir_dev - > dev , io , 8 , KBUILD_MODNAME ) ) {
2012-11-08 22:53:37 +04:00
pr_err ( " i/o port 0x%.4x already in use. \n " , io ) ;
2010-07-27 03:32:03 +04:00
return - EBUSY ;
}
2017-05-17 20:32:51 +03:00
retval = devm_request_irq ( & sir_ir_dev - > dev , irq , sir_interrupt , 0 ,
KBUILD_MODNAME , NULL ) ;
2010-07-27 03:32:03 +04:00
if ( retval < 0 ) {
2012-11-08 22:53:37 +04:00
pr_err ( " IRQ %d already in use. \n " , irq ) ;
2010-07-27 03:32:03 +04:00
return retval ;
}
2012-11-08 22:53:37 +04:00
pr_info ( " I/O port 0x%.4x, IRQ %d. \n " , io , irq ) ;
2010-07-27 03:32:03 +04:00
2017-05-17 20:32:53 +03:00
retval = devm_rc_register_device ( & sir_ir_dev - > dev , rcdev ) ;
2017-03-25 13:31:57 +03:00
if ( retval < 0 )
return retval ;
2017-05-17 20:32:53 +03:00
init_hardware ( ) ;
return 0 ;
2012-06-04 20:05:24 +04:00
}
2016-12-20 01:07:08 +03:00
static int sir_ir_remove ( struct platform_device * dev )
2012-06-04 20:05:24 +04:00
{
2017-05-17 20:32:50 +03:00
drop_hardware ( ) ;
2017-05-17 20:32:52 +03:00
del_timer_sync ( & timerlist ) ;
2012-06-04 20:05:24 +04:00
return 0 ;
}
2016-12-20 01:07:08 +03:00
static struct platform_driver sir_ir_driver = {
. probe = sir_ir_probe ,
. remove = sir_ir_remove ,
2012-06-04 20:05:24 +04:00
. driver = {
2016-12-20 01:07:08 +03:00
. name = " sir_ir " ,
2012-06-04 20:05:24 +04:00
} ,
} ;
2010-07-27 03:32:03 +04:00
2016-12-20 01:07:08 +03:00
static int __init sir_ir_init ( void )
2010-07-27 03:32:03 +04:00
{
int retval ;
2016-12-20 01:07:08 +03:00
retval = platform_driver_register ( & sir_ir_driver ) ;
2017-03-25 13:41:39 +03:00
if ( retval )
return retval ;
2012-06-04 20:05:24 +04:00
2016-12-20 01:07:08 +03:00
sir_ir_dev = platform_device_alloc ( " sir_ir " , 0 ) ;
if ( ! sir_ir_dev ) {
2012-06-04 20:05:24 +04:00
retval = - ENOMEM ;
goto pdev_alloc_fail ;
}
2016-12-20 01:07:08 +03:00
retval = platform_device_add ( sir_ir_dev ) ;
2017-03-25 13:41:39 +03:00
if ( retval )
2012-06-04 20:05:24 +04:00
goto pdev_add_fail ;
2010-07-27 03:32:03 +04:00
return 0 ;
2012-06-04 20:05:24 +04:00
pdev_add_fail :
2016-12-20 01:07:08 +03:00
platform_device_put ( sir_ir_dev ) ;
2012-06-04 20:05:24 +04:00
pdev_alloc_fail :
2016-12-20 01:07:08 +03:00
platform_driver_unregister ( & sir_ir_driver ) ;
2012-06-04 20:05:24 +04:00
return retval ;
2010-07-27 03:32:03 +04:00
}
2016-12-20 01:07:08 +03:00
static void __exit sir_ir_exit ( void )
2010-07-27 03:32:03 +04:00
{
2016-12-20 01:07:08 +03:00
platform_device_unregister ( sir_ir_dev ) ;
platform_driver_unregister ( & sir_ir_driver ) ;
2010-07-27 03:32:03 +04:00
}
2016-12-20 01:07:08 +03:00
module_init ( sir_ir_init ) ;
module_exit ( sir_ir_exit ) ;
2010-07-27 03:32:03 +04:00
MODULE_DESCRIPTION ( " Infrared receiver driver for SIR type serial ports " ) ;
MODULE_AUTHOR ( " Milan Pikula " ) ;
MODULE_LICENSE ( " GPL " ) ;
2017-06-02 13:19:39 +03:00
module_param_hw ( io , int , ioport , 0444 ) ;
2010-07-27 03:32:03 +04:00
MODULE_PARM_DESC ( io , " I/O address base (0x3f8 or 0x2f8) " ) ;
2017-06-02 13:19:39 +03:00
module_param_hw ( irq , int , irq , 0444 ) ;
2010-07-27 03:32:03 +04:00
MODULE_PARM_DESC ( irq , " Interrupt (4 or 3) " ) ;
2017-02-11 03:42:38 +03:00
module_param ( threshold , int , 0444 ) ;
2010-07-27 03:32:03 +04:00
MODULE_PARM_DESC ( threshold , " space detection threshold (3) " ) ;