Logo Search packages:      
Sourcecode: linux version File versions  Download package

pwm.c

/*
 * linux/arch/arm/mach-pxa/pwm.c
 *
 * simple driver for PWM (Pulse Width Modulator) controller
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * 2008-02-13     initial version
 *          eric miao <eric.miao@marvell.com>
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/pwm.h>

#include <asm/div64.h>

#define HAS_SECONDARY_PWM     0x10
#define PWM_ID_BASE(d)        ((d) & 0xf)

static const struct platform_device_id pwm_id_table[] = {
      /*   PWM    has_secondary_pwm? */
      { "pxa25x-pwm", 0 },
      { "pxa27x-pwm", 0 | HAS_SECONDARY_PWM },
      { "pxa168-pwm", 1 },
      { "pxa910-pwm", 1 },
      { },
};
MODULE_DEVICE_TABLE(platform, pwm_id_table);

/* PWM registers and bits definitions */
#define PWMCR           (0x00)
#define PWMDCR          (0x04)
#define PWMPCR          (0x08)

#define PWMCR_SD  (1 << 6)
#define PWMDCR_FD (1 << 10)

struct pwm_device {
      struct list_head  node;
      struct pwm_device *secondary;
      struct platform_device  *pdev;

      const char  *label;
      struct clk  *clk;
      int         clk_enabled;
      void __iomem      *mmio_base;

      unsigned int      use_count;
      unsigned int      pwm_id;
};

/*
 * period_ns = 10^9 * (PRESCALE + 1) * (PV + 1) / PWM_CLK_RATE
 * duty_ns   = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE
 */
int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
{
      unsigned long long c;
      unsigned long period_cycles, prescale, pv, dc;

      if (pwm == NULL || period_ns == 0 || duty_ns > period_ns)
            return -EINVAL;

      c = clk_get_rate(pwm->clk);
      c = c * period_ns;
      do_div(c, 1000000000);
      period_cycles = c;

      if (period_cycles < 1)
            period_cycles = 1;
      prescale = (period_cycles - 1) / 1024;
      pv = period_cycles / (prescale + 1) - 1;

      if (prescale > 63)
            return -EINVAL;

      if (duty_ns == period_ns)
            dc = PWMDCR_FD;
      else
            dc = (pv + 1) * duty_ns / period_ns;

      /* NOTE: the clock to PWM has to be enabled first
       * before writing to the registers
       */
      clk_enable(pwm->clk);
      __raw_writel(prescale, pwm->mmio_base + PWMCR);
      __raw_writel(dc, pwm->mmio_base + PWMDCR);
      __raw_writel(pv, pwm->mmio_base + PWMPCR);
      clk_disable(pwm->clk);

      return 0;
}
EXPORT_SYMBOL(pwm_config);

int pwm_enable(struct pwm_device *pwm)
{
      int rc = 0;

      if (!pwm->clk_enabled) {
            rc = clk_enable(pwm->clk);
            if (!rc)
                  pwm->clk_enabled = 1;
      }
      return rc;
}
EXPORT_SYMBOL(pwm_enable);

void pwm_disable(struct pwm_device *pwm)
{
      if (pwm->clk_enabled) {
            clk_disable(pwm->clk);
            pwm->clk_enabled = 0;
      }
}
EXPORT_SYMBOL(pwm_disable);

static DEFINE_MUTEX(pwm_lock);
static LIST_HEAD(pwm_list);

struct pwm_device *pwm_request(int pwm_id, const char *label)
{
      struct pwm_device *pwm;
      int found = 0;

      mutex_lock(&pwm_lock);

      list_for_each_entry(pwm, &pwm_list, node) {
            if (pwm->pwm_id == pwm_id) {
                  found = 1;
                  break;
            }
      }

      if (found) {
            if (pwm->use_count == 0) {
                  pwm->use_count++;
                  pwm->label = label;
            } else
                  pwm = ERR_PTR(-EBUSY);
      } else
            pwm = ERR_PTR(-ENOENT);

      mutex_unlock(&pwm_lock);
      return pwm;
}
EXPORT_SYMBOL(pwm_request);

void pwm_free(struct pwm_device *pwm)
{
      mutex_lock(&pwm_lock);

      if (pwm->use_count) {
            pwm->use_count--;
            pwm->label = NULL;
      } else
            pr_warning("PWM device already freed\n");

      mutex_unlock(&pwm_lock);
}
EXPORT_SYMBOL(pwm_free);

static inline void __add_pwm(struct pwm_device *pwm)
{
      mutex_lock(&pwm_lock);
      list_add_tail(&pwm->node, &pwm_list);
      mutex_unlock(&pwm_lock);
}

static int __devinit pwm_probe(struct platform_device *pdev)
{
      struct platform_device_id *id = platform_get_device_id(pdev);
      struct pwm_device *pwm, *secondary = NULL;
      struct resource *r;
      int ret = 0;

      pwm = kzalloc(sizeof(struct pwm_device), GFP_KERNEL);
      if (pwm == NULL) {
            dev_err(&pdev->dev, "failed to allocate memory\n");
            return -ENOMEM;
      }

      pwm->clk = clk_get(&pdev->dev, NULL);
      if (IS_ERR(pwm->clk)) {
            ret = PTR_ERR(pwm->clk);
            goto err_free;
      }
      pwm->clk_enabled = 0;

      pwm->use_count = 0;
      pwm->pwm_id = PWM_ID_BASE(id->driver_data) + pdev->id;
      pwm->pdev = pdev;

      r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
      if (r == NULL) {
            dev_err(&pdev->dev, "no memory resource defined\n");
            ret = -ENODEV;
            goto err_free_clk;
      }

      r = request_mem_region(r->start, r->end - r->start + 1, pdev->name);
      if (r == NULL) {
            dev_err(&pdev->dev, "failed to request memory resource\n");
            ret = -EBUSY;
            goto err_free_clk;
      }

      pwm->mmio_base = ioremap(r->start, r->end - r->start + 1);
      if (pwm->mmio_base == NULL) {
            dev_err(&pdev->dev, "failed to ioremap() registers\n");
            ret = -ENODEV;
            goto err_free_mem;
      }

      if (id->driver_data & HAS_SECONDARY_PWM) {
            secondary = kzalloc(sizeof(struct pwm_device), GFP_KERNEL);
            if (secondary == NULL) {
                  ret = -ENOMEM;
                  goto err_free_mem;
            }

            *secondary = *pwm;
            pwm->secondary = secondary;

            /* registers for the second PWM has offset of 0x10 */
            secondary->mmio_base = pwm->mmio_base + 0x10;
            secondary->pwm_id = pdev->id + 2;
      }

      __add_pwm(pwm);
      if (secondary)
            __add_pwm(secondary);

      platform_set_drvdata(pdev, pwm);
      return 0;

err_free_mem:
      release_mem_region(r->start, r->end - r->start + 1);
err_free_clk:
      clk_put(pwm->clk);
err_free:
      kfree(pwm);
      return ret;
}

static int __devexit pwm_remove(struct platform_device *pdev)
{
      struct pwm_device *pwm;
      struct resource *r;

      pwm = platform_get_drvdata(pdev);
      if (pwm == NULL)
            return -ENODEV;

      mutex_lock(&pwm_lock);

      if (pwm->secondary) {
            list_del(&pwm->secondary->node);
            kfree(pwm->secondary);
      }

      list_del(&pwm->node);
      mutex_unlock(&pwm_lock);

      iounmap(pwm->mmio_base);

      r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
      release_mem_region(r->start, r->end - r->start + 1);

      clk_put(pwm->clk);
      kfree(pwm);
      return 0;
}

static struct platform_driver pwm_driver = {
      .driver           = {
            .name = "pxa25x-pwm",
            .owner      = THIS_MODULE,
      },
      .probe            = pwm_probe,
      .remove           = __devexit_p(pwm_remove),
      .id_table   = pwm_id_table,
};

static int __init pwm_init(void)
{
      return platform_driver_register(&pwm_driver);
}
arch_initcall(pwm_init);

static void __exit pwm_exit(void)
{
      platform_driver_unregister(&pwm_driver);
}
module_exit(pwm_exit);

MODULE_LICENSE("GPL v2");

Generated by  Doxygen 1.6.0   Back to index