485 lines
12 KiB
C
485 lines
12 KiB
C
/*
|
|
* Copyright (C) 2007-2009 Apple Inc. All rights reserved.
|
|
* Copyright (C) 2006 Apple Computer, Inc. All rights reserved.
|
|
*
|
|
* This document is the property of Apple Inc.
|
|
* It is considered confidential and proprietary.
|
|
*
|
|
* This document may not be reproduced or transmitted in any form,
|
|
* in whole or in part, without the express written permission of
|
|
* Apple Inc.
|
|
*/
|
|
#include <arch.h>
|
|
#include <debug.h>
|
|
#include <platform.h>
|
|
#include <platform/clocks.h>
|
|
#include <platform/int.h>
|
|
#include <platform/soc/hwclocks.h>
|
|
#include <platform/soc/hwisr.h>
|
|
#include <sys.h>
|
|
#include <sys/callout.h>
|
|
|
|
#include "timer.h"
|
|
|
|
/* 16-bit timers */
|
|
#define TIMER_A 0
|
|
#define TIMER_B 1
|
|
#define TIMER_C 2
|
|
#define TIMER_D 3
|
|
|
|
/* 32-bit timers */
|
|
#define TIMER_E 4
|
|
#define TIMER_F 5
|
|
#define TIMER_G 6
|
|
#define TIMER_H 7
|
|
|
|
/* 64-bit timer */
|
|
#define TIMER_64 8
|
|
|
|
#define INVALID_TIMER -1
|
|
|
|
#ifndef TIMER_DEADLINE_TIMER
|
|
# if TIMER_DEADLINE_USE16
|
|
# define TIMER_DEADLINE_TIMER TIMER_D
|
|
# else
|
|
# define TIMER_DEADLINE_TIMER TIMER_E
|
|
# endif
|
|
#endif
|
|
|
|
/* timer16/32 stuff */
|
|
typedef struct {
|
|
volatile u_int32_t *tcon;
|
|
volatile u_int32_t *tcmd;
|
|
volatile u_int32_t *tdata0;
|
|
volatile u_int32_t *tdata1;
|
|
volatile u_int32_t *tpre;
|
|
volatile u_int32_t *tcnt;
|
|
} timer_regs_t;
|
|
|
|
static const timer_regs_t timer_regs_list[] = {
|
|
{ &rTACON, &rTACMD, &rTADATA0, &rTADATA1, &rTAPRE, &rTACNT },
|
|
{ &rTBCON, &rTBCMD, &rTBDATA0, &rTBDATA1, &rTBPRE, &rTBCNT },
|
|
{ &rTCCON, &rTCCMD, &rTCDATA0, &rTCDATA1, &rTCPRE, &rTCCNT },
|
|
{ &rTDCON, &rTDCMD, &rTDDATA0, &rTDDATA1, &rTDPRE, &rTDCNT },
|
|
{ &rTECON, &rTECMD, &rTEDATA0, &rTEDATA1, &rTEPRE, &rTECNT },
|
|
{ &rTFCON, &rTFCMD, &rTFDATA0, &rTFDATA1, &rTFPRE, &rTFCNT },
|
|
{ &rTGCON, &rTGCMD, &rTGDATA0, &rTGDATA1, &rTGPRE, &rTGCNT },
|
|
{ &rTHCON, &rTHCMD, &rTHDATA0, &rTHDATA1, &rTHPRE, &rTHCNT }
|
|
};
|
|
static const timer_regs_t *timer_regs(int timer);
|
|
|
|
enum timer_clk {
|
|
PCLK,
|
|
NCLK,
|
|
SLOWCLK,
|
|
};
|
|
|
|
typedef struct {
|
|
/* configuration */
|
|
enum timer_clk source_clk;
|
|
uint32_t divider_bits;
|
|
int prescalar;
|
|
|
|
/* callout routine */
|
|
void (*ovf_tick)(void);
|
|
void (*mat0_tick)(void);
|
|
void (*mat1_tick)(void);
|
|
} timer_status_t;
|
|
|
|
static timer_status_t timer_status[TIMER_H+1];
|
|
|
|
enum timer_mode {
|
|
MODE_INTERVAL = 0,
|
|
MODE_PWM = 0x2,
|
|
MODE_PDM = 0x3,
|
|
MODE_ONESHOT_PWM = 0x4,
|
|
MODE_ONESHOT_PDM = 0x5,
|
|
MODE_CAPTURE = 0x6,
|
|
};
|
|
|
|
static void setup_timer(int timer, uint32_t data0, uint32_t data1, enum timer_mode mode, bool countdown, bool countdown_start, bool start_polarity);
|
|
static void setup_timer_clk(int timer, enum timer_clk clk, int divider, int prescalar);
|
|
static void start_stop_timer(int timer, bool start);
|
|
|
|
static void timer_deadline(void);
|
|
static void (* timer_deadline_func)(void);
|
|
|
|
/* timer 64 stuff */
|
|
static u_int64_t read_timer64(void);
|
|
|
|
static int timer64_ticks_per_sec; // for system time
|
|
static uint64_t timer64_scale;
|
|
|
|
static void timer32_int(void *);
|
|
static void timer16_int(void *);
|
|
|
|
static u_int64_t read_timer64(void)
|
|
{
|
|
u_int32_t low, high;
|
|
|
|
retry:
|
|
high = rTM64_CNTH;
|
|
low = rTM64_CNTL;
|
|
if (high != rTM64_CNTH)
|
|
goto retry;
|
|
|
|
return (u_int64_t)high << 32 | low;
|
|
}
|
|
|
|
static u_int32_t read_timer(int timer)
|
|
{
|
|
const timer_regs_t *regs = timer_regs(timer);
|
|
|
|
return *regs->tcnt;
|
|
}
|
|
|
|
static const timer_regs_t *timer_regs(int timer)
|
|
{
|
|
if (timer == TIMER_64)
|
|
panic("timer_regs: asked for invalid timer registers\n");
|
|
|
|
return &timer_regs_list[timer];
|
|
}
|
|
|
|
static void setup_timer_clk(int timer, enum timer_clk clk, int divider, int prescalar)
|
|
{
|
|
if (clk == SLOWCLK) {
|
|
timer_status[timer].source_clk = PCLK;
|
|
timer_status[timer].divider_bits = 6;
|
|
} else {
|
|
timer_status[timer].source_clk = clk;
|
|
switch (divider) {
|
|
case 2:
|
|
timer_status[timer].divider_bits = 0;
|
|
break;
|
|
case 4:
|
|
timer_status[timer].divider_bits = 1;
|
|
break;
|
|
case 16:
|
|
timer_status[timer].divider_bits = 2;
|
|
break;
|
|
case 64:
|
|
timer_status[timer].divider_bits = 3;
|
|
break;
|
|
case 1:
|
|
timer_status[timer].divider_bits = 4;
|
|
break;
|
|
default:
|
|
dprintf(DEBUG_CRITICAL, "setup_timer_clk: invalid divider %d\n", divider);
|
|
}
|
|
}
|
|
timer_status[timer].prescalar = prescalar;
|
|
}
|
|
|
|
static void setup_timer(int timer, uint32_t data0, uint32_t data1, enum timer_mode mode, bool countdown, bool countdown_start, bool start_polarity)
|
|
{
|
|
start_stop_timer(timer, false);
|
|
|
|
switch (timer) {
|
|
case TIMER_A: case TIMER_B: case TIMER_C: case TIMER_D: {
|
|
const timer_regs_t *regs = timer_regs(timer);
|
|
|
|
/* setup timer (16 bit) */
|
|
*regs->tcon = (start_polarity ? (1<<11) : 0) |
|
|
(mode << 3) |
|
|
(timer_status[timer].divider_bits<<8) | ((timer_status[timer].source_clk == NCLK) ? (1<<6) : 0) |
|
|
(7<<12); // mode, upcount (no downcount feature), divider, clock source, enable all ints
|
|
*regs->tpre = timer_status[timer].prescalar;
|
|
*regs->tdata0 = data0;
|
|
*regs->tdata1 = data1;
|
|
*regs->tcmd = 2; // initialize the timer
|
|
break;
|
|
}
|
|
case TIMER_E: case TIMER_F: case TIMER_G: case TIMER_H: {
|
|
const timer_regs_t *regs = timer_regs(timer);
|
|
|
|
/* setup timer (32 bit) */
|
|
*regs->tcon = (countdown_start ? (1<<28) : 0) |
|
|
(countdown ? (1<<24) : 0) |
|
|
(start_polarity ? (1<<11) : 0) |
|
|
(mode << 3) |
|
|
(timer_status[timer].divider_bits<<8) | ((timer_status[timer].source_clk == NCLK) ? (1<<6) : 0) |
|
|
(7<<12); // mode, downcount, pwm/pdm start bit, divider, clock source, enable all ints
|
|
*regs->tpre = timer_status[timer].prescalar;
|
|
*regs->tdata0 = data0;
|
|
*regs->tdata1 = data1;
|
|
*regs->tcmd = 2; // initialize the timer
|
|
break;
|
|
}
|
|
default:
|
|
panic("setup_timer: timer %d not supported\n", timer);
|
|
}
|
|
}
|
|
|
|
static void start_stop_timer(int timer, bool start)
|
|
{
|
|
switch (timer) {
|
|
case TIMER_A: case TIMER_B: case TIMER_C: case TIMER_D:
|
|
case TIMER_E: case TIMER_F: case TIMER_G: case TIMER_H: {
|
|
const timer_regs_t *regs = timer_regs(timer);
|
|
if (start)
|
|
*regs->tcmd = 1; // start it
|
|
else
|
|
*regs->tcmd = 0; // stop it
|
|
break;
|
|
}
|
|
case TIMER_64:
|
|
if (start)
|
|
rTM64_CON &= ~0xf;
|
|
else
|
|
rTM64_CON |= 0xa;
|
|
break;
|
|
default:
|
|
panic("start_stop_timer: timer %d not supported\n", timer);
|
|
}
|
|
}
|
|
|
|
int timer_init(u_int32_t timer)
|
|
{
|
|
dprintf(DEBUG_SPEW, "timers_init()\n");
|
|
|
|
#ifndef TIMER_NO_INIT
|
|
clock_gate(CLK_TIMER, true);
|
|
|
|
/* stop all timers */
|
|
start_stop_timer(TIMER_A, false);
|
|
start_stop_timer(TIMER_B, false);
|
|
start_stop_timer(TIMER_C, false);
|
|
start_stop_timer(TIMER_D, false);
|
|
start_stop_timer(TIMER_E, false);
|
|
start_stop_timer(TIMER_F, false);
|
|
start_stop_timer(TIMER_G, false);
|
|
start_stop_timer(TIMER_H, false);
|
|
start_stop_timer(TIMER_64, false);
|
|
|
|
setup_timer_clk(TIMER_A, NCLK, 2, 0);
|
|
setup_timer_clk(TIMER_B, NCLK, 2, 0);
|
|
setup_timer_clk(TIMER_C, NCLK, 2, 0);
|
|
setup_timer_clk(TIMER_D, NCLK, 2, 0);
|
|
setup_timer_clk(TIMER_E, NCLK, 2, 0);
|
|
setup_timer_clk(TIMER_F, NCLK, 2, 0);
|
|
setup_timer_clk(TIMER_G, NCLK, 2, 0);
|
|
setup_timer_clk(TIMER_H, NCLK, 2, 0);
|
|
|
|
/* set up the 64-bit timer */
|
|
rTM64_CON = 0xa; // disable it
|
|
rTM64_DATA0L = 0xffffffff;
|
|
rTM64_DATA0H = 0xffffffff;
|
|
rTM64_DATA1L = 0xffffffff;
|
|
rTM64_DATA1H = 0xffffffff;
|
|
rTM64_CON = (3<<15) | (1 << 4); // set nclk source, prescalar at /2, enable
|
|
|
|
/* calculate how fast timer64 is running */
|
|
timer64_ticks_per_sec = clock_get_frequency(CLK_NCLK) / 2;
|
|
#else
|
|
# ifndef TIMER_TICK_RATE
|
|
# error TIMER_TICK_RATE not defined but we did not init the timebase
|
|
# endif
|
|
timer64_ticks_per_sec = TIMER_TICK_RATE;
|
|
#endif
|
|
|
|
// Precompute a 44.20 fixed-point representation of the microsecond to ticks conversion rate.
|
|
// This limits the maximum number of microseconds that can be represented to 44 bits worth of ticks.
|
|
timer64_scale = ((uint64_t)timer64_ticks_per_sec << 20) / (1000 * 1000);
|
|
|
|
dprintf(DEBUG_SPEW, "timer64 running at %u Hz, will wrap around in %llu secs\n",
|
|
timer64_ticks_per_sec, 0xffffffffffffffffULL / timer64_ticks_per_sec);
|
|
|
|
dprintf(DEBUG_SPEW, "using 64-bit timer for system time\n");
|
|
|
|
/* install interrupt handler */
|
|
#if TIMER_DEADLINE_USE16
|
|
install_int_handler(INT_TIMERIRQ, &timer16_int, (void *)1);
|
|
unmask_int(INT_TIMERIRQ);
|
|
#else
|
|
install_int_handler(INT_TIMERFIQ, &timer32_int, (void *)1);
|
|
unmask_int(INT_TIMERFIQ);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
void timer_gpio(int timer, int level)
|
|
{
|
|
setup_timer(timer, 0, 0, MODE_PWM, false, false, level);
|
|
}
|
|
|
|
static void timer_handle_int(int timer, u_int32_t status)
|
|
{
|
|
if (status & (1<<2)) {
|
|
// overflow
|
|
if (timer_status[timer].ovf_tick)
|
|
timer_status[timer].ovf_tick();
|
|
}
|
|
if (status & (1<<1)) {
|
|
// int1
|
|
if (timer_status[timer].mat1_tick)
|
|
timer_status[timer].mat1_tick();
|
|
}
|
|
if (status & (1<<0)) {
|
|
// int0
|
|
if (timer_status[timer].mat0_tick)
|
|
timer_status[timer].mat0_tick();
|
|
}
|
|
}
|
|
|
|
static void timer16_int(void *arg)
|
|
{
|
|
u_int32_t tcon;
|
|
|
|
tcon = rTACON;
|
|
if (tcon & (7<<16)) {
|
|
timer_handle_int(TIMER_A, tcon >> 16);
|
|
rTACON |= tcon & (7<<16);
|
|
}
|
|
tcon = rTBCON;
|
|
if (tcon & (7<<16)) {
|
|
timer_handle_int(TIMER_B, tcon >> 16);
|
|
rTBCON |= tcon & (7<<16);
|
|
}
|
|
tcon = rTCCON;
|
|
if (tcon & (7<<16)) {
|
|
timer_handle_int(TIMER_C, tcon >> 16);
|
|
rTCCON |= tcon & (7<<16);
|
|
}
|
|
tcon = rTDCON;
|
|
if (tcon & (7<<16)) {
|
|
timer_handle_int(TIMER_D, tcon >> 16);
|
|
rTDCON |= tcon & (7<<16);
|
|
}
|
|
}
|
|
|
|
static void timer32_int(void *arg)
|
|
{
|
|
u_int32_t tm32_status, tm32_ack, status, cnt;
|
|
|
|
tm32_status = rTM32_INT;
|
|
tm32_ack = 0;
|
|
cnt = 0;
|
|
|
|
#if TIMER_VERSION != 0
|
|
status = (tm32_status >> (cnt * 8)) & 0x7;
|
|
timer_handle_int(TIMER_H, status);
|
|
tm32_ack |= (status << (cnt * 8));
|
|
cnt++;
|
|
#endif
|
|
status = (tm32_status >> (cnt * 8)) & 0x7;
|
|
timer_handle_int(TIMER_G, status);
|
|
tm32_ack |= (status << (cnt * 8));
|
|
cnt++;
|
|
|
|
status = (tm32_status >> (cnt * 8)) & 0x7;
|
|
timer_handle_int(TIMER_F, status);
|
|
tm32_ack |= (status << (cnt * 8));
|
|
cnt++;
|
|
|
|
status = (tm32_status >> (cnt * 8)) & 0x7;
|
|
timer_handle_int(TIMER_E, status);
|
|
tm32_ack |= (status << (cnt * 8));
|
|
cnt++;
|
|
|
|
rTM32_INT = tm32_ack;
|
|
}
|
|
|
|
static void timer_deadline(void)
|
|
{
|
|
|
|
// printf("deadline interrupt at %llu\n", read_timer64());
|
|
/* stop the timer */
|
|
start_stop_timer(TIMER_DEADLINE_TIMER, false);
|
|
|
|
/* call the handler */
|
|
if (timer_deadline_func)
|
|
timer_deadline_func();
|
|
}
|
|
|
|
u_int64_t timer_get_ticks()
|
|
{
|
|
return read_timer64();
|
|
}
|
|
|
|
u_int32_t timer_get_tick_rate(void)
|
|
{
|
|
return timer64_ticks_per_sec;
|
|
}
|
|
|
|
utime_t timer_ticks_to_usecs(uint64_t ticks)
|
|
{
|
|
return (ticks * 1000 * 1000) / timer64_ticks_per_sec;
|
|
}
|
|
|
|
uint64_t timer_usecs_to_ticks(utime_t usecs)
|
|
{
|
|
return (usecs * timer64_scale) >> 20;
|
|
}
|
|
|
|
void timer_stop_all(void)
|
|
{
|
|
#ifndef TIMER_NO_INIT
|
|
/* stop all timers */
|
|
start_stop_timer(TIMER_A, false);
|
|
start_stop_timer(TIMER_B, false);
|
|
start_stop_timer(TIMER_C, false);
|
|
start_stop_timer(TIMER_D, false);
|
|
start_stop_timer(TIMER_E, false);
|
|
start_stop_timer(TIMER_F, false);
|
|
start_stop_timer(TIMER_G, false);
|
|
start_stop_timer(TIMER_H, false);
|
|
start_stop_timer(TIMER_64, false);
|
|
#endif
|
|
}
|
|
|
|
void timer_deadline_enter(uint64_t deadline, void (* callback)(void))
|
|
{
|
|
uint64_t ticks;
|
|
uint32_t decr;
|
|
|
|
timer_deadline_func = callback;
|
|
if (callback != NULL) {
|
|
/* convert absolute deadline to relative time */
|
|
ticks = read_timer64();
|
|
if (deadline <= ticks) {
|
|
// printf("setting deadline %llu before current time %llu\n", deadline, ticks);
|
|
deadline = 1;
|
|
} else {
|
|
// printf("setting deadline %llu (0x%llx away)\n", deadline, deadline - ticks);
|
|
deadline -= ticks;
|
|
}
|
|
|
|
/* clamp the deadline to our maximum */
|
|
#if TIMER_DEADLINE_USE16
|
|
decr = (deadline > 0xffff) ? 0xffff : deadline;
|
|
#else
|
|
decr = (deadline > 0xffffffff) ? 0xffffffff : deadline;
|
|
#endif
|
|
// printf(" decr 0x%x\n", decr);
|
|
|
|
/*
|
|
* Start the timer in interval mode. We will stop it when we handle the
|
|
* interrupt, so it doesn't really matter as long as we get an interrupt after
|
|
* (decr) ticks or so.
|
|
*/
|
|
timer_status[TIMER_DEADLINE_TIMER].mat0_tick = timer_deadline;
|
|
setup_timer(TIMER_DEADLINE_TIMER, decr, 0, MODE_INTERVAL, false, false, 0);
|
|
start_stop_timer(TIMER_DEADLINE_TIMER, true);
|
|
} else {
|
|
/* stop the timer */
|
|
start_stop_timer(TIMER_DEADLINE_TIMER, false);
|
|
}
|
|
}
|
|
|
|
u_int32_t timer_get_entropy(void)
|
|
{
|
|
return arch_get_entropy(&rTM64_CNTL);
|
|
}
|
|
|
|
void wdt_enable(void)
|
|
{
|
|
rWDTCON = (1 << 20) | (15 << 16) | (4 << 12) | (0xA << 8);
|
|
}
|
|
|
|
void wdt_chip_reset(void)
|
|
{
|
|
rWDTCON = (1 << 20) | (0 << 16) | (0 << 15) | (0 << 12) | (0 << 8) | (0 << 0);
|
|
}
|