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