1 // SPDX-License-Identifier: GPL-2.0+
3 * Copyright (C) 2011 Samsung Electronics
5 * Donghwa Lee <dh09.lee@samsung.com>
11 #include <asm/arch/pwm.h>
12 #include <asm/arch/clk.h>
14 int s5p_pwm_enable(int pwm_id)
16 const struct s5p_timer *pwm =
17 #if defined(CONFIG_ARCH_NEXELL)
18 (struct s5p_timer *)PHY_BASEADDR_PWM;
20 (struct s5p_timer *)samsung_get_base_timer();
24 tcon = readl(&pwm->tcon);
25 tcon |= TCON_START(pwm_id);
27 writel(tcon, &pwm->tcon);
32 void s5p_pwm_disable(int pwm_id)
34 const struct s5p_timer *pwm =
35 #if defined(CONFIG_ARCH_NEXELL)
36 (struct s5p_timer *)PHY_BASEADDR_PWM;
38 (struct s5p_timer *)samsung_get_base_timer();
42 tcon = readl(&pwm->tcon);
43 tcon &= ~TCON_START(pwm_id);
45 writel(tcon, &pwm->tcon);
48 static unsigned long pwm_calc_tin(int pwm_id, unsigned long freq)
50 unsigned long tin_parent_rate;
53 #if defined(CONFIG_ARCH_NEXELL)
55 const struct s5p_timer *pwm =
56 (struct s5p_timer *)PHY_BASEADDR_PWM;
58 struct clk *clk = clk_get(CORECLK_NAME_PCLK);
60 tin_parent_rate = clk_get_rate(clk);
62 tin_parent_rate = get_pwm_clk();
65 #if defined(CONFIG_ARCH_NEXELL)
66 writel(0, &pwm->tcfg0);
67 val = readl(&pwm->tcfg0);
70 div = ((val >> 0) & 0xff) + 1;
72 div = ((val >> 8) & 0xff) + 1;
74 writel(0, &pwm->tcfg1);
75 val = readl(&pwm->tcfg1);
76 val = (val >> MUX_DIV_SHIFT(pwm_id)) & 0xF;
77 pre_div = (1UL << val);
79 freq = tin_parent_rate / div / pre_div;
83 for (div = 2; div <= 16; div *= 2) {
84 if ((tin_parent_rate / (div << 16)) < freq)
85 return tin_parent_rate / div;
88 return tin_parent_rate / 16;
92 #define NS_IN_SEC 1000000000UL
94 int s5p_pwm_config(int pwm_id, int duty_ns, int period_ns)
96 const struct s5p_timer *pwm =
97 #if defined(CONFIG_ARCH_NEXELL)
98 (struct s5p_timer *)PHY_BASEADDR_PWM;
100 (struct s5p_timer *)samsung_get_base_timer();
103 unsigned long tin_rate;
104 unsigned long tin_ns;
105 unsigned long frequency;
111 * We currently avoid using 64bit arithmetic by using the
112 * fact that anything faster than 1GHz is easily representable
115 if (period_ns > NS_IN_SEC || duty_ns > NS_IN_SEC || period_ns == 0)
118 if (duty_ns > period_ns)
121 frequency = NS_IN_SEC / period_ns;
123 /* Check to see if we are changing the clock rate of the PWM */
124 tin_rate = pwm_calc_tin(pwm_id, frequency);
126 tin_ns = NS_IN_SEC / tin_rate;
128 if (IS_ENABLED(CONFIG_ARCH_NEXELL))
129 /* The counter starts at zero. */
130 tcnt = (period_ns / tin_ns) - 1;
132 tcnt = period_ns / tin_ns;
134 /* Note, counters count down */
135 tcmp = duty_ns / tin_ns;
138 /* Update the PWM register block. */
141 writel(tcnt, &pwm->tcntb0 + offset);
142 writel(tcmp, &pwm->tcmpb0 + offset);
145 tcon = readl(&pwm->tcon);
146 tcon |= TCON_UPDATE(pwm_id);
148 tcon |= TCON_AUTO_RELOAD(pwm_id);
150 tcon |= TCON4_AUTO_RELOAD;
151 writel(tcon, &pwm->tcon);
153 tcon &= ~TCON_UPDATE(pwm_id);
154 writel(tcon, &pwm->tcon);
159 int s5p_pwm_init(int pwm_id, int div, int invert)
162 const struct s5p_timer *pwm =
163 #if defined(CONFIG_ARCH_NEXELL)
164 (struct s5p_timer *)PHY_BASEADDR_PWM;
166 (struct s5p_timer *)samsung_get_base_timer();
168 unsigned long ticks_per_period;
169 unsigned int offset, prescaler;
173 * PWM_CLK / { (prescaler_value + 1) * (divider_value) }
176 val = readl(&pwm->tcfg0);
178 prescaler = PRESCALER_0;
180 val |= (prescaler & 0xff);
182 prescaler = PRESCALER_1;
184 val |= (prescaler & 0xff) << 8;
186 writel(val, &pwm->tcfg0);
187 val = readl(&pwm->tcfg1);
188 val &= ~(0xf << MUX_DIV_SHIFT(pwm_id));
189 val |= (div & 0xf) << MUX_DIV_SHIFT(pwm_id);
190 writel(val, &pwm->tcfg1);
194 * TODO(sjg): Use this as a countdown timer for now. We count
195 * down from the maximum value to 0, then reset.
197 ticks_per_period = -1UL;
199 const unsigned long pwm_hz = 1000;
200 #if defined(CONFIG_ARCH_NEXELL)
201 struct clk *clk = clk_get(CORECLK_NAME_PCLK);
202 unsigned long timer_rate_hz = clk_get_rate(clk) /
204 unsigned long timer_rate_hz = get_pwm_clk() /
206 ((prescaler + 1) * (1 << div));
208 ticks_per_period = timer_rate_hz / pwm_hz;
211 /* set count value */
214 writel(ticks_per_period, &pwm->tcntb0 + offset);
216 val = readl(&pwm->tcon) & ~(0xf << TCON_OFFSET(pwm_id));
217 if (invert && (pwm_id < 4))
218 val |= TCON_INVERTER(pwm_id);
219 writel(val, &pwm->tcon);
221 s5p_pwm_enable(pwm_id);