2010年8月17日星期二

TQ2440上的PWM驱动移植

硬件分析:














使用的是TOUT0 端口。可以通过控制PWM的周期来控制蜂鸣器的发声频率。


内核API查找:
通过lxr搜到在include/kernel/pwm.harch/arm/plat-samsung/pwm.cpwmapi
6/*
7 * pwm_request - request a PWM device
8 */
9struct pwm_device *pwm_request(int pwm_id, const char *label);
10
11/*
12 * pwm_free - free a PWM device
13 */
14void pwm_free(struct pwm_device *pwm);
15
16/*
17 * pwm_config - change a PWM device configuration
18 */
19int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns);
20
21/*
22 * pwm_enable - start a PWM output toggling
23 */
24int pwm_enable(struct pwm_device *pwm);
25
26/*
27 * pwm_disable - stop a PWM output toggling
28 */
29void pwm_disable(struct pwm_device *pwm);


找到API就好办了,开工了^_^

module的代码(pwm..c)

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/pwm.h> //for pwm functions
#include <linux/fs.h>
#include <linux/cdev.h>

#define DEV_NAME "pwm_beep"
static struct pwm_device *pwm_beep;
static dev_t beep_dev;
static struct cdev beep_cdev;

int open_beep(struct inode *inode, struct file *file)
{
int retval = 0;
pwm_beep = pwm_request(0, DEV_NAME);
if(pwm_beep == NULL)
retval = -EBUSY;
return retval;
}

int release_beep(struct inode *inode, struct file *file)
{
pwm_disable(pwm_beep);
pwm_free(pwm_beep);
return 0;
}

int ioctl_beep(
struct inode *inode,
struct file *file,
unsigned int cmd,
unsigned long arg)
{
int period_ns;
pwm_disable(pwm_beep);
printk(KERN_INFO "MARK 1 \n");
if(cmd > 0)
{
period_ns = 1000000000/cmd;
printk(KERN_INFO "mark 2 \n");
if(pwm_config(pwm_beep, period_ns>>1, period_ns) >= 0)
{
printk(KERN_INFO "mark 3\n");
pwm_enable(pwm_beep);
}
else
printk(KERN_INFO "config fail\n");
}
return 0;
}

struct file_operations beep_ops = {
.owner = THIS_MODULE,
.release = release_beep,
.open = open_beep,
.ioctl = ioctl_beep,
};

static int __init beep_init(void)
{
int retval;
retval = alloc_chrdev_region(&beep_dev, 0, 1, DEV_NAME);
if (retval < 0)
goto no_dev;
cdev_init(&beep_cdev, &beep_ops);
beep_cdev.owner = THIS_MODULE;
retval = cdev_add(&beep_cdev, beep_dev, 1);
if(retval < 0)
goto no_cdev;
printk(KERN_INFO "BEEP initialize successfully!\n");
return 0;
no_cdev:
unregister_chrdev_region(beep_dev, 1);
no_dev:
return retval;
}

static void __exit beep_exit(void)
{
cdev_del(&beep_cdev);
unregister_chrdev_region(beep_dev, 1);
printk(KERN_INFO "beep unloaded.\n");
}

module_init(beep_init);
module_exit(beep_exit);

MODULE_LICENSE("GPL");

说明:
  1. pwm被打开时(open),开始申请pwm资源。
  2. User program通过调用ioctl来控制蜂鸣器的频率。第二个参数即为频率
———————————————————————————————————————
User program(beep.c)

#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>

#define beep_path "/dev/beep"

int main(void)
{
int freq= 0;
int fd;

fd = open(beep_path, O_RDWR);
if(fd < 0)
{
printf("file: %s open failed! \n", beep_path);
return -EINVAL;
}
printf("Input the frequency(0 to exit):\n");
while(1)
{
scanf("%d", &freq);
printf("freq = %d\n", freq);
ioctl(fd,freq,0);
if(freq == 0)
break;
}
close(fd);
return 0;
}


装载module

Insmod pwm.ko
出错,说pwm_request(), pwm_enable(), pwm_disabled(), pwm_free() 没有找到对应的函数,装载失败。
有可能是linux/pwm.h并没有包含arch/arm/plat-samsung/pwm.c,于是将这个文件复制到源文件里,并改名为pwm.h(避免重名),在pwm.c中吧#include <linux/pwm.h>这行del掉,换成#include "pwm.h"
make后出错:
/home/oneyoung/modules/pwm/pwm.c:86: error: redefinition of '__inittest'
/home/oneyoung/modules/pwm/pwm.h:406: note: previous definition of '__inittest' was here
/home/oneyoung/modules/pwm/pwm.c:86: error: redefinition of 'init_module'
/home/oneyoung/modules/pwm/pwm.h:406: note: previous definition of 'init_module' was here
打开pwm.h后发现最后一行arch_initcall(pwm_init); pwm.c 中的module_init()重了,注释掉这一句后编译成功。

insmod pwm.ko成功^_^,运行beep,在调用ioctl是出现kernel oops,系统崩溃(T_T). 我勒个去,再回去看arch/arm/plat-samsung/pwm.c的源代码。


====================分割线======================

看了代码才发现pwm.c是作为build in module在内核里的,struct pwm_device还包括很多其他的跟系统有关的数据结构,比如deviceclass等。看来这个直接拿来用不太现实,得大刀破斧改,还是自己再写一个吧。


改过之后的代码:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>

#include <mach/regs-gpio.h>
#include <linux/irq.h>
#include <linux/io.h> //for __raw_writel() __raw_readl()

#include <plat/regs-timer.h> //for S3C2410_TCONB S3C2410_TCMPB etc.

#define DEV_NAME "pwm_beep"
//static struct pwm_device *pwm_beep;
static dev_t beep_dev;
static struct cdev beep_cdev;

/***********pwm control mode**************/
#define pwm_tcon_start(tcon_base) (1 << (tcon_base + 0))
#define pwm_tcon_invert(tcon_base) (1 << (tcon_base + 2))
#define pwm_tcon_autoreload(tcon_base) (1 << (tcon_base + 3))
#define pwm_tcon_manulupdate(tcon_base) (1 << (tcon_base + 1))

int open_beep(struct inode *inode, struct file *file)
{
unsigned long flags;
unsigned long gcon;
unsigned long tcon;
local_irq_save(flags);
//_____________set the gpio to TOUT________________
gcon = __raw_readl(S3C2410_GPBCON);
gcon &= ~3;
gcon |= 2;
__raw_writel(gcon, S3C2410_GPBCON);
tcon = __raw_readl(S3C2410_TCON);
tcon |= pwm_tcon_start(0);
__raw_writel(tcon, S3C2410_TCON);
local_irq_restore(flags);
return 0;
}


int release_beep(struct inode *inode, struct file *file)
{
unsigned long flags;
unsigned long tcon;
//*********disable the pwm ************/
local_irq_save(flags);

tcon = __raw_readl(S3C2410_TCON);
tcon &= ~pwm_tcon_start(0);
__raw_writel(tcon, S3C2410_TCON);

local_irq_restore(flags);
return 0;
}



int ioctl_beep(
struct inode *inode,
struct file *file,
unsigned int cmd,
unsigned long arg)
{
unsigned long flags;
unsigned long tcon;
unsigned long tcnt;
// pwm_disable(pwm_beep);
// printk(KERN_INFO "MARK 1 \n");
if(cmd > 0)
{
tcnt = 0xffffffff/cmd;
local_irq_save(flags);
//*********set to manulupdate mode ********//
tcon = __raw_readl(S3C2410_TCON);
tcon |= pwm_tcon_manulupdate(0);
tcon |= pwm_tcon_autoreload(0);
__raw_writel(tcon, S3C2410_TCON);
__raw_writel(tcnt, S3C2410_TCNTB(0));
__raw_writel(tcnt>>1, S3C2410_TCMPB(0));
tcon &= ~pwm_tcon_manulupdate(0);
__raw_writel(tcon, S3C2410_TCON);
local_irq_restore(flags);
}
return 0;
}

struct file_operations beep_ops = {
.owner = THIS_MODULE,
.release = release_beep,
.open = open_beep,
.ioctl = ioctl_beep,
};

static int __init beep_init(void)
{
int retval;
retval = alloc_chrdev_region(&beep_dev, 0, 1, DEV_NAME);
if (retval < 0)
goto no_dev;
cdev_init(&beep_cdev, &beep_ops);
beep_cdev.owner = THIS_MODULE;
retval = cdev_add(&beep_cdev, beep_dev, 1);
if(retval < 0)
goto no_cdev;
printk(KERN_INFO "BEEP initialize successfully!\n");
return 0;
no_cdev:
unregister_chrdev_region(beep_dev, 1);
no_dev:
return retval;
}

static void __exit beep_exit(void)
{
cdev_del(&beep_cdev);
unregister_chrdev_region(beep_dev, 1);
printk(KERN_INFO "beep unloaded.\n");
}

module_init(beep_init);
module_exit(beep_exit);

MODULE_LICENSE("GPL");
下载到机子里,insmod后,一切正常
mknod后也正常
beep程序打开后,在输入频率,蜂鸣器终于发声了--- 总算是成功了!!

没有评论:

发表评论