Linux驱动之LED

Linux驱动—LED第一个IO控制,从点灯开始

控制一个LED,就是对IO的控制,首先初始化配置,设置IO复用,配置IO的电器属性,配置IO的输入输出等。但是这些操作是在驱动程序中实现的。

字符设备的开发流程:应用程序调用open函数打开一个设备文件(LED也是一个设备),这个设备文件是在驱动程序加载完成之后产生的,存放于/dev/目录下,应用程序的open与驱动的open对应,应用层有相应的打开,读写操作函数,驱动层也有与之对应的读写函数,在Linux内核文件中include/linux/fs.h中,有一个结构体类型专门存放内核驱动操作函数集(函数指针集)——struct file_operations;

因此:字符驱动的编写主要是驱动对应的open write close ,其实就是对struct file_operations 结构体的成员变量的实现

(1)模块的加载与卸载注册函数

头文件:

#include <linux/module.h>
#include <linux/init.h>
module_init(xxxx_init)向Linux内核注册一个模块加载函数module_exit(xxxx_exit);注册一个模块卸载函数
MODULE_LICENSE(“GPL”);      模块的许可证信息,必须添加!
MODULE_AUTHOR(“Liupeng);   模块的作者

模块的加载:驱动程序编译为.ko文件后,需要手动加载驱动程序,加载方式有:insmod xxx.ko 和 modprobe xxx.ko两种
insmod
不能解决模块之间的依赖关系,
例如,该模块调用另外一个模块则首先需要加载被调用模块

modprobe
会自动确定模块之间的依赖关系,将有依赖关系的驱动程序全部加载,一般使用该方式加载驱动.ko

在使用modprobe加载一个新的驱动之前需要使用depmod命令一次加载出来三个新的文件.deb .alias .sysbols,且需要加载的驱动对应的.ko文件必须在 /lib/modules/4.1.15 这个目录下,因为modprobe执行时会在该路径下查找对应的.ko文件调用其中一个加载命令,就会执行驱动程序中注册好的模块加载函数,该模块加载函数的主要功能就是字符设备的加载,设备的配置(也可以在驱动的open中实现设备的配置)。

模块的卸载:rmmod xxx.ko该命令执行之后,会调module_exit(xxxx_exit);注册的卸载函数,清理工作。

(2)字符设备的加载和卸载

我们要在注册好的字符设备加载函数中完成初始化工作,完成后进行设备的加载,实现设备的读写操作,在注册的字符设备卸载函数中完成设备的卸载工作
头文件:
#include <linux/fs.h>

static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)static inline void unregister_chrdev(unsigned int major, const char *name)

major : 主设备号
name:设备名
fops:结构体file_operations类型指针,指向设备的操作函数集合变量;
file_operations所在头文件:#include <linux/fs.h>
register_chrdev 函数一般在函数xxx_init()中进行
unregister_chrdev函数一般在函数xxx_exit()中进行

设备号:
unsigned int 32位数据 dev_t
头文件 include/linux/types.h
高12位为主设备号 范围:0—4095低20位为次设备号 范围:0 –

一个主设备号代表了一类设备,一类设备下面包含了其他的设备,其他的设备用次设备号来区分

在Linux下查看所有设备的设备号:cat /proc/devices

设备号的操作函数或宏:
静态分配设备号:
头文件: include/linux/kdev_t.h
从dev_t 获取主设备号和次设备号:dev_t为总的设备号MAJOR(dev_t)
MINOR(dev_t)
也可以使用主设备号和次设备号构成dev_t.,
MKDEV(major,minor)

(3)实现具体的读写操作

地址映射MMU:内存管理单元
①、完成虚拟空间到物理空间的映射。
②、内存保护,设置存储器的访问权限,设置虚拟存储空间的缓冲特性。

Linux内核启动之后,会初始化MMU,设置好之后CPU访问的都是虚拟地址,比如(imx6uLL)GPIO1_IO03 引 脚 的 复 用 寄 存 器 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 地址为 0X020E0068。如果没有开启 MMU 的话直接向 0X020E0068 这个寄存器 地址写入数据就可以配置 GPIO1_IO03 的复用功能。现在开启了 MMU,设置了内存映射,因此就不能直接向 0X020E0068物理地址写入数据了。我们必须得到 0X020E0068 物理地址对应的虚拟地址。

(1)ioremap()
头文件: #include <linux/io.h>
原型:

#define ioremap(cookie,size) __arm_ioremap((cookie), (size),  MT_DEVICE)

假设需要获取0X020E0068物理地址对应的虚拟地址xxx(代表地址),xxx=ioremap(0X020E0068,4);
4代表内存长度字节,寄存器是4字节的。

iounmap()void iounmap (volatile void __iomem *addr)

卸载驱动的时候需要释放掉ioremap函数所做的映射
addr:要取消掉的虚拟空间首地址对映射后的虚拟内存操作

写操作:

void writeb(u8  value, volatile void __iomem *addr)
void writew(u16 value, volatile void __iomem *addr)
void writel(u32 value, volatile void __iomem *addr)

value:需要写入的数据
addr:地址

读操作:

u8 readb (const volatile void __iomem *addr)
u16 readw(const volatile void __iomem *addr)
u32 readl(const volatile void __iomem *addr)

addr:地址
返回值:读到的数据

用户空间与内核空间的数据传递应用

空间的读写操作,是对内核空间的一种数据访问与获取,它们之间的数据交换获取方式:
copy_from_user();

static inline long copy_from_user(void __user *to, const void  *from, unsigned long n)

用户空间向内核空间的数据传递
copy_to_user();

static inline long copy_to_user(void __user *to, const void  *from, unsigned long n)

to:目标
from:依赖
n:字节数。由用户程序决定
返回值:0 成功, 其他值失败,返回未传输成功的字节个数内核空间向内核空间的数据传递

通过以上基本框架的建立,再根据实际的寄存器地址,通过对虚拟地址的读写,实现对寄存器的配置,在驱动层的读写中对寄存器的读写,即实现了应用层对Led的控制。具体的代码实现:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>

#define NEWCHRLED_CNT   1  
#define NEWCHRLED_NAME  "newchrled" 
#define LEDON  1
#define LEDOFF 0

#define CCM_CCGR1_BASE         (0x020c406c)
#define SW_MUX_GPIO1_IO03_BASE (0x020e0068)
#define SW_PAD_GPIO1_IO03_BASE (0x020e02f4)
#define GPIO1_DR_BASE          (0x0209c000)
#define GPIO1_GDIR_BASE        (0x0209c004)

static void __iomem  *IMX6U_CCM_CCGR1  ;
static void __iomem  *SW_MUX_GPIO1_IO03; 
static void __iomem  *SW_PAD_GPIO1_IO03;
static void __iomem  *GPIO1_DR;
static void __iomem  *GPIO1_GDIR;

struct newchrled_dev{
    dev_t devid ;        /*设备号*/
    struct cdev   cdev;
    struct class *class;
    struct device *devices;
    int major;
    int minor;
};
struct newchrled_dev newchrled;

void led_switch(u8 sta)
{
    u32 val=0;
    if(sta==LEDON){
        val=readl(GPIO1_DR);
        val&=~(1<<3);
        writel(val,GPIO1_DR);
    }else {
        val=readl(GPIO1_DR);
        val|=(1<<3);
        writel(val,GPIO1_DR);
    }
}
static int led_open(struct inode *inode,struct file *filp)
{
    filp->private_data = &newchrled;/*设置私有数据*/
	return 0;
}
static ssize_t led_read(struct file *filp, char __user *buf,
            size_t cnt, loff_t *offt)
{
    return 0;
}
static ssize_t led_write(struct file*filp, const char __user *buf,
            size_t cnt,loff_t *offt)
{
    int retvalue;
    u8 databuf[1];
    u8 ledstat;
    retvalue=copy_from_user(databuf,buf,cnt);
    if(retvalue<0){
        printk("kernel write error");
        return 1;
    }
    ledstat=databuf[0];
    if(ledstat==LEDON)
        led_switch(LEDON);
    else if(ledstat==LEDOFF){
        led_switch(LEDOFF);
    }
    return 0;
}
static int led_release(struct inode *inode,struct file *filp)
{
	return 0;
}
static struct file_operations newchrled_fops={
    .owner= THIS_MODULE,
    .open = led_open,
    .read = led_read,
    .write= led_write,
    .release= led_release
};


static int __init led_init(void)
{
    u32 val=0;
    IMX6U_CCM_CCGR1    = ioremap(CCM_CCGR1_BASE,4);
    SW_MUX_GPIO1_IO03  = ioremap(SW_MUX_GPIO1_IO03_BASE,4);
    SW_PAD_GPIO1_IO03  = ioremap(SW_PAD_GPIO1_IO03_BASE,4);
    GPIO1_DR           = ioremap(GPIO1_DR_BASE,4);
    GPIO1_GDIR         = ioremap(GPIO1_GDIR_BASE,4);      

    val = readl(IMX6U_CCM_CCGR1);
    val&= ~(3<<26);
    val|=  (3<<26);
    writel(val,IMX6U_CCM_CCGR1);

    writel(5,SW_MUX_GPIO1_IO03);
    
    writel(0x10b0,SW_PAD_GPIO1_IO03);

    val = readl(GPIO1_GDIR);
    val|= (1<<3);
    writel(val,GPIO1_GDIR);

    val = readl(GPIO1_DR);
    val|= (1<<3);
    writel(val,GPIO1_DR);
    
    /*创建设备号*/
    if(newchrled.major){    /*如果定义了设备号*/
        newchrled.devid = MKDEV(newchrled.major,0);  //确定设备号
        register_chrdev_region(newchrled.devid,NEWCHRLED_CNT,
                        NEWCHRLED_NAME);  /*注册设备号*/
    }else {
        alloc_chrdev_region(&newchrled.devid,0,NEWCHRLED_CNT,
                        NEWCHRLED_NAME);  /*申请设备号*/
        newchrled.major = MAJOR(newchrled.devid);
        newchrled.minor = MINOR(newchrled.devid);
    }
    printk("newchrled major = %d, minor = %d \r\n",
                    newchrled.major,newchrled.minor);
    newchrled.cdev.owner = THIS_MODULE;  /*cdev结构体初始化*/
    cdev_init(&newchrled.cdev,&newchrled_fops);
    cdev_add( &newchrled.cdev,newchrled.devid,NEWCHRLED_CNT);  /*添加字符设备*/
    /*创建设备节点*/
    newchrled.class = class_create(THIS_MODULE,NEWCHRLED_NAME);/*创建类*/
    if(IS_ERR(newchrled.class)){
        return PTR_ERR(newchrled.class);
    }
    newchrled.devices = device_create(newchrled.class,NULL ,   /*创建设备*/
                    newchrled.devid,NULL,NEWCHRLED_NAME);
    if(IS_ERR(newchrled.devices)){
        return PTR_ERR(newchrled.devices);
    }
    return 0;
}

static void __exit led_exit(void)
{
    iounmap(IMX6U_CCM_CCGR1)  ;
    iounmap(SW_MUX_GPIO1_IO03);
    iounmap(SW_PAD_GPIO1_IO03);
    iounmap(GPIO1_DR);
    iounmap(GPIO1_GDIR);      
    unregister_chrdev_region(newchrled.devid,1);//释放掉设备号
    cdev_del(&newchrled.cdev);
    class_destroy(newchrled.class);
    device_destroy(newchrled.class,newchrled.devid);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Liupeng Peng");


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值