EPIT + PWM
摘要:
imx6 提供了一个 GPT 和两个 EPIT,共三个定时器中断,但是 GPT 已经被用作为系统的时钟中断,所以本文使用 EPIT 来实现 PWM 脉冲个数的输出,先确定 PWM 的周期,然后根据周期计算出对应脉冲所占用的时间,当时间达到设定时,产生定时中断,关闭 PWM 的输出。PWM 输出有多种方式,本文直接调用内核空间 PWM 中的API进行操作,将 PWM 中的部分参数放在设备树中。
编写EPIT驱动存在的问题:
GPC中断号 《设备树中》
a. 手册中对应的硬件中断号 - 32 = interrupts = <0 56 IRQ_TYPE_LEVEL_HIGH>;
b. cat /proc/interrupts 第一个数为映射出的中断号 倒数第二个为硬件 GIC中断号 = 设备数
c. cd /proc/device-tree 根节点下的所有属性和子节点
d. 定义全局指针时,一定要分配空间
e. 内核变量改变,应用空间调用时, ==》》 需上锁
f. 卸载驱动时,出现错误原因:定义一个reg 结构体指针时,如果需要进行地址ioremap映射,不应再kmalloc分配空间。
EPIT 驱动设置步骤
- 先添加设备树文件,最好添加在最后调用的设备树文件
需指定父中断:interrupt-parent = <&gpc> 或者直接放入 imx6qdl.dtsi 中;
epit1: epit@020d0000 { /* EPIT1 */
compatible = "fsl,imx6q-epit1";
reg = <0x020d0000 0x4000>;
interrupts = <0 56 IRQ_TYPE_LEVEL_HIGH>;
};
epit2: epit@020d4000 { /* EPIT2 */
compatible = "fsl,imx6q-epit2";
reg = <0x020d4000 0x4000>;
interrupts = <0 57 IRQ_TYPE_LEVEL_HIGH>;
};
- 在 include/dt-bindings/clock/imx6qdl-clock.h 添加两个宏
#define IMX6QDL_CLK_EPIT1 ***
#define IMX6QDL_CLK_EPIT2 ***
//数字大小,定义在原来宏后面,防止 CLK[] 数组越界, 宏大小 < IMX6QDL_CLK_END;
- 添加时钟,【/sys/kernel/debug/clk/】开发板可用时钟目录
在driver/clk/imx/clk-imx6q.c
-> 在imx6q_clocks_init函数中添加:
clk[IMX6QDL_CLK_EPIT1] = imx_clk_gate2("epit1(/sys/kernel/debug/clk/名字)", "ipg_per(/sys/kernel/debug/clk/名字/clk-rate)", base + 0x6c(epit1 控制地址), 12 (epit1 clocks (epit1_clk_enable)));
clk[IMX6QDL_CLK_EPIT2] = imx_clk_gate2("epit2", "ipg_per", base + 0x6c, 14);
和
clk_register_clkdev(clk[IMX6QDL_CLK_EPIT1], "per(匹配的字符)", "imx-epit.1(匹配的字符)");
clk_register_clkdev(clk[IMX6QDL_CLK_EPIT2], "per", "imx-epit.2");
这里一定不能漏了,否则在驱动中clk_get_sys会失败的。clk_get_sys("imx-epit.2", "per");
最后,给出一些与时钟有关的API函数:
struct clk *__clk_lookup(const char *name) 通过时钟名找到时钟
static inline int clk_enable(struct clk *clk) 使能时钟,不会睡眠
static inline void clk_disable(struct clk *clk) 禁止时钟,不会睡眠
static inline int clk_prepare_enable(struct clk *clk) 使能时钟,可能会睡眠
static inline void clk_disable_unprepare(struct clk *clk) 禁止时钟,可能会睡眠
static inline unsigned long clk_get_rate(struct clk *clk) 获取时钟频率
static inline int clk_set_rate(struct clk *clk, unsigned long rate) 设置时钟频率
static inline long clk_round_rate(struct clk *clk, unsigned long rate) 获取最接近的时钟频率
static inline int clk_set_parent(struct clk *clk, struct clk *parent) 设置时钟的父时钟
static inline struct clk *clk_get_parent(struct clk *clk) 获取时钟的父时钟
驱动源文件
#include "pwm_epit.h"
pwm_dev *pwmdev;
epit_reg *reg;
/* Clear interrupt flag */
static void clearinter(epit_reg *reg)
{
writel(0x1, &(reg->EPITSR));
}
/* After request_irq enable epit by epit EPITCR register */
static void epit_enable(epit_reg *reg, int isOn)
{
u32 val;
val = readl(&(reg->EPITCR));
if(isOn) {
val |= EPITCR_EN;
} else {
val &= ~(EPITCR_EN);
}
writel(val, &(reg->EPITCR));
}
/* Definite interrupt hander function */
static irqreturn_t epit2_irq(int irq, void *dev_id)
{
int val;
epit_reg *dev = (epit_reg *)dev_id;
val = readl(&(dev->EPITSR));
if(val & (1 <<0)) { /* judge compare event */
pwm_imx6_disable(pwmdev);
atomic_set(&pwmdev->flag, 1);
if (pwmdev->async_queue)
kill_fasync(&pwmdev->async_queue, SIGUSR1, POLL_IN);
}
clearinter(dev);
epit_enable(dev, 0); /* exe only one */
return IRQ_HANDLED;
}
/* Init epit2 register */
static int init_epit(pwm_dev *pwmdev)
{
u32 val;
struct clk *timer_clk;
char name[15];
unsigned long rate;
unsigned int ret = 0;
pwmdev->nd = of_find_node_by_path("/soc/aips-bus@02000000/epit@020d4000");
// countsdev.nd = of_find_compatible_node(NULL, NULL, "fsl,imx6q-epit2");
if(pwmdev->nd) {
reg = of_iomap(pwmdev->nd, 0);
pwmdev->irq = irq_of_parse_and_map(pwmdev->nd, 0);
// printk("irq = %d\r\n", countsdev.irq);
// printk("®.EPITCR = %x\r\n", ®->EPITCR);
// printk("®.EPITSR = %x\r\n", ®->EPITSR);
// printk("®.EPITLR = %x\r\n", ®->EPITLR);
// printk("®.EPITCMPR = %x\r\n", ®->EPITCMPR);
// printk("®.EPITCNR = %x\r\n", ®->EPITCNR);
} else {
printk("can not find countsdev.nd!!!\n");
return -1;
}
/* enable epit2 clock with per */
timer_clk = clk_get_sys("imx-epit.2", "per");
if (IS_ERR(timer_clk)) {
printk("Faild to get timer_clk !\r\n");
return -1;
}
rate = clk_get_rate(timer_clk);
printk(KERN_INFO"rate = %u\n", rate);
clk_prepare_enable(timer_clk);
/* clearing register EPITCR */
writel(0, &(reg->EPITCR));
/* Init register EPITCR */
val = EPITCR_CLKSRC_PERIPHERAL|EPITCR_IOVW|EPITCR_WAITEN|EPITCR_STOPEN;
val |= (EPITCR_RLD | EPITCR_OCIEN | EPITCR_ENMOD);
writel(val, &(reg->EPITCR));
printk("EPITCR = %x\r\n", readl(&(reg->EPITCR)));
/* set value of load */
// writel(0, &(reg->EPITLR));
/* set value register EPITCMPR, it will generate interrupt when the EPITCNR = EPITCMPR */
writel(0, &(reg->EPITCMPR));
/* request irq */
memset(pwmdev->name, 0, sizeof(name));
sprintf(pwmdev->name, "epit2_timer");
ret = request_irq(pwmdev->irq, epit2_irq, IRQF_TRIGGER_NONE, pwmdev->name, reg);
if(ret < 0) {
return -EFAULT;
}
return 0;
}
/* Set frac and timer timeout */
/* Tout(s) = ((frac +1 )* value) / Tclk */
static void epit_set_count(epit_reg *reg, unsigned int frac, unsigned int value)
{
u32 val;
if(frac > 0xFFF)
frac = 0xFFF;
val = readl(&(reg->EPITCR));
val |= EPITCR_PRESC(frac);
writel(val, &(reg->EPITCR));
/* set value of load */
writel(value, &(reg->EPITLR));
}
/******************************************************************************/
/*
* unify API with PWM in kernel
* set/get/enable/disbale PWM parament by ususal API interface
*/
/* update pwm paraments */
static void pwm_imx6_update(pwm_dev *pwmdev)
{
pwm_config(pwmdev->pwm, pwmdev->pwminfo.duty_ns, pwmdev->pwminfo.period_ns);
}
/* enable pwm */
static void pwm_imx6_enable(pwm_dev *pwmdev)
{
pwm_enable(pwmdev->pwm);
}
/* disable pwm */
static void pwm_imx6_disable(pwm_dev *pwmdev)
{
//set duty_ns = 0 before shutdowm pwm
pwmdev->pwminfo.duty_ns= 0;
pwm_imx6_update(pwmdev);
pwm_disable(pwmdev->pwm);
}
/* set pwm period_ns */
static void pwm_imx6_set_period(pwm_dev *pwmdev, unsigned int period_ns)
{
pwmdev->pwminfo.period_ns = period_ns;
pwm_set_period(pwmdev->pwm, pwmdev->pwminfo.period_ns);
}
/* get pwm period_ns */
static unsigned int pwm_imx6_get_period(pwm_dev *pwmdev)
{
return pwm_get_period(pwmdev->pwm);
}
/******************************************************************************/
/* init filp_operation functions */
static int pwm_imx6_open(struct inode *inode, struct file *filp)
{
filp->private_data = pwmdev;
return 0;
}
static ssize_t pwm_imx6_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
int ret = 0;
unsigned char tem_flag = 0;
pwm_dev *dev = (pwm_dev *)filp->private_data;
tem_flag = atomic_read(&dev->flag);
atomic_set(&dev->flag, 0);
ret = copy_to_user(buf, &tem_flag, sizeof(tem_flag));
if (ret < 0) {
printk("kernel read failed!!!\r\n");
return -EINVAL;
}
return 0;
}
static int pwm_imx6_fasync(int fd, struct file *filp, int on)
{
int ret;
pwm_dev *dev = (pwm_dev *)filp->private_data;
ret = fasync_helper(fd, filp, on, &dev->async_queue);
if (ret < 0)
return -EIO;
return 0;
}
static long pwm_imx6_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
unsigned int ret;
filp->private_data = pwmdev;
switch (cmd)
{
case PWM_B_SET_PARA:
// tripple args to be checked
ret = copy_from_user(¶Configs, (struct pwm_para_config __user *)arg,
sizeof(struct pwm_para_config));
if (ret) {
printk("copy user failed\n");
return -EFAULT;
}
pwmdev->pwminfo.polarity = paraConfigs.c_polarity;
pwmdev->pwminfo.period_ns = paraConfigs.c_period_ns;
pwmdev->pwminfo.duty_ns = paraConfigs.c_duty_ns;
pwmdev->ms_counts = (unsigned long)paraConfigs.c_counts;
if(pwmdev->ms_counts < 30000) { /* prevent overflow interrupt */
pwmdev->ms_counts *= 66000;
pwmdev->ms_counts -= 66*200; /* compensation 200 us */
epit_set_count(reg, 0, pwmdev->ms_counts); /* set paraConfigs.c_counts(ms) interrupt */
} else if (pwmdev->ms_counts < 100000) {
pwmdev->ms_counts /= 1000;
pwmdev->ms_counts *= 100000;
pwmdev->ms_counts -= 2000; /* compensation 200 us */
epit_set_count(reg, 659, pwmdev->ms_counts); /* set paraConfigs.c_counts(ms) interrupt */
} else {
pwmdev->ms_counts /= 100000;
pwmdev->ms_counts *= 2000000;
pwmdev->ms_counts -= 4; /* compensation 200 us */
epit_set_count(reg, 3299, pwmdev->ms_counts); /* set paraConfigs.c_counts(ms) interrupt */
}
pwm_set_polarity(pwmdev->pwm, pwmdev->pwminfo.polarity);
pwm_imx6_update(pwmdev);
pwm_imx6_enable(pwmdev);
epit_enable(reg, 1);
break;
case PWM_B_GET_PARA:
paraConfigs.c_polarity = pwmdev->pwminfo.polarity;
paraConfigs.c_period_ns = pwmdev->pwminfo.period_ns;
paraConfigs.c_duty_ns = pwmdev->pwminfo.duty_ns;
ret = copy_to_user((struct pwm_para_config __user *)arg, ¶Configs,
sizeof(struct pwm_para_config));
if (ret) {
printk("copy_to_user failed.");
return -EFAULT;
}
break;
case PWM_B_ENABLE:
pwm_imx6_enable(pwmdev);
break;
case PWM_B_DISABLE:
pwm_imx6_disable(pwmdev);
break;
default:
printk("pwmdev: cmd error!\n");
return -EFAULT;
}
return 0;
}
static int pwm_imx6_release(struct inode *inode, struct file *filp)
{
filp->private_data = pwmdev;
atomic_inc(&pwmdev->flag);
// pwm_imx6_disable(pwmdev); pwm disable when call close function in use spcae
return pwm_imx6_fasync(-1, filp, 0);;
}
struct file_operations pwm_imx6_fops = {
.owner = THIS_MODULE,
.open = pwm_imx6_open,
.release = pwm_imx6_release,
.unlocked_ioctl = pwm_imx6_ioctl,
.fasync = pwm_imx6_fasync,
.read = pwm_imx6_read,
};
/********* get pwm-b informations from device tree *********/
static ssize_t pwm_imx6_parse_dt(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
int ret;
if (!node) {
dev_err(&pdev->dev, "pwm-b: Device Tree node missing\n");
return -EINVAL;
}
ret = of_property_read_u32(node, "pwmchannel", &pwmdev->pwminfo.channel);
if (ret < 0) {
dev_err(&pdev->dev, "pwm-b: pwmchannel missing\n");
return ret;
}
ret = of_property_read_u32(node, "period_ns", &pwmdev->pwminfo.period_ns);
if (ret < 0) {
dev_err(&pdev->dev, "pwm-b: period_ns missing\n");
return ret;
}
ret = of_property_read_u32(node, "duty_ns", &pwmdev->pwminfo.duty_ns);
if (ret < 0) {
dev_err(&pdev->dev, "pwm-b: duty_ns missing\n");
return ret;
}
return 0;
}
/******************** probe function **************************/
static int pwm_probe(struct platform_device *pdev)
{
int ret;
pwmdev = kzalloc(sizeof(pwm_dev), GFP_KERNEL);
if (!pwmdev) {
return -ENOMEM;
}
/* creater device number */
if (pwmdev->major) { /* definite device number */
pwmdev->devid = MKDEV(pwmdev->major, 0);
register_chrdev_region(pwmdev->devid, PWM_DEVICE,
PWM_DEV_NAME);
} else { /* without definite device number */
alloc_chrdev_region(&pwmdev->devid, 0, PWM_DEVICE,
PWM_DEV_NAME);
pwmdev->major = MAJOR(pwmdev->devid); /* get major device number */
pwmdev->minor = MINOR(pwmdev->devid); /* get minor device number */
}
printk(KERN_INFO"pwmdev major=%d,minor=%d\r\n", pwmdev->major,
pwmdev->minor);
/* Init cdev */
pwmdev->cdev.owner = THIS_MODULE;
cdev_init(&pwmdev->cdev, &pwm_imx6_fops);
/* add a cdev */
ret = cdev_add(&pwmdev->cdev, pwmdev->devid, PWM_DEVICE);
if(ret < 0) {
printk(KERN_INFO"error : cdev_add\r\n");
return ret;
}
/* creater a class */
pwmdev->class = class_create(THIS_MODULE, PWM_DEV_NAME);
if(IS_ERR(pwmdev->class)) {
ret = PTR_ERR(pwmdev->class);
goto error_class_create;
}
/* creater /device/PWM_DEV_NAME */
pwmdev->device = device_create(pwmdev->class, NULL, pwmdev->devid, NULL, PWM_DEV_NAME);
if(IS_ERR(pwmdev->device)) {
ret = PTR_ERR(pwmdev->device);
goto error_device_create;
}
/* default paraments pwm from device tree */
ret = pwm_imx6_parse_dt(pdev);
if (ret < 0) {
dev_err(&pdev->dev, "pwm-b: Device not found\n");
return -ENODEV;
}
/* request pwm-b device */
pwmdev->pwm = pwm_request(pwmdev->pwminfo.channel, "pwm-b");
if (IS_ERR(pwmdev->pwm)) {
dev_err(&pdev->dev, "pwm-b: unable to request legacy PWM\n");
ret = PTR_ERR(pwmdev->pwm);
return ret;
}
/* set default paraments */
pwm_imx6_update(pwmdev);
/* pwm_set_polarity before enable pwm, and after set pwm paraments */
ret = pwm_set_polarity(pwmdev->pwm, PWM_POLARITY_NORMAL);
if (ret < 0)
printk("pwm set polarity fail, ret = %d\n", ret);
init_epit(pwmdev);
/* Init flag */
atomic_set(&pwmdev->flag, 0);
return 0;
error_device_create:
class_destroy(pwmdev->class);
error_class_create:
cdev_del(&pwmdev->cdev);
kfree(pwmdev);
return ret;
}
static int pwm_remove(struct platform_device *dev)
{
free_irq(pwmdev->irq, reg);
/* release map address */
iounmap(reg);
/* disable pwm_device */
pwm_imx6_disable(pwmdev);
/* free pwm_device */
pwm_free(pwmdev->pwm);
/* logout char device */
device_destroy(pwmdev->class, pwmdev->devid);
class_destroy(pwmdev->class);
cdev_del(&pwmdev->cdev);
unregister_chrdev_region(pwmdev->devid, PWM_DEVICE);
/* release point to pwmdev after pwmdev->devid */
kfree(pwmdev);
printk(KERN_INFO"unregister_chrdev ok !!!\r\n");
return 0;
}
/******************** match table **************************/
static const struct of_device_id pwm_of_match[] = {
{ .compatible = "pwm-pulse" },
{ /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, pwm_of_match);
static struct platform_driver pwm_driver = {
.driver = {
.name = "pwm",
.of_match_table = pwm_of_match,
},
.probe = pwm_probe,
.remove = pwm_remove,
};
static int __init pwm_driver_init(void)
{
return platform_driver_register(&pwm_driver);
}
static void __exit pwm_driver_exit(void)
{
platform_driver_unregister(&pwm_driver);
}
module_init(pwm_driver_init);
module_exit(pwm_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zsj");
MODULE_DESCRIPTION("pwm b send 1 ms pulse!!!");
MODULE_ALIAS("pwm-b");