2018-12-18 19:10:42 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2017 - 2018 Cadence
*
* Authors :
* Jan Kotas < jank @ cadence . com >
* Boris Brezillon < boris . brezillon @ free - electrons . com >
*/
# include <linux/gpio/driver.h>
# include <linux/clk.h>
# include <linux/interrupt.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/spinlock.h>
# define CDNS_GPIO_BYPASS_MODE 0x00
# define CDNS_GPIO_DIRECTION_MODE 0x04
# define CDNS_GPIO_OUTPUT_EN 0x08
# define CDNS_GPIO_OUTPUT_VALUE 0x0c
# define CDNS_GPIO_INPUT_VALUE 0x10
# define CDNS_GPIO_IRQ_MASK 0x14
# define CDNS_GPIO_IRQ_EN 0x18
# define CDNS_GPIO_IRQ_DIS 0x1c
# define CDNS_GPIO_IRQ_STATUS 0x20
# define CDNS_GPIO_IRQ_TYPE 0x24
# define CDNS_GPIO_IRQ_VALUE 0x28
# define CDNS_GPIO_IRQ_ANY_EDGE 0x2c
struct cdns_gpio_chip {
struct gpio_chip gc ;
struct clk * pclk ;
void __iomem * regs ;
u32 bypass_orig ;
} ;
static int cdns_gpio_request ( struct gpio_chip * chip , unsigned int offset )
{
struct cdns_gpio_chip * cgpio = gpiochip_get_data ( chip ) ;
unsigned long flags ;
spin_lock_irqsave ( & chip - > bgpio_lock , flags ) ;
iowrite32 ( ioread32 ( cgpio - > regs + CDNS_GPIO_BYPASS_MODE ) & ~ BIT ( offset ) ,
cgpio - > regs + CDNS_GPIO_BYPASS_MODE ) ;
spin_unlock_irqrestore ( & chip - > bgpio_lock , flags ) ;
return 0 ;
}
static void cdns_gpio_free ( struct gpio_chip * chip , unsigned int offset )
{
struct cdns_gpio_chip * cgpio = gpiochip_get_data ( chip ) ;
unsigned long flags ;
spin_lock_irqsave ( & chip - > bgpio_lock , flags ) ;
iowrite32 ( ioread32 ( cgpio - > regs + CDNS_GPIO_BYPASS_MODE ) |
( BIT ( offset ) & cgpio - > bypass_orig ) ,
cgpio - > regs + CDNS_GPIO_BYPASS_MODE ) ;
spin_unlock_irqrestore ( & chip - > bgpio_lock , flags ) ;
}
static void cdns_gpio_irq_mask ( struct irq_data * d )
{
struct gpio_chip * chip = irq_data_get_irq_chip_data ( d ) ;
struct cdns_gpio_chip * cgpio = gpiochip_get_data ( chip ) ;
iowrite32 ( BIT ( d - > hwirq ) , cgpio - > regs + CDNS_GPIO_IRQ_DIS ) ;
}
static void cdns_gpio_irq_unmask ( struct irq_data * d )
{
struct gpio_chip * chip = irq_data_get_irq_chip_data ( d ) ;
struct cdns_gpio_chip * cgpio = gpiochip_get_data ( chip ) ;
iowrite32 ( BIT ( d - > hwirq ) , cgpio - > regs + CDNS_GPIO_IRQ_EN ) ;
}
static int cdns_gpio_irq_set_type ( struct irq_data * d , unsigned int type )
{
struct gpio_chip * chip = irq_data_get_irq_chip_data ( d ) ;
struct cdns_gpio_chip * cgpio = gpiochip_get_data ( chip ) ;
unsigned long flags ;
u32 int_value ;
u32 int_type ;
u32 mask = BIT ( d - > hwirq ) ;
int ret = 0 ;
spin_lock_irqsave ( & chip - > bgpio_lock , flags ) ;
int_value = ioread32 ( cgpio - > regs + CDNS_GPIO_IRQ_VALUE ) & ~ mask ;
int_type = ioread32 ( cgpio - > regs + CDNS_GPIO_IRQ_TYPE ) & ~ mask ;
/*
* The GPIO controller doesn ' t have an ACK register .
* All interrupt statuses are cleared on a status register read .
* Don ' t support edge interrupts for now .
*/
if ( type = = IRQ_TYPE_LEVEL_HIGH ) {
int_type | = mask ;
int_value | = mask ;
} else if ( type = = IRQ_TYPE_LEVEL_LOW ) {
int_type | = mask ;
} else {
ret = - EINVAL ;
goto err_irq_type ;
}
iowrite32 ( int_value , cgpio - > regs + CDNS_GPIO_IRQ_VALUE ) ;
iowrite32 ( int_type , cgpio - > regs + CDNS_GPIO_IRQ_TYPE ) ;
err_irq_type :
spin_unlock_irqrestore ( & chip - > bgpio_lock , flags ) ;
return ret ;
}
static void cdns_gpio_irq_handler ( struct irq_desc * desc )
{
struct gpio_chip * chip = irq_desc_get_handler_data ( desc ) ;
struct cdns_gpio_chip * cgpio = gpiochip_get_data ( chip ) ;
struct irq_chip * irqchip = irq_desc_get_chip ( desc ) ;
unsigned long status ;
int hwirq ;
chained_irq_enter ( irqchip , desc ) ;
status = ioread32 ( cgpio - > regs + CDNS_GPIO_IRQ_STATUS ) &
~ ioread32 ( cgpio - > regs + CDNS_GPIO_IRQ_MASK ) ;
for_each_set_bit ( hwirq , & status , chip - > ngpio )
generic_handle_irq ( irq_find_mapping ( chip - > irq . domain , hwirq ) ) ;
chained_irq_exit ( irqchip , desc ) ;
}
static struct irq_chip cdns_gpio_irqchip = {
. name = " cdns-gpio " ,
. irq_mask = cdns_gpio_irq_mask ,
. irq_unmask = cdns_gpio_irq_unmask ,
. irq_set_type = cdns_gpio_irq_set_type
} ;
static int cdns_gpio_probe ( struct platform_device * pdev )
{
struct cdns_gpio_chip * cgpio ;
int ret , irq ;
u32 dir_prev ;
u32 num_gpios = 32 ;
cgpio = devm_kzalloc ( & pdev - > dev , sizeof ( * cgpio ) , GFP_KERNEL ) ;
if ( ! cgpio )
return - ENOMEM ;
2019-03-11 21:54:45 +03:00
cgpio - > regs = devm_platform_ioremap_resource ( pdev , 0 ) ;
2018-12-18 19:10:42 +03:00
if ( IS_ERR ( cgpio - > regs ) )
return PTR_ERR ( cgpio - > regs ) ;
of_property_read_u32 ( pdev - > dev . of_node , " ngpios " , & num_gpios ) ;
if ( num_gpios > 32 ) {
dev_err ( & pdev - > dev , " ngpios must be less or equal 32 \n " ) ;
return - EINVAL ;
}
/*
* Set all pins as inputs by default , otherwise :
* gpiochip_lock_as_irq :
* tried to flag a GPIO set as output for IRQ
* Generic GPIO driver stores the direction value internally ,
* so it needs to be changed before bgpio_init ( ) is called .
*/
dir_prev = ioread32 ( cgpio - > regs + CDNS_GPIO_DIRECTION_MODE ) ;
iowrite32 ( GENMASK ( num_gpios - 1 , 0 ) ,
cgpio - > regs + CDNS_GPIO_DIRECTION_MODE ) ;
ret = bgpio_init ( & cgpio - > gc , & pdev - > dev , 4 ,
cgpio - > regs + CDNS_GPIO_INPUT_VALUE ,
cgpio - > regs + CDNS_GPIO_OUTPUT_VALUE ,
NULL ,
NULL ,
cgpio - > regs + CDNS_GPIO_DIRECTION_MODE ,
BGPIOF_READ_OUTPUT_REG_SET ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Failed to register generic gpio, %d \n " ,
ret ) ;
goto err_revert_dir ;
}
cgpio - > gc . label = dev_name ( & pdev - > dev ) ;
cgpio - > gc . ngpio = num_gpios ;
cgpio - > gc . parent = & pdev - > dev ;
cgpio - > gc . base = - 1 ;
cgpio - > gc . owner = THIS_MODULE ;
cgpio - > gc . request = cdns_gpio_request ;
cgpio - > gc . free = cdns_gpio_free ;
cgpio - > pclk = devm_clk_get ( & pdev - > dev , NULL ) ;
if ( IS_ERR ( cgpio - > pclk ) ) {
ret = PTR_ERR ( cgpio - > pclk ) ;
dev_err ( & pdev - > dev ,
" Failed to retrieve peripheral clock, %d \n " , ret ) ;
goto err_revert_dir ;
}
ret = clk_prepare_enable ( cgpio - > pclk ) ;
if ( ret ) {
dev_err ( & pdev - > dev ,
" Failed to enable the peripheral clock, %d \n " , ret ) ;
goto err_revert_dir ;
}
/*
2019-08-09 16:18:04 +03:00
* Optional irq_chip support
2018-12-18 19:10:42 +03:00
*/
irq = platform_get_irq ( pdev , 0 ) ;
if ( irq > = 0 ) {
2019-08-09 16:18:04 +03:00
struct gpio_irq_chip * girq ;
girq = & cgpio - > gc . irq ;
girq - > chip = & cdns_gpio_irqchip ;
girq - > parent_handler = cdns_gpio_irq_handler ;
girq - > num_parents = 1 ;
girq - > parents = devm_kcalloc ( & pdev - > dev , 1 ,
sizeof ( * girq - > parents ) ,
GFP_KERNEL ) ;
if ( ! girq - > parents ) {
ret = - ENOMEM ;
2018-12-18 19:10:42 +03:00
goto err_disable_clk ;
}
2019-08-09 16:18:04 +03:00
girq - > parents [ 0 ] = irq ;
girq - > default_type = IRQ_TYPE_NONE ;
girq - > handler = handle_level_irq ;
}
ret = devm_gpiochip_add_data ( & pdev - > dev , & cgpio - > gc , cgpio ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " Could not register gpiochip, %d \n " , ret ) ;
goto err_disable_clk ;
2018-12-18 19:10:42 +03:00
}
cgpio - > bypass_orig = ioread32 ( cgpio - > regs + CDNS_GPIO_BYPASS_MODE ) ;
/*
* Enable gpio outputs , ignored for input direction
*/
iowrite32 ( GENMASK ( num_gpios - 1 , 0 ) ,
cgpio - > regs + CDNS_GPIO_OUTPUT_EN ) ;
iowrite32 ( 0 , cgpio - > regs + CDNS_GPIO_BYPASS_MODE ) ;
platform_set_drvdata ( pdev , cgpio ) ;
return 0 ;
err_disable_clk :
clk_disable_unprepare ( cgpio - > pclk ) ;
err_revert_dir :
iowrite32 ( dir_prev , cgpio - > regs + CDNS_GPIO_DIRECTION_MODE ) ;
return ret ;
}
static int cdns_gpio_remove ( struct platform_device * pdev )
{
struct cdns_gpio_chip * cgpio = platform_get_drvdata ( pdev ) ;
iowrite32 ( cgpio - > bypass_orig , cgpio - > regs + CDNS_GPIO_BYPASS_MODE ) ;
clk_disable_unprepare ( cgpio - > pclk ) ;
return 0 ;
}
static const struct of_device_id cdns_of_ids [ ] = {
{ . compatible = " cdns,gpio-r1p02 " } ,
{ /* sentinel */ } ,
} ;
static struct platform_driver cdns_gpio_driver = {
. driver = {
. name = " cdns-gpio " ,
. of_match_table = cdns_of_ids ,
} ,
. probe = cdns_gpio_probe ,
. remove = cdns_gpio_remove ,
} ;
module_platform_driver ( cdns_gpio_driver ) ;
MODULE_AUTHOR ( " Jan Kotas <jank@cadence.com> " ) ;
MODULE_DESCRIPTION ( " Cadence GPIO driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_ALIAS ( " platform:cdns-gpio " ) ;