2019-01-25 23:59:11 +03:00
// SPDX-License-Identifier: GPL-2.0
//
// IXP4 GPIO driver
// Copyright (C) 2019 Linus Walleij <linus.walleij@linaro.org>
//
// based on previous work and know-how from:
// Deepak Saxena <dsaxena@plexity.net>
# include <linux/gpio/driver.h>
# include <linux/io.h>
# include <linux/irq.h>
# include <linux/irqdomain.h>
# include <linux/irqchip.h>
2019-01-28 23:16:44 +03:00
# include <linux/of_irq.h>
2019-01-25 23:59:11 +03:00
# include <linux/platform_device.h>
# include <linux/bitops.h>
# define IXP4XX_REG_GPOUT 0x00
# define IXP4XX_REG_GPOE 0x04
# define IXP4XX_REG_GPIN 0x08
# define IXP4XX_REG_GPIS 0x0C
# define IXP4XX_REG_GPIT1 0x10
# define IXP4XX_REG_GPIT2 0x14
# define IXP4XX_REG_GPCLK 0x18
# define IXP4XX_REG_GPDBSEL 0x1C
/*
* The hardware uses 3 bits to indicate interrupt " style " .
* we clear and set these three bits accordingly . The lower 24
* bits in two registers ( GPIT1 and GPIT2 ) are used to set up
* the style for 8 lines each for a total of 16 GPIO lines .
*/
# define IXP4XX_GPIO_STYLE_ACTIVE_HIGH 0x0
# define IXP4XX_GPIO_STYLE_ACTIVE_LOW 0x1
# define IXP4XX_GPIO_STYLE_RISING_EDGE 0x2
# define IXP4XX_GPIO_STYLE_FALLING_EDGE 0x3
# define IXP4XX_GPIO_STYLE_TRANSITIONAL 0x4
# define IXP4XX_GPIO_STYLE_MASK GENMASK(2, 0)
# define IXP4XX_GPIO_STYLE_SIZE 3
/**
* struct ixp4xx_gpio - IXP4 GPIO state container
* @ dev : containing device for this instance
* @ fwnode : the fwnode for this GPIO chip
* @ gc : gpiochip for this instance
* @ base : remapped I / O - memory base
* @ irq_edge : Each bit represents an IRQ : 1 : edge - triggered ,
* 0 : level triggered
*/
struct ixp4xx_gpio {
struct device * dev ;
struct fwnode_handle * fwnode ;
struct gpio_chip gc ;
void __iomem * base ;
unsigned long long irq_edge ;
} ;
static void ixp4xx_gpio_irq_ack ( struct irq_data * d )
{
2019-08-08 15:32:38 +03:00
struct gpio_chip * gc = irq_data_get_irq_chip_data ( d ) ;
struct ixp4xx_gpio * g = gpiochip_get_data ( gc ) ;
2019-01-25 23:59:11 +03:00
__raw_writel ( BIT ( d - > hwirq ) , g - > base + IXP4XX_REG_GPIS ) ;
}
2022-09-11 22:07:51 +03:00
static void ixp4xx_gpio_mask_irq ( struct irq_data * d )
{
struct gpio_chip * gc = irq_data_get_irq_chip_data ( d ) ;
irq_chip_mask_parent ( d ) ;
gpiochip_disable_irq ( gc , d - > hwirq ) ;
}
2019-01-25 23:59:11 +03:00
static void ixp4xx_gpio_irq_unmask ( struct irq_data * d )
{
2019-08-08 15:32:38 +03:00
struct gpio_chip * gc = irq_data_get_irq_chip_data ( d ) ;
struct ixp4xx_gpio * g = gpiochip_get_data ( gc ) ;
2019-01-25 23:59:11 +03:00
/* ACK when unmasking if not edge-triggered */
if ( ! ( g - > irq_edge & BIT ( d - > hwirq ) ) )
ixp4xx_gpio_irq_ack ( d ) ;
2022-09-11 22:07:51 +03:00
gpiochip_enable_irq ( gc , d - > hwirq ) ;
2019-01-25 23:59:11 +03:00
irq_chip_unmask_parent ( d ) ;
}
static int ixp4xx_gpio_irq_set_type ( struct irq_data * d , unsigned int type )
{
2019-08-08 15:32:38 +03:00
struct gpio_chip * gc = irq_data_get_irq_chip_data ( d ) ;
struct ixp4xx_gpio * g = gpiochip_get_data ( gc ) ;
2019-01-25 23:59:11 +03:00
int line = d - > hwirq ;
unsigned long flags ;
u32 int_style ;
u32 int_reg ;
u32 val ;
switch ( type ) {
case IRQ_TYPE_EDGE_BOTH :
irq_set_handler_locked ( d , handle_edge_irq ) ;
int_style = IXP4XX_GPIO_STYLE_TRANSITIONAL ;
g - > irq_edge | = BIT ( d - > hwirq ) ;
break ;
case IRQ_TYPE_EDGE_RISING :
irq_set_handler_locked ( d , handle_edge_irq ) ;
int_style = IXP4XX_GPIO_STYLE_RISING_EDGE ;
g - > irq_edge | = BIT ( d - > hwirq ) ;
break ;
case IRQ_TYPE_EDGE_FALLING :
irq_set_handler_locked ( d , handle_edge_irq ) ;
int_style = IXP4XX_GPIO_STYLE_FALLING_EDGE ;
g - > irq_edge | = BIT ( d - > hwirq ) ;
break ;
case IRQ_TYPE_LEVEL_HIGH :
irq_set_handler_locked ( d , handle_level_irq ) ;
int_style = IXP4XX_GPIO_STYLE_ACTIVE_HIGH ;
g - > irq_edge & = ~ BIT ( d - > hwirq ) ;
break ;
case IRQ_TYPE_LEVEL_LOW :
irq_set_handler_locked ( d , handle_level_irq ) ;
int_style = IXP4XX_GPIO_STYLE_ACTIVE_LOW ;
g - > irq_edge & = ~ BIT ( d - > hwirq ) ;
break ;
default :
return - EINVAL ;
}
if ( line > = 8 ) {
/* pins 8-15 */
line - = 8 ;
int_reg = IXP4XX_REG_GPIT2 ;
} else {
/* pins 0-7 */
int_reg = IXP4XX_REG_GPIT1 ;
}
2022-04-19 04:28:10 +03:00
raw_spin_lock_irqsave ( & g - > gc . bgpio_lock , flags ) ;
2019-01-25 23:59:11 +03:00
/* Clear the style for the appropriate pin */
val = __raw_readl ( g - > base + int_reg ) ;
val & = ~ ( IXP4XX_GPIO_STYLE_MASK < < ( line * IXP4XX_GPIO_STYLE_SIZE ) ) ;
__raw_writel ( val , g - > base + int_reg ) ;
__raw_writel ( BIT ( line ) , g - > base + IXP4XX_REG_GPIS ) ;
/* Set the new style */
val = __raw_readl ( g - > base + int_reg ) ;
val | = ( int_style < < ( line * IXP4XX_GPIO_STYLE_SIZE ) ) ;
__raw_writel ( val , g - > base + int_reg ) ;
/* Force-configure this line as an input */
val = __raw_readl ( g - > base + IXP4XX_REG_GPOE ) ;
val | = BIT ( d - > hwirq ) ;
__raw_writel ( val , g - > base + IXP4XX_REG_GPOE ) ;
2022-04-19 04:28:10 +03:00
raw_spin_unlock_irqrestore ( & g - > gc . bgpio_lock , flags ) ;
2019-01-25 23:59:11 +03:00
/* This parent only accept level high (asserted) */
return irq_chip_set_type_parent ( d , IRQ_TYPE_LEVEL_HIGH ) ;
}
2022-09-11 22:07:51 +03:00
static const struct irq_chip ixp4xx_gpio_irqchip = {
2019-01-25 23:59:11 +03:00
. name = " IXP4GPIO " ,
. irq_ack = ixp4xx_gpio_irq_ack ,
2022-09-11 22:07:51 +03:00
. irq_mask = ixp4xx_gpio_mask_irq ,
2019-01-25 23:59:11 +03:00
. irq_unmask = ixp4xx_gpio_irq_unmask ,
. irq_set_type = ixp4xx_gpio_irq_set_type ,
2022-09-11 22:07:51 +03:00
. flags = IRQCHIP_IMMUTABLE ,
GPIOCHIP_IRQ_RESOURCE_HELPERS ,
2019-01-25 23:59:11 +03:00
} ;
2019-08-08 15:32:38 +03:00
static int ixp4xx_gpio_child_to_parent_hwirq ( struct gpio_chip * gc ,
unsigned int child ,
unsigned int child_type ,
unsigned int * parent ,
unsigned int * parent_type )
2019-01-25 23:59:11 +03:00
{
2019-08-08 15:32:38 +03:00
/* All these interrupts are level high in the CPU */
* parent_type = IRQ_TYPE_LEVEL_HIGH ;
2019-01-25 23:59:11 +03:00
2019-08-08 15:32:38 +03:00
/* GPIO lines 0..12 have dedicated IRQs */
if ( child = = 0 ) {
* parent = 6 ;
return 0 ;
2019-01-25 23:59:11 +03:00
}
2019-08-08 15:32:38 +03:00
if ( child = = 1 ) {
* parent = 7 ;
2019-01-25 23:59:11 +03:00
return 0 ;
}
2019-08-08 15:32:38 +03:00
if ( child > = 2 & & child < = 12 ) {
* parent = child + 17 ;
return 0 ;
2019-01-25 23:59:11 +03:00
}
2019-08-08 15:32:38 +03:00
return - EINVAL ;
2019-01-25 23:59:11 +03:00
}
static int ixp4xx_gpio_probe ( struct platform_device * pdev )
{
unsigned long flags ;
struct device * dev = & pdev - > dev ;
2019-01-28 23:16:44 +03:00
struct device_node * np = dev - > of_node ;
2019-01-25 23:59:11 +03:00
struct irq_domain * parent ;
struct resource * res ;
struct ixp4xx_gpio * g ;
2019-08-08 15:32:38 +03:00
struct gpio_irq_chip * girq ;
2022-04-21 23:06:54 +03:00
struct device_node * irq_parent ;
2019-01-25 23:59:11 +03:00
int ret ;
g = devm_kzalloc ( dev , sizeof ( * g ) , GFP_KERNEL ) ;
if ( ! g )
return - ENOMEM ;
g - > dev = dev ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
g - > base = devm_ioremap_resource ( dev , res ) ;
2019-07-31 12:47:54 +03:00
if ( IS_ERR ( g - > base ) )
2019-01-25 23:59:11 +03:00
return PTR_ERR ( g - > base ) ;
2022-04-21 23:06:54 +03:00
irq_parent = of_irq_find_parent ( np ) ;
if ( ! irq_parent ) {
dev_err ( dev , " no IRQ parent node \n " ) ;
return - ENODEV ;
}
parent = irq_find_host ( irq_parent ) ;
if ( ! parent ) {
dev_err ( dev , " no IRQ parent domain \n " ) ;
return - ENODEV ;
2019-08-08 15:32:38 +03:00
}
2022-04-21 23:06:54 +03:00
g - > fwnode = of_node_to_fwnode ( np ) ;
2019-08-08 15:32:38 +03:00
2019-01-25 23:59:11 +03:00
/*
* Make sure GPIO 14 and 15 are NOT used as clocks but GPIO on
* specific machines .
*/
2022-04-06 23:51:56 +03:00
if ( of_machine_is_compatible ( " dlink,dsm-g600-a " ) | |
of_machine_is_compatible ( " iom,nas-100d " ) )
2019-01-25 23:59:11 +03:00
__raw_writel ( 0x0 , g - > base + IXP4XX_REG_GPCLK ) ;
/*
* This is a very special big - endian ARM issue : when the IXP4xx is
* run in big endian mode , all registers in the machine are switched
* around to the CPU - native endianness . As you see mostly in the
* driver we use __raw_readl ( ) / __raw_writel ( ) to access the registers
* in the appropriate order . With the GPIO library we need to specify
* byte order explicitly , so this flag needs to be set when compiling
* for big endian .
*/
# if defined(CONFIG_CPU_BIG_ENDIAN)
flags = BGPIOF_BIG_ENDIAN_BYTE_ORDER ;
# else
flags = 0 ;
# endif
/* Populate and register gpio chip */
ret = bgpio_init ( & g - > gc , dev , 4 ,
g - > base + IXP4XX_REG_GPIN ,
g - > base + IXP4XX_REG_GPOUT ,
NULL ,
NULL ,
g - > base + IXP4XX_REG_GPOE ,
flags ) ;
if ( ret ) {
dev_err ( dev , " unable to init generic GPIO \n " ) ;
return ret ;
}
g - > gc . ngpio = 16 ;
g - > gc . label = " IXP4XX_GPIO_CHIP " ;
/*
* TODO : when we have migrated to device tree and all GPIOs
* are fetched using phandles , set this to - 1 to get rid of
* the fixed gpiochip base .
*/
g - > gc . base = 0 ;
g - > gc . parent = & pdev - > dev ;
g - > gc . owner = THIS_MODULE ;
2019-08-08 15:32:38 +03:00
girq = & g - > gc . irq ;
2022-09-11 22:07:51 +03:00
gpio_irq_chip_set_chip ( girq , & ixp4xx_gpio_irqchip ) ;
2019-08-08 15:32:38 +03:00
girq - > fwnode = g - > fwnode ;
girq - > parent_domain = parent ;
girq - > child_to_parent_hwirq = ixp4xx_gpio_child_to_parent_hwirq ;
girq - > handler = handle_bad_irq ;
girq - > default_type = IRQ_TYPE_NONE ;
2019-01-25 23:59:11 +03:00
ret = devm_gpiochip_add_data ( dev , & g - > gc , g ) ;
if ( ret ) {
dev_err ( dev , " failed to add SoC gpiochip \n " ) ;
return ret ;
}
platform_set_drvdata ( pdev , g ) ;
2019-08-08 15:32:38 +03:00
dev_info ( dev , " IXP4 GPIO registered \n " ) ;
2019-01-25 23:59:11 +03:00
return 0 ;
}
2019-01-28 23:16:44 +03:00
static const struct of_device_id ixp4xx_gpio_of_match [ ] = {
{
. compatible = " intel,ixp4xx-gpio " ,
} ,
{ } ,
} ;
2019-01-25 23:59:11 +03:00
static struct platform_driver ixp4xx_gpio_driver = {
. driver = {
. name = " ixp4xx-gpio " ,
2019-01-28 23:16:44 +03:00
. of_match_table = of_match_ptr ( ixp4xx_gpio_of_match ) ,
2019-01-25 23:59:11 +03:00
} ,
. probe = ixp4xx_gpio_probe ,
} ;
builtin_platform_driver ( ixp4xx_gpio_driver ) ;