uboot/drivers/pwm/pwm-at91.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * PWM support for Microchip AT91 architectures.
   4 *
   5 * Copyright (C) 2021 Microchip Technology Inc. and its subsidiaries
   6 *
   7 * Author: Dan Sneddon <daniel.sneddon@microchip.com>
   8 *
   9 * Based on drivers/pwm/pwm-atmel.c from Linux.
  10 */
  11#include <clk.h>
  12#include <common.h>
  13#include <div64.h>
  14#include <dm.h>
  15#include <linux/bitops.h>
  16#include <linux/io.h>
  17#include <pwm.h>
  18
  19#define PERIOD_BITS 16
  20#define PWM_MAX_PRES 10
  21#define NSEC_PER_SEC 1000000000L
  22
  23#define PWM_ENA 0x04
  24#define PWM_CHANNEL_OFFSET 0x20
  25#define PWM_CMR 0x200
  26#define PWM_CMR_CPRE_MSK GENMASK(3, 0)
  27#define PWM_CMR_CPOL BIT(9)
  28#define PWM_CDTY 0x204
  29#define PWM_CPRD 0x20C
  30
  31struct at91_pwm_priv {
  32        void __iomem *base;
  33        struct clk pclk;
  34        u32 clkrate;
  35};
  36
  37static int at91_pwm_calculate_cprd_and_pres(struct udevice *dev,
  38                                            unsigned long clkrate,
  39                                            uint period_ns, uint duty_ns,
  40                                            unsigned long *cprd, u32 *pres)
  41{
  42        u64 cycles = period_ns;
  43        int shift;
  44
  45        /* Calculate the period cycles and prescale value */
  46        cycles *= clkrate;
  47        do_div(cycles, NSEC_PER_SEC);
  48
  49        /*
  50         * The register for the period length is period_bits bits wide.
  51         * So for each bit the number of clock cycles is wider divide the input
  52         * clock frequency by two using pres and shift cprd accordingly.
  53         */
  54        shift = fls(cycles) - PERIOD_BITS;
  55
  56        if (shift > PWM_MAX_PRES) {
  57                return -EINVAL;
  58        } else if (shift > 0) {
  59                *pres = shift;
  60                cycles >>= *pres;
  61        } else {
  62                *pres = 0;
  63        }
  64
  65        *cprd = cycles;
  66
  67        return 0;
  68}
  69
  70static void at91_pwm_calculate_cdty(uint period_ns, uint duty_ns,
  71                                    unsigned long clkrate, unsigned long cprd,
  72                                     u32 pres, unsigned long *cdty)
  73{
  74        u64 cycles = duty_ns;
  75
  76        cycles *= clkrate;
  77        do_div(cycles, NSEC_PER_SEC);
  78        cycles >>= pres;
  79        *cdty = cprd - cycles;
  80}
  81
  82/**
  83 * Returns: channel status after set operation
  84 */
  85static bool at91_pwm_set(void __iomem *base, uint channel, bool enable)
  86{
  87        u32 val, cur_status;
  88
  89        val = ioread32(base + PWM_ENA);
  90        cur_status = !!(val & BIT(channel));
  91
  92        /* if channel is already in that state, do nothing */
  93        if (!(enable ^ cur_status))
  94                return cur_status;
  95
  96        if (enable)
  97                val |= BIT(channel);
  98        else
  99                val &= ~(BIT(channel));
 100
 101        iowrite32(val, base + PWM_ENA);
 102
 103        return cur_status;
 104}
 105
 106static int at91_pwm_set_enable(struct udevice *dev, uint channel, bool enable)
 107{
 108        struct at91_pwm_priv *priv = dev_get_priv(dev);
 109
 110        at91_pwm_set(priv->base, channel, enable);
 111
 112        return 0;
 113}
 114
 115static int at91_pwm_set_config(struct udevice *dev, uint channel,
 116                               uint period_ns, uint duty_ns)
 117{
 118        struct at91_pwm_priv *priv = dev_get_priv(dev);
 119        unsigned long cprd, cdty;
 120        u32 pres, val;
 121        int channel_enabled;
 122        int ret;
 123
 124        ret = at91_pwm_calculate_cprd_and_pres(dev, priv->clkrate, period_ns,
 125                                               duty_ns, &cprd, &pres);
 126        if (ret)
 127                return ret;
 128
 129        at91_pwm_calculate_cdty(period_ns, duty_ns, priv->clkrate, cprd, pres, &cdty);
 130
 131        /* disable the channel */
 132        channel_enabled = at91_pwm_set(priv->base, channel, false);
 133
 134        /* It is necessary to preserve CPOL, inside CMR */
 135        val = ioread32(priv->base + (channel * PWM_CHANNEL_OFFSET) + PWM_CMR);
 136        val = (val & ~PWM_CMR_CPRE_MSK) | (pres & PWM_CMR_CPRE_MSK);
 137        iowrite32(val, priv->base + (channel * PWM_CHANNEL_OFFSET) + PWM_CMR);
 138
 139        iowrite32(cprd, priv->base + (channel * PWM_CHANNEL_OFFSET) + PWM_CPRD);
 140
 141        iowrite32(cdty, priv->base + (channel * PWM_CHANNEL_OFFSET) + PWM_CDTY);
 142
 143        /* renable the channel if needed */
 144        if (channel_enabled)
 145                at91_pwm_set(priv->base, channel, true);
 146
 147        return 0;
 148}
 149
 150static int at91_pwm_set_invert(struct udevice *dev, uint channel,
 151                               bool polarity)
 152{
 153        struct at91_pwm_priv *priv = dev_get_priv(dev);
 154        u32 val;
 155
 156        val = ioread32(priv->base + (channel * PWM_CHANNEL_OFFSET) + PWM_CMR);
 157        if (polarity)
 158                val |= PWM_CMR_CPOL;
 159        else
 160                val &= ~PWM_CMR_CPOL;
 161        iowrite32(val, priv->base + (channel * PWM_CHANNEL_OFFSET) + PWM_CMR);
 162
 163        return 0;
 164}
 165
 166static int at91_pwm_probe(struct udevice *dev)
 167{
 168        struct at91_pwm_priv *priv = dev_get_priv(dev);
 169        int ret;
 170
 171        priv->base = dev_read_addr_ptr(dev);
 172        if (!priv->base)
 173                return -EINVAL;
 174
 175        ret = clk_get_by_index(dev, 0, &priv->pclk);
 176        if (ret)
 177                return ret;
 178
 179        /* clocks aren't ref-counted so just enabled them once here */
 180        ret = clk_enable(&priv->pclk);
 181        if (ret)
 182                return ret;
 183
 184        priv->clkrate = clk_get_rate(&priv->pclk);
 185
 186        return ret;
 187}
 188
 189static const struct pwm_ops at91_pwm_ops = {
 190        .set_config = at91_pwm_set_config,
 191        .set_enable = at91_pwm_set_enable,
 192        .set_invert = at91_pwm_set_invert,
 193};
 194
 195static const struct udevice_id at91_pwm_of_match[] = {
 196        { .compatible = "atmel,sama5d2-pwm" },
 197        { }
 198};
 199
 200U_BOOT_DRIVER(at91_pwm) = {
 201        .name = "at91_pwm",
 202        .id = UCLASS_PWM,
 203        .of_match = at91_pwm_of_match,
 204        .probe = at91_pwm_probe,
 205        .priv_auto = sizeof(struct at91_pwm_priv),
 206        .ops = &at91_pwm_ops,
 207};
 208