I.MX6U嵌入式Linux Platform设备驱动开发(1)初识驱动

1、无设备树的情况下:

需要写两部分:platform_driver, platform_device.
点灯为例:

cd /liunx/IMX6ULL/Linux_Driver/
mkdir 17_platform
cp 16_asyncnoti/ * 17_platform/ -rf
cp 16_asyncnoti/.vscode 17_platform/ -rf
cd 17_platform/
ls
rm tasklet.c work.c
rm asyncnotiqAPP asyncnotiAPP.c
mv imx6uirq.c leddevice.c

打开leddevice.c文件,保留头文件,剩余的全删了。

1.1、platform设备

/* 设备加载 */
static int __init leddevice_init(void)
{
	return 0;
}

/* 设备卸载 */
static void __exit leddevice_exit(void)
{
	
}

module_init(leddevice_init);
module_exit(leddevice_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("yang");

修改Makefile文件,将其修改为:leddevice.o

编译一下代码,看有无错误。

在leddevice_init()中调用platform_device_register函数将设备信息注册到 Linux 内核中,如果不再使用 platform 的话可以通过 platform_device_unregister 函数注销掉相应的 platform设备,原型如下:

int platform_device_register(struct platform_device *pdev);
void platform_device_unregister(struct platform_device *pdev);
/* 设备加载 */
static int __init leddevice_init(void)
{
	return platform_device_register(struct platform_device *pdev);
}

注册的东西就是一个结构体,这个结构体就是:struct platform_device *device

static struct platform_device leddevice = {
	
};

因此:

return platform_device_register (&leddevice);

注册完了之后,需要卸载掉:

/* 设备卸载 */
static void __exit leddevice_exit(void)
{
	platform_device_unregister(&leddevice);
}

实现platform_device,用来实现一个设备的,在linux内核源码中找到这个结构体的定义:

struct platform_device {
	const char	*name;
	int		id;
	bool		id_auto;
	struct device	dev;
	u32		num_resources;
	struct resource	*resource;

	const struct platform_device_id	*id_entry;
	char *driver_override; /* Driver name to force a match */

	/* MFD cell pointer */
	struct mfd_cell *mfd_cell;

	/* arch specific additions */
	struct pdev_archdata	archdata;
};

因此:

void leddevice_release(struct device *dev)
{
	printk("leddevice_release!\r\n");
}
static struct platform_device leddevice = {
	.name = "imx6ull-led",
	.id = -1,//表示设备无ID
	.dev = {
		.release = leddevice_release,
	},
	.num_resources = ,//num_resources 表示资源数量,一般为resource 资源的大小
	.resource = ,//resource 表示资源,也就是设备信息,比如外设寄存器等
};

Linux 内核使用 resource结构体表示资源,resource 结构体内容如下:

struct resource {
	resource_size_t start;
	resource_size_t end;
	const char *name;
	unsigned long flags;
	struct resource *parent, *sibling, *child;
};

这个resource可以定义一个结构体数组,用来描述内存,有哪些内存呢?点灯的实验需要用到5个寄存器,这5个寄存器对应的就是内存段,但是内存段又不连续,因此,我们需要5个内存地址。所以需要5个数组。

先把内存地址拷贝过来:

/* 寄存器物理地址 */
#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)

#define REGISTER_LENGTH				4
static struct resource led_resources[] = {
	[0] = {
		.start = CCM_CCGR1_BASE,
		.end = CCM_CCGR1_BASE + REGISTER_LENGTH - 1,
		.flag = IORESOURCE_MEM,
	},
	[1] = {
		.start = SW_MUX_GPIO1_IO03_BASE,
		.end = SW_MUX_GPIO1_IO03_BASE+ REGISTER_LENGTH - 1,
		.flag = IORESOURCE_MEM,
	},
	[2] = {
		.start = SW_PAD_GPIO1_IO03_BASE,
		.end = CCM_CCGR1_BASE + REGISTER_LENGTH - 1,
		.flag = IORESOURCE_MEM,
	},
	[3] = {
		.start = GPIO1_DR_BASE,
		.end = GPIO1_DR_BASE+ REGISTER_LENGTH - 1,
		.flag = IORESOURCE_MEM,
	},
	[4] = {
		.start = GPIO1_GDIR_BASE,
		.end = GPIO1_GDIR_BASE+ REGISTER_LENGTH - 1,
		.flag = IORESOURCE_MEM,
	},
};

因此:

.num_resources = ARRAY_SIZE(led_resources),//num_resources 表示资源数量,一般为resource 资源的大小
.resource = led_resources,//resource 表示资源,也就是设备信息,比如外设寄存器等

编译报错,没有添加头文件:

#include <linux/platform_device.h>

编译生成一个.ko文件。

make
sudo cp imx6uirq.ko asyncnotiAPP /home/yang/nfs/rootfs/lib/modules/4.1.15/ -f

打开开发板,进入lib/modules/4.1.15/

depmod
modprobe leddevice.ko
cd /sys/bus/
ls
cd platform/
ls
cd devices/
ls  //看一下有没有刚才加载的imx6ull-led

1.2、platform驱动

将leddevice.c拷贝一下:cp leddevice.c leddriver.c

留下头文件,留下下面内容:

/* 驱动加载 */
static int __init leddriver_init(void)
{
	/* 注册platform驱动函数 */
	return 0;
}

/* 驱动卸载 */
static void __exit leddriver_exit(void)
{
	
}

module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("yang");

修改Makefile,obj-m := leddevice.o leddriver.o

/* platform 驱动结构体 */
static platform_driver led_driver = {
	
};

补充驱动加载卸载函数:

/* 驱动加载 */
static int __init leddriver_init(void)
{
	/* 注册platform驱动函数 */
	return platform_driver_register(&led_driver);
}

/* 驱动卸载 */
static void __exit leddriver_exit(void)
{
	platform_driver_unregister(&led_driver);
}

实现这个结构体的成员变量,重点实现:probe remove driver

/* platform 驱动结构体 */
static struct platform_driver led_driver = {
	.driver = {
		.name = "imx6ull-led", //驱动名字,用于和设备匹配
	},
	.probe = led_probe,
	.remove = led_remove,
};

名字与设备的名字一样,当匹配成功后,就会执行probe函数,因此要实现这个函数。当卸载的时候肯定要释放一些东西,像内存,因此也要实现remove函数。

static int led_probe(struct platform_device *dev)
{
	printk("led driver device probe\r\n");
	return 0;
}
static int led_remove(struct platform_device * dev)
{
	printk("led driver device remove\r\n");
	return 0;
}

编译一下:

make
sudo cp leddriver.ko leddevice.ko /home/yang/nfs/rootfs/lib/modules/4.1.15/ -f

开发板上电:
先卸载之前的驱动;再加载
先添加设备,没有任何执行,因为驱动还没有添加,没有匹配;再添加驱动,此时设备已经存在了,驱动添加之后要运行,运行的时候去寻找匹配的设备

/lib/modules/4.1.15 # rmmod leddevice.ko
/lib/modules/4.1.15 # lsmod
/lib/modules/4.1.15 # depmod
/lib/modules/4.1.15 # modprobe leddevice.ko
/lib/modules/4.1.15 # rmmod leddevice.ko

卸载设备和驱动。
后面做开发的时候,基本上驱动不需要修改,只修改设备。

当probe函数执行的时候,需要初始化LED、字符设备驱动。编写驱动需要寄存器地址信息,地址信息使用设备信息,定义在platform_device里面,因此需要在驱动里面获取设备中的信息,或者叫资源。使用platform_get_resource()函数来获取设备中的地址。

platform_get_resource()函数中的一个参数是索引,这个索引就是此类型中的第几个,因此需要定义一个循环。返回值是一个指针类型,因此要定义一个指针类型的数组,里面的指针就是一个指针。

接下来就是内存映射,可以把前面讲的字符设备的一系列的内容复制下来。只需要把内存映射部分改成我们现在需要的即可。

static int led_probe(struct platform_device *dev)
{
	int i = 0;
	struct resource *ledsource[5];
	/* 初始化LED,字符设备驱动 */
	/* 1、从设备中获取资源 */
	for(i=0; i<5; i++){
		ledsource[i] = platform_get_resource(dev, IORESOURCE_MEM, i);
		if(ledsource[i]==NULL)
			return -EINVAL;
	}
}

复制过来的部分:

/* 映射后的寄存器虚拟地址指针 */
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;

#define LEDOFF 					0			/* 关灯 */
#define LEDON 					1			/* 开灯 */

/* newchrled设备结构体 */
struct newchrled_dev{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;		/* 类 		*/
	struct device *device;	/* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
};

struct newchrled_dev newchrled;	/* led设备 */
/* 初始化LED灯,地址映射 */
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);

/* 初始化 */
......
fail_decid;
	return ret;

下面只需要修改内存映射部分,找准寄存器的起始地址,传递的多少字节,需要一个函数resource_size()

/* 初始化LED灯,地址映射 */
/* 1、从设备中获取资源 */
IMX6U_CCM_CCGR1 = ioremap(ledsource[0]->start, resource_size(ledsource[0]));
SW_MUX_GPIO1_IO03 = ioremap(ledsource[1]->start, resource_size(ledsource[1]));
SW_PAD_GPIO1_IO03 = ioremap(ledsource[2]->start, resource_size(ledsource[2]));
GPIO1_DR = ioremap(ledsource[3]->start, resource_size(ledsource[3]));
GPIO1_GDIR = ioremap(ledsource[4]->start, resource_size(ledsource[4]));

编译一下:需要一个宏定义:

#define PLATFORM_CNT			1		  	/* 设备号个数 */
#define PLATFORM_NAME			"platled"	/* 名字 */

把剩余的open函数、release函数也给复制过来,编译一下。

在newchar实验中卸载(newchrled_exit())的时候,把取消地址映射、删除字符设备、注销设备号、摧毁设备、摧毁类等放进去。而现在用的平台设备驱动里面leddriver_exit函数,只调用了platform_driver_unregister(),所以将newcharled_exit()中的内容放到led_release()中。

编译一下,拷贝过来。

make
sudo cp leddriver.ko leddevice.ko /home/yang/nfs/rootfs/lib/modules/4.1.15/ -f

开发板上电:

/lib/modules/4.1.15 # lsmod
/lib/modules/4.1.15 # rmmod leddevice.ko
/lib/modules/4.1.15 # rmmod leddriver.ko
/lib/modules/4.1.15 # modprobe leddevice.ko
/lib/modules/4.1.15 # modprobe leddriver.ko
/lib/modules/4.1.15 # ls /sys/bus/
/lib/modules/4.1.15 # cd /sys/bus/
/sys/bus/ # ls
/sys/bus/ # cd platform/
/sys/bus/platform/ # ls
/sys/bus/platform/ # cd /drivers

1.3、编写测试APP

在vscode中,打开终端,输入:

touch platledAPP.c

将newcharledAPP.c中的内容复制过来,

/* argc :应用程序参数个数
 * argv[] : 具体的参数内容,字符串形式
 * ./platledAPP  <filename>  <0:1>  0表示关灯, 1表示开灯
 * ./platledAPP  /dev/platled  0  关灯
 * ./platledAPP  /dev/platled  1  开灯
 */ 

编译:

arm-linux-gnueabihf-gcc platledAPP.c -o platledAPP
sudo cp platledAPP /home/yang/nfs/rootfs/lib/modules/4.1.15/ -f
/lib/modules/4.1.15 # lsmod
/lib/modules/4.1.15 # ls /dev/p*
/lib/modules/4.1.15 # ls
/lib/modules/4.1.15 # ./platledAPP /dev/platled 1
/lib/modules/4.1.15 # ./platledAPP /dev/platled 0

2、有设备树的情况下:

有设备树的时候,设备是由设备树描述的,这个时候就不需要向总线注册设备了,直接修改设备树。这个时候只需要修改设备树,编写驱动。
这个实验在gpioled的基础上面完成,因此我们不需要修改设备树,只需要编写驱动。

cd /liunx/IMX6ULL/Linux_Driver/
mkdir 18_dtsplatform
cp 17_platform/ * 18_dtsplatform/ -rf
cp 17_platform/.vscode 18_dtsplatform/ -rf
cd 18_dtsplatform/
ls
rm leddevice.c asyncnoti.code-workspace

打开leddriver.c文件:删除内容,只留下头文件和下面内容:

static int led_probe(struct platform_device *dev)
{
	printk("led driver device probe\r\n");
	return 0;
}

static int led_remove(struct platform_device * dev)
{
	printk("led driver device remove\r\n");
	return 0;
}

/* platform 驱动结构体 */
static struct platform_driver led_driver = {
	.driver = {
		.name = "imx6ull-led", //无设备树时,驱动名字,用于和设备匹配
		.of_match_table = 
	},
	.probe = led_probe,
	.remove = led_remove,
};

/* 驱动加载 */
static int __init leddriver_init(void)
{
	/* 注册platform驱动函数 */
	return platform_driver_register(&led_driver);
}

/* 驱动卸载 */
static void __exit leddriver_exit(void)
{
	platform_driver_unregister(&led_driver);
}

module_init(leddevice_init);
module_exit(leddevice_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("yang");

需要完成led_probe、led_remove函数、driver,在driver结构体中,在无设备树的时候,靠name进行连接的,在有设备树的时候,需要完成of_match_table。of_match_table的类型是个结构体,因为有很多个匹配的值,所以要定义一个数组形式的。of_devide_id原型:

struct of_device_id {
	char name[32];
	char type[32];
	char compatible[128];
	const void *data;
};

因此:

struct of_device_id led_of_match[] = {
	{.compatible = "alientek,gpioled"},//与设备树中的compatible的属性值匹配
	{.compatible = "***,gpioled"},//也可以有2条,只要跟其中一个匹配即可
	{/* Sentinel */}
};

因此:

.of_match_table = led_of_match,//设备树匹配表

编译一下:

make
sudo cp leddriver.ko leddevice.ko /home/yang/nfs/rootfs/lib/modules/4.1.15/ -f

启动开发板:先检查一下设备中的节点是否存在。

/ # ls
/ # cd proc/
/proc # ls
/proc # cd device-tree/
/sys/firmware/devicetree/base # ls
/sys/firmware/devicetree/base # cd gpioled/
/sys/firmware/devicetree/base/gpioled # ls
/sys/firmware/devicetree/base/gpioled # cat compatible
alientek,gpioled
/sys/firmware/devicetree/base/gpioled # cd/
/ # cd lib/modules/4.1.15/
/lib/modules/4.1.15 # ls
/lib/modules/4.1.15 # depmod
/lib/modules/4.1.15 # modprobe leddriver.ko
/lib/modules/4.1.15 # remod leddriver.ko

将gpioled.c文件中的内容复制过来,
#define GPIOLED_NAME “dtsplatled”

编译一下,然后测试APP也不需要修改,直接拷贝过去:

make
sudo cp leddriver.ko platledAPP.ko /home/yang/nfs/rootfs/lib/modules/4.1.15/ -f

开发板:

/lib/modules/4.1.15 # modprobe leddriver.ko
/lib/modules/4.1.15 # ./platledAPP /dev/platled 1
/lib/modules/4.1.15 # ./platledAPP /dev/platled 0
/lib/modules/4.1.15 # rmmod leddriver.ko

在这个驱动中,我们需要使用of_find_node_by_path()来获取设备节点,当这个驱动和设备匹配成功以后,有个platform_device就代表平台设备了,那么这样的话,这个平台设备肯定包含节点信息了,因此,修改为:

	/* 获取设备节点 */
#if 0
	gpioled.nd = of_find_node_by_path("/gpioled");
	if(
		...
	}
#endif
	gpioled.nd = dev->dev.of_node;

编译:

make
sudo cp leddriver.ko /home/yang/nfs/rootfs/lib/modules/4.1.15/ -f
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值