linux/arch/arm/plat-pxa/pwm.c
<<
>>
Prefs
   1/*
   2 * linux/arch/arm/mach-pxa/pwm.c
   3 *
   4 * simple driver for PWM (Pulse Width Modulator) controller
   5 *
   6 * This program is free software; you can redistribute it and/or modify
   7 * it under the terms of the GNU General Public License version 2 as
   8 * published by the Free Software Foundation.
   9 *
  10 * 2008-02-13   initial version
  11 *              eric miao <eric.miao@marvell.com>
  12 */
  13
  14#include <linux/module.h>
  15#include <linux/kernel.h>
  16#include <linux/platform_device.h>
  17#include <linux/slab.h>
  18#include <linux/err.h>
  19#include <linux/clk.h>
  20#include <linux/io.h>
  21#include <linux/pwm.h>
  22
  23#include <asm/div64.h>
  24
  25#define HAS_SECONDARY_PWM       0x10
  26#define PWM_ID_BASE(d)          ((d) & 0xf)
  27
  28static const struct platform_device_id pwm_id_table[] = {
  29        /*   PWM    has_secondary_pwm? */
  30        { "pxa25x-pwm", 0 },
  31        { "pxa27x-pwm", 0 | HAS_SECONDARY_PWM },
  32        { "pxa168-pwm", 1 },
  33        { "pxa910-pwm", 1 },
  34        { },
  35};
  36MODULE_DEVICE_TABLE(platform, pwm_id_table);
  37
  38/* PWM registers and bits definitions */
  39#define PWMCR           (0x00)
  40#define PWMDCR          (0x04)
  41#define PWMPCR          (0x08)
  42
  43#define PWMCR_SD        (1 << 6)
  44#define PWMDCR_FD       (1 << 10)
  45
  46struct pwm_device {
  47        struct list_head        node;
  48        struct pwm_device       *secondary;
  49        struct platform_device  *pdev;
  50
  51        const char      *label;
  52        struct clk      *clk;
  53        int             clk_enabled;
  54        void __iomem    *mmio_base;
  55
  56        unsigned int    use_count;
  57        unsigned int    pwm_id;
  58};
  59
  60/*
  61 * period_ns = 10^9 * (PRESCALE + 1) * (PV + 1) / PWM_CLK_RATE
  62 * duty_ns   = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE
  63 */
  64int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
  65{
  66        unsigned long long c;
  67        unsigned long period_cycles, prescale, pv, dc;
  68
  69        if (pwm == NULL || period_ns == 0 || duty_ns > period_ns)
  70                return -EINVAL;
  71
  72        c = clk_get_rate(pwm->clk);
  73        c = c * period_ns;
  74        do_div(c, 1000000000);
  75        period_cycles = c;
  76
  77        if (period_cycles < 1)
  78                period_cycles = 1;
  79        prescale = (period_cycles - 1) / 1024;
  80        pv = period_cycles / (prescale + 1) - 1;
  81
  82        if (prescale > 63)
  83                return -EINVAL;
  84
  85        if (duty_ns == period_ns)
  86                dc = PWMDCR_FD;
  87        else
  88                dc = (pv + 1) * duty_ns / period_ns;
  89
  90        /* NOTE: the clock to PWM has to be enabled first
  91         * before writing to the registers
  92         */
  93        clk_enable(pwm->clk);
  94        __raw_writel(prescale, pwm->mmio_base + PWMCR);
  95        __raw_writel(dc, pwm->mmio_base + PWMDCR);
  96        __raw_writel(pv, pwm->mmio_base + PWMPCR);
  97        clk_disable(pwm->clk);
  98
  99        return 0;
 100}
 101EXPORT_SYMBOL(pwm_config);
 102
 103int pwm_enable(struct pwm_device *pwm)
 104{
 105        int rc = 0;
 106
 107        if (!pwm->clk_enabled) {
 108                rc = clk_enable(pwm->clk);
 109                if (!rc)
 110                        pwm->clk_enabled = 1;
 111        }
 112        return rc;
 113}
 114EXPORT_SYMBOL(pwm_enable);
 115
 116void pwm_disable(struct pwm_device *pwm)
 117{
 118        if (pwm->clk_enabled) {
 119                clk_disable(pwm->clk);
 120                pwm->clk_enabled = 0;
 121        }
 122}
 123EXPORT_SYMBOL(pwm_disable);
 124
 125static DEFINE_MUTEX(pwm_lock);
 126static LIST_HEAD(pwm_list);
 127
 128struct pwm_device *pwm_request(int pwm_id, const char *label)
 129{
 130        struct pwm_device *pwm;
 131        int found = 0;
 132
 133        mutex_lock(&pwm_lock);
 134
 135        list_for_each_entry(pwm, &pwm_list, node) {
 136                if (pwm->pwm_id == pwm_id) {
 137                        found = 1;
 138                        break;
 139                }
 140        }
 141
 142        if (found) {
 143                if (pwm->use_count == 0) {
 144                        pwm->use_count++;
 145                        pwm->label = label;
 146                } else
 147                        pwm = ERR_PTR(-EBUSY);
 148        } else
 149                pwm = ERR_PTR(-ENOENT);
 150
 151        mutex_unlock(&pwm_lock);
 152        return pwm;
 153}
 154EXPORT_SYMBOL(pwm_request);
 155
 156void pwm_free(struct pwm_device *pwm)
 157{
 158        mutex_lock(&pwm_lock);
 159
 160        if (pwm->use_count) {
 161                pwm->use_count--;
 162                pwm->label = NULL;
 163        } else
 164                pr_warning("PWM device already freed\n");
 165
 166        mutex_unlock(&pwm_lock);
 167}
 168EXPORT_SYMBOL(pwm_free);
 169
 170static inline void __add_pwm(struct pwm_device *pwm)
 171{
 172        mutex_lock(&pwm_lock);
 173        list_add_tail(&pwm->node, &pwm_list);
 174        mutex_unlock(&pwm_lock);
 175}
 176
 177static int __devinit pwm_probe(struct platform_device *pdev)
 178{
 179        const struct platform_device_id *id = platform_get_device_id(pdev);
 180        struct pwm_device *pwm, *secondary = NULL;
 181        struct resource *r;
 182        int ret = 0;
 183
 184        pwm = kzalloc(sizeof(struct pwm_device), GFP_KERNEL);
 185        if (pwm == NULL) {
 186                dev_err(&pdev->dev, "failed to allocate memory\n");
 187                return -ENOMEM;
 188        }
 189
 190        pwm->clk = clk_get(&pdev->dev, NULL);
 191        if (IS_ERR(pwm->clk)) {
 192                ret = PTR_ERR(pwm->clk);
 193                goto err_free;
 194        }
 195        pwm->clk_enabled = 0;
 196
 197        pwm->use_count = 0;
 198        pwm->pwm_id = PWM_ID_BASE(id->driver_data) + pdev->id;
 199        pwm->pdev = pdev;
 200
 201        r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 202        if (r == NULL) {
 203                dev_err(&pdev->dev, "no memory resource defined\n");
 204                ret = -ENODEV;
 205                goto err_free_clk;
 206        }
 207
 208        r = request_mem_region(r->start, resource_size(r), pdev->name);
 209        if (r == NULL) {
 210                dev_err(&pdev->dev, "failed to request memory resource\n");
 211                ret = -EBUSY;
 212                goto err_free_clk;
 213        }
 214
 215        pwm->mmio_base = ioremap(r->start, resource_size(r));
 216        if (pwm->mmio_base == NULL) {
 217                dev_err(&pdev->dev, "failed to ioremap() registers\n");
 218                ret = -ENODEV;
 219                goto err_free_mem;
 220        }
 221
 222        if (id->driver_data & HAS_SECONDARY_PWM) {
 223                secondary = kzalloc(sizeof(struct pwm_device), GFP_KERNEL);
 224                if (secondary == NULL) {
 225                        ret = -ENOMEM;
 226                        goto err_free_mem;
 227                }
 228
 229                *secondary = *pwm;
 230                pwm->secondary = secondary;
 231
 232                /* registers for the second PWM has offset of 0x10 */
 233                secondary->mmio_base = pwm->mmio_base + 0x10;
 234                secondary->pwm_id = pdev->id + 2;
 235        }
 236
 237        __add_pwm(pwm);
 238        if (secondary)
 239                __add_pwm(secondary);
 240
 241        platform_set_drvdata(pdev, pwm);
 242        return 0;
 243
 244err_free_mem:
 245        release_mem_region(r->start, resource_size(r));
 246err_free_clk:
 247        clk_put(pwm->clk);
 248err_free:
 249        kfree(pwm);
 250        return ret;
 251}
 252
 253static int __devexit pwm_remove(struct platform_device *pdev)
 254{
 255        struct pwm_device *pwm;
 256        struct resource *r;
 257
 258        pwm = platform_get_drvdata(pdev);
 259        if (pwm == NULL)
 260                return -ENODEV;
 261
 262        mutex_lock(&pwm_lock);
 263
 264        if (pwm->secondary) {
 265                list_del(&pwm->secondary->node);
 266                kfree(pwm->secondary);
 267        }
 268
 269        list_del(&pwm->node);
 270        mutex_unlock(&pwm_lock);
 271
 272        iounmap(pwm->mmio_base);
 273
 274        r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 275        release_mem_region(r->start, resource_size(r));
 276
 277        clk_put(pwm->clk);
 278        kfree(pwm);
 279        return 0;
 280}
 281
 282static struct platform_driver pwm_driver = {
 283        .driver         = {
 284                .name   = "pxa25x-pwm",
 285                .owner  = THIS_MODULE,
 286        },
 287        .probe          = pwm_probe,
 288        .remove         = __devexit_p(pwm_remove),
 289        .id_table       = pwm_id_table,
 290};
 291
 292static int __init pwm_init(void)
 293{
 294        return platform_driver_register(&pwm_driver);
 295}
 296arch_initcall(pwm_init);
 297
 298static void __exit pwm_exit(void)
 299{
 300        platform_driver_unregister(&pwm_driver);
 301}
 302module_exit(pwm_exit);
 303
 304MODULE_LICENSE("GPL v2");
 305
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.