diff options
Diffstat (limited to 'sys/arm64/apple')
-rw-r--r-- | sys/arm64/apple/apple_aic.c | 780 | ||||
-rw-r--r-- | sys/arm64/apple/apple_pinctrl.c | 469 | ||||
-rw-r--r-- | sys/arm64/apple/apple_wdog.c | 234 | ||||
-rw-r--r-- | sys/arm64/apple/exynos_uart.c | 568 | ||||
-rw-r--r-- | sys/arm64/apple/exynos_uart.h | 136 |
5 files changed, 2187 insertions, 0 deletions
diff --git a/sys/arm64/apple/apple_aic.c b/sys/arm64/apple/apple_aic.c new file mode 100644 index 000000000000..c9ce3b4d2165 --- /dev/null +++ b/sys/arm64/apple/apple_aic.c @@ -0,0 +1,780 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2021 Andrew Turner + * Copyright (c) 2022 Michael J. Karels <karels@freebsd.org> + * Copyright (c) 2022 Kyle Evans <kevans@FreeBSD.org> + * + * 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_platform.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/bus.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/proc.h> +#include <sys/rman.h> +#include <sys/smp.h> + +#include <machine/bus.h> +#include <machine/machdep.h> +#ifdef SMP +#include <machine/intr.h> +#include <machine/smp.h> +#endif + +#include <dev/fdt/fdt_intr.h> + +#include <dev/ofw/openfirm.h> +#include <dev/ofw/ofw_bus.h> +#include <dev/ofw/ofw_bus_subr.h> + +#include <dt-bindings/interrupt-controller/apple-aic.h> + +#include "pic_if.h" + +#define AIC_INFO 0x0004 +#define AIC_INFO_NDIE(val) (((val) >> 24) & 0xf) +#define AIC_INFO_NIRQS(val) ((val) & 0x0000ffff) + +#define AIC_WHOAMI 0x2000 +#define AIC_EVENT 0x2004 +#define AIC_EVENT_DIE(val) (((val) >> 24) & 0xff) +#define AIC_EVENT_TYPE(val) (((val) >> 16) & 0xff) +#define AIC_EVENT_TYPE_NONE 0 +#define AIC_EVENT_TYPE_IRQ 1 +#define AIC_EVENT_TYPE_IPI 4 +#define AIC_EVENT_IRQ(val) ((val) & 0xffff) +#define AIC_EVENT_IPI_OTHER 1 +#define AIC_EVENT_IPI_SELF 2 +#define AIC_IPI_SEND 0x2008 +#define AIC_IPI_ACK 0x200c +#define AIC_IPI_MASK_SET 0x2024 +#define AIC_IPI_MASK_CLR 0x2028 +#define AIC_IPI_OTHER 0x00000001 +#define AIC_IPI_SELF 0x80000000 +#define AIC_TARGET_CPU(irq) (0x3000 + (irq) * 4) +#define AIC_SW_SET(irq) (0x4000 + (((irq) >> 5) * 4)) +#define AIC_SW_CLEAR(irq) (0x4080 + (((irq) >> 5) * 4)) +#define AIC_MASK_SET(irq) (0x4100 + (((irq) >> 5) * 4)) +#define AIC_MASK_CLEAR(irq) (0x4180 + (((irq) >> 5) * 4)) +#define AIC_IRQ_MASK(irq) (1u << ((irq) & 0x1f)) + +#define AIC_IPI_LOCAL_RR_EL1 s3_5_c15_c0_0 +#define AIC_IPI_GLOBAL_RR_EL1 s3_5_c15_c0_1 + +#define AIC_IPI_SR_EL1 s3_5_c15_c1_1 +#define AIC_IPI_SR_EL1_PENDING (1 << 0) + +#define AIC_FIQ_VM_TIMER s3_5_c15_c1_3 +#define AIC_FIQ_VM_TIMER_VEN (1 << 0) +#define AIC_FIQ_VM_TIMER_PEN (1 << 1) +#define AIC_FIQ_VM_TIMER_BITS (AIC_FIQ_VM_TIMER_VEN | AIC_FIQ_VM_TIMER_PEN) + +#define CNTV_CTL_ENABLE (1 << 0) +#define CNTV_CTL_IMASK (1 << 1) +#define CNTV_CTL_ISTATUS (1 << 2) +#define CNTV_CTL_BITS \ + (CNTV_CTL_ENABLE | CNTV_CTL_IMASK | CNTV_CTL_ISTATUS) + +#define AIC_MAXCPUS 32 +#define AIC_MAXDIES 4 + +static struct ofw_compat_data compat_data[] = { + { "apple,aic", 1 }, + { NULL, 0 } +}; + +enum apple_aic_irq_type { + AIC_TYPE_INVAL, + AIC_TYPE_IRQ, + AIC_TYPE_FIQ, + AIC_TYPE_IPI, +}; + +struct apple_aic_irqsrc { + struct intr_irqsrc ai_isrc; + enum apple_aic_irq_type ai_type; + struct { + /* AIC_TYPE_IRQ */ + enum intr_polarity ai_pol; + enum intr_trigger ai_trig; + u_int ai_irq; + }; +}; + +#ifdef SMP +#define AIC_NIPIS INTR_IPI_COUNT +#endif + +struct apple_aic_softc { + device_t sc_dev; + struct resource *sc_mem; + struct apple_aic_irqsrc *sc_isrcs[AIC_MAXDIES]; + u_int sc_nirqs; + u_int sc_ndie; +#ifdef SMP + struct apple_aic_irqsrc sc_ipi_srcs[AIC_NIPIS]; + uint32_t *sc_ipimasks; +#endif + u_int *sc_cpuids; /* cpu index to AIC CPU ID */ +}; + +static u_int aic_next_cpu; + +static device_probe_t apple_aic_probe; +static device_attach_t apple_aic_attach; + +static pic_disable_intr_t apple_aic_disable_intr; +static pic_enable_intr_t apple_aic_enable_intr; +static pic_map_intr_t apple_aic_map_intr; +static pic_setup_intr_t apple_aic_setup_intr; +static pic_teardown_intr_t apple_aic_teardown_intr; +static pic_post_filter_t apple_aic_post_filter; +static pic_post_ithread_t apple_aic_post_ithread; +static pic_pre_ithread_t apple_aic_pre_ithread; +#ifdef SMP +static pic_bind_intr_t apple_aic_bind_intr; +static pic_init_secondary_t apple_aic_init_secondary; +static pic_ipi_send_t apple_aic_ipi_send; +static pic_ipi_setup_t apple_aic_ipi_setup; +#endif + +static int apple_aic_irq(void *); +static int apple_aic_fiq(void *); + +static int +apple_aic_probe(device_t dev) +{ + + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) + return (ENXIO); + + device_set_desc(dev, "Apple Interrupt Controller"); + return (BUS_PROBE_DEFAULT); +} + +static int +apple_aic_attach(device_t dev) +{ + struct apple_aic_softc *sc; + struct intr_irqsrc *isrc; + const char *name; + intptr_t xref; + int error, rid; + u_int i, cpu, j, info; + + sc = device_get_softc(dev); + sc->sc_dev = dev; + + rid = 0; + sc->sc_mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, + RF_ACTIVE); + if (sc->sc_mem == NULL) { + device_printf(dev, "Unable to allocate memory\n"); + return (ENXIO); + } + + info = bus_read_4(sc->sc_mem, AIC_INFO); + sc->sc_nirqs = AIC_INFO_NIRQS(info); + sc->sc_ndie = AIC_INFO_NDIE(info) + 1; + if (bootverbose) + device_printf(dev, "Found %d interrupts, %d die\n", + sc->sc_nirqs, sc->sc_ndie); + + for (i = 0; i < sc->sc_ndie; i++) { + sc->sc_isrcs[i] = mallocarray(sc->sc_nirqs, + sizeof(**sc->sc_isrcs), M_DEVBUF, M_WAITOK | M_ZERO); + } + +#ifdef SMP + sc->sc_ipimasks = malloc(sizeof(*sc->sc_ipimasks) * mp_maxid + 1, + M_DEVBUF, M_WAITOK | M_ZERO); +#endif + sc->sc_cpuids = malloc(sizeof(*sc->sc_cpuids) * mp_maxid + 1, + M_DEVBUF, M_WAITOK | M_ZERO); + + cpu = PCPU_GET(cpuid); + sc->sc_cpuids[cpu] = bus_read_4(sc->sc_mem, AIC_WHOAMI); + if (bootverbose) + device_printf(dev, "BSP CPU %d: whoami %x\n", cpu, + sc->sc_cpuids[cpu]); + + name = device_get_nameunit(dev); + for (i = 0; i < sc->sc_ndie; i++) { + struct apple_aic_irqsrc *die_isrcs; + + die_isrcs = sc->sc_isrcs[i]; + for (j = 0; j < sc->sc_nirqs; j++) { + isrc = &die_isrcs[j].ai_isrc; + die_isrcs[j].ai_pol = INTR_POLARITY_CONFORM; + die_isrcs[j].ai_trig = INTR_TRIGGER_CONFORM; + die_isrcs[j].ai_type = AIC_TYPE_INVAL; + die_isrcs[j].ai_irq = j; + + error = intr_isrc_register(isrc, dev, 0, "%s,d%us%u", name, + i, j); + if (error != 0) { + device_printf(dev, "Unable to register irq %u:%u\n", + i, j); + return (error); + } + } + } + + xref = OF_xref_from_node(ofw_bus_get_node(dev)); + if (intr_pic_register(dev, xref) == NULL) { + device_printf(dev, "Unable to register interrupt handler\n"); + return (ENXIO); + } + + if (intr_pic_claim_root(dev, xref, apple_aic_irq, sc, + INTR_ROOT_IRQ) != 0) { + device_printf(dev, + "Unable to set root interrupt controller\n"); + intr_pic_deregister(dev, xref); + return (ENXIO); + } + + if (intr_pic_claim_root(dev, xref, apple_aic_fiq, sc, + INTR_ROOT_FIQ) != 0) { + device_printf(dev, + "Unable to set root fiq controller\n"); + intr_pic_deregister(dev, xref); + return (ENXIO); + } + +#ifdef SMP + if (intr_ipi_pic_register(dev, 0) != 0) { + device_printf(dev, "could not register for IPIs\n"); + return (ENXIO); + } +#endif + + OF_device_register_xref(xref, dev); + + return (0); +} + +static int +apple_aic_map_intr_fdt(struct apple_aic_softc *sc, + struct intr_map_data_fdt *data, u_int *irq, enum apple_aic_irq_type *typep, + enum intr_polarity *polp, enum intr_trigger *trigp, u_int *die) +{ + if (data->ncells != 3) + return (EINVAL); + + /* XXX AIC2 */ + *die = 0; + + /* + * The first cell is the interrupt type: + * 0 = IRQ + * 1 = FIQ + * The second cell is the interrupt number + * The third cell is the flags + */ + switch(data->cells[0]) { + case 0: + if (typep != NULL) + *typep = AIC_TYPE_IRQ; + break; + case 1: + if (typep != NULL) + *typep = AIC_TYPE_FIQ; + break; + default: + return (EINVAL); + } + + *irq = data->cells[1]; + if (*irq > sc->sc_nirqs) + return (EINVAL); + + if (trigp != NULL) { + if ((data->cells[2] & FDT_INTR_EDGE_MASK) != 0) + *trigp = INTR_TRIGGER_EDGE; + else + *trigp = INTR_TRIGGER_LEVEL; + } + if (polp != NULL) { + if ((data->cells[2] & FDT_INTR_LEVEL_HIGH) != 0) + *polp = INTR_POLARITY_HIGH; + else + *polp = INTR_POLARITY_LOW; + } + + return (0); +} + +static int +apple_aic_map_intr(device_t dev, struct intr_map_data *data, + struct intr_irqsrc **isrcp) +{ + struct apple_aic_softc *sc; + int error; + u_int irq; + u_int die; + + sc = device_get_softc(dev); + + error = 0; + switch(data->type) { + case INTR_MAP_DATA_FDT: + error = apple_aic_map_intr_fdt(sc, + (struct intr_map_data_fdt *)data, &irq, NULL, NULL, NULL, + &die); + if (error == 0) + *isrcp = &sc->sc_isrcs[0 /* XXX */][irq].ai_isrc; + break; + default: + return (ENOTSUP); + } + + return (error); +} + +static int +apple_aic_setup_intr(device_t dev, struct intr_irqsrc *isrc, + struct resource *res, struct intr_map_data *data) +{ + struct apple_aic_softc *sc; + enum apple_aic_irq_type type; + struct apple_aic_irqsrc *ai; + enum intr_trigger trig; + enum intr_polarity pol; + int error; + u_int die, irq; + + sc = device_get_softc(dev); + ai = (struct apple_aic_irqsrc *)isrc; + + if (data != NULL) { + KASSERT(data->type == INTR_MAP_DATA_FDT, + ("%s: Only FDT data is supported (got %#x)", __func__, + data->type)); + error = apple_aic_map_intr_fdt(sc, + (struct intr_map_data_fdt *)data, &irq, &type, &pol, &trig, + &die); + if (error != 0) + return (error); + } else { + pol = INTR_POLARITY_CONFORM; + trig = INTR_TRIGGER_CONFORM; + } + + if (isrc->isrc_handlers != 0) { + /* TODO */ + return (0); + } + + if (pol == INTR_POLARITY_CONFORM) + pol = INTR_POLARITY_LOW; + if (trig == INTR_TRIGGER_CONFORM) + trig = INTR_TRIGGER_EDGE; + + ai->ai_pol = pol; + ai->ai_trig = trig; + ai->ai_type = type; + + /* + * Only the timer uses FIQs. These could be sent to any CPU. + */ + switch (type) { + case AIC_TYPE_IRQ: + /* XXX die sensitive? */ + aic_next_cpu = intr_irq_next_cpu(aic_next_cpu, &all_cpus); + bus_write_4(sc->sc_mem, AIC_TARGET_CPU(irq), + 1 << sc->sc_cpuids[aic_next_cpu]); + break; + case AIC_TYPE_FIQ: + isrc->isrc_flags |= INTR_ISRCF_PPI; + break; + default: + return (EINVAL); + } + + return (0); +} + +static int +apple_aic_teardown_intr(device_t dev, struct intr_irqsrc *isrc, + struct resource *res, struct intr_map_data *data) +{ + panic("%s\n", __func__); +} + +static void +apple_aic_enable_intr(device_t dev, struct intr_irqsrc *isrc) +{ + struct apple_aic_irqsrc *ai; + struct apple_aic_softc *sc; + u_int irq; + + ai = (struct apple_aic_irqsrc *)isrc; + irq = ai->ai_irq; + switch(ai->ai_type) { + case AIC_TYPE_IRQ: + sc = device_get_softc(dev); + bus_write_4(sc->sc_mem, AIC_MASK_CLEAR(irq), AIC_IRQ_MASK(irq)); + break; + case AIC_TYPE_IPI: + /* Nothing needed here. */ + break; + case AIC_TYPE_FIQ: + /* TODO */ + break; + default: + panic("%s: %x\n", __func__, ai->ai_type); + } +} + +static void +apple_aic_disable_intr(device_t dev, struct intr_irqsrc *isrc) +{ + struct apple_aic_irqsrc *ai; + struct apple_aic_softc *sc; + u_int irq; + + ai = (struct apple_aic_irqsrc *)isrc; + irq = ai->ai_irq; + switch(ai->ai_type) { + case AIC_TYPE_IRQ: + sc = device_get_softc(dev); + bus_write_4(sc->sc_mem, AIC_MASK_SET(irq), AIC_IRQ_MASK(irq)); + break; + case AIC_TYPE_IPI: + /* Nothing needed here. */ + break; + case AIC_TYPE_FIQ: + /* TODO */ + break; + default: + panic("%s: %x\n", __func__, ai->ai_type); + } +} + +static void +apple_aic_post_filter(device_t dev, struct intr_irqsrc *isrc) +{ + struct apple_aic_softc *sc; + struct apple_aic_irqsrc *ai; + int irq; + + ai = (struct apple_aic_irqsrc *)isrc; + irq = ai->ai_irq; + switch(ai->ai_type) { + case AIC_TYPE_IRQ: + sc = device_get_softc(dev); + bus_write_4(sc->sc_mem, AIC_SW_CLEAR(irq), AIC_IRQ_MASK(irq)); + bus_write_4(sc->sc_mem, AIC_MASK_CLEAR(irq), AIC_IRQ_MASK(irq)); + break; + case AIC_TYPE_FIQ: + /* TODO */ + break; + default: + panic("%s: %x\n", __func__, ai->ai_type); + } +} + +static void +apple_aic_pre_ithread(device_t dev, struct intr_irqsrc *isrc) +{ + struct apple_aic_softc *sc; + struct apple_aic_irqsrc *ai; + int irq; + + ai = (struct apple_aic_irqsrc *)isrc; + sc = device_get_softc(dev); + irq = ai->ai_irq; + bus_write_4(sc->sc_mem, AIC_SW_CLEAR(irq), AIC_IRQ_MASK(irq)); + apple_aic_disable_intr(dev, isrc); + /* ACK IT */ +} + +static void +apple_aic_post_ithread(device_t dev, struct intr_irqsrc *isrc) +{ + struct apple_aic_softc *sc; + struct apple_aic_irqsrc *ai; + int irq; + + ai = (struct apple_aic_irqsrc *)isrc; + sc = device_get_softc(dev); + irq = ai->ai_irq; + + bus_write_4(sc->sc_mem, AIC_MASK_CLEAR(irq), AIC_IRQ_MASK(irq)); + apple_aic_enable_intr(dev, isrc); +} + +#ifdef SMP +static void +apple_aic_ipi_received(struct apple_aic_softc *sc, struct trapframe *tf) +{ + uint32_t mask; + uint32_t ipi; + int cpu; + + cpu = PCPU_GET(cpuid); + + mask = atomic_readandclear_32(&sc->sc_ipimasks[cpu]); + + while (mask != 0) { + ipi = ffs(mask) - 1; + mask &= ~(1 << ipi); + + intr_ipi_dispatch(ipi); + } +} +#endif + +static int +apple_aic_irq(void *arg) +{ + struct apple_aic_softc *sc; + uint32_t die, event, irq, type; + struct apple_aic_irqsrc *aisrc; + struct trapframe *tf; + + sc = arg; + tf = curthread->td_intr_frame; + + event = bus_read_4(sc->sc_mem, AIC_EVENT); + type = AIC_EVENT_TYPE(event); + + /* If we get an IPI here, we really goofed. */ + MPASS(type != AIC_EVENT_TYPE_IPI); + + if (type != AIC_EVENT_TYPE_IRQ) { + if (type != AIC_EVENT_TYPE_NONE) + device_printf(sc->sc_dev, "unexpected event type %d\n", + type); + return (FILTER_STRAY); + } + + die = AIC_EVENT_DIE(event); + irq = AIC_EVENT_IRQ(event); + + if (die >= sc->sc_ndie) + panic("%s: unexpected die %d", __func__, die); + if (irq >= sc->sc_nirqs) + panic("%s: unexpected irq %d", __func__, irq); + + aisrc = &sc->sc_isrcs[die][irq]; + if (intr_isrc_dispatch(&aisrc->ai_isrc, tf) != 0) { + device_printf(sc->sc_dev, "Stray irq %u:%u disabled\n", + die, irq); + return (FILTER_STRAY); + } + + return (FILTER_HANDLED); +} + +static int +apple_aic_fiq(void *arg) +{ + struct apple_aic_softc *sc; + struct apple_aic_irqsrc *isrcs; + struct trapframe *tf; + + sc = arg; + tf = curthread->td_intr_frame; + +#ifdef SMP + /* Handle IPIs. */ + if ((READ_SPECIALREG(AIC_IPI_SR_EL1) & AIC_IPI_SR_EL1_PENDING) != 0) { + WRITE_SPECIALREG(AIC_IPI_SR_EL1, AIC_IPI_SR_EL1_PENDING); + apple_aic_ipi_received(sc, tf); + } +#endif + + /* + * FIQs don't store any state in the interrupt controller at all outside + * of IPI handling, so we have to probe around outside of AIC to + * determine if we might have been fired off due to a timer. + */ + isrcs = sc->sc_isrcs[0]; + if ((READ_SPECIALREG(cntv_ctl_el0) & CNTV_CTL_BITS) == + (CNTV_CTL_ENABLE | CNTV_CTL_ISTATUS)) { + intr_isrc_dispatch(&isrcs[AIC_TMR_GUEST_VIRT].ai_isrc, tf); + } + + if (has_hyp()) { + uint64_t reg; + + if ((READ_SPECIALREG(cntp_ctl_el0) & CNTV_CTL_ISTATUS) != 0) { + intr_isrc_dispatch(&isrcs[AIC_TMR_GUEST_PHYS].ai_isrc, + tf); + } + + reg = READ_SPECIALREG(AIC_FIQ_VM_TIMER); + if ((reg & AIC_FIQ_VM_TIMER_PEN) != 0) { + intr_isrc_dispatch(&isrcs[AIC_TMR_HV_PHYS].ai_isrc, tf); + } + + if ((reg & AIC_FIQ_VM_TIMER_VEN) != 0) { + intr_isrc_dispatch(&isrcs[AIC_TMR_HV_VIRT].ai_isrc, tf); + } + } + + return (FILTER_HANDLED); +} + +#ifdef SMP +static int +apple_aic_bind_intr(device_t dev, struct intr_irqsrc *isrc) +{ + struct apple_aic_softc *sc = device_get_softc(dev); + static int aic_next_cpu; + uint32_t targets = 0; + u_int irq, cpu; + + MPASS(((struct apple_aic_irqsrc *)isrc)->ai_type == AIC_TYPE_IRQ); + irq = ((struct apple_aic_irqsrc *)isrc)->ai_irq; + if (CPU_EMPTY(&isrc->isrc_cpu)) { + aic_next_cpu = intr_irq_next_cpu(aic_next_cpu, &all_cpus); + CPU_SETOF(aic_next_cpu, &isrc->isrc_cpu); + bus_write_4(sc->sc_mem, AIC_TARGET_CPU(irq), + sc->sc_cpuids[aic_next_cpu] << 1); + } else { + CPU_FOREACH_ISSET(cpu, &isrc->isrc_cpu) { + targets |= sc->sc_cpuids[cpu] << 1; + } + bus_write_4(sc->sc_mem, AIC_TARGET_CPU(irq), targets); + } + return (0); +} + +static void +apple_aic_ipi_send(device_t dev, struct intr_irqsrc *isrc, cpuset_t cpus, + u_int ipi) +{ + struct apple_aic_softc *sc; + uint64_t aff, localgrp, sendmask; + u_int cpu; + + sc = device_get_softc(dev); + sendmask = 0; + localgrp = CPU_AFF1(CPU_AFFINITY(PCPU_GET(cpuid))); + + KASSERT(isrc == &sc->sc_ipi_srcs[ipi].ai_isrc, + ("%s: bad ISRC %p argument", __func__, isrc)); + for (cpu = 0; cpu <= mp_maxid; cpu++) { + if (CPU_ISSET(cpu, &cpus)) { + aff = CPU_AFFINITY(cpu); + sendmask = CPU_AFF0(aff); + atomic_set_32(&sc->sc_ipimasks[cpu], 1 << ipi); + + /* + * The above write to sc_ipimasks needs to be visible + * before we write to the ipi register to avoid the + * targetted CPU missing the dispatch in + * apple_aic_ipi_received(). Note that WRITE_SPECIALREG + * isn't a memory operation, so we can't relax this to a + * a dmb. + */ + dsb(ishst); + + if (CPU_AFF1(aff) == localgrp) { + WRITE_SPECIALREG(AIC_IPI_LOCAL_RR_EL1, + sendmask); + } else { + sendmask |= CPU_AFF1(aff) << 16; + WRITE_SPECIALREG(AIC_IPI_GLOBAL_RR_EL1, + sendmask); + } + + isb(); + } + } +} + +static int +apple_aic_ipi_setup(device_t dev, u_int ipi, struct intr_irqsrc **isrcp) +{ + struct apple_aic_softc *sc = device_get_softc(dev); + struct apple_aic_irqsrc *ai; + + KASSERT(ipi < AIC_NIPIS, ("%s: ipi %u too high", __func__, ipi)); + + ai = &sc->sc_ipi_srcs[ipi]; + ai->ai_type = AIC_TYPE_IPI; + + *isrcp = &ai->ai_isrc; + return (0); +} + +static void +apple_aic_init_secondary(device_t dev, uint32_t rootnum) +{ + struct apple_aic_softc *sc = device_get_softc(dev); + u_int cpu = PCPU_GET(cpuid); + + /* We don't need to re-initialize for the FIQ root. */ + if (rootnum != INTR_ROOT_IRQ) + return; + + sc->sc_cpuids[cpu] = bus_read_4(sc->sc_mem, AIC_WHOAMI); + if (bootverbose) + device_printf(dev, "CPU %d: whoami %x\n", cpu, + sc->sc_cpuids[cpu]); + + bus_write_4(sc->sc_mem, AIC_IPI_MASK_SET, AIC_IPI_SELF | AIC_IPI_OTHER); +} +#endif + +static device_method_t apple_aic_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, apple_aic_probe), + DEVMETHOD(device_attach, apple_aic_attach), + + /* Interrupt controller interface */ + DEVMETHOD(pic_disable_intr, apple_aic_disable_intr), + DEVMETHOD(pic_enable_intr, apple_aic_enable_intr), + DEVMETHOD(pic_map_intr, apple_aic_map_intr), + DEVMETHOD(pic_setup_intr, apple_aic_setup_intr), + DEVMETHOD(pic_teardown_intr, apple_aic_teardown_intr), + DEVMETHOD(pic_post_filter, apple_aic_post_filter), + DEVMETHOD(pic_post_ithread, apple_aic_post_ithread), + DEVMETHOD(pic_pre_ithread, apple_aic_pre_ithread), +#ifdef SMP + DEVMETHOD(pic_bind_intr, apple_aic_bind_intr), + DEVMETHOD(pic_init_secondary, apple_aic_init_secondary), + DEVMETHOD(pic_ipi_send, apple_aic_ipi_send), + DEVMETHOD(pic_ipi_setup, apple_aic_ipi_setup), +#endif + + /* End */ + DEVMETHOD_END +}; + +static DEFINE_CLASS_0(aic, apple_aic_driver, apple_aic_methods, + sizeof(struct apple_aic_softc)); + +EARLY_DRIVER_MODULE(aic, simplebus, apple_aic_driver, 0, 0, + BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE); diff --git a/sys/arm64/apple/apple_pinctrl.c b/sys/arm64/apple/apple_pinctrl.c new file mode 100644 index 000000000000..ec2dd5907024 --- /dev/null +++ b/sys/arm64/apple/apple_pinctrl.c @@ -0,0 +1,469 @@ +/* $OpenBSD: aplpinctrl.c,v 1.4 2022/04/06 18:59:26 naddy Exp $ */ +/* + * Copyright (c) 2021 Mark Kettenis <kettenis@openbsd.org> + * Copyright (c) 2022 Kyle Evans <kevans@FreeBSD.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/bus.h> +#include <sys/gpio.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/mutex.h> + +#include <machine/bus.h> +#include <machine/intr.h> +#include <machine/resource.h> + +#include <dev/gpio/gpiobusvar.h> +#include <dev/ofw/ofw_bus.h> +#include <dev/ofw/ofw_bus_subr.h> +#include <dev/fdt/fdt_pinctrl.h> + +#include "pic_if.h" +#include "gpio_if.h" + +#define APPLE_PIN(pinmux) ((pinmux) & 0xffff) +#define APPLE_FUNC(pinmux) ((pinmux) >> 16) + +#define GPIO_PIN(pin) ((pin) * 4) +#define GPIO_PIN_GROUP_MASK (7 << 16) +#define GPIO_PIN_INPUT_ENABLE (1 << 9) +#define GPIO_PIN_FUNC_MASK (3 << 5) +#define GPIO_PIN_FUNC_SHIFT 5 +#define GPIO_PIN_MODE_MASK (7 << 1) +#define GPIO_PIN_MODE_INPUT (0 << 1) +#define GPIO_PIN_MODE_OUTPUT (1 << 1) +#define GPIO_PIN_MODE_IRQ_HI (2 << 1) +#define GPIO_PIN_MODE_IRQ_LO (3 << 1) +#define GPIO_PIN_MODE_IRQ_UP (4 << 1) +#define GPIO_PIN_MODE_IRQ_DN (5 << 1) +#define GPIO_PIN_MODE_IRQ_ANY (6 << 1) +#define GPIO_PIN_MODE_IRQ_OFF (7 << 1) +#define GPIO_PIN_DATA (1 << 0) +#define GPIO_IRQ(grp, pin) (0x800 + (grp) * 64 + ((pin) >> 5) * 4) + +#define APPLE_PINCTRL_DEFAULT_CAPS \ + (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT) + +#define HREAD4(sc, reg) \ + bus_read_4((sc)->sc_res[APPLE_PINCTRL_MEMRES], reg) +#define HWRITE4(sc, reg, val) \ + bus_write_4((sc)->sc_res[APPLE_PINCTRL_MEMRES], reg, val) +#define HSET4(sc, reg, bits) \ + HWRITE4((sc), (reg), HREAD4((sc), (reg)) | (bits)) +#define HCLR4(sc, reg, bits) \ + HWRITE4((sc), (reg), HREAD4((sc), (reg)) & ~(bits)) + +struct apple_pinctrl_irqsrc { + struct intr_irqsrc isrc; + int irq; + int type; +}; + +enum { + APPLE_PINCTRL_MEMRES = 0, + APPLE_PINCTRL_IRQRES, + APPLE_PINCTRL_NRES, +}; + +struct apple_pinctrl_softc { + device_t sc_dev; + device_t sc_busdev; + struct mtx sc_mtx; + int sc_ngpios; + + void *sc_intrhand; + struct resource *sc_res[APPLE_PINCTRL_NRES]; + struct apple_pinctrl_irqsrc *sc_irqs; +}; + +#define APPLE_PINCTRL_LOCK(sc) mtx_lock_spin(&(sc)->sc_mtx) +#define APPLE_PINCTRL_UNLOCK(sc) mtx_unlock_spin(&(sc)->sc_mtx) +#define APPLE_PINCTRL_LOCK_ASSERT(sc) mtx_assert(&(sc)->sc_mtx, MA_OWNED) + +static struct ofw_compat_data compat_data[] = { + {"apple,pinctrl", 1}, + {NULL, 0}, +}; + +static struct resource_spec apple_pinctrl_res_spec[] = { + { SYS_RES_MEMORY, 0, RF_ACTIVE }, + { SYS_RES_IRQ, 0, RF_ACTIVE }, + { -1, 0, 0 }, +}; + +static int apple_pinctrl_probe(device_t dev); +static int apple_pinctrl_attach(device_t dev); +static int apple_pinctrl_detach(device_t dev); + +static int apple_pinctrl_configure(device_t, phandle_t); +static phandle_t apple_pinctrl_get_node(device_t, device_t); + +static int +apple_pinctrl_probe(device_t dev) +{ + + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) + return (ENXIO); + + device_set_desc(dev, "Apple Pinmux Controller"); + return (BUS_PROBE_DEFAULT); +} + +static int +apple_pinctrl_attach(device_t dev) +{ + pcell_t gpio_ranges[4]; + phandle_t node; + struct apple_pinctrl_softc *sc; + int error; + + sc = device_get_softc(dev); + sc->sc_dev = dev; + + node = ofw_bus_get_node(dev); + + if (bus_alloc_resources(dev, apple_pinctrl_res_spec, sc->sc_res) != 0) { + device_printf(dev, "cannot allocate device resources\n"); + return (ENXIO); + } + + mtx_init(&sc->sc_mtx, "aapl gpio", "gpio", MTX_SPIN); + + error = OF_getencprop(node, "gpio-ranges", gpio_ranges, + sizeof(gpio_ranges)); + if (error == -1) { + device_printf(dev, "failed to get gpio-ranges\n"); + goto error; + } + + sc->sc_ngpios = gpio_ranges[3]; + if (sc->sc_ngpios == 0) { + device_printf(dev, "no GPIOs\n"); + goto error; + } + + sc->sc_busdev = gpiobus_attach_bus(dev); + if (sc->sc_busdev == NULL) { + device_printf(dev, "failed to attach gpiobus\n"); + goto error; + } + + fdt_pinctrl_register(dev, "pinmux"); + fdt_pinctrl_configure_tree(dev); + + if (!OF_hasprop(node, "interrupt-controller")) + return (0); + + sc->sc_irqs = mallocarray(sc->sc_ngpios, + sizeof(*sc->sc_irqs), M_DEVBUF, M_ZERO | M_WAITOK); + intr_pic_register(dev, OF_xref_from_node(ofw_bus_get_node(dev))); + + return (0); +error: + mtx_destroy(&sc->sc_mtx); + bus_release_resources(dev, apple_pinctrl_res_spec, sc->sc_res); + return (ENXIO); +} + +static int +apple_pinctrl_detach(device_t dev) +{ + + return (EBUSY); +} + +static void +apple_pinctrl_pin_configure(struct apple_pinctrl_softc *sc, uint32_t pin, + uint32_t flags) +{ + uint32_t reg; + + APPLE_PINCTRL_LOCK_ASSERT(sc); + + MPASS(pin < sc->sc_ngpios); + + reg = HREAD4(sc, GPIO_PIN(pin)); + reg &= ~GPIO_PIN_FUNC_MASK; + reg &= ~GPIO_PIN_MODE_MASK; + + if ((flags & GPIO_PIN_PRESET_LOW) != 0) + reg &= ~GPIO_PIN_DATA; + else if ((flags & GPIO_PIN_PRESET_HIGH) != 0) + reg |= GPIO_PIN_DATA; + + if ((flags & GPIO_PIN_INPUT) != 0) + reg |= GPIO_PIN_MODE_INPUT; + else if ((flags & GPIO_PIN_OUTPUT) != 0) + reg |= GPIO_PIN_MODE_OUTPUT; + + HWRITE4(sc, GPIO_PIN(pin), reg); +} + +static device_t +apple_pinctrl_get_bus(device_t dev) +{ + struct apple_pinctrl_softc *sc; + + sc = device_get_softc(dev); + return (sc->sc_busdev); +} + +static int +apple_pinctrl_pin_max(device_t dev, int *maxpin) +{ + struct apple_pinctrl_softc *sc; + + sc = device_get_softc(dev); + *maxpin = sc->sc_ngpios - 1; + return (0); +} + +static int +apple_pinctrl_pin_getname(device_t dev, uint32_t pin, char *name) +{ + struct apple_pinctrl_softc *sc; + + sc = device_get_softc(dev); + if (pin >= sc->sc_ngpios) + return (EINVAL); + + snprintf(name, GPIOMAXNAME - 1, "gpio%c%d", + device_get_unit(dev) + 'a', pin); + + return (0); +} + +static int +apple_pinctrl_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags) +{ + struct apple_pinctrl_softc *sc; + uint32_t reg; + + sc = device_get_softc(dev); + if (pin >= sc->sc_ngpios) + return (EINVAL); + + *flags = 0; + + APPLE_PINCTRL_LOCK(sc); + + reg = HREAD4(sc, GPIO_PIN(pin)); + if ((reg & GPIO_PIN_MODE_INPUT) != 0) + *flags |= GPIO_PIN_INPUT; + else if ((reg & GPIO_PIN_MODE_OUTPUT) != 0) + *flags |= GPIO_PIN_OUTPUT; + + APPLE_PINCTRL_UNLOCK(sc); + + return (0); +} + +static int +apple_pinctrl_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) +{ + + *caps = APPLE_PINCTRL_DEFAULT_CAPS; + return (0); +} + +static int +apple_pinctrl_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) +{ + struct apple_pinctrl_softc *sc; + + sc = device_get_softc(dev); + if (pin >= sc->sc_ngpios) + return (EINVAL); + + APPLE_PINCTRL_LOCK(sc); + apple_pinctrl_pin_configure(sc, pin, flags); + APPLE_PINCTRL_UNLOCK(sc); + + return (0); +} + +static int +apple_pinctrl_pin_get(device_t dev, uint32_t pin, unsigned int *val) +{ + struct apple_pinctrl_softc *sc; + uint32_t reg; + + sc = device_get_softc(dev); + if (pin >= sc->sc_ngpios) + return (EINVAL); + + APPLE_PINCTRL_LOCK(sc); + reg = HREAD4(sc, GPIO_PIN(pin)); + *val = !!(reg & GPIO_PIN_DATA); + APPLE_PINCTRL_UNLOCK(sc); + + return (0); +} + +static int +apple_pinctrl_pin_set(device_t dev, uint32_t pin, unsigned int value) +{ + struct apple_pinctrl_softc *sc; + + sc = device_get_softc(dev); + if (pin >= sc->sc_ngpios) + return (EINVAL); + + APPLE_PINCTRL_LOCK(sc); + if (value) + HSET4(sc, GPIO_PIN(pin), GPIO_PIN_DATA); + else + HCLR4(sc, GPIO_PIN(pin), GPIO_PIN_DATA); + device_printf(sc->sc_dev, "set pin %d to %x\n", + pin, HREAD4(sc, GPIO_PIN(pin))); + APPLE_PINCTRL_UNLOCK(sc); + return (0); +} + + +static int +apple_pinctrl_pin_toggle(device_t dev, uint32_t pin) +{ + struct apple_pinctrl_softc *sc; + uint32_t reg; + + sc = device_get_softc(dev); + if (pin >= sc->sc_ngpios) + return (EINVAL); + + APPLE_PINCTRL_LOCK(sc); + reg = HREAD4(sc, GPIO_PIN(pin)); + if ((reg & GPIO_PIN_DATA) == 0) + reg |= GPIO_PIN_DATA; + else + reg &= ~GPIO_PIN_DATA; + HWRITE4(sc, GPIO_PIN(pin), reg); + APPLE_PINCTRL_UNLOCK(sc); + return (0); +} + + +static int +apple_pinctrl_pin_config_32(device_t dev, uint32_t first_pin, uint32_t num_pins, + uint32_t *pin_flags) +{ + struct apple_pinctrl_softc *sc; + uint32_t pin; + + sc = device_get_softc(dev); + if (first_pin >= sc->sc_ngpios) + return (EINVAL); + + /* + * The configuration for a bank of pins is scattered among several + * registers; we cannot g'tee to simultaneously change the state of all + * the pins in the flags array. So just loop through the array + * configuring each pin for now. If there was a strong need, it might + * be possible to support some limited simultaneous config, such as + * adjacent groups of 8 pins that line up the same as the config regs. + */ + APPLE_PINCTRL_LOCK(sc); + for (pin = first_pin; pin < num_pins; ++pin) { + if (pin_flags[pin] & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) + apple_pinctrl_pin_configure(sc, pin, pin_flags[pin]); + } + APPLE_PINCTRL_UNLOCK(sc); + + return (0); +} + +static phandle_t +apple_pinctrl_get_node(device_t dev, device_t bus) +{ + + /* GPIO bus */ + return (ofw_bus_get_node(dev)); +} + +static int +apple_pinctrl_configure(device_t dev, phandle_t cfgxref) +{ + struct apple_pinctrl_softc *sc; + pcell_t *pinmux; + phandle_t node; + ssize_t len; + uint32_t reg; + uint16_t pin, func; + int i; + + sc = device_get_softc(dev); + node = OF_node_from_xref(cfgxref); + + len = OF_getencprop_alloc(node, "pinmux", (void **)&pinmux); + if (len <= 0) + return (-1); + + APPLE_PINCTRL_LOCK(sc); + for (i = 0; i < len / sizeof(pcell_t); i++) { + pin = APPLE_PIN(pinmux[i]); + func = APPLE_FUNC(pinmux[i]); + reg = HREAD4(sc, GPIO_PIN(pin)); + reg &= ~GPIO_PIN_FUNC_MASK; + reg |= (func << GPIO_PIN_FUNC_SHIFT) & GPIO_PIN_FUNC_MASK; + HWRITE4(sc, GPIO_PIN(pin), reg); + } + APPLE_PINCTRL_UNLOCK(sc); + + OF_prop_free(pinmux); + return 0; +} + +static device_method_t apple_pinctrl_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, apple_pinctrl_probe), + DEVMETHOD(device_attach, apple_pinctrl_attach), + DEVMETHOD(device_detach, apple_pinctrl_detach), + + /* GPIO protocol */ + DEVMETHOD(gpio_get_bus, apple_pinctrl_get_bus), + DEVMETHOD(gpio_pin_max, apple_pinctrl_pin_max), + DEVMETHOD(gpio_pin_getname, apple_pinctrl_pin_getname), + DEVMETHOD(gpio_pin_getflags, apple_pinctrl_pin_getflags), + DEVMETHOD(gpio_pin_getcaps, apple_pinctrl_pin_getcaps), + DEVMETHOD(gpio_pin_setflags, apple_pinctrl_pin_setflags), + DEVMETHOD(gpio_pin_get, apple_pinctrl_pin_get), + DEVMETHOD(gpio_pin_set, apple_pinctrl_pin_set), + DEVMETHOD(gpio_pin_toggle, apple_pinctrl_pin_toggle), + DEVMETHOD(gpio_pin_config_32, apple_pinctrl_pin_config_32), + + /* ofw_bus interface */ + DEVMETHOD(ofw_bus_get_node, apple_pinctrl_get_node), + + /* fdt_pinctrl interface */ + DEVMETHOD(fdt_pinctrl_configure, apple_pinctrl_configure), + + DEVMETHOD_END +}; + +static driver_t apple_pinctrl_driver = { + "gpio", + apple_pinctrl_methods, + sizeof(struct apple_pinctrl_softc), +}; + +EARLY_DRIVER_MODULE(apple_pinctrl, simplebus, apple_pinctrl_driver, + 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LATE); diff --git a/sys/arm64/apple/apple_wdog.c b/sys/arm64/apple/apple_wdog.c new file mode 100644 index 000000000000..aaa899298571 --- /dev/null +++ b/sys/arm64/apple/apple_wdog.c @@ -0,0 +1,234 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2022 Michael J. Karels <karels@freebsd.org> + * Copyright (c) 2012 Alexander Rybalko <ray@freebsd.org> + * 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 <sys/param.h> +#include <sys/bus.h> +#include <sys/eventhandler.h> +#include <sys/kernel.h> +#include <sys/limits.h> +#include <sys/lock.h> +#include <sys/module.h> +#include <sys/mutex.h> +#include <sys/reboot.h> +#include <sys/rman.h> +#include <sys/systm.h> +#include <sys/watchdog.h> + +#include <dev/ofw/openfirm.h> +#include <dev/ofw/ofw_bus.h> +#include <dev/ofw/ofw_bus_subr.h> + +#include <dev/clk/clk.h> + +#include <machine/bus.h> +#include <machine/machdep.h> + +#define APPLE_WDOG_WD0_TIMER 0x0000 +#define APPLE_WDOG_WD0_RESET 0x0004 +#define APPLE_WDOG_WD0_INTR 0x0008 +#define APPLE_WDOG_WD0_CNTL 0x000c + +#define APPLE_WDOG_WD1_TIMER 0x0010 +#define APPLE_WDOG_WD1_RESET 0x0014 +#define APPLE_WDOG_WD1_CNTL 0x001c + +#define APPLE_WDOG_WD2_TIMER 0x0020 +#define APPLE_WDOG_WD2_RESET 0x0024 +#define APPLE_WDOG_WD2_CNTL 0x002c + +#define APPLE_WDOG_CNTL_INTENABLE 0x0001 +#define APPLE_WDOG_CNTL_INTSTAT 0x0002 +#define APPLE_WDOG_CNTL_RSTENABLE 0x0004 + +#define READ(_sc, _r) bus_space_read_4((_sc)->bst, (_sc)->bsh, (_r)) +#define WRITE(_sc, _r, _v) bus_space_write_4((_sc)->bst, (_sc)->bsh, (_r), (_v)) + +struct apple_wdog_softc { + device_t dev; + struct resource * res; + bus_space_tag_t bst; + bus_space_handle_t bsh; + clk_t clk; + uint64_t clk_freq; + struct mtx mtx; +}; + +static struct ofw_compat_data compat_data[] = { + {"apple,wdt", 1}, + {NULL, 0} +}; + +static void apple_wdog_watchdog_fn(void *private, u_int cmd, int *error); +static void apple_wdog_reboot_system(void *, int); + +static int +apple_wdog_probe(device_t dev) +{ + + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) + return (ENXIO); + + device_set_desc(dev, "Apple Watchdog"); + + return (BUS_PROBE_DEFAULT); +} + +static int +apple_wdog_attach(device_t dev) +{ + struct apple_wdog_softc *sc; + int error, rid; + + sc = device_get_softc(dev); + sc->dev = dev; + + rid = 0; + sc->res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); + if (sc->res == NULL) { + device_printf(dev, "could not allocate memory resource\n"); + return (ENXIO); + } + + sc->bst = rman_get_bustag(sc->res); + sc->bsh = rman_get_bushandle(sc->res); + + error = clk_get_by_ofw_index(dev, 0, 0, &sc->clk); + if (error != 0) { + device_printf(dev, "cannot get clock\n"); + goto fail; + } + error = clk_enable(sc->clk); + if (error != 0) { + device_printf(dev, "cannot enable clock\n"); + goto fail; + } + error = clk_get_freq(sc->clk, &sc->clk_freq); + if (error != 0) { + device_printf(dev, "cannot get base frequency\n"); + goto fail_clk; + } + + mtx_init(&sc->mtx, "Apple Watchdog", "apple_wdog", MTX_DEF); + EVENTHANDLER_REGISTER(watchdog_list, apple_wdog_watchdog_fn, sc, 0); + EVENTHANDLER_REGISTER(shutdown_final, apple_wdog_reboot_system, sc, + SHUTDOWN_PRI_LAST); + + /* Reset the watchdog timers. */ + WRITE(sc, APPLE_WDOG_WD0_CNTL, 0); + WRITE(sc, APPLE_WDOG_WD1_CNTL, 0); + + return (0); + +fail_clk: + clk_disable(sc->clk); +fail: + bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->res); + return (error); +} + +static void +apple_wdog_watchdog_fn(void *private, u_int cmd, int *error) +{ + struct apple_wdog_softc *sc; + uint64_t sec; + uint32_t ticks, sec_max; + + sc = private; + mtx_lock(&sc->mtx); + + cmd &= WD_INTERVAL; + + if (cmd > 0) { + sec = ((uint64_t)1 << (cmd & WD_INTERVAL)) / 1000000000; + sec_max = UINT_MAX / sc->clk_freq; + if (sec == 0 || sec > sec_max) { + /* + * Can't arm + * disable watchdog as watchdog(9) requires + */ + device_printf(sc->dev, + "Can't arm, timeout must be between 1-%d seconds\n", + sec_max); + WRITE(sc, APPLE_WDOG_WD1_CNTL, 0); + mtx_unlock(&sc->mtx); + *error = EINVAL; + return; + } + + ticks = sec * sc->clk_freq; + WRITE(sc, APPLE_WDOG_WD1_TIMER, 0); + WRITE(sc, APPLE_WDOG_WD1_RESET, ticks); + WRITE(sc, APPLE_WDOG_WD1_CNTL, APPLE_WDOG_CNTL_RSTENABLE); + + *error = 0; + } else + WRITE(sc, APPLE_WDOG_WD1_CNTL, 0); + + mtx_unlock(&sc->mtx); +} + +static void +apple_wdog_reboot_system(void *private, int howto) +{ + struct apple_wdog_softc *sc = private; + + /* Only handle reset. */ + if ((howto & (RB_HALT | RB_POWEROFF)) != 0) + return; + + printf("Resetting system ... "); + + WRITE(sc, APPLE_WDOG_WD1_CNTL, APPLE_WDOG_CNTL_RSTENABLE); + WRITE(sc, APPLE_WDOG_WD1_RESET, 1); + WRITE(sc, APPLE_WDOG_WD1_TIMER, 0); + + /* Wait for watchdog timeout; should take milliseconds. */ + DELAY(2000000); + + /* Not reached ... one hopes. */ + printf("failed to reset.\n"); +} + +static device_method_t apple_wdog_methods[] = { + DEVMETHOD(device_probe, apple_wdog_probe), + DEVMETHOD(device_attach, apple_wdog_attach), + + DEVMETHOD_END +}; + +static driver_t apple_wdog_driver = { + "apple_wdog", + apple_wdog_methods, + sizeof(struct apple_wdog_softc), +}; + +DRIVER_MODULE(apple_wdog, simplebus, apple_wdog_driver, 0, 0); diff --git a/sys/arm64/apple/exynos_uart.c b/sys/arm64/apple/exynos_uart.c new file mode 100644 index 000000000000..2767c338b918 --- /dev/null +++ b/sys/arm64/apple/exynos_uart.c @@ -0,0 +1,568 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2003 Marcel Moolenaar + * Copyright (c) 2007-2009 Andrew Turner + * Copyright (c) 2013 Ruslan Bukin <br@bsdpad.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/bus.h> +#include <sys/conf.h> +#include <sys/cons.h> +#include <sys/rman.h> +#include <machine/bus.h> +#include <machine/intr.h> + +#include <dev/uart/uart.h> +#include <dev/uart/uart_cpu.h> +#include <dev/uart/uart_cpu_fdt.h> +#include <dev/uart/uart_bus.h> + +#include <arm64/apple/exynos_uart.h> + +#include "uart_if.h" + +struct exynos_uart_cfg; + +#define DEF_CLK 100000000 + +static int sscomspeed(long, long); +static int exynos4210_uart_param(struct uart_bas *, int, int, int, int); + +/* + * Low-level UART interface. + */ +static int exynos4210_probe(struct uart_bas *bas); +static void exynos4210_init_common(struct exynos_uart_cfg *cfg, + struct uart_bas *bas, int, int, int, int); +static void exynos4210_init(struct uart_bas *bas, int, int, int, int); +static void exynos4210_s5l_init(struct uart_bas *bas, int, int, int, int); +static void exynos4210_term(struct uart_bas *bas); +static void exynos4210_putc(struct uart_bas *bas, int); +static int exynos4210_rxready(struct uart_bas *bas); +static int exynos4210_getc(struct uart_bas *bas, struct mtx *mtx); + +extern SLIST_HEAD(uart_devinfo_list, uart_devinfo) uart_sysdevs; + +static struct uart_ops uart_exynos4210_ops; +static struct uart_ops uart_s5l_ops; +static kobj_method_t exynos4210_methods[]; +static kobj_method_t s5l_methods[]; +static struct ofw_compat_data compat_data[]; + +enum exynos_uart_type { + EXUART_4210, + EXUART_S5L, +}; + +struct exynos_uart_cfg { + enum exynos_uart_type cfg_type; + uint64_t cfg_uart_full_mask; +}; + +struct exynos_uart_class { + struct uart_class base; + struct exynos_uart_cfg cfg; +}; + +static struct exynos_uart_class uart_ex4210_class = { + .base = { + "exynos4210 class", + exynos4210_methods, + 1, + .uc_ops = &uart_exynos4210_ops, + .uc_range = 8, + .uc_rclk = 0, + .uc_rshift = 0 + }, + .cfg = { + .cfg_type = EXUART_4210, + .cfg_uart_full_mask = UFSTAT_TXFULL, + }, +}; + + +static struct exynos_uart_class uart_s5l_class = { + .base = { + "s5l class", + s5l_methods, + 1, + .uc_ops = &uart_s5l_ops, + .uc_range = 8, + .uc_rclk = 0, + .uc_rshift = 0 + }, + .cfg = { + .cfg_type = EXUART_S5L, + .cfg_uart_full_mask = UFSTAT_S5L_TXFULL, + }, +}; + +static int +sscomspeed(long speed, long frequency) +{ + int x; + + if (speed <= 0 || frequency <= 0) + return (-1); + x = (frequency / 16) / speed; + return (x-1); +} + +static int +exynos4210_uart_param(struct uart_bas *bas, int baudrate, int databits, + int stopbits, int parity) +{ + int brd, ulcon; + + ulcon = 0; + + switch(databits) { + case 5: + ulcon |= ULCON_LENGTH_5; + break; + case 6: + ulcon |= ULCON_LENGTH_6; + break; + case 7: + ulcon |= ULCON_LENGTH_7; + break; + case 8: + ulcon |= ULCON_LENGTH_8; + break; + default: + return (EINVAL); + } + + switch (parity) { + case UART_PARITY_NONE: + ulcon |= ULCON_PARITY_NONE; + break; + case UART_PARITY_ODD: + ulcon |= ULCON_PARITY_ODD; + break; + case UART_PARITY_EVEN: + ulcon |= ULCON_PARITY_EVEN; + break; + case UART_PARITY_MARK: + case UART_PARITY_SPACE: + default: + return (EINVAL); + } + + if (stopbits == 2) + ulcon |= ULCON_STOP; + + uart_setreg(bas, SSCOM_ULCON, ulcon); + + /* baudrate may be negative, in which case we just leave it alone. */ + if (baudrate > 0) { + brd = sscomspeed(baudrate, bas->rclk); + uart_setreg(bas, SSCOM_UBRDIV, brd); + } + + return (0); +} + +static struct uart_ops uart_exynos4210_ops = { + .probe = exynos4210_probe, + .init = exynos4210_init, + .term = exynos4210_term, + .putc = exynos4210_putc, + .rxready = exynos4210_rxready, + .getc = exynos4210_getc, +}; + +static struct uart_ops uart_s5l_ops = { + .probe = exynos4210_probe, + .init = exynos4210_s5l_init, + .term = exynos4210_term, + .putc = exynos4210_putc, + .rxready = exynos4210_rxready, + .getc = exynos4210_getc, +}; + +static int +exynos4210_probe(struct uart_bas *bas) +{ + + return (0); +} + +static void +exynos4210_init_common(struct exynos_uart_cfg *cfg, struct uart_bas *bas, + int baudrate, int databits, int stopbits, int parity) +{ + + if (bas->rclk == 0) + bas->rclk = DEF_CLK; + + KASSERT(bas->rclk != 0, ("exynos4210_init: Invalid rclk")); + + bas->driver1 = cfg; + + /* Clear interrupts */ + if (cfg->cfg_type == EXUART_S5L) { + uart_setreg(bas, SSCOM_UTRSTAT, 0); + } else { + uart_setreg(bas, SSCOM_UCON, 0); + uart_setreg(bas, SSCOM_UFCON, + UFCON_TXTRIGGER_8 | UFCON_RXTRIGGER_8 | + UFCON_TXFIFO_RESET | UFCON_RXFIFO_RESET | + UFCON_FIFO_ENABLE); + } + + exynos4210_uart_param(bas, baudrate, databits, stopbits, parity); + + /* Enable UART. */ + if (cfg->cfg_type == EXUART_S5L) { + uart_setreg(bas, SSCOM_UCON, uart_getreg(bas, SSCOM_UCON) | + UCON_TOINT | UCON_S5L_RXTHRESH | UCON_S5L_RX_TIMEOUT | + UCON_S5L_TXTHRESH); + } else { + uart_setreg(bas, SSCOM_UCON, uart_getreg(bas, SSCOM_UCON) | + UCON_TXMODE_INT | UCON_RXMODE_INT | UCON_TOINT); + uart_setreg(bas, SSCOM_UMCON, UMCON_RTS); + } +} + +static void +exynos4210_init(struct uart_bas *bas, int baudrate, int databits, int stopbits, + int parity) +{ + + return (exynos4210_init_common(&uart_ex4210_class.cfg, bas, baudrate, + databits, stopbits, parity)); +} + +static void +exynos4210_s5l_init(struct uart_bas *bas, int baudrate, int databits, int stopbits, + int parity) +{ + + return (exynos4210_init_common(&uart_s5l_class.cfg, bas, baudrate, + databits, stopbits, parity)); +} + +static void +exynos4210_term(struct uart_bas *bas) +{ + /* XXX */ +} + +static void +exynos4210_putc(struct uart_bas *bas, int c) +{ + struct exynos_uart_cfg *cfg; + + cfg = bas->driver1; + + while ((bus_space_read_4(bas->bst, bas->bsh, SSCOM_UFSTAT) & + cfg->cfg_uart_full_mask) != 0) + continue; + + uart_setreg(bas, SSCOM_UTXH, c); + uart_barrier(bas); +} + +static int +exynos4210_rxready_impl(struct uart_bas *bas, bool intr) +{ + struct exynos_uart_cfg *cfg; + int ufstat, utrstat; + + cfg = bas->driver1; + if (!intr || cfg->cfg_type != EXUART_S5L) { + utrstat = bus_space_read_4(bas->bst, bas->bsh, SSCOM_UTRSTAT); + + if ((utrstat & UTRSTAT_RXREADY) != 0) + return (1); + if (cfg->cfg_type != EXUART_S5L) + return (0); + } + + ufstat = bus_space_read_4(bas->bst, bas->bsh, SSCOM_UFSTAT); + + return ((ufstat & (UFSTAT_RXCOUNT | UFSTAT_RXFULL)) != 0); +} + +static int +exynos4210_rxready(struct uart_bas *bas) +{ + + return (exynos4210_rxready_impl(bas, false)); +} + +static int +exynos4210_getc(struct uart_bas *bas, struct mtx *mtx) +{ + + while (!exynos4210_rxready(bas)) { + continue; + } + + return (uart_getreg(bas, SSCOM_URXH)); +} + +static int exynos4210_bus_probe(struct uart_softc *sc); +static int exynos4210_bus_attach(struct uart_softc *sc); +static int exynos4210_bus_flush(struct uart_softc *, int); +static int exynos4210_bus_getsig(struct uart_softc *); +static int exynos4210_bus_ioctl(struct uart_softc *, int, intptr_t); +static int exynos4210_bus_ipend(struct uart_softc *); +static int s5l_bus_ipend(struct uart_softc *); +static int exynos4210_bus_param(struct uart_softc *, int, int, int, int); +static int exynos4210_bus_receive(struct uart_softc *); +static int exynos4210_bus_setsig(struct uart_softc *, int); +static int exynos4210_bus_transmit(struct uart_softc *); + +static kobj_method_t exynos4210_methods[] = { + KOBJMETHOD(uart_probe, exynos4210_bus_probe), + KOBJMETHOD(uart_attach, exynos4210_bus_attach), + KOBJMETHOD(uart_flush, exynos4210_bus_flush), + KOBJMETHOD(uart_getsig, exynos4210_bus_getsig), + KOBJMETHOD(uart_ioctl, exynos4210_bus_ioctl), + KOBJMETHOD(uart_ipend, exynos4210_bus_ipend), + KOBJMETHOD(uart_param, exynos4210_bus_param), + KOBJMETHOD(uart_receive, exynos4210_bus_receive), + KOBJMETHOD(uart_setsig, exynos4210_bus_setsig), + KOBJMETHOD(uart_transmit, exynos4210_bus_transmit), + {0, 0 } +}; + +static kobj_method_t s5l_methods[] = { + KOBJMETHOD(uart_probe, exynos4210_bus_probe), + KOBJMETHOD(uart_attach, exynos4210_bus_attach), + KOBJMETHOD(uart_flush, exynos4210_bus_flush), + KOBJMETHOD(uart_getsig, exynos4210_bus_getsig), + KOBJMETHOD(uart_ioctl, exynos4210_bus_ioctl), + KOBJMETHOD(uart_ipend, s5l_bus_ipend), + KOBJMETHOD(uart_param, exynos4210_bus_param), + KOBJMETHOD(uart_receive, exynos4210_bus_receive), + KOBJMETHOD(uart_setsig, exynos4210_bus_setsig), + KOBJMETHOD(uart_transmit, exynos4210_bus_transmit), + {0, 0 } +}; + +int +exynos4210_bus_probe(struct uart_softc *sc) +{ + + sc->sc_txfifosz = 16; + sc->sc_rxfifosz = 16; + + return (0); +} + +static int +exynos4210_bus_attach(struct uart_softc *sc) +{ + struct exynos_uart_class *class; + struct exynos_uart_cfg *cfg; + + sc->sc_hwiflow = 0; + sc->sc_hwoflow = 0; + + class = (struct exynos_uart_class *)ofw_bus_search_compatible(sc->sc_dev, + compat_data)->ocd_data; + MPASS(class != NULL); + + cfg = &class->cfg; + MPASS(sc->sc_sysdev == NULL || cfg == sc->sc_sysdev->bas.driver1); + sc->sc_bas.driver1 = cfg; + + return (0); +} + +static int +exynos4210_bus_transmit(struct uart_softc *sc) +{ + struct exynos_uart_cfg *cfg; + int i; + int reg; + + cfg = sc->sc_bas.driver1; + uart_lock(sc->sc_hwmtx); + + /* tx fifo has room, fire away. */ + for (i = 0; i < sc->sc_txdatasz; i++) { + uart_setreg(&sc->sc_bas, SSCOM_UTXH, sc->sc_txbuf[i]); + uart_barrier(&sc->sc_bas); + } + + if (cfg->cfg_type == EXUART_S5L) { + sc->sc_txbusy = 1; + } else { + /* unmask TX interrupt */ + reg = bus_space_read_4(sc->sc_bas.bst, sc->sc_bas.bsh, + SSCOM_UINTM); + reg &= ~(1 << 2); + bus_space_write_4(sc->sc_bas.bst, sc->sc_bas.bsh, SSCOM_UINTM, + reg); + } + + uart_unlock(sc->sc_hwmtx); + + return (0); +} + +static int +exynos4210_bus_setsig(struct uart_softc *sc, int sig) +{ + + return (0); +} + +static int +exynos4210_bus_receive(struct uart_softc *sc) +{ + struct uart_bas *bas; + + bas = &sc->sc_bas; + uart_lock(sc->sc_hwmtx); + + while (exynos4210_rxready_impl(bas, true)) { + if (uart_rx_full(sc)) { + sc->sc_rxbuf[sc->sc_rxput] = UART_STAT_OVERRUN; + break; + } + + uart_rx_put(sc, uart_getreg(&sc->sc_bas, SSCOM_URXH)); + } + + uart_unlock(sc->sc_hwmtx); + + return (0); +} + +static int +exynos4210_bus_param(struct uart_softc *sc, int baudrate, int databits, + int stopbits, int parity) +{ + int error; + + if (sc->sc_bas.rclk == 0) + sc->sc_bas.rclk = DEF_CLK; + + KASSERT(sc->sc_bas.rclk != 0, ("exynos4210_init: Invalid rclk")); + + uart_lock(sc->sc_hwmtx); + error = exynos4210_uart_param(&sc->sc_bas, baudrate, databits, stopbits, + parity); + uart_unlock(sc->sc_hwmtx); + + return (error); +} + +static int +s5l_bus_ipend(struct uart_softc *sc) +{ + int ipend; + uint32_t uerstat, utrstat; + + ipend = 0; + uart_lock(sc->sc_hwmtx); + utrstat = bus_space_read_4(sc->sc_bas.bst, sc->sc_bas.bsh, + SSCOM_UTRSTAT); + + if (utrstat & (UTRSTAT_S5L_RXTHRESH | UTRSTAT_S5L_RX_TIMEOUT)) + ipend |= SER_INT_RXREADY; + + if (utrstat & UTRSTAT_S5L_TXTHRESH) + ipend |= SER_INT_TXIDLE; + + uerstat = bus_space_read_4(sc->sc_bas.bst, sc->sc_bas.bsh, + SSCOM_UERSTAT); + if ((uerstat & UERSTAT_BREAK) != 0) + ipend |= SER_INT_BREAK; + + bus_space_write_4(sc->sc_bas.bst, sc->sc_bas.bsh, SSCOM_UTRSTAT, + utrstat); + uart_unlock(sc->sc_hwmtx); + + return (ipend); +} + +static int +exynos4210_bus_ipend(struct uart_softc *sc) +{ + uint32_t ints; + int reg; + int ipend; + + uart_lock(sc->sc_hwmtx); + ints = bus_space_read_4(sc->sc_bas.bst, sc->sc_bas.bsh, SSCOM_UINTP); + bus_space_write_4(sc->sc_bas.bst, sc->sc_bas.bsh, SSCOM_UINTP, ints); + + ipend = 0; + if ((ints & UINTP_TXEMPTY) != 0) { + if (sc->sc_txbusy != 0) + ipend |= SER_INT_TXIDLE; + + /* mask TX interrupt */ + reg = bus_space_read_4(sc->sc_bas.bst, sc->sc_bas.bsh, + SSCOM_UINTM); + reg |= UINTM_TXINTR; + bus_space_write_4(sc->sc_bas.bst, sc->sc_bas.bsh, + SSCOM_UINTM, reg); + } + + if ((ints & UINTP_RXREADY) != 0) { + ipend |= SER_INT_RXREADY; + } + + uart_unlock(sc->sc_hwmtx); + return (ipend); +} + +static int +exynos4210_bus_flush(struct uart_softc *sc, int what) +{ + + return (0); +} + +static int +exynos4210_bus_getsig(struct uart_softc *sc) +{ + + return (0); +} + +static int +exynos4210_bus_ioctl(struct uart_softc *sc, int request, intptr_t data) +{ + + return (EINVAL); +} + +static struct ofw_compat_data compat_data[] = { + {"apple,s5l-uart", (uintptr_t)&uart_s5l_class.base}, + {"samsung,exynos4210-uart", (uintptr_t)&uart_ex4210_class.base}, + {NULL, (uintptr_t)NULL}, +}; +UART_FDT_CLASS_AND_DEVICE(compat_data); diff --git a/sys/arm64/apple/exynos_uart.h b/sys/arm64/apple/exynos_uart.h new file mode 100644 index 000000000000..6c817252a69a --- /dev/null +++ b/sys/arm64/apple/exynos_uart.h @@ -0,0 +1,136 @@ +/* $NetBSD: s3c2xx0reg.h,v 1.4 2004/02/12 03:47:29 bsh Exp $ */ + +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2002, 2003 Fujitsu Component Limited + * Copyright (c) 2002, 2003 Genetec Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of The Fujitsu Component Limited nor the name of + * Genetec corporation may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY FUJITSU COMPONENT LIMITED AND GENETEC + * CORPORATION ``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 FUJITSU COMPONENT LIMITED OR GENETEC + * CORPORATION 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. + */ + +/* s3c2410-specific registers */ +#define UMCON_AFC (1 << 4) /* auto flow control */ +#define UMSTAT_DCTS (1 << 2) /* CTS change */ +#define ULCON_IR (1 << 6) +#define ULCON_PARITY_SHIFT 3 + +/* + * Exynos-specific + * + * UFSTAT_TXFULL register differs between Exynos and others. + * Others have UFSTAT_TXFULL (1 << 9) + */ +#define UFSTAT_TXFULL (1 << 24) +#define UFSTAT_S5L_TXFULL (1 << 9) + +#define SSCOM_UINTM 0x038 +#define UINTM_TXINTR (1 << 2) +#define SSCOM_UINTP 0x030 +#define UINTP_RXREADY (1 << 0) +#define UINTP_TXEMPTY (1 << 2) + +/* common for s3c2800 and s3c24x0 */ +#define SSCOM_ULCON 0x00 /* UART line control */ +#define ULCON_PARITY_NONE (0 << ULCON_PARITY_SHIFT) +#define ULCON_PARITY_ODD (4 << ULCON_PARITY_SHIFT) +#define ULCON_PARITY_EVEN (5 << ULCON_PARITY_SHIFT) +#define ULCON_PARITY_ONE (6 << ULCON_PARITY_SHIFT) +#define ULCON_PARITY_ZERO (7 << ULCON_PARITY_SHIFT) +#define ULCON_STOP (1 << 2) +#define ULCON_LENGTH_5 0 +#define ULCON_LENGTH_6 1 +#define ULCON_LENGTH_7 2 +#define ULCON_LENGTH_8 3 +#define SSCOM_UCON 0x04 /* UART control */ +#define UCON_TXINT_TYPE (1 << 9) /* Tx interrupt. 0=pulse,1=level */ +#define UCON_TXINT_TYPE_LEVEL UCON_TXINT_TYPE +#define UCON_TXINT_TYPE_PULSE 0 +#define UCON_RXINT_TYPE (1 << 8) /* Rx interrupt */ +#define UCON_RXINT_TYPE_LEVEL UCON_RXINT_TYPE +#define UCON_RXINT_TYPE_PULSE 0 +#define UCON_TOINT (1 << 7) /* Rx timeout interrupt */ +#define UCON_ERRINT (1 << 6) /* receive error interrupt */ +#define UCON_LOOP (1 << 5) /* loopback */ +#define UCON_SBREAK (1 << 4) /* send break */ +#define UCON_TXMODE_DISABLE (0 << 2) +#define UCON_TXMODE_INT (1 << 2) +#define UCON_TXMODE_DMA (2 << 2) +#define UCON_TXMODE_MASK (3 << 2) +#define UCON_RXMODE_DISABLE (0 << 0) +#define UCON_RXMODE_INT (1 << 0) +#define UCON_RXMODE_DMA (2 << 0) +#define UCON_RXMODE_MASK (3 << 0) +#define UCON_S5L_RX_TIMEOUT (0x1 << 9) +#define UCON_S5L_RXTHRESH (0x1 << 12) +#define UCON_S5L_TXTHRESH (0x1 << 13) +#define SSCOM_UFCON 0x08 /* FIFO control */ +#define UFCON_TXTRIGGER_0 (0 << 6) +#define UFCON_TXTRIGGER_4 (1 << 6) +#define UFCON_TXTRIGGER_8 (2 << 6) +#define UFCON_TXTRIGGER_16 (3 << 6) +#define UFCON_RXTRIGGER_4 (0 << 4) +#define UFCON_RXTRIGGER_8 (1 << 4) +#define UFCON_RXTRIGGER_12 (2 << 4) +#define UFCON_RXTRIGGER_16 (3 << 4) +#define UFCON_TXFIFO_RESET (1 << 2) +#define UFCON_RXFIFO_RESET (1 << 1) +#define UFCON_FIFO_ENABLE (1 << 0) +#define SSCOM_UMCON 0x0c /* MODEM control */ +#define UMCON_RTS (1 << 0) /* Request to send */ +#define SSCOM_UTRSTAT 0x10 /* Status register */ +#define UTRSTAT_TXSHIFTER_EMPTY ( 1<< 2) +#define UTRSTAT_TXEMPTY (1 << 1) /* TX fifo or buffer empty */ +#define UTRSTAT_RXREADY (1 << 0) /* RX fifo or buffer is not empty */ +#define UTRSTAT_S5L_RXTHRESH (0x1 << 4) +#define UTRSTAT_S5L_TXTHRESH (0x1 << 5) +#define UTRSTAT_S5L_RX_TIMEOUT (0x1 << 9) +#define SSCOM_UERSTAT 0x14 /* Error status register */ +#define UERSTAT_BREAK (1 << 3) /* Break signal, not 2410 */ +#define UERSTAT_FRAME (1 << 2) /* Frame error */ +#define UERSTAT_PARITY (1 << 1) /* Parity error, not 2410 */ +#define UERSTAT_OVERRUN (1 << 0) /* Overrun */ +#define UERSTAT_ALL_ERRORS \ + (UERSTAT_OVERRUN|UERSTAT_BREAK|UERSTAT_FRAME|UERSTAT_PARITY) +#define SSCOM_UFSTAT 0x18 /* Fifo status register */ +#define UFSTAT_RXFULL (1 <<8) /* Rx fifo full */ +#define UFSTAT_TXCOUNT_SHIFT 4 /* TX FIFO count */ +#define UFSTAT_TXCOUNT (0x0f << UFSTAT_TXCOUNT_SHIFT) +#define UFSTAT_RXCOUNT_SHIFT 0 /* RX FIFO count */ +#define UFSTAT_RXCOUNT (0x0f << UFSTAT_RXCOUNT_SHIFT) +#define SSCOM_UMSTAT 0x1c /* Modem status register */ +#define UMSTAT_CTS (1 << 0) /* Clear to send */ +#if _BYTE_ORDER == _LITTLE_ENDIAN +#define SSCOM_UTXH 0x20 /* Transmit data register */ +#define SSCOM_URXH 0x24 /* Receive data register */ +#else +#define SSCOM_UTXH 0x23 /* Transmit data register */ +#define SSCOM_URXH 0x27 /* Receive data register */ +#endif +#define SSCOM_UBRDIV 0x28 /* baud-reate divisor */ +#define SSCOM_SIZE 0x2c |