2020-05-28 18:27:53 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright ( C ) 2020 , Jiaxun Yang < jiaxun . yang @ flygoat . com >
* Loongson PCH MSI support
*/
# define pr_fmt(fmt) "pch-msi: " fmt
# include <linux/irqchip.h>
# include <linux/msi.h>
# include <linux/of.h>
# include <linux/of_address.h>
# include <linux/of_irq.h>
# include <linux/of_pci.h>
# include <linux/pci.h>
# include <linux/slab.h>
2022-07-20 13:51:28 +03:00
static int nr_pics ;
2020-05-28 18:27:53 +03:00
struct pch_msi_data {
struct mutex msi_map_lock ;
phys_addr_t doorbell ;
u32 irq_first ; /* The vector number that MSIs starts */
u32 num_irqs ; /* The number of vectors for MSIs */
unsigned long * msi_map ;
} ;
2022-07-20 13:51:28 +03:00
static struct fwnode_handle * pch_msi_handle [ MAX_IO_PICS ] ;
2020-05-28 18:27:53 +03:00
static void pch_msi_mask_msi_irq ( struct irq_data * d )
{
pci_msi_mask_irq ( d ) ;
irq_chip_mask_parent ( d ) ;
}
static void pch_msi_unmask_msi_irq ( struct irq_data * d )
{
irq_chip_unmask_parent ( d ) ;
pci_msi_unmask_irq ( d ) ;
}
static struct irq_chip pch_msi_irq_chip = {
. name = " PCH PCI MSI " ,
. irq_mask = pch_msi_mask_msi_irq ,
. irq_unmask = pch_msi_unmask_msi_irq ,
. irq_ack = irq_chip_ack_parent ,
. irq_set_affinity = irq_chip_set_affinity_parent ,
} ;
static int pch_msi_allocate_hwirq ( struct pch_msi_data * priv , int num_req )
{
int first ;
mutex_lock ( & priv - > msi_map_lock ) ;
first = bitmap_find_free_region ( priv - > msi_map , priv - > num_irqs ,
get_count_order ( num_req ) ) ;
if ( first < 0 ) {
mutex_unlock ( & priv - > msi_map_lock ) ;
return - ENOSPC ;
}
mutex_unlock ( & priv - > msi_map_lock ) ;
return priv - > irq_first + first ;
}
static void pch_msi_free_hwirq ( struct pch_msi_data * priv ,
int hwirq , int num_req )
{
int first = hwirq - priv - > irq_first ;
mutex_lock ( & priv - > msi_map_lock ) ;
bitmap_release_region ( priv - > msi_map , first , get_count_order ( num_req ) ) ;
mutex_unlock ( & priv - > msi_map_lock ) ;
}
static void pch_msi_compose_msi_msg ( struct irq_data * data ,
struct msi_msg * msg )
{
struct pch_msi_data * priv = irq_data_get_irq_chip_data ( data ) ;
msg - > address_hi = upper_32_bits ( priv - > doorbell ) ;
msg - > address_lo = lower_32_bits ( priv - > doorbell ) ;
msg - > data = data - > hwirq ;
}
static struct msi_domain_info pch_msi_domain_info = {
. flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
MSI_FLAG_MULTI_PCI_MSI | MSI_FLAG_PCI_MSIX ,
. chip = & pch_msi_irq_chip ,
} ;
static struct irq_chip middle_irq_chip = {
. name = " PCH MSI " ,
. irq_mask = irq_chip_mask_parent ,
. irq_unmask = irq_chip_unmask_parent ,
. irq_ack = irq_chip_ack_parent ,
. irq_set_affinity = irq_chip_set_affinity_parent ,
. irq_compose_msi_msg = pch_msi_compose_msi_msg ,
} ;
static int pch_msi_parent_domain_alloc ( struct irq_domain * domain ,
unsigned int virq , int hwirq )
{
struct irq_fwspec fwspec ;
fwspec . fwnode = domain - > parent - > fwnode ;
fwspec . param_count = 1 ;
fwspec . param [ 0 ] = hwirq ;
2020-07-07 05:12:50 +03:00
return irq_domain_alloc_irqs_parent ( domain , virq , 1 , & fwspec ) ;
2020-05-28 18:27:53 +03:00
}
static int pch_msi_middle_domain_alloc ( struct irq_domain * domain ,
unsigned int virq ,
unsigned int nr_irqs , void * args )
{
struct pch_msi_data * priv = domain - > host_data ;
int hwirq , err , i ;
hwirq = pch_msi_allocate_hwirq ( priv , nr_irqs ) ;
if ( hwirq < 0 )
return hwirq ;
for ( i = 0 ; i < nr_irqs ; i + + ) {
err = pch_msi_parent_domain_alloc ( domain , virq + i , hwirq + i ) ;
if ( err )
goto err_hwirq ;
irq_domain_set_hwirq_and_chip ( domain , virq + i , hwirq + i ,
& middle_irq_chip , priv ) ;
}
return 0 ;
err_hwirq :
pch_msi_free_hwirq ( priv , hwirq , nr_irqs ) ;
irq_domain_free_irqs_parent ( domain , virq , i - 1 ) ;
return err ;
}
static void pch_msi_middle_domain_free ( struct irq_domain * domain ,
unsigned int virq ,
unsigned int nr_irqs )
{
struct irq_data * d = irq_domain_get_irq_data ( domain , virq ) ;
struct pch_msi_data * priv = irq_data_get_irq_chip_data ( d ) ;
irq_domain_free_irqs_parent ( domain , virq , nr_irqs ) ;
pch_msi_free_hwirq ( priv , d - > hwirq , nr_irqs ) ;
}
static const struct irq_domain_ops pch_msi_middle_domain_ops = {
. alloc = pch_msi_middle_domain_alloc ,
. free = pch_msi_middle_domain_free ,
} ;
static int pch_msi_init_domains ( struct pch_msi_data * priv ,
2022-07-20 13:51:28 +03:00
struct irq_domain * parent ,
struct fwnode_handle * domain_handle )
2020-05-28 18:27:53 +03:00
{
struct irq_domain * middle_domain , * msi_domain ;
2023-02-13 13:43:00 +03:00
middle_domain = irq_domain_create_hierarchy ( parent , 0 , priv - > num_irqs ,
domain_handle ,
& pch_msi_middle_domain_ops ,
priv ) ;
2020-05-28 18:27:53 +03:00
if ( ! middle_domain ) {
pr_err ( " Failed to create the MSI middle domain \n " ) ;
return - ENOMEM ;
}
irq_domain_update_bus_token ( middle_domain , DOMAIN_BUS_NEXUS ) ;
2022-07-20 13:51:28 +03:00
msi_domain = pci_msi_create_irq_domain ( domain_handle ,
2020-05-28 18:27:53 +03:00
& pch_msi_domain_info ,
middle_domain ) ;
if ( ! msi_domain ) {
pr_err ( " Failed to create PCI MSI domain \n " ) ;
irq_domain_remove ( middle_domain ) ;
return - ENOMEM ;
}
return 0 ;
}
2022-07-20 13:51:28 +03:00
static int pch_msi_init ( phys_addr_t msg_address , int irq_base , int irq_count ,
struct irq_domain * parent_domain , struct fwnode_handle * domain_handle )
2020-05-28 18:27:53 +03:00
{
int ret ;
2022-07-20 13:51:28 +03:00
struct pch_msi_data * priv ;
2020-05-28 18:27:53 +03:00
priv = kzalloc ( sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
mutex_init ( & priv - > msi_map_lock ) ;
2022-07-20 13:51:28 +03:00
priv - > doorbell = msg_address ;
priv - > irq_first = irq_base ;
priv - > num_irqs = irq_count ;
2020-05-28 18:27:53 +03:00
2021-02-09 10:10:51 +03:00
priv - > msi_map = bitmap_zalloc ( priv - > num_irqs , GFP_KERNEL ) ;
2022-07-20 13:51:28 +03:00
if ( ! priv - > msi_map )
2020-05-28 18:27:53 +03:00
goto err_priv ;
pr_debug ( " Registering %d MSIs, starting at %d \n " ,
priv - > num_irqs , priv - > irq_first ) ;
2022-07-20 13:51:28 +03:00
ret = pch_msi_init_domains ( priv , parent_domain , domain_handle ) ;
2020-05-28 18:27:53 +03:00
if ( ret )
goto err_map ;
2022-07-20 13:51:28 +03:00
pch_msi_handle [ nr_pics + + ] = domain_handle ;
2020-05-28 18:27:53 +03:00
return 0 ;
err_map :
2021-12-26 17:46:21 +03:00
bitmap_free ( priv - > msi_map ) ;
2020-05-28 18:27:53 +03:00
err_priv :
kfree ( priv ) ;
2022-07-20 13:51:28 +03:00
return - EINVAL ;
}
# ifdef CONFIG_OF
static int pch_msi_of_init ( struct device_node * node , struct device_node * parent )
{
int err ;
int irq_base , irq_count ;
struct resource res ;
struct irq_domain * parent_domain ;
parent_domain = irq_find_host ( parent ) ;
if ( ! parent_domain ) {
pr_err ( " Failed to find the parent domain \n " ) ;
return - ENXIO ;
}
if ( of_address_to_resource ( node , 0 , & res ) ) {
pr_err ( " Failed to allocate resource \n " ) ;
return - EINVAL ;
}
if ( of_property_read_u32 ( node , " loongson,msi-base-vec " , & irq_base ) ) {
pr_err ( " Unable to parse MSI vec base \n " ) ;
return - EINVAL ;
}
if ( of_property_read_u32 ( node , " loongson,msi-num-vecs " , & irq_count ) ) {
pr_err ( " Unable to parse MSI vec number \n " ) ;
return - EINVAL ;
}
err = pch_msi_init ( res . start , irq_base , irq_count , parent_domain , of_node_to_fwnode ( node ) ) ;
if ( err < 0 )
return err ;
return 0 ;
2020-05-28 18:27:53 +03:00
}
2022-07-20 13:51:28 +03:00
IRQCHIP_DECLARE ( pch_msi , " loongson,pch-msi-1.0 " , pch_msi_of_init ) ;
# endif
# ifdef CONFIG_ACPI
struct fwnode_handle * get_pch_msi_handle ( int pci_segment )
{
int i ;
for ( i = 0 ; i < MAX_IO_PICS ; i + + ) {
if ( msi_group [ i ] . pci_segment = = pci_segment )
return pch_msi_handle [ i ] ;
}
return NULL ;
}
int __init pch_msi_acpi_init ( struct irq_domain * parent ,
struct acpi_madt_msi_pic * acpi_pchmsi )
{
int ret ;
struct fwnode_handle * domain_handle ;
2022-08-08 13:50:20 +03:00
domain_handle = irq_domain_alloc_fwnode ( & acpi_pchmsi - > msg_address ) ;
2022-07-20 13:51:28 +03:00
ret = pch_msi_init ( acpi_pchmsi - > msg_address , acpi_pchmsi - > start ,
acpi_pchmsi - > count , parent , domain_handle ) ;
if ( ret < 0 )
irq_domain_free_fwnode ( domain_handle ) ;
return ret ;
}
# endif