2022-03-28 20:38:11

by Serge Semin

[permalink] [raw]
Subject: [PATCH 00/16] PCI: dwc: Add dma-ranges/YAML-schema/Baikal-T1 support

This patchset is a third one in the series created in the framework of
my Baikal-T1 PCIe/eDMA-related work:

[1: In-progress] clk: Baikal-T1 DDR/PCIe resets and some xGMAC fixes
Link: --submitted--
[2: In-progress] PCI: dwc: Various fixes and cleanups
Link: --submitted--
[3: In-progress] PCI: dwc: Add dma-ranges/YAML-schema/Baikal-T1 support
Link: --you are looking at it--
[4: Stalling] dmaengine: dw-edma: Add RP/EP local DMA controllers support
Link: --being submitted afterwards--

Note it is very recommended to merge the patchsets in the same order as
they are listed in the set above in order to have them applied smoothly.
Nothing prevents them from being reviewed synchronously though.

This series is about adding new features to the DW PCIe Host/End-point
driver. First of all we start from splitting up the DT-bindings into
common properties schema and generic DW PCIe bindings definition. It's
done to support the generic DW PCIe Host/End-point available platforms
with adding a common YAML-schema to be reused by the platform-specific DW
PCIe bindings. @Rob could you please take a look at that patch? I've got a
problem with dt_bindings_check-ing the schema which is likely caused by
the dt-schema parser misbehaviour. After that we suggest to add a more
verbose link-up log message. Really printing link generation and width
would be much more informative than just "link up". Then a series of
IP-core version-related patches go, like using a native FourCC version
representation, adding the IP-core auto-detection, adding better
structured IP-core version/type interface. After that the
platform-specific host de-initialization method is introduced. A series of
iATU optimizations, cleanups and new features goes afterwards. In
particular we suggest to drop some redundant enumerations, add iATU
regions size detection procedure and then use the regions parameters to
verify the requested by the platform iATU ranges/dma-ranges settings.
After that the dma-ranges property support is added for the DW PCIe Host
controllers. Then a structured set of the DW PCIe RP/EP specific clocks
and resets names/IDs is introduced so to be re-used by the generic and new
platforms. Note it is fully coherent with the DW PCIe controller manuals
(see the patch log for details). Also note the patch doesn't affect the
already available DW PCIe platform-specific code since it would be too
risky for my to do the corresponding conversion, but the maintainers are
welcome to do that. Finally at the series closure we introduce the
Baikal-T1 PCIe interface support, which uses all the recently added
features including the set of the generic clocks and resets names.

Signed-off-by: Serge Semin <[email protected]>
Cc: Alexey Malahov <[email protected]>
Cc: Pavel Parkhomenko <[email protected]>
Cc: Lorenzo Pieralisi <[email protected]>
Cc: Rob Herring <[email protected]>
Cc: "Krzysztof WilczyƄski" <[email protected]>
Cc: Frank Li <[email protected]>
Cc: Manivannan Sadhasivam <[email protected]>
Cc: Rob Herring <[email protected]>
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]

Serge Semin (16):
dt-bindings: PCI: dwc: Define generic and native DT bindings
dt-bindings: PCI: dwc: Add Baikal-T1 PCIe Root Port bindings
PCI: dwc: Add more verbose link-up message
PCI: dwc: Convert to using native IP-core versions representation
PCI: dwc: Add IP-core version detection procedure
PCI: dwc: Introduce Synopsys IP-core versions/types interface
PCI: dwc: Add host de-initialization callback
PCI: dwc: Drop inbound iATU types enumeration - dw_pcie_as_type
PCI: dwc: Simplify in/outbound iATU setup methods
PCI: dwc: Drop iATU regions enumeration - dw_pcie_region_type
PCI: dwc: Add iATU regions size detection procedure
PCI: dwc: Verify in/out regions against iATU constraints
PCI: dwc: Check iATU in/outbound ranges setup methods status
PCI: dwc: Introduce dma-ranges property support for RC-host
PCI: dwc: Introduce generic platform clocks and resets sets
PCI: dwc: Add Baikal-T1 PCIe controller support

.../bindings/pci/baikal,bt1-pcie.yaml | 148 ++++
.../bindings/pci/fsl,imx6q-pcie.yaml | 5 +-
.../bindings/pci/hisilicon,kirin-pcie.yaml | 4 +-
.../bindings/pci/sifive,fu740-pcie.yaml | 4 +-
.../bindings/pci/snps,dw-pcie-common.yaml | 298 ++++++++
.../bindings/pci/snps,dw-pcie-ep.yaml | 143 ++--
.../devicetree/bindings/pci/snps,dw-pcie.yaml | 189 ++++--
.../bindings/pci/toshiba,visconti-pcie.yaml | 2 +-
drivers/pci/controller/dwc/Kconfig | 9 +
drivers/pci/controller/dwc/Makefile | 1 +
drivers/pci/controller/dwc/pci-keystone.c | 12 +-
drivers/pci/controller/dwc/pcie-bt1.c | 638 ++++++++++++++++++
.../pci/controller/dwc/pcie-designware-ep.c | 36 +-
.../pci/controller/dwc/pcie-designware-host.c | 198 ++++--
drivers/pci/controller/dwc/pcie-designware.c | 465 ++++++-------
drivers/pci/controller/dwc/pcie-designware.h | 200 ++++--
drivers/pci/controller/dwc/pcie-intel-gw.c | 10 +-
drivers/pci/controller/dwc/pcie-tegra194.c | 2 +-
18 files changed, 1857 insertions(+), 507 deletions(-)
create mode 100644 Documentation/devicetree/bindings/pci/baikal,bt1-pcie.yaml
create mode 100644 Documentation/devicetree/bindings/pci/snps,dw-pcie-common.yaml
create mode 100644 drivers/pci/controller/dwc/pcie-bt1.c

--
2.35.1


2022-03-28 20:49:19

by Serge Semin

[permalink] [raw]
Subject: [PATCH 10/16] PCI: dwc: Drop iATU regions enumeration - dw_pcie_region_type

There is no point in having the dw_pcie_region_type enumeration for almost
the same reasons as it was stated for dw_pcie_as_type. First of all it's
redundant since the driver already has a set of macro declared which
describe the possible inbound and outbound iATU regions. Having an
addition abstraction just needlessly complicates the code. Secondly
checking the region index passed to the dw_pcie_disable_atu() method for
validity is pointless since the erroneous situation will be just
ignored in the current code implementation. So to speak let's drop the
redundant dw_pcie_region_type enumeration replacing it with the direct
iATU direction macro usage.

While at it we suggest to convert the dw_pcie_disable_atu() method to
being more consistent with the dw_pcie_readl_atu{_ib}() and
dw_pcie_readl_atu{_ob}() functions by having the direction parameter
specified ahead of the region index. Thus the code will be a little bit
more pleasant to read.

Signed-off-by: Serge Semin <[email protected]>
---
drivers/pci/controller/dwc/pcie-designware-ep.c | 4 ++--
.../pci/controller/dwc/pcie-designware-host.c | 2 +-
drivers/pci/controller/dwc/pcie-designware.c | 16 +---------------
drivers/pci/controller/dwc/pcie-designware.h | 9 +--------
4 files changed, 5 insertions(+), 26 deletions(-)

diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c
index 3bd9026071e8..83ceba84b79d 100644
--- a/drivers/pci/controller/dwc/pcie-designware-ep.c
+++ b/drivers/pci/controller/dwc/pcie-designware-ep.c
@@ -212,7 +212,7 @@ static void dw_pcie_ep_clear_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no,

__dw_pcie_ep_reset_bar(pci, func_no, bar, epf_bar->flags);

- dw_pcie_disable_atu(pci, atu_index, DW_PCIE_REGION_INBOUND);
+ dw_pcie_disable_atu(pci, PCIE_ATU_REGION_DIR_IB, atu_index);
clear_bit(atu_index, ep->ib_window_map);
ep->epf_bar[bar] = NULL;
}
@@ -286,7 +286,7 @@ static void dw_pcie_ep_unmap_addr(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
if (ret < 0)
return;

- dw_pcie_disable_atu(pci, atu_index, DW_PCIE_REGION_OUTBOUND);
+ dw_pcie_disable_atu(pci, PCIE_ATU_REGION_DIR_OB, atu_index);
clear_bit(atu_index, ep->ob_window_map);
}

diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c
index 602cf4fe502b..e9aa3d8539d8 100644
--- a/drivers/pci/controller/dwc/pcie-designware-host.c
+++ b/drivers/pci/controller/dwc/pcie-designware-host.c
@@ -644,7 +644,7 @@ void dw_pcie_setup_rc(struct pcie_port *pp)
* multiple matches
*/
for (i = 0; i < pci->num_ob_windows; i++)
- dw_pcie_disable_atu(pci, i, DW_PCIE_REGION_OUTBOUND);
+ dw_pcie_disable_atu(pci, PCIE_ATU_REGION_DIR_OB, i);

/* Get last memory resource entry */
resource_list_for_each_entry(entry, &pp->bridge->windows) {
diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c
index f1aa6e2e85fe..ce360986609f 100644
--- a/drivers/pci/controller/dwc/pcie-designware.c
+++ b/drivers/pci/controller/dwc/pcie-designware.c
@@ -421,22 +421,8 @@ int dw_pcie_prog_inbound_atu(struct dw_pcie *pci, u8 func_no, int index,
return -ETIMEDOUT;
}

-void dw_pcie_disable_atu(struct dw_pcie *pci, int index,
- enum dw_pcie_region_type type)
+void dw_pcie_disable_atu(struct dw_pcie *pci, u32 dir, int index)
{
- u32 dir;
-
- switch (type) {
- case DW_PCIE_REGION_INBOUND:
- dir = PCIE_ATU_REGION_DIR_IB;
- break;
- case DW_PCIE_REGION_OUTBOUND:
- dir = PCIE_ATU_REGION_DIR_OB;
- break;
- default:
- return;
- }
-
dw_pcie_writel_atu(pci, dir, index, PCIE_ATU_REGION_CTRL2, 0);
}

diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h
index 6adf0c957c3b..203f9dfb9048 100644
--- a/drivers/pci/controller/dwc/pcie-designware.h
+++ b/drivers/pci/controller/dwc/pcie-designware.h
@@ -175,12 +175,6 @@ struct pcie_port;
struct dw_pcie;
struct dw_pcie_ep;

-enum dw_pcie_region_type {
- DW_PCIE_REGION_UNKNOWN,
- DW_PCIE_REGION_INBOUND,
- DW_PCIE_REGION_OUTBOUND,
-};
-
enum dw_pcie_device_mode {
DW_PCIE_UNKNOWN_TYPE,
DW_PCIE_EP_TYPE,
@@ -316,8 +310,7 @@ void dw_pcie_prog_ep_outbound_atu(struct dw_pcie *pci, u8 func_no, int index,
u64 size);
int dw_pcie_prog_inbound_atu(struct dw_pcie *pci, u8 func_no, int index,
int type, u64 cpu_addr, u8 bar);
-void dw_pcie_disable_atu(struct dw_pcie *pci, int index,
- enum dw_pcie_region_type type);
+void dw_pcie_disable_atu(struct dw_pcie *pci, u32 dir, int index);
void dw_pcie_setup(struct dw_pcie *pci);
void dw_pcie_iatu_detect(struct dw_pcie *pci);

--
2.35.1

2022-03-28 21:06:41

by Serge Semin

[permalink] [raw]
Subject: [PATCH 13/16] PCI: dwc: Check iATU in/outbound ranges setup methods status

Let's make the DWC PCIe RC/EP safer and more verbose for the invalid or
failed inbound and outbound iATU windows setups. Needless to say that
silently ignoring iATU regions setup errors may cause unpredictable
errors. For instance if for some reason a cfg or IO window fails to be
activated, then any CFG/IO requested won't reach target PCIe devices and
the corresponding accessors will return platform-specific random values.

First of all we need to convert dw_pcie_ep_outbound_atu() method to check
whether the specified outbound iATU range is successfully setup. That
method is called by the pci_epc_ops.map_addr callback. Thus we'll make the
EP-specific CPU->PCIe memory mappings saver.

Secondly since the iATU outbound range programming method now returns the
operation status, it will be handy to take that status into account in the
pci_ops.{map_bus,read,write} methods. Thus any failed mapping will be
immediately noticeable by the PCIe CFG operations requesters.

Finally we need to convert the dw_pcie_setup_rc() method to returning the
operation status, since the iATU outbound ranges setup procedure may now
fail. It will be especially handy in case if the DW PCIe RC DT-node has
invalid/unsupported (dma-)ranges property. Note since the suggested
modification causes having too wide code indentation, it is reasonable
from maintainability and readability points of view to move the outbound
ranges setup procedure in the separate function.

Signed-off-by: Serge Semin <[email protected]>
---
.../pci/controller/dwc/pcie-designware-ep.c | 9 +-
.../pci/controller/dwc/pcie-designware-host.c | 149 ++++++++++++------
drivers/pci/controller/dwc/pcie-designware.h | 5 +-
drivers/pci/controller/dwc/pcie-intel-gw.c | 6 +-
4 files changed, 112 insertions(+), 57 deletions(-)

diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c
index 83ceba84b79d..47ed9256b87c 100644
--- a/drivers/pci/controller/dwc/pcie-designware-ep.c
+++ b/drivers/pci/controller/dwc/pcie-designware-ep.c
@@ -184,8 +184,9 @@ static int dw_pcie_ep_outbound_atu(struct dw_pcie_ep *ep, u8 func_no,
phys_addr_t phys_addr,
u64 pci_addr, size_t size)
{
- u32 free_win;
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
+ u32 free_win;
+ int ret;

free_win = find_first_zero_bit(ep->ob_window_map, pci->num_ob_windows);
if (free_win >= pci->num_ob_windows) {
@@ -193,8 +194,10 @@ static int dw_pcie_ep_outbound_atu(struct dw_pcie_ep *ep, u8 func_no,
return -EINVAL;
}

- dw_pcie_prog_ep_outbound_atu(pci, func_no, free_win, PCIE_ATU_TYPE_MEM,
- phys_addr, pci_addr, size);
+ ret = dw_pcie_prog_ep_outbound_atu(pci, func_no, free_win, PCIE_ATU_TYPE_MEM,
+ phys_addr, pci_addr, size);
+ if (ret)
+ return ret;

set_bit(free_win, ep->ob_window_map);
ep->outbound_addr[free_win] = phys_addr;
diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c
index e9aa3d8539d8..41c673c31940 100644
--- a/drivers/pci/controller/dwc/pcie-designware-host.c
+++ b/drivers/pci/controller/dwc/pcie-designware-host.c
@@ -405,7 +405,9 @@ int dw_pcie_host_init(struct pcie_port *pp)

dw_pcie_iatu_detect(pci);

- dw_pcie_setup_rc(pp);
+ ret = dw_pcie_setup_rc(pp);
+ if (ret)
+ goto err_free_msi;

if (!dw_pcie_link_up(pci) && pci->ops && pci->ops->start_link) {
ret = pci->ops->start_link(pci);
@@ -461,10 +463,10 @@ EXPORT_SYMBOL_GPL(dw_pcie_host_deinit);
static void __iomem *dw_pcie_other_conf_map_bus(struct pci_bus *bus,
unsigned int devfn, int where)
{
- int type;
- u32 busdev;
struct pcie_port *pp = bus->sysdata;
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+ int type, ret;
+ u32 busdev;

/*
* Checking whether the link is up here is a last line of defense
@@ -485,8 +487,10 @@ static void __iomem *dw_pcie_other_conf_map_bus(struct pci_bus *bus,
else
type = PCIE_ATU_TYPE_CFG1;

-
- dw_pcie_prog_outbound_atu(pci, 0, type, pp->cfg0_base, busdev, pp->cfg0_size);
+ ret = dw_pcie_prog_outbound_atu(pci, 0, type, pp->cfg0_base, busdev,
+ pp->cfg0_size);
+ if (ret)
+ return NULL;

return pp->va_cfg0_base + where;
}
@@ -499,12 +503,18 @@ static int dw_pcie_rd_other_conf(struct pci_bus *bus, unsigned int devfn,
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);

ret = pci_generic_config_read(bus, devfn, where, size, val);
+ if (ret != PCIBIOS_SUCCESSFUL)
+ return ret;

- if (!ret && pci->io_cfg_atu_shared)
- dw_pcie_prog_outbound_atu(pci, 0, PCIE_ATU_TYPE_IO, pp->io_base,
- pp->io_bus_addr, pp->io_size);
+ if (pci->io_cfg_atu_shared) {
+ ret = dw_pcie_prog_outbound_atu(pci, 0, PCIE_ATU_TYPE_IO,
+ pp->io_base, pp->io_bus_addr,
+ pp->io_size);
+ if (ret)
+ return PCIBIOS_SET_FAILED;
+ }

- return ret;
+ return PCIBIOS_SUCCESSFUL;
}

static int dw_pcie_wr_other_conf(struct pci_bus *bus, unsigned int devfn,
@@ -515,12 +525,18 @@ static int dw_pcie_wr_other_conf(struct pci_bus *bus, unsigned int devfn,
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);

ret = pci_generic_config_write(bus, devfn, where, size, val);
+ if (ret != PCIBIOS_SUCCESSFUL)
+ return ret;

- if (!ret && pci->io_cfg_atu_shared)
- dw_pcie_prog_outbound_atu(pci, 0, PCIE_ATU_TYPE_IO, pp->io_base,
- pp->io_bus_addr, pp->io_size);
+ if (pci->io_cfg_atu_shared) {
+ ret = dw_pcie_prog_outbound_atu(pci, 0, PCIE_ATU_TYPE_IO,
+ pp->io_base, pp->io_bus_addr,
+ pp->io_size);
+ if (ret)
+ return PCIBIOS_SET_FAILED;
+ }

- return ret;
+ return PCIBIOS_SUCCESSFUL;
}

static struct pci_ops dw_child_pcie_ops = {
@@ -577,10 +593,72 @@ static struct pci_ops dw_pcie_ops = {
.write = dw_pcie_wr_own_conf,
};

-void dw_pcie_setup_rc(struct pcie_port *pp)
+static int dw_pcie_iatu_setup(struct pcie_port *pp)
+{
+ struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+ struct resource_entry *entry;
+ int i, ret;
+
+ /* Note the very first outbound ATU is used for CFG IOs */
+ if (!pci->num_ob_windows) {
+ dev_err(pci->dev, "No outbound iATU found\n");
+ return -EINVAL;
+ }
+
+ /*
+ * Ensure all outbound windows are disabled before proceeding with
+ * the MEM/IO ranges setups.
+ */
+ for (i = 0; i < pci->num_ob_windows; i++)
+ dw_pcie_disable_atu(pci, PCIE_ATU_REGION_DIR_OB, i);
+
+ i = 0;
+ resource_list_for_each_entry(entry, &pp->bridge->windows) {
+ if (resource_type(entry->res) != IORESOURCE_MEM)
+ continue;
+
+ if (pci->num_ob_windows <= ++i)
+ break;
+
+ ret = dw_pcie_prog_outbound_atu(pci, i, PCIE_ATU_TYPE_MEM,
+ entry->res->start,
+ entry->res->start - entry->offset,
+ resource_size(entry->res));
+ if (ret) {
+ dev_err(pci->dev, "Failed to set MEM range %pr\n",
+ entry->res);
+ return ret;
+ }
+ }
+
+ if (pp->io_size) {
+ if (pci->num_ob_windows > ++i) {
+ ret = dw_pcie_prog_outbound_atu(pci, i, PCIE_ATU_TYPE_IO,
+ pp->io_base,
+ pp->io_bus_addr,
+ pp->io_size);
+ if (ret) {
+ dev_err(pci->dev, "Failed to set IO range %pr\n",
+ entry->res);
+ return ret;
+ }
+ } else {
+ pci->io_cfg_atu_shared = true;
+ }
+ }
+
+ if (pci->num_ob_windows <= i)
+ dev_warn(pci->dev, "Resources exceed number of ATU entries (%d)\n",
+ pci->num_ob_windows);
+
+ return 0;
+}
+
+int dw_pcie_setup_rc(struct pcie_port *pp)
{
- u32 val, ctrl, num_ctrls;
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+ u32 val, ctrl, num_ctrls;
+ int ret;

/*
* Enable DBI read-only registers for writing/updating configuration.
@@ -636,42 +714,9 @@ void dw_pcie_setup_rc(struct pcie_port *pp)
* ATU, so we should not program the ATU here.
*/
if (pp->bridge->child_ops == &dw_child_pcie_ops) {
- int i, atu_idx = 0;
- struct resource_entry *entry;
-
- /*
- * Ensure all outbound windows are disabled so there are
- * multiple matches
- */
- for (i = 0; i < pci->num_ob_windows; i++)
- dw_pcie_disable_atu(pci, PCIE_ATU_REGION_DIR_OB, i);
-
- /* Get last memory resource entry */
- resource_list_for_each_entry(entry, &pp->bridge->windows) {
- if (resource_type(entry->res) != IORESOURCE_MEM)
- continue;
-
- if (pci->num_ob_windows <= ++atu_idx)
- break;
-
- dw_pcie_prog_outbound_atu(pci, atu_idx,
- PCIE_ATU_TYPE_MEM, entry->res->start,
- entry->res->start - entry->offset,
- resource_size(entry->res));
- }
-
- if (pp->io_size) {
- if (pci->num_ob_windows > ++atu_idx)
- dw_pcie_prog_outbound_atu(pci, atu_idx,
- PCIE_ATU_TYPE_IO, pp->io_base,
- pp->io_bus_addr, pp->io_size);
- else
- pci->io_cfg_atu_shared = true;
- }
-
- if (pci->num_ob_windows <= atu_idx)
- dev_warn(pci->dev, "Resources exceed number of ATU entries (%d)\n",
- pci->num_ob_windows);
+ ret = dw_pcie_iatu_setup(pp);
+ if (ret)
+ return ret;
}

dw_pcie_writel_dbi(pci, PCI_BASE_ADDRESS_0, 0);
@@ -684,5 +729,7 @@ void dw_pcie_setup_rc(struct pcie_port *pp)
dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, val);

dw_pcie_dbi_ro_wr_dis(pci);
+
+ return 0;
}
EXPORT_SYMBOL_GPL(dw_pcie_setup_rc);
diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h
index bcda63393390..15fce8fd33df 100644
--- a/drivers/pci/controller/dwc/pcie-designware.h
+++ b/drivers/pci/controller/dwc/pcie-designware.h
@@ -373,7 +373,7 @@ static inline void dw_pcie_dbi_ro_wr_dis(struct dw_pcie *pci)

#ifdef CONFIG_PCIE_DW_HOST
irqreturn_t dw_handle_msi_irq(struct pcie_port *pp);
-void dw_pcie_setup_rc(struct pcie_port *pp);
+int dw_pcie_setup_rc(struct pcie_port *pp);
int dw_pcie_host_init(struct pcie_port *pp);
void dw_pcie_host_deinit(struct pcie_port *pp);
int dw_pcie_allocate_domains(struct pcie_port *pp);
@@ -385,8 +385,9 @@ static inline irqreturn_t dw_handle_msi_irq(struct pcie_port *pp)
return IRQ_NONE;
}

-static inline void dw_pcie_setup_rc(struct pcie_port *pp)
+static inline int dw_pcie_setup_rc(struct pcie_port *pp)
{
+ return 0;
}

static inline int dw_pcie_host_init(struct pcie_port *pp)
diff --git a/drivers/pci/controller/dwc/pcie-intel-gw.c b/drivers/pci/controller/dwc/pcie-intel-gw.c
index 786af2ba379f..c3bafaa803de 100644
--- a/drivers/pci/controller/dwc/pcie-intel-gw.c
+++ b/drivers/pci/controller/dwc/pcie-intel-gw.c
@@ -306,7 +306,11 @@ static int intel_pcie_host_setup(struct intel_pcie *pcie)
intel_pcie_ltssm_disable(pcie);
intel_pcie_link_setup(pcie);
intel_pcie_init_n_fts(pci);
- dw_pcie_setup_rc(&pci->pp);
+
+ ret = dw_pcie_setup_rc(&pci->pp);
+ if (ret)
+ goto app_init_err;
+
dw_pcie_upconfig_setup(pci);

intel_pcie_device_rst_deassert(pcie);
--
2.35.1

2022-03-28 22:01:45

by Serge Semin

[permalink] [raw]
Subject: [PATCH 01/16] dt-bindings: PCI: dwc: Define generic and native DT bindings

Currently both DW PCIe Root Port and End-point DT bindings are too generic
to be used as a descriptive set the device properties. Yes, it's very handy
to have them that way so the DT-schemas could be used to evaluate as many
DW PCIe-related DT-nodes as possible. But at the same time they don't
provide well defined DW PCIe RP/EP DT interface description thus leaving
too much flexibility for the new platforms, no encouraging the developers
to preserve a compatible interface.

Instead of currently implemented approach we suggest to be more
restrictive and yet preserve some level of flexibility in the DW PCIe
DT-bindings description. The device tree DT-schema is split up into
three parts: a common YAML-schema applicable for both DWC Root Port and
End-point controller configs, DWC PCIe Root Port-specific YAML-schema
and DWC PCIe End-point-specific YAML-schema, where
1) pci/snps,dw-pcie-common.yaml - The common DT-schema describes the most
generic constraints of the "reg", "interrupts", "clocks", "resets" and
"phys" properties together with a set of common for both device types
PCIe/AXI bus properties like a maximum number of lanes or a maximum link
speed, number of inbound and outbound iATU windows. In addition to that a
set of schema definitions declared under the "$defs" property with "reg",
"interrupt", "clock" and "reset" names common for DWC PCIe Root Port and
End-point devices. They can be used by the successive DT-schemas in case
they are supposed to be compatible with the generic DWC PCIe controller
DT-bindings.
2) pci/snps,dw-pcie.yaml, pci/snps,dw-pcie-ep.yaml - generic DW PCIe Root
Port and End-point DT-bindings which aside with the device-specific
properties set also contain more restrictive constraints. All new DW PCIe
platforms are supposed to be compatible with one of these bindings by
using "allOf: " property and additionally defining their own constraints
to close up the DT-bindings set.

So to speak in case if a DW PCIe-based device for some reason has too many
specific properties or it's bindings have already been defined in a
non-generic way, it's DT-schema is supposed to include 1) YAML-file and
provide its own constraints. Otherwise the ready-to-use bindings from 2)
should be utilized. There only two DT-schemas compatible with 2) at the
moment are samsung,axynos-pcie.yaml and intel-gw-pcie.yaml. The
rest of the DW PCIe-related DT-schemas are supposed to use more generic
DW PCIe DT-bindings - pci/snps,dw-pcie-common.yaml.

Note the provided here generic properties and their possible values are
derived from the DW PCIe RC/EP hardware manuals and from the interface
implemented in the driver. The DT-bindings schemas are created to be as
full as possible with detailed properties/names description for the
easier interface comprehension and new platforms bindings development.

Also note since there are no generic DT-nodes can be found in the kernel
dts-es which would have a pure "snps,dw-pcie" compatible string, these
DT-bindings have been created to not be selected by default for
evaluation. They are supposed to be used by the new vendor-specific
DT-schemas to at least stop adding new bindings for the same set of DWC
PCIe signals or properties.

Signed-off-by: Serge Semin <[email protected]>

---

Rob, if you comment the "select: false" line out you'll get a lot of "'*'
is not of type 'array'" errors on the dt_bindings_check command execution.
I have spend tons of time trying to figure out what was wrong, but still
failed to find out a solution. For some reason the "$ref"-ed schemas
defined under the "$defs" basic property are considered by the parser as
defining an array. No matter whether I specified the "type: string"
keyword or not, as long as there is "enum" or "oneOf" keyword used in the
"*-names" property definition in "$defs" it's considered as describing an
array thus causing the errors like:

> DTC Documentation/devicetree/bindings/pci/snps,dw-pcie.example.dt.yaml
> CHECK Documentation/devicetree/bindings/pci/snps,dw-pcie.example.dt.yaml
> /.../snps,dw-pcie.example.dt.yaml: pcie@1f052000: reg-names:0: 'oneOf' conditional failed, one must be fixed:
> 'dbi' is not of type 'array'
> From schema: /.../snps,dw-pcie.yaml
> /.../snps,dw-pcie.example.dt.yaml: pcie@1f052000: reg-names:1: 'oneOf' conditional failed, one must be fixed:
> 'config' is not of type 'array'
> From schema: /.../snps,dw-pcie.yaml
> /.../snps,dw-pcie.example.dt.yaml: pcie@1f052000: interrupt-names:0: 'anyOf' conditional failed, one must be fixed:
> /.../snps,dw-pcie.example.dt.yaml: pcie@1f052000: interrupt-names:0: 'oneOf' conditional failed, one must be fixed:
> 'msi' is not of type 'array'
> /../snps,dw-pcie.example.dt.yaml: pcie@1f052000: interrupt-names:0: 'oneOf' conditional failed, one must be fixed:
> 'msi' is not of type 'array'
> From schema: /.../snps,dw-pcie.yaml

As soon as I manually moved the same "$defs"-schemas into the places they
are referenced to, the errors disappeared and the DT-schemas validation
worked as expected by the semantics.

As I see it there must be some bug in the parser, otherwise I am out of
ideas how to implement the suggested in this patch design pattern. Rob,
could you take a look at the DT-schemas and help me out with debugging the
problem described above?
---
.../bindings/pci/fsl,imx6q-pcie.yaml | 5 +-
.../bindings/pci/hisilicon,kirin-pcie.yaml | 4 +-
.../bindings/pci/sifive,fu740-pcie.yaml | 4 +-
.../bindings/pci/snps,dw-pcie-common.yaml | 298 ++++++++++++++++++
.../bindings/pci/snps,dw-pcie-ep.yaml | 143 ++++++---
.../devicetree/bindings/pci/snps,dw-pcie.yaml | 189 +++++++----
.../bindings/pci/toshiba,visconti-pcie.yaml | 2 +-
7 files changed, 524 insertions(+), 121 deletions(-)
create mode 100644 Documentation/devicetree/bindings/pci/snps,dw-pcie-common.yaml

diff --git a/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.yaml b/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.yaml
index 643a6333b07b..ccb5cfdf869f 100644
--- a/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.yaml
+++ b/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.yaml
@@ -12,10 +12,11 @@ maintainers:

description: |+
This PCIe host controller is based on the Synopsys DesignWare PCIe IP
- and thus inherits all the common properties defined in snps,dw-pcie.yaml.
+ and thus inherits all the common properties defined in
+ snps,dw-pcie-common.yaml.

allOf:
- - $ref: /schemas/pci/snps,dw-pcie.yaml#
+ - $ref: /schemas/pci/snps,dw-pcie-common.yaml#

properties:
compatible:
diff --git a/Documentation/devicetree/bindings/pci/hisilicon,kirin-pcie.yaml b/Documentation/devicetree/bindings/pci/hisilicon,kirin-pcie.yaml
index c9f04999c9cf..879d9ec41cb7 100644
--- a/Documentation/devicetree/bindings/pci/hisilicon,kirin-pcie.yaml
+++ b/Documentation/devicetree/bindings/pci/hisilicon,kirin-pcie.yaml
@@ -14,10 +14,10 @@ description: |
Kirin PCIe host controller is based on the Synopsys DesignWare PCI core.
It shares common functions with the PCIe DesignWare core driver and
inherits common properties defined in
- Documentation/devicetree/bindings/pci/snps,dw-pcie.yaml.
+ Documentation/devicetree/bindings/pci/snps,dw-pcie-common.yaml.

allOf:
- - $ref: /schemas/pci/snps,dw-pcie.yaml#
+ - $ref: /schemas/pci/snps,dw-pcie-common.yaml#

properties:
compatible:
diff --git a/Documentation/devicetree/bindings/pci/sifive,fu740-pcie.yaml b/Documentation/devicetree/bindings/pci/sifive,fu740-pcie.yaml
index 392f0ab488c2..437614257935 100644
--- a/Documentation/devicetree/bindings/pci/sifive,fu740-pcie.yaml
+++ b/Documentation/devicetree/bindings/pci/sifive,fu740-pcie.yaml
@@ -10,14 +10,14 @@ description: |+
SiFive FU740 PCIe host controller is based on the Synopsys DesignWare
PCI core. It shares common features with the PCIe DesignWare core and
inherits common properties defined in
- Documentation/devicetree/bindings/pci/snps,dw-pcie.yaml.
+ Documentation/devicetree/bindings/pci/snps,dw-pcie-common.yaml.

maintainers:
- Paul Walmsley <[email protected]>
- Greentime Hu <[email protected]>

allOf:
- - $ref: /schemas/pci/snps,dw-pcie.yaml#
+ - $ref: /schemas/pci/snps,dw-pcie-common.yaml#

properties:
compatible:
diff --git a/Documentation/devicetree/bindings/pci/snps,dw-pcie-common.yaml b/Documentation/devicetree/bindings/pci/snps,dw-pcie-common.yaml
new file mode 100644
index 000000000000..27fe1f5c450f
--- /dev/null
+++ b/Documentation/devicetree/bindings/pci/snps,dw-pcie-common.yaml
@@ -0,0 +1,298 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/pci/snps,dw-pcie-common.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Synopsys DWC PCIe RP/EP controller
+
+maintainers:
+ - Jingoo Han <[email protected]>
+ - Gustavo Pimentel <[email protected]>
+
+properties:
+ reg:
+ description:
+ DWC PCIe CSR space is normally accessed over the dedicated Data Bus
+ Interface - DBI. In accordance with the reference manual the register
+ configuration space belongs to the Configuration-Dependent Module (CDM)
+ and is split up into several sub-parts Standard PCIe configuration
+ space, Port Logic Registers (PL), Shadow Config-space Registers,
+ iATU/eDMA registers. The particular sub-space is selected by the
+ CDM/ELBI (dbi_cs) and CS2 (dbi_cs2) signals (selector bits). Such
+ configuration provides a flexible interface for the system engineers to
+ either map the particular space at a desired MMIO address or just leave
+ them in a contiguous memory space if pure Native or AXI Bridge DBI access
+ is selected. Note the PCIe CFG-space, PL and Shadow registers are
+ specific to each activated function, while the rest of the sub-spaces
+ are common for all of them (if there are more than one).
+ minItems: 2
+ maxItems: 6
+
+ interrupts:
+ description:
+ There are two main sub-blocks which are normally capable of
+ generating interrupts. It's System Information Interface and MSI
+ interface. While the former one has some common for the Host and
+ End-point controllers IRQ-signals, the later interface is obviously
+ Root Complex specific since it's responsible for the incoming MSI
+ messages signalling. The System Information IRQ signals are mainly
+ responsible for reporting the generic PCIe hierarchy and Root
+ Complex events like VPD IO request, general AER, PME, Hot-plug, link
+ bandwidth change, link equalization request, INTx asserted/deasserted
+ Message detection, embedded DMA Tx/Rx/Error.
+ minItems: 1
+ maxItems: 26
+
+ clocks:
+ description:
+ DWC PCIe reference manual explicitly defines a set of the clocks required
+ to get the controller working correctly. In general all of them can
+ be divided into two groups':' application and core clocks. Note the
+ platforms may have some of the clock sources unspecified in case if the
+ corresponding domains are fed up from a common clock source.
+ minItems: 1
+ maxItems: 7
+
+ resets:
+ description:
+ DWC PCIe reference manual explicitly defines a set of the reset
+ signals required to be de-asserted to properly activate the controller
+ sub-parts. All of these signals can be divided into two sub-groups':'
+ application and core resets with respect to the main sub-domains they
+ are supposed to reset. Note the platforms may have some of these signals
+ unspecified in case if they are automatically handled or aggregated into
+ a comprehensive control module.
+ minItems: 1
+ maxItems: 10
+
+ reset-gpio:
+ deprecated: true
+ description:
+ Reference to the GPIO-controlled PERST# signal. It is used to reset all
+ the peripheral devices available on the PCIe bus.
+ maxItems: 1
+
+ reset-gpios:
+ description:
+ Reference to the GPIO-controlled PERST# signal. It is used to reset all
+ the peripheral devices available on the PCIe bus.
+ maxItems: 1
+
+ phys:
+ description:
+ There can be up to number of possible lanes PHYs specified.
+ Obviously each specified PHY is supposed to be able to work in the
+ PCIe mode with a speed implied by the DWC PCIe controller it is
+ attached to.
+ minItems: 1
+ maxItems: 16
+
+ phy-names:
+ minItems: 1
+ maxItems: 16
+ items:
+ pattern: '^pcie([0-9]+|-?phy)?$'
+
+ num-lanes:
+ maximum: 16
+
+ max-link-speed:
+ maximum: 4
+
+ num-ob-windows:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ deprecated: true
+ description:
+ Number of outbound address translation windows. This parameter can be
+ auto-detected based on the iATU memory writability. So there is no
+ point in having a dedicated DT-property for it.
+ maximum: 256
+
+ num-ib-windows:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ deprecated: true
+ description:
+ Number of inbound address translation windows. In the same way as
+ for the outbound AT windows, this parameter can be auto-detected based
+ on the iATU memory writability. There is no point having a dedicated
+ DT-property for it either.
+ maximum: 256
+
+ num-viewport:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ deprecated: true
+ description:
+ Number of outbound view ports configured in hardware. It's the same as
+ the number of outbound AT windows.
+ maximum: 256
+
+ snps,enable-cdm-check:
+ $ref: /schemas/types.yaml#/definitions/flag
+ description:
+ Enable automatic checking of CDM (Configuration Dependent Module)
+ registers for data corruption. CDM registers include standard PCIe
+ configuration space registers, Port Logic registers, DMA and iATU
+ registers. This feature has been available since DWC PCIe v4.80a.
+
+additionalProperties: true
+
+$defs:
+ reg-names:
+ description:
+ CSR space names common for the DWC PCIe Root Port and End-point
+ controllers.
+ oneOf:
+ - description:
+ Basic DWC PCIe controller configuration-space accessible over
+ the DBI interface. This memory space is either activated with
+ the CDM/ELBI = 0 and CS2 = 0 or is a contiguous memory region
+ with all spaces. Note iATU/eDMA CSRs are indirectly accessible
+ via the PL viewports on the DWC PCIe controllers older than
+ v4.80a.
+ const: dbi
+ - description:
+ Shadow DWC PCIe config-space registers. This space is selected
+ by setting CDM/ELBI = 0 and CS2 = 1. This is an intermix of
+ the PCI-SIG PCIe CFG-space with the shadow registers for some
+ PCI Header space, PCI Standard and Extended Structures. It's
+ mainly relevant for the end-point controller configuration,
+ but still there are some shadow registers available for the
+ Root Port mode too.
+ const: dbi2
+ - description:
+ External Local Bus registers. It's an application-dependent
+ registers normally defined by the platform engineers, which
+ are selected by setting CDM/ELBI = 1 and CS2 = 0.
+ enum: [ elbi, appl, app ]
+ - description:
+ iATU/eDMA registers common for all device functions. It's an
+ unrolled memory space with the internal Address Translation
+ Unit and Enhanced DMA, which is selected by setting CDM/ELBI = 1
+ and CS2 = 1. For IP-core releases prior v4.80a, these registers
+ have been programmed via an indirect addressing scheme using a
+ set of viewport CSRs mapped into the PL space. Note iATU is
+ normally mapped to the 0x0 address of this region, while eDMA
+ is available at 0x80000 base address.
+ enum: [ atu, atu_dma ]
+ - description:
+ Outbound iATU-available memory-region which usage scenario
+ depends on the DWC PCIe controller being either Root Port or
+ End-point. If it's Root Port then the register space shall
+ have the "config" name and it will be used to access the
+ peripheral PCIe devices configuration space. If it's PCIe
+ end-point controller, then the region shall be named as
+ "addr_space" and it will be used to generate various traffic
+ on the PCIe bus hierarchy. It's usage scenario depends on the
+ end-point functionality, for instance it can be used to create
+ MSI(X) messages.
+ enum: [ config, addr_space ]
+ - description:
+ PHY/PCS configuration registers. Some platforms can have the
+ PCS and PHY CSRs accessible over a dedicated memory mapped
+ region, but mainly these registers are indirectly accessible
+ either by means of the embedded PHY viewport schema or by some
+ platform-specific method.
+ enum: [ link, phy ]
+
+ interrupt-names:
+ description:
+ IRQ signal names common for the DWC PCIe Root Port and End-point
+ controllers.
+ oneOf:
+ - description:
+ Controller request to read or write virtual product data
+ from/to the VPD capability registers.
+ const: vpd
+ - description:
+ Link Equalization Request flag is set in the Link Status 2
+ register (applicable if the corresponding IRQ is enabled in
+ the Link Control 3 register).
+ const: l_eq
+ - description:
+ Indicates that the eDMA Tx/Rx transfer is complete or that an
+ error has occurred on the corresponding channel. eDMA can have
+ eight Tx (Write) and Rx (Read) eDMA channels thus supporting up
+ to 16 IRQ signals all together. Write eDMA channels shall go
+ first in the ordered row as per default edma_int[*] bus setup.
+ pattern: '^dma([0-9]|1[0-5])?$'
+ - description:
+ PCIe protocol correctable error or a Data Path protection
+ correctable error is detected by the automotive/safety
+ feature.
+ const: sft_ce
+ - description:
+ Indicates that the internal safety mechanism detected and
+ uncorrectable error.
+ const: sft_ue
+
+ clock-names:
+ description:
+ Reference clock names common for the DWC PCIe Root Port and End-point
+ controllers.
+ anyOf:
+ - description:
+ Data Bus Interface (DBI) clock. Clock signal for the AXI-bus
+ interface of the Configuration-Dependent Module, which is
+ basically the set of the controller CSRs.
+ enum: [ dbi, pcie ]
+ - description:
+ Application AXI-bus Master interface clock. Basically this is
+ a clock for the controller DMA interface (PCI-to-CPU).
+ enum: [ mstr, pcie_bus ]
+ - description:
+ Application AXI-bus Slave interface clock. This is a clock for
+ the CPU-to-PCI memory IO interface.
+ enum: [ slv, pcie_bus ]
+ - description:
+ Controller Core-PCS PIPE interface clock. It's normally
+ supplied by an external PCS-PHY.
+ const: pipe
+ - description:
+ Controller Primary clock. It's assumed that all controller input
+ signals (except resets) are synchronous to this clock.
+ const: core
+ - description:
+ Auxiliary clock for the controller PMC domain. The controller
+ partitioning implies having some parts to operate with this
+ clock in some power management states.
+ const: aux
+ - description:
+ Generic reference clock. In case if there are several
+ interfaces fed up with a common clock source it's advisable to
+ define it with this name (for instance pipe, core and aux can
+ be connected to a single source of the periodic signal).
+ const: ref
+ - description:
+ Clock for the PHY registers interface. Originally this is
+ a PHY-viewport-based interface, but some platform may have
+ specifically designed one.
+ const: phy_reg
+
+ reset-names:
+ description:
+ Reset signal names common for the DWC PCIe Root Port and End-point
+ controllers.
+ anyOf:
+ - description: Data Bus Interface (DBI) domain reset
+ const: dbi
+ - description: AXI-bus Master interface reset
+ const: mstr
+ - description: AXI-bus Slave interface reset
+ const: slv
+ - description: Controller Non-sticky CSR flags reset
+ const: non-sticky
+ - description: Controller sticky CSR flags reset
+ const: sticky
+ - description: PIPE-interface (Core-PCS) logic reset
+ const: pipe
+ - description:
+ Controller primary reset (resets everything except PMC module)
+ const: core
+ - description: PCS/PHY block reset
+ const: phy
+ - description: PMC hot reset signal
+ const: hot
+ - description: Cold reset signal
+ const: pwr
+...
diff --git a/Documentation/devicetree/bindings/pci/snps,dw-pcie-ep.yaml b/Documentation/devicetree/bindings/pci/snps,dw-pcie-ep.yaml
index e59059ab5be0..c3bbe90264dc 100644
--- a/Documentation/devicetree/bindings/pci/snps,dw-pcie-ep.yaml
+++ b/Documentation/devicetree/bindings/pci/snps,dw-pcie-ep.yaml
@@ -13,74 +13,115 @@ maintainers:
description: |
Synopsys DesignWare PCIe host controller endpoint

+# Please create a separate DT-schema for the particular DWC PCIe End-point
+# controller and make sure it's assigned with the vendor-specific
+# compatible string together with the generic Synopsys DWC PCIe strings so
+# the bindings would be successfully evaluated against this schema.
+select: false
+
allOf:
- $ref: /schemas/pci/pci-ep.yaml#
+ - $ref: snps,dw-pcie-common.yaml#

properties:
compatible:
- anyOf:
- - {}
- - const: snps,dw-pcie-ep
+ contains:
+ oneOf:
+ - description:
+ DWC PCIe End-point controller (IP-core version is explicitly
+ specified in the additional compatible string)
+ items:
+ - pattern: '^snps,dw-pcie-ep-[0-9]+\.[0-9]+a?$'
+ - const: snps,dw-pcie-ep
+ - description:
+ DWC PCIe End-point controller (IP-core version is either unknown
+ or can be read from the PCIe version register of the PL reg-space)
+ const: snps,dw-pcie-ep

reg:
- description: |
- It should contain Data Bus Interface (dbi) and config registers for all
- versions.
- For designware core version >= 4.80, it may contain ATU address space.
- minItems: 2
- maxItems: 4
+ description:
+ DBI, DBI2 reg-spaces and outbound memory window are required for the
+ normal controller functioning. iATU memory IO region is also required
+ if the space is unrolled (IP-core version >= 4.80a).
+ minItems: 3
+ maxItems: 5

reg-names:
- minItems: 2
- maxItems: 4
+ minItems: 3
+ maxItems: 5
items:
- enum: [dbi, dbi2, config, atu, addr_space, link, atu_dma, appl]
-
- reset-gpio:
- description: GPIO pin number of PERST# signal
- maxItems: 1
- deprecated: true
-
- reset-gpios:
- description: GPIO controlled connection to PERST# signal
- maxItems: 1
-
- snps,enable-cdm-check:
- type: boolean
- description: |
- This is a boolean property and if present enables
- automatic checking of CDM (Configuration Dependent Module) registers
- for data corruption. CDM registers include standard PCIe configuration
- space registers, Port Logic registers, DMA and iATU (internal Address
- Translation Unit) registers.
-
- num-ib-windows:
- description: number of inbound address translation windows
- maxItems: 1
- deprecated: true
-
- num-ob-windows:
- description: number of outbound address translation windows
- maxItems: 1
- deprecated: true
+ $ref: snps,dw-pcie-common.yaml#/$defs/reg-names
+ allOf:
+ - contains:
+ const: dbi
+ - contains:
+ const: dbi2
+ - contains:
+ const: addr_space
+
+ interrupts:
+ description:
+ There is no mandatory IRQ signals for the normal controller functioning,
+ but in addition to the native set the platforms may have a link- or
+ PM-related IRQs specified.
+ minItems: 1
+ maxItems: 20
+
+ interrupt-names:
+ minItems: 1
+ maxItems: 20
+ items:
+ $ref: snps,dw-pcie-common.yaml#/$defs/interrupt-names
+
+ clocks:
+ minItems: 1
+ maxItems: 7
+
+ clock-names:
+ minItems: 1
+ maxItems: 7
+ items:
+ $ref: snps,dw-pcie-common.yaml#/$defs/clock-names
+
+ resets:
+ minItems: 1
+ maxItems: 10
+
+ reset-names:
+ minItems: 1
+ maxItems: 10
+ items:
+ $ref: snps,dw-pcie-common.yaml#/$defs/reset-names
+
+ max-functions:
+ maximum: 32

required:
+ - compatible
- reg
- reg-names
- - compatible

additionalProperties: true

examples:
- |
- bus {
- #address-cells = <1>;
- #size-cells = <1>;
- pcie-ep@dfd00000 {
- compatible = "snps,dw-pcie-ep";
- reg = <0xdfc00000 0x0001000>, /* IP registers 1 */
- <0xdfc01000 0x0001000>, /* IP registers 2 */
- <0xd0000000 0x2000000>; /* Configuration space */
- reg-names = "dbi", "dbi2", "addr_space";
- };
+ pcie-ep@1f052000 {
+ compatible = "vendor,soc-pcie", "snps,dw-pcie-ep-4.60a", "snps,dw-pcie-ep";
+ reg = <0x66000000 0x1000>, <0x66010000 0x10000>, <0x67000000 0x400000>;
+ reg-names = "dbi", "dbi2", "addr_space";
+
+ clocks = <&sys_clk 12>, <&sys_clk 24>;
+ clock-names = "dbi", "ref";
+
+ resets = <&sys_rst 12>, <&sys_rst 24>;
+ reset-names = "dbi", "phy";
+
+ phys = <&pcie_phy>;
+ phy-names = "pcie-phy";
+
+ num-lanes = <4>;
+ max-link-speed = <4>;
+ num-ib-windows = <16>;
+ num-ob-windows = <16>;
};
+...
diff --git a/Documentation/devicetree/bindings/pci/snps,dw-pcie.yaml b/Documentation/devicetree/bindings/pci/snps,dw-pcie.yaml
index a5345c494744..6c397ae3c71a 100644
--- a/Documentation/devicetree/bindings/pci/snps,dw-pcie.yaml
+++ b/Documentation/devicetree/bindings/pci/snps,dw-pcie.yaml
@@ -13,20 +13,36 @@ maintainers:
description: |
Synopsys DesignWare PCIe host controller

+# Please create a separate DT-schema for the particular DWC PCIe Root Port
+# controller and make sure it's assigned with the vendor-specific
+# compatible string together with the generic Synopsys DWC PCIe strings so
+# the bindings would be successfully evaluated against this schema.
+select: false
+
allOf:
- $ref: /schemas/pci/pci-bus.yaml#
+ - $ref: snps,dw-pcie-common.yaml#

properties:
compatible:
- anyOf:
- - {}
- - const: snps,dw-pcie
+ contains:
+ oneOf:
+ - description:
+ DWC PCIe Root Port controller (IP-core version is explicitly
+ specified in the additional compatible string)
+ items:
+ - pattern: '^snps,dw-pcie-[0-9]+\.[0-9]+a?$'
+ - const: snps,dw-pcie
+ - description:
+ DWC PCIe Root Port controller (IP-core version is either unknown
+ or can be read from the PCIe version register of the PL reg-space)
+ const: snps,dw-pcie

reg:
- description: |
- It should contain Data Bus Interface (dbi) and config registers for all
- versions.
- For designware core version >= 4.80, it may contain ATU address space.
+ description:
+ At least DBI reg-space and peripheral devices CFG-space outbound window
+ are required for the normal controller work. iATU memory IO region is
+ also required if the space is unrolled (IP-core version >= 4.80a).
minItems: 2
maxItems: 5

@@ -34,69 +50,116 @@ properties:
minItems: 2
maxItems: 5
items:
- enum: [ dbi, dbi2, config, atu, app, elbi, mgmt, ctrl, parf, cfg, link,
- ulreg, smu, mpu, apb, phy ]
-
- num-lanes:
- description: |
- number of lanes to use (this property should be specified unless
- the link is brought already up in firmware)
- maximum: 16
-
- reset-gpio:
- description: GPIO pin number of PERST# signal
- maxItems: 1
- deprecated: true
-
- reset-gpios:
- description: GPIO controlled connection to PERST# signal
- maxItems: 1
-
- interrupts: true
-
- interrupt-names: true
-
- clocks: true
-
- snps,enable-cdm-check:
- type: boolean
- description: |
- This is a boolean property and if present enables
- automatic checking of CDM (Configuration Dependent Module) registers
- for data corruption. CDM registers include standard PCIe configuration
- space registers, Port Logic registers, DMA and iATU (internal Address
- Translation Unit) registers.
+ $ref: snps,dw-pcie-common.yaml#/$defs/reg-names
+ allOf:
+ - contains:
+ const: dbi
+ - contains:
+ const: config
+
+ interrupts:
+ description:
+ At least MSI interrupt signal is supposed to be specified for
+ the DWC PCIe host controller.
+ minItems: 1
+ maxItems: 26
+
+ interrupt-names:
+ minItems: 1
+ maxItems: 26
+ items:
+ anyOf:
+ - $ref: snps,dw-pcie-common.yaml#/$defs/interrupt-names
+ - $ref: '#/$defs/interrupt-names'
+ allOf:
+ - contains:
+ const: msi
+
+ clocks:
+ minItems: 1
+ maxItems: 7
+
+ clock-names:
+ minItems: 1
+ maxItems: 7
+ items:
+ $ref: snps,dw-pcie-common.yaml#/$defs/clock-names

- num-viewport:
- description: |
- number of view ports configured in hardware. If a platform
- does not specify it, the driver autodetects it.
- deprecated: true
+ resets:
+ minItems: 1
+ maxItems: 10

-additionalProperties: true
+ reset-names:
+ minItems: 1
+ maxItems: 10
+ items:
+ $ref: snps,dw-pcie-common.yaml#/$defs/reset-names

required:
+ - compatible
- reg
- reg-names
- - compatible
+ - interrupts
+ - interrupt-names
+
+additionalProperties: true
+
+$defs:
+ interrupt-names:
+ description:
+ DWC PCIe Root Port/Complex specific IRQ signal names.
+ oneOf:
+ - description:
+ DSP AXI MSI Interrupt detected. It gets de-asserted when there is
+ no more MSI interrupt pending. The interrupt is relevant to the
+ iMSI-RX - Integrated MSI Receiver (AXI bridge).
+ const: msi
+ - description:
+ Error condition detected and a bit is set in the Root Error Status
+ register of the AER capability. It's asserted when the RC
+ internally generated an error or an error message is received by
+ the RC.
+ const: aer
+ - description:
+ PME message is received by the port. That means having the PME
+ status bit set in the Root Status register (the event is
+ supposed to be unmasked in the Root Control register).
+ const: pme
+ - description:
+ Hot-plug event is detected. That is a bit has been set in the
+ Slot Status register and the corresponding event is enabled in
+ the Slot Control register.
+ const: hp
+ - description:
+ Link Autonomous Bandwidth Status flag has been set in the Link
+ Status register (the event is supposed to be unmasked in the
+ Link Control register).
+ const: bw_au
+ - description:
+ Bandwidth Management Status flag has been set in the Link
+ Status register (the event is supposed to be unmasked in the
+ Link Control register).
+ const: bw_mg

examples:
- |
- bus {
- #address-cells = <1>;
- #size-cells = <1>;
- pcie@dfc00000 {
- device_type = "pci";
- compatible = "snps,dw-pcie";
- reg = <0xdfc00000 0x0001000>, /* IP registers */
- <0xd0000000 0x0002000>; /* Configuration space */
- reg-names = "dbi", "config";
- #address-cells = <3>;
- #size-cells = <2>;
- ranges = <0x81000000 0 0x00000000 0xde000000 0 0x00010000>,
- <0x82000000 0 0xd0400000 0xd0400000 0 0x0d000000>;
- interrupts = <25>, <24>;
- #interrupt-cells = <1>;
- num-lanes = <1>;
- };
+ pcie@1f052000 {
+ compatible = "vendor,soc-pcie", "snps,dw-pcie-4.60a", "snps,dw-pcie";
+ device_type = "pci";
+ reg = <0x1f052000 0x1000>, <0x1bdbf000 0x1000>;
+ reg-names = "dbi", "config";
+ #address-cells = <3>;
+ #size-cells = <2>;
+ ranges = <0x81000000 0 0x00000000 0x1bdb0000 0 0x00008000>,
+ <0x82000000 0 0x20000000 0x08000000 0 0x13db0000>;
+ bus-range = <0x0 0xff>;
+
+ interrupts = <0 80 4>;
+ interrupt-names = "msi";
+
+ reset-gpios = <&port0 0 1>;
+
+ num-lanes = <4>;
+ max-link-speed = <3>;
};
+...
diff --git a/Documentation/devicetree/bindings/pci/toshiba,visconti-pcie.yaml b/Documentation/devicetree/bindings/pci/toshiba,visconti-pcie.yaml
index 30b6396d83c8..f0a3c436c6d1 100644
--- a/Documentation/devicetree/bindings/pci/toshiba,visconti-pcie.yaml
+++ b/Documentation/devicetree/bindings/pci/toshiba,visconti-pcie.yaml
@@ -13,7 +13,7 @@ description:
Toshiba Visconti5 SoC PCIe host controller is based on the Synopsys DesignWare PCIe IP.

allOf:
- - $ref: /schemas/pci/snps,dw-pcie.yaml#
+ - $ref: /schemas/pci/snps,dw-pcie-common.yaml#

properties:
compatible:
--
2.35.1

2022-03-28 22:14:39

by Serge Semin

[permalink] [raw]
Subject: [PATCH 16/16] PCI: dwc: Add Baikal-T1 PCIe controller support

Baikal-T1 SoC is equipped with DWC PCIe v4.60a host controller. It can be
trained to work up to Gen.3 speed over up to x4 lanes. The host controller
is attached to the DW PCIe 3.0 PCS via the PIPE-4 interface, which in its
turn is connected to the DWC 10G PHY. The whole system is supposed to be
fed up with four clock sources: DBI peripheral clock, AXI application
clocks and external PHY/core reference clock generating the 100MHz signal.
In addition to that the platform provide a way to reset each part of the
controller: sticky/non-sticky bits, host controller core, PIPE interface,
PCS/PHY and Hot/Power reset signal. The driver also provides a way to
handle the GPIO-based PERST# signal.

Note due to the Baikal-T1 MMIO peculiarity we have to implement the DBI
interface accessors which make sure the IO is dword-aligned.

Signed-off-by: Serge Semin <[email protected]>
---
drivers/pci/controller/dwc/Kconfig | 9 +
drivers/pci/controller/dwc/Makefile | 1 +
drivers/pci/controller/dwc/pcie-bt1.c | 638 ++++++++++++++++++++++++++
3 files changed, 648 insertions(+)
create mode 100644 drivers/pci/controller/dwc/pcie-bt1.c

diff --git a/drivers/pci/controller/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig
index 62ce3abf0f19..771b8b146623 100644
--- a/drivers/pci/controller/dwc/Kconfig
+++ b/drivers/pci/controller/dwc/Kconfig
@@ -222,6 +222,15 @@ config PCIE_ARTPEC6_EP
Enables support for the PCIe controller in the ARTPEC-6 SoC to work in
endpoint mode. This uses the DesignWare core.

+config PCIE_BT1
+ tristate "Baikal-T1 PCIe controller"
+ depends on MIPS_BAIKAL_T1 || COMPILE_TEST
+ depends on PCI_MSI_IRQ_DOMAIN
+ select PCIE_DW_HOST
+ help
+ Enables support for the PCIe controller in the Baikal-T1 SoC to work
+ in host mode. It's based on the Synopsys DWC PCIe v4.60a IP-core.
+
config PCIE_ROCKCHIP_DW_HOST
bool "Rockchip DesignWare PCIe controller"
select PCIE_DW
diff --git a/drivers/pci/controller/dwc/Makefile b/drivers/pci/controller/dwc/Makefile
index 8ba7b67f5e50..bf5c311875a1 100644
--- a/drivers/pci/controller/dwc/Makefile
+++ b/drivers/pci/controller/dwc/Makefile
@@ -3,6 +3,7 @@ obj-$(CONFIG_PCIE_DW) += pcie-designware.o
obj-$(CONFIG_PCIE_DW_HOST) += pcie-designware-host.o
obj-$(CONFIG_PCIE_DW_EP) += pcie-designware-ep.o
obj-$(CONFIG_PCIE_DW_PLAT) += pcie-designware-plat.o
+obj-$(CONFIG_PCIE_BT1) += pcie-bt1.o
obj-$(CONFIG_PCI_DRA7XX) += pci-dra7xx.o
obj-$(CONFIG_PCI_EXYNOS) += pci-exynos.o
obj-$(CONFIG_PCIE_FU740) += pcie-fu740.o
diff --git a/drivers/pci/controller/dwc/pcie-bt1.c b/drivers/pci/controller/dwc/pcie-bt1.c
new file mode 100644
index 000000000000..8cb6a9b3e39d
--- /dev/null
+++ b/drivers/pci/controller/dwc/pcie-bt1.c
@@ -0,0 +1,638 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2021 BAIKAL ELECTRONICS, JSC
+ *
+ * Authors:
+ * Vadim Vlasov <[email protected]>
+ * Serge Semin <[email protected]>
+ *
+ * Baikal-T1 PCIe controller driver
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/types.h>
+
+#include "pcie-designware.h"
+
+/* Baikal-T1 System CCU control registers */
+#define BT1_CCU_PCIE_CLKC 0x140
+#define BT1_CCU_PCIE_REQ_PCS_CLK BIT(16)
+#define BT1_CCU_PCIE_REQ_MAC_CLK BIT(17)
+#define BT1_CCU_PCIE_REQ_PIPE_CLK BIT(18)
+
+#define BT1_CCU_PCIE_RSTC 0x144
+#define BT1_CCU_PCIE_REQ_LINK_RST BIT(13)
+#define BT1_CCU_PCIE_REQ_SMLH_RST BIT(14)
+#define BT1_CCU_PCIE_REQ_PHY_RST BIT(16)
+#define BT1_CCU_PCIE_REQ_CORE_RST BIT(24)
+#define BT1_CCU_PCIE_REQ_STICKY_RST BIT(26)
+#define BT1_CCU_PCIE_REQ_NSTICKY_RST BIT(27)
+
+#define BT1_CCU_PCIE_PMSC 0x148
+#define BT1_CCU_PCIE_LTSSM_STATE_MASK GENMASK(5, 0)
+#define BT1_CCU_PCIE_LTSSM_DET_QUIET 0x00
+#define BT1_CCU_PCIE_LTSSM_DET_ACT 0x01
+#define BT1_CCU_PCIE_LTSSM_POLL_ACT 0x02
+#define BT1_CCU_PCIE_LTSSM_POLL_COMP 0x03
+#define BT1_CCU_PCIE_LTSSM_POLL_CONF 0x04
+#define BT1_CCU_PCIE_LTSSM_PRE_DET_QUIET 0x05
+#define BT1_CCU_PCIE_LTSSM_DET_WAIT 0x06
+#define BT1_CCU_PCIE_LTSSM_CFG_LNKWD_START 0x07
+#define BT1_CCU_PCIE_LTSSM_CFG_LNKWD_ACEPT 0x08
+#define BT1_CCU_PCIE_LTSSM_CFG_LNNUM_WAIT 0x09
+#define BT1_CCU_PCIE_LTSSM_CFG_LNNUM_ACEPT 0x0a
+#define BT1_CCU_PCIE_LTSSM_CFG_COMPLETE 0x0b
+#define BT1_CCU_PCIE_LTSSM_CFG_IDLE 0x0c
+#define BT1_CCU_PCIE_LTSSM_RCVR_LOCK 0x0d
+#define BT1_CCU_PCIE_LTSSM_RCVR_SPEED 0x0e
+#define BT1_CCU_PCIE_LTSSM_RCVR_RCVRCFG 0x0f
+#define BT1_CCU_PCIE_LTSSM_RCVR_IDLE 0x10
+#define BT1_CCU_PCIE_LTSSM_L0 0x11
+#define BT1_CCU_PCIE_LTSSM_L0S 0x12
+#define BT1_CCU_PCIE_LTSSM_L123_SEND_IDLE 0x13
+#define BT1_CCU_PCIE_LTSSM_L1_IDLE 0x14
+#define BT1_CCU_PCIE_LTSSM_L2_IDLE 0x15
+#define BT1_CCU_PCIE_LTSSM_L2_WAKE 0x16
+#define BT1_CCU_PCIE_LTSSM_DIS_ENTRY 0x17
+#define BT1_CCU_PCIE_LTSSM_DIS_IDLE 0x18
+#define BT1_CCU_PCIE_LTSSM_DISABLE 0x19
+#define BT1_CCU_PCIE_LTSSM_LPBK_ENTRY 0x1a
+#define BT1_CCU_PCIE_LTSSM_LPBK_ACTIVE 0x1b
+#define BT1_CCU_PCIE_LTSSM_LPBK_EXIT 0x1c
+#define BT1_CCU_PCIE_LTSSM_LPBK_EXIT_TOUT 0x1d
+#define BT1_CCU_PCIE_LTSSM_HOT_RST_ENTRY 0x1e
+#define BT1_CCU_PCIE_LTSSM_HOT_RST 0x1f
+#define BT1_CCU_PCIE_LTSSM_RCVR_EQ0 0x20
+#define BT1_CCU_PCIE_LTSSM_RCVR_EQ1 0x21
+#define BT1_CCU_PCIE_LTSSM_RCVR_EQ2 0x22
+#define BT1_CCU_PCIE_LTSSM_RCVR_EQ3 0x23
+#define BT1_CCU_PCIE_SMLH_LINKUP BIT(6)
+#define BT1_CCU_PCIE_RDLH_LINKUP BIT(7)
+#define BT1_CCU_PCIE_PM_LINKSTATE_L0S BIT(8)
+#define BT1_CCU_PCIE_PM_LINKSTATE_L1 BIT(9)
+#define BT1_CCU_PCIE_PM_LINKSTATE_L2 BIT(10)
+#define BT1_CCU_PCIE_L1_PENDING BIT(12)
+#define BT1_CCU_PCIE_REQ_EXIT_L1 BIT(14)
+#define BT1_CCU_PCIE_LTSSM_RCVR_EQ BIT(15)
+#define BT1_CCU_PCIE_PM_DSTAT_MASK GENMASK(18, 16)
+#define BT1_CCU_PCIE_PM_PME_EN BIT(20)
+#define BT1_CCU_PCIE_PM_PME_STATUS BIT(21)
+#define BT1_CCU_PCIE_AUX_PM_EN BIT(22)
+#define BT1_CCU_PCIE_AUX_PWR_DET BIT(23)
+#define BT1_CCU_PCIE_WAKE_DET BIT(24)
+#define BT1_CCU_PCIE_TURNOFF_REQ BIT(30)
+#define BT1_CCU_PCIE_TURNOFF_ACK BIT(31)
+
+#define BT1_CCU_PCIE_GENC 0x14c
+#define BT1_CCU_PCIE_LTSSM_EN BIT(1)
+#define BT1_CCU_PCIE_DBI2_MODE BIT(2)
+#define BT1_CCU_PCIE_MGMT_EN BIT(3)
+#define BT1_CCU_PCIE_RXLANE_FLIP_EN BIT(16)
+#define BT1_CCU_PCIE_TXLANE_FLIP_EN BIT(17)
+#define BT1_CCU_PCIE_SLV_XFER_PEND BIT(24)
+#define BT1_CCU_PCIE_RCV_XFER_PEND BIT(25)
+#define BT1_CCU_PCIE_DBI_XFER_PEND BIT(26)
+#define BT1_CCU_PCIE_DMA_XFER_PEND BIT(27)
+
+#define BT1_CCU_PCIE_LTSSM_LINKUP(_pmsc) \
+({ \
+ int __state = FIELD_GET(BT1_CCU_PCIE_LTSSM_STATE_MASK, _pmsc); \
+ __state >= BT1_CCU_PCIE_LTSSM_L0 && __state <= BT1_CCU_PCIE_LTSSM_L2_WAKE; \
+})
+
+/* Baikal-T1 PCIe specific control registers */
+#define BT1_PCIE_AXI2MGM_LANENUM 0xd04
+#define BT1_PCIE_AXI2MGM_LANESEL_MASK GENMASK(3, 0)
+
+#define BT1_PCIE_AXI2MGM_ADDRCTL 0xd08
+#define BT1_PCIE_AXI2MGM_PHYREG_ADDR_MASK GENMASK(20, 0)
+#define BT1_PCIE_AXI2MGM_READ_FLAG BIT(29)
+#define BT1_PCIE_AXI2MGM_DONE BIT(30)
+#define BT1_PCIE_AXI2MGM_BUSY BIT(31)
+
+#define BT1_PCIE_AXI2MGM_WRITEDATA 0xd0c
+#define BT1_PCIE_AXI2MGM_WDATA GENMASK(15, 0)
+
+#define BT1_PCIE_AXI2MGM_READDATA 0xd10
+#define BT1_PCIE_AXI2MGM_RDATA GENMASK(15, 0)
+
+/* General Baikal-T1 PCIe interface resources */
+#define BT1_PCIE_NUM_CLKS ARRAY_SIZE(bt1_pcie_clks)
+#define BT1_PCIE_NUM_APP_RSTS ARRAY_SIZE(bt1_pcie_app_rsts)
+#define BT1_PCIE_NUM_CORE_RSTS ARRAY_SIZE(bt1_pcie_core_rsts)
+
+enum bt1_pcie_core_rst {
+ BT1_PCIE_NON_STICKY_RST,
+ BT1_PCIE_STICKY_RST,
+ BT1_PCIE_CORE_RST,
+ BT1_PCIE_PIPE_RST,
+ BT1_PCIE_PHY_RST,
+ BT1_PCIE_HOT_RST,
+ BT1_PCIE_PWR_RST,
+};
+
+static const enum dw_pcie_clk bt1_pcie_clks[] = {
+ DW_PCIE_DBI_CLK, DW_PCIE_MSTR_CLK, DW_PCIE_SLV_CLK, DW_PCIE_REF_CLK
+};
+
+static const enum dw_pcie_app_rst bt1_pcie_app_rsts[] = {
+ DW_PCIE_MSTR_RST, DW_PCIE_SLV_RST
+};
+
+static const enum dw_pcie_core_rst bt1_pcie_core_rsts[] = {
+ [BT1_PCIE_NON_STICKY_RST] = DW_PCIE_NON_STICKY_RST,
+ [BT1_PCIE_STICKY_RST] = DW_PCIE_STICKY_RST,
+ [BT1_PCIE_CORE_RST] = DW_PCIE_CORE_RST,
+ [BT1_PCIE_PIPE_RST] = DW_PCIE_PIPE_RST,
+ [BT1_PCIE_PHY_RST] = DW_PCIE_PHY_RST,
+ [BT1_PCIE_HOT_RST] = DW_PCIE_HOT_RST,
+ [BT1_PCIE_PWR_RST] = DW_PCIE_PWR_RST,
+};
+
+struct bt1_pcie {
+ struct dw_pcie dw;
+ struct platform_device *pdev;
+ struct regmap *sys_regs;
+
+ struct clk_bulk_data clks[BT1_PCIE_NUM_CLKS];
+ struct reset_control_bulk_data app_rsts[BT1_PCIE_NUM_APP_RSTS];
+ struct reset_control_bulk_data core_rsts[BT1_PCIE_NUM_CORE_RSTS];
+ struct gpio_desc *pe_rst;
+};
+#define to_bt1_pcie(_dw) container_of(_dw, struct bt1_pcie, dw)
+
+/*
+ * Baikal-T1 MMIO space must be read/written by the dword-aligned
+ * instructions. Note the methods are optimized to have the dword operations
+ * performed with minimum overhead as the most frequently used ones.
+ */
+static int bt1_pcie_read_mmio(void __iomem *addr, int size, u32 *val)
+{
+ unsigned int ofs = (uintptr_t)addr & 0x3;
+
+ if (!IS_ALIGNED((uintptr_t)addr, size))
+ return PCIBIOS_BAD_REGISTER_NUMBER;
+
+ *val = readl(addr - ofs) >> ofs * BITS_PER_BYTE;
+ if (size == 4) {
+ return PCIBIOS_SUCCESSFUL;
+ } else if (size == 2) {
+ *val &= 0xffff;
+ return PCIBIOS_SUCCESSFUL;
+ } else if (size == 1) {
+ *val &= 0xff;
+ return PCIBIOS_SUCCESSFUL;
+ }
+
+ return PCIBIOS_BAD_REGISTER_NUMBER;
+}
+
+static int bt1_pcie_write_mmio(void __iomem *addr, int size, u32 val)
+{
+ unsigned int ofs = (uintptr_t)addr & 0x3;
+ u32 tmp, mask;
+
+ if (!IS_ALIGNED((uintptr_t)addr, size))
+ return PCIBIOS_BAD_REGISTER_NUMBER;
+
+ if (size == 4) {
+ writel(val, addr);
+ return PCIBIOS_SUCCESSFUL;
+ } else if (size == 2 || size == 1) {
+ mask = GENMASK(size * BITS_PER_BYTE - 1, 0);
+ tmp = readl(addr - ofs) & ~(mask << ofs * BITS_PER_BYTE);
+ tmp |= (val & mask) << ofs * BITS_PER_BYTE;
+ writel(tmp, addr - ofs);
+ return PCIBIOS_SUCCESSFUL;
+ }
+
+ return PCIBIOS_BAD_REGISTER_NUMBER;
+}
+
+static u32 bt1_pcie_read_dbi(struct dw_pcie *pci, void __iomem *base, u32 reg,
+ size_t size)
+{
+ int ret;
+ u32 val;
+
+ ret = bt1_pcie_read_mmio(base + reg, size, &val);
+ if (ret != PCIBIOS_SUCCESSFUL) {
+ dev_err(pci->dev, "Read DBI address failed\n");
+ return ~0U;
+ }
+
+ return val;
+}
+
+static void bt1_pcie_write_dbi(struct dw_pcie *pci, void __iomem *base, u32 reg,
+ size_t size, u32 val)
+{
+ int ret;
+
+ ret = bt1_pcie_write_mmio(base + reg, size, val);
+ if (ret != PCIBIOS_SUCCESSFUL)
+ dev_err(pci->dev, "Write DBI address failed\n");
+}
+
+static void bt1_pcie_write_dbi2(struct dw_pcie *pci, void __iomem *base, u32 reg,
+ size_t size, u32 val)
+{
+ struct bt1_pcie *btpci = to_bt1_pcie(pci);
+ int ret;
+
+ regmap_update_bits(btpci->sys_regs, BT1_CCU_PCIE_GENC,
+ BT1_CCU_PCIE_DBI2_MODE, BT1_CCU_PCIE_DBI2_MODE);
+
+ ret = bt1_pcie_write_mmio(base + reg, size, val);
+ if (ret != PCIBIOS_SUCCESSFUL)
+ dev_err(pci->dev, "Write DBI2 address failed\n");
+
+ regmap_update_bits(btpci->sys_regs, BT1_CCU_PCIE_GENC,
+ BT1_CCU_PCIE_DBI2_MODE, 0);
+}
+
+static int bt1_pcie_start_ltssm(struct dw_pcie *pci)
+{
+ struct bt1_pcie *btpci = to_bt1_pcie(pci);
+ u32 val;
+ int ret;
+
+ /*
+ * Enable LTSSM and make sure it was able to establish both PHY and
+ * data links. This procedure shall work fine to reach 2.5 GT/s speed.
+ */
+ regmap_update_bits(btpci->sys_regs, BT1_CCU_PCIE_GENC,
+ BT1_CCU_PCIE_LTSSM_EN, BT1_CCU_PCIE_LTSSM_EN);
+
+ ret = regmap_read_poll_timeout(btpci->sys_regs, BT1_CCU_PCIE_PMSC, val,
+ (val & BT1_CCU_PCIE_SMLH_LINKUP),
+ 1000, 1000000);
+ if (ret) {
+ dev_err(pci->dev, "LTSSM failed to set PHY link up\n");
+ return ret;
+ }
+
+ ret = regmap_read_poll_timeout(btpci->sys_regs, BT1_CCU_PCIE_PMSC, val,
+ (val & BT1_CCU_PCIE_RDLH_LINKUP),
+ 1000, 1000000);
+ if (ret) {
+ dev_err(pci->dev, "LTSSM failed to set data link up\n");
+ return ret;
+ }
+
+ /*
+ * Activate direct speed change after the link is established in an
+ * attempt to reach a higher bus performance (up to Gen.3 - 8.0 GT/s).
+ * This is required at least to get 8.0 GT/s speed.
+ */
+ val = dw_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL);
+ val |= PORT_LOGIC_SPEED_CHANGE;
+ dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, val);
+
+ ret = regmap_read_poll_timeout(btpci->sys_regs, BT1_CCU_PCIE_PMSC, val,
+ BT1_CCU_PCIE_LTSSM_LINKUP(val),
+ 1000, 1000000);
+ if (ret)
+ dev_err(pci->dev, "LTSSM failed to get into L0 state\n");
+
+ return ret;
+}
+
+static void bt1_pcie_stop_ltssm(struct dw_pcie *pci)
+{
+ struct bt1_pcie *btpci = to_bt1_pcie(pci);
+
+ regmap_update_bits(btpci->sys_regs, BT1_CCU_PCIE_GENC,
+ BT1_CCU_PCIE_LTSSM_EN, 0);
+}
+
+struct dw_pcie_ops bt1_pcie_dw_ops = {
+ .read_dbi = bt1_pcie_read_dbi,
+ .write_dbi = bt1_pcie_write_dbi,
+ .write_dbi2 = bt1_pcie_write_dbi2,
+ .start_link = bt1_pcie_start_ltssm,
+ .stop_link = bt1_pcie_stop_ltssm,
+};
+
+static int bt1_pcie_get_res(struct bt1_pcie *btpci)
+{
+ struct device *dev = btpci->dw.dev;
+ int ret;
+
+ /* AXI-interface is configured with 64-bit address bus width */
+ ret = dma_coerce_mask_and_coherent(&btpci->dw.pp.bridge->dev,
+ DMA_BIT_MASK(64));
+ if (ret) {
+ ret = dma_set_mask_and_coherent(&btpci->dw.pp.bridge->dev,
+ DMA_BIT_MASK(32));
+ if (ret)
+ return ret;
+ }
+
+ /* These CSRs are in MMIO so we won't check the regmap-methods status */
+ btpci->sys_regs = syscon_regmap_lookup_by_phandle(dev->of_node, "syscon");
+ if (IS_ERR(btpci->sys_regs))
+ return dev_err_probe(dev, PTR_ERR(btpci->sys_regs),
+ "Failed to get syscon\n");
+
+ ret = devm_clk_bulk_get(dev, BT1_PCIE_NUM_CLKS, btpci->clks);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get clocks\n");
+
+ ret = devm_reset_control_bulk_get_exclusive(dev, BT1_PCIE_NUM_APP_RSTS,
+ btpci->app_rsts);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get app resets\n");
+
+ ret = devm_reset_control_bulk_get_exclusive(dev, BT1_PCIE_NUM_CORE_RSTS,
+ btpci->core_rsts);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get core resets\n");
+
+ btpci->pe_rst = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(btpci->pe_rst))
+ return dev_err_probe(dev, PTR_ERR(btpci->pe_rst),
+ "Failed to get PERST#\n");
+
+ return 0;
+}
+
+static void bt1_pcie_full_stop_bus(struct bt1_pcie *btpci, bool init)
+{
+ struct device *dev = btpci->dw.dev;
+ int ret;
+
+ /* Disable LTSSM for sure */
+ regmap_update_bits(btpci->sys_regs, BT1_CCU_PCIE_GENC,
+ BT1_CCU_PCIE_LTSSM_EN, 0);
+
+ /*
+ * Application reset controls are trigger-based so de-assert the core
+ * resets only.
+ */
+ ret = reset_control_bulk_assert(BT1_PCIE_NUM_CORE_RSTS, btpci->core_rsts);
+ if (ret)
+ dev_err(dev, "Failed to assert core resets\n");
+
+ /*
+ * Clocks are disabled by default at least in accordance with the clk
+ * enable counter value on init stage.
+ */
+ if (!init)
+ clk_bulk_disable_unprepare(BT1_PCIE_NUM_CLKS, btpci->clks);
+
+ /* The peripheral devices are unavailable anyway so reset them too */
+ gpiod_set_value_cansleep(btpci->pe_rst, 1);
+
+ /* Make sure the reset is settled */
+ usleep_range(1, 10);
+}
+
+/*
+ * Implements the cold reset procedure in accordance with the reference manual
+ * and available PM signals.
+ */
+static int bt1_pcie_cold_start_bus(struct bt1_pcie *btpci)
+{
+ struct device *dev = btpci->dw.dev;
+ u32 val;
+ int ret;
+
+ /* First get out of the Power/Hot reset state */
+ ret = reset_control_deassert(btpci->core_rsts[BT1_PCIE_PWR_RST].rstc);
+ if (ret) {
+ dev_err(dev, "Failed to deassert PHY reset\n");
+ return ret;
+ }
+
+ ret = reset_control_deassert(btpci->core_rsts[BT1_PCIE_HOT_RST].rstc);
+ if (ret) {
+ dev_err(dev, "Failed to deassert hot reset\n");
+ goto err_assert_pwr_rst;
+ }
+
+ /* Wait for the PM-core to stop requesting the PHY reset */
+ ret = regmap_read_poll_timeout(btpci->sys_regs, BT1_CCU_PCIE_RSTC, val,
+ !(val & BT1_CCU_PCIE_REQ_PHY_RST), 1, 1000);
+ if (ret) {
+ dev_err(dev, "Timed out waiting for PM to stop PHY resetting\n");
+ goto err_assert_hot_rst;
+ }
+
+ ret = reset_control_deassert(btpci->core_rsts[BT1_PCIE_PHY_RST].rstc);
+ if (ret) {
+ dev_err(dev, "Failed to deassert PHY reset\n");
+ goto err_assert_hot_rst;
+ }
+
+ /* Clocks can be now enabled, but the ref one is crucial at this stage */
+ ret = clk_bulk_prepare_enable(BT1_PCIE_NUM_CLKS, btpci->clks);
+ if (ret) {
+ dev_err(dev, "Failed to enable ref clocks\n");
+ goto err_assert_phy_rst;
+ }
+
+ /* Wait for the PM to stop requesting the controller core reset */
+ ret = regmap_read_poll_timeout(btpci->sys_regs, BT1_CCU_PCIE_RSTC, val,
+ !(val & BT1_CCU_PCIE_REQ_CORE_RST), 1, 1000);
+ if (ret) {
+ dev_err(dev, "Timed out waiting for PM to stop core resetting\n");
+ goto err_clk_disable;
+ }
+
+ /* PCS-PIPE interface and controller core can be now activated */
+ ret = reset_control_deassert(btpci->core_rsts[BT1_PCIE_PIPE_RST].rstc);
+ if (ret) {
+ dev_err(dev, "Failed to deassert PIPE reset\n");
+ goto err_clk_disable;
+ }
+
+ ret = reset_control_deassert(btpci->core_rsts[BT1_PCIE_CORE_RST].rstc);
+ if (ret) {
+ dev_err(dev, "Failed to deassert core reset\n");
+ goto err_assert_pipe_rst;
+ }
+
+ /* It's recommended to reset the core and application logic together */
+ ret = reset_control_bulk_reset(BT1_PCIE_NUM_APP_RSTS, btpci->app_rsts);
+ if (ret) {
+ dev_err(dev, "Failed to reset app domain\n");
+ goto err_assert_core_rst;
+ }
+
+ /* Sticky/Non-sticky CSR flags can be now unreset too */
+ ret = reset_control_deassert(btpci->core_rsts[BT1_PCIE_STICKY_RST].rstc);
+ if (ret) {
+ dev_err(dev, "Failed to deassert sticky reset\n");
+ goto err_assert_core_rst;
+ }
+
+ ret = reset_control_deassert(btpci->core_rsts[BT1_PCIE_NON_STICKY_RST].rstc);
+ if (ret) {
+ dev_err(dev, "Failed to deassert non-sticky reset\n");
+ goto err_assert_sticky_rst;
+ }
+
+ /* Activate the PCIe bus peripheral devices */
+ gpiod_set_value_cansleep(btpci->pe_rst, 0);
+
+ /* Make sure the state is settled (LTSSM is still disabled though) */
+ usleep_range(1, 10);
+
+ return 0;
+
+err_assert_sticky_rst:
+ reset_control_assert(btpci->core_rsts[BT1_PCIE_STICKY_RST].rstc);
+
+err_assert_core_rst:
+ reset_control_assert(btpci->core_rsts[BT1_PCIE_CORE_RST].rstc);
+
+err_assert_pipe_rst:
+ reset_control_assert(btpci->core_rsts[BT1_PCIE_PIPE_RST].rstc);
+
+err_clk_disable:
+ clk_bulk_disable_unprepare(BT1_PCIE_NUM_CLKS, btpci->clks);
+
+err_assert_phy_rst:
+ reset_control_assert(btpci->core_rsts[BT1_PCIE_PHY_RST].rstc);
+
+err_assert_hot_rst:
+ reset_control_assert(btpci->core_rsts[BT1_PCIE_HOT_RST].rstc);
+
+err_assert_pwr_rst:
+ reset_control_assert(btpci->core_rsts[BT1_PCIE_PWR_RST].rstc);
+
+ return ret;
+}
+
+static int bt1_pcie_host_init(struct pcie_port *pp)
+{
+ struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+ struct bt1_pcie *btpci = to_bt1_pcie(pci);
+ int ret;
+
+ ret = bt1_pcie_get_res(btpci);
+ if (ret)
+ return ret;
+
+ bt1_pcie_full_stop_bus(btpci, true);
+
+ return bt1_pcie_cold_start_bus(btpci);
+}
+
+static void bt1_pcie_host_deinit(struct pcie_port *pp)
+{
+ struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+ struct bt1_pcie *btpci = to_bt1_pcie(pci);
+
+ bt1_pcie_full_stop_bus(btpci, false);
+}
+
+struct dw_pcie_host_ops bt1_pcie_host_ops = {
+ .host_init = bt1_pcie_host_init,
+ .host_deinit = bt1_pcie_host_deinit,
+};
+
+static struct bt1_pcie *bt1_pcie_create_data(struct platform_device *pdev)
+{
+ struct bt1_pcie *btpci;
+ int i;
+
+ btpci = devm_kzalloc(&pdev->dev, sizeof(*btpci), GFP_KERNEL);
+ if (!btpci)
+ return ERR_PTR(-ENOMEM);
+
+ btpci->pdev = pdev;
+
+ for (i = 0; i < BT1_PCIE_NUM_CLKS; ++i)
+ btpci->clks[i].id = dw_pcie_clk_name(bt1_pcie_clks[i]);
+
+ for (i = 0; i < BT1_PCIE_NUM_APP_RSTS; ++i)
+ btpci->app_rsts[i].id = dw_pcie_app_rst_name(bt1_pcie_app_rsts[i]);
+
+ for (i = 0; i < BT1_PCIE_NUM_CORE_RSTS; ++i)
+ btpci->core_rsts[i].id = dw_pcie_core_rst_name(bt1_pcie_core_rsts[i]);
+
+ platform_set_drvdata(pdev, btpci);
+
+ return btpci;
+}
+
+static int bt1_pcie_add_dw_port(struct bt1_pcie *btpci)
+{
+ struct device *dev = &btpci->pdev->dev;
+ int ret;
+
+ ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
+ if (ret)
+ return ret;
+
+ btpci->dw.version = DW_PCIE_VER_460A;
+ btpci->dw.dev = dev;
+ btpci->dw.ops = &bt1_pcie_dw_ops;
+
+ btpci->dw.pp.num_vectors = MAX_MSI_IRQS;
+ btpci->dw.pp.ops = &bt1_pcie_host_ops;
+
+ ret = dw_pcie_host_init(&btpci->dw.pp);
+ if (ret)
+ dev_err_probe(dev, ret, "Failed to initialize DWC PCIe host\n");
+
+ return ret;
+}
+
+static void bt1_pcie_del_dw_port(struct bt1_pcie *btpci)
+{
+ dw_pcie_host_deinit(&btpci->dw.pp);
+}
+
+static int bt1_pcie_probe(struct platform_device *pdev)
+{
+ struct bt1_pcie *btpci;
+
+ btpci = bt1_pcie_create_data(pdev);
+ if (IS_ERR(btpci))
+ return PTR_ERR(btpci);
+
+ return bt1_pcie_add_dw_port(btpci);
+}
+
+static int bt1_pcie_remove(struct platform_device *pdev)
+{
+ struct bt1_pcie *btpci = platform_get_drvdata(pdev);
+
+ bt1_pcie_del_dw_port(btpci);
+
+ return 0;
+}
+
+static const struct of_device_id bt1_pcie_of_match[] = {
+ { .compatible = "baikal,bt1-pcie" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, bt1_pcie_of_match);
+
+static struct platform_driver bt1_pcie_driver = {
+ .probe = bt1_pcie_probe,
+ .remove = bt1_pcie_remove,
+ .driver = {
+ .name = "bt1-pcie",
+ .of_match_table = bt1_pcie_of_match,
+ },
+};
+module_platform_driver(bt1_pcie_driver);
+
+MODULE_AUTHOR("Serge Semin <[email protected]>");
+MODULE_DESCRIPTION("Baikal-T1 PCIe driver");
+MODULE_LICENSE("GPL v2");
--
2.35.1

2022-03-28 22:16:25

by Serge Semin

[permalink] [raw]
Subject: [PATCH 06/16] PCI: dwc: Introduce Synopsys IP-core versions/types interface

Instead of manual DW PCIe data version field comparison let's use a handy
macro-based interface in order to shorten out the statements, simplify the
corresponding parts, improve the code readability and maintainability in
perspective when more complex version-based dependencies need to
implemented. Similar approaches have already been implemented in the DWC
USB3 and DW SPI drivers (though with some IP-core evolution specifics).

Signed-off-by: Serge Semin <[email protected]>
---
drivers/pci/controller/dwc/pci-keystone.c | 2 +-
drivers/pci/controller/dwc/pcie-designware.c | 8 ++++----
drivers/pci/controller/dwc/pcie-designware.h | 15 +++++++++++++++
3 files changed, 20 insertions(+), 5 deletions(-)

diff --git a/drivers/pci/controller/dwc/pci-keystone.c b/drivers/pci/controller/dwc/pci-keystone.c
index ec65355dd29b..8fbebeb923b8 100644
--- a/drivers/pci/controller/dwc/pci-keystone.c
+++ b/drivers/pci/controller/dwc/pci-keystone.c
@@ -1233,7 +1233,7 @@ static int __init ks_pcie_probe(struct platform_device *pdev)
goto err_get_sync;
}

- if (pci->version >= DW_PCIE_VER_480A)
+ if (dw_pcie_ver_is_ge(pci, 480A))
ret = ks_pcie_am654_set_mode(dev, mode);
else
ret = ks_pcie_set_mode(dev);
diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c
index 49c494d82042..d5ce19625769 100644
--- a/drivers/pci/controller/dwc/pcie-designware.c
+++ b/drivers/pci/controller/dwc/pcie-designware.c
@@ -313,7 +313,7 @@ static void dw_pcie_prog_outbound_atu_unroll(struct dw_pcie *pci, u8 func_no,
val = type | PCIE_ATU_FUNC_NUM(func_no);
if (upper_32_bits(limit_addr) > upper_32_bits(cpu_addr))
val |= PCIE_ATU_INCREASE_REGION_SIZE;
- if (pci->version == DW_PCIE_VER_490A)
+ if (dw_pcie_ver_is(pci, 490A))
val = dw_pcie_enable_ecrc(val);
dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL1, val);
dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2,
@@ -360,7 +360,7 @@ static void __dw_pcie_prog_outbound_atu(struct dw_pcie *pci, u8 func_no,
upper_32_bits(cpu_addr));
dw_pcie_writel_dbi(pci, PCIE_ATU_LIMIT,
lower_32_bits(limit_addr));
- if (pci->version >= DW_PCIE_VER_460A)
+ if (dw_pcie_ver_is_ge(pci, 460A))
dw_pcie_writel_dbi(pci, PCIE_ATU_UPPER_LIMIT,
upper_32_bits(limit_addr));
dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_TARGET,
@@ -369,9 +369,9 @@ static void __dw_pcie_prog_outbound_atu(struct dw_pcie *pci, u8 func_no,
upper_32_bits(pci_addr));
val = type | PCIE_ATU_FUNC_NUM(func_no);
if (upper_32_bits(limit_addr) > upper_32_bits(cpu_addr) &&
- pci->version >= DW_PCIE_VER_460A)
+ dw_pcie_ver_is_ge(pci, 460A))
val |= PCIE_ATU_INCREASE_REGION_SIZE;
- if (pci->version == DW_PCIE_VER_490A)
+ if (dw_pcie_ver_is(pci, 490A))
val = dw_pcie_enable_ecrc(val);
dw_pcie_writel_dbi(pci, PCIE_ATU_CR1, val);
dw_pcie_writel_dbi(pci, PCIE_ATU_CR2, PCIE_ATU_ENABLE);
diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h
index f70cbdedf5a3..1868773ecb91 100644
--- a/drivers/pci/controller/dwc/pcie-designware.h
+++ b/drivers/pci/controller/dwc/pcie-designware.h
@@ -28,6 +28,21 @@
#define DW_PCIE_VER_490A 0x3439302a
#define DW_PCIE_VER_520A 0x3532302a

+#define __dw_pcie_ver_cmp(_pci, _ver, _op) \
+ ((_pci)->version _op DW_PCIE_VER_ ## _ver)
+
+#define dw_pcie_ver_is(_pci, _ver) __dw_pcie_ver_cmp(_pci, _ver, ==)
+
+#define dw_pcie_ver_is_ge(_pci, _ver) __dw_pcie_ver_cmp(_pci, _ver, >=)
+
+#define dw_pcie_ver_type_is(_pci, _ver, _type) \
+ (__dw_pcie_ver_cmp(_pci, _ver, ==) && \
+ __dw_pcie_ver_cmp(_pci, TYPE_ ## _type, ==))
+
+#define dw_pcie_ver_type_is_ge(_pci, _ver, _type) \
+ (__dw_pcie_ver_cmp(_pci, _ver, ==) && \
+ __dw_pcie_ver_cmp(_pci, TYPE_ ## _type, >=))
+
/* Parameters for the waiting for link up routine */
#define LINK_WAIT_MAX_RETRIES 10
#define LINK_WAIT_USLEEP_MIN 90000
--
2.35.1

2022-03-28 22:21:23

by Serge Semin

[permalink] [raw]
Subject: [PATCH 09/16] PCI: dwc: Simplify in/outbound iATU setup methods

From maintainability and scalability points of view it has been wrong to
use different iATU inbound and outbound regions accessors for the viewport
and unrolled versions of the iATU CSRs mapping. Seeing the particular iATU
region-wise registers layout is almost fully compatible for different
IP-core versions, there were no much points in splitting the code up that
way since it was possible to implement a common windows setup methods for
both viewport and unrolled iATU CSRs spaces. While what we can observe in
the current driver implementation of these methods, is a lot of code
duplication, which consequently worsen the code readability,
maintainability and scalability. Note the current implementation is a bit
more performant than the one suggested in this commit since it implies
having less MMIO accesses. But the gain just doesn't worth having the
denoted difficulties especially seeing the iATU setup methods are mainly
called on the DW PCIe controller and peripheral devices initialization
stage.

Here we suggest to move the iATU viewport and unrolled CSR access
specifics in the dw_pcie_readl_atu() and dw_pcie_writel_atu() method, and
convert the dw_pcie_prog_outbound_atu() and
dw_pcie_prog_{ep_}inbound_atu() functions to being generic instead of
having a different methods for each viewport and unrolled types of iATU
CSRs mapping. Nothing complex really. First of all the dw_pcie_readl_atu()
and dw_pcie_writel_atu() are converted to accept relative iATU CSRs
address together with the iATU region direction (inbound or outbound) and
region index. If DW PCIe controller doesn't have the unrolled iATU CSRs
space, then the accessors will need to activate a iATU viewport based on
the specified direction and index, otherwise a base address for the
corresponding region CSRs will be calculated by means of the
PCIE_ATU_UNROLL_BASE() macro. The CSRs macro have been modified in
accordance with that logic in the pcie-designware.h header file.

The rest of the changes in this commit just concern converting the iATU
in-/out-bound setup methods and iATU regions detection procedure to be
compatible with the new accessors semantics. As a result we've dropped the
no more required dw_pcie_prog_outbound_atu_unroll(),
dw_pcie_prog_inbound_atu_unroll() and dw_pcie_iatu_detect_regions_unroll()
methods.

Note aside with the denoted code improvements, there is an additional
positive side effect of this change. If at some point an atomic iATU
configs setup procedure is required, it will be possible to be done with
no much effort just by adding the synchronization into the
dw_pcie_readl_atu() and dw_pcie_writel_atu() accessors.

Signed-off-by: Serge Semin <[email protected]>
---
drivers/pci/controller/dwc/pcie-designware.c | 301 ++++++-------------
drivers/pci/controller/dwc/pcie-designware.h | 50 ++-
2 files changed, 112 insertions(+), 239 deletions(-)

diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c
index b983128584ff..f1aa6e2e85fe 100644
--- a/drivers/pci/controller/dwc/pcie-designware.c
+++ b/drivers/pci/controller/dwc/pcie-designware.c
@@ -205,48 +205,67 @@ void dw_pcie_write_dbi2(struct dw_pcie *pci, u32 reg, size_t size, u32 val)
dev_err(pci->dev, "write DBI address failed\n");
}

-static u32 dw_pcie_readl_atu(struct dw_pcie *pci, u32 reg)
+static u32 dw_pcie_readl_atu(struct dw_pcie *pci, u32 dir, u32 region, u32 reg)
{
+ void __iomem *base;
int ret;
u32 val;

+ if (pci->iatu_unroll_enabled) {
+ base = pci->atu_base;
+ reg = reg + PCIE_ATU_UNROLL_BASE(dir, region);
+ } else {
+ base = pci->dbi_base;
+ reg = reg + PCIE_ATU_VIEWPORT_BASE;
+
+ dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, dir | region);
+ }
+
if (pci->ops && pci->ops->read_dbi)
- return pci->ops->read_dbi(pci, pci->atu_base, reg, 4);
+ return pci->ops->read_dbi(pci, base, reg, 4);

- ret = dw_pcie_read(pci->atu_base + reg, 4, &val);
+ ret = dw_pcie_read(base + reg, 4, &val);
if (ret)
dev_err(pci->dev, "Read ATU address failed\n");

return val;
}

-static void dw_pcie_writel_atu(struct dw_pcie *pci, u32 reg, u32 val)
+static void dw_pcie_writel_atu(struct dw_pcie *pci, u32 dir, u32 region,
+ u32 reg, u32 val)
{
+ void __iomem *base;
int ret;

+ if (pci->iatu_unroll_enabled) {
+ base = pci->atu_base;
+ reg = reg + PCIE_ATU_UNROLL_BASE(dir, region);
+ } else {
+ base = pci->dbi_base;
+ reg = reg + PCIE_ATU_VIEWPORT_BASE;
+
+ dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, dir | region);
+ }
+
if (pci->ops && pci->ops->write_dbi) {
- pci->ops->write_dbi(pci, pci->atu_base, reg, 4, val);
+ pci->ops->write_dbi(pci, base, reg, 4, val);
return;
}

- ret = dw_pcie_write(pci->atu_base + reg, 4, val);
+ ret = dw_pcie_write(base + reg, 4, val);
if (ret)
dev_err(pci->dev, "Write ATU address failed\n");
}

-static u32 dw_pcie_readl_ob_unroll(struct dw_pcie *pci, u32 index, u32 reg)
+static inline u32 dw_pcie_readl_atu_ob(struct dw_pcie *pci, u32 region, u32 reg)
{
- u32 offset = PCIE_GET_ATU_OUTB_UNR_REG_OFFSET(index);
-
- return dw_pcie_readl_atu(pci, offset + reg);
+ return dw_pcie_readl_atu(pci, PCIE_ATU_REGION_DIR_OB, region, reg);
}

-static void dw_pcie_writel_ob_unroll(struct dw_pcie *pci, u32 index, u32 reg,
- u32 val)
+static inline void dw_pcie_writel_atu_ob(struct dw_pcie *pci, u32 region, u32 reg,
+ u32 val)
{
- u32 offset = PCIE_GET_ATU_OUTB_UNR_REG_OFFSET(index);
-
- dw_pcie_writel_atu(pci, offset + reg, val);
+ dw_pcie_writel_atu(pci, PCIE_ATU_REGION_DIR_OB, region, reg, val);
}

static inline u32 dw_pcie_enable_ecrc(u32 val)
@@ -290,50 +309,6 @@ static inline u32 dw_pcie_enable_ecrc(u32 val)
return val | PCIE_ATU_TD;
}

-static void dw_pcie_prog_outbound_atu_unroll(struct dw_pcie *pci, u8 func_no,
- int index, int type,
- u64 cpu_addr, u64 pci_addr,
- u64 size)
-{
- u32 retries, val;
- u64 limit_addr = cpu_addr + size - 1;
-
- dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_LOWER_BASE,
- lower_32_bits(cpu_addr));
- dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_UPPER_BASE,
- upper_32_bits(cpu_addr));
- dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_LOWER_LIMIT,
- lower_32_bits(limit_addr));
- dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_UPPER_LIMIT,
- upper_32_bits(limit_addr));
- dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_LOWER_TARGET,
- lower_32_bits(pci_addr));
- dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_UPPER_TARGET,
- upper_32_bits(pci_addr));
- val = type | PCIE_ATU_FUNC_NUM(func_no);
- if (upper_32_bits(limit_addr) > upper_32_bits(cpu_addr))
- val |= PCIE_ATU_INCREASE_REGION_SIZE;
- if (dw_pcie_ver_is(pci, 490A))
- val = dw_pcie_enable_ecrc(val);
- dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL1, val);
- dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2,
- PCIE_ATU_ENABLE);
-
- /*
- * Make sure ATU enable takes effect before any subsequent config
- * and I/O accesses.
- */
- for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) {
- val = dw_pcie_readl_ob_unroll(pci, index,
- PCIE_ATU_UNR_REGION_CTRL2);
- if (val & PCIE_ATU_ENABLE)
- return;
-
- mdelay(LINK_WAIT_IATU);
- }
- dev_err(pci->dev, "Outbound iATU is not being enabled\n");
-}
-
static void __dw_pcie_prog_outbound_atu(struct dw_pcie *pci, u8 func_no,
int index, int type, u64 cpu_addr,
u64 pci_addr, u64 size)
@@ -344,49 +319,46 @@ static void __dw_pcie_prog_outbound_atu(struct dw_pcie *pci, u8 func_no,
if (pci->ops && pci->ops->cpu_addr_fixup)
cpu_addr = pci->ops->cpu_addr_fixup(pci, cpu_addr);

- if (pci->iatu_unroll_enabled) {
- dw_pcie_prog_outbound_atu_unroll(pci, func_no, index, type,
- cpu_addr, pci_addr, size);
- return;
- }
-
limit_addr = cpu_addr + size - 1;

- dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT,
- PCIE_ATU_REGION_OUTBOUND | index);
- dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_BASE,
- lower_32_bits(cpu_addr));
- dw_pcie_writel_dbi(pci, PCIE_ATU_UPPER_BASE,
- upper_32_bits(cpu_addr));
- dw_pcie_writel_dbi(pci, PCIE_ATU_LIMIT,
- lower_32_bits(limit_addr));
+ dw_pcie_writel_atu_ob(pci, index, PCIE_ATU_LOWER_BASE,
+ lower_32_bits(cpu_addr));
+ dw_pcie_writel_atu_ob(pci, index, PCIE_ATU_UPPER_BASE,
+ upper_32_bits(cpu_addr));
+
+ dw_pcie_writel_atu_ob(pci, index, PCIE_ATU_LIMIT,
+ lower_32_bits(limit_addr));
if (dw_pcie_ver_is_ge(pci, 460A))
- dw_pcie_writel_dbi(pci, PCIE_ATU_UPPER_LIMIT,
- upper_32_bits(limit_addr));
- dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_TARGET,
- lower_32_bits(pci_addr));
- dw_pcie_writel_dbi(pci, PCIE_ATU_UPPER_TARGET,
- upper_32_bits(pci_addr));
+ dw_pcie_writel_atu_ob(pci, index, PCIE_ATU_UPPER_LIMIT,
+ upper_32_bits(limit_addr));
+
+ dw_pcie_writel_atu_ob(pci, index, PCIE_ATU_LOWER_TARGET,
+ lower_32_bits(pci_addr));
+ dw_pcie_writel_atu_ob(pci, index, PCIE_ATU_UPPER_TARGET,
+ upper_32_bits(pci_addr));
+
val = type | PCIE_ATU_FUNC_NUM(func_no);
if (upper_32_bits(limit_addr) > upper_32_bits(cpu_addr) &&
dw_pcie_ver_is_ge(pci, 460A))
val |= PCIE_ATU_INCREASE_REGION_SIZE;
if (dw_pcie_ver_is(pci, 490A))
val = dw_pcie_enable_ecrc(val);
- dw_pcie_writel_dbi(pci, PCIE_ATU_CR1, val);
- dw_pcie_writel_dbi(pci, PCIE_ATU_CR2, PCIE_ATU_ENABLE);
+ dw_pcie_writel_atu_ob(pci, index, PCIE_ATU_REGION_CTRL1, val);
+
+ dw_pcie_writel_atu_ob(pci, index, PCIE_ATU_REGION_CTRL2, PCIE_ATU_ENABLE);

/*
* Make sure ATU enable takes effect before any subsequent config
* and I/O accesses.
*/
for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) {
- val = dw_pcie_readl_dbi(pci, PCIE_ATU_CR2);
+ val = dw_pcie_readl_atu_ob(pci, index, PCIE_ATU_REGION_CTRL2);
if (val & PCIE_ATU_ENABLE)
return;

mdelay(LINK_WAIT_IATU);
}
+
dev_err(pci->dev, "Outbound iATU is not being enabled\n");
}

@@ -405,54 +377,15 @@ void dw_pcie_prog_ep_outbound_atu(struct dw_pcie *pci, u8 func_no, int index,
cpu_addr, pci_addr, size);
}

-static u32 dw_pcie_readl_ib_unroll(struct dw_pcie *pci, u32 index, u32 reg)
-{
- u32 offset = PCIE_GET_ATU_INB_UNR_REG_OFFSET(index);
-
- return dw_pcie_readl_atu(pci, offset + reg);
-}
-
-static void dw_pcie_writel_ib_unroll(struct dw_pcie *pci, u32 index, u32 reg,
- u32 val)
+static inline u32 dw_pcie_readl_atu_ib(struct dw_pcie *pci, u32 region, u32 reg)
{
- u32 offset = PCIE_GET_ATU_INB_UNR_REG_OFFSET(index);
-
- dw_pcie_writel_atu(pci, offset + reg, val);
+ return dw_pcie_readl_atu(pci, PCIE_ATU_REGION_DIR_IB, region, reg);
}

-static int dw_pcie_prog_inbound_atu_unroll(struct dw_pcie *pci, u8 func_no,
- int index, int type,
- u64 cpu_addr, u8 bar)
+static inline void dw_pcie_writel_atu_ib(struct dw_pcie *pci, u32 region, u32 reg,
+ u32 val)
{
- u32 retries, val;
-
- dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_LOWER_TARGET,
- lower_32_bits(cpu_addr));
- dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_UPPER_TARGET,
- upper_32_bits(cpu_addr));
-
- dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL1, type |
- PCIE_ATU_FUNC_NUM(func_no));
- dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2,
- PCIE_ATU_FUNC_NUM_MATCH_EN |
- PCIE_ATU_ENABLE |
- PCIE_ATU_BAR_MODE_ENABLE | (bar << 8));
-
- /*
- * Make sure ATU enable takes effect before any subsequent config
- * and I/O accesses.
- */
- for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) {
- val = dw_pcie_readl_ib_unroll(pci, index,
- PCIE_ATU_UNR_REGION_CTRL2);
- if (val & PCIE_ATU_ENABLE)
- return 0;
-
- mdelay(LINK_WAIT_IATU);
- }
- dev_err(pci->dev, "Inbound iATU is not being enabled\n");
-
- return -EBUSY;
+ dw_pcie_writel_atu(pci, PCIE_ATU_REGION_DIR_IB, region, reg, val);
}

int dw_pcie_prog_inbound_atu(struct dw_pcie *pci, u8 func_no, int index,
@@ -460,65 +393,51 @@ int dw_pcie_prog_inbound_atu(struct dw_pcie *pci, u8 func_no, int index,
{
u32 retries, val;

- if (pci->iatu_unroll_enabled)
- return dw_pcie_prog_inbound_atu_unroll(pci, func_no, index, type,
- cpu_addr, bar);
-
- dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, PCIE_ATU_REGION_INBOUND |
- index);
- dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_TARGET, lower_32_bits(cpu_addr));
- dw_pcie_writel_dbi(pci, PCIE_ATU_UPPER_TARGET, upper_32_bits(cpu_addr));
+ dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_LOWER_TARGET,
+ lower_32_bits(cpu_addr));
+ dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_UPPER_TARGET,
+ upper_32_bits(cpu_addr));

- dw_pcie_writel_dbi(pci, PCIE_ATU_CR1, type |
- PCIE_ATU_FUNC_NUM(func_no));
- dw_pcie_writel_dbi(pci, PCIE_ATU_CR2, PCIE_ATU_ENABLE |
- PCIE_ATU_FUNC_NUM_MATCH_EN |
- PCIE_ATU_BAR_MODE_ENABLE | (bar << 8));
+ dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_REGION_CTRL1, type |
+ PCIE_ATU_FUNC_NUM(func_no));
+ dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_REGION_CTRL2,
+ PCIE_ATU_ENABLE | PCIE_ATU_FUNC_NUM_MATCH_EN |
+ PCIE_ATU_BAR_MODE_ENABLE | (bar << 8));

/*
* Make sure ATU enable takes effect before any subsequent config
* and I/O accesses.
*/
for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) {
- val = dw_pcie_readl_dbi(pci, PCIE_ATU_CR2);
+ val = dw_pcie_readl_atu_ib(pci, index, PCIE_ATU_REGION_CTRL2);
if (val & PCIE_ATU_ENABLE)
return 0;

mdelay(LINK_WAIT_IATU);
}
+
dev_err(pci->dev, "Inbound iATU is not being enabled\n");

- return -EBUSY;
+ return -ETIMEDOUT;
}

void dw_pcie_disable_atu(struct dw_pcie *pci, int index,
enum dw_pcie_region_type type)
{
- int region;
+ u32 dir;

switch (type) {
case DW_PCIE_REGION_INBOUND:
- region = PCIE_ATU_REGION_INBOUND;
+ dir = PCIE_ATU_REGION_DIR_IB;
break;
case DW_PCIE_REGION_OUTBOUND:
- region = PCIE_ATU_REGION_OUTBOUND;
+ dir = PCIE_ATU_REGION_DIR_OB;
break;
default:
return;
}

- if (pci->iatu_unroll_enabled) {
- if (region == PCIE_ATU_REGION_INBOUND) {
- dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2,
- ~(u32)PCIE_ATU_ENABLE);
- } else {
- dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2,
- ~(u32)PCIE_ATU_ENABLE);
- }
- } else {
- dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, region | index);
- dw_pcie_writel_dbi(pci, PCIE_ATU_CR2, ~(u32)PCIE_ATU_ENABLE);
- }
+ dw_pcie_writel_atu(pci, dir, index, PCIE_ATU_REGION_CTRL2, 0);
}

int dw_pcie_wait_for_link(struct dw_pcie *pci)
@@ -622,63 +541,29 @@ static bool dw_pcie_iatu_unroll_enabled(struct dw_pcie *pci)
return false;
}

-static void dw_pcie_iatu_detect_regions_unroll(struct dw_pcie *pci)
-{
- int max_region, i, ob = 0, ib = 0;
- u32 val;
-
- max_region = min((int)pci->atu_size / 512, 256);
-
- for (i = 0; i < max_region; i++) {
- dw_pcie_writel_ob_unroll(pci, i, PCIE_ATU_UNR_LOWER_TARGET,
- 0x11110000);
-
- val = dw_pcie_readl_ob_unroll(pci, i, PCIE_ATU_UNR_LOWER_TARGET);
- if (val == 0x11110000)
- ob++;
- else
- break;
- }
-
- for (i = 0; i < max_region; i++) {
- dw_pcie_writel_ib_unroll(pci, i, PCIE_ATU_UNR_LOWER_TARGET,
- 0x11110000);
-
- val = dw_pcie_readl_ib_unroll(pci, i, PCIE_ATU_UNR_LOWER_TARGET);
- if (val == 0x11110000)
- ib++;
- else
- break;
- }
- pci->num_ib_windows = ib;
- pci->num_ob_windows = ob;
-}
-
static void dw_pcie_iatu_detect_regions(struct dw_pcie *pci)
{
- int max_region, i, ob = 0, ib = 0;
+ int max_region, ob, ib;
u32 val;

- dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, 0xFF);
- max_region = dw_pcie_readl_dbi(pci, PCIE_ATU_VIEWPORT) + 1;
+ if (pci->iatu_unroll_enabled) {
+ max_region = min((int)pci->atu_size / 512, 256);
+ } else {
+ dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, 0xFF);
+ max_region = dw_pcie_readl_dbi(pci, PCIE_ATU_VIEWPORT) + 1;
+ }

- for (i = 0; i < max_region; i++) {
- dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, PCIE_ATU_REGION_OUTBOUND | i);
- dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_TARGET, 0x11110000);
- val = dw_pcie_readl_dbi(pci, PCIE_ATU_LOWER_TARGET);
- if (val == 0x11110000)
- ob++;
- else
+ for (ob = 0; ob < max_region; ob++) {
+ dw_pcie_writel_atu_ob(pci, ob, PCIE_ATU_LOWER_TARGET, 0x11110000);
+ val = dw_pcie_readl_atu_ob(pci, ob, PCIE_ATU_LOWER_TARGET);
+ if (val != 0x11110000)
break;
}

- for (i = 0; i < max_region; i++) {
- dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, PCIE_ATU_REGION_INBOUND | i);
- dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_TARGET, 0x11110000);
- val = dw_pcie_readl_dbi(pci, PCIE_ATU_LOWER_TARGET);
- if (val == 0x11110000)
- ib++;
- else
+ for (ib = 0; ib < max_region; ib++) {
+ dw_pcie_writel_atu_ib(pci, ib, PCIE_ATU_LOWER_TARGET, 0x11110000);
+ val = dw_pcie_readl_atu_ib(pci, ib, PCIE_ATU_LOWER_TARGET);
+ if (val != 0x11110000)
break;
}

@@ -707,12 +592,10 @@ void dw_pcie_iatu_detect(struct dw_pcie *pci)
if (!pci->atu_size)
/* Pick a minimal default, enough for 8 in and 8 out windows */
pci->atu_size = SZ_4K;
-
- dw_pcie_iatu_detect_regions_unroll(pci);
- } else {
- dw_pcie_iatu_detect_regions(pci);
}

+ dw_pcie_iatu_detect_regions(pci);
+
dev_info(pci->dev, "iATU unroll: %s\n", pci->iatu_unroll_enabled ?
"enabled" : "disabled");

diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h
index 449c5ad92edc..6adf0c957c3b 100644
--- a/drivers/pci/controller/dwc/pcie-designware.h
+++ b/drivers/pci/controller/dwc/pcie-designware.h
@@ -103,10 +103,20 @@
#define PCIE_VERSION_NUMBER 0x8F8
#define PCIE_VERSION_TYPE 0x8FC

+/*
+ * iATU inbound and outbound windows CSRs. Before the IP-core v4.80a each
+ * iATU region CSRs had been indirectly accessible by means of the dedicated
+ * viewport selector. The iATU/eDMA CSRs space was re-designed in DWC PCIe
+ * v4.80a in a way so the viewport was unrolled into the directly accessible
+ * iATU/eDMA CSRs space.
+ */
#define PCIE_ATU_VIEWPORT 0x900
-#define PCIE_ATU_REGION_INBOUND BIT(31)
-#define PCIE_ATU_REGION_OUTBOUND 0
-#define PCIE_ATU_CR1 0x904
+#define PCIE_ATU_REGION_DIR_IB BIT(31)
+#define PCIE_ATU_REGION_DIR_OB 0
+#define PCIE_ATU_VIEWPORT_BASE 0x904
+#define PCIE_ATU_UNROLL_BASE(dir, region) \
+ (((region) << 9) | ((dir == PCIE_ATU_REGION_DIR_IB) ? BIT(8) : 0))
+#define PCIE_ATU_REGION_CTRL1 0x000
#define PCIE_ATU_INCREASE_REGION_SIZE BIT(13)
#define PCIE_ATU_TYPE_MEM 0x0
#define PCIE_ATU_TYPE_IO 0x2
@@ -114,19 +124,19 @@
#define PCIE_ATU_TYPE_CFG1 0x5
#define PCIE_ATU_TD BIT(8)
#define PCIE_ATU_FUNC_NUM(pf) ((pf) << 20)
-#define PCIE_ATU_CR2 0x908
+#define PCIE_ATU_REGION_CTRL2 0x004
#define PCIE_ATU_ENABLE BIT(31)
#define PCIE_ATU_BAR_MODE_ENABLE BIT(30)
#define PCIE_ATU_FUNC_NUM_MATCH_EN BIT(19)
-#define PCIE_ATU_LOWER_BASE 0x90C
-#define PCIE_ATU_UPPER_BASE 0x910
-#define PCIE_ATU_LIMIT 0x914
-#define PCIE_ATU_LOWER_TARGET 0x918
+#define PCIE_ATU_LOWER_BASE 0x008
+#define PCIE_ATU_UPPER_BASE 0x00C
+#define PCIE_ATU_LIMIT 0x010
+#define PCIE_ATU_LOWER_TARGET 0x014
#define PCIE_ATU_BUS(x) FIELD_PREP(GENMASK(31, 24), x)
#define PCIE_ATU_DEV(x) FIELD_PREP(GENMASK(23, 19), x)
#define PCIE_ATU_FUNC(x) FIELD_PREP(GENMASK(18, 16), x)
-#define PCIE_ATU_UPPER_TARGET 0x91C
-#define PCIE_ATU_UPPER_LIMIT 0x924
+#define PCIE_ATU_UPPER_TARGET 0x018
+#define PCIE_ATU_UPPER_LIMIT 0x020

#define PCIE_MISC_CONTROL_1_OFF 0x8BC
#define PCIE_DBI_RO_WR_EN BIT(0)
@@ -143,19 +153,6 @@

#define PCIE_PL_CHK_REG_ERR_ADDR 0xB28

-/*
- * iATU Unroll-specific register definitions
- * From 4.80 core version the address translation will be made by unroll
- */
-#define PCIE_ATU_UNR_REGION_CTRL1 0x00
-#define PCIE_ATU_UNR_REGION_CTRL2 0x04
-#define PCIE_ATU_UNR_LOWER_BASE 0x08
-#define PCIE_ATU_UNR_UPPER_BASE 0x0C
-#define PCIE_ATU_UNR_LOWER_LIMIT 0x10
-#define PCIE_ATU_UNR_LOWER_TARGET 0x14
-#define PCIE_ATU_UNR_UPPER_TARGET 0x18
-#define PCIE_ATU_UNR_UPPER_LIMIT 0x20
-
/*
* The default address offset between dbi_base and atu_base. Root controller
* drivers are not required to initialize atu_base if the offset matches this
@@ -164,13 +161,6 @@
*/
#define DEFAULT_DBI_ATU_OFFSET (0x3 << 20)

-/* Register address builder */
-#define PCIE_GET_ATU_OUTB_UNR_REG_OFFSET(region) \
- ((region) << 9)
-
-#define PCIE_GET_ATU_INB_UNR_REG_OFFSET(region) \
- (((region) << 9) | BIT(8))
-
#define MAX_MSI_IRQS 256
#define MAX_MSI_IRQS_PER_CTRL 32
#define MAX_MSI_CTRLS (MAX_MSI_IRQS / MAX_MSI_IRQS_PER_CTRL)
--
2.35.1

2022-03-28 22:21:40

by Serge Semin

[permalink] [raw]
Subject: [PATCH 03/16] PCI: dwc: Add more verbose link-up message

Printing just "link up" isn't that much informative especially when it
comes to working with the PCI Express bus. Even if the link is up, due to
multiple reasons the bus performance can degrade to slower speeds or to
narrower width than both Root Port and its partner is capable of. In that
case it would be handy to know the link specifications as early as
possible. So let's add a more verbose message to the busy-wait link-state
method, which will contain the link speed generation and the PCIe bus
width in case if the link up state is discovered. Otherwise an error will
be printed to the system log.

Signed-off-by: Serge Semin <[email protected]>
---
drivers/pci/controller/dwc/pcie-designware.c | 22 +++++++++++++++-----
1 file changed, 17 insertions(+), 5 deletions(-)

diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c
index 6e81264fdfb4..f1693e25afcb 100644
--- a/drivers/pci/controller/dwc/pcie-designware.c
+++ b/drivers/pci/controller/dwc/pcie-designware.c
@@ -528,14 +528,26 @@ int dw_pcie_wait_for_link(struct dw_pcie *pci)

/* Check if the link is up or not */
for (retries = 0; retries < LINK_WAIT_MAX_RETRIES; retries++) {
- if (dw_pcie_link_up(pci)) {
- dev_info(pci->dev, "Link up\n");
- return 0;
- }
+ if (dw_pcie_link_up(pci))
+ break;
+
usleep_range(LINK_WAIT_USLEEP_MIN, LINK_WAIT_USLEEP_MAX);
}

- dev_info(pci->dev, "Phy link never came up\n");
+ if (retries < LINK_WAIT_MAX_RETRIES) {
+ u32 offset, val;
+
+ offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
+ val = dw_pcie_readw_dbi(pci, offset + PCI_EXP_LNKSTA);
+
+ dev_info(pci->dev, "PCIe Gen.%u x%u link up\n",
+ FIELD_GET(PCI_EXP_LNKSTA_CLS, val),
+ FIELD_GET(PCI_EXP_LNKSTA_NLW, val));
+
+ return 0;
+ }
+
+ dev_err(pci->dev, "Phy link never came up\n");

return -ETIMEDOUT;
}
--
2.35.1

2022-03-28 22:31:25

by Serge Semin

[permalink] [raw]
Subject: [PATCH 14/16] PCI: dwc: Introduce dma-ranges property support for RC-host

In accordance with the generic PCIe Root Port DT-bindings the "dma-ranges"
property has the same format as the "ranges" property. The only difference
is in their semantics. The "dma-ranges" property describes the PCIe-to-CPU
memory mapping in opposite to the CPU-to-PCIe mapping of the "ranges"
property. Even though the DW PCIe controllers are normally equipped with
internal Address Translation Unit which inbound and outbound tables can be
used to implement both properties semantics, it was surprise for me to
discover that the host-related part of the DW PCIe driver currently
supports the "ranges" property only while the "dma-ranges" windows are
just ignored. Having the "dma-ranges" supported in the driver would be
very handy for the platforms, that don't tolerate the 1:1 CPU-PCIe memory
mapping and require customized the PCIe memory layout. So let's fix that
by introducing the "dma-ranges" property support.

First of all we suggest to rename the dw_pcie_prog_inbound_atu() method to
dw_pcie_prog_ep_inbound_atu() and create a new version of the
dw_pcie_prog_inbound_atu() function. Thus we'll have two methods for RC
and EP controllers respectively in the same way as it has been developed
for the outbound ATU setup methods.

Secondly aside with the memory window index and type the new
dw_pcie_prog_inbound_atu() function will accept CPU address, PCIe address
and size as its arguments. These parameters define the PCIe and CPU memory
ranges which will be used to setup the respective inbound ATU mapping. The
passed parameters need to be verified against the ATU ranges constraints
in the same way as it is done for the outbound ranges.

Finally the DMA-ranges detected for the PCIe controller need to be
converted into the inbound ATU entries during the host controller
initialization procedure. It will be done in the framework of the
dw_pcie_iatu_setup() method. Note before setting the inbound ranges up we
need to disable all the inbound ATU entries in order to prevent unexpected
PCIe TLPs translations defined by some third party software like
bootloader.

Signed-off-by: Serge Semin <[email protected]>
---
.../pci/controller/dwc/pcie-designware-ep.c | 4 +-
.../pci/controller/dwc/pcie-designware-host.c | 32 ++++++++++-
drivers/pci/controller/dwc/pcie-designware.c | 57 ++++++++++++++++++-
drivers/pci/controller/dwc/pcie-designware.h | 6 +-
4 files changed, 90 insertions(+), 9 deletions(-)

diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c
index 47ed9256b87c..23401f17e8f0 100644
--- a/drivers/pci/controller/dwc/pcie-designware-ep.c
+++ b/drivers/pci/controller/dwc/pcie-designware-ep.c
@@ -167,8 +167,8 @@ static int dw_pcie_ep_inbound_atu(struct dw_pcie_ep *ep, u8 func_no, int type,
return -EINVAL;
}

- ret = dw_pcie_prog_inbound_atu(pci, func_no, free_win, type,
- cpu_addr, bar);
+ ret = dw_pcie_prog_ep_inbound_atu(pci, func_no, free_win, type,
+ cpu_addr, bar);
if (ret < 0) {
dev_err(pci->dev, "Failed to program IB window\n");
return ret;
diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c
index 41c673c31940..715a13b90e43 100644
--- a/drivers/pci/controller/dwc/pcie-designware-host.c
+++ b/drivers/pci/controller/dwc/pcie-designware-host.c
@@ -606,12 +606,15 @@ static int dw_pcie_iatu_setup(struct pcie_port *pp)
}

/*
- * Ensure all outbound windows are disabled before proceeding with
- * the MEM/IO ranges setups.
+ * Ensure all out/inbound windows are disabled before proceeding with
+ * the MEM/IO (dma-)ranges setups.
*/
for (i = 0; i < pci->num_ob_windows; i++)
dw_pcie_disable_atu(pci, PCIE_ATU_REGION_DIR_OB, i);

+ for (i = 0; i < pci->num_ib_windows; i++)
+ dw_pcie_disable_atu(pci, PCIE_ATU_REGION_DIR_IB, i);
+
i = 0;
resource_list_for_each_entry(entry, &pp->bridge->windows) {
if (resource_type(entry->res) != IORESOURCE_MEM)
@@ -648,9 +651,32 @@ static int dw_pcie_iatu_setup(struct pcie_port *pp)
}

if (pci->num_ob_windows <= i)
- dev_warn(pci->dev, "Resources exceed number of ATU entries (%d)\n",
+ dev_warn(pci->dev, "Ranges exceed outbound iATU size (%d)\n",
pci->num_ob_windows);

+ i = 0;
+ resource_list_for_each_entry(entry, &pp->bridge->dma_ranges) {
+ if (resource_type(entry->res) != IORESOURCE_MEM)
+ continue;
+
+ if (pci->num_ib_windows <= i)
+ break;
+
+ ret = dw_pcie_prog_inbound_atu(pci, i++, PCIE_ATU_TYPE_MEM,
+ entry->res->start,
+ entry->res->start - entry->offset,
+ resource_size(entry->res));
+ if (ret) {
+ dev_err(pci->dev, "Failed to set DMA range %pr\n",
+ entry->res);
+ return ret;
+ }
+ }
+
+ if (pci->num_ib_windows <= i)
+ dev_warn(pci->dev, "Dma-ranges exceed inbound iATU size (%u)\n",
+ pci->num_ib_windows);
+
return 0;
}

diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c
index 2a7f23a2045c..4a95a7b112e9 100644
--- a/drivers/pci/controller/dwc/pcie-designware.c
+++ b/drivers/pci/controller/dwc/pcie-designware.c
@@ -400,8 +400,61 @@ static inline void dw_pcie_writel_atu_ib(struct dw_pcie *pci, u32 region, u32 re
dw_pcie_writel_atu(pci, PCIE_ATU_REGION_DIR_IB, region, reg, val);
}

-int dw_pcie_prog_inbound_atu(struct dw_pcie *pci, u8 func_no, int index,
- int type, u64 cpu_addr, u8 bar)
+int dw_pcie_prog_inbound_atu(struct dw_pcie *pci, int index, int type,
+ u64 cpu_addr, u64 pci_addr, u64 size)
+{
+ u64 limit_addr = pci_addr + size - 1;
+ u32 retries, val;
+
+ if ((limit_addr & ~pci->region_limit) != (pci_addr & ~pci->region_limit) ||
+ !IS_ALIGNED(cpu_addr, pci->region_align) ||
+ !IS_ALIGNED(pci_addr, pci->region_align) ||
+ !IS_ALIGNED(size, pci->region_align) || !size) {
+ return -EINVAL;
+ }
+
+ dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_LOWER_BASE,
+ lower_32_bits(pci_addr));
+ dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_UPPER_BASE,
+ upper_32_bits(pci_addr));
+
+ dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_LIMIT,
+ lower_32_bits(limit_addr));
+ if (dw_pcie_ver_is_ge(pci, 460A))
+ dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_UPPER_LIMIT,
+ upper_32_bits(limit_addr));
+
+ dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_LOWER_TARGET,
+ lower_32_bits(cpu_addr));
+ dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_UPPER_TARGET,
+ upper_32_bits(cpu_addr));
+
+ val = type;
+ if (upper_32_bits(limit_addr) > upper_32_bits(pci_addr) &&
+ dw_pcie_ver_is_ge(pci, 460A))
+ val |= PCIE_ATU_INCREASE_REGION_SIZE;
+ dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_REGION_CTRL1, val);
+ dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_REGION_CTRL2, PCIE_ATU_ENABLE);
+
+ /*
+ * Make sure ATU enable takes effect before any subsequent config
+ * and I/O accesses.
+ */
+ for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) {
+ val = dw_pcie_readl_atu_ib(pci, index, PCIE_ATU_REGION_CTRL2);
+ if (val & PCIE_ATU_ENABLE)
+ return 0;
+
+ mdelay(LINK_WAIT_IATU);
+ }
+
+ dev_err(pci->dev, "Inbound iATU is not being enabled\n");
+
+ return -ETIMEDOUT;
+}
+
+int dw_pcie_prog_ep_inbound_atu(struct dw_pcie *pci, u8 func_no, int index,
+ int type, u64 cpu_addr, u8 bar)
{
u32 retries, val;

diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h
index 15fce8fd33df..ade854217332 100644
--- a/drivers/pci/controller/dwc/pcie-designware.h
+++ b/drivers/pci/controller/dwc/pcie-designware.h
@@ -308,8 +308,10 @@ int dw_pcie_prog_outbound_atu(struct dw_pcie *pci, int index, int type,
u64 cpu_addr, u64 pci_addr, u64 size);
int dw_pcie_prog_ep_outbound_atu(struct dw_pcie *pci, u8 func_no, int index,
int type, u64 cpu_addr, u64 pci_addr, u64 size);
-int dw_pcie_prog_inbound_atu(struct dw_pcie *pci, u8 func_no, int index,
- int type, u64 cpu_addr, u8 bar);
+int dw_pcie_prog_inbound_atu(struct dw_pcie *pci, int index, int type,
+ u64 cpu_addr, u64 pci_addr, u64 size);
+int dw_pcie_prog_ep_inbound_atu(struct dw_pcie *pci, u8 func_no, int index,
+ int type, u64 cpu_addr, u8 bar);
void dw_pcie_disable_atu(struct dw_pcie *pci, u32 dir, int index);
void dw_pcie_setup(struct dw_pcie *pci);
void dw_pcie_iatu_detect(struct dw_pcie *pci);
--
2.35.1

2022-03-28 22:33:55

by Rob Herring

[permalink] [raw]
Subject: Re: [PATCH 01/16] dt-bindings: PCI: dwc: Define generic and native DT bindings

On Thu, Mar 24, 2022 at 04:37:19AM +0300, Serge Semin wrote:
> Currently both DW PCIe Root Port and End-point DT bindings are too generic
> to be used as a descriptive set the device properties. Yes, it's very handy
> to have them that way so the DT-schemas could be used to evaluate as many
> DW PCIe-related DT-nodes as possible. But at the same time they don't
> provide well defined DW PCIe RP/EP DT interface description thus leaving
> too much flexibility for the new platforms, no encouraging the developers
> to preserve a compatible interface.
>
> Instead of currently implemented approach we suggest to be more
> restrictive and yet preserve some level of flexibility in the DW PCIe
> DT-bindings description. The device tree DT-schema is split up into
> three parts: a common YAML-schema applicable for both DWC Root Port and
> End-point controller configs, DWC PCIe Root Port-specific YAML-schema
> and DWC PCIe End-point-specific YAML-schema, where
> 1) pci/snps,dw-pcie-common.yaml - The common DT-schema describes the most
> generic constraints of the "reg", "interrupts", "clocks", "resets" and
> "phys" properties together with a set of common for both device types
> PCIe/AXI bus properties like a maximum number of lanes or a maximum link
> speed, number of inbound and outbound iATU windows. In addition to that a
> set of schema definitions declared under the "$defs" property with "reg",
> "interrupt", "clock" and "reset" names common for DWC PCIe Root Port and
> End-point devices. They can be used by the successive DT-schemas in case
> they are supposed to be compatible with the generic DWC PCIe controller
> DT-bindings.
> 2) pci/snps,dw-pcie.yaml, pci/snps,dw-pcie-ep.yaml - generic DW PCIe Root
> Port and End-point DT-bindings which aside with the device-specific
> properties set also contain more restrictive constraints. All new DW PCIe
> platforms are supposed to be compatible with one of these bindings by
> using "allOf: " property and additionally defining their own constraints
> to close up the DT-bindings set.
>
> So to speak in case if a DW PCIe-based device for some reason has too many
> specific properties or it's bindings have already been defined in a
> non-generic way, it's DT-schema is supposed to include 1) YAML-file and
> provide its own constraints. Otherwise the ready-to-use bindings from 2)
> should be utilized. There only two DT-schemas compatible with 2) at the
> moment are samsung,axynos-pcie.yaml and intel-gw-pcie.yaml. The
> rest of the DW PCIe-related DT-schemas are supposed to use more generic
> DW PCIe DT-bindings - pci/snps,dw-pcie-common.yaml.
>
> Note the provided here generic properties and their possible values are
> derived from the DW PCIe RC/EP hardware manuals and from the interface
> implemented in the driver. The DT-bindings schemas are created to be as
> full as possible with detailed properties/names description for the
> easier interface comprehension and new platforms bindings development.
>
> Also note since there are no generic DT-nodes can be found in the kernel
> dts-es which would have a pure "snps,dw-pcie" compatible string, these
> DT-bindings have been created to not be selected by default for
> evaluation. They are supposed to be used by the new vendor-specific
> DT-schemas to at least stop adding new bindings for the same set of DWC
> PCIe signals or properties.
>
> Signed-off-by: Serge Semin <[email protected]>
>
> ---
>
> Rob, if you comment the "select: false" line out you'll get a lot of "'*'
> is not of type 'array'" errors on the dt_bindings_check command execution.
> I have spend tons of time trying to figure out what was wrong, but still
> failed to find out a solution. For some reason the "$ref"-ed schemas
> defined under the "$defs" basic property are considered by the parser as
> defining an array. No matter whether I specified the "type: string"
> keyword or not, as long as there is "enum" or "oneOf" keyword used in the
> "*-names" property definition in "$defs" it's considered as describing an
> array thus causing the errors like:

As-is, there were lots of errors...

>
> > DTC Documentation/devicetree/bindings/pci/snps,dw-pcie.example.dt.yaml
> > CHECK Documentation/devicetree/bindings/pci/snps,dw-pcie.example.dt.yaml
> > /.../snps,dw-pcie.example.dt.yaml: pcie@1f052000: reg-names:0: 'oneOf' conditional failed, one must be fixed:
> > 'dbi' is not of type 'array'
> > From schema: /.../snps,dw-pcie.yaml
> > /.../snps,dw-pcie.example.dt.yaml: pcie@1f052000: reg-names:1: 'oneOf' conditional failed, one must be fixed:
> > 'config' is not of type 'array'
> > From schema: /.../snps,dw-pcie.yaml
> > /.../snps,dw-pcie.example.dt.yaml: pcie@1f052000: interrupt-names:0: 'anyOf' conditional failed, one must be fixed:
> > /.../snps,dw-pcie.example.dt.yaml: pcie@1f052000: interrupt-names:0: 'oneOf' conditional failed, one must be fixed:
> > 'msi' is not of type 'array'
> > /../snps,dw-pcie.example.dt.yaml: pcie@1f052000: interrupt-names:0: 'oneOf' conditional failed, one must be fixed:
> > 'msi' is not of type 'array'
> > From schema: /.../snps,dw-pcie.yaml
>
> As soon as I manually moved the same "$defs"-schemas into the places they
> are referenced to, the errors disappeared and the DT-schemas validation
> worked as expected by the semantics.
>
> As I see it there must be some bug in the parser, otherwise I am out of
> ideas how to implement the suggested in this patch design pattern. Rob,
> could you take a look at the DT-schemas and help me out with debugging the
> problem described above?

There is at least an error in your schema, details latter on.

It could be an issue in the fixups which in general convert
schemas for strings to string arrays and scalars/arrays to matrix. You
have to look at processed-schema.json to debug it.

The other thing is 'oneOf' errors can be confusing in terms of error
messages because the tooling doesn't really know which entry had the
least error. Like the type was right, but some secondary constraint
failed (as what's secondary is not expressed in any way).

I would debug it further, but I don't really like the end result. I
think it's too hard to follow. For the most part, we avoid $refs at the
property level across different schema files with the big exception
being types.yaml. $defs are also used sparingly. The challenge is that
json-schema by default will be silent on schemas misformed in lots of
ways. Our meta-schema adds a lot of constraints which limits what the
schemas can look like and ensures schemas are valid. It also allows me
to define new constraints rather than repeat the same review comment
over and over.

> ---
> .../bindings/pci/fsl,imx6q-pcie.yaml | 5 +-
> .../bindings/pci/hisilicon,kirin-pcie.yaml | 4 +-
> .../bindings/pci/sifive,fu740-pcie.yaml | 4 +-
> .../bindings/pci/snps,dw-pcie-common.yaml | 298 ++++++++++++++++++
> .../bindings/pci/snps,dw-pcie-ep.yaml | 143 ++++++---
> .../devicetree/bindings/pci/snps,dw-pcie.yaml | 189 +++++++----
> .../bindings/pci/toshiba,visconti-pcie.yaml | 2 +-
> 7 files changed, 524 insertions(+), 121 deletions(-)
> create mode 100644 Documentation/devicetree/bindings/pci/snps,dw-pcie-common.yaml


> diff --git a/Documentation/devicetree/bindings/pci/snps,dw-pcie-common.yaml b/Documentation/devicetree/bindings/pci/snps,dw-pcie-common.yaml
> new file mode 100644
> index 000000000000..27fe1f5c450f
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/pci/snps,dw-pcie-common.yaml
> @@ -0,0 +1,298 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/pci/snps,dw-pcie-common.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Synopsys DWC PCIe RP/EP controller
> +
> +maintainers:
> + - Jingoo Han <[email protected]>
> + - Gustavo Pimentel <[email protected]>
> +
> +properties:
> + reg:
> + description:
> + DWC PCIe CSR space is normally accessed over the dedicated Data Bus
> + Interface - DBI. In accordance with the reference manual the register
> + configuration space belongs to the Configuration-Dependent Module (CDM)
> + and is split up into several sub-parts Standard PCIe configuration
> + space, Port Logic Registers (PL), Shadow Config-space Registers,
> + iATU/eDMA registers. The particular sub-space is selected by the
> + CDM/ELBI (dbi_cs) and CS2 (dbi_cs2) signals (selector bits). Such
> + configuration provides a flexible interface for the system engineers to
> + either map the particular space at a desired MMIO address or just leave
> + them in a contiguous memory space if pure Native or AXI Bridge DBI access
> + is selected. Note the PCIe CFG-space, PL and Shadow registers are
> + specific to each activated function, while the rest of the sub-spaces
> + are common for all of them (if there are more than one).
> + minItems: 2
> + maxItems: 6
> +
> + interrupts:
> + description:
> + There are two main sub-blocks which are normally capable of
> + generating interrupts. It's System Information Interface and MSI
> + interface. While the former one has some common for the Host and
> + End-point controllers IRQ-signals, the later interface is obviously
> + Root Complex specific since it's responsible for the incoming MSI
> + messages signalling. The System Information IRQ signals are mainly
> + responsible for reporting the generic PCIe hierarchy and Root
> + Complex events like VPD IO request, general AER, PME, Hot-plug, link
> + bandwidth change, link equalization request, INTx asserted/deasserted
> + Message detection, embedded DMA Tx/Rx/Error.
> + minItems: 1
> + maxItems: 26
> +
> + clocks:
> + description:
> + DWC PCIe reference manual explicitly defines a set of the clocks required
> + to get the controller working correctly. In general all of them can
> + be divided into two groups':' application and core clocks. Note the
> + platforms may have some of the clock sources unspecified in case if the
> + corresponding domains are fed up from a common clock source.
> + minItems: 1
> + maxItems: 7
> +
> + resets:
> + description:
> + DWC PCIe reference manual explicitly defines a set of the reset
> + signals required to be de-asserted to properly activate the controller
> + sub-parts. All of these signals can be divided into two sub-groups':'
> + application and core resets with respect to the main sub-domains they
> + are supposed to reset. Note the platforms may have some of these signals
> + unspecified in case if they are automatically handled or aggregated into
> + a comprehensive control module.
> + minItems: 1
> + maxItems: 10
> +
> + reset-gpio:
> + deprecated: true
> + description:
> + Reference to the GPIO-controlled PERST# signal. It is used to reset all
> + the peripheral devices available on the PCIe bus.
> + maxItems: 1
> +
> + reset-gpios:
> + description:
> + Reference to the GPIO-controlled PERST# signal. It is used to reset all
> + the peripheral devices available on the PCIe bus.
> + maxItems: 1
> +
> + phys:
> + description:
> + There can be up to number of possible lanes PHYs specified.
> + Obviously each specified PHY is supposed to be able to work in the
> + PCIe mode with a speed implied by the DWC PCIe controller it is
> + attached to.
> + minItems: 1
> + maxItems: 16
> +
> + phy-names:
> + minItems: 1
> + maxItems: 16
> + items:
> + pattern: '^pcie([0-9]+|-?phy)?$'
> +
> + num-lanes:
> + maximum: 16
> +
> + max-link-speed:
> + maximum: 4
> +
> + num-ob-windows:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + deprecated: true
> + description:
> + Number of outbound address translation windows. This parameter can be
> + auto-detected based on the iATU memory writability. So there is no
> + point in having a dedicated DT-property for it.
> + maximum: 256
> +
> + num-ib-windows:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + deprecated: true
> + description:
> + Number of inbound address translation windows. In the same way as
> + for the outbound AT windows, this parameter can be auto-detected based
> + on the iATU memory writability. There is no point having a dedicated
> + DT-property for it either.
> + maximum: 256
> +
> + num-viewport:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + deprecated: true
> + description:
> + Number of outbound view ports configured in hardware. It's the same as
> + the number of outbound AT windows.
> + maximum: 256
> +
> + snps,enable-cdm-check:
> + $ref: /schemas/types.yaml#/definitions/flag
> + description:
> + Enable automatic checking of CDM (Configuration Dependent Module)
> + registers for data corruption. CDM registers include standard PCIe
> + configuration space registers, Port Logic registers, DMA and iATU
> + registers. This feature has been available since DWC PCIe v4.80a.
> +
> +additionalProperties: true
> +
> +$defs:
> + reg-names:
> + description:
> + CSR space names common for the DWC PCIe Root Port and End-point
> + controllers.
> + oneOf:
> + - description:
> + Basic DWC PCIe controller configuration-space accessible over
> + the DBI interface. This memory space is either activated with
> + the CDM/ELBI = 0 and CS2 = 0 or is a contiguous memory region
> + with all spaces. Note iATU/eDMA CSRs are indirectly accessible
> + via the PL viewports on the DWC PCIe controllers older than
> + v4.80a.
> + const: dbi

This passes only for 'reg-names = "dbi";'

> + - description:
> + Shadow DWC PCIe config-space registers. This space is selected
> + by setting CDM/ELBI = 0 and CS2 = 1. This is an intermix of
> + the PCI-SIG PCIe CFG-space with the shadow registers for some
> + PCI Header space, PCI Standard and Extended Structures. It's
> + mainly relevant for the end-point controller configuration,
> + but still there are some shadow registers available for the
> + Root Port mode too.
> + const: dbi2

This passes only for 'reg-names = "dbi2";'

> + - description:
> + External Local Bus registers. It's an application-dependent
> + registers normally defined by the platform engineers, which
> + are selected by setting CDM/ELBI = 1 and CS2 = 0.
> + enum: [ elbi, appl, app ]

This passes only for 'reg-names = "elbi", "appl", "app";'

See the problem? Replacing the 'oneOf' above with 'items' is what you
want. But then the schema will not allow any missing entries either.
DBI2 is optional for example. There is no way in json-schema to define a
list of ordered values(string) once and say some entries are optional
which I think is what you are trying to do here.

> + - description:
> + iATU/eDMA registers common for all device functions. It's an
> + unrolled memory space with the internal Address Translation
> + Unit and Enhanced DMA, which is selected by setting CDM/ELBI = 1
> + and CS2 = 1. For IP-core releases prior v4.80a, these registers
> + have been programmed via an indirect addressing scheme using a
> + set of viewport CSRs mapped into the PL space. Note iATU is
> + normally mapped to the 0x0 address of this region, while eDMA
> + is available at 0x80000 base address.
> + enum: [ atu, atu_dma ]
> + - description:
> + Outbound iATU-available memory-region which usage scenario
> + depends on the DWC PCIe controller being either Root Port or
> + End-point. If it's Root Port then the register space shall
> + have the "config" name and it will be used to access the
> + peripheral PCIe devices configuration space. If it's PCIe
> + end-point controller, then the region shall be named as
> + "addr_space" and it will be used to generate various traffic
> + on the PCIe bus hierarchy. It's usage scenario depends on the
> + end-point functionality, for instance it can be used to create
> + MSI(X) messages.
> + enum: [ config, addr_space ]

So 'addr_space' is now valid for host mode?

(Really, for DW blocks with iATU, this should have been the whole memory
space and the driver could size the config space itself. (IOW, the
config region is configuration rather than h/w description.)

> + - description:
> + PHY/PCS configuration registers. Some platforms can have the
> + PCS and PHY CSRs accessible over a dedicated memory mapped
> + region, but mainly these registers are indirectly accessible
> + either by means of the embedded PHY viewport schema or by some
> + platform-specific method.
> + enum: [ link, phy ]

These should not be in any new users as the phy should be separate.

> +
> + interrupt-names:
> + description:
> + IRQ signal names common for the DWC PCIe Root Port and End-point
> + controllers.
> + oneOf:
> + - description:
> + Controller request to read or write virtual product data
> + from/to the VPD capability registers.
> + const: vpd
> + - description:
> + Link Equalization Request flag is set in the Link Status 2
> + register (applicable if the corresponding IRQ is enabled in
> + the Link Control 3 register).
> + const: l_eq
> + - description:
> + Indicates that the eDMA Tx/Rx transfer is complete or that an
> + error has occurred on the corresponding channel. eDMA can have
> + eight Tx (Write) and Rx (Read) eDMA channels thus supporting up
> + to 16 IRQ signals all together. Write eDMA channels shall go
> + first in the ordered row as per default edma_int[*] bus setup.
> + pattern: '^dma([0-9]|1[0-5])?$'
> + - description:
> + PCIe protocol correctable error or a Data Path protection
> + correctable error is detected by the automotive/safety
> + feature.
> + const: sft_ce
> + - description:
> + Indicates that the internal safety mechanism detected and
> + uncorrectable error.
> + const: sft_ue

This says there is only a single interrupt entry. Same for the other
$defs.

Rob

2022-03-28 22:34:31

by Serge Semin

[permalink] [raw]
Subject: [PATCH 04/16] PCI: dwc: Convert to using native IP-core versions representation

Since DWC PCIe v4.70a the controller version can be read from the
PORT_LOGIC.PCIE_VERSION_OFF register. Version is represented in the FourCC
format [1]. It's standard versioning approach for the Synopsys DWC
IP-cores. Moreover some of the DWC kernel drivers already make use of it
to fixup version-dependent functionality (See DWC USB3, Stmicro STMMAC or
recent DW SPI driver). In order to preserve the standard version
representation and prevent the data conversion back and forth, we suggest
to preserve the native version representation in the DWC PCIe driver too
in the same way as it has already been done in the rest of the DWC
drivers. IP-core version reading from the CSR will be introduced in the
next commit together with a simple macro-based API to use it.

[1] https://en.wikipedia.org/wiki/FourCC

Signed-off-by: Serge Semin <[email protected]>
---
drivers/pci/controller/dwc/pci-keystone.c | 12 ++++++------
drivers/pci/controller/dwc/pcie-designware.c | 8 ++++----
drivers/pci/controller/dwc/pcie-designware.h | 10 +++++++++-
drivers/pci/controller/dwc/pcie-intel-gw.c | 4 ++--
drivers/pci/controller/dwc/pcie-tegra194.c | 2 +-
5 files changed, 22 insertions(+), 14 deletions(-)

diff --git a/drivers/pci/controller/dwc/pci-keystone.c b/drivers/pci/controller/dwc/pci-keystone.c
index 1c2ee4e13f1c..ec65355dd29b 100644
--- a/drivers/pci/controller/dwc/pci-keystone.c
+++ b/drivers/pci/controller/dwc/pci-keystone.c
@@ -109,7 +109,7 @@ struct ks_pcie_of_data {
enum dw_pcie_device_mode mode;
const struct dw_pcie_host_ops *host_ops;
const struct dw_pcie_ep_ops *ep_ops;
- unsigned int version;
+ u32 version;
};

struct keystone_pcie {
@@ -1069,19 +1069,19 @@ static int ks_pcie_am654_set_mode(struct device *dev,

static const struct ks_pcie_of_data ks_pcie_rc_of_data = {
.host_ops = &ks_pcie_host_ops,
- .version = 0x365A,
+ .version = DW_PCIE_VER_365A,
};

static const struct ks_pcie_of_data ks_pcie_am654_rc_of_data = {
.host_ops = &ks_pcie_am654_host_ops,
.mode = DW_PCIE_RC_TYPE,
- .version = 0x490A,
+ .version = DW_PCIE_VER_490A,
};

static const struct ks_pcie_of_data ks_pcie_am654_ep_of_data = {
.ep_ops = &ks_pcie_am654_ep_ops,
.mode = DW_PCIE_EP_TYPE,
- .version = 0x490A,
+ .version = DW_PCIE_VER_490A,
};

static const struct of_device_id ks_pcie_of_match[] = {
@@ -1114,12 +1114,12 @@ static int __init ks_pcie_probe(struct platform_device *pdev)
struct device_link **link;
struct gpio_desc *gpiod;
struct resource *res;
- unsigned int version;
void __iomem *base;
u32 num_viewport;
struct phy **phy;
u32 num_lanes;
char name[10];
+ u32 version;
int ret;
int irq;
int i;
@@ -1233,7 +1233,7 @@ static int __init ks_pcie_probe(struct platform_device *pdev)
goto err_get_sync;
}

- if (pci->version >= 0x480A)
+ if (pci->version >= DW_PCIE_VER_480A)
ret = ks_pcie_am654_set_mode(dev, mode);
else
ret = ks_pcie_set_mode(dev);
diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c
index f1693e25afcb..c21373c6cb51 100644
--- a/drivers/pci/controller/dwc/pcie-designware.c
+++ b/drivers/pci/controller/dwc/pcie-designware.c
@@ -289,7 +289,7 @@ static void dw_pcie_prog_outbound_atu_unroll(struct dw_pcie *pci, u8 func_no,
val = type | PCIE_ATU_FUNC_NUM(func_no);
if (upper_32_bits(limit_addr) > upper_32_bits(cpu_addr))
val |= PCIE_ATU_INCREASE_REGION_SIZE;
- if (pci->version == 0x490A)
+ if (pci->version == DW_PCIE_VER_490A)
val = dw_pcie_enable_ecrc(val);
dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL1, val);
dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2,
@@ -336,7 +336,7 @@ static void __dw_pcie_prog_outbound_atu(struct dw_pcie *pci, u8 func_no,
upper_32_bits(cpu_addr));
dw_pcie_writel_dbi(pci, PCIE_ATU_LIMIT,
lower_32_bits(limit_addr));
- if (pci->version >= 0x460A)
+ if (pci->version >= DW_PCIE_VER_460A)
dw_pcie_writel_dbi(pci, PCIE_ATU_UPPER_LIMIT,
upper_32_bits(limit_addr));
dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_TARGET,
@@ -345,9 +345,9 @@ static void __dw_pcie_prog_outbound_atu(struct dw_pcie *pci, u8 func_no,
upper_32_bits(pci_addr));
val = type | PCIE_ATU_FUNC_NUM(func_no);
if (upper_32_bits(limit_addr) > upper_32_bits(cpu_addr) &&
- pci->version >= 0x460A)
+ pci->version >= DW_PCIE_VER_460A)
val |= PCIE_ATU_INCREASE_REGION_SIZE;
- if (pci->version == 0x490A)
+ if (pci->version == DW_PCIE_VER_490A)
val = dw_pcie_enable_ecrc(val);
dw_pcie_writel_dbi(pci, PCIE_ATU_CR1, val);
dw_pcie_writel_dbi(pci, PCIE_ATU_CR2, PCIE_ATU_ENABLE);
diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h
index 7d6e9b7576be..5be43c662176 100644
--- a/drivers/pci/controller/dwc/pcie-designware.h
+++ b/drivers/pci/controller/dwc/pcie-designware.h
@@ -20,6 +20,14 @@
#include <linux/pci-epc.h>
#include <linux/pci-epf.h>

+/* DWC PCIe IP-core versions (native support since v4.70a) */
+#define DW_PCIE_VER_365A 0x3336352a
+#define DW_PCIE_VER_460A 0x3436302a
+#define DW_PCIE_VER_470A 0x3437302a
+#define DW_PCIE_VER_480A 0x3438302a
+#define DW_PCIE_VER_490A 0x3439302a
+#define DW_PCIE_VER_520A 0x3532302a
+
/* Parameters for the waiting for link up routine */
#define LINK_WAIT_MAX_RETRIES 10
#define LINK_WAIT_USLEEP_MIN 90000
@@ -269,7 +277,7 @@ struct dw_pcie {
struct pcie_port pp;
struct dw_pcie_ep ep;
const struct dw_pcie_ops *ops;
- unsigned int version;
+ u32 version;
int num_lanes;
int link_gen;
u8 n_fts[2];
diff --git a/drivers/pci/controller/dwc/pcie-intel-gw.c b/drivers/pci/controller/dwc/pcie-intel-gw.c
index 5ba144924ff8..786af2ba379f 100644
--- a/drivers/pci/controller/dwc/pcie-intel-gw.c
+++ b/drivers/pci/controller/dwc/pcie-intel-gw.c
@@ -59,7 +59,7 @@
#define RESET_INTERVAL_MS 100

struct intel_pcie_soc {
- unsigned int pcie_ver;
+ u32 pcie_ver;
};

struct intel_pcie {
@@ -395,7 +395,7 @@ static const struct dw_pcie_host_ops intel_pcie_dw_ops = {
};

static const struct intel_pcie_soc pcie_data = {
- .pcie_ver = 0x520A,
+ .pcie_ver = DW_PCIE_VER_520A,
};

static int intel_pcie_probe(struct platform_device *pdev)
diff --git a/drivers/pci/controller/dwc/pcie-tegra194.c b/drivers/pci/controller/dwc/pcie-tegra194.c
index b1b5f836a806..6f1330ed63e5 100644
--- a/drivers/pci/controller/dwc/pcie-tegra194.c
+++ b/drivers/pci/controller/dwc/pcie-tegra194.c
@@ -1981,7 +1981,7 @@ static int tegra194_pcie_probe(struct platform_device *pdev)
pci->ops = &tegra_dw_pcie_ops;
pci->n_fts[0] = N_FTS_VAL;
pci->n_fts[1] = FTS_VAL;
- pci->version = 0x490A;
+ pci->version = DW_PCIE_VER_490A;

pp = &pci->pp;
pp->num_vectors = MAX_MSI_IRQS;
--
2.35.1

2022-03-28 22:40:39

by Serge Semin

[permalink] [raw]
Subject: [PATCH 07/16] PCI: dwc: Add host de-initialization callback

Seeing the platform-specific DW PCIe host-initialization is performed from
within the generic dw_pcie_host_init() method by means of the dedicated
dw_pcie_ops.host_init() callback, there must be declared an antagonist
which would perform the corresponding cleanups. Let's add such callback
then. It will be called in the dw_pcie_host_deinit() method and in the
cleanup-on-error path in the dw_pcie_host_init() function.

Signed-off-by: Serge Semin <[email protected]>
---
.../pci/controller/dwc/pcie-designware-host.c | 21 ++++++++++++++-----
drivers/pci/controller/dwc/pcie-designware.h | 1 +
2 files changed, 17 insertions(+), 5 deletions(-)

diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c
index 8f0d473ff770..602cf4fe502b 100644
--- a/drivers/pci/controller/dwc/pcie-designware-host.c
+++ b/drivers/pci/controller/dwc/pcie-designware-host.c
@@ -354,20 +354,23 @@ int dw_pcie_host_init(struct pcie_port *pp)
pp->num_vectors = MSI_DEF_NUM_VECTORS;
} else if (pp->num_vectors > MAX_MSI_IRQS) {
dev_err(dev, "Invalid number of vectors\n");
- return -EINVAL;
+ ret = -EINVAL;
+ goto err_host_deinit;
}

if (pp->ops->msi_host_init) {
ret = pp->ops->msi_host_init(pp);
if (ret < 0)
- return ret;
+ goto err_host_deinit;
} else if (pp->has_msi_ctrl) {
if (!pp->msi_irq) {
pp->msi_irq = platform_get_irq_byname_optional(pdev, "msi");
if (pp->msi_irq < 0) {
pp->msi_irq = platform_get_irq(pdev, 0);
- if (pp->msi_irq < 0)
- return pp->msi_irq;
+ if (pp->msi_irq < 0) {
+ ret = pp->msi_irq;
+ goto err_host_deinit;
+ }
}
}

@@ -375,7 +378,7 @@ int dw_pcie_host_init(struct pcie_port *pp)

ret = dw_pcie_allocate_domains(pp);
if (ret)
- return ret;
+ goto err_host_deinit;

if (pp->msi_irq > 0)
irq_set_chained_handler_and_data(pp->msi_irq,
@@ -428,6 +431,11 @@ int dw_pcie_host_init(struct pcie_port *pp)
err_free_msi:
if (pp->has_msi_ctrl)
dw_pcie_free_msi(pp);
+
+err_host_deinit:
+ if (pp->ops->host_deinit)
+ pp->ops->host_deinit(pp);
+
return ret;
}
EXPORT_SYMBOL_GPL(dw_pcie_host_init);
@@ -444,6 +452,9 @@ void dw_pcie_host_deinit(struct pcie_port *pp)

if (pp->has_msi_ctrl)
dw_pcie_free_msi(pp);
+
+ if (pp->ops->host_deinit)
+ pp->ops->host_deinit(pp);
}
EXPORT_SYMBOL_GPL(dw_pcie_host_deinit);

diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h
index 1868773ecb91..bca1d3e83636 100644
--- a/drivers/pci/controller/dwc/pcie-designware.h
+++ b/drivers/pci/controller/dwc/pcie-designware.h
@@ -200,6 +200,7 @@ enum dw_pcie_device_mode {

struct dw_pcie_host_ops {
int (*host_init)(struct pcie_port *pp);
+ void (*host_deinit)(struct pcie_port *pp);
int (*msi_host_init)(struct pcie_port *pp);
};

--
2.35.1

2022-03-28 22:57:59

by Serge Semin

[permalink] [raw]
Subject: [PATCH 08/16] PCI: dwc: Drop inbound iATU types enumeration - dw_pcie_as_type

There is no point in having an enumeration declared in the driver for the
PCIe end-point. First of all it's redundant since the driver already has a
set of macro declared which describe the available in/out iATU types, thus
having an addition abstraction just needlessly complicates the code.
Secondly checking the passed iATU type for validity within a single driver
is pointless since the driver is supposed to be consistent by its nature.
Finally the outbound iATU type isn't encoded by the denoted enumeration,
thus giving a false impression that the in and out iATU types are
unrelated while they are the same. So to speak let's drop the redundant
dw_pcie_as_type enumeration replacing it with the direct iATU type usage.

While at it, since we are touching the iATU inbound regions config methods
anyway, let's fix the arguments order so the type would be followed by the
address-related parameters. Thus the inbound and outbound iATU setup
methods will look alike. That shall improve the code readability a bit.

Signed-off-by: Serge Semin <[email protected]>
---
.../pci/controller/dwc/pcie-designware-ep.c | 21 +++++------
drivers/pci/controller/dwc/pcie-designware.c | 35 +++----------------
drivers/pci/controller/dwc/pcie-designware.h | 9 +----
3 files changed, 15 insertions(+), 50 deletions(-)

diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c
index 3b981d13cca9..3bd9026071e8 100644
--- a/drivers/pci/controller/dwc/pcie-designware-ep.c
+++ b/drivers/pci/controller/dwc/pcie-designware-ep.c
@@ -154,9 +154,8 @@ static int dw_pcie_ep_write_header(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
return 0;
}

-static int dw_pcie_ep_inbound_atu(struct dw_pcie_ep *ep, u8 func_no,
- enum pci_barno bar, dma_addr_t cpu_addr,
- enum dw_pcie_as_type as_type)
+static int dw_pcie_ep_inbound_atu(struct dw_pcie_ep *ep, u8 func_no, int type,
+ dma_addr_t cpu_addr, enum pci_barno bar)
{
int ret;
u32 free_win;
@@ -168,8 +167,8 @@ static int dw_pcie_ep_inbound_atu(struct dw_pcie_ep *ep, u8 func_no,
return -EINVAL;
}

- ret = dw_pcie_prog_inbound_atu(pci, func_no, free_win, bar, cpu_addr,
- as_type);
+ ret = dw_pcie_prog_inbound_atu(pci, func_no, free_win, type,
+ cpu_addr, bar);
if (ret < 0) {
dev_err(pci->dev, "Failed to program IB window\n");
return ret;
@@ -221,27 +220,25 @@ static void dw_pcie_ep_clear_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
static int dw_pcie_ep_set_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
struct pci_epf_bar *epf_bar)
{
- int ret;
struct dw_pcie_ep *ep = epc_get_drvdata(epc);
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
enum pci_barno bar = epf_bar->barno;
size_t size = epf_bar->size;
int flags = epf_bar->flags;
- enum dw_pcie_as_type as_type;
- u32 reg;
unsigned int func_offset = 0;
+ int ret, type;
+ u32 reg;

func_offset = dw_pcie_ep_func_select(ep, func_no);

reg = PCI_BASE_ADDRESS_0 + (4 * bar) + func_offset;

if (!(flags & PCI_BASE_ADDRESS_SPACE))
- as_type = DW_PCIE_AS_MEM;
+ type = PCIE_ATU_TYPE_MEM;
else
- as_type = DW_PCIE_AS_IO;
+ type = PCIE_ATU_TYPE_IO;

- ret = dw_pcie_ep_inbound_atu(ep, func_no, bar,
- epf_bar->phys_addr, as_type);
+ ret = dw_pcie_ep_inbound_atu(ep, func_no, type, epf_bar->phys_addr, bar);
if (ret)
return ret;

diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c
index d5ce19625769..b983128584ff 100644
--- a/drivers/pci/controller/dwc/pcie-designware.c
+++ b/drivers/pci/controller/dwc/pcie-designware.c
@@ -421,10 +421,9 @@ static void dw_pcie_writel_ib_unroll(struct dw_pcie *pci, u32 index, u32 reg,
}

static int dw_pcie_prog_inbound_atu_unroll(struct dw_pcie *pci, u8 func_no,
- int index, int bar, u64 cpu_addr,
- enum dw_pcie_as_type as_type)
+ int index, int type,
+ u64 cpu_addr, u8 bar)
{
- int type;
u32 retries, val;

dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_LOWER_TARGET,
@@ -432,17 +431,6 @@ static int dw_pcie_prog_inbound_atu_unroll(struct dw_pcie *pci, u8 func_no,
dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_UPPER_TARGET,
upper_32_bits(cpu_addr));

- switch (as_type) {
- case DW_PCIE_AS_MEM:
- type = PCIE_ATU_TYPE_MEM;
- break;
- case DW_PCIE_AS_IO:
- type = PCIE_ATU_TYPE_IO;
- break;
- default:
- return -EINVAL;
- }
-
dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL1, type |
PCIE_ATU_FUNC_NUM(func_no));
dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2,
@@ -468,32 +456,19 @@ static int dw_pcie_prog_inbound_atu_unroll(struct dw_pcie *pci, u8 func_no,
}

int dw_pcie_prog_inbound_atu(struct dw_pcie *pci, u8 func_no, int index,
- int bar, u64 cpu_addr,
- enum dw_pcie_as_type as_type)
+ int type, u64 cpu_addr, u8 bar)
{
- int type;
u32 retries, val;

if (pci->iatu_unroll_enabled)
- return dw_pcie_prog_inbound_atu_unroll(pci, func_no, index, bar,
- cpu_addr, as_type);
+ return dw_pcie_prog_inbound_atu_unroll(pci, func_no, index, type,
+ cpu_addr, bar);

dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, PCIE_ATU_REGION_INBOUND |
index);
dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_TARGET, lower_32_bits(cpu_addr));
dw_pcie_writel_dbi(pci, PCIE_ATU_UPPER_TARGET, upper_32_bits(cpu_addr));

- switch (as_type) {
- case DW_PCIE_AS_MEM:
- type = PCIE_ATU_TYPE_MEM;
- break;
- case DW_PCIE_AS_IO:
- type = PCIE_ATU_TYPE_IO;
- break;
- default:
- return -EINVAL;
- }
-
dw_pcie_writel_dbi(pci, PCIE_ATU_CR1, type |
PCIE_ATU_FUNC_NUM(func_no));
dw_pcie_writel_dbi(pci, PCIE_ATU_CR2, PCIE_ATU_ENABLE |
diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h
index bca1d3e83636..449c5ad92edc 100644
--- a/drivers/pci/controller/dwc/pcie-designware.h
+++ b/drivers/pci/controller/dwc/pcie-designware.h
@@ -227,12 +227,6 @@ struct pcie_port {
DECLARE_BITMAP(msi_irq_in_use, MAX_MSI_IRQS);
};

-enum dw_pcie_as_type {
- DW_PCIE_AS_UNKNOWN,
- DW_PCIE_AS_MEM,
- DW_PCIE_AS_IO,
-};
-
struct dw_pcie_ep_ops {
void (*ep_init)(struct dw_pcie_ep *ep);
int (*raise_irq)(struct dw_pcie_ep *ep, u8 func_no,
@@ -331,8 +325,7 @@ void dw_pcie_prog_ep_outbound_atu(struct dw_pcie *pci, u8 func_no, int index,
int type, u64 cpu_addr, u64 pci_addr,
u64 size);
int dw_pcie_prog_inbound_atu(struct dw_pcie *pci, u8 func_no, int index,
- int bar, u64 cpu_addr,
- enum dw_pcie_as_type as_type);
+ int type, u64 cpu_addr, u8 bar);
void dw_pcie_disable_atu(struct dw_pcie *pci, int index,
enum dw_pcie_region_type type);
void dw_pcie_setup(struct dw_pcie *pci);
--
2.35.1

2022-03-29 00:03:11

by Joe Perches

[permalink] [raw]
Subject: Re: [PATCH 03/16] PCI: dwc: Add more verbose link-up message

On Thu, 2022-03-24 at 04:37 +0300, Serge Semin wrote:
> Printing just "link up" isn't that much informative especially when it
> comes to working with the PCI Express bus. Even if the link is up, due to
> multiple reasons the bus performance can degrade to slower speeds or to
> narrower width than both Root Port and its partner is capable of. In that
> case it would be handy to know the link specifications as early as
> possible. So let's add a more verbose message to the busy-wait link-state
> method, which will contain the link speed generation and the PCIe bus
> width in case if the link up state is discovered. Otherwise an error will
> be printed to the system log.
>
> Signed-off-by: Serge Semin <[email protected]>
> ---
> drivers/pci/controller/dwc/pcie-designware.c | 22 +++++++++++++++-----
> 1 file changed, 17 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c
[]
> @@ -528,14 +528,26 @@ int dw_pcie_wait_for_link(struct dw_pcie *pci)
>
> /* Check if the link is up or not */
> for (retries = 0; retries < LINK_WAIT_MAX_RETRIES; retries++) {
> - if (dw_pcie_link_up(pci)) {
> - dev_info(pci->dev, "Link up\n");
> - return 0;
> - }
> + if (dw_pcie_link_up(pci))
> + break;
> +
> usleep_range(LINK_WAIT_USLEEP_MIN, LINK_WAIT_USLEEP_MAX);
> }
>
> - dev_info(pci->dev, "Phy link never came up\n");
> + if (retries < LINK_WAIT_MAX_RETRIES) {
> + u32 offset, val;
> +
> + offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
> + val = dw_pcie_readw_dbi(pci, offset + PCI_EXP_LNKSTA);
> +
> + dev_info(pci->dev, "PCIe Gen.%u x%u link up\n",
> + FIELD_GET(PCI_EXP_LNKSTA_CLS, val),
> + FIELD_GET(PCI_EXP_LNKSTA_NLW, val));
> +
> + return 0;
> + }
> +
> + dev_err(pci->dev, "Phy link never came up\n");
>
> return -ETIMEDOUT;
> }

IMO: it's generally bette to test the error condition and unindent
the typical return.

if (retries >= LINK_WAIT_MAX_RETRIES) {
dev_err(pci->dev, "Phy link never came up\n");
return -ETIMEDOUT;
}

offset = ...
val = ...
dev_info(...)

return 0;
}

2022-03-30 10:03:29

by Rob Herring

[permalink] [raw]
Subject: Re: [PATCH 09/16] PCI: dwc: Simplify in/outbound iATU setup methods

On Thu, Mar 24, 2022 at 04:37:27AM +0300, Serge Semin wrote:
> From maintainability and scalability points of view it has been wrong to
> use different iATU inbound and outbound regions accessors for the viewport
> and unrolled versions of the iATU CSRs mapping. Seeing the particular iATU
> region-wise registers layout is almost fully compatible for different
> IP-core versions, there were no much points in splitting the code up that
> way since it was possible to implement a common windows setup methods for
> both viewport and unrolled iATU CSRs spaces. While what we can observe in
> the current driver implementation of these methods, is a lot of code
> duplication, which consequently worsen the code readability,
> maintainability and scalability. Note the current implementation is a bit
> more performant than the one suggested in this commit since it implies
> having less MMIO accesses. But the gain just doesn't worth having the
> denoted difficulties especially seeing the iATU setup methods are mainly
> called on the DW PCIe controller and peripheral devices initialization
> stage.
>
> Here we suggest to move the iATU viewport and unrolled CSR access
> specifics in the dw_pcie_readl_atu() and dw_pcie_writel_atu() method, and
> convert the dw_pcie_prog_outbound_atu() and
> dw_pcie_prog_{ep_}inbound_atu() functions to being generic instead of
> having a different methods for each viewport and unrolled types of iATU
> CSRs mapping. Nothing complex really. First of all the dw_pcie_readl_atu()
> and dw_pcie_writel_atu() are converted to accept relative iATU CSRs
> address together with the iATU region direction (inbound or outbound) and
> region index. If DW PCIe controller doesn't have the unrolled iATU CSRs
> space, then the accessors will need to activate a iATU viewport based on
> the specified direction and index, otherwise a base address for the
> corresponding region CSRs will be calculated by means of the
> PCIE_ATU_UNROLL_BASE() macro. The CSRs macro have been modified in
> accordance with that logic in the pcie-designware.h header file.
>
> The rest of the changes in this commit just concern converting the iATU
> in-/out-bound setup methods and iATU regions detection procedure to be
> compatible with the new accessors semantics. As a result we've dropped the
> no more required dw_pcie_prog_outbound_atu_unroll(),
> dw_pcie_prog_inbound_atu_unroll() and dw_pcie_iatu_detect_regions_unroll()
> methods.
>
> Note aside with the denoted code improvements, there is an additional
> positive side effect of this change. If at some point an atomic iATU
> configs setup procedure is required, it will be possible to be done with
> no much effort just by adding the synchronization into the
> dw_pcie_readl_atu() and dw_pcie_writel_atu() accessors.
>
> Signed-off-by: Serge Semin <[email protected]>
> ---
> drivers/pci/controller/dwc/pcie-designware.c | 301 ++++++-------------
> drivers/pci/controller/dwc/pcie-designware.h | 50 ++-
> 2 files changed, 112 insertions(+), 239 deletions(-)

Nice diffstat. I didn't really like how this was implemented either.

I'm guessing you tested the unrolled case only? IIRC, QEMU has the older
interface. I can also throw this at kernel-ci if needed. There's a few
platforms with DWC that get tested.

>
> diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c
> index b983128584ff..f1aa6e2e85fe 100644
> --- a/drivers/pci/controller/dwc/pcie-designware.c
> +++ b/drivers/pci/controller/dwc/pcie-designware.c
> @@ -205,48 +205,67 @@ void dw_pcie_write_dbi2(struct dw_pcie *pci, u32 reg, size_t size, u32 val)
> dev_err(pci->dev, "write DBI address failed\n");
> }
>
> -static u32 dw_pcie_readl_atu(struct dw_pcie *pci, u32 reg)
> +static u32 dw_pcie_readl_atu(struct dw_pcie *pci, u32 dir, u32 region, u32 reg)
> {
> + void __iomem *base;
> int ret;
> u32 val;
>
> + if (pci->iatu_unroll_enabled) {
> + base = pci->atu_base;
> + reg = reg + PCIE_ATU_UNROLL_BASE(dir, region);
> + } else {
> + base = pci->dbi_base;
> + reg = reg + PCIE_ATU_VIEWPORT_BASE;
> +
> + dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, dir | region);
> + }
> +
> if (pci->ops && pci->ops->read_dbi)
> - return pci->ops->read_dbi(pci, pci->atu_base, reg, 4);
> + return pci->ops->read_dbi(pci, base, reg, 4);
>
> - ret = dw_pcie_read(pci->atu_base + reg, 4, &val);
> + ret = dw_pcie_read(base + reg, 4, &val);
> if (ret)
> dev_err(pci->dev, "Read ATU address failed\n");
>
> return val;
> }
>
> -static void dw_pcie_writel_atu(struct dw_pcie *pci, u32 reg, u32 val)
> +static void dw_pcie_writel_atu(struct dw_pcie *pci, u32 dir, u32 region,
> + u32 reg, u32 val)
> {
> + void __iomem *base;
> int ret;
>
> + if (pci->iatu_unroll_enabled) {
> + base = pci->atu_base;
> + reg = reg + PCIE_ATU_UNROLL_BASE(dir, region);
> + } else {
> + base = pci->dbi_base;
> + reg = reg + PCIE_ATU_VIEWPORT_BASE;
> +
> + dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, dir | region);
> + }

You have this same sequence twice. Make it a helper function.

> +
> if (pci->ops && pci->ops->write_dbi) {
> - pci->ops->write_dbi(pci, pci->atu_base, reg, 4, val);
> + pci->ops->write_dbi(pci, base, reg, 4, val);
> return;
> }
>
> - ret = dw_pcie_write(pci->atu_base + reg, 4, val);
> + ret = dw_pcie_write(base + reg, 4, val);
> if (ret)
> dev_err(pci->dev, "Write ATU address failed\n");
> }
>
> -static u32 dw_pcie_readl_ob_unroll(struct dw_pcie *pci, u32 index, u32 reg)
> +static inline u32 dw_pcie_readl_atu_ob(struct dw_pcie *pci, u32 region, u32 reg)
> {
> - u32 offset = PCIE_GET_ATU_OUTB_UNR_REG_OFFSET(index);
> -
> - return dw_pcie_readl_atu(pci, offset + reg);
> + return dw_pcie_readl_atu(pci, PCIE_ATU_REGION_DIR_OB, region, reg);
> }
>
> -static void dw_pcie_writel_ob_unroll(struct dw_pcie *pci, u32 index, u32 reg,
> - u32 val)
> +static inline void dw_pcie_writel_atu_ob(struct dw_pcie *pci, u32 region, u32 reg,
> + u32 val)
> {
> - u32 offset = PCIE_GET_ATU_OUTB_UNR_REG_OFFSET(index);
> -
> - dw_pcie_writel_atu(pci, offset + reg, val);
> + dw_pcie_writel_atu(pci, PCIE_ATU_REGION_DIR_OB, region, reg, val);
> }
>
> static inline u32 dw_pcie_enable_ecrc(u32 val)
> @@ -290,50 +309,6 @@ static inline u32 dw_pcie_enable_ecrc(u32 val)
> return val | PCIE_ATU_TD;
> }
>
> -static void dw_pcie_prog_outbound_atu_unroll(struct dw_pcie *pci, u8 func_no,
> - int index, int type,
> - u64 cpu_addr, u64 pci_addr,
> - u64 size)
> -{
> - u32 retries, val;
> - u64 limit_addr = cpu_addr + size - 1;
> -
> - dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_LOWER_BASE,
> - lower_32_bits(cpu_addr));
> - dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_UPPER_BASE,
> - upper_32_bits(cpu_addr));
> - dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_LOWER_LIMIT,
> - lower_32_bits(limit_addr));
> - dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_UPPER_LIMIT,
> - upper_32_bits(limit_addr));
> - dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_LOWER_TARGET,
> - lower_32_bits(pci_addr));
> - dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_UPPER_TARGET,
> - upper_32_bits(pci_addr));
> - val = type | PCIE_ATU_FUNC_NUM(func_no);
> - if (upper_32_bits(limit_addr) > upper_32_bits(cpu_addr))
> - val |= PCIE_ATU_INCREASE_REGION_SIZE;
> - if (dw_pcie_ver_is(pci, 490A))
> - val = dw_pcie_enable_ecrc(val);
> - dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL1, val);
> - dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2,
> - PCIE_ATU_ENABLE);
> -
> - /*
> - * Make sure ATU enable takes effect before any subsequent config
> - * and I/O accesses.
> - */
> - for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) {
> - val = dw_pcie_readl_ob_unroll(pci, index,
> - PCIE_ATU_UNR_REGION_CTRL2);
> - if (val & PCIE_ATU_ENABLE)
> - return;
> -
> - mdelay(LINK_WAIT_IATU);
> - }
> - dev_err(pci->dev, "Outbound iATU is not being enabled\n");
> -}
> -
> static void __dw_pcie_prog_outbound_atu(struct dw_pcie *pci, u8 func_no,
> int index, int type, u64 cpu_addr,
> u64 pci_addr, u64 size)
> @@ -344,49 +319,46 @@ static void __dw_pcie_prog_outbound_atu(struct dw_pcie *pci, u8 func_no,
> if (pci->ops && pci->ops->cpu_addr_fixup)
> cpu_addr = pci->ops->cpu_addr_fixup(pci, cpu_addr);
>
> - if (pci->iatu_unroll_enabled) {
> - dw_pcie_prog_outbound_atu_unroll(pci, func_no, index, type,
> - cpu_addr, pci_addr, size);
> - return;
> - }
> -
> limit_addr = cpu_addr + size - 1;
>
> - dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT,
> - PCIE_ATU_REGION_OUTBOUND | index);
> - dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_BASE,
> - lower_32_bits(cpu_addr));
> - dw_pcie_writel_dbi(pci, PCIE_ATU_UPPER_BASE,
> - upper_32_bits(cpu_addr));
> - dw_pcie_writel_dbi(pci, PCIE_ATU_LIMIT,
> - lower_32_bits(limit_addr));
> + dw_pcie_writel_atu_ob(pci, index, PCIE_ATU_LOWER_BASE,
> + lower_32_bits(cpu_addr));
> + dw_pcie_writel_atu_ob(pci, index, PCIE_ATU_UPPER_BASE,
> + upper_32_bits(cpu_addr));
> +
> + dw_pcie_writel_atu_ob(pci, index, PCIE_ATU_LIMIT,
> + lower_32_bits(limit_addr));
> if (dw_pcie_ver_is_ge(pci, 460A))
> - dw_pcie_writel_dbi(pci, PCIE_ATU_UPPER_LIMIT,
> - upper_32_bits(limit_addr));
> - dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_TARGET,
> - lower_32_bits(pci_addr));
> - dw_pcie_writel_dbi(pci, PCIE_ATU_UPPER_TARGET,
> - upper_32_bits(pci_addr));
> + dw_pcie_writel_atu_ob(pci, index, PCIE_ATU_UPPER_LIMIT,
> + upper_32_bits(limit_addr));
> +
> + dw_pcie_writel_atu_ob(pci, index, PCIE_ATU_LOWER_TARGET,
> + lower_32_bits(pci_addr));
> + dw_pcie_writel_atu_ob(pci, index, PCIE_ATU_UPPER_TARGET,
> + upper_32_bits(pci_addr));
> +
> val = type | PCIE_ATU_FUNC_NUM(func_no);
> if (upper_32_bits(limit_addr) > upper_32_bits(cpu_addr) &&
> dw_pcie_ver_is_ge(pci, 460A))
> val |= PCIE_ATU_INCREASE_REGION_SIZE;
> if (dw_pcie_ver_is(pci, 490A))
> val = dw_pcie_enable_ecrc(val);
> - dw_pcie_writel_dbi(pci, PCIE_ATU_CR1, val);
> - dw_pcie_writel_dbi(pci, PCIE_ATU_CR2, PCIE_ATU_ENABLE);
> + dw_pcie_writel_atu_ob(pci, index, PCIE_ATU_REGION_CTRL1, val);
> +
> + dw_pcie_writel_atu_ob(pci, index, PCIE_ATU_REGION_CTRL2, PCIE_ATU_ENABLE);
>
> /*
> * Make sure ATU enable takes effect before any subsequent config
> * and I/O accesses.
> */
> for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) {
> - val = dw_pcie_readl_dbi(pci, PCIE_ATU_CR2);
> + val = dw_pcie_readl_atu_ob(pci, index, PCIE_ATU_REGION_CTRL2);
> if (val & PCIE_ATU_ENABLE)
> return;
>
> mdelay(LINK_WAIT_IATU);
> }
> +
> dev_err(pci->dev, "Outbound iATU is not being enabled\n");
> }
>
> @@ -405,54 +377,15 @@ void dw_pcie_prog_ep_outbound_atu(struct dw_pcie *pci, u8 func_no, int index,
> cpu_addr, pci_addr, size);
> }
>
> -static u32 dw_pcie_readl_ib_unroll(struct dw_pcie *pci, u32 index, u32 reg)
> -{
> - u32 offset = PCIE_GET_ATU_INB_UNR_REG_OFFSET(index);
> -
> - return dw_pcie_readl_atu(pci, offset + reg);
> -}
> -
> -static void dw_pcie_writel_ib_unroll(struct dw_pcie *pci, u32 index, u32 reg,
> - u32 val)
> +static inline u32 dw_pcie_readl_atu_ib(struct dw_pcie *pci, u32 region, u32 reg)
> {
> - u32 offset = PCIE_GET_ATU_INB_UNR_REG_OFFSET(index);
> -
> - dw_pcie_writel_atu(pci, offset + reg, val);
> + return dw_pcie_readl_atu(pci, PCIE_ATU_REGION_DIR_IB, region, reg);
> }
>
> -static int dw_pcie_prog_inbound_atu_unroll(struct dw_pcie *pci, u8 func_no,
> - int index, int type,
> - u64 cpu_addr, u8 bar)
> +static inline void dw_pcie_writel_atu_ib(struct dw_pcie *pci, u32 region, u32 reg,
> + u32 val)
> {
> - u32 retries, val;
> -
> - dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_LOWER_TARGET,
> - lower_32_bits(cpu_addr));
> - dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_UPPER_TARGET,
> - upper_32_bits(cpu_addr));
> -
> - dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL1, type |
> - PCIE_ATU_FUNC_NUM(func_no));
> - dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2,
> - PCIE_ATU_FUNC_NUM_MATCH_EN |
> - PCIE_ATU_ENABLE |
> - PCIE_ATU_BAR_MODE_ENABLE | (bar << 8));
> -
> - /*
> - * Make sure ATU enable takes effect before any subsequent config
> - * and I/O accesses.
> - */
> - for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) {
> - val = dw_pcie_readl_ib_unroll(pci, index,
> - PCIE_ATU_UNR_REGION_CTRL2);
> - if (val & PCIE_ATU_ENABLE)
> - return 0;
> -
> - mdelay(LINK_WAIT_IATU);
> - }
> - dev_err(pci->dev, "Inbound iATU is not being enabled\n");
> -
> - return -EBUSY;
> + dw_pcie_writel_atu(pci, PCIE_ATU_REGION_DIR_IB, region, reg, val);
> }
>
> int dw_pcie_prog_inbound_atu(struct dw_pcie *pci, u8 func_no, int index,
> @@ -460,65 +393,51 @@ int dw_pcie_prog_inbound_atu(struct dw_pcie *pci, u8 func_no, int index,
> {
> u32 retries, val;
>
> - if (pci->iatu_unroll_enabled)
> - return dw_pcie_prog_inbound_atu_unroll(pci, func_no, index, type,
> - cpu_addr, bar);
> -
> - dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, PCIE_ATU_REGION_INBOUND |
> - index);
> - dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_TARGET, lower_32_bits(cpu_addr));
> - dw_pcie_writel_dbi(pci, PCIE_ATU_UPPER_TARGET, upper_32_bits(cpu_addr));
> + dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_LOWER_TARGET,
> + lower_32_bits(cpu_addr));
> + dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_UPPER_TARGET,
> + upper_32_bits(cpu_addr));
>
> - dw_pcie_writel_dbi(pci, PCIE_ATU_CR1, type |
> - PCIE_ATU_FUNC_NUM(func_no));
> - dw_pcie_writel_dbi(pci, PCIE_ATU_CR2, PCIE_ATU_ENABLE |
> - PCIE_ATU_FUNC_NUM_MATCH_EN |
> - PCIE_ATU_BAR_MODE_ENABLE | (bar << 8));
> + dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_REGION_CTRL1, type |
> + PCIE_ATU_FUNC_NUM(func_no));
> + dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_REGION_CTRL2,
> + PCIE_ATU_ENABLE | PCIE_ATU_FUNC_NUM_MATCH_EN |
> + PCIE_ATU_BAR_MODE_ENABLE | (bar << 8));
>
> /*
> * Make sure ATU enable takes effect before any subsequent config
> * and I/O accesses.
> */
> for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) {
> - val = dw_pcie_readl_dbi(pci, PCIE_ATU_CR2);
> + val = dw_pcie_readl_atu_ib(pci, index, PCIE_ATU_REGION_CTRL2);
> if (val & PCIE_ATU_ENABLE)
> return 0;
>
> mdelay(LINK_WAIT_IATU);
> }
> +
> dev_err(pci->dev, "Inbound iATU is not being enabled\n");
>
> - return -EBUSY;
> + return -ETIMEDOUT;
> }
>
> void dw_pcie_disable_atu(struct dw_pcie *pci, int index,
> enum dw_pcie_region_type type)
> {
> - int region;
> + u32 dir;
>
> switch (type) {
> case DW_PCIE_REGION_INBOUND:
> - region = PCIE_ATU_REGION_INBOUND;
> + dir = PCIE_ATU_REGION_DIR_IB;

Is this the same double definition with the enum?

> break;
> case DW_PCIE_REGION_OUTBOUND:
> - region = PCIE_ATU_REGION_OUTBOUND;
> + dir = PCIE_ATU_REGION_DIR_OB;
> break;
> default:
> return;
> }
>
> - if (pci->iatu_unroll_enabled) {
> - if (region == PCIE_ATU_REGION_INBOUND) {
> - dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2,
> - ~(u32)PCIE_ATU_ENABLE);
> - } else {
> - dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2,
> - ~(u32)PCIE_ATU_ENABLE);
> - }
> - } else {
> - dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, region | index);
> - dw_pcie_writel_dbi(pci, PCIE_ATU_CR2, ~(u32)PCIE_ATU_ENABLE);
> - }
> + dw_pcie_writel_atu(pci, dir, index, PCIE_ATU_REGION_CTRL2, 0);
> }
>
> int dw_pcie_wait_for_link(struct dw_pcie *pci)
> @@ -622,63 +541,29 @@ static bool dw_pcie_iatu_unroll_enabled(struct dw_pcie *pci)
> return false;
> }
>
> -static void dw_pcie_iatu_detect_regions_unroll(struct dw_pcie *pci)
> -{
> - int max_region, i, ob = 0, ib = 0;
> - u32 val;
> -
> - max_region = min((int)pci->atu_size / 512, 256);
> -
> - for (i = 0; i < max_region; i++) {
> - dw_pcie_writel_ob_unroll(pci, i, PCIE_ATU_UNR_LOWER_TARGET,
> - 0x11110000);
> -
> - val = dw_pcie_readl_ob_unroll(pci, i, PCIE_ATU_UNR_LOWER_TARGET);
> - if (val == 0x11110000)
> - ob++;
> - else
> - break;
> - }
> -
> - for (i = 0; i < max_region; i++) {
> - dw_pcie_writel_ib_unroll(pci, i, PCIE_ATU_UNR_LOWER_TARGET,
> - 0x11110000);
> -
> - val = dw_pcie_readl_ib_unroll(pci, i, PCIE_ATU_UNR_LOWER_TARGET);
> - if (val == 0x11110000)
> - ib++;
> - else
> - break;
> - }
> - pci->num_ib_windows = ib;
> - pci->num_ob_windows = ob;
> -}
> -
> static void dw_pcie_iatu_detect_regions(struct dw_pcie *pci)
> {
> - int max_region, i, ob = 0, ib = 0;
> + int max_region, ob, ib;
> u32 val;
>
> - dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, 0xFF);
> - max_region = dw_pcie_readl_dbi(pci, PCIE_ATU_VIEWPORT) + 1;
> + if (pci->iatu_unroll_enabled) {
> + max_region = min((int)pci->atu_size / 512, 256);
> + } else {
> + dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, 0xFF);
> + max_region = dw_pcie_readl_dbi(pci, PCIE_ATU_VIEWPORT) + 1;
> + }
>
> - for (i = 0; i < max_region; i++) {
> - dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, PCIE_ATU_REGION_OUTBOUND | i);
> - dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_TARGET, 0x11110000);
> - val = dw_pcie_readl_dbi(pci, PCIE_ATU_LOWER_TARGET);
> - if (val == 0x11110000)
> - ob++;
> - else
> + for (ob = 0; ob < max_region; ob++) {
> + dw_pcie_writel_atu_ob(pci, ob, PCIE_ATU_LOWER_TARGET, 0x11110000);
> + val = dw_pcie_readl_atu_ob(pci, ob, PCIE_ATU_LOWER_TARGET);
> + if (val != 0x11110000)
> break;
> }
>
> - for (i = 0; i < max_region; i++) {
> - dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, PCIE_ATU_REGION_INBOUND | i);
> - dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_TARGET, 0x11110000);
> - val = dw_pcie_readl_dbi(pci, PCIE_ATU_LOWER_TARGET);
> - if (val == 0x11110000)
> - ib++;
> - else
> + for (ib = 0; ib < max_region; ib++) {
> + dw_pcie_writel_atu_ib(pci, ib, PCIE_ATU_LOWER_TARGET, 0x11110000);
> + val = dw_pcie_readl_atu_ib(pci, ib, PCIE_ATU_LOWER_TARGET);
> + if (val != 0x11110000)
> break;
> }
>
> @@ -707,12 +592,10 @@ void dw_pcie_iatu_detect(struct dw_pcie *pci)
> if (!pci->atu_size)
> /* Pick a minimal default, enough for 8 in and 8 out windows */
> pci->atu_size = SZ_4K;
> -
> - dw_pcie_iatu_detect_regions_unroll(pci);
> - } else {
> - dw_pcie_iatu_detect_regions(pci);
> }
>
> + dw_pcie_iatu_detect_regions(pci);
> +
> dev_info(pci->dev, "iATU unroll: %s\n", pci->iatu_unroll_enabled ?
> "enabled" : "disabled");
>
> diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h
> index 449c5ad92edc..6adf0c957c3b 100644
> --- a/drivers/pci/controller/dwc/pcie-designware.h
> +++ b/drivers/pci/controller/dwc/pcie-designware.h
> @@ -103,10 +103,20 @@
> #define PCIE_VERSION_NUMBER 0x8F8
> #define PCIE_VERSION_TYPE 0x8FC
>
> +/*
> + * iATU inbound and outbound windows CSRs. Before the IP-core v4.80a each
> + * iATU region CSRs had been indirectly accessible by means of the dedicated
> + * viewport selector. The iATU/eDMA CSRs space was re-designed in DWC PCIe
> + * v4.80a in a way so the viewport was unrolled into the directly accessible
> + * iATU/eDMA CSRs space.

IIRC, I think it is configurable in later versions. There was some
discussion when I did the detection.

> + */
> #define PCIE_ATU_VIEWPORT 0x900
> -#define PCIE_ATU_REGION_INBOUND BIT(31)
> -#define PCIE_ATU_REGION_OUTBOUND 0
> -#define PCIE_ATU_CR1 0x904
> +#define PCIE_ATU_REGION_DIR_IB BIT(31)
> +#define PCIE_ATU_REGION_DIR_OB 0
> +#define PCIE_ATU_VIEWPORT_BASE 0x904
> +#define PCIE_ATU_UNROLL_BASE(dir, region) \
> + (((region) << 9) | ((dir == PCIE_ATU_REGION_DIR_IB) ? BIT(8) : 0))
> +#define PCIE_ATU_REGION_CTRL1 0x000
> #define PCIE_ATU_INCREASE_REGION_SIZE BIT(13)
> #define PCIE_ATU_TYPE_MEM 0x0
> #define PCIE_ATU_TYPE_IO 0x2
> @@ -114,19 +124,19 @@
> #define PCIE_ATU_TYPE_CFG1 0x5
> #define PCIE_ATU_TD BIT(8)
> #define PCIE_ATU_FUNC_NUM(pf) ((pf) << 20)
> -#define PCIE_ATU_CR2 0x908
> +#define PCIE_ATU_REGION_CTRL2 0x004
> #define PCIE_ATU_ENABLE BIT(31)
> #define PCIE_ATU_BAR_MODE_ENABLE BIT(30)
> #define PCIE_ATU_FUNC_NUM_MATCH_EN BIT(19)
> -#define PCIE_ATU_LOWER_BASE 0x90C
> -#define PCIE_ATU_UPPER_BASE 0x910
> -#define PCIE_ATU_LIMIT 0x914
> -#define PCIE_ATU_LOWER_TARGET 0x918
> +#define PCIE_ATU_LOWER_BASE 0x008
> +#define PCIE_ATU_UPPER_BASE 0x00C
> +#define PCIE_ATU_LIMIT 0x010
> +#define PCIE_ATU_LOWER_TARGET 0x014
> #define PCIE_ATU_BUS(x) FIELD_PREP(GENMASK(31, 24), x)
> #define PCIE_ATU_DEV(x) FIELD_PREP(GENMASK(23, 19), x)
> #define PCIE_ATU_FUNC(x) FIELD_PREP(GENMASK(18, 16), x)
> -#define PCIE_ATU_UPPER_TARGET 0x91C
> -#define PCIE_ATU_UPPER_LIMIT 0x924
> +#define PCIE_ATU_UPPER_TARGET 0x018
> +#define PCIE_ATU_UPPER_LIMIT 0x020
>
> #define PCIE_MISC_CONTROL_1_OFF 0x8BC
> #define PCIE_DBI_RO_WR_EN BIT(0)
> @@ -143,19 +153,6 @@
>
> #define PCIE_PL_CHK_REG_ERR_ADDR 0xB28
>
> -/*
> - * iATU Unroll-specific register definitions
> - * From 4.80 core version the address translation will be made by unroll
> - */
> -#define PCIE_ATU_UNR_REGION_CTRL1 0x00
> -#define PCIE_ATU_UNR_REGION_CTRL2 0x04
> -#define PCIE_ATU_UNR_LOWER_BASE 0x08
> -#define PCIE_ATU_UNR_UPPER_BASE 0x0C
> -#define PCIE_ATU_UNR_LOWER_LIMIT 0x10
> -#define PCIE_ATU_UNR_LOWER_TARGET 0x14
> -#define PCIE_ATU_UNR_UPPER_TARGET 0x18
> -#define PCIE_ATU_UNR_UPPER_LIMIT 0x20
> -
> /*
> * The default address offset between dbi_base and atu_base. Root controller
> * drivers are not required to initialize atu_base if the offset matches this
> @@ -164,13 +161,6 @@
> */
> #define DEFAULT_DBI_ATU_OFFSET (0x3 << 20)
>
> -/* Register address builder */
> -#define PCIE_GET_ATU_OUTB_UNR_REG_OFFSET(region) \
> - ((region) << 9)
> -
> -#define PCIE_GET_ATU_INB_UNR_REG_OFFSET(region) \
> - (((region) << 9) | BIT(8))
> -
> #define MAX_MSI_IRQS 256
> #define MAX_MSI_IRQS_PER_CTRL 32
> #define MAX_MSI_CTRLS (MAX_MSI_IRQS / MAX_MSI_IRQS_PER_CTRL)
> --
> 2.35.1
>

2022-03-30 11:49:18

by Rob Herring

[permalink] [raw]
Subject: Re: [PATCH 08/16] PCI: dwc: Drop inbound iATU types enumeration - dw_pcie_as_type

On Thu, Mar 24, 2022 at 04:37:26AM +0300, Serge Semin wrote:
> There is no point in having an enumeration declared in the driver for the
> PCIe end-point. First of all it's redundant since the driver already has a
> set of macro declared which describe the available in/out iATU types, thus
> having an addition abstraction just needlessly complicates the code.
> Secondly checking the passed iATU type for validity within a single driver
> is pointless since the driver is supposed to be consistent by its nature.
> Finally the outbound iATU type isn't encoded by the denoted enumeration,
> thus giving a false impression that the in and out iATU types are
> unrelated while they are the same. So to speak let's drop the redundant
> dw_pcie_as_type enumeration replacing it with the direct iATU type usage.
>
> While at it, since we are touching the iATU inbound regions config methods
> anyway, let's fix the arguments order so the type would be followed by the
> address-related parameters. Thus the inbound and outbound iATU setup
> methods will look alike. That shall improve the code readability a bit.
>
> Signed-off-by: Serge Semin <[email protected]>
> ---
> .../pci/controller/dwc/pcie-designware-ep.c | 21 +++++------
> drivers/pci/controller/dwc/pcie-designware.c | 35 +++----------------
> drivers/pci/controller/dwc/pcie-designware.h | 9 +----
> 3 files changed, 15 insertions(+), 50 deletions(-)

Reviewed-by: Rob Herring <[email protected]>

2022-03-30 11:50:49

by Rob Herring

[permalink] [raw]
Subject: Re: [PATCH 03/16] PCI: dwc: Add more verbose link-up message

On Thu, Mar 24, 2022 at 04:37:21AM +0300, Serge Semin wrote:
> Printing just "link up" isn't that much informative especially when it
> comes to working with the PCI Express bus. Even if the link is up, due to
> multiple reasons the bus performance can degrade to slower speeds or to
> narrower width than both Root Port and its partner is capable of. In that
> case it would be handy to know the link specifications as early as
> possible. So let's add a more verbose message to the busy-wait link-state
> method, which will contain the link speed generation and the PCIe bus
> width in case if the link up state is discovered. Otherwise an error will
> be printed to the system log.
>
> Signed-off-by: Serge Semin <[email protected]>
> ---
> drivers/pci/controller/dwc/pcie-designware.c | 22 +++++++++++++++-----
> 1 file changed, 17 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c
> index 6e81264fdfb4..f1693e25afcb 100644
> --- a/drivers/pci/controller/dwc/pcie-designware.c
> +++ b/drivers/pci/controller/dwc/pcie-designware.c
> @@ -528,14 +528,26 @@ int dw_pcie_wait_for_link(struct dw_pcie *pci)
>
> /* Check if the link is up or not */
> for (retries = 0; retries < LINK_WAIT_MAX_RETRIES; retries++) {
> - if (dw_pcie_link_up(pci)) {
> - dev_info(pci->dev, "Link up\n");
> - return 0;
> - }
> + if (dw_pcie_link_up(pci))
> + break;
> +
> usleep_range(LINK_WAIT_USLEEP_MIN, LINK_WAIT_USLEEP_MAX);
> }
>
> - dev_info(pci->dev, "Phy link never came up\n");
> + if (retries < LINK_WAIT_MAX_RETRIES) {
> + u32 offset, val;
> +
> + offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
> + val = dw_pcie_readw_dbi(pci, offset + PCI_EXP_LNKSTA);
> +
> + dev_info(pci->dev, "PCIe Gen.%u x%u link up\n",
> + FIELD_GET(PCI_EXP_LNKSTA_CLS, val),
> + FIELD_GET(PCI_EXP_LNKSTA_NLW, val));

Given these are standard registers can we do this in the core code? The
main issue I think is that the config space accessors don't work until
you create the bus struct. That still should be early enough.

I think it is possible some implementations don't report the link state
in these registers. Maybe we don't really need to care.

Rob

2022-03-30 13:22:38

by Rob Herring

[permalink] [raw]
Subject: Re: [PATCH 04/16] PCI: dwc: Convert to using native IP-core versions representation

On Thu, Mar 24, 2022 at 04:37:22AM +0300, Serge Semin wrote:
> Since DWC PCIe v4.70a the controller version can be read from the
> PORT_LOGIC.PCIE_VERSION_OFF register. Version is represented in the FourCC
> format [1]. It's standard versioning approach for the Synopsys DWC
> IP-cores. Moreover some of the DWC kernel drivers already make use of it
> to fixup version-dependent functionality (See DWC USB3, Stmicro STMMAC or
> recent DW SPI driver). In order to preserve the standard version
> representation and prevent the data conversion back and forth, we suggest
> to preserve the native version representation in the DWC PCIe driver too
> in the same way as it has already been done in the rest of the DWC
> drivers. IP-core version reading from the CSR will be introduced in the
> next commit together with a simple macro-based API to use it.
>
> [1] https://en.wikipedia.org/wiki/FourCC
>
> Signed-off-by: Serge Semin <[email protected]>
> ---
> drivers/pci/controller/dwc/pci-keystone.c | 12 ++++++------
> drivers/pci/controller/dwc/pcie-designware.c | 8 ++++----
> drivers/pci/controller/dwc/pcie-designware.h | 10 +++++++++-
> drivers/pci/controller/dwc/pcie-intel-gw.c | 4 ++--
> drivers/pci/controller/dwc/pcie-tegra194.c | 2 +-
> 5 files changed, 22 insertions(+), 14 deletions(-)
>
> diff --git a/drivers/pci/controller/dwc/pci-keystone.c b/drivers/pci/controller/dwc/pci-keystone.c
> index 1c2ee4e13f1c..ec65355dd29b 100644
> --- a/drivers/pci/controller/dwc/pci-keystone.c
> +++ b/drivers/pci/controller/dwc/pci-keystone.c
> @@ -109,7 +109,7 @@ struct ks_pcie_of_data {
> enum dw_pcie_device_mode mode;
> const struct dw_pcie_host_ops *host_ops;
> const struct dw_pcie_ep_ops *ep_ops;
> - unsigned int version;
> + u32 version;
> };
>
> struct keystone_pcie {
> @@ -1069,19 +1069,19 @@ static int ks_pcie_am654_set_mode(struct device *dev,
>
> static const struct ks_pcie_of_data ks_pcie_rc_of_data = {
> .host_ops = &ks_pcie_host_ops,
> - .version = 0x365A,
> + .version = DW_PCIE_VER_365A,
> };
>
> static const struct ks_pcie_of_data ks_pcie_am654_rc_of_data = {
> .host_ops = &ks_pcie_am654_host_ops,
> .mode = DW_PCIE_RC_TYPE,
> - .version = 0x490A,
> + .version = DW_PCIE_VER_490A,

This version should be readable, so get rid of hardcoding it here.

> };
>
> static const struct ks_pcie_of_data ks_pcie_am654_ep_of_data = {
> .ep_ops = &ks_pcie_am654_ep_ops,
> .mode = DW_PCIE_EP_TYPE,
> - .version = 0x490A,
> + .version = DW_PCIE_VER_490A,
> };
>
> static const struct of_device_id ks_pcie_of_match[] = {
> @@ -1114,12 +1114,12 @@ static int __init ks_pcie_probe(struct platform_device *pdev)
> struct device_link **link;
> struct gpio_desc *gpiod;
> struct resource *res;
> - unsigned int version;
> void __iomem *base;
> u32 num_viewport;
> struct phy **phy;
> u32 num_lanes;
> char name[10];
> + u32 version;
> int ret;
> int irq;
> int i;
> @@ -1233,7 +1233,7 @@ static int __init ks_pcie_probe(struct platform_device *pdev)
> goto err_get_sync;
> }
>
> - if (pci->version >= 0x480A)
> + if (pci->version >= DW_PCIE_VER_480A)
> ret = ks_pcie_am654_set_mode(dev, mode);
> else
> ret = ks_pcie_set_mode(dev);
> diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c
> index f1693e25afcb..c21373c6cb51 100644
> --- a/drivers/pci/controller/dwc/pcie-designware.c
> +++ b/drivers/pci/controller/dwc/pcie-designware.c
> @@ -289,7 +289,7 @@ static void dw_pcie_prog_outbound_atu_unroll(struct dw_pcie *pci, u8 func_no,
> val = type | PCIE_ATU_FUNC_NUM(func_no);
> if (upper_32_bits(limit_addr) > upper_32_bits(cpu_addr))
> val |= PCIE_ATU_INCREASE_REGION_SIZE;
> - if (pci->version == 0x490A)
> + if (pci->version == DW_PCIE_VER_490A)
> val = dw_pcie_enable_ecrc(val);
> dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL1, val);
> dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2,
> @@ -336,7 +336,7 @@ static void __dw_pcie_prog_outbound_atu(struct dw_pcie *pci, u8 func_no,
> upper_32_bits(cpu_addr));
> dw_pcie_writel_dbi(pci, PCIE_ATU_LIMIT,
> lower_32_bits(limit_addr));
> - if (pci->version >= 0x460A)
> + if (pci->version >= DW_PCIE_VER_460A)
> dw_pcie_writel_dbi(pci, PCIE_ATU_UPPER_LIMIT,
> upper_32_bits(limit_addr));
> dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_TARGET,
> @@ -345,9 +345,9 @@ static void __dw_pcie_prog_outbound_atu(struct dw_pcie *pci, u8 func_no,
> upper_32_bits(pci_addr));
> val = type | PCIE_ATU_FUNC_NUM(func_no);
> if (upper_32_bits(limit_addr) > upper_32_bits(cpu_addr) &&
> - pci->version >= 0x460A)
> + pci->version >= DW_PCIE_VER_460A)
> val |= PCIE_ATU_INCREASE_REGION_SIZE;
> - if (pci->version == 0x490A)
> + if (pci->version == DW_PCIE_VER_490A)
> val = dw_pcie_enable_ecrc(val);
> dw_pcie_writel_dbi(pci, PCIE_ATU_CR1, val);
> dw_pcie_writel_dbi(pci, PCIE_ATU_CR2, PCIE_ATU_ENABLE);
> diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h
> index 7d6e9b7576be..5be43c662176 100644
> --- a/drivers/pci/controller/dwc/pcie-designware.h
> +++ b/drivers/pci/controller/dwc/pcie-designware.h
> @@ -20,6 +20,14 @@
> #include <linux/pci-epc.h>
> #include <linux/pci-epf.h>
>
> +/* DWC PCIe IP-core versions (native support since v4.70a) */
> +#define DW_PCIE_VER_365A 0x3336352a
> +#define DW_PCIE_VER_460A 0x3436302a
> +#define DW_PCIE_VER_470A 0x3437302a
> +#define DW_PCIE_VER_480A 0x3438302a
> +#define DW_PCIE_VER_490A 0x3439302a
> +#define DW_PCIE_VER_520A 0x3532302a

Create a macro to form these so we can do:

DW_PCIE_VER('5', '2', '0', 'A')

> +
> /* Parameters for the waiting for link up routine */
> #define LINK_WAIT_MAX_RETRIES 10
> #define LINK_WAIT_USLEEP_MIN 90000
> @@ -269,7 +277,7 @@ struct dw_pcie {
> struct pcie_port pp;
> struct dw_pcie_ep ep;
> const struct dw_pcie_ops *ops;
> - unsigned int version;
> + u32 version;
> int num_lanes;
> int link_gen;
> u8 n_fts[2];
> diff --git a/drivers/pci/controller/dwc/pcie-intel-gw.c b/drivers/pci/controller/dwc/pcie-intel-gw.c
> index 5ba144924ff8..786af2ba379f 100644
> --- a/drivers/pci/controller/dwc/pcie-intel-gw.c
> +++ b/drivers/pci/controller/dwc/pcie-intel-gw.c
> @@ -59,7 +59,7 @@
> #define RESET_INTERVAL_MS 100
>
> struct intel_pcie_soc {
> - unsigned int pcie_ver;
> + u32 pcie_ver;
> };
>
> struct intel_pcie {
> @@ -395,7 +395,7 @@ static const struct dw_pcie_host_ops intel_pcie_dw_ops = {
> };
>
> static const struct intel_pcie_soc pcie_data = {
> - .pcie_ver = 0x520A,
> + .pcie_ver = DW_PCIE_VER_520A,
> };
>
> static int intel_pcie_probe(struct platform_device *pdev)
> diff --git a/drivers/pci/controller/dwc/pcie-tegra194.c b/drivers/pci/controller/dwc/pcie-tegra194.c
> index b1b5f836a806..6f1330ed63e5 100644
> --- a/drivers/pci/controller/dwc/pcie-tegra194.c
> +++ b/drivers/pci/controller/dwc/pcie-tegra194.c
> @@ -1981,7 +1981,7 @@ static int tegra194_pcie_probe(struct platform_device *pdev)
> pci->ops = &tegra_dw_pcie_ops;
> pci->n_fts[0] = N_FTS_VAL;
> pci->n_fts[1] = FTS_VAL;
> - pci->version = 0x490A;
> + pci->version = DW_PCIE_VER_490A;
>
> pp = &pci->pp;
> pp->num_vectors = MAX_MSI_IRQS;
> --
> 2.35.1
>

2022-03-30 16:03:53

by Rob Herring

[permalink] [raw]
Subject: Re: [PATCH 10/16] PCI: dwc: Drop iATU regions enumeration - dw_pcie_region_type

On Thu, Mar 24, 2022 at 04:37:28AM +0300, Serge Semin wrote:
> There is no point in having the dw_pcie_region_type enumeration for almost
> the same reasons as it was stated for dw_pcie_as_type. First of all it's
> redundant since the driver already has a set of macro declared which
> describe the possible inbound and outbound iATU regions. Having an
> addition abstraction just needlessly complicates the code. Secondly
> checking the region index passed to the dw_pcie_disable_atu() method for
> validity is pointless since the erroneous situation will be just
> ignored in the current code implementation. So to speak let's drop the
> redundant dw_pcie_region_type enumeration replacing it with the direct
> iATU direction macro usage.
>
> While at it we suggest to convert the dw_pcie_disable_atu() method to
> being more consistent with the dw_pcie_readl_atu{_ib}() and
> dw_pcie_readl_atu{_ob}() functions by having the direction parameter
> specified ahead of the region index. Thus the code will be a little bit
> more pleasant to read.
>
> Signed-off-by: Serge Semin <[email protected]>
> ---
> drivers/pci/controller/dwc/pcie-designware-ep.c | 4 ++--
> .../pci/controller/dwc/pcie-designware-host.c | 2 +-
> drivers/pci/controller/dwc/pcie-designware.c | 16 +---------------
> drivers/pci/controller/dwc/pcie-designware.h | 9 +--------
> 4 files changed, 5 insertions(+), 26 deletions(-)

This answers my question. I would have expected this to come before the
previous patch, but if it's easier to do it this way it's fine.

Reviewed-by: Rob Herring <[email protected]>

2022-04-16 02:42:45

by Serge Semin

[permalink] [raw]
Subject: Re: [PATCH 01/16] dt-bindings: PCI: dwc: Define generic and native DT bindings

Hello Rob,

On Mon, Mar 28, 2022 at 03:46:04PM -0500, Rob Herring wrote:
> On Thu, Mar 24, 2022 at 04:37:19AM +0300, Serge Semin wrote:
> > Currently both DW PCIe Root Port and End-point DT bindings are too generic
> > to be used as a descriptive set the device properties. Yes, it's very handy
> > to have them that way so the DT-schemas could be used to evaluate as many
> > DW PCIe-related DT-nodes as possible. But at the same time they don't
> > provide well defined DW PCIe RP/EP DT interface description thus leaving
> > too much flexibility for the new platforms, no encouraging the developers
> > to preserve a compatible interface.
> >
> > Instead of currently implemented approach we suggest to be more
> > restrictive and yet preserve some level of flexibility in the DW PCIe
> > DT-bindings description. The device tree DT-schema is split up into
> > three parts: a common YAML-schema applicable for both DWC Root Port and
> > End-point controller configs, DWC PCIe Root Port-specific YAML-schema
> > and DWC PCIe End-point-specific YAML-schema, where
> > 1) pci/snps,dw-pcie-common.yaml - The common DT-schema describes the most
> > generic constraints of the "reg", "interrupts", "clocks", "resets" and
> > "phys" properties together with a set of common for both device types
> > PCIe/AXI bus properties like a maximum number of lanes or a maximum link
> > speed, number of inbound and outbound iATU windows. In addition to that a
> > set of schema definitions declared under the "$defs" property with "reg",
> > "interrupt", "clock" and "reset" names common for DWC PCIe Root Port and
> > End-point devices. They can be used by the successive DT-schemas in case
> > they are supposed to be compatible with the generic DWC PCIe controller
> > DT-bindings.
> > 2) pci/snps,dw-pcie.yaml, pci/snps,dw-pcie-ep.yaml - generic DW PCIe Root
> > Port and End-point DT-bindings which aside with the device-specific
> > properties set also contain more restrictive constraints. All new DW PCIe
> > platforms are supposed to be compatible with one of these bindings by
> > using "allOf: " property and additionally defining their own constraints
> > to close up the DT-bindings set.
> >
> > So to speak in case if a DW PCIe-based device for some reason has too many
> > specific properties or it's bindings have already been defined in a
> > non-generic way, it's DT-schema is supposed to include 1) YAML-file and
> > provide its own constraints. Otherwise the ready-to-use bindings from 2)
> > should be utilized. There only two DT-schemas compatible with 2) at the
> > moment are samsung,axynos-pcie.yaml and intel-gw-pcie.yaml. The
> > rest of the DW PCIe-related DT-schemas are supposed to use more generic
> > DW PCIe DT-bindings - pci/snps,dw-pcie-common.yaml.
> >
> > Note the provided here generic properties and their possible values are
> > derived from the DW PCIe RC/EP hardware manuals and from the interface
> > implemented in the driver. The DT-bindings schemas are created to be as
> > full as possible with detailed properties/names description for the
> > easier interface comprehension and new platforms bindings development.
> >
> > Also note since there are no generic DT-nodes can be found in the kernel
> > dts-es which would have a pure "snps,dw-pcie" compatible string, these
> > DT-bindings have been created to not be selected by default for
> > evaluation. They are supposed to be used by the new vendor-specific
> > DT-schemas to at least stop adding new bindings for the same set of DWC
> > PCIe signals or properties.
> >
> > Signed-off-by: Serge Semin <[email protected]>
> >
> > ---
> >
> > Rob, if you comment the "select: false" line out you'll get a lot of "'*'
> > is not of type 'array'" errors on the dt_bindings_check command execution.
> > I have spend tons of time trying to figure out what was wrong, but still
> > failed to find out a solution. For some reason the "$ref"-ed schemas
> > defined under the "$defs" basic property are considered by the parser as
> > defining an array. No matter whether I specified the "type: string"
> > keyword or not, as long as there is "enum" or "oneOf" keyword used in the
> > "*-names" property definition in "$defs" it's considered as describing an
> > array thus causing the errors like:
>
> As-is, there were lots of errors...
>
> >
> > > DTC Documentation/devicetree/bindings/pci/snps,dw-pcie.example.dt.yaml
> > > CHECK Documentation/devicetree/bindings/pci/snps,dw-pcie.example.dt.yaml
> > > /.../snps,dw-pcie.example.dt.yaml: pcie@1f052000: reg-names:0: 'oneOf' conditional failed, one must be fixed:
> > > 'dbi' is not of type 'array'
> > > From schema: /.../snps,dw-pcie.yaml
> > > /.../snps,dw-pcie.example.dt.yaml: pcie@1f052000: reg-names:1: 'oneOf' conditional failed, one must be fixed:
> > > 'config' is not of type 'array'
> > > From schema: /.../snps,dw-pcie.yaml
> > > /.../snps,dw-pcie.example.dt.yaml: pcie@1f052000: interrupt-names:0: 'anyOf' conditional failed, one must be fixed:
> > > /.../snps,dw-pcie.example.dt.yaml: pcie@1f052000: interrupt-names:0: 'oneOf' conditional failed, one must be fixed:
> > > 'msi' is not of type 'array'
> > > /../snps,dw-pcie.example.dt.yaml: pcie@1f052000: interrupt-names:0: 'oneOf' conditional failed, one must be fixed:
> > > 'msi' is not of type 'array'
> > > From schema: /.../snps,dw-pcie.yaml
> >
> > As soon as I manually moved the same "$defs"-schemas into the places they
> > are referenced to, the errors disappeared and the DT-schemas validation
> > worked as expected by the semantics.
> >
> > As I see it there must be some bug in the parser, otherwise I am out of
> > ideas how to implement the suggested in this patch design pattern. Rob,
> > could you take a look at the DT-schemas and help me out with debugging the
> > problem described above?
>

> There is at least an error in your schema, details latter on.

It depends on the schema usage. Please see my next comments for the
detailed explanation what I am trying to achieve here and why it would
be very useful.

>
> It could be an issue in the fixups which in general convert
> schemas for strings to string arrays and scalars/arrays to matrix. You
> have to look at processed-schema.json to debug it.
>
> The other thing is 'oneOf' errors can be confusing in terms of error
> messages because the tooling doesn't really know which entry had the
> least error. Like the type was right, but some secondary constraint
> failed (as what's secondary is not expressed in any way).
>
> I would debug it further, but I don't really like the end result. I
> think it's too hard to follow. For the most part, we avoid $refs at the
> property level across different schema files with the big exception
> being types.yaml. $defs are also used sparingly. The challenge is that
> json-schema by default will be silent on schemas misformed in lots of
> ways. Our meta-schema adds a lot of constraints which limits what the
> schemas can look like and ensures schemas are valid. It also allows me
> to define new constraints rather than repeat the same review comment
> over and over.

I see your point, but in this case I suppose an exception would gain
good benefits too. Here is why.

As I said in the patch log the main idea of this modification was to
create a common schema for the DW PCIe compatible controllers which would
be more restrictive than the existing one but more descriptive and yet
extendable if required. So using $refs was the best method for such
case.

In the framework of this patch aside with a set of the generic DW PCIe
properties, I tried to implement the schemas for the string array with
a pre-defined set values. These schemas can be used to set the
constraints on the device-specific bindings or can be omitted if
either the bindings have already been defined in a different way or
the device is incompatible with the generic bindings for some reason.
Please see the schemas defined in the pci/snps,dw-pcie-common.yaml
file.

As you can see mainly the snps,dw-pcie-common.yaml schema contains the
normal properties descriptions like 'reg', 'clocks', 'interrupts',
'resets', etc. The only peculiarity is the sub-properties defined under
the "$defs" property. See, they aren't used in that file, but are
referenced in the snps,dw-pcie-ep.yaml and snps,dw-pcie.yaml schemas in
order to create the corresponding string arrays constraints. See my
next comments for the further explanation.

>
> > ---
> > .../bindings/pci/fsl,imx6q-pcie.yaml | 5 +-
> > .../bindings/pci/hisilicon,kirin-pcie.yaml | 4 +-
> > .../bindings/pci/sifive,fu740-pcie.yaml | 4 +-
> > .../bindings/pci/snps,dw-pcie-common.yaml | 298 ++++++++++++++++++
> > .../bindings/pci/snps,dw-pcie-ep.yaml | 143 ++++++---
> > .../devicetree/bindings/pci/snps,dw-pcie.yaml | 189 +++++++----
> > .../bindings/pci/toshiba,visconti-pcie.yaml | 2 +-
> > 7 files changed, 524 insertions(+), 121 deletions(-)
> > create mode 100644 Documentation/devicetree/bindings/pci/snps,dw-pcie-common.yaml
>
>
> > diff --git a/Documentation/devicetree/bindings/pci/snps,dw-pcie-common.yaml b/Documentation/devicetree/bindings/pci/snps,dw-pcie-common.yaml
> > new file mode 100644
> > index 000000000000..27fe1f5c450f
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/pci/snps,dw-pcie-common.yaml
> > @@ -0,0 +1,298 @@
> > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> > +%YAML 1.2
> > +---
> > +$id: http://devicetree.org/schemas/pci/snps,dw-pcie-common.yaml#
> > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > +
> > +title: Synopsys DWC PCIe RP/EP controller
> > +
> > +maintainers:
> > + - Jingoo Han <[email protected]>
> > + - Gustavo Pimentel <[email protected]>
> > +
> > +properties:
> > + reg:
> > + description:
> > + DWC PCIe CSR space is normally accessed over the dedicated Data Bus
> > + Interface - DBI. In accordance with the reference manual the register
> > + configuration space belongs to the Configuration-Dependent Module (CDM)
> > + and is split up into several sub-parts Standard PCIe configuration
> > + space, Port Logic Registers (PL), Shadow Config-space Registers,
> > + iATU/eDMA registers. The particular sub-space is selected by the
> > + CDM/ELBI (dbi_cs) and CS2 (dbi_cs2) signals (selector bits). Such
> > + configuration provides a flexible interface for the system engineers to
> > + either map the particular space at a desired MMIO address or just leave
> > + them in a contiguous memory space if pure Native or AXI Bridge DBI access
> > + is selected. Note the PCIe CFG-space, PL and Shadow registers are
> > + specific to each activated function, while the rest of the sub-spaces
> > + are common for all of them (if there are more than one).
> > + minItems: 2
> > + maxItems: 6
> > +
> > + interrupts:
> > + description:
> > + There are two main sub-blocks which are normally capable of
> > + generating interrupts. It's System Information Interface and MSI
> > + interface. While the former one has some common for the Host and
> > + End-point controllers IRQ-signals, the later interface is obviously
> > + Root Complex specific since it's responsible for the incoming MSI
> > + messages signalling. The System Information IRQ signals are mainly
> > + responsible for reporting the generic PCIe hierarchy and Root
> > + Complex events like VPD IO request, general AER, PME, Hot-plug, link
> > + bandwidth change, link equalization request, INTx asserted/deasserted
> > + Message detection, embedded DMA Tx/Rx/Error.
> > + minItems: 1
> > + maxItems: 26
> > +
> > + clocks:
> > + description:
> > + DWC PCIe reference manual explicitly defines a set of the clocks required
> > + to get the controller working correctly. In general all of them can
> > + be divided into two groups':' application and core clocks. Note the
> > + platforms may have some of the clock sources unspecified in case if the
> > + corresponding domains are fed up from a common clock source.
> > + minItems: 1
> > + maxItems: 7
> > +
> > + resets:
> > + description:
> > + DWC PCIe reference manual explicitly defines a set of the reset
> > + signals required to be de-asserted to properly activate the controller
> > + sub-parts. All of these signals can be divided into two sub-groups':'
> > + application and core resets with respect to the main sub-domains they
> > + are supposed to reset. Note the platforms may have some of these signals
> > + unspecified in case if they are automatically handled or aggregated into
> > + a comprehensive control module.
> > + minItems: 1
> > + maxItems: 10
> > +
> > + reset-gpio:
> > + deprecated: true
> > + description:
> > + Reference to the GPIO-controlled PERST# signal. It is used to reset all
> > + the peripheral devices available on the PCIe bus.
> > + maxItems: 1
> > +
> > + reset-gpios:
> > + description:
> > + Reference to the GPIO-controlled PERST# signal. It is used to reset all
> > + the peripheral devices available on the PCIe bus.
> > + maxItems: 1
> > +
> > + phys:
> > + description:
> > + There can be up to number of possible lanes PHYs specified.
> > + Obviously each specified PHY is supposed to be able to work in the
> > + PCIe mode with a speed implied by the DWC PCIe controller it is
> > + attached to.
> > + minItems: 1
> > + maxItems: 16
> > +
> > + phy-names:
> > + minItems: 1
> > + maxItems: 16
> > + items:
> > + pattern: '^pcie([0-9]+|-?phy)?$'
> > +
> > + num-lanes:
> > + maximum: 16
> > +
> > + max-link-speed:
> > + maximum: 4
> > +
> > + num-ob-windows:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + deprecated: true
> > + description:
> > + Number of outbound address translation windows. This parameter can be
> > + auto-detected based on the iATU memory writability. So there is no
> > + point in having a dedicated DT-property for it.
> > + maximum: 256
> > +
> > + num-ib-windows:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + deprecated: true
> > + description:
> > + Number of inbound address translation windows. In the same way as
> > + for the outbound AT windows, this parameter can be auto-detected based
> > + on the iATU memory writability. There is no point having a dedicated
> > + DT-property for it either.
> > + maximum: 256
> > +
> > + num-viewport:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + deprecated: true
> > + description:
> > + Number of outbound view ports configured in hardware. It's the same as
> > + the number of outbound AT windows.
> > + maximum: 256
> > +
> > + snps,enable-cdm-check:
> > + $ref: /schemas/types.yaml#/definitions/flag
> > + description:
> > + Enable automatic checking of CDM (Configuration Dependent Module)
> > + registers for data corruption. CDM registers include standard PCIe
> > + configuration space registers, Port Logic registers, DMA and iATU
> > + registers. This feature has been available since DWC PCIe v4.80a.
> > +
> > +additionalProperties: true
> > +
> > +$defs:
> > + reg-names:
> > + description:
> > + CSR space names common for the DWC PCIe Root Port and End-point
> > + controllers.
> > + oneOf:
> > + - description:
> > + Basic DWC PCIe controller configuration-space accessible over
> > + the DBI interface. This memory space is either activated with
> > + the CDM/ELBI = 0 and CS2 = 0 or is a contiguous memory region
> > + with all spaces. Note iATU/eDMA CSRs are indirectly accessible
> > + via the PL viewports on the DWC PCIe controllers older than
> > + v4.80a.
> > + const: dbi
>

> This passes only for 'reg-names = "dbi";'

No, if the defined here "reg-names" schema is correctly referenced. See my
next comments.

>
> > + - description:
> > + Shadow DWC PCIe config-space registers. This space is selected
> > + by setting CDM/ELBI = 0 and CS2 = 1. This is an intermix of
> > + the PCI-SIG PCIe CFG-space with the shadow registers for some
> > + PCI Header space, PCI Standard and Extended Structures. It's
> > + mainly relevant for the end-point controller configuration,
> > + but still there are some shadow registers available for the
> > + Root Port mode too.
> > + const: dbi2
>

> This passes only for 'reg-names = "dbi2";'

ditto

>
> > + - description:
> > + External Local Bus registers. It's an application-dependent
> > + registers normally defined by the platform engineers, which
> > + are selected by setting CDM/ELBI = 1 and CS2 = 0.
> > + enum: [ elbi, appl, app ]
>

> This passes only for 'reg-names = "elbi", "appl", "app";'
>
> See the problem? Replacing the 'oneOf' above with 'items' is what you
> want. But then the schema will not allow any missing entries either.
> DBI2 is optional for example. There is no way in json-schema to define a
> list of ordered values(string) once and say some entries are optional
> which I think is what you are trying to do here.

Please see the way the "$defs: *-names" properties are referenced in
the framework of this patch. My idea was to define a set of strings in
the "$defs: *-names" properties here. Then re-use that set to define
the device-specific constraints. That's why I've intentionally used
'oneOf'-based composition so the properties could be used to set a
constraints on !each! item of the corresponding strings array, but not
on the whole array as you suggest by replacing 'oneOf' with 'items'.
BTW the ordering can be defined in the corresponding schemas if it's
required.

As an example please see the json schema of the generic DWC
PCIe End-point reg-names property I am trying to achieve here:
https://www.jsonschemavalidator.net/s/eGV3M3gK

As you can see it makes sure the reg-names array contains at least
the "dbi", "dbi2" and "addr_space" strings and may also have the
optional CSR names if it's specific to the corresponding platform.

If you need the ordered set of the strings then the next schema
can be used:
https://www.jsonschemavalidator.net/s/53KKrXV9

Regarding the ordering. As I see it, it isn't something what we need
in the modern bindings in the reference to the "*-names" properties
since the new drivers mainly rely on getting the CSR maps, interrupts,
clocks and resets by their names rather than a position in the 'reg'
phandles array. The CSRs order isn't something what hardware defines
either. Moreover tons of the DT source files don't really have the
reg-names, clock-names, etc properties initialized with some strict
order. So if we want the schema to be applicable for the already
created DT sources the ordering constraints need to be avoided.

Getting back to the patch. As I said in the patch log, alas I failed
to reproduce the described schema in the DT bindings due to the
denoted error. Seeing the JSON-schema examples are working and so does
the DT-schema parser if I manually move the "$defs: *-names" schemas
content in the place of the references, there must be some problem
with the parser. That's why I asked your help with debugging.

>
> > + - description:
> > + iATU/eDMA registers common for all device functions. It's an
> > + unrolled memory space with the internal Address Translation
> > + Unit and Enhanced DMA, which is selected by setting CDM/ELBI = 1
> > + and CS2 = 1. For IP-core releases prior v4.80a, these registers
> > + have been programmed via an indirect addressing scheme using a
> > + set of viewport CSRs mapped into the PL space. Note iATU is
> > + normally mapped to the 0x0 address of this region, while eDMA
> > + is available at 0x80000 base address.
> > + enum: [ atu, atu_dma ]
> > + - description:
> > + Outbound iATU-available memory-region which usage scenario
> > + depends on the DWC PCIe controller being either Root Port or
> > + End-point. If it's Root Port then the register space shall
> > + have the "config" name and it will be used to access the
> > + peripheral PCIe devices configuration space. If it's PCIe
> > + end-point controller, then the region shall be named as
> > + "addr_space" and it will be used to generate various traffic
> > + on the PCIe bus hierarchy. It's usage scenario depends on the
> > + end-point functionality, for instance it can be used to create
> > + MSI(X) messages.
> > + enum: [ config, addr_space ]
>

> So 'addr_space' is now valid for host mode?

Well, in general this reg-space is supposed to be of the same memory
space type - outbound iATU sub-region - for both Host and End-point
controller, but the names are different for them. Anyway basically you
are right, the schemas referencing this property will permit having
both reg names. I can split this property up into two: "$defs:
host-reg-names" and "$defs: ep-reg-names" and use the new schemas in
the Host and EP reg-names property constraints respectively. What do
you think?

>
> (Really, for DW blocks with iATU, this should have been the whole memory
> space and the driver could size the config space itself. (IOW, the
> config region is configuration rather than h/w description.)

I am fully agree with you but the addr/config-space reg-bindings have
already been defined that way. It is implemented in the DWC PCIe driver
and many DT files rely on having that bindings implemented. So alas
nothing we can do about it.

>
> > + - description:
> > + PHY/PCS configuration registers. Some platforms can have the
> > + PCS and PHY CSRs accessible over a dedicated memory mapped
> > + region, but mainly these registers are indirectly accessible
> > + either by means of the embedded PHY viewport schema or by some
> > + platform-specific method.
> > + enum: [ link, phy ]
>

> These should not be in any new users as the phy should be separate.

I agree, but these names have already been referenced in some glue
drivers so does the DW PCIe Host/EP manual have the PHY/PCS CSRs IO
interface described. Thus we have to have them described here for the
sake of compatibility.

>
> > +
> > + interrupt-names:
> > + description:
> > + IRQ signal names common for the DWC PCIe Root Port and End-point
> > + controllers.
> > + oneOf:
> > + - description:
> > + Controller request to read or write virtual product data
> > + from/to the VPD capability registers.
> > + const: vpd
> > + - description:
> > + Link Equalization Request flag is set in the Link Status 2
> > + register (applicable if the corresponding IRQ is enabled in
> > + the Link Control 3 register).
> > + const: l_eq
> > + - description:
> > + Indicates that the eDMA Tx/Rx transfer is complete or that an
> > + error has occurred on the corresponding channel. eDMA can have
> > + eight Tx (Write) and Rx (Read) eDMA channels thus supporting up
> > + to 16 IRQ signals all together. Write eDMA channels shall go
> > + first in the ordered row as per default edma_int[*] bus setup.
> > + pattern: '^dma([0-9]|1[0-5])?$'
> > + - description:
> > + PCIe protocol correctable error or a Data Path protection
> > + correctable error is detected by the automotive/safety
> > + feature.
> > + const: sft_ce
> > + - description:
> > + Indicates that the internal safety mechanism detected and
> > + uncorrectable error.
> > + const: sft_ue
>

> This says there is only a single interrupt entry. Same for the other
> $defs.

No, this property is supposed to be considered as applicable to each
"interrupt-names" item, not for the whole array. Please see my comments
before.

-Sergey

>
> Rob

2022-04-16 09:24:09

by Serge Semin

[permalink] [raw]
Subject: Re: [PATCH 03/16] PCI: dwc: Add more verbose link-up message

On Mon, Mar 28, 2022 at 04:31:53PM -0700, Joe Perches wrote:
> On Thu, 2022-03-24 at 04:37 +0300, Serge Semin wrote:
> > Printing just "link up" isn't that much informative especially when it
> > comes to working with the PCI Express bus. Even if the link is up, due to
> > multiple reasons the bus performance can degrade to slower speeds or to
> > narrower width than both Root Port and its partner is capable of. In that
> > case it would be handy to know the link specifications as early as
> > possible. So let's add a more verbose message to the busy-wait link-state
> > method, which will contain the link speed generation and the PCIe bus
> > width in case if the link up state is discovered. Otherwise an error will
> > be printed to the system log.
> >
> > Signed-off-by: Serge Semin <[email protected]>
> > ---
> > drivers/pci/controller/dwc/pcie-designware.c | 22 +++++++++++++++-----
> > 1 file changed, 17 insertions(+), 5 deletions(-)
> >
> > diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c
> []
> > @@ -528,14 +528,26 @@ int dw_pcie_wait_for_link(struct dw_pcie *pci)
> >
> > /* Check if the link is up or not */
> > for (retries = 0; retries < LINK_WAIT_MAX_RETRIES; retries++) {
> > - if (dw_pcie_link_up(pci)) {
> > - dev_info(pci->dev, "Link up\n");
> > - return 0;
> > - }
> > + if (dw_pcie_link_up(pci))
> > + break;
> > +
> > usleep_range(LINK_WAIT_USLEEP_MIN, LINK_WAIT_USLEEP_MAX);
> > }
> >
> > - dev_info(pci->dev, "Phy link never came up\n");
> > + if (retries < LINK_WAIT_MAX_RETRIES) {
> > + u32 offset, val;
> > +
> > + offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
> > + val = dw_pcie_readw_dbi(pci, offset + PCI_EXP_LNKSTA);
> > +
> > + dev_info(pci->dev, "PCIe Gen.%u x%u link up\n",
> > + FIELD_GET(PCI_EXP_LNKSTA_CLS, val),
> > + FIELD_GET(PCI_EXP_LNKSTA_NLW, val));
> > +
> > + return 0;
> > + }
> > +
> > + dev_err(pci->dev, "Phy link never came up\n");
> >
> > return -ETIMEDOUT;
> > }
>

> IMO: it's generally bette to test the error condition and unindent
> the typical return.

Absolutely right. Thanks for noticing that. No idea why I haven't done
the way you said 'cause it seems neater, more maintainable than what I
suggested here.

-Sergey

>
> if (retries >= LINK_WAIT_MAX_RETRIES) {
> dev_err(pci->dev, "Phy link never came up\n");
> return -ETIMEDOUT;
> }
>
> offset = ...
> val = ...
> dev_info(...)
>
> return 0;
> }
>

2022-04-18 03:54:40

by Serge Semin

[permalink] [raw]
Subject: Re: [PATCH 04/16] PCI: dwc: Convert to using native IP-core versions representation

On Tue, Mar 29, 2022 at 10:00:38AM -0500, Rob Herring wrote:
> On Thu, Mar 24, 2022 at 04:37:22AM +0300, Serge Semin wrote:
> > Since DWC PCIe v4.70a the controller version can be read from the
> > PORT_LOGIC.PCIE_VERSION_OFF register. Version is represented in the FourCC
> > format [1]. It's standard versioning approach for the Synopsys DWC
> > IP-cores. Moreover some of the DWC kernel drivers already make use of it
> > to fixup version-dependent functionality (See DWC USB3, Stmicro STMMAC or
> > recent DW SPI driver). In order to preserve the standard version
> > representation and prevent the data conversion back and forth, we suggest
> > to preserve the native version representation in the DWC PCIe driver too
> > in the same way as it has already been done in the rest of the DWC
> > drivers. IP-core version reading from the CSR will be introduced in the
> > next commit together with a simple macro-based API to use it.
> >
> > [1] https://en.wikipedia.org/wiki/FourCC
> >
> > Signed-off-by: Serge Semin <[email protected]>
> > ---
> > drivers/pci/controller/dwc/pci-keystone.c | 12 ++++++------
> > drivers/pci/controller/dwc/pcie-designware.c | 8 ++++----
> > drivers/pci/controller/dwc/pcie-designware.h | 10 +++++++++-
> > drivers/pci/controller/dwc/pcie-intel-gw.c | 4 ++--
> > drivers/pci/controller/dwc/pcie-tegra194.c | 2 +-
> > 5 files changed, 22 insertions(+), 14 deletions(-)
> >
> > diff --git a/drivers/pci/controller/dwc/pci-keystone.c b/drivers/pci/controller/dwc/pci-keystone.c
> > index 1c2ee4e13f1c..ec65355dd29b 100644
> > --- a/drivers/pci/controller/dwc/pci-keystone.c
> > +++ b/drivers/pci/controller/dwc/pci-keystone.c
> > @@ -109,7 +109,7 @@ struct ks_pcie_of_data {
> > enum dw_pcie_device_mode mode;
> > const struct dw_pcie_host_ops *host_ops;
> > const struct dw_pcie_ep_ops *ep_ops;
> > - unsigned int version;
> > + u32 version;
> > };
> >
> > struct keystone_pcie {
> > @@ -1069,19 +1069,19 @@ static int ks_pcie_am654_set_mode(struct device *dev,
> >
> > static const struct ks_pcie_of_data ks_pcie_rc_of_data = {
> > .host_ops = &ks_pcie_host_ops,
> > - .version = 0x365A,
> > + .version = DW_PCIE_VER_365A,
> > };
> >
> > static const struct ks_pcie_of_data ks_pcie_am654_rc_of_data = {
> > .host_ops = &ks_pcie_am654_host_ops,
> > .mode = DW_PCIE_RC_TYPE,

> > - .version = 0x490A,
> > + .version = DW_PCIE_VER_490A,
>
> This version should be readable, so get rid of hardcoding it here.

Sorry but what do you mean by 'readable'? Do you mean that since the
version CSR has been available since 4.70a, it is supposed to be read
from the h/w registers for the newer IPs and there is no point in
having that field initialized here? If so do you suggest to drop the
initialization from the both pci-keystone.c:ks_pcie_am654_rc_of_data
and pci-keystone.c:ks_pcie_am654_rc_of_data structure instances? If
so then the same can be done in the pcie-tegra194.c and
pcie-intel-gw.c drivers. Shall I drop it from there too?

Anyway if that's what you mean it needs to be done in a dedicated
patch, not here.

>
> > };
> >
> > static const struct ks_pcie_of_data ks_pcie_am654_ep_of_data = {
> > .ep_ops = &ks_pcie_am654_ep_ops,
> > .mode = DW_PCIE_EP_TYPE,
> > - .version = 0x490A,
> > + .version = DW_PCIE_VER_490A,
> > };
> >
> > static const struct of_device_id ks_pcie_of_match[] = {
> > @@ -1114,12 +1114,12 @@ static int __init ks_pcie_probe(struct platform_device *pdev)
> > struct device_link **link;
> > struct gpio_desc *gpiod;
> > struct resource *res;
> > - unsigned int version;
> > void __iomem *base;
> > u32 num_viewport;
> > struct phy **phy;
> > u32 num_lanes;
> > char name[10];
> > + u32 version;
> > int ret;
> > int irq;
> > int i;
> > @@ -1233,7 +1233,7 @@ static int __init ks_pcie_probe(struct platform_device *pdev)
> > goto err_get_sync;
> > }
> >
> > - if (pci->version >= 0x480A)
> > + if (pci->version >= DW_PCIE_VER_480A)
> > ret = ks_pcie_am654_set_mode(dev, mode);
> > else
> > ret = ks_pcie_set_mode(dev);
> > diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c
> > index f1693e25afcb..c21373c6cb51 100644
> > --- a/drivers/pci/controller/dwc/pcie-designware.c
> > +++ b/drivers/pci/controller/dwc/pcie-designware.c
> > @@ -289,7 +289,7 @@ static void dw_pcie_prog_outbound_atu_unroll(struct dw_pcie *pci, u8 func_no,
> > val = type | PCIE_ATU_FUNC_NUM(func_no);
> > if (upper_32_bits(limit_addr) > upper_32_bits(cpu_addr))
> > val |= PCIE_ATU_INCREASE_REGION_SIZE;
> > - if (pci->version == 0x490A)
> > + if (pci->version == DW_PCIE_VER_490A)
> > val = dw_pcie_enable_ecrc(val);
> > dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL1, val);
> > dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2,
> > @@ -336,7 +336,7 @@ static void __dw_pcie_prog_outbound_atu(struct dw_pcie *pci, u8 func_no,
> > upper_32_bits(cpu_addr));
> > dw_pcie_writel_dbi(pci, PCIE_ATU_LIMIT,
> > lower_32_bits(limit_addr));
> > - if (pci->version >= 0x460A)
> > + if (pci->version >= DW_PCIE_VER_460A)
> > dw_pcie_writel_dbi(pci, PCIE_ATU_UPPER_LIMIT,
> > upper_32_bits(limit_addr));
> > dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_TARGET,
> > @@ -345,9 +345,9 @@ static void __dw_pcie_prog_outbound_atu(struct dw_pcie *pci, u8 func_no,
> > upper_32_bits(pci_addr));
> > val = type | PCIE_ATU_FUNC_NUM(func_no);
> > if (upper_32_bits(limit_addr) > upper_32_bits(cpu_addr) &&
> > - pci->version >= 0x460A)
> > + pci->version >= DW_PCIE_VER_460A)
> > val |= PCIE_ATU_INCREASE_REGION_SIZE;
> > - if (pci->version == 0x490A)
> > + if (pci->version == DW_PCIE_VER_490A)
> > val = dw_pcie_enable_ecrc(val);
> > dw_pcie_writel_dbi(pci, PCIE_ATU_CR1, val);
> > dw_pcie_writel_dbi(pci, PCIE_ATU_CR2, PCIE_ATU_ENABLE);
> > diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h
> > index 7d6e9b7576be..5be43c662176 100644
> > --- a/drivers/pci/controller/dwc/pcie-designware.h
> > +++ b/drivers/pci/controller/dwc/pcie-designware.h
> > @@ -20,6 +20,14 @@
> > #include <linux/pci-epc.h>
> > #include <linux/pci-epf.h>
> >

> > +/* DWC PCIe IP-core versions (native support since v4.70a) */
> > +#define DW_PCIE_VER_365A 0x3336352a
> > +#define DW_PCIE_VER_460A 0x3436302a
> > +#define DW_PCIE_VER_470A 0x3437302a
> > +#define DW_PCIE_VER_480A 0x3438302a
> > +#define DW_PCIE_VER_490A 0x3439302a
> > +#define DW_PCIE_VER_520A 0x3532302a
>
> Create a macro to form these so we can do:
>
> DW_PCIE_VER('5', '2', '0', 'A')

I am ok with that as long as you suggest to use it in the definition
of the macros above. Like this:

+#define DW_PCIE_VER_365A __DW_PCIE_VER('3', '5', '6', '*')

otherwise if we used the macro suggested by you right in the code or
in the introduced further dw_pcie_ver_is() macros, the version
checking conditional statements turn into the much longer constructions.
I'd rather avoid that. So could you clarify what is the usage scenario
of the macro DW_PCIE_VER() suggested by you?

Please also note that mainly the DW PCIe (USB, SPI, *MAC, etc) FourCC
versions don't contain the letter symbols, but are enclosed with the
'*' symbol (see 0x2a ASCII code). So the 3.65a version isn't encoded
as 0x333635_61_, but as 0x333635_2a_. I don't know a real reasoning of
that, but can guess that the same version value might be used for the
series of releases like 3.65a, 4.65b, 4.65c, etc as some kind of
wildcard. Alas I don't have any non-'a' implementation of the Synopsys
IP-core to check that out.

-Sergey

>
> > +
> > /* Parameters for the waiting for link up routine */
> > #define LINK_WAIT_MAX_RETRIES 10
> > #define LINK_WAIT_USLEEP_MIN 90000
> > @@ -269,7 +277,7 @@ struct dw_pcie {
> > struct pcie_port pp;
> > struct dw_pcie_ep ep;
> > const struct dw_pcie_ops *ops;
> > - unsigned int version;
> > + u32 version;
> > int num_lanes;
> > int link_gen;
> > u8 n_fts[2];
> > diff --git a/drivers/pci/controller/dwc/pcie-intel-gw.c b/drivers/pci/controller/dwc/pcie-intel-gw.c
> > index 5ba144924ff8..786af2ba379f 100644
> > --- a/drivers/pci/controller/dwc/pcie-intel-gw.c
> > +++ b/drivers/pci/controller/dwc/pcie-intel-gw.c
> > @@ -59,7 +59,7 @@
> > #define RESET_INTERVAL_MS 100
> >
> > struct intel_pcie_soc {
> > - unsigned int pcie_ver;
> > + u32 pcie_ver;
> > };
> >
> > struct intel_pcie {
> > @@ -395,7 +395,7 @@ static const struct dw_pcie_host_ops intel_pcie_dw_ops = {
> > };
> >
> > static const struct intel_pcie_soc pcie_data = {
> > - .pcie_ver = 0x520A,
> > + .pcie_ver = DW_PCIE_VER_520A,
> > };
> >
> > static int intel_pcie_probe(struct platform_device *pdev)
> > diff --git a/drivers/pci/controller/dwc/pcie-tegra194.c b/drivers/pci/controller/dwc/pcie-tegra194.c
> > index b1b5f836a806..6f1330ed63e5 100644
> > --- a/drivers/pci/controller/dwc/pcie-tegra194.c
> > +++ b/drivers/pci/controller/dwc/pcie-tegra194.c
> > @@ -1981,7 +1981,7 @@ static int tegra194_pcie_probe(struct platform_device *pdev)
> > pci->ops = &tegra_dw_pcie_ops;
> > pci->n_fts[0] = N_FTS_VAL;
> > pci->n_fts[1] = FTS_VAL;
> > - pci->version = 0x490A;
> > + pci->version = DW_PCIE_VER_490A;
> >
> > pp = &pci->pp;
> > pp->num_vectors = MAX_MSI_IRQS;
> > --
> > 2.35.1
> >

2022-04-18 04:55:56

by Serge Semin

[permalink] [raw]
Subject: Re: [PATCH 09/16] PCI: dwc: Simplify in/outbound iATU setup methods

On Tue, Mar 29, 2022 at 10:28:56AM -0500, Rob Herring wrote:
> On Thu, Mar 24, 2022 at 04:37:27AM +0300, Serge Semin wrote:
> > From maintainability and scalability points of view it has been wrong to
> > use different iATU inbound and outbound regions accessors for the viewport
> > and unrolled versions of the iATU CSRs mapping. Seeing the particular iATU
> > region-wise registers layout is almost fully compatible for different
> > IP-core versions, there were no much points in splitting the code up that
> > way since it was possible to implement a common windows setup methods for
> > both viewport and unrolled iATU CSRs spaces. While what we can observe in
> > the current driver implementation of these methods, is a lot of code
> > duplication, which consequently worsen the code readability,
> > maintainability and scalability. Note the current implementation is a bit
> > more performant than the one suggested in this commit since it implies
> > having less MMIO accesses. But the gain just doesn't worth having the
> > denoted difficulties especially seeing the iATU setup methods are mainly
> > called on the DW PCIe controller and peripheral devices initialization
> > stage.
> >
> > Here we suggest to move the iATU viewport and unrolled CSR access
> > specifics in the dw_pcie_readl_atu() and dw_pcie_writel_atu() method, and
> > convert the dw_pcie_prog_outbound_atu() and
> > dw_pcie_prog_{ep_}inbound_atu() functions to being generic instead of
> > having a different methods for each viewport and unrolled types of iATU
> > CSRs mapping. Nothing complex really. First of all the dw_pcie_readl_atu()
> > and dw_pcie_writel_atu() are converted to accept relative iATU CSRs
> > address together with the iATU region direction (inbound or outbound) and
> > region index. If DW PCIe controller doesn't have the unrolled iATU CSRs
> > space, then the accessors will need to activate a iATU viewport based on
> > the specified direction and index, otherwise a base address for the
> > corresponding region CSRs will be calculated by means of the
> > PCIE_ATU_UNROLL_BASE() macro. The CSRs macro have been modified in
> > accordance with that logic in the pcie-designware.h header file.
> >
> > The rest of the changes in this commit just concern converting the iATU
> > in-/out-bound setup methods and iATU regions detection procedure to be
> > compatible with the new accessors semantics. As a result we've dropped the
> > no more required dw_pcie_prog_outbound_atu_unroll(),
> > dw_pcie_prog_inbound_atu_unroll() and dw_pcie_iatu_detect_regions_unroll()
> > methods.
> >
> > Note aside with the denoted code improvements, there is an additional
> > positive side effect of this change. If at some point an atomic iATU
> > configs setup procedure is required, it will be possible to be done with
> > no much effort just by adding the synchronization into the
> > dw_pcie_readl_atu() and dw_pcie_writel_atu() accessors.
> >
> > Signed-off-by: Serge Semin <[email protected]>
> > ---
> > drivers/pci/controller/dwc/pcie-designware.c | 301 ++++++-------------
> > drivers/pci/controller/dwc/pcie-designware.h | 50 ++-
> > 2 files changed, 112 insertions(+), 239 deletions(-)
>

> Nice diffstat. I didn't really like how this was implemented either.

Yeah, the change has turned to be a bit bulky. I wouldn't really
bother with it if I didn't need to add the "dma-ranges" support.

>
> I'm guessing you tested the unrolled case only? IIRC, QEMU has the older
> interface. I can also throw this at kernel-ci if needed. There's a few
> platforms with DWC that get tested.

In fact I tested the series on DW PCIe Host v4.60a with the legacy
iATU/eDMA registers mapping (viewport-based). So testing on the modern
controllers with the unrolled iATU/eDMA region would be very welcome.

>
> >
> > diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c
> > index b983128584ff..f1aa6e2e85fe 100644
> > --- a/drivers/pci/controller/dwc/pcie-designware.c
> > +++ b/drivers/pci/controller/dwc/pcie-designware.c
> > @@ -205,48 +205,67 @@ void dw_pcie_write_dbi2(struct dw_pcie *pci, u32 reg, size_t size, u32 val)
> > dev_err(pci->dev, "write DBI address failed\n");
> > }
> >
> > -static u32 dw_pcie_readl_atu(struct dw_pcie *pci, u32 reg)
> > +static u32 dw_pcie_readl_atu(struct dw_pcie *pci, u32 dir, u32 region, u32 reg)
> > {
> > + void __iomem *base;
> > int ret;
> > u32 val;
> >
> > + if (pci->iatu_unroll_enabled) {
> > + base = pci->atu_base;
> > + reg = reg + PCIE_ATU_UNROLL_BASE(dir, region);
> > + } else {
> > + base = pci->dbi_base;
> > + reg = reg + PCIE_ATU_VIEWPORT_BASE;
> > +
> > + dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, dir | region);
> > + }
> > +
> > if (pci->ops && pci->ops->read_dbi)
> > - return pci->ops->read_dbi(pci, pci->atu_base, reg, 4);
> > + return pci->ops->read_dbi(pci, base, reg, 4);
> >
> > - ret = dw_pcie_read(pci->atu_base + reg, 4, &val);
> > + ret = dw_pcie_read(base + reg, 4, &val);
> > if (ret)
> > dev_err(pci->dev, "Read ATU address failed\n");
> >
> > return val;
> > }
> >
> > -static void dw_pcie_writel_atu(struct dw_pcie *pci, u32 reg, u32 val)
> > +static void dw_pcie_writel_atu(struct dw_pcie *pci, u32 dir, u32 region,
> > + u32 reg, u32 val)
> > {
> > + void __iomem *base;
> > int ret;
> >
> > + if (pci->iatu_unroll_enabled) {
> > + base = pci->atu_base;
> > + reg = reg + PCIE_ATU_UNROLL_BASE(dir, region);
> > + } else {
> > + base = pci->dbi_base;
> > + reg = reg + PCIE_ATU_VIEWPORT_BASE;
> > +
> > + dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, dir | region);
> > + }
>

> You have this same sequence twice. Make it a helper function.

Ok. I'll move this code into a helper function - dw_pcie_select_atu().

>
> > +
> > if (pci->ops && pci->ops->write_dbi) {
> > - pci->ops->write_dbi(pci, pci->atu_base, reg, 4, val);
> > + pci->ops->write_dbi(pci, base, reg, 4, val);
> > return;
> > }
> >
> > - ret = dw_pcie_write(pci->atu_base + reg, 4, val);
> > + ret = dw_pcie_write(base + reg, 4, val);
> > if (ret)
> > dev_err(pci->dev, "Write ATU address failed\n");
> > }
> >
> > -static u32 dw_pcie_readl_ob_unroll(struct dw_pcie *pci, u32 index, u32 reg)
> > +static inline u32 dw_pcie_readl_atu_ob(struct dw_pcie *pci, u32 region, u32 reg)
> > {
> > - u32 offset = PCIE_GET_ATU_OUTB_UNR_REG_OFFSET(index);
> > -
> > - return dw_pcie_readl_atu(pci, offset + reg);
> > + return dw_pcie_readl_atu(pci, PCIE_ATU_REGION_DIR_OB, region, reg);
> > }
> >
> > -static void dw_pcie_writel_ob_unroll(struct dw_pcie *pci, u32 index, u32 reg,
> > - u32 val)
> > +static inline void dw_pcie_writel_atu_ob(struct dw_pcie *pci, u32 region, u32 reg,
> > + u32 val)
> > {
> > - u32 offset = PCIE_GET_ATU_OUTB_UNR_REG_OFFSET(index);
> > -
> > - dw_pcie_writel_atu(pci, offset + reg, val);
> > + dw_pcie_writel_atu(pci, PCIE_ATU_REGION_DIR_OB, region, reg, val);
> > }
> >
> > static inline u32 dw_pcie_enable_ecrc(u32 val)
> > @@ -290,50 +309,6 @@ static inline u32 dw_pcie_enable_ecrc(u32 val)
> > return val | PCIE_ATU_TD;
> > }
> >
> > -static void dw_pcie_prog_outbound_atu_unroll(struct dw_pcie *pci, u8 func_no,
> > - int index, int type,
> > - u64 cpu_addr, u64 pci_addr,
> > - u64 size)
> > -{
> > - u32 retries, val;
> > - u64 limit_addr = cpu_addr + size - 1;
> > -
> > - dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_LOWER_BASE,
> > - lower_32_bits(cpu_addr));
> > - dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_UPPER_BASE,
> > - upper_32_bits(cpu_addr));
> > - dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_LOWER_LIMIT,
> > - lower_32_bits(limit_addr));
> > - dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_UPPER_LIMIT,
> > - upper_32_bits(limit_addr));
> > - dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_LOWER_TARGET,
> > - lower_32_bits(pci_addr));
> > - dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_UPPER_TARGET,
> > - upper_32_bits(pci_addr));
> > - val = type | PCIE_ATU_FUNC_NUM(func_no);
> > - if (upper_32_bits(limit_addr) > upper_32_bits(cpu_addr))
> > - val |= PCIE_ATU_INCREASE_REGION_SIZE;
> > - if (dw_pcie_ver_is(pci, 490A))
> > - val = dw_pcie_enable_ecrc(val);
> > - dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL1, val);
> > - dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2,
> > - PCIE_ATU_ENABLE);
> > -
> > - /*
> > - * Make sure ATU enable takes effect before any subsequent config
> > - * and I/O accesses.
> > - */
> > - for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) {
> > - val = dw_pcie_readl_ob_unroll(pci, index,
> > - PCIE_ATU_UNR_REGION_CTRL2);
> > - if (val & PCIE_ATU_ENABLE)
> > - return;
> > -
> > - mdelay(LINK_WAIT_IATU);
> > - }
> > - dev_err(pci->dev, "Outbound iATU is not being enabled\n");
> > -}
> > -
> > static void __dw_pcie_prog_outbound_atu(struct dw_pcie *pci, u8 func_no,
> > int index, int type, u64 cpu_addr,
> > u64 pci_addr, u64 size)
> > @@ -344,49 +319,46 @@ static void __dw_pcie_prog_outbound_atu(struct dw_pcie *pci, u8 func_no,
> > if (pci->ops && pci->ops->cpu_addr_fixup)
> > cpu_addr = pci->ops->cpu_addr_fixup(pci, cpu_addr);
> >
> > - if (pci->iatu_unroll_enabled) {
> > - dw_pcie_prog_outbound_atu_unroll(pci, func_no, index, type,
> > - cpu_addr, pci_addr, size);
> > - return;
> > - }
> > -
> > limit_addr = cpu_addr + size - 1;
> >
> > - dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT,
> > - PCIE_ATU_REGION_OUTBOUND | index);
> > - dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_BASE,
> > - lower_32_bits(cpu_addr));
> > - dw_pcie_writel_dbi(pci, PCIE_ATU_UPPER_BASE,
> > - upper_32_bits(cpu_addr));
> > - dw_pcie_writel_dbi(pci, PCIE_ATU_LIMIT,
> > - lower_32_bits(limit_addr));
> > + dw_pcie_writel_atu_ob(pci, index, PCIE_ATU_LOWER_BASE,
> > + lower_32_bits(cpu_addr));
> > + dw_pcie_writel_atu_ob(pci, index, PCIE_ATU_UPPER_BASE,
> > + upper_32_bits(cpu_addr));
> > +
> > + dw_pcie_writel_atu_ob(pci, index, PCIE_ATU_LIMIT,
> > + lower_32_bits(limit_addr));
> > if (dw_pcie_ver_is_ge(pci, 460A))
> > - dw_pcie_writel_dbi(pci, PCIE_ATU_UPPER_LIMIT,
> > - upper_32_bits(limit_addr));
> > - dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_TARGET,
> > - lower_32_bits(pci_addr));
> > - dw_pcie_writel_dbi(pci, PCIE_ATU_UPPER_TARGET,
> > - upper_32_bits(pci_addr));
> > + dw_pcie_writel_atu_ob(pci, index, PCIE_ATU_UPPER_LIMIT,
> > + upper_32_bits(limit_addr));
> > +
> > + dw_pcie_writel_atu_ob(pci, index, PCIE_ATU_LOWER_TARGET,
> > + lower_32_bits(pci_addr));
> > + dw_pcie_writel_atu_ob(pci, index, PCIE_ATU_UPPER_TARGET,
> > + upper_32_bits(pci_addr));
> > +
> > val = type | PCIE_ATU_FUNC_NUM(func_no);
> > if (upper_32_bits(limit_addr) > upper_32_bits(cpu_addr) &&
> > dw_pcie_ver_is_ge(pci, 460A))
> > val |= PCIE_ATU_INCREASE_REGION_SIZE;
> > if (dw_pcie_ver_is(pci, 490A))
> > val = dw_pcie_enable_ecrc(val);
> > - dw_pcie_writel_dbi(pci, PCIE_ATU_CR1, val);
> > - dw_pcie_writel_dbi(pci, PCIE_ATU_CR2, PCIE_ATU_ENABLE);
> > + dw_pcie_writel_atu_ob(pci, index, PCIE_ATU_REGION_CTRL1, val);
> > +
> > + dw_pcie_writel_atu_ob(pci, index, PCIE_ATU_REGION_CTRL2, PCIE_ATU_ENABLE);
> >
> > /*
> > * Make sure ATU enable takes effect before any subsequent config
> > * and I/O accesses.
> > */
> > for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) {
> > - val = dw_pcie_readl_dbi(pci, PCIE_ATU_CR2);
> > + val = dw_pcie_readl_atu_ob(pci, index, PCIE_ATU_REGION_CTRL2);
> > if (val & PCIE_ATU_ENABLE)
> > return;
> >
> > mdelay(LINK_WAIT_IATU);
> > }
> > +
> > dev_err(pci->dev, "Outbound iATU is not being enabled\n");
> > }
> >
> > @@ -405,54 +377,15 @@ void dw_pcie_prog_ep_outbound_atu(struct dw_pcie *pci, u8 func_no, int index,
> > cpu_addr, pci_addr, size);
> > }
> >
> > -static u32 dw_pcie_readl_ib_unroll(struct dw_pcie *pci, u32 index, u32 reg)
> > -{
> > - u32 offset = PCIE_GET_ATU_INB_UNR_REG_OFFSET(index);
> > -
> > - return dw_pcie_readl_atu(pci, offset + reg);
> > -}
> > -
> > -static void dw_pcie_writel_ib_unroll(struct dw_pcie *pci, u32 index, u32 reg,
> > - u32 val)
> > +static inline u32 dw_pcie_readl_atu_ib(struct dw_pcie *pci, u32 region, u32 reg)
> > {
> > - u32 offset = PCIE_GET_ATU_INB_UNR_REG_OFFSET(index);
> > -
> > - dw_pcie_writel_atu(pci, offset + reg, val);
> > + return dw_pcie_readl_atu(pci, PCIE_ATU_REGION_DIR_IB, region, reg);
> > }
> >
> > -static int dw_pcie_prog_inbound_atu_unroll(struct dw_pcie *pci, u8 func_no,
> > - int index, int type,
> > - u64 cpu_addr, u8 bar)
> > +static inline void dw_pcie_writel_atu_ib(struct dw_pcie *pci, u32 region, u32 reg,
> > + u32 val)
> > {
> > - u32 retries, val;
> > -
> > - dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_LOWER_TARGET,
> > - lower_32_bits(cpu_addr));
> > - dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_UPPER_TARGET,
> > - upper_32_bits(cpu_addr));
> > -
> > - dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL1, type |
> > - PCIE_ATU_FUNC_NUM(func_no));
> > - dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2,
> > - PCIE_ATU_FUNC_NUM_MATCH_EN |
> > - PCIE_ATU_ENABLE |
> > - PCIE_ATU_BAR_MODE_ENABLE | (bar << 8));
> > -
> > - /*
> > - * Make sure ATU enable takes effect before any subsequent config
> > - * and I/O accesses.
> > - */
> > - for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) {
> > - val = dw_pcie_readl_ib_unroll(pci, index,
> > - PCIE_ATU_UNR_REGION_CTRL2);
> > - if (val & PCIE_ATU_ENABLE)
> > - return 0;
> > -
> > - mdelay(LINK_WAIT_IATU);
> > - }
> > - dev_err(pci->dev, "Inbound iATU is not being enabled\n");
> > -
> > - return -EBUSY;
> > + dw_pcie_writel_atu(pci, PCIE_ATU_REGION_DIR_IB, region, reg, val);
> > }
> >
> > int dw_pcie_prog_inbound_atu(struct dw_pcie *pci, u8 func_no, int index,
> > @@ -460,65 +393,51 @@ int dw_pcie_prog_inbound_atu(struct dw_pcie *pci, u8 func_no, int index,
> > {
> > u32 retries, val;
> >
> > - if (pci->iatu_unroll_enabled)
> > - return dw_pcie_prog_inbound_atu_unroll(pci, func_no, index, type,
> > - cpu_addr, bar);
> > -
> > - dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, PCIE_ATU_REGION_INBOUND |
> > - index);
> > - dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_TARGET, lower_32_bits(cpu_addr));
> > - dw_pcie_writel_dbi(pci, PCIE_ATU_UPPER_TARGET, upper_32_bits(cpu_addr));
> > + dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_LOWER_TARGET,
> > + lower_32_bits(cpu_addr));
> > + dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_UPPER_TARGET,
> > + upper_32_bits(cpu_addr));
> >
> > - dw_pcie_writel_dbi(pci, PCIE_ATU_CR1, type |
> > - PCIE_ATU_FUNC_NUM(func_no));
> > - dw_pcie_writel_dbi(pci, PCIE_ATU_CR2, PCIE_ATU_ENABLE |
> > - PCIE_ATU_FUNC_NUM_MATCH_EN |
> > - PCIE_ATU_BAR_MODE_ENABLE | (bar << 8));
> > + dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_REGION_CTRL1, type |
> > + PCIE_ATU_FUNC_NUM(func_no));
> > + dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_REGION_CTRL2,
> > + PCIE_ATU_ENABLE | PCIE_ATU_FUNC_NUM_MATCH_EN |
> > + PCIE_ATU_BAR_MODE_ENABLE | (bar << 8));
> >
> > /*
> > * Make sure ATU enable takes effect before any subsequent config
> > * and I/O accesses.
> > */
> > for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) {
> > - val = dw_pcie_readl_dbi(pci, PCIE_ATU_CR2);
> > + val = dw_pcie_readl_atu_ib(pci, index, PCIE_ATU_REGION_CTRL2);
> > if (val & PCIE_ATU_ENABLE)
> > return 0;
> >
> > mdelay(LINK_WAIT_IATU);
> > }
> > +
> > dev_err(pci->dev, "Inbound iATU is not being enabled\n");
> >
> > - return -EBUSY;
> > + return -ETIMEDOUT;
> > }
> >
> > void dw_pcie_disable_atu(struct dw_pcie *pci, int index,
> > enum dw_pcie_region_type type)
> > {
> > - int region;
> > + u32 dir;
> >
> > switch (type) {
> > case DW_PCIE_REGION_INBOUND:
> > - region = PCIE_ATU_REGION_INBOUND;
> > + dir = PCIE_ATU_REGION_DIR_IB;
>

> Is this the same double definition with the enum?

Absolutely right. I'll drop it in the next patch of this series. I'll
also move that patch to being applied before this one so to prevent
this question from araising and to simplify this patch a bit.

>
> > break;
> > case DW_PCIE_REGION_OUTBOUND:
> > - region = PCIE_ATU_REGION_OUTBOUND;
> > + dir = PCIE_ATU_REGION_DIR_OB;
> > break;
> > default:
> > return;
> > }
> >
> > - if (pci->iatu_unroll_enabled) {
> > - if (region == PCIE_ATU_REGION_INBOUND) {
> > - dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2,
> > - ~(u32)PCIE_ATU_ENABLE);
> > - } else {
> > - dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2,
> > - ~(u32)PCIE_ATU_ENABLE);
> > - }
> > - } else {
> > - dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, region | index);
> > - dw_pcie_writel_dbi(pci, PCIE_ATU_CR2, ~(u32)PCIE_ATU_ENABLE);
> > - }
> > + dw_pcie_writel_atu(pci, dir, index, PCIE_ATU_REGION_CTRL2, 0);
> > }
> >
> > int dw_pcie_wait_for_link(struct dw_pcie *pci)
> > @@ -622,63 +541,29 @@ static bool dw_pcie_iatu_unroll_enabled(struct dw_pcie *pci)
> > return false;
> > }
> >
> > -static void dw_pcie_iatu_detect_regions_unroll(struct dw_pcie *pci)
> > -{
> > - int max_region, i, ob = 0, ib = 0;
> > - u32 val;
> > -
> > - max_region = min((int)pci->atu_size / 512, 256);
> > -
> > - for (i = 0; i < max_region; i++) {
> > - dw_pcie_writel_ob_unroll(pci, i, PCIE_ATU_UNR_LOWER_TARGET,
> > - 0x11110000);
> > -
> > - val = dw_pcie_readl_ob_unroll(pci, i, PCIE_ATU_UNR_LOWER_TARGET);
> > - if (val == 0x11110000)
> > - ob++;
> > - else
> > - break;
> > - }
> > -
> > - for (i = 0; i < max_region; i++) {
> > - dw_pcie_writel_ib_unroll(pci, i, PCIE_ATU_UNR_LOWER_TARGET,
> > - 0x11110000);
> > -
> > - val = dw_pcie_readl_ib_unroll(pci, i, PCIE_ATU_UNR_LOWER_TARGET);
> > - if (val == 0x11110000)
> > - ib++;
> > - else
> > - break;
> > - }
> > - pci->num_ib_windows = ib;
> > - pci->num_ob_windows = ob;
> > -}
> > -
> > static void dw_pcie_iatu_detect_regions(struct dw_pcie *pci)
> > {
> > - int max_region, i, ob = 0, ib = 0;
> > + int max_region, ob, ib;
> > u32 val;
> >
> > - dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, 0xFF);
> > - max_region = dw_pcie_readl_dbi(pci, PCIE_ATU_VIEWPORT) + 1;
> > + if (pci->iatu_unroll_enabled) {
> > + max_region = min((int)pci->atu_size / 512, 256);
> > + } else {
> > + dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, 0xFF);
> > + max_region = dw_pcie_readl_dbi(pci, PCIE_ATU_VIEWPORT) + 1;
> > + }
> >
> > - for (i = 0; i < max_region; i++) {
> > - dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, PCIE_ATU_REGION_OUTBOUND | i);
> > - dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_TARGET, 0x11110000);
> > - val = dw_pcie_readl_dbi(pci, PCIE_ATU_LOWER_TARGET);
> > - if (val == 0x11110000)
> > - ob++;
> > - else
> > + for (ob = 0; ob < max_region; ob++) {
> > + dw_pcie_writel_atu_ob(pci, ob, PCIE_ATU_LOWER_TARGET, 0x11110000);
> > + val = dw_pcie_readl_atu_ob(pci, ob, PCIE_ATU_LOWER_TARGET);
> > + if (val != 0x11110000)
> > break;
> > }
> >
> > - for (i = 0; i < max_region; i++) {
> > - dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, PCIE_ATU_REGION_INBOUND | i);
> > - dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_TARGET, 0x11110000);
> > - val = dw_pcie_readl_dbi(pci, PCIE_ATU_LOWER_TARGET);
> > - if (val == 0x11110000)
> > - ib++;
> > - else
> > + for (ib = 0; ib < max_region; ib++) {
> > + dw_pcie_writel_atu_ib(pci, ib, PCIE_ATU_LOWER_TARGET, 0x11110000);
> > + val = dw_pcie_readl_atu_ib(pci, ib, PCIE_ATU_LOWER_TARGET);
> > + if (val != 0x11110000)
> > break;
> > }
> >
> > @@ -707,12 +592,10 @@ void dw_pcie_iatu_detect(struct dw_pcie *pci)
> > if (!pci->atu_size)
> > /* Pick a minimal default, enough for 8 in and 8 out windows */
> > pci->atu_size = SZ_4K;
> > -
> > - dw_pcie_iatu_detect_regions_unroll(pci);
> > - } else {
> > - dw_pcie_iatu_detect_regions(pci);
> > }
> >
> > + dw_pcie_iatu_detect_regions(pci);
> > +
> > dev_info(pci->dev, "iATU unroll: %s\n", pci->iatu_unroll_enabled ?
> > "enabled" : "disabled");
> >
> > diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h
> > index 449c5ad92edc..6adf0c957c3b 100644
> > --- a/drivers/pci/controller/dwc/pcie-designware.h
> > +++ b/drivers/pci/controller/dwc/pcie-designware.h
> > @@ -103,10 +103,20 @@
> > #define PCIE_VERSION_NUMBER 0x8F8
> > #define PCIE_VERSION_TYPE 0x8FC
> >

> > +/*
> > + * iATU inbound and outbound windows CSRs. Before the IP-core v4.80a each
> > + * iATU region CSRs had been indirectly accessible by means of the dedicated
> > + * viewport selector. The iATU/eDMA CSRs space was re-designed in DWC PCIe
> > + * v4.80a in a way so the viewport was unrolled into the directly accessible
> > + * iATU/eDMA CSRs space.
>
> IIRC, I think it is configurable in later versions. There was some
> discussion when I did the detection.

There are two internal IP-core parameters which indicate whether the
unrolled eDMA/iATU CSRs mapping is enabled:
CC_UNROLL_EN = (CC_DMA_ENABLE || CX_INTERNAL_ATU_ENABLE) && !AHB_POPULATED
and
CC_UNROLL_ENABLE = ((CC_UNROLL_EN ==0) ? 0: 1)
Normally these parameters shouldn't be manually changed since they
aren't advertised in the list of the configurable IP-core parameters.
But there is a note in the databook (v4.90a, v5.20a, v5.40a) which
suggests to contact the Synopsys support if it's required to enable
the legacy mapping on the later IP-core versions. So basically you are
right. It is configurable, but not in the normal circumstances.
Practically I don't think there is any modern DW PCIe controller
implemented with the legacy eDMA/iATU CSRs mapping since the modern
CPUs/SoC-Interconnects mainly have enough unused IO space for
the unrolled access.

-Sergey

>
> > + */
> > #define PCIE_ATU_VIEWPORT 0x900
> > -#define PCIE_ATU_REGION_INBOUND BIT(31)
> > -#define PCIE_ATU_REGION_OUTBOUND 0
> > -#define PCIE_ATU_CR1 0x904
> > +#define PCIE_ATU_REGION_DIR_IB BIT(31)
> > +#define PCIE_ATU_REGION_DIR_OB 0
> > +#define PCIE_ATU_VIEWPORT_BASE 0x904
> > +#define PCIE_ATU_UNROLL_BASE(dir, region) \
> > + (((region) << 9) | ((dir == PCIE_ATU_REGION_DIR_IB) ? BIT(8) : 0))
> > +#define PCIE_ATU_REGION_CTRL1 0x000
> > #define PCIE_ATU_INCREASE_REGION_SIZE BIT(13)
> > #define PCIE_ATU_TYPE_MEM 0x0
> > #define PCIE_ATU_TYPE_IO 0x2
> > @@ -114,19 +124,19 @@
> > #define PCIE_ATU_TYPE_CFG1 0x5
> > #define PCIE_ATU_TD BIT(8)
> > #define PCIE_ATU_FUNC_NUM(pf) ((pf) << 20)
> > -#define PCIE_ATU_CR2 0x908
> > +#define PCIE_ATU_REGION_CTRL2 0x004
> > #define PCIE_ATU_ENABLE BIT(31)
> > #define PCIE_ATU_BAR_MODE_ENABLE BIT(30)
> > #define PCIE_ATU_FUNC_NUM_MATCH_EN BIT(19)
> > -#define PCIE_ATU_LOWER_BASE 0x90C
> > -#define PCIE_ATU_UPPER_BASE 0x910
> > -#define PCIE_ATU_LIMIT 0x914
> > -#define PCIE_ATU_LOWER_TARGET 0x918
> > +#define PCIE_ATU_LOWER_BASE 0x008
> > +#define PCIE_ATU_UPPER_BASE 0x00C
> > +#define PCIE_ATU_LIMIT 0x010
> > +#define PCIE_ATU_LOWER_TARGET 0x014
> > #define PCIE_ATU_BUS(x) FIELD_PREP(GENMASK(31, 24), x)
> > #define PCIE_ATU_DEV(x) FIELD_PREP(GENMASK(23, 19), x)
> > #define PCIE_ATU_FUNC(x) FIELD_PREP(GENMASK(18, 16), x)
> > -#define PCIE_ATU_UPPER_TARGET 0x91C
> > -#define PCIE_ATU_UPPER_LIMIT 0x924
> > +#define PCIE_ATU_UPPER_TARGET 0x018
> > +#define PCIE_ATU_UPPER_LIMIT 0x020
> >
> > #define PCIE_MISC_CONTROL_1_OFF 0x8BC
> > #define PCIE_DBI_RO_WR_EN BIT(0)
> > @@ -143,19 +153,6 @@
> >
> > #define PCIE_PL_CHK_REG_ERR_ADDR 0xB28
> >
> > -/*
> > - * iATU Unroll-specific register definitions
> > - * From 4.80 core version the address translation will be made by unroll
> > - */
> > -#define PCIE_ATU_UNR_REGION_CTRL1 0x00
> > -#define PCIE_ATU_UNR_REGION_CTRL2 0x04
> > -#define PCIE_ATU_UNR_LOWER_BASE 0x08
> > -#define PCIE_ATU_UNR_UPPER_BASE 0x0C
> > -#define PCIE_ATU_UNR_LOWER_LIMIT 0x10
> > -#define PCIE_ATU_UNR_LOWER_TARGET 0x14
> > -#define PCIE_ATU_UNR_UPPER_TARGET 0x18
> > -#define PCIE_ATU_UNR_UPPER_LIMIT 0x20
> > -
> > /*
> > * The default address offset between dbi_base and atu_base. Root controller
> > * drivers are not required to initialize atu_base if the offset matches this
> > @@ -164,13 +161,6 @@
> > */
> > #define DEFAULT_DBI_ATU_OFFSET (0x3 << 20)
> >
> > -/* Register address builder */
> > -#define PCIE_GET_ATU_OUTB_UNR_REG_OFFSET(region) \
> > - ((region) << 9)
> > -
> > -#define PCIE_GET_ATU_INB_UNR_REG_OFFSET(region) \
> > - (((region) << 9) | BIT(8))
> > -
> > #define MAX_MSI_IRQS 256
> > #define MAX_MSI_IRQS_PER_CTRL 32
> > #define MAX_MSI_CTRLS (MAX_MSI_IRQS / MAX_MSI_IRQS_PER_CTRL)
> > --
> > 2.35.1
> >

2022-04-18 09:05:33

by Serge Semin

[permalink] [raw]
Subject: Re: [PATCH 10/16] PCI: dwc: Drop iATU regions enumeration - dw_pcie_region_type

On Tue, Mar 29, 2022 at 10:31:18AM -0500, Rob Herring wrote:
> On Thu, Mar 24, 2022 at 04:37:28AM +0300, Serge Semin wrote:
> > There is no point in having the dw_pcie_region_type enumeration for almost
> > the same reasons as it was stated for dw_pcie_as_type. First of all it's
> > redundant since the driver already has a set of macro declared which
> > describe the possible inbound and outbound iATU regions. Having an
> > addition abstraction just needlessly complicates the code. Secondly
> > checking the region index passed to the dw_pcie_disable_atu() method for
> > validity is pointless since the erroneous situation will be just
> > ignored in the current code implementation. So to speak let's drop the
> > redundant dw_pcie_region_type enumeration replacing it with the direct
> > iATU direction macro usage.
> >
> > While at it we suggest to convert the dw_pcie_disable_atu() method to
> > being more consistent with the dw_pcie_readl_atu{_ib}() and
> > dw_pcie_readl_atu{_ob}() functions by having the direction parameter
> > specified ahead of the region index. Thus the code will be a little bit
> > more pleasant to read.
> >
> > Signed-off-by: Serge Semin <[email protected]>
> > ---
> > drivers/pci/controller/dwc/pcie-designware-ep.c | 4 ++--
> > .../pci/controller/dwc/pcie-designware-host.c | 2 +-
> > drivers/pci/controller/dwc/pcie-designware.c | 16 +---------------
> > drivers/pci/controller/dwc/pcie-designware.h | 9 +--------
> > 4 files changed, 5 insertions(+), 26 deletions(-)
>

> This answers my question. I would have expected this to come before the
> previous patch, but if it's easier to do it this way it's fine.
>
> Reviewed-by: Rob Herring <[email protected]>

I thought about placing this patch before the previous one, but it
turned a bit easier for me to split the changes in the reverse order.
It has made this patch a bit smaller and more coherent. But seeing you
weren't happy with too many changes in the previous patch I'll do as
you suggest and change the patches order. Since the patch content will
be changed I won't add your reviewed-by tag there on v2. So please
consider re-reviewing it one more time.

-Sergey

2022-04-18 11:29:03

by Serge Semin

[permalink] [raw]
Subject: Re: [PATCH 03/16] PCI: dwc: Add more verbose link-up message

On Tue, Mar 29, 2022 at 09:47:02AM -0500, Rob Herring wrote:
> On Thu, Mar 24, 2022 at 04:37:21AM +0300, Serge Semin wrote:
> > Printing just "link up" isn't that much informative especially when it
> > comes to working with the PCI Express bus. Even if the link is up, due to
> > multiple reasons the bus performance can degrade to slower speeds or to
> > narrower width than both Root Port and its partner is capable of. In that
> > case it would be handy to know the link specifications as early as
> > possible. So let's add a more verbose message to the busy-wait link-state
> > method, which will contain the link speed generation and the PCIe bus
> > width in case if the link up state is discovered. Otherwise an error will
> > be printed to the system log.
> >
> > Signed-off-by: Serge Semin <[email protected]>
> > ---
> > drivers/pci/controller/dwc/pcie-designware.c | 22 +++++++++++++++-----
> > 1 file changed, 17 insertions(+), 5 deletions(-)
> >
> > diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c
> > index 6e81264fdfb4..f1693e25afcb 100644
> > --- a/drivers/pci/controller/dwc/pcie-designware.c
> > +++ b/drivers/pci/controller/dwc/pcie-designware.c
> > @@ -528,14 +528,26 @@ int dw_pcie_wait_for_link(struct dw_pcie *pci)
> >
> > /* Check if the link is up or not */
> > for (retries = 0; retries < LINK_WAIT_MAX_RETRIES; retries++) {
> > - if (dw_pcie_link_up(pci)) {
> > - dev_info(pci->dev, "Link up\n");
> > - return 0;
> > - }
> > + if (dw_pcie_link_up(pci))
> > + break;
> > +
> > usleep_range(LINK_WAIT_USLEEP_MIN, LINK_WAIT_USLEEP_MAX);
> > }
> >
> > - dev_info(pci->dev, "Phy link never came up\n");
> > + if (retries < LINK_WAIT_MAX_RETRIES) {
> > + u32 offset, val;
> > +
> > + offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
> > + val = dw_pcie_readw_dbi(pci, offset + PCI_EXP_LNKSTA);
> > +
> > + dev_info(pci->dev, "PCIe Gen.%u x%u link up\n",
> > + FIELD_GET(PCI_EXP_LNKSTA_CLS, val),
> > + FIELD_GET(PCI_EXP_LNKSTA_NLW, val));
>

> Given these are standard registers can we do this in the core code? The
> main issue I think is that the config space accessors don't work until
> you create the bus struct. That still should be early enough.

AFAICS there are generic methods in the core code to get and print the
link status. See the __pcie_print_link_status() method implementation.
But as you said they rely on having the bus struct instance created and
properly initialized. It's created in the framework of the PCI Host bridge
registration procedure:
pci_host_probe()
+-> pci_scan_root_bus_bridge()
+-> pci_register_host_bridge()
+-> pci_alloc_bus(NULL);
+-> ...
As for me it would be more logical to have the PCIe link established
(at least activated) and it' status logged before any of the denoted
actions are made since further initialization rely on the PCIe bus
transfers. Moreover the PCIe host probe procedure doesn't really
perform any link up/down related activity, so there is no logical
place to implement the link state checking except someplace at the
traceback top position, but again the bus struct instance isn't
available at that stage. Of course we could implement an alternative
__pcie_print_HOST_link_status() method, which wouldn't need the bus
struct passed. But that would have required some more modifications
(and may cause some functionality duplication) than fixing a few lines
of code and wasn't a subject of this patchset. As such I decided to
stick with having the local link status logging procedure especially
seeing it's done in the framework of the link-wait method, which is
called right after the DW PCIe LTSSM is activated (at least for the DW
PCIe Host controller).

>
> I think it is possible some implementations don't report the link state
> in these registers. Maybe we don't really need to care.

I don't see a way to disable the PCIe capability in the DW PCIe
controllers. So if some implementations lack of these registers
reporting the link state, then either those implementations must have
been broken or they violate the PCIe Base Specification [1]. IMO that
must be considered as abnormal situation and needs to be specifically
handled.

[1] PCI Express? Base Specification Revision 5.0, p. 742.

-Sergey

>
> Rob