驱动实现led,pwm和中断基础知识

时间:2021-12-09 12:07:37

2015.4.8
星期三 晴天

今天老师讲的内容是内核编写led和pwm驱动,实现花样灯和放歌的功能。理解应用和驱动的对接,最后自己实现了在放歌的时候根据
歌曲的节奏亮灭一个小灯,应为两个独立的驱动都已经写好,想要组合其实很简单,只要在主调函数里面打开两个驱动的设备节点,
分别进行操作并有机的组合在一起就行了。最后老师复习了中断的一些基础知识,总结一下:

异常处理:
当异常发送时:
nand flash 拷贝到sdram中运行,这是和nor flash 的区别之一

1.拷贝cpsr到spsr
2.设置适当的cpsr位
2.1 改变处理器状态进入arm态
2.2 改变处理器模式进入相应的异常模式
2.3 设置中断禁止位禁止相应中断
3.保存返回地址pc+4到lr
4.设置pc相应的异常向量

返回时,异常处理需要:
1.从SPSR恢复到CPSR
2.从LR恢复到PC
3.Noto: 这些操作只能在ARM态执行

中断的控制步骤:
1.要想使用中断:首先需要打开总中断
2.设置中断控制器使能(vic0intenable)
3.设置外设屏蔽寄存器:mask
4.相应管脚设成可以触发中断
5.设置中断激发方式:上升沿,下降沿......
6.中断处理完成后需要清pending, pending 标识这个外设曾经发生过中断,中断触发后自动置1

笼统的说就是:
总中断
mask
enable
清pending 不然会一直中断

下面是pwm控制的内核驱动程序,可参考学习:
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/string.h>
#include <asm/uaccess.h> /*copy_to_user copy_from_user*/
#include <asm/io.h> /*ioremap*/

#define NUM_MINORS 1
#define MINOR_NR 0
#define DEV_NAME "s5pc100_pwm_char"
#define KBUF_SIZE 50

#define CMD_ON _IO('k', 0)
#define CMD_OFF _IO('k', 1)

#define GPDCON 0xe0300080
#define TIMER_BASE 0xea000000 这是偏移量的基址,下面的寄存器地址加上这个基址构成真正的地址
#define TCFG0 0x0
#define TCFG1 0x4
#define TCON 0x8
#define TCNTB1 0x18
#define TCMPB1 0x1c

#define magic_number 'k'
#define BEEP_ON _IO(magic_number,0)
#define BEEP_OFF _IO(magic_number,1)
#define SET_FRE _IO(magic_number,2)

static void *vgpdcon;
static void *vtimer_base;

static struct cdev s5pc100_pwm_cdev;
static int s5pc100_pwm_major = 250; /*Documentation/devices.txt*/
static dev_t s5pc100_pwm_devno;

MODULE_LICENSE("GPL");

void pwm_init(void)
{
/*init pin func as pwm tout1*/
writel((readl(vgpdcon) & ~(0xf << 4)) | (0x2 << 4), vgpdcon); 先读后写,避免改变寄存器的其他位,值得学习
/*init first prescaler: fout = pclk/(value + 1) */
writel(65, vtimer_base + TCFG0);
/*init mux*/
writel(readl(vtimer_base + TCFG1) & ~(0xf << 4), vtimer_base + TCFG1);
/*init TCMPB1 TCNTB1*/
writel(1000, vtimer_base + TCNTB1);
writel(500, vtimer_base + TCMPB1);
/*manual update*/
writel(readl(vtimer_base + TCON) & ~(0xf << 8) | (1 << 9),
vtimer_base + TCON);
}

void timer_start(void)
{
/*start timer and enable auto reload*/
writel(readl(vtimer_base + TCON) & ~(0xf << 8) | (1 << 11) | (1 << 8),
vtimer_base + TCON);
}

void timer_stop(void)
{
writel(readl(vtimer_base + TCON) & ~(1 << 8),
vtimer_base + TCON);
}

void set_tcntb1(unsigned int value)
{
writel(value, vtimer_base + TCNTB1);
}

void set_tcmpb1(unsigned int value)
{
writel(value, vtimer_base + TCMPB1);
}

static int s5pc100_pwm_open(struct inode *nodp, struct file *filp)
{
pwm_init(); 在打开设备的时候初始化定时器的设置,方便应用的操作
printk(KERN_INFO "s5pc100_pwm open! \n");
return 0;
}

static int s5pc100_pwm_release(struct inode *nodp, struct file *filp)
{
timer_stop(); 在退出程序的时候,停止定时器
printk(KERN_INFO "s5pc100_pwm release! \n");
return 0;
}

static long
s5pc100_pwm_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
switch (cmd) {
case BEEP_ON:
printk(KERN_INFO "cmd = %d arg = %lu \n", cmd, arg);
timer_start();
break;
case BEEP_OFF:
printk(KERN_INFO "cmd = %d arg = %lu \n", cmd, arg);
timer_stop();
break;
case SET_FRE:
printk(KERN_INFO "cmd = %d arg = %lu \n", cmd, arg);
set_tcntb1(1000000/arg);
set_tcmpb1(1000000/(arg + arg));
default:
printk(KERN_INFO "no such command! \n");
break;
}
return 0;
}

struct file_operations s5pc100_pwm_ops = {
.open = s5pc100_pwm_open,
.release = s5pc100_pwm_release,
.unlocked_ioctl = s5pc100_pwm_unlocked_ioctl,
};

static int __init s5pc100_pwm_init(void)
{
int ret;

/*construct cdev number*/
s5pc100_pwm_devno = MKDEV(s5pc100_pwm_major, MINOR_NR); //s5pc100_pwm_major << 20 | s5pc100_pwm_minor;
ret = register_chrdev_region(s5pc100_pwm_devno, NUM_MINORS, DEV_NAME);

/*register char dev number*/
if (ret) {
printk(KERN_WARNING "register_chrdev_region failed! \n");
ret = alloc_chrdev_region(&s5pc100_pwm_devno, MINOR_NR, NUM_MINORS, DEV_NAME);
if (ret) {
printk(KERN_WARNING "alloc chrdev_region failed! \n");
goto err_0;
}
}

/*register char device*/
/*cdev initialize*/
cdev_init(&s5pc100_pwm_cdev, &s5pc100_pwm_ops);

/*add to kernel*/
ret = cdev_add(&s5pc100_pwm_cdev, s5pc100_pwm_devno, NUM_MINORS);
if (ret) {
printk(KERN_WARNING "cdev add failed! \n");
goto err_1;
}

/*physical address ---> virtual address*/
vgpdcon = ioremap(GPDCON, 4); 将物理地址转化成虚拟地址,每个地址转换为四字节
vtimer_base = ioremap(TIMER_BASE, 0x20); ***这里需要注意一下,因为这里不是针对一个地址转化,这里表示一组地址,所以转换地址需要够大才行***

printk(KERN_INFO "s5pc100_pwm_init ! \n");
return 0;

/*error management*/
err_1:
unregister_chrdev_region(s5pc100_pwm_devno, NUM_MINORS);
err_0:
return ret;
}

static void __exit s5pc100_pwm_exit(void)
{
iounmap(vgpdcon);
iounmap(vtimer_base);
cdev_del(&s5pc100_pwm_cdev);
unregister_chrdev_region(s5pc100_pwm_devno, NUM_MINORS);
printk("s5pc100_pwm_exit ! \n");
}

module_init(s5pc100_pwm_init);
module_exit(s5pc100_pwm_exit);

MODULE_AUTHOR("minj@farsight.com.cn");
MODULE_DESCRIPTION("just for test!");

led小灯的驱动实现程序,很简单,但是很有参考价值,这些都是基础:

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/string.h>
#include <asm/uaccess.h> /*copy_to_user copy_from_user*/
#include <asm/io.h> /*ioremap*/

#define NUM_MINORS 1
#define MINOR_NR 0
#define DEV_NAME "s5pc100_led_char"
#define KBUF_SIZE 50

#define CMD_ON _IO('k', 0)
#define CMD_OFF _IO('k', 1)

#define GPG3CON 0xe03001c0
#define GPG3DAT 0xe03001c4
static int *vgpg3con;
static int *vgpg3dat;

static struct cdev s5pc100_led_cdev;
static int s5pc100_led_major = 250; /*Documentation/devices.txt*/
static dev_t s5pc100_led_devno;
static char kbuf[KBUF_SIZE];

MODULE_LICENSE("GPL");

/**
* led_init: init led pins
*/
void led_init(void)
{
writel((readl(vgpg3con) & ~(0xffff)) | 0x1111, vgpg3con);
}

void led_exit(void)
{
writel(readl(vgpg3dat) & ~(0xf), vgpg3con);
}

static int s5pc100_led_open(struct inode *nodp, struct file *filp)
{
led_init();
printk(KERN_INFO "s5pc100_led open! \n");
return 0;
}
static int s5pc100_led_release(struct inode *nodp, struct file *filp)
{
led_exit();
printk(KERN_INFO "s5pc100_led release! \n");
return 0;
}

static long
s5pc100_led_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
switch (cmd) {
case CMD_ON: 这个参数相当于应用的命令
printk(KERN_INFO "cmd = %d arg = %lu \n", cmd, arg);
writel(readl(vgpg3dat) | (1 << arg), vgpg3dat); 注意参数的作用和传递方式
break;
case CMD_OFF:
printk(KERN_INFO "cmd = %d arg = %lu \n", cmd, arg);
writel(readl(vgpg3dat) & ~(1 << arg), vgpg3dat);
break;
default:
printk(KERN_INFO "no such command! \n");
break;
}
return 0;
}

struct file_operations s5pc100_led_ops = {
.open = s5pc100_led_open,
.release = s5pc100_led_release,
.unlocked_ioctl = s5pc100_led_unlocked_ioctl,
};

static int __init s5pc100_led_init(void)
{
int ret;

/*construct cdev number*/
s5pc100_led_devno = MKDEV(s5pc100_led_major, MINOR_NR); //s5pc100_led_major << 20 | s5pc100_led_minor;
ret = register_chrdev_region(s5pc100_led_devno, NUM_MINORS, DEV_NAME);

/*register char dev number*/
if (ret) {
printk(KERN_WARNING "register_chrdev_region failed! \n");
ret = alloc_chrdev_region(&s5pc100_led_devno, MINOR_NR, NUM_MINORS, DEV_NAME);
if (ret) {
printk(KERN_WARNING "alloc chrdev_region failed! \n");
goto err_0;
}
}

/*register char device*/
/*cdev initialize*/
cdev_init(&s5pc100_led_cdev, &s5pc100_led_ops);

/*add to kernel*/
ret = cdev_add(&s5pc100_led_cdev, s5pc100_led_devno, NUM_MINORS);
if (ret) {
printk(KERN_WARNING "cdev add failed! \n");
goto err_1;
}

/*physical address ---> virtual address*/
vgpg3con = ioremap(GPG3CON, 4);
vgpg3dat = ioremap(GPG3DAT, 4);

printk(KERN_INFO "s5pc100_led_init ! \n");
return 0;

/*error management*/
err_1:
unregister_chrdev_region(s5pc100_led_devno, NUM_MINORS);
err_0:
return ret;
}

static void __exit s5pc100_led_exit(void)
{
iounmap(vgpg3con);
iounmap(vgpg3dat);
cdev_del(&s5pc100_led_cdev);
unregister_chrdev_region(s5pc100_led_devno, NUM_MINORS);
printk("s5pc100_led_exit ! \n");
}

module_init(s5pc100_led_init);
module_exit(s5pc100_led_exit);

MODULE_AUTHOR("minj@farsight.com.cn");
MODULE_DESCRIPTION("just for test!");