Index: head/sys/arm/allwinner/aw_gpio.c =================================================================== --- head/sys/arm/allwinner/aw_gpio.c (revision 332309) +++ head/sys/arm/allwinner/aw_gpio.c (revision 332310) @@ -1,1027 +1,1027 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2013 Ganbold Tsagaankhuu * Copyright (c) 2012 Oleksandr Tymoshenko * Copyright (c) 2012 Luiz Otavio O Souza. * 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$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__aarch64__) #include "opt_soc.h" #endif #include "gpio_if.h" #define AW_GPIO_DEFAULT_CAPS (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | \ GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN) #define AW_GPIO_NONE 0 #define AW_GPIO_PULLUP 1 #define AW_GPIO_PULLDOWN 2 #define AW_GPIO_INPUT 0 #define AW_GPIO_OUTPUT 1 #define AW_GPIO_DRV_MASK 0x3 #define AW_GPIO_PUD_MASK 0x3 #define AW_PINCTRL 1 #define AW_R_PINCTRL 2 /* Defined in aw_padconf.c */ #ifdef SOC_ALLWINNER_A10 extern const struct allwinner_padconf a10_padconf; #endif /* Defined in a13_padconf.c */ #ifdef SOC_ALLWINNER_A13 extern const struct allwinner_padconf a13_padconf; #endif /* Defined in a20_padconf.c */ #ifdef SOC_ALLWINNER_A20 extern const struct allwinner_padconf a20_padconf; #endif /* Defined in a31_padconf.c */ #ifdef SOC_ALLWINNER_A31 extern const struct allwinner_padconf a31_padconf; #endif /* Defined in a31s_padconf.c */ #ifdef SOC_ALLWINNER_A31S extern const struct allwinner_padconf a31s_padconf; #endif #if defined(SOC_ALLWINNER_A31) || defined(SOC_ALLWINNER_A31S) extern const struct allwinner_padconf a31_r_padconf; #endif /* Defined in a33_padconf.c */ #ifdef SOC_ALLWINNER_A33 extern const struct allwinner_padconf a33_padconf; #endif /* Defined in h3_padconf.c */ #if defined(SOC_ALLWINNER_H3) || defined(SOC_ALLWINNER_H5) extern const struct allwinner_padconf h3_padconf; extern const struct allwinner_padconf h3_r_padconf; #endif /* Defined in a83t_padconf.c */ #ifdef SOC_ALLWINNER_A83T extern const struct allwinner_padconf a83t_padconf; extern const struct allwinner_padconf a83t_r_padconf; #endif /* Defined in a64_padconf.c */ #ifdef SOC_ALLWINNER_A64 extern const struct allwinner_padconf a64_padconf; extern const struct allwinner_padconf a64_r_padconf; #endif static struct ofw_compat_data compat_data[] = { #ifdef SOC_ALLWINNER_A10 {"allwinner,sun4i-a10-pinctrl", (uintptr_t)&a10_padconf}, #endif #ifdef SOC_ALLWINNER_A13 {"allwinner,sun5i-a13-pinctrl", (uintptr_t)&a13_padconf}, #endif #ifdef SOC_ALLWINNER_A20 {"allwinner,sun7i-a20-pinctrl", (uintptr_t)&a20_padconf}, #endif #ifdef SOC_ALLWINNER_A31 {"allwinner,sun6i-a31-pinctrl", (uintptr_t)&a31_padconf}, #endif #ifdef SOC_ALLWINNER_A31S {"allwinner,sun6i-a31s-pinctrl", (uintptr_t)&a31s_padconf}, #endif #if defined(SOC_ALLWINNER_A31) || defined(SOC_ALLWINNER_A31S) {"allwinner,sun6i-a31-r-pinctrl", (uintptr_t)&a31_r_padconf}, #endif #ifdef SOC_ALLWINNER_A33 {"allwinner,sun6i-a33-pinctrl", (uintptr_t)&a33_padconf}, #endif #ifdef SOC_ALLWINNER_A83T {"allwinner,sun8i-a83t-pinctrl", (uintptr_t)&a83t_padconf}, {"allwinner,sun8i-a83t-r-pinctrl", (uintptr_t)&a83t_r_padconf}, #endif #if defined(SOC_ALLWINNER_H3) || defined(SOC_ALLWINNER_H5) {"allwinner,sun8i-h3-pinctrl", (uintptr_t)&h3_padconf}, {"allwinner,sun50i-h5-pinctrl", (uintptr_t)&h3_padconf}, {"allwinner,sun8i-h3-r-pinctrl", (uintptr_t)&h3_r_padconf}, #endif #ifdef SOC_ALLWINNER_A64 {"allwinner,sun50i-a64-pinctrl", (uintptr_t)&a64_padconf}, {"allwinner,sun50i-a64-r-pinctrl", (uintptr_t)&a64_r_padconf}, #endif {NULL, 0} }; struct clk_list { TAILQ_ENTRY(clk_list) next; clk_t clk; }; struct aw_gpio_softc { device_t sc_dev; device_t sc_busdev; struct mtx sc_mtx; struct resource * sc_mem_res; struct resource * sc_irq_res; bus_space_tag_t sc_bst; bus_space_handle_t sc_bsh; void * sc_intrhand; const struct allwinner_padconf * padconf; TAILQ_HEAD(, clk_list) clk_list; }; #define AW_GPIO_LOCK(_sc) mtx_lock_spin(&(_sc)->sc_mtx) #define AW_GPIO_UNLOCK(_sc) mtx_unlock_spin(&(_sc)->sc_mtx) #define AW_GPIO_LOCK_ASSERT(_sc) mtx_assert(&(_sc)->sc_mtx, MA_OWNED) #define AW_GPIO_GP_CFG(_bank, _idx) 0x00 + ((_bank) * 0x24) + ((_idx) << 2) #define AW_GPIO_GP_DAT(_bank) 0x10 + ((_bank) * 0x24) #define AW_GPIO_GP_DRV(_bank, _idx) 0x14 + ((_bank) * 0x24) + ((_idx) << 2) #define AW_GPIO_GP_PUL(_bank, _idx) 0x1c + ((_bank) * 0x24) + ((_idx) << 2) #define AW_GPIO_GP_INT_CFG0 0x200 #define AW_GPIO_GP_INT_CFG1 0x204 #define AW_GPIO_GP_INT_CFG2 0x208 #define AW_GPIO_GP_INT_CFG3 0x20c #define AW_GPIO_GP_INT_CTL 0x210 #define AW_GPIO_GP_INT_STA 0x214 #define AW_GPIO_GP_INT_DEB 0x218 static char *aw_gpio_parse_function(phandle_t node); static const char **aw_gpio_parse_pins(phandle_t node, int *pins_nb); static uint32_t aw_gpio_parse_bias(phandle_t node); static int aw_gpio_parse_drive_strength(phandle_t node, uint32_t *drive); static int aw_gpio_pin_get(device_t dev, uint32_t pin, unsigned int *value); static int aw_gpio_pin_set(device_t dev, uint32_t pin, unsigned int value); static int aw_gpio_pin_get_locked(struct aw_gpio_softc *sc, uint32_t pin, unsigned int *value); static int aw_gpio_pin_set_locked(struct aw_gpio_softc *sc, uint32_t pin, unsigned int value); #define AW_GPIO_WRITE(_sc, _off, _val) \ bus_space_write_4(_sc->sc_bst, _sc->sc_bsh, _off, _val) #define AW_GPIO_READ(_sc, _off) \ bus_space_read_4(_sc->sc_bst, _sc->sc_bsh, _off) static uint32_t aw_gpio_get_function(struct aw_gpio_softc *sc, uint32_t pin) { uint32_t bank, func, offset; /* Must be called with lock held. */ AW_GPIO_LOCK_ASSERT(sc); if (pin > sc->padconf->npins) return (0); bank = sc->padconf->pins[pin].port; pin = sc->padconf->pins[pin].pin; offset = ((pin & 0x07) << 2); func = AW_GPIO_READ(sc, AW_GPIO_GP_CFG(bank, pin >> 3)); return ((func >> offset) & 0x7); } static int aw_gpio_set_function(struct aw_gpio_softc *sc, uint32_t pin, uint32_t f) { uint32_t bank, data, offset; /* Check if the function exists in the padconf data */ if (sc->padconf->pins[pin].functions[f] == NULL) return (EINVAL); /* Must be called with lock held. */ AW_GPIO_LOCK_ASSERT(sc); bank = sc->padconf->pins[pin].port; pin = sc->padconf->pins[pin].pin; offset = ((pin & 0x07) << 2); data = AW_GPIO_READ(sc, AW_GPIO_GP_CFG(bank, pin >> 3)); data &= ~(7 << offset); data |= (f << offset); AW_GPIO_WRITE(sc, AW_GPIO_GP_CFG(bank, pin >> 3), data); return (0); } static uint32_t aw_gpio_get_pud(struct aw_gpio_softc *sc, uint32_t pin) { uint32_t bank, offset, val; /* Must be called with lock held. */ AW_GPIO_LOCK_ASSERT(sc); bank = sc->padconf->pins[pin].port; pin = sc->padconf->pins[pin].pin; offset = ((pin & 0x0f) << 1); val = AW_GPIO_READ(sc, AW_GPIO_GP_PUL(bank, pin >> 4)); return ((val >> offset) & AW_GPIO_PUD_MASK); } static void aw_gpio_set_pud(struct aw_gpio_softc *sc, uint32_t pin, uint32_t state) { uint32_t bank, offset, val; if (aw_gpio_get_pud(sc, pin) == state) return; /* Must be called with lock held. */ AW_GPIO_LOCK_ASSERT(sc); bank = sc->padconf->pins[pin].port; pin = sc->padconf->pins[pin].pin; offset = ((pin & 0x0f) << 1); val = AW_GPIO_READ(sc, AW_GPIO_GP_PUL(bank, pin >> 4)); val &= ~(AW_GPIO_PUD_MASK << offset); val |= (state << offset); AW_GPIO_WRITE(sc, AW_GPIO_GP_PUL(bank, pin >> 4), val); } static uint32_t aw_gpio_get_drv(struct aw_gpio_softc *sc, uint32_t pin) { uint32_t bank, offset, val; /* Must be called with lock held. */ AW_GPIO_LOCK_ASSERT(sc); bank = sc->padconf->pins[pin].port; pin = sc->padconf->pins[pin].pin; offset = ((pin & 0x0f) << 1); val = AW_GPIO_READ(sc, AW_GPIO_GP_DRV(bank, pin >> 4)); return ((val >> offset) & AW_GPIO_DRV_MASK); } static void aw_gpio_set_drv(struct aw_gpio_softc *sc, uint32_t pin, uint32_t drive) { uint32_t bank, offset, val; if (aw_gpio_get_drv(sc, pin) == drive) return; /* Must be called with lock held. */ AW_GPIO_LOCK_ASSERT(sc); bank = sc->padconf->pins[pin].port; pin = sc->padconf->pins[pin].pin; offset = ((pin & 0x0f) << 1); val = AW_GPIO_READ(sc, AW_GPIO_GP_DRV(bank, pin >> 4)); val &= ~(AW_GPIO_DRV_MASK << offset); val |= (drive << offset); AW_GPIO_WRITE(sc, AW_GPIO_GP_DRV(bank, pin >> 4), val); } static int aw_gpio_pin_configure(struct aw_gpio_softc *sc, uint32_t pin, uint32_t flags) { u_int val; int err = 0; /* Must be called with lock held. */ AW_GPIO_LOCK_ASSERT(sc); if (pin > sc->padconf->npins) return (EINVAL); /* Manage input/output. */ if (flags & GPIO_PIN_INPUT) { err = aw_gpio_set_function(sc, pin, AW_GPIO_INPUT); } else if ((flags & GPIO_PIN_OUTPUT) && aw_gpio_get_function(sc, pin) != AW_GPIO_OUTPUT) { if (flags & GPIO_PIN_PRESET_LOW) { aw_gpio_pin_set_locked(sc, pin, 0); } else if (flags & GPIO_PIN_PRESET_HIGH) { aw_gpio_pin_set_locked(sc, pin, 1); } else { /* Read the pin and preset output to current state. */ err = aw_gpio_set_function(sc, pin, AW_GPIO_INPUT); if (err == 0) { aw_gpio_pin_get_locked(sc, pin, &val); aw_gpio_pin_set_locked(sc, pin, val); } } if (err == 0) err = aw_gpio_set_function(sc, pin, AW_GPIO_OUTPUT); } if (err) return (err); /* Manage Pull-up/pull-down. */ if (flags & GPIO_PIN_PULLUP) aw_gpio_set_pud(sc, pin, AW_GPIO_PULLUP); else if (flags & GPIO_PIN_PULLDOWN) aw_gpio_set_pud(sc, pin, AW_GPIO_PULLDOWN); else aw_gpio_set_pud(sc, pin, AW_GPIO_NONE); return (0); } static device_t aw_gpio_get_bus(device_t dev) { struct aw_gpio_softc *sc; sc = device_get_softc(dev); return (sc->sc_busdev); } static int aw_gpio_pin_max(device_t dev, int *maxpin) { struct aw_gpio_softc *sc; sc = device_get_softc(dev); *maxpin = sc->padconf->npins - 1; return (0); } static int aw_gpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) { struct aw_gpio_softc *sc; sc = device_get_softc(dev); if (pin >= sc->padconf->npins) return (EINVAL); *caps = AW_GPIO_DEFAULT_CAPS; return (0); } static int aw_gpio_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags) { struct aw_gpio_softc *sc; uint32_t func; uint32_t pud; sc = device_get_softc(dev); if (pin >= sc->padconf->npins) return (EINVAL); AW_GPIO_LOCK(sc); func = aw_gpio_get_function(sc, pin); switch (func) { case AW_GPIO_INPUT: *flags = GPIO_PIN_INPUT; break; case AW_GPIO_OUTPUT: *flags = GPIO_PIN_OUTPUT; break; default: *flags = 0; break; } pud = aw_gpio_get_pud(sc, pin); switch (pud) { case AW_GPIO_PULLDOWN: *flags |= GPIO_PIN_PULLDOWN; break; case AW_GPIO_PULLUP: *flags |= GPIO_PIN_PULLUP; break; default: break; } AW_GPIO_UNLOCK(sc); return (0); } static int aw_gpio_pin_getname(device_t dev, uint32_t pin, char *name) { struct aw_gpio_softc *sc; sc = device_get_softc(dev); if (pin >= sc->padconf->npins) return (EINVAL); snprintf(name, GPIOMAXNAME - 1, "%s", sc->padconf->pins[pin].name); name[GPIOMAXNAME - 1] = '\0'; return (0); } static int aw_gpio_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) { struct aw_gpio_softc *sc; int err; sc = device_get_softc(dev); if (pin > sc->padconf->npins) return (EINVAL); AW_GPIO_LOCK(sc); err = aw_gpio_pin_configure(sc, pin, flags); AW_GPIO_UNLOCK(sc); return (err); } static int aw_gpio_pin_set_locked(struct aw_gpio_softc *sc, uint32_t pin, unsigned int value) { uint32_t bank, data; AW_GPIO_LOCK_ASSERT(sc); if (pin > sc->padconf->npins) return (EINVAL); bank = sc->padconf->pins[pin].port; pin = sc->padconf->pins[pin].pin; data = AW_GPIO_READ(sc, AW_GPIO_GP_DAT(bank)); if (value) data |= (1 << pin); else data &= ~(1 << pin); AW_GPIO_WRITE(sc, AW_GPIO_GP_DAT(bank), data); return (0); } static int aw_gpio_pin_set(device_t dev, uint32_t pin, unsigned int value) { struct aw_gpio_softc *sc; int ret; sc = device_get_softc(dev); AW_GPIO_LOCK(sc); ret = aw_gpio_pin_set_locked(sc, pin, value); AW_GPIO_UNLOCK(sc); return (ret); } static int aw_gpio_pin_get_locked(struct aw_gpio_softc *sc,uint32_t pin, unsigned int *val) { uint32_t bank, reg_data; AW_GPIO_LOCK_ASSERT(sc); if (pin > sc->padconf->npins) return (EINVAL); bank = sc->padconf->pins[pin].port; pin = sc->padconf->pins[pin].pin; reg_data = AW_GPIO_READ(sc, AW_GPIO_GP_DAT(bank)); *val = (reg_data & (1 << pin)) ? 1 : 0; return (0); } static char * aw_gpio_parse_function(phandle_t node) { char *function; - if (OF_getprop_alloc(node, "function", sizeof(*function), + if (OF_getprop_alloc(node, "function", (void **)&function) != -1) return (function); - if (OF_getprop_alloc(node, "allwinner,function", sizeof(*function), + if (OF_getprop_alloc(node, "allwinner,function", (void **)&function) != -1) return (function); return (NULL); } static const char ** aw_gpio_parse_pins(phandle_t node, int *pins_nb) { const char **pinlist; *pins_nb = ofw_bus_string_list_to_array(node, "pins", &pinlist); if (*pins_nb > 0) return (pinlist); *pins_nb = ofw_bus_string_list_to_array(node, "allwinner,pins", &pinlist); if (*pins_nb > 0) return (pinlist); return (NULL); } static uint32_t aw_gpio_parse_bias(phandle_t node) { uint32_t bias; if (OF_getencprop(node, "pull", &bias, sizeof(bias)) != -1) return (bias); if (OF_getencprop(node, "allwinner,pull", &bias, sizeof(bias)) != -1) return (bias); if (OF_hasprop(node, "bias-disable")) return (AW_GPIO_NONE); if (OF_hasprop(node, "bias-pull-up")) return (AW_GPIO_PULLUP); if (OF_hasprop(node, "bias-pull-down")) return (AW_GPIO_PULLDOWN); return (AW_GPIO_NONE); } static int aw_gpio_parse_drive_strength(phandle_t node, uint32_t *drive) { uint32_t drive_str; if (OF_getencprop(node, "drive", drive, sizeof(*drive)) != -1) return (0); if (OF_getencprop(node, "allwinner,drive", drive, sizeof(*drive)) != -1) return (0); if (OF_getencprop(node, "drive-strength", &drive_str, sizeof(drive_str)) != -1) { *drive = (drive_str / 10) - 1; return (0); } return (1); } static int aw_gpio_pin_get(device_t dev, uint32_t pin, unsigned int *val) { struct aw_gpio_softc *sc; int ret; sc = device_get_softc(dev); AW_GPIO_LOCK(sc); ret = aw_gpio_pin_get_locked(sc, pin, val); AW_GPIO_UNLOCK(sc); return (ret); } static int aw_gpio_pin_toggle(device_t dev, uint32_t pin) { struct aw_gpio_softc *sc; uint32_t bank, data; sc = device_get_softc(dev); if (pin > sc->padconf->npins) return (EINVAL); bank = sc->padconf->pins[pin].port; pin = sc->padconf->pins[pin].pin; AW_GPIO_LOCK(sc); data = AW_GPIO_READ(sc, AW_GPIO_GP_DAT(bank)); if (data & (1 << pin)) data &= ~(1 << pin); else data |= (1 << pin); AW_GPIO_WRITE(sc, AW_GPIO_GP_DAT(bank), data); AW_GPIO_UNLOCK(sc); return (0); } static int aw_gpio_pin_access_32(device_t dev, uint32_t first_pin, uint32_t clear_pins, uint32_t change_pins, uint32_t *orig_pins) { struct aw_gpio_softc *sc; uint32_t bank, data, pin; sc = device_get_softc(dev); if (first_pin > sc->padconf->npins) return (EINVAL); /* * We require that first_pin refers to the first pin in a bank, because * this API is not about convenience, it's for making a set of pins * change simultaneously (required) with reasonably high performance * (desired); we need to do a read-modify-write on a single register. */ bank = sc->padconf->pins[first_pin].port; pin = sc->padconf->pins[first_pin].pin; if (pin != 0) return (EINVAL); AW_GPIO_LOCK(sc); data = AW_GPIO_READ(sc, AW_GPIO_GP_DAT(bank)); if ((clear_pins | change_pins) != 0) AW_GPIO_WRITE(sc, AW_GPIO_GP_DAT(bank), (data & ~clear_pins) ^ change_pins); AW_GPIO_UNLOCK(sc); if (orig_pins != NULL) *orig_pins = data; return (0); } static int aw_gpio_pin_config_32(device_t dev, uint32_t first_pin, uint32_t num_pins, uint32_t *pin_flags) { struct aw_gpio_softc *sc; uint32_t bank, pin; int err; sc = device_get_softc(dev); if (first_pin > sc->padconf->npins) return (EINVAL); bank = sc->padconf->pins[first_pin].port; if (sc->padconf->pins[first_pin].pin != 0) return (EINVAL); /* * The configuration for a bank of pins is scattered among several * registers; we cannot g'tee to simultaneously change the state of all * the pins in the flags array. So just loop through the array * configuring each pin for now. If there was a strong need, it might * be possible to support some limited simultaneous config, such as * adjacent groups of 8 pins that line up the same as the config regs. */ for (err = 0, pin = first_pin; err == 0 && pin < num_pins; ++pin) { if (pin_flags[pin] & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) err = aw_gpio_pin_configure(sc, pin, pin_flags[pin]); } return (err); } static int aw_find_pinnum_by_name(struct aw_gpio_softc *sc, const char *pinname) { int i; for (i = 0; i < sc->padconf->npins; i++) if (!strcmp(pinname, sc->padconf->pins[i].name)) return i; return (-1); } static int aw_find_pin_func(struct aw_gpio_softc *sc, int pin, const char *func) { int i; for (i = 0; i < AW_MAX_FUNC_BY_PIN; i++) if (sc->padconf->pins[pin].functions[i] && !strcmp(func, sc->padconf->pins[pin].functions[i])) return (i); return (-1); } static int aw_fdt_configure_pins(device_t dev, phandle_t cfgxref) { struct aw_gpio_softc *sc; phandle_t node; const char **pinlist = NULL; char *pin_function = NULL; uint32_t pin_drive, pin_pull; int pins_nb, pin_num, pin_func, i, ret; bool set_drive; sc = device_get_softc(dev); node = OF_node_from_xref(cfgxref); ret = 0; set_drive = false; /* Getting all prop for configuring pins */ pinlist = aw_gpio_parse_pins(node, &pins_nb); if (pinlist == NULL) return (ENOENT); pin_function = aw_gpio_parse_function(node); if (pin_function == NULL) { ret = ENOENT; goto out; } if (aw_gpio_parse_drive_strength(node, &pin_drive) == 0) set_drive = true; pin_pull = aw_gpio_parse_bias(node); /* Configure each pin to the correct function, drive and pull */ for (i = 0; i < pins_nb; i++) { pin_num = aw_find_pinnum_by_name(sc, pinlist[i]); if (pin_num == -1) { ret = ENOENT; goto out; } pin_func = aw_find_pin_func(sc, pin_num, pin_function); if (pin_func == -1) { ret = ENOENT; goto out; } AW_GPIO_LOCK(sc); if (aw_gpio_get_function(sc, pin_num) != pin_func) aw_gpio_set_function(sc, pin_num, pin_func); if (set_drive) aw_gpio_set_drv(sc, pin_num, pin_drive); if (pin_pull != AW_GPIO_NONE) aw_gpio_set_pud(sc, pin_num, pin_pull); AW_GPIO_UNLOCK(sc); } out: OF_prop_free(pinlist); OF_prop_free(pin_function); return (ret); } static int aw_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, "Allwinner GPIO/Pinmux controller"); return (BUS_PROBE_DEFAULT); } static int aw_gpio_attach(device_t dev) { int rid, error; phandle_t gpio; struct aw_gpio_softc *sc; struct clk_list *clkp, *clkp_tmp; clk_t clk; hwreset_t rst = NULL; int off, err, clkret; sc = device_get_softc(dev); sc->sc_dev = dev; mtx_init(&sc->sc_mtx, "aw gpio", "gpio", MTX_SPIN); rid = 0; sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (!sc->sc_mem_res) { device_printf(dev, "cannot allocate memory window\n"); goto fail; } sc->sc_bst = rman_get_bustag(sc->sc_mem_res); sc->sc_bsh = rman_get_bushandle(sc->sc_mem_res); rid = 0; sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); if (!sc->sc_irq_res) { device_printf(dev, "cannot allocate interrupt\n"); goto fail; } /* Find our node. */ gpio = ofw_bus_get_node(sc->sc_dev); if (!OF_hasprop(gpio, "gpio-controller")) /* Node is not a GPIO controller. */ goto fail; /* Use the right pin data for the current SoC */ sc->padconf = (struct allwinner_padconf *)ofw_bus_search_compatible(dev, compat_data)->ocd_data; if (hwreset_get_by_ofw_idx(dev, 0, 0, &rst) == 0) { error = hwreset_deassert(rst); if (error != 0) { device_printf(dev, "cannot de-assert reset\n"); goto fail; } } TAILQ_INIT(&sc->clk_list); for (off = 0, clkret = 0; clkret == 0; off++) { clkret = clk_get_by_ofw_index(dev, 0, off, &clk); if (clkret != 0) break; err = clk_enable(clk); if (err != 0) { device_printf(dev, "Could not enable clock %s\n", clk_get_name(clk)); goto fail; } clkp = malloc(sizeof(*clkp), M_DEVBUF, M_WAITOK | M_ZERO); clkp->clk = clk; TAILQ_INSERT_TAIL(&sc->clk_list, clkp, next); } if (clkret != 0 && clkret != ENOENT) { device_printf(dev, "Could not find clock at offset %d (%d)\n", off, clkret); goto fail; } sc->sc_busdev = gpiobus_attach_bus(dev); if (sc->sc_busdev == NULL) goto fail; /* * Register as a pinctrl device */ fdt_pinctrl_register(dev, "pins"); fdt_pinctrl_configure_tree(dev); fdt_pinctrl_register(dev, "allwinner,pins"); fdt_pinctrl_configure_tree(dev); return (0); fail: if (sc->sc_irq_res) bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res); if (sc->sc_mem_res) bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res); mtx_destroy(&sc->sc_mtx); /* Disable clock */ TAILQ_FOREACH_SAFE(clkp, &sc->clk_list, next, clkp_tmp) { err = clk_disable(clkp->clk); if (err != 0) device_printf(dev, "Could not disable clock %s\n", clk_get_name(clkp->clk)); err = clk_release(clkp->clk); if (err != 0) device_printf(dev, "Could not release clock %s\n", clk_get_name(clkp->clk)); TAILQ_REMOVE(&sc->clk_list, clkp, next); free(clkp, M_DEVBUF); } /* Assert resets */ if (rst) { hwreset_assert(rst); hwreset_release(rst); } return (ENXIO); } static int aw_gpio_detach(device_t dev) { return (EBUSY); } static phandle_t aw_gpio_get_node(device_t dev, device_t bus) { /* We only have one child, the GPIO bus, which needs our own node. */ return (ofw_bus_get_node(dev)); } static int aw_gpio_map_gpios(device_t bus, phandle_t dev, phandle_t gparent, int gcells, pcell_t *gpios, uint32_t *pin, uint32_t *flags) { struct aw_gpio_softc *sc; int i; sc = device_get_softc(bus); /* The GPIO pins are mapped as: . */ for (i = 0; i < sc->padconf->npins; i++) if (sc->padconf->pins[i].port == gpios[0] && sc->padconf->pins[i].pin == gpios[1]) { *pin = i; break; } *flags = gpios[gcells - 1]; return (0); } static device_method_t aw_gpio_methods[] = { /* Device interface */ DEVMETHOD(device_probe, aw_gpio_probe), DEVMETHOD(device_attach, aw_gpio_attach), DEVMETHOD(device_detach, aw_gpio_detach), /* GPIO protocol */ DEVMETHOD(gpio_get_bus, aw_gpio_get_bus), DEVMETHOD(gpio_pin_max, aw_gpio_pin_max), DEVMETHOD(gpio_pin_getname, aw_gpio_pin_getname), DEVMETHOD(gpio_pin_getflags, aw_gpio_pin_getflags), DEVMETHOD(gpio_pin_getcaps, aw_gpio_pin_getcaps), DEVMETHOD(gpio_pin_setflags, aw_gpio_pin_setflags), DEVMETHOD(gpio_pin_get, aw_gpio_pin_get), DEVMETHOD(gpio_pin_set, aw_gpio_pin_set), DEVMETHOD(gpio_pin_toggle, aw_gpio_pin_toggle), DEVMETHOD(gpio_pin_access_32, aw_gpio_pin_access_32), DEVMETHOD(gpio_pin_config_32, aw_gpio_pin_config_32), DEVMETHOD(gpio_map_gpios, aw_gpio_map_gpios), /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_node, aw_gpio_get_node), /* fdt_pinctrl interface */ DEVMETHOD(fdt_pinctrl_configure,aw_fdt_configure_pins), DEVMETHOD_END }; static devclass_t aw_gpio_devclass; static driver_t aw_gpio_driver = { "gpio", aw_gpio_methods, sizeof(struct aw_gpio_softc), }; EARLY_DRIVER_MODULE(aw_gpio, simplebus, aw_gpio_driver, aw_gpio_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LATE); Index: head/sys/arm/allwinner/aw_if_dwc.c =================================================================== --- head/sys/arm/allwinner/aw_if_dwc.c (revision 332309) +++ head/sys/arm/allwinner/aw_if_dwc.c (revision 332310) @@ -1,145 +1,145 @@ /*- * Copyright (c) 2015 Luiz Otavio O Souza * 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$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include "if_dwc_if.h" static int a20_if_dwc_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "allwinner,sun7i-a20-gmac")) return (ENXIO); device_set_desc(dev, "A20 Gigabit Ethernet Controller"); return (BUS_PROBE_DEFAULT); } static int a20_if_dwc_init(device_t dev) { const char *tx_parent_name; char *phy_type; clk_t clk_tx, clk_tx_parent; regulator_t reg; phandle_t node; int error; node = ofw_bus_get_node(dev); /* Configure PHY for MII or RGMII mode */ - if (OF_getprop_alloc(node, "phy-mode", 1, (void **)&phy_type)) { + if (OF_getprop_alloc(node, "phy-mode", (void **)&phy_type)) { error = clk_get_by_ofw_name(dev, 0, "allwinner_gmac_tx", &clk_tx); if (error != 0) { device_printf(dev, "could not get tx clk\n"); return (error); } if (strcmp(phy_type, "rgmii") == 0) tx_parent_name = "gmac_int_tx"; else tx_parent_name = "mii_phy_tx"; error = clk_get_by_name(dev, tx_parent_name, &clk_tx_parent); if (error != 0) { device_printf(dev, "could not get clock '%s'\n", tx_parent_name); return (error); } error = clk_set_parent_by_clk(clk_tx, clk_tx_parent); if (error != 0) { device_printf(dev, "could not set tx clk parent\n"); return (error); } } /* Enable PHY regulator if applicable */ if (regulator_get_by_ofw_property(dev, 0, "phy-supply", ®) == 0) { error = regulator_enable(reg); if (error != 0) { device_printf(dev, "could not enable PHY regulator\n"); return (error); } } return (0); } static int a20_if_dwc_mac_type(device_t dev) { return (DWC_GMAC_ALT_DESC); } static int a20_if_dwc_mii_clk(device_t dev) { return (GMAC_MII_CLK_150_250M_DIV102); } static device_method_t a20_dwc_methods[] = { DEVMETHOD(device_probe, a20_if_dwc_probe), DEVMETHOD(if_dwc_init, a20_if_dwc_init), DEVMETHOD(if_dwc_mac_type, a20_if_dwc_mac_type), DEVMETHOD(if_dwc_mii_clk, a20_if_dwc_mii_clk), DEVMETHOD_END }; static devclass_t a20_dwc_devclass; extern driver_t dwc_driver; DEFINE_CLASS_1(dwc, a20_dwc_driver, a20_dwc_methods, sizeof(struct dwc_softc), dwc_driver); DRIVER_MODULE(a20_dwc, simplebus, a20_dwc_driver, a20_dwc_devclass, 0, 0); MODULE_DEPEND(a20_dwc, dwc, 1, 1, 1); Index: head/sys/arm/allwinner/if_awg.c =================================================================== --- head/sys/arm/allwinner/if_awg.c (revision 332309) +++ head/sys/arm/allwinner/if_awg.c (revision 332310) @@ -1,1911 +1,1911 @@ /*- * Copyright (c) 2016 Jared McNeill * 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 ``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 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. * * $FreeBSD$ */ /* * Allwinner Gigabit Ethernet MAC (EMAC) controller */ #include "opt_device_polling.h" #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "syscon_if.h" #include "miibus_if.h" #include "gpio_if.h" #define RD4(sc, reg) bus_read_4((sc)->res[_RES_EMAC], (reg)) #define WR4(sc, reg, val) bus_write_4((sc)->res[_RES_EMAC], (reg), (val)) #define AWG_LOCK(sc) mtx_lock(&(sc)->mtx) #define AWG_UNLOCK(sc) mtx_unlock(&(sc)->mtx); #define AWG_ASSERT_LOCKED(sc) mtx_assert(&(sc)->mtx, MA_OWNED) #define AWG_ASSERT_UNLOCKED(sc) mtx_assert(&(sc)->mtx, MA_NOTOWNED) #define DESC_ALIGN 4 #define TX_DESC_COUNT 1024 #define TX_DESC_SIZE (sizeof(struct emac_desc) * TX_DESC_COUNT) #define RX_DESC_COUNT 256 #define RX_DESC_SIZE (sizeof(struct emac_desc) * RX_DESC_COUNT) #define DESC_OFF(n) ((n) * sizeof(struct emac_desc)) #define TX_NEXT(n) (((n) + 1) & (TX_DESC_COUNT - 1)) #define TX_SKIP(n, o) (((n) + (o)) & (TX_DESC_COUNT - 1)) #define RX_NEXT(n) (((n) + 1) & (RX_DESC_COUNT - 1)) #define TX_MAX_SEGS 20 #define SOFT_RST_RETRY 1000 #define MII_BUSY_RETRY 1000 #define MDIO_FREQ 2500000 #define BURST_LEN_DEFAULT 8 #define RX_TX_PRI_DEFAULT 0 #define PAUSE_TIME_DEFAULT 0x400 #define TX_INTERVAL_DEFAULT 64 #define RX_BATCH_DEFAULT 64 /* syscon EMAC clock register */ #define EMAC_CLK_REG 0x30 #define EMAC_CLK_EPHY_ADDR (0x1f << 20) /* H3 */ #define EMAC_CLK_EPHY_ADDR_SHIFT 20 #define EMAC_CLK_EPHY_LED_POL (1 << 17) /* H3 */ #define EMAC_CLK_EPHY_SHUTDOWN (1 << 16) /* H3 */ #define EMAC_CLK_EPHY_SELECT (1 << 15) /* H3 */ #define EMAC_CLK_RMII_EN (1 << 13) #define EMAC_CLK_ETXDC (0x7 << 10) #define EMAC_CLK_ETXDC_SHIFT 10 #define EMAC_CLK_ERXDC (0x1f << 5) #define EMAC_CLK_ERXDC_SHIFT 5 #define EMAC_CLK_PIT (0x1 << 2) #define EMAC_CLK_PIT_MII (0 << 2) #define EMAC_CLK_PIT_RGMII (1 << 2) #define EMAC_CLK_SRC (0x3 << 0) #define EMAC_CLK_SRC_MII (0 << 0) #define EMAC_CLK_SRC_EXT_RGMII (1 << 0) #define EMAC_CLK_SRC_RGMII (2 << 0) /* Burst length of RX and TX DMA transfers */ static int awg_burst_len = BURST_LEN_DEFAULT; TUNABLE_INT("hw.awg.burst_len", &awg_burst_len); /* RX / TX DMA priority. If 1, RX DMA has priority over TX DMA. */ static int awg_rx_tx_pri = RX_TX_PRI_DEFAULT; TUNABLE_INT("hw.awg.rx_tx_pri", &awg_rx_tx_pri); /* Pause time field in the transmitted control frame */ static int awg_pause_time = PAUSE_TIME_DEFAULT; TUNABLE_INT("hw.awg.pause_time", &awg_pause_time); /* Request a TX interrupt every descriptors */ static int awg_tx_interval = TX_INTERVAL_DEFAULT; TUNABLE_INT("hw.awg.tx_interval", &awg_tx_interval); /* Maximum number of mbufs to send to if_input */ static int awg_rx_batch = RX_BATCH_DEFAULT; TUNABLE_INT("hw.awg.rx_batch", &awg_rx_batch); enum awg_type { EMAC_A83T = 1, EMAC_H3, EMAC_A64, }; static struct ofw_compat_data compat_data[] = { { "allwinner,sun8i-a83t-emac", EMAC_A83T }, { "allwinner,sun8i-h3-emac", EMAC_H3 }, { "allwinner,sun50i-a64-emac", EMAC_A64 }, { NULL, 0 } }; struct awg_bufmap { bus_dmamap_t map; struct mbuf *mbuf; }; struct awg_txring { bus_dma_tag_t desc_tag; bus_dmamap_t desc_map; struct emac_desc *desc_ring; bus_addr_t desc_ring_paddr; bus_dma_tag_t buf_tag; struct awg_bufmap buf_map[TX_DESC_COUNT]; u_int cur, next, queued; u_int segs; }; struct awg_rxring { bus_dma_tag_t desc_tag; bus_dmamap_t desc_map; struct emac_desc *desc_ring; bus_addr_t desc_ring_paddr; bus_dma_tag_t buf_tag; struct awg_bufmap buf_map[RX_DESC_COUNT]; bus_dmamap_t buf_spare_map; u_int cur; }; enum { _RES_EMAC, _RES_IRQ, _RES_SYSCON, _RES_NITEMS }; struct awg_softc { struct resource *res[_RES_NITEMS]; struct mtx mtx; if_t ifp; device_t dev; device_t miibus; struct callout stat_ch; struct task link_task; void *ih; u_int mdc_div_ratio_m; int link; int if_flags; enum awg_type type; struct syscon *syscon; struct awg_txring tx; struct awg_rxring rx; }; static struct resource_spec awg_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE }, { SYS_RES_MEMORY, 1, RF_ACTIVE | RF_OPTIONAL }, { -1, 0 } }; static void awg_txeof(struct awg_softc *sc); static uint32_t syscon_read_emac_clk_reg(device_t dev); static void syscon_write_emac_clk_reg(device_t dev, uint32_t val); static phandle_t awg_get_phy_node(device_t dev); static bool awg_has_internal_phy(device_t dev); static int awg_miibus_readreg(device_t dev, int phy, int reg) { struct awg_softc *sc; int retry, val; sc = device_get_softc(dev); val = 0; WR4(sc, EMAC_MII_CMD, (sc->mdc_div_ratio_m << MDC_DIV_RATIO_M_SHIFT) | (phy << PHY_ADDR_SHIFT) | (reg << PHY_REG_ADDR_SHIFT) | MII_BUSY); for (retry = MII_BUSY_RETRY; retry > 0; retry--) { if ((RD4(sc, EMAC_MII_CMD) & MII_BUSY) == 0) { val = RD4(sc, EMAC_MII_DATA); break; } DELAY(10); } if (retry == 0) device_printf(dev, "phy read timeout, phy=%d reg=%d\n", phy, reg); return (val); } static int awg_miibus_writereg(device_t dev, int phy, int reg, int val) { struct awg_softc *sc; int retry; sc = device_get_softc(dev); WR4(sc, EMAC_MII_DATA, val); WR4(sc, EMAC_MII_CMD, (sc->mdc_div_ratio_m << MDC_DIV_RATIO_M_SHIFT) | (phy << PHY_ADDR_SHIFT) | (reg << PHY_REG_ADDR_SHIFT) | MII_WR | MII_BUSY); for (retry = MII_BUSY_RETRY; retry > 0; retry--) { if ((RD4(sc, EMAC_MII_CMD) & MII_BUSY) == 0) break; DELAY(10); } if (retry == 0) device_printf(dev, "phy write timeout, phy=%d reg=%d\n", phy, reg); return (0); } static void awg_update_link_locked(struct awg_softc *sc) { struct mii_data *mii; uint32_t val; AWG_ASSERT_LOCKED(sc); if ((if_getdrvflags(sc->ifp) & IFF_DRV_RUNNING) == 0) return; mii = device_get_softc(sc->miibus); if ((mii->mii_media_status & (IFM_ACTIVE | IFM_AVALID)) == (IFM_ACTIVE | IFM_AVALID)) { switch (IFM_SUBTYPE(mii->mii_media_active)) { case IFM_1000_T: case IFM_1000_SX: case IFM_100_TX: case IFM_10_T: sc->link = 1; break; default: sc->link = 0; break; } } else sc->link = 0; if (sc->link == 0) return; val = RD4(sc, EMAC_BASIC_CTL_0); val &= ~(BASIC_CTL_SPEED | BASIC_CTL_DUPLEX); if (IFM_SUBTYPE(mii->mii_media_active) == IFM_1000_T || IFM_SUBTYPE(mii->mii_media_active) == IFM_1000_SX) val |= BASIC_CTL_SPEED_1000 << BASIC_CTL_SPEED_SHIFT; else if (IFM_SUBTYPE(mii->mii_media_active) == IFM_100_TX) val |= BASIC_CTL_SPEED_100 << BASIC_CTL_SPEED_SHIFT; else val |= BASIC_CTL_SPEED_10 << BASIC_CTL_SPEED_SHIFT; if ((IFM_OPTIONS(mii->mii_media_active) & IFM_FDX) != 0) val |= BASIC_CTL_DUPLEX; WR4(sc, EMAC_BASIC_CTL_0, val); val = RD4(sc, EMAC_RX_CTL_0); val &= ~RX_FLOW_CTL_EN; if ((IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_RXPAUSE) != 0) val |= RX_FLOW_CTL_EN; WR4(sc, EMAC_RX_CTL_0, val); val = RD4(sc, EMAC_TX_FLOW_CTL); val &= ~(PAUSE_TIME|TX_FLOW_CTL_EN); if ((IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_TXPAUSE) != 0) val |= TX_FLOW_CTL_EN; if ((IFM_OPTIONS(mii->mii_media_active) & IFM_FDX) != 0) val |= awg_pause_time << PAUSE_TIME_SHIFT; WR4(sc, EMAC_TX_FLOW_CTL, val); } static void awg_link_task(void *arg, int pending) { struct awg_softc *sc; sc = arg; AWG_LOCK(sc); awg_update_link_locked(sc); AWG_UNLOCK(sc); } static void awg_miibus_statchg(device_t dev) { struct awg_softc *sc; sc = device_get_softc(dev); taskqueue_enqueue(taskqueue_swi, &sc->link_task); } static void awg_media_status(if_t ifp, struct ifmediareq *ifmr) { struct awg_softc *sc; struct mii_data *mii; sc = if_getsoftc(ifp); mii = device_get_softc(sc->miibus); AWG_LOCK(sc); mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; AWG_UNLOCK(sc); } static int awg_media_change(if_t ifp) { struct awg_softc *sc; struct mii_data *mii; int error; sc = if_getsoftc(ifp); mii = device_get_softc(sc->miibus); AWG_LOCK(sc); error = mii_mediachg(mii); AWG_UNLOCK(sc); return (error); } static int awg_encap(struct awg_softc *sc, struct mbuf **mp) { bus_dmamap_t map; bus_dma_segment_t segs[TX_MAX_SEGS]; int error, nsegs, cur, first, last, i; u_int csum_flags; uint32_t flags, status; struct mbuf *m; cur = first = sc->tx.cur; map = sc->tx.buf_map[first].map; m = *mp; error = bus_dmamap_load_mbuf_sg(sc->tx.buf_tag, map, m, segs, &nsegs, BUS_DMA_NOWAIT); if (error == EFBIG) { m = m_collapse(m, M_NOWAIT, TX_MAX_SEGS); if (m == NULL) { device_printf(sc->dev, "awg_encap: m_collapse failed\n"); m_freem(*mp); *mp = NULL; return (ENOMEM); } *mp = m; error = bus_dmamap_load_mbuf_sg(sc->tx.buf_tag, map, m, segs, &nsegs, BUS_DMA_NOWAIT); if (error != 0) { m_freem(*mp); *mp = NULL; } } if (error != 0) { device_printf(sc->dev, "awg_encap: bus_dmamap_load_mbuf_sg failed\n"); return (error); } if (nsegs == 0) { m_freem(*mp); *mp = NULL; return (EIO); } if (sc->tx.queued + nsegs > TX_DESC_COUNT) { bus_dmamap_unload(sc->tx.buf_tag, map); return (ENOBUFS); } bus_dmamap_sync(sc->tx.buf_tag, map, BUS_DMASYNC_PREWRITE); flags = TX_FIR_DESC; status = 0; if ((m->m_pkthdr.csum_flags & CSUM_IP) != 0) { if ((m->m_pkthdr.csum_flags & (CSUM_TCP|CSUM_UDP)) != 0) csum_flags = TX_CHECKSUM_CTL_FULL; else csum_flags = TX_CHECKSUM_CTL_IP; flags |= (csum_flags << TX_CHECKSUM_CTL_SHIFT); } for (i = 0; i < nsegs; i++) { sc->tx.segs++; if (i == nsegs - 1) { flags |= TX_LAST_DESC; /* * Can only request TX completion * interrupt on last descriptor. */ if (sc->tx.segs >= awg_tx_interval) { sc->tx.segs = 0; flags |= TX_INT_CTL; } } sc->tx.desc_ring[cur].addr = htole32((uint32_t)segs[i].ds_addr); sc->tx.desc_ring[cur].size = htole32(flags | segs[i].ds_len); sc->tx.desc_ring[cur].status = htole32(status); flags &= ~TX_FIR_DESC; /* * Setting of the valid bit in the first descriptor is * deferred until the whole chain is fully set up. */ status = TX_DESC_CTL; ++sc->tx.queued; cur = TX_NEXT(cur); } sc->tx.cur = cur; /* Store mapping and mbuf in the last segment */ last = TX_SKIP(cur, TX_DESC_COUNT - 1); sc->tx.buf_map[first].map = sc->tx.buf_map[last].map; sc->tx.buf_map[last].map = map; sc->tx.buf_map[last].mbuf = m; /* * The whole mbuf chain has been DMA mapped, * fix the first descriptor. */ sc->tx.desc_ring[first].status = htole32(TX_DESC_CTL); return (0); } static void awg_clean_txbuf(struct awg_softc *sc, int index) { struct awg_bufmap *bmap; --sc->tx.queued; bmap = &sc->tx.buf_map[index]; if (bmap->mbuf != NULL) { bus_dmamap_sync(sc->tx.buf_tag, bmap->map, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->tx.buf_tag, bmap->map); m_freem(bmap->mbuf); bmap->mbuf = NULL; } } static void awg_setup_rxdesc(struct awg_softc *sc, int index, bus_addr_t paddr) { uint32_t status, size; status = RX_DESC_CTL; size = MCLBYTES - 1; sc->rx.desc_ring[index].addr = htole32((uint32_t)paddr); sc->rx.desc_ring[index].size = htole32(size); sc->rx.desc_ring[index].status = htole32(status); } static void awg_reuse_rxdesc(struct awg_softc *sc, int index) { sc->rx.desc_ring[index].status = htole32(RX_DESC_CTL); } static int awg_newbuf_rx(struct awg_softc *sc, int index) { struct mbuf *m; bus_dma_segment_t seg; bus_dmamap_t map; int nsegs; m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (m == NULL) return (ENOBUFS); m->m_pkthdr.len = m->m_len = m->m_ext.ext_size; m_adj(m, ETHER_ALIGN); if (bus_dmamap_load_mbuf_sg(sc->rx.buf_tag, sc->rx.buf_spare_map, m, &seg, &nsegs, BUS_DMA_NOWAIT) != 0) { m_freem(m); return (ENOBUFS); } if (sc->rx.buf_map[index].mbuf != NULL) { bus_dmamap_sync(sc->rx.buf_tag, sc->rx.buf_map[index].map, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->rx.buf_tag, sc->rx.buf_map[index].map); } map = sc->rx.buf_map[index].map; sc->rx.buf_map[index].map = sc->rx.buf_spare_map; sc->rx.buf_spare_map = map; bus_dmamap_sync(sc->rx.buf_tag, sc->rx.buf_map[index].map, BUS_DMASYNC_PREREAD); sc->rx.buf_map[index].mbuf = m; awg_setup_rxdesc(sc, index, seg.ds_addr); return (0); } static void awg_start_locked(struct awg_softc *sc) { struct mbuf *m; uint32_t val; if_t ifp; int cnt, err; AWG_ASSERT_LOCKED(sc); if (!sc->link) return; ifp = sc->ifp; if ((if_getdrvflags(ifp) & (IFF_DRV_RUNNING|IFF_DRV_OACTIVE)) != IFF_DRV_RUNNING) return; for (cnt = 0; ; cnt++) { m = if_dequeue(ifp); if (m == NULL) break; err = awg_encap(sc, &m); if (err != 0) { if (err == ENOBUFS) if_setdrvflagbits(ifp, IFF_DRV_OACTIVE, 0); if (m != NULL) if_sendq_prepend(ifp, m); break; } if_bpfmtap(ifp, m); } if (cnt != 0) { bus_dmamap_sync(sc->tx.desc_tag, sc->tx.desc_map, BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE); /* Start and run TX DMA */ val = RD4(sc, EMAC_TX_CTL_1); WR4(sc, EMAC_TX_CTL_1, val | TX_DMA_START); } } static void awg_start(if_t ifp) { struct awg_softc *sc; sc = if_getsoftc(ifp); AWG_LOCK(sc); awg_start_locked(sc); AWG_UNLOCK(sc); } static void awg_tick(void *softc) { struct awg_softc *sc; struct mii_data *mii; if_t ifp; int link; sc = softc; ifp = sc->ifp; mii = device_get_softc(sc->miibus); AWG_ASSERT_LOCKED(sc); if ((if_getdrvflags(ifp) & IFF_DRV_RUNNING) == 0) return; link = sc->link; mii_tick(mii); if (sc->link && !link) awg_start_locked(sc); callout_reset(&sc->stat_ch, hz, awg_tick, sc); } /* Bit Reversal - http://aggregate.org/MAGIC/#Bit%20Reversal */ static uint32_t bitrev32(uint32_t x) { x = (((x & 0xaaaaaaaa) >> 1) | ((x & 0x55555555) << 1)); x = (((x & 0xcccccccc) >> 2) | ((x & 0x33333333) << 2)); x = (((x & 0xf0f0f0f0) >> 4) | ((x & 0x0f0f0f0f) << 4)); x = (((x & 0xff00ff00) >> 8) | ((x & 0x00ff00ff) << 8)); return (x >> 16) | (x << 16); } static void awg_setup_rxfilter(struct awg_softc *sc) { uint32_t val, crc, hashreg, hashbit, hash[2], machi, maclo; int mc_count, mcnt, i; uint8_t *eaddr, *mta; if_t ifp; AWG_ASSERT_LOCKED(sc); ifp = sc->ifp; val = 0; hash[0] = hash[1] = 0; mc_count = if_multiaddr_count(ifp, -1); if (if_getflags(ifp) & IFF_PROMISC) val |= DIS_ADDR_FILTER; else if (if_getflags(ifp) & IFF_ALLMULTI) { val |= RX_ALL_MULTICAST; hash[0] = hash[1] = ~0; } else if (mc_count > 0) { val |= HASH_MULTICAST; mta = malloc(sizeof(unsigned char) * ETHER_ADDR_LEN * mc_count, M_DEVBUF, M_NOWAIT); if (mta == NULL) { if_printf(ifp, "failed to allocate temporary multicast list\n"); return; } if_multiaddr_array(ifp, mta, &mcnt, mc_count); for (i = 0; i < mcnt; i++) { crc = ether_crc32_le(mta + (i * ETHER_ADDR_LEN), ETHER_ADDR_LEN) & 0x7f; crc = bitrev32(~crc) >> 26; hashreg = (crc >> 5); hashbit = (crc & 0x1f); hash[hashreg] |= (1 << hashbit); } free(mta, M_DEVBUF); } /* Write our unicast address */ eaddr = IF_LLADDR(ifp); machi = (eaddr[5] << 8) | eaddr[4]; maclo = (eaddr[3] << 24) | (eaddr[2] << 16) | (eaddr[1] << 8) | (eaddr[0] << 0); WR4(sc, EMAC_ADDR_HIGH(0), machi); WR4(sc, EMAC_ADDR_LOW(0), maclo); /* Multicast hash filters */ WR4(sc, EMAC_RX_HASH_0, hash[1]); WR4(sc, EMAC_RX_HASH_1, hash[0]); /* RX frame filter config */ WR4(sc, EMAC_RX_FRM_FLT, val); } static void awg_enable_intr(struct awg_softc *sc) { /* Enable interrupts */ WR4(sc, EMAC_INT_EN, RX_INT_EN | TX_INT_EN | TX_BUF_UA_INT_EN); } static void awg_disable_intr(struct awg_softc *sc) { /* Disable interrupts */ WR4(sc, EMAC_INT_EN, 0); } static void awg_init_locked(struct awg_softc *sc) { struct mii_data *mii; uint32_t val; if_t ifp; mii = device_get_softc(sc->miibus); ifp = sc->ifp; AWG_ASSERT_LOCKED(sc); if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) return; awg_setup_rxfilter(sc); /* Configure DMA burst length and priorities */ val = awg_burst_len << BASIC_CTL_BURST_LEN_SHIFT; if (awg_rx_tx_pri) val |= BASIC_CTL_RX_TX_PRI; WR4(sc, EMAC_BASIC_CTL_1, val); /* Enable interrupts */ #ifdef DEVICE_POLLING if ((if_getcapenable(ifp) & IFCAP_POLLING) == 0) awg_enable_intr(sc); else awg_disable_intr(sc); #else awg_enable_intr(sc); #endif /* Enable transmit DMA */ val = RD4(sc, EMAC_TX_CTL_1); WR4(sc, EMAC_TX_CTL_1, val | TX_DMA_EN | TX_MD | TX_NEXT_FRAME); /* Enable receive DMA */ val = RD4(sc, EMAC_RX_CTL_1); WR4(sc, EMAC_RX_CTL_1, val | RX_DMA_EN | RX_MD); /* Enable transmitter */ val = RD4(sc, EMAC_TX_CTL_0); WR4(sc, EMAC_TX_CTL_0, val | TX_EN); /* Enable receiver */ val = RD4(sc, EMAC_RX_CTL_0); WR4(sc, EMAC_RX_CTL_0, val | RX_EN | CHECK_CRC); if_setdrvflagbits(ifp, IFF_DRV_RUNNING, IFF_DRV_OACTIVE); mii_mediachg(mii); callout_reset(&sc->stat_ch, hz, awg_tick, sc); } static void awg_init(void *softc) { struct awg_softc *sc; sc = softc; AWG_LOCK(sc); awg_init_locked(sc); AWG_UNLOCK(sc); } static void awg_stop(struct awg_softc *sc) { if_t ifp; uint32_t val; int i; AWG_ASSERT_LOCKED(sc); ifp = sc->ifp; callout_stop(&sc->stat_ch); /* Stop transmit DMA and flush data in the TX FIFO */ val = RD4(sc, EMAC_TX_CTL_1); val &= ~TX_DMA_EN; val |= FLUSH_TX_FIFO; WR4(sc, EMAC_TX_CTL_1, val); /* Disable transmitter */ val = RD4(sc, EMAC_TX_CTL_0); WR4(sc, EMAC_TX_CTL_0, val & ~TX_EN); /* Disable receiver */ val = RD4(sc, EMAC_RX_CTL_0); WR4(sc, EMAC_RX_CTL_0, val & ~RX_EN); /* Disable interrupts */ awg_disable_intr(sc); /* Disable transmit DMA */ val = RD4(sc, EMAC_TX_CTL_1); WR4(sc, EMAC_TX_CTL_1, val & ~TX_DMA_EN); /* Disable receive DMA */ val = RD4(sc, EMAC_RX_CTL_1); WR4(sc, EMAC_RX_CTL_1, val & ~RX_DMA_EN); sc->link = 0; /* Finish handling transmitted buffers */ awg_txeof(sc); /* Release any untransmitted buffers. */ for (i = sc->tx.next; sc->tx.queued > 0; i = TX_NEXT(i)) { val = le32toh(sc->tx.desc_ring[i].status); if ((val & TX_DESC_CTL) != 0) break; awg_clean_txbuf(sc, i); } sc->tx.next = i; for (; sc->tx.queued > 0; i = TX_NEXT(i)) { sc->tx.desc_ring[i].status = 0; awg_clean_txbuf(sc, i); } sc->tx.cur = sc->tx.next; bus_dmamap_sync(sc->tx.desc_tag, sc->tx.desc_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); /* Setup RX buffers for reuse */ bus_dmamap_sync(sc->rx.desc_tag, sc->rx.desc_map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); for (i = sc->rx.cur; ; i = RX_NEXT(i)) { val = le32toh(sc->rx.desc_ring[i].status); if ((val & RX_DESC_CTL) != 0) break; awg_reuse_rxdesc(sc, i); } sc->rx.cur = i; bus_dmamap_sync(sc->rx.desc_tag, sc->rx.desc_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); if_setdrvflagbits(ifp, 0, IFF_DRV_RUNNING | IFF_DRV_OACTIVE); } static int awg_rxintr(struct awg_softc *sc) { if_t ifp; struct mbuf *m, *mh, *mt; int error, index, len, cnt, npkt; uint32_t status; ifp = sc->ifp; mh = mt = NULL; cnt = 0; npkt = 0; bus_dmamap_sync(sc->rx.desc_tag, sc->rx.desc_map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); for (index = sc->rx.cur; ; index = RX_NEXT(index)) { status = le32toh(sc->rx.desc_ring[index].status); if ((status & RX_DESC_CTL) != 0) break; len = (status & RX_FRM_LEN) >> RX_FRM_LEN_SHIFT; if (len == 0) { if ((status & (RX_NO_ENOUGH_BUF_ERR | RX_OVERFLOW_ERR)) != 0) if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); awg_reuse_rxdesc(sc, index); continue; } m = sc->rx.buf_map[index].mbuf; error = awg_newbuf_rx(sc, index); if (error != 0) { if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1); awg_reuse_rxdesc(sc, index); continue; } m->m_pkthdr.rcvif = ifp; m->m_pkthdr.len = len; m->m_len = len; if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1); if ((if_getcapenable(ifp) & IFCAP_RXCSUM) != 0 && (status & RX_FRM_TYPE) != 0) { m->m_pkthdr.csum_flags = CSUM_IP_CHECKED; if ((status & RX_HEADER_ERR) == 0) m->m_pkthdr.csum_flags |= CSUM_IP_VALID; if ((status & RX_PAYLOAD_ERR) == 0) { m->m_pkthdr.csum_flags |= CSUM_DATA_VALID | CSUM_PSEUDO_HDR; m->m_pkthdr.csum_data = 0xffff; } } m->m_nextpkt = NULL; if (mh == NULL) mh = m; else mt->m_nextpkt = m; mt = m; ++cnt; ++npkt; if (cnt == awg_rx_batch) { AWG_UNLOCK(sc); if_input(ifp, mh); AWG_LOCK(sc); mh = mt = NULL; cnt = 0; } } if (index != sc->rx.cur) { bus_dmamap_sync(sc->rx.desc_tag, sc->rx.desc_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); } if (mh != NULL) { AWG_UNLOCK(sc); if_input(ifp, mh); AWG_LOCK(sc); } sc->rx.cur = index; return (npkt); } static void awg_txeof(struct awg_softc *sc) { struct emac_desc *desc; uint32_t status, size; if_t ifp; int i, prog; AWG_ASSERT_LOCKED(sc); bus_dmamap_sync(sc->tx.desc_tag, sc->tx.desc_map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); ifp = sc->ifp; prog = 0; for (i = sc->tx.next; sc->tx.queued > 0; i = TX_NEXT(i)) { desc = &sc->tx.desc_ring[i]; status = le32toh(desc->status); if ((status & TX_DESC_CTL) != 0) break; size = le32toh(desc->size); if (size & TX_LAST_DESC) { if ((status & (TX_HEADER_ERR | TX_PAYLOAD_ERR)) != 0) if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); else if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); } prog++; awg_clean_txbuf(sc, i); } if (prog > 0) { sc->tx.next = i; if_setdrvflagbits(ifp, 0, IFF_DRV_OACTIVE); } } static void awg_intr(void *arg) { struct awg_softc *sc; uint32_t val; sc = arg; AWG_LOCK(sc); val = RD4(sc, EMAC_INT_STA); WR4(sc, EMAC_INT_STA, val); if (val & RX_INT) awg_rxintr(sc); if (val & TX_INT) awg_txeof(sc); if (val & (TX_INT | TX_BUF_UA_INT)) { if (!if_sendq_empty(sc->ifp)) awg_start_locked(sc); } AWG_UNLOCK(sc); } #ifdef DEVICE_POLLING static int awg_poll(if_t ifp, enum poll_cmd cmd, int count) { struct awg_softc *sc; uint32_t val; int rx_npkts; sc = if_getsoftc(ifp); rx_npkts = 0; AWG_LOCK(sc); if ((if_getdrvflags(ifp) & IFF_DRV_RUNNING) == 0) { AWG_UNLOCK(sc); return (0); } rx_npkts = awg_rxintr(sc); awg_txeof(sc); if (!if_sendq_empty(ifp)) awg_start_locked(sc); if (cmd == POLL_AND_CHECK_STATUS) { val = RD4(sc, EMAC_INT_STA); if (val != 0) WR4(sc, EMAC_INT_STA, val); } AWG_UNLOCK(sc); return (rx_npkts); } #endif static int awg_ioctl(if_t ifp, u_long cmd, caddr_t data) { struct awg_softc *sc; struct mii_data *mii; struct ifreq *ifr; int flags, mask, error; sc = if_getsoftc(ifp); mii = device_get_softc(sc->miibus); ifr = (struct ifreq *)data; error = 0; switch (cmd) { case SIOCSIFFLAGS: AWG_LOCK(sc); if (if_getflags(ifp) & IFF_UP) { if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) { flags = if_getflags(ifp) ^ sc->if_flags; if ((flags & (IFF_PROMISC|IFF_ALLMULTI)) != 0) awg_setup_rxfilter(sc); } else awg_init_locked(sc); } else { if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) awg_stop(sc); } sc->if_flags = if_getflags(ifp); AWG_UNLOCK(sc); break; case SIOCADDMULTI: case SIOCDELMULTI: if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) { AWG_LOCK(sc); awg_setup_rxfilter(sc); AWG_UNLOCK(sc); } break; case SIOCSIFMEDIA: case SIOCGIFMEDIA: error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, cmd); break; case SIOCSIFCAP: mask = ifr->ifr_reqcap ^ if_getcapenable(ifp); #ifdef DEVICE_POLLING if (mask & IFCAP_POLLING) { if ((ifr->ifr_reqcap & IFCAP_POLLING) != 0) { error = ether_poll_register(awg_poll, ifp); if (error != 0) break; AWG_LOCK(sc); awg_disable_intr(sc); if_setcapenablebit(ifp, IFCAP_POLLING, 0); AWG_UNLOCK(sc); } else { error = ether_poll_deregister(ifp); AWG_LOCK(sc); awg_enable_intr(sc); if_setcapenablebit(ifp, 0, IFCAP_POLLING); AWG_UNLOCK(sc); } } #endif if (mask & IFCAP_VLAN_MTU) if_togglecapenable(ifp, IFCAP_VLAN_MTU); if (mask & IFCAP_RXCSUM) if_togglecapenable(ifp, IFCAP_RXCSUM); if (mask & IFCAP_TXCSUM) if_togglecapenable(ifp, IFCAP_TXCSUM); if ((if_getcapenable(ifp) & IFCAP_TXCSUM) != 0) if_sethwassistbits(ifp, CSUM_IP | CSUM_UDP | CSUM_TCP, 0); else if_sethwassistbits(ifp, 0, CSUM_IP | CSUM_UDP | CSUM_TCP); break; default: error = ether_ioctl(ifp, cmd, data); break; } return (error); } static uint32_t syscon_read_emac_clk_reg(device_t dev) { struct awg_softc *sc; sc = device_get_softc(dev); if (sc->syscon != NULL) return (SYSCON_READ_4(sc->syscon, EMAC_CLK_REG)); else if (sc->res[_RES_SYSCON] != NULL) return (bus_read_4(sc->res[_RES_SYSCON], 0)); return (0); } static void syscon_write_emac_clk_reg(device_t dev, uint32_t val) { struct awg_softc *sc; sc = device_get_softc(dev); if (sc->syscon != NULL) SYSCON_WRITE_4(sc->syscon, EMAC_CLK_REG, val); else if (sc->res[_RES_SYSCON] != NULL) bus_write_4(sc->res[_RES_SYSCON], 0, val); } static phandle_t awg_get_phy_node(device_t dev) { phandle_t node; pcell_t phy_handle; node = ofw_bus_get_node(dev); if (OF_getencprop(node, "phy-handle", (void *)&phy_handle, sizeof(phy_handle)) <= 0) return (0); return (OF_node_from_xref(phy_handle)); } static bool awg_has_internal_phy(device_t dev) { phandle_t node, phy_node; node = ofw_bus_get_node(dev); /* Legacy binding */ if (OF_hasprop(node, "allwinner,use-internal-phy")) return (true); phy_node = awg_get_phy_node(dev); return (phy_node != 0 && ofw_bus_node_is_compatible(OF_parent(phy_node), "allwinner,sun8i-h3-mdio-internal") != 0); } static int awg_setup_phy(device_t dev) { struct awg_softc *sc; clk_t clk_tx, clk_tx_parent; const char *tx_parent_name; char *phy_type; phandle_t node; uint32_t reg, tx_delay, rx_delay; int error; bool use_syscon; sc = device_get_softc(dev); node = ofw_bus_get_node(dev); use_syscon = false; - if (OF_getprop_alloc(node, "phy-mode", 1, (void **)&phy_type) == 0) + if (OF_getprop_alloc(node, "phy-mode", (void **)&phy_type) == 0) return (0); if (sc->syscon != NULL || sc->res[_RES_SYSCON] != NULL) use_syscon = true; if (bootverbose) device_printf(dev, "PHY type: %s, conf mode: %s\n", phy_type, use_syscon ? "reg" : "clk"); if (use_syscon) { /* * Abstract away writing to syscon for devices like the pine64. * For the pine64, we get dtb from U-Boot and it still uses the * legacy setup of specifying syscon register in emac node * rather than as its own node and using an xref in emac. * These abstractions can go away once U-Boot dts is up-to-date. */ reg = syscon_read_emac_clk_reg(dev); reg &= ~(EMAC_CLK_PIT | EMAC_CLK_SRC | EMAC_CLK_RMII_EN); if (strncmp(phy_type, "rgmii", 5) == 0) reg |= EMAC_CLK_PIT_RGMII | EMAC_CLK_SRC_RGMII; else if (strcmp(phy_type, "rmii") == 0) reg |= EMAC_CLK_RMII_EN; else reg |= EMAC_CLK_PIT_MII | EMAC_CLK_SRC_MII; if (OF_getencprop(node, "tx-delay", &tx_delay, sizeof(tx_delay)) > 0) { reg &= ~EMAC_CLK_ETXDC; reg |= (tx_delay << EMAC_CLK_ETXDC_SHIFT); } if (OF_getencprop(node, "rx-delay", &rx_delay, sizeof(rx_delay)) > 0) { reg &= ~EMAC_CLK_ERXDC; reg |= (rx_delay << EMAC_CLK_ERXDC_SHIFT); } if (sc->type == EMAC_H3) { if (awg_has_internal_phy(dev)) { reg |= EMAC_CLK_EPHY_SELECT; reg &= ~EMAC_CLK_EPHY_SHUTDOWN; if (OF_hasprop(node, "allwinner,leds-active-low")) reg |= EMAC_CLK_EPHY_LED_POL; else reg &= ~EMAC_CLK_EPHY_LED_POL; /* Set internal PHY addr to 1 */ reg &= ~EMAC_CLK_EPHY_ADDR; reg |= (1 << EMAC_CLK_EPHY_ADDR_SHIFT); } else { reg &= ~EMAC_CLK_EPHY_SELECT; } } if (bootverbose) device_printf(dev, "EMAC clock: 0x%08x\n", reg); syscon_write_emac_clk_reg(dev, reg); } else { if (strncmp(phy_type, "rgmii", 5) == 0) tx_parent_name = "emac_int_tx"; else tx_parent_name = "mii_phy_tx"; /* Get the TX clock */ error = clk_get_by_ofw_name(dev, 0, "tx", &clk_tx); if (error != 0) { device_printf(dev, "cannot get tx clock\n"); goto fail; } /* Find the desired parent clock based on phy-mode property */ error = clk_get_by_name(dev, tx_parent_name, &clk_tx_parent); if (error != 0) { device_printf(dev, "cannot get clock '%s'\n", tx_parent_name); goto fail; } /* Set TX clock parent */ error = clk_set_parent_by_clk(clk_tx, clk_tx_parent); if (error != 0) { device_printf(dev, "cannot set tx clock parent\n"); goto fail; } /* Enable TX clock */ error = clk_enable(clk_tx); if (error != 0) { device_printf(dev, "cannot enable tx clock\n"); goto fail; } } error = 0; fail: OF_prop_free(phy_type); return (error); } static int awg_setup_extres(device_t dev) { struct awg_softc *sc; phandle_t node, phy_node; hwreset_t rst_ahb, rst_ephy; clk_t clk_ahb, clk_ephy; regulator_t reg; uint64_t freq; int error, div; sc = device_get_softc(dev); rst_ahb = rst_ephy = NULL; clk_ahb = clk_ephy = NULL; reg = NULL; node = ofw_bus_get_node(dev); phy_node = awg_get_phy_node(dev); if (phy_node == 0 && OF_hasprop(node, "phy-handle")) { error = ENXIO; device_printf(dev, "cannot get phy handle\n"); goto fail; } /* Get AHB clock and reset resources */ error = hwreset_get_by_ofw_name(dev, 0, "stmmaceth", &rst_ahb); if (error != 0) error = hwreset_get_by_ofw_name(dev, 0, "ahb", &rst_ahb); if (error != 0) { device_printf(dev, "cannot get ahb reset\n"); goto fail; } if (hwreset_get_by_ofw_name(dev, 0, "ephy", &rst_ephy) != 0) if (phy_node == 0 || hwreset_get_by_ofw_idx(dev, phy_node, 0, &rst_ephy) != 0) rst_ephy = NULL; error = clk_get_by_ofw_name(dev, 0, "stmmaceth", &clk_ahb); if (error != 0) error = clk_get_by_ofw_name(dev, 0, "ahb", &clk_ahb); if (error != 0) { device_printf(dev, "cannot get ahb clock\n"); goto fail; } if (clk_get_by_ofw_name(dev, 0, "ephy", &clk_ephy) != 0) if (phy_node == 0 || clk_get_by_ofw_index(dev, phy_node, 0, &clk_ephy) != 0) clk_ephy = NULL; if (OF_hasprop(node, "syscon") && syscon_get_by_ofw_property(dev, node, "syscon", &sc->syscon) != 0) { device_printf(dev, "cannot get syscon driver handle\n"); goto fail; } /* Configure PHY for MII or RGMII mode */ if (awg_setup_phy(dev) != 0) goto fail; /* Enable clocks */ error = clk_enable(clk_ahb); if (error != 0) { device_printf(dev, "cannot enable ahb clock\n"); goto fail; } if (clk_ephy != NULL) { error = clk_enable(clk_ephy); if (error != 0) { device_printf(dev, "cannot enable ephy clock\n"); goto fail; } } /* De-assert reset */ error = hwreset_deassert(rst_ahb); if (error != 0) { device_printf(dev, "cannot de-assert ahb reset\n"); goto fail; } if (rst_ephy != NULL) { error = hwreset_deassert(rst_ephy); if (error != 0) { device_printf(dev, "cannot de-assert ephy reset\n"); goto fail; } } /* Enable PHY regulator if applicable */ if (regulator_get_by_ofw_property(dev, 0, "phy-supply", ®) == 0) { error = regulator_enable(reg); if (error != 0) { device_printf(dev, "cannot enable PHY regulator\n"); goto fail; } } /* Determine MDC clock divide ratio based on AHB clock */ error = clk_get_freq(clk_ahb, &freq); if (error != 0) { device_printf(dev, "cannot get AHB clock frequency\n"); goto fail; } div = freq / MDIO_FREQ; if (div <= 16) sc->mdc_div_ratio_m = MDC_DIV_RATIO_M_16; else if (div <= 32) sc->mdc_div_ratio_m = MDC_DIV_RATIO_M_32; else if (div <= 64) sc->mdc_div_ratio_m = MDC_DIV_RATIO_M_64; else if (div <= 128) sc->mdc_div_ratio_m = MDC_DIV_RATIO_M_128; else { device_printf(dev, "cannot determine MDC clock divide ratio\n"); error = ENXIO; goto fail; } if (bootverbose) device_printf(dev, "AHB frequency %ju Hz, MDC div: 0x%x\n", (uintmax_t)freq, sc->mdc_div_ratio_m); return (0); fail: if (reg != NULL) regulator_release(reg); if (clk_ephy != NULL) clk_release(clk_ephy); if (clk_ahb != NULL) clk_release(clk_ahb); if (rst_ephy != NULL) hwreset_release(rst_ephy); if (rst_ahb != NULL) hwreset_release(rst_ahb); return (error); } static void awg_get_eaddr(device_t dev, uint8_t *eaddr) { struct awg_softc *sc; uint32_t maclo, machi, rnd; u_char rootkey[16]; sc = device_get_softc(dev); machi = RD4(sc, EMAC_ADDR_HIGH(0)) & 0xffff; maclo = RD4(sc, EMAC_ADDR_LOW(0)); if (maclo == 0xffffffff && machi == 0xffff) { /* MAC address in hardware is invalid, create one */ if (aw_sid_get_rootkey(rootkey) == 0 && (rootkey[3] | rootkey[12] | rootkey[13] | rootkey[14] | rootkey[15]) != 0) { /* MAC address is derived from the root key in SID */ maclo = (rootkey[13] << 24) | (rootkey[12] << 16) | (rootkey[3] << 8) | 0x02; machi = (rootkey[15] << 8) | rootkey[14]; } else { /* Create one */ rnd = arc4random(); maclo = 0x00f2 | (rnd & 0xffff0000); machi = rnd & 0xffff; } } eaddr[0] = maclo & 0xff; eaddr[1] = (maclo >> 8) & 0xff; eaddr[2] = (maclo >> 16) & 0xff; eaddr[3] = (maclo >> 24) & 0xff; eaddr[4] = machi & 0xff; eaddr[5] = (machi >> 8) & 0xff; } #ifdef AWG_DEBUG static void awg_dump_regs(device_t dev) { static const struct { const char *name; u_int reg; } regs[] = { { "BASIC_CTL_0", EMAC_BASIC_CTL_0 }, { "BASIC_CTL_1", EMAC_BASIC_CTL_1 }, { "INT_STA", EMAC_INT_STA }, { "INT_EN", EMAC_INT_EN }, { "TX_CTL_0", EMAC_TX_CTL_0 }, { "TX_CTL_1", EMAC_TX_CTL_1 }, { "TX_FLOW_CTL", EMAC_TX_FLOW_CTL }, { "TX_DMA_LIST", EMAC_TX_DMA_LIST }, { "RX_CTL_0", EMAC_RX_CTL_0 }, { "RX_CTL_1", EMAC_RX_CTL_1 }, { "RX_DMA_LIST", EMAC_RX_DMA_LIST }, { "RX_FRM_FLT", EMAC_RX_FRM_FLT }, { "RX_HASH_0", EMAC_RX_HASH_0 }, { "RX_HASH_1", EMAC_RX_HASH_1 }, { "MII_CMD", EMAC_MII_CMD }, { "ADDR_HIGH0", EMAC_ADDR_HIGH(0) }, { "ADDR_LOW0", EMAC_ADDR_LOW(0) }, { "TX_DMA_STA", EMAC_TX_DMA_STA }, { "TX_DMA_CUR_DESC", EMAC_TX_DMA_CUR_DESC }, { "TX_DMA_CUR_BUF", EMAC_TX_DMA_CUR_BUF }, { "RX_DMA_STA", EMAC_RX_DMA_STA }, { "RX_DMA_CUR_DESC", EMAC_RX_DMA_CUR_DESC }, { "RX_DMA_CUR_BUF", EMAC_RX_DMA_CUR_BUF }, { "RGMII_STA", EMAC_RGMII_STA }, }; struct awg_softc *sc; unsigned int n; sc = device_get_softc(dev); for (n = 0; n < nitems(regs); n++) device_printf(dev, " %-20s %08x\n", regs[n].name, RD4(sc, regs[n].reg)); } #endif #define GPIO_ACTIVE_LOW 1 static int awg_phy_reset(device_t dev) { pcell_t gpio_prop[4], delay_prop[3]; phandle_t node, gpio_node; device_t gpio; uint32_t pin, flags; uint32_t pin_value; node = ofw_bus_get_node(dev); if (OF_getencprop(node, "allwinner,reset-gpio", gpio_prop, sizeof(gpio_prop)) <= 0) return (0); if (OF_getencprop(node, "allwinner,reset-delays-us", delay_prop, sizeof(delay_prop)) <= 0) return (ENXIO); gpio_node = OF_node_from_xref(gpio_prop[0]); if ((gpio = OF_device_from_xref(gpio_prop[0])) == NULL) return (ENXIO); if (GPIO_MAP_GPIOS(gpio, node, gpio_node, nitems(gpio_prop) - 1, gpio_prop + 1, &pin, &flags) != 0) return (ENXIO); pin_value = GPIO_PIN_LOW; if (OF_hasprop(node, "allwinner,reset-active-low")) pin_value = GPIO_PIN_HIGH; if (flags & GPIO_ACTIVE_LOW) pin_value = !pin_value; GPIO_PIN_SETFLAGS(gpio, pin, GPIO_PIN_OUTPUT); GPIO_PIN_SET(gpio, pin, pin_value); DELAY(delay_prop[0]); GPIO_PIN_SET(gpio, pin, !pin_value); DELAY(delay_prop[1]); GPIO_PIN_SET(gpio, pin, pin_value); DELAY(delay_prop[2]); return (0); } static int awg_reset(device_t dev) { struct awg_softc *sc; int retry; sc = device_get_softc(dev); /* Reset PHY if necessary */ if (awg_phy_reset(dev) != 0) { device_printf(dev, "failed to reset PHY\n"); return (ENXIO); } /* Soft reset all registers and logic */ WR4(sc, EMAC_BASIC_CTL_1, BASIC_CTL_SOFT_RST); /* Wait for soft reset bit to self-clear */ for (retry = SOFT_RST_RETRY; retry > 0; retry--) { if ((RD4(sc, EMAC_BASIC_CTL_1) & BASIC_CTL_SOFT_RST) == 0) break; DELAY(10); } if (retry == 0) { device_printf(dev, "soft reset timed out\n"); #ifdef AWG_DEBUG awg_dump_regs(dev); #endif return (ETIMEDOUT); } return (0); } static void awg_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nseg, int error) { if (error != 0) return; *(bus_addr_t *)arg = segs[0].ds_addr; } static int awg_setup_dma(device_t dev) { struct awg_softc *sc; int error, i; sc = device_get_softc(dev); /* Setup TX ring */ error = bus_dma_tag_create( bus_get_dma_tag(dev), /* Parent tag */ DESC_ALIGN, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ TX_DESC_SIZE, 1, /* maxsize, nsegs */ TX_DESC_SIZE, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->tx.desc_tag); if (error != 0) { device_printf(dev, "cannot create TX descriptor ring tag\n"); return (error); } error = bus_dmamem_alloc(sc->tx.desc_tag, (void **)&sc->tx.desc_ring, BUS_DMA_COHERENT | BUS_DMA_WAITOK | BUS_DMA_ZERO, &sc->tx.desc_map); if (error != 0) { device_printf(dev, "cannot allocate TX descriptor ring\n"); return (error); } error = bus_dmamap_load(sc->tx.desc_tag, sc->tx.desc_map, sc->tx.desc_ring, TX_DESC_SIZE, awg_dmamap_cb, &sc->tx.desc_ring_paddr, 0); if (error != 0) { device_printf(dev, "cannot load TX descriptor ring\n"); return (error); } for (i = 0; i < TX_DESC_COUNT; i++) sc->tx.desc_ring[i].next = htole32(sc->tx.desc_ring_paddr + DESC_OFF(TX_NEXT(i))); error = bus_dma_tag_create( bus_get_dma_tag(dev), /* Parent tag */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ MCLBYTES, TX_MAX_SEGS, /* maxsize, nsegs */ MCLBYTES, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->tx.buf_tag); if (error != 0) { device_printf(dev, "cannot create TX buffer tag\n"); return (error); } sc->tx.queued = 0; for (i = 0; i < TX_DESC_COUNT; i++) { error = bus_dmamap_create(sc->tx.buf_tag, 0, &sc->tx.buf_map[i].map); if (error != 0) { device_printf(dev, "cannot create TX buffer map\n"); return (error); } } /* Setup RX ring */ error = bus_dma_tag_create( bus_get_dma_tag(dev), /* Parent tag */ DESC_ALIGN, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ RX_DESC_SIZE, 1, /* maxsize, nsegs */ RX_DESC_SIZE, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->rx.desc_tag); if (error != 0) { device_printf(dev, "cannot create RX descriptor ring tag\n"); return (error); } error = bus_dmamem_alloc(sc->rx.desc_tag, (void **)&sc->rx.desc_ring, BUS_DMA_COHERENT | BUS_DMA_WAITOK | BUS_DMA_ZERO, &sc->rx.desc_map); if (error != 0) { device_printf(dev, "cannot allocate RX descriptor ring\n"); return (error); } error = bus_dmamap_load(sc->rx.desc_tag, sc->rx.desc_map, sc->rx.desc_ring, RX_DESC_SIZE, awg_dmamap_cb, &sc->rx.desc_ring_paddr, 0); if (error != 0) { device_printf(dev, "cannot load RX descriptor ring\n"); return (error); } error = bus_dma_tag_create( bus_get_dma_tag(dev), /* Parent tag */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ MCLBYTES, 1, /* maxsize, nsegs */ MCLBYTES, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->rx.buf_tag); if (error != 0) { device_printf(dev, "cannot create RX buffer tag\n"); return (error); } error = bus_dmamap_create(sc->rx.buf_tag, 0, &sc->rx.buf_spare_map); if (error != 0) { device_printf(dev, "cannot create RX buffer spare map\n"); return (error); } for (i = 0; i < RX_DESC_COUNT; i++) { sc->rx.desc_ring[i].next = htole32(sc->rx.desc_ring_paddr + DESC_OFF(RX_NEXT(i))); error = bus_dmamap_create(sc->rx.buf_tag, 0, &sc->rx.buf_map[i].map); if (error != 0) { device_printf(dev, "cannot create RX buffer map\n"); return (error); } sc->rx.buf_map[i].mbuf = NULL; error = awg_newbuf_rx(sc, i); if (error != 0) { device_printf(dev, "cannot create RX buffer\n"); return (error); } } bus_dmamap_sync(sc->rx.desc_tag, sc->rx.desc_map, BUS_DMASYNC_PREWRITE); /* Write transmit and receive descriptor base address registers */ WR4(sc, EMAC_TX_DMA_LIST, sc->tx.desc_ring_paddr); WR4(sc, EMAC_RX_DMA_LIST, sc->rx.desc_ring_paddr); return (0); } static int awg_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, "Allwinner Gigabit Ethernet"); return (BUS_PROBE_DEFAULT); } static int awg_attach(device_t dev) { uint8_t eaddr[ETHER_ADDR_LEN]; struct awg_softc *sc; int error; sc = device_get_softc(dev); sc->dev = dev; sc->type = ofw_bus_search_compatible(dev, compat_data)->ocd_data; if (bus_alloc_resources(dev, awg_spec, sc->res) != 0) { device_printf(dev, "cannot allocate resources for device\n"); return (ENXIO); } mtx_init(&sc->mtx, device_get_nameunit(dev), MTX_NETWORK_LOCK, MTX_DEF); callout_init_mtx(&sc->stat_ch, &sc->mtx, 0); TASK_INIT(&sc->link_task, 0, awg_link_task, sc); /* Setup clocks and regulators */ error = awg_setup_extres(dev); if (error != 0) return (error); /* Read MAC address before resetting the chip */ awg_get_eaddr(dev, eaddr); /* Soft reset EMAC core */ error = awg_reset(dev); if (error != 0) return (error); /* Setup DMA descriptors */ error = awg_setup_dma(dev); if (error != 0) return (error); /* Install interrupt handler */ error = bus_setup_intr(dev, sc->res[_RES_IRQ], INTR_TYPE_NET | INTR_MPSAFE, NULL, awg_intr, sc, &sc->ih); if (error != 0) { device_printf(dev, "cannot setup interrupt handler\n"); return (error); } /* Setup ethernet interface */ sc->ifp = if_alloc(IFT_ETHER); if_setsoftc(sc->ifp, sc); if_initname(sc->ifp, device_get_name(dev), device_get_unit(dev)); if_setflags(sc->ifp, IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST); if_setstartfn(sc->ifp, awg_start); if_setioctlfn(sc->ifp, awg_ioctl); if_setinitfn(sc->ifp, awg_init); if_setsendqlen(sc->ifp, TX_DESC_COUNT - 1); if_setsendqready(sc->ifp); if_sethwassist(sc->ifp, CSUM_IP | CSUM_UDP | CSUM_TCP); if_setcapabilities(sc->ifp, IFCAP_VLAN_MTU | IFCAP_HWCSUM); if_setcapenable(sc->ifp, if_getcapabilities(sc->ifp)); #ifdef DEVICE_POLLING if_setcapabilitiesbit(sc->ifp, IFCAP_POLLING, 0); #endif /* Attach MII driver */ error = mii_attach(dev, &sc->miibus, sc->ifp, awg_media_change, awg_media_status, BMSR_DEFCAPMASK, MII_PHY_ANY, MII_OFFSET_ANY, MIIF_DOPAUSE); if (error != 0) { device_printf(dev, "cannot attach PHY\n"); return (error); } /* Attach ethernet interface */ ether_ifattach(sc->ifp, eaddr); return (0); } static device_method_t awg_methods[] = { /* Device interface */ DEVMETHOD(device_probe, awg_probe), DEVMETHOD(device_attach, awg_attach), /* MII interface */ DEVMETHOD(miibus_readreg, awg_miibus_readreg), DEVMETHOD(miibus_writereg, awg_miibus_writereg), DEVMETHOD(miibus_statchg, awg_miibus_statchg), DEVMETHOD_END }; static driver_t awg_driver = { "awg", awg_methods, sizeof(struct awg_softc), }; static devclass_t awg_devclass; DRIVER_MODULE(awg, simplebus, awg_driver, awg_devclass, 0, 0); DRIVER_MODULE(miibus, awg, miibus_driver, miibus_devclass, 0, 0); MODULE_DEPEND(awg, ether, 1, 1, 1); MODULE_DEPEND(awg, miibus, 1, 1, 1); Index: head/sys/arm/amlogic/aml8726/aml8726_ccm.c =================================================================== --- head/sys/arm/amlogic/aml8726/aml8726_ccm.c (revision 332309) +++ head/sys/arm/amlogic/aml8726/aml8726_ccm.c (revision 332310) @@ -1,229 +1,229 @@ /*- * Copyright 2015 John Wehle * 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. */ /* * Amlogic aml8726 clock control module driver. * */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct aml8726_ccm_softc { device_t dev; struct aml8726_ccm_function *soc; struct resource *res[1]; struct mtx mtx; }; static struct resource_spec aml8726_ccm_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { -1, 0 } }; #define AML_CCM_LOCK(sc) mtx_lock(&(sc)->mtx) #define AML_CCM_UNLOCK(sc) mtx_unlock(&(sc)->mtx) #define AML_CCM_LOCK_INIT(sc) \ mtx_init(&(sc)->mtx, device_get_nameunit((sc)->dev), \ "ccm", MTX_DEF) #define AML_CCM_LOCK_DESTROY(sc) mtx_destroy(&(sc)->mtx); #define CSR_WRITE_4(sc, reg, val) bus_write_4((sc)->res[0], reg, (val)) #define CSR_READ_4(sc, reg) bus_read_4((sc)->res[0], reg) static int aml8726_ccm_configure_gates(struct aml8726_ccm_softc *sc) { struct aml8726_ccm_function *f; struct aml8726_ccm_gate *g; char *function_name; char *functions; phandle_t node; ssize_t len; uint32_t value; node = ofw_bus_get_node(sc->dev); - len = OF_getprop_alloc(node, "functions", sizeof(char), + len = OF_getprop_alloc(node, "functions", (void **)&functions); if (len < 0) { device_printf(sc->dev, "missing functions attribute in FDT\n"); return (ENXIO); } function_name = functions; while (len) { for (f = sc->soc; f->name != NULL; f++) if (strncmp(f->name, function_name, len) == 0) break; if (f->name == NULL) { /* display message prior to queuing up next string */ device_printf(sc->dev, "unknown function attribute %.*s in FDT\n", len, function_name); } /* queue up next string */ while (*function_name && len) { function_name++; len--; } if (len) { function_name++; len--; } if (f->name == NULL) continue; AML_CCM_LOCK(sc); /* * Enable the clock gates necessary for the function. * * In some cases a clock may be shared across functions * (meaning don't disable a clock without ensuring that * it's not required by someone else). */ for (g = f->gates; g->bits != 0x00000000; g++) { value = CSR_READ_4(sc, g->addr); value |= g->bits; CSR_WRITE_4(sc, g->addr, value); } AML_CCM_UNLOCK(sc); } OF_prop_free(functions); return (0); } static int aml8726_ccm_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "amlogic,aml8726-ccm")) return (ENXIO); device_set_desc(dev, "Amlogic aml8726 ccm"); return (BUS_PROBE_DEFAULT); } static int aml8726_ccm_attach(device_t dev) { struct aml8726_ccm_softc *sc = device_get_softc(dev); sc->dev = dev; switch (aml8726_soc_hw_rev) { case AML_SOC_HW_REV_M3: sc->soc = aml8726_m3_ccm; break; case AML_SOC_HW_REV_M6: sc->soc = aml8726_m6_ccm; break; case AML_SOC_HW_REV_M8: sc->soc = aml8726_m8_ccm; break; case AML_SOC_HW_REV_M8B: sc->soc = aml8726_m8b_ccm; break; default: device_printf(dev, "unsupported SoC\n"); return (ENXIO); /* NOTREACHED */ } if (bus_alloc_resources(dev, aml8726_ccm_spec, sc->res)) { device_printf(dev, "can not allocate resources for device\n"); return (ENXIO); } AML_CCM_LOCK_INIT(sc); return (aml8726_ccm_configure_gates(sc)); } static int aml8726_ccm_detach(device_t dev) { struct aml8726_ccm_softc *sc = device_get_softc(dev); AML_CCM_LOCK_DESTROY(sc); bus_release_resources(dev, aml8726_ccm_spec, sc->res); return (0); } static device_method_t aml8726_ccm_methods[] = { /* Device interface */ DEVMETHOD(device_probe, aml8726_ccm_probe), DEVMETHOD(device_attach, aml8726_ccm_attach), DEVMETHOD(device_detach, aml8726_ccm_detach), DEVMETHOD_END }; static driver_t aml8726_ccm_driver = { "ccm", aml8726_ccm_methods, sizeof(struct aml8726_ccm_softc), }; static devclass_t aml8726_ccm_devclass; EARLY_DRIVER_MODULE(ccm, simplebus, aml8726_ccm_driver, aml8726_ccm_devclass, 0, 0, BUS_PASS_CPU + BUS_PASS_ORDER_LATE); Index: head/sys/arm/amlogic/aml8726/aml8726_mmc.c =================================================================== --- head/sys/arm/amlogic/aml8726/aml8726_mmc.c (revision 332309) +++ head/sys/arm/amlogic/aml8726/aml8726_mmc.c (revision 332310) @@ -1,1098 +1,1098 @@ /*- * Copyright 2013-2015 John Wehle * 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. */ /* * Amlogic aml8726 MMC host controller driver. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gpio_if.h" #include "mmcbr_if.h" struct aml8726_mmc_gpio { device_t dev; uint32_t pin; uint32_t pol; }; struct aml8726_mmc_softc { device_t dev; struct resource *res[2]; struct mtx mtx; struct callout ch; uint32_t port; unsigned int ref_freq; struct aml8726_mmc_gpio pwr_en; int voltages[2]; struct aml8726_mmc_gpio vselect; bus_dma_tag_t dmatag; bus_dmamap_t dmamap; void *ih_cookie; struct mmc_host host; int bus_busy; struct mmc_command *cmd; uint32_t stop_timeout; }; static struct resource_spec aml8726_mmc_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE }, { -1, 0 } }; #define AML_MMC_LOCK(sc) mtx_lock(&(sc)->mtx) #define AML_MMC_UNLOCK(sc) mtx_unlock(&(sc)->mtx) #define AML_MMC_LOCK_ASSERT(sc) mtx_assert(&(sc)->mtx, MA_OWNED) #define AML_MMC_LOCK_INIT(sc) \ mtx_init(&(sc)->mtx, device_get_nameunit((sc)->dev), \ "mmc", MTX_DEF) #define AML_MMC_LOCK_DESTROY(sc) mtx_destroy(&(sc)->mtx); #define CSR_WRITE_4(sc, reg, val) bus_write_4((sc)->res[0], reg, (val)) #define CSR_READ_4(sc, reg) bus_read_4((sc)->res[0], reg) #define CSR_BARRIER(sc, reg) bus_barrier((sc)->res[0], reg, 4, \ (BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE)) #define PWR_ON_FLAG(pol) ((pol) == 0 ? GPIO_PIN_LOW : \ GPIO_PIN_HIGH) #define PWR_OFF_FLAG(pol) ((pol) == 0 ? GPIO_PIN_HIGH : \ GPIO_PIN_LOW) #define MSECS_TO_TICKS(ms) (((ms)*hz)/1000 + 1) static void aml8726_mmc_timeout(void *arg); static unsigned int aml8726_mmc_clk(phandle_t node) { pcell_t prop; ssize_t len; phandle_t clk_node; len = OF_getencprop(node, "clocks", &prop, sizeof(prop)); if ((len / sizeof(prop)) != 1 || prop == 0 || (clk_node = OF_node_from_xref(prop)) == 0) return (0); len = OF_getencprop(clk_node, "clock-frequency", &prop, sizeof(prop)); if ((len / sizeof(prop)) != 1 || prop == 0) return (0); return ((unsigned int)prop); } static uint32_t aml8726_mmc_freq(struct aml8726_mmc_softc *sc, uint32_t divisor) { return (sc->ref_freq / ((divisor + 1) * 2)); } static uint32_t aml8726_mmc_div(struct aml8726_mmc_softc *sc, uint32_t desired_freq) { uint32_t divisor; divisor = sc->ref_freq / (desired_freq * 2); if (divisor == 0) divisor = 1; divisor -= 1; if (aml8726_mmc_freq(sc, divisor) > desired_freq) divisor += 1; if (divisor > (AML_MMC_CONFIG_CMD_CLK_DIV_MASK >> AML_MMC_CONFIG_CMD_CLK_DIV_SHIFT)) { divisor = AML_MMC_CONFIG_CMD_CLK_DIV_MASK >> AML_MMC_CONFIG_CMD_CLK_DIV_SHIFT; } return (divisor); } static void aml8726_mmc_mapmem(void *arg, bus_dma_segment_t *segs, int nseg, int error) { bus_addr_t *busaddrp; /* * There should only be one bus space address since * bus_dma_tag_create was called with nsegments = 1. */ busaddrp = (bus_addr_t *)arg; *busaddrp = segs->ds_addr; } static int aml8726_mmc_power_off(struct aml8726_mmc_softc *sc) { if (sc->pwr_en.dev == NULL) return (0); return (GPIO_PIN_SET(sc->pwr_en.dev, sc->pwr_en.pin, PWR_OFF_FLAG(sc->pwr_en.pol))); } static int aml8726_mmc_power_on(struct aml8726_mmc_softc *sc) { if (sc->pwr_en.dev == NULL) return (0); return (GPIO_PIN_SET(sc->pwr_en.dev, sc->pwr_en.pin, PWR_ON_FLAG(sc->pwr_en.pol))); } static void aml8726_mmc_soft_reset(struct aml8726_mmc_softc *sc, boolean_t enable_irq) { uint32_t icr; icr = AML_MMC_IRQ_CONFIG_SOFT_RESET; if (enable_irq == true) icr |= AML_MMC_IRQ_CONFIG_CMD_DONE_EN; CSR_WRITE_4(sc, AML_MMC_IRQ_CONFIG_REG, icr); CSR_BARRIER(sc, AML_MMC_IRQ_CONFIG_REG); } static int aml8726_mmc_start_command(struct aml8726_mmc_softc *sc, struct mmc_command *cmd) { struct mmc_ios *ios = &sc->host.ios; bus_addr_t baddr; uint32_t block_size; uint32_t bus_width; uint32_t cmdr; uint32_t extr; uint32_t mcfgr; uint32_t nbits_per_pkg; uint32_t timeout; int error; struct mmc_data *data; if (cmd->opcode > 0x3f) return (MMC_ERR_INVALID); /* * Ensure the hardware state machine is in a known state. */ aml8726_mmc_soft_reset(sc, true); /* * Start and transmission bits are per section 4.7.2 of the: * * SD Specifications Part 1 * Physical Layer Simplified Specification * Version 4.10 */ cmdr = AML_MMC_CMD_START_BIT | AML_MMC_CMD_TRANS_BIT_HOST | cmd->opcode; baddr = 0; extr = 0; mcfgr = sc->port; timeout = AML_MMC_CMD_TIMEOUT; /* * If this is a linked command, then use the previous timeout. */ if (cmd == cmd->mrq->stop && sc->stop_timeout) timeout = sc->stop_timeout; sc->stop_timeout = 0; if ((cmd->flags & MMC_RSP_136) != 0) { cmdr |= AML_MMC_CMD_RESP_CRC7_FROM_8; cmdr |= (133 << AML_MMC_CMD_RESP_BITS_SHIFT); } else if ((cmd->flags & MMC_RSP_PRESENT) != 0) cmdr |= (45 << AML_MMC_CMD_RESP_BITS_SHIFT); if ((cmd->flags & MMC_RSP_CRC) == 0) cmdr |= AML_MMC_CMD_RESP_NO_CRC7; if ((cmd->flags & MMC_RSP_BUSY) != 0) cmdr |= AML_MMC_CMD_CHECK_DAT0_BUSY; data = cmd->data; if (data && data->len && (data->flags & (MMC_DATA_READ | MMC_DATA_WRITE)) != 0) { block_size = data->len; if ((data->flags & MMC_DATA_MULTI) != 0) { block_size = MMC_SECTOR_SIZE; if ((data->len % block_size) != 0) return (MMC_ERR_INVALID); } cmdr |= (((data->len / block_size) - 1) << AML_MMC_CMD_REP_PKG_CNT_SHIFT); mcfgr |= (data->flags & MMC_DATA_STREAM) ? AML_MMC_MULT_CONFIG_STREAM_EN : 0; /* * The number of bits per package equals the number * of data bits + the number of CRC bits. There are * 16 bits of CRC calculate per bus line. * * A completed package appears to be detected by when * a counter decremented by the width underflows, thus * a value of zero always transfers 1 (or 4 bits depending * on the mode) which is why bus_width is subtracted. */ bus_width = (ios->bus_width == bus_width_4) ? 4 : 1; nbits_per_pkg = block_size * 8 + 16 * bus_width - bus_width; if (nbits_per_pkg > 0x3fff) return (MMC_ERR_INVALID); extr |= (nbits_per_pkg << AML_MMC_EXTENSION_PKT_SIZE_SHIFT); error = bus_dmamap_load(sc->dmatag, sc->dmamap, data->data, data->len, aml8726_mmc_mapmem, &baddr, BUS_DMA_NOWAIT); if (error) return (MMC_ERR_NO_MEMORY); if ((data->flags & MMC_DATA_READ) != 0) { cmdr |= AML_MMC_CMD_RESP_HAS_DATA; bus_dmamap_sync(sc->dmatag, sc->dmamap, BUS_DMASYNC_PREREAD); timeout = AML_MMC_READ_TIMEOUT * (data->len / block_size); } else { cmdr |= AML_MMC_CMD_CMD_HAS_DATA; bus_dmamap_sync(sc->dmatag, sc->dmamap, BUS_DMASYNC_PREWRITE); timeout = AML_MMC_WRITE_TIMEOUT * (data->len / block_size); } /* * Stop terminates a multiblock read / write and thus * can take as long to execute as an actual read / write. */ if (cmd->mrq->stop != NULL) sc->stop_timeout = timeout; } sc->cmd = cmd; cmd->error = MMC_ERR_NONE; if (timeout > AML_MMC_MAX_TIMEOUT) timeout = AML_MMC_MAX_TIMEOUT; callout_reset(&sc->ch, MSECS_TO_TICKS(timeout), aml8726_mmc_timeout, sc); CSR_WRITE_4(sc, AML_MMC_CMD_ARGUMENT_REG, cmd->arg); CSR_WRITE_4(sc, AML_MMC_MULT_CONFIG_REG, mcfgr); CSR_WRITE_4(sc, AML_MMC_EXTENSION_REG, extr); CSR_WRITE_4(sc, AML_MMC_DMA_ADDR_REG, (uint32_t)baddr); CSR_WRITE_4(sc, AML_MMC_CMD_SEND_REG, cmdr); CSR_BARRIER(sc, AML_MMC_CMD_SEND_REG); return (MMC_ERR_NONE); } static void aml8726_mmc_finish_command(struct aml8726_mmc_softc *sc, int mmc_error) { int mmc_stop_error; struct mmc_command *cmd; struct mmc_command *stop_cmd; struct mmc_data *data; AML_MMC_LOCK_ASSERT(sc); /* Clear all interrupts since the request is no longer in flight. */ CSR_WRITE_4(sc, AML_MMC_IRQ_STATUS_REG, AML_MMC_IRQ_STATUS_CLEAR_IRQ); CSR_BARRIER(sc, AML_MMC_IRQ_STATUS_REG); /* In some cases (e.g. finish called via timeout) this is a NOP. */ callout_stop(&sc->ch); cmd = sc->cmd; sc->cmd = NULL; cmd->error = mmc_error; data = cmd->data; if (data && data->len && (data->flags & (MMC_DATA_READ | MMC_DATA_WRITE)) != 0) { if ((data->flags & MMC_DATA_READ) != 0) bus_dmamap_sync(sc->dmatag, sc->dmamap, BUS_DMASYNC_POSTREAD); else bus_dmamap_sync(sc->dmatag, sc->dmamap, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->dmatag, sc->dmamap); } /* * If there's a linked stop command, then start the stop command. * In order to establish a known state attempt the stop command * even if the original request encountered an error. */ stop_cmd = (cmd->mrq->stop != cmd) ? cmd->mrq->stop : NULL; if (stop_cmd != NULL) { mmc_stop_error = aml8726_mmc_start_command(sc, stop_cmd); if (mmc_stop_error == MMC_ERR_NONE) { AML_MMC_UNLOCK(sc); return; } stop_cmd->error = mmc_stop_error; } AML_MMC_UNLOCK(sc); /* Execute the callback after dropping the lock. */ if (cmd->mrq) cmd->mrq->done(cmd->mrq); } static void aml8726_mmc_timeout(void *arg) { struct aml8726_mmc_softc *sc = (struct aml8726_mmc_softc *)arg; /* * The command failed to complete in time so forcefully * terminate it. */ aml8726_mmc_soft_reset(sc, false); /* * Ensure the command has terminated before continuing on * to things such as bus_dmamap_sync / bus_dmamap_unload. */ while ((CSR_READ_4(sc, AML_MMC_IRQ_STATUS_REG) & AML_MMC_IRQ_STATUS_CMD_BUSY) != 0) cpu_spinwait(); aml8726_mmc_finish_command(sc, MMC_ERR_TIMEOUT); } static void aml8726_mmc_intr(void *arg) { struct aml8726_mmc_softc *sc = (struct aml8726_mmc_softc *)arg; uint32_t cmdr; uint32_t isr; uint32_t mcfgr; uint32_t previous_byte; uint32_t resp; int mmc_error; unsigned int i; AML_MMC_LOCK(sc); isr = CSR_READ_4(sc, AML_MMC_IRQ_STATUS_REG); cmdr = CSR_READ_4(sc, AML_MMC_CMD_SEND_REG); if (sc->cmd == NULL) goto spurious; mmc_error = MMC_ERR_NONE; if ((isr & AML_MMC_IRQ_STATUS_CMD_DONE_IRQ) != 0) { /* Check for CRC errors if the command has completed. */ if ((cmdr & AML_MMC_CMD_RESP_NO_CRC7) == 0 && (isr & AML_MMC_IRQ_STATUS_RESP_CRC7_OK) == 0) mmc_error = MMC_ERR_BADCRC; if ((cmdr & AML_MMC_CMD_RESP_HAS_DATA) != 0 && (isr & AML_MMC_IRQ_STATUS_RD_CRC16_OK) == 0) mmc_error = MMC_ERR_BADCRC; if ((cmdr & AML_MMC_CMD_CMD_HAS_DATA) != 0 && (isr & AML_MMC_IRQ_STATUS_WR_CRC16_OK) == 0) mmc_error = MMC_ERR_BADCRC; } else { spurious: /* * Clear spurious interrupts while leaving intacted any * interrupts that may have occurred after we read the * interrupt status register. */ CSR_WRITE_4(sc, AML_MMC_IRQ_STATUS_REG, (AML_MMC_IRQ_STATUS_CLEAR_IRQ & isr)); CSR_BARRIER(sc, AML_MMC_IRQ_STATUS_REG); AML_MMC_UNLOCK(sc); return; } if ((cmdr & AML_MMC_CMD_RESP_BITS_MASK) != 0) { mcfgr = sc->port; mcfgr |= AML_MMC_MULT_CONFIG_RESP_READOUT_EN; CSR_WRITE_4(sc, AML_MMC_MULT_CONFIG_REG, mcfgr); if ((cmdr & AML_MMC_CMD_RESP_CRC7_FROM_8) != 0) { /* * Controller supplies 135:8 instead of * 127:0 so discard the leading 8 bits * and provide a trailing 8 zero bits * where the CRC belongs. */ previous_byte = 0; for (i = 0; i < 4; i++) { resp = CSR_READ_4(sc, AML_MMC_CMD_ARGUMENT_REG); sc->cmd->resp[3 - i] = (resp << 8) | previous_byte; previous_byte = (resp >> 24) & 0xff; } } else sc->cmd->resp[0] = CSR_READ_4(sc, AML_MMC_CMD_ARGUMENT_REG); } if ((isr & AML_MMC_IRQ_STATUS_CMD_BUSY) != 0 && /* * A multiblock operation may keep the hardware * busy until stop transmission is executed. */ (isr & AML_MMC_IRQ_STATUS_CMD_DONE_IRQ) == 0) { if (mmc_error == MMC_ERR_NONE) mmc_error = MMC_ERR_FAILED; /* * Issue a soft reset to terminate the command. * * Ensure the command has terminated before continuing on * to things such as bus_dmamap_sync / bus_dmamap_unload. */ aml8726_mmc_soft_reset(sc, false); while ((CSR_READ_4(sc, AML_MMC_IRQ_STATUS_REG) & AML_MMC_IRQ_STATUS_CMD_BUSY) != 0) cpu_spinwait(); } aml8726_mmc_finish_command(sc, mmc_error); } static int aml8726_mmc_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "amlogic,aml8726-mmc")) return (ENXIO); device_set_desc(dev, "Amlogic aml8726 MMC"); return (BUS_PROBE_DEFAULT); } static int aml8726_mmc_attach(device_t dev) { struct aml8726_mmc_softc *sc = device_get_softc(dev); char *function_name; char *voltages; char *voltage; int error; int nvoltages; pcell_t prop[3]; phandle_t node; ssize_t len; device_t child; sc->dev = dev; node = ofw_bus_get_node(dev); sc->ref_freq = aml8726_mmc_clk(node); if (sc->ref_freq == 0) { device_printf(dev, "missing clocks attribute in FDT\n"); return (ENXIO); } /* * The pins must be specified as part of the device in order * to know which port to used. */ len = OF_getencprop(node, "pinctrl-0", prop, sizeof(prop)); if ((len / sizeof(prop[0])) != 1 || prop[0] == 0) { device_printf(dev, "missing pinctrl-0 attribute in FDT\n"); return (ENXIO); } len = OF_getprop_alloc(OF_node_from_xref(prop[0]), "amlogic,function", - sizeof(char), (void **)&function_name); + (void **)&function_name); if (len < 0) { device_printf(dev, "missing amlogic,function attribute in FDT\n"); return (ENXIO); } if (strncmp("sdio-a", function_name, len) == 0) sc->port = AML_MMC_MULT_CONFIG_PORT_A; else if (strncmp("sdio-b", function_name, len) == 0) sc->port = AML_MMC_MULT_CONFIG_PORT_B; else if (strncmp("sdio-c", function_name, len) == 0) sc->port = AML_MMC_MULT_CONFIG_PORT_C; else { device_printf(dev, "unknown function attribute %.*s in FDT\n", len, function_name); OF_prop_free(function_name); return (ENXIO); } OF_prop_free(function_name); sc->pwr_en.dev = NULL; len = OF_getencprop(node, "mmc-pwr-en", prop, sizeof(prop)); if (len > 0) { if ((len / sizeof(prop[0])) == 3) { sc->pwr_en.dev = OF_device_from_xref(prop[0]); sc->pwr_en.pin = prop[1]; sc->pwr_en.pol = prop[2]; } if (sc->pwr_en.dev == NULL) { device_printf(dev, "unable to process mmc-pwr-en attribute in FDT\n"); return (ENXIO); } /* Turn off power and then configure the output driver. */ if (aml8726_mmc_power_off(sc) != 0 || GPIO_PIN_SETFLAGS(sc->pwr_en.dev, sc->pwr_en.pin, GPIO_PIN_OUTPUT) != 0) { device_printf(dev, "could not use gpio to control power\n"); return (ENXIO); } } len = OF_getprop_alloc(node, "mmc-voltages", - sizeof(char), (void **)&voltages); + (void **)&voltages); if (len < 0) { device_printf(dev, "missing mmc-voltages attribute in FDT\n"); return (ENXIO); } sc->voltages[0] = 0; sc->voltages[1] = 0; voltage = voltages; nvoltages = 0; while (len && nvoltages < 2) { if (strncmp("1.8", voltage, len) == 0) sc->voltages[nvoltages] = MMC_OCR_LOW_VOLTAGE; else if (strncmp("3.3", voltage, len) == 0) sc->voltages[nvoltages] = MMC_OCR_320_330 | MMC_OCR_330_340; else { device_printf(dev, "unknown voltage attribute %.*s in FDT\n", len, voltage); OF_prop_free(voltages); return (ENXIO); } nvoltages++; /* queue up next string */ while (*voltage && len) { voltage++; len--; } if (len) { voltage++; len--; } } OF_prop_free(voltages); sc->vselect.dev = NULL; len = OF_getencprop(node, "mmc-vselect", prop, sizeof(prop)); if (len > 0) { if ((len / sizeof(prop[0])) == 2) { sc->vselect.dev = OF_device_from_xref(prop[0]); sc->vselect.pin = prop[1]; sc->vselect.pol = 1; } if (sc->vselect.dev == NULL) { device_printf(dev, "unable to process mmc-vselect attribute in FDT\n"); return (ENXIO); } /* * With the power off select voltage 0 and then * configure the output driver. */ if (GPIO_PIN_SET(sc->vselect.dev, sc->vselect.pin, 0) != 0 || GPIO_PIN_SETFLAGS(sc->vselect.dev, sc->vselect.pin, GPIO_PIN_OUTPUT) != 0) { device_printf(dev, "could not use gpio to set voltage\n"); return (ENXIO); } } if (nvoltages == 0) { device_printf(dev, "no voltages in FDT\n"); return (ENXIO); } else if (nvoltages == 1 && sc->vselect.dev != NULL) { device_printf(dev, "only one voltage in FDT\n"); return (ENXIO); } else if (nvoltages == 2 && sc->vselect.dev == NULL) { device_printf(dev, "too many voltages in FDT\n"); return (ENXIO); } if (bus_alloc_resources(dev, aml8726_mmc_spec, sc->res)) { device_printf(dev, "could not allocate resources for device\n"); return (ENXIO); } AML_MMC_LOCK_INIT(sc); error = bus_dma_tag_create(bus_get_dma_tag(dev), AML_MMC_ALIGN_DMA, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, AML_MMC_MAX_DMA, 1, AML_MMC_MAX_DMA, 0, NULL, NULL, &sc->dmatag); if (error) goto fail; error = bus_dmamap_create(sc->dmatag, 0, &sc->dmamap); if (error) goto fail; error = bus_setup_intr(dev, sc->res[1], INTR_TYPE_MISC | INTR_MPSAFE, NULL, aml8726_mmc_intr, sc, &sc->ih_cookie); if (error) { device_printf(dev, "could not setup interrupt handler\n"); goto fail; } callout_init_mtx(&sc->ch, &sc->mtx, CALLOUT_RETURNUNLOCKED); sc->host.f_min = aml8726_mmc_freq(sc, aml8726_mmc_div(sc, 200000)); sc->host.f_max = aml8726_mmc_freq(sc, aml8726_mmc_div(sc, 50000000)); sc->host.host_ocr = sc->voltages[0] | sc->voltages[1]; sc->host.caps = MMC_CAP_4_BIT_DATA | MMC_CAP_HSPEED; child = device_add_child(dev, "mmc", -1); if (!child) { device_printf(dev, "could not add mmc\n"); error = ENXIO; goto fail; } error = device_probe_and_attach(child); if (error) { device_printf(dev, "could not attach mmc\n"); goto fail; } return (0); fail: if (sc->ih_cookie) bus_teardown_intr(dev, sc->res[1], sc->ih_cookie); if (sc->dmamap) bus_dmamap_destroy(sc->dmatag, sc->dmamap); if (sc->dmatag) bus_dma_tag_destroy(sc->dmatag); AML_MMC_LOCK_DESTROY(sc); aml8726_mmc_power_off(sc); bus_release_resources(dev, aml8726_mmc_spec, sc->res); return (error); } static int aml8726_mmc_detach(device_t dev) { struct aml8726_mmc_softc *sc = device_get_softc(dev); AML_MMC_LOCK(sc); if (sc->cmd != NULL) { AML_MMC_UNLOCK(sc); return (EBUSY); } /* * Turn off the power, reset the hardware state machine, * disable the interrupts, and clear the interrupts. */ (void)aml8726_mmc_power_off(sc); aml8726_mmc_soft_reset(sc, false); CSR_WRITE_4(sc, AML_MMC_IRQ_STATUS_REG, AML_MMC_IRQ_STATUS_CLEAR_IRQ); /* This should be a NOP since no command was in flight. */ callout_stop(&sc->ch); AML_MMC_UNLOCK(sc); bus_generic_detach(dev); bus_teardown_intr(dev, sc->res[1], sc->ih_cookie); bus_dmamap_destroy(sc->dmatag, sc->dmamap); bus_dma_tag_destroy(sc->dmatag); AML_MMC_LOCK_DESTROY(sc); bus_release_resources(dev, aml8726_mmc_spec, sc->res); return (0); } static int aml8726_mmc_shutdown(device_t dev) { struct aml8726_mmc_softc *sc = device_get_softc(dev); /* * Turn off the power, reset the hardware state machine, * disable the interrupts, and clear the interrupts. */ (void)aml8726_mmc_power_off(sc); aml8726_mmc_soft_reset(sc, false); CSR_WRITE_4(sc, AML_MMC_IRQ_STATUS_REG, AML_MMC_IRQ_STATUS_CLEAR_IRQ); return (0); } static int aml8726_mmc_update_ios(device_t bus, device_t child) { struct aml8726_mmc_softc *sc = device_get_softc(bus); struct mmc_ios *ios = &sc->host.ios; int error; int i; uint32_t cfgr; cfgr = (2 << AML_MMC_CONFIG_WR_CRC_STAT_SHIFT) | (2 << AML_MMC_CONFIG_WR_DELAY_SHIFT) | AML_MMC_CONFIG_DMA_ENDIAN_SBW | (39 << AML_MMC_CONFIG_CMD_ARG_BITS_SHIFT); switch (ios->bus_width) { case bus_width_4: cfgr |= AML_MMC_CONFIG_BUS_WIDTH_4; break; case bus_width_1: cfgr |= AML_MMC_CONFIG_BUS_WIDTH_1; break; default: return (EINVAL); } cfgr |= aml8726_mmc_div(sc, ios->clock) << AML_MMC_CONFIG_CMD_CLK_DIV_SHIFT; CSR_WRITE_4(sc, AML_MMC_CONFIG_REG, cfgr); error = 0; switch (ios->power_mode) { case power_up: /* * Configure and power on the regulator so that the * voltage stabilizes prior to powering on the card. */ if (sc->vselect.dev != NULL) { for (i = 0; i < 2; i++) if ((sc->voltages[i] & (1 << ios->vdd)) != 0) break; if (i >= 2) return (EINVAL); error = GPIO_PIN_SET(sc->vselect.dev, sc->vselect.pin, i); } break; case power_on: error = aml8726_mmc_power_on(sc); break; case power_off: error = aml8726_mmc_power_off(sc); break; default: return (EINVAL); } return (error); } static int aml8726_mmc_request(device_t bus, device_t child, struct mmc_request *req) { struct aml8726_mmc_softc *sc = device_get_softc(bus); int mmc_error; AML_MMC_LOCK(sc); if (sc->cmd != NULL) { AML_MMC_UNLOCK(sc); return (EBUSY); } mmc_error = aml8726_mmc_start_command(sc, req->cmd); AML_MMC_UNLOCK(sc); /* Execute the callback after dropping the lock. */ if (mmc_error != MMC_ERR_NONE) { req->cmd->error = mmc_error; req->done(req); } return (0); } static int aml8726_mmc_read_ivar(device_t bus, device_t child, int which, uintptr_t *result) { struct aml8726_mmc_softc *sc = device_get_softc(bus); switch (which) { case MMCBR_IVAR_BUS_MODE: *(int *)result = sc->host.ios.bus_mode; break; case MMCBR_IVAR_BUS_WIDTH: *(int *)result = sc->host.ios.bus_width; break; case MMCBR_IVAR_CHIP_SELECT: *(int *)result = sc->host.ios.chip_select; break; case MMCBR_IVAR_CLOCK: *(int *)result = sc->host.ios.clock; break; case MMCBR_IVAR_F_MIN: *(int *)result = sc->host.f_min; break; case MMCBR_IVAR_F_MAX: *(int *)result = sc->host.f_max; break; case MMCBR_IVAR_HOST_OCR: *(int *)result = sc->host.host_ocr; break; case MMCBR_IVAR_MODE: *(int *)result = sc->host.mode; break; case MMCBR_IVAR_OCR: *(int *)result = sc->host.ocr; break; case MMCBR_IVAR_POWER_MODE: *(int *)result = sc->host.ios.power_mode; break; case MMCBR_IVAR_VDD: *(int *)result = sc->host.ios.vdd; break; case MMCBR_IVAR_CAPS: *(int *)result = sc->host.caps; break; case MMCBR_IVAR_MAX_DATA: *(int *)result = AML_MMC_MAX_DMA / MMC_SECTOR_SIZE; break; default: return (EINVAL); } return (0); } static int aml8726_mmc_write_ivar(device_t bus, device_t child, int which, uintptr_t value) { struct aml8726_mmc_softc *sc = device_get_softc(bus); switch (which) { case MMCBR_IVAR_BUS_MODE: sc->host.ios.bus_mode = value; break; case MMCBR_IVAR_BUS_WIDTH: sc->host.ios.bus_width = value; break; case MMCBR_IVAR_CHIP_SELECT: sc->host.ios.chip_select = value; break; case MMCBR_IVAR_CLOCK: sc->host.ios.clock = value; break; case MMCBR_IVAR_MODE: sc->host.mode = value; break; case MMCBR_IVAR_OCR: sc->host.ocr = value; break; case MMCBR_IVAR_POWER_MODE: sc->host.ios.power_mode = value; break; case MMCBR_IVAR_VDD: sc->host.ios.vdd = value; break; /* These are read-only */ case MMCBR_IVAR_CAPS: case MMCBR_IVAR_HOST_OCR: case MMCBR_IVAR_F_MIN: case MMCBR_IVAR_F_MAX: case MMCBR_IVAR_MAX_DATA: default: return (EINVAL); } return (0); } static int aml8726_mmc_get_ro(device_t bus, device_t child) { return (0); } static int aml8726_mmc_acquire_host(device_t bus, device_t child) { struct aml8726_mmc_softc *sc = device_get_softc(bus); AML_MMC_LOCK(sc); while (sc->bus_busy) mtx_sleep(sc, &sc->mtx, PZERO, "mmc", hz / 5); sc->bus_busy++; AML_MMC_UNLOCK(sc); return (0); } static int aml8726_mmc_release_host(device_t bus, device_t child) { struct aml8726_mmc_softc *sc = device_get_softc(bus); AML_MMC_LOCK(sc); sc->bus_busy--; wakeup(sc); AML_MMC_UNLOCK(sc); return (0); } static device_method_t aml8726_mmc_methods[] = { /* Device interface */ DEVMETHOD(device_probe, aml8726_mmc_probe), DEVMETHOD(device_attach, aml8726_mmc_attach), DEVMETHOD(device_detach, aml8726_mmc_detach), DEVMETHOD(device_shutdown, aml8726_mmc_shutdown), /* Bus interface */ DEVMETHOD(bus_read_ivar, aml8726_mmc_read_ivar), DEVMETHOD(bus_write_ivar, aml8726_mmc_write_ivar), /* MMC bridge interface */ DEVMETHOD(mmcbr_update_ios, aml8726_mmc_update_ios), DEVMETHOD(mmcbr_request, aml8726_mmc_request), DEVMETHOD(mmcbr_get_ro, aml8726_mmc_get_ro), DEVMETHOD(mmcbr_acquire_host, aml8726_mmc_acquire_host), DEVMETHOD(mmcbr_release_host, aml8726_mmc_release_host), DEVMETHOD_END }; static driver_t aml8726_mmc_driver = { "aml8726_mmc", aml8726_mmc_methods, sizeof(struct aml8726_mmc_softc), }; static devclass_t aml8726_mmc_devclass; DRIVER_MODULE(aml8726_mmc, simplebus, aml8726_mmc_driver, aml8726_mmc_devclass, NULL, NULL); MODULE_DEPEND(aml8726_mmc, aml8726_gpio, 1, 1, 1); MMC_DECLARE_BRIDGE(aml8726_mmc); Index: head/sys/arm/amlogic/aml8726/aml8726_pinctrl.c =================================================================== --- head/sys/arm/amlogic/aml8726/aml8726_pinctrl.c (revision 332309) +++ head/sys/arm/amlogic/aml8726/aml8726_pinctrl.c (revision 332310) @@ -1,432 +1,432 @@ /*- * Copyright 2015 John Wehle * 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. */ /* * Amlogic aml8726 pinctrl driver. * */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct aml8726_pinctrl_softc { device_t dev; struct { struct aml8726_pinctrl_function *func; struct aml8726_pinctrl_pkg_pin *ppin; boolean_t pud_ctrl; } soc; struct resource *res[6]; struct mtx mtx; }; static struct resource_spec aml8726_pinctrl_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, /* mux */ { SYS_RES_MEMORY, 1, RF_ACTIVE | RF_SHAREABLE }, /* pu/pd */ { SYS_RES_MEMORY, 2, RF_ACTIVE | RF_SHAREABLE }, /* pull enable */ { SYS_RES_MEMORY, 3, RF_ACTIVE }, /* ao mux */ { SYS_RES_MEMORY, 4, RF_ACTIVE | RF_SHAREABLE }, /* ao pu/pd */ { SYS_RES_MEMORY, 5, RF_ACTIVE | RF_SHAREABLE }, /* ao pull enable */ { -1, 0 } }; #define AML_PINCTRL_LOCK(sc) mtx_lock(&(sc)->mtx) #define AML_PINCTRL_UNLOCK(sc) mtx_unlock(&(sc)->mtx) #define AML_PINCTRL_LOCK_INIT(sc) \ mtx_init(&(sc)->mtx, device_get_nameunit((sc)->dev), \ "pinctrl", MTX_DEF) #define AML_PINCTRL_LOCK_DESTROY(sc) mtx_destroy(&(sc)->mtx); #define MUX_WRITE_4(sc, reg, val) bus_write_4((sc)->res[0], reg, (val)) #define MUX_READ_4(sc, reg) bus_read_4((sc)->res[0], reg) #define PUD_WRITE_4(sc, reg, val) bus_write_4((sc)->res[1], reg, (val)) #define PUD_READ_4(sc, reg) bus_read_4((sc)->res[1], reg) #define PEN_WRITE_4(sc, reg, val) bus_write_4((sc)->res[2], reg, (val)) #define PEN_READ_4(sc, reg) bus_read_4((sc)->res[2], reg) #define AOMUX_WRITE_4(sc, reg, val) bus_write_4((sc)->res[3], reg, (val)) #define AOMUX_READ_4(sc, reg) bus_read_4((sc)->res[3], reg) #define AOPUD_WRITE_4(sc, reg, val) bus_write_4((sc)->res[4], reg, (val)) #define AOPUD_READ_4(sc, reg) bus_read_4((sc)->res[4], reg) #define AOPEN_WRITE_4(sc, reg, val) bus_write_4((sc)->res[5], reg, (val)) #define AOPEN_READ_4(sc, reg) bus_read_4((sc)->res[5], reg) static int aml8726_pinctrl_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "amlogic,aml8726-pinctrl")) return (ENXIO); device_set_desc(dev, "Amlogic aml8726 pinctrl"); return (BUS_PROBE_DEFAULT); } static int aml8726_pinctrl_attach(device_t dev) { struct aml8726_pinctrl_softc *sc = device_get_softc(dev); sc->dev = dev; sc->soc.pud_ctrl = false; switch (aml8726_soc_hw_rev) { case AML_SOC_HW_REV_M3: sc->soc.func = aml8726_m3_pinctrl; sc->soc.ppin = aml8726_m3_pkg_pin; break; case AML_SOC_HW_REV_M6: sc->soc.func = aml8726_m6_pinctrl; sc->soc.ppin = aml8726_m6_pkg_pin; break; case AML_SOC_HW_REV_M8: sc->soc.func = aml8726_m8_pinctrl; sc->soc.ppin = aml8726_m8_pkg_pin; sc->soc.pud_ctrl = true; break; case AML_SOC_HW_REV_M8B: sc->soc.func = aml8726_m8b_pinctrl; sc->soc.ppin = aml8726_m8b_pkg_pin; sc->soc.pud_ctrl = true; break; default: device_printf(dev, "unsupported SoC\n"); return (ENXIO); /* NOTREACHED */ } if (bus_alloc_resources(dev, aml8726_pinctrl_spec, sc->res)) { device_printf(dev, "could not allocate resources for device\n"); return (ENXIO); } AML_PINCTRL_LOCK_INIT(sc); fdt_pinctrl_register(dev, "amlogic,pins"); fdt_pinctrl_configure_tree(dev); return (0); } static int aml8726_pinctrl_detach(device_t dev) { struct aml8726_pinctrl_softc *sc = device_get_softc(dev); AML_PINCTRL_LOCK_DESTROY(sc); bus_release_resources(dev, aml8726_pinctrl_spec, sc->res); return (0); } static int aml8726_pinctrl_configure_pins(device_t dev, phandle_t cfgxref) { struct aml8726_pinctrl_softc *sc = device_get_softc(dev); struct aml8726_pinctrl_function *cf; struct aml8726_pinctrl_function *f; struct aml8726_pinctrl_pkg_pin *pp; struct aml8726_pinctrl_pin *cp; struct aml8726_pinctrl_pin *p; enum aml8726_pinctrl_pull_mode pm; char *function_name; char *pins; char *pin_name; char *pull; phandle_t node; ssize_t len; uint32_t value; node = OF_node_from_xref(cfgxref); len = OF_getprop_alloc(node, "amlogic,function", - sizeof(char), (void **)&function_name); + (void **)&function_name); if (len < 0) { device_printf(dev, "missing amlogic,function attribute in FDT\n"); return (ENXIO); } for (f = sc->soc.func; f->name != NULL; f++) if (strncmp(f->name, function_name, len) == 0) break; if (f->name == NULL) { device_printf(dev, "unknown function attribute %.*s in FDT\n", len, function_name); OF_prop_free(function_name); return (ENXIO); } OF_prop_free(function_name); len = OF_getprop_alloc(node, "amlogic,pull", - sizeof(char), (void **)&pull); + (void **)&pull); pm = aml8726_unknown_pm; if (len > 0) { if (strncmp(pull, "enable", len) == 0) pm = aml8726_enable_pm; else if (strncmp(pull, "disable", len) == 0) pm = aml8726_disable_pm; else if (strncmp(pull, "down", len) == 0) pm = aml8726_enable_down_pm; else if (strncmp(pull, "up", len) == 0) pm = aml8726_enable_up_pm; else { device_printf(dev, "unknown pull attribute %.*s in FDT\n", len, pull); OF_prop_free(pull); return (ENXIO); } } OF_prop_free(pull); /* * Setting the pull direction isn't supported on all SoC. */ switch (pm) { case aml8726_enable_down_pm: case aml8726_enable_up_pm: if (sc->soc.pud_ctrl == false) { device_printf(dev, "SoC doesn't support setting pull direction.\n"); return (ENXIO); } break; default: break; } len = OF_getprop_alloc(node, "amlogic,pins", - sizeof(char), (void **)&pins); + (void **)&pins); if (len < 0) { device_printf(dev, "missing amlogic,pins attribute in FDT\n"); return (ENXIO); } pin_name = pins; while (len) { for (p = f->pins; p->name != NULL; p++) if (strncmp(p->name, pin_name, len) == 0) break; if (p->name == NULL) { /* display message prior to queuing up next string */ device_printf(dev, "unknown pin attribute %.*s in FDT\n", len, pin_name); } /* queue up next string */ while (*pin_name && len) { pin_name++; len--; } if (len) { pin_name++; len--; } if (p->name == NULL) continue; for (pp = sc->soc.ppin; pp->pkg_name != NULL; pp++) if (strcmp(pp->pkg_name, p->pkg_name) == 0) break; if (pp->pkg_name == NULL) { device_printf(dev, "missing entry for package pin %s\n", p->pkg_name); continue; } if (pm != aml8726_unknown_pm && pp->pull_bits == 0x00000000) { device_printf(dev, "missing pull info for package pin %s\n", p->pkg_name); continue; } AML_PINCTRL_LOCK(sc); /* * First clear all other mux bits associated with this * package pin. This may briefly configure the pin as * GPIO ... however this should be fine since after * reset the default GPIO mode is input. */ for (cf = sc->soc.func; cf->name != NULL; cf++) for (cp = cf->pins; cp->name != NULL; cp++) { if (cp == p) continue; if (strcmp(cp->pkg_name, p->pkg_name) != 0) continue; if (cp->mux_bits == 0) continue; if (pp->aobus == false) { value = MUX_READ_4(sc, cp->mux_addr); value &= ~cp->mux_bits; MUX_WRITE_4(sc, cp->mux_addr, value); } else { value = AOMUX_READ_4(sc, cp->mux_addr); value &= ~cp->mux_bits; AOMUX_WRITE_4(sc, cp->mux_addr, value); } } /* * Now set the desired mux bits. * * In the case of GPIO there's no bits to set. */ if (p->mux_bits != 0) { if (pp->aobus == false) { value = MUX_READ_4(sc, p->mux_addr); value |= p->mux_bits; MUX_WRITE_4(sc, p->mux_addr, value); } else { value = AOMUX_READ_4(sc, p->mux_addr); value |= p->mux_bits; AOMUX_WRITE_4(sc, p->mux_addr, value); } } /* * Finally set the pull mode if it was specified. */ switch (pm) { case aml8726_enable_down_pm: case aml8726_enable_up_pm: if (pp->aobus == false) { value = PUD_READ_4(sc, pp->pull_addr); if (pm == aml8726_enable_down_pm) value &= ~pp->pull_bits; else value |= pp->pull_bits; PUD_WRITE_4(sc, pp->pull_addr, value); } else { value = AOPUD_READ_4(sc, pp->pull_addr); if (pm == aml8726_enable_down_pm) value &= ~(pp->pull_bits << 16); else value |= (pp->pull_bits << 16); AOPUD_WRITE_4(sc, pp->pull_addr, value); } /* FALLTHROUGH */ case aml8726_disable_pm: case aml8726_enable_pm: if (pp->aobus == false) { value = PEN_READ_4(sc, pp->pull_addr); if (pm == aml8726_disable_pm) value &= ~pp->pull_bits; else value |= pp->pull_bits; PEN_WRITE_4(sc, pp->pull_addr, value); } else { value = AOPEN_READ_4(sc, pp->pull_addr); if (pm == aml8726_disable_pm) value &= ~pp->pull_bits; else value |= pp->pull_bits; AOPEN_WRITE_4(sc, pp->pull_addr, value); } break; default: break; } AML_PINCTRL_UNLOCK(sc); } OF_prop_free(pins); return (0); } static device_method_t aml8726_pinctrl_methods[] = { /* Device interface */ DEVMETHOD(device_probe, aml8726_pinctrl_probe), DEVMETHOD(device_attach, aml8726_pinctrl_attach), DEVMETHOD(device_detach, aml8726_pinctrl_detach), /* fdt_pinctrl interface */ DEVMETHOD(fdt_pinctrl_configure,aml8726_pinctrl_configure_pins), DEVMETHOD_END }; static driver_t aml8726_pinctrl_driver = { "pinctrl", aml8726_pinctrl_methods, sizeof(struct aml8726_pinctrl_softc), }; static devclass_t aml8726_pinctrl_devclass; EARLY_DRIVER_MODULE(pinctrl, simplebus, aml8726_pinctrl_driver, aml8726_pinctrl_devclass, 0, 0, BUS_PASS_CPU + BUS_PASS_ORDER_LATE); Index: head/sys/arm/amlogic/aml8726/aml8726_sdxc-m8.c =================================================================== --- head/sys/arm/amlogic/aml8726/aml8726_sdxc-m8.c (revision 332309) +++ head/sys/arm/amlogic/aml8726/aml8726_sdxc-m8.c (revision 332310) @@ -1,1378 +1,1378 @@ /*- * Copyright 2015 John Wehle * 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. */ /* * Amlogic aml8726-m8 (and later) SDXC host controller driver. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gpio_if.h" #include "mmcbr_if.h" /* * The table is sorted from highest to lowest and * last entry in the table is mark by freq == 0. */ struct { uint32_t voltage; uint32_t freq; uint32_t rx_phase; } aml8726_sdxc_clk_phases[] = { { MMC_OCR_LOW_VOLTAGE | MMC_OCR_320_330 | MMC_OCR_330_340, 100000000, 1 }, { MMC_OCR_320_330 | MMC_OCR_330_340, 45000000, 15 }, { MMC_OCR_LOW_VOLTAGE, 45000000, 11 }, { MMC_OCR_LOW_VOLTAGE | MMC_OCR_320_330 | MMC_OCR_330_340, 24999999, 15 }, { MMC_OCR_LOW_VOLTAGE | MMC_OCR_320_330 | MMC_OCR_330_340, 5000000, 23 }, { MMC_OCR_LOW_VOLTAGE | MMC_OCR_320_330 | MMC_OCR_330_340, 1000000, 55 }, { MMC_OCR_LOW_VOLTAGE | MMC_OCR_320_330 | MMC_OCR_330_340, 0, 1061 }, }; struct aml8726_sdxc_gpio { device_t dev; uint32_t pin; uint32_t pol; }; struct aml8726_sdxc_softc { device_t dev; boolean_t auto_fill_flush; struct resource *res[2]; struct mtx mtx; struct callout ch; unsigned int ref_freq; struct aml8726_sdxc_gpio pwr_en; int voltages[2]; struct aml8726_sdxc_gpio vselect; struct aml8726_sdxc_gpio card_rst; bus_dma_tag_t dmatag; bus_dmamap_t dmamap; void *ih_cookie; struct mmc_host host; int bus_busy; struct { uint32_t time; uint32_t error; } busy; struct mmc_command *cmd; }; static struct resource_spec aml8726_sdxc_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE }, { -1, 0 } }; #define AML_SDXC_LOCK(sc) mtx_lock(&(sc)->mtx) #define AML_SDXC_UNLOCK(sc) mtx_unlock(&(sc)->mtx) #define AML_SDXC_LOCK_ASSERT(sc) mtx_assert(&(sc)->mtx, MA_OWNED) #define AML_SDXC_LOCK_INIT(sc) \ mtx_init(&(sc)->mtx, device_get_nameunit((sc)->dev), \ "sdxc", MTX_DEF) #define AML_SDXC_LOCK_DESTROY(sc) mtx_destroy(&(sc)->mtx); #define CSR_WRITE_4(sc, reg, val) bus_write_4((sc)->res[0], reg, (val)) #define CSR_READ_4(sc, reg) bus_read_4((sc)->res[0], reg) #define CSR_BARRIER(sc, reg) bus_barrier((sc)->res[0], reg, 4, \ (BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE)) #define PIN_ON_FLAG(pol) ((pol) == 0 ? \ GPIO_PIN_LOW : GPIO_PIN_HIGH) #define PIN_OFF_FLAG(pol) ((pol) == 0 ? \ GPIO_PIN_HIGH : GPIO_PIN_LOW) #define msecs_to_ticks(ms) (((ms)*hz)/1000 + 1) static void aml8726_sdxc_timeout(void *arg); static void aml8726_sdxc_mapmem(void *arg, bus_dma_segment_t *segs, int nseg, int error) { bus_addr_t *busaddrp; /* * There should only be one bus space address since * bus_dma_tag_create was called with nsegments = 1. */ busaddrp = (bus_addr_t *)arg; *busaddrp = segs->ds_addr; } static int aml8726_sdxc_power_off(struct aml8726_sdxc_softc *sc) { if (sc->pwr_en.dev == NULL) return (0); return (GPIO_PIN_SET(sc->pwr_en.dev, sc->pwr_en.pin, PIN_OFF_FLAG(sc->pwr_en.pol))); } static int aml8726_sdxc_power_on(struct aml8726_sdxc_softc *sc) { if (sc->pwr_en.dev == NULL) return (0); return (GPIO_PIN_SET(sc->pwr_en.dev, sc->pwr_en.pin, PIN_ON_FLAG(sc->pwr_en.pol))); } static void aml8726_sdxc_soft_reset(struct aml8726_sdxc_softc *sc) { CSR_WRITE_4(sc, AML_SDXC_SOFT_RESET_REG, AML_SDXC_SOFT_RESET); CSR_BARRIER(sc, AML_SDXC_SOFT_RESET_REG); DELAY(5); } static void aml8726_sdxc_engage_dma(struct aml8726_sdxc_softc *sc) { int i; uint32_t pdmar; uint32_t sr; struct mmc_data *data; data = sc->cmd->data; if (data == NULL || data->len == 0) return; /* * Engaging the DMA hardware is recommended before writing * to AML_SDXC_SEND_REG so that the FIFOs are ready to go. * * Presumably AML_SDXC_CNTRL_REG and AML_SDXC_DMA_ADDR_REG * must be set up prior to this happening. */ pdmar = CSR_READ_4(sc, AML_SDXC_PDMA_REG); pdmar &= ~AML_SDXC_PDMA_RX_FLUSH_MODE_SW; pdmar |= AML_SDXC_PDMA_DMA_EN; if (sc->auto_fill_flush == true) { CSR_WRITE_4(sc, AML_SDXC_PDMA_REG, pdmar); CSR_BARRIER(sc, AML_SDXC_PDMA_REG); return; } if ((data->flags & MMC_DATA_READ) != 0) { pdmar |= AML_SDXC_PDMA_RX_FLUSH_MODE_SW; CSR_WRITE_4(sc, AML_SDXC_PDMA_REG, pdmar); CSR_BARRIER(sc, AML_SDXC_PDMA_REG); } else { pdmar |= AML_SDXC_PDMA_TX_FILL; CSR_WRITE_4(sc, AML_SDXC_PDMA_REG, pdmar); CSR_BARRIER(sc, AML_SDXC_PDMA_REG); /* * Wait up to 100us for data to show up. */ for (i = 0; i < 100; i++) { sr = CSR_READ_4(sc, AML_SDXC_STATUS_REG); if ((sr & AML_SDXC_STATUS_TX_CNT_MASK) != 0) break; DELAY(1); } if (i >= 100) device_printf(sc->dev, "TX FIFO fill timeout\n"); } } static void aml8726_sdxc_disengage_dma(struct aml8726_sdxc_softc *sc) { int i; uint32_t pdmar; uint32_t sr; struct mmc_data *data; data = sc->cmd->data; if (data == NULL || data->len == 0) return; pdmar = CSR_READ_4(sc, AML_SDXC_PDMA_REG); if (sc->auto_fill_flush == true) { pdmar &= ~AML_SDXC_PDMA_DMA_EN; CSR_WRITE_4(sc, AML_SDXC_PDMA_REG, pdmar); CSR_BARRIER(sc, AML_SDXC_PDMA_REG); return; } if ((data->flags & MMC_DATA_READ) != 0) { pdmar |= AML_SDXC_PDMA_RX_FLUSH_NOW; CSR_WRITE_4(sc, AML_SDXC_PDMA_REG, pdmar); CSR_BARRIER(sc, AML_SDXC_PDMA_REG); /* * Wait up to 100us for data to drain. */ for (i = 0; i < 100; i++) { sr = CSR_READ_4(sc, AML_SDXC_STATUS_REG); if ((sr & AML_SDXC_STATUS_RX_CNT_MASK) == 0) break; DELAY(1); } if (i >= 100) device_printf(sc->dev, "RX FIFO drain timeout\n"); } pdmar &= ~(AML_SDXC_PDMA_DMA_EN | AML_SDXC_PDMA_RX_FLUSH_MODE_SW); CSR_WRITE_4(sc, AML_SDXC_PDMA_REG, pdmar); CSR_BARRIER(sc, AML_SDXC_PDMA_REG); } static int aml8726_sdxc_start_command(struct aml8726_sdxc_softc *sc, struct mmc_command *cmd) { bus_addr_t baddr; uint32_t block_size; uint32_t ctlr; uint32_t ier; uint32_t sndr; uint32_t timeout; int error; struct mmc_data *data; AML_SDXC_LOCK_ASSERT(sc); if (cmd->opcode > 0x3f) return (MMC_ERR_INVALID); /* * Ensure the hardware state machine is in a known state. */ aml8726_sdxc_soft_reset(sc); sndr = cmd->opcode; if ((cmd->flags & MMC_RSP_136) != 0) { sndr |= AML_SDXC_SEND_CMD_HAS_RESP; sndr |= AML_SDXC_SEND_RESP_136; /* * According to the SD spec the 136 bit response is * used for getting the CID or CSD in which case the * CRC7 is embedded in the contents rather than being * calculated over the entire response (the controller * always checks the CRC7 over the entire response). */ sndr |= AML_SDXC_SEND_RESP_NO_CRC7_CHECK; } else if ((cmd->flags & MMC_RSP_PRESENT) != 0) sndr |= AML_SDXC_SEND_CMD_HAS_RESP; if ((cmd->flags & MMC_RSP_CRC) == 0) sndr |= AML_SDXC_SEND_RESP_NO_CRC7_CHECK; if (cmd->opcode == MMC_STOP_TRANSMISSION) sndr |= AML_SDXC_SEND_DATA_STOP; data = cmd->data; baddr = 0; ctlr = CSR_READ_4(sc, AML_SDXC_CNTRL_REG); ier = AML_SDXC_IRQ_ENABLE_STANDARD; timeout = AML_SDXC_CMD_TIMEOUT; ctlr &= ~AML_SDXC_CNTRL_PKG_LEN_MASK; if (data && data->len && (data->flags & (MMC_DATA_READ | MMC_DATA_WRITE)) != 0) { block_size = data->len; if ((data->flags & MMC_DATA_MULTI) != 0) { block_size = MMC_SECTOR_SIZE; if ((data->len % block_size) != 0) return (MMC_ERR_INVALID); } if (block_size > 512) return (MMC_ERR_INVALID); sndr |= AML_SDXC_SEND_CMD_HAS_DATA; sndr |= ((data->flags & MMC_DATA_WRITE) != 0) ? AML_SDXC_SEND_DATA_WRITE : 0; sndr |= (((data->len / block_size) - 1) << AML_SDXC_SEND_REP_PKG_CNT_SHIFT); ctlr |= ((block_size < 512) ? block_size : 0) << AML_SDXC_CNTRL_PKG_LEN_SHIFT; ier &= ~AML_SDXC_IRQ_ENABLE_RESP_OK; ier |= (sc->auto_fill_flush == true || (data->flags & MMC_DATA_WRITE) != 0) ? AML_SDXC_IRQ_ENABLE_DMA_DONE : AML_SDXC_IRQ_ENABLE_TRANSFER_DONE_OK; error = bus_dmamap_load(sc->dmatag, sc->dmamap, data->data, data->len, aml8726_sdxc_mapmem, &baddr, BUS_DMA_NOWAIT); if (error) return (MMC_ERR_NO_MEMORY); if ((data->flags & MMC_DATA_READ) != 0) { bus_dmamap_sync(sc->dmatag, sc->dmamap, BUS_DMASYNC_PREREAD); timeout = AML_SDXC_READ_TIMEOUT * (data->len / block_size); } else { bus_dmamap_sync(sc->dmatag, sc->dmamap, BUS_DMASYNC_PREWRITE); timeout = AML_SDXC_WRITE_TIMEOUT * (data->len / block_size); } } sc->cmd = cmd; cmd->error = MMC_ERR_NONE; sc->busy.time = 0; sc->busy.error = MMC_ERR_NONE; if (timeout > AML_SDXC_MAX_TIMEOUT) timeout = AML_SDXC_MAX_TIMEOUT; callout_reset(&sc->ch, msecs_to_ticks(timeout), aml8726_sdxc_timeout, sc); CSR_WRITE_4(sc, AML_SDXC_IRQ_ENABLE_REG, ier); CSR_WRITE_4(sc, AML_SDXC_CNTRL_REG, ctlr); CSR_WRITE_4(sc, AML_SDXC_DMA_ADDR_REG, (uint32_t)baddr); CSR_WRITE_4(sc, AML_SDXC_CMD_ARGUMENT_REG, cmd->arg); aml8726_sdxc_engage_dma(sc); CSR_WRITE_4(sc, AML_SDXC_SEND_REG, sndr); CSR_BARRIER(sc, AML_SDXC_SEND_REG); return (MMC_ERR_NONE); } static void aml8726_sdxc_finish_command(struct aml8726_sdxc_softc *sc, int mmc_error) { int mmc_stop_error; struct mmc_command *cmd; struct mmc_command *stop_cmd; struct mmc_data *data; AML_SDXC_LOCK_ASSERT(sc); /* Clear all interrupts since the request is no longer in flight. */ CSR_WRITE_4(sc, AML_SDXC_IRQ_STATUS_REG, AML_SDXC_IRQ_STATUS_CLEAR); CSR_BARRIER(sc, AML_SDXC_IRQ_STATUS_REG); /* In some cases (e.g. finish called via timeout) this is a NOP. */ callout_stop(&sc->ch); cmd = sc->cmd; sc->cmd = NULL; cmd->error = mmc_error; data = cmd->data; if (data && data->len && (data->flags & (MMC_DATA_READ | MMC_DATA_WRITE)) != 0) { if ((data->flags & MMC_DATA_READ) != 0) bus_dmamap_sync(sc->dmatag, sc->dmamap, BUS_DMASYNC_POSTREAD); else bus_dmamap_sync(sc->dmatag, sc->dmamap, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->dmatag, sc->dmamap); } /* * If there's a linked stop command, then start the stop command. * In order to establish a known state attempt the stop command * even if the original request encountered an error. */ stop_cmd = (cmd->mrq->stop != cmd) ? cmd->mrq->stop : NULL; if (stop_cmd != NULL) { /* * If the original command executed successfully, then * the hardware will also have automatically executed * a stop command so don't bother with the one supplied * with the original request. */ if (mmc_error == MMC_ERR_NONE) { stop_cmd->error = MMC_ERR_NONE; stop_cmd->resp[0] = cmd->resp[0]; stop_cmd->resp[1] = cmd->resp[1]; stop_cmd->resp[2] = cmd->resp[2]; stop_cmd->resp[3] = cmd->resp[3]; } else { mmc_stop_error = aml8726_sdxc_start_command(sc, stop_cmd); if (mmc_stop_error == MMC_ERR_NONE) { AML_SDXC_UNLOCK(sc); return; } stop_cmd->error = mmc_stop_error; } } AML_SDXC_UNLOCK(sc); /* Execute the callback after dropping the lock. */ if (cmd->mrq != NULL) cmd->mrq->done(cmd->mrq); } static void aml8726_sdxc_timeout(void *arg) { struct aml8726_sdxc_softc *sc = (struct aml8726_sdxc_softc *)arg; /* * The command failed to complete in time so forcefully * terminate it. */ aml8726_sdxc_soft_reset(sc); /* * Ensure the command has terminated before continuing on * to things such as bus_dmamap_sync / bus_dmamap_unload. */ while ((CSR_READ_4(sc, AML_SDXC_STATUS_REG) & AML_SDXC_STATUS_BUSY) != 0) cpu_spinwait(); aml8726_sdxc_finish_command(sc, MMC_ERR_TIMEOUT); } static void aml8726_sdxc_busy_check(void *arg) { struct aml8726_sdxc_softc *sc = (struct aml8726_sdxc_softc *)arg; uint32_t sr; sc->busy.time += AML_SDXC_BUSY_POLL_INTVL; sr = CSR_READ_4(sc, AML_SDXC_STATUS_REG); if ((sr & AML_SDXC_STATUS_DAT0) == 0) { if (sc->busy.time < AML_SDXC_BUSY_TIMEOUT) { callout_reset(&sc->ch, msecs_to_ticks(AML_SDXC_BUSY_POLL_INTVL), aml8726_sdxc_busy_check, sc); AML_SDXC_UNLOCK(sc); return; } if (sc->busy.error == MMC_ERR_NONE) sc->busy.error = MMC_ERR_TIMEOUT; } aml8726_sdxc_finish_command(sc, sc->busy.error); } static void aml8726_sdxc_intr(void *arg) { struct aml8726_sdxc_softc *sc = (struct aml8726_sdxc_softc *)arg; uint32_t isr; uint32_t pdmar; uint32_t sndr; uint32_t sr; int i; int mmc_error; int start; int stop; AML_SDXC_LOCK(sc); isr = CSR_READ_4(sc, AML_SDXC_IRQ_STATUS_REG); sndr = CSR_READ_4(sc, AML_SDXC_SEND_REG); sr = CSR_READ_4(sc, AML_SDXC_STATUS_REG); if (sc->cmd == NULL) goto spurious; mmc_error = MMC_ERR_NONE; if ((isr & (AML_SDXC_IRQ_STATUS_TX_FIFO_EMPTY | AML_SDXC_IRQ_STATUS_RX_FIFO_FULL)) != 0) mmc_error = MMC_ERR_FIFO; else if ((isr & (AML_SDXC_IRQ_ENABLE_A_PKG_CRC_ERR | AML_SDXC_IRQ_ENABLE_RESP_CRC_ERR)) != 0) mmc_error = MMC_ERR_BADCRC; else if ((isr & (AML_SDXC_IRQ_ENABLE_A_PKG_TIMEOUT_ERR | AML_SDXC_IRQ_ENABLE_RESP_TIMEOUT_ERR)) != 0) mmc_error = MMC_ERR_TIMEOUT; else if ((isr & (AML_SDXC_IRQ_STATUS_RESP_OK | AML_SDXC_IRQ_STATUS_DMA_DONE | AML_SDXC_IRQ_STATUS_TRANSFER_DONE_OK)) != 0) { ; } else { spurious: /* * Clear spurious interrupts while leaving intacted any * interrupts that may have occurred after we read the * interrupt status register. */ CSR_WRITE_4(sc, AML_SDXC_IRQ_STATUS_REG, (AML_SDXC_IRQ_STATUS_CLEAR & isr)); CSR_BARRIER(sc, AML_SDXC_IRQ_STATUS_REG); AML_SDXC_UNLOCK(sc); return; } aml8726_sdxc_disengage_dma(sc); if ((sndr & AML_SDXC_SEND_CMD_HAS_RESP) != 0) { start = 0; stop = 1; if ((sndr & AML_SDXC_SEND_RESP_136) != 0) { start = 1; stop = start + 4; } for (i = start; i < stop; i++) { pdmar = CSR_READ_4(sc, AML_SDXC_PDMA_REG); pdmar &= ~(AML_SDXC_PDMA_DMA_EN | AML_SDXC_PDMA_RESP_INDEX_MASK); pdmar |= i << AML_SDXC_PDMA_RESP_INDEX_SHIFT; CSR_WRITE_4(sc, AML_SDXC_PDMA_REG, pdmar); sc->cmd->resp[(stop - 1) - i] = CSR_READ_4(sc, AML_SDXC_CMD_ARGUMENT_REG); } } if ((sr & AML_SDXC_STATUS_BUSY) != 0 && /* * A multiblock operation may keep the hardware * busy until stop transmission is executed. */ (isr & (AML_SDXC_IRQ_STATUS_DMA_DONE | AML_SDXC_IRQ_STATUS_TRANSFER_DONE_OK)) == 0) { if (mmc_error == MMC_ERR_NONE) mmc_error = MMC_ERR_FAILED; /* * Issue a soft reset to terminate the command. * * Ensure the command has terminated before continuing on * to things such as bus_dmamap_sync / bus_dmamap_unload. */ aml8726_sdxc_soft_reset(sc); while ((CSR_READ_4(sc, AML_SDXC_STATUS_REG) & AML_SDXC_STATUS_BUSY) != 0) cpu_spinwait(); } /* * The stop command can be generated either manually or * automatically by the hardware if MISC_MANUAL_STOP_MODE * has not been set. In either case check for busy. */ if (((sc->cmd->flags & MMC_RSP_BUSY) != 0 || (sndr & AML_SDXC_SEND_INDEX_MASK) == MMC_STOP_TRANSMISSION) && (sr & AML_SDXC_STATUS_DAT0) == 0) { sc->busy.error = mmc_error; callout_reset(&sc->ch, msecs_to_ticks(AML_SDXC_BUSY_POLL_INTVL), aml8726_sdxc_busy_check, sc); CSR_WRITE_4(sc, AML_SDXC_IRQ_STATUS_REG, (AML_SDXC_IRQ_STATUS_CLEAR & isr)); CSR_BARRIER(sc, AML_SDXC_IRQ_STATUS_REG); AML_SDXC_UNLOCK(sc); return; } aml8726_sdxc_finish_command(sc, mmc_error); } static int aml8726_sdxc_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "amlogic,aml8726-sdxc-m8")) return (ENXIO); device_set_desc(dev, "Amlogic aml8726-m8 SDXC"); return (BUS_PROBE_DEFAULT); } static int aml8726_sdxc_attach(device_t dev) { struct aml8726_sdxc_softc *sc = device_get_softc(dev); char *voltages; char *voltage; int error; int nvoltages; pcell_t prop[3]; phandle_t node; ssize_t len; device_t child; uint32_t ectlr; uint32_t miscr; uint32_t pdmar; sc->dev = dev; sc->auto_fill_flush = false; pdmar = AML_SDXC_PDMA_DMA_URGENT | (49 << AML_SDXC_PDMA_TX_THOLD_SHIFT) | (7 << AML_SDXC_PDMA_RX_THOLD_SHIFT) | (15 << AML_SDXC_PDMA_RD_BURST_SHIFT) | (7 << AML_SDXC_PDMA_WR_BURST_SHIFT); miscr = (2 << AML_SDXC_MISC_WCRC_OK_PAT_SHIFT) | (5 << AML_SDXC_MISC_WCRC_ERR_PAT_SHIFT); ectlr = (12 << AML_SDXC_ENH_CNTRL_SDIO_IRQ_PERIOD_SHIFT); /* * Certain bitfields are dependent on the hardware revision. */ switch (aml8726_soc_hw_rev) { case AML_SOC_HW_REV_M8: switch (aml8726_soc_metal_rev) { case AML_SOC_M8_METAL_REV_M2_A: sc->auto_fill_flush = true; miscr |= (6 << AML_SDXC_MISC_TXSTART_THOLD_SHIFT); ectlr |= (64 << AML_SDXC_ENH_CNTRL_RX_FULL_THOLD_SHIFT) | AML_SDXC_ENH_CNTRL_WR_RESP_MODE_SKIP_M8M2; break; default: miscr |= (7 << AML_SDXC_MISC_TXSTART_THOLD_SHIFT); ectlr |= (63 << AML_SDXC_ENH_CNTRL_RX_FULL_THOLD_SHIFT) | AML_SDXC_ENH_CNTRL_DMA_NO_WR_RESP_CHECK_M8 | (255 << AML_SDXC_ENH_CNTRL_RX_TIMEOUT_SHIFT_M8); break; } break; case AML_SOC_HW_REV_M8B: miscr |= (7 << AML_SDXC_MISC_TXSTART_THOLD_SHIFT); ectlr |= (63 << AML_SDXC_ENH_CNTRL_RX_FULL_THOLD_SHIFT) | AML_SDXC_ENH_CNTRL_DMA_NO_WR_RESP_CHECK_M8 | (255 << AML_SDXC_ENH_CNTRL_RX_TIMEOUT_SHIFT_M8); break; default: device_printf(dev, "unsupported SoC\n"); return (ENXIO); /* NOTREACHED */ } node = ofw_bus_get_node(dev); len = OF_getencprop(node, "clock-frequency", prop, sizeof(prop)); if ((len / sizeof(prop[0])) != 1 || prop[0] == 0) { device_printf(dev, "missing clock-frequency attribute in FDT\n"); return (ENXIO); } sc->ref_freq = prop[0]; sc->pwr_en.dev = NULL; len = OF_getencprop(node, "mmc-pwr-en", prop, sizeof(prop)); if (len > 0) { if ((len / sizeof(prop[0])) == 3) { sc->pwr_en.dev = OF_device_from_xref(prop[0]); sc->pwr_en.pin = prop[1]; sc->pwr_en.pol = prop[2]; } if (sc->pwr_en.dev == NULL) { device_printf(dev, "unable to process mmc-pwr-en attribute in FDT\n"); return (ENXIO); } /* Turn off power and then configure the output driver. */ if (aml8726_sdxc_power_off(sc) != 0 || GPIO_PIN_SETFLAGS(sc->pwr_en.dev, sc->pwr_en.pin, GPIO_PIN_OUTPUT) != 0) { device_printf(dev, "could not use gpio to control power\n"); return (ENXIO); } } len = OF_getprop_alloc(node, "mmc-voltages", - sizeof(char), (void **)&voltages); + (void **)&voltages); if (len < 0) { device_printf(dev, "missing mmc-voltages attribute in FDT\n"); return (ENXIO); } sc->voltages[0] = 0; sc->voltages[1] = 0; voltage = voltages; nvoltages = 0; while (len && nvoltages < 2) { if (strncmp("1.8", voltage, len) == 0) sc->voltages[nvoltages] = MMC_OCR_LOW_VOLTAGE; else if (strncmp("3.3", voltage, len) == 0) sc->voltages[nvoltages] = MMC_OCR_320_330 | MMC_OCR_330_340; else { device_printf(dev, "unknown voltage attribute %.*s in FDT\n", len, voltage); OF_prop_free(voltages); return (ENXIO); } nvoltages++; /* queue up next string */ while (*voltage && len) { voltage++; len--; } if (len) { voltage++; len--; } } OF_prop_free(voltages); sc->vselect.dev = NULL; len = OF_getencprop(node, "mmc-vselect", prop, sizeof(prop)); if (len > 0) { if ((len / sizeof(prop[0])) == 2) { sc->vselect.dev = OF_device_from_xref(prop[0]); sc->vselect.pin = prop[1]; sc->vselect.pol = 1; } if (sc->vselect.dev == NULL) { device_printf(dev, "unable to process mmc-vselect attribute in FDT\n"); return (ENXIO); } /* * With the power off select voltage 0 and then * configure the output driver. */ if (GPIO_PIN_SET(sc->vselect.dev, sc->vselect.pin, 0) != 0 || GPIO_PIN_SETFLAGS(sc->vselect.dev, sc->vselect.pin, GPIO_PIN_OUTPUT) != 0) { device_printf(dev, "could not use gpio to set voltage\n"); return (ENXIO); } } if (nvoltages == 0) { device_printf(dev, "no voltages in FDT\n"); return (ENXIO); } else if (nvoltages == 1 && sc->vselect.dev != NULL) { device_printf(dev, "only one voltage in FDT\n"); return (ENXIO); } else if (nvoltages == 2 && sc->vselect.dev == NULL) { device_printf(dev, "too many voltages in FDT\n"); return (ENXIO); } sc->card_rst.dev = NULL; len = OF_getencprop(node, "mmc-rst", prop, sizeof(prop)); if (len > 0) { if ((len / sizeof(prop[0])) == 3) { sc->card_rst.dev = OF_device_from_xref(prop[0]); sc->card_rst.pin = prop[1]; sc->card_rst.pol = prop[2]; } if (sc->card_rst.dev == NULL) { device_printf(dev, "unable to process mmc-rst attribute in FDT\n"); return (ENXIO); } } if (bus_alloc_resources(dev, aml8726_sdxc_spec, sc->res)) { device_printf(dev, "could not allocate resources for device\n"); return (ENXIO); } AML_SDXC_LOCK_INIT(sc); error = bus_dma_tag_create(bus_get_dma_tag(dev), AML_SDXC_ALIGN_DMA, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, AML_SDXC_MAX_DMA, 1, AML_SDXC_MAX_DMA, 0, NULL, NULL, &sc->dmatag); if (error) goto fail; error = bus_dmamap_create(sc->dmatag, 0, &sc->dmamap); if (error) goto fail; error = bus_setup_intr(dev, sc->res[1], INTR_TYPE_MISC | INTR_MPSAFE, NULL, aml8726_sdxc_intr, sc, &sc->ih_cookie); if (error) { device_printf(dev, "could not setup interrupt handler\n"); goto fail; } callout_init_mtx(&sc->ch, &sc->mtx, CALLOUT_RETURNUNLOCKED); sc->host.f_min = 200000; sc->host.f_max = 100000000; sc->host.host_ocr = sc->voltages[0] | sc->voltages[1]; sc->host.caps = MMC_CAP_8_BIT_DATA | MMC_CAP_4_BIT_DATA | MMC_CAP_HSPEED; aml8726_sdxc_soft_reset(sc); CSR_WRITE_4(sc, AML_SDXC_PDMA_REG, pdmar); CSR_WRITE_4(sc, AML_SDXC_MISC_REG, miscr); CSR_WRITE_4(sc, AML_SDXC_ENH_CNTRL_REG, ectlr); child = device_add_child(dev, "mmc", -1); if (!child) { device_printf(dev, "could not add mmc\n"); error = ENXIO; goto fail; } error = device_probe_and_attach(child); if (error) { device_printf(dev, "could not attach mmc\n"); goto fail; } return (0); fail: if (sc->ih_cookie) bus_teardown_intr(dev, sc->res[1], sc->ih_cookie); if (sc->dmamap) bus_dmamap_destroy(sc->dmatag, sc->dmamap); if (sc->dmatag) bus_dma_tag_destroy(sc->dmatag); AML_SDXC_LOCK_DESTROY(sc); (void)aml8726_sdxc_power_off(sc); bus_release_resources(dev, aml8726_sdxc_spec, sc->res); return (error); } static int aml8726_sdxc_detach(device_t dev) { struct aml8726_sdxc_softc *sc = device_get_softc(dev); AML_SDXC_LOCK(sc); if (sc->cmd != NULL) { AML_SDXC_UNLOCK(sc); return (EBUSY); } /* * Turn off the power, reset the hardware state machine, * and disable the interrupts. */ aml8726_sdxc_power_off(sc); aml8726_sdxc_soft_reset(sc); CSR_WRITE_4(sc, AML_SDXC_IRQ_ENABLE_REG, 0); AML_SDXC_UNLOCK(sc); bus_generic_detach(dev); bus_teardown_intr(dev, sc->res[1], sc->ih_cookie); bus_dmamap_destroy(sc->dmatag, sc->dmamap); bus_dma_tag_destroy(sc->dmatag); AML_SDXC_LOCK_DESTROY(sc); bus_release_resources(dev, aml8726_sdxc_spec, sc->res); return (0); } static int aml8726_sdxc_shutdown(device_t dev) { struct aml8726_sdxc_softc *sc = device_get_softc(dev); /* * Turn off the power, reset the hardware state machine, * and disable the interrupts. */ aml8726_sdxc_power_off(sc); aml8726_sdxc_soft_reset(sc); CSR_WRITE_4(sc, AML_SDXC_IRQ_ENABLE_REG, 0); return (0); } static int aml8726_sdxc_update_ios(device_t bus, device_t child) { struct aml8726_sdxc_softc *sc = device_get_softc(bus); struct mmc_ios *ios = &sc->host.ios; unsigned int divisor; int error; int i; uint32_t cctlr; uint32_t clk2r; uint32_t ctlr; uint32_t freq; ctlr = (7 << AML_SDXC_CNTRL_TX_ENDIAN_SHIFT) | (7 << AML_SDXC_CNTRL_RX_ENDIAN_SHIFT) | (0xf << AML_SDXC_CNTRL_RX_PERIOD_SHIFT) | (0x7f << AML_SDXC_CNTRL_RX_TIMEOUT_SHIFT); switch (ios->bus_width) { case bus_width_8: ctlr |= AML_SDXC_CNTRL_BUS_WIDTH_8; break; case bus_width_4: ctlr |= AML_SDXC_CNTRL_BUS_WIDTH_4; break; case bus_width_1: ctlr |= AML_SDXC_CNTRL_BUS_WIDTH_1; break; default: return (EINVAL); } CSR_WRITE_4(sc, AML_SDXC_CNTRL_REG, ctlr); /* * Disable clocks and then clock module prior to setting desired values. */ cctlr = CSR_READ_4(sc, AML_SDXC_CLK_CNTRL_REG); cctlr &= ~(AML_SDXC_CLK_CNTRL_TX_CLK_EN | AML_SDXC_CLK_CNTRL_RX_CLK_EN | AML_SDXC_CLK_CNTRL_SD_CLK_EN); CSR_WRITE_4(sc, AML_SDXC_CLK_CNTRL_REG, cctlr); CSR_BARRIER(sc, AML_SDXC_CLK_CNTRL_REG); cctlr &= ~AML_SDXC_CLK_CNTRL_CLK_MODULE_EN; CSR_WRITE_4(sc, AML_SDXC_CLK_CNTRL_REG, cctlr); CSR_BARRIER(sc, AML_SDXC_CLK_CNTRL_REG); /* * aml8726-m8 * * Clock select 1 fclk_div2 (1.275 GHz) */ cctlr &= ~AML_SDXC_CLK_CNTRL_CLK_SEL_MASK; cctlr |= (1 << AML_SDXC_CLK_CNTRL_CLK_SEL_SHIFT); divisor = sc->ref_freq / ios->clock - 1; if (divisor == 0 || divisor == -1) divisor = 1; if ((sc->ref_freq / (divisor + 1)) > ios->clock) divisor += 1; if (divisor > (AML_SDXC_CLK_CNTRL_CLK_DIV_MASK >> AML_SDXC_CLK_CNTRL_CLK_DIV_SHIFT)) divisor = AML_SDXC_CLK_CNTRL_CLK_DIV_MASK >> AML_SDXC_CLK_CNTRL_CLK_DIV_SHIFT; cctlr &= ~AML_SDXC_CLK_CNTRL_CLK_DIV_MASK; cctlr |= divisor << AML_SDXC_CLK_CNTRL_CLK_DIV_SHIFT; cctlr &= ~AML_SDXC_CLK_CNTRL_MEM_PWR_MASK; cctlr |= AML_SDXC_CLK_CNTRL_MEM_PWR_ON; CSR_WRITE_4(sc, AML_SDXC_CLK_CNTRL_REG, cctlr); CSR_BARRIER(sc, AML_SDXC_CLK_CNTRL_REG); /* * Enable clock module and then clocks after setting desired values. */ cctlr |= AML_SDXC_CLK_CNTRL_CLK_MODULE_EN; CSR_WRITE_4(sc, AML_SDXC_CLK_CNTRL_REG, cctlr); CSR_BARRIER(sc, AML_SDXC_CLK_CNTRL_REG); cctlr |= AML_SDXC_CLK_CNTRL_TX_CLK_EN | AML_SDXC_CLK_CNTRL_RX_CLK_EN | AML_SDXC_CLK_CNTRL_SD_CLK_EN; CSR_WRITE_4(sc, AML_SDXC_CLK_CNTRL_REG, cctlr); CSR_BARRIER(sc, AML_SDXC_CLK_CNTRL_REG); freq = sc->ref_freq / divisor; for (i = 0; aml8726_sdxc_clk_phases[i].voltage; i++) { if ((aml8726_sdxc_clk_phases[i].voltage & (1 << ios->vdd)) != 0 && freq > aml8726_sdxc_clk_phases[i].freq) break; if (aml8726_sdxc_clk_phases[i].freq == 0) break; } clk2r = (1 << AML_SDXC_CLK2_SD_PHASE_SHIFT) | (aml8726_sdxc_clk_phases[i].rx_phase << AML_SDXC_CLK2_RX_PHASE_SHIFT); CSR_WRITE_4(sc, AML_SDXC_CLK2_REG, clk2r); CSR_BARRIER(sc, AML_SDXC_CLK2_REG); error = 0; switch (ios->power_mode) { case power_up: /* * Configure and power on the regulator so that the * voltage stabilizes prior to powering on the card. */ if (sc->vselect.dev != NULL) { for (i = 0; i < 2; i++) if ((sc->voltages[i] & (1 << ios->vdd)) != 0) break; if (i >= 2) return (EINVAL); error = GPIO_PIN_SET(sc->vselect.dev, sc->vselect.pin, i); } break; case power_on: error = aml8726_sdxc_power_on(sc); if (error) break; if (sc->card_rst.dev != NULL) { if (GPIO_PIN_SET(sc->card_rst.dev, sc->card_rst.pin, PIN_ON_FLAG(sc->card_rst.pol)) != 0 || GPIO_PIN_SETFLAGS(sc->card_rst.dev, sc->card_rst.pin, GPIO_PIN_OUTPUT) != 0) error = ENXIO; DELAY(5); if (GPIO_PIN_SET(sc->card_rst.dev, sc->card_rst.pin, PIN_OFF_FLAG(sc->card_rst.pol)) != 0) error = ENXIO; DELAY(5); if (error) { device_printf(sc->dev, "could not use gpio to reset card\n"); break; } } break; case power_off: error = aml8726_sdxc_power_off(sc); break; default: return (EINVAL); } return (error); } static int aml8726_sdxc_request(device_t bus, device_t child, struct mmc_request *req) { struct aml8726_sdxc_softc *sc = device_get_softc(bus); int mmc_error; AML_SDXC_LOCK(sc); if (sc->cmd != NULL) { AML_SDXC_UNLOCK(sc); return (EBUSY); } mmc_error = aml8726_sdxc_start_command(sc, req->cmd); AML_SDXC_UNLOCK(sc); /* Execute the callback after dropping the lock. */ if (mmc_error != MMC_ERR_NONE) { req->cmd->error = mmc_error; req->done(req); } return (0); } static int aml8726_sdxc_read_ivar(device_t bus, device_t child, int which, uintptr_t *result) { struct aml8726_sdxc_softc *sc = device_get_softc(bus); switch (which) { case MMCBR_IVAR_BUS_MODE: *(int *)result = sc->host.ios.bus_mode; break; case MMCBR_IVAR_BUS_WIDTH: *(int *)result = sc->host.ios.bus_width; break; case MMCBR_IVAR_CHIP_SELECT: *(int *)result = sc->host.ios.chip_select; break; case MMCBR_IVAR_CLOCK: *(int *)result = sc->host.ios.clock; break; case MMCBR_IVAR_F_MIN: *(int *)result = sc->host.f_min; break; case MMCBR_IVAR_F_MAX: *(int *)result = sc->host.f_max; break; case MMCBR_IVAR_HOST_OCR: *(int *)result = sc->host.host_ocr; break; case MMCBR_IVAR_MODE: *(int *)result = sc->host.mode; break; case MMCBR_IVAR_OCR: *(int *)result = sc->host.ocr; break; case MMCBR_IVAR_POWER_MODE: *(int *)result = sc->host.ios.power_mode; break; case MMCBR_IVAR_VDD: *(int *)result = sc->host.ios.vdd; break; case MMCBR_IVAR_CAPS: *(int *)result = sc->host.caps; break; case MMCBR_IVAR_MAX_DATA: *(int *)result = AML_SDXC_MAX_DMA / MMC_SECTOR_SIZE; break; default: return (EINVAL); } return (0); } static int aml8726_sdxc_write_ivar(device_t bus, device_t child, int which, uintptr_t value) { struct aml8726_sdxc_softc *sc = device_get_softc(bus); switch (which) { case MMCBR_IVAR_BUS_MODE: sc->host.ios.bus_mode = value; break; case MMCBR_IVAR_BUS_WIDTH: sc->host.ios.bus_width = value; break; case MMCBR_IVAR_CHIP_SELECT: sc->host.ios.chip_select = value; break; case MMCBR_IVAR_CLOCK: sc->host.ios.clock = value; break; case MMCBR_IVAR_MODE: sc->host.mode = value; break; case MMCBR_IVAR_OCR: sc->host.ocr = value; break; case MMCBR_IVAR_POWER_MODE: sc->host.ios.power_mode = value; break; case MMCBR_IVAR_VDD: sc->host.ios.vdd = value; break; /* These are read-only */ case MMCBR_IVAR_CAPS: case MMCBR_IVAR_HOST_OCR: case MMCBR_IVAR_F_MIN: case MMCBR_IVAR_F_MAX: case MMCBR_IVAR_MAX_DATA: default: return (EINVAL); } return (0); } static int aml8726_sdxc_get_ro(device_t bus, device_t child) { return (0); } static int aml8726_sdxc_acquire_host(device_t bus, device_t child) { struct aml8726_sdxc_softc *sc = device_get_softc(bus); AML_SDXC_LOCK(sc); while (sc->bus_busy) mtx_sleep(sc, &sc->mtx, PZERO, "sdxc", hz / 5); sc->bus_busy++; AML_SDXC_UNLOCK(sc); return (0); } static int aml8726_sdxc_release_host(device_t bus, device_t child) { struct aml8726_sdxc_softc *sc = device_get_softc(bus); AML_SDXC_LOCK(sc); sc->bus_busy--; wakeup(sc); AML_SDXC_UNLOCK(sc); return (0); } static device_method_t aml8726_sdxc_methods[] = { /* Device interface */ DEVMETHOD(device_probe, aml8726_sdxc_probe), DEVMETHOD(device_attach, aml8726_sdxc_attach), DEVMETHOD(device_detach, aml8726_sdxc_detach), DEVMETHOD(device_shutdown, aml8726_sdxc_shutdown), /* Bus interface */ DEVMETHOD(bus_read_ivar, aml8726_sdxc_read_ivar), DEVMETHOD(bus_write_ivar, aml8726_sdxc_write_ivar), /* MMC bridge interface */ DEVMETHOD(mmcbr_update_ios, aml8726_sdxc_update_ios), DEVMETHOD(mmcbr_request, aml8726_sdxc_request), DEVMETHOD(mmcbr_get_ro, aml8726_sdxc_get_ro), DEVMETHOD(mmcbr_acquire_host, aml8726_sdxc_acquire_host), DEVMETHOD(mmcbr_release_host, aml8726_sdxc_release_host), DEVMETHOD_END }; static driver_t aml8726_sdxc_driver = { "aml8726_sdxc", aml8726_sdxc_methods, sizeof(struct aml8726_sdxc_softc), }; static devclass_t aml8726_sdxc_devclass; DRIVER_MODULE(aml8726_sdxc, simplebus, aml8726_sdxc_driver, aml8726_sdxc_devclass, NULL, NULL); MODULE_DEPEND(aml8726_sdxc, aml8726_gpio, 1, 1, 1); MMC_DECLARE_BRIDGE(aml8726_sdxc); Index: head/sys/arm/amlogic/aml8726/aml8726_usb_phy-m3.c =================================================================== --- head/sys/arm/amlogic/aml8726/aml8726_usb_phy-m3.c (revision 332309) +++ head/sys/arm/amlogic/aml8726/aml8726_usb_phy-m3.c (revision 332310) @@ -1,428 +1,428 @@ /*- * Copyright 2014-2015 John Wehle * 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. */ /* * Amlogic aml8726-m3 USB physical layer driver. * * Both USB physical interfaces share the same configuration register. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gpio_if.h" struct aml8726_usb_phy_gpio { device_t dev; uint32_t pin; uint32_t pol; }; struct aml8726_usb_phy_softc { device_t dev; struct resource *res[1]; uint32_t npwr_en; struct aml8726_usb_phy_gpio *pwr_en; }; static struct resource_spec aml8726_usb_phy_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { -1, 0 } }; #define AML_USB_PHY_CFG_REG 0 #define AML_USB_PHY_CFG_A_CLK_DETECTED (1U << 31) #define AML_USB_PHY_CFG_CLK_DIV_MASK (0x7f << 24) #define AML_USB_PHY_CFG_CLK_DIV_SHIFT 24 #define AML_USB_PHY_CFG_B_CLK_DETECTED (1 << 22) #define AML_USB_PHY_CFG_A_PLL_RST (1 << 19) #define AML_USB_PHY_CFG_A_PHYS_RST (1 << 18) #define AML_USB_PHY_CFG_A_RST (1 << 17) #define AML_USB_PHY_CFG_B_PLL_RST (1 << 13) #define AML_USB_PHY_CFG_B_PHYS_RST (1 << 12) #define AML_USB_PHY_CFG_B_RST (1 << 11) #define AML_USB_PHY_CFG_CLK_EN (1 << 8) #define AML_USB_PHY_CFG_CLK_SEL_MASK (7 << 5) #define AML_USB_PHY_CFG_CLK_SEL_XTAL (0 << 5) #define AML_USB_PHY_CFG_CLK_SEL_XTAL_DIV2 (1 << 5) #define AML_USB_PHY_CFG_B_POR (1 << 1) #define AML_USB_PHY_CFG_A_POR (1 << 0) #define AML_USB_PHY_CFG_CLK_DETECTED \ (AML_USB_PHY_CFG_A_CLK_DETECTED | AML_USB_PHY_CFG_B_CLK_DETECTED) #define AML_USB_PHY_MISC_A_REG 12 #define AML_USB_PHY_MISC_B_REG 16 #define AML_USB_PHY_MISC_ID_OVERIDE_EN (1 << 23) #define AML_USB_PHY_MISC_ID_OVERIDE_DEVICE (1 << 22) #define AML_USB_PHY_MISC_ID_OVERIDE_HOST (0 << 22) #define CSR_WRITE_4(sc, reg, val) bus_write_4((sc)->res[0], reg, (val)) #define CSR_READ_4(sc, reg) bus_read_4((sc)->res[0], reg) #define CSR_BARRIER(sc, reg) bus_barrier((sc)->res[0], reg, 4, \ (BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE)) #define PIN_ON_FLAG(pol) ((pol) == 0 ? \ GPIO_PIN_LOW : GPIO_PIN_HIGH) #define PIN_OFF_FLAG(pol) ((pol) == 0 ? \ GPIO_PIN_HIGH : GPIO_PIN_LOW) static int aml8726_usb_phy_mode(const char *dwcotg_path, uint32_t *mode) { char *usb_mode; phandle_t node; ssize_t len; if ((node = OF_finddevice(dwcotg_path)) == -1) return (ENXIO); if (fdt_is_compatible_strict(node, "synopsys,designware-hs-otg2") == 0) return (ENXIO); *mode = 0; len = OF_getprop_alloc(node, "dr_mode", - sizeof(char), (void **)&usb_mode); + (void **)&usb_mode); if (len <= 0) return (0); if (strcasecmp(usb_mode, "host") == 0) { *mode = AML_USB_PHY_MISC_ID_OVERIDE_EN | AML_USB_PHY_MISC_ID_OVERIDE_HOST; } else if (strcasecmp(usb_mode, "peripheral") == 0) { *mode = AML_USB_PHY_MISC_ID_OVERIDE_EN | AML_USB_PHY_MISC_ID_OVERIDE_DEVICE; } OF_prop_free(usb_mode); return (0); } static int aml8726_usb_phy_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "amlogic,aml8726-m3-usb-phy")) return (ENXIO); device_set_desc(dev, "Amlogic aml8726-m3 USB PHY"); return (BUS_PROBE_DEFAULT); } static int aml8726_usb_phy_attach(device_t dev) { struct aml8726_usb_phy_softc *sc = device_get_softc(dev); int err; int npwr_en; pcell_t *prop; phandle_t node; ssize_t len; uint32_t div; uint32_t i; uint32_t mode_a; uint32_t mode_b; uint32_t value; sc->dev = dev; if (aml8726_usb_phy_mode("/soc/usb@c9040000", &mode_a) != 0) { device_printf(dev, "missing usb@c9040000 node in FDT\n"); return (ENXIO); } if (aml8726_usb_phy_mode("/soc/usb@c90c0000", &mode_b) != 0) { device_printf(dev, "missing usb@c90c0000 node in FDT\n"); return (ENXIO); } if (bus_alloc_resources(dev, aml8726_usb_phy_spec, sc->res)) { device_printf(dev, "can not allocate resources for device\n"); return (ENXIO); } node = ofw_bus_get_node(dev); err = 0; len = OF_getencprop_alloc(node, "usb-pwr-en", 3 * sizeof(pcell_t), (void **)&prop); npwr_en = (len > 0) ? len : 0; sc->npwr_en = 0; sc->pwr_en = (struct aml8726_usb_phy_gpio *) malloc(npwr_en * sizeof (*sc->pwr_en), M_DEVBUF, M_WAITOK); for (i = 0; i < npwr_en; i++) { sc->pwr_en[i].dev = OF_device_from_xref(prop[i * 3]); sc->pwr_en[i].pin = prop[i * 3 + 1]; sc->pwr_en[i].pol = prop[i * 3 + 2]; if (sc->pwr_en[i].dev == NULL) { err = 1; break; } } OF_prop_free(prop); if (err) { device_printf(dev, "unable to parse gpio\n"); goto fail; } /* Turn on power by setting pin and then enabling output driver. */ for (i = 0; i < npwr_en; i++) { if (GPIO_PIN_SET(sc->pwr_en[i].dev, sc->pwr_en[i].pin, PIN_ON_FLAG(sc->pwr_en[i].pol)) != 0 || GPIO_PIN_SETFLAGS(sc->pwr_en[i].dev, sc->pwr_en[i].pin, GPIO_PIN_OUTPUT) != 0) { device_printf(dev, "could not use gpio to control power\n"); goto fail; } sc->npwr_en++; } /* * Configure the clock source and divider. */ div = 2; value = CSR_READ_4(sc, AML_USB_PHY_CFG_REG); value &= ~(AML_USB_PHY_CFG_CLK_DIV_MASK | AML_USB_PHY_CFG_CLK_SEL_MASK); value &= ~(AML_USB_PHY_CFG_A_RST | AML_USB_PHY_CFG_B_RST); value &= ~(AML_USB_PHY_CFG_A_PLL_RST | AML_USB_PHY_CFG_B_PLL_RST); value &= ~(AML_USB_PHY_CFG_A_PHYS_RST | AML_USB_PHY_CFG_B_PHYS_RST); value &= ~(AML_USB_PHY_CFG_A_POR | AML_USB_PHY_CFG_B_POR); value |= AML_USB_PHY_CFG_CLK_SEL_XTAL; value |= ((div - 1) << AML_USB_PHY_CFG_CLK_DIV_SHIFT) & AML_USB_PHY_CFG_CLK_DIV_MASK; value |= AML_USB_PHY_CFG_CLK_EN; CSR_WRITE_4(sc, AML_USB_PHY_CFG_REG, value); CSR_BARRIER(sc, AML_USB_PHY_CFG_REG); /* * Issue the reset sequence. */ value |= (AML_USB_PHY_CFG_A_RST | AML_USB_PHY_CFG_B_RST); CSR_WRITE_4(sc, AML_USB_PHY_CFG_REG, value); CSR_BARRIER(sc, AML_USB_PHY_CFG_REG); DELAY(200); value &= ~(AML_USB_PHY_CFG_A_RST | AML_USB_PHY_CFG_B_RST); CSR_WRITE_4(sc, AML_USB_PHY_CFG_REG, value); CSR_BARRIER(sc, AML_USB_PHY_CFG_REG); DELAY(200); value |= (AML_USB_PHY_CFG_A_PLL_RST | AML_USB_PHY_CFG_B_PLL_RST); CSR_WRITE_4(sc, AML_USB_PHY_CFG_REG, value); CSR_BARRIER(sc, AML_USB_PHY_CFG_REG); DELAY(200); value &= ~(AML_USB_PHY_CFG_A_PLL_RST | AML_USB_PHY_CFG_B_PLL_RST); CSR_WRITE_4(sc, AML_USB_PHY_CFG_REG, value); CSR_BARRIER(sc, AML_USB_PHY_CFG_REG); DELAY(200); value |= (AML_USB_PHY_CFG_A_PHYS_RST | AML_USB_PHY_CFG_B_PHYS_RST); CSR_WRITE_4(sc, AML_USB_PHY_CFG_REG, value); CSR_BARRIER(sc, AML_USB_PHY_CFG_REG); DELAY(200); value &= ~(AML_USB_PHY_CFG_A_PHYS_RST | AML_USB_PHY_CFG_B_PHYS_RST); CSR_WRITE_4(sc, AML_USB_PHY_CFG_REG, value); CSR_BARRIER(sc, AML_USB_PHY_CFG_REG); DELAY(200); value |= (AML_USB_PHY_CFG_A_POR | AML_USB_PHY_CFG_B_POR); CSR_WRITE_4(sc, AML_USB_PHY_CFG_REG, value); CSR_BARRIER(sc, AML_USB_PHY_CFG_REG); DELAY(200); /* * Enable by clearing the power on reset. */ value &= ~(AML_USB_PHY_CFG_A_POR | AML_USB_PHY_CFG_B_POR); CSR_WRITE_4(sc, AML_USB_PHY_CFG_REG, value); CSR_BARRIER(sc, AML_USB_PHY_CFG_REG); DELAY(200); /* * Check if the clock was detected. */ value = CSR_READ_4(sc, AML_USB_PHY_CFG_REG); if ((value & AML_USB_PHY_CFG_CLK_DETECTED) != AML_USB_PHY_CFG_CLK_DETECTED) device_printf(dev, "PHY Clock not detected\n"); /* * Configure the mode for each port. */ value = CSR_READ_4(sc, AML_USB_PHY_MISC_A_REG); value &= ~(AML_USB_PHY_MISC_ID_OVERIDE_EN | AML_USB_PHY_MISC_ID_OVERIDE_DEVICE | AML_USB_PHY_MISC_ID_OVERIDE_HOST); value |= mode_a; CSR_WRITE_4(sc, AML_USB_PHY_MISC_A_REG, value); value = CSR_READ_4(sc, AML_USB_PHY_MISC_B_REG); value &= ~(AML_USB_PHY_MISC_ID_OVERIDE_EN | AML_USB_PHY_MISC_ID_OVERIDE_DEVICE | AML_USB_PHY_MISC_ID_OVERIDE_HOST); value |= mode_b; CSR_WRITE_4(sc, AML_USB_PHY_MISC_B_REG, value); CSR_BARRIER(sc, AML_USB_PHY_MISC_B_REG); return (0); fail: /* In the event of problems attempt to turn things back off. */ i = sc->npwr_en; while (i-- != 0) { GPIO_PIN_SET(sc->pwr_en[i].dev, sc->pwr_en[i].pin, PIN_OFF_FLAG(sc->pwr_en[i].pol)); } free (sc->pwr_en, M_DEVBUF); sc->pwr_en = NULL; bus_release_resources(dev, aml8726_usb_phy_spec, sc->res); return (ENXIO); } static int aml8726_usb_phy_detach(device_t dev) { struct aml8726_usb_phy_softc *sc = device_get_softc(dev); uint32_t i; uint32_t value; /* * Disable by issuing a power on reset. */ value = CSR_READ_4(sc, AML_USB_PHY_CFG_REG); value |= (AML_USB_PHY_CFG_A_POR | AML_USB_PHY_CFG_B_POR); CSR_WRITE_4(sc, AML_USB_PHY_CFG_REG, value); CSR_BARRIER(sc, AML_USB_PHY_CFG_REG); /* Turn off power */ i = sc->npwr_en; while (i-- != 0) { (void)GPIO_PIN_SET(sc->pwr_en[i].dev, sc->pwr_en[i].pin, PIN_OFF_FLAG(sc->pwr_en[i].pol)); } free (sc->pwr_en, M_DEVBUF); sc->pwr_en = NULL; bus_release_resources(dev, aml8726_usb_phy_spec, sc->res); return (0); } static device_method_t aml8726_usb_phy_methods[] = { /* Device interface */ DEVMETHOD(device_probe, aml8726_usb_phy_probe), DEVMETHOD(device_attach, aml8726_usb_phy_attach), DEVMETHOD(device_detach, aml8726_usb_phy_detach), DEVMETHOD_END }; static driver_t aml8726_usb_phy_driver = { "usbphy", aml8726_usb_phy_methods, sizeof(struct aml8726_usb_phy_softc), }; static devclass_t aml8726_usb_phy_devclass; DRIVER_MODULE(aml8726_m3usbphy, simplebus, aml8726_usb_phy_driver, aml8726_usb_phy_devclass, 0, 0); MODULE_DEPEND(aml8726_m3usbphy, aml8726_gpio, 1, 1, 1); Index: head/sys/arm/amlogic/aml8726/aml8726_usb_phy-m6.c =================================================================== --- head/sys/arm/amlogic/aml8726/aml8726_usb_phy-m6.c (revision 332309) +++ head/sys/arm/amlogic/aml8726/aml8726_usb_phy-m6.c (revision 332310) @@ -1,418 +1,418 @@ /*- * Copyright 2014-2015 John Wehle * 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. */ /* * Amlogic aml8726-m6 (and later) USB physical layer driver. * * Each USB physical interface has a dedicated register block. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gpio_if.h" struct aml8726_usb_phy_gpio { device_t dev; uint32_t pin; uint32_t pol; }; struct aml8726_usb_phy_softc { device_t dev; struct resource *res[1]; uint32_t npwr_en; struct aml8726_usb_phy_gpio *pwr_en; boolean_t force_aca; struct aml8726_usb_phy_gpio hub_rst; }; static struct resource_spec aml8726_usb_phy_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { -1, 0 } }; #define AML_USB_PHY_CFG_REG 0 #define AML_USB_PHY_CFG_CLK_SEL_32K_ALT (1 << 15) #define AML_USB_PHY_CFG_CLK_DIV_MASK (0x7f << 4) #define AML_USB_PHY_CFG_CLK_DIV_SHIFT 4 #define AML_USB_PHY_CFG_CLK_SEL_MASK (7 << 1) #define AML_USB_PHY_CFG_CLK_SEL_XTAL (0 << 1) #define AML_USB_PHY_CFG_CLK_SEL_XTAL_DIV2 (1 << 1) #define AML_USB_PHY_CFG_CLK_EN (1 << 0) #define AML_USB_PHY_CTRL_REG 4 #define AML_USB_PHY_CTRL_FSEL_MASK (7 << 22) #define AML_USB_PHY_CTRL_FSEL_12M (2 << 22) #define AML_USB_PHY_CTRL_FSEL_24M (5 << 22) #define AML_USB_PHY_CTRL_POR (1 << 15) #define AML_USB_PHY_CTRL_CLK_DETECTED (1 << 8) #define AML_USB_PHY_ADP_BC_REG 12 #define AML_USB_PHY_ADP_BC_ACA_FLOATING (1 << 26) #define AML_USB_PHY_ADP_BC_ACA_EN (1 << 16) #define CSR_WRITE_4(sc, reg, val) bus_write_4((sc)->res[0], reg, (val)) #define CSR_READ_4(sc, reg) bus_read_4((sc)->res[0], reg) #define CSR_BARRIER(sc, reg) bus_barrier((sc)->res[0], reg, 4, \ (BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE)) #define PIN_ON_FLAG(pol) ((pol) == 0 ? \ GPIO_PIN_LOW : GPIO_PIN_HIGH) #define PIN_OFF_FLAG(pol) ((pol) == 0 ? \ GPIO_PIN_HIGH : GPIO_PIN_LOW) static int aml8726_usb_phy_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "amlogic,aml8726-m6-usb-phy") && !ofw_bus_is_compatible(dev, "amlogic,aml8726-m8-usb-phy")) return (ENXIO); switch (aml8726_soc_hw_rev) { case AML_SOC_HW_REV_M8: case AML_SOC_HW_REV_M8B: device_set_desc(dev, "Amlogic aml8726-m8 USB PHY"); break; default: device_set_desc(dev, "Amlogic aml8726-m6 USB PHY"); break; } return (BUS_PROBE_DEFAULT); } static int aml8726_usb_phy_attach(device_t dev) { struct aml8726_usb_phy_softc *sc = device_get_softc(dev); char *force_aca; int err; int npwr_en; pcell_t *prop; phandle_t node; ssize_t len; uint32_t div; uint32_t i; uint32_t value; sc->dev = dev; if (bus_alloc_resources(dev, aml8726_usb_phy_spec, sc->res)) { device_printf(dev, "can not allocate resources for device\n"); return (ENXIO); } node = ofw_bus_get_node(dev); len = OF_getprop_alloc(node, "force-aca", - sizeof(char), (void **)&force_aca); + (void **)&force_aca); sc->force_aca = FALSE; if (len > 0) { if (strncmp(force_aca, "true", len) == 0) sc->force_aca = TRUE; } OF_prop_free(force_aca); err = 0; len = OF_getencprop_alloc(node, "usb-pwr-en", 3 * sizeof(pcell_t), (void **)&prop); npwr_en = (len > 0) ? len : 0; sc->npwr_en = 0; sc->pwr_en = (struct aml8726_usb_phy_gpio *) malloc(npwr_en * sizeof (*sc->pwr_en), M_DEVBUF, M_WAITOK); for (i = 0; i < npwr_en; i++) { sc->pwr_en[i].dev = OF_device_from_xref(prop[i * 3]); sc->pwr_en[i].pin = prop[i * 3 + 1]; sc->pwr_en[i].pol = prop[i * 3 + 2]; if (sc->pwr_en[i].dev == NULL) { err = 1; break; } } OF_prop_free(prop); len = OF_getencprop_alloc(node, "usb-hub-rst", 3 * sizeof(pcell_t), (void **)&prop); if (len > 0) { sc->hub_rst.dev = OF_device_from_xref(prop[0]); sc->hub_rst.pin = prop[1]; sc->hub_rst.pol = prop[2]; if (len > 1 || sc->hub_rst.dev == NULL) err = 1; } OF_prop_free(prop); if (err) { device_printf(dev, "unable to parse gpio\n"); goto fail; } /* Turn on power by setting pin and then enabling output driver. */ for (i = 0; i < npwr_en; i++) { if (GPIO_PIN_SET(sc->pwr_en[i].dev, sc->pwr_en[i].pin, PIN_ON_FLAG(sc->pwr_en[i].pol)) != 0 || GPIO_PIN_SETFLAGS(sc->pwr_en[i].dev, sc->pwr_en[i].pin, GPIO_PIN_OUTPUT) != 0) { device_printf(dev, "could not use gpio to control power\n"); goto fail; } sc->npwr_en++; } /* * Configure the clock source and divider. */ value = CSR_READ_4(sc, AML_USB_PHY_CFG_REG); value &= ~(AML_USB_PHY_CFG_CLK_SEL_32K_ALT | AML_USB_PHY_CFG_CLK_DIV_MASK | AML_USB_PHY_CFG_CLK_SEL_MASK | AML_USB_PHY_CFG_CLK_EN); switch (aml8726_soc_hw_rev) { case AML_SOC_HW_REV_M8: case AML_SOC_HW_REV_M8B: value |= AML_USB_PHY_CFG_CLK_SEL_32K_ALT; break; default: div = 2; value |= AML_USB_PHY_CFG_CLK_SEL_XTAL; value |= ((div - 1) << AML_USB_PHY_CFG_CLK_DIV_SHIFT) & AML_USB_PHY_CFG_CLK_DIV_MASK; value |= AML_USB_PHY_CFG_CLK_EN; break; } CSR_WRITE_4(sc, AML_USB_PHY_CFG_REG, value); CSR_BARRIER(sc, AML_USB_PHY_CFG_REG); /* * Configure the clock frequency and issue a power on reset. */ value = CSR_READ_4(sc, AML_USB_PHY_CTRL_REG); value &= ~AML_USB_PHY_CTRL_FSEL_MASK; switch (aml8726_soc_hw_rev) { case AML_SOC_HW_REV_M8: case AML_SOC_HW_REV_M8B: value |= AML_USB_PHY_CTRL_FSEL_24M; break; default: value |= AML_USB_PHY_CTRL_FSEL_12M; break; } value |= AML_USB_PHY_CTRL_POR; CSR_WRITE_4(sc, AML_USB_PHY_CTRL_REG, value); CSR_BARRIER(sc, AML_USB_PHY_CTRL_REG); DELAY(500); /* * Enable by clearing the power on reset. */ value &= ~AML_USB_PHY_CTRL_POR; CSR_WRITE_4(sc, AML_USB_PHY_CTRL_REG, value); CSR_BARRIER(sc, AML_USB_PHY_CTRL_REG); DELAY(1000); /* * Check if the clock was detected. */ value = CSR_READ_4(sc, AML_USB_PHY_CTRL_REG); if ((value & AML_USB_PHY_CTRL_CLK_DETECTED) == 0) device_printf(dev, "PHY Clock not detected\n"); /* * If necessary enabled Accessory Charger Adaptor detection * so that the port knows what mode to operate in. */ if (sc->force_aca) { value = CSR_READ_4(sc, AML_USB_PHY_ADP_BC_REG); value |= AML_USB_PHY_ADP_BC_ACA_EN; CSR_WRITE_4(sc, AML_USB_PHY_ADP_BC_REG, value); CSR_BARRIER(sc, AML_USB_PHY_ADP_BC_REG); DELAY(50); value = CSR_READ_4(sc, AML_USB_PHY_ADP_BC_REG); if ((value & AML_USB_PHY_ADP_BC_ACA_FLOATING) != 0) { device_printf(dev, "force-aca requires newer silicon\n"); goto fail; } } /* * Reset the hub. */ if (sc->hub_rst.dev != NULL) { err = 0; if (GPIO_PIN_SET(sc->hub_rst.dev, sc->hub_rst.pin, PIN_ON_FLAG(sc->hub_rst.pol)) != 0 || GPIO_PIN_SETFLAGS(sc->hub_rst.dev, sc->hub_rst.pin, GPIO_PIN_OUTPUT) != 0) err = 1; DELAY(30); if (GPIO_PIN_SET(sc->hub_rst.dev, sc->hub_rst.pin, PIN_OFF_FLAG(sc->hub_rst.pol)) != 0) err = 1; DELAY(60000); if (err) { device_printf(dev, "could not use gpio to reset hub\n"); goto fail; } } return (0); fail: /* In the event of problems attempt to turn things back off. */ i = sc->npwr_en; while (i-- != 0) { GPIO_PIN_SET(sc->pwr_en[i].dev, sc->pwr_en[i].pin, PIN_OFF_FLAG(sc->pwr_en[i].pol)); } free (sc->pwr_en, M_DEVBUF); sc->pwr_en = NULL; bus_release_resources(dev, aml8726_usb_phy_spec, sc->res); return (ENXIO); } static int aml8726_usb_phy_detach(device_t dev) { struct aml8726_usb_phy_softc *sc = device_get_softc(dev); uint32_t i; uint32_t value; /* * Disable by issuing a power on reset. */ value = CSR_READ_4(sc, AML_USB_PHY_CTRL_REG); value |= AML_USB_PHY_CTRL_POR; CSR_WRITE_4(sc, AML_USB_PHY_CTRL_REG, value); CSR_BARRIER(sc, AML_USB_PHY_CTRL_REG); /* Turn off power */ i = sc->npwr_en; while (i-- != 0) { GPIO_PIN_SET(sc->pwr_en[i].dev, sc->pwr_en[i].pin, PIN_OFF_FLAG(sc->pwr_en[i].pol)); } free (sc->pwr_en, M_DEVBUF); sc->pwr_en = NULL; bus_release_resources(dev, aml8726_usb_phy_spec, sc->res); return (0); } static device_method_t aml8726_usb_phy_methods[] = { /* Device interface */ DEVMETHOD(device_probe, aml8726_usb_phy_probe), DEVMETHOD(device_attach, aml8726_usb_phy_attach), DEVMETHOD(device_detach, aml8726_usb_phy_detach), DEVMETHOD_END }; static driver_t aml8726_usb_phy_driver = { "usbphy", aml8726_usb_phy_methods, sizeof(struct aml8726_usb_phy_softc), }; static devclass_t aml8726_usb_phy_devclass; DRIVER_MODULE(aml8726_m6usbphy, simplebus, aml8726_usb_phy_driver, aml8726_usb_phy_devclass, 0, 0); MODULE_DEPEND(aml8726_m6usbphy, aml8726_gpio, 1, 1, 1); Index: head/sys/arm/broadcom/bcm2835/bcm2835_gpio.c =================================================================== --- head/sys/arm/broadcom/bcm2835/bcm2835_gpio.c (revision 332309) +++ head/sys/arm/broadcom/bcm2835/bcm2835_gpio.c (revision 332310) @@ -1,1324 +1,1324 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2012 Oleksandr Tymoshenko * Copyright (c) 2012-2015 Luiz Otavio O Souza * 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$"); #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gpio_if.h" #include "pic_if.h" #ifdef DEBUG #define dprintf(fmt, args...) do { printf("%s(): ", __func__); \ printf(fmt,##args); } while (0) #else #define dprintf(fmt, args...) #endif #define BCM_GPIO_IRQS 4 #define BCM_GPIO_PINS 54 #define BCM_GPIO_PINS_PER_BANK 32 #define BCM_GPIO_DEFAULT_CAPS (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | \ GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN | GPIO_INTR_LEVEL_LOW | \ GPIO_INTR_LEVEL_HIGH | GPIO_INTR_EDGE_RISING | \ GPIO_INTR_EDGE_FALLING | GPIO_INTR_EDGE_BOTH) #define BCM2835_FSEL_GPIO_IN 0 #define BCM2835_FSEL_GPIO_OUT 1 #define BCM2835_FSEL_ALT5 2 #define BCM2835_FSEL_ALT4 3 #define BCM2835_FSEL_ALT0 4 #define BCM2835_FSEL_ALT1 5 #define BCM2835_FSEL_ALT2 6 #define BCM2835_FSEL_ALT3 7 #define BCM2835_PUD_OFF 0 #define BCM2835_PUD_DOWN 1 #define BCM2835_PUD_UP 2 static struct resource_spec bcm_gpio_res_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE }, /* bank 0 interrupt */ { SYS_RES_IRQ, 1, RF_ACTIVE }, /* bank 1 interrupt */ { -1, 0, 0 } }; struct bcm_gpio_sysctl { struct bcm_gpio_softc *sc; uint32_t pin; }; struct bcm_gpio_irqsrc { struct intr_irqsrc bgi_isrc; uint32_t bgi_irq; uint32_t bgi_mode; uint32_t bgi_mask; }; struct bcm_gpio_softc { device_t sc_dev; device_t sc_busdev; struct mtx sc_mtx; struct resource * sc_res[BCM_GPIO_IRQS + 1]; bus_space_tag_t sc_bst; bus_space_handle_t sc_bsh; void * sc_intrhand[BCM_GPIO_IRQS]; int sc_gpio_npins; int sc_ro_npins; int sc_ro_pins[BCM_GPIO_PINS]; struct gpio_pin sc_gpio_pins[BCM_GPIO_PINS]; struct bcm_gpio_sysctl sc_sysctl[BCM_GPIO_PINS]; struct bcm_gpio_irqsrc sc_isrcs[BCM_GPIO_PINS]; }; enum bcm_gpio_pud { BCM_GPIO_NONE, BCM_GPIO_PULLDOWN, BCM_GPIO_PULLUP, }; #define BCM_GPIO_LOCK(_sc) mtx_lock_spin(&(_sc)->sc_mtx) #define BCM_GPIO_UNLOCK(_sc) mtx_unlock_spin(&(_sc)->sc_mtx) #define BCM_GPIO_LOCK_ASSERT(_sc) mtx_assert(&(_sc)->sc_mtx, MA_OWNED) #define BCM_GPIO_WRITE(_sc, _off, _val) \ bus_space_write_4((_sc)->sc_bst, (_sc)->sc_bsh, _off, _val) #define BCM_GPIO_READ(_sc, _off) \ bus_space_read_4((_sc)->sc_bst, (_sc)->sc_bsh, _off) #define BCM_GPIO_CLEAR_BITS(_sc, _off, _bits) \ BCM_GPIO_WRITE(_sc, _off, BCM_GPIO_READ(_sc, _off) & ~(_bits)) #define BCM_GPIO_SET_BITS(_sc, _off, _bits) \ BCM_GPIO_WRITE(_sc, _off, BCM_GPIO_READ(_sc, _off) | _bits) #define BCM_GPIO_BANK(a) (a / BCM_GPIO_PINS_PER_BANK) #define BCM_GPIO_MASK(a) (1U << (a % BCM_GPIO_PINS_PER_BANK)) #define BCM_GPIO_GPFSEL(_bank) (0x00 + _bank * 4) /* Function Select */ #define BCM_GPIO_GPSET(_bank) (0x1c + _bank * 4) /* Pin Out Set */ #define BCM_GPIO_GPCLR(_bank) (0x28 + _bank * 4) /* Pin Out Clear */ #define BCM_GPIO_GPLEV(_bank) (0x34 + _bank * 4) /* Pin Level */ #define BCM_GPIO_GPEDS(_bank) (0x40 + _bank * 4) /* Event Status */ #define BCM_GPIO_GPREN(_bank) (0x4c + _bank * 4) /* Rising Edge irq */ #define BCM_GPIO_GPFEN(_bank) (0x58 + _bank * 4) /* Falling Edge irq */ #define BCM_GPIO_GPHEN(_bank) (0x64 + _bank * 4) /* High Level irq */ #define BCM_GPIO_GPLEN(_bank) (0x70 + _bank * 4) /* Low Level irq */ #define BCM_GPIO_GPAREN(_bank) (0x7c + _bank * 4) /* Async Rising Edge */ #define BCM_GPIO_GPAFEN(_bank) (0x88 + _bank * 4) /* Async Falling Egde */ #define BCM_GPIO_GPPUD(_bank) (0x94) /* Pin Pull up/down */ #define BCM_GPIO_GPPUDCLK(_bank) (0x98 + _bank * 4) /* Pin Pull up clock */ static struct ofw_compat_data compat_data[] = { {"broadcom,bcm2835-gpio", 1}, {"brcm,bcm2835-gpio", 1}, {NULL, 0} }; static struct bcm_gpio_softc *bcm_gpio_sc = NULL; static int bcm_gpio_intr_bank0(void *arg); static int bcm_gpio_intr_bank1(void *arg); static int bcm_gpio_pic_attach(struct bcm_gpio_softc *sc); static int bcm_gpio_pic_detach(struct bcm_gpio_softc *sc); static int bcm_gpio_pin_is_ro(struct bcm_gpio_softc *sc, int pin) { int i; for (i = 0; i < sc->sc_ro_npins; i++) if (pin == sc->sc_ro_pins[i]) return (1); return (0); } static uint32_t bcm_gpio_get_function(struct bcm_gpio_softc *sc, uint32_t pin) { uint32_t bank, func, offset; /* Five banks, 10 pins per bank, 3 bits per pin. */ bank = pin / 10; offset = (pin - bank * 10) * 3; BCM_GPIO_LOCK(sc); func = (BCM_GPIO_READ(sc, BCM_GPIO_GPFSEL(bank)) >> offset) & 7; BCM_GPIO_UNLOCK(sc); return (func); } static void bcm_gpio_func_str(uint32_t nfunc, char *buf, int bufsize) { switch (nfunc) { case BCM2835_FSEL_GPIO_IN: strncpy(buf, "input", bufsize); break; case BCM2835_FSEL_GPIO_OUT: strncpy(buf, "output", bufsize); break; case BCM2835_FSEL_ALT0: strncpy(buf, "alt0", bufsize); break; case BCM2835_FSEL_ALT1: strncpy(buf, "alt1", bufsize); break; case BCM2835_FSEL_ALT2: strncpy(buf, "alt2", bufsize); break; case BCM2835_FSEL_ALT3: strncpy(buf, "alt3", bufsize); break; case BCM2835_FSEL_ALT4: strncpy(buf, "alt4", bufsize); break; case BCM2835_FSEL_ALT5: strncpy(buf, "alt5", bufsize); break; default: strncpy(buf, "invalid", bufsize); } } static int bcm_gpio_str_func(char *func, uint32_t *nfunc) { if (strcasecmp(func, "input") == 0) *nfunc = BCM2835_FSEL_GPIO_IN; else if (strcasecmp(func, "output") == 0) *nfunc = BCM2835_FSEL_GPIO_OUT; else if (strcasecmp(func, "alt0") == 0) *nfunc = BCM2835_FSEL_ALT0; else if (strcasecmp(func, "alt1") == 0) *nfunc = BCM2835_FSEL_ALT1; else if (strcasecmp(func, "alt2") == 0) *nfunc = BCM2835_FSEL_ALT2; else if (strcasecmp(func, "alt3") == 0) *nfunc = BCM2835_FSEL_ALT3; else if (strcasecmp(func, "alt4") == 0) *nfunc = BCM2835_FSEL_ALT4; else if (strcasecmp(func, "alt5") == 0) *nfunc = BCM2835_FSEL_ALT5; else return (-1); return (0); } static uint32_t bcm_gpio_func_flag(uint32_t nfunc) { switch (nfunc) { case BCM2835_FSEL_GPIO_IN: return (GPIO_PIN_INPUT); case BCM2835_FSEL_GPIO_OUT: return (GPIO_PIN_OUTPUT); } return (0); } static void bcm_gpio_set_function(struct bcm_gpio_softc *sc, uint32_t pin, uint32_t f) { uint32_t bank, data, offset; /* Must be called with lock held. */ BCM_GPIO_LOCK_ASSERT(sc); /* Five banks, 10 pins per bank, 3 bits per pin. */ bank = pin / 10; offset = (pin - bank * 10) * 3; data = BCM_GPIO_READ(sc, BCM_GPIO_GPFSEL(bank)); data &= ~(7 << offset); data |= (f << offset); BCM_GPIO_WRITE(sc, BCM_GPIO_GPFSEL(bank), data); } static void bcm_gpio_set_pud(struct bcm_gpio_softc *sc, uint32_t pin, uint32_t state) { uint32_t bank; /* Must be called with lock held. */ BCM_GPIO_LOCK_ASSERT(sc); bank = BCM_GPIO_BANK(pin); BCM_GPIO_WRITE(sc, BCM_GPIO_GPPUD(0), state); BCM_GPIO_WRITE(sc, BCM_GPIO_GPPUDCLK(bank), BCM_GPIO_MASK(pin)); BCM_GPIO_WRITE(sc, BCM_GPIO_GPPUD(0), 0); BCM_GPIO_WRITE(sc, BCM_GPIO_GPPUDCLK(bank), 0); } static void bcm_gpio_set_alternate(device_t dev, uint32_t pin, uint32_t nfunc) { struct bcm_gpio_softc *sc; int i; sc = device_get_softc(dev); BCM_GPIO_LOCK(sc); /* Set the pin function. */ bcm_gpio_set_function(sc, pin, nfunc); /* Update the pin flags. */ for (i = 0; i < sc->sc_gpio_npins; i++) { if (sc->sc_gpio_pins[i].gp_pin == pin) break; } if (i < sc->sc_gpio_npins) sc->sc_gpio_pins[i].gp_flags = bcm_gpio_func_flag(nfunc); BCM_GPIO_UNLOCK(sc); } static void bcm_gpio_pin_configure(struct bcm_gpio_softc *sc, struct gpio_pin *pin, unsigned int flags) { BCM_GPIO_LOCK(sc); /* * Manage input/output. */ if (flags & (GPIO_PIN_INPUT|GPIO_PIN_OUTPUT)) { pin->gp_flags &= ~(GPIO_PIN_INPUT|GPIO_PIN_OUTPUT); if (flags & GPIO_PIN_OUTPUT) { pin->gp_flags |= GPIO_PIN_OUTPUT; bcm_gpio_set_function(sc, pin->gp_pin, BCM2835_FSEL_GPIO_OUT); } else { pin->gp_flags |= GPIO_PIN_INPUT; bcm_gpio_set_function(sc, pin->gp_pin, BCM2835_FSEL_GPIO_IN); } } /* Manage Pull-up/pull-down. */ pin->gp_flags &= ~(GPIO_PIN_PULLUP|GPIO_PIN_PULLDOWN); if (flags & (GPIO_PIN_PULLUP|GPIO_PIN_PULLDOWN)) { if (flags & GPIO_PIN_PULLUP) { pin->gp_flags |= GPIO_PIN_PULLUP; bcm_gpio_set_pud(sc, pin->gp_pin, BCM_GPIO_PULLUP); } else { pin->gp_flags |= GPIO_PIN_PULLDOWN; bcm_gpio_set_pud(sc, pin->gp_pin, BCM_GPIO_PULLDOWN); } } else bcm_gpio_set_pud(sc, pin->gp_pin, BCM_GPIO_NONE); BCM_GPIO_UNLOCK(sc); } static device_t bcm_gpio_get_bus(device_t dev) { struct bcm_gpio_softc *sc; sc = device_get_softc(dev); return (sc->sc_busdev); } static int bcm_gpio_pin_max(device_t dev, int *maxpin) { *maxpin = BCM_GPIO_PINS - 1; return (0); } static int bcm_gpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) { struct bcm_gpio_softc *sc = device_get_softc(dev); int i; for (i = 0; i < sc->sc_gpio_npins; i++) { if (sc->sc_gpio_pins[i].gp_pin == pin) break; } if (i >= sc->sc_gpio_npins) return (EINVAL); BCM_GPIO_LOCK(sc); *caps = sc->sc_gpio_pins[i].gp_caps; BCM_GPIO_UNLOCK(sc); return (0); } static int bcm_gpio_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags) { struct bcm_gpio_softc *sc = device_get_softc(dev); int i; for (i = 0; i < sc->sc_gpio_npins; i++) { if (sc->sc_gpio_pins[i].gp_pin == pin) break; } if (i >= sc->sc_gpio_npins) return (EINVAL); BCM_GPIO_LOCK(sc); *flags = sc->sc_gpio_pins[i].gp_flags; BCM_GPIO_UNLOCK(sc); return (0); } static int bcm_gpio_pin_getname(device_t dev, uint32_t pin, char *name) { struct bcm_gpio_softc *sc = device_get_softc(dev); int i; for (i = 0; i < sc->sc_gpio_npins; i++) { if (sc->sc_gpio_pins[i].gp_pin == pin) break; } if (i >= sc->sc_gpio_npins) return (EINVAL); BCM_GPIO_LOCK(sc); memcpy(name, sc->sc_gpio_pins[i].gp_name, GPIOMAXNAME); BCM_GPIO_UNLOCK(sc); return (0); } static int bcm_gpio_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) { struct bcm_gpio_softc *sc = device_get_softc(dev); int i; for (i = 0; i < sc->sc_gpio_npins; i++) { if (sc->sc_gpio_pins[i].gp_pin == pin) break; } if (i >= sc->sc_gpio_npins) return (EINVAL); /* We never touch on read-only/reserved pins. */ if (bcm_gpio_pin_is_ro(sc, pin)) return (EINVAL); bcm_gpio_pin_configure(sc, &sc->sc_gpio_pins[i], flags); return (0); } static int bcm_gpio_pin_set(device_t dev, uint32_t pin, unsigned int value) { struct bcm_gpio_softc *sc = device_get_softc(dev); uint32_t bank, reg; int i; for (i = 0; i < sc->sc_gpio_npins; i++) { if (sc->sc_gpio_pins[i].gp_pin == pin) break; } if (i >= sc->sc_gpio_npins) return (EINVAL); /* We never write to read-only/reserved pins. */ if (bcm_gpio_pin_is_ro(sc, pin)) return (EINVAL); BCM_GPIO_LOCK(sc); bank = BCM_GPIO_BANK(pin); if (value) reg = BCM_GPIO_GPSET(bank); else reg = BCM_GPIO_GPCLR(bank); BCM_GPIO_WRITE(sc, reg, BCM_GPIO_MASK(pin)); BCM_GPIO_UNLOCK(sc); return (0); } static int bcm_gpio_pin_get(device_t dev, uint32_t pin, unsigned int *val) { struct bcm_gpio_softc *sc = device_get_softc(dev); uint32_t bank, reg_data; int i; for (i = 0; i < sc->sc_gpio_npins; i++) { if (sc->sc_gpio_pins[i].gp_pin == pin) break; } if (i >= sc->sc_gpio_npins) return (EINVAL); bank = BCM_GPIO_BANK(pin); BCM_GPIO_LOCK(sc); reg_data = BCM_GPIO_READ(sc, BCM_GPIO_GPLEV(bank)); BCM_GPIO_UNLOCK(sc); *val = (reg_data & BCM_GPIO_MASK(pin)) ? 1 : 0; return (0); } static int bcm_gpio_pin_toggle(device_t dev, uint32_t pin) { struct bcm_gpio_softc *sc = device_get_softc(dev); uint32_t bank, data, reg; int i; for (i = 0; i < sc->sc_gpio_npins; i++) { if (sc->sc_gpio_pins[i].gp_pin == pin) break; } if (i >= sc->sc_gpio_npins) return (EINVAL); /* We never write to read-only/reserved pins. */ if (bcm_gpio_pin_is_ro(sc, pin)) return (EINVAL); BCM_GPIO_LOCK(sc); bank = BCM_GPIO_BANK(pin); data = BCM_GPIO_READ(sc, BCM_GPIO_GPLEV(bank)); if (data & BCM_GPIO_MASK(pin)) reg = BCM_GPIO_GPCLR(bank); else reg = BCM_GPIO_GPSET(bank); BCM_GPIO_WRITE(sc, reg, BCM_GPIO_MASK(pin)); BCM_GPIO_UNLOCK(sc); return (0); } static int bcm_gpio_func_proc(SYSCTL_HANDLER_ARGS) { char buf[16]; struct bcm_gpio_softc *sc; struct bcm_gpio_sysctl *sc_sysctl; uint32_t nfunc; int error; sc_sysctl = arg1; sc = sc_sysctl->sc; /* Get the current pin function. */ nfunc = bcm_gpio_get_function(sc, sc_sysctl->pin); bcm_gpio_func_str(nfunc, buf, sizeof(buf)); error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); /* Ignore changes on read-only pins. */ if (bcm_gpio_pin_is_ro(sc, sc_sysctl->pin)) return (0); /* Parse the user supplied string and check for a valid pin function. */ if (bcm_gpio_str_func(buf, &nfunc) != 0) return (EINVAL); /* Update the pin alternate function. */ bcm_gpio_set_alternate(sc->sc_dev, sc_sysctl->pin, nfunc); return (0); } static void bcm_gpio_sysctl_init(struct bcm_gpio_softc *sc) { char pinbuf[3]; struct bcm_gpio_sysctl *sc_sysctl; struct sysctl_ctx_list *ctx; struct sysctl_oid *tree_node, *pin_node, *pinN_node; struct sysctl_oid_list *tree, *pin_tree, *pinN_tree; int i; /* * Add per-pin sysctl tree/handlers. */ ctx = device_get_sysctl_ctx(sc->sc_dev); tree_node = device_get_sysctl_tree(sc->sc_dev); tree = SYSCTL_CHILDREN(tree_node); pin_node = SYSCTL_ADD_NODE(ctx, tree, OID_AUTO, "pin", CTLFLAG_RD, NULL, "GPIO Pins"); pin_tree = SYSCTL_CHILDREN(pin_node); for (i = 0; i < sc->sc_gpio_npins; i++) { snprintf(pinbuf, sizeof(pinbuf), "%d", i); pinN_node = SYSCTL_ADD_NODE(ctx, pin_tree, OID_AUTO, pinbuf, CTLFLAG_RD, NULL, "GPIO Pin"); pinN_tree = SYSCTL_CHILDREN(pinN_node); sc->sc_sysctl[i].sc = sc; sc_sysctl = &sc->sc_sysctl[i]; sc_sysctl->sc = sc; sc_sysctl->pin = sc->sc_gpio_pins[i].gp_pin; SYSCTL_ADD_PROC(ctx, pinN_tree, OID_AUTO, "function", CTLFLAG_RW | CTLTYPE_STRING, sc_sysctl, sizeof(struct bcm_gpio_sysctl), bcm_gpio_func_proc, "A", "Pin Function"); } } static int bcm_gpio_get_ro_pins(struct bcm_gpio_softc *sc, phandle_t node, const char *propname, const char *label) { int i, need_comma, npins, range_start, range_stop; pcell_t *pins; /* Get the property data. */ npins = OF_getencprop_alloc(node, propname, sizeof(*pins), (void **)&pins); if (npins < 0) return (-1); if (npins == 0) { OF_prop_free(pins); return (0); } for (i = 0; i < npins; i++) sc->sc_ro_pins[i + sc->sc_ro_npins] = pins[i]; sc->sc_ro_npins += npins; need_comma = 0; device_printf(sc->sc_dev, "%s pins: ", label); range_start = range_stop = pins[0]; for (i = 1; i < npins; i++) { if (pins[i] != range_stop + 1) { if (need_comma) printf(","); if (range_start != range_stop) printf("%d-%d", range_start, range_stop); else printf("%d", range_start); range_start = range_stop = pins[i]; need_comma = 1; } else range_stop++; } if (need_comma) printf(","); if (range_start != range_stop) printf("%d-%d.\n", range_start, range_stop); else printf("%d.\n", range_start); OF_prop_free(pins); return (0); } static int bcm_gpio_get_reserved_pins(struct bcm_gpio_softc *sc) { char *name; phandle_t gpio, node, reserved; ssize_t len; /* Get read-only pins if they're provided */ gpio = ofw_bus_get_node(sc->sc_dev); if (bcm_gpio_get_ro_pins(sc, gpio, "broadcom,read-only", "read-only") != 0) return (0); /* Traverse the GPIO subnodes to find the reserved pins node. */ reserved = 0; node = OF_child(gpio); while ((node != 0) && (reserved == 0)) { - len = OF_getprop_alloc(node, "name", 1, (void **)&name); + len = OF_getprop_alloc(node, "name", (void **)&name); if (len == -1) return (-1); if (strcmp(name, "reserved") == 0) reserved = node; OF_prop_free(name); node = OF_peer(node); } if (reserved == 0) return (-1); /* Get the reserved pins. */ if (bcm_gpio_get_ro_pins(sc, reserved, "broadcom,pins", "reserved") != 0) return (-1); return (0); } static int bcm_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, "BCM2708/2835 GPIO controller"); return (BUS_PROBE_DEFAULT); } static int bcm_gpio_intr_attach(device_t dev) { struct bcm_gpio_softc *sc; /* * Only first two interrupt lines are used. Third line is * mirrored second line and forth line is common for all banks. */ sc = device_get_softc(dev); if (sc->sc_res[1] == NULL || sc->sc_res[2] == NULL) return (-1); if (bcm_gpio_pic_attach(sc) != 0) { device_printf(dev, "unable to attach PIC\n"); return (-1); } if (bus_setup_intr(dev, sc->sc_res[1], INTR_TYPE_MISC | INTR_MPSAFE, bcm_gpio_intr_bank0, NULL, sc, &sc->sc_intrhand[0]) != 0) return (-1); if (bus_setup_intr(dev, sc->sc_res[2], INTR_TYPE_MISC | INTR_MPSAFE, bcm_gpio_intr_bank1, NULL, sc, &sc->sc_intrhand[1]) != 0) return (-1); return (0); } static void bcm_gpio_intr_detach(device_t dev) { struct bcm_gpio_softc *sc; sc = device_get_softc(dev); if (sc->sc_intrhand[0] != NULL) bus_teardown_intr(dev, sc->sc_res[1], sc->sc_intrhand[0]); if (sc->sc_intrhand[1] != NULL) bus_teardown_intr(dev, sc->sc_res[2], sc->sc_intrhand[1]); bcm_gpio_pic_detach(sc); } static int bcm_gpio_attach(device_t dev) { int i, j; phandle_t gpio; struct bcm_gpio_softc *sc; uint32_t func; if (bcm_gpio_sc != NULL) return (ENXIO); bcm_gpio_sc = sc = device_get_softc(dev); sc->sc_dev = dev; mtx_init(&sc->sc_mtx, "bcm gpio", "gpio", MTX_SPIN); if (bus_alloc_resources(dev, bcm_gpio_res_spec, sc->sc_res) != 0) { device_printf(dev, "cannot allocate resources\n"); goto fail; } sc->sc_bst = rman_get_bustag(sc->sc_res[0]); sc->sc_bsh = rman_get_bushandle(sc->sc_res[0]); /* Setup the GPIO interrupt handler. */ if (bcm_gpio_intr_attach(dev)) { device_printf(dev, "unable to setup the gpio irq handler\n"); goto fail; } /* Find our node. */ gpio = ofw_bus_get_node(sc->sc_dev); if (!OF_hasprop(gpio, "gpio-controller")) /* Node is not a GPIO controller. */ goto fail; /* * Find the read-only pins. These are pins we never touch or bad * things could happen. */ if (bcm_gpio_get_reserved_pins(sc) == -1) goto fail; /* Initialize the software controlled pins. */ for (i = 0, j = 0; j < BCM_GPIO_PINS; j++) { snprintf(sc->sc_gpio_pins[i].gp_name, GPIOMAXNAME, "pin %d", j); func = bcm_gpio_get_function(sc, j); sc->sc_gpio_pins[i].gp_pin = j; sc->sc_gpio_pins[i].gp_caps = BCM_GPIO_DEFAULT_CAPS; sc->sc_gpio_pins[i].gp_flags = bcm_gpio_func_flag(func); i++; } sc->sc_gpio_npins = i; bcm_gpio_sysctl_init(sc); sc->sc_busdev = gpiobus_attach_bus(dev); if (sc->sc_busdev == NULL) goto fail; fdt_pinctrl_register(dev, "brcm,pins"); fdt_pinctrl_configure_tree(dev); return (0); fail: bcm_gpio_intr_detach(dev); bus_release_resources(dev, bcm_gpio_res_spec, sc->sc_res); mtx_destroy(&sc->sc_mtx); return (ENXIO); } static int bcm_gpio_detach(device_t dev) { return (EBUSY); } static inline void bcm_gpio_modify(struct bcm_gpio_softc *sc, uint32_t reg, uint32_t mask, bool set_bits) { if (set_bits) BCM_GPIO_SET_BITS(sc, reg, mask); else BCM_GPIO_CLEAR_BITS(sc, reg, mask); } static inline void bcm_gpio_isrc_eoi(struct bcm_gpio_softc *sc, struct bcm_gpio_irqsrc *bgi) { uint32_t bank; /* Write 1 to clear. */ bank = BCM_GPIO_BANK(bgi->bgi_irq); BCM_GPIO_WRITE(sc, BCM_GPIO_GPEDS(bank), bgi->bgi_mask); } static inline bool bcm_gpio_isrc_is_level(struct bcm_gpio_irqsrc *bgi) { return (bgi->bgi_mode == GPIO_INTR_LEVEL_LOW || bgi->bgi_mode == GPIO_INTR_LEVEL_HIGH); } static inline void bcm_gpio_isrc_mask(struct bcm_gpio_softc *sc, struct bcm_gpio_irqsrc *bgi) { uint32_t bank; bank = BCM_GPIO_BANK(bgi->bgi_irq); BCM_GPIO_LOCK(sc); switch (bgi->bgi_mode) { case GPIO_INTR_LEVEL_LOW: BCM_GPIO_CLEAR_BITS(sc, BCM_GPIO_GPLEN(bank), bgi->bgi_mask); break; case GPIO_INTR_LEVEL_HIGH: BCM_GPIO_CLEAR_BITS(sc, BCM_GPIO_GPHEN(bank), bgi->bgi_mask); break; case GPIO_INTR_EDGE_RISING: BCM_GPIO_CLEAR_BITS(sc, BCM_GPIO_GPREN(bank), bgi->bgi_mask); break; case GPIO_INTR_EDGE_FALLING: BCM_GPIO_CLEAR_BITS(sc, BCM_GPIO_GPFEN(bank), bgi->bgi_mask); break; case GPIO_INTR_EDGE_BOTH: BCM_GPIO_CLEAR_BITS(sc, BCM_GPIO_GPREN(bank), bgi->bgi_mask); BCM_GPIO_CLEAR_BITS(sc, BCM_GPIO_GPFEN(bank), bgi->bgi_mask); break; } BCM_GPIO_UNLOCK(sc); } static inline void bcm_gpio_isrc_unmask(struct bcm_gpio_softc *sc, struct bcm_gpio_irqsrc *bgi) { uint32_t bank; bank = BCM_GPIO_BANK(bgi->bgi_irq); BCM_GPIO_LOCK(sc); switch (bgi->bgi_mode) { case GPIO_INTR_LEVEL_LOW: BCM_GPIO_SET_BITS(sc, BCM_GPIO_GPLEN(bank), bgi->bgi_mask); break; case GPIO_INTR_LEVEL_HIGH: BCM_GPIO_SET_BITS(sc, BCM_GPIO_GPHEN(bank), bgi->bgi_mask); break; case GPIO_INTR_EDGE_RISING: BCM_GPIO_SET_BITS(sc, BCM_GPIO_GPREN(bank), bgi->bgi_mask); break; case GPIO_INTR_EDGE_FALLING: BCM_GPIO_SET_BITS(sc, BCM_GPIO_GPFEN(bank), bgi->bgi_mask); break; case GPIO_INTR_EDGE_BOTH: BCM_GPIO_SET_BITS(sc, BCM_GPIO_GPREN(bank), bgi->bgi_mask); BCM_GPIO_SET_BITS(sc, BCM_GPIO_GPFEN(bank), bgi->bgi_mask); break; } BCM_GPIO_UNLOCK(sc); } static int bcm_gpio_intr_internal(struct bcm_gpio_softc *sc, uint32_t bank) { u_int irq; struct bcm_gpio_irqsrc *bgi; uint32_t reg; /* Do not care of spurious interrupt on GPIO. */ reg = BCM_GPIO_READ(sc, BCM_GPIO_GPEDS(bank)); while (reg != 0) { irq = BCM_GPIO_PINS_PER_BANK * bank + ffs(reg) - 1; bgi = sc->sc_isrcs + irq; if (!bcm_gpio_isrc_is_level(bgi)) bcm_gpio_isrc_eoi(sc, bgi); if (intr_isrc_dispatch(&bgi->bgi_isrc, curthread->td_intr_frame) != 0) { bcm_gpio_isrc_mask(sc, bgi); if (bcm_gpio_isrc_is_level(bgi)) bcm_gpio_isrc_eoi(sc, bgi); device_printf(sc->sc_dev, "Stray irq %u disabled\n", irq); } reg &= ~bgi->bgi_mask; } return (FILTER_HANDLED); } static int bcm_gpio_intr_bank0(void *arg) { return (bcm_gpio_intr_internal(arg, 0)); } static int bcm_gpio_intr_bank1(void *arg) { return (bcm_gpio_intr_internal(arg, 1)); } static int bcm_gpio_pic_attach(struct bcm_gpio_softc *sc) { int error; uint32_t irq; const char *name; name = device_get_nameunit(sc->sc_dev); for (irq = 0; irq < BCM_GPIO_PINS; irq++) { sc->sc_isrcs[irq].bgi_irq = irq; sc->sc_isrcs[irq].bgi_mask = BCM_GPIO_MASK(irq); sc->sc_isrcs[irq].bgi_mode = GPIO_INTR_CONFORM; error = intr_isrc_register(&sc->sc_isrcs[irq].bgi_isrc, sc->sc_dev, 0, "%s,%u", name, irq); if (error != 0) return (error); /* XXX deregister ISRCs */ } if (intr_pic_register(sc->sc_dev, OF_xref_from_node(ofw_bus_get_node(sc->sc_dev))) == NULL) return (ENXIO); return (0); } static int bcm_gpio_pic_detach(struct bcm_gpio_softc *sc) { /* * There has not been established any procedure yet * how to detach PIC from living system correctly. */ device_printf(sc->sc_dev, "%s: not implemented yet\n", __func__); return (EBUSY); } static void bcm_gpio_pic_config_intr(struct bcm_gpio_softc *sc, struct bcm_gpio_irqsrc *bgi, uint32_t mode) { uint32_t bank; bank = BCM_GPIO_BANK(bgi->bgi_irq); BCM_GPIO_LOCK(sc); bcm_gpio_modify(sc, BCM_GPIO_GPREN(bank), bgi->bgi_mask, mode == GPIO_INTR_EDGE_RISING || mode == GPIO_INTR_EDGE_BOTH); bcm_gpio_modify(sc, BCM_GPIO_GPFEN(bank), bgi->bgi_mask, mode == GPIO_INTR_EDGE_FALLING || mode == GPIO_INTR_EDGE_BOTH); bcm_gpio_modify(sc, BCM_GPIO_GPHEN(bank), bgi->bgi_mask, mode == GPIO_INTR_LEVEL_HIGH); bcm_gpio_modify(sc, BCM_GPIO_GPLEN(bank), bgi->bgi_mask, mode == GPIO_INTR_LEVEL_LOW); bgi->bgi_mode = mode; BCM_GPIO_UNLOCK(sc); } static void bcm_gpio_pic_disable_intr(device_t dev, struct intr_irqsrc *isrc) { struct bcm_gpio_softc *sc = device_get_softc(dev); struct bcm_gpio_irqsrc *bgi = (struct bcm_gpio_irqsrc *)isrc; bcm_gpio_isrc_mask(sc, bgi); } static void bcm_gpio_pic_enable_intr(device_t dev, struct intr_irqsrc *isrc) { struct bcm_gpio_softc *sc = device_get_softc(dev); struct bcm_gpio_irqsrc *bgi = (struct bcm_gpio_irqsrc *)isrc; arm_irq_memory_barrier(bgi->bgi_irq); bcm_gpio_isrc_unmask(sc, bgi); } static int bcm_gpio_pic_map_fdt(struct bcm_gpio_softc *sc, struct intr_map_data_fdt *daf, u_int *irqp, uint32_t *modep) { u_int irq; uint32_t mode; /* * 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 (daf->ncells != 2) return (EINVAL); irq = daf->cells[0]; if (irq >= BCM_GPIO_PINS || bcm_gpio_pin_is_ro(sc, irq)) return (EINVAL); /* Only reasonable modes are supported. */ if (daf->cells[1] == 1) mode = GPIO_INTR_EDGE_RISING; else if (daf->cells[1] == 2) mode = GPIO_INTR_EDGE_FALLING; else if (daf->cells[1] == 3) mode = GPIO_INTR_EDGE_BOTH; else if (daf->cells[1] == 4) mode = GPIO_INTR_LEVEL_HIGH; else if (daf->cells[1] == 8) mode = GPIO_INTR_LEVEL_LOW; else return (EINVAL); *irqp = irq; if (modep != NULL) *modep = mode; return (0); } static int bcm_gpio_pic_map_gpio(struct bcm_gpio_softc *sc, struct intr_map_data_gpio *dag, u_int *irqp, uint32_t *modep) { u_int irq; uint32_t mode; irq = dag->gpio_pin_num; if (irq >= BCM_GPIO_PINS || bcm_gpio_pin_is_ro(sc, irq)) return (EINVAL); mode = dag->gpio_intr_mode; if (mode != GPIO_INTR_LEVEL_LOW && mode != GPIO_INTR_LEVEL_HIGH && mode != GPIO_INTR_EDGE_RISING && mode != GPIO_INTR_EDGE_FALLING && mode != GPIO_INTR_EDGE_BOTH) return (EINVAL); *irqp = irq; if (modep != NULL) *modep = mode; return (0); } static int bcm_gpio_pic_map(struct bcm_gpio_softc *sc, struct intr_map_data *data, u_int *irqp, uint32_t *modep) { switch (data->type) { case INTR_MAP_DATA_FDT: return (bcm_gpio_pic_map_fdt(sc, (struct intr_map_data_fdt *)data, irqp, modep)); case INTR_MAP_DATA_GPIO: return (bcm_gpio_pic_map_gpio(sc, (struct intr_map_data_gpio *)data, irqp, modep)); default: return (ENOTSUP); } } static int bcm_gpio_pic_map_intr(device_t dev, struct intr_map_data *data, struct intr_irqsrc **isrcp) { int error; u_int irq; struct bcm_gpio_softc *sc = device_get_softc(dev); error = bcm_gpio_pic_map(sc, data, &irq, NULL); if (error == 0) *isrcp = &sc->sc_isrcs[irq].bgi_isrc; return (error); } static void bcm_gpio_pic_post_filter(device_t dev, struct intr_irqsrc *isrc) { struct bcm_gpio_softc *sc = device_get_softc(dev); struct bcm_gpio_irqsrc *bgi = (struct bcm_gpio_irqsrc *)isrc; if (bcm_gpio_isrc_is_level(bgi)) bcm_gpio_isrc_eoi(sc, bgi); } static void bcm_gpio_pic_post_ithread(device_t dev, struct intr_irqsrc *isrc) { bcm_gpio_pic_enable_intr(dev, isrc); } static void bcm_gpio_pic_pre_ithread(device_t dev, struct intr_irqsrc *isrc) { struct bcm_gpio_softc *sc = device_get_softc(dev); struct bcm_gpio_irqsrc *bgi = (struct bcm_gpio_irqsrc *)isrc; bcm_gpio_isrc_mask(sc, bgi); if (bcm_gpio_isrc_is_level(bgi)) bcm_gpio_isrc_eoi(sc, bgi); } static int bcm_gpio_pic_setup_intr(device_t dev, struct intr_irqsrc *isrc, struct resource *res, struct intr_map_data *data) { u_int irq; uint32_t mode; struct bcm_gpio_softc *sc; struct bcm_gpio_irqsrc *bgi; if (data == NULL) return (ENOTSUP); sc = device_get_softc(dev); bgi = (struct bcm_gpio_irqsrc *)isrc; /* Get and check config for an interrupt. */ if (bcm_gpio_pic_map(sc, data, &irq, &mode) != 0 || bgi->bgi_irq != irq) return (EINVAL); /* * If this is a setup for another handler, * only check that its configuration match. */ if (isrc->isrc_handlers != 0) return (bgi->bgi_mode == mode ? 0 : EINVAL); bcm_gpio_pic_config_intr(sc, bgi, mode); return (0); } static int bcm_gpio_pic_teardown_intr(device_t dev, struct intr_irqsrc *isrc, struct resource *res, struct intr_map_data *data) { struct bcm_gpio_softc *sc = device_get_softc(dev); struct bcm_gpio_irqsrc *bgi = (struct bcm_gpio_irqsrc *)isrc; if (isrc->isrc_handlers == 0) bcm_gpio_pic_config_intr(sc, bgi, GPIO_INTR_CONFORM); return (0); } static phandle_t bcm_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 int bcm_gpio_configure_pins(device_t dev, phandle_t cfgxref) { phandle_t cfgnode; int i, pintuples, pulltuples; uint32_t pin; uint32_t *pins; uint32_t *pulls; uint32_t function; static struct bcm_gpio_softc *sc; sc = device_get_softc(dev); cfgnode = OF_node_from_xref(cfgxref); pins = NULL; pintuples = OF_getencprop_alloc(cfgnode, "brcm,pins", sizeof(*pins), (void **)&pins); char name[32]; OF_getprop(cfgnode, "name", &name, sizeof(name)); if (pintuples < 0) return (ENOENT); if (pintuples == 0) return (0); /* Empty property is not an error. */ if (OF_getencprop(cfgnode, "brcm,function", &function, sizeof(function)) <= 0) { OF_prop_free(pins); return (EINVAL); } pulls = NULL; pulltuples = OF_getencprop_alloc(cfgnode, "brcm,pull", sizeof(*pulls), (void **)&pulls); if ((pulls != NULL) && (pulltuples != pintuples)) { OF_prop_free(pins); OF_prop_free(pulls); return (EINVAL); } for (i = 0; i < pintuples; i++) { pin = pins[i]; bcm_gpio_set_alternate(dev, pin, function); if (bootverbose) device_printf(dev, "set pin %d to func %d", pin, function); if (pulls) { if (bootverbose) printf(", pull %d", pulls[i]); switch (pulls[i]) { /* Convert to gpio(4) flags */ case BCM2835_PUD_OFF: bcm_gpio_pin_setflags(dev, pin, 0); break; case BCM2835_PUD_UP: bcm_gpio_pin_setflags(dev, pin, GPIO_PIN_PULLUP); break; case BCM2835_PUD_DOWN: bcm_gpio_pin_setflags(dev, pin, GPIO_PIN_PULLDOWN); break; default: printf("%s: invalid pull value for pin %d: %d\n", name, pin, pulls[i]); } } if (bootverbose) printf("\n"); } OF_prop_free(pins); if (pulls) OF_prop_free(pulls); return (0); } static device_method_t bcm_gpio_methods[] = { /* Device interface */ DEVMETHOD(device_probe, bcm_gpio_probe), DEVMETHOD(device_attach, bcm_gpio_attach), DEVMETHOD(device_detach, bcm_gpio_detach), /* GPIO protocol */ DEVMETHOD(gpio_get_bus, bcm_gpio_get_bus), DEVMETHOD(gpio_pin_max, bcm_gpio_pin_max), DEVMETHOD(gpio_pin_getname, bcm_gpio_pin_getname), DEVMETHOD(gpio_pin_getflags, bcm_gpio_pin_getflags), DEVMETHOD(gpio_pin_getcaps, bcm_gpio_pin_getcaps), DEVMETHOD(gpio_pin_setflags, bcm_gpio_pin_setflags), DEVMETHOD(gpio_pin_get, bcm_gpio_pin_get), DEVMETHOD(gpio_pin_set, bcm_gpio_pin_set), DEVMETHOD(gpio_pin_toggle, bcm_gpio_pin_toggle), /* Interrupt controller interface */ DEVMETHOD(pic_disable_intr, bcm_gpio_pic_disable_intr), DEVMETHOD(pic_enable_intr, bcm_gpio_pic_enable_intr), DEVMETHOD(pic_map_intr, bcm_gpio_pic_map_intr), DEVMETHOD(pic_post_filter, bcm_gpio_pic_post_filter), DEVMETHOD(pic_post_ithread, bcm_gpio_pic_post_ithread), DEVMETHOD(pic_pre_ithread, bcm_gpio_pic_pre_ithread), DEVMETHOD(pic_setup_intr, bcm_gpio_pic_setup_intr), DEVMETHOD(pic_teardown_intr, bcm_gpio_pic_teardown_intr), /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_node, bcm_gpio_get_node), /* fdt_pinctrl interface */ DEVMETHOD(fdt_pinctrl_configure, bcm_gpio_configure_pins), DEVMETHOD_END }; static devclass_t bcm_gpio_devclass; static driver_t bcm_gpio_driver = { "gpio", bcm_gpio_methods, sizeof(struct bcm_gpio_softc), }; EARLY_DRIVER_MODULE(bcm_gpio, simplebus, bcm_gpio_driver, bcm_gpio_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LATE); Index: head/sys/arm/freescale/vybrid/vf_ccm.c =================================================================== --- head/sys/arm/freescale/vybrid/vf_ccm.c (revision 332309) +++ head/sys/arm/freescale/vybrid/vf_ccm.c (revision 332310) @@ -1,502 +1,502 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2013-2014 Ruslan Bukin * 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. */ /* * Vybrid Family Clock Controller Module (CCM) * Chapter 10, Vybrid Reference Manual, Rev. 5, 07/2013 */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define CCM_CCR 0x00 /* Control Register */ #define CCM_CSR 0x04 /* Status Register */ #define CCM_CCSR 0x08 /* Clock Switcher Register */ #define CCM_CACRR 0x0C /* ARM Clock Root Register */ #define CCM_CSCMR1 0x10 /* Serial Clock Multiplexer Register 1 */ #define CCM_CSCDR1 0x14 /* Serial Clock Divider Register 1 */ #define CCM_CSCDR2 0x18 /* Serial Clock Divider Register 2 */ #define CCM_CSCDR3 0x1C /* Serial Clock Divider Register 3 */ #define CCM_CSCMR2 0x20 /* Serial Clock Multiplexer Register 2 */ #define CCM_CTOR 0x28 /* Testing Observability Register */ #define CCM_CLPCR 0x2C /* Low Power Control Register */ #define CCM_CISR 0x30 /* Interrupt Status Register */ #define CCM_CIMR 0x34 /* Interrupt Mask Register */ #define CCM_CCOSR 0x38 /* Clock Output Source Register */ #define CCM_CGPR 0x3C /* General Purpose Register */ #define CCM_CCGRN 12 #define CCM_CCGR(n) (0x40 + (n * 0x04)) /* Clock Gating Register */ #define CCM_CMEOR(n) (0x70 + (n * 0x70)) /* Module Enable Override */ #define CCM_CCPGR(n) (0x90 + (n * 0x04)) /* Platform Clock Gating */ #define CCM_CPPDSR 0x88 /* PLL PFD Disable Status Register */ #define CCM_CCOWR 0x8C /* CORE Wakeup Register */ #define PLL3_PFD4_EN (1U << 31) #define PLL3_PFD3_EN (1 << 30) #define PLL3_PFD2_EN (1 << 29) #define PLL3_PFD1_EN (1 << 28) #define PLL2_PFD4_EN (1 << 15) #define PLL2_PFD3_EN (1 << 14) #define PLL2_PFD2_EN (1 << 13) #define PLL2_PFD1_EN (1 << 12) #define PLL1_PFD4_EN (1 << 11) #define PLL1_PFD3_EN (1 << 10) #define PLL1_PFD2_EN (1 << 9) #define PLL1_PFD1_EN (1 << 8) /* CCM_CCR */ #define FIRC_EN (1 << 16) #define FXOSC_EN (1 << 12) #define FXOSC_RDY (1 << 5) /* CCM_CSCDR1 */ #define ENET_TS_EN (1 << 23) #define RMII_CLK_EN (1 << 24) #define SAI3_EN (1 << 19) /* CCM_CSCDR2 */ #define ESAI_EN (1 << 30) #define ESDHC1_EN (1 << 29) #define ESDHC0_EN (1 << 28) #define NFC_EN (1 << 9) #define ESDHC1_DIV_S 20 #define ESDHC1_DIV_M 0xf #define ESDHC0_DIV_S 16 #define ESDHC0_DIV_M 0xf /* CCM_CSCDR3 */ #define DCU0_EN (1 << 19) #define QSPI1_EN (1 << 12) #define QSPI1_DIV (1 << 11) #define QSPI1_X2_DIV (1 << 10) #define QSPI1_X4_DIV_M 0x3 #define QSPI1_X4_DIV_S 8 #define QSPI0_EN (1 << 4) #define QSPI0_DIV (1 << 3) #define QSPI0_X2_DIV (1 << 2) #define QSPI0_X4_DIV_M 0x3 #define QSPI0_X4_DIV_S 0 #define SAI3_DIV_SHIFT 12 #define SAI3_DIV_MASK 0xf #define ESAI_DIV_SHIFT 24 #define ESAI_DIV_MASK 0xf #define PLL4_CLK_DIV_SHIFT 6 #define PLL4_CLK_DIV_MASK 0x7 #define IPG_CLK_DIV_SHIFT 11 #define IPG_CLK_DIV_MASK 0x3 #define ESAI_CLK_SEL_SHIFT 20 #define ESAI_CLK_SEL_MASK 0x3 #define SAI3_CLK_SEL_SHIFT 6 #define SAI3_CLK_SEL_MASK 0x3 #define CKO1_EN (1 << 10) #define CKO1_DIV_MASK 0xf #define CKO1_DIV_SHIFT 6 #define CKO1_SEL_MASK 0x3f #define CKO1_SEL_SHIFT 0 #define CKO1_PLL4_MAIN 0x6 #define CKO1_PLL4_DIVD 0x7 struct clk { uint32_t reg; uint32_t enable_reg; uint32_t div_mask; uint32_t div_shift; uint32_t div_val; uint32_t sel_reg; uint32_t sel_mask; uint32_t sel_shift; uint32_t sel_val; }; static struct clk ipg_clk = { .reg = CCM_CACRR, .enable_reg = 0, .div_mask = IPG_CLK_DIV_MASK, .div_shift = IPG_CLK_DIV_SHIFT, .div_val = 1, /* Divide by 2 */ .sel_reg = 0, .sel_mask = 0, .sel_shift = 0, .sel_val = 0, }; /* PLL4 clock divider (before switching the clocks should be gated) 000 Divide by 1 (only if PLL frequency less than or equal to 650 MHz) 001 Divide by 4 010 Divide by 6 011 Divide by 8 100 Divide by 10 101 Divide by 12 110 Divide by 14 111 Divide by 16 */ static struct clk pll4_clk = { .reg = CCM_CACRR, .enable_reg = 0, .div_mask = PLL4_CLK_DIV_MASK, .div_shift = PLL4_CLK_DIV_SHIFT, .div_val = 5, /* Divide by 12 */ .sel_reg = 0, .sel_mask = 0, .sel_shift = 0, .sel_val = 0, }; static struct clk sai3_clk = { .reg = CCM_CSCDR1, .enable_reg = SAI3_EN, .div_mask = SAI3_DIV_MASK, .div_shift = SAI3_DIV_SHIFT, .div_val = 1, .sel_reg = CCM_CSCMR1, .sel_mask = SAI3_CLK_SEL_MASK, .sel_shift = SAI3_CLK_SEL_SHIFT, .sel_val = 0x3, /* Divided PLL4 main clock */ }; static struct clk cko1_clk = { .reg = CCM_CCOSR, .enable_reg = CKO1_EN, .div_mask = CKO1_DIV_MASK, .div_shift = CKO1_DIV_SHIFT, .div_val = 1, .sel_reg = CCM_CCOSR, .sel_mask = CKO1_SEL_MASK, .sel_shift = CKO1_SEL_SHIFT, .sel_val = CKO1_PLL4_DIVD, }; static struct clk esdhc0_clk = { .reg = CCM_CSCDR2, .enable_reg = ESDHC0_EN, .div_mask = ESDHC0_DIV_M, .div_shift = ESDHC0_DIV_S, .div_val = 0x9, .sel_reg = 0, .sel_mask = 0, .sel_shift = 0, .sel_val = 0, }; static struct clk esdhc1_clk = { .reg = CCM_CSCDR2, .enable_reg = ESDHC1_EN, .div_mask = ESDHC1_DIV_M, .div_shift = ESDHC1_DIV_S, .div_val = 0x9, .sel_reg = 0, .sel_mask = 0, .sel_shift = 0, .sel_val = 0, }; static struct clk qspi0_clk = { .reg = CCM_CSCDR3, .enable_reg = QSPI0_EN, .div_mask = 0, .div_shift = 0, .div_val = 0, .sel_reg = 0, .sel_mask = 0, .sel_shift = 0, .sel_val = 0, }; static struct clk dcu0_clk = { .reg = CCM_CSCDR3, .enable_reg = DCU0_EN, .div_mask = 0x7, .div_shift = 16, /* DCU0_DIV */ .div_val = 0, /* divide by 1 */ .sel_reg = 0, .sel_mask = 0, .sel_shift = 0, .sel_val = 0, }; static struct clk enet_clk = { .reg = CCM_CSCDR1, .enable_reg = (ENET_TS_EN | RMII_CLK_EN), .div_mask = 0, .div_shift = 0, .div_val = 0, .sel_reg = 0, .sel_mask = 0, .sel_shift = 0, .sel_val = 0, }; static struct clk nand_clk = { .reg = CCM_CSCDR2, .enable_reg = NFC_EN, .div_mask = 0, .div_shift = 0, .div_val = 0, .sel_reg = 0, .sel_mask = 0, .sel_shift = 0, .sel_val = 0, }; /* Divider to generate ESAI clock 0000 Divide by 1 0001 Divide by 2 ... ... 1111 Divide by 16 */ static struct clk esai_clk = { .reg = CCM_CSCDR2, .enable_reg = ESAI_EN, .div_mask = ESAI_DIV_MASK, .div_shift = ESAI_DIV_SHIFT, .div_val = 3, /* Divide by 4 */ .sel_reg = CCM_CSCMR1, .sel_mask = ESAI_CLK_SEL_MASK, .sel_shift = ESAI_CLK_SEL_SHIFT, .sel_val = 0x3, /* Divided PLL4 main clock */ }; struct clock_entry { char *name; struct clk *clk; }; static struct clock_entry clock_map[] = { {"ipg", &ipg_clk}, {"pll4", &pll4_clk}, {"sai3", &sai3_clk}, {"cko1", &cko1_clk}, {"esdhc0", &esdhc0_clk}, {"esdhc1", &esdhc1_clk}, {"qspi0", &qspi0_clk}, {"dcu0", &dcu0_clk}, {"enet", &enet_clk}, {"nand", &nand_clk}, {"esai", &esai_clk}, {NULL, NULL} }; struct ccm_softc { struct resource *res[1]; bus_space_tag_t bst; bus_space_handle_t bsh; device_t dev; }; static struct resource_spec ccm_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { -1, 0 } }; static int ccm_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "fsl,mvf600-ccm")) return (ENXIO); device_set_desc(dev, "Vybrid Family CCM Unit"); return (BUS_PROBE_DEFAULT); } static int set_clock(struct ccm_softc *sc, char *name) { struct clk *clk; int reg; int i; for (i = 0; clock_map[i].name != NULL; i++) { if (strcmp(clock_map[i].name, name) == 0) { #if 0 device_printf(sc->dev, "Configuring %s clk\n", name); #endif clk = clock_map[i].clk; if (clk->sel_reg != 0) { reg = READ4(sc, clk->sel_reg); reg &= ~(clk->sel_mask << clk->sel_shift); reg |= (clk->sel_val << clk->sel_shift); WRITE4(sc, clk->sel_reg, reg); } reg = READ4(sc, clk->reg); reg |= clk->enable_reg; reg &= ~(clk->div_mask << clk->div_shift); reg |= (clk->div_val << clk->div_shift); WRITE4(sc, clk->reg, reg); } } return (0); } static int ccm_fdt_set(struct ccm_softc *sc) { phandle_t child, parent, root; int len; char *fdt_config, *name; root = OF_finddevice("/"); len = 0; parent = root; /* Find 'clock_names' prop in the tree */ for (child = OF_child(parent); child != 0; child = OF_peer(child)) { /* Find a 'leaf'. Start the search from this node. */ while (OF_child(child)) { parent = child; child = OF_child(child); } if (!ofw_bus_node_status_okay(child)) continue; if ((len = OF_getproplen(child, "clock_names")) > 0) { len = OF_getproplen(child, "clock_names"); - OF_getprop_alloc(child, "clock_names", 1, + OF_getprop_alloc(child, "clock_names", (void **)&fdt_config); while (len > 0) { name = fdt_config; fdt_config += strlen(name) + 1; len -= strlen(name) + 1; set_clock(sc, name); } } if (OF_peer(child) == 0) { /* No more siblings. */ child = parent; parent = OF_parent(child); } } return (0); } static int ccm_attach(device_t dev) { struct ccm_softc *sc; int reg; int i; sc = device_get_softc(dev); sc->dev = dev; if (bus_alloc_resources(dev, ccm_spec, sc->res)) { device_printf(dev, "could not allocate resources\n"); return (ENXIO); } /* Memory interface */ sc->bst = rman_get_bustag(sc->res[0]); sc->bsh = rman_get_bushandle(sc->res[0]); /* Enable oscillator */ reg = READ4(sc, CCM_CCR); reg |= (FIRC_EN | FXOSC_EN); WRITE4(sc, CCM_CCR, reg); /* Wait 10 times */ for (i = 0; i < 10; i++) { if (READ4(sc, CCM_CSR) & FXOSC_RDY) { device_printf(sc->dev, "On board oscillator is ready.\n"); break; } cpufunc_nullop(); } /* Clock is on during all modes, except stop mode. */ for (i = 0; i < CCM_CCGRN; i++) { WRITE4(sc, CCM_CCGR(i), 0xffffffff); } /* Take and apply FDT clocks */ ccm_fdt_set(sc); return (0); } static device_method_t ccm_methods[] = { DEVMETHOD(device_probe, ccm_probe), DEVMETHOD(device_attach, ccm_attach), { 0, 0 } }; static driver_t ccm_driver = { "ccm", ccm_methods, sizeof(struct ccm_softc), }; static devclass_t ccm_devclass; DRIVER_MODULE(ccm, simplebus, ccm_driver, ccm_devclass, 0, 0); Index: head/sys/arm/mv/mv_localbus.c =================================================================== --- head/sys/arm/mv/mv_localbus.c (revision 332309) +++ head/sys/arm/mv/mv_localbus.c (revision 332310) @@ -1,494 +1,494 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2012 Semihalf. * 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$"); #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dev/fdt/fdt_common.h" #include "ofw_bus_if.h" #include #include #ifdef DEBUG #define debugf(fmt, args...) do { printf("%s(): ", __func__); \ printf(fmt,##args); } while (0) #else #define debugf(fmt, args...) #endif #define MV_LOCALBUS_MAX_BANKS 8 #define MV_LOCALBUS_MAX_BANK_CELLS 4 static MALLOC_DEFINE(M_LOCALBUS, "localbus", "localbus devices information"); struct localbus_bank { vm_offset_t va; /* VA of the bank */ vm_paddr_t pa; /* physical address of the bank */ vm_size_t size; /* bank size */ uint8_t mapped; /* device memory has mapping */ }; struct localbus_softc { device_t sc_dev; bus_space_handle_t sc_bsh; bus_space_tag_t sc_bst; int sc_rid; struct localbus_bank *sc_banks; }; struct localbus_devinfo { struct ofw_bus_devinfo di_ofw; struct resource_list di_res; int di_bank; }; struct localbus_va_entry { int8_t bank; vm_offset_t va; vm_size_t size; }; /* * Prototypes. */ static int localbus_probe(device_t); static int localbus_attach(device_t); static int localbus_print_child(device_t, device_t); static struct resource *localbus_alloc_resource(device_t, device_t, int, int *, rman_res_t, rman_res_t, rman_res_t, u_int); static struct resource_list *localbus_get_resource_list(device_t, device_t); static ofw_bus_get_devinfo_t localbus_get_devinfo; /* * Bus interface definition. */ static device_method_t localbus_methods[] = { /* Device interface */ DEVMETHOD(device_probe, localbus_probe), DEVMETHOD(device_attach, localbus_attach), DEVMETHOD(device_detach, bus_generic_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* Bus interface */ DEVMETHOD(bus_print_child, localbus_print_child), DEVMETHOD(bus_alloc_resource, localbus_alloc_resource), DEVMETHOD(bus_release_resource, bus_generic_release_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD(bus_get_resource_list, localbus_get_resource_list), /* OFW bus interface */ DEVMETHOD(ofw_bus_get_devinfo, localbus_get_devinfo), DEVMETHOD(ofw_bus_get_compat, ofw_bus_gen_get_compat), DEVMETHOD(ofw_bus_get_model, ofw_bus_gen_get_model), DEVMETHOD(ofw_bus_get_name, ofw_bus_gen_get_name), DEVMETHOD(ofw_bus_get_node, ofw_bus_gen_get_node), DEVMETHOD(ofw_bus_get_type, ofw_bus_gen_get_type), { 0, 0 } }; static driver_t localbus_driver = { "localbus", localbus_methods, sizeof(struct localbus_softc) }; const struct localbus_va_entry localbus_virtmap[] = { { 0, MV_DEV_BOOT_BASE, MV_DEV_BOOT_SIZE }, { 1, MV_DEV_CS0_BASE, MV_DEV_CS0_SIZE }, { 2, MV_DEV_CS1_BASE, MV_DEV_CS1_SIZE }, { 3, MV_DEV_CS2_BASE, MV_DEV_CS2_SIZE }, { -1, 0, 0 } }; static struct localbus_bank localbus_banks[MV_LOCALBUS_MAX_BANKS]; devclass_t localbus_devclass; DRIVER_MODULE(localbus, ofwbus, localbus_driver, localbus_devclass, 0, 0); static int fdt_localbus_reg_decode(phandle_t node, struct localbus_softc *sc, struct localbus_devinfo *di) { u_long start, end, count; pcell_t *reg, *regptr; pcell_t addr_cells, size_cells; int tuple_size, tuples; int i, rv, bank; if (fdt_addrsize_cells(OF_parent(node), &addr_cells, &size_cells) != 0) return (ENXIO); tuple_size = sizeof(pcell_t) * (addr_cells + size_cells); - tuples = OF_getprop_alloc(node, "reg", tuple_size, (void **)®); + tuples = OF_getprop_alloc_multi(node, "reg", tuple_size, (void **)®); debugf("addr_cells = %d, size_cells = %d\n", addr_cells, size_cells); debugf("tuples = %d, tuple size = %d\n", tuples, tuple_size); if (tuples <= 0) /* No 'reg' property in this node. */ return (0); regptr = reg; for (i = 0; i < tuples; i++) { bank = fdt_data_get((void *)regptr, 1); if (bank >= MV_LOCALBUS_MAX_BANKS) { device_printf(sc->sc_dev, "bank number [%d] out of " "range\n", bank); continue; } /* * If device doesn't have virtual to physical mapping don't add * resources */ if (!(sc->sc_banks[bank].mapped)) { device_printf(sc->sc_dev, "device [%d]: missing memory " "mapping\n", bank); continue; } di->di_bank = bank; regptr += 1; /* Get address/size. */ rv = fdt_data_to_res(regptr, addr_cells - 1, size_cells, &start, &count); if (rv != 0) { resource_list_free(&di->di_res); goto out; } /* Check if enough amount of memory is mapped */ if (sc->sc_banks[bank].size < count) { device_printf(sc->sc_dev, "device [%d]: not enough " "memory reserved\n", bank); continue; } regptr += addr_cells - 1 + size_cells; /* Calculate address range relative to VA base. */ start = sc->sc_banks[bank].va + start; end = start + count - 1; debugf("reg addr bank = %d, start = %lx, end = %lx, " "count = %lx\n", bank, start, end, count); /* Use bank (CS) cell as rid. */ resource_list_add(&di->di_res, SYS_RES_MEMORY, di->di_bank, start, end, count); } rv = 0; out: OF_prop_free(reg); return (rv); } static int localbus_probe(device_t dev) { if (!ofw_bus_is_compatible_strict(dev, "mrvl,lbc")) return (ENXIO); device_set_desc(dev, "Marvell device bus"); return (BUS_PROBE_DEFAULT); } static int localbus_attach(device_t dev) { device_t dev_child; struct localbus_softc *sc; struct localbus_devinfo *di; phandle_t dt_node, dt_child; sc = device_get_softc(dev); sc->sc_dev = dev; sc->sc_banks = localbus_banks; /* * Walk localbus and add direct subordinates as our children. */ dt_node = ofw_bus_get_node(dev); for (dt_child = OF_child(dt_node); dt_child != 0; dt_child = OF_peer(dt_child)) { /* Check and process 'status' property. */ if (!(ofw_bus_node_status_okay(dt_child))) continue; if (!(mv_fdt_pm(dt_child))) continue; di = malloc(sizeof(*di), M_LOCALBUS, M_WAITOK | M_ZERO); if (ofw_bus_gen_setup_devinfo(&di->di_ofw, dt_child) != 0) { free(di, M_LOCALBUS); device_printf(dev, "could not set up devinfo\n"); continue; } resource_list_init(&di->di_res); if (fdt_localbus_reg_decode(dt_child, sc, di)) { device_printf(dev, "could not process 'reg' " "property\n"); ofw_bus_gen_destroy_devinfo(&di->di_ofw); free(di, M_LOCALBUS); continue; } /* Add newbus device for this FDT node */ dev_child = device_add_child(dev, NULL, -1); if (dev_child == NULL) { device_printf(dev, "could not add child: %s\n", di->di_ofw.obd_name); resource_list_free(&di->di_res); ofw_bus_gen_destroy_devinfo(&di->di_ofw); free(di, M_LOCALBUS); continue; } #ifdef DEBUG device_printf(dev, "added child: %s\n\n", di->di_ofw.obd_name); #endif device_set_ivars(dev_child, di); } return (bus_generic_attach(dev)); } static int localbus_print_child(device_t dev, device_t child) { struct localbus_devinfo *di; struct resource_list *rl; int rv; di = device_get_ivars(child); rl = &di->di_res; rv = 0; rv += bus_print_child_header(dev, child); rv += resource_list_print_type(rl, "mem", SYS_RES_MEMORY, "%#jx"); rv += resource_list_print_type(rl, "irq", SYS_RES_IRQ, "%jd"); rv += bus_print_child_footer(dev, child); return (rv); } static struct resource * localbus_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 localbus_devinfo *di; struct resource_list_entry *rle; /* * Request for the default allocation with a given rid: use resource * list stored in the local device info. */ if (RMAN_IS_DEFAULT_RANGE(start, end)) { if ((di = device_get_ivars(child)) == NULL) return (NULL); if (type == SYS_RES_IOPORT) type = SYS_RES_MEMORY; rid = &di->di_bank; rle = resource_list_find(&di->di_res, type, *rid); if (rle == NULL) { device_printf(bus, "no default resources for " "rid = %d, type = %d\n", *rid, type); return (NULL); } start = rle->start; end = rle->end; count = rle->count; } return (bus_generic_alloc_resource(bus, child, type, rid, start, end, count, flags)); } static struct resource_list * localbus_get_resource_list(device_t bus, device_t child) { struct localbus_devinfo *di; di = device_get_ivars(child); return (&di->di_res); } static const struct ofw_bus_devinfo * localbus_get_devinfo(device_t bus, device_t child) { struct localbus_devinfo *di; di = device_get_ivars(child); return (&di->di_ofw); } int fdt_localbus_devmap(phandle_t dt_node, struct devmap_entry *fdt_devmap, int banks_max_num, int *banks_added) { pcell_t ranges[MV_LOCALBUS_MAX_BANKS * MV_LOCALBUS_MAX_BANK_CELLS]; pcell_t *rangesptr; uint32_t tuple_size, bank; vm_paddr_t offset; vm_size_t size; int dev_num, addr_cells, size_cells, par_addr_cells, va_index, i, j, k; if ((fdt_addrsize_cells(dt_node, &addr_cells, &size_cells)) != 0) return (EINVAL); par_addr_cells = fdt_parent_addr_cells(dt_node); if (par_addr_cells > 2) { /* * Localbus devmap initialization error: unsupported parent * #addr-cells */ return (ERANGE); } tuple_size = (addr_cells + par_addr_cells + size_cells); if (tuple_size > MV_LOCALBUS_MAX_BANK_CELLS) return (ERANGE); tuple_size *= sizeof(pcell_t); dev_num = OF_getprop(dt_node, "ranges", ranges, sizeof(ranges)); if (dev_num <= 0) return (EINVAL); /* Calculate number of devices attached to bus */ dev_num = dev_num / tuple_size; /* * If number of ranges > max number of localbus devices, * additional entries will not be processed */ dev_num = MIN(dev_num, banks_max_num); rangesptr = &ranges[0]; j = 0; /* Process data from FDT */ for (i = 0; i < dev_num; i++) { /* First field is bank number */ bank = fdt_data_get((void *)rangesptr, 1); rangesptr += 1; if (bank > MV_LOCALBUS_MAX_BANKS) { /* Bank out of range */ rangesptr += ((addr_cells - 1) + par_addr_cells + size_cells); continue; } /* Find virtmap entry for this bank */ va_index = -1; for (k = 0; localbus_virtmap[k].bank >= 0; k++) { if (localbus_virtmap[k].bank == bank) { va_index = k; break; } } /* Check if virtmap entry was found */ if (va_index == -1) { rangesptr += ((addr_cells - 1) + par_addr_cells + size_cells); continue; } /* Remaining child's address fields are unused */ rangesptr += (addr_cells - 1); /* Parent address offset */ offset = fdt_data_get((void *)rangesptr, par_addr_cells); rangesptr += par_addr_cells; /* Last field is size */ size = fdt_data_get((void *)rangesptr, size_cells); rangesptr += size_cells; if (size > localbus_virtmap[va_index].size) { /* Not enough space reserved in virtual memory map */ continue; } fdt_devmap[j].pd_va = localbus_virtmap[va_index].va; fdt_devmap[j].pd_pa = offset; fdt_devmap[j].pd_size = size; /* Copy data to structure used by localbus driver */ localbus_banks[bank].va = fdt_devmap[j].pd_va; localbus_banks[bank].pa = fdt_devmap[j].pd_pa; localbus_banks[bank].size = fdt_devmap[j].pd_size; localbus_banks[bank].mapped = 1; j++; } *banks_added = j; return (0); } Index: head/sys/arm/nvidia/as3722_gpio.c =================================================================== --- head/sys/arm/nvidia/as3722_gpio.c (revision 332309) +++ head/sys/arm/nvidia/as3722_gpio.c (revision 332310) @@ -1,577 +1,577 @@ /*- * Copyright (c) 2016 Michal Meloun * 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$"); #include #include #include #include #include #include #include #include #include #include #include "as3722.h" MALLOC_DEFINE(M_AS3722_GPIO, "AS3722 gpio", "AS3722 GPIO"); /* AS3722_GPIOx_CONTROL MODE and IOSF definition. */ #define AS3722_IOSF_GPIO 0x00 #define AS3722_IOSF_INTERRUPT_OUT 0x01 #define AS3722_IOSF_VSUP_VBAT_LOW_UNDEBOUNCE_OUT 0x02 #define AS3722_IOSF_GPIO_IN_INTERRUPT 0x03 #define AS3722_IOSF_PWM_IN 0x04 #define AS3722_IOSF_VOLTAGE_IN_STANDBY 0x05 #define AS3722_IOSF_OC_PG_SD0 0x06 #define AS3722_IOSF_POWERGOOD_OUT 0x07 #define AS3722_IOSF_CLK32K_OUT 0x08 #define AS3722_IOSF_WATCHDOG_IN 0x09 #define AS3722_IOSF_SOFT_RESET_IN 0x0b #define AS3722_IOSF_PWM_OUT 0x0c #define AS3722_IOSF_VSUP_VBAT_LOW_DEBOUNCE_OUT 0x0d #define AS3722_IOSF_OC_PG_SD6 0x0e #define AS3722_MODE_INPUT 0 #define AS3722_MODE_PUSH_PULL 1 #define AS3722_MODE_OPEN_DRAIN 2 #define AS3722_MODE_TRISTATE 3 #define AS3722_MODE_INPUT_PULL_UP_LV 4 #define AS3722_MODE_INPUT_PULL_DOWN 5 #define AS3722_MODE_OPEN_DRAIN_LV 6 #define AS3722_MODE_PUSH_PULL_LV 7 #define NGPIO 8 #define GPIO_LOCK(_sc) sx_slock(&(_sc)->gpio_lock) #define GPIO_UNLOCK(_sc) sx_unlock(&(_sc)->gpio_lock) #define GPIO_ASSERT(_sc) sx_assert(&(_sc)->gpio_lock, SA_LOCKED) #define AS3722_CFG_BIAS_DISABLE 0x0001 #define AS3722_CFG_BIAS_PULL_UP 0x0002 #define AS3722_CFG_BIAS_PULL_DOWN 0x0004 #define AS3722_CFG_BIAS_HIGH_IMPEDANCE 0x0008 #define AS3722_CFG_OPEN_DRAIN 0x0010 static const struct { const char *name; int config; /* AS3722_CFG_ */ } as3722_cfg_names[] = { {"bias-disable", AS3722_CFG_BIAS_DISABLE}, {"bias-pull-up", AS3722_CFG_BIAS_PULL_UP}, {"bias-pull-down", AS3722_CFG_BIAS_PULL_DOWN}, {"bias-high-impedance", AS3722_CFG_BIAS_HIGH_IMPEDANCE}, {"drive-open-drain", AS3722_CFG_OPEN_DRAIN}, }; static struct { const char *name; int fnc_val; } as3722_fnc_table[] = { {"gpio", AS3722_IOSF_GPIO}, {"interrupt-out", AS3722_IOSF_INTERRUPT_OUT}, {"vsup-vbat-low-undebounce-out", AS3722_IOSF_VSUP_VBAT_LOW_UNDEBOUNCE_OUT}, {"gpio-in-interrupt", AS3722_IOSF_GPIO_IN_INTERRUPT}, {"pwm-in", AS3722_IOSF_PWM_IN}, {"voltage-in-standby", AS3722_IOSF_VOLTAGE_IN_STANDBY}, {"oc-pg-sd0", AS3722_IOSF_OC_PG_SD0}, {"powergood-out", AS3722_IOSF_POWERGOOD_OUT}, {"clk32k-out", AS3722_IOSF_CLK32K_OUT}, {"watchdog-in", AS3722_IOSF_WATCHDOG_IN}, {"soft-reset-in", AS3722_IOSF_SOFT_RESET_IN}, {"pwm-out", AS3722_IOSF_PWM_OUT}, {"vsup-vbat-low-debounce-out", AS3722_IOSF_VSUP_VBAT_LOW_DEBOUNCE_OUT}, {"oc-pg-sd6", AS3722_IOSF_OC_PG_SD6}, }; struct as3722_pincfg { char *function; int flags; }; struct as3722_gpio_pin { int pin_caps; uint8_t pin_ctrl_reg; char pin_name[GPIOMAXNAME]; int pin_cfg_flags; }; /* -------------------------------------------------------------------------- * * Pinmux functions. */ static int as3722_pinmux_get_function(struct as3722_softc *sc, char *name) { int i; for (i = 0; i < nitems(as3722_fnc_table); i++) { if (strcmp(as3722_fnc_table[i].name, name) == 0) return (as3722_fnc_table[i].fnc_val); } return (-1); } static int as3722_pinmux_config_node(struct as3722_softc *sc, char *pin_name, struct as3722_pincfg *cfg) { uint8_t ctrl; int rv, fnc, pin; for (pin = 0; pin < sc->gpio_npins; pin++) { if (strcmp(sc->gpio_pins[pin]->pin_name, pin_name) == 0) break; } if (pin >= sc->gpio_npins) { device_printf(sc->dev, "Unknown pin: %s\n", pin_name); return (ENXIO); } ctrl = sc->gpio_pins[pin]->pin_ctrl_reg; sc->gpio_pins[pin]->pin_cfg_flags = cfg->flags; if (cfg->function != NULL) { fnc = as3722_pinmux_get_function(sc, cfg->function); if (fnc == -1) { device_printf(sc->dev, "Unknown function %s for pin %s\n", cfg->function, sc->gpio_pins[pin]->pin_name); return (ENXIO); } switch (fnc) { case AS3722_IOSF_INTERRUPT_OUT: case AS3722_IOSF_VSUP_VBAT_LOW_UNDEBOUNCE_OUT: case AS3722_IOSF_OC_PG_SD0: case AS3722_IOSF_POWERGOOD_OUT: case AS3722_IOSF_CLK32K_OUT: case AS3722_IOSF_PWM_OUT: case AS3722_IOSF_OC_PG_SD6: ctrl &= ~(AS3722_GPIO_MODE_MASK << AS3722_GPIO_MODE_SHIFT); ctrl |= AS3722_MODE_PUSH_PULL << AS3722_GPIO_MODE_SHIFT; /* XXX Handle flags (OC + pullup) */ break; case AS3722_IOSF_GPIO_IN_INTERRUPT: case AS3722_IOSF_PWM_IN: case AS3722_IOSF_VOLTAGE_IN_STANDBY: case AS3722_IOSF_WATCHDOG_IN: case AS3722_IOSF_SOFT_RESET_IN: ctrl &= ~(AS3722_GPIO_MODE_MASK << AS3722_GPIO_MODE_SHIFT); ctrl |= AS3722_MODE_INPUT << AS3722_GPIO_MODE_SHIFT; /* XXX Handle flags (pulldown + pullup) */ default: break; } ctrl &= ~(AS3722_GPIO_IOSF_MASK << AS3722_GPIO_IOSF_SHIFT); ctrl |= fnc << AS3722_GPIO_IOSF_SHIFT; } rv = 0; if (ctrl != sc->gpio_pins[pin]->pin_ctrl_reg) { rv = WR1(sc, AS3722_GPIO0_CONTROL + pin, ctrl); sc->gpio_pins[pin]->pin_ctrl_reg = ctrl; } return (rv); } static int as3722_pinmux_read_node(struct as3722_softc *sc, phandle_t node, struct as3722_pincfg *cfg, char **pins, int *lpins) { int rv, i; - *lpins = OF_getprop_alloc(node, "pins", 1, (void **)pins); + *lpins = OF_getprop_alloc(node, "pins", (void **)pins); if (*lpins <= 0) return (ENOENT); /* Read function (mux) settings. */ - rv = OF_getprop_alloc(node, "function", 1, (void **)&cfg->function); + rv = OF_getprop_alloc(node, "function", (void **)&cfg->function); if (rv <= 0) cfg->function = NULL; /* Read boolean properties. */ for (i = 0; i < nitems(as3722_cfg_names); i++) { if (OF_hasprop(node, as3722_cfg_names[i].name)) cfg->flags |= as3722_cfg_names[i].config; } return (0); } static int as3722_pinmux_process_node(struct as3722_softc *sc, phandle_t node) { struct as3722_pincfg cfg; char *pins, *pname; int i, len, lpins, rv; rv = as3722_pinmux_read_node(sc, node, &cfg, &pins, &lpins); if (rv != 0) return (rv); len = 0; pname = pins; do { i = strlen(pname) + 1; rv = as3722_pinmux_config_node(sc, pname, &cfg); if (rv != 0) { device_printf(sc->dev, "Cannot configure pin: %s: %d\n", pname, rv); } len += i; pname += i; } while (len < lpins); if (pins != NULL) OF_prop_free(pins); if (cfg.function != NULL) OF_prop_free(cfg.function); return (rv); } int as3722_pinmux_configure(device_t dev, phandle_t cfgxref) { struct as3722_softc *sc; phandle_t node, cfgnode; int rv; sc = device_get_softc(dev); cfgnode = OF_node_from_xref(cfgxref); for (node = OF_child(cfgnode); node != 0; node = OF_peer(node)) { if (!ofw_bus_node_status_okay(node)) continue; rv = as3722_pinmux_process_node(sc, node); if (rv != 0) device_printf(dev, "Failed to process pinmux"); } return (0); } /* -------------------------------------------------------------------------- * * GPIO */ device_t as3722_gpio_get_bus(device_t dev) { struct as3722_softc *sc; sc = device_get_softc(dev); return (sc->gpio_busdev); } int as3722_gpio_pin_max(device_t dev, int *maxpin) { *maxpin = NGPIO - 1; return (0); } int as3722_gpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) { struct as3722_softc *sc; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); GPIO_LOCK(sc); *caps = sc->gpio_pins[pin]->pin_caps; GPIO_UNLOCK(sc); return (0); } int as3722_gpio_pin_getname(device_t dev, uint32_t pin, char *name) { struct as3722_softc *sc; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); GPIO_LOCK(sc); memcpy(name, sc->gpio_pins[pin]->pin_name, GPIOMAXNAME); GPIO_UNLOCK(sc); return (0); } int as3722_gpio_pin_getflags(device_t dev, uint32_t pin, uint32_t *out_flags) { struct as3722_softc *sc; uint8_t tmp, mode, iosf; uint32_t flags; bool inverted; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); GPIO_LOCK(sc); tmp = sc->gpio_pins[pin]->pin_ctrl_reg; GPIO_UNLOCK(sc); iosf = (tmp >> AS3722_GPIO_IOSF_SHIFT) & AS3722_GPIO_IOSF_MASK; mode = (tmp >> AS3722_GPIO_MODE_SHIFT) & AS3722_GPIO_MODE_MASK; inverted = (tmp & AS3722_GPIO_INVERT) != 0; /* Is pin in GPIO mode ? */ if (iosf != AS3722_IOSF_GPIO) return (ENXIO); flags = 0; switch (mode) { case AS3722_MODE_INPUT: flags = GPIO_PIN_INPUT; break; case AS3722_MODE_PUSH_PULL: case AS3722_MODE_PUSH_PULL_LV: flags = GPIO_PIN_OUTPUT; break; case AS3722_MODE_OPEN_DRAIN: case AS3722_MODE_OPEN_DRAIN_LV: flags = GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | GPIO_PIN_OPENDRAIN; break; case AS3722_MODE_TRISTATE: flags = GPIO_PIN_TRISTATE; break; case AS3722_MODE_INPUT_PULL_UP_LV: flags = GPIO_PIN_INPUT | GPIO_PIN_PULLUP; break; case AS3722_MODE_INPUT_PULL_DOWN: flags = GPIO_PIN_OUTPUT | GPIO_PIN_PULLDOWN; break; } if (inverted) flags |= GPIO_PIN_INVIN | GPIO_PIN_INVOUT; *out_flags = flags; return (0); } static int as3722_gpio_get_mode(struct as3722_softc *sc, uint32_t pin, uint32_t gpio_flags) { uint8_t ctrl; int flags; ctrl = sc->gpio_pins[pin]->pin_ctrl_reg; flags = sc->gpio_pins[pin]->pin_cfg_flags; /* Tristate mode. */ if (flags & AS3722_CFG_BIAS_HIGH_IMPEDANCE || gpio_flags & GPIO_PIN_TRISTATE) return (AS3722_MODE_TRISTATE); /* Open drain modes. */ if (flags & AS3722_CFG_OPEN_DRAIN || gpio_flags & GPIO_PIN_OPENDRAIN) { /* Only pull up have effect */ if (flags & AS3722_CFG_BIAS_PULL_UP || gpio_flags & GPIO_PIN_PULLUP) return (AS3722_MODE_OPEN_DRAIN_LV); return (AS3722_MODE_OPEN_DRAIN); } /* Input modes. */ if (gpio_flags & GPIO_PIN_INPUT) { /* Accept pull up or pull down. */ if (flags & AS3722_CFG_BIAS_PULL_UP || gpio_flags & GPIO_PIN_PULLUP) return (AS3722_MODE_INPUT_PULL_UP_LV); if (flags & AS3722_CFG_BIAS_PULL_DOWN || gpio_flags & GPIO_PIN_PULLDOWN) return (AS3722_MODE_INPUT_PULL_DOWN); return (AS3722_MODE_INPUT); } /* * Output modes. * Pull down is used as indicator of low voltage output. */ if (flags & AS3722_CFG_BIAS_PULL_DOWN || gpio_flags & GPIO_PIN_PULLDOWN) return (AS3722_MODE_PUSH_PULL_LV); return (AS3722_MODE_PUSH_PULL); } int as3722_gpio_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) { struct as3722_softc *sc; uint8_t ctrl, mode, iosf; int rv; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); GPIO_LOCK(sc); ctrl = sc->gpio_pins[pin]->pin_ctrl_reg; iosf = (ctrl >> AS3722_GPIO_IOSF_SHIFT) & AS3722_GPIO_IOSF_MASK; /* Is pin in GPIO mode ? */ if (iosf != AS3722_IOSF_GPIO) { GPIO_UNLOCK(sc); return (ENXIO); } mode = as3722_gpio_get_mode(sc, pin, flags); ctrl &= ~(AS3722_GPIO_MODE_MASK << AS3722_GPIO_MODE_SHIFT); ctrl |= AS3722_MODE_PUSH_PULL << AS3722_GPIO_MODE_SHIFT; rv = 0; if (ctrl != sc->gpio_pins[pin]->pin_ctrl_reg) { rv = WR1(sc, AS3722_GPIO0_CONTROL + pin, ctrl); sc->gpio_pins[pin]->pin_ctrl_reg = ctrl; } GPIO_UNLOCK(sc); return (rv); } int as3722_gpio_pin_set(device_t dev, uint32_t pin, uint32_t val) { struct as3722_softc *sc; uint8_t tmp; int rv; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); tmp = (val != 0) ? 1 : 0; if (sc->gpio_pins[pin]->pin_ctrl_reg & AS3722_GPIO_INVERT) tmp ^= 1; GPIO_LOCK(sc); rv = RM1(sc, AS3722_GPIO_SIGNAL_OUT, (1 << pin), (tmp << pin)); GPIO_UNLOCK(sc); return (rv); } int as3722_gpio_pin_get(device_t dev, uint32_t pin, uint32_t *val) { struct as3722_softc *sc; uint8_t tmp, mode, ctrl; int rv; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); GPIO_LOCK(sc); ctrl = sc->gpio_pins[pin]->pin_ctrl_reg; mode = (ctrl >> AS3722_GPIO_MODE_SHIFT) & AS3722_GPIO_MODE_MASK; if ((mode == AS3722_MODE_PUSH_PULL) || (mode == AS3722_MODE_PUSH_PULL_LV)) rv = RD1(sc, AS3722_GPIO_SIGNAL_OUT, &tmp); else rv = RD1(sc, AS3722_GPIO_SIGNAL_IN, &tmp); GPIO_UNLOCK(sc); if (rv != 0) return (rv); *val = tmp & (1 << pin) ? 1 : 0; if (ctrl & AS3722_GPIO_INVERT) *val ^= 1; return (0); } int as3722_gpio_pin_toggle(device_t dev, uint32_t pin) { struct as3722_softc *sc; uint8_t tmp; int rv; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); GPIO_LOCK(sc); rv = RD1(sc, AS3722_GPIO_SIGNAL_OUT, &tmp); if (rv != 0) { GPIO_UNLOCK(sc); return (rv); } tmp ^= (1 <gpio_lock, "AS3722 GPIO lock"); sc->gpio_npins = NGPIO; sc->gpio_pins = malloc(sizeof(struct as3722_gpio_pin *) * sc->gpio_npins, M_AS3722_GPIO, M_WAITOK | M_ZERO); sc->gpio_busdev = gpiobus_attach_bus(sc->dev); if (sc->gpio_busdev == NULL) return (ENXIO); for (i = 0; i < sc->gpio_npins; i++) { sc->gpio_pins[i] = malloc(sizeof(struct as3722_gpio_pin), M_AS3722_GPIO, M_WAITOK | M_ZERO); pin = sc->gpio_pins[i]; sprintf(pin->pin_name, "gpio%d", i); pin->pin_caps = GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | GPIO_PIN_OPENDRAIN | GPIO_PIN_PUSHPULL | GPIO_PIN_TRISTATE | GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN | GPIO_PIN_INVIN | GPIO_PIN_INVOUT; rv = RD1(sc, AS3722_GPIO0_CONTROL + i, &pin->pin_ctrl_reg); if (rv != 0) { device_printf(sc->dev, "Cannot read configuration for pin %s\n", sc->gpio_pins[i]->pin_name); } } return (0); } Index: head/sys/arm/nvidia/as3722_regulators.c =================================================================== --- head/sys/arm/nvidia/as3722_regulators.c (revision 332309) +++ head/sys/arm/nvidia/as3722_regulators.c (revision 332310) @@ -1,717 +1,717 @@ /*- * Copyright 2016 Michal Meloun * 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$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include "as3722.h" MALLOC_DEFINE(M_AS3722_REG, "AS3722 regulator", "AS3722 power regulator"); #define DIV_ROUND_UP(n,d) howmany(n, d) enum as3722_reg_id { AS3722_REG_ID_SD0, AS3722_REG_ID_SD1, AS3722_REG_ID_SD2, AS3722_REG_ID_SD3, AS3722_REG_ID_SD4, AS3722_REG_ID_SD5, AS3722_REG_ID_SD6, AS3722_REG_ID_LDO0, AS3722_REG_ID_LDO1, AS3722_REG_ID_LDO2, AS3722_REG_ID_LDO3, AS3722_REG_ID_LDO4, AS3722_REG_ID_LDO5, AS3722_REG_ID_LDO6, AS3722_REG_ID_LDO7, AS3722_REG_ID_LDO9, AS3722_REG_ID_LDO10, AS3722_REG_ID_LDO11, }; /* Regulator HW definition. */ struct reg_def { intptr_t id; /* ID */ char *name; /* Regulator name */ char *supply_name; /* Source property name */ uint8_t volt_reg; uint8_t volt_vsel_mask; uint8_t enable_reg; uint8_t enable_mask; uint8_t ext_enable_reg; uint8_t ext_enable_mask; struct regulator_range *ranges; int nranges; }; struct as3722_reg_sc { struct regnode *regnode; struct as3722_softc *base_sc; struct reg_def *def; phandle_t xref; struct regnode_std_param *param; int ext_control; int enable_tracking; int enable_usec; }; static struct regulator_range as3722_sd016_ranges[] = { REG_RANGE_INIT(0x00, 0x00, 0, 0), REG_RANGE_INIT(0x01, 0x5A, 610000, 10000), }; static struct regulator_range as3722_sd0_lv_ranges[] = { REG_RANGE_INIT(0x00, 0x00, 0, 0), REG_RANGE_INIT(0x01, 0x6E, 410000, 10000), }; static struct regulator_range as3722_sd_ranges[] = { REG_RANGE_INIT(0x00, 0x00, 0, 0), REG_RANGE_INIT(0x01, 0x40, 612500, 12500), REG_RANGE_INIT(0x41, 0x70, 1425000, 25000), REG_RANGE_INIT(0x71, 0x7F, 2650000, 50000), }; static struct regulator_range as3722_ldo3_ranges[] = { REG_RANGE_INIT(0x00, 0x00, 0, 0), REG_RANGE_INIT(0x01, 0x2D, 620000, 20000), }; static struct regulator_range as3722_ldo_ranges[] = { REG_RANGE_INIT(0x00, 0x00, 0, 0), REG_RANGE_INIT(0x01, 0x24, 825000, 25000), REG_RANGE_INIT(0x40, 0x7F, 1725000, 25000), }; static struct reg_def as3722s_def[] = { { .id = AS3722_REG_ID_SD0, .name = "sd0", .volt_reg = AS3722_SD0_VOLTAGE, .volt_vsel_mask = AS3722_SD_VSEL_MASK, .enable_reg = AS3722_SD_CONTROL, .enable_mask = AS3722_SDN_CTRL(0), .ext_enable_reg = AS3722_ENABLE_CTRL1, .ext_enable_mask = AS3722_SD0_EXT_ENABLE_MASK, .ranges = as3722_sd016_ranges, .nranges = nitems(as3722_sd016_ranges), }, { .id = AS3722_REG_ID_SD1, .name = "sd1", .volt_reg = AS3722_SD1_VOLTAGE, .volt_vsel_mask = AS3722_SD_VSEL_MASK, .enable_reg = AS3722_SD_CONTROL, .enable_mask = AS3722_SDN_CTRL(1), .ext_enable_reg = AS3722_ENABLE_CTRL1, .ext_enable_mask = AS3722_SD1_EXT_ENABLE_MASK, .ranges = as3722_sd_ranges, .nranges = nitems(as3722_sd_ranges), }, { .id = AS3722_REG_ID_SD2, .name = "sd2", .supply_name = "vsup-sd2", .volt_reg = AS3722_SD2_VOLTAGE, .volt_vsel_mask = AS3722_SD_VSEL_MASK, .enable_reg = AS3722_SD_CONTROL, .enable_mask = AS3722_SDN_CTRL(2), .ext_enable_reg = AS3722_ENABLE_CTRL1, .ext_enable_mask = AS3722_SD2_EXT_ENABLE_MASK, .ranges = as3722_sd_ranges, .nranges = nitems(as3722_sd_ranges), }, { .id = AS3722_REG_ID_SD3, .name = "sd3", .supply_name = "vsup-sd3", .volt_reg = AS3722_SD3_VOLTAGE, .volt_vsel_mask = AS3722_SD_VSEL_MASK, .enable_reg = AS3722_SD_CONTROL, .enable_mask = AS3722_SDN_CTRL(3), .ext_enable_reg = AS3722_ENABLE_CTRL1, .ext_enable_mask = AS3722_SD3_EXT_ENABLE_MASK, .ranges = as3722_sd_ranges, .nranges = nitems(as3722_sd_ranges), }, { .id = AS3722_REG_ID_SD4, .name = "sd4", .supply_name = "vsup-sd4", .volt_reg = AS3722_SD4_VOLTAGE, .volt_vsel_mask = AS3722_SD_VSEL_MASK, .enable_reg = AS3722_SD_CONTROL, .enable_mask = AS3722_SDN_CTRL(4), .ext_enable_reg = AS3722_ENABLE_CTRL2, .ext_enable_mask = AS3722_SD4_EXT_ENABLE_MASK, .ranges = as3722_sd_ranges, .nranges = nitems(as3722_sd_ranges), }, { .id = AS3722_REG_ID_SD5, .name = "sd5", .supply_name = "vsup-sd5", .volt_reg = AS3722_SD5_VOLTAGE, .volt_vsel_mask = AS3722_SD_VSEL_MASK, .enable_reg = AS3722_SD_CONTROL, .enable_mask = AS3722_SDN_CTRL(5), .ext_enable_reg = AS3722_ENABLE_CTRL2, .ext_enable_mask = AS3722_SD5_EXT_ENABLE_MASK, .ranges = as3722_sd_ranges, .nranges = nitems(as3722_sd_ranges), }, { .id = AS3722_REG_ID_SD6, .name = "sd6", .volt_reg = AS3722_SD6_VOLTAGE, .volt_vsel_mask = AS3722_SD_VSEL_MASK, .enable_reg = AS3722_SD_CONTROL, .enable_mask = AS3722_SDN_CTRL(6), .ext_enable_reg = AS3722_ENABLE_CTRL2, .ext_enable_mask = AS3722_SD6_EXT_ENABLE_MASK, .ranges = as3722_sd016_ranges, .nranges = nitems(as3722_sd016_ranges), }, { .id = AS3722_REG_ID_LDO0, .name = "ldo0", .supply_name = "vin-ldo0", .volt_reg = AS3722_LDO0_VOLTAGE, .volt_vsel_mask = AS3722_LDO0_VSEL_MASK, .enable_reg = AS3722_LDO_CONTROL0, .enable_mask = AS3722_LDO0_CTRL, .ext_enable_reg = AS3722_ENABLE_CTRL3, .ext_enable_mask = AS3722_LDO0_EXT_ENABLE_MASK, .ranges = as3722_ldo_ranges, .nranges = nitems(as3722_ldo_ranges), }, { .id = AS3722_REG_ID_LDO1, .name = "ldo1", .supply_name = "vin-ldo1-6", .volt_reg = AS3722_LDO1_VOLTAGE, .volt_vsel_mask = AS3722_LDO_VSEL_MASK, .enable_reg = AS3722_LDO_CONTROL0, .enable_mask = AS3722_LDO1_CTRL, .ext_enable_reg = AS3722_ENABLE_CTRL3, .ext_enable_mask = AS3722_LDO1_EXT_ENABLE_MASK, .ranges = as3722_ldo_ranges, .nranges = nitems(as3722_ldo_ranges), }, { .id = AS3722_REG_ID_LDO2, .name = "ldo2", .supply_name = "vin-ldo2-5-7", .volt_reg = AS3722_LDO2_VOLTAGE, .volt_vsel_mask = AS3722_LDO_VSEL_MASK, .enable_reg = AS3722_LDO_CONTROL0, .enable_mask = AS3722_LDO2_CTRL, .ext_enable_reg = AS3722_ENABLE_CTRL3, .ext_enable_mask = AS3722_LDO2_EXT_ENABLE_MASK, .ranges = as3722_ldo_ranges, .nranges = nitems(as3722_ldo_ranges), }, { .id = AS3722_REG_ID_LDO3, .name = "ldo3", .supply_name = "vin-ldo3-4", .volt_reg = AS3722_LDO3_VOLTAGE, .volt_vsel_mask = AS3722_LDO3_VSEL_MASK, .enable_reg = AS3722_LDO_CONTROL0, .enable_mask = AS3722_LDO3_CTRL, .ext_enable_reg = AS3722_ENABLE_CTRL3, .ext_enable_mask = AS3722_LDO3_EXT_ENABLE_MASK, .ranges = as3722_ldo3_ranges, .nranges = nitems(as3722_ldo3_ranges), }, { .id = AS3722_REG_ID_LDO4, .name = "ldo4", .supply_name = "vin-ldo3-4", .volt_reg = AS3722_LDO4_VOLTAGE, .volt_vsel_mask = AS3722_LDO_VSEL_MASK, .enable_reg = AS3722_LDO_CONTROL0, .enable_mask = AS3722_LDO4_CTRL, .ext_enable_reg = AS3722_ENABLE_CTRL4, .ext_enable_mask = AS3722_LDO4_EXT_ENABLE_MASK, .ranges = as3722_ldo_ranges, .nranges = nitems(as3722_ldo_ranges), }, { .id = AS3722_REG_ID_LDO5, .name = "ldo5", .supply_name = "vin-ldo2-5-7", .volt_reg = AS3722_LDO5_VOLTAGE, .volt_vsel_mask = AS3722_LDO_VSEL_MASK, .enable_reg = AS3722_LDO_CONTROL0, .enable_mask = AS3722_LDO5_CTRL, .ext_enable_reg = AS3722_ENABLE_CTRL4, .ext_enable_mask = AS3722_LDO5_EXT_ENABLE_MASK, .ranges = as3722_ldo_ranges, .nranges = nitems(as3722_ldo_ranges), }, { .id = AS3722_REG_ID_LDO6, .name = "ldo6", .supply_name = "vin-ldo1-6", .volt_reg = AS3722_LDO6_VOLTAGE, .volt_vsel_mask = AS3722_LDO_VSEL_MASK, .enable_reg = AS3722_LDO_CONTROL0, .enable_mask = AS3722_LDO6_CTRL, .ext_enable_reg = AS3722_ENABLE_CTRL4, .ext_enable_mask = AS3722_LDO6_EXT_ENABLE_MASK, .ranges = as3722_ldo_ranges, .nranges = nitems(as3722_ldo_ranges), }, { .id = AS3722_REG_ID_LDO7, .name = "ldo7", .supply_name = "vin-ldo2-5-7", .volt_reg = AS3722_LDO7_VOLTAGE, .volt_vsel_mask = AS3722_LDO_VSEL_MASK, .enable_reg = AS3722_LDO_CONTROL0, .enable_mask = AS3722_LDO7_CTRL, .ext_enable_reg = AS3722_ENABLE_CTRL4, .ext_enable_mask = AS3722_LDO7_EXT_ENABLE_MASK, .ranges = as3722_ldo_ranges, .nranges = nitems(as3722_ldo_ranges), }, { .id = AS3722_REG_ID_LDO9, .name = "ldo9", .supply_name = "vin-ldo9-10", .volt_reg = AS3722_LDO9_VOLTAGE, .volt_vsel_mask = AS3722_LDO_VSEL_MASK, .enable_reg = AS3722_LDO_CONTROL1, .enable_mask = AS3722_LDO9_CTRL, .ext_enable_reg = AS3722_ENABLE_CTRL5, .ext_enable_mask = AS3722_LDO9_EXT_ENABLE_MASK, .ranges = as3722_ldo_ranges, .nranges = nitems(as3722_ldo_ranges), }, { .id = AS3722_REG_ID_LDO10, .name = "ldo10", .supply_name = "vin-ldo9-10", .volt_reg = AS3722_LDO10_VOLTAGE, .volt_vsel_mask = AS3722_LDO_VSEL_MASK, .enable_reg = AS3722_LDO_CONTROL1, .enable_mask = AS3722_LDO10_CTRL, .ext_enable_reg = AS3722_ENABLE_CTRL5, .ext_enable_mask = AS3722_LDO10_EXT_ENABLE_MASK, .ranges = as3722_ldo_ranges, .nranges = nitems(as3722_ldo_ranges), }, { .id = AS3722_REG_ID_LDO11, .name = "ldo11", .supply_name = "vin-ldo11", .volt_reg = AS3722_LDO11_VOLTAGE, .volt_vsel_mask = AS3722_LDO_VSEL_MASK, .enable_reg = AS3722_LDO_CONTROL1, .enable_mask = AS3722_LDO11_CTRL, .ext_enable_reg = AS3722_ENABLE_CTRL5, .ext_enable_mask = AS3722_LDO11_EXT_ENABLE_MASK, .ranges = as3722_ldo_ranges, .nranges = nitems(as3722_ldo_ranges), }, }; struct as3722_regnode_init_def { struct regnode_init_def reg_init_def; int ext_control; int enable_tracking; }; static int as3722_regnode_init(struct regnode *regnode); static int as3722_regnode_enable(struct regnode *regnode, bool enable, int *udelay); static int as3722_regnode_set_volt(struct regnode *regnode, int min_uvolt, int max_uvolt, int *udelay); static int as3722_regnode_get_volt(struct regnode *regnode, int *uvolt); static regnode_method_t as3722_regnode_methods[] = { /* Regulator interface */ REGNODEMETHOD(regnode_init, as3722_regnode_init), REGNODEMETHOD(regnode_enable, as3722_regnode_enable), REGNODEMETHOD(regnode_set_voltage, as3722_regnode_set_volt), REGNODEMETHOD(regnode_get_voltage, as3722_regnode_get_volt), REGNODEMETHOD_END }; DEFINE_CLASS_1(as3722_regnode, as3722_regnode_class, as3722_regnode_methods, sizeof(struct as3722_reg_sc), regnode_class); static int as3722_read_sel(struct as3722_reg_sc *sc, uint8_t *sel) { int rv; rv = RD1(sc->base_sc, sc->def->volt_reg, sel); if (rv != 0) return (rv); *sel &= sc->def->volt_vsel_mask; *sel >>= ffs(sc->def->volt_vsel_mask) - 1; return (0); } static int as3722_write_sel(struct as3722_reg_sc *sc, uint8_t sel) { int rv; sel <<= ffs(sc->def->volt_vsel_mask) - 1; sel &= sc->def->volt_vsel_mask; rv = RM1(sc->base_sc, sc->def->volt_reg, sc->def->volt_vsel_mask, sel); if (rv != 0) return (rv); return (rv); } static bool as3722_sd0_is_low_voltage(struct as3722_reg_sc *sc) { uint8_t val; int rv; rv = RD1(sc->base_sc, AS3722_FUSE7, &val); if (rv != 0) return (rv); return (val & AS3722_FUSE7_SD0_LOW_VOLTAGE ? true : false); } static int as3722_reg_extreg_setup(struct as3722_reg_sc *sc, int ext_pwr_ctrl) { uint8_t val; int rv; val = ext_pwr_ctrl << (ffs(sc->def->ext_enable_mask) - 1); rv = RM1(sc->base_sc, sc->def->ext_enable_reg, sc->def->ext_enable_mask, val); return (rv); } static int as3722_reg_enable(struct as3722_reg_sc *sc) { int rv; rv = RM1(sc->base_sc, sc->def->enable_reg, sc->def->enable_mask, sc->def->enable_mask); return (rv); } static int as3722_reg_disable(struct as3722_reg_sc *sc) { int rv; rv = RM1(sc->base_sc, sc->def->enable_reg, sc->def->enable_mask, 0); return (rv); } static int as3722_regnode_init(struct regnode *regnode) { struct as3722_reg_sc *sc; int rv; sc = regnode_get_softc(regnode); sc->enable_usec = 500; if (sc->def->id == AS3722_REG_ID_SD0) { if (as3722_sd0_is_low_voltage(sc)) { sc->def->ranges = as3722_sd0_lv_ranges; sc->def->nranges = nitems(as3722_sd0_lv_ranges); } sc->enable_usec = 600; } else if (sc->def->id == AS3722_REG_ID_LDO3) { if (sc->enable_tracking) { rv = RM1(sc->base_sc, sc->def->volt_reg, AS3722_LDO3_MODE_MASK, AS3722_LDO3_MODE_PMOS_TRACKING); if (rv < 0) { device_printf(sc->base_sc->dev, "LDO3 tracking failed: %d\n", rv); return (rv); } } } if (sc->ext_control) { rv = as3722_reg_enable(sc); if (rv < 0) { device_printf(sc->base_sc->dev, "Failed to enable %s regulator: %d\n", sc->def->name, rv); return (rv); } rv = as3722_reg_extreg_setup(sc, sc->ext_control); if (rv < 0) { device_printf(sc->base_sc->dev, "%s ext control failed: %d", sc->def->name, rv); return (rv); } } return (0); } static void as3722_fdt_parse(struct as3722_softc *sc, phandle_t node, struct reg_def *def, struct as3722_regnode_init_def *init_def) { int rv; phandle_t parent, supply_node; char prop_name[64]; /* Maximum OFW property name length. */ rv = regulator_parse_ofw_stdparam(sc->dev, node, &init_def->reg_init_def); rv = OF_getencprop(node, "ams,ext-control", &init_def->ext_control, sizeof(init_def->ext_control)); if (rv <= 0) init_def->ext_control = 0; if (init_def->ext_control > 3) { device_printf(sc->dev, "Invalid value for ams,ext-control property: %d\n", init_def->ext_control); init_def->ext_control = 0; } if (OF_hasprop(node, "ams,enable-tracking")) init_def->enable_tracking = 1; /* Get parent supply. */ if (def->supply_name == NULL) return; parent = OF_parent(node); snprintf(prop_name, sizeof(prop_name), "%s-supply", def->supply_name); rv = OF_getencprop(parent, prop_name, &supply_node, sizeof(supply_node)); if (rv <= 0) return; supply_node = OF_node_from_xref(supply_node); - rv = OF_getprop_alloc(supply_node, "regulator-name", 1, + rv = OF_getprop_alloc(supply_node, "regulator-name", (void **)&init_def->reg_init_def.parent_name); if (rv <= 0) init_def->reg_init_def.parent_name = NULL; } static struct as3722_reg_sc * as3722_attach(struct as3722_softc *sc, phandle_t node, struct reg_def *def) { struct as3722_reg_sc *reg_sc; struct as3722_regnode_init_def init_def; struct regnode *regnode; bzero(&init_def, sizeof(init_def)); as3722_fdt_parse(sc, node, def, &init_def); init_def.reg_init_def.id = def->id; init_def.reg_init_def.ofw_node = node; regnode = regnode_create(sc->dev, &as3722_regnode_class, &init_def.reg_init_def); if (regnode == NULL) { device_printf(sc->dev, "Cannot create regulator.\n"); return (NULL); } reg_sc = regnode_get_softc(regnode); /* Init regulator softc. */ reg_sc->regnode = regnode; reg_sc->base_sc = sc; reg_sc->def = def; reg_sc->xref = OF_xref_from_node(node); reg_sc->param = regnode_get_stdparam(regnode); reg_sc->ext_control = init_def.ext_control; reg_sc->enable_tracking = init_def.enable_tracking; regnode_register(regnode); if (bootverbose) { int volt, rv; regnode_topo_slock(); rv = regnode_get_voltage(regnode, &volt); if (rv == ENODEV) { device_printf(sc->dev, " Regulator %s: parent doesn't exist yet.\n", regnode_get_name(regnode)); } else if (rv != 0) { device_printf(sc->dev, " Regulator %s: voltage: INVALID!!!\n", regnode_get_name(regnode)); } else { device_printf(sc->dev, " Regulator %s: voltage: %d uV\n", regnode_get_name(regnode), volt); } regnode_topo_unlock(); } return (reg_sc); } int as3722_regulator_attach(struct as3722_softc *sc, phandle_t node) { struct as3722_reg_sc *reg; phandle_t child, rnode; int i; rnode = ofw_bus_find_child(node, "regulators"); if (rnode <= 0) { device_printf(sc->dev, " Cannot find regulators subnode\n"); return (ENXIO); } sc->nregs = nitems(as3722s_def); sc->regs = malloc(sizeof(struct as3722_reg_sc *) * sc->nregs, M_AS3722_REG, M_WAITOK | M_ZERO); /* Attach all known regulators if exist in DT. */ for (i = 0; i < sc->nregs; i++) { child = ofw_bus_find_child(rnode, as3722s_def[i].name); if (child == 0) { if (bootverbose) device_printf(sc->dev, "Regulator %s missing in DT\n", as3722s_def[i].name); continue; } reg = as3722_attach(sc, child, as3722s_def + i); if (reg == NULL) { device_printf(sc->dev, "Cannot attach regulator: %s\n", as3722s_def[i].name); return (ENXIO); } sc->regs[i] = reg; } return (0); } int as3722_regulator_map(device_t dev, phandle_t xref, int ncells, pcell_t *cells, int *num) { struct as3722_softc *sc; int i; sc = device_get_softc(dev); for (i = 0; i < sc->nregs; i++) { if (sc->regs[i] == NULL) continue; if (sc->regs[i]->xref == xref) { *num = sc->regs[i]->def->id; return (0); } } return (ENXIO); } static int as3722_regnode_enable(struct regnode *regnode, bool val, int *udelay) { struct as3722_reg_sc *sc; int rv; sc = regnode_get_softc(regnode); if (val) rv = as3722_reg_enable(sc); else rv = as3722_reg_disable(sc); *udelay = sc->enable_usec; return (rv); } static int as3722_regnode_set_volt(struct regnode *regnode, int min_uvolt, int max_uvolt, int *udelay) { struct as3722_reg_sc *sc; uint8_t sel; int rv; sc = regnode_get_softc(regnode); *udelay = 0; rv = regulator_range_volt_to_sel8(sc->def->ranges, sc->def->nranges, min_uvolt, max_uvolt, &sel); if (rv != 0) return (rv); rv = as3722_write_sel(sc, sel); return (rv); } static int as3722_regnode_get_volt(struct regnode *regnode, int *uvolt) { struct as3722_reg_sc *sc; uint8_t sel; int rv; sc = regnode_get_softc(regnode); rv = as3722_read_sel(sc, &sel); if (rv != 0) return (rv); /* LDO6 have bypass. */ if (sc->def->id == AS3722_REG_ID_LDO6 && sel == AS3722_LDO6_SEL_BYPASS) return (ENOENT); rv = regulator_range_sel8_to_volt(sc->def->ranges, sc->def->nranges, sel, uvolt); return (rv); } Index: head/sys/arm/nvidia/tegra124/tegra124_xusbpadctl.c =================================================================== --- head/sys/arm/nvidia/tegra124/tegra124_xusbpadctl.c (revision 332309) +++ head/sys/arm/nvidia/tegra124/tegra124_xusbpadctl.c (revision 332310) @@ -1,1212 +1,1212 @@ /*- * Copyright (c) 2016 Michal Meloun * 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$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "phydev_if.h" /* FUSE calibration data. */ #define FUSE_XUSB_CALIB 0x0F0 #define FUSE_XUSB_CALIB_HS_CURR_LEVEL_123(x) (((x) >> 15) & 0x3F); #define FUSE_XUSB_CALIB_HS_IREF_CAP(x) (((x) >> 13) & 0x03); #define FUSE_XUSB_CALIB_HS_SQUELCH_LEVEL(x) (((x) >> 11) & 0x03); #define FUSE_XUSB_CALIB_HS_TERM_RANGE_ADJ(x) (((x) >> 7) & 0x0F); #define FUSE_XUSB_CALIB_HS_CURR_LEVEL_0(x) (((x) >> 0) & 0x3F); /* Registers. */ #define XUSB_PADCTL_USB2_PAD_MUX 0x004 #define XUSB_PADCTL_USB2_PORT_CAP 0x008 #define USB2_PORT_CAP_ULPI_PORT_INTERNAL (1 << 25) #define USB2_PORT_CAP_ULPI_PORT_CAP (1 << 24) #define USB2_PORT_CAP_PORT_REVERSE_ID(p) (1 << (3 + (p) * 4)) #define USB2_PORT_CAP_PORT_INTERNAL(p) (1 << (2 + (p) * 4)) #define USB2_PORT_CAP_PORT_CAP(p, x) (((x) & 3) << ((p) * 4)) #define USB2_PORT_CAP_PORT_CAP_OTG 0x3 #define USB2_PORT_CAP_PORT_CAP_DEVICE 0x2 #define USB2_PORT_CAP_PORT_CAP_HOST 0x1 #define USB2_PORT_CAP_PORT_CAP_DISABLED 0x0 #define XUSB_PADCTL_SS_PORT_MAP 0x014 #define SS_PORT_MAP_PORT_INTERNAL(p) (1 << (3 + (p) * 4)) #define SS_PORT_MAP_PORT_MAP(p, x) (((x) & 7) << ((p) * 4)) #define XUSB_PADCTL_ELPG_PROGRAM 0x01C #define ELPG_PROGRAM_AUX_MUX_LP0_VCORE_DOWN (1 << 26) #define ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN_EARLY (1 << 25) #define ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN (1 << 24) #define ELPG_PROGRAM_SSP_ELPG_VCORE_DOWN(x) (1 << (18 + (x) * 4)) #define ELPG_PROGRAM_SSP_ELPG_CLAMP_EN_EARLY(x) (1 << (17 + (x) * 4)) #define ELPG_PROGRAM_SSP_ELPG_CLAMP_EN(x) (1 << (16 + (x) * 4)) #define XUSB_PADCTL_IOPHY_PLL_P0_CTL1 0x040 #define IOPHY_PLL_P0_CTL1_PLL0_LOCKDET (1 << 19) #define IOPHY_PLL_P0_CTL1_REFCLK_SEL(x) (((x) & 0xF) << 12) #define IOPHY_PLL_P0_CTL1_PLL_RST (1 << 1) #define XUSB_PADCTL_IOPHY_PLL_P0_CTL2 0x044 #define IOPHY_PLL_P0_CTL2_REFCLKBUF_EN (1 << 6) #define IOPHY_PLL_P0_CTL2_TXCLKREF_EN (1 << 5) #define IOPHY_PLL_P0_CTL2_TXCLKREF_SEL (1 << 4) #define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2(x) (0x058 + (x) * 4) #define IOPHY_USB3_PAD_CTL2_CDR_CNTL(x) (((x) & 0x00FF) << 4) #define IOPHY_USB3_PAD_CTL2_RX_EQ(x) (((x) & 0xFFFF) << 8) #define IOPHY_USB3_PAD_CTL2_RX_WANDER(x) (((x) & 0x000F) << 4) #define IOPHY_USB3_PAD_CTL2_RX_TERM_CNTL(x) (((x) & 0x0003) << 2) #define IOPHY_USB3_PAD_CTL2_TX_TERM_CNTL(x) (((x) & 0x0003) << 0) #define XUSB_PADCTL_IOPHY_USB3_PAD_CTL4(x) (0x068 + (x) * 4) #define XUSB_PADCTL_USB2_OTG_PAD_CTL0(x) (0x0A0 + (x) * 4) #define USB2_OTG_PAD_CTL0_LSBIAS_SEL (1 << 23) #define USB2_OTG_PAD_CTL0_DISCON_DETECT_METHOD (1 << 22) #define USB2_OTG_PAD_CTL0_PD_ZI (1 << 21) #define USB2_OTG_PAD_CTL0_PD2 (1 << 20) #define USB2_OTG_PAD_CTL0_PD (1 << 19) #define USB2_OTG_PAD_CTL0_TERM_EN (1 << 18) #define USB2_OTG_PAD_CTL0_LS_LS_FSLEW(x) (((x) & 0x03) << 16) #define USB2_OTG_PAD_CTL0_LS_RSLEW(x) (((x) & 0x03) << 14) #define USB2_OTG_PAD_CTL0_FS_SLEW(x) (((x) & 0x03) << 12) #define USB2_OTG_PAD_CTL0_HS_SLEW(x) (((x) & 0x3F) << 6) #define USB2_OTG_PAD_CTL0_HS_CURR_LEVEL(x) (((x) & 0x3F) << 0) #define XUSB_PADCTL_USB2_OTG_PAD_CTL1(x) (0x0AC + (x) * 4) #define USB2_OTG_PAD_CTL1_RPU_RANGE_ADJ(x) (((x) & 0x3) << 11) #define USB2_OTG_PAD_CTL1_HS_IREF_CAP(x) (((x) & 0x3) << 9) #define USB2_OTG_PAD_CTL1_SPARE(x) (((x) & 0x3) << 7) #define USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ(x) (((x) & 0xF) << 3) #define USB2_OTG_PAD_CTL1_PD_DR (1 << 2) #define USB2_OTG_PAD_CTL1_PD_DISC_FORCE_POWERUP (1 << 1) #define USB2_OTG_PAD_CTL1_PD_CHRP_FORCE_POWERUP (1 << 0) #define XUSB_PADCTL_USB2_BIAS_PAD_CTL0 0x0B8 #define USB2_BIAS_PAD_CTL0_ADJRPU(x) (((x) & 0x7) << 14) #define USB2_BIAS_PAD_CTL0_PD_TRK (1 << 13) #define USB2_BIAS_PAD_CTL0_PD (1 << 12) #define USB2_BIAS_PAD_CTL0_TERM_OFFSETL(x) (((x) & 0x3) << 9) #define USB2_BIAS_PAD_CTL0_VBUS_LEVEL(x) (((x) & 0x3) << 7) #define USB2_BIAS_PAD_CTL0_HS_CHIRP_LEVEL(x) (((x) & 0x3) << 5) #define USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL(x) (((x) & 0x7) << 2) #define USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL(x) (((x) & 0x3) << 0) #define XUSB_PADCTL_HSIC_PAD0_CTL0 0x0C8 #define HSIC_PAD0_CTL0_HSIC_OPT(x) (((x) & 0xF) << 16) #define HSIC_PAD0_CTL0_TX_SLEWN(x) (((x) & 0xF) << 12) #define HSIC_PAD0_CTL0_TX_SLEWP(x) (((x) & 0xF) << 8) #define HSIC_PAD0_CTL0_TX_RTUNEN(x) (((x) & 0xF) << 4) #define HSIC_PAD0_CTL0_TX_RTUNEP(x) (((x) & 0xF) << 0) #define XUSB_PADCTL_USB3_PAD_MUX 0x134 #define USB3_PAD_MUX_PCIE_IDDQ_DISABLE(x) (1 << (1 + (x))) #define USB3_PAD_MUX_SATA_IDDQ_DISABLE (1 << 6) #define XUSB_PADCTL_IOPHY_PLL_S0_CTL1 0x138 #define IOPHY_PLL_S0_CTL1_PLL1_LOCKDET (1 << 27) #define IOPHY_PLL_S0_CTL1_PLL1_MODE (1 << 24) #define IOPHY_PLL_S0_CTL1_PLL_PWR_OVRD (1 << 3) #define IOPHY_PLL_S0_CTL1_PLL_RST_L (1 << 1) #define IOPHY_PLL_S0_CTL1_PLL_IDDQ (1 << 0) #define XUSB_PADCTL_IOPHY_PLL_S0_CTL2 0x13C #define XUSB_PADCTL_IOPHY_PLL_S0_CTL3 0x140 #define XUSB_PADCTL_IOPHY_PLL_S0_CTL4 0x144 #define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1 0x148 #define IOPHY_MISC_PAD_S0_CTL1_IDDQ_OVRD (1 << 1) #define IOPHY_MISC_PAD_S0_CTL1_IDDQ (1 << 0) #define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL2 0x14C #define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL3 0x150 #define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL4 0x154 #define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL5 0x158 #define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL6 0x15C #define WR4(_sc, _r, _v) bus_write_4((_sc)->mem_res, (_r), (_v)) #define RD4(_sc, _r) bus_read_4((_sc)->mem_res, (_r)) struct padctl_softc { device_t dev; struct resource *mem_res; hwreset_t rst; int phy_ena_cnt; /* Fuses calibration data */ uint32_t hs_curr_level_0; uint32_t hs_curr_level_123; uint32_t hs_iref_cap; uint32_t hs_term_range_adj; uint32_t hs_squelch_level; uint32_t hs_curr_level_offset; }; static struct ofw_compat_data compat_data[] = { {"nvidia,tegra124-xusb-padctl", 1}, {NULL, 0}, }; /* Ports. */ enum padctl_port_type { PADCTL_PORT_USB2, PADCTL_PORT_ULPI, PADCTL_PORT_HSIC, PADCTL_PORT_USB3, }; struct padctl_lane; struct padctl_port { enum padctl_port_type type; const char *name; const char *base_name; int idx; int (*init)(struct padctl_softc *sc, struct padctl_port *port); /* Runtime data. */ bool enabled; regulator_t supply_vbus; /* USB2, USB3 */ bool internal; /* ULPI, USB2, USB3 */ uint32_t companion; /* USB3 */ struct padctl_lane *lane; }; static int usb3_port_init(struct padctl_softc *sc, struct padctl_port *port); #define PORT(t, n, p, i) { \ .type = t, \ .name = n "-" #p, \ .base_name = n, \ .idx = p, \ .init = i, \ } static struct padctl_port ports_tbl[] = { PORT(PADCTL_PORT_USB2, "usb2", 0, NULL), PORT(PADCTL_PORT_USB2, "usb2", 1, NULL), PORT(PADCTL_PORT_USB2, "usb2", 2, NULL), PORT(PADCTL_PORT_ULPI, "ulpi", 0, NULL), PORT(PADCTL_PORT_HSIC, "hsic", 0, NULL), PORT(PADCTL_PORT_HSIC, "hsic", 1, NULL), PORT(PADCTL_PORT_USB3, "usb3", 0, usb3_port_init), PORT(PADCTL_PORT_USB3, "usb3", 1, usb3_port_init), }; /* Pads - a group of lannes. */ enum padctl_pad_type { PADCTL_PAD_USB2, PADCTL_PAD_ULPI, PADCTL_PAD_HSIC, PADCTL_PAD_PCIE, PADCTL_PAD_SATA, }; struct padctl_lane; struct padctl_pad { const char *name; enum padctl_pad_type type; int (*powerup)(struct padctl_softc *sc, struct padctl_lane *lane); int (*powerdown)(struct padctl_softc *sc, struct padctl_lane *lane); /* Runtime data. */ bool enabled; struct padctl_lane *lanes[8]; /* Safe maximum value. */ int nlanes; }; static int usb2_powerup(struct padctl_softc *sc, struct padctl_lane *lane); static int usb2_powerdown(struct padctl_softc *sc, struct padctl_lane *lane); static int pcie_powerup(struct padctl_softc *sc, struct padctl_lane *lane); static int pcie_powerdown(struct padctl_softc *sc, struct padctl_lane *lane); static int sata_powerup(struct padctl_softc *sc, struct padctl_lane *lane); static int sata_powerdown(struct padctl_softc *sc, struct padctl_lane *lane); #define PAD(n, t, u, d) { \ .name = n, \ .type = t, \ .powerup = u, \ .powerdown = d, \ } static struct padctl_pad pads_tbl[] = { PAD("usb2", PADCTL_PAD_USB2, usb2_powerup, usb2_powerdown), PAD("ulpi", PADCTL_PAD_ULPI, NULL, NULL), PAD("hsic", PADCTL_PAD_HSIC, NULL, NULL), PAD("pcie", PADCTL_PAD_PCIE, pcie_powerup, pcie_powerdown), PAD("sata", PADCTL_PAD_SATA, sata_powerup, sata_powerdown), }; /* Lanes. */ static char *otg_mux[] = {"snps", "xusb", "uart", "rsvd"}; static char *usb_mux[] = {"snps", "xusb"}; static char *pci_mux[] = {"pcie", "usb3-ss", "sata", "rsvd"}; struct padctl_lane { const char *name; int idx; bus_size_t reg; uint32_t shift; uint32_t mask; char **mux; int nmux; /* Runtime data. */ bool enabled; struct padctl_pad *pad; struct padctl_port *port; int mux_idx; }; #define LANE(n, p, r, s, m, mx) { \ .name = n "-" #p, \ .idx = p, \ .reg = r, \ .shift = s, \ .mask = m, \ .mux = mx, \ .nmux = nitems(mx), \ } static struct padctl_lane lanes_tbl[] = { LANE("usb2", 0, XUSB_PADCTL_USB2_PAD_MUX, 0, 0x3, otg_mux), LANE("usb2", 1, XUSB_PADCTL_USB2_PAD_MUX, 2, 0x3, otg_mux), LANE("usb2", 2, XUSB_PADCTL_USB2_PAD_MUX, 4, 0x3, otg_mux), LANE("ulpi", 0, XUSB_PADCTL_USB2_PAD_MUX, 12, 0x1, usb_mux), LANE("hsic", 0, XUSB_PADCTL_USB2_PAD_MUX, 14, 0x1, usb_mux), LANE("hsic", 1, XUSB_PADCTL_USB2_PAD_MUX, 15, 0x1, usb_mux), LANE("pcie", 0, XUSB_PADCTL_USB3_PAD_MUX, 16, 0x3, pci_mux), LANE("pcie", 1, XUSB_PADCTL_USB3_PAD_MUX, 18, 0x3, pci_mux), LANE("pcie", 2, XUSB_PADCTL_USB3_PAD_MUX, 20, 0x3, pci_mux), LANE("pcie", 3, XUSB_PADCTL_USB3_PAD_MUX, 22, 0x3, pci_mux), LANE("pcie", 4, XUSB_PADCTL_USB3_PAD_MUX, 24, 0x3, pci_mux), LANE("sata", 0, XUSB_PADCTL_USB3_PAD_MUX, 26, 0x3, pci_mux), }; /* Define all possible mappings for USB3 port lanes */ struct padctl_lane_map { int port_idx; enum padctl_pad_type pad_type; int lane_idx; }; #define LANE_MAP(pi, pt, li) { \ .port_idx = pi, \ .pad_type = pt, \ .lane_idx = li, \ } static struct padctl_lane_map lane_map_tbl[] = { LANE_MAP(0, PADCTL_PAD_PCIE, 0), /* port USB3-0 -> lane PCIE-0 */ LANE_MAP(1, PADCTL_PAD_PCIE, 1), /* port USB3-1 -> lane PCIE-1 */ /* -- or -- */ LANE_MAP(1, PADCTL_PAD_SATA, 0), /* port USB3-1 -> lane SATA-0 */ }; /* Phy class and methods. */ static int xusbpadctl_phy_enable(struct phynode *phy, bool enable); static phynode_method_t xusbpadctl_phynode_methods[] = { PHYNODEMETHOD(phynode_enable, xusbpadctl_phy_enable), PHYNODEMETHOD_END }; DEFINE_CLASS_1(xusbpadctl_phynode, xusbpadctl_phynode_class, xusbpadctl_phynode_methods, 0, phynode_class); static struct padctl_port *search_lane_port(struct padctl_softc *sc, struct padctl_lane *lane); /* ------------------------------------------------------------------------- * * PHY functions */ static int usb3_port_init(struct padctl_softc *sc, struct padctl_port *port) { uint32_t reg; reg = RD4(sc, XUSB_PADCTL_SS_PORT_MAP); if (port->internal) reg &= ~SS_PORT_MAP_PORT_INTERNAL(port->idx); else reg |= SS_PORT_MAP_PORT_INTERNAL(port->idx); reg &= ~SS_PORT_MAP_PORT_MAP(port->idx, ~0); reg |= SS_PORT_MAP_PORT_MAP(port->idx, port->companion); WR4(sc, XUSB_PADCTL_SS_PORT_MAP, reg); reg = RD4(sc, XUSB_PADCTL_IOPHY_USB3_PAD_CTL2(port->idx)); reg &= ~IOPHY_USB3_PAD_CTL2_CDR_CNTL(~0); reg &= ~IOPHY_USB3_PAD_CTL2_RX_EQ(~0); reg &= ~IOPHY_USB3_PAD_CTL2_RX_WANDER(~0); reg |= IOPHY_USB3_PAD_CTL2_CDR_CNTL(0x24); reg |= IOPHY_USB3_PAD_CTL2_RX_EQ(0xF070); reg |= IOPHY_USB3_PAD_CTL2_RX_WANDER(0xF); WR4(sc, XUSB_PADCTL_IOPHY_USB3_PAD_CTL2(port->idx), reg); WR4(sc, XUSB_PADCTL_IOPHY_USB3_PAD_CTL4(port->idx), 0x002008EE); reg = RD4(sc, XUSB_PADCTL_ELPG_PROGRAM); reg &= ~ELPG_PROGRAM_SSP_ELPG_VCORE_DOWN(port->idx); WR4(sc, XUSB_PADCTL_ELPG_PROGRAM, reg); DELAY(100); reg = RD4(sc, XUSB_PADCTL_ELPG_PROGRAM); reg &= ~ELPG_PROGRAM_SSP_ELPG_CLAMP_EN_EARLY(port->idx); WR4(sc, XUSB_PADCTL_ELPG_PROGRAM, reg); DELAY(100); reg = RD4(sc, XUSB_PADCTL_ELPG_PROGRAM); reg &= ~ELPG_PROGRAM_SSP_ELPG_CLAMP_EN(port->idx); WR4(sc, XUSB_PADCTL_ELPG_PROGRAM, reg); DELAY(100); return (0); } static int pcie_powerup(struct padctl_softc *sc, struct padctl_lane *lane) { uint32_t reg; int i; reg = RD4(sc, XUSB_PADCTL_IOPHY_PLL_P0_CTL1); reg &= ~IOPHY_PLL_P0_CTL1_REFCLK_SEL(~0); WR4(sc, XUSB_PADCTL_IOPHY_PLL_P0_CTL1, reg); DELAY(100); reg = RD4(sc, XUSB_PADCTL_IOPHY_PLL_P0_CTL2); reg |= IOPHY_PLL_P0_CTL2_REFCLKBUF_EN; reg |= IOPHY_PLL_P0_CTL2_TXCLKREF_EN; reg |= IOPHY_PLL_P0_CTL2_TXCLKREF_SEL; WR4(sc, XUSB_PADCTL_IOPHY_PLL_P0_CTL2, reg); DELAY(100); reg = RD4(sc, XUSB_PADCTL_IOPHY_PLL_P0_CTL1); reg |= IOPHY_PLL_P0_CTL1_PLL_RST; WR4(sc, XUSB_PADCTL_IOPHY_PLL_P0_CTL1, reg); DELAY(100); for (i = 100; i > 0; i--) { reg = RD4(sc, XUSB_PADCTL_IOPHY_PLL_P0_CTL1); if (reg & IOPHY_PLL_P0_CTL1_PLL0_LOCKDET) break; DELAY(10); } if (i <= 0) { device_printf(sc->dev, "Failed to power up PCIe phy\n"); return (ETIMEDOUT); } reg = RD4(sc, XUSB_PADCTL_USB3_PAD_MUX); reg |= USB3_PAD_MUX_PCIE_IDDQ_DISABLE(lane->idx); WR4(sc, XUSB_PADCTL_USB3_PAD_MUX, reg); return (0); } static int pcie_powerdown(struct padctl_softc *sc, struct padctl_lane *lane) { uint32_t reg; reg = RD4(sc, XUSB_PADCTL_USB3_PAD_MUX); reg &= ~USB3_PAD_MUX_PCIE_IDDQ_DISABLE(lane->idx); WR4(sc, XUSB_PADCTL_USB3_PAD_MUX, reg); reg = RD4(sc, XUSB_PADCTL_IOPHY_PLL_P0_CTL1); reg &= ~IOPHY_PLL_P0_CTL1_PLL_RST; WR4(sc, XUSB_PADCTL_IOPHY_PLL_P0_CTL1, reg); DELAY(100); return (0); } static int sata_powerup(struct padctl_softc *sc, struct padctl_lane *lane) { uint32_t reg; int i; reg = RD4(sc, XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1); reg &= ~IOPHY_MISC_PAD_S0_CTL1_IDDQ_OVRD; reg &= ~IOPHY_MISC_PAD_S0_CTL1_IDDQ; WR4(sc, XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1, reg); reg = RD4(sc, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); reg &= ~IOPHY_PLL_S0_CTL1_PLL_PWR_OVRD; reg &= ~IOPHY_PLL_S0_CTL1_PLL_IDDQ; WR4(sc, XUSB_PADCTL_IOPHY_PLL_S0_CTL1, reg); reg = RD4(sc, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); reg |= IOPHY_PLL_S0_CTL1_PLL1_MODE; WR4(sc, XUSB_PADCTL_IOPHY_PLL_S0_CTL1, reg); reg = RD4(sc, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); reg |= IOPHY_PLL_S0_CTL1_PLL_RST_L; WR4(sc, XUSB_PADCTL_IOPHY_PLL_S0_CTL1, reg); for (i = 100; i >= 0; i--) { reg = RD4(sc, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); if (reg & IOPHY_PLL_S0_CTL1_PLL1_LOCKDET) break; DELAY(100); } if (i <= 0) { device_printf(sc->dev, "Failed to power up SATA phy\n"); return (ETIMEDOUT); } reg = RD4(sc, XUSB_PADCTL_USB3_PAD_MUX); reg |= IOPHY_PLL_S0_CTL1_PLL_RST_L; WR4(sc, XUSB_PADCTL_USB3_PAD_MUX, reg); reg = RD4(sc, XUSB_PADCTL_USB3_PAD_MUX); reg |= USB3_PAD_MUX_SATA_IDDQ_DISABLE; WR4(sc, XUSB_PADCTL_USB3_PAD_MUX, reg); return (0); } static int sata_powerdown(struct padctl_softc *sc, struct padctl_lane *lane) { uint32_t reg; reg = RD4(sc, XUSB_PADCTL_USB3_PAD_MUX); reg &= ~USB3_PAD_MUX_SATA_IDDQ_DISABLE; WR4(sc, XUSB_PADCTL_USB3_PAD_MUX, reg); reg = RD4(sc, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); reg &= ~IOPHY_PLL_S0_CTL1_PLL_RST_L; WR4(sc, XUSB_PADCTL_IOPHY_PLL_S0_CTL1, reg); DELAY(100); reg = RD4(sc, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); reg &= ~IOPHY_PLL_S0_CTL1_PLL1_MODE; WR4(sc, XUSB_PADCTL_IOPHY_PLL_S0_CTL1, reg); DELAY(100); reg = RD4(sc, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); reg |= IOPHY_PLL_S0_CTL1_PLL_PWR_OVRD; reg |= IOPHY_PLL_S0_CTL1_PLL_IDDQ; WR4(sc, XUSB_PADCTL_IOPHY_PLL_S0_CTL1, reg); DELAY(100); reg = RD4(sc, XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1); reg |= IOPHY_MISC_PAD_S0_CTL1_IDDQ_OVRD; reg |= IOPHY_MISC_PAD_S0_CTL1_IDDQ; WR4(sc, XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1, reg); DELAY(100); return (0); } static int usb2_powerup(struct padctl_softc *sc, struct padctl_lane *lane) { uint32_t reg; struct padctl_port *port; int rv; port = search_lane_port(sc, lane); if (port == NULL) { device_printf(sc->dev, "Cannot find port for lane: %s\n", lane->name); } reg = RD4(sc, XUSB_PADCTL_USB2_BIAS_PAD_CTL0); reg &= ~USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL(~0); reg &= ~USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL(~0); reg |= USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL(sc->hs_squelch_level); reg |= USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL(5); WR4(sc, XUSB_PADCTL_USB2_BIAS_PAD_CTL0, reg); reg = RD4(sc, XUSB_PADCTL_USB2_PORT_CAP); reg &= ~USB2_PORT_CAP_PORT_CAP(lane->idx, ~0); reg |= USB2_PORT_CAP_PORT_CAP(lane->idx, USB2_PORT_CAP_PORT_CAP_HOST); WR4(sc, XUSB_PADCTL_USB2_PORT_CAP, reg); reg = RD4(sc, XUSB_PADCTL_USB2_OTG_PAD_CTL0(lane->idx)); reg &= ~USB2_OTG_PAD_CTL0_HS_CURR_LEVEL(~0); reg &= ~USB2_OTG_PAD_CTL0_HS_SLEW(~0); reg &= ~USB2_OTG_PAD_CTL0_LS_RSLEW(~0); reg &= ~USB2_OTG_PAD_CTL0_PD; reg &= ~USB2_OTG_PAD_CTL0_PD2; reg &= ~USB2_OTG_PAD_CTL0_PD_ZI; reg |= USB2_OTG_PAD_CTL0_HS_SLEW(14); if (lane->idx == 0) { reg |= USB2_OTG_PAD_CTL0_HS_CURR_LEVEL(sc->hs_curr_level_0); reg |= USB2_OTG_PAD_CTL0_LS_RSLEW(3); } else { reg |= USB2_OTG_PAD_CTL0_HS_CURR_LEVEL(sc->hs_curr_level_123); reg |= USB2_OTG_PAD_CTL0_LS_RSLEW(0); } WR4(sc, XUSB_PADCTL_USB2_OTG_PAD_CTL0(lane->idx), reg); reg = RD4(sc, XUSB_PADCTL_USB2_OTG_PAD_CTL1(lane->idx)); reg &= ~USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ(~0); reg &= ~USB2_OTG_PAD_CTL1_HS_IREF_CAP(~0); reg &= ~USB2_OTG_PAD_CTL1_PD_DR; reg &= ~USB2_OTG_PAD_CTL1_PD_DISC_FORCE_POWERUP; reg &= ~USB2_OTG_PAD_CTL1_PD_CHRP_FORCE_POWERUP; reg |= USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ(sc->hs_term_range_adj); reg |= USB2_OTG_PAD_CTL1_HS_IREF_CAP(sc->hs_iref_cap); WR4(sc, XUSB_PADCTL_USB2_OTG_PAD_CTL1(lane->idx), reg); if (port != NULL && port->supply_vbus != NULL) { rv = regulator_enable(port->supply_vbus); if (rv != 0) { device_printf(sc->dev, "Cannot enable vbus regulator\n"); return (rv); } } reg = RD4(sc, XUSB_PADCTL_USB2_BIAS_PAD_CTL0); reg &= ~USB2_BIAS_PAD_CTL0_PD; WR4(sc, XUSB_PADCTL_USB2_BIAS_PAD_CTL0, reg); return (0); } static int usb2_powerdown(struct padctl_softc *sc, struct padctl_lane *lane) { uint32_t reg; struct padctl_port *port; int rv; port = search_lane_port(sc, lane); if (port == NULL) { device_printf(sc->dev, "Cannot find port for lane: %s\n", lane->name); } reg = RD4(sc, XUSB_PADCTL_USB2_BIAS_PAD_CTL0); reg |= USB2_BIAS_PAD_CTL0_PD; WR4(sc, XUSB_PADCTL_USB2_BIAS_PAD_CTL0, reg); if (port != NULL && port->supply_vbus != NULL) { rv = regulator_enable(port->supply_vbus); if (rv != 0) { device_printf(sc->dev, "Cannot disable vbus regulator\n"); return (rv); } } return (0); } static int phy_powerup(struct padctl_softc *sc) { uint32_t reg; reg = RD4(sc, XUSB_PADCTL_ELPG_PROGRAM); reg &= ~ELPG_PROGRAM_AUX_MUX_LP0_VCORE_DOWN; WR4(sc, XUSB_PADCTL_ELPG_PROGRAM, reg); DELAY(100); reg = RD4(sc, XUSB_PADCTL_ELPG_PROGRAM); reg &= ~ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN; WR4(sc, XUSB_PADCTL_ELPG_PROGRAM, reg); DELAY(100); reg = RD4(sc, XUSB_PADCTL_ELPG_PROGRAM); reg &= ~ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN_EARLY; WR4(sc, XUSB_PADCTL_ELPG_PROGRAM, reg); DELAY(100); return (0); } static int phy_powerdown(struct padctl_softc *sc) { uint32_t reg; reg = RD4(sc, XUSB_PADCTL_ELPG_PROGRAM); reg |= ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN_EARLY; WR4(sc, XUSB_PADCTL_ELPG_PROGRAM, reg); DELAY(100); reg = RD4(sc, XUSB_PADCTL_ELPG_PROGRAM); reg |= ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN; WR4(sc, XUSB_PADCTL_ELPG_PROGRAM, reg); DELAY(100); reg = RD4(sc, XUSB_PADCTL_ELPG_PROGRAM); reg |= ELPG_PROGRAM_AUX_MUX_LP0_VCORE_DOWN; WR4(sc, XUSB_PADCTL_ELPG_PROGRAM, reg); DELAY(100); return (0); } static int xusbpadctl_phy_enable(struct phynode *phy, bool enable) { device_t dev; intptr_t id; struct padctl_softc *sc; struct padctl_lane *lane; struct padctl_pad *pad; int rv; dev = phynode_get_device(phy); id = phynode_get_id(phy); sc = device_get_softc(dev); if (id < 0 || id >= nitems(lanes_tbl)) { device_printf(dev, "Unknown phy: %d\n", id); return (ENXIO); } lane = lanes_tbl + id; if (!lane->enabled) { device_printf(dev, "Lane is not enabled/configured: %s\n", lane->name); return (ENXIO); } pad = lane->pad; if (enable) { if (sc->phy_ena_cnt == 0) { rv = phy_powerup(sc); if (rv != 0) return (rv); } sc->phy_ena_cnt++; } if (enable) rv = pad->powerup(sc, lane); else rv = pad->powerdown(sc, lane); if (rv != 0) return (rv); if (!enable) { if (sc->phy_ena_cnt == 1) { rv = phy_powerdown(sc); if (rv != 0) return (rv); } sc->phy_ena_cnt--; } return (0); } /* ------------------------------------------------------------------------- * * FDT processing */ static struct padctl_port * search_port(struct padctl_softc *sc, char *port_name) { int i; for (i = 0; i < nitems(ports_tbl); i++) { if (strcmp(port_name, ports_tbl[i].name) == 0) return (&ports_tbl[i]); } return (NULL); } static struct padctl_port * search_lane_port(struct padctl_softc *sc, struct padctl_lane *lane) { int i; for (i = 0; i < nitems(ports_tbl); i++) { if (!ports_tbl[i].enabled) continue; if (ports_tbl[i].lane == lane) return (ports_tbl + i); } return (NULL); } static struct padctl_lane * search_lane(struct padctl_softc *sc, char *lane_name) { int i; for (i = 0; i < nitems(lanes_tbl); i++) { if (strcmp(lane_name, lanes_tbl[i].name) == 0) return (lanes_tbl + i); } return (NULL); } static struct padctl_lane * search_pad_lane(struct padctl_softc *sc, enum padctl_pad_type type, int idx) { int i; for (i = 0; i < nitems(lanes_tbl); i++) { if (!lanes_tbl[i].enabled) continue; if (type == lanes_tbl[i].pad->type && idx == lanes_tbl[i].idx) return (lanes_tbl + i); } return (NULL); } static struct padctl_lane * search_usb3_pad_lane(struct padctl_softc *sc, int idx) { int i; struct padctl_lane *lane, *tmp; lane = NULL; for (i = 0; i < nitems(lane_map_tbl); i++) { if (idx != lane_map_tbl[i].port_idx) continue; tmp = search_pad_lane(sc, lane_map_tbl[i].pad_type, lane_map_tbl[i].lane_idx); if (tmp == NULL) continue; if (strcmp(tmp->mux[tmp->mux_idx], "usb3-ss") != 0) continue; if (lane != NULL) { device_printf(sc->dev, "Duplicated mappings found for" " lanes: %s and %s\n", lane->name, tmp->name); return (NULL); } lane = tmp; } return (lane); } static struct padctl_pad * search_pad(struct padctl_softc *sc, char *pad_name) { int i; for (i = 0; i < nitems(pads_tbl); i++) { if (strcmp(pad_name, pads_tbl[i].name) == 0) return (pads_tbl + i); } return (NULL); } static int search_mux(struct padctl_softc *sc, struct padctl_lane *lane, char *fnc_name) { int i; for (i = 0; i < lane->nmux; i++) { if (strcmp(fnc_name, lane->mux[i]) == 0) return (i); } return (-1); } static int config_lane(struct padctl_softc *sc, struct padctl_lane *lane) { uint32_t reg; reg = RD4(sc, lane->reg); reg &= ~(lane->mask << lane->shift); reg |= (lane->mux_idx & lane->mask) << lane->shift; WR4(sc, lane->reg, reg); return (0); } static int process_lane(struct padctl_softc *sc, phandle_t node, struct padctl_pad *pad) { struct padctl_lane *lane; struct phynode *phynode; struct phynode_init_def phy_init; char *name; char *function; int rv; name = NULL; function = NULL; - rv = OF_getprop_alloc(node, "name", 1, (void **)&name); + rv = OF_getprop_alloc(node, "name", (void **)&name); if (rv <= 0) { device_printf(sc->dev, "Cannot read lane name.\n"); return (ENXIO); } lane = search_lane(sc, name); if (lane == NULL) { device_printf(sc->dev, "Unknown lane: %s\n", name); rv = ENXIO; goto end; } /* Read function (mux) settings. */ - rv = OF_getprop_alloc(node, "nvidia,function", 1, (void **)&function); + rv = OF_getprop_alloc(node, "nvidia,function", (void **)&function); if (rv <= 0) { device_printf(sc->dev, "Cannot read lane function.\n"); rv = ENXIO; goto end; } lane->mux_idx = search_mux(sc, lane, function); if (lane->mux_idx == ~0) { device_printf(sc->dev, "Unknown function %s for lane %s\n", function, name); rv = ENXIO; goto end; } rv = config_lane(sc, lane); if (rv != 0) { device_printf(sc->dev, "Cannot configure lane: %s: %d\n", name, rv); rv = ENXIO; goto end; } lane->pad = pad; lane->enabled = true; pad->lanes[pad->nlanes++] = lane; /* Create and register phy. */ bzero(&phy_init, sizeof(phy_init)); phy_init.id = lane - lanes_tbl; phy_init.ofw_node = node; phynode = phynode_create(sc->dev, &xusbpadctl_phynode_class, &phy_init); if (phynode == NULL) { device_printf(sc->dev, "Cannot create phy\n"); rv = ENXIO; goto end; } if (phynode_register(phynode) == NULL) { device_printf(sc->dev, "Cannot create phy\n"); return (ENXIO); } rv = 0; end: if (name != NULL) OF_prop_free(name); if (function != NULL) OF_prop_free(function); return (rv); } static int process_pad(struct padctl_softc *sc, phandle_t node) { struct padctl_pad *pad; char *name; int rv; name = NULL; - rv = OF_getprop_alloc(node, "name", 1, (void **)&name); + rv = OF_getprop_alloc(node, "name", (void **)&name); if (rv <= 0) { device_printf(sc->dev, "Cannot read pad name.\n"); return (ENXIO); } pad = search_pad(sc, name); if (pad == NULL) { device_printf(sc->dev, "Unknown pad: %s\n", name); rv = ENXIO; goto end; } /* Read and process associated lanes. */ node = ofw_bus_find_child(node, "lanes"); if (node <= 0) { device_printf(sc->dev, "Cannot find regulators subnode\n"); rv = ENXIO; goto end; } for (node = OF_child(node); node != 0; node = OF_peer(node)) { if (!ofw_bus_node_status_okay(node)) continue; rv = process_lane(sc, node, pad); if (rv != 0) goto end; } pad->enabled = true; rv = 0; end: if (name != NULL) OF_prop_free(name); return (rv); } static int process_port(struct padctl_softc *sc, phandle_t node) { struct padctl_port *port; char *name; int rv; name = NULL; - rv = OF_getprop_alloc(node, "name", 1, (void **)&name); + rv = OF_getprop_alloc(node, "name", (void **)&name); if (rv <= 0) { device_printf(sc->dev, "Cannot read port name.\n"); return (ENXIO); } port = search_port(sc, name); if (port == NULL) { device_printf(sc->dev, "Unknown port: %s\n", name); rv = ENXIO; goto end; } if (port->type == PADCTL_PORT_USB3) { rv = OF_getencprop(node, "nvidia,usb2-companion", &(port->companion), sizeof(port->companion)); if (rv <= 0) { device_printf(sc->dev, "Missing 'nvidia,usb2-companion' property " "for port: %s\n", name); rv = ENXIO; goto end; } } if (OF_hasprop(node, "vbus-supply")) { rv = regulator_get_by_ofw_property(sc->dev, 0, "vbus-supply", &port->supply_vbus); if (rv <= 0) { device_printf(sc->dev, "Cannot get 'vbus-supply' regulator " "for port: %s\n", name); rv = ENXIO; goto end; } } if (OF_hasprop(node, "nvidia,internal")) port->internal = true; /* Find assigned lane */ if (port->lane == NULL) { switch(port->type) { /* Routing is fixed for USB2, ULPI AND HSIC. */ case PADCTL_PORT_USB2: port->lane = search_pad_lane(sc, PADCTL_PAD_USB2, port->idx); break; case PADCTL_PORT_ULPI: port->lane = search_pad_lane(sc, PADCTL_PAD_ULPI, port->idx); break; case PADCTL_PORT_HSIC: port->lane = search_pad_lane(sc, PADCTL_PAD_HSIC, port->idx); break; case PADCTL_PORT_USB3: port->lane = search_usb3_pad_lane(sc, port->idx); break; } } if (port->lane == NULL) { device_printf(sc->dev, "Cannot find lane for port: %s\n", name); rv = ENXIO; goto end; } port->enabled = true; rv = 0; end: if (name != NULL) OF_prop_free(name); return (rv); } static int parse_fdt(struct padctl_softc *sc, phandle_t base_node) { phandle_t node; int rv; rv = 0; node = ofw_bus_find_child(base_node, "pads"); if (node <= 0) { device_printf(sc->dev, "Cannot find pads subnode.\n"); return (ENXIO); } for (node = OF_child(node); node != 0; node = OF_peer(node)) { if (!ofw_bus_node_status_okay(node)) continue; rv = process_pad(sc, node); if (rv != 0) return (rv); } node = ofw_bus_find_child(base_node, "ports"); if (node <= 0) { device_printf(sc->dev, "Cannot find ports subnode.\n"); return (ENXIO); } for (node = OF_child(node); node != 0; node = OF_peer(node)) { if (!ofw_bus_node_status_okay(node)) continue; rv = process_port(sc, node); if (rv != 0) return (rv); } return (0); } static void load_calibration(struct padctl_softc *sc) { uint32_t reg; /* All XUSB pad calibrations are packed into single dword.*/ reg = tegra_fuse_read_4(FUSE_XUSB_CALIB); sc->hs_curr_level_0 = FUSE_XUSB_CALIB_HS_CURR_LEVEL_0(reg); sc->hs_curr_level_123 = FUSE_XUSB_CALIB_HS_CURR_LEVEL_123(reg); sc->hs_iref_cap = FUSE_XUSB_CALIB_HS_IREF_CAP(reg); sc->hs_squelch_level = FUSE_XUSB_CALIB_HS_SQUELCH_LEVEL(reg); sc->hs_term_range_adj = FUSE_XUSB_CALIB_HS_TERM_RANGE_ADJ(reg); } /* ------------------------------------------------------------------------- * * BUS functions */ static int xusbpadctl_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, "Tegra XUSB phy"); return (BUS_PROBE_DEFAULT); } static int xusbpadctl_detach(device_t dev) { /* This device is always present. */ return (EBUSY); } static int xusbpadctl_attach(device_t dev) { struct padctl_softc * sc; int i, rid, rv; struct padctl_port *port; phandle_t node; sc = device_get_softc(dev); sc->dev = dev; node = ofw_bus_get_node(dev); rid = 0; sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->mem_res == NULL) { device_printf(dev, "Cannot allocate memory resources\n"); return (ENXIO); } rv = hwreset_get_by_ofw_name(dev, 0, "padctl", &sc->rst); if (rv != 0) { device_printf(dev, "Cannot get 'padctl' reset: %d\n", rv); return (rv); } rv = hwreset_deassert(sc->rst); if (rv != 0) { device_printf(dev, "Cannot unreset 'padctl' reset: %d\n", rv); return (rv); } load_calibration(sc); rv = parse_fdt(sc, node); if (rv != 0) { device_printf(dev, "Cannot parse fdt configuration: %d\n", rv); return (rv); } for (i = 0; i < nitems(ports_tbl); i++) { port = ports_tbl + i; if (!port->enabled) continue; if (port->init == NULL) continue; rv = port->init(sc, port); if (rv != 0) { device_printf(dev, "Cannot init port '%s'\n", port->name); return (rv); } } return (0); } static device_method_t tegra_xusbpadctl_methods[] = { /* Device interface */ DEVMETHOD(device_probe, xusbpadctl_probe), DEVMETHOD(device_attach, xusbpadctl_attach), DEVMETHOD(device_detach, xusbpadctl_detach), DEVMETHOD_END }; static devclass_t tegra_xusbpadctl_devclass; static DEFINE_CLASS_0(xusbpadctl, tegra_xusbpadctl_driver, tegra_xusbpadctl_methods, sizeof(struct padctl_softc)); EARLY_DRIVER_MODULE(tegra_xusbpadctl, simplebus, tegra_xusbpadctl_driver, tegra_xusbpadctl_devclass, NULL, NULL, 73); Index: head/sys/arm/nvidia/tegra_pinmux.c =================================================================== --- head/sys/arm/nvidia/tegra_pinmux.c (revision 332309) +++ head/sys/arm/nvidia/tegra_pinmux.c (revision 332310) @@ -1,799 +1,799 @@ /*- * Copyright (c) 2016 Michal Meloun * 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$"); /* * Pin multiplexer driver for Tegra SoCs. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Pin multipexor register. */ #define TEGRA_MUX_FUNCTION_MASK 0x03 #define TEGRA_MUX_FUNCTION_SHIFT 0 #define TEGRA_MUX_PUPD_MASK 0x03 #define TEGRA_MUX_PUPD_SHIFT 2 #define TEGRA_MUX_TRISTATE_SHIFT 4 #define TEGRA_MUX_ENABLE_INPUT_SHIFT 5 #define TEGRA_MUX_OPEN_DRAIN_SHIFT 6 #define TEGRA_MUX_LOCK_SHIFT 7 #define TEGRA_MUX_IORESET_SHIFT 8 #define TEGRA_MUX_RCV_SEL_SHIFT 9 /* Pin goup register. */ #define TEGRA_GRP_HSM_SHIFT 2 #define TEGRA_GRP_SCHMT_SHIFT 3 #define TEGRA_GRP_DRV_TYPE_SHIFT 6 #define TEGRA_GRP_DRV_TYPE_MASK 0x03 #define TEGRA_GRP_DRV_DRVDN_SLWR_SHIFT 28 #define TEGRA_GRP_DRV_DRVDN_SLWR_MASK 0x03 #define TEGRA_GRP_DRV_DRVUP_SLWF_SHIFT 30 #define TEGRA_GRP_DRV_DRVUP_SLWF_MASK 0x03 struct pinmux_softc { device_t dev; struct resource *pad_mem_res; struct resource *mux_mem_res; struct resource *mipi_mem_res; }; static struct ofw_compat_data compat_data[] = { {"nvidia,tegra124-pinmux", 1}, {NULL, 0}, }; enum prop_id { PROP_ID_PULL, PROP_ID_TRISTATE, PROP_ID_ENABLE_INPUT, PROP_ID_OPEN_DRAIN, PROP_ID_LOCK, PROP_ID_IORESET, PROP_ID_RCV_SEL, PROP_ID_HIGH_SPEED_MODE, PROP_ID_SCHMITT, PROP_ID_LOW_POWER_MODE, PROP_ID_DRIVE_DOWN_STRENGTH, PROP_ID_DRIVE_UP_STRENGTH, PROP_ID_SLEW_RATE_FALLING, PROP_ID_SLEW_RATE_RISING, PROP_ID_DRIVE_TYPE, PROP_ID_MAX_ID }; /* Numeric based parameters. */ static const struct prop_name { const char *name; enum prop_id id; } prop_names[] = { {"nvidia,pull", PROP_ID_PULL}, {"nvidia,tristate", PROP_ID_TRISTATE}, {"nvidia,enable-input", PROP_ID_ENABLE_INPUT}, {"nvidia,open-drain", PROP_ID_OPEN_DRAIN}, {"nvidia,lock", PROP_ID_LOCK}, {"nvidia,io-reset", PROP_ID_IORESET}, {"nvidia,rcv-sel", PROP_ID_RCV_SEL}, {"nvidia,high-speed-mode", PROP_ID_HIGH_SPEED_MODE}, {"nvidia,schmitt", PROP_ID_SCHMITT}, {"nvidia,low-power-mode", PROP_ID_LOW_POWER_MODE}, {"nvidia,pull-down-strength", PROP_ID_DRIVE_DOWN_STRENGTH}, {"nvidia,pull-up-strength", PROP_ID_DRIVE_UP_STRENGTH}, {"nvidia,slew-rate-falling", PROP_ID_SLEW_RATE_FALLING}, {"nvidia,slew-rate-rising", PROP_ID_SLEW_RATE_RISING}, {"nvidia,drive-type", PROP_ID_DRIVE_TYPE}, }; /* * configuration for one pin group. */ struct pincfg { char *function; int params[PROP_ID_MAX_ID]; }; #define GPIO_BANK_A 0 #define GPIO_BANK_B 1 #define GPIO_BANK_C 2 #define GPIO_BANK_D 3 #define GPIO_BANK_E 4 #define GPIO_BANK_F 5 #define GPIO_BANK_G 6 #define GPIO_BANK_H 7 #define GPIO_BANK_I 8 #define GPIO_BANK_J 9 #define GPIO_BANK_K 10 #define GPIO_BANK_L 11 #define GPIO_BANK_M 12 #define GPIO_BANK_N 13 #define GPIO_BANK_O 14 #define GPIO_BANK_P 15 #define GPIO_BANK_Q 16 #define GPIO_BANK_R 17 #define GPIO_BANK_S 18 #define GPIO_BANK_T 19 #define GPIO_BANK_U 20 #define GPIO_BANK_V 21 #define GPIO_BANK_W 22 #define GPIO_BANK_X 23 #define GPIO_BANK_Y 24 #define GPIO_BANK_Z 25 #define GPIO_BANK_AA 26 #define GPIO_BANK_BB 27 #define GPIO_BANK_CC 28 #define GPIO_BANK_DD 29 #define GPIO_BANK_EE 30 #define GPIO_BANK_FF 31 #define GPIO_NUM(b, p) (8 * (b) + (p)) struct tegra_mux { char *name; bus_size_t reg; char *functions[4]; int gpio_num; }; #define GMUX(r, gb, gi, nm, f1, f2, f3, f4) \ { \ .name = #nm, \ .reg = r, \ .gpio_num = GPIO_NUM(GPIO_BANK_##gb, gi), \ .functions = {#f1, #f2, #f3, #f4}, \ } #define FMUX(r, nm, f1, f2, f3, f4) \ { \ .name = #nm, \ .reg = r, \ .gpio_num = -1, \ .functions = {#f1, #f2, #f3, #f4}, \ } static const struct tegra_mux pin_mux_tbl[] = { GMUX(0x000, O, 1, ulpi_data0_po1, spi3, hsi, uarta, ulpi), GMUX(0x004, O, 2, ulpi_data1_po2, spi3, hsi, uarta, ulpi), GMUX(0x008, O, 3, ulpi_data2_po3, spi3, hsi, uarta, ulpi), GMUX(0x00C, O, 4, ulpi_data3_po4, spi3, hsi, uarta, ulpi), GMUX(0x010, O, 5, ulpi_data4_po5, spi2, hsi, uarta, ulpi), GMUX(0x014, O, 6, ulpi_data5_po6, spi2, hsi, uarta, ulpi), GMUX(0x018, O, 7, ulpi_data6_po7, spi2, hsi, uarta, ulpi), GMUX(0x01C, O, 0, ulpi_data7_po0, spi2, hsi, uarta, ulpi), GMUX(0x020, P, 9, ulpi_clk_py0, spi1, spi5, uartd, ulpi), GMUX(0x024, P, 1, ulpi_dir_py1, spi1, spi5, uartd, ulpi), GMUX(0x028, P, 2, ulpi_nxt_py2, spi1, spi5, uartd, ulpi), GMUX(0x02C, P, 3, ulpi_stp_py3, spi1, spi5, uartd, ulpi), GMUX(0x030, P, 0, dap3_fs_pp0, i2s2, spi5, displaya, displayb), GMUX(0x034, P, 1, dap3_din_pp1, i2s2, spi5, displaya, displayb), GMUX(0x038, P, 2, dap3_dout_pp2, i2s2, spi5, displaya, rsvd4), GMUX(0x03C, P, 3, dap3_sclk_pp3, i2s2, spi5, rsvd3, displayb), GMUX(0x040, V, 0, pv0, rsvd1, rsvd2, rsvd3, rsvd4), GMUX(0x044, V, 1, pv1, rsvd1, rsvd2, rsvd3, rsvd4), GMUX(0x048, Z, 0, sdmmc1_clk_pz0, sdmmc1, clk12, rsvd3, rsvd4), GMUX(0x04C, Z, 1, sdmmc1_cmd_pz1, sdmmc1, spdif, spi4, uarta), GMUX(0x050, Y, 4, sdmmc1_dat3_py4, sdmmc1, spdif, spi4, uarta), GMUX(0x054, Y, 5, sdmmc1_dat2_py5, sdmmc1, pwm0, spi4, uarta), GMUX(0x058, Y, 6, sdmmc1_dat1_py6, sdmmc1, pwm1, spi4, uarta), GMUX(0x05C, Y, 7, sdmmc1_dat0_py7, sdmmc1, rsvd2, spi4, uarta), GMUX(0x068, W, 5, clk2_out_pw5, extperiph2, rsvd2, rsvd3, rsvd4), GMUX(0x06C, CC, 5, clk2_req_pcc5, dap, rsvd2, rsvd3, rsvd4), GMUX(0x110, N, 7, hdmi_int_pn7, rsvd1, rsvd2, rsvd3, rsvd4), GMUX(0x114, V, 4, ddc_scl_pv4, i2c4, rsvd2, rsvd3, rsvd4), GMUX(0x118, V, 5, ddc_sda_pv5, i2c4, rsvd2, rsvd3, rsvd4), GMUX(0x164, V, 3, uart2_rxd_pc3, irda, spdif, uarta, spi4), GMUX(0x168, C, 2, uart2_txd_pc2, irda, spdif, uarta, spi4), GMUX(0x16C, J, 6, uart2_rts_n_pj6, uarta, uartb, gmi, spi4), GMUX(0x170, J, 5, uart2_cts_n_pj5, uarta, uartb, gmi, spi4), GMUX(0x174, W, 6, uart3_txd_pw6, uartc, rsvd2, gmi, spi4), GMUX(0x178, W, 7, uart3_rxd_pw7, uartc, rsvd2, gmi, spi4), GMUX(0x17C, S, 1, uart3_cts_n_pa1, uartc, sdmmc1, dtv, gmi), GMUX(0x180, C, 0, uart3_rts_n_pc0, uartc, pwm0, dtv, gmi), GMUX(0x184, U, 0, pu0, owr, uarta, gmi, rsvd4), GMUX(0x188, U, 1, pu1, rsvd1, uarta, gmi, rsvd4), GMUX(0x18C, U, 2, pu2, rsvd1, uarta, gmi, rsvd4), GMUX(0x190, U, 3, pu3, pwm0, uarta, gmi, displayb), GMUX(0x194, U, 4, pu4, pwm1, uarta, gmi, displayb), GMUX(0x198, U, 5, pu5, pwm2, uarta, gmi, displayb), GMUX(0x19C, U, 6, pu6, pwm3, uarta, rsvd3, gmi), GMUX(0x1A0, C, 5, gen1_i2c_sda_pc5, i2c1, rsvd2, rsvd3, rsvd4), GMUX(0x1A4, C, 4, gen1_i2c_scl_pc4, i2c1, rsvd2, rsvd3, rsvd4), GMUX(0x1A8, P, 3, dap4_fs_pp4, i2s3, gmi, dtv, rsvd4), GMUX(0x1AC, P, 4, dap4_din_pp5, i2s3, gmi, rsvd3, rsvd4), GMUX(0x1B0, P, 5, dap4_dout_pp6, i2s3, gmi, dtv, rsvd4), GMUX(0x1B4, P, 7, dap4_sclk_pp7, i2s3, gmi, rsvd3, rsvd4), GMUX(0x1B8, P, 0, clk3_out_pee0, extperiph3, rsvd2, rsvd3, rsvd4), GMUX(0x1BC, EE, 1, clk3_req_pee1, dev3, rsvd2, rsvd3, rsvd4), GMUX(0x1C0, C, 7, pc7, rsvd1, rsvd2, gmi, gmi_alt), GMUX(0x1C4, I, 5, pi5, sdmmc2, rsvd2, gmi, rsvd4), GMUX(0x1C8, I, 7, pi7, rsvd1, trace, gmi, dtv), GMUX(0x1CC, K, 0, pk0, rsvd1, sdmmc3, gmi, soc), GMUX(0x1D0, K, 1, pk1, sdmmc2, trace, gmi, rsvd4), GMUX(0x1D4, J, 0, pj0, rsvd1, rsvd2, gmi, usb), GMUX(0x1D8, J, 2, pj2, rsvd1, rsvd2, gmi, soc), GMUX(0x1DC, K, 3, pk3, sdmmc2, trace, gmi, ccla), GMUX(0x1E0, K, 4, pk4, sdmmc2, rsvd2, gmi, gmi_alt), GMUX(0x1E4, K, 2, pk2, rsvd1, rsvd2, gmi, rsvd4), GMUX(0x1E8, I, 3, pi3, rsvd1, rsvd2, gmi, spi4), GMUX(0x1EC, I, 6, pi6, rsvd1, rsvd2, gmi, sdmmc2), GMUX(0x1F0, G, 0, pg0, rsvd1, rsvd2, gmi, rsvd4), GMUX(0x1F4, G, 1, pg1, rsvd1, rsvd2, gmi, rsvd4), GMUX(0x1F8, G, 2, pg2, rsvd1, trace, gmi, rsvd4), GMUX(0x1FC, G, 3, pg3, rsvd1, trace, gmi, rsvd4), GMUX(0x200, G, 4, pg4, rsvd1, tmds, gmi, spi4), GMUX(0x204, G, 5, pg5, rsvd1, rsvd2, gmi, spi4), GMUX(0x208, G, 6, pg6, rsvd1, rsvd2, gmi, spi4), GMUX(0x20C, G, 7, pg7, rsvd1, rsvd2, gmi, spi4), GMUX(0x210, H, 0, ph0, pwm0, trace, gmi, dtv), GMUX(0x214, H, 1, ph1, pwm1, tmds, gmi, displaya), GMUX(0x218, H, 2, ph2, pwm2, tmds, gmi, cldvfs), GMUX(0x21C, H, 3, ph3, pwm3, spi4, gmi, cldvfs), GMUX(0x220, H, 4, ph4, sdmmc2, rsvd2, gmi, rsvd4), GMUX(0x224, H, 5, ph5, sdmmc2, rsvd2, gmi, rsvd4), GMUX(0x228, H, 6, ph6, sdmmc2, trace, gmi, dtv), GMUX(0x22C, H, 7, ph7, sdmmc2, trace, gmi, dtv), GMUX(0x230, J, 7, pj7, uartd, rsvd2, gmi, gmi_alt), GMUX(0x234, B, 0, pb0, uartd, rsvd2, gmi, rsvd4), GMUX(0x238, B, 1, pb1, uartd, rsvd2, gmi, rsvd4), GMUX(0x23C, K, 7, pk7, uartd, rsvd2, gmi, rsvd4), GMUX(0x240, I, 0, pi0, rsvd1, rsvd2, gmi, rsvd4), GMUX(0x244, I, 1, pi1, rsvd1, rsvd2, gmi, rsvd4), GMUX(0x248, I, 2, pi2, sdmmc2, trace, gmi, rsvd4), GMUX(0x24C, I, 4, pi4, spi4, trace, gmi, displaya), GMUX(0x250, T, 5, gen2_i2c_scl_pt5, i2c2, rsvd2, gmi, rsvd4), GMUX(0x254, T, 6, gen2_i2c_sda_pt6, i2c2, rsvd2, gmi, rsvd4), GMUX(0x258, CC, 4, sdmmc4_clk_pcc4, sdmmc4, rsvd2, gmi, rsvd4), GMUX(0x25C, T, 7, sdmmc4_cmd_pt7, sdmmc4, rsvd2, gmi, rsvd4), GMUX(0x260, AA, 0, sdmmc4_dat0_paa0, sdmmc4, spi3, gmi, rsvd4), GMUX(0x264, AA, 1, sdmmc4_dat1_paa1, sdmmc4, spi3, gmi, rsvd4), GMUX(0x268, AA, 2, sdmmc4_dat2_paa2, sdmmc4, spi3, gmi, rsvd4), GMUX(0x26C, AA, 3, sdmmc4_dat3_paa3, sdmmc4, spi3, gmi, rsvd4), GMUX(0x270, AA, 4, sdmmc4_dat4_paa4, sdmmc4, spi3, gmi, rsvd4), GMUX(0x274, AA, 5, sdmmc4_dat5_paa5, sdmmc4, spi3, rsvd3, rsvd4), GMUX(0x278, AA, 6, sdmmc4_dat6_paa6, sdmmc4, spi3, gmi, rsvd4), GMUX(0x27C, AA, 7, sdmmc4_dat7_paa7, sdmmc4, rsvd2, gmi, rsvd4), GMUX(0x284, CC, 0, cam_mclk_pcc0, vi, vi_alt1, vi_alt3, sdmmc2), GMUX(0x288, CC, 1, pcc1, i2s4, rsvd2, rsvd3, sdmmc2), GMUX(0x28C, BB, 0, pbb0, vgp6, vimclk2, sdmmc2, vimclk2_alt), GMUX(0x290, BB, 1, cam_i2c_scl_pbb1, vgp1, i2c3, rsvd3, sdmmc2), GMUX(0x294, BB, 2, cam_i2c_sda_pbb2, vgp2, i2c3, rsvd3, sdmmc2), GMUX(0x298, BB, 3, pbb3, vgp3, displaya, displayb, sdmmc2), GMUX(0x29C, BB, 4, pbb4, vgp4, displaya, displayb, sdmmc2), GMUX(0x2A0, BB, 5, pbb5, vgp5, displaya, rsvd3, sdmmc2), GMUX(0x2A4, BB, 6, pbb6, i2s4, rsvd2, displayb, sdmmc2), GMUX(0x2A8, BB, 7, pbb7, i2s4, rsvd2, rsvd3, sdmmc2), GMUX(0x2AC, CC, 2, pcc2, i2s4, rsvd2, sdmmc3, sdmmc2), FMUX(0x2B0, jtag_rtck, rtck, rsvd2, rsvd3, rsvd4), GMUX(0x2B4, Z, 6, pwr_i2c_scl_pz6, i2cpwr, rsvd2, rsvd3, rsvd4), GMUX(0x2B8, Z, 7, pwr_i2c_sda_pz7, i2cpwr, rsvd2, rsvd3, rsvd4), GMUX(0x2BC, R, 0, kb_row0_pr0, kbc, rsvd2, rsvd3, rsvd4), GMUX(0x2C0, R, 1, kb_row1_pr1, kbc, rsvd2, rsvd3, rsvd4), GMUX(0x2C4, R, 2, kb_row2_pr2, kbc, rsvd2, rsvd3, rsvd4), GMUX(0x2C8, R, 3, kb_row3_pr3, kbc, displaya, sys, displayb), GMUX(0x2CC, R, 4, kb_row4_pr4, kbc, displaya, rsvd3, displayb), GMUX(0x2D0, R, 5, kb_row5_pr5, kbc, displaya, rsvd3, displayb), GMUX(0x2D4, R, 6, kb_row6_pr6, kbc, displaya, displaya_alt, displayb), GMUX(0x2D8, R, 7, kb_row7_pr7, kbc, rsvd2, cldvfs, uarta), GMUX(0x2DC, S, 0, kb_row8_ps0, kbc, rsvd2, cldvfs, uarta), GMUX(0x2E0, S, 1, kb_row9_ps1, kbc, rsvd2, rsvd3, uarta), GMUX(0x2E4, S, 2, kb_row10_ps2, kbc, rsvd2, rsvd3, uarta), GMUX(0x2E8, S, 3, kb_row11_ps3, kbc, rsvd2, rsvd3, irda), GMUX(0x2EC, S, 4, kb_row12_ps4, kbc, rsvd2, rsvd3, irda), GMUX(0x2F0, S, 5, kb_row13_ps5, kbc, rsvd2, spi2, rsvd4), GMUX(0x2F4, S, 6, kb_row14_ps6, kbc, rsvd2, spi2, rsvd4), GMUX(0x2F8, S, 7, kb_row15_ps7, kbc, soc, rsvd3, rsvd4), GMUX(0x2FC, Q, 0, kb_col0_pq0, kbc, rsvd2, spi2, rsvd4), GMUX(0x300, Q, 1, kb_col1_pq1, kbc, rsvd2, spi2, rsvd4), GMUX(0x304, Q, 2, kb_col2_pq2, kbc, rsvd2, spi2, rsvd4), GMUX(0x308, Q, 3, kb_col3_pq3, kbc, displaya, pwm2, uarta), GMUX(0x30C, Q, 4, kb_col4_pq4, kbc, owr, sdmmc3, uarta), GMUX(0x310, Q, 5, kb_col5_pq5, kbc, rsvd2, sdmmc3, rsvd4), GMUX(0x314, Q, 6, kb_col6_pq6, kbc, rsvd2, spi2, uartd), GMUX(0x318, Q, 7, kb_col7_pq7, kbc, rsvd2, spi2, uartd), GMUX(0x31C, A, 0, clk_32k_out_pa0, blink, soc, rsvd3, rsvd4), FMUX(0x324, core_pwr_req, pwron, rsvd2, rsvd3, rsvd4), FMUX(0x328, cpu_pwr_req, cpu, rsvd2, rsvd3, rsvd4), FMUX(0x32C, pwr_int_n, pmi, rsvd2, rsvd3, rsvd4), FMUX(0x330, clk_32k_in, clk, rsvd2, rsvd3, rsvd4), FMUX(0x334, owr, owr, rsvd2, rsvd3, rsvd4), GMUX(0x338, N, 0, dap1_fs_pn0, i2s0, hda, gmi, rsvd4), GMUX(0x33C, N, 1, dap1_din_pn1, i2s0, hda, gmi, rsvd4), GMUX(0x340, N, 2, dap1_dout_pn2, i2s0, hda, gmi, sata), GMUX(0x344, N, 3, dap1_sclk_pn3, i2s0, hda, gmi, rsvd4), GMUX(0x348, EE, 2, dap_mclk1_req_pee2, dap, dap1, sata, rsvd4), GMUX(0x34C, W, 4, dap_mclk1_pw4, extperiph1, dap2, rsvd3, rsvd4), GMUX(0x350, K, 6, spdif_in_pk6, spdif, rsvd2, rsvd3, i2c3), GMUX(0x354, K, 5, spdif_out_pk5, spdif, rsvd2, rsvd3, i2c3), GMUX(0x358, A, 2, dap2_fs_pa2, i2s1, hda, gmi, rsvd4), GMUX(0x35C, A, 4, dap2_din_pa4, i2s1, hda, gmi, rsvd4), GMUX(0x360, A, 5, dap2_dout_pa5, i2s1, hda, gmi, rsvd4), GMUX(0x364, A, 3, dap2_sclk_pa3, i2s1, hda, gmi, rsvd4), GMUX(0x368, X, 0, dvfs_pwm_px0, spi6, cldvfs, gmi, rsvd4), GMUX(0x36C, X, 1, gpio_x1_aud_px1, spi6, rsvd2, gmi, rsvd4), GMUX(0x370, X, 3, gpio_x3_aud_px3, spi6, spi1, gmi, rsvd4), GMUX(0x374, X, 2, dvfs_clk_px2, spi6, cldvfs, gmi, rsvd4), GMUX(0x378, X, 4, gpio_x4_aud_px4, gmi, spi1, spi2, dap2), GMUX(0x37C, X, 5, gpio_x5_aud_px5, gmi, spi1, spi2, rsvd4), GMUX(0x380, X, 6, gpio_x6_aud_px6, spi6, spi1, spi2, gmi), GMUX(0x384, X, 7, gpio_x7_aud_px7, rsvd1, spi1, spi2, rsvd4), GMUX(0x390, A, 6, sdmmc3_clk_pa6, sdmmc3, rsvd2, rsvd3, spi3), GMUX(0x394, A, 7, sdmmc3_cmd_pa7, sdmmc3, pwm3, uarta, spi3), GMUX(0x398, B, 7, sdmmc3_dat0_pb7, sdmmc3, rsvd2, rsvd3, spi3), GMUX(0x39C, B, 6, sdmmc3_dat1_pb6, sdmmc3, pwm2, uarta, spi3), GMUX(0x3A0, B, 5, sdmmc3_dat2_pb5, sdmmc3, pwm1, displaya, spi3), GMUX(0x3A4, B, 4, sdmmc3_dat3_pb4, sdmmc3, pwm0, displayb, spi3), GMUX(0x3BC, DD, 1, pex_l0_rst_n_pdd1, pe0, rsvd2, rsvd3, rsvd4), GMUX(0x3C0, DD, 2, pex_l0_clkreq_n_pdd2, pe0, rsvd2, rsvd3, rsvd4), GMUX(0x3C4, DD, 3, pex_wake_n_pdd3, pe, rsvd2, rsvd3, rsvd4), GMUX(0x3CC, DD, 5, pex_l1_rst_n_pdd5, pe1, rsvd2, rsvd3, rsvd4), GMUX(0x3D0, DD, 6, pex_l1_clkreq_n_pdd6, pe1, rsvd2, rsvd3, rsvd4), GMUX(0x3E0, EE, 3, hdmi_cec_pee3, cec, rsvd2, rsvd3, rsvd4), GMUX(0x3E4, V, 3, sdmmc1_wp_n_pv3, sdmmc1, clk12, spi4, uarta), GMUX(0x3E8, V, 2, sdmmc3_cd_n_pv2, sdmmc3, owr, rsvd3, rsvd4), GMUX(0x3EC, W, 2, gpio_w2_aud_pw2, spi6, rsvd2, spi2, i2c1), GMUX(0x3F0, W, 3, gpio_w3_aud_pw3, spi6, spi1, spi2, i2c1), GMUX(0x3F4, N, 4, usb_vbus_en0_pn4, usb, rsvd2, rsvd3, rsvd4), GMUX(0x3F8, N, 5, usb_vbus_en1_pn5, usb, rsvd2, rsvd3, rsvd4), GMUX(0x3FC, EE, 5, sdmmc3_clk_lb_in_pee5, sdmmc3, rsvd2, rsvd3, rsvd4), GMUX(0x400, EE, 4, sdmmc3_clk_lb_out_pee4, sdmmc3, rsvd2, rsvd3, rsvd4), FMUX(0x404, gmi_clk_lb, sdmmc2, rsvd2, gmi, rsvd4), FMUX(0x408, reset_out_n, rsvd1, rsvd2, rsvd3, reset_out_n), GMUX(0x40C, T, 0, kb_row16_pt0, kbc, rsvd2, rsvd3, uartc), GMUX(0x410, T, 1, kb_row17_pt1, kbc, rsvd2, rsvd3, uartc), GMUX(0x414, FF, 1, usb_vbus_en2_pff1, usb, rsvd2, rsvd3, rsvd4), GMUX(0x418, FF, 2, pff2, sata, rsvd2, rsvd3, rsvd4), GMUX(0x430, FF, 0, dp_hpd_pff0, dp, rsvd2, rsvd3, rsvd4), }; struct tegra_grp { char *name; bus_size_t reg; int drvdn_shift; int drvdn_mask; int drvup_shift; int drvup_mask; }; #define GRP(r, nm, dn_s, dn_w, up_s, up_w) \ { \ .name = #nm, \ .reg = r - 0x868, \ .drvdn_shift = dn_s, \ .drvdn_mask = (1 << dn_w) - 1, \ .drvup_shift = up_s, \ .drvup_mask = (1 << dn_w) - 1, \ } /* Use register offsets from TRM */ static const struct tegra_grp pin_grp_tbl[] = { GRP(0x868, ao1, 12, 5, 20, 5), GRP(0x86C, ao2, 12, 5, 20, 5), GRP(0x870, at1, 12, 7, 20, 7), GRP(0x874, at2, 12, 7, 20, 7), GRP(0x878, at3, 12, 7, 20, 7), GRP(0x87C, at4, 12, 7, 20, 7), GRP(0x880, at5, 14, 5, 19, 5), GRP(0x884, cdev1, 12, 5, 20, 5), GRP(0x888, cdev2, 12, 5, 20, 5), GRP(0x890, dap1, 12, 5, 20, 5), GRP(0x894, dap2, 12, 5, 20, 5), GRP(0x898, dap3, 12, 5, 20, 5), GRP(0x89C, dap4, 12, 5, 20, 5), GRP(0x8A0, dbg, 12, 5, 20, 5), GRP(0x8B0, sdio3, 12, 7, 20, 7), GRP(0x8B4, spi, 12, 5, 20, 5), GRP(0x8B8, uaa, 12, 5, 20, 5), GRP(0x8BC, uab, 12, 5, 20, 5), GRP(0x8C0, uart2, 12, 5, 20, 5), GRP(0x8C4, uart3, 12, 5, 20, 5), GRP(0x8EC, sdio1, 12, 7, 20, 7), GRP(0x8FC, ddc, 12, 5, 20, 5), GRP(0x900, gma, 14, 5, 20, 5), GRP(0x910, gme, 14, 5, 19, 5), GRP(0x914, gmf, 14, 5, 19, 5), GRP(0x918, gmg, 14, 5, 19, 5), GRP(0x91C, gmh, 14, 5, 19, 5), GRP(0x920, owr, 12, 5, 20, 5), GRP(0x924, uda, 12, 5, 20, 5), GRP(0x928, gpv, 12, 5, 20, 5), GRP(0x92C, dev3, 12, 5, 20, 5), GRP(0x938, cec, 12, 5, 20, 5), GRP(0x994, at6, 12, 7, 20, 7), GRP(0x998, dap5, 12, 5, 20, 5), GRP(0x99C, usb_vbus_en, 12, 5, 20, 5), GRP(0x9A8, ao3, 12, 5, -1, 0), GRP(0x9B0, ao0, 12, 5, 20, 5), GRP(0x9B4, hv0, 12, 5, -1, 0), GRP(0x9C4, sdio4, 12, 5, 20, 5), GRP(0x9C8, ao4, 12, 7, 20, 7), }; static const struct tegra_grp * pinmux_search_grp(char *grp_name) { int i; for (i = 0; i < nitems(pin_grp_tbl); i++) { if (strcmp(grp_name, pin_grp_tbl[i].name) == 0) return (&pin_grp_tbl[i]); } return (NULL); } static const struct tegra_mux * pinmux_search_mux(char *pin_name) { int i; for (i = 0; i < nitems(pin_mux_tbl); i++) { if (strcmp(pin_name, pin_mux_tbl[i].name) == 0) return (&pin_mux_tbl[i]); } return (NULL); } static int pinmux_mux_function(const struct tegra_mux *mux, char *fnc_name) { int i; for (i = 0; i < 4; i++) { if (strcmp(fnc_name, mux->functions[i]) == 0) return (i); } return (-1); } static int pinmux_config_mux(struct pinmux_softc *sc, char *pin_name, const struct tegra_mux *mux, struct pincfg *cfg) { int tmp; uint32_t reg; reg = bus_read_4(sc->mux_mem_res, mux->reg); if (cfg->function != NULL) { tmp = pinmux_mux_function(mux, cfg->function); if (tmp == -1) { device_printf(sc->dev, "Unknown function %s for pin %s\n", cfg->function, pin_name); return (ENXIO); } reg &= ~(TEGRA_MUX_FUNCTION_MASK << TEGRA_MUX_FUNCTION_SHIFT); reg |= (tmp & TEGRA_MUX_FUNCTION_MASK) << TEGRA_MUX_FUNCTION_SHIFT; } if (cfg->params[PROP_ID_PULL] != -1) { reg &= ~(TEGRA_MUX_PUPD_MASK << TEGRA_MUX_PUPD_SHIFT); reg |= (cfg->params[PROP_ID_PULL] & TEGRA_MUX_PUPD_MASK) << TEGRA_MUX_PUPD_SHIFT; } if (cfg->params[PROP_ID_TRISTATE] != -1) { reg &= ~(1 << TEGRA_MUX_TRISTATE_SHIFT); reg |= (cfg->params[PROP_ID_TRISTATE] & 1) << TEGRA_MUX_TRISTATE_SHIFT; } if (cfg->params[TEGRA_MUX_ENABLE_INPUT_SHIFT] != -1) { reg &= ~(1 << TEGRA_MUX_ENABLE_INPUT_SHIFT); reg |= (cfg->params[TEGRA_MUX_ENABLE_INPUT_SHIFT] & 1) << TEGRA_MUX_ENABLE_INPUT_SHIFT; } if (cfg->params[PROP_ID_ENABLE_INPUT] != -1) { reg &= ~(1 << TEGRA_MUX_ENABLE_INPUT_SHIFT); reg |= (cfg->params[PROP_ID_ENABLE_INPUT] & 1) << TEGRA_MUX_ENABLE_INPUT_SHIFT; } if (cfg->params[PROP_ID_ENABLE_INPUT] != -1) { reg &= ~(1 << TEGRA_MUX_ENABLE_INPUT_SHIFT); reg |= (cfg->params[PROP_ID_OPEN_DRAIN] & 1) << TEGRA_MUX_ENABLE_INPUT_SHIFT; } if (cfg->params[PROP_ID_LOCK] != -1) { reg &= ~(1 << TEGRA_MUX_LOCK_SHIFT); reg |= (cfg->params[PROP_ID_LOCK] & 1) << TEGRA_MUX_LOCK_SHIFT; } if (cfg->params[PROP_ID_IORESET] != -1) { reg &= ~(1 << TEGRA_MUX_IORESET_SHIFT); reg |= (cfg->params[PROP_ID_IORESET] & 1) << TEGRA_MUX_IORESET_SHIFT; } if (cfg->params[PROP_ID_RCV_SEL] != -1) { reg &= ~(1 << TEGRA_MUX_RCV_SEL_SHIFT); reg |= (cfg->params[PROP_ID_RCV_SEL] & 1) << TEGRA_MUX_RCV_SEL_SHIFT; } bus_write_4(sc->mux_mem_res, mux->reg, reg); return (0); } static int pinmux_config_grp(struct pinmux_softc *sc, char *grp_name, const struct tegra_grp *grp, struct pincfg *cfg) { uint32_t reg; reg = bus_read_4(sc->pad_mem_res, grp->reg); if (cfg->params[PROP_ID_HIGH_SPEED_MODE] != -1) { reg &= ~(1 << TEGRA_GRP_HSM_SHIFT); reg |= (cfg->params[PROP_ID_HIGH_SPEED_MODE] & 1) << TEGRA_GRP_HSM_SHIFT; } if (cfg->params[PROP_ID_SCHMITT] != -1) { reg &= ~(1 << TEGRA_GRP_SCHMT_SHIFT); reg |= (cfg->params[PROP_ID_SCHMITT] & 1) << TEGRA_GRP_SCHMT_SHIFT; } if (cfg->params[PROP_ID_DRIVE_TYPE] != -1) { reg &= ~(TEGRA_GRP_DRV_TYPE_MASK << TEGRA_GRP_DRV_TYPE_SHIFT); reg |= (cfg->params[PROP_ID_DRIVE_TYPE] & TEGRA_GRP_DRV_TYPE_MASK) << TEGRA_GRP_DRV_TYPE_SHIFT; } if (cfg->params[PROP_ID_SLEW_RATE_RISING] != -1) { reg &= ~(TEGRA_GRP_DRV_DRVDN_SLWR_MASK << TEGRA_GRP_DRV_DRVDN_SLWR_SHIFT); reg |= (cfg->params[PROP_ID_SLEW_RATE_RISING] & TEGRA_GRP_DRV_DRVDN_SLWR_MASK) << TEGRA_GRP_DRV_DRVDN_SLWR_SHIFT; } if (cfg->params[PROP_ID_SLEW_RATE_FALLING] != -1) { reg &= ~(TEGRA_GRP_DRV_DRVUP_SLWF_MASK << TEGRA_GRP_DRV_DRVUP_SLWF_SHIFT); reg |= (cfg->params[PROP_ID_SLEW_RATE_FALLING] & TEGRA_GRP_DRV_DRVUP_SLWF_MASK) << TEGRA_GRP_DRV_DRVUP_SLWF_SHIFT; } if ((cfg->params[PROP_ID_DRIVE_DOWN_STRENGTH] != -1) && (grp->drvdn_mask != -1)) { reg &= ~(grp->drvdn_shift << grp->drvdn_mask); reg |= (cfg->params[PROP_ID_DRIVE_DOWN_STRENGTH] & grp->drvdn_mask) << grp->drvdn_shift; } if ((cfg->params[PROP_ID_DRIVE_UP_STRENGTH] != -1) && (grp->drvup_mask != -1)) { reg &= ~(grp->drvup_shift << grp->drvup_mask); reg |= (cfg->params[PROP_ID_DRIVE_UP_STRENGTH] & grp->drvup_mask) << grp->drvup_shift; } bus_write_4(sc->pad_mem_res, grp->reg, reg); return (0); } static int pinmux_config_node(struct pinmux_softc *sc, char *pin_name, struct pincfg *cfg) { const struct tegra_mux *mux; const struct tegra_grp *grp; uint32_t reg; int rv; /* Handle MIPI special case first */ if (strcmp(pin_name, "dsi_b") == 0) { if (cfg->function == NULL) { /* nothing to set */ return (0); } reg = bus_read_4(sc->mipi_mem_res, 0); /* register 0x820 */ if (strcmp(cfg->function, "csi") == 0) reg &= ~(1 << 1); else if (strcmp(cfg->function, "dsi_b") == 0) reg |= (1 << 1); bus_write_4(sc->mipi_mem_res, 0, reg); /* register 0x820 */ } /* Handle pin muxes */ mux = pinmux_search_mux(pin_name); if (mux != NULL) { if (mux->gpio_num != -1) { /* XXXX TODO: Reserve gpio here */ } rv = pinmux_config_mux(sc, pin_name, mux, cfg); return (rv); } /* Handle pin groups */ grp = pinmux_search_grp(pin_name); if (grp != NULL) { rv = pinmux_config_grp(sc, pin_name, grp, cfg); return (rv); } device_printf(sc->dev, "Unknown pin: %s\n", pin_name); return (ENXIO); } static int pinmux_read_node(struct pinmux_softc *sc, phandle_t node, struct pincfg *cfg, char **pins, int *lpins) { int rv, i; - *lpins = OF_getprop_alloc(node, "nvidia,pins", 1, (void **)pins); + *lpins = OF_getprop_alloc(node, "nvidia,pins", (void **)pins); if (*lpins <= 0) return (ENOENT); /* Read function (mux) settings. */ - rv = OF_getprop_alloc(node, "nvidia,function", 1, + rv = OF_getprop_alloc(node, "nvidia,function", (void **)&cfg->function); if (rv <= 0) cfg->function = NULL; /* Read numeric properties. */ for (i = 0; i < PROP_ID_MAX_ID; i++) { rv = OF_getencprop(node, prop_names[i].name, &cfg->params[i], sizeof(cfg->params[i])); if (rv <= 0) cfg->params[i] = -1; } return (0); } static int pinmux_process_node(struct pinmux_softc *sc, phandle_t node) { struct pincfg cfg; char *pins, *pname; int i, len, lpins, rv; rv = pinmux_read_node(sc, node, &cfg, &pins, &lpins); if (rv != 0) return (rv); len = 0; pname = pins; do { i = strlen(pname) + 1; rv = pinmux_config_node(sc, pname, &cfg); if (rv != 0) device_printf(sc->dev, "Cannot configure pin: %s: %d\n", pname, rv); len += i; pname += i; } while (len < lpins); if (pins != NULL) OF_prop_free(pins); if (cfg.function != NULL) OF_prop_free(cfg.function); return (rv); } static int pinmux_configure(device_t dev, phandle_t cfgxref) { struct pinmux_softc *sc; phandle_t node, cfgnode; int rv; sc = device_get_softc(dev); cfgnode = OF_node_from_xref(cfgxref); for (node = OF_child(cfgnode); node != 0; node = OF_peer(node)) { if (!ofw_bus_node_status_okay(node)) continue; rv = pinmux_process_node(sc, node); } return (0); } static int pinmux_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, "Tegra pin configuration"); return (BUS_PROBE_DEFAULT); } static int pinmux_detach(device_t dev) { /* This device is always present. */ return (EBUSY); } static int pinmux_attach(device_t dev) { struct pinmux_softc * sc; int rid; sc = device_get_softc(dev); sc->dev = dev; rid = 0; sc->pad_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->pad_mem_res == NULL) { device_printf(dev, "Cannot allocate memory resources\n"); return (ENXIO); } rid = 1; sc->mux_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->mux_mem_res == NULL) { device_printf(dev, "Cannot allocate memory resources\n"); return (ENXIO); } rid = 2; sc->mipi_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->mipi_mem_res == NULL) { device_printf(dev, "Cannot allocate memory resources\n"); return (ENXIO); } /* Register as a pinctrl device and process default configuration */ fdt_pinctrl_register(dev, NULL); fdt_pinctrl_configure_by_name(dev, "boot"); return (0); } static device_method_t tegra_pinmux_methods[] = { /* Device interface */ DEVMETHOD(device_probe, pinmux_probe), DEVMETHOD(device_attach, pinmux_attach), DEVMETHOD(device_detach, pinmux_detach), /* fdt_pinctrl interface */ DEVMETHOD(fdt_pinctrl_configure,pinmux_configure), DEVMETHOD_END }; static devclass_t tegra_pinmux_devclass; static DEFINE_CLASS_0(pinmux, tegra_pinmux_driver, tegra_pinmux_methods, sizeof(struct pinmux_softc)); EARLY_DRIVER_MODULE(tegra_pinmux, simplebus, tegra_pinmux_driver, tegra_pinmux_devclass, NULL, NULL, 71); Index: head/sys/arm/nvidia/tegra_usbphy.c =================================================================== --- head/sys/arm/nvidia/tegra_usbphy.c (revision 332309) +++ head/sys/arm/nvidia/tegra_usbphy.c (revision 332310) @@ -1,857 +1,857 @@ /*- * Copyright (c) 2016 Michal Meloun * 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$"); /* * USB phy driver for Tegra SoCs. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "phynode_if.h" #define CTRL_ICUSB_CTRL 0x15c #define ICUSB_CTR_IC_ENB1 (1 << 3) #define CTRL_USB_USBMODE 0x1f8 #define USB_USBMODE_MASK (3 << 0) #define USB_USBMODE_HOST (3 << 0) #define USB_USBMODE_DEVICE (2 << 0) #define CTRL_USB_HOSTPC1_DEVLC 0x1b4 #define USB_HOSTPC1_DEVLC_PTS(x) (((x) & 0x7) << 29) #define USB_HOSTPC1_DEVLC_STS (1 << 28) #define USB_HOSTPC1_DEVLC_PHCD (1 << 22) #define IF_USB_SUSP_CTRL 0x400 #define FAST_WAKEUP_RESP (1 << 26) #define UTMIP_SUSPL1_SET (1 << 25) #define USB_WAKEUP_DEBOUNCE_COUNT(x) (((x) & 0x7) << 16) #define USB_SUSP_SET (1 << 14) #define UTMIP_PHY_ENB (1 << 12) #define UTMIP_RESET (1 << 11) #define USB_SUSP_POL (1 << 10) #define USB_PHY_CLK_VALID_INT_ENB (1 << 9) #define USB_PHY_CLK_VALID_INT_STS (1 << 8) #define USB_PHY_CLK_VALID (1 << 7) #define USB_CLKEN (1 << 6) #define USB_SUSP_CLR (1 << 5) #define USB_WAKE_ON_DISCON_EN_DEV (1 << 4) #define USB_WAKE_ON_CNNT_EN_DEV (1 << 3) #define USB_WAKE_ON_RESUME_EN (1 << 2) #define USB_WAKEUP_INT_ENB (1 << 1) #define USB_WAKEUP_INT_STS (1 << 0) #define IF_USB_PHY_VBUS_SENSORS 0x404 #define B_SESS_END_SW_VALUE (1 << 4) #define B_SESS_END_SW_EN (1 << 3) #define UTMIP_XCVR_CFG0 0x808 #define UTMIP_XCVR_HSSLEW_MSB(x) ((((x) & 0x1fc) >> 2) << 25) #define UTMIP_XCVR_SETUP_MSB(x) ((((x) & 0x70) >> 4) << 22) #define UTMIP_XCVR_LSBIAS_SEL (1 << 21) #define UTMIP_XCVR_DISCON_METHOD (1 << 20) #define UTMIP_FORCE_PDZI_POWERUP (1 << 19) #define UTMIP_FORCE_PDZI_POWERDOWN (1 << 18) #define UTMIP_FORCE_PD2_POWERUP (1 << 17) #define UTMIP_FORCE_PD2_POWERDOWN (1 << 16) #define UTMIP_FORCE_PD_POWERUP (1 << 15) #define UTMIP_FORCE_PD_POWERDOWN (1 << 14) #define UTMIP_XCVR_TERMEN (1 << 13) #define UTMIP_XCVR_HSLOOPBACK (1 << 12) #define UTMIP_XCVR_LSFSLEW(x) (((x) & 0x3) << 10) #define UTMIP_XCVR_LSRSLEW(x) (((x) & 0x3) << 8) #define UTMIP_XCVR_FSSLEW(x) (((x) & 0x3) << 6) #define UTMIP_XCVR_HSSLEW(x) (((x) & 0x3) << 4) #define UTMIP_XCVR_SETUP(x) (((x) & 0xf) << 0) #define UTMIP_BIAS_CFG0 0x80C #define UTMIP_IDDIG_C_VAL (1 << 30) #define UTMIP_IDDIG_C_SEL (1 << 29) #define UTMIP_IDDIG_B_VAL (1 << 28) #define UTMIP_IDDIG_B_SEL (1 << 27) #define UTMIP_IDDIG_A_VAL (1 << 26) #define UTMIP_IDDIG_A_SEL (1 << 25) #define UTMIP_HSDISCON_LEVEL_MSB(x) ((((x) & 0x4) >> 2) << 24) #define UTMIP_IDPD_VAL (1 << 23) #define UTMIP_IDPD_SEL (1 << 22) #define UTMIP_IDDIG_VAL (1 << 21) #define UTMIP_IDDIG_SEL (1 << 20) #define UTMIP_GPI_VAL (1 << 19) #define UTMIP_GPI_SEL (1 << 18) #define UTMIP_ACTIVE_TERM_OFFSET(x) (((x) & 0x7) << 15) #define UTMIP_ACTIVE_PULLUP_OFFSET(x) (((x) & 0x7) << 12) #define UTMIP_OTGPD (1 << 11) #define UTMIP_BIASPD (1 << 10) #define UTMIP_VBUS_LEVEL_LEVEL(x) (((x) & 0x3) << 8) #define UTMIP_SESS_LEVEL_LEVEL(x) (((x) & 0x3) << 6) #define UTMIP_HSCHIRP_LEVEL(x) (((x) & 0x3) << 4) #define UTMIP_HSDISCON_LEVEL(x) (((x) & 0x3) << 2) #define UTMIP_HSSQUELCH_LEVEL(x) (((x) & 0x3) << 0) #define UTMIP_HSRX_CFG0 0x810 #define UTMIP_KEEP_PATT_ON_ACTIVE(x) (((x) & 0x3) << 30) #define UTMIP_ALLOW_CONSEC_UPDN (1 << 29) #define UTMIP_REALIGN_ON_NEW_PKT (1 << 28) #define UTMIP_PCOUNT_UPDN_DIV(x) (((x) & 0xf) << 24) #define UTMIP_SQUELCH_EOP_DLY(x) (((x) & 0x7) << 21) #define UTMIP_NO_STRIPPING (1 << 20) #define UTMIP_IDLE_WAIT(x) (((x) & 0x1f) << 15) #define UTMIP_ELASTIC_LIMIT(x) (((x) & 0x1f) << 10) #define UTMIP_ELASTIC_OVERRUN_DISABLE (1 << 9) #define UTMIP_ELASTIC_UNDERRUN_DISABLE (1 << 8) #define UTMIP_PASS_CHIRP (1 << 7) #define UTMIP_PASS_FEEDBACK (1 << 6) #define UTMIP_PCOUNT_INERTIA(x) (((x) & 0x3) << 4) #define UTMIP_PHASE_ADJUST(x) (((x) & 0x3) << 2) #define UTMIP_THREE_SYNCBITS (1 << 1) #define UTMIP_USE4SYNC_TRAN (1 << 0) #define UTMIP_HSRX_CFG1 0x814 #define UTMIP_HS_SYNC_START_DLY(x) (((x) & 0x1F) << 1) #define UTMIP_HS_ALLOW_KEEP_ALIVE (1 << 0) #define UTMIP_TX_CFG0 0x820 #define UTMIP_FS_PREAMBLE_J (1 << 19) #define UTMIP_FS_POSTAMBLE_OUTPUT_ENABLE (1 << 18) #define UTMIP_FS_PREAMBLE_OUTPUT_ENABLE (1 << 17) #define UTMIP_FSLS_ALLOW_SOP_TX_STUFF_ERR (1 << 16) #define UTMIP_HS_READY_WAIT_FOR_VALID (1 << 15) #define UTMIP_HS_TX_IPG_DLY(x) (((x) & 0x1f) << 10) #define UTMIP_HS_DISCON_EOP_ONLY (1 << 9) #define UTMIP_HS_DISCON_DISABLE (1 << 8) #define UTMIP_HS_POSTAMBLE_OUTPUT_ENABLE (1 << 7) #define UTMIP_HS_PREAMBLE_OUTPUT_ENABLE (1 << 6) #define UTMIP_SIE_RESUME_ON_LINESTATE (1 << 5) #define UTMIP_SOF_ON_NO_STUFF (1 << 4) #define UTMIP_SOF_ON_NO_ENCODE (1 << 3) #define UTMIP_NO_STUFFING (1 << 2) #define UTMIP_NO_ENCODING (1 << 1) #define UTMIP_NO_SYNC_NO_EOP (1 << 0) #define UTMIP_MISC_CFG0 0x824 #define UTMIP_DPDM_OBSERVE_SEL(x) (((x) & 0xf) << 27) #define UTMIP_DPDM_OBSERVE (1 << 26) #define UTMIP_KEEP_XCVR_PD_ON_SOFT_DISCON (1 << 25) #define UTMIP_ALLOW_LS_ON_SOFT_DISCON (1 << 24) #define UTMIP_FORCE_FS_DISABLE_ON_DEV_CHIRP (1 << 23) #define UTMIP_SUSPEND_EXIT_ON_EDGE (1 << 22) #define UTMIP_LS_TO_FS_SKIP_4MS (1 << 21) #define UTMIP_INJECT_ERROR_TYPE(x) (((x) & 0x3) << 19) #define UTMIP_FORCE_HS_CLOCK_ON (1 << 18) #define UTMIP_DISABLE_HS_TERM (1 << 17) #define UTMIP_FORCE_HS_TERM (1 << 16) #define UTMIP_DISABLE_PULLUP_DP (1 << 15) #define UTMIP_DISABLE_PULLUP_DM (1 << 14) #define UTMIP_DISABLE_PULLDN_DP (1 << 13) #define UTMIP_DISABLE_PULLDN_DM (1 << 12) #define UTMIP_FORCE_PULLUP_DP (1 << 11) #define UTMIP_FORCE_PULLUP_DM (1 << 10) #define UTMIP_FORCE_PULLDN_DP (1 << 9) #define UTMIP_FORCE_PULLDN_DM (1 << 8) #define UTMIP_STABLE_COUNT(x) (((x) & 0x7) << 5) #define UTMIP_STABLE_ALL (1 << 4) #define UTMIP_NO_FREE_ON_SUSPEND (1 << 3) #define UTMIP_NEVER_FREE_RUNNING_TERMS (1 << 2) #define UTMIP_ALWAYS_FREE_RUNNING_TERMS (1 << 1) #define UTMIP_COMB_TERMS (1 << 0) #define UTMIP_MISC_CFG1 0x828 #define UTMIP_PHY_XTAL_CLOCKEN (1 << 30) #define UTMIP_DEBOUNCE_CFG0 0x82C #define UTMIP_BIAS_DEBOUNCE_B(x) (((x) & 0xffff) << 16) #define UTMIP_BIAS_DEBOUNCE_A(x) (((x) & 0xffff) << 0) #define UTMIP_BAT_CHRG_CFG0 0x830 #define UTMIP_CHRG_DEBOUNCE_TIMESCALE(x) (((x) & 0x1f) << 8) #define UTMIP_OP_I_SRC_ENG (1 << 5) #define UTMIP_ON_SRC_ENG (1 << 4) #define UTMIP_OP_SRC_ENG (1 << 3) #define UTMIP_ON_SINK_ENG (1 << 2) #define UTMIP_OP_SINK_ENG (1 << 1) #define UTMIP_PD_CHRG (1 << 0) #define UTMIP_SPARE_CFG0 0x834 #define FUSE_HS_IREF_CAP_CFG (1 << 7) #define FUSE_HS_SQUELCH_LEVEL (1 << 6) #define FUSE_SPARE (1 << 5) #define FUSE_TERM_RANGE_ADJ_SEL (1 << 4) #define FUSE_SETUP_SEL (1 << 3) #define HS_RX_LATE_SQUELCH (1 << 2) #define HS_RX_FLUSH_ALAP (1 << 1) #define HS_RX_IPG_ERROR_ENABLE (1 << 0) #define UTMIP_XCVR_CFG1 0x838 #define UTMIP_XCVR_RPU_RANGE_ADJ(x) (((x) & 0x3) << 26) #define UTMIP_XCVR_HS_IREF_CAP(x) (((x) & 0x3) << 24) #define UTMIP_XCVR_SPARE(x) (((x) & 0x3) << 22) #define UTMIP_XCVR_TERM_RANGE_ADJ(x) (((x) & 0xf) << 18) #define UTMIP_RCTRL_SW_SET (1 << 17) #define UTMIP_RCTRL_SW_VAL(x) (((x) & 0x1f) << 12) #define UTMIP_TCTRL_SW_SET (1 << 11) #define UTMIP_TCTRL_SW_VAL(x) (((x) & 0x1f) << 6) #define UTMIP_FORCE_PDDR_POWERUP (1 << 5) #define UTMIP_FORCE_PDDR_POWERDOWN (1 << 4) #define UTMIP_FORCE_PDCHRP_POWERUP (1 << 3) #define UTMIP_FORCE_PDCHRP_POWERDOWN (1 << 2) #define UTMIP_FORCE_PDDISC_POWERUP (1 << 1) #define UTMIP_FORCE_PDDISC_POWERDOWN (1 << 0) #define UTMIP_BIAS_CFG1 0x83c #define UTMIP_BIAS_DEBOUNCE_TIMESCALE(x) (((x) & 0x3f) << 8) #define UTMIP_BIAS_PDTRK_COUNT(x) (((x) & 0x1f) << 3) #define UTMIP_VBUS_WAKEUP_POWERDOWN (1 << 2) #define UTMIP_FORCE_PDTRK_POWERUP (1 << 1) #define UTMIP_FORCE_PDTRK_POWERDOWN (1 << 0) static int usbpby_enable_cnt; enum usb_ifc_type { USB_IFC_TYPE_UNKNOWN = 0, USB_IFC_TYPE_UTMI, USB_IFC_TYPE_ULPI }; enum usb_dr_mode { USB_DR_MODE_UNKNOWN = 0, USB_DR_MODE_DEVICE, USB_DR_MODE_HOST, USB_DR_MODE_OTG }; struct usbphy_softc { device_t dev; struct resource *mem_res; struct resource *pads_res; clk_t clk_reg; clk_t clk_pads; clk_t clk_pllu; regulator_t supply_vbus; hwreset_t reset_usb; hwreset_t reset_pads; enum usb_ifc_type ifc_type; enum usb_dr_mode dr_mode; bool have_utmi_regs; /* UTMI params */ int hssync_start_delay; int elastic_limit; int idle_wait_delay; int term_range_adj; int xcvr_lsfslew; int xcvr_lsrslew; int xcvr_hsslew; int hssquelch_level; int hsdiscon_level; int xcvr_setup; int xcvr_setup_use_fuses; }; static struct ofw_compat_data compat_data[] = { {"nvidia,tegra30-usb-phy", 1}, {NULL, 0}, }; /* Phy controller class and methods. */ static int usbphy_phy_enable(struct phynode *phy, bool enable); static phynode_method_t usbphy_phynode_methods[] = { PHYNODEMETHOD(phynode_enable, usbphy_phy_enable), PHYNODEMETHOD_END }; DEFINE_CLASS_1(usbphy_phynode, usbphy_phynode_class, usbphy_phynode_methods, 0, phynode_class); #define RD4(sc, offs) \ bus_read_4(sc->mem_res, offs) #define WR4(sc, offs, val) \ bus_write_4(sc->mem_res, offs, val) static int reg_wait(struct usbphy_softc *sc, uint32_t reg, uint32_t mask, uint32_t val) { int i; for (i = 0; i < 1000; i++) { if ((RD4(sc, reg) & mask) == val) return (0); DELAY(10); } return (ETIMEDOUT); } static int usbphy_utmi_phy_clk(struct usbphy_softc *sc, bool enable) { uint32_t val; int rv; val = RD4(sc, CTRL_USB_HOSTPC1_DEVLC); if (enable) val &= ~USB_HOSTPC1_DEVLC_PHCD; else val |= USB_HOSTPC1_DEVLC_PHCD; WR4(sc, CTRL_USB_HOSTPC1_DEVLC, val); rv = reg_wait(sc, IF_USB_SUSP_CTRL, USB_PHY_CLK_VALID, enable ? USB_PHY_CLK_VALID: 0); if (rv != 0) { device_printf(sc->dev, "USB phy clock timeout.\n"); return (ETIMEDOUT); } return (0); } static int usbphy_utmi_enable(struct usbphy_softc *sc) { int rv; uint32_t val; /* Reset phy */ val = RD4(sc, IF_USB_SUSP_CTRL); val |= UTMIP_RESET; WR4(sc, IF_USB_SUSP_CTRL, val); val = RD4(sc, UTMIP_TX_CFG0); val |= UTMIP_FS_PREAMBLE_J; WR4(sc, UTMIP_TX_CFG0, val); val = RD4(sc, UTMIP_HSRX_CFG0); val &= ~UTMIP_IDLE_WAIT(~0); val &= ~UTMIP_ELASTIC_LIMIT(~0); val |= UTMIP_IDLE_WAIT(sc->idle_wait_delay); val |= UTMIP_ELASTIC_LIMIT(sc->elastic_limit); WR4(sc, UTMIP_HSRX_CFG0, val); val = RD4(sc, UTMIP_HSRX_CFG1); val &= ~UTMIP_HS_SYNC_START_DLY(~0); val |= UTMIP_HS_SYNC_START_DLY(sc->hssync_start_delay); WR4(sc, UTMIP_HSRX_CFG1, val); val = RD4(sc, UTMIP_DEBOUNCE_CFG0); val &= ~UTMIP_BIAS_DEBOUNCE_A(~0); val |= UTMIP_BIAS_DEBOUNCE_A(0x7530); /* For 12MHz */ WR4(sc, UTMIP_DEBOUNCE_CFG0, val); val = RD4(sc, UTMIP_MISC_CFG0); val &= ~UTMIP_SUSPEND_EXIT_ON_EDGE; WR4(sc, UTMIP_MISC_CFG0, val); if (sc->dr_mode == USB_DR_MODE_DEVICE) { val = RD4(sc,IF_USB_SUSP_CTRL); val &= ~USB_WAKE_ON_CNNT_EN_DEV; val &= ~USB_WAKE_ON_DISCON_EN_DEV; WR4(sc, IF_USB_SUSP_CTRL, val); val = RD4(sc, UTMIP_BAT_CHRG_CFG0); val &= ~UTMIP_PD_CHRG; WR4(sc, UTMIP_BAT_CHRG_CFG0, val); } else { val = RD4(sc, UTMIP_BAT_CHRG_CFG0); val |= UTMIP_PD_CHRG; WR4(sc, UTMIP_BAT_CHRG_CFG0, val); } usbpby_enable_cnt++; if (usbpby_enable_cnt == 1) { rv = hwreset_deassert(sc->reset_pads); if (rv != 0) { device_printf(sc->dev, "Cannot unreset 'utmi-pads' reset\n"); return (rv); } rv = clk_enable(sc->clk_pads); if (rv != 0) { device_printf(sc->dev, "Cannot enable 'utmi-pads' clock\n"); return (rv); } val = bus_read_4(sc->pads_res, UTMIP_BIAS_CFG0); val &= ~UTMIP_OTGPD; val &= ~UTMIP_BIASPD; val &= ~UTMIP_HSSQUELCH_LEVEL(~0); val &= ~UTMIP_HSDISCON_LEVEL(~0); val &= ~UTMIP_HSDISCON_LEVEL_MSB(~0); val |= UTMIP_HSSQUELCH_LEVEL(sc->hssquelch_level); val |= UTMIP_HSDISCON_LEVEL(sc->hsdiscon_level); val |= UTMIP_HSDISCON_LEVEL_MSB(sc->hsdiscon_level); bus_write_4(sc->pads_res, UTMIP_BIAS_CFG0, val); rv = clk_disable(sc->clk_pads); if (rv != 0) { device_printf(sc->dev, "Cannot disable 'utmi-pads' clock\n"); return (rv); } } val = RD4(sc, UTMIP_XCVR_CFG0); val &= ~UTMIP_FORCE_PD_POWERDOWN; val &= ~UTMIP_FORCE_PD2_POWERDOWN ; val &= ~UTMIP_FORCE_PDZI_POWERDOWN; val &= ~UTMIP_XCVR_LSBIAS_SEL; val &= ~UTMIP_XCVR_LSFSLEW(~0); val &= ~UTMIP_XCVR_LSRSLEW(~0); val &= ~UTMIP_XCVR_HSSLEW(~0); val &= ~UTMIP_XCVR_HSSLEW_MSB(~0); val |= UTMIP_XCVR_LSFSLEW(sc->xcvr_lsfslew); val |= UTMIP_XCVR_LSRSLEW(sc->xcvr_lsrslew); val |= UTMIP_XCVR_HSSLEW(sc->xcvr_hsslew); val |= UTMIP_XCVR_HSSLEW_MSB(sc->xcvr_hsslew); if (!sc->xcvr_setup_use_fuses) { val &= ~UTMIP_XCVR_SETUP(~0); val &= ~UTMIP_XCVR_SETUP_MSB(~0); val |= UTMIP_XCVR_SETUP(sc->xcvr_setup); val |= UTMIP_XCVR_SETUP_MSB(sc->xcvr_setup); } WR4(sc, UTMIP_XCVR_CFG0, val); val = RD4(sc, UTMIP_XCVR_CFG1); val &= ~UTMIP_FORCE_PDDISC_POWERDOWN; val &= ~UTMIP_FORCE_PDCHRP_POWERDOWN; val &= ~UTMIP_FORCE_PDDR_POWERDOWN; val &= ~UTMIP_XCVR_TERM_RANGE_ADJ(~0); val |= UTMIP_XCVR_TERM_RANGE_ADJ(sc->term_range_adj); WR4(sc, UTMIP_XCVR_CFG1, val); val = RD4(sc, UTMIP_BIAS_CFG1); val &= ~UTMIP_BIAS_PDTRK_COUNT(~0); val |= UTMIP_BIAS_PDTRK_COUNT(0x5); WR4(sc, UTMIP_BIAS_CFG1, val); val = RD4(sc, UTMIP_SPARE_CFG0); if (sc->xcvr_setup_use_fuses) val |= FUSE_SETUP_SEL; else val &= ~FUSE_SETUP_SEL; WR4(sc, UTMIP_SPARE_CFG0, val); val = RD4(sc, IF_USB_SUSP_CTRL); val |= UTMIP_PHY_ENB; WR4(sc, IF_USB_SUSP_CTRL, val); val = RD4(sc, IF_USB_SUSP_CTRL); val &= ~UTMIP_RESET; WR4(sc, IF_USB_SUSP_CTRL, val); usbphy_utmi_phy_clk(sc, true); val = RD4(sc, CTRL_USB_USBMODE); val &= ~USB_USBMODE_MASK; if (sc->dr_mode == USB_DR_MODE_HOST) val |= USB_USBMODE_HOST; else val |= USB_USBMODE_DEVICE; WR4(sc, CTRL_USB_USBMODE, val); val = RD4(sc, CTRL_USB_HOSTPC1_DEVLC); val &= ~USB_HOSTPC1_DEVLC_PTS(~0); val |= USB_HOSTPC1_DEVLC_PTS(0); WR4(sc, CTRL_USB_HOSTPC1_DEVLC, val); return (0); } static int usbphy_utmi_disable(struct usbphy_softc *sc) { int rv; uint32_t val; usbphy_utmi_phy_clk(sc, false); if (sc->dr_mode == USB_DR_MODE_DEVICE) { val = RD4(sc, IF_USB_SUSP_CTRL); val &= ~USB_WAKEUP_DEBOUNCE_COUNT(~0); val |= USB_WAKE_ON_CNNT_EN_DEV; val |= USB_WAKEUP_DEBOUNCE_COUNT(5); WR4(sc, IF_USB_SUSP_CTRL, val); } val = RD4(sc, IF_USB_SUSP_CTRL); val |= UTMIP_RESET; WR4(sc, IF_USB_SUSP_CTRL, val); val = RD4(sc, UTMIP_BAT_CHRG_CFG0); val |= UTMIP_PD_CHRG; WR4(sc, UTMIP_BAT_CHRG_CFG0, val); val = RD4(sc, UTMIP_XCVR_CFG0); val |= UTMIP_FORCE_PD_POWERDOWN; val |= UTMIP_FORCE_PD2_POWERDOWN; val |= UTMIP_FORCE_PDZI_POWERDOWN; WR4(sc, UTMIP_XCVR_CFG0, val); val = RD4(sc, UTMIP_XCVR_CFG1); val |= UTMIP_FORCE_PDDISC_POWERDOWN; val |= UTMIP_FORCE_PDCHRP_POWERDOWN; val |= UTMIP_FORCE_PDDR_POWERDOWN; WR4(sc, UTMIP_XCVR_CFG1, val); usbpby_enable_cnt--; if (usbpby_enable_cnt <= 0) { rv = clk_enable(sc->clk_pads); if (rv != 0) { device_printf(sc->dev, "Cannot enable 'utmi-pads' clock\n"); return (rv); } val =bus_read_4(sc->pads_res, UTMIP_BIAS_CFG0); val |= UTMIP_OTGPD; val |= UTMIP_BIASPD; bus_write_4(sc->pads_res, UTMIP_BIAS_CFG0, val); rv = clk_disable(sc->clk_pads); if (rv != 0) { device_printf(sc->dev, "Cannot disable 'utmi-pads' clock\n"); return (rv); } } return (0); } static int usbphy_phy_enable(struct phynode *phy, bool enable) { device_t dev; struct usbphy_softc *sc; int rv = 0; dev = phynode_get_device(phy); sc = device_get_softc(dev); if (sc->ifc_type != USB_IFC_TYPE_UTMI) { device_printf(sc->dev, "Only UTMI interface is supported.\n"); return (ENXIO); } if (enable) rv = usbphy_utmi_enable(sc); else rv = usbphy_utmi_disable(sc); return (rv); } static enum usb_ifc_type usb_get_ifc_mode(device_t dev, phandle_t node, char *name) { char *tmpstr; int rv; enum usb_ifc_type ret; - rv = OF_getprop_alloc(node, name, 1, (void **)&tmpstr); + rv = OF_getprop_alloc(node, name, (void **)&tmpstr); if (rv <= 0) return (USB_IFC_TYPE_UNKNOWN); ret = USB_IFC_TYPE_UNKNOWN; if (strcmp(tmpstr, "utmi") == 0) ret = USB_IFC_TYPE_UTMI; else if (strcmp(tmpstr, "ulpi") == 0) ret = USB_IFC_TYPE_ULPI; else device_printf(dev, "Unsupported phy type: %s\n", tmpstr); OF_prop_free(tmpstr); return (ret); } static enum usb_dr_mode usb_get_dr_mode(device_t dev, phandle_t node, char *name) { char *tmpstr; int rv; enum usb_dr_mode ret; - rv = OF_getprop_alloc(node, name, 1, (void **)&tmpstr); + rv = OF_getprop_alloc(node, name, (void **)&tmpstr); if (rv <= 0) return (USB_DR_MODE_UNKNOWN); ret = USB_DR_MODE_UNKNOWN; if (strcmp(tmpstr, "device") == 0) ret = USB_DR_MODE_DEVICE; else if (strcmp(tmpstr, "host") == 0) ret = USB_DR_MODE_HOST; else if (strcmp(tmpstr, "otg") == 0) ret = USB_DR_MODE_OTG; else device_printf(dev, "Unknown dr mode: %s\n", tmpstr); OF_prop_free(tmpstr); return (ret); } static int usbphy_utmi_read_params(struct usbphy_softc *sc, phandle_t node) { int rv; rv = OF_getencprop(node, "nvidia,hssync-start-delay", &sc->hssync_start_delay, sizeof (sc->hssync_start_delay)); if (rv <= 0) return (ENXIO); rv = OF_getencprop(node, "nvidia,elastic-limit", &sc->elastic_limit, sizeof (sc->elastic_limit)); if (rv <= 0) return (ENXIO); rv = OF_getencprop(node, "nvidia,idle-wait-delay", &sc->idle_wait_delay, sizeof (sc->idle_wait_delay)); if (rv <= 0) return (ENXIO); rv = OF_getencprop(node, "nvidia,term-range-adj", &sc->term_range_adj, sizeof (sc->term_range_adj)); if (rv <= 0) return (ENXIO); rv = OF_getencprop(node, "nvidia,xcvr-lsfslew", &sc->xcvr_lsfslew, sizeof (sc->xcvr_lsfslew)); if (rv <= 0) return (ENXIO); rv = OF_getencprop(node, "nvidia,xcvr-lsrslew", &sc->xcvr_lsrslew, sizeof (sc->xcvr_lsrslew)); if (rv <= 0) return (ENXIO); rv = OF_getencprop(node, "nvidia,xcvr-hsslew", &sc->xcvr_hsslew, sizeof (sc->xcvr_hsslew)); if (rv <= 0) return (ENXIO); rv = OF_getencprop(node, "nvidia,hssquelch-level", &sc->hssquelch_level, sizeof (sc->hssquelch_level)); if (rv <= 0) return (ENXIO); rv = OF_getencprop(node, "nvidia,hsdiscon-level", &sc->hsdiscon_level, sizeof (sc->hsdiscon_level)); if (rv <= 0) return (ENXIO); rv = OF_getproplen(node, "nvidia,xcvr-setup-use-fuses"); if (rv >= 1) { sc->xcvr_setup_use_fuses = 1; } else { rv = OF_getencprop(node, "nvidia,xcvr-setup", &sc->xcvr_setup, sizeof (sc->xcvr_setup)); if (rv <= 0) return (ENXIO); } return (0); } static int usbphy_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, "Tegra USB phy"); return (BUS_PROBE_DEFAULT); } static int usbphy_attach(device_t dev) { struct usbphy_softc *sc; int rid, rv; phandle_t node; struct phynode *phynode; struct phynode_init_def phy_init; sc = device_get_softc(dev); sc->dev = dev; rid = 0; sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE | RF_SHAREABLE); if (sc->mem_res == NULL) { device_printf(dev, "Cannot allocate memory resources\n"); return (ENXIO); } rid = 1; sc->pads_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE | RF_SHAREABLE); if (sc->mem_res == NULL) { device_printf(dev, "Cannot allocate memory resources\n"); return (ENXIO); } node = ofw_bus_get_node(dev); rv = hwreset_get_by_ofw_name(sc->dev, 0, "usb", &sc->reset_usb); if (rv != 0) { device_printf(dev, "Cannot get 'usb' reset\n"); return (ENXIO); } rv = hwreset_get_by_ofw_name(sc->dev, 0, "utmi-pads", &sc->reset_pads); if (rv != 0) { device_printf(dev, "Cannot get 'utmi-pads' reset\n"); return (ENXIO); } rv = clk_get_by_ofw_name(sc->dev, 0, "reg", &sc->clk_reg); if (rv != 0) { device_printf(sc->dev, "Cannot get 'reg' clock\n"); return (ENXIO); } rv = clk_get_by_ofw_name(sc->dev, 0, "pll_u", &sc->clk_pllu); if (rv != 0) { device_printf(sc->dev, "Cannot get 'pll_u' clock\n"); return (ENXIO); } rv = clk_get_by_ofw_name(sc->dev, 0, "utmi-pads", &sc->clk_pads); if (rv != 0) { device_printf(sc->dev, "Cannot get 'utmi-pads' clock\n"); return (ENXIO); } rv = hwreset_deassert(sc->reset_usb); if (rv != 0) { device_printf(dev, "Cannot unreset 'usb' reset\n"); return (ENXIO); } rv = clk_enable(sc->clk_pllu); if (rv != 0) { device_printf(sc->dev, "Cannot enable 'pllu' clock\n"); return (ENXIO); } rv = clk_enable(sc->clk_reg); if (rv != 0) { device_printf(sc->dev, "Cannot enable 'reg' clock\n"); return (ENXIO); } if (OF_hasprop(node, "nvidia,has-utmi-pad-registers")) sc->have_utmi_regs = true; sc->dr_mode = usb_get_dr_mode(dev, node, "dr_mode"); if (sc->dr_mode == USB_DR_MODE_UNKNOWN) sc->dr_mode = USB_DR_MODE_HOST; sc->ifc_type = usb_get_ifc_mode(dev, node, "phy_type"); /* We supports only utmi phy mode for now .... */ if (sc->ifc_type != USB_IFC_TYPE_UTMI) { device_printf(dev, "Unsupported phy type\n"); return (ENXIO); } rv = usbphy_utmi_read_params(sc, node); if (rv < 0) return rv; if (OF_hasprop(node, "vbus-supply")) { rv = regulator_get_by_ofw_property(sc->dev, 0, "vbus-supply", &sc->supply_vbus); if (rv != 0) { device_printf(sc->dev, "Cannot get \"vbus\" regulator\n"); return (ENXIO); } rv = regulator_enable(sc->supply_vbus); if (rv != 0) { device_printf(sc->dev, "Cannot enable \"vbus\" regulator\n"); return (rv); } } /* Create and register phy. */ bzero(&phy_init, sizeof(phy_init)); phy_init.id = 1; phy_init.ofw_node = node; phynode = phynode_create(dev, &usbphy_phynode_class, &phy_init); if (phynode == NULL) { device_printf(sc->dev, "Cannot create phy\n"); return (ENXIO); } if (phynode_register(phynode) == NULL) { device_printf(sc->dev, "Cannot create phy\n"); return (ENXIO); } return (0); } static int usbphy_detach(device_t dev) { /* This device is always present. */ return (EBUSY); } static device_method_t tegra_usbphy_methods[] = { /* Device interface */ DEVMETHOD(device_probe, usbphy_probe), DEVMETHOD(device_attach, usbphy_attach), DEVMETHOD(device_detach, usbphy_detach), DEVMETHOD_END }; static devclass_t tegra_usbphy_devclass; static DEFINE_CLASS_0(usbphy, tegra_usbphy_driver, tegra_usbphy_methods, sizeof(struct usbphy_softc)); EARLY_DRIVER_MODULE(tegra_usbphy, simplebus, tegra_usbphy_driver, tegra_usbphy_devclass, NULL, NULL, 79); Index: head/sys/arm/ti/cpsw/if_cpsw.c =================================================================== --- head/sys/arm/ti/cpsw/if_cpsw.c (revision 332309) +++ head/sys/arm/ti/cpsw/if_cpsw.c (revision 332310) @@ -1,2989 +1,2989 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2012 Damjan Marion * Copyright (c) 2016 Rubicon Communications, LLC (Netgate) * 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. */ /* * TI Common Platform Ethernet Switch (CPSW) Driver * Found in TI8148 "DaVinci" and AM335x "Sitara" SoCs. * * This controller is documented in the AM335x Technical Reference * Manual, in the TMS320DM814x DaVinci Digital Video Processors TRM * and in the TMS320C6452 3 Port Switch Ethernet Subsystem TRM. * * It is basically a single Ethernet port (port 0) wired internally to * a 3-port store-and-forward switch connected to two independent * "sliver" controllers (port 1 and port 2). You can operate the * controller in a variety of different ways by suitably configuring * the slivers and the Address Lookup Engine (ALE) that routes packets * between the ports. * * This code was developed and tested on a BeagleBone with * an AM335x SoC. */ #include __FBSDID("$FreeBSD$"); #include "opt_cpsw.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CPSW_ETHERSWITCH #include #include "etherswitch_if.h" #endif #include "if_cpswreg.h" #include "if_cpswvar.h" #include "miibus_if.h" /* Device probe/attach/detach. */ static int cpsw_probe(device_t); static int cpsw_attach(device_t); static int cpsw_detach(device_t); static int cpswp_probe(device_t); static int cpswp_attach(device_t); static int cpswp_detach(device_t); static phandle_t cpsw_get_node(device_t, device_t); /* Device Init/shutdown. */ static int cpsw_shutdown(device_t); static void cpswp_init(void *); static void cpswp_init_locked(void *); static void cpswp_stop_locked(struct cpswp_softc *); /* Device Suspend/Resume. */ static int cpsw_suspend(device_t); static int cpsw_resume(device_t); /* Ioctl. */ static int cpswp_ioctl(struct ifnet *, u_long command, caddr_t data); static int cpswp_miibus_readreg(device_t, int phy, int reg); static int cpswp_miibus_writereg(device_t, int phy, int reg, int value); static void cpswp_miibus_statchg(device_t); /* Send/Receive packets. */ static void cpsw_intr_rx(void *arg); static struct mbuf *cpsw_rx_dequeue(struct cpsw_softc *); static void cpsw_rx_enqueue(struct cpsw_softc *); static void cpswp_start(struct ifnet *); static void cpsw_intr_tx(void *); static void cpswp_tx_enqueue(struct cpswp_softc *); static int cpsw_tx_dequeue(struct cpsw_softc *); /* Misc interrupts and watchdog. */ static void cpsw_intr_rx_thresh(void *); static void cpsw_intr_misc(void *); static void cpswp_tick(void *); static void cpswp_ifmedia_sts(struct ifnet *, struct ifmediareq *); static int cpswp_ifmedia_upd(struct ifnet *); static void cpsw_tx_watchdog(void *); /* ALE support */ static void cpsw_ale_read_entry(struct cpsw_softc *, uint16_t, uint32_t *); static void cpsw_ale_write_entry(struct cpsw_softc *, uint16_t, uint32_t *); static int cpsw_ale_mc_entry_set(struct cpsw_softc *, uint8_t, int, uint8_t *); static void cpsw_ale_dump_table(struct cpsw_softc *); static int cpsw_ale_update_vlan_table(struct cpsw_softc *, int, int, int, int, int); static int cpswp_ale_update_addresses(struct cpswp_softc *, int); /* Statistics and sysctls. */ static void cpsw_add_sysctls(struct cpsw_softc *); static void cpsw_stats_collect(struct cpsw_softc *); static int cpsw_stats_sysctl(SYSCTL_HANDLER_ARGS); #ifdef CPSW_ETHERSWITCH static etherswitch_info_t *cpsw_getinfo(device_t); static int cpsw_getport(device_t, etherswitch_port_t *); static int cpsw_setport(device_t, etherswitch_port_t *); static int cpsw_getconf(device_t, etherswitch_conf_t *); static int cpsw_getvgroup(device_t, etherswitch_vlangroup_t *); static int cpsw_setvgroup(device_t, etherswitch_vlangroup_t *); static int cpsw_readreg(device_t, int); static int cpsw_writereg(device_t, int, int); static int cpsw_readphy(device_t, int, int); static int cpsw_writephy(device_t, int, int, int); #endif /* * Arbitrary limit on number of segments in an mbuf to be transmitted. * Packets with more segments than this will be defragmented before * they are queued. */ #define CPSW_TXFRAGS 16 /* Shared resources. */ static device_method_t cpsw_methods[] = { /* Device interface */ DEVMETHOD(device_probe, cpsw_probe), DEVMETHOD(device_attach, cpsw_attach), DEVMETHOD(device_detach, cpsw_detach), DEVMETHOD(device_shutdown, cpsw_shutdown), DEVMETHOD(device_suspend, cpsw_suspend), DEVMETHOD(device_resume, cpsw_resume), /* Bus interface */ DEVMETHOD(bus_add_child, device_add_child_ordered), /* OFW methods */ DEVMETHOD(ofw_bus_get_node, cpsw_get_node), #ifdef CPSW_ETHERSWITCH /* etherswitch interface */ DEVMETHOD(etherswitch_getinfo, cpsw_getinfo), DEVMETHOD(etherswitch_readreg, cpsw_readreg), DEVMETHOD(etherswitch_writereg, cpsw_writereg), DEVMETHOD(etherswitch_readphyreg, cpsw_readphy), DEVMETHOD(etherswitch_writephyreg, cpsw_writephy), DEVMETHOD(etherswitch_getport, cpsw_getport), DEVMETHOD(etherswitch_setport, cpsw_setport), DEVMETHOD(etherswitch_getvgroup, cpsw_getvgroup), DEVMETHOD(etherswitch_setvgroup, cpsw_setvgroup), DEVMETHOD(etherswitch_getconf, cpsw_getconf), #endif DEVMETHOD_END }; static driver_t cpsw_driver = { "cpswss", cpsw_methods, sizeof(struct cpsw_softc), }; static devclass_t cpsw_devclass; DRIVER_MODULE(cpswss, simplebus, cpsw_driver, cpsw_devclass, 0, 0); /* Port/Slave resources. */ static device_method_t cpswp_methods[] = { /* Device interface */ DEVMETHOD(device_probe, cpswp_probe), DEVMETHOD(device_attach, cpswp_attach), DEVMETHOD(device_detach, cpswp_detach), /* MII interface */ DEVMETHOD(miibus_readreg, cpswp_miibus_readreg), DEVMETHOD(miibus_writereg, cpswp_miibus_writereg), DEVMETHOD(miibus_statchg, cpswp_miibus_statchg), DEVMETHOD_END }; static driver_t cpswp_driver = { "cpsw", cpswp_methods, sizeof(struct cpswp_softc), }; static devclass_t cpswp_devclass; #ifdef CPSW_ETHERSWITCH DRIVER_MODULE(etherswitch, cpswss, etherswitch_driver, etherswitch_devclass, 0, 0); MODULE_DEPEND(cpswss, etherswitch, 1, 1, 1); #endif DRIVER_MODULE(cpsw, cpswss, cpswp_driver, cpswp_devclass, 0, 0); DRIVER_MODULE(miibus, cpsw, miibus_driver, miibus_devclass, 0, 0); MODULE_DEPEND(cpsw, ether, 1, 1, 1); MODULE_DEPEND(cpsw, miibus, 1, 1, 1); #ifdef CPSW_ETHERSWITCH static struct cpsw_vlangroups cpsw_vgroups[CPSW_VLANS]; #endif static uint32_t slave_mdio_addr[] = { 0x4a100200, 0x4a100300 }; static struct resource_spec irq_res_spec[] = { { SYS_RES_IRQ, 0, RF_ACTIVE | RF_SHAREABLE }, { SYS_RES_IRQ, 1, RF_ACTIVE | RF_SHAREABLE }, { SYS_RES_IRQ, 2, RF_ACTIVE | RF_SHAREABLE }, { SYS_RES_IRQ, 3, RF_ACTIVE | RF_SHAREABLE }, { -1, 0 } }; static struct { void (*cb)(void *); } cpsw_intr_cb[] = { { cpsw_intr_rx_thresh }, { cpsw_intr_rx }, { cpsw_intr_tx }, { cpsw_intr_misc }, }; /* Number of entries here must match size of stats * array in struct cpswp_softc. */ static struct cpsw_stat { int reg; char *oid; } cpsw_stat_sysctls[CPSW_SYSCTL_COUNT] = { {0x00, "GoodRxFrames"}, {0x04, "BroadcastRxFrames"}, {0x08, "MulticastRxFrames"}, {0x0C, "PauseRxFrames"}, {0x10, "RxCrcErrors"}, {0x14, "RxAlignErrors"}, {0x18, "OversizeRxFrames"}, {0x1c, "RxJabbers"}, {0x20, "ShortRxFrames"}, {0x24, "RxFragments"}, {0x30, "RxOctets"}, {0x34, "GoodTxFrames"}, {0x38, "BroadcastTxFrames"}, {0x3c, "MulticastTxFrames"}, {0x40, "PauseTxFrames"}, {0x44, "DeferredTxFrames"}, {0x48, "CollisionsTxFrames"}, {0x4c, "SingleCollisionTxFrames"}, {0x50, "MultipleCollisionTxFrames"}, {0x54, "ExcessiveCollisions"}, {0x58, "LateCollisions"}, {0x5c, "TxUnderrun"}, {0x60, "CarrierSenseErrors"}, {0x64, "TxOctets"}, {0x68, "RxTx64OctetFrames"}, {0x6c, "RxTx65to127OctetFrames"}, {0x70, "RxTx128to255OctetFrames"}, {0x74, "RxTx256to511OctetFrames"}, {0x78, "RxTx512to1024OctetFrames"}, {0x7c, "RxTx1024upOctetFrames"}, {0x80, "NetOctets"}, {0x84, "RxStartOfFrameOverruns"}, {0x88, "RxMiddleOfFrameOverruns"}, {0x8c, "RxDmaOverruns"} }; /* * Basic debug support. */ static void cpsw_debugf_head(const char *funcname) { int t = (int)(time_second % (24 * 60 * 60)); printf("%02d:%02d:%02d %s ", t / (60 * 60), (t / 60) % 60, t % 60, funcname); } static void cpsw_debugf(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vprintf(fmt, ap); va_end(ap); printf("\n"); } #define CPSW_DEBUGF(_sc, a) do { \ if ((_sc)->debug) { \ cpsw_debugf_head(__func__); \ cpsw_debugf a; \ } \ } while (0) /* * Locking macros */ #define CPSW_TX_LOCK(sc) do { \ mtx_assert(&(sc)->rx.lock, MA_NOTOWNED); \ mtx_lock(&(sc)->tx.lock); \ } while (0) #define CPSW_TX_UNLOCK(sc) mtx_unlock(&(sc)->tx.lock) #define CPSW_TX_LOCK_ASSERT(sc) mtx_assert(&(sc)->tx.lock, MA_OWNED) #define CPSW_RX_LOCK(sc) do { \ mtx_assert(&(sc)->tx.lock, MA_NOTOWNED); \ mtx_lock(&(sc)->rx.lock); \ } while (0) #define CPSW_RX_UNLOCK(sc) mtx_unlock(&(sc)->rx.lock) #define CPSW_RX_LOCK_ASSERT(sc) mtx_assert(&(sc)->rx.lock, MA_OWNED) #define CPSW_PORT_LOCK(_sc) do { \ mtx_assert(&(_sc)->lock, MA_NOTOWNED); \ mtx_lock(&(_sc)->lock); \ } while (0) #define CPSW_PORT_UNLOCK(_sc) mtx_unlock(&(_sc)->lock) #define CPSW_PORT_LOCK_ASSERT(_sc) mtx_assert(&(_sc)->lock, MA_OWNED) /* * Read/Write macros */ #define cpsw_read_4(_sc, _reg) bus_read_4((_sc)->mem_res, (_reg)) #define cpsw_write_4(_sc, _reg, _val) \ bus_write_4((_sc)->mem_res, (_reg), (_val)) #define cpsw_cpdma_bd_offset(i) (CPSW_CPPI_RAM_OFFSET + ((i)*16)) #define cpsw_cpdma_bd_paddr(sc, slot) \ BUS_SPACE_PHYSADDR(sc->mem_res, slot->bd_offset) #define cpsw_cpdma_read_bd(sc, slot, val) \ bus_read_region_4(sc->mem_res, slot->bd_offset, (uint32_t *) val, 4) #define cpsw_cpdma_write_bd(sc, slot, val) \ bus_write_region_4(sc->mem_res, slot->bd_offset, (uint32_t *) val, 4) #define cpsw_cpdma_write_bd_next(sc, slot, next_slot) \ cpsw_write_4(sc, slot->bd_offset, cpsw_cpdma_bd_paddr(sc, next_slot)) #define cpsw_cpdma_write_bd_flags(sc, slot, val) \ bus_write_2(sc->mem_res, slot->bd_offset + 14, val) #define cpsw_cpdma_read_bd_flags(sc, slot) \ bus_read_2(sc->mem_res, slot->bd_offset + 14) #define cpsw_write_hdp_slot(sc, queue, slot) \ cpsw_write_4(sc, (queue)->hdp_offset, cpsw_cpdma_bd_paddr(sc, slot)) #define CP_OFFSET (CPSW_CPDMA_TX_CP(0) - CPSW_CPDMA_TX_HDP(0)) #define cpsw_read_cp(sc, queue) \ cpsw_read_4(sc, (queue)->hdp_offset + CP_OFFSET) #define cpsw_write_cp(sc, queue, val) \ cpsw_write_4(sc, (queue)->hdp_offset + CP_OFFSET, (val)) #define cpsw_write_cp_slot(sc, queue, slot) \ cpsw_write_cp(sc, queue, cpsw_cpdma_bd_paddr(sc, slot)) #if 0 /* XXX temporary function versions for debugging. */ static void cpsw_write_hdp_slotX(struct cpsw_softc *sc, struct cpsw_queue *queue, struct cpsw_slot *slot) { uint32_t reg = queue->hdp_offset; uint32_t v = cpsw_cpdma_bd_paddr(sc, slot); CPSW_DEBUGF(("HDP <=== 0x%08x (was 0x%08x)", v, cpsw_read_4(sc, reg))); cpsw_write_4(sc, reg, v); } static void cpsw_write_cp_slotX(struct cpsw_softc *sc, struct cpsw_queue *queue, struct cpsw_slot *slot) { uint32_t v = cpsw_cpdma_bd_paddr(sc, slot); CPSW_DEBUGF(("CP <=== 0x%08x (expecting 0x%08x)", v, cpsw_read_cp(sc, queue))); cpsw_write_cp(sc, queue, v); } #endif /* * Expanded dump routines for verbose debugging. */ static void cpsw_dump_slot(struct cpsw_softc *sc, struct cpsw_slot *slot) { static const char *flags[] = {"SOP", "EOP", "Owner", "EOQ", "TDownCmplt", "PassCRC", "Long", "Short", "MacCtl", "Overrun", "PktErr1", "PortEn/PktErr0", "RxVlanEncap", "Port2", "Port1", "Port0"}; struct cpsw_cpdma_bd bd; const char *sep; int i; cpsw_cpdma_read_bd(sc, slot, &bd); printf("BD Addr : 0x%08x Next : 0x%08x\n", cpsw_cpdma_bd_paddr(sc, slot), bd.next); printf(" BufPtr: 0x%08x BufLen: 0x%08x\n", bd.bufptr, bd.buflen); printf(" BufOff: 0x%08x PktLen: 0x%08x\n", bd.bufoff, bd.pktlen); printf(" Flags: "); sep = ""; for (i = 0; i < 16; ++i) { if (bd.flags & (1 << (15 - i))) { printf("%s%s", sep, flags[i]); sep = ","; } } printf("\n"); if (slot->mbuf) { printf(" Ether: %14D\n", (char *)(slot->mbuf->m_data), " "); printf(" Packet: %16D\n", (char *)(slot->mbuf->m_data) + 14, " "); } } #define CPSW_DUMP_SLOT(cs, slot) do { \ IF_DEBUG(sc) { \ cpsw_dump_slot(sc, slot); \ } \ } while (0) static void cpsw_dump_queue(struct cpsw_softc *sc, struct cpsw_slots *q) { struct cpsw_slot *slot; int i = 0; int others = 0; STAILQ_FOREACH(slot, q, next) { if (i > CPSW_TXFRAGS) ++others; else cpsw_dump_slot(sc, slot); ++i; } if (others) printf(" ... and %d more.\n", others); printf("\n"); } #define CPSW_DUMP_QUEUE(sc, q) do { \ IF_DEBUG(sc) { \ cpsw_dump_queue(sc, q); \ } \ } while (0) static void cpsw_init_slots(struct cpsw_softc *sc) { struct cpsw_slot *slot; int i; STAILQ_INIT(&sc->avail); /* Put the slot descriptors onto the global avail list. */ for (i = 0; i < nitems(sc->_slots); i++) { slot = &sc->_slots[i]; slot->bd_offset = cpsw_cpdma_bd_offset(i); STAILQ_INSERT_TAIL(&sc->avail, slot, next); } } static int cpsw_add_slots(struct cpsw_softc *sc, struct cpsw_queue *queue, int requested) { const int max_slots = nitems(sc->_slots); struct cpsw_slot *slot; int i; if (requested < 0) requested = max_slots; for (i = 0; i < requested; ++i) { slot = STAILQ_FIRST(&sc->avail); if (slot == NULL) return (0); if (bus_dmamap_create(sc->mbuf_dtag, 0, &slot->dmamap)) { device_printf(sc->dev, "failed to create dmamap\n"); return (ENOMEM); } STAILQ_REMOVE_HEAD(&sc->avail, next); STAILQ_INSERT_TAIL(&queue->avail, slot, next); ++queue->avail_queue_len; ++queue->queue_slots; } return (0); } static void cpsw_free_slot(struct cpsw_softc *sc, struct cpsw_slot *slot) { int error; if (slot->dmamap) { if (slot->mbuf) bus_dmamap_unload(sc->mbuf_dtag, slot->dmamap); error = bus_dmamap_destroy(sc->mbuf_dtag, slot->dmamap); KASSERT(error == 0, ("Mapping still active")); slot->dmamap = NULL; } if (slot->mbuf) { m_freem(slot->mbuf); slot->mbuf = NULL; } } static void cpsw_reset(struct cpsw_softc *sc) { int i; callout_stop(&sc->watchdog.callout); /* Reset RMII/RGMII wrapper. */ cpsw_write_4(sc, CPSW_WR_SOFT_RESET, 1); while (cpsw_read_4(sc, CPSW_WR_SOFT_RESET) & 1) ; /* Disable TX and RX interrupts for all cores. */ for (i = 0; i < 3; ++i) { cpsw_write_4(sc, CPSW_WR_C_RX_THRESH_EN(i), 0x00); cpsw_write_4(sc, CPSW_WR_C_TX_EN(i), 0x00); cpsw_write_4(sc, CPSW_WR_C_RX_EN(i), 0x00); cpsw_write_4(sc, CPSW_WR_C_MISC_EN(i), 0x00); } /* Reset CPSW subsystem. */ cpsw_write_4(sc, CPSW_SS_SOFT_RESET, 1); while (cpsw_read_4(sc, CPSW_SS_SOFT_RESET) & 1) ; /* Reset Sliver port 1 and 2 */ for (i = 0; i < 2; i++) { /* Reset */ cpsw_write_4(sc, CPSW_SL_SOFT_RESET(i), 1); while (cpsw_read_4(sc, CPSW_SL_SOFT_RESET(i)) & 1) ; } /* Reset DMA controller. */ cpsw_write_4(sc, CPSW_CPDMA_SOFT_RESET, 1); while (cpsw_read_4(sc, CPSW_CPDMA_SOFT_RESET) & 1) ; /* Disable TX & RX DMA */ cpsw_write_4(sc, CPSW_CPDMA_TX_CONTROL, 0); cpsw_write_4(sc, CPSW_CPDMA_RX_CONTROL, 0); /* Clear all queues. */ for (i = 0; i < 8; i++) { cpsw_write_4(sc, CPSW_CPDMA_TX_HDP(i), 0); cpsw_write_4(sc, CPSW_CPDMA_RX_HDP(i), 0); cpsw_write_4(sc, CPSW_CPDMA_TX_CP(i), 0); cpsw_write_4(sc, CPSW_CPDMA_RX_CP(i), 0); } /* Clear all interrupt Masks */ cpsw_write_4(sc, CPSW_CPDMA_RX_INTMASK_CLEAR, 0xFFFFFFFF); cpsw_write_4(sc, CPSW_CPDMA_TX_INTMASK_CLEAR, 0xFFFFFFFF); } static void cpsw_init(struct cpsw_softc *sc) { struct cpsw_slot *slot; uint32_t reg; /* Disable the interrupt pacing. */ reg = cpsw_read_4(sc, CPSW_WR_INT_CONTROL); reg &= ~(CPSW_WR_INT_PACE_EN | CPSW_WR_INT_PRESCALE_MASK); cpsw_write_4(sc, CPSW_WR_INT_CONTROL, reg); /* Clear ALE */ cpsw_write_4(sc, CPSW_ALE_CONTROL, CPSW_ALE_CTL_CLEAR_TBL); /* Enable ALE */ reg = CPSW_ALE_CTL_ENABLE; if (sc->dualemac) reg |= CPSW_ALE_CTL_VLAN_AWARE; cpsw_write_4(sc, CPSW_ALE_CONTROL, reg); /* Set Host Port Mapping. */ cpsw_write_4(sc, CPSW_PORT_P0_CPDMA_TX_PRI_MAP, 0x76543210); cpsw_write_4(sc, CPSW_PORT_P0_CPDMA_RX_CH_MAP, 0); /* Initialize ALE: set host port to forwarding(3). */ cpsw_write_4(sc, CPSW_ALE_PORTCTL(0), ALE_PORTCTL_INGRESS | ALE_PORTCTL_FORWARD); cpsw_write_4(sc, CPSW_SS_PTYPE, 0); /* Enable statistics for ports 0, 1 and 2 */ cpsw_write_4(sc, CPSW_SS_STAT_PORT_EN, 7); /* Turn off flow control. */ cpsw_write_4(sc, CPSW_SS_FLOW_CONTROL, 0); /* Make IP hdr aligned with 4 */ cpsw_write_4(sc, CPSW_CPDMA_RX_BUFFER_OFFSET, 2); /* Initialize RX Buffer Descriptors */ cpsw_write_4(sc, CPSW_CPDMA_RX_PENDTHRESH(0), 0); cpsw_write_4(sc, CPSW_CPDMA_RX_FREEBUFFER(0), 0); /* Enable TX & RX DMA */ cpsw_write_4(sc, CPSW_CPDMA_TX_CONTROL, 1); cpsw_write_4(sc, CPSW_CPDMA_RX_CONTROL, 1); /* Enable Interrupts for core 0 */ cpsw_write_4(sc, CPSW_WR_C_RX_THRESH_EN(0), 0xFF); cpsw_write_4(sc, CPSW_WR_C_RX_EN(0), 0xFF); cpsw_write_4(sc, CPSW_WR_C_TX_EN(0), 0xFF); cpsw_write_4(sc, CPSW_WR_C_MISC_EN(0), 0x1F); /* Enable host Error Interrupt */ cpsw_write_4(sc, CPSW_CPDMA_DMA_INTMASK_SET, 3); /* Enable interrupts for RX and TX on Channel 0 */ cpsw_write_4(sc, CPSW_CPDMA_RX_INTMASK_SET, CPSW_CPDMA_RX_INT(0) | CPSW_CPDMA_RX_INT_THRESH(0)); cpsw_write_4(sc, CPSW_CPDMA_TX_INTMASK_SET, 1); /* Initialze MDIO - ENABLE, PREAMBLE=0, FAULTENB, CLKDIV=0xFF */ /* TODO Calculate MDCLK=CLK/(CLKDIV+1) */ cpsw_write_4(sc, MDIOCONTROL, MDIOCTL_ENABLE | MDIOCTL_FAULTENB | 0xff); /* Select MII in GMII_SEL, Internal Delay mode */ //ti_scm_reg_write_4(0x650, 0); /* Initialize active queues. */ slot = STAILQ_FIRST(&sc->tx.active); if (slot != NULL) cpsw_write_hdp_slot(sc, &sc->tx, slot); slot = STAILQ_FIRST(&sc->rx.active); if (slot != NULL) cpsw_write_hdp_slot(sc, &sc->rx, slot); cpsw_rx_enqueue(sc); cpsw_write_4(sc, CPSW_CPDMA_RX_FREEBUFFER(0), sc->rx.active_queue_len); cpsw_write_4(sc, CPSW_CPDMA_RX_PENDTHRESH(0), CPSW_TXFRAGS); /* Activate network interface. */ sc->rx.running = 1; sc->tx.running = 1; sc->watchdog.timer = 0; callout_init(&sc->watchdog.callout, 0); callout_reset(&sc->watchdog.callout, hz, cpsw_tx_watchdog, sc); } /* * * Device Probe, Attach, Detach. * */ static int cpsw_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "ti,cpsw")) return (ENXIO); device_set_desc(dev, "3-port Switch Ethernet Subsystem"); return (BUS_PROBE_DEFAULT); } static int cpsw_intr_attach(struct cpsw_softc *sc) { int i; for (i = 0; i < CPSW_INTR_COUNT; i++) { if (bus_setup_intr(sc->dev, sc->irq_res[i], INTR_TYPE_NET | INTR_MPSAFE, NULL, cpsw_intr_cb[i].cb, sc, &sc->ih_cookie[i]) != 0) { return (-1); } } return (0); } static void cpsw_intr_detach(struct cpsw_softc *sc) { int i; for (i = 0; i < CPSW_INTR_COUNT; i++) { if (sc->ih_cookie[i]) { bus_teardown_intr(sc->dev, sc->irq_res[i], sc->ih_cookie[i]); } } } static int cpsw_get_fdt_data(struct cpsw_softc *sc, int port) { char *name; int len, phy, vlan; pcell_t phy_id[3], vlan_id; phandle_t child; unsigned long mdio_child_addr; /* Find any slave with phy_id */ phy = -1; vlan = -1; for (child = OF_child(sc->node); child != 0; child = OF_peer(child)) { - if (OF_getprop_alloc(child, "name", 1, (void **)&name) < 0) + if (OF_getprop_alloc(child, "name", (void **)&name) < 0) continue; if (sscanf(name, "slave@%lx", &mdio_child_addr) != 1) { OF_prop_free(name); continue; } OF_prop_free(name); if (mdio_child_addr != slave_mdio_addr[port]) continue; len = OF_getproplen(child, "phy_id"); if (len / sizeof(pcell_t) == 2) { /* Get phy address from fdt */ if (OF_getencprop(child, "phy_id", phy_id, len) > 0) phy = phy_id[1]; } len = OF_getproplen(child, "dual_emac_res_vlan"); if (len / sizeof(pcell_t) == 1) { /* Get phy address from fdt */ if (OF_getencprop(child, "dual_emac_res_vlan", &vlan_id, len) > 0) { vlan = vlan_id; } } break; } if (phy == -1) return (ENXIO); sc->port[port].phy = phy; sc->port[port].vlan = vlan; return (0); } static int cpsw_attach(device_t dev) { int error, i; struct cpsw_softc *sc; uint32_t reg; sc = device_get_softc(dev); sc->dev = dev; sc->node = ofw_bus_get_node(dev); getbinuptime(&sc->attach_uptime); if (OF_getencprop(sc->node, "active_slave", &sc->active_slave, sizeof(sc->active_slave)) <= 0) { sc->active_slave = 0; } if (sc->active_slave > 1) sc->active_slave = 1; if (OF_hasprop(sc->node, "dual_emac")) sc->dualemac = 1; for (i = 0; i < CPSW_PORTS; i++) { if (!sc->dualemac && i != sc->active_slave) continue; if (cpsw_get_fdt_data(sc, i) != 0) { device_printf(dev, "failed to get PHY address from FDT\n"); return (ENXIO); } } /* Initialize mutexes */ mtx_init(&sc->tx.lock, device_get_nameunit(dev), "cpsw TX lock", MTX_DEF); mtx_init(&sc->rx.lock, device_get_nameunit(dev), "cpsw RX lock", MTX_DEF); /* Allocate IRQ resources */ error = bus_alloc_resources(dev, irq_res_spec, sc->irq_res); if (error) { device_printf(dev, "could not allocate IRQ resources\n"); cpsw_detach(dev); return (ENXIO); } sc->mem_rid = 0; sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->mem_rid, RF_ACTIVE); if (sc->mem_res == NULL) { device_printf(sc->dev, "failed to allocate memory resource\n"); cpsw_detach(dev); return (ENXIO); } reg = cpsw_read_4(sc, CPSW_SS_IDVER); device_printf(dev, "CPSW SS Version %d.%d (%d)\n", (reg >> 8 & 0x7), reg & 0xFF, (reg >> 11) & 0x1F); cpsw_add_sysctls(sc); /* Allocate a busdma tag and DMA safe memory for mbufs. */ error = bus_dma_tag_create( bus_get_dma_tag(sc->dev), /* parent */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filtfunc, filtfuncarg */ MCLBYTES, CPSW_TXFRAGS, /* maxsize, nsegments */ MCLBYTES, 0, /* maxsegsz, flags */ NULL, NULL, /* lockfunc, lockfuncarg */ &sc->mbuf_dtag); /* dmatag */ if (error) { device_printf(dev, "bus_dma_tag_create failed\n"); cpsw_detach(dev); return (error); } /* Allocate a NULL buffer for padding. */ sc->nullpad = malloc(ETHER_MIN_LEN, M_DEVBUF, M_WAITOK | M_ZERO); cpsw_init_slots(sc); /* Allocate slots to TX and RX queues. */ STAILQ_INIT(&sc->rx.avail); STAILQ_INIT(&sc->rx.active); STAILQ_INIT(&sc->tx.avail); STAILQ_INIT(&sc->tx.active); // For now: 128 slots to TX, rest to RX. // XXX TODO: start with 32/64 and grow dynamically based on demand. if (cpsw_add_slots(sc, &sc->tx, 128) || cpsw_add_slots(sc, &sc->rx, -1)) { device_printf(dev, "failed to allocate dmamaps\n"); cpsw_detach(dev); return (ENOMEM); } device_printf(dev, "Initial queue size TX=%d RX=%d\n", sc->tx.queue_slots, sc->rx.queue_slots); sc->tx.hdp_offset = CPSW_CPDMA_TX_HDP(0); sc->rx.hdp_offset = CPSW_CPDMA_RX_HDP(0); if (cpsw_intr_attach(sc) == -1) { device_printf(dev, "failed to setup interrupts\n"); cpsw_detach(dev); return (ENXIO); } #ifdef CPSW_ETHERSWITCH for (i = 0; i < CPSW_VLANS; i++) cpsw_vgroups[i].vid = -1; #endif /* Reset the controller. */ cpsw_reset(sc); cpsw_init(sc); for (i = 0; i < CPSW_PORTS; i++) { if (!sc->dualemac && i != sc->active_slave) continue; sc->port[i].dev = device_add_child(dev, "cpsw", i); if (sc->port[i].dev == NULL) { cpsw_detach(dev); return (ENXIO); } } bus_generic_probe(dev); bus_generic_attach(dev); return (0); } static int cpsw_detach(device_t dev) { struct cpsw_softc *sc; int error, i; bus_generic_detach(dev); sc = device_get_softc(dev); for (i = 0; i < CPSW_PORTS; i++) { if (sc->port[i].dev) device_delete_child(dev, sc->port[i].dev); } if (device_is_attached(dev)) { callout_stop(&sc->watchdog.callout); callout_drain(&sc->watchdog.callout); } /* Stop and release all interrupts */ cpsw_intr_detach(sc); /* Free dmamaps and mbufs */ for (i = 0; i < nitems(sc->_slots); ++i) cpsw_free_slot(sc, &sc->_slots[i]); /* Free null padding buffer. */ if (sc->nullpad) free(sc->nullpad, M_DEVBUF); /* Free DMA tag */ if (sc->mbuf_dtag) { error = bus_dma_tag_destroy(sc->mbuf_dtag); KASSERT(error == 0, ("Unable to destroy DMA tag")); } /* Free IO memory handler */ if (sc->mem_res != NULL) bus_release_resource(dev, SYS_RES_MEMORY, sc->mem_rid, sc->mem_res); bus_release_resources(dev, irq_res_spec, sc->irq_res); /* Destroy mutexes */ mtx_destroy(&sc->rx.lock); mtx_destroy(&sc->tx.lock); /* Detach the switch device, if present. */ error = bus_generic_detach(dev); if (error != 0) return (error); return (device_delete_children(dev)); } static phandle_t cpsw_get_node(device_t bus, device_t dev) { /* Share controller node with port device. */ return (ofw_bus_get_node(bus)); } static int cpswp_probe(device_t dev) { if (device_get_unit(dev) > 1) { device_printf(dev, "Only two ports are supported.\n"); return (ENXIO); } device_set_desc(dev, "Ethernet Switch Port"); return (BUS_PROBE_DEFAULT); } static int cpswp_attach(device_t dev) { int error; struct ifnet *ifp; struct cpswp_softc *sc; uint32_t reg; uint8_t mac_addr[ETHER_ADDR_LEN]; sc = device_get_softc(dev); sc->dev = dev; sc->pdev = device_get_parent(dev); sc->swsc = device_get_softc(sc->pdev); sc->unit = device_get_unit(dev); sc->phy = sc->swsc->port[sc->unit].phy; sc->vlan = sc->swsc->port[sc->unit].vlan; if (sc->swsc->dualemac && sc->vlan == -1) sc->vlan = sc->unit + 1; if (sc->unit == 0) { sc->physel = MDIOUSERPHYSEL0; sc->phyaccess = MDIOUSERACCESS0; } else { sc->physel = MDIOUSERPHYSEL1; sc->phyaccess = MDIOUSERACCESS1; } mtx_init(&sc->lock, device_get_nameunit(dev), "cpsw port lock", MTX_DEF); /* Allocate network interface */ ifp = sc->ifp = if_alloc(IFT_ETHER); if (ifp == NULL) { cpswp_detach(dev); return (ENXIO); } if_initname(ifp, device_get_name(sc->dev), sc->unit); ifp->if_softc = sc; ifp->if_flags = IFF_SIMPLEX | IFF_MULTICAST | IFF_BROADCAST; ifp->if_capabilities = IFCAP_VLAN_MTU | IFCAP_HWCSUM; //FIXME VLAN? ifp->if_capenable = ifp->if_capabilities; ifp->if_init = cpswp_init; ifp->if_start = cpswp_start; ifp->if_ioctl = cpswp_ioctl; ifp->if_snd.ifq_drv_maxlen = sc->swsc->tx.queue_slots; IFQ_SET_MAXLEN(&ifp->if_snd, ifp->if_snd.ifq_drv_maxlen); IFQ_SET_READY(&ifp->if_snd); /* Get high part of MAC address from control module (mac_id[0|1]_hi) */ ti_scm_reg_read_4(SCM_MAC_ID0_HI + sc->unit * 8, ®); mac_addr[0] = reg & 0xFF; mac_addr[1] = (reg >> 8) & 0xFF; mac_addr[2] = (reg >> 16) & 0xFF; mac_addr[3] = (reg >> 24) & 0xFF; /* Get low part of MAC address from control module (mac_id[0|1]_lo) */ ti_scm_reg_read_4(SCM_MAC_ID0_LO + sc->unit * 8, ®); mac_addr[4] = reg & 0xFF; mac_addr[5] = (reg >> 8) & 0xFF; error = mii_attach(dev, &sc->miibus, ifp, cpswp_ifmedia_upd, cpswp_ifmedia_sts, BMSR_DEFCAPMASK, sc->phy, MII_OFFSET_ANY, 0); if (error) { device_printf(dev, "attaching PHYs failed\n"); cpswp_detach(dev); return (error); } sc->mii = device_get_softc(sc->miibus); /* Select PHY and enable interrupts */ cpsw_write_4(sc->swsc, sc->physel, MDIO_PHYSEL_LINKINTENB | (sc->phy & 0x1F)); ether_ifattach(sc->ifp, mac_addr); callout_init(&sc->mii_callout, 0); return (0); } static int cpswp_detach(device_t dev) { struct cpswp_softc *sc; sc = device_get_softc(dev); CPSW_DEBUGF(sc->swsc, ("")); if (device_is_attached(dev)) { ether_ifdetach(sc->ifp); CPSW_PORT_LOCK(sc); cpswp_stop_locked(sc); CPSW_PORT_UNLOCK(sc); callout_drain(&sc->mii_callout); } bus_generic_detach(dev); if_free(sc->ifp); mtx_destroy(&sc->lock); return (0); } /* * * Init/Shutdown. * */ static int cpsw_ports_down(struct cpsw_softc *sc) { struct cpswp_softc *psc; struct ifnet *ifp1, *ifp2; if (!sc->dualemac) return (1); psc = device_get_softc(sc->port[0].dev); ifp1 = psc->ifp; psc = device_get_softc(sc->port[1].dev); ifp2 = psc->ifp; if ((ifp1->if_flags & IFF_UP) == 0 && (ifp2->if_flags & IFF_UP) == 0) return (1); return (0); } static void cpswp_init(void *arg) { struct cpswp_softc *sc = arg; CPSW_DEBUGF(sc->swsc, ("")); CPSW_PORT_LOCK(sc); cpswp_init_locked(arg); CPSW_PORT_UNLOCK(sc); } static void cpswp_init_locked(void *arg) { #ifdef CPSW_ETHERSWITCH int i; #endif struct cpswp_softc *sc = arg; struct ifnet *ifp; uint32_t reg; CPSW_DEBUGF(sc->swsc, ("")); CPSW_PORT_LOCK_ASSERT(sc); ifp = sc->ifp; if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) return; getbinuptime(&sc->init_uptime); if (!sc->swsc->rx.running && !sc->swsc->tx.running) { /* Reset the controller. */ cpsw_reset(sc->swsc); cpsw_init(sc->swsc); } /* Set Slave Mapping. */ cpsw_write_4(sc->swsc, CPSW_SL_RX_PRI_MAP(sc->unit), 0x76543210); cpsw_write_4(sc->swsc, CPSW_PORT_P_TX_PRI_MAP(sc->unit + 1), 0x33221100); cpsw_write_4(sc->swsc, CPSW_SL_RX_MAXLEN(sc->unit), 0x5f2); /* Enable MAC RX/TX modules. */ /* TODO: Docs claim that IFCTL_B and IFCTL_A do the same thing? */ /* Huh? Docs call bit 0 "Loopback" some places, "FullDuplex" others. */ reg = cpsw_read_4(sc->swsc, CPSW_SL_MACCONTROL(sc->unit)); reg |= CPSW_SL_MACTL_GMII_ENABLE; cpsw_write_4(sc->swsc, CPSW_SL_MACCONTROL(sc->unit), reg); /* Initialize ALE: set port to forwarding, initialize addrs */ cpsw_write_4(sc->swsc, CPSW_ALE_PORTCTL(sc->unit + 1), ALE_PORTCTL_INGRESS | ALE_PORTCTL_FORWARD); cpswp_ale_update_addresses(sc, 1); if (sc->swsc->dualemac) { /* Set Port VID. */ cpsw_write_4(sc->swsc, CPSW_PORT_P_VLAN(sc->unit + 1), sc->vlan & 0xfff); cpsw_ale_update_vlan_table(sc->swsc, sc->vlan, (1 << (sc->unit + 1)) | (1 << 0), /* Member list */ (1 << (sc->unit + 1)) | (1 << 0), /* Untagged egress */ (1 << (sc->unit + 1)) | (1 << 0), 0); /* mcast reg flood */ #ifdef CPSW_ETHERSWITCH for (i = 0; i < CPSW_VLANS; i++) { if (cpsw_vgroups[i].vid != -1) continue; cpsw_vgroups[i].vid = sc->vlan; break; } #endif } mii_mediachg(sc->mii); callout_reset(&sc->mii_callout, hz, cpswp_tick, sc); ifp->if_drv_flags |= IFF_DRV_RUNNING; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; } static int cpsw_shutdown(device_t dev) { struct cpsw_softc *sc; struct cpswp_softc *psc; int i; sc = device_get_softc(dev); CPSW_DEBUGF(sc, ("")); for (i = 0; i < CPSW_PORTS; i++) { if (!sc->dualemac && i != sc->active_slave) continue; psc = device_get_softc(sc->port[i].dev); CPSW_PORT_LOCK(psc); cpswp_stop_locked(psc); CPSW_PORT_UNLOCK(psc); } return (0); } static void cpsw_rx_teardown(struct cpsw_softc *sc) { int i = 0; CPSW_RX_LOCK(sc); CPSW_DEBUGF(sc, ("starting RX teardown")); sc->rx.teardown = 1; cpsw_write_4(sc, CPSW_CPDMA_RX_TEARDOWN, 0); CPSW_RX_UNLOCK(sc); while (sc->rx.running) { if (++i > 10) { device_printf(sc->dev, "Unable to cleanly shutdown receiver\n"); return; } DELAY(200); } if (!sc->rx.running) CPSW_DEBUGF(sc, ("finished RX teardown (%d retries)", i)); } static void cpsw_tx_teardown(struct cpsw_softc *sc) { int i = 0; CPSW_TX_LOCK(sc); CPSW_DEBUGF(sc, ("starting TX teardown")); /* Start the TX queue teardown if queue is not empty. */ if (STAILQ_FIRST(&sc->tx.active) != NULL) cpsw_write_4(sc, CPSW_CPDMA_TX_TEARDOWN, 0); else sc->tx.teardown = 1; cpsw_tx_dequeue(sc); while (sc->tx.running && ++i < 10) { DELAY(200); cpsw_tx_dequeue(sc); } if (sc->tx.running) { device_printf(sc->dev, "Unable to cleanly shutdown transmitter\n"); } CPSW_DEBUGF(sc, ("finished TX teardown (%d retries, %d idle buffers)", i, sc->tx.active_queue_len)); CPSW_TX_UNLOCK(sc); } static void cpswp_stop_locked(struct cpswp_softc *sc) { struct ifnet *ifp; uint32_t reg; ifp = sc->ifp; CPSW_DEBUGF(sc->swsc, ("")); CPSW_PORT_LOCK_ASSERT(sc); if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) return; /* Disable interface */ ifp->if_drv_flags &= ~IFF_DRV_RUNNING; ifp->if_drv_flags |= IFF_DRV_OACTIVE; /* Stop ticker */ callout_stop(&sc->mii_callout); /* Tear down the RX/TX queues. */ if (cpsw_ports_down(sc->swsc)) { cpsw_rx_teardown(sc->swsc); cpsw_tx_teardown(sc->swsc); } /* Stop MAC RX/TX modules. */ reg = cpsw_read_4(sc->swsc, CPSW_SL_MACCONTROL(sc->unit)); reg &= ~CPSW_SL_MACTL_GMII_ENABLE; cpsw_write_4(sc->swsc, CPSW_SL_MACCONTROL(sc->unit), reg); if (cpsw_ports_down(sc->swsc)) { /* Capture stats before we reset controller. */ cpsw_stats_collect(sc->swsc); cpsw_reset(sc->swsc); cpsw_init(sc->swsc); } } /* * Suspend/Resume. */ static int cpsw_suspend(device_t dev) { struct cpsw_softc *sc; struct cpswp_softc *psc; int i; sc = device_get_softc(dev); CPSW_DEBUGF(sc, ("")); for (i = 0; i < CPSW_PORTS; i++) { if (!sc->dualemac && i != sc->active_slave) continue; psc = device_get_softc(sc->port[i].dev); CPSW_PORT_LOCK(psc); cpswp_stop_locked(psc); CPSW_PORT_UNLOCK(psc); } return (0); } static int cpsw_resume(device_t dev) { struct cpsw_softc *sc; sc = device_get_softc(dev); CPSW_DEBUGF(sc, ("UNIMPLEMENTED")); return (0); } /* * * IOCTL * */ static void cpsw_set_promisc(struct cpswp_softc *sc, int set) { uint32_t reg; /* * Enabling promiscuous mode requires ALE_BYPASS to be enabled. * That disables the ALE forwarding logic and causes every * packet to be sent only to the host port. In bypass mode, * the ALE processes host port transmit packets the same as in * normal mode. */ reg = cpsw_read_4(sc->swsc, CPSW_ALE_CONTROL); reg &= ~CPSW_ALE_CTL_BYPASS; if (set) reg |= CPSW_ALE_CTL_BYPASS; cpsw_write_4(sc->swsc, CPSW_ALE_CONTROL, reg); } static void cpsw_set_allmulti(struct cpswp_softc *sc, int set) { if (set) { printf("All-multicast mode unimplemented\n"); } } static int cpswp_ioctl(struct ifnet *ifp, u_long command, caddr_t data) { struct cpswp_softc *sc; struct ifreq *ifr; int error; uint32_t changed; error = 0; sc = ifp->if_softc; ifr = (struct ifreq *)data; switch (command) { case SIOCSIFCAP: changed = ifp->if_capenable ^ ifr->ifr_reqcap; if (changed & IFCAP_HWCSUM) { if ((ifr->ifr_reqcap & changed) & IFCAP_HWCSUM) ifp->if_capenable |= IFCAP_HWCSUM; else ifp->if_capenable &= ~IFCAP_HWCSUM; } error = 0; break; case SIOCSIFFLAGS: CPSW_PORT_LOCK(sc); if (ifp->if_flags & IFF_UP) { if (ifp->if_drv_flags & IFF_DRV_RUNNING) { changed = ifp->if_flags ^ sc->if_flags; CPSW_DEBUGF(sc->swsc, ("SIOCSIFFLAGS: UP & RUNNING (changed=0x%x)", changed)); if (changed & IFF_PROMISC) cpsw_set_promisc(sc, ifp->if_flags & IFF_PROMISC); if (changed & IFF_ALLMULTI) cpsw_set_allmulti(sc, ifp->if_flags & IFF_ALLMULTI); } else { CPSW_DEBUGF(sc->swsc, ("SIOCSIFFLAGS: starting up")); cpswp_init_locked(sc); } } else if (ifp->if_drv_flags & IFF_DRV_RUNNING) { CPSW_DEBUGF(sc->swsc, ("SIOCSIFFLAGS: shutting down")); cpswp_stop_locked(sc); } sc->if_flags = ifp->if_flags; CPSW_PORT_UNLOCK(sc); break; case SIOCADDMULTI: cpswp_ale_update_addresses(sc, 0); break; case SIOCDELMULTI: /* Ugh. DELMULTI doesn't provide the specific address being removed, so the best we can do is remove everything and rebuild it all. */ cpswp_ale_update_addresses(sc, 1); break; case SIOCGIFMEDIA: case SIOCSIFMEDIA: error = ifmedia_ioctl(ifp, ifr, &sc->mii->mii_media, command); break; default: error = ether_ioctl(ifp, command, data); } return (error); } /* * * MIIBUS * */ static int cpswp_miibus_ready(struct cpsw_softc *sc, uint32_t reg) { uint32_t r, retries = CPSW_MIIBUS_RETRIES; while (--retries) { r = cpsw_read_4(sc, reg); if ((r & MDIO_PHYACCESS_GO) == 0) return (1); DELAY(CPSW_MIIBUS_DELAY); } return (0); } static int cpswp_miibus_readreg(device_t dev, int phy, int reg) { struct cpswp_softc *sc; uint32_t cmd, r; sc = device_get_softc(dev); if (!cpswp_miibus_ready(sc->swsc, sc->phyaccess)) { device_printf(dev, "MDIO not ready to read\n"); return (0); } /* Set GO, reg, phy */ cmd = MDIO_PHYACCESS_GO | (reg & 0x1F) << 21 | (phy & 0x1F) << 16; cpsw_write_4(sc->swsc, sc->phyaccess, cmd); if (!cpswp_miibus_ready(sc->swsc, sc->phyaccess)) { device_printf(dev, "MDIO timed out during read\n"); return (0); } r = cpsw_read_4(sc->swsc, sc->phyaccess); if ((r & MDIO_PHYACCESS_ACK) == 0) { device_printf(dev, "Failed to read from PHY.\n"); r = 0; } return (r & 0xFFFF); } static int cpswp_miibus_writereg(device_t dev, int phy, int reg, int value) { struct cpswp_softc *sc; uint32_t cmd; sc = device_get_softc(dev); if (!cpswp_miibus_ready(sc->swsc, sc->phyaccess)) { device_printf(dev, "MDIO not ready to write\n"); return (0); } /* Set GO, WRITE, reg, phy, and value */ cmd = MDIO_PHYACCESS_GO | MDIO_PHYACCESS_WRITE | (reg & 0x1F) << 21 | (phy & 0x1F) << 16 | (value & 0xFFFF); cpsw_write_4(sc->swsc, sc->phyaccess, cmd); if (!cpswp_miibus_ready(sc->swsc, sc->phyaccess)) { device_printf(dev, "MDIO timed out during write\n"); return (0); } return (0); } static void cpswp_miibus_statchg(device_t dev) { struct cpswp_softc *sc; uint32_t mac_control, reg; sc = device_get_softc(dev); CPSW_DEBUGF(sc->swsc, ("")); reg = CPSW_SL_MACCONTROL(sc->unit); mac_control = cpsw_read_4(sc->swsc, reg); mac_control &= ~(CPSW_SL_MACTL_GIG | CPSW_SL_MACTL_IFCTL_A | CPSW_SL_MACTL_IFCTL_B | CPSW_SL_MACTL_FULLDUPLEX); switch(IFM_SUBTYPE(sc->mii->mii_media_active)) { case IFM_1000_SX: case IFM_1000_LX: case IFM_1000_CX: case IFM_1000_T: mac_control |= CPSW_SL_MACTL_GIG; break; case IFM_100_TX: mac_control |= CPSW_SL_MACTL_IFCTL_A; break; } if (sc->mii->mii_media_active & IFM_FDX) mac_control |= CPSW_SL_MACTL_FULLDUPLEX; cpsw_write_4(sc->swsc, reg, mac_control); } /* * * Transmit/Receive Packets. * */ static void cpsw_intr_rx(void *arg) { struct cpsw_softc *sc; struct ifnet *ifp; struct mbuf *received, *next; sc = (struct cpsw_softc *)arg; CPSW_RX_LOCK(sc); if (sc->rx.teardown) { sc->rx.running = 0; sc->rx.teardown = 0; cpsw_write_cp(sc, &sc->rx, 0xfffffffc); } received = cpsw_rx_dequeue(sc); cpsw_rx_enqueue(sc); cpsw_write_4(sc, CPSW_CPDMA_CPDMA_EOI_VECTOR, 1); CPSW_RX_UNLOCK(sc); while (received != NULL) { next = received->m_nextpkt; received->m_nextpkt = NULL; ifp = received->m_pkthdr.rcvif; (*ifp->if_input)(ifp, received); if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1); received = next; } } static struct mbuf * cpsw_rx_dequeue(struct cpsw_softc *sc) { int nsegs, port, removed; struct cpsw_cpdma_bd bd; struct cpsw_slot *last, *slot; struct cpswp_softc *psc; struct mbuf *m, *m0, *mb_head, *mb_tail; uint16_t m0_flags; nsegs = 0; m0 = NULL; last = NULL; mb_head = NULL; mb_tail = NULL; removed = 0; /* Pull completed packets off hardware RX queue. */ while ((slot = STAILQ_FIRST(&sc->rx.active)) != NULL) { cpsw_cpdma_read_bd(sc, slot, &bd); /* * Stop on packets still in use by hardware, but do not stop * on packets with the teardown complete flag, they will be * discarded later. */ if ((bd.flags & (CPDMA_BD_OWNER | CPDMA_BD_TDOWNCMPLT)) == CPDMA_BD_OWNER) break; last = slot; ++removed; STAILQ_REMOVE_HEAD(&sc->rx.active, next); STAILQ_INSERT_TAIL(&sc->rx.avail, slot, next); bus_dmamap_sync(sc->mbuf_dtag, slot->dmamap, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->mbuf_dtag, slot->dmamap); m = slot->mbuf; slot->mbuf = NULL; if (bd.flags & CPDMA_BD_TDOWNCMPLT) { CPSW_DEBUGF(sc, ("RX teardown is complete")); m_freem(m); sc->rx.running = 0; sc->rx.teardown = 0; break; } port = (bd.flags & CPDMA_BD_PORT_MASK) - 1; KASSERT(port >= 0 && port <= 1, ("patcket received with invalid port: %d", port)); psc = device_get_softc(sc->port[port].dev); /* Set up mbuf */ m->m_data += bd.bufoff; m->m_len = bd.buflen; if (bd.flags & CPDMA_BD_SOP) { m->m_pkthdr.len = bd.pktlen; m->m_pkthdr.rcvif = psc->ifp; m->m_flags |= M_PKTHDR; m0_flags = bd.flags; m0 = m; } nsegs++; m->m_next = NULL; m->m_nextpkt = NULL; if (bd.flags & CPDMA_BD_EOP && m0 != NULL) { if (m0_flags & CPDMA_BD_PASS_CRC) m_adj(m0, -ETHER_CRC_LEN); m0_flags = 0; m0 = NULL; if (nsegs > sc->rx.longest_chain) sc->rx.longest_chain = nsegs; nsegs = 0; } if ((psc->ifp->if_capenable & IFCAP_RXCSUM) != 0) { /* check for valid CRC by looking into pkt_err[5:4] */ if ((bd.flags & (CPDMA_BD_SOP | CPDMA_BD_PKT_ERR_MASK)) == CPDMA_BD_SOP) { m->m_pkthdr.csum_flags |= CSUM_IP_CHECKED; m->m_pkthdr.csum_flags |= CSUM_IP_VALID; m->m_pkthdr.csum_data = 0xffff; } } if (STAILQ_FIRST(&sc->rx.active) != NULL && (bd.flags & (CPDMA_BD_EOP | CPDMA_BD_EOQ)) == (CPDMA_BD_EOP | CPDMA_BD_EOQ)) { cpsw_write_hdp_slot(sc, &sc->rx, STAILQ_FIRST(&sc->rx.active)); sc->rx.queue_restart++; } /* Add mbuf to packet list to be returned. */ if (mb_tail != NULL && (bd.flags & CPDMA_BD_SOP)) { mb_tail->m_nextpkt = m; } else if (mb_tail != NULL) { mb_tail->m_next = m; } else if (mb_tail == NULL && (bd.flags & CPDMA_BD_SOP) == 0) { if (bootverbose) printf( "%s: %s: discanding fragment packet w/o header\n", __func__, psc->ifp->if_xname); m_freem(m); continue; } else { mb_head = m; } mb_tail = m; } if (removed != 0) { cpsw_write_cp_slot(sc, &sc->rx, last); sc->rx.queue_removes += removed; sc->rx.avail_queue_len += removed; sc->rx.active_queue_len -= removed; if (sc->rx.avail_queue_len > sc->rx.max_avail_queue_len) sc->rx.max_avail_queue_len = sc->rx.avail_queue_len; CPSW_DEBUGF(sc, ("Removed %d received packet(s) from RX queue", removed)); } return (mb_head); } static void cpsw_rx_enqueue(struct cpsw_softc *sc) { bus_dma_segment_t seg[1]; struct cpsw_cpdma_bd bd; struct cpsw_slot *first_new_slot, *last_old_slot, *next, *slot; int error, nsegs, added = 0; /* Register new mbufs with hardware. */ first_new_slot = NULL; last_old_slot = STAILQ_LAST(&sc->rx.active, cpsw_slot, next); while ((slot = STAILQ_FIRST(&sc->rx.avail)) != NULL) { if (first_new_slot == NULL) first_new_slot = slot; if (slot->mbuf == NULL) { slot->mbuf = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (slot->mbuf == NULL) { device_printf(sc->dev, "Unable to fill RX queue\n"); break; } slot->mbuf->m_len = slot->mbuf->m_pkthdr.len = slot->mbuf->m_ext.ext_size; } error = bus_dmamap_load_mbuf_sg(sc->mbuf_dtag, slot->dmamap, slot->mbuf, seg, &nsegs, BUS_DMA_NOWAIT); KASSERT(nsegs == 1, ("More than one segment (nsegs=%d)", nsegs)); KASSERT(error == 0, ("DMA error (error=%d)", error)); if (error != 0 || nsegs != 1) { device_printf(sc->dev, "%s: Can't prep RX buf for DMA (nsegs=%d, error=%d)\n", __func__, nsegs, error); bus_dmamap_unload(sc->mbuf_dtag, slot->dmamap); m_freem(slot->mbuf); slot->mbuf = NULL; break; } bus_dmamap_sync(sc->mbuf_dtag, slot->dmamap, BUS_DMASYNC_PREREAD); /* Create and submit new rx descriptor. */ if ((next = STAILQ_NEXT(slot, next)) != NULL) bd.next = cpsw_cpdma_bd_paddr(sc, next); else bd.next = 0; bd.bufptr = seg->ds_addr; bd.bufoff = 0; bd.buflen = MCLBYTES - 1; bd.pktlen = bd.buflen; bd.flags = CPDMA_BD_OWNER; cpsw_cpdma_write_bd(sc, slot, &bd); ++added; STAILQ_REMOVE_HEAD(&sc->rx.avail, next); STAILQ_INSERT_TAIL(&sc->rx.active, slot, next); } if (added == 0 || first_new_slot == NULL) return; CPSW_DEBUGF(sc, ("Adding %d buffers to RX queue", added)); /* Link new entries to hardware RX queue. */ if (last_old_slot == NULL) { /* Start a fresh queue. */ cpsw_write_hdp_slot(sc, &sc->rx, first_new_slot); } else { /* Add buffers to end of current queue. */ cpsw_cpdma_write_bd_next(sc, last_old_slot, first_new_slot); } sc->rx.queue_adds += added; sc->rx.avail_queue_len -= added; sc->rx.active_queue_len += added; cpsw_write_4(sc, CPSW_CPDMA_RX_FREEBUFFER(0), added); if (sc->rx.active_queue_len > sc->rx.max_active_queue_len) sc->rx.max_active_queue_len = sc->rx.active_queue_len; } static void cpswp_start(struct ifnet *ifp) { struct cpswp_softc *sc; sc = ifp->if_softc; if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0 || sc->swsc->tx.running == 0) { return; } CPSW_TX_LOCK(sc->swsc); cpswp_tx_enqueue(sc); cpsw_tx_dequeue(sc->swsc); CPSW_TX_UNLOCK(sc->swsc); } static void cpsw_intr_tx(void *arg) { struct cpsw_softc *sc; sc = (struct cpsw_softc *)arg; CPSW_TX_LOCK(sc); if (cpsw_read_4(sc, CPSW_CPDMA_TX_CP(0)) == 0xfffffffc) cpsw_write_cp(sc, &sc->tx, 0xfffffffc); cpsw_tx_dequeue(sc); cpsw_write_4(sc, CPSW_CPDMA_CPDMA_EOI_VECTOR, 2); CPSW_TX_UNLOCK(sc); } static void cpswp_tx_enqueue(struct cpswp_softc *sc) { bus_dma_segment_t segs[CPSW_TXFRAGS]; struct cpsw_cpdma_bd bd; struct cpsw_slot *first_new_slot, *last, *last_old_slot, *next, *slot; struct mbuf *m0; int error, nsegs, seg, added = 0, padlen; /* Pull pending packets from IF queue and prep them for DMA. */ last = NULL; first_new_slot = NULL; last_old_slot = STAILQ_LAST(&sc->swsc->tx.active, cpsw_slot, next); while ((slot = STAILQ_FIRST(&sc->swsc->tx.avail)) != NULL) { IF_DEQUEUE(&sc->ifp->if_snd, m0); if (m0 == NULL) break; slot->mbuf = m0; padlen = ETHER_MIN_LEN - ETHER_CRC_LEN - m0->m_pkthdr.len; if (padlen < 0) padlen = 0; else if (padlen > 0) m_append(slot->mbuf, padlen, sc->swsc->nullpad); /* Create mapping in DMA memory */ error = bus_dmamap_load_mbuf_sg(sc->swsc->mbuf_dtag, slot->dmamap, slot->mbuf, segs, &nsegs, BUS_DMA_NOWAIT); /* If the packet is too fragmented, try to simplify. */ if (error == EFBIG || (error == 0 && nsegs > sc->swsc->tx.avail_queue_len)) { bus_dmamap_unload(sc->swsc->mbuf_dtag, slot->dmamap); m0 = m_defrag(slot->mbuf, M_NOWAIT); if (m0 == NULL) { device_printf(sc->dev, "Can't defragment packet; dropping\n"); m_freem(slot->mbuf); } else { CPSW_DEBUGF(sc->swsc, ("Requeueing defragmented packet")); IF_PREPEND(&sc->ifp->if_snd, m0); } slot->mbuf = NULL; continue; } if (error != 0) { device_printf(sc->dev, "%s: Can't setup DMA (error=%d), dropping packet\n", __func__, error); bus_dmamap_unload(sc->swsc->mbuf_dtag, slot->dmamap); m_freem(slot->mbuf); slot->mbuf = NULL; break; } bus_dmamap_sync(sc->swsc->mbuf_dtag, slot->dmamap, BUS_DMASYNC_PREWRITE); CPSW_DEBUGF(sc->swsc, ("Queueing TX packet: %d segments + %d pad bytes", nsegs, padlen)); if (first_new_slot == NULL) first_new_slot = slot; /* Link from the previous descriptor. */ if (last != NULL) cpsw_cpdma_write_bd_next(sc->swsc, last, slot); slot->ifp = sc->ifp; /* If there is only one segment, the for() loop * gets skipped and the single buffer gets set up * as both SOP and EOP. */ if (nsegs > 1) { next = STAILQ_NEXT(slot, next); bd.next = cpsw_cpdma_bd_paddr(sc->swsc, next); } else bd.next = 0; /* Start by setting up the first buffer. */ bd.bufptr = segs[0].ds_addr; bd.bufoff = 0; bd.buflen = segs[0].ds_len; bd.pktlen = m_length(slot->mbuf, NULL); bd.flags = CPDMA_BD_SOP | CPDMA_BD_OWNER; if (sc->swsc->dualemac) { bd.flags |= CPDMA_BD_TO_PORT; bd.flags |= ((sc->unit + 1) & CPDMA_BD_PORT_MASK); } for (seg = 1; seg < nsegs; ++seg) { /* Save the previous buffer (which isn't EOP) */ cpsw_cpdma_write_bd(sc->swsc, slot, &bd); STAILQ_REMOVE_HEAD(&sc->swsc->tx.avail, next); STAILQ_INSERT_TAIL(&sc->swsc->tx.active, slot, next); slot = STAILQ_FIRST(&sc->swsc->tx.avail); /* Setup next buffer (which isn't SOP) */ if (nsegs > seg + 1) { next = STAILQ_NEXT(slot, next); bd.next = cpsw_cpdma_bd_paddr(sc->swsc, next); } else bd.next = 0; bd.bufptr = segs[seg].ds_addr; bd.bufoff = 0; bd.buflen = segs[seg].ds_len; bd.pktlen = 0; bd.flags = CPDMA_BD_OWNER; } /* Save the final buffer. */ bd.flags |= CPDMA_BD_EOP; cpsw_cpdma_write_bd(sc->swsc, slot, &bd); STAILQ_REMOVE_HEAD(&sc->swsc->tx.avail, next); STAILQ_INSERT_TAIL(&sc->swsc->tx.active, slot, next); last = slot; added += nsegs; if (nsegs > sc->swsc->tx.longest_chain) sc->swsc->tx.longest_chain = nsegs; BPF_MTAP(sc->ifp, m0); } if (first_new_slot == NULL) return; /* Attach the list of new buffers to the hardware TX queue. */ if (last_old_slot != NULL && (cpsw_cpdma_read_bd_flags(sc->swsc, last_old_slot) & CPDMA_BD_EOQ) == 0) { /* Add buffers to end of current queue. */ cpsw_cpdma_write_bd_next(sc->swsc, last_old_slot, first_new_slot); } else { /* Start a fresh queue. */ cpsw_write_hdp_slot(sc->swsc, &sc->swsc->tx, first_new_slot); } sc->swsc->tx.queue_adds += added; sc->swsc->tx.avail_queue_len -= added; sc->swsc->tx.active_queue_len += added; if (sc->swsc->tx.active_queue_len > sc->swsc->tx.max_active_queue_len) { sc->swsc->tx.max_active_queue_len = sc->swsc->tx.active_queue_len; } CPSW_DEBUGF(sc->swsc, ("Queued %d TX packet(s)", added)); } static int cpsw_tx_dequeue(struct cpsw_softc *sc) { struct cpsw_slot *slot, *last_removed_slot = NULL; struct cpsw_cpdma_bd bd; uint32_t flags, removed = 0; /* Pull completed buffers off the hardware TX queue. */ slot = STAILQ_FIRST(&sc->tx.active); while (slot != NULL) { flags = cpsw_cpdma_read_bd_flags(sc, slot); /* TearDown complete is only marked on the SOP for the packet. */ if ((flags & (CPDMA_BD_SOP | CPDMA_BD_TDOWNCMPLT)) == (CPDMA_BD_SOP | CPDMA_BD_TDOWNCMPLT)) { sc->tx.teardown = 1; } if ((flags & (CPDMA_BD_SOP | CPDMA_BD_OWNER)) == (CPDMA_BD_SOP | CPDMA_BD_OWNER) && sc->tx.teardown == 0) break; /* Hardware is still using this packet. */ bus_dmamap_sync(sc->mbuf_dtag, slot->dmamap, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->mbuf_dtag, slot->dmamap); m_freem(slot->mbuf); slot->mbuf = NULL; if (slot->ifp) { if (sc->tx.teardown == 0) if_inc_counter(slot->ifp, IFCOUNTER_OPACKETS, 1); else if_inc_counter(slot->ifp, IFCOUNTER_OQDROPS, 1); } /* Dequeue any additional buffers used by this packet. */ while (slot != NULL && slot->mbuf == NULL) { STAILQ_REMOVE_HEAD(&sc->tx.active, next); STAILQ_INSERT_TAIL(&sc->tx.avail, slot, next); ++removed; last_removed_slot = slot; slot = STAILQ_FIRST(&sc->tx.active); } cpsw_write_cp_slot(sc, &sc->tx, last_removed_slot); /* Restart the TX queue if necessary. */ cpsw_cpdma_read_bd(sc, last_removed_slot, &bd); if (slot != NULL && bd.next != 0 && (bd.flags & (CPDMA_BD_EOP | CPDMA_BD_OWNER | CPDMA_BD_EOQ)) == (CPDMA_BD_EOP | CPDMA_BD_EOQ)) { cpsw_write_hdp_slot(sc, &sc->tx, slot); sc->tx.queue_restart++; break; } } if (removed != 0) { sc->tx.queue_removes += removed; sc->tx.active_queue_len -= removed; sc->tx.avail_queue_len += removed; if (sc->tx.avail_queue_len > sc->tx.max_avail_queue_len) sc->tx.max_avail_queue_len = sc->tx.avail_queue_len; CPSW_DEBUGF(sc, ("TX removed %d completed packet(s)", removed)); } if (sc->tx.teardown && STAILQ_EMPTY(&sc->tx.active)) { CPSW_DEBUGF(sc, ("TX teardown is complete")); sc->tx.teardown = 0; sc->tx.running = 0; } return (removed); } /* * * Miscellaneous interrupts. * */ static void cpsw_intr_rx_thresh(void *arg) { struct cpsw_softc *sc; struct ifnet *ifp; struct mbuf *received, *next; sc = (struct cpsw_softc *)arg; CPSW_RX_LOCK(sc); received = cpsw_rx_dequeue(sc); cpsw_rx_enqueue(sc); cpsw_write_4(sc, CPSW_CPDMA_CPDMA_EOI_VECTOR, 0); CPSW_RX_UNLOCK(sc); while (received != NULL) { next = received->m_nextpkt; received->m_nextpkt = NULL; ifp = received->m_pkthdr.rcvif; (*ifp->if_input)(ifp, received); if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1); received = next; } } static void cpsw_intr_misc_host_error(struct cpsw_softc *sc) { uint32_t intstat; uint32_t dmastat; int txerr, rxerr, txchan, rxchan; printf("\n\n"); device_printf(sc->dev, "HOST ERROR: PROGRAMMING ERROR DETECTED BY HARDWARE\n"); printf("\n\n"); intstat = cpsw_read_4(sc, CPSW_CPDMA_DMA_INTSTAT_MASKED); device_printf(sc->dev, "CPSW_CPDMA_DMA_INTSTAT_MASKED=0x%x\n", intstat); dmastat = cpsw_read_4(sc, CPSW_CPDMA_DMASTATUS); device_printf(sc->dev, "CPSW_CPDMA_DMASTATUS=0x%x\n", dmastat); txerr = (dmastat >> 20) & 15; txchan = (dmastat >> 16) & 7; rxerr = (dmastat >> 12) & 15; rxchan = (dmastat >> 8) & 7; switch (txerr) { case 0: break; case 1: printf("SOP error on TX channel %d\n", txchan); break; case 2: printf("Ownership bit not set on SOP buffer on TX channel %d\n", txchan); break; case 3: printf("Zero Next Buffer but not EOP on TX channel %d\n", txchan); break; case 4: printf("Zero Buffer Pointer on TX channel %d\n", txchan); break; case 5: printf("Zero Buffer Length on TX channel %d\n", txchan); break; case 6: printf("Packet length error on TX channel %d\n", txchan); break; default: printf("Unknown error on TX channel %d\n", txchan); break; } if (txerr != 0) { printf("CPSW_CPDMA_TX%d_HDP=0x%x\n", txchan, cpsw_read_4(sc, CPSW_CPDMA_TX_HDP(txchan))); printf("CPSW_CPDMA_TX%d_CP=0x%x\n", txchan, cpsw_read_4(sc, CPSW_CPDMA_TX_CP(txchan))); cpsw_dump_queue(sc, &sc->tx.active); } switch (rxerr) { case 0: break; case 2: printf("Ownership bit not set on RX channel %d\n", rxchan); break; case 4: printf("Zero Buffer Pointer on RX channel %d\n", rxchan); break; case 5: printf("Zero Buffer Length on RX channel %d\n", rxchan); break; case 6: printf("Buffer offset too big on RX channel %d\n", rxchan); break; default: printf("Unknown RX error on RX channel %d\n", rxchan); break; } if (rxerr != 0) { printf("CPSW_CPDMA_RX%d_HDP=0x%x\n", rxchan, cpsw_read_4(sc,CPSW_CPDMA_RX_HDP(rxchan))); printf("CPSW_CPDMA_RX%d_CP=0x%x\n", rxchan, cpsw_read_4(sc, CPSW_CPDMA_RX_CP(rxchan))); cpsw_dump_queue(sc, &sc->rx.active); } printf("\nALE Table\n"); cpsw_ale_dump_table(sc); // XXX do something useful here?? panic("CPSW HOST ERROR INTERRUPT"); // Suppress this interrupt in the future. cpsw_write_4(sc, CPSW_CPDMA_DMA_INTMASK_CLEAR, intstat); printf("XXX HOST ERROR INTERRUPT SUPPRESSED\n"); // The watchdog will probably reset the controller // in a little while. It will probably fail again. } static void cpsw_intr_misc(void *arg) { struct cpsw_softc *sc = arg; uint32_t stat = cpsw_read_4(sc, CPSW_WR_C_MISC_STAT(0)); if (stat & CPSW_WR_C_MISC_EVNT_PEND) CPSW_DEBUGF(sc, ("Time sync event interrupt unimplemented")); if (stat & CPSW_WR_C_MISC_STAT_PEND) cpsw_stats_collect(sc); if (stat & CPSW_WR_C_MISC_HOST_PEND) cpsw_intr_misc_host_error(sc); if (stat & CPSW_WR_C_MISC_MDIOLINK) { cpsw_write_4(sc, MDIOLINKINTMASKED, cpsw_read_4(sc, MDIOLINKINTMASKED)); } if (stat & CPSW_WR_C_MISC_MDIOUSER) { CPSW_DEBUGF(sc, ("MDIO operation completed interrupt unimplemented")); } cpsw_write_4(sc, CPSW_CPDMA_CPDMA_EOI_VECTOR, 3); } /* * * Periodic Checks and Watchdog. * */ static void cpswp_tick(void *msc) { struct cpswp_softc *sc = msc; /* Check for media type change */ mii_tick(sc->mii); if (sc->media_status != sc->mii->mii_media.ifm_media) { printf("%s: media type changed (ifm_media=%x)\n", __func__, sc->mii->mii_media.ifm_media); cpswp_ifmedia_upd(sc->ifp); } /* Schedule another timeout one second from now */ callout_reset(&sc->mii_callout, hz, cpswp_tick, sc); } static void cpswp_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr) { struct cpswp_softc *sc; struct mii_data *mii; sc = ifp->if_softc; CPSW_DEBUGF(sc->swsc, ("")); CPSW_PORT_LOCK(sc); mii = sc->mii; mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; CPSW_PORT_UNLOCK(sc); } static int cpswp_ifmedia_upd(struct ifnet *ifp) { struct cpswp_softc *sc; sc = ifp->if_softc; CPSW_DEBUGF(sc->swsc, ("")); CPSW_PORT_LOCK(sc); mii_mediachg(sc->mii); sc->media_status = sc->mii->mii_media.ifm_media; CPSW_PORT_UNLOCK(sc); return (0); } static void cpsw_tx_watchdog_full_reset(struct cpsw_softc *sc) { struct cpswp_softc *psc; int i; cpsw_debugf_head("CPSW watchdog"); device_printf(sc->dev, "watchdog timeout\n"); printf("CPSW_CPDMA_TX%d_HDP=0x%x\n", 0, cpsw_read_4(sc, CPSW_CPDMA_TX_HDP(0))); printf("CPSW_CPDMA_TX%d_CP=0x%x\n", 0, cpsw_read_4(sc, CPSW_CPDMA_TX_CP(0))); cpsw_dump_queue(sc, &sc->tx.active); for (i = 0; i < CPSW_PORTS; i++) { if (!sc->dualemac && i != sc->active_slave) continue; psc = device_get_softc(sc->port[i].dev); CPSW_PORT_LOCK(psc); cpswp_stop_locked(psc); CPSW_PORT_UNLOCK(psc); } } static void cpsw_tx_watchdog(void *msc) { struct cpsw_softc *sc; sc = msc; CPSW_TX_LOCK(sc); if (sc->tx.active_queue_len == 0 || !sc->tx.running) { sc->watchdog.timer = 0; /* Nothing to do. */ } else if (sc->tx.queue_removes > sc->tx.queue_removes_at_last_tick) { sc->watchdog.timer = 0; /* Stuff done while we weren't looking. */ } else if (cpsw_tx_dequeue(sc) > 0) { sc->watchdog.timer = 0; /* We just did something. */ } else { /* There was something to do but it didn't get done. */ ++sc->watchdog.timer; if (sc->watchdog.timer > 5) { sc->watchdog.timer = 0; ++sc->watchdog.resets; cpsw_tx_watchdog_full_reset(sc); } } sc->tx.queue_removes_at_last_tick = sc->tx.queue_removes; CPSW_TX_UNLOCK(sc); /* Schedule another timeout one second from now */ callout_reset(&sc->watchdog.callout, hz, cpsw_tx_watchdog, sc); } /* * * ALE support routines. * */ static void cpsw_ale_read_entry(struct cpsw_softc *sc, uint16_t idx, uint32_t *ale_entry) { cpsw_write_4(sc, CPSW_ALE_TBLCTL, idx & 1023); ale_entry[0] = cpsw_read_4(sc, CPSW_ALE_TBLW0); ale_entry[1] = cpsw_read_4(sc, CPSW_ALE_TBLW1); ale_entry[2] = cpsw_read_4(sc, CPSW_ALE_TBLW2); } static void cpsw_ale_write_entry(struct cpsw_softc *sc, uint16_t idx, uint32_t *ale_entry) { cpsw_write_4(sc, CPSW_ALE_TBLW0, ale_entry[0]); cpsw_write_4(sc, CPSW_ALE_TBLW1, ale_entry[1]); cpsw_write_4(sc, CPSW_ALE_TBLW2, ale_entry[2]); cpsw_write_4(sc, CPSW_ALE_TBLCTL, 1 << 31 | (idx & 1023)); } static void cpsw_ale_remove_all_mc_entries(struct cpsw_softc *sc) { int i; uint32_t ale_entry[3]; /* First four entries are link address and broadcast. */ for (i = 10; i < CPSW_MAX_ALE_ENTRIES; i++) { cpsw_ale_read_entry(sc, i, ale_entry); if ((ALE_TYPE(ale_entry) == ALE_TYPE_ADDR || ALE_TYPE(ale_entry) == ALE_TYPE_VLAN_ADDR) && ALE_MCAST(ale_entry) == 1) { /* MCast link addr */ ale_entry[0] = ale_entry[1] = ale_entry[2] = 0; cpsw_ale_write_entry(sc, i, ale_entry); } } } static int cpsw_ale_mc_entry_set(struct cpsw_softc *sc, uint8_t portmap, int vlan, uint8_t *mac) { int free_index = -1, matching_index = -1, i; uint32_t ale_entry[3], ale_type; /* Find a matching entry or a free entry. */ for (i = 10; i < CPSW_MAX_ALE_ENTRIES; i++) { cpsw_ale_read_entry(sc, i, ale_entry); /* Entry Type[61:60] is 0 for free entry */ if (free_index < 0 && ALE_TYPE(ale_entry) == 0) free_index = i; if ((((ale_entry[1] >> 8) & 0xFF) == mac[0]) && (((ale_entry[1] >> 0) & 0xFF) == mac[1]) && (((ale_entry[0] >>24) & 0xFF) == mac[2]) && (((ale_entry[0] >>16) & 0xFF) == mac[3]) && (((ale_entry[0] >> 8) & 0xFF) == mac[4]) && (((ale_entry[0] >> 0) & 0xFF) == mac[5])) { matching_index = i; break; } } if (matching_index < 0) { if (free_index < 0) return (ENOMEM); i = free_index; } if (vlan != -1) ale_type = ALE_TYPE_VLAN_ADDR << 28 | vlan << 16; else ale_type = ALE_TYPE_ADDR << 28; /* Set MAC address */ ale_entry[0] = mac[2] << 24 | mac[3] << 16 | mac[4] << 8 | mac[5]; ale_entry[1] = mac[0] << 8 | mac[1]; /* Entry type[61:60] and Mcast fwd state[63:62] is fw(3). */ ale_entry[1] |= ALE_MCAST_FWD | ale_type; /* Set portmask [68:66] */ ale_entry[2] = (portmap & 7) << 2; cpsw_ale_write_entry(sc, i, ale_entry); return 0; } static void cpsw_ale_dump_table(struct cpsw_softc *sc) { int i; uint32_t ale_entry[3]; for (i = 0; i < CPSW_MAX_ALE_ENTRIES; i++) { cpsw_ale_read_entry(sc, i, ale_entry); switch (ALE_TYPE(ale_entry)) { case ALE_TYPE_VLAN: printf("ALE[%4u] %08x %08x %08x ", i, ale_entry[2], ale_entry[1], ale_entry[0]); printf("type: %u ", ALE_TYPE(ale_entry)); printf("vlan: %u ", ALE_VLAN(ale_entry)); printf("untag: %u ", ALE_VLAN_UNTAG(ale_entry)); printf("reg flood: %u ", ALE_VLAN_REGFLOOD(ale_entry)); printf("unreg flood: %u ", ALE_VLAN_UNREGFLOOD(ale_entry)); printf("members: %u ", ALE_VLAN_MEMBERS(ale_entry)); printf("\n"); break; case ALE_TYPE_ADDR: case ALE_TYPE_VLAN_ADDR: printf("ALE[%4u] %08x %08x %08x ", i, ale_entry[2], ale_entry[1], ale_entry[0]); printf("type: %u ", ALE_TYPE(ale_entry)); printf("mac: %02x:%02x:%02x:%02x:%02x:%02x ", (ale_entry[1] >> 8) & 0xFF, (ale_entry[1] >> 0) & 0xFF, (ale_entry[0] >>24) & 0xFF, (ale_entry[0] >>16) & 0xFF, (ale_entry[0] >> 8) & 0xFF, (ale_entry[0] >> 0) & 0xFF); printf(ALE_MCAST(ale_entry) ? "mcast " : "ucast "); if (ALE_TYPE(ale_entry) == ALE_TYPE_VLAN_ADDR) printf("vlan: %u ", ALE_VLAN(ale_entry)); printf("port: %u ", ALE_PORTS(ale_entry)); printf("\n"); break; } } printf("\n"); } static int cpswp_ale_update_addresses(struct cpswp_softc *sc, int purge) { uint8_t *mac; uint32_t ale_entry[3], ale_type, portmask; struct ifmultiaddr *ifma; if (sc->swsc->dualemac) { ale_type = ALE_TYPE_VLAN_ADDR << 28 | sc->vlan << 16; portmask = 1 << (sc->unit + 1) | 1 << 0; } else { ale_type = ALE_TYPE_ADDR << 28; portmask = 7; } /* * Route incoming packets for our MAC address to Port 0 (host). * For simplicity, keep this entry at table index 0 for port 1 and * at index 2 for port 2 in the ALE. */ if_addr_rlock(sc->ifp); mac = LLADDR((struct sockaddr_dl *)sc->ifp->if_addr->ifa_addr); ale_entry[0] = mac[2] << 24 | mac[3] << 16 | mac[4] << 8 | mac[5]; ale_entry[1] = ale_type | mac[0] << 8 | mac[1]; /* addr entry + mac */ ale_entry[2] = 0; /* port = 0 */ cpsw_ale_write_entry(sc->swsc, 0 + 2 * sc->unit, ale_entry); /* Set outgoing MAC Address for slave port. */ cpsw_write_4(sc->swsc, CPSW_PORT_P_SA_HI(sc->unit + 1), mac[3] << 24 | mac[2] << 16 | mac[1] << 8 | mac[0]); cpsw_write_4(sc->swsc, CPSW_PORT_P_SA_LO(sc->unit + 1), mac[5] << 8 | mac[4]); if_addr_runlock(sc->ifp); /* Keep the broadcast address at table entry 1 (or 3). */ ale_entry[0] = 0xffffffff; /* Lower 32 bits of MAC */ /* ALE_MCAST_FWD, Addr type, upper 16 bits of Mac */ ale_entry[1] = ALE_MCAST_FWD | ale_type | 0xffff; ale_entry[2] = portmask << 2; cpsw_ale_write_entry(sc->swsc, 1 + 2 * sc->unit, ale_entry); /* SIOCDELMULTI doesn't specify the particular address being removed, so we have to remove all and rebuild. */ if (purge) cpsw_ale_remove_all_mc_entries(sc->swsc); /* Set other multicast addrs desired. */ if_maddr_rlock(sc->ifp); TAILQ_FOREACH(ifma, &sc->ifp->if_multiaddrs, ifma_link) { if (ifma->ifma_addr->sa_family != AF_LINK) continue; cpsw_ale_mc_entry_set(sc->swsc, portmask, sc->vlan, LLADDR((struct sockaddr_dl *)ifma->ifma_addr)); } if_maddr_runlock(sc->ifp); return (0); } static int cpsw_ale_update_vlan_table(struct cpsw_softc *sc, int vlan, int ports, int untag, int mcregflood, int mcunregflood) { int free_index, i, matching_index; uint32_t ale_entry[3]; free_index = matching_index = -1; /* Find a matching entry or a free entry. */ for (i = 5; i < CPSW_MAX_ALE_ENTRIES; i++) { cpsw_ale_read_entry(sc, i, ale_entry); /* Entry Type[61:60] is 0 for free entry */ if (free_index < 0 && ALE_TYPE(ale_entry) == 0) free_index = i; if (ALE_VLAN(ale_entry) == vlan) { matching_index = i; break; } } if (matching_index < 0) { if (free_index < 0) return (-1); i = free_index; } ale_entry[0] = (untag & 7) << 24 | (mcregflood & 7) << 16 | (mcunregflood & 7) << 8 | (ports & 7); ale_entry[1] = ALE_TYPE_VLAN << 28 | vlan << 16; ale_entry[2] = 0; cpsw_ale_write_entry(sc, i, ale_entry); return (0); } /* * * Statistics and Sysctls. * */ #if 0 static void cpsw_stats_dump(struct cpsw_softc *sc) { int i; uint32_t r; for (i = 0; i < CPSW_SYSCTL_COUNT; ++i) { r = cpsw_read_4(sc, CPSW_STATS_OFFSET + cpsw_stat_sysctls[i].reg); CPSW_DEBUGF(sc, ("%s: %ju + %u = %ju", cpsw_stat_sysctls[i].oid, (intmax_t)sc->shadow_stats[i], r, (intmax_t)sc->shadow_stats[i] + r)); } } #endif static void cpsw_stats_collect(struct cpsw_softc *sc) { int i; uint32_t r; CPSW_DEBUGF(sc, ("Controller shadow statistics updated.")); for (i = 0; i < CPSW_SYSCTL_COUNT; ++i) { r = cpsw_read_4(sc, CPSW_STATS_OFFSET + cpsw_stat_sysctls[i].reg); sc->shadow_stats[i] += r; cpsw_write_4(sc, CPSW_STATS_OFFSET + cpsw_stat_sysctls[i].reg, r); } } static int cpsw_stats_sysctl(SYSCTL_HANDLER_ARGS) { struct cpsw_softc *sc; struct cpsw_stat *stat; uint64_t result; sc = (struct cpsw_softc *)arg1; stat = &cpsw_stat_sysctls[oidp->oid_number]; result = sc->shadow_stats[oidp->oid_number]; result += cpsw_read_4(sc, CPSW_STATS_OFFSET + stat->reg); return (sysctl_handle_64(oidp, &result, 0, req)); } static int cpsw_stat_attached(SYSCTL_HANDLER_ARGS) { struct cpsw_softc *sc; struct bintime t; unsigned result; sc = (struct cpsw_softc *)arg1; getbinuptime(&t); bintime_sub(&t, &sc->attach_uptime); result = t.sec; return (sysctl_handle_int(oidp, &result, 0, req)); } static int cpsw_intr_coalesce(SYSCTL_HANDLER_ARGS) { int error; struct cpsw_softc *sc; uint32_t ctrl, intr_per_ms; sc = (struct cpsw_softc *)arg1; error = sysctl_handle_int(oidp, &sc->coal_us, 0, req); if (error != 0 || req->newptr == NULL) return (error); ctrl = cpsw_read_4(sc, CPSW_WR_INT_CONTROL); ctrl &= ~(CPSW_WR_INT_PACE_EN | CPSW_WR_INT_PRESCALE_MASK); if (sc->coal_us == 0) { /* Disable the interrupt pace hardware. */ cpsw_write_4(sc, CPSW_WR_INT_CONTROL, ctrl); cpsw_write_4(sc, CPSW_WR_C_RX_IMAX(0), 0); cpsw_write_4(sc, CPSW_WR_C_TX_IMAX(0), 0); return (0); } if (sc->coal_us > CPSW_WR_C_IMAX_US_MAX) sc->coal_us = CPSW_WR_C_IMAX_US_MAX; if (sc->coal_us < CPSW_WR_C_IMAX_US_MIN) sc->coal_us = CPSW_WR_C_IMAX_US_MIN; intr_per_ms = 1000 / sc->coal_us; /* Just to make sure... */ if (intr_per_ms > CPSW_WR_C_IMAX_MAX) intr_per_ms = CPSW_WR_C_IMAX_MAX; if (intr_per_ms < CPSW_WR_C_IMAX_MIN) intr_per_ms = CPSW_WR_C_IMAX_MIN; /* Set the prescale to produce 4us pulses from the 125 Mhz clock. */ ctrl |= (125 * 4) & CPSW_WR_INT_PRESCALE_MASK; /* Enable the interrupt pace hardware. */ cpsw_write_4(sc, CPSW_WR_C_RX_IMAX(0), intr_per_ms); cpsw_write_4(sc, CPSW_WR_C_TX_IMAX(0), intr_per_ms); ctrl |= CPSW_WR_INT_C0_RX_PULSE | CPSW_WR_INT_C0_TX_PULSE; cpsw_write_4(sc, CPSW_WR_INT_CONTROL, ctrl); return (0); } static int cpsw_stat_uptime(SYSCTL_HANDLER_ARGS) { struct cpsw_softc *swsc; struct cpswp_softc *sc; struct bintime t; unsigned result; swsc = arg1; sc = device_get_softc(swsc->port[arg2].dev); if (sc->ifp->if_drv_flags & IFF_DRV_RUNNING) { getbinuptime(&t); bintime_sub(&t, &sc->init_uptime); result = t.sec; } else result = 0; return (sysctl_handle_int(oidp, &result, 0, req)); } static void cpsw_add_queue_sysctls(struct sysctl_ctx_list *ctx, struct sysctl_oid *node, struct cpsw_queue *queue) { struct sysctl_oid_list *parent; parent = SYSCTL_CHILDREN(node); SYSCTL_ADD_INT(ctx, parent, OID_AUTO, "totalBuffers", CTLFLAG_RD, &queue->queue_slots, 0, "Total buffers currently assigned to this queue"); SYSCTL_ADD_INT(ctx, parent, OID_AUTO, "activeBuffers", CTLFLAG_RD, &queue->active_queue_len, 0, "Buffers currently registered with hardware controller"); SYSCTL_ADD_INT(ctx, parent, OID_AUTO, "maxActiveBuffers", CTLFLAG_RD, &queue->max_active_queue_len, 0, "Max value of activeBuffers since last driver reset"); SYSCTL_ADD_INT(ctx, parent, OID_AUTO, "availBuffers", CTLFLAG_RD, &queue->avail_queue_len, 0, "Buffers allocated to this queue but not currently " "registered with hardware controller"); SYSCTL_ADD_INT(ctx, parent, OID_AUTO, "maxAvailBuffers", CTLFLAG_RD, &queue->max_avail_queue_len, 0, "Max value of availBuffers since last driver reset"); SYSCTL_ADD_UINT(ctx, parent, OID_AUTO, "totalEnqueued", CTLFLAG_RD, &queue->queue_adds, 0, "Total buffers added to queue"); SYSCTL_ADD_UINT(ctx, parent, OID_AUTO, "totalDequeued", CTLFLAG_RD, &queue->queue_removes, 0, "Total buffers removed from queue"); SYSCTL_ADD_UINT(ctx, parent, OID_AUTO, "queueRestart", CTLFLAG_RD, &queue->queue_restart, 0, "Total times the queue has been restarted"); SYSCTL_ADD_UINT(ctx, parent, OID_AUTO, "longestChain", CTLFLAG_RD, &queue->longest_chain, 0, "Max buffers used for a single packet"); } static void cpsw_add_watchdog_sysctls(struct sysctl_ctx_list *ctx, struct sysctl_oid *node, struct cpsw_softc *sc) { struct sysctl_oid_list *parent; parent = SYSCTL_CHILDREN(node); SYSCTL_ADD_INT(ctx, parent, OID_AUTO, "resets", CTLFLAG_RD, &sc->watchdog.resets, 0, "Total number of watchdog resets"); } static void cpsw_add_sysctls(struct cpsw_softc *sc) { struct sysctl_ctx_list *ctx; struct sysctl_oid *stats_node, *queue_node, *node; struct sysctl_oid_list *parent, *stats_parent, *queue_parent; struct sysctl_oid_list *ports_parent, *port_parent; char port[16]; int i; ctx = device_get_sysctl_ctx(sc->dev); parent = SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)); SYSCTL_ADD_INT(ctx, parent, OID_AUTO, "debug", CTLFLAG_RW, &sc->debug, 0, "Enable switch debug messages"); SYSCTL_ADD_PROC(ctx, parent, OID_AUTO, "attachedSecs", CTLTYPE_UINT | CTLFLAG_RD, sc, 0, cpsw_stat_attached, "IU", "Time since driver attach"); SYSCTL_ADD_PROC(ctx, parent, OID_AUTO, "intr_coalesce_us", CTLTYPE_UINT | CTLFLAG_RW, sc, 0, cpsw_intr_coalesce, "IU", "minimum time between interrupts"); node = SYSCTL_ADD_NODE(ctx, parent, OID_AUTO, "ports", CTLFLAG_RD, NULL, "CPSW Ports Statistics"); ports_parent = SYSCTL_CHILDREN(node); for (i = 0; i < CPSW_PORTS; i++) { if (!sc->dualemac && i != sc->active_slave) continue; port[0] = '0' + i; port[1] = '\0'; node = SYSCTL_ADD_NODE(ctx, ports_parent, OID_AUTO, port, CTLFLAG_RD, NULL, "CPSW Port Statistics"); port_parent = SYSCTL_CHILDREN(node); SYSCTL_ADD_PROC(ctx, port_parent, OID_AUTO, "uptime", CTLTYPE_UINT | CTLFLAG_RD, sc, i, cpsw_stat_uptime, "IU", "Seconds since driver init"); } stats_node = SYSCTL_ADD_NODE(ctx, parent, OID_AUTO, "stats", CTLFLAG_RD, NULL, "CPSW Statistics"); stats_parent = SYSCTL_CHILDREN(stats_node); for (i = 0; i < CPSW_SYSCTL_COUNT; ++i) { SYSCTL_ADD_PROC(ctx, stats_parent, i, cpsw_stat_sysctls[i].oid, CTLTYPE_U64 | CTLFLAG_RD, sc, 0, cpsw_stats_sysctl, "IU", cpsw_stat_sysctls[i].oid); } queue_node = SYSCTL_ADD_NODE(ctx, parent, OID_AUTO, "queue", CTLFLAG_RD, NULL, "CPSW Queue Statistics"); queue_parent = SYSCTL_CHILDREN(queue_node); node = SYSCTL_ADD_NODE(ctx, queue_parent, OID_AUTO, "tx", CTLFLAG_RD, NULL, "TX Queue Statistics"); cpsw_add_queue_sysctls(ctx, node, &sc->tx); node = SYSCTL_ADD_NODE(ctx, queue_parent, OID_AUTO, "rx", CTLFLAG_RD, NULL, "RX Queue Statistics"); cpsw_add_queue_sysctls(ctx, node, &sc->rx); node = SYSCTL_ADD_NODE(ctx, parent, OID_AUTO, "watchdog", CTLFLAG_RD, NULL, "Watchdog Statistics"); cpsw_add_watchdog_sysctls(ctx, node, sc); } #ifdef CPSW_ETHERSWITCH static etherswitch_info_t etherswitch_info = { .es_nports = CPSW_PORTS + 1, .es_nvlangroups = CPSW_VLANS, .es_name = "TI Common Platform Ethernet Switch (CPSW)", .es_vlan_caps = ETHERSWITCH_VLAN_DOT1Q, }; static etherswitch_info_t * cpsw_getinfo(device_t dev) { return (ðerswitch_info); } static int cpsw_getport(device_t dev, etherswitch_port_t *p) { int err; struct cpsw_softc *sc; struct cpswp_softc *psc; struct ifmediareq *ifmr; uint32_t reg; if (p->es_port < 0 || p->es_port > CPSW_PORTS) return (ENXIO); err = 0; sc = device_get_softc(dev); if (p->es_port == CPSW_CPU_PORT) { p->es_flags |= ETHERSWITCH_PORT_CPU; ifmr = &p->es_ifmr; ifmr->ifm_current = ifmr->ifm_active = IFM_ETHER | IFM_1000_T | IFM_FDX; ifmr->ifm_mask = 0; ifmr->ifm_status = IFM_ACTIVE | IFM_AVALID; ifmr->ifm_count = 0; } else { psc = device_get_softc(sc->port[p->es_port - 1].dev); err = ifmedia_ioctl(psc->ifp, &p->es_ifr, &psc->mii->mii_media, SIOCGIFMEDIA); } reg = cpsw_read_4(sc, CPSW_PORT_P_VLAN(p->es_port)); p->es_pvid = reg & ETHERSWITCH_VID_MASK; reg = cpsw_read_4(sc, CPSW_ALE_PORTCTL(p->es_port)); if (reg & ALE_PORTCTL_DROP_UNTAGGED) p->es_flags |= ETHERSWITCH_PORT_DROPUNTAGGED; if (reg & ALE_PORTCTL_INGRESS) p->es_flags |= ETHERSWITCH_PORT_INGRESS; return (err); } static int cpsw_setport(device_t dev, etherswitch_port_t *p) { struct cpsw_softc *sc; struct cpswp_softc *psc; struct ifmedia *ifm; uint32_t reg; if (p->es_port < 0 || p->es_port > CPSW_PORTS) return (ENXIO); sc = device_get_softc(dev); if (p->es_pvid != 0) { cpsw_write_4(sc, CPSW_PORT_P_VLAN(p->es_port), p->es_pvid & ETHERSWITCH_VID_MASK); } reg = cpsw_read_4(sc, CPSW_ALE_PORTCTL(p->es_port)); if (p->es_flags & ETHERSWITCH_PORT_DROPUNTAGGED) reg |= ALE_PORTCTL_DROP_UNTAGGED; else reg &= ~ALE_PORTCTL_DROP_UNTAGGED; if (p->es_flags & ETHERSWITCH_PORT_INGRESS) reg |= ALE_PORTCTL_INGRESS; else reg &= ~ALE_PORTCTL_INGRESS; cpsw_write_4(sc, CPSW_ALE_PORTCTL(p->es_port), reg); /* CPU port does not allow media settings. */ if (p->es_port == CPSW_CPU_PORT) return (0); psc = device_get_softc(sc->port[p->es_port - 1].dev); ifm = &psc->mii->mii_media; return (ifmedia_ioctl(psc->ifp, &p->es_ifr, ifm, SIOCSIFMEDIA)); } static int cpsw_getconf(device_t dev, etherswitch_conf_t *conf) { /* Return the VLAN mode. */ conf->cmd = ETHERSWITCH_CONF_VLAN_MODE; conf->vlan_mode = ETHERSWITCH_VLAN_DOT1Q; return (0); } static int cpsw_getvgroup(device_t dev, etherswitch_vlangroup_t *vg) { int i, vid; uint32_t ale_entry[3]; struct cpsw_softc *sc; sc = device_get_softc(dev); if (vg->es_vlangroup >= CPSW_VLANS) return (EINVAL); vg->es_vid = 0; vid = cpsw_vgroups[vg->es_vlangroup].vid; if (vid == -1) return (0); for (i = 0; i < CPSW_MAX_ALE_ENTRIES; i++) { cpsw_ale_read_entry(sc, i, ale_entry); if (ALE_TYPE(ale_entry) != ALE_TYPE_VLAN) continue; if (vid != ALE_VLAN(ale_entry)) continue; vg->es_fid = 0; vg->es_vid = ALE_VLAN(ale_entry) | ETHERSWITCH_VID_VALID; vg->es_member_ports = ALE_VLAN_MEMBERS(ale_entry); vg->es_untagged_ports = ALE_VLAN_UNTAG(ale_entry); } return (0); } static void cpsw_remove_vlan(struct cpsw_softc *sc, int vlan) { int i; uint32_t ale_entry[3]; for (i = 0; i < CPSW_MAX_ALE_ENTRIES; i++) { cpsw_ale_read_entry(sc, i, ale_entry); if (ALE_TYPE(ale_entry) != ALE_TYPE_VLAN) continue; if (vlan != ALE_VLAN(ale_entry)) continue; ale_entry[0] = ale_entry[1] = ale_entry[2] = 0; cpsw_ale_write_entry(sc, i, ale_entry); break; } } static int cpsw_setvgroup(device_t dev, etherswitch_vlangroup_t *vg) { int i; struct cpsw_softc *sc; sc = device_get_softc(dev); for (i = 0; i < CPSW_VLANS; i++) { /* Is this Vlan ID in use by another vlangroup ? */ if (vg->es_vlangroup != i && cpsw_vgroups[i].vid == vg->es_vid) return (EINVAL); } if (vg->es_vid == 0) { if (cpsw_vgroups[vg->es_vlangroup].vid == -1) return (0); cpsw_remove_vlan(sc, cpsw_vgroups[vg->es_vlangroup].vid); cpsw_vgroups[vg->es_vlangroup].vid = -1; vg->es_untagged_ports = 0; vg->es_member_ports = 0; vg->es_vid = 0; return (0); } vg->es_vid &= ETHERSWITCH_VID_MASK; vg->es_member_ports &= CPSW_PORTS_MASK; vg->es_untagged_ports &= CPSW_PORTS_MASK; if (cpsw_vgroups[vg->es_vlangroup].vid != -1 && cpsw_vgroups[vg->es_vlangroup].vid != vg->es_vid) return (EINVAL); cpsw_vgroups[vg->es_vlangroup].vid = vg->es_vid; cpsw_ale_update_vlan_table(sc, vg->es_vid, vg->es_member_ports, vg->es_untagged_ports, vg->es_member_ports, 0); return (0); } static int cpsw_readreg(device_t dev, int addr) { /* Not supported. */ return (0); } static int cpsw_writereg(device_t dev, int addr, int value) { /* Not supported. */ return (0); } static int cpsw_readphy(device_t dev, int phy, int reg) { /* Not supported. */ return (0); } static int cpsw_writephy(device_t dev, int phy, int reg, int data) { /* Not supported. */ return (0); } #endif Index: head/sys/arm/ti/ti_hwmods.c =================================================================== --- head/sys/arm/ti/ti_hwmods.c (revision 332309) +++ head/sys/arm/ti/ti_hwmods.c (revision 332310) @@ -1,204 +1,204 @@ /*- * Copyright (c) 2015 Oleksandr Tymoshenko * 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 ``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 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. * * $FreeBSD$ */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include struct hwmod { const char *name; int clock_id; }; struct hwmod ti_hwmods[] = { {"i2c1", I2C1_CLK}, {"i2c2", I2C2_CLK}, {"i2c3", I2C3_CLK}, {"i2c4", I2C4_CLK}, {"i2c5", I2C5_CLK}, {"gpio1", GPIO1_CLK}, {"gpio2", GPIO2_CLK}, {"gpio3", GPIO3_CLK}, {"gpio4", GPIO4_CLK}, {"gpio5", GPIO5_CLK}, {"gpio6", GPIO6_CLK}, {"gpio7", GPIO7_CLK}, {"mmc1", MMC1_CLK}, {"mmc2", MMC2_CLK}, {"mmc3", MMC3_CLK}, {"mmc4", MMC4_CLK}, {"mmc5", MMC5_CLK}, {"mmc6", MMC6_CLK}, {"epwmss0", PWMSS0_CLK}, {"epwmss1", PWMSS1_CLK}, {"epwmss2", PWMSS2_CLK}, {"spi0", SPI0_CLK}, {"spi1", SPI1_CLK}, {"timer1", TIMER1_CLK}, {"timer2", TIMER2_CLK}, {"timer3", TIMER3_CLK}, {"timer4", TIMER4_CLK}, {"timer5", TIMER5_CLK}, {"timer6", TIMER6_CLK}, {"timer7", TIMER7_CLK}, {"uart1", UART1_CLK}, {"uart2", UART2_CLK}, {"uart3", UART3_CLK}, {"uart4", UART4_CLK}, {"uart5", UART5_CLK}, {"uart6", UART6_CLK}, {"uart7", UART7_CLK}, {NULL, 0} }; clk_ident_t ti_hwmods_get_clock(device_t dev) { phandle_t node; int len, l; char *name; char *buf; int clk; struct hwmod *hw; if ((node = ofw_bus_get_node(dev)) == 0) return (INVALID_CLK_IDENT); - if ((len = OF_getprop_alloc(node, "ti,hwmods", 1, (void**)&name)) <= 0) + if ((len = OF_getprop_alloc(node, "ti,hwmods", (void**)&name)) <= 0) return (INVALID_CLK_IDENT); buf = name; clk = INVALID_CLK_IDENT; while ((len > 0) && (clk == INVALID_CLK_IDENT)) { for (hw = ti_hwmods; hw->name != NULL; ++hw) { if (strcmp(hw->name, name) == 0) { clk = hw->clock_id; break; } } /* Slide to the next sub-string. */ l = strlen(name) + 1; name += l; len -= l; } if (len > 0) device_printf(dev, "WARNING: more than one ti,hwmod \n"); OF_prop_free(buf); return (clk); } int ti_hwmods_contains(device_t dev, const char *hwmod) { phandle_t node; int len, l; char *name; char *buf; int result; if ((node = ofw_bus_get_node(dev)) == 0) return (0); - if ((len = OF_getprop_alloc(node, "ti,hwmods", 1, (void**)&name)) <= 0) + if ((len = OF_getprop_alloc(node, "ti,hwmods", (void**)&name)) <= 0) return (0); buf = name; result = 0; while (len > 0) { if (strcmp(name, hwmod) == 0) { result = 1; break; } /* Slide to the next sub-string. */ l = strlen(name) + 1; name += l; len -= l; } OF_prop_free(buf); return (result); } int ti_hwmods_get_unit(device_t dev, const char *hwmod) { phandle_t node; int l, len, hwmodlen, result; char *name; char *buf; if ((node = ofw_bus_get_node(dev)) == 0) return (0); - if ((len = OF_getprop_alloc(node, "ti,hwmods", 1, (void**)&name)) <= 0) + if ((len = OF_getprop_alloc(node, "ti,hwmods", (void**)&name)) <= 0) return (0); buf = name; hwmodlen = strlen(hwmod); result = 0; while (len > 0) { if (strncmp(name, hwmod, hwmodlen) == 0) { result = (int)strtoul(name + hwmodlen, NULL, 10); break; } /* Slide to the next sub-string. */ l = strlen(name) + 1; name += l; len -= l; } OF_prop_free(buf); return (result); } Index: head/sys/arm/ti/usb/omap_host.c =================================================================== --- head/sys/arm/ti/usb/omap_host.c (revision 332309) +++ head/sys/arm/ti/usb/omap_host.c (revision 332310) @@ -1,465 +1,465 @@ /*- * Copyright (c) 2015 Oleksandr Tymoshenko * Copyright (c) 2011 Ben Gray . * 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 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 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$"); #include #include #include #include #include #include #include #include #include #include #include /* * USB Host Module */ /* UHH */ #define OMAP_USBHOST_UHH_REVISION 0x0000 #define OMAP_USBHOST_UHH_SYSCONFIG 0x0010 #define OMAP_USBHOST_UHH_SYSSTATUS 0x0014 #define OMAP_USBHOST_UHH_HOSTCONFIG 0x0040 #define OMAP_USBHOST_UHH_DEBUG_CSR 0x0044 /* UHH Register Set */ #define UHH_SYSCONFIG_MIDLEMODE_MASK (3UL << 12) #define UHH_SYSCONFIG_MIDLEMODE_SMARTSTANDBY (2UL << 12) #define UHH_SYSCONFIG_MIDLEMODE_NOSTANDBY (1UL << 12) #define UHH_SYSCONFIG_MIDLEMODE_FORCESTANDBY (0UL << 12) #define UHH_SYSCONFIG_CLOCKACTIVITY (1UL << 8) #define UHH_SYSCONFIG_SIDLEMODE_MASK (3UL << 3) #define UHH_SYSCONFIG_SIDLEMODE_SMARTIDLE (2UL << 3) #define UHH_SYSCONFIG_SIDLEMODE_NOIDLE (1UL << 3) #define UHH_SYSCONFIG_SIDLEMODE_FORCEIDLE (0UL << 3) #define UHH_SYSCONFIG_ENAWAKEUP (1UL << 2) #define UHH_SYSCONFIG_SOFTRESET (1UL << 1) #define UHH_SYSCONFIG_AUTOIDLE (1UL << 0) #define UHH_HOSTCONFIG_APP_START_CLK (1UL << 31) #define UHH_HOSTCONFIG_P3_CONNECT_STATUS (1UL << 10) #define UHH_HOSTCONFIG_P2_CONNECT_STATUS (1UL << 9) #define UHH_HOSTCONFIG_P1_CONNECT_STATUS (1UL << 8) #define UHH_HOSTCONFIG_ENA_INCR_ALIGN (1UL << 5) #define UHH_HOSTCONFIG_ENA_INCR16 (1UL << 4) #define UHH_HOSTCONFIG_ENA_INCR8 (1UL << 3) #define UHH_HOSTCONFIG_ENA_INCR4 (1UL << 2) #define UHH_HOSTCONFIG_AUTOPPD_ON_OVERCUR_EN (1UL << 1) #define UHH_HOSTCONFIG_P1_ULPI_BYPASS (1UL << 0) /* The following are on rev2 (OMAP44xx) of the EHCI only */ #define UHH_SYSCONFIG_IDLEMODE_MASK (3UL << 2) #define UHH_SYSCONFIG_IDLEMODE_NOIDLE (1UL << 2) #define UHH_SYSCONFIG_STANDBYMODE_MASK (3UL << 4) #define UHH_SYSCONFIG_STANDBYMODE_NOSTDBY (1UL << 4) #define UHH_HOSTCONFIG_P1_MODE_MASK (3UL << 16) #define UHH_HOSTCONFIG_P1_MODE_ULPI_PHY (0UL << 16) #define UHH_HOSTCONFIG_P1_MODE_UTMI_PHY (1UL << 16) #define UHH_HOSTCONFIG_P1_MODE_HSIC (3UL << 16) #define UHH_HOSTCONFIG_P2_MODE_MASK (3UL << 18) #define UHH_HOSTCONFIG_P2_MODE_ULPI_PHY (0UL << 18) #define UHH_HOSTCONFIG_P2_MODE_UTMI_PHY (1UL << 18) #define UHH_HOSTCONFIG_P2_MODE_HSIC (3UL << 18) /* * Values of UHH_REVISION - Note: these are not given in the TRM but taken * from the linux OMAP EHCI driver (thanks guys). It has been verified on * a Panda and Beagle board. */ #define OMAP_UHH_REV1 0x00000010 /* OMAP3 */ #define OMAP_UHH_REV2 0x50700100 /* OMAP4 */ struct omap_uhh_softc { struct simplebus_softc simplebus_sc; device_t sc_dev; /* UHH register set */ struct resource* uhh_mem_res; /* The revision of the HS USB HOST read from UHH_REVISION */ uint32_t uhh_rev; /* The following details are provided by conf hints */ int port_mode[3]; }; static device_attach_t omap_uhh_attach; static device_detach_t omap_uhh_detach; static inline uint32_t omap_uhh_read_4(struct omap_uhh_softc *sc, bus_size_t off) { return bus_read_4(sc->uhh_mem_res, off); } static inline void omap_uhh_write_4(struct omap_uhh_softc *sc, bus_size_t off, uint32_t val) { bus_write_4(sc->uhh_mem_res, off, val); } static int omap_uhh_init(struct omap_uhh_softc *isc) { uint8_t tll_ch_mask; uint32_t reg; int i; /* Enable Clocks for high speed USBHOST */ ti_prcm_clk_enable(USBHSHOST_CLK); /* Read the UHH revision */ isc->uhh_rev = omap_uhh_read_4(isc, OMAP_USBHOST_UHH_REVISION); device_printf(isc->sc_dev, "UHH revision 0x%08x\n", isc->uhh_rev); if (isc->uhh_rev == OMAP_UHH_REV2) { /* For OMAP44xx devices you have to enable the per-port clocks: * PHY_MODE - External ULPI clock * TTL_MODE - Internal UTMI clock * HSIC_MODE - Internal 480Mhz and 60Mhz clocks */ switch(isc->port_mode[0]) { case EHCI_HCD_OMAP_MODE_UNKNOWN: break; case EHCI_HCD_OMAP_MODE_PHY: if (ti_prcm_clk_set_source(USBP1_PHY_CLK, EXT_CLK)) device_printf(isc->sc_dev, "failed to set clock source for port 0\n"); if (ti_prcm_clk_enable(USBP1_PHY_CLK)) device_printf(isc->sc_dev, "failed to set clock USBP1_PHY_CLK source for port 0\n"); break; case EHCI_HCD_OMAP_MODE_TLL: if (ti_prcm_clk_enable(USBP1_UTMI_CLK)) device_printf(isc->sc_dev, "failed to set clock USBP1_PHY_CLK source for port 0\n"); break; case EHCI_HCD_OMAP_MODE_HSIC: if (ti_prcm_clk_enable(USBP1_HSIC_CLK)) device_printf(isc->sc_dev, "failed to set clock USBP1_PHY_CLK source for port 0\n"); break; default: device_printf(isc->sc_dev, "unknown port mode %d for port 0\n", isc->port_mode[0]); } switch(isc->port_mode[1]) { case EHCI_HCD_OMAP_MODE_UNKNOWN: break; case EHCI_HCD_OMAP_MODE_PHY: if (ti_prcm_clk_set_source(USBP2_PHY_CLK, EXT_CLK)) device_printf(isc->sc_dev, "failed to set clock source for port 0\n"); if (ti_prcm_clk_enable(USBP2_PHY_CLK)) device_printf(isc->sc_dev, "failed to set clock USBP2_PHY_CLK source for port 1\n"); break; case EHCI_HCD_OMAP_MODE_TLL: if (ti_prcm_clk_enable(USBP2_UTMI_CLK)) device_printf(isc->sc_dev, "failed to set clock USBP2_UTMI_CLK source for port 1\n"); break; case EHCI_HCD_OMAP_MODE_HSIC: if (ti_prcm_clk_enable(USBP2_HSIC_CLK)) device_printf(isc->sc_dev, "failed to set clock USBP2_HSIC_CLK source for port 1\n"); break; default: device_printf(isc->sc_dev, "unknown port mode %d for port 1\n", isc->port_mode[1]); } } /* Put UHH in SmartIdle/SmartStandby mode */ reg = omap_uhh_read_4(isc, OMAP_USBHOST_UHH_SYSCONFIG); if (isc->uhh_rev == OMAP_UHH_REV1) { reg &= ~(UHH_SYSCONFIG_SIDLEMODE_MASK | UHH_SYSCONFIG_MIDLEMODE_MASK); reg |= (UHH_SYSCONFIG_ENAWAKEUP | UHH_SYSCONFIG_AUTOIDLE | UHH_SYSCONFIG_CLOCKACTIVITY | UHH_SYSCONFIG_SIDLEMODE_SMARTIDLE | UHH_SYSCONFIG_MIDLEMODE_SMARTSTANDBY); } else if (isc->uhh_rev == OMAP_UHH_REV2) { reg &= ~UHH_SYSCONFIG_IDLEMODE_MASK; reg |= UHH_SYSCONFIG_IDLEMODE_NOIDLE; reg &= ~UHH_SYSCONFIG_STANDBYMODE_MASK; reg |= UHH_SYSCONFIG_STANDBYMODE_NOSTDBY; } omap_uhh_write_4(isc, OMAP_USBHOST_UHH_SYSCONFIG, reg); device_printf(isc->sc_dev, "OMAP_UHH_SYSCONFIG: 0x%08x\n", reg); reg = omap_uhh_read_4(isc, OMAP_USBHOST_UHH_HOSTCONFIG); /* Setup ULPI bypass and burst configurations */ reg |= (UHH_HOSTCONFIG_ENA_INCR4 | UHH_HOSTCONFIG_ENA_INCR8 | UHH_HOSTCONFIG_ENA_INCR16); reg &= ~UHH_HOSTCONFIG_ENA_INCR_ALIGN; if (isc->uhh_rev == OMAP_UHH_REV1) { if (isc->port_mode[0] == EHCI_HCD_OMAP_MODE_UNKNOWN) reg &= ~UHH_HOSTCONFIG_P1_CONNECT_STATUS; if (isc->port_mode[1] == EHCI_HCD_OMAP_MODE_UNKNOWN) reg &= ~UHH_HOSTCONFIG_P2_CONNECT_STATUS; if (isc->port_mode[2] == EHCI_HCD_OMAP_MODE_UNKNOWN) reg &= ~UHH_HOSTCONFIG_P3_CONNECT_STATUS; /* Bypass the TLL module for PHY mode operation */ if ((isc->port_mode[0] == EHCI_HCD_OMAP_MODE_PHY) || (isc->port_mode[1] == EHCI_HCD_OMAP_MODE_PHY) || (isc->port_mode[2] == EHCI_HCD_OMAP_MODE_PHY)) reg &= ~UHH_HOSTCONFIG_P1_ULPI_BYPASS; else reg |= UHH_HOSTCONFIG_P1_ULPI_BYPASS; } else if (isc->uhh_rev == OMAP_UHH_REV2) { reg |= UHH_HOSTCONFIG_APP_START_CLK; /* Clear port mode fields for PHY mode*/ reg &= ~UHH_HOSTCONFIG_P1_MODE_MASK; reg &= ~UHH_HOSTCONFIG_P2_MODE_MASK; if (isc->port_mode[0] == EHCI_HCD_OMAP_MODE_TLL) reg |= UHH_HOSTCONFIG_P1_MODE_UTMI_PHY; else if (isc->port_mode[0] == EHCI_HCD_OMAP_MODE_HSIC) reg |= UHH_HOSTCONFIG_P1_MODE_HSIC; if (isc->port_mode[1] == EHCI_HCD_OMAP_MODE_TLL) reg |= UHH_HOSTCONFIG_P2_MODE_UTMI_PHY; else if (isc->port_mode[1] == EHCI_HCD_OMAP_MODE_HSIC) reg |= UHH_HOSTCONFIG_P2_MODE_HSIC; } omap_uhh_write_4(isc, OMAP_USBHOST_UHH_HOSTCONFIG, reg); device_printf(isc->sc_dev, "UHH setup done, uhh_hostconfig=0x%08x\n", reg); /* I found the code and comments in the Linux EHCI driver - thanks guys :) * * "An undocumented "feature" in the OMAP3 EHCI controller, causes suspended * ports to be taken out of suspend when the USBCMD.Run/Stop bit is cleared * (for example when we do omap_uhh_bus_suspend). This breaks suspend-resume if * the root-hub is allowed to suspend. Writing 1 to this undocumented * register bit disables this feature and restores normal behavior." */ #if 0 omap_uhh_write_4(isc, OMAP_USBHOST_INSNREG04, OMAP_USBHOST_INSNREG04_DISABLE_UNSUSPEND); #endif tll_ch_mask = 0; for (i = 0; i < OMAP_HS_USB_PORTS; i++) { if (isc->port_mode[i] == EHCI_HCD_OMAP_MODE_TLL) tll_ch_mask |= (1 << i); } if (tll_ch_mask) omap_tll_utmi_enable(tll_ch_mask); return(0); } /** * omap_uhh_fini - shutdown the EHCI controller * @isc: omap ehci device context * * * * LOCKING: * none * * RETURNS: * 0 on success, a negative error code on failure. */ static void omap_uhh_fini(struct omap_uhh_softc *isc) { unsigned long timeout; device_printf(isc->sc_dev, "Stopping TI EHCI USB Controller\n"); /* Set the timeout */ if (hz < 10) timeout = 1; else timeout = (100 * hz) / 1000; /* Reset the UHH, OHCI and EHCI modules */ omap_uhh_write_4(isc, OMAP_USBHOST_UHH_SYSCONFIG, 0x0002); while ((omap_uhh_read_4(isc, OMAP_USBHOST_UHH_SYSSTATUS) & 0x07) == 0x00) { /* Sleep for a tick */ pause("USBRESET", 1); if (timeout-- == 0) { device_printf(isc->sc_dev, "operation timed out\n"); break; } } /* Disable functional and interface clocks for the TLL and HOST modules */ ti_prcm_clk_disable(USBHSHOST_CLK); device_printf(isc->sc_dev, "Clock to USB host has been disabled\n"); } int omap_usb_port_mode(device_t dev, int port) { struct omap_uhh_softc *isc; isc = device_get_softc(dev); if ((port < 0) || (port >= OMAP_HS_USB_PORTS)) return (-1); return isc->port_mode[port]; } static int omap_uhh_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "ti,usbhs-host")) return (ENXIO); device_set_desc(dev, "TI OMAP USB 2.0 Host module"); return (BUS_PROBE_DEFAULT); } static int omap_uhh_attach(device_t dev) { struct omap_uhh_softc *isc = device_get_softc(dev); int err; int rid; int i; phandle_t node; char propname[16]; char *mode; /* save the device */ isc->sc_dev = dev; /* Allocate resource for the UHH register set */ rid = 0; isc->uhh_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (!isc->uhh_mem_res) { device_printf(dev, "Error: Could not map UHH memory\n"); goto error; } node = ofw_bus_get_node(dev); if (node == -1) goto error; /* Get port modes from FDT */ for (i = 0; i < OMAP_HS_USB_PORTS; i++) { isc->port_mode[i] = EHCI_HCD_OMAP_MODE_UNKNOWN; snprintf(propname, sizeof(propname), "port%d-mode", i+1); - if (OF_getprop_alloc(node, propname, 1, (void**)&mode) <= 0) + if (OF_getprop_alloc(node, propname, (void**)&mode) <= 0) continue; if (strcmp(mode, "ehci-phy") == 0) isc->port_mode[i] = EHCI_HCD_OMAP_MODE_PHY; else if (strcmp(mode, "ehci-tll") == 0) isc->port_mode[i] = EHCI_HCD_OMAP_MODE_TLL; else if (strcmp(mode, "ehci-hsic") == 0) isc->port_mode[i] = EHCI_HCD_OMAP_MODE_HSIC; } /* Initialise the ECHI registers */ err = omap_uhh_init(isc); if (err) { device_printf(dev, "Error: could not setup OMAP EHCI, %d\n", err); goto error; } simplebus_init(dev, node); /* * Allow devices to identify. */ bus_generic_probe(dev); /* * Now walk the OFW tree and attach top-level devices. */ for (node = OF_child(node); node > 0; node = OF_peer(node)) simplebus_add_device(dev, node, 0, NULL, -1, NULL); return (bus_generic_attach(dev)); error: omap_uhh_detach(dev); return (ENXIO); } static int omap_uhh_detach(device_t dev) { struct omap_uhh_softc *isc = device_get_softc(dev); /* during module unload there are lots of children leftover */ device_delete_children(dev); if (isc->uhh_mem_res) { bus_release_resource(dev, SYS_RES_MEMORY, 0, isc->uhh_mem_res); isc->uhh_mem_res = NULL; } omap_uhh_fini(isc); return (0); } static device_method_t omap_uhh_methods[] = { /* Device interface */ DEVMETHOD(device_probe, omap_uhh_probe), DEVMETHOD(device_attach, omap_uhh_attach), DEVMETHOD(device_detach, omap_uhh_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD_END }; DEFINE_CLASS_1(omap_uhh, omap_uhh_driver, omap_uhh_methods, sizeof(struct omap_uhh_softc), simplebus_driver); static devclass_t omap_uhh_devclass; DRIVER_MODULE(omap_uhh, simplebus, omap_uhh_driver, omap_uhh_devclass, 0, 0); Index: head/sys/dev/altera/avgen/altera_avgen_fdt.c =================================================================== --- head/sys/dev/altera/avgen/altera_avgen_fdt.c (revision 332309) +++ head/sys/dev/altera/avgen/altera_avgen_fdt.c (revision 332310) @@ -1,163 +1,163 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2012-2013, 2016 Robert N. M. Watson * All rights reserved. * * This software was developed by SRI International and the University of * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) * ("CTSRD"), as part of the DARPA CRASH research programme. * * 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$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int altera_avgen_fdt_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_is_compatible(dev, "sri-cambridge,avgen")) { device_set_desc(dev, "Generic Altera Avalon device attachment"); return (BUS_PROBE_DEFAULT); } return (ENXIO); } static int altera_avgen_fdt_attach(device_t dev) { struct altera_avgen_softc *sc; char *str_fileio, *str_geomio, *str_mmapio; char *str_devname; phandle_t node; pcell_t cell; int devunit, error; sc = device_get_softc(dev); sc->avg_dev = dev; sc->avg_unit = device_get_unit(dev); /* * Query driver-specific OpenFirmware properties to determine how to * expose the device via /dev. */ str_fileio = NULL; str_geomio = NULL; str_mmapio = NULL; str_devname = NULL; devunit = -1; sc->avg_width = 1; node = ofw_bus_get_node(dev); if (OF_getprop(node, "sri-cambridge,width", &cell, sizeof(cell)) > 0) sc->avg_width = cell; - (void)OF_getprop_alloc(node, "sri-cambridge,fileio", sizeof(char), + (void)OF_getprop_alloc(node, "sri-cambridge,fileio", (void **)&str_fileio); - (void)OF_getprop_alloc(node, "sri-cambridge,geomio", sizeof(char), + (void)OF_getprop_alloc(node, "sri-cambridge,geomio", (void **)&str_geomio); - (void)OF_getprop_alloc(node, "sri-cambridge,mmapio", sizeof(char), + (void)OF_getprop_alloc(node, "sri-cambridge,mmapio", (void **)&str_mmapio); - (void)OF_getprop_alloc(node, "sri-cambridge,devname", sizeof(char), + (void)OF_getprop_alloc(node, "sri-cambridge,devname", (void **)&str_devname); if (OF_getprop(node, "sri-cambridge,devunit", &cell, sizeof(cell)) > 0) devunit = cell; /* Memory allocation and checking. */ sc->avg_rid = 0; sc->avg_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->avg_rid, RF_ACTIVE); if (sc->avg_res == NULL) { device_printf(dev, "couldn't map memory\n"); return (ENXIO); } error = altera_avgen_attach(sc, str_fileio, str_geomio, str_mmapio, str_devname, devunit); if (error != 0) bus_release_resource(dev, SYS_RES_MEMORY, sc->avg_rid, sc->avg_res); if (str_fileio != NULL) OF_prop_free(str_fileio); if (str_geomio != NULL) OF_prop_free(str_geomio); if (str_mmapio != NULL) OF_prop_free(str_mmapio); if (str_devname != NULL) OF_prop_free(str_devname); return (error); } static int altera_avgen_fdt_detach(device_t dev) { struct altera_avgen_softc *sc; sc = device_get_softc(dev); altera_avgen_detach(sc); bus_release_resource(dev, SYS_RES_MEMORY, sc->avg_rid, sc->avg_res); return (0); } static device_method_t altera_avgen_fdt_methods[] = { DEVMETHOD(device_probe, altera_avgen_fdt_probe), DEVMETHOD(device_attach, altera_avgen_fdt_attach), DEVMETHOD(device_detach, altera_avgen_fdt_detach), { 0, 0 } }; static driver_t altera_avgen_fdt_driver = { "altera_avgen", altera_avgen_fdt_methods, sizeof(struct altera_avgen_softc), }; DRIVER_MODULE(avgen, simplebus, altera_avgen_fdt_driver, altera_avgen_devclass, 0, 0); Index: head/sys/dev/atkbdc/atkbdc_ebus.c =================================================================== --- head/sys/dev/atkbdc/atkbdc_ebus.c (revision 332309) +++ head/sys/dev/atkbdc/atkbdc_ebus.c (revision 332310) @@ -1,307 +1,307 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 1999 Kazutaka YOKOTA * Copyright (c) 2005 Marius Strobl * 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 as * the first lines of this file unmodified. * 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 AUTHORS ``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 AUTHORS 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. * * from: FreeBSD: src/sys/isa/atkbdc_isa.c,v 1.31 2005/05/29 04:42:28 nyan */ #include __FBSDID("$FreeBSD$"); #include "opt_kbd.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static device_probe_t atkbdc_ebus_probe; static device_attach_t atkbdc_ebus_attach; static device_method_t atkbdc_ebus_methods[] = { DEVMETHOD(device_probe, atkbdc_ebus_probe), DEVMETHOD(device_attach, atkbdc_ebus_attach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(bus_print_child, atkbdc_print_child), DEVMETHOD(bus_read_ivar, atkbdc_read_ivar), DEVMETHOD(bus_write_ivar, atkbdc_write_ivar), DEVMETHOD(bus_get_resource_list,atkbdc_get_resource_list), DEVMETHOD(bus_alloc_resource, bus_generic_rl_alloc_resource), DEVMETHOD(bus_release_resource, bus_generic_rl_release_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), DEVMETHOD(bus_set_resource, bus_generic_rl_set_resource), DEVMETHOD(bus_delete_resource, bus_generic_rl_delete_resource), DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), { 0, 0 } }; static driver_t atkbdc_ebus_driver = { ATKBDC_DRIVER_NAME, atkbdc_ebus_methods, sizeof(atkbdc_softc_t *), }; DRIVER_MODULE(atkbdc, ebus, atkbdc_ebus_driver, atkbdc_devclass, 0, 0); static int atkbdc_ebus_probe(device_t dev) { struct resource *port0, *port1; rman_res_t count, start; int error, rid; if (strcmp(ofw_bus_get_name(dev), "8042") != 0) return (ENXIO); /* * On AXi and AXmp boards the NS16550 (used to connect keyboard/ * mouse) share their IRQ lines with the i8042. Any IRQ activity * (typically during attach) of the NS16550 used to connect the * keyboard when actually the PS/2 keyboard is selected in OFW * causes interaction with the OBP i8042 driver resulting in a * hang and vice versa. As RS232 keyboards and mice obviously * aren't meant to be used in parallel with PS/2 ones on these * boards don't attach to the i8042 in case the PS/2 keyboard * isn't selected in order to prevent such hangs. * Note that it's not sufficient here to rely on the '8042' node * only showing up when a PS/2 keyboard is actually connected as * the user still might have adjusted the 'keyboard' alias to * point to the RS232 keyboard. */ if ((!strcmp(sparc64_model, "SUNW,UltraAX-MP") || !strcmp(sparc64_model, "SUNW,UltraSPARC-IIi-Engine")) && OF_finddevice("keyboard") != ofw_bus_get_node(dev)) { device_disable(dev); return (ENXIO); } device_set_desc(dev, "Keyboard controller (i8042)"); /* * The '8042' node has two identical 8 addresses wide resources * which are apparently meant to be used one for the keyboard * half and the other one for the mouse half. To simplify matters * we use one for the command/data port resource and the other * one for the status port resource as the atkbdc(4) back-end * expects two struct resource rather than two bus space handles. */ rid = 0; if (bus_get_resource(dev, SYS_RES_MEMORY, rid, &start, &count) != 0) { device_printf(dev, "cannot determine command/data port resource\n"); return (ENXIO); } port0 = bus_alloc_resource(dev, SYS_RES_MEMORY, &rid, start, start, 1, RF_ACTIVE); if (port0 == NULL) { device_printf(dev, "cannot allocate command/data port resource\n"); return (ENXIO); } rid = 1; if (bus_get_resource(dev, SYS_RES_MEMORY, rid, &start, &count) != 0) { device_printf(dev, "cannot determine status port resource\n"); error = ENXIO; goto fail_port0; } start += KBD_STATUS_PORT; port1 = bus_alloc_resource(dev, SYS_RES_MEMORY, &rid, start, start, 1, RF_ACTIVE); if (port1 == NULL) { device_printf(dev, "cannot allocate status port resource\n"); error = ENXIO; goto fail_port0; } error = atkbdc_probe_unit(device_get_unit(dev), port0, port1); if (error != 0) device_printf(dev, "atkbdc_porbe_unit failed\n"); bus_release_resource(dev, SYS_RES_MEMORY, 1, port1); fail_port0: bus_release_resource(dev, SYS_RES_MEMORY, 0, port0); return (error); } static int atkbdc_ebus_attach(device_t dev) { atkbdc_softc_t *sc; atkbdc_device_t *adi; device_t cdev; phandle_t child; rman_res_t count, intr, start; int children, error, rid, unit; char *cname, *dname; unit = device_get_unit(dev); sc = *(atkbdc_softc_t **)device_get_softc(dev); if (sc == NULL) { /* * We have to maintain two copies of the kbdc_softc struct, * as the low-level console needs to have access to the * keyboard controller before kbdc is probed and attached. * kbdc_soft[] contains the default entry for that purpose. * See atkbdc.c. XXX */ sc = atkbdc_get_softc(unit); if (sc == NULL) return (ENOMEM); device_set_softc(dev, sc); } rid = 0; if (bus_get_resource(dev, SYS_RES_MEMORY, rid, &start, &count) != 0) { device_printf(dev, "cannot determine command/data port resource\n"); return (ENXIO); } sc->retry = 5000; sc->port0 = bus_alloc_resource(dev, SYS_RES_MEMORY, &rid, start, start, 1, RF_ACTIVE); if (sc->port0 == NULL) { device_printf(dev, "cannot allocate command/data port resource\n"); return (ENXIO); } rid = 1; if (bus_get_resource(dev, SYS_RES_MEMORY, rid, &start, &count) != 0) { device_printf(dev, "cannot determine status port resource\n"); error = ENXIO; goto fail_port0; } start += KBD_STATUS_PORT; sc->port1 = bus_alloc_resource(dev, SYS_RES_MEMORY, &rid, start, start, 1, RF_ACTIVE); if (sc->port1 == NULL) { device_printf(dev, "cannot allocate status port resource\n"); error = ENXIO; goto fail_port0; } error = atkbdc_attach_unit(unit, sc, sc->port0, sc->port1); if (error != 0) { device_printf(dev, "atkbdc_attach_unit failed\n"); goto fail_port1; } /* Attach children. */ children = 0; for (child = OF_child(ofw_bus_get_node(dev)); child != 0; child = OF_peer(child)) { - if ((OF_getprop_alloc(child, "name", 1, (void **)&cname)) == -1) + if ((OF_getprop_alloc(child, "name", (void **)&cname)) == -1) continue; if (children >= 2) { device_printf(dev, "<%s>: only two children per 8042 supported\n", cname); OF_prop_free(cname); continue; } adi = malloc(sizeof(struct atkbdc_device), M_ATKBDDEV, M_NOWAIT | M_ZERO); if (adi == NULL) { device_printf(dev, "<%s>: malloc failed\n", cname); OF_prop_free(cname); continue; } if (strcmp(cname, "kb_ps2") == 0) { adi->rid = KBDC_RID_KBD; dname = ATKBD_DRIVER_NAME; } else if (strcmp(cname, "kdmouse") == 0) { adi->rid = KBDC_RID_AUX; dname = PSM_DRIVER_NAME; } else { device_printf(dev, "<%s>: unknown device\n", cname); free(adi, M_ATKBDDEV); OF_prop_free(cname); continue; } intr = bus_get_resource_start(dev, SYS_RES_IRQ, adi->rid); if (intr == 0) { device_printf(dev, "<%s>: cannot determine interrupt resource\n", cname); free(adi, M_ATKBDDEV); OF_prop_free(cname); continue; } resource_list_init(&adi->resources); resource_list_add(&adi->resources, SYS_RES_IRQ, adi->rid, intr, intr, 1); if ((cdev = device_add_child(dev, dname, -1)) == NULL) { device_printf(dev, "<%s>: device_add_child failed\n", cname); resource_list_free(&adi->resources); free(adi, M_ATKBDDEV); OF_prop_free(cname); continue; } device_set_ivars(cdev, adi); children++; } error = bus_generic_attach(dev); if (error != 0) { device_printf(dev, "bus_generic_attach failed\n"); goto fail_port1; } return (0); fail_port1: bus_release_resource(dev, SYS_RES_MEMORY, 1, sc->port1); fail_port0: bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->port0); return (error); } Index: head/sys/dev/etherswitch/e6000sw/e6000sw.c =================================================================== --- head/sys/dev/etherswitch/e6000sw/e6000sw.c (revision 332309) +++ head/sys/dev/etherswitch/e6000sw/e6000sw.c (revision 332310) @@ -1,1300 +1,1300 @@ /*- * Copyright (c) 2015 Semihalf * Copyright (c) 2015 Stormshield * 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$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "e6000swreg.h" #include "etherswitch_if.h" #include "miibus_if.h" #include "mdio_if.h" MALLOC_DECLARE(M_E6000SW); MALLOC_DEFINE(M_E6000SW, "e6000sw", "e6000sw switch"); #define E6000SW_LOCK(_sc) sx_xlock(&(_sc)->sx) #define E6000SW_UNLOCK(_sc) sx_unlock(&(_sc)->sx) #define E6000SW_LOCK_ASSERT(_sc, _what) sx_assert(&(_sc)->sx, (_what)) #define E6000SW_TRYLOCK(_sc) sx_tryxlock(&(_sc)->sx) typedef struct e6000sw_softc { device_t dev; phandle_t node; struct sx sx; struct ifnet *ifp[E6000SW_MAX_PORTS]; char *ifname[E6000SW_MAX_PORTS]; device_t miibus[E6000SW_MAX_PORTS]; struct proc *kproc; uint32_t swid; uint32_t vlan_mode; uint32_t cpuports_mask; uint32_t fixed_mask; uint32_t fixed25_mask; uint32_t ports_mask; int phy_base; int sw_addr; int num_ports; boolean_t multi_chip; } e6000sw_softc_t; static etherswitch_info_t etherswitch_info = { .es_nports = 0, .es_nvlangroups = 0, .es_vlan_caps = ETHERSWITCH_VLAN_PORT, .es_name = "Marvell 6000 series switch" }; static void e6000sw_identify(driver_t *, device_t); static int e6000sw_probe(device_t); static int e6000sw_attach(device_t); static int e6000sw_detach(device_t); static int e6000sw_readphy(device_t, int, int); static int e6000sw_writephy(device_t, int, int, int); static etherswitch_info_t* e6000sw_getinfo(device_t); static int e6000sw_getconf(device_t, etherswitch_conf_t *); static void e6000sw_lock(device_t); static void e6000sw_unlock(device_t); static int e6000sw_getport(device_t, etherswitch_port_t *); static int e6000sw_setport(device_t, etherswitch_port_t *); static int e6000sw_readreg_wrapper(device_t, int); static int e6000sw_writereg_wrapper(device_t, int, int); static int e6000sw_readphy_wrapper(device_t, int, int); static int e6000sw_writephy_wrapper(device_t, int, int, int); static int e6000sw_getvgroup_wrapper(device_t, etherswitch_vlangroup_t *); static int e6000sw_setvgroup_wrapper(device_t, etherswitch_vlangroup_t *); static int e6000sw_setvgroup(device_t, etherswitch_vlangroup_t *); static int e6000sw_getvgroup(device_t, etherswitch_vlangroup_t *); static void e6000sw_setup(device_t, e6000sw_softc_t *); static void e6000sw_port_vlan_conf(e6000sw_softc_t *); static void e6000sw_tick(void *); static void e6000sw_set_atustat(device_t, e6000sw_softc_t *, int, int); static int e6000sw_atu_flush(device_t, e6000sw_softc_t *, int); static __inline void e6000sw_writereg(e6000sw_softc_t *, int, int, int); static __inline uint32_t e6000sw_readreg(e6000sw_softc_t *, int, int); static int e6000sw_ifmedia_upd(struct ifnet *); static void e6000sw_ifmedia_sts(struct ifnet *, struct ifmediareq *); static int e6000sw_atu_mac_table(device_t, e6000sw_softc_t *, struct atu_opt *, int); static int e6000sw_get_pvid(e6000sw_softc_t *, int, int *); static int e6000sw_set_pvid(e6000sw_softc_t *, int, int); static __inline bool e6000sw_is_cpuport(e6000sw_softc_t *, int); static __inline bool e6000sw_is_fixedport(e6000sw_softc_t *, int); static __inline bool e6000sw_is_fixed25port(e6000sw_softc_t *, int); static __inline bool e6000sw_is_phyport(e6000sw_softc_t *, int); static __inline bool e6000sw_is_portenabled(e6000sw_softc_t *, int); static __inline struct mii_data *e6000sw_miiforphy(e6000sw_softc_t *, unsigned int); static device_method_t e6000sw_methods[] = { /* device interface */ DEVMETHOD(device_identify, e6000sw_identify), DEVMETHOD(device_probe, e6000sw_probe), DEVMETHOD(device_attach, e6000sw_attach), DEVMETHOD(device_detach, e6000sw_detach), /* bus interface */ DEVMETHOD(bus_add_child, device_add_child_ordered), /* mii interface */ DEVMETHOD(miibus_readreg, e6000sw_readphy), DEVMETHOD(miibus_writereg, e6000sw_writephy), /* etherswitch interface */ DEVMETHOD(etherswitch_getinfo, e6000sw_getinfo), DEVMETHOD(etherswitch_getconf, e6000sw_getconf), DEVMETHOD(etherswitch_lock, e6000sw_lock), DEVMETHOD(etherswitch_unlock, e6000sw_unlock), DEVMETHOD(etherswitch_getport, e6000sw_getport), DEVMETHOD(etherswitch_setport, e6000sw_setport), DEVMETHOD(etherswitch_readreg, e6000sw_readreg_wrapper), DEVMETHOD(etherswitch_writereg, e6000sw_writereg_wrapper), DEVMETHOD(etherswitch_readphyreg, e6000sw_readphy_wrapper), DEVMETHOD(etherswitch_writephyreg, e6000sw_writephy_wrapper), DEVMETHOD(etherswitch_setvgroup, e6000sw_setvgroup_wrapper), DEVMETHOD(etherswitch_getvgroup, e6000sw_getvgroup_wrapper), DEVMETHOD_END }; static devclass_t e6000sw_devclass; DEFINE_CLASS_0(e6000sw, e6000sw_driver, e6000sw_methods, sizeof(e6000sw_softc_t)); DRIVER_MODULE(e6000sw, mdio, e6000sw_driver, e6000sw_devclass, 0, 0); DRIVER_MODULE(etherswitch, e6000sw, etherswitch_driver, etherswitch_devclass, 0, 0); DRIVER_MODULE(miibus, e6000sw, miibus_driver, miibus_devclass, 0, 0); MODULE_DEPEND(e6000sw, mdio, 1, 1, 1); #define SMI_CMD 0 #define SMI_CMD_BUSY (1 << 15) #define SMI_CMD_OP_READ ((2 << 10) | SMI_CMD_BUSY | (1 << 12)) #define SMI_CMD_OP_WRITE ((1 << 10) | SMI_CMD_BUSY | (1 << 12)) #define SMI_DATA 1 #define MDIO_READ(dev, addr, reg) \ MDIO_READREG(device_get_parent(dev), (addr), (reg)) #define MDIO_WRITE(dev, addr, reg, val) \ MDIO_WRITEREG(device_get_parent(dev), (addr), (reg), (val)) static void e6000sw_identify(driver_t *driver, device_t parent) { if (device_find_child(parent, "e6000sw", -1) == NULL) BUS_ADD_CHILD(parent, 0, "e6000sw", -1); } static int e6000sw_probe(device_t dev) { e6000sw_softc_t *sc; const char *description; phandle_t dsa_node, switch_node; dsa_node = fdt_find_compatible(OF_finddevice("/"), "marvell,dsa", 0); switch_node = OF_child(dsa_node); if (switch_node == 0) return (ENXIO); sc = device_get_softc(dev); sc->dev = dev; sc->node = switch_node; if (OF_getencprop(sc->node, "reg", &sc->sw_addr, sizeof(sc->sw_addr)) < 0) return (ENXIO); if (sc->sw_addr != 0 && (sc->sw_addr % 2) == 0) sc->multi_chip = true; /* * Create temporary lock, just to satisfy assertions, * when obtaining the switch ID. Destroy immediately afterwards. */ sx_init(&sc->sx, "e6000sw_tmp"); E6000SW_LOCK(sc); sc->swid = e6000sw_readreg(sc, REG_PORT(0), SWITCH_ID) & 0xfff0; E6000SW_UNLOCK(sc); sx_destroy(&sc->sx); switch (sc->swid) { case MV88E6141: description = "Marvell 88E6141"; sc->phy_base = 0x10; sc->num_ports = 6; break; case MV88E6341: description = "Marvell 88E6341"; sc->phy_base = 0x10; sc->num_ports = 6; break; case MV88E6352: description = "Marvell 88E6352"; sc->num_ports = 7; break; case MV88E6172: description = "Marvell 88E6172"; sc->num_ports = 7; break; case MV88E6176: description = "Marvell 88E6176"; sc->num_ports = 7; break; default: device_printf(dev, "Unrecognized device, id 0x%x.\n", sc->swid); return (ENXIO); } device_set_desc(dev, description); return (BUS_PROBE_DEFAULT); } static int e6000sw_parse_child_fdt(e6000sw_softc_t *sc, phandle_t child, int *pport) { char *name, *portlabel; int speed; phandle_t fixed_link; uint32_t port; if (pport == NULL) return (ENXIO); if (OF_getencprop(child, "reg", (void *)&port, sizeof(port)) < 0) return (ENXIO); if (port >= sc->num_ports) return (ENXIO); *pport = port; - if (OF_getprop_alloc(child, "label", 1, (void **)&portlabel) > 0) { + if (OF_getprop_alloc(child, "label", (void **)&portlabel) > 0) { if (strncmp(portlabel, "cpu", 3) == 0) { device_printf(sc->dev, "CPU port at %d\n", port); sc->cpuports_mask |= (1 << port); sc->fixed_mask |= (1 << port); } free(portlabel, M_OFWPROP); } fixed_link = OF_child(child); if (fixed_link != 0 && - OF_getprop_alloc(fixed_link, "name", 1, (void **)&name) > 0) { + OF_getprop_alloc(fixed_link, "name", (void **)&name) > 0) { if (strncmp(name, "fixed-link", 10) == 0) { /* Assume defaults: 1g - full-duplex. */ sc->fixed_mask |= (1 << port); if (OF_getencprop(fixed_link, "speed", &speed, sizeof(speed)) > 0) { if (speed == 2500 && (MVSWITCH(sc, MV88E6141) || MVSWITCH(sc, MV88E6341))) { sc->fixed25_mask |= (1 << port); } } } free(name, M_OFWPROP); } if ((sc->fixed_mask & (1 << port)) != 0) device_printf(sc->dev, "fixed port at %d\n", port); else device_printf(sc->dev, "PHY at port %d\n", port); return (0); } static int e6000sw_init_interface(e6000sw_softc_t *sc, int port) { char name[IFNAMSIZ]; snprintf(name, IFNAMSIZ, "%sport", device_get_nameunit(sc->dev)); sc->ifp[port] = if_alloc(IFT_ETHER); if (sc->ifp[port] == NULL) return (ENOMEM); sc->ifp[port]->if_softc = sc; sc->ifp[port]->if_flags |= IFF_UP | IFF_BROADCAST | IFF_DRV_RUNNING | IFF_SIMPLEX; sc->ifname[port] = malloc(strlen(name) + 1, M_E6000SW, M_NOWAIT); if (sc->ifname[port] == NULL) { if_free(sc->ifp[port]); return (ENOMEM); } memcpy(sc->ifname[port], name, strlen(name) + 1); if_initname(sc->ifp[port], sc->ifname[port], port); return (0); } static int e6000sw_attach_miibus(e6000sw_softc_t *sc, int port) { int err; err = mii_attach(sc->dev, &sc->miibus[port], sc->ifp[port], e6000sw_ifmedia_upd, e6000sw_ifmedia_sts, BMSR_DEFCAPMASK, port + sc->phy_base, MII_OFFSET_ANY, 0); if (err != 0) return (err); return (0); } static int e6000sw_attach(device_t dev) { e6000sw_softc_t *sc; phandle_t child; int err, port; uint32_t reg; err = 0; sc = device_get_softc(dev); if (sc->multi_chip) device_printf(dev, "multi-chip addressing mode\n"); else device_printf(dev, "single-chip addressing mode\n"); sx_init(&sc->sx, "e6000sw"); E6000SW_LOCK(sc); e6000sw_setup(dev, sc); for (child = OF_child(sc->node); child != 0; child = OF_peer(child)) { err = e6000sw_parse_child_fdt(sc, child, &port); if (err != 0) { device_printf(sc->dev, "failed to parse DTS\n"); goto out_fail; } /* Port is in use. */ sc->ports_mask |= (1 << port); err = e6000sw_init_interface(sc, port); if (err != 0) { device_printf(sc->dev, "failed to init interface\n"); goto out_fail; } if (e6000sw_is_fixedport(sc, port)) { /* Link must be down to change speed force value. */ reg = e6000sw_readreg(sc, REG_PORT(port), PSC_CONTROL); reg &= ~PSC_CONTROL_LINK_UP; reg |= PSC_CONTROL_FORCED_LINK; e6000sw_writereg(sc, REG_PORT(port), PSC_CONTROL, reg); /* * Force speed, full-duplex, EEE off and flow-control * on. */ if (e6000sw_is_fixed25port(sc, port)) reg = PSC_CONTROL_SPD2500; else reg = PSC_CONTROL_SPD1000; reg |= PSC_CONTROL_FORCED_DPX | PSC_CONTROL_FULLDPX | PSC_CONTROL_FORCED_LINK | PSC_CONTROL_LINK_UP | PSC_CONTROL_FORCED_FC | PSC_CONTROL_FC_ON | PSC_CONTROL_FORCED_SPD; if (MVSWITCH(sc, MV88E6141) || MVSWITCH(sc, MV88E6341)) reg |= PSC_CONTROL_FORCED_EEE; e6000sw_writereg(sc, REG_PORT(port), PSC_CONTROL, reg); } /* Don't attach miibus at CPU/fixed ports */ if (!e6000sw_is_phyport(sc, port)) continue; err = e6000sw_attach_miibus(sc, port); if (err != 0) { device_printf(sc->dev, "failed to attach miibus\n"); goto out_fail; } } etherswitch_info.es_nports = sc->num_ports; /* Default to port vlan. */ e6000sw_port_vlan_conf(sc); E6000SW_UNLOCK(sc); bus_generic_probe(dev); bus_generic_attach(dev); kproc_create(e6000sw_tick, sc, &sc->kproc, 0, 0, "e6000sw tick kproc"); return (0); out_fail: E6000SW_UNLOCK(sc); e6000sw_detach(dev); return (err); } static __inline int e6000sw_poll_done(e6000sw_softc_t *sc) { int i; for (i = 0; i < E6000SW_SMI_TIMEOUT; i++) { if ((e6000sw_readreg(sc, REG_GLOBAL2, SMI_PHY_CMD_REG) & (1 << PHY_CMD_SMI_BUSY)) == 0) return (0); pause("e6000sw PHY poll", hz/1000); } return (ETIMEDOUT); } /* * PHY registers are paged. Put page index in reg 22 (accessible from every * page), then access specific register. */ static int e6000sw_readphy(device_t dev, int phy, int reg) { e6000sw_softc_t *sc; uint32_t val; int err; sc = device_get_softc(dev); if (!e6000sw_is_phyport(sc, phy) || reg >= E6000SW_NUM_PHY_REGS) { device_printf(dev, "Wrong register address.\n"); return (EINVAL); } E6000SW_LOCK_ASSERT(sc, SA_XLOCKED); err = e6000sw_poll_done(sc); if (err != 0) { device_printf(dev, "Timeout while waiting for switch\n"); return (err); } val = 1 << PHY_CMD_SMI_BUSY; val |= PHY_CMD_MODE_MDIO << PHY_CMD_MODE; val |= PHY_CMD_OPCODE_READ << PHY_CMD_OPCODE; val |= (reg << PHY_CMD_REG_ADDR) & PHY_CMD_REG_ADDR_MASK; val |= (phy << PHY_CMD_DEV_ADDR) & PHY_CMD_DEV_ADDR_MASK; e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_CMD_REG, val); err = e6000sw_poll_done(sc); if (err != 0) { device_printf(dev, "Timeout while waiting for switch\n"); return (err); } val = e6000sw_readreg(sc, REG_GLOBAL2, SMI_PHY_DATA_REG); return (val & PHY_DATA_MASK); } static int e6000sw_writephy(device_t dev, int phy, int reg, int data) { e6000sw_softc_t *sc; uint32_t val; int err; sc = device_get_softc(dev); if (!e6000sw_is_phyport(sc, phy) || reg >= E6000SW_NUM_PHY_REGS) { device_printf(dev, "Wrong register address.\n"); return (EINVAL); } E6000SW_LOCK_ASSERT(sc, SA_XLOCKED); err = e6000sw_poll_done(sc); if (err != 0) { device_printf(dev, "Timeout while waiting for switch\n"); return (err); } val = 1 << PHY_CMD_SMI_BUSY; val |= PHY_CMD_MODE_MDIO << PHY_CMD_MODE; val |= PHY_CMD_OPCODE_WRITE << PHY_CMD_OPCODE; val |= (reg << PHY_CMD_REG_ADDR) & PHY_CMD_REG_ADDR_MASK; val |= (phy << PHY_CMD_DEV_ADDR) & PHY_CMD_DEV_ADDR_MASK; e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_DATA_REG, data & PHY_DATA_MASK); e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_CMD_REG, val); err = e6000sw_poll_done(sc); if (err != 0) device_printf(dev, "Timeout while waiting for switch\n"); return (err); } static int e6000sw_detach(device_t dev) { int phy; e6000sw_softc_t *sc; sc = device_get_softc(dev); bus_generic_detach(dev); sx_destroy(&sc->sx); for (phy = 0; phy < sc->num_ports; phy++) { if (sc->miibus[phy] != NULL) device_delete_child(dev, sc->miibus[phy]); if (sc->ifp[phy] != NULL) if_free(sc->ifp[phy]); if (sc->ifname[phy] != NULL) free(sc->ifname[phy], M_E6000SW); } return (0); } static etherswitch_info_t* e6000sw_getinfo(device_t dev) { return (ðerswitch_info); } static int e6000sw_getconf(device_t dev, etherswitch_conf_t *conf) { struct e6000sw_softc *sc; /* Return the VLAN mode. */ sc = device_get_softc(dev); conf->cmd = ETHERSWITCH_CONF_VLAN_MODE; conf->vlan_mode = sc->vlan_mode; return (0); } static void e6000sw_lock(device_t dev) { struct e6000sw_softc *sc; sc = device_get_softc(dev); E6000SW_LOCK_ASSERT(sc, SA_UNLOCKED); E6000SW_LOCK(sc); } static void e6000sw_unlock(device_t dev) { struct e6000sw_softc *sc; sc = device_get_softc(dev); E6000SW_LOCK_ASSERT(sc, SA_XLOCKED); E6000SW_UNLOCK(sc); } static int e6000sw_getport(device_t dev, etherswitch_port_t *p) { struct mii_data *mii; int err; struct ifmediareq *ifmr; e6000sw_softc_t *sc = device_get_softc(dev); E6000SW_LOCK_ASSERT(sc, SA_UNLOCKED); if (p->es_port >= sc->num_ports || p->es_port < 0) return (EINVAL); if (!e6000sw_is_portenabled(sc, p->es_port)) return (0); err = 0; E6000SW_LOCK(sc); e6000sw_get_pvid(sc, p->es_port, &p->es_pvid); if (e6000sw_is_fixedport(sc, p->es_port)) { if (e6000sw_is_cpuport(sc, p->es_port)) p->es_flags |= ETHERSWITCH_PORT_CPU; ifmr = &p->es_ifmr; ifmr->ifm_status = IFM_ACTIVE | IFM_AVALID; ifmr->ifm_count = 0; if (e6000sw_is_fixed25port(sc, p->es_port)) ifmr->ifm_active = IFM_2500_T; else ifmr->ifm_active = IFM_1000_T; ifmr->ifm_active |= IFM_ETHER | IFM_FDX; ifmr->ifm_current = ifmr->ifm_active; ifmr->ifm_mask = 0; } else { mii = e6000sw_miiforphy(sc, p->es_port); err = ifmedia_ioctl(mii->mii_ifp, &p->es_ifr, &mii->mii_media, SIOCGIFMEDIA); } E6000SW_UNLOCK(sc); return (err); } static int e6000sw_setport(device_t dev, etherswitch_port_t *p) { e6000sw_softc_t *sc; int err; struct mii_data *mii; sc = device_get_softc(dev); E6000SW_LOCK_ASSERT(sc, SA_UNLOCKED); if (p->es_port >= sc->num_ports || p->es_port < 0) return (EINVAL); if (!e6000sw_is_portenabled(sc, p->es_port)) return (0); err = 0; E6000SW_LOCK(sc); if (p->es_pvid != 0) e6000sw_set_pvid(sc, p->es_port, p->es_pvid); if (e6000sw_is_phyport(sc, p->es_port)) { mii = e6000sw_miiforphy(sc, p->es_port); err = ifmedia_ioctl(mii->mii_ifp, &p->es_ifr, &mii->mii_media, SIOCSIFMEDIA); } E6000SW_UNLOCK(sc); return (err); } /* * Registers in this switch are divided into sections, specified in * documentation. So as to access any of them, section index and reg index * is necessary. etherswitchcfg uses only one variable, so indexes were * compressed into addr_reg: 32 * section_index + reg_index. */ static int e6000sw_readreg_wrapper(device_t dev, int addr_reg) { if ((addr_reg > (REG_GLOBAL2 * 32 + REG_NUM_MAX)) || (addr_reg < (REG_PORT(0) * 32))) { device_printf(dev, "Wrong register address.\n"); return (EINVAL); } return (e6000sw_readreg(device_get_softc(dev), addr_reg / 32, addr_reg % 32)); } static int e6000sw_writereg_wrapper(device_t dev, int addr_reg, int val) { if ((addr_reg > (REG_GLOBAL2 * 32 + REG_NUM_MAX)) || (addr_reg < (REG_PORT(0) * 32))) { device_printf(dev, "Wrong register address.\n"); return (EINVAL); } e6000sw_writereg(device_get_softc(dev), addr_reg / 5, addr_reg % 32, val); return (0); } /* * These wrappers are necessary because PHY accesses from etherswitchcfg * need to be synchronized with locks, while miibus PHY accesses do not. */ static int e6000sw_readphy_wrapper(device_t dev, int phy, int reg) { e6000sw_softc_t *sc; int ret; sc = device_get_softc(dev); E6000SW_LOCK_ASSERT(sc, SA_UNLOCKED); E6000SW_LOCK(sc); ret = e6000sw_readphy(dev, phy, reg); E6000SW_UNLOCK(sc); return (ret); } static int e6000sw_writephy_wrapper(device_t dev, int phy, int reg, int data) { e6000sw_softc_t *sc; int ret; sc = device_get_softc(dev); E6000SW_LOCK_ASSERT(sc, SA_UNLOCKED); E6000SW_LOCK(sc); ret = e6000sw_writephy(dev, phy, reg, data); E6000SW_UNLOCK(sc); return (ret); } /* * setvgroup/getvgroup called from etherswitchfcg need to be locked, * while internal calls do not. */ static int e6000sw_setvgroup_wrapper(device_t dev, etherswitch_vlangroup_t *vg) { e6000sw_softc_t *sc; int ret; sc = device_get_softc(dev); E6000SW_LOCK_ASSERT(sc, SA_UNLOCKED); E6000SW_LOCK(sc); ret = e6000sw_setvgroup(dev, vg); E6000SW_UNLOCK(sc); return (ret); } static int e6000sw_getvgroup_wrapper(device_t dev, etherswitch_vlangroup_t *vg) { e6000sw_softc_t *sc; int ret; sc = device_get_softc(dev); E6000SW_LOCK_ASSERT(sc, SA_UNLOCKED); E6000SW_LOCK(sc); ret = e6000sw_getvgroup(dev, vg); E6000SW_UNLOCK(sc); return (ret); } static __inline void e6000sw_port_vlan_assign(e6000sw_softc_t *sc, int port, uint32_t fid, uint32_t members) { uint32_t reg; reg = e6000sw_readreg(sc, REG_PORT(port), PORT_VLAN_MAP); reg &= ~PORT_VLAN_MAP_TABLE_MASK; reg &= ~PORT_VLAN_MAP_FID_MASK; reg |= members & PORT_VLAN_MAP_TABLE_MASK & ~(1 << port); reg |= (fid << PORT_VLAN_MAP_FID) & PORT_VLAN_MAP_FID_MASK; e6000sw_writereg(sc, REG_PORT(port), PORT_VLAN_MAP, reg); reg = e6000sw_readreg(sc, REG_PORT(port), PORT_CONTROL_1); reg &= ~PORT_CONTROL_1_FID_MASK; reg |= (fid >> 4) & PORT_CONTROL_1_FID_MASK; e6000sw_writereg(sc, REG_PORT(port), PORT_CONTROL_1, reg); } static int e6000sw_set_port_vlan(e6000sw_softc_t *sc, etherswitch_vlangroup_t *vg) { uint32_t port; port = vg->es_vlangroup; if (port > sc->num_ports) return (EINVAL); if (vg->es_member_ports != vg->es_untagged_ports) { device_printf(sc->dev, "Tagged ports not supported.\n"); return (EINVAL); } e6000sw_port_vlan_assign(sc, port, port + 1, vg->es_untagged_ports); vg->es_vid = port | ETHERSWITCH_VID_VALID; return (0); } static int e6000sw_setvgroup(device_t dev, etherswitch_vlangroup_t *vg) { e6000sw_softc_t *sc; sc = device_get_softc(dev); E6000SW_LOCK_ASSERT(sc, SA_XLOCKED); if (sc->vlan_mode == ETHERSWITCH_VLAN_PORT) return (e6000sw_set_port_vlan(sc, vg)); return (EINVAL); } static int e6000sw_get_port_vlan(e6000sw_softc_t *sc, etherswitch_vlangroup_t *vg) { uint32_t port, reg; port = vg->es_vlangroup; if (port > sc->num_ports) return (EINVAL); if (!e6000sw_is_portenabled(sc, port)) { vg->es_vid = port; return (0); } reg = e6000sw_readreg(sc, REG_PORT(port), PORT_VLAN_MAP); vg->es_untagged_ports = vg->es_member_ports = reg & PORT_VLAN_MAP_TABLE_MASK; vg->es_vid = port | ETHERSWITCH_VID_VALID; vg->es_fid = (reg & PORT_VLAN_MAP_FID_MASK) >> PORT_VLAN_MAP_FID; reg = e6000sw_readreg(sc, REG_PORT(port), PORT_CONTROL_1); vg->es_fid |= (reg & PORT_CONTROL_1_FID_MASK) << 4; return (0); } static int e6000sw_getvgroup(device_t dev, etherswitch_vlangroup_t *vg) { e6000sw_softc_t *sc; sc = device_get_softc(dev); E6000SW_LOCK_ASSERT(sc, SA_XLOCKED); if (sc->vlan_mode == ETHERSWITCH_VLAN_PORT) return (e6000sw_get_port_vlan(sc, vg)); return (EINVAL); } static __inline struct mii_data* e6000sw_miiforphy(e6000sw_softc_t *sc, unsigned int phy) { if (!e6000sw_is_phyport(sc, phy)) return (NULL); return (device_get_softc(sc->miibus[phy])); } static int e6000sw_ifmedia_upd(struct ifnet *ifp) { e6000sw_softc_t *sc; struct mii_data *mii; sc = ifp->if_softc; mii = e6000sw_miiforphy(sc, ifp->if_dunit); if (mii == NULL) return (ENXIO); mii_mediachg(mii); return (0); } static void e6000sw_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr) { e6000sw_softc_t *sc; struct mii_data *mii; sc = ifp->if_softc; mii = e6000sw_miiforphy(sc, ifp->if_dunit); if (mii == NULL) return; mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; } static int e6000sw_smi_waitready(e6000sw_softc_t *sc, int phy) { int i; for (i = 0; i < E6000SW_SMI_TIMEOUT; i++) { if ((MDIO_READ(sc->dev, phy, SMI_CMD) & SMI_CMD_BUSY) == 0) return (0); DELAY(1); } return (1); } static __inline uint32_t e6000sw_readreg(e6000sw_softc_t *sc, int addr, int reg) { E6000SW_LOCK_ASSERT(sc, SA_XLOCKED); if (!sc->multi_chip) return (MDIO_READ(sc->dev, addr, reg) & 0xffff); if (e6000sw_smi_waitready(sc, sc->sw_addr)) { printf("e6000sw: readreg timeout\n"); return (0xffff); } MDIO_WRITE(sc->dev, sc->sw_addr, SMI_CMD, SMI_CMD_OP_READ | (addr << 5) | reg); if (e6000sw_smi_waitready(sc, sc->sw_addr)) { printf("e6000sw: readreg timeout\n"); return (0xffff); } return (MDIO_READ(sc->dev, sc->sw_addr, SMI_DATA) & 0xffff); } static __inline void e6000sw_writereg(e6000sw_softc_t *sc, int addr, int reg, int val) { E6000SW_LOCK_ASSERT(sc, SA_XLOCKED); if (!sc->multi_chip) { MDIO_WRITE(sc->dev, addr, reg, val); return; } if (e6000sw_smi_waitready(sc, sc->sw_addr)) { printf("e6000sw: readreg timeout\n"); return; } MDIO_WRITE(sc->dev, sc->sw_addr, SMI_DATA, val); MDIO_WRITE(sc->dev, sc->sw_addr, SMI_CMD, SMI_CMD_OP_WRITE | (addr << 5) | reg); if (e6000sw_smi_waitready(sc, sc->sw_addr)) { printf("e6000sw: readreg timeout\n"); return; } } static __inline bool e6000sw_is_cpuport(e6000sw_softc_t *sc, int port) { return ((sc->cpuports_mask & (1 << port)) ? true : false); } static __inline bool e6000sw_is_fixedport(e6000sw_softc_t *sc, int port) { return ((sc->fixed_mask & (1 << port)) ? true : false); } static __inline bool e6000sw_is_fixed25port(e6000sw_softc_t *sc, int port) { return ((sc->fixed25_mask & (1 << port)) ? true : false); } static __inline bool e6000sw_is_phyport(e6000sw_softc_t *sc, int port) { uint32_t phy_mask; phy_mask = ~(sc->fixed_mask | sc->cpuports_mask); return ((phy_mask & (1 << port)) ? true : false); } static __inline bool e6000sw_is_portenabled(e6000sw_softc_t *sc, int port) { return ((sc->ports_mask & (1 << port)) ? true : false); } static __inline int e6000sw_set_pvid(e6000sw_softc_t *sc, int port, int pvid) { e6000sw_writereg(sc, REG_PORT(port), PORT_VID, pvid & PORT_VID_DEF_VID_MASK); return (0); } static __inline int e6000sw_get_pvid(e6000sw_softc_t *sc, int port, int *pvid) { if (pvid == NULL) return (ENXIO); *pvid = e6000sw_readreg(sc, REG_PORT(port), PORT_VID) & PORT_VID_DEF_VID_MASK; return (0); } /* * Convert port status to ifmedia. */ static void e6000sw_update_ifmedia(uint16_t portstatus, u_int *media_status, u_int *media_active) { *media_active = IFM_ETHER; *media_status = IFM_AVALID; if ((portstatus & PORT_STATUS_LINK_MASK) != 0) *media_status |= IFM_ACTIVE; else { *media_active |= IFM_NONE; return; } switch (portstatus & PORT_STATUS_SPEED_MASK) { case PORT_STATUS_SPEED_10: *media_active |= IFM_10_T; break; case PORT_STATUS_SPEED_100: *media_active |= IFM_100_TX; break; case PORT_STATUS_SPEED_1000: *media_active |= IFM_1000_T; break; } if ((portstatus & PORT_STATUS_DUPLEX_MASK) == 0) *media_active |= IFM_FDX; else *media_active |= IFM_HDX; } static void e6000sw_tick (void *arg) { e6000sw_softc_t *sc; struct mii_data *mii; struct mii_softc *miisc; uint16_t portstatus; int port; sc = arg; E6000SW_LOCK_ASSERT(sc, SA_UNLOCKED); for (;;) { E6000SW_LOCK(sc); for (port = 0; port < sc->num_ports; port++) { /* Tick only on PHY ports */ if (!e6000sw_is_portenabled(sc, port) || !e6000sw_is_phyport(sc, port)) continue; mii = e6000sw_miiforphy(sc, port); if (mii == NULL) continue; portstatus = e6000sw_readreg(sc, REG_PORT(port), PORT_STATUS); e6000sw_update_ifmedia(portstatus, &mii->mii_media_status, &mii->mii_media_active); LIST_FOREACH(miisc, &mii->mii_phys, mii_list) { if (IFM_INST(mii->mii_media.ifm_cur->ifm_media) != miisc->mii_inst) continue; mii_phy_update(miisc, MII_POLLSTAT); } } E6000SW_UNLOCK(sc); pause("e6000sw tick", 1000); } } static void e6000sw_setup(device_t dev, e6000sw_softc_t *sc) { uint16_t atu_ctrl, atu_age; /* Set aging time */ e6000sw_writereg(sc, REG_GLOBAL, ATU_CONTROL, (E6000SW_DEFAULT_AGETIME << ATU_CONTROL_AGETIME) | (1 << ATU_CONTROL_LEARN2ALL)); /* Send all with specific mac address to cpu port */ e6000sw_writereg(sc, REG_GLOBAL2, MGMT_EN_2x, MGMT_EN_ALL); e6000sw_writereg(sc, REG_GLOBAL2, MGMT_EN_0x, MGMT_EN_ALL); /* Disable Remote Management */ e6000sw_writereg(sc, REG_GLOBAL, SWITCH_GLOBAL_CONTROL2, 0); /* Disable loopback filter and flow control messages */ e6000sw_writereg(sc, REG_GLOBAL2, SWITCH_MGMT, SWITCH_MGMT_PRI_MASK | (1 << SWITCH_MGMT_RSVD2CPU) | SWITCH_MGMT_FC_PRI_MASK | (1 << SWITCH_MGMT_FORCEFLOW)); e6000sw_atu_flush(dev, sc, NO_OPERATION); e6000sw_atu_mac_table(dev, sc, NULL, NO_OPERATION); e6000sw_set_atustat(dev, sc, 0, COUNT_ALL); /* Set ATU AgeTime to 15 seconds */ atu_age = 1; atu_ctrl = e6000sw_readreg(sc, REG_GLOBAL, ATU_CONTROL); /* Set new AgeTime field */ atu_ctrl &= ~ATU_CONTROL_AGETIME_MASK; e6000sw_writereg(sc, REG_GLOBAL, ATU_CONTROL, atu_ctrl | (atu_age << ATU_CONTROL_AGETIME)); } static void e6000sw_port_vlan_conf(e6000sw_softc_t *sc) { int i, port, ret; uint32_t members; /* Disable all ports */ for (port = 0; port < sc->num_ports; port++) { ret = e6000sw_readreg(sc, REG_PORT(port), PORT_CONTROL); e6000sw_writereg(sc, REG_PORT(port), PORT_CONTROL, (ret & ~PORT_CONTROL_ENABLE)); } /* Set port priority */ for (port = 0; port < sc->num_ports; port++) { if (!e6000sw_is_portenabled(sc, port)) continue; ret = e6000sw_readreg(sc, REG_PORT(port), PORT_VID); ret &= ~PORT_VID_PRIORITY_MASK; e6000sw_writereg(sc, REG_PORT(port), PORT_VID, ret); } /* Set VID map */ for (port = 0; port < sc->num_ports; port++) { if (!e6000sw_is_portenabled(sc, port)) continue; ret = e6000sw_readreg(sc, REG_PORT(port), PORT_VID); ret &= ~PORT_VID_DEF_VID_MASK; ret |= (port + 1); e6000sw_writereg(sc, REG_PORT(port), PORT_VID, ret); } /* Enable all ports */ for (port = 0; port < sc->num_ports; port++) { if (!e6000sw_is_portenabled(sc, port)) continue; ret = e6000sw_readreg(sc, REG_PORT(port), PORT_CONTROL); e6000sw_writereg(sc, REG_PORT(port), PORT_CONTROL, (ret | PORT_CONTROL_ENABLE)); } /* Set VLAN mode. */ sc->vlan_mode = ETHERSWITCH_VLAN_PORT; etherswitch_info.es_nvlangroups = sc->num_ports; for (port = 0; port < sc->num_ports; port++) { members = 0; if (e6000sw_is_portenabled(sc, port)) { for (i = 0; i < sc->num_ports; i++) { if (i == port || !e6000sw_is_portenabled(sc, i)) continue; members |= (1 << i); } } e6000sw_port_vlan_assign(sc, port, port + 1, members); } } static void e6000sw_set_atustat(device_t dev, e6000sw_softc_t *sc, int bin, int flag) { uint16_t ret; ret = e6000sw_readreg(sc, REG_GLOBAL2, ATU_STATS); e6000sw_writereg(sc, REG_GLOBAL2, ATU_STATS, (bin << ATU_STATS_BIN ) | (flag << ATU_STATS_FLAG)); } static int e6000sw_atu_mac_table(device_t dev, e6000sw_softc_t *sc, struct atu_opt *atu, int flag) { uint16_t ret_opt; uint16_t ret_data; int retries; if (flag == NO_OPERATION) return (0); else if ((flag & (LOAD_FROM_FIB | PURGE_FROM_FIB | GET_NEXT_IN_FIB | GET_VIOLATION_DATA | CLEAR_VIOLATION_DATA)) == 0) { device_printf(dev, "Wrong Opcode for ATU operation\n"); return (EINVAL); } ret_opt = e6000sw_readreg(sc, REG_GLOBAL, ATU_OPERATION); if (ret_opt & ATU_UNIT_BUSY) { device_printf(dev, "ATU unit is busy, cannot access" "register\n"); return (EBUSY); } else { if(flag & LOAD_FROM_FIB) { ret_data = e6000sw_readreg(sc, REG_GLOBAL, ATU_DATA); e6000sw_writereg(sc, REG_GLOBAL2, ATU_DATA, (ret_data & ~ENTRY_STATE)); } e6000sw_writereg(sc, REG_GLOBAL, ATU_MAC_ADDR01, atu->mac_01); e6000sw_writereg(sc, REG_GLOBAL, ATU_MAC_ADDR23, atu->mac_23); e6000sw_writereg(sc, REG_GLOBAL, ATU_MAC_ADDR45, atu->mac_45); e6000sw_writereg(sc, REG_GLOBAL, ATU_FID, atu->fid); e6000sw_writereg(sc, REG_GLOBAL, ATU_OPERATION, (ret_opt | ATU_UNIT_BUSY | flag)); retries = E6000SW_RETRIES; while (--retries & (e6000sw_readreg(sc, REG_GLOBAL, ATU_OPERATION) & ATU_UNIT_BUSY)) DELAY(1); if (retries == 0) device_printf(dev, "Timeout while flushing\n"); else if (flag & GET_NEXT_IN_FIB) { atu->mac_01 = e6000sw_readreg(sc, REG_GLOBAL, ATU_MAC_ADDR01); atu->mac_23 = e6000sw_readreg(sc, REG_GLOBAL, ATU_MAC_ADDR23); atu->mac_45 = e6000sw_readreg(sc, REG_GLOBAL, ATU_MAC_ADDR45); } } return (0); } static int e6000sw_atu_flush(device_t dev, e6000sw_softc_t *sc, int flag) { uint16_t ret; int retries; if (flag == NO_OPERATION) return (0); ret = e6000sw_readreg(sc, REG_GLOBAL, ATU_OPERATION); if (ret & ATU_UNIT_BUSY) { device_printf(dev, "Atu unit is busy, cannot flush\n"); return (EBUSY); } else { e6000sw_writereg(sc, REG_GLOBAL, ATU_OPERATION, (ret | ATU_UNIT_BUSY | flag)); retries = E6000SW_RETRIES; while (--retries & (e6000sw_readreg(sc, REG_GLOBAL, ATU_OPERATION) & ATU_UNIT_BUSY)) DELAY(1); if (retries == 0) device_printf(dev, "Timeout while flushing\n"); } return (0); } Index: head/sys/dev/extres/regulator/regulator.c =================================================================== --- head/sys/dev/extres/regulator/regulator.c (revision 332309) +++ head/sys/dev/extres/regulator/regulator.c (revision 332310) @@ -1,1191 +1,1191 @@ /*- * Copyright 2016 Michal Meloun * 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$"); #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef FDT #include #include #include #endif #include #include "regdev_if.h" SYSCTL_NODE(_hw, OID_AUTO, regulator, CTLFLAG_RD, NULL, "Regulators"); MALLOC_DEFINE(M_REGULATOR, "regulator", "Regulator framework"); #define DIV_ROUND_UP(n,d) howmany(n, d) /* Forward declarations. */ struct regulator; struct regnode; typedef TAILQ_HEAD(regnode_list, regnode) regnode_list_t; typedef TAILQ_HEAD(regulator_list, regulator) regulator_list_t; /* Default regulator methods. */ static int regnode_method_enable(struct regnode *regnode, bool enable, int *udelay); static int regnode_method_status(struct regnode *regnode, int *status); static int regnode_method_set_voltage(struct regnode *regnode, int min_uvolt, int max_uvolt, int *udelay); static int regnode_method_get_voltage(struct regnode *regnode, int *uvolt); static void regulator_shutdown(void *dummy); /* * Regulator controller methods. */ static regnode_method_t regnode_methods[] = { REGNODEMETHOD(regnode_enable, regnode_method_enable), REGNODEMETHOD(regnode_status, regnode_method_status), REGNODEMETHOD(regnode_set_voltage, regnode_method_set_voltage), REGNODEMETHOD(regnode_get_voltage, regnode_method_get_voltage), REGNODEMETHOD_END }; DEFINE_CLASS_0(regnode, regnode_class, regnode_methods, 0); /* * Regulator node - basic element for modelling SOC and bard power supply * chains. Its contains producer data. */ struct regnode { KOBJ_FIELDS; TAILQ_ENTRY(regnode) reglist_link; /* Global list entry */ regulator_list_t consumers_list; /* Consumers list */ /* Cache for already resolved names */ struct regnode *parent; /* Resolved parent */ /* Details of this device. */ const char *name; /* Globally unique name */ const char *parent_name; /* Parent name */ device_t pdev; /* Producer device_t */ void *softc; /* Producer softc */ intptr_t id; /* Per producer unique id */ #ifdef FDT phandle_t ofw_node; /* OFW node of regulator */ #endif int flags; /* REGULATOR_FLAGS_ */ struct sx lock; /* Lock for this regulator */ int ref_cnt; /* Reference counter */ int enable_cnt; /* Enabled counter */ struct regnode_std_param std_param; /* Standard parameters */ struct sysctl_ctx_list sysctl_ctx; }; /* * Per consumer data, information about how a consumer is using a regulator * node. * A pointer to this structure is used as a handle in the consumer interface. */ struct regulator { device_t cdev; /* Consumer device */ struct regnode *regnode; TAILQ_ENTRY(regulator) link; /* Consumers list entry */ int enable_cnt; int min_uvolt; /* Requested uvolt range */ int max_uvolt; }; /* * Regulator names must be system wide unique. */ static regnode_list_t regnode_list = TAILQ_HEAD_INITIALIZER(regnode_list); static struct sx regnode_topo_lock; SX_SYSINIT(regulator_topology, ®node_topo_lock, "Regulator topology lock"); #define REG_TOPO_SLOCK() sx_slock(®node_topo_lock) #define REG_TOPO_XLOCK() sx_xlock(®node_topo_lock) #define REG_TOPO_UNLOCK() sx_unlock(®node_topo_lock) #define REG_TOPO_ASSERT() sx_assert(®node_topo_lock, SA_LOCKED) #define REG_TOPO_XASSERT() sx_assert(®node_topo_lock, SA_XLOCKED) #define REGNODE_SLOCK(_sc) sx_slock(&((_sc)->lock)) #define REGNODE_XLOCK(_sc) sx_xlock(&((_sc)->lock)) #define REGNODE_UNLOCK(_sc) sx_unlock(&((_sc)->lock)) SYSINIT(regulator_shutdown, SI_SUB_LAST, SI_ORDER_ANY, regulator_shutdown, NULL); /* * Disable unused regulator * We run this function at SI_SUB_LAST which mean that every driver that needs * regulator should have already enable them. * All the remaining regulators should be those left enabled by the bootloader * or enable by default by the PMIC. */ static void regulator_shutdown(void *dummy) { struct regnode *entry; int disable = 1; REG_TOPO_SLOCK(); TUNABLE_INT_FETCH("hw.regulator.disable_unused", &disable); TAILQ_FOREACH(entry, ®node_list, reglist_link) { if (entry->enable_cnt == 0 && entry->std_param.always_on == 0 && disable) { if (bootverbose) printf("regulator: shuting down %s\n", entry->name); regnode_stop(entry, 0); } } REG_TOPO_UNLOCK(); } /* * sysctl handler */ static int regnode_uvolt_sysctl(SYSCTL_HANDLER_ARGS) { struct regnode *regnode = arg1; int rv, uvolt; if (regnode->std_param.min_uvolt == regnode->std_param.max_uvolt) { uvolt = regnode->std_param.min_uvolt; } else { REG_TOPO_SLOCK(); if ((rv = regnode_get_voltage(regnode, &uvolt)) != 0) { REG_TOPO_UNLOCK(); return (rv); } REG_TOPO_UNLOCK(); } return sysctl_handle_int(oidp, &uvolt, sizeof(uvolt), req); } /* ---------------------------------------------------------------------------- * * Default regulator methods for base class. * */ static int regnode_method_enable(struct regnode *regnode, bool enable, int *udelay) { if (!enable) return (ENXIO); *udelay = 0; return (0); } static int regnode_method_status(struct regnode *regnode, int *status) { *status = REGULATOR_STATUS_ENABLED; return (0); } static int regnode_method_set_voltage(struct regnode *regnode, int min_uvolt, int max_uvolt, int *udelay) { if ((min_uvolt > regnode->std_param.max_uvolt) || (max_uvolt < regnode->std_param.min_uvolt)) return (ERANGE); *udelay = 0; return (0); } static int regnode_method_get_voltage(struct regnode *regnode, int *uvolt) { return (regnode->std_param.min_uvolt + (regnode->std_param.max_uvolt - regnode->std_param.min_uvolt) / 2); } /* ---------------------------------------------------------------------------- * * Internal functions. * */ static struct regnode * regnode_find_by_name(const char *name) { struct regnode *entry; REG_TOPO_ASSERT(); TAILQ_FOREACH(entry, ®node_list, reglist_link) { if (strcmp(entry->name, name) == 0) return (entry); } return (NULL); } static struct regnode * regnode_find_by_id(device_t dev, intptr_t id) { struct regnode *entry; REG_TOPO_ASSERT(); TAILQ_FOREACH(entry, ®node_list, reglist_link) { if ((entry->pdev == dev) && (entry->id == id)) return (entry); } return (NULL); } /* * Create and initialize regulator object, but do not register it. */ struct regnode * regnode_create(device_t pdev, regnode_class_t regnode_class, struct regnode_init_def *def) { struct regnode *regnode; struct sysctl_oid *regnode_oid; KASSERT(def->name != NULL, ("regulator name is NULL")); KASSERT(def->name[0] != '\0', ("regulator name is empty")); REG_TOPO_SLOCK(); if (regnode_find_by_name(def->name) != NULL) panic("Duplicated regulator registration: %s\n", def->name); REG_TOPO_UNLOCK(); /* Create object and initialize it. */ regnode = malloc(sizeof(struct regnode), M_REGULATOR, M_WAITOK | M_ZERO); kobj_init((kobj_t)regnode, (kobj_class_t)regnode_class); sx_init(®node->lock, "Regulator node lock"); /* Allocate softc if required. */ if (regnode_class->size > 0) { regnode->softc = malloc(regnode_class->size, M_REGULATOR, M_WAITOK | M_ZERO); } /* Copy all strings unless they're flagged as static. */ if (def->flags & REGULATOR_FLAGS_STATIC) { regnode->name = def->name; regnode->parent_name = def->parent_name; } else { regnode->name = strdup(def->name, M_REGULATOR); if (def->parent_name != NULL) regnode->parent_name = strdup(def->parent_name, M_REGULATOR); } /* Rest of init. */ TAILQ_INIT(®node->consumers_list); regnode->id = def->id; regnode->pdev = pdev; regnode->flags = def->flags; regnode->parent = NULL; regnode->std_param = def->std_param; #ifdef FDT regnode->ofw_node = def->ofw_node; #endif sysctl_ctx_init(®node->sysctl_ctx); regnode_oid = SYSCTL_ADD_NODE(®node->sysctl_ctx, SYSCTL_STATIC_CHILDREN(_hw_regulator), OID_AUTO, regnode->name, CTLFLAG_RD, 0, "A regulator node"); SYSCTL_ADD_INT(®node->sysctl_ctx, SYSCTL_CHILDREN(regnode_oid), OID_AUTO, "min_uvolt", CTLFLAG_RD, ®node->std_param.min_uvolt, 0, "Minimal voltage (in uV)"); SYSCTL_ADD_INT(®node->sysctl_ctx, SYSCTL_CHILDREN(regnode_oid), OID_AUTO, "max_uvolt", CTLFLAG_RD, ®node->std_param.max_uvolt, 0, "Maximal voltage (in uV)"); SYSCTL_ADD_INT(®node->sysctl_ctx, SYSCTL_CHILDREN(regnode_oid), OID_AUTO, "min_uamp", CTLFLAG_RD, ®node->std_param.min_uamp, 0, "Minimal amperage (in uA)"); SYSCTL_ADD_INT(®node->sysctl_ctx, SYSCTL_CHILDREN(regnode_oid), OID_AUTO, "max_uamp", CTLFLAG_RD, ®node->std_param.max_uamp, 0, "Maximal amperage (in uA)"); SYSCTL_ADD_INT(®node->sysctl_ctx, SYSCTL_CHILDREN(regnode_oid), OID_AUTO, "ramp_delay", CTLFLAG_RD, ®node->std_param.ramp_delay, 0, "Ramp delay (in uV/us)"); SYSCTL_ADD_INT(®node->sysctl_ctx, SYSCTL_CHILDREN(regnode_oid), OID_AUTO, "enable_delay", CTLFLAG_RD, ®node->std_param.enable_delay, 0, "Enable delay (in us)"); SYSCTL_ADD_INT(®node->sysctl_ctx, SYSCTL_CHILDREN(regnode_oid), OID_AUTO, "enable_cnt", CTLFLAG_RD, ®node->enable_cnt, 0, "The regulator enable counter"); SYSCTL_ADD_INT(®node->sysctl_ctx, SYSCTL_CHILDREN(regnode_oid), OID_AUTO, "boot_on", CTLFLAG_RD, (int *) ®node->std_param.boot_on, 0, "Is enabled on boot"); SYSCTL_ADD_INT(®node->sysctl_ctx, SYSCTL_CHILDREN(regnode_oid), OID_AUTO, "always_on", CTLFLAG_RD, (int *)®node->std_param.always_on, 0, "Is always enabled"); SYSCTL_ADD_PROC(®node->sysctl_ctx, SYSCTL_CHILDREN(regnode_oid), OID_AUTO, "uvolt", CTLTYPE_INT | CTLFLAG_RD, regnode, 0, regnode_uvolt_sysctl, "I", "Current voltage (in uV)"); return (regnode); } /* Register regulator object. */ struct regnode * regnode_register(struct regnode *regnode) { int rv; #ifdef FDT if (regnode->ofw_node <= 0) regnode->ofw_node = ofw_bus_get_node(regnode->pdev); if (regnode->ofw_node <= 0) return (NULL); #endif rv = REGNODE_INIT(regnode); if (rv != 0) { printf("REGNODE_INIT failed: %d\n", rv); return (NULL); } REG_TOPO_XLOCK(); TAILQ_INSERT_TAIL(®node_list, regnode, reglist_link); REG_TOPO_UNLOCK(); #ifdef FDT OF_device_register_xref(OF_xref_from_node(regnode->ofw_node), regnode->pdev); #endif return (regnode); } static int regnode_resolve_parent(struct regnode *regnode) { /* All ready resolved or no parent? */ if ((regnode->parent != NULL) || (regnode->parent_name == NULL)) return (0); regnode->parent = regnode_find_by_name(regnode->parent_name); if (regnode->parent == NULL) return (ENODEV); return (0); } static void regnode_delay(int usec) { int ticks; if (usec == 0) return; ticks = (usec * hz + 999999) / 1000000; if (cold || ticks < 2) DELAY(usec); else pause("REGULATOR", ticks); } /* -------------------------------------------------------------------------- * * Regulator providers interface * */ const char * regnode_get_name(struct regnode *regnode) { return (regnode->name); } const char * regnode_get_parent_name(struct regnode *regnode) { return (regnode->parent_name); } int regnode_get_flags(struct regnode *regnode) { return (regnode->flags); } void * regnode_get_softc(struct regnode *regnode) { return (regnode->softc); } device_t regnode_get_device(struct regnode *regnode) { return (regnode->pdev); } struct regnode_std_param *regnode_get_stdparam(struct regnode *regnode) { return (®node->std_param); } void regnode_topo_unlock(void) { REG_TOPO_UNLOCK(); } void regnode_topo_xlock(void) { REG_TOPO_XLOCK(); } void regnode_topo_slock(void) { REG_TOPO_SLOCK(); } /* -------------------------------------------------------------------------- * * Real consumers executive * */ struct regnode * regnode_get_parent(struct regnode *regnode) { int rv; REG_TOPO_ASSERT(); rv = regnode_resolve_parent(regnode); if (rv != 0) return (NULL); return (regnode->parent); } /* * Enable regulator. */ int regnode_enable(struct regnode *regnode) { int udelay; int rv; REG_TOPO_ASSERT(); /* Enable regulator for each node in chain, starting from source. */ rv = regnode_resolve_parent(regnode); if (rv != 0) return (rv); if (regnode->parent != NULL) { rv = regnode_enable(regnode->parent); if (rv != 0) return (rv); } /* Handle this node. */ REGNODE_XLOCK(regnode); if (regnode->enable_cnt == 0) { rv = REGNODE_ENABLE(regnode, true, &udelay); if (rv != 0) { REGNODE_UNLOCK(regnode); return (rv); } regnode_delay(udelay); } regnode->enable_cnt++; REGNODE_UNLOCK(regnode); return (0); } /* * Disable regulator. */ int regnode_disable(struct regnode *regnode) { int udelay; int rv; REG_TOPO_ASSERT(); rv = 0; REGNODE_XLOCK(regnode); /* Disable regulator for each node in chain, starting from consumer. */ if ((regnode->enable_cnt == 1) && ((regnode->flags & REGULATOR_FLAGS_NOT_DISABLE) == 0)) { rv = REGNODE_ENABLE(regnode, false, &udelay); if (rv != 0) { REGNODE_UNLOCK(regnode); return (rv); } regnode_delay(udelay); } regnode->enable_cnt--; REGNODE_UNLOCK(regnode); rv = regnode_resolve_parent(regnode); if (rv != 0) return (rv); if (regnode->parent != NULL) rv = regnode_disable(regnode->parent); return (rv); } /* * Stop regulator. */ int regnode_stop(struct regnode *regnode, int depth) { int udelay; int rv; REG_TOPO_ASSERT(); rv = 0; REGNODE_XLOCK(regnode); /* The first node must not be enabled. */ if ((regnode->enable_cnt != 0) && (depth == 0)) { REGNODE_UNLOCK(regnode); return (EBUSY); } /* Disable regulator for each node in chain, starting from consumer */ if ((regnode->enable_cnt == 0) && ((regnode->flags & REGULATOR_FLAGS_NOT_DISABLE) == 0)) { rv = REGNODE_ENABLE(regnode, false, &udelay); if (rv != 0) { REGNODE_UNLOCK(regnode); return (rv); } regnode_delay(udelay); } REGNODE_UNLOCK(regnode); rv = regnode_resolve_parent(regnode); if (rv != 0) return (rv); if (regnode->parent != NULL) rv = regnode_stop(regnode->parent, depth + 1); return (rv); } /* * Get regulator status. (REGULATOR_STATUS_*) */ int regnode_status(struct regnode *regnode, int *status) { int rv; REG_TOPO_ASSERT(); REGNODE_XLOCK(regnode); rv = REGNODE_STATUS(regnode, status); REGNODE_UNLOCK(regnode); return (rv); } /* * Get actual regulator voltage. */ int regnode_get_voltage(struct regnode *regnode, int *uvolt) { int rv; REG_TOPO_ASSERT(); REGNODE_XLOCK(regnode); rv = REGNODE_GET_VOLTAGE(regnode, uvolt); REGNODE_UNLOCK(regnode); /* Pass call into parent, if regulator is in bypass mode. */ if (rv == ENOENT) { rv = regnode_resolve_parent(regnode); if (rv != 0) return (rv); if (regnode->parent != NULL) rv = regnode_get_voltage(regnode->parent, uvolt); } return (rv); } /* * Set regulator voltage. */ int regnode_set_voltage(struct regnode *regnode, int min_uvolt, int max_uvolt) { int udelay; int rv; REG_TOPO_ASSERT(); REGNODE_XLOCK(regnode); rv = REGNODE_SET_VOLTAGE(regnode, min_uvolt, max_uvolt, &udelay); if (rv == 0) regnode_delay(udelay); REGNODE_UNLOCK(regnode); return (rv); } /* * Consumer variant of regnode_set_voltage(). */ static int regnode_set_voltage_checked(struct regnode *regnode, struct regulator *reg, int min_uvolt, int max_uvolt) { int udelay; int all_max_uvolt; int all_min_uvolt; struct regulator *tmp; int rv; REG_TOPO_ASSERT(); REGNODE_XLOCK(regnode); /* Return error if requested range is outside of regulator range. */ if ((min_uvolt > regnode->std_param.max_uvolt) || (max_uvolt < regnode->std_param.min_uvolt)) { REGNODE_UNLOCK(regnode); return (ERANGE); } /* Get actual voltage range for all consumers. */ all_min_uvolt = regnode->std_param.min_uvolt; all_max_uvolt = regnode->std_param.max_uvolt; TAILQ_FOREACH(tmp, ®node->consumers_list, link) { /* Don't take requestor in account. */ if (tmp == reg) continue; if (all_min_uvolt < tmp->min_uvolt) all_min_uvolt = tmp->min_uvolt; if (all_max_uvolt > tmp->max_uvolt) all_max_uvolt = tmp->max_uvolt; } /* Test if request fits to actual contract. */ if ((min_uvolt > all_max_uvolt) || (max_uvolt < all_min_uvolt)) { REGNODE_UNLOCK(regnode); return (ERANGE); } /* Adjust new range.*/ if (min_uvolt < all_min_uvolt) min_uvolt = all_min_uvolt; if (max_uvolt > all_max_uvolt) max_uvolt = all_max_uvolt; rv = REGNODE_SET_VOLTAGE(regnode, min_uvolt, max_uvolt, &udelay); regnode_delay(udelay); REGNODE_UNLOCK(regnode); return (rv); } #ifdef FDT phandle_t regnode_get_ofw_node(struct regnode *regnode) { return (regnode->ofw_node); } #endif /* -------------------------------------------------------------------------- * * Regulator consumers interface. * */ /* Helper function for regulator_get*() */ static regulator_t regulator_create(struct regnode *regnode, device_t cdev) { struct regulator *reg; REG_TOPO_ASSERT(); reg = malloc(sizeof(struct regulator), M_REGULATOR, M_WAITOK | M_ZERO); reg->cdev = cdev; reg->regnode = regnode; reg->enable_cnt = 0; REGNODE_XLOCK(regnode); regnode->ref_cnt++; TAILQ_INSERT_TAIL(®node->consumers_list, reg, link); reg ->min_uvolt = regnode->std_param.min_uvolt; reg ->max_uvolt = regnode->std_param.max_uvolt; REGNODE_UNLOCK(regnode); return (reg); } int regulator_enable(regulator_t reg) { int rv; struct regnode *regnode; regnode = reg->regnode; KASSERT(regnode->ref_cnt > 0, ("Attempt to access unreferenced regulator: %s\n", regnode->name)); REG_TOPO_SLOCK(); rv = regnode_enable(regnode); if (rv == 0) reg->enable_cnt++; REG_TOPO_UNLOCK(); return (rv); } int regulator_disable(regulator_t reg) { int rv; struct regnode *regnode; regnode = reg->regnode; KASSERT(regnode->ref_cnt > 0, ("Attempt to access unreferenced regulator: %s\n", regnode->name)); KASSERT(reg->enable_cnt > 0, ("Attempt to disable already disabled regulator: %s\n", regnode->name)); REG_TOPO_SLOCK(); rv = regnode_disable(regnode); if (rv == 0) reg->enable_cnt--; REG_TOPO_UNLOCK(); return (rv); } int regulator_stop(regulator_t reg) { int rv; struct regnode *regnode; regnode = reg->regnode; KASSERT(regnode->ref_cnt > 0, ("Attempt to access unreferenced regulator: %s\n", regnode->name)); KASSERT(reg->enable_cnt == 0, ("Attempt to stop already enabled regulator: %s\n", regnode->name)); REG_TOPO_SLOCK(); rv = regnode_stop(regnode, 0); REG_TOPO_UNLOCK(); return (rv); } int regulator_status(regulator_t reg, int *status) { int rv; struct regnode *regnode; regnode = reg->regnode; KASSERT(regnode->ref_cnt > 0, ("Attempt to access unreferenced regulator: %s\n", regnode->name)); REG_TOPO_SLOCK(); rv = regnode_status(regnode, status); REG_TOPO_UNLOCK(); return (rv); } int regulator_get_voltage(regulator_t reg, int *uvolt) { int rv; struct regnode *regnode; regnode = reg->regnode; KASSERT(regnode->ref_cnt > 0, ("Attempt to access unreferenced regulator: %s\n", regnode->name)); REG_TOPO_SLOCK(); rv = regnode_get_voltage(regnode, uvolt); REG_TOPO_UNLOCK(); return (rv); } int regulator_set_voltage(regulator_t reg, int min_uvolt, int max_uvolt) { struct regnode *regnode; int rv; regnode = reg->regnode; KASSERT(regnode->ref_cnt > 0, ("Attempt to access unreferenced regulator: %s\n", regnode->name)); REG_TOPO_SLOCK(); rv = regnode_set_voltage_checked(regnode, reg, min_uvolt, max_uvolt); if (rv == 0) { reg->min_uvolt = min_uvolt; reg->max_uvolt = max_uvolt; } REG_TOPO_UNLOCK(); return (rv); } const char * regulator_get_name(regulator_t reg) { struct regnode *regnode; regnode = reg->regnode; KASSERT(regnode->ref_cnt > 0, ("Attempt to access unreferenced regulator: %s\n", regnode->name)); return (regnode->name); } int regulator_get_by_name(device_t cdev, const char *name, regulator_t *reg) { struct regnode *regnode; REG_TOPO_SLOCK(); regnode = regnode_find_by_name(name); if (regnode == NULL) { REG_TOPO_UNLOCK(); return (ENODEV); } *reg = regulator_create(regnode, cdev); REG_TOPO_UNLOCK(); return (0); } int regulator_get_by_id(device_t cdev, device_t pdev, intptr_t id, regulator_t *reg) { struct regnode *regnode; REG_TOPO_SLOCK(); regnode = regnode_find_by_id(pdev, id); if (regnode == NULL) { REG_TOPO_UNLOCK(); return (ENODEV); } *reg = regulator_create(regnode, cdev); REG_TOPO_UNLOCK(); return (0); } int regulator_release(regulator_t reg) { struct regnode *regnode; regnode = reg->regnode; KASSERT(regnode->ref_cnt > 0, ("Attempt to access unreferenced regulator: %s\n", regnode->name)); REG_TOPO_SLOCK(); while (reg->enable_cnt > 0) { regnode_disable(regnode); reg->enable_cnt--; } REGNODE_XLOCK(regnode); TAILQ_REMOVE(®node->consumers_list, reg, link); regnode->ref_cnt--; REGNODE_UNLOCK(regnode); REG_TOPO_UNLOCK(); free(reg, M_REGULATOR); return (0); } #ifdef FDT /* Default DT mapper. */ int regdev_default_ofw_map(device_t dev, phandle_t xref, int ncells, pcell_t *cells, intptr_t *id) { if (ncells == 0) *id = 1; else if (ncells == 1) *id = cells[0]; else return (ERANGE); return (0); } int regulator_parse_ofw_stdparam(device_t pdev, phandle_t node, struct regnode_init_def *def) { phandle_t supply_xref; struct regnode_std_param *par; int rv; par = &def->std_param; - rv = OF_getprop_alloc(node, "regulator-name", 1, + rv = OF_getprop_alloc(node, "regulator-name", (void **)&def->name); if (rv <= 0) { device_printf(pdev, "%s: Missing regulator name\n", __func__); return (ENXIO); } rv = OF_getencprop(node, "regulator-min-microvolt", &par->min_uvolt, sizeof(par->min_uvolt)); if (rv <= 0) par->min_uvolt = 0; rv = OF_getencprop(node, "regulator-max-microvolt", &par->max_uvolt, sizeof(par->max_uvolt)); if (rv <= 0) par->max_uvolt = 0; rv = OF_getencprop(node, "regulator-min-microamp", &par->min_uamp, sizeof(par->min_uamp)); if (rv <= 0) par->min_uamp = 0; rv = OF_getencprop(node, "regulator-max-microamp", &par->max_uamp, sizeof(par->max_uamp)); if (rv <= 0) par->max_uamp = 0; rv = OF_getencprop(node, "regulator-ramp-delay", &par->ramp_delay, sizeof(par->ramp_delay)); if (rv <= 0) par->ramp_delay = 0; rv = OF_getencprop(node, "regulator-enable-ramp-delay", &par->enable_delay, sizeof(par->enable_delay)); if (rv <= 0) par->enable_delay = 0; if (OF_hasprop(node, "regulator-boot-on")) par->boot_on = 1; if (OF_hasprop(node, "regulator-always-on")) par->always_on = 1; if (OF_hasprop(node, "enable-active-high")) par->enable_active_high = 1; rv = OF_getencprop(node, "vin-supply", &supply_xref, sizeof(supply_xref)); if (rv >= 0) { - rv = OF_getprop_alloc(supply_xref, "regulator-name", 1, + rv = OF_getprop_alloc(supply_xref, "regulator-name", (void **)&def->parent_name); if (rv <= 0) def->parent_name = NULL; } return (0); } int regulator_get_by_ofw_property(device_t cdev, phandle_t cnode, char *name, regulator_t *reg) { phandle_t *cells; device_t regdev; int ncells, rv; intptr_t id; *reg = NULL; if (cnode <= 0) cnode = ofw_bus_get_node(cdev); if (cnode <= 0) { device_printf(cdev, "%s called on not ofw based device\n", __func__); return (ENXIO); } cells = NULL; ncells = OF_getencprop_alloc(cnode, name, sizeof(*cells), (void **)&cells); if (ncells <= 0) return (ENXIO); /* Translate xref to device */ regdev = OF_device_from_xref(cells[0]); if (regdev == NULL) { OF_prop_free(cells); return (ENODEV); } /* Map regulator to number */ rv = REGDEV_MAP(regdev, cells[0], ncells - 1, cells + 1, &id); OF_prop_free(cells); if (rv != 0) return (rv); return (regulator_get_by_id(cdev, regdev, id, reg)); } #endif /* -------------------------------------------------------------------------- * * Regulator utility functions. * */ /* Convert raw selector value to real voltage */ int regulator_range_sel8_to_volt(struct regulator_range *ranges, int nranges, uint8_t sel, int *volt) { struct regulator_range *range; int i; if (nranges == 0) panic("Voltage regulator have zero ranges\n"); for (i = 0; i < nranges ; i++) { range = ranges + i; if (!(sel >= range->min_sel && sel <= range->max_sel)) continue; sel -= range->min_sel; *volt = range->min_uvolt + sel * range->step_uvolt; return (0); } return (ERANGE); } int regulator_range_volt_to_sel8(struct regulator_range *ranges, int nranges, int min_uvolt, int max_uvolt, uint8_t *out_sel) { struct regulator_range *range; uint8_t sel; int uvolt; int rv, i; if (nranges == 0) panic("Voltage regulator have zero ranges\n"); for (i = 0; i < nranges; i++) { range = ranges + i; uvolt = range->min_uvolt + (range->max_sel - range->min_sel) * range->step_uvolt; if ((min_uvolt > uvolt) || (max_uvolt < range->min_uvolt)) continue; if (min_uvolt <= range->min_uvolt) min_uvolt = range->min_uvolt; /* if step == 0 -> fixed voltage range. */ if (range->step_uvolt == 0) sel = 0; else sel = DIV_ROUND_UP(min_uvolt - range->min_uvolt, range->step_uvolt); sel += range->min_sel; break; } if (i >= nranges) return (ERANGE); /* Verify new settings. */ rv = regulator_range_sel8_to_volt(ranges, nranges, sel, &uvolt); if (rv != 0) return (rv); if ((uvolt < min_uvolt) || (uvolt > max_uvolt)) return (ERANGE); *out_sel = sel; return (0); } Index: head/sys/dev/fdt/fdt_pinctrl.c =================================================================== --- head/sys/dev/fdt/fdt_pinctrl.c (revision 332309) +++ head/sys/dev/fdt/fdt_pinctrl.c (revision 332310) @@ -1,150 +1,150 @@ /*- * Copyright (c) 2014 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. * * $FreeBSD$ */ #include #include #include #include #include "fdt_pinctrl_if.h" #include #include int fdt_pinctrl_configure(device_t client, u_int index) { device_t pinctrl; phandle_t *configs; int i, nconfigs; char name[16]; snprintf(name, sizeof(name), "pinctrl-%u", index); nconfigs = OF_getencprop_alloc(ofw_bus_get_node(client), name, sizeof(*configs), (void **)&configs); if (nconfigs < 0) return (ENOENT); if (nconfigs == 0) return (0); /* Empty property is documented as valid. */ for (i = 0; i < nconfigs; i++) { if ((pinctrl = OF_device_from_xref(configs[i])) != NULL) FDT_PINCTRL_CONFIGURE(pinctrl, configs[i]); } OF_prop_free(configs); return (0); } int fdt_pinctrl_configure_by_name(device_t client, const char * name) { char * names; int i, offset, nameslen; nameslen = OF_getprop_alloc(ofw_bus_get_node(client), "pinctrl-names", - sizeof(*names), (void **)&names); + (void **)&names); if (nameslen <= 0) return (ENOENT); for (i = 0, offset = 0; offset < nameslen; i++) { if (strcmp(name, &names[offset]) == 0) break; offset += strlen(&names[offset]) + 1; } OF_prop_free(names); if (offset < nameslen) return (fdt_pinctrl_configure(client, i)); else return (ENOENT); } static int pinctrl_register_children(device_t pinctrl, phandle_t parent, const char *pinprop) { phandle_t node; /* * Recursively descend from parent, looking for nodes that have the * given property, and associate the pinctrl device_t with each one. */ for (node = OF_child(parent); node != 0; node = OF_peer(node)) { pinctrl_register_children(pinctrl, node, pinprop); if (pinprop == NULL || OF_hasprop(node, pinprop)) { OF_device_register_xref(OF_xref_from_node(node), pinctrl); } } return (0); } int fdt_pinctrl_register(device_t pinctrl, const char *pinprop) { phandle_t node; node = ofw_bus_get_node(pinctrl); OF_device_register_xref(OF_xref_from_node(node), pinctrl); return (pinctrl_register_children(pinctrl, node, pinprop)); } static int pinctrl_configure_children(device_t pinctrl, phandle_t parent) { phandle_t node, *configs; int i, nconfigs; for (node = OF_child(parent); node != 0; node = OF_peer(node)) { if (!ofw_bus_node_status_okay(node)) continue; pinctrl_configure_children(pinctrl, node); nconfigs = OF_getencprop_alloc(node, "pinctrl-0", sizeof(*configs), (void **)&configs); if (nconfigs <= 0) continue; if (bootverbose) { char name[32]; OF_getprop(node, "name", &name, sizeof(name)); printf("Processing %d pin-config node(s) in pinctrl-0 for %s\n", nconfigs, name); } for (i = 0; i < nconfigs; i++) { if (OF_device_from_xref(configs[i]) == pinctrl) FDT_PINCTRL_CONFIGURE(pinctrl, configs[i]); } OF_prop_free(configs); } return (0); } int fdt_pinctrl_configure_tree(device_t pinctrl) { return (pinctrl_configure_children(pinctrl, OF_peer(0))); } Index: head/sys/dev/fdt/fdt_slicer.c =================================================================== --- head/sys/dev/fdt/fdt_slicer.c (revision 332309) +++ head/sys/dev/fdt/fdt_slicer.c (revision 332310) @@ -1,140 +1,140 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2012 Semihalf. * 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$"); #include #include #include #include #include #include #include #ifdef DEBUG #define debugf(fmt, args...) do { printf("%s(): ", __func__); \ printf(fmt,##args); } while (0) #else #define debugf(fmt, args...) #endif static int fdt_flash_fill_slices(device_t dev, const char *provider, struct flash_slice *slices, int *slices_num); static void fdt_slicer_init(void); static int fdt_flash_fill_slices(device_t dev, const char *provider __unused, struct flash_slice *slices, int *slices_num) { char *slice_name; phandle_t dt_node, dt_child; u_long base, size; int i; ssize_t name_len; /* * We assume the caller provides buffer for FLASH_SLICES_MAX_NUM * flash_slice structures. */ if (slices == NULL) { *slices_num = 0; return (ENOMEM); } dt_node = ofw_bus_get_node(dev); for (dt_child = OF_child(dt_node), i = 0; dt_child != 0; dt_child = OF_peer(dt_child)) { if (i == FLASH_SLICES_MAX_NUM) { debugf("not enough buffer for slice i=%d\n", i); break; } /* * Retrieve start and size of the slice. */ if (fdt_regsize(dt_child, &base, &size) != 0) { debugf("error during processing reg property, i=%d\n", i); continue; } if (size == 0) { debugf("slice i=%d with no size\n", i); continue; } /* * Retrieve label. */ - name_len = OF_getprop_alloc(dt_child, "label", sizeof(char), + name_len = OF_getprop_alloc(dt_child, "label", (void **)&slice_name); if (name_len <= 0) { /* Use node name if no label defined */ name_len = OF_getprop_alloc(dt_child, "name", - sizeof(char), (void **)&slice_name); + (void **)&slice_name); if (name_len <= 0) { debugf("slice i=%d with no name\n", i); slice_name = NULL; } } /* * Fill slice entry data. */ slices[i].base = base; slices[i].size = size; slices[i].label = slice_name; i++; } *slices_num = i; return (0); } static void fdt_slicer_init(void) { flash_register_slicer(fdt_flash_fill_slices, FLASH_SLICES_TYPE_NAND, FALSE); flash_register_slicer(fdt_flash_fill_slices, FLASH_SLICES_TYPE_CFI, FALSE); flash_register_slicer(fdt_flash_fill_slices, FLASH_SLICES_TYPE_SPI, FALSE); } /* * Must be initialized after GEOM classes (SI_SUB_DRIVERS/SI_ORDER_FIRST), * i. e. after g_init() is called, due to the use of the GEOM topology_lock * in flash_register_slicer(). However, must be before SI_SUB_CONFIGURE. */ SYSINIT(fdt_slicer_rootconf, SI_SUB_DRIVERS, SI_ORDER_SECOND, fdt_slicer_init, NULL); Index: head/sys/dev/gpio/gpiokeys.c =================================================================== --- head/sys/dev/gpio/gpiokeys.c (revision 332309) +++ head/sys/dev/gpio/gpiokeys.c (revision 332310) @@ -1,1017 +1,1017 @@ /*- * Copyright (c) 2015-2016 Oleksandr Tymoshenko * 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$"); #include "opt_platform.h" #include "opt_kbd.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define KBD_DRIVER_NAME "gpiokeys" #define GPIOKEYS_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) #define GPIOKEYS_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) #define GPIOKEYS_LOCK_INIT(_sc) \ mtx_init(&_sc->sc_mtx, device_get_nameunit((_sc)->sc_dev), \ "gpiokeys", MTX_DEF) #define GPIOKEYS_LOCK_DESTROY(_sc) mtx_destroy(&(_sc)->sc_mtx); #define GPIOKEYS_ASSERT_LOCKED(_sc) mtx_assert(&(_sc)->sc_mtx, MA_OWNED) #define GPIOKEY_LOCK(_key) mtx_lock(&(_key)->mtx) #define GPIOKEY_UNLOCK(_key) mtx_unlock(&(_key)->mtx) #define GPIOKEY_LOCK_INIT(_key) \ mtx_init(&(_key)->mtx, "gpiokey", "gpiokey", MTX_DEF) #define GPIOKEY_LOCK_DESTROY(_key) mtx_destroy(&(_key)->mtx); #define KEY_PRESS 0 #define KEY_RELEASE 0x80 #define SCAN_PRESS 0 #define SCAN_RELEASE 0x80 #define SCAN_CHAR(c) ((c) & 0x7f) #define GPIOKEYS_GLOBAL_NMOD 8 /* units */ #define GPIOKEYS_GLOBAL_NKEYCODE 6 /* units */ #define GPIOKEYS_GLOBAL_IN_BUF_SIZE (2*(GPIOKEYS_GLOBAL_NMOD + (2*GPIOKEYS_GLOBAL_NKEYCODE))) /* bytes */ #define GPIOKEYS_GLOBAL_IN_BUF_FULL (GPIOKEYS_GLOBAL_IN_BUF_SIZE / 2) /* bytes */ #define GPIOKEYS_GLOBAL_NFKEY (sizeof(fkey_tab)/sizeof(fkey_tab[0])) /* units */ #define GPIOKEYS_GLOBAL_BUFFER_SIZE 64 /* bytes */ #define AUTOREPEAT_DELAY 250 #define AUTOREPEAT_REPEAT 34 struct gpiokeys_softc; struct gpiokey { struct gpiokeys_softc *parent_sc; gpio_pin_t pin; int irq_rid; struct resource *irq_res; void *intr_hl; struct mtx mtx; uint32_t keycode; int autorepeat; struct callout debounce_callout; struct callout repeat_callout; int repeat_delay; int repeat; int debounce_interval; }; struct gpiokeys_softc { device_t sc_dev; struct mtx sc_mtx; struct gpiokey *sc_keys; int sc_total_keys; keyboard_t sc_kbd; keymap_t sc_keymap; accentmap_t sc_accmap; fkeytab_t sc_fkeymap[GPIOKEYS_GLOBAL_NFKEY]; uint32_t sc_input[GPIOKEYS_GLOBAL_IN_BUF_SIZE]; /* input buffer */ uint32_t sc_time_ms; #define GPIOKEYS_GLOBAL_FLAG_POLLING 0x00000002 uint32_t sc_flags; /* flags */ int sc_mode; /* input mode (K_XLATE,K_RAW,K_CODE) */ int sc_state; /* shift/lock key state */ int sc_accents; /* accent key index (> 0) */ int sc_kbd_size; uint16_t sc_inputs; uint16_t sc_inputhead; uint16_t sc_inputtail; uint8_t sc_kbd_id; }; /* gpio-keys device */ static int gpiokeys_probe(device_t); static int gpiokeys_attach(device_t); static int gpiokeys_detach(device_t); /* kbd methods prototypes */ static int gpiokeys_set_typematic(keyboard_t *, int); static uint32_t gpiokeys_read_char(keyboard_t *, int); static void gpiokeys_clear_state(keyboard_t *); static int gpiokeys_ioctl(keyboard_t *, u_long, caddr_t); static int gpiokeys_enable(keyboard_t *); static int gpiokeys_disable(keyboard_t *); static void gpiokeys_event_keyinput(struct gpiokeys_softc *); static void gpiokeys_put_key(struct gpiokeys_softc *sc, uint32_t key) { GPIOKEYS_ASSERT_LOCKED(sc); if (sc->sc_inputs < GPIOKEYS_GLOBAL_IN_BUF_SIZE) { sc->sc_input[sc->sc_inputtail] = key; ++(sc->sc_inputs); ++(sc->sc_inputtail); if (sc->sc_inputtail >= GPIOKEYS_GLOBAL_IN_BUF_SIZE) { sc->sc_inputtail = 0; } } else { device_printf(sc->sc_dev, "input buffer is full\n"); } } static void gpiokeys_key_event(struct gpiokeys_softc *sc, uint16_t keycode, int pressed) { uint32_t key; key = keycode & SCAN_KEYCODE_MASK; if (!pressed) key |= KEY_RELEASE; GPIOKEYS_LOCK(sc); if (keycode & SCAN_PREFIX_E0) gpiokeys_put_key(sc, 0xe0); else if (keycode & SCAN_PREFIX_E1) gpiokeys_put_key(sc, 0xe1); gpiokeys_put_key(sc, key); GPIOKEYS_UNLOCK(sc); gpiokeys_event_keyinput(sc); } static void gpiokey_autorepeat(void *arg) { struct gpiokey *key; key = arg; if (key->keycode == GPIOKEY_NONE) return; gpiokeys_key_event(key->parent_sc, key->keycode, 1); callout_reset(&key->repeat_callout, key->repeat, gpiokey_autorepeat, key); } static void gpiokey_debounced_intr(void *arg) { struct gpiokey *key; bool active; key = arg; if (key->keycode == GPIOKEY_NONE) return; gpio_pin_is_active(key->pin, &active); if (active) { gpiokeys_key_event(key->parent_sc, key->keycode, 1); if (key->autorepeat) { callout_reset(&key->repeat_callout, key->repeat_delay, gpiokey_autorepeat, key); } } else { if (key->autorepeat && callout_pending(&key->repeat_callout)) callout_stop(&key->repeat_callout); gpiokeys_key_event(key->parent_sc, key->keycode, 0); } } static void gpiokey_intr(void *arg) { struct gpiokey *key; int debounce_ticks; key = arg; GPIOKEY_LOCK(key); debounce_ticks = (hz * key->debounce_interval) / 1000; if (debounce_ticks == 0) debounce_ticks = 1; if (!callout_pending(&key->debounce_callout)) callout_reset(&key->debounce_callout, debounce_ticks, gpiokey_debounced_intr, key); GPIOKEY_UNLOCK(key); } static void gpiokeys_attach_key(struct gpiokeys_softc *sc, phandle_t node, struct gpiokey *key) { pcell_t prop; char *name; uint32_t code; int err; const char *key_name; GPIOKEY_LOCK_INIT(key); key->parent_sc = sc; callout_init_mtx(&key->debounce_callout, &key->mtx, 0); callout_init_mtx(&key->repeat_callout, &key->mtx, 0); name = NULL; - if (OF_getprop_alloc(node, "label", 1, (void **)&name) == -1) - OF_getprop_alloc(node, "name", 1, (void **)&name); + if (OF_getprop_alloc(node, "label", (void **)&name) == -1) + OF_getprop_alloc(node, "name", (void **)&name); if (name != NULL) key_name = name; else key_name = "unknown"; key->autorepeat = OF_hasprop(node, "autorepeat"); key->repeat_delay = (hz * AUTOREPEAT_DELAY) / 1000; if (key->repeat_delay == 0) key->repeat_delay = 1; key->repeat = (hz * AUTOREPEAT_REPEAT) / 1000; if (key->repeat == 0) key->repeat = 1; if ((OF_getprop(node, "debounce-interval", &prop, sizeof(prop))) > 0) key->debounce_interval = fdt32_to_cpu(prop); else key->debounce_interval = 5; if ((OF_getprop(node, "freebsd,code", &prop, sizeof(prop))) > 0) key->keycode = fdt32_to_cpu(prop); else if ((OF_getprop(node, "linux,code", &prop, sizeof(prop))) > 0) { code = fdt32_to_cpu(prop); key->keycode = gpiokey_map_linux_code(code); if (key->keycode == GPIOKEY_NONE) device_printf(sc->sc_dev, "<%s> failed to map linux,code value 0x%x\n", key_name, code); } else device_printf(sc->sc_dev, "<%s> no linux,code or freebsd,code property\n", key_name); err = gpio_pin_get_by_ofw_idx(sc->sc_dev, node, 0, &key->pin); if (err) { device_printf(sc->sc_dev, "<%s> failed to map pin\n", key_name); if (name) OF_prop_free(name); return; } key->irq_res = gpio_alloc_intr_resource(sc->sc_dev, &key->irq_rid, RF_ACTIVE, key->pin, GPIO_INTR_EDGE_BOTH); if (!key->irq_res) { device_printf(sc->sc_dev, "<%s> cannot allocate interrupt\n", key_name); gpio_pin_release(key->pin); key->pin = NULL; if (name) OF_prop_free(name); return; } if (bus_setup_intr(sc->sc_dev, key->irq_res, INTR_TYPE_MISC | INTR_MPSAFE, NULL, gpiokey_intr, key, &key->intr_hl) != 0) { device_printf(sc->sc_dev, "<%s> unable to setup the irq handler\n", key_name); bus_release_resource(sc->sc_dev, SYS_RES_IRQ, key->irq_rid, key->irq_res); gpio_pin_release(key->pin); key->pin = NULL; key->irq_res = NULL; if (name) OF_prop_free(name); return; } if (bootverbose) device_printf(sc->sc_dev, "<%s> code=%08x, autorepeat=%d, "\ "repeat=%d, repeat_delay=%d\n", key_name, key->keycode, key->autorepeat, key->repeat, key->repeat_delay); if (name) OF_prop_free(name); } static void gpiokeys_detach_key(struct gpiokeys_softc *sc, struct gpiokey *key) { GPIOKEY_LOCK(key); if (key->intr_hl) bus_teardown_intr(sc->sc_dev, key->irq_res, key->intr_hl); if (key->irq_res) bus_release_resource(sc->sc_dev, SYS_RES_IRQ, key->irq_rid, key->irq_res); if (callout_pending(&key->repeat_callout)) callout_drain(&key->repeat_callout); if (callout_pending(&key->debounce_callout)) callout_drain(&key->debounce_callout); if (key->pin) gpio_pin_release(key->pin); GPIOKEY_UNLOCK(key); GPIOKEY_LOCK_DESTROY(key); } static int gpiokeys_probe(device_t dev) { if (!ofw_bus_is_compatible(dev, "gpio-keys")) return (ENXIO); device_set_desc(dev, "GPIO keyboard"); return (0); } static int gpiokeys_attach(device_t dev) { int unit; struct gpiokeys_softc *sc; keyboard_t *kbd; phandle_t keys, child; int total_keys; if ((keys = ofw_bus_get_node(dev)) == -1) return (ENXIO); sc = device_get_softc(dev); sc->sc_dev = dev; kbd = &sc->sc_kbd; GPIOKEYS_LOCK_INIT(sc); unit = device_get_unit(dev); kbd_init_struct(kbd, KBD_DRIVER_NAME, KB_OTHER, unit, 0, 0, 0); kbd->kb_data = (void *)sc; sc->sc_mode = K_XLATE; sc->sc_keymap = key_map; sc->sc_accmap = accent_map; kbd_set_maps(kbd, &sc->sc_keymap, &sc->sc_accmap, sc->sc_fkeymap, GPIOKEYS_GLOBAL_NFKEY); KBD_FOUND_DEVICE(kbd); gpiokeys_clear_state(kbd); KBD_PROBE_DONE(kbd); KBD_INIT_DONE(kbd); if (kbd_register(kbd) < 0) { goto detach; } KBD_CONFIG_DONE(kbd); gpiokeys_enable(kbd); #ifdef KBD_INSTALL_CDEV if (kbd_attach(kbd)) { goto detach; } #endif if (bootverbose) { genkbd_diag(kbd, 1); } total_keys = 0; /* Traverse the 'gpio-keys' node and count keys */ for (child = OF_child(keys); child != 0; child = OF_peer(child)) { if (!OF_hasprop(child, "gpios")) continue; total_keys++; } if (total_keys) { sc->sc_keys = malloc(sizeof(struct gpiokey) * total_keys, M_DEVBUF, M_WAITOK | M_ZERO); sc->sc_total_keys = 0; /* Traverse the 'gpio-keys' node and count keys */ for (child = OF_child(keys); child != 0; child = OF_peer(child)) { if (!OF_hasprop(child, "gpios")) continue; gpiokeys_attach_key(sc, child ,&sc->sc_keys[sc->sc_total_keys]); sc->sc_total_keys++; } } return (0); detach: gpiokeys_detach(dev); return (ENXIO); } static int gpiokeys_detach(device_t dev) { struct gpiokeys_softc *sc; keyboard_t *kbd; int i; sc = device_get_softc(dev); for (i = 0; i < sc->sc_total_keys; i++) gpiokeys_detach_key(sc, &sc->sc_keys[i]); kbd = kbd_get_keyboard(kbd_find_keyboard(KBD_DRIVER_NAME, device_get_unit(dev))); #ifdef KBD_INSTALL_CDEV kbd_detach(kbd); #endif kbd_unregister(kbd); GPIOKEYS_LOCK_DESTROY(sc); if (sc->sc_keys) free(sc->sc_keys, M_DEVBUF); return (0); } /* early keyboard probe, not supported */ static int gpiokeys_configure(int flags) { return (0); } /* detect a keyboard, not used */ static int gpiokeys__probe(int unit, void *arg, int flags) { return (ENXIO); } /* reset and initialize the device, not used */ static int gpiokeys_init(int unit, keyboard_t **kbdp, void *arg, int flags) { return (ENXIO); } /* test the interface to the device, not used */ static int gpiokeys_test_if(keyboard_t *kbd) { return (0); } /* finish using this keyboard, not used */ static int gpiokeys_term(keyboard_t *kbd) { return (ENXIO); } /* keyboard interrupt routine, not used */ static int gpiokeys_intr(keyboard_t *kbd, void *arg) { return (0); } /* lock the access to the keyboard, not used */ static int gpiokeys_lock(keyboard_t *kbd, int lock) { return (1); } /* * Enable the access to the device; until this function is called, * the client cannot read from the keyboard. */ static int gpiokeys_enable(keyboard_t *kbd) { struct gpiokeys_softc *sc; sc = kbd->kb_data; GPIOKEYS_LOCK(sc); KBD_ACTIVATE(kbd); GPIOKEYS_UNLOCK(sc); return (0); } /* disallow the access to the device */ static int gpiokeys_disable(keyboard_t *kbd) { struct gpiokeys_softc *sc; sc = kbd->kb_data; GPIOKEYS_LOCK(sc); KBD_DEACTIVATE(kbd); GPIOKEYS_UNLOCK(sc); return (0); } static void gpiokeys_do_poll(struct gpiokeys_softc *sc, uint8_t wait) { KASSERT((sc->sc_flags & GPIOKEYS_GLOBAL_FLAG_POLLING) != 0, ("gpiokeys_do_poll called when not polling\n")); GPIOKEYS_ASSERT_LOCKED(sc); if (!kdb_active && !SCHEDULER_STOPPED()) { while (sc->sc_inputs == 0) { kern_yield(PRI_UNCHANGED); if (!wait) break; } return; } while ((sc->sc_inputs == 0) && wait) { printf("POLL!\n"); } } /* check if data is waiting */ static int gpiokeys_check(keyboard_t *kbd) { struct gpiokeys_softc *sc = kbd->kb_data; GPIOKEYS_ASSERT_LOCKED(sc); if (!KBD_IS_ACTIVE(kbd)) return (0); if (sc->sc_flags & GPIOKEYS_GLOBAL_FLAG_POLLING) gpiokeys_do_poll(sc, 0); if (sc->sc_inputs > 0) { return (1); } return (0); } /* check if char is waiting */ static int gpiokeys_check_char_locked(keyboard_t *kbd) { if (!KBD_IS_ACTIVE(kbd)) return (0); return (gpiokeys_check(kbd)); } static int gpiokeys_check_char(keyboard_t *kbd) { int result; struct gpiokeys_softc *sc = kbd->kb_data; GPIOKEYS_LOCK(sc); result = gpiokeys_check_char_locked(kbd); GPIOKEYS_UNLOCK(sc); return (result); } static int32_t gpiokeys_get_key(struct gpiokeys_softc *sc, uint8_t wait) { int32_t c; KASSERT((!kdb_active && !SCHEDULER_STOPPED()) || (sc->sc_flags & GPIOKEYS_GLOBAL_FLAG_POLLING) != 0, ("not polling in kdb or panic\n")); GPIOKEYS_ASSERT_LOCKED(sc); if (sc->sc_flags & GPIOKEYS_GLOBAL_FLAG_POLLING) gpiokeys_do_poll(sc, wait); if (sc->sc_inputs == 0) { c = -1; } else { c = sc->sc_input[sc->sc_inputhead]; --(sc->sc_inputs); ++(sc->sc_inputhead); if (sc->sc_inputhead >= GPIOKEYS_GLOBAL_IN_BUF_SIZE) { sc->sc_inputhead = 0; } } return (c); } /* read one byte from the keyboard if it's allowed */ static int gpiokeys_read(keyboard_t *kbd, int wait) { struct gpiokeys_softc *sc = kbd->kb_data; int32_t keycode; if (!KBD_IS_ACTIVE(kbd)) return (-1); /* XXX */ keycode = gpiokeys_get_key(sc, (wait == FALSE) ? 0 : 1); if (!KBD_IS_ACTIVE(kbd) || (keycode == -1)) return (-1); ++(kbd->kb_count); return (keycode); } /* read char from the keyboard */ static uint32_t gpiokeys_read_char_locked(keyboard_t *kbd, int wait) { struct gpiokeys_softc *sc = kbd->kb_data; uint32_t action; uint32_t keycode; if (!KBD_IS_ACTIVE(kbd)) return (NOKEY); next_code: /* see if there is something in the keyboard port */ /* XXX */ keycode = gpiokeys_get_key(sc, (wait == FALSE) ? 0 : 1); ++kbd->kb_count; /* return the byte as is for the K_RAW mode */ if (sc->sc_mode == K_RAW) { return (keycode); } /* return the key code in the K_CODE mode */ /* XXX: keycode |= SCAN_RELEASE; */ if (sc->sc_mode == K_CODE) { return (keycode); } /* keycode to key action */ action = genkbd_keyaction(kbd, SCAN_CHAR(keycode), (keycode & SCAN_RELEASE), &sc->sc_state, &sc->sc_accents); if (action == NOKEY) { goto next_code; } return (action); } /* Currently wait is always false. */ static uint32_t gpiokeys_read_char(keyboard_t *kbd, int wait) { uint32_t keycode; struct gpiokeys_softc *sc = kbd->kb_data; GPIOKEYS_LOCK(sc); keycode = gpiokeys_read_char_locked(kbd, wait); GPIOKEYS_UNLOCK(sc); return (keycode); } /* some useful control functions */ static int gpiokeys_ioctl_locked(keyboard_t *kbd, u_long cmd, caddr_t arg) { struct gpiokeys_softc *sc = kbd->kb_data; #if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ defined(COMPAT_FREEBSD4) || defined(COMPAT_43) int ival; #endif switch (cmd) { case KDGKBMODE: /* get keyboard mode */ *(int *)arg = sc->sc_mode; break; #if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ defined(COMPAT_FREEBSD4) || defined(COMPAT_43) case _IO('K', 7): ival = IOCPARM_IVAL(arg); arg = (caddr_t)&ival; /* FALLTHROUGH */ #endif case KDSKBMODE: /* set keyboard mode */ switch (*(int *)arg) { case K_XLATE: if (sc->sc_mode != K_XLATE) { /* make lock key state and LED state match */ sc->sc_state &= ~LOCK_MASK; sc->sc_state |= KBD_LED_VAL(kbd); } /* FALLTHROUGH */ case K_RAW: case K_CODE: if (sc->sc_mode != *(int *)arg) { if ((sc->sc_flags & GPIOKEYS_GLOBAL_FLAG_POLLING) == 0) gpiokeys_clear_state(kbd); sc->sc_mode = *(int *)arg; } break; default: return (EINVAL); } break; case KDGETLED: /* get keyboard LED */ *(int *)arg = KBD_LED_VAL(kbd); break; #if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ defined(COMPAT_FREEBSD4) || defined(COMPAT_43) case _IO('K', 66): ival = IOCPARM_IVAL(arg); arg = (caddr_t)&ival; /* FALLTHROUGH */ #endif case KDSETLED: /* set keyboard LED */ KBD_LED_VAL(kbd) = *(int *)arg; break; case KDGKBSTATE: /* get lock key state */ *(int *)arg = sc->sc_state & LOCK_MASK; break; #if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ defined(COMPAT_FREEBSD4) || defined(COMPAT_43) case _IO('K', 20): ival = IOCPARM_IVAL(arg); arg = (caddr_t)&ival; /* FALLTHROUGH */ #endif case KDSKBSTATE: /* set lock key state */ if (*(int *)arg & ~LOCK_MASK) { return (EINVAL); } sc->sc_state &= ~LOCK_MASK; sc->sc_state |= *(int *)arg; return (0); case KDSETREPEAT: /* set keyboard repeat rate (new * interface) */ if (!KBD_HAS_DEVICE(kbd)) { return (0); } if (((int *)arg)[1] < 0) { return (EINVAL); } if (((int *)arg)[0] < 0) { return (EINVAL); } if (((int *)arg)[0] < 200) /* fastest possible value */ kbd->kb_delay1 = 200; else kbd->kb_delay1 = ((int *)arg)[0]; kbd->kb_delay2 = ((int *)arg)[1]; return (0); #if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ defined(COMPAT_FREEBSD4) || defined(COMPAT_43) case _IO('K', 67): ival = IOCPARM_IVAL(arg); arg = (caddr_t)&ival; /* FALLTHROUGH */ #endif case KDSETRAD: /* set keyboard repeat rate (old * interface) */ return (gpiokeys_set_typematic(kbd, *(int *)arg)); case PIO_KEYMAP: /* set keyboard translation table */ case OPIO_KEYMAP: /* set keyboard translation table * (compat) */ case PIO_KEYMAPENT: /* set keyboard translation table * entry */ case PIO_DEADKEYMAP: /* set accent key translation table */ sc->sc_accents = 0; /* FALLTHROUGH */ default: return (genkbd_commonioctl(kbd, cmd, arg)); } return (0); } static int gpiokeys_ioctl(keyboard_t *kbd, u_long cmd, caddr_t arg) { int result; struct gpiokeys_softc *sc; sc = kbd->kb_data; /* * XXX Check if someone is calling us from a critical section: */ if (curthread->td_critnest != 0) return (EDEADLK); GPIOKEYS_LOCK(sc); result = gpiokeys_ioctl_locked(kbd, cmd, arg); GPIOKEYS_UNLOCK(sc); return (result); } /* clear the internal state of the keyboard */ static void gpiokeys_clear_state(keyboard_t *kbd) { struct gpiokeys_softc *sc = kbd->kb_data; sc->sc_flags &= ~(GPIOKEYS_GLOBAL_FLAG_POLLING); sc->sc_state &= LOCK_MASK; /* preserve locking key state */ sc->sc_accents = 0; } /* get the internal state, not used */ static int gpiokeys_get_state(keyboard_t *kbd, void *buf, size_t len) { return (len == 0) ? 1 : -1; } /* set the internal state, not used */ static int gpiokeys_set_state(keyboard_t *kbd, void *buf, size_t len) { return (EINVAL); } static int gpiokeys_poll(keyboard_t *kbd, int on) { struct gpiokeys_softc *sc = kbd->kb_data; GPIOKEYS_LOCK(sc); if (on) sc->sc_flags |= GPIOKEYS_GLOBAL_FLAG_POLLING; else sc->sc_flags &= ~GPIOKEYS_GLOBAL_FLAG_POLLING; GPIOKEYS_UNLOCK(sc); return (0); } static int gpiokeys_set_typematic(keyboard_t *kbd, int code) { static const int delays[] = {250, 500, 750, 1000}; static const int rates[] = {34, 38, 42, 46, 50, 55, 59, 63, 68, 76, 84, 92, 100, 110, 118, 126, 136, 152, 168, 184, 200, 220, 236, 252, 272, 304, 336, 368, 400, 440, 472, 504}; if (code & ~0x7f) { return (EINVAL); } kbd->kb_delay1 = delays[(code >> 5) & 3]; kbd->kb_delay2 = rates[code & 0x1f]; return (0); } static void gpiokeys_event_keyinput(struct gpiokeys_softc *sc) { int c; if ((sc->sc_flags & GPIOKEYS_GLOBAL_FLAG_POLLING) != 0) return; if (KBD_IS_ACTIVE(&sc->sc_kbd) && KBD_IS_BUSY(&sc->sc_kbd)) { /* let the callback function process the input */ (sc->sc_kbd.kb_callback.kc_func) (&sc->sc_kbd, KBDIO_KEYINPUT, sc->sc_kbd.kb_callback.kc_arg); } else { /* read and discard the input, no one is waiting for it */ do { c = gpiokeys_read_char(&sc->sc_kbd, 0); } while (c != NOKEY); } } static keyboard_switch_t gpiokeyssw = { .probe = &gpiokeys__probe, .init = &gpiokeys_init, .term = &gpiokeys_term, .intr = &gpiokeys_intr, .test_if = &gpiokeys_test_if, .enable = &gpiokeys_enable, .disable = &gpiokeys_disable, .read = &gpiokeys_read, .check = &gpiokeys_check, .read_char = &gpiokeys_read_char, .check_char = &gpiokeys_check_char, .ioctl = &gpiokeys_ioctl, .lock = &gpiokeys_lock, .clear_state = &gpiokeys_clear_state, .get_state = &gpiokeys_get_state, .set_state = &gpiokeys_set_state, .get_fkeystr = &genkbd_get_fkeystr, .poll = &gpiokeys_poll, .diag = &genkbd_diag, }; KEYBOARD_DRIVER(gpiokeys, gpiokeyssw, gpiokeys_configure); static int gpiokeys_driver_load(module_t mod, int what, void *arg) { switch (what) { case MOD_LOAD: kbd_add_driver(&gpiokeys_kbd_driver); break; case MOD_UNLOAD: kbd_delete_driver(&gpiokeys_kbd_driver); break; } return (0); } static devclass_t gpiokeys_devclass; static device_method_t gpiokeys_methods[] = { DEVMETHOD(device_probe, gpiokeys_probe), DEVMETHOD(device_attach, gpiokeys_attach), DEVMETHOD(device_detach, gpiokeys_detach), DEVMETHOD_END }; static driver_t gpiokeys_driver = { "gpiokeys", gpiokeys_methods, sizeof(struct gpiokeys_softc), }; DRIVER_MODULE(gpiokeys, simplebus, gpiokeys_driver, gpiokeys_devclass, gpiokeys_driver_load, 0); MODULE_VERSION(gpiokeys, 1); Index: head/sys/dev/gpio/gpioled_fdt.c =================================================================== --- head/sys/dev/gpio/gpioled_fdt.c (revision 332309) +++ head/sys/dev/gpio/gpioled_fdt.c (revision 332310) @@ -1,230 +1,230 @@ /*- * Copyright (c) 2009 Oleksandr Tymoshenko * 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$"); #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gpiobus_if.h" struct gpioled { struct gpioleds_softc *parent_sc; gpio_pin_t pin; struct cdev *leddev; }; struct gpioleds_softc { device_t sc_dev; device_t sc_busdev; struct gpioled *sc_leds; int sc_total_leds; }; static void gpioled_control(void *, int); static int gpioled_probe(device_t); static int gpioled_attach(device_t); static int gpioled_detach(device_t); static void gpioled_control(void *priv, int onoff) { struct gpioled *led; led = (struct gpioled *)priv; if (led->pin) gpio_pin_set_active(led->pin, onoff); } static void gpioleds_attach_led(struct gpioleds_softc *sc, phandle_t node, struct gpioled *led) { char *name; int state, err; char *default_state; led->parent_sc = sc; state = 0; if (OF_getprop_alloc(node, "default-state", - sizeof(char), (void **)&default_state) != -1) { + (void **)&default_state) != -1) { if (strcasecmp(default_state, "on") == 0) state = 1; else if (strcasecmp(default_state, "off") == 0) state = 0; else if (strcasecmp(default_state, "keep") == 0) state = -1; else { state = -1; device_printf(sc->sc_dev, "unknown value for default-state in FDT\n"); } OF_prop_free(default_state); } name = NULL; - if (OF_getprop_alloc(node, "label", 1, (void **)&name) == -1) - OF_getprop_alloc(node, "name", 1, (void **)&name); + if (OF_getprop_alloc(node, "label", (void **)&name) == -1) + OF_getprop_alloc(node, "name", (void **)&name); if (name == NULL) { device_printf(sc->sc_dev, "no name provided for gpio LED, skipping\n"); return; } err = gpio_pin_get_by_ofw_idx(sc->sc_dev, node, 0, &led->pin); if (err) { device_printf(sc->sc_dev, "<%s> failed to map pin\n", name); if (name) OF_prop_free(name); return; } gpio_pin_setflags(led->pin, GPIO_PIN_OUTPUT); led->leddev = led_create_state(gpioled_control, led, name, state); if (name != NULL) OF_prop_free(name); } static void gpioleds_detach_led(struct gpioled *led) { if (led->leddev != NULL) led_destroy(led->leddev); if (led->pin) gpio_pin_release(led->pin); } static int gpioled_probe(device_t dev) { if (!ofw_bus_is_compatible(dev, "gpio-leds")) return (ENXIO); device_set_desc(dev, "GPIO LEDs"); return (BUS_PROBE_DEFAULT); } static int gpioled_attach(device_t dev) { struct gpioleds_softc *sc; phandle_t child, leds; int total_leds; if ((leds = ofw_bus_get_node(dev)) == -1) return (ENXIO); sc = device_get_softc(dev); sc->sc_dev = dev; sc->sc_busdev = device_get_parent(dev); /* Traverse the 'gpio-leds' node and count leds */ total_leds = 0; for (child = OF_child(leds); child != 0; child = OF_peer(child)) { if (!OF_hasprop(child, "gpios")) continue; total_leds++; } if (total_leds) { sc->sc_leds = malloc(sizeof(struct gpioled) * total_leds, M_DEVBUF, M_WAITOK | M_ZERO); sc->sc_total_leds = 0; /* Traverse the 'gpio-leds' node and count leds */ for (child = OF_child(leds); child != 0; child = OF_peer(child)) { if (!OF_hasprop(child, "gpios")) continue; gpioleds_attach_led(sc, child, &sc->sc_leds[sc->sc_total_leds]); sc->sc_total_leds++; } } return (0); } static int gpioled_detach(device_t dev) { struct gpioleds_softc *sc; int i; sc = device_get_softc(dev); for (i = 0; i < sc->sc_total_leds; i++) gpioleds_detach_led(&sc->sc_leds[i]); if (sc->sc_leds) free(sc->sc_leds, M_DEVBUF); return (0); } static devclass_t gpioled_devclass; static device_method_t gpioled_methods[] = { /* Device interface */ DEVMETHOD(device_probe, gpioled_probe), DEVMETHOD(device_attach, gpioled_attach), DEVMETHOD(device_detach, gpioled_detach), DEVMETHOD_END }; static driver_t gpioled_driver = { "gpioled", gpioled_methods, sizeof(struct gpioleds_softc), }; DRIVER_MODULE(gpioled, ofwbus, gpioled_driver, gpioled_devclass, 0, 0); DRIVER_MODULE(gpioled, simplebus, gpioled_driver, gpioled_devclass, 0, 0); MODULE_DEPEND(gpioled, gpiobus, 1, 1, 1); Index: head/sys/dev/iicbus/adt746x.c =================================================================== --- head/sys/dev/iicbus/adt746x.c (revision 332309) +++ head/sys/dev/iicbus/adt746x.c (revision 332310) @@ -1,667 +1,667 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2012 Andreas Tobler * Copyright (c) 2014 Justin Hibbits * 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 ``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 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$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* ADT746X registers. */ #define ADT746X_TACH1LOW 0x28 #define ADT746X_TACH1HIGH 0x29 #define ADT746X_TACH2LOW 0x2a #define ADT746X_TACH2HIGH 0x2b #define ADT746X_PWM1 0x30 #define ADT746X_PWM2 0x31 #define ADT746X_DEVICE_ID 0x3d #define ADT746X_COMPANY_ID 0x3e #define ADT746X_REV_ID 0x3f #define ADT746X_CONFIG 0x40 #define ADT746X_PWM1_CONF 0x5c #define ADT746X_PWM2_CONF 0x5d #define ADT746X_MANUAL_MASK 0xe0 #define ADT7460_DEV_ID 0x27 #define ADT7467_DEV_ID 0x68 struct adt746x_fan { struct pmac_fan fan; device_t dev; int id; int setpoint; int pwm_reg; int conf_reg; }; struct adt746x_sensor { struct pmac_therm therm; device_t dev; int id; cell_t reg; enum { ADT746X_SENSOR_TEMP, ADT746X_SENSOR_VOLT, ADT746X_SENSOR_SPEED } type; }; struct adt746x_softc { device_t sc_dev; struct intr_config_hook enum_hook; uint32_t sc_addr; /* The 7467 supports up to 4 fans, 2 voltage and 3 temperature sensors. */ struct adt746x_fan sc_fans[4]; int sc_nfans; struct adt746x_sensor sc_sensors[9]; int sc_nsensors; int device_id; }; /* Regular bus attachment functions */ static int adt746x_probe(device_t); static int adt746x_attach(device_t); /* Utility functions */ static void adt746x_attach_fans(device_t dev); static void adt746x_attach_sensors(device_t dev); static int adt746x_fill_fan_prop(device_t dev); static int adt746x_fill_sensor_prop(device_t dev); static int adt746x_fan_set_pwm(struct adt746x_fan *fan, int pwm); static int adt746x_fan_get_pwm(struct adt746x_fan *fan); static int adt746x_sensor_read(struct adt746x_sensor *sens); static void adt746x_start(void *xdev); /* i2c read/write functions. */ static int adt746x_write(device_t dev, uint32_t addr, uint8_t reg, uint8_t *buf); static int adt746x_read(device_t dev, uint32_t addr, uint8_t reg, uint8_t *data); static device_method_t adt746x_methods[] = { /* Device interface */ DEVMETHOD(device_probe, adt746x_probe), DEVMETHOD(device_attach, adt746x_attach), { 0, 0 }, }; static driver_t adt746x_driver = { "adt746x", adt746x_methods, sizeof(struct adt746x_softc) }; static devclass_t adt746x_devclass; DRIVER_MODULE(adt746x, iicbus, adt746x_driver, adt746x_devclass, 0, 0); static MALLOC_DEFINE(M_ADT746X, "adt746x", "ADT Sensor Information"); /* i2c read/write functions. */ static int adt746x_write(device_t dev, uint32_t addr, uint8_t reg, uint8_t *buff) { uint8_t buf[4]; int try = 0; struct iic_msg msg[] = { {addr, IIC_M_WR, 2, buf } }; /* Prepare the write msg. */ buf[0] = reg; memcpy(buf + 1, buff, 1); for (;;) { if (iicbus_transfer(dev, msg, 1) == 0) return (0); if (++try > 5) { device_printf(dev, "iicbus write failed\n"); return (-1); } pause("adt746x_write", hz); } return (0); } static int adt746x_read(device_t dev, uint32_t addr, uint8_t reg, uint8_t *data) { uint8_t buf[4]; int err, try = 0; struct iic_msg msg[2] = { {addr, IIC_M_WR | IIC_M_NOSTOP, 1, ®}, {addr, IIC_M_RD, 1, buf}, }; for (;;) { err = iicbus_transfer(dev, msg, 2); if (err != 0) goto retry; *data = *((uint8_t*)buf); return (0); retry: if (++try > 5) { device_printf(dev, "iicbus read failed\n"); return (-1); } pause("adt746x_read", hz); } } static int adt746x_probe(device_t dev) { const char *name, *compatible; struct adt746x_softc *sc; name = ofw_bus_get_name(dev); compatible = ofw_bus_get_compat(dev); if (!name) return (ENXIO); if (strcmp(name, "fan") != 0 || (strcmp(compatible, "adt7460") != 0 && strcmp(compatible, "adt7467") != 0)) return (ENXIO); sc = device_get_softc(dev); sc->sc_dev = dev; sc->sc_addr = iicbus_get_addr(dev); device_set_desc(dev, "Apple Thermostat Unit ADT746X"); return (0); } static int adt746x_attach(device_t dev) { struct adt746x_softc *sc; sc = device_get_softc(dev); sc->enum_hook.ich_func = adt746x_start; sc->enum_hook.ich_arg = dev; /* We have to wait until interrupts are enabled. I2C read and write * only works if the interrupts are available. * The unin/i2c is controlled by the htpic on unin. But this is not * the master. The openpic on mac-io is controlling the htpic. * This one gets attached after the mac-io probing and then the * interrupts will be available. */ if (config_intrhook_establish(&sc->enum_hook) != 0) return (ENOMEM); return (0); } static void adt746x_start(void *xdev) { uint8_t did, cid, rev, conf; struct adt746x_softc *sc; device_t dev = (device_t)xdev; sc = device_get_softc(dev); adt746x_read(sc->sc_dev, sc->sc_addr, ADT746X_DEVICE_ID, &did); adt746x_read(sc->sc_dev, sc->sc_addr, ADT746X_COMPANY_ID, &cid); adt746x_read(sc->sc_dev, sc->sc_addr, ADT746X_REV_ID, &rev); adt746x_read(sc->sc_dev, sc->sc_addr, ADT746X_CONFIG, &conf); device_printf(dev, "Dev ID %#x, Company ID %#x, Rev ID %#x CNF: %#x\n", did, cid, rev, conf); /* We can get the device id either from 'of' properties or from the chip itself. This method makes sure we can read the chip, otherwise we return. */ sc->device_id = did; conf = 1; /* Start the ADT7460. */ if (sc->device_id == ADT7460_DEV_ID) adt746x_write(sc->sc_dev, sc->sc_addr, ADT746X_CONFIG, &conf); /* Detect and attach child devices. */ adt746x_attach_fans(dev); adt746x_attach_sensors(dev); config_intrhook_disestablish(&sc->enum_hook); } /* * Sensor and fan management */ static int adt746x_fan_set_pwm(struct adt746x_fan *fan, int pwm) { uint8_t reg = 0, manual, mode = 0; struct adt746x_softc *sc; uint8_t buf; sc = device_get_softc(fan->dev); /* Clamp to allowed range */ pwm = max(fan->fan.min_rpm, pwm); pwm = min(fan->fan.max_rpm, pwm); reg = fan->pwm_reg; mode = fan->conf_reg; /* From the 7460 datasheet: PWM dutycycle can be programmed from 0% (0x00) to 100% (0xFF) in steps of 0.39% (256 steps). */ buf = (pwm * 100 / 39) - (pwm ? 1 : 0); fan->setpoint = buf; /* Manual mode. */ adt746x_read(sc->sc_dev, sc->sc_addr, mode, &manual); manual |= ADT746X_MANUAL_MASK; adt746x_write(sc->sc_dev, sc->sc_addr, mode, &manual); /* Write speed. */ adt746x_write(sc->sc_dev, sc->sc_addr, reg, &buf); return (0); } static int adt746x_fan_get_pwm(struct adt746x_fan *fan) { uint8_t buf, reg; uint16_t pwm; struct adt746x_softc *sc; sc = device_get_softc(fan->dev); reg = fan->pwm_reg; adt746x_read(sc->sc_dev, sc->sc_addr, reg, &buf); pwm = (buf * 39 / 100) + (buf ? 1 : 0); return (pwm); } static int adt746x_fill_fan_prop(device_t dev) { phandle_t child; struct adt746x_softc *sc; u_int *id; char *location; int i, id_len, len = 0, location_len, prev_len = 0; sc = device_get_softc(dev); child = ofw_bus_get_node(dev); /* Fill the fan location property. */ - location_len = OF_getprop_alloc(child, "hwctrl-location", 1, (void **)&location); - id_len = OF_getprop_alloc(child, "hwctrl-id", sizeof(cell_t), (void **)&id); + location_len = OF_getprop_alloc(child, "hwctrl-location", (void **)&location); + id_len = OF_getprop_alloc_multi(child, "hwctrl-id", sizeof(cell_t), (void **)&id); if (location_len == -1 || id_len == -1) { OF_prop_free(location); OF_prop_free(id); return 0; } /* Fill in all the properties for each fan. */ for (i = 0; i < id_len; i++) { strlcpy(sc->sc_fans[i].fan.name, location + len, 32); prev_len = strlen(location + len) + 1; len += prev_len; sc->sc_fans[i].id = id[i]; if (id[i] == 6) { sc->sc_fans[i].pwm_reg = ADT746X_PWM1; sc->sc_fans[i].conf_reg = ADT746X_PWM1_CONF; } else if (id[i] == 7) { sc->sc_fans[i].pwm_reg = ADT746X_PWM2; sc->sc_fans[i].conf_reg = ADT746X_PWM2_CONF; } else { sc->sc_fans[i].pwm_reg = ADT746X_PWM1 + i; sc->sc_fans[i].conf_reg = ADT746X_PWM1_CONF + i; } sc->sc_fans[i].dev = sc->sc_dev; sc->sc_fans[i].fan.min_rpm = 5; /* Percent */ sc->sc_fans[i].fan.max_rpm = 100; sc->sc_fans[i].fan.read = NULL; sc->sc_fans[i].fan.set = (int (*)(struct pmac_fan *, int))(adt746x_fan_set_pwm); sc->sc_fans[i].fan.default_rpm = sc->sc_fans[i].fan.max_rpm; } OF_prop_free(location); OF_prop_free(id); return (i); } static int adt746x_fill_sensor_prop(device_t dev) { phandle_t child, node; struct adt746x_softc *sc; char sens_type[32]; int i = 0, reg, sensid; sc = device_get_softc(dev); child = ofw_bus_get_node(dev); /* Fill in the sensor properties for each child. */ for (node = OF_child(child); node != 0; node = OF_peer(node)) { if (OF_getprop(node, "sensor-id", &sensid, sizeof(sensid)) == -1) continue; OF_getprop(node, "location", sc->sc_sensors[i].therm.name, 32); OF_getprop(node, "device_type", sens_type, sizeof(sens_type)); if (strcmp(sens_type, "temperature") == 0) sc->sc_sensors[i].type = ADT746X_SENSOR_TEMP; else if (strcmp(sens_type, "voltage") == 0) sc->sc_sensors[i].type = ADT746X_SENSOR_VOLT; else sc->sc_sensors[i].type = ADT746X_SENSOR_SPEED; OF_getprop(node, "reg", ®, sizeof(reg)); OF_getprop(node, "sensor-id", &sensid, sizeof(sensid)); /* This is the i2c register of the sensor. */ sc->sc_sensors[i].reg = reg; sc->sc_sensors[i].id = sensid; OF_getprop(node, "zone", &sc->sc_sensors[i].therm.zone, sizeof(sc->sc_sensors[i].therm.zone)); sc->sc_sensors[i].dev = dev; sc->sc_sensors[i].therm.read = (int (*)(struct pmac_therm *))adt746x_sensor_read; if (sc->sc_sensors[i].type == ADT746X_SENSOR_TEMP) { /* Make up some ranges */ sc->sc_sensors[i].therm.target_temp = 500 + ZERO_C_TO_K; sc->sc_sensors[i].therm.max_temp = 800 + ZERO_C_TO_K; pmac_thermal_sensor_register(&sc->sc_sensors[i].therm); } i++; } return (i); } static int adt746x_fanrpm_sysctl(SYSCTL_HANDLER_ARGS) { device_t adt; struct adt746x_softc *sc; struct adt746x_fan *fan; int pwm = 0, error; adt = arg1; sc = device_get_softc(adt); fan = &sc->sc_fans[arg2]; pwm = adt746x_fan_get_pwm(fan); error = sysctl_handle_int(oidp, &pwm, 0, req); if (error || !req->newptr) return (error); return (adt746x_fan_set_pwm(fan, pwm)); } static void adt746x_attach_fans(device_t dev) { struct adt746x_softc *sc; struct sysctl_oid *oid, *fanroot_oid; struct sysctl_ctx_list *ctx; phandle_t child; char sysctl_name[32]; int i, j; sc = device_get_softc(dev); sc->sc_nfans = 0; child = ofw_bus_get_node(dev); /* Count the actual number of fans. */ sc->sc_nfans = adt746x_fill_fan_prop(dev); device_printf(dev, "%d fans detected!\n", sc->sc_nfans); if (sc->sc_nfans == 0) { device_printf(dev, "WARNING: No fans detected!\n"); return; } ctx = device_get_sysctl_ctx(dev); fanroot_oid = SYSCTL_ADD_NODE(ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "fans", CTLFLAG_RD, 0, "ADT Fan Information"); /* Now we can fill the properties into the allocated struct. */ sc->sc_nfans = adt746x_fill_fan_prop(dev); /* Register fans with pmac_thermal */ for (i = 0; i < sc->sc_nfans; i++) pmac_thermal_fan_register(&sc->sc_fans[i].fan); /* Add sysctls for the fans. */ for (i = 0; i < sc->sc_nfans; i++) { for (j = 0; j < strlen(sc->sc_fans[i].fan.name); j++) { sysctl_name[j] = tolower(sc->sc_fans[i].fan.name[j]); if (isspace(sysctl_name[j])) sysctl_name[j] = '_'; } sysctl_name[j] = 0; sc->sc_fans[i].setpoint = adt746x_fan_get_pwm(&sc->sc_fans[i]); oid = SYSCTL_ADD_NODE(ctx, SYSCTL_CHILDREN(fanroot_oid), OID_AUTO, sysctl_name, CTLFLAG_RD, 0, "Fan Information"); /* I use i to pass the fan id. */ SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "pwm", CTLTYPE_INT | CTLFLAG_RW, dev, i, adt746x_fanrpm_sysctl, "I", "Fan PWM in %"); } /* Dump fan location & type. */ if (bootverbose) { for (i = 0; i < sc->sc_nfans; i++) { device_printf(dev, "Fan location: %s", sc->sc_fans[i].fan.name); device_printf(dev, " id: %d RPM: %d\n", sc->sc_fans[i].id, sc->sc_fans[i].setpoint); } } } static int adt746x_sensor_read(struct adt746x_sensor *sens) { struct adt746x_softc *sc; int tmp = 0; uint16_t val; uint8_t data[1], data1[1]; int8_t temp; sc = device_get_softc(sens->dev); if (sens->type != ADT746X_SENSOR_SPEED) { if (adt746x_read(sc->sc_dev, sc->sc_addr, sens->reg, &temp) < 0) return (-1); if (sens->type == ADT746X_SENSOR_TEMP) tmp = 10 * temp + ZERO_C_TO_K; else tmp = temp; } else { if (adt746x_read(sc->sc_dev, sc->sc_addr, sens->reg, data) < 0) return (-1); if (adt746x_read(sc->sc_dev, sc->sc_addr, sens->reg + 1, data1) < 0) return (-1); val = data[0] + (data1[0] << 8); /* A value of 0xffff means the fan is stopped. */ if (val == 0 || val == 0xffff) tmp = 0; else tmp = (90000 * 60) / val; } return (tmp); } static int adt746x_sensor_sysctl(SYSCTL_HANDLER_ARGS) { device_t dev; struct adt746x_softc *sc; struct adt746x_sensor *sens; int value, error; dev = arg1; sc = device_get_softc(dev); sens = &sc->sc_sensors[arg2]; value = sens->therm.read(&sens->therm); if (value < 0) return (ENXIO); error = sysctl_handle_int(oidp, &value, 0, req); return (error); } static void adt746x_attach_sensors(device_t dev) { struct adt746x_softc *sc; struct sysctl_oid *oid, *sensroot_oid; struct sysctl_ctx_list *ctx; phandle_t child; char sysctl_name[40]; const char *unit; const char *desc; int i, j; sc = device_get_softc(dev); sc->sc_nsensors = 0; child = ofw_bus_get_node(dev); /* Count the actual number of sensors. */ sc->sc_nsensors = adt746x_fill_sensor_prop(dev); device_printf(dev, "%d sensors detected!\n", sc->sc_nsensors); if (sc->sc_nsensors == 0) { device_printf(dev, "WARNING: No sensors detected!\n"); return; } ctx = device_get_sysctl_ctx(dev); sensroot_oid = SYSCTL_ADD_NODE(ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "sensors", CTLFLAG_RD, 0, "ADT Sensor Information"); /* Add the sysctl for the sensors. */ for (i = 0; i < sc->sc_nsensors; i++) { for (j = 0; j < strlen(sc->sc_sensors[i].therm.name); j++) { sysctl_name[j] = tolower(sc->sc_sensors[i].therm.name[j]); if (isspace(sysctl_name[j])) sysctl_name[j] = '_'; } sysctl_name[j] = 0; oid = SYSCTL_ADD_NODE(ctx, SYSCTL_CHILDREN(sensroot_oid), OID_AUTO, sysctl_name, CTLFLAG_RD, 0, "Sensor Information"); if (sc->sc_sensors[i].type == ADT746X_SENSOR_TEMP) { unit = "temp"; desc = "sensor unit (C)"; } else if (sc->sc_sensors[i].type == ADT746X_SENSOR_VOLT) { unit = "volt"; desc = "sensor unit (mV)"; } else { unit = "rpm"; desc = "sensor unit (RPM)"; } /* I use i to pass the sensor id. */ SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, unit, CTLTYPE_INT | CTLFLAG_RD, dev, i, adt746x_sensor_sysctl, sc->sc_sensors[i].type == ADT746X_SENSOR_TEMP ? "IK" : "I", desc); } /* Dump sensor location & type. */ if (bootverbose) { for (i = 0; i < sc->sc_nsensors; i++) { device_printf(dev, "Sensor location: %s", sc->sc_sensors[i].therm.name); device_printf(dev, " type: %d id: %d reg: 0x%x\n", sc->sc_sensors[i].type, sc->sc_sensors[i].id, sc->sc_sensors[i].reg); } } } Index: head/sys/dev/le/lebuffer_sbus.c =================================================================== --- head/sys/dev/le/lebuffer_sbus.c (revision 332309) +++ head/sys/dev/le/lebuffer_sbus.c (revision 332310) @@ -1,305 +1,305 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2006 Marius Strobl * 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$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct lebuffer_devinfo { struct ofw_bus_devinfo ldi_obdinfo; struct resource_list ldi_rl; }; static devclass_t lebuffer_devclass; static device_probe_t lebuffer_probe; static device_attach_t lebuffer_attach; static device_detach_t lebuffer_detach; static bus_print_child_t lebuffer_print_child; static bus_probe_nomatch_t lebuffer_probe_nomatch; static bus_get_resource_list_t lebuffer_get_resource_list; static ofw_bus_get_devinfo_t lebuffer_get_devinfo; static struct lebuffer_devinfo *lebuffer_setup_dinfo(device_t, phandle_t); static void lebuffer_destroy_dinfo(struct lebuffer_devinfo *); static int lebuffer_print_res(struct lebuffer_devinfo *); static device_method_t lebuffer_methods[] = { /* Device interface */ DEVMETHOD(device_probe, lebuffer_probe), DEVMETHOD(device_attach, lebuffer_attach), DEVMETHOD(device_detach, lebuffer_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* Bus interface */ DEVMETHOD(bus_print_child, lebuffer_print_child), DEVMETHOD(bus_probe_nomatch, lebuffer_probe_nomatch), DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD(bus_alloc_resource, bus_generic_rl_alloc_resource), DEVMETHOD(bus_adjust_resource, bus_generic_adjust_resource), DEVMETHOD(bus_release_resource, bus_generic_rl_release_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_get_resource_list, lebuffer_get_resource_list), DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_devinfo, lebuffer_get_devinfo), DEVMETHOD(ofw_bus_get_compat, ofw_bus_gen_get_compat), DEVMETHOD(ofw_bus_get_model, ofw_bus_gen_get_model), DEVMETHOD(ofw_bus_get_name, ofw_bus_gen_get_name), DEVMETHOD(ofw_bus_get_node, ofw_bus_gen_get_node), DEVMETHOD(ofw_bus_get_type, ofw_bus_gen_get_type), { 0, 0 } }; DEFINE_CLASS_0(lebuffer, lebuffer_driver, lebuffer_methods, 1); DRIVER_MODULE(lebuffer, sbus, lebuffer_driver, lebuffer_devclass, 0, 0); MODULE_DEPEND(lebuffer, sbus, 1, 1, 1); MODULE_VERSION(lebuffer, 1); static int lebuffer_probe(device_t dev) { const char *name; name = ofw_bus_get_name(dev); if (strcmp(name, "lebuffer") == 0) { device_set_desc_copy(dev, name); return (0); } return (ENXIO); } static int lebuffer_attach(device_t dev) { struct lebuffer_devinfo *ldi; device_t cdev; phandle_t child; int children; children = 0; for (child = OF_child(ofw_bus_get_node(dev)); child != 0; child = OF_peer(child)) { if ((ldi = lebuffer_setup_dinfo(dev, child)) == NULL) continue; if (children != 0) { device_printf(dev, "<%s>: only one child per buffer supported\n", ldi->ldi_obdinfo.obd_name); lebuffer_destroy_dinfo(ldi); continue; } if ((cdev = device_add_child(dev, NULL, -1)) == NULL) { device_printf(dev, "<%s>: device_add_child failed\n", ldi->ldi_obdinfo.obd_name); lebuffer_destroy_dinfo(ldi); continue; } device_set_ivars(cdev, ldi); children++; } return (bus_generic_attach(dev)); } static int lebuffer_detach(device_t dev) { device_t *children; int i, nchildren; bus_generic_detach(dev); if (device_get_children(dev, &children, &nchildren) == 0) { for (i = 0; i < nchildren; i++) { lebuffer_destroy_dinfo(device_get_ivars(children[i])); device_delete_child(dev, children[i]); } free(children, M_TEMP); } return (0); } static struct lebuffer_devinfo * lebuffer_setup_dinfo(device_t dev, phandle_t node) { struct lebuffer_devinfo *ldi; struct sbus_regs *reg; uint32_t base, iv, *intr; int i, nreg, nintr, slot, rslot; ldi = malloc(sizeof(*ldi), M_DEVBUF, M_WAITOK | M_ZERO); if (ofw_bus_gen_setup_devinfo(&ldi->ldi_obdinfo, node) != 0) { free(ldi, M_DEVBUF); return (NULL); } resource_list_init(&ldi->ldi_rl); slot = -1; - nreg = OF_getprop_alloc(node, "reg", sizeof(*reg), (void **)®); + nreg = OF_getprop_alloc_multi(node, "reg", sizeof(*reg), (void **)®); if (nreg == -1) { device_printf(dev, "<%s>: incomplete\n", ldi->ldi_obdinfo.obd_name); goto fail; } for (i = 0; i < nreg; i++) { base = reg[i].sbr_offset; if (SBUS_ABS(base)) { rslot = SBUS_ABS_TO_SLOT(base); base = SBUS_ABS_TO_OFFSET(base); } else rslot = reg[i].sbr_slot; if (slot != -1 && slot != rslot) { device_printf(dev, "<%s>: multiple slots\n", ldi->ldi_obdinfo.obd_name); OF_prop_free(reg); goto fail; } slot = rslot; resource_list_add(&ldi->ldi_rl, SYS_RES_MEMORY, i, base, base + reg[i].sbr_size, reg[i].sbr_size); } OF_prop_free(reg); if (slot != sbus_get_slot(dev)) { device_printf(dev, "<%s>: parent and child slot do not match\n", ldi->ldi_obdinfo.obd_name); goto fail; } /* * The `interrupts' property contains the SBus interrupt level. */ - nintr = OF_getprop_alloc(node, "interrupts", sizeof(*intr), + nintr = OF_getprop_alloc_multi(node, "interrupts", sizeof(*intr), (void **)&intr); if (nintr != -1) { for (i = 0; i < nintr; i++) { iv = intr[i]; /* * SBus card devices need the slot number encoded into * the vector as this is generally not done. */ if ((iv & INTMAP_OBIO_MASK) == 0) iv |= slot << 3; /* Set the IGN as appropriate. */ iv |= sbus_get_ign(dev) << INTMAP_IGN_SHIFT; resource_list_add(&ldi->ldi_rl, SYS_RES_IRQ, i, iv, iv, 1); } OF_prop_free(intr); } return (ldi); fail: lebuffer_destroy_dinfo(ldi); return (NULL); } static void lebuffer_destroy_dinfo(struct lebuffer_devinfo *dinfo) { resource_list_free(&dinfo->ldi_rl); ofw_bus_gen_destroy_devinfo(&dinfo->ldi_obdinfo); free(dinfo, M_DEVBUF); } static int lebuffer_print_child(device_t dev, device_t child) { int rv; rv = bus_print_child_header(dev, child); rv += lebuffer_print_res(device_get_ivars(child)); rv += bus_print_child_footer(dev, child); return (rv); } static void lebuffer_probe_nomatch(device_t dev, device_t child) { const char *type; device_printf(dev, "<%s>", ofw_bus_get_name(child)); lebuffer_print_res(device_get_ivars(child)); type = ofw_bus_get_type(child); printf(" type %s (no driver attached)\n", type != NULL ? type : "unknown"); } static struct resource_list * lebuffer_get_resource_list(device_t dev, device_t child) { struct lebuffer_devinfo *ldi; ldi = device_get_ivars(child); return (&ldi->ldi_rl); } static const struct ofw_bus_devinfo * lebuffer_get_devinfo(device_t bus, device_t child) { struct lebuffer_devinfo *ldi; ldi = device_get_ivars(child); return (&ldi->ldi_obdinfo); } static int lebuffer_print_res(struct lebuffer_devinfo *ldi) { int rv; rv = 0; rv += resource_list_print_type(&ldi->ldi_rl, "mem", SYS_RES_MEMORY, "%#jx"); rv += resource_list_print_type(&ldi->ldi_rl, "irq", SYS_RES_IRQ, "%jd"); return (rv); } Index: head/sys/dev/neta/if_mvneta_fdt.c =================================================================== --- head/sys/dev/neta/if_mvneta_fdt.c (revision 332309) +++ head/sys/dev/neta/if_mvneta_fdt.c (revision 332310) @@ -1,231 +1,231 @@ /* * Copyright (c) 2017 Stormshield. * Copyright (c) 2017 Semihalf. * 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 ``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 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 "opt_platform.h" #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "if_mvnetareg.h" #include "if_mvnetavar.h" #define PHY_MODE_MAXLEN 10 #define INBAND_STATUS_MAXLEN 16 static int mvneta_fdt_probe(device_t); static int mvneta_fdt_attach(device_t); static device_method_t mvneta_fdt_methods[] = { /* Device interface */ DEVMETHOD(device_probe, mvneta_fdt_probe), DEVMETHOD(device_attach, mvneta_fdt_attach), /* End */ DEVMETHOD_END }; DEFINE_CLASS_1(mvneta, mvneta_fdt_driver, mvneta_fdt_methods, sizeof(struct mvneta_softc), mvneta_driver); static devclass_t mvneta_fdt_devclass; DRIVER_MODULE(mvneta, ofwbus, mvneta_fdt_driver, mvneta_fdt_devclass, 0, 0); DRIVER_MODULE(mvneta, simplebus, mvneta_fdt_driver, mvneta_fdt_devclass, 0, 0); static int mvneta_fdt_phy_acquire(device_t); static struct ofw_compat_data compat_data[] = { {"marvell,armada-370-neta", true}, {"marvell,armada-3700-neta", true}, {NULL, false} }; static int mvneta_fdt_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, "NETA controller"); return (BUS_PROBE_DEFAULT); } static int mvneta_fdt_attach(device_t dev) { int err; /* Try to fetch PHY information from FDT */ err = mvneta_fdt_phy_acquire(dev); if (err != 0) return (err); return (mvneta_attach(dev)); } static int mvneta_fdt_phy_acquire(device_t dev) { struct mvneta_softc *sc; phandle_t node, child, phy_handle; char phymode[PHY_MODE_MAXLEN]; char managed[INBAND_STATUS_MAXLEN]; char *name; sc = device_get_softc(dev); node = ofw_bus_get_node(dev); /* PHY mode is crucial */ if (OF_getprop(node, "phy-mode", phymode, sizeof(phymode)) <= 0) { device_printf(dev, "Failed to acquire PHY mode from FDT.\n"); return (ENXIO); } if (strncmp(phymode, "rgmii-id", 8) == 0) sc->phy_mode = MVNETA_PHY_RGMII_ID; else if (strncmp(phymode, "rgmii", 5) == 0) sc->phy_mode = MVNETA_PHY_RGMII; else if (strncmp(phymode, "sgmii", 5) == 0) sc->phy_mode = MVNETA_PHY_SGMII; else if (strncmp(phymode, "qsgmii", 6) == 0) sc->phy_mode = MVNETA_PHY_QSGMII; else sc->phy_mode = MVNETA_PHY_SGMII; /* Check if in-band link status will be used */ if (OF_getprop(node, "managed", managed, sizeof(managed)) > 0) { if (strncmp(managed, "in-band-status", 14) == 0) { sc->use_inband_status = TRUE; device_printf(dev, "Use in-band link status.\n"); return (0); } } if (OF_getencprop(node, "phy", (void *)&phy_handle, sizeof(phy_handle)) <= 0) { /* Test for fixed-link (present i.e. in 388-gp) */ for (child = OF_child(node); child != 0; child = OF_peer(child)) { if (OF_getprop_alloc(child, - "name", 1, (void **)&name) <= 0) { + "name", (void **)&name) <= 0) { continue; } if (strncmp(name, "fixed-link", 10) == 0) { free(name, M_OFWPROP); if (OF_getencprop(child, "speed", &sc->phy_speed, sizeof(sc->phy_speed)) <= 0) { if (bootverbose) { device_printf(dev, "No PHY information.\n"); } return (ENXIO); } if (OF_hasprop(child, "full-duplex")) sc->phy_fdx = TRUE; else sc->phy_fdx = FALSE; /* Keep this flag just for the record */ sc->phy_addr = MII_PHY_ANY; return (0); } free(name, M_OFWPROP); } if (bootverbose) { device_printf(dev, "Could not find PHY information in FDT.\n"); } return (ENXIO); } else { phy_handle = OF_instance_to_package(phy_handle); if (OF_getencprop(phy_handle, "reg", &sc->phy_addr, sizeof(sc->phy_addr)) <= 0) { device_printf(dev, "Could not find PHY address in FDT.\n"); return (ENXIO); } } return (0); } int mvneta_fdt_mac_address(struct mvneta_softc *sc, uint8_t *addr) { phandle_t node; uint8_t lmac[ETHER_ADDR_LEN]; uint8_t zeromac[] = {[0 ... (ETHER_ADDR_LEN - 1)] = 0}; int len; /* * Retrieve hw address from the device tree. */ node = ofw_bus_get_node(sc->dev); if (node == 0) return (ENXIO); len = OF_getprop(node, "local-mac-address", (void *)lmac, sizeof(lmac)); if (len != ETHER_ADDR_LEN) return (ENOENT); if (memcmp(lmac, zeromac, ETHER_ADDR_LEN) == 0) { /* Invalid MAC address (all zeros) */ return (EINVAL); } memcpy(addr, lmac, ETHER_ADDR_LEN); return (0); } Index: head/sys/dev/ofw/ofw_bus_subr.c =================================================================== --- head/sys/dev/ofw/ofw_bus_subr.c (revision 332309) +++ head/sys/dev/ofw/ofw_bus_subr.c (revision 332310) @@ -1,980 +1,980 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2001 - 2003 by Thomas Moestl . * Copyright (c) 2005 Marius Strobl * 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, * without modification, immediately at the beginning of the file. * 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$"); #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include "ofw_bus_if.h" #define OFW_COMPAT_LEN 255 #define OFW_STATUS_LEN 16 int ofw_bus_gen_setup_devinfo(struct ofw_bus_devinfo *obd, phandle_t node) { if (obd == NULL) return (ENOMEM); /* The 'name' property is considered mandatory. */ - if ((OF_getprop_alloc(node, "name", 1, (void **)&obd->obd_name)) == -1) + if ((OF_getprop_alloc(node, "name", (void **)&obd->obd_name)) == -1) return (EINVAL); - OF_getprop_alloc(node, "compatible", 1, (void **)&obd->obd_compat); - OF_getprop_alloc(node, "device_type", 1, (void **)&obd->obd_type); - OF_getprop_alloc(node, "model", 1, (void **)&obd->obd_model); - OF_getprop_alloc(node, "status", 1, (void **)&obd->obd_status); + OF_getprop_alloc(node, "compatible", (void **)&obd->obd_compat); + OF_getprop_alloc(node, "device_type", (void **)&obd->obd_type); + OF_getprop_alloc(node, "model", (void **)&obd->obd_model); + OF_getprop_alloc(node, "status", (void **)&obd->obd_status); obd->obd_node = node; return (0); } void ofw_bus_gen_destroy_devinfo(struct ofw_bus_devinfo *obd) { if (obd == NULL) return; if (obd->obd_compat != NULL) free(obd->obd_compat, M_OFWPROP); if (obd->obd_model != NULL) free(obd->obd_model, M_OFWPROP); if (obd->obd_name != NULL) free(obd->obd_name, M_OFWPROP); if (obd->obd_type != NULL) free(obd->obd_type, M_OFWPROP); if (obd->obd_status != NULL) free(obd->obd_status, M_OFWPROP); } int ofw_bus_gen_child_pnpinfo_str(device_t cbdev, device_t child, char *buf, size_t buflen) { *buf = '\0'; if (ofw_bus_get_name(child) != NULL) { strlcat(buf, "name=", buflen); strlcat(buf, ofw_bus_get_name(child), buflen); } if (ofw_bus_get_compat(child) != NULL) { strlcat(buf, " compat=", buflen); strlcat(buf, ofw_bus_get_compat(child), buflen); } return (0); }; const char * ofw_bus_gen_get_compat(device_t bus, device_t dev) { const struct ofw_bus_devinfo *obd; obd = OFW_BUS_GET_DEVINFO(bus, dev); if (obd == NULL) return (NULL); return (obd->obd_compat); } const char * ofw_bus_gen_get_model(device_t bus, device_t dev) { const struct ofw_bus_devinfo *obd; obd = OFW_BUS_GET_DEVINFO(bus, dev); if (obd == NULL) return (NULL); return (obd->obd_model); } const char * ofw_bus_gen_get_name(device_t bus, device_t dev) { const struct ofw_bus_devinfo *obd; obd = OFW_BUS_GET_DEVINFO(bus, dev); if (obd == NULL) return (NULL); return (obd->obd_name); } phandle_t ofw_bus_gen_get_node(device_t bus, device_t dev) { const struct ofw_bus_devinfo *obd; obd = OFW_BUS_GET_DEVINFO(bus, dev); if (obd == NULL) return (0); return (obd->obd_node); } const char * ofw_bus_gen_get_type(device_t bus, device_t dev) { const struct ofw_bus_devinfo *obd; obd = OFW_BUS_GET_DEVINFO(bus, dev); if (obd == NULL) return (NULL); return (obd->obd_type); } const char * ofw_bus_get_status(device_t dev) { const struct ofw_bus_devinfo *obd; obd = OFW_BUS_GET_DEVINFO(device_get_parent(dev), dev); if (obd == NULL) return (NULL); return (obd->obd_status); } int ofw_bus_status_okay(device_t dev) { const char *status; status = ofw_bus_get_status(dev); if (status == NULL || strcmp(status, "okay") == 0 || strcmp(status, "ok") == 0) return (1); return (0); } int ofw_bus_node_status_okay(phandle_t node) { char status[OFW_STATUS_LEN]; int len; len = OF_getproplen(node, "status"); if (len <= 0) return (1); OF_getprop(node, "status", status, OFW_STATUS_LEN); if ((len == 5 && (bcmp(status, "okay", len) == 0)) || (len == 3 && (bcmp(status, "ok", len)))) return (1); return (0); } static int ofw_bus_node_is_compatible_int(const char *compat, int len, const char *onecompat) { int onelen, l, ret; onelen = strlen(onecompat); ret = 0; while (len > 0) { if (strlen(compat) == onelen && strncasecmp(compat, onecompat, onelen) == 0) { /* Found it. */ ret = 1; break; } /* Slide to the next sub-string. */ l = strlen(compat) + 1; compat += l; len -= l; } return (ret); } int ofw_bus_node_is_compatible(phandle_t node, const char *compatstr) { char compat[OFW_COMPAT_LEN]; int len, rv; if ((len = OF_getproplen(node, "compatible")) <= 0) return (0); bzero(compat, OFW_COMPAT_LEN); if (OF_getprop(node, "compatible", compat, OFW_COMPAT_LEN) < 0) return (0); rv = ofw_bus_node_is_compatible_int(compat, len, compatstr); return (rv); } int ofw_bus_is_compatible(device_t dev, const char *onecompat) { phandle_t node; const char *compat; int len; if ((compat = ofw_bus_get_compat(dev)) == NULL) return (0); if ((node = ofw_bus_get_node(dev)) == -1) return (0); /* Get total 'compatible' prop len */ if ((len = OF_getproplen(node, "compatible")) <= 0) return (0); return (ofw_bus_node_is_compatible_int(compat, len, onecompat)); } int ofw_bus_is_compatible_strict(device_t dev, const char *compatible) { const char *compat; size_t len; if ((compat = ofw_bus_get_compat(dev)) == NULL) return (0); len = strlen(compatible); if (strlen(compat) == len && strncasecmp(compat, compatible, len) == 0) return (1); return (0); } const struct ofw_compat_data * ofw_bus_search_compatible(device_t dev, const struct ofw_compat_data *compat) { if (compat == NULL) return NULL; for (; compat->ocd_str != NULL; ++compat) { if (ofw_bus_is_compatible(dev, compat->ocd_str)) break; } return (compat); } int ofw_bus_has_prop(device_t dev, const char *propname) { phandle_t node; if ((node = ofw_bus_get_node(dev)) == -1) return (0); return (OF_hasprop(node, propname)); } void ofw_bus_setup_iinfo(phandle_t node, struct ofw_bus_iinfo *ii, int intrsz) { pcell_t addrc; int msksz; if (OF_getencprop(node, "#address-cells", &addrc, sizeof(addrc)) == -1) addrc = 2; ii->opi_addrc = addrc * sizeof(pcell_t); ii->opi_imapsz = OF_getencprop_alloc(node, "interrupt-map", 1, (void **)&ii->opi_imap); if (ii->opi_imapsz > 0) { msksz = OF_getencprop_alloc(node, "interrupt-map-mask", 1, (void **)&ii->opi_imapmsk); /* * Failure to get the mask is ignored; a full mask is used * then. We barf on bad mask sizes, however. */ if (msksz != -1 && msksz != ii->opi_addrc + intrsz) panic("ofw_bus_setup_iinfo: bad interrupt-map-mask " "property!"); } } int ofw_bus_lookup_imap(phandle_t node, struct ofw_bus_iinfo *ii, void *reg, int regsz, void *pintr, int pintrsz, void *mintr, int mintrsz, phandle_t *iparent) { uint8_t maskbuf[regsz + pintrsz]; int rv; if (ii->opi_imapsz <= 0) return (0); KASSERT(regsz >= ii->opi_addrc, ("ofw_bus_lookup_imap: register size too small: %d < %d", regsz, ii->opi_addrc)); if (node != -1) { rv = OF_getencprop(node, "reg", reg, regsz); if (rv < regsz) panic("ofw_bus_lookup_imap: cannot get reg property"); } return (ofw_bus_search_intrmap(pintr, pintrsz, reg, ii->opi_addrc, ii->opi_imap, ii->opi_imapsz, ii->opi_imapmsk, maskbuf, mintr, mintrsz, iparent)); } /* * Map an interrupt using the firmware reg, interrupt-map and * interrupt-map-mask properties. * The interrupt property to be mapped must be of size intrsz, and pointed to * by intr. The regs property of the node for which the mapping is done must * be passed as regs. This property is an array of register specifications; * the size of the address part of such a specification must be passed as * physsz. Only the first element of the property is used. * imap and imapsz hold the interrupt mask and it's size. * imapmsk is a pointer to the interrupt-map-mask property, which must have * a size of physsz + intrsz; it may be NULL, in which case a full mask is * assumed. * maskbuf must point to a buffer of length physsz + intrsz. * The interrupt is returned in result, which must point to a buffer of length * rintrsz (which gives the expected size of the mapped interrupt). * Returns number of cells in the interrupt if a mapping was found, 0 otherwise. */ int ofw_bus_search_intrmap(void *intr, int intrsz, void *regs, int physsz, void *imap, int imapsz, void *imapmsk, void *maskbuf, void *result, int rintrsz, phandle_t *iparent) { phandle_t parent; uint8_t *ref = maskbuf; uint8_t *uiintr = intr; uint8_t *uiregs = regs; uint8_t *uiimapmsk = imapmsk; uint8_t *mptr; pcell_t paddrsz; pcell_t pintrsz; int i, tsz; if (imapmsk != NULL) { for (i = 0; i < physsz; i++) ref[i] = uiregs[i] & uiimapmsk[i]; for (i = 0; i < intrsz; i++) ref[physsz + i] = uiintr[i] & uiimapmsk[physsz + i]; } else { bcopy(regs, ref, physsz); bcopy(intr, ref + physsz, intrsz); } mptr = imap; i = imapsz; paddrsz = 0; while (i > 0) { bcopy(mptr + physsz + intrsz, &parent, sizeof(parent)); #ifndef OFW_IMAP_NO_IPARENT_ADDR_CELLS /* * Find if we need to read the parent address data. * CHRP-derived OF bindings, including ePAPR-compliant FDTs, * use this as an optional part of the specifier. */ if (OF_getencprop(OF_node_from_xref(parent), "#address-cells", &paddrsz, sizeof(paddrsz)) == -1) paddrsz = 0; /* default */ paddrsz *= sizeof(pcell_t); #endif if (OF_searchencprop(OF_node_from_xref(parent), "#interrupt-cells", &pintrsz, sizeof(pintrsz)) == -1) pintrsz = 1; /* default */ pintrsz *= sizeof(pcell_t); /* Compute the map stride size. */ tsz = physsz + intrsz + sizeof(phandle_t) + paddrsz + pintrsz; KASSERT(i >= tsz, ("ofw_bus_search_intrmap: truncated map")); if (bcmp(ref, mptr, physsz + intrsz) == 0) { bcopy(mptr + physsz + intrsz + sizeof(parent) + paddrsz, result, MIN(rintrsz, pintrsz)); if (iparent != NULL) *iparent = parent; return (pintrsz/sizeof(pcell_t)); } mptr += tsz; i -= tsz; } return (0); } int ofw_bus_msimap(phandle_t node, uint16_t pci_rid, phandle_t *msi_parent, uint32_t *msi_rid) { pcell_t *map, mask, msi_base, rid_base, rid_length; ssize_t len; uint32_t masked_rid; int err, i; /* TODO: This should be OF_searchprop_alloc if we had it */ len = OF_getencprop_alloc(node, "msi-map", sizeof(*map), (void **)&map); if (len < 0) { if (msi_parent != NULL) { *msi_parent = 0; OF_getencprop(node, "msi-parent", msi_parent, sizeof(*msi_parent)); } if (msi_rid != NULL) *msi_rid = pci_rid; return (0); } err = ENOENT; mask = 0xffffffff; OF_getencprop(node, "msi-map-mask", &mask, sizeof(mask)); masked_rid = pci_rid & mask; for (i = 0; i < len; i += 4) { rid_base = map[i + 0]; rid_length = map[i + 3]; if (masked_rid < rid_base || masked_rid >= (rid_base + rid_length)) continue; msi_base = map[i + 2]; if (msi_parent != NULL) *msi_parent = map[i + 1]; if (msi_rid != NULL) *msi_rid = masked_rid - rid_base + msi_base; err = 0; break; } free(map, M_OFWPROP); return (err); } static int ofw_bus_reg_to_rl_helper(device_t dev, phandle_t node, pcell_t acells, pcell_t scells, struct resource_list *rl, const char *reg_source) { uint64_t phys, size; ssize_t i, j, rid, nreg, ret; uint32_t *reg; char *name; /* * This may be just redundant when having ofw_bus_devinfo * but makes this routine independent of it. */ - ret = OF_getprop_alloc(node, "name", sizeof(*name), (void **)&name); + ret = OF_getprop_alloc(node, "name", (void **)&name); if (ret == -1) name = NULL; ret = OF_getencprop_alloc(node, reg_source, sizeof(*reg), (void **)®); nreg = (ret == -1) ? 0 : ret; if (nreg % (acells + scells) != 0) { if (bootverbose) device_printf(dev, "Malformed reg property on <%s>\n", (name == NULL) ? "unknown" : name); nreg = 0; } for (i = 0, rid = 0; i < nreg; i += acells + scells, rid++) { phys = size = 0; for (j = 0; j < acells; j++) { phys <<= 32; phys |= reg[i + j]; } for (j = 0; j < scells; j++) { size <<= 32; size |= reg[i + acells + j]; } /* Skip the dummy reg property of glue devices like ssm(4). */ if (size != 0) resource_list_add(rl, SYS_RES_MEMORY, rid, phys, phys + size - 1, size); } free(name, M_OFWPROP); free(reg, M_OFWPROP); return (0); } int ofw_bus_reg_to_rl(device_t dev, phandle_t node, pcell_t acells, pcell_t scells, struct resource_list *rl) { return (ofw_bus_reg_to_rl_helper(dev, node, acells, scells, rl, "reg")); } int ofw_bus_assigned_addresses_to_rl(device_t dev, phandle_t node, pcell_t acells, pcell_t scells, struct resource_list *rl) { return (ofw_bus_reg_to_rl_helper(dev, node, acells, scells, rl, "assigned-addresses")); } /* * Get interrupt parent for given node. * Returns 0 if interrupt parent doesn't exist. */ phandle_t ofw_bus_find_iparent(phandle_t node) { phandle_t iparent; if (OF_searchencprop(node, "interrupt-parent", &iparent, sizeof(iparent)) == -1) { for (iparent = node; iparent != 0; iparent = OF_parent(iparent)) { if (OF_hasprop(iparent, "interrupt-controller")) break; } iparent = OF_xref_from_node(iparent); } return (iparent); } int ofw_bus_intr_to_rl(device_t dev, phandle_t node, struct resource_list *rl, int *rlen) { phandle_t iparent; uint32_t icells, *intr; int err, i, irqnum, nintr, rid; boolean_t extended; nintr = OF_getencprop_alloc(node, "interrupts", sizeof(*intr), (void **)&intr); if (nintr > 0) { iparent = ofw_bus_find_iparent(node); if (iparent == 0) { device_printf(dev, "No interrupt-parent found, " "assuming direct parent\n"); iparent = OF_parent(node); iparent = OF_xref_from_node(iparent); } if (OF_searchencprop(OF_node_from_xref(iparent), "#interrupt-cells", &icells, sizeof(icells)) == -1) { device_printf(dev, "Missing #interrupt-cells " "property, assuming <1>\n"); icells = 1; } if (icells < 1 || icells > nintr) { device_printf(dev, "Invalid #interrupt-cells property " "value <%d>, assuming <1>\n", icells); icells = 1; } extended = false; } else { nintr = OF_getencprop_alloc(node, "interrupts-extended", sizeof(*intr), (void **)&intr); if (nintr <= 0) return (0); extended = true; } err = 0; rid = 0; for (i = 0; i < nintr; i += icells) { if (extended) { iparent = intr[i++]; if (OF_searchencprop(OF_node_from_xref(iparent), "#interrupt-cells", &icells, sizeof(icells)) == -1) { device_printf(dev, "Missing #interrupt-cells " "property\n"); err = ENOENT; break; } if (icells < 1 || (i + icells) > nintr) { device_printf(dev, "Invalid #interrupt-cells " "property value <%d>\n", icells); err = ERANGE; break; } } irqnum = ofw_bus_map_intr(dev, iparent, icells, &intr[i]); resource_list_add(rl, SYS_RES_IRQ, rid++, irqnum, irqnum, 1); } if (rlen != NULL) *rlen = rid; free(intr, M_OFWPROP); return (err); } int ofw_bus_intr_by_rid(device_t dev, phandle_t node, int wanted_rid, phandle_t *producer, int *ncells, pcell_t **cells) { phandle_t iparent; uint32_t icells, *intr; int err, i, nintr, rid; boolean_t extended; nintr = OF_getencprop_alloc(node, "interrupts", sizeof(*intr), (void **)&intr); if (nintr > 0) { iparent = ofw_bus_find_iparent(node); if (iparent == 0) { device_printf(dev, "No interrupt-parent found, " "assuming direct parent\n"); iparent = OF_parent(node); iparent = OF_xref_from_node(iparent); } if (OF_searchencprop(OF_node_from_xref(iparent), "#interrupt-cells", &icells, sizeof(icells)) == -1) { device_printf(dev, "Missing #interrupt-cells " "property, assuming <1>\n"); icells = 1; } if (icells < 1 || icells > nintr) { device_printf(dev, "Invalid #interrupt-cells property " "value <%d>, assuming <1>\n", icells); icells = 1; } extended = false; } else { nintr = OF_getencprop_alloc(node, "interrupts-extended", sizeof(*intr), (void **)&intr); if (nintr <= 0) return (ESRCH); extended = true; } err = ESRCH; rid = 0; for (i = 0; i < nintr; i += icells, rid++) { if (extended) { iparent = intr[i++]; if (OF_searchencprop(OF_node_from_xref(iparent), "#interrupt-cells", &icells, sizeof(icells)) == -1) { device_printf(dev, "Missing #interrupt-cells " "property\n"); err = ENOENT; break; } if (icells < 1 || (i + icells) > nintr) { device_printf(dev, "Invalid #interrupt-cells " "property value <%d>\n", icells); err = ERANGE; break; } } if (rid == wanted_rid) { *cells = malloc(icells * sizeof(**cells), M_OFWPROP, M_WAITOK); *producer = iparent; *ncells= icells; memcpy(*cells, intr + i, icells * sizeof(**cells)); err = 0; break; } } free(intr, M_OFWPROP); return (err); } phandle_t ofw_bus_find_child(phandle_t start, const char *child_name) { char *name; int ret; phandle_t child; for (child = OF_child(start); child != 0; child = OF_peer(child)) { - ret = OF_getprop_alloc(child, "name", sizeof(*name), (void **)&name); + ret = OF_getprop_alloc(child, "name", (void **)&name); if (ret == -1) continue; if (strcmp(name, child_name) == 0) { free(name, M_OFWPROP); return (child); } free(name, M_OFWPROP); } return (0); } phandle_t ofw_bus_find_compatible(phandle_t node, const char *onecompat) { phandle_t child, ret; /* * Traverse all children of 'start' node, and find first with * matching 'compatible' property. */ for (child = OF_child(node); child != 0; child = OF_peer(child)) { if (ofw_bus_node_is_compatible(child, onecompat) != 0) return (child); ret = ofw_bus_find_compatible(child, onecompat); if (ret != 0) return (ret); } return (0); } /** * @brief Return child of bus whose phandle is node * * A direct child of @p will be returned if it its phandle in the * OFW tree is @p node. Otherwise, NULL is returned. * * @param bus The bus to examine * @param node The phandle_t to look for. */ device_t ofw_bus_find_child_device_by_phandle(device_t bus, phandle_t node) { device_t *children, retval, child; int nkid, i; /* * Nothing can match the flag value for no node. */ if (node == -1) return (NULL); /* * Search the children for a match. We microoptimize * a bit by not using ofw_bus_get since we already know * the parent. We do not recurse. */ if (device_get_children(bus, &children, &nkid) != 0) return (NULL); retval = NULL; for (i = 0; i < nkid; i++) { child = children[i]; if (OFW_BUS_GET_NODE(bus, child) == node) { retval = child; break; } } free(children, M_TEMP); return (retval); } /* * Parse property that contain list of xrefs and values * (like standard "clocks" and "resets" properties) * Input arguments: * node - consumers device node * list_name - name of parsed list - "clocks" * cells_name - name of size property - "#clock-cells" * idx - the index of the requested list entry, or, if -1, an indication * to return the number of entries in the parsed list. * Output arguments: * producer - handle of producer * ncells - number of cells in result or the number of items in the list when * idx == -1. * cells - array of decoded cells */ static int ofw_bus_parse_xref_list_internal(phandle_t node, const char *list_name, const char *cells_name, int idx, phandle_t *producer, int *ncells, pcell_t **cells) { phandle_t pnode; phandle_t *elems; uint32_t pcells; int rv, i, j, nelems, cnt; elems = NULL; nelems = OF_getencprop_alloc(node, list_name, sizeof(*elems), (void **)&elems); if (nelems <= 0) return (ENOENT); rv = (idx == -1) ? 0 : ENOENT; for (i = 0, cnt = 0; i < nelems; i += pcells, cnt++) { pnode = elems[i++]; if (OF_getencprop(OF_node_from_xref(pnode), cells_name, &pcells, sizeof(pcells)) == -1) { printf("Missing %s property\n", cells_name); rv = ENOENT; break; } if ((i + pcells) > nelems) { printf("Invalid %s property value <%d>\n", cells_name, pcells); rv = ERANGE; break; } if (cnt == idx) { *cells= malloc(pcells * sizeof(**cells), M_OFWPROP, M_WAITOK); *producer = pnode; *ncells = pcells; for (j = 0; j < pcells; j++) (*cells)[j] = elems[i + j]; rv = 0; break; } } if (elems != NULL) free(elems, M_OFWPROP); if (idx == -1 && rv == 0) *ncells = cnt; return (rv); } /* * Parse property that contain list of xrefs and values * (like standard "clocks" and "resets" properties) * Input arguments: * node - consumers device node * list_name - name of parsed list - "clocks" * cells_name - name of size property - "#clock-cells" * idx - the index of the requested list entry (>= 0) * Output arguments: * producer - handle of producer * ncells - number of cells in result * cells - array of decoded cells */ int ofw_bus_parse_xref_list_alloc(phandle_t node, const char *list_name, const char *cells_name, int idx, phandle_t *producer, int *ncells, pcell_t **cells) { KASSERT(idx >= 0, ("ofw_bus_parse_xref_list_alloc: negative index supplied")); return (ofw_bus_parse_xref_list_internal(node, list_name, cells_name, idx, producer, ncells, cells)); } /* * Parse property that contain list of xrefs and values * (like standard "clocks" and "resets" properties) * and determine the number of items in the list * Input arguments: * node - consumers device node * list_name - name of parsed list - "clocks" * cells_name - name of size property - "#clock-cells" * Output arguments: * count - number of items in list */ int ofw_bus_parse_xref_list_get_length(phandle_t node, const char *list_name, const char *cells_name, int *count) { return (ofw_bus_parse_xref_list_internal(node, list_name, cells_name, -1, NULL, count, NULL)); } /* * Find index of string in string list property (case sensitive). */ int ofw_bus_find_string_index(phandle_t node, const char *list_name, const char *name, int *idx) { char *elems; int rv, i, cnt, nelems; elems = NULL; - nelems = OF_getprop_alloc(node, list_name, 1, (void **)&elems); + nelems = OF_getprop_alloc(node, list_name, (void **)&elems); if (nelems <= 0) return (ENOENT); rv = ENOENT; for (i = 0, cnt = 0; i < nelems; cnt++) { if (strcmp(elems + i, name) == 0) { *idx = cnt; rv = 0; break; } i += strlen(elems + i) + 1; } if (elems != NULL) free(elems, M_OFWPROP); return (rv); } /* * Create zero terminated array of strings from string list property. */ int ofw_bus_string_list_to_array(phandle_t node, const char *list_name, const char ***out_array) { char *elems, *tptr; const char **array; int i, cnt, nelems, len; elems = NULL; - nelems = OF_getprop_alloc(node, list_name, 1, (void **)&elems); + nelems = OF_getprop_alloc(node, list_name, (void **)&elems); if (nelems <= 0) return (nelems); /* Count number of strings. */ for (i = 0, cnt = 0; i < nelems; cnt++) i += strlen(elems + i) + 1; /* Allocate space for arrays and all strings. */ array = malloc((cnt + 1) * sizeof(char *) + nelems, M_OFWPROP, M_WAITOK); /* Get address of first string. */ tptr = (char *)(array + cnt + 1); /* Copy strings. */ memcpy(tptr, elems, nelems); free(elems, M_OFWPROP); /* Fill string pointers. */ for (i = 0, cnt = 0; i < nelems; cnt++) { len = strlen(tptr) + 1; array[cnt] = tptr; i += len; tptr += len; } array[cnt] = NULL; *out_array = array; return (cnt); } Index: head/sys/dev/ofw/openfirm.c =================================================================== --- head/sys/dev/ofw/openfirm.c (revision 332309) +++ head/sys/dev/ofw/openfirm.c (revision 332310) @@ -1,813 +1,840 @@ /* $NetBSD: Locore.c,v 1.7 2000/08/20 07:04:59 tsubai Exp $ */ /*- * SPDX-License-Identifier: BSD-4-Clause * * Copyright (C) 1995, 1996 Wolfgang Solfrank. * Copyright (C) 1995, 1996 TooLs GmbH. * 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. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by TooLs GmbH. * 4. The name of TooLs GmbH may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY TOOLS GMBH ``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 TOOLS GMBH 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. */ /*- * Copyright (C) 2000 Benno Rice. * 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 Benno Rice ``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 TOOLS GMBH 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$"); #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #include "ofw_if.h" static void OF_putchar(int c, void *arg); MALLOC_DEFINE(M_OFWPROP, "openfirm", "Open Firmware properties"); static ihandle_t stdout; static ofw_def_t *ofw_def_impl = NULL; static ofw_t ofw_obj; static struct ofw_kobj ofw_kernel_obj; static struct kobj_ops ofw_kernel_kops; struct xrefinfo { phandle_t xref; phandle_t node; device_t dev; SLIST_ENTRY(xrefinfo) next_entry; }; static SLIST_HEAD(, xrefinfo) xreflist = SLIST_HEAD_INITIALIZER(xreflist); static struct mtx xreflist_lock; static boolean_t xref_init_done; #define FIND_BY_XREF 0 #define FIND_BY_NODE 1 #define FIND_BY_DEV 2 /* * xref-phandle-device lookup helper routines. * * As soon as we are able to use malloc(), walk the node tree and build a list * of info that cross-references node handles, xref handles, and device_t * instances. This list exists primarily to allow association of a device_t * with an xref handle, but it is also used to speed up translation between xref * and node handles. Before malloc() is available we have to recursively search * the node tree each time we want to translate between a node and xref handle. * Afterwards we can do the translations by searching this much shorter list. */ static void xrefinfo_create(phandle_t node) { struct xrefinfo * xi; phandle_t child, xref; /* * Recursively descend from parent, looking for nodes with a property * named either "phandle", "ibm,phandle", or "linux,phandle". For each * such node found create an entry in the xreflist. */ for (child = OF_child(node); child != 0; child = OF_peer(child)) { xrefinfo_create(child); if (OF_getencprop(child, "phandle", &xref, sizeof(xref)) == -1 && OF_getencprop(child, "ibm,phandle", &xref, sizeof(xref)) == -1 && OF_getencprop(child, "linux,phandle", &xref, sizeof(xref)) == -1) continue; xi = malloc(sizeof(*xi), M_OFWPROP, M_WAITOK | M_ZERO); xi->node = child; xi->xref = xref; SLIST_INSERT_HEAD(&xreflist, xi, next_entry); } } static void xrefinfo_init(void *unsed) { /* * There is no locking during this init because it runs much earlier * than any of the clients/consumers of the xref list data, but we do * initialize the mutex that will be used for access later. */ mtx_init(&xreflist_lock, "OF xreflist lock", NULL, MTX_DEF); xrefinfo_create(OF_peer(0)); xref_init_done = true; } SYSINIT(xrefinfo, SI_SUB_KMEM, SI_ORDER_ANY, xrefinfo_init, NULL); static struct xrefinfo * xrefinfo_find(uintptr_t key, int find_by) { struct xrefinfo *rv, *xi; rv = NULL; mtx_lock(&xreflist_lock); SLIST_FOREACH(xi, &xreflist, next_entry) { if ((find_by == FIND_BY_XREF && (phandle_t)key == xi->xref) || (find_by == FIND_BY_NODE && (phandle_t)key == xi->node) || (find_by == FIND_BY_DEV && key == (uintptr_t)xi->dev)) { rv = xi; break; } } mtx_unlock(&xreflist_lock); return (rv); } static struct xrefinfo * xrefinfo_add(phandle_t node, phandle_t xref, device_t dev) { struct xrefinfo *xi; xi = malloc(sizeof(*xi), M_OFWPROP, M_WAITOK); xi->node = node; xi->xref = xref; xi->dev = dev; mtx_lock(&xreflist_lock); SLIST_INSERT_HEAD(&xreflist, xi, next_entry); mtx_unlock(&xreflist_lock); return (xi); } /* * OFW install routines. Highest priority wins, equal priority also * overrides allowing last-set to win. */ SET_DECLARE(ofw_set, ofw_def_t); boolean_t OF_install(char *name, int prio) { ofw_def_t *ofwp, **ofwpp; static int curr_prio = 0; /* Allow OF layer to be uninstalled */ if (name == NULL) { ofw_def_impl = NULL; return (FALSE); } /* * Try and locate the OFW kobj corresponding to the name. */ SET_FOREACH(ofwpp, ofw_set) { ofwp = *ofwpp; if (ofwp->name && !strcmp(ofwp->name, name) && prio >= curr_prio) { curr_prio = prio; ofw_def_impl = ofwp; return (TRUE); } } return (FALSE); } /* Initializer */ int OF_init(void *cookie) { phandle_t chosen; int rv; if (ofw_def_impl == NULL) return (-1); ofw_obj = &ofw_kernel_obj; /* * Take care of compiling the selected class, and * then statically initialize the OFW object. */ kobj_class_compile_static(ofw_def_impl, &ofw_kernel_kops); kobj_init_static((kobj_t)ofw_obj, ofw_def_impl); rv = OFW_INIT(ofw_obj, cookie); if ((chosen = OF_finddevice("/chosen")) != -1) if (OF_getencprop(chosen, "stdout", &stdout, sizeof(stdout)) == -1) stdout = -1; return (rv); } static void OF_putchar(int c, void *arg __unused) { char cbuf; if (c == '\n') { cbuf = '\r'; OF_write(stdout, &cbuf, 1); } cbuf = c; OF_write(stdout, &cbuf, 1); } void OF_printf(const char *fmt, ...) { va_list va; va_start(va, fmt); (void)kvprintf(fmt, OF_putchar, NULL, 10, va); va_end(va); } /* * Generic functions */ /* Test to see if a service exists. */ int OF_test(const char *name) { if (ofw_def_impl == NULL) return (-1); return (OFW_TEST(ofw_obj, name)); } int OF_interpret(const char *cmd, int nreturns, ...) { va_list ap; cell_t slots[16]; int i = 0; int status; if (ofw_def_impl == NULL) return (-1); status = OFW_INTERPRET(ofw_obj, cmd, nreturns, slots); if (status == -1) return (status); va_start(ap, nreturns); while (i < nreturns) *va_arg(ap, cell_t *) = slots[i++]; va_end(ap); return (status); } /* * Device tree functions */ /* Return the next sibling of this node or 0. */ phandle_t OF_peer(phandle_t node) { if (ofw_def_impl == NULL) return (0); return (OFW_PEER(ofw_obj, node)); } /* Return the first child of this node or 0. */ phandle_t OF_child(phandle_t node) { if (ofw_def_impl == NULL) return (0); return (OFW_CHILD(ofw_obj, node)); } /* Return the parent of this node or 0. */ phandle_t OF_parent(phandle_t node) { if (ofw_def_impl == NULL) return (0); return (OFW_PARENT(ofw_obj, node)); } /* Return the package handle that corresponds to an instance handle. */ phandle_t OF_instance_to_package(ihandle_t instance) { if (ofw_def_impl == NULL) return (-1); return (OFW_INSTANCE_TO_PACKAGE(ofw_obj, instance)); } /* Get the length of a property of a package. */ ssize_t OF_getproplen(phandle_t package, const char *propname) { if (ofw_def_impl == NULL) return (-1); return (OFW_GETPROPLEN(ofw_obj, package, propname)); } /* Check existence of a property of a package. */ int OF_hasprop(phandle_t package, const char *propname) { return (OF_getproplen(package, propname) >= 0 ? 1 : 0); } /* Get the value of a property of a package. */ ssize_t OF_getprop(phandle_t package, const char *propname, void *buf, size_t buflen) { if (ofw_def_impl == NULL) return (-1); return (OFW_GETPROP(ofw_obj, package, propname, buf, buflen)); } ssize_t OF_getencprop(phandle_t node, const char *propname, pcell_t *buf, size_t len) { ssize_t retval; int i; KASSERT(len % 4 == 0, ("Need a multiple of 4 bytes")); retval = OF_getprop(node, propname, buf, len); if (retval <= 0) return (retval); for (i = 0; i < len/4; i++) buf[i] = be32toh(buf[i]); return (retval); } /* * Recursively search the node and its parent for the given property, working * downward from the node to the device tree root. Returns the value of the * first match. */ ssize_t OF_searchprop(phandle_t node, const char *propname, void *buf, size_t len) { ssize_t rv; for (; node != 0; node = OF_parent(node)) if ((rv = OF_getprop(node, propname, buf, len)) != -1) return (rv); return (-1); } ssize_t OF_searchencprop(phandle_t node, const char *propname, void *buf, size_t len) { ssize_t rv; for (; node != 0; node = OF_parent(node)) if ((rv = OF_getencprop(node, propname, buf, len)) != -1) return (rv); return (-1); } /* * Store the value of a property of a package into newly allocated memory + * (using the M_OFWPROP malloc pool and M_WAITOK). + */ +ssize_t +OF_getprop_alloc(phandle_t package, const char *propname, void **buf) +{ + int len; + + *buf = NULL; + if ((len = OF_getproplen(package, propname)) == -1) + return (-1); + + if (len > 0) { + *buf = malloc(len, M_OFWPROP, M_WAITOK); + if (OF_getprop(package, propname, *buf, len) == -1) { + free(*buf, M_OFWPROP); + *buf = NULL; + return (-1); + } + } + return (len); +} + +/* + * Store the value of a property of a package into newly allocated memory * (using the M_OFWPROP malloc pool and M_WAITOK). elsz is the size of a * single element, the number of elements is return in number. */ ssize_t -OF_getprop_alloc(phandle_t package, const char *propname, int elsz, void **buf) +OF_getprop_alloc_multi(phandle_t package, const char *propname, int elsz, void **buf) { int len; *buf = NULL; if ((len = OF_getproplen(package, propname)) == -1 || len % elsz != 0) return (-1); - *buf = malloc(len, M_OFWPROP, M_WAITOK); - if (OF_getprop(package, propname, *buf, len) == -1) { - free(*buf, M_OFWPROP); - *buf = NULL; - return (-1); + if (len > 0) { + *buf = malloc(len, M_OFWPROP, M_WAITOK); + if (OF_getprop(package, propname, *buf, len) == -1) { + free(*buf, M_OFWPROP); + *buf = NULL; + return (-1); + } } return (len / elsz); } + ssize_t OF_getencprop_alloc(phandle_t package, const char *name, int elsz, void **buf) { ssize_t retval; pcell_t *cell; int i; - retval = OF_getprop_alloc(package, name, elsz, buf); + retval = OF_getprop_alloc_multi(package, name, elsz, buf); if (retval == -1) return (-1); if (retval * elsz % 4 != 0) { free(*buf, M_OFWPROP); *buf = NULL; return (-1); } cell = *buf; for (i = 0; i < retval * elsz / 4; i++) cell[i] = be32toh(cell[i]); return (retval); } /* Free buffer allocated by OF_getencprop_alloc or OF_getprop_alloc */ void OF_prop_free(void *buf) { free(buf, M_OFWPROP); } /* Get the next property of a package. */ int OF_nextprop(phandle_t package, const char *previous, char *buf, size_t size) { if (ofw_def_impl == NULL) return (-1); return (OFW_NEXTPROP(ofw_obj, package, previous, buf, size)); } /* Set the value of a property of a package. */ int OF_setprop(phandle_t package, const char *propname, const void *buf, size_t len) { if (ofw_def_impl == NULL) return (-1); return (OFW_SETPROP(ofw_obj, package, propname, buf,len)); } /* Convert a device specifier to a fully qualified pathname. */ ssize_t OF_canon(const char *device, char *buf, size_t len) { if (ofw_def_impl == NULL) return (-1); return (OFW_CANON(ofw_obj, device, buf, len)); } /* Return a package handle for the specified device. */ phandle_t OF_finddevice(const char *device) { if (ofw_def_impl == NULL) return (-1); return (OFW_FINDDEVICE(ofw_obj, device)); } /* Return the fully qualified pathname corresponding to an instance. */ ssize_t OF_instance_to_path(ihandle_t instance, char *buf, size_t len) { if (ofw_def_impl == NULL) return (-1); return (OFW_INSTANCE_TO_PATH(ofw_obj, instance, buf, len)); } /* Return the fully qualified pathname corresponding to a package. */ ssize_t OF_package_to_path(phandle_t package, char *buf, size_t len) { if (ofw_def_impl == NULL) return (-1); return (OFW_PACKAGE_TO_PATH(ofw_obj, package, buf, len)); } /* Look up effective phandle (see FDT/PAPR spec) */ static phandle_t OF_child_xref_phandle(phandle_t parent, phandle_t xref) { phandle_t child, rxref; /* * Recursively descend from parent, looking for a node with a property * named either "phandle", "ibm,phandle", or "linux,phandle" that * matches the xref we are looking for. */ for (child = OF_child(parent); child != 0; child = OF_peer(child)) { rxref = OF_child_xref_phandle(child, xref); if (rxref != -1) return (rxref); if (OF_getencprop(child, "phandle", &rxref, sizeof(rxref)) == -1 && OF_getencprop(child, "ibm,phandle", &rxref, sizeof(rxref)) == -1 && OF_getencprop(child, "linux,phandle", &rxref, sizeof(rxref)) == -1) continue; if (rxref == xref) return (child); } return (-1); } phandle_t OF_node_from_xref(phandle_t xref) { struct xrefinfo *xi; phandle_t node; if (xref_init_done) { if ((xi = xrefinfo_find(xref, FIND_BY_XREF)) == NULL) return (xref); return (xi->node); } if ((node = OF_child_xref_phandle(OF_peer(0), xref)) == -1) return (xref); return (node); } phandle_t OF_xref_from_node(phandle_t node) { struct xrefinfo *xi; phandle_t xref; if (xref_init_done) { if ((xi = xrefinfo_find(node, FIND_BY_NODE)) == NULL) return (node); return (xi->xref); } if (OF_getencprop(node, "phandle", &xref, sizeof(xref)) == -1 && OF_getencprop(node, "ibm,phandle", &xref, sizeof(xref)) == -1 && OF_getencprop(node, "linux,phandle", &xref, sizeof(xref)) == -1) return (node); return (xref); } device_t OF_device_from_xref(phandle_t xref) { struct xrefinfo *xi; if (xref_init_done) { if ((xi = xrefinfo_find(xref, FIND_BY_XREF)) == NULL) return (NULL); return (xi->dev); } panic("Attempt to find device before xreflist_init"); } phandle_t OF_xref_from_device(device_t dev) { struct xrefinfo *xi; if (xref_init_done) { if ((xi = xrefinfo_find((uintptr_t)dev, FIND_BY_DEV)) == NULL) return (0); return (xi->xref); } panic("Attempt to find xref before xreflist_init"); } int OF_device_register_xref(phandle_t xref, device_t dev) { struct xrefinfo *xi; /* * If the given xref handle doesn't already exist in the list then we * add a list entry. In theory this can only happen on a system where * nodes don't contain phandle properties and xref and node handles are * synonymous, so the xref handle is added as the node handle as well. */ if (xref_init_done) { if ((xi = xrefinfo_find(xref, FIND_BY_XREF)) == NULL) xrefinfo_add(xref, xref, dev); else xi->dev = dev; return (0); } panic("Attempt to register device before xreflist_init"); } /* Call the method in the scope of a given instance. */ int OF_call_method(const char *method, ihandle_t instance, int nargs, int nreturns, ...) { va_list ap; cell_t args_n_results[12]; int n, status; if (nargs > 6 || ofw_def_impl == NULL) return (-1); va_start(ap, nreturns); for (n = 0; n < nargs; n++) args_n_results[n] = va_arg(ap, cell_t); status = OFW_CALL_METHOD(ofw_obj, instance, method, nargs, nreturns, args_n_results); if (status != 0) return (status); for (; n < nargs + nreturns; n++) *va_arg(ap, cell_t *) = args_n_results[n]; va_end(ap); return (0); } /* * Device I/O functions */ /* Open an instance for a device. */ ihandle_t OF_open(const char *device) { if (ofw_def_impl == NULL) return (0); return (OFW_OPEN(ofw_obj, device)); } /* Close an instance. */ void OF_close(ihandle_t instance) { if (ofw_def_impl == NULL) return; OFW_CLOSE(ofw_obj, instance); } /* Read from an instance. */ ssize_t OF_read(ihandle_t instance, void *addr, size_t len) { if (ofw_def_impl == NULL) return (-1); return (OFW_READ(ofw_obj, instance, addr, len)); } /* Write to an instance. */ ssize_t OF_write(ihandle_t instance, const void *addr, size_t len) { if (ofw_def_impl == NULL) return (-1); return (OFW_WRITE(ofw_obj, instance, addr, len)); } /* Seek to a position. */ int OF_seek(ihandle_t instance, uint64_t pos) { if (ofw_def_impl == NULL) return (-1); return (OFW_SEEK(ofw_obj, instance, pos)); } /* * Memory functions */ /* Claim an area of memory. */ void * OF_claim(void *virt, size_t size, u_int align) { if (ofw_def_impl == NULL) return ((void *)-1); return (OFW_CLAIM(ofw_obj, virt, size, align)); } /* Release an area of memory. */ void OF_release(void *virt, size_t size) { if (ofw_def_impl == NULL) return; OFW_RELEASE(ofw_obj, virt, size); } /* * Control transfer functions */ /* Suspend and drop back to the Open Firmware interface. */ void OF_enter() { if (ofw_def_impl == NULL) return; OFW_ENTER(ofw_obj); } /* Shut down and drop back to the Open Firmware interface. */ void OF_exit() { if (ofw_def_impl == NULL) panic("OF_exit: Open Firmware not available"); /* Should not return */ OFW_EXIT(ofw_obj); for (;;) /* just in case */ ; } Index: head/sys/dev/ofw/openfirm.h =================================================================== --- head/sys/dev/ofw/openfirm.h (revision 332309) +++ head/sys/dev/ofw/openfirm.h (revision 332310) @@ -1,186 +1,188 @@ /* $NetBSD: openfirm.h,v 1.1 1998/05/15 10:16:00 tsubai Exp $ */ /*- * SPDX-License-Identifier: BSD-4-Clause * * Copyright (C) 1995, 1996 Wolfgang Solfrank. * Copyright (C) 1995, 1996 TooLs GmbH. * 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. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by TooLs GmbH. * 4. The name of TooLs GmbH may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY TOOLS GMBH ``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 TOOLS GMBH 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. */ /* * Copyright (C) 2000 Benno Rice. * 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 Benno Rice ``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 TOOLS GMBH 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. * * $FreeBSD$ */ #ifndef _DEV_OPENFIRM_H_ #define _DEV_OPENFIRM_H_ #include #include /* * Prototypes for Open Firmware Interface Routines */ typedef uint32_t ihandle_t; typedef uint32_t phandle_t; typedef uint32_t pcell_t; #ifdef _KERNEL #include #include MALLOC_DECLARE(M_OFWPROP); /* * Open Firmware interface initialization. OF_install installs the named * interface as the Open Firmware access mechanism, OF_init initializes it. */ boolean_t OF_install(char *name, int prio); int OF_init(void *cookie); /* * Known Open Firmware interface names */ #define OFW_STD_DIRECT "ofw_std" /* Standard OF interface */ #define OFW_STD_REAL "ofw_real" /* Real-mode OF interface */ #define OFW_STD_32BIT "ofw_32bit" /* 32-bit OF interface */ #define OFW_FDT "ofw_fdt" /* Flattened Device Tree */ /* Generic functions */ int OF_test(const char *name); void OF_printf(const char *fmt, ...); /* Device tree functions */ phandle_t OF_peer(phandle_t node); phandle_t OF_child(phandle_t node); phandle_t OF_parent(phandle_t node); ssize_t OF_getproplen(phandle_t node, const char *propname); ssize_t OF_getprop(phandle_t node, const char *propname, void *buf, size_t len); ssize_t OF_getencprop(phandle_t node, const char *prop, pcell_t *buf, size_t len); /* Same as getprop, but maintains endianness */ int OF_hasprop(phandle_t node, const char *propname); ssize_t OF_searchprop(phandle_t node, const char *propname, void *buf, size_t len); ssize_t OF_searchencprop(phandle_t node, const char *propname, void *buf, size_t len); ssize_t OF_getprop_alloc(phandle_t node, const char *propname, + void **buf); +ssize_t OF_getprop_alloc_multi(phandle_t node, const char *propname, int elsz, void **buf); ssize_t OF_getencprop_alloc(phandle_t node, const char *propname, int elsz, void **buf); void OF_prop_free(void *buf); int OF_nextprop(phandle_t node, const char *propname, char *buf, size_t len); int OF_setprop(phandle_t node, const char *name, const void *buf, size_t len); ssize_t OF_canon(const char *path, char *buf, size_t len); phandle_t OF_finddevice(const char *path); ssize_t OF_package_to_path(phandle_t node, char *buf, size_t len); /* * Some OF implementations (IBM, FDT) have a concept of effective phandles * used for device-tree cross-references. Given one of these, returns the * real phandle. If one can't be found (or running on OF implementations * without this property), returns its input. */ phandle_t OF_node_from_xref(phandle_t xref); phandle_t OF_xref_from_node(phandle_t node); /* * When properties contain references to other nodes using xref handles it is * often necessary to use interfaces provided by the driver for the referenced * instance. These routines allow a driver that provides such an interface to * register its association with an xref handle, and for other drivers to obtain * the device_t associated with an xref handle. */ device_t OF_device_from_xref(phandle_t xref); phandle_t OF_xref_from_device(device_t dev); int OF_device_register_xref(phandle_t xref, device_t dev); /* Device I/O functions */ ihandle_t OF_open(const char *path); void OF_close(ihandle_t instance); ssize_t OF_read(ihandle_t instance, void *buf, size_t len); ssize_t OF_write(ihandle_t instance, const void *buf, size_t len); int OF_seek(ihandle_t instance, uint64_t where); phandle_t OF_instance_to_package(ihandle_t instance); ssize_t OF_instance_to_path(ihandle_t instance, char *buf, size_t len); int OF_call_method(const char *method, ihandle_t instance, int nargs, int nreturns, ...); /* Memory functions */ void *OF_claim(void *virtrequest, size_t size, u_int align); void OF_release(void *virt, size_t size); /* Control transfer functions */ void OF_enter(void); void OF_exit(void) __attribute__((noreturn)); /* User interface functions */ int OF_interpret(const char *cmd, int nreturns, ...); /* * Decode the Nth register property of the given device node and create a bus * space tag and handle for accessing it. This is for use in setting up things * like early console output before newbus is available. The implementation is * machine-dependent, and sparc uses a different function signature as well. */ #ifndef __sparc64__ int OF_decode_addr(phandle_t dev, int regno, bus_space_tag_t *ptag, bus_space_handle_t *phandle, bus_size_t *sz); #endif #endif /* _KERNEL */ #endif /* _DEV_OPENFIRM_H_ */ Index: head/sys/dev/usb/controller/ehci_fsl.c =================================================================== --- head/sys/dev/usb/controller/ehci_fsl.c (revision 332309) +++ head/sys/dev/usb/controller/ehci_fsl.c (revision 332310) @@ -1,429 +1,429 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2010-2012 Semihalf * 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$"); #include "opt_bus.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "opt_platform.h" /* * Register the driver */ /* Forward declarations */ static int fsl_ehci_attach(device_t self); static int fsl_ehci_detach(device_t self); static int fsl_ehci_probe(device_t self); static device_method_t ehci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, fsl_ehci_probe), DEVMETHOD(device_attach, fsl_ehci_attach), DEVMETHOD(device_detach, fsl_ehci_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), /* Bus interface */ DEVMETHOD(bus_print_child, bus_generic_print_child), { 0, 0 } }; /* kobj_class definition */ static driver_t ehci_driver = { "ehci", ehci_methods, sizeof(struct ehci_softc) }; static devclass_t ehci_devclass; DRIVER_MODULE(ehci, simplebus, ehci_driver, ehci_devclass, 0, 0); MODULE_DEPEND(ehci, usb, 1, 1, 1); /* * Private defines */ #define FSL_EHCI_REG_OFF 0x100 #define FSL_EHCI_REG_SIZE 0x300 /* * Internal interface registers' offsets. * Offsets from 0x000 ehci dev space, big-endian access. */ enum internal_reg { SNOOP1 = 0x400, SNOOP2 = 0x404, AGE_CNT_THRESH = 0x408, SI_CTRL = 0x410, CONTROL = 0x500 }; /* CONTROL register bit flags */ enum control_flags { USB_EN = 0x00000004, UTMI_PHY_EN = 0x00000200, ULPI_INT_EN = 0x00000001 }; /* SI_CTRL register bit flags */ enum si_ctrl_flags { FETCH_32 = 1, FETCH_64 = 0 }; #define SNOOP_RANGE_2GB 0x1E /* * Operational registers' offsets. * Offsets from USBCMD register, little-endian access. */ enum special_op_reg { USBMODE = 0x0A8, PORTSC = 0x084, ULPI_VIEWPORT = 0x70 }; /* USBMODE register bit flags */ enum usbmode_flags { HOST_MODE = 0x3, DEVICE_MODE = 0x2 }; #define PORT_POWER_MASK 0x00001000 /* * Private methods */ static void set_to_host_mode(ehci_softc_t *sc) { int tmp; tmp = bus_space_read_4(sc->sc_io_tag, sc->sc_io_hdl, USBMODE); bus_space_write_4(sc->sc_io_tag, sc->sc_io_hdl, USBMODE, tmp | HOST_MODE); } static void enable_usb(device_t dev, bus_space_tag_t iot, bus_space_handle_t ioh) { int tmp; phandle_t node; char *phy_type; phy_type = NULL; tmp = bus_space_read_4(iot, ioh, CONTROL) | USB_EN; node = ofw_bus_get_node(dev); if ((node != 0) && - (OF_getprop_alloc(node, "phy_type", 1, (void **)&phy_type) > 0)) { + (OF_getprop_alloc(node, "phy_type", (void **)&phy_type) > 0)) { if (strncasecmp(phy_type, "utmi", strlen("utmi")) == 0) tmp |= UTMI_PHY_EN; OF_prop_free(phy_type); } bus_space_write_4(iot, ioh, CONTROL, tmp); } static void set_32b_prefetch(bus_space_tag_t iot, bus_space_handle_t ioh) { bus_space_write_4(iot, ioh, SI_CTRL, FETCH_32); } static void set_snooping(bus_space_tag_t iot, bus_space_handle_t ioh) { bus_space_write_4(iot, ioh, SNOOP1, SNOOP_RANGE_2GB); bus_space_write_4(iot, ioh, SNOOP2, 0x80000000 | SNOOP_RANGE_2GB); } static void clear_port_power(ehci_softc_t *sc) { int tmp; tmp = bus_space_read_4(sc->sc_io_tag, sc->sc_io_hdl, PORTSC); bus_space_write_4(sc->sc_io_tag, sc->sc_io_hdl, PORTSC, tmp & ~PORT_POWER_MASK); } /* * Public methods */ static int fsl_ehci_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (((ofw_bus_is_compatible(dev, "fsl-usb2-dr")) == 0) && ((ofw_bus_is_compatible(dev, "fsl-usb2-mph")) == 0)) return (ENXIO); device_set_desc(dev, "Freescale integrated EHCI controller"); return (BUS_PROBE_DEFAULT); } static int fsl_ehci_attach(device_t self) { ehci_softc_t *sc; int rid; int err; bus_space_handle_t ioh; bus_space_tag_t iot; sc = device_get_softc(self); rid = 0; sc->sc_bus.parent = self; sc->sc_bus.devices = sc->sc_devices; sc->sc_bus.devices_max = EHCI_MAX_DEVICES; sc->sc_bus.dma_bits = 32; if (usb_bus_mem_alloc_all(&sc->sc_bus, USB_GET_DMA_TAG(self), &ehci_iterate_hw_softc)) return (ENOMEM); /* Allocate io resource for EHCI */ sc->sc_io_res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->sc_io_res == NULL) { err = fsl_ehci_detach(self); if (err) { device_printf(self, "Detach of the driver failed with error %d\n", err); } return (ENXIO); } iot = rman_get_bustag(sc->sc_io_res); /* * Set handle to USB related registers subregion used by generic * EHCI driver */ ioh = rman_get_bushandle(sc->sc_io_res); err = bus_space_subregion(iot, ioh, FSL_EHCI_REG_OFF, FSL_EHCI_REG_SIZE, &sc->sc_io_hdl); if (err != 0) { err = fsl_ehci_detach(self); if (err) { device_printf(self, "Detach of the driver failed with error %d\n", err); } return (ENXIO); } /* Set little-endian tag for use by the generic EHCI driver */ sc->sc_io_tag = &bs_le_tag; /* Allocate irq */ sc->sc_irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid, RF_ACTIVE); if (sc->sc_irq_res == NULL) { err = fsl_ehci_detach(self); if (err) { device_printf(self, "Detach of the driver failed with error %d\n", err); } return (ENXIO); } /* Setup interrupt handler */ err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, NULL, (driver_intr_t *)ehci_interrupt, sc, &sc->sc_intr_hdl); if (err) { device_printf(self, "Could not setup irq, %d\n", err); sc->sc_intr_hdl = NULL; err = fsl_ehci_detach(self); if (err) { device_printf(self, "Detach of the driver failed with error %d\n", err); } return (ENXIO); } /* Add USB device */ sc->sc_bus.bdev = device_add_child(self, "usbus", -1); if (!sc->sc_bus.bdev) { device_printf(self, "Could not add USB device\n"); err = fsl_ehci_detach(self); if (err) { device_printf(self, "Detach of the driver failed with error %d\n", err); } return (ENOMEM); } device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); sc->sc_id_vendor = 0x1234; strlcpy(sc->sc_vendor, "Freescale", sizeof(sc->sc_vendor)); /* Enable USB */ err = ehci_reset(sc); if (err) { device_printf(self, "Could not reset the controller\n"); err = fsl_ehci_detach(self); if (err) { device_printf(self, "Detach of the driver failed with error %d\n", err); } return (ENXIO); } enable_usb(self, iot, ioh); set_snooping(iot, ioh); set_to_host_mode(sc); set_32b_prefetch(iot, ioh); /* * If usb subsystem is enabled in U-Boot, port power has to be turned * off to allow proper discovery of devices during boot up. */ clear_port_power(sc); /* Set flags */ sc->sc_flags |= EHCI_SCFLG_DONTRESET | EHCI_SCFLG_NORESTERM; err = ehci_init(sc); if (!err) { sc->sc_flags |= EHCI_SCFLG_DONEINIT; err = device_probe_and_attach(sc->sc_bus.bdev); } if (err) { device_printf(self, "USB init failed err=%d\n", err); err = fsl_ehci_detach(self); if (err) { device_printf(self, "Detach of the driver failed with error %d\n", err); } return (EIO); } return (0); } static int fsl_ehci_detach(device_t self) { int err; ehci_softc_t *sc; sc = device_get_softc(self); /* * only call ehci_detach() after ehci_init() */ if (sc->sc_flags & EHCI_SCFLG_DONEINIT) { ehci_detach(sc); sc->sc_flags &= ~EHCI_SCFLG_DONEINIT; } /* Disable interrupts that might have been switched on in ehci_init */ if (sc->sc_io_tag && sc->sc_io_hdl) bus_space_write_4(sc->sc_io_tag, sc->sc_io_hdl, EHCI_USBINTR, 0); if (sc->sc_irq_res && sc->sc_intr_hdl) { err = bus_teardown_intr(self, sc->sc_irq_res, sc->sc_intr_hdl); if (err) { device_printf(self, "Could not tear down irq, %d\n", err); return (err); } sc->sc_intr_hdl = NULL; } if (sc->sc_bus.bdev) { device_delete_child(self, sc->sc_bus.bdev); sc->sc_bus.bdev = NULL; } /* During module unload there are lots of children leftover */ device_delete_children(self); if (sc->sc_irq_res) { bus_release_resource(self, SYS_RES_IRQ, 0, sc->sc_irq_res); sc->sc_irq_res = NULL; } if (sc->sc_io_res) { bus_release_resource(self, SYS_RES_MEMORY, 0, sc->sc_io_res); sc->sc_io_res = NULL; sc->sc_io_tag = 0; sc->sc_io_hdl = 0; } return (0); } Index: head/sys/dev/vnic/thunder_bgx_fdt.c =================================================================== --- head/sys/dev/vnic/thunder_bgx_fdt.c (revision 332309) +++ head/sys/dev/vnic/thunder_bgx_fdt.c (revision 332310) @@ -1,460 +1,460 @@ /*- * Copyright (c) 2015 The FreeBSD Foundation * All rights reserved. * * This software was developed by Semihalf under * the sponsorship of 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "thunder_bgx.h" #include "thunder_bgx_var.h" #define CONN_TYPE_MAXLEN 16 #define CONN_TYPE_OFFSET 2 #define BGX_NODE_NAME "bgx" #define BGX_MAXID 9 /* BGX func. 0, i.e.: reg = <0x8000 0 0 0 0>; DEVFN = 0x80 */ #define BGX_DEVFN_0 0x80 #define FDT_NAME_MAXLEN 31 int bgx_fdt_init_phy(struct bgx *); static void bgx_fdt_get_macaddr(phandle_t phy, uint8_t *hwaddr) { uint8_t addr[ETHER_ADDR_LEN]; if (OF_getprop(phy, "local-mac-address", addr, ETHER_ADDR_LEN) == -1) { /* Missing MAC address should be marked by clearing it */ memset(hwaddr, 0, ETHER_ADDR_LEN); } else memcpy(hwaddr, addr, ETHER_ADDR_LEN); } static boolean_t bgx_fdt_phy_mode_match(struct bgx *bgx, char *qlm_mode, ssize_t size) { const char *type; ssize_t sz; ssize_t offset; switch (bgx->qlm_mode) { case QLM_MODE_SGMII: type = "sgmii"; sz = sizeof("sgmii") - 1; offset = size - sz; break; case QLM_MODE_XAUI_1X4: type = "xaui"; sz = sizeof("xaui") - 1; offset = size - sz; if (offset < 0) return (FALSE); if (strncmp(&qlm_mode[offset], type, sz) == 0) return (TRUE); type = "dxaui"; sz = sizeof("dxaui") - 1; offset = size - sz; break; case QLM_MODE_RXAUI_2X2: type = "raui"; sz = sizeof("raui") - 1; offset = size - sz; break; case QLM_MODE_XFI_4X1: type = "xfi"; sz = sizeof("xfi") - 1; offset = size - sz; break; case QLM_MODE_XLAUI_1X4: type = "xlaui"; sz = sizeof("xlaui") - 1; offset = size - sz; break; case QLM_MODE_10G_KR_4X1: type = "xfi-10g-kr"; sz = sizeof("xfi-10g-kr") - 1; offset = size - sz; break; case QLM_MODE_40G_KR4_1X4: type = "xlaui-40g-kr"; sz = sizeof("xlaui-40g-kr") - 1; offset = size - sz; break; default: return (FALSE); } if (offset < 0) return (FALSE); if (strncmp(&qlm_mode[offset], type, sz) == 0) return (TRUE); return (FALSE); } static boolean_t bgx_fdt_phy_name_match(struct bgx *bgx, char *phy_name, ssize_t size) { const char *type; ssize_t sz; switch (bgx->qlm_mode) { case QLM_MODE_SGMII: type = "sgmii"; sz = sizeof("sgmii") - 1; break; case QLM_MODE_XAUI_1X4: type = "xaui"; sz = sizeof("xaui") - 1; if (sz < size) return (FALSE); if (strncmp(phy_name, type, sz) == 0) return (TRUE); type = "dxaui"; sz = sizeof("dxaui") - 1; break; case QLM_MODE_RXAUI_2X2: type = "raui"; sz = sizeof("raui") - 1; break; case QLM_MODE_XFI_4X1: type = "xfi"; sz = sizeof("xfi") - 1; break; case QLM_MODE_XLAUI_1X4: type = "xlaui"; sz = sizeof("xlaui") - 1; break; case QLM_MODE_10G_KR_4X1: type = "xfi-10g-kr"; sz = sizeof("xfi-10g-kr") - 1; break; case QLM_MODE_40G_KR4_1X4: type = "xlaui-40g-kr"; sz = sizeof("xlaui-40g-kr") - 1; break; default: return (FALSE); } if (sz > size) return (FALSE); if (strncmp(phy_name, type, sz) == 0) return (TRUE); return (FALSE); } static phandle_t bgx_fdt_traverse_nodes(uint8_t unit, phandle_t start, char *name, size_t len) { phandle_t node, ret; uint32_t *reg; size_t buf_size; ssize_t proplen; char *node_name; int err; /* * Traverse all subordinate nodes of 'start' to find BGX instance. * This supports both old (by name) and new (by reg) methods. */ buf_size = sizeof(*node_name) * FDT_NAME_MAXLEN; if (len > buf_size) { /* * This is an erroneous situation since the string * to compare cannot be longer than FDT_NAME_MAXLEN. */ return (0); } node_name = malloc(buf_size, M_BGX, M_WAITOK); for (node = OF_child(start); node != 0; node = OF_peer(node)) { /* Clean-up the buffer */ memset(node_name, 0, buf_size); /* Recurse to children */ if (OF_child(node) != 0) { ret = bgx_fdt_traverse_nodes(unit, node, name, len); if (ret != 0) { free(node_name, M_BGX); return (ret); } } /* * Old way - by name */ proplen = OF_getproplen(node, "name"); if ((proplen <= 0) || (proplen < len)) continue; err = OF_getprop(node, "name", node_name, proplen); if (err <= 0) continue; if (strncmp(node_name, name, len) == 0) { free(node_name, M_BGX); return (node); } /* * New way - by reg */ /* Check if even BGX */ if (strncmp(node_name, BGX_NODE_NAME, sizeof(BGX_NODE_NAME) - 1) != 0) continue; /* Get reg */ err = OF_getencprop_alloc(node, "reg", sizeof(*reg), (void **)®); if (err == -1) { free(reg, M_OFWPROP); continue; } /* Match BGX device function */ if ((BGX_DEVFN_0 + unit) == (reg[0] >> 8)) { free(reg, M_OFWPROP); free(node_name, M_BGX); return (node); } free(reg, M_OFWPROP); } free(node_name, M_BGX); return (0); } /* * Similar functionality to pci_find_pcie_root_port() * but this one works for ThunderX. */ static device_t bgx_find_root_pcib(device_t dev) { devclass_t pci_class; device_t pcib, bus; pci_class = devclass_find("pci"); KASSERT(device_get_devclass(device_get_parent(dev)) == pci_class, ("%s: non-pci device %s", __func__, device_get_nameunit(dev))); /* Walk the bridge hierarchy until we find a non-PCI device */ for (;;) { bus = device_get_parent(dev); KASSERT(bus != NULL, ("%s: null parent of %s", __func__, device_get_nameunit(dev))); if (device_get_devclass(bus) != pci_class) return (NULL); pcib = device_get_parent(bus); KASSERT(pcib != NULL, ("%s: null bridge of %s", __func__, device_get_nameunit(bus))); /* * If the parent of this PCIB is not PCI * then we found our root PCIB. */ if (device_get_devclass(device_get_parent(pcib)) != pci_class) return (pcib); dev = pcib; } } static __inline phandle_t bgx_fdt_find_node(struct bgx *bgx) { device_t root_pcib; phandle_t node; char *bgx_sel; size_t len; KASSERT(bgx->bgx_id <= BGX_MAXID, ("Invalid BGX ID: %d, max: %d", bgx->bgx_id, BGX_MAXID)); len = sizeof(BGX_NODE_NAME) + 1; /* ++<\0> */ /* Allocate memory for BGX node name + "/" character */ bgx_sel = malloc(sizeof(*bgx_sel) * (len + 1), M_BGX, M_ZERO | M_WAITOK); /* Prepare node's name */ snprintf(bgx_sel, len + 1, "/"BGX_NODE_NAME"%d", bgx->bgx_id); /* First try the root node */ node = OF_finddevice(bgx_sel); if (node != -1) { /* Found relevant node */ goto out; } /* * Clean-up and try to find BGX in DT * starting from the parent PCI bridge node. */ memset(bgx_sel, 0, sizeof(*bgx_sel) * (len + 1)); snprintf(bgx_sel, len, BGX_NODE_NAME"%d", bgx->bgx_id); /* Find PCI bridge that we are connected to */ root_pcib = bgx_find_root_pcib(bgx->dev); if (root_pcib == NULL) { device_printf(bgx->dev, "Unable to find BGX root bridge\n"); node = 0; goto out; } node = ofw_bus_get_node(root_pcib); if ((int)node <= 0) { device_printf(bgx->dev, "No parent FDT node for BGX\n"); goto out; } node = bgx_fdt_traverse_nodes(bgx->bgx_id, node, bgx_sel, len); out: free(bgx_sel, M_BGX); return (node); } int bgx_fdt_init_phy(struct bgx *bgx) { char *node_name; phandle_t node, child; phandle_t phy, mdio; ssize_t len; uint8_t lmac; char qlm_mode[CONN_TYPE_MAXLEN]; node = bgx_fdt_find_node(bgx); if (node == 0) { device_printf(bgx->dev, "Could not find bgx%d node in FDT\n", bgx->bgx_id); return (ENXIO); } lmac = 0; for (child = OF_child(node); child > 0; child = OF_peer(child)) { len = OF_getprop(child, "qlm-mode", qlm_mode, sizeof(qlm_mode)); if (len > 0) { if (!bgx_fdt_phy_mode_match(bgx, qlm_mode, len)) { /* * Connection type not match with BGX mode. */ continue; } } else { - len = OF_getprop_alloc(child, "name", 1, + len = OF_getprop_alloc(child, "name", (void **)&node_name); if (len <= 0) { continue; } if (!bgx_fdt_phy_name_match(bgx, node_name, len)) { free(node_name, M_OFWPROP); continue; } free(node_name, M_OFWPROP); } /* Acquire PHY address */ if (OF_getencprop(child, "reg", &bgx->lmac[lmac].phyaddr, sizeof(bgx->lmac[lmac].phyaddr)) <= 0) { if (bootverbose) { device_printf(bgx->dev, "Could not retrieve PHY address\n"); } bgx->lmac[lmac].phyaddr = MII_PHY_ANY; } if (OF_getencprop(child, "phy-handle", &phy, sizeof(phy)) <= 0) { if (bootverbose) { device_printf(bgx->dev, "No phy-handle in PHY node. Skipping...\n"); } continue; } phy = OF_instance_to_package(phy); /* * Get PHY interface (MDIO bus) device. * Driver must be already attached. */ mdio = OF_parent(phy); bgx->lmac[lmac].phy_if_dev = OF_device_from_xref(OF_xref_from_node(mdio)); if (bgx->lmac[lmac].phy_if_dev == NULL) { if (bootverbose) { device_printf(bgx->dev, "Could not find interface to PHY\n"); } continue; } /* Get mac address from FDT */ bgx_fdt_get_macaddr(child, bgx->lmac[lmac].mac); bgx->lmac[lmac].lmacid = lmac; lmac++; if (lmac == MAX_LMAC_PER_BGX) break; } if (lmac == 0) { device_printf(bgx->dev, "Could not find matching PHY\n"); return (ENXIO); } return (0); } Index: head/sys/mips/mediatek/mtk_pinctrl.c =================================================================== --- head/sys/mips/mediatek/mtk_pinctrl.c (revision 332309) +++ head/sys/mips/mediatek/mtk_pinctrl.c (revision 332310) @@ -1,240 +1,240 @@ /*- * Copyright (c) 2016 Stanislav Galabov * 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, * without modification, immediately at the beginning of the file. * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * 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$"); #include #include #include #include #include #include #include #include #include #include #include #include #include "fdt_pinctrl_if.h" static const struct ofw_compat_data compat_data[] = { { "ralink,rt2880-pinmux", 1 }, /* Sentinel */ { NULL, 0 } }; static int mtk_pinctrl_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, "MTK Pin Controller"); return (0); } static int mtk_pinctrl_attach(device_t dev) { if (device_get_unit(dev) != 0) { device_printf(dev, "Only one pin control allowed\n"); return (ENXIO); } if (bootverbose) device_printf(dev, "GPIO mode start: 0x%08x\n", mtk_sysctl_get(SYSCTL_GPIOMODE)); fdt_pinctrl_register(dev, NULL); fdt_pinctrl_configure_tree(dev); if (bootverbose) device_printf(dev, "GPIO mode end : 0x%08x\n", mtk_sysctl_get(SYSCTL_GPIOMODE)); return (0); } static int mtk_pinctrl_process_entry(device_t dev, struct mtk_pin_group *table, const char *group, char *func) { uint32_t val; int found = 0, i, j; for (i = 0; table[i].name != NULL; i++) { if (strcmp(table[i].name, group) == 0) { found = 1; break; } } if (!found) return (ENOENT); for (j = 0; j < table[i].funcnum; j++) { if (strcmp(table[i].functions[j].name, func) == 0) { val = mtk_sysctl_get(table[i].sysc_reg); val &= ~(table[i].mask << table[i].offset); val |= (table[i].functions[j].value << table[i].offset); mtk_sysctl_set(table[i].sysc_reg, val); return (0); } } return (ENOENT); } static int mtk_pinctrl_process_node(device_t dev, struct mtk_pin_group *table, phandle_t node) { const char **group_list = NULL; char *pin_function = NULL; int ret, num_groups, i; ret = 0; num_groups = ofw_bus_string_list_to_array(node, "ralink,group", &group_list); if (num_groups <= 0) return (ENOENT); - if (OF_getprop_alloc(node, "ralink,function", sizeof(*pin_function), + if (OF_getprop_alloc_multi(node, "ralink,function", sizeof(*pin_function), (void **)&pin_function) == -1) { ret = ENOENT; goto out; } for (i = 0; i < num_groups; i++) { if ((ret = mtk_pinctrl_process_entry(dev, table, group_list[i], pin_function)) != 0) goto out; } out: OF_prop_free(group_list); OF_prop_free(pin_function); return (ret); } static int mtk_pinctrl_configure(device_t dev, phandle_t cfgxref) { struct mtk_pin_group *pintable; phandle_t node, child; uint32_t socid; int ret; node = OF_node_from_xref(cfgxref); ret = 0; /* Now, get the system type, so we can get the proper GPIO mode array */ socid = mtk_soc_get_socid(); switch (socid) { case MTK_SOC_RT2880: pintable = rt2880_pintable; break; case MTK_SOC_RT3050: /* fallthrough */ case MTK_SOC_RT3052: case MTK_SOC_RT3350: pintable = rt3050_pintable; break; case MTK_SOC_RT3352: pintable = rt3352_pintable; break; case MTK_SOC_RT3662: /* fallthrough */ case MTK_SOC_RT3883: pintable = rt3883_pintable; break; case MTK_SOC_RT5350: pintable = rt5350_pintable; break; case MTK_SOC_MT7620A: /* fallthrough */ case MTK_SOC_MT7620N: pintable = mt7620_pintable; break; case MTK_SOC_MT7628: /* fallthrough */ case MTK_SOC_MT7688: pintable = mt7628_pintable; break; case MTK_SOC_MT7621: pintable = mt7621_pintable; break; default: ret = ENOENT; goto out; } /* * OpenWRT dts files have single child within the pinctrl nodes, which * contains the 'ralink,group' and 'ralink,function' properties. */ for (child = OF_child(node); child != 0 && child != -1; child = OF_peer(child)) { if ((ret = mtk_pinctrl_process_node(dev, pintable, child)) != 0) return (ret); } out: return (ret); } static device_method_t mtk_pinctrl_methods[] = { DEVMETHOD(device_probe, mtk_pinctrl_probe), DEVMETHOD(device_attach, mtk_pinctrl_attach), /* fdt_pinctrl interface */ DEVMETHOD(fdt_pinctrl_configure, mtk_pinctrl_configure), DEVMETHOD_END }; static driver_t mtk_pinctrl_driver = { "pinctrl", mtk_pinctrl_methods, 0, }; static devclass_t mtk_pinctrl_devclass; EARLY_DRIVER_MODULE(mtk_pinctrl, simplebus, mtk_pinctrl_driver, mtk_pinctrl_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_EARLY); MODULE_DEPEND(mtk_pinctrl, mtk_sysctl, 1, 1, 1); Index: head/sys/powerpc/mpc85xx/fsl_diu.c =================================================================== --- head/sys/powerpc/mpc85xx/fsl_diu.c (revision 332309) +++ head/sys/powerpc/mpc85xx/fsl_diu.c (revision 332310) @@ -1,472 +1,472 @@ /*- * Copyright (c) 2016 Justin Hibbits * 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$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fb_if.h" #define DIU_DESC_1 0x000 /* Plane1 Area Descriptor Pointer Register */ #define DIU_DESC_2 0x004 /* Plane2 Area Descriptor Pointer Register */ #define DIU_DESC_3 0x008 /* Plane3 Area Descriptor Pointer Register */ #define DIU_GAMMA 0x00C /* Gamma Register */ #define DIU_PALETTE 0x010 /* Palette Register */ #define DIU_CURSOR 0x014 /* Cursor Register */ #define DIU_CURS_POS 0x018 /* Cursor Position Register */ #define CURSOR_Y_SHIFT 16 #define CURSOR_X_SHIFT 0 #define DIU_DIU_MODE 0x01C /* DIU4 Mode */ #define DIU_MODE_M 0x7 #define DIU_MODE_S 0 #define DIU_MODE_NORMAL 0x1 #define DIU_MODE_2 0x2 #define DIU_MODE_3 0x3 #define DIU_MODE_COLBAR 0x4 #define DIU_BGND 0x020 /* Background */ #define DIU_BGND_WB 0x024 /* Background Color in write back Mode Register */ #define DIU_DISP_SIZE 0x028 /* Display Size */ #define DELTA_Y_S 16 #define DELTA_X_S 0 #define DIU_WB_SIZE 0x02C /* Write back Plane Size Register */ #define DELTA_Y_WB_S 16 #define DELTA_X_WB_S 0 #define DIU_WB_MEM_ADDR 0x030 /* Address to Store the write back Plane Register */ #define DIU_HSYN_PARA 0x034 /* Horizontal Sync Parameter */ #define BP_H_SHIFT 22 #define PW_H_SHIFT 11 #define FP_H_SHIFT 0 #define DIU_VSYN_PARA 0x038 /* Vertical Sync Parameter */ #define BP_V_SHIFT 22 #define PW_V_SHIFT 11 #define FP_V_SHIFT 0 #define DIU_SYNPOL 0x03C /* Synchronize Polarity */ #define BP_VS (1 << 4) #define BP_HS (1 << 3) #define INV_CS (1 << 2) #define INV_VS (1 << 1) #define INV_HS (1 << 0) #define INV_PDI_VS (1 << 8) /* Polarity of PDI input VSYNC. */ #define INV_PDI_HS (1 << 9) /* Polarity of PDI input HSYNC. */ #define INV_PDI_DE (1 << 10) /* Polarity of PDI input DE. */ #define DIU_THRESHOLD 0x040 /* Threshold */ #define LS_BF_VS_SHIFT 16 #define OUT_BUF_LOW_SHIFT 0 #define DIU_INT_STATUS 0x044 /* Interrupt Status */ #define DIU_INT_MASK 0x048 /* Interrupt Mask */ #define DIU_COLBAR_1 0x04C /* COLBAR_1 */ #define DIU_COLORBARn_R(x) ((x & 0xff) << 16) #define DIU_COLORBARn_G(x) ((x & 0xff) << 8) #define DIU_COLORBARn_B(x) ((x & 0xff) << 0) #define DIU_COLBAR_2 0x050 /* COLBAR_2 */ #define DIU_COLBAR_3 0x054 /* COLBAR_3 */ #define DIU_COLBAR_4 0x058 /* COLBAR_4 */ #define DIU_COLBAR_5 0x05c /* COLBAR_5 */ #define DIU_COLBAR_6 0x060 /* COLBAR_6 */ #define DIU_COLBAR_7 0x064 /* COLBAR_7 */ #define DIU_COLBAR_8 0x068 /* COLBAR_8 */ #define DIU_FILLING 0x06C /* Filling Register */ #define DIU_PLUT 0x070 /* Priority Look Up Table Register */ /* Control Descriptor */ #define DIU_CTRLDESCL(n, m) 0x200 + (0x40 * n) + 0x4 * (m - 1) #define DIU_CTRLDESCLn_1(n) DIU_CTRLDESCL(n, 1) #define DIU_CTRLDESCLn_2(n) DIU_CTRLDESCL(n, 2) #define DIU_CTRLDESCLn_3(n) DIU_CTRLDESCL(n, 3) #define TRANS_SHIFT 20 #define DIU_CTRLDESCLn_4(n) DIU_CTRLDESCL(n, 4) #define BPP_MASK 0xf /* Bit per pixel Mask */ #define BPP_SHIFT 16 /* Bit per pixel Shift */ #define BPP24 0x5 #define EN_LAYER (1 << 31) /* Enable the layer */ #define DIU_CTRLDESCLn_5(n) DIU_CTRLDESCL(n, 5) #define DIU_CTRLDESCLn_6(n) DIU_CTRLDESCL(n, 6) #define DIU_CTRLDESCLn_7(n) DIU_CTRLDESCL(n, 7) #define DIU_CTRLDESCLn_8(n) DIU_CTRLDESCL(n, 8) #define DIU_CTRLDESCLn_9(n) DIU_CTRLDESCL(n, 9) #define NUM_LAYERS 1 struct panel_info { uint32_t panel_width; uint32_t panel_height; uint32_t panel_hbp; uint32_t panel_hpw; uint32_t panel_hfp; uint32_t panel_vbp; uint32_t panel_vpw; uint32_t panel_vfp; uint32_t panel_freq; uint32_t clk_div; }; struct diu_area_descriptor { uint32_t pixel_format; uint32_t bitmap_address; uint32_t source_size; uint32_t aoi_size; uint32_t aoi_offset; uint32_t display_offset; uint32_t chroma_key_max; uint32_t chroma_key_min; uint32_t next_ad_addr; } __aligned(32); struct diu_softc { struct resource *res[2]; void *ih; device_t sc_dev; device_t sc_fbd; /* fbd child */ struct fb_info sc_info; struct panel_info sc_panel; struct diu_area_descriptor *sc_planes[3]; uint8_t *sc_gamma; uint8_t *sc_cursor; }; static struct resource_spec diu_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE }, { -1, 0 } }; static int diu_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "fsl,diu")) return (ENXIO); device_set_desc(dev, "Freescale Display Interface Unit"); return (BUS_PROBE_DEFAULT); } static void diu_intr(void *arg) { struct diu_softc *sc; int reg; sc = arg; /* Ack interrupts */ reg = bus_read_4(sc->res[0], DIU_INT_STATUS); bus_write_4(sc->res[0], DIU_INT_STATUS, reg); /* TODO interrupt handler */ } static int diu_set_pxclk(device_t dev, unsigned int freq) { phandle_t node; unsigned long bus_freq; uint32_t pxclk_set; uint32_t clkdvd; node = ofw_bus_get_node(device_get_parent(dev)); if ((bus_freq = mpc85xx_get_platform_clock()) <= 0) { device_printf(dev, "Unable to get bus frequency\n"); return (ENXIO); } /* freq is in kHz */ freq *= 1000; /* adding freq/2 to round-to-closest */ pxclk_set = min(max((bus_freq + freq/2) / freq, 2), 255) << 16; pxclk_set |= OCP85XX_CLKDVDR_PXCKEN; clkdvd = ccsr_read4(OCP85XX_CLKDVDR); clkdvd &= ~(OCP85XX_CLKDVDR_PXCKEN | OCP85XX_CLKDVDR_PXCKINV | OCP85XX_CLKDVDR_PXCLK_MASK); ccsr_write4(OCP85XX_CLKDVDR, clkdvd); ccsr_write4(OCP85XX_CLKDVDR, clkdvd | pxclk_set); return (0); } static int diu_init(struct diu_softc *sc) { struct panel_info *panel; int reg; panel = &sc->sc_panel; /* Temporarily disable the DIU while configuring */ reg = bus_read_4(sc->res[0], DIU_DIU_MODE); reg &= ~(DIU_MODE_M << DIU_MODE_S); bus_write_4(sc->res[0], DIU_DIU_MODE, reg); if (diu_set_pxclk(sc->sc_dev, panel->panel_freq) < 0) { return (ENXIO); } /* Configure DIU */ /* Need to set these somehow later... */ bus_write_4(sc->res[0], DIU_GAMMA, vtophys(sc->sc_gamma)); bus_write_4(sc->res[0], DIU_CURSOR, vtophys(sc->sc_cursor)); bus_write_4(sc->res[0], DIU_CURS_POS, 0); reg = ((sc->sc_info.fb_height) << DELTA_Y_S); reg |= sc->sc_info.fb_width; bus_write_4(sc->res[0], DIU_DISP_SIZE, reg); reg = (panel->panel_hbp << BP_H_SHIFT); reg |= (panel->panel_hpw << PW_H_SHIFT); reg |= (panel->panel_hfp << FP_H_SHIFT); bus_write_4(sc->res[0], DIU_HSYN_PARA, reg); reg = (panel->panel_vbp << BP_V_SHIFT); reg |= (panel->panel_vpw << PW_V_SHIFT); reg |= (panel->panel_vfp << FP_V_SHIFT); bus_write_4(sc->res[0], DIU_VSYN_PARA, reg); bus_write_4(sc->res[0], DIU_BGND, 0); /* Mask all the interrupts */ bus_write_4(sc->res[0], DIU_INT_MASK, 0x3f); /* Reset all layers */ sc->sc_planes[0] = contigmalloc(sizeof(struct diu_area_descriptor), M_DEVBUF, M_ZERO, 0, BUS_SPACE_MAXADDR_32BIT, 32, 0); bus_write_4(sc->res[0], DIU_DESC_1, vtophys(sc->sc_planes[0])); bus_write_4(sc->res[0], DIU_DESC_2, 0); bus_write_4(sc->res[0], DIU_DESC_3, 0); /* Setup first plane */ /* Area descriptor fields are little endian, so byte swap. */ /* Word 0: Pixel format */ /* Set to 8:8:8:8 ARGB, 4 bytes per pixel, no flip. */ #define MAKE_PXLFMT(as,rs,gs,bs,a,r,g,b,f,s) \ htole32((as << (4 * a)) | (rs << 4 * r) | \ (gs << 4 * g) | (bs << 4 * b) | \ (f << 28) | (s << 16) | \ (a << 25) | (r << 19) | \ (g << 21) | (b << 24)) reg = MAKE_PXLFMT(8, 8, 8, 8, 3, 2, 1, 0, 1, 3); sc->sc_planes[0]->pixel_format = reg; /* Word 1: Bitmap address */ sc->sc_planes[0]->bitmap_address = htole32(sc->sc_info.fb_pbase); /* Word 2: Source size/global alpha */ reg = (sc->sc_info.fb_width | (sc->sc_info.fb_height << 12)); sc->sc_planes[0]->source_size = htole32(reg); /* Word 3: AOI Size */ reg = (sc->sc_info.fb_width | (sc->sc_info.fb_height << 16)); sc->sc_planes[0]->aoi_size = htole32(reg); /* Word 4: AOI Offset */ sc->sc_planes[0]->aoi_offset = 0; /* Word 5: Display offset */ sc->sc_planes[0]->display_offset = 0; /* Word 6: Chroma key max */ sc->sc_planes[0]->chroma_key_max = 0; /* Word 7: Chroma key min */ reg = 255 << 16 | 255 << 8 | 255; sc->sc_planes[0]->chroma_key_min = htole32(reg); /* Word 8: Next AD */ sc->sc_planes[0]->next_ad_addr = 0; /* TODO: derive this from the panel size */ bus_write_4(sc->res[0], DIU_PLUT, 0x1f5f666); /* Enable DIU in normal mode */ reg = bus_read_4(sc->res[0], DIU_DIU_MODE); reg &= ~(DIU_MODE_M << DIU_MODE_S); reg |= (DIU_MODE_NORMAL << DIU_MODE_S); bus_write_4(sc->res[0], DIU_DIU_MODE, reg); return (0); } static int diu_attach(device_t dev) { struct edid_info edid; struct diu_softc *sc; const struct videomode *videomode; void *edid_cells; const char *vm_name; phandle_t node; int h, r, w; int err, i; sc = device_get_softc(dev); sc->sc_dev = dev; if (bus_alloc_resources(dev, diu_spec, sc->res)) { device_printf(dev, "could not allocate resources\n"); return (ENXIO); } node = ofw_bus_get_node(dev); /* Setup interrupt handler */ err = bus_setup_intr(dev, sc->res[1], INTR_TYPE_BIO | INTR_MPSAFE, NULL, diu_intr, sc, &sc->ih); if (err) { device_printf(dev, "Unable to alloc interrupt resource.\n"); return (ENXIO); } /* TODO: Eventually, allow EDID to be dynamically provided. */ - if (OF_getprop_alloc(node, "edid", 1, &edid_cells) <= 0) { + if (OF_getprop_alloc(node, "edid", &edid_cells) <= 0) { /* * u-boot uses the environment variable name 'video-mode', so * just use the same name here. Should allow another variable * that better fits our design model, but this is fine. */ if ((vm_name = kern_getenv("video-mode")) == NULL) { device_printf(dev, "No EDID data and no video-mode env set\n"); return (ENXIO); } } if (edid_cells != NULL) { if (edid_parse(edid_cells, &edid) != 0) { device_printf(dev, "Error parsing EDID\n"); OF_prop_free(edid_cells); return (ENXIO); } videomode = edid.edid_preferred_mode; } else { /* Parse video-mode kenv variable. */ if ((err = sscanf(vm_name, "fslfb:%dx%d@%d", &w, &h, &r)) != 3) { device_printf(dev, "Cannot parse video mode: %s\n", vm_name); return (ENXIO); } videomode = pick_mode_by_ref(w, h, r); if (videomode == NULL) { device_printf(dev, "Cannot find mode for %dx%d@%d", w, h, r); return (ENXIO); } } sc->sc_panel.panel_width = videomode->hdisplay; sc->sc_panel.panel_height = videomode->vdisplay; sc->sc_panel.panel_hbp = videomode->hsync_start - videomode->hdisplay; sc->sc_panel.panel_hfp = videomode->htotal - videomode->hsync_end; sc->sc_panel.panel_hpw = videomode->hsync_end - videomode->hsync_start; sc->sc_panel.panel_vbp = videomode->vsync_start - videomode->vdisplay; sc->sc_panel.panel_vfp = videomode->vtotal - videomode->vsync_end; sc->sc_panel.panel_vpw = videomode->vsync_end - videomode->vsync_start; sc->sc_panel.panel_freq = videomode->dot_clock; sc->sc_info.fb_width = sc->sc_panel.panel_width; sc->sc_info.fb_height = sc->sc_panel.panel_height; sc->sc_info.fb_stride = sc->sc_info.fb_width * 4; sc->sc_info.fb_bpp = sc->sc_info.fb_depth = 32; sc->sc_info.fb_size = sc->sc_info.fb_height * sc->sc_info.fb_stride; sc->sc_info.fb_vbase = (intptr_t)contigmalloc(sc->sc_info.fb_size, M_DEVBUF, M_ZERO, 0, BUS_SPACE_MAXADDR_32BIT, PAGE_SIZE, 0); sc->sc_info.fb_pbase = (intptr_t)vtophys(sc->sc_info.fb_vbase); sc->sc_info.fb_flags = FB_FLAG_MEMATTR; sc->sc_info.fb_memattr = VM_MEMATTR_DEFAULT; /* Gamma table is 3 consecutive segments of 256 bytes. */ sc->sc_gamma = contigmalloc(3 * 256, M_DEVBUF, 0, 0, BUS_SPACE_MAXADDR_32BIT, PAGE_SIZE, 0); /* Initialize gamma to default */ for (i = 0; i < 3 * 256; i++) sc->sc_gamma[i] = (i % 256); /* Cursor format is 32x32x16bpp */ sc->sc_cursor = contigmalloc(32 * 32 * 2, M_DEVBUF, M_ZERO, 0, BUS_SPACE_MAXADDR_32BIT, PAGE_SIZE, 0); diu_init(sc); sc->sc_info.fb_name = device_get_nameunit(dev); /* Ask newbus to attach framebuffer device to me. */ sc->sc_fbd = device_add_child(dev, "fbd", device_get_unit(dev)); if (sc->sc_fbd == NULL) device_printf(dev, "Can't attach fbd device\n"); if ((err = device_probe_and_attach(sc->sc_fbd)) != 0) { device_printf(dev, "Failed to attach fbd device: %d\n", err); } return (0); } static struct fb_info * diu_fb_getinfo(device_t dev) { struct diu_softc *sc = device_get_softc(dev); return (&sc->sc_info); } static device_method_t diu_methods[] = { DEVMETHOD(device_probe, diu_probe), DEVMETHOD(device_attach, diu_attach), /* Framebuffer service methods */ DEVMETHOD(fb_getinfo, diu_fb_getinfo), { 0, 0 } }; static driver_t diu_driver = { "fb", diu_methods, sizeof(struct diu_softc), }; static devclass_t diu_devclass; DRIVER_MODULE(fb, simplebus, diu_driver, diu_devclass, 0, 0); Index: head/sys/powerpc/powermac/macio.c =================================================================== --- head/sys/powerpc/powermac/macio.c (revision 332309) +++ head/sys/powerpc/powermac/macio.c (revision 332310) @@ -1,705 +1,705 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright 2002 by Peter Grehan. 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. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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. * * $FreeBSD$ */ /* * Driver for KeyLargo/Pangea, the MacPPC south bridge ASIC. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Macio softc */ struct macio_softc { phandle_t sc_node; vm_offset_t sc_base; vm_offset_t sc_size; struct rman sc_mem_rman; /* FCR registers */ int sc_memrid; struct resource *sc_memr; }; static MALLOC_DEFINE(M_MACIO, "macio", "macio device information"); static int macio_probe(device_t); static int macio_attach(device_t); static int macio_print_child(device_t dev, device_t child); static void macio_probe_nomatch(device_t, device_t); static struct resource *macio_alloc_resource(device_t, device_t, int, int *, rman_res_t, rman_res_t, rman_res_t, u_int); static int macio_activate_resource(device_t, device_t, int, int, struct resource *); static int macio_deactivate_resource(device_t, device_t, int, int, struct resource *); static int macio_release_resource(device_t, device_t, int, int, struct resource *); static struct resource_list *macio_get_resource_list (device_t, device_t); static ofw_bus_get_devinfo_t macio_get_devinfo; /* * Bus interface definition */ static device_method_t macio_methods[] = { /* Device interface */ DEVMETHOD(device_probe, macio_probe), DEVMETHOD(device_attach, macio_attach), DEVMETHOD(device_detach, bus_generic_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* Bus interface */ DEVMETHOD(bus_print_child, macio_print_child), DEVMETHOD(bus_probe_nomatch, macio_probe_nomatch), DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD(bus_alloc_resource, macio_alloc_resource), DEVMETHOD(bus_release_resource, macio_release_resource), DEVMETHOD(bus_activate_resource, macio_activate_resource), DEVMETHOD(bus_deactivate_resource, macio_deactivate_resource), DEVMETHOD(bus_get_resource_list, macio_get_resource_list), DEVMETHOD(bus_child_pnpinfo_str, ofw_bus_gen_child_pnpinfo_str), /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_devinfo, macio_get_devinfo), DEVMETHOD(ofw_bus_get_compat, ofw_bus_gen_get_compat), DEVMETHOD(ofw_bus_get_model, ofw_bus_gen_get_model), DEVMETHOD(ofw_bus_get_name, ofw_bus_gen_get_name), DEVMETHOD(ofw_bus_get_node, ofw_bus_gen_get_node), DEVMETHOD(ofw_bus_get_type, ofw_bus_gen_get_type), { 0, 0 } }; static driver_t macio_pci_driver = { "macio", macio_methods, sizeof(struct macio_softc) }; devclass_t macio_devclass; DRIVER_MODULE(macio, pci, macio_pci_driver, macio_devclass, 0, 0); /* * PCI ID search table */ static struct macio_pci_dev { u_int32_t mpd_devid; char *mpd_desc; } macio_pci_devlist[] = { { 0x0017106b, "Paddington I/O Controller" }, { 0x0022106b, "KeyLargo I/O Controller" }, { 0x0025106b, "Pangea I/O Controller" }, { 0x003e106b, "Intrepid I/O Controller" }, { 0x0041106b, "K2 KeyLargo I/O Controller" }, { 0x004f106b, "Shasta I/O Controller" }, { 0, NULL } }; /* * Devices to exclude from the probe * XXX some of these may be required in the future... */ #define MACIO_QUIRK_IGNORE 0x00000001 #define MACIO_QUIRK_CHILD_HAS_INTR 0x00000002 #define MACIO_QUIRK_USE_CHILD_REG 0x00000004 struct macio_quirk_entry { const char *mq_name; int mq_quirks; }; static struct macio_quirk_entry macio_quirks[] = { { "escc-legacy", MACIO_QUIRK_IGNORE }, { "timer", MACIO_QUIRK_IGNORE }, { "escc", MACIO_QUIRK_CHILD_HAS_INTR }, { "i2s", MACIO_QUIRK_CHILD_HAS_INTR | MACIO_QUIRK_USE_CHILD_REG }, { NULL, 0 } }; static int macio_get_quirks(const char *name) { struct macio_quirk_entry *mqe; for (mqe = macio_quirks; mqe->mq_name != NULL; mqe++) if (strcmp(name, mqe->mq_name) == 0) return (mqe->mq_quirks); return (0); } /* * Add an interrupt to the dev's resource list if present */ static void macio_add_intr(phandle_t devnode, struct macio_devinfo *dinfo) { phandle_t iparent; int *intr; int i, nintr; int icells; if (dinfo->mdi_ninterrupts >= 6) { printf("macio: device has more than 6 interrupts\n"); return; } - nintr = OF_getprop_alloc(devnode, "interrupts", sizeof(*intr), + nintr = OF_getprop_alloc_multi(devnode, "interrupts", sizeof(*intr), (void **)&intr); if (nintr == -1) { - nintr = OF_getprop_alloc(devnode, "AAPL,interrupts", + nintr = OF_getprop_alloc_multi(devnode, "AAPL,interrupts", sizeof(*intr), (void **)&intr); if (nintr == -1) return; } if (intr[0] == -1) return; if (OF_getprop(devnode, "interrupt-parent", &iparent, sizeof(iparent)) <= 0) panic("Interrupt but no interrupt parent!\n"); if (OF_getprop(OF_node_from_xref(iparent), "#interrupt-cells", &icells, sizeof(icells)) <= 0) icells = 1; for (i = 0; i < nintr; i+=icells) { u_int irq = MAP_IRQ(iparent, intr[i]); resource_list_add(&dinfo->mdi_resources, SYS_RES_IRQ, dinfo->mdi_ninterrupts, irq, irq, 1); dinfo->mdi_interrupts[dinfo->mdi_ninterrupts] = irq; dinfo->mdi_ninterrupts++; } } static void macio_add_reg(phandle_t devnode, struct macio_devinfo *dinfo) { struct macio_reg *reg, *regp; phandle_t child; char buf[8]; int i, layout_id = 0, nreg, res; - nreg = OF_getprop_alloc(devnode, "reg", sizeof(*reg), (void **)®); + nreg = OF_getprop_alloc_multi(devnode, "reg", sizeof(*reg), (void **)®); if (nreg == -1) return; /* * Some G5's have broken properties in the i2s-a area. If so we try * to fix it. Right now we know of two different cases, one for * sound layout-id 36 and the other one for sound layout-id 76. * What is missing is the base address for the memory addresses. * We take them from the parent node (i2s) and use the size * information from the child. */ if (reg[0].mr_base == 0) { child = OF_child(devnode); while (child != 0) { res = OF_getprop(child, "name", buf, sizeof(buf)); if (res > 0 && strcmp(buf, "sound") == 0) break; child = OF_peer(child); } res = OF_getprop(child, "layout-id", &layout_id, sizeof(layout_id)); if (res > 0 && (layout_id == 36 || layout_id == 76)) { - res = OF_getprop_alloc(OF_parent(devnode), "reg", + res = OF_getprop_alloc_multi(OF_parent(devnode), "reg", sizeof(*regp), (void **)®p); reg[0] = regp[0]; reg[1].mr_base = regp[1].mr_base; reg[2].mr_base = regp[1].mr_base + reg[1].mr_size; } } for (i = 0; i < nreg; i++) { resource_list_add(&dinfo->mdi_resources, SYS_RES_MEMORY, i, reg[i].mr_base, reg[i].mr_base + reg[i].mr_size, reg[i].mr_size); } } /* * PCI probe */ static int macio_probe(device_t dev) { int i; u_int32_t devid; devid = pci_get_devid(dev); for (i = 0; macio_pci_devlist[i].mpd_desc != NULL; i++) { if (devid == macio_pci_devlist[i].mpd_devid) { device_set_desc(dev, macio_pci_devlist[i].mpd_desc); return (0); } } return (ENXIO); } /* * PCI attach: scan Open Firmware child nodes, and attach these as children * of the macio bus */ static int macio_attach(device_t dev) { struct macio_softc *sc; struct macio_devinfo *dinfo; phandle_t root; phandle_t child; phandle_t subchild; device_t cdev; u_int reg[3]; char compat[32]; int error, quirks; sc = device_get_softc(dev); root = sc->sc_node = ofw_bus_get_node(dev); /* * Locate the device node and it's base address */ if (OF_getprop(root, "assigned-addresses", reg, sizeof(reg)) < (ssize_t)sizeof(reg)) { return (ENXIO); } /* Used later to see if we have to enable the I2S part. */ OF_getprop(root, "compatible", compat, sizeof(compat)); sc->sc_base = reg[2]; sc->sc_size = MACIO_REG_SIZE; sc->sc_memrid = PCIR_BAR(0); sc->sc_memr = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->sc_memrid, RF_ACTIVE); sc->sc_mem_rman.rm_type = RMAN_ARRAY; sc->sc_mem_rman.rm_descr = "MacIO Device Memory"; error = rman_init(&sc->sc_mem_rman); if (error) { device_printf(dev, "rman_init() failed. error = %d\n", error); return (error); } error = rman_manage_region(&sc->sc_mem_rman, 0, sc->sc_size); if (error) { device_printf(dev, "rman_manage_region() failed. error = %d\n", error); return (error); } /* * Iterate through the sub-devices */ for (child = OF_child(root); child != 0; child = OF_peer(child)) { dinfo = malloc(sizeof(*dinfo), M_MACIO, M_WAITOK | M_ZERO); if (ofw_bus_gen_setup_devinfo(&dinfo->mdi_obdinfo, child) != 0) { free(dinfo, M_MACIO); continue; } quirks = macio_get_quirks(dinfo->mdi_obdinfo.obd_name); if ((quirks & MACIO_QUIRK_IGNORE) != 0) { ofw_bus_gen_destroy_devinfo(&dinfo->mdi_obdinfo); free(dinfo, M_MACIO); continue; } resource_list_init(&dinfo->mdi_resources); dinfo->mdi_ninterrupts = 0; macio_add_intr(child, dinfo); if ((quirks & MACIO_QUIRK_USE_CHILD_REG) != 0) macio_add_reg(OF_child(child), dinfo); else macio_add_reg(child, dinfo); if ((quirks & MACIO_QUIRK_CHILD_HAS_INTR) != 0) for (subchild = OF_child(child); subchild != 0; subchild = OF_peer(subchild)) macio_add_intr(subchild, dinfo); cdev = device_add_child(dev, NULL, -1); if (cdev == NULL) { device_printf(dev, "<%s>: device_add_child failed\n", dinfo->mdi_obdinfo.obd_name); resource_list_free(&dinfo->mdi_resources); ofw_bus_gen_destroy_devinfo(&dinfo->mdi_obdinfo); free(dinfo, M_MACIO); continue; } device_set_ivars(cdev, dinfo); /* Set FCRs to enable some devices */ if (sc->sc_memr == NULL) continue; if (strcmp(ofw_bus_get_name(cdev), "bmac") == 0 || strcmp(ofw_bus_get_compat(cdev), "bmac+") == 0) { uint32_t fcr; fcr = bus_read_4(sc->sc_memr, HEATHROW_FCR); fcr |= FCR_ENET_ENABLE & ~FCR_ENET_RESET; bus_write_4(sc->sc_memr, HEATHROW_FCR, fcr); DELAY(50000); fcr |= FCR_ENET_RESET; bus_write_4(sc->sc_memr, HEATHROW_FCR, fcr); DELAY(50000); fcr &= ~FCR_ENET_RESET; bus_write_4(sc->sc_memr, HEATHROW_FCR, fcr); DELAY(50000); bus_write_4(sc->sc_memr, HEATHROW_FCR, fcr); } /* * Make sure the I2S0 and the I2S0_CLK are enabled. * On certain G5's they are not. */ if ((strcmp(ofw_bus_get_name(cdev), "i2s") == 0) && (strcmp(compat, "K2-Keylargo") == 0)) { uint32_t fcr1; fcr1 = bus_read_4(sc->sc_memr, KEYLARGO_FCR1); fcr1 |= FCR1_I2S0_CLK_ENABLE | FCR1_I2S0_ENABLE; bus_write_4(sc->sc_memr, KEYLARGO_FCR1, fcr1); } } return (bus_generic_attach(dev)); } static int macio_print_child(device_t dev, device_t child) { struct macio_devinfo *dinfo; struct resource_list *rl; int retval = 0; dinfo = device_get_ivars(child); rl = &dinfo->mdi_resources; retval += bus_print_child_header(dev, child); retval += resource_list_print_type(rl, "mem", SYS_RES_MEMORY, "%#jx"); retval += resource_list_print_type(rl, "irq", SYS_RES_IRQ, "%jd"); retval += bus_print_child_footer(dev, child); return (retval); } static void macio_probe_nomatch(device_t dev, device_t child) { struct macio_devinfo *dinfo; struct resource_list *rl; const char *type; if (bootverbose) { dinfo = device_get_ivars(child); rl = &dinfo->mdi_resources; if ((type = ofw_bus_get_type(child)) == NULL) type = "(unknown)"; device_printf(dev, "<%s, %s>", type, ofw_bus_get_name(child)); resource_list_print_type(rl, "mem", SYS_RES_MEMORY, "%#jx"); resource_list_print_type(rl, "irq", SYS_RES_IRQ, "%jd"); printf(" (no driver attached)\n"); } } static struct resource * macio_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 macio_softc *sc; int needactivate; struct resource *rv; struct rman *rm; u_long adjstart, adjend, adjcount; struct macio_devinfo *dinfo; struct resource_list_entry *rle; sc = device_get_softc(bus); dinfo = device_get_ivars(child); needactivate = flags & RF_ACTIVE; flags &= ~RF_ACTIVE; switch (type) { case SYS_RES_MEMORY: case SYS_RES_IOPORT: rle = resource_list_find(&dinfo->mdi_resources, SYS_RES_MEMORY, *rid); if (rle == NULL) { device_printf(bus, "no rle for %s memory %d\n", device_get_nameunit(child), *rid); return (NULL); } if (start < rle->start) adjstart = rle->start; else if (start > rle->end) adjstart = rle->end; else adjstart = start; if (end < rle->start) adjend = rle->start; else if (end > rle->end) adjend = rle->end; else adjend = end; adjcount = adjend - adjstart; rm = &sc->sc_mem_rman; break; case SYS_RES_IRQ: /* Check for passthrough from subattachments like macgpio */ if (device_get_parent(child) != bus) return BUS_ALLOC_RESOURCE(device_get_parent(bus), child, type, rid, start, end, count, flags); rle = resource_list_find(&dinfo->mdi_resources, SYS_RES_IRQ, *rid); if (rle == NULL) { if (dinfo->mdi_ninterrupts >= 6) { device_printf(bus, "%s has more than 6 interrupts\n", device_get_nameunit(child)); return (NULL); } resource_list_add(&dinfo->mdi_resources, SYS_RES_IRQ, dinfo->mdi_ninterrupts, start, start, 1); dinfo->mdi_interrupts[dinfo->mdi_ninterrupts] = start; dinfo->mdi_ninterrupts++; } return (resource_list_alloc(&dinfo->mdi_resources, bus, child, type, rid, start, end, count, flags)); default: device_printf(bus, "unknown resource request from %s\n", device_get_nameunit(child)); return (NULL); } rv = rman_reserve_resource(rm, adjstart, adjend, adjcount, flags, child); if (rv == NULL) { device_printf(bus, "failed to reserve resource %#lx - %#lx (%#lx) for %s\n", adjstart, adjend, adjcount, device_get_nameunit(child)); return (NULL); } rman_set_rid(rv, *rid); if (needactivate) { if (bus_activate_resource(child, type, *rid, rv) != 0) { device_printf(bus, "failed to activate resource for %s\n", device_get_nameunit(child)); rman_release_resource(rv); return (NULL); } } return (rv); } static int macio_release_resource(device_t bus, device_t child, int type, int rid, struct resource *res) { if (rman_get_flags(res) & RF_ACTIVE) { int error = bus_deactivate_resource(child, type, rid, res); if (error) return error; } return (rman_release_resource(res)); } static int macio_activate_resource(device_t bus, device_t child, int type, int rid, struct resource *res) { struct macio_softc *sc; void *p; sc = device_get_softc(bus); if (type == SYS_RES_IRQ) return (bus_activate_resource(bus, type, rid, res)); if ((type == SYS_RES_MEMORY) || (type == SYS_RES_IOPORT)) { p = pmap_mapdev((vm_offset_t)rman_get_start(res) + sc->sc_base, (vm_size_t)rman_get_size(res)); if (p == NULL) return (ENOMEM); rman_set_virtual(res, p); rman_set_bustag(res, &bs_le_tag); rman_set_bushandle(res, (u_long)p); } return (rman_activate_resource(res)); } static int macio_deactivate_resource(device_t bus, device_t child, int type, int rid, struct resource *res) { /* * If this is a memory resource, unmap it. */ if ((type == SYS_RES_MEMORY) || (type == SYS_RES_IOPORT)) { u_int32_t psize; psize = rman_get_size(res); pmap_unmapdev((vm_offset_t)rman_get_virtual(res), psize); } return (rman_deactivate_resource(res)); } static struct resource_list * macio_get_resource_list (device_t dev, device_t child) { struct macio_devinfo *dinfo; dinfo = device_get_ivars(child); return (&dinfo->mdi_resources); } static const struct ofw_bus_devinfo * macio_get_devinfo(device_t dev, device_t child) { struct macio_devinfo *dinfo; dinfo = device_get_ivars(child); return (&dinfo->mdi_obdinfo); } int macio_enable_wireless(device_t dev, bool enable) { struct macio_softc *sc = device_get_softc(dev); uint32_t x; if (enable) { x = bus_read_4(sc->sc_memr, KEYLARGO_FCR2); x |= 0x4; bus_write_4(sc->sc_memr, KEYLARGO_FCR2, x); /* Enable card slot. */ bus_write_1(sc->sc_memr, KEYLARGO_GPIO_BASE + 0x0f, 5); DELAY(1000); bus_write_1(sc->sc_memr, KEYLARGO_GPIO_BASE + 0x0f, 4); DELAY(1000); x = bus_read_4(sc->sc_memr, KEYLARGO_FCR2); x &= ~0x80000000; bus_write_4(sc->sc_memr, KEYLARGO_FCR2, x); /* out8(gpio + 0x10, 4); */ bus_write_1(sc->sc_memr, KEYLARGO_EXTINT_GPIO_REG_BASE + 0x0b, 0); bus_write_1(sc->sc_memr, KEYLARGO_EXTINT_GPIO_REG_BASE + 0x0a, 0x28); bus_write_1(sc->sc_memr, KEYLARGO_EXTINT_GPIO_REG_BASE + 0x0d, 0x28); bus_write_1(sc->sc_memr, KEYLARGO_GPIO_BASE + 0x0d, 0x28); bus_write_1(sc->sc_memr, KEYLARGO_GPIO_BASE + 0x0e, 0x28); bus_write_4(sc->sc_memr, 0x1c000, 0); /* Initialize the card. */ bus_write_4(sc->sc_memr, 0x1a3e0, 0x41); x = bus_read_4(sc->sc_memr, KEYLARGO_FCR2); x |= 0x80000000; bus_write_4(sc->sc_memr, KEYLARGO_FCR2, x); } else { x = bus_read_4(sc->sc_memr, KEYLARGO_FCR2); x &= ~0x4; bus_write_4(sc->sc_memr, KEYLARGO_FCR2, x); /* out8(gpio + 0x10, 0); */ } return (0); } Index: head/sys/powerpc/powermac/uninorth.c =================================================================== --- head/sys/powerpc/powermac/uninorth.c (revision 332309) +++ head/sys/powerpc/powermac/uninorth.c (revision 332310) @@ -1,676 +1,676 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (C) 2002 Benno Rice. * 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 Benno Rice ``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 TOOLS GMBH 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. * * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Driver for the Uninorth chip itself. */ static MALLOC_DEFINE(M_UNIN, "unin", "unin device information"); /* * Device interface. */ static int unin_chip_probe(device_t); static int unin_chip_attach(device_t); /* * Bus interface. */ static int unin_chip_print_child(device_t dev, device_t child); static void unin_chip_probe_nomatch(device_t, device_t); static struct resource *unin_chip_alloc_resource(device_t, device_t, int, int *, rman_res_t, rman_res_t, rman_res_t, u_int); static int unin_chip_activate_resource(device_t, device_t, int, int, struct resource *); static int unin_chip_deactivate_resource(device_t, device_t, int, int, struct resource *); static int unin_chip_release_resource(device_t, device_t, int, int, struct resource *); static struct resource_list *unin_chip_get_resource_list (device_t, device_t); /* * OFW Bus interface */ static ofw_bus_get_devinfo_t unin_chip_get_devinfo; /* * Local routines */ static void unin_enable_gmac(device_t dev); static void unin_enable_mpic(device_t dev); /* * Driver methods. */ static device_method_t unin_chip_methods[] = { /* Device interface */ DEVMETHOD(device_probe, unin_chip_probe), DEVMETHOD(device_attach, unin_chip_attach), /* Bus interface */ DEVMETHOD(bus_print_child, unin_chip_print_child), DEVMETHOD(bus_probe_nomatch, unin_chip_probe_nomatch), DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD(bus_alloc_resource, unin_chip_alloc_resource), DEVMETHOD(bus_release_resource, unin_chip_release_resource), DEVMETHOD(bus_activate_resource, unin_chip_activate_resource), DEVMETHOD(bus_deactivate_resource, unin_chip_deactivate_resource), DEVMETHOD(bus_get_resource_list, unin_chip_get_resource_list), DEVMETHOD(bus_child_pnpinfo_str, ofw_bus_gen_child_pnpinfo_str), /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_devinfo, unin_chip_get_devinfo), DEVMETHOD(ofw_bus_get_compat, ofw_bus_gen_get_compat), DEVMETHOD(ofw_bus_get_model, ofw_bus_gen_get_model), DEVMETHOD(ofw_bus_get_name, ofw_bus_gen_get_name), DEVMETHOD(ofw_bus_get_node, ofw_bus_gen_get_node), DEVMETHOD(ofw_bus_get_type, ofw_bus_gen_get_type), { 0, 0 } }; static driver_t unin_chip_driver = { "unin", unin_chip_methods, sizeof(struct unin_chip_softc) }; static devclass_t unin_chip_devclass; /* * Assume there is only one unin chip in a PowerMac, so that pmu.c functions can * suspend the chip after the whole rest of the device tree is suspended, not * earlier. */ static device_t unin_chip; DRIVER_MODULE(unin, ofwbus, unin_chip_driver, unin_chip_devclass, 0, 0); /* * Add an interrupt to the dev's resource list if present */ static void unin_chip_add_intr(phandle_t devnode, struct unin_chip_devinfo *dinfo) { phandle_t iparent; int *intr; int i, nintr; int icells; if (dinfo->udi_ninterrupts >= 6) { printf("unin: device has more than 6 interrupts\n"); return; } - nintr = OF_getprop_alloc(devnode, "interrupts", sizeof(*intr), + nintr = OF_getprop_alloc_multi(devnode, "interrupts", sizeof(*intr), (void **)&intr); if (nintr == -1) { - nintr = OF_getprop_alloc(devnode, "AAPL,interrupts", + nintr = OF_getprop_alloc_multi(devnode, "AAPL,interrupts", sizeof(*intr), (void **)&intr); if (nintr == -1) return; } if (intr[0] == -1) return; if (OF_getprop(devnode, "interrupt-parent", &iparent, sizeof(iparent)) <= 0) panic("Interrupt but no interrupt parent!\n"); if (OF_searchprop(iparent, "#interrupt-cells", &icells, sizeof(icells)) <= 0) icells = 1; for (i = 0; i < nintr; i+=icells) { u_int irq = MAP_IRQ(iparent, intr[i]); resource_list_add(&dinfo->udi_resources, SYS_RES_IRQ, dinfo->udi_ninterrupts, irq, irq, 1); if (icells > 1) { powerpc_config_intr(irq, (intr[i+1] & 1) ? INTR_TRIGGER_LEVEL : INTR_TRIGGER_EDGE, INTR_POLARITY_LOW); } dinfo->udi_interrupts[dinfo->udi_ninterrupts] = irq; dinfo->udi_ninterrupts++; } } static void unin_chip_add_reg(phandle_t devnode, struct unin_chip_devinfo *dinfo) { struct unin_chip_reg *reg; int i, nreg; - nreg = OF_getprop_alloc(devnode, "reg", sizeof(*reg), (void **)®); + nreg = OF_getprop_alloc_multi(devnode, "reg", sizeof(*reg), (void **)®); if (nreg == -1) return; for (i = 0; i < nreg; i++) { resource_list_add(&dinfo->udi_resources, SYS_RES_MEMORY, i, reg[i].mr_base, reg[i].mr_base + reg[i].mr_size, reg[i].mr_size); } } static void unin_update_reg(device_t dev, uint32_t regoff, uint32_t set, uint32_t clr) { volatile u_int *reg; struct unin_chip_softc *sc; u_int32_t tmpl; sc = device_get_softc(dev); reg = (void *)(sc->sc_addr + regoff); tmpl = inl(reg); tmpl &= ~clr; tmpl |= set; outl(reg, tmpl); } static void unin_enable_gmac(device_t dev) { unin_update_reg(dev, UNIN_CLOCKCNTL, UNIN_CLOCKCNTL_GMAC, 0); } static void unin_enable_mpic(device_t dev) { unin_update_reg(dev, UNIN_TOGGLE_REG, UNIN_MPIC_RESET | UNIN_MPIC_OUTPUT_ENABLE, 0); } static int unin_chip_probe(device_t dev) { const char *name; name = ofw_bus_get_name(dev); if (name == NULL) return (ENXIO); if (strcmp(name, "uni-n") != 0 && strcmp(name, "u3") != 0 && strcmp(name, "u4") != 0) return (ENXIO); device_set_desc(dev, "Apple UniNorth System Controller"); return (0); } static int unin_chip_attach(device_t dev) { struct unin_chip_softc *sc; struct unin_chip_devinfo *dinfo; phandle_t root; phandle_t child; phandle_t iparent; device_t cdev; cell_t acells, scells; char compat[32]; char name[32]; u_int irq, reg[3]; int error, i = 0; sc = device_get_softc(dev); root = ofw_bus_get_node(dev); if (OF_getprop(root, "reg", reg, sizeof(reg)) < 8) return (ENXIO); acells = scells = 1; OF_getprop(OF_parent(root), "#address-cells", &acells, sizeof(acells)); OF_getprop(OF_parent(root), "#size-cells", &scells, sizeof(scells)); i = 0; sc->sc_physaddr = reg[i++]; if (acells == 2) { sc->sc_physaddr <<= 32; sc->sc_physaddr |= reg[i++]; } sc->sc_size = reg[i++]; if (scells == 2) { sc->sc_size <<= 32; sc->sc_size |= reg[i++]; } sc->sc_mem_rman.rm_type = RMAN_ARRAY; sc->sc_mem_rman.rm_descr = "UniNorth Device Memory"; error = rman_init(&sc->sc_mem_rman); if (error) { device_printf(dev, "rman_init() failed. error = %d\n", error); return (error); } error = rman_manage_region(&sc->sc_mem_rman, sc->sc_physaddr, sc->sc_physaddr + sc->sc_size - 1); if (error) { device_printf(dev, "rman_manage_region() failed. error = %d\n", error); return (error); } if (unin_chip == NULL) unin_chip = dev; /* * Iterate through the sub-devices */ for (child = OF_child(root); child != 0; child = OF_peer(child)) { dinfo = malloc(sizeof(*dinfo), M_UNIN, M_WAITOK | M_ZERO); if (ofw_bus_gen_setup_devinfo(&dinfo->udi_obdinfo, child) != 0) { free(dinfo, M_UNIN); continue; } resource_list_init(&dinfo->udi_resources); dinfo->udi_ninterrupts = 0; unin_chip_add_intr(child, dinfo); /* * Some Apple machines do have a bug in OF, they miss * the interrupt entries on the U3 I2C node. That means they * do not have an entry with number of interrupts nor the * entry of the interrupt parent handle. * We define an interrupt and hardwire it to the /u3/mpic * handle. */ if (OF_getprop(child, "name", name, sizeof(name)) <= 0) device_printf(dev, "device has no name!\n"); if (dinfo->udi_ninterrupts == 0 && (strcmp(name, "i2c-bus") == 0 || strcmp(name, "i2c") == 0)) { if (OF_getprop(child, "interrupt-parent", &iparent, sizeof(iparent)) <= 0) { iparent = OF_finddevice("/u3/mpic"); device_printf(dev, "Set /u3/mpic as iparent!\n"); } /* Add an interrupt number 0 to the parent. */ irq = MAP_IRQ(iparent, 0); resource_list_add(&dinfo->udi_resources, SYS_RES_IRQ, dinfo->udi_ninterrupts, irq, irq, 1); dinfo->udi_interrupts[dinfo->udi_ninterrupts] = irq; dinfo->udi_ninterrupts++; } unin_chip_add_reg(child, dinfo); cdev = device_add_child(dev, NULL, -1); if (cdev == NULL) { device_printf(dev, "<%s>: device_add_child failed\n", dinfo->udi_obdinfo.obd_name); resource_list_free(&dinfo->udi_resources); ofw_bus_gen_destroy_devinfo(&dinfo->udi_obdinfo); free(dinfo, M_UNIN); continue; } device_set_ivars(cdev, dinfo); } /* * Only map the first page, since that is where the registers * of interest lie. */ sc->sc_addr = (vm_offset_t)pmap_mapdev(sc->sc_physaddr, PAGE_SIZE); sc->sc_version = *(u_int *)sc->sc_addr; device_printf(dev, "Version %d\n", sc->sc_version); /* * Enable the GMAC Ethernet cell and the integrated OpenPIC * if Open Firmware says they are used. */ for (child = OF_child(root); child; child = OF_peer(child)) { memset(compat, 0, sizeof(compat)); OF_getprop(child, "compatible", compat, sizeof(compat)); if (strcmp(compat, "gmac") == 0) unin_enable_gmac(dev); if (strcmp(compat, "chrp,open-pic") == 0) unin_enable_mpic(dev); } /* * GMAC lives under the PCI bus, so just check if enet is gmac. */ child = OF_finddevice("enet"); memset(compat, 0, sizeof(compat)); OF_getprop(child, "compatible", compat, sizeof(compat)); if (strcmp(compat, "gmac") == 0) unin_enable_gmac(dev); return (bus_generic_attach(dev)); } static int unin_chip_print_child(device_t dev, device_t child) { struct unin_chip_devinfo *dinfo; struct resource_list *rl; int retval = 0; dinfo = device_get_ivars(child); rl = &dinfo->udi_resources; retval += bus_print_child_header(dev, child); retval += resource_list_print_type(rl, "mem", SYS_RES_MEMORY, "%#jx"); retval += resource_list_print_type(rl, "irq", SYS_RES_IRQ, "%jd"); retval += bus_print_child_footer(dev, child); return (retval); } static void unin_chip_probe_nomatch(device_t dev, device_t child) { struct unin_chip_devinfo *dinfo; struct resource_list *rl; const char *type; if (bootverbose) { dinfo = device_get_ivars(child); rl = &dinfo->udi_resources; if ((type = ofw_bus_get_type(child)) == NULL) type = "(unknown)"; device_printf(dev, "<%s, %s>", type, ofw_bus_get_name(child)); resource_list_print_type(rl, "mem", SYS_RES_MEMORY, "%#jx"); resource_list_print_type(rl, "irq", SYS_RES_IRQ, "%jd"); printf(" (no driver attached)\n"); } } static struct resource * unin_chip_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 unin_chip_softc *sc; int needactivate; struct resource *rv; struct rman *rm; u_long adjstart, adjend, adjcount; struct unin_chip_devinfo *dinfo; struct resource_list_entry *rle; sc = device_get_softc(bus); dinfo = device_get_ivars(child); needactivate = flags & RF_ACTIVE; flags &= ~RF_ACTIVE; switch (type) { case SYS_RES_MEMORY: case SYS_RES_IOPORT: rle = resource_list_find(&dinfo->udi_resources, SYS_RES_MEMORY, *rid); if (rle == NULL) { device_printf(bus, "no rle for %s memory %d\n", device_get_nameunit(child), *rid); return (NULL); } rle->end = rle->end - 1; /* Hack? */ if (start < rle->start) adjstart = rle->start; else if (start > rle->end) adjstart = rle->end; else adjstart = start; if (end < rle->start) adjend = rle->start; else if (end > rle->end) adjend = rle->end; else adjend = end; adjcount = adjend - adjstart; rm = &sc->sc_mem_rman; break; case SYS_RES_IRQ: /* Check for passthrough from subattachments. */ if (device_get_parent(child) != bus) return BUS_ALLOC_RESOURCE(device_get_parent(bus), child, type, rid, start, end, count, flags); rle = resource_list_find(&dinfo->udi_resources, SYS_RES_IRQ, *rid); if (rle == NULL) { if (dinfo->udi_ninterrupts >= 6) { device_printf(bus, "%s has more than 6 interrupts\n", device_get_nameunit(child)); return (NULL); } resource_list_add(&dinfo->udi_resources, SYS_RES_IRQ, dinfo->udi_ninterrupts, start, start, 1); dinfo->udi_interrupts[dinfo->udi_ninterrupts] = start; dinfo->udi_ninterrupts++; } return (resource_list_alloc(&dinfo->udi_resources, bus, child, type, rid, start, end, count, flags)); default: device_printf(bus, "unknown resource request from %s\n", device_get_nameunit(child)); return (NULL); } rv = rman_reserve_resource(rm, adjstart, adjend, adjcount, flags, child); if (rv == NULL) { device_printf(bus, "failed to reserve resource %#lx - %#lx (%#lx)" " for %s\n", adjstart, adjend, adjcount, device_get_nameunit(child)); return (NULL); } rman_set_rid(rv, *rid); if (needactivate) { if (bus_activate_resource(child, type, *rid, rv) != 0) { device_printf(bus, "failed to activate resource for %s\n", device_get_nameunit(child)); rman_release_resource(rv); return (NULL); } } return (rv); } static int unin_chip_release_resource(device_t bus, device_t child, int type, int rid, struct resource *res) { if (rman_get_flags(res) & RF_ACTIVE) { int error = bus_deactivate_resource(child, type, rid, res); if (error) return error; } return (rman_release_resource(res)); } static int unin_chip_activate_resource(device_t bus, device_t child, int type, int rid, struct resource *res) { void *p; if (type == SYS_RES_IRQ) return (bus_activate_resource(bus, type, rid, res)); if ((type == SYS_RES_MEMORY) || (type == SYS_RES_IOPORT)) { vm_offset_t start; start = (vm_offset_t) rman_get_start(res); if (bootverbose) printf("unin mapdev: start %zx, len %jd\n", start, rman_get_size(res)); p = pmap_mapdev(start, (vm_size_t) rman_get_size(res)); if (p == NULL) return (ENOMEM); rman_set_virtual(res, p); rman_set_bustag(res, &bs_be_tag); rman_set_bushandle(res, (u_long)p); } return (rman_activate_resource(res)); } static int unin_chip_deactivate_resource(device_t bus, device_t child, int type, int rid, struct resource *res) { /* * If this is a memory resource, unmap it. */ if ((type == SYS_RES_MEMORY) || (type == SYS_RES_IOPORT)) { u_int32_t psize; psize = rman_get_size(res); pmap_unmapdev((vm_offset_t)rman_get_virtual(res), psize); } return (rman_deactivate_resource(res)); } static struct resource_list * unin_chip_get_resource_list (device_t dev, device_t child) { struct unin_chip_devinfo *dinfo; dinfo = device_get_ivars(child); return (&dinfo->udi_resources); } static const struct ofw_bus_devinfo * unin_chip_get_devinfo(device_t dev, device_t child) { struct unin_chip_devinfo *dinfo; dinfo = device_get_ivars(child); return (&dinfo->udi_obdinfo); } int unin_chip_wake(device_t dev) { if (dev == NULL) dev = unin_chip; unin_update_reg(dev, UNIN_PWR_MGMT, UNIN_PWR_NORMAL, UNIN_PWR_MASK); DELAY(10); unin_update_reg(dev, UNIN_HWINIT_STATE, UNIN_RUNNING, 0); DELAY(100); return (0); } int unin_chip_sleep(device_t dev, int idle) { if (dev == NULL) dev = unin_chip; unin_update_reg(dev, UNIN_HWINIT_STATE, UNIN_SLEEPING, 0); DELAY(10); if (idle) unin_update_reg(dev, UNIN_PWR_MGMT, UNIN_PWR_IDLE2, UNIN_PWR_MASK); else unin_update_reg(dev, UNIN_PWR_MGMT, UNIN_PWR_SLEEP, UNIN_PWR_MASK); DELAY(10); return (0); } Index: head/sys/powerpc/psim/iobus.c =================================================================== --- head/sys/powerpc/psim/iobus.c (revision 332309) +++ head/sys/powerpc/psim/iobus.c (revision 332310) @@ -1,415 +1,415 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright 2002 by Peter Grehan. 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. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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. * * $FreeBSD$ */ /* * PSIM 'iobus' local bus. Should be set up in the device tree like: * * /iobus@0x80000000/name psim-iobus * * Code borrowed from various nexus.c and uninorth.c :-) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct iobus_softc { phandle_t sc_node; vm_offset_t sc_addr; vm_offset_t sc_size; struct rman sc_mem_rman; }; static MALLOC_DEFINE(M_IOBUS, "iobus", "iobus device information"); static int iobus_probe(device_t); static int iobus_attach(device_t); static int iobus_print_child(device_t dev, device_t child); static void iobus_probe_nomatch(device_t, device_t); static int iobus_read_ivar(device_t, device_t, int, uintptr_t *); static int iobus_write_ivar(device_t, device_t, int, uintptr_t); static struct resource *iobus_alloc_resource(device_t, device_t, int, int *, rman_res_t, rman_res_t, rman_res_t, u_int); static int iobus_activate_resource(device_t, device_t, int, int, struct resource *); static int iobus_deactivate_resource(device_t, device_t, int, int, struct resource *); static int iobus_release_resource(device_t, device_t, int, int, struct resource *); /* * Bus interface definition */ static device_method_t iobus_methods[] = { /* Device interface */ DEVMETHOD(device_probe, iobus_probe), DEVMETHOD(device_attach, iobus_attach), DEVMETHOD(device_detach, bus_generic_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* Bus interface */ DEVMETHOD(bus_print_child, iobus_print_child), DEVMETHOD(bus_probe_nomatch, iobus_probe_nomatch), DEVMETHOD(bus_read_ivar, iobus_read_ivar), DEVMETHOD(bus_write_ivar, iobus_write_ivar), DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD(bus_alloc_resource, iobus_alloc_resource), DEVMETHOD(bus_release_resource, iobus_release_resource), DEVMETHOD(bus_activate_resource, iobus_activate_resource), DEVMETHOD(bus_deactivate_resource, iobus_deactivate_resource), { 0, 0 } }; static driver_t iobus_driver = { "iobus", iobus_methods, sizeof(struct iobus_softc) }; devclass_t iobus_devclass; DRIVER_MODULE(iobus, ofwbus, iobus_driver, iobus_devclass, 0, 0); static int iobus_probe(device_t dev) { const char *type = ofw_bus_get_name(dev); if (strcmp(type, "psim-iobus") != 0) return (ENXIO); device_set_desc(dev, "PSIM local bus"); return (0); } /* * Add interrupt/addr range to the dev's resource list if present */ static void iobus_add_intr(phandle_t devnode, struct iobus_devinfo *dinfo) { u_int intr = -1; if (OF_getprop(devnode, "interrupt", &intr, sizeof(intr)) != -1) { resource_list_add(&dinfo->id_resources, SYS_RES_IRQ, 0, intr, intr, 1); } dinfo->id_interrupt = intr; } static void iobus_add_reg(phandle_t devnode, struct iobus_devinfo *dinfo, vm_offset_t iobus_off) { u_int size; int i; size = OF_getprop(devnode, "reg", dinfo->id_reg,sizeof(dinfo->id_reg)); if (size != -1) { dinfo->id_nregs = size / (sizeof(dinfo->id_reg[0])); for (i = 0; i < dinfo->id_nregs; i+= 3) { /* * Scale the absolute addresses back to iobus * relative offsets. This is to better simulate * macio */ dinfo->id_reg[i+1] -= iobus_off; resource_list_add(&dinfo->id_resources, SYS_RES_MEMORY, 0, dinfo->id_reg[i+1], dinfo->id_reg[i+1] + dinfo->id_reg[i+2], dinfo->id_reg[i+2]); } } } static int iobus_attach(device_t dev) { struct iobus_softc *sc; struct iobus_devinfo *dinfo; phandle_t root; phandle_t child; device_t cdev; char *name; u_int reg[2]; int size; sc = device_get_softc(dev); sc->sc_node = ofw_bus_get_node(dev); /* * Find the base addr/size of the iobus, and initialize the * resource manager */ size = OF_getprop(sc->sc_node, "reg", reg, sizeof(reg)); if (size == sizeof(reg)) { sc->sc_addr = reg[0]; sc->sc_size = reg[1]; } else { return (ENXIO); } sc->sc_mem_rman.rm_type = RMAN_ARRAY; sc->sc_mem_rman.rm_descr = "IOBus Device Memory"; if (rman_init(&sc->sc_mem_rman) != 0) { device_printf(dev, "failed to init mem range resources\n"); return (ENXIO); } rman_manage_region(&sc->sc_mem_rman, 0, sc->sc_size); /* * Iterate through the sub-devices */ root = sc->sc_node; for (child = OF_child(root); child != 0; child = OF_peer(child)) { - OF_getprop_alloc(child, "name", 1, (void **)&name); + OF_getprop_alloc(child, "name", (void **)&name); cdev = device_add_child(dev, NULL, -1); if (cdev != NULL) { dinfo = malloc(sizeof(*dinfo), M_IOBUS, M_WAITOK); memset(dinfo, 0, sizeof(*dinfo)); resource_list_init(&dinfo->id_resources); dinfo->id_node = child; dinfo->id_name = name; iobus_add_intr(child, dinfo); iobus_add_reg(child, dinfo, sc->sc_addr); device_set_ivars(cdev, dinfo); } else { OF_prop_free(name); } } return (bus_generic_attach(dev)); } static int iobus_print_child(device_t dev, device_t child) { struct iobus_devinfo *dinfo; struct resource_list *rl; int retval = 0; dinfo = device_get_ivars(child); rl = &dinfo->id_resources; retval += bus_print_child_header(dev, child); retval += printf(" offset 0x%x", dinfo->id_reg[1]); retval += resource_list_print_type(rl, "irq", SYS_RES_IRQ, "%jd"); retval += bus_print_child_footer(dev, child); return (retval); } static void iobus_probe_nomatch(device_t dev, device_t child) { } static int iobus_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) { struct iobus_devinfo *dinfo; if ((dinfo = device_get_ivars(child)) == NULL) return (ENOENT); switch (which) { case IOBUS_IVAR_NODE: *result = dinfo->id_node; break; case IOBUS_IVAR_NAME: *result = (uintptr_t)dinfo->id_name; break; case IOBUS_IVAR_NREGS: *result = dinfo->id_nregs; break; case IOBUS_IVAR_REGS: *result = (uintptr_t)dinfo->id_reg; break; default: return (ENOENT); } return (0); } static int iobus_write_ivar(device_t dev, device_t child, int which, uintptr_t value) { return (EINVAL); } static struct resource * iobus_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 iobus_softc *sc; int needactivate; struct resource *rv; struct rman *rm; sc = device_get_softc(bus); needactivate = flags & RF_ACTIVE; flags &= ~RF_ACTIVE; switch (type) { case SYS_RES_MEMORY: case SYS_RES_IOPORT: rm = &sc->sc_mem_rman; break; case SYS_RES_IRQ: return (bus_alloc_resource(bus, type, rid, start, end, count, flags)); default: device_printf(bus, "unknown resource request from %s\n", device_get_nameunit(child)); return (NULL); } rv = rman_reserve_resource(rm, start, end, count, flags, child); if (rv == NULL) { device_printf(bus, "failed to reserve resource for %s\n", device_get_nameunit(child)); return (NULL); } rman_set_rid(rv, *rid); if (needactivate) { if (bus_activate_resource(child, type, *rid, rv) != 0) { device_printf(bus, "failed to activate resource for %s\n", device_get_nameunit(child)); rman_release_resource(rv); return (NULL); } } return (rv); } static int iobus_release_resource(device_t bus, device_t child, int type, int rid, struct resource *res) { if (rman_get_flags(res) & RF_ACTIVE) { int error = bus_deactivate_resource(child, type, rid, res); if (error) return error; } return (rman_release_resource(res)); } static int iobus_activate_resource(device_t bus, device_t child, int type, int rid, struct resource *res) { struct iobus_softc *sc; void *p; sc = device_get_softc(bus); if (type == SYS_RES_IRQ) return (bus_activate_resource(bus, type, rid, res)); if ((type == SYS_RES_MEMORY) || (type == SYS_RES_IOPORT)) { p = pmap_mapdev((vm_offset_t)rman_get_start(res) + sc->sc_addr, (vm_size_t)rman_get_size(res)); if (p == NULL) return (ENOMEM); rman_set_virtual(res, p); rman_set_bustag(res, &bs_le_tag); rman_set_bushandle(res, (u_long)p); } return (rman_activate_resource(res)); } static int iobus_deactivate_resource(device_t bus, device_t child, int type, int rid, struct resource *res) { /* * If this is a memory resource, unmap it. */ if ((type == SYS_RES_MEMORY) || (type == SYS_RES_IOPORT)) { u_int32_t psize; psize = rman_get_size(res); pmap_unmapdev((vm_offset_t)rman_get_virtual(res), psize); } return (rman_deactivate_resource(res)); } Index: head/sys/sparc64/central/central.c =================================================================== --- head/sys/sparc64/central/central.c (revision 332309) +++ head/sys/sparc64/central/central.c (revision 332310) @@ -1,301 +1,301 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2003 Jake Burkholder. * 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$"); #include #include #include #include #include #include #include #include #include #include #include #include #include struct central_devinfo { struct ofw_bus_devinfo cdi_obdinfo; struct resource_list cdi_rl; }; struct central_softc { int sc_nrange; struct sbus_ranges *sc_ranges; }; static device_probe_t central_probe; static device_attach_t central_attach; static bus_print_child_t central_print_child; static bus_probe_nomatch_t central_probe_nomatch; static bus_alloc_resource_t central_alloc_resource; static bus_adjust_resource_t central_adjust_resource; static bus_get_resource_list_t central_get_resource_list; static ofw_bus_get_devinfo_t central_get_devinfo; static int central_print_res(struct central_devinfo *); static device_method_t central_methods[] = { /* Device interface */ DEVMETHOD(device_probe, central_probe), DEVMETHOD(device_attach, central_attach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* Bus interface */ DEVMETHOD(bus_print_child, central_print_child), DEVMETHOD(bus_probe_nomatch, central_probe_nomatch), DEVMETHOD(bus_alloc_resource, central_alloc_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_adjust_resource, central_adjust_resource), DEVMETHOD(bus_release_resource, bus_generic_rl_release_resource), DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), DEVMETHOD(bus_get_resource_list, central_get_resource_list), DEVMETHOD(bus_child_pnpinfo_str, ofw_bus_gen_child_pnpinfo_str), /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_devinfo, central_get_devinfo), DEVMETHOD(ofw_bus_get_compat, ofw_bus_gen_get_compat), DEVMETHOD(ofw_bus_get_model, ofw_bus_gen_get_model), DEVMETHOD(ofw_bus_get_name, ofw_bus_gen_get_name), DEVMETHOD(ofw_bus_get_node, ofw_bus_gen_get_node), DEVMETHOD(ofw_bus_get_type, ofw_bus_gen_get_type), DEVMETHOD_END }; static driver_t central_driver = { "central", central_methods, sizeof(struct central_softc), }; static devclass_t central_devclass; EARLY_DRIVER_MODULE(central, nexus, central_driver, central_devclass, 0, 0, BUS_PASS_BUS); MODULE_DEPEND(fhc, nexus, 1, 1, 1); MODULE_VERSION(central, 1); static int central_probe(device_t dev) { if (strcmp(ofw_bus_get_name(dev), "central") == 0) { device_set_desc(dev, "central"); return (0); } return (ENXIO); } static int central_attach(device_t dev) { struct central_devinfo *cdi; struct sbus_regs *reg; struct central_softc *sc; phandle_t child; phandle_t node; device_t cdev; int nreg; int i; sc = device_get_softc(dev); node = ofw_bus_get_node(dev); - sc->sc_nrange = OF_getprop_alloc(node, "ranges", + sc->sc_nrange = OF_getprop_alloc_multi(node, "ranges", sizeof(*sc->sc_ranges), (void **)&sc->sc_ranges); if (sc->sc_nrange == -1) { device_printf(dev, "can't get ranges\n"); return (ENXIO); } for (child = OF_child(node); child != 0; child = OF_peer(child)) { cdi = malloc(sizeof(*cdi), M_DEVBUF, M_WAITOK | M_ZERO); if (ofw_bus_gen_setup_devinfo(&cdi->cdi_obdinfo, child) != 0) { free(cdi, M_DEVBUF); continue; } - nreg = OF_getprop_alloc(child, "reg", sizeof(*reg), + nreg = OF_getprop_alloc_multi(child, "reg", sizeof(*reg), (void **)®); if (nreg == -1) { device_printf(dev, "<%s>: incomplete\n", cdi->cdi_obdinfo.obd_name); ofw_bus_gen_destroy_devinfo(&cdi->cdi_obdinfo); free(cdi, M_DEVBUF); continue; } resource_list_init(&cdi->cdi_rl); for (i = 0; i < nreg; i++) resource_list_add(&cdi->cdi_rl, SYS_RES_MEMORY, i, reg[i].sbr_offset, reg[i].sbr_offset + reg[i].sbr_size, reg[i].sbr_size); OF_prop_free(reg); cdev = device_add_child(dev, NULL, -1); if (cdev == NULL) { device_printf(dev, "<%s>: device_add_child failed\n", cdi->cdi_obdinfo.obd_name); resource_list_free(&cdi->cdi_rl); ofw_bus_gen_destroy_devinfo(&cdi->cdi_obdinfo); free(cdi, M_DEVBUF); continue; } device_set_ivars(cdev, cdi); } return (bus_generic_attach(dev)); } static int central_adjust_resource(device_t bus __unused, device_t child __unused, int type __unused, struct resource *r __unused, rman_res_t start __unused, rman_res_t end __unused) { return (ENXIO); } static int central_print_child(device_t dev, device_t child) { int rv; rv = bus_print_child_header(dev, child); rv += central_print_res(device_get_ivars(child)); rv += bus_print_child_footer(dev, child); return (rv); } static void central_probe_nomatch(device_t dev, device_t child) { const char *type; device_printf(dev, "<%s>", ofw_bus_get_name(child)); central_print_res(device_get_ivars(child)); type = ofw_bus_get_type(child); printf(" type %s (no driver attached)\n", type != NULL ? type : "unknown"); } static struct resource * central_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 resource_list *rl; struct resource_list_entry *rle; struct central_softc *sc; struct resource *res; bus_addr_t coffset; bus_addr_t cend; bus_addr_t phys; int isdefault; int passthrough; int i; isdefault = RMAN_IS_DEFAULT_RANGE(start, end); passthrough = (device_get_parent(child) != bus); res = NULL; rle = NULL; rl = BUS_GET_RESOURCE_LIST(bus, child); sc = device_get_softc(bus); switch (type) { case SYS_RES_IRQ: return (resource_list_alloc(rl, bus, child, type, rid, start, end, count, flags)); case SYS_RES_MEMORY: if (!passthrough) { rle = resource_list_find(rl, type, *rid); if (rle == NULL) return (NULL); if (rle->res != NULL) panic("%s: resource entry is busy", __func__); if (isdefault) { start = rle->start; count = ulmax(count, rle->count); end = ulmax(rle->end, start + count - 1); } } for (i = 0; i < sc->sc_nrange; i++) { coffset = sc->sc_ranges[i].coffset; cend = coffset + sc->sc_ranges[i].size - 1; if (start >= coffset && end <= cend) { start -= coffset; end -= coffset; phys = sc->sc_ranges[i].poffset | ((bus_addr_t)sc->sc_ranges[i].pspace << 32); res = bus_generic_alloc_resource(bus, child, type, rid, phys + start, phys + end, count, flags); if (!passthrough) rle->res = res; break; } } break; } return (res); } static struct resource_list * central_get_resource_list(device_t bus, device_t child) { struct central_devinfo *cdi; cdi = device_get_ivars(child); return (&cdi->cdi_rl); } static const struct ofw_bus_devinfo * central_get_devinfo(device_t bus, device_t child) { struct central_devinfo *cdi; cdi = device_get_ivars(child); return (&cdi->cdi_obdinfo); } static int central_print_res(struct central_devinfo *cdi) { return (resource_list_print_type(&cdi->cdi_rl, "mem", SYS_RES_MEMORY, "%#jx")); } Index: head/sys/sparc64/ebus/ebus.c =================================================================== --- head/sys/sparc64/ebus/ebus.c (revision 332309) +++ head/sys/sparc64/ebus/ebus.c (revision 332310) @@ -1,730 +1,730 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-NetBSD AND BSD-3-Clause * * Copyright (c) 1999, 2000 Matthew R. Green * Copyright (c) 2009 by Marius Strobl * 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. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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. * * from: NetBSD: ebus.c,v 1.52 2008/05/29 14:51:26 mrg Exp */ /*- * Copyright (c) 2001 Thomas Moestl * 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. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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 JBus to EBus and PCI to EBus bridges */ #include #include #include #include #include #include #include #include #include #include #include #ifndef SUN4V #include #endif #include #include #include #include #include /* * The register, interrupt map and for the PCI variant also the ranges * properties are identical to the ISA ones. */ #include struct ebus_nexus_ranges { uint32_t child_hi; uint32_t child_lo; uint32_t phys_hi; uint32_t phys_lo; uint32_t size; }; struct ebus_devinfo { struct ofw_bus_devinfo edi_obdinfo; struct resource_list edi_rl; }; struct ebus_rinfo { int eri_rtype; struct rman eri_rman; struct resource *eri_res; }; struct ebus_softc { void *sc_range; struct ebus_rinfo *sc_rinfo; u_int sc_flags; #define EBUS_PCI (1 << 0) int sc_nrange; struct ofw_bus_iinfo sc_iinfo; #ifndef SUN4V uint32_t sc_ign; #endif }; static device_probe_t ebus_nexus_probe; static device_attach_t ebus_nexus_attach; static device_probe_t ebus_pci_probe; static device_attach_t ebus_pci_attach; static bus_print_child_t ebus_print_child; static bus_probe_nomatch_t ebus_probe_nomatch; static bus_alloc_resource_t ebus_alloc_resource; static bus_activate_resource_t ebus_activate_resource; static bus_adjust_resource_t ebus_adjust_resource; static bus_release_resource_t ebus_release_resource; static bus_setup_intr_t ebus_setup_intr; static bus_get_resource_list_t ebus_get_resource_list; static ofw_bus_get_devinfo_t ebus_get_devinfo; static int ebus_attach(device_t dev, struct ebus_softc *sc, phandle_t node); static struct ebus_devinfo *ebus_setup_dinfo(device_t dev, struct ebus_softc *sc, phandle_t node); static void ebus_destroy_dinfo(struct ebus_devinfo *edi); static int ebus_print_res(struct ebus_devinfo *edi); static devclass_t ebus_devclass; static device_method_t ebus_nexus_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ebus_nexus_probe), DEVMETHOD(device_attach, ebus_nexus_attach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* Bus interface */ DEVMETHOD(bus_print_child, ebus_print_child), DEVMETHOD(bus_probe_nomatch, ebus_probe_nomatch), DEVMETHOD(bus_alloc_resource, ebus_alloc_resource), DEVMETHOD(bus_activate_resource, ebus_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_adjust_resource, ebus_adjust_resource), DEVMETHOD(bus_release_resource, ebus_release_resource), DEVMETHOD(bus_setup_intr, ebus_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), DEVMETHOD(bus_get_resource_list, ebus_get_resource_list), DEVMETHOD(bus_child_pnpinfo_str, ofw_bus_gen_child_pnpinfo_str), /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_devinfo, ebus_get_devinfo), DEVMETHOD(ofw_bus_get_compat, ofw_bus_gen_get_compat), DEVMETHOD(ofw_bus_get_model, ofw_bus_gen_get_model), DEVMETHOD(ofw_bus_get_name, ofw_bus_gen_get_name), DEVMETHOD(ofw_bus_get_node, ofw_bus_gen_get_node), DEVMETHOD(ofw_bus_get_type, ofw_bus_gen_get_type), DEVMETHOD_END }; static driver_t ebus_nexus_driver = { "ebus", ebus_nexus_methods, sizeof(struct ebus_softc), }; /* * NB: we rely on the interrupt controllers of the accompanying PCI-Express * bridge to be registered as the nexus variant of the EBus bridges doesn't * employ its own one. */ EARLY_DRIVER_MODULE(ebus, nexus, ebus_nexus_driver, ebus_devclass, 0, 0, BUS_PASS_BUS + 1); MODULE_DEPEND(ebus, nexus, 1, 1, 1); static device_method_t ebus_pci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ebus_pci_probe), DEVMETHOD(device_attach, ebus_pci_attach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* Bus interface */ DEVMETHOD(bus_print_child, ebus_print_child), DEVMETHOD(bus_probe_nomatch, ebus_probe_nomatch), DEVMETHOD(bus_alloc_resource, ebus_alloc_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_release_resource, ebus_release_resource), DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), DEVMETHOD(bus_get_resource_list, ebus_get_resource_list), DEVMETHOD(bus_child_pnpinfo_str, ofw_bus_gen_child_pnpinfo_str), /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_devinfo, ebus_get_devinfo), DEVMETHOD(ofw_bus_get_compat, ofw_bus_gen_get_compat), DEVMETHOD(ofw_bus_get_model, ofw_bus_gen_get_model), DEVMETHOD(ofw_bus_get_name, ofw_bus_gen_get_name), DEVMETHOD(ofw_bus_get_node, ofw_bus_gen_get_node), DEVMETHOD(ofw_bus_get_type, ofw_bus_gen_get_type), DEVMETHOD_END }; static driver_t ebus_pci_driver = { "ebus", ebus_pci_methods, sizeof(struct ebus_softc), }; EARLY_DRIVER_MODULE(ebus, pci, ebus_pci_driver, ebus_devclass, 0, 0, BUS_PASS_BUS); MODULE_DEPEND(ebus, pci, 1, 1, 1); MODULE_VERSION(ebus, 1); static int ebus_nexus_probe(device_t dev) { const char* compat; compat = ofw_bus_get_compat(dev); if (compat != NULL && strcmp(ofw_bus_get_name(dev), "ebus") == 0 && strcmp(compat, "jbus-ebus") == 0) { device_set_desc(dev, "JBus-EBus bridge"); return (BUS_PROBE_GENERIC); } return (ENXIO); } static int ebus_pci_probe(device_t dev) { if (pci_get_class(dev) != PCIC_BRIDGE || pci_get_vendor(dev) != 0x108e || strcmp(ofw_bus_get_name(dev), "ebus") != 0) return (ENXIO); if (pci_get_device(dev) == 0x1000) device_set_desc(dev, "PCI-EBus2 bridge"); else if (pci_get_device(dev) == 0x1100) device_set_desc(dev, "PCI-EBus3 bridge"); else return (ENXIO); return (BUS_PROBE_GENERIC); } static int ebus_nexus_attach(device_t dev) { struct ebus_softc *sc; phandle_t node; sc = device_get_softc(dev); node = ofw_bus_get_node(dev); #ifndef SUN4V if (OF_getprop(node, "portid", &sc->sc_ign, sizeof(sc->sc_ign)) == -1) { device_printf(dev, "could not determine IGN"); return (ENXIO); } #endif - sc->sc_nrange = OF_getprop_alloc(node, "ranges", + sc->sc_nrange = OF_getprop_alloc_multi(node, "ranges", sizeof(struct ebus_nexus_ranges), &sc->sc_range); if (sc->sc_nrange == -1) { device_printf(dev, "could not get ranges property\n"); return (ENXIO); } return (ebus_attach(dev, sc, node)); } static int ebus_pci_attach(device_t dev) { struct ebus_softc *sc; struct ebus_rinfo *eri; struct resource *res; struct isa_ranges *range; phandle_t node; int i, rnum, rid; sc = device_get_softc(dev); sc->sc_flags |= EBUS_PCI; pci_write_config(dev, PCIR_COMMAND, pci_read_config(dev, PCIR_COMMAND, 2) | PCIM_CMD_SERRESPEN | PCIM_CMD_PERRESPEN | PCIM_CMD_BUSMASTEREN | PCIM_CMD_MEMEN, 2); pci_write_config(dev, PCIR_CACHELNSZ, 16 /* 64 bytes */, 1); pci_write_config(dev, PCIR_LATTIMER, 64 /* 64 PCI cycles */, 1); node = ofw_bus_get_node(dev); - sc->sc_nrange = OF_getprop_alloc(node, "ranges", + sc->sc_nrange = OF_getprop_alloc_multi(node, "ranges", sizeof(struct isa_ranges), &sc->sc_range); if (sc->sc_nrange == -1) { device_printf(dev, "could not get ranges property\n"); return (ENXIO); } sc->sc_rinfo = malloc(sizeof(*sc->sc_rinfo) * sc->sc_nrange, M_DEVBUF, M_WAITOK | M_ZERO); /* For every range, there must be a matching resource. */ for (rnum = 0; rnum < sc->sc_nrange; rnum++) { eri = &sc->sc_rinfo[rnum]; range = &((struct isa_ranges *)sc->sc_range)[rnum]; eri->eri_rtype = ofw_isa_range_restype(range); rid = PCIR_BAR(rnum); res = bus_alloc_resource_any(dev, eri->eri_rtype, &rid, RF_ACTIVE); if (res == NULL) { device_printf(dev, "could not allocate range resource %d\n", rnum); goto fail; } if (rman_get_start(res) != ISA_RANGE_PHYS(range)) { device_printf(dev, "mismatch in start of range %d (0x%lx/0x%lx)\n", rnum, rman_get_start(res), ISA_RANGE_PHYS(range)); goto fail; } if (rman_get_size(res) != range->size) { device_printf(dev, "mismatch in size of range %d (0x%lx/0x%x)\n", rnum, rman_get_size(res), range->size); goto fail; } eri->eri_res = res; eri->eri_rman.rm_type = RMAN_ARRAY; eri->eri_rman.rm_descr = "EBus range"; if (rman_init_from_resource(&eri->eri_rman, res) != 0) { device_printf(dev, "could not initialize rman for range %d", rnum); goto fail; } } return (ebus_attach(dev, sc, node)); fail: for (i = rnum; i >= 0; i--) { eri = &sc->sc_rinfo[i]; if (i < rnum) rman_fini(&eri->eri_rman); if (eri->eri_res != NULL) { bus_release_resource(dev, eri->eri_rtype, PCIR_BAR(rnum), eri->eri_res); } } free(sc->sc_rinfo, M_DEVBUF); OF_prop_free(sc->sc_range); return (ENXIO); } static int ebus_attach(device_t dev, struct ebus_softc *sc, phandle_t node) { struct ebus_devinfo *edi; device_t cdev; ofw_bus_setup_iinfo(node, &sc->sc_iinfo, sizeof(ofw_isa_intr_t)); /* * Now attach our children. */ for (node = OF_child(node); node > 0; node = OF_peer(node)) { if ((edi = ebus_setup_dinfo(dev, sc, node)) == NULL) continue; if ((cdev = device_add_child(dev, NULL, -1)) == NULL) { device_printf(dev, "<%s>: device_add_child failed\n", edi->edi_obdinfo.obd_name); ebus_destroy_dinfo(edi); continue; } device_set_ivars(cdev, edi); } return (bus_generic_attach(dev)); } static int ebus_print_child(device_t dev, device_t child) { int retval; retval = bus_print_child_header(dev, child); retval += ebus_print_res(device_get_ivars(child)); retval += bus_print_child_footer(dev, child); return (retval); } static void ebus_probe_nomatch(device_t dev, device_t child) { device_printf(dev, "<%s>", ofw_bus_get_name(child)); ebus_print_res(device_get_ivars(child)); printf(" (no driver attached)\n"); } static struct resource * ebus_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 ebus_softc *sc; struct resource_list *rl; struct resource_list_entry *rle = NULL; struct resource *res; struct ebus_rinfo *eri; struct ebus_nexus_ranges *enr; uint64_t cend, cstart, offset; int i, isdefault, passthrough, ridx; isdefault = RMAN_IS_DEFAULT_RANGE(start, end); passthrough = (device_get_parent(child) != bus); sc = device_get_softc(bus); rl = BUS_GET_RESOURCE_LIST(bus, child); switch (type) { case SYS_RES_MEMORY: KASSERT(!(isdefault && passthrough), ("%s: passthrough of default allocation", __func__)); if (!passthrough) { rle = resource_list_find(rl, type, *rid); if (rle == NULL) return (NULL); KASSERT(rle->res == NULL, ("%s: resource entry is busy", __func__)); if (isdefault) { start = rle->start; count = ulmax(count, rle->count); end = ulmax(rle->end, start + count - 1); } } res = NULL; if ((sc->sc_flags & EBUS_PCI) != 0) { /* * Map EBus ranges to PCI ranges. This may include * changing the allocation type. */ type = ofw_isa_range_map(sc->sc_range, sc->sc_nrange, &start, &end, &ridx); eri = &sc->sc_rinfo[ridx]; res = rman_reserve_resource(&eri->eri_rman, start, end, count, flags & ~RF_ACTIVE, child); if (res == NULL) return (NULL); rman_set_rid(res, *rid); if ((flags & RF_ACTIVE) != 0 && bus_activate_resource( child, type, *rid, res) != 0) { rman_release_resource(res); return (NULL); } } else { /* Map EBus ranges to nexus ranges. */ for (i = 0; i < sc->sc_nrange; i++) { enr = &((struct ebus_nexus_ranges *) sc->sc_range)[i]; cstart = (((uint64_t)enr->child_hi) << 32) | enr->child_lo; cend = cstart + enr->size - 1; if (start >= cstart && end <= cend) { offset = (((uint64_t)enr->phys_hi) << 32) | enr->phys_lo; start += offset - cstart; end += offset - cstart; res = bus_generic_alloc_resource(bus, child, type, rid, start, end, count, flags); break; } } } if (!passthrough) rle->res = res; return (res); case SYS_RES_IRQ: return (resource_list_alloc(rl, bus, child, type, rid, start, end, count, flags)); } return (NULL); } static int ebus_activate_resource(device_t bus, device_t child, int type, int rid, struct resource *res) { struct ebus_softc *sc; struct ebus_rinfo *eri; bus_space_tag_t bt; bus_space_handle_t bh; int i, rv; sc = device_get_softc(bus); if ((sc->sc_flags & EBUS_PCI) != 0 && type != SYS_RES_IRQ) { for (i = 0; i < sc->sc_nrange; i++) { eri = &sc->sc_rinfo[i]; if (rman_is_region_manager(res, &eri->eri_rman) != 0) { bt = rman_get_bustag(eri->eri_res); rv = bus_space_subregion(bt, rman_get_bushandle(eri->eri_res), rman_get_start(res) - rman_get_start(eri->eri_res), rman_get_size(res), &bh); if (rv != 0) return (rv); rman_set_bustag(res, bt); rman_set_bushandle(res, bh); return (rman_activate_resource(res)); } } return (EINVAL); } return (bus_generic_activate_resource(bus, child, type, rid, res)); } static int ebus_adjust_resource(device_t bus __unused, device_t child __unused, int type __unused, struct resource *res __unused, rman_res_t start __unused, rman_res_t end __unused) { return (ENXIO); } static int ebus_release_resource(device_t bus, device_t child, int type, int rid, struct resource *res) { struct ebus_softc *sc; struct resource_list *rl; struct resource_list_entry *rle; int passthrough, rv; passthrough = (device_get_parent(child) != bus); rl = BUS_GET_RESOURCE_LIST(bus, child); sc = device_get_softc(bus); if ((sc->sc_flags & EBUS_PCI) != 0 && type != SYS_RES_IRQ) { if ((rman_get_flags(res) & RF_ACTIVE) != 0 ){ rv = bus_deactivate_resource(child, type, rid, res); if (rv != 0) return (rv); } rv = rman_release_resource(res); if (rv != 0) return (rv); if (!passthrough) { rle = resource_list_find(rl, type, rid); KASSERT(rle != NULL, ("%s: resource entry not found!", __func__)); KASSERT(rle->res != NULL, ("%s: resource entry is not busy", __func__)); rle->res = NULL; } return (0); } return (resource_list_release(rl, bus, child, type, rid, res)); } static int ebus_setup_intr(device_t dev, device_t child, struct resource *ires, int flags, driver_filter_t *filt, driver_intr_t *intr, void *arg, void **cookiep) { #ifndef SUN4V struct ebus_softc *sc; u_long vec; sc = device_get_softc(dev); if ((sc->sc_flags & EBUS_PCI) == 0) { /* * Make sure the vector is fully specified. This isn't * necessarily the case with the PCI variant. */ vec = rman_get_start(ires); if (INTIGN(vec) != sc->sc_ign) { device_printf(dev, "invalid interrupt vector 0x%lx\n", vec); return (EINVAL); } /* * As we rely on the interrupt controllers of the * accompanying PCI-Express bridge ensure at least * something is registered for this vector. */ if (intr_vectors[vec].iv_ic == NULL) { device_printf(dev, "invalid interrupt controller for vector 0x%lx\n", vec); return (EINVAL); } } #endif return (bus_generic_setup_intr(dev, child, ires, flags, filt, intr, arg, cookiep)); } static struct resource_list * ebus_get_resource_list(device_t dev, device_t child) { struct ebus_devinfo *edi; edi = device_get_ivars(child); return (&edi->edi_rl); } static const struct ofw_bus_devinfo * ebus_get_devinfo(device_t bus, device_t dev) { struct ebus_devinfo *edi; edi = device_get_ivars(dev); return (&edi->edi_obdinfo); } static struct ebus_devinfo * ebus_setup_dinfo(device_t dev, struct ebus_softc *sc, phandle_t node) { struct isa_regs reg, *regs; ofw_isa_intr_t intr, *intrs; struct ebus_devinfo *edi; uint64_t start; uint32_t rintr; int i, nintr, nreg, rv; edi = malloc(sizeof(*edi), M_DEVBUF, M_ZERO | M_WAITOK); if (ofw_bus_gen_setup_devinfo(&edi->edi_obdinfo, node) != 0) { free(edi, M_DEVBUF); return (NULL); } resource_list_init(&edi->edi_rl); - nreg = OF_getprop_alloc(node, "reg", sizeof(*regs), (void **)®s); + nreg = OF_getprop_alloc_multi(node, "reg", sizeof(*regs), (void **)®s); if (nreg == -1) { device_printf(dev, "<%s>: incomplete\n", edi->edi_obdinfo.obd_name); ebus_destroy_dinfo(edi); return (NULL); } for (i = 0; i < nreg; i++) { start = ISA_REG_PHYS(regs + i); (void)resource_list_add(&edi->edi_rl, SYS_RES_MEMORY, i, start, start + regs[i].size - 1, regs[i].size); } OF_prop_free(regs); - nintr = OF_getprop_alloc(node, "interrupts", sizeof(*intrs), + nintr = OF_getprop_alloc_multi(node, "interrupts", sizeof(*intrs), (void **)&intrs); if (nintr == -1) return (edi); for (i = 0; i < nintr; i++) { rv = 0; if ((sc->sc_flags & EBUS_PCI) != 0) { rintr = ofw_isa_route_intr(dev, node, &sc->sc_iinfo, intrs[i]); } else { intr = intrs[i]; rv = ofw_bus_lookup_imap(node, &sc->sc_iinfo, ®, sizeof(reg), &intr, sizeof(intr), &rintr, sizeof(rintr), NULL); #ifndef SUN4V if (rv != 0) rintr = INTMAP_VEC(sc->sc_ign, rintr); #endif } if ((sc->sc_flags & EBUS_PCI) == 0 ? rv == 0 : rintr == PCI_INVALID_IRQ) { device_printf(dev, "<%s>: could not map EBus interrupt %d\n", edi->edi_obdinfo.obd_name, intrs[i]); continue; } (void)resource_list_add(&edi->edi_rl, SYS_RES_IRQ, i, rintr, rintr, 1); } OF_prop_free(intrs); return (edi); } static void ebus_destroy_dinfo(struct ebus_devinfo *edi) { resource_list_free(&edi->edi_rl); ofw_bus_gen_destroy_devinfo(&edi->edi_obdinfo); free(edi, M_DEVBUF); } static int ebus_print_res(struct ebus_devinfo *edi) { int retval; retval = 0; retval += resource_list_print_type(&edi->edi_rl, "addr", SYS_RES_MEMORY, "%#jx"); retval += resource_list_print_type(&edi->edi_rl, "irq", SYS_RES_IRQ, "%jd"); return (retval); } Index: head/sys/sparc64/fhc/fhc.c =================================================================== --- head/sys/sparc64/fhc/fhc.c (revision 332309) +++ head/sys/sparc64/fhc/fhc.c (revision 332310) @@ -1,537 +1,537 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2003 Jake Burkholder. * Copyright (c) 2005 Marius Strobl * 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$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct fhc_devinfo { struct ofw_bus_devinfo fdi_obdinfo; struct resource_list fdi_rl; }; struct fhc_softc { struct resource *sc_memres[FHC_NREG]; int sc_nrange; struct sbus_ranges *sc_ranges; int sc_ign; struct cdev *sc_led_dev; }; static device_probe_t fhc_probe; static device_attach_t fhc_attach; static bus_print_child_t fhc_print_child; static bus_probe_nomatch_t fhc_probe_nomatch; static bus_setup_intr_t fhc_setup_intr; static bus_alloc_resource_t fhc_alloc_resource; static bus_adjust_resource_t fhc_adjust_resource; static bus_get_resource_list_t fhc_get_resource_list; static ofw_bus_get_devinfo_t fhc_get_devinfo; static void fhc_intr_enable(void *); static void fhc_intr_disable(void *); static void fhc_intr_assign(void *); static void fhc_intr_clear(void *); static void fhc_led_func(void *, int); static int fhc_print_res(struct fhc_devinfo *); static device_method_t fhc_methods[] = { /* Device interface */ DEVMETHOD(device_probe, fhc_probe), DEVMETHOD(device_attach, fhc_attach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* Bus interface */ DEVMETHOD(bus_print_child, fhc_print_child), DEVMETHOD(bus_probe_nomatch, fhc_probe_nomatch), DEVMETHOD(bus_alloc_resource, fhc_alloc_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_adjust_resource, fhc_adjust_resource), DEVMETHOD(bus_release_resource, bus_generic_rl_release_resource), DEVMETHOD(bus_setup_intr, fhc_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), DEVMETHOD(bus_get_resource_list, fhc_get_resource_list), DEVMETHOD(bus_child_pnpinfo_str, ofw_bus_gen_child_pnpinfo_str), /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_devinfo, fhc_get_devinfo), DEVMETHOD(ofw_bus_get_compat, ofw_bus_gen_get_compat), DEVMETHOD(ofw_bus_get_model, ofw_bus_gen_get_model), DEVMETHOD(ofw_bus_get_name, ofw_bus_gen_get_name), DEVMETHOD(ofw_bus_get_node, ofw_bus_gen_get_node), DEVMETHOD(ofw_bus_get_type, ofw_bus_gen_get_type), DEVMETHOD_END }; static driver_t fhc_driver = { "fhc", fhc_methods, sizeof(struct fhc_softc), }; static devclass_t fhc_devclass; EARLY_DRIVER_MODULE(fhc, central, fhc_driver, fhc_devclass, 0, 0, BUS_PASS_BUS); MODULE_DEPEND(fhc, central, 1, 1, 1); EARLY_DRIVER_MODULE(fhc, nexus, fhc_driver, fhc_devclass, 0, 0, BUS_PASS_BUS); MODULE_DEPEND(fhc, nexus, 1, 1, 1); MODULE_VERSION(fhc, 1); static const struct intr_controller fhc_ic = { fhc_intr_enable, fhc_intr_disable, fhc_intr_assign, fhc_intr_clear }; struct fhc_icarg { struct fhc_softc *fica_sc; struct resource *fica_memres; }; static int fhc_probe(device_t dev) { if (strcmp(ofw_bus_get_name(dev), "fhc") == 0) { device_set_desc(dev, "fhc"); return (0); } return (ENXIO); } static int fhc_attach(device_t dev) { char ledname[sizeof("boardXX")]; struct fhc_devinfo *fdi; struct fhc_icarg *fica; struct fhc_softc *sc; struct sbus_regs *reg; phandle_t child; phandle_t node; device_t cdev; uint32_t board; uint32_t ctrl; uint32_t *intr; uint32_t iv; char *name; int central; int error; int i; int j; sc = device_get_softc(dev); node = ofw_bus_get_node(dev); central = 0; if (strcmp(device_get_name(device_get_parent(dev)), "central") == 0) central = 1; for (i = 0; i < FHC_NREG; i++) { j = i; sc->sc_memres[i] = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &j, RF_ACTIVE); if (sc->sc_memres[i] == NULL) { device_printf(dev, "cannot allocate resource %d\n", i); error = ENXIO; goto fail_memres; } } if (central != 0) { board = bus_read_4(sc->sc_memres[FHC_INTERNAL], FHC_BSR); board = ((board >> 16) & 0x1) | ((board >> 12) & 0xe); } else { if (OF_getprop(node, "board#", &board, sizeof(board)) == -1) { device_printf(dev, "cannot get board number\n"); error = ENXIO; goto fail_memres; } } device_printf(dev, "board %d, ", board); - if (OF_getprop_alloc(node, "board-model", 1, (void **)&name) != -1) { + if (OF_getprop_alloc(node, "board-model", (void **)&name) != -1) { printf("model %s\n", name); OF_prop_free(name); } else printf("model unknown\n"); for (i = FHC_FANFAIL; i <= FHC_TOD; i++) { bus_write_4(sc->sc_memres[i], FHC_ICLR, INTCLR_IDLE); (void)bus_read_4(sc->sc_memres[i], FHC_ICLR); } sc->sc_ign = board << 1; bus_write_4(sc->sc_memres[FHC_IGN], 0x0, sc->sc_ign); sc->sc_ign = bus_read_4(sc->sc_memres[FHC_IGN], 0x0); ctrl = bus_read_4(sc->sc_memres[FHC_INTERNAL], FHC_CTRL); if (central == 0) ctrl |= FHC_CTRL_IXIST; ctrl &= ~(FHC_CTRL_AOFF | FHC_CTRL_BOFF | FHC_CTRL_SLINE); bus_write_4(sc->sc_memres[FHC_INTERNAL], FHC_CTRL, ctrl); (void)bus_read_4(sc->sc_memres[FHC_INTERNAL], FHC_CTRL); - sc->sc_nrange = OF_getprop_alloc(node, "ranges", + sc->sc_nrange = OF_getprop_alloc_multi(node, "ranges", sizeof(*sc->sc_ranges), (void **)&sc->sc_ranges); if (sc->sc_nrange == -1) { device_printf(dev, "cannot get ranges\n"); error = ENXIO; goto fail_memres; } /* * Apparently only the interrupt controller of boards hanging off * of central(4) is indented to be used, otherwise we would have * conflicts registering the interrupt controllers for all FHC * boards as the board number and thus the IGN isn't unique. */ if (central == 1) { /* * Hunt through all the interrupt mapping regs and register * our interrupt controller for the corresponding interrupt * vectors. We do this early in order to be able to catch * stray interrupts. */ for (i = FHC_FANFAIL; i <= FHC_TOD; i++) { fica = malloc(sizeof(*fica), M_DEVBUF, M_NOWAIT); if (fica == NULL) panic("%s: could not allocate interrupt " "controller argument", __func__); fica->fica_sc = sc; fica->fica_memres = sc->sc_memres[i]; #ifdef FHC_DEBUG device_printf(dev, "intr map %d: %#lx, clr: %#lx\n", i, (u_long)bus_read_4(fica->fica_memres, FHC_IMAP), (u_long)bus_read_4(fica->fica_memres, FHC_ICLR)); #endif /* * XXX we only pick the INO rather than the INR * from the IMR since the firmware may not provide * the IGN and the IGN is constant for all devices * on that FireHose controller. */ j = intr_controller_register(INTMAP_VEC(sc->sc_ign, INTINO(bus_read_4(fica->fica_memres, FHC_IMAP))), &fhc_ic, fica); if (j != 0) device_printf(dev, "could not register " "interrupt controller for map %d (%d)\n", i, j); } } else { snprintf(ledname, sizeof(ledname), "board%d", board); sc->sc_led_dev = led_create(fhc_led_func, sc, ledname); } for (child = OF_child(node); child != 0; child = OF_peer(child)) { fdi = malloc(sizeof(*fdi), M_DEVBUF, M_WAITOK | M_ZERO); if (ofw_bus_gen_setup_devinfo(&fdi->fdi_obdinfo, child) != 0) { free(fdi, M_DEVBUF); continue; } - i = OF_getprop_alloc(child, "reg", sizeof(*reg), + i = OF_getprop_alloc_multi(child, "reg", sizeof(*reg), (void **)®); if (i == -1) { device_printf(dev, "<%s>: incomplete\n", fdi->fdi_obdinfo.obd_name); ofw_bus_gen_destroy_devinfo(&fdi->fdi_obdinfo); free(fdi, M_DEVBUF); continue; } resource_list_init(&fdi->fdi_rl); for (j = 0; j < i; j++) resource_list_add(&fdi->fdi_rl, SYS_RES_MEMORY, j, reg[j].sbr_offset, reg[j].sbr_offset + reg[j].sbr_size, reg[j].sbr_size); OF_prop_free(reg); if (central == 1) { - i = OF_getprop_alloc(child, "interrupts", + i = OF_getprop_alloc_multi(child, "interrupts", sizeof(*intr), (void **)&intr); if (i != -1) { for (j = 0; j < i; j++) { iv = INTMAP_VEC(sc->sc_ign, intr[j]); resource_list_add(&fdi->fdi_rl, SYS_RES_IRQ, j, iv, iv, 1); } OF_prop_free(intr); } } cdev = device_add_child(dev, NULL, -1); if (cdev == NULL) { device_printf(dev, "<%s>: device_add_child failed\n", fdi->fdi_obdinfo.obd_name); resource_list_free(&fdi->fdi_rl); ofw_bus_gen_destroy_devinfo(&fdi->fdi_obdinfo); free(fdi, M_DEVBUF); continue; } device_set_ivars(cdev, fdi); } return (bus_generic_attach(dev)); fail_memres: for (i = 0; i < FHC_NREG; i++) if (sc->sc_memres[i] != NULL) bus_release_resource(dev, SYS_RES_MEMORY, rman_get_rid(sc->sc_memres[i]), sc->sc_memres[i]); return (error); } static int fhc_print_child(device_t dev, device_t child) { int rv; rv = bus_print_child_header(dev, child); rv += fhc_print_res(device_get_ivars(child)); rv += bus_print_child_footer(dev, child); return (rv); } static void fhc_probe_nomatch(device_t dev, device_t child) { const char *type; device_printf(dev, "<%s>", ofw_bus_get_name(child)); fhc_print_res(device_get_ivars(child)); type = ofw_bus_get_type(child); printf(" type %s (no driver attached)\n", type != NULL ? type : "unknown"); } static void fhc_intr_enable(void *arg) { struct intr_vector *iv = arg; struct fhc_icarg *fica = iv->iv_icarg; bus_write_4(fica->fica_memres, FHC_IMAP, INTMAP_ENABLE(iv->iv_vec, iv->iv_mid)); (void)bus_read_4(fica->fica_memres, FHC_IMAP); } static void fhc_intr_disable(void *arg) { struct intr_vector *iv = arg; struct fhc_icarg *fica = iv->iv_icarg; bus_write_4(fica->fica_memres, FHC_IMAP, iv->iv_vec); (void)bus_read_4(fica->fica_memres, FHC_IMAP); } static void fhc_intr_assign(void *arg) { struct intr_vector *iv = arg; struct fhc_icarg *fica = iv->iv_icarg; bus_write_4(fica->fica_memres, FHC_IMAP, INTMAP_TID( bus_read_4(fica->fica_memres, FHC_IMAP), iv->iv_mid)); (void)bus_read_4(fica->fica_memres, FHC_IMAP); } static void fhc_intr_clear(void *arg) { struct intr_vector *iv = arg; struct fhc_icarg *fica = iv->iv_icarg; bus_write_4(fica->fica_memres, FHC_ICLR, INTCLR_IDLE); (void)bus_read_4(fica->fica_memres, FHC_ICLR); } static int fhc_setup_intr(device_t bus, device_t child, struct resource *r, int flags, driver_filter_t *filt, driver_intr_t *func, void *arg, void **cookiep) { struct fhc_softc *sc; u_long vec; sc = device_get_softc(bus); /* * Make sure the vector is fully specified and we registered * our interrupt controller for it. */ vec = rman_get_start(r); if (INTIGN(vec) != sc->sc_ign || intr_vectors[vec].iv_ic != &fhc_ic) { device_printf(bus, "invalid interrupt vector 0x%lx\n", vec); return (EINVAL); } return (bus_generic_setup_intr(bus, child, r, flags, filt, func, arg, cookiep)); } static struct resource * fhc_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 resource_list *rl; struct resource_list_entry *rle; struct fhc_softc *sc; struct resource *res; bus_addr_t coffset; bus_addr_t cend; bus_addr_t phys; int isdefault; int passthrough; int i; isdefault = RMAN_IS_DEFAULT_RANGE(start, end); passthrough = (device_get_parent(child) != bus); res = NULL; rle = NULL; rl = BUS_GET_RESOURCE_LIST(bus, child); sc = device_get_softc(bus); switch (type) { case SYS_RES_IRQ: return (resource_list_alloc(rl, bus, child, type, rid, start, end, count, flags)); case SYS_RES_MEMORY: if (!passthrough) { rle = resource_list_find(rl, type, *rid); if (rle == NULL) return (NULL); if (rle->res != NULL) panic("%s: resource entry is busy", __func__); if (isdefault) { start = rle->start; count = ulmax(count, rle->count); end = ulmax(rle->end, start + count - 1); } } for (i = 0; i < sc->sc_nrange; i++) { coffset = sc->sc_ranges[i].coffset; cend = coffset + sc->sc_ranges[i].size - 1; if (start >= coffset && end <= cend) { start -= coffset; end -= coffset; phys = sc->sc_ranges[i].poffset | ((bus_addr_t)sc->sc_ranges[i].pspace << 32); res = bus_generic_alloc_resource(bus, child, type, rid, phys + start, phys + end, count, flags); if (!passthrough) rle->res = res; break; } } break; } return (res); } static int fhc_adjust_resource(device_t bus __unused, device_t child __unused, int type __unused, struct resource *r __unused, rman_res_t start __unused, rman_res_t end __unused) { return (ENXIO); } static struct resource_list * fhc_get_resource_list(device_t bus, device_t child) { struct fhc_devinfo *fdi; fdi = device_get_ivars(child); return (&fdi->fdi_rl); } static const struct ofw_bus_devinfo * fhc_get_devinfo(device_t bus, device_t child) { struct fhc_devinfo *fdi; fdi = device_get_ivars(child); return (&fdi->fdi_obdinfo); } static void fhc_led_func(void *arg, int onoff) { struct fhc_softc *sc; uint32_t ctrl; sc = (struct fhc_softc *)arg; ctrl = bus_read_4(sc->sc_memres[FHC_INTERNAL], FHC_CTRL); if (onoff) ctrl |= FHC_CTRL_RLED; else ctrl &= ~FHC_CTRL_RLED; ctrl &= ~(FHC_CTRL_AOFF | FHC_CTRL_BOFF | FHC_CTRL_SLINE); bus_write_4(sc->sc_memres[FHC_INTERNAL], FHC_CTRL, ctrl); (void)bus_read_4(sc->sc_memres[FHC_INTERNAL], FHC_CTRL); } static int fhc_print_res(struct fhc_devinfo *fdi) { int rv; rv = 0; rv += resource_list_print_type(&fdi->fdi_rl, "mem", SYS_RES_MEMORY, "%#jx"); rv += resource_list_print_type(&fdi->fdi_rl, "irq", SYS_RES_IRQ, "%jd"); return (rv); } Index: head/sys/sparc64/isa/isa.c =================================================================== --- head/sys/sparc64/isa/isa.c (revision 332309) +++ head/sys/sparc64/isa/isa.c (revision 332310) @@ -1,363 +1,363 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 1998 Doug Rabson * Copyright (c) 2001 Thomas Moestl * 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. * * from: FreeBSD: src/sys/alpha/isa/isa.c,v 1.26 2001/07/11 */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* There can be only one ISA bus, so it is safe to use globals. */ static u_int64_t isa_io_base; static u_int64_t isa_io_limit; static u_int64_t isa_mem_base; static u_int64_t isa_mem_limit; device_t isa_bus_device; static phandle_t isab_node; static struct isa_ranges *isab_ranges; static int isab_nrange; static struct ofw_bus_iinfo isa_iinfo; /* * XXX: This is really partly PCI-specific, but unfortunately is * differently enough to have to duplicate it here... */ #define ISAB_RANGE_PHYS(r) \ (((u_int64_t)(r)->phys_mid << 32) | (u_int64_t)(r)->phys_lo) #define ISAB_RANGE_SPACE(r) (((r)->phys_hi >> 24) & 0x03) #define ISAR_SPACE_IO 0x01 #define ISAR_SPACE_MEM 0x02 #define INRANGE(x, start, end) ((x) >= (start) && (x) <= (end)) static void isa_setup_children(device_t, phandle_t); void isa_init(device_t dev) { device_t bridge; int i; /* The parent of the bus must be a PCI-ISA bridge. */ bridge = device_get_parent(dev); isab_node = ofw_bus_get_node(bridge); - isab_nrange = OF_getprop_alloc(isab_node, "ranges", + isab_nrange = OF_getprop_alloc_multi(isab_node, "ranges", sizeof(*isab_ranges), (void **)&isab_ranges); if (isab_nrange <= 0) panic("isa_init: cannot get bridge range property"); ofw_bus_setup_iinfo(isab_node, &isa_iinfo, sizeof(ofw_isa_intr_t)); isa_setup_children(dev, isab_node); for (i = isab_nrange - 1; i >= 0; i--) { switch(ISAB_RANGE_SPACE(&isab_ranges[i])) { case ISAR_SPACE_IO: /* This is probably always 0. */ isa_io_base = ISAB_RANGE_PHYS(&isab_ranges[i]); isa_io_limit = isab_ranges[i].size; break; case ISAR_SPACE_MEM: /* This is probably always 0. */ isa_mem_base = ISAB_RANGE_PHYS(&isab_ranges[i]); isa_mem_limit = isab_ranges[i].size; break; } } } static const struct { const char *const name; uint32_t id; } ofw_isa_pnp_map[] = { { "SUNW,lomh", 0x0000ae4e }, /* SUN0000 */ { "dma", 0x0002d041 }, /* PNP0200 */ { "floppy", 0x0007d041 }, /* PNP0700 */ { "rtc", 0x000bd041 }, /* PNP0B00 */ { "flashprom", 0x0100ae4e }, /* SUN0001 */ { "parallel", 0x0104d041 }, /* PNP0401 */ { "serial", 0x0105d041 }, /* PNP0501 */ { "su", 0x0105d041 }, /* PNP0501 */ { "i2c", 0x0200ae4e }, /* SUN0002 */ { "rmc-comm", 0x0300ae4e }, /* SUN0003 */ { "kb_ps2", 0x0303d041 }, /* PNP0303 */ { "kdmouse", 0x030fd041 }, /* PNP0F03 */ { "bscbus", 0x0400ae4e }, /* SUN0004 */ { "power", 0x0c0cd041 }, /* PNP0C0C */ { NULL, 0x0 } }; static void isa_setup_children(device_t dev, phandle_t parent) { struct isa_regs *regs; struct resource_list *rl; device_t cdev; u_int64_t end, start; ofw_isa_intr_t *intrs, rintr; phandle_t node; uint32_t *drqs, *regidx; int i, ndrq, nintr, nreg, nregidx, rid, rtype; char *name; /* * Loop through children and fake up PnP devices for them. * Their resources are added as fully mapped and specified because * adjusting the resources and the resource list entries respectively * in isa_alloc_resource() causes trouble with drivers which use * rman_get_start(), pass-through or allocate and release resources * multiple times, etc. Adjusting the resources might be better off * in a bus_activate_resource method but the common ISA code doesn't * allow for an isa_activate_resource(). */ for (node = OF_child(parent); node != 0; node = OF_peer(node)) { - if ((OF_getprop_alloc(node, "name", 1, (void **)&name)) == -1) + if ((OF_getprop_alloc(node, "name", (void **)&name)) == -1) continue; /* * Keyboard and mouse controllers hang off of the `8042' * node but we have no real use for the `8042' itself. */ if (strcmp(name, "8042") == 0) { isa_setup_children(dev, node); OF_prop_free(name); continue; } for (i = 0; ofw_isa_pnp_map[i].name != NULL; i++) if (strcmp(ofw_isa_pnp_map[i].name, name) == 0) break; if (ofw_isa_pnp_map[i].name == NULL) { device_printf(dev, "no PnP map entry for node " "0x%lx: %s\n", (unsigned long)node, name); OF_prop_free(name); continue; } if ((cdev = BUS_ADD_CHILD(dev, ISA_ORDER_PNPBIOS, NULL, -1)) == NULL) panic("isa_setup_children: BUS_ADD_CHILD failed"); isa_set_logicalid(cdev, ofw_isa_pnp_map[i].id); isa_set_vendorid(cdev, ofw_isa_pnp_map[i].id); rl = BUS_GET_RESOURCE_LIST(dev, cdev); - nreg = OF_getprop_alloc(node, "reg", sizeof(*regs), + nreg = OF_getprop_alloc_multi(node, "reg", sizeof(*regs), (void **)®s); for (i = 0; i < nreg; i++) { start = ISA_REG_PHYS(®s[i]); end = start + regs[i].size - 1; rtype = ofw_isa_range_map(isab_ranges, isab_nrange, &start, &end, NULL); rid = 0; while (resource_list_find(rl, rtype, rid) != NULL) rid++; bus_set_resource(cdev, rtype, rid, start, end - start + 1); } if (nreg == -1 && parent != isab_node) { /* * The "reg" property still might be an index into * the set of registers of the parent device like * with the nodes hanging off of the `8042' node. */ - nregidx = OF_getprop_alloc(node, "reg", sizeof(*regidx), + nregidx = OF_getprop_alloc_multi(node, "reg", sizeof(*regidx), (void **)®idx); if (nregidx > 2) panic("isa_setup_children: impossible number " "of register indices"); - if (nregidx != -1 && (nreg = OF_getprop_alloc(parent, + if (nregidx != -1 && (nreg = OF_getprop_alloc_multi(parent, "reg", sizeof(*regs), (void **)®s)) >= nregidx) { for (i = 0; i < nregidx; i++) { start = ISA_REG_PHYS(®s[regidx[i]]); end = start + regs[regidx[i]].size - 1; rtype = ofw_isa_range_map(isab_ranges, isab_nrange, &start, &end, NULL); rid = 0; while (resource_list_find(rl, rtype, rid) != NULL) rid++; bus_set_resource(cdev, rtype, rid, start, end - start + 1); } } if (regidx != NULL) OF_prop_free(regidx); } if (regs != NULL) OF_prop_free(regs); - nintr = OF_getprop_alloc(node, "interrupts", sizeof(*intrs), + nintr = OF_getprop_alloc_multi(node, "interrupts", sizeof(*intrs), (void **)&intrs); for (i = 0; i < nintr; i++) { if (intrs[i] > 7) panic("isa_setup_children: intr too large"); rintr = ofw_isa_route_intr(device_get_parent(dev), node, &isa_iinfo, intrs[i]); if (rintr == PCI_INVALID_IRQ) { device_printf(dev, "could not map ISA " "interrupt %d for node 0x%lx: %s\n", intrs[i], (unsigned long)node, name); continue; } bus_set_resource(cdev, SYS_RES_IRQ, i, rintr, 1); } if (intrs != NULL) OF_prop_free(intrs); - ndrq = OF_getprop_alloc(node, "dma-channel", sizeof(*drqs), + ndrq = OF_getprop_alloc_multi(node, "dma-channel", sizeof(*drqs), (void **)&drqs); for (i = 0; i < ndrq; i++) bus_set_resource(cdev, SYS_RES_DRQ, i, drqs[i], 1); if (drqs != NULL) OF_prop_free(drqs); /* * Devices using DMA hang off of the `dma' node instead of * directly from the ISA bridge node. */ if (strcmp(name, "dma") == 0) isa_setup_children(dev, node); OF_prop_free(name); } } struct resource * isa_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) { /* * Consider adding a resource definition. */ int passthrough = (device_get_parent(child) != bus); int isdefault = RMAN_IS_DEFAULT_RANGE(start, end); struct resource_list *rl; struct resource_list_entry *rle; u_long base, limit; rl = BUS_GET_RESOURCE_LIST(bus, child); if (!passthrough && !isdefault) { rle = resource_list_find(rl, type, *rid); if (!rle) { if (*rid < 0) return (NULL); switch (type) { case SYS_RES_IRQ: if (*rid >= ISA_NIRQ) return (NULL); break; case SYS_RES_DRQ: if (*rid >= ISA_NDRQ) return (NULL); break; case SYS_RES_MEMORY: if (*rid >= ISA_NMEM) return (NULL); break; case SYS_RES_IOPORT: if (*rid >= ISA_NPORT) return (NULL); break; default: return (NULL); } resource_list_add(rl, type, *rid, start, end, count); } } /* * Sanity check if the resource in the respective entry is fully * mapped and specified and its type allocable. A driver could * have added an out of range resource on its own. */ if (!passthrough) { if ((rle = resource_list_find(rl, type, *rid)) == NULL) return (NULL); base = limit = 0; switch (type) { case SYS_RES_MEMORY: base = isa_mem_base; limit = base + isa_mem_limit; break; case SYS_RES_IOPORT: base = isa_io_base; limit = base + isa_io_limit; break; case SYS_RES_IRQ: if (rle->start != rle->end || rle->start <= 7) return (NULL); break; case SYS_RES_DRQ: break; default: return (NULL); } if (type == SYS_RES_MEMORY || type == SYS_RES_IOPORT) { if (!INRANGE(rle->start, base, limit) || !INRANGE(rle->end, base, limit)) return (NULL); } } return (resource_list_alloc(rl, bus, child, type, rid, start, end, count, flags)); } int isa_release_resource(device_t bus, device_t child, int type, int rid, struct resource *res) { return (bus_generic_rl_release_resource(bus, child, type, rid, res)); } Index: head/sys/sparc64/pci/ofw_pci.c =================================================================== --- head/sys/sparc64/pci/ofw_pci.c (revision 332309) +++ head/sys/sparc64/pci/ofw_pci.c (revision 332310) @@ -1,406 +1,406 @@ /*- * Copyright (c) 1999, 2000 Matthew R. Green * Copyright (c) 2001 - 2003 by Thomas Moestl * Copyright (c) 2005 - 2015 by Marius Strobl * 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. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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. * * from: NetBSD: psycho.c,v 1.35 2001/09/10 16:17:06 eeh Exp */ #include __FBSDID("$FreeBSD$"); #include "opt_ofw_pci.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include int ofw_pci_attach_common(device_t dev, bus_dma_tag_t dmat, u_long iosize, u_long memsize) { struct ofw_pci_softc *sc; struct ofw_pci_ranges *range; phandle_t node; uint32_t prop_array[2]; u_int i, j, nrange; sc = device_get_softc(dev); node = ofw_bus_get_node(dev); sc->sc_node = node; sc->sc_pci_dmat = dmat; /* Initialize memory and I/O rmans. */ sc->sc_pci_io_rman.rm_type = RMAN_ARRAY; sc->sc_pci_io_rman.rm_descr = "PCI I/O Ports"; if (rman_init(&sc->sc_pci_io_rman) != 0 || rman_manage_region(&sc->sc_pci_io_rman, 0, iosize) != 0) { device_printf(dev, "failed to set up I/O rman\n"); return (ENXIO); } sc->sc_pci_mem_rman.rm_type = RMAN_ARRAY; sc->sc_pci_mem_rman.rm_descr = "PCI Memory"; if (rman_init(&sc->sc_pci_mem_rman) != 0 || rman_manage_region(&sc->sc_pci_mem_rman, 0, memsize) != 0) { device_printf(dev, "failed to set up memory rman\n"); return (ENXIO); } /* * Find the addresses of the various bus spaces. The physical * start addresses of the ranges are the configuration, I/O and * memory handles. There should not be multiple ones of one kind. */ - nrange = OF_getprop_alloc(node, "ranges", sizeof(*range), + nrange = OF_getprop_alloc_multi(node, "ranges", sizeof(*range), (void **)&range); for (i = 0; i < nrange; i++) { j = OFW_PCI_RANGE_CS(&range[i]); if (sc->sc_pci_bh[j] != 0) { device_printf(dev, "duplicate range for space %d\n", j); OF_prop_free(range); return (EINVAL); } sc->sc_pci_bh[j] = OFW_PCI_RANGE_PHYS(&range[i]); } OF_prop_free(range); /* * Make sure that the expected ranges are actually present. * The OFW_PCI_CS_MEM64 one is not currently used. */ if (sc->sc_pci_bh[OFW_PCI_CS_CONFIG] == 0) { device_printf(dev, "missing CONFIG range\n"); return (ENXIO); } if (sc->sc_pci_bh[OFW_PCI_CS_IO] == 0) { device_printf(dev, "missing IO range\n"); return (ENXIO); } if (sc->sc_pci_bh[OFW_PCI_CS_MEM32] == 0) { device_printf(dev, "missing MEM32 range\n"); return (ENXIO); } /* Allocate our tags. */ sc->sc_pci_iot = sparc64_alloc_bus_tag(NULL, PCI_IO_BUS_SPACE); if (sc->sc_pci_iot == NULL) { device_printf(dev, "could not allocate PCI I/O tag\n"); return (ENXIO); } sc->sc_pci_cfgt = sparc64_alloc_bus_tag(NULL, PCI_CONFIG_BUS_SPACE); if (sc->sc_pci_cfgt == NULL) { device_printf(dev, "could not allocate PCI configuration space tag\n"); return (ENXIO); } /* * Get the bus range from the firmware. */ i = OF_getprop(node, "bus-range", (void *)prop_array, sizeof(prop_array)); if (i == -1) { device_printf(dev, "could not get bus-range\n"); return (ENXIO); } if (i != sizeof(prop_array)) { device_printf(dev, "broken bus-range (%d)", i); return (EINVAL); } sc->sc_pci_secbus = prop_array[0]; sc->sc_pci_subbus = prop_array[1]; if (bootverbose != 0) device_printf(dev, "bus range %u to %u; PCI bus %d\n", sc->sc_pci_secbus, sc->sc_pci_subbus, sc->sc_pci_secbus); ofw_bus_setup_iinfo(node, &sc->sc_pci_iinfo, sizeof(ofw_pci_intr_t)); return (0); } uint32_t ofw_pci_read_config_common(device_t dev, u_int regmax, u_long offset, u_int bus, u_int slot, u_int func, u_int reg, int width) { struct ofw_pci_softc *sc; bus_space_handle_t bh; uint32_t r, wrd; int i; uint16_t shrt; uint8_t byte; sc = device_get_softc(dev); if (bus < sc->sc_pci_secbus || bus > sc->sc_pci_subbus || slot > PCI_SLOTMAX || func > PCI_FUNCMAX || reg > regmax) return (-1); bh = sc->sc_pci_bh[OFW_PCI_CS_CONFIG]; switch (width) { case 1: i = bus_space_peek_1(sc->sc_pci_cfgt, bh, offset, &byte); r = byte; break; case 2: i = bus_space_peek_2(sc->sc_pci_cfgt, bh, offset, &shrt); r = shrt; break; case 4: i = bus_space_peek_4(sc->sc_pci_cfgt, bh, offset, &wrd); r = wrd; break; default: panic("%s: bad width %d", __func__, width); /* NOTREACHED */ } if (i) { #ifdef OFW_PCI_DEBUG printf("%s: read data error reading: %d.%d.%d: 0x%x\n", __func__, bus, slot, func, reg); #endif r = -1; } return (r); } void ofw_pci_write_config_common(device_t dev, u_int regmax, u_long offset, u_int bus, u_int slot, u_int func, u_int reg, uint32_t val, int width) { struct ofw_pci_softc *sc; bus_space_handle_t bh; sc = device_get_softc(dev); if (bus < sc->sc_pci_secbus || bus > sc->sc_pci_subbus || slot > PCI_SLOTMAX || func > PCI_FUNCMAX || reg > regmax) return; bh = sc->sc_pci_bh[OFW_PCI_CS_CONFIG]; switch (width) { case 1: bus_space_write_1(sc->sc_pci_cfgt, bh, offset, val); break; case 2: bus_space_write_2(sc->sc_pci_cfgt, bh, offset, val); break; case 4: bus_space_write_4(sc->sc_pci_cfgt, bh, offset, val); break; default: panic("%s: bad width %d", __func__, width); /* NOTREACHED */ } } ofw_pci_intr_t ofw_pci_route_interrupt_common(device_t bridge, device_t dev, int pin) { struct ofw_pci_softc *sc; struct ofw_pci_register reg; ofw_pci_intr_t pintr, mintr; sc = device_get_softc(bridge); pintr = pin; if (ofw_bus_lookup_imap(ofw_bus_get_node(dev), &sc->sc_pci_iinfo, ®, sizeof(reg), &pintr, sizeof(pintr), &mintr, sizeof(mintr), NULL) != 0) return (mintr); return (PCI_INVALID_IRQ); } void ofw_pci_dmamap_sync_stst_order_common(void) { static u_char buf[VIS_BLOCKSIZE] __aligned(VIS_BLOCKSIZE); register_t reg, s; s = intr_disable(); reg = rd(fprs); wr(fprs, reg | FPRS_FEF, 0); __asm __volatile("stda %%f0, [%0] %1" : : "r" (buf), "n" (ASI_BLK_COMMIT_S)); membar(Sync); wr(fprs, reg, 0); intr_restore(s); } int ofw_pci_read_ivar(device_t dev, device_t child __unused, int which, uintptr_t *result) { struct ofw_pci_softc *sc; switch (which) { case PCIB_IVAR_DOMAIN: *result = device_get_unit(dev); return (0); case PCIB_IVAR_BUS: sc = device_get_softc(dev); *result = sc->sc_pci_secbus; return (0); } return (ENOENT); } struct resource * ofw_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 ofw_pci_softc *sc; struct resource *rv; struct rman *rm; sc = device_get_softc(bus); switch (type) { case SYS_RES_IRQ: /* * XXX: Don't accept blank ranges for now, only single * interrupts. The other case should not happen with * the MI PCI code ... * XXX: This may return a resource that is out of the * range that was specified. Is this correct ...? */ if (start != end) panic("%s: XXX: interrupt range", __func__); return (bus_generic_alloc_resource(bus, child, type, rid, start, end, count, flags)); case SYS_RES_MEMORY: rm = &sc->sc_pci_mem_rman; break; case SYS_RES_IOPORT: rm = &sc->sc_pci_io_rman; break; default: return (NULL); } rv = rman_reserve_resource(rm, start, end, count, flags & ~RF_ACTIVE, child); if (rv == NULL) return (NULL); rman_set_rid(rv, *rid); if ((flags & RF_ACTIVE) != 0 && bus_activate_resource(child, type, *rid, rv) != 0) { rman_release_resource(rv); return (NULL); } return (rv); } int ofw_pci_activate_resource(device_t bus, device_t child, int type, int rid, struct resource *r) { struct ofw_pci_softc *sc; struct bus_space_tag *tag; sc = device_get_softc(bus); switch (type) { case SYS_RES_IRQ: return (bus_generic_activate_resource(bus, child, type, rid, r)); case SYS_RES_MEMORY: tag = sparc64_alloc_bus_tag(r, PCI_MEMORY_BUS_SPACE); if (tag == NULL) return (ENOMEM); rman_set_bustag(r, tag); rman_set_bushandle(r, sc->sc_pci_bh[OFW_PCI_CS_MEM32] + rman_get_start(r)); break; case SYS_RES_IOPORT: rman_set_bustag(r, sc->sc_pci_iot); rman_set_bushandle(r, sc->sc_pci_bh[OFW_PCI_CS_IO] + rman_get_start(r)); break; } return (rman_activate_resource(r)); } int ofw_pci_adjust_resource(device_t bus, device_t child, int type, struct resource *r, rman_res_t start, rman_res_t end) { struct ofw_pci_softc *sc; struct rman *rm; sc = device_get_softc(bus); switch (type) { case SYS_RES_IRQ: return (bus_generic_adjust_resource(bus, child, type, r, start, end)); case SYS_RES_MEMORY: rm = &sc->sc_pci_mem_rman; break; case SYS_RES_IOPORT: rm = &sc->sc_pci_io_rman; break; default: return (EINVAL); } if (rman_is_region_manager(r, rm) == 0) return (EINVAL); return (rman_adjust_resource(r, start, end)); } bus_dma_tag_t ofw_pci_get_dma_tag(device_t bus, device_t child __unused) { struct ofw_pci_softc *sc; sc = device_get_softc(bus); return (sc->sc_pci_dmat); } phandle_t ofw_pci_get_node(device_t bus, device_t child __unused) { struct ofw_pci_softc *sc; sc = device_get_softc(bus); /* We only have one child, the PCI bus, which needs our own node. */ return (sc->sc_node); } Index: head/sys/sparc64/sbus/dma_sbus.c =================================================================== --- head/sys/sparc64/sbus/dma_sbus.c (revision 332309) +++ head/sys/sparc64/sbus/dma_sbus.c (revision 332310) @@ -1,417 +1,417 @@ /* $OpenBSD: dma_sbus.c,v 1.16 2008/06/26 05:42:18 ray Exp $ */ /* $NetBSD: dma_sbus.c,v 1.32 2008/04/28 20:23:57 martin Exp $ */ /*- * SPDX-License-Identifier: BSD-2-Clause-NetBSD AND BSD-2-Clause-FreeBSD * * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Paul Kranenburg. * * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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. */ /*- * Copyright (c) 1994 Peter Galbavy. All rights reserved. * Copyright (c) 2005 Marius Strobl . 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 ``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 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$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct dma_devinfo { struct ofw_bus_devinfo ddi_obdinfo; struct resource_list ddi_rl; }; struct dma_softc { struct lsi64854_softc sc_lsi64854; /* base device */ int sc_ign; int sc_slot; }; static devclass_t dma_devclass; static device_probe_t dma_probe; static device_attach_t dma_attach; static bus_print_child_t dma_print_child; static bus_probe_nomatch_t dma_probe_nomatch; static bus_get_resource_list_t dma_get_resource_list; static ofw_bus_get_devinfo_t dma_get_devinfo; static struct dma_devinfo *dma_setup_dinfo(device_t, struct dma_softc *, phandle_t); static void dma_destroy_dinfo(struct dma_devinfo *); static int dma_print_res(struct dma_devinfo *); static device_method_t dma_methods[] = { /* Device interface */ DEVMETHOD(device_probe, dma_probe), DEVMETHOD(device_attach, dma_attach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* Bus interface */ DEVMETHOD(bus_print_child, dma_print_child), DEVMETHOD(bus_probe_nomatch, dma_probe_nomatch), DEVMETHOD(bus_alloc_resource, bus_generic_rl_alloc_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_adjust_resource, bus_generic_adjust_resource), DEVMETHOD(bus_release_resource, bus_generic_rl_release_resource), DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), DEVMETHOD(bus_get_resource_list, dma_get_resource_list), DEVMETHOD(bus_child_pnpinfo_str, ofw_bus_gen_child_pnpinfo_str), /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_devinfo, dma_get_devinfo), DEVMETHOD(ofw_bus_get_compat, ofw_bus_gen_get_compat), DEVMETHOD(ofw_bus_get_model, ofw_bus_gen_get_model), DEVMETHOD(ofw_bus_get_name, ofw_bus_gen_get_name), DEVMETHOD(ofw_bus_get_node, ofw_bus_gen_get_node), DEVMETHOD(ofw_bus_get_type, ofw_bus_gen_get_type), DEVMETHOD_END }; static driver_t dma_driver = { "dma", dma_methods, sizeof(struct dma_softc), }; /* * The probe order is handled by sbus(4) as we don't want the variants * with children to be attached earlier than the stand-alone controllers * in order to generally preserve the OFW device tree order. */ EARLY_DRIVER_MODULE(dma, sbus, dma_driver, dma_devclass, 0, 0, BUS_PASS_DEFAULT); MODULE_DEPEND(dma, sbus, 1, 1, 1); MODULE_VERSION(dma, 1); static int dma_probe(device_t dev) { const char *name; name = ofw_bus_get_name(dev); if (strcmp(name, "espdma") == 0 || strcmp(name, "dma") == 0 || strcmp(name, "ledma") == 0) { device_set_desc_copy(dev, name); return (0); } return (ENXIO); } static int dma_attach(device_t dev) { struct dma_softc *dsc; struct lsi64854_softc *lsc; struct dma_devinfo *ddi; device_t cdev; const char *name; char *cabletype; uint32_t csr; phandle_t child, node; int error, i; dsc = device_get_softc(dev); lsc = &dsc->sc_lsi64854; name = ofw_bus_get_name(dev); node = ofw_bus_get_node(dev); dsc->sc_ign = sbus_get_ign(dev); dsc->sc_slot = sbus_get_slot(dev); i = 0; lsc->sc_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &i, RF_ACTIVE); if (lsc->sc_res == NULL) { device_printf(dev, "cannot allocate resources\n"); return (ENXIO); } if (strcmp(name, "espdma") == 0 || strcmp(name, "dma") == 0) lsc->sc_channel = L64854_CHANNEL_SCSI; else if (strcmp(name, "ledma") == 0) { /* * Check to see which cable type is currently active and * set the appropriate bit in the ledma csr so that it * gets used. If we didn't netboot, the PROM won't have * the "cable-selection" property; default to TP and then * the user can change it via a "media" option to ifconfig. */ csr = L64854_GCSR(lsc); - if ((OF_getprop_alloc(node, "cable-selection", 1, + if ((OF_getprop_alloc(node, "cable-selection", (void **)&cabletype)) == -1) { /* assume TP if nothing there */ csr |= E_TP_AUI; } else { if (strcmp(cabletype, "aui") == 0) csr &= ~E_TP_AUI; else csr |= E_TP_AUI; OF_prop_free(cabletype); } L64854_SCSR(lsc, csr); DELAY(20000); /* manual says we need a 20ms delay */ lsc->sc_channel = L64854_CHANNEL_ENET; } else { device_printf(dev, "unsupported DMA channel\n"); error = ENXIO; goto fail_lres; } error = bus_dma_tag_create( bus_get_dma_tag(dev), /* parent */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ BUS_SPACE_MAXSIZE, /* maxsize */ BUS_SPACE_UNRESTRICTED, /* nsegments */ BUS_SPACE_MAXSIZE, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* no locking */ &lsc->sc_parent_dmat); if (error != 0) { device_printf(dev, "cannot allocate parent DMA tag\n"); goto fail_lres; } i = sbus_get_burstsz(dev); lsc->sc_burst = (i & SBUS_BURST_32) ? 32 : (i & SBUS_BURST_16) ? 16 : 0; lsc->sc_dev = dev; /* Attach children. */ i = 0; for (child = OF_child(node); child != 0; child = OF_peer(child)) { if ((ddi = dma_setup_dinfo(dev, dsc, child)) == NULL) continue; if (i != 0) { device_printf(dev, "<%s>: only one child per DMA channel supported\n", ddi->ddi_obdinfo.obd_name); dma_destroy_dinfo(ddi); continue; } if ((cdev = device_add_child(dev, NULL, -1)) == NULL) { device_printf(dev, "<%s>: device_add_child failed\n", ddi->ddi_obdinfo.obd_name); dma_destroy_dinfo(ddi); continue; } device_set_ivars(cdev, ddi); i++; } return (bus_generic_attach(dev)); fail_lres: bus_release_resource(dev, SYS_RES_MEMORY, rman_get_rid(lsc->sc_res), lsc->sc_res); return (error); } static struct dma_devinfo * dma_setup_dinfo(device_t dev, struct dma_softc *dsc, phandle_t node) { struct dma_devinfo *ddi; struct sbus_regs *reg; uint32_t base, iv, *intr; int i, nreg, nintr, slot, rslot; ddi = malloc(sizeof(*ddi), M_DEVBUF, M_WAITOK | M_ZERO); if (ofw_bus_gen_setup_devinfo(&ddi->ddi_obdinfo, node) != 0) { free(ddi, M_DEVBUF); return (NULL); } resource_list_init(&ddi->ddi_rl); slot = -1; - nreg = OF_getprop_alloc(node, "reg", sizeof(*reg), (void **)®); + nreg = OF_getprop_alloc_multi(node, "reg", sizeof(*reg), (void **)®); if (nreg == -1) { device_printf(dev, "<%s>: incomplete\n", ddi->ddi_obdinfo.obd_name); goto fail; } for (i = 0; i < nreg; i++) { base = reg[i].sbr_offset; if (SBUS_ABS(base)) { rslot = SBUS_ABS_TO_SLOT(base); base = SBUS_ABS_TO_OFFSET(base); } else rslot = reg[i].sbr_slot; if (slot != -1 && slot != rslot) { device_printf(dev, "<%s>: multiple slots\n", ddi->ddi_obdinfo.obd_name); OF_prop_free(reg); goto fail; } slot = rslot; resource_list_add(&ddi->ddi_rl, SYS_RES_MEMORY, i, base, base + reg[i].sbr_size, reg[i].sbr_size); } OF_prop_free(reg); if (slot != dsc->sc_slot) { device_printf(dev, "<%s>: parent and child slot do not match\n", ddi->ddi_obdinfo.obd_name); goto fail; } /* * The `interrupts' property contains the SBus interrupt level. */ - nintr = OF_getprop_alloc(node, "interrupts", sizeof(*intr), + nintr = OF_getprop_alloc_multi(node, "interrupts", sizeof(*intr), (void **)&intr); if (nintr != -1) { for (i = 0; i < nintr; i++) { iv = intr[i]; /* * SBus card devices need the slot number encoded into * the vector as this is generally not done. */ if ((iv & INTMAP_OBIO_MASK) == 0) iv |= slot << 3; /* Set the IGN as appropriate. */ iv |= dsc->sc_ign << INTMAP_IGN_SHIFT; resource_list_add(&ddi->ddi_rl, SYS_RES_IRQ, i, iv, iv, 1); } OF_prop_free(intr); } return (ddi); fail: dma_destroy_dinfo(ddi); return (NULL); } static void dma_destroy_dinfo(struct dma_devinfo *dinfo) { resource_list_free(&dinfo->ddi_rl); ofw_bus_gen_destroy_devinfo(&dinfo->ddi_obdinfo); free(dinfo, M_DEVBUF); } static int dma_print_child(device_t dev, device_t child) { int rv; rv = bus_print_child_header(dev, child); rv += dma_print_res(device_get_ivars(child)); rv += bus_print_child_footer(dev, child); return (rv); } static void dma_probe_nomatch(device_t dev, device_t child) { const char *type; device_printf(dev, "<%s>", ofw_bus_get_name(child)); dma_print_res(device_get_ivars(child)); type = ofw_bus_get_type(child); printf(" type %s (no driver attached)\n", type != NULL ? type : "unknown"); } static struct resource_list * dma_get_resource_list(device_t dev, device_t child) { struct dma_devinfo *ddi; ddi = device_get_ivars(child); return (&ddi->ddi_rl); } static const struct ofw_bus_devinfo * dma_get_devinfo(device_t bus, device_t child) { struct dma_devinfo *ddi; ddi = device_get_ivars(child); return (&ddi->ddi_obdinfo); } static int dma_print_res(struct dma_devinfo *ddi) { int rv; rv = 0; rv += resource_list_print_type(&ddi->ddi_rl, "mem", SYS_RES_MEMORY, "%#jx"); rv += resource_list_print_type(&ddi->ddi_rl, "irq", SYS_RES_IRQ, "%jd"); return (rv); } Index: head/sys/sparc64/sbus/sbus.c =================================================================== --- head/sys/sparc64/sbus/sbus.c (revision 332309) +++ head/sys/sparc64/sbus/sbus.c (revision 332310) @@ -1,938 +1,938 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1999-2002 Eduardo Horvath * 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. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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. * * from: NetBSD: sbus.c,v 1.50 2002/06/20 18:26:24 eeh Exp */ /*- * Copyright (c) 2002 by Thomas Moestl . * Copyright (c) 2005 Marius Strobl * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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$"); /* * SBus support. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct sbus_devinfo { int sdi_burstsz; int sdi_clockfreq; int sdi_slot; struct ofw_bus_devinfo sdi_obdinfo; struct resource_list sdi_rl; }; /* Range descriptor, allocated for each sc_range. */ struct sbus_rd { bus_addr_t rd_poffset; bus_addr_t rd_pend; int rd_slot; bus_addr_t rd_coffset; bus_addr_t rd_cend; struct rman rd_rman; bus_space_handle_t rd_bushandle; struct resource *rd_res; }; struct sbus_softc { device_t sc_dev; bus_dma_tag_t sc_cdmatag; int sc_clockfreq; /* clock frequency (in Hz) */ int sc_nrange; struct sbus_rd *sc_rd; int sc_burst; /* burst transfer sizes supp. */ struct resource *sc_sysio_res; int sc_ign; /* IGN for this sysio */ struct iommu_state sc_is; /* IOMMU state (iommuvar.h) */ struct resource *sc_ot_ires; void *sc_ot_ihand; struct resource *sc_pf_ires; void *sc_pf_ihand; }; #define SYSIO_READ8(sc, off) \ bus_read_8((sc)->sc_sysio_res, (off)) #define SYSIO_WRITE8(sc, off, v) \ bus_write_8((sc)->sc_sysio_res, (off), (v)) static device_probe_t sbus_probe; static device_attach_t sbus_attach; static bus_print_child_t sbus_print_child; static bus_probe_nomatch_t sbus_probe_nomatch; static bus_read_ivar_t sbus_read_ivar; static bus_get_resource_list_t sbus_get_resource_list; static bus_setup_intr_t sbus_setup_intr; static bus_alloc_resource_t sbus_alloc_resource; static bus_activate_resource_t sbus_activate_resource; static bus_adjust_resource_t sbus_adjust_resource; static bus_release_resource_t sbus_release_resource; static bus_get_dma_tag_t sbus_get_dma_tag; static ofw_bus_get_devinfo_t sbus_get_devinfo; static int sbus_inlist(const char *, const char *const *); static struct sbus_devinfo * sbus_setup_dinfo(device_t, struct sbus_softc *, phandle_t); static void sbus_destroy_dinfo(struct sbus_devinfo *); static void sbus_intr_enable(void *); static void sbus_intr_disable(void *); static void sbus_intr_assign(void *); static void sbus_intr_clear(void *); static int sbus_find_intrmap(struct sbus_softc *, u_int, bus_addr_t *, bus_addr_t *); static driver_intr_t sbus_overtemp; static driver_intr_t sbus_pwrfail; static int sbus_print_res(struct sbus_devinfo *); static device_method_t sbus_methods[] = { /* Device interface */ DEVMETHOD(device_probe, sbus_probe), DEVMETHOD(device_attach, sbus_attach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* Bus interface */ DEVMETHOD(bus_print_child, sbus_print_child), DEVMETHOD(bus_probe_nomatch, sbus_probe_nomatch), DEVMETHOD(bus_read_ivar, sbus_read_ivar), DEVMETHOD(bus_alloc_resource, sbus_alloc_resource), DEVMETHOD(bus_activate_resource, sbus_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_adjust_resource, sbus_adjust_resource), DEVMETHOD(bus_release_resource, sbus_release_resource), DEVMETHOD(bus_setup_intr, sbus_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), DEVMETHOD(bus_get_resource_list, sbus_get_resource_list), DEVMETHOD(bus_child_pnpinfo_str, ofw_bus_gen_child_pnpinfo_str), DEVMETHOD(bus_get_dma_tag, sbus_get_dma_tag), /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_devinfo, sbus_get_devinfo), DEVMETHOD(ofw_bus_get_compat, ofw_bus_gen_get_compat), DEVMETHOD(ofw_bus_get_model, ofw_bus_gen_get_model), DEVMETHOD(ofw_bus_get_name, ofw_bus_gen_get_name), DEVMETHOD(ofw_bus_get_node, ofw_bus_gen_get_node), DEVMETHOD(ofw_bus_get_type, ofw_bus_gen_get_type), DEVMETHOD_END }; static driver_t sbus_driver = { "sbus", sbus_methods, sizeof(struct sbus_softc), }; static devclass_t sbus_devclass; EARLY_DRIVER_MODULE(sbus, nexus, sbus_driver, sbus_devclass, NULL, NULL, BUS_PASS_BUS); MODULE_DEPEND(sbus, nexus, 1, 1, 1); MODULE_VERSION(sbus, 1); #define OFW_SBUS_TYPE "sbus" #define OFW_SBUS_NAME "sbus" static const struct intr_controller sbus_ic = { sbus_intr_enable, sbus_intr_disable, sbus_intr_assign, sbus_intr_clear }; struct sbus_icarg { struct sbus_softc *sica_sc; bus_addr_t sica_map; bus_addr_t sica_clr; }; static const char *const sbus_order_first[] = { "auxio", "dma", NULL }; static int sbus_inlist(const char *name, const char *const *list) { int i; if (name == NULL) return (0); for (i = 0; list[i] != NULL; i++) { if (strcmp(name, list[i]) == 0) return (1); } return (0); } static int sbus_probe(device_t dev) { const char *t; t = ofw_bus_get_type(dev); if (((t == NULL || strcmp(t, OFW_SBUS_TYPE) != 0)) && strcmp(ofw_bus_get_name(dev), OFW_SBUS_NAME) != 0) return (ENXIO); device_set_desc(dev, "U2S UPA-SBus bridge"); return (0); } static int sbus_attach(device_t dev) { struct sbus_softc *sc; struct sbus_devinfo *sdi; struct sbus_icarg *sica; struct sbus_ranges *range; struct resource *res; struct resource_list *rl; device_t cdev; bus_addr_t intrclr, intrmap, phys; bus_size_t size; u_long vec; phandle_t child, node; uint32_t prop; int i, j; sc = device_get_softc(dev); sc->sc_dev = dev; node = ofw_bus_get_node(dev); i = 0; sc->sc_sysio_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &i, RF_ACTIVE); if (sc->sc_sysio_res == NULL) panic("%s: cannot allocate device memory", __func__); if (OF_getprop(node, "interrupts", &prop, sizeof(prop)) == -1) panic("%s: cannot get IGN", __func__); sc->sc_ign = INTIGN(prop); /* * Record clock frequency for synchronous SCSI. * IS THIS THE CORRECT DEFAULT?? */ if (OF_getprop(node, "clock-frequency", &prop, sizeof(prop)) == -1) prop = 25000000; sc->sc_clockfreq = prop; prop /= 1000; device_printf(dev, "clock %d.%03d MHz\n", prop / 1000, prop % 1000); /* * Collect address translations from the OBP. */ - if ((sc->sc_nrange = OF_getprop_alloc(node, "ranges", + if ((sc->sc_nrange = OF_getprop_alloc_multi(node, "ranges", sizeof(*range), (void **)&range)) == -1) { panic("%s: error getting ranges property", __func__); } sc->sc_rd = malloc(sizeof(*sc->sc_rd) * sc->sc_nrange, M_DEVBUF, M_NOWAIT | M_ZERO); if (sc->sc_rd == NULL) panic("%s: cannot allocate rmans", __func__); /* * Preallocate all space that the SBus bridge decodes, so that nothing * else gets in the way; set up rmans etc. */ rl = BUS_GET_RESOURCE_LIST(device_get_parent(dev), dev); for (i = 0; i < sc->sc_nrange; i++) { phys = range[i].poffset | ((bus_addr_t)range[i].pspace << 32); size = range[i].size; sc->sc_rd[i].rd_slot = range[i].cspace; sc->sc_rd[i].rd_coffset = range[i].coffset; sc->sc_rd[i].rd_cend = sc->sc_rd[i].rd_coffset + size; j = resource_list_add_next(rl, SYS_RES_MEMORY, phys, phys + size - 1, size); if ((res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &j, RF_ACTIVE)) == NULL) panic("%s: cannot allocate decoded range", __func__); sc->sc_rd[i].rd_bushandle = rman_get_bushandle(res); sc->sc_rd[i].rd_rman.rm_type = RMAN_ARRAY; sc->sc_rd[i].rd_rman.rm_descr = "SBus Device Memory"; if (rman_init(&sc->sc_rd[i].rd_rman) != 0 || rman_manage_region(&sc->sc_rd[i].rd_rman, 0, size) != 0) panic("%s: failed to set up memory rman", __func__); sc->sc_rd[i].rd_poffset = phys; sc->sc_rd[i].rd_pend = phys + size; sc->sc_rd[i].rd_res = res; } OF_prop_free(range); /* * Get the SBus burst transfer size if burst transfers are supported. */ if (OF_getprop(node, "up-burst-sizes", &sc->sc_burst, sizeof(sc->sc_burst)) == -1 || sc->sc_burst == 0) sc->sc_burst = (SBUS_BURST64_DEF << SBUS_BURST64_SHIFT) | SBUS_BURST_DEF; /* initialise the IOMMU */ /* punch in our copies */ sc->sc_is.is_pmaxaddr = IOMMU_MAXADDR(SBUS_IOMMU_BITS); sc->sc_is.is_bustag = rman_get_bustag(sc->sc_sysio_res); sc->sc_is.is_bushandle = rman_get_bushandle(sc->sc_sysio_res); sc->sc_is.is_iommu = SBR_IOMMU; sc->sc_is.is_dtag = SBR_IOMMU_TLB_TAG_DIAG; sc->sc_is.is_ddram = SBR_IOMMU_TLB_DATA_DIAG; sc->sc_is.is_dqueue = SBR_IOMMU_QUEUE_DIAG; sc->sc_is.is_dva = SBR_IOMMU_SVADIAG; sc->sc_is.is_dtcmp = 0; sc->sc_is.is_sb[0] = SBR_STRBUF; sc->sc_is.is_sb[1] = 0; /* * Note: the SBus IOMMU ignores the high bits of an address, so a NULL * DMA pointer will be translated by the first page of the IOTSB. * To detect bugs we'll allocate and ignore the first entry. */ iommu_init(device_get_nameunit(dev), &sc->sc_is, 3, -1, 1); /* Create the DMA tag. */ if (bus_dma_tag_create(bus_get_dma_tag(dev), 8, 0, sc->sc_is.is_pmaxaddr, ~0, NULL, NULL, sc->sc_is.is_pmaxaddr, 0xff, 0xffffffff, 0, NULL, NULL, &sc->sc_cdmatag) != 0) panic("%s: bus_dma_tag_create failed", __func__); /* Customize the tag. */ sc->sc_cdmatag->dt_cookie = &sc->sc_is; sc->sc_cdmatag->dt_mt = &iommu_dma_methods; /* * Hunt through all the interrupt mapping regs and register our * interrupt controller for the corresponding interrupt vectors. * We do this early in order to be able to catch stray interrupts. */ for (i = 0; i <= SBUS_MAX_INO; i++) { if (sbus_find_intrmap(sc, i, &intrmap, &intrclr) == 0) continue; sica = malloc(sizeof(*sica), M_DEVBUF, M_NOWAIT); if (sica == NULL) panic("%s: could not allocate interrupt controller " "argument", __func__); sica->sica_sc = sc; sica->sica_map = intrmap; sica->sica_clr = intrclr; #ifdef SBUS_DEBUG device_printf(dev, "intr map (INO %d, %s) %#lx: %#lx, clr: %#lx\n", i, (i & INTMAP_OBIO_MASK) == 0 ? "SBus slot" : "OBIO", (u_long)intrmap, (u_long)SYSIO_READ8(sc, intrmap), (u_long)intrclr); #endif j = intr_controller_register(INTMAP_VEC(sc->sc_ign, i), &sbus_ic, sica); if (j != 0) device_printf(dev, "could not register interrupt " "controller for INO %d (%d)\n", i, j); } /* Enable the over-temperature and power-fail interrupts. */ i = 4; sc->sc_ot_ires = bus_alloc_resource_any(dev, SYS_RES_IRQ, &i, RF_ACTIVE); if (sc->sc_ot_ires == NULL || INTIGN(vec = rman_get_start(sc->sc_ot_ires)) != sc->sc_ign || INTVEC(SYSIO_READ8(sc, SBR_THERM_INT_MAP)) != vec || intr_vectors[vec].iv_ic != &sbus_ic || bus_setup_intr(dev, sc->sc_ot_ires, INTR_TYPE_MISC | INTR_BRIDGE | INTR_MPSAFE, NULL, sbus_overtemp, sc, &sc->sc_ot_ihand) != 0) panic("%s: failed to set up temperature interrupt", __func__); i = 3; sc->sc_pf_ires = bus_alloc_resource_any(dev, SYS_RES_IRQ, &i, RF_ACTIVE); if (sc->sc_pf_ires == NULL || INTIGN(vec = rman_get_start(sc->sc_pf_ires)) != sc->sc_ign || INTVEC(SYSIO_READ8(sc, SBR_POWER_INT_MAP)) != vec || intr_vectors[vec].iv_ic != &sbus_ic || bus_setup_intr(dev, sc->sc_pf_ires, INTR_TYPE_MISC | INTR_BRIDGE | INTR_MPSAFE, NULL, sbus_pwrfail, sc, &sc->sc_pf_ihand) != 0) panic("%s: failed to set up power fail interrupt", __func__); /* Initialize the counter-timer. */ sparc64_counter_init(device_get_nameunit(dev), rman_get_bustag(sc->sc_sysio_res), rman_get_bushandle(sc->sc_sysio_res), SBR_TC0); /* * Loop through ROM children, fixing any relative addresses * and then configuring each device. */ for (child = OF_child(node); child != 0; child = OF_peer(child)) { if ((sdi = sbus_setup_dinfo(dev, sc, child)) == NULL) continue; /* * For devices where there are variants that are actually * split into two SBus devices (as opposed to the first * half of the device being a SBus device and the second * half hanging off of the first one) like 'auxio' and * 'SUNW,fdtwo' or 'dma' and 'esp' probe the SBus device * which is a prerequisite to the driver attaching to the * second one with a lower order. Saves us from dealing * with different probe orders in the respective device * drivers which generally is more hackish. */ cdev = device_add_child_ordered(dev, (OF_child(child) == 0 && sbus_inlist(sdi->sdi_obdinfo.obd_name, sbus_order_first)) ? SBUS_ORDER_FIRST : SBUS_ORDER_NORMAL, NULL, -1); if (cdev == NULL) { device_printf(dev, "<%s>: device_add_child_ordered failed\n", sdi->sdi_obdinfo.obd_name); sbus_destroy_dinfo(sdi); continue; } device_set_ivars(cdev, sdi); } return (bus_generic_attach(dev)); } static struct sbus_devinfo * sbus_setup_dinfo(device_t dev, struct sbus_softc *sc, phandle_t node) { struct sbus_devinfo *sdi; struct sbus_regs *reg; u_int32_t base, iv, *intr; int i, nreg, nintr, slot, rslot; sdi = malloc(sizeof(*sdi), M_DEVBUF, M_ZERO | M_WAITOK); if (ofw_bus_gen_setup_devinfo(&sdi->sdi_obdinfo, node) != 0) { free(sdi, M_DEVBUF); return (NULL); } resource_list_init(&sdi->sdi_rl); slot = -1; - nreg = OF_getprop_alloc(node, "reg", sizeof(*reg), (void **)®); + nreg = OF_getprop_alloc_multi(node, "reg", sizeof(*reg), (void **)®); if (nreg == -1) { if (sdi->sdi_obdinfo.obd_type == NULL || strcmp(sdi->sdi_obdinfo.obd_type, "hierarchical") != 0) { device_printf(dev, "<%s>: incomplete\n", sdi->sdi_obdinfo.obd_name); goto fail; } } else { for (i = 0; i < nreg; i++) { base = reg[i].sbr_offset; if (SBUS_ABS(base)) { rslot = SBUS_ABS_TO_SLOT(base); base = SBUS_ABS_TO_OFFSET(base); } else rslot = reg[i].sbr_slot; if (slot != -1 && slot != rslot) { device_printf(dev, "<%s>: multiple slots\n", sdi->sdi_obdinfo.obd_name); OF_prop_free(reg); goto fail; } slot = rslot; resource_list_add(&sdi->sdi_rl, SYS_RES_MEMORY, i, base, base + reg[i].sbr_size, reg[i].sbr_size); } OF_prop_free(reg); } sdi->sdi_slot = slot; /* * The `interrupts' property contains the SBus interrupt level. */ - nintr = OF_getprop_alloc(node, "interrupts", sizeof(*intr), + nintr = OF_getprop_alloc_multi(node, "interrupts", sizeof(*intr), (void **)&intr); if (nintr != -1) { for (i = 0; i < nintr; i++) { iv = intr[i]; /* * SBus card devices need the slot number encoded into * the vector as this is generally not done. */ if ((iv & INTMAP_OBIO_MASK) == 0) iv |= slot << 3; iv = INTMAP_VEC(sc->sc_ign, iv); resource_list_add(&sdi->sdi_rl, SYS_RES_IRQ, i, iv, iv, 1); } OF_prop_free(intr); } if (OF_getprop(node, "burst-sizes", &sdi->sdi_burstsz, sizeof(sdi->sdi_burstsz)) == -1) sdi->sdi_burstsz = sc->sc_burst; else sdi->sdi_burstsz &= sc->sc_burst; if (OF_getprop(node, "clock-frequency", &sdi->sdi_clockfreq, sizeof(sdi->sdi_clockfreq)) == -1) sdi->sdi_clockfreq = sc->sc_clockfreq; return (sdi); fail: sbus_destroy_dinfo(sdi); return (NULL); } static void sbus_destroy_dinfo(struct sbus_devinfo *dinfo) { resource_list_free(&dinfo->sdi_rl); ofw_bus_gen_destroy_devinfo(&dinfo->sdi_obdinfo); free(dinfo, M_DEVBUF); } static int sbus_print_child(device_t dev, device_t child) { int rv; rv = bus_print_child_header(dev, child); rv += sbus_print_res(device_get_ivars(child)); rv += bus_print_child_footer(dev, child); return (rv); } static void sbus_probe_nomatch(device_t dev, device_t child) { const char *type; device_printf(dev, "<%s>", ofw_bus_get_name(child)); sbus_print_res(device_get_ivars(child)); type = ofw_bus_get_type(child); printf(" type %s (no driver attached)\n", type != NULL ? type : "unknown"); } static int sbus_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) { struct sbus_softc *sc; struct sbus_devinfo *dinfo; sc = device_get_softc(dev); if ((dinfo = device_get_ivars(child)) == NULL) return (ENOENT); switch (which) { case SBUS_IVAR_BURSTSZ: *result = dinfo->sdi_burstsz; break; case SBUS_IVAR_CLOCKFREQ: *result = dinfo->sdi_clockfreq; break; case SBUS_IVAR_IGN: *result = sc->sc_ign; break; case SBUS_IVAR_SLOT: *result = dinfo->sdi_slot; break; default: return (ENOENT); } return (0); } static struct resource_list * sbus_get_resource_list(device_t dev, device_t child) { struct sbus_devinfo *sdi; sdi = device_get_ivars(child); return (&sdi->sdi_rl); } static void sbus_intr_enable(void *arg) { struct intr_vector *iv = arg; struct sbus_icarg *sica = iv->iv_icarg; SYSIO_WRITE8(sica->sica_sc, sica->sica_map, INTMAP_ENABLE(iv->iv_vec, iv->iv_mid)); } static void sbus_intr_disable(void *arg) { struct intr_vector *iv = arg; struct sbus_icarg *sica = iv->iv_icarg; SYSIO_WRITE8(sica->sica_sc, sica->sica_map, iv->iv_vec); } static void sbus_intr_assign(void *arg) { struct intr_vector *iv = arg; struct sbus_icarg *sica = iv->iv_icarg; SYSIO_WRITE8(sica->sica_sc, sica->sica_map, INTMAP_TID( SYSIO_READ8(sica->sica_sc, sica->sica_map), iv->iv_mid)); } static void sbus_intr_clear(void *arg) { struct intr_vector *iv = arg; struct sbus_icarg *sica = iv->iv_icarg; SYSIO_WRITE8(sica->sica_sc, sica->sica_clr, INTCLR_IDLE); } static int sbus_find_intrmap(struct sbus_softc *sc, u_int ino, bus_addr_t *intrmapptr, bus_addr_t *intrclrptr) { bus_addr_t intrclr, intrmap; int i; if (ino > SBUS_MAX_INO) { device_printf(sc->sc_dev, "out of range INO %d requested\n", ino); return (0); } if ((ino & INTMAP_OBIO_MASK) == 0) { intrmap = SBR_SLOT0_INT_MAP + INTSLOT(ino) * 8; intrclr = SBR_SLOT0_INT_CLR + (INTSLOT(ino) * 8 * 8) + (INTPRI(ino) * 8); } else { intrclr = 0; for (i = 0, intrmap = SBR_SCSI_INT_MAP; intrmap <= SBR_RESERVED_INT_MAP; intrmap += 8, i++) { if (INTVEC(SYSIO_READ8(sc, intrmap)) == INTMAP_VEC(sc->sc_ign, ino)) { intrclr = SBR_SCSI_INT_CLR + i * 8; break; } } if (intrclr == 0) return (0); } if (intrmapptr != NULL) *intrmapptr = intrmap; if (intrclrptr != NULL) *intrclrptr = intrclr; return (1); } static int sbus_setup_intr(device_t dev, device_t child, struct resource *ires, int flags, driver_filter_t *filt, driver_intr_t *intr, void *arg, void **cookiep) { struct sbus_softc *sc; u_long vec; sc = device_get_softc(dev); /* * Make sure the vector is fully specified and we registered * our interrupt controller for it. */ vec = rman_get_start(ires); if (INTIGN(vec) != sc->sc_ign || intr_vectors[vec].iv_ic != &sbus_ic) { device_printf(dev, "invalid interrupt vector 0x%lx\n", vec); return (EINVAL); } return (bus_generic_setup_intr(dev, child, ires, flags, filt, intr, arg, cookiep)); } static struct resource * sbus_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 sbus_softc *sc; struct rman *rm; struct resource *rv; struct resource_list *rl; struct resource_list_entry *rle; device_t schild; bus_addr_t toffs; bus_size_t tend; int i, slot; int isdefault, passthrough; isdefault = RMAN_IS_DEFAULT_RANGE(start, end); passthrough = (device_get_parent(child) != bus); rle = NULL; sc = device_get_softc(bus); rl = BUS_GET_RESOURCE_LIST(bus, child); switch (type) { case SYS_RES_IRQ: return (resource_list_alloc(rl, bus, child, type, rid, start, end, count, flags)); case SYS_RES_MEMORY: if (!passthrough) { rle = resource_list_find(rl, type, *rid); if (rle == NULL) return (NULL); if (rle->res != NULL) panic("%s: resource entry is busy", __func__); if (isdefault) { start = rle->start; count = ulmax(count, rle->count); end = ulmax(rle->end, start + count - 1); } } rm = NULL; schild = child; while (device_get_parent(schild) != bus) schild = device_get_parent(schild); slot = sbus_get_slot(schild); for (i = 0; i < sc->sc_nrange; i++) { if (sc->sc_rd[i].rd_slot != slot || start < sc->sc_rd[i].rd_coffset || start > sc->sc_rd[i].rd_cend) continue; /* Disallow cross-range allocations. */ if (end > sc->sc_rd[i].rd_cend) return (NULL); /* We've found the connection to the parent bus */ toffs = start - sc->sc_rd[i].rd_coffset; tend = end - sc->sc_rd[i].rd_coffset; rm = &sc->sc_rd[i].rd_rman; break; } if (rm == NULL) return (NULL); rv = rman_reserve_resource(rm, toffs, tend, count, flags & ~RF_ACTIVE, child); if (rv == NULL) return (NULL); rman_set_rid(rv, *rid); if ((flags & RF_ACTIVE) != 0 && bus_activate_resource(child, type, *rid, rv)) { rman_release_resource(rv); return (NULL); } if (!passthrough) rle->res = rv; return (rv); default: return (NULL); } } static int sbus_activate_resource(device_t bus, device_t child, int type, int rid, struct resource *r) { struct sbus_softc *sc; struct bus_space_tag *tag; int i; switch (type) { case SYS_RES_IRQ: return (bus_generic_activate_resource(bus, child, type, rid, r)); case SYS_RES_MEMORY: sc = device_get_softc(bus); for (i = 0; i < sc->sc_nrange; i++) { if (rman_is_region_manager(r, &sc->sc_rd[i].rd_rman) != 0) { tag = sparc64_alloc_bus_tag(r, SBUS_BUS_SPACE); if (tag == NULL) return (ENOMEM); rman_set_bustag(r, tag); rman_set_bushandle(r, sc->sc_rd[i].rd_bushandle + rman_get_start(r)); return (rman_activate_resource(r)); } } /* FALLTHROUGH */ default: return (EINVAL); } } static int sbus_adjust_resource(device_t bus, device_t child, int type, struct resource *r, rman_res_t start, rman_res_t end) { struct sbus_softc *sc; int i; if (type == SYS_RES_MEMORY) { sc = device_get_softc(bus); for (i = 0; i < sc->sc_nrange; i++) if (rman_is_region_manager(r, &sc->sc_rd[i].rd_rman) != 0) return (rman_adjust_resource(r, start, end)); return (EINVAL); } return (bus_generic_adjust_resource(bus, child, type, r, start, end)); } static int sbus_release_resource(device_t bus, device_t child, int type, int rid, struct resource *r) { struct resource_list *rl; struct resource_list_entry *rle; int error, passthrough; passthrough = (device_get_parent(child) != bus); rl = BUS_GET_RESOURCE_LIST(bus, child); if (type == SYS_RES_MEMORY) { if ((rman_get_flags(r) & RF_ACTIVE) != 0) { error = bus_deactivate_resource(child, type, rid, r); if (error) return (error); } error = rman_release_resource(r); if (error != 0) return (error); if (!passthrough) { rle = resource_list_find(rl, type, rid); KASSERT(rle != NULL, ("%s: resource entry not found!", __func__)); KASSERT(rle->res != NULL, ("%s: resource entry is not busy", __func__)); rle->res = NULL; } return (0); } return (resource_list_release(rl, bus, child, type, rid, r)); } static bus_dma_tag_t sbus_get_dma_tag(device_t bus, device_t child) { struct sbus_softc *sc; sc = device_get_softc(bus); return (sc->sc_cdmatag); } static const struct ofw_bus_devinfo * sbus_get_devinfo(device_t bus, device_t child) { struct sbus_devinfo *sdi; sdi = device_get_ivars(child); return (&sdi->sdi_obdinfo); } /* * Handle an overtemp situation. * * SPARCs have temperature sensors which generate interrupts * if the machine's temperature exceeds a certain threshold. * This handles the interrupt and powers off the machine. * The same needs to be done to PCI controller drivers. */ static void sbus_overtemp(void *arg __unused) { static int shutdown; /* As the interrupt is cleared we may be called multiple times. */ if (shutdown != 0) return; shutdown++; printf("DANGER: OVER TEMPERATURE detected\nShutting down NOW.\n"); shutdown_nice(RB_POWEROFF); } /* Try to shut down in time in case of power failure. */ static void sbus_pwrfail(void *arg __unused) { static int shutdown; /* As the interrupt is cleared we may be called multiple times. */ if (shutdown != 0) return; shutdown++; printf("Power failure detected\nShutting down NOW.\n"); shutdown_nice(RB_POWEROFF); } static int sbus_print_res(struct sbus_devinfo *sdi) { int rv; rv = 0; rv += resource_list_print_type(&sdi->sdi_rl, "mem", SYS_RES_MEMORY, "%#jx"); rv += resource_list_print_type(&sdi->sdi_rl, "irq", SYS_RES_IRQ, "%jd"); return (rv); } Index: head/sys/sparc64/sparc64/nexus.c =================================================================== --- head/sys/sparc64/sparc64/nexus.c (revision 332309) +++ head/sys/sparc64/sparc64/nexus.c (revision 332310) @@ -1,612 +1,612 @@ /*- * Copyright 1998 Massachusetts Institute of Technology * Copyright 2001 by Thomas Moestl . * Copyright 2006 by Marius Strobl . * All rights reserved. * * Permission to use, copy, modify, and distribute this software and * its documentation for any purpose and without fee is hereby * granted, provided that both the above copyright notice and this * permission notice appear in all copies, that both the above * copyright notice and this permission notice appear in all * supporting documentation, and that the name of M.I.T. not be used * in advertising or publicity pertaining to distribution of the * software without specific, written prior permission. M.I.T. makes * no representations about the suitability of this software for any * purpose. It is provided "as is" without express or implied * warranty. * * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''. M.I.T. DISCLAIMS * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT * SHALL M.I.T. 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. * * from: FreeBSD: src/sys/i386/i386/nexus.c,v 1.43 2001/02/09 */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * The nexus (which is a pseudo-bus actually) iterates over the nodes that * hang from the Open Firmware root node and adds them as devices to this bus * (except some special nodes which are excluded) so that drivers can be * attached to them. * * Additionally, interrupt setup/teardown and some resource management are * done at this level. * * Maybe this code should get into dev/ofw to some extent, as some of it should * work for all Open Firmware based machines... */ struct nexus_devinfo { struct ofw_bus_devinfo ndi_obdinfo; struct resource_list ndi_rl; }; struct nexus_softc { struct rman sc_intr_rman; struct rman sc_mem_rman; }; static device_probe_t nexus_probe; static device_attach_t nexus_attach; static bus_print_child_t nexus_print_child; static bus_add_child_t nexus_add_child; static bus_probe_nomatch_t nexus_probe_nomatch; static bus_setup_intr_t nexus_setup_intr; static bus_teardown_intr_t nexus_teardown_intr; static bus_alloc_resource_t nexus_alloc_resource; static bus_activate_resource_t nexus_activate_resource; static bus_deactivate_resource_t nexus_deactivate_resource; static bus_adjust_resource_t nexus_adjust_resource; static bus_release_resource_t nexus_release_resource; static bus_get_resource_list_t nexus_get_resource_list; #ifdef SMP static bus_bind_intr_t nexus_bind_intr; #endif static bus_describe_intr_t nexus_describe_intr; static bus_get_dma_tag_t nexus_get_dma_tag; static bus_get_bus_tag_t nexus_get_bus_tag; static ofw_bus_get_devinfo_t nexus_get_devinfo; static int nexus_inlist(const char *, const char *const *); static struct nexus_devinfo * nexus_setup_dinfo(device_t, phandle_t); static void nexus_destroy_dinfo(struct nexus_devinfo *); static int nexus_print_res(struct nexus_devinfo *); static device_method_t nexus_methods[] = { /* Device interface */ DEVMETHOD(device_probe, nexus_probe), DEVMETHOD(device_attach, nexus_attach), DEVMETHOD(device_detach, bus_generic_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* Bus interface */ DEVMETHOD(bus_print_child, nexus_print_child), DEVMETHOD(bus_probe_nomatch, nexus_probe_nomatch), DEVMETHOD(bus_read_ivar, bus_generic_read_ivar), DEVMETHOD(bus_write_ivar, bus_generic_write_ivar), DEVMETHOD(bus_add_child, nexus_add_child), DEVMETHOD(bus_alloc_resource, nexus_alloc_resource), DEVMETHOD(bus_activate_resource, nexus_activate_resource), DEVMETHOD(bus_deactivate_resource, nexus_deactivate_resource), DEVMETHOD(bus_adjust_resource, nexus_adjust_resource), DEVMETHOD(bus_release_resource, nexus_release_resource), DEVMETHOD(bus_setup_intr, nexus_setup_intr), DEVMETHOD(bus_teardown_intr, nexus_teardown_intr), DEVMETHOD(bus_set_resource, bus_generic_rl_set_resource), DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), DEVMETHOD(bus_get_resource_list, nexus_get_resource_list), #ifdef SMP DEVMETHOD(bus_bind_intr, nexus_bind_intr), #endif DEVMETHOD(bus_describe_intr, nexus_describe_intr), DEVMETHOD(bus_get_dma_tag, nexus_get_dma_tag), DEVMETHOD(bus_get_bus_tag, nexus_get_bus_tag), /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_devinfo, nexus_get_devinfo), DEVMETHOD(ofw_bus_get_compat, ofw_bus_gen_get_compat), DEVMETHOD(ofw_bus_get_model, ofw_bus_gen_get_model), DEVMETHOD(ofw_bus_get_name, ofw_bus_gen_get_name), DEVMETHOD(ofw_bus_get_node, ofw_bus_gen_get_node), DEVMETHOD(ofw_bus_get_type, ofw_bus_gen_get_type), DEVMETHOD_END }; static devclass_t nexus_devclass; DEFINE_CLASS_0(nexus, nexus_driver, nexus_methods, sizeof(struct nexus_softc)); EARLY_DRIVER_MODULE(nexus, root, nexus_driver, nexus_devclass, 0, 0, BUS_PASS_BUS); MODULE_VERSION(nexus, 1); static const char *const nexus_excl_name[] = { "FJSV,system", "aliases", "associations", "chosen", "cmp", "counter-timer", /* No separate device; handled by psycho/sbus */ "failsafe", "memory", "openprom", "options", "packages", "physical-memory", "rsc", "sgcn", "todsg", "virtual-memory", NULL }; static const char *const nexus_excl_type[] = { "core", "cpu", NULL }; extern struct bus_space_tag nexus_bustag; extern struct bus_dma_tag nexus_dmatag; static int nexus_inlist(const char *name, const char *const *list) { int i; if (name == NULL) return (0); for (i = 0; list[i] != NULL; i++) if (strcmp(name, list[i]) == 0) return (1); return (0); } #define NEXUS_EXCLUDED(name, type) \ (nexus_inlist((name), nexus_excl_name) || \ ((type) != NULL && nexus_inlist((type), nexus_excl_type))) static int nexus_probe(device_t dev) { /* Nexus does always match. */ device_set_desc(dev, "Open Firmware Nexus device"); return (0); } static int nexus_attach(device_t dev) { struct nexus_devinfo *ndi; struct nexus_softc *sc; device_t cdev; phandle_t node; if (strcmp(device_get_name(device_get_parent(dev)), "root") == 0) { node = OF_peer(0); if (node == -1) panic("%s: OF_peer failed.", __func__); sc = device_get_softc(dev); sc->sc_intr_rman.rm_type = RMAN_ARRAY; sc->sc_intr_rman.rm_descr = "Interrupts"; sc->sc_mem_rman.rm_type = RMAN_ARRAY; sc->sc_mem_rman.rm_descr = "Device Memory"; if (rman_init(&sc->sc_intr_rman) != 0 || rman_init(&sc->sc_mem_rman) != 0 || rman_manage_region(&sc->sc_intr_rman, 0, IV_MAX - 1) != 0 || rman_manage_region(&sc->sc_mem_rman, 0, BUS_SPACE_MAXADDR) != 0) panic("%s: failed to set up rmans.", __func__); } else node = ofw_bus_get_node(dev); /* * Allow devices to identify. */ bus_generic_probe(dev); /* * Now walk the OFW tree and attach top-level devices. */ for (node = OF_child(node); node > 0; node = OF_peer(node)) { if ((ndi = nexus_setup_dinfo(dev, node)) == NULL) continue; cdev = device_add_child(dev, NULL, -1); if (cdev == NULL) { device_printf(dev, "<%s>: device_add_child failed\n", ndi->ndi_obdinfo.obd_name); nexus_destroy_dinfo(ndi); continue; } device_set_ivars(cdev, ndi); } return (bus_generic_attach(dev)); } static device_t nexus_add_child(device_t dev, u_int order, const char *name, int unit) { device_t cdev; struct nexus_devinfo *ndi; cdev = device_add_child_ordered(dev, order, name, unit); if (cdev == NULL) return (NULL); ndi = malloc(sizeof(*ndi), M_DEVBUF, M_WAITOK | M_ZERO); ndi->ndi_obdinfo.obd_node = -1; ndi->ndi_obdinfo.obd_name = strdup(name, M_OFWPROP); resource_list_init(&ndi->ndi_rl); device_set_ivars(cdev, ndi); return (cdev); } static int nexus_print_child(device_t bus, device_t child) { int rv; rv = bus_print_child_header(bus, child); rv += nexus_print_res(device_get_ivars(child)); rv += bus_print_child_footer(bus, child); return (rv); } static void nexus_probe_nomatch(device_t bus, device_t child) { const char *type; device_printf(bus, "<%s>", ofw_bus_get_name(child)); nexus_print_res(device_get_ivars(child)); type = ofw_bus_get_type(child); printf(" type %s (no driver attached)\n", type != NULL ? type : "unknown"); } static int nexus_setup_intr(device_t bus __unused, device_t child, struct resource *r, int flags, driver_filter_t *filt, driver_intr_t *intr, void *arg, void **cookiep) { int error; if (r == NULL) panic("%s: NULL interrupt resource!", __func__); if ((rman_get_flags(r) & RF_SHAREABLE) == 0) flags |= INTR_EXCL; /* We depend here on rman_activate_resource() being idempotent. */ error = rman_activate_resource(r); if (error) return (error); error = inthand_add(device_get_nameunit(child), rman_get_start(r), filt, intr, arg, flags, cookiep); /* * XXX in case of the AFB/FFB interrupt and a Psycho, Sabre or U2S * bridge enable the interrupt in the respective bridge. */ return (error); } static int nexus_teardown_intr(device_t bus __unused, device_t child __unused, struct resource *r, void *ih) { inthand_remove(rman_get_start(r), ih); return (0); } #ifdef SMP static int nexus_bind_intr(device_t bus __unused, device_t child __unused, struct resource *r, int cpu) { return (intr_bind(rman_get_start(r), cpu)); } #endif static int nexus_describe_intr(device_t bus __unused, device_t child __unused, struct resource *r, void *cookie, const char *descr) { return (intr_describe(rman_get_start(r), cookie, descr)); } static struct resource * nexus_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 nexus_softc *sc; struct rman *rm; struct resource *rv; struct resource_list_entry *rle; device_t nexus; int isdefault, passthrough; isdefault = RMAN_IS_DEFAULT_RANGE(start, end); passthrough = (device_get_parent(child) != bus); nexus = bus; while (strcmp(device_get_name(device_get_parent(nexus)), "root") != 0) nexus = device_get_parent(nexus); sc = device_get_softc(nexus); rle = NULL; if (!passthrough) { rle = resource_list_find(BUS_GET_RESOURCE_LIST(bus, child), type, *rid); if (rle == NULL) return (NULL); if (rle->res != NULL) panic("%s: resource entry is busy", __func__); if (isdefault) { start = rle->start; count = ulmax(count, rle->count); end = ulmax(rle->end, start + count - 1); } } switch (type) { case SYS_RES_IRQ: rm = &sc->sc_intr_rman; break; case SYS_RES_MEMORY: rm = &sc->sc_mem_rman; break; default: return (NULL); } rv = rman_reserve_resource(rm, start, end, count, flags & ~RF_ACTIVE, child); if (rv == NULL) return (NULL); rman_set_rid(rv, *rid); if ((flags & RF_ACTIVE) != 0 && bus_activate_resource(child, type, *rid, rv) != 0) { rman_release_resource(rv); return (NULL); } if (!passthrough) { rle->res = rv; rle->start = rman_get_start(rv); rle->end = rman_get_end(rv); rle->count = rle->end - rle->start + 1; } return (rv); } static int nexus_activate_resource(device_t bus __unused, device_t child __unused, int type, int rid __unused, struct resource *r) { if (type == SYS_RES_MEMORY) { rman_set_bustag(r, &nexus_bustag); rman_set_bushandle(r, rman_get_start(r)); } return (rman_activate_resource(r)); } static int nexus_deactivate_resource(device_t bus __unused, device_t child __unused, int type __unused, int rid __unused, struct resource *r) { return (rman_deactivate_resource(r)); } static int nexus_adjust_resource(device_t bus, device_t child __unused, int type, struct resource *r, rman_res_t start, rman_res_t end) { struct nexus_softc *sc; struct rman *rm; device_t nexus; nexus = bus; while (strcmp(device_get_name(device_get_parent(nexus)), "root") != 0) nexus = device_get_parent(nexus); sc = device_get_softc(nexus); switch (type) { case SYS_RES_IRQ: rm = &sc->sc_intr_rman; break; case SYS_RES_MEMORY: rm = &sc->sc_mem_rman; break; default: return (EINVAL); } if (rm == NULL) return (ENXIO); if (rman_is_region_manager(r, rm) == 0) return (EINVAL); return (rman_adjust_resource(r, start, end)); } static int nexus_release_resource(device_t bus __unused, device_t child, int type, int rid, struct resource *r) { int error; if ((rman_get_flags(r) & RF_ACTIVE) != 0) { error = bus_deactivate_resource(child, type, rid, r); if (error) return (error); } return (rman_release_resource(r)); } static struct resource_list * nexus_get_resource_list(device_t bus __unused, device_t child) { struct nexus_devinfo *ndi; ndi = device_get_ivars(child); return (&ndi->ndi_rl); } static bus_dma_tag_t nexus_get_dma_tag(device_t bus __unused, device_t child __unused) { return (&nexus_dmatag); } static bus_space_tag_t nexus_get_bus_tag(device_t bus __unused, device_t child __unused) { return (&nexus_bustag); } static const struct ofw_bus_devinfo * nexus_get_devinfo(device_t bus __unused, device_t child) { struct nexus_devinfo *ndi; ndi = device_get_ivars(child); return (&ndi->ndi_obdinfo); } static struct nexus_devinfo * nexus_setup_dinfo(device_t dev, phandle_t node) { struct nexus_devinfo *ndi; struct nexus_regs *reg; bus_addr_t phys; bus_size_t size; uint32_t ign; uint32_t *intr; int i; int nintr; int nreg; ndi = malloc(sizeof(*ndi), M_DEVBUF, M_WAITOK | M_ZERO); if (ofw_bus_gen_setup_devinfo(&ndi->ndi_obdinfo, node) != 0) { free(ndi, M_DEVBUF); return (NULL); } if (NEXUS_EXCLUDED(ndi->ndi_obdinfo.obd_name, ndi->ndi_obdinfo.obd_type)) { ofw_bus_gen_destroy_devinfo(&ndi->ndi_obdinfo); free(ndi, M_DEVBUF); return (NULL); } resource_list_init(&ndi->ndi_rl); - nreg = OF_getprop_alloc(node, "reg", sizeof(*reg), (void **)®); + nreg = OF_getprop_alloc_multi(node, "reg", sizeof(*reg), (void **)®); if (nreg == -1) { device_printf(dev, "<%s>: incomplete\n", ndi->ndi_obdinfo.obd_name); goto fail; } for (i = 0; i < nreg; i++) { phys = NEXUS_REG_PHYS(®[i]); size = NEXUS_REG_SIZE(®[i]); /* Skip the dummy reg property of glue devices like ssm(4). */ if (size != 0) resource_list_add(&ndi->ndi_rl, SYS_RES_MEMORY, i, phys, phys + size - 1, size); } OF_prop_free(reg); - nintr = OF_getprop_alloc(node, "interrupts", sizeof(*intr), + nintr = OF_getprop_alloc_multi(node, "interrupts", sizeof(*intr), (void **)&intr); if (nintr > 0) { if (OF_getprop(node, PCPU_GET(impl) < CPU_IMPL_ULTRASPARCIII ? "upa-portid" : "portid", &ign, sizeof(ign)) <= 0) { device_printf(dev, "<%s>: could not determine portid\n", ndi->ndi_obdinfo.obd_name); OF_prop_free(intr); goto fail; } /* XXX 7-bit MID on Starfire */ ign = (ign << INTMAP_IGN_SHIFT) & INTMAP_IGN_MASK; for (i = 0; i < nintr; i++) { intr[i] |= ign; resource_list_add(&ndi->ndi_rl, SYS_RES_IRQ, i, intr[i], intr[i], 1); } OF_prop_free(intr); } return (ndi); fail: nexus_destroy_dinfo(ndi); return (NULL); } static void nexus_destroy_dinfo(struct nexus_devinfo *ndi) { resource_list_free(&ndi->ndi_rl); ofw_bus_gen_destroy_devinfo(&ndi->ndi_obdinfo); free(ndi, M_DEVBUF); } static int nexus_print_res(struct nexus_devinfo *ndi) { int rv; rv = 0; rv += resource_list_print_type(&ndi->ndi_rl, "mem", SYS_RES_MEMORY, "%#jx"); rv += resource_list_print_type(&ndi->ndi_rl, "irq", SYS_RES_IRQ, "%jd"); return (rv); } Index: head/sys/sparc64/sparc64/upa.c =================================================================== --- head/sys/sparc64/sparc64/upa.c (revision 332309) +++ head/sys/sparc64/sparc64/upa.c (revision 332310) @@ -1,597 +1,597 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2006 Marius Strobl * 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$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define UPA_NREG 3 #define UPA_CFG 0 #define UPA_IMR1 1 #define UPA_IMR2 2 /* UPA_CFG bank */ #define UPA_CFG_UPA0 0x00 /* UPA0 config register */ #define UPA_CFG_UPA1 0x08 /* UPA1 config register */ #define UPA_CFG_IF 0x10 /* interface config register */ #define UPA_CFG_IF_RST 0x00 #define UPA_CFG_IF_POK_RST 0x02 #define UPA_CFG_IF_POK 0x03 #define UPA_CFG_ESTAR 0x18 /* Estar config register */ #define UPA_CFG_ESTAR_SPEED_FULL 0x01 #define UPA_CFG_ESTAR_SPEED_1_2 0x02 #define UPA_CFG_ESTAR_SPEED_1_64 0x40 #define UPA_INO_BASE 0x2a #define UPA_INO_MAX 0x2b struct upa_regs { uint64_t phys; uint64_t size; }; struct upa_ranges { uint64_t child; uint64_t parent; uint64_t size; }; struct upa_devinfo { struct ofw_bus_devinfo udi_obdinfo; struct resource_list udi_rl; }; struct upa_softc { struct resource *sc_res[UPA_NREG]; bus_space_tag_t sc_bt[UPA_NREG]; bus_space_handle_t sc_bh[UPA_NREG]; uint32_t sc_ign; int sc_nrange; struct upa_ranges *sc_ranges; }; #define UPA_READ(sc, reg, off) \ bus_space_read_8((sc)->sc_bt[(reg)], (sc)->sc_bh[(reg)], (off)) #define UPA_WRITE(sc, reg, off, val) \ bus_space_write_8((sc)->sc_bt[(reg)], (sc)->sc_bh[(reg)], (off), (val)) static device_probe_t upa_probe; static device_attach_t upa_attach; static bus_print_child_t upa_print_child; static bus_probe_nomatch_t upa_probe_nomatch; static bus_alloc_resource_t upa_alloc_resource; static bus_adjust_resource_t upa_adjust_resource; static bus_setup_intr_t upa_setup_intr; static bus_get_resource_list_t upa_get_resource_list; static ofw_bus_get_devinfo_t upa_get_devinfo; static void upa_intr_enable(void *); static void upa_intr_disable(void *); static void upa_intr_assign(void *); static struct upa_devinfo *upa_setup_dinfo(device_t, struct upa_softc *, phandle_t, uint32_t); static void upa_destroy_dinfo(struct upa_devinfo *); static int upa_print_res(struct upa_devinfo *); static device_method_t upa_methods[] = { /* Device interface */ DEVMETHOD(device_probe, upa_probe), DEVMETHOD(device_attach, upa_attach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* Bus interface */ DEVMETHOD(bus_print_child, upa_print_child), DEVMETHOD(bus_probe_nomatch, upa_probe_nomatch), DEVMETHOD(bus_read_ivar, bus_generic_read_ivar), DEVMETHOD(bus_write_ivar, bus_generic_write_ivar), DEVMETHOD(bus_alloc_resource, upa_alloc_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_adjust_resource, upa_adjust_resource), DEVMETHOD(bus_release_resource, bus_generic_rl_release_resource), DEVMETHOD(bus_setup_intr, upa_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), DEVMETHOD(bus_get_resource_list, upa_get_resource_list), DEVMETHOD(bus_child_pnpinfo_str, ofw_bus_gen_child_pnpinfo_str), /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_devinfo, upa_get_devinfo), DEVMETHOD(ofw_bus_get_compat, ofw_bus_gen_get_compat), DEVMETHOD(ofw_bus_get_model, ofw_bus_gen_get_model), DEVMETHOD(ofw_bus_get_name, ofw_bus_gen_get_name), DEVMETHOD(ofw_bus_get_node, ofw_bus_gen_get_node), DEVMETHOD(ofw_bus_get_type, ofw_bus_gen_get_type), DEVMETHOD_END }; static devclass_t upa_devclass; DEFINE_CLASS_0(upa, upa_driver, upa_methods, sizeof(struct upa_softc)); EARLY_DRIVER_MODULE(upa, nexus, upa_driver, upa_devclass, 0, 0, BUS_PASS_BUS); static const struct intr_controller upa_ic = { upa_intr_enable, upa_intr_disable, upa_intr_assign, /* The interrupts are pulse type and thus automatically cleared. */ NULL }; struct upa_icarg { struct upa_softc *uica_sc; u_int uica_imr; }; static int upa_probe(device_t dev) { const char* compat; compat = ofw_bus_get_compat(dev); if (compat != NULL && strcmp(ofw_bus_get_name(dev), "upa") == 0 && strcmp(compat, "upa64s") == 0) { device_set_desc(dev, "UPA bridge"); return (BUS_PROBE_DEFAULT); } return (ENXIO); } static int upa_attach(device_t dev) { struct upa_devinfo *udi; struct upa_icarg *uica; struct upa_softc *sc; phandle_t child, node; device_t cdev; uint32_t portid; int i, imr, j, rid; #if 1 device_t *children, schizo; rman_res_t scount, sstart, ucount, ustart; int nchildren; #endif sc = device_get_softc(dev); node = ofw_bus_get_node(dev); for (i = UPA_CFG; i <= UPA_IMR2; i++) { rid = i; /* * The UPA_IMR{1,2} resources are shared with that of the * Schizo PCI bus B CSR bank. */ #if 0 sc->sc_res[i] = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, ((i == UPA_IMR1 || i == UPA_IMR2) ? RF_SHAREABLE : 0) | RF_ACTIVE); if (sc->sc_res[i] == NULL) { device_printf(dev, "could not allocate resource %d\n", i); goto fail; } sc->sc_bt[i] = rman_get_bustag(sc->sc_res[i]); sc->sc_bh[i] = rman_get_bushandle(sc->sc_res[i]); #else /* * Workaround for the fact that rman(9) only allows to * share resources of the same size. */ if (i == UPA_IMR1 || i == UPA_IMR2) { if (bus_get_resource(dev, SYS_RES_MEMORY, i, &ustart, &ucount) != 0) { device_printf(dev, "could not determine UPA resource\n"); goto fail; } if (device_get_children(device_get_parent(dev), &children, &nchildren) != 0) { device_printf(dev, "could not get children\n"); goto fail; } schizo = NULL; for (j = 0; j < nchildren; j++) { if (ofw_bus_get_type(children[j]) != NULL && strcmp(ofw_bus_get_type(children[j]), "pci") == 0 && ofw_bus_get_compat(children[j]) != NULL && strcmp(ofw_bus_get_compat(children[j]), "pci108e,8001") == 0 && ((bus_get_resource_start(children[j], SYS_RES_MEMORY, 0) >> 20) & 1) == 1) { schizo = children[j]; break; } } free(children, M_TEMP); if (schizo == NULL) { device_printf(dev, "could not find Schizo\n"); goto fail; } if (bus_get_resource(schizo, SYS_RES_MEMORY, 0, &sstart, &scount) != 0) { device_printf(dev, "could not determine Schizo resource\n"); goto fail; } sc->sc_res[i] = bus_alloc_resource(dev, SYS_RES_MEMORY, &rid, sstart, sstart + scount - 1, scount, RF_SHAREABLE | RF_ACTIVE); } else sc->sc_res[i] = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->sc_res[i] == NULL) { device_printf(dev, "could not allocate resource %d\n", i); goto fail; } sc->sc_bt[i] = rman_get_bustag(sc->sc_res[i]); sc->sc_bh[i] = rman_get_bushandle(sc->sc_res[i]); if (i == UPA_IMR1 || i == UPA_IMR2) bus_space_subregion(sc->sc_bt[i], sc->sc_bh[i], ustart - sstart, ucount, &sc->sc_bh[i]); #endif } if (OF_getprop(node, "portid", &sc->sc_ign, sizeof(sc->sc_ign)) == -1) { device_printf(dev, "could not determine IGN\n"); goto fail; } - sc->sc_nrange = OF_getprop_alloc(node, "ranges", sizeof(*sc->sc_ranges), + sc->sc_nrange = OF_getprop_alloc_multi(node, "ranges", sizeof(*sc->sc_ranges), (void **)&sc->sc_ranges); if (sc->sc_nrange == -1) { device_printf(dev, "could not determine ranges\n"); goto fail; } /* * Hunt through all the interrupt mapping regs and register our * interrupt controller for the corresponding interrupt vectors. * We do this early in order to be able to catch stray interrupts. */ for (i = UPA_INO_BASE; i <= UPA_INO_MAX; i++) { imr = 0; for (j = UPA_IMR1; j <= UPA_IMR2; j++) { if (INTVEC(UPA_READ(sc, j, 0x0)) == INTMAP_VEC(sc->sc_ign, i)) { imr = j; break; } } if (imr == 0) continue; uica = malloc(sizeof(*uica), M_DEVBUF, M_NOWAIT); if (uica == NULL) panic("%s: could not allocate interrupt controller " "argument", __func__); uica->uica_sc = sc; uica->uica_imr = imr; #ifdef UPA_DEBUG device_printf(dev, "intr map (INO %d) IMR%d: %#lx\n", i, imr, (u_long)UPA_READ(sc, imr, 0x0)); #endif j = intr_controller_register(INTMAP_VEC(sc->sc_ign, i), &upa_ic, uica); if (j != 0) device_printf(dev, "could not register interrupt " "controller for INO %d (%d)\n", i, j); } /* Make sure the power level is appropriate for normal operation. */ if (UPA_READ(sc, UPA_CFG, UPA_CFG_IF) != UPA_CFG_IF_POK) { if (bootverbose) device_printf(dev, "applying power\n"); UPA_WRITE(sc, UPA_CFG, UPA_CFG_ESTAR, UPA_CFG_ESTAR_SPEED_1_2); UPA_WRITE(sc, UPA_CFG, UPA_CFG_ESTAR, UPA_CFG_ESTAR_SPEED_FULL); (void)UPA_READ(sc, UPA_CFG, UPA_CFG_ESTAR); UPA_WRITE(sc, UPA_CFG, UPA_CFG_IF, UPA_CFG_IF_POK_RST); (void)UPA_READ(sc, UPA_CFG, UPA_CFG_IF); DELAY(20000); UPA_WRITE(sc, UPA_CFG, UPA_CFG_IF, UPA_CFG_IF_POK); (void)UPA_READ(sc, UPA_CFG, UPA_CFG_IF); } for (child = OF_child(node); child != 0; child = OF_peer(child)) { /* * The `upa-portid' properties of the children are used as * index for the interrupt mapping registers. * The `upa-portid' properties are also used to make up the * INOs of the children as the values contained in their * `interrupts' properties are bogus. */ if (OF_getprop(child, "upa-portid", &portid, sizeof(portid)) == -1) { device_printf(dev, "could not determine upa-portid of child 0x%lx\n", (unsigned long)child); continue; } if (portid > 1) { device_printf(dev, "upa-portid %d of child 0x%lx invalid\n", portid, (unsigned long)child); continue; } if ((udi = upa_setup_dinfo(dev, sc, child, portid)) == NULL) continue; if ((cdev = device_add_child(dev, NULL, -1)) == NULL) { device_printf(dev, "<%s>: device_add_child failed\n", udi->udi_obdinfo.obd_name); upa_destroy_dinfo(udi); continue; } device_set_ivars(cdev, udi); } return (bus_generic_attach(dev)); fail: for (i = UPA_CFG; i <= UPA_IMR2 && sc->sc_res[i] != NULL; i++) bus_release_resource(dev, SYS_RES_MEMORY, rman_get_rid(sc->sc_res[i]), sc->sc_res[i]); return (ENXIO); } static int upa_print_child(device_t dev, device_t child) { int rv; rv = bus_print_child_header(dev, child); rv += upa_print_res(device_get_ivars(child)); rv += bus_print_child_footer(dev, child); return (rv); } static void upa_probe_nomatch(device_t dev, device_t child) { const char *type; device_printf(dev, "<%s>", ofw_bus_get_name(child)); upa_print_res(device_get_ivars(child)); type = ofw_bus_get_type(child); printf(" type %s (no driver attached)\n", type != NULL ? type : "unknown"); } static struct resource * upa_alloc_resource(device_t dev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct resource_list *rl; struct resource_list_entry *rle; struct upa_softc *sc; struct resource *rv; bus_addr_t cend, cstart; int i, isdefault, passthrough; isdefault = RMAN_IS_DEFAULT_RANGE(start, end); passthrough = (device_get_parent(child) != dev); sc = device_get_softc(dev); rl = BUS_GET_RESOURCE_LIST(dev, child); rle = NULL; switch (type) { case SYS_RES_IRQ: return (resource_list_alloc(rl, dev, child, type, rid, start, end, count, flags)); case SYS_RES_MEMORY: if (!passthrough) { rle = resource_list_find(rl, type, *rid); if (rle == NULL) return (NULL); if (rle->res != NULL) panic("%s: resource entry is busy", __func__); if (isdefault) { start = rle->start; count = ulmax(count, rle->count); end = ulmax(rle->end, start + count - 1); } } for (i = 0; i < sc->sc_nrange; i++) { cstart = sc->sc_ranges[i].child; cend = cstart + sc->sc_ranges[i].size - 1; if (start < cstart || start > cend) continue; if (end < cstart || end > cend) return (NULL); start += sc->sc_ranges[i].parent - cstart; end += sc->sc_ranges[i].parent - cstart; rv = bus_generic_alloc_resource(dev, child, type, rid, start, end, count, flags); if (!passthrough) rle->res = rv; return (rv); } /* FALLTHROUGH */ default: return (NULL); } } static void upa_intr_enable(void *arg) { struct intr_vector *iv = arg; struct upa_icarg *uica = iv->iv_icarg; UPA_WRITE(uica->uica_sc, uica->uica_imr, 0x0, INTMAP_ENABLE(iv->iv_vec, iv->iv_mid)); (void)UPA_READ(uica->uica_sc, uica->uica_imr, 0x0); } static void upa_intr_disable(void *arg) { struct intr_vector *iv = arg; struct upa_icarg *uica = iv->iv_icarg; UPA_WRITE(uica->uica_sc, uica->uica_imr, 0x0, iv->iv_vec); (void)UPA_READ(uica->uica_sc, uica->uica_imr, 0x0); } static void upa_intr_assign(void *arg) { struct intr_vector *iv = arg; struct upa_icarg *uica = iv->iv_icarg; UPA_WRITE(uica->uica_sc, uica->uica_imr, 0x0, INTMAP_TID( UPA_READ(uica->uica_sc, uica->uica_imr, 0x0), iv->iv_mid)); (void)UPA_READ(uica->uica_sc, uica->uica_imr, 0x0); } static int upa_setup_intr(device_t dev, device_t child, struct resource *ires, int flags, driver_filter_t *filt, driver_intr_t *func, void *arg, void **cookiep) { struct upa_softc *sc; u_long vec; sc = device_get_softc(dev); /* * Make sure the vector is fully specified and we registered * our interrupt controller for it. */ vec = rman_get_start(ires); if (INTIGN(vec) != sc->sc_ign || intr_vectors[vec].iv_ic != &upa_ic) { device_printf(dev, "invalid interrupt vector 0x%lx\n", vec); return (EINVAL); } return (bus_generic_setup_intr(dev, child, ires, flags, filt, func, arg, cookiep)); } static int upa_adjust_resource(device_t bus __unused, device_t child __unused, int type __unused, struct resource *r __unused, rman_res_t start __unused, rman_res_t end __unused) { return (ENXIO); } static struct resource_list * upa_get_resource_list(device_t dev, device_t child) { struct upa_devinfo *udi; udi = device_get_ivars(child); return (&udi->udi_rl); } static const struct ofw_bus_devinfo * upa_get_devinfo(device_t dev, device_t child) { struct upa_devinfo *udi; udi = device_get_ivars(child); return (&udi->udi_obdinfo); } static struct upa_devinfo * upa_setup_dinfo(device_t dev, struct upa_softc *sc, phandle_t node, uint32_t portid) { struct upa_devinfo *udi; struct upa_regs *reg; uint32_t intr; int i, nreg; udi = malloc(sizeof(*udi), M_DEVBUF, M_WAITOK | M_ZERO); if (ofw_bus_gen_setup_devinfo(&udi->udi_obdinfo, node) != 0) { free(udi, M_DEVBUF); return (NULL); } resource_list_init(&udi->udi_rl); - nreg = OF_getprop_alloc(node, "reg", sizeof(*reg), (void **)®); + nreg = OF_getprop_alloc_multi(node, "reg", sizeof(*reg), (void **)®); if (nreg == -1) { device_printf(dev, "<%s>: incomplete\n", udi->udi_obdinfo.obd_name); goto fail; } for (i = 0; i < nreg; i++) resource_list_add(&udi->udi_rl, SYS_RES_MEMORY, i, reg[i].phys, reg[i].phys + reg[i].size - 1, reg[i].size); OF_prop_free(reg); intr = INTMAP_VEC(sc->sc_ign, (UPA_INO_BASE + portid)); resource_list_add(&udi->udi_rl, SYS_RES_IRQ, 0, intr, intr, 1); return (udi); fail: upa_destroy_dinfo(udi); return (NULL); } static void upa_destroy_dinfo(struct upa_devinfo *dinfo) { resource_list_free(&dinfo->udi_rl); ofw_bus_gen_destroy_devinfo(&dinfo->udi_obdinfo); free(dinfo, M_DEVBUF); } static int upa_print_res(struct upa_devinfo *udi) { int rv; rv = 0; rv += resource_list_print_type(&udi->udi_rl, "mem", SYS_RES_MEMORY, "%#jx"); rv += resource_list_print_type(&udi->udi_rl, "irq", SYS_RES_IRQ, "%jd"); return (rv); }