xref: /openbmc/linux/drivers/bus/fsl-mc/fsl-mc-msi.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
16bd067c4SBogdan Purcareata // SPDX-License-Identifier: GPL-2.0
26bd067c4SBogdan Purcareata /*
36bd067c4SBogdan Purcareata  * Freescale Management Complex (MC) bus driver MSI support
46bd067c4SBogdan Purcareata  *
56bd067c4SBogdan Purcareata  * Copyright (C) 2015-2016 Freescale Semiconductor, Inc.
66bd067c4SBogdan Purcareata  * Author: German Rivera <German.Rivera@freescale.com>
76bd067c4SBogdan Purcareata  *
86bd067c4SBogdan Purcareata  */
96bd067c4SBogdan Purcareata 
106bd067c4SBogdan Purcareata #include <linux/of_irq.h>
116bd067c4SBogdan Purcareata #include <linux/irq.h>
126bd067c4SBogdan Purcareata #include <linux/irqdomain.h>
136bd067c4SBogdan Purcareata #include <linux/msi.h>
146305166cSMakarand Pawagi #include <linux/acpi_iort.h>
156bd067c4SBogdan Purcareata 
166bd067c4SBogdan Purcareata #include "fsl-mc-private.h"
176bd067c4SBogdan Purcareata 
186bd067c4SBogdan Purcareata #ifdef GENERIC_MSI_DOMAIN_OPS
196bd067c4SBogdan Purcareata /*
206bd067c4SBogdan Purcareata  * Generate a unique ID identifying the interrupt (only used within the MSI
216bd067c4SBogdan Purcareata  * irqdomain.  Combine the icid with the interrupt index.
226bd067c4SBogdan Purcareata  */
fsl_mc_domain_calc_hwirq(struct fsl_mc_device * dev,struct msi_desc * desc)236bd067c4SBogdan Purcareata static irq_hw_number_t fsl_mc_domain_calc_hwirq(struct fsl_mc_device *dev,
246bd067c4SBogdan Purcareata 						struct msi_desc *desc)
256bd067c4SBogdan Purcareata {
266bd067c4SBogdan Purcareata 	/*
276bd067c4SBogdan Purcareata 	 * Make the base hwirq value for ICID*10000 so it is readable
286bd067c4SBogdan Purcareata 	 * as a decimal value in /proc/interrupts.
296bd067c4SBogdan Purcareata 	 */
3078ee9fb4SThomas Gleixner 	return (irq_hw_number_t)(desc->msi_index + (dev->icid * 10000));
316bd067c4SBogdan Purcareata }
326bd067c4SBogdan Purcareata 
fsl_mc_msi_set_desc(msi_alloc_info_t * arg,struct msi_desc * desc)336bd067c4SBogdan Purcareata static void fsl_mc_msi_set_desc(msi_alloc_info_t *arg,
346bd067c4SBogdan Purcareata 				struct msi_desc *desc)
356bd067c4SBogdan Purcareata {
366bd067c4SBogdan Purcareata 	arg->desc = desc;
376bd067c4SBogdan Purcareata 	arg->hwirq = fsl_mc_domain_calc_hwirq(to_fsl_mc_device(desc->dev),
386bd067c4SBogdan Purcareata 					      desc);
396bd067c4SBogdan Purcareata }
406bd067c4SBogdan Purcareata #else
416bd067c4SBogdan Purcareata #define fsl_mc_msi_set_desc NULL
426bd067c4SBogdan Purcareata #endif
436bd067c4SBogdan Purcareata 
fsl_mc_msi_update_dom_ops(struct msi_domain_info * info)446bd067c4SBogdan Purcareata static void fsl_mc_msi_update_dom_ops(struct msi_domain_info *info)
456bd067c4SBogdan Purcareata {
466bd067c4SBogdan Purcareata 	struct msi_domain_ops *ops = info->ops;
476bd067c4SBogdan Purcareata 
486bd067c4SBogdan Purcareata 	if (!ops)
496bd067c4SBogdan Purcareata 		return;
506bd067c4SBogdan Purcareata 
516bd067c4SBogdan Purcareata 	/*
526bd067c4SBogdan Purcareata 	 * set_desc should not be set by the caller
536bd067c4SBogdan Purcareata 	 */
546bd067c4SBogdan Purcareata 	if (!ops->set_desc)
556bd067c4SBogdan Purcareata 		ops->set_desc = fsl_mc_msi_set_desc;
566bd067c4SBogdan Purcareata }
576bd067c4SBogdan Purcareata 
__fsl_mc_msi_write_msg(struct fsl_mc_device * mc_bus_dev,struct fsl_mc_device_irq * mc_dev_irq,struct msi_desc * msi_desc)586bd067c4SBogdan Purcareata static void __fsl_mc_msi_write_msg(struct fsl_mc_device *mc_bus_dev,
59d86a6d47SThomas Gleixner 				   struct fsl_mc_device_irq *mc_dev_irq,
60d86a6d47SThomas Gleixner 				   struct msi_desc *msi_desc)
616bd067c4SBogdan Purcareata {
626bd067c4SBogdan Purcareata 	int error;
636bd067c4SBogdan Purcareata 	struct fsl_mc_device *owner_mc_dev = mc_dev_irq->mc_dev;
646bd067c4SBogdan Purcareata 	struct dprc_irq_cfg irq_cfg;
656bd067c4SBogdan Purcareata 
666bd067c4SBogdan Purcareata 	/*
676bd067c4SBogdan Purcareata 	 * msi_desc->msg.address is 0x0 when this function is invoked in
686bd067c4SBogdan Purcareata 	 * the free_irq() code path. In this case, for the MC, we don't
696bd067c4SBogdan Purcareata 	 * really need to "unprogram" the MSI, so we just return.
706bd067c4SBogdan Purcareata 	 */
716bd067c4SBogdan Purcareata 	if (msi_desc->msg.address_lo == 0x0 && msi_desc->msg.address_hi == 0x0)
726bd067c4SBogdan Purcareata 		return;
736bd067c4SBogdan Purcareata 
746bd067c4SBogdan Purcareata 	if (!owner_mc_dev)
756bd067c4SBogdan Purcareata 		return;
766bd067c4SBogdan Purcareata 
776bd067c4SBogdan Purcareata 	irq_cfg.paddr = ((u64)msi_desc->msg.address_hi << 32) |
786bd067c4SBogdan Purcareata 			msi_desc->msg.address_lo;
796bd067c4SBogdan Purcareata 	irq_cfg.val = msi_desc->msg.data;
806bd067c4SBogdan Purcareata 	irq_cfg.irq_num = msi_desc->irq;
816bd067c4SBogdan Purcareata 
826bd067c4SBogdan Purcareata 	if (owner_mc_dev == mc_bus_dev) {
836bd067c4SBogdan Purcareata 		/*
846bd067c4SBogdan Purcareata 		 * IRQ is for the mc_bus_dev's DPRC itself
856bd067c4SBogdan Purcareata 		 */
866bd067c4SBogdan Purcareata 		error = dprc_set_irq(mc_bus_dev->mc_io,
876bd067c4SBogdan Purcareata 				     MC_CMD_FLAG_INTR_DIS | MC_CMD_FLAG_PRI,
886bd067c4SBogdan Purcareata 				     mc_bus_dev->mc_handle,
896bd067c4SBogdan Purcareata 				     mc_dev_irq->dev_irq_index,
906bd067c4SBogdan Purcareata 				     &irq_cfg);
916bd067c4SBogdan Purcareata 		if (error < 0) {
926bd067c4SBogdan Purcareata 			dev_err(&owner_mc_dev->dev,
936bd067c4SBogdan Purcareata 				"dprc_set_irq() failed: %d\n", error);
946bd067c4SBogdan Purcareata 		}
956bd067c4SBogdan Purcareata 	} else {
966bd067c4SBogdan Purcareata 		/*
976bd067c4SBogdan Purcareata 		 * IRQ is for for a child device of mc_bus_dev
986bd067c4SBogdan Purcareata 		 */
996bd067c4SBogdan Purcareata 		error = dprc_set_obj_irq(mc_bus_dev->mc_io,
1006bd067c4SBogdan Purcareata 					 MC_CMD_FLAG_INTR_DIS | MC_CMD_FLAG_PRI,
1016bd067c4SBogdan Purcareata 					 mc_bus_dev->mc_handle,
1026bd067c4SBogdan Purcareata 					 owner_mc_dev->obj_desc.type,
1036bd067c4SBogdan Purcareata 					 owner_mc_dev->obj_desc.id,
1046bd067c4SBogdan Purcareata 					 mc_dev_irq->dev_irq_index,
1056bd067c4SBogdan Purcareata 					 &irq_cfg);
1066bd067c4SBogdan Purcareata 		if (error < 0) {
1076bd067c4SBogdan Purcareata 			dev_err(&owner_mc_dev->dev,
1086bd067c4SBogdan Purcareata 				"dprc_obj_set_irq() failed: %d\n", error);
1096bd067c4SBogdan Purcareata 		}
1106bd067c4SBogdan Purcareata 	}
1116bd067c4SBogdan Purcareata }
1126bd067c4SBogdan Purcareata 
1136bd067c4SBogdan Purcareata /*
1146bd067c4SBogdan Purcareata  * NOTE: This function is invoked with interrupts disabled
1156bd067c4SBogdan Purcareata  */
fsl_mc_msi_write_msg(struct irq_data * irq_data,struct msi_msg * msg)1166bd067c4SBogdan Purcareata static void fsl_mc_msi_write_msg(struct irq_data *irq_data,
1176bd067c4SBogdan Purcareata 				 struct msi_msg *msg)
1186bd067c4SBogdan Purcareata {
1196bd067c4SBogdan Purcareata 	struct msi_desc *msi_desc = irq_data_get_msi_desc(irq_data);
1206bd067c4SBogdan Purcareata 	struct fsl_mc_device *mc_bus_dev = to_fsl_mc_device(msi_desc->dev);
1216bd067c4SBogdan Purcareata 	struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev);
1226bd067c4SBogdan Purcareata 	struct fsl_mc_device_irq *mc_dev_irq =
12378ee9fb4SThomas Gleixner 		&mc_bus->irq_resources[msi_desc->msi_index];
1246bd067c4SBogdan Purcareata 
1256bd067c4SBogdan Purcareata 	msi_desc->msg = *msg;
1266bd067c4SBogdan Purcareata 
1276bd067c4SBogdan Purcareata 	/*
1286bd067c4SBogdan Purcareata 	 * Program the MSI (paddr, value) pair in the device:
1296bd067c4SBogdan Purcareata 	 */
130d86a6d47SThomas Gleixner 	__fsl_mc_msi_write_msg(mc_bus_dev, mc_dev_irq, msi_desc);
1316bd067c4SBogdan Purcareata }
1326bd067c4SBogdan Purcareata 
fsl_mc_msi_update_chip_ops(struct msi_domain_info * info)1336bd067c4SBogdan Purcareata static void fsl_mc_msi_update_chip_ops(struct msi_domain_info *info)
1346bd067c4SBogdan Purcareata {
1356bd067c4SBogdan Purcareata 	struct irq_chip *chip = info->chip;
1366bd067c4SBogdan Purcareata 
1376bd067c4SBogdan Purcareata 	if (!chip)
1386bd067c4SBogdan Purcareata 		return;
1396bd067c4SBogdan Purcareata 
1406bd067c4SBogdan Purcareata 	/*
1416bd067c4SBogdan Purcareata 	 * irq_write_msi_msg should not be set by the caller
1426bd067c4SBogdan Purcareata 	 */
1436bd067c4SBogdan Purcareata 	if (!chip->irq_write_msi_msg)
1446bd067c4SBogdan Purcareata 		chip->irq_write_msi_msg = fsl_mc_msi_write_msg;
1456bd067c4SBogdan Purcareata }
1466bd067c4SBogdan Purcareata 
1476bd067c4SBogdan Purcareata /**
1486bd067c4SBogdan Purcareata  * fsl_mc_msi_create_irq_domain - Create a fsl-mc MSI interrupt domain
149b4fa2e83SLee Jones  * @fwnode:	Optional firmware node of the interrupt controller
1506bd067c4SBogdan Purcareata  * @info:	MSI domain info
1516bd067c4SBogdan Purcareata  * @parent:	Parent irq domain
1526bd067c4SBogdan Purcareata  *
1536bd067c4SBogdan Purcareata  * Updates the domain and chip ops and creates a fsl-mc MSI
1546bd067c4SBogdan Purcareata  * interrupt domain.
1556bd067c4SBogdan Purcareata  *
1566bd067c4SBogdan Purcareata  * Returns:
1576bd067c4SBogdan Purcareata  * A domain pointer or NULL in case of failure.
1586bd067c4SBogdan Purcareata  */
fsl_mc_msi_create_irq_domain(struct fwnode_handle * fwnode,struct msi_domain_info * info,struct irq_domain * parent)1596bd067c4SBogdan Purcareata struct irq_domain *fsl_mc_msi_create_irq_domain(struct fwnode_handle *fwnode,
1606bd067c4SBogdan Purcareata 						struct msi_domain_info *info,
1616bd067c4SBogdan Purcareata 						struct irq_domain *parent)
1626bd067c4SBogdan Purcareata {
1636bd067c4SBogdan Purcareata 	struct irq_domain *domain;
1646bd067c4SBogdan Purcareata 
1656988e0e0SMarc Zyngier 	if (WARN_ON((info->flags & MSI_FLAG_LEVEL_CAPABLE)))
1666988e0e0SMarc Zyngier 		info->flags &= ~MSI_FLAG_LEVEL_CAPABLE;
1676bd067c4SBogdan Purcareata 	if (info->flags & MSI_FLAG_USE_DEF_DOM_OPS)
1686bd067c4SBogdan Purcareata 		fsl_mc_msi_update_dom_ops(info);
1696bd067c4SBogdan Purcareata 	if (info->flags & MSI_FLAG_USE_DEF_CHIP_OPS)
1706bd067c4SBogdan Purcareata 		fsl_mc_msi_update_chip_ops(info);
171e8604b14SThomas Gleixner 	info->flags |= MSI_FLAG_ALLOC_SIMPLE_MSI_DESCS | MSI_FLAG_FREE_MSI_DESCS;
1726bd067c4SBogdan Purcareata 
1736bd067c4SBogdan Purcareata 	domain = msi_create_irq_domain(fwnode, info, parent);
1746bd067c4SBogdan Purcareata 	if (domain)
1756bd067c4SBogdan Purcareata 		irq_domain_update_bus_token(domain, DOMAIN_BUS_FSL_MC_MSI);
1766bd067c4SBogdan Purcareata 
1776bd067c4SBogdan Purcareata 	return domain;
1786bd067c4SBogdan Purcareata }
1796bd067c4SBogdan Purcareata 
fsl_mc_find_msi_domain(struct device * dev)180998fb7baSDiana Craciun struct irq_domain *fsl_mc_find_msi_domain(struct device *dev)
1816bd067c4SBogdan Purcareata {
1826305166cSMakarand Pawagi 	struct device *root_dprc_dev;
1836305166cSMakarand Pawagi 	struct device *bus_dev;
1846305166cSMakarand Pawagi 	struct irq_domain *msi_domain;
185998fb7baSDiana Craciun 	struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev);
1866bd067c4SBogdan Purcareata 
1876305166cSMakarand Pawagi 	fsl_mc_get_root_dprc(dev, &root_dprc_dev);
1886305166cSMakarand Pawagi 	bus_dev = root_dprc_dev->parent;
1896305166cSMakarand Pawagi 
1906305166cSMakarand Pawagi 	if (bus_dev->of_node) {
1916305166cSMakarand Pawagi 		msi_domain = of_msi_map_get_device_domain(dev,
1926305166cSMakarand Pawagi 						  mc_dev->icid,
1936bd067c4SBogdan Purcareata 						  DOMAIN_BUS_FSL_MC_MSI);
1946bd067c4SBogdan Purcareata 
195998fb7baSDiana Craciun 		/*
196998fb7baSDiana Craciun 		 * if the msi-map property is missing assume that all the
197998fb7baSDiana Craciun 		 * child containers inherit the domain from the parent
198998fb7baSDiana Craciun 		 */
1996305166cSMakarand Pawagi 		if (!msi_domain)
200998fb7baSDiana Craciun 
201998fb7baSDiana Craciun 			msi_domain = of_msi_get_domain(bus_dev,
202998fb7baSDiana Craciun 						bus_dev->of_node,
203998fb7baSDiana Craciun 						DOMAIN_BUS_FSL_MC_MSI);
2046305166cSMakarand Pawagi 	} else {
2056305166cSMakarand Pawagi 		msi_domain = iort_get_device_domain(dev, mc_dev->icid,
2066305166cSMakarand Pawagi 						    DOMAIN_BUS_FSL_MC_MSI);
2076bd067c4SBogdan Purcareata 	}
2086bd067c4SBogdan Purcareata 
209998fb7baSDiana Craciun 	return msi_domain;
2106bd067c4SBogdan Purcareata }
2116bd067c4SBogdan Purcareata 
fsl_mc_msi_domain_alloc_irqs(struct device * dev,unsigned int irq_count)212e8604b14SThomas Gleixner int fsl_mc_msi_domain_alloc_irqs(struct device *dev,  unsigned int irq_count)
2136bd067c4SBogdan Purcareata {
214*46a2bc8cSThomas Gleixner 	int error = msi_setup_device_data(dev);
2156bd067c4SBogdan Purcareata 
216c7d2f89fSShin'ichiro Kawasaki 	if (error)
217c7d2f89fSShin'ichiro Kawasaki 		return error;
2186bd067c4SBogdan Purcareata 
2196bd067c4SBogdan Purcareata 	/*
2206bd067c4SBogdan Purcareata 	 * NOTE: Calling this function will trigger the invocation of the
2216bd067c4SBogdan Purcareata 	 * its_fsl_mc_msi_prepare() callback
2226bd067c4SBogdan Purcareata 	 */
223*46a2bc8cSThomas Gleixner 	error = msi_domain_alloc_irqs_range(dev, MSI_DEFAULT_DOMAIN, 0, irq_count - 1);
2246bd067c4SBogdan Purcareata 
225e8604b14SThomas Gleixner 	if (error)
2266bd067c4SBogdan Purcareata 		dev_err(dev, "Failed to allocate IRQs\n");
2276bd067c4SBogdan Purcareata 	return error;
2286bd067c4SBogdan Purcareata }
2296bd067c4SBogdan Purcareata 
fsl_mc_msi_domain_free_irqs(struct device * dev)2306bd067c4SBogdan Purcareata void fsl_mc_msi_domain_free_irqs(struct device *dev)
2316bd067c4SBogdan Purcareata {
232*46a2bc8cSThomas Gleixner 	msi_domain_free_irqs_all(dev, MSI_DEFAULT_DOMAIN);
2336bd067c4SBogdan Purcareata }
234