Index: stable/11/sys/arm/freescale/imx/imx_gpio.c =================================================================== --- stable/11/sys/arm/freescale/imx/imx_gpio.c (revision 331518) +++ stable/11/sys/arm/freescale/imx/imx_gpio.c (revision 331519) @@ -1,873 +1,873 @@ /*- * Copyright (c) 2012, 2013 The FreeBSD Foundation * All rights reserved. * * This software was developed by Oleksandr Rybalko under sponsorship * from the FreeBSD Foundation. * * 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. */ /* * Freescale i.MX515 GPIO driver. */ #include __FBSDID("$FreeBSD$"); #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gpio_if.h" #ifdef INTRNG #include "pic_if.h" #endif #define WRITE4(_sc, _r, _v) \ bus_space_write_4((_sc)->sc_iot, (_sc)->sc_ioh, (_r), (_v)) #define READ4(_sc, _r) \ bus_space_read_4((_sc)->sc_iot, (_sc)->sc_ioh, (_r)) #define SET4(_sc, _r, _m) \ WRITE4((_sc), (_r), READ4((_sc), (_r)) | (_m)) #define CLEAR4(_sc, _r, _m) \ WRITE4((_sc), (_r), READ4((_sc), (_r)) & ~(_m)) /* Registers definition for Freescale i.MX515 GPIO controller */ #define IMX_GPIO_DR_REG 0x000 /* Pin Data */ #define IMX_GPIO_OE_REG 0x004 /* Set Pin Output */ #define IMX_GPIO_PSR_REG 0x008 /* Pad Status */ #define IMX_GPIO_ICR1_REG 0x00C /* Interrupt Configuration */ #define IMX_GPIO_ICR2_REG 0x010 /* Interrupt Configuration */ #define GPIO_ICR_COND_LOW 0 #define GPIO_ICR_COND_HIGH 1 #define GPIO_ICR_COND_RISE 2 #define GPIO_ICR_COND_FALL 3 #define GPIO_ICR_COND_MASK 0x3 #define IMX_GPIO_IMR_REG 0x014 /* Interrupt Mask Register */ #define IMX_GPIO_ISR_REG 0x018 /* Interrupt Status Register */ #define IMX_GPIO_EDGE_REG 0x01C /* Edge Detect Register */ #ifdef INTRNG #define DEFAULT_CAPS (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | \ GPIO_INTR_LEVEL_LOW | GPIO_INTR_LEVEL_HIGH | GPIO_INTR_EDGE_RISING | \ GPIO_INTR_EDGE_FALLING | GPIO_INTR_EDGE_BOTH) #else #define DEFAULT_CAPS (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT) #endif #define NGPIO 32 #ifdef INTRNG struct gpio_irqsrc { struct intr_irqsrc gi_isrc; u_int gi_irq; uint32_t gi_mode; }; #endif struct imx51_gpio_softc { device_t dev; device_t sc_busdev; struct mtx sc_mtx; struct resource *sc_res[3]; /* 1 x mem, 2 x IRQ */ void *gpio_ih[2]; bus_space_tag_t sc_iot; bus_space_handle_t sc_ioh; int gpio_npins; struct gpio_pin gpio_pins[NGPIO]; #ifdef INTRNG struct gpio_irqsrc gpio_pic_irqsrc[NGPIO]; #endif }; static struct ofw_compat_data compat_data[] = { {"fsl,imx6q-gpio", 1}, {"fsl,imx53-gpio", 1}, {"fsl,imx51-gpio", 1}, {NULL, 0} }; static struct resource_spec imx_gpio_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE }, { SYS_RES_IRQ, 1, RF_ACTIVE }, { -1, 0 } }; /* * Helpers */ static void imx51_gpio_pin_configure(struct imx51_gpio_softc *, struct gpio_pin *, uint32_t); /* * Driver stuff */ static int imx51_gpio_probe(device_t); static int imx51_gpio_attach(device_t); static int imx51_gpio_detach(device_t); /* * GPIO interface */ static device_t imx51_gpio_get_bus(device_t); static int imx51_gpio_pin_max(device_t, int *); static int imx51_gpio_pin_getcaps(device_t, uint32_t, uint32_t *); static int imx51_gpio_pin_getflags(device_t, uint32_t, uint32_t *); static int imx51_gpio_pin_getname(device_t, uint32_t, char *); static int imx51_gpio_pin_setflags(device_t, uint32_t, uint32_t); static int imx51_gpio_pin_set(device_t, uint32_t, unsigned int); static int imx51_gpio_pin_get(device_t, uint32_t, unsigned int *); static int imx51_gpio_pin_toggle(device_t, uint32_t pin); #ifdef INTRNG static int gpio_pic_map_fdt(struct imx51_gpio_softc *sc, struct intr_map_data_fdt *daf, u_int *irqp, uint32_t *modep) { u_int irq; uint32_t mode; /* * From devicetree/bindings/gpio/fsl-imx-gpio.txt: * #interrupt-cells: 2. The first cell is the GPIO number. The second * cell bits[3:0] is used to specify 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. * We can do any single one of these modes, and also edge low+high * (i.e., trigger on both edges); other combinations are not supported. */ if (daf->ncells != 2) { device_printf(sc->dev, "Invalid #interrupt-cells\n"); return (EINVAL); } irq = daf->cells[0]; if (irq >= sc->gpio_npins) { device_printf(sc->dev, "Invalid interrupt number %u\n", irq); return (EINVAL); } switch (daf->cells[1]) { case 1: mode = GPIO_INTR_EDGE_RISING; break; case 2: mode = GPIO_INTR_EDGE_FALLING; break; case 3: mode = GPIO_INTR_EDGE_BOTH; break; case 4: mode = GPIO_INTR_LEVEL_HIGH; break; case 8: mode = GPIO_INTR_LEVEL_LOW; break; default: device_printf(sc->dev, "Unsupported interrupt mode 0x%2x\n", daf->cells[1]); return (ENOTSUP); } *irqp = irq; if (modep != NULL) *modep = mode; return (0); } static int gpio_pic_map_gpio(struct imx51_gpio_softc *sc, struct intr_map_data_gpio *dag, u_int *irqp, uint32_t *modep) { u_int irq; irq = dag->gpio_pin_num; if (irq >= sc->gpio_npins) { device_printf(sc->dev, "Invalid interrupt number %u\n", irq); return (EINVAL); } switch (dag->gpio_intr_mode) { case GPIO_INTR_LEVEL_LOW: case GPIO_INTR_LEVEL_HIGH: case GPIO_INTR_EDGE_RISING: case GPIO_INTR_EDGE_FALLING: case GPIO_INTR_EDGE_BOTH: break; default: device_printf(sc->dev, "Unsupported interrupt mode 0x%8x\n", dag->gpio_intr_mode); return (EINVAL); } *irqp = irq; if (modep != NULL) *modep = dag->gpio_intr_mode; return (0); } static int gpio_pic_map(struct imx51_gpio_softc *sc, struct intr_map_data *data, u_int *irqp, uint32_t *modep) { switch (data->type) { case INTR_MAP_DATA_FDT: return (gpio_pic_map_fdt(sc, (struct intr_map_data_fdt *)data, irqp, modep)); case INTR_MAP_DATA_GPIO: return (gpio_pic_map_gpio(sc, (struct intr_map_data_gpio *)data, irqp, modep)); default: return (ENOTSUP); } } static int gpio_pic_map_intr(device_t dev, struct intr_map_data *data, struct intr_irqsrc **isrcp) { int error; u_int irq; struct imx51_gpio_softc *sc; sc = device_get_softc(dev); error = gpio_pic_map(sc, data, &irq, NULL); if (error == 0) *isrcp = &sc->gpio_pic_irqsrc[irq].gi_isrc; return (error); } static int gpio_pic_teardown_intr(device_t dev, struct intr_irqsrc *isrc, struct resource *res, struct intr_map_data *data) { struct imx51_gpio_softc *sc; struct gpio_irqsrc *gi; sc = device_get_softc(dev); if (isrc->isrc_handlers == 0) { gi = (struct gpio_irqsrc *)isrc; gi->gi_mode = GPIO_INTR_CONFORM; // XXX Not sure this is necessary mtx_lock_spin(&sc->sc_mtx); CLEAR4(sc, IMX_GPIO_IMR_REG, (1U << gi->gi_irq)); WRITE4(sc, IMX_GPIO_ISR_REG, (1U << gi->gi_irq)); mtx_unlock_spin(&sc->sc_mtx); } return (0); } static int gpio_pic_setup_intr(device_t dev, struct intr_irqsrc *isrc, struct resource *res, struct intr_map_data *data) { struct imx51_gpio_softc *sc; struct gpio_irqsrc *gi; int error; u_int icfg, irq, reg, shift, wrk; uint32_t mode; if (data == NULL) return (ENOTSUP); sc = device_get_softc(dev); gi = (struct gpio_irqsrc *)isrc; /* Get config for interrupt. */ error = gpio_pic_map(sc, data, &irq, &mode); if (error != 0) return (error); if (gi->gi_irq != irq) return (EINVAL); /* Compare config if this is not first setup. */ if (isrc->isrc_handlers != 0) return (gi->gi_mode == mode ? 0 : EINVAL); gi->gi_mode = mode; /* * To interrupt on both edges we have to use the EDGE register. The * manual says it only exists for backwards compatibilty with older imx * chips, but it's also the only way to configure interrupting on both * edges. If the EDGE bit is on, the corresponding ICRn bit is ignored. */ mtx_lock_spin(&sc->sc_mtx); if (mode == GPIO_INTR_EDGE_BOTH) { SET4(sc, IMX_GPIO_EDGE_REG, (1u << irq)); } else { CLEAR4(sc, IMX_GPIO_EDGE_REG, (1u << irq)); switch (mode) { default: /* silence warnings; default can't actually happen. */ /* FALLTHROUGH */ case GPIO_INTR_LEVEL_LOW: icfg = GPIO_ICR_COND_LOW; break; case GPIO_INTR_LEVEL_HIGH: icfg = GPIO_ICR_COND_HIGH; break; case GPIO_INTR_EDGE_RISING: icfg = GPIO_ICR_COND_RISE; break; case GPIO_INTR_EDGE_FALLING: icfg = GPIO_ICR_COND_FALL; break; } if (irq < 16) { reg = IMX_GPIO_ICR1_REG; shift = 2 * irq; } else { reg = IMX_GPIO_ICR2_REG; shift = 2 * (irq - 16); } wrk = READ4(sc, reg); wrk &= ~(GPIO_ICR_COND_MASK << shift); wrk |= icfg << shift; WRITE4(sc, reg, wrk); } WRITE4(sc, IMX_GPIO_ISR_REG, (1u << irq)); SET4(sc, IMX_GPIO_IMR_REG, (1u << irq)); mtx_unlock_spin(&sc->sc_mtx); return (0); } /* * this is mask_intr */ static void gpio_pic_disable_intr(device_t dev, struct intr_irqsrc *isrc) { struct imx51_gpio_softc *sc; u_int irq; sc = device_get_softc(dev); irq = ((struct gpio_irqsrc *)isrc)->gi_irq; mtx_lock_spin(&sc->sc_mtx); CLEAR4(sc, IMX_GPIO_IMR_REG, (1U << irq)); mtx_unlock_spin(&sc->sc_mtx); } /* * this is unmask_intr */ static void gpio_pic_enable_intr(device_t dev, struct intr_irqsrc *isrc) { struct imx51_gpio_softc *sc; u_int irq; sc = device_get_softc(dev); irq = ((struct gpio_irqsrc *)isrc)->gi_irq; mtx_lock_spin(&sc->sc_mtx); SET4(sc, IMX_GPIO_IMR_REG, (1U << irq)); mtx_unlock_spin(&sc->sc_mtx); } static void gpio_pic_post_filter(device_t dev, struct intr_irqsrc *isrc) { struct imx51_gpio_softc *sc; u_int irq; sc = device_get_softc(dev); irq = ((struct gpio_irqsrc *)isrc)->gi_irq; arm_irq_memory_barrier(0); /* EOI. W1C reg so no r-m-w, no locking needed. */ WRITE4(sc, IMX_GPIO_ISR_REG, (1U << irq)); } static void gpio_pic_post_ithread(device_t dev, struct intr_irqsrc *isrc) { struct imx51_gpio_softc *sc; u_int irq; sc = device_get_softc(dev); irq = ((struct gpio_irqsrc *)isrc)->gi_irq; arm_irq_memory_barrier(0); /* EOI. W1C reg so no r-m-w, no locking needed. */ WRITE4(sc, IMX_GPIO_ISR_REG, (1U << irq)); gpio_pic_enable_intr(dev, isrc); } static void gpio_pic_pre_ithread(device_t dev, struct intr_irqsrc *isrc) { gpio_pic_disable_intr(dev, isrc); } static int gpio_pic_filter(void *arg) { struct imx51_gpio_softc *sc; struct intr_irqsrc *isrc; uint32_t i, interrupts; sc = arg; mtx_lock_spin(&sc->sc_mtx); interrupts = READ4(sc, IMX_GPIO_ISR_REG) & READ4(sc, IMX_GPIO_IMR_REG); mtx_unlock_spin(&sc->sc_mtx); for (i = 0; interrupts != 0; i++, interrupts >>= 1) { if ((interrupts & 0x1) == 0) continue; isrc = &sc->gpio_pic_irqsrc[i].gi_isrc; if (intr_isrc_dispatch(isrc, curthread->td_intr_frame) != 0) { gpio_pic_disable_intr(sc->dev, isrc); gpio_pic_post_filter(sc->dev, isrc); device_printf(sc->dev, "Stray irq %u disabled\n", i); } } return (FILTER_HANDLED); } /* * Initialize our isrcs and register them with intrng. */ static int gpio_pic_register_isrcs(struct imx51_gpio_softc *sc) { int error; uint32_t irq; const char *name; name = device_get_nameunit(sc->dev); for (irq = 0; irq < NGPIO; irq++) { sc->gpio_pic_irqsrc[irq].gi_irq = irq; sc->gpio_pic_irqsrc[irq].gi_mode = GPIO_INTR_CONFORM; error = intr_isrc_register(&sc->gpio_pic_irqsrc[irq].gi_isrc, sc->dev, 0, "%s,%u", name, irq); if (error != 0) { /* XXX call intr_isrc_deregister() */ device_printf(sc->dev, "%s failed", __func__); return (error); } } return (0); } #endif /* * */ static void imx51_gpio_pin_configure(struct imx51_gpio_softc *sc, struct gpio_pin *pin, unsigned int flags) { u_int newflags; mtx_lock_spin(&sc->sc_mtx); /* * Manage input/output; other flags not supported yet. * * Note that changes to pin->gp_flags must be acccumulated in newflags * and stored with a single writeback to gp_flags at the end, to enable * unlocked reads of that value elsewhere. */ if (flags & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) { newflags = pin->gp_flags & ~(GPIO_PIN_INPUT | GPIO_PIN_OUTPUT); if (flags & GPIO_PIN_OUTPUT) { newflags |= GPIO_PIN_OUTPUT; SET4(sc, IMX_GPIO_OE_REG, (1U << pin->gp_pin)); } else { newflags |= GPIO_PIN_INPUT; CLEAR4(sc, IMX_GPIO_OE_REG, (1U << pin->gp_pin)); } pin->gp_flags = newflags; } mtx_unlock_spin(&sc->sc_mtx); } static device_t imx51_gpio_get_bus(device_t dev) { struct imx51_gpio_softc *sc; sc = device_get_softc(dev); return (sc->sc_busdev); } static int imx51_gpio_pin_max(device_t dev, int *maxpin) { struct imx51_gpio_softc *sc; sc = device_get_softc(dev); *maxpin = sc->gpio_npins - 1; return (0); } static int imx51_gpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) { struct imx51_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 imx51_gpio_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags) { struct imx51_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 imx51_gpio_pin_getname(device_t dev, uint32_t pin, char *name) { struct imx51_gpio_softc *sc; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); mtx_lock_spin(&sc->sc_mtx); memcpy(name, sc->gpio_pins[pin].gp_name, GPIOMAXNAME); mtx_unlock_spin(&sc->sc_mtx); return (0); } static int imx51_gpio_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) { struct imx51_gpio_softc *sc; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); imx51_gpio_pin_configure(sc, &sc->gpio_pins[pin], flags); return (0); } static int imx51_gpio_pin_set(device_t dev, uint32_t pin, unsigned int value) { struct imx51_gpio_softc *sc; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); mtx_lock_spin(&sc->sc_mtx); if (value) SET4(sc, IMX_GPIO_DR_REG, (1U << pin)); else CLEAR4(sc, IMX_GPIO_DR_REG, (1U << pin)); mtx_unlock_spin(&sc->sc_mtx); return (0); } static int imx51_gpio_pin_get(device_t dev, uint32_t pin, unsigned int *val) { struct imx51_gpio_softc *sc; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); *val = (READ4(sc, IMX_GPIO_PSR_REG) >> pin) & 1; return (0); } static int imx51_gpio_pin_toggle(device_t dev, uint32_t pin) { struct imx51_gpio_softc *sc; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); mtx_lock_spin(&sc->sc_mtx); WRITE4(sc, IMX_GPIO_DR_REG, (READ4(sc, IMX_GPIO_DR_REG) ^ (1U << pin))); mtx_unlock_spin(&sc->sc_mtx); return (0); } static int imx51_gpio_pin_access_32(device_t dev, uint32_t first_pin, uint32_t clear_pins, uint32_t change_pins, uint32_t *orig_pins) { struct imx51_gpio_softc *sc; if (first_pin != 0) return (EINVAL); sc = device_get_softc(dev); if (orig_pins != NULL) *orig_pins = READ4(sc, IMX_GPIO_PSR_REG); if ((clear_pins | change_pins) != 0) { mtx_lock_spin(&sc->sc_mtx); WRITE4(sc, IMX_GPIO_DR_REG, (READ4(sc, IMX_GPIO_DR_REG) & ~clear_pins) ^ change_pins); mtx_unlock_spin(&sc->sc_mtx); } return (0); } static int imx51_gpio_pin_config_32(device_t dev, uint32_t first_pin, uint32_t num_pins, uint32_t *pin_flags) { struct imx51_gpio_softc *sc; u_int i; uint32_t bit, drclr, drset, flags, oeclr, oeset, pads; sc = device_get_softc(dev); if (first_pin != 0 || num_pins > sc->gpio_npins) return (EINVAL); drclr = drset = oeclr = oeset = 0; pads = READ4(sc, IMX_GPIO_PSR_REG); for (i = 0; i < num_pins; ++i) { bit = 1u << i; flags = pin_flags[i]; if (flags & GPIO_PIN_INPUT) { oeclr |= bit; } else if (flags & GPIO_PIN_OUTPUT) { oeset |= bit; if (flags & GPIO_PIN_PRESET_LOW) drclr |= bit; else if (flags & GPIO_PIN_PRESET_HIGH) drset |= bit; else /* Drive whatever it's now pulled to. */ drset |= pads & bit; } } mtx_lock_spin(&sc->sc_mtx); WRITE4(sc, IMX_GPIO_DR_REG, (READ4(sc, IMX_GPIO_DR_REG) & ~drclr) | drset); WRITE4(sc, IMX_GPIO_OE_REG, (READ4(sc, IMX_GPIO_OE_REG) & ~oeclr) | oeset); mtx_unlock_spin(&sc->sc_mtx); return (0); } static int imx51_gpio_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data != 0) { device_set_desc(dev, "Freescale i.MX GPIO Controller"); return (BUS_PROBE_DEFAULT); } return (ENXIO); } static int imx51_gpio_attach(device_t dev) { struct imx51_gpio_softc *sc; int i, irq, unit; sc = device_get_softc(dev); sc->dev = dev; sc->gpio_npins = NGPIO; mtx_init(&sc->sc_mtx, device_get_nameunit(sc->dev), NULL, MTX_SPIN); if (bus_alloc_resources(dev, imx_gpio_spec, sc->sc_res)) { device_printf(dev, "could not allocate resources\n"); bus_release_resources(dev, imx_gpio_spec, sc->sc_res); mtx_destroy(&sc->sc_mtx); return (ENXIO); } sc->sc_iot = rman_get_bustag(sc->sc_res[0]); sc->sc_ioh = rman_get_bushandle(sc->sc_res[0]); /* * Mask off all interrupts in hardware, then set up interrupt handling. */ WRITE4(sc, IMX_GPIO_IMR_REG, 0); for (irq = 0; irq < 2; irq++) { #ifdef INTRNG if ((bus_setup_intr(dev, sc->sc_res[1 + irq], INTR_TYPE_CLK, gpio_pic_filter, NULL, sc, &sc->gpio_ih[irq]))) { device_printf(dev, "WARNING: unable to register interrupt handler\n"); imx51_gpio_detach(dev); return (ENXIO); } #endif } unit = device_get_unit(dev); for (i = 0; i < sc->gpio_npins; i++) { sc->gpio_pins[i].gp_pin = i; sc->gpio_pins[i].gp_caps = DEFAULT_CAPS; sc->gpio_pins[i].gp_flags = (READ4(sc, IMX_GPIO_OE_REG) & (1U << i)) ? GPIO_PIN_OUTPUT : GPIO_PIN_INPUT; snprintf(sc->gpio_pins[i].gp_name, GPIOMAXNAME, "GPIO%d_IO%02d", unit + 1, i); } #ifdef INTRNG gpio_pic_register_isrcs(sc); intr_pic_register(dev, OF_xref_from_node(ofw_bus_get_node(dev))); #endif sc->sc_busdev = gpiobus_attach_bus(dev); if (sc->sc_busdev == NULL) { imx51_gpio_detach(dev); return (ENXIO); } return (0); } static int imx51_gpio_detach(device_t dev) { int irq; struct imx51_gpio_softc *sc; sc = device_get_softc(dev); gpiobus_detach_bus(dev); for (irq = 1; irq <= 2; irq++) { if (sc->gpio_ih[irq]) bus_teardown_intr(dev, sc->sc_res[irq], sc->gpio_ih[irq]); } bus_release_resources(dev, imx_gpio_spec, sc->sc_res); mtx_destroy(&sc->sc_mtx); return(0); } static device_method_t imx51_gpio_methods[] = { DEVMETHOD(device_probe, imx51_gpio_probe), DEVMETHOD(device_attach, imx51_gpio_attach), DEVMETHOD(device_detach, imx51_gpio_detach), #ifdef INTRNG /* Interrupt controller interface */ DEVMETHOD(pic_disable_intr, gpio_pic_disable_intr), DEVMETHOD(pic_enable_intr, gpio_pic_enable_intr), DEVMETHOD(pic_map_intr, gpio_pic_map_intr), DEVMETHOD(pic_setup_intr, gpio_pic_setup_intr), DEVMETHOD(pic_teardown_intr, gpio_pic_teardown_intr), DEVMETHOD(pic_post_filter, gpio_pic_post_filter), DEVMETHOD(pic_post_ithread, gpio_pic_post_ithread), DEVMETHOD(pic_pre_ithread, gpio_pic_pre_ithread), #endif /* GPIO protocol */ DEVMETHOD(gpio_get_bus, imx51_gpio_get_bus), DEVMETHOD(gpio_pin_max, imx51_gpio_pin_max), DEVMETHOD(gpio_pin_getname, imx51_gpio_pin_getname), DEVMETHOD(gpio_pin_getflags, imx51_gpio_pin_getflags), DEVMETHOD(gpio_pin_getcaps, imx51_gpio_pin_getcaps), DEVMETHOD(gpio_pin_setflags, imx51_gpio_pin_setflags), DEVMETHOD(gpio_pin_get, imx51_gpio_pin_get), DEVMETHOD(gpio_pin_set, imx51_gpio_pin_set), DEVMETHOD(gpio_pin_toggle, imx51_gpio_pin_toggle), DEVMETHOD(gpio_pin_access_32, imx51_gpio_pin_access_32), DEVMETHOD(gpio_pin_config_32, imx51_gpio_pin_config_32), {0, 0}, }; static driver_t imx51_gpio_driver = { "gpio", imx51_gpio_methods, sizeof(struct imx51_gpio_softc), }; static devclass_t imx51_gpio_devclass; -DRIVER_MODULE(imx51_gpio, simplebus, imx51_gpio_driver, imx51_gpio_devclass, - 0, 0); +EARLY_DRIVER_MODULE(imx51_gpio, simplebus, imx51_gpio_driver, + imx51_gpio_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LATE); Index: stable/11/sys/arm/freescale/imx/imx_spi.c =================================================================== --- stable/11/sys/arm/freescale/imx/imx_spi.c (revision 331518) +++ stable/11/sys/arm/freescale/imx/imx_spi.c (revision 331519) @@ -1,604 +1,611 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2018 Ian Lepore * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); /* * Driver for imx Enhanced Configurable SPI; master-mode only. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "spibus_if.h" #define ECSPI_RXDATA 0x00 #define ECSPI_TXDATA 0x04 #define ECSPI_CTLREG 0x08 #define CTLREG_BLEN_SHIFT 20 #define CTLREG_BLEN_MASK 0x0fff #define CTLREG_CSEL_SHIFT 18 #define CTLREG_CSEL_MASK 0x03 #define CTLREG_DRCTL_SHIFT 16 #define CTLREG_DRCTL_MASK 0x03 #define CTLREG_PREDIV_SHIFT 12 #define CTLREG_PREDIV_MASK 0x0f #define CTLREG_POSTDIV_SHIFT 8 #define CTLREG_POSTDIV_MASK 0x0f #define CTLREG_CMODE_SHIFT 4 #define CTLREG_CMODE_MASK 0x0f #define CTLREG_CMODES_MASTER (CTLREG_CMODE_MASK << CTLREG_CMODE_SHIFT) #define CTLREG_SMC (1u << 3) #define CTLREG_XCH (1u << 2) #define CTLREG_HT (1u << 1) #define CTLREG_EN (1u << 0) #define ECSPI_CFGREG 0x0c #define CFGREG_HTLEN_SHIFT 24 #define CFGREG_SCLKCTL_SHIFT 20 #define CFGREG_DATACTL_SHIFT 16 #define CFGREG_SSPOL_SHIFT 12 #define CFGREG_SSCTL_SHIFT 8 #define CFGREG_SCLKPOL_SHIFT 4 #define CFGREG_SCLKPHA_SHIFT 0 #define CFGREG_MASK 0x0f /* all CFGREG fields are 4 bits */ #define ECSPI_INTREG 0x10 #define INTREG_TCEN (1u << 7) #define INTREG_ROEN (1u << 6) #define INTREG_RFEN (1u << 5) #define INTREG_RDREN (1u << 4) #define INTREG_RREN (1u << 3) #define INTREG_TFEN (1u << 2) #define INTREG_TDREN (1u << 1) #define INTREG_TEEN (1u << 0) #define ECSPI_DMAREG 0x14 #define DMA_RX_THRESH_SHIFT 16 #define DMA_RX_THRESH_MASK 0x3f #define DMA_TX_THRESH_SHIFT 0 #define DMA_TX_THRESH_MASK 0x3f #define ECSPI_STATREG 0x18 #define SREG_TC (1u << 7) #define SREG_RO (1u << 6) #define SREG_RF (1u << 5) #define SREG_RDR (1u << 4) #define SREG_RR (1u << 3) #define SREG_TF (1u << 2) #define SREG_TDR (1u << 1) #define SREG_TE (1u << 0) #define ECSPI_PERIODREG 0x1c #define ECSPI_TESTREG 0x20 #define CS_MAX 4 /* Max number of chip selects. */ #define CS_MASK 0x03 /* Mask flag bits out of chipsel. */ #define FIFO_SIZE 64 #define FIFO_RXTHRESH 32 #define FIFO_TXTHRESH 32 struct spi_softc { device_t dev; device_t spibus; struct mtx mtx; struct resource *memres; struct resource *intres; void *inthandle; gpio_pin_t cspins[CS_MAX]; u_int debug; u_int basefreq; uint32_t ctlreg; uint32_t intreg; uint32_t fifocnt; uint8_t *rxbuf; uint32_t rxidx; uint32_t rxlen; uint8_t *txbuf; uint32_t txidx; uint32_t txlen; }; static struct ofw_compat_data compat_data[] = { {"fsl,imx51-ecspi", true}, {"fsl,imx53-ecspi", true}, {"fsl,imx6dl-ecspi", true}, {"fsl,imx6q-ecspi", true}, {"fsl,imx6sx-ecspi", true}, {"fsl,imx6ul-ecspi", true}, {NULL, false} }; static inline uint32_t RD4(struct spi_softc *sc, bus_size_t offset) { return (bus_read_4(sc->memres, offset)); } static inline void WR4(struct spi_softc *sc, bus_size_t offset, uint32_t value) { bus_write_4(sc->memres, offset, value); } static u_int spi_calc_clockdiv(struct spi_softc *sc, u_int busfreq) { u_int post, pre; /* Returning 0 effectively sets both dividers to 1. */ if (sc->basefreq <= busfreq) return (0); /* * Brute-force this; all real-world bus speeds are going to be found on * the 1st or 2nd time through this loop. */ for (post = 0; post < 16; ++post) { pre = ((sc->basefreq >> post) / busfreq) - 1; if (pre < 16) break; } if (post == 16) { /* The lowest we can go is ~115 Hz. */ pre = 15; post = 15; } if (sc->debug >= 2) { device_printf(sc->dev, "base %u bus %u; pre %u, post %u; actual busfreq %u\n", sc->basefreq, busfreq, pre, post, (sc->basefreq / (pre + 1)) / (1 << post)); } return (pre << CTLREG_PREDIV_SHIFT) | (post << CTLREG_POSTDIV_SHIFT); } static void spi_set_chipsel(struct spi_softc *sc, u_int cs, bool active) { bool pinactive; /* * This is kinda crazy... the gpio pins for chipsel are defined as * active-high in the dts, but are supposed to be treated as active-low * by this driver. So to turn on chipsel we have to invert the value * passed to gpio_pin_set_active(). Then, to make it more fun, any * slave can say its chipsel is active-high, so if that option is * on, we have to invert the value again. */ pinactive = !active ^ (bool)(cs & SPIBUS_CS_HIGH); if (sc->debug >= 2) { device_printf(sc->dev, "chipsel %u changed to %u\n", (cs & ~SPIBUS_CS_HIGH), pinactive); } /* * Change the pin, then do a dummy read of its current state to ensure * that the state change reaches the hardware before proceeding. */ gpio_pin_set_active(sc->cspins[cs & ~SPIBUS_CS_HIGH], pinactive); gpio_pin_is_active(sc->cspins[cs & ~SPIBUS_CS_HIGH], &pinactive); } static void spi_hw_setup(struct spi_softc *sc, u_int cs, u_int mode, u_int freq) { uint32_t reg; /* * Set up control register, and write it first to bring the device out * of reset. */ sc->ctlreg = CTLREG_EN | CTLREG_CMODES_MASTER | CTLREG_SMC; sc->ctlreg |= spi_calc_clockdiv(sc, freq); sc->ctlreg |= 7 << CTLREG_BLEN_SHIFT; /* XXX byte at a time */ WR4(sc, ECSPI_CTLREG, sc->ctlreg); /* * Set up the config register. Note that we do all transfers with the * SPI hardware's chip-select set to zero. The actual chip select is * handled with a gpio pin. */ reg = 0; if (cs & SPIBUS_CS_HIGH) reg |= 1u << CFGREG_SSPOL_SHIFT; if (mode & SPIBUS_MODE_CPHA) reg |= 1u << CFGREG_SCLKPHA_SHIFT; if (mode & SPIBUS_MODE_CPOL) { reg |= 1u << CFGREG_SCLKPOL_SHIFT; reg |= 1u << CFGREG_SCLKCTL_SHIFT; } WR4(sc, ECSPI_CFGREG, reg); /* * Set up the rx/tx FIFO interrupt thresholds. */ reg = (FIFO_RXTHRESH << DMA_RX_THRESH_SHIFT); reg |= (FIFO_TXTHRESH << DMA_TX_THRESH_SHIFT); WR4(sc, ECSPI_DMAREG, reg); /* * Do a dummy read, to make sure the preceding writes reach the spi * hardware before we assert any gpio chip select. */ (void)RD4(sc, ECSPI_CFGREG); } static void spi_empty_rxfifo(struct spi_softc *sc) { while (sc->rxidx < sc->rxlen && (RD4(sc, ECSPI_STATREG) & SREG_RR)) { sc->rxbuf[sc->rxidx++] = (uint8_t)RD4(sc, ECSPI_RXDATA); --sc->fifocnt; } } static void spi_fill_txfifo(struct spi_softc *sc) { while (sc->txidx < sc->txlen && sc->fifocnt < FIFO_SIZE) { WR4(sc, ECSPI_TXDATA, sc->txbuf[sc->txidx++]); ++sc->fifocnt; } /* * If we're out of data, disable tx data ready (threshold) interrupts, * and enable tx fifo empty interrupts. */ if (sc->txidx == sc->txlen) sc->intreg = (sc->intreg & ~INTREG_TDREN) | INTREG_TEEN; } static void spi_intr(void *arg) { struct spi_softc *sc = arg; uint32_t intreg, status; mtx_lock(&sc->mtx); sc = arg; intreg = sc->intreg; status = RD4(sc, ECSPI_STATREG); WR4(sc, ECSPI_STATREG, status); /* Clear w1c bits. */ /* * If we get an overflow error, just signal that the transfer is done * and wakeup the waiting thread, which will see that txidx != txlen and * return an IO error to the caller. */ if (__predict_false(status & SREG_RO)) { if (sc->debug || bootverbose) { device_printf(sc->dev, "rxoverflow rxidx %u txidx %u\n", sc->rxidx, sc->txidx); } sc->intreg = 0; wakeup(sc); mtx_unlock(&sc->mtx); return; } if (status & SREG_RR) spi_empty_rxfifo(sc); if (status & SREG_TDR) spi_fill_txfifo(sc); /* * If we're out of bytes to send... * - If Transfer Complete is set (shift register is empty) and we've * received everything we expect, we're all done. * - Else if Tx Fifo Empty is set, we need to stop waiting for that and * switch to waiting for Transfer Complete (wait for shift register * to empty out), and also for Receive Ready (last of incoming data). */ if (sc->txidx == sc->txlen) { if ((status & SREG_TC) && sc->fifocnt == 0) { sc->intreg = 0; wakeup(sc); } else if (status & SREG_TE) { sc->intreg &= ~(sc->intreg & ~INTREG_TEEN); sc->intreg |= INTREG_TCEN | INTREG_RREN; } } /* * If interrupt flags changed, write the new flags to the hardware and * do a dummy readback to ensure the changes reach the hardware before * we exit the isr. */ if (sc->intreg != intreg) { WR4(sc, ECSPI_INTREG, sc->intreg); (void)RD4(sc, ECSPI_INTREG); } if (sc->debug >= 3) { device_printf(sc->dev, "spi_intr, sreg 0x%08x intreg was 0x%08x now 0x%08x\n", status, intreg, sc->intreg); } mtx_unlock(&sc->mtx); } static int spi_xfer_buf(struct spi_softc *sc, void *rxbuf, void *txbuf, uint32_t len) { int err; if (sc->debug >= 1) { device_printf(sc->dev, "spi_xfer_buf, rxbuf %p txbuf %p len %u\n", rxbuf, txbuf, len); } if (len == 0) return (0); sc->rxbuf = rxbuf; sc->rxlen = len; sc->rxidx = 0; sc->txbuf = txbuf; sc->txlen = len; sc->txidx = 0; sc->intreg = INTREG_RDREN | INTREG_TDREN; spi_fill_txfifo(sc); /* Enable interrupts last; spi_fill_txfifo() can change sc->intreg */ WR4(sc, ECSPI_INTREG, sc->intreg); err = 0; while (err == 0 && sc->intreg != 0) err = msleep(sc, &sc->mtx, 0, "imxspi", 10 * hz); if (sc->rxidx != sc->rxlen || sc->txidx != sc->txlen) err = EIO; return (err); } static int spi_transfer(device_t dev, device_t child, struct spi_command *cmd) { struct spi_softc *sc = device_get_softc(dev); uint32_t cs, mode, clock; int err; spibus_get_cs(child, &cs); spibus_get_clock(child, &clock); spibus_get_mode(child, &mode); if (cs > CS_MAX || sc->cspins[cs] == NULL) { if (sc->debug || bootverbose) device_printf(sc->dev, "Invalid chip select %u\n", cs); return (EINVAL); } mtx_lock(&sc->mtx); if (sc->debug >= 1) { device_printf(sc->dev, "spi_transfer, cs 0x%x clock %u mode %u\n", cs, clock, mode); } /* Set up the hardware and select the device. */ spi_hw_setup(sc, cs, mode, clock); spi_set_chipsel(sc, cs, true); /* Transfer command then data bytes. */ err = 0; if (cmd->tx_cmd_sz > 0) err = spi_xfer_buf(sc, cmd->rx_cmd, cmd->tx_cmd, cmd->tx_cmd_sz); if (cmd->tx_data_sz > 0 && err == 0) err = spi_xfer_buf(sc, cmd->rx_data, cmd->tx_data, cmd->tx_data_sz); /* Deselect the device, turn off (and reset) hardware. */ spi_set_chipsel(sc, cs, false); WR4(sc, ECSPI_CTLREG, 0); mtx_unlock(&sc->mtx); return (err); } static phandle_t spi_get_node(device_t bus, device_t dev) { /* * Share our controller node with our spibus child; it instantiates * devices by walking the children contained within our node. */ return ofw_bus_get_node(bus); } static int spi_detach(device_t dev) { struct spi_softc *sc = device_get_softc(dev); int idx; mtx_lock(&sc->mtx); bus_generic_detach(sc->dev); if (sc->spibus != NULL) device_delete_child(dev, sc->spibus); for (idx = 0; idx < nitems(sc->cspins); ++idx) { if (sc->cspins[idx] != NULL) gpio_pin_release(sc->cspins[idx]); } if (sc->inthandle != NULL) bus_teardown_intr(sc->dev, sc->intres, sc->inthandle); if (sc->intres != NULL) bus_release_resource(sc->dev, SYS_RES_IRQ, 0, sc->intres); if (sc->memres != NULL) bus_release_resource(sc->dev, SYS_RES_MEMORY, 0, sc->memres); mtx_unlock(&sc->mtx); mtx_destroy(&sc->mtx); return (0); } static int spi_attach(device_t dev) { struct spi_softc *sc = device_get_softc(dev); phandle_t node; int err, idx, rid; sc->dev = dev; sc->basefreq = imx_ccm_ecspi_hz(); mtx_init(&sc->mtx, device_get_nameunit(dev), NULL, MTX_DEF); /* Set up debug-enable sysctl. */ SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->dev), SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), OID_AUTO, "debug", CTLFLAG_RWTUN, &sc->debug, 0, "Enable debug, higher values = more info"); /* Allocate mmio register access resources. */ rid = 0; sc->memres = bus_alloc_resource_any(sc->dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->memres == NULL) { device_printf(sc->dev, "could not allocate registers\n"); spi_detach(sc->dev); return (ENXIO); } /* Allocate interrupt resources and set up handler. */ rid = 0; sc->intres = bus_alloc_resource_any(sc->dev, SYS_RES_IRQ, &rid, RF_ACTIVE); if (sc->intres == NULL) { device_printf(sc->dev, "could not allocate interrupt\n"); device_detach(sc->dev); return (ENXIO); } err = bus_setup_intr(sc->dev, sc->intres, INTR_TYPE_MISC | INTR_MPSAFE, NULL, spi_intr, sc, &sc->inthandle); if (err != 0) { device_printf(sc->dev, "could not setup interrupt handler"); device_detach(sc->dev); return (ENXIO); } /* Allocate gpio pins for configured chip selects. */ node = ofw_bus_get_node(sc->dev); - for (err = 0, idx = 0; err == 0 && idx < nitems(sc->cspins); ++idx) { + for (idx = 0; idx < nitems(sc->cspins); ++idx) { err = gpio_pin_get_by_ofw_propidx(sc->dev, node, "cs-gpios", idx, &sc->cspins[idx]); if (err == 0) { gpio_pin_setflags(sc->cspins[idx], GPIO_PIN_OUTPUT); } else if (sc->debug >= 2) { device_printf(sc->dev, "cannot configure gpio for chip select %u\n", idx); } } /* * Hardware init: put all channels into Master mode, turn off the enable * bit (gates off clocks); we only enable the hardware while xfers run. */ WR4(sc, ECSPI_CTLREG, CTLREG_CMODES_MASTER); - /* Attach the bus driver. */ + /* + * Add the spibus driver as a child, and setup a one-shot intrhook to + * attach it after interrupts are working. It will attach actual SPI + * devices as its children, and those devices may need to do IO during + * their attach. We can't do IO until timers and interrupts are working. + */ sc->spibus = device_add_child(dev, "spibus", -1); - return (bus_generic_attach(sc->dev)); + config_intrhook_oneshot((ich_func_t)bus_generic_attach, dev); + + return (0); } static int spi_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, "i.MX ECSPI Master"); return (BUS_PROBE_DEFAULT); } static device_method_t spi_methods[] = { DEVMETHOD(device_probe, spi_probe), DEVMETHOD(device_attach, spi_attach), DEVMETHOD(device_detach, spi_detach), /* spibus_if */ DEVMETHOD(spibus_transfer, spi_transfer), /* ofw_bus_if */ DEVMETHOD(ofw_bus_get_node, spi_get_node), DEVMETHOD_END }; static driver_t spi_driver = { "imx_spi", spi_methods, sizeof(struct spi_softc), }; static devclass_t spi_devclass; DRIVER_MODULE(imx_spi, simplebus, spi_driver, spi_devclass, 0, 0); DRIVER_MODULE(ofw_spibus, imx_spi, ofw_spibus_driver, ofw_spibus_devclass, 0, 0); MODULE_DEPEND(imx_spi, ofw_spibus, 1, 1, 1); Index: stable/11/sys/modules/imx/imx_spi/Makefile =================================================================== --- stable/11/sys/modules/imx/imx_spi/Makefile (revision 331518) +++ stable/11/sys/modules/imx/imx_spi/Makefile (revision 331519) @@ -1,16 +1,17 @@ # $FreeBSD$ .PATH: ${SRCTOP}/sys/arm/freescale/imx KMOD= imx_spi SRCS= imx_spi.c # Generated files... SRCS+= \ bus_if.h \ device_if.h \ + gpio_if.h \ ofw_bus_if.h \ opt_platform.h \ spibus_if.h \ .include