mirror of
https://github.com/torvalds/linux.git
synced 2024-12-04 18:13:04 +00:00
c860d701cc
Allow pwm_enable()/pwm_disable() to be called as many times as the driver wants (and not even count them). The PWM model is different from things like the clock API where we need enable counting, because PWMs have one exclusive user per PWM whereas the clock API can have multiple users of the same clock. Acked-by: eric miao <eric.miao@marvell.com> Signed-off-by: Robert Jarzmik <rjarzmik@free.fr> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
314 lines
6.3 KiB
C
314 lines
6.3 KiB
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>
|
|
#include <asm/arch/pxa-regs.h>
|
|
|
|
/* 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 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 < 0)
|
|
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 && pwm->use_count == 0) {
|
|
pwm->use_count++;
|
|
pwm->label = label;
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&pwm_lock);
|
|
|
|
return (found) ? pwm : NULL;
|
|
}
|
|
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 struct pwm_device *pwm_probe(struct platform_device *pdev,
|
|
unsigned int pwm_id, struct pwm_device *parent_pwm)
|
|
{
|
|
struct pwm_device *pwm;
|
|
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 ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
pwm->clk = clk_get(&pdev->dev, "PWMCLK");
|
|
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;
|
|
pwm->pdev = pdev;
|
|
|
|
if (parent_pwm != NULL) {
|
|
/* registers for the second PWM has offset of 0x10 */
|
|
pwm->mmio_base = parent_pwm->mmio_base + 0x10;
|
|
__add_pwm(pwm);
|
|
return pwm;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
__add_pwm(pwm);
|
|
platform_set_drvdata(pdev, pwm);
|
|
return pwm;
|
|
|
|
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 ERR_PTR(ret);
|
|
}
|
|
|
|
static int __devinit pxa25x_pwm_probe(struct platform_device *pdev)
|
|
{
|
|
struct pwm_device *pwm = pwm_probe(pdev, pdev->id, NULL);
|
|
|
|
if (IS_ERR(pwm))
|
|
return PTR_ERR(pwm);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __devinit pxa27x_pwm_probe(struct platform_device *pdev)
|
|
{
|
|
struct pwm_device *pwm;
|
|
|
|
pwm = pwm_probe(pdev, pdev->id, NULL);
|
|
if (IS_ERR(pwm))
|
|
return PTR_ERR(pwm);
|
|
|
|
pwm = pwm_probe(pdev, pdev->id + 2, pwm);
|
|
if (IS_ERR(pwm))
|
|
return PTR_ERR(pwm);
|
|
|
|
return 0;
|
|
}
|
|
|
|
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);
|
|
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 pxa25x_pwm_driver = {
|
|
.driver = {
|
|
.name = "pxa25x-pwm",
|
|
},
|
|
.probe = pxa25x_pwm_probe,
|
|
.remove = __devexit_p(pwm_remove),
|
|
};
|
|
|
|
static struct platform_driver pxa27x_pwm_driver = {
|
|
.driver = {
|
|
.name = "pxa27x-pwm",
|
|
},
|
|
.probe = pxa27x_pwm_probe,
|
|
.remove = __devexit_p(pwm_remove),
|
|
};
|
|
|
|
static int __init pwm_init(void)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret = platform_driver_register(&pxa25x_pwm_driver);
|
|
if (ret) {
|
|
printk(KERN_ERR "failed to register pxa25x_pwm_driver\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = platform_driver_register(&pxa27x_pwm_driver);
|
|
if (ret) {
|
|
printk(KERN_ERR "failed to register pxa27x_pwm_driver\n");
|
|
return ret;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
arch_initcall(pwm_init);
|
|
|
|
static void __exit pwm_exit(void)
|
|
{
|
|
platform_driver_unregister(&pxa25x_pwm_driver);
|
|
platform_driver_unregister(&pxa27x_pwm_driver);
|
|
}
|
|
module_exit(pwm_exit);
|
|
|
|
MODULE_LICENSE("GPL v2");
|