/*- * Copyright (c) 2015 Stanislav Galabov. * * 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. * * This is based on the pci allocator code from sys/dev/arm/mv/: * * Copyright (c) 2008 MARVELL INTERNATIONAL LTD. * Copyright (c) 2010 The FreeBSD Foundation * Copyright (c) 2010-2012 Semihalf * All rights reserved. * * Developed by Semihalf. */ #include __FBSDID("$FreeBSD$"); #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 #include #include struct mtx rt305x_pci_mtx; MTX_SYSINIT(rt305x_pci_mtx, &rt305x_pci_mtx, "rt305x PCI/PCIe mutex", MTX_SPIN); struct rt305x_pci_softc { device_t sc_dev; bus_space_tag_t sc_bst; bus_space_handle_t sc_bsh; int sc_busno; struct rman sc_mem_rman; struct rman sc_io_rman; struct rman sc_irq_rman; bus_addr_t sc_mem_base; bus_addr_t sc_mem_size; uint32_t sc_mem_map[(256*1024*1024) / (PCI_MIN_MEM_ALLOC * BITS_PER_UINT32)]; bus_addr_t sc_io_base; bus_addr_t sc_io_size; uint32_t sc_io_map[(16*1024*1024) / (PCI_MIN_IO_ALLOC * BITS_PER_UINT32)]; struct intr_event *sc_eventstab[RT305X_PCI_NIRQS]; mips_intrcnt_t sc_intr_counter[RT305X_PCI_NIRQS]; int pcie_link_status; }; static void rt305x_pci_phy_init(device_t); static void rt305x_pci_init(device_t); static int rt305x_pcib_init(device_t, int, int); static int rt305x_pci_intr(void *); static void rt305x_pci_dump_regs(device_t); static struct rt305x_pci_softc *rt_sc = NULL; static int rt305x_pci_probe(device_t dev) { return (BUS_PROBE_NOWILDCARD); } static int rt305x_pci_attach(device_t dev) { struct rt305x_pci_softc *sc = device_get_softc(dev); rt_sc = sc; sc->sc_dev = dev; sc->sc_mem_base = PCIE_MEM_BASE; sc->sc_mem_size = 0x10000000; sc->sc_io_base = PCIE_IO_BASE; sc->sc_io_size = 0x10000; sc->sc_bsh = MIPS_PHYS_TO_KSEG1(PCIE_BASE); sc->sc_bst = mips_bus_space_generic; sc->sc_mem_rman.rm_type = RMAN_ARRAY; sc->sc_mem_rman.rm_descr = "rt305x pci memory window"; if (rman_init(&sc->sc_mem_rman) != 0 || rman_manage_region(&sc->sc_mem_rman, sc->sc_mem_base, sc->sc_mem_base + sc->sc_mem_size - 1) != 0) { panic("%s: failed to set up memory rman", __FUNCTION__); } sc->sc_io_rman.rm_type = RMAN_ARRAY; sc->sc_io_rman.rm_descr = "rt305x pci io window"; if (rman_init(&sc->sc_io_rman) != 0 || rman_manage_region(&sc->sc_io_rman, sc->sc_io_base, sc->sc_io_base + sc->sc_io_size - 1) != 0) { panic("%s: failed to set up io rman", __FUNCTION__); } sc->sc_irq_rman.rm_type = RMAN_ARRAY; sc->sc_irq_rman.rm_descr = "rt305x pci irqs"; if (rman_init(&sc->sc_irq_rman) != 0 || rman_manage_region(&sc->sc_irq_rman, RT305X_PCIE0_IRQ, RT305X_PCIE0_IRQ) != 0) { panic("%s: failed to set up irq rman", __FUNCTION__); } cpu_establish_hardintr("pci", rt305x_pci_intr, NULL, sc, RT305X_PCI_INTR_PIN, INTR_TYPE_MISC | INTR_EXCL, NULL); rt305x_pci_phy_init(dev); rt305x_pci_init(dev); rt305x_pci_dump_regs(dev); rt305x_pcib_init(dev, 0, PCI_SLOTMAX); device_add_child(dev, "pci", -1); return (bus_generic_attach(dev)); } static int rt305x_pci_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) { struct rt305x_pci_softc *sc = device_get_softc(dev); switch (which) { case PCIB_IVAR_DOMAIN: *result = device_get_unit(dev); return (0); case PCIB_IVAR_BUS: *result = sc->sc_busno; return (0); } return (ENOENT); } static int rt305x_pci_write_ivar(device_t dev, device_t child, int which, uintptr_t result) { struct rt305x_pci_softc *sc = device_get_softc(dev); switch (which) { case PCIB_IVAR_BUS: sc->sc_busno = result; return (0); } return (ENOENT); } static struct resource * rt305x_pci_alloc_resource(device_t bus, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct rt305x_pci_softc *sc = device_get_softc(bus); struct resource *rv; struct rman *rm; vm_offset_t va; switch (type) { case SYS_RES_IRQ: rm = &sc->sc_irq_rman; break; case SYS_RES_IOPORT: rm = &sc->sc_io_rman; break; case SYS_RES_MEMORY: rm = &sc->sc_mem_rman; break; default: return (NULL); } rv = rman_reserve_resource(rm, start, end, count, flags, child); if (rv == NULL) return (NULL); rman_set_rid(rv, *rid); if (type != SYS_RES_IRQ) { if (type == SYS_RES_MEMORY) { va = (vm_offset_t)pmap_mapdev(start, count); } else if (type == SYS_RES_IOPORT){ va = (vm_offset_t)MIPS_PHYS_TO_KSEG1(start); } rman_set_bushandle(rv, va); rman_set_virtual(rv, (void *)va); rman_set_bustag(rv, mips_bus_space_generic); } if (flags & RF_ACTIVE) { if (bus_activate_resource(child, type, *rid, rv)) { rman_release_resource(rv); return (NULL); } } return (rv); } static int rt305x_pci_activate_resource(device_t bus, device_t child, int type, int rid, struct resource *r) { return rman_activate_resource(r); } static inline int rt305x_idx_to_irq(int idx) { return ((idx == 0) ? RT305X_PCIE0_IRQ : (idx == 1) ? RT305X_PCIE1_IRQ : (idx == 2) ? RT305X_PCIE2_IRQ : -1); } static inline int rt305x_irq_to_idx(int irq) { return ((irq == RT305X_PCIE0_IRQ) ? 0 : (irq == RT305X_PCIE1_IRQ) ? 1 : (irq == RT305X_PCIE2_IRQ) ? 2 : -1); } static void rt305x_pci_mask_irq(void *source) { RT_WRITE32(rt_sc, RT305X_PCI_PCIENA, RT_READ32(rt_sc, RT305X_PCI_PCIENA) & ~(1<<((int)source))); } static void rt305x_pci_unmask_irq(void *source) { RT_WRITE32(rt_sc, RT305X_PCI_PCIENA, RT_READ32(rt_sc, RT305X_PCI_PCIENA) | (1<<((int)source))); } static int rt305x_pci_setup_intr(device_t bus, device_t child, struct resource *ires, int flags, driver_filter_t *filt, driver_intr_t *handler, void *arg, void **cookiep) { struct rt305x_pci_softc *sc = device_get_softc(bus); struct intr_event *event; int irq, error, irqidx; irq = rman_get_start(ires); if ((irqidx = rt305x_irq_to_idx(irq)) == -1) panic("%s: bad irq %d", __FUNCTION__, irq); event = sc->sc_eventstab[irqidx]; if (event == NULL) { error = intr_event_create(&event, (void *)irq, 0, irq, rt305x_pci_mask_irq, rt305x_pci_unmask_irq, NULL, NULL, "pci intr%d:", irq); if (error == 0) { sc->sc_eventstab[irqidx] = event; sc->sc_intr_counter[irqidx] = mips_intrcnt_create(event->ie_name); } else return (error); } intr_event_add_handler(event, device_get_nameunit(child), filt, handler, arg, intr_priority(flags), flags, cookiep); mips_intrcnt_setname(sc->sc_intr_counter[irqidx], event->ie_fullname); rt305x_pci_unmask_irq((void*)irq); return (0); } static int rt305x_pci_teardown_intr(device_t dev, device_t child, struct resource *ires, void *cookie) { struct rt305x_pci_softc *sc = device_get_softc(dev); int irq, result, irqidx; irq = rman_get_start(ires); if ((irqidx = rt305x_irq_to_idx(irq)) == -1) panic("%s: bad irq %d", __FUNCTION__, irq); if (sc->sc_eventstab[irqidx] == NULL) panic("Trying to teardown unoccupied IRQ"); rt305x_pci_mask_irq((void*)irq); result = intr_event_remove_handler(cookie); if (!result) sc->sc_eventstab[irqidx] = NULL; return (result); } static inline uint32_t rt305x_pci_make_addr(int bus, int slot, int func, int reg) { uint32_t addr; addr = (((reg & 0xf00) >> 8) << 24) | (bus << 16) | (slot << 11) | (func << 8) | (reg & 0xfc) | (1 << 31); return (addr); } static int rt305x_pci_maxslots(device_t dev) { return (PCI_SLOTMAX); } static uint32_t rt305x_pci_read_config(device_t dev, u_int bus, u_int slot, u_int func, u_int reg, int bytes) { struct rt305x_pci_softc *sc = device_get_softc(dev); uint32_t addr = 0, data = 0; if (bus == 0 && (sc->pcie_link_status & (1<pcie_link_status & (1<sc_mem_base); RT_WRITE32(sc, RT305X_PCI_IOBASE, sc->sc_io_base); RT_WRITE32(sc, RT305X_PCI_PCICFG, RT_READ32(sc, 0) & ~(1<<1)); DELAY(500000); if ((RT_READ32(sc, RT305X_PCI_PCIE0_STATUS) & 0x1) == 1) sc->pcie_link_status = 1; else sc->pcie_link_status = 0; RT_WRITE32(sc, RT305X_PCI_PCIE0_BAR0SETUP, 0x7FFF0001); RT_WRITE32(sc, RT305X_PCI_PCIE0_BAR1SETUP, 0x00000000); RT_WRITE32(sc, RT305X_PCI_PCIE0_IMBASEBAR0, 0x00000000); RT_WRITE32(sc, RT305X_PCI_PCIE0_CLASS, 0x06040001); tmp = rt305x_pci_read_config(dev, 0, 0, 0, 4, 4); rt305x_pci_write_config(dev, 0, 0, 0, 4, tmp | 0x7, 4); tmp = rt305x_pci_read_config(dev, 0, 0, 0, 0x70c, 4); tmp &= ~(0xff)<<8; tmp |= 0x50<<8; rt305x_pci_write_config(dev, 0, 0, 0, 0x70c, tmp, 4); tmp = rt305x_pci_read_config(dev, 0, 0, 0, 0x70c, 4); rt305x_pci_write_config(dev, 0, 0, 0, PCIR_BAR(0), 0, 4); } static inline uint32_t pcib_bit_get(uint32_t *map, uint32_t bit) { uint32_t n = bit / BITS_PER_UINT32; bit = bit % BITS_PER_UINT32; return (map[n] & (1 << bit)); } static inline void pcib_bit_set(uint32_t *map, uint32_t bit) { uint32_t n = bit / BITS_PER_UINT32; bit = bit % BITS_PER_UINT32; map[n] |= (1 << bit); } static inline uint32_t pcib_map_check(uint32_t *map, uint32_t start, uint32_t bits) { uint32_t i; for (i = start; i < start + bits; i++) if (pcib_bit_get(map, i)) return (0); return (1); } static inline void pcib_map_set(uint32_t *map, uint32_t start, uint32_t bits) { uint32_t i; for (i = start; i < start + bits; i++) pcib_bit_set(map, i); } static bus_addr_t pcib_alloc(device_t dev, uint32_t smask) { struct rt305x_pci_softc *sc = device_get_softc(dev); uint32_t bits, bits_limit, i, *map, min_alloc, size; bus_addr_t addr = 0; bus_addr_t base; if (smask & 1) { base = sc->sc_io_base; min_alloc = PCI_MIN_IO_ALLOC; bits_limit = sc->sc_io_size / min_alloc; map = sc->sc_io_map; smask &= ~0x3; } else { base = sc->sc_mem_base; min_alloc = PCI_MIN_MEM_ALLOC; bits_limit = sc->sc_mem_size / min_alloc; map = sc->sc_mem_map; smask &= ~0xF; } size = ~smask + 1; bits = size / min_alloc; for (i = 0; i + bits <= bits_limit; i+= bits) if (pcib_map_check(map, i, bits)) { pcib_map_set(map, i, bits); addr = base + (i * min_alloc); return (addr); } return (addr); } static int rt305x_pcib_init_bar(device_t dev, int bus, int slot, int func, int barno) { uint32_t addr, bar; int reg, width; reg = PCIR_BAR(barno); rt305x_pci_write_config(dev, bus, slot, func, reg, ~0, 4); bar = rt305x_pci_read_config(dev, bus, slot, func, reg, 4); if (bar == 0) return (1); /* Calculate BAR size: 64 or 32 bit (in 32-bit units) */ width = ((bar & 7) == 4) ? 2 : 1; addr = pcib_alloc(dev, bar); if (!addr) return (-1); if (bootverbose) printf("PCI %u:%u:%u: reg %x: smask=%08x: addr=%08x\n", bus, slot, func, reg, bar, addr); rt305x_pci_write_config(dev, bus, slot, func, reg, addr, 4); if (width == 2) rt305x_pci_write_config(dev, bus, slot, func, reg + 4, 0, 4); return (width); } static int rt305x_pcib_init_all_bars(device_t dev, int bus, int slot, int func, int hdrtype) { int maxbar, bar, i; maxbar = (hdrtype & PCIM_HDRTYPE) ? 0 : 6; bar = 0; while (bar < maxbar) { i = rt305x_pcib_init_bar(dev, bus, slot, func, bar); bar += i; if (i < 0) { device_printf(dev, "PCI IO/Memory space exhausted\n"); return (ENOMEM); } } return (0); } static inline int rt305x_pci_slot_has_link(device_t dev, int slot) { struct rt305x_pci_softc *sc = device_get_softc(dev); return !!(sc->pcie_link_status & (1<sc_io_base; io_limit = io_base + sc->sc_io_size - 1; mem_base = sc->sc_mem_base; mem_limit = mem_base + sc->sc_mem_size - 1; rt305x_pci_write_config(dev, bus, slot, func, PCIR_IOBASEL_1, io_base >> 8, 1); rt305x_pci_write_config(dev, bus, slot, func, PCIR_IOBASEH_1, io_base >> 16, 2); rt305x_pci_write_config(dev, bus, slot, func, PCIR_IOLIMITL_1, io_limit >> 8, 1); rt305x_pci_write_config(dev, bus, slot, func, PCIR_IOLIMITH_1, io_limit >> 16, 2); rt305x_pci_write_config(dev, bus, slot, func, PCIR_MEMBASE_1, mem_base >> 16, 2); rt305x_pci_write_config(dev, bus, slot, func, PCIR_MEMLIMIT_1, mem_limit >> 16, 2); rt305x_pci_write_config(dev, bus, slot, func, PCIR_PMBASEL_1, 0x10, 2); rt305x_pci_write_config(dev, bus, slot, func, PCIR_PMBASEH_1, 0x0, 4); rt305x_pci_write_config(dev, bus, slot, func, PCIR_PMLIMITL_1, 0xF, 2); rt305x_pci_write_config(dev, bus, slot, func, PCIR_PMLIMITH_1, 0x0, 4); secbus = rt305x_pci_read_config(dev, bus, slot, func, PCIR_SECBUS_1, 1); if (secbus == 0) { rt305x_pci_write_config(dev, bus, slot, func, PCIR_SECBUS_1, ++cur_secbus, 1); rt305x_pci_write_config(dev, bus, slot, func, PCIR_SUBBUS_1, cur_secbus, 1); secbus = cur_secbus; } rt305x_pcib_init(dev, secbus, PCI_SLOTMAX); } static int rt305x_pcib_init(device_t dev, int bus, int maxslot) { int slot, func, maxfunc, error; uint8_t hdrtype, command, class, subclass; for (slot = 0; slot <= maxslot; slot++) { maxfunc = 0; for (func = 0; func <= maxfunc; func++) { hdrtype = rt305x_pci_read_config(dev, bus, slot, func, PCIR_HDRTYPE, 1); if ((hdrtype & PCIM_HDRTYPE) > PCI_MAXHDRTYPE) continue; if (func == 0 && (hdrtype & PCIM_MFDEV)) maxfunc = PCI_FUNCMAX; command = rt305x_pci_read_config(dev, bus, slot, func, PCIR_COMMAND, 1); command &= ~(PCIM_CMD_MEMEN | PCIM_CMD_PORTEN); rt305x_pci_write_config(dev, bus, slot, func, PCIR_COMMAND, command, 1); error = rt305x_pcib_init_all_bars(dev, bus, slot, func, hdrtype); if (error) return (error); command |= PCIM_CMD_BUSMASTEREN | PCIM_CMD_MEMEN | PCIM_CMD_PORTEN; rt305x_pci_write_config(dev, bus, slot, func, PCIR_COMMAND, command, 1); rt305x_pci_write_config(dev, bus, slot, func, PCIR_CACHELNSZ, 16, 1); class = rt305x_pci_read_config(dev, bus, slot, func, PCIR_CLASS, 1); subclass = rt305x_pci_read_config(dev, bus, slot, func, PCIR_SUBCLASS, 1); if (class != PCIC_BRIDGE || subclass != PCIS_BRIDGE_PCI) continue; rt305x_pcib_init_bridge(dev, bus, slot, func); } } return (0); } #define BUSY 0x80000000 #define WAITRETRY_MAX 10 #define WRITE_MODE (1<<23) #define DATA_SHIFT 0 #define ADDR_SHIFT 8 static int rt305x_wait_pci_phy_busy(struct rt305x_pci_softc *sc) { uint32_t reg_value = 0x0, retry = 0; while (1) { reg_value = RT_READ32(sc, RT305X_PCI_PHY0_CFG); if (reg_value & BUSY) DELAY(100000); else break; if (retry++ > WAITRETRY_MAX) { printf("PHY retry failed\n"); return (-1); } } return (0); } static uint32_t rt305x_pci_phy(struct rt305x_pci_softc *sc, char rwmode, uint32_t addr, uint32_t val) { uint32_t reg_value = 0x0; rt305x_wait_pci_phy_busy(sc); if (rwmode == 'w') { reg_value |= WRITE_MODE; reg_value |= (val) << DATA_SHIFT; } reg_value |= (addr) << ADDR_SHIFT; RT_WRITE32(sc, RT305X_PCI_PHY0_CFG, reg_value); DELAY(1000); rt305x_wait_pci_phy_busy(sc); if (rwmode == 'r') { reg_value = RT_READ32(sc, RT305X_PCI_PHY0_CFG); return (reg_value); } return (0); } static void rt305x_pci_phy_init(device_t dev) { struct rt305x_pci_softc *sc = device_get_softc(dev); uint32_t tmp; rt305x_pci_phy(sc, 'w', 0x00, 0x80); rt305x_pci_phy(sc, 'w', 0x01, 0x04); rt305x_pci_phy(sc, 'w', 0x68, 0x84); rt305x_sysctl_set(SYSCTL_RSTCTRL, rt305x_sysctl_get(SYSCTL_RSTCTRL) | (1<<26)); rt305x_sysctl_set(SYSCTL_CLKCFG1, rt305x_sysctl_get(SYSCTL_CLKCFG1) & ~(1<<26)); tmp = rt305x_sysctl_get(SYSCTL_PPLL_CFG1); tmp &= ~(1<<19); rt305x_sysctl_set(SYSCTL_PPLL_CFG1, tmp); tmp |= (1<<31); rt305x_sysctl_set(SYSCTL_PPLL_CFG1, tmp); } static int rt305x_pci_intr(void *arg) { struct rt305x_pci_softc *sc = arg; struct intr_event *event; uint32_t reg, irq, irqidx; reg = RT_READ32(sc, RT305X_PCI_PCIINT); for (irqidx = 0; irqidx < RT305X_PCI_NIRQS; irqidx++) { irq = rt305x_idx_to_irq(irqidx); if (reg & (1<sc_eventstab[irqidx]; if (!event || TAILQ_EMPTY(&event->ie_handlers)) { if (irq != 0) printf("Stray PCI IRQ %d\n", irq); continue; } intr_event_handle(event, NULL); mips_intrcnt_inc(sc->sc_intr_counter[irqidx]); } } return (FILTER_HANDLED); }