写驱动程序的时候,需要把驱动分为平台device和平台driver两部分。在平台device中会放入硬件所使用的资源,使用C代码来指定platform_device,当需要修改硬件资源时,比如说想去修改led的引脚时,需要重新修改C文件,重新编译内核。再后来我们使用了设备树,可以在设备树中指定硬件资源。设备树是dts文件,它会转换成dtb文件,最终给内核使用。内核会来解析dtb文件得到一系列的device_node,最后一步,将device_node转换成platform_device.
1.根节点下有compatible属性的节点会被转化为platform_device.
2.根节点下含有 compatile 属性的子节点,如果一个节点的compatile属性,它的值是这4者之一 :"simple-bus","simple-mfd","isa","arm,amba-bus",那么它的子结点(需含 compatile 属性)也可以转换为 platform_device。
3. 总线 I2C、SPI 节点下的子节点:不转换为 platform_device某个总线下到子节点,应该交给对应的总线驱动程序来处理, 它们不应该被转换为 platform_device。
/ {
mytest {
compatile = "mytest", "simple-bus";
mytest@0 {
compatile = "mytest_0";
};
};
i2c {
compatile = "samsung,i2c";
at24c02 {
compatile = "at24c02";
};
};
spi {
compatile = "samsung,spi";
flash@0 {
compatible = "winbond,w25q32dw";
spi-max-frequency = <25000000>;
reg = <0>;
};
};
};
对于根目录下的第一级子节点,比如说I2c,它应该转换成platform_device,它会对应有一个platform_driver,当匹配时,driver中的probe函数就会被调用,对于I2c下面的这些子节点,比如说AT24C02应该交给probe函数来处理。如果仍然把这些子节点转成platfrom_device的话,就不太合适了。
2. device_node如何转换成platform_device,内核处理设备树的函数调用过程,这里不去分析;我们只需要得到如下结论:
首先来看一下,platform_device结构体的定义:
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u32 num_resources;
struct resource *resource; //指向一个动态生成的数组,数组的大小由num_resources决定。意味着平台设备中可以有0项,或1项,或多项资源。
//这些资源来自设备树中的reg属性,如果设备树中设有reg属性,那么在对应的platform_device中就会有一项资源,
//用来表示它所占用的内存空间。还可以指定一些中断属性,那么在platform_device就会表示它占据哪个中断号。
//资源的类别:I/O资源,内存资源,中断资源,这3中资源都可以在设备树中指定。这些资源会从device_node转换得到。
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;
};
platform_device中含有resource数组, 它来自device_node的reg, interrupts属性;
在设备树中还有一些属性,它们并不对应什么资源,那么这些属性保存在哪里?
platform_device结构体中有struct device结构体,在它里面有一项of_node,它指向device_node结构体,因此以后想得到某个属性时,可以从platform_device中先找到dev,再找到of_node,从of_node中读取那些属性值。
struct device {
struct device_node *of_node; /* associated device tree node */
};
platform_device.dev.of_node指向device_node, 可以通过它获得其他属性。
在代码中获得中断:
1 对于 platform_device
编写驱动的时候需要用到中断号,我们用到中断号,中断信息已经写到了设备树里面,因此可以通过 irq_of_parse_and_map 函数从 interupts 属性中提取到对应的设备号,函数原型如下:
unsigned int irq_of_parse_and_map(struct device_node *dev,
int index)
int gpio_to_irq(unsigned int gpio)
对于.c文件创建的普通的platform_device, 可以使用platform_get_resource获取相关资源,切记不适用与设备树的,设备树的节点会转换为platform_device,但是不会构造资源resource , 一 个 节 点 能 被 转 换 为 platform_device , 如 果 它 的 设 备 树 里 指 定 了 中 断 属 性 , 那 么 可 以 从 platform_device 中获得“中断资源”,函数如下,可以使用下列函数获得 IORESOURCE_IRQ 资源,即中断号:
/**
* platform_get_resource - get a resource for a device
* @dev: platform device
* @type: resource type // 取哪类资源?IORESOURCE_MEM、IORESOURCE_REG
* // IORESOURCE_IRQ等
* @num: resource index // 这类资源中的哪一个?
*/
struct resource *platform_get_resource(struct platform_device *dev,
unsigned int type, unsigned int num);
static int gpio_key_probe(struct platform_device *pdev)
{
struct resource *res;
int err;
/*
if (!pdev->dev.of_node) //如果不是由设备树构造的platform_device
{
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (res == NULL)
{
printk("gpio_key_probe platform_get_resource err\n");
return -EINVAL;
}
irq = res->start;
}
else //是由设备树构造的platform_device
{
irq = of_irq_get(pdev->dev.of_node, 0);
}
*/
irq = platform_get_irq(pdev,0);
printk("get irq = %d\n", irq);
err = request_irq(irq, gpio_key_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "100ask_gpio_key", NULL);
return err;
}
在/home/ysy/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga/drivers/base/platform.c有platform_get_irq函数,相当于上面的函数,因此,在上述程序中,一个函数就可以实现。
int platform_get_irq(struct platform_device *dev, unsigned int num)
{
#ifdef CONFIG_SPARC
/* sparc does not have irqs represented as IORESOURCE_IRQ resources */
if (!dev || num >= dev->archdata.num_irqs)
return -ENXIO;
return dev->archdata.irqs[num];
#else
struct resource *r;
if (IS_ENABLED(CONFIG_OF_IRQ) && dev->dev.of_node) {
int ret;
ret = of_irq_get(dev->dev.of_node, num);
if (ret >= 0 || ret == -EPROBE_DEFER)
return ret;
}
r = platform_get_resource(dev, IORESOURCE_IRQ, num);
/*
* The resources may pass trigger flags to the irqs that need
* to be set up. It so happens that the trigger flags for
* IORESOURCE_BITS correspond 1-to-1 to the IRQF_TRIGGER*
* settings.
*/
if (r && r->flags & IORESOURCE_BITS)
irqd_set_trigger_type(irq_get_irq_data(r->start),
r->flags & IORESOURCE_BITS);
return r ? r->start : -ENXIO;
#endif
}
EXPORT_SYMBOL_GPL(platform_get_irq);
gpio_keys_100ask {
compatible = "100ask,gpio_key";
key-gpios = <&gpio1 18 GPIO_ACTIVE_HIGH>;
//interrupt-parent = <&gpio1>;
//interrupts = <18 IRQ_TYPE_EDGE_BOTH>;
};
3 调用 of_irq_get 获得中断号
gpio-keys {
compatible = "gpio-keys";
pinctrl-names = "default";
user {
label = "User Button";
gpios = <&gpio5 1 GPIO_ACTIVE_HIGH>;
gpio-key,wakeup;
linux,code = <KEY_1>;
};
};
那么可以使用下面的函数获得引脚和flag:
button->gpio = of_get_gpio_flags(pp, 0, &flags);
bdata->gpiod = gpio_to_desc(button->gpio);
irq = gpiod_to_irq(bdata->gpiod);
举例:
static int sr501_probe(struct platform_device *pdev)
{
/* 1. 获得硬件信息 */
sr501_gpio = gpiod_get(&pdev->dev, NULL, 0);
gpiod_direction_input(sr501_gpio);
irq = gpiod_to_irq(sr501_gpio);
request_irq(irq, sr501_isr, IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, "sr501", NULL);
/* 2. device_create */
device_create(sr501_class, NULL, MKDEV(major, 0), NULL, "/dev/sr501");
return 0;
}
static int gpio_key_probe(struct platform_device *pdev)
{
//struct resource *res;
int err;
/*
if (!pdev->dev.of_node)
{
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (res == NULL)
{
printk("gpio_key_probe platform_get_resource err\n");
return -EINVAL;
}
irq = res->start;
}
else
{
irq = of_irq_get(pdev->dev.of_node, 0);
}
*/
//irq = platform_get_irq(pdev,0);
sr04_trig = gpiod_get(&pdev->dev, "key", GPIOD_IN);
irq = gpiod_to_irq(sr04_trig);
printk("get irq = %d\n", irq);
err = request_irq(irq, gpio_key_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "100ask_gpio_key", NULL);
return err;
}
gpio_keys_100ask {
compatible = "100ask,gpio_key";
key-gpios = <&gpio1 18 GPIO_ACTIVE_HIGH>;
//interrupt-parent = <&gpio1>;
//interrupts = <18 IRQ_TYPE_EDGE_BOTH>;
};