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