@ -16,13 +16,39 @@
* along with this program . If not , see < http : //www.gnu.org/licenses/>.
*/
# include <linux/interrupt.h>
# include <linux/irqchip.h>
# include <linux/module.h>
# include <linux/msi.h>
# include <linux/of_address.h>
# include <linux/of_irq.h>
# include <linux/of_platform.h>
# include <linux/platform_device.h>
# include <linux/slab.h>
/* Interrupt numbers per mbigen node supported */
# define IRQS_PER_MBIGEN_NODE 128
/* 64 irqs (Pin0-pin63) are reserved for each mbigen chip */
# define RESERVED_IRQ_PER_MBIGEN_CHIP 64
/* The maximum IRQ pin number of mbigen chip(start from 0) */
# define MAXIMUM_IRQ_PIN_NUM 1407
/**
* In mbigen vector register
* bit [ 21 : 12 ] : event id value
* bit [ 11 : 0 ] : device id
*/
# define IRQ_EVENT_ID_SHIFT 12
# define IRQ_EVENT_ID_MASK 0x3ff
/* register range of each mbigen node */
# define MBIGEN_NODE_OFFSET 0x1000
/* offset of vector register in mbigen node */
# define REG_MBIGEN_VEC_OFFSET 0x200
/**
* struct mbigen_device - holds the information of mbigen device .
*
@ -34,10 +60,107 @@ struct mbigen_device {
void __iomem * base ;
} ;
static inline unsigned int get_mbigen_vec_reg ( irq_hw_number_t hwirq )
{
unsigned int nid , pin ;
hwirq - = RESERVED_IRQ_PER_MBIGEN_CHIP ;
nid = hwirq / IRQS_PER_MBIGEN_NODE + 1 ;
pin = hwirq % IRQS_PER_MBIGEN_NODE ;
return pin * 4 + nid * MBIGEN_NODE_OFFSET
+ REG_MBIGEN_VEC_OFFSET ;
}
static struct irq_chip mbigen_irq_chip = {
. name = " mbigen-v2 " ,
} ;
static void mbigen_write_msg ( struct msi_desc * desc , struct msi_msg * msg )
{
struct irq_data * d = irq_get_irq_data ( desc - > irq ) ;
void __iomem * base = d - > chip_data ;
u32 val ;
base + = get_mbigen_vec_reg ( d - > hwirq ) ;
val = readl_relaxed ( base ) ;
val & = ~ ( IRQ_EVENT_ID_MASK < < IRQ_EVENT_ID_SHIFT ) ;
val | = ( msg - > data < < IRQ_EVENT_ID_SHIFT ) ;
/* The address of doorbell is encoded in mbigen register by default
* So , we don ' t need to program the doorbell address at here
*/
writel_relaxed ( val , base ) ;
}
static int mbigen_domain_translate ( struct irq_domain * d ,
struct irq_fwspec * fwspec ,
unsigned long * hwirq ,
unsigned int * type )
{
if ( is_of_node ( fwspec - > fwnode ) ) {
if ( fwspec - > param_count ! = 2 )
return - EINVAL ;
if ( ( fwspec - > param [ 0 ] > MAXIMUM_IRQ_PIN_NUM ) | |
( fwspec - > param [ 0 ] < RESERVED_IRQ_PER_MBIGEN_CHIP ) )
return - EINVAL ;
else
* hwirq = fwspec - > param [ 0 ] ;
/* If there is no valid irq type, just use the default type */
if ( ( fwspec - > param [ 1 ] = = IRQ_TYPE_EDGE_RISING ) | |
( fwspec - > param [ 1 ] = = IRQ_TYPE_LEVEL_HIGH ) )
* type = fwspec - > param [ 1 ] ;
else
return - EINVAL ;
return 0 ;
}
return - EINVAL ;
}
static int mbigen_irq_domain_alloc ( struct irq_domain * domain ,
unsigned int virq ,
unsigned int nr_irqs ,
void * args )
{
struct irq_fwspec * fwspec = args ;
irq_hw_number_t hwirq ;
unsigned int type ;
struct mbigen_device * mgn_chip ;
int i , err ;
err = mbigen_domain_translate ( domain , fwspec , & hwirq , & type ) ;
if ( err )
return err ;
err = platform_msi_domain_alloc ( domain , virq , nr_irqs ) ;
if ( err )
return err ;
mgn_chip = platform_msi_get_host_data ( domain ) ;
for ( i = 0 ; i < nr_irqs ; i + + )
irq_domain_set_hwirq_and_chip ( domain , virq + i , hwirq + i ,
& mbigen_irq_chip , mgn_chip - > base ) ;
return 0 ;
}
static struct irq_domain_ops mbigen_domain_ops = {
. translate = mbigen_domain_translate ,
. alloc = mbigen_irq_domain_alloc ,
. free = irq_domain_free_irqs_common ,
} ;
static int mbigen_device_probe ( struct platform_device * pdev )
{
struct mbigen_device * mgn_chip ;
struct resource * res ;
struct irq_domain * domain ;
u32 num_pins ;
mgn_chip = devm_kzalloc ( & pdev - > dev , sizeof ( * mgn_chip ) , GFP_KERNEL ) ;
if ( ! mgn_chip )
@ -50,8 +173,23 @@ static int mbigen_device_probe(struct platform_device *pdev)
if ( IS_ERR ( mgn_chip - > base ) )
return PTR_ERR ( mgn_chip - > base ) ;
if ( of_property_read_u32 ( pdev - > dev . of_node , " num-pins " , & num_pins ) < 0 ) {
dev_err ( & pdev - > dev , " No num-pins property \n " ) ;
return - EINVAL ;
}
domain = platform_msi_create_device_domain ( & pdev - > dev , num_pins ,
mbigen_write_msg ,
& mbigen_domain_ops ,
mgn_chip ) ;
if ( ! domain )
return - ENOMEM ;
platform_set_drvdata ( pdev , mgn_chip ) ;
dev_info ( & pdev - > dev , " Allocated %d MSIs \n " , num_pins ) ;
return 0 ;
}