summaryrefslogtreecommitdiff
path: root/firmware/target/mips/ingenic_x1000/pwm-x1000.c
diff options
context:
space:
mode:
Diffstat (limited to 'firmware/target/mips/ingenic_x1000/pwm-x1000.c')
-rw-r--r--firmware/target/mips/ingenic_x1000/pwm-x1000.c170
1 files changed, 170 insertions, 0 deletions
diff --git a/firmware/target/mips/ingenic_x1000/pwm-x1000.c b/firmware/target/mips/ingenic_x1000/pwm-x1000.c
new file mode 100644
index 0000000000..37d2856c1a
--- /dev/null
+++ b/firmware/target/mips/ingenic_x1000/pwm-x1000.c
@@ -0,0 +1,170 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2021 Aidan MacDonald
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include "pwm-x1000.h"
23#include "clk-x1000.h"
24#include "gpio-x1000.h"
25#include "system.h"
26#include "kernel.h"
27#include "x1000/tcu.h"
28
29struct pwm_gpio_data {
30 int port;
31 unsigned pin;
32 int func;
33};
34
35struct pwm_state {
36 struct pwm_gpio_data gpio;
37 int period_ns;
38 int duty_ns;
39 int full_ticks;
40 int half_ticks;
41 int prescaler;
42};
43
44static struct pwm_state pwm_state[] = {
45 {{GPIO_C, 1 << 25, GPIO_DEVICE(0)}, -1, -1, -1, -1, -1},
46 {{GPIO_C, 1 << 26, GPIO_DEVICE(1)}, -1, -1, -1, -1, -1},
47 {{GPIO_C, 1 << 27, GPIO_DEVICE(1)}, -1, -1, -1, -1, -1},
48 {{GPIO_B, 1 << 6, GPIO_DEVICE(2)}, -1, -1, -1, -1, -1},
49 {{GPIO_C, 1 << 24, GPIO_DEVICE(0)}, -1, -1, -1, -1, -1},
50};
51
52void pwm_init(int chn)
53{
54 /* clear cached state */
55 struct pwm_state* st = &pwm_state[chn];
56 st->period_ns = -1;
57 st->duty_ns = -1;
58 st->full_ticks = -1;
59 st->prescaler = -1;
60 st->prescaler = -1;
61
62 /* clear GPIO and disable timer */
63 gpio_config(st->gpio.port, st->gpio.pin, GPIO_OUTPUT(0));
64 jz_clr(TCU_STOP, 1 << chn);
65 jz_clr(TCU_ENABLE, 1 << chn);
66 jz_set(TCU_STOP, 1 << chn);
67}
68
69void pwm_set_period(int chn, int period_ns, int duty_ns)
70{
71 struct pwm_state* st = &pwm_state[chn];
72 unsigned long long tmp;
73 int full_ticks = st->full_ticks;
74 int half_ticks = st->half_ticks;
75 int prescaler = st->prescaler;
76
77 if(period_ns != st->period_ns) {
78 /* calculate full tick period and prescaler */
79 tmp = clk_get(X1000_CLK_PCLK) / 1000000;
80 tmp *= period_ns;
81 tmp /= 1000;
82
83 prescaler = 0;
84 while(tmp > 0xffff && prescaler < 5) {
85 tmp /= 4;
86 prescaler += 1;
87 }
88
89 full_ticks = (tmp > 0xffff) ? 0xffff : tmp;
90 st->period_ns = period_ns;
91 }
92
93 if(duty_ns != st->duty_ns) {
94 /* calculate half tick value */
95 tmp = full_ticks;
96 tmp *= duty_ns;
97 tmp /= period_ns;
98
99 half_ticks = (tmp > 0xffff) ? 0xffff : tmp;
100 if(half_ticks >= full_ticks)
101 half_ticks = full_ticks - 1;
102 st->duty_ns = duty_ns;
103 }
104
105 /* need to clear STOP bit to access timer unit registers */
106 int was_stopped = !!(jz_read(TCU_STOP) & (1 << chn));
107 if(was_stopped)
108 jz_clr(TCU_STOP, 1 << chn);
109
110 /* check if timer is currently running */
111 int was_enabled = !!(jz_read(TCU_ENABLE) & (1 << chn));
112 int enabled = was_enabled;
113
114 if(prescaler != st->prescaler) {
115 /* must disable timer to change these settings */
116 if(was_enabled) {
117 jz_clr(TCU_ENABLE, 1 << chn);
118 enabled = 0;
119 }
120
121 jz_overwritef(TCU_CTRL(chn), SHUTDOWN_V(GRACEFUL), INIT_LVL(0),
122 PWM_EN(1), PRESCALE(prescaler), SOURCE_V(PCLK));
123 REG_TCU_COUNT(chn) = 0;
124 st->prescaler = prescaler;
125 }
126
127 if(full_ticks != st->full_ticks || half_ticks != st->half_ticks) {
128 if(enabled) {
129 /* avoid changing PWM settings in the middle of a cycle */
130 unsigned cmp = REG_TCU_CMP_FULL(chn) - 1;
131 long deadline = current_tick + 3;
132 while(REG_TCU_COUNT(chn) < cmp
133 && TIME_BEFORE(current_tick, deadline));
134 }
135
136 /* these can be changed while the timer is running */
137 REG_TCU_CMP_FULL(chn) = full_ticks;
138 REG_TCU_CMP_HALF(chn) = full_ticks - half_ticks;
139 st->full_ticks = full_ticks;
140 st->half_ticks = half_ticks;
141 }
142
143 /* restore the enable/stop state */
144 if(was_enabled && !enabled)
145 jz_set(TCU_ENABLE, 1 << chn);
146 if(was_stopped)
147 jz_set(TCU_STOP, 1 << chn);
148}
149
150void pwm_enable(int chn)
151{
152 /* Start timer */
153 jz_clr(TCU_STOP, 1 << chn);
154 jz_set(TCU_ENABLE, 1 << chn);
155
156 /* Configure GPIO function */
157 struct pwm_state* st = &pwm_state[chn];
158 gpio_config(st->gpio.port, st->gpio.pin, st->gpio.func);
159}
160
161void pwm_disable(int chn)
162{
163 /* Set GPIO to output 0 */
164 struct pwm_state* st = &pwm_state[chn];
165 gpio_config(st->gpio.port, st->gpio.pin, GPIO_OUTPUT(0));
166
167 /* Stop timer */
168 jz_clr(TCU_ENABLE, 1 << chn);
169 jz_set(TCU_STOP, 1 << chn);
170}