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