#include <sys/param.h>
#include <sys/systm.h>
#include <sys/endian.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/bus.h>
#include <sys/proc.h>
#include <sys/rman.h>
#include <sys/intr.h>
#include <sys/mutex.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
#include <dev/pci/pci_host_generic.h>
#include <dev/pci/pci_host_generic_fdt.h>
#include <dev/pci/pcivar.h>
#include <dev/pci/pcireg.h>
#include <dev/pci/pcib_private.h>
#include <machine/bus.h>
#include <machine/intr.h>
#include "pcib_if.h"
#include "msi_if.h"
#define PCI_ID_VAL3 0x43c
#define CLASS_SHIFT 0x10
#define SUBCLASS_SHIFT 0x8
#define REG_CONTROLLER_HW_REV 0x406c
#define REG_BRIDGE_CTRL 0x9210
#define BRIDGE_DISABLE_FLAG 0x1
#define BRIDGE_RESET_FLAG 0x2
#define REG_PCIE_HARD_DEBUG 0x4204
#define REG_DMA_CONFIG 0x4008
#define REG_DMA_WINDOW_LOW 0x4034
#define REG_DMA_WINDOW_HIGH 0x4038
#define REG_DMA_WINDOW_1 0x403c
#define REG_BRIDGE_GISB_WINDOW 0x402c
#define REG_BRIDGE_STATE 0x4068
#define REG_BRIDGE_LINK_STATE 0x00bc
#define REG_BUS_WINDOW_LOW 0x400c
#define REG_BUS_WINDOW_HIGH 0x4010
#define REG_CPU_WINDOW_LOW 0x4070
#define REG_CPU_WINDOW_START_HIGH 0x4080
#define REG_CPU_WINDOW_END_HIGH 0x4084
#define REG_MSI_ADDR_LOW 0x4044
#define REG_MSI_ADDR_HIGH 0x4048
#define REG_MSI_CONFIG 0x404c
#define REG_MSI_CLR 0x4508
#define REG_MSI_MASK_CLR 0x4514
#define REG_MSI_RAISED 0x4500
#define REG_MSI_EOI 0x4060
#define NUM_MSI 32
#define REG_EP_CONFIG_CHOICE 0x9000
#define REG_EP_CONFIG_DATA 0x8000
#define L1SS_ENABLE 0x00200000
#define CLKREQ_ENABLE 0x2
#define DMA_HIGH_LIMIT 0x3c000000
#define MAX_MEMORY_LOG2 0x21
#define REG_VALUE_DMA_WINDOW_LOW (MAX_MEMORY_LOG2 - 0xf)
#define REG_VALUE_DMA_WINDOW_HIGH 0x0
#define DMA_WINDOW_ENABLE 0x3000
#define REG_VALUE_DMA_WINDOW_CONFIG \
(((MAX_MEMORY_LOG2 - 0xf) << 0x1b) | DMA_WINDOW_ENABLE)
#define REG_VALUE_MSI_CONFIG 0xffe06540
struct bcm_pcib_irqsrc {
struct intr_irqsrc isrc;
u_int irq;
bool allocated;
};
struct bcm_pcib_softc {
struct generic_pcie_fdt_softc base;
device_t dev;
bus_dma_tag_t dmat;
struct mtx config_mtx;
struct mtx msi_mtx;
struct resource *msi_irq_res;
void *msi_intr_cookie;
struct bcm_pcib_irqsrc *msi_isrcs;
pci_addr_t msi_addr;
};
static struct ofw_compat_data compat_data[] = {
{"brcm,bcm2711-pcie", 1},
{"brcm,bcm7211-pcie", 1},
{"brcm,bcm7445-pcie", 1},
{NULL, 0}
};
static int
bcm_pcib_probe(device_t dev)
{
if (!ofw_bus_status_okay(dev))
return (ENXIO);
if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data)
return (ENXIO);
device_set_desc(dev,
"BCM2838-compatible PCI-express controller");
return (BUS_PROBE_DEFAULT);
}
static bus_dma_tag_t
bcm_pcib_get_dma_tag(device_t dev, device_t child)
{
struct bcm_pcib_softc *sc;
sc = device_get_softc(dev);
return (sc->dmat);
}
static void
bcm_pcib_set_reg(struct bcm_pcib_softc *sc, uint32_t reg, uint32_t val)
{
bus_write_4(sc->base.base.res, reg, htole32(val));
}
static uint32_t
bcm_pcib_read_reg(struct bcm_pcib_softc *sc, uint32_t reg)
{
return (le32toh(bus_read_4(sc->base.base.res, reg)));
}
static void
bcm_pcib_reset_controller(struct bcm_pcib_softc *sc)
{
uint32_t val;
val = bcm_pcib_read_reg(sc, REG_BRIDGE_CTRL);
val = val | BRIDGE_RESET_FLAG | BRIDGE_DISABLE_FLAG;
bcm_pcib_set_reg(sc, REG_BRIDGE_CTRL, val);
DELAY(100);
val = bcm_pcib_read_reg(sc, REG_BRIDGE_CTRL);
val = val & ~BRIDGE_RESET_FLAG;
bcm_pcib_set_reg(sc, REG_BRIDGE_CTRL, val);
DELAY(100);
bcm_pcib_set_reg(sc, REG_PCIE_HARD_DEBUG, 0);
DELAY(100);
}
static void
bcm_pcib_enable_controller(struct bcm_pcib_softc *sc)
{
uint32_t val;
val = bcm_pcib_read_reg(sc, REG_BRIDGE_CTRL);
val = val & ~BRIDGE_DISABLE_FLAG;
bcm_pcib_set_reg(sc, REG_BRIDGE_CTRL, val);
DELAY(100);
}
static int
bcm_pcib_check_ranges(device_t dev)
{
struct bcm_pcib_softc *sc;
struct pcie_range *ranges;
int error = 0, i;
sc = device_get_softc(dev);
ranges = &sc->base.base.ranges[0];
if (ranges[0].size == 0) {
device_printf(dev, "error: first outbound memory range "
"(pci addr: 0x%jx, cpu addr: 0x%jx) has zero size.\n",
ranges[0].pci_base, ranges[0].phys_base);
error = ENXIO;
}
for (i = 1; (bootverbose || error) && i < MAX_RANGES_TUPLES; ++i) {
if (ranges[i].size > 0)
device_printf(dev,
"note: outbound memory range %d (pci addr: 0x%jx, "
"cpu addr: 0x%jx, size: 0x%jx) will be ignored.\n",
i, ranges[i].pci_base, ranges[i].phys_base,
ranges[i].size);
}
return (error);
}
static const char *
bcm_pcib_link_state_string(uint32_t mode)
{
switch(mode & PCIEM_LINK_STA_SPEED) {
case 0:
return ("not up");
case 1:
return ("2.5 GT/s");
case 2:
return ("5.0 GT/s");
case 4:
return ("8.0 GT/s");
default:
return ("unknown");
}
}
static bus_addr_t
bcm_get_offset_and_prepare_config(struct bcm_pcib_softc *sc, u_int bus,
u_int slot, u_int func, u_int reg)
{
uint32_t func_index;
if (bus == 0 && slot == 0 && func == 0)
return (reg);
func_index = PCIE_ADDR_OFFSET(bus, slot, func, 0);
bcm_pcib_set_reg(sc, REG_EP_CONFIG_CHOICE, func_index);
return (REG_EP_CONFIG_DATA + reg);
}
static bool
bcm_pcib_is_valid_quad(struct bcm_pcib_softc *sc, u_int bus, u_int slot,
u_int func, u_int reg)
{
if ((bus < sc->base.base.bus_start) || (bus > sc->base.base.bus_end))
return (false);
if ((slot > PCI_SLOTMAX) || (func > PCI_FUNCMAX) || (reg > PCIE_REGMAX))
return (false);
if (bus == 0 && slot == 0 && func == 0)
return (true);
if (bus == 0)
return (false);
return (true);
}
static uint32_t
bcm_pcib_read_config(device_t dev, u_int bus, u_int slot, u_int func, u_int reg,
int bytes)
{
struct bcm_pcib_softc *sc;
bus_addr_t offset;
uint32_t data;
sc = device_get_softc(dev);
if (!bcm_pcib_is_valid_quad(sc, bus, slot, func, reg))
return (~0U);
mtx_lock(&sc->config_mtx);
offset = bcm_get_offset_and_prepare_config(sc, bus, slot, func, reg);
switch (bytes) {
case 1:
data = bus_read_1(sc->base.base.res, offset);
break;
case 2:
data = le16toh(bus_read_2(sc->base.base.res, offset));
break;
case 4:
data = le32toh(bus_read_4(sc->base.base.res, offset));
break;
default:
data = ~0U;
break;
}
mtx_unlock(&sc->config_mtx);
return (data);
}
static void
bcm_pcib_write_config(device_t dev, u_int bus, u_int slot,
u_int func, u_int reg, uint32_t val, int bytes)
{
struct bcm_pcib_softc *sc;
uint32_t offset;
sc = device_get_softc(dev);
if (!bcm_pcib_is_valid_quad(sc, bus, slot, func, reg))
return;
mtx_lock(&sc->config_mtx);
offset = bcm_get_offset_and_prepare_config(sc, bus, slot, func, reg);
switch (bytes) {
case 1:
bus_write_1(sc->base.base.res, offset, val);
break;
case 2:
bus_write_2(sc->base.base.res, offset, htole16(val));
break;
case 4:
bus_write_4(sc->base.base.res, offset, htole32(val));
break;
default:
break;
}
mtx_unlock(&sc->config_mtx);
}
static void
bcm_pcib_msi_intr_process(struct bcm_pcib_softc *sc, uint32_t interrupt_bitmap,
struct trapframe *tf)
{
struct bcm_pcib_irqsrc *irqsrc;
uint32_t bit, irq;
while ((bit = ffs(interrupt_bitmap))) {
irq = bit - 1;
bcm_pcib_set_reg(sc, REG_MSI_CLR, 1 << irq);
bcm_pcib_set_reg(sc, REG_MSI_EOI, 1);
irqsrc = &sc->msi_isrcs[irq];
if (intr_isrc_dispatch(&irqsrc->isrc, tf))
device_printf(sc->dev,
"note: unexpected interrupt (%d) triggered.\n",
irq);
interrupt_bitmap = interrupt_bitmap & ~(1 << irq);
}
}
static int
bcm_pcib_msi_intr(void *arg)
{
struct bcm_pcib_softc *sc;
struct trapframe *tf;
uint32_t interrupt_bitmap;
sc = (struct bcm_pcib_softc *) arg;
tf = curthread->td_intr_frame;
while ((interrupt_bitmap = bcm_pcib_read_reg(sc, REG_MSI_RAISED)))
bcm_pcib_msi_intr_process(sc, interrupt_bitmap, tf);
return (FILTER_HANDLED);
}
static int
bcm_pcib_alloc_msi(device_t dev, device_t child, int count, int maxcount,
device_t *pic, struct intr_irqsrc **srcs)
{
struct bcm_pcib_softc *sc;
int first_int, i;
sc = device_get_softc(dev);
mtx_lock(&sc->msi_mtx);
for (first_int = 0; first_int + count < NUM_MSI; ) {
for (i = first_int; i < first_int + count; ++i) {
if (sc->msi_isrcs[i].allocated)
goto next;
}
goto found;
next:
first_int = i + 1;
}
mtx_unlock(&sc->msi_mtx);
device_printf(dev, "warning: failed to allocate %d MSI messages.\n",
count);
return (ENXIO);
found:
for (i = 0; i < count; ++i) {
sc->msi_isrcs[i + first_int].allocated = true;
srcs[i] = &(sc->msi_isrcs[i + first_int].isrc);
}
mtx_unlock(&sc->msi_mtx);
*pic = device_get_parent(dev);
return (0);
}
static int
bcm_pcib_map_msi(device_t dev, device_t child, struct intr_irqsrc *isrc,
uint64_t *addr, uint32_t *data)
{
struct bcm_pcib_softc *sc;
struct bcm_pcib_irqsrc *msi_msg;
sc = device_get_softc(dev);
msi_msg = (struct bcm_pcib_irqsrc *) isrc;
*addr = sc->msi_addr;
*data = (REG_VALUE_MSI_CONFIG & 0xffff) | msi_msg->irq;
return (0);
}
static int
bcm_pcib_release_msi(device_t dev, device_t child, int count,
struct intr_irqsrc **isrc)
{
struct bcm_pcib_softc *sc;
struct bcm_pcib_irqsrc *msi_isrc;
int i;
sc = device_get_softc(dev);
mtx_lock(&sc->msi_mtx);
for (i = 0; i < count; i++) {
msi_isrc = (struct bcm_pcib_irqsrc *) isrc[i];
msi_isrc->allocated = false;
}
mtx_unlock(&sc->msi_mtx);
return (0);
}
static int
bcm_pcib_msi_attach(device_t dev)
{
struct bcm_pcib_softc *sc;
phandle_t node, xref;
char const *bcm_name;
int error, i, rid;
sc = device_get_softc(dev);
sc->msi_addr = 0xffffffffc;
bcm_pcib_set_reg(sc, REG_MSI_CLR, 0xffffffff);
rid = 1;
sc->msi_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
RF_ACTIVE);
if (sc->msi_irq_res == NULL) {
device_printf(dev, "could not allocate MSI irq resource.\n");
return (ENXIO);
}
sc->msi_isrcs = malloc(sizeof(*sc->msi_isrcs) * NUM_MSI, M_DEVBUF,
M_WAITOK | M_ZERO);
error = bus_setup_intr(dev, sc->msi_irq_res, INTR_TYPE_BIO |
INTR_MPSAFE, bcm_pcib_msi_intr, NULL, sc, &sc->msi_intr_cookie);
if (error != 0) {
device_printf(dev, "error: failed to setup MSI handler.\n");
return (error);
}
bcm_name = device_get_nameunit(dev);
for (i = 0; i < NUM_MSI; i++) {
sc->msi_isrcs[i].irq = i;
error = intr_isrc_register(&sc->msi_isrcs[i].isrc, dev, 0,
"%s,%u", bcm_name, i);
if (error != 0) {
device_printf(dev,
"error: failed to register interrupt %d.\n", i);
return (error);
}
}
node = ofw_bus_get_node(dev);
xref = OF_xref_from_node(node);
OF_device_register_xref(xref, dev);
error = intr_msi_register(dev, xref);
if (error != 0)
return (error);
mtx_init(&sc->msi_mtx, "bcm_pcib: msi_mtx", NULL, MTX_DEF);
bcm_pcib_set_reg(sc, REG_MSI_MASK_CLR, 0xffffffff);
bcm_pcib_set_reg(sc, REG_MSI_ADDR_LOW, (sc->msi_addr & 0xffffffff) | 1);
bcm_pcib_set_reg(sc, REG_MSI_ADDR_HIGH, (sc->msi_addr >> 32));
bcm_pcib_set_reg(sc, REG_MSI_CONFIG, REG_VALUE_MSI_CONFIG);
return (0);
}
static void
bcm_pcib_relocate_bridge_window(device_t dev)
{
struct bcm_pcib_softc *sc;
pci_addr_t base, size, new_base, new_limit;
uint16_t val;
sc = device_get_softc(dev);
val = bcm_pcib_read_config(dev, 0, 0, 0, PCIR_MEMBASE_1, 2);
base = PCI_PPBMEMBASE(0, val);
val = bcm_pcib_read_config(dev, 0, 0, 0, PCIR_MEMLIMIT_1, 2);
size = PCI_PPBMEMLIMIT(0, val) - base;
new_base = sc->base.base.ranges[0].pci_base;
val = (uint16_t) (new_base >> 16);
bcm_pcib_write_config(dev, 0, 0, 0, PCIR_MEMBASE_1, val, 2);
new_limit = new_base + size;
val = (uint16_t) (new_limit >> 16);
bcm_pcib_write_config(dev, 0, 0, 0, PCIR_MEMLIMIT_1, val, 2);
}
static uint32_t
encode_cpu_window_low(pci_addr_t phys_base, bus_size_t size)
{
return (((phys_base >> 0x10) & 0xfff0) |
((phys_base + size - 1) & 0xfff00000));
}
static uint32_t
encode_cpu_window_start_high(pci_addr_t phys_base)
{
return ((phys_base >> 0x20) & 0xff);
}
static uint32_t
encode_cpu_window_end_high(pci_addr_t phys_base, bus_size_t size)
{
return (((phys_base + size - 1) >> 0x20) & 0xff);
}
static int
bcm_pcib_attach(device_t dev)
{
struct bcm_pcib_softc *sc;
pci_addr_t phys_base, pci_base;
bus_size_t size;
uint32_t hardware_rev, bridge_state, link_state, tmp;
int error, tries;
sc = device_get_softc(dev);
sc->dev = dev;
error = bus_dma_tag_create(bus_get_dma_tag(dev),
1, 0,
DMA_HIGH_LIMIT,
BUS_SPACE_MAXADDR,
NULL, NULL,
DMA_HIGH_LIMIT,
BUS_SPACE_UNRESTRICTED,
DMA_HIGH_LIMIT,
0,
NULL, NULL,
&sc->dmat);
if (error != 0)
return (error);
error = pci_host_generic_setup_fdt(dev);
if (error != 0)
return (error);
error = bcm_pcib_check_ranges(dev);
if (error != 0)
return (error);
mtx_init(&sc->config_mtx, "bcm_pcib: config_mtx", NULL, MTX_DEF);
bcm_pcib_reset_controller(sc);
hardware_rev = bcm_pcib_read_reg(sc, REG_CONTROLLER_HW_REV) & 0xffff;
device_printf(dev, "hardware identifies as revision 0x%x.\n",
hardware_rev);
bcm_pcib_set_reg(sc, REG_DMA_WINDOW_LOW, REG_VALUE_DMA_WINDOW_LOW);
bcm_pcib_set_reg(sc, REG_DMA_WINDOW_HIGH, REG_VALUE_DMA_WINDOW_HIGH);
bcm_pcib_set_reg(sc, REG_DMA_CONFIG, REG_VALUE_DMA_WINDOW_CONFIG);
bcm_pcib_set_reg(sc, REG_BRIDGE_GISB_WINDOW, 0);
bcm_pcib_set_reg(sc, REG_DMA_WINDOW_1, 0);
bcm_pcib_enable_controller(sc);
for(tries = 0; ; ++tries) {
bridge_state = bcm_pcib_read_reg(sc, REG_BRIDGE_STATE);
if ((bridge_state & 0x30) == 0x30)
break;
if (tries > 100) {
device_printf(dev,
"error: controller failed to start.\n");
return (ENXIO);
}
DELAY(1000);
}
link_state = bcm_pcib_read_reg(sc, REG_BRIDGE_LINK_STATE) >> 0x10;
if (!link_state) {
device_printf(dev, "error: controller started but link is not "
"up.\n");
return (ENXIO);
}
if (bootverbose)
device_printf(dev, "note: reported link speed is %s.\n",
bcm_pcib_link_state_string(link_state));
pci_base = sc->base.base.ranges[0].pci_base;
phys_base = sc->base.base.ranges[0].phys_base;
size = sc->base.base.ranges[0].size;
bcm_pcib_set_reg(sc, REG_BUS_WINDOW_LOW, pci_base & 0xffffffff);
bcm_pcib_set_reg(sc, REG_BUS_WINDOW_HIGH, pci_base >> 32);
bcm_pcib_set_reg(sc, REG_CPU_WINDOW_LOW,
encode_cpu_window_low(phys_base, size));
bcm_pcib_set_reg(sc, REG_CPU_WINDOW_START_HIGH,
encode_cpu_window_start_high(phys_base));
bcm_pcib_set_reg(sc, REG_CPU_WINDOW_END_HIGH,
encode_cpu_window_end_high(phys_base, size));
bcm_pcib_set_reg(sc, PCI_ID_VAL3,
PCIC_BRIDGE << CLASS_SHIFT | PCIS_BRIDGE_PCI << SUBCLASS_SHIFT);
tmp = bcm_pcib_read_reg(sc, REG_PCIE_HARD_DEBUG);
tmp |= CLKREQ_ENABLE;
if (ofw_bus_has_prop(dev, "brcm,enable-l1ss")) {
if (bootverbose)
device_printf(dev, "note: enabling L1SS due to OF "
"property brcm,enable-l1ss\n");
tmp |= L1SS_ENABLE;
}
bcm_pcib_set_reg(sc, REG_PCIE_HARD_DEBUG, tmp);
DELAY(100);
bcm_pcib_relocate_bridge_window(dev);
error = bcm_pcib_msi_attach(dev);
if (error != 0)
return (error);
device_add_child(dev, "pci", DEVICE_UNIT_ANY);
bus_attach_children(dev);
return (0);
}
static device_method_t bcm_pcib_methods[] = {
DEVMETHOD(bus_get_dma_tag, bcm_pcib_get_dma_tag),
DEVMETHOD(device_probe, bcm_pcib_probe),
DEVMETHOD(device_attach, bcm_pcib_attach),
DEVMETHOD(pcib_read_config, bcm_pcib_read_config),
DEVMETHOD(pcib_write_config, bcm_pcib_write_config),
DEVMETHOD(msi_alloc_msi, bcm_pcib_alloc_msi),
DEVMETHOD(msi_release_msi, bcm_pcib_release_msi),
DEVMETHOD(msi_map_msi, bcm_pcib_map_msi),
DEVMETHOD_END
};
DEFINE_CLASS_1(pcib, bcm_pcib_driver, bcm_pcib_methods,
sizeof(struct bcm_pcib_softc), generic_pcie_fdt_driver);
DRIVER_MODULE(bcm_pcib, simplebus, bcm_pcib_driver, 0, 0);