linux/drivers/pwm/pwm-spear.c
<<
>>
Prefs
   1/*
   2 * ST Microelectronics SPEAr Pulse Width Modulator driver
   3 *
   4 * Copyright (C) 2012 ST Microelectronics
   5 * Shiraz Hashim <shiraz.hashim@st.com>
   6 *
   7 * This file is licensed under the terms of the GNU General Public
   8 * License version 2. This program is licensed "as is" without any
   9 * warranty of any kind, whether express or implied.
  10 */
  11
  12#include <linux/clk.h>
  13#include <linux/err.h>
  14#include <linux/io.h>
  15#include <linux/ioport.h>
  16#include <linux/kernel.h>
  17#include <linux/math64.h>
  18#include <linux/module.h>
  19#include <linux/of.h>
  20#include <linux/platform_device.h>
  21#include <linux/pwm.h>
  22#include <linux/slab.h>
  23#include <linux/types.h>
  24
  25#define NUM_PWM         4
  26
  27/* PWM registers and bits definitions */
  28#define PWMCR                   0x00    /* Control Register */
  29#define PWMCR_PWM_ENABLE        0x1
  30#define PWMCR_PRESCALE_SHIFT    2
  31#define PWMCR_MIN_PRESCALE      0x00
  32#define PWMCR_MAX_PRESCALE      0x3FFF
  33
  34#define PWMDCR                  0x04    /* Duty Cycle Register */
  35#define PWMDCR_MIN_DUTY         0x0001
  36#define PWMDCR_MAX_DUTY         0xFFFF
  37
  38#define PWMPCR                  0x08    /* Period Register */
  39#define PWMPCR_MIN_PERIOD       0x0001
  40#define PWMPCR_MAX_PERIOD       0xFFFF
  41
  42/* Following only available on 13xx SoCs */
  43#define PWMMCR                  0x3C    /* Master Control Register */
  44#define PWMMCR_PWM_ENABLE       0x1
  45
  46/**
  47 * struct spear_pwm_chip - struct representing pwm chip
  48 *
  49 * @mmio_base: base address of pwm chip
  50 * @clk: pointer to clk structure of pwm chip
  51 * @chip: linux pwm chip representation
  52 * @dev: pointer to device structure of pwm chip
  53 */
  54struct spear_pwm_chip {
  55        void __iomem *mmio_base;
  56        struct clk *clk;
  57        struct pwm_chip chip;
  58        struct device *dev;
  59};
  60
  61static inline struct spear_pwm_chip *to_spear_pwm_chip(struct pwm_chip *chip)
  62{
  63        return container_of(chip, struct spear_pwm_chip, chip);
  64}
  65
  66static inline u32 spear_pwm_readl(struct spear_pwm_chip *chip, unsigned int num,
  67                                  unsigned long offset)
  68{
  69        return readl_relaxed(chip->mmio_base + (num << 4) + offset);
  70}
  71
  72static inline void spear_pwm_writel(struct spear_pwm_chip *chip,
  73                                    unsigned int num, unsigned long offset,
  74                                    unsigned long val)
  75{
  76        writel_relaxed(val, chip->mmio_base + (num << 4) + offset);
  77}
  78
  79static int spear_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
  80                            int duty_ns, int period_ns)
  81{
  82        struct spear_pwm_chip *pc = to_spear_pwm_chip(chip);
  83        u64 val, div, clk_rate;
  84        unsigned long prescale = PWMCR_MIN_PRESCALE, pv, dc;
  85        int ret;
  86
  87        /*
  88         * Find pv, dc and prescale to suit duty_ns and period_ns. This is done
  89         * according to formulas described below:
  90         *
  91         * period_ns = 10^9 * (PRESCALE + 1) * PV / PWM_CLK_RATE
  92         * duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE
  93         *
  94         * PV = (PWM_CLK_RATE * period_ns) / (10^9 * (PRESCALE + 1))
  95         * DC = (PWM_CLK_RATE * duty_ns) / (10^9 * (PRESCALE + 1))
  96         */
  97        clk_rate = clk_get_rate(pc->clk);
  98        while (1) {
  99                div = 1000000000;
 100                div *= 1 + prescale;
 101                val = clk_rate * period_ns;
 102                pv = div64_u64(val, div);
 103                val = clk_rate * duty_ns;
 104                dc = div64_u64(val, div);
 105
 106                /* if duty_ns and period_ns are not achievable then return */
 107                if (pv < PWMPCR_MIN_PERIOD || dc < PWMDCR_MIN_DUTY)
 108                        return -EINVAL;
 109
 110                /*
 111                 * if pv and dc have crossed their upper limit, then increase
 112                 * prescale and recalculate pv and dc.
 113                 */
 114                if (pv > PWMPCR_MAX_PERIOD || dc > PWMDCR_MAX_DUTY) {
 115                        if (++prescale > PWMCR_MAX_PRESCALE)
 116                                return -EINVAL;
 117                        continue;
 118                }
 119                break;
 120        }
 121
 122        /*
 123         * NOTE: the clock to PWM has to be enabled first before writing to the
 124         * registers.
 125         */
 126        ret = clk_enable(pc->clk);
 127        if (ret)
 128                return ret;
 129
 130        spear_pwm_writel(pc, pwm->hwpwm, PWMCR,
 131                        prescale << PWMCR_PRESCALE_SHIFT);
 132        spear_pwm_writel(pc, pwm->hwpwm, PWMDCR, dc);
 133        spear_pwm_writel(pc, pwm->hwpwm, PWMPCR, pv);
 134        clk_disable(pc->clk);
 135
 136        return 0;
 137}
 138
 139static int spear_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
 140{
 141        struct spear_pwm_chip *pc = to_spear_pwm_chip(chip);
 142        int rc = 0;
 143        u32 val;
 144
 145        rc = clk_enable(pc->clk);
 146        if (!rc)
 147                return rc;
 148
 149        val = spear_pwm_readl(pc, pwm->hwpwm, PWMCR);
 150        val |= PWMCR_PWM_ENABLE;
 151        spear_pwm_writel(pc, pwm->hwpwm, PWMCR, val);
 152
 153        return 0;
 154}
 155
 156static void spear_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
 157{
 158        struct spear_pwm_chip *pc = to_spear_pwm_chip(chip);
 159        u32 val;
 160
 161        val = spear_pwm_readl(pc, pwm->hwpwm, PWMCR);
 162        val &= ~PWMCR_PWM_ENABLE;
 163        spear_pwm_writel(pc, pwm->hwpwm, PWMCR, val);
 164
 165        clk_disable(pc->clk);
 166}
 167
 168static const struct pwm_ops spear_pwm_ops = {
 169        .config = spear_pwm_config,
 170        .enable = spear_pwm_enable,
 171        .disable = spear_pwm_disable,
 172        .owner = THIS_MODULE,
 173};
 174
 175static int spear_pwm_probe(struct platform_device *pdev)
 176{
 177        struct device_node *np = pdev->dev.of_node;
 178        struct spear_pwm_chip *pc;
 179        struct resource *r;
 180        int ret;
 181        u32 val;
 182
 183        r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 184        if (!r) {
 185                dev_err(&pdev->dev, "no memory resources defined\n");
 186                return -ENODEV;
 187        }
 188
 189        pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
 190        if (!pc) {
 191                dev_err(&pdev->dev, "failed to allocate memory\n");
 192                return -ENOMEM;
 193        }
 194
 195        pc->mmio_base = devm_request_and_ioremap(&pdev->dev, r);
 196        if (!pc->mmio_base)
 197                return -EADDRNOTAVAIL;
 198
 199        pc->clk = devm_clk_get(&pdev->dev, NULL);
 200        if (IS_ERR(pc->clk))
 201                return PTR_ERR(pc->clk);
 202
 203        pc->dev = &pdev->dev;
 204        platform_set_drvdata(pdev, pc);
 205
 206        pc->chip.dev = &pdev->dev;
 207        pc->chip.ops = &spear_pwm_ops;
 208        pc->chip.base = -1;
 209        pc->chip.npwm = NUM_PWM;
 210
 211        ret = clk_prepare(pc->clk);
 212        if (!ret)
 213                return ret;
 214
 215        if (of_device_is_compatible(np, "st,spear1340-pwm")) {
 216                ret = clk_enable(pc->clk);
 217                if (!ret) {
 218                        clk_unprepare(pc->clk);
 219                        return ret;
 220                }
 221                /*
 222                 * Following enables PWM chip, channels would still be
 223                 * enabled individually through their control register
 224                 */
 225                val = readl_relaxed(pc->mmio_base + PWMMCR);
 226                val |= PWMMCR_PWM_ENABLE;
 227                writel_relaxed(val, pc->mmio_base + PWMMCR);
 228
 229                clk_disable(pc->clk);
 230        }
 231
 232        ret = pwmchip_add(&pc->chip);
 233        if (!ret) {
 234                clk_unprepare(pc->clk);
 235                dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
 236        }
 237
 238        return ret;
 239}
 240
 241static int spear_pwm_remove(struct platform_device *pdev)
 242{
 243        struct spear_pwm_chip *pc = platform_get_drvdata(pdev);
 244        int i;
 245
 246        for (i = 0; i < NUM_PWM; i++)
 247                pwm_disable(&pc->chip.pwms[i]);
 248
 249        /* clk was prepared in probe, hence unprepare it here */
 250        clk_unprepare(pc->clk);
 251        return pwmchip_remove(&pc->chip);
 252}
 253
 254static struct of_device_id spear_pwm_of_match[] = {
 255        { .compatible = "st,spear320-pwm" },
 256        { .compatible = "st,spear1340-pwm" },
 257        { }
 258};
 259
 260MODULE_DEVICE_TABLE(of, spear_pwm_of_match);
 261
 262static struct platform_driver spear_pwm_driver = {
 263        .driver = {
 264                .name = "spear-pwm",
 265                .of_match_table = spear_pwm_of_match,
 266        },
 267        .probe = spear_pwm_probe,
 268        .remove = spear_pwm_remove,
 269};
 270
 271module_platform_driver(spear_pwm_driver);
 272
 273MODULE_LICENSE("GPL");
 274MODULE_AUTHOR("Shiraz Hashim <shiraz.hashim@st.com>");
 275MODULE_AUTHOR("Viresh Kumar <viresh.kumar@linaro.com>");
 276MODULE_ALIAS("platform:spear-pwm");
 277
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.