Prepare v2023.10
[platform/kernel/u-boot.git] / arch / arm / cpu / armv7 / s5p-common / pwm.c
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Copyright (C) 2011 Samsung Electronics
4  *
5  * Donghwa Lee <dh09.lee@samsung.com>
6  */
7
8 #include <common.h>
9 #include <errno.h>
10 #include <asm/io.h>
11 #include <asm/arch/pwm.h>
12 #include <asm/arch/clk.h>
13
14 int s5p_pwm_enable(int pwm_id)
15 {
16         const struct s5p_timer *pwm =
17 #if defined(CONFIG_ARCH_NEXELL)
18                         (struct s5p_timer *)PHY_BASEADDR_PWM;
19 #else
20                         (struct s5p_timer *)samsung_get_base_timer();
21 #endif
22         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
32 void s5p_pwm_disable(int pwm_id)
33 {
34         const struct s5p_timer *pwm =
35 #if defined(CONFIG_ARCH_NEXELL)
36                         (struct s5p_timer *)PHY_BASEADDR_PWM;
37 #else
38                         (struct s5p_timer *)samsung_get_base_timer();
39 #endif
40         unsigned long tcon;
41
42         tcon = readl(&pwm->tcon);
43         tcon &= ~TCON_START(pwm_id);
44
45         writel(tcon, &pwm->tcon);
46 }
47
48 static unsigned long pwm_calc_tin(int pwm_id, unsigned long freq)
49 {
50         unsigned long tin_parent_rate;
51         unsigned int div;
52
53 #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
62         tin_parent_rate = get_pwm_clk();
63 #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;
73
74         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
83         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;
89 #endif
90 }
91
92 #define NS_IN_SEC 1000000000UL
93
94 int s5p_pwm_config(int pwm_id, int duty_ns, int period_ns)
95 {
96         const struct s5p_timer *pwm =
97 #if defined(CONFIG_ARCH_NEXELL)
98                         (struct s5p_timer *)PHY_BASEADDR_PWM;
99 #else
100                         (struct s5p_timer *)samsung_get_base_timer();
101 #endif
102         unsigned int offset;
103         unsigned long tin_rate;
104         unsigned long tin_ns;
105         unsigned long frequency;
106         unsigned long tcon;
107         unsigned long tcnt;
108         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          */
115         if (period_ns > NS_IN_SEC || duty_ns > NS_IN_SEC || period_ns == 0)
116                 return -ERANGE;
117
118         if (duty_ns > period_ns)
119                 return -EINVAL;
120
121         frequency = NS_IN_SEC / period_ns;
122
123         /* Check to see if we are changing the clock rate of the PWM */
124         tin_rate = pwm_calc_tin(pwm_id, frequency);
125
126         tin_ns = NS_IN_SEC / tin_rate;
127
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;
133
134         /* Note, counters count down */
135         tcmp = duty_ns / tin_ns;
136         tcmp = tcnt - tcmp;
137
138         /* 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
159 int s5p_pwm_init(int pwm_id, int div, int invert)
160 {
161         u32 val;
162         const struct s5p_timer *pwm =
163 #if defined(CONFIG_ARCH_NEXELL)
164                         (struct s5p_timer *)PHY_BASEADDR_PWM;
165 #else
166                         (struct s5p_timer *)samsung_get_base_timer();
167 #endif
168         unsigned long ticks_per_period;
169         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
192         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;
200 #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
204                 unsigned long timer_rate_hz = get_pwm_clk() /
205 #endif
206                         ((prescaler + 1) * (1 << div));
207
208                 ticks_per_period = timer_rate_hz / pwm_hz;
209         }
210
211         /* set count value */
212         offset = pwm_id * 3;
213
214         writel(ticks_per_period, &pwm->tcntb0 + offset);
215
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
221         s5p_pwm_enable(pwm_id);
222
223         return 0;
224 }