2010-03-06 00:44:39 +03:00
/*
2011-06-05 04:38:28 +04:00
* GPIO interface for IT8761E Super I / O chip
2010-03-06 00:44:39 +03:00
*
* Author : Denis Turischev < denis @ compulab . co . il >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License 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 .
*
* You should have received a copy of the GNU General Public License
* along with this program ; see the file COPYING . If not , write to
* the Free Software Foundation , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*/
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/io.h>
# include <linux/errno.h>
# include <linux/ioport.h>
# include <linux/gpio.h>
# define SIO_CHIP_ID 0x8761
# define CHIP_ID_HIGH_BYTE 0x20
# define CHIP_ID_LOW_BYTE 0x21
static u8 ports [ 2 ] = { 0x2e , 0x4e } ;
static u8 port ;
static DEFINE_SPINLOCK ( sio_lock ) ;
# define GPIO_NAME "it8761-gpio"
# define GPIO_BA_HIGH_BYTE 0x60
# define GPIO_BA_LOW_BYTE 0x61
# define GPIO_IOSIZE 4
# define GPIO1X_IO 0xf0
# define GPIO2X_IO 0xf1
static u16 gpio_ba ;
static u8 read_reg ( u8 addr , u8 port )
{
outb ( addr , port ) ;
return inb ( port + 1 ) ;
}
static void write_reg ( u8 data , u8 addr , u8 port )
{
outb ( addr , port ) ;
outb ( data , port + 1 ) ;
}
static void enter_conf_mode ( u8 port )
{
outb ( 0x87 , port ) ;
outb ( 0x61 , port ) ;
outb ( 0x55 , port ) ;
outb ( ( port = = 0x2e ) ? 0x55 : 0xaa , port ) ;
}
static void exit_conf_mode ( u8 port )
{
outb ( 0x2 , port ) ;
outb ( 0x2 , port + 1 ) ;
}
static void enter_gpio_mode ( u8 port )
{
write_reg ( 0x2 , 0x7 , port ) ;
}
static int it8761e_gpio_get ( struct gpio_chip * gc , unsigned gpio_num )
{
u16 reg ;
u8 bit ;
2010-05-12 01:06:44 +04:00
bit = gpio_num % 8 ;
reg = ( gpio_num > = 8 ) ? gpio_ba + 1 : gpio_ba ;
2010-03-06 00:44:39 +03:00
return ! ! ( inb ( reg ) & ( 1 < < bit ) ) ;
}
static int it8761e_gpio_direction_in ( struct gpio_chip * gc , unsigned gpio_num )
{
u8 curr_dirs ;
u8 io_reg , bit ;
2010-05-12 01:06:44 +04:00
bit = gpio_num % 8 ;
io_reg = ( gpio_num > = 8 ) ? GPIO2X_IO : GPIO1X_IO ;
2010-03-06 00:44:39 +03:00
spin_lock ( & sio_lock ) ;
enter_conf_mode ( port ) ;
enter_gpio_mode ( port ) ;
curr_dirs = read_reg ( io_reg , port ) ;
if ( curr_dirs & ( 1 < < bit ) )
write_reg ( curr_dirs & ~ ( 1 < < bit ) , io_reg , port ) ;
exit_conf_mode ( port ) ;
spin_unlock ( & sio_lock ) ;
return 0 ;
}
static void it8761e_gpio_set ( struct gpio_chip * gc ,
unsigned gpio_num , int val )
{
u8 curr_vals , bit ;
u16 reg ;
2010-05-12 01:06:44 +04:00
bit = gpio_num % 8 ;
reg = ( gpio_num > = 8 ) ? gpio_ba + 1 : gpio_ba ;
2010-03-06 00:44:39 +03:00
spin_lock ( & sio_lock ) ;
curr_vals = inb ( reg ) ;
if ( val )
outb ( curr_vals | ( 1 < < bit ) , reg ) ;
else
outb ( curr_vals & ~ ( 1 < < bit ) , reg ) ;
spin_unlock ( & sio_lock ) ;
}
static int it8761e_gpio_direction_out ( struct gpio_chip * gc ,
unsigned gpio_num , int val )
{
u8 curr_dirs , io_reg , bit ;
2010-05-12 01:06:44 +04:00
bit = gpio_num % 8 ;
io_reg = ( gpio_num > = 8 ) ? GPIO2X_IO : GPIO1X_IO ;
2010-03-06 00:44:39 +03:00
it8761e_gpio_set ( gc , gpio_num , val ) ;
spin_lock ( & sio_lock ) ;
enter_conf_mode ( port ) ;
enter_gpio_mode ( port ) ;
curr_dirs = read_reg ( io_reg , port ) ;
if ( ! ( curr_dirs & ( 1 < < bit ) ) )
write_reg ( curr_dirs | ( 1 < < bit ) , io_reg , port ) ;
exit_conf_mode ( port ) ;
spin_unlock ( & sio_lock ) ;
return 0 ;
}
static struct gpio_chip it8761e_gpio_chip = {
. label = GPIO_NAME ,
. owner = THIS_MODULE ,
. get = it8761e_gpio_get ,
. direction_input = it8761e_gpio_direction_in ,
. set = it8761e_gpio_set ,
. direction_output = it8761e_gpio_direction_out ,
} ;
static int __init it8761e_gpio_init ( void )
{
int i , id , err ;
/* chip and port detection */
for ( i = 0 ; i < ARRAY_SIZE ( ports ) ; i + + ) {
spin_lock ( & sio_lock ) ;
enter_conf_mode ( ports [ i ] ) ;
id = ( read_reg ( CHIP_ID_HIGH_BYTE , ports [ i ] ) < < 8 ) +
read_reg ( CHIP_ID_LOW_BYTE , ports [ i ] ) ;
exit_conf_mode ( ports [ i ] ) ;
spin_unlock ( & sio_lock ) ;
if ( id = = SIO_CHIP_ID ) {
port = ports [ i ] ;
break ;
}
}
if ( ! port )
return - ENODEV ;
/* fetch GPIO base address */
enter_conf_mode ( port ) ;
enter_gpio_mode ( port ) ;
gpio_ba = ( read_reg ( GPIO_BA_HIGH_BYTE , port ) < < 8 ) +
read_reg ( GPIO_BA_LOW_BYTE , port ) ;
exit_conf_mode ( port ) ;
if ( ! request_region ( gpio_ba , GPIO_IOSIZE , GPIO_NAME ) )
return - EBUSY ;
it8761e_gpio_chip . base = - 1 ;
2010-05-12 01:06:44 +04:00
it8761e_gpio_chip . ngpio = 16 ;
2010-03-06 00:44:39 +03:00
err = gpiochip_add ( & it8761e_gpio_chip ) ;
if ( err < 0 )
goto gpiochip_add_err ;
return 0 ;
gpiochip_add_err :
release_region ( gpio_ba , GPIO_IOSIZE ) ;
gpio_ba = 0 ;
return err ;
}
static void __exit it8761e_gpio_exit ( void )
{
if ( gpio_ba ) {
2010-05-27 01:42:26 +04:00
int ret = gpiochip_remove ( & it8761e_gpio_chip ) ;
WARN ( ret , " %s(): gpiochip_remove() failed, ret=%d \n " ,
__func__ , ret ) ;
2010-03-06 00:44:39 +03:00
release_region ( gpio_ba , GPIO_IOSIZE ) ;
gpio_ba = 0 ;
}
}
module_init ( it8761e_gpio_init ) ;
module_exit ( it8761e_gpio_exit ) ;
MODULE_AUTHOR ( " Denis Turischev <denis@compulab.co.il> " ) ;
MODULE_DESCRIPTION ( " GPIO interface for IT8761E Super I/O chip " ) ;
MODULE_LICENSE ( " GPL " ) ;