From: Suneel Garapati <sgarap...@marvell.com> Adds support for PCI ECAM/PEM controllers found on OcteonTX or OcteonTX2 SoC platforms.
Signed-off-by: Suneel Garapati <sgarap...@marvell.com> Reviewed-by: Simon Glass <s...@chromium.org> Cc: Bin Meng <bmeng...@gmail.com> Signed-off-by: Stefan Roese <s...@denx.de> --- Changes in v2: - Add Reviewed-by tag from Simon - Add comments to enum declaration and "type" declaration - Return -EINVAL in default case statement for unsupported length - Add helper function octeontx_bdf_invalid() and return -EPERM in case of an errror - Use livetree API Changes in v1: - Change patch subject - Remove inclusion of common.h - Remove #ifdef's and use driver specific data instead - Add comments to struct - Add some helper functions to reduce code size - Misc coding style changes (blank lines etc) - Use debug() instead of printf() in some cases drivers/pci/Kconfig | 8 + drivers/pci/Makefile | 1 + drivers/pci/pci_octeontx.c | 364 +++++++++++++++++++++++++++++++++++++ 3 files changed, 373 insertions(+) create mode 100644 drivers/pci/pci_octeontx.c diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig index 377806f3b0..06d39df1d3 100644 --- a/drivers/pci/Kconfig +++ b/drivers/pci/Kconfig @@ -149,6 +149,14 @@ config PCI_TEGRA with a total of 5 lanes. Some boards require this for Ethernet support to work (e.g. beaver, jetson-tk1). +config PCI_OCTEONTX + bool "OcteonTX PCI support" + depends on (ARCH_OCTEONTX || ARCH_OCTEONTX2) + help + Enable support for the OcteonTX/TX2 SoC family ECAM/PEM controllers. + These controllers provide PCI configuration access to all on-board + peripherals so it should only be disabled for testing purposes + config PCI_XILINX bool "Xilinx AXI Bridge for PCI Express" depends on DM_PCI diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile index 9db90fb53c..8b4d49a590 100644 --- a/drivers/pci/Makefile +++ b/drivers/pci/Makefile @@ -49,3 +49,4 @@ obj-$(CONFIG_PCI_KEYSTONE) += pcie_dw_ti.o obj-$(CONFIG_PCIE_MEDIATEK) += pcie_mediatek.o obj-$(CONFIG_PCIE_ROCKCHIP) += pcie_rockchip.o obj-$(CONFIG_PCI_BRCMSTB) += pcie_brcmstb.o +obj-$(CONFIG_PCI_OCTEONTX) += pci_octeontx.o diff --git a/drivers/pci/pci_octeontx.c b/drivers/pci/pci_octeontx.c new file mode 100644 index 0000000000..30537543a0 --- /dev/null +++ b/drivers/pci/pci_octeontx.c @@ -0,0 +1,364 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Marvell International Ltd. + * + * https://spdx.org/licenses + */ + +#include <dm.h> +#include <errno.h> +#include <fdtdec.h> +#include <log.h> +#include <malloc.h> +#include <pci.h> + +#include <asm/io.h> + +#include <linux/ioport.h> + +DECLARE_GLOBAL_DATA_PTR; + +/* + * This driver supports multiple types of operations / host bridges / busses: + * + * OTX_ECAM: Octeon TX & TX2 ECAM (Enhanced Configuration Access Mechanism) + * Used to access the internal on-chip devices which are connected + * to internal buses + * OTX_PEM: Octeon TX PEM (PCI Express MAC) + * Used to access the external (off-chip) PCI devices + * OTX2_PEM: Octeon TX2 PEM (PCI Express MAC) + * Used to access the external (off-chip) PCI devices + */ +enum { + OTX_ECAM, + OTX_PEM, + OTX2_PEM, +}; + +/** + * struct octeontx_pci - Driver private data + * @type: Device type matched via compatible (e.g. OTX_ECAM etc) + * @cfg: Config resource + * @bus: Bus resource + */ +struct octeontx_pci { + unsigned int type; + + struct resource cfg; + struct resource bus; +}; + +static uintptr_t octeontx_cfg_addr(struct octeontx_pci *pcie, + int bus_offs, int shift_offs, + pci_dev_t bdf, uint offset) +{ + u32 bus, dev, func; + uintptr_t address; + + bus = PCI_BUS(bdf) + bus_offs; + dev = PCI_DEV(bdf); + func = PCI_FUNC(bdf); + + address = (bus << (20 + shift_offs)) | + (dev << (15 + shift_offs)) | + (func << (12 + shift_offs)) | offset; + address += pcie->cfg.start; + + return address; +} + +static ulong readl_size(uintptr_t addr, enum pci_size_t size) +{ + ulong val; + + switch (size) { + case PCI_SIZE_8: + val = readb(addr); + break; + case PCI_SIZE_16: + val = readw(addr); + break; + case PCI_SIZE_32: + val = readl(addr); + break; + default: + printf("Invalid size\n"); + return -EINVAL; + }; + + return val; +} + +static void writel_size(uintptr_t addr, enum pci_size_t size, ulong valuep) +{ + switch (size) { + case PCI_SIZE_8: + writeb(valuep, addr); + break; + case PCI_SIZE_16: + writew(valuep, addr); + break; + case PCI_SIZE_32: + writel(valuep, addr); + break; + default: + printf("Invalid size\n"); + }; +} + +static bool octeontx_bdf_invalid(pci_dev_t bdf) +{ + if (PCI_BUS(bdf) == 1 && PCI_DEV(bdf) > 0) + return true; + + return false; +} + +static int octeontx_ecam_read_config(const struct udevice *bus, pci_dev_t bdf, + uint offset, ulong *valuep, + enum pci_size_t size) +{ + struct octeontx_pci *pcie = (struct octeontx_pci *)dev_get_priv(bus); + struct pci_controller *hose = dev_get_uclass_priv(bus); + uintptr_t address; + + address = octeontx_cfg_addr(pcie, pcie->bus.start - hose->first_busno, + 0, bdf, offset); + *valuep = readl_size(address, size); + + debug("%02x.%02x.%02x: u%d %x -> %lx\n", + PCI_BUS(bdf), PCI_DEV(bdf), PCI_FUNC(bdf), size, offset, *valuep); + + return 0; +} + +static int octeontx_ecam_write_config(struct udevice *bus, pci_dev_t bdf, + uint offset, ulong value, + enum pci_size_t size) +{ + struct octeontx_pci *pcie = (struct octeontx_pci *)dev_get_priv(bus); + struct pci_controller *hose = dev_get_uclass_priv(bus); + uintptr_t address; + + address = octeontx_cfg_addr(pcie, pcie->bus.start - hose->first_busno, + 0, bdf, offset); + writel_size(address, size, value); + + debug("%02x.%02x.%02x: u%d %x <- %lx\n", + PCI_BUS(bdf), PCI_DEV(bdf), PCI_FUNC(bdf), size, offset, value); + + return 0; +} + +static int octeontx_pem_read_config(const struct udevice *bus, pci_dev_t bdf, + uint offset, ulong *valuep, + enum pci_size_t size) +{ + struct octeontx_pci *pcie = (struct octeontx_pci *)dev_get_priv(bus); + struct pci_controller *hose = dev_get_uclass_priv(bus); + uintptr_t address; + u8 hdrtype; + u8 pri_bus = pcie->bus.start + 1 - hose->first_busno; + u32 bus_offs = (pri_bus << 16) | (pri_bus << 8) | (pri_bus << 0); + + address = octeontx_cfg_addr(pcie, 1 - hose->first_busno, 4, + bdf, 0); + + *valuep = pci_conv_32_to_size(~0UL, offset, size); + + if (octeontx_bdf_invalid(bdf)) + return -EPERM; + + *valuep = readl_size(address + offset, size); + + hdrtype = readb(address + PCI_HEADER_TYPE); + if (hdrtype == PCI_HEADER_TYPE_BRIDGE && + offset >= PCI_PRIMARY_BUS && + offset <= PCI_SUBORDINATE_BUS && + *valuep != pci_conv_32_to_size(~0UL, offset, size)) + *valuep -= pci_conv_32_to_size(bus_offs, offset, size); + + return 0; +} + +static int octeontx_pem_write_config(struct udevice *bus, pci_dev_t bdf, + uint offset, ulong value, + enum pci_size_t size) +{ + struct octeontx_pci *pcie = (struct octeontx_pci *)dev_get_priv(bus); + struct pci_controller *hose = dev_get_uclass_priv(bus); + uintptr_t address; + u8 hdrtype; + u8 pri_bus = pcie->bus.start + 1 - hose->first_busno; + u32 bus_offs = (pri_bus << 16) | (pri_bus << 8) | (pri_bus << 0); + + address = octeontx_cfg_addr(pcie, 1 - hose->first_busno, 4, bdf, 0); + + hdrtype = readb(address + PCI_HEADER_TYPE); + if (hdrtype == PCI_HEADER_TYPE_BRIDGE && + offset >= PCI_PRIMARY_BUS && + offset <= PCI_SUBORDINATE_BUS && + value != pci_conv_32_to_size(~0UL, offset, size)) + value += pci_conv_32_to_size(bus_offs, offset, size); + + if (octeontx_bdf_invalid(bdf)) + return -EPERM; + + writel_size(address + offset, size, value); + + debug("%02x.%02x.%02x: u%d %x (%lx) <- %lx\n", + PCI_BUS(bdf), PCI_DEV(bdf), PCI_FUNC(bdf), size, offset, + address, value); + + return 0; +} + +static int octeontx2_pem_read_config(const struct udevice *bus, pci_dev_t bdf, + uint offset, ulong *valuep, + enum pci_size_t size) +{ + struct octeontx_pci *pcie = (struct octeontx_pci *)dev_get_priv(bus); + struct pci_controller *hose = dev_get_uclass_priv(bus); + uintptr_t address; + + address = octeontx_cfg_addr(pcie, 1 - hose->first_busno, 0, + bdf, 0); + + *valuep = pci_conv_32_to_size(~0UL, offset, size); + + if (octeontx_bdf_invalid(bdf)) + return -EPERM; + + *valuep = readl_size(address + offset, size); + + debug("%02x.%02x.%02x: u%d %x (%lx) -> %lx\n", + PCI_BUS(bdf), PCI_DEV(bdf), PCI_FUNC(bdf), size, offset, + address, *valuep); + + return 0; +} + +static int octeontx2_pem_write_config(struct udevice *bus, pci_dev_t bdf, + uint offset, ulong value, + enum pci_size_t size) +{ + struct octeontx_pci *pcie = (struct octeontx_pci *)dev_get_priv(bus); + struct pci_controller *hose = dev_get_uclass_priv(bus); + uintptr_t address; + + address = octeontx_cfg_addr(pcie, 1 - hose->first_busno, 0, + bdf, 0); + + if (octeontx_bdf_invalid(bdf)) + return -EPERM; + + writel_size(address + offset, size, value); + + debug("%02x.%02x.%02x: u%d %x (%lx) <- %lx\n", + PCI_BUS(bdf), PCI_DEV(bdf), PCI_FUNC(bdf), size, offset, + address, value); + + return 0; +} + +int pci_octeontx_read_config(const struct udevice *bus, pci_dev_t bdf, + uint offset, ulong *valuep, + enum pci_size_t size) +{ + struct octeontx_pci *pcie = (struct octeontx_pci *)dev_get_priv(bus); + int ret = -EIO; + + switch (pcie->type) { + case OTX_ECAM: + ret = octeontx_ecam_read_config(bus, bdf, offset, valuep, + size); + break; + case OTX_PEM: + ret = octeontx_pem_read_config(bus, bdf, offset, valuep, + size); + break; + case OTX2_PEM: + ret = octeontx2_pem_read_config(bus, bdf, offset, valuep, + size); + break; + } + + return ret; +} + +int pci_octeontx_write_config(struct udevice *bus, pci_dev_t bdf, + uint offset, ulong value, + enum pci_size_t size) +{ + struct octeontx_pci *pcie = (struct octeontx_pci *)dev_get_priv(bus); + int ret = -EIO; + + switch (pcie->type) { + case OTX_ECAM: + ret = octeontx_ecam_write_config(bus, bdf, offset, value, + size); + break; + case OTX_PEM: + ret = octeontx_pem_write_config(bus, bdf, offset, value, + size); + break; + case OTX2_PEM: + ret = octeontx2_pem_write_config(bus, bdf, offset, value, + size); + break; + } + + return ret; +} + +static int pci_octeontx_ofdata_to_platdata(struct udevice *dev) +{ + return 0; +} + +static int pci_octeontx_probe(struct udevice *dev) +{ + struct octeontx_pci *pcie = (struct octeontx_pci *)dev_get_priv(dev); + int err; + + pcie->type = dev_get_driver_data(dev); + + err = dev_read_resource(dev, 0, &pcie->cfg); + if (err) { + debug("Error reading resource: %s\n", fdt_strerror(err)); + return err; + } + + err = dev_read_pci_bus_range(dev, &pcie->bus); + if (err) { + debug("Error reading resource: %s\n", fdt_strerror(err)); + return err; + } + + return 0; +} + +static const struct dm_pci_ops pci_octeontx_ops = { + .read_config = pci_octeontx_read_config, + .write_config = pci_octeontx_write_config, +}; + +static const struct udevice_id pci_octeontx_ids[] = { + { .compatible = "cavium,pci-host-thunder-ecam", .data = OTX_ECAM }, + { .compatible = "cavium,pci-host-octeontx-ecam", .data = OTX_ECAM }, + { .compatible = "pci-host-ecam-generic", .data = OTX_ECAM }, + { .compatible = "cavium,pci-host-thunder-pem", .data = OTX_PEM }, + { .compatible = "marvell,pci-host-octeontx2-pem", .data = OTX2_PEM }, + { } +}; + +U_BOOT_DRIVER(pci_octeontx) = { + .name = "pci_octeontx", + .id = UCLASS_PCI, + .of_match = pci_octeontx_ids, + .ops = &pci_octeontx_ops, + .ofdata_to_platdata = pci_octeontx_ofdata_to_platdata, + .probe = pci_octeontx_probe, + .priv_auto_alloc_size = sizeof(struct octeontx_pci), + .flags = DM_FLAG_PRE_RELOC, +}; -- 2.28.0