From 26abae3f17644c606271c829b0b3e9332422bd22 Mon Sep 17 00:00:00 2001 From: Michal Meloun Date: Sun, 20 Oct 2019 11:11:32 +0000 Subject: [PATCH] Add driver for DesignWare PCIE core, and its Armada 8K specific attachement. MFC after: 3 weeks --- sys/conf/files.arm64 | 3 + sys/dev/pci/pci_dw.c | 697 ++++++++++++++++++++++++++++++++++++++++ sys/dev/pci/pci_dw.h | 158 +++++++++ sys/dev/pci/pci_dw_if.m | 73 +++++ sys/dev/pci/pci_dw_mv.c | 328 +++++++++++++++++++ 5 files changed, 1259 insertions(+) create mode 100644 sys/dev/pci/pci_dw.c create mode 100644 sys/dev/pci/pci_dw.h create mode 100644 sys/dev/pci/pci_dw_if.m create mode 100644 sys/dev/pci/pci_dw_mv.c diff --git a/sys/conf/files.arm64 b/sys/conf/files.arm64 index ac05e28b77e6..4e465d306116 100644 --- a/sys/conf/files.arm64 +++ b/sys/conf/files.arm64 @@ -246,6 +246,9 @@ dev/ofw/ofwpci.c optional fdt pci dev/pci/pci_host_generic.c optional pci dev/pci/pci_host_generic_acpi.c optional pci acpi dev/pci/pci_host_generic_fdt.c optional pci fdt +dev/pci/pci_dw_mv.c optional pci fdt +dev/pci/pci_dw.c optional pci fdt +dev/pci/pci_dw_if.m optional pci fdt dev/psci/psci.c standard dev/psci/smccc_arm64.S standard dev/psci/smccc.c standard diff --git a/sys/dev/pci/pci_dw.c b/sys/dev/pci/pci_dw.c new file mode 100644 index 000000000000..de9bc354d283 --- /dev/null +++ b/sys/dev/pci/pci_dw.c @@ -0,0 +1,697 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2019 Michal Meloun + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +/* Base class for all Synopsys DesignWare PCI/PCIe drivers */ + +#include +__FBSDID("$FreeBSD$"); + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pcib_if.h" +#include "pci_dw_if.h" + +#define DEBUG +#ifdef DEBUG +#define debugf(fmt, args...) do { printf(fmt,##args); } while (0) +#else +#define debugf(fmt, args...) +#endif + +#define DBI_WR1(sc, reg, val) pci_dw_dbi_wr1((sc)->dev, reg, val) +#define DBI_WR2(sc, reg, val) pci_dw_dbi_wr2((sc)->dev, reg, val) +#define DBI_WR4(sc, reg, val) pci_dw_dbi_wr4((sc)->dev, reg, val) +#define DBI_RD1(sc, reg) pci_dw_dbi_rd1((sc)->dev, reg) +#define DBI_RD2(sc, reg) pci_dw_dbi_rd2((sc)->dev, reg) +#define DBI_RD4(sc, reg) pci_dw_dbi_rd4((sc)->dev, reg) + +#define PCI_BUS_SHIFT 20 +#define PCI_SLOT_SHIFT 15 +#define PCI_FUNC_SHIFT 12 +#define PCI_BUS_MASK 0xFF +#define PCI_SLOT_MASK 0x1F +#define PCI_FUNC_MASK 0x07 +#define PCI_REG_MASK 0xFFF + + +#define IATU_CFG_BUS(bus) ((uint64_t)((bus) & 0xff) << 24) +#define IATU_CFG_SLOT(slot) ((uint64_t)((slot) & 0x1f) << 19) +#define IATU_CFG_FUNC(func) ((uint64_t)((func) & 0x07) << 16) + + + +static uint32_t +pci_dw_dbi_read(device_t dev, u_int reg, int width) +{ + struct pci_dw_softc *sc; + + sc = device_get_softc(dev); + MPASS(sc->dbi_res != NULL); + + switch (width) { + case 4: + return (bus_read_4(sc->dbi_res, reg)); + case 2: + return (bus_read_2(sc->dbi_res, reg)); + case 1: + return (bus_read_1(sc->dbi_res, reg)); + default: + device_printf(sc->dev, "Unsupported width: %d\n", width); + return (0xFFFFFFFF); + } +} + +static void +pci_dw_dbi_write(device_t dev, u_int reg, uint32_t val, int width) +{ + struct pci_dw_softc *sc; + + sc = device_get_softc(dev); + MPASS(sc->dbi_res != NULL); + + switch (width) { + case 4: + bus_write_4(sc->dbi_res, reg, val); + break; + case 2: + bus_write_2(sc->dbi_res, reg, val); + break; + case 1: + bus_write_1(sc->dbi_res, reg, val); + break; + default: + device_printf(sc->dev, "Unsupported width: %d\n", width); + break; + } +} + + +static void +pci_dw_dbi_protect(struct pci_dw_softc *sc, bool protect) +{ + uint32_t reg; + + reg = DBI_RD4(sc, DW_MISC_CONTROL_1); + if (protect) + reg &= ~DBI_RO_WR_EN; + else + reg |= DBI_RO_WR_EN; + DBI_WR4(sc, DW_MISC_CONTROL_1, reg); +} + +static bool +pci_dw_check_dev(struct pci_dw_softc *sc, u_int bus, u_int slot, u_int func, + u_int reg) +{ + bool status; + int rv; + + if (bus < sc->bus_start || bus > sc->bus_end || slot > PCI_SLOTMAX || + func > PCI_FUNCMAX || reg > PCI_REGMAX) + return (false); + + /* link is needed for access to all non-root busses */ + if (bus != sc->root_bus) { + rv = PCI_DW_GET_LINK(sc->dev, &status); + if (rv != 0 || !status) + return (false); + return (true); + } + + /* we have only 1 device with 1 function root port */ + if (slot > 0 || func > 0) + return (false); + return (true); +} + +/* Map one uoutbound ATU region */ +static int +pci_dw_map_out_atu(struct pci_dw_softc *sc, int idx, int type, + uint64_t pa, uint64_t pci_addr, uint32_t size) +{ + uint32_t reg; + int i; + + if (size == 0) + return (0); + + DBI_WR4(sc, DW_IATU_VIEWPORT, IATU_REGION_INDEX(idx)); + DBI_WR4(sc, DW_IATU_LWR_BASE_ADDR, pa & 0xFFFFFFFF); + DBI_WR4(sc, DW_IATU_UPPER_BASE_ADDR, (pa >> 32) & 0xFFFFFFFF); + DBI_WR4(sc, DW_IATU_LIMIT_ADDR, (pa + size - 1) & 0xFFFFFFFF); + DBI_WR4(sc, DW_IATU_LWR_TARGET_ADDR, pci_addr & 0xFFFFFFFF); + DBI_WR4(sc, DW_IATU_UPPER_TARGET_ADDR, (pci_addr >> 32) & 0xFFFFFFFF); + DBI_WR4(sc, DW_IATU_CTRL1, IATU_CTRL1_TYPE(type)); + DBI_WR4(sc, DW_IATU_CTRL2, IATU_CTRL2_REGION_EN); + + /* Wait until setup becomes valid */ + for (i = 10; i > 0; i--) { + reg = DBI_RD4(sc, DW_IATU_CTRL2); + if (reg & IATU_CTRL2_REGION_EN) + return (0); + DELAY(5); + } + device_printf(sc->dev, + "Cannot map outbound region(%d) in iATU\n", idx); + return (ETIMEDOUT); +} + +static int +pci_dw_setup_hw(struct pci_dw_softc *sc) +{ + uint32_t reg; + int rv; + + pci_dw_dbi_protect(sc, false); + + /* Setup config registers */ + DBI_WR1(sc, PCIR_CLASS, PCIC_BRIDGE); + DBI_WR1(sc, PCIR_SUBCLASS, PCIS_BRIDGE_PCI); + DBI_WR4(sc, PCIR_BAR(0), 4); + DBI_WR4(sc, PCIR_BAR(1), 0); + DBI_WR1(sc, PCIR_INTPIN, 1); + DBI_WR1(sc, PCIR_PRIBUS_1, sc->root_bus); + DBI_WR1(sc, PCIR_SECBUS_1, sc->sub_bus); + DBI_WR1(sc, PCIR_SUBBUS_1, sc->bus_end); + DBI_WR2(sc, PCIR_COMMAND, + PCIM_CMD_PORTEN | PCIM_CMD_MEMEN | + PCIM_CMD_BUSMASTEREN | PCIM_CMD_SERRESPEN); + pci_dw_dbi_protect(sc, true); + + /* Setup outbound memory window */ + rv = pci_dw_map_out_atu(sc, 0, IATU_CTRL1_TYPE_MEM, + sc->mem_range.host, sc->mem_range.pci, sc->mem_range.size); + if (rv != 0) + return (rv); + + /* If we have enouht viewports ..*/ + if (sc->num_viewport >= 3) { + /* Setup outbound I/O window */ + rv = pci_dw_map_out_atu(sc, 0, IATU_CTRL1_TYPE_MEM, + sc->io_range.host, sc->io_range.pci, sc->io_range.size); + if (rv != 0) + return (rv); + } + /* XXX Should we handle also prefetch memory? */ + + + /* Adjust number of lanes */ + reg = DBI_RD4(sc, DW_PORT_LINK_CTRL); + reg &= ~PORT_LINK_CAPABLE(~0); + switch (sc->num_lanes) { + case 1: + reg |= PORT_LINK_CAPABLE(PORT_LINK_CAPABLE_1); + break; + case 2: + reg |= PORT_LINK_CAPABLE(PORT_LINK_CAPABLE_2); + break; + case 4: + reg |= PORT_LINK_CAPABLE(PORT_LINK_CAPABLE_4); + break; + case 8: + reg |= PORT_LINK_CAPABLE(PORT_LINK_CAPABLE_8); + break; + case 16: + reg |= PORT_LINK_CAPABLE(PORT_LINK_CAPABLE_16); + break; + case 32: + reg |= PORT_LINK_CAPABLE(PORT_LINK_CAPABLE_32); + break; + default: + device_printf(sc->dev, + "'num-lanes' property have invalid value: %d\n", + sc->num_lanes); + return (EINVAL); + } + DBI_WR4(sc, DW_PORT_LINK_CTRL, reg); + + + /* And link width */ + reg = DBI_RD4(sc, DW_GEN2_CTRL); + reg &= ~GEN2_CTRL_NUM_OF_LANES(~0); + switch (sc->num_lanes) { + case 1: + reg |= GEN2_CTRL_NUM_OF_LANES(GEN2_CTRL_NUM_OF_LANES_1); + break; + case 2: + reg |= GEN2_CTRL_NUM_OF_LANES(GEN2_CTRL_NUM_OF_LANES_2); + break; + case 4: + reg |= GEN2_CTRL_NUM_OF_LANES(GEN2_CTRL_NUM_OF_LANES_4); + break; + case 8: + reg |= GEN2_CTRL_NUM_OF_LANES(GEN2_CTRL_NUM_OF_LANES_8); + break; + case 16: + reg |= GEN2_CTRL_NUM_OF_LANES(GEN2_CTRL_NUM_OF_LANES_16); + break; + case 32: + reg |= GEN2_CTRL_NUM_OF_LANES(GEN2_CTRL_NUM_OF_LANES_32); + break; + } + DBI_WR4(sc, DW_GEN2_CTRL, reg); + + reg = DBI_RD4(sc, DW_GEN2_CTRL); + reg |= DIRECT_SPEED_CHANGE; + DBI_WR4(sc, DW_GEN2_CTRL, reg); + + + return (0); +} + +static int +pci_dw_decode_ranges(struct pci_dw_softc *sc, struct ofw_pci_range *ranges, + int nranges) +{ + int i; + + for (i = 0; i < nranges; i++) { + if ((ranges[i].pci_hi & OFW_PCI_PHYS_HI_SPACEMASK) == + OFW_PCI_PHYS_HI_SPACE_IO) { + if (sc->io_range.size != 0) { + device_printf(sc->dev, + "Duplicated IO range found in DT\n"); + return (ENXIO); + } + sc->io_range = ranges[i]; + } + if (((ranges[i].pci_hi & OFW_PCI_PHYS_HI_SPACEMASK) == + OFW_PCI_PHYS_HI_SPACE_MEM32)) { + if (ranges[i].pci_hi & OFW_PCI_PHYS_HI_PREFETCHABLE) { + if (sc->pref_mem_range.size != 0) { + device_printf(sc->dev, + "Duplicated memory range found " + "in DT\n"); + return (ENXIO); + } + sc->pref_mem_range = ranges[i]; + } else { + if (sc->mem_range.size != 0) { + device_printf(sc->dev, + "Duplicated memory range found " + "in DT\n"); + return (ENXIO); + } + sc->mem_range = ranges[i]; + } + } + } + if ((sc->io_range.size == 0) || (sc->mem_range.size == 0)) { + device_printf(sc->dev, + " Not all required ranges are found in DT\n"); + return (ENXIO); + } + return (0); +} + + + +/*----------------------------------------------------------------------------- + * + * P C I B I N T E R F A C E + */ + +static uint32_t +pci_dw_read_config(device_t dev, u_int bus, u_int slot, + u_int func, u_int reg, int bytes) +{ + struct pci_dw_softc *sc; + struct resource *res; + uint32_t data; + uint64_t addr; + int type, rv; + + sc = device_get_softc(dev); + + if (!pci_dw_check_dev(sc, bus, slot, func, reg)) + return (0xFFFFFFFFU); + + if ((slot > PCI_SLOTMAX) || (func > PCI_FUNCMAX) || + (reg > PCI_REGMAX)) + return (0xFFFFFFFFU); + + if (bus == sc->root_bus) { + res = (sc->dbi_res); + } else { + addr = IATU_CFG_BUS(bus) | IATU_CFG_SLOT(slot) | + IATU_CFG_FUNC(func); + if (bus == sc->sub_bus) + type = IATU_CTRL1_TYPE_CFG0; + else + type = IATU_CTRL1_TYPE_CFG1; + rv = pci_dw_map_out_atu(sc, 1, type, + sc->cfg_pa, addr, sc->cfg_size); + if (rv != 0) + return (0xFFFFFFFFU); + res = sc->cfg_res; + } + + switch (bytes) { + case 1: + data = bus_read_1(res, reg); + break; + case 2: + data = bus_read_2(res, reg); + break; + case 4: + data = bus_read_4(res, reg); + break; + default: + data = 0xFFFFFFFFU; + } + + return (data); + +} + +static void +pci_dw_write_config(device_t dev, u_int bus, u_int slot, + u_int func, u_int reg, uint32_t val, int bytes) +{ + struct pci_dw_softc *sc; + struct resource *res; + uint64_t addr; + int type, rv; + + sc = device_get_softc(dev); + if (!pci_dw_check_dev(sc, bus, slot, func, reg)) + return; + + if ((slot > PCI_SLOTMAX) || (func > PCI_FUNCMAX) || + (reg > PCI_REGMAX)) + return; + + if (bus == sc->root_bus) { + res = (sc->dbi_res); + } else { + addr = IATU_CFG_BUS(bus) | IATU_CFG_SLOT(slot) | + IATU_CFG_FUNC(func); + if (bus == sc->sub_bus) + type = IATU_CTRL1_TYPE_CFG0; + else + type = IATU_CTRL1_TYPE_CFG1; + rv = pci_dw_map_out_atu(sc, 1, type, + sc->cfg_pa, addr, sc->cfg_size); + if (rv != 0) + return ; + res = sc->cfg_res; + } + + + switch (bytes) { + case 1: + bus_write_1(res, reg, val); + break; + case 2: + bus_write_2(res, reg, val); + break; + case 4: + bus_write_4(res, reg, val); + break; + default: + break; + } +} + +static int +pci_dw_alloc_msi(device_t pci, device_t child, int count, + int maxcount, int *irqs) +{ + phandle_t msi_parent; + int rv; + + rv = ofw_bus_msimap(ofw_bus_get_node(pci), pci_get_rid(child), + &msi_parent, NULL); + if (rv != 0) + return (rv); + + return (intr_alloc_msi(pci, child, msi_parent, count, maxcount, + irqs)); +} + +static int +pci_dw_release_msi(device_t pci, device_t child, int count, int *irqs) +{ + phandle_t msi_parent; + int rv; + + rv = ofw_bus_msimap(ofw_bus_get_node(pci), pci_get_rid(child), + &msi_parent, NULL); + if (rv != 0) + return (rv); + return (intr_release_msi(pci, child, msi_parent, count, irqs)); +} + +static int +pci_dw_map_msi(device_t pci, device_t child, int irq, uint64_t *addr, + uint32_t *data) +{ + phandle_t msi_parent; + int rv; + + rv = ofw_bus_msimap(ofw_bus_get_node(pci), pci_get_rid(child), + &msi_parent, NULL); + if (rv != 0) + return (rv); + + return (intr_map_msi(pci, child, msi_parent, irq, addr, data)); +} + +static int +pci_dw_alloc_msix(device_t pci, device_t child, int *irq) +{ + phandle_t msi_parent; + int rv; + + rv = ofw_bus_msimap(ofw_bus_get_node(pci), pci_get_rid(child), + &msi_parent, NULL); + if (rv != 0) + return (rv); + return (intr_alloc_msix(pci, child, msi_parent, irq)); +} + +static int +pci_dw_release_msix(device_t pci, device_t child, int irq) +{ + phandle_t msi_parent; + int rv; + + rv = ofw_bus_msimap(ofw_bus_get_node(pci), pci_get_rid(child), + &msi_parent, NULL); + if (rv != 0) + return (rv); + return (intr_release_msix(pci, child, msi_parent, irq)); +} + +static int +pci_dw_get_id(device_t pci, device_t child, enum pci_id_type type, + uintptr_t *id) +{ + phandle_t node; + int rv; + uint32_t rid; + uint16_t pci_rid; + + if (type != PCI_ID_MSI) + return (pcib_get_id(pci, child, type, id)); + + node = ofw_bus_get_node(pci); + pci_rid = pci_get_rid(child); + + rv = ofw_bus_msimap(node, pci_rid, NULL, &rid); + if (rv != 0) + return (rv); + *id = rid; + + return (0); +} + +/*----------------------------------------------------------------------------- + * + * B U S / D E V I C E I N T E R F A C E + */ +static bus_dma_tag_t +pci_dw_get_dma_tag(device_t dev, device_t child) +{ + struct pci_dw_softc *sc; + + sc = device_get_softc(dev); + return (sc->dmat); +} + +int +pci_dw_init(device_t dev) +{ + struct pci_dw_softc *sc; + int rv, rid; + + sc = device_get_softc(dev); + sc->dev = dev; + sc->node = ofw_bus_get_node(dev); + + mtx_init(&sc->mtx, "pci_dw_mtx", NULL, MTX_DEF); + + /* XXXn Should not be this configurable ? */ + sc->bus_start = 0; + sc->bus_end = 255; + sc->root_bus = 0; + sc->sub_bus = 1; + + /* Read FDT properties */ + if (!sc->coherent) + sc->coherent = OF_hasprop(sc->node, "dma-coherent"); + + rv = OF_getencprop(sc->node, "num-viewport", &sc->num_viewport, + sizeof(sc->num_viewport)); + if (rv != sizeof(sc->num_viewport)) + sc->num_viewport = 2; + + rv = OF_getencprop(sc->node, "num-lanes", &sc->num_lanes, + sizeof(sc->num_viewport)); + if (rv != sizeof(sc->num_lanes)) + sc->num_lanes = 1; + if (sc->num_lanes != 1 && sc->num_lanes != 2 && + sc->num_lanes != 4 && sc->num_lanes != 8) { + device_printf(dev, + "invalid number of lanes: %d\n",sc->num_lanes); + sc->num_lanes = 0; + rv = ENXIO; + goto out; + } + + rid = 0; + rv = ofw_bus_find_string_index(sc->node, "reg-names", "config", &rid); + if (rv != 0) { + device_printf(dev, "Cannot get config space memory\n"); + rv = ENXIO; + goto out; + } + sc->cfg_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, + RF_ACTIVE); + if (sc->cfg_res == NULL) { + device_printf(dev, "Cannot allocate config space(rid: %d)\n", + rid); + rv = ENXIO; + goto out; + } + + /* Fill up config region related variables */ + sc->cfg_size = rman_get_size(sc->cfg_res); + sc->cfg_pa = rman_get_start(sc->cfg_res) ; + + if (bootverbose) + device_printf(dev, "Bus is%s cache-coherent\n", + sc->coherent ? "" : " not"); + rv = bus_dma_tag_create(bus_get_dma_tag(dev), /* parent */ + 1, 0, /* alignment, bounds */ + BUS_SPACE_MAXADDR, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, NULL, /* filter, filterarg */ + BUS_SPACE_MAXSIZE, /* maxsize */ + BUS_SPACE_UNRESTRICTED, /* nsegments */ + BUS_SPACE_MAXSIZE, /* maxsegsize */ + sc->coherent ? BUS_DMA_COHERENT : 0, /* flags */ + NULL, NULL, /* lockfunc, lockarg */ + &sc->dmat); + if (rv != 0) + goto out; + + rv = ofw_pci_init(dev); + if (rv != 0) + goto out; + rv = pci_dw_decode_ranges(sc, sc->ofw_pci.sc_range, + sc->ofw_pci.sc_nrange); + if (rv != 0) + goto out; + + rv = pci_dw_setup_hw(sc); + if (rv != 0) + goto out; + + device_add_child(dev, "pci", -1); + + return (bus_generic_attach(dev)); +out: + /* XXX Cleanup */ + return (rv); +} + +static device_method_t pci_dw_methods[] = { + + /* Bus interface */ + DEVMETHOD(bus_get_dma_tag, pci_dw_get_dma_tag), + + /* pcib interface */ + DEVMETHOD(pcib_read_config, pci_dw_read_config), + DEVMETHOD(pcib_write_config, pci_dw_write_config), + DEVMETHOD(pcib_alloc_msi, pci_dw_alloc_msi), + DEVMETHOD(pcib_release_msi, pci_dw_release_msi), + DEVMETHOD(pcib_alloc_msix, pci_dw_alloc_msix), + DEVMETHOD(pcib_release_msix, pci_dw_release_msix), + DEVMETHOD(pcib_map_msi, pci_dw_map_msi), + DEVMETHOD(pcib_get_id, pci_dw_get_id), + + /* OFW bus interface */ + DEVMETHOD(ofw_bus_get_compat, ofw_bus_gen_get_compat), + DEVMETHOD(ofw_bus_get_model, ofw_bus_gen_get_model), + DEVMETHOD(ofw_bus_get_name, ofw_bus_gen_get_name), + DEVMETHOD(ofw_bus_get_node, ofw_bus_gen_get_node), + DEVMETHOD(ofw_bus_get_type, ofw_bus_gen_get_type), + + /* PCI DW interface */ + DEVMETHOD(pci_dw_dbi_read, pci_dw_dbi_read), + DEVMETHOD(pci_dw_dbi_write, pci_dw_dbi_write), + DEVMETHOD_END +}; + +DEFINE_CLASS_1(pcib, pci_dw_driver, pci_dw_methods, + sizeof(struct pci_dw_softc), ofw_pci_driver); diff --git a/sys/dev/pci/pci_dw.h b/sys/dev/pci/pci_dw.h new file mode 100644 index 000000000000..b3c4147009a3 --- /dev/null +++ b/sys/dev/pci/pci_dw.h @@ -0,0 +1,158 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2019 Michal Meloun + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * + * $FreeBSD$ + * + */ + +#ifndef _PCI_DW_H_ +#define _PCI_DW_H_ + +#include "pci_dw_if.h" + + +/* DesignWare CIe configuration registers */ +#define DW_PORT_LINK_CTRL 0x710 +#define PORT_LINK_CAPABLE(n) (((n) & 0x3F) << 16) +#define PORT_LINK_CAPABLE_1 0x01 +#define PORT_LINK_CAPABLE_2 0x03 +#define PORT_LINK_CAPABLE_4 0x07 +#define PORT_LINK_CAPABLE_8 0x0F +#define PORT_LINK_CAPABLE_16 0x1F +#define PORT_LINK_CAPABLE_32 0x3F + + +#define DW_GEN2_CTRL 0x80C +#define DIRECT_SPEED_CHANGE (1 << 17) +#define GEN2_CTRL_NUM_OF_LANES(n) (((n) & 0x3F) << 8) +#define GEN2_CTRL_NUM_OF_LANES_1 0x01 +#define GEN2_CTRL_NUM_OF_LANES_2 0x03 +#define GEN2_CTRL_NUM_OF_LANES_4 0x07 +#define GEN2_CTRL_NUM_OF_LANES_8 0x0F +#define GEN2_CTRL_NUM_OF_LANES_16 0x1F +#define GEN2_CTRL_NUM_OF_LANES_32 0x3F + +#define DW_MSI_ADDR_LO 0x820 +#define DW_MSI_ADDR_HI 0x824 +#define DW_MSI_INTR0_ENABLE 0x828 +#define DW_MSI_INTR0_MASK 0x82C +#define DW_MSI_INTR0_STATUS 0x830 + + +#define DW_MISC_CONTROL_1 0x8BC +#define DBI_RO_WR_EN (1 << 0) + +#define DW_IATU_VIEWPORT 0x900 +#define IATU_REGION_INBOUND (1U << 31) +#define IATU_REGION_INDEX(x) ((x) & 0x7) +#define DW_IATU_CTRL1 0x904 +#define IATU_CTRL1_TYPE(x) ((x) & 0x1F) +#define IATU_CTRL1_TYPE_MEM 0x0 +#define IATU_CTRL1_TYPE_IO 0x2 +#define IATU_CTRL1_TYPE_CFG0 0x4 +#define IATU_CTRL1_TYPE_CFG1 0x5 +#define DW_IATU_CTRL2 0x908 +#define IATU_CTRL2_REGION_EN (1U << 31) +#define DW_IATU_LWR_BASE_ADDR 0x90C +#define DW_IATU_UPPER_BASE_ADDR 0x910 +#define DW_IATU_LIMIT_ADDR 0x914 +#define DW_IATU_LWR_TARGET_ADDR 0x918 +#define DW_IATU_UPPER_TARGET_ADDR 0x91C + + +struct pci_dw_softc { + struct ofw_pci_softc ofw_pci; /* Must be first */ + + /* Filled by attachement stub */ + struct resource *dbi_res; + + /* pci_dw variables */ + device_t dev; + phandle_t node; + struct mtx mtx; + struct resource *cfg_res; + + struct ofw_pci_range mem_range; + struct ofw_pci_range pref_mem_range; + struct ofw_pci_range io_range; + + bool coherent; + bus_dma_tag_t dmat; + + int num_lanes; + int num_viewport; + bus_addr_t cfg_pa; /* PA of config memoty */ + bus_size_t cfg_size; /* size of config region */ + + u_int bus_start; + u_int bus_end; + u_int root_bus; + u_int sub_bus; +}; + +DECLARE_CLASS(pci_dw_driver); + + +static inline void +pci_dw_dbi_wr4(device_t dev, u_int reg, uint32_t val) +{ + PCI_DW_DBI_WRITE(dev, reg, val, 4); +} + +static inline void +pci_dw_dbi_wr2(device_t dev, u_int reg, uint16_t val) +{ + PCI_DW_DBI_WRITE(dev, reg, val, 2); +} + +static inline void +pci_dw_dbi_wr1(device_t dev, u_int reg, uint8_t val) +{ + PCI_DW_DBI_WRITE(dev, reg, val, 1); +} + +static inline uint32_t +pci_dw_dbi_rd4(device_t dev, u_int reg) +{ + return (PCI_DW_DBI_READ(dev, reg, 4)); +} + +static inline uint16_t +pci_dw_dbi_rd2(device_t dev, u_int reg) +{ + return ((uint16_t)PCI_DW_DBI_READ(dev, reg, 2)); +} + +static inline uint8_t +pci_dw_dbi_rd1(device_t dev, u_int reg) +{ + return ((uint8_t)PCI_DW_DBI_READ(dev, reg, 1)); +} + +int pci_dw_init(device_t); + +#endif /* __PCI_HOST_GENERIC_H_ */ diff --git a/sys/dev/pci/pci_dw_if.m b/sys/dev/pci/pci_dw_if.m new file mode 100644 index 000000000000..e5fc3ae444cf --- /dev/null +++ b/sys/dev/pci/pci_dw_if.m @@ -0,0 +1,73 @@ +#- +# Copyright (c) 2019 Michal Meloun +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# $FreeBSD$ +# + +#include + +INTERFACE pci_dw; + + +/** + * Read from dbi space. + * The reg argument is a byte offset into dbi space. + * The width argument (which should be 1, 2 or 4) specifies how + * many bytes to read from that offset. + */ +METHOD uint32_t dbi_read{ + device_t dev; + u_int reg; + int width; +}; + +/** + * Write to dbi space. + * The reg argument is a byte offset into dbi space. + * The width argument (which should be 1, 2 or 4) specifies how + * many bytes to write to that offset. + */ +METHOD void dbi_write{ + device_t dev; + u_int reg; + uint32_t value; + int width; +}; + +/** + * Start or stop link + */ +METHOD int set_link{ + device_t dev; + bool start; +}; + +/** + * Query link status (up/down) + */ +METHOD int get_link{ + device_t dev; + bool *status; +}; diff --git a/sys/dev/pci/pci_dw_mv.c b/sys/dev/pci/pci_dw_mv.c new file mode 100644 index 000000000000..06a29fefbdd2 --- /dev/null +++ b/sys/dev/pci/pci_dw_mv.c @@ -0,0 +1,328 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2019 Michal Meloun + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +/* Armada 8k DesignWare PCIe driver */ + +#include +__FBSDID("$FreeBSD$"); + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pcib_if.h" +#include "pci_dw_if.h" + +#define MV_GLOBAL_CONTROL_REG 0x8000 +#define PCIE_APP_LTSSM_EN (1 << 2) +//#define PCIE_DEVICE_TYPE_SHIFT 4 +//#define PCIE_DEVICE_TYPE_MASK 0xF +//#define PCIE_DEVICE_TYPE_RC 0x4/ + +#define MV_GLOBAL_STATUS_REG 0x8008 +#define MV_STATUS_RDLH_LINK_UP (1 << 1) +#define MV_STATUS_PHY_LINK_UP (1 << 9) + + +#define MV_INT_CAUSE1 0x801C +#define MV_INT_MASK1 0x8020 +#define INT_A_ASSERT_MASK (1 << 9) +#define INT_B_ASSERT_MASK (1 << 10) +#define INT_C_ASSERT_MASK (1 << 11) +#define INT_D_ASSERT_MASK (1 << 12) + +#define MV_INT_CAUSE2 0x8024 +#define MV_INT_MASK2 0x8028 +#define MV_ERR_INT_CAUSE 0x802C +#define MV_ERR_INT_MASK 0x8030 + +#define MV_ARCACHE_TRC_REG 0x8050 +#define MV_AWCACHE_TRC_REG 0x8054 +#define MV_ARUSER_REG 0x805C +#define MV_AWUSER_REG 0x8060 + + + +#define MV_MAX_LANES 8 + + +struct pci_mv_softc { + struct pci_dw_softc dw_sc; + device_t dev; + phandle_t node; + struct resource *irq_res; + void *intr_cookie; + phy_t phy[MV_MAX_LANES]; + clk_t clk_core; + clk_t clk_reg; +}; + +/* Compatible devices. */ +static struct ofw_compat_data compat_data[] = { + {"marvell,armada8k-pcie", 1}, + {NULL, 0}, +}; + + +static int +pci_mv_phy_init(struct pci_mv_softc *sc) +{ + int i, rv; + + for (i = 0; i < MV_MAX_LANES; i++) { + rv = phy_get_by_ofw_idx(sc->dev, sc->node, i, &(sc->phy[i])); + if (rv != 0 && rv != ENOENT) { + device_printf(sc->dev, "Cannot get phy[%d]\n", i); + goto fail; + } + if (sc->phy[i] == NULL) + continue; + rv = phy_enable(sc->phy[i]); + if (rv != 0) { + device_printf(sc->dev, "Cannot enable phy[%d]\n", i); + goto fail; + } + } + return (0); + +fail: + for (i = 0; i < MV_MAX_LANES; i++) { + if (sc->phy[i] == NULL) + continue; + phy_release(sc->phy[i]); + } + + return (rv); +} + +static void +pci_mv_init(struct pci_mv_softc *sc) +{ + uint32_t reg; + + + /* Set device configuration to RC */ + reg = pci_dw_dbi_rd4(sc->dev, MV_GLOBAL_CONTROL_REG); + reg &= ~0x000000F0; + reg |= 0x000000040; + pci_dw_dbi_wr4(sc->dev, MV_GLOBAL_CONTROL_REG, reg); + + /* AxCache master transaction attribures */ + pci_dw_dbi_wr4(sc->dev, MV_ARCACHE_TRC_REG, 0x3511); + pci_dw_dbi_wr4(sc->dev, MV_AWCACHE_TRC_REG, 0x5311); + + /* AxDomain master transaction attribures */ + pci_dw_dbi_wr4(sc->dev, MV_ARUSER_REG, 0x0002); + pci_dw_dbi_wr4(sc->dev, MV_AWUSER_REG, 0x0002); + + /* Enable all INTx interrupt (virtuual) pins */ + reg = pci_dw_dbi_rd4(sc->dev, MV_INT_MASK1); + reg |= INT_A_ASSERT_MASK | INT_B_ASSERT_MASK | + INT_C_ASSERT_MASK | INT_D_ASSERT_MASK; + pci_dw_dbi_wr4(sc->dev, MV_INT_MASK1, reg); + + /* Enable local interrupts */ + pci_dw_dbi_wr4(sc->dev, DW_MSI_INTR0_MASK, 0xFFFFFFFF); + pci_dw_dbi_wr4(sc->dev, MV_INT_MASK1, 0xFFFFFFFF); + pci_dw_dbi_wr4(sc->dev, MV_INT_MASK2, 0xFFFFFFFF); + pci_dw_dbi_wr4(sc->dev, MV_INT_CAUSE1, 0xFFFFFFFF); + pci_dw_dbi_wr4(sc->dev, MV_INT_CAUSE2, 0xFFFFFFFF); + + /* Errors have own interrupt, not yet populated in DTt */ + pci_dw_dbi_wr4(sc->dev, MV_ERR_INT_MASK, 0); +} +static int pci_mv_intr(void *arg) +{ + struct pci_mv_softc *sc = arg; + uint32_t cause1, cause2; + + /* Ack all interrups */ + cause1 = pci_dw_dbi_rd4(sc->dev, MV_INT_CAUSE1); + cause2 = pci_dw_dbi_rd4(sc->dev, MV_INT_CAUSE2); + if (cause1 == 0 || cause2 == 0) + return(FILTER_STRAY); + + pci_dw_dbi_wr4(sc->dev, MV_INT_CAUSE1, cause1); + pci_dw_dbi_wr4(sc->dev, MV_INT_CAUSE2, cause2); + return (FILTER_HANDLED); +} + +static int +pci_mv_get_link(device_t dev, bool *status) +{ + uint32_t reg; + + reg = pci_dw_dbi_rd4(dev, MV_GLOBAL_STATUS_REG); + if ((reg & (MV_STATUS_RDLH_LINK_UP | MV_STATUS_PHY_LINK_UP)) == + (MV_STATUS_RDLH_LINK_UP | MV_STATUS_PHY_LINK_UP)) + *status = true; + else + *status = false; + + return (0); +} + +static int +pci_mv_probe(device_t dev) +{ + + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) + return (ENXIO); + + device_set_desc(dev, "Marvell Armada8K PCI-E Controller"); + return (BUS_PROBE_DEFAULT); +} + +static int +pci_mv_attach(device_t dev) +{ + struct pci_mv_softc *sc; + phandle_t node; + int rv; + int rid; + + sc = device_get_softc(dev); + node = ofw_bus_get_node(dev); + sc->dev = dev; + sc->node = node; + + rid = 0; + sc->dw_sc.dbi_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, + RF_ACTIVE); + if (sc->dw_sc.dbi_res == NULL) { + device_printf(dev, "Cannot allocate DBI memory\n"); + rv = ENXIO; + goto out; + } + + /* PCI interrupt */ + rid = 0; + sc->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, + RF_ACTIVE | RF_SHAREABLE); + if (sc->irq_res == NULL) { + device_printf(dev, "Cannot allocate IRQ resources\n"); + rv = ENXIO; + goto out; + } + + /* Clocks */ + rv = clk_get_by_ofw_name(sc->dev, 0, "core", &sc->clk_core); + if (rv != 0) { + device_printf(sc->dev, "Cannot get 'core' clock\n"); + rv = ENXIO; + goto out; + } + + rv = clk_get_by_ofw_name(sc->dev, 0, "reg", &sc->clk_reg); + if (rv != 0) { + device_printf(sc->dev, "Cannot get 'reg' clock\n"); + rv = ENXIO; + goto out; + } + + + rv = clk_enable(sc->clk_core); + if (rv != 0) { + device_printf(sc->dev, "Cannot enable 'core' clock\n"); + rv = ENXIO; + goto out; + } + + rv = clk_enable(sc->clk_reg); + if (rv != 0) { + device_printf(sc->dev, "Cannot enable 'reg' clock\n"); + rv = ENXIO; + goto out; + } + + rv = pci_mv_phy_init(sc); + if (rv) + goto out; + + rv = pci_dw_init(dev); + if (rv != 0) + goto out; + + pci_mv_init(sc); + + /* Setup interrupt */ + if (bus_setup_intr(dev, sc->irq_res, INTR_TYPE_MISC | INTR_MPSAFE, + pci_mv_intr, NULL, sc, &sc->intr_cookie)) { + device_printf(dev, "cannot setup interrupt handler\n"); + rv = ENXIO; + goto out; + } + + return (bus_generic_attach(dev)); +out: + /* XXX Cleanup */ + return (rv); +} + +static device_method_t pci_mv_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, pci_mv_probe), + DEVMETHOD(device_attach, pci_mv_attach), + + DEVMETHOD(pci_dw_get_link, pci_mv_get_link), + + DEVMETHOD_END +}; + +DEFINE_CLASS_1(pcib, pci_mv_driver, pci_mv_methods, + sizeof(struct pci_mv_softc), pci_dw_driver); +static devclass_t pci_mv_devclass; +DRIVER_MODULE( pci_mv, simplebus, pci_mv_driver, pci_mv_devclass, + NULL, NULL); \ No newline at end of file