uboot/drivers/pwm/sunxi_pwm.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * Copyright (c) 2017-2018 Vasily Khoruzhick <anarsoul@gmail.com>
   4 */
   5
   6#include <common.h>
   7#include <div64.h>
   8#include <dm.h>
   9#include <log.h>
  10#include <pwm.h>
  11#include <regmap.h>
  12#include <syscon.h>
  13#include <asm/global_data.h>
  14#include <asm/io.h>
  15#include <asm/arch/pwm.h>
  16#include <asm/arch/gpio.h>
  17#include <power/regulator.h>
  18
  19DECLARE_GLOBAL_DATA_PTR;
  20
  21#define OSC_24MHZ 24000000
  22
  23struct sunxi_pwm_priv {
  24        struct sunxi_pwm *regs;
  25        bool invert;
  26        u32 prescaler;
  27};
  28
  29static const u32 prescaler_table[] = {
  30        120,    /* 0000 */
  31        180,    /* 0001 */
  32        240,    /* 0010 */
  33        360,    /* 0011 */
  34        480,    /* 0100 */
  35        0,      /* 0101 */
  36        0,      /* 0110 */
  37        0,      /* 0111 */
  38        12000,  /* 1000 */
  39        24000,  /* 1001 */
  40        36000,  /* 1010 */
  41        48000,  /* 1011 */
  42        72000,  /* 1100 */
  43        0,      /* 1101 */
  44        0,      /* 1110 */
  45        1,      /* 1111 */
  46};
  47
  48static int sunxi_pwm_config_pinmux(void)
  49{
  50#ifdef CONFIG_MACH_SUN50I
  51        sunxi_gpio_set_cfgpin(SUNXI_GPD(22), SUNXI_GPD_PWM);
  52#endif
  53        return 0;
  54}
  55
  56static int sunxi_pwm_set_invert(struct udevice *dev, uint channel,
  57                                bool polarity)
  58{
  59        struct sunxi_pwm_priv *priv = dev_get_priv(dev);
  60
  61        debug("%s: polarity=%u\n", __func__, polarity);
  62        priv->invert = polarity;
  63
  64        return 0;
  65}
  66
  67static int sunxi_pwm_set_config(struct udevice *dev, uint channel,
  68                                uint period_ns, uint duty_ns)
  69{
  70        struct sunxi_pwm_priv *priv = dev_get_priv(dev);
  71        struct sunxi_pwm *regs = priv->regs;
  72        int best_prescaler = 0;
  73        u32 v, best_period = 0, duty;
  74        u64 best_scaled_freq = 0;
  75        const u32 nsecs_per_sec = 1000000000U;
  76
  77        debug("%s: period_ns=%u, duty_ns=%u\n", __func__, period_ns, duty_ns);
  78
  79        for (int prescaler = 0; prescaler <= SUNXI_PWM_CTRL_PRESCALE0_MASK;
  80             prescaler++) {
  81                u32 period = 0;
  82                u64 scaled_freq = 0;
  83                if (!prescaler_table[prescaler])
  84                        continue;
  85                scaled_freq = lldiv(OSC_24MHZ, prescaler_table[prescaler]);
  86                period = lldiv(scaled_freq * period_ns, nsecs_per_sec);
  87                if ((period - 1 <= SUNXI_PWM_CH0_PERIOD_MAX) &&
  88                    best_period < period) {
  89                        best_period = period;
  90                        best_scaled_freq = scaled_freq;
  91                        best_prescaler = prescaler;
  92                }
  93        }
  94
  95        if (best_period - 1 > SUNXI_PWM_CH0_PERIOD_MAX) {
  96                debug("%s: failed to find prescaler value\n", __func__);
  97                return -EINVAL;
  98        }
  99
 100        duty = lldiv(best_scaled_freq * duty_ns, nsecs_per_sec);
 101
 102        if (priv->prescaler != best_prescaler) {
 103                /* Mask clock to update prescaler */
 104                v = readl(&regs->ctrl);
 105                v &= ~SUNXI_PWM_CTRL_CLK_GATE;
 106                writel(v, &regs->ctrl);
 107                v &= ~SUNXI_PWM_CTRL_PRESCALE0_MASK;
 108                v |= (best_prescaler & SUNXI_PWM_CTRL_PRESCALE0_MASK);
 109                writel(v, &regs->ctrl);
 110                v |= SUNXI_PWM_CTRL_CLK_GATE;
 111                writel(v, &regs->ctrl);
 112                priv->prescaler = best_prescaler;
 113        }
 114
 115        writel(SUNXI_PWM_CH0_PERIOD_PRD(best_period) |
 116               SUNXI_PWM_CH0_PERIOD_DUTY(duty), &regs->ch0_period);
 117
 118        debug("%s: prescaler: %d, period: %d, duty: %d\n",
 119              __func__, priv->prescaler,
 120              best_period, duty);
 121
 122        return 0;
 123}
 124
 125static int sunxi_pwm_set_enable(struct udevice *dev, uint channel, bool enable)
 126{
 127        struct sunxi_pwm_priv *priv = dev_get_priv(dev);
 128        struct sunxi_pwm *regs = priv->regs;
 129        u32 v;
 130
 131        debug("%s: Enable '%s'\n", __func__, dev->name);
 132
 133        v = readl(&regs->ctrl);
 134        if (!enable) {
 135                v &= ~SUNXI_PWM_CTRL_ENABLE0;
 136                writel(v, &regs->ctrl);
 137                return 0;
 138        }
 139
 140        sunxi_pwm_config_pinmux();
 141
 142        if (priv->invert)
 143                v &= ~SUNXI_PWM_CTRL_CH0_ACT_STA;
 144        else
 145                v |= SUNXI_PWM_CTRL_CH0_ACT_STA;
 146        v |= SUNXI_PWM_CTRL_ENABLE0;
 147        writel(v, &regs->ctrl);
 148
 149        return 0;
 150}
 151
 152static int sunxi_pwm_of_to_plat(struct udevice *dev)
 153{
 154        struct sunxi_pwm_priv *priv = dev_get_priv(dev);
 155
 156        priv->regs = dev_read_addr_ptr(dev);
 157
 158        return 0;
 159}
 160
 161static int sunxi_pwm_probe(struct udevice *dev)
 162{
 163        return 0;
 164}
 165
 166static const struct pwm_ops sunxi_pwm_ops = {
 167        .set_invert     = sunxi_pwm_set_invert,
 168        .set_config     = sunxi_pwm_set_config,
 169        .set_enable     = sunxi_pwm_set_enable,
 170};
 171
 172static const struct udevice_id sunxi_pwm_ids[] = {
 173        { .compatible = "allwinner,sun5i-a13-pwm" },
 174        { .compatible = "allwinner,sun50i-a64-pwm" },
 175        { }
 176};
 177
 178U_BOOT_DRIVER(sunxi_pwm) = {
 179        .name   = "sunxi_pwm",
 180        .id     = UCLASS_PWM,
 181        .of_match = sunxi_pwm_ids,
 182        .ops    = &sunxi_pwm_ops,
 183        .of_to_plat     = sunxi_pwm_of_to_plat,
 184        .probe          = sunxi_pwm_probe,
 185        .priv_auto      = sizeof(struct sunxi_pwm_priv),
 186};
 187