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