/*- * Copyright (c) 2012 Damjan Marion * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define AM335X_NUM_TIMERS 8 #define DMTIMER_TIDR 0x00 /* Identification Register */ #define DMTIMER_TIOCP_CFG 0x10 /* Timer OCP Configuration Reg */ #define DMTIMER_IQR_EOI 0x20 /* Timer IRQ End-Of-Interrupt Reg */ #define DMTIMER_IRQSTATUS_RAW 0x24 /* Timer IRQSTATUS Raw Reg */ #define DMTIMER_IRQSTATUS 0x28 /* Timer IRQSTATUS Reg */ #define DMTIMER_IRQENABLE_SET 0x2c /* Timer IRQSTATUS Set Reg */ #define DMTIMER_IRQENABLE_CLR 0x30 /* Timer IRQSTATUS Clear Reg */ #define DMTIMER_IRQWAKEEN 0x34 /* Timer IRQ Wakeup Enable Reg */ #define DMTIMER_TCLR 0x38 /* Timer Control Register */ #define DMTIMER_TCRR 0x3C /* Timer Counter Register */ #define DMTIMER_TLDR 0x40 /* Timer Load Reg */ #define DMTIMER_TTGR 0x44 /* Timer Trigger Reg */ #define DMTIMER_TWPS 0x48 /* Timer Write Posted Status Reg */ #define DMTIMER_TMAR 0x4C /* Timer Match Reg */ #define DMTIMER_TCAR1 0x50 /* Timer Capture Reg */ #define DMTIMER_TSICR 0x54 /* Timer Synchr. Interface Control Reg */ #define DMTIMER_TCAR2 0x48 /* Timer Capture Reg */ struct am335x_dmtimer_softc { struct resource * tmr_mem_res[AM335X_NUM_TIMERS]; struct resource * tmr_irq_res[AM335X_NUM_TIMERS]; uint32_t sysclk_freq; struct am335x_dmtimer { bus_space_tag_t bst; bus_space_handle_t bsh; struct eventtimer et; } t[AM335X_NUM_TIMERS]; }; static struct resource_spec am335x_dmtimer_mem_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_MEMORY, 1, RF_ACTIVE }, { SYS_RES_MEMORY, 2, RF_ACTIVE }, { SYS_RES_MEMORY, 3, RF_ACTIVE }, { SYS_RES_MEMORY, 4, RF_ACTIVE }, { SYS_RES_MEMORY, 5, RF_ACTIVE }, { SYS_RES_MEMORY, 6, RF_ACTIVE }, { SYS_RES_MEMORY, 7, RF_ACTIVE }, { -1, 0, 0 } }; static struct resource_spec am335x_dmtimer_irq_spec[] = { { SYS_RES_IRQ, 0, RF_ACTIVE }, { SYS_RES_IRQ, 1, RF_ACTIVE }, { SYS_RES_IRQ, 2, RF_ACTIVE }, { SYS_RES_IRQ, 3, RF_ACTIVE }, { SYS_RES_IRQ, 4, RF_ACTIVE }, { SYS_RES_IRQ, 5, RF_ACTIVE }, { SYS_RES_IRQ, 6, RF_ACTIVE }, { SYS_RES_IRQ, 7, RF_ACTIVE }, { -1, 0, 0 } }; static struct am335x_dmtimer *am335x_dmtimer_tc_tmr = NULL; /* Read/Write macros for Timer used as timecounter */ #define am335x_dmtimer_tc_read_4(reg) \ bus_space_read_4(am335x_dmtimer_tc_tmr->bst, \ am335x_dmtimer_tc_tmr->bsh, reg) #define am335x_dmtimer_tc_write_4(reg, val) \ bus_space_write_4(am335x_dmtimer_tc_tmr->bst, \ am335x_dmtimer_tc_tmr->bsh, reg, val) /* Read/Write macros for Timer used as eventtimer */ #define am335x_dmtimer_et_read_4(reg) \ bus_space_read_4(tmr->bst, tmr->bsh, reg) #define am335x_dmtimer_et_write_4(reg, val) \ bus_space_write_4(tmr->bst, tmr->bsh, reg, val) static unsigned am335x_dmtimer_tc_get_timecount(struct timecounter *); static struct timecounter am335x_dmtimer_tc = { .tc_name = "AM335x Timecounter", .tc_get_timecount = am335x_dmtimer_tc_get_timecount, .tc_poll_pps = NULL, .tc_counter_mask = ~0u, .tc_frequency = 0, .tc_quality = 1000, }; static unsigned am335x_dmtimer_tc_get_timecount(struct timecounter *tc) { return am335x_dmtimer_tc_read_4(DMTIMER_TCRR); } static int am335x_dmtimer_start(struct eventtimer *et, struct bintime *first, struct bintime *period) { struct am335x_dmtimer *tmr = (struct am335x_dmtimer *)et->et_priv; uint32_t load, count; uint32_t tclr = 0; if (period != NULL) { load = (et->et_frequency * (period->frac >> 32)) >> 32; if (period->sec > 0) load += et->et_frequency * period->sec; tclr |= 2; /* autoreload bit */ panic("periodic timer not implemented\n"); } else { load = 0; } if (first != NULL) { count = (tmr->et.et_frequency * (first->frac >> 32)) >> 32; if (first->sec != 0) count += tmr->et.et_frequency * first->sec; } else { count = load; } /* Reset Timer */ am335x_dmtimer_et_write_4(DMTIMER_TSICR, 2); /* Wait for reset to complete */ while (am335x_dmtimer_et_read_4(DMTIMER_TIOCP_CFG) & 1); /* set load value */ am335x_dmtimer_et_write_4(DMTIMER_TLDR, 0xFFFFFFFE - load); /* set counter value */ am335x_dmtimer_et_write_4(DMTIMER_TCRR, 0xFFFFFFFE - count); /* enable overflow interrupt */ am335x_dmtimer_et_write_4(DMTIMER_IRQENABLE_SET, 2); /* start timer(ST) */ tclr |= 1; am335x_dmtimer_et_write_4(DMTIMER_TCLR, tclr); return (0); } static int am335x_dmtimer_stop(struct eventtimer *et) { struct am335x_dmtimer *tmr = (struct am335x_dmtimer *)et->et_priv; /* Disable all interrupts */ am335x_dmtimer_et_write_4(DMTIMER_IRQENABLE_CLR, 7); /* Stop Timer */ am335x_dmtimer_et_write_4(DMTIMER_TCLR, 0); return (0); } static int am335x_dmtimer_intr(void *arg) { struct am335x_dmtimer *tmr = (struct am335x_dmtimer *)arg; /* Ack interrupt */ am335x_dmtimer_et_write_4(DMTIMER_IRQSTATUS, 7); if (tmr->et.et_active) tmr->et.et_event_cb(&tmr->et, tmr->et.et_arg); return (FILTER_HANDLED); } static int am335x_dmtimer_probe(device_t dev) { struct am335x_dmtimer_softc *sc; sc = (struct am335x_dmtimer_softc *)device_get_softc(dev); if (ofw_bus_is_compatible(dev, "ti,am335x-dmtimer")) { device_set_desc(dev, "AM335x DMTimer"); return(BUS_PROBE_DEFAULT); } return (ENXIO); } static int am335x_dmtimer_attach(device_t dev) { struct am335x_dmtimer_softc *sc = device_get_softc(dev); void *ihl; int err; int i; if (am335x_dmtimer_tc_tmr != NULL) return (EINVAL); /* Get the base clock frequency */ err = ti_prcm_clk_get_source_freq(SYS_CLK, &sc->sysclk_freq); if (err) { device_printf(dev, "Error: could not get sysclk frequency\n"); return (ENXIO); } /* Request the memory resources */ err = bus_alloc_resources(dev, am335x_dmtimer_mem_spec, sc->tmr_mem_res); if (err) { device_printf(dev, "Error: could not allocate mem resources\n"); return (ENXIO); } /* Request the IRQ resources */ err = bus_alloc_resources(dev, am335x_dmtimer_irq_spec, sc->tmr_irq_res); if (err) { device_printf(dev, "Error: could not allocate irq resources\n"); return (ENXIO); } for(i=0;it[i].bst = rman_get_bustag(sc->tmr_mem_res[i]); sc->t[i].bsh = rman_get_bushandle(sc->tmr_mem_res[i]); } /* Configure DMTimer2 and DMTimer3 source and enable them */ err = ti_prcm_clk_set_source(DMTIMER2_CLK, SYSCLK_CLK); err |= ti_prcm_clk_enable(DMTIMER2_CLK); err |= ti_prcm_clk_set_source(DMTIMER3_CLK, SYSCLK_CLK); err |= ti_prcm_clk_enable(DMTIMER3_CLK); if (err) { device_printf(dev, "Error: could not setup timer clock\n"); return (ENXIO); } /* Take DMTimer2 for TC */ am335x_dmtimer_tc_tmr = &sc->t[2]; /* Reset Timer */ am335x_dmtimer_tc_write_4(DMTIMER_TSICR, 2); /* Wait for reset to complete */ while (am335x_dmtimer_tc_read_4(DMTIMER_TIOCP_CFG) & 1); /* set load value */ am335x_dmtimer_tc_write_4(DMTIMER_TLDR, 0); /* set counter value */ am335x_dmtimer_tc_write_4(DMTIMER_TCRR, 0); /* Set Timer autoreload(AR) and start timer(ST) */ am335x_dmtimer_tc_write_4(DMTIMER_TCLR, 3); am335x_dmtimer_tc.tc_frequency = sc->sysclk_freq; tc_init(&am335x_dmtimer_tc); /* Register DMTimer3 as ET */ /* Setup and enable the timer */ if (bus_setup_intr(dev, sc->tmr_irq_res[3], INTR_TYPE_CLK, am335x_dmtimer_intr, NULL, &sc->t[3], &ihl) != 0) { bus_release_resources(dev, am335x_dmtimer_irq_spec, sc->tmr_irq_res); device_printf(dev, "Unable to setup the clock irq handler.\n"); return (ENXIO); } sc->t[3].et.et_name = "AM335x Eventtimer0"; sc->t[3].et.et_flags = ET_FLAGS_PERIODIC | ET_FLAGS_ONESHOT; sc->t[3].et.et_quality = 1000; sc->t[3].et.et_frequency = sc->sysclk_freq; sc->t[3].et.et_min_period.sec = 0; sc->t[3].et.et_min_period.frac = ((0x00000002LLU << 32) / sc->t[3].et.et_frequency) << 32; sc->t[3].et.et_max_period.sec = 0xfffffff0U / sc->t[3].et.et_frequency; sc->t[3].et.et_max_period.frac = ((0xfffffffeLLU << 32) / sc->t[3].et.et_frequency) << 32; sc->t[3].et.et_start = am335x_dmtimer_start; sc->t[3].et.et_stop = am335x_dmtimer_stop; sc->t[3].et.et_priv = &sc->t[3]; et_register(&sc->t[3].et); return (0); } static device_method_t am335x_dmtimer_methods[] = { DEVMETHOD(device_probe, am335x_dmtimer_probe), DEVMETHOD(device_attach, am335x_dmtimer_attach), { 0, 0 } }; static driver_t am335x_dmtimer_driver = { "am335x_dmtimer", am335x_dmtimer_methods, sizeof(struct am335x_dmtimer_softc), }; static devclass_t am335x_dmtimer_devclass; DRIVER_MODULE(am335x_dmtimer, simplebus, am335x_dmtimer_driver, am335x_dmtimer_devclass, 0, 0); MODULE_DEPEND(am335x_dmtimer, am335x_prcm, 1, 1, 1); void cpu_initclocks(void) { cpu_initclocks_bsp(); } void DELAY(int usec) { int32_t counts; uint32_t first, last; if (am335x_dmtimer_tc_tmr == NULL) { for (; usec > 0; usec--) for (counts = 200; counts > 0; counts--) /* Prevent gcc from optimizing out the loop */ cpufunc_nullop(); return; } /* Get the number of times to count */ counts = usec * ((am335x_dmtimer_tc.tc_frequency / 1000000) + 1); first = am335x_dmtimer_tc_read_4(DMTIMER_TCRR); while (counts > 0) { last = am335x_dmtimer_tc_read_4(DMTIMER_TCRR); if (last>first) { counts -= (int32_t)(last - first); } else { counts -= (int32_t)((0xFFFFFFFF - first) + last); } first = last; } }