22997b7550
This has been present since the first revision of the file. The debugf
macros have always been unused so it doesn't actually do anything
useful, and besides, debugging should not be unconditionally turned on
for a production driver. Moreover, this breaks the riscv LINT kernel
build as sys/conf/NOTES includes options DEBUG, resulting in a macro
redefinition error. This does not show up in the arm64 LINT kernel build
since that has an explicit nooptions DEBUG, which is dubious and should
be revisited. Rather than copy such a hack to riscv's NOTES, fix this
specific instance of DEBUG breaking.
Fixes: 896e217a0e
("fu740_pci_dw: Add SiFive FU740 PCIe controller driver")
MFC after: 1 week
864 lines
21 KiB
C
864 lines
21 KiB
C
/*-
|
|
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
|
*
|
|
* Copyright (c) 2019 Michal Meloun <mmel@FreeBSD.org>
|
|
*
|
|
* 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 <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/devmap.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/lock.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/module.h>
|
|
#include <sys/mutex.h>
|
|
#include <sys/rman.h>
|
|
|
|
#include <machine/bus.h>
|
|
#include <machine/intr.h>
|
|
#include <machine/resource.h>
|
|
|
|
#include <dev/ofw/ofw_bus.h>
|
|
#include <dev/ofw/ofw_bus_subr.h>
|
|
#include <dev/ofw/ofw_pci.h>
|
|
#include <dev/ofw/ofwpci.h>
|
|
#include <dev/pci/pcivar.h>
|
|
#include <dev/pci/pcireg.h>
|
|
#include <dev/pci/pcib_private.h>
|
|
#include <dev/pci/pci_dw.h>
|
|
|
|
#include "pcib_if.h"
|
|
#include "pci_dw_if.h"
|
|
|
|
#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 IATU_UR_WR4(sc, reg, val) \
|
|
bus_write_4((sc)->iatu_ur_res, (sc)->iatu_ur_offset + (reg), (val))
|
|
#define IATU_UR_RD4(sc, reg) \
|
|
bus_read_4((sc)->iatu_ur_res, (sc)->iatu_ur_offset + (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 > PCIE_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);
|
|
}
|
|
|
|
static bool
|
|
pci_dw_detect_atu_unroll(struct pci_dw_softc *sc)
|
|
{
|
|
return (DBI_RD4(sc, DW_IATU_VIEWPORT) == 0xFFFFFFFFU);
|
|
}
|
|
|
|
static int
|
|
pci_dw_detect_out_atu_regions_unroll(struct pci_dw_softc *sc)
|
|
{
|
|
int num_regions, i;
|
|
uint32_t reg;
|
|
|
|
num_regions = sc->iatu_ur_size / DW_IATU_UR_STEP;
|
|
|
|
for (i = 0; i < num_regions; ++i) {
|
|
IATU_UR_WR4(sc, DW_IATU_UR_REG(i, LWR_TARGET_ADDR),
|
|
0x12340000);
|
|
reg = IATU_UR_RD4(sc, DW_IATU_UR_REG(i, LWR_TARGET_ADDR));
|
|
if (reg != 0x12340000)
|
|
break;
|
|
}
|
|
|
|
sc->num_out_regions = i;
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
pci_dw_detect_out_atu_regions_legacy(struct pci_dw_softc *sc)
|
|
{
|
|
int num_viewports, i;
|
|
uint32_t reg;
|
|
|
|
/* Find out how many viewports there are in total */
|
|
DBI_WR4(sc, DW_IATU_VIEWPORT, IATU_REGION_INDEX(~0U));
|
|
reg = DBI_RD4(sc, DW_IATU_VIEWPORT);
|
|
if (reg > IATU_REGION_INDEX(~0U)) {
|
|
device_printf(sc->dev,
|
|
"Cannot detect number of output iATU regions; read %#x\n",
|
|
reg);
|
|
return (ENXIO);
|
|
}
|
|
|
|
num_viewports = reg + 1;
|
|
|
|
/*
|
|
* Find out how many of them are outbound by seeing whether a dummy
|
|
* page-aligned address sticks.
|
|
*/
|
|
for (i = 0; i < num_viewports; ++i) {
|
|
DBI_WR4(sc, DW_IATU_VIEWPORT, IATU_REGION_INDEX(i));
|
|
DBI_WR4(sc, DW_IATU_LWR_TARGET_ADDR, 0x12340000);
|
|
reg = DBI_RD4(sc, DW_IATU_LWR_TARGET_ADDR);
|
|
if (reg != 0x12340000)
|
|
break;
|
|
}
|
|
|
|
sc->num_out_regions = i;
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
pci_dw_detect_out_atu_regions(struct pci_dw_softc *sc)
|
|
{
|
|
if (sc->iatu_ur_res)
|
|
return (pci_dw_detect_out_atu_regions_unroll(sc));
|
|
else
|
|
return (pci_dw_detect_out_atu_regions_legacy(sc));
|
|
}
|
|
|
|
static int
|
|
pci_dw_map_out_atu_unroll(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);
|
|
|
|
IATU_UR_WR4(sc, DW_IATU_UR_REG(idx, LWR_BASE_ADDR),
|
|
pa & 0xFFFFFFFF);
|
|
IATU_UR_WR4(sc, DW_IATU_UR_REG(idx, UPPER_BASE_ADDR),
|
|
(pa >> 32) & 0xFFFFFFFF);
|
|
IATU_UR_WR4(sc, DW_IATU_UR_REG(idx, LIMIT_ADDR),
|
|
(pa + size - 1) & 0xFFFFFFFF);
|
|
IATU_UR_WR4(sc, DW_IATU_UR_REG(idx, LWR_TARGET_ADDR),
|
|
pci_addr & 0xFFFFFFFF);
|
|
IATU_UR_WR4(sc, DW_IATU_UR_REG(idx, UPPER_TARGET_ADDR),
|
|
(pci_addr >> 32) & 0xFFFFFFFF);
|
|
IATU_UR_WR4(sc, DW_IATU_UR_REG(idx, CTRL1),
|
|
IATU_CTRL1_TYPE(type));
|
|
IATU_UR_WR4(sc, DW_IATU_UR_REG(idx, CTRL2),
|
|
IATU_CTRL2_REGION_EN);
|
|
|
|
/* Wait until setup becomes valid */
|
|
for (i = 10; i > 0; i--) {
|
|
reg = IATU_UR_RD4(sc, DW_IATU_UR_REG(idx, CTRL2));
|
|
if (reg & IATU_CTRL2_REGION_EN)
|
|
return (0);
|
|
DELAY(5);
|
|
}
|
|
|
|
device_printf(sc->dev,
|
|
"Cannot map outbound region %d in unroll mode iATU\n", idx);
|
|
return (ETIMEDOUT);
|
|
}
|
|
|
|
static int
|
|
pci_dw_map_out_atu_legacy(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 legacy mode iATU\n", idx);
|
|
return (ETIMEDOUT);
|
|
}
|
|
|
|
/* Map one outbound 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)
|
|
{
|
|
if (sc->iatu_ur_res)
|
|
return (pci_dw_map_out_atu_unroll(sc, idx, type, pa,
|
|
pci_addr, size));
|
|
else
|
|
return (pci_dw_map_out_atu_legacy(sc, idx, type, pa,
|
|
pci_addr, size));
|
|
}
|
|
|
|
static int
|
|
pci_dw_setup_hw(struct pci_dw_softc *sc)
|
|
{
|
|
uint32_t reg;
|
|
int rv, i;
|
|
|
|
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 windows */
|
|
for (i = 0; i < min(sc->num_mem_ranges, sc->num_out_regions - 1); ++i) {
|
|
rv = pci_dw_map_out_atu(sc, i + 1, IATU_CTRL1_TYPE_MEM,
|
|
sc->mem_ranges[i].host, sc->mem_ranges[i].pci,
|
|
sc->mem_ranges[i].size);
|
|
if (rv != 0)
|
|
return (rv);
|
|
}
|
|
|
|
/* If we have enough regions ... */
|
|
if (sc->num_mem_ranges + 1 < sc->num_out_regions &&
|
|
sc->io_range.size != 0) {
|
|
/* Setup outbound I/O window */
|
|
rv = pci_dw_map_out_atu(sc, sc->num_mem_ranges + 1,
|
|
IATU_CTRL1_TYPE_IO, sc->io_range.host, sc->io_range.pci,
|
|
sc->io_range.size);
|
|
if (rv != 0)
|
|
return (rv);
|
|
}
|
|
|
|
/* 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, nmem, rv;
|
|
|
|
nmem = 0;
|
|
for (i = 0; i < nranges; i++) {
|
|
if ((ranges[i].pci_hi & OFW_PCI_PHYS_HI_SPACEMASK) ==
|
|
OFW_PCI_PHYS_HI_SPACE_MEM32)
|
|
++nmem;
|
|
}
|
|
|
|
sc->mem_ranges = malloc(nmem * sizeof(*sc->mem_ranges), M_DEVBUF,
|
|
M_WAITOK);
|
|
sc->num_mem_ranges = nmem;
|
|
|
|
nmem = 0;
|
|
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");
|
|
rv = ENXIO;
|
|
goto out;
|
|
}
|
|
|
|
sc->io_range = ranges[i];
|
|
if (sc->io_range.size > UINT32_MAX) {
|
|
device_printf(sc->dev,
|
|
"ATU IO window size is too large. "
|
|
"Up to 4GB windows are supported, "
|
|
"trimming window size to 4GB\n");
|
|
sc->io_range.size = UINT32_MAX;
|
|
}
|
|
}
|
|
if ((ranges[i].pci_hi & OFW_PCI_PHYS_HI_SPACEMASK) ==
|
|
OFW_PCI_PHYS_HI_SPACE_MEM32) {
|
|
MPASS(nmem < sc->num_mem_ranges);
|
|
sc->mem_ranges[nmem] = ranges[i];
|
|
if (sc->mem_ranges[nmem].size > UINT32_MAX) {
|
|
device_printf(sc->dev,
|
|
"ATU MEM window size is too large. "
|
|
"Up to 4GB windows are supported, "
|
|
"trimming window size to 4GB\n");
|
|
sc->mem_ranges[nmem].size = UINT32_MAX;
|
|
}
|
|
++nmem;
|
|
}
|
|
}
|
|
|
|
MPASS(nmem == sc->num_mem_ranges);
|
|
|
|
if (nmem == 0) {
|
|
device_printf(sc->dev,
|
|
"Missing required memory range in DT\n");
|
|
return (ENXIO);
|
|
}
|
|
|
|
return (0);
|
|
|
|
out:
|
|
free(sc->mem_ranges, M_DEVBUF);
|
|
return (rv);
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
*
|
|
* 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 (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, 0, 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 (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, 0, 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;
|
|
bool unroll_mode;
|
|
|
|
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-lanes", &sc->num_lanes,
|
|
sizeof(sc->num_lanes));
|
|
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_pcib_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;
|
|
|
|
unroll_mode = pci_dw_detect_atu_unroll(sc);
|
|
if (bootverbose)
|
|
device_printf(dev, "Using iATU %s mode\n",
|
|
unroll_mode ? "unroll" : "legacy");
|
|
if (unroll_mode) {
|
|
rid = 0;
|
|
rv = ofw_bus_find_string_index(sc->node, "reg-names", "atu", &rid);
|
|
if (rv == 0) {
|
|
sc->iatu_ur_res = bus_alloc_resource_any(dev,
|
|
SYS_RES_MEMORY, &rid, RF_ACTIVE);
|
|
if (sc->iatu_ur_res == NULL) {
|
|
device_printf(dev,
|
|
"Cannot allocate iATU space (rid: %d)\n",
|
|
rid);
|
|
rv = ENXIO;
|
|
goto out;
|
|
}
|
|
sc->iatu_ur_offset = 0;
|
|
sc->iatu_ur_size = rman_get_size(sc->iatu_ur_res);
|
|
} else if (rv == ENOENT) {
|
|
sc->iatu_ur_res = sc->dbi_res;
|
|
sc->iatu_ur_offset = DW_DEFAULT_IATU_UR_DBI_OFFSET;
|
|
sc->iatu_ur_size = DW_DEFAULT_IATU_UR_DBI_SIZE;
|
|
} else {
|
|
device_printf(dev, "Cannot get iATU space memory\n");
|
|
rv = ENXIO;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
rv = pci_dw_detect_out_atu_regions(sc);
|
|
if (rv != 0)
|
|
goto out;
|
|
|
|
if (bootverbose)
|
|
device_printf(sc->dev, "Detected outbound iATU regions: %d\n",
|
|
sc->num_out_regions);
|
|
|
|
rv = pci_dw_setup_hw(sc);
|
|
if (rv != 0)
|
|
goto out;
|
|
|
|
device_add_child(dev, "pci", -1);
|
|
|
|
return (0);
|
|
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_pcib_driver);
|