2022-09-22 11:12:43 -05:00
// SPDX-License-Identifier: GPL-2.0-only
/*
* Freescale MU used as MSI controller
*
* Copyright ( c ) 2018 Pengutronix , Oleksij Rempel < o . rempel @ pengutronix . de >
* Copyright 2022 NXP
* Frank Li < Frank . Li @ nxp . com >
* Peng Fan < peng . fan @ nxp . com >
*
* Based on drivers / mailbox / imx - mailbox . c
*/
# include <linux/clk.h>
# include <linux/irq.h>
# include <linux/irqchip.h>
# include <linux/irqchip/chained_irq.h>
# include <linux/irqdomain.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/msi.h>
# include <linux/of_irq.h>
# include <linux/of_platform.h>
# include <linux/pm_runtime.h>
# include <linux/pm_domain.h>
# include <linux/spinlock.h>
# define IMX_MU_CHANS 4
enum imx_mu_xcr {
IMX_MU_GIER ,
IMX_MU_GCR ,
IMX_MU_TCR ,
IMX_MU_RCR ,
IMX_MU_xCR_MAX ,
} ;
enum imx_mu_xsr {
IMX_MU_SR ,
IMX_MU_GSR ,
IMX_MU_TSR ,
IMX_MU_RSR ,
IMX_MU_xSR_MAX
} ;
enum imx_mu_type {
IMX_MU_V2 = BIT ( 1 ) ,
} ;
/* Receive Interrupt Enable */
# define IMX_MU_xCR_RIEn(data, x) ((data->cfg->type) & IMX_MU_V2 ? BIT(x) : BIT(24 + (3 - (x))))
# define IMX_MU_xSR_RFn(data, x) ((data->cfg->type) & IMX_MU_V2 ? BIT(x) : BIT(24 + (3 - (x))))
struct imx_mu_dcfg {
enum imx_mu_type type ;
u32 xTR ; /* Transmit Register0 */
u32 xRR ; /* Receive Register0 */
u32 xSR [ IMX_MU_xSR_MAX ] ; /* Status Registers */
u32 xCR [ IMX_MU_xCR_MAX ] ; /* Control Registers */
} ;
struct imx_mu_msi {
raw_spinlock_t lock ;
struct irq_domain * msi_domain ;
void __iomem * regs ;
phys_addr_t msiir_addr ;
const struct imx_mu_dcfg * cfg ;
unsigned long used ;
struct clk * clk ;
} ;
static void imx_mu_write ( struct imx_mu_msi * msi_data , u32 val , u32 offs )
{
iowrite32 ( val , msi_data - > regs + offs ) ;
}
static u32 imx_mu_read ( struct imx_mu_msi * msi_data , u32 offs )
{
return ioread32 ( msi_data - > regs + offs ) ;
}
static u32 imx_mu_xcr_rmw ( struct imx_mu_msi * msi_data , enum imx_mu_xcr type , u32 set , u32 clr )
{
unsigned long flags ;
u32 val ;
raw_spin_lock_irqsave ( & msi_data - > lock , flags ) ;
val = imx_mu_read ( msi_data , msi_data - > cfg - > xCR [ type ] ) ;
val & = ~ clr ;
val | = set ;
imx_mu_write ( msi_data , val , msi_data - > cfg - > xCR [ type ] ) ;
raw_spin_unlock_irqrestore ( & msi_data - > lock , flags ) ;
return val ;
}
static void imx_mu_msi_parent_mask_irq ( struct irq_data * data )
{
struct imx_mu_msi * msi_data = irq_data_get_irq_chip_data ( data ) ;
imx_mu_xcr_rmw ( msi_data , IMX_MU_RCR , 0 , IMX_MU_xCR_RIEn ( msi_data , data - > hwirq ) ) ;
}
static void imx_mu_msi_parent_unmask_irq ( struct irq_data * data )
{
struct imx_mu_msi * msi_data = irq_data_get_irq_chip_data ( data ) ;
imx_mu_xcr_rmw ( msi_data , IMX_MU_RCR , IMX_MU_xCR_RIEn ( msi_data , data - > hwirq ) , 0 ) ;
}
static void imx_mu_msi_parent_ack_irq ( struct irq_data * data )
{
struct imx_mu_msi * msi_data = irq_data_get_irq_chip_data ( data ) ;
imx_mu_read ( msi_data , msi_data - > cfg - > xRR + data - > hwirq * 4 ) ;
}
static struct irq_chip imx_mu_msi_irq_chip = {
. name = " MU-MSI " ,
. irq_ack = irq_chip_ack_parent ,
} ;
static struct msi_domain_ops imx_mu_msi_irq_ops = {
} ;
static struct msi_domain_info imx_mu_msi_domain_info = {
. flags = ( MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS ) ,
. ops = & imx_mu_msi_irq_ops ,
. chip = & imx_mu_msi_irq_chip ,
} ;
static void imx_mu_msi_parent_compose_msg ( struct irq_data * data ,
struct msi_msg * msg )
{
struct imx_mu_msi * msi_data = irq_data_get_irq_chip_data ( data ) ;
u64 addr = msi_data - > msiir_addr + 4 * data - > hwirq ;
msg - > address_hi = upper_32_bits ( addr ) ;
msg - > address_lo = lower_32_bits ( addr ) ;
msg - > data = data - > hwirq ;
}
static int imx_mu_msi_parent_set_affinity ( struct irq_data * irq_data ,
const struct cpumask * mask , bool force )
{
return - EINVAL ;
}
static struct irq_chip imx_mu_msi_parent_chip = {
. name = " MU " ,
. irq_mask = imx_mu_msi_parent_mask_irq ,
. irq_unmask = imx_mu_msi_parent_unmask_irq ,
. irq_ack = imx_mu_msi_parent_ack_irq ,
. irq_compose_msi_msg = imx_mu_msi_parent_compose_msg ,
. irq_set_affinity = imx_mu_msi_parent_set_affinity ,
} ;
static int imx_mu_msi_domain_irq_alloc ( struct irq_domain * domain ,
unsigned int virq ,
unsigned int nr_irqs ,
void * args )
{
struct imx_mu_msi * msi_data = domain - > host_data ;
unsigned long flags ;
int pos , err = 0 ;
WARN_ON ( nr_irqs ! = 1 ) ;
raw_spin_lock_irqsave ( & msi_data - > lock , flags ) ;
pos = find_first_zero_bit ( & msi_data - > used , IMX_MU_CHANS ) ;
if ( pos < IMX_MU_CHANS )
__set_bit ( pos , & msi_data - > used ) ;
else
err = - ENOSPC ;
raw_spin_unlock_irqrestore ( & msi_data - > lock , flags ) ;
if ( err )
return err ;
irq_domain_set_info ( domain , virq , pos ,
& imx_mu_msi_parent_chip , msi_data ,
handle_edge_irq , NULL , NULL ) ;
return 0 ;
}
static void imx_mu_msi_domain_irq_free ( struct irq_domain * domain ,
unsigned int virq , unsigned int nr_irqs )
{
struct irq_data * d = irq_domain_get_irq_data ( domain , virq ) ;
struct imx_mu_msi * msi_data = irq_data_get_irq_chip_data ( d ) ;
unsigned long flags ;
raw_spin_lock_irqsave ( & msi_data - > lock , flags ) ;
__clear_bit ( d - > hwirq , & msi_data - > used ) ;
raw_spin_unlock_irqrestore ( & msi_data - > lock , flags ) ;
}
static const struct irq_domain_ops imx_mu_msi_domain_ops = {
. alloc = imx_mu_msi_domain_irq_alloc ,
. free = imx_mu_msi_domain_irq_free ,
} ;
static void imx_mu_msi_irq_handler ( struct irq_desc * desc )
{
struct imx_mu_msi * msi_data = irq_desc_get_handler_data ( desc ) ;
struct irq_chip * chip = irq_desc_get_chip ( desc ) ;
u32 status ;
int i ;
status = imx_mu_read ( msi_data , msi_data - > cfg - > xSR [ IMX_MU_RSR ] ) ;
chained_irq_enter ( chip , desc ) ;
for ( i = 0 ; i < IMX_MU_CHANS ; i + + ) {
if ( status & IMX_MU_xSR_RFn ( msi_data , i ) )
generic_handle_domain_irq ( msi_data - > msi_domain , i ) ;
}
chained_irq_exit ( chip , desc ) ;
}
static int imx_mu_msi_domains_init ( struct imx_mu_msi * msi_data , struct device * dev )
{
struct fwnode_handle * fwnodes = dev_fwnode ( dev ) ;
struct irq_domain * parent ;
/* Initialize MSI domain parent */
parent = irq_domain_create_linear ( fwnodes ,
IMX_MU_CHANS ,
& imx_mu_msi_domain_ops ,
msi_data ) ;
if ( ! parent ) {
dev_err ( dev , " failed to create IRQ domain \n " ) ;
return - ENOMEM ;
}
irq_domain_update_bus_token ( parent , DOMAIN_BUS_NEXUS ) ;
msi_data - > msi_domain = platform_msi_create_irq_domain ( fwnodes ,
& imx_mu_msi_domain_info ,
parent ) ;
if ( ! msi_data - > msi_domain ) {
dev_err ( dev , " failed to create MSI domain \n " ) ;
irq_domain_remove ( parent ) ;
return - ENOMEM ;
}
irq_domain_set_pm_device ( msi_data - > msi_domain , dev ) ;
return 0 ;
}
/* Register offset of different version MU IP */
static const struct imx_mu_dcfg imx_mu_cfg_imx6sx = {
. type = 0 ,
. xTR = 0x0 ,
. xRR = 0x10 ,
. xSR = {
[ IMX_MU_SR ] = 0x20 ,
[ IMX_MU_GSR ] = 0x20 ,
[ IMX_MU_TSR ] = 0x20 ,
[ IMX_MU_RSR ] = 0x20 ,
} ,
. xCR = {
[ IMX_MU_GIER ] = 0x24 ,
[ IMX_MU_GCR ] = 0x24 ,
[ IMX_MU_TCR ] = 0x24 ,
[ IMX_MU_RCR ] = 0x24 ,
} ,
} ;
static const struct imx_mu_dcfg imx_mu_cfg_imx7ulp = {
. type = 0 ,
. xTR = 0x20 ,
. xRR = 0x40 ,
. xSR = {
[ IMX_MU_SR ] = 0x60 ,
[ IMX_MU_GSR ] = 0x60 ,
[ IMX_MU_TSR ] = 0x60 ,
[ IMX_MU_RSR ] = 0x60 ,
} ,
. xCR = {
[ IMX_MU_GIER ] = 0x64 ,
[ IMX_MU_GCR ] = 0x64 ,
[ IMX_MU_TCR ] = 0x64 ,
[ IMX_MU_RCR ] = 0x64 ,
} ,
} ;
static const struct imx_mu_dcfg imx_mu_cfg_imx8ulp = {
. type = IMX_MU_V2 ,
. xTR = 0x200 ,
. xRR = 0x280 ,
. xSR = {
[ IMX_MU_SR ] = 0xC ,
[ IMX_MU_GSR ] = 0x118 ,
2022-10-04 15:24:14 -05:00
[ IMX_MU_TSR ] = 0x124 ,
2022-09-22 11:12:43 -05:00
[ IMX_MU_RSR ] = 0x12C ,
} ,
. xCR = {
[ IMX_MU_GIER ] = 0x110 ,
[ IMX_MU_GCR ] = 0x114 ,
[ IMX_MU_TCR ] = 0x120 ,
[ IMX_MU_RCR ] = 0x128
} ,
} ;
static int __init imx_mu_of_init ( struct device_node * dn ,
struct device_node * parent ,
const struct imx_mu_dcfg * cfg )
{
struct platform_device * pdev = of_find_device_by_node ( dn ) ;
struct device_link * pd_link_a ;
struct device_link * pd_link_b ;
struct imx_mu_msi * msi_data ;
struct resource * res ;
struct device * pd_a ;
struct device * pd_b ;
struct device * dev ;
int ret ;
int irq ;
dev = & pdev - > dev ;
msi_data = devm_kzalloc ( & pdev - > dev , sizeof ( * msi_data ) , GFP_KERNEL ) ;
if ( ! msi_data )
return - ENOMEM ;
msi_data - > cfg = cfg ;
msi_data - > regs = devm_platform_ioremap_resource_byname ( pdev , " processor-a-side " ) ;
if ( IS_ERR ( msi_data - > regs ) ) {
dev_err ( & pdev - > dev , " failed to initialize 'regs' \n " ) ;
return PTR_ERR ( msi_data - > regs ) ;
}
res = platform_get_resource_byname ( pdev , IORESOURCE_MEM , " processor-b-side " ) ;
if ( ! res )
return - EIO ;
msi_data - > msiir_addr = res - > start + msi_data - > cfg - > xTR ;
irq = platform_get_irq ( pdev , 0 ) ;
if ( irq < = 0 )
return - ENODEV ;
platform_set_drvdata ( pdev , msi_data ) ;
msi_data - > clk = devm_clk_get ( dev , NULL ) ;
if ( IS_ERR ( msi_data - > clk ) )
return PTR_ERR ( msi_data - > clk ) ;
pd_a = dev_pm_domain_attach_by_name ( dev , " processor-a-side " ) ;
if ( IS_ERR ( pd_a ) )
return PTR_ERR ( pd_a ) ;
pd_b = dev_pm_domain_attach_by_name ( dev , " processor-b-side " ) ;
if ( IS_ERR ( pd_b ) )
return PTR_ERR ( pd_b ) ;
pd_link_a = device_link_add ( dev , pd_a ,
DL_FLAG_STATELESS |
DL_FLAG_PM_RUNTIME |
DL_FLAG_RPM_ACTIVE ) ;
if ( ! pd_link_a ) {
dev_err ( dev , " Failed to add device_link to mu a. \n " ) ;
goto err_pd_a ;
}
pd_link_b = device_link_add ( dev , pd_b ,
DL_FLAG_STATELESS |
DL_FLAG_PM_RUNTIME |
DL_FLAG_RPM_ACTIVE ) ;
if ( ! pd_link_b ) {
dev_err ( dev , " Failed to add device_link to mu a. \n " ) ;
goto err_pd_b ;
}
ret = imx_mu_msi_domains_init ( msi_data , dev ) ;
if ( ret )
goto err_dm_init ;
pm_runtime_enable ( dev ) ;
irq_set_chained_handler_and_data ( irq ,
imx_mu_msi_irq_handler ,
msi_data ) ;
return 0 ;
err_dm_init :
device_link_remove ( dev , pd_b ) ;
err_pd_b :
device_link_remove ( dev , pd_a ) ;
err_pd_a :
return - EINVAL ;
}
static int __maybe_unused imx_mu_runtime_suspend ( struct device * dev )
{
struct imx_mu_msi * priv = dev_get_drvdata ( dev ) ;
clk_disable_unprepare ( priv - > clk ) ;
return 0 ;
}
static int __maybe_unused imx_mu_runtime_resume ( struct device * dev )
{
struct imx_mu_msi * priv = dev_get_drvdata ( dev ) ;
int ret ;
ret = clk_prepare_enable ( priv - > clk ) ;
if ( ret )
dev_err ( dev , " failed to enable clock \n " ) ;
return ret ;
}
static const struct dev_pm_ops imx_mu_pm_ops = {
SET_RUNTIME_PM_OPS ( imx_mu_runtime_suspend ,
imx_mu_runtime_resume , NULL )
} ;
static int __init imx_mu_imx7ulp_of_init ( struct device_node * dn ,
struct device_node * parent )
{
return imx_mu_of_init ( dn , parent , & imx_mu_cfg_imx7ulp ) ;
}
static int __init imx_mu_imx6sx_of_init ( struct device_node * dn ,
struct device_node * parent )
{
return imx_mu_of_init ( dn , parent , & imx_mu_cfg_imx6sx ) ;
}
static int __init imx_mu_imx8ulp_of_init ( struct device_node * dn ,
struct device_node * parent )
{
return imx_mu_of_init ( dn , parent , & imx_mu_cfg_imx8ulp ) ;
}
IRQCHIP_PLATFORM_DRIVER_BEGIN ( imx_mu_msi )
IRQCHIP_MATCH ( " fsl,imx7ulp-mu-msi " , imx_mu_imx7ulp_of_init )
IRQCHIP_MATCH ( " fsl,imx6sx-mu-msi " , imx_mu_imx6sx_of_init )
IRQCHIP_MATCH ( " fsl,imx8ulp-mu-msi " , imx_mu_imx8ulp_of_init )
IRQCHIP_PLATFORM_DRIVER_END ( imx_mu_msi , . pm = & imx_mu_pm_ops )
MODULE_AUTHOR ( " Frank Li <Frank.Li@nxp.com> " ) ;
MODULE_DESCRIPTION ( " Freescale MU MSI controller driver " ) ;
MODULE_LICENSE ( " GPL " ) ;