uboot/arch/arm/cpu/armv7/s5p-common/pwm.c
<<
>>
Prefs
   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
  15int pwm_enable(int pwm_id)
  16{
  17        const struct s5p_timer *pwm =
  18                        (struct s5p_timer *)samsung_get_base_timer();
  19        unsigned long tcon;
  20
  21        tcon = readl(&pwm->tcon);
  22        tcon |= TCON_START(pwm_id);
  23
  24        writel(tcon, &pwm->tcon);
  25
  26        return 0;
  27}
  28
  29void pwm_disable(int pwm_id)
  30{
  31        const struct s5p_timer *pwm =
  32                        (struct s5p_timer *)samsung_get_base_timer();
  33        unsigned long tcon;
  34
  35        tcon = readl(&pwm->tcon);
  36        tcon &= ~TCON_START(pwm_id);
  37
  38        writel(tcon, &pwm->tcon);
  39}
  40
  41static unsigned long pwm_calc_tin(int pwm_id, unsigned long freq)
  42{
  43        unsigned long tin_parent_rate;
  44        unsigned int div;
  45
  46        tin_parent_rate = get_pwm_clk();
  47
  48        for (div = 2; div <= 16; div *= 2) {
  49                if ((tin_parent_rate / (div << 16)) < freq)
  50                        return tin_parent_rate / div;
  51        }
  52
  53        return tin_parent_rate / 16;
  54}
  55
  56#define NS_IN_SEC 1000000000UL
  57
  58int pwm_config(int pwm_id, int duty_ns, int period_ns)
  59{
  60        const struct s5p_timer *pwm =
  61                        (struct s5p_timer *)samsung_get_base_timer();
  62        unsigned int offset;
  63        unsigned long tin_rate;
  64        unsigned long tin_ns;
  65        unsigned long frequency;
  66        unsigned long tcon;
  67        unsigned long tcnt;
  68        unsigned long tcmp;
  69
  70        /*
  71         * We currently avoid using 64bit arithmetic by using the
  72         * fact that anything faster than 1GHz is easily representable
  73         * by 32bits.
  74         */
  75        if (period_ns > NS_IN_SEC || duty_ns > NS_IN_SEC || period_ns == 0)
  76                return -ERANGE;
  77
  78        if (duty_ns > period_ns)
  79                return -EINVAL;
  80
  81        frequency = NS_IN_SEC / period_ns;
  82
  83        /* Check to see if we are changing the clock rate of the PWM */
  84        tin_rate = pwm_calc_tin(pwm_id, frequency);
  85
  86        tin_ns = NS_IN_SEC / tin_rate;
  87        tcnt = period_ns / tin_ns;
  88
  89        /* Note, counters count down */
  90        tcmp = duty_ns / tin_ns;
  91        tcmp = tcnt - tcmp;
  92
  93        /* Update the PWM register block. */
  94        offset = pwm_id * 3;
  95        if (pwm_id < 4) {
  96                writel(tcnt, &pwm->tcntb0 + offset);
  97                writel(tcmp, &pwm->tcmpb0 + offset);
  98        }
  99
 100        tcon = readl(&pwm->tcon);
 101        tcon |= TCON_UPDATE(pwm_id);
 102        if (pwm_id < 4)
 103                tcon |= TCON_AUTO_RELOAD(pwm_id);
 104        else
 105                tcon |= TCON4_AUTO_RELOAD;
 106        writel(tcon, &pwm->tcon);
 107
 108        tcon &= ~TCON_UPDATE(pwm_id);
 109        writel(tcon, &pwm->tcon);
 110
 111        return 0;
 112}
 113
 114int pwm_init(int pwm_id, int div, int invert)
 115{
 116        u32 val;
 117        const struct s5p_timer *pwm =
 118                        (struct s5p_timer *)samsung_get_base_timer();
 119        unsigned long ticks_per_period;
 120        unsigned int offset, prescaler;
 121
 122        /*
 123         * Timer Freq(HZ) =
 124         *      PWM_CLK / { (prescaler_value + 1) * (divider_value) }
 125         */
 126
 127        val = readl(&pwm->tcfg0);
 128        if (pwm_id < 2) {
 129                prescaler = PRESCALER_0;
 130                val &= ~0xff;
 131                val |= (prescaler & 0xff);
 132        } else {
 133                prescaler = PRESCALER_1;
 134                val &= ~(0xff << 8);
 135                val |= (prescaler & 0xff) << 8;
 136        }
 137        writel(val, &pwm->tcfg0);
 138        val = readl(&pwm->tcfg1);
 139        val &= ~(0xf << MUX_DIV_SHIFT(pwm_id));
 140        val |= (div & 0xf) << MUX_DIV_SHIFT(pwm_id);
 141        writel(val, &pwm->tcfg1);
 142
 143        if (pwm_id == 4) {
 144                /*
 145                 * TODO(sjg): Use this as a countdown timer for now. We count
 146                 * down from the maximum value to 0, then reset.
 147                 */
 148                ticks_per_period = -1UL;
 149        } else {
 150                const unsigned long pwm_hz = 1000;
 151                unsigned long timer_rate_hz = get_pwm_clk() /
 152                        ((prescaler + 1) * (1 << div));
 153
 154                ticks_per_period = timer_rate_hz / pwm_hz;
 155        }
 156
 157        /* set count value */
 158        offset = pwm_id * 3;
 159
 160        writel(ticks_per_period, &pwm->tcntb0 + offset);
 161
 162        val = readl(&pwm->tcon) & ~(0xf << TCON_OFFSET(pwm_id));
 163        if (invert && (pwm_id < 4))
 164                val |= TCON_INVERTER(pwm_id);
 165        writel(val, &pwm->tcon);
 166
 167        pwm_enable(pwm_id);
 168
 169        return 0;
 170}
 171