blob: 6b9e865803792237c1bd2dd705cf2266a90d8280 [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001// SPDX-License-Identifier: GPL-2.0+
Donghwa Lee85f5df22011-03-07 21:11:42 +00002/*
3 * Copyright (C) 2011 Samsung Electronics
4 *
5 * Donghwa Lee <dh09.lee@samsung.com>
Donghwa Lee85f5df22011-03-07 21:11:42 +00006 */
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
15int 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
29void 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
41static 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
Gabe Blackad73b912013-03-28 04:32:20 +000056#define NS_IN_SEC 1000000000UL
Donghwa Lee85f5df22011-03-07 21:11:42 +000057
58int 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;
Gabe Blackad73b912013-03-28 04:32:20 +000065 unsigned long frequency;
Donghwa Lee85f5df22011-03-07 21:11:42 +000066 unsigned long tcon;
67 unsigned long tcnt;
Donghwa Lee85f5df22011-03-07 21:11:42 +000068 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 */
Gabe Blackad73b912013-03-28 04:32:20 +000075 if (period_ns > NS_IN_SEC || duty_ns > NS_IN_SEC || period_ns == 0)
Donghwa Lee85f5df22011-03-07 21:11:42 +000076 return -ERANGE;
77
78 if (duty_ns > period_ns)
79 return -EINVAL;
80
Gabe Blackad73b912013-03-28 04:32:20 +000081 frequency = NS_IN_SEC / period_ns;
Donghwa Lee85f5df22011-03-07 21:11:42 +000082
83 /* Check to see if we are changing the clock rate of the PWM */
Gabe Blackad73b912013-03-28 04:32:20 +000084 tin_rate = pwm_calc_tin(pwm_id, frequency);
Donghwa Lee85f5df22011-03-07 21:11:42 +000085
Gabe Blackad73b912013-03-28 04:32:20 +000086 tin_ns = NS_IN_SEC / tin_rate;
Donghwa Lee85f5df22011-03-07 21:11:42 +000087 tcnt = period_ns / tin_ns;
88
89 /* Note, counters count down */
90 tcmp = duty_ns / tin_ns;
91 tcmp = tcnt - tcmp;
92
Donghwa Lee85f5df22011-03-07 21:11:42 +000093 /* 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
114int 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();
Gabe Black4f44a3c2013-03-28 04:32:18 +0000119 unsigned long ticks_per_period;
Donghwa Lee85f5df22011-03-07 21:11:42 +0000120 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
Gabe Black4f44a3c2013-03-28 04:32:18 +0000143 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));
Donghwa Lee85f5df22011-03-07 21:11:42 +0000153
Gabe Black4f44a3c2013-03-28 04:32:18 +0000154 ticks_per_period = timer_rate_hz / pwm_hz;
155 }
Donghwa Lee85f5df22011-03-07 21:11:42 +0000156
157 /* set count value */
158 offset = pwm_id * 3;
Simon Glassa9e9abb2013-03-28 04:32:16 +0000159
Gabe Black4f44a3c2013-03-28 04:32:18 +0000160 writel(ticks_per_period, &pwm->tcntb0 + offset);
Donghwa Lee85f5df22011-03-07 21:11:42 +0000161
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}