mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-04-13 14:22:13 -04:00
Merge branch 'pci/controller/iommu-map'
- Add host bridge .enable_device() and .disable_device() hooks for bridges
that need to configure things like Requester ID to StreamID mapping when
enabling devices (Frank Li)
- Add imx6 Requester ID to StreamID mapping configuration when enabling
devices (Frank Li)
- Extend struct pci_ecam_ops with .enable_device() and .disable_device()
hooks so drivers that use pci_host_common_probe() instead of their own
.probe() have a way to set the .enable_device() callbacks (Marc Zyngier)
- Convert pcie-apple StreamID mapping configuration from a bus notifier to
the .enable_device() and .disable_device() callbacks (Marc Zyngier)
* pci/controller/iommu-map:
PCI: apple: Convert to {en,dis}able_device() callbacks
PCI: host-generic: Allow {en,dis}able_device() to be provided via pci_ecam_ops
PCI: imx6: Add IOMMU and ITS MSI support for i.MX95
PCI: Add enable_device() and disable_device() callbacks for bridges
This commit is contained in:
@@ -55,6 +55,22 @@
|
||||
#define IMX95_PE0_GEN_CTRL_3 0x1058
|
||||
#define IMX95_PCIE_LTSSM_EN BIT(0)
|
||||
|
||||
#define IMX95_PE0_LUT_ACSCTRL 0x1008
|
||||
#define IMX95_PEO_LUT_RWA BIT(16)
|
||||
#define IMX95_PE0_LUT_ENLOC GENMASK(4, 0)
|
||||
|
||||
#define IMX95_PE0_LUT_DATA1 0x100c
|
||||
#define IMX95_PE0_LUT_VLD BIT(31)
|
||||
#define IMX95_PE0_LUT_DAC_ID GENMASK(10, 8)
|
||||
#define IMX95_PE0_LUT_STREAM_ID GENMASK(5, 0)
|
||||
|
||||
#define IMX95_PE0_LUT_DATA2 0x1010
|
||||
#define IMX95_PE0_LUT_REQID GENMASK(31, 16)
|
||||
#define IMX95_PE0_LUT_MASK GENMASK(15, 0)
|
||||
|
||||
#define IMX95_SID_MASK GENMASK(5, 0)
|
||||
#define IMX95_MAX_LUT 32
|
||||
|
||||
#define to_imx_pcie(x) dev_get_drvdata((x)->dev)
|
||||
|
||||
enum imx_pcie_variants {
|
||||
@@ -87,6 +103,7 @@ enum imx_pcie_variants {
|
||||
* workaround suspend resume on some devices which are affected by this errata.
|
||||
*/
|
||||
#define IMX_PCIE_FLAG_BROKEN_SUSPEND BIT(9)
|
||||
#define IMX_PCIE_FLAG_HAS_LUT BIT(10)
|
||||
|
||||
#define imx_check_flag(pci, val) (pci->drvdata->flags & val)
|
||||
|
||||
@@ -139,6 +156,9 @@ struct imx_pcie {
|
||||
struct device *pd_pcie_phy;
|
||||
struct phy *phy;
|
||||
const struct imx_pcie_drvdata *drvdata;
|
||||
|
||||
/* Ensure that only one device's LUT is configured at any given time */
|
||||
struct mutex lock;
|
||||
};
|
||||
|
||||
/* Parameters for the waiting for PCIe PHY PLL to lock on i.MX7 */
|
||||
@@ -930,6 +950,184 @@ static void imx_pcie_stop_link(struct dw_pcie *pci)
|
||||
imx_pcie_ltssm_disable(dev);
|
||||
}
|
||||
|
||||
static int imx_pcie_add_lut(struct imx_pcie *imx_pcie, u16 rid, u8 sid)
|
||||
{
|
||||
struct dw_pcie *pci = imx_pcie->pci;
|
||||
struct device *dev = pci->dev;
|
||||
u32 data1, data2;
|
||||
int free = -1;
|
||||
int i;
|
||||
|
||||
if (sid >= 64) {
|
||||
dev_err(dev, "Invalid SID for index %d\n", sid);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
guard(mutex)(&imx_pcie->lock);
|
||||
|
||||
/*
|
||||
* Iterate through all LUT entries to check for duplicate RID and
|
||||
* identify the first available entry. Configure this available entry
|
||||
* immediately after verification to avoid rescanning it.
|
||||
*/
|
||||
for (i = 0; i < IMX95_MAX_LUT; i++) {
|
||||
regmap_write(imx_pcie->iomuxc_gpr,
|
||||
IMX95_PE0_LUT_ACSCTRL, IMX95_PEO_LUT_RWA | i);
|
||||
regmap_read(imx_pcie->iomuxc_gpr, IMX95_PE0_LUT_DATA1, &data1);
|
||||
|
||||
if (!(data1 & IMX95_PE0_LUT_VLD)) {
|
||||
if (free < 0)
|
||||
free = i;
|
||||
continue;
|
||||
}
|
||||
|
||||
regmap_read(imx_pcie->iomuxc_gpr, IMX95_PE0_LUT_DATA2, &data2);
|
||||
|
||||
/* Do not add duplicate RID */
|
||||
if (rid == FIELD_GET(IMX95_PE0_LUT_REQID, data2)) {
|
||||
dev_warn(dev, "Existing LUT entry available for RID (%d)", rid);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (free < 0) {
|
||||
dev_err(dev, "LUT entry is not available\n");
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
data1 = FIELD_PREP(IMX95_PE0_LUT_DAC_ID, 0);
|
||||
data1 |= FIELD_PREP(IMX95_PE0_LUT_STREAM_ID, sid);
|
||||
data1 |= IMX95_PE0_LUT_VLD;
|
||||
regmap_write(imx_pcie->iomuxc_gpr, IMX95_PE0_LUT_DATA1, data1);
|
||||
|
||||
data2 = IMX95_PE0_LUT_MASK; /* Match all bits of RID */
|
||||
data2 |= FIELD_PREP(IMX95_PE0_LUT_REQID, rid);
|
||||
regmap_write(imx_pcie->iomuxc_gpr, IMX95_PE0_LUT_DATA2, data2);
|
||||
|
||||
regmap_write(imx_pcie->iomuxc_gpr, IMX95_PE0_LUT_ACSCTRL, free);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void imx_pcie_remove_lut(struct imx_pcie *imx_pcie, u16 rid)
|
||||
{
|
||||
u32 data2;
|
||||
int i;
|
||||
|
||||
guard(mutex)(&imx_pcie->lock);
|
||||
|
||||
for (i = 0; i < IMX95_MAX_LUT; i++) {
|
||||
regmap_write(imx_pcie->iomuxc_gpr,
|
||||
IMX95_PE0_LUT_ACSCTRL, IMX95_PEO_LUT_RWA | i);
|
||||
regmap_read(imx_pcie->iomuxc_gpr, IMX95_PE0_LUT_DATA2, &data2);
|
||||
if (FIELD_GET(IMX95_PE0_LUT_REQID, data2) == rid) {
|
||||
regmap_write(imx_pcie->iomuxc_gpr,
|
||||
IMX95_PE0_LUT_DATA1, 0);
|
||||
regmap_write(imx_pcie->iomuxc_gpr,
|
||||
IMX95_PE0_LUT_DATA2, 0);
|
||||
regmap_write(imx_pcie->iomuxc_gpr,
|
||||
IMX95_PE0_LUT_ACSCTRL, i);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int imx_pcie_enable_device(struct pci_host_bridge *bridge,
|
||||
struct pci_dev *pdev)
|
||||
{
|
||||
struct imx_pcie *imx_pcie = to_imx_pcie(to_dw_pcie_from_pp(bridge->sysdata));
|
||||
u32 sid_i, sid_m, rid = pci_dev_id(pdev);
|
||||
struct device_node *target;
|
||||
struct device *dev;
|
||||
int err_i, err_m;
|
||||
u32 sid = 0;
|
||||
|
||||
dev = imx_pcie->pci->dev;
|
||||
|
||||
target = NULL;
|
||||
err_i = of_map_id(dev->of_node, rid, "iommu-map", "iommu-map-mask",
|
||||
&target, &sid_i);
|
||||
if (target) {
|
||||
of_node_put(target);
|
||||
} else {
|
||||
/*
|
||||
* "target == NULL && err_i == 0" means RID out of map range.
|
||||
* Use 1:1 map RID to streamID. Hardware can't support this
|
||||
* because the streamID is only 6 bits
|
||||
*/
|
||||
err_i = -EINVAL;
|
||||
}
|
||||
|
||||
target = NULL;
|
||||
err_m = of_map_id(dev->of_node, rid, "msi-map", "msi-map-mask",
|
||||
&target, &sid_m);
|
||||
|
||||
/*
|
||||
* err_m target
|
||||
* 0 NULL RID out of range. Use 1:1 map RID to
|
||||
* streamID, Current hardware can't
|
||||
* support it, so return -EINVAL.
|
||||
* != 0 NULL msi-map does not exist, use built-in MSI
|
||||
* 0 != NULL Get correct streamID from RID
|
||||
* != 0 != NULL Invalid combination
|
||||
*/
|
||||
if (!err_m && !target)
|
||||
return -EINVAL;
|
||||
else if (target)
|
||||
of_node_put(target); /* Find streamID map entry for RID in msi-map */
|
||||
|
||||
/*
|
||||
* msi-map iommu-map
|
||||
* N N DWC MSI Ctrl
|
||||
* Y Y ITS + SMMU, require the same SID
|
||||
* Y N ITS
|
||||
* N Y DWC MSI Ctrl + SMMU
|
||||
*/
|
||||
if (err_i && err_m)
|
||||
return 0;
|
||||
|
||||
if (!err_i && !err_m) {
|
||||
/*
|
||||
* Glue Layer
|
||||
* <==========>
|
||||
* ┌─────┐ ┌──────────┐
|
||||
* │ LUT │ 6-bit streamID │ │
|
||||
* │ │─────────────────►│ MSI │
|
||||
* └─────┘ 2-bit ctrl ID │ │
|
||||
* ┌───────────►│ │
|
||||
* (i.MX95) │ │ │
|
||||
* 00 PCIe0 │ │ │
|
||||
* 01 ENETC │ │ │
|
||||
* 10 PCIe1 │ │ │
|
||||
* │ └──────────┘
|
||||
* The MSI glue layer auto adds 2 bits controller ID ahead of
|
||||
* streamID, so mask these 2 bits to get streamID. The
|
||||
* IOMMU glue layer doesn't do that.
|
||||
*/
|
||||
if (sid_i != (sid_m & IMX95_SID_MASK)) {
|
||||
dev_err(dev, "iommu-map and msi-map entries mismatch!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!err_i)
|
||||
sid = sid_i;
|
||||
else if (!err_m)
|
||||
sid = sid_m & IMX95_SID_MASK;
|
||||
|
||||
return imx_pcie_add_lut(imx_pcie, rid, sid);
|
||||
}
|
||||
|
||||
static void imx_pcie_disable_device(struct pci_host_bridge *bridge,
|
||||
struct pci_dev *pdev)
|
||||
{
|
||||
struct imx_pcie *imx_pcie;
|
||||
|
||||
imx_pcie = to_imx_pcie(to_dw_pcie_from_pp(bridge->sysdata));
|
||||
imx_pcie_remove_lut(imx_pcie, pci_dev_id(pdev));
|
||||
}
|
||||
|
||||
static int imx_pcie_host_init(struct dw_pcie_rp *pp)
|
||||
{
|
||||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||||
@@ -946,6 +1144,11 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp)
|
||||
}
|
||||
}
|
||||
|
||||
if (pp->bridge && imx_check_flag(imx_pcie, IMX_PCIE_FLAG_HAS_LUT)) {
|
||||
pp->bridge->enable_device = imx_pcie_enable_device;
|
||||
pp->bridge->disable_device = imx_pcie_disable_device;
|
||||
}
|
||||
|
||||
imx_pcie_assert_core_reset(imx_pcie);
|
||||
|
||||
if (imx_pcie->drvdata->init_phy)
|
||||
@@ -1330,6 +1533,8 @@ static int imx_pcie_probe(struct platform_device *pdev)
|
||||
imx_pcie->pci = pci;
|
||||
imx_pcie->drvdata = of_device_get_match_data(dev);
|
||||
|
||||
mutex_init(&imx_pcie->lock);
|
||||
|
||||
/* Find the PHY if one is defined, only imx7d uses it */
|
||||
np = of_parse_phandle(node, "fsl,imx7d-pcie-phy", 0);
|
||||
if (np) {
|
||||
@@ -1627,7 +1832,8 @@ static const struct imx_pcie_drvdata drvdata[] = {
|
||||
},
|
||||
[IMX95] = {
|
||||
.variant = IMX95,
|
||||
.flags = IMX_PCIE_FLAG_HAS_SERDES,
|
||||
.flags = IMX_PCIE_FLAG_HAS_SERDES |
|
||||
IMX_PCIE_FLAG_HAS_LUT,
|
||||
.clk_names = imx8mq_clks,
|
||||
.clks_cnt = ARRAY_SIZE(imx8mq_clks),
|
||||
.ltssm_off = IMX95_PE0_GEN_CTRL_3,
|
||||
|
||||
@@ -75,6 +75,8 @@ int pci_host_common_probe(struct platform_device *pdev)
|
||||
|
||||
bridge->sysdata = cfg;
|
||||
bridge->ops = (struct pci_ops *)&ops->pci_ops;
|
||||
bridge->enable_device = ops->enable_device;
|
||||
bridge->disable_device = ops->disable_device;
|
||||
bridge->msi_domain = true;
|
||||
|
||||
return pci_host_probe(bridge);
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include <linux/list.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/msi.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/pci-ecam.h>
|
||||
|
||||
@@ -667,12 +666,16 @@ static struct apple_pcie_port *apple_pcie_get_port(struct pci_dev *pdev)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int apple_pcie_add_device(struct apple_pcie_port *port,
|
||||
struct pci_dev *pdev)
|
||||
static int apple_pcie_enable_device(struct pci_host_bridge *bridge, struct pci_dev *pdev)
|
||||
{
|
||||
u32 sid, rid = pci_dev_id(pdev);
|
||||
struct apple_pcie_port *port;
|
||||
int idx, err;
|
||||
|
||||
port = apple_pcie_get_port(pdev);
|
||||
if (!port)
|
||||
return 0;
|
||||
|
||||
dev_dbg(&pdev->dev, "added to bus %s, index %d\n",
|
||||
pci_name(pdev->bus->self), port->idx);
|
||||
|
||||
@@ -698,12 +701,16 @@ static int apple_pcie_add_device(struct apple_pcie_port *port,
|
||||
return idx >= 0 ? 0 : -ENOSPC;
|
||||
}
|
||||
|
||||
static void apple_pcie_release_device(struct apple_pcie_port *port,
|
||||
struct pci_dev *pdev)
|
||||
static void apple_pcie_disable_device(struct pci_host_bridge *bridge, struct pci_dev *pdev)
|
||||
{
|
||||
struct apple_pcie_port *port;
|
||||
u32 rid = pci_dev_id(pdev);
|
||||
int idx;
|
||||
|
||||
port = apple_pcie_get_port(pdev);
|
||||
if (!port)
|
||||
return;
|
||||
|
||||
mutex_lock(&port->pcie->lock);
|
||||
|
||||
for_each_set_bit(idx, port->sid_map, port->sid_map_sz) {
|
||||
@@ -721,45 +728,6 @@ static void apple_pcie_release_device(struct apple_pcie_port *port,
|
||||
mutex_unlock(&port->pcie->lock);
|
||||
}
|
||||
|
||||
static int apple_pcie_bus_notifier(struct notifier_block *nb,
|
||||
unsigned long action,
|
||||
void *data)
|
||||
{
|
||||
struct device *dev = data;
|
||||
struct pci_dev *pdev = to_pci_dev(dev);
|
||||
struct apple_pcie_port *port;
|
||||
int err;
|
||||
|
||||
/*
|
||||
* This is a bit ugly. We assume that if we get notified for
|
||||
* any PCI device, we must be in charge of it, and that there
|
||||
* is no other PCI controller in the whole system. It probably
|
||||
* holds for now, but who knows for how long?
|
||||
*/
|
||||
port = apple_pcie_get_port(pdev);
|
||||
if (!port)
|
||||
return NOTIFY_DONE;
|
||||
|
||||
switch (action) {
|
||||
case BUS_NOTIFY_ADD_DEVICE:
|
||||
err = apple_pcie_add_device(port, pdev);
|
||||
if (err)
|
||||
return notifier_from_errno(err);
|
||||
break;
|
||||
case BUS_NOTIFY_DEL_DEVICE:
|
||||
apple_pcie_release_device(port, pdev);
|
||||
break;
|
||||
default:
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
static struct notifier_block apple_pcie_nb = {
|
||||
.notifier_call = apple_pcie_bus_notifier,
|
||||
};
|
||||
|
||||
static int apple_pcie_init(struct pci_config_window *cfg)
|
||||
{
|
||||
struct device *dev = cfg->parent;
|
||||
@@ -799,23 +767,10 @@ static int apple_pcie_init(struct pci_config_window *cfg)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int apple_pcie_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = bus_register_notifier(&pci_bus_type, &apple_pcie_nb);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = pci_host_common_probe(pdev);
|
||||
if (ret)
|
||||
bus_unregister_notifier(&pci_bus_type, &apple_pcie_nb);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct pci_ecam_ops apple_pcie_cfg_ecam_ops = {
|
||||
.init = apple_pcie_init,
|
||||
.enable_device = apple_pcie_enable_device,
|
||||
.disable_device = apple_pcie_disable_device,
|
||||
.pci_ops = {
|
||||
.map_bus = pci_ecam_map_bus,
|
||||
.read = pci_generic_config_read,
|
||||
@@ -830,7 +785,7 @@ static const struct of_device_id apple_pcie_of_match[] = {
|
||||
MODULE_DEVICE_TABLE(of, apple_pcie_of_match);
|
||||
|
||||
static struct platform_driver apple_pcie_driver = {
|
||||
.probe = apple_pcie_probe,
|
||||
.probe = pci_host_common_probe,
|
||||
.driver = {
|
||||
.name = "pcie-apple",
|
||||
.of_match_table = apple_pcie_of_match,
|
||||
|
||||
@@ -2031,6 +2031,28 @@ int __weak pcibios_enable_device(struct pci_dev *dev, int bars)
|
||||
return pci_enable_resources(dev, bars);
|
||||
}
|
||||
|
||||
static int pci_host_bridge_enable_device(struct pci_dev *dev)
|
||||
{
|
||||
struct pci_host_bridge *host_bridge = pci_find_host_bridge(dev->bus);
|
||||
int err;
|
||||
|
||||
if (host_bridge && host_bridge->enable_device) {
|
||||
err = host_bridge->enable_device(host_bridge, dev);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pci_host_bridge_disable_device(struct pci_dev *dev)
|
||||
{
|
||||
struct pci_host_bridge *host_bridge = pci_find_host_bridge(dev->bus);
|
||||
|
||||
if (host_bridge && host_bridge->disable_device)
|
||||
host_bridge->disable_device(host_bridge, dev);
|
||||
}
|
||||
|
||||
static int do_pci_enable_device(struct pci_dev *dev, int bars)
|
||||
{
|
||||
int err;
|
||||
@@ -2046,9 +2068,13 @@ static int do_pci_enable_device(struct pci_dev *dev, int bars)
|
||||
if (bridge)
|
||||
pcie_aspm_powersave_config_link(bridge);
|
||||
|
||||
err = pci_host_bridge_enable_device(dev);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = pcibios_enable_device(dev, bars);
|
||||
if (err < 0)
|
||||
return err;
|
||||
goto err_enable;
|
||||
pci_fixup_device(pci_fixup_enable, dev);
|
||||
|
||||
if (dev->msi_enabled || dev->msix_enabled)
|
||||
@@ -2063,6 +2089,12 @@ static int do_pci_enable_device(struct pci_dev *dev, int bars)
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_enable:
|
||||
pci_host_bridge_disable_device(dev);
|
||||
|
||||
return err;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2246,6 +2278,8 @@ void pci_disable_device(struct pci_dev *dev)
|
||||
if (atomic_dec_return(&dev->enable_cnt) != 0)
|
||||
return;
|
||||
|
||||
pci_host_bridge_disable_device(dev);
|
||||
|
||||
do_pci_disable_device(dev);
|
||||
|
||||
dev->is_busmaster = 0;
|
||||
|
||||
@@ -45,6 +45,10 @@ struct pci_ecam_ops {
|
||||
unsigned int bus_shift;
|
||||
struct pci_ops pci_ops;
|
||||
int (*init)(struct pci_config_window *);
|
||||
int (*enable_device)(struct pci_host_bridge *,
|
||||
struct pci_dev *);
|
||||
void (*disable_device)(struct pci_host_bridge *,
|
||||
struct pci_dev *);
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -595,6 +595,8 @@ struct pci_host_bridge {
|
||||
u8 (*swizzle_irq)(struct pci_dev *, u8 *); /* Platform IRQ swizzler */
|
||||
int (*map_irq)(const struct pci_dev *, u8, u8);
|
||||
void (*release_fn)(struct pci_host_bridge *);
|
||||
int (*enable_device)(struct pci_host_bridge *bridge, struct pci_dev *dev);
|
||||
void (*disable_device)(struct pci_host_bridge *bridge, struct pci_dev *dev);
|
||||
void *release_data;
|
||||
unsigned int ignore_reset_delay:1; /* For entire hierarchy */
|
||||
unsigned int no_ext_tags:1; /* No Extended Tags */
|
||||
|
||||
Reference in New Issue
Block a user