uboot/drivers/pwm/pwm-sifive.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * Copyright (C) 2020 SiFive, Inc
   4 * For SiFive's PWM IP block documentation please refer Chapter 14 of
   5 * Reference Manual : https://static.dev.sifive.com/FU540-C000-v1.0.pdf
   6 *
   7 * Limitations:
   8 * - When changing both duty cycle and period, we cannot prevent in
   9 *   software that the output might produce a period with mixed
  10 *   settings (new period length and old duty cycle).
  11 * - The hardware cannot generate a 100% duty cycle.
  12 * - The hardware generates only inverted output.
  13 */
  14
  15#include <common.h>
  16#include <clk.h>
  17#include <div64.h>
  18#include <dm.h>
  19#include <pwm.h>
  20#include <regmap.h>
  21#include <asm/global_data.h>
  22#include <linux/io.h>
  23#include <linux/log2.h>
  24#include <linux/bitfield.h>
  25
  26/* PWMCFG fields */
  27#define PWM_SIFIVE_PWMCFG_SCALE         GENMASK(3, 0)
  28#define PWM_SIFIVE_PWMCFG_STICKY        BIT(8)
  29#define PWM_SIFIVE_PWMCFG_ZERO_CMP      BIT(9)
  30#define PWM_SIFIVE_PWMCFG_DEGLITCH      BIT(10)
  31#define PWM_SIFIVE_PWMCFG_EN_ALWAYS     BIT(12)
  32#define PWM_SIFIVE_PWMCFG_EN_ONCE       BIT(13)
  33#define PWM_SIFIVE_PWMCFG_CENTER        BIT(16)
  34#define PWM_SIFIVE_PWMCFG_GANG          BIT(24)
  35#define PWM_SIFIVE_PWMCFG_IP            BIT(28)
  36
  37/* PWM_SIFIVE_SIZE_PWMCMP is used to calculate offset for pwmcmpX registers */
  38#define PWM_SIFIVE_SIZE_PWMCMP          4
  39#define PWM_SIFIVE_CMPWIDTH             16
  40
  41#define PWM_SIFIVE_CHANNEL_ENABLE_VAL   0
  42#define PWM_SIFIVE_CHANNEL_DISABLE_VAL  0xffff
  43
  44DECLARE_GLOBAL_DATA_PTR;
  45
  46struct pwm_sifive_regs {
  47        unsigned long cfg;
  48        unsigned long cnt;
  49        unsigned long pwms;
  50        unsigned long cmp0;
  51};
  52
  53struct pwm_sifive_data {
  54        struct pwm_sifive_regs regs;
  55};
  56
  57struct pwm_sifive_priv {
  58        void __iomem *base;
  59        ulong freq;
  60        const struct pwm_sifive_data *data;
  61};
  62
  63static int pwm_sifive_set_config(struct udevice *dev, uint channel,
  64                                 uint period_ns, uint duty_ns)
  65{
  66        struct pwm_sifive_priv *priv = dev_get_priv(dev);
  67        const struct pwm_sifive_regs *regs = &priv->data->regs;
  68        unsigned long scale_pow;
  69        unsigned long long num;
  70        u32 scale, val = 0, frac;
  71
  72        debug("%s: period_ns=%u, duty_ns=%u\n", __func__, period_ns, duty_ns);
  73
  74        /*
  75         * The PWM unit is used with pwmzerocmp=0, so the only way to modify the
  76         * period length is using pwmscale which provides the number of bits the
  77         * counter is shifted before being feed to the comparators. A period
  78         * lasts (1 << (PWM_SIFIVE_CMPWIDTH + pwmscale)) clock ticks.
  79         * (1 << (PWM_SIFIVE_CMPWIDTH + scale)) * 10^9/rate = period
  80         */
  81        scale_pow = lldiv((uint64_t)priv->freq * period_ns, 1000000000);
  82        scale = clamp(ilog2(scale_pow) - PWM_SIFIVE_CMPWIDTH, 0, 0xf);
  83        val |= (FIELD_PREP(PWM_SIFIVE_PWMCFG_SCALE, scale) | PWM_SIFIVE_PWMCFG_EN_ALWAYS);
  84
  85        /*
  86         * The problem of output producing mixed setting as mentioned at top,
  87         * occurs here. To minimize the window for this problem, we are
  88         * calculating the register values first and then writing them
  89         * consecutively
  90         */
  91        num = (u64)duty_ns * (1U << PWM_SIFIVE_CMPWIDTH);
  92        frac = DIV_ROUND_CLOSEST_ULL(num, period_ns);
  93        frac = min(frac, (1U << PWM_SIFIVE_CMPWIDTH) - 1);
  94        frac = (1U << PWM_SIFIVE_CMPWIDTH) - 1 - frac;
  95
  96        writel(val, priv->base + regs->cfg);
  97        writel(frac, priv->base + regs->cmp0 + channel *
  98               PWM_SIFIVE_SIZE_PWMCMP);
  99
 100        return 0;
 101}
 102
 103static int pwm_sifive_set_enable(struct udevice *dev, uint channel, bool enable)
 104{
 105        struct pwm_sifive_priv *priv = dev_get_priv(dev);
 106        const struct pwm_sifive_regs *regs = &priv->data->regs;
 107
 108        debug("%s: Enable '%s'\n", __func__, dev->name);
 109
 110        if (enable)
 111                writel(PWM_SIFIVE_CHANNEL_ENABLE_VAL, priv->base +
 112                       regs->cmp0 + channel * PWM_SIFIVE_SIZE_PWMCMP);
 113        else
 114                writel(PWM_SIFIVE_CHANNEL_DISABLE_VAL, priv->base +
 115                       regs->cmp0 + channel * PWM_SIFIVE_SIZE_PWMCMP);
 116
 117        return 0;
 118}
 119
 120static int pwm_sifive_of_to_plat(struct udevice *dev)
 121{
 122        struct pwm_sifive_priv *priv = dev_get_priv(dev);
 123
 124        priv->base = dev_read_addr_ptr(dev);
 125
 126        return 0;
 127}
 128
 129static int pwm_sifive_probe(struct udevice *dev)
 130{
 131        struct pwm_sifive_priv *priv = dev_get_priv(dev);
 132        struct clk clk;
 133        int ret = 0;
 134
 135        ret = clk_get_by_index(dev, 0, &clk);
 136        if (ret < 0) {
 137                debug("%s get clock fail!\n", __func__);
 138                return -EINVAL;
 139        }
 140
 141        priv->freq = clk_get_rate(&clk);
 142        priv->data = (struct pwm_sifive_data *)dev_get_driver_data(dev);
 143
 144        return 0;
 145}
 146
 147static const struct pwm_ops pwm_sifive_ops = {
 148        .set_config     = pwm_sifive_set_config,
 149        .set_enable     = pwm_sifive_set_enable,
 150};
 151
 152static const struct pwm_sifive_data pwm_data = {
 153        .regs = {
 154                .cfg = 0x00,
 155                .cnt = 0x08,
 156                .pwms = 0x10,
 157                .cmp0 = 0x20,
 158        },
 159};
 160
 161static const struct udevice_id pwm_sifive_ids[] = {
 162        { .compatible = "sifive,pwm0", .data = (ulong)&pwm_data},
 163        { }
 164};
 165
 166U_BOOT_DRIVER(pwm_sifive) = {
 167        .name   = "pwm_sifive",
 168        .id     = UCLASS_PWM,
 169        .of_match = pwm_sifive_ids,
 170        .ops    = &pwm_sifive_ops,
 171        .of_to_plat     = pwm_sifive_of_to_plat,
 172        .probe          = pwm_sifive_probe,
 173        .priv_auto      = sizeof(struct pwm_sifive_priv),
 174};
 175