2017-12-26 19:09:42 +01:00
// SPDX-License-Identifier: GPL-2.0+
//
// S3C64xx specific support for pinctrl-samsung driver.
//
// Copyright (c) 2013 Tomasz Figa <tomasz.figa@gmail.com>
//
// Based on pinctrl-exynos.c, please see the file for original copyrights.
//
// This file contains the Samsung S3C64xx specific information required by the
// the Samsung pinctrl/gpiolib driver. It also includes the implementation of
// external gpio and wakeup interrupt support.
2013-03-18 22:31:55 +01:00
2017-05-22 16:56:46 -04:00
# include <linux/init.h>
2013-03-18 22:31:55 +01:00
# include <linux/device.h>
# include <linux/interrupt.h>
# include <linux/irqdomain.h>
# include <linux/irq.h>
# include <linux/of_irq.h>
# include <linux/io.h>
2013-04-17 00:23:59 +02:00
# include <linux/irqchip/chained_irq.h>
2013-03-18 22:31:55 +01:00
# include <linux/slab.h>
# include <linux/err.h>
# include "pinctrl-samsung.h"
# define NUM_EINT0 28
# define NUM_EINT0_IRQ 4
# define EINT_MAX_PER_REG 16
# define EINT_MAX_PER_GROUP 16
/* External GPIO and wakeup interrupt related definitions */
# define SVC_GROUP_SHIFT 4
# define SVC_GROUP_MASK 0xf
# define SVC_NUM_MASK 0xf
# define SVC_GROUP(x) ((x >> SVC_GROUP_SHIFT) & \
SVC_GROUP_MASK )
# define EINT12CON_REG 0x200
# define EINT12MASK_REG 0x240
# define EINT12PEND_REG 0x260
# define EINT_OFFS(i) ((i) % (2 * EINT_MAX_PER_GROUP))
# define EINT_GROUP(i) ((i) / EINT_MAX_PER_GROUP)
# define EINT_REG(g) (4 * ((g) / 2))
# define EINTCON_REG(i) (EINT12CON_REG + EINT_REG(EINT_GROUP(i)))
# define EINTMASK_REG(i) (EINT12MASK_REG + EINT_REG(EINT_GROUP(i)))
# define EINTPEND_REG(i) (EINT12PEND_REG + EINT_REG(EINT_GROUP(i)))
# define SERVICE_REG 0x284
# define SERVICEPEND_REG 0x288
# define EINT0CON0_REG 0x900
# define EINT0MASK_REG 0x920
# define EINT0PEND_REG 0x924
/* S3C64xx specific external interrupt trigger types */
# define EINT_LEVEL_LOW 0
# define EINT_LEVEL_HIGH 1
# define EINT_EDGE_FALLING 2
# define EINT_EDGE_RISING 4
# define EINT_EDGE_BOTH 6
# define EINT_CON_MASK 0xF
# define EINT_CON_LEN 4
2014-09-23 21:05:39 +02:00
static const struct samsung_pin_bank_type bank_type_4bit_off = {
2013-03-18 22:31:55 +01:00
. fld_width = { 4 , 1 , 2 , 0 , 2 , 2 , } ,
. reg_offset = { 0x00 , 0x04 , 0x08 , 0 , 0x0c , 0x10 , } ,
} ;
2014-09-23 21:05:39 +02:00
static const struct samsung_pin_bank_type bank_type_4bit_alive = {
2013-03-18 22:31:55 +01:00
. fld_width = { 4 , 1 , 2 , } ,
. reg_offset = { 0x00 , 0x04 , 0x08 , } ,
} ;
2014-09-23 21:05:39 +02:00
static const struct samsung_pin_bank_type bank_type_4bit2_off = {
2013-03-18 22:31:55 +01:00
. fld_width = { 4 , 1 , 2 , 0 , 2 , 2 , } ,
. reg_offset = { 0x00 , 0x08 , 0x0c , 0 , 0x10 , 0x14 , } ,
} ;
2014-09-23 21:05:39 +02:00
static const struct samsung_pin_bank_type bank_type_4bit2_alive = {
2013-03-18 22:31:55 +01:00
. fld_width = { 4 , 1 , 2 , } ,
. reg_offset = { 0x00 , 0x08 , 0x0c , } ,
} ;
2014-09-23 21:05:39 +02:00
static const struct samsung_pin_bank_type bank_type_2bit_off = {
2013-03-18 22:31:55 +01:00
. fld_width = { 2 , 1 , 2 , 0 , 2 , 2 , } ,
. reg_offset = { 0x00 , 0x04 , 0x08 , 0 , 0x0c , 0x10 , } ,
} ;
2014-09-23 21:05:39 +02:00
static const struct samsung_pin_bank_type bank_type_2bit_alive = {
2013-03-18 22:31:55 +01:00
. fld_width = { 2 , 1 , 2 , } ,
. reg_offset = { 0x00 , 0x04 , 0x08 , } ,
} ;
# define PIN_BANK_4BIT(pins, reg, id) \
{ \
. type = & bank_type_4bit_off , \
. pctl_offset = reg , \
. nr_pins = pins , \
. eint_type = EINT_TYPE_NONE , \
. name = id \
}
# define PIN_BANK_4BIT_EINTG(pins, reg, id, eoffs) \
{ \
. type = & bank_type_4bit_off , \
. pctl_offset = reg , \
. nr_pins = pins , \
. eint_type = EINT_TYPE_GPIO , \
. eint_func = 7 , \
. eint_mask = ( 1 < < ( pins ) ) - 1 , \
. eint_offset = eoffs , \
. name = id \
}
# define PIN_BANK_4BIT_EINTW(pins, reg, id, eoffs, emask) \
{ \
. type = & bank_type_4bit_alive , \
. pctl_offset = reg , \
. nr_pins = pins , \
. eint_type = EINT_TYPE_WKUP , \
. eint_func = 3 , \
. eint_mask = emask , \
. eint_offset = eoffs , \
. name = id \
}
# define PIN_BANK_4BIT2_EINTG(pins, reg, id, eoffs) \
{ \
. type = & bank_type_4bit2_off , \
. pctl_offset = reg , \
. nr_pins = pins , \
. eint_type = EINT_TYPE_GPIO , \
. eint_func = 7 , \
. eint_mask = ( 1 < < ( pins ) ) - 1 , \
. eint_offset = eoffs , \
. name = id \
}
# define PIN_BANK_4BIT2_EINTW(pins, reg, id, eoffs, emask) \
{ \
. type = & bank_type_4bit2_alive , \
. pctl_offset = reg , \
. nr_pins = pins , \
. eint_type = EINT_TYPE_WKUP , \
. eint_func = 3 , \
. eint_mask = emask , \
. eint_offset = eoffs , \
. name = id \
}
# define PIN_BANK_4BIT2_ALIVE(pins, reg, id) \
{ \
. type = & bank_type_4bit2_alive , \
. pctl_offset = reg , \
. nr_pins = pins , \
. eint_type = EINT_TYPE_NONE , \
. name = id \
}
# define PIN_BANK_2BIT(pins, reg, id) \
{ \
. type = & bank_type_2bit_off , \
. pctl_offset = reg , \
. nr_pins = pins , \
. eint_type = EINT_TYPE_NONE , \
. name = id \
}
# define PIN_BANK_2BIT_EINTG(pins, reg, id, eoffs, emask) \
{ \
. type = & bank_type_2bit_off , \
. pctl_offset = reg , \
. nr_pins = pins , \
. eint_type = EINT_TYPE_GPIO , \
. eint_func = 3 , \
. eint_mask = emask , \
. eint_offset = eoffs , \
. name = id \
}
# define PIN_BANK_2BIT_EINTW(pins, reg, id, eoffs) \
{ \
. type = & bank_type_2bit_alive , \
. pctl_offset = reg , \
. nr_pins = pins , \
. eint_type = EINT_TYPE_WKUP , \
. eint_func = 2 , \
. eint_mask = ( 1 < < ( pins ) ) - 1 , \
. eint_offset = eoffs , \
. name = id \
}
/**
2020-07-13 15:49:13 +01:00
* struct s3c64xx_eint0_data - EINT0 common data
2013-03-18 22:31:55 +01:00
* @ drvdata : pin controller driver data
* @ domains : IRQ domains of particular EINT0 interrupts
* @ pins : pin offsets inside of banks of particular EINT0 interrupts
*/
struct s3c64xx_eint0_data {
struct samsung_pinctrl_drv_data * drvdata ;
struct irq_domain * domains [ NUM_EINT0 ] ;
u8 pins [ NUM_EINT0 ] ;
} ;
/**
2020-07-13 15:49:13 +01:00
* struct s3c64xx_eint0_domain_data - EINT0 per - domain data
2013-03-18 22:31:55 +01:00
* @ bank : pin bank related to the domain
* @ eints : EINT0 interrupts related to the domain
*/
struct s3c64xx_eint0_domain_data {
struct samsung_pin_bank * bank ;
u8 eints [ ] ;
} ;
/**
2020-07-13 15:49:13 +01:00
* struct s3c64xx_eint_gpio_data - GPIO EINT data
2013-03-18 22:31:55 +01:00
* @ drvdata : pin controller driver data
* @ domains : array of domains related to EINT interrupt groups
*/
struct s3c64xx_eint_gpio_data {
struct samsung_pinctrl_drv_data * drvdata ;
struct irq_domain * domains [ ] ;
} ;
/*
* Common functions for S3C64xx EINT configuration
*/
static int s3c64xx_irq_get_trigger ( unsigned int type )
{
int trigger ;
switch ( type ) {
case IRQ_TYPE_EDGE_RISING :
trigger = EINT_EDGE_RISING ;
break ;
case IRQ_TYPE_EDGE_FALLING :
trigger = EINT_EDGE_FALLING ;
break ;
case IRQ_TYPE_EDGE_BOTH :
trigger = EINT_EDGE_BOTH ;
break ;
case IRQ_TYPE_LEVEL_HIGH :
trigger = EINT_LEVEL_HIGH ;
break ;
case IRQ_TYPE_LEVEL_LOW :
trigger = EINT_LEVEL_LOW ;
break ;
default :
return - EINVAL ;
}
return trigger ;
}
2015-06-23 15:52:57 +02:00
static void s3c64xx_irq_set_handler ( struct irq_data * d , unsigned int type )
2013-03-18 22:31:55 +01:00
{
/* Edge- and level-triggered interrupts need different handlers */
if ( type & IRQ_TYPE_EDGE_BOTH )
2015-06-23 15:52:57 +02:00
irq_set_handler_locked ( d , handle_edge_irq ) ;
2013-03-18 22:31:55 +01:00
else
2015-06-23 15:52:57 +02:00
irq_set_handler_locked ( d , handle_level_irq ) ;
2013-03-18 22:31:55 +01:00
}
static void s3c64xx_irq_set_function ( struct samsung_pinctrl_drv_data * d ,
struct samsung_pin_bank * bank , int pin )
{
2014-09-23 21:05:39 +02:00
const struct samsung_pin_bank_type * bank_type = bank - > type ;
2013-03-18 22:31:55 +01:00
unsigned long flags ;
void __iomem * reg ;
u8 shift ;
u32 mask ;
u32 val ;
/* Make sure that pin is configured as interrupt */
2017-06-15 17:46:37 +02:00
reg = d - > virt_base + bank - > pctl_offset ;
2013-03-18 22:31:55 +01:00
shift = pin ;
if ( bank_type - > fld_width [ PINCFG_TYPE_FUNC ] * shift > = 32 ) {
/* 4-bit bank type with 2 con regs */
reg + = 4 ;
shift - = 8 ;
}
shift = shift * bank_type - > fld_width [ PINCFG_TYPE_FUNC ] ;
mask = ( 1 < < bank_type - > fld_width [ PINCFG_TYPE_FUNC ] ) - 1 ;
2021-01-27 09:16:31 +09:00
raw_spin_lock_irqsave ( & bank - > slock , flags ) ;
2013-03-18 22:31:55 +01:00
val = readl ( reg ) ;
val & = ~ ( mask < < shift ) ;
val | = bank - > eint_func < < shift ;
writel ( val , reg ) ;
2021-01-27 09:16:31 +09:00
raw_spin_unlock_irqrestore ( & bank - > slock , flags ) ;
2013-03-18 22:31:55 +01:00
}
/*
* Functions for EINT GPIO configuration ( EINT groups 1 - 9 )
*/
static inline void s3c64xx_gpio_irq_set_mask ( struct irq_data * irqd , bool mask )
{
struct samsung_pin_bank * bank = irq_data_get_irq_chip_data ( irqd ) ;
2017-06-15 17:46:37 +02:00
struct samsung_pinctrl_drv_data * d = bank - > drvdata ;
2013-03-18 22:31:55 +01:00
unsigned char index = EINT_OFFS ( bank - > eint_offset ) + irqd - > hwirq ;
2017-06-15 17:46:37 +02:00
void __iomem * reg = d - > virt_base + EINTMASK_REG ( bank - > eint_offset ) ;
2013-03-18 22:31:55 +01:00
u32 val ;
val = readl ( reg ) ;
if ( mask )
val | = 1 < < index ;
else
val & = ~ ( 1 < < index ) ;
writel ( val , reg ) ;
}
static void s3c64xx_gpio_irq_unmask ( struct irq_data * irqd )
{
s3c64xx_gpio_irq_set_mask ( irqd , false ) ;
}
static void s3c64xx_gpio_irq_mask ( struct irq_data * irqd )
{
s3c64xx_gpio_irq_set_mask ( irqd , true ) ;
}
static void s3c64xx_gpio_irq_ack ( struct irq_data * irqd )
{
struct samsung_pin_bank * bank = irq_data_get_irq_chip_data ( irqd ) ;
2017-06-15 17:46:37 +02:00
struct samsung_pinctrl_drv_data * d = bank - > drvdata ;
2013-03-18 22:31:55 +01:00
unsigned char index = EINT_OFFS ( bank - > eint_offset ) + irqd - > hwirq ;
2017-06-15 17:46:37 +02:00
void __iomem * reg = d - > virt_base + EINTPEND_REG ( bank - > eint_offset ) ;
2013-03-18 22:31:55 +01:00
writel ( 1 < < index , reg ) ;
}
static int s3c64xx_gpio_irq_set_type ( struct irq_data * irqd , unsigned int type )
{
struct samsung_pin_bank * bank = irq_data_get_irq_chip_data ( irqd ) ;
struct samsung_pinctrl_drv_data * d = bank - > drvdata ;
void __iomem * reg ;
int trigger ;
u8 shift ;
u32 val ;
trigger = s3c64xx_irq_get_trigger ( type ) ;
if ( trigger < 0 ) {
pr_err ( " unsupported external interrupt type \n " ) ;
return - EINVAL ;
}
2015-06-23 15:52:57 +02:00
s3c64xx_irq_set_handler ( irqd , type ) ;
2013-03-18 22:31:55 +01:00
/* Set up interrupt trigger */
2017-06-15 17:46:37 +02:00
reg = d - > virt_base + EINTCON_REG ( bank - > eint_offset ) ;
2013-03-18 22:31:55 +01:00
shift = EINT_OFFS ( bank - > eint_offset ) + irqd - > hwirq ;
shift = 4 * ( shift / 4 ) ; /* 4 EINTs per trigger selector */
val = readl ( reg ) ;
val & = ~ ( EINT_CON_MASK < < shift ) ;
val | = trigger < < shift ;
writel ( val , reg ) ;
s3c64xx_irq_set_function ( d , bank , irqd - > hwirq ) ;
return 0 ;
}
/*
* irq_chip for gpio interrupts .
*/
static struct irq_chip s3c64xx_gpio_irq_chip = {
. name = " GPIO " ,
. irq_unmask = s3c64xx_gpio_irq_unmask ,
. irq_mask = s3c64xx_gpio_irq_mask ,
. irq_ack = s3c64xx_gpio_irq_ack ,
. irq_set_type = s3c64xx_gpio_irq_set_type ,
} ;
static int s3c64xx_gpio_irq_map ( struct irq_domain * h , unsigned int virq ,
irq_hw_number_t hw )
{
struct samsung_pin_bank * bank = h - > host_data ;
if ( ! ( bank - > eint_mask & ( 1 < < hw ) ) )
return - EINVAL ;
irq_set_chip_and_handler ( virq ,
& s3c64xx_gpio_irq_chip , handle_level_irq ) ;
irq_set_chip_data ( virq , bank ) ;
return 0 ;
}
/*
* irq domain callbacks for external gpio interrupt controller .
*/
static const struct irq_domain_ops s3c64xx_gpio_irqd_ops = {
. map = s3c64xx_gpio_irq_map ,
. xlate = irq_domain_xlate_twocell ,
} ;
2015-09-14 10:42:37 +02:00
static void s3c64xx_eint_gpio_irq ( struct irq_desc * desc )
2013-03-18 22:31:55 +01:00
{
2015-06-04 12:13:16 +08:00
struct irq_chip * chip = irq_desc_get_chip ( desc ) ;
struct s3c64xx_eint_gpio_data * data = irq_desc_get_handler_data ( desc ) ;
2017-06-15 17:46:37 +02:00
struct samsung_pinctrl_drv_data * drvdata = data - > drvdata ;
2013-03-18 22:31:55 +01:00
chained_irq_enter ( chip , desc ) ;
do {
unsigned int svc ;
unsigned int group ;
unsigned int pin ;
unsigned int virq ;
2017-06-15 17:46:37 +02:00
svc = readl ( drvdata - > virt_base + SERVICE_REG ) ;
2013-03-18 22:31:55 +01:00
group = SVC_GROUP ( svc ) ;
pin = svc & SVC_NUM_MASK ;
if ( ! group )
break ;
/* Group 1 is used for two pin banks */
if ( group = = 1 ) {
if ( pin < 8 )
group = 0 ;
else
pin - = 8 ;
}
virq = irq_linear_revmap ( data - > domains [ group ] , pin ) ;
/*
* Something must be really wrong if an unmapped EINT
* was unmasked . . .
*/
BUG_ON ( ! virq ) ;
generic_handle_irq ( virq ) ;
} while ( 1 ) ;
chained_irq_exit ( chip , desc ) ;
}
/**
* s3c64xx_eint_gpio_init ( ) - setup handling of external gpio interrupts .
* @ d : driver data of samsung pinctrl driver .
*/
static int s3c64xx_eint_gpio_init ( struct samsung_pinctrl_drv_data * d )
{
struct s3c64xx_eint_gpio_data * data ;
struct samsung_pin_bank * bank ;
struct device * dev = d - > dev ;
unsigned int nr_domains ;
unsigned int i ;
if ( ! d - > irq ) {
dev_err ( dev , " irq number not available \n " ) ;
return - EINVAL ;
}
nr_domains = 0 ;
2014-09-23 21:05:40 +02:00
bank = d - > pin_banks ;
for ( i = 0 ; i < d - > nr_banks ; + + i , + + bank ) {
2013-03-18 22:31:55 +01:00
unsigned int nr_eints ;
unsigned int mask ;
if ( bank - > eint_type ! = EINT_TYPE_GPIO )
continue ;
mask = bank - > eint_mask ;
nr_eints = fls ( mask ) ;
bank - > irq_domain = irq_domain_add_linear ( bank - > of_node ,
nr_eints , & s3c64xx_gpio_irqd_ops , bank ) ;
if ( ! bank - > irq_domain ) {
dev_err ( dev , " gpio irq domain add failed \n " ) ;
return - ENXIO ;
}
+ + nr_domains ;
}
treewide: Use struct_size() for devm_kmalloc() and friends
Replaces open-coded struct size calculations with struct_size() for
devm_*, f2fs_*, and sock_* allocations. Automatically generated (and
manually adjusted) from the following Coccinelle script:
// Direct reference to struct field.
@@
identifier alloc =~ "devm_kmalloc|devm_kzalloc|sock_kmalloc|f2fs_kmalloc|f2fs_kzalloc";
expression HANDLE;
expression GFP;
identifier VAR, ELEMENT;
expression COUNT;
@@
- alloc(HANDLE, sizeof(*VAR) + COUNT * sizeof(*VAR->ELEMENT), GFP)
+ alloc(HANDLE, struct_size(VAR, ELEMENT, COUNT), GFP)
// mr = kzalloc(sizeof(*mr) + m * sizeof(mr->map[0]), GFP_KERNEL);
@@
identifier alloc =~ "devm_kmalloc|devm_kzalloc|sock_kmalloc|f2fs_kmalloc|f2fs_kzalloc";
expression HANDLE;
expression GFP;
identifier VAR, ELEMENT;
expression COUNT;
@@
- alloc(HANDLE, sizeof(*VAR) + COUNT * sizeof(VAR->ELEMENT[0]), GFP)
+ alloc(HANDLE, struct_size(VAR, ELEMENT, COUNT), GFP)
// Same pattern, but can't trivially locate the trailing element name,
// or variable name.
@@
identifier alloc =~ "devm_kmalloc|devm_kzalloc|sock_kmalloc|f2fs_kmalloc|f2fs_kzalloc";
expression HANDLE;
expression GFP;
expression SOMETHING, COUNT, ELEMENT;
@@
- alloc(HANDLE, sizeof(SOMETHING) + COUNT * sizeof(ELEMENT), GFP)
+ alloc(HANDLE, CHECKME_struct_size(&SOMETHING, ELEMENT, COUNT), GFP)
Signed-off-by: Kees Cook <keescook@chromium.org>
2018-05-08 16:08:53 -07:00
data = devm_kzalloc ( dev , struct_size ( data , domains , nr_domains ) ,
GFP_KERNEL ) ;
2017-01-19 14:48:45 +01:00
if ( ! data )
2013-03-18 22:31:55 +01:00
return - ENOMEM ;
data - > drvdata = d ;
2014-09-23 21:05:40 +02:00
bank = d - > pin_banks ;
2013-03-18 22:31:55 +01:00
nr_domains = 0 ;
2014-09-23 21:05:40 +02:00
for ( i = 0 ; i < d - > nr_banks ; + + i , + + bank ) {
2013-03-18 22:31:55 +01:00
if ( bank - > eint_type ! = EINT_TYPE_GPIO )
continue ;
data - > domains [ nr_domains + + ] = bank - > irq_domain ;
}
pinctrl/samsung: Fix race in installing chained IRQ handler
Fix a race where a pending interrupt could be received and the handler
called before the handler's data has been setup, by converting to
irq_set_chained_handler_and_data().
Search and conversion was done with coccinelle:
@@
expression E1, E2, E3;
@@
(
-if (irq_set_chained_handler(E1, E3) != 0)
- BUG();
|
-irq_set_chained_handler(E1, E3);
)
-irq_set_handler_data(E1, E2);
+irq_set_chained_handler_and_data(E1, E3, E2);
@@
expression E1, E2, E3;
@@
(
-if (irq_set_chained_handler(E1, E3) != 0)
- BUG();
...
|
-irq_set_chained_handler(E1, E3);
...
)
-irq_set_handler_data(E1, E2);
+irq_set_chained_handler_and_data(E1, E3, E2);
Reported-by: Russell King <rmk+kernel@arm.linux.org.uk>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Cc: Julia Lawall <Julia.Lawall@lip6.fr>
Cc: Tomasz Figa <tomasz.figa@gmail.com>
Cc: Thomas Abraham <thomas.abraham@linaro.org>
Cc: Linus Walleij <linus.walleij@linaro.org>
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-samsung-soc@vger.kernel.org
Cc: linux-gpio@vger.kernel.org
2015-06-21 20:16:18 +02:00
irq_set_chained_handler_and_data ( d - > irq , s3c64xx_eint_gpio_irq , data ) ;
2013-03-18 22:31:55 +01:00
return 0 ;
}
/*
* Functions for configuration of EINT0 wake - up interrupts
*/
static inline void s3c64xx_eint0_irq_set_mask ( struct irq_data * irqd , bool mask )
{
struct s3c64xx_eint0_domain_data * ddata =
irq_data_get_irq_chip_data ( irqd ) ;
2017-06-15 17:46:37 +02:00
struct samsung_pinctrl_drv_data * d = ddata - > bank - > drvdata ;
2013-03-18 22:31:55 +01:00
u32 val ;
2017-06-15 17:46:37 +02:00
val = readl ( d - > virt_base + EINT0MASK_REG ) ;
2013-03-18 22:31:55 +01:00
if ( mask )
val | = 1 < < ddata - > eints [ irqd - > hwirq ] ;
else
val & = ~ ( 1 < < ddata - > eints [ irqd - > hwirq ] ) ;
2017-06-15 17:46:37 +02:00
writel ( val , d - > virt_base + EINT0MASK_REG ) ;
2013-03-18 22:31:55 +01:00
}
static void s3c64xx_eint0_irq_unmask ( struct irq_data * irqd )
{
s3c64xx_eint0_irq_set_mask ( irqd , false ) ;
}
static void s3c64xx_eint0_irq_mask ( struct irq_data * irqd )
{
s3c64xx_eint0_irq_set_mask ( irqd , true ) ;
}
static void s3c64xx_eint0_irq_ack ( struct irq_data * irqd )
{
struct s3c64xx_eint0_domain_data * ddata =
irq_data_get_irq_chip_data ( irqd ) ;
2017-06-15 17:46:37 +02:00
struct samsung_pinctrl_drv_data * d = ddata - > bank - > drvdata ;
2013-03-18 22:31:55 +01:00
writel ( 1 < < ddata - > eints [ irqd - > hwirq ] ,
2017-06-15 17:46:37 +02:00
d - > virt_base + EINT0PEND_REG ) ;
2013-03-18 22:31:55 +01:00
}
static int s3c64xx_eint0_irq_set_type ( struct irq_data * irqd , unsigned int type )
{
struct s3c64xx_eint0_domain_data * ddata =
irq_data_get_irq_chip_data ( irqd ) ;
struct samsung_pin_bank * bank = ddata - > bank ;
2017-06-15 17:46:37 +02:00
struct samsung_pinctrl_drv_data * d = bank - > drvdata ;
2013-03-18 22:31:55 +01:00
void __iomem * reg ;
int trigger ;
u8 shift ;
u32 val ;
trigger = s3c64xx_irq_get_trigger ( type ) ;
if ( trigger < 0 ) {
pr_err ( " unsupported external interrupt type \n " ) ;
return - EINVAL ;
}
2015-06-23 15:52:57 +02:00
s3c64xx_irq_set_handler ( irqd , type ) ;
2013-03-18 22:31:55 +01:00
/* Set up interrupt trigger */
2017-06-15 17:46:37 +02:00
reg = d - > virt_base + EINT0CON0_REG ;
2013-03-18 22:31:55 +01:00
shift = ddata - > eints [ irqd - > hwirq ] ;
if ( shift > = EINT_MAX_PER_REG ) {
reg + = 4 ;
shift - = EINT_MAX_PER_REG ;
}
shift = EINT_CON_LEN * ( shift / 2 ) ;
val = readl ( reg ) ;
val & = ~ ( EINT_CON_MASK < < shift ) ;
val | = trigger < < shift ;
writel ( val , reg ) ;
s3c64xx_irq_set_function ( d , bank , irqd - > hwirq ) ;
return 0 ;
}
/*
* irq_chip for wakeup interrupts
*/
static struct irq_chip s3c64xx_eint0_irq_chip = {
. name = " EINT0 " ,
. irq_unmask = s3c64xx_eint0_irq_unmask ,
. irq_mask = s3c64xx_eint0_irq_mask ,
. irq_ack = s3c64xx_eint0_irq_ack ,
. irq_set_type = s3c64xx_eint0_irq_set_type ,
} ;
2015-06-04 12:13:16 +08:00
static inline void s3c64xx_irq_demux_eint ( struct irq_desc * desc , u32 range )
2013-03-18 22:31:55 +01:00
{
2015-06-04 12:13:16 +08:00
struct irq_chip * chip = irq_desc_get_chip ( desc ) ;
struct s3c64xx_eint0_data * data = irq_desc_get_handler_data ( desc ) ;
2017-06-15 17:46:37 +02:00
struct samsung_pinctrl_drv_data * drvdata = data - > drvdata ;
2013-03-18 22:31:55 +01:00
unsigned int pend , mask ;
chained_irq_enter ( chip , desc ) ;
2017-06-15 17:46:37 +02:00
pend = readl ( drvdata - > virt_base + EINT0PEND_REG ) ;
mask = readl ( drvdata - > virt_base + EINT0MASK_REG ) ;
2013-03-18 22:31:55 +01:00
pend = pend & range & ~ mask ;
pend & = range ;
while ( pend ) {
2015-06-04 12:13:16 +08:00
unsigned int virq , irq ;
2013-03-18 22:31:55 +01:00
irq = fls ( pend ) - 1 ;
pend & = ~ ( 1 < < irq ) ;
virq = irq_linear_revmap ( data - > domains [ irq ] , data - > pins [ irq ] ) ;
/*
* Something must be really wrong if an unmapped EINT
* was unmasked . . .
*/
BUG_ON ( ! virq ) ;
generic_handle_irq ( virq ) ;
}
chained_irq_exit ( chip , desc ) ;
}
2015-09-14 10:42:37 +02:00
static void s3c64xx_demux_eint0_3 ( struct irq_desc * desc )
2013-03-18 22:31:55 +01:00
{
2015-06-04 12:13:16 +08:00
s3c64xx_irq_demux_eint ( desc , 0xf ) ;
2013-03-18 22:31:55 +01:00
}
2015-09-14 10:42:37 +02:00
static void s3c64xx_demux_eint4_11 ( struct irq_desc * desc )
2013-03-18 22:31:55 +01:00
{
2015-06-04 12:13:16 +08:00
s3c64xx_irq_demux_eint ( desc , 0xff0 ) ;
2013-03-18 22:31:55 +01:00
}
2015-09-14 10:42:37 +02:00
static void s3c64xx_demux_eint12_19 ( struct irq_desc * desc )
2013-03-18 22:31:55 +01:00
{
2015-06-04 12:13:16 +08:00
s3c64xx_irq_demux_eint ( desc , 0xff000 ) ;
2013-03-18 22:31:55 +01:00
}
2015-09-14 10:42:37 +02:00
static void s3c64xx_demux_eint20_27 ( struct irq_desc * desc )
2013-03-18 22:31:55 +01:00
{
2015-06-04 12:13:16 +08:00
s3c64xx_irq_demux_eint ( desc , 0xff00000 ) ;
2013-03-18 22:31:55 +01:00
}
static irq_flow_handler_t s3c64xx_eint0_handlers [ NUM_EINT0_IRQ ] = {
s3c64xx_demux_eint0_3 ,
s3c64xx_demux_eint4_11 ,
s3c64xx_demux_eint12_19 ,
s3c64xx_demux_eint20_27 ,
} ;
static int s3c64xx_eint0_irq_map ( struct irq_domain * h , unsigned int virq ,
irq_hw_number_t hw )
{
struct s3c64xx_eint0_domain_data * ddata = h - > host_data ;
struct samsung_pin_bank * bank = ddata - > bank ;
if ( ! ( bank - > eint_mask & ( 1 < < hw ) ) )
return - EINVAL ;
irq_set_chip_and_handler ( virq ,
& s3c64xx_eint0_irq_chip , handle_level_irq ) ;
irq_set_chip_data ( virq , ddata ) ;
return 0 ;
}
/*
* irq domain callbacks for external wakeup interrupt controller .
*/
static const struct irq_domain_ops s3c64xx_eint0_irqd_ops = {
. map = s3c64xx_eint0_irq_map ,
. xlate = irq_domain_xlate_twocell ,
} ;
/* list of external wakeup controllers supported */
static const struct of_device_id s3c64xx_eint0_irq_ids [ ] = {
{ . compatible = " samsung,s3c64xx-wakeup-eint " , } ,
{ }
} ;
/**
* s3c64xx_eint_eint0_init ( ) - setup handling of external wakeup interrupts .
* @ d : driver data of samsung pinctrl driver .
*/
static int s3c64xx_eint_eint0_init ( struct samsung_pinctrl_drv_data * d )
{
struct device * dev = d - > dev ;
struct device_node * eint0_np = NULL ;
struct device_node * np ;
struct samsung_pin_bank * bank ;
struct s3c64xx_eint0_data * data ;
unsigned int i ;
for_each_child_of_node ( dev - > of_node , np ) {
if ( of_match_node ( s3c64xx_eint0_irq_ids , np ) ) {
eint0_np = np ;
break ;
}
}
if ( ! eint0_np )
return - ENODEV ;
data = devm_kzalloc ( dev , sizeof ( * data ) , GFP_KERNEL ) ;
2019-08-05 18:27:09 +02:00
if ( ! data ) {
of_node_put ( eint0_np ) ;
2013-03-18 22:31:55 +01:00
return - ENOMEM ;
2019-08-05 18:27:09 +02:00
}
2013-03-18 22:31:55 +01:00
data - > drvdata = d ;
for ( i = 0 ; i < NUM_EINT0_IRQ ; + + i ) {
unsigned int irq ;
irq = irq_of_parse_and_map ( eint0_np , i ) ;
if ( ! irq ) {
dev_err ( dev , " failed to get wakeup EINT IRQ %d \n " , i ) ;
2019-08-05 18:27:09 +02:00
of_node_put ( eint0_np ) ;
2013-03-18 22:31:55 +01:00
return - ENXIO ;
}
pinctrl/samsung: Fix race in installing chained IRQ handler
Fix a race where a pending interrupt could be received and the handler
called before the handler's data has been setup, by converting to
irq_set_chained_handler_and_data().
Search and conversion was done with coccinelle:
@@
expression E1, E2, E3;
@@
(
-if (irq_set_chained_handler(E1, E3) != 0)
- BUG();
|
-irq_set_chained_handler(E1, E3);
)
-irq_set_handler_data(E1, E2);
+irq_set_chained_handler_and_data(E1, E3, E2);
@@
expression E1, E2, E3;
@@
(
-if (irq_set_chained_handler(E1, E3) != 0)
- BUG();
...
|
-irq_set_chained_handler(E1, E3);
...
)
-irq_set_handler_data(E1, E2);
+irq_set_chained_handler_and_data(E1, E3, E2);
Reported-by: Russell King <rmk+kernel@arm.linux.org.uk>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Cc: Julia Lawall <Julia.Lawall@lip6.fr>
Cc: Tomasz Figa <tomasz.figa@gmail.com>
Cc: Thomas Abraham <thomas.abraham@linaro.org>
Cc: Linus Walleij <linus.walleij@linaro.org>
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-samsung-soc@vger.kernel.org
Cc: linux-gpio@vger.kernel.org
2015-06-21 20:16:18 +02:00
irq_set_chained_handler_and_data ( irq ,
s3c64xx_eint0_handlers [ i ] ,
data ) ;
2013-03-18 22:31:55 +01:00
}
2019-08-05 18:27:09 +02:00
of_node_put ( eint0_np ) ;
2013-03-18 22:31:55 +01:00
2014-09-23 21:05:40 +02:00
bank = d - > pin_banks ;
for ( i = 0 ; i < d - > nr_banks ; + + i , + + bank ) {
2013-03-18 22:31:55 +01:00
struct s3c64xx_eint0_domain_data * ddata ;
unsigned int nr_eints ;
unsigned int mask ;
unsigned int irq ;
unsigned int pin ;
if ( bank - > eint_type ! = EINT_TYPE_WKUP )
continue ;
mask = bank - > eint_mask ;
nr_eints = fls ( mask ) ;
ddata = devm_kzalloc ( dev ,
sizeof ( * ddata ) + nr_eints , GFP_KERNEL ) ;
2017-01-19 14:48:45 +01:00
if ( ! ddata )
2013-03-18 22:31:55 +01:00
return - ENOMEM ;
ddata - > bank = bank ;
bank - > irq_domain = irq_domain_add_linear ( bank - > of_node ,
nr_eints , & s3c64xx_eint0_irqd_ops , ddata ) ;
if ( ! bank - > irq_domain ) {
dev_err ( dev , " wkup irq domain add failed \n " ) ;
return - ENXIO ;
}
irq = bank - > eint_offset ;
mask = bank - > eint_mask ;
for ( pin = 0 ; mask ; + + pin , mask > > = 1 ) {
if ( ! ( mask & 1 ) )
continue ;
data - > domains [ irq ] = bank - > irq_domain ;
data - > pins [ irq ] = pin ;
ddata - > eints [ pin ] = irq ;
+ + irq ;
}
}
return 0 ;
}
/* pin banks of s3c64xx pin-controller 0 */
2014-09-23 21:05:41 +02:00
static const struct samsung_pin_bank_data s3c64xx_pin_banks0 [ ] __initconst = {
2013-03-18 22:31:55 +01:00
PIN_BANK_4BIT_EINTG ( 8 , 0x000 , " gpa " , 0 ) ,
PIN_BANK_4BIT_EINTG ( 7 , 0x020 , " gpb " , 8 ) ,
PIN_BANK_4BIT_EINTG ( 8 , 0x040 , " gpc " , 16 ) ,
PIN_BANK_4BIT_EINTG ( 5 , 0x060 , " gpd " , 32 ) ,
PIN_BANK_4BIT ( 5 , 0x080 , " gpe " ) ,
PIN_BANK_2BIT_EINTG ( 16 , 0x0a0 , " gpf " , 48 , 0x3fff ) ,
PIN_BANK_4BIT_EINTG ( 7 , 0x0c0 , " gpg " , 64 ) ,
PIN_BANK_4BIT2_EINTG ( 10 , 0x0e0 , " gph " , 80 ) ,
PIN_BANK_2BIT ( 16 , 0x100 , " gpi " ) ,
PIN_BANK_2BIT ( 12 , 0x120 , " gpj " ) ,
PIN_BANK_4BIT2_ALIVE ( 16 , 0x800 , " gpk " ) ,
PIN_BANK_4BIT2_EINTW ( 15 , 0x810 , " gpl " , 16 , 0x7f00 ) ,
PIN_BANK_4BIT_EINTW ( 6 , 0x820 , " gpm " , 23 , 0x1f ) ,
PIN_BANK_2BIT_EINTW ( 16 , 0x830 , " gpn " , 0 ) ,
PIN_BANK_2BIT_EINTG ( 16 , 0x140 , " gpo " , 96 , 0xffff ) ,
PIN_BANK_2BIT_EINTG ( 15 , 0x160 , " gpp " , 112 , 0x7fff ) ,
PIN_BANK_2BIT_EINTG ( 9 , 0x180 , " gpq " , 128 , 0x1ff ) ,
} ;
/*
* Samsung pinctrl driver data for S3C64xx SoC . S3C64xx SoC includes
* one gpio / pin - mux / pinconfig controller .
*/
2018-02-20 19:17:51 +01:00
static const struct samsung_pin_ctrl s3c64xx_pin_ctrl [ ] __initconst = {
2013-03-18 22:31:55 +01:00
{
/* pin-controller instance 1 data */
. pin_banks = s3c64xx_pin_banks0 ,
. nr_banks = ARRAY_SIZE ( s3c64xx_pin_banks0 ) ,
. eint_gpio_init = s3c64xx_eint_gpio_init ,
. eint_wkup_init = s3c64xx_eint_eint0_init ,
} ,
} ;
2018-02-20 19:17:51 +01:00
const struct samsung_pinctrl_of_match_data s3c64xx_of_data __initconst = {
. ctrl = s3c64xx_pin_ctrl ,
. num_ctrl = ARRAY_SIZE ( s3c64xx_pin_ctrl ) ,
} ;