mirror of
https://source.denx.de/u-boot/u-boot.git
synced 2026-05-06 05:06:13 +02:00
Merge patch series "Add TI K3 PCIe Endpoint Controller support for AM64X"
Hrushikesh Salunke <h-salunke@ti.com> says: This series adds support for the Endpoint mode on Cadence PCIe controller on TI's K3 family of SoCs. The driver is an adaptation of the Linux driver (drivers/pci/controller/cadence/pci-j721e.c) and has been implemented specifically for Endpoint mode of operation on AM64X. A minor set of changes will be sufficient to support other K3 SoCs as well. This patch is tested on AM64X EVM. Following are the log corresponding to this feature. https://gist.github.com/hrushikesh221/e8557cbe7667877c50f7d7e9bb96d060 Link: https://lore.kernel.org/r/20250616164929.631791-1-h-salunke@ti.com
This commit is contained in:
commit
231b56fd9b
@ -143,6 +143,8 @@ CONFIG_TI_ICSSG_PRUETH=y
|
||||
CONFIG_NVME_PCI=y
|
||||
CONFIG_PCI_CONFIG_HOST_BRIDGE=y
|
||||
CONFIG_PCIE_CDNS_TI=y
|
||||
CONFIG_PCI_ENDPOINT=y
|
||||
CONFIG_PCIE_CDNS_TI_EP=y
|
||||
CONFIG_PHY=y
|
||||
CONFIG_SPL_PHY=y
|
||||
CONFIG_PHY_CADENCE_TORRENT=y
|
||||
|
||||
@ -22,6 +22,12 @@ config PCIE_CADENCE_EP
|
||||
endpoint mode. This PCIe controller may be embedded into many
|
||||
different vendors SoCs.
|
||||
|
||||
config PCIE_CDNS_TI_EP
|
||||
bool "TI K3 PCIe EP support"
|
||||
help
|
||||
Say Y here to enable support for the Canence PCIe Controller
|
||||
in Endpoint Mode on TI's K3 Socs.
|
||||
|
||||
config PCI_SANDBOX_EP
|
||||
bool "Sandbox PCIe endpoint controller"
|
||||
depends on PCI_ENDPOINT
|
||||
|
||||
@ -6,3 +6,4 @@
|
||||
obj-y += pci_ep-uclass.o
|
||||
obj-$(CONFIG_PCIE_CADENCE_EP) += pcie-cadence-ep.o
|
||||
obj-$(CONFIG_PCI_SANDBOX_EP) += sandbox-pci_ep.o
|
||||
obj-$(CONFIG_PCIE_CDNS_TI_EP) += pcie_cdns_ti_ep.o
|
||||
|
||||
395
drivers/pci_endpoint/pcie_cdns_ti_ep.c
Normal file
395
drivers/pci_endpoint/pcie_cdns_ti_ep.c
Normal file
@ -0,0 +1,395 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR MIT
|
||||
/*
|
||||
* Copyright (C) 2025 Texas Instruments Incorporated - https://www.ti.com
|
||||
*
|
||||
* PCIe Endpoint controller driver for TI's K3 SoCs with Cadence PCIe controller
|
||||
*
|
||||
* Ported from the Linux driver - drivers/pci/controller/cadence/pci-j721e.c
|
||||
*
|
||||
* Author: Hrushikesh Salunke <h-salunke@ti.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <clk.h>
|
||||
#include <dm.h>
|
||||
#include <dm/device_compat.h>
|
||||
#include <generic-phy.h>
|
||||
#include <linux/log2.h>
|
||||
#include <linux/sizes.h>
|
||||
#include <power-domain.h>
|
||||
#include <regmap.h>
|
||||
#include <syscon.h>
|
||||
#include <pcie-cadence.h>
|
||||
#include <pci_ep.h>
|
||||
|
||||
#define PCIE_USER_CMD_STATUS_REG_OFFSET 0x4
|
||||
#define LINK_TRAINING_ENABLE BIT(0)
|
||||
|
||||
#define PCIE_MODE_SEL_MASK BIT(7)
|
||||
#define PCIE_GEN_SEL_MASK GENMASK(1, 0)
|
||||
#define PCIE_LINK_WIDTH_MASK GENMASK(9, 8)
|
||||
|
||||
struct pcie_cdns_ti_ep_data {
|
||||
unsigned int quirk_retrain_flag:1;
|
||||
unsigned int quirk_detect_quiet_flag:1;
|
||||
unsigned int quirk_disable_flr:1;
|
||||
unsigned int byte_access_allowed:1;
|
||||
unsigned int max_lanes;
|
||||
};
|
||||
|
||||
struct pcie_cdns_ti_ep {
|
||||
struct udevice *dev;
|
||||
void __iomem *intd_cfg_base;
|
||||
void __iomem *user_cfg_base;
|
||||
void __iomem *reg_base;
|
||||
void __iomem *mem_base;
|
||||
fdt_size_t cfg_size;
|
||||
struct regmap *syscon_base;
|
||||
u32 max_link_speed;
|
||||
u32 num_lanes;
|
||||
u32 pcie_ctrl_offset;
|
||||
unsigned int quirk_retrain_flag:1;
|
||||
unsigned int quirk_detect_quiet_flag:1;
|
||||
unsigned int quirk_disable_flr:1;
|
||||
unsigned int byte_access_allowed:1;
|
||||
};
|
||||
|
||||
static inline u32 pcie_cdns_ti_ep_user_readl(struct pcie_cdns_ti_ep *pcie, u32 offset)
|
||||
{
|
||||
return readl(pcie->user_cfg_base + offset);
|
||||
}
|
||||
|
||||
static inline void pcie_cdns_ti_ep_user_writel(struct pcie_cdns_ti_ep *pcie, u32 offset,
|
||||
u32 val)
|
||||
{
|
||||
writel(val, pcie->user_cfg_base + offset);
|
||||
}
|
||||
|
||||
static void pcie_cdns_ti_start_link(struct pcie_cdns_ti_ep *pcie)
|
||||
{
|
||||
u32 reg;
|
||||
|
||||
reg = pcie_cdns_ti_ep_user_readl(pcie, PCIE_USER_CMD_STATUS_REG_OFFSET);
|
||||
reg |= LINK_TRAINING_ENABLE;
|
||||
pcie_cdns_ti_ep_user_writel(pcie, PCIE_USER_CMD_STATUS_REG_OFFSET, reg);
|
||||
}
|
||||
|
||||
static int pcie_cdns_reset(struct udevice *dev, struct power_domain *pci_pwrdmn)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = power_domain_off(pci_pwrdmn);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to power off\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = power_domain_on(pci_pwrdmn);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to power on: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pcie_cdns_config_serdes(struct udevice *dev)
|
||||
{
|
||||
if (CONFIG_IS_ENABLED(PHY_CADENCE_TORRENT)) {
|
||||
struct phy serdes;
|
||||
int ret = 7;
|
||||
|
||||
ret = generic_phy_get_by_name(dev, "pcie-phy", &serdes);
|
||||
if (ret != 0 && ret != -EBUSY) {
|
||||
dev_err(dev, "unable to get serdes\n");
|
||||
return ret;
|
||||
}
|
||||
generic_phy_reset(&serdes);
|
||||
generic_phy_init(&serdes);
|
||||
generic_phy_power_on(&serdes);
|
||||
} else {
|
||||
dev_info(dev, "Proceeding with the assumption that the SERDES is already configured\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pcie_cdns_ti_ctrl_init(struct pcie_cdns_ti_ep *pcie)
|
||||
{
|
||||
struct regmap *syscon = pcie->syscon_base;
|
||||
u32 val = 0;
|
||||
|
||||
/* Set mode of operation */
|
||||
regmap_update_bits(syscon, pcie->pcie_ctrl_offset, PCIE_MODE_SEL_MASK,
|
||||
val);
|
||||
|
||||
/* Set link speed */
|
||||
regmap_update_bits(syscon, pcie->pcie_ctrl_offset, PCIE_GEN_SEL_MASK,
|
||||
pcie->max_link_speed - 1);
|
||||
|
||||
/* Set link width */
|
||||
regmap_update_bits(syscon, pcie->pcie_ctrl_offset, PCIE_LINK_WIDTH_MASK,
|
||||
(pcie->num_lanes - 1) << 8);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pcie_cdns_ti_write_header(struct udevice *dev, uint fn,
|
||||
struct pci_ep_header *hdr)
|
||||
{
|
||||
struct pcie_cdns_ti_ep *pcie_ep = dev_get_priv(dev);
|
||||
struct cdns_pcie pcie;
|
||||
|
||||
pcie.reg_base = pcie_ep->reg_base;
|
||||
|
||||
cdns_pcie_ep_fn_writew(&pcie, fn, PCI_DEVICE_ID, hdr->deviceid);
|
||||
cdns_pcie_ep_fn_writeb(&pcie, fn, PCI_REVISION_ID, hdr->revid);
|
||||
cdns_pcie_ep_fn_writeb(&pcie, fn, PCI_CLASS_PROG,
|
||||
hdr->progif_code);
|
||||
cdns_pcie_ep_fn_writew(&pcie, fn, PCI_CLASS_DEVICE,
|
||||
hdr->subclass_code |
|
||||
hdr->baseclass_code << 8);
|
||||
cdns_pcie_ep_fn_writeb(&pcie, fn, PCI_CACHE_LINE_SIZE,
|
||||
hdr->cache_line_size);
|
||||
cdns_pcie_ep_fn_writew(&pcie, fn, PCI_SUBSYSTEM_ID,
|
||||
hdr->subsys_id);
|
||||
cdns_pcie_ep_fn_writeb(&pcie, fn, PCI_INTERRUPT_PIN,
|
||||
hdr->interrupt_pin);
|
||||
|
||||
/*
|
||||
* Vendor ID can only be modified from function 0, all other functions
|
||||
* use the same vendor ID as function 0.
|
||||
*/
|
||||
if (fn == 0) {
|
||||
/* Update the vendor IDs. */
|
||||
u32 id = CDNS_PCIE_LM_ID_VENDOR(hdr->vendorid) |
|
||||
CDNS_PCIE_LM_ID_SUBSYS(hdr->subsys_vendor_id);
|
||||
|
||||
cdns_pcie_writel(&pcie, CDNS_PCIE_LM_ID, id);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pcie_cdns_ti_set_bar(struct udevice *dev, uint fn,
|
||||
struct pci_bar *ep_bar)
|
||||
{
|
||||
struct pcie_cdns_ti_ep *pcie_ep = dev_get_priv(dev);
|
||||
struct cdns_pcie pcie;
|
||||
dma_addr_t bar_phys = ep_bar->phys_addr;
|
||||
enum pci_barno bar = ep_bar->barno;
|
||||
int flags = ep_bar->flags;
|
||||
u32 addr0, addr1, reg, cfg, b, aperture, ctrl;
|
||||
u64 sz;
|
||||
|
||||
pcie.reg_base = pcie_ep->reg_base;
|
||||
|
||||
/* BAR size is 2^(aperture + 7) */
|
||||
sz = max_t(size_t, ep_bar->size, CDNS_PCIE_EP_MIN_APERTURE);
|
||||
/*
|
||||
* roundup_pow_of_two() returns an unsigned long, which is not suited
|
||||
* for 64bit values.
|
||||
*/
|
||||
sz = 1ULL << fls64(sz - 1);
|
||||
aperture = ilog2(sz) - 7; /* 128B -> 0, 256B -> 1, 512B -> 2, ... */
|
||||
|
||||
if ((flags & PCI_BASE_ADDRESS_SPACE) == PCI_BASE_ADDRESS_SPACE_IO) {
|
||||
ctrl = CDNS_PCIE_LM_BAR_CFG_CTRL_IO_32BITS;
|
||||
} else {
|
||||
bool is_prefetch = !!(flags & PCI_BASE_ADDRESS_MEM_PREFETCH);
|
||||
bool is_64bits = (sz > SZ_2G) |
|
||||
!!(ep_bar->flags & PCI_BASE_ADDRESS_MEM_TYPE_64);
|
||||
|
||||
if (is_64bits && (bar & 1))
|
||||
return -EINVAL;
|
||||
|
||||
if (is_64bits && !(flags & PCI_BASE_ADDRESS_MEM_TYPE_64))
|
||||
ep_bar->flags |= PCI_BASE_ADDRESS_MEM_TYPE_64;
|
||||
|
||||
if (is_64bits && is_prefetch)
|
||||
ctrl = CDNS_PCIE_LM_BAR_CFG_CTRL_PREFETCH_MEM_64BITS;
|
||||
else if (is_prefetch)
|
||||
ctrl = CDNS_PCIE_LM_BAR_CFG_CTRL_PREFETCH_MEM_32BITS;
|
||||
else if (is_64bits)
|
||||
ctrl = CDNS_PCIE_LM_BAR_CFG_CTRL_MEM_64BITS;
|
||||
else
|
||||
ctrl = CDNS_PCIE_LM_BAR_CFG_CTRL_MEM_32BITS;
|
||||
}
|
||||
|
||||
addr0 = lower_32_bits(bar_phys);
|
||||
addr1 = upper_32_bits(bar_phys);
|
||||
cdns_pcie_writel(&pcie, CDNS_PCIE_AT_IB_EP_FUNC_BAR_ADDR0(fn, bar),
|
||||
addr0);
|
||||
cdns_pcie_writel(&pcie, CDNS_PCIE_AT_IB_EP_FUNC_BAR_ADDR1(fn, bar),
|
||||
addr1);
|
||||
|
||||
/*
|
||||
* Cadence PCIe controller provides a register interface to configure
|
||||
* BAR of an Endpoint function. Per function there are two BAR configuration
|
||||
* registers, out of which first is used to configure BAR_0 to BAR_4 and
|
||||
* second is used to configure the remaining BARs.
|
||||
*/
|
||||
if (bar < BAR_4) {
|
||||
reg = CDNS_PCIE_LM_EP_FUNC_BAR_CFG0(fn);
|
||||
b = bar;
|
||||
} else {
|
||||
reg = CDNS_PCIE_LM_EP_FUNC_BAR_CFG1(fn);
|
||||
b = bar - BAR_4;
|
||||
}
|
||||
|
||||
cfg = cdns_pcie_readl(&pcie, reg);
|
||||
|
||||
cfg &= ~(CDNS_PCIE_LM_EP_FUNC_BAR_CFG_BAR_APERTURE_MASK(b) |
|
||||
CDNS_PCIE_LM_EP_FUNC_BAR_CFG_BAR_CTRL_MASK(b));
|
||||
cfg |= (CDNS_PCIE_LM_EP_FUNC_BAR_CFG_BAR_APERTURE(b, aperture) |
|
||||
CDNS_PCIE_LM_EP_FUNC_BAR_CFG_BAR_CTRL(b, ctrl));
|
||||
cdns_pcie_writel(&pcie, reg, cfg);
|
||||
|
||||
cfg = cdns_pcie_readl(&pcie, reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pcie_cdns_ti_start(struct udevice *dev)
|
||||
{
|
||||
struct pcie_cdns_ti_ep *pcie = dev_get_priv(dev);
|
||||
|
||||
pcie_cdns_ti_start_link(pcie);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pcie_cdns_ti_ep_probe(struct udevice *dev)
|
||||
{
|
||||
struct pcie_cdns_ti_ep *pcie = dev_get_priv(dev);
|
||||
struct pcie_cdns_ti_ep_data *data;
|
||||
struct power_domain pci_pwrdmn;
|
||||
struct clk *clk;
|
||||
int ret;
|
||||
|
||||
pcie->dev = dev;
|
||||
data = (struct pcie_cdns_ti_ep_data *)dev_get_driver_data(dev);
|
||||
if (!data)
|
||||
return -EINVAL;
|
||||
|
||||
pcie->quirk_retrain_flag = data->quirk_retrain_flag;
|
||||
pcie->quirk_detect_quiet_flag = data->quirk_detect_quiet_flag;
|
||||
pcie->quirk_disable_flr = data->quirk_disable_flr;
|
||||
|
||||
if (pcie->num_lanes > data->max_lanes) {
|
||||
dev_warn(dev, "cannot support %d lanes, defaulting to %d\n",
|
||||
pcie->num_lanes, data->max_lanes);
|
||||
pcie->num_lanes = data->max_lanes;
|
||||
}
|
||||
|
||||
ret = power_domain_get_by_index(dev, &pci_pwrdmn, 0);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to get power domain: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Reset the PCIe controller so that newly configured BAR
|
||||
* values are reflected.
|
||||
*/
|
||||
ret = pcie_cdns_reset(dev, &pci_pwrdmn);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to reset controller: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
clk = devm_clk_get(dev, "fck");
|
||||
if (IS_ERR(clk)) {
|
||||
ret = PTR_ERR(clk);
|
||||
dev_err(dev, "failed to get functional clock\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = pcie_cdns_config_serdes(dev);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to configure serdes: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = pcie_cdns_ti_ctrl_init(pcie);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to initialize controller: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pcie_cdns_ti_ep_of_to_plat(struct udevice *dev)
|
||||
{
|
||||
struct pcie_cdns_ti_ep *pcie = dev_get_priv(dev);
|
||||
struct regmap *syscon;
|
||||
u32 offset;
|
||||
int ret;
|
||||
|
||||
pcie->intd_cfg_base = dev_remap_addr_name(dev, "intd_cfg");
|
||||
if (!pcie->intd_cfg_base)
|
||||
return -EINVAL;
|
||||
|
||||
pcie->user_cfg_base = dev_remap_addr_name(dev, "user_cfg");
|
||||
if (!pcie->user_cfg_base)
|
||||
return -EINVAL;
|
||||
|
||||
pcie->reg_base = dev_remap_addr_name(dev, "reg");
|
||||
if (!pcie->reg_base)
|
||||
return -EINVAL;
|
||||
|
||||
pcie->mem_base = dev_remap_addr_name(dev, "mem");
|
||||
if (!pcie->mem_base)
|
||||
return -EINVAL;
|
||||
|
||||
ret = dev_read_u32(dev, "num-lanes", &pcie->num_lanes);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = dev_read_u32(dev, "max-link-speed", &pcie->max_link_speed);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
syscon = syscon_regmap_lookup_by_phandle(dev, "ti,syscon-pcie-ctrl");
|
||||
if (IS_ERR(syscon)) {
|
||||
if (PTR_ERR(syscon) == -ENODEV)
|
||||
return 0;
|
||||
return PTR_ERR(syscon);
|
||||
}
|
||||
|
||||
ret = dev_read_u32_index(dev, "ti,syscon-pcie-ctrl", 1, &offset);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
pcie->syscon_base = syscon;
|
||||
pcie->pcie_ctrl_offset = offset;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct pci_ep_ops pcie_cdns_ti_ep_ops = {
|
||||
.write_header = pcie_cdns_ti_write_header,
|
||||
.set_bar = pcie_cdns_ti_set_bar,
|
||||
.start = pcie_cdns_ti_start,
|
||||
};
|
||||
|
||||
static const struct pcie_cdns_ti_ep_data am64_pcie_ep_data = {
|
||||
.max_lanes = 1,
|
||||
};
|
||||
|
||||
static const struct udevice_id pcie_cdns_ti_ep_ids[] = {
|
||||
{
|
||||
.compatible = "ti,am64-pcie-ep",
|
||||
.data = (ulong)&am64_pcie_ep_data,
|
||||
},
|
||||
{},
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(pcie_cdns_ti_ep) = {
|
||||
.name = "pcie_cdns_ti_ep",
|
||||
.id = UCLASS_PCI_EP,
|
||||
.of_match = pcie_cdns_ti_ep_ids,
|
||||
.ops = &pcie_cdns_ti_ep_ops,
|
||||
.of_to_plat = pcie_cdns_ti_ep_of_to_plat,
|
||||
.probe = pcie_cdns_ti_ep_probe,
|
||||
.priv_auto = sizeof(struct pcie_cdns_ti_ep),
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user