blob: 464be6298180030db176d7a9dc5bf3bfc3b35418 [file] [log] [blame]
/*
* Copyright 2019, Data61, CSIRO (ABN 41 687 119 230)
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <stdio.h>
#include <assert.h>
#include <errno.h>
#include <stdlib.h>
#include <utils/util.h>
#include <platsupport/timer.h>
#include <platsupport/plat/pwm.h>
#define PWMSCALE_MASK MASK(4)
#define PWMSTICKY BIT(8)
#define PWMZEROCMP BIT(9)
#define PWMENALWAYS BIT(12)
#define PWMENONESHOT BIT(13)
#define PWMCMP0IP BIT(28)
#define PWMCMP1IP BIT(29)
#define PWMCMP2IP BIT(30)
#define PWMCMP3IP BIT(31)
#define PWMCMP_WIDTH 16
#define PWMCMP_MASK MASK(PWMCMP_WIDTH)
/* Largest timeout that can be programmed */
#define MAX_TIMEOUT_NS (BIT(31) * (NS_IN_S / PWM_INPUT_FREQ))
int pwm_start(pwm_t *pwm)
{
pwm->pwm_map->pwmcfg |= PWMENALWAYS;
return 0;
}
int pwm_stop(pwm_t *pwm)
{
/* Disable timer. */
pwm->pwm_map->pwmcmp0 = PWMCMP_MASK;
pwm->pwm_map->pwmcfg &= ~(PWMENALWAYS|PWMENONESHOT|PWMCMP0IP|PWMCMP1IP|PWMCMP2IP|PWMCMP3IP);
pwm->pwm_map->pwmcount = 0;
return 0;
}
int pwm_set_timeout(pwm_t *pwm, uint64_t ns, bool periodic)
{
if(pwm->mode == UPCOUNTER) {
ZF_LOGE("pwm is in UPCOUNTER mode and doesn't support setting timeouts.");
return -1;
}
// Clear whatever state the timer is in.
pwm_stop(pwm);
size_t num_ticks = ns / (NS_IN_S / PWM_INPUT_FREQ);
if (num_ticks > MAX_TIMEOUT_NS) {
ZF_LOGE("Cannot program a timeout larget than %ld ns", MAX_TIMEOUT_NS);
return -1;
}
/* We calculate the prescale by dividing the number of ticks we need
* by the width of the comparison register. The remainder is how much
* we want to prescale by, however the prescale value needs to be a
* power of 2 so we take the log2() and then increment it by 1 if it
* would otherwise be too low.
*/
size_t prescale = num_ticks >> (PWMCMP_WIDTH);
size_t base_2 = LOG_BASE_2(prescale);
if (BIT(base_2)< prescale) {
base_2++;
}
assert(prescale < BIT(PWMSCALE_MASK));
/* There will be a loss of resolution by this shift.
* We add 1 so that we sleep for at least as long as needed.
*/
pwm->pwm_map->pwmcmp0 = (num_ticks >> base_2) +1;
/* assert we didn't overflow... */
assert((num_ticks >> base_2) +1);
/* Reset the counter mode and prescaler, for some reason this doesn't work in pwm_stop */
pwm->pwm_map->pwmcfg &= ~(PWMSCALE_MASK);
if (periodic) {
pwm->pwm_map->pwmcfg |= PWMENALWAYS | (base_2 & PWMSCALE_MASK);
} else {
pwm->pwm_map->pwmcfg |= PWMENONESHOT | (base_2 & PWMSCALE_MASK);
}
return 0;
}
void pwm_handle_irq(pwm_t *pwm, uint32_t irq)
{
if(pwm->mode == UPCOUNTER) {
pwm->time_h++;
}
pwm->pwm_map->pwmcfg &= ~(PWMCMP0IP|PWMCMP1IP|PWMCMP2IP|PWMCMP3IP);
}
uint64_t pwm_get_time(pwm_t *pwm)
{
/* Include unhandled interrupt. */
if (pwm->pwm_map->pwmcfg & PWMCMP0IP) {
pwm->time_h++;
pwm->pwm_map->pwmcfg &= ~PWMCMP0IP;
}
uint64_t num_ticks = (pwm->time_h * (PWMCMP_MASK << PWMSCALE_MASK) + pwm->pwm_map->pwmcount);
uint64_t time = num_ticks * (NS_IN_S / PWM_INPUT_FREQ);
return time;
}
int pwm_init(pwm_t *pwm, pwm_config_t config)
{
pwm->pwm_map = (volatile struct pwm_map*) config.vaddr;
uint8_t scale = 0;
if (config.mode == UPCOUNTER) {
scale = PWMSCALE_MASK;
}
pwm->mode = config.mode;
pwm->pwm_map->pwmcmp0 = PWMCMP_MASK;
pwm->pwm_map->pwmcmp1 = PWMCMP_MASK;
pwm->pwm_map->pwmcmp2 = PWMCMP_MASK;
pwm->pwm_map->pwmcmp3 = PWMCMP_MASK;
pwm->pwm_map->pwmcfg = (scale & PWMSCALE_MASK) | PWMZEROCMP | PWMSTICKY;
return 0;
}