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