Index: head/sys/arm/broadcom/bcm2835/bcm2838_pci.c =================================================================== --- head/sys/arm/broadcom/bcm2835/bcm2838_pci.c (revision 363120) +++ head/sys/arm/broadcom/bcm2835/bcm2838_pci.c (revision 363121) @@ -1,743 +1,743 @@ /*- * SPDX-License-Identifier: ISC * * Copyright (c) 2020 Dr Robert Harvey Crowston * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * * $FreeBSD$ * */ /* * BCM2838-compatible PCI-express controller. * * Broadcom likes to give the same chip lots of different names. The name of * this driver is taken from the Raspberry Pi 4 Broadcom 2838 chip. */ #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 "msi_if.h" extern struct bus_space memmap_bus; #define BUS_SPACE_3G_MAXADDR 0xc0000000 #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_BRIDGE_SERDES_MODE 0x4204 #define REG_BRIDGE_CONFIG 0x4008 #define REG_BRIDGE_MEM_WINDOW_LOW 0x4034 #define REG_BRIDGE_MEM_WINDOW_HIGH 0x4038 #define REG_BRIDGE_MEM_WINDOW_1 0x403c #define REG_BRIDGE_GISB_WINDOW 0x402c #define REG_BRIDGE_STATE 0x4068 #define REG_BRIDGE_LINK_STATE 0x00bc #define REG_BRIDGE_BUS_WINDOW_LOW 0x400c #define REG_BRIDGE_BUS_WINDOW_HIGH 0x4010 #define REG_BRIDGE_CPU_WINDOW_LOW 0x4070 #define REG_BRIDGE_CPU_WINDOW_START_HIGH 0x4080 #define REG_BRIDGE_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 /* * These values were obtained from runtime inspection of a Linux system using a * JTAG. The very limited documentation I have obtained from Broadcom does not * explain how to compute them. */ #define REG_VALUE_4GB_WINDOW 0x11 #define REG_VALUE_4GB_CONFIG 0x88003000 #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; 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 void bcm_pcib_set_reg(struct bcm_pcib_softc *sc, uint32_t reg, uint32_t val) { bus_space_write_4(sc->base.base.bst, sc->base.base.bsh, reg, htole32(val)); } static uint32_t bcm_pcib_read_reg(struct bcm_pcib_softc *sc, uint32_t reg) { return (le32toh(bus_space_read_4(sc->base.base.bst, sc->base.base.bsh, 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_BRIDGE_SERDES_MODE, 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]; /* The first range needs to be non-zero. */ 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; } /* * The controller can actually handle three distinct ranges, but we * only implement support for one. */ 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) { /* * Config for an end point is only available through a narrow window for * one end point at a time. We first tell the controller which end point * we want, then access it through the window. */ uint32_t func_index; if (bus == 0 && slot == 0 && func == 0) /* * Special case for root device; its config is always available * through the zero-offset. */ return (reg); /* Tell the controller to show us the config in question. */ 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) /* * Probing other slots and funcs on bus 0 will lock up the * memory controller. */ 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_space_handle_t h; bus_space_tag_t t; 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); t = sc->base.base.bst; h = sc->base.base.bsh; switch (bytes) { case 1: data = bus_space_read_1(t, h, offset); break; case 2: data = le16toh(bus_space_read_2(t, h, offset)); break; case 4: data = le32toh(bus_space_read_4(t, h, 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; bus_space_handle_t h; bus_space_tag_t t; 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); t = sc->base.base.bst; h = sc->base.base.bsh; switch (bytes) { case 1: bus_space_write_1(t, h, offset, val); break; case 2: bus_space_write_2(t, h, offset, htole16(val)); break; case 4: bus_space_write_4(t, h, 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; /* Acknowledge interrupt. */ bcm_pcib_set_reg(sc, REG_MSI_CLR, 1 << irq); /* Send EOI. */ bcm_pcib_set_reg(sc, REG_MSI_EOI, 1); /* Despatch to handler. */ irqsrc = &sc->msi_isrcs[irq]; if (intr_isrc_dispatch(&irqsrc->isrc, tf)) device_printf(sc->dev, "note: unexpected interrupt (%d) triggered.\n", irq); /* Done with this interrupt. */ 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); /* Find a continguous region of free message-signalled interrupts. */ 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; } /* No appropriate region available. */ mtx_unlock(&sc->msi_mtx); device_printf(dev, "warning: failed to allocate %d MSI messages.\n", count); return (ENXIO); found: /* Mark the messages as in use. */ 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 i, rid; sc = device_get_softc(dev); sc->msi_addr = 0xffffffffc; /* Clear any pending interrupts. */ 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); int 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) { device_printf(dev, "error: failed to setup MSI handler.\n"); return (ENXIO); } 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) { device_printf(dev, "error: failed to register interrupt %d.\n", i); return (ENXIO); } } 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) return (ENXIO); 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) { /* * In principle an out-of-bounds bridge window could be automatically * adjusted at resource-activation time to lie within the bus address * space by pcib_grow_window(), but that is not possible because the * out-of-bounds resource allocation fails at allocation time. Instead, * we will just fix up the window on the controller here, before it is * re-discovered by pcib_probe_windows(). */ 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; int error, tries; sc = device_get_softc(dev); sc->dev = dev; error = pci_host_generic_setup_fdt(dev); if (error) return (error); error = bcm_pcib_check_ranges(dev); if (error) 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); /* * Set PCI->CPU memory window. This encodes the inbound window showing * up to 4 GiB of system memory to the controller, with zero offset. * Thus, from the perspective of a device on the PCI-E bus, there is a * 1:1 map from PCI-E bus addresses to system memory addresses. However, * a hardware limitation means that the controller can only perform DMA * on the lower 3 GiB of system memory. */ bcm_pcib_set_reg(sc, REG_BRIDGE_MEM_WINDOW_LOW, REG_VALUE_4GB_WINDOW); bcm_pcib_set_reg(sc, REG_BRIDGE_MEM_WINDOW_HIGH, 0); bcm_pcib_set_reg(sc, REG_BRIDGE_CONFIG, REG_VALUE_4GB_CONFIG); bcm_pcib_set_reg(sc, REG_BRIDGE_GISB_WINDOW, 0); bcm_pcib_set_reg(sc, REG_BRIDGE_MEM_WINDOW_1, 0); bcm_pcib_enable_controller(sc); /* Wait for controller to start. */ for(tries = 0; ; ++tries) { bridge_state = bcm_pcib_read_reg(sc, REG_BRIDGE_STATE); if ((bridge_state & 0x30) == 0x30) /* Controller ready. */ 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)); /* * Set the CPU->PCI memory window. The map in this direction is not 1:1. * Addresses seen by the CPU need to be adjusted to make sense to the * controller as they pass through the window. */ 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_BRIDGE_BUS_WINDOW_LOW, pci_base & 0xffffffff); bcm_pcib_set_reg(sc, REG_BRIDGE_BUS_WINDOW_HIGH, pci_base >> 32); bcm_pcib_set_reg(sc, REG_BRIDGE_CPU_WINDOW_LOW, encode_cpu_window_low(phys_base, size)); bcm_pcib_set_reg(sc, REG_BRIDGE_CPU_WINDOW_START_HIGH, encode_cpu_window_start_high(phys_base)); bcm_pcib_set_reg(sc, REG_BRIDGE_CPU_WINDOW_END_HIGH, encode_cpu_window_end_high(phys_base, size)); /* * The controller starts up declaring itself an endpoint; readvertise it * as a bridge. */ bcm_pcib_set_reg(sc, PCI_ID_VAL3, PCIC_BRIDGE << CLASS_SHIFT | PCIS_BRIDGE_PCI << SUBCLASS_SHIFT); bcm_pcib_set_reg(sc, REG_BRIDGE_SERDES_MODE, 0x2); DELAY(100); bcm_pcib_relocate_bridge_window(dev); /* Configure interrupts. */ error = bcm_pcib_msi_attach(dev); if (error) return (error); /* Done. */ device_add_child(dev, "pci", -1); return (bus_generic_attach(dev)); } /* * Device method table. */ static device_method_t bcm_pcib_methods[] = { /* Device interface. */ DEVMETHOD(device_probe, bcm_pcib_probe), DEVMETHOD(device_attach, bcm_pcib_attach), /* PCIB interface. */ DEVMETHOD(pcib_read_config, bcm_pcib_read_config), DEVMETHOD(pcib_write_config, bcm_pcib_write_config), /* MSI interface. */ 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); static devclass_t bcm_pcib_devclass; -DRIVER_MODULE(pcib, simplebus, bcm_pcib_driver, bcm_pcib_devclass, 0, 0); +DRIVER_MODULE(bcm_pcib, simplebus, bcm_pcib_driver, bcm_pcib_devclass, 0, 0); Index: head/sys/arm/mv/mvebu_gpio.c =================================================================== --- head/sys/arm/mv/mvebu_gpio.c (revision 363120) +++ head/sys/arm/mv/mvebu_gpio.c (revision 363121) @@ -1,869 +1,869 @@ /*- * Copyright (c) 2020 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. */ #include __FBSDID("$FreeBSD$"); /* * ARMADA 8040 GPIO driver. */ #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pic_if.h" #include "syscon_if.h" #define GPIO_LOCK(_sc) mtx_lock(&(_sc)->mtx) #define GPIO_UNLOCK(_sc) mtx_unlock(&(_sc)->mtx) #define GPIO_LOCK_INIT(_sc) mtx_init(&_sc->mtx, \ device_get_nameunit(_sc->dev), "mvebu_gpio", MTX_DEF) #define GPIO_LOCK_DESTROY(_sc) mtx_destroy(&_sc->mtx); #define GPIO_ASSERT_LOCKED(_sc) mtx_assert(&_sc->mtx, MA_OWNED); #define GPIO_ASSERT_UNLOCKED(_sc) mtx_assert(&_sc->mtx, MA_NOTOWNED); #define GPIO_DATA_OUT 0x00 #define GPIO_CONTROL 0x04 #define GPIO_BLINK_ENA 0x08 #define GPIO_DATA_IN_POL 0x0C #define GPIO_DATA_IN 0x10 #define GPIO_INT_CAUSE 0x14 #define GPIO_INT_MASK 0x18 #define GPIO_INT_LEVEL_MASK 0x1C #define GPIO_CONTROL_SET 0x28 #define GPIO_CONTROL_CLR 0x2C #define GPIO_DATA_SET 0x30 #define GPIO_DATA_CLR 0x34 #define GPIO_BIT(_p) ((_p) % 32) #define GPIO_REGNUM(_p) ((_p) / 32) #define MV_GPIO_MAX_NIRQS 4 #define MV_GPIO_MAX_NPINS 32 #define RD4(sc, reg) SYSCON_READ_4((sc)->syscon, (reg)) #define WR4(sc, reg, val) SYSCON_WRITE_4((sc)->syscon, (reg), (val)) struct mvebu_gpio_irqsrc { struct intr_irqsrc isrc; u_int irq; bool is_level; bool is_inverted; }; struct mvebu_gpio_softc; struct mvebu_gpio_irq_cookie { struct mvebu_gpio_softc *sc; int bank_num; }; struct mvebu_gpio_softc { device_t dev; device_t busdev; struct mtx mtx; struct syscon *syscon; uint32_t offset; struct resource *irq_res[MV_GPIO_MAX_NIRQS]; void *irq_ih[MV_GPIO_MAX_NIRQS]; struct mvebu_gpio_irq_cookie irq_cookies[MV_GPIO_MAX_NIRQS]; int gpio_npins; struct gpio_pin gpio_pins[MV_GPIO_MAX_NPINS]; struct mvebu_gpio_irqsrc *isrcs; }; static struct ofw_compat_data compat_data[] = { {"marvell,armada-8k-gpio", 1}, {NULL, 0} }; /* -------------------------------------------------------------------------- * * GPIO * */ static inline void gpio_write(struct mvebu_gpio_softc *sc, bus_size_t reg, struct gpio_pin *pin, uint32_t val) { uint32_t tmp; int bit; bit = GPIO_BIT(pin->gp_pin); tmp = 0x100 << bit; /* mask */ tmp |= (val & 1) << bit; /* value */ SYSCON_WRITE_4(sc->syscon, sc->offset + GPIO_REGNUM(pin->gp_pin) + reg, tmp); } static inline uint32_t gpio_read(struct mvebu_gpio_softc *sc, bus_size_t reg, struct gpio_pin *pin) { int bit; uint32_t val; bit = GPIO_BIT(pin->gp_pin); val = SYSCON_READ_4(sc->syscon, sc->offset + GPIO_REGNUM(pin->gp_pin) + reg); return (val >> bit) & 1; } static void mvebu_gpio_pin_configure(struct mvebu_gpio_softc *sc, struct gpio_pin *pin, unsigned int flags) { if ((flags & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) == 0) return; /* Manage input/output */ pin->gp_flags &= ~(GPIO_PIN_INPUT | GPIO_PIN_OUTPUT); if (flags & GPIO_PIN_OUTPUT) { pin->gp_flags |= GPIO_PIN_OUTPUT; gpio_write(sc, GPIO_CONTROL_SET, pin, 1); } else { pin->gp_flags |= GPIO_PIN_INPUT; gpio_write(sc, GPIO_CONTROL_CLR, pin, 1); } } static device_t mvebu_gpio_get_bus(device_t dev) { struct mvebu_gpio_softc *sc; sc = device_get_softc(dev); return (sc->busdev); } static int mvebu_gpio_pin_max(device_t dev, int *maxpin) { struct mvebu_gpio_softc *sc; sc = device_get_softc(dev); *maxpin = sc->gpio_npins - 1; return (0); } static int mvebu_gpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) { struct mvebu_gpio_softc *sc; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); *caps = sc->gpio_pins[pin].gp_caps; return (0); } static int mvebu_gpio_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags) { struct mvebu_gpio_softc *sc; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); *flags = sc->gpio_pins[pin].gp_flags; return (0); } static int mvebu_gpio_pin_getname(device_t dev, uint32_t pin, char *name) { struct mvebu_gpio_softc *sc; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); memcpy(name, sc->gpio_pins[pin].gp_name, GPIOMAXNAME); return (0); } static int mvebu_gpio_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) { struct mvebu_gpio_softc *sc; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); mvebu_gpio_pin_configure(sc, &sc->gpio_pins[pin], flags); return (0); } static int mvebu_gpio_pin_set(device_t dev, uint32_t pin, unsigned int value) { struct mvebu_gpio_softc *sc; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); if (value != 0) gpio_write(sc, GPIO_DATA_SET, &sc->gpio_pins[pin], 1); else gpio_write(sc, GPIO_DATA_CLR, &sc->gpio_pins[pin], 1); return (0); } static int mvebu_gpio_pin_get(device_t dev, uint32_t pin, unsigned int *val) { struct mvebu_gpio_softc *sc; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); GPIO_LOCK(sc); *val = gpio_read(sc, GPIO_DATA_IN, &sc->gpio_pins[pin]); *val ^= gpio_read(sc, GPIO_DATA_IN_POL, &sc->gpio_pins[pin]); GPIO_UNLOCK(sc); return (0); } static int mvebu_gpio_pin_toggle(device_t dev, uint32_t pin) { struct mvebu_gpio_softc *sc; uint32_t val; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); GPIO_LOCK(sc); mvebu_gpio_pin_get(sc->dev, pin, &val); if (val != 0) gpio_write(sc, GPIO_DATA_CLR, &sc->gpio_pins[pin], 1); else gpio_write(sc, GPIO_DATA_SET, &sc->gpio_pins[pin], 1); GPIO_UNLOCK(sc); return (0); } /* -------------------------------------------------------------------------- * * Interrupts * */ static inline void intr_modify(struct mvebu_gpio_softc *sc, bus_addr_t reg, struct mvebu_gpio_irqsrc *mgi, uint32_t val, uint32_t mask) { int bit; bit = GPIO_BIT(mgi->irq); GPIO_LOCK(sc); val = SYSCON_MODIFY_4(sc->syscon, sc->offset + GPIO_REGNUM(mgi->irq) + reg, val, mask); GPIO_UNLOCK(sc); } static inline void mvebu_gpio_isrc_mask(struct mvebu_gpio_softc *sc, struct mvebu_gpio_irqsrc *mgi, uint32_t val) { if (mgi->is_level) intr_modify(sc, GPIO_INT_LEVEL_MASK, mgi, val, 1); else intr_modify(sc, GPIO_INT_MASK, mgi, val, 1); } static inline void mvebu_gpio_isrc_eoi(struct mvebu_gpio_softc *sc, struct mvebu_gpio_irqsrc *mgi) { if (!mgi->is_level) intr_modify(sc, GPIO_INT_CAUSE, mgi, 1, 1); } static int mvebu_gpio_pic_attach(struct mvebu_gpio_softc *sc) { int rv; uint32_t irq; const char *name; sc->isrcs = malloc(sizeof(*sc->isrcs) * sc->gpio_npins, M_DEVBUF, M_WAITOK | M_ZERO); name = device_get_nameunit(sc->dev); for (irq = 0; irq < sc->gpio_npins; irq++) { sc->isrcs[irq].irq = irq; sc->isrcs[irq].is_level = false; sc->isrcs[irq].is_inverted = false; rv = intr_isrc_register(&sc->isrcs[irq].isrc, sc->dev, 0, "%s,%u", name, irq); if (rv != 0) return (rv); /* XXX deregister ISRCs */ } if (intr_pic_register(sc->dev, OF_xref_from_node(ofw_bus_get_node(sc->dev))) == NULL) return (ENXIO); return (0); } static int mvebu_gpio_pic_detach(struct mvebu_gpio_softc *sc) { /* * There has not been established any procedure yet * how to detach PIC from living system correctly. */ device_printf(sc->dev, "%s: not implemented yet\n", __func__); return (EBUSY); } static void mvebu_gpio_pic_disable_intr(device_t dev, struct intr_irqsrc *isrc) { struct mvebu_gpio_softc *sc; struct mvebu_gpio_irqsrc *mgi; sc = device_get_softc(dev); mgi = (struct mvebu_gpio_irqsrc *)isrc; mvebu_gpio_isrc_mask(sc, mgi, 0); } static void mvebu_gpio_pic_enable_intr(device_t dev, struct intr_irqsrc *isrc) { struct mvebu_gpio_softc *sc; struct mvebu_gpio_irqsrc *mgi; sc = device_get_softc(dev); mgi = (struct mvebu_gpio_irqsrc *)isrc; mvebu_gpio_isrc_mask(sc, mgi, 1); } static int mvebu_gpio_pic_map_fdt(struct mvebu_gpio_softc *sc, u_int ncells, pcell_t *cells, u_int *irqp, bool *invertedp, bool *levelp) { bool inverted, level; /* * The first cell is the interrupt number. * The second cell is used to specify flags: * bits[3:0] trigger type and level flags: * 1 = low-to-high edge triggered. * 2 = high-to-low edge triggered. * 4 = active high level-sensitive. * 8 = active low level-sensitive. */ if (ncells != 2 || cells[0] >= sc->gpio_npins) return (EINVAL); switch (cells[1]) { case 1: inverted = false; level = false; break; case 2: inverted = true; level = false; break; case 4: inverted = false; level = true; break; case 8: inverted = true; level = true; break; default: return (EINVAL); } *irqp = cells[0]; if (invertedp != NULL) *invertedp = inverted; if (levelp != NULL) *levelp = level; return (0); } static int mvebu_gpio_pic_map_gpio(struct mvebu_gpio_softc *sc, u_int gpio_pin_num, u_int gpio_pin_flags, u_int intr_mode, u_int *irqp, bool *invertedp, bool *levelp) { bool inverted, level; if (gpio_pin_num >= sc->gpio_npins) return (EINVAL); switch (intr_mode) { case GPIO_INTR_LEVEL_LOW: inverted = true; level = true; break; case GPIO_INTR_LEVEL_HIGH: inverted = false; level = true; break; case GPIO_INTR_CONFORM: case GPIO_INTR_EDGE_RISING: inverted = false; level = false; break; case GPIO_INTR_EDGE_FALLING: inverted = true; level = false; break; default: return (EINVAL); } *irqp = gpio_pin_num; if (invertedp != NULL) *invertedp = inverted; if (levelp != NULL) *levelp = level; return (0); } static int mvebu_gpio_pic_map_intr(device_t dev, struct intr_map_data *data, struct intr_irqsrc **isrcp) { int rv; u_int irq; struct mvebu_gpio_softc *sc; sc = device_get_softc(dev); if (data->type == INTR_MAP_DATA_FDT) { struct intr_map_data_fdt *daf; daf = (struct intr_map_data_fdt *)data; rv = mvebu_gpio_pic_map_fdt(sc, daf->ncells, daf->cells, &irq, NULL, NULL); } else if (data->type == INTR_MAP_DATA_GPIO) { struct intr_map_data_gpio *dag; dag = (struct intr_map_data_gpio *)data; rv = mvebu_gpio_pic_map_gpio(sc, dag->gpio_pin_num, dag->gpio_pin_flags, dag->gpio_intr_mode, &irq, NULL, NULL); } else return (ENOTSUP); if (rv == 0) *isrcp = &sc->isrcs[irq].isrc; return (rv); } static void mvebu_gpio_pic_post_filter(device_t dev, struct intr_irqsrc *isrc) { struct mvebu_gpio_softc *sc; struct mvebu_gpio_irqsrc *mgi; sc = device_get_softc(dev); mgi = (struct mvebu_gpio_irqsrc *)isrc; if (mgi->is_level) mvebu_gpio_isrc_eoi(sc, mgi); } static void mvebu_gpio_pic_post_ithread(device_t dev, struct intr_irqsrc *isrc) { struct mvebu_gpio_softc *sc; struct mvebu_gpio_irqsrc *mgi; sc = device_get_softc(dev); mgi = (struct mvebu_gpio_irqsrc *)isrc; mvebu_gpio_isrc_mask(sc, mgi, 1); } static void mvebu_gpio_pic_pre_ithread(device_t dev, struct intr_irqsrc *isrc) { struct mvebu_gpio_softc *sc; struct mvebu_gpio_irqsrc *mgi; sc = device_get_softc(dev); mgi = (struct mvebu_gpio_irqsrc *)isrc; mvebu_gpio_isrc_mask(sc, mgi, 0); if (mgi->is_level) mvebu_gpio_isrc_eoi(sc, mgi); } static int mvebu_gpio_pic_setup_intr(device_t dev, struct intr_irqsrc *isrc, struct resource *res, struct intr_map_data *data) { u_int irq; bool inverted, level; int rv; struct mvebu_gpio_softc *sc; struct mvebu_gpio_irqsrc *mgi; sc = device_get_softc(dev); mgi = (struct mvebu_gpio_irqsrc *)isrc; if (data == NULL) return (ENOTSUP); /* Get and check config for an interrupt. */ if (data->type == INTR_MAP_DATA_FDT) { struct intr_map_data_fdt *daf; daf = (struct intr_map_data_fdt *)data; rv = mvebu_gpio_pic_map_fdt(sc, daf->ncells, daf->cells, &irq, &inverted, &level); } else if (data->type == INTR_MAP_DATA_GPIO) { struct intr_map_data_gpio *dag; dag = (struct intr_map_data_gpio *)data; rv = mvebu_gpio_pic_map_gpio(sc, dag->gpio_pin_num, dag->gpio_pin_flags, dag->gpio_intr_mode, &irq, &inverted, &level); } else return (ENOTSUP); if (rv != 0) return (EINVAL); /* * If this is a setup for another handler, * only check that its configuration match. */ if (isrc->isrc_handlers != 0) return ( mgi->is_level == level && mgi->is_inverted == inverted ? 0 : EINVAL); mgi->is_level = level; mgi->is_inverted = inverted; intr_modify(sc, GPIO_DATA_IN_POL, mgi, inverted ? 1 : 0, 1); mvebu_gpio_pic_enable_intr(dev, isrc); return (0); } static int mvebu_gpio_pic_teardown_intr(device_t dev, struct intr_irqsrc *isrc, struct resource *res, struct intr_map_data *data) { struct mvebu_gpio_softc *sc; struct mvebu_gpio_irqsrc *mgi; sc = device_get_softc(dev); mgi = (struct mvebu_gpio_irqsrc *)isrc; if (isrc->isrc_handlers == 0) mvebu_gpio_isrc_mask(sc, mgi, 0); return (0); } /* -------------------------------------------------------------------------- * * Bus * */ static int mvebu_gpio_intr(void *arg) { u_int i, lvl, edge; struct mvebu_gpio_softc *sc; struct trapframe *tf; struct mvebu_gpio_irqsrc *mgi; struct mvebu_gpio_irq_cookie *cookie; cookie = (struct mvebu_gpio_irq_cookie *)arg; sc = cookie->sc; tf = curthread->td_intr_frame; for (i = 0; i < sc->gpio_npins; i++) { lvl = gpio_read(sc, GPIO_DATA_IN, &sc->gpio_pins[i]); lvl &= gpio_read(sc, GPIO_INT_LEVEL_MASK, &sc->gpio_pins[i]); edge = gpio_read(sc, GPIO_DATA_IN, &sc->gpio_pins[i]); edge &= gpio_read(sc, GPIO_INT_LEVEL_MASK, &sc->gpio_pins[i]); if (edge == 0 || lvl == 0) continue; mgi = &sc->isrcs[i]; if (!mgi->is_level) mvebu_gpio_isrc_eoi(sc, mgi); if (intr_isrc_dispatch(&mgi->isrc, tf) != 0) { mvebu_gpio_isrc_mask(sc, mgi, 0); if (mgi->is_level) mvebu_gpio_isrc_eoi(sc, mgi); device_printf(sc->dev, "Stray irq %u disabled\n", mgi->irq); } } return (FILTER_HANDLED); } static int mvebu_gpio_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 Integrated GPIO Controller"); return (0); } static int mvebu_gpio_detach(device_t dev) { struct mvebu_gpio_softc *sc; int i; sc = device_get_softc(dev); KASSERT(mtx_initialized(&sc->mtx), ("gpio mutex not initialized")); for (i = 0; i < MV_GPIO_MAX_NIRQS; i++) { if (sc->irq_ih[i] != NULL) bus_teardown_intr(dev, sc->irq_res[i], sc->irq_ih[i]); } if (sc->isrcs != NULL) mvebu_gpio_pic_detach(sc); gpiobus_detach_bus(dev); for (i = 0; i < MV_GPIO_MAX_NIRQS; i++) { if (sc->irq_res[i] != NULL) bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq_res[i]); } GPIO_LOCK_DESTROY(sc); return(0); } static int mvebu_gpio_attach(device_t dev) { struct mvebu_gpio_softc *sc; phandle_t node; struct gpio_pin *pin; pcell_t pincnt; int i, rv, rid; sc = device_get_softc(dev); sc->dev = dev; node = ofw_bus_get_node(dev); GPIO_LOCK_INIT(sc); pincnt = 0; rv = OF_getencprop(node, "ngpios", &pincnt, sizeof(pcell_t)); if (rv < 0) { device_printf(dev, "ERROR: no pin-count or ngpios entry found!\n"); return (ENXIO); } sc->gpio_npins = MIN(pincnt, MV_GPIO_MAX_NPINS); if (bootverbose) device_printf(dev, "%d pins available\n", sc->gpio_npins); rv = OF_getencprop(node, "offset", &sc->offset, sizeof(sc->offset)); if (rv == -1) { device_printf(dev, "ERROR: no 'offset' property found!\n"); return (ENXIO); } if (SYSCON_GET_HANDLE(sc->dev, &sc->syscon) != 0 || sc->syscon == NULL) { device_printf(dev, "ERROR: cannot get syscon handle!\n"); return (ENXIO); } /* Allocate interrupts. */ for (i = 0; i < MV_GPIO_MAX_NIRQS; i++) { sc->irq_cookies[i].sc = sc; sc->irq_cookies[i].bank_num = i; rid = i; sc->irq_res[i] = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); if (sc->irq_res[i] == NULL) break; if ((bus_setup_intr(dev, sc->irq_res[i], INTR_TYPE_MISC | INTR_MPSAFE, mvebu_gpio_intr, NULL, &sc->irq_cookies[i], &sc->irq_ih[i]))) { device_printf(dev, "WARNING: unable to register interrupt handler\n"); mvebu_gpio_detach(dev); return (ENXIO); } } /* Init GPIO pins */ for (i = 0; i < sc->gpio_npins; i++) { pin = sc->gpio_pins + i; pin->gp_pin = i; if (sc->irq_res[0] != NULL) pin->gp_caps = GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | GPIO_INTR_LEVEL_LOW | GPIO_INTR_LEVEL_HIGH | GPIO_INTR_EDGE_RISING | GPIO_INTR_EDGE_FALLING; else pin->gp_caps = GPIO_PIN_INPUT | GPIO_PIN_OUTPUT; pin->gp_flags = gpio_read(sc, GPIO_CONTROL, &sc->gpio_pins[i]) != 0 ? GPIO_PIN_OUTPUT : GPIO_PIN_INPUT; snprintf(pin->gp_name, GPIOMAXNAME, "gpio%d", i); /* Init HW */ gpio_write(sc, GPIO_INT_MASK, pin, 0); gpio_write(sc, GPIO_INT_LEVEL_MASK, pin, 0); gpio_write(sc, GPIO_INT_CAUSE, pin, 1); gpio_write(sc, GPIO_DATA_IN_POL, pin, 1); gpio_write(sc, GPIO_BLINK_ENA, pin, 0); } if (sc->irq_res[0] != NULL) { rv = mvebu_gpio_pic_attach(sc); if (rv != 0) { device_printf(dev, "WARNING: unable to attach PIC\n"); mvebu_gpio_detach(dev); return (rv); } } sc->busdev = gpiobus_attach_bus(dev); if (sc->busdev == NULL) { mvebu_gpio_detach(dev); return (ENXIO); } return (bus_generic_attach(dev)); } static int mvebu_gpio_map_gpios(device_t dev, phandle_t pdev, phandle_t gparent, int gcells, pcell_t *gpios, uint32_t *pin, uint32_t *flags) { if (gcells != 2) return (ERANGE); *pin = gpios[0]; *flags= gpios[1]; return (0); } static phandle_t mvebu_gpio_get_node(device_t bus, device_t dev) { /* We only have one child, the GPIO bus, which needs our own node. */ return (ofw_bus_get_node(bus)); } static device_method_t mvebu_gpio_methods[] = { DEVMETHOD(device_probe, mvebu_gpio_probe), DEVMETHOD(device_attach, mvebu_gpio_attach), DEVMETHOD(device_detach, mvebu_gpio_detach), /* Interrupt controller interface */ DEVMETHOD(pic_disable_intr, mvebu_gpio_pic_disable_intr), DEVMETHOD(pic_enable_intr, mvebu_gpio_pic_enable_intr), DEVMETHOD(pic_map_intr, mvebu_gpio_pic_map_intr), DEVMETHOD(pic_setup_intr, mvebu_gpio_pic_setup_intr), DEVMETHOD(pic_teardown_intr, mvebu_gpio_pic_teardown_intr), DEVMETHOD(pic_post_filter, mvebu_gpio_pic_post_filter), DEVMETHOD(pic_post_ithread, mvebu_gpio_pic_post_ithread), DEVMETHOD(pic_pre_ithread, mvebu_gpio_pic_pre_ithread), /* GPIO protocol */ DEVMETHOD(gpio_get_bus, mvebu_gpio_get_bus), DEVMETHOD(gpio_pin_max, mvebu_gpio_pin_max), DEVMETHOD(gpio_pin_getname, mvebu_gpio_pin_getname), DEVMETHOD(gpio_pin_getflags, mvebu_gpio_pin_getflags), DEVMETHOD(gpio_pin_getcaps, mvebu_gpio_pin_getcaps), DEVMETHOD(gpio_pin_setflags, mvebu_gpio_pin_setflags), DEVMETHOD(gpio_pin_get, mvebu_gpio_pin_get), DEVMETHOD(gpio_pin_set, mvebu_gpio_pin_set), DEVMETHOD(gpio_pin_toggle, mvebu_gpio_pin_toggle), DEVMETHOD(gpio_map_gpios, mvebu_gpio_map_gpios), /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_node, mvebu_gpio_get_node), DEVMETHOD_END }; static devclass_t mvebu_gpio_devclass; static DEFINE_CLASS_0(gpio, mvebu_gpio_driver, mvebu_gpio_methods, sizeof(struct mvebu_gpio_softc)); -EARLY_DRIVER_MODULE(gpio, simplebus, mvebu_gpio_driver, +EARLY_DRIVER_MODULE(mvebu_gpio, simplebus, mvebu_gpio_driver, mvebu_gpio_devclass, NULL, NULL, BUS_PASS_TIMER + BUS_PASS_ORDER_LAST);