linux系列目录:
linux基础篇(一)——GCC和Makefile编译过程
linux基础篇(二)——静态和动态链接
ARM裸机篇(一)——i.MX6ULL介绍
ARM裸机篇(二)——i.MX6ULL启动过程
ARM裸机篇(三)——i.MX6ULL第一个裸机程序
ARM裸机篇(四)——重定位和地址无关码
ARM裸机篇(五)——异常和中断
linux系统移植篇(一)—— linux系统组成
linux系统移植篇(二)—— Uboot使用介绍
linux系统移植篇(三)—— Linux 内核使用介绍
linux系统移植篇(四)—— 根文件系统使用介绍
linux驱动开发篇(一)—— Linux 内核模块介绍
linux驱动开发篇(二)—— 字符设备驱动框架
linux驱动开发篇(三)—— 总线设备驱动模型
linux驱动开发篇(四)—— platform平台设备驱动
linux驱动开发篇(五)—— linux驱动面向对象的编程思想
linux驱动开发篇(六)—— 设备树的引入
在设备驱动模型中,引入总线的概念可以对驱动代码和设备信息进行分离。但是驱动中总线的概念是软件层面的一种抽象,与我们 SOC 中物理总线的概念并不严格相等:
一般对于 I2C、 SPI、 USB 这些常见类型的物理总线来说, Linux 内核会自动创建与之相应的驱动总线,因此 I2C 设备、 SPI 设备、 USB 设备自然是注册挂载在相应的总线上。但是,实际项目开发中还有很多结构简单的设备,对它们进行控制并不需要特殊的时序。它们也就没有相应的物理总线,比如 led、 rtc 时钟、蜂鸣器、按键等等, Linux 内核将不会为它们创建相应的驱动总线。为了使这部分设备的驱动开发也能够遵循设备驱动模型, Linux 内核引入了一种虚拟的总线——平台总线(platform bus)。
平台设备驱动的核心依然是 Linux 设备驱动模型,平台设备使用 platform_device 结构体来进行表示,其继承了设备驱动模型中的device 结构体。而平台驱动使用 platform_driver 结构体来进行表示,其则是继承了设备驱动模型中的 device_driver结构体。
内核中使用 bus_type 来抽象描述系统中的总线,平台总线结构体原型如下所示:
platform_bus_type 结 构 体 (内 核 源码/driver/base/platform.c)
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.dma_configure = platform_dma_configure,
.pm = &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);
内核用 platform_bus_type 来描述平台总线,该总线在 linux 内核启动的时候自动进行注册。
这里重点是 platform 总线的 match 函数指针,该函数指针指向的函数将负责实现平台总线和平台设备的匹配过程。对于每个驱动总线,它都必须实例化该函数指针。 platform_match 的函数原型如下:
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/* When driver_override is set, only bind to the matching driver */
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}
platform 总线提供了四种匹配方式,并且这四种方式存在着优先级:设备树机
制 >ACPI 匹配模式 >id_table 方式 > 字符串比较。
内核使用 platform_device 结构体来描述平台设备,platform_device 结 构 体 (内 核 源码/include/linux/platform_device.h)结构体原型如下:
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;
};
平台设备的工作是为驱动程序提供设备信息, 设备信息包括硬件信息和软件信息两部分。
对于硬件信息,使用结构体 struct resource 来保存设备所提供的资源,比如设备使用的中断编号,寄存器物理地址等,结构体原型如下:
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
};
资源宏定义 | 描述 |
---|---|
IORESOURCE_IO | 用于 IO 地址空间,对应于 IO 端口映射方式 |
IORESOURCE_MEM | 用于外设的可直接寻址的地址空间 |
IORESOURCE_IRQ | 用于指定该设备使用某个中断 |
IORESOURCE_DMA | 用于指定使用的 DMA 通道 |
注册/注销平台设备API原型:
int platform_device_register(struct platform_device *pdev);
void platform_device_unregister(struct platform_device *pdev);
内核中使用 platform_driver 结构体来描述平台驱动,platform_driver 结 构 体 (内 核 源
码/include/platform_device.h)结构体原型如下所示:
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
};
platform_device_id 结构体原型如下所示:
struct platform_device_id {
char name[PLATFORM_NAME_SIZE];
kernel_ulong_t driver_data;
};
在 platform_device_id 这个结构体中,有两个成员,第一个是数组用于指定驱动的名称,总线进行匹配时,会依据该结构体的 name 成员与 platform_device 中的变量 name 进行比较匹配,另一个成员变量 driver_data,则是用于来保存设备的配置。
注册/注销平台驱动API
int platform_driver_register(struct platform_driver *drv);
void platform_driver_unregister(struct platform_driver *drv);
在学习平台设备的时候,我们知道平台设备使用结构体 resource 来抽象表示硬件信息,而软件信息则可以利用设备结构体 device 中的成员 platform_data 来保存。先看一下如何获取平台设备中结构体 resource 提供的资源。
platform_get_resource() 函数通常会在驱动的 probe 函数中执行,用于获取平台设备提供的资源结构体,最终会返回一个 struct resource 类型的指针,该函数原型如下:
struct resource *platform_get_resource(struct platform_device *dev,unsigned int type, unsigned int num);
对于存放在 device 结构体中成员 platform_data 的软件信息,我们可以使用 dev_get_platdata 函数来获取,函数原型如下所示:
static inline void *dev_get_platdata(const struct device *dev)
{
return dev->platform_data;
}
总结一下平台驱动需要实现 probe 函数,当平台总线成功匹配驱动和设备时,则会调用驱动的 probe 函数,在该函数中使用上述的函数接口来获取资源,以初始化设备,最后
填充结构体 platform_driver,调用 platform_driver_register 进行注册。
把平台设备驱动应用到 LED 字符设备驱动的代码中,实现硬件与软件代码相分离。
在平台设备总线上,注册/挂载平台设备和平台驱动时,会自动进行配对。配对成功后,回调执行平台驱动的 probe 函数,从而完成字符设备驱动的创建。
#include
#include
#include
#define CCM_CCGR1 0x20C406C //时钟控制寄存器
#define IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04 0x20E006C //GPIO1_04复用功能选择寄存器
#define IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04 0x20E02F8 //PAD属性设置寄存器
#define GPIO1_GDIR 0x0209C004 //GPIO方向设置寄存器(输入或输出)
#define GPIO1_DR 0x0209C000 //GPIO输出状态寄存器
#define CCM_CCGR3 0x020C4074
#define GPIO4_GDIR 0x020A8004
#define GPIO4_DR 0x020A8000
#define IOMUXC_SW_MUX_CTL_PAD_GPIO4_IO020 0x020E01E0
#define IOMUXC_SW_PAD_CTL_PAD_GPIO4_IO020 0x020E046C
#define IOMUXC_SW_MUX_CTL_PAD_GPIO4_IO019 0x020E01DC
#define IOMUXC_SW_PAD_CTL_PAD_GPIO4_IO019 0x020E0468
static struct resource rled_resource[] = {
[0] = DEFINE_RES_MEM(GPIO1_DR, 4),
[1] = DEFINE_RES_MEM(GPIO1_GDIR, 4),
[2] = DEFINE_RES_MEM(IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04, 4),
[3] = DEFINE_RES_MEM(CCM_CCGR1, 4),
[4] = DEFINE_RES_MEM(IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04, 4),
};
static struct resource gled_resource[] = {
[0] = DEFINE_RES_MEM(GPIO4_DR, 4),
[1] = DEFINE_RES_MEM(GPIO4_GDIR, 4),
[2] = DEFINE_RES_MEM(IOMUXC_SW_MUX_CTL_PAD_GPIO4_IO020, 4),
[3] = DEFINE_RES_MEM(CCM_CCGR3, 4),
[4] = DEFINE_RES_MEM(IOMUXC_SW_PAD_CTL_PAD_GPIO4_IO020, 4),
};
static struct resource bled_resource[] = {
[0] = DEFINE_RES_MEM(GPIO4_DR, 4),
[1] = DEFINE_RES_MEM(GPIO4_GDIR, 4),
[2] = DEFINE_RES_MEM(IOMUXC_SW_MUX_CTL_PAD_GPIO4_IO019, 4),
[3] = DEFINE_RES_MEM(CCM_CCGR3, 4),
[4] = DEFINE_RES_MEM(IOMUXC_SW_PAD_CTL_PAD_GPIO4_IO019, 4),
};
/* not used */
static void led_release(struct device *dev)
{
}
/* led hardware information */
unsigned int rled_hwinfo[2] = { 4, 26 };
unsigned int gled_hwinfo[2] = { 20, 12 };
unsigned int bled_hwinfo[2] = { 19, 12 };
/* red led device */
static struct platform_device rled_pdev = {
.name = "led_pdev",
.id = 0,
.num_resources = ARRAY_SIZE(rled_resource),
.resource = rled_resource,
.dev = {
.release = led_release,
.platform_data = rled_hwinfo,
},
};
/* green led device */
static struct platform_device gled_pdev = {
.name = "led_pdev",
.id = 1,
.num_resources = ARRAY_SIZE(gled_resource),
.resource = gled_resource,
.dev = {
.release = led_release,
.platform_data = gled_hwinfo,
},
};
/* blue led device */
static struct platform_device bled_pdev = {
.name = "led_pdev",
.id = 2,
.num_resources = ARRAY_SIZE(bled_resource),
.resource = bled_resource,
.dev = {
.release = led_release,
.platform_data = bled_hwinfo,
},
};
static __init int led_pdev_init(void)
{
printk("pdev init\n");
platform_device_register(&rled_pdev);
platform_device_register(&gled_pdev);
platform_device_register(&bled_pdev);
return 0;
}
module_init(led_pdev_init);
static __exit void led_pdev_exit(void)
{
printk("pdev exit\n");
platform_device_unregister(&rled_pdev);
platform_device_unregister(&gled_pdev);
platform_device_unregister(&bled_pdev);
}
module_exit(led_pdev_exit);
MODULE_AUTHOR("embedfire");
MODULE_LICENSE("GPL");
#include
#include
#include
#include
#include
#include
#include
#define DEV_MAJOR 243
#define DEV_NAME "led"
static struct class *my_led_class;
//结构体led_data来管理我们LED灯的硬件信息
struct led_data {
unsigned int led_pin;
unsigned int clk_regshift;
unsigned int __iomem *va_dr;
unsigned int __iomem *va_gdir;
unsigned int __iomem *va_iomuxc_mux;
unsigned int __iomem *va_ccm_ccgrx;
unsigned int __iomem *va_iomux_pad;
struct cdev led_cdev;
};
static int led_cdev_open(struct inode *inode, struct file *filp)
{
printk("%s\n", __func__);
struct led_data *cur_led = container_of(inode->i_cdev, struct led_data, led_cdev);
unsigned int val = 0;
val = readl(cur_led->va_ccm_ccgrx);
val &= ~(3 << cur_led->clk_regshift);
val |= (3 << cur_led->clk_regshift);
writel(val, cur_led->va_ccm_ccgrx);
writel(5, cur_led->va_iomuxc_mux);
writel(0x1F838, cur_led->va_iomux_pad);
val = readl(cur_led->va_gdir);
val &= ~(1 << cur_led->led_pin);
val |= (1 << cur_led->led_pin);
writel(val, cur_led->va_gdir);
val = readl(cur_led->va_dr);
val |= (0x01 << cur_led->led_pin);
writel(val, cur_led->va_dr);
filp->private_data = cur_led;
return 0;
}
static int led_cdev_release(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t led_cdev_write(struct file *filp, const char __user * buf,
size_t count, loff_t * ppos)
{
unsigned long val = 0;
unsigned long ret = 0;
int tmp = count;
struct led_data *cur_led = (struct led_data *)filp->private_data;
kstrtoul_from_user(buf, tmp, 10, &ret);
val = readl(cur_led->va_dr);
if (ret == 0)
val &= ~(0x01 << cur_led->led_pin);
else
val |= (0x01 << cur_led->led_pin);
writel(val, cur_led->va_dr);
*ppos += tmp;
return tmp;
}
static struct file_operations led_cdev_fops = {
.open = led_cdev_open,
.release = led_cdev_release,
.write = led_cdev_write,
};
//probe函数中,驱动需要去提取设备的资源,完成字符设备的注册等工作
static int led_pdrv_probe(struct platform_device *pdev)
{
struct led_data *cur_led;
unsigned int *led_hwinfo;
struct resource *mem_dr;
struct resource *mem_gdir;
struct resource *mem_iomuxc_mux;
struct resource *mem_ccm_ccgrx;
struct resource *mem_iomux_pad;
dev_t cur_dev;
int ret = 0;
printk("led platform driver probe\n");
//第一步:提取平台设备提供的资源
//devm_kzalloc函数申请cur_led和led_hwinfo结构体内存大小
cur_led = devm_kzalloc(&pdev->dev, sizeof(struct led_data), GFP_KERNEL);
if(!cur_led)
return -ENOMEM;
led_hwinfo = devm_kzalloc(&pdev->dev, sizeof(unsigned int)*2, GFP_KERNEL);
if(!led_hwinfo)
return -ENOMEM;
/* get the pin for led and the reg's shift */
//dev_get_platdata函数获取私有数据,得到LED灯的寄存器偏移量,并赋值给cur_led->led_pin和cur_led->clk_regshift
led_hwinfo = dev_get_platdata(&pdev->dev);
cur_led->led_pin = led_hwinfo[0];
cur_led->clk_regshift = led_hwinfo[1];
/* get platform resource */
//利用函数platform_get_resource可以获取到各个寄存器的地址
mem_dr = platform_get_resource(pdev, IORESOURCE_MEM, 0);
mem_gdir = platform_get_resource(pdev, IORESOURCE_MEM, 1);
mem_iomuxc_mux = platform_get_resource(pdev, IORESOURCE_MEM, 2);
mem_ccm_ccgrx = platform_get_resource(pdev, IORESOURCE_MEM, 3);
mem_iomux_pad = platform_get_resource(pdev, IORESOURCE_MEM, 4);
//使用devm_ioremap将获取到的寄存器地址转化为虚拟地址
cur_led->va_dr =
devm_ioremap(&pdev->dev, mem_dr->start, resource_size(mem_dr));
cur_led->va_gdir =
devm_ioremap(&pdev->dev, mem_gdir->start, resource_size(mem_gdir));
cur_led->va_iomuxc_mux =
devm_ioremap(&pdev->dev, mem_iomuxc_mux->start,
resource_size(mem_iomuxc_mux));
cur_led->va_ccm_ccgrx =
devm_ioremap(&pdev->dev, mem_ccm_ccgrx->start,
resource_size(mem_ccm_ccgrx));
cur_led->va_iomux_pad =
devm_ioremap(&pdev->dev, mem_iomux_pad->start,
resource_size(mem_iomux_pad));
//第二步:注册字符设备
cur_dev = MKDEV(DEV_MAJOR, pdev->id);
register_chrdev_region(cur_dev, 1, "led_cdev");
cdev_init(&cur_led->led_cdev, &led_cdev_fops);
ret = cdev_add(&cur_led->led_cdev, cur_dev, 1);
if(ret < 0)
{
printk("fail to add cdev\n");
goto add_err;
}
device_create(my_led_class, NULL, cur_dev, NULL, DEV_NAME "%d", pdev->id);
/* save as drvdata */
//platform_set_drvdata函数,将LED数据信息存入在平台驱动结构体中pdev->dev->driver_data中
platform_set_drvdata(pdev, cur_led);
return 0;
add_err:
unregister_chrdev_region(cur_dev, 1);
return ret;
}
static int led_pdrv_remove(struct platform_device *pdev)
{
dev_t cur_dev;
//platform_get_drvdata,获取当前LED灯对应的结构体
struct led_data *cur_data = platform_get_drvdata(pdev);
printk("led platform driver remove\n");
cur_dev = MKDEV(DEV_MAJOR, pdev->id);
//cdev_del删除对应的字符设备
cdev_del(&cur_data->led_cdev);
//删除/dev目录下的设备
device_destroy(my_led_class, cur_dev);
//unregister_chrdev_region, 注销掉当前的字符设备编号
unregister_chrdev_region(cur_dev, 1);
return 0;
}
static struct platform_device_id led_pdev_ids[] = {
{.name = "led_pdev"},
{}
};
MODULE_DEVICE_TABLE(platform, led_pdev_ids);
//led_pdrv中定义了两种匹配模式
//平台总线匹配过程中 ,只会根据id_table中的name值进行匹配,若和平台设备的name值相等,则表示匹配成功; 反之,则匹配不成功,表明当前内核没有该驱动能够支持的设备。
static struct platform_driver led_pdrv = {
.probe = led_pdrv_probe,
.remove = led_pdrv_remove,
.driver.name = "led_pdev",
.id_table = led_pdev_ids,
};
static __init int led_pdrv_init(void)
{
printk("led platform driver init\n");
//class_create,来创建一个led类
my_led_class = class_create(THIS_MODULE, "my_leds");
//调用函数platform_driver_register,注册我们的平台驱动结构体,这样当加载该内核模块时, 就会有新的平台驱动加入到内核中。 第20-27行,注销
platform_driver_register(&led_pdrv);
return 0;
}
module_init(led_pdrv_init);
static __exit void led_pdrv_exit(void)
{
printk("led platform driver exit\n");
platform_driver_unregister(&led_pdrv);
class_destroy(my_led_class);
}
module_exit(led_pdrv_exit);
MODULE_AUTHOR("Embedfire");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("the example for platform driver");
makefile文件
KERNEL_DIR=../ebf_linux_kernel
ARCH=arm
CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH CROSS_COMPILE
obj-m := led_pdev.o led_pdrv.o
all:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules
modules clean:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean
编译成功后,实验目录下会生成两个名为“led_pdev.ko”、” led_pdrv.ko”的驱动模块文件。