linux/drivers/pwm/pwm-mxs.c
<<
>>
Prefs
   1/*
   2 * Copyright 2012 Freescale Semiconductor, Inc.
   3 *
   4 * The code contained herein is licensed under the GNU General Public
   5 * License. You may obtain a copy of the GNU General Public License
   6 * Version 2 or later at the following locations:
   7 *
   8 * http://www.opensource.org/licenses/gpl-license.html
   9 * http://www.gnu.org/copyleft/gpl.html
  10 */
  11
  12#include <linux/clk.h>
  13#include <linux/err.h>
  14#include <linux/io.h>
  15#include <linux/kernel.h>
  16#include <linux/module.h>
  17#include <linux/of.h>
  18#include <linux/of_address.h>
  19#include <linux/platform_device.h>
  20#include <linux/pwm.h>
  21#include <linux/slab.h>
  22#include <linux/stmp_device.h>
  23
  24#define SET     0x4
  25#define CLR     0x8
  26#define TOG     0xc
  27
  28#define PWM_CTRL                0x0
  29#define PWM_ACTIVE0             0x10
  30#define PWM_PERIOD0             0x20
  31#define  PERIOD_PERIOD(p)       ((p) & 0xffff)
  32#define  PERIOD_PERIOD_MAX      0x10000
  33#define  PERIOD_ACTIVE_HIGH     (3 << 16)
  34#define  PERIOD_INACTIVE_LOW    (2 << 18)
  35#define  PERIOD_CDIV(div)       (((div) & 0x7) << 20)
  36#define  PERIOD_CDIV_MAX        8
  37
  38struct mxs_pwm_chip {
  39        struct pwm_chip chip;
  40        struct clk *clk;
  41        void __iomem *base;
  42};
  43
  44#define to_mxs_pwm_chip(_chip) container_of(_chip, struct mxs_pwm_chip, chip)
  45
  46static int mxs_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
  47                          int duty_ns, int period_ns)
  48{
  49        struct mxs_pwm_chip *mxs = to_mxs_pwm_chip(chip);
  50        int ret, div = 0;
  51        unsigned int period_cycles, duty_cycles;
  52        unsigned long rate;
  53        unsigned long long c;
  54
  55        rate = clk_get_rate(mxs->clk);
  56        while (1) {
  57                c = rate / (1 << div);
  58                c = c * period_ns;
  59                do_div(c, 1000000000);
  60                if (c < PERIOD_PERIOD_MAX)
  61                        break;
  62                div++;
  63                if (div > PERIOD_CDIV_MAX)
  64                        return -EINVAL;
  65        }
  66
  67        period_cycles = c;
  68        c *= duty_ns;
  69        do_div(c, period_ns);
  70        duty_cycles = c;
  71
  72        /*
  73         * If the PWM channel is disabled, make sure to turn on the clock
  74         * before writing the register. Otherwise, keep it enabled.
  75         */
  76        if (!test_bit(PWMF_ENABLED, &pwm->flags)) {
  77                ret = clk_prepare_enable(mxs->clk);
  78                if (ret)
  79                        return ret;
  80        }
  81
  82        writel(duty_cycles << 16,
  83                        mxs->base + PWM_ACTIVE0 + pwm->hwpwm * 0x20);
  84        writel(PERIOD_PERIOD(period_cycles) | PERIOD_ACTIVE_HIGH |
  85               PERIOD_INACTIVE_LOW | PERIOD_CDIV(div),
  86                        mxs->base + PWM_PERIOD0 + pwm->hwpwm * 0x20);
  87
  88        /*
  89         * If the PWM is not enabled, turn the clock off again to save power.
  90         */
  91        if (!test_bit(PWMF_ENABLED, &pwm->flags))
  92                clk_disable_unprepare(mxs->clk);
  93
  94        return 0;
  95}
  96
  97static int mxs_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
  98{
  99        struct mxs_pwm_chip *mxs = to_mxs_pwm_chip(chip);
 100        int ret;
 101
 102        ret = clk_prepare_enable(mxs->clk);
 103        if (ret)
 104                return ret;
 105
 106        writel(1 << pwm->hwpwm, mxs->base + PWM_CTRL + SET);
 107
 108        return 0;
 109}
 110
 111static void mxs_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
 112{
 113        struct mxs_pwm_chip *mxs = to_mxs_pwm_chip(chip);
 114
 115        writel(1 << pwm->hwpwm, mxs->base + PWM_CTRL + CLR);
 116
 117        clk_disable_unprepare(mxs->clk);
 118}
 119
 120static const struct pwm_ops mxs_pwm_ops = {
 121        .config = mxs_pwm_config,
 122        .enable = mxs_pwm_enable,
 123        .disable = mxs_pwm_disable,
 124        .owner = THIS_MODULE,
 125};
 126
 127static int mxs_pwm_probe(struct platform_device *pdev)
 128{
 129        struct device_node *np = pdev->dev.of_node;
 130        struct mxs_pwm_chip *mxs;
 131        struct resource *res;
 132        int ret;
 133
 134        mxs = devm_kzalloc(&pdev->dev, sizeof(*mxs), GFP_KERNEL);
 135        if (!mxs)
 136                return -ENOMEM;
 137
 138        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 139        mxs->base = devm_ioremap_resource(&pdev->dev, res);
 140        if (IS_ERR(mxs->base))
 141                return PTR_ERR(mxs->base);
 142
 143        mxs->clk = devm_clk_get(&pdev->dev, NULL);
 144        if (IS_ERR(mxs->clk))
 145                return PTR_ERR(mxs->clk);
 146
 147        mxs->chip.dev = &pdev->dev;
 148        mxs->chip.ops = &mxs_pwm_ops;
 149        mxs->chip.base = -1;
 150        ret = of_property_read_u32(np, "fsl,pwm-number", &mxs->chip.npwm);
 151        if (ret < 0) {
 152                dev_err(&pdev->dev, "failed to get pwm number: %d\n", ret);
 153                return ret;
 154        }
 155
 156        ret = pwmchip_add(&mxs->chip);
 157        if (ret < 0) {
 158                dev_err(&pdev->dev, "failed to add pwm chip %d\n", ret);
 159                return ret;
 160        }
 161
 162        platform_set_drvdata(pdev, mxs);
 163
 164        stmp_reset_block(mxs->base);
 165
 166        return 0;
 167}
 168
 169static int mxs_pwm_remove(struct platform_device *pdev)
 170{
 171        struct mxs_pwm_chip *mxs = platform_get_drvdata(pdev);
 172
 173        return pwmchip_remove(&mxs->chip);
 174}
 175
 176static const struct of_device_id mxs_pwm_dt_ids[] = {
 177        { .compatible = "fsl,imx23-pwm", },
 178        { /* sentinel */ }
 179};
 180MODULE_DEVICE_TABLE(of, mxs_pwm_dt_ids);
 181
 182static struct platform_driver mxs_pwm_driver = {
 183        .driver = {
 184                .name = "mxs-pwm",
 185                .owner = THIS_MODULE,
 186                .of_match_table = of_match_ptr(mxs_pwm_dt_ids),
 187        },
 188        .probe = mxs_pwm_probe,
 189        .remove = mxs_pwm_remove,
 190};
 191module_platform_driver(mxs_pwm_driver);
 192
 193MODULE_ALIAS("platform:mxs-pwm");
 194MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>");
 195MODULE_DESCRIPTION("Freescale MXS PWM Driver");
 196MODULE_LICENSE("GPL v2");
 197
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.