mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-09-18 23:09:29 +02:00
Pull PCI updates from Bjorn Helgaas:
"Resource management:
- Add hotplug reservation only once (not at each level of the
hierarchy) so bridge windows don't grow more than necessary (Ilpo
Järvinen)
Driver binding:
- Rework device matching so device ID lifetime only needs to cover
the probe path since dynamic IDs can be removed at any time (Gary
Guo)
Error handling:
- Update mappings of AER errors to agent & layer and log them for
each individual error when multiple errors detected (Lukas Wunner)
- Log Error Source only once, not twice in separate messages (Lukas
Wunner)
- Emit TLP Log only for unmasked errors (Lukas Wunner)
- Support Advisory Non-Fatal Errors (Lukas Wunner)
- Allow DPC on all Downstream Ports, not just Root Ports, when OS
controls AER (Darshit Shah)
ASPM:
- Program the same ASPM Control values for every function of
multi-function devices, as recommended by the PCIe spec (Krishna
Chaitanya Chundru)
- Avoid L0s for Realtek RTS525A, where it causes an AER interrupt
storm (Max Lee)
- Avoid ASPM L0s, L1, and L1 PM Substates based on 'aspm-no-l0s',
'aspm-no-l1' [1], and 'aspm-no-l1ss' DT properties (Krishna
Chaitanya Chundru)
Power management:
- Allow D3 for native hotplug-capable Root Ports on non-x86 platforms
(we avoid D3 for these ports on x86 because some old platforms
didn't validate it) (Manivannan Sadhasivam)
- Allow portdrv to claim Ports even if they don't support services
(AER, PME, DPC, hotplug, etc) so it can do power management (Brian
Norris)
Power control:
- Add support for PCIe WAKE# interrupt when described via DT (Krishna
Chaitanya Chundru)
- For the TC9563 PCIe switch:
- Take a reference on the I2C adapter to avoid uninterruptible
hang when unloading an I2C module while in-use (Johan Hovold)
- Update DT binding and driver to restrict Tx Amplitude, DFE and
N_FTS to USP, DSP1 and DSP2 (Manivannan Sadhasivam)
- Power off only external-facing ports (DSP1, DSP2), leaving USP
and DSP3 (aka VDSP) powered up (Manivannan Sadhasivam)
- Move integrated MAC Endpoint out of the list of internal ports
and configure it separately (Manivannan Sadhasivam)
Virtualization:
- Add ACS quirk for Pericom PI7C9X2G608 switches (Tim Harvey)
- Fix a long-standing bug in the Intel PCH Root Port MPC ACS quirk
that didn't update the intended INTEL_MPC_REG_IRBNCE bit because it
used a 16-bit config write when a 32-bit write was intended
(Mohamad Raizudeen)
Procfs:
- Avoid spurious runtime PM wakeup on config space accesses that are
outside config space and fail before reaching PCI (Krzysztof
Wilczyński)
- Warn on user-space writes to kernel-exclusive config space regions,
as we already do for sysfs (Krzysztof Wilczyński)
- Check credentials of opener, not reader, for config space reads, as
we already do for sysfs (Krzysztof Wilczyński)
Sysfs:
- In pci_write_legacy_io(), avoid out-of-bounds reads from the user
buffer and fix incorrect ioport write data (1-byte writes on
little-endian powerpc, 2- and 4-byte writes on big-endian powerpc)
(Krzysztof Wilczyński)
- In pci_read_legacy_io(), fix incorrect ioport read data for 2- and
4-byte reads on big-endian powerpc (Krzysztof Wilczyński)
- Fix I/O port accessor argument order in Alpha pci_legacy_write()
(Krzysztof Wilczyński)
- Avoid spurious runtime PM wakeup on config space accesses that are
outside config space and fail before reaching PCI (Krzysztof
Wilczyński)
- Return -EINVAL, not -ENODEV, for mmap of I/O BAR that fails because
the arch doesn't support it, as we do for procfs (Krzysztof
Wilczyński)
- Check for LOCKDOWN_PCI_ACCESS for legacy_io and legacy_mem, as we
do for other config space accessors (Krzysztof Wilczyński)
Peer-to-peer DMA:
- Add Nvidia Vera Rubin to list of platforms that support P2PDMA
(Leon Romanovsky)
Endpoint framework:
- Check doorbell SUCCESS bit in pci_endpoint_test to avoid treating
some failures as successes (Niklas Cassel)
- Fail doorbell test when the trigger IRQ is missed (Niklas Cassel)
New native PCIe controller drivers:
- Add DT binding and driver for NVIDIA Tegra264 (Thierry Reding)
Native PCIe controllers:
- Use common wait time definitions for PCIe link monitoring instead
of defining driver-private duplicates (Thierry Reding)
Generic host bridge driver:
- Fix NULL pointer dereference that caused enumeration failures on
32-bit CAM systems (Steffen Persvold)
Amlogic Meson PCIe controller driver:
- Correct the PERST# GPIO state so it remains asserted until power
and REFCLK become stable to fix enumeration failure (Ronald
Claveau)
ASPEED PCIe controller driver:
- Switch to irq_domain_create_linear() so we can obsolete
irq_domain_add_linear() (Jiri Slaby)
Cadence PCIe controller driver:
- Add MODULE_DEVICE_TABLE to generate module aliases for OF-based
module autoloading (Pengpeng Hou)
- Add debugfs 'ltssm_status' file for LGA- and HPA-based Cadence
controllers (Hans Zhang)
- Support up to x4 (not x2) lanes for J200 (Takuma Fujiwara)
- Fix host/endpoint dependencies for cadence-plat driver to fix link
error when cadence-plat is built-in but the host or endpoint driver
is modular (Aksh Garg)
Freescale i.MX6 PCIe controller driver:
- Add imx6 intr/aer/pme interrupt lines for i.MX95 (Richard Zhu)
- Remove PERST# checking from pci_host_common_parse_port() so callers
can decide whether to fall back to legacy DT binding with PERST# in
the host bridge (Sherry Sun)
- Fix build issues when PCI_PWRCTRL_GENERIC or PCI_HOST_COMMON is a
module (Arnd Bergmann)
- Create pwrctrl devices only once by doing it from imx_pcie_probe()
instead of imx_pcie_host_init(), which is used during both probe
and resume (Sherry Sun)
- Use 'dw_pcie_rp->skip_pwrctrl_off' to avoid powering off devices
during suspend to preserve wakeup capability (Sherry Sun)
- Add runtime PM support for i.MX95 to allow dynamic power management
when the link is idle (Richard Zhu)
Intel VMD host bridge driver:
- Support device ID 0x28C1 and assume that BIOS has already
enumerated the hierarchy below VMD and stored bus range info for OS
to use (Nirmal Patel)
- Add support for VMCONFIG BUS_RESTRICT_CFG=3, which makes it
possible to enumerate downstream devices on Intel Arrow Lake-HX
systems and probably others (Ali Alaei)
- Pay attention to _OSC negotiation for VMD hierarchy only when
running on bare metal, not when running in a VM (Nirmal Patel)
- Add Nova Lake (NVL) and Dunlow (DNL) Device IDs (Szymon Durawa)
MediaTek PCIe controller driver:
- Add support for PCIe controller in EcoNet EN7528 and EN751221 SoCs
(Caleb James DeLisle)
MediaTek PCIe Gen3 controller driver:
- Add mediatek-gen3 'memory-region' for restricted DMA buffer
(Chen-Yu Tsai)
NVIDIA Tegra264 PCIe controller driver:
- Distinguish Tegra264 C0 PCIe controller for internal GPU from C1-C5
controllers so the unit address matches the first 'reg' entry
(Thierry Reding)
- Add Tegra264 Root Port stanzas to prepare for generic WAKE#
handling (Thierry Reding)
Qualcomm PCIe controller driver:
- Add IPQ9650 compatible with global interrupt (Kathiravan
Thirumoorthy)
- Add IPQ5210 compatible with IPQ9574 fallback (Varadarajan
Narayanan)
- Add DT binding and driver support for Hawi SoC (Matthew Leung)
- Skip PERST# GPIOs provided by downstream PCIe devices, which should
be handled by drivers of those devices (Manivannan Sadhasivam)
- Stop advertising Attention Button Present (no Qcom SoCs support
Attention Buttons) so pciehp can use Presence Detect Changed events
(Qiang Yu)
Renesas R-Car PCIe controller driver:
- Add rcar-gen4-pci-host optional 'msi-parent' for GIT ITS (Marek
Vasut)
- When MSI is enabled but iMSI-RX is not used, configure AXIINTC to
allow GIT ITS to handle MSI (Marek Vasut)
- Refactor GIC600 implementation to make it easier to add platforms
that only support 32-bit addressing (Marek Vasut)
- Add Renesas R-Car Gen4 S4/V4H/V4M to the list of GIC600
integrations that only support 32-bit addressing (Marek Vasut)
Renesas RZ/G3S PCIe controller driver:
- Add DT binding and driver support for RZ/V2H(P) SoC, which contains
two PCIe controllers, configured either as a single x4 link or two
independent x2 link controllers (Lad Prabhakar)
SpacemiT K1 PCIe controller driver:
- Add missing MODULE_DEVICE_TABLE() to generate module alias info for
OF-based module autoloading (Pengpeng Hou)
StarFive PCIe controller driver:
- Fix resource leaks on error paths in host_init() (Ali Tariq)
- Fix runtime PM handling and teardown ordering to avoid register
access while power or clocks are disabled (Ali Tariq)
- Check for runtime PM resume failure to avoid register access while
power or clocks are disabled (Ali Tariq)
Synopsys DesignWare PCIe controller driver:
- Add LECARC PMU IDs to the DWC RAS/DES VSEC list so it can take
advantage of the existing debugfs support for silicon debug, error
injection, and event counters (Brett Zhou)
- Factor pcie_valid_speed() and pci_bus_speed2lnkctl2() out of bwctrl
so they can be shared by the DWC core (Hans Zhang)
- Flush MSI writes from endpoint before unmapping the iATU, as we
already do for MSI-X writes (Niklas Cassel)
- Unmap MSI iATU window before mapping MSI-X window, to avoid a
subsequent MSI write using a disabled aperture and losing the
interrupt (Niklas Cassel)
- Change endpoint .pre_init() and .init() callbacks to return errors
and handle them (Marek Vasut)
UltraRISC PCIe controller driver:
- Add 'core', 'dbi', and 'aux' clocks to DT binding and manage them
in the driver (Jia Wang)
- Use module_platform_driver() since this may be built as a module,
though not removable because IRQs can't be safely disposed (Jia
Wang)
MicroSemi Switchtec management driver:
- Add Microchip PCI1008 device ID and include it in NTB DMA alias
quirk (Logan Gunthorpe)
Miscellaneous:
- Document how to write PCI Host Controller drivers (Manivannan
Sadhasivam)
- Fix typos in documentation (D'Orus Tsitera)
- Use %pe format specifier to print error pointers so we get symbolic
errname when available (Krzysztof Wilczyński)"
* tag 'pci-v7.3-changes' of git://git.kernel.org/pub/scm/linux/kernel/git/pci/pci: (124 commits)
PCI: vmd: Add Nova Lake (NVL) and Dunlow (DNL) Device IDs
PCI: tegra264: Add Tegra264 support
dt-bindings: PCI: tegra264: Switch to PCIe Root Port bindings
dt-bindings: PCI: tegra264: Strictly distinguish C0 from C1-C5
PCI/AER: Support Advisory Non-Fatal Errors
PCI: Fix 32-bit config write in Intel PCH Root Port MPC ACS quirk
PCI: dwc: Handle return value from endpoint .pre_init callback
PCI: dwc: Handle return value from endpoint .init callback
PCI: dwc: Add PCI ID for LECARC PCIe PMU
PCI/ASPM: Mask ASPM states based on Devicetree properties
PCI/ASPM: Disable/restore ASPM on every function for multi-function devices
Documentation: PCI: Document how to write PCI Host Controller drivers
PCI/ASPM: Use pcie_capability_clear_and_set_word() for ASPM disable/restore
PCI: Add support for PCIe WAKE# interrupt
PCI: Allow D3 for native hotplug-capable Root Ports on non-x86 platforms
dt-bindings: PCI: Correct white-space style
PCI/ASPM: Avoid L0s for Realtek RTS525A
PCI: ultrarisc: Use module_platform_driver()
PCI: ultrarisc: Get and enable DP1000 PCIe controller clocks
dt-bindings: PCI: ultrarisc: Add required DP1000 PCIe clocks
...
488 lines
11 KiB
C
488 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* PCIe host controller driver for Amlogic MESON SoCs
|
|
*
|
|
* Copyright (c) 2018 Amlogic, inc.
|
|
* Author: Yue Wang <yue.wang@amlogic.com>
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/reset.h>
|
|
#include <linux/resource.h>
|
|
#include <linux/types.h>
|
|
#include <linux/phy/phy.h>
|
|
#include <linux/module.h>
|
|
|
|
#include "pcie-designware.h"
|
|
|
|
#define to_meson_pcie(x) dev_get_drvdata((x)->dev)
|
|
|
|
#define PCIE_CAP_MAX_PAYLOAD_SIZE(x) ((x) << 5)
|
|
#define PCIE_CAP_MAX_READ_REQ_SIZE(x) ((x) << 12)
|
|
|
|
/* PCIe specific config registers */
|
|
#define PCIE_CFG0 0x0
|
|
#define APP_LTSSM_ENABLE BIT(7)
|
|
|
|
#define PCIE_CFG_STATUS12 0x30
|
|
#define IS_SMLH_LINK_UP(x) ((x) & (1 << 6))
|
|
#define IS_RDLH_LINK_UP(x) ((x) & (1 << 16))
|
|
#define IS_LTSSM_UP(x) ((((x) >> 10) & 0x1f) == 0x11)
|
|
|
|
#define PCIE_CFG_STATUS17 0x44
|
|
#define PM_CURRENT_STATE(x) (((x) >> 7) & 0x1)
|
|
|
|
#define PORT_CLK_RATE 100000000UL
|
|
#define MAX_PAYLOAD_SIZE 256
|
|
#define MAX_READ_REQ_SIZE 256
|
|
#define PCIE_RESET_DELAY 500
|
|
#define PCIE_SHARED_RESET 1
|
|
#define PCIE_NORMAL_RESET 0
|
|
|
|
enum pcie_data_rate {
|
|
PCIE_GEN1,
|
|
PCIE_GEN2,
|
|
PCIE_GEN3,
|
|
PCIE_GEN4
|
|
};
|
|
|
|
struct meson_pcie_clk_res {
|
|
struct clk *clk;
|
|
struct clk *port_clk;
|
|
struct clk *general_clk;
|
|
};
|
|
|
|
struct meson_pcie_rc_reset {
|
|
struct reset_control *port;
|
|
struct reset_control *apb;
|
|
};
|
|
|
|
struct meson_pcie {
|
|
struct dw_pcie pci;
|
|
void __iomem *cfg_base;
|
|
struct meson_pcie_clk_res clk_res;
|
|
struct meson_pcie_rc_reset mrst;
|
|
struct gpio_desc *reset_gpio;
|
|
struct phy *phy;
|
|
};
|
|
|
|
static struct reset_control *meson_pcie_get_reset(struct meson_pcie *mp,
|
|
const char *id,
|
|
u32 reset_type)
|
|
{
|
|
struct device *dev = mp->pci.dev;
|
|
struct reset_control *reset;
|
|
|
|
if (reset_type == PCIE_SHARED_RESET)
|
|
reset = devm_reset_control_get_shared(dev, id);
|
|
else
|
|
reset = devm_reset_control_get(dev, id);
|
|
|
|
return reset;
|
|
}
|
|
|
|
static int meson_pcie_get_resets(struct meson_pcie *mp)
|
|
{
|
|
struct meson_pcie_rc_reset *mrst = &mp->mrst;
|
|
|
|
mrst->port = meson_pcie_get_reset(mp, "port", PCIE_NORMAL_RESET);
|
|
if (IS_ERR(mrst->port))
|
|
return PTR_ERR(mrst->port);
|
|
reset_control_deassert(mrst->port);
|
|
|
|
mrst->apb = meson_pcie_get_reset(mp, "apb", PCIE_SHARED_RESET);
|
|
if (IS_ERR(mrst->apb))
|
|
return PTR_ERR(mrst->apb);
|
|
reset_control_deassert(mrst->apb);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int meson_pcie_get_mems(struct platform_device *pdev,
|
|
struct meson_pcie *mp)
|
|
{
|
|
struct dw_pcie *pci = &mp->pci;
|
|
struct resource *res;
|
|
|
|
/*
|
|
* For the broken DTs that supply 'dbi' as 'elbi', parse the 'elbi'
|
|
* region and assign it to both 'pci->elbi_base' and 'pci->dbi_space' so
|
|
* that the DWC core can skip parsing both regions.
|
|
*/
|
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "elbi");
|
|
if (res) {
|
|
pci->elbi_base = devm_pci_remap_cfg_resource(pci->dev, res);
|
|
if (IS_ERR(pci->elbi_base))
|
|
return PTR_ERR(pci->elbi_base);
|
|
|
|
pci->dbi_base = pci->elbi_base;
|
|
pci->dbi_phys_addr = res->start;
|
|
}
|
|
|
|
mp->cfg_base = devm_platform_ioremap_resource_byname(pdev, "cfg");
|
|
if (IS_ERR(mp->cfg_base))
|
|
return PTR_ERR(mp->cfg_base);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int meson_pcie_power_on(struct meson_pcie *mp)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret = phy_init(mp->phy);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = phy_power_on(mp->phy);
|
|
if (ret) {
|
|
phy_exit(mp->phy);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void meson_pcie_power_off(struct meson_pcie *mp)
|
|
{
|
|
phy_power_off(mp->phy);
|
|
phy_exit(mp->phy);
|
|
}
|
|
|
|
static int meson_pcie_reset(struct meson_pcie *mp)
|
|
{
|
|
struct meson_pcie_rc_reset *mrst = &mp->mrst;
|
|
int ret = 0;
|
|
|
|
ret = phy_reset(mp->phy);
|
|
if (ret)
|
|
return ret;
|
|
|
|
reset_control_assert(mrst->port);
|
|
reset_control_assert(mrst->apb);
|
|
udelay(PCIE_RESET_DELAY);
|
|
reset_control_deassert(mrst->port);
|
|
reset_control_deassert(mrst->apb);
|
|
udelay(PCIE_RESET_DELAY);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline void meson_pcie_disable_clock(void *data)
|
|
{
|
|
struct clk *clk = data;
|
|
|
|
clk_disable_unprepare(clk);
|
|
}
|
|
|
|
static inline struct clk *meson_pcie_probe_clock(struct device *dev,
|
|
const char *id, u64 rate)
|
|
{
|
|
struct clk *clk;
|
|
int ret;
|
|
|
|
clk = devm_clk_get(dev, id);
|
|
if (IS_ERR(clk))
|
|
return clk;
|
|
|
|
if (rate) {
|
|
ret = clk_set_rate(clk, rate);
|
|
if (ret) {
|
|
dev_err(dev, "set clk rate failed, ret = %d\n", ret);
|
|
return ERR_PTR(ret);
|
|
}
|
|
}
|
|
|
|
ret = clk_prepare_enable(clk);
|
|
if (ret) {
|
|
dev_err(dev, "couldn't enable clk\n");
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
ret = devm_add_action_or_reset(dev, meson_pcie_disable_clock, clk);
|
|
if (ret)
|
|
return ERR_PTR(ret);
|
|
|
|
return clk;
|
|
}
|
|
|
|
static int meson_pcie_probe_clocks(struct meson_pcie *mp)
|
|
{
|
|
struct device *dev = mp->pci.dev;
|
|
struct meson_pcie_clk_res *res = &mp->clk_res;
|
|
|
|
res->port_clk = meson_pcie_probe_clock(dev, "port", PORT_CLK_RATE);
|
|
if (IS_ERR(res->port_clk))
|
|
return PTR_ERR(res->port_clk);
|
|
|
|
res->general_clk = meson_pcie_probe_clock(dev, "general", 0);
|
|
if (IS_ERR(res->general_clk))
|
|
return PTR_ERR(res->general_clk);
|
|
|
|
res->clk = meson_pcie_probe_clock(dev, "pclk", 0);
|
|
if (IS_ERR(res->clk))
|
|
return PTR_ERR(res->clk);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline u32 meson_cfg_readl(struct meson_pcie *mp, u32 reg)
|
|
{
|
|
return readl(mp->cfg_base + reg);
|
|
}
|
|
|
|
static inline void meson_cfg_writel(struct meson_pcie *mp, u32 val, u32 reg)
|
|
{
|
|
writel(val, mp->cfg_base + reg);
|
|
}
|
|
|
|
static void meson_pcie_assert_reset(struct meson_pcie *mp)
|
|
{
|
|
gpiod_set_value_cansleep(mp->reset_gpio, 1);
|
|
udelay(500);
|
|
gpiod_set_value_cansleep(mp->reset_gpio, 0);
|
|
}
|
|
|
|
static void meson_pcie_ltssm_enable(struct meson_pcie *mp)
|
|
{
|
|
u32 val;
|
|
|
|
val = meson_cfg_readl(mp, PCIE_CFG0);
|
|
val |= APP_LTSSM_ENABLE;
|
|
meson_cfg_writel(mp, val, PCIE_CFG0);
|
|
}
|
|
|
|
static int meson_size_to_payload(struct meson_pcie *mp, int size)
|
|
{
|
|
struct device *dev = mp->pci.dev;
|
|
|
|
/*
|
|
* dwc supports 2^(val+7) payload size, which val is 0~5 default to 1.
|
|
* So if input size is not 2^order alignment or less than 2^7 or bigger
|
|
* than 2^12, just set to default size 2^(1+7).
|
|
*/
|
|
if (!is_power_of_2(size) || size < 128 || size > 4096) {
|
|
dev_warn(dev, "payload size %d, set to default 256\n", size);
|
|
return 1;
|
|
}
|
|
|
|
return fls(size) - 8;
|
|
}
|
|
|
|
static void meson_set_max_payload(struct meson_pcie *mp, int size)
|
|
{
|
|
struct dw_pcie *pci = &mp->pci;
|
|
u32 val;
|
|
u16 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
|
|
int max_payload_size = meson_size_to_payload(mp, size);
|
|
|
|
val = dw_pcie_readl_dbi(pci, offset + PCI_EXP_DEVCTL);
|
|
val &= ~PCI_EXP_DEVCTL_PAYLOAD;
|
|
dw_pcie_writel_dbi(pci, offset + PCI_EXP_DEVCTL, val);
|
|
|
|
val = dw_pcie_readl_dbi(pci, offset + PCI_EXP_DEVCTL);
|
|
val |= PCIE_CAP_MAX_PAYLOAD_SIZE(max_payload_size);
|
|
dw_pcie_writel_dbi(pci, offset + PCI_EXP_DEVCTL, val);
|
|
}
|
|
|
|
static void meson_set_max_rd_req_size(struct meson_pcie *mp, int size)
|
|
{
|
|
struct dw_pcie *pci = &mp->pci;
|
|
u32 val;
|
|
u16 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
|
|
int max_rd_req_size = meson_size_to_payload(mp, size);
|
|
|
|
val = dw_pcie_readl_dbi(pci, offset + PCI_EXP_DEVCTL);
|
|
val &= ~PCI_EXP_DEVCTL_READRQ;
|
|
dw_pcie_writel_dbi(pci, offset + PCI_EXP_DEVCTL, val);
|
|
|
|
val = dw_pcie_readl_dbi(pci, offset + PCI_EXP_DEVCTL);
|
|
val |= PCIE_CAP_MAX_READ_REQ_SIZE(max_rd_req_size);
|
|
dw_pcie_writel_dbi(pci, offset + PCI_EXP_DEVCTL, val);
|
|
}
|
|
|
|
static int meson_pcie_start_link(struct dw_pcie *pci)
|
|
{
|
|
struct meson_pcie *mp = to_meson_pcie(pci);
|
|
|
|
meson_pcie_ltssm_enable(mp);
|
|
meson_pcie_assert_reset(mp);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int meson_pcie_rd_own_conf(struct pci_bus *bus, u32 devfn,
|
|
int where, int size, u32 *val)
|
|
{
|
|
int ret;
|
|
|
|
ret = pci_generic_config_read(bus, devfn, where, size, val);
|
|
if (ret != PCIBIOS_SUCCESSFUL)
|
|
return ret;
|
|
|
|
/*
|
|
* There is a bug in the MESON AXG PCIe controller whereby software
|
|
* cannot program the PCI_CLASS_DEVICE register, so we must fabricate
|
|
* the return value in the config accessors.
|
|
*/
|
|
if ((where & ~3) == PCI_CLASS_REVISION) {
|
|
if (size <= 2)
|
|
*val = (*val & ((1 << (size * 8)) - 1)) << (8 * (where & 3));
|
|
*val &= ~0xffffff00;
|
|
*val |= PCI_CLASS_BRIDGE_PCI_NORMAL << 8;
|
|
if (size <= 2)
|
|
*val = (*val >> (8 * (where & 3))) & ((1 << (size * 8)) - 1);
|
|
}
|
|
|
|
return PCIBIOS_SUCCESSFUL;
|
|
}
|
|
|
|
static struct pci_ops meson_pci_ops = {
|
|
.map_bus = dw_pcie_own_conf_map_bus,
|
|
.read = meson_pcie_rd_own_conf,
|
|
.write = pci_generic_config_write,
|
|
};
|
|
|
|
static bool meson_pcie_link_up(struct dw_pcie *pci)
|
|
{
|
|
struct meson_pcie *mp = to_meson_pcie(pci);
|
|
u32 state12;
|
|
|
|
state12 = meson_cfg_readl(mp, PCIE_CFG_STATUS12);
|
|
return IS_SMLH_LINK_UP(state12) && IS_RDLH_LINK_UP(state12);
|
|
}
|
|
|
|
static int meson_pcie_host_init(struct dw_pcie_rp *pp)
|
|
{
|
|
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
|
struct meson_pcie *mp = to_meson_pcie(pci);
|
|
|
|
pp->bridge->ops = &meson_pci_ops;
|
|
|
|
meson_set_max_payload(mp, MAX_PAYLOAD_SIZE);
|
|
meson_set_max_rd_req_size(mp, MAX_READ_REQ_SIZE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct dw_pcie_host_ops meson_pcie_host_ops = {
|
|
.init = meson_pcie_host_init,
|
|
};
|
|
|
|
static const struct dw_pcie_ops dw_pcie_ops = {
|
|
.link_up = meson_pcie_link_up,
|
|
.start_link = meson_pcie_start_link,
|
|
};
|
|
|
|
static int meson_pcie_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct dw_pcie *pci;
|
|
struct meson_pcie *mp;
|
|
int ret;
|
|
|
|
mp = devm_kzalloc(dev, sizeof(*mp), GFP_KERNEL);
|
|
if (!mp)
|
|
return -ENOMEM;
|
|
|
|
pci = &mp->pci;
|
|
pci->dev = dev;
|
|
pci->ops = &dw_pcie_ops;
|
|
pci->pp.ops = &meson_pcie_host_ops;
|
|
pci->num_lanes = 1;
|
|
|
|
mp->phy = devm_phy_get(dev, "pcie");
|
|
if (IS_ERR(mp->phy)) {
|
|
dev_err(dev, "get phy failed, %pe\n", mp->phy);
|
|
return PTR_ERR(mp->phy);
|
|
}
|
|
|
|
mp->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
|
|
if (IS_ERR(mp->reset_gpio)) {
|
|
dev_err(dev, "get reset gpio failed\n");
|
|
return PTR_ERR(mp->reset_gpio);
|
|
}
|
|
|
|
ret = meson_pcie_get_resets(mp);
|
|
if (ret) {
|
|
dev_err(dev, "get reset resource failed, %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = meson_pcie_get_mems(pdev, mp);
|
|
if (ret) {
|
|
dev_err(dev, "get memory resource failed, %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = meson_pcie_power_on(mp);
|
|
if (ret) {
|
|
dev_err(dev, "phy power on failed, %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = meson_pcie_reset(mp);
|
|
if (ret) {
|
|
dev_err(dev, "reset failed, %d\n", ret);
|
|
goto err_phy;
|
|
}
|
|
|
|
ret = meson_pcie_probe_clocks(mp);
|
|
if (ret) {
|
|
dev_err(dev, "init clock resources failed, %d\n", ret);
|
|
goto err_phy;
|
|
}
|
|
|
|
platform_set_drvdata(pdev, mp);
|
|
|
|
ret = dw_pcie_host_init(&pci->pp);
|
|
if (ret < 0) {
|
|
dev_err(dev, "Add PCIe port failed, %d\n", ret);
|
|
goto err_phy;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_phy:
|
|
meson_pcie_power_off(mp);
|
|
return ret;
|
|
}
|
|
|
|
static void meson_pcie_remove(struct platform_device *pdev)
|
|
{
|
|
struct meson_pcie *mp = platform_get_drvdata(pdev);
|
|
|
|
dw_pcie_host_deinit(&mp->pci.pp);
|
|
meson_pcie_power_off(mp);
|
|
}
|
|
|
|
static const struct of_device_id meson_pcie_of_match[] = {
|
|
{
|
|
.compatible = "amlogic,axg-pcie",
|
|
},
|
|
{
|
|
.compatible = "amlogic,g12a-pcie",
|
|
},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, meson_pcie_of_match);
|
|
|
|
static struct platform_driver meson_pcie_driver = {
|
|
.probe = meson_pcie_probe,
|
|
.remove = meson_pcie_remove,
|
|
.driver = {
|
|
.name = "meson-pcie",
|
|
.of_match_table = meson_pcie_of_match,
|
|
},
|
|
};
|
|
|
|
module_platform_driver(meson_pcie_driver);
|
|
|
|
MODULE_AUTHOR("Yue Wang <yue.wang@amlogic.com>");
|
|
MODULE_DESCRIPTION("Amlogic PCIe Controller driver");
|
|
MODULE_LICENSE("GPL v2");
|