(一)linux驱动之led

一、我的第一个linux驱动程序

1.1、采用的linux板子

我采用的是 正点原子 的linux板子,觉得原子的板子还是很不错的。

1.2、硬件原理

由于正点原子使用的设计是通过电平 拉低 是点亮led,拉高则是关闭led。

1.3、个人理解的linux驱动流程

1.构建驱动加载和卸载函数
2.申请设备号,设备号可以指定,或者让系统自动分配
3.初始化创建的cdev结构体。
4.最后自动创建设备节点

1.4、驱动代码

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/cdev.h>


#define NEWCHRLED_NAME    "newchrled"

struct newchrled {
    struct cdev cdev;
    struct class *class;
    struct device *device;
    dev_t devid;
    int major;
    int minor;
};




#define CCM_CCGR1           (0X020C406C)
#define SW_MUX_GPIO1_IO03   (0X020E0068)
#define SW_PAD_GPIO1_IO03   (0X020E02F4)
#define GPIO1_DR            (0X0209C000)
#define GPIO1_GDIR          (0X0209C004)


#define LED_ON  1
#define LED_OFF 0

static void __iomem *ccm_ccgr1;
static void __iomem *sw_mux_gpio01_io03;
static void __iomem *sw_pad_gpio01_io03;
static void __iomem *gpio01_dr;
static void __iomem *gpio01_gdir;

//申请虚拟地址映射
static void led_physical_to_virtual_map(void)
{
   ccm_ccgr1            = ioremap(CCM_CCGR1,4);
   sw_mux_gpio01_io03   = ioremap(SW_MUX_GPIO1_IO03,4);
   sw_pad_gpio01_io03   = ioremap(SW_PAD_GPIO1_IO03,4);
   gpio01_dr            = ioremap(GPIO1_DR,4);
   gpio01_gdir          = ioremap(GPIO1_GDIR,4);
}

//释放虚拟地址映射
static void led_physical_to_virtual_unmap(void)
{
    iounmap(ccm_ccgr1);
    iounmap(sw_mux_gpio01_io03);
    iounmap(sw_pad_gpio01_io03);
    iounmap(gpio01_dr);
    iounmap(gpio01_gdir);
}

static void led_driver_switch(unsigned char sta)
{
    unsigned int val = 0;
    if(sta == LED_OFF){
        //设置默认高点平
        val = readl(gpio01_dr);
        val |= (1 << 3);
        writel(val,gpio01_dr);

    }else if(sta == LED_ON){
        //设置默认高点平
        val = readl(gpio01_dr);
        val &= ~(1 << 3);
        writel(val,gpio01_dr);
    }
}

//注册模块时候初始化GPIO
static void led_driver_init(void)
{
    unsigned int val = 0;

    //初始化GPIO的时钟
    val = readl(ccm_ccgr1);
    val &= ~(3 << 26);
    val |= (3 << 26);
    writel(val,ccm_ccgr1);

    writel(0x5,sw_mux_gpio01_io03);
    writel(0X10B0,sw_pad_gpio01_io03);

    //设置为输出
    val = readl(gpio01_gdir);
    val |= (1 << 3);
    writel(val,gpio01_gdir);

    //设置默认关闭led
    led_driver_switch(LED_OFF);

}


//卸载模块时候禁止led
static void led_driver_freed(void)
{
    led_driver_switch(LED_OFF);
}


static int newchrled_open(struct inode *inode, struct file *file)
{
    return 0;
}

static int newchrled_release(struct inode *inode, struct file *file)
{
    return 0;
}

static ssize_t newchrled_write(struct file *filp, const char __user *buf,
			size_t count, loff_t * ppos)
{
    int ret = 0;
    char read_buf[1];
    ret = copy_from_user(read_buf,buf,count);
    if(ret < 0){
        printk("kernel:write data err\r\n");
    }
    else{
        led_driver_switch(read_buf[0]);
    }
    return 0;
}


static  struct file_operations newchrled_fops = {
    .owner      = THIS_MODULE,
    .open       = newchrled_open,
    .write      = newchrled_write,
    .release    = newchrled_release,
};

struct newchrled newchrled;


static int __init newchrled_init(void)
{
    int result = 0;

    led_physical_to_virtual_map();
    led_driver_init();

    /*申请设备号*/
    newchrled.major = 0;  //默认系统自动分配

    if (newchrled.major){
        newchrled.devid = MKDEV(newchrled.major,0);
        result = register_chrdev_region(newchrled.devid,1,NEWCHRLED_NAME);
    }
    else {
		result = alloc_chrdev_region(&newchrled.devid, 0, 1, NEWCHRLED_NAME);
		newchrled.major = MAJOR(newchrled.devid);
        newchrled.minor = MINOR(newchrled.devid);
	}
    if(result < 0){
        printk("devid err\r\n");
        goto devid_err;
        
    }
    printk("major:%d minor:%d\r\n",newchrled.major,newchrled.minor);

    newchrled.cdev.owner = THIS_MODULE;
    cdev_init(&newchrled.cdev,&newchrled_fops);

    

    result  = cdev_add(&newchrled.cdev,newchrled.devid,1);

    if(result < 0){
        printk("cdev_add err\r\n");
        goto cdev_err;
        
    }

    newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
	if(IS_ERR(newchrled.class)) {
		result = PTR_ERR(newchrled.class);
		goto class_create_cdev;
	}


    newchrled.device = device_create(newchrled.class,NULL,newchrled.devid,NULL,NEWCHRLED_NAME);
    if(IS_ERR(newchrled.device)) {
        result = PTR_ERR(newchrled.device);
        goto device_create_out;
    }

    printk("newchrled init ok\r\n");
    return 0;

device_create_out:
    class_destroy(newchrled.class);

class_create_cdev:
    cdev_del(&newchrled.cdev);

cdev_err:
    unregister_chrdev_region(newchrled.devid,1);

devid_err:
    return result;
}

static void __exit newchrled_exit(void)
{
    led_driver_freed();

    led_physical_to_virtual_unmap();

    device_destroy(newchrled.class, newchrled.devid);
    class_destroy(newchrled.class);
    cdev_del(&newchrled.cdev);
    unregister_chrdev_region(newchrled.devid,1);

    printk("module exit\r\n");
}

module_init(newchrled_init);
module_exit(newchrled_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("gale");

1.5、要点个人理解

1、linux使用虚拟内存管理,所以要想实际操作真实的物理地址,必须调用函数申请虚拟地址对应的物理地址,相对应的,在加载函数的时候申请了,那么在卸载驱动的时候必须将这些申请的虚拟地址给释放掉!
2、任何资源在加载时候申请了,除非逼不得已,否则都要在卸载驱动时候将其释放掉。
3、个人采用的是使用**alloc_chrdev_region()**这个函数系统自动申请设备号,最后再将主设备号和从设备号分离。
4、个人使用的是在加载驱动的时候,就自动创建设备节点,否则的话还需要使用这个命令创建设备节点 mknod

1.6、个人感想

作为我的第一个程序,有些地方可能还需要优化,关于驱动一个IO做出的比较多的步骤,linux应该会有更好的框架来处理这部分,当然这只是我的猜想,关于linux方面的知识还在学习中,后续学到在接着更新。

发布了29 篇原创文章 · 获赞 0 · 访问量 436

猜你喜欢

转载自blog.csdn.net/weixin_42547950/article/details/104157738