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