linux/drivers/pwm/pwm-imx.c
<<
>>
Prefs
   1/*
   2 * simple driver for PWM (Pulse Width Modulator) controller
   3 *
   4 * This program is free software; you can redistribute it and/or modify
   5 * it under the terms of the GNU General Public License version 2 as
   6 * published by the Free Software Foundation.
   7 *
   8 * Derived from pxa PWM driver by eric miao <eric.miao@marvell.com>
   9 */
  10
  11#include <linux/module.h>
  12#include <linux/kernel.h>
  13#include <linux/platform_device.h>
  14#include <linux/slab.h>
  15#include <linux/err.h>
  16#include <linux/clk.h>
  17#include <linux/io.h>
  18#include <linux/pwm.h>
  19#include <linux/of_device.h>
  20
  21/* i.MX1 and i.MX21 share the same PWM function block: */
  22
  23#define MX1_PWMC    0x00   /* PWM Control Register */
  24#define MX1_PWMS    0x04   /* PWM Sample Register */
  25#define MX1_PWMP    0x08   /* PWM Period Register */
  26
  27#define MX1_PWMC_EN             (1 << 4)
  28
  29/* i.MX27, i.MX31, i.MX35 share the same PWM function block: */
  30
  31#define MX3_PWMCR                 0x00    /* PWM Control Register */
  32#define MX3_PWMSAR                0x0C    /* PWM Sample Register */
  33#define MX3_PWMPR                 0x10    /* PWM Period Register */
  34#define MX3_PWMCR_PRESCALER(x)    (((x - 1) & 0xFFF) << 4)
  35#define MX3_PWMCR_DOZEEN                (1 << 24)
  36#define MX3_PWMCR_WAITEN                (1 << 23)
  37#define MX3_PWMCR_DBGEN                 (1 << 22)
  38#define MX3_PWMCR_CLKSRC_IPG_HIGH (2 << 16)
  39#define MX3_PWMCR_CLKSRC_IPG      (1 << 16)
  40#define MX3_PWMCR_EN              (1 << 0)
  41
  42struct imx_chip {
  43        struct clk      *clk_per;
  44        struct clk      *clk_ipg;
  45
  46        int             enabled;
  47        void __iomem    *mmio_base;
  48
  49        struct pwm_chip chip;
  50
  51        int (*config)(struct pwm_chip *chip,
  52                struct pwm_device *pwm, int duty_ns, int period_ns);
  53        void (*set_enable)(struct pwm_chip *chip, bool enable);
  54};
  55
  56#define to_imx_chip(chip)       container_of(chip, struct imx_chip, chip)
  57
  58static int imx_pwm_config_v1(struct pwm_chip *chip,
  59                struct pwm_device *pwm, int duty_ns, int period_ns)
  60{
  61        struct imx_chip *imx = to_imx_chip(chip);
  62
  63        /*
  64         * The PWM subsystem allows for exact frequencies. However,
  65         * I cannot connect a scope on my device to the PWM line and
  66         * thus cannot provide the program the PWM controller
  67         * exactly. Instead, I'm relying on the fact that the
  68         * Bootloader (u-boot or WinCE+haret) has programmed the PWM
  69         * function group already. So I'll just modify the PWM sample
  70         * register to follow the ratio of duty_ns vs. period_ns
  71         * accordingly.
  72         *
  73         * This is good enough for programming the brightness of
  74         * the LCD backlight.
  75         *
  76         * The real implementation would divide PERCLK[0] first by
  77         * both the prescaler (/1 .. /128) and then by CLKSEL
  78         * (/2 .. /16).
  79         */
  80        u32 max = readl(imx->mmio_base + MX1_PWMP);
  81        u32 p = max * duty_ns / period_ns;
  82        writel(max - p, imx->mmio_base + MX1_PWMS);
  83
  84        return 0;
  85}
  86
  87static void imx_pwm_set_enable_v1(struct pwm_chip *chip, bool enable)
  88{
  89        struct imx_chip *imx = to_imx_chip(chip);
  90        u32 val;
  91
  92        val = readl(imx->mmio_base + MX1_PWMC);
  93
  94        if (enable)
  95                val |= MX1_PWMC_EN;
  96        else
  97                val &= ~MX1_PWMC_EN;
  98
  99        writel(val, imx->mmio_base + MX1_PWMC);
 100}
 101
 102static int imx_pwm_config_v2(struct pwm_chip *chip,
 103                struct pwm_device *pwm, int duty_ns, int period_ns)
 104{
 105        struct imx_chip *imx = to_imx_chip(chip);
 106        unsigned long long c;
 107        unsigned long period_cycles, duty_cycles, prescale;
 108        u32 cr;
 109
 110        c = clk_get_rate(imx->clk_per);
 111        c = c * period_ns;
 112        do_div(c, 1000000000);
 113        period_cycles = c;
 114
 115        prescale = period_cycles / 0x10000 + 1;
 116
 117        period_cycles /= prescale;
 118        c = (unsigned long long)period_cycles * duty_ns;
 119        do_div(c, period_ns);
 120        duty_cycles = c;
 121
 122        /*
 123         * according to imx pwm RM, the real period value should be
 124         * PERIOD value in PWMPR plus 2.
 125         */
 126        if (period_cycles > 2)
 127                period_cycles -= 2;
 128        else
 129                period_cycles = 0;
 130
 131        writel(duty_cycles, imx->mmio_base + MX3_PWMSAR);
 132        writel(period_cycles, imx->mmio_base + MX3_PWMPR);
 133
 134        cr = MX3_PWMCR_PRESCALER(prescale) |
 135                MX3_PWMCR_DOZEEN | MX3_PWMCR_WAITEN |
 136                MX3_PWMCR_DBGEN | MX3_PWMCR_CLKSRC_IPG_HIGH;
 137
 138        if (imx->enabled)
 139                cr |= MX3_PWMCR_EN;
 140
 141        writel(cr, imx->mmio_base + MX3_PWMCR);
 142
 143        return 0;
 144}
 145
 146static void imx_pwm_set_enable_v2(struct pwm_chip *chip, bool enable)
 147{
 148        struct imx_chip *imx = to_imx_chip(chip);
 149        u32 val;
 150
 151        val = readl(imx->mmio_base + MX3_PWMCR);
 152
 153        if (enable)
 154                val |= MX3_PWMCR_EN;
 155        else
 156                val &= ~MX3_PWMCR_EN;
 157
 158        writel(val, imx->mmio_base + MX3_PWMCR);
 159}
 160
 161static int imx_pwm_config(struct pwm_chip *chip,
 162                struct pwm_device *pwm, int duty_ns, int period_ns)
 163{
 164        struct imx_chip *imx = to_imx_chip(chip);
 165        int ret;
 166
 167        ret = clk_prepare_enable(imx->clk_ipg);
 168        if (ret)
 169                return ret;
 170
 171        ret = imx->config(chip, pwm, duty_ns, period_ns);
 172
 173        clk_disable_unprepare(imx->clk_ipg);
 174
 175        return ret;
 176}
 177
 178static int imx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
 179{
 180        struct imx_chip *imx = to_imx_chip(chip);
 181        int ret;
 182
 183        ret = clk_prepare_enable(imx->clk_per);
 184        if (ret)
 185                return ret;
 186
 187        imx->set_enable(chip, true);
 188
 189        imx->enabled = 1;
 190
 191        return 0;
 192}
 193
 194static void imx_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
 195{
 196        struct imx_chip *imx = to_imx_chip(chip);
 197
 198        imx->set_enable(chip, false);
 199
 200        clk_disable_unprepare(imx->clk_per);
 201        imx->enabled = 0;
 202}
 203
 204static struct pwm_ops imx_pwm_ops = {
 205        .enable = imx_pwm_enable,
 206        .disable = imx_pwm_disable,
 207        .config = imx_pwm_config,
 208        .owner = THIS_MODULE,
 209};
 210
 211struct imx_pwm_data {
 212        int (*config)(struct pwm_chip *chip,
 213                struct pwm_device *pwm, int duty_ns, int period_ns);
 214        void (*set_enable)(struct pwm_chip *chip, bool enable);
 215};
 216
 217static struct imx_pwm_data imx_pwm_data_v1 = {
 218        .config = imx_pwm_config_v1,
 219        .set_enable = imx_pwm_set_enable_v1,
 220};
 221
 222static struct imx_pwm_data imx_pwm_data_v2 = {
 223        .config = imx_pwm_config_v2,
 224        .set_enable = imx_pwm_set_enable_v2,
 225};
 226
 227static const struct of_device_id imx_pwm_dt_ids[] = {
 228        { .compatible = "fsl,imx1-pwm", .data = &imx_pwm_data_v1, },
 229        { .compatible = "fsl,imx27-pwm", .data = &imx_pwm_data_v2, },
 230        { /* sentinel */ }
 231};
 232MODULE_DEVICE_TABLE(of, imx_pwm_dt_ids);
 233
 234static int imx_pwm_probe(struct platform_device *pdev)
 235{
 236        const struct of_device_id *of_id =
 237                        of_match_device(imx_pwm_dt_ids, &pdev->dev);
 238        const struct imx_pwm_data *data;
 239        struct imx_chip *imx;
 240        struct resource *r;
 241        int ret = 0;
 242
 243        if (!of_id)
 244                return -ENODEV;
 245
 246        imx = devm_kzalloc(&pdev->dev, sizeof(*imx), GFP_KERNEL);
 247        if (imx == NULL) {
 248                dev_err(&pdev->dev, "failed to allocate memory\n");
 249                return -ENOMEM;
 250        }
 251
 252        imx->clk_per = devm_clk_get(&pdev->dev, "per");
 253        if (IS_ERR(imx->clk_per)) {
 254                dev_err(&pdev->dev, "getting per clock failed with %ld\n",
 255                                PTR_ERR(imx->clk_per));
 256                return PTR_ERR(imx->clk_per);
 257        }
 258
 259        imx->clk_ipg = devm_clk_get(&pdev->dev, "ipg");
 260        if (IS_ERR(imx->clk_ipg)) {
 261                dev_err(&pdev->dev, "getting ipg clock failed with %ld\n",
 262                                PTR_ERR(imx->clk_ipg));
 263                return PTR_ERR(imx->clk_ipg);
 264        }
 265
 266        imx->chip.ops = &imx_pwm_ops;
 267        imx->chip.dev = &pdev->dev;
 268        imx->chip.base = -1;
 269        imx->chip.npwm = 1;
 270
 271        r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 272        if (r == NULL) {
 273                dev_err(&pdev->dev, "no memory resource defined\n");
 274                return -ENODEV;
 275        }
 276
 277        imx->mmio_base = devm_request_and_ioremap(&pdev->dev, r);
 278        if (imx->mmio_base == NULL)
 279                return -EADDRNOTAVAIL;
 280
 281        data = of_id->data;
 282        imx->config = data->config;
 283        imx->set_enable = data->set_enable;
 284
 285        ret = pwmchip_add(&imx->chip);
 286        if (ret < 0)
 287                return ret;
 288
 289        platform_set_drvdata(pdev, imx);
 290        return 0;
 291}
 292
 293static int imx_pwm_remove(struct platform_device *pdev)
 294{
 295        struct imx_chip *imx;
 296
 297        imx = platform_get_drvdata(pdev);
 298        if (imx == NULL)
 299                return -ENODEV;
 300
 301        return pwmchip_remove(&imx->chip);
 302}
 303
 304static struct platform_driver imx_pwm_driver = {
 305        .driver         = {
 306                .name   = "imx-pwm",
 307                .of_match_table = of_match_ptr(imx_pwm_dt_ids),
 308        },
 309        .probe          = imx_pwm_probe,
 310        .remove         = imx_pwm_remove,
 311};
 312
 313module_platform_driver(imx_pwm_driver);
 314
 315MODULE_LICENSE("GPL v2");
 316MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
 317
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.