blob: aef2e5574b41450cc182b29c6965906024d7502b [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
8#include <common.h>
9#include <errno.h>
10#include <pwm.h>
11#include <asm/io.h>
12#include <asm/arch/pwm.h>
13#include <asm/arch/clk.h>
14
15int pwm_enable(int pwm_id)
16{
17 const struct s5p_timer *pwm =
Stefan Bosch34cffbf2020-07-10 19:07:31 +020018#if defined(CONFIG_ARCH_NEXELL)
19 (struct s5p_timer *)PHY_BASEADDR_PWM;
20#else
Donghwa Lee85f5df22011-03-07 21:11:42 +000021 (struct s5p_timer *)samsung_get_base_timer();
Stefan Bosch34cffbf2020-07-10 19:07:31 +020022#endif
Donghwa Lee85f5df22011-03-07 21:11:42 +000023 unsigned long tcon;
24
25 tcon = readl(&pwm->tcon);
26 tcon |= TCON_START(pwm_id);
27
28 writel(tcon, &pwm->tcon);
29
30 return 0;
31}
32
33void pwm_disable(int pwm_id)
34{
35 const struct s5p_timer *pwm =
Stefan Bosch34cffbf2020-07-10 19:07:31 +020036#if defined(CONFIG_ARCH_NEXELL)
37 (struct s5p_timer *)PHY_BASEADDR_PWM;
38#else
Donghwa Lee85f5df22011-03-07 21:11:42 +000039 (struct s5p_timer *)samsung_get_base_timer();
Stefan Bosch34cffbf2020-07-10 19:07:31 +020040#endif
Donghwa Lee85f5df22011-03-07 21:11:42 +000041 unsigned long tcon;
42
43 tcon = readl(&pwm->tcon);
44 tcon &= ~TCON_START(pwm_id);
45
46 writel(tcon, &pwm->tcon);
47}
48
49static unsigned long pwm_calc_tin(int pwm_id, unsigned long freq)
50{
51 unsigned long tin_parent_rate;
52 unsigned int div;
53
Stefan Bosch34cffbf2020-07-10 19:07:31 +020054#if defined(CONFIG_ARCH_NEXELL)
55 unsigned int pre_div;
56 const struct s5p_timer *pwm =
57 (struct s5p_timer *)PHY_BASEADDR_PWM;
58 unsigned int val;
59 struct clk *clk = clk_get(CORECLK_NAME_PCLK);
60
61 tin_parent_rate = clk_get_rate(clk);
62#else
Donghwa Lee85f5df22011-03-07 21:11:42 +000063 tin_parent_rate = get_pwm_clk();
Stefan Bosch34cffbf2020-07-10 19:07:31 +020064#endif
65
66#if defined(CONFIG_ARCH_NEXELL)
67 writel(0, &pwm->tcfg0);
68 val = readl(&pwm->tcfg0);
69
70 if (pwm_id < 2)
71 div = ((val >> 0) & 0xff) + 1;
72 else
73 div = ((val >> 8) & 0xff) + 1;
Donghwa Lee85f5df22011-03-07 21:11:42 +000074
Stefan Bosch34cffbf2020-07-10 19:07:31 +020075 writel(0, &pwm->tcfg1);
76 val = readl(&pwm->tcfg1);
77 val = (val >> MUX_DIV_SHIFT(pwm_id)) & 0xF;
78 pre_div = (1UL << val);
79
80 freq = tin_parent_rate / div / pre_div;
81
82 return freq;
83#else
Donghwa Lee85f5df22011-03-07 21:11:42 +000084 for (div = 2; div <= 16; div *= 2) {
85 if ((tin_parent_rate / (div << 16)) < freq)
86 return tin_parent_rate / div;
87 }
88
89 return tin_parent_rate / 16;
Stefan Bosch34cffbf2020-07-10 19:07:31 +020090#endif
Donghwa Lee85f5df22011-03-07 21:11:42 +000091}
92
Gabe Blackad73b912013-03-28 04:32:20 +000093#define NS_IN_SEC 1000000000UL
Donghwa Lee85f5df22011-03-07 21:11:42 +000094
95int pwm_config(int pwm_id, int duty_ns, int period_ns)
96{
97 const struct s5p_timer *pwm =
Stefan Bosch34cffbf2020-07-10 19:07:31 +020098#if defined(CONFIG_ARCH_NEXELL)
99 (struct s5p_timer *)PHY_BASEADDR_PWM;
100#else
Donghwa Lee85f5df22011-03-07 21:11:42 +0000101 (struct s5p_timer *)samsung_get_base_timer();
Stefan Bosch34cffbf2020-07-10 19:07:31 +0200102#endif
Donghwa Lee85f5df22011-03-07 21:11:42 +0000103 unsigned int offset;
104 unsigned long tin_rate;
105 unsigned long tin_ns;
Gabe Blackad73b912013-03-28 04:32:20 +0000106 unsigned long frequency;
Donghwa Lee85f5df22011-03-07 21:11:42 +0000107 unsigned long tcon;
108 unsigned long tcnt;
Donghwa Lee85f5df22011-03-07 21:11:42 +0000109 unsigned long tcmp;
110
111 /*
112 * We currently avoid using 64bit arithmetic by using the
113 * fact that anything faster than 1GHz is easily representable
114 * by 32bits.
115 */
Gabe Blackad73b912013-03-28 04:32:20 +0000116 if (period_ns > NS_IN_SEC || duty_ns > NS_IN_SEC || period_ns == 0)
Donghwa Lee85f5df22011-03-07 21:11:42 +0000117 return -ERANGE;
118
119 if (duty_ns > period_ns)
120 return -EINVAL;
121
Gabe Blackad73b912013-03-28 04:32:20 +0000122 frequency = NS_IN_SEC / period_ns;
Donghwa Lee85f5df22011-03-07 21:11:42 +0000123
124 /* Check to see if we are changing the clock rate of the PWM */
Gabe Blackad73b912013-03-28 04:32:20 +0000125 tin_rate = pwm_calc_tin(pwm_id, frequency);
Donghwa Lee85f5df22011-03-07 21:11:42 +0000126
Gabe Blackad73b912013-03-28 04:32:20 +0000127 tin_ns = NS_IN_SEC / tin_rate;
Stefan Bosch34cffbf2020-07-10 19:07:31 +0200128
129 if (IS_ENABLED(CONFIG_ARCH_NEXELL))
130 /* The counter starts at zero. */
131 tcnt = (period_ns / tin_ns) - 1;
132 else
133 tcnt = period_ns / tin_ns;
Donghwa Lee85f5df22011-03-07 21:11:42 +0000134
135 /* Note, counters count down */
136 tcmp = duty_ns / tin_ns;
137 tcmp = tcnt - tcmp;
138
Donghwa Lee85f5df22011-03-07 21:11:42 +0000139 /* Update the PWM register block. */
140 offset = pwm_id * 3;
141 if (pwm_id < 4) {
142 writel(tcnt, &pwm->tcntb0 + offset);
143 writel(tcmp, &pwm->tcmpb0 + offset);
144 }
145
146 tcon = readl(&pwm->tcon);
147 tcon |= TCON_UPDATE(pwm_id);
148 if (pwm_id < 4)
149 tcon |= TCON_AUTO_RELOAD(pwm_id);
150 else
151 tcon |= TCON4_AUTO_RELOAD;
152 writel(tcon, &pwm->tcon);
153
154 tcon &= ~TCON_UPDATE(pwm_id);
155 writel(tcon, &pwm->tcon);
156
157 return 0;
158}
159
160int pwm_init(int pwm_id, int div, int invert)
161{
162 u32 val;
163 const struct s5p_timer *pwm =
Stefan Bosch34cffbf2020-07-10 19:07:31 +0200164#if defined(CONFIG_ARCH_NEXELL)
165 (struct s5p_timer *)PHY_BASEADDR_PWM;
166#else
Donghwa Lee85f5df22011-03-07 21:11:42 +0000167 (struct s5p_timer *)samsung_get_base_timer();
Stefan Bosch34cffbf2020-07-10 19:07:31 +0200168#endif
Gabe Black4f44a3c2013-03-28 04:32:18 +0000169 unsigned long ticks_per_period;
Donghwa Lee85f5df22011-03-07 21:11:42 +0000170 unsigned int offset, prescaler;
171
172 /*
173 * Timer Freq(HZ) =
174 * PWM_CLK / { (prescaler_value + 1) * (divider_value) }
175 */
176
177 val = readl(&pwm->tcfg0);
178 if (pwm_id < 2) {
179 prescaler = PRESCALER_0;
180 val &= ~0xff;
181 val |= (prescaler & 0xff);
182 } else {
183 prescaler = PRESCALER_1;
184 val &= ~(0xff << 8);
185 val |= (prescaler & 0xff) << 8;
186 }
187 writel(val, &pwm->tcfg0);
188 val = readl(&pwm->tcfg1);
189 val &= ~(0xf << MUX_DIV_SHIFT(pwm_id));
190 val |= (div & 0xf) << MUX_DIV_SHIFT(pwm_id);
191 writel(val, &pwm->tcfg1);
192
Gabe Black4f44a3c2013-03-28 04:32:18 +0000193 if (pwm_id == 4) {
194 /*
195 * TODO(sjg): Use this as a countdown timer for now. We count
196 * down from the maximum value to 0, then reset.
197 */
198 ticks_per_period = -1UL;
199 } else {
200 const unsigned long pwm_hz = 1000;
Stefan Bosch34cffbf2020-07-10 19:07:31 +0200201#if defined(CONFIG_ARCH_NEXELL)
202 struct clk *clk = clk_get(CORECLK_NAME_PCLK);
203 unsigned long timer_rate_hz = clk_get_rate(clk) /
204#else
Gabe Black4f44a3c2013-03-28 04:32:18 +0000205 unsigned long timer_rate_hz = get_pwm_clk() /
Stefan Bosch34cffbf2020-07-10 19:07:31 +0200206#endif
Gabe Black4f44a3c2013-03-28 04:32:18 +0000207 ((prescaler + 1) * (1 << div));
Donghwa Lee85f5df22011-03-07 21:11:42 +0000208
Gabe Black4f44a3c2013-03-28 04:32:18 +0000209 ticks_per_period = timer_rate_hz / pwm_hz;
210 }
Donghwa Lee85f5df22011-03-07 21:11:42 +0000211
212 /* set count value */
213 offset = pwm_id * 3;
Simon Glassa9e9abb2013-03-28 04:32:16 +0000214
Gabe Black4f44a3c2013-03-28 04:32:18 +0000215 writel(ticks_per_period, &pwm->tcntb0 + offset);
Donghwa Lee85f5df22011-03-07 21:11:42 +0000216
217 val = readl(&pwm->tcon) & ~(0xf << TCON_OFFSET(pwm_id));
218 if (invert && (pwm_id < 4))
219 val |= TCON_INVERTER(pwm_id);
220 writel(val, &pwm->tcon);
221
222 pwm_enable(pwm_id);
223
224 return 0;
225}