uboot/drivers/pwm/exynos_pwm.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * Copyright 2016 Google Inc.
   4 */
   5
   6#include <common.h>
   7#include <dm.h>
   8#include <log.h>
   9#include <pwm.h>
  10#include <asm/io.h>
  11#include <asm/arch/clk.h>
  12#include <asm/arch/clock.h>
  13#include <asm/arch/pwm.h>
  14
  15struct exynos_pwm_priv {
  16        struct s5p_timer *regs;
  17};
  18
  19static int exynos_pwm_set_config(struct udevice *dev, uint channel,
  20                                uint period_ns, uint duty_ns)
  21{
  22        struct exynos_pwm_priv *priv = dev_get_priv(dev);
  23        struct s5p_timer *regs = priv->regs;
  24        unsigned int offset, prescaler;
  25        uint div = 4, rate, rate_ns;
  26        u32 val;
  27        u32 tcnt, tcmp, tcon;
  28
  29        if (channel >= 5)
  30                return -EINVAL;
  31        debug("%s: Configure '%s' channel %u, period_ns %u, duty_ns %u\n",
  32              __func__, dev->name, channel, period_ns, duty_ns);
  33
  34        val = readl(&regs->tcfg0);
  35        prescaler = (channel < 2 ? val : (val >> 8)) & 0xff;
  36        div = (readl(&regs->tcfg1) >> MUX_DIV_SHIFT(channel)) & 0xf;
  37
  38        rate = get_pwm_clk() / ((prescaler + 1) * (1 << div));
  39        debug("%s: pwm_clk %lu, rate %u\n", __func__, get_pwm_clk(), rate);
  40
  41        if (channel < 4) {
  42                rate_ns = 1000000000 / rate;
  43                tcnt = period_ns / rate_ns;
  44                tcmp = duty_ns / rate_ns;
  45                debug("%s: tcnt %u, tcmp %u\n", __func__, tcnt, tcmp);
  46
  47                /* Ensure that the comparitor will actually hit the target */
  48                if (tcmp == tcnt)
  49                        tcmp = tcnt - 1;
  50                offset = channel * 3;
  51                writel(tcnt, &regs->tcntb0 + offset);
  52                writel(tcmp, &regs->tcmpb0 + offset);
  53        }
  54
  55        tcon = readl(&regs->tcon);
  56        tcon |= TCON_UPDATE(channel);
  57        if (channel < 4)
  58                tcon |= TCON_AUTO_RELOAD(channel);
  59        else
  60                tcon |= TCON4_AUTO_RELOAD;
  61        writel(tcon, &regs->tcon);
  62
  63        tcon &= ~TCON_UPDATE(channel);
  64        writel(tcon, &regs->tcon);
  65
  66        return 0;
  67}
  68
  69static int exynos_pwm_set_enable(struct udevice *dev, uint channel,
  70                                 bool enable)
  71{
  72        struct exynos_pwm_priv *priv = dev_get_priv(dev);
  73        struct s5p_timer *regs = priv->regs;
  74        u32 mask;
  75
  76        if (channel >= 4)
  77                return -EINVAL;
  78        debug("%s: Enable '%s' channel %u\n", __func__, dev->name, channel);
  79        mask = TCON_START(channel);
  80        clrsetbits_le32(&regs->tcon, mask, enable ? mask : 0);
  81
  82        return 0;
  83}
  84
  85static int exynos_pwm_probe(struct udevice *dev)
  86{
  87        struct exynos_pwm_priv *priv = dev_get_priv(dev);
  88        struct s5p_timer *regs = priv->regs;
  89
  90        writel(PRESCALER_0 | PRESCALER_1 << 8, &regs->tcfg0);
  91
  92        return 0;
  93}
  94
  95static int exynos_pwm_of_to_plat(struct udevice *dev)
  96{
  97        struct exynos_pwm_priv *priv = dev_get_priv(dev);
  98
  99        priv->regs = dev_read_addr_ptr(dev);
 100
 101        return 0;
 102}
 103
 104static const struct pwm_ops exynos_pwm_ops = {
 105        .set_config     = exynos_pwm_set_config,
 106        .set_enable     = exynos_pwm_set_enable,
 107};
 108
 109static const struct udevice_id exynos_channels[] = {
 110        { .compatible = "samsung,exynos4210-pwm" },
 111        { }
 112};
 113
 114U_BOOT_DRIVER(exynos_pwm) = {
 115        .name   = "exynos_pwm",
 116        .id     = UCLASS_PWM,
 117        .of_match = exynos_channels,
 118        .ops    = &exynos_pwm_ops,
 119        .probe  = exynos_pwm_probe,
 120        .of_to_plat     = exynos_pwm_of_to_plat,
 121        .priv_auto      = sizeof(struct exynos_pwm_priv),
 122};
 123