162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Freescale Management Complex (MC) bus driver MSI support 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2015-2016 Freescale Semiconductor, Inc. 662306a36Sopenharmony_ci * Author: German Rivera <German.Rivera@freescale.com> 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/of_irq.h> 1162306a36Sopenharmony_ci#include <linux/irq.h> 1262306a36Sopenharmony_ci#include <linux/irqdomain.h> 1362306a36Sopenharmony_ci#include <linux/msi.h> 1462306a36Sopenharmony_ci#include <linux/acpi_iort.h> 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#include "fsl-mc-private.h" 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#ifdef GENERIC_MSI_DOMAIN_OPS 1962306a36Sopenharmony_ci/* 2062306a36Sopenharmony_ci * Generate a unique ID identifying the interrupt (only used within the MSI 2162306a36Sopenharmony_ci * irqdomain. Combine the icid with the interrupt index. 2262306a36Sopenharmony_ci */ 2362306a36Sopenharmony_cistatic irq_hw_number_t fsl_mc_domain_calc_hwirq(struct fsl_mc_device *dev, 2462306a36Sopenharmony_ci struct msi_desc *desc) 2562306a36Sopenharmony_ci{ 2662306a36Sopenharmony_ci /* 2762306a36Sopenharmony_ci * Make the base hwirq value for ICID*10000 so it is readable 2862306a36Sopenharmony_ci * as a decimal value in /proc/interrupts. 2962306a36Sopenharmony_ci */ 3062306a36Sopenharmony_ci return (irq_hw_number_t)(desc->msi_index + (dev->icid * 10000)); 3162306a36Sopenharmony_ci} 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_cistatic void fsl_mc_msi_set_desc(msi_alloc_info_t *arg, 3462306a36Sopenharmony_ci struct msi_desc *desc) 3562306a36Sopenharmony_ci{ 3662306a36Sopenharmony_ci arg->desc = desc; 3762306a36Sopenharmony_ci arg->hwirq = fsl_mc_domain_calc_hwirq(to_fsl_mc_device(desc->dev), 3862306a36Sopenharmony_ci desc); 3962306a36Sopenharmony_ci} 4062306a36Sopenharmony_ci#else 4162306a36Sopenharmony_ci#define fsl_mc_msi_set_desc NULL 4262306a36Sopenharmony_ci#endif 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_cistatic void fsl_mc_msi_update_dom_ops(struct msi_domain_info *info) 4562306a36Sopenharmony_ci{ 4662306a36Sopenharmony_ci struct msi_domain_ops *ops = info->ops; 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci if (!ops) 4962306a36Sopenharmony_ci return; 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci /* 5262306a36Sopenharmony_ci * set_desc should not be set by the caller 5362306a36Sopenharmony_ci */ 5462306a36Sopenharmony_ci if (!ops->set_desc) 5562306a36Sopenharmony_ci ops->set_desc = fsl_mc_msi_set_desc; 5662306a36Sopenharmony_ci} 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_cistatic void __fsl_mc_msi_write_msg(struct fsl_mc_device *mc_bus_dev, 5962306a36Sopenharmony_ci struct fsl_mc_device_irq *mc_dev_irq, 6062306a36Sopenharmony_ci struct msi_desc *msi_desc) 6162306a36Sopenharmony_ci{ 6262306a36Sopenharmony_ci int error; 6362306a36Sopenharmony_ci struct fsl_mc_device *owner_mc_dev = mc_dev_irq->mc_dev; 6462306a36Sopenharmony_ci struct dprc_irq_cfg irq_cfg; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci /* 6762306a36Sopenharmony_ci * msi_desc->msg.address is 0x0 when this function is invoked in 6862306a36Sopenharmony_ci * the free_irq() code path. In this case, for the MC, we don't 6962306a36Sopenharmony_ci * really need to "unprogram" the MSI, so we just return. 7062306a36Sopenharmony_ci */ 7162306a36Sopenharmony_ci if (msi_desc->msg.address_lo == 0x0 && msi_desc->msg.address_hi == 0x0) 7262306a36Sopenharmony_ci return; 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci if (!owner_mc_dev) 7562306a36Sopenharmony_ci return; 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci irq_cfg.paddr = ((u64)msi_desc->msg.address_hi << 32) | 7862306a36Sopenharmony_ci msi_desc->msg.address_lo; 7962306a36Sopenharmony_ci irq_cfg.val = msi_desc->msg.data; 8062306a36Sopenharmony_ci irq_cfg.irq_num = msi_desc->irq; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci if (owner_mc_dev == mc_bus_dev) { 8362306a36Sopenharmony_ci /* 8462306a36Sopenharmony_ci * IRQ is for the mc_bus_dev's DPRC itself 8562306a36Sopenharmony_ci */ 8662306a36Sopenharmony_ci error = dprc_set_irq(mc_bus_dev->mc_io, 8762306a36Sopenharmony_ci MC_CMD_FLAG_INTR_DIS | MC_CMD_FLAG_PRI, 8862306a36Sopenharmony_ci mc_bus_dev->mc_handle, 8962306a36Sopenharmony_ci mc_dev_irq->dev_irq_index, 9062306a36Sopenharmony_ci &irq_cfg); 9162306a36Sopenharmony_ci if (error < 0) { 9262306a36Sopenharmony_ci dev_err(&owner_mc_dev->dev, 9362306a36Sopenharmony_ci "dprc_set_irq() failed: %d\n", error); 9462306a36Sopenharmony_ci } 9562306a36Sopenharmony_ci } else { 9662306a36Sopenharmony_ci /* 9762306a36Sopenharmony_ci * IRQ is for for a child device of mc_bus_dev 9862306a36Sopenharmony_ci */ 9962306a36Sopenharmony_ci error = dprc_set_obj_irq(mc_bus_dev->mc_io, 10062306a36Sopenharmony_ci MC_CMD_FLAG_INTR_DIS | MC_CMD_FLAG_PRI, 10162306a36Sopenharmony_ci mc_bus_dev->mc_handle, 10262306a36Sopenharmony_ci owner_mc_dev->obj_desc.type, 10362306a36Sopenharmony_ci owner_mc_dev->obj_desc.id, 10462306a36Sopenharmony_ci mc_dev_irq->dev_irq_index, 10562306a36Sopenharmony_ci &irq_cfg); 10662306a36Sopenharmony_ci if (error < 0) { 10762306a36Sopenharmony_ci dev_err(&owner_mc_dev->dev, 10862306a36Sopenharmony_ci "dprc_obj_set_irq() failed: %d\n", error); 10962306a36Sopenharmony_ci } 11062306a36Sopenharmony_ci } 11162306a36Sopenharmony_ci} 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci/* 11462306a36Sopenharmony_ci * NOTE: This function is invoked with interrupts disabled 11562306a36Sopenharmony_ci */ 11662306a36Sopenharmony_cistatic void fsl_mc_msi_write_msg(struct irq_data *irq_data, 11762306a36Sopenharmony_ci struct msi_msg *msg) 11862306a36Sopenharmony_ci{ 11962306a36Sopenharmony_ci struct msi_desc *msi_desc = irq_data_get_msi_desc(irq_data); 12062306a36Sopenharmony_ci struct fsl_mc_device *mc_bus_dev = to_fsl_mc_device(msi_desc->dev); 12162306a36Sopenharmony_ci struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev); 12262306a36Sopenharmony_ci struct fsl_mc_device_irq *mc_dev_irq = 12362306a36Sopenharmony_ci &mc_bus->irq_resources[msi_desc->msi_index]; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci msi_desc->msg = *msg; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci /* 12862306a36Sopenharmony_ci * Program the MSI (paddr, value) pair in the device: 12962306a36Sopenharmony_ci */ 13062306a36Sopenharmony_ci __fsl_mc_msi_write_msg(mc_bus_dev, mc_dev_irq, msi_desc); 13162306a36Sopenharmony_ci} 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_cistatic void fsl_mc_msi_update_chip_ops(struct msi_domain_info *info) 13462306a36Sopenharmony_ci{ 13562306a36Sopenharmony_ci struct irq_chip *chip = info->chip; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci if (!chip) 13862306a36Sopenharmony_ci return; 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci /* 14162306a36Sopenharmony_ci * irq_write_msi_msg should not be set by the caller 14262306a36Sopenharmony_ci */ 14362306a36Sopenharmony_ci if (!chip->irq_write_msi_msg) 14462306a36Sopenharmony_ci chip->irq_write_msi_msg = fsl_mc_msi_write_msg; 14562306a36Sopenharmony_ci} 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci/** 14862306a36Sopenharmony_ci * fsl_mc_msi_create_irq_domain - Create a fsl-mc MSI interrupt domain 14962306a36Sopenharmony_ci * @fwnode: Optional firmware node of the interrupt controller 15062306a36Sopenharmony_ci * @info: MSI domain info 15162306a36Sopenharmony_ci * @parent: Parent irq domain 15262306a36Sopenharmony_ci * 15362306a36Sopenharmony_ci * Updates the domain and chip ops and creates a fsl-mc MSI 15462306a36Sopenharmony_ci * interrupt domain. 15562306a36Sopenharmony_ci * 15662306a36Sopenharmony_ci * Returns: 15762306a36Sopenharmony_ci * A domain pointer or NULL in case of failure. 15862306a36Sopenharmony_ci */ 15962306a36Sopenharmony_cistruct irq_domain *fsl_mc_msi_create_irq_domain(struct fwnode_handle *fwnode, 16062306a36Sopenharmony_ci struct msi_domain_info *info, 16162306a36Sopenharmony_ci struct irq_domain *parent) 16262306a36Sopenharmony_ci{ 16362306a36Sopenharmony_ci struct irq_domain *domain; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci if (WARN_ON((info->flags & MSI_FLAG_LEVEL_CAPABLE))) 16662306a36Sopenharmony_ci info->flags &= ~MSI_FLAG_LEVEL_CAPABLE; 16762306a36Sopenharmony_ci if (info->flags & MSI_FLAG_USE_DEF_DOM_OPS) 16862306a36Sopenharmony_ci fsl_mc_msi_update_dom_ops(info); 16962306a36Sopenharmony_ci if (info->flags & MSI_FLAG_USE_DEF_CHIP_OPS) 17062306a36Sopenharmony_ci fsl_mc_msi_update_chip_ops(info); 17162306a36Sopenharmony_ci info->flags |= MSI_FLAG_ALLOC_SIMPLE_MSI_DESCS | MSI_FLAG_FREE_MSI_DESCS; 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci domain = msi_create_irq_domain(fwnode, info, parent); 17462306a36Sopenharmony_ci if (domain) 17562306a36Sopenharmony_ci irq_domain_update_bus_token(domain, DOMAIN_BUS_FSL_MC_MSI); 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci return domain; 17862306a36Sopenharmony_ci} 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_cistruct irq_domain *fsl_mc_find_msi_domain(struct device *dev) 18162306a36Sopenharmony_ci{ 18262306a36Sopenharmony_ci struct device *root_dprc_dev; 18362306a36Sopenharmony_ci struct device *bus_dev; 18462306a36Sopenharmony_ci struct irq_domain *msi_domain; 18562306a36Sopenharmony_ci struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci fsl_mc_get_root_dprc(dev, &root_dprc_dev); 18862306a36Sopenharmony_ci bus_dev = root_dprc_dev->parent; 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci if (bus_dev->of_node) { 19162306a36Sopenharmony_ci msi_domain = of_msi_map_get_device_domain(dev, 19262306a36Sopenharmony_ci mc_dev->icid, 19362306a36Sopenharmony_ci DOMAIN_BUS_FSL_MC_MSI); 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci /* 19662306a36Sopenharmony_ci * if the msi-map property is missing assume that all the 19762306a36Sopenharmony_ci * child containers inherit the domain from the parent 19862306a36Sopenharmony_ci */ 19962306a36Sopenharmony_ci if (!msi_domain) 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci msi_domain = of_msi_get_domain(bus_dev, 20262306a36Sopenharmony_ci bus_dev->of_node, 20362306a36Sopenharmony_ci DOMAIN_BUS_FSL_MC_MSI); 20462306a36Sopenharmony_ci } else { 20562306a36Sopenharmony_ci msi_domain = iort_get_device_domain(dev, mc_dev->icid, 20662306a36Sopenharmony_ci DOMAIN_BUS_FSL_MC_MSI); 20762306a36Sopenharmony_ci } 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci return msi_domain; 21062306a36Sopenharmony_ci} 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ciint fsl_mc_msi_domain_alloc_irqs(struct device *dev, unsigned int irq_count) 21362306a36Sopenharmony_ci{ 21462306a36Sopenharmony_ci int error = msi_setup_device_data(dev); 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci if (error) 21762306a36Sopenharmony_ci return error; 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci /* 22062306a36Sopenharmony_ci * NOTE: Calling this function will trigger the invocation of the 22162306a36Sopenharmony_ci * its_fsl_mc_msi_prepare() callback 22262306a36Sopenharmony_ci */ 22362306a36Sopenharmony_ci error = msi_domain_alloc_irqs_range(dev, MSI_DEFAULT_DOMAIN, 0, irq_count - 1); 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci if (error) 22662306a36Sopenharmony_ci dev_err(dev, "Failed to allocate IRQs\n"); 22762306a36Sopenharmony_ci return error; 22862306a36Sopenharmony_ci} 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_civoid fsl_mc_msi_domain_free_irqs(struct device *dev) 23162306a36Sopenharmony_ci{ 23262306a36Sopenharmony_ci msi_domain_free_irqs_all(dev, MSI_DEFAULT_DOMAIN); 23362306a36Sopenharmony_ci} 234