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
  38static const unsigned int cdiv[PERIOD_CDIV_MAX] = {
  39        1, 2, 4, 8, 16, 64, 256, 1024
  40};
  41
  42struct mxs_pwm_chip {
  43        struct pwm_chip chip;
  44        struct clk *clk;
  45        void __iomem *base;
  46};
  47
  48#define to_mxs_pwm_chip(_chip) container_of(_chip, struct mxs_pwm_chip, chip)
  49
  50static int mxs_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
  51                          int duty_ns, int period_ns)
  52{
  53        struct mxs_pwm_chip *mxs = to_mxs_pwm_chip(chip);
  54        int ret, div = 0;
  55        unsigned int period_cycles, duty_cycles;
  56        unsigned long rate;
  57        unsigned long long c;
  58
  59        rate = clk_get_rate(mxs->clk);
  60        while (1) {
  61                c = rate / cdiv[div];
  62                c = c * period_ns;
  63                do_div(c, 1000000000);
  64                if (c < PERIOD_PERIOD_MAX)
  65                        break;
  66                div++;
  67                if (div >= PERIOD_CDIV_MAX)
  68                        return -EINVAL;
  69        }
  70
  71        period_cycles = c;
  72        c *= duty_ns;
  73        do_div(c, period_ns);
  74        duty_cycles = c;
  75
  76        /*
  77         * If the PWM channel is disabled, make sure to turn on the clock
  78         * before writing the register. Otherwise, keep it enabled.
  79         */
  80        if (!pwm_is_enabled(pwm)) {
  81                ret = clk_prepare_enable(mxs->clk);
  82                if (ret)
  83                        return ret;
  84        }
  85
  86        writel(duty_cycles << 16,
  87                        mxs->base + PWM_ACTIVE0 + pwm->hwpwm * 0x20);
  88        writel(PERIOD_PERIOD(period_cycles) | PERIOD_ACTIVE_HIGH |
  89               PERIOD_INACTIVE_LOW | PERIOD_CDIV(div),
  90                        mxs->base + PWM_PERIOD0 + pwm->hwpwm * 0x20);
  91
  92        /*
  93         * If the PWM is not enabled, turn the clock off again to save power.
  94         */
  95        if (!pwm_is_enabled(pwm))
  96                clk_disable_unprepare(mxs->clk);
  97
  98        return 0;
  99}
 100
 101static int mxs_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
 102{
 103        struct mxs_pwm_chip *mxs = to_mxs_pwm_chip(chip);
 104        int ret;
 105
 106        ret = clk_prepare_enable(mxs->clk);
 107        if (ret)
 108                return ret;
 109
 110        writel(1 << pwm->hwpwm, mxs->base + PWM_CTRL + SET);
 111
 112        return 0;
 113}
 114
 115static void mxs_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
 116{
 117        struct mxs_pwm_chip *mxs = to_mxs_pwm_chip(chip);
 118
 119        writel(1 << pwm->hwpwm, mxs->base + PWM_CTRL + CLR);
 120
 121        clk_disable_unprepare(mxs->clk);
 122}
 123
 124static const struct pwm_ops mxs_pwm_ops = {
 125        .config = mxs_pwm_config,
 126        .enable = mxs_pwm_enable,
 127        .disable = mxs_pwm_disable,
 128        .owner = THIS_MODULE,
 129};
 130
 131static int mxs_pwm_probe(struct platform_device *pdev)
 132{
 133        struct device_node *np = pdev->dev.of_node;
 134        struct mxs_pwm_chip *mxs;
 135        struct resource *res;
 136        int ret;
 137
 138        mxs = devm_kzalloc(&pdev->dev, sizeof(*mxs), GFP_KERNEL);
 139        if (!mxs)
 140                return -ENOMEM;
 141
 142        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 143        mxs->base = devm_ioremap_resource(&pdev->dev, res);
 144        if (IS_ERR(mxs->base))
 145                return PTR_ERR(mxs->base);
 146
 147        mxs->clk = devm_clk_get(&pdev->dev, NULL);
 148        if (IS_ERR(mxs->clk))
 149                return PTR_ERR(mxs->clk);
 150
 151        mxs->chip.dev = &pdev->dev;
 152        mxs->chip.ops = &mxs_pwm_ops;
 153        mxs->chip.base = -1;
 154        mxs->chip.can_sleep = true;
 155        ret = of_property_read_u32(np, "fsl,pwm-number", &mxs->chip.npwm);
 156        if (ret < 0) {
 157                dev_err(&pdev->dev, "failed to get pwm number: %d\n", ret);
 158                return ret;
 159        }
 160
 161        ret = pwmchip_add(&mxs->chip);
 162        if (ret < 0) {
 163                dev_err(&pdev->dev, "failed to add pwm chip %d\n", ret);
 164                return ret;
 165        }
 166
 167        platform_set_drvdata(pdev, mxs);
 168
 169        ret = stmp_reset_block(mxs->base);
 170        if (ret)
 171                goto pwm_remove;
 172
 173        return 0;
 174
 175pwm_remove:
 176        pwmchip_remove(&mxs->chip);
 177        return ret;
 178}
 179
 180static int mxs_pwm_remove(struct platform_device *pdev)
 181{
 182        struct mxs_pwm_chip *mxs = platform_get_drvdata(pdev);
 183
 184        return pwmchip_remove(&mxs->chip);
 185}
 186
 187static const struct of_device_id mxs_pwm_dt_ids[] = {
 188        { .compatible = "fsl,imx23-pwm", },
 189        { /* sentinel */ }
 190};
 191MODULE_DEVICE_TABLE(of, mxs_pwm_dt_ids);
 192
 193static struct platform_driver mxs_pwm_driver = {
 194        .driver = {
 195                .name = "mxs-pwm",
 196                .of_match_table = mxs_pwm_dt_ids,
 197        },
 198        .probe = mxs_pwm_probe,
 199        .remove = mxs_pwm_remove,
 200};
 201module_platform_driver(mxs_pwm_driver);
 202
 203MODULE_ALIAS("platform:mxs-pwm");
 204MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>");
 205MODULE_DESCRIPTION("Freescale MXS PWM Driver");
 206MODULE_LICENSE("GPL v2");
 207