linux/drivers/pwm/pwm-bcm2835.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * Copyright 2014 Bart Tanghe <bart.tanghe@thomasmore.be>
   4 */
   5
   6#include <linux/clk.h>
   7#include <linux/err.h>
   8#include <linux/io.h>
   9#include <linux/module.h>
  10#include <linux/of.h>
  11#include <linux/platform_device.h>
  12#include <linux/pwm.h>
  13
  14#define PWM_CONTROL             0x000
  15#define PWM_CONTROL_SHIFT(x)    ((x) * 8)
  16#define PWM_CONTROL_MASK        0xff
  17#define PWM_MODE                0x80            /* set timer in PWM mode */
  18#define PWM_ENABLE              (1 << 0)
  19#define PWM_POLARITY            (1 << 4)
  20
  21#define PERIOD(x)               (((x) * 0x10) + 0x10)
  22#define DUTY(x)                 (((x) * 0x10) + 0x14)
  23
  24#define PERIOD_MIN              0x2
  25
  26struct bcm2835_pwm {
  27        struct pwm_chip chip;
  28        struct device *dev;
  29        void __iomem *base;
  30        struct clk *clk;
  31};
  32
  33static inline struct bcm2835_pwm *to_bcm2835_pwm(struct pwm_chip *chip)
  34{
  35        return container_of(chip, struct bcm2835_pwm, chip);
  36}
  37
  38static int bcm2835_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
  39{
  40        struct bcm2835_pwm *pc = to_bcm2835_pwm(chip);
  41        u32 value;
  42
  43        value = readl(pc->base + PWM_CONTROL);
  44        value &= ~(PWM_CONTROL_MASK << PWM_CONTROL_SHIFT(pwm->hwpwm));
  45        value |= (PWM_MODE << PWM_CONTROL_SHIFT(pwm->hwpwm));
  46        writel(value, pc->base + PWM_CONTROL);
  47
  48        return 0;
  49}
  50
  51static void bcm2835_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
  52{
  53        struct bcm2835_pwm *pc = to_bcm2835_pwm(chip);
  54        u32 value;
  55
  56        value = readl(pc->base + PWM_CONTROL);
  57        value &= ~(PWM_CONTROL_MASK << PWM_CONTROL_SHIFT(pwm->hwpwm));
  58        writel(value, pc->base + PWM_CONTROL);
  59}
  60
  61static int bcm2835_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
  62                             const struct pwm_state *state)
  63{
  64
  65        struct bcm2835_pwm *pc = to_bcm2835_pwm(chip);
  66        unsigned long rate = clk_get_rate(pc->clk);
  67        unsigned long long period;
  68        unsigned long scaler;
  69        u32 val;
  70
  71        if (!rate) {
  72                dev_err(pc->dev, "failed to get clock rate\n");
  73                return -EINVAL;
  74        }
  75
  76        scaler = DIV_ROUND_CLOSEST(NSEC_PER_SEC, rate);
  77        /* set period */
  78        period = DIV_ROUND_CLOSEST_ULL(state->period, scaler);
  79
  80        /* dont accept a period that is too small or has been truncated */
  81        if ((period < PERIOD_MIN) || (period > U32_MAX))
  82                return -EINVAL;
  83
  84        writel(period, pc->base + PERIOD(pwm->hwpwm));
  85
  86        /* set duty cycle */
  87        val = DIV_ROUND_CLOSEST_ULL(state->duty_cycle, scaler);
  88        writel(val, pc->base + DUTY(pwm->hwpwm));
  89
  90        /* set polarity */
  91        val = readl(pc->base + PWM_CONTROL);
  92
  93        if (state->polarity == PWM_POLARITY_NORMAL)
  94                val &= ~(PWM_POLARITY << PWM_CONTROL_SHIFT(pwm->hwpwm));
  95        else
  96                val |= PWM_POLARITY << PWM_CONTROL_SHIFT(pwm->hwpwm);
  97
  98        /* enable/disable */
  99        if (state->enabled)
 100                val |= PWM_ENABLE << PWM_CONTROL_SHIFT(pwm->hwpwm);
 101        else
 102                val &= ~(PWM_ENABLE << PWM_CONTROL_SHIFT(pwm->hwpwm));
 103
 104        writel(val, pc->base + PWM_CONTROL);
 105
 106        return 0;
 107}
 108
 109static const struct pwm_ops bcm2835_pwm_ops = {
 110        .request = bcm2835_pwm_request,
 111        .free = bcm2835_pwm_free,
 112        .apply = bcm2835_pwm_apply,
 113        .owner = THIS_MODULE,
 114};
 115
 116static int bcm2835_pwm_probe(struct platform_device *pdev)
 117{
 118        struct bcm2835_pwm *pc;
 119        int ret;
 120
 121        pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
 122        if (!pc)
 123                return -ENOMEM;
 124
 125        pc->dev = &pdev->dev;
 126
 127        pc->base = devm_platform_ioremap_resource(pdev, 0);
 128        if (IS_ERR(pc->base))
 129                return PTR_ERR(pc->base);
 130
 131        pc->clk = devm_clk_get(&pdev->dev, NULL);
 132        if (IS_ERR(pc->clk))
 133                return dev_err_probe(&pdev->dev, PTR_ERR(pc->clk),
 134                                     "clock not found\n");
 135
 136        ret = clk_prepare_enable(pc->clk);
 137        if (ret)
 138                return ret;
 139
 140        pc->chip.dev = &pdev->dev;
 141        pc->chip.ops = &bcm2835_pwm_ops;
 142        pc->chip.base = -1;
 143        pc->chip.npwm = 2;
 144        pc->chip.of_xlate = of_pwm_xlate_with_flags;
 145        pc->chip.of_pwm_n_cells = 3;
 146
 147        platform_set_drvdata(pdev, pc);
 148
 149        ret = pwmchip_add(&pc->chip);
 150        if (ret < 0)
 151                goto add_fail;
 152
 153        return 0;
 154
 155add_fail:
 156        clk_disable_unprepare(pc->clk);
 157        return ret;
 158}
 159
 160static int bcm2835_pwm_remove(struct platform_device *pdev)
 161{
 162        struct bcm2835_pwm *pc = platform_get_drvdata(pdev);
 163
 164        clk_disable_unprepare(pc->clk);
 165
 166        return pwmchip_remove(&pc->chip);
 167}
 168
 169static const struct of_device_id bcm2835_pwm_of_match[] = {
 170        { .compatible = "brcm,bcm2835-pwm", },
 171        { /* sentinel */ }
 172};
 173MODULE_DEVICE_TABLE(of, bcm2835_pwm_of_match);
 174
 175static struct platform_driver bcm2835_pwm_driver = {
 176        .driver = {
 177                .name = "bcm2835-pwm",
 178                .of_match_table = bcm2835_pwm_of_match,
 179        },
 180        .probe = bcm2835_pwm_probe,
 181        .remove = bcm2835_pwm_remove,
 182};
 183module_platform_driver(bcm2835_pwm_driver);
 184
 185MODULE_AUTHOR("Bart Tanghe <bart.tanghe@thomasmore.be>");
 186MODULE_DESCRIPTION("Broadcom BCM2835 PWM driver");
 187MODULE_LICENSE("GPL v2");
 188