前面几篇文章学习了向设备树文件中添加 Led设备节点信息。Led驱动代码框架搭建。文章地址如下:
设备树下Led驱动实验-向设备树文件添加Led设备节点-CSDN博客
设备树下Led驱动实验-Led驱动代码框架搭建-CSDN博客
本文继续进行 Led驱动实现。主要实现读取设备树文件中的寄存器地址,进行初始化,最后,实现Led灯的打开与关闭。
实现效果:通过应用程序调用 Led驱动,实现 Led灯的开与关。
这里在上一篇文章所实现的字符设备驱动框架代码的基础上,添加读取设备树中 Led设备节点信息,同时,进行Led的 IO的初始化工作,与开关灯的功能。
dtsled.c文件中的代码实现如下:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#define DEV_NAME "alpha_led"
#define DEV_CNT 1
#define ON_LED 1
#define OFF_LED 0
//地址映射后寄存器的虚拟地址指针
static void __iomem * IMX6ULL_CCM_CCGR1;
static void __iomem * IMX6ULL_SW_MUX_GPIO1_IO03;
static void __iomem * IMX6ULL_SW_PAD_GPIO01_IO03;
static void __iomem * IMX6ULL_GDIR;
static void __iomem * IMX6ULL_DR;
//Led设备结构体
struct dtsled_dev {
dev_t devid; //设备号(主+次设备号)
int major; //主设备号
int minor; //次设备号
struct cdev cdev;
struct class* class;
struct device * dev;
struct device_node * dev_node;
};
struct dtsled_dev dtsled;
void contrl_led(unsigned int status)
{
unsigned int reg_value = 0;
if(OFF_LED == status) //关闭Led灯
{
reg_value = readl(IMX6ULL_DR);
reg_value |= (1 << 3); //写入1,关闭Led
writel(reg_value, IMX6ULL_DR);
}
else if(ON_LED == status) //打开Led灯
{
reg_value = readl(IMX6ULL_DR);
reg_value &= ~(1 << 3); //写入0,打开Led
writel(reg_value, IMX6ULL_DR);
}
}
/*打开设备函数 */
static int dtsled_open(struct inode * node, struct file * file)
{
// file->private_data = &dtsled;
return 0;
}
/*写数据函数*/
static ssize_t dtsled_write(struct file * file, const char __user * buf, size_t count, loff_t * opt)
{
int ret = 0;
int rev_buf[2] = {0};
// struct dtsled_dev * led = (struct dtsled_dev*)(file->private_data);
ret = copy_from_user(rev_buf, buf, count);
if(ret < 0)
{
printk("led_write failed!\r\n");
return -EFAULT;
}
contrl_led(rev_buf[0]);
return 0;
}
/*关闭设备函数 */
static int dtsled_release(struct inode * node, struct file * file)
{
// struct dtsled_dev* led = (struct dtsled_dev*)(file->private_data);
return 0;
}
//字符设备的函数操作集
const struct file_operations dtsled_fops = {
.owner = THIS_MODULE,
.open = dtsled_open,
.write = dtsled_write,
.release = dtsled_release,
};
/*驱动入口函数 */
static int __init dtsled_init(void)
{
int ret = 0;
unsigned int buf[20] = {0};
int i = 0;
u32 reg_value = 0;
/*1. 读取设备树文件中寄存器地址 */
dtsled.dev_node = of_find_node_by_path("/alpha_led");
if(NULL == dtsled.dev_node)
{
printk("find dev-node failed!\n");
ret = -EINVAL;
goto apply_devid_failed;
}
ret = of_property_read_u32_array(dtsled.dev_node, "reg", buf, 10);
if(ret < 0)
{
printk("read dev_node'data failed!\n");
goto apply_devid_failed;
}
for(i=0; i<10; i++)
{
printk("0X%X ", buf[i]);
}
printk("\n");
/*2. 物理地址转换为虚拟地址*/
IMX6ULL_CCM_CCGR1 = ioremap(buf[0], buf[1]);
IMX6ULL_SW_MUX_GPIO1_IO03 = ioremap(buf[2], buf[3]);
IMX6ULL_SW_PAD_GPIO01_IO03 = ioremap(buf[4], buf[5]);
IMX6ULL_GDIR = ioremap(buf[6], buf[7]);
IMX6ULL_DR = ioremap(buf[8], buf[9]);
/*3. Led设备IO初始化 */
//使能时钟
reg_value = readl(IMX6ULL_CCM_CCGR1);
reg_value &= ~(3 << 26);
reg_value |= (3 << 26);
writel(reg_value, IMX6ULL_CCM_CCGR1);
//复用为GPIO功能
writel(0X05, IMX6ULL_SW_MUX_GPIO1_IO03);
//配置电气特性
writel(0X10B0, IMX6ULL_SW_PAD_GPIO01_IO03);
//设置为输出
reg_value = readl(IMX6ULL_GDIR);
reg_value &= ~(1 << 3);
reg_value |= (1 << 3);
writel(reg_value, IMX6ULL_GDIR);
//关闭Led灯
reg_value = readl(IMX6ULL_DR);
reg_value |= (1 << 3); //置1,关闭Led
writel(reg_value, IMX6ULL_DR);
dtsled.major = 0;
/*1. 申请设备号 */
if(dtsled.major) //给定设备号
{
dtsled.devid = MKDEV(dtsled.major, 0);
ret = register_chrdev_region(dtsled.devid, DEV_CNT, DEV_NAME);
}
else //向内核申请设备号
{
ret = alloc_chrdev_region(&dtsled.devid, 0, DEV_CNT, DEV_NAME);
dtsled.major = MAJOR(dtsled.devid);
dtsled.minor = MINOR(dtsled.devid);
printk("dtsled.major: %d\n", dtsled.major);
printk("dtsled.minor: %d\n", dtsled.minor);
}
if(ret < 0)
{
printk("apply_dev_numbers failed!\n");
goto apply_devid_failed;
}
/*2. 添加字符设备 */
dtsled.cdev.owner = THIS_MODULE;
cdev_init(&dtsled.cdev, &dtsled_fops);
ret = cdev_add(&dtsled.cdev, dtsled.devid, DEV_CNT);
if(ret < 0)
{
printk("cdev-add failed!\n");
goto cdev_add_failed;
}
/*3. 自动创建设备节点 */
//创建类
dtsled.class = class_create(THIS_MODULE, DEV_NAME);
if (IS_ERR(dtsled.class)) {
printk("class_create failed!\n");
ret = PTR_ERR(dtsled.class);
goto auto_class_create_failed;
}
//创建设备
dtsled.dev = device_create(dtsled.class, NULL, dtsled.devid, NULL, DEV_NAME);
if (IS_ERR(dtsled.dev)) {
printk("device_create failed!\n");
ret = PTR_ERR(dtsled.dev);
goto auto_create_dev_failed;
}
return 0;
auto_create_dev_failed:
class_destroy(dtsled.class);
auto_class_create_failed:
cdev_del(&dtsled.cdev);
cdev_add_failed:
unregister_chrdev_region(dtsled.devid, DEV_CNT);
apply_devid_failed:
return ret;
}
/*驱动出口函数 */
static void __exit dtsled_exit(void)
{
/*取消地址映射*/
iounmap(IMX6ULL_CCM_CCGR1);
iounmap(IMX6ULL_SW_MUX_GPIO1_IO03);
iounmap(IMX6ULL_SW_PAD_GPIO01_IO03);
iounmap(IMX6ULL_GDIR);
iounmap(IMX6ULL_DR);
/*1.删除字符设备 */
cdev_del(&dtsled.cdev);
/*2.注销设备号 */
unregister_chrdev_region(dtsled.devid, DEV_CNT);
/*3. 摧毁设备 */
device_destroy(dtsled.class, dtsled.devid);
/*4. 摧毁类*/
class_destroy(dtsled.class);
}
/*模块入口与出口*/
module_init(dtsled_init);
module_exit(dtsled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("LingXiaoZhan");
通过 ubuntu终端进入 5_dtsled工程目录下,编译驱动程序:
wangtian@wangtian-virtual-machine:~/zhengdian_Linux/Linux_Drivers/5_dtsled.c$ make
make -C /home/wangtian/zhengdian_Linux/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga M=/home/wangtian/zhengdian_Linux/Linux_Drivers/5_dtsled.c modules
make[1]: 进入目录“/home/wangtian/zhengdian_Linux/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga”
CC [M] /home/wangtian/zhengdian_Linux/Linux_Drivers/5_dtsled.c/dtsled.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/wangtian/zhengdian_Linux/Linux_Drivers/5_dtsled.c/dtsled.mod.o
LD [M] /home/wangtian/zhengdian_Linux/Linux_Drivers/5_dtsled.c/dtsled.ko
make[1]: 离开目录“/home/wangtian/zhengdian_Linux/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga”
wangtian@wangtian-virtual-machine:~/zhengdian_Linux/Linux_Drivers/5_dtsled.c$
可以看出,驱动程序已经编译成功。下面就是进行 驱动程序的测试。