xref: /openbmc/u-boot/arch/arm/cpu/armv7/s5p-common/pwm.c (revision 83d290c56fab2d38cd1ab4c4cc7099559c1d5046)
1*83d290c5STom Rini // SPDX-License-Identifier: GPL-2.0+
23f129280SDonghwa Lee /*
33f129280SDonghwa Lee  * Copyright (C) 2011 Samsung Electronics
43f129280SDonghwa Lee  *
53f129280SDonghwa Lee  * Donghwa Lee <dh09.lee@samsung.com>
63f129280SDonghwa Lee  */
73f129280SDonghwa Lee 
83f129280SDonghwa Lee #include <common.h>
93f129280SDonghwa Lee #include <errno.h>
103f129280SDonghwa Lee #include <pwm.h>
113f129280SDonghwa Lee #include <asm/io.h>
123f129280SDonghwa Lee #include <asm/arch/pwm.h>
133f129280SDonghwa Lee #include <asm/arch/clk.h>
143f129280SDonghwa Lee 
pwm_enable(int pwm_id)153f129280SDonghwa Lee int pwm_enable(int pwm_id)
163f129280SDonghwa Lee {
173f129280SDonghwa Lee 	const struct s5p_timer *pwm =
183f129280SDonghwa Lee 			(struct s5p_timer *)samsung_get_base_timer();
193f129280SDonghwa Lee 	unsigned long tcon;
203f129280SDonghwa Lee 
213f129280SDonghwa Lee 	tcon = readl(&pwm->tcon);
223f129280SDonghwa Lee 	tcon |= TCON_START(pwm_id);
233f129280SDonghwa Lee 
243f129280SDonghwa Lee 	writel(tcon, &pwm->tcon);
253f129280SDonghwa Lee 
263f129280SDonghwa Lee 	return 0;
273f129280SDonghwa Lee }
283f129280SDonghwa Lee 
pwm_disable(int pwm_id)293f129280SDonghwa Lee void pwm_disable(int pwm_id)
303f129280SDonghwa Lee {
313f129280SDonghwa Lee 	const struct s5p_timer *pwm =
323f129280SDonghwa Lee 			(struct s5p_timer *)samsung_get_base_timer();
333f129280SDonghwa Lee 	unsigned long tcon;
343f129280SDonghwa Lee 
353f129280SDonghwa Lee 	tcon = readl(&pwm->tcon);
363f129280SDonghwa Lee 	tcon &= ~TCON_START(pwm_id);
373f129280SDonghwa Lee 
383f129280SDonghwa Lee 	writel(tcon, &pwm->tcon);
393f129280SDonghwa Lee }
403f129280SDonghwa Lee 
pwm_calc_tin(int pwm_id,unsigned long freq)413f129280SDonghwa Lee static unsigned long pwm_calc_tin(int pwm_id, unsigned long freq)
423f129280SDonghwa Lee {
433f129280SDonghwa Lee 	unsigned long tin_parent_rate;
443f129280SDonghwa Lee 	unsigned int div;
453f129280SDonghwa Lee 
463f129280SDonghwa Lee 	tin_parent_rate = get_pwm_clk();
473f129280SDonghwa Lee 
483f129280SDonghwa Lee 	for (div = 2; div <= 16; div *= 2) {
493f129280SDonghwa Lee 		if ((tin_parent_rate / (div << 16)) < freq)
503f129280SDonghwa Lee 			return tin_parent_rate / div;
513f129280SDonghwa Lee 	}
523f129280SDonghwa Lee 
533f129280SDonghwa Lee 	return tin_parent_rate / 16;
543f129280SDonghwa Lee }
553f129280SDonghwa Lee 
5692809eeeSGabe Black #define NS_IN_SEC 1000000000UL
573f129280SDonghwa Lee 
pwm_config(int pwm_id,int duty_ns,int period_ns)583f129280SDonghwa Lee int pwm_config(int pwm_id, int duty_ns, int period_ns)
593f129280SDonghwa Lee {
603f129280SDonghwa Lee 	const struct s5p_timer *pwm =
613f129280SDonghwa Lee 			(struct s5p_timer *)samsung_get_base_timer();
623f129280SDonghwa Lee 	unsigned int offset;
633f129280SDonghwa Lee 	unsigned long tin_rate;
643f129280SDonghwa Lee 	unsigned long tin_ns;
6592809eeeSGabe Black 	unsigned long frequency;
663f129280SDonghwa Lee 	unsigned long tcon;
673f129280SDonghwa Lee 	unsigned long tcnt;
683f129280SDonghwa Lee 	unsigned long tcmp;
693f129280SDonghwa Lee 
703f129280SDonghwa Lee 	/*
713f129280SDonghwa Lee 	 * We currently avoid using 64bit arithmetic by using the
723f129280SDonghwa Lee 	 * fact that anything faster than 1GHz is easily representable
733f129280SDonghwa Lee 	 * by 32bits.
743f129280SDonghwa Lee 	 */
7592809eeeSGabe Black 	if (period_ns > NS_IN_SEC || duty_ns > NS_IN_SEC || period_ns == 0)
763f129280SDonghwa Lee 		return -ERANGE;
773f129280SDonghwa Lee 
783f129280SDonghwa Lee 	if (duty_ns > period_ns)
793f129280SDonghwa Lee 		return -EINVAL;
803f129280SDonghwa Lee 
8192809eeeSGabe Black 	frequency = NS_IN_SEC / period_ns;
823f129280SDonghwa Lee 
833f129280SDonghwa Lee 	/* Check to see if we are changing the clock rate of the PWM */
8492809eeeSGabe Black 	tin_rate = pwm_calc_tin(pwm_id, frequency);
853f129280SDonghwa Lee 
8692809eeeSGabe Black 	tin_ns = NS_IN_SEC / tin_rate;
873f129280SDonghwa Lee 	tcnt = period_ns / tin_ns;
883f129280SDonghwa Lee 
893f129280SDonghwa Lee 	/* Note, counters count down */
903f129280SDonghwa Lee 	tcmp = duty_ns / tin_ns;
913f129280SDonghwa Lee 	tcmp = tcnt - tcmp;
923f129280SDonghwa Lee 
933f129280SDonghwa Lee 	/* Update the PWM register block. */
943f129280SDonghwa Lee 	offset = pwm_id * 3;
953f129280SDonghwa Lee 	if (pwm_id < 4) {
963f129280SDonghwa Lee 		writel(tcnt, &pwm->tcntb0 + offset);
973f129280SDonghwa Lee 		writel(tcmp, &pwm->tcmpb0 + offset);
983f129280SDonghwa Lee 	}
993f129280SDonghwa Lee 
1003f129280SDonghwa Lee 	tcon = readl(&pwm->tcon);
1013f129280SDonghwa Lee 	tcon |= TCON_UPDATE(pwm_id);
1023f129280SDonghwa Lee 	if (pwm_id < 4)
1033f129280SDonghwa Lee 		tcon |= TCON_AUTO_RELOAD(pwm_id);
1043f129280SDonghwa Lee 	else
1053f129280SDonghwa Lee 		tcon |= TCON4_AUTO_RELOAD;
1063f129280SDonghwa Lee 	writel(tcon, &pwm->tcon);
1073f129280SDonghwa Lee 
1083f129280SDonghwa Lee 	tcon &= ~TCON_UPDATE(pwm_id);
1093f129280SDonghwa Lee 	writel(tcon, &pwm->tcon);
1103f129280SDonghwa Lee 
1113f129280SDonghwa Lee 	return 0;
1123f129280SDonghwa Lee }
1133f129280SDonghwa Lee 
pwm_init(int pwm_id,int div,int invert)1143f129280SDonghwa Lee int pwm_init(int pwm_id, int div, int invert)
1153f129280SDonghwa Lee {
1163f129280SDonghwa Lee 	u32 val;
1173f129280SDonghwa Lee 	const struct s5p_timer *pwm =
1183f129280SDonghwa Lee 			(struct s5p_timer *)samsung_get_base_timer();
119c059f274SGabe Black 	unsigned long ticks_per_period;
1203f129280SDonghwa Lee 	unsigned int offset, prescaler;
1213f129280SDonghwa Lee 
1223f129280SDonghwa Lee 	/*
1233f129280SDonghwa Lee 	 * Timer Freq(HZ) =
1243f129280SDonghwa Lee 	 *	PWM_CLK / { (prescaler_value + 1) * (divider_value) }
1253f129280SDonghwa Lee 	 */
1263f129280SDonghwa Lee 
1273f129280SDonghwa Lee 	val = readl(&pwm->tcfg0);
1283f129280SDonghwa Lee 	if (pwm_id < 2) {
1293f129280SDonghwa Lee 		prescaler = PRESCALER_0;
1303f129280SDonghwa Lee 		val &= ~0xff;
1313f129280SDonghwa Lee 		val |= (prescaler & 0xff);
1323f129280SDonghwa Lee 	} else {
1333f129280SDonghwa Lee 		prescaler = PRESCALER_1;
1343f129280SDonghwa Lee 		val &= ~(0xff << 8);
1353f129280SDonghwa Lee 		val |= (prescaler & 0xff) << 8;
1363f129280SDonghwa Lee 	}
1373f129280SDonghwa Lee 	writel(val, &pwm->tcfg0);
1383f129280SDonghwa Lee 	val = readl(&pwm->tcfg1);
1393f129280SDonghwa Lee 	val &= ~(0xf << MUX_DIV_SHIFT(pwm_id));
1403f129280SDonghwa Lee 	val |= (div & 0xf) << MUX_DIV_SHIFT(pwm_id);
1413f129280SDonghwa Lee 	writel(val, &pwm->tcfg1);
1423f129280SDonghwa Lee 
143c059f274SGabe Black 	if (pwm_id == 4) {
144c059f274SGabe Black 		/*
145c059f274SGabe Black 		 * TODO(sjg): Use this as a countdown timer for now. We count
146c059f274SGabe Black 		 * down from the maximum value to 0, then reset.
147c059f274SGabe Black 		 */
148c059f274SGabe Black 		ticks_per_period = -1UL;
149c059f274SGabe Black 	} else {
150c059f274SGabe Black 		const unsigned long pwm_hz = 1000;
151c059f274SGabe Black 		unsigned long timer_rate_hz = get_pwm_clk() /
152c059f274SGabe Black 			((prescaler + 1) * (1 << div));
1533f129280SDonghwa Lee 
154c059f274SGabe Black 		ticks_per_period = timer_rate_hz / pwm_hz;
155c059f274SGabe Black 	}
1563f129280SDonghwa Lee 
1573f129280SDonghwa Lee 	/* set count value */
1583f129280SDonghwa Lee 	offset = pwm_id * 3;
1593d00c0cbSSimon Glass 
160c059f274SGabe Black 	writel(ticks_per_period, &pwm->tcntb0 + offset);
1613f129280SDonghwa Lee 
1623f129280SDonghwa Lee 	val = readl(&pwm->tcon) & ~(0xf << TCON_OFFSET(pwm_id));
1633f129280SDonghwa Lee 	if (invert && (pwm_id < 4))
1643f129280SDonghwa Lee 		val |= TCON_INVERTER(pwm_id);
1653f129280SDonghwa Lee 	writel(val, &pwm->tcon);
1663f129280SDonghwa Lee 
1673f129280SDonghwa Lee 	pwm_enable(pwm_id);
1683f129280SDonghwa Lee 
1693f129280SDonghwa Lee 	return 0;
1703f129280SDonghwa Lee }
171