Linux驱动——platform设备驱动实验

文章目录

    • 1. 驱动的分离和分层
      • 1.1 驱动的分离
      • 1.3 驱动的分层
    • 2. 实验程序编写
      • 2.1 leddevice.c编写
      • 2.2 leddriver.c编写
    • 3. 测试
    • 4. 总结


1. 驱动的分离和分层

1.1 驱动的分离

Linux驱动——platform设备驱动实验_第1张图片
    假设平台A、B、C都有MPU6050这个IIC接口的六轴传感器,那么驱动框架应该如上图所示。主机驱动是必须的,但设备驱动都是相同的,没必要每个平台都写一个驱动。
Linux驱动——platform设备驱动实验_第2张图片
    那么最好的做法就是每个平台的I2C控制器都提供一个统一的接口,每个设备也只提供一个驱动程序,每个设备通过统一的I2C接口驱动来访问,这样就大大方便了。
Linux驱动——platform设备驱动实验_第3张图片
    实际的I2C驱动设备肯定有很多种,所以实际的架构如上图所示。
    这就是驱动的分离,就是将主机驱动和设备驱动分离开来,相当于将设备信息从设备驱动中剥离开来,驱动使用标准方法获取到设备信息(比如从设备树中获取设备信息),根据获取到的信息来初始化设备。
    这就相当于驱动只负责驱动,设备只负责设备,想办法将两者匹配即可,这就是Linux中的总线(bus)、设备(device)、驱动(driver)模型。总线就是帮助设备和驱动之间进行牵线搭桥。
Linux驱动——platform设备驱动实验_第4张图片

1.3 驱动的分层

    Linux下的驱动也是分层的,不同层会处理不同的内容。以input为例介绍一下驱动的分层。input子系统负责管理所有跟输入有关的驱动,包括键盘、鼠标、触摸屏等,最底层的就是设备的原始驱动,负责获取输入设备的原始值,获取到的输入事件上报给input核心层,input核心层会处理各种IO模型,并且提供file_operations操作集合,我们在编写输入设备驱动的时候只需要处理好输入事件的上报即可,至于如何处理这些上报的输入事件那是上层去考虑的,我们不用管。

2. 实验程序编写

2.1 leddevice.c编写

    首先我们来编写platform设备程序。

/*
 * platform设备结构体 
 */
static struct platform_device leddevice = {
	.name = "imx6ul-led",
	.id = -1,
	.dev = {
		.release = &led_release,
	},
	.num_resources = ARRAY_SIZE(led_resources),
	.resource = led_resources,
};

    需要定义一个platform设备结构体,其中存放有设备的名称、id、资源、资源数量等相关设备信息。

/*
 * @description	: 设备模块加载 
 * @param 		: 无
 * @return 		: 无
 */
static int __init leddevice_init(void)
{
	return platform_device_register(&leddevice);
}

/*
 * @description	: 设备模块注销
 * @param 		: 无
 * @return 		: 无
 */
static void __exit leddevice_exit(void)
{
	platform_device_unregister(&leddevice);
}

    在init中完成设备的注册,在exit注销设备。

2.2 leddriver.c编写

    接下来开始编写platform驱动文件,首先是定义一个设备结构体。

struct leddev_dev{
	dev_t devid;			/* 设备号	*/
	struct cdev cdev;		/* cdev		*/
	struct class *class;	/* 类 		*/
	struct device *device;	/* 设备		*/
	int major;				/* 主设备号	*/		
};

    还需要定义file_operations结构体,存放设备操作函数。

/* 设备操作函数 */
static struct file_operations led_fops = {
	.owner = THIS_MODULE,
	.open = led_open,
	.write = led_write,
};

/*
 * @description		: 向设备写数据 
 * @param - filp 	: 设备文件,表示打开的文件描述符
 * @param - buf 	: 要写给设备写入的数据
 * @param - cnt 	: 要写入的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	int retvalue;
	unsigned char databuf[1];
	unsigned char ledstat;

	retvalue = copy_from_user(databuf, buf, cnt);
	if(retvalue < 0) {
		return -EFAULT;
	}

	ledstat = databuf[0];		/* 获取状态值 */
	if(ledstat == LEDON) {
		led0_switch(LEDON);		/* 打开LED灯 */
	}else if(ledstat == LEDOFF) {
		led0_switch(LEDOFF);	/* 关闭LED灯 */
	}
	return 0;
}

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int led_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &leddev; /* 设置私有数据  */
	return 0;
}

    紧接着,编写led_probe函数,当驱动与设备匹配后,就会调用函数,它的功能如下:

  • 获取设备资源;
  • 进行管脚映射,完成硬件方面的工作;
  • 注册字符设备驱动,对第一步中定义的设备结构体leddev中的成员进行初始化,如创建设备号、创建cdev结构体、创建类、创建设备。
/*
 * @description		: flatform驱动的probe函数,当驱动与设备匹配以后此函数就会执行
 * @param - dev 	: platform设备
 * @return 			: 0,成功;其他负值,失败
 */
static int led_probe(struct platform_device *dev)
{	
	int i = 0;
	int ressize[5];
	u32 val = 0;
	struct resource *ledsource[5];

	printk("led driver and device has matched!\r\n");
	/* 1、获取资源 */
	for (i = 0; i < 5; i++) {
		ledsource[i] = platform_get_resource(dev, IORESOURCE_MEM, i); /* 依次MEM类型资源 */
		if (!ledsource[i]) {
			dev_err(&dev->dev, "No MEM resource for always on\n");
			return -ENXIO;
		}
		ressize[i] = resource_size(ledsource[i]);	
	}	

	/* 2、初始化LED */
	/* 寄存器地址映射 */
 	IMX6U_CCM_CCGR1 = ioremap(ledsource[0]->start, ressize[0]);
	SW_MUX_GPIO1_IO03 = ioremap(ledsource[1]->start, ressize[1]);
  	SW_PAD_GPIO1_IO03 = ioremap(ledsource[2]->start, ressize[2]);
	GPIO1_DR = ioremap(ledsource[3]->start, ressize[3]);
	GPIO1_GDIR = ioremap(ledsource[4]->start, ressize[4]);
	
	val = readl(IMX6U_CCM_CCGR1);
	val &= ~(3 << 26);				/* 清除以前的设置 */
	val |= (3 << 26);				/* 设置新值 */
	writel(val, IMX6U_CCM_CCGR1);

	/* 设置GPIO1_IO03复用功能,将其复用为GPIO1_IO03 */
	writel(5, SW_MUX_GPIO1_IO03);
	writel(0x10B0, SW_PAD_GPIO1_IO03);

	/* 设置GPIO1_IO03为输出功能 */
	val = readl(GPIO1_GDIR);
	val &= ~(1 << 3);			/* 清除以前的设置 */
	val |= (1 << 3);			/* 设置为输出 */
	writel(val, GPIO1_GDIR);

	/* 默认关闭LED1 */
	val = readl(GPIO1_DR);
	val |= (1 << 3) ;	
	writel(val, GPIO1_DR);
	
	/* 注册字符设备驱动 */
	/*1、创建设备号 */
	if (leddev.major) {		/*  定义了设备号 */
		leddev.devid = MKDEV(leddev.major, 0);
		register_chrdev_region(leddev.devid, LEDDEV_CNT, LEDDEV_NAME);
	} else {						/* 没有定义设备号 */
		alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME);	/* 申请设备号 */
		leddev.major = MAJOR(leddev.devid);	/* 获取分配号的主设备号 */
	}
	
	/* 2、初始化cdev */
	leddev.cdev.owner = THIS_MODULE;
	cdev_init(&leddev.cdev, &led_fops);
	
	/* 3、添加一个cdev */
	cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);

	/* 4、创建类 */
	leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);
	if (IS_ERR(leddev.class)) {
		return PTR_ERR(leddev.class);
	}

	/* 5、创建设备 */
	leddev.device = device_create(leddev.class, NULL, leddev.devid, NULL, LEDDEV_NAME);
	if (IS_ERR(leddev.device)) {
		return PTR_ERR(leddev.device);
	}

	return 0;
}

    编写platform驱动的remove函数,它会取消地址映射,删除cedv结构体,注销设备号,完成设备的卸载、类的卸载。

/*
 * @description		: platform驱动的remove函数,移除platform驱动的时候此函数会执行
 * @param - dev 	: platform设备
 * @return 			: 0,成功;其他负值,失败
 */
static int led_remove(struct platform_device *dev)
{
	iounmap(IMX6U_CCM_CCGR1);
	iounmap(SW_MUX_GPIO1_IO03);
	iounmap(SW_PAD_GPIO1_IO03);
	iounmap(GPIO1_DR);
	iounmap(GPIO1_GDIR);

	cdev_del(&leddev.cdev);/*  删除cdev */
	unregister_chrdev_region(leddev.devid, LEDDEV_CNT); /* 注销设备号 */
	device_destroy(leddev.class, leddev.devid);
	class_destroy(leddev.class);
	return 0;
}

    剩下的就是编写platform的驱动结构体和init以及exit函数。

/* platform驱动结构体 */
static struct platform_driver led_driver = {
	.driver		= {
		.name	= "imx6ul-led",			/* 驱动名字,用于和设备匹配 */
	},
	.probe		= led_probe,
	.remove		= led_remove,
};
		
/*
 * @description	: 驱动模块加载函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init leddriver_init(void)
{
	return platform_driver_register(&led_driver);
}

/*
 * @description	: 驱动模块卸载函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit leddriver_exit(void)
{
	platform_driver_unregister(&led_driver);
}

3. 测试

    测试APP的编写过程与前面LED驱动的APP基本一致,就不过多赘述。直接进行测试。

depmod //第一次加载驱动的时候需要运行此命令
modprobe leddevice.ko //加载设备模块
modprobe leddriver.ko //加载驱动模块

    在/sys/bus/platform/devices/目录下查询有没有设备文件,下面的情况说明设备加载成功。
在这里插入图片描述
    紧接着在/sys/bus/platform/drivers/查询是否有驱动文件,有的话说明驱动模块加载成功。
在这里插入图片描述
    加载成功后platfomr总线就会进行匹配,匹配成功如下图所示。
在这里插入图片描述
    输入以下命令进行测试:

./ledApp /dev/platled 1 //打开 LED 灯
./ledApp /dev/platled 0 //关闭 LED 灯
rmmod leddevice.ko
rmmod leddriver.ko

4. 总结

leddevice.c:

  • 定义platform设备结构体,存放名字、id、设备资源等;
  • init函数完成设备的注册,exit函数完成设备的注销。

leddrivers.c:

  • 定义一个led设备结构体,存放设备号、类、cdev结构体等;
  • 定义file_operations结构体存放操作函数如open、write;
  • 编写led_probe函数,匹配成功后,进行管脚的映射,设备资源的加载,并进行字符设备驱动的注册,就是对第一步中led设备结构体的初始化;
  • 编写led_remove函数,取消地址映射,进行字符设备的卸载,类的卸载;
  • 编写驱动结构体,并编写驱动模块卸载函数、驱动模块加载函数。

你可能感兴趣的:(Linux驱动,linux,驱动开发)