1/*
2 * Copyright 2019, Data61
3 * Commonwealth Scientific and Industrial Research Organisation (CSIRO)
4 * ABN 41 687 119 230.
5 *
6 * This software may be distributed and modified according to the terms of
7 * the BSD 2-Clause license. Note that NO WARRANTY is provided.
8 * See "LICENSE_BSD2.txt" for details.
9 *
10 * @TAG(DATA61_BSD)
11 */
12
13#include <stdio.h>
14#include <assert.h>
15#include <errno.h>
16#include <stdlib.h>
17
18#include <utils/util.h>
19
20#include <platsupport/timer.h>
21#include <platsupport/plat/pwm.h>
22
23
24#define PWMSCALE_MASK MASK(4)
25#define PWMSTICKY BIT(8)
26#define PWMZEROCMP BIT(9)
27#define PWMENALWAYS  BIT(12)
28#define PWMENONESHOT BIT(13)
29#define PWMCMP0IP BIT(28)
30#define PWMCMP1IP BIT(29)
31#define PWMCMP2IP BIT(30)
32#define PWMCMP3IP BIT(31)
33#define PWMCMP_WIDTH 16
34#define PWMCMP_MASK MASK(PWMCMP_WIDTH)
35
36/* Largest timeout that can be programmed */
37#define MAX_TIMEOUT_NS (BIT(31) * (NS_IN_S / PWM_INPUT_FREQ))
38
39
40int pwm_start(pwm_t *pwm)
41{
42    pwm->pwm_map->pwmcfg |= PWMENALWAYS;
43    return 0;
44}
45
46int pwm_stop(pwm_t *pwm)
47{
48    /* Disable timer. */
49    pwm->pwm_map->pwmcmp0 = PWMCMP_MASK;
50    pwm->pwm_map->pwmcfg &= ~(PWMENALWAYS|PWMENONESHOT|PWMCMP0IP|PWMCMP1IP|PWMCMP2IP|PWMCMP3IP);
51    pwm->pwm_map->pwmcount = 0;
52
53    return 0;
54}
55
56int pwm_set_timeout(pwm_t *pwm, uint64_t ns, bool periodic)
57{
58    if(pwm->mode == UPCOUNTER) {
59        ZF_LOGE("pwm is in UPCOUNTER mode and doesn't support setting timeouts.");
60        return -1;
61    }
62    // Clear whatever state the timer is in.
63    pwm_stop(pwm);
64    size_t num_ticks = ns / (NS_IN_S / PWM_INPUT_FREQ);
65    if (num_ticks > MAX_TIMEOUT_NS) {
66        ZF_LOGE("Cannot program a timeout larget than %ld ns", MAX_TIMEOUT_NS);
67        return -1;
68    }
69
70    /* We calculate the prescale by dividing the number of ticks we need
71     * by the width of the comparison register. The remainder is how much
72     * we want to prescale by, however the prescale value needs to be a
73     * power of 2 so we take the log2() and then increment it by 1 if it
74     * would otherwise be too low.
75     */
76    size_t prescale = num_ticks >> (PWMCMP_WIDTH);
77    size_t base_2 = LOG_BASE_2(prescale);
78    if (BIT(base_2)< prescale) {
79        base_2++;
80    }
81    assert(prescale < BIT(PWMSCALE_MASK));
82
83    /* There will be a loss of resolution by this shift.
84     * We add 1 so that we sleep for at least as long as needed.
85     */
86    pwm->pwm_map->pwmcmp0 = (num_ticks >> base_2) +1;
87    /* assert we didn't overflow... */
88    assert((num_ticks >> base_2) +1);
89    /* Reset the counter mode and prescaler, for some reason this doesn't work in pwm_stop */
90    pwm->pwm_map->pwmcfg &= ~(PWMSCALE_MASK);
91    if (periodic) {
92        pwm->pwm_map->pwmcfg |= PWMENALWAYS | (base_2 & PWMSCALE_MASK);
93    } else {
94        pwm->pwm_map->pwmcfg |= PWMENONESHOT | (base_2 & PWMSCALE_MASK);
95    }
96
97    return 0;
98}
99
100void pwm_handle_irq(pwm_t *pwm, uint32_t irq)
101{
102    if(pwm->mode == UPCOUNTER) {
103        pwm->time_h++;
104    }
105
106    pwm->pwm_map->pwmcfg &= ~(PWMCMP0IP|PWMCMP1IP|PWMCMP2IP|PWMCMP3IP);
107}
108
109uint64_t pwm_get_time(pwm_t *pwm)
110{
111    /* Include unhandled interrupt. */
112    if (pwm->pwm_map->pwmcfg & PWMCMP0IP) {
113        pwm->time_h++;
114        pwm->pwm_map->pwmcfg &= ~PWMCMP0IP;
115    }
116
117    uint64_t num_ticks = (pwm->time_h * (PWMCMP_MASK << PWMSCALE_MASK) + pwm->pwm_map->pwmcount);
118    uint64_t time = num_ticks * (NS_IN_S / PWM_INPUT_FREQ);
119    return time;
120}
121
122int pwm_init(pwm_t *pwm, pwm_config_t config)
123{
124    pwm->pwm_map = (volatile struct pwm_map*) config.vaddr;
125    uint8_t scale = 0;
126    if (config.mode == UPCOUNTER) {
127        scale = PWMSCALE_MASK;
128    }
129    pwm->mode = config.mode;
130    pwm->pwm_map->pwmcmp0 = PWMCMP_MASK;
131    pwm->pwm_map->pwmcmp1 = PWMCMP_MASK;
132    pwm->pwm_map->pwmcmp2 = PWMCMP_MASK;
133    pwm->pwm_map->pwmcmp3 = PWMCMP_MASK;
134    pwm->pwm_map->pwmcfg =  (scale & PWMSCALE_MASK) | PWMZEROCMP | PWMSTICKY;
135
136    return 0;
137}
138