pwm驱动框架提供的函数有:
struct pwm_device;
/*
* pwm_request - request a PWM device
*/
struct pwm_device *pwm_request(int pwm_id, const char *label);
/*
* pwm_free - free a PWM device
*/
void pwm_free(struct pwm_device *pwm);
/*
* pwm_config - change a PWM device configuration
*/
int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns);
/*
* pwm_enable - start a PWM output toggling
*/
int pwm_enable(struct pwm_device *pwm);
/*
* pwm_disable - stop a PWM output toggling
*/
void pwm_disable(struct pwm_device *pwm);
struct pwm_device结构体为:
struct pwm_device {
struct list_head list;
struct platform_device *pdev;
const char *label;
unsigned int period_ns;
unsigned int duty_ns;
unsigned char running;
unsigned char use_count;
unsigned char pwm_id;
};
在内核的pwm驱动框架中,struct pwm_device结构体以链表的形式存在,pwm的链表形成是基于平台总线模式下的。
具体过程为:先声明与pwm有关的定时器的资源,就是把中断号保存在资源中,供给驱动匹配后申请中断使用和时钟获取。
/* Standard setup for a timer block. */
#define TIMER_RESOURCE_SIZE (1)
#define TIMER_RESOURCE(_tmr, _irq) \
(struct resource [TIMER_RESOURCE_SIZE]) { \
[0] = { \
.start = _irq, \
.end = _irq, \
.flags = IORESOURCE_IRQ \
} \
}
#define DEFINE_S3C_TIMER(_tmr_no, _irq) \
.name = "s3c24xx-pwm", \
.id = _tmr_no, \
.num_resources = TIMER_RESOURCE_SIZE, \
.resource = TIMER_RESOURCE(_tmr_no, _irq), \
/* since we already have an static mapping for the timer, we do not
* bother setting any IO resource for the base.
*/
struct platform_device s3c_device_timer[] = {
[0] = { DEFINE_S3C_TIMER(0, IRQ_TIMER0) },
[1] = { DEFINE_S3C_TIMER(1, IRQ_TIMER1) },
[2] = { DEFINE_S3C_TIMER(2, IRQ_TIMER2) },
[3] = { DEFINE_S3C_TIMER(3, IRQ_TIMER3) },
[4] = { DEFINE_S3C_TIMER(4, IRQ_TIMER4) },
};
int platform_add_devices(struct platform_device * * devs, int num)
platform_add_devices实现了s3c_device_timer[]平台数据的注册,这样在总线上就会有相关的设备。
static struct platform_driver s3c_pwm_driver = {
.driver = {
.name = "s3c24xx-pwm",
.owner = THIS_MODULE,
},
.probe = s3c_pwm_probe,
.remove = __devexit_p(s3c_pwm_remove),
};
s3c_pwm_driver 使用.name = "s3c24xx-pwm",与
#define DEFINE_S3C_TIMER(_tmr_no, _irq) \
.name = "s3c24xx-pwm", \
.id = _tmr_no, \
.num_resources = TIMER_RESOURCE_SIZE, \
.resource = TIMER_RESOURCE(_tmr_no, _irq), \
中的名字匹配,要记住的是id只是为了区分同类设备,因为多个设备要匹配一个驱动就要靠名字来匹配,所以名字就要设置成为一样的,而在sys文件中为了生成管理的对应文件,就要用id来区分,所以id仅仅用于设备之间来区分,不用于匹配。
static int __init pwm_init(void)
{
int ret;
clk_scaler[0] = clk_get(NULL, "pwm-scaler0");
clk_scaler[1] = clk_get(NULL, "pwm-scaler1");
if (IS_ERR(clk_scaler[0]) || IS_ERR(clk_scaler[1])) {
printk(KERN_ERR "%s: failed to get scaler clocks\n", __func__);
return -EINVAL;
}
ret = platform_driver_register(&s3c_pwm_driver);
if (ret)
printk(KERN_ERR "%s: failed to add pwm driver\n", __func__);
return ret;
}
arch_initcall(pwm_init);
获取时钟, clk_scaler[0] = clk_get(NULL, "pwm-scaler0");
注册驱动:ret = platform_driver_register(&s3c_pwm_driver);
这次学到的重要知识:arch_initcall(pwm_init);是在系统启动时内核调用的,自动运行,不需要自己加载。所以在编写驱动的时候如果要自动加载的话,可以使用arch_initcall(入口函数名)。
static int s3c_pwm_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct pwm_device *pwm;
unsigned long flags;
unsigned long tcon;
unsigned int id = pdev->id;
int ret;
if (id == 4) {
dev_err(dev, "TIMER4 is currently not supported\n");
return -ENXIO;
}
pwm = kzalloc(sizeof(struct pwm_device), GFP_KERNEL);
if (pwm == NULL) {
dev_err(dev, "failed to allocate pwm_device\n");
return -ENOMEM;
}
pwm->pdev = pdev;
pwm->pwm_id = id;
/* calculate base of control bits in TCON */
pwm->tcon_base = id == 0 ? 0 : (id * 4) + 4;
pwm->clk = clk_get(dev, "pwm-tin");
if (IS_ERR(pwm->clk)) {
dev_err(dev, "failed to get pwm tin clk\n");
ret = PTR_ERR(pwm->clk);
goto err_alloc;
}
pwm->clk_div = clk_get(dev, "pwm-tdiv");
if (IS_ERR(pwm->clk_div)) {
dev_err(dev, "failed to get pwm tdiv clk\n");
ret = PTR_ERR(pwm->clk_div);
goto err_clk_tin;
}
local_irq_save(flags);
tcon = __raw_readl(S3C2410_TCON);
tcon |= pwm_tcon_invert(pwm);
__raw_writel(tcon, S3C2410_TCON);
local_irq_restore(flags);
ret = pwm_register(pwm);
if (ret) {
dev_err(dev, "failed to register pwm\n");
goto err_clk_tdiv;
}
pwm_dbg(pwm, "config bits %02x\n",
(__raw_readl(S3C2410_TCON) >> pwm->tcon_base) & 0x0f);
dev_info(dev, "tin at %lu, tdiv at %lu, tin=%sclk, base %d\n",
clk_get_rate(pwm->clk),
clk_get_rate(pwm->clk_div),
pwm_is_tdiv(pwm) ? "div" : "ext", pwm->tcon_base);
platform_set_drvdata(pdev, pwm);
return 0;
err_clk_tdiv:
clk_put(pwm->clk_div);
err_clk_tin:
clk_put(pwm->clk);
err_alloc:
kfree(pwm);
return ret;
}
static int s3c_pwm_remove(struct platform_device *pdev)
{
struct pwm_device *pwm = platform_get_drvdata(pdev);
clk_put(pwm->clk_div);
clk_put(pwm->clk);
kfree(pwm);
return 0;
}
probe函数和remove函数实现了pwm设备的初始化和加载进入链表的过程,还包括时钟定时器的获取。
static int pwm_register(struct pwm_device *pwm)
{
pwm->duty_ns = -1;
pwm->period_ns = -1;
mutex_lock(&pwm_lock);
list_add_tail(&pwm->list, &pwm_list);
mutex_unlock(&pwm_lock);
return 0;
}
获取时钟的API接口
/* Clock API calls */
struct clk *clk_get(struct device *dev, const char *id)
{
struct clk *p;
struct clk *clk = ERR_PTR(-ENOENT);
int idno;
if (dev == NULL || dev->bus != &platform_bus_type)
idno = -1;
else
idno = to_platform_device(dev)->id;
spin_lock(&clocks_lock);
list_for_each_entry(p, &clocks, list) {
if (p->id == idno &&
strcmp(id, p->name) == 0 &&
try_module_get(p->owner)) {
clk = p;
break;
}
}
/* check for the case where a device was supplied, but the
* clock that was being searched for is not device specific */
if (IS_ERR(clk)) {
list_for_each_entry(p, &clocks, list) {
if (p->id == -1 && strcmp(id, p->name) == 0 &&
try_module_get(p->owner)) {
clk = p;
break;
}
}
}
spin_unlock(&clocks_lock);
return clk;
}
综上所述:pwm的内核驱动框架还是基于平台设备总线实现的,接入了链表管理多个pwm的申请于配置。
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, list) {
if (pwm->pwm_id == pwm_id) {
found = 1;
break;
}
}
if (found) {
if (pwm->use_count == 0) {
pwm->use_count = 1;
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
printk(KERN_ERR "PWM%d device already freed\n", pwm->pwm_id);
mutex_unlock(&pwm_lock);
}
有申请函数和释放函数可以看出,pwm可以被多个驱动申请,pwm结构体使用count计量申请的次数,在释放时恢复原来的计数。
pwm->clk = clk_get(dev, "pwm-tin");
if (IS_ERR(pwm->clk)) {
dev_err(dev, "failed to get pwm tin clk\n");
ret = PTR_ERR(pwm->clk);
goto err_alloc;
}
pwm->clk_div = clk_get(dev, "pwm-tdiv");
if (IS_ERR(pwm->clk_div)) {
dev_err(dev, "failed to get pwm tdiv clk\n");
ret = PTR_ERR(pwm->clk_div);
goto err_clk_tin;
}
获取了两个时钟,在pwm_config函数里面使用。是pwm的两种不同工作模式所需要不同的时钟。
int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
{
unsigned long tin_rate;
unsigned long tin_ns;
unsigned long period;
unsigned long flags;
unsigned long tcon;
unsigned long tcnt;
long tcmp;
/* We currently avoid using 64bit arithmetic by using the
* fact that anything faster than 1Hz is easily representable
* by 32bits. */
if (period_ns > NS_IN_HZ || duty_ns > NS_IN_HZ)
return -ERANGE;
if (duty_ns > period_ns)
return -EINVAL;
if (period_ns == pwm->period_ns &&
duty_ns == pwm->duty_ns)
return 0;
/* The TCMP and TCNT can be read without a lock, they're not
* shared between the timers. */
tcmp = __raw_readl(S3C2410_TCMPB(pwm->pwm_id));
tcnt = __raw_readl(S3C2410_TCNTB(pwm->pwm_id));
period = NS_IN_HZ / period_ns;
pwm_dbg(pwm, "duty_ns=%d, period_ns=%d (%lu)\n",
duty_ns, period_ns, period);
/* Check to see if we are changing the clock rate of the PWM */
if (pwm->period_ns != period_ns) {
if (pwm_is_tdiv(pwm)) {
tin_rate = pwm_calc_tin(pwm, period);
clk_set_rate(pwm->clk_div, tin_rate);
} else
tin_rate = clk_get_rate(pwm->clk);
pwm->period_ns = period_ns;
pwm_dbg(pwm, "tin_rate=%lu\n", tin_rate);
tin_ns = NS_IN_HZ / tin_rate;
tcnt = period_ns / tin_ns;
} else
tin_ns = NS_IN_HZ / clk_get_rate(pwm->clk);
/* Note, counters count down */
tcmp = duty_ns / tin_ns;
tcmp = tcnt - tcmp;
pwm_dbg(pwm, "tin_ns=%lu, tcmp=%ld/%lu\n", tin_ns, tcmp, tcnt);
if (tcmp < 0)
tcmp = 0;
/* Update the PWM register block. */
local_irq_save(flags);
__raw_writel(tcmp, S3C2410_TCMPB(pwm->pwm_id));
__raw_writel(tcnt, S3C2410_TCNTB(pwm->pwm_id));
tcon = __raw_readl(S3C2410_TCON);
tcon |= pwm_tcon_manulupdate(pwm);
tcon |= pwm_tcon_autoreload(pwm);
__raw_writel(tcon, S3C2410_TCON);
tcon &= ~pwm_tcon_manulupdate(pwm);
__raw_writel(tcon, S3C2410_TCON);
local_irq_restore(flags);
return 0;
}