/*- * 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 #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 */ break; } 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); 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); free(function_name, M_OFWPROP); return (ENXIO); } free(function_name, M_OFWPROP); len = OF_getprop_alloc(node, "amlogic,pull", sizeof(char), (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); free(pull, M_OFWPROP); return (ENXIO); } } free(pull, M_OFWPROP); /* * 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); 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); } free(pins, M_OFWPROP); 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);