2015-10-19 19:59:14 +03:00
/*
* GPIO driver for the ACCES 104 - IDIO - 16 family
* Copyright ( C ) 2015 William Breathitt Gray
*
* 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 .
*
* 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 .
*/
2015-11-03 15:54:23 +03:00
# include <linux/bitops.h>
2015-10-19 19:59:14 +03:00
# include <linux/device.h>
# include <linux/errno.h>
# include <linux/gpio/driver.h>
# include <linux/io.h>
# include <linux/ioport.h>
2015-11-03 15:54:23 +03:00
# include <linux/interrupt.h>
# include <linux/irqdesc.h>
2015-10-19 19:59:14 +03:00
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/platform_device.h>
# include <linux/spinlock.h>
static unsigned idio_16_base ;
module_param ( idio_16_base , uint , 0 ) ;
MODULE_PARM_DESC ( idio_16_base , " ACCES 104-IDIO-16 base address " ) ;
2015-11-03 15:54:23 +03:00
static unsigned idio_16_irq ;
module_param ( idio_16_irq , uint , 0 ) ;
MODULE_PARM_DESC ( idio_16_irq , " ACCES 104-IDIO-16 interrupt line number " ) ;
2015-10-19 19:59:14 +03:00
/**
* struct idio_16_gpio - GPIO device private data structure
* @ chip : instance of the gpio_chip
2015-11-03 15:54:23 +03:00
* @ lock : synchronization lock to prevent I / O race conditions
* @ irq_mask : I / O bits affected by interrupts
2015-10-19 19:59:14 +03:00
* @ base : base port address of the GPIO device
* @ extent : extent of port address region of the GPIO device
2015-11-03 15:54:23 +03:00
* @ irq : Interrupt line number
2015-10-19 19:59:14 +03:00
* @ out_state : output bits state
*/
struct idio_16_gpio {
struct gpio_chip chip ;
spinlock_t lock ;
2015-11-03 15:54:23 +03:00
unsigned long irq_mask ;
2015-10-19 19:59:14 +03:00
unsigned base ;
unsigned extent ;
2015-11-03 15:54:23 +03:00
unsigned irq ;
2015-10-19 19:59:14 +03:00
unsigned out_state ;
} ;
static int idio_16_gpio_get_direction ( struct gpio_chip * chip , unsigned offset )
{
if ( offset > 15 )
return 1 ;
return 0 ;
}
static int idio_16_gpio_direction_input ( struct gpio_chip * chip , unsigned offset )
{
return 0 ;
}
static int idio_16_gpio_direction_output ( struct gpio_chip * chip ,
unsigned offset , int value )
{
chip - > set ( chip , offset , value ) ;
return 0 ;
}
static struct idio_16_gpio * to_idio16gpio ( struct gpio_chip * gc )
{
return container_of ( gc , struct idio_16_gpio , chip ) ;
}
static int idio_16_gpio_get ( struct gpio_chip * chip , unsigned offset )
{
struct idio_16_gpio * const idio16gpio = to_idio16gpio ( chip ) ;
2015-11-18 02:58:16 +03:00
const unsigned mask = BIT ( offset - 16 ) ;
2015-10-19 19:59:14 +03:00
if ( offset < 16 )
return - EINVAL ;
if ( offset < 24 )
2015-11-18 02:58:16 +03:00
return ! ! ( inb ( idio16gpio - > base + 1 ) & mask ) ;
2015-10-19 19:59:14 +03:00
2015-11-18 02:58:16 +03:00
return ! ! ( inb ( idio16gpio - > base + 5 ) & ( mask > > 8 ) ) ;
2015-10-19 19:59:14 +03:00
}
static void idio_16_gpio_set ( struct gpio_chip * chip , unsigned offset , int value )
{
struct idio_16_gpio * const idio16gpio = to_idio16gpio ( chip ) ;
2015-11-18 02:58:16 +03:00
const unsigned mask = BIT ( offset ) ;
2015-10-19 19:59:14 +03:00
unsigned long flags ;
if ( offset > 15 )
return ;
spin_lock_irqsave ( & idio16gpio - > lock , flags ) ;
if ( value )
2015-11-18 02:58:16 +03:00
idio16gpio - > out_state | = mask ;
2015-10-19 19:59:14 +03:00
else
2015-11-18 02:58:16 +03:00
idio16gpio - > out_state & = ~ mask ;
2015-10-19 19:59:14 +03:00
if ( offset > 7 )
outb ( idio16gpio - > out_state > > 8 , idio16gpio - > base + 4 ) ;
else
outb ( idio16gpio - > out_state , idio16gpio - > base ) ;
spin_unlock_irqrestore ( & idio16gpio - > lock , flags ) ;
}
2015-11-03 15:54:23 +03:00
static void idio_16_irq_ack ( struct irq_data * data )
{
struct gpio_chip * chip = irq_data_get_irq_chip_data ( data ) ;
struct idio_16_gpio * const idio16gpio = to_idio16gpio ( chip ) ;
unsigned long flags ;
spin_lock_irqsave ( & idio16gpio - > lock , flags ) ;
outb ( 0 , idio16gpio - > base + 1 ) ;
spin_unlock_irqrestore ( & idio16gpio - > lock , flags ) ;
}
static void idio_16_irq_mask ( struct irq_data * data )
{
struct gpio_chip * chip = irq_data_get_irq_chip_data ( data ) ;
struct idio_16_gpio * const idio16gpio = to_idio16gpio ( chip ) ;
const unsigned long mask = BIT ( irqd_to_hwirq ( data ) ) ;
unsigned long flags ;
idio16gpio - > irq_mask & = ~ mask ;
if ( ! idio16gpio - > irq_mask ) {
spin_lock_irqsave ( & idio16gpio - > lock , flags ) ;
outb ( 0 , idio16gpio - > base + 2 ) ;
spin_unlock_irqrestore ( & idio16gpio - > lock , flags ) ;
}
}
static void idio_16_irq_unmask ( struct irq_data * data )
{
struct gpio_chip * chip = irq_data_get_irq_chip_data ( data ) ;
struct idio_16_gpio * const idio16gpio = to_idio16gpio ( chip ) ;
const unsigned long mask = BIT ( irqd_to_hwirq ( data ) ) ;
const unsigned long prev_irq_mask = idio16gpio - > irq_mask ;
unsigned long flags ;
idio16gpio - > irq_mask | = mask ;
if ( ! prev_irq_mask ) {
spin_lock_irqsave ( & idio16gpio - > lock , flags ) ;
outb ( 0 , idio16gpio - > base + 1 ) ;
inb ( idio16gpio - > base + 2 ) ;
spin_unlock_irqrestore ( & idio16gpio - > lock , flags ) ;
}
}
static int idio_16_irq_set_type ( struct irq_data * data , unsigned flow_type )
{
/* The only valid irq types are none and both-edges */
if ( flow_type ! = IRQ_TYPE_NONE & &
( flow_type & IRQ_TYPE_EDGE_BOTH ) ! = IRQ_TYPE_EDGE_BOTH )
return - EINVAL ;
return 0 ;
}
static struct irq_chip idio_16_irqchip = {
. name = " 104-idio-16 " ,
. irq_ack = idio_16_irq_ack ,
. irq_mask = idio_16_irq_mask ,
. irq_unmask = idio_16_irq_unmask ,
. irq_set_type = idio_16_irq_set_type
} ;
static irqreturn_t idio_16_irq_handler ( int irq , void * dev_id )
{
struct idio_16_gpio * const idio16gpio = dev_id ;
struct gpio_chip * const chip = & idio16gpio - > chip ;
int gpio ;
for_each_set_bit ( gpio , & idio16gpio - > irq_mask , chip - > ngpio )
generic_handle_irq ( irq_find_mapping ( chip - > irqdomain , gpio ) ) ;
return IRQ_HANDLED ;
}
2015-10-19 19:59:14 +03:00
static int __init idio_16_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct idio_16_gpio * idio16gpio ;
2015-11-18 02:58:16 +03:00
const unsigned base = idio_16_base ;
const unsigned extent = 8 ;
const char * const name = dev_name ( dev ) ;
2015-10-19 19:59:14 +03:00
int err ;
2015-11-18 02:58:16 +03:00
const unsigned irq = idio_16_irq ;
2015-10-19 19:59:14 +03:00
idio16gpio = devm_kzalloc ( dev , sizeof ( * idio16gpio ) , GFP_KERNEL ) ;
if ( ! idio16gpio )
return - ENOMEM ;
2015-11-18 02:58:16 +03:00
if ( ! request_region ( base , extent , name ) ) {
2015-10-19 19:59:14 +03:00
dev_err ( dev , " Unable to lock %s port addresses (0x%X-0x%X) \n " ,
2015-11-18 02:58:16 +03:00
name , base , base + extent ) ;
2015-10-19 19:59:14 +03:00
err = - EBUSY ;
goto err_lock_io_port ;
}
2015-11-18 02:58:16 +03:00
idio16gpio - > chip . label = name ;
2015-11-04 11:56:26 +03:00
idio16gpio - > chip . parent = dev ;
2015-10-19 19:59:14 +03:00
idio16gpio - > chip . owner = THIS_MODULE ;
idio16gpio - > chip . base = - 1 ;
idio16gpio - > chip . ngpio = 32 ;
idio16gpio - > chip . get_direction = idio_16_gpio_get_direction ;
idio16gpio - > chip . direction_input = idio_16_gpio_direction_input ;
idio16gpio - > chip . direction_output = idio_16_gpio_direction_output ;
idio16gpio - > chip . get = idio_16_gpio_get ;
idio16gpio - > chip . set = idio_16_gpio_set ;
2015-11-18 02:58:16 +03:00
idio16gpio - > base = base ;
idio16gpio - > extent = extent ;
idio16gpio - > irq = irq ;
2015-10-19 19:59:14 +03:00
idio16gpio - > out_state = 0xFFFF ;
spin_lock_init ( & idio16gpio - > lock ) ;
dev_set_drvdata ( dev , idio16gpio ) ;
err = gpiochip_add ( & idio16gpio - > chip ) ;
if ( err ) {
dev_err ( dev , " GPIO registering failed (%d) \n " , err ) ;
goto err_gpio_register ;
}
2015-11-22 19:38:55 +03:00
/* Disable IRQ by default */
outb ( 0 , base + 2 ) ;
2015-11-03 15:54:23 +03:00
err = gpiochip_irqchip_add ( & idio16gpio - > chip , & idio_16_irqchip , 0 ,
handle_edge_irq , IRQ_TYPE_NONE ) ;
if ( err ) {
dev_err ( dev , " Could not add irqchip (%d) \n " , err ) ;
goto err_gpiochip_irqchip_add ;
}
2015-11-18 02:58:16 +03:00
err = request_irq ( irq , idio_16_irq_handler , 0 , name , idio16gpio ) ;
2015-11-03 15:54:23 +03:00
if ( err ) {
dev_err ( dev , " IRQ handler registering failed (%d) \n " , err ) ;
goto err_request_irq ;
}
2015-10-19 19:59:14 +03:00
return 0 ;
2015-11-03 15:54:23 +03:00
err_request_irq :
err_gpiochip_irqchip_add :
gpiochip_remove ( & idio16gpio - > chip ) ;
2015-10-19 19:59:14 +03:00
err_gpio_register :
2015-11-18 02:58:16 +03:00
release_region ( base , extent ) ;
2015-10-19 19:59:14 +03:00
err_lock_io_port :
return err ;
}
static int idio_16_remove ( struct platform_device * pdev )
{
struct idio_16_gpio * const idio16gpio = platform_get_drvdata ( pdev ) ;
2015-11-03 15:54:23 +03:00
free_irq ( idio16gpio - > irq , idio16gpio ) ;
2015-10-19 19:59:14 +03:00
gpiochip_remove ( & idio16gpio - > chip ) ;
release_region ( idio16gpio - > base , idio16gpio - > extent ) ;
return 0 ;
}
static struct platform_device * idio_16_device ;
static struct platform_driver idio_16_driver = {
. driver = {
. name = " 104-idio-16 "
} ,
. remove = idio_16_remove
} ;
static void __exit idio_16_exit ( void )
{
platform_device_unregister ( idio_16_device ) ;
platform_driver_unregister ( & idio_16_driver ) ;
}
static int __init idio_16_init ( void )
{
int err ;
idio_16_device = platform_device_alloc ( idio_16_driver . driver . name , - 1 ) ;
if ( ! idio_16_device )
return - ENOMEM ;
err = platform_device_add ( idio_16_device ) ;
if ( err )
goto err_platform_device ;
err = platform_driver_probe ( & idio_16_driver , idio_16_probe ) ;
if ( err )
goto err_platform_driver ;
return 0 ;
err_platform_driver :
platform_device_del ( idio_16_device ) ;
err_platform_device :
platform_device_put ( idio_16_device ) ;
return err ;
}
module_init ( idio_16_init ) ;
module_exit ( idio_16_exit ) ;
MODULE_AUTHOR ( " William Breathitt Gray <vilhelm.gray@gmail.com> " ) ;
MODULE_DESCRIPTION ( " ACCES 104-IDIO-16 GPIO driver " ) ;
MODULE_LICENSE ( " GPL " ) ;