2016-07-26 00:59:38 +03:00
/*
* Intel Whiskey Cove PMIC GPIO Driver
*
* This driver is written based on gpio - crystalcove . c
*
* Copyright ( C ) 2016 Intel Corporation . All rights reserved .
*
* 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 .
*
* 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 .
*/
# include <linux/bitops.h>
2016-09-13 01:16:30 +03:00
# include <linux/module.h>
2016-07-26 00:59:38 +03:00
# include <linux/interrupt.h>
# include <linux/gpio/driver.h>
# include <linux/mfd/intel_soc_pmic.h>
# include <linux/platform_device.h>
# include <linux/regmap.h>
# include <linux/seq_file.h>
/*
* Whiskey Cove PMIC has 13 physical GPIO pins divided into 3 banks :
* Bank 0 : Pin 0 - 6
* Bank 1 : Pin 7 - 10
* Bank 2 : Pin 11 - 12
* Each pin has one output control register and one input control register .
*/
# define BANK0_NR_PINS 7
# define BANK1_NR_PINS 4
# define BANK2_NR_PINS 2
# define WCOVE_GPIO_NUM (BANK0_NR_PINS + BANK1_NR_PINS + BANK2_NR_PINS)
# define WCOVE_VGPIO_NUM 94
/* GPIO output control registers (one per pin): 0x4e44 - 0x4e50 */
# define GPIO_OUT_CTRL_BASE 0x4e44
/* GPIO input control registers (one per pin): 0x4e51 - 0x4e5d */
# define GPIO_IN_CTRL_BASE 0x4e51
/*
* GPIO interrupts are organized in two groups :
* Group 0 : Bank 0 pins ( Pin 0 - 6 )
* Group 1 : Bank 1 and Bank 2 pins ( Pin 7 - 12 )
* Each group has two registers ( one bit per pin ) : status and mask .
*/
# define GROUP0_NR_IRQS 7
# define GROUP1_NR_IRQS 6
# define IRQ_MASK_BASE 0x4e19
# define IRQ_STATUS_BASE 0x4e0b
# define UPDATE_IRQ_TYPE BIT(0)
# define UPDATE_IRQ_MASK BIT(1)
# define CTLI_INTCNT_DIS (0 << 1)
# define CTLI_INTCNT_NE (1 << 1)
# define CTLI_INTCNT_PE (2 << 1)
# define CTLI_INTCNT_BE (3 << 1)
# define CTLO_DIR_IN (0 << 5)
# define CTLO_DIR_OUT (1 << 5)
# define CTLO_DRV_MASK (1 << 4)
# define CTLO_DRV_OD (0 << 4)
# define CTLO_DRV_CMOS (1 << 4)
# define CTLO_DRV_REN (1 << 3)
# define CTLO_RVAL_2KDOWN (0 << 1)
# define CTLO_RVAL_2KUP (1 << 1)
# define CTLO_RVAL_50KDOWN (2 << 1)
# define CTLO_RVAL_50KUP (3 << 1)
# define CTLO_INPUT_SET (CTLO_DRV_CMOS | CTLO_DRV_REN | CTLO_RVAL_2KUP)
# define CTLO_OUTPUT_SET (CTLO_DIR_OUT | CTLO_INPUT_SET)
enum ctrl_register {
CTRL_IN ,
CTRL_OUT ,
} ;
/*
* struct wcove_gpio - Whiskey Cove GPIO controller
* @ buslock : for bus lock / sync and unlock .
* @ chip : the abstract gpio_chip structure .
* @ dev : the gpio device
* @ regmap : the regmap from the parent device .
* @ regmap_irq_chip : the regmap of the gpio irq chip .
* @ update : pending IRQ setting update , to be written to the chip upon unlock .
* @ intcnt : the Interrupt Detect value to be written .
* @ set_irq_mask : true if the IRQ mask needs to be set , false to clear .
*/
struct wcove_gpio {
struct mutex buslock ;
struct gpio_chip chip ;
struct device * dev ;
struct regmap * regmap ;
struct regmap_irq_chip_data * regmap_irq_chip ;
int update ;
int intcnt ;
bool set_irq_mask ;
} ;
static inline unsigned int to_reg ( int gpio , enum ctrl_register reg_type )
{
unsigned int reg ;
int bank ;
if ( gpio < BANK0_NR_PINS )
bank = 0 ;
else if ( gpio < BANK0_NR_PINS + BANK1_NR_PINS )
bank = 1 ;
else
bank = 2 ;
if ( reg_type = = CTRL_IN )
reg = GPIO_IN_CTRL_BASE + bank ;
else
reg = GPIO_OUT_CTRL_BASE + bank ;
return reg ;
}
static void wcove_update_irq_mask ( struct wcove_gpio * wg , int gpio )
{
unsigned int reg , mask ;
if ( gpio < GROUP0_NR_IRQS ) {
reg = IRQ_MASK_BASE ;
mask = BIT ( gpio % GROUP0_NR_IRQS ) ;
} else {
reg = IRQ_MASK_BASE + 1 ;
mask = BIT ( ( gpio - GROUP0_NR_IRQS ) % GROUP1_NR_IRQS ) ;
}
if ( wg - > set_irq_mask )
regmap_update_bits ( wg - > regmap , reg , mask , mask ) ;
else
regmap_update_bits ( wg - > regmap , reg , mask , 0 ) ;
}
static void wcove_update_irq_ctrl ( struct wcove_gpio * wg , int gpio )
{
unsigned int reg = to_reg ( gpio , CTRL_IN ) ;
regmap_update_bits ( wg - > regmap , reg , CTLI_INTCNT_BE , wg - > intcnt ) ;
}
static int wcove_gpio_dir_in ( struct gpio_chip * chip , unsigned int gpio )
{
struct wcove_gpio * wg = gpiochip_get_data ( chip ) ;
return regmap_write ( wg - > regmap , to_reg ( gpio , CTRL_OUT ) ,
CTLO_INPUT_SET ) ;
}
static int wcove_gpio_dir_out ( struct gpio_chip * chip , unsigned int gpio ,
int value )
{
struct wcove_gpio * wg = gpiochip_get_data ( chip ) ;
return regmap_write ( wg - > regmap , to_reg ( gpio , CTRL_OUT ) ,
CTLO_OUTPUT_SET | value ) ;
}
2016-08-15 21:03:23 +03:00
static int wcove_gpio_get_direction ( struct gpio_chip * chip , unsigned int gpio )
{
struct wcove_gpio * wg = gpiochip_get_data ( chip ) ;
unsigned int val ;
int ret ;
ret = regmap_read ( wg - > regmap , to_reg ( gpio , CTRL_OUT ) , & val ) ;
if ( ret )
return ret ;
return ! ( val & CTLO_DIR_OUT ) ;
}
2016-07-26 00:59:38 +03:00
static int wcove_gpio_get ( struct gpio_chip * chip , unsigned int gpio )
{
struct wcove_gpio * wg = gpiochip_get_data ( chip ) ;
unsigned int val ;
int ret ;
ret = regmap_read ( wg - > regmap , to_reg ( gpio , CTRL_IN ) , & val ) ;
if ( ret )
return ret ;
return val & 0x1 ;
}
static void wcove_gpio_set ( struct gpio_chip * chip ,
unsigned int gpio , int value )
{
struct wcove_gpio * wg = gpiochip_get_data ( chip ) ;
if ( value )
regmap_update_bits ( wg - > regmap , to_reg ( gpio , CTRL_OUT ) , 1 , 1 ) ;
else
regmap_update_bits ( wg - > regmap , to_reg ( gpio , CTRL_OUT ) , 1 , 0 ) ;
}
2017-01-23 15:34:34 +03:00
static int wcove_gpio_set_config ( struct gpio_chip * chip , unsigned int gpio ,
unsigned long config )
2016-07-26 00:59:38 +03:00
{
struct wcove_gpio * wg = gpiochip_get_data ( chip ) ;
2017-01-23 15:34:34 +03:00
switch ( pinconf_to_config_param ( config ) ) {
case PIN_CONFIG_DRIVE_OPEN_DRAIN :
2016-07-26 00:59:38 +03:00
return regmap_update_bits ( wg - > regmap , to_reg ( gpio , CTRL_OUT ) ,
CTLO_DRV_MASK , CTLO_DRV_OD ) ;
2017-01-23 15:34:34 +03:00
case PIN_CONFIG_DRIVE_PUSH_PULL :
2016-07-26 00:59:38 +03:00
return regmap_update_bits ( wg - > regmap , to_reg ( gpio , CTRL_OUT ) ,
CTLO_DRV_MASK , CTLO_DRV_CMOS ) ;
default :
break ;
}
return - ENOTSUPP ;
}
static int wcove_irq_type ( struct irq_data * data , unsigned int type )
{
struct gpio_chip * chip = irq_data_get_irq_chip_data ( data ) ;
struct wcove_gpio * wg = gpiochip_get_data ( chip ) ;
switch ( type ) {
case IRQ_TYPE_NONE :
wg - > intcnt = CTLI_INTCNT_DIS ;
break ;
case IRQ_TYPE_EDGE_BOTH :
wg - > intcnt = CTLI_INTCNT_BE ;
break ;
case IRQ_TYPE_EDGE_RISING :
wg - > intcnt = CTLI_INTCNT_PE ;
break ;
case IRQ_TYPE_EDGE_FALLING :
wg - > intcnt = CTLI_INTCNT_NE ;
break ;
default :
return - EINVAL ;
}
wg - > update | = UPDATE_IRQ_TYPE ;
return 0 ;
}
static void wcove_bus_lock ( struct irq_data * data )
{
struct gpio_chip * chip = irq_data_get_irq_chip_data ( data ) ;
struct wcove_gpio * wg = gpiochip_get_data ( chip ) ;
mutex_lock ( & wg - > buslock ) ;
}
static void wcove_bus_sync_unlock ( struct irq_data * data )
{
struct gpio_chip * chip = irq_data_get_irq_chip_data ( data ) ;
struct wcove_gpio * wg = gpiochip_get_data ( chip ) ;
int gpio = data - > hwirq ;
if ( wg - > update & UPDATE_IRQ_TYPE )
wcove_update_irq_ctrl ( wg , gpio ) ;
if ( wg - > update & UPDATE_IRQ_MASK )
wcove_update_irq_mask ( wg , gpio ) ;
wg - > update = 0 ;
mutex_unlock ( & wg - > buslock ) ;
}
static void wcove_irq_unmask ( struct irq_data * data )
{
struct gpio_chip * chip = irq_data_get_irq_chip_data ( data ) ;
struct wcove_gpio * wg = gpiochip_get_data ( chip ) ;
wg - > set_irq_mask = false ;
wg - > update | = UPDATE_IRQ_MASK ;
}
static void wcove_irq_mask ( struct irq_data * data )
{
struct gpio_chip * chip = irq_data_get_irq_chip_data ( data ) ;
struct wcove_gpio * wg = gpiochip_get_data ( chip ) ;
wg - > set_irq_mask = true ;
wg - > update | = UPDATE_IRQ_MASK ;
}
static struct irq_chip wcove_irqchip = {
. name = " Whiskey Cove " ,
. irq_mask = wcove_irq_mask ,
. irq_unmask = wcove_irq_unmask ,
. irq_set_type = wcove_irq_type ,
. irq_bus_lock = wcove_bus_lock ,
. irq_bus_sync_unlock = wcove_bus_sync_unlock ,
} ;
static irqreturn_t wcove_gpio_irq_handler ( int irq , void * data )
{
struct wcove_gpio * wg = ( struct wcove_gpio * ) data ;
unsigned int pending , virq , gpio , mask , offset ;
u8 p [ 2 ] ;
if ( regmap_bulk_read ( wg - > regmap , IRQ_STATUS_BASE , p , 2 ) ) {
dev_err ( wg - > dev , " Failed to read irq status register \n " ) ;
return IRQ_NONE ;
}
pending = p [ 0 ] | ( p [ 1 ] < < 8 ) ;
if ( ! pending )
return IRQ_NONE ;
/* Iterate until no interrupt is pending */
while ( pending ) {
/* One iteration is for all pending bits */
for_each_set_bit ( gpio , ( const unsigned long * ) & pending ,
2017-04-14 20:29:25 +03:00
WCOVE_GPIO_NUM ) {
2016-07-26 00:59:38 +03:00
offset = ( gpio > GROUP0_NR_IRQS ) ? 1 : 0 ;
mask = ( offset = = 1 ) ? BIT ( gpio - GROUP0_NR_IRQS ) :
BIT ( gpio ) ;
virq = irq_find_mapping ( wg - > chip . irqdomain , gpio ) ;
handle_nested_irq ( virq ) ;
regmap_update_bits ( wg - > regmap , IRQ_STATUS_BASE + offset ,
mask , mask ) ;
}
/* Next iteration */
if ( regmap_bulk_read ( wg - > regmap , IRQ_STATUS_BASE , p , 2 ) ) {
dev_err ( wg - > dev , " Failed to read irq status \n " ) ;
break ;
}
pending = p [ 0 ] | ( p [ 1 ] < < 8 ) ;
}
return IRQ_HANDLED ;
}
static void wcove_gpio_dbg_show ( struct seq_file * s ,
struct gpio_chip * chip )
{
unsigned int ctlo , ctli , irq_mask , irq_status ;
struct wcove_gpio * wg = gpiochip_get_data ( chip ) ;
int gpio , offset , group , ret = 0 ;
for ( gpio = 0 ; gpio < WCOVE_GPIO_NUM ; gpio + + ) {
group = gpio < GROUP0_NR_IRQS ? 0 : 1 ;
ret + = regmap_read ( wg - > regmap , to_reg ( gpio , CTRL_OUT ) , & ctlo ) ;
ret + = regmap_read ( wg - > regmap , to_reg ( gpio , CTRL_IN ) , & ctli ) ;
ret + = regmap_read ( wg - > regmap , IRQ_MASK_BASE + group ,
& irq_mask ) ;
ret + = regmap_read ( wg - > regmap , IRQ_STATUS_BASE + group ,
& irq_status ) ;
if ( ret ) {
pr_err ( " Failed to read registers: ctrl out/in or irq status/mask \n " ) ;
break ;
}
offset = gpio % 8 ;
seq_printf ( s , " gpio-%-2d %s %s %s %s ctlo=%2x,%s %s \n " ,
gpio , ctlo & CTLO_DIR_OUT ? " out " : " in " ,
ctli & 0x1 ? " hi " : " lo " ,
ctli & CTLI_INTCNT_NE ? " fall " : " " ,
ctli & CTLI_INTCNT_PE ? " rise " : " " ,
ctlo ,
irq_mask & BIT ( offset ) ? " mask " : " unmask " ,
irq_status & BIT ( offset ) ? " pending " : " " ) ;
}
}
static int wcove_gpio_probe ( struct platform_device * pdev )
{
struct intel_soc_pmic * pmic ;
struct wcove_gpio * wg ;
int virq , ret , irq ;
struct device * dev ;
/*
* This gpio platform device is created by a mfd device ( see
* drivers / mfd / intel_soc_pmic_bxtwc . c for details ) . Information
* shared by all sub - devices created by the mfd device , the regmap
* pointer for instance , is stored as driver data of the mfd device
* driver .
*/
pmic = dev_get_drvdata ( pdev - > dev . parent ) ;
if ( ! pmic )
return - ENODEV ;
irq = platform_get_irq ( pdev , 0 ) ;
if ( irq < 0 )
return irq ;
dev = & pdev - > dev ;
wg = devm_kzalloc ( dev , sizeof ( * wg ) , GFP_KERNEL ) ;
if ( ! wg )
return - ENOMEM ;
wg - > regmap_irq_chip = pmic - > irq_chip_data_level2 ;
platform_set_drvdata ( pdev , wg ) ;
mutex_init ( & wg - > buslock ) ;
wg - > chip . label = KBUILD_MODNAME ;
wg - > chip . direction_input = wcove_gpio_dir_in ;
wg - > chip . direction_output = wcove_gpio_dir_out ;
2016-08-15 21:03:23 +03:00
wg - > chip . get_direction = wcove_gpio_get_direction ;
2016-07-26 00:59:38 +03:00
wg - > chip . get = wcove_gpio_get ;
wg - > chip . set = wcove_gpio_set ;
2017-01-23 15:34:34 +03:00
wg - > chip . set_config = wcove_gpio_set_config ,
2016-07-26 00:59:38 +03:00
wg - > chip . base = - 1 ;
wg - > chip . ngpio = WCOVE_VGPIO_NUM ;
wg - > chip . can_sleep = true ;
wg - > chip . parent = pdev - > dev . parent ;
wg - > chip . dbg_show = wcove_gpio_dbg_show ;
wg - > dev = dev ;
wg - > regmap = pmic - > regmap ;
ret = devm_gpiochip_add_data ( dev , & wg - > chip , wg ) ;
if ( ret ) {
dev_err ( dev , " Failed to add gpiochip: %d \n " , ret ) ;
return ret ;
}
2016-11-24 12:57:25 +03:00
ret = gpiochip_irqchip_add_nested ( & wg - > chip , & wcove_irqchip , 0 ,
handle_simple_irq , IRQ_TYPE_NONE ) ;
2016-07-26 00:59:38 +03:00
if ( ret ) {
dev_err ( dev , " Failed to add irqchip: %d \n " , ret ) ;
return ret ;
}
virq = regmap_irq_get_virq ( wg - > regmap_irq_chip , irq ) ;
if ( virq < 0 ) {
dev_err ( dev , " Failed to get virq by irq %d \n " , irq ) ;
return virq ;
}
ret = devm_request_threaded_irq ( dev , virq , NULL ,
wcove_gpio_irq_handler , IRQF_ONESHOT , pdev - > name , wg ) ;
if ( ret ) {
dev_err ( dev , " Failed to request irq %d \n " , virq ) ;
return ret ;
}
2016-11-24 15:27:54 +03:00
gpiochip_set_nested_irqchip ( & wg - > chip , & wcove_irqchip , virq ) ;
2016-07-26 00:59:38 +03:00
return 0 ;
}
/*
* Whiskey Cove PMIC itself is a analog device ( but with digital control
* interface ) providing power management support for other devices in
* the accompanied SoC , so we have no . pm for Whiskey Cove GPIO driver .
*/
static struct platform_driver wcove_gpio_driver = {
. driver = {
. name = " bxt_wcove_gpio " ,
} ,
. probe = wcove_gpio_probe ,
} ;
module_platform_driver ( wcove_gpio_driver ) ;
MODULE_AUTHOR ( " Ajay Thomas <ajay.thomas.david.rajamanickam@intel.com> " ) ;
MODULE_AUTHOR ( " Bin Gao <bin.gao@intel.com> " ) ;
MODULE_DESCRIPTION ( " Intel Whiskey Cove GPIO Driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_ALIAS ( " platform:bxt_wcove_gpio " ) ;