led硬件地址映射和操作
1.点亮s5pv210的led灯首先需要知道其所用引脚,以及引脚所对应的状态寄存器与数据寄存器
根据外围电路图可知两个灯所用的引脚分别为gpc0_3与gpc0_4,找到其引脚所对应的寄存器
状态寄存器
5位的数据寄存器
在驱动模块加载中做地址映射,映射的地址为8个字节64位(包括状态寄存器与数据寄存器)
gpc0con + 1(一个寄存器为4个字节32位,表示指向下一个寄存器0xE0200064)
// 3,实现驱动模块加载/卸载入口函数
static int led_drv_init(void)
{
printk("--------^_* %s-------\n", __FUNCTION__);
int ret;
// 申请主设备号, 默认次设备号为0
// 参数1---指定的主设备号--就是一个整数,选255以上
//参数2--设备的描述--自定义的字符串
//参数3--设备驱动的文件操作对象
//返回值: 错误为负数,正确为0
ret = register_chrdev(led_major, led_name, &led_fops);
if(ret < 0)
{
printk("register_chrdev error\n");
return ret;
}
// 自动创建设备节点
//创建设备文件所属类别
//参数1--拥有者--当前模块
//参数2--类别的名字--自定义
//返回值---返回一个指针
led_cls = class_create(THIS_MODULE, "led_cls");
//创建设备文件
//参数1--所属类别
//参数2--当前创建的设备文件的父类是谁--一般NULL
//参数3--关联的设备号
//参数4--当前设备文件的私有数据--一般NULL
//参数5/6--设定设备文件的名字
device_create(led_cls, NULL, MKDEV(led_major, 0), NULL, "led0"); // led0
//参数1--硬件的物理地址
//参数2--映射的地址长度
//返回值---映射之后的虚拟地址
gpc0con = ioremap(0xE0200060, 8);
gpc0dat = gpc0con + 1;
return 0;
}
在卸载的函数中 去映射
static void led_drv_exit(void)
{
printk("--------^_* %s-------\n", __FUNCTION__);
//去映射
//参数1---映射之后的虚拟地址
iounmap(gpc0con);
//参数1--所属类别
//参数2--关联的设备号
device_destroy(led_cls, MKDEV(led_major, 0));
//参数1--所属类别
class_destroy(led_cls);
// 参数1---指定的主设备号--就是一个整数,选255以上
//参数2--设备的描述--自定义的字符串
unregister_chrdev(led_major, led_name);
}
在open函数中使能,配置状态寄存器为输出功能
int led_drv_open (struct inode *inode, struct file *filp)
{
printk("--------^_* %s-------\n", __FUNCTION__);
//直接对硬件进行操作
//配置成输出功能
*gpc0con &= ~(0xff<<12);
*gpc0con |= (0x11<<12);
return 0;
}
在write函数中给数据寄存器写入数据,控制灯的亮灭
// write(fd, buf, size); //虽然buf就是应用空间的数据存储的地址,但是驱动不要直接通过指针访问,copy_from_user会判断传进来的地址是否为空,做出错误处理.
ssize_t led_drv_write(struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
{
int ret = -1;
int value;
printk("--------^_* %s-------\n", __FUNCTION__);
//设计led的控制功能--灭亮---用户只要传递1(亮)/0(灭)
//参数1--内核驱动中的某个空间--用于存放用户空间拷贝过来的数据
//参数2--用户传递数据对应的地址
//参数3--传递数据的个数
//返回值: 返回没有拷贝成功的数据个数: 大于0表示出错(返回一个负数),0表示正确
ret = copy_from_user(&value, buf, count);
if(ret > 0)
{
printk("copy_from_user error\n");
return -EFAULT;
}
switch(value){
case 0:
// 全灭
*gpc0dat &= ~(0x3<<3);
break;
case 1: //全亮
*gpc0dat |= (0x3<<3);
break;
case 2: // 2--led1_on
*gpc0dat &= ~(0x3<<3);
*gpc0dat |= (0x1<<3);
break;
case 3: // 3_led2_on
*gpc0dat &= ~(0x3<<3);
*gpc0dat |= (0x2<<3);
break;
case 4: // 4_led1_off
*gpc0dat &= ~(0x1<<3);
break;
case 5: // 5_led2_off
*gpc0dat &= ~(0x1<<4);
break;
}
//返回拷贝功能的数据个数
return count;
}
在close中最好做一下灭灯操作
int led_drv_close(struct inode *inode, struct file *filp)
{
printk("--------^_* %s-------\n", __FUNCTION__);
//灭灯
*gpc0dat &= ~(0x3<<3);
return 0;
}
写一个应用层app控制灯的亮灭
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#define LED1_ON 2
#define LED2_ON 3
#define LED1_OFF 4
#define LED2_OFF 5
int main(int argc, char *argv[])
{
int on;
int ret;
//直接将驱动模块当做文件来操作
int fd = open("/dev/led0", O_RDWR);
if(fd < 0)
{
perror("open");
exit(1);
}
//先全灭
on = 0;
ret = write(fd, &on, 4);
if(ret < 0)
{
perror("write off ");
exit(1);
}
while(1)
{
//led1 on
on = LED1_ON;
ret = write(fd, &on, 4);
if(ret < 0)
{
perror("write on ");
exit(1);
}
sleep(1);
on = LED1_OFF;
ret = write(fd, &on, 4);
if(ret < 0)
{
perror("write on ");
exit(1);
}
sleep(1);
on = LED2_ON;
ret = write(fd, &on, 4);
if(ret < 0)
{
perror("write on ");
exit(1);
}
sleep(1);
on = LED2_OFF;
ret = write(fd, &on, 4);
if(ret < 0)
{
perror("write off ");
exit(1);
}
sleep(1);
on = 1;
ret = write(fd, &on, 4);
if(ret < 0)
{
perror("write off ");
exit(1);
}
sleep(1);
on = 0;
ret = write(fd, &on, 4);
if(ret < 0)
{
perror("write off ");
exit(1);
}
sleep(1);
}
close(fd);
}
完整led灯驱动代码
// 1, 添加头文件
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/io.h>
#include <asm/uaccess.h>
static unsigned int led_major = 266;
static char *led_name = "led_device";
static struct class *led_cls;
static volatile unsigned long *gpc0con = NULL;
static volatile unsigned long *gpc0dat = NULL;
int led_drv_open (struct inode *inode, struct file *filp)
{
printk("--------^_* %s-------\n", __FUNCTION__);
//直接对硬件进行操作
//配置成输出功能
*gpc0con &= ~(0xff<<12);
*gpc0con |= (0x11<<12);
return 0;
}
// read(fd, buf, size);
ssize_t led_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
printk("--------^_* %s-------\n", __FUNCTION__);
return 0;
}
// write(fd, buf, size); //虽然buf就是应用空间的数据存储的地址,但是驱动不要直接通过指针访问,copy_from_user会判断传进来的地址是否为空,做出错误处理.
ssize_t led_drv_write(struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
{
int ret = -1;
int value;
printk("--------^_* %s-------\n", __FUNCTION__);
//设计led的控制功能--灭亮---用户只要传递1(亮)/0(灭)
//参数1--内核驱动中的某个空间--用于存放用户空间拷贝过来的数据
//参数2--用户传递数据对应的地址
//参数3--传递数据的个数
//返回值: 返回没有拷贝成功的数据个数: 大于0表示出错(返回一个负数),0表示正确
ret = copy_from_user(&value, buf, count);
if(ret > 0)
{
printk("copy_from_user error\n");
return -EFAULT;
}
switch(value){
case 0:
// 全灭
*gpc0dat &= ~(0x3<<3);
break;
case 1: //全亮
*gpc0dat |= (0x3<<3);
break;
case 2: // 2--led1_on
*gpc0dat &= ~(0x3<<3);
*gpc0dat |= (0x1<<3);
break;
case 3: // 3_led2_on
*gpc0dat &= ~(0x3<<3);
*gpc0dat |= (0x2<<3);
break;
case 4: // 4_led1_off
*gpc0dat &= ~(0x1<<3);
break;
case 5: // 5_led2_off
*gpc0dat &= ~(0x1<<4);
break;
}
//返回拷贝功能的数据个数
return count;
}
int led_drv_close(struct inode *inode, struct file *filp)
{
printk("--------^_* %s-------\n", __FUNCTION__);
//灭灯
*gpc0dat &= ~(0x3<<3);
return 0;
}
//文件操作对象---为用户程序提供文件io接口的对象
const struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_drv_open,
.read = led_drv_read,
.write = led_drv_write,
.release =led_drv_close,
};
// 3,实现驱动模块加载/卸载入口函数
static int led_drv_init(void)
{
printk("--------^_* %s-------\n", __FUNCTION__);
int ret;
// 申请主设备号, 默认次设备号为0
// 参数1---指定的主设备号--就是一个整数,选255以上
//参数2--设备的描述--自定义的字符串
//参数3--设备驱动的文件操作对象
//返回值: 错误为负数,正确为0
ret = register_chrdev(led_major, led_name, &led_fops);
if(ret < 0)
{
printk("register_chrdev error\n");
return ret;
}
// 自动创建设备节点
//创建设备文件所属类别
//参数1--拥有者--当前模块
//参数2--类别的名字--自定义
//返回值---返回一个指针
led_cls = class_create(THIS_MODULE, "led_cls");
//创建设备文件
//参数1--所属类别
//参数2--当前创建的设备文件的父类是谁--一般NULL
//参数3--关联的设备号
//参数4--当前设备文件的私有数据--一般NULL
//参数5/6--设定设备文件的名字
device_create(led_cls, NULL, MKDEV(led_major, 0), NULL, "led0"); // led0
//参数1--硬件的物理地址
//参数2--映射的地址长度
//返回值---映射之后的虚拟地址
gpc0con = ioremap(0xE0200060, 8);
gpc0dat = gpc0con + 1;
return 0;
}
static void led_drv_exit(void)
{
printk("--------^_* %s-------\n", __FUNCTION__);
//去映射
//参数1---映射之后的虚拟地址
iounmap(gpc0con);
//参数1--所属类别
//参数2--关联的设备号
device_destroy(led_cls, MKDEV(led_major, 0));
//参数1--所属类别
class_destroy(led_cls);
// 参数1---指定的主设备号--就是一个整数,选255以上
//参数2--设备的描述--自定义的字符串
unregister_chrdev(led_major, led_name);
}
// 2,声明驱动模块加载/卸载入口函数
module_init(led_drv_init);
module_exit(led_drv_exit);
//在驱动装载的时候传递参数 insmod led_drv.ko led_major=270 led_name="fs210_led"
module_param(led_major, int, 0644);
module_param(led_name, charp, 0644);
// 4, 添加gpl认证
MODULE_LICENSE("GPL");
MODULE_AUTHOR("BaJie <bajie@qq.com>");
MODULE_DESCRIPTION("Led driver for s5pv210");