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#if defined(CONFIG_ARCH_NEXELL)
  19                        (struct s5p_timer *)PHY_BASEADDR_PWM;
  20#else
  21                        (struct s5p_timer *)samsung_get_base_timer();
  22#endif
  23        unsigned long tcon;
  24
  25        tcon = readl(&pwm->tcon);
  26        tcon |= TCON_START(pwm_id);
  27
  28        writel(tcon, &pwm->tcon);
  29
  30        return 0;
  31}
  32
  33void pwm_disable(int pwm_id)
  34{
  35        const struct s5p_timer *pwm =
  36#if defined(CONFIG_ARCH_NEXELL)
  37                        (struct s5p_timer *)PHY_BASEADDR_PWM;
  38#else
  39                        (struct s5p_timer *)samsung_get_base_timer();
  40#endif
  41        unsigned long tcon;
  42
  43        tcon = readl(&pwm->tcon);
  44        tcon &= ~TCON_START(pwm_id);
  45
  46        writel(tcon, &pwm->tcon);
  47}
  48
  49static unsigned long pwm_calc_tin(int pwm_id, unsigned long freq)
  50{
  51        unsigned long tin_parent_rate;
  52        unsigned int div;
  53
  54#if defined(CONFIG_ARCH_NEXELL)
  55        unsigned int pre_div;
  56        const struct s5p_timer *pwm =
  57                (struct s5p_timer *)PHY_BASEADDR_PWM;
  58        unsigned int val;
  59        struct clk *clk = clk_get(CORECLK_NAME_PCLK);
  60
  61        tin_parent_rate = clk_get_rate(clk);
  62#else
  63        tin_parent_rate = get_pwm_clk();
  64#endif
  65
  66#if defined(CONFIG_ARCH_NEXELL)
  67        writel(0, &pwm->tcfg0);
  68        val = readl(&pwm->tcfg0);
  69
  70        if (pwm_id < 2)
  71                div = ((val >> 0) & 0xff) + 1;
  72        else
  73                div = ((val >> 8) & 0xff) + 1;
  74
  75        writel(0, &pwm->tcfg1);
  76        val = readl(&pwm->tcfg1);
  77        val = (val >> MUX_DIV_SHIFT(pwm_id)) & 0xF;
  78        pre_div = (1UL << val);
  79
  80        freq = tin_parent_rate / div / pre_div;
  81
  82        return freq;
  83#else
  84        for (div = 2; div <= 16; div *= 2) {
  85                if ((tin_parent_rate / (div << 16)) < freq)
  86                        return tin_parent_rate / div;
  87        }
  88
  89        return tin_parent_rate / 16;
  90#endif
  91}
  92
  93#define NS_IN_SEC 1000000000UL
  94
  95int pwm_config(int pwm_id, int duty_ns, int period_ns)
  96{
  97        const struct s5p_timer *pwm =
  98#if defined(CONFIG_ARCH_NEXELL)
  99                        (struct s5p_timer *)PHY_BASEADDR_PWM;
 100#else
 101                        (struct s5p_timer *)samsung_get_base_timer();
 102#endif
 103        unsigned int offset;
 104        unsigned long tin_rate;
 105        unsigned long tin_ns;
 106        unsigned long frequency;
 107        unsigned long tcon;
 108        unsigned long tcnt;
 109        unsigned long tcmp;
 110
 111        /*
 112         * We currently avoid using 64bit arithmetic by using the
 113         * fact that anything faster than 1GHz is easily representable
 114         * by 32bits.
 115         */
 116        if (period_ns > NS_IN_SEC || duty_ns > NS_IN_SEC || period_ns == 0)
 117                return -ERANGE;
 118
 119        if (duty_ns > period_ns)
 120                return -EINVAL;
 121
 122        frequency = NS_IN_SEC / period_ns;
 123
 124        /* Check to see if we are changing the clock rate of the PWM */
 125        tin_rate = pwm_calc_tin(pwm_id, frequency);
 126
 127        tin_ns = NS_IN_SEC / tin_rate;
 128
 129        if (IS_ENABLED(CONFIG_ARCH_NEXELL))
 130                /* The counter starts at zero. */
 131                tcnt = (period_ns / tin_ns) - 1;
 132        else
 133                tcnt = period_ns / tin_ns;
 134
 135        /* Note, counters count down */
 136        tcmp = duty_ns / tin_ns;
 137        tcmp = tcnt - tcmp;
 138
 139        /* Update the PWM register block. */
 140        offset = pwm_id * 3;
 141        if (pwm_id < 4) {
 142                writel(tcnt, &pwm->tcntb0 + offset);
 143                writel(tcmp, &pwm->tcmpb0 + offset);
 144        }
 145
 146        tcon = readl(&pwm->tcon);
 147        tcon |= TCON_UPDATE(pwm_id);
 148        if (pwm_id < 4)
 149                tcon |= TCON_AUTO_RELOAD(pwm_id);
 150        else
 151                tcon |= TCON4_AUTO_RELOAD;
 152        writel(tcon, &pwm->tcon);
 153
 154        tcon &= ~TCON_UPDATE(pwm_id);
 155        writel(tcon, &pwm->tcon);
 156
 157        return 0;
 158}
 159
 160int pwm_init(int pwm_id, int div, int invert)
 161{
 162        u32 val;
 163        const struct s5p_timer *pwm =
 164#if defined(CONFIG_ARCH_NEXELL)
 165                        (struct s5p_timer *)PHY_BASEADDR_PWM;
 166#else
 167                        (struct s5p_timer *)samsung_get_base_timer();
 168#endif
 169        unsigned long ticks_per_period;
 170        unsigned int offset, prescaler;
 171
 172        /*
 173         * Timer Freq(HZ) =
 174         *      PWM_CLK / { (prescaler_value + 1) * (divider_value) }
 175         */
 176
 177        val = readl(&pwm->tcfg0);
 178        if (pwm_id < 2) {
 179                prescaler = PRESCALER_0;
 180                val &= ~0xff;
 181                val |= (prescaler & 0xff);
 182        } else {
 183                prescaler = PRESCALER_1;
 184                val &= ~(0xff << 8);
 185                val |= (prescaler & 0xff) << 8;
 186        }
 187        writel(val, &pwm->tcfg0);
 188        val = readl(&pwm->tcfg1);
 189        val &= ~(0xf << MUX_DIV_SHIFT(pwm_id));
 190        val |= (div & 0xf) << MUX_DIV_SHIFT(pwm_id);
 191        writel(val, &pwm->tcfg1);
 192
 193        if (pwm_id == 4) {
 194                /*
 195                 * TODO(sjg): Use this as a countdown timer for now. We count
 196                 * down from the maximum value to 0, then reset.
 197                 */
 198                ticks_per_period = -1UL;
 199        } else {
 200                const unsigned long pwm_hz = 1000;
 201#if defined(CONFIG_ARCH_NEXELL)
 202                struct clk *clk = clk_get(CORECLK_NAME_PCLK);
 203                unsigned long timer_rate_hz = clk_get_rate(clk) /
 204#else
 205                unsigned long timer_rate_hz = get_pwm_clk() /
 206#endif
 207                        ((prescaler + 1) * (1 << div));
 208
 209                ticks_per_period = timer_rate_hz / pwm_hz;
 210        }
 211
 212        /* set count value */
 213        offset = pwm_id * 3;
 214
 215        writel(ticks_per_period, &pwm->tcntb0 + offset);
 216
 217        val = readl(&pwm->tcon) & ~(0xf << TCON_OFFSET(pwm_id));
 218        if (invert && (pwm_id < 4))
 219                val |= TCON_INVERTER(pwm_id);
 220        writel(val, &pwm->tcon);
 221
 222        pwm_enable(pwm_id);
 223
 224        return 0;
 225}
 226