1
0
mirror of https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git synced 2026-01-11 17:10:13 +00:00

Merge branch 'pci/controller/s32g'

- Add NXP S32G host controller DT binding and driver (Vincent Guittot)

* pci/controller/s32g:
  MAINTAINERS: Add NXP S32G PCIe controller driver maintainer
  PCI: s32g: Add NXP S32G PCIe controller driver (RC)
  PCI: dwc: Add register and bitfield definitions
  dt-bindings: PCI: s32g: Add NXP S32G PCIe controller
This commit is contained in:
Bjorn Helgaas 2025-12-03 14:18:42 -06:00
commit dfb77c81a6
6 changed files with 564 additions and 0 deletions

View File

@ -0,0 +1,130 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/pci/nxp,s32g-pcie.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: NXP S32G2xxx/S32G3xxx PCIe Root Complex controller
maintainers:
- Bogdan Hamciuc <bogdan.hamciuc@nxp.com>
- Ionut Vicovan <ionut.vicovan@nxp.com>
description:
This PCIe controller is based on the Synopsys DesignWare PCIe IP.
The S32G SoC family has two PCIe controllers, which can be configured as
either Root Complex or Endpoint.
properties:
compatible:
oneOf:
- enum:
- nxp,s32g2-pcie
- items:
- const: nxp,s32g3-pcie
- const: nxp,s32g2-pcie
reg:
maxItems: 6
reg-names:
items:
- const: dbi
- const: dbi2
- const: atu
- const: dma
- const: ctrl
- const: config
interrupts:
minItems: 1
maxItems: 2
interrupt-names:
items:
- const: msi
- const: dma
minItems: 1
pcie@0:
description:
Describe the S32G Root Port.
type: object
$ref: /schemas/pci/pci-pci-bridge.yaml#
properties:
reg:
maxItems: 1
phys:
maxItems: 1
required:
- reg
- phys
unevaluatedProperties: false
required:
- compatible
- reg
- reg-names
- interrupts
- interrupt-names
- ranges
- pcie@0
allOf:
- $ref: /schemas/pci/snps,dw-pcie.yaml#
unevaluatedProperties: false
examples:
- |
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/phy/phy.h>
bus {
#address-cells = <2>;
#size-cells = <2>;
pcie@40400000 {
compatible = "nxp,s32g3-pcie", "nxp,s32g2-pcie";
reg = <0x00 0x40400000 0x0 0x00001000>, /* dbi registers */
<0x00 0x40420000 0x0 0x00001000>, /* dbi2 registers */
<0x00 0x40460000 0x0 0x00001000>, /* atu registers */
<0x00 0x40470000 0x0 0x00001000>, /* dma registers */
<0x00 0x40481000 0x0 0x000000f8>, /* ctrl registers */
<0x5f 0xffffe000 0x0 0x00002000>; /* config space */
reg-names = "dbi", "dbi2", "atu", "dma", "ctrl", "config";
dma-coherent;
#address-cells = <3>;
#size-cells = <2>;
device_type = "pci";
ranges =
<0x01000000 0x0 0x00000000 0x5f 0xfffe0000 0x0 0x00010000>,
<0x02000000 0x0 0x00000000 0x58 0x00000000 0x0 0x80000000>,
<0x02000000 0x1 0x00000000 0x59 0x00000000 0x6 0xfffe0000>;
bus-range = <0x0 0xff>;
interrupts = <GIC_SPI 125 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 123 IRQ_TYPE_LEVEL_HIGH>;
interrupt-names = "msi", "dma";
#interrupt-cells = <1>;
interrupt-map-mask = <0 0 0 0x7>;
interrupt-map = <0 0 0 1 &gic 0 0 GIC_SPI 128 IRQ_TYPE_LEVEL_HIGH>,
<0 0 0 2 &gic 0 0 GIC_SPI 129 IRQ_TYPE_LEVEL_HIGH>,
<0 0 0 3 &gic 0 0 GIC_SPI 130 IRQ_TYPE_LEVEL_HIGH>,
<0 0 0 4 &gic 0 0 GIC_SPI 131 IRQ_TYPE_LEVEL_HIGH>;
pcie@0 {
reg = <0x0 0x0 0x0 0x0 0x0>;
#address-cells = <3>;
#size-cells = <2>;
ranges;
device_type = "pci";
phys = <&serdes0 PHY_TYPE_PCIE 0 0>;
};
};
};

View File

@ -3132,6 +3132,15 @@ F: arch/arm64/boot/dts/freescale/s32g*.dts*
F: drivers/pinctrl/nxp/
F: drivers/rtc/rtc-s32g.c
ARM/NXP S32G PCIE CONTROLLER DRIVER
M: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com>
R: NXP S32 Linux Team <s32@nxp.com>
L: imx@lists.linux.dev
L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
S: Maintained
F: Documentation/devicetree/bindings/pci/nxp,s32g-pcie.yaml
F: drivers/pci/controller/dwc/pcie-nxp-s32g*
ARM/NXP S32G/S32R DWMAC ETHERNET DRIVER
M: Jan Petrous <jan.petrous@oss.nxp.com>
R: s32@nxp.com

View File

@ -256,6 +256,16 @@ config PCIE_TEGRA194_EP
in order to enable device-specific features PCIE_TEGRA194_EP must be
selected. This uses the DesignWare core.
config PCIE_NXP_S32G
bool "NXP S32G PCIe controller (host mode)"
depends on ARCH_S32 || COMPILE_TEST
select PCIE_DW_HOST
help
Enable support for the PCIe controller in NXP S32G based boards to
work in Host mode. The controller is based on DesignWare IP and
can work either as RC or EP. In order to enable host-specific
features PCIE_NXP_S32G must be selected.
config PCIE_DW_PLAT
bool

View File

@ -10,6 +10,7 @@ obj-$(CONFIG_PCI_DRA7XX) += pci-dra7xx.o
obj-$(CONFIG_PCI_EXYNOS) += pci-exynos.o
obj-$(CONFIG_PCIE_FU740) += pcie-fu740.o
obj-$(CONFIG_PCI_IMX6) += pci-imx6.o
obj-$(CONFIG_PCIE_NXP_S32G) += pcie-nxp-s32g.o
obj-$(CONFIG_PCIE_SPEAR13XX) += pcie-spear13xx.o
# ARM32 platforms use hook_fault_code() and cannot support loadable module.
obj-$(CONFIG_PCI_KEYSTONE) += pci-keystone.o

View File

@ -121,6 +121,7 @@
#define GEN3_RELATED_OFF 0x890
#define GEN3_RELATED_OFF_GEN3_ZRXDC_NONCOMPL BIT(0)
#define GEN3_RELATED_OFF_EQ_PHASE_2_3 BIT(9)
#define GEN3_RELATED_OFF_RXEQ_RGRDLESS_RXTS BIT(13)
#define GEN3_RELATED_OFF_GEN3_EQ_DISABLE BIT(16)
#define GEN3_RELATED_OFF_RATE_SHADOW_SEL_SHIFT 24
@ -138,6 +139,13 @@
#define GEN3_EQ_FMDC_MAX_PRE_CURSOR_DELTA GENMASK(13, 10)
#define GEN3_EQ_FMDC_MAX_POST_CURSOR_DELTA GENMASK(17, 14)
#define COHERENCY_CONTROL_1_OFF 0x8E0
#define CFG_MEMTYPE_BOUNDARY_LOW_ADDR_MASK GENMASK(31, 2)
#define CFG_MEMTYPE_VALUE BIT(0)
#define COHERENCY_CONTROL_2_OFF 0x8E4
#define COHERENCY_CONTROL_3_OFF 0x8E8
#define PCIE_PORT_MULTI_LANE_CTRL 0x8C0
#define PORT_MLTI_UPCFG_SUPPORT BIT(7)

View File

@ -0,0 +1,406 @@
// SPDX-License-Identifier: GPL-2.0
/*
* PCIe host controller driver for NXP S32G SoCs
*
* Copyright 2019-2025 NXP
*/
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/pci.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/sizes.h>
#include <linux/types.h>
#include "pcie-designware.h"
/* PCIe controller Sub-System */
/* PCIe controller 0 General Control 1 */
#define PCIE_S32G_PE0_GEN_CTRL_1 0x50
#define DEVICE_TYPE_MASK GENMASK(3, 0)
#define SRIS_MODE BIT(8)
/* PCIe controller 0 General Control 3 */
#define PCIE_S32G_PE0_GEN_CTRL_3 0x58
#define LTSSM_EN BIT(0)
/* PCIe Controller 0 Interrupt Status */
#define PCIE_S32G_PE0_INT_STS 0xE8
#define HP_INT_STS BIT(6)
/* Boundary between peripheral space and physical memory space */
#define S32G_MEMORY_BOUNDARY_ADDR 0x80000000
struct s32g_pcie_port {
struct list_head list;
struct phy *phy;
};
struct s32g_pcie {
struct dw_pcie pci;
void __iomem *ctrl_base;
struct list_head ports;
};
#define to_s32g_from_dw_pcie(x) \
container_of(x, struct s32g_pcie, pci)
static void s32g_pcie_writel_ctrl(struct s32g_pcie *s32g_pp, u32 reg, u32 val)
{
writel(val, s32g_pp->ctrl_base + reg);
}
static u32 s32g_pcie_readl_ctrl(struct s32g_pcie *s32g_pp, u32 reg)
{
return readl(s32g_pp->ctrl_base + reg);
}
static void s32g_pcie_enable_ltssm(struct s32g_pcie *s32g_pp)
{
u32 reg;
reg = s32g_pcie_readl_ctrl(s32g_pp, PCIE_S32G_PE0_GEN_CTRL_3);
reg |= LTSSM_EN;
s32g_pcie_writel_ctrl(s32g_pp, PCIE_S32G_PE0_GEN_CTRL_3, reg);
}
static void s32g_pcie_disable_ltssm(struct s32g_pcie *s32g_pp)
{
u32 reg;
reg = s32g_pcie_readl_ctrl(s32g_pp, PCIE_S32G_PE0_GEN_CTRL_3);
reg &= ~LTSSM_EN;
s32g_pcie_writel_ctrl(s32g_pp, PCIE_S32G_PE0_GEN_CTRL_3, reg);
}
static int s32g_pcie_start_link(struct dw_pcie *pci)
{
struct s32g_pcie *s32g_pp = to_s32g_from_dw_pcie(pci);
s32g_pcie_enable_ltssm(s32g_pp);
return 0;
}
static void s32g_pcie_stop_link(struct dw_pcie *pci)
{
struct s32g_pcie *s32g_pp = to_s32g_from_dw_pcie(pci);
s32g_pcie_disable_ltssm(s32g_pp);
}
static struct dw_pcie_ops s32g_pcie_ops = {
.start_link = s32g_pcie_start_link,
.stop_link = s32g_pcie_stop_link,
};
/* Configure the AMBA AXI Coherency Extensions (ACE) interface */
static void s32g_pcie_reset_mstr_ace(struct dw_pcie *pci)
{
u32 ddr_base_low = lower_32_bits(S32G_MEMORY_BOUNDARY_ADDR);
u32 ddr_base_high = upper_32_bits(S32G_MEMORY_BOUNDARY_ADDR);
dw_pcie_dbi_ro_wr_en(pci);
dw_pcie_writel_dbi(pci, COHERENCY_CONTROL_3_OFF, 0x0);
/*
* Ncore is a cache-coherent interconnect module that enables the
* integration of heterogeneous coherent and non-coherent agents in
* the chip. Ncore transactions to peripheral should be non-coherent
* or it might drop them.
*
* One example where this is needed are PCIe MSIs, which use NoSnoop=0
* and might end up routed to Ncore. PCIe coherent traffic (e.g. MSIs)
* that targets peripheral space will be dropped by Ncore because
* peripherals on S32G are not coherent as slaves. We add a hard
* boundary in the PCIe controller coherency control registers to
* separate physical memory space from peripheral space.
*
* Define the start of DDR as seen by Linux as this boundary between
* "memory" and "peripherals", with peripherals being below.
*/
dw_pcie_writel_dbi(pci, COHERENCY_CONTROL_1_OFF,
(ddr_base_low & CFG_MEMTYPE_BOUNDARY_LOW_ADDR_MASK));
dw_pcie_writel_dbi(pci, COHERENCY_CONTROL_2_OFF, ddr_base_high);
dw_pcie_dbi_ro_wr_dis(pci);
}
static int s32g_init_pcie_controller(struct dw_pcie_rp *pp)
{
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
struct s32g_pcie *s32g_pp = to_s32g_from_dw_pcie(pci);
u32 val;
/* Set RP mode */
val = s32g_pcie_readl_ctrl(s32g_pp, PCIE_S32G_PE0_GEN_CTRL_1);
val &= ~DEVICE_TYPE_MASK;
val |= FIELD_PREP(DEVICE_TYPE_MASK, PCI_EXP_TYPE_ROOT_PORT);
/* Use default CRNS */
val &= ~SRIS_MODE;
s32g_pcie_writel_ctrl(s32g_pp, PCIE_S32G_PE0_GEN_CTRL_1, val);
/*
* Make sure we use the coherency defaults (just in case the settings
* have been changed from their reset values)
*/
s32g_pcie_reset_mstr_ace(pci);
dw_pcie_dbi_ro_wr_en(pci);
val = dw_pcie_readl_dbi(pci, PCIE_PORT_FORCE);
val |= PORT_FORCE_DO_DESKEW_FOR_SRIS;
dw_pcie_writel_dbi(pci, PCIE_PORT_FORCE, val);
val = dw_pcie_readl_dbi(pci, GEN3_RELATED_OFF);
val |= GEN3_RELATED_OFF_EQ_PHASE_2_3;
dw_pcie_writel_dbi(pci, GEN3_RELATED_OFF, val);
dw_pcie_dbi_ro_wr_dis(pci);
return 0;
}
static const struct dw_pcie_host_ops s32g_pcie_host_ops = {
.init = s32g_init_pcie_controller,
};
static int s32g_init_pcie_phy(struct s32g_pcie *s32g_pp)
{
struct dw_pcie *pci = &s32g_pp->pci;
struct device *dev = pci->dev;
struct s32g_pcie_port *port, *tmp;
int ret;
list_for_each_entry(port, &s32g_pp->ports, list) {
ret = phy_init(port->phy);
if (ret) {
dev_err(dev, "Failed to init serdes PHY\n");
goto err_phy_revert;
}
ret = phy_set_mode_ext(port->phy, PHY_MODE_PCIE, 0);
if (ret) {
dev_err(dev, "Failed to set mode on serdes PHY\n");
goto err_phy_exit;
}
ret = phy_power_on(port->phy);
if (ret) {
dev_err(dev, "Failed to power on serdes PHY\n");
goto err_phy_exit;
}
}
return 0;
err_phy_exit:
phy_exit(port->phy);
err_phy_revert:
list_for_each_entry_continue_reverse(port, &s32g_pp->ports, list) {
phy_power_off(port->phy);
phy_exit(port->phy);
}
list_for_each_entry_safe(port, tmp, &s32g_pp->ports, list)
list_del(&port->list);
return ret;
}
static void s32g_deinit_pcie_phy(struct s32g_pcie *s32g_pp)
{
struct s32g_pcie_port *port, *tmp;
list_for_each_entry_safe(port, tmp, &s32g_pp->ports, list) {
phy_power_off(port->phy);
phy_exit(port->phy);
list_del(&port->list);
}
}
static int s32g_pcie_init(struct device *dev, struct s32g_pcie *s32g_pp)
{
s32g_pcie_disable_ltssm(s32g_pp);
return s32g_init_pcie_phy(s32g_pp);
}
static void s32g_pcie_deinit(struct s32g_pcie *s32g_pp)
{
s32g_pcie_disable_ltssm(s32g_pp);
s32g_deinit_pcie_phy(s32g_pp);
}
static int s32g_pcie_parse_port(struct s32g_pcie *s32g_pp, struct device_node *node)
{
struct device *dev = s32g_pp->pci.dev;
struct s32g_pcie_port *port;
int num_lanes;
port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
if (!port)
return -ENOMEM;
port->phy = devm_of_phy_get(dev, node, NULL);
if (IS_ERR(port->phy))
return dev_err_probe(dev, PTR_ERR(port->phy),
"Failed to get serdes PHY\n");
INIT_LIST_HEAD(&port->list);
list_add_tail(&port->list, &s32g_pp->ports);
/*
* The DWC core initialization code cannot yet parse the num-lanes
* attribute in the Root Port node. The S32G only supports one Root
* Port for now so its driver can parse the node and set the num_lanes
* field of struct dwc_pcie before calling dw_pcie_host_init().
*/
if (!of_property_read_u32(node, "num-lanes", &num_lanes))
s32g_pp->pci.num_lanes = num_lanes;
return 0;
}
static int s32g_pcie_parse_ports(struct device *dev, struct s32g_pcie *s32g_pp)
{
struct s32g_pcie_port *port, *tmp;
int ret = -ENOENT;
for_each_available_child_of_node_scoped(dev->of_node, of_port) {
if (!of_node_is_type(of_port, "pci"))
continue;
ret = s32g_pcie_parse_port(s32g_pp, of_port);
if (ret)
goto err_port;
}
err_port:
list_for_each_entry_safe(port, tmp, &s32g_pp->ports, list)
list_del(&port->list);
return ret;
}
static int s32g_pcie_get_resources(struct platform_device *pdev,
struct s32g_pcie *s32g_pp)
{
struct dw_pcie *pci = &s32g_pp->pci;
struct device *dev = &pdev->dev;
int ret;
pci->dev = dev;
pci->ops = &s32g_pcie_ops;
s32g_pp->ctrl_base = devm_platform_ioremap_resource_byname(pdev, "ctrl");
if (IS_ERR(s32g_pp->ctrl_base))
return PTR_ERR(s32g_pp->ctrl_base);
INIT_LIST_HEAD(&s32g_pp->ports);
ret = s32g_pcie_parse_ports(dev, s32g_pp);
if (ret)
return dev_err_probe(dev, ret,
"Failed to parse Root Port: %d\n", ret);
platform_set_drvdata(pdev, s32g_pp);
return 0;
}
static int s32g_pcie_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct s32g_pcie *s32g_pp;
struct dw_pcie_rp *pp;
int ret;
s32g_pp = devm_kzalloc(dev, sizeof(*s32g_pp), GFP_KERNEL);
if (!s32g_pp)
return -ENOMEM;
ret = s32g_pcie_get_resources(pdev, s32g_pp);
if (ret)
return ret;
pm_runtime_no_callbacks(dev);
devm_pm_runtime_enable(dev);
ret = pm_runtime_get_sync(dev);
if (ret < 0)
goto err_pm_runtime_put;
ret = s32g_pcie_init(dev, s32g_pp);
if (ret)
goto err_pm_runtime_put;
pp = &s32g_pp->pci.pp;
pp->ops = &s32g_pcie_host_ops;
pp->use_atu_msg = true;
ret = dw_pcie_host_init(pp);
if (ret)
goto err_pcie_deinit;
return 0;
err_pcie_deinit:
s32g_pcie_deinit(s32g_pp);
err_pm_runtime_put:
pm_runtime_put(dev);
return ret;
}
static int s32g_pcie_suspend_noirq(struct device *dev)
{
struct s32g_pcie *s32g_pp = dev_get_drvdata(dev);
struct dw_pcie *pci = &s32g_pp->pci;
return dw_pcie_suspend_noirq(pci);
}
static int s32g_pcie_resume_noirq(struct device *dev)
{
struct s32g_pcie *s32g_pp = dev_get_drvdata(dev);
struct dw_pcie *pci = &s32g_pp->pci;
return dw_pcie_resume_noirq(pci);
}
static const struct dev_pm_ops s32g_pcie_pm_ops = {
NOIRQ_SYSTEM_SLEEP_PM_OPS(s32g_pcie_suspend_noirq,
s32g_pcie_resume_noirq)
};
static const struct of_device_id s32g_pcie_of_match[] = {
{ .compatible = "nxp,s32g2-pcie" },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, s32g_pcie_of_match);
static struct platform_driver s32g_pcie_driver = {
.driver = {
.name = "s32g-pcie",
.of_match_table = s32g_pcie_of_match,
.suppress_bind_attrs = true,
.pm = pm_sleep_ptr(&s32g_pcie_pm_ops),
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
},
.probe = s32g_pcie_probe,
};
builtin_platform_driver(s32g_pcie_driver);
MODULE_AUTHOR("Ionut Vicovan <Ionut.Vicovan@nxp.com>");
MODULE_DESCRIPTION("NXP S32G PCIe Host controller driver");
MODULE_LICENSE("GPL");