1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Copyright (C) 2011 Samsung Electronics
4  *
5  * Donghwa Lee <dh09.lee@samsung.com>
6  */
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 
pwm_enable(int pwm_id)15 int pwm_enable(int pwm_id)
16 {
17 	const struct s5p_timer *pwm =
18 #if defined(CONFIG_ARCH_NEXELL)
19 			(struct s5p_timer *)PHY_BASEADDR_PWM;
20 #else
21 			(struct s5p_timer *)samsung_get_base_timer();
22 #endif
23 	unsigned long tcon;
24 
25 	tcon = readl(&pwm->tcon);
26 	tcon |= TCON_START(pwm_id);
27 
28 	writel(tcon, &pwm->tcon);
29 
30 	return 0;
31 }
32 
pwm_disable(int pwm_id)33 void pwm_disable(int pwm_id)
34 {
35 	const struct s5p_timer *pwm =
36 #if defined(CONFIG_ARCH_NEXELL)
37 			(struct s5p_timer *)PHY_BASEADDR_PWM;
38 #else
39 			(struct s5p_timer *)samsung_get_base_timer();
40 #endif
41 	unsigned long tcon;
42 
43 	tcon = readl(&pwm->tcon);
44 	tcon &= ~TCON_START(pwm_id);
45 
46 	writel(tcon, &pwm->tcon);
47 }
48 
pwm_calc_tin(int pwm_id,unsigned long freq)49 static unsigned long pwm_calc_tin(int pwm_id, unsigned long freq)
50 {
51 	unsigned long tin_parent_rate;
52 	unsigned int div;
53 
54 #if defined(CONFIG_ARCH_NEXELL)
55 	unsigned int pre_div;
56 	const struct s5p_timer *pwm =
57 		(struct s5p_timer *)PHY_BASEADDR_PWM;
58 	unsigned int val;
59 	struct clk *clk = clk_get(CORECLK_NAME_PCLK);
60 
61 	tin_parent_rate = clk_get_rate(clk);
62 #else
63 	tin_parent_rate = get_pwm_clk();
64 #endif
65 
66 #if defined(CONFIG_ARCH_NEXELL)
67 	writel(0, &pwm->tcfg0);
68 	val = readl(&pwm->tcfg0);
69 
70 	if (pwm_id < 2)
71 		div = ((val >> 0) & 0xff) + 1;
72 	else
73 		div = ((val >> 8) & 0xff) + 1;
74 
75 	writel(0, &pwm->tcfg1);
76 	val = readl(&pwm->tcfg1);
77 	val = (val >> MUX_DIV_SHIFT(pwm_id)) & 0xF;
78 	pre_div = (1UL << val);
79 
80 	freq = tin_parent_rate / div / pre_div;
81 
82 	return freq;
83 #else
84 	for (div = 2; div <= 16; div *= 2) {
85 		if ((tin_parent_rate / (div << 16)) < freq)
86 			return tin_parent_rate / div;
87 	}
88 
89 	return tin_parent_rate / 16;
90 #endif
91 }
92 
93 #define NS_IN_SEC 1000000000UL
94 
pwm_config(int pwm_id,int duty_ns,int period_ns)95 int pwm_config(int pwm_id, int duty_ns, int period_ns)
96 {
97 	const struct s5p_timer *pwm =
98 #if defined(CONFIG_ARCH_NEXELL)
99 			(struct s5p_timer *)PHY_BASEADDR_PWM;
100 #else
101 			(struct s5p_timer *)samsung_get_base_timer();
102 #endif
103 	unsigned int offset;
104 	unsigned long tin_rate;
105 	unsigned long tin_ns;
106 	unsigned long frequency;
107 	unsigned long tcon;
108 	unsigned long tcnt;
109 	unsigned long tcmp;
110 
111 	/*
112 	 * We currently avoid using 64bit arithmetic by using the
113 	 * fact that anything faster than 1GHz is easily representable
114 	 * by 32bits.
115 	 */
116 	if (period_ns > NS_IN_SEC || duty_ns > NS_IN_SEC || period_ns == 0)
117 		return -ERANGE;
118 
119 	if (duty_ns > period_ns)
120 		return -EINVAL;
121 
122 	frequency = NS_IN_SEC / period_ns;
123 
124 	/* Check to see if we are changing the clock rate of the PWM */
125 	tin_rate = pwm_calc_tin(pwm_id, frequency);
126 
127 	tin_ns = NS_IN_SEC / tin_rate;
128 
129 	if (IS_ENABLED(CONFIG_ARCH_NEXELL))
130 		/* The counter starts at zero. */
131 		tcnt = (period_ns / tin_ns) - 1;
132 	else
133 		tcnt = period_ns / tin_ns;
134 
135 	/* Note, counters count down */
136 	tcmp = duty_ns / tin_ns;
137 	tcmp = tcnt - tcmp;
138 
139 	/* Update the PWM register block. */
140 	offset = pwm_id * 3;
141 	if (pwm_id < 4) {
142 		writel(tcnt, &pwm->tcntb0 + offset);
143 		writel(tcmp, &pwm->tcmpb0 + offset);
144 	}
145 
146 	tcon = readl(&pwm->tcon);
147 	tcon |= TCON_UPDATE(pwm_id);
148 	if (pwm_id < 4)
149 		tcon |= TCON_AUTO_RELOAD(pwm_id);
150 	else
151 		tcon |= TCON4_AUTO_RELOAD;
152 	writel(tcon, &pwm->tcon);
153 
154 	tcon &= ~TCON_UPDATE(pwm_id);
155 	writel(tcon, &pwm->tcon);
156 
157 	return 0;
158 }
159 
pwm_init(int pwm_id,int div,int invert)160 int pwm_init(int pwm_id, int div, int invert)
161 {
162 	u32 val;
163 	const struct s5p_timer *pwm =
164 #if defined(CONFIG_ARCH_NEXELL)
165 			(struct s5p_timer *)PHY_BASEADDR_PWM;
166 #else
167 			(struct s5p_timer *)samsung_get_base_timer();
168 #endif
169 	unsigned long ticks_per_period;
170 	unsigned int offset, prescaler;
171 
172 	/*
173 	 * Timer Freq(HZ) =
174 	 *	PWM_CLK / { (prescaler_value + 1) * (divider_value) }
175 	 */
176 
177 	val = readl(&pwm->tcfg0);
178 	if (pwm_id < 2) {
179 		prescaler = PRESCALER_0;
180 		val &= ~0xff;
181 		val |= (prescaler & 0xff);
182 	} else {
183 		prescaler = PRESCALER_1;
184 		val &= ~(0xff << 8);
185 		val |= (prescaler & 0xff) << 8;
186 	}
187 	writel(val, &pwm->tcfg0);
188 	val = readl(&pwm->tcfg1);
189 	val &= ~(0xf << MUX_DIV_SHIFT(pwm_id));
190 	val |= (div & 0xf) << MUX_DIV_SHIFT(pwm_id);
191 	writel(val, &pwm->tcfg1);
192 
193 	if (pwm_id == 4) {
194 		/*
195 		 * TODO(sjg): Use this as a countdown timer for now. We count
196 		 * down from the maximum value to 0, then reset.
197 		 */
198 		ticks_per_period = -1UL;
199 	} else {
200 		const unsigned long pwm_hz = 1000;
201 #if defined(CONFIG_ARCH_NEXELL)
202 		struct clk *clk = clk_get(CORECLK_NAME_PCLK);
203 		unsigned long timer_rate_hz = clk_get_rate(clk) /
204 #else
205 		unsigned long timer_rate_hz = get_pwm_clk() /
206 #endif
207 			((prescaler + 1) * (1 << div));
208 
209 		ticks_per_period = timer_rate_hz / pwm_hz;
210 	}
211 
212 	/* set count value */
213 	offset = pwm_id * 3;
214 
215 	writel(ticks_per_period, &pwm->tcntb0 + offset);
216 
217 	val = readl(&pwm->tcon) & ~(0xf << TCON_OFFSET(pwm_id));
218 	if (invert && (pwm_id < 4))
219 		val |= TCON_INVERTER(pwm_id);
220 	writel(val, &pwm->tcon);
221 
222 	pwm_enable(pwm_id);
223 
224 	return 0;
225 }
226