mirror of
https://github.com/torvalds/linux.git
synced 2024-12-27 05:11:48 +00:00
[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: eric miao <eric.miao@marvell.com> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
This commit is contained in:
parent
1a189b9719
commit
75540c1ac3
@ -273,4 +273,9 @@ config PXA_SSP
|
||||
tristate
|
||||
help
|
||||
Enable support for PXA2xx SSP ports
|
||||
|
||||
config PXA_PWM
|
||||
tristate
|
||||
help
|
||||
Enable support for PXA2xx/PXA3xx PWM controllers
|
||||
endif
|
||||
|
@ -10,6 +10,7 @@ obj-$(CONFIG_CPU_FREQ) += cpu-pxa.o
|
||||
|
||||
# Generic drivers that other drivers may depend upon
|
||||
obj-$(CONFIG_PXA_SSP) += ssp.o
|
||||
obj-$(CONFIG_PXA_PWM) += pwm.o
|
||||
|
||||
# SoC-specific code
|
||||
obj-$(CONFIG_PXA25x) += mfp-pxa2xx.o pxa25x.o
|
||||
|
@ -280,6 +280,36 @@ struct platform_device pxa_device_rtc = {
|
||||
|
||||
#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 struct resource pxa25x_resource_ssp[] = {
|
||||
@ -568,6 +598,36 @@ struct platform_device pxa27x_device_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[] = {
|
||||
[0] = {
|
||||
.start = 0x50000000,
|
||||
|
@ -24,4 +24,9 @@ extern struct platform_device pxa27x_device_ssp2;
|
||||
extern struct platform_device pxa27x_device_ssp3;
|
||||
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);
|
||||
|
299
arch/arm/mach-pxa/pwm.c
Normal file
299
arch/arm/mach-pxa/pwm.c
Normal file
@ -0,0 +1,299 @@
|
||||
/*
|
||||
* 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[] = {
|
||||
INIT_CKEN("SSPCLK", SSP, 3686400, 0, &pxa25x_device_ssp.dev),
|
||||
INIT_CKEN("SSPCLK", NSSP, 3686400, 0, &pxa25x_device_nssp.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("PWMCLK", PWM0, 3686400, 0, NULL),
|
||||
INIT_CKEN("PWMCLK", PWM0, 3686400, 0, NULL),
|
||||
INIT_CKEN("I2SCLK", I2S, 14745600, 0, NULL),
|
||||
*/
|
||||
INIT_CKEN("FICPCLK", FICP, 47923000, 0, NULL),
|
||||
@ -269,6 +269,8 @@ static struct platform_device *pxa25x_devices[] __initdata = {
|
||||
&pxa25x_device_ssp,
|
||||
&pxa25x_device_nssp,
|
||||
&pxa25x_device_assp,
|
||||
&pxa25x_device_pwm0,
|
||||
&pxa25x_device_pwm1,
|
||||
};
|
||||
|
||||
static struct sys_device pxa25x_sysdev[] = {
|
||||
|
@ -157,12 +157,13 @@ static struct clk pxa27x_clks[] = {
|
||||
INIT_CKEN("SSPCLK", SSP1, 13000000, 0, &pxa27x_device_ssp1.dev),
|
||||
INIT_CKEN("SSPCLK", SSP2, 13000000, 0, &pxa27x_device_ssp2.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("AC97CONFCLK", AC97CONF, 24576000, 0, NULL),
|
||||
|
||||
/*
|
||||
INIT_CKEN("PWMCLK", PWM0, 13000000, 0, NULL),
|
||||
INIT_CKEN("MSLCLK", MSL, 48000000, 0, NULL),
|
||||
INIT_CKEN("USIMCLK", USIM, 48000000, 0, NULL),
|
||||
INIT_CKEN("MSTKCLK", MEMSTK, 19500000, 0, NULL),
|
||||
@ -363,6 +364,8 @@ static struct platform_device *devices[] __initdata = {
|
||||
&pxa27x_device_ssp1,
|
||||
&pxa27x_device_ssp2,
|
||||
&pxa27x_device_ssp3,
|
||||
&pxa27x_device_pwm0,
|
||||
&pxa27x_device_pwm1,
|
||||
};
|
||||
|
||||
static struct sys_device pxa27x_sysdev[] = {
|
||||
|
@ -239,6 +239,8 @@ static struct clk pxa3xx_clks[] = {
|
||||
PXA3xx_CKEN("SSPCLK", SSP2, 13000000, 0, &pxa27x_device_ssp2.dev),
|
||||
PXA3xx_CKEN("SSPCLK", SSP3, 13000000, 0, &pxa27x_device_ssp3.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", MMC2, 19500000, 0, &pxa3xx_device_mci2.dev),
|
||||
@ -530,6 +532,8 @@ static struct platform_device *devices[] __initdata = {
|
||||
&pxa27x_device_ssp2,
|
||||
&pxa27x_device_ssp3,
|
||||
&pxa3xx_device_ssp4,
|
||||
&pxa27x_device_pwm0,
|
||||
&pxa27x_device_pwm1,
|
||||
};
|
||||
|
||||
static struct sys_device pxa3xx_sysdev[] = {
|
||||
|
Loading…
Reference in New Issue
Block a user