diff --git a/sys/mips/atheros/apb.c b/sys/mips/atheros/apb.c new file mode 100644 index 000000000000..d71b24210e0d --- /dev/null +++ b/sys/mips/atheros/apb.c @@ -0,0 +1,462 @@ +/*- + * Copyright (c) 2009, Oleksandr Tymoshenko + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, 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 + +#undef APB_DEBUG +#ifdef APB_DEBUG +#define dprintf printf +#else +#define dprintf(x, arg...) +#endif /* APB_DEBUG */ + +static int apb_activate_resource(device_t, device_t, int, int, + struct resource *); +static device_t apb_add_child(device_t, int, const char *, int); +static struct resource * + apb_alloc_resource(device_t, device_t, int, int *, u_long, + u_long, u_long, u_int); +static int apb_attach(device_t); +static int apb_deactivate_resource(device_t, device_t, int, int, + struct resource *); +static struct resource_list * + apb_get_resource_list(device_t, device_t); +static void apb_hinted_child(device_t, const char *, int); +static int apb_intr(void *); +static int apb_probe(device_t); +static int apb_release_resource(device_t, device_t, int, int, + struct resource *); +static int apb_setup_intr(device_t, device_t, struct resource *, int, + driver_filter_t *, driver_intr_t *, void *, void **); +static int apb_teardown_intr(device_t, device_t, struct resource *, + void *); + +static void +apb_mask_irq(void *source) +{ + unsigned int irq = (unsigned int)source; + uint32_t reg; + + reg = ATH_READ_REG(AR71XX_MISC_INTR_MASK); + ATH_WRITE_REG(AR71XX_MISC_INTR_MASK, reg & ~(1 << irq)); + +} + +static void +apb_unmask_irq(void *source) +{ + uint32_t reg; + unsigned int irq = (unsigned int)source; + + reg = ATH_READ_REG(AR71XX_MISC_INTR_MASK); + ATH_WRITE_REG(AR71XX_MISC_INTR_MASK, reg | (1 << irq)); +} + +static int +apb_probe(device_t dev) +{ + + return (0); +} + +static int +apb_attach(device_t dev) +{ + struct apb_softc *sc = device_get_softc(dev); + int rid = 0; + + device_set_desc(dev, "APB Bus bridge"); + + sc->apb_mem_rman.rm_type = RMAN_ARRAY; + sc->apb_mem_rman.rm_descr = "APB memory window"; + + if (rman_init(&sc->apb_mem_rman) != 0 || + rman_manage_region(&sc->apb_mem_rman, + AR71XX_APB_BASE, + AR71XX_APB_BASE + AR71XX_APB_SIZE - 1) != 0) + panic("apb_attach: failed to set up memory rman"); + + sc->apb_irq_rman.rm_type = RMAN_ARRAY; + sc->apb_irq_rman.rm_descr = "APB IRQ"; + + if (rman_init(&sc->apb_irq_rman) != 0 || + rman_manage_region(&sc->apb_irq_rman, + APB_IRQ_BASE, APB_IRQ_END) != 0) + panic("apb_attach: failed to set up IRQ rman"); + + if ((sc->sc_misc_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, + RF_SHAREABLE | RF_ACTIVE)) == NULL) { + device_printf(dev, "unable to allocate IRQ resource\n"); + return (ENXIO); + } + + if ((bus_setup_intr(dev, sc->sc_misc_irq, INTR_TYPE_MISC, + apb_intr, NULL, sc, &sc->sc_misc_ih))) { + device_printf(dev, + "WARNING: unable to register interrupt handler\n"); + return (ENXIO); + } + + bus_generic_probe(dev); + bus_enumerate_hinted_children(dev); + bus_generic_attach(dev); + + return (0); +} + +static struct resource * +apb_alloc_resource(device_t bus, device_t child, int type, int *rid, + u_long start, u_long end, u_long count, u_int flags) +{ + struct apb_softc *sc = device_get_softc(bus); + struct apb_ivar *ivar = device_get_ivars(child); + struct resource *rv; + struct resource_list_entry *rle; + struct rman *rm; + int isdefault, needactivate, passthrough; + + isdefault = (start == 0UL && end == ~0UL); + needactivate = flags & RF_ACTIVE; + /* + * Pass memory requests to nexus device + */ + passthrough = (device_get_parent(child) != bus); + rle = NULL; + + dprintf("%s: entry (%p, %p, %d, %d, %p, %p, %ld, %d)\n", + __func__, bus, child, type, *rid, (void *)(intptr_t)start, + (void *)(intptr_t)end, count, flags); + + if (passthrough) + return (BUS_ALLOC_RESOURCE(device_get_parent(bus), child, type, + rid, start, end, count, flags)); + + /* + * If this is an allocation of the "default" range for a given RID, + * and we know what the resources for this device are (ie. they aren't + * maintained by a child bus), then work out the start/end values. + */ + + if (isdefault) { + rle = resource_list_find(&ivar->resources, type, *rid); + if (rle == NULL) { + return (NULL); + } + + if (rle->res != NULL) { + panic("%s: resource entry is busy", __func__); + } + start = rle->start; + end = rle->end; + count = rle->count; + + dprintf("%s: default resource (%p, %p, %ld)\n", + __func__, (void *)(intptr_t)start, + (void *)(intptr_t)end, count); + } + + switch (type) { + case SYS_RES_IRQ: + rm = &sc->apb_irq_rman; + break; + case SYS_RES_MEMORY: + rm = &sc->apb_mem_rman; + break; + default: + printf("%s: unknown resource type %d\n", __func__, type); + return (0); + } + + rv = rman_reserve_resource(rm, start, end, count, flags, child); + if (rv == 0) { + printf("%s: could not reserve resource\n", __func__); + return (0); + } + + rman_set_rid(rv, *rid); + + if (needactivate) { + if (bus_activate_resource(child, type, *rid, rv)) { + printf("%s: could not activate resource\n", __func__); + rman_release_resource(rv); + return (0); + } + } + + return (rv); +} + +static int +apb_activate_resource(device_t bus, device_t child, int type, int rid, + struct resource *r) +{ + + /* XXX: should we mask/unmask IRQ here? */ + return (BUS_ACTIVATE_RESOURCE(device_get_parent(bus), child, + type, rid, r)); +} + +static int +apb_deactivate_resource(device_t bus, device_t child, int type, int rid, + struct resource *r) +{ + + /* XXX: should we mask/unmask IRQ here? */ + return (BUS_DEACTIVATE_RESOURCE(device_get_parent(bus), child, + type, rid, r)); +} + +static int +apb_release_resource(device_t dev, device_t child, int type, + int rid, struct resource *r) +{ + struct resource_list *rl; + struct resource_list_entry *rle; + + rl = apb_get_resource_list(dev, child); + if (rl == NULL) + return (EINVAL); + rle = resource_list_find(rl, type, rid); + if (rle == NULL) + return (EINVAL); + rman_release_resource(r); + rle->res = NULL; + + return (0); +} + +static int +apb_setup_intr(device_t bus, device_t child, struct resource *ires, + int flags, driver_filter_t *filt, driver_intr_t *handler, + void *arg, void **cookiep) +{ + struct apb_softc *sc = device_get_softc(bus); + struct intr_event *event; + int irq, error; + + irq = rman_get_start(ires); + + if (irq > APB_IRQ_END) + panic("%s: bad irq %d", __func__, irq); + + event = sc->sc_eventstab[irq]; + if (event == NULL) { + error = intr_event_create(&event, (void *)irq, 0, irq, + apb_mask_irq, apb_unmask_irq, + NULL, NULL, + "apb intr%d:", irq); + + if (error == 0) { + sc->sc_eventstab[irq] = event; + sc->sc_intr_counter[irq] = + mips_intrcnt_create(event->ie_name); + } + else + return (error); + } + + intr_event_add_handler(event, device_get_nameunit(child), filt, + handler, arg, intr_priority(flags), flags, cookiep); + mips_intrcnt_setname(sc->sc_intr_counter[irq], event->ie_fullname); + + apb_unmask_irq((void*)irq); + + return (0); +} + +static int +apb_teardown_intr(device_t dev, device_t child, struct resource *ires, + void *cookie) +{ + struct apb_softc *sc = device_get_softc(dev); + int irq, result; + + irq = rman_get_start(ires); + if (irq > APB_IRQ_END) + panic("%s: bad irq %d", __func__, irq); + + if (sc->sc_eventstab[irq] == NULL) + panic("Trying to teardown unoccupied IRQ"); + + apb_mask_irq((void*)irq); + + result = intr_event_remove_handler(cookie); + if (!result) + sc->sc_eventstab[irq] = NULL; + + return (result); +} + +static int +apb_intr(void *arg) +{ + struct apb_softc *sc = arg; + struct intr_event *event; + uint32_t reg, irq; + + reg = ATH_READ_REG(AR71XX_MISC_INTR_STATUS); + for (irq = 0; irq < APB_NIRQS; irq++) { + if (reg & (1 << irq)) { + event = sc->sc_eventstab[irq]; + if (!event || TAILQ_EMPTY(&event->ie_handlers)) { + /* Ignore timer interrupts */ + if (irq != 0) + printf("Stray APB IRQ %d\n", irq); + continue; + } + + /* TODO: frame instead of NULL? */ + intr_event_handle(event, NULL); + mips_intrcnt_inc(sc->sc_intr_counter[irq]); + } + } + + return (FILTER_HANDLED); +} + +static void +apb_hinted_child(device_t bus, const char *dname, int dunit) +{ + device_t child; + long maddr; + int msize; + int irq; + int result; + int mem_hints_count; + + child = BUS_ADD_CHILD(bus, 0, dname, dunit); + + /* + * Set hard-wired resources for hinted child using + * specific RIDs. + */ + mem_hints_count = 0; + if (resource_long_value(dname, dunit, "maddr", &maddr) == 0) + mem_hints_count++; + if (resource_int_value(dname, dunit, "msize", &msize) == 0) + mem_hints_count++; + + /* check if all info for mem resource has been provided */ + if ((mem_hints_count > 0) && (mem_hints_count < 2)) { + printf("Either maddr or msize hint is missing for %s%d\n", + dname, dunit); + } else if (mem_hints_count) { + result = bus_set_resource(child, SYS_RES_MEMORY, 0, + maddr, msize); + if (result != 0) + device_printf(bus, + "warning: bus_set_resource() failed\n"); + } + + if (resource_int_value(dname, dunit, "irq", &irq) == 0) { + result = bus_set_resource(child, SYS_RES_IRQ, 0, irq, 1); + if (result != 0) + device_printf(bus, + "warning: bus_set_resource() failed\n"); + } +} + +static device_t +apb_add_child(device_t bus, int order, const char *name, int unit) +{ + device_t child; + struct apb_ivar *ivar; + + ivar = malloc(sizeof(struct apb_ivar), M_DEVBUF, M_WAITOK | M_ZERO); + if (ivar == NULL) { + printf("Failed to allocate ivar\n"); + return (0); + } + resource_list_init(&ivar->resources); + + child = device_add_child_ordered(bus, order, name, unit); + if (child == NULL) { + printf("Can't add child %s%d ordered\n", name, unit); + return (0); + } + + device_set_ivars(child, ivar); + + return (child); +} + +/* + * Helper routine for bus_generic_rl_get_resource/bus_generic_rl_set_resource + * Provides pointer to resource_list for these routines + */ +static struct resource_list * +apb_get_resource_list(device_t dev, device_t child) +{ + struct apb_ivar *ivar; + + ivar = device_get_ivars(child); + return (&(ivar->resources)); +} + +static device_method_t apb_methods[] = { + DEVMETHOD(bus_activate_resource, apb_activate_resource), + DEVMETHOD(bus_add_child, apb_add_child), + DEVMETHOD(bus_alloc_resource, apb_alloc_resource), + DEVMETHOD(bus_deactivate_resource, apb_deactivate_resource), + DEVMETHOD(bus_get_resource_list, apb_get_resource_list), + DEVMETHOD(bus_hinted_child, apb_hinted_child), + DEVMETHOD(bus_print_child, bus_generic_print_child), + DEVMETHOD(bus_release_resource, apb_release_resource), + DEVMETHOD(bus_setup_intr, apb_setup_intr), + DEVMETHOD(bus_teardown_intr, apb_teardown_intr), + DEVMETHOD(device_attach, apb_attach), + DEVMETHOD(device_probe, apb_probe), + DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), + DEVMETHOD(bus_set_resource, bus_generic_rl_set_resource), + + {0, 0}, +}; + +static driver_t apb_driver = { + "apb", + apb_methods, + sizeof(struct apb_softc), +}; +static devclass_t apb_devclass; + +DRIVER_MODULE(apb, nexus, apb_driver, apb_devclass, 0, 0); diff --git a/sys/mips/atheros/apbvar.h b/sys/mips/atheros/apbvar.h new file mode 100644 index 000000000000..2706034d2546 --- /dev/null +++ b/sys/mips/atheros/apbvar.h @@ -0,0 +1,50 @@ +/*- + * Copyright (c) 2009, Oleksandr Tymoshenko + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, 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. + */ + +#ifndef _APBVAR_H_ +#define _APBVAR_H_ + +#define APB_IRQ_BASE 0 +#define APB_IRQ_END 7 +#define APB_NIRQS 8 + +struct apb_softc { + struct rman apb_irq_rman; + struct rman apb_mem_rman; + /* IRQ events structs for child devices */ + struct intr_event *sc_eventstab[APB_NIRQS]; + mips_intrcnt_t sc_intr_counter[APB_NIRQS]; + /* Resources and cookies for MIPS CPU INTs */ + struct resource *sc_misc_irq; + void *sc_misc_ih; +}; + +struct apb_ivar { + struct resource_list resources; +}; + +#endif /* _APBVAR_H_ */ diff --git a/sys/mips/atheros/ar71xx_bus_space_reversed.c b/sys/mips/atheros/ar71xx_bus_space_reversed.c new file mode 100644 index 000000000000..e03ade628f5d --- /dev/null +++ b/sys/mips/atheros/ar71xx_bus_space_reversed.c @@ -0,0 +1,181 @@ +/*- + * Copyright (c) 2009, Oleksandr Tymoshenko + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, 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 + +static bs_r_1_proto(reversed); +static bs_r_2_proto(reversed); +static bs_w_1_proto(reversed); +static bs_w_2_proto(reversed); + +/* + * Bus space that handles offsets in word for 1/2 bytes read/write access. + * Byte order of values is handled by device drivers itself. + */ +static struct bus_space bus_space_reversed = { + /* cookie */ + (void *) 0, + + /* mapping/unmapping */ + generic_bs_map, + generic_bs_unmap, + generic_bs_subregion, + + /* allocation/deallocation */ + NULL, + NULL, + + /* barrier */ + generic_bs_barrier, + + /* read (single) */ + reversed_bs_r_1, + reversed_bs_r_2, + generic_bs_r_4, + NULL, + + /* read multiple */ + generic_bs_rm_1, + generic_bs_rm_2, + generic_bs_rm_4, + NULL, + + /* read region */ + generic_bs_rr_1, + generic_bs_rr_2, + generic_bs_rr_4, + NULL, + + /* write (single) */ + reversed_bs_w_1, + reversed_bs_w_2, + generic_bs_w_4, + NULL, + + /* write multiple */ + generic_bs_wm_1, + generic_bs_wm_2, + generic_bs_wm_4, + NULL, + + /* write region */ + NULL, + generic_bs_wr_2, + generic_bs_wr_4, + NULL, + + /* set multiple */ + NULL, + NULL, + NULL, + NULL, + + /* set region */ + NULL, + generic_bs_sr_2, + generic_bs_sr_4, + NULL, + + /* copy */ + NULL, + generic_bs_c_2, + NULL, + NULL, + + /* read (single) stream */ + generic_bs_r_1, + generic_bs_r_2, + generic_bs_r_4, + NULL, + + /* read multiple stream */ + generic_bs_rm_1, + generic_bs_rm_2, + generic_bs_rm_4, + NULL, + + /* read region stream */ + generic_bs_rr_1, + generic_bs_rr_2, + generic_bs_rr_4, + NULL, + + /* write (single) stream */ + generic_bs_w_1, + generic_bs_w_2, + generic_bs_w_4, + NULL, + + /* write multiple stream */ + generic_bs_wm_1, + generic_bs_wm_2, + generic_bs_wm_4, + NULL, + + /* write region stream */ + NULL, + generic_bs_wr_2, + generic_bs_wr_4, + NULL, +}; + +bus_space_tag_t ar71xx_bus_space_reversed = &bus_space_reversed; + +static uint8_t +reversed_bs_r_1(void *t, bus_space_handle_t h, bus_size_t o) +{ + + return readb(h + (o &~ 3) + (3 - (o & 3))); +} + +static void +reversed_bs_w_1(void *t, bus_space_handle_t h, bus_size_t o, u_int8_t v) +{ + + writeb(h + (o &~ 3) + (3 - (o & 3)), v); +} + +static uint16_t +reversed_bs_r_2(void *t, bus_space_handle_t h, bus_size_t o) +{ + + return readw(h + (o &~ 3) + (2 - (o & 3))); +} + +static void +reversed_bs_w_2(void *t, bus_space_handle_t h, bus_size_t o, uint16_t v) +{ + + writew(h + (o &~ 3) + (2 - (o & 3)), v); +} diff --git a/sys/mips/atheros/ar71xx_bus_space_reversed.h b/sys/mips/atheros/ar71xx_bus_space_reversed.h new file mode 100644 index 000000000000..c8d28dc70350 --- /dev/null +++ b/sys/mips/atheros/ar71xx_bus_space_reversed.h @@ -0,0 +1,33 @@ +/*- + * Copyright (c) 2009, Oleksandr Tymoshenko + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, 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. + */ + +#ifndef __AR71XX_BUS_SPACE_REVERSEDH__ +#define __AR71XX_BUS_SPACE_REVERSEDH__ + +extern bus_space_tag_t ar71xx_bus_space_reversed; + +#endif /* __AR71XX_BUS_SPACE_REVERSEDH__ */ diff --git a/sys/mips/atheros/ar71xx_ehci.c b/sys/mips/atheros/ar71xx_ehci.c new file mode 100644 index 000000000000..936fd355a00e --- /dev/null +++ b/sys/mips/atheros/ar71xx_ehci.c @@ -0,0 +1,288 @@ +/*- + * Copyright (c) 2008 Sam Leffler. 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. + */ + +/* + * AR71XX attachment driver for the USB Enhanced Host Controller. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include "opt_bus.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#define EHCI_HC_DEVSTR "AR71XX Integrated USB 2.0 controller" + +struct ar71xx_ehci_softc { + ehci_softc_t base; /* storage for EHCI code */ +}; + +static device_attach_t ar71xx_ehci_attach; +static device_detach_t ar71xx_ehci_detach; +static device_shutdown_t ar71xx_ehci_shutdown; +static device_suspend_t ar71xx_ehci_suspend; +static device_resume_t ar71xx_ehci_resume; + +bs_r_1_proto(reversed); +bs_w_1_proto(reversed); + +static int +ar71xx_ehci_suspend(device_t self) +{ + ehci_softc_t *sc = device_get_softc(self); + int err; + + err = bus_generic_suspend(self); + if (err) + return (err); + ehci_suspend(sc); + return (0); +} + +static int +ar71xx_ehci_resume(device_t self) +{ + ehci_softc_t *sc = device_get_softc(self); + + ehci_resume(sc); + + bus_generic_resume(self); + + return (0); +} + +static int +ar71xx_ehci_shutdown(device_t self) +{ + ehci_softc_t *sc = device_get_softc(self); + int err; + + err = bus_generic_shutdown(self); + if (err) + return (err); + ehci_shutdown(sc); + + return (0); +} + +static int +ar71xx_ehci_probe(device_t self) +{ + + device_set_desc(self, EHCI_HC_DEVSTR); + + return (BUS_PROBE_DEFAULT); +} + +static int +ar71xx_ehci_attach(device_t self) +{ + struct ar71xx_ehci_softc *isc = device_get_softc(self); + ehci_softc_t *sc = &isc->base; + int err; + int rid; + + /* initialise some bus fields */ + sc->sc_bus.parent = self; + sc->sc_bus.devices = sc->sc_devices; + sc->sc_bus.devices_max = EHCI_MAX_DEVICES; + + /* get all DMA memory */ + if (usb_bus_mem_alloc_all(&sc->sc_bus, + USB_GET_DMA_TAG(self), &ehci_iterate_hw_softc)) { + return (ENOMEM); + } + + sc->sc_bus.usbrev = USB_REV_2_0; + + /* NB: hints fix the memory location and irq */ + + rid = 0; + sc->sc_io_res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid, RF_ACTIVE); + if (!sc->sc_io_res) { + device_printf(self, "Could not map memory\n"); + goto error; + } + + /* + * Craft special resource for bus space ops that handle + * byte-alignment of non-word addresses. + */ + sc->sc_io_tag = ar71xx_bus_space_reversed; + sc->sc_io_hdl = rman_get_bushandle(sc->sc_io_res); + sc->sc_io_size = rman_get_size(sc->sc_io_res); + + rid = 0; + sc->sc_irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid, + RF_ACTIVE); + if (sc->sc_irq_res == NULL) { + device_printf(self, "Could not allocate irq\n"); + goto error; + } + sc->sc_bus.bdev = device_add_child(self, "usbus", -1); + if (!sc->sc_bus.bdev) { + device_printf(self, "Could not add USB device\n"); + goto error; + } + device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); + device_set_desc(sc->sc_bus.bdev, EHCI_HC_DEVSTR); + + sprintf(sc->sc_vendor, "Atheros"); + + + err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + NULL, (driver_intr_t *)ehci_interrupt, sc, &sc->sc_intr_hdl); + if (err) { + device_printf(self, "Could not setup irq, %d\n", err); + sc->sc_intr_hdl = NULL; + goto error; + } + + /* + * Arrange to force Host mode, select big-endian byte alignment, + * and arrange to not terminate reset operations (the adapter + * will ignore it if we do but might as well save a reg write). + * Also, the controller has an embedded Transaction Translator + * which means port speed must be read from the Port Status + * register following a port enable. + */ + sc->sc_flags = EHCI_SCFLG_SETMODE; + (void) ehci_reset(sc); + + err = ehci_init(sc); + if (!err) { + err = device_probe_and_attach(sc->sc_bus.bdev); + } + if (err) { + device_printf(self, "USB init failed err=%d\n", err); + goto error; + } + return (0); + +error: + ar71xx_ehci_detach(self); + return (ENXIO); +} + +static int +ar71xx_ehci_detach(device_t self) +{ + struct ar71xx_ehci_softc *isc = device_get_softc(self); + ehci_softc_t *sc = &isc->base; + device_t bdev; + int err; + + if (sc->sc_bus.bdev) { + bdev = sc->sc_bus.bdev; + device_detach(bdev); + device_delete_child(self, bdev); + } + /* during module unload there are lots of children leftover */ + device_delete_all_children(self); + + /* + * disable interrupts that might have been switched on in ehci_init + */ + if (sc->sc_io_res) { + EWRITE4(sc, EHCI_USBINTR, 0); + } + + if (sc->sc_irq_res && sc->sc_intr_hdl) { + /* + * only call ehci_detach() after ehci_init() + */ + ehci_detach(sc); + + err = bus_teardown_intr(self, sc->sc_irq_res, sc->sc_intr_hdl); + + if (err) + /* XXX or should we panic? */ + device_printf(self, "Could not tear down irq, %d\n", + err); + sc->sc_intr_hdl = NULL; + } + + if (sc->sc_irq_res) { + bus_release_resource(self, SYS_RES_IRQ, 0, sc->sc_irq_res); + sc->sc_irq_res = NULL; + } + if (sc->sc_io_res) { + bus_release_resource(self, SYS_RES_MEMORY, 0, + sc->sc_io_res); + sc->sc_io_res = NULL; + } + usb_bus_mem_free_all(&sc->sc_bus, &ehci_iterate_hw_softc); + + return (0); +} + +static device_method_t ehci_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ar71xx_ehci_probe), + DEVMETHOD(device_attach, ar71xx_ehci_attach), + DEVMETHOD(device_detach, ar71xx_ehci_detach), + DEVMETHOD(device_suspend, ar71xx_ehci_suspend), + DEVMETHOD(device_resume, ar71xx_ehci_resume), + DEVMETHOD(device_shutdown, ar71xx_ehci_shutdown), + + /* Bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + + {0, 0} +}; + +static driver_t ehci_driver = { + "ehci", + ehci_methods, + sizeof(struct ar71xx_ehci_softc), +}; + +static devclass_t ehci_devclass; + +DRIVER_MODULE(ehci, nexus, ehci_driver, ehci_devclass, 0, 0); +MODULE_DEPEND(ehci, usb, 1, 1, 1); diff --git a/sys/mips/atheros/ar71xx_machdep.c b/sys/mips/atheros/ar71xx_machdep.c new file mode 100644 index 000000000000..eaf08f27e780 --- /dev/null +++ b/sys/mips/atheros/ar71xx_machdep.c @@ -0,0 +1,262 @@ +/*- + * Copyright (c) 2009 Oleksandr Tymoshenko + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include + +#include + +#include "opt_ddb.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include + +extern int *edata; +extern int *end; +uint32_t ar711_base_mac[ETHER_ADDR_LEN]; +/* 4KB static data aread to keep a copy of the bootload env until + the dynamic kenv is setup */ +char boot1_env[4096]; + +/* + * We get a string in from Redboot with the all the arguments together, + * "foo=bar bar=baz". Split them up and save in kenv. + */ +static void +parse_argv(char *str) +{ + char *n, *v; + + while ((v = strsep(&str, " ")) != NULL) { + if (*v == '\0') + continue; + if (*v == '-') { + while (*v != '\0') { + v++; + switch (*v) { + case 'a': boothowto |= RB_ASKNAME; break; + case 'd': boothowto |= RB_KDB; break; + case 'g': boothowto |= RB_GDB; break; + case 's': boothowto |= RB_SINGLE; break; + case 'v': boothowto |= RB_VERBOSE; break; + } + } + } else { + n = strsep(&v, "="); + if (v == NULL) + setenv(n, "1"); + else + setenv(n, v); + } + } +} + +void +platform_cpu_init() +{ + /* Nothing special */ +} + +void +platform_halt(void) +{ + +} + +void +platform_identify(void) +{ + +} + +void +platform_reset(void) +{ + uint32_t reg = ATH_READ_REG(AR71XX_RST_RESET); + + ATH_WRITE_REG(AR71XX_RST_RESET, reg | RST_RESET_FULL_CHIP); + /* Wait for reset */ + while(1) + ; +} + +void +platform_trap_enter(void) +{ + +} + +void +platform_trap_exit(void) +{ + +} + +void +platform_start(__register_t a0 __unused, __register_t a1 __unused, + __register_t a2 __unused, __register_t a3 __unused) +{ + vm_offset_t kernend; + uint64_t platform_counter_freq; + uint32_t reg; + int argc, i, count = 0; + char **argv, **envp; + + /* clear the BSS and SBSS segments */ + kernend = round_page((vm_offset_t)&end); + memset(&edata, 0, kernend - (vm_offset_t)(&edata)); + + /* Initialize pcpu stuff */ + mips_pcpu0_init(); + + argc = a0; + argv = (char**)a1; + envp = (char**)a2; + /* + * Protect ourselves from garbage in registers + */ + if (MIPS_IS_VALID_PTR(envp)) { + for (i = 0; envp[i]; i += 2) + { + if (strcmp(envp[i], "memsize") == 0) + realmem = btoc(strtoul(envp[i+1], NULL, 16)); + else if (strcmp(envp[i], "ethaddr") == 0) { + count = sscanf(envp[i+1], "%x.%x.%x.%x.%x.%x", + &ar711_base_mac[0], &ar711_base_mac[1], + &ar711_base_mac[2], &ar711_base_mac[3], + &ar711_base_mac[4], &ar711_base_mac[5]); + if (count < 6) + memset(ar711_base_mac, 0, + sizeof(ar711_base_mac)); + } + } + } + + /* + * Just wild guess. RedBoot let us down and didn't reported + * memory size + */ + if (realmem == 0) + realmem = btoc(32*1024*1024); + + /* phys_avail regions are in bytes */ + phys_avail[0] = MIPS_KSEG0_TO_PHYS((vm_offset_t)&end); + phys_avail[1] = ctob(realmem); + + physmem = realmem; + + /* + * ns8250 uart code uses DELAY so ticker should be inititalized + * before cninit. And tick_init_params refers to hz, so * init_param1 + * should be called first. + */ + init_param1(); + platform_counter_freq = ar71xx_cpu_freq(); + mips_timer_init_params(platform_counter_freq, 1); + cninit(); + init_static_kenv(boot1_env, sizeof(boot1_env)); + + printf("platform frequency: %lld\n", platform_counter_freq); + printf("arguments: \n"); + printf(" a0 = %08x\n", a0); + printf(" a1 = %08x\n", a1); + printf(" a2 = %08x\n", a2); + printf(" a3 = %08x\n", a3); + + printf("Cmd line:"); + if (MIPS_IS_VALID_PTR(argv)) { + for (i = 0; i < argc; i++) { + printf(" %s", argv[i]); + parse_argv(argv[i]); + } + } + else + printf ("argv is invalid"); + printf("\n"); + + printf("Environment:\n"); + if (MIPS_IS_VALID_PTR(envp)) { + for (i = 0; envp[i]; i+=2) { + printf(" %s = %s\n", envp[i], envp[i+1]); + setenv(envp[i], envp[i+1]); + } + } + else + printf ("envp is invalid\n"); + + init_param2(physmem); + mips_cpu_init(); + pmap_bootstrap(); + mips_proc0_init(); + mutex_init(); + + /* + * Reset USB devices + */ + reg = ATH_READ_REG(AR71XX_RST_RESET); + reg |= + RST_RESET_USB_OHCI_DLL | RST_RESET_USB_HOST | RST_RESET_USB_PHY; + ATH_WRITE_REG(AR71XX_RST_RESET, reg); + DELAY(1000); + reg &= + ~(RST_RESET_USB_OHCI_DLL | RST_RESET_USB_HOST | RST_RESET_USB_PHY); + ATH_WRITE_REG(AR71XX_RST_RESET, reg); + + ATH_WRITE_REG(AR71XX_USB_CTRL_CONFIG, + USB_CTRL_CONFIG_OHCI_DES_SWAP | USB_CTRL_CONFIG_OHCI_BUF_SWAP | + USB_CTRL_CONFIG_EHCI_DES_SWAP | USB_CTRL_CONFIG_EHCI_BUF_SWAP); + + ATH_WRITE_REG(AR71XX_USB_CTRL_FLADJ, + (32 << USB_CTRL_FLADJ_HOST_SHIFT) | (3 << USB_CTRL_FLADJ_A5_SHIFT)); + DELAY(1000); + +#ifdef DDB + kdb_init(); +#endif +} diff --git a/sys/mips/atheros/ar71xx_ohci.c b/sys/mips/atheros/ar71xx_ohci.c new file mode 100644 index 000000000000..acccd850a1c5 --- /dev/null +++ b/sys/mips/atheros/ar71xx_ohci.c @@ -0,0 +1,215 @@ +/*- + * Copyright (c) 2009, Oleksandr Tymoshenko + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, 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 + +static int ar71xx_ohci_attach(device_t dev); +static int ar71xx_ohci_detach(device_t dev); +static int ar71xx_ohci_probe(device_t dev); + +struct ar71xx_ohci_softc +{ + struct ohci_softc sc_ohci; +}; + +static int +ar71xx_ohci_probe(device_t dev) +{ + device_set_desc(dev, "AR71XX integrated OHCI controller"); + return (BUS_PROBE_DEFAULT); +} + +static int +ar71xx_ohci_attach(device_t dev) +{ + struct ar71xx_ohci_softc *sc = device_get_softc(dev); + int err; + int rid; + + /* initialise some bus fields */ + sc->sc_ohci.sc_bus.parent = dev; + sc->sc_ohci.sc_bus.devices = sc->sc_ohci.sc_devices; + sc->sc_ohci.sc_bus.devices_max = OHCI_MAX_DEVICES; + + /* get all DMA memory */ + if (usb_bus_mem_alloc_all(&sc->sc_ohci.sc_bus, + USB_GET_DMA_TAG(dev), &ohci_iterate_hw_softc)) { + return (ENOMEM); + } + + sc->sc_ohci.sc_dev = dev; + + rid = 0; + sc->sc_ohci.sc_io_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, + RF_ACTIVE); + if (sc->sc_ohci.sc_io_res == NULL) { + err = ENOMEM; + goto error; + } + sc->sc_ohci.sc_io_tag = rman_get_bustag(sc->sc_ohci.sc_io_res); + sc->sc_ohci.sc_io_hdl = rman_get_bushandle(sc->sc_ohci.sc_io_res); + sc->sc_ohci.sc_io_size = rman_get_size(sc->sc_ohci.sc_io_res); + + rid = 0; + sc->sc_ohci.sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, + RF_ACTIVE); + if (sc->sc_ohci.sc_irq_res == NULL) { + err = ENOMEM; + goto error; + } + sc->sc_ohci.sc_bus.bdev = device_add_child(dev, "usbus", -1); + if (sc->sc_ohci.sc_bus.bdev == NULL) { + err = ENOMEM; + goto error; + } + device_set_ivars(sc->sc_ohci.sc_bus.bdev, &sc->sc_ohci.sc_bus); + + err = bus_setup_intr(dev, sc->sc_ohci.sc_irq_res, + INTR_TYPE_BIO | INTR_MPSAFE, NULL, + (driver_intr_t *)ohci_interrupt, sc, &sc->sc_ohci.sc_intr_hdl); + if (err) { + err = ENXIO; + goto error; + } + + strlcpy(sc->sc_ohci.sc_vendor, "Atheros", sizeof(sc->sc_ohci.sc_vendor)); + + bus_space_write_4(sc->sc_ohci.sc_io_tag, sc->sc_ohci.sc_io_hdl, OHCI_CONTROL, 0); + + err = ohci_init(&sc->sc_ohci); + if (!err) + err = device_probe_and_attach(sc->sc_ohci.sc_bus.bdev); + + if (err) + goto error; + return (0); + +error: + if (err) { + ar71xx_ohci_detach(dev); + return (err); + } + return (err); +} + +static int +ar71xx_ohci_detach(device_t dev) +{ + struct ar71xx_ohci_softc *sc = device_get_softc(dev); + device_t bdev; + + if (sc->sc_ohci.sc_bus.bdev) { + bdev = sc->sc_ohci.sc_bus.bdev; + device_detach(bdev); + device_delete_child(dev, bdev); + } + /* during module unload there are lots of children leftover */ + device_delete_all_children(dev); + + /* + * Put the controller into reset, then disable clocks and do + * the MI tear down. We have to disable the clocks/hardware + * after we do the rest of the teardown. We also disable the + * clocks in the opposite order we acquire them, but that + * doesn't seem to be absolutely necessary. We free up the + * clocks after we disable them, so the system could, in + * theory, reuse them. + */ + bus_space_write_4(sc->sc_ohci.sc_io_tag, sc->sc_ohci.sc_io_hdl, + OHCI_CONTROL, 0); + + if (sc->sc_ohci.sc_intr_hdl) { + bus_teardown_intr(dev, sc->sc_ohci.sc_irq_res, sc->sc_ohci.sc_intr_hdl); + sc->sc_ohci.sc_intr_hdl = NULL; + } + + if (sc->sc_ohci.sc_irq_res && sc->sc_ohci.sc_intr_hdl) { + /* + * only call ohci_detach() after ohci_init() + */ + ohci_detach(&sc->sc_ohci); + + bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_ohci.sc_irq_res); + sc->sc_ohci.sc_irq_res = NULL; + } + if (sc->sc_ohci.sc_io_res) { + bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_ohci.sc_io_res); + sc->sc_ohci.sc_io_res = NULL; + sc->sc_ohci.sc_io_tag = 0; + sc->sc_ohci.sc_io_hdl = 0; + } + usb_bus_mem_free_all(&sc->sc_ohci.sc_bus, &ohci_iterate_hw_softc); + + return (0); +} + +static device_method_t ohci_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ar71xx_ohci_probe), + DEVMETHOD(device_attach, ar71xx_ohci_attach), + DEVMETHOD(device_detach, ar71xx_ohci_detach), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + + /* Bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + + {0, 0} +}; + +static driver_t ohci_driver = { + "ohci", + ohci_methods, + sizeof(struct ar71xx_ohci_softc), +}; + +static devclass_t ohci_devclass; + +DRIVER_MODULE(ohci, apb, ohci_driver, ohci_devclass, 0, 0); diff --git a/sys/mips/atheros/ar71xx_pci.c b/sys/mips/atheros/ar71xx_pci.c new file mode 100644 index 000000000000..000860d904c5 --- /dev/null +++ b/sys/mips/atheros/ar71xx_pci.c @@ -0,0 +1,559 @@ +/*- + * Copyright (c) 2009, Oleksandr Tymoshenko + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, 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 "pcib_if.h" + +#include +#include + +#undef AR71XX_PCI_DEBUG +#ifdef AR71XX_PCI_DEBUG +#define dprintf printf +#else +#define dprintf(x, arg...) +#endif + +struct ar71xx_pci_softc { + device_t sc_dev; + + int sc_busno; + struct rman sc_mem_rman; + struct rman sc_irq_rman; + + struct intr_event *sc_eventstab[AR71XX_PCI_NIRQS]; + struct resource *sc_irq; + void *sc_ih; +}; + +static int ar71xx_pci_setup_intr(device_t, device_t, struct resource *, int, + driver_filter_t *, driver_intr_t *, void *, void **); +static int ar71xx_pci_teardown_intr(device_t, device_t, struct resource *, + void *); +static int ar71xx_pci_intr(void *); + +static void +ar71xx_pci_mask_irq(void *source) +{ + uint32_t reg; + unsigned int irq = (unsigned int)source; + + reg = ATH_READ_REG(AR71XX_PCI_INTR_MASK); + /* flush */ + reg = ATH_READ_REG(AR71XX_PCI_INTR_MASK); + ATH_WRITE_REG(AR71XX_PCI_INTR_MASK, reg & ~(1 << irq)); +} + +static void +ar71xx_pci_unmask_irq(void *source) +{ + uint32_t reg; + unsigned int irq = (unsigned int)source; + + reg = ATH_READ_REG(AR71XX_PCI_INTR_MASK); + ATH_WRITE_REG(AR71XX_PCI_INTR_MASK, reg | (1 << irq)); + /* flush */ + reg = ATH_READ_REG(AR71XX_PCI_INTR_MASK); +} + +/* + * get bitmask for bytes of interest: + * 0 - we want this byte, 1 - ignore it. e.g: we read 1 byte + * from register 7. Bitmask would be: 0111 + */ +static uint32_t +ar71xx_get_bytes_to_read(int reg, int bytes) +{ + uint32_t bytes_to_read = 0; + if ((bytes % 4) == 0) + bytes_to_read = 0; + else if ((bytes % 4) == 1) + bytes_to_read = (~(1 << (reg % 4))) & 0xf; + else if ((bytes % 4) == 2) + bytes_to_read = (~(3 << (reg % 4))) & 0xf; + else + panic("%s: wrong combination", __func__); + + return (bytes_to_read); +} + +static int +ar71xx_pci_check_bus_error(void) +{ + uint32_t error, addr, has_errors = 0; + error = ATH_READ_REG(AR71XX_PCI_ERROR) & 0x3; + dprintf("%s: PCI error = %02x\n", __func__, error); + if (error) { + addr = ATH_READ_REG(AR71XX_PCI_ERROR_ADDR); + + /* Do not report it yet */ +#if 0 + printf("PCI bus error %d at addr 0x%08x\n", error, addr); +#endif + ATH_WRITE_REG(AR71XX_PCI_ERROR, error); + has_errors = 1; + } + + error = ATH_READ_REG(AR71XX_PCI_AHB_ERROR) & 0x1; + dprintf("%s: AHB error = %02x\n", __func__, error); + if (error) { + addr = ATH_READ_REG(AR71XX_PCI_AHB_ERROR_ADDR); + /* Do not report it yet */ +#if 0 + printf("AHB bus error %d at addr 0x%08x\n", error, addr); +#endif + ATH_WRITE_REG(AR71XX_PCI_AHB_ERROR, error); + has_errors = 1; + } + + return (has_errors); +} + +static uint32_t +ar71xx_pci_make_addr(int bus, int slot, int func, int reg) +{ + if (bus == 0) { + return ((1 << slot) | (func << 8) | (reg & ~3)); + } else { + return ((bus << 16) | (slot << 11) | (func << 8) + | (reg & ~3) | 1); + } +} + +static int +ar71xx_pci_conf_setup(int bus, int slot, int func, int reg, int bytes, + uint32_t cmd) +{ + uint32_t addr = ar71xx_pci_make_addr(bus, slot, func, (reg & ~3)); + cmd |= (ar71xx_get_bytes_to_read(reg, bytes) << 4); + + ATH_WRITE_REG(AR71XX_PCI_CONF_ADDR, addr); + ATH_WRITE_REG(AR71XX_PCI_CONF_CMD, cmd); + + dprintf("%s: tag (%x, %x, %x) %d/%d addr=%08x, cmd=%08x\n", __func__, + bus, slot, func, reg, bytes, addr, cmd); + + return ar71xx_pci_check_bus_error(); +} + +static uint32_t +ar71xx_pci_read_config(device_t dev, u_int bus, u_int slot, u_int func, + u_int reg, int bytes) +{ + uint32_t data; + uint32_t cmd, shift, mask; + + /* register access is 32-bit aligned */ + shift = (reg & 3) * 8; + if (shift) + mask = (1 << shift) - 1; + else + mask = 0xffffffff; + + dprintf("%s: tag (%x, %x, %x) reg %d(%d)\n", __func__, bus, slot, + func, reg, bytes); + + if ((bus == 0) && (slot == 0) && (func == 0)) { + cmd = PCI_LCONF_CMD_READ | (reg & ~3); + ATH_WRITE_REG(AR71XX_PCI_LCONF_CMD, cmd); + data = ATH_READ_REG(AR71XX_PCI_LCONF_READ_DATA); + } else { + if (ar71xx_pci_conf_setup(bus, slot, func, reg, bytes, + PCI_CONF_CMD_READ) == 0) + data = ATH_READ_REG(AR71XX_PCI_CONF_READ_DATA); + else + data = -1; + } + + /* get request bytes from 32-bit word */ + data = (data >> shift) & mask; + + dprintf("%s: read 0x%x\n", __func__, data); + + return (data); +} + +static void +ar71xx_pci_write_config(device_t dev, u_int bus, u_int slot, u_int func, + u_int reg, uint32_t data, int bytes) +{ + uint32_t cmd; + + dprintf("%s: tag (%x, %x, %x) reg %d(%d)\n", __func__, bus, slot, + func, reg, bytes); + + data = data << (8*(reg % 4)); + + if ((bus == 0) && (slot == 0) && (func == 0)) { + cmd = PCI_LCONF_CMD_WRITE | (reg & ~3); + cmd |= ar71xx_get_bytes_to_read(reg, bytes) << 20; + ATH_WRITE_REG(AR71XX_PCI_LCONF_CMD, cmd); + ATH_WRITE_REG(AR71XX_PCI_LCONF_WRITE_DATA, data); + } else { + if (ar71xx_pci_conf_setup(bus, slot, func, reg, bytes, + PCI_CONF_CMD_WRITE) == 0) + ATH_WRITE_REG(AR71XX_PCI_CONF_WRITE_DATA, data); + } +} + +static int +ar71xx_pci_probe(device_t dev) +{ + + return (0); +} + +static int +ar71xx_pci_attach(device_t dev) +{ + int busno = 0; + int rid = 0; + uint32_t reset; + struct ar71xx_pci_softc *sc = device_get_softc(dev); + + sc->sc_mem_rman.rm_type = RMAN_ARRAY; + sc->sc_mem_rman.rm_descr = "ar71xx PCI memory window"; + if (rman_init(&sc->sc_mem_rman) != 0 || + rman_manage_region(&sc->sc_mem_rman, AR71XX_PCI_MEM_BASE, + AR71XX_PCI_MEM_BASE + AR71XX_PCI_MEM_SIZE - 1) != 0) { + panic("ar71xx_pci_attach: failed to set up I/O rman"); + } + + sc->sc_irq_rman.rm_type = RMAN_ARRAY; + sc->sc_irq_rman.rm_descr = "ar71xx PCI IRQs"; + if (rman_init(&sc->sc_irq_rman) != 0 || + rman_manage_region(&sc->sc_irq_rman, AR71XX_PCI_IRQ_START, + AR71XX_PCI_IRQ_END) != 0) + panic("ar71xx_pci_attach: failed to set up IRQ rman"); + + + ATH_WRITE_REG(AR71XX_PCI_INTR_STATUS, 0); + ATH_WRITE_REG(AR71XX_PCI_INTR_MASK, 0); + + /* Hook up our interrupt handler. */ + if ((sc->sc_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, + RF_SHAREABLE | RF_ACTIVE)) == NULL) { + device_printf(dev, "unable to allocate IRQ resource\n"); + return ENXIO; + } + + if ((bus_setup_intr(dev, sc->sc_irq, INTR_TYPE_MISC, + ar71xx_pci_intr, NULL, sc, &sc->sc_ih))) { + device_printf(dev, + "WARNING: unable to register interrupt handler\n"); + return ENXIO; + } + + /* reset PCI core and PCI bus */ + reset = ATH_READ_REG(AR71XX_RST_RESET); + reset |= (RST_RESET_PCI_CORE | RST_RESET_PCI_BUS); + ATH_WRITE_REG(AR71XX_RST_RESET, reset); + ATH_READ_REG(AR71XX_RST_RESET); + DELAY(1000); + + reset &= ~(RST_RESET_PCI_CORE | RST_RESET_PCI_BUS); + ATH_WRITE_REG(AR71XX_RST_RESET, reset); + ATH_READ_REG(AR71XX_RST_RESET); + DELAY(1000); + + /* Init PCI windows */ + ATH_WRITE_REG(AR71XX_PCI_WINDOW0, PCI_WINDOW0_ADDR); + ATH_WRITE_REG(AR71XX_PCI_WINDOW1, PCI_WINDOW1_ADDR); + ATH_WRITE_REG(AR71XX_PCI_WINDOW2, PCI_WINDOW2_ADDR); + ATH_WRITE_REG(AR71XX_PCI_WINDOW3, PCI_WINDOW3_ADDR); + ATH_WRITE_REG(AR71XX_PCI_WINDOW4, PCI_WINDOW4_ADDR); + ATH_WRITE_REG(AR71XX_PCI_WINDOW5, PCI_WINDOW5_ADDR); + ATH_WRITE_REG(AR71XX_PCI_WINDOW6, PCI_WINDOW6_ADDR); + ATH_WRITE_REG(AR71XX_PCI_WINDOW7, PCI_WINDOW7_CONF_ADDR); + DELAY(1000); + + ar71xx_pci_check_bus_error(); + + /* Fixup internal PCI bridge */ + ar71xx_pci_write_config(dev, 0, 0, 0, PCIR_COMMAND, + PCIM_CMD_BUSMASTEREN | PCIM_CMD_MEMEN + | PCIM_CMD_SERRESPEN | PCIM_CMD_BACKTOBACK + | PCIM_CMD_PERRESPEN | PCIM_CMD_MWRICEN, 2); + + device_add_child(dev, "pci", busno); + return (bus_generic_attach(dev)); +} + +static int +ar71xx_pci_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) +{ + struct ar71xx_pci_softc *sc = device_get_softc(dev); + + switch (which) { + case PCIB_IVAR_DOMAIN: + *result = 0; + return (0); + case PCIB_IVAR_BUS: + *result = sc->sc_busno; + return (0); + } + + return (ENOENT); +} + +static int +ar71xx_pci_write_ivar(device_t dev, device_t child, int which, uintptr_t result) +{ + struct ar71xx_pci_softc * sc = device_get_softc(dev); + + switch (which) { + case PCIB_IVAR_BUS: + sc->sc_busno = result; + return (0); + } + + return (ENOENT); +} + +static struct resource * +ar71xx_pci_alloc_resource(device_t bus, device_t child, int type, int *rid, + u_long start, u_long end, u_long count, u_int flags) +{ + + struct ar71xx_pci_softc *sc = device_get_softc(bus); + struct resource *rv; + struct rman *rm; + + switch (type) { + case SYS_RES_IRQ: + rm = &sc->sc_irq_rman; + break; + case SYS_RES_MEMORY: + rm = &sc->sc_mem_rman; + break; + default: + return (NULL); + } + + rv = rman_reserve_resource(rm, start, end, count, flags, child); + + if (rv == NULL) + return (NULL); + + rman_set_rid(rv, *rid); + + if (flags & RF_ACTIVE) { + if (bus_activate_resource(child, type, *rid, rv)) { + rman_release_resource(rv); + return (NULL); + } + } + + + return (rv); +} + + +static int +ar71xx_pci_activate_resource(device_t bus, device_t child, int type, int rid, + struct resource *r) +{ + int res = (BUS_ACTIVATE_RESOURCE(device_get_parent(bus), + child, type, rid, r)); + + if (!res) { + switch(type) { + case SYS_RES_MEMORY: + case SYS_RES_IOPORT: + rman_set_bustag(r, ar71xx_bus_space_pcimem); + break; + } + } + + return (res); +} + + + +static int +ar71xx_pci_setup_intr(device_t bus, device_t child, struct resource *ires, + int flags, driver_filter_t *filt, driver_intr_t *handler, + void *arg, void **cookiep) +{ + struct ar71xx_pci_softc *sc = device_get_softc(bus); + struct intr_event *event; + int irq, error; + + irq = rman_get_start(ires); + + if (irq > AR71XX_PCI_IRQ_END) + panic("%s: bad irq %d", __func__, irq); + + event = sc->sc_eventstab[irq]; + if (event == NULL) { + error = intr_event_create(&event, (void *)irq, 0, irq, + ar71xx_pci_mask_irq, ar71xx_pci_unmask_irq, NULL, NULL, + "ar71xx_pci intr%d:", irq); + + sc->sc_eventstab[irq] = event; + } + + intr_event_add_handler(event, device_get_nameunit(child), filt, + handler, arg, intr_priority(flags), flags, cookiep); + + ar71xx_pci_unmask_irq((void*)irq); + + return (0); +} + +static int +ar71xx_pci_teardown_intr(device_t dev, device_t child, struct resource *ires, + void *cookie) +{ + struct ar71xx_pci_softc *sc = device_get_softc(dev); + int irq, result; + + irq = rman_get_start(ires); + if (irq > AR71XX_PCI_IRQ_END) + panic("%s: bad irq %d", __func__, irq); + + if (sc->sc_eventstab[irq] == NULL) + panic("Trying to teardown unoccupied IRQ"); + + ar71xx_pci_mask_irq((void*)irq); + + result = intr_event_remove_handler(cookie); + if (!result) + sc->sc_eventstab[irq] = NULL; + + return (result); +} + +static int +ar71xx_pci_intr(void *arg) +{ + struct ar71xx_pci_softc *sc = arg; + struct intr_event *event; + uint32_t reg, irq, mask; + + reg = ATH_READ_REG(AR71XX_PCI_INTR_STATUS); + mask = ATH_READ_REG(AR71XX_PCI_INTR_MASK); + /* + * Handle only unmasked interrupts + */ + reg &= mask; + for (irq = AR71XX_PCI_IRQ_START; irq <= AR71XX_PCI_IRQ_END; irq++) { + if (reg & (1 << irq)) { + event = sc->sc_eventstab[irq]; + if (!event || TAILQ_EMPTY(&event->ie_handlers)) { + /* Ignore timer interrupts */ + if (irq != 0) + printf("Stray IRQ %d\n", irq); + continue; + } + + /* TODO: frame instead of NULL? */ + intr_event_handle(event, NULL); + } + } + + return (FILTER_HANDLED); +} + +static int +ar71xx_pci_maxslots(device_t dev) +{ + + return (PCI_SLOTMAX); +} + +static int +ar71xx_pci_route_interrupt(device_t pcib, device_t device, int pin) +{ + if (pci_get_slot(device) < AR71XX_PCI_BASE_SLOT) + panic("%s: PCI slot %d is less then AR71XX_PCI_BASE_SLOT", + __func__, pci_get_slot(device)); + + return (pci_get_slot(device) - AR71XX_PCI_BASE_SLOT); +} + +static device_method_t ar71xx_pci_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ar71xx_pci_probe), + DEVMETHOD(device_attach, ar71xx_pci_attach), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), + + /* Bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + DEVMETHOD(bus_read_ivar, ar71xx_pci_read_ivar), + DEVMETHOD(bus_write_ivar, ar71xx_pci_write_ivar), + DEVMETHOD(bus_alloc_resource, ar71xx_pci_alloc_resource), + DEVMETHOD(bus_release_resource, bus_generic_release_resource), + DEVMETHOD(bus_activate_resource, ar71xx_pci_activate_resource), + DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), + DEVMETHOD(bus_setup_intr, ar71xx_pci_setup_intr), + DEVMETHOD(bus_teardown_intr, ar71xx_pci_teardown_intr), + + /* pcib interface */ + DEVMETHOD(pcib_maxslots, ar71xx_pci_maxslots), + DEVMETHOD(pcib_read_config, ar71xx_pci_read_config), + DEVMETHOD(pcib_write_config, ar71xx_pci_write_config), + DEVMETHOD(pcib_route_interrupt, ar71xx_pci_route_interrupt), + + {0, 0} +}; + +static driver_t ar71xx_pci_driver = { + "pcib", + ar71xx_pci_methods, + sizeof(struct ar71xx_pci_softc), +}; + +static devclass_t ar71xx_pci_devclass; + +DRIVER_MODULE(ar71xx_pci, nexus, ar71xx_pci_driver, ar71xx_pci_devclass, 0, 0); diff --git a/sys/mips/atheros/ar71xx_pci_bus_space.c b/sys/mips/atheros/ar71xx_pci_bus_space.c new file mode 100644 index 000000000000..554018196735 --- /dev/null +++ b/sys/mips/atheros/ar71xx_pci_bus_space.c @@ -0,0 +1,198 @@ +/*- + * Copyright (c) 2009, Oleksandr Tymoshenko + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, 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 + +static bs_r_1_s_proto(pcimem); +static bs_r_2_s_proto(pcimem); +static bs_r_4_s_proto(pcimem); +static bs_w_1_s_proto(pcimem); +static bs_w_2_s_proto(pcimem); +static bs_w_4_s_proto(pcimem); + +/* + * Bus space that handles offsets in word for 1/2 bytes read/write access. + * Byte order of values is handled by device drivers itself. + */ +static struct bus_space bus_space_pcimem = { + /* cookie */ + (void *) 0, + + /* mapping/unmapping */ + generic_bs_map, + generic_bs_unmap, + generic_bs_subregion, + + /* allocation/deallocation */ + NULL, + NULL, + + /* barrier */ + generic_bs_barrier, + + /* read (single) */ + generic_bs_r_1, + generic_bs_r_2, + generic_bs_r_4, + NULL, + + /* read multiple */ + generic_bs_rm_1, + generic_bs_rm_2, + generic_bs_rm_4, + NULL, + + /* read region */ + generic_bs_rr_1, + generic_bs_rr_2, + generic_bs_rr_4, + NULL, + + /* write (single) */ + generic_bs_w_1, + generic_bs_w_2, + generic_bs_w_4, + NULL, + + /* write multiple */ + generic_bs_wm_1, + generic_bs_wm_2, + generic_bs_wm_4, + NULL, + + /* write region */ + NULL, + generic_bs_wr_2, + generic_bs_wr_4, + NULL, + + /* set multiple */ + NULL, + NULL, + NULL, + NULL, + + /* set region */ + NULL, + generic_bs_sr_2, + generic_bs_sr_4, + NULL, + + /* copy */ + NULL, + generic_bs_c_2, + NULL, + NULL, + + /* read (single) stream */ + pcimem_bs_r_1_s, + pcimem_bs_r_2_s, + pcimem_bs_r_4_s, + NULL, + + /* read multiple stream */ + generic_bs_rm_1, + generic_bs_rm_2, + generic_bs_rm_4, + NULL, + + /* read region stream */ + generic_bs_rr_1, + generic_bs_rr_2, + generic_bs_rr_4, + NULL, + + /* write (single) stream */ + pcimem_bs_w_1_s, + pcimem_bs_w_2_s, + pcimem_bs_w_4_s, + NULL, + + /* write multiple stream */ + generic_bs_wm_1, + generic_bs_wm_2, + generic_bs_wm_4, + NULL, + + /* write region stream */ + NULL, + generic_bs_wr_2, + generic_bs_wr_4, + NULL, +}; + +bus_space_tag_t ar71xx_bus_space_pcimem = &bus_space_pcimem; + +static uint8_t +pcimem_bs_r_1_s(void *t, bus_space_handle_t h, bus_size_t o) +{ + + return readb(h + (o &~ 3) + (3 - (o & 3))); +} + +static void +pcimem_bs_w_1_s(void *t, bus_space_handle_t h, bus_size_t o, u_int8_t v) +{ + + writeb(h + (o &~ 3) + (3 - (o & 3)), v); +} + +static uint16_t +pcimem_bs_r_2_s(void *t, bus_space_handle_t h, bus_size_t o) +{ + + return readw(h + (o &~ 3) + (2 - (o & 3))); +} + +static void +pcimem_bs_w_2_s(void *t, bus_space_handle_t h, bus_size_t o, uint16_t v) +{ + + writew(h + (o &~ 3) + (2 - (o & 3)), v); +} + +static uint32_t +pcimem_bs_r_4_s(void *t, bus_space_handle_t h, bus_size_t o) +{ + + return le32toh(readl(h + o)); +} + +static void +pcimem_bs_w_4_s(void *t, bus_space_handle_t h, bus_size_t o, uint32_t v) +{ + + writel(h + o, htole32(v)); +} diff --git a/sys/mips/atheros/ar71xx_pci_bus_space.h b/sys/mips/atheros/ar71xx_pci_bus_space.h new file mode 100644 index 000000000000..5dead02a1ece --- /dev/null +++ b/sys/mips/atheros/ar71xx_pci_bus_space.h @@ -0,0 +1,33 @@ +/*- + * Copyright (c) 2009, Oleksandr Tymoshenko + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, 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. + */ + +#ifndef __AR71XX_PCI_BUS_SPACEH__ +#define __AR71XX_PCI_BUS_SPACEH__ + +extern bus_space_tag_t ar71xx_bus_space_pcimem; + +#endif /* __AR71XX_PCI_BUS_SPACEH__ */ diff --git a/sys/mips/atheros/ar71xx_spi.c b/sys/mips/atheros/ar71xx_spi.c new file mode 100644 index 000000000000..9f7eb1acd87a --- /dev/null +++ b/sys/mips/atheros/ar71xx_spi.c @@ -0,0 +1,228 @@ +/*- + * Copyright (c) 2009, Oleksandr Tymoshenko + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, 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 "spibus_if.h" + +#include + +#undef AR71XX_SPI_DEBUG +#ifdef AR71XX_SPI_DEBUG +#define dprintf printf +#else +#define dprintf(x, arg...) +#endif + +/* + * register space access macros + */ +#define SPI_WRITE(sc, reg, val) do { \ + bus_write_4(sc->sc_mem_res, (reg), (val)); \ + } while (0) + +#define SPI_READ(sc, reg) bus_read_4(sc->sc_mem_res, (reg)) + +#define SPI_SET_BITS(sc, reg, bits) \ + SPI_WRITE(sc, reg, SPI_READ(sc, (reg)) | (bits)) + +#define SPI_CLEAR_BITS(sc, reg, bits) \ + SPI_WRITE(sc, reg, SPI_READ(sc, (reg)) & ~(bits)) + +struct ar71xx_spi_softc { + device_t sc_dev; + struct resource *sc_mem_res; + uint32_t sc_reg_ioctrl; +}; + +static int +ar71xx_spi_probe(device_t dev) +{ + device_set_desc(dev, "AR71XX SPI"); + return (0); +} + +static int +ar71xx_spi_attach(device_t dev) +{ + struct ar71xx_spi_softc *sc = device_get_softc(dev); + int rid; + + sc->sc_dev = dev; + 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, "Could not map memory\n"); + return (ENXIO); + } + + sc->sc_reg_ioctrl = SPI_READ(sc, AR71XX_SPI_IO_CTRL); + + SPI_WRITE(sc, AR71XX_SPI_IO_CTRL, SPI_IO_CTRL_CS0 | SPI_IO_CTRL_CS1 | + SPI_IO_CTRL_CS2); + SPI_WRITE(sc, AR71XX_SPI_CTRL, sc->sc_reg_ioctrl); + SPI_WRITE(sc, AR71XX_SPI_FS, 0); + + device_add_child(dev, "spibus", 0); + return (bus_generic_attach(dev)); +} + +static void +ar71xx_spi_chip_activate(struct ar71xx_spi_softc *sc, int cs) +{ + uint32_t ioctrl = SPI_IO_CTRL_CS0 |SPI_IO_CTRL_CS1 | SPI_IO_CTRL_CS2; + /* + * Put respective CSx to low + */ + ioctrl &= ~(SPI_IO_CTRL_CS0 << cs); + + SPI_WRITE(sc, AR71XX_SPI_FS, 1); + SPI_WRITE(sc, AR71XX_SPI_CTRL, 0x43); + SPI_WRITE(sc, AR71XX_SPI_IO_CTRL, ioctrl); +} + +static void +ar71xx_spi_chip_deactivate(struct ar71xx_spi_softc *sc, int cs) +{ + /* + * Put all CSx to high + */ + SPI_WRITE(sc, AR71XX_SPI_IO_CTRL, SPI_IO_CTRL_CS0 | SPI_IO_CTRL_CS1 | + SPI_IO_CTRL_CS2); + SPI_WRITE(sc, AR71XX_SPI_CTRL, sc->sc_reg_ioctrl); + SPI_WRITE(sc, AR71XX_SPI_FS, 0); +} + +static uint8_t +ar71xx_spi_txrx(struct ar71xx_spi_softc *sc, uint8_t data) +{ + int bit; + /* CS0 */ + uint32_t ioctrl = SPI_IO_CTRL_CS1 | SPI_IO_CTRL_CS2; + + uint32_t iod, rds; + for (bit = 7; bit >=0; bit--) { + if (data & (1 << bit)) + iod = ioctrl | SPI_IO_CTRL_DO; + else + iod = ioctrl & ~SPI_IO_CTRL_DO; + SPI_WRITE(sc, AR71XX_SPI_IO_CTRL, iod); + SPI_WRITE(sc, AR71XX_SPI_IO_CTRL, iod | SPI_IO_CTRL_CLK); + } + + rds = SPI_READ(sc, AR71XX_SPI_RDS); + + return (rds & 0xff); +} + +static int +ar71xx_spi_transfer(device_t dev, device_t child, struct spi_command *cmd) +{ + struct ar71xx_spi_softc *sc; + uint8_t *buf_in, *buf_out; + struct spibus_ivar *devi = SPIBUS_IVAR(child); + int i; + + sc = device_get_softc(dev); + + ar71xx_spi_chip_activate(sc, devi->cs); + + 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")); + + /* + * Transfer command + */ + buf_out = (uint8_t *)cmd->tx_cmd; + buf_in = (uint8_t *)cmd->rx_cmd; + for (i = 0; i < cmd->tx_cmd_sz; i++) + buf_in[i] = ar71xx_spi_txrx(sc, buf_out[i]); + + /* + * Receive/transmit data (depends on command) + */ + buf_out = (uint8_t *)cmd->tx_data; + buf_in = (uint8_t *)cmd->rx_data; + for (i = 0; i < cmd->tx_data_sz; i++) + buf_in[i] = ar71xx_spi_txrx(sc, buf_out[i]); + + ar71xx_spi_chip_deactivate(sc, devi->cs); + + return (0); +} + +static int +ar71xx_spi_detach(device_t dev) +{ + + return (EBUSY); /* XXX */ +} + +static device_method_t ar71xx_spi_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ar71xx_spi_probe), + DEVMETHOD(device_attach, ar71xx_spi_attach), + DEVMETHOD(device_detach, ar71xx_spi_detach), + + DEVMETHOD(spibus_transfer, ar71xx_spi_transfer), + + {0, 0} +}; + +static driver_t ar71xx_spi_driver = { + "spi", + ar71xx_spi_methods, + sizeof(struct ar71xx_spi_softc), +}; + +static devclass_t ar71xx_spi_devclass; + +DRIVER_MODULE(ar71xx_spi, nexus, ar71xx_spi_driver, ar71xx_spi_devclass, 0, 0); diff --git a/sys/mips/atheros/ar71xx_wdog.c b/sys/mips/atheros/ar71xx_wdog.c new file mode 100644 index 000000000000..90caaa84f8af --- /dev/null +++ b/sys/mips/atheros/ar71xx_wdog.c @@ -0,0 +1,118 @@ +/*- + * Copyright (c) 2009, Oleksandr Tymoshenko + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, 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. + */ + +/* + * Watchdog driver for AR71xx + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include + +#include + +struct ar71xx_wdog_softc { + device_t dev; + int armed; +}; + +static void +ar71xx_wdog_watchdog_fn(void *private, u_int cmd, int *error) +{ + struct ar71xx_wdog_softc *sc = private; + uint64_t timer_val; + + cmd &= WD_INTERVAL; + if (cmd > 0) { + timer_val = (uint64_t)(1ULL << cmd) * ar71xx_ahb_freq() / + 1000000000; + /* + * Load timer with large enough value to prevent spurious + * reset + */ + ATH_WRITE_REG(AR71XX_RST_WDOG_TIMER, + ar71xx_ahb_freq() * 10); + ATH_WRITE_REG(AR71XX_RST_WDOG_CONTROL, + RST_WDOG_ACTION_RESET); + ATH_WRITE_REG(AR71XX_RST_WDOG_TIMER, + (timer_val & 0xffffffff)); + sc->armed = 1; + *error = 0; + } else { + if (sc->armed) { + ATH_WRITE_REG(AR71XX_RST_WDOG_CONTROL, + RST_WDOG_ACTION_NOACTION); + sc->armed = 0; + } + } +} + +static int +ar71xx_wdog_probe(device_t dev) +{ + + device_set_desc(dev, "Atheros AR71XX watchdog timer"); + return (0); +} + +static int +ar71xx_wdog_attach(device_t dev) +{ + struct ar71xx_wdog_softc *sc = device_get_softc(dev); + + if (ATH_READ_REG(AR71XX_RST_WDOG_CONTROL) & RST_WDOG_LAST) + device_printf (dev, + "Previous reset was due to watchdog timeout\n"); + + ATH_WRITE_REG(AR71XX_RST_WDOG_CONTROL, RST_WDOG_ACTION_NOACTION); + + sc->dev = dev; + EVENTHANDLER_REGISTER(watchdog_list, ar71xx_wdog_watchdog_fn, sc, 0); + + return (0); +} + +static device_method_t ar71xx_wdog_methods[] = { + DEVMETHOD(device_probe, ar71xx_wdog_probe), + DEVMETHOD(device_attach, ar71xx_wdog_attach), + {0, 0}, +}; + +static driver_t ar71xx_wdog_driver = { + "ar71xx_wdog", + ar71xx_wdog_methods, + sizeof(struct ar71xx_wdog_softc), +}; +static devclass_t ar71xx_wdog_devclass; + +DRIVER_MODULE(ar71xx_wdog, nexus, ar71xx_wdog_driver, ar71xx_wdog_devclass, 0, 0); diff --git a/sys/mips/atheros/ar71xxreg.h b/sys/mips/atheros/ar71xxreg.h new file mode 100644 index 000000000000..5677a4eff599 --- /dev/null +++ b/sys/mips/atheros/ar71xxreg.h @@ -0,0 +1,468 @@ +/*- + * Copyright (c) 2009 Oleksandr Tymoshenko + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#ifndef _AR71XX_REG_H_ +#define _AR71XX_REG_H_ + +/* PCI region */ +#define AR71XX_PCI_MEM_BASE 0x10000000 +/* + * PCI mem windows is 0x08000000 bytes long but we exclude control + * region from the resource manager + */ +#define AR71XX_PCI_MEM_SIZE 0x07000000 +#define AR71XX_PCI_IRQ_START 0 +#define AR71XX_PCI_IRQ_END 2 +#define AR71XX_PCI_NIRQS 3 +/* + * PCI devices slots are starting from this number + */ +#define AR71XX_PCI_BASE_SLOT 17 + +/* PCI config registers */ +#define AR71XX_PCI_LCONF_CMD 0x17010000 +#define PCI_LCONF_CMD_READ 0x00000000 +#define PCI_LCONF_CMD_WRITE 0x00010000 +#define AR71XX_PCI_LCONF_WRITE_DATA 0x17010004 +#define AR71XX_PCI_LCONF_READ_DATA 0x17010008 +#define AR71XX_PCI_CONF_ADDR 0x1701000C +#define AR71XX_PCI_CONF_CMD 0x17010010 +#define PCI_CONF_CMD_READ 0x0000000A +#define PCI_CONF_CMD_WRITE 0x0000000B +#define AR71XX_PCI_CONF_WRITE_DATA 0x17010014 +#define AR71XX_PCI_CONF_READ_DATA 0x17010018 +#define AR71XX_PCI_ERROR 0x1701001C +#define AR71XX_PCI_ERROR_ADDR 0x17010020 +#define AR71XX_PCI_AHB_ERROR 0x17010024 +#define AR71XX_PCI_AHB_ERROR_ADDR 0x17010028 + +/* APB region */ +/* + * Size is not really true actual APB window size is + * 0x01000000 but it should handle OHCI memory as well + * because this controller's interrupt is routed through + * APB. + */ +#define AR71XX_APB_BASE 0x18000000 +#define AR71XX_APB_SIZE 0x06000000 + +/* DDR registers */ +#define AR71XX_DDR_CONFIG 0x18000000 +#define AR71XX_DDR_CONFIG2 0x18000004 +#define AR71XX_DDR_MODE_REGISTER 0x18000008 +#define AR71XX_DDR_EXT_MODE_REGISTER 0x1800000C +#define AR71XX_DDR_CONTROL 0x18000010 +#define AR71XX_DDR_REFRESH 0x18000014 +#define AR71XX_DDR_RD_DATA_THIS_CYCLE 0x18000018 +#define AR71XX_TAP_CONTROL0 0x1800001C +#define AR71XX_TAP_CONTROL1 0x18000020 +#define AR71XX_TAP_CONTROL2 0x18000024 +#define AR71XX_TAP_CONTROL3 0x18000028 +#define AR71XX_PCI_WINDOW0 0x1800007C +#define AR71XX_PCI_WINDOW1 0x18000080 +#define AR71XX_PCI_WINDOW2 0x18000084 +#define AR71XX_PCI_WINDOW3 0x18000088 +#define AR71XX_PCI_WINDOW4 0x1800008C +#define AR71XX_PCI_WINDOW5 0x18000090 +#define AR71XX_PCI_WINDOW6 0x18000094 +#define AR71XX_PCI_WINDOW7 0x18000098 +#define AR71XX_WB_FLUSH_GE0 0x1800009C +#define AR71XX_WB_FLUSH_GE1 0x180000A0 +#define AR71XX_WB_FLUSH_USB 0x180000A4 +#define AR71XX_WB_FLUSH_PCI 0x180000A8 + +/* + * Values for PCI_WINDOW_X registers + */ +#define PCI_WINDOW0_ADDR 0x10000000 +#define PCI_WINDOW1_ADDR 0x11000000 +#define PCI_WINDOW2_ADDR 0x12000000 +#define PCI_WINDOW3_ADDR 0x13000000 +#define PCI_WINDOW4_ADDR 0x14000000 +#define PCI_WINDOW5_ADDR 0x15000000 +#define PCI_WINDOW6_ADDR 0x16000000 +#define PCI_WINDOW7_ADDR 0x17000000 +/* This value enables acces to PCI config registers */ +#define PCI_WINDOW7_CONF_ADDR 0x07000000 + +#define AR71XX_UART_ADDR 0x18020000 + +#define AR71XX_USB_CTRL_FLADJ 0x18030000 +#define USB_CTRL_FLADJ_HOST_SHIFT 12 +#define USB_CTRL_FLADJ_A5_SHIFT 10 +#define USB_CTRL_FLADJ_A4_SHIFT 8 +#define USB_CTRL_FLADJ_A3_SHIFT 6 +#define USB_CTRL_FLADJ_A2_SHIFT 4 +#define USB_CTRL_FLADJ_A1_SHIFT 2 +#define USB_CTRL_FLADJ_A0_SHIFT 0 +#define AR71XX_USB_CTRL_CONFIG 0x18030004 +#define USB_CTRL_CONFIG_OHCI_DES_SWAP (1 << 19) +#define USB_CTRL_CONFIG_OHCI_BUF_SWAP (1 << 18) +#define USB_CTRL_CONFIG_EHCI_DES_SWAP (1 << 17) +#define USB_CTRL_CONFIG_EHCI_BUF_SWAP (1 << 16) +#define USB_CTRL_CONFIG_DISABLE_XTL (1 << 13) +#define USB_CTRL_CONFIG_OVERRIDE_XTL (1 << 12) +#define USB_CTRL_CONFIG_CLK_SEL_SHIFT 4 +#define USB_CTRL_CONFIG_CLK_SEL_MASK 3 +#define USB_CTRL_CONFIG_CLK_SEL_12 0 +#define USB_CTRL_CONFIG_CLK_SEL_24 1 +#define USB_CTRL_CONFIG_CLK_SEL_48 2 +#define USB_CTRL_CONFIG_OVER_CURRENT_AS_GPIO (1 << 8) +#define USB_CTRL_CONFIG_SS_SIMULATION_MODE (1 << 2) +#define USB_CTRL_CONFIG_RESUME_UTMI_PLS_DIS (1 << 1) +#define USB_CTRL_CONFIG_UTMI_BACKWARD_ENB (1 << 0) + +#define AR71XX_BASE_FREQ 40000000 +#define AR71XX_PLL_CPU_CONFIG 0x18050000 +#define PLL_SW_UPDATE (1 << 31) +#define PLL_LOCKED (1 << 30) +#define PLL_AHB_DIV_SHIFT 20 +#define PLL_AHB_DIV_MASK 7 +#define PLL_DDR_DIV_SEL_SHIFT 18 +#define PLL_DDR_DIV_SEL_MASK 3 +#define PLL_CPU_DIV_SEL_SHIFT 16 +#define PLL_CPU_DIV_SEL_MASK 3 +#define PLL_LOOP_BW_SHIFT 12 +#define PLL_LOOP_BW_MASK 0xf +#define PLL_DIV_IN_SHIFT 10 +#define PLL_DIV_IN_MASK 3 +#define PLL_DIV_OUT_SHIFT 8 +#define PLL_DIV_OUT_MASK 3 +#define PLL_FB_SHIFT 3 +#define PLL_FB_MASK 0x1f +#define PLL_BYPASS (1 << 1) +#define PLL_POWER_DOWN (1 << 0) +#define AR71XX_PLL_SEC_CONFIG 0x18050004 +#define AR71XX_PLL_CPU_CLK_CTRL 0x18050008 +#define AR71XX_PLL_ETH_INT0_CLK 0x18050010 +#define AR71XX_PLL_ETH_INT1_CLK 0x18050014 +#define XPLL_ETH_INT_CLK_10 0x00991099 +#define XPLL_ETH_INT_CLK_100 0x00441011 +#define XPLL_ETH_INT_CLK_1000 0x13110000 +#define XPLL_ETH_INT_CLK_1000_GMII 0x14110000 +#define PLL_ETH_INT_CLK_10 0x00991099 +#define PLL_ETH_INT_CLK_100 0x00001099 +#define PLL_ETH_INT_CLK_1000 0x00110000 +#define AR71XX_PLL_ETH_EXT_CLK 0x18050018 +#define AR71XX_PLL_PCI_CLK 0x1805001C + +#define AR71XX_RST_WDOG_CONTROL 0x18060008 +#define RST_WDOG_LAST (1 << 31) +#define RST_WDOG_ACTION_MASK 3 +#define RST_WDOG_ACTION_RESET 3 +#define RST_WDOG_ACTION_NMI 2 +#define RST_WDOG_ACTION_GP_INTR 1 +#define RST_WDOG_ACTION_NOACTION 0 + +#define AR71XX_RST_WDOG_TIMER 0x1806000C +/* + * APB interrupt status and mask register and interrupt bit numbers for + */ +#define AR71XX_MISC_INTR_STATUS 0x18060010 +#define AR71XX_MISC_INTR_MASK 0x18060014 +#define MISC_INTR_TIMER 0 +#define MISC_INTR_ERROR 1 +#define MISC_INTR_GPIO 2 +#define MISC_INTR_UART 3 +#define MISC_INTR_WATCHDOG 4 +#define MISC_INTR_PERF 5 +#define MISC_INTR_OHCI 6 +#define MISC_INTR_DMA 7 + +#define AR71XX_PCI_INTR_STATUS 0x18060018 +#define AR71XX_PCI_INTR_MASK 0x1806001C +#define PCI_INTR_CORE (1 << 4) + +#define AR71XX_RST_RESET 0x18060024 +#define RST_RESET_FULL_CHIP (1 << 24) /* Same as pulling + the reset pin */ +#define RST_RESET_CPU_COLD (1 << 20) /* Cold reset */ +#define RST_RESET_GE1_MAC (1 << 13) +#define RST_RESET_GE1_PHY (1 << 12) +#define RST_RESET_GE0_MAC (1 << 9) +#define RST_RESET_GE0_PHY (1 << 8) +#define RST_RESET_USB_OHCI_DLL (1 << 6) +#define RST_RESET_USB_HOST (1 << 5) +#define RST_RESET_USB_PHY (1 << 4) +#define RST_RESET_PCI_BUS (1 << 1) +#define RST_RESET_PCI_CORE (1 << 0) + +/* + * GigE adapters region + */ +#define AR71XX_MAC0_BASE 0x19000000 +#define AR71XX_MAC1_BASE 0x1A000000 +/* + * All 5 PHYs accessible only through MAC0 register space + */ +#define AR71XX_MII_BASE 0x19000000 + +#define AR71XX_MAC_CFG1 0x00 +#define MAC_CFG1_SOFT_RESET (1 << 31) +#define MAC_CFG1_SIMUL_RESET (1 << 30) +#define MAC_CFG1_MAC_RX_BLOCK_RESET (1 << 19) +#define MAC_CFG1_MAC_TX_BLOCK_RESET (1 << 18) +#define MAC_CFG1_RX_FUNC_RESET (1 << 17) +#define MAC_CFG1_TX_FUNC_RESET (1 << 16) +#define MAC_CFG1_LOOPBACK (1 << 8) +#define MAC_CFG1_RXFLOW_CTRL (1 << 5) +#define MAC_CFG1_TXFLOW_CTRL (1 << 4) +#define MAC_CFG1_SYNC_RX (1 << 3) +#define MAC_CFG1_RX_ENABLE (1 << 2) +#define MAC_CFG1_SYNC_TX (1 << 1) +#define MAC_CFG1_TX_ENABLE (1 << 0) +#define AR71XX_MAC_CFG2 0x04 +#define MAC_CFG2_PREAMBLE_LEN_MASK 0xf +#define MAC_CFG2_PREAMBLE_LEN_SHIFT 12 +#define MAC_CFG2_IFACE_MODE_1000 (2 << 8) +#define MAC_CFG2_IFACE_MODE_10_100 (1 << 8) +#define MAC_CFG2_IFACE_MODE_SHIFT 8 +#define MAC_CFG2_IFACE_MODE_MASK 3 +#define MAC_CFG2_HUGE_FRAME (1 << 5) +#define MAC_CFG2_LENGTH_FIELD (1 << 4) +#define MAC_CFG2_ENABLE_PADCRC (1 << 2) +#define MAC_CFG2_ENABLE_CRC (1 << 1) +#define MAC_CFG2_FULL_DUPLEX (1 << 0) +#define AR71XX_MAC_IFG 0x08 +#define AR71XX_MAC_HDUPLEX 0x0C +#define AR71XX_MAC_MAX_FRAME_LEN 0x10 +#define AR71XX_MAC_MII_CFG 0x20 +#define MAC_MII_CFG_RESET (1 << 31) +#define MAC_MII_CFG_SCAN_AUTO_INC (1 << 5) +#define MAC_MII_CFG_PREAMBLE_SUP (1 << 4) +#define MAC_MII_CFG_CLOCK_SELECT_MASK 0x7 +#define MAC_MII_CFG_CLOCK_DIV_4 0 +#define MAC_MII_CFG_CLOCK_DIV_6 2 +#define MAC_MII_CFG_CLOCK_DIV_8 3 +#define MAC_MII_CFG_CLOCK_DIV_10 4 +#define MAC_MII_CFG_CLOCK_DIV_14 5 +#define MAC_MII_CFG_CLOCK_DIV_20 6 +#define MAC_MII_CFG_CLOCK_DIV_28 7 +#define AR71XX_MAC_MII_CMD 0x24 +#define MAC_MII_CMD_SCAN_CYCLE (1 << 1) +#define MAC_MII_CMD_READ 1 +#define MAC_MII_CMD_WRITE 0 +#define AR71XX_MAC_MII_ADDR 0x28 +#define MAC_MII_PHY_ADDR_SHIFT 8 +#define MAC_MII_PHY_ADDR_MASK 0xff +#define MAC_MII_REG_MASK 0x1f +#define AR71XX_MAC_MII_CONTROL 0x2C +#define MAC_MII_CONTROL_MASK 0xffff +#define AR71XX_MAC_MII_STATUS 0x30 +#define MAC_MII_STATUS_MASK 0xffff +#define AR71XX_MAC_MII_INDICATOR 0x34 +#define MAC_MII_INDICATOR_NOT_VALID (1 << 2) +#define MAC_MII_INDICATOR_SCANNING (1 << 1) +#define MAC_MII_INDICATOR_BUSY (1 << 0) +#define AR71XX_MAC_IFCONTROL 0x38 +#define MAC_IFCONTROL_SPEED (1 << 16) +#define AR71XX_MAC_STA_ADDR1 0x40 +#define AR71XX_MAC_STA_ADDR2 0x44 +#define AR71XX_MAC_FIFO_CFG0 0x48 +#define FIFO_CFG0_TX_FABRIC (1 << 4) +#define FIFO_CFG0_TX_SYSTEM (1 << 3) +#define FIFO_CFG0_RX_FABRIC (1 << 2) +#define FIFO_CFG0_RX_SYSTEM (1 << 1) +#define FIFO_CFG0_WATERMARK (1 << 0) +#define FIFO_CFG0_ALL ((1 << 5) - 1) +#define FIFO_CFG0_ENABLE_SHIFT 8 +#define AR71XX_MAC_FIFO_CFG1 0x4C +#define AR71XX_MAC_FIFO_CFG2 0x50 +#define AR71XX_MAC_FIFO_TX_THRESHOLD 0x54 +#define AR71XX_MAC_FIFO_RX_FILTMATCH 0x58 +/* + * These flags applicable both to AR71XX_MAC_FIFO_RX_FILTMASK and + * to AR71XX_MAC_FIFO_RX_FILTMATCH + */ +#define FIFO_RX_MATCH_UNICAST (1 << 17) +#define FIFO_RX_MATCH_TRUNC_FRAME (1 << 16) +#define FIFO_RX_MATCH_VLAN_TAG (1 << 15) +#define FIFO_RX_MATCH_UNSUP_OPCODE (1 << 14) +#define FIFO_RX_MATCH_PAUSE_FRAME (1 << 13) +#define FIFO_RX_MATCH_CTRL_FRAME (1 << 12) +#define FIFO_RX_MATCH_LONG_EVENT (1 << 11) +#define FIFO_RX_MATCH_DRIBBLE_NIBBLE (1 << 10) +#define FIFO_RX_MATCH_BCAST (1 << 9) +#define FIFO_RX_MATCH_MCAST (1 << 8) +#define FIFO_RX_MATCH_OK (1 << 7) +#define FIFO_RX_MATCH_OORANGE (1 << 6) +#define FIFO_RX_MATCH_LEN_MSMTCH (1 << 5) +#define FIFO_RX_MATCH_CRC_ERROR (1 << 4) +#define FIFO_RX_MATCH_CODE_ERROR (1 << 3) +#define FIFO_RX_MATCH_FALSE_CARRIER (1 << 2) +#define FIFO_RX_MATCH_RX_DV_EVENT (1 << 1) +#define FIFO_RX_MATCH_DROP_EVENT (1 << 0) +/* + * Exclude unicast and truncated frames from matching + */ +#define FIFO_RX_FILTMATCH_DEFAULT \ + (FIFO_RX_MATCH_VLAN_TAG | \ + FIFO_RX_MATCH_UNSUP_OPCODE | \ + FIFO_RX_MATCH_PAUSE_FRAME | \ + FIFO_RX_MATCH_CTRL_FRAME | \ + FIFO_RX_MATCH_LONG_EVENT | \ + FIFO_RX_MATCH_DRIBBLE_NIBBLE | \ + FIFO_RX_MATCH_BCAST | \ + FIFO_RX_MATCH_MCAST | \ + FIFO_RX_MATCH_OK | \ + FIFO_RX_MATCH_OORANGE | \ + FIFO_RX_MATCH_LEN_MSMTCH | \ + FIFO_RX_MATCH_CRC_ERROR | \ + FIFO_RX_MATCH_CODE_ERROR | \ + FIFO_RX_MATCH_FALSE_CARRIER | \ + FIFO_RX_MATCH_RX_DV_EVENT | \ + FIFO_RX_MATCH_DROP_EVENT) +#define AR71XX_MAC_FIFO_RX_FILTMASK 0x5C +#define FIFO_RX_MASK_BYTE_MODE (1 << 19) +#define FIFO_RX_MASK_NO_SHORT_FRAME (1 << 18) +#define FIFO_RX_MASK_BIT17 (1 << 17) +#define FIFO_RX_MASK_BIT16 (1 << 16) +#define FIFO_RX_MASK_TRUNC_FRAME (1 << 15) +#define FIFO_RX_MASK_LONG_EVENT (1 << 14) +#define FIFO_RX_MASK_VLAN_TAG (1 << 13) +#define FIFO_RX_MASK_UNSUP_OPCODE (1 << 12) +#define FIFO_RX_MASK_PAUSE_FRAME (1 << 11) +#define FIFO_RX_MASK_CTRL_FRAME (1 << 10) +#define FIFO_RX_MASK_DRIBBLE_NIBBLE (1 << 9) +#define FIFO_RX_MASK_BCAST (1 << 8) +#define FIFO_RX_MASK_MCAST (1 << 7) +#define FIFO_RX_MASK_OK (1 << 6) +#define FIFO_RX_MASK_OORANGE (1 << 5) +#define FIFO_RX_MASK_LEN_MSMTCH (1 << 4) +#define FIFO_RX_MASK_CODE_ERROR (1 << 3) +#define FIFO_RX_MASK_FALSE_CARRIER (1 << 2) +#define FIFO_RX_MASK_RX_DV_EVENT (1 << 1) +#define FIFO_RX_MASK_DROP_EVENT (1 << 0) + +/* + * Len. mismatch, unsup. opcode and short frmae bits excluded + */ +#define FIFO_RX_FILTMASK_DEFAULT \ + (FIFO_RX_MASK_NO_SHORT_FRAME | \ + FIFO_RX_MASK_BIT17 | \ + FIFO_RX_MASK_BIT16 | \ + FIFO_RX_MASK_TRUNC_FRAME | \ + FIFO_RX_MASK_LONG_EVENT | \ + FIFO_RX_MASK_VLAN_TAG | \ + FIFO_RX_MASK_PAUSE_FRAME | \ + FIFO_RX_MASK_CTRL_FRAME | \ + FIFO_RX_MASK_DRIBBLE_NIBBLE | \ + FIFO_RX_MASK_BCAST | \ + FIFO_RX_MASK_MCAST | \ + FIFO_RX_MASK_OK | \ + FIFO_RX_MASK_OORANGE | \ + FIFO_RX_MASK_CODE_ERROR | \ + FIFO_RX_MASK_FALSE_CARRIER | \ + FIFO_RX_MASK_RX_DV_EVENT | \ + FIFO_RX_MASK_DROP_EVENT) + +#define AR71XX_MAC_FIFO_RAM0 0x60 +#define AR71XX_MAC_FIFO_RAM1 0x64 +#define AR71XX_MAC_FIFO_RAM2 0x68 +#define AR71XX_MAC_FIFO_RAM3 0x6C +#define AR71XX_MAC_FIFO_RAM4 0x70 +#define AR71XX_MAC_FIFO_RAM5 0x74 +#define AR71XX_MAC_FIFO_RAM6 0x78 +#define AR71XX_DMA_TX_CONTROL 0x180 +#define DMA_TX_CONTROL_EN (1 << 0) +#define AR71XX_DMA_TX_DESC 0x184 +#define AR71XX_DMA_TX_STATUS 0x188 +#define DMA_TX_STATUS_PCOUNT_MASK 0xff +#define DMA_TX_STATUS_PCOUNT_SHIFT 16 +#define DMA_TX_STATUS_BUS_ERROR (1 << 3) +#define DMA_TX_STATUS_UNDERRUN (1 << 1) +#define DMA_TX_STATUS_PKT_SENT (1 << 0) +#define AR71XX_DMA_RX_CONTROL 0x18C +#define DMA_RX_CONTROL_EN (1 << 0) +#define AR71XX_DMA_RX_DESC 0x190 +#define AR71XX_DMA_RX_STATUS 0x194 +#define DMA_RX_STATUS_PCOUNT_MASK 0xff +#define DMA_RX_STATUS_PCOUNT_SHIFT 16 +#define DMA_RX_STATUS_BUS_ERROR (1 << 3) +#define DMA_RX_STATUS_OVERFLOW (1 << 1) +#define DMA_RX_STATUS_PKT_RECVD (1 << 0) +#define AR71XX_DMA_INTR 0x198 +#define AR71XX_DMA_INTR_STATUS 0x19C +#define DMA_INTR_ALL ((1 << 8) - 1) +#define DMA_INTR_RX_BUS_ERROR (1 << 7) +#define DMA_INTR_RX_OVERFLOW (1 << 6) +#define DMA_INTR_RX_PKT_RCVD (1 << 4) +#define DMA_INTR_TX_BUS_ERROR (1 << 3) +#define DMA_INTR_TX_UNDERRUN (1 << 1) +#define DMA_INTR_TX_PKT_SENT (1 << 0) + +#define AR71XX_SPI_BASE 0x1f000000 +#define AR71XX_SPI_FS 0x00 +#define AR71XX_SPI_CTRL 0x04 +#define SPI_CTRL_REMAP_DISABLE (1 << 6) +#define SPI_CTRL_CLOCK_DIVIDER_MASK ((1 << 6) - 1) +#define AR71XX_SPI_IO_CTRL 0x08 +#define SPI_IO_CTRL_CS2 (1 << 18) +#define SPI_IO_CTRL_CS1 (1 << 17) +#define SPI_IO_CTRL_CS0 (1 << 16) +#define SPI_IO_CTRL_CLK (1 << 8) +#define SPI_IO_CTRL_DO 1 +#define AR71XX_SPI_RDS 0x0C + +#define ATH_READ_REG(reg) \ + *((volatile uint32_t *)MIPS_PHYS_TO_KSEG1((reg))) + +#define ATH_WRITE_REG(reg, val) \ + *((volatile uint32_t *)MIPS_PHYS_TO_KSEG1((reg))) = (val) + +static inline uint64_t +ar71xx_cpu_freq(void) +{ + uint32_t pll_config, div; + uint64_t freq; + + /* PLL freq */ + pll_config = ATH_READ_REG(AR71XX_PLL_CPU_CONFIG); + div = ((pll_config >> PLL_FB_SHIFT) & PLL_FB_MASK) + 1; + freq = div * AR71XX_BASE_FREQ; + /* CPU freq */ + div = ((pll_config >> PLL_CPU_DIV_SEL_SHIFT) & PLL_CPU_DIV_SEL_MASK) + + 1; + freq = freq / div; + + return (freq); +} + +static inline uint64_t +ar71xx_ahb_freq(void) +{ + uint32_t pll_config, div; + uint64_t freq; + + /* PLL freq */ + pll_config = ATH_READ_REG(AR71XX_PLL_CPU_CONFIG); + /* AHB freq */ + div = (((pll_config >> PLL_AHB_DIV_SHIFT) & PLL_AHB_DIV_MASK) + 1) * 2; + freq = ar71xx_cpu_freq() / div; + return (freq); +} + + +#endif /* _AR71XX_REG_H_ */ diff --git a/sys/mips/atheros/files.ar71xx b/sys/mips/atheros/files.ar71xx new file mode 100644 index 000000000000..29add2e06f02 --- /dev/null +++ b/sys/mips/atheros/files.ar71xx @@ -0,0 +1,16 @@ +# $FreeBSD$ + +mips/atheros/apb.c standard +mips/atheros/ar71xx_machdep.c standard +mips/atheros/ar71xx_ehci.c optional ehci +mips/atheros/ar71xx_ohci.c optional ohci +mips/atheros/ar71xx_pci.c optional pci +mips/atheros/ar71xx_pci_bus_space.c optional pci +mips/atheros/ar71xx_spi.c optional ar71xx_spi +mips/atheros/ar71xx_wdog.c optional ar71xx_wdog +mips/atheros/if_arge.c optional arge +mips/atheros/uart_bus_ar71xx.c optional uart +mips/atheros/uart_cpu_ar71xx.c optional uart +mips/atheros/ar71xx_bus_space_reversed.c standard +mips/mips/intr_machdep.c standard +mips/mips/tick.c standard diff --git a/sys/mips/atheros/if_arge.c b/sys/mips/atheros/if_arge.c new file mode 100644 index 000000000000..da8d181de088 --- /dev/null +++ b/sys/mips/atheros/if_arge.c @@ -0,0 +1,1811 @@ +/*- + * Copyright (c) 2009, Oleksandr Tymoshenko + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, 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$"); + +/* + * AR71XX gigabit ethernet driver + */ +#ifdef HAVE_KERNEL_OPTION_HEADERS +#include "opt_device_polling.h" +#endif + +#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 + +MODULE_DEPEND(arge, ether, 1, 1, 1); +MODULE_DEPEND(arge, miibus, 1, 1, 1); + +#include "miibus_if.h" + +#include +#include + +#undef ARGE_DEBUG +#ifdef ARGE_DEBUG +#define dprintf printf +#else +#define dprintf(x, arg...) +#endif + +static int arge_attach(device_t); +static int arge_detach(device_t); +static void arge_flush_ddr(struct arge_softc *); +static int arge_ifmedia_upd(struct ifnet *); +static void arge_ifmedia_sts(struct ifnet *, struct ifmediareq *); +static int arge_ioctl(struct ifnet *, u_long, caddr_t); +static void arge_init(void *); +static void arge_init_locked(struct arge_softc *); +static void arge_link_task(void *, int); +static void arge_set_pll(struct arge_softc *, int, int); +static int arge_miibus_readreg(device_t, int, int); +static void arge_miibus_statchg(device_t); +static int arge_miibus_writereg(device_t, int, int, int); +static int arge_probe(device_t); +static void arge_reset_dma(struct arge_softc *); +static int arge_resume(device_t); +static int arge_rx_ring_init(struct arge_softc *); +static int arge_tx_ring_init(struct arge_softc *); +#ifdef DEVICE_POLLING +static int arge_poll(struct ifnet *, enum poll_cmd, int); +#endif +static int arge_shutdown(device_t); +static void arge_start(struct ifnet *); +static void arge_start_locked(struct ifnet *); +static void arge_stop(struct arge_softc *); +static int arge_suspend(device_t); + +static int arge_rx_locked(struct arge_softc *); +static void arge_tx_locked(struct arge_softc *); +static void arge_intr(void *); +static int arge_intr_filter(void *); +static void arge_tick(void *); + +/* + * ifmedia callbacks for multiPHY MAC + */ +void arge_multiphy_mediastatus(struct ifnet *, struct ifmediareq *); +int arge_multiphy_mediachange(struct ifnet *); + +static void arge_dmamap_cb(void *, bus_dma_segment_t *, int, int); +static int arge_dma_alloc(struct arge_softc *); +static void arge_dma_free(struct arge_softc *); +static int arge_newbuf(struct arge_softc *, int); +static __inline void arge_fixup_rx(struct mbuf *); + +static device_method_t arge_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, arge_probe), + DEVMETHOD(device_attach, arge_attach), + DEVMETHOD(device_detach, arge_detach), + DEVMETHOD(device_suspend, arge_suspend), + DEVMETHOD(device_resume, arge_resume), + DEVMETHOD(device_shutdown, arge_shutdown), + + /* bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + DEVMETHOD(bus_driver_added, bus_generic_driver_added), + + /* MII interface */ + DEVMETHOD(miibus_readreg, arge_miibus_readreg), + DEVMETHOD(miibus_writereg, arge_miibus_writereg), + DEVMETHOD(miibus_statchg, arge_miibus_statchg), + + { 0, 0 } +}; + +static driver_t arge_driver = { + "arge", + arge_methods, + sizeof(struct arge_softc) +}; + +static devclass_t arge_devclass; + +DRIVER_MODULE(arge, nexus, arge_driver, arge_devclass, 0, 0); +DRIVER_MODULE(miibus, arge, miibus_driver, miibus_devclass, 0, 0); + +/* + * RedBoot passes MAC address to entry point as environment + * variable. platfrom_start parses it and stores in this variable + */ +extern uint32_t ar711_base_mac[ETHER_ADDR_LEN]; + +static struct mtx miibus_mtx; + +MTX_SYSINIT(miibus_mtx, &miibus_mtx, "arge mii lock", MTX_SPIN); + + +/* + * Flushes all + */ +static void +arge_flush_ddr(struct arge_softc *sc) +{ + + ATH_WRITE_REG(sc->arge_ddr_flush_reg, 1); + while (ATH_READ_REG(sc->arge_ddr_flush_reg) & 1) + ; + + ATH_WRITE_REG(sc->arge_ddr_flush_reg, 1); + while (ATH_READ_REG(sc->arge_ddr_flush_reg) & 1) + ; +} + +static int +arge_probe(device_t dev) +{ + + device_set_desc(dev, "Atheros AR71xx built-in ethernet interface"); + return (0); +} + +static int +arge_attach(device_t dev) +{ + uint8_t eaddr[ETHER_ADDR_LEN]; + struct ifnet *ifp; + struct arge_softc *sc; + int error = 0, rid, phymask; + uint32_t reg, rnd; + int is_base_mac_empty, i, phys_total; + uint32_t hint; + + sc = device_get_softc(dev); + sc->arge_dev = dev; + sc->arge_mac_unit = device_get_unit(dev); + + KASSERT(((sc->arge_mac_unit == 0) || (sc->arge_mac_unit == 1)), + ("if_arge: Only MAC0 and MAC1 supported")); + if (sc->arge_mac_unit == 0) { + sc->arge_ddr_flush_reg = AR71XX_WB_FLUSH_GE0; + sc->arge_pll_reg = AR71XX_PLL_ETH_INT0_CLK; + sc->arge_pll_reg_shift = 17; + } else { + sc->arge_ddr_flush_reg = AR71XX_WB_FLUSH_GE1; + sc->arge_pll_reg = AR71XX_PLL_ETH_INT1_CLK; + sc->arge_pll_reg_shift = 19; + } + + /* + * Get which PHY of 5 available we should use for this unit + */ + if (resource_int_value(device_get_name(dev), device_get_unit(dev), + "phymask", &phymask) != 0) { + /* + * Use port 4 (WAN) for GE0. For any other port use + * its PHY the same as its unit number + */ + if (sc->arge_mac_unit == 0) + phymask = (1 << 4); + else + /* Use all phys up to 4 */ + phymask = (1 << 4) - 1; + + device_printf(dev, "No PHY specified, using mask %d\n", phymask); + } + + /* + * Get default media & duplex mode, by default its Base100T + * and full duplex + */ + if (resource_int_value(device_get_name(dev), device_get_unit(dev), + "media", &hint) != 0) + hint = 0; + + if (hint == 1000) + sc->arge_media_type = IFM_1000_T; + else + sc->arge_media_type = IFM_100_TX; + + if (resource_int_value(device_get_name(dev), device_get_unit(dev), + "fduplex", &hint) != 0) + hint = 1; + + if (hint) + sc->arge_duplex_mode = IFM_FDX; + else + sc->arge_duplex_mode = 0; + + sc->arge_phymask = phymask; + + mtx_init(&sc->arge_mtx, device_get_nameunit(dev), MTX_NETWORK_LOCK, + MTX_DEF); + callout_init_mtx(&sc->arge_stat_callout, &sc->arge_mtx, 0); + TASK_INIT(&sc->arge_link_task, 0, arge_link_task, sc); + + /* Map control/status registers. */ + sc->arge_rid = 0; + sc->arge_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, + &sc->arge_rid, RF_ACTIVE); + + if (sc->arge_res == NULL) { + device_printf(dev, "couldn't map memory\n"); + error = ENXIO; + goto fail; + } + + /* Allocate interrupts */ + rid = 0; + sc->arge_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, + RF_SHAREABLE | RF_ACTIVE); + + if (sc->arge_irq == NULL) { + device_printf(dev, "couldn't map interrupt\n"); + error = ENXIO; + goto fail; + } + + /* Allocate ifnet structure. */ + ifp = sc->arge_ifp = if_alloc(IFT_ETHER); + + if (ifp == NULL) { + device_printf(dev, "couldn't allocate ifnet structure\n"); + error = ENOSPC; + goto fail; + } + + ifp->if_softc = sc; + if_initname(ifp, device_get_name(dev), device_get_unit(dev)); + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; + ifp->if_ioctl = arge_ioctl; + ifp->if_start = arge_start; + ifp->if_init = arge_init; + sc->arge_if_flags = ifp->if_flags; + + /* XXX: add real size */ + IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN); + ifp->if_snd.ifq_maxlen = IFQ_MAXLEN; + IFQ_SET_READY(&ifp->if_snd); + + ifp->if_capenable = ifp->if_capabilities; +#ifdef DEVICE_POLLING + ifp->if_capabilities |= IFCAP_POLLING; +#endif + + is_base_mac_empty = 1; + for (i = 0; i < ETHER_ADDR_LEN; i++) { + eaddr[i] = ar711_base_mac[i] & 0xff; + if (eaddr[i] != 0) + is_base_mac_empty = 0; + } + + if (is_base_mac_empty) { + /* + * No MAC address configured. Generate the random one. + */ + if (bootverbose) + device_printf(dev, + "Generating random ethernet address.\n"); + + rnd = arc4random(); + eaddr[0] = 'b'; + eaddr[1] = 's'; + eaddr[2] = 'd'; + eaddr[3] = (rnd >> 24) & 0xff; + eaddr[4] = (rnd >> 16) & 0xff; + eaddr[5] = (rnd >> 8) & 0xff; + } + + if (sc->arge_mac_unit != 0) + eaddr[5] += sc->arge_mac_unit; + + if (arge_dma_alloc(sc) != 0) { + error = ENXIO; + goto fail; + } + + /* Initialize the MAC block */ + + /* Step 1. Soft-reset MAC */ + ARGE_SET_BITS(sc, AR71XX_MAC_CFG1, MAC_CFG1_SOFT_RESET); + DELAY(20); + + /* Step 2. Punt the MAC core from the central reset register */ + reg = ATH_READ_REG(AR71XX_RST_RESET); + if (sc->arge_mac_unit == 0) + reg |= RST_RESET_GE0_MAC; + else if (sc->arge_mac_unit == 1) + reg |= RST_RESET_GE1_MAC; + ATH_WRITE_REG(AR71XX_RST_RESET, reg); + DELAY(100); + reg = ATH_READ_REG(AR71XX_RST_RESET); + if (sc->arge_mac_unit == 0) + reg &= ~RST_RESET_GE0_MAC; + else if (sc->arge_mac_unit == 1) + reg &= ~RST_RESET_GE1_MAC; + ATH_WRITE_REG(AR71XX_RST_RESET, reg); + + /* Step 3. Reconfigure MAC block */ + ARGE_WRITE(sc, AR71XX_MAC_CFG1, + MAC_CFG1_SYNC_RX | MAC_CFG1_RX_ENABLE | + MAC_CFG1_SYNC_TX | MAC_CFG1_TX_ENABLE); + + reg = ARGE_READ(sc, AR71XX_MAC_CFG2); + reg |= MAC_CFG2_ENABLE_PADCRC | MAC_CFG2_LENGTH_FIELD ; + ARGE_WRITE(sc, AR71XX_MAC_CFG2, reg); + + ARGE_WRITE(sc, AR71XX_MAC_MAX_FRAME_LEN, 1536); + + /* Reset MII bus */ + ARGE_WRITE(sc, AR71XX_MAC_MII_CFG, MAC_MII_CFG_RESET); + DELAY(100); + ARGE_WRITE(sc, AR71XX_MAC_MII_CFG, MAC_MII_CFG_CLOCK_DIV_28); + DELAY(100); + + /* + * Set all Ethernet address registers to the same initial values + * set all four addresses to 66-88-aa-cc-dd-ee + */ + ARGE_WRITE(sc, AR71XX_MAC_STA_ADDR1, + (eaddr[2] << 24) | (eaddr[3] << 16) | (eaddr[4] << 8) | eaddr[5]); + ARGE_WRITE(sc, AR71XX_MAC_STA_ADDR2, (eaddr[0] << 8) | eaddr[1]); + + ARGE_WRITE(sc, AR71XX_MAC_FIFO_CFG0, + FIFO_CFG0_ALL << FIFO_CFG0_ENABLE_SHIFT); + ARGE_WRITE(sc, AR71XX_MAC_FIFO_CFG1, 0x0fff0000); + ARGE_WRITE(sc, AR71XX_MAC_FIFO_CFG2, 0x00001fff); + + ARGE_WRITE(sc, AR71XX_MAC_FIFO_RX_FILTMATCH, + FIFO_RX_FILTMATCH_DEFAULT); + + ARGE_WRITE(sc, AR71XX_MAC_FIFO_RX_FILTMASK, + FIFO_RX_FILTMASK_DEFAULT); + + /* + * Check if we have single-PHY MAC or multi-PHY + */ + phys_total = 0; + for (i = 0; i < ARGE_NPHY; i++) + if (phymask & (1 << i)) + phys_total ++; + + if (phys_total == 0) { + error = EINVAL; + goto fail; + } + + if (phys_total == 1) { + /* Do MII setup. */ + if (mii_phy_probe(dev, &sc->arge_miibus, + arge_ifmedia_upd, arge_ifmedia_sts)) { + device_printf(dev, "MII without any phy!\n"); + error = ENXIO; + goto fail; + } + } + else { + ifmedia_init(&sc->arge_ifmedia, 0, + arge_multiphy_mediachange, + arge_multiphy_mediastatus); + ifmedia_add(&sc->arge_ifmedia, + IFM_ETHER | sc->arge_media_type | sc->arge_duplex_mode, + 0, NULL); + ifmedia_set(&sc->arge_ifmedia, + IFM_ETHER | sc->arge_media_type | sc->arge_duplex_mode); + arge_set_pll(sc, sc->arge_media_type, sc->arge_duplex_mode); + } + + /* Call MI attach routine. */ + ether_ifattach(ifp, eaddr); + + /* Hook interrupt last to avoid having to lock softc */ + error = bus_setup_intr(dev, sc->arge_irq, INTR_TYPE_NET | INTR_MPSAFE, + arge_intr_filter, arge_intr, sc, &sc->arge_intrhand); + + if (error) { + device_printf(dev, "couldn't set up irq\n"); + ether_ifdetach(ifp); + goto fail; + } + +fail: + if (error) + arge_detach(dev); + + return (error); +} + +static int +arge_detach(device_t dev) +{ + struct arge_softc *sc = device_get_softc(dev); + struct ifnet *ifp = sc->arge_ifp; + + KASSERT(mtx_initialized(&sc->arge_mtx), ("arge mutex not initialized")); + + /* These should only be active if attach succeeded */ + if (device_is_attached(dev)) { + ARGE_LOCK(sc); + sc->arge_detach = 1; +#ifdef DEVICE_POLLING + if (ifp->if_capenable & IFCAP_POLLING) + ether_poll_deregister(ifp); +#endif + + arge_stop(sc); + ARGE_UNLOCK(sc); + taskqueue_drain(taskqueue_swi, &sc->arge_link_task); + ether_ifdetach(ifp); + } + + if (sc->arge_miibus) + device_delete_child(dev, sc->arge_miibus); + + bus_generic_detach(dev); + + if (sc->arge_intrhand) + bus_teardown_intr(dev, sc->arge_irq, sc->arge_intrhand); + + if (sc->arge_res) + bus_release_resource(dev, SYS_RES_MEMORY, sc->arge_rid, + sc->arge_res); + + if (ifp) + if_free(ifp); + + arge_dma_free(sc); + + mtx_destroy(&sc->arge_mtx); + + return (0); + +} + +static int +arge_suspend(device_t dev) +{ + + panic("%s", __func__); + return 0; +} + +static int +arge_resume(device_t dev) +{ + + panic("%s", __func__); + return 0; +} + +static int +arge_shutdown(device_t dev) +{ + struct arge_softc *sc; + + sc = device_get_softc(dev); + + ARGE_LOCK(sc); + arge_stop(sc); + ARGE_UNLOCK(sc); + + return (0); +} + +static int +arge_miibus_readreg(device_t dev, int phy, int reg) +{ + struct arge_softc * sc = device_get_softc(dev); + int i, result; + uint32_t addr = (phy << MAC_MII_PHY_ADDR_SHIFT) + | (reg & MAC_MII_REG_MASK); + + if ((sc->arge_phymask & (1 << phy)) == 0) + return (0); + + mtx_lock(&miibus_mtx); + ARGE_MII_WRITE(AR71XX_MAC_MII_CMD, MAC_MII_CMD_WRITE); + ARGE_MII_WRITE(AR71XX_MAC_MII_ADDR, addr); + ARGE_MII_WRITE(AR71XX_MAC_MII_CMD, MAC_MII_CMD_READ); + + i = ARGE_MII_TIMEOUT; + while ((ARGE_MII_READ(AR71XX_MAC_MII_INDICATOR) & + MAC_MII_INDICATOR_BUSY) && (i--)) + DELAY(5); + + if (i < 0) { + mtx_unlock(&miibus_mtx); + dprintf("%s timedout\n", __func__); + /* XXX: return ERRNO istead? */ + return (-1); + } + + result = ARGE_MII_READ(AR71XX_MAC_MII_STATUS) & MAC_MII_STATUS_MASK; + ARGE_MII_WRITE(AR71XX_MAC_MII_CMD, MAC_MII_CMD_WRITE); + mtx_unlock(&miibus_mtx); + + dprintf("%s: phy=%d, reg=%02x, value[%08x]=%04x\n", __func__, + phy, reg, addr, result); + + return (result); +} + +static int +arge_miibus_writereg(device_t dev, int phy, int reg, int data) +{ + struct arge_softc * sc = device_get_softc(dev); + int i; + uint32_t addr = + (phy << MAC_MII_PHY_ADDR_SHIFT) | (reg & MAC_MII_REG_MASK); + + + if ((sc->arge_phymask & (1 << phy)) == 0) + return (-1); + + dprintf("%s: phy=%d, reg=%02x, value=%04x\n", __func__, + phy, reg, data); + + mtx_lock(&miibus_mtx); + ARGE_MII_WRITE(AR71XX_MAC_MII_ADDR, addr); + ARGE_MII_WRITE(AR71XX_MAC_MII_CONTROL, data); + + i = ARGE_MII_TIMEOUT; + while ((ARGE_MII_READ(AR71XX_MAC_MII_INDICATOR) & + MAC_MII_INDICATOR_BUSY) && (i--)) + DELAY(5); + + mtx_unlock(&miibus_mtx); + + if (i < 0) { + dprintf("%s timedout\n", __func__); + /* XXX: return ERRNO istead? */ + return (-1); + } + + return (0); +} + +static void +arge_miibus_statchg(device_t dev) +{ + struct arge_softc *sc; + + sc = device_get_softc(dev); + taskqueue_enqueue(taskqueue_swi, &sc->arge_link_task); +} + +static void +arge_link_task(void *arg, int pending) +{ + struct arge_softc *sc; + struct mii_data *mii; + struct ifnet *ifp; + uint32_t media, duplex; + + sc = (struct arge_softc *)arg; + + ARGE_LOCK(sc); + mii = device_get_softc(sc->arge_miibus); + ifp = sc->arge_ifp; + if (mii == NULL || ifp == NULL || + (ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) { + ARGE_UNLOCK(sc); + return; + } + + if (mii->mii_media_status & IFM_ACTIVE) { + + media = IFM_SUBTYPE(mii->mii_media_active); + + if (media != IFM_NONE) { + sc->arge_link_status = 1; + duplex = mii->mii_media_active & IFM_GMASK; + arge_set_pll(sc, media, duplex); + } + } else + sc->arge_link_status = 0; + + ARGE_UNLOCK(sc); +} + +static void +arge_set_pll(struct arge_softc *sc, int media, int duplex) +{ + uint32_t cfg, ifcontrol, rx_filtmask, pll, sec_cfg; + + cfg = ARGE_READ(sc, AR71XX_MAC_CFG2); + cfg &= ~(MAC_CFG2_IFACE_MODE_1000 + | MAC_CFG2_IFACE_MODE_10_100 + | MAC_CFG2_FULL_DUPLEX); + + if (duplex == IFM_FDX) + cfg |= MAC_CFG2_FULL_DUPLEX; + + ifcontrol = ARGE_READ(sc, AR71XX_MAC_IFCONTROL); + ifcontrol &= ~MAC_IFCONTROL_SPEED; + rx_filtmask = + ARGE_READ(sc, AR71XX_MAC_FIFO_RX_FILTMASK); + rx_filtmask &= ~FIFO_RX_MASK_BYTE_MODE; + + switch(media) { + case IFM_10_T: + cfg |= MAC_CFG2_IFACE_MODE_10_100; + pll = PLL_ETH_INT_CLK_10; + break; + case IFM_100_TX: + cfg |= MAC_CFG2_IFACE_MODE_10_100; + ifcontrol |= MAC_IFCONTROL_SPEED; + pll = PLL_ETH_INT_CLK_100; + break; + case IFM_1000_T: + case IFM_1000_SX: + cfg |= MAC_CFG2_IFACE_MODE_1000; + rx_filtmask |= FIFO_RX_MASK_BYTE_MODE; + pll = PLL_ETH_INT_CLK_1000; + break; + default: + pll = PLL_ETH_INT_CLK_100; + device_printf(sc->arge_dev, + "Unknown media %d\n", media); + } + + ARGE_WRITE(sc, AR71XX_MAC_FIFO_TX_THRESHOLD, + 0x008001ff); + + ARGE_WRITE(sc, AR71XX_MAC_CFG2, cfg); + ARGE_WRITE(sc, AR71XX_MAC_IFCONTROL, ifcontrol); + ARGE_WRITE(sc, AR71XX_MAC_FIFO_RX_FILTMASK, + rx_filtmask); + + /* set PLL registers */ + sec_cfg = ATH_READ_REG(AR71XX_PLL_SEC_CONFIG); + sec_cfg &= ~(3 << sc->arge_pll_reg_shift); + sec_cfg |= (2 << sc->arge_pll_reg_shift); + + ATH_WRITE_REG(AR71XX_PLL_SEC_CONFIG, sec_cfg); + DELAY(100); + + ATH_WRITE_REG(sc->arge_pll_reg, pll); + + sec_cfg |= (3 << sc->arge_pll_reg_shift); + ATH_WRITE_REG(AR71XX_PLL_SEC_CONFIG, sec_cfg); + DELAY(100); + + sec_cfg &= ~(3 << sc->arge_pll_reg_shift); + ATH_WRITE_REG(AR71XX_PLL_SEC_CONFIG, sec_cfg); + DELAY(100); +} + + +static void +arge_reset_dma(struct arge_softc *sc) +{ + ARGE_WRITE(sc, AR71XX_DMA_RX_CONTROL, 0); + ARGE_WRITE(sc, AR71XX_DMA_TX_CONTROL, 0); + + ARGE_WRITE(sc, AR71XX_DMA_RX_DESC, 0); + ARGE_WRITE(sc, AR71XX_DMA_TX_DESC, 0); + + /* Clear all possible RX interrupts */ + while(ARGE_READ(sc, AR71XX_DMA_RX_STATUS) & DMA_RX_STATUS_PKT_RECVD) + ARGE_WRITE(sc, AR71XX_DMA_RX_STATUS, DMA_RX_STATUS_PKT_RECVD); + + /* + * Clear all possible TX interrupts + */ + while(ARGE_READ(sc, AR71XX_DMA_TX_STATUS) & DMA_TX_STATUS_PKT_SENT) + ARGE_WRITE(sc, AR71XX_DMA_TX_STATUS, DMA_TX_STATUS_PKT_SENT); + + /* + * Now Rx/Tx errors + */ + ARGE_WRITE(sc, AR71XX_DMA_RX_STATUS, + DMA_RX_STATUS_BUS_ERROR | DMA_RX_STATUS_OVERFLOW); + ARGE_WRITE(sc, AR71XX_DMA_TX_STATUS, + DMA_TX_STATUS_BUS_ERROR | DMA_TX_STATUS_UNDERRUN); +} + + + +static void +arge_init(void *xsc) +{ + struct arge_softc *sc = xsc; + + ARGE_LOCK(sc); + arge_init_locked(sc); + ARGE_UNLOCK(sc); +} + +static void +arge_init_locked(struct arge_softc *sc) +{ + struct ifnet *ifp = sc->arge_ifp; + struct mii_data *mii; + + ARGE_LOCK_ASSERT(sc); + + arge_stop(sc); + + /* Init circular RX list. */ + if (arge_rx_ring_init(sc) != 0) { + device_printf(sc->arge_dev, + "initialization failed: no memory for rx buffers\n"); + arge_stop(sc); + return; + } + + /* Init tx descriptors. */ + arge_tx_ring_init(sc); + + arge_reset_dma(sc); + + + if (sc->arge_miibus) { + sc->arge_link_status = 0; + mii = device_get_softc(sc->arge_miibus); + mii_mediachg(mii); + } + else { + /* + * Sun always shines over multiPHY interface + */ + sc->arge_link_status = 1; + } + + ifp->if_drv_flags |= IFF_DRV_RUNNING; + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + + if (sc->arge_miibus) + callout_reset(&sc->arge_stat_callout, hz, arge_tick, sc); + + ARGE_WRITE(sc, AR71XX_DMA_TX_DESC, ARGE_TX_RING_ADDR(sc, 0)); + ARGE_WRITE(sc, AR71XX_DMA_RX_DESC, ARGE_RX_RING_ADDR(sc, 0)); + + /* Start listening */ + ARGE_WRITE(sc, AR71XX_DMA_RX_CONTROL, DMA_RX_CONTROL_EN); + + /* Enable interrupts */ + ARGE_WRITE(sc, AR71XX_DMA_INTR, DMA_INTR_ALL); +} + +/* + * Encapsulate an mbuf chain in a descriptor by coupling the mbuf data + * pointers to the fragment pointers. + */ +static int +arge_encap(struct arge_softc *sc, struct mbuf **m_head) +{ + struct arge_txdesc *txd; + struct arge_desc *desc, *prev_desc; + bus_dma_segment_t txsegs[ARGE_MAXFRAGS]; + int error, i, nsegs, prod, prev_prod; + struct mbuf *m; + + ARGE_LOCK_ASSERT(sc); + + /* + * Fix mbuf chain, all fragments should be 4 bytes aligned and + * even 4 bytes + */ + m = *m_head; + if((mtod(m, intptr_t) & 3) != 0) { + m = m_defrag(*m_head, M_DONTWAIT); + if (m == NULL) { + *m_head = NULL; + return (ENOBUFS); + } + *m_head = m; + } + + prod = sc->arge_cdata.arge_tx_prod; + txd = &sc->arge_cdata.arge_txdesc[prod]; + error = bus_dmamap_load_mbuf_sg(sc->arge_cdata.arge_tx_tag, + txd->tx_dmamap, *m_head, txsegs, &nsegs, BUS_DMA_NOWAIT); + + if (error == EFBIG) { + panic("EFBIG"); + } else if (error != 0) + return (error); + + if (nsegs == 0) { + m_freem(*m_head); + *m_head = NULL; + return (EIO); + } + + /* Check number of available descriptors. */ + if (sc->arge_cdata.arge_tx_cnt + nsegs >= (ARGE_TX_RING_COUNT - 1)) { + bus_dmamap_unload(sc->arge_cdata.arge_tx_tag, txd->tx_dmamap); + return (ENOBUFS); + } + + txd->tx_m = *m_head; + bus_dmamap_sync(sc->arge_cdata.arge_tx_tag, txd->tx_dmamap, + BUS_DMASYNC_PREWRITE); + + /* + * Make a list of descriptors for this packet. DMA controller will + * walk through it while arge_link is not zero. + */ + prev_prod = prod; + desc = prev_desc = NULL; + for (i = 0; i < nsegs; i++) { + desc = &sc->arge_rdata.arge_tx_ring[prod]; + desc->packet_ctrl = ARGE_DMASIZE(txsegs[i].ds_len); + + if (txsegs[i].ds_addr & 3) + panic("TX packet address unaligned\n"); + + desc->packet_addr = txsegs[i].ds_addr; + + /* link with previous descriptor */ + if (prev_desc) + prev_desc->packet_ctrl |= ARGE_DESC_MORE; + + sc->arge_cdata.arge_tx_cnt++; + prev_desc = desc; + ARGE_INC(prod, ARGE_TX_RING_COUNT); + } + + /* Update producer index. */ + sc->arge_cdata.arge_tx_prod = prod; + + /* Sync descriptors. */ + bus_dmamap_sync(sc->arge_cdata.arge_tx_ring_tag, + sc->arge_cdata.arge_tx_ring_map, + BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); + + /* Start transmitting */ + ARGE_WRITE(sc, AR71XX_DMA_TX_CONTROL, DMA_TX_CONTROL_EN); + return (0); +} + +static void +arge_start(struct ifnet *ifp) +{ + struct arge_softc *sc; + + sc = ifp->if_softc; + + ARGE_LOCK(sc); + arge_start_locked(ifp); + ARGE_UNLOCK(sc); +} + +static void +arge_start_locked(struct ifnet *ifp) +{ + struct arge_softc *sc; + struct mbuf *m_head; + int enq; + + sc = ifp->if_softc; + + ARGE_LOCK_ASSERT(sc); + + if ((ifp->if_drv_flags & (IFF_DRV_RUNNING | IFF_DRV_OACTIVE)) != + IFF_DRV_RUNNING || sc->arge_link_status == 0 ) + return; + + arge_flush_ddr(sc); + + for (enq = 0; !IFQ_DRV_IS_EMPTY(&ifp->if_snd) && + sc->arge_cdata.arge_tx_cnt < ARGE_TX_RING_COUNT - 2; ) { + IFQ_DRV_DEQUEUE(&ifp->if_snd, m_head); + if (m_head == NULL) + break; + + + /* + * Pack the data into the transmit ring. + */ + if (arge_encap(sc, &m_head)) { + if (m_head == NULL) + break; + IFQ_DRV_PREPEND(&ifp->if_snd, m_head); + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + break; + } + + enq++; + /* + * If there's a BPF listener, bounce a copy of this frame + * to him. + */ + ETHER_BPF_MTAP(ifp, m_head); + } +} + +static void +arge_stop(struct arge_softc *sc) +{ + struct ifnet *ifp; + + ARGE_LOCK_ASSERT(sc); + + ifp = sc->arge_ifp; + ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); + if (sc->arge_miibus) + callout_stop(&sc->arge_stat_callout); + + /* mask out interrupts */ + ARGE_WRITE(sc, AR71XX_DMA_INTR, 0); + + arge_reset_dma(sc); +} + + +static int +arge_ioctl(struct ifnet *ifp, u_long command, caddr_t data) +{ + struct arge_softc *sc = ifp->if_softc; + struct ifreq *ifr = (struct ifreq *) data; + struct mii_data *mii; + int error; +#ifdef DEVICE_POLLING + int mask; +#endif + + switch (command) { + case SIOCSIFFLAGS: + ARGE_LOCK(sc); + if ((ifp->if_flags & IFF_UP) != 0) { + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) { + if (((ifp->if_flags ^ sc->arge_if_flags) + & (IFF_PROMISC | IFF_ALLMULTI)) != 0) { + /* XXX: handle promisc & multi flags */ + } + + } else { + if (!sc->arge_detach) + arge_init_locked(sc); + } + } else if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) { + ifp->if_drv_flags &= ~IFF_DRV_RUNNING; + arge_stop(sc); + } + sc->arge_if_flags = ifp->if_flags; + ARGE_UNLOCK(sc); + error = 0; + break; + case SIOCADDMULTI: + case SIOCDELMULTI: + /* XXX: implement SIOCDELMULTI */ + error = 0; + break; + case SIOCGIFMEDIA: + case SIOCSIFMEDIA: + if (sc->arge_miibus) { + mii = device_get_softc(sc->arge_miibus); + error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, command); + } + else + error = ifmedia_ioctl(ifp, ifr, &sc->arge_ifmedia, command); + break; + case SIOCSIFCAP: + /* XXX: Check other capabilities */ +#ifdef DEVICE_POLLING + mask = ifp->if_capenable ^ ifr->ifr_reqcap; + if (mask & IFCAP_POLLING) { + if (ifr->ifr_reqcap & IFCAP_POLLING) { + ARGE_WRITE(sc, AR71XX_DMA_INTR, 0); + error = ether_poll_register(arge_poll, ifp); + if (error) + return error; + ARGE_LOCK(sc); + ifp->if_capenable |= IFCAP_POLLING; + ARGE_UNLOCK(sc); + } else { + ARGE_WRITE(sc, AR71XX_DMA_INTR, DMA_INTR_ALL); + error = ether_poll_deregister(ifp); + ARGE_LOCK(sc); + ifp->if_capenable &= ~IFCAP_POLLING; + ARGE_UNLOCK(sc); + } + } + error = 0; + break; +#endif + default: + error = ether_ioctl(ifp, command, data); + break; + } + + return (error); +} + +/* + * Set media options. + */ +static int +arge_ifmedia_upd(struct ifnet *ifp) +{ + struct arge_softc *sc; + struct mii_data *mii; + struct mii_softc *miisc; + int error; + + sc = ifp->if_softc; + ARGE_LOCK(sc); + mii = device_get_softc(sc->arge_miibus); + if (mii->mii_instance) { + LIST_FOREACH(miisc, &mii->mii_phys, mii_list) + mii_phy_reset(miisc); + } + error = mii_mediachg(mii); + ARGE_UNLOCK(sc); + + return (error); +} + +/* + * Report current media status. + */ +static void +arge_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr) +{ + struct arge_softc *sc = ifp->if_softc; + struct mii_data *mii; + + mii = device_get_softc(sc->arge_miibus); + ARGE_LOCK(sc); + mii_pollstat(mii); + ARGE_UNLOCK(sc); + ifmr->ifm_active = mii->mii_media_active; + ifmr->ifm_status = mii->mii_media_status; +} + +struct arge_dmamap_arg { + bus_addr_t arge_busaddr; +}; + +static void +arge_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nseg, int error) +{ + struct arge_dmamap_arg *ctx; + + if (error != 0) + return; + ctx = arg; + ctx->arge_busaddr = segs[0].ds_addr; +} + +static int +arge_dma_alloc(struct arge_softc *sc) +{ + struct arge_dmamap_arg ctx; + struct arge_txdesc *txd; + struct arge_rxdesc *rxd; + int error, i; + + /* Create parent DMA tag. */ + error = bus_dma_tag_create( + bus_get_dma_tag(sc->arge_dev), /* parent */ + 1, 0, /* alignment, boundary */ + BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, NULL, /* filter, filterarg */ + BUS_SPACE_MAXSIZE_32BIT, /* maxsize */ + 0, /* nsegments */ + BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ + 0, /* flags */ + NULL, NULL, /* lockfunc, lockarg */ + &sc->arge_cdata.arge_parent_tag); + if (error != 0) { + device_printf(sc->arge_dev, "failed to create parent DMA tag\n"); + goto fail; + } + /* Create tag for Tx ring. */ + error = bus_dma_tag_create( + sc->arge_cdata.arge_parent_tag, /* parent */ + ARGE_RING_ALIGN, 0, /* alignment, boundary */ + BUS_SPACE_MAXADDR, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, NULL, /* filter, filterarg */ + ARGE_TX_DMA_SIZE, /* maxsize */ + 1, /* nsegments */ + ARGE_TX_DMA_SIZE, /* maxsegsize */ + 0, /* flags */ + NULL, NULL, /* lockfunc, lockarg */ + &sc->arge_cdata.arge_tx_ring_tag); + if (error != 0) { + device_printf(sc->arge_dev, "failed to create Tx ring DMA tag\n"); + goto fail; + } + + /* Create tag for Rx ring. */ + error = bus_dma_tag_create( + sc->arge_cdata.arge_parent_tag, /* parent */ + ARGE_RING_ALIGN, 0, /* alignment, boundary */ + BUS_SPACE_MAXADDR, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, NULL, /* filter, filterarg */ + ARGE_RX_DMA_SIZE, /* maxsize */ + 1, /* nsegments */ + ARGE_RX_DMA_SIZE, /* maxsegsize */ + 0, /* flags */ + NULL, NULL, /* lockfunc, lockarg */ + &sc->arge_cdata.arge_rx_ring_tag); + if (error != 0) { + device_printf(sc->arge_dev, "failed to create Rx ring DMA tag\n"); + goto fail; + } + + /* Create tag for Tx buffers. */ + error = bus_dma_tag_create( + sc->arge_cdata.arge_parent_tag, /* parent */ + sizeof(uint32_t), 0, /* alignment, boundary */ + BUS_SPACE_MAXADDR, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, NULL, /* filter, filterarg */ + MCLBYTES * ARGE_MAXFRAGS, /* maxsize */ + ARGE_MAXFRAGS, /* nsegments */ + MCLBYTES, /* maxsegsize */ + 0, /* flags */ + NULL, NULL, /* lockfunc, lockarg */ + &sc->arge_cdata.arge_tx_tag); + if (error != 0) { + device_printf(sc->arge_dev, "failed to create Tx DMA tag\n"); + goto fail; + } + + /* Create tag for Rx buffers. */ + error = bus_dma_tag_create( + sc->arge_cdata.arge_parent_tag, /* parent */ + ARGE_RX_ALIGN, 0, /* alignment, boundary */ + BUS_SPACE_MAXADDR, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, NULL, /* filter, filterarg */ + MCLBYTES, /* maxsize */ + ARGE_MAXFRAGS, /* nsegments */ + MCLBYTES, /* maxsegsize */ + 0, /* flags */ + NULL, NULL, /* lockfunc, lockarg */ + &sc->arge_cdata.arge_rx_tag); + if (error != 0) { + device_printf(sc->arge_dev, "failed to create Rx DMA tag\n"); + goto fail; + } + + /* Allocate DMA'able memory and load the DMA map for Tx ring. */ + error = bus_dmamem_alloc(sc->arge_cdata.arge_tx_ring_tag, + (void **)&sc->arge_rdata.arge_tx_ring, BUS_DMA_WAITOK | + BUS_DMA_COHERENT | BUS_DMA_ZERO, &sc->arge_cdata.arge_tx_ring_map); + if (error != 0) { + device_printf(sc->arge_dev, + "failed to allocate DMA'able memory for Tx ring\n"); + goto fail; + } + + ctx.arge_busaddr = 0; + error = bus_dmamap_load(sc->arge_cdata.arge_tx_ring_tag, + sc->arge_cdata.arge_tx_ring_map, sc->arge_rdata.arge_tx_ring, + ARGE_TX_DMA_SIZE, arge_dmamap_cb, &ctx, 0); + if (error != 0 || ctx.arge_busaddr == 0) { + device_printf(sc->arge_dev, + "failed to load DMA'able memory for Tx ring\n"); + goto fail; + } + sc->arge_rdata.arge_tx_ring_paddr = ctx.arge_busaddr; + + /* Allocate DMA'able memory and load the DMA map for Rx ring. */ + error = bus_dmamem_alloc(sc->arge_cdata.arge_rx_ring_tag, + (void **)&sc->arge_rdata.arge_rx_ring, BUS_DMA_WAITOK | + BUS_DMA_COHERENT | BUS_DMA_ZERO, &sc->arge_cdata.arge_rx_ring_map); + if (error != 0) { + device_printf(sc->arge_dev, + "failed to allocate DMA'able memory for Rx ring\n"); + goto fail; + } + + ctx.arge_busaddr = 0; + error = bus_dmamap_load(sc->arge_cdata.arge_rx_ring_tag, + sc->arge_cdata.arge_rx_ring_map, sc->arge_rdata.arge_rx_ring, + ARGE_RX_DMA_SIZE, arge_dmamap_cb, &ctx, 0); + if (error != 0 || ctx.arge_busaddr == 0) { + device_printf(sc->arge_dev, + "failed to load DMA'able memory for Rx ring\n"); + goto fail; + } + sc->arge_rdata.arge_rx_ring_paddr = ctx.arge_busaddr; + + /* Create DMA maps for Tx buffers. */ + for (i = 0; i < ARGE_TX_RING_COUNT; i++) { + txd = &sc->arge_cdata.arge_txdesc[i]; + txd->tx_m = NULL; + txd->tx_dmamap = NULL; + error = bus_dmamap_create(sc->arge_cdata.arge_tx_tag, 0, + &txd->tx_dmamap); + if (error != 0) { + device_printf(sc->arge_dev, + "failed to create Tx dmamap\n"); + goto fail; + } + } + /* Create DMA maps for Rx buffers. */ + if ((error = bus_dmamap_create(sc->arge_cdata.arge_rx_tag, 0, + &sc->arge_cdata.arge_rx_sparemap)) != 0) { + device_printf(sc->arge_dev, + "failed to create spare Rx dmamap\n"); + goto fail; + } + for (i = 0; i < ARGE_RX_RING_COUNT; i++) { + rxd = &sc->arge_cdata.arge_rxdesc[i]; + rxd->rx_m = NULL; + rxd->rx_dmamap = NULL; + error = bus_dmamap_create(sc->arge_cdata.arge_rx_tag, 0, + &rxd->rx_dmamap); + if (error != 0) { + device_printf(sc->arge_dev, + "failed to create Rx dmamap\n"); + goto fail; + } + } + +fail: + return (error); +} + +static void +arge_dma_free(struct arge_softc *sc) +{ + struct arge_txdesc *txd; + struct arge_rxdesc *rxd; + int i; + + /* Tx ring. */ + if (sc->arge_cdata.arge_tx_ring_tag) { + if (sc->arge_cdata.arge_tx_ring_map) + bus_dmamap_unload(sc->arge_cdata.arge_tx_ring_tag, + sc->arge_cdata.arge_tx_ring_map); + if (sc->arge_cdata.arge_tx_ring_map && + sc->arge_rdata.arge_tx_ring) + bus_dmamem_free(sc->arge_cdata.arge_tx_ring_tag, + sc->arge_rdata.arge_tx_ring, + sc->arge_cdata.arge_tx_ring_map); + sc->arge_rdata.arge_tx_ring = NULL; + sc->arge_cdata.arge_tx_ring_map = NULL; + bus_dma_tag_destroy(sc->arge_cdata.arge_tx_ring_tag); + sc->arge_cdata.arge_tx_ring_tag = NULL; + } + /* Rx ring. */ + if (sc->arge_cdata.arge_rx_ring_tag) { + if (sc->arge_cdata.arge_rx_ring_map) + bus_dmamap_unload(sc->arge_cdata.arge_rx_ring_tag, + sc->arge_cdata.arge_rx_ring_map); + if (sc->arge_cdata.arge_rx_ring_map && + sc->arge_rdata.arge_rx_ring) + bus_dmamem_free(sc->arge_cdata.arge_rx_ring_tag, + sc->arge_rdata.arge_rx_ring, + sc->arge_cdata.arge_rx_ring_map); + sc->arge_rdata.arge_rx_ring = NULL; + sc->arge_cdata.arge_rx_ring_map = NULL; + bus_dma_tag_destroy(sc->arge_cdata.arge_rx_ring_tag); + sc->arge_cdata.arge_rx_ring_tag = NULL; + } + /* Tx buffers. */ + if (sc->arge_cdata.arge_tx_tag) { + for (i = 0; i < ARGE_TX_RING_COUNT; i++) { + txd = &sc->arge_cdata.arge_txdesc[i]; + if (txd->tx_dmamap) { + bus_dmamap_destroy(sc->arge_cdata.arge_tx_tag, + txd->tx_dmamap); + txd->tx_dmamap = NULL; + } + } + bus_dma_tag_destroy(sc->arge_cdata.arge_tx_tag); + sc->arge_cdata.arge_tx_tag = NULL; + } + /* Rx buffers. */ + if (sc->arge_cdata.arge_rx_tag) { + for (i = 0; i < ARGE_RX_RING_COUNT; i++) { + rxd = &sc->arge_cdata.arge_rxdesc[i]; + if (rxd->rx_dmamap) { + bus_dmamap_destroy(sc->arge_cdata.arge_rx_tag, + rxd->rx_dmamap); + rxd->rx_dmamap = NULL; + } + } + if (sc->arge_cdata.arge_rx_sparemap) { + bus_dmamap_destroy(sc->arge_cdata.arge_rx_tag, + sc->arge_cdata.arge_rx_sparemap); + sc->arge_cdata.arge_rx_sparemap = 0; + } + bus_dma_tag_destroy(sc->arge_cdata.arge_rx_tag); + sc->arge_cdata.arge_rx_tag = NULL; + } + + if (sc->arge_cdata.arge_parent_tag) { + bus_dma_tag_destroy(sc->arge_cdata.arge_parent_tag); + sc->arge_cdata.arge_parent_tag = NULL; + } +} + +/* + * Initialize the transmit descriptors. + */ +static int +arge_tx_ring_init(struct arge_softc *sc) +{ + struct arge_ring_data *rd; + struct arge_txdesc *txd; + bus_addr_t addr; + int i; + + sc->arge_cdata.arge_tx_prod = 0; + sc->arge_cdata.arge_tx_cons = 0; + sc->arge_cdata.arge_tx_cnt = 0; + sc->arge_cdata.arge_tx_pkts = 0; + + rd = &sc->arge_rdata; + bzero(rd->arge_tx_ring, sizeof(rd->arge_tx_ring)); + for (i = 0; i < ARGE_TX_RING_COUNT; i++) { + if (i == ARGE_TX_RING_COUNT - 1) + addr = ARGE_TX_RING_ADDR(sc, 0); + else + addr = ARGE_TX_RING_ADDR(sc, i + 1); + rd->arge_tx_ring[i].packet_ctrl = ARGE_DESC_EMPTY; + rd->arge_tx_ring[i].next_desc = addr; + txd = &sc->arge_cdata.arge_txdesc[i]; + txd->tx_m = NULL; + } + + bus_dmamap_sync(sc->arge_cdata.arge_tx_ring_tag, + sc->arge_cdata.arge_tx_ring_map, + BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); + + return (0); +} + +/* + * Initialize the RX descriptors and allocate mbufs for them. Note that + * we arrange the descriptors in a closed ring, so that the last descriptor + * points back to the first. + */ +static int +arge_rx_ring_init(struct arge_softc *sc) +{ + struct arge_ring_data *rd; + struct arge_rxdesc *rxd; + bus_addr_t addr; + int i; + + sc->arge_cdata.arge_rx_cons = 0; + + rd = &sc->arge_rdata; + bzero(rd->arge_rx_ring, sizeof(rd->arge_rx_ring)); + for (i = 0; i < ARGE_RX_RING_COUNT; i++) { + rxd = &sc->arge_cdata.arge_rxdesc[i]; + rxd->rx_m = NULL; + rxd->desc = &rd->arge_rx_ring[i]; + if (i == ARGE_RX_RING_COUNT - 1) + addr = ARGE_RX_RING_ADDR(sc, 0); + else + addr = ARGE_RX_RING_ADDR(sc, i + 1); + rd->arge_rx_ring[i].next_desc = addr; + if (arge_newbuf(sc, i) != 0) { + return (ENOBUFS); + } + } + + bus_dmamap_sync(sc->arge_cdata.arge_rx_ring_tag, + sc->arge_cdata.arge_rx_ring_map, + BUS_DMASYNC_PREWRITE); + + return (0); +} + +/* + * Initialize an RX descriptor and attach an MBUF cluster. + */ +static int +arge_newbuf(struct arge_softc *sc, int idx) +{ + struct arge_desc *desc; + struct arge_rxdesc *rxd; + struct mbuf *m; + bus_dma_segment_t segs[1]; + bus_dmamap_t map; + int nsegs; + + m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + if (m == NULL) + return (ENOBUFS); + m->m_len = m->m_pkthdr.len = MCLBYTES; + m_adj(m, sizeof(uint64_t)); + + if (bus_dmamap_load_mbuf_sg(sc->arge_cdata.arge_rx_tag, + sc->arge_cdata.arge_rx_sparemap, m, segs, &nsegs, 0) != 0) { + m_freem(m); + return (ENOBUFS); + } + KASSERT(nsegs == 1, ("%s: %d segments returned!", __func__, nsegs)); + + rxd = &sc->arge_cdata.arge_rxdesc[idx]; + if (rxd->rx_m != NULL) { + bus_dmamap_unload(sc->arge_cdata.arge_rx_tag, rxd->rx_dmamap); + } + map = rxd->rx_dmamap; + rxd->rx_dmamap = sc->arge_cdata.arge_rx_sparemap; + sc->arge_cdata.arge_rx_sparemap = map; + rxd->rx_m = m; + desc = rxd->desc; + if (segs[0].ds_addr & 3) + panic("RX packet address unaligned"); + desc->packet_addr = segs[0].ds_addr; + desc->packet_ctrl = ARGE_DESC_EMPTY | ARGE_DMASIZE(segs[0].ds_len); + + bus_dmamap_sync(sc->arge_cdata.arge_rx_ring_tag, + sc->arge_cdata.arge_rx_ring_map, + BUS_DMASYNC_PREWRITE); + + return (0); +} + +static __inline void +arge_fixup_rx(struct mbuf *m) +{ + int i; + uint16_t *src, *dst; + + src = mtod(m, uint16_t *); + dst = src - 1; + + for (i = 0; i < m->m_len / sizeof(uint16_t); i++) { + *dst++ = *src++; + } + + if (m->m_len % sizeof(uint16_t)) + *(uint8_t *)dst = *(uint8_t *)src; + + m->m_data -= ETHER_ALIGN; +} + +#ifdef DEVICE_POLLING +static int +arge_poll(struct ifnet *ifp, enum poll_cmd cmd, int count) +{ + struct arge_softc *sc = ifp->if_softc; + int rx_npkts = 0; + + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + ARGE_LOCK(sc); + arge_tx_locked(sc); + rx_npkts = arge_rx_locked(sc); + ARGE_UNLOCK(sc); + } + + return (rx_npkts); +} +#endif /* DEVICE_POLLING */ + + +static void +arge_tx_locked(struct arge_softc *sc) +{ + struct arge_txdesc *txd; + struct arge_desc *cur_tx; + struct ifnet *ifp; + uint32_t ctrl; + int cons, prod; + + ARGE_LOCK_ASSERT(sc); + + cons = sc->arge_cdata.arge_tx_cons; + prod = sc->arge_cdata.arge_tx_prod; + if (cons == prod) + return; + + bus_dmamap_sync(sc->arge_cdata.arge_tx_ring_tag, + sc->arge_cdata.arge_tx_ring_map, + BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); + + ifp = sc->arge_ifp; + /* + * Go through our tx list and free mbufs for those + * frames that have been transmitted. + */ + for (; cons != prod; ARGE_INC(cons, ARGE_TX_RING_COUNT)) { + cur_tx = &sc->arge_rdata.arge_tx_ring[cons]; + ctrl = cur_tx->packet_ctrl; + /* Check if descriptor has "finished" flag */ + if ((ctrl & ARGE_DESC_EMPTY) == 0) + break; + + ARGE_WRITE(sc, AR71XX_DMA_TX_STATUS, DMA_TX_STATUS_PKT_SENT); + + sc->arge_cdata.arge_tx_cnt--; + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + + txd = &sc->arge_cdata.arge_txdesc[cons]; + + ifp->if_opackets++; + + bus_dmamap_sync(sc->arge_cdata.arge_tx_tag, txd->tx_dmamap, + BUS_DMASYNC_POSTWRITE); + bus_dmamap_unload(sc->arge_cdata.arge_tx_tag, txd->tx_dmamap); + + /* Free only if it's first descriptor in list */ + if (txd->tx_m) + m_freem(txd->tx_m); + txd->tx_m = NULL; + + /* reset descriptor */ + cur_tx->packet_addr = 0; + } + + sc->arge_cdata.arge_tx_cons = cons; + + bus_dmamap_sync(sc->arge_cdata.arge_tx_ring_tag, + sc->arge_cdata.arge_tx_ring_map, BUS_DMASYNC_PREWRITE); +} + + +static int +arge_rx_locked(struct arge_softc *sc) +{ + struct arge_rxdesc *rxd; + struct ifnet *ifp = sc->arge_ifp; + int cons, prog, packet_len, i; + struct arge_desc *cur_rx; + struct mbuf *m; + int rx_npkts = 0; + + ARGE_LOCK_ASSERT(sc); + + cons = sc->arge_cdata.arge_rx_cons; + + bus_dmamap_sync(sc->arge_cdata.arge_rx_ring_tag, + sc->arge_cdata.arge_rx_ring_map, + BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); + + for (prog = 0; prog < ARGE_RX_RING_COUNT; + ARGE_INC(cons, ARGE_RX_RING_COUNT)) { + cur_rx = &sc->arge_rdata.arge_rx_ring[cons]; + rxd = &sc->arge_cdata.arge_rxdesc[cons]; + m = rxd->rx_m; + + if ((cur_rx->packet_ctrl & ARGE_DESC_EMPTY) != 0) + break; + + ARGE_WRITE(sc, AR71XX_DMA_RX_STATUS, DMA_RX_STATUS_PKT_RECVD); + + prog++; + + packet_len = ARGE_DMASIZE(cur_rx->packet_ctrl); + bus_dmamap_sync(sc->arge_cdata.arge_rx_tag, rxd->rx_dmamap, + BUS_DMASYNC_POSTREAD); + m = rxd->rx_m; + + arge_fixup_rx(m); + m->m_pkthdr.rcvif = ifp; + /* Skip 4 bytes of CRC */ + m->m_pkthdr.len = m->m_len = packet_len - ETHER_CRC_LEN; + ifp->if_ipackets++; + rx_npkts++; + + ARGE_UNLOCK(sc); + (*ifp->if_input)(ifp, m); + ARGE_LOCK(sc); + cur_rx->packet_addr = 0; + } + + if (prog > 0) { + + i = sc->arge_cdata.arge_rx_cons; + for (; prog > 0 ; prog--) { + if (arge_newbuf(sc, i) != 0) { + device_printf(sc->arge_dev, + "Failed to allocate buffer\n"); + break; + } + ARGE_INC(i, ARGE_RX_RING_COUNT); + } + + bus_dmamap_sync(sc->arge_cdata.arge_rx_ring_tag, + sc->arge_cdata.arge_rx_ring_map, + BUS_DMASYNC_PREWRITE); + + sc->arge_cdata.arge_rx_cons = cons; + } + + return (rx_npkts); +} + +static int +arge_intr_filter(void *arg) +{ + struct arge_softc *sc = arg; + uint32_t status, ints; + + status = ARGE_READ(sc, AR71XX_DMA_INTR_STATUS); + ints = ARGE_READ(sc, AR71XX_DMA_INTR); + +#if 0 + dprintf("int mask(filter) = %b\n", ints, + "\20\10RX_BUS_ERROR\7RX_OVERFLOW\5RX_PKT_RCVD" + "\4TX_BUS_ERROR\2TX_UNDERRUN\1TX_PKT_SENT"); + dprintf("status(filter) = %b\n", status, + "\20\10RX_BUS_ERROR\7RX_OVERFLOW\5RX_PKT_RCVD" + "\4TX_BUS_ERROR\2TX_UNDERRUN\1TX_PKT_SENT"); +#endif + + if (status & DMA_INTR_ALL) { + sc->arge_intr_status |= status; + ARGE_WRITE(sc, AR71XX_DMA_INTR, 0); + return (FILTER_SCHEDULE_THREAD); + } + + sc->arge_intr_status = 0; + return (FILTER_STRAY); +} + +static void +arge_intr(void *arg) +{ + struct arge_softc *sc = arg; + uint32_t status; + + status = ARGE_READ(sc, AR71XX_DMA_INTR_STATUS); + status |= sc->arge_intr_status; + +#if 0 + dprintf("int status(intr) = %b\n", status, + "\20\10\7RX_OVERFLOW\5RX_PKT_RCVD" + "\4TX_BUS_ERROR\2TX_UNDERRUN\1TX_PKT_SENT"); +#endif + + /* + * Is it our interrupt at all? + */ + if (status == 0) + return; + + if (status & DMA_INTR_RX_BUS_ERROR) { + ARGE_WRITE(sc, AR71XX_DMA_RX_STATUS, DMA_RX_STATUS_BUS_ERROR); + device_printf(sc->arge_dev, "RX bus error"); + return; + } + + if (status & DMA_INTR_TX_BUS_ERROR) { + ARGE_WRITE(sc, AR71XX_DMA_TX_STATUS, DMA_TX_STATUS_BUS_ERROR); + device_printf(sc->arge_dev, "TX bus error"); + return; + } + + ARGE_LOCK(sc); + + if (status & DMA_INTR_RX_PKT_RCVD) + arge_rx_locked(sc); + + /* + * RX overrun disables the receiver. + * Clear indication and re-enable rx. + */ + if ( status & DMA_INTR_RX_OVERFLOW) { + ARGE_WRITE(sc, AR71XX_DMA_RX_STATUS, DMA_RX_STATUS_OVERFLOW); + ARGE_WRITE(sc, AR71XX_DMA_RX_CONTROL, DMA_RX_CONTROL_EN); + } + + if (status & DMA_INTR_TX_PKT_SENT) + arge_tx_locked(sc); + /* + * Underrun turns off TX. Clear underrun indication. + * If there's anything left in the ring, reactivate the tx. + */ + if (status & DMA_INTR_TX_UNDERRUN) { + ARGE_WRITE(sc, AR71XX_DMA_TX_STATUS, DMA_TX_STATUS_UNDERRUN); + if (sc->arge_cdata.arge_tx_pkts > 0 ) { + ARGE_WRITE(sc, AR71XX_DMA_TX_CONTROL, + DMA_TX_CONTROL_EN); + } + } + + /* + * We handled all bits, clear status + */ + sc->arge_intr_status = 0; + ARGE_UNLOCK(sc); + /* + * re-enable all interrupts + */ + ARGE_WRITE(sc, AR71XX_DMA_INTR, DMA_INTR_ALL); +} + + +static void +arge_tick(void *xsc) +{ + struct arge_softc *sc = xsc; + struct mii_data *mii; + + ARGE_LOCK_ASSERT(sc); + + if (sc->arge_miibus) { + mii = device_get_softc(sc->arge_miibus); + mii_tick(mii); + callout_reset(&sc->arge_stat_callout, hz, arge_tick, sc); + } +} + +int +arge_multiphy_mediachange(struct ifnet *ifp) +{ + struct arge_softc *sc = ifp->if_softc; + struct ifmedia *ifm = &sc->arge_ifmedia; + struct ifmedia_entry *ife = ifm->ifm_cur; + + if (IFM_TYPE(ifm->ifm_media) != IFM_ETHER) + return (EINVAL); + + if (IFM_SUBTYPE(ife->ifm_media) == IFM_AUTO) { + device_printf(sc->arge_dev, + "AUTO is not supported for multiphy MAC"); + return (EINVAL); + } + + /* + * Ignore everything + */ + return (0); +} + +void +arge_multiphy_mediastatus(struct ifnet *ifp, struct ifmediareq *ifmr) +{ + struct arge_softc *sc = ifp->if_softc; + + ifmr->ifm_status = IFM_AVALID | IFM_ACTIVE; + ifmr->ifm_active = IFM_ETHER | sc->arge_media_type | + sc->arge_duplex_mode; +} + diff --git a/sys/mips/atheros/if_argevar.h b/sys/mips/atheros/if_argevar.h new file mode 100644 index 000000000000..bfa45099cc96 --- /dev/null +++ b/sys/mips/atheros/if_argevar.h @@ -0,0 +1,157 @@ +/*- + * Copyright (c) 2009, Oleksandr Tymoshenko + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, 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. + */ + +#ifndef __IF_ARGEVAR_H__ +#define __IF_ARGEVAR_H__ + +#define ARGE_NPHY 32 +#define ARGE_TX_RING_COUNT 128 +#define ARGE_RX_RING_COUNT 128 +#define ARGE_RX_DMA_SIZE ARGE_RX_RING_COUNT * sizeof(struct arge_desc) +#define ARGE_TX_DMA_SIZE ARGE_TX_RING_COUNT * sizeof(struct arge_desc) +#define ARGE_MAXFRAGS 8 +#define ARGE_RING_ALIGN sizeof(struct arge_desc) +#define ARGE_RX_ALIGN sizeof(uint32_t) +#define ARGE_MAXFRAGS 8 +#define ARGE_TX_RING_ADDR(sc, i) \ + ((sc)->arge_rdata.arge_tx_ring_paddr + sizeof(struct arge_desc) * (i)) +#define ARGE_RX_RING_ADDR(sc, i) \ + ((sc)->arge_rdata.arge_rx_ring_paddr + sizeof(struct arge_desc) * (i)) +#define ARGE_INC(x,y) (x) = (((x) + 1) % y) + + +#define ARGE_MII_TIMEOUT 1000 + +#define ARGE_LOCK(_sc) mtx_lock(&(_sc)->arge_mtx) +#define ARGE_UNLOCK(_sc) mtx_unlock(&(_sc)->arge_mtx) +#define ARGE_LOCK_ASSERT(_sc) mtx_assert(&(_sc)->arge_mtx, MA_OWNED) + +/* + * register space access macros + */ +#define ARGE_WRITE(sc, reg, val) do { \ + bus_write_4(sc->arge_res, (reg), (val)); \ + } while (0) + +#define ARGE_READ(sc, reg) bus_read_4(sc->arge_res, (reg)) + +#define ARGE_SET_BITS(sc, reg, bits) \ + ARGE_WRITE(sc, reg, ARGE_READ(sc, (reg)) | (bits)) + +#define ARGE_CLEAR_BITS(sc, reg, bits) \ + ARGE_WRITE(sc, reg, ARGE_READ(sc, (reg)) & ~(bits)) + +/* + * MII registers access macros + */ +#define ARGE_MII_READ(reg) \ + *((volatile uint32_t *)MIPS_PHYS_TO_KSEG1((AR71XX_MII_BASE + reg))) + +#define ARGE_MII_WRITE(reg, val) \ + *((volatile uint32_t *)MIPS_PHYS_TO_KSEG1((AR71XX_MII_BASE + reg))) = (val) + + +#define ARGE_DESC_EMPTY (1 << 31) +#define ARGE_DESC_MORE (1 << 24) +#define ARGE_DESC_SIZE_MASK ((1 << 12) - 1) +#define ARGE_DMASIZE(len) ((len) & ARGE_DESC_SIZE_MASK) +struct arge_desc { + uint32_t packet_addr; + uint32_t packet_ctrl; + uint32_t next_desc; + uint32_t padding; +}; + +struct arge_txdesc { + struct mbuf *tx_m; + bus_dmamap_t tx_dmamap; +}; + +struct arge_rxdesc { + struct mbuf *rx_m; + bus_dmamap_t rx_dmamap; + struct arge_desc *desc; +}; + +struct arge_chain_data { + bus_dma_tag_t arge_parent_tag; + bus_dma_tag_t arge_tx_tag; + struct arge_txdesc arge_txdesc[ARGE_TX_RING_COUNT]; + bus_dma_tag_t arge_rx_tag; + struct arge_rxdesc arge_rxdesc[ARGE_RX_RING_COUNT]; + bus_dma_tag_t arge_tx_ring_tag; + bus_dma_tag_t arge_rx_ring_tag; + bus_dmamap_t arge_tx_ring_map; + bus_dmamap_t arge_rx_ring_map; + bus_dmamap_t arge_rx_sparemap; + int arge_tx_pkts; + int arge_tx_prod; + int arge_tx_cons; + int arge_tx_cnt; + int arge_rx_cons; +}; + +struct arge_ring_data { + struct arge_desc *arge_rx_ring; + struct arge_desc *arge_tx_ring; + bus_addr_t arge_rx_ring_paddr; + bus_addr_t arge_tx_ring_paddr; +}; + +struct arge_softc { + struct ifnet *arge_ifp; /* interface info */ + device_t arge_dev; + struct ifmedia arge_ifmedia; + /* + * Media & duples settings for multiPHY MAC + */ + uint32_t arge_media_type; + uint32_t arge_duplex_mode; + struct resource *arge_res; + int arge_rid; + struct resource *arge_irq; + void *arge_intrhand; + device_t arge_miibus; + bus_dma_tag_t arge_parent_tag; + bus_dma_tag_t arge_tag; + struct mtx arge_mtx; + struct callout arge_stat_callout; + struct task arge_link_task; + struct arge_chain_data arge_cdata; + struct arge_ring_data arge_rdata; + int arge_link_status; + int arge_detach; + uint32_t arge_intr_status; + int arge_mac_unit; + int arge_phymask; + uint32_t arge_ddr_flush_reg; + uint32_t arge_pll_reg; + uint32_t arge_pll_reg_shift; + int arge_if_flags; +}; + +#endif /* __IF_ARGEVAR_H__ */ diff --git a/sys/mips/atheros/uart_bus_ar71xx.c b/sys/mips/atheros/uart_bus_ar71xx.c new file mode 100644 index 000000000000..8d83291c271a --- /dev/null +++ b/sys/mips/atheros/uart_bus_ar71xx.c @@ -0,0 +1,88 @@ +/*- + * Copyright (c) 2009, Oleksandr Tymoshenko + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + */ +#include "opt_uart.h" + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include + +#include "uart_if.h" + +static int uart_ar71xx_probe(device_t dev); +extern struct uart_class uart_ar71xx_uart_class; + +static device_method_t uart_ar71xx_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, uart_ar71xx_probe), + DEVMETHOD(device_attach, uart_bus_attach), + DEVMETHOD(device_detach, uart_bus_detach), + { 0, 0 } +}; + +static driver_t uart_ar71xx_driver = { + uart_driver_name, + uart_ar71xx_methods, + sizeof(struct uart_softc), +}; + +extern SLIST_HEAD(uart_devinfo_list, uart_devinfo) uart_sysdevs; + +static int +uart_ar71xx_probe(device_t dev) +{ + struct uart_softc *sc; + uint64_t freq; + + freq = ar71xx_ahb_freq(); + + sc = device_get_softc(dev); + sc->sc_sysdev = SLIST_FIRST(&uart_sysdevs); + sc->sc_class = &uart_ns8250_class; + bcopy(&sc->sc_sysdev->bas, &sc->sc_bas, sizeof(sc->sc_bas)); + sc->sc_sysdev->bas.regshft = 2; + sc->sc_sysdev->bas.bst = mips_bus_space_generic; + sc->sc_sysdev->bas.bsh = MIPS_PHYS_TO_KSEG1(AR71XX_UART_ADDR) + 3; + sc->sc_bas.regshft = 2; + sc->sc_bas.bst = mips_bus_space_generic; + sc->sc_bas.bsh = MIPS_PHYS_TO_KSEG1(AR71XX_UART_ADDR) + 3; + + return (uart_bus_probe(dev, 2, freq, 0, 0)); +} + +DRIVER_MODULE(uart, apb, uart_ar71xx_driver, uart_devclass, 0, 0); diff --git a/sys/mips/atheros/uart_cpu_ar71xx.c b/sys/mips/atheros/uart_cpu_ar71xx.c new file mode 100644 index 000000000000..4528ac68142f --- /dev/null +++ b/sys/mips/atheros/uart_cpu_ar71xx.c @@ -0,0 +1,75 @@ +/*- + * Copyright (c) 2009 Oleksandr Tymoshenko + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ +#include "opt_uart.h" + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include + +#include + +#include +#include + +#include +#include + +bus_space_tag_t uart_bus_space_io; +bus_space_tag_t uart_bus_space_mem; + +int +uart_cpu_eqres(struct uart_bas *b1, struct uart_bas *b2) +{ + return ((b1->bsh == b2->bsh && b1->bst == b2->bst) ? 1 : 0); +} + +int +uart_cpu_getdev(int devtype, struct uart_devinfo *di) +{ + uint64_t freq; + + freq = ar71xx_ahb_freq(); + + di->ops = uart_getops(&uart_ns8250_class); + di->bas.chan = 0; + di->bas.bst = ar71xx_bus_space_reversed; + di->bas.regshft = 2; + di->bas.rclk = freq; + di->baudrate = 115200; + di->databits = 8; + di->stopbits = 1; + + di->parity = UART_PARITY_NONE; + + uart_bus_space_io = NULL; + uart_bus_space_mem = ar71xx_bus_space_reversed; + di->bas.bsh = MIPS_PHYS_TO_KSEG1(AR71XX_UART_ADDR); + return (0); +}