blob: b8805c8d4303afaa28c30f62ec606c1ea4f0a12d [file] [log] [blame]
Donghwa Lee85f5df22011-03-07 21:11:42 +00001/*
2 * Copyright (C) 2011 Samsung Electronics
3 *
4 * Donghwa Lee <dh09.lee@samsung.com>
5 *
Wolfgang Denkbd8ec7e2013-10-07 13:07:26 +02006 * SPDX-License-Identifier: GPL-2.0+
Donghwa Lee85f5df22011-03-07 21:11:42 +00007 */
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
16int 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
30void 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
42static 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
Gabe Blackad73b912013-03-28 04:32:20 +000057#define NS_IN_SEC 1000000000UL
Donghwa Lee85f5df22011-03-07 21:11:42 +000058
59int 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;
Gabe Blackad73b912013-03-28 04:32:20 +000066 unsigned long frequency;
Donghwa Lee85f5df22011-03-07 21:11:42 +000067 unsigned long tcon;
68 unsigned long tcnt;
Donghwa Lee85f5df22011-03-07 21:11:42 +000069 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 */
Gabe Blackad73b912013-03-28 04:32:20 +000076 if (period_ns > NS_IN_SEC || duty_ns > NS_IN_SEC || period_ns == 0)
Donghwa Lee85f5df22011-03-07 21:11:42 +000077 return -ERANGE;
78
79 if (duty_ns > period_ns)
80 return -EINVAL;
81
Gabe Blackad73b912013-03-28 04:32:20 +000082 frequency = NS_IN_SEC / period_ns;
Donghwa Lee85f5df22011-03-07 21:11:42 +000083
84 /* Check to see if we are changing the clock rate of the PWM */
Gabe Blackad73b912013-03-28 04:32:20 +000085 tin_rate = pwm_calc_tin(pwm_id, frequency);
Donghwa Lee85f5df22011-03-07 21:11:42 +000086
Gabe Blackad73b912013-03-28 04:32:20 +000087 tin_ns = NS_IN_SEC / tin_rate;
Donghwa Lee85f5df22011-03-07 21:11:42 +000088 tcnt = period_ns / tin_ns;
89
90 /* Note, counters count down */
91 tcmp = duty_ns / tin_ns;
92 tcmp = tcnt - tcmp;
93
Donghwa Lee85f5df22011-03-07 21:11:42 +000094 /* 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
115int 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();
Gabe Black4f44a3c2013-03-28 04:32:18 +0000120 unsigned long ticks_per_period;
Donghwa Lee85f5df22011-03-07 21:11:42 +0000121 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
Gabe Black4f44a3c2013-03-28 04:32:18 +0000144 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));
Donghwa Lee85f5df22011-03-07 21:11:42 +0000154
Gabe Black4f44a3c2013-03-28 04:32:18 +0000155 ticks_per_period = timer_rate_hz / pwm_hz;
156 }
Donghwa Lee85f5df22011-03-07 21:11:42 +0000157
158 /* set count value */
159 offset = pwm_id * 3;
Simon Glassa9e9abb2013-03-28 04:32:16 +0000160
Gabe Black4f44a3c2013-03-28 04:32:18 +0000161 writel(ticks_per_period, &pwm->tcntb0 + offset);
Donghwa Lee85f5df22011-03-07 21:11:42 +0000162
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}