blob: 986b585b70e92a0e8a9a7af015bf68f7677fc63a [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001// SPDX-License-Identifier: GPL-2.0+
Donghwa Lee85f5df22011-03-07 21:11:42 +00002/*
3 * Copyright (C) 2011 Samsung Electronics
4 *
5 * Donghwa Lee <dh09.lee@samsung.com>
Donghwa Lee85f5df22011-03-07 21:11:42 +00006 */
7
Tom Rinidec7ea02024-05-20 13:35:03 -06008#include <config.h>
Donghwa Lee85f5df22011-03-07 21:11:42 +00009#include <errno.h>
Donghwa Lee85f5df22011-03-07 21:11:42 +000010#include <asm/io.h>
11#include <asm/arch/pwm.h>
12#include <asm/arch/clk.h>
13
Tom Rini66faa432022-12-04 10:03:26 -050014int s5p_pwm_enable(int pwm_id)
Donghwa Lee85f5df22011-03-07 21:11:42 +000015{
16 const struct s5p_timer *pwm =
Stefan Bosch34cffbf2020-07-10 19:07:31 +020017#if defined(CONFIG_ARCH_NEXELL)
18 (struct s5p_timer *)PHY_BASEADDR_PWM;
19#else
Donghwa Lee85f5df22011-03-07 21:11:42 +000020 (struct s5p_timer *)samsung_get_base_timer();
Stefan Bosch34cffbf2020-07-10 19:07:31 +020021#endif
Donghwa Lee85f5df22011-03-07 21:11:42 +000022 unsigned long tcon;
23
24 tcon = readl(&pwm->tcon);
25 tcon |= TCON_START(pwm_id);
26
27 writel(tcon, &pwm->tcon);
28
29 return 0;
30}
31
Tom Rini66faa432022-12-04 10:03:26 -050032void s5p_pwm_disable(int pwm_id)
Donghwa Lee85f5df22011-03-07 21:11:42 +000033{
34 const struct s5p_timer *pwm =
Stefan Bosch34cffbf2020-07-10 19:07:31 +020035#if defined(CONFIG_ARCH_NEXELL)
36 (struct s5p_timer *)PHY_BASEADDR_PWM;
37#else
Donghwa Lee85f5df22011-03-07 21:11:42 +000038 (struct s5p_timer *)samsung_get_base_timer();
Stefan Bosch34cffbf2020-07-10 19:07:31 +020039#endif
Donghwa Lee85f5df22011-03-07 21:11:42 +000040 unsigned long tcon;
41
42 tcon = readl(&pwm->tcon);
43 tcon &= ~TCON_START(pwm_id);
44
45 writel(tcon, &pwm->tcon);
46}
47
48static unsigned long pwm_calc_tin(int pwm_id, unsigned long freq)
49{
50 unsigned long tin_parent_rate;
51 unsigned int div;
52
Stefan Bosch34cffbf2020-07-10 19:07:31 +020053#if defined(CONFIG_ARCH_NEXELL)
54 unsigned int pre_div;
55 const struct s5p_timer *pwm =
56 (struct s5p_timer *)PHY_BASEADDR_PWM;
57 unsigned int val;
58 struct clk *clk = clk_get(CORECLK_NAME_PCLK);
59
60 tin_parent_rate = clk_get_rate(clk);
61#else
Donghwa Lee85f5df22011-03-07 21:11:42 +000062 tin_parent_rate = get_pwm_clk();
Stefan Bosch34cffbf2020-07-10 19:07:31 +020063#endif
64
65#if defined(CONFIG_ARCH_NEXELL)
66 writel(0, &pwm->tcfg0);
67 val = readl(&pwm->tcfg0);
68
69 if (pwm_id < 2)
70 div = ((val >> 0) & 0xff) + 1;
71 else
72 div = ((val >> 8) & 0xff) + 1;
Donghwa Lee85f5df22011-03-07 21:11:42 +000073
Stefan Bosch34cffbf2020-07-10 19:07:31 +020074 writel(0, &pwm->tcfg1);
75 val = readl(&pwm->tcfg1);
76 val = (val >> MUX_DIV_SHIFT(pwm_id)) & 0xF;
77 pre_div = (1UL << val);
78
79 freq = tin_parent_rate / div / pre_div;
80
81 return freq;
82#else
Donghwa Lee85f5df22011-03-07 21:11:42 +000083 for (div = 2; div <= 16; div *= 2) {
84 if ((tin_parent_rate / (div << 16)) < freq)
85 return tin_parent_rate / div;
86 }
87
88 return tin_parent_rate / 16;
Stefan Bosch34cffbf2020-07-10 19:07:31 +020089#endif
Donghwa Lee85f5df22011-03-07 21:11:42 +000090}
91
Gabe Blackad73b912013-03-28 04:32:20 +000092#define NS_IN_SEC 1000000000UL
Donghwa Lee85f5df22011-03-07 21:11:42 +000093
Tom Rini66faa432022-12-04 10:03:26 -050094int s5p_pwm_config(int pwm_id, int duty_ns, int period_ns)
Donghwa Lee85f5df22011-03-07 21:11:42 +000095{
96 const struct s5p_timer *pwm =
Stefan Bosch34cffbf2020-07-10 19:07:31 +020097#if defined(CONFIG_ARCH_NEXELL)
98 (struct s5p_timer *)PHY_BASEADDR_PWM;
99#else
Donghwa Lee85f5df22011-03-07 21:11:42 +0000100 (struct s5p_timer *)samsung_get_base_timer();
Stefan Bosch34cffbf2020-07-10 19:07:31 +0200101#endif
Donghwa Lee85f5df22011-03-07 21:11:42 +0000102 unsigned int offset;
103 unsigned long tin_rate;
104 unsigned long tin_ns;
Gabe Blackad73b912013-03-28 04:32:20 +0000105 unsigned long frequency;
Donghwa Lee85f5df22011-03-07 21:11:42 +0000106 unsigned long tcon;
107 unsigned long tcnt;
Donghwa Lee85f5df22011-03-07 21:11:42 +0000108 unsigned long tcmp;
109
110 /*
111 * We currently avoid using 64bit arithmetic by using the
112 * fact that anything faster than 1GHz is easily representable
113 * by 32bits.
114 */
Gabe Blackad73b912013-03-28 04:32:20 +0000115 if (period_ns > NS_IN_SEC || duty_ns > NS_IN_SEC || period_ns == 0)
Donghwa Lee85f5df22011-03-07 21:11:42 +0000116 return -ERANGE;
117
118 if (duty_ns > period_ns)
119 return -EINVAL;
120
Gabe Blackad73b912013-03-28 04:32:20 +0000121 frequency = NS_IN_SEC / period_ns;
Donghwa Lee85f5df22011-03-07 21:11:42 +0000122
123 /* Check to see if we are changing the clock rate of the PWM */
Gabe Blackad73b912013-03-28 04:32:20 +0000124 tin_rate = pwm_calc_tin(pwm_id, frequency);
Donghwa Lee85f5df22011-03-07 21:11:42 +0000125
Gabe Blackad73b912013-03-28 04:32:20 +0000126 tin_ns = NS_IN_SEC / tin_rate;
Stefan Bosch34cffbf2020-07-10 19:07:31 +0200127
128 if (IS_ENABLED(CONFIG_ARCH_NEXELL))
129 /* The counter starts at zero. */
130 tcnt = (period_ns / tin_ns) - 1;
131 else
132 tcnt = period_ns / tin_ns;
Donghwa Lee85f5df22011-03-07 21:11:42 +0000133
134 /* Note, counters count down */
135 tcmp = duty_ns / tin_ns;
136 tcmp = tcnt - tcmp;
137
Donghwa Lee85f5df22011-03-07 21:11:42 +0000138 /* Update the PWM register block. */
139 offset = pwm_id * 3;
140 if (pwm_id < 4) {
141 writel(tcnt, &pwm->tcntb0 + offset);
142 writel(tcmp, &pwm->tcmpb0 + offset);
143 }
144
145 tcon = readl(&pwm->tcon);
146 tcon |= TCON_UPDATE(pwm_id);
147 if (pwm_id < 4)
148 tcon |= TCON_AUTO_RELOAD(pwm_id);
149 else
150 tcon |= TCON4_AUTO_RELOAD;
151 writel(tcon, &pwm->tcon);
152
153 tcon &= ~TCON_UPDATE(pwm_id);
154 writel(tcon, &pwm->tcon);
155
156 return 0;
157}
158
Tom Rini66faa432022-12-04 10:03:26 -0500159int s5p_pwm_init(int pwm_id, int div, int invert)
Donghwa Lee85f5df22011-03-07 21:11:42 +0000160{
161 u32 val;
162 const struct s5p_timer *pwm =
Stefan Bosch34cffbf2020-07-10 19:07:31 +0200163#if defined(CONFIG_ARCH_NEXELL)
164 (struct s5p_timer *)PHY_BASEADDR_PWM;
165#else
Donghwa Lee85f5df22011-03-07 21:11:42 +0000166 (struct s5p_timer *)samsung_get_base_timer();
Stefan Bosch34cffbf2020-07-10 19:07:31 +0200167#endif
Gabe Black4f44a3c2013-03-28 04:32:18 +0000168 unsigned long ticks_per_period;
Donghwa Lee85f5df22011-03-07 21:11:42 +0000169 unsigned int offset, prescaler;
170
171 /*
172 * Timer Freq(HZ) =
173 * PWM_CLK / { (prescaler_value + 1) * (divider_value) }
174 */
175
176 val = readl(&pwm->tcfg0);
177 if (pwm_id < 2) {
178 prescaler = PRESCALER_0;
179 val &= ~0xff;
180 val |= (prescaler & 0xff);
181 } else {
182 prescaler = PRESCALER_1;
183 val &= ~(0xff << 8);
184 val |= (prescaler & 0xff) << 8;
185 }
186 writel(val, &pwm->tcfg0);
187 val = readl(&pwm->tcfg1);
188 val &= ~(0xf << MUX_DIV_SHIFT(pwm_id));
189 val |= (div & 0xf) << MUX_DIV_SHIFT(pwm_id);
190 writel(val, &pwm->tcfg1);
191
Gabe Black4f44a3c2013-03-28 04:32:18 +0000192 if (pwm_id == 4) {
193 /*
194 * TODO(sjg): Use this as a countdown timer for now. We count
195 * down from the maximum value to 0, then reset.
196 */
197 ticks_per_period = -1UL;
198 } else {
199 const unsigned long pwm_hz = 1000;
Stefan Bosch34cffbf2020-07-10 19:07:31 +0200200#if defined(CONFIG_ARCH_NEXELL)
201 struct clk *clk = clk_get(CORECLK_NAME_PCLK);
202 unsigned long timer_rate_hz = clk_get_rate(clk) /
203#else
Gabe Black4f44a3c2013-03-28 04:32:18 +0000204 unsigned long timer_rate_hz = get_pwm_clk() /
Stefan Bosch34cffbf2020-07-10 19:07:31 +0200205#endif
Gabe Black4f44a3c2013-03-28 04:32:18 +0000206 ((prescaler + 1) * (1 << div));
Donghwa Lee85f5df22011-03-07 21:11:42 +0000207
Gabe Black4f44a3c2013-03-28 04:32:18 +0000208 ticks_per_period = timer_rate_hz / pwm_hz;
209 }
Donghwa Lee85f5df22011-03-07 21:11:42 +0000210
211 /* set count value */
212 offset = pwm_id * 3;
Simon Glassa9e9abb2013-03-28 04:32:16 +0000213
Gabe Black4f44a3c2013-03-28 04:32:18 +0000214 writel(ticks_per_period, &pwm->tcntb0 + offset);
Donghwa Lee85f5df22011-03-07 21:11:42 +0000215
216 val = readl(&pwm->tcon) & ~(0xf << TCON_OFFSET(pwm_id));
217 if (invert && (pwm_id < 4))
218 val |= TCON_INVERTER(pwm_id);
219 writel(val, &pwm->tcon);
220
Tom Rini66faa432022-12-04 10:03:26 -0500221 s5p_pwm_enable(pwm_id);
Donghwa Lee85f5df22011-03-07 21:11:42 +0000222
223 return 0;
224}