]> git.sur5r.net Git - u-boot/blob - arch/arm/cpu/armv7/s5p-common/pwm.c
SPDX: Convert all of our single license tags to Linux Kernel style
[u-boot] / 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 <pwm.h>
11 #include <asm/io.h>
12 #include <asm/arch/pwm.h>
13 #include <asm/arch/clk.h>
14
15 int pwm_enable(int pwm_id)
16 {
17         const struct s5p_timer *pwm =
18                         (struct s5p_timer *)samsung_get_base_timer();
19         unsigned long tcon;
20
21         tcon = readl(&pwm->tcon);
22         tcon |= TCON_START(pwm_id);
23
24         writel(tcon, &pwm->tcon);
25
26         return 0;
27 }
28
29 void pwm_disable(int pwm_id)
30 {
31         const struct s5p_timer *pwm =
32                         (struct s5p_timer *)samsung_get_base_timer();
33         unsigned long tcon;
34
35         tcon = readl(&pwm->tcon);
36         tcon &= ~TCON_START(pwm_id);
37
38         writel(tcon, &pwm->tcon);
39 }
40
41 static unsigned long pwm_calc_tin(int pwm_id, unsigned long freq)
42 {
43         unsigned long tin_parent_rate;
44         unsigned int div;
45
46         tin_parent_rate = get_pwm_clk();
47
48         for (div = 2; div <= 16; div *= 2) {
49                 if ((tin_parent_rate / (div << 16)) < freq)
50                         return tin_parent_rate / div;
51         }
52
53         return tin_parent_rate / 16;
54 }
55
56 #define NS_IN_SEC 1000000000UL
57
58 int pwm_config(int pwm_id, int duty_ns, int period_ns)
59 {
60         const struct s5p_timer *pwm =
61                         (struct s5p_timer *)samsung_get_base_timer();
62         unsigned int offset;
63         unsigned long tin_rate;
64         unsigned long tin_ns;
65         unsigned long frequency;
66         unsigned long tcon;
67         unsigned long tcnt;
68         unsigned long tcmp;
69
70         /*
71          * We currently avoid using 64bit arithmetic by using the
72          * fact that anything faster than 1GHz is easily representable
73          * by 32bits.
74          */
75         if (period_ns > NS_IN_SEC || duty_ns > NS_IN_SEC || period_ns == 0)
76                 return -ERANGE;
77
78         if (duty_ns > period_ns)
79                 return -EINVAL;
80
81         frequency = NS_IN_SEC / period_ns;
82
83         /* Check to see if we are changing the clock rate of the PWM */
84         tin_rate = pwm_calc_tin(pwm_id, frequency);
85
86         tin_ns = NS_IN_SEC / tin_rate;
87         tcnt = period_ns / tin_ns;
88
89         /* Note, counters count down */
90         tcmp = duty_ns / tin_ns;
91         tcmp = tcnt - tcmp;
92
93         /* Update the PWM register block. */
94         offset = pwm_id * 3;
95         if (pwm_id < 4) {
96                 writel(tcnt, &pwm->tcntb0 + offset);
97                 writel(tcmp, &pwm->tcmpb0 + offset);
98         }
99
100         tcon = readl(&pwm->tcon);
101         tcon |= TCON_UPDATE(pwm_id);
102         if (pwm_id < 4)
103                 tcon |= TCON_AUTO_RELOAD(pwm_id);
104         else
105                 tcon |= TCON4_AUTO_RELOAD;
106         writel(tcon, &pwm->tcon);
107
108         tcon &= ~TCON_UPDATE(pwm_id);
109         writel(tcon, &pwm->tcon);
110
111         return 0;
112 }
113
114 int pwm_init(int pwm_id, int div, int invert)
115 {
116         u32 val;
117         const struct s5p_timer *pwm =
118                         (struct s5p_timer *)samsung_get_base_timer();
119         unsigned long ticks_per_period;
120         unsigned int offset, prescaler;
121
122         /*
123          * Timer Freq(HZ) =
124          *      PWM_CLK / { (prescaler_value + 1) * (divider_value) }
125          */
126
127         val = readl(&pwm->tcfg0);
128         if (pwm_id < 2) {
129                 prescaler = PRESCALER_0;
130                 val &= ~0xff;
131                 val |= (prescaler & 0xff);
132         } else {
133                 prescaler = PRESCALER_1;
134                 val &= ~(0xff << 8);
135                 val |= (prescaler & 0xff) << 8;
136         }
137         writel(val, &pwm->tcfg0);
138         val = readl(&pwm->tcfg1);
139         val &= ~(0xf << MUX_DIV_SHIFT(pwm_id));
140         val |= (div & 0xf) << MUX_DIV_SHIFT(pwm_id);
141         writel(val, &pwm->tcfg1);
142
143         if (pwm_id == 4) {
144                 /*
145                  * TODO(sjg): Use this as a countdown timer for now. We count
146                  * down from the maximum value to 0, then reset.
147                  */
148                 ticks_per_period = -1UL;
149         } else {
150                 const unsigned long pwm_hz = 1000;
151                 unsigned long timer_rate_hz = get_pwm_clk() /
152                         ((prescaler + 1) * (1 << div));
153
154                 ticks_per_period = timer_rate_hz / pwm_hz;
155         }
156
157         /* set count value */
158         offset = pwm_id * 3;
159
160         writel(ticks_per_period, &pwm->tcntb0 + offset);
161
162         val = readl(&pwm->tcon) & ~(0xf << TCON_OFFSET(pwm_id));
163         if (invert && (pwm_id < 4))
164                 val |= TCON_INVERTER(pwm_id);
165         writel(val, &pwm->tcon);
166
167         pwm_enable(pwm_id);
168
169         return 0;
170 }