Davinci DM6446 Linux 内核分析——davinci_pwm.c
http://www.61ic.com/Article/DaVinci/DM644X/201201/40305.html
/* include Linux files */
#include <linux/config.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h> /* printk() */
#include <linux/slab.h> /* kmalloc() */
#include <linux/fs.h> /* everything... */
#include <linux/errno.h> /* error codes */
#include <linux/types.h> /* size_t */
#include <linux/cdev.h> /* Used for struct cdev */
#include <linux/dma-mapping.h> /* For class_simple_create */
#include <linux/interrupt.h> /* For IRQ_HANDLED and irqreturn_t */
#include <asm/uaccess.h> /* for VERIFY_READ/VERIFY_WRITE/
copy_from_user */
#include <linux/wait.h>
#include <linux/devfs_fs_kernel.h> /* for devfs */
#include <asm/hardware/clock.h>
#include <asm/arch/davinci_pwm.h>
#include <asm/arch/cpu.h>
#include <asm/semaphore.h>
#include <asm/arch/irqs.h>
#define DRIVER_NAME "PWM"
#define DAVINCI_PWM_TIMEOUT (1*HZ)
/*
一、用ti的datasheet上的话来说明达芬奇平台pwm模块的特点:
1.This PWM peripheral is basically a timer with a period counter and a first-phase
duration comparator, where bit width of the period and first-phase duration are
both programmable.
2.The PWM has the following features:
· 32-bit period counter
· 32-bit first-phase duration counter
· 8-bit repeat counter for one-shot operation. One-shot operation will produce
N + 1 periods of the waveform, where N is the repeat counter value.
· Configurable to operate in either one-shot or continuous mode.
· One-shot operation can be triggered by the CCD VSYNC output of the video
processing subsystem to allow any of the PWM instantiations to be used as a CCD timer.
· Configurable PWM output pin inactive state.
· Interrupt and EDMA synchronization events.
· Emulation support for stop or free-run operation.
二、应用程序调用过程
1.使用open()打开;
2.使用ioctl()设置参数。
三、调试中可能出现的问题
1.应用程序使用ioctl调用时,最后一个形参必须是指针,从驱动中能看出;
2.一般没什么大难题。
*/
/* 用于管理达芬奇平台上的每个pwm接口 */
struct pwm_davinci_device {
char name[20];
int intr_complete;
dev_t devno; // 字符设备号
davinci_pwmregsovly regs; // 该pwm接口的物理寄存器结构体
wait_queue_head_t intr_wait;
struct clk *pwm_clk; // 该pwm接口的时钟
};
char *dm644x_name[] = { "PWM0_CLK", "PWM1_CLK", "PWM2_CLK" };
char *dm646x_name[] = { "PWM0_CLK", "PWM1_CLK" };
char *dm355_name[] = { "PWM0_CLK", "PWM1_CLK", "PWM2_CLK", "PWM3_CLK" };
/* Instance of the private PWM device structure */
static struct pwm_davinci_device *pwm_dev_array[DAVINCI_PWM_MINORS];
static DEFINE_SPINLOCK(pwm_dev_array_lock);
static unsigned int pwm_major = 0;
static unsigned int pwm_minor_start = 0;
static unsigned int pwm_minor_count = DM644X_PWM_MINORS;
static unsigned int pwm_device_count = 1;
/* For registeration of charatcer device*/
static struct cdev c_dev;
/* 通过次设备号来获得该pwm接口的控制机构体 */
struct pwm_davinci_device *pwm_dev_get_by_minor(unsigned index)
{
struct pwm_davinci_device *pwm_dev;
spin_lock(&pwm_dev_array_lock);
pwm_dev = pwm_dev_array[index];
spin_unlock(&pwm_dev_array_lock);
return pwm_dev;
}
static loff_t pwm_llseek(struct file *file, loff_t offset, int whence)
{
return -ESPIPE; /* Not seekable */
}
/* 字符设备驱动的打开函数 */
static int pwm_open(struct inode *inode, struct file *file)
{
unsigned int minor = iminor(inode);
struct pwm_davinci_device *pwm_dev;
pwm_dev = pwm_dev_get_by_minor(minor);
/* sample configuration */
pwm_dev->regs->per = 0xf; // pwm总周期
pwm_dev->regs->ph1d = 0xf; // 第一相的周期
pwm_dev->regs->rpt = 1; // One-shot mode下的重复次数
pwm_dev->regs->cfg |= 0x1; // 配置成One shot mode
pwm_dev->intr_complete = 0;
return 0;
}
/* 字符设备驱动的关闭函数, */
static int pwm_release(struct inode *inode, struct file *file)
{
unsigned int minor = iminor(inode);
struct pwm_davinci_device *pwm_dev;
pwm_dev = pwm_dev_get_by_minor(minor);
pwm_dev->regs->cfg &= 0xFFFFFFFC; // 关闭pwm
/* This is called when the reference count goes to zero */
return 0;
}
/* 字符设备驱动的ioctl函数,用于设置pwm参数,控制pwm运行模式*/
int pwm_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
unsigned long arg)
{
int mode;
unsigned int minor = iminor(inode);
struct pwm_davinci_device *pwm_dev;
pwm_dev = pwm_dev_get_by_minor(minor);
switch (cmd) {
/* 设置pwm模式,有两种模式,One-Shot Mode和Continuous Mode */
case PWMIOC_SET_MODE:
if (pwm_dev->regs->cfg & 0x20000)
return -EBUSY;
get_user(mode, (int *)arg);
if (mode == PWM_ONESHOT_MODE) {
pwm_dev->regs->cfg &= 0xFFFFFFFC;
pwm_dev->regs->cfg |= 0x1;
} else if (mode == PWM_CONTINUOUS_MODE) {
pwm_dev->regs->cfg &= 0xFFFFFFFC;
pwm_dev->regs->cfg |= 0x2;
} else
return -EINVAL;
break;
/*
设置pwm周期,设置时如果pwm运行在continuous模式并且正在运行,那么必须的等待pwm一个
周期完成后(通过产生中断来同步)再设置per寄存器
*/
case PWMIOC_SET_PERIOD:
get_user(mode, (int *)arg);
if (mode < 0 || mode > 0xffffffff)
return -EINVAL;
// 检测pwm是否运行在continuous模式
if (pwm_dev->regs->cfg & 0x2 && pwm_dev->regs->cfg & 0x20000) {
if (mode < 7)
return -EINVAL;
/* Enable PWM interrupts */
pwm_dev->regs->cfg |= 0x40;
/* wait for the transaction to complete */
wait_event_timeout(pwm_dev->intr_wait,
pwm_dev->intr_complete,
DAVINCI_PWM_TIMEOUT);
if (pwm_dev->intr_complete)
pwm_dev->regs->per = mode;
else
return -1;
} else
pwm_dev->regs->per = mode;
break;
/*
设置pwm第一相周期,设置时如果pwm运行在continuous模式并且正在运行,那么必须的等待pwm一个
周期完成后(通过产生中断来同步)再设置ph1d寄存器
*/
case PWMIOC_SET_DURATION:
get_user(mode, (int *)arg);
if (mode < 0 || mode > 0xffffffff)
return -EINVAL;
// 检测pwm是否运行在continuous模式
if (pwm_dev->regs->cfg & 0x2 && pwm_dev->regs->cfg & 0x20000) {
/* Enable PWM interrupts */
pwm_dev->regs->cfg |= 0x40;
/* wait for the transaction to complete */
wait_event_timeout(pwm_dev->intr_wait,
pwm_dev->intr_complete,
DAVINCI_PWM_TIMEOUT);
if (pwm_dev->intr_complete)
pwm_dev->regs->ph1d = mode;
else
return -1;
} else
pwm_dev->regs->ph1d = mode;
break;
/* 在one-shot operation,设置重复次数 */
case PWMIOC_SET_RPT_VAL:
get_user(mode, (int *)arg);
if (mode < 0 || mode > 0xff)
return -EINVAL;
pwm_dev->regs->rpt = mode;
break;
/* 设置First-phase output level,也就是第一相的输出电平 */
case PWMIOC_SET_FIRST_PHASE_STATE:
get_user(mode, (int *)arg);
if (pwm_dev->regs->cfg & 0x20000)
return -EBUSY;
if (mode == 1) // 输出高电平
pwm_dev->regs->cfg |= 0x10;
else if (mode == 0) // 输出低电平
pwm_dev->regs->cfg &= ~0x10;
else
return -EINVAL;
break;
/* 设置Inactive output level,也就是pwm停止时的输出电平 */
case PWMIOC_SET_INACT_OUT_STATE:
get_user(mode, (int *)arg);
if (pwm_dev->regs->cfg & 0x20000)
return -EBUSY;
if (mode == 1)
pwm_dev->regs->cfg |= 0x20;
else if (mode == 0)
pwm_dev->regs->cfg &= ~0x20;
else
return -EINVAL;
break;
/* pwm开始运行 */
case PWMIOC_START:
pwm_dev->regs->start = 0x1;
break;
/* pwm停止。针对不同的运行模式采取不同的操作 */
case PWMIOC_STOP:
// 如果运行在one-shot mode,则直接停止
if (pwm_dev->regs->cfg & 0x1 && pwm_dev->regs->cfg & 0x20000)
pwm_dev->regs->cfg &= 0xFFFFFFFC;
/* 如果运行在continuous mode,则通过中断来同步,先设置成one-shot mode,运行完后
自然就停止了。
*/
if (pwm_dev->regs->cfg & 0x2 && pwm_dev->regs->cfg & 0x20000) {
unsigned long temp;
temp = pwm_dev->regs->cfg;
temp &= 0xFFFFFFFC;
temp |= 0x1;
/* Enable PWM interrupts */
pwm_dev->regs->cfg |= 0x40;
/* wait for the transaction to complete */
wait_event_timeout(pwm_dev->intr_wait,
pwm_dev->intr_complete,
DAVINCI_PWM_TIMEOUT);
if (pwm_dev->intr_complete)
pwm_dev->regs->cfg = temp;
else
return -1;
}
break;
}
return 0;
}
static int pwm_remove(struct device *device)
{
return 0;
}
static void pwm_platform_release(struct device *device)
{
/* this function does nothing */
}
/* 字符设备驱动的例程集 */
static struct file_operations pwm_fops = {
.owner = THIS_MODULE,
.llseek = pwm_llseek,
.open = pwm_open,
.release = pwm_release,
.ioctl = pwm_ioctl,
};
static struct class_simple *pwm_class = NULL;
static struct platform_device pwm_device[] = {
[0] = {
.name = "davinci_pwm0",
.id = 0,
.dev = {
.release = pwm_platform_release,
}
},
[1] = {
.name = "davinci_pwm1",
.id = 1,
.dev = {
.release = pwm_platform_release,
}
},
[2] = {
.name = "davinci_pwm2",
.id = 2,
.dev = {
.release = pwm_platform_release,
}
},
[3] = {.name = "davinci_pwm3",
.id = 3,
.dev = {
.release = pwm_platform_release,
}
}
};
static struct device_driver pwm_driver[] = {
[0] = {
.name = "davinci_pwm0",
.bus = &platform_bus_type,
.remove = pwm_remove
},
[1] = {
.name = "davinci_pwm1",
.bus = &platform_bus_type,
.remove = pwm_remove
},
[2] = {
.name = "davinci_pwm2",
.bus = &platform_bus_type,
.remove = pwm_remove
},
[3] = {
.name = "davinci_pwm3",
.bus = &platform_bus_type,
.remove = pwm_remove
},
};
/*
* This function marks a transaction as complete.
*/
static inline void pwm_davinci_complete_intr(struct pwm_davinci_device *dev)
{
dev->intr_complete = 1;
wake_up(&dev->intr_wait);
}
/* pwm中断处理程序,用于同步,设置某些寄存器 */
static irqreturn_t pwm_isr(int irq, void *dev_id, struct pt_regs *regs)
{
struct pwm_davinci_device *dev = dev_id;
/* Disable PWM interrupts */
dev->regs->cfg &= ~0x40;
pwm_davinci_complete_intr(dev);
return IRQ_HANDLED;
}
/* 驱动模块加载的初始化函数 */
static int __init pwm_init(void)
{
int result;
dev_t devno;
unsigned int size, i, j;
char *name[DAVINCI_PWM_MINORS];
if (cpu_is_davinci_dm6467()) {
pwm_minor_count = DM646X_PWM_MINORS;
for (i = 0; i < pwm_minor_count; i++)
name[i] = dm646x_name[i];
} else if (cpu_is_davinci_dm355()) {
pwm_minor_count = DM355_PWM_MINORS;
for (i = 0; i < pwm_minor_count; i++)
name[i] = dm355_name[i];
} else {
pwm_minor_count = DM644X_PWM_MINORS;
for (i = 0; i < pwm_minor_count; i++)
name[i] = dm644x_name[i];
}
size = pwm_device_count * pwm_minor_count;
/* Register the driver in the kernel */
/* 自动分配主设备号并分配size个次设备号*/
result = alloc_chrdev_region(&devno, 0, size, DRIVER_NAME);
if (result < 0) {
printk("DaVinciPWM: Module intialization failed.\
could not register character device\n");
return -ENODEV;
}
// 获取主设备号
pwm_major = MAJOR(devno);
/* Initialize of character device */
/* 初始化cdev,kobj,并注册c_dev.ops */
cdev_init(&c_dev, &pwm_fops);
c_dev.owner = THIS_MODULE;
c_dev.ops = &pwm_fops;
/* addding character device */
/*
将该c_dev加入到kobj_map中。这样当应用程序调用open()时,便能根据设备号找到该cdev。查找cdev
是在chrdev_open()例程中进行的,在该例程中,找到cdev后会将其注册到inode->i_cdev,并将
inode->i_devices加入到该cdev->list()(list_add(&inode->i_devices, &p->list)),
再将该cdev->ops注册到filp->f_op,最后调用filp->f_op->open(inode,filp);
*/
result = cdev_add(&c_dev, devno, pwm_minor_count);
if (result) {
printk("DaVinciPWM:Error adding DavinciPWM\n");
unregister_chrdev_region(devno, size);
return result;
}
/* 生成一个simple class类并注册到subsys */
pwm_class = class_simple_create(THIS_MODULE, "davinci_pwm");
if (!pwm_class) {
cdev_del(&c_dev);
return -EIO;
}
/*
注册所有达芬奇平台pwm接口的device driver和platform device;将device加入到
pwm_class,在/dev/下生成相应的节点,并开启时钟。
*/
for (i = 0; i < pwm_device_count; i++) {
for (j = 0; j < pwm_minor_count; j++) {
pwm_dev_array[j] =
kmalloc(sizeof(struct pwm_davinci_device),GFP_KERNEL);
pwm_dev_array[j]->devno = devno;
init_waitqueue_head(&pwm_dev_array[j]->intr_wait);
sprintf(pwm_dev_array[j]->name, "davinci_pwm%d", j);
/* 注册为 platform driver */
/* register driver as a platform driver */
if (driver_register(&pwm_driver[j]) != 0) {
unregister_chrdev_region(devno, size);
cdev_del(&c_dev);
kfree(pwm_dev_array[j]);
return -EINVAL;
}
/* 注册为 platform device */
/* Register the drive as a platform device */
if (platform_device_register(&pwm_device[j]) != 0) {
driver_unregister(&pwm_driver[j]);
unregister_chrdev_region(devno, size);
cdev_del(&c_dev);
kfree(pwm_dev_array[j]);
return -EINVAL;
}
/* 在/dev目录下生成节点 */
devno =
MKDEV(pwm_major,
pwm_minor_start + i * pwm_minor_count + j);
class_simple_device_add(pwm_class, devno, NULL,
"davinci_pwm%d", j);
/* 注册中断例程 */
/*
* DM355 has PWM3 IRQ at #28
*/
if (j == 3) {
result =
request_irq(IRQ_DM355_PWMINT3, pwm_isr,
SA_INTERRUPT,
pwm_dev_array[j]->name,
pwm_dev_array[j]);
} else {
result = request_irq(IRQ_PWMINT0 + j,
pwm_isr, SA_INTERRUPT,
pwm_dev_array[j]->name,
pwm_dev_array[j]);
}
if (result < 0) {
printk("Cannot initialize IRQ \n");
platform_device_unregister(&pwm_device[j]);
driver_unregister(&pwm_driver[j]);
kfree(pwm_dev_array[j]);
return result;
}
/* 打开pwm模块时钟 */
pwm_dev_array[j]->pwm_clk = clk_get(NULL, *(name + j));
if (IS_ERR(pwm_dev_array[j]->pwm_clk)) {
printk("Cannot get clock\n");
return -1;
}
clk_use(pwm_dev_array[j]->pwm_clk);
clk_enable(pwm_dev_array[j]->pwm_clk);
/* 获得该pwm接口的物理寄存器地址 */
pwm_dev_array[j]->regs =
(davinci_pwmregsovly) IO_ADDRESS(DAVINCI_PWM0_BASE +
j * 0x400);
}
}
return 0;
}
/* 驱动模块卸载时调用的函数 */
static void __exit pwm_exit(void)
{
dev_t devno;
unsigned int size, i;
/* 释放掉所有申请的资源 */
if (pwm_class != NULL) {
size = pwm_device_count * pwm_minor_count;
for (i = 0; i < size; i++) {
platform_device_unregister(&pwm_device[i]);
driver_unregister(&pwm_driver[i]);
devno = MKDEV(pwm_major, pwm_minor_start + i);
class_simple_device_remove(devno);
if ((i == 3) && (cpu_is_davinci_dm355()))
free_irq(IRQ_DM355_PWMINT3, pwm_dev_array[i]);
else
free_irq(IRQ_PWMINT0 + i, pwm_dev_array[i]);
clk_unuse(pwm_dev_array[i]->pwm_clk);
clk_disable(pwm_dev_array[i]->pwm_clk);
kfree(pwm_dev_array[i]);
}
class_simple_destroy(pwm_class);
}
cdev_del(&c_dev);
/* Release major/minor numbers */
if (pwm_major != 0) {
devno = MKDEV(pwm_major, pwm_minor_start);
size = pwm_device_count * pwm_minor_count;
unregister_chrdev_region(devno, size);
}
}
module_init(pwm_init);
module_exit(pwm_exit);
MODULE_AUTHOR("Texas Instruments");
MODULE_LICENSE("GPL");
davinci_pwm.h:
/*
* linux/drivers/char/davinci_pwm.h
*
* BRIEF MODULE DESCRIPTION
* DaVinci PWM register definitions
*
* Copyright (C) 2006 Texas Instruments.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef _DAVINCI_PWM_H
#define _DAVINCI_PWM_H
/**************************************************************************\
* Register Overlay Structure
\**************************************************************************/
typedef struct {
unsigned int pid;
unsigned int pcr;
unsigned int cfg;
unsigned int start;
unsigned int rpt;
unsigned int per;
unsigned int ph1d;
} davinci_pwmregs;
/**************************************************************************\
* Overlay structure typedef definition
\**************************************************************************/
typedef volatile davinci_pwmregs *davinci_pwmregsovly;
#define PWM_MINORS 3
#define DM646X_PWM_MINORS 2
#define DM644X_PWM_MINORS 3
#define DM355_PWM_MINORS 4
#define DAVINCI_PWM_MINORS DM355_PWM_MINORS /* MAX of all PWM_MINORS */
#define PWMIOC_SET_MODE 0x01
#define PWMIOC_SET_PERIOD 0x02
#define PWMIOC_SET_DURATION 0x03
#define PWMIOC_SET_RPT_VAL 0x04
#define PWMIOC_START 0x05
#define PWMIOC_STOP 0x06
#define PWMIOC_SET_FIRST_PHASE_STATE 0x07
#define PWMIOC_SET_INACT_OUT_STATE 0x08
#define PWM_ONESHOT_MODE 0
#define PWM_CONTINUOUS_MODE 1
#endif /* _DAVINCI_PWM_H */