blob: 3b59f2082781b7ec51efed712cb075559aa4b04e [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <stdint.h>
#include <errno.h>
#include <stdbool.h>
#include "os/mynewt.h"
#include <hal/hal_timer.h>
#include <mcu/plic.h>
#include <mcu/fe310_hal.h>
#include <env/freedom-e300-hifive1/platform.h>
#define FE310_HAL_TIMER_MAX (3)
struct fe310_hal_tmr {
void *pwm_regs; /* Pointer to timer registers */
uint32_t value; /* Acumulated timer value, incremented on CMP0 */
uint8_t max_scale; /* Max value for pwmcfg.pwmscale 7 (for PMW0) or 15 (for PWM1/2) */
uint8_t pwmxcmp0_int; /* PWMxCMP0 interrupt number */
TAILQ_HEAD(hal_timer_qhead, hal_timer) sht_timers;
};
#if MYNEWT_VAL(TIMER_0)
struct fe310_hal_tmr fe310_pwm2 = {
(uint32_t *) PWM2_CTRL_ADDR, 0, 15, INT_PWM2_BASE
};
#endif
#if MYNEWT_VAL(TIMER_1)
struct fe310_hal_tmr fe310_pwm1 = {
(uint32_t *) PWM1_CTRL_ADDR, 0, 15, INT_PWM1_BASE
};
#endif
#if MYNEWT_VAL(TIMER_2)
struct fe310_hal_tmr fe310_pwm0 = {
(uint32_t *) PWM0_CTRL_ADDR, 0, 7, INT_PWM0_BASE
};
#endif
static struct fe310_hal_tmr *fe310_tmr_devs[FE310_HAL_TIMER_MAX] = {
#if MYNEWT_VAL(TIMER_0)
&fe310_pwm2,
#else
NULL,
#endif
#if MYNEWT_VAL(TIMER_1)
&fe310_pwm1,
#else
NULL,
#endif
#if MYNEWT_VAL(TIMER_2)
&fe310_pwm0,
#else
NULL,
#endif
};
static uint8_t pwm_to_timer[FE310_HAL_TIMER_MAX];
static uint32_t
hal_timer_cnt(struct fe310_hal_tmr *tmr)
{
uint32_t cnt;
int sr;
uint32_t regs = (uint32_t) tmr->pwm_regs;
__HAL_DISABLE_INTERRUPTS(sr);
cnt = _REG32(regs, PWM_S) + tmr->value;
/* Check if just overflowed */
if (_REG32(regs, PWM_CFG) & PWM_CMP0) {
cnt += _REG32(regs, PWM_CMP0) + 1;
}
__HAL_ENABLE_INTERRUPTS(sr);
return cnt;
}
static void
fe310_tmr_check_first(struct fe310_hal_tmr *tmr)
{
struct hal_timer *ht;
ht = TAILQ_FIRST(&tmr->sht_timers);
if (ht) {
uint32_t cnt = hal_timer_cnt(tmr);
int32_t ticks = (int32_t)(ht->expiry - cnt);
if (ticks < _REG32(tmr->pwm_regs, PWM_CMP0)) {
_REG32(tmr->pwm_regs, PWM_CMP1) = ticks;
plic_enable_interrupt(tmr->pwmxcmp0_int + 1);
return;
}
}
_REG32(tmr->pwm_regs, PWM_CMP1) = _REG32(tmr->pwm_regs, PWM_CMP0);
/* Disable PWMxCMP1 interrupt, leaving only CMP0 which is used all the time */
plic_disable_interrupt(tmr->pwmxcmp0_int + 1);
}
/*
* Call expired timer callbacks
*/
static void
fe310_tmr_cbs(struct fe310_hal_tmr *tmr)
{
uint32_t cnt;
struct hal_timer *ht;
while ((ht = TAILQ_FIRST(&tmr->sht_timers)) != NULL) {
cnt = hal_timer_cnt(tmr);
if (((int32_t)(cnt - ht->expiry)) >= 0) {
TAILQ_REMOVE(&tmr->sht_timers, ht, link);
ht->link.tqe_prev = NULL;
ht->cb_func(ht->cb_arg);
} else {
break;
}
}
fe310_tmr_check_first(tmr);
}
void
fe310_pwm_cmp0_handler(int num)
{
int pwm_num = (num - INT_PWM0_BASE) >> 2;
int timer_num = pwm_to_timer[pwm_num];
struct fe310_hal_tmr *tmr = fe310_tmr_devs[timer_num];
/* Turn of CMPxIP */
_REG32(tmr->pwm_regs, PWM_CFG) &= ~PWM_CFG_CMP0IP;
tmr->value += _REG32(tmr->pwm_regs, PWM_CMP0) + 1;
fe310_tmr_cbs(tmr);
}
void
fe310_pwm_cmp1_handler(int num)
{
int pwm_num = (num - INT_PWM0_BASE) >> 2;
int timer_num = pwm_to_timer[pwm_num];
struct fe310_hal_tmr *tmr = fe310_tmr_devs[timer_num];
/* Turn of CMPxIP */
_REG32(tmr->pwm_regs, PWM_CFG) &= ~PWM_CFG_CMP1IP;
fe310_tmr_cbs(tmr);
}
/**
* hal timer init
*
* Initialize platform specific timer items
*
* @param timer_num Timer number to initialize
* @param cfg Pointer to platform specific configuration
*
* @return int 0: success; error code otherwise
*/
int
hal_timer_init(int timer_num, void *cfg)
{
struct fe310_hal_tmr *tmr;
if (timer_num >= FE310_HAL_TIMER_MAX || !(tmr = fe310_tmr_devs[timer_num]) ||
(cfg == NULL)) {
return -1;
}
return 0;
}
/**
* hal timer config
*
* Configure a timer to run at the desired frequency. This starts the timer.
*
* @param timer_num
* @param freq_hz
*
* @return int
*/
int
hal_timer_config(int timer_num, uint32_t freq_hz)
{
struct fe310_hal_tmr *tmr;
uint32_t cpu_freq;
uint32_t div;
int scale = -1;
if (timer_num >= FE310_HAL_TIMER_MAX || !(tmr = fe310_tmr_devs[timer_num])) {
return -1;
}
cpu_freq = get_cpu_freq();
div = cpu_freq / freq_hz;
if (div > (1 << tmr->max_scale) || div == 0) {
return -1;
}
div += div >> 1;
while (div) {
div >>= 1;
scale++;
}
_REG32(tmr->pwm_regs, PWM_CFG) = 0;
_REG32(tmr->pwm_regs, PWM_COUNT) = 0;
/* 15 -> 0xFFFF, 7 -> 0xFF*/
_REG32(tmr->pwm_regs, PWM_CMP0) = (1 << (1 + tmr->max_scale)) - 1;
_REG32(tmr->pwm_regs, PWM_CMP1) = _REG32(tmr->pwm_regs, PWM_CMP0);
pwm_to_timer[(tmr->pwmxcmp0_int - INT_PWM0_BASE) >> 2] = (uint8_t) timer_num;
plic_set_handler(tmr->pwmxcmp0_int, fe310_pwm_cmp0_handler, 3);
plic_set_handler(tmr->pwmxcmp0_int + 1, fe310_pwm_cmp1_handler, 3);
_REG32(tmr->pwm_regs, PWM_CFG) = PWM_CFG_ZEROCMP |
PWM_CFG_ENALWAYS | scale;
plic_enable_interrupt(tmr->pwmxcmp0_int);
return 0;
}
/**
* hal timer deinit
*
* De-initialize a HW timer.
*
* @param timer_num
*
* @return int
*/
int
hal_timer_deinit(int timer_num)
{
struct fe310_hal_tmr *tmr;
int sr;
if (timer_num >= FE310_HAL_TIMER_MAX || !(tmr = fe310_tmr_devs[timer_num])) {
return -1;
}
__HAL_DISABLE_INTERRUPTS(sr);
_REG32(tmr->pwm_regs, PWM_CFG) = 0;
plic_disable_interrupt(tmr->pwmxcmp0_int);
plic_disable_interrupt(tmr->pwmxcmp0_int + 1);
__HAL_ENABLE_INTERRUPTS(sr);
return 0;
}
/**
* hal timer get resolution
*
* Get the resolution of the timer. This is the timer period, in nanoseconds
*
* @param timer_num
*
* @return uint32_t The
*/
uint32_t
hal_timer_get_resolution(int timer_num)
{
struct fe310_hal_tmr *tmr;
uint32_t cpu_freq;
if (timer_num >= FE310_HAL_TIMER_MAX || !(tmr = fe310_tmr_devs[timer_num])) {
return -1;
}
cpu_freq = get_cpu_freq();
return 1000000000 / (cpu_freq >> (_REG32(tmr->pwm_regs, PWM_CFG) & PWM_CFG_SCALE));
}
/**
* hal timer read
*
* Returns the timer counter.
*
* @return uint32_t The timer counter register.
*/
uint32_t
hal_timer_read(int timer_num)
{
struct fe310_hal_tmr *tmr;
if (timer_num >= FE310_HAL_TIMER_MAX || !(tmr = fe310_tmr_devs[timer_num])) {
return -1;
}
return hal_timer_cnt(tmr);
}
/**
* hal timer delay
*
* Blocking delay for n ticks
*
* @param timer_num
* @param ticks
*
* @return int 0 on success; error code otherwise.
*/
int
hal_timer_delay(int timer_num, uint32_t ticks)
{
struct fe310_hal_tmr *tmr;
uint32_t until;
if (timer_num >= FE310_HAL_TIMER_MAX || !(tmr = fe310_tmr_devs[timer_num])) {
return -1;
}
until = hal_timer_cnt(tmr) + ticks;
while ((int32_t)(hal_timer_cnt(tmr) - until) <= 0) {
/* Loop here till finished */
}
return 0;
}
/**
*
* Initialize the HAL timer structure with the callback and the callback
* argument. Also initializes the HW specific timer pointer.
*
* @param cb_func
*
* @return int
*/
int
hal_timer_set_cb(int timer_num, struct hal_timer *timer, hal_timer_cb cb_func,
void *arg)
{
struct fe310_hal_tmr *tmr;
if (timer_num >= FE310_HAL_TIMER_MAX || !(tmr = fe310_tmr_devs[timer_num])) {
return -1;
}
timer->cb_func = cb_func;
timer->cb_arg = arg;
timer->link.tqe_prev = NULL;
timer->bsp_timer = tmr;
return 0;
}
int
hal_timer_start(struct hal_timer *timer, uint32_t ticks)
{
struct fe310_hal_tmr *tmr;
uint32_t tick;
tmr = (struct fe310_hal_tmr *)timer->bsp_timer;
tick = ticks + hal_timer_cnt(tmr);
return hal_timer_start_at(timer, tick);
}
int
hal_timer_start_at(struct hal_timer *timer, uint32_t tick)
{
struct fe310_hal_tmr *tmr;
struct hal_timer *ht;
int sr;
bool first = true;
tmr = (struct fe310_hal_tmr *)timer->bsp_timer;
timer->expiry = tick;
__HAL_DISABLE_INTERRUPTS(sr);
if (TAILQ_EMPTY(&tmr->sht_timers)) {
TAILQ_INSERT_HEAD(&tmr->sht_timers, timer, link);
} else {
TAILQ_FOREACH(ht, &tmr->sht_timers, link) {
if ((int32_t)(timer->expiry - ht->expiry) < 0) {
TAILQ_INSERT_BEFORE(ht, timer, link);
break;
}
first = false;
}
if (!ht) {
TAILQ_INSERT_TAIL(&tmr->sht_timers, timer, link);
}
}
if (first) {
fe310_tmr_check_first(tmr);
}
__HAL_ENABLE_INTERRUPTS(sr);
return 0;
}
/**
* hal timer stop
*
* Stop a timer.
*
* @param timer
*
* @return int
*/
int
hal_timer_stop(struct hal_timer *timer)
{
struct fe310_hal_tmr *tmr;
int sr;
bool recalc = false;
__HAL_DISABLE_INTERRUPTS(sr);
tmr = (struct fe310_hal_tmr *)timer->bsp_timer;
/* Is it still in the list ? */
if (timer->link.tqe_prev != NULL) {
/* Recalculation only if removing first timer */
recalc = (timer == TAILQ_FIRST(&tmr->sht_timers));
TAILQ_REMOVE(&tmr->sht_timers, timer, link);
timer->link.tqe_prev = NULL;
if (recalc) {
fe310_tmr_check_first(tmr);
}
}
__HAL_ENABLE_INTERRUPTS(sr);
return 0;
}