2013-12-05 03:56:43 +04:00
/*
* GPIO driver for the SMSC SCH311x Super - I / O chips
*
* Copyright ( C ) 2013 Bruno Randolf < br1 @ einfach . org >
*
* SuperIO functions and chip detection :
* ( c ) Copyright 2008 Wim Van Sebroeck < wim @ iguana . be > .
*
* 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 .
*/
2016-02-03 23:17:40 +03:00
# include <linux/ioport.h>
2013-12-05 03:56:43 +04:00
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/platform_device.h>
2018-06-27 11:42:01 +03:00
# include <linux/gpio/driver.h>
2013-12-05 03:56:43 +04:00
# include <linux/bitops.h>
2013-12-23 21:12:48 +04:00
# include <linux/io.h>
2013-12-05 03:56:43 +04:00
# define DRV_NAME "gpio-sch311x"
2018-06-27 11:52:07 +03:00
# define SCH311X_GPIO_CONF_DIR BIT(0)
# define SCH311X_GPIO_CONF_INVERT BIT(1)
# define SCH311X_GPIO_CONF_OPEN_DRAIN BIT(7)
2013-12-05 03:56:43 +04:00
# define SIO_CONFIG_KEY_ENTER 0x55
# define SIO_CONFIG_KEY_EXIT 0xaa
# define GP1 0x4b
static int sch311x_ioports [ ] = { 0x2e , 0x4e , 0x162e , 0x164e } ;
static struct platform_device * sch311x_gpio_pdev ;
struct sch311x_pdev_data { /* platform device data */
unsigned short runtime_reg ; /* runtime register base address */
} ;
struct sch311x_gpio_block { /* one GPIO block runtime data */
struct gpio_chip chip ;
unsigned short data_reg ; /* from definition below */
unsigned short * config_regs ; /* pointer to definition below */
unsigned short runtime_reg ; /* runtime register */
spinlock_t lock ; /* lock for this GPIO block */
} ;
struct sch311x_gpio_priv { /* driver private data */
struct sch311x_gpio_block blocks [ 6 ] ;
} ;
struct sch311x_gpio_block_def { /* register address definitions */
unsigned short data_reg ;
unsigned short config_regs [ 8 ] ;
unsigned short base ;
} ;
/* Note: some GPIOs are not available, these are marked with 0x00 */
static struct sch311x_gpio_block_def sch311x_gpio_blocks [ ] = {
{
. data_reg = 0x4b , /* GP1 */
. config_regs = { 0x23 , 0x24 , 0x25 , 0x26 , 0x27 , 0x29 , 0x2a , 0x2b } ,
. base = 10 ,
} ,
{
. data_reg = 0x4c , /* GP2 */
. config_regs = { 0x00 , 0x2c , 0x2d , 0x00 , 0x00 , 0x00 , 0x00 , 0x32 } ,
. base = 20 ,
} ,
{
. data_reg = 0x4d , /* GP3 */
. config_regs = { 0x33 , 0x34 , 0x35 , 0x36 , 0x37 , 0x00 , 0x39 , 0x3a } ,
. base = 30 ,
} ,
{
. data_reg = 0x4e , /* GP4 */
. config_regs = { 0x3b , 0x00 , 0x3d , 0x00 , 0x6e , 0x6f , 0x72 , 0x73 } ,
. base = 40 ,
} ,
{
. data_reg = 0x4f , /* GP5 */
. config_regs = { 0x3f , 0x40 , 0x41 , 0x42 , 0x43 , 0x44 , 0x45 , 0x46 } ,
. base = 50 ,
} ,
{
. data_reg = 0x50 , /* GP6 */
. config_regs = { 0x47 , 0x48 , 0x54 , 0x55 , 0x56 , 0x57 , 0x58 , 0x59 } ,
. base = 60 ,
} ,
} ;
/*
* Super - IO functions
*/
static inline int sch311x_sio_enter ( int sio_config_port )
{
/* Don't step on other drivers' I/O space by accident. */
if ( ! request_muxed_region ( sio_config_port , 2 , DRV_NAME ) ) {
pr_err ( DRV_NAME " I/O address 0x%04x already in use \n " ,
sio_config_port ) ;
return - EBUSY ;
}
outb ( SIO_CONFIG_KEY_ENTER , sio_config_port ) ;
return 0 ;
}
static inline void sch311x_sio_exit ( int sio_config_port )
{
outb ( SIO_CONFIG_KEY_EXIT , sio_config_port ) ;
release_region ( sio_config_port , 2 ) ;
}
static inline int sch311x_sio_inb ( int sio_config_port , int reg )
{
outb ( reg , sio_config_port ) ;
return inb ( sio_config_port + 1 ) ;
}
static inline void sch311x_sio_outb ( int sio_config_port , int reg , int val )
{
outb ( reg , sio_config_port ) ;
outb ( val , sio_config_port + 1 ) ;
}
/*
* GPIO functions
*/
static int sch311x_gpio_request ( struct gpio_chip * chip , unsigned offset )
{
2015-12-07 16:24:24 +03:00
struct sch311x_gpio_block * block = gpiochip_get_data ( chip ) ;
2013-12-05 03:56:43 +04:00
if ( block - > config_regs [ offset ] = = 0 ) /* GPIO is not available */
return - ENODEV ;
if ( ! request_region ( block - > runtime_reg + block - > config_regs [ offset ] ,
1 , DRV_NAME ) ) {
2015-11-04 11:56:26 +03:00
dev_err ( chip - > parent , " Failed to request region 0x%04x. \n " ,
2013-12-05 03:56:43 +04:00
block - > runtime_reg + block - > config_regs [ offset ] ) ;
return - EBUSY ;
}
return 0 ;
}
static void sch311x_gpio_free ( struct gpio_chip * chip , unsigned offset )
{
2015-12-07 16:24:24 +03:00
struct sch311x_gpio_block * block = gpiochip_get_data ( chip ) ;
2013-12-05 03:56:43 +04:00
if ( block - > config_regs [ offset ] = = 0 ) /* GPIO is not available */
return ;
release_region ( block - > runtime_reg + block - > config_regs [ offset ] , 1 ) ;
}
static int sch311x_gpio_get ( struct gpio_chip * chip , unsigned offset )
{
2015-12-07 16:24:24 +03:00
struct sch311x_gpio_block * block = gpiochip_get_data ( chip ) ;
2018-06-27 12:03:22 +03:00
u8 data ;
2013-12-05 03:56:43 +04:00
spin_lock ( & block - > lock ) ;
data = inb ( block - > runtime_reg + block - > data_reg ) ;
spin_unlock ( & block - > lock ) ;
return ! ! ( data & BIT ( offset ) ) ;
}
static void __sch311x_gpio_set ( struct sch311x_gpio_block * block ,
unsigned offset , int value )
{
2018-06-27 12:03:22 +03:00
u8 data = inb ( block - > runtime_reg + block - > data_reg ) ;
2013-12-05 03:56:43 +04:00
if ( value )
data | = BIT ( offset ) ;
else
data & = ~ BIT ( offset ) ;
outb ( data , block - > runtime_reg + block - > data_reg ) ;
}
static void sch311x_gpio_set ( struct gpio_chip * chip , unsigned offset ,
int value )
{
2015-12-07 16:24:24 +03:00
struct sch311x_gpio_block * block = gpiochip_get_data ( chip ) ;
2013-12-05 03:56:43 +04:00
spin_lock ( & block - > lock ) ;
2018-10-30 17:07:22 +03:00
__sch311x_gpio_set ( block , offset , value ) ;
2013-12-05 03:56:43 +04:00
spin_unlock ( & block - > lock ) ;
}
static int sch311x_gpio_direction_in ( struct gpio_chip * chip , unsigned offset )
{
2015-12-07 16:24:24 +03:00
struct sch311x_gpio_block * block = gpiochip_get_data ( chip ) ;
2018-06-27 12:03:22 +03:00
u8 data ;
2013-12-05 03:56:43 +04:00
spin_lock ( & block - > lock ) ;
2018-06-27 11:52:07 +03:00
data = inb ( block - > runtime_reg + block - > config_regs [ offset ] ) ;
data | = SCH311X_GPIO_CONF_DIR ;
outb ( data , block - > runtime_reg + block - > config_regs [ offset ] ) ;
2013-12-05 03:56:43 +04:00
spin_unlock ( & block - > lock ) ;
return 0 ;
}
static int sch311x_gpio_direction_out ( struct gpio_chip * chip , unsigned offset ,
int value )
{
2015-12-07 16:24:24 +03:00
struct sch311x_gpio_block * block = gpiochip_get_data ( chip ) ;
2018-06-27 12:03:22 +03:00
u8 data ;
2013-12-05 03:56:43 +04:00
spin_lock ( & block - > lock ) ;
2018-06-27 11:52:07 +03:00
data = inb ( block - > runtime_reg + block - > config_regs [ offset ] ) ;
data & = ~ SCH311X_GPIO_CONF_DIR ;
outb ( data , block - > runtime_reg + block - > config_regs [ offset ] ) ;
2013-12-05 03:56:43 +04:00
__sch311x_gpio_set ( block , offset , value ) ;
spin_unlock ( & block - > lock ) ;
return 0 ;
}
2018-06-27 11:47:28 +03:00
static int sch311x_gpio_get_direction ( struct gpio_chip * chip , unsigned offset )
{
struct sch311x_gpio_block * block = gpiochip_get_data ( chip ) ;
2018-06-27 12:03:22 +03:00
u8 data ;
2018-06-27 11:47:28 +03:00
spin_lock ( & block - > lock ) ;
data = inb ( block - > runtime_reg + block - > config_regs [ offset ] ) ;
spin_unlock ( & block - > lock ) ;
2018-06-27 11:52:07 +03:00
return ! ! ( data & SCH311X_GPIO_CONF_DIR ) ;
2018-06-27 11:47:28 +03:00
}
2018-06-27 12:00:44 +03:00
static int sch311x_gpio_set_config ( struct gpio_chip * chip , unsigned offset ,
unsigned long config )
{
struct sch311x_gpio_block * block = gpiochip_get_data ( chip ) ;
enum pin_config_param param = pinconf_to_config_param ( config ) ;
2018-06-27 12:03:22 +03:00
u8 data ;
2018-06-27 12:00:44 +03:00
switch ( param ) {
case PIN_CONFIG_DRIVE_OPEN_DRAIN :
spin_lock ( & block - > lock ) ;
data = inb ( block - > runtime_reg + block - > config_regs [ offset ] ) ;
data | = SCH311X_GPIO_CONF_OPEN_DRAIN ;
outb ( data , block - > runtime_reg + block - > config_regs [ offset ] ) ;
spin_unlock ( & block - > lock ) ;
return 0 ;
case PIN_CONFIG_DRIVE_PUSH_PULL :
spin_lock ( & block - > lock ) ;
data = inb ( block - > runtime_reg + block - > config_regs [ offset ] ) ;
data & = ~ SCH311X_GPIO_CONF_OPEN_DRAIN ;
outb ( data , block - > runtime_reg + block - > config_regs [ offset ] ) ;
spin_unlock ( & block - > lock ) ;
return 0 ;
default :
break ;
}
return - ENOTSUPP ;
}
2013-12-05 03:56:43 +04:00
static int sch311x_gpio_probe ( struct platform_device * pdev )
{
2015-11-23 18:23:18 +03:00
struct sch311x_pdev_data * pdata = dev_get_platdata ( & pdev - > dev ) ;
2013-12-05 03:56:43 +04:00
struct sch311x_gpio_priv * priv ;
struct sch311x_gpio_block * block ;
int err , i ;
/* we can register all GPIO data registers at once */
2016-02-03 23:17:40 +03:00
if ( ! devm_request_region ( & pdev - > dev , pdata - > runtime_reg + GP1 , 6 ,
DRV_NAME ) ) {
2013-12-05 03:56:43 +04:00
dev_err ( & pdev - > dev , " Failed to request region 0x%04x-0x%04x. \n " ,
pdata - > runtime_reg + GP1 , pdata - > runtime_reg + GP1 + 5 ) ;
return - EBUSY ;
}
priv = devm_kzalloc ( & pdev - > dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
platform_set_drvdata ( pdev , priv ) ;
for ( i = 0 ; i < ARRAY_SIZE ( priv - > blocks ) ; i + + ) {
block = & priv - > blocks [ i ] ;
spin_lock_init ( & block - > lock ) ;
block - > chip . label = DRV_NAME ;
block - > chip . owner = THIS_MODULE ;
block - > chip . request = sch311x_gpio_request ;
block - > chip . free = sch311x_gpio_free ;
block - > chip . direction_input = sch311x_gpio_direction_in ;
block - > chip . direction_output = sch311x_gpio_direction_out ;
2018-06-27 11:47:28 +03:00
block - > chip . get_direction = sch311x_gpio_get_direction ;
2018-06-27 12:00:44 +03:00
block - > chip . set_config = sch311x_gpio_set_config ;
2013-12-05 03:56:43 +04:00
block - > chip . get = sch311x_gpio_get ;
block - > chip . set = sch311x_gpio_set ;
block - > chip . ngpio = 8 ;
2015-11-04 11:56:26 +03:00
block - > chip . parent = & pdev - > dev ;
2013-12-05 03:56:43 +04:00
block - > chip . base = sch311x_gpio_blocks [ i ] . base ;
block - > config_regs = sch311x_gpio_blocks [ i ] . config_regs ;
block - > data_reg = sch311x_gpio_blocks [ i ] . data_reg ;
block - > runtime_reg = pdata - > runtime_reg ;
2015-12-07 16:24:24 +03:00
err = gpiochip_add_data ( & block - > chip , block ) ;
2013-12-05 03:56:43 +04:00
if ( err < 0 ) {
dev_err ( & pdev - > dev ,
" Could not register gpiochip, %d \n " , err ) ;
goto exit_err ;
}
dev_info ( & pdev - > dev ,
" SMSC SCH311x GPIO block %d registered. \n " , i ) ;
}
return 0 ;
exit_err :
/* release already registered chips */
for ( - - i ; i > = 0 ; i - - )
gpiochip_remove ( & priv - > blocks [ i ] . chip ) ;
return err ;
}
static int sch311x_gpio_remove ( struct platform_device * pdev )
{
struct sch311x_gpio_priv * priv = platform_get_drvdata ( pdev ) ;
2014-07-13 00:30:12 +04:00
int i ;
2013-12-05 03:56:43 +04:00
for ( i = 0 ; i < ARRAY_SIZE ( priv - > blocks ) ; i + + ) {
2014-07-13 00:30:12 +04:00
gpiochip_remove ( & priv - > blocks [ i ] . chip ) ;
2013-12-05 03:56:43 +04:00
dev_info ( & pdev - > dev ,
" SMSC SCH311x GPIO block %d unregistered. \n " , i ) ;
}
return 0 ;
}
static struct platform_driver sch311x_gpio_driver = {
. driver . name = DRV_NAME ,
. probe = sch311x_gpio_probe ,
. remove = sch311x_gpio_remove ,
} ;
/*
* Init & exit routines
*/
static int __init sch311x_detect ( int sio_config_port , unsigned short * addr )
{
int err = 0 , reg ;
unsigned short base_addr ;
2018-06-27 12:03:22 +03:00
u8 dev_id ;
2013-12-05 03:56:43 +04:00
err = sch311x_sio_enter ( sio_config_port ) ;
if ( err )
return err ;
2014-04-15 06:15:27 +04:00
/* Check device ID. */
2013-12-05 03:56:43 +04:00
reg = sch311x_sio_inb ( sio_config_port , 0x20 ) ;
2014-04-15 06:15:27 +04:00
switch ( reg ) {
case 0x7c : /* SCH3112 */
dev_id = 2 ;
break ;
case 0x7d : /* SCH3114 */
dev_id = 4 ;
break ;
case 0x7f : /* SCH3116 */
dev_id = 6 ;
break ;
default :
2013-12-05 03:56:43 +04:00
err = - ENODEV ;
goto exit ;
}
/* Select logical device A (runtime registers) */
sch311x_sio_outb ( sio_config_port , 0x07 , 0x0a ) ;
/* Check if Logical Device Register is currently active */
if ( ( sch311x_sio_inb ( sio_config_port , 0x30 ) & 0x01 ) = = 0 )
pr_info ( " Seems that LDN 0x0a is not active... \n " ) ;
/* Get the base address of the runtime registers */
base_addr = ( sch311x_sio_inb ( sio_config_port , 0x60 ) < < 8 ) |
sch311x_sio_inb ( sio_config_port , 0x61 ) ;
if ( ! base_addr ) {
pr_err ( " Base address not set \n " ) ;
err = - ENODEV ;
goto exit ;
}
* addr = base_addr ;
pr_info ( " Found an SMSC SCH311%d chip at 0x%04x \n " , dev_id , base_addr ) ;
exit :
sch311x_sio_exit ( sio_config_port ) ;
return err ;
}
static int __init sch311x_gpio_pdev_add ( const unsigned short addr )
{
struct sch311x_pdev_data pdata ;
int err ;
pdata . runtime_reg = addr ;
sch311x_gpio_pdev = platform_device_alloc ( DRV_NAME , - 1 ) ;
if ( ! sch311x_gpio_pdev )
return - ENOMEM ;
err = platform_device_add_data ( sch311x_gpio_pdev ,
& pdata , sizeof ( pdata ) ) ;
if ( err ) {
pr_err ( DRV_NAME " Platform data allocation failed \n " ) ;
goto err ;
}
err = platform_device_add ( sch311x_gpio_pdev ) ;
if ( err ) {
pr_err ( DRV_NAME " Device addition failed \n " ) ;
goto err ;
}
return 0 ;
err :
platform_device_put ( sch311x_gpio_pdev ) ;
return err ;
}
static int __init sch311x_gpio_init ( void )
{
int err , i ;
unsigned short addr = 0 ;
for ( i = 0 ; i < ARRAY_SIZE ( sch311x_ioports ) ; i + + )
if ( sch311x_detect ( sch311x_ioports [ i ] , & addr ) = = 0 )
break ;
if ( ! addr )
return - ENODEV ;
err = platform_driver_register ( & sch311x_gpio_driver ) ;
if ( err )
return err ;
err = sch311x_gpio_pdev_add ( addr ) ;
if ( err )
goto unreg_platform_driver ;
return 0 ;
unreg_platform_driver :
platform_driver_unregister ( & sch311x_gpio_driver ) ;
return err ;
}
static void __exit sch311x_gpio_exit ( void )
{
platform_device_unregister ( sch311x_gpio_pdev ) ;
platform_driver_unregister ( & sch311x_gpio_driver ) ;
}
module_init ( sch311x_gpio_init ) ;
module_exit ( sch311x_gpio_exit ) ;
MODULE_AUTHOR ( " Bruno Randolf <br1@einfach.org> " ) ;
MODULE_DESCRIPTION ( " SMSC SCH311x GPIO Driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS ( " platform:gpio-sch311x " ) ;