2016-02-29 21:44:43 +03:00
/*
* MEN 16 Z127 GPIO driver
*
* Copyright ( C ) 2016 MEN Mikroelektronik GmbH ( www . men . de )
*
* 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 ; version 2 of the License .
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/io.h>
# include <linux/err.h>
# include <linux/mcb.h>
# include <linux/bitops.h>
# include <linux/gpio/driver.h>
# define MEN_Z127_CTRL 0x00
# define MEN_Z127_PSR 0x04
# define MEN_Z127_IRQR 0x08
# define MEN_Z127_GPIODR 0x0c
# define MEN_Z127_IER1 0x10
# define MEN_Z127_IER2 0x14
# define MEN_Z127_DBER 0x18
# define MEN_Z127_ODER 0x1C
# define GPIO_TO_DBCNT_REG(gpio) ((gpio * 4) + 0x80)
# define MEN_Z127_DB_MIN_US 50
/* 16 bit compare register. Each bit represents 50us */
# define MEN_Z127_DB_MAX_US (0xffff * MEN_Z127_DB_MIN_US)
# define MEN_Z127_DB_IN_RANGE(db) ((db >= MEN_Z127_DB_MIN_US) && \
( db < = MEN_Z127_DB_MAX_US ) )
struct men_z127_gpio {
struct gpio_chip gc ;
void __iomem * reg_base ;
struct resource * mem ;
} ;
static int men_z127_debounce ( struct gpio_chip * gc , unsigned gpio ,
unsigned debounce )
{
struct men_z127_gpio * priv = gpiochip_get_data ( gc ) ;
2016-03-09 15:39:57 +03:00
struct device * dev = gc - > parent ;
2016-02-29 21:44:43 +03:00
unsigned int rnd ;
u32 db_en , db_cnt ;
if ( ! MEN_Z127_DB_IN_RANGE ( debounce ) ) {
dev_err ( dev , " debounce value %u out of range " , debounce ) ;
return - EINVAL ;
}
if ( debounce > 0 ) {
/* round up or down depending on MSB-1 */
rnd = fls ( debounce ) - 1 ;
if ( rnd & & ( debounce & BIT ( rnd - 1 ) ) )
2018-06-04 16:58:14 +03:00
debounce = roundup ( debounce , MEN_Z127_DB_MIN_US ) ;
2016-02-29 21:44:43 +03:00
else
2018-06-04 16:58:14 +03:00
debounce = rounddown ( debounce , MEN_Z127_DB_MIN_US ) ;
2016-02-29 21:44:43 +03:00
if ( debounce > MEN_Z127_DB_MAX_US )
debounce = MEN_Z127_DB_MAX_US ;
/* 50us per register unit */
debounce / = 50 ;
}
2016-03-09 15:38:55 +03:00
spin_lock ( & gc - > bgpio_lock ) ;
2016-02-29 21:44:43 +03:00
db_en = readl ( priv - > reg_base + MEN_Z127_DBER ) ;
if ( debounce = = 0 ) {
db_en & = ~ BIT ( gpio ) ;
db_cnt = 0 ;
} else {
db_en | = BIT ( gpio ) ;
db_cnt = debounce ;
}
writel ( db_en , priv - > reg_base + MEN_Z127_DBER ) ;
writel ( db_cnt , priv - > reg_base + GPIO_TO_DBCNT_REG ( gpio ) ) ;
2016-03-09 15:38:55 +03:00
spin_unlock ( & gc - > bgpio_lock ) ;
2016-02-29 21:44:43 +03:00
return 0 ;
}
2016-04-09 22:53:39 +03:00
static int men_z127_set_single_ended ( struct gpio_chip * gc ,
unsigned offset ,
2017-01-23 15:34:34 +03:00
enum pin_config_param param )
2016-02-29 21:44:43 +03:00
{
struct men_z127_gpio * priv = gpiochip_get_data ( gc ) ;
u32 od_en ;
2016-03-09 15:38:55 +03:00
spin_lock ( & gc - > bgpio_lock ) ;
2016-02-29 21:44:43 +03:00
od_en = readl ( priv - > reg_base + MEN_Z127_ODER ) ;
2017-01-23 15:34:34 +03:00
if ( param = = PIN_CONFIG_DRIVE_OPEN_DRAIN )
2016-04-09 22:53:39 +03:00
od_en | = BIT ( offset ) ;
2016-02-29 21:44:43 +03:00
else
2017-01-23 15:34:34 +03:00
/* Implicitly PIN_CONFIG_DRIVE_PUSH_PULL */
2016-04-09 22:53:39 +03:00
od_en & = ~ BIT ( offset ) ;
2016-02-29 21:44:43 +03:00
writel ( od_en , priv - > reg_base + MEN_Z127_ODER ) ;
2016-03-09 15:38:55 +03:00
spin_unlock ( & gc - > bgpio_lock ) ;
2016-02-29 21:44:43 +03:00
return 0 ;
}
2017-01-23 15:34:34 +03:00
static int men_z127_set_config ( struct gpio_chip * gc , unsigned offset ,
unsigned long config )
{
enum pin_config_param param = pinconf_to_config_param ( config ) ;
switch ( param ) {
case PIN_CONFIG_DRIVE_OPEN_DRAIN :
case PIN_CONFIG_DRIVE_PUSH_PULL :
return men_z127_set_single_ended ( gc , offset , param ) ;
case PIN_CONFIG_INPUT_DEBOUNCE :
return men_z127_debounce ( gc , offset ,
pinconf_to_config_argument ( config ) ) ;
default :
break ;
}
return - ENOTSUPP ;
}
2016-02-29 21:44:43 +03:00
static int men_z127_probe ( struct mcb_device * mdev ,
const struct mcb_device_id * id )
{
struct men_z127_gpio * men_z127_gpio ;
struct device * dev = & mdev - > dev ;
int ret ;
men_z127_gpio = devm_kzalloc ( dev , sizeof ( struct men_z127_gpio ) ,
GFP_KERNEL ) ;
if ( ! men_z127_gpio )
return - ENOMEM ;
men_z127_gpio - > mem = mcb_request_mem ( mdev , dev_name ( dev ) ) ;
if ( IS_ERR ( men_z127_gpio - > mem ) ) {
dev_err ( dev , " failed to request device memory " ) ;
return PTR_ERR ( men_z127_gpio - > mem ) ;
}
men_z127_gpio - > reg_base = ioremap ( men_z127_gpio - > mem - > start ,
resource_size ( men_z127_gpio - > mem ) ) ;
if ( men_z127_gpio - > reg_base = = NULL ) {
ret = - ENXIO ;
goto err_release ;
}
mcb_set_drvdata ( mdev , men_z127_gpio ) ;
ret = bgpio_init ( & men_z127_gpio - > gc , & mdev - > dev , 4 ,
men_z127_gpio - > reg_base + MEN_Z127_PSR ,
men_z127_gpio - > reg_base + MEN_Z127_CTRL ,
NULL ,
men_z127_gpio - > reg_base + MEN_Z127_GPIODR ,
NULL , 0 ) ;
if ( ret )
goto err_unmap ;
2017-01-23 15:34:34 +03:00
men_z127_gpio - > gc . set_config = men_z127_set_config ;
2016-02-29 21:44:43 +03:00
ret = gpiochip_add_data ( & men_z127_gpio - > gc , men_z127_gpio ) ;
if ( ret ) {
dev_err ( dev , " failed to register MEN 16Z127 GPIO controller " ) ;
goto err_unmap ;
}
dev_info ( dev , " MEN 16Z127 GPIO driver registered " ) ;
return 0 ;
err_unmap :
iounmap ( men_z127_gpio - > reg_base ) ;
err_release :
mcb_release_mem ( men_z127_gpio - > mem ) ;
return ret ;
}
static void men_z127_remove ( struct mcb_device * mdev )
{
struct men_z127_gpio * men_z127_gpio = mcb_get_drvdata ( mdev ) ;
gpiochip_remove ( & men_z127_gpio - > gc ) ;
iounmap ( men_z127_gpio - > reg_base ) ;
mcb_release_mem ( men_z127_gpio - > mem ) ;
}
static const struct mcb_device_id men_z127_ids [ ] = {
{ . device = 0x7f } ,
{ }
} ;
MODULE_DEVICE_TABLE ( mcb , men_z127_ids ) ;
static struct mcb_driver men_z127_driver = {
. driver = {
. name = " z127-gpio " ,
} ,
. probe = men_z127_probe ,
. remove = men_z127_remove ,
. id_table = men_z127_ids ,
} ;
module_mcb_driver ( men_z127_driver ) ;
MODULE_AUTHOR ( " Andreas Werner <andreas.werner@men.de> " ) ;
MODULE_DESCRIPTION ( " MEN 16z127 GPIO Controller " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_ALIAS ( " mcb:16z127 " ) ;