diff options
author | Wilfred Mallawa <wilfred.mallawa@wdc.com> | 2025-05-09 12:30:12 +1000 |
---|---|---|
committer | Krzysztof Wilczyński <kwilczynski@kernel.org> | 2025-05-16 10:27:27 +0000 |
commit | 0f9555f740de35f5509792b6f04c6b8c6ad9396d (patch) | |
tree | 18e5c27bb33b5f4e59592b4f356fd5e23a157406 | |
parent | b5c714170a4dd73154ae7bf1d01d19e267e668a3 (diff) | |
download | pci-controller/dw-rockchip.tar.gz |
PCI: dw-rockchip: Add support for slot reset on link down eventcontroller/dw-rockchip
The PCIe link may go down in cases like firmware crashes or unstable
connections. When this occurs, the PCIe slot must be reset to restore
functionality. However, the current driver lacks link down handling,
forcing users to reboot the system to recover.
This patch implements the 'reset_slot' callback for link down handling
for DWC PCIe host controller. In which, the RC is reset, reconfigured
and link training initiated to recover from the link down event.
This patch by extension fixes issues with sysfs initiated bus resets.
In that, currently, when a sysfs initiated bus reset is issued, the
endpoint device is non-functional after (may link up with downgraded link
status). With this patch adding support for link down recovery, a sysfs
initiated bus reset works as intended. Testing conducted on a ROCK5B board
with an M.2 NVMe drive.
Signed-off-by: Wilfred Mallawa <wilfred.mallawa@wdc.com>
[kwilczynski: update comments wording, make error messages lower case to
match rest of the style within the code base]
Signed-off-by: Krzysztof Wilczyński <kwilczynski@kernel.org>
Reviewed-by: Niklas Cassel <cassel@kernel.org>
Link: https://lore.kernel.org/r/20250509-b4-pci_dwc_reset_support-v3-1-37e96b4692e7@wdc.com
-rw-r--r-- | drivers/pci/controller/dwc/Kconfig | 1 | ||||
-rw-r--r-- | drivers/pci/controller/dwc/pcie-dw-rockchip.c | 88 |
2 files changed, 87 insertions, 2 deletions
diff --git a/drivers/pci/controller/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig index ce04ee6fbd99cb..01e2650242ccc3 100644 --- a/drivers/pci/controller/dwc/Kconfig +++ b/drivers/pci/controller/dwc/Kconfig @@ -348,6 +348,7 @@ config PCIE_ROCKCHIP_DW_HOST depends on OF select PCIE_DW_HOST select PCIE_ROCKCHIP_DW + select PCI_HOST_COMMON help Enables support for the DesignWare PCIe controller in the Rockchip SoC (except RK3399) to work in host mode. diff --git a/drivers/pci/controller/dwc/pcie-dw-rockchip.c b/drivers/pci/controller/dwc/pcie-dw-rockchip.c index ae171a545df6d5..f4736215749057 100644 --- a/drivers/pci/controller/dwc/pcie-dw-rockchip.c +++ b/drivers/pci/controller/dwc/pcie-dw-rockchip.c @@ -23,6 +23,8 @@ #include <linux/reset.h> #include "pcie-designware.h" +#include "../../pci.h" +#include "../pci-host-common.h" /* * The upper 16 bits of PCIE_CLIENT_CONFIG are a write @@ -83,6 +85,9 @@ struct rockchip_pcie_of_data { const struct pci_epc_features *epc_features; }; +static int rockchip_pcie_rc_reset_slot(struct pci_host_bridge *bridge, + struct pci_dev *pdev); + static int rockchip_pcie_readl_apb(struct rockchip_pcie *rockchip, u32 reg) { return readl_relaxed(rockchip->apb_base + reg); @@ -256,6 +261,7 @@ static int rockchip_pcie_host_init(struct dw_pcie_rp *pp) rockchip); rockchip_pcie_enable_l0s(pci); + pp->bridge->reset_slot = rockchip_pcie_rc_reset_slot; return 0; } @@ -455,6 +461,11 @@ static irqreturn_t rockchip_pcie_rc_sys_irq_thread(int irq, void *arg) dev_dbg(dev, "PCIE_CLIENT_INTR_STATUS_MISC: %#x\n", reg); dev_dbg(dev, "LTSSM_STATUS: %#x\n", rockchip_pcie_get_ltssm(rockchip)); + if (reg & PCIE_LINK_REQ_RST_NOT_INT) { + dev_dbg(dev, "hot reset or link-down reset\n"); + pci_host_handle_link_down(pp->bridge); + } + if (reg & PCIE_RDLH_LINK_UP_CHGED) { if (rockchip_pcie_link_up(pci)) { dev_dbg(dev, "Received Link up event. Starting enumeration!\n"); @@ -536,8 +547,8 @@ static int rockchip_pcie_configure_rc(struct platform_device *pdev, return ret; } - /* unmask DLL up/down indicator */ - val = HIWORD_UPDATE(PCIE_RDLH_LINK_UP_CHGED, 0); + /* Unmask DLL up/down indicator and hot reset/link-down reset IRQ. */ + val = HIWORD_UPDATE(PCIE_RDLH_LINK_UP_CHGED | PCIE_LINK_REQ_RST_NOT_INT, 0); rockchip_pcie_writel_apb(rockchip, val, PCIE_CLIENT_INTR_MASK_MISC); return ret; @@ -688,6 +699,79 @@ disable_regulator: return ret; } +static int rockchip_pcie_rc_reset_slot(struct pci_host_bridge *bridge, + struct pci_dev *pdev) +{ + struct pci_bus *bus = bridge->bus; + struct dw_pcie_rp *pp = bus->sysdata; + struct dw_pcie *pci = to_dw_pcie_from_pp(pp); + struct rockchip_pcie *rockchip = to_rockchip_pcie(pci); + struct device *dev = rockchip->pci.dev; + u32 val; + int ret; + + dw_pcie_stop_link(pci); + clk_bulk_disable_unprepare(rockchip->clk_cnt, rockchip->clks); + rockchip_pcie_phy_deinit(rockchip); + + ret = reset_control_assert(rockchip->rst); + if (ret) + return ret; + + ret = rockchip_pcie_phy_init(rockchip); + if (ret) + goto disable_regulator; + + ret = reset_control_deassert(rockchip->rst); + if (ret) + goto deinit_phy; + + ret = rockchip_pcie_clk_init(rockchip); + if (ret) + goto deinit_phy; + + ret = pp->ops->init(pp); + if (ret) { + dev_err(dev, "host init failed: %d\n", ret); + goto deinit_clk; + } + + /* LTSSM enable control mode. */ + val = HIWORD_UPDATE_BIT(PCIE_LTSSM_ENABLE_ENHANCE); + rockchip_pcie_writel_apb(rockchip, val, PCIE_CLIENT_HOT_RESET_CTRL); + + rockchip_pcie_writel_apb(rockchip, PCIE_CLIENT_RC_MODE, PCIE_CLIENT_GENERAL_CON); + + ret = dw_pcie_setup_rc(pp); + if (ret) { + dev_err(dev, "failed to setup RC: %d\n", ret); + goto deinit_clk; + } + + /* Unmask DLL up/down indicator and hot reset/link-down reset IRQ. */ + val = HIWORD_UPDATE(PCIE_RDLH_LINK_UP_CHGED | PCIE_LINK_REQ_RST_NOT_INT, 0); + rockchip_pcie_writel_apb(rockchip, val, PCIE_CLIENT_INTR_MASK_MISC); + + ret = dw_pcie_start_link(pci); + if (ret) + goto deinit_clk; + + /* Ignore errors, the link may come up later. */ + dw_pcie_wait_for_link(pci); + dev_dbg(dev, "slot reset completed\n"); + return ret; + +deinit_clk: + clk_bulk_disable_unprepare(rockchip->clk_cnt, rockchip->clks); +deinit_phy: + rockchip_pcie_phy_deinit(rockchip); +disable_regulator: + if (rockchip->vpcie3v3) + regulator_disable(rockchip->vpcie3v3); + + return ret; +} + static const struct rockchip_pcie_of_data rockchip_pcie_rc_of_data_rk3568 = { .mode = DW_PCIE_RC_TYPE, }; |