Re-implement clocks for these SoC by using now standard extres/clk framework. This is necessary for future expansion of these. The new implementation is (due to the size of the patch) only the initial (minimum) version. It will be updated/expanded with a subsequent set of particular patches. This patch is also not tested on OMAP4 based boards (BeagleBone), so all possible issues should be (and will be) fixed by ASAP once identified. Submited by: Oskar Holmlund (oskar.holmlund@ohdata.se) Differential Revision: https://reviews.freebsd.org/D25118
409 lines
11 KiB
C
409 lines
11 KiB
C
/*-
|
|
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
|
*
|
|
* Copyright (c) 2012 Damjan Marion <dmarion@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/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/module.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/rman.h>
|
|
#include <sys/timeet.h>
|
|
#include <sys/timetc.h>
|
|
#include <machine/bus.h>
|
|
|
|
#include <machine/machdep.h> /* For arm_set_delay */
|
|
|
|
#include <dev/extres/clk/clk.h>
|
|
|
|
#include <dev/ofw/openfirm.h>
|
|
#include <dev/ofw/ofw_bus.h>
|
|
#include <dev/ofw/ofw_bus_subr.h>
|
|
|
|
#include <arm/ti/ti_sysc.h>
|
|
|
|
#include "am335x_dmtreg.h"
|
|
|
|
struct am335x_dmtimer_softc {
|
|
device_t dev;
|
|
int tmr_mem_rid;
|
|
struct resource * tmr_mem_res;
|
|
int tmr_irq_rid;
|
|
struct resource * tmr_irq_res;
|
|
void *tmr_irq_handler;
|
|
clk_t clk_fck;
|
|
uint64_t sysclk_freq;
|
|
uint32_t tclr; /* Cached TCLR register. */
|
|
union {
|
|
struct timecounter tc;
|
|
struct eventtimer et;
|
|
} func;
|
|
int tmr_num; /* Hardware unit number. */
|
|
char tmr_name[12]; /* "DMTimerN", N = tmr_num */
|
|
};
|
|
|
|
static struct am335x_dmtimer_softc *am335x_dmtimer_et_sc = NULL;
|
|
static struct am335x_dmtimer_softc *am335x_dmtimer_tc_sc = NULL;
|
|
|
|
static void am335x_dmtimer_delay(int, void *);
|
|
|
|
/*
|
|
* We use dmtimer2 for eventtimer and dmtimer3 for timecounter.
|
|
*/
|
|
#define ET_TMR_NUM 2
|
|
#define TC_TMR_NUM 3
|
|
|
|
/* List of compatible strings for FDT tree */
|
|
static struct ofw_compat_data compat_data[] = {
|
|
{"ti,am335x-timer", 1},
|
|
{"ti,am335x-timer-1ms", 1},
|
|
{NULL, 0},
|
|
};
|
|
|
|
#define DMTIMER_READ4(sc, reg) bus_read_4((sc)->tmr_mem_res, (reg))
|
|
#define DMTIMER_WRITE4(sc, reg, val) bus_write_4((sc)->tmr_mem_res, (reg), (val))
|
|
|
|
static int
|
|
am335x_dmtimer_et_start(struct eventtimer *et, sbintime_t first, sbintime_t period)
|
|
{
|
|
struct am335x_dmtimer_softc *sc;
|
|
uint32_t initial_count, reload_count;
|
|
|
|
sc = et->et_priv;
|
|
|
|
/*
|
|
* Stop the timer before changing it. This routine will often be called
|
|
* while the timer is still running, to either lengthen or shorten the
|
|
* current event time. We need to ensure the timer doesn't expire while
|
|
* we're working with it.
|
|
*
|
|
* Also clear any pending interrupt status, because it's at least
|
|
* theoretically possible that we're running in a primary interrupt
|
|
* context now, and a timer interrupt could be pending even before we
|
|
* stopped the timer. The more likely case is that we're being called
|
|
* from the et_event_cb() routine dispatched from our own handler, but
|
|
* it's not clear to me that that's the only case possible.
|
|
*/
|
|
sc->tclr &= ~(DMT_TCLR_START | DMT_TCLR_AUTOLOAD);
|
|
DMTIMER_WRITE4(sc, DMT_TCLR, sc->tclr);
|
|
DMTIMER_WRITE4(sc, DMT_IRQSTATUS, DMT_IRQ_OVF);
|
|
|
|
if (period != 0) {
|
|
reload_count = ((uint32_t)et->et_frequency * period) >> 32;
|
|
sc->tclr |= DMT_TCLR_AUTOLOAD;
|
|
} else {
|
|
reload_count = 0;
|
|
}
|
|
|
|
if (first != 0)
|
|
initial_count = ((uint32_t)et->et_frequency * first) >> 32;
|
|
else
|
|
initial_count = reload_count;
|
|
|
|
/*
|
|
* Set auto-reload and current-count values. This timer hardware counts
|
|
* up from the initial/reload value and interrupts on the zero rollover.
|
|
*/
|
|
DMTIMER_WRITE4(sc, DMT_TLDR, 0xFFFFFFFF - reload_count);
|
|
DMTIMER_WRITE4(sc, DMT_TCRR, 0xFFFFFFFF - initial_count);
|
|
|
|
/* Enable overflow interrupt, and start the timer. */
|
|
DMTIMER_WRITE4(sc, DMT_IRQENABLE_SET, DMT_IRQ_OVF);
|
|
sc->tclr |= DMT_TCLR_START;
|
|
DMTIMER_WRITE4(sc, DMT_TCLR, sc->tclr);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
am335x_dmtimer_et_stop(struct eventtimer *et)
|
|
{
|
|
struct am335x_dmtimer_softc *sc;
|
|
|
|
sc = et->et_priv;
|
|
|
|
/* Stop timer, disable and clear interrupt. */
|
|
sc->tclr &= ~(DMT_TCLR_START | DMT_TCLR_AUTOLOAD);
|
|
DMTIMER_WRITE4(sc, DMT_TCLR, sc->tclr);
|
|
DMTIMER_WRITE4(sc, DMT_IRQENABLE_CLR, DMT_IRQ_OVF);
|
|
DMTIMER_WRITE4(sc, DMT_IRQSTATUS, DMT_IRQ_OVF);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
am335x_dmtimer_et_intr(void *arg)
|
|
{
|
|
struct am335x_dmtimer_softc *sc;
|
|
|
|
sc = arg;
|
|
|
|
/* Ack the interrupt, and invoke the callback if it's still enabled. */
|
|
DMTIMER_WRITE4(sc, DMT_IRQSTATUS, DMT_IRQ_OVF);
|
|
if (sc->func.et.et_active)
|
|
sc->func.et.et_event_cb(&sc->func.et, sc->func.et.et_arg);
|
|
|
|
return (FILTER_HANDLED);
|
|
}
|
|
|
|
static int
|
|
am335x_dmtimer_et_init(struct am335x_dmtimer_softc *sc)
|
|
{
|
|
KASSERT(am335x_dmtimer_et_sc == NULL, ("already have an eventtimer"));
|
|
|
|
/*
|
|
* Setup eventtimer interrupt handling. Panic if anything goes wrong,
|
|
* because the system just isn't going to run without an eventtimer.
|
|
*/
|
|
sc->tmr_irq_res = bus_alloc_resource_any(sc->dev, SYS_RES_IRQ,
|
|
&sc->tmr_irq_rid, RF_ACTIVE);
|
|
if (sc->tmr_irq_res == NULL)
|
|
panic("am335x_dmtimer: could not allocate irq resources");
|
|
if (bus_setup_intr(sc->dev, sc->tmr_irq_res, INTR_TYPE_CLK,
|
|
am335x_dmtimer_et_intr, NULL, sc, &sc->tmr_irq_handler) != 0)
|
|
panic("am335x_dmtimer: count not setup irq handler");
|
|
|
|
sc->func.et.et_name = sc->tmr_name;
|
|
sc->func.et.et_flags = ET_FLAGS_PERIODIC | ET_FLAGS_ONESHOT;
|
|
sc->func.et.et_quality = 500;
|
|
sc->func.et.et_frequency = sc->sysclk_freq;
|
|
sc->func.et.et_min_period =
|
|
((0x00000005LLU << 32) / sc->func.et.et_frequency);
|
|
sc->func.et.et_max_period =
|
|
(0xfffffffeLLU << 32) / sc->func.et.et_frequency;
|
|
sc->func.et.et_start = am335x_dmtimer_et_start;
|
|
sc->func.et.et_stop = am335x_dmtimer_et_stop;
|
|
sc->func.et.et_priv = sc;
|
|
|
|
am335x_dmtimer_et_sc = sc;
|
|
et_register(&sc->func.et);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static unsigned
|
|
am335x_dmtimer_tc_get_timecount(struct timecounter *tc)
|
|
{
|
|
struct am335x_dmtimer_softc *sc;
|
|
|
|
sc = tc->tc_priv;
|
|
|
|
return (DMTIMER_READ4(sc, DMT_TCRR));
|
|
}
|
|
|
|
static int
|
|
am335x_dmtimer_tc_init(struct am335x_dmtimer_softc *sc)
|
|
{
|
|
KASSERT(am335x_dmtimer_tc_sc == NULL, ("already have a timecounter"));
|
|
|
|
/* Set up timecounter, start it, register it. */
|
|
DMTIMER_WRITE4(sc, DMT_TSICR, DMT_TSICR_RESET);
|
|
while (DMTIMER_READ4(sc, DMT_TIOCP_CFG) & DMT_TIOCP_RESET)
|
|
continue;
|
|
|
|
sc->tclr |= DMT_TCLR_START | DMT_TCLR_AUTOLOAD;
|
|
DMTIMER_WRITE4(sc, DMT_TLDR, 0);
|
|
DMTIMER_WRITE4(sc, DMT_TCRR, 0);
|
|
DMTIMER_WRITE4(sc, DMT_TCLR, sc->tclr);
|
|
|
|
sc->func.tc.tc_name = sc->tmr_name;
|
|
sc->func.tc.tc_get_timecount = am335x_dmtimer_tc_get_timecount;
|
|
sc->func.tc.tc_counter_mask = ~0u;
|
|
sc->func.tc.tc_frequency = sc->sysclk_freq;
|
|
sc->func.tc.tc_quality = 500;
|
|
sc->func.tc.tc_priv = sc;
|
|
|
|
am335x_dmtimer_tc_sc = sc;
|
|
tc_init(&sc->func.tc);
|
|
|
|
arm_set_delay(am335x_dmtimer_delay, sc);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
am335x_dmtimer_probe(device_t dev)
|
|
{
|
|
char strbuf[32];
|
|
int tmr_num;
|
|
uint64_t rev_address;
|
|
|
|
if (!ofw_bus_status_okay(dev))
|
|
return (ENXIO);
|
|
|
|
if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
|
|
return (ENXIO);
|
|
|
|
/*
|
|
* Get the hardware unit number from address of rev register.
|
|
* If this isn't the hardware unit we're going to use for either the
|
|
* eventtimer or the timecounter, no point in instantiating the device.
|
|
*/
|
|
rev_address = ti_sysc_get_rev_address(device_get_parent(dev));
|
|
switch (rev_address) {
|
|
case DMTIMER2_REV:
|
|
tmr_num = 2;
|
|
break;
|
|
case DMTIMER3_REV:
|
|
tmr_num = 3;
|
|
break;
|
|
default:
|
|
/* Not DMTIMER2 or DMTIMER3 */
|
|
return (ENXIO);
|
|
}
|
|
|
|
snprintf(strbuf, sizeof(strbuf), "AM335x DMTimer%d", tmr_num);
|
|
device_set_desc_copy(dev, strbuf);
|
|
|
|
return(BUS_PROBE_DEFAULT);
|
|
}
|
|
|
|
static int
|
|
am335x_dmtimer_attach(device_t dev)
|
|
{
|
|
struct am335x_dmtimer_softc *sc;
|
|
int err;
|
|
uint64_t rev_address;
|
|
clk_t sys_clkin;
|
|
|
|
sc = device_get_softc(dev);
|
|
sc->dev = dev;
|
|
|
|
/* expect one clock */
|
|
err = clk_get_by_ofw_index(dev, 0, 0, &sc->clk_fck);
|
|
if (err != 0) {
|
|
device_printf(dev, "Cant find clock index 0. err: %d\n", err);
|
|
return (ENXIO);
|
|
}
|
|
|
|
err = clk_get_by_name(dev, "sys_clkin_ck@40", &sys_clkin);
|
|
if (err != 0) {
|
|
device_printf(dev, "Cant find sys_clkin_ck@40 err: %d\n", err);
|
|
return (ENXIO);
|
|
}
|
|
|
|
/* Select M_OSC as DPLL parent */
|
|
err = clk_set_parent_by_clk(sc->clk_fck, sys_clkin);
|
|
if (err != 0) {
|
|
device_printf(dev, "Cant set mux to CLK_M_OSC\n");
|
|
return (ENXIO);
|
|
}
|
|
|
|
/* Enable clocks and power on the device. */
|
|
err = ti_sysc_clock_enable(device_get_parent(dev));
|
|
if (err != 0) {
|
|
device_printf(dev, "Cant enable sysc clkctrl, err %d\n", err);
|
|
return (ENXIO);
|
|
}
|
|
|
|
/* Get the base clock frequency. */
|
|
err = clk_get_freq(sc->clk_fck, &sc->sysclk_freq);
|
|
if (err != 0) {
|
|
device_printf(dev, "Cant get sysclk frequency, err %d\n", err);
|
|
return (ENXIO);
|
|
}
|
|
|
|
/* Request the memory resources. */
|
|
sc->tmr_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
|
|
&sc->tmr_mem_rid, RF_ACTIVE);
|
|
if (sc->tmr_mem_res == NULL) {
|
|
return (ENXIO);
|
|
}
|
|
|
|
rev_address = ti_sysc_get_rev_address(device_get_parent(dev));
|
|
switch (rev_address) {
|
|
case DMTIMER2_REV:
|
|
sc->tmr_num = 2;
|
|
break;
|
|
case DMTIMER3_REV:
|
|
sc->tmr_num = 3;
|
|
break;
|
|
default:
|
|
device_printf(dev, "Not timer 2 or 3! %#jx\n",
|
|
rev_address);
|
|
return (ENXIO);
|
|
}
|
|
|
|
snprintf(sc->tmr_name, sizeof(sc->tmr_name), "DMTimer%d", sc->tmr_num);
|
|
|
|
/*
|
|
* Go set up either a timecounter or eventtimer. We wouldn't have
|
|
* attached if we weren't one or the other.
|
|
*/
|
|
if (sc->tmr_num == ET_TMR_NUM)
|
|
am335x_dmtimer_et_init(sc);
|
|
else if (sc->tmr_num == TC_TMR_NUM)
|
|
am335x_dmtimer_tc_init(sc);
|
|
else
|
|
panic("am335x_dmtimer: bad timer number %d", sc->tmr_num);
|
|
|
|
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, ti_sysc, 1, 1, 1);
|
|
|
|
static void
|
|
am335x_dmtimer_delay(int usec, void *arg)
|
|
{
|
|
struct am335x_dmtimer_softc *sc = arg;
|
|
int32_t counts;
|
|
uint32_t first, last;
|
|
|
|
/* Get the number of times to count */
|
|
counts = (usec + 1) * (sc->sysclk_freq / 1000000);
|
|
|
|
first = DMTIMER_READ4(sc, DMT_TCRR);
|
|
|
|
while (counts > 0) {
|
|
last = DMTIMER_READ4(sc, DMT_TCRR);
|
|
if (last > first) {
|
|
counts -= (int32_t)(last - first);
|
|
} else {
|
|
counts -= (int32_t)((0xFFFFFFFF - first) + last);
|
|
}
|
|
first = last;
|
|
}
|
|
}
|