diff --git a/sys/arm/ti/am335x/am3359_cppi41.c b/sys/arm/ti/am335x/am3359_cppi41.c index dd5e668776ee..57d049cde8ea 100644 --- a/sys/arm/ti/am335x/am3359_cppi41.c +++ b/sys/arm/ti/am335x/am3359_cppi41.c @@ -1,190 +1,188 @@ /*- * Copyright (c) 2019 Emmanuel Vadot * * Copyright (c) 2020 Oskar Holmlund * * 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$ */ /* Based on sys/arm/ti/ti_sysc.c */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if 0 #define DPRINTF(dev, msg...) device_printf(dev, msg) #else #define DPRINTF(dev, msg...) #endif struct ti_am3359_cppi41_softc { device_t dev; struct syscon * syscon; struct resource * res[4]; bus_space_tag_t bst; bus_space_handle_t bsh; struct mtx mtx; }; static struct resource_spec ti_am3359_cppi41_res_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE | RF_SHAREABLE }, { SYS_RES_MEMORY, 1, RF_ACTIVE | RF_SHAREABLE }, { SYS_RES_MEMORY, 2, RF_ACTIVE | RF_SHAREABLE }, { SYS_RES_MEMORY, 3, RF_ACTIVE | RF_SHAREABLE }, { -1, 0 } }; /* Device */ static struct ofw_compat_data compat_data[] = { { "ti,am3359-cppi41", 1 }, { NULL, 0 } }; static int ti_am3359_cppi41_write_4(device_t dev, bus_addr_t addr, uint32_t val) { struct ti_am3359_cppi41_softc *sc; sc = device_get_softc(dev); DPRINTF(sc->dev, "offset=%lx write %x\n", addr, val); mtx_lock(&sc->mtx); bus_space_write_4(sc->bst, sc->bsh, addr, val); mtx_unlock(&sc->mtx); return (0); } static uint32_t ti_am3359_cppi41_read_4(device_t dev, bus_addr_t addr) { struct ti_am3359_cppi41_softc *sc; uint32_t val; sc = device_get_softc(dev); mtx_lock(&sc->mtx); val = bus_space_read_4(sc->bst, sc->bsh, addr); mtx_unlock(&sc->mtx); DPRINTF(sc->dev, "offset=%lx Read %x\n", addr, val); return (val); } /* device interface */ static int ti_am3359_cppi41_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, "TI AM3359 CPPI 41"); return(BUS_PROBE_DEFAULT); } static int ti_am3359_cppi41_attach(device_t dev) { struct ti_am3359_cppi41_softc *sc; - phandle_t node; uint32_t reg, reset_bit, timeout=10; uint64_t sysc_address; device_t parent; sc = device_get_softc(dev); sc->dev = dev; if (bus_alloc_resources(dev, ti_am3359_cppi41_res_spec, sc->res)) { device_printf(sc->dev, "Cant allocate resources\n"); return (ENXIO); } sc->dev = dev; sc->bst = rman_get_bustag(sc->res[0]); sc->bsh = rman_get_bushandle(sc->res[0]); mtx_init(&sc->mtx, device_get_nameunit(sc->dev), NULL, MTX_DEF); - node = ofw_bus_get_node(sc->dev); /* variant of am335x_usbss.c */ DPRINTF(dev, "-- RESET USB --\n"); parent = device_get_parent(dev); reset_bit = ti_sysc_get_soft_reset_bit(parent); if (reset_bit == 0) { DPRINTF(dev, "Dont have reset bit\n"); return (0); } sysc_address = ti_sysc_get_sysc_address_offset_host(parent); DPRINTF(dev, "sysc_address %x\n", sysc_address); ti_am3359_cppi41_write_4(dev, sysc_address, reset_bit); DELAY(100); reg = ti_am3359_cppi41_read_4(dev, sysc_address); if ((reg & reset_bit) && timeout--) { DPRINTF(dev, "Reset still ongoing - wait a little bit longer\n"); DELAY(100); reg = ti_am3359_cppi41_read_4(dev, sysc_address); } if (timeout == 0) device_printf(dev, "USB Reset timeout\n"); return (0); } static device_method_t ti_am3359_cppi41_methods[] = { DEVMETHOD(device_probe, ti_am3359_cppi41_probe), DEVMETHOD(device_attach, ti_am3359_cppi41_attach), DEVMETHOD_END }; DEFINE_CLASS_1(ti_am3359_cppi41, ti_am3359_cppi41_driver, ti_am3359_cppi41_methods,sizeof(struct ti_am3359_cppi41_softc), simplebus_driver); static devclass_t ti_am3359_cppi41_devclass; EARLY_DRIVER_MODULE(ti_am3359_cppi41, simplebus, ti_am3359_cppi41_driver, ti_am3359_cppi41_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); MODULE_VERSION(ti_am3359_cppi41, 1); MODULE_DEPEND(ti_am3359_cppi41, ti_sysc, 1, 1, 1); diff --git a/sys/arm/ti/am335x/am335x_musb.c b/sys/arm/ti/am335x/am335x_musb.c index d868e8a64f8b..2143338a39f7 100644 --- a/sys/arm/ti/am335x/am335x_musb.c +++ b/sys/arm/ti/am335x/am335x_musb.c @@ -1,462 +1,461 @@ /*- * Copyright (c) 2013 Oleksandr Tymoshenko * * 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 #include #include #include #define USB_DEBUG_VAR usbssdebug #include #include #include #include #include #include #include #include #include #include "syscon_if.h" #define USBCTRL_REV 0x00 #define USBCTRL_CTRL 0x14 #define USBCTRL_STAT 0x18 #define USBCTRL_IRQ_STAT0 0x30 #define IRQ_STAT0_RXSHIFT 16 #define IRQ_STAT0_TXSHIFT 0 #define USBCTRL_IRQ_STAT1 0x34 #define IRQ_STAT1_DRVVBUS (1 << 8) #define USBCTRL_INTEN_SET0 0x38 #define USBCTRL_INTEN_SET1 0x3C #define USBCTRL_INTEN_USB_ALL 0x1ff #define USBCTRL_INTEN_USB_SOF (1 << 3) #define USBCTRL_INTEN_CLR0 0x40 #define USBCTRL_INTEN_CLR1 0x44 #define USBCTRL_UTMI 0xE0 #define USBCTRL_UTMI_FSDATAEXT (1 << 1) #define USBCTRL_MODE 0xE8 #define USBCTRL_MODE_IDDIG (1 << 8) #define USBCTRL_MODE_IDDIGMUX (1 << 7) /* USBSS resource + 2 MUSB ports */ #define RES_USBCORE 0 #define RES_USBCTRL 1 #define USB_WRITE4(sc, idx, reg, val) do { \ bus_write_4((sc)->sc_mem_res[idx], (reg), (val)); \ } while (0) #define USB_READ4(sc, idx, reg) bus_read_4((sc)->sc_mem_res[idx], (reg)) #define USBCTRL_WRITE4(sc, reg, val) \ USB_WRITE4((sc), RES_USBCTRL, (reg), (val)) #define USBCTRL_READ4(sc, reg) \ USB_READ4((sc), RES_USBCTRL, (reg)) static struct resource_spec am335x_musbotg_mem_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_MEMORY, 1, RF_ACTIVE }, { -1, 0, 0 } }; #ifdef USB_DEBUG static int usbssdebug = 0; static SYSCTL_NODE(_hw_usb, OID_AUTO, am335x_usbss, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "AM335x USBSS"); SYSCTL_INT(_hw_usb_am335x_usbss, OID_AUTO, debug, CTLFLAG_RW, &usbssdebug, 0, "Debug level"); #endif static device_probe_t musbotg_probe; static device_attach_t musbotg_attach; static device_detach_t musbotg_detach; struct musbotg_super_softc { struct musbotg_softc sc_otg; struct resource *sc_mem_res[2]; int sc_irq_rid; struct syscon *syscon; }; static void musbotg_vbus_poll(struct musbotg_super_softc *sc) { uint32_t stat; if (sc->sc_otg.sc_mode == MUSB2_DEVICE_MODE) musbotg_vbus_interrupt(&sc->sc_otg, 1); else { stat = USBCTRL_READ4(sc, USBCTRL_STAT); musbotg_vbus_interrupt(&sc->sc_otg, stat & 1); } } /* * Arg to musbotg_clocks_on and musbot_clocks_off is * a uint32_t * pointing to the SCM register offset. */ static uint32_t USB_CTRL[] = {SCM_USB_CTRL0, SCM_USB_CTRL1}; static void musbotg_clocks_on(void *arg) { struct musbotg_softc *sc; struct musbotg_super_softc *ssc; uint32_t reg; sc = arg; ssc = sc->sc_platform_data; reg = SYSCON_READ_4(ssc->syscon, USB_CTRL[sc->sc_id]); reg &= ~3; /* Enable power */ reg |= 1 << 19; /* VBUS detect enable */ reg |= 1 << 20; /* Session end enable */ SYSCON_WRITE_4(ssc->syscon, USB_CTRL[sc->sc_id], reg); } static void musbotg_clocks_off(void *arg) { struct musbotg_softc *sc; struct musbotg_super_softc *ssc; uint32_t reg; sc = arg; ssc = sc->sc_platform_data; /* Disable power to PHY */ reg = SYSCON_READ_4(ssc->syscon, USB_CTRL[sc->sc_id]); SYSCON_WRITE_4(ssc->syscon, USB_CTRL[sc->sc_id], reg | 3); } static void musbotg_ep_int_set(struct musbotg_softc *sc, int ep, int on) { struct musbotg_super_softc *ssc = sc->sc_platform_data; uint32_t epmask; epmask = ((1 << ep) << IRQ_STAT0_RXSHIFT); epmask |= ((1 << ep) << IRQ_STAT0_TXSHIFT); if (on) USBCTRL_WRITE4(ssc, USBCTRL_INTEN_SET0, epmask); else USBCTRL_WRITE4(ssc, USBCTRL_INTEN_CLR0, epmask); } static void musbotg_wrapper_interrupt(void *arg) { struct musbotg_softc *sc = arg; struct musbotg_super_softc *ssc = sc->sc_platform_data; uint32_t stat, stat0, stat1; stat = USBCTRL_READ4(ssc, USBCTRL_STAT); stat0 = USBCTRL_READ4(ssc, USBCTRL_IRQ_STAT0); stat1 = USBCTRL_READ4(ssc, USBCTRL_IRQ_STAT1); if (stat0) USBCTRL_WRITE4(ssc, USBCTRL_IRQ_STAT0, stat0); if (stat1) USBCTRL_WRITE4(ssc, USBCTRL_IRQ_STAT1, stat1); DPRINTFN(4, "port%d: stat0=%08x stat1=%08x, stat=%08x\n", sc->sc_id, stat0, stat1, stat); if (stat1 & IRQ_STAT1_DRVVBUS) musbotg_vbus_interrupt(sc, stat & 1); musbotg_interrupt(arg, ((stat0 >> 16) & 0xffff), stat0 & 0xffff, stat1 & 0xff); } static int musbotg_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "ti,musb-am33xx")) return (ENXIO); device_set_desc(dev, "TI AM33xx integrated USB OTG controller"); return (BUS_PROBE_DEFAULT); } static int musbotg_attach(device_t dev) { struct musbotg_super_softc *sc = device_get_softc(dev); char mode[16]; int err; uint32_t reg; phandle_t opp_table; clk_t clk_usbotg_fck; sc->sc_otg.sc_id = device_get_unit(dev); /* FIXME: The devicetree needs to be updated to get a handle to the gate * usbotg_fck@47c. see TRM 8.1.12.2 CM_WKUP CM_CLKDCOLDO_DPLL_PER. */ err = clk_get_by_name(dev, "usbotg_fck@47c", &clk_usbotg_fck); if (err) { device_printf(dev, "Can not find usbotg_fck@47c\n"); return (ENXIO); } err = clk_enable(clk_usbotg_fck); if (err) { device_printf(dev, "Can not enable usbotg_fck@47c\n"); return (ENXIO); } /* FIXME: For now; Go and kidnap syscon from opp-table */ opp_table = OF_finddevice("/opp-table"); if (opp_table == -1) { device_printf(dev, "Cant find /opp-table\n"); return (ENXIO); } if (!OF_hasprop(opp_table, "syscon")) { device_printf(dev, "/opp-table missing syscon property\n"); return (ENXIO); } err = syscon_get_by_ofw_property(dev, opp_table, "syscon", &sc->syscon); if (err) { device_printf(dev, "Failed to get syscon\n"); return (ENXIO); } /* Request the memory resources */ err = bus_alloc_resources(dev, am335x_musbotg_mem_spec, sc->sc_mem_res); if (err) { device_printf(dev, "Error: could not allocate mem resources\n"); return (ENXIO); } /* Request the IRQ resources */ sc->sc_otg.sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->sc_irq_rid, RF_ACTIVE); if (sc->sc_otg.sc_irq_res == NULL) { device_printf(dev, "Error: could not allocate irq resources\n"); return (ENXIO); } /* setup MUSB OTG USB controller interface softc */ sc->sc_otg.sc_clocks_on = &musbotg_clocks_on; sc->sc_otg.sc_clocks_off = &musbotg_clocks_off; sc->sc_otg.sc_clocks_arg = &sc->sc_otg; sc->sc_otg.sc_ep_int_set = musbotg_ep_int_set; /* initialise some bus fields */ sc->sc_otg.sc_bus.parent = dev; sc->sc_otg.sc_bus.devices = sc->sc_otg.sc_devices; sc->sc_otg.sc_bus.devices_max = MUSB2_MAX_DEVICES; sc->sc_otg.sc_bus.dma_bits = 32; /* get all DMA memory */ if (usb_bus_mem_alloc_all(&sc->sc_otg.sc_bus, USB_GET_DMA_TAG(dev), NULL)) { device_printf(dev, "Failed allocate bus mem for musb\n"); return (ENOMEM); } sc->sc_otg.sc_io_res = sc->sc_mem_res[RES_USBCORE]; sc->sc_otg.sc_io_tag = rman_get_bustag(sc->sc_otg.sc_io_res); sc->sc_otg.sc_io_hdl = rman_get_bushandle(sc->sc_otg.sc_io_res); sc->sc_otg.sc_io_size = rman_get_size(sc->sc_otg.sc_io_res); sc->sc_otg.sc_bus.bdev = device_add_child(dev, "usbus", -1); if (!(sc->sc_otg.sc_bus.bdev)) { device_printf(dev, "No busdev for musb\n"); goto error; } device_set_ivars(sc->sc_otg.sc_bus.bdev, &sc->sc_otg.sc_bus); err = bus_setup_intr(dev, sc->sc_otg.sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, NULL, (driver_intr_t *)musbotg_wrapper_interrupt, &sc->sc_otg, &sc->sc_otg.sc_intr_hdl); if (err) { sc->sc_otg.sc_intr_hdl = NULL; device_printf(dev, "Failed to setup interrupt for musb\n"); goto error; } sc->sc_otg.sc_platform_data = sc; if (OF_getprop(ofw_bus_get_node(dev), "dr_mode", mode, sizeof(mode)) > 0) { if (strcasecmp(mode, "host") == 0) sc->sc_otg.sc_mode = MUSB2_HOST_MODE; else sc->sc_otg.sc_mode = MUSB2_DEVICE_MODE; } else { /* Beaglebone defaults: USB0 device, USB1 HOST. */ if (sc->sc_otg.sc_id == 0) sc->sc_otg.sc_mode = MUSB2_DEVICE_MODE; else sc->sc_otg.sc_mode = MUSB2_HOST_MODE; } /* * software-controlled function */ if (sc->sc_otg.sc_mode == MUSB2_HOST_MODE) { reg = USBCTRL_READ4(sc, USBCTRL_MODE); reg |= USBCTRL_MODE_IDDIGMUX; reg &= ~USBCTRL_MODE_IDDIG; USBCTRL_WRITE4(sc, USBCTRL_MODE, reg); USBCTRL_WRITE4(sc, USBCTRL_UTMI, USBCTRL_UTMI_FSDATAEXT); } else { reg = USBCTRL_READ4(sc, USBCTRL_MODE); reg |= USBCTRL_MODE_IDDIGMUX; reg |= USBCTRL_MODE_IDDIG; USBCTRL_WRITE4(sc, USBCTRL_MODE, reg); } reg = USBCTRL_INTEN_USB_ALL & ~USBCTRL_INTEN_USB_SOF; USBCTRL_WRITE4(sc, USBCTRL_INTEN_SET1, reg); USBCTRL_WRITE4(sc, USBCTRL_INTEN_CLR0, 0xffffffff); err = musbotg_init(&sc->sc_otg); if (!err) err = device_probe_and_attach(sc->sc_otg.sc_bus.bdev); if (err) goto error; /* poll VBUS one time */ musbotg_vbus_poll(sc); return (0); error: musbotg_detach(dev); return (ENXIO); } static int musbotg_detach(device_t dev) { struct musbotg_super_softc *sc = device_get_softc(dev); - int err; /* during module unload there are lots of children leftover */ device_delete_children(dev); if (sc->sc_otg.sc_irq_res && sc->sc_otg.sc_intr_hdl) { /* * only call musbotg_uninit() after musbotg_init() */ musbotg_uninit(&sc->sc_otg); - err = bus_teardown_intr(dev, sc->sc_otg.sc_irq_res, + bus_teardown_intr(dev, sc->sc_otg.sc_irq_res, sc->sc_otg.sc_intr_hdl); sc->sc_otg.sc_intr_hdl = NULL; } usb_bus_mem_free_all(&sc->sc_otg.sc_bus, NULL); /* Free resources if any */ if (sc->sc_mem_res[0]) bus_release_resources(dev, am335x_musbotg_mem_spec, sc->sc_mem_res); if (sc->sc_otg.sc_irq_res) bus_release_resource(dev, SYS_RES_IRQ, sc->sc_irq_rid, sc->sc_otg.sc_irq_res); return (0); } static device_method_t musbotg_methods[] = { /* Device interface */ DEVMETHOD(device_probe, musbotg_probe), DEVMETHOD(device_attach, musbotg_attach), DEVMETHOD(device_detach, musbotg_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD_END }; static driver_t musbotg_driver = { .name = "musbotg", .methods = musbotg_methods, .size = sizeof(struct musbotg_super_softc), }; static devclass_t musbotg_devclass; DRIVER_MODULE(musbotg, ti_sysc, musbotg_driver, musbotg_devclass, 0, 0); MODULE_DEPEND(musbotg, ti_sysc, 1, 1, 1); MODULE_DEPEND(musbotg, ti_am3359_cppi41, 1, 1, 1); MODULE_DEPEND(usbss, usb, 1, 1, 1); diff --git a/sys/arm/ti/am335x/am335x_usb_phy.c b/sys/arm/ti/am335x/am335x_usb_phy.c index 00e28122dcec..8fb54bd08f11 100644 --- a/sys/arm/ti/am335x/am335x_usb_phy.c +++ b/sys/arm/ti/am335x/am335x_usb_phy.c @@ -1,121 +1,119 @@ /*- * * Copyright (c) 2020 Oskar Holmlund * * 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 #include #include #include #include #include #define TI_AM335X_USB_PHY 1 #define TI_AM335X_USB_PHY_END 0 static struct ofw_compat_data compat_data[] = { { "ti,am335x-usb-phy", TI_AM335X_USB_PHY }, { NULL, TI_AM335X_USB_PHY_END } }; struct ti_usb_phy_softc { device_t dev; }; static int ti_usb_phy_probe(device_t dev); static int ti_usb_phy_attach(device_t dev); static int ti_usb_phy_detach(device_t dev); static int ti_usb_phy_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, "TI AM335x USB PHY"); if (!bootverbose) device_quiet(dev); return (BUS_PROBE_DEFAULT); } static int ti_usb_phy_attach(device_t dev) { struct ti_usb_phy_softc *sc; - phandle_t node; sc = device_get_softc(dev); sc->dev = dev; - node = ofw_bus_get_node(dev); /* FIXME: Add dev/extres/phy/ interface */ return (bus_generic_attach(dev)); } static int ti_usb_phy_detach(device_t dev) { return (EBUSY); } static device_method_t ti_usb_phy_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ti_usb_phy_probe), DEVMETHOD(device_attach, ti_usb_phy_attach), DEVMETHOD(device_detach, ti_usb_phy_detach), DEVMETHOD_END }; DEFINE_CLASS_1(ti_usb_phy, ti_usb_phy_driver, ti_usb_phy_methods, sizeof(struct ti_usb_phy_softc), simplebus_driver); static devclass_t ti_usb_phy_devclass; EARLY_DRIVER_MODULE(ti_usb_phy, simplebus, ti_usb_phy_driver, ti_usb_phy_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_FIRST); MODULE_VERSION(ti_usb_phy, 1); MODULE_DEPEND(ti_usb_phy, ti_sysc, 1, 1, 1); diff --git a/sys/arm/ti/clk/ti_clk_dpll.c b/sys/arm/ti/clk/ti_clk_dpll.c index ed1c78b19dcb..5a2109b3306d 100644 --- a/sys/arm/ti/clk/ti_clk_dpll.c +++ b/sys/arm/ti/clk/ti_clk_dpll.c @@ -1,340 +1,336 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2019 Emmanuel Vadot * * Copyright (c) 2020 Oskar Holmlund * * 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. * * based on sys/arm/allwinner/clkng/aw_clk_np.c * * $FreeBSD$ */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include "clkdev_if.h" /* * clknode for clocks matching the formula : * * clk = clkin * n / p * */ struct ti_dpll_clknode_sc { uint32_t ti_clkmode_offset; /* control */ uint8_t ti_clkmode_flags; uint32_t ti_idlest_offset; uint32_t ti_clksel_offset; /* mult-div1 */ struct ti_clk_factor n; /* ti_clksel_mult */ struct ti_clk_factor p; /* ti_clksel_div */ uint32_t ti_autoidle_offset; }; #define WRITE4(_clk, off, val) \ CLKDEV_WRITE_4(clknode_get_device(_clk), off, val) #define READ4(_clk, off, val) \ CLKDEV_READ_4(clknode_get_device(_clk), off, val) #define DEVICE_LOCK(_clk) \ CLKDEV_DEVICE_LOCK(clknode_get_device(_clk)) #define DEVICE_UNLOCK(_clk) \ CLKDEV_DEVICE_UNLOCK(clknode_get_device(_clk)) static int ti_dpll_clk_init(struct clknode *clk, device_t dev) { - struct ti_dpll_clknode_sc *sc; - - sc = clknode_get_softc(clk); - clknode_init_parent_idx(clk, 0); return (0); } /* helper to keep aw_clk_np_find_best "intact" */ static inline uint32_t ti_clk_factor_get_max(struct ti_clk_factor *factor) { uint32_t max; if (factor->flags & TI_CLK_FACTOR_FIXED) max = factor->value; else { max = (1 << factor->width); } return (max); } static inline uint32_t ti_clk_factor_get_min(struct ti_clk_factor *factor) { uint32_t min; if (factor->flags & TI_CLK_FACTOR_FIXED) min = factor->value; else if (factor->flags & TI_CLK_FACTOR_ZERO_BASED) min = 0; else if (factor->flags & TI_CLK_FACTOR_MIN_VALUE) min = factor->min_value; else min = 1; return (min); } static uint64_t ti_dpll_clk_find_best(struct ti_dpll_clknode_sc *sc, uint64_t fparent, uint64_t *fout, uint32_t *factor_n, uint32_t *factor_p) { uint64_t cur, best; uint32_t n, p, max_n, max_p, min_n, min_p; *factor_n = *factor_p = 0; max_n = ti_clk_factor_get_max(&sc->n); max_p = ti_clk_factor_get_max(&sc->p); min_n = ti_clk_factor_get_min(&sc->n); min_p = ti_clk_factor_get_min(&sc->p); for (p = min_p; p <= max_p; ) { for (n = min_n; n <= max_n; ) { cur = fparent * n / p; if (abs(*fout - cur) < abs(*fout - best)) { best = cur; *factor_n = n; *factor_p = p; } n++; } p++; } return (best); } static inline uint32_t ti_clk_get_factor(uint32_t val, struct ti_clk_factor *factor) { uint32_t factor_val; if (factor->flags & TI_CLK_FACTOR_FIXED) return (factor->value); factor_val = (val & factor->mask) >> factor->shift; if (!(factor->flags & TI_CLK_FACTOR_ZERO_BASED)) factor_val += 1; return (factor_val); } static inline uint32_t ti_clk_factor_get_value(struct ti_clk_factor *factor, uint32_t raw) { uint32_t val; if (factor->flags & TI_CLK_FACTOR_FIXED) return (factor->value); if (factor->flags & TI_CLK_FACTOR_ZERO_BASED) val = raw; else if (factor->flags & TI_CLK_FACTOR_MAX_VALUE && raw > factor->max_value) val = factor->max_value; else val = raw - 1; return (val); } static int ti_dpll_clk_set_freq(struct clknode *clk, uint64_t fparent, uint64_t *fout, int flags, int *stop) { struct ti_dpll_clknode_sc *sc; uint64_t cur, best; uint32_t val, n, p, best_n, best_p, timeout; sc = clknode_get_softc(clk); best = cur = 0; best = ti_dpll_clk_find_best(sc, fparent, fout, &best_n, &best_p); if ((flags & CLK_SET_DRYRUN) != 0) { *fout = best; *stop = 1; return (0); } if ((best < *fout) && (flags == CLK_SET_ROUND_DOWN)) { *stop = 1; return (ERANGE); } if ((best > *fout) && (flags == CLK_SET_ROUND_UP)) { *stop = 1; return (ERANGE); } DEVICE_LOCK(clk); /* 1 switch PLL to bypass mode */ WRITE4(clk, sc->ti_clkmode_offset, DPLL_EN_MN_BYPASS_MODE); /* 2 Ensure PLL is in bypass */ timeout = 10000; do { DELAY(10); READ4(clk, sc->ti_idlest_offset, &val); } while (!(val & ST_MN_BYPASS_MASK) && timeout--); if (timeout == 0) { DEVICE_UNLOCK(clk); return (ERANGE); // FIXME: Better return value? } /* 3 Set DPLL_MULT & DPLL_DIV bits */ READ4(clk, sc->ti_clksel_offset, &val); n = ti_clk_factor_get_value(&sc->n, best_n); p = ti_clk_factor_get_value(&sc->p, best_p); val &= ~sc->n.mask; val &= ~sc->p.mask; val |= n << sc->n.shift; val |= p << sc->p.shift; WRITE4(clk, sc->ti_clksel_offset, val); /* 4. configure M2, M4, M5 and M6 */ /* * FIXME: According to documentation M2/M4/M5/M6 can be set "later" * See note in TRM 8.1.6.7.1 */ /* 5 Switch over to lock mode */ WRITE4(clk, sc->ti_clkmode_offset, DPLL_EN_LOCK_MODE); /* 6 Ensure PLL is locked */ timeout = 10000; do { DELAY(10); READ4(clk, sc->ti_idlest_offset, &val); } while (!(val & ST_DPLL_CLK_MASK) && timeout--); DEVICE_UNLOCK(clk); if (timeout == 0) { return (ERANGE); // FIXME: Better return value? } *fout = best; *stop = 1; return (0); } static int ti_dpll_clk_recalc(struct clknode *clk, uint64_t *freq) { struct ti_dpll_clknode_sc *sc; uint32_t val, n, p; sc = clknode_get_softc(clk); DEVICE_LOCK(clk); READ4(clk, sc->ti_clksel_offset, &val); DEVICE_UNLOCK(clk); n = ti_clk_get_factor(val, &sc->n); p = ti_clk_get_factor(val, &sc->p); *freq = *freq * n / p; return (0); } static clknode_method_t ti_dpll_clknode_methods[] = { /* Device interface */ CLKNODEMETHOD(clknode_init, ti_dpll_clk_init), CLKNODEMETHOD(clknode_recalc_freq, ti_dpll_clk_recalc), CLKNODEMETHOD(clknode_set_freq, ti_dpll_clk_set_freq), CLKNODEMETHOD_END }; DEFINE_CLASS_1(ti_dpll_clknode, ti_dpll_clknode_class, ti_dpll_clknode_methods, sizeof(struct ti_dpll_clknode_sc), clknode_class); int ti_clknode_dpll_register(struct clkdom *clkdom, struct ti_clk_dpll_def *clkdef) { struct clknode *clk; struct ti_dpll_clknode_sc *sc; clk = clknode_create(clkdom, &ti_dpll_clknode_class, &clkdef->clkdef); if (clk == NULL) return (1); sc = clknode_get_softc(clk); sc->ti_clkmode_offset = clkdef->ti_clkmode_offset; sc->ti_clkmode_flags = clkdef->ti_clkmode_flags; sc->ti_idlest_offset = clkdef->ti_idlest_offset; sc->ti_clksel_offset = clkdef->ti_clksel_offset; sc->n.shift = clkdef->ti_clksel_mult.shift; sc->n.mask = clkdef->ti_clksel_mult.mask; sc->n.width = clkdef->ti_clksel_mult.width; sc->n.value = clkdef->ti_clksel_mult.value; sc->n.min_value = clkdef->ti_clksel_mult.min_value; sc->n.max_value = clkdef->ti_clksel_mult.max_value; sc->n.flags = clkdef->ti_clksel_mult.flags; sc->p.shift = clkdef->ti_clksel_div.shift; sc->p.mask = clkdef->ti_clksel_div.mask; sc->p.width = clkdef->ti_clksel_div.width; sc->p.value = clkdef->ti_clksel_div.value; sc->p.min_value = clkdef->ti_clksel_div.min_value; sc->p.max_value = clkdef->ti_clksel_div.max_value; sc->p.flags = clkdef->ti_clksel_div.flags; sc->ti_autoidle_offset = clkdef->ti_autoidle_offset; clknode_register(clkdom, clk); return (0); } diff --git a/sys/arm/ti/ti_mbox.c b/sys/arm/ti/ti_mbox.c index f77f2d9eafbf..864b245ad7d1 100644 --- a/sys/arm/ti/ti_mbox.c +++ b/sys/arm/ti/ti_mbox.c @@ -1,268 +1,265 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2013 Rui Paulo * 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 #include "mbox_if.h" #ifdef DEBUG #define DPRINTF(fmt, ...) do { \ printf("%s: ", __func__); \ printf(fmt, __VA_ARGS__); \ } while (0) #else #define DPRINTF(fmt, ...) #endif static device_probe_t ti_mbox_probe; static device_attach_t ti_mbox_attach; static device_detach_t ti_mbox_detach; static void ti_mbox_intr(void *); static int ti_mbox_read(device_t, int, uint32_t *); static int ti_mbox_write(device_t, int, uint32_t); struct ti_mbox_softc { struct mtx sc_mtx; struct resource *sc_mem_res; struct resource *sc_irq_res; void *sc_intr; bus_space_tag_t sc_bt; bus_space_handle_t sc_bh; }; #define TI_MBOX_LOCK(sc) mtx_lock(&(sc)->sc_mtx) #define TI_MBOX_UNLOCK(sc) mtx_unlock(&(sc)->sc_mtx) static device_method_t ti_mbox_methods[] = { DEVMETHOD(device_probe, ti_mbox_probe), DEVMETHOD(device_attach, ti_mbox_attach), DEVMETHOD(device_detach, ti_mbox_detach), DEVMETHOD(mbox_read, ti_mbox_read), DEVMETHOD(mbox_write, ti_mbox_write), DEVMETHOD_END }; static driver_t ti_mbox_driver = { "ti_mbox", ti_mbox_methods, sizeof(struct ti_mbox_softc) }; static devclass_t ti_mbox_devclass; DRIVER_MODULE(ti_mbox, simplebus, ti_mbox_driver, ti_mbox_devclass, 0, 0); MODULE_DEPEND(ti_mbox, ti_sysc, 1, 1, 1); static __inline uint32_t ti_mbox_reg_read(struct ti_mbox_softc *sc, uint16_t reg) { return (bus_space_read_4(sc->sc_bt, sc->sc_bh, reg)); } static __inline void ti_mbox_reg_write(struct ti_mbox_softc *sc, uint16_t reg, uint32_t val) { bus_space_write_4(sc->sc_bt, sc->sc_bh, reg, val); } static int ti_mbox_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_is_compatible(dev, "ti,omap4-mailbox")) { device_set_desc(dev, "TI System Mailbox"); return (BUS_PROBE_DEFAULT); } return (ENXIO); } static int ti_mbox_attach(device_t dev) { struct ti_mbox_softc *sc; int rid, delay, chan; uint32_t rev, sysconfig; if (ti_sysc_clock_enable(device_get_parent(dev)) != 0) { device_printf(dev, "could not enable MBOX clock\n"); return (ENXIO); } sc = device_get_softc(dev); rid = 0; mtx_init(&sc->sc_mtx, "TI mbox", NULL, MTX_DEF); sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->sc_mem_res == NULL) { device_printf(dev, "could not allocate memory resource\n"); return (ENXIO); } sc->sc_bt = rman_get_bustag(sc->sc_mem_res); sc->sc_bh = 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 == NULL) { device_printf(dev, "could not allocate interrupt resource\n"); ti_mbox_detach(dev); return (ENXIO); } if (bus_setup_intr(dev, sc->sc_irq_res, INTR_MPSAFE | INTR_TYPE_MISC, NULL, ti_mbox_intr, sc, &sc->sc_intr) != 0) { device_printf(dev, "unable to setup the interrupt handler\n"); ti_mbox_detach(dev); return (ENXIO); } /* * Reset the controller. */ sysconfig = ti_mbox_reg_read(sc, TI_MBOX_SYSCONFIG); DPRINTF("initial sysconfig %d\n", sysconfig); sysconfig |= TI_MBOX_SYSCONFIG_SOFTRST; delay = 100; while (ti_mbox_reg_read(sc, TI_MBOX_SYSCONFIG) & TI_MBOX_SYSCONFIG_SOFTRST) { delay--; DELAY(10); } if (delay == 0) { device_printf(dev, "controller reset failed\n"); ti_mbox_detach(dev); return (ENXIO); } /* * Enable smart idle mode. */ ti_mbox_reg_write(sc, TI_MBOX_SYSCONFIG, ti_mbox_reg_read(sc, TI_MBOX_SYSCONFIG) | TI_MBOX_SYSCONFIG_SMARTIDLE); rev = ti_mbox_reg_read(sc, ti_sysc_get_rev_address_offset_host(device_get_parent(dev))); DPRINTF("rev %d\n", rev); device_printf(dev, "revision %d.%d\n", (rev >> 8) & 0x4, rev & 0x40); /* * Enable message interrupts. */ for (chan = 0; chan < 8; chan++) ti_mbox_reg_write(sc, TI_MBOX_IRQENABLE_SET(chan), 1); return (0); } static int ti_mbox_detach(device_t dev) { struct ti_mbox_softc *sc; sc = device_get_softc(dev); if (sc->sc_intr) bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_intr); if (sc->sc_irq_res) bus_release_resource(dev, SYS_RES_IRQ, rman_get_rid(sc->sc_irq_res), sc->sc_irq_res); if (sc->sc_mem_res) bus_release_resource(dev, SYS_RES_MEMORY, rman_get_rid(sc->sc_mem_res), sc->sc_mem_res); return (0); } static void ti_mbox_intr(void *arg) { - struct ti_mbox_softc *sc; - - sc = arg; - DPRINTF("interrupt %p", sc); + DPRINTF("interrupt %p", arg); } static int ti_mbox_read(device_t dev, int chan, uint32_t *data) { struct ti_mbox_softc *sc; if (chan < 0 || chan > 7) return (EINVAL); sc = device_get_softc(dev); return (ti_mbox_reg_read(sc, TI_MBOX_MESSAGE(chan))); } static int ti_mbox_write(device_t dev, int chan, uint32_t data) { int limit = 500; struct ti_mbox_softc *sc; if (chan < 0 || chan > 7) return (EINVAL); sc = device_get_softc(dev); TI_MBOX_LOCK(sc); /* XXX implement interrupt method */ while (ti_mbox_reg_read(sc, TI_MBOX_FIFOSTATUS(chan)) == 1 && limit--) { DELAY(10); } if (limit == 0) { device_printf(dev, "FIFOSTAUS%d stuck\n", chan); TI_MBOX_UNLOCK(sc); return (EAGAIN); } ti_mbox_reg_write(sc, TI_MBOX_MESSAGE(chan), data); return (0); } diff --git a/sys/arm/ti/ti_pruss.c b/sys/arm/ti/ti_pruss.c index a8dc15ab80b0..296c1efa86fc 100644 --- a/sys/arm/ti/ti_pruss.c +++ b/sys/arm/ti/ti_pruss.c @@ -1,854 +1,850 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2013 Rui Paulo * Copyright (c) 2017 Manuel Stuehn * 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 #include #include #include #include #include #include #include #include #include #include #ifdef DEBUG #define DPRINTF(fmt, ...) do { \ printf("%s: ", __func__); \ printf(fmt, __VA_ARGS__); \ } while (0) #else #define DPRINTF(fmt, ...) #endif static d_open_t ti_pruss_irq_open; static d_read_t ti_pruss_irq_read; static d_poll_t ti_pruss_irq_poll; static device_probe_t ti_pruss_probe; static device_attach_t ti_pruss_attach; static device_detach_t ti_pruss_detach; static void ti_pruss_intr(void *); static d_open_t ti_pruss_open; static d_mmap_t ti_pruss_mmap; static void ti_pruss_irq_kqread_detach(struct knote *); static int ti_pruss_irq_kqevent(struct knote *, long); static d_kqfilter_t ti_pruss_irq_kqfilter; static void ti_pruss_privdtor(void *data); #define TI_PRUSS_PRU_IRQS 2 #define TI_PRUSS_HOST_IRQS 8 #define TI_PRUSS_IRQS (TI_PRUSS_HOST_IRQS+TI_PRUSS_PRU_IRQS) #define TI_PRUSS_EVENTS 64 #define NOT_SET_STR "NONE" #define TI_TS_ARRAY 16 struct ctl { size_t cnt; size_t idx; }; struct ts_ring_buf { struct ctl ctl; uint64_t ts[TI_TS_ARRAY]; }; struct ti_pruss_irqsc { struct mtx sc_mtx; struct cdev *sc_pdev; struct selinfo sc_selinfo; int8_t channel; int8_t last; int8_t event; bool enable; struct ts_ring_buf tstamps; }; static struct cdevsw ti_pruss_cdevirq = { .d_version = D_VERSION, .d_name = "ti_pruss_irq", .d_open = ti_pruss_irq_open, .d_read = ti_pruss_irq_read, .d_poll = ti_pruss_irq_poll, .d_kqfilter = ti_pruss_irq_kqfilter, }; struct ti_pruss_softc { struct mtx sc_mtx; struct resource *sc_mem_res; struct resource *sc_irq_res[TI_PRUSS_HOST_IRQS]; void *sc_intr[TI_PRUSS_HOST_IRQS]; struct ti_pruss_irqsc sc_irq_devs[TI_PRUSS_IRQS]; bus_space_tag_t sc_bt; bus_space_handle_t sc_bh; struct cdev *sc_pdev; struct selinfo sc_selinfo; bool sc_glob_irqen; }; static struct cdevsw ti_pruss_cdevsw = { .d_version = D_VERSION, .d_name = "ti_pruss", .d_open = ti_pruss_open, .d_mmap = ti_pruss_mmap, }; static device_method_t ti_pruss_methods[] = { DEVMETHOD(device_probe, ti_pruss_probe), DEVMETHOD(device_attach, ti_pruss_attach), DEVMETHOD(device_detach, ti_pruss_detach), DEVMETHOD_END }; static driver_t ti_pruss_driver = { "ti_pruss", ti_pruss_methods, sizeof(struct ti_pruss_softc) }; static devclass_t ti_pruss_devclass; DRIVER_MODULE(ti_pruss, simplebus, ti_pruss_driver, ti_pruss_devclass, 0, 0); MODULE_DEPEND(ti_pruss, ti_sysc, 1, 1, 1); MODULE_DEPEND(ti_pruss, ti_prm, 1, 1, 1); static struct resource_spec ti_pruss_irq_spec[] = { { SYS_RES_IRQ, 0, RF_ACTIVE }, { SYS_RES_IRQ, 1, RF_ACTIVE }, { SYS_RES_IRQ, 2, RF_ACTIVE }, { SYS_RES_IRQ, 3, RF_ACTIVE }, { SYS_RES_IRQ, 4, RF_ACTIVE }, { SYS_RES_IRQ, 5, RF_ACTIVE }, { SYS_RES_IRQ, 6, RF_ACTIVE }, { SYS_RES_IRQ, 7, RF_ACTIVE }, { -1, 0, 0 } }; CTASSERT(TI_PRUSS_HOST_IRQS == nitems(ti_pruss_irq_spec) - 1); static int ti_pruss_irq_open(struct cdev *dev, int oflags, int devtype, struct thread *td) { struct ctl* irqs; struct ti_pruss_irqsc *sc; sc = dev->si_drv1; irqs = malloc(sizeof(struct ctl), M_DEVBUF, M_WAITOK); if (!irqs) return (ENOMEM); irqs->cnt = sc->tstamps.ctl.cnt; irqs->idx = sc->tstamps.ctl.idx; return devfs_set_cdevpriv(irqs, ti_pruss_privdtor); } static void ti_pruss_privdtor(void *data) { free(data, M_DEVBUF); } static int ti_pruss_irq_poll(struct cdev *dev, int events, struct thread *td) { struct ctl* irqs; struct ti_pruss_irqsc *sc; sc = dev->si_drv1; devfs_get_cdevpriv((void**)&irqs); if (events & (POLLIN | POLLRDNORM)) { if (sc->tstamps.ctl.cnt != irqs->cnt) return events & (POLLIN | POLLRDNORM); else selrecord(td, &sc->sc_selinfo); } return 0; } static int ti_pruss_irq_read(struct cdev *cdev, struct uio *uio, int ioflag) { const size_t ts_len = sizeof(uint64_t); struct ti_pruss_irqsc* irq; struct ctl* priv; int error = 0; size_t idx; ssize_t level; irq = cdev->si_drv1; if (uio->uio_resid < ts_len) return (EINVAL); error = devfs_get_cdevpriv((void**)&priv); if (error) return (error); mtx_lock(&irq->sc_mtx); if (irq->tstamps.ctl.cnt - priv->cnt > TI_TS_ARRAY) { priv->cnt = irq->tstamps.ctl.cnt; priv->idx = irq->tstamps.ctl.idx; mtx_unlock(&irq->sc_mtx); return (ENXIO); } do { idx = priv->idx; level = irq->tstamps.ctl.idx - idx; if (level < 0) level += TI_TS_ARRAY; if (level == 0) { if (ioflag & O_NONBLOCK) { mtx_unlock(&irq->sc_mtx); return (EWOULDBLOCK); } error = msleep(irq, &irq->sc_mtx, PCATCH | PDROP, "pruirq", 0); if (error) return error; mtx_lock(&irq->sc_mtx); } }while(level == 0); mtx_unlock(&irq->sc_mtx); error = uiomove(&irq->tstamps.ts[idx], ts_len, uio); if (++idx == TI_TS_ARRAY) idx = 0; priv->idx = idx; atomic_add_32(&priv->cnt, 1); return (error); } static struct ti_pruss_irq_arg { int irq; struct ti_pruss_softc *sc; } ti_pruss_irq_args[TI_PRUSS_IRQS]; static __inline uint32_t ti_pruss_reg_read(struct ti_pruss_softc *sc, uint32_t reg) { return (bus_space_read_4(sc->sc_bt, sc->sc_bh, reg)); } static __inline void ti_pruss_reg_write(struct ti_pruss_softc *sc, uint32_t reg, uint32_t val) { bus_space_write_4(sc->sc_bt, sc->sc_bh, reg, val); } static __inline void ti_pruss_interrupts_clear(struct ti_pruss_softc *sc) { /* disable global interrupt */ ti_pruss_reg_write(sc, PRUSS_INTC_GER, 0 ); /* clear all events */ ti_pruss_reg_write(sc, PRUSS_INTC_SECR0, 0xFFFFFFFF); ti_pruss_reg_write(sc, PRUSS_INTC_SECR1, 0xFFFFFFFF); /* disable all host interrupts */ ti_pruss_reg_write(sc, PRUSS_INTC_HIER, 0); } static __inline int ti_pruss_interrupts_enable(struct ti_pruss_softc *sc, int8_t irq, bool enable) { if (enable && ((sc->sc_irq_devs[irq].channel == -1) || (sc->sc_irq_devs[irq].event== -1))) { device_printf( sc->sc_pdev->si_drv1, "Interrupt chain not fully configured, not possible to enable\n" ); return (EINVAL); } sc->sc_irq_devs[irq].enable = enable; if (sc->sc_irq_devs[irq].sc_pdev) { destroy_dev(sc->sc_irq_devs[irq].sc_pdev); sc->sc_irq_devs[irq].sc_pdev = NULL; } if (enable) { sc->sc_irq_devs[irq].sc_pdev = make_dev(&ti_pruss_cdevirq, 0, UID_ROOT, GID_WHEEL, 0600, "pruss%d.irq%d", device_get_unit(sc->sc_pdev->si_drv1), irq); sc->sc_irq_devs[irq].sc_pdev->si_drv1 = &sc->sc_irq_devs[irq]; sc->sc_irq_devs[irq].tstamps.ctl.idx = 0; } uint32_t reg = enable ? PRUSS_INTC_HIEISR : PRUSS_INTC_HIDISR; ti_pruss_reg_write(sc, reg, sc->sc_irq_devs[irq].channel); reg = enable ? PRUSS_INTC_EISR : PRUSS_INTC_EICR; ti_pruss_reg_write(sc, reg, sc->sc_irq_devs[irq].event ); return (0); } static __inline void ti_pruss_map_write(struct ti_pruss_softc *sc, uint32_t basereg, uint8_t index, uint8_t content) { const size_t regadr = basereg + index & ~0x03; const size_t bitpos = (index & 0x03) * 8; uint32_t rmw = ti_pruss_reg_read(sc, regadr); rmw = (rmw & ~( 0xF << bitpos)) | ( (content & 0xF) << bitpos); ti_pruss_reg_write(sc, regadr, rmw); } static int ti_pruss_event_map( SYSCTL_HANDLER_ARGS ) { struct ti_pruss_softc *sc; const int8_t irq = arg2; int err; char event[sizeof(NOT_SET_STR)]; sc = arg1; if(sc->sc_irq_devs[irq].event == -1) bcopy(NOT_SET_STR, event, sizeof(event)); else snprintf(event, sizeof(event), "%d", sc->sc_irq_devs[irq].event); err = sysctl_handle_string(oidp, event, sizeof(event), req); if(err != 0) return (err); if (req->newptr) { // write event if (strcmp(NOT_SET_STR, event) == 0) { ti_pruss_interrupts_enable(sc, irq, false); sc->sc_irq_devs[irq].event = -1; } else { if (sc->sc_irq_devs[irq].channel == -1) { device_printf( sc->sc_pdev->si_drv1, "corresponding channel not configured\n"); return (ENXIO); } const int8_t channelnr = sc->sc_irq_devs[irq].channel; const int8_t eventnr = strtol( event, NULL, 10 ); // TODO: check if strol is valid if (eventnr > TI_PRUSS_EVENTS || eventnr < 0) { device_printf( sc->sc_pdev->si_drv1, "Event number %d not valid (0 - %d)", channelnr, TI_PRUSS_EVENTS -1); return (EINVAL); } sc->sc_irq_devs[irq].channel = channelnr; sc->sc_irq_devs[irq].event = eventnr; // event[nr] <= channel ti_pruss_map_write(sc, PRUSS_INTC_CMR_BASE, eventnr, channelnr); } } return (err); } static int ti_pruss_channel_map(SYSCTL_HANDLER_ARGS) { struct ti_pruss_softc *sc; int err; char channel[sizeof(NOT_SET_STR)]; const int8_t irq = arg2; sc = arg1; if (sc->sc_irq_devs[irq].channel == -1) bcopy(NOT_SET_STR, channel, sizeof(channel)); else snprintf(channel, sizeof(channel), "%d", sc->sc_irq_devs[irq].channel); err = sysctl_handle_string(oidp, channel, sizeof(channel), req); if (err != 0) return (err); if (req->newptr) { // write event if (strcmp(NOT_SET_STR, channel) == 0) { ti_pruss_interrupts_enable(sc, irq, false); ti_pruss_reg_write(sc, PRUSS_INTC_HIDISR, sc->sc_irq_devs[irq].channel); sc->sc_irq_devs[irq].channel = -1; } else { const int8_t channelnr = strtol(channel, NULL, 10); // TODO: check if strol is valid if (channelnr > TI_PRUSS_IRQS || channelnr < 0) { device_printf(sc->sc_pdev->si_drv1, "Channel number %d not valid (0 - %d)", channelnr, TI_PRUSS_IRQS-1); return (EINVAL); } sc->sc_irq_devs[irq].channel = channelnr; sc->sc_irq_devs[irq].last = -1; // channel[nr] <= irqnr ti_pruss_map_write(sc, PRUSS_INTC_HMR_BASE, irq, channelnr); } } return (err); } static int ti_pruss_interrupt_enable(SYSCTL_HANDLER_ARGS) { struct ti_pruss_softc *sc; int err; bool irqenable; const int8_t irq = arg2; sc = arg1; irqenable = sc->sc_irq_devs[arg2].enable; err = sysctl_handle_bool(oidp, &irqenable, arg2, req); if (err != 0) return (err); if (req->newptr) // write enable return ti_pruss_interrupts_enable(sc, irq, irqenable); return (err); } static int ti_pruss_global_interrupt_enable(SYSCTL_HANDLER_ARGS) { struct ti_pruss_softc *sc; int err; bool glob_irqen; sc = arg1; glob_irqen = sc->sc_glob_irqen; err = sysctl_handle_bool(oidp, &glob_irqen, arg2, req); if (err != 0) return (err); if (req->newptr) { sc->sc_glob_irqen = glob_irqen; ti_pruss_reg_write(sc, PRUSS_INTC_GER, glob_irqen); } return (err); } static int ti_pruss_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_is_compatible(dev, "ti,pruss-v1") || ofw_bus_is_compatible(dev, "ti,pruss-v2")) { device_set_desc(dev, "TI Programmable Realtime Unit Subsystem"); return (BUS_PROBE_DEFAULT); } return (ENXIO); } static int ti_pruss_attach(device_t dev) { struct ti_pruss_softc *sc; int rid, i, err, ncells; - uint32_t reg; phandle_t node; clk_t l3_gclk, pruss_ocp_gclk; phandle_t ti_prm_ref, *cells; device_t ti_prm_dev; rid = 0; sc = device_get_softc(dev); node = ofw_bus_get_node(device_get_parent(dev)); if (node <= 0) { device_printf(dev, "Cant get ofw node\n"); return (ENXIO); } /* * Follow activate pattern from sys/arm/ti/am335x/am335x_prcm.c * by Damjan Marion */ /* Set MODULEMODE to ENABLE(2) */ /* Wait for MODULEMODE to become ENABLE(2) */ if (ti_sysc_clock_enable(device_get_parent(dev)) != 0) { device_printf(dev, "Could not enable PRUSS clock\n"); return (ENXIO); } /* Set CLKTRCTRL to SW_WKUP(2) */ /* Wait for the 200 MHz OCP clock to become active */ /* Wait for the 200 MHz IEP clock to become active */ /* Wait for the 192 MHz UART clock to become active */ /* * At the moment there is no reference to CM_PER_PRU_ICSS_CLKSTCTRL@140 * in the devicetree. The register reset state are SW_WKUP(2) as default * so at the moment ignore setting this register. */ /* Select L3F as OCP clock */ /* Get the clock and set the parent */ err = clk_get_by_name(dev, "l3_gclk", &l3_gclk); if (err) { device_printf(dev, "Cant get l3_gclk err %d\n", err); return (ENXIO); } err = clk_get_by_name(dev, "pruss_ocp_gclk@530", &pruss_ocp_gclk); if (err) { device_printf(dev, "Cant get pruss_ocp_gclk@530 err %d\n", err); return (ENXIO); } err = clk_set_parent_by_clk(pruss_ocp_gclk, l3_gclk); if (err) { device_printf(dev, "Cant set pruss_ocp_gclk parent to l3_gclk err %d\n", err); return (ENXIO); } /* Clear the RESET bit */ /* Find the ti_prm */ /* #reset-cells should not been used in this way but... */ err = ofw_bus_parse_xref_list_alloc(node, "resets", "#reset-cells", 0, &ti_prm_ref, &ncells, &cells); OF_prop_free(cells); if (err) { device_printf(dev, "Cant fetch \"resets\" reference %x\n", err); return (ENXIO); } ti_prm_dev = OF_device_from_xref(ti_prm_ref); if (ti_prm_dev == NULL) { device_printf(dev, "Cant get device from \"resets\"\n"); return (ENXIO); } err = ti_prm_reset(ti_prm_dev); if (err) { device_printf(dev, "ti_prm_reset failed %d\n", err); return (ENXIO); } /* End of clock activation */ mtx_init(&sc->sc_mtx, "TI PRUSS", NULL, MTX_DEF); sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->sc_mem_res == NULL) { device_printf(dev, "could not allocate memory resource\n"); return (ENXIO); } struct sysctl_ctx_list *clist = device_get_sysctl_ctx(dev); if (!clist) return (EINVAL); struct sysctl_oid *poid; poid = device_get_sysctl_tree( dev ); if (!poid) return (EINVAL); sc->sc_glob_irqen = false; struct sysctl_oid *irq_root = SYSCTL_ADD_NODE(clist, SYSCTL_CHILDREN(poid), OID_AUTO, "irq", CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "PRUSS Host Interrupts"); SYSCTL_ADD_PROC(clist, SYSCTL_CHILDREN(poid), OID_AUTO, "global_interrupt_enable", CTLFLAG_RW | CTLTYPE_U8 | CTLFLAG_NEEDGIANT, sc, 0, ti_pruss_global_interrupt_enable, "CU", "Global interrupt enable"); sc->sc_bt = rman_get_bustag(sc->sc_mem_res); sc->sc_bh = rman_get_bushandle(sc->sc_mem_res); if (bus_alloc_resources(dev, ti_pruss_irq_spec, sc->sc_irq_res) != 0) { device_printf(dev, "could not allocate interrupt resource\n"); ti_pruss_detach(dev); return (ENXIO); } ti_pruss_interrupts_clear(sc); for (i = 0; i < TI_PRUSS_IRQS; i++) { char name[8]; snprintf(name, sizeof(name), "%d", i); struct sysctl_oid *irq_nodes = SYSCTL_ADD_NODE(clist, SYSCTL_CHILDREN(irq_root), OID_AUTO, name, CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "PRUSS Interrupts"); SYSCTL_ADD_PROC(clist, SYSCTL_CHILDREN(irq_nodes), OID_AUTO, "channel", CTLFLAG_RW | CTLTYPE_STRING | CTLFLAG_NEEDGIANT, sc, i, ti_pruss_channel_map, "A", "Channel attached to this irq"); SYSCTL_ADD_PROC(clist, SYSCTL_CHILDREN(irq_nodes), OID_AUTO, "event", CTLFLAG_RW | CTLTYPE_STRING | CTLFLAG_NEEDGIANT, sc, i, ti_pruss_event_map, "A", "Event attached to this irq"); SYSCTL_ADD_PROC(clist, SYSCTL_CHILDREN(irq_nodes), OID_AUTO, "enable", CTLFLAG_RW | CTLTYPE_U8 | CTLFLAG_NEEDGIANT, sc, i, ti_pruss_interrupt_enable, "CU", "Enable/Disable interrupt"); sc->sc_irq_devs[i].event = -1; sc->sc_irq_devs[i].channel = -1; sc->sc_irq_devs[i].tstamps.ctl.idx = 0; if (i < TI_PRUSS_HOST_IRQS) { ti_pruss_irq_args[i].irq = i; ti_pruss_irq_args[i].sc = sc; if (bus_setup_intr(dev, sc->sc_irq_res[i], INTR_MPSAFE | INTR_TYPE_MISC, NULL, ti_pruss_intr, &ti_pruss_irq_args[i], &sc->sc_intr[i]) != 0) { device_printf(dev, "unable to setup the interrupt handler\n"); ti_pruss_detach(dev); return (ENXIO); } mtx_init(&sc->sc_irq_devs[i].sc_mtx, "TI PRUSS IRQ", NULL, MTX_DEF); knlist_init_mtx(&sc->sc_irq_devs[i].sc_selinfo.si_note, &sc->sc_irq_devs[i].sc_mtx); } } - reg = ti_pruss_reg_read(sc, - ti_sysc_get_sysc_address_offset_host(device_get_parent(dev))); - if (ti_pruss_reg_read(sc, PRUSS_AM33XX_INTC) == PRUSS_AM33XX_REV) device_printf(dev, "AM33xx PRU-ICSS\n"); sc->sc_pdev = make_dev(&ti_pruss_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "pruss%d", device_get_unit(dev)); sc->sc_pdev->si_drv1 = dev; /* Acc. to datasheet always write 1 to polarity registers */ ti_pruss_reg_write(sc, PRUSS_INTC_SIPR0, 0xFFFFFFFF); ti_pruss_reg_write(sc, PRUSS_INTC_SIPR1, 0xFFFFFFFF); /* Acc. to datasheet always write 0 to event type registers */ ti_pruss_reg_write(sc, PRUSS_INTC_SITR0, 0); ti_pruss_reg_write(sc, PRUSS_INTC_SITR1, 0); return (0); } static int ti_pruss_detach(device_t dev) { struct ti_pruss_softc *sc = device_get_softc(dev); ti_pruss_interrupts_clear(sc); for (int i = 0; i < TI_PRUSS_HOST_IRQS; i++) { ti_pruss_interrupts_enable( sc, i, false ); if (sc->sc_intr[i]) bus_teardown_intr(dev, sc->sc_irq_res[i], sc->sc_intr[i]); if (sc->sc_irq_res[i]) bus_release_resource(dev, SYS_RES_IRQ, rman_get_rid(sc->sc_irq_res[i]), sc->sc_irq_res[i]); knlist_clear(&sc->sc_irq_devs[i].sc_selinfo.si_note, 0); mtx_lock(&sc->sc_irq_devs[i].sc_mtx); if (!knlist_empty(&sc->sc_irq_devs[i].sc_selinfo.si_note)) printf("IRQ %d KQueue not empty!\n", i ); mtx_unlock(&sc->sc_irq_devs[i].sc_mtx); knlist_destroy(&sc->sc_irq_devs[i].sc_selinfo.si_note); mtx_destroy(&sc->sc_irq_devs[i].sc_mtx); } mtx_destroy(&sc->sc_mtx); if (sc->sc_mem_res) bus_release_resource(dev, SYS_RES_MEMORY, rman_get_rid(sc->sc_mem_res), sc->sc_mem_res); if (sc->sc_pdev) destroy_dev(sc->sc_pdev); return (0); } static void ti_pruss_intr(void *arg) { int val; struct ti_pruss_irq_arg *iap = arg; struct ti_pruss_softc *sc = iap->sc; /* * Interrupts pr1_host_intr[0:7] are mapped to * Host-2 to Host-9 of PRU-ICSS IRQ-controller. */ const int pru_int = iap->irq + TI_PRUSS_PRU_IRQS; const int pru_int_mask = (1 << pru_int); const int pru_channel = sc->sc_irq_devs[pru_int].channel; const int pru_event = sc->sc_irq_devs[pru_channel].event; val = ti_pruss_reg_read(sc, PRUSS_INTC_HIER); if (!(val & pru_int_mask)) return; ti_pruss_reg_write(sc, PRUSS_INTC_HIDISR, pru_int); ti_pruss_reg_write(sc, PRUSS_INTC_SICR, pru_event); ti_pruss_reg_write(sc, PRUSS_INTC_HIEISR, pru_int); struct ti_pruss_irqsc* irq = &sc->sc_irq_devs[pru_channel]; size_t wr = irq->tstamps.ctl.idx; struct timespec ts; nanouptime(&ts); irq->tstamps.ts[wr] = ts.tv_sec * 1000000000 + ts.tv_nsec; if (++wr == TI_TS_ARRAY) wr = 0; atomic_add_32(&irq->tstamps.ctl.cnt, 1); irq->tstamps.ctl.idx = wr; KNOTE_UNLOCKED(&irq->sc_selinfo.si_note, pru_int); wakeup(irq); selwakeup(&irq->sc_selinfo); } static int ti_pruss_open(struct cdev *cdev __unused, int oflags __unused, int devtype __unused, struct thread *td __unused) { return (0); } static int ti_pruss_mmap(struct cdev *cdev, vm_ooffset_t offset, vm_paddr_t *paddr, int nprot, vm_memattr_t *memattr) { device_t dev = cdev->si_drv1; struct ti_pruss_softc *sc = device_get_softc(dev); if (offset >= rman_get_size(sc->sc_mem_res)) return (ENOSPC); *paddr = rman_get_start(sc->sc_mem_res) + offset; *memattr = VM_MEMATTR_UNCACHEABLE; return (0); } static struct filterops ti_pruss_kq_read = { .f_isfd = 1, .f_detach = ti_pruss_irq_kqread_detach, .f_event = ti_pruss_irq_kqevent, }; static void ti_pruss_irq_kqread_detach(struct knote *kn) { struct ti_pruss_irqsc *sc = kn->kn_hook; knlist_remove(&sc->sc_selinfo.si_note, kn, 0); } static int ti_pruss_irq_kqevent(struct knote *kn, long hint) { struct ti_pruss_irqsc* irq_sc; int notify; irq_sc = kn->kn_hook; if (hint > 0) kn->kn_data = hint - 2; if (hint > 0 || irq_sc->last > 0) notify = 1; else notify = 0; irq_sc->last = hint; return (notify); } static int ti_pruss_irq_kqfilter(struct cdev *cdev, struct knote *kn) { struct ti_pruss_irqsc *sc = cdev->si_drv1; switch (kn->kn_filter) { case EVFILT_READ: kn->kn_hook = sc; kn->kn_fop = &ti_pruss_kq_read; knlist_add(&sc->sc_selinfo.si_note, kn, 0); break; default: return (EINVAL); } return (0); } diff --git a/sys/arm/ti/ti_spi.c b/sys/arm/ti/ti_spi.c index 19b80605b9b6..e51d3de353bd 100644 --- a/sys/arm/ti/ti_spi.c +++ b/sys/arm/ti/ti_spi.c @@ -1,582 +1,577 @@ /*- * 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. * */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "spibus_if.h" static void ti_spi_intr(void *); static int ti_spi_detach(device_t); #undef TI_SPI_DEBUG #ifdef TI_SPI_DEBUG #define IRQSTATUSBITS \ "\020\1TX0_EMPTY\2TX0_UNDERFLOW\3RX0_FULL\4RX0_OVERFLOW" \ "\5TX1_EMPTY\6TX1_UNDERFLOW\7RX1_FULL\11TX2_EMPTY" \ "\12TX1_UNDERFLOW\13RX2_FULL\15TX3_EMPTY\16TX3_UNDERFLOW" \ "\17RX3_FULL\22EOW" #define CONFBITS \ "\020\1PHA\2POL\7EPOL\17DMAW\20DMAR\21DPE0\22DPE1\23IS" \ "\24TURBO\25FORCE\30SBE\31SBPOL\34FFEW\35FFER\36CLKG" #define STATBITS \ "\020\1RXS\2TXS\3EOT\4TXFFE\5TXFFF\6RXFFE\7RXFFFF" #define MODULCTRLBITS \ "\020\1SINGLE\2NOSPIEN\3SLAVE\4SYST\10MOA\11FDAA" #define CTRLBITS \ "\020\1ENABLED" static void ti_spi_printr(device_t dev) { int clk, conf, ctrl, div, i, j, wl; struct ti_spi_softc *sc; uint32_t reg; sc = device_get_softc(dev); reg = TI_SPI_READ(sc, MCSPI_SYSCONFIG); device_printf(dev, "SYSCONFIG: %#x\n", reg); reg = TI_SPI_READ(sc, MCSPI_SYSSTATUS); device_printf(dev, "SYSSTATUS: %#x\n", reg); reg = TI_SPI_READ(sc, MCSPI_IRQSTATUS); device_printf(dev, "IRQSTATUS: 0x%b\n", reg, IRQSTATUSBITS); reg = TI_SPI_READ(sc, MCSPI_IRQENABLE); device_printf(dev, "IRQENABLE: 0x%b\n", reg, IRQSTATUSBITS); reg = TI_SPI_READ(sc, MCSPI_MODULCTRL); device_printf(dev, "MODULCTRL: 0x%b\n", reg, MODULCTRLBITS); for (i = 0; i < sc->sc_numcs; i++) { ctrl = TI_SPI_READ(sc, MCSPI_CTRL_CH(i)); conf = TI_SPI_READ(sc, MCSPI_CONF_CH(i)); device_printf(dev, "CH%dCONF: 0x%b\n", i, conf, CONFBITS); if (conf & MCSPI_CONF_CLKG) { div = (conf >> MCSPI_CONF_CLK_SHIFT) & MCSPI_CONF_CLK_MSK; div |= ((ctrl >> MCSPI_CTRL_EXTCLK_SHIFT) & MCSPI_CTRL_EXTCLK_MSK) << 4; } else { div = 1; j = (conf >> MCSPI_CONF_CLK_SHIFT) & MCSPI_CONF_CLK_MSK; while (j-- > 0) div <<= 1; } clk = TI_SPI_GCLK / div; wl = ((conf >> MCSPI_CONF_WL_SHIFT) & MCSPI_CONF_WL_MSK) + 1; device_printf(dev, "wordlen: %-2d clock: %d\n", wl, clk); reg = TI_SPI_READ(sc, MCSPI_STAT_CH(i)); device_printf(dev, "CH%dSTAT: 0x%b\n", i, reg, STATBITS); device_printf(dev, "CH%dCTRL: 0x%b\n", i, ctrl, CTRLBITS); } reg = TI_SPI_READ(sc, MCSPI_XFERLEVEL); device_printf(dev, "XFERLEVEL: %#x\n", reg); } #endif static void ti_spi_set_clock(struct ti_spi_softc *sc, int ch, int freq) { uint32_t clkdiv, conf, div, extclk, reg; clkdiv = TI_SPI_GCLK / freq; if (clkdiv > MCSPI_EXTCLK_MSK) { extclk = 0; clkdiv = 0; div = 1; while (TI_SPI_GCLK / div > freq && clkdiv <= 0xf) { clkdiv++; div <<= 1; } conf = clkdiv << MCSPI_CONF_CLK_SHIFT; } else { extclk = clkdiv >> 4; clkdiv &= MCSPI_CONF_CLK_MSK; conf = MCSPI_CONF_CLKG | clkdiv << MCSPI_CONF_CLK_SHIFT; } reg = TI_SPI_READ(sc, MCSPI_CTRL_CH(ch)); reg &= ~(MCSPI_CTRL_EXTCLK_MSK << MCSPI_CTRL_EXTCLK_SHIFT); reg |= extclk << MCSPI_CTRL_EXTCLK_SHIFT; TI_SPI_WRITE(sc, MCSPI_CTRL_CH(ch), reg); reg = TI_SPI_READ(sc, MCSPI_CONF_CH(ch)); reg &= ~(MCSPI_CONF_CLKG | MCSPI_CONF_CLK_MSK << MCSPI_CONF_CLK_SHIFT); TI_SPI_WRITE(sc, MCSPI_CONF_CH(ch), reg | conf); } static int ti_spi_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "ti,omap4-mcspi")) return (ENXIO); device_set_desc(dev, "TI McSPI controller"); return (BUS_PROBE_DEFAULT); } static int ti_spi_attach(device_t dev) { int err, i, rid, timeout; struct ti_spi_softc *sc; uint32_t rev; sc = device_get_softc(dev); sc->sc_dev = dev; /* Activate the McSPI module. */ err = ti_sysc_clock_enable(device_get_parent(dev)); if (err) { device_printf(dev, "Error: failed to activate source clock\n"); return (err); } /* Get the number of available channels. */ if ((OF_getencprop(ofw_bus_get_node(dev), "ti,spi-num-cs", &sc->sc_numcs, sizeof(sc->sc_numcs))) <= 0) { sc->sc_numcs = 2; } 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"); return (ENXIO); } 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) { bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res); device_printf(dev, "cannot allocate interrupt\n"); return (ENXIO); } /* Hook up our interrupt handler. */ if (bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_MISC | INTR_MPSAFE, NULL, ti_spi_intr, sc, &sc->sc_intrhand)) { bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res); bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res); device_printf(dev, "cannot setup the interrupt handler\n"); return (ENXIO); } mtx_init(&sc->sc_mtx, "ti_spi", NULL, MTX_DEF); /* Issue a softreset to the controller */ TI_SPI_WRITE(sc, MCSPI_SYSCONFIG, MCSPI_SYSCONFIG_SOFTRESET); timeout = 1000; while (!(TI_SPI_READ(sc, MCSPI_SYSSTATUS) & MCSPI_SYSSTATUS_RESETDONE)) { if (--timeout == 0) { device_printf(dev, "Error: Controller reset operation timed out\n"); ti_spi_detach(dev); return (ENXIO); } DELAY(100); } /* Print the McSPI module revision. */ rev = TI_SPI_READ(sc, ti_sysc_get_rev_address_offset_host(device_get_parent(dev))); device_printf(dev, "scheme: %#x func: %#x rtl: %d rev: %d.%d custom rev: %d\n", (rev >> MCSPI_REVISION_SCHEME_SHIFT) & MCSPI_REVISION_SCHEME_MSK, (rev >> MCSPI_REVISION_FUNC_SHIFT) & MCSPI_REVISION_FUNC_MSK, (rev >> MCSPI_REVISION_RTL_SHIFT) & MCSPI_REVISION_RTL_MSK, (rev >> MCSPI_REVISION_MAJOR_SHIFT) & MCSPI_REVISION_MAJOR_MSK, (rev >> MCSPI_REVISION_MINOR_SHIFT) & MCSPI_REVISION_MINOR_MSK, (rev >> MCSPI_REVISION_CUSTOM_SHIFT) & MCSPI_REVISION_CUSTOM_MSK); /* Set Master mode, single channel. */ TI_SPI_WRITE(sc, MCSPI_MODULCTRL, MCSPI_MODULCTRL_SINGLE); /* Clear pending interrupts and disable interrupts. */ TI_SPI_WRITE(sc, MCSPI_IRQENABLE, 0x0); TI_SPI_WRITE(sc, MCSPI_IRQSTATUS, 0xffff); for (i = 0; i < sc->sc_numcs; i++) { /* * Default to SPI mode 0, CS active low, 8 bits word length and * 500kHz clock. */ TI_SPI_WRITE(sc, MCSPI_CONF_CH(i), MCSPI_CONF_DPE0 | MCSPI_CONF_EPOL | (8 - 1) << MCSPI_CONF_WL_SHIFT); /* Set initial clock - 500kHz. */ ti_spi_set_clock(sc, i, 500000); } #ifdef TI_SPI_DEBUG ti_spi_printr(dev); #endif device_add_child(dev, "spibus", -1); return (bus_generic_attach(dev)); } static int ti_spi_detach(device_t dev) { struct ti_spi_softc *sc; sc = device_get_softc(dev); /* Clear pending interrupts and disable interrupts. */ TI_SPI_WRITE(sc, MCSPI_IRQENABLE, 0); TI_SPI_WRITE(sc, MCSPI_IRQSTATUS, 0xffff); /* Reset controller. */ TI_SPI_WRITE(sc, MCSPI_SYSCONFIG, MCSPI_SYSCONFIG_SOFTRESET); bus_generic_detach(dev); mtx_destroy(&sc->sc_mtx); if (sc->sc_intrhand) bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_intrhand); 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); return (0); } static int ti_spi_fill_fifo(struct ti_spi_softc *sc) { int bytes, timeout; struct spi_command *cmd; uint32_t written; uint8_t *data; cmd = sc->sc_cmd; bytes = min(sc->sc_len - sc->sc_written, sc->sc_fifolvl); while (bytes-- > 0) { data = (uint8_t *)cmd->tx_cmd; written = sc->sc_written++; if (written >= cmd->tx_cmd_sz) { data = (uint8_t *)cmd->tx_data; written -= cmd->tx_cmd_sz; } if (sc->sc_fifolvl == 1) { /* FIFO disabled. */ timeout = 1000; while (--timeout > 0 && (TI_SPI_READ(sc, MCSPI_STAT_CH(sc->sc_cs)) & MCSPI_STAT_TXS) == 0) { DELAY(100); } if (timeout == 0) return (-1); } TI_SPI_WRITE(sc, MCSPI_TX_CH(sc->sc_cs), data[written]); } return (0); } static int ti_spi_drain_fifo(struct ti_spi_softc *sc) { int bytes, timeout; struct spi_command *cmd; uint32_t read; uint8_t *data; cmd = sc->sc_cmd; bytes = min(sc->sc_len - sc->sc_read, sc->sc_fifolvl); while (bytes-- > 0) { data = (uint8_t *)cmd->rx_cmd; read = sc->sc_read++; if (read >= cmd->rx_cmd_sz) { data = (uint8_t *)cmd->rx_data; read -= cmd->rx_cmd_sz; } if (sc->sc_fifolvl == 1) { /* FIFO disabled. */ timeout = 1000; while (--timeout > 0 && (TI_SPI_READ(sc, MCSPI_STAT_CH(sc->sc_cs)) & MCSPI_STAT_RXS) == 0) { DELAY(100); } if (timeout == 0) return (-1); } data[read] = TI_SPI_READ(sc, MCSPI_RX_CH(sc->sc_cs)); } return (0); } static void ti_spi_intr(void *arg) { - int eow; struct ti_spi_softc *sc; uint32_t status; - eow = 0; sc = (struct ti_spi_softc *)arg; TI_SPI_LOCK(sc); status = TI_SPI_READ(sc, MCSPI_IRQSTATUS); /* * No new TX_empty or RX_full event will be asserted while the CPU has * not performed the number of writes or reads defined by * MCSPI_XFERLEVEL[AEL] and MCSPI_XFERLEVEL[AFL]. It is responsibility * of CPU perform the right number of writes and reads. */ if (status & MCSPI_IRQ_TX0_EMPTY) ti_spi_fill_fifo(sc); if (status & MCSPI_IRQ_RX0_FULL) ti_spi_drain_fifo(sc); - if (status & MCSPI_IRQ_EOW) - eow = 1; - /* Clear interrupt status. */ TI_SPI_WRITE(sc, MCSPI_IRQSTATUS, status); /* Check for end of transfer. */ if (sc->sc_written == sc->sc_len && sc->sc_read == sc->sc_len) { sc->sc_flags |= TI_SPI_DONE; wakeup(sc->sc_dev); } TI_SPI_UNLOCK(sc); } static int ti_spi_pio_transfer(struct ti_spi_softc *sc) { while (sc->sc_len - sc->sc_written > 0) { if (ti_spi_fill_fifo(sc) == -1) return (EIO); if (ti_spi_drain_fifo(sc) == -1) return (EIO); } return (0); } static int ti_spi_gcd(int a, int b) { int m; while ((m = a % b) != 0) { a = b; b = m; } return (b); } static int ti_spi_transfer(device_t dev, device_t child, struct spi_command *cmd) { int err; struct ti_spi_softc *sc; uint32_t clockhz, cs, mode, reg; sc = device_get_softc(dev); KASSERT(cmd->tx_cmd_sz == cmd->rx_cmd_sz, ("TX/RX command sizes should be equal")); KASSERT(cmd->tx_data_sz == cmd->rx_data_sz, ("TX/RX data sizes should be equal")); /* Get the proper chip select for this child. */ spibus_get_cs(child, &cs); spibus_get_clock(child, &clockhz); spibus_get_mode(child, &mode); cs &= ~SPIBUS_CS_HIGH; if (cs > sc->sc_numcs) { device_printf(dev, "Invalid chip select %d requested by %s\n", cs, device_get_nameunit(child)); return (EINVAL); } if (mode > 3) { device_printf(dev, "Invalid mode %d requested by %s\n", mode, device_get_nameunit(child)); return (EINVAL); } TI_SPI_LOCK(sc); /* If the controller is in use wait until it is available. */ while (sc->sc_flags & TI_SPI_BUSY) mtx_sleep(dev, &sc->sc_mtx, 0, "ti_spi", 0); /* Now we have control over SPI controller. */ sc->sc_flags = TI_SPI_BUSY; /* Save the SPI command data. */ sc->sc_cs = cs; sc->sc_cmd = cmd; sc->sc_read = 0; sc->sc_written = 0; sc->sc_len = cmd->tx_cmd_sz + cmd->tx_data_sz; sc->sc_fifolvl = ti_spi_gcd(sc->sc_len, TI_SPI_FIFOSZ); if (sc->sc_fifolvl < 2 || sc->sc_len > 0xffff) sc->sc_fifolvl = 1; /* FIFO disabled. */ /* Disable FIFO for now. */ sc->sc_fifolvl = 1; /* Set the bus frequency. */ ti_spi_set_clock(sc, sc->sc_cs, clockhz); /* Disable the FIFO. */ TI_SPI_WRITE(sc, MCSPI_XFERLEVEL, 0); /* 8 bits word, d0 miso, d1 mosi, mode 0 and CS active low. */ reg = TI_SPI_READ(sc, MCSPI_CONF_CH(sc->sc_cs)); reg &= ~(MCSPI_CONF_FFER | MCSPI_CONF_FFEW | MCSPI_CONF_SBPOL | MCSPI_CONF_SBE | MCSPI_CONF_TURBO | MCSPI_CONF_IS | MCSPI_CONF_DPE1 | MCSPI_CONF_DPE0 | MCSPI_CONF_DMAR | MCSPI_CONF_DMAW | MCSPI_CONF_EPOL); reg |= MCSPI_CONF_DPE0 | MCSPI_CONF_EPOL | MCSPI_CONF_WL8BITS; reg |= mode; /* POL and PHA are the low bits, we can just OR-in mode */ TI_SPI_WRITE(sc, MCSPI_CONF_CH(sc->sc_cs), reg); #if 0 /* Enable channel interrupts. */ reg = TI_SPI_READ(sc, MCSPI_IRQENABLE); reg |= 0xf; TI_SPI_WRITE(sc, MCSPI_IRQENABLE, reg); #endif /* Start the transfer. */ reg = TI_SPI_READ(sc, MCSPI_CTRL_CH(sc->sc_cs)); TI_SPI_WRITE(sc, MCSPI_CTRL_CH(sc->sc_cs), reg | MCSPI_CTRL_ENABLE); /* Force CS on. */ reg = TI_SPI_READ(sc, MCSPI_CONF_CH(sc->sc_cs)); TI_SPI_WRITE(sc, MCSPI_CONF_CH(sc->sc_cs), reg |= MCSPI_CONF_FORCE); err = 0; if (sc->sc_fifolvl == 1) err = ti_spi_pio_transfer(sc); /* Force CS off. */ reg = TI_SPI_READ(sc, MCSPI_CONF_CH(sc->sc_cs)); reg &= ~MCSPI_CONF_FORCE; TI_SPI_WRITE(sc, MCSPI_CONF_CH(sc->sc_cs), reg); /* Disable IRQs. */ reg = TI_SPI_READ(sc, MCSPI_IRQENABLE); reg &= ~0xf; TI_SPI_WRITE(sc, MCSPI_IRQENABLE, reg); TI_SPI_WRITE(sc, MCSPI_IRQSTATUS, 0xf); /* Disable the SPI channel. */ reg = TI_SPI_READ(sc, MCSPI_CTRL_CH(sc->sc_cs)); reg &= ~MCSPI_CTRL_ENABLE; TI_SPI_WRITE(sc, MCSPI_CTRL_CH(sc->sc_cs), reg); /* Disable FIFO. */ reg = TI_SPI_READ(sc, MCSPI_CONF_CH(sc->sc_cs)); reg &= ~(MCSPI_CONF_FFER | MCSPI_CONF_FFEW); TI_SPI_WRITE(sc, MCSPI_CONF_CH(sc->sc_cs), reg); /* Release the controller and wakeup the next thread waiting for it. */ sc->sc_flags = 0; wakeup_one(dev); TI_SPI_UNLOCK(sc); return (err); } static phandle_t ti_spi_get_node(device_t bus, device_t dev) { /* Share controller node with spibus. */ return (ofw_bus_get_node(bus)); } static device_method_t ti_spi_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ti_spi_probe), DEVMETHOD(device_attach, ti_spi_attach), DEVMETHOD(device_detach, ti_spi_detach), /* SPI interface */ DEVMETHOD(spibus_transfer, ti_spi_transfer), /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_node, ti_spi_get_node), DEVMETHOD_END }; static devclass_t ti_spi_devclass; static driver_t ti_spi_driver = { "spi", ti_spi_methods, sizeof(struct ti_spi_softc), }; DRIVER_MODULE(ti_spi, simplebus, ti_spi_driver, ti_spi_devclass, 0, 0); MODULE_DEPEND(ti_spi, ti_sysc, 1, 1, 1); diff --git a/sys/arm/ti/ti_sysc.c b/sys/arm/ti/ti_sysc.c index b16158aa5d83..3aad5fd0730d 100644 --- a/sys/arm/ti/ti_sysc.c +++ b/sys/arm/ti/ti_sysc.c @@ -1,622 +1,619 @@ /*- * Copyright (c) 2019 Emmanuel Vadot * * Copyright (c) 2020 Oskar Holmlund * * 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 #include #include #include #include #include #include #include #include #include #define DEBUG_SYSC 0 #if DEBUG_SYSC #define DPRINTF(dev, msg...) device_printf(dev, msg) #else #define DPRINTF(dev, msg...) #endif /* Documentation/devicetree/bindings/bus/ti-sysc.txt * * Documentation/devicetree/clock/clock-bindings.txt * Defines phandle + optional pair * Documentation/devicetree/clock/ti-clkctl.txt */ static int ti_sysc_probe(device_t dev); static int ti_sysc_attach(device_t dev); static int ti_sysc_detach(device_t dev); #define TI_SYSC_DRA7_MCAN 15 #define TI_SYSC_USB_HOST_FS 14 #define TI_SYSC_DRA7_MCASP 13 #define TI_SYSC_MCASP 12 #define TI_SYSC_OMAP_AES 11 #define TI_SYSC_OMAP3_SHAM 10 #define TI_SYSC_OMAP4_SR 9 #define TI_SYSC_OMAP3630_SR 8 #define TI_SYSC_OMAP3430_SR 7 #define TI_SYSC_OMAP4_TIMER 6 #define TI_SYSC_OMAP2_TIMER 5 /* Above needs special workarounds */ #define TI_SYSC_OMAP4_SIMPLE 4 #define TI_SYSC_OMAP4 3 #define TI_SYSC_OMAP2 2 #define TI_SYSC 1 #define TI_SYSC_END 0 static struct ofw_compat_data compat_data[] = { { "ti,sysc-dra7-mcan", TI_SYSC_DRA7_MCAN }, { "ti,sysc-usb-host-fs", TI_SYSC_USB_HOST_FS }, { "ti,sysc-dra7-mcasp", TI_SYSC_DRA7_MCASP }, { "ti,sysc-mcasp", TI_SYSC_MCASP }, { "ti,sysc-omap-aes", TI_SYSC_OMAP_AES }, { "ti,sysc-omap3-sham", TI_SYSC_OMAP3_SHAM }, { "ti,sysc-omap4-sr", TI_SYSC_OMAP4_SR }, { "ti,sysc-omap3630-sr", TI_SYSC_OMAP3630_SR }, { "ti,sysc-omap3430-sr", TI_SYSC_OMAP3430_SR }, { "ti,sysc-omap4-timer", TI_SYSC_OMAP4_TIMER }, { "ti,sysc-omap2-timer", TI_SYSC_OMAP2_TIMER }, /* Above needs special workarounds */ { "ti,sysc-omap4-simple", TI_SYSC_OMAP4_SIMPLE }, { "ti,sysc-omap4", TI_SYSC_OMAP4 }, { "ti,sysc-omap2", TI_SYSC_OMAP2 }, { "ti,sysc", TI_SYSC }, { NULL, TI_SYSC_END } }; /* reg-names can be "rev", "sysc" and "syss" */ static const char * reg_names[] = { "rev", "sysc", "syss" }; #define REG_REV 0 #define REG_SYSC 1 #define REG_SYSS 2 #define REG_MAX 3 /* master idle / slave idle mode defined in 8.1.3.2.1 / 8.1.3.2.2 */ #include #define SYSC_IDLE_MAX 4 struct sysc_reg { uint64_t address; uint64_t size; }; struct clk_list { TAILQ_ENTRY(clk_list) next; clk_t clk; }; struct ti_sysc_softc { struct simplebus_softc sc; bool attach_done; device_t dev; int device_type; struct sysc_reg reg[REG_MAX]; /* Offset from host base address */ uint64_t offset_reg[REG_MAX]; uint32_t ti_sysc_mask; int32_t ti_sysc_midle[SYSC_IDLE_MAX]; int32_t ti_sysc_sidle[SYSC_IDLE_MAX]; uint32_t ti_sysc_delay_us; uint32_t ti_syss_mask; int num_clocks; TAILQ_HEAD(, clk_list) clk_list; /* deprecated ti_hwmods */ bool ti_no_reset_on_init; bool ti_no_idle_on_init; bool ti_no_idle; }; /* * All sysc seems to have a reg["rev"] register. * Lets use that for identification of which module the driver are connected to. */ uint64_t ti_sysc_get_rev_address(device_t dev) { struct ti_sysc_softc *sc = device_get_softc(dev); return (sc->reg[REG_REV].address); } uint64_t ti_sysc_get_rev_address_offset_host(device_t dev) { struct ti_sysc_softc *sc = device_get_softc(dev); return (sc->offset_reg[REG_REV]); } uint64_t ti_sysc_get_sysc_address(device_t dev) { struct ti_sysc_softc *sc = device_get_softc(dev); return (sc->reg[REG_SYSC].address); } uint64_t ti_sysc_get_sysc_address_offset_host(device_t dev) { struct ti_sysc_softc *sc = device_get_softc(dev); return (sc->offset_reg[REG_SYSC]); } uint64_t ti_sysc_get_syss_address(device_t dev) { struct ti_sysc_softc *sc = device_get_softc(dev); return (sc->reg[REG_SYSS].address); } uint64_t ti_sysc_get_syss_address_offset_host(device_t dev) { struct ti_sysc_softc *sc = device_get_softc(dev); return (sc->offset_reg[REG_SYSS]); } /* * Due no memory region is assigned the sysc driver the children needs to * handle the practical read/writes to the registers. * Check if sysc has reset bit. */ uint32_t ti_sysc_get_soft_reset_bit(device_t dev) { struct ti_sysc_softc *sc = device_get_softc(dev); switch (sc->device_type) { case TI_SYSC_OMAP4_TIMER: case TI_SYSC_OMAP4_SIMPLE: case TI_SYSC_OMAP4: if (sc->ti_sysc_mask & SYSC_OMAP4_SOFTRESET) { return (SYSC_OMAP4_SOFTRESET); } break; case TI_SYSC_OMAP2_TIMER: case TI_SYSC_OMAP2: case TI_SYSC: if (sc->ti_sysc_mask & SYSC_OMAP2_SOFTRESET) { return (SYSC_OMAP2_SOFTRESET); } break; default: break; } return (0); } int ti_sysc_clock_enable(device_t dev) { struct clk_list *clkp, *clkp_tmp; struct ti_sysc_softc *sc = device_get_softc(dev); int err; TAILQ_FOREACH_SAFE(clkp, &sc->clk_list, next, clkp_tmp) { err = clk_enable(clkp->clk); if (err) { DPRINTF(sc->dev, "clk_enable %s failed %d\n", clk_get_name(clkp->clk), err); break; } } return (err); } int ti_sysc_clock_disable(device_t dev) { struct clk_list *clkp, *clkp_tmp; struct ti_sysc_softc *sc = device_get_softc(dev); int err = 0; TAILQ_FOREACH_SAFE(clkp, &sc->clk_list, next, clkp_tmp) { err = clk_disable(clkp->clk); if (err) { DPRINTF(sc->dev, "clk_enable %s failed %d\n", clk_get_name(clkp->clk), err); break; } } return (err); } static int parse_regfields(struct ti_sysc_softc *sc) { phandle_t node; uint32_t parent_address_cells; uint32_t parent_size_cells; cell_t *reg; ssize_t nreg; int err, k, reg_i, prop_idx; uint32_t idx; node = ofw_bus_get_node(sc->dev); /* Get parents address and size properties */ err = OF_searchencprop(OF_parent(node), "#address-cells", &parent_address_cells, sizeof(parent_address_cells)); if (err == -1) return (ENXIO); if (!(parent_address_cells == 1 || parent_address_cells == 2)) { DPRINTF(sc->dev, "Expect parent #address-cells=[1||2]\n"); return (ENXIO); } err = OF_searchencprop(OF_parent(node), "#size-cells", &parent_size_cells, sizeof(parent_size_cells)); if (err == -1) return (ENXIO); if (!(parent_size_cells == 1 || parent_size_cells == 2)) { DPRINTF(sc->dev, "Expect parent #size-cells = [1||2]\n"); return (ENXIO); } /* Grab the content of reg properties */ nreg = OF_getproplen(node, "reg"); reg = malloc(nreg, M_DEVBUF, M_WAITOK); OF_getencprop(node, "reg", reg, nreg); /* Make sure address & size are 0 */ for (idx = 0; idx < REG_MAX; idx++) { sc->reg[idx].address = 0; sc->reg[idx].size = 0; } /* Loop through reg-names and figure out which reg-name corresponds to * index populate the values into the reg array. */ for (idx = 0, reg_i = 0; idx < REG_MAX && reg_i < nreg; idx++) { err = ofw_bus_find_string_index(node, "reg-names", reg_names[idx], &prop_idx); if (err != 0) continue; for (k = 0; k < parent_address_cells; k++) { sc->reg[prop_idx].address <<= 32; sc->reg[prop_idx].address |= reg[reg_i++]; } for (k = 0; k < parent_size_cells; k++) { sc->reg[prop_idx].size <<= 32; sc->reg[prop_idx].size |= reg[reg_i++]; } if (sc->sc.nranges == 0) sc->offset_reg[prop_idx] = sc->reg[prop_idx].address; else sc->offset_reg[prop_idx] = sc->reg[prop_idx].address - sc->sc.ranges[REG_REV].host; DPRINTF(sc->dev, "reg[%s] adress %#jx size %#jx\n", reg_names[idx], sc->reg[prop_idx].address, sc->reg[prop_idx].size); } free(reg, M_DEVBUF); return (0); } static void parse_idle(struct ti_sysc_softc *sc, const char *name, uint32_t *idle) { phandle_t node; cell_t value[SYSC_IDLE_MAX]; int len, no, i; node = ofw_bus_get_node(sc->dev); if (!OF_hasprop(node, name)) { return; } len = OF_getproplen(node, name); no = len / sizeof(cell_t); if (no >= SYSC_IDLE_MAX) { DPRINTF(sc->dev, "Limit %s\n", name); no = SYSC_IDLE_MAX-1; len = no * sizeof(cell_t); } OF_getencprop(node, name, value, len); for (i = 0; i < no; i++) { idle[i] = value[i]; #if DEBUG_SYSC DPRINTF(sc->dev, "%s[%d] = %d ", name, i, value[i]); switch(value[i]) { case SYSC_IDLE_FORCE: DPRINTF(sc->dev, "SYSC_IDLE_FORCE\n"); break; case SYSC_IDLE_NO: DPRINTF(sc->dev, "SYSC_IDLE_NO\n"); break; case SYSC_IDLE_SMART: DPRINTF(sc->dev, "SYSC_IDLE_SMART\n"); break; case SYSC_IDLE_SMART_WKUP: DPRINTF(sc->dev, "SYSC_IDLE_SMART_WKUP\n"); break; } #endif } for ( ; i < SYSC_IDLE_MAX; i++) idle[i] = -1; } static int ti_sysc_attach_clocks(struct ti_sysc_softc *sc) { clk_t *clk; struct clk_list *clkp; int index, err; - phandle_t cnode; clk = malloc(sc->num_clocks*sizeof(clk_t), M_DEVBUF, M_WAITOK | M_ZERO); - cnode = ofw_bus_get_node(sc->dev); - /* Check if all clocks can be found */ for (index = 0; index < sc->num_clocks; index++) { err = clk_get_by_ofw_index(sc->dev, 0, index, &clk[index]); if (err != 0) { free(clk, M_DEVBUF); return (1); } } /* All clocks are found, add to list */ for (index = 0; index < sc->num_clocks; index++) { clkp = malloc(sizeof(*clkp), M_DEVBUF, M_WAITOK | M_ZERO); clkp->clk = clk[index]; TAILQ_INSERT_TAIL(&sc->clk_list, clkp, next); } /* Release the clk array */ free(clk, M_DEVBUF); return (0); } static int ti_sysc_simplebus_attach_child(device_t dev) { device_t cdev; phandle_t node, child; struct ti_sysc_softc *sc = device_get_softc(dev); node = ofw_bus_get_node(sc->dev); for (child = OF_child(node); child > 0; child = OF_peer(child)) { cdev = simplebus_add_device(sc->dev, child, 0, NULL, -1, NULL); if (cdev != NULL) device_probe_and_attach(cdev); } return (0); } /* Device interface */ static int ti_sysc_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, "TI SYSC Interconnect"); return (BUS_PROBE_DEFAULT); } static int ti_sysc_attach(device_t dev) { struct ti_sysc_softc *sc; phandle_t node; int err; cell_t value; sc = device_get_softc(dev); sc->dev = dev; sc->device_type = ofw_bus_search_compatible(dev, compat_data)->ocd_data; node = ofw_bus_get_node(sc->dev); /* ranges - use simplebus */ simplebus_init(sc->dev, node); if (simplebus_fill_ranges(node, &sc->sc) < 0) { DPRINTF(sc->dev, "could not get ranges\n"); return (ENXIO); } if (sc->sc.nranges == 0) { DPRINTF(sc->dev, "nranges == 0\n"); return (ENXIO); } /* Required field reg & reg-names - assume at least "rev" exists */ err = parse_regfields(sc); if (err) { DPRINTF(sc->dev, "parse_regfields failed %d\n", err); return (ENXIO); } /* Optional */ if (OF_hasprop(node, "ti,sysc-mask")) { OF_getencprop(node, "ti,sysc-mask", &value, sizeof(cell_t)); sc->ti_sysc_mask = value; } if (OF_hasprop(node, "ti,syss-mask")) { OF_getencprop(node, "ti,syss-mask", &value, sizeof(cell_t)); sc->ti_syss_mask = value; } if (OF_hasprop(node, "ti,sysc-delay-us")) { OF_getencprop(node, "ti,sysc-delay-us", &value, sizeof(cell_t)); sc->ti_sysc_delay_us = value; } DPRINTF(sc->dev, "sysc_mask %x syss_mask %x delay_us %x\n", sc->ti_sysc_mask, sc->ti_syss_mask, sc->ti_sysc_delay_us); parse_idle(sc, "ti,sysc-midle", sc->ti_sysc_midle); parse_idle(sc, "ti,sysc-sidle", sc->ti_sysc_sidle); if (OF_hasprop(node, "ti,no-reset-on-init")) sc->ti_no_reset_on_init = true; else sc->ti_no_reset_on_init = false; if (OF_hasprop(node, "ti,no-idle-on-init")) sc->ti_no_idle_on_init = true; else sc->ti_no_idle_on_init = false; if (OF_hasprop(node, "ti,no-idle")) sc->ti_no_idle = true; else sc->ti_no_idle = false; DPRINTF(sc->dev, "no-reset-on-init %d, no-idle-on-init %d, no-idle %d\n", sc->ti_no_reset_on_init, sc->ti_no_idle_on_init, sc->ti_no_idle); if (OF_hasprop(node, "clocks")) { struct clock_cell_info cell_info; read_clock_cells(sc->dev, &cell_info); free(cell_info.clock_cells, M_DEVBUF); free(cell_info.clock_cells_ncells, M_DEVBUF); sc->num_clocks = cell_info.num_real_clocks; TAILQ_INIT(&sc->clk_list); err = ti_sysc_attach_clocks(sc); if (err) { DPRINTF(sc->dev, "Failed to attach clocks\n"); return (bus_generic_attach(sc->dev)); } } err = ti_sysc_simplebus_attach_child(sc->dev); if (err) { DPRINTF(sc->dev, "ti_sysc_simplebus_attach_child %d\n", err); return (err); } sc->attach_done = true; return (bus_generic_attach(sc->dev)); } static int ti_sysc_detach(device_t dev) { return (EBUSY); } /* Bus interface */ static void ti_sysc_new_pass(device_t dev) { struct ti_sysc_softc *sc; int err; phandle_t node; sc = device_get_softc(dev); if (sc->attach_done) { bus_generic_new_pass(sc->dev); return; } node = ofw_bus_get_node(sc->dev); if (OF_hasprop(node, "clocks")) { err = ti_sysc_attach_clocks(sc); if (err) { DPRINTF(sc->dev, "Failed to attach clocks\n"); return; } } err = ti_sysc_simplebus_attach_child(sc->dev); if (err) { DPRINTF(sc->dev, "ti_sysc_simplebus_attach_child failed %d\n", err); return; } sc->attach_done = true; bus_generic_attach(sc->dev); } static device_method_t ti_sysc_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ti_sysc_probe), DEVMETHOD(device_attach, ti_sysc_attach), DEVMETHOD(device_detach, ti_sysc_detach), /* Bus interface */ DEVMETHOD(bus_new_pass, ti_sysc_new_pass), DEVMETHOD_END }; DEFINE_CLASS_1(ti_sysc, ti_sysc_driver, ti_sysc_methods, sizeof(struct ti_sysc_softc), simplebus_driver); static devclass_t ti_sysc_devclass; EARLY_DRIVER_MODULE(ti_sysc, simplebus, ti_sysc_driver, ti_sysc_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_FIRST);