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