blob: 6f401b8d9e5ff7759e3bd4e49fed34a72ccac067 [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 *
6 * See file CREDITS for list of people who contributed to this
7 * project.
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License as
11 * published by the Free Software Foundation; either version 2 of
12 * the License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
22 * MA 02111-1307 USA
23 */
24
25#include <common.h>
26#include <errno.h>
27#include <pwm.h>
28#include <asm/io.h>
29#include <asm/arch/pwm.h>
30#include <asm/arch/clk.h>
31
32int pwm_enable(int pwm_id)
33{
34 const struct s5p_timer *pwm =
35 (struct s5p_timer *)samsung_get_base_timer();
36 unsigned long tcon;
37
38 tcon = readl(&pwm->tcon);
39 tcon |= TCON_START(pwm_id);
40
41 writel(tcon, &pwm->tcon);
42
43 return 0;
44}
45
46void pwm_disable(int pwm_id)
47{
48 const struct s5p_timer *pwm =
49 (struct s5p_timer *)samsung_get_base_timer();
50 unsigned long tcon;
51
52 tcon = readl(&pwm->tcon);
53 tcon &= ~TCON_START(pwm_id);
54
55 writel(tcon, &pwm->tcon);
56}
57
58static unsigned long pwm_calc_tin(int pwm_id, unsigned long freq)
59{
60 unsigned long tin_parent_rate;
61 unsigned int div;
62
63 tin_parent_rate = get_pwm_clk();
64
65 for (div = 2; div <= 16; div *= 2) {
66 if ((tin_parent_rate / (div << 16)) < freq)
67 return tin_parent_rate / div;
68 }
69
70 return tin_parent_rate / 16;
71}
72
Gabe Blackad73b912013-03-28 04:32:20 +000073#define NS_IN_SEC 1000000000UL
Donghwa Lee85f5df22011-03-07 21:11:42 +000074
75int pwm_config(int pwm_id, int duty_ns, int period_ns)
76{
77 const struct s5p_timer *pwm =
78 (struct s5p_timer *)samsung_get_base_timer();
79 unsigned int offset;
80 unsigned long tin_rate;
81 unsigned long tin_ns;
Gabe Blackad73b912013-03-28 04:32:20 +000082 unsigned long frequency;
Donghwa Lee85f5df22011-03-07 21:11:42 +000083 unsigned long tcon;
84 unsigned long tcnt;
Donghwa Lee85f5df22011-03-07 21:11:42 +000085 unsigned long tcmp;
86
87 /*
88 * We currently avoid using 64bit arithmetic by using the
89 * fact that anything faster than 1GHz is easily representable
90 * by 32bits.
91 */
Gabe Blackad73b912013-03-28 04:32:20 +000092 if (period_ns > NS_IN_SEC || duty_ns > NS_IN_SEC || period_ns == 0)
Donghwa Lee85f5df22011-03-07 21:11:42 +000093 return -ERANGE;
94
95 if (duty_ns > period_ns)
96 return -EINVAL;
97
Gabe Blackad73b912013-03-28 04:32:20 +000098 frequency = NS_IN_SEC / period_ns;
Donghwa Lee85f5df22011-03-07 21:11:42 +000099
100 /* Check to see if we are changing the clock rate of the PWM */
Gabe Blackad73b912013-03-28 04:32:20 +0000101 tin_rate = pwm_calc_tin(pwm_id, frequency);
Donghwa Lee85f5df22011-03-07 21:11:42 +0000102
Gabe Blackad73b912013-03-28 04:32:20 +0000103 tin_ns = NS_IN_SEC / tin_rate;
Donghwa Lee85f5df22011-03-07 21:11:42 +0000104 tcnt = period_ns / tin_ns;
105
106 /* Note, counters count down */
107 tcmp = duty_ns / tin_ns;
108 tcmp = tcnt - tcmp;
109
Donghwa Lee85f5df22011-03-07 21:11:42 +0000110 /* Update the PWM register block. */
111 offset = pwm_id * 3;
112 if (pwm_id < 4) {
113 writel(tcnt, &pwm->tcntb0 + offset);
114 writel(tcmp, &pwm->tcmpb0 + offset);
115 }
116
117 tcon = readl(&pwm->tcon);
118 tcon |= TCON_UPDATE(pwm_id);
119 if (pwm_id < 4)
120 tcon |= TCON_AUTO_RELOAD(pwm_id);
121 else
122 tcon |= TCON4_AUTO_RELOAD;
123 writel(tcon, &pwm->tcon);
124
125 tcon &= ~TCON_UPDATE(pwm_id);
126 writel(tcon, &pwm->tcon);
127
128 return 0;
129}
130
131int pwm_init(int pwm_id, int div, int invert)
132{
133 u32 val;
134 const struct s5p_timer *pwm =
135 (struct s5p_timer *)samsung_get_base_timer();
Gabe Black4f44a3c2013-03-28 04:32:18 +0000136 unsigned long ticks_per_period;
Donghwa Lee85f5df22011-03-07 21:11:42 +0000137 unsigned int offset, prescaler;
138
139 /*
140 * Timer Freq(HZ) =
141 * PWM_CLK / { (prescaler_value + 1) * (divider_value) }
142 */
143
144 val = readl(&pwm->tcfg0);
145 if (pwm_id < 2) {
146 prescaler = PRESCALER_0;
147 val &= ~0xff;
148 val |= (prescaler & 0xff);
149 } else {
150 prescaler = PRESCALER_1;
151 val &= ~(0xff << 8);
152 val |= (prescaler & 0xff) << 8;
153 }
154 writel(val, &pwm->tcfg0);
155 val = readl(&pwm->tcfg1);
156 val &= ~(0xf << MUX_DIV_SHIFT(pwm_id));
157 val |= (div & 0xf) << MUX_DIV_SHIFT(pwm_id);
158 writel(val, &pwm->tcfg1);
159
Gabe Black4f44a3c2013-03-28 04:32:18 +0000160 if (pwm_id == 4) {
161 /*
162 * TODO(sjg): Use this as a countdown timer for now. We count
163 * down from the maximum value to 0, then reset.
164 */
165 ticks_per_period = -1UL;
166 } else {
167 const unsigned long pwm_hz = 1000;
168 unsigned long timer_rate_hz = get_pwm_clk() /
169 ((prescaler + 1) * (1 << div));
Donghwa Lee85f5df22011-03-07 21:11:42 +0000170
Gabe Black4f44a3c2013-03-28 04:32:18 +0000171 ticks_per_period = timer_rate_hz / pwm_hz;
172 }
Donghwa Lee85f5df22011-03-07 21:11:42 +0000173
174 /* set count value */
175 offset = pwm_id * 3;
Simon Glassa9e9abb2013-03-28 04:32:16 +0000176
Gabe Black4f44a3c2013-03-28 04:32:18 +0000177 writel(ticks_per_period, &pwm->tcntb0 + offset);
Donghwa Lee85f5df22011-03-07 21:11:42 +0000178
179 val = readl(&pwm->tcon) & ~(0xf << TCON_OFFSET(pwm_id));
180 if (invert && (pwm_id < 4))
181 val |= TCON_INVERTER(pwm_id);
182 writel(val, &pwm->tcon);
183
184 pwm_enable(pwm_id);
185
186 return 0;
187}