Commit 75540c1a authored by eric miao's avatar eric miao Committed by Russell King

[ARM] pxa: Add PXA support for PWM API

Patch mainly from Eric Miao, with minor edits by rmk.

Note: PWM0 and PWM2 share the same register I/O space and clock gating
on pxa{27x, 3xx}, thus PWM2 is treated in the driver as a child PWM of
PWM0. And this is also true for PWM1/3.
Signed-off-by: default avatareric miao <eric.miao@marvell.com>
Signed-off-by: default avatarRussell King <rmk+kernel@arm.linux.org.uk>
parent 1a189b97
...@@ -273,4 +273,9 @@ config PXA_SSP ...@@ -273,4 +273,9 @@ config PXA_SSP
tristate tristate
help help
Enable support for PXA2xx SSP ports Enable support for PXA2xx SSP ports
config PXA_PWM
tristate
help
Enable support for PXA2xx/PXA3xx PWM controllers
endif endif
...@@ -10,6 +10,7 @@ obj-$(CONFIG_CPU_FREQ) += cpu-pxa.o ...@@ -10,6 +10,7 @@ obj-$(CONFIG_CPU_FREQ) += cpu-pxa.o
# Generic drivers that other drivers may depend upon # Generic drivers that other drivers may depend upon
obj-$(CONFIG_PXA_SSP) += ssp.o obj-$(CONFIG_PXA_SSP) += ssp.o
obj-$(CONFIG_PXA_PWM) += pwm.o
# SoC-specific code # SoC-specific code
obj-$(CONFIG_PXA25x) += mfp-pxa2xx.o pxa25x.o obj-$(CONFIG_PXA25x) += mfp-pxa2xx.o pxa25x.o
......
...@@ -280,6 +280,36 @@ struct platform_device pxa_device_rtc = { ...@@ -280,6 +280,36 @@ struct platform_device pxa_device_rtc = {
#ifdef CONFIG_PXA25x #ifdef CONFIG_PXA25x
static struct resource pxa25x_resource_pwm0[] = {
[0] = {
.start = 0x40b00000,
.end = 0x40b0000f,
.flags = IORESOURCE_MEM,
},
};
struct platform_device pxa25x_device_pwm0 = {
.name = "pxa25x-pwm",
.id = 0,
.resource = pxa25x_resource_pwm0,
.num_resources = ARRAY_SIZE(pxa25x_resource_pwm0),
};
static struct resource pxa25x_resource_pwm1[] = {
[0] = {
.start = 0x40c00000,
.end = 0x40c0000f,
.flags = IORESOURCE_MEM,
},
};
struct platform_device pxa25x_device_pwm1 = {
.name = "pxa25x-pwm",
.id = 1,
.resource = pxa25x_resource_pwm1,
.num_resources = ARRAY_SIZE(pxa25x_resource_pwm1),
};
static u64 pxa25x_ssp_dma_mask = DMA_BIT_MASK(32); static u64 pxa25x_ssp_dma_mask = DMA_BIT_MASK(32);
static struct resource pxa25x_resource_ssp[] = { static struct resource pxa25x_resource_ssp[] = {
...@@ -568,6 +598,36 @@ struct platform_device pxa27x_device_ssp3 = { ...@@ -568,6 +598,36 @@ struct platform_device pxa27x_device_ssp3 = {
.num_resources = ARRAY_SIZE(pxa27x_resource_ssp3), .num_resources = ARRAY_SIZE(pxa27x_resource_ssp3),
}; };
static struct resource pxa27x_resource_pwm0[] = {
[0] = {
.start = 0x40b00000,
.end = 0x40b0001f,
.flags = IORESOURCE_MEM,
},
};
struct platform_device pxa27x_device_pwm0 = {
.name = "pxa27x-pwm",
.id = 0,
.resource = pxa27x_resource_pwm0,
.num_resources = ARRAY_SIZE(pxa27x_resource_pwm0),
};
static struct resource pxa27x_resource_pwm1[] = {
[0] = {
.start = 0x40c00000,
.end = 0x40c0001f,
.flags = IORESOURCE_MEM,
},
};
struct platform_device pxa27x_device_pwm1 = {
.name = "pxa27x-pwm",
.id = 1,
.resource = pxa27x_resource_pwm1,
.num_resources = ARRAY_SIZE(pxa27x_resource_pwm1),
};
static struct resource pxa27x_resource_camera[] = { static struct resource pxa27x_resource_camera[] = {
[0] = { [0] = {
.start = 0x50000000, .start = 0x50000000,
......
...@@ -24,4 +24,9 @@ extern struct platform_device pxa27x_device_ssp2; ...@@ -24,4 +24,9 @@ extern struct platform_device pxa27x_device_ssp2;
extern struct platform_device pxa27x_device_ssp3; extern struct platform_device pxa27x_device_ssp3;
extern struct platform_device pxa3xx_device_ssp4; extern struct platform_device pxa3xx_device_ssp4;
extern struct platform_device pxa25x_device_pwm0;
extern struct platform_device pxa25x_device_pwm1;
extern struct platform_device pxa27x_device_pwm0;
extern struct platform_device pxa27x_device_pwm1;
void __init pxa_register_device(struct platform_device *dev, void *data); void __init pxa_register_device(struct platform_device *dev, void *data);
/*
* 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;
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)
{
return clk_enable(pwm->clk);
}
EXPORT_SYMBOL(pwm_enable);
void pwm_disable(struct pwm_device *pwm)
{
clk_disable(pwm->clk);
}
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->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 * 2, NULL);
if (IS_ERR(pwm))
return PTR_ERR(pwm);
pwm = pwm_probe(pdev, pdev->id * 2 + 1, 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);
...@@ -129,12 +129,12 @@ static struct clk pxa25x_clks[] = { ...@@ -129,12 +129,12 @@ static struct clk pxa25x_clks[] = {
INIT_CKEN("SSPCLK", SSP, 3686400, 0, &pxa25x_device_ssp.dev), INIT_CKEN("SSPCLK", SSP, 3686400, 0, &pxa25x_device_ssp.dev),
INIT_CKEN("SSPCLK", NSSP, 3686400, 0, &pxa25x_device_nssp.dev), INIT_CKEN("SSPCLK", NSSP, 3686400, 0, &pxa25x_device_nssp.dev),
INIT_CKEN("SSPCLK", ASSP, 3686400, 0, &pxa25x_device_assp.dev), INIT_CKEN("SSPCLK", ASSP, 3686400, 0, &pxa25x_device_assp.dev),
INIT_CKEN("PWMCLK", PWM0, 3686400, 0, &pxa25x_device_pwm0.dev),
INIT_CKEN("PWMCLK", PWM1, 3686400, 0, &pxa25x_device_pwm1.dev),
INIT_CKEN("AC97CLK", AC97, 24576000, 0, NULL), INIT_CKEN("AC97CLK", AC97, 24576000, 0, NULL),
/* /*
INIT_CKEN("PWMCLK", PWM0, 3686400, 0, NULL),
INIT_CKEN("PWMCLK", PWM0, 3686400, 0, NULL),
INIT_CKEN("I2SCLK", I2S, 14745600, 0, NULL), INIT_CKEN("I2SCLK", I2S, 14745600, 0, NULL),
*/ */
INIT_CKEN("FICPCLK", FICP, 47923000, 0, NULL), INIT_CKEN("FICPCLK", FICP, 47923000, 0, NULL),
...@@ -269,6 +269,8 @@ static struct platform_device *pxa25x_devices[] __initdata = { ...@@ -269,6 +269,8 @@ static struct platform_device *pxa25x_devices[] __initdata = {
&pxa25x_device_ssp, &pxa25x_device_ssp,
&pxa25x_device_nssp, &pxa25x_device_nssp,
&pxa25x_device_assp, &pxa25x_device_assp,
&pxa25x_device_pwm0,
&pxa25x_device_pwm1,
}; };
static struct sys_device pxa25x_sysdev[] = { static struct sys_device pxa25x_sysdev[] = {
......
...@@ -157,12 +157,13 @@ static struct clk pxa27x_clks[] = { ...@@ -157,12 +157,13 @@ static struct clk pxa27x_clks[] = {
INIT_CKEN("SSPCLK", SSP1, 13000000, 0, &pxa27x_device_ssp1.dev), INIT_CKEN("SSPCLK", SSP1, 13000000, 0, &pxa27x_device_ssp1.dev),
INIT_CKEN("SSPCLK", SSP2, 13000000, 0, &pxa27x_device_ssp2.dev), INIT_CKEN("SSPCLK", SSP2, 13000000, 0, &pxa27x_device_ssp2.dev),
INIT_CKEN("SSPCLK", SSP3, 13000000, 0, &pxa27x_device_ssp3.dev), INIT_CKEN("SSPCLK", SSP3, 13000000, 0, &pxa27x_device_ssp3.dev),
INIT_CKEN("PWMCLK", PWM0, 13000000, 0, &pxa27x_device_pwm0.dev),
INIT_CKEN("PWMCLK", PWM1, 13000000, 0, &pxa27x_device_pwm1.dev),
INIT_CKEN("AC97CLK", AC97, 24576000, 0, NULL), INIT_CKEN("AC97CLK", AC97, 24576000, 0, NULL),
INIT_CKEN("AC97CONFCLK", AC97CONF, 24576000, 0, NULL), INIT_CKEN("AC97CONFCLK", AC97CONF, 24576000, 0, NULL),
/* /*
INIT_CKEN("PWMCLK", PWM0, 13000000, 0, NULL),
INIT_CKEN("MSLCLK", MSL, 48000000, 0, NULL), INIT_CKEN("MSLCLK", MSL, 48000000, 0, NULL),
INIT_CKEN("USIMCLK", USIM, 48000000, 0, NULL), INIT_CKEN("USIMCLK", USIM, 48000000, 0, NULL),
INIT_CKEN("MSTKCLK", MEMSTK, 19500000, 0, NULL), INIT_CKEN("MSTKCLK", MEMSTK, 19500000, 0, NULL),
...@@ -363,6 +364,8 @@ static struct platform_device *devices[] __initdata = { ...@@ -363,6 +364,8 @@ static struct platform_device *devices[] __initdata = {
&pxa27x_device_ssp1, &pxa27x_device_ssp1,
&pxa27x_device_ssp2, &pxa27x_device_ssp2,
&pxa27x_device_ssp3, &pxa27x_device_ssp3,
&pxa27x_device_pwm0,
&pxa27x_device_pwm1,
}; };
static struct sys_device pxa27x_sysdev[] = { static struct sys_device pxa27x_sysdev[] = {
......
...@@ -239,6 +239,8 @@ static struct clk pxa3xx_clks[] = { ...@@ -239,6 +239,8 @@ static struct clk pxa3xx_clks[] = {
PXA3xx_CKEN("SSPCLK", SSP2, 13000000, 0, &pxa27x_device_ssp2.dev), PXA3xx_CKEN("SSPCLK", SSP2, 13000000, 0, &pxa27x_device_ssp2.dev),
PXA3xx_CKEN("SSPCLK", SSP3, 13000000, 0, &pxa27x_device_ssp3.dev), PXA3xx_CKEN("SSPCLK", SSP3, 13000000, 0, &pxa27x_device_ssp3.dev),
PXA3xx_CKEN("SSPCLK", SSP4, 13000000, 0, &pxa3xx_device_ssp4.dev), PXA3xx_CKEN("SSPCLK", SSP4, 13000000, 0, &pxa3xx_device_ssp4.dev),
PXA3xx_CKEN("PWMCLK", PWM0, 13000000, 0, &pxa27x_device_pwm0.dev),
PXA3xx_CKEN("PWMCLK", PWM1, 13000000, 0, &pxa27x_device_pwm1.dev),
PXA3xx_CKEN("MMCCLK", MMC1, 19500000, 0, &pxa_device_mci.dev), PXA3xx_CKEN("MMCCLK", MMC1, 19500000, 0, &pxa_device_mci.dev),
PXA3xx_CKEN("MMCCLK", MMC2, 19500000, 0, &pxa3xx_device_mci2.dev), PXA3xx_CKEN("MMCCLK", MMC2, 19500000, 0, &pxa3xx_device_mci2.dev),
...@@ -530,6 +532,8 @@ static struct platform_device *devices[] __initdata = { ...@@ -530,6 +532,8 @@ static struct platform_device *devices[] __initdata = {
&pxa27x_device_ssp2, &pxa27x_device_ssp2,
&pxa27x_device_ssp3, &pxa27x_device_ssp3,
&pxa3xx_device_ssp4, &pxa3xx_device_ssp4,
&pxa27x_device_pwm0,
&pxa27x_device_pwm1,
}; };
static struct sys_device pxa3xx_sysdev[] = { static struct sys_device pxa3xx_sysdev[] = {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment