2006-01-10 07:51:29 +03:00
/*
* AMD CS5535 / CS5536 GPIO driver .
* Allows a user space process to play with the GPIO pins .
*
* Copyright ( c ) 2005 Ben Gardner < bgardner @ wabtec . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the smems of the GNU General Public License as published by
* the Free Software Foundation ; version 2 of the License .
*/
# include <linux/fs.h>
# include <linux/module.h>
# include <linux/errno.h>
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/cdev.h>
# include <linux/ioport.h>
# include <linux/pci.h>
# include <asm/uaccess.h>
# include <asm/io.h>
# define NAME "cs5535_gpio"
MODULE_AUTHOR ( " Ben Gardner <bgardner@wabtec.com> " ) ;
MODULE_DESCRIPTION ( " AMD CS5535/CS5536 GPIO Pin Driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
static int major ;
module_param ( major , int , 0 ) ;
MODULE_PARM_DESC ( major , " Major device number " ) ;
static ulong mask ;
module_param ( mask , ulong , 0 ) ;
MODULE_PARM_DESC ( mask , " GPIO channel mask " ) ;
# define MSR_LBAR_GPIO 0x5140000C
static u32 gpio_base ;
static struct pci_device_id divil_pci [ ] = {
{ PCI_DEVICE ( PCI_VENDOR_ID_NS , PCI_DEVICE_ID_NS_CS5535_ISA ) } ,
{ PCI_DEVICE ( PCI_VENDOR_ID_AMD , PCI_DEVICE_ID_AMD_CS5536_ISA ) } ,
{ } /* NULL entry */
} ;
static struct cdev cs5535_gpio_cdev ;
/* reserve 32 entries even though some aren't usable */
# define CS5535_GPIO_COUNT 32
/* IO block size */
# define CS5535_GPIO_SIZE 256
struct gpio_regmap {
u32 rd_offset ;
u32 wr_offset ;
char on ;
char off ;
} ;
static struct gpio_regmap rm [ ] =
{
{ 0x30 , 0x00 , ' 1 ' , ' 0 ' } , /* GPIOx_READ_BACK / GPIOx_OUT_VAL */
{ 0x20 , 0x20 , ' I ' , ' i ' } , /* GPIOx_IN_EN */
{ 0x04 , 0x04 , ' O ' , ' o ' } , /* GPIOx_OUT_EN */
{ 0x08 , 0x08 , ' t ' , ' T ' } , /* GPIOx_OUT_OD_EN */
{ 0x18 , 0x18 , ' P ' , ' p ' } , /* GPIOx_OUT_PU_EN */
{ 0x1c , 0x1c , ' D ' , ' d ' } , /* GPIOx_OUT_PD_EN */
} ;
/**
* Gets the register offset for the GPIO bank .
* Low ( 0 - 15 ) starts at 0x00 , high ( 16 - 31 ) starts at 0x80
*/
static inline u32 cs5535_lowhigh_base ( int reg )
{
return ( reg & 0x10 ) < < 3 ;
}
static ssize_t cs5535_gpio_write ( struct file * file , const char __user * data ,
size_t len , loff_t * ppos )
{
u32 m = iminor ( file - > f_dentry - > d_inode ) ;
int i , j ;
u32 base = gpio_base + cs5535_lowhigh_base ( m ) ;
u32 m0 , m1 ;
char c ;
/**
* Creates the mask for atomic bit programming .
* The high 16 bits and the low 16 bits are used to set the mask .
* For example , GPIO 15 maps to 31 , 15 : 0 , 1 = > On ; 1 , 0 = > Off
*/
m1 = 1 < < ( m & 0x0F ) ;
m0 = m1 < < 16 ;
for ( i = 0 ; i < len ; + + i ) {
if ( get_user ( c , data + i ) )
return - EFAULT ;
for ( j = 0 ; j < ARRAY_SIZE ( rm ) ; j + + ) {
if ( c = = rm [ j ] . on ) {
outl ( m1 , base + rm [ j ] . wr_offset ) ;
break ;
} else if ( c = = rm [ j ] . off ) {
outl ( m0 , base + rm [ j ] . wr_offset ) ;
break ;
}
}
}
* ppos = 0 ;
return len ;
}
static ssize_t cs5535_gpio_read ( struct file * file , char __user * buf ,
size_t len , loff_t * ppos )
{
u32 m = iminor ( file - > f_dentry - > d_inode ) ;
u32 base = gpio_base + cs5535_lowhigh_base ( m ) ;
int rd_bit = 1 < < ( m & 0x0f ) ;
int i ;
char ch ;
ssize_t count = 0 ;
if ( * ppos > = ARRAY_SIZE ( rm ) )
return 0 ;
for ( i = * ppos ; ( i < ( * ppos + len ) ) & & ( i < ARRAY_SIZE ( rm ) ) ; i + + ) {
ch = ( inl ( base + rm [ i ] . rd_offset ) & rd_bit ) ?
rm [ i ] . on : rm [ i ] . off ;
if ( put_user ( ch , buf + count ) )
return - EFAULT ;
count + + ;
}
/* add a line-feed if there is room */
if ( ( i = = ARRAY_SIZE ( rm ) ) & & ( count < len ) ) {
put_user ( ' \n ' , buf + count ) ;
count + + ;
}
* ppos + = count ;
return count ;
}
static int cs5535_gpio_open ( struct inode * inode , struct file * file )
{
u32 m = iminor ( inode ) ;
/* the mask says which pins are usable by this driver */
if ( ( mask & ( 1 < < m ) ) = = 0 )
return - EINVAL ;
return nonseekable_open ( inode , file ) ;
}
static struct file_operations cs5535_gpio_fops = {
. owner = THIS_MODULE ,
. write = cs5535_gpio_write ,
. read = cs5535_gpio_read ,
. open = cs5535_gpio_open
} ;
static int __init cs5535_gpio_init ( void )
{
dev_t dev_id ;
u32 low , hi ;
int retval ;
if ( pci_dev_present ( divil_pci ) = = 0 ) {
printk ( KERN_WARNING NAME " : DIVIL not found \n " ) ;
return - ENODEV ;
}
/* Grab the GPIO I/O range */
rdmsr ( MSR_LBAR_GPIO , low , hi ) ;
/* Check the mask and whether GPIO is enabled (sanity check) */
if ( hi ! = 0x0000f001 ) {
printk ( KERN_WARNING NAME " : GPIO not enabled \n " ) ;
return - ENODEV ;
}
/* Mask off the IO base address */
gpio_base = low & 0x0000ff00 ;
/**
* Some GPIO pins
* 31 - 29 , 23 : reserved ( always mask out )
* 28 : Power Button
* 26 : PME #
* 22 - 16 : LPC
* 14 , 15 : SMBus
* 9 , 8 : UART1
* 7 : PCI INTB
* 3 , 4 : UART2 / DDC
* 2 : IDE_IRQ0
* 0 : PCI INTA
*
* If a mask was not specified , be conservative and only allow :
* 1 , 2 , 5 , 6 , 10 - 13 , 24 , 25 , 27
*/
if ( mask ! = 0 )
mask & = 0x1f7fffff ;
else
mask = 0x0b003c66 ;
if ( request_region ( gpio_base , CS5535_GPIO_SIZE , NAME ) = = 0 ) {
printk ( KERN_ERR NAME " : can't allocate I/O for GPIO \n " ) ;
return - ENODEV ;
}
if ( major ) {
dev_id = MKDEV ( major , 0 ) ;
retval = register_chrdev_region ( dev_id , CS5535_GPIO_COUNT ,
NAME ) ;
} else {
retval = alloc_chrdev_region ( & dev_id , 0 , CS5535_GPIO_COUNT ,
NAME ) ;
major = MAJOR ( dev_id ) ;
}
if ( retval ) {
release_region ( gpio_base , CS5535_GPIO_SIZE ) ;
return - 1 ;
}
printk ( KERN_DEBUG NAME " : base=%#x mask=%#lx major=%d \n " ,
gpio_base , mask , major ) ;
cdev_init ( & cs5535_gpio_cdev , & cs5535_gpio_fops ) ;
cdev_add ( & cs5535_gpio_cdev , dev_id , CS5535_GPIO_COUNT ) ;
return 0 ;
}
static void __exit cs5535_gpio_cleanup ( void )
{
dev_t dev_id = MKDEV ( major , 0 ) ;
2006-04-20 13:43:15 +04:00
cdev_del ( & cs5535_gpio_cdev ) ;
2006-01-10 07:51:29 +03:00
unregister_chrdev_region ( dev_id , CS5535_GPIO_COUNT ) ;
2006-04-20 13:43:15 +04:00
release_region ( gpio_base , CS5535_GPIO_SIZE ) ;
2006-01-10 07:51:29 +03:00
}
module_init ( cs5535_gpio_init ) ;
module_exit ( cs5535_gpio_cleanup ) ;