2009-06-19 03:48:58 +04:00
/*
* linux / drivers / gpio / pl061 . c
*
* Copyright ( C ) 2008 , 2009 Provigent Ltd .
*
* 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 .
*
* Driver for the ARM PrimeCell ( tm ) General Purpose Input / Output ( PL061 )
*
* Data sheet : ARM DDI 01 90 B , September 2000
*/
# include <linux/spinlock.h>
# include <linux/errno.h>
# include <linux/module.h>
# include <linux/list.h>
# include <linux/io.h>
# include <linux/ioport.h>
# include <linux/irq.h>
# include <linux/bitops.h>
# include <linux/workqueue.h>
# include <linux/gpio.h>
# include <linux/device.h>
# include <linux/amba/bus.h>
# include <linux/amba/pl061.h>
# define GPIODIR 0x400
# define GPIOIS 0x404
# define GPIOIBE 0x408
# define GPIOIEV 0x40C
# define GPIOIE 0x410
# define GPIORIS 0x414
# define GPIOMIS 0x418
# define GPIOIC 0x41C
# define PL061_GPIO_NR 8
struct pl061_gpio {
/* We use a list of pl061_gpio structs for each trigger IRQ in the main
* interrupts controller of the system . We need this to support systems
* in which more that one PL061s are connected to the same IRQ . The ISR
* interates through this list to find the source of the interrupt .
*/
struct list_head list ;
/* Each of the two spinlocks protects a different set of hardware
* regiters and data structurs . This decouples the code of the IRQ from
* the GPIO code . This also makes the case of a GPIO routine call from
* the IRQ code simpler .
*/
spinlock_t lock ; /* GPIO registers */
spinlock_t irq_lock ; /* IRQ registers */
void __iomem * base ;
unsigned irq_base ;
struct gpio_chip gc ;
} ;
static int pl061_direction_input ( struct gpio_chip * gc , unsigned offset )
{
struct pl061_gpio * chip = container_of ( gc , struct pl061_gpio , gc ) ;
unsigned long flags ;
unsigned char gpiodir ;
if ( offset > = gc - > ngpio )
return - EINVAL ;
spin_lock_irqsave ( & chip - > lock , flags ) ;
gpiodir = readb ( chip - > base + GPIODIR ) ;
gpiodir & = ~ ( 1 < < offset ) ;
writeb ( gpiodir , chip - > base + GPIODIR ) ;
spin_unlock_irqrestore ( & chip - > lock , flags ) ;
return 0 ;
}
static int pl061_direction_output ( struct gpio_chip * gc , unsigned offset ,
int value )
{
struct pl061_gpio * chip = container_of ( gc , struct pl061_gpio , gc ) ;
unsigned long flags ;
unsigned char gpiodir ;
if ( offset > = gc - > ngpio )
return - EINVAL ;
spin_lock_irqsave ( & chip - > lock , flags ) ;
writeb ( ! ! value < < offset , chip - > base + ( 1 < < ( offset + 2 ) ) ) ;
gpiodir = readb ( chip - > base + GPIODIR ) ;
gpiodir | = 1 < < offset ;
writeb ( gpiodir , chip - > base + GPIODIR ) ;
spin_unlock_irqrestore ( & chip - > lock , flags ) ;
return 0 ;
}
static int pl061_get_value ( struct gpio_chip * gc , unsigned offset )
{
struct pl061_gpio * chip = container_of ( gc , struct pl061_gpio , gc ) ;
return ! ! readb ( chip - > base + ( 1 < < ( offset + 2 ) ) ) ;
}
static void pl061_set_value ( struct gpio_chip * gc , unsigned offset , int value )
{
struct pl061_gpio * chip = container_of ( gc , struct pl061_gpio , gc ) ;
writeb ( ! ! value < < offset , chip - > base + ( 1 < < ( offset + 2 ) ) ) ;
}
2009-06-30 22:41:39 +04:00
static int pl061_to_irq ( struct gpio_chip * gc , unsigned offset )
{
struct pl061_gpio * chip = container_of ( gc , struct pl061_gpio , gc ) ;
if ( chip - > irq_base = = ( unsigned ) - 1 )
return - EINVAL ;
return chip - > irq_base + offset ;
}
2009-06-19 03:48:58 +04:00
/*
* PL061 GPIO IRQ
*/
static void pl061_irq_disable ( unsigned irq )
{
struct pl061_gpio * chip = get_irq_chip_data ( irq ) ;
int offset = irq - chip - > irq_base ;
unsigned long flags ;
u8 gpioie ;
spin_lock_irqsave ( & chip - > irq_lock , flags ) ;
gpioie = readb ( chip - > base + GPIOIE ) ;
gpioie & = ~ ( 1 < < offset ) ;
writeb ( gpioie , chip - > base + GPIOIE ) ;
spin_unlock_irqrestore ( & chip - > irq_lock , flags ) ;
}
static void pl061_irq_enable ( unsigned irq )
{
struct pl061_gpio * chip = get_irq_chip_data ( irq ) ;
int offset = irq - chip - > irq_base ;
unsigned long flags ;
u8 gpioie ;
spin_lock_irqsave ( & chip - > irq_lock , flags ) ;
gpioie = readb ( chip - > base + GPIOIE ) ;
gpioie | = 1 < < offset ;
writeb ( gpioie , chip - > base + GPIOIE ) ;
spin_unlock_irqrestore ( & chip - > irq_lock , flags ) ;
}
static int pl061_irq_type ( unsigned irq , unsigned trigger )
{
struct pl061_gpio * chip = get_irq_chip_data ( irq ) ;
int offset = irq - chip - > irq_base ;
unsigned long flags ;
u8 gpiois , gpioibe , gpioiev ;
if ( offset < 0 | | offset > PL061_GPIO_NR )
return - EINVAL ;
spin_lock_irqsave ( & chip - > irq_lock , flags ) ;
gpioiev = readb ( chip - > base + GPIOIEV ) ;
gpiois = readb ( chip - > base + GPIOIS ) ;
if ( trigger & ( IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW ) ) {
gpiois | = 1 < < offset ;
if ( trigger & IRQ_TYPE_LEVEL_HIGH )
gpioiev | = 1 < < offset ;
else
gpioiev & = ~ ( 1 < < offset ) ;
} else
gpiois & = ~ ( 1 < < offset ) ;
writeb ( gpiois , chip - > base + GPIOIS ) ;
gpioibe = readb ( chip - > base + GPIOIBE ) ;
if ( ( trigger & IRQ_TYPE_EDGE_BOTH ) = = IRQ_TYPE_EDGE_BOTH )
gpioibe | = 1 < < offset ;
else {
gpioibe & = ~ ( 1 < < offset ) ;
if ( trigger & IRQ_TYPE_EDGE_RISING )
gpioiev | = 1 < < offset ;
else
gpioiev & = ~ ( 1 < < offset ) ;
}
writeb ( gpioibe , chip - > base + GPIOIBE ) ;
writeb ( gpioiev , chip - > base + GPIOIEV ) ;
spin_unlock_irqrestore ( & chip - > irq_lock , flags ) ;
return 0 ;
}
static struct irq_chip pl061_irqchip = {
. name = " GPIO " ,
. enable = pl061_irq_enable ,
. disable = pl061_irq_disable ,
. set_type = pl061_irq_type ,
} ;
static void pl061_irq_handler ( unsigned irq , struct irq_desc * desc )
{
struct list_head * chip_list = get_irq_chip_data ( irq ) ;
struct list_head * ptr ;
struct pl061_gpio * chip ;
desc - > chip - > ack ( irq ) ;
list_for_each ( ptr , chip_list ) {
unsigned long pending ;
2009-06-30 22:41:39 +04:00
int offset ;
2009-06-19 03:48:58 +04:00
chip = list_entry ( ptr , struct pl061_gpio , list ) ;
pending = readb ( chip - > base + GPIOMIS ) ;
writeb ( pending , chip - > base + GPIOIC ) ;
if ( pending = = 0 )
continue ;
2009-06-30 22:41:39 +04:00
for_each_bit ( offset , & pending , PL061_GPIO_NR )
generic_handle_irq ( pl061_to_irq ( & chip - > gc , offset ) ) ;
2009-06-19 03:48:58 +04:00
}
desc - > chip - > unmask ( irq ) ;
}
static int __init pl061_probe ( struct amba_device * dev , struct amba_id * id )
{
struct pl061_platform_data * pdata ;
struct pl061_gpio * chip ;
struct list_head * chip_list ;
int ret , irq , i ;
2009-06-30 22:41:38 +04:00
static DECLARE_BITMAP ( init_irq , NR_IRQS ) ;
2009-06-19 03:48:58 +04:00
pdata = dev - > dev . platform_data ;
if ( pdata = = NULL )
return - ENODEV ;
chip = kzalloc ( sizeof ( * chip ) , GFP_KERNEL ) ;
if ( chip = = NULL )
return - ENOMEM ;
if ( ! request_mem_region ( dev - > res . start ,
resource_size ( & dev - > res ) , " pl061 " ) ) {
ret = - EBUSY ;
goto free_mem ;
}
chip - > base = ioremap ( dev - > res . start , resource_size ( & dev - > res ) ) ;
if ( chip - > base = = NULL ) {
ret = - ENOMEM ;
goto release_region ;
}
spin_lock_init ( & chip - > lock ) ;
spin_lock_init ( & chip - > irq_lock ) ;
INIT_LIST_HEAD ( & chip - > list ) ;
chip - > gc . direction_input = pl061_direction_input ;
chip - > gc . direction_output = pl061_direction_output ;
chip - > gc . get = pl061_get_value ;
chip - > gc . set = pl061_set_value ;
2009-06-30 22:41:39 +04:00
chip - > gc . to_irq = pl061_to_irq ;
2009-06-19 03:48:58 +04:00
chip - > gc . base = pdata - > gpio_base ;
chip - > gc . ngpio = PL061_GPIO_NR ;
chip - > gc . label = dev_name ( & dev - > dev ) ;
chip - > gc . dev = & dev - > dev ;
chip - > gc . owner = THIS_MODULE ;
chip - > irq_base = pdata - > irq_base ;
ret = gpiochip_add ( & chip - > gc ) ;
if ( ret )
goto iounmap ;
/*
* irq_chip support
*/
if ( chip - > irq_base = = ( unsigned ) - 1 )
return 0 ;
writeb ( 0 , chip - > base + GPIOIE ) ; /* disable irqs */
irq = dev - > irq [ 0 ] ;
if ( irq < 0 ) {
ret = - ENODEV ;
goto iounmap ;
}
set_irq_chained_handler ( irq , pl061_irq_handler ) ;
if ( ! test_and_set_bit ( irq , init_irq ) ) { /* list initialized? */
chip_list = kmalloc ( sizeof ( * chip_list ) , GFP_KERNEL ) ;
if ( chip_list = = NULL ) {
2009-06-30 22:41:38 +04:00
clear_bit ( irq , init_irq ) ;
2009-06-19 03:48:58 +04:00
ret = - ENOMEM ;
goto iounmap ;
}
INIT_LIST_HEAD ( chip_list ) ;
set_irq_chip_data ( irq , chip_list ) ;
} else
chip_list = get_irq_chip_data ( irq ) ;
list_add ( & chip - > list , chip_list ) ;
for ( i = 0 ; i < PL061_GPIO_NR ; i + + ) {
if ( pdata - > directions & ( 1 < < i ) )
pl061_direction_output ( & chip - > gc , i ,
pdata - > values & ( 1 < < i ) ) ;
else
pl061_direction_input ( & chip - > gc , i ) ;
set_irq_chip ( i + chip - > irq_base , & pl061_irqchip ) ;
set_irq_handler ( i + chip - > irq_base , handle_simple_irq ) ;
set_irq_flags ( i + chip - > irq_base , IRQF_VALID ) ;
set_irq_chip_data ( i + chip - > irq_base , chip ) ;
}
return 0 ;
iounmap :
iounmap ( chip - > base ) ;
release_region :
release_mem_region ( dev - > res . start , resource_size ( & dev - > res ) ) ;
free_mem :
kfree ( chip ) ;
return ret ;
}
static struct amba_id pl061_ids [ ] __initdata = {
{
. id = 0x00041061 ,
. mask = 0x000fffff ,
} ,
{ 0 , 0 } ,
} ;
static struct amba_driver pl061_gpio_driver = {
. drv = {
. name = " pl061_gpio " ,
} ,
. id_table = pl061_ids ,
. probe = pl061_probe ,
} ;
static int __init pl061_gpio_init ( void )
{
return amba_driver_register ( & pl061_gpio_driver ) ;
}
subsys_initcall ( pl061_gpio_init ) ;
MODULE_AUTHOR ( " Baruch Siach <baruch@tkos.co.il> " ) ;
MODULE_DESCRIPTION ( " PL061 GPIO driver " ) ;
MODULE_LICENSE ( " GPL " ) ;