Linux驱动_Platform平台驱动

        在Linux操作系统中,驱动程序占用了Linux内核代码量的大头,如果不进行管理,将会造成数量庞大的结果。因此引入了驱动的分离

Linux驱动_Platform平台驱动_第1张图片         

        上图所示就是驱动的分离,相当于驱动使用标准方法去获取到设备信息(比如从设备树中获取到设备信息),然后根据获取到的设备信息来初始化设备。 这样就相当于驱动只负责驱动,设备只负责设备,想办法将两者进行匹配即可。这个就是 Linux 中的总线(bus)驱动(driver)设备(device)模型,也就是常说的驱动分离。总线就是驱动和设备信息的月老,负责给两者牵线搭桥。

一、platform 平台驱动

        为了允许没有总线模型概念的外设使用总线(bus)、驱动(driver)和设备(device)模型。Linux系统提出了platform 这个虚拟总线。        

struct bus_type platform_bus_type = {
     .name = "platform",
     .dev_groups = platform_dev_groups,
     .match = platform_match,
     .uevent = platform_uevent,
     .pm = &platform_dev_pm_ops,
 };

        platform_bus_type就是platform平台总线,其中最关键的就是.match = platform_match,总线就是通过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_match函数中包含了4中驱动和设备匹配的方法。分别为:

①设备树匹配的方法
②ACPI匹配方式
③id_table匹配方式
④通过驱动和设备.name匹配

并且四种方式的优先级为:①>②>③>④。主要学习方式①和④。

二、通过驱动和设备.name匹配

        platform平台设备驱动,它只不过是将 device 进一步封装成为 platform_device,将 device_driver 进一步封装成为 platform_device_driver
        要想使用platform平台驱动,需要定义在设备中定义 platform_device结构体(如果采用设备数匹配方式的话则不用定义设备结构体),在驱动中定义platform_driver结构体。

 platform_driver结构体

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;
};

        int (*probe)(struct platform_device *)表示probe函数指针,当驱动与设备匹配成功以后 probe 函数就会执行。
        struct device_driver driver表示device_driver结构体,在此处定义类似于C++中的继承特性,即platform_driver结构体继承了device_driver结构体的成员变量。device_driver结构体具体定义如下:             

struct device_driver {
	const char		*name;
	struct bus_type		*bus;

	struct module		*owner;
	const char		*mod_name;	/* used for built-in modules */

	bool suppress_bind_attrs;	/* disables bind/unbind via sysfs */

	const struct of_device_id	*of_match_table;
	const struct acpi_device_id	*acpi_match_table;

	int (*probe) (struct device *dev);
	int (*remove) (struct device *dev);
	void (*shutdown) (struct device *dev);
	int (*suspend) (struct device *dev, pm_message_t state);
	int (*resume) (struct device *dev);
	const struct attribute_group **groups;

	const struct dev_pm_ops *pm;

	struct driver_private *p;
};

        name表示设备的名字,需要和设备中使用的name相匹配。
        probe 函数,当设备和驱动匹配以后此函数就会执行。
        remove 函数,当卸载platform驱动的时候此函数就会执行。
        of_match_table 就是采用设备树的时候驱动使用的匹配表,同样是数组,每个匹
配项都为 of_device_id 结构体类型。定义如下:

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

        compatible,对于设备树而言,就是通过设备节点的 compatible 属性值和 of_match_table 中每个项目的 compatible 成员变量进行比较,如果有相等的就表示设备和此驱动匹配成功。

注册platform_driver设备

        使用platform 驱动的时候,首先定义一个 platform_driver 结构体变量,然后实现结构体
中的各个成员变量,重点是实现匹配方法以及 probe 函数。当驱动和设备匹配成功以后 probe
函数就会执行。
        当我们定义并初始化好 platform_driver 结构体变量以后,需要在驱动入口函数里面调用
platform_driver_register 函数向 Linux 内核注册一个 platform 驱动。

int platform_driver_register (struct platform_driver *driver)

函数参数和返回值含义如下:
driver:要注册的 platform 驱动。
返回值: 负数,失败; 0,成功 

        驱动卸载函数中通过platform_driver_unregister函数卸载 platform 驱动。

void platform_driver_unregister(struct platform_driver *drv)

函数参数和返回值含义如下:
drv:要卸载的 platform 驱动。
返回值: 无

platform_device结构体

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;
};

        name表示设备的名字,需要和驱动中使用的name相匹配,方式④就是通过name来完成驱动和设备的匹配的。
        num_resources表示struct resource *resource结构体指针指向的资源的大小。
        struct resource *resource为结构体指针,指向设备信息。具体定义如下:

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

        startend 分别表示资源的起始和终止信息,对于内存类的资源,就表示内存起始和终止
地址,name 表示资源名字,flags表示资源类型。

注册platform_device设备

        在不使用方式①设备树,利用方式④匹配方法中,需要使用两个函数完成platform_device设备的注册和注销。

int platform_device_register(struct platform_device *pdev)

函数参数和返回值含义如下:
pdev:要注册的 platform 设备。
返回值: 负数,失败; 0,成功。

void platform_device_unregister(struct platform_device *pdev)

函数参数和返回值含义如下:
pdev:要注销的 platform 设备。
返回值: 无 

实验代码:

platform_driver驱动代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

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

#define PLATFORMLED_NAME "platformled"
#define PLATFORMLED_COUNT 1

struct newchrled_dev newchrled; /*led设备*/


/*映射后虚拟地址指针*/
static void __iomem *IMX6ULL_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;


/*LED设备结构体*/ 
struct newchrled_dev{
    struct cdev cdev;   /*字符设备结构体*/
    dev_t devid;    /*定义设备号*/
    struct class *class;    /*类*/
    struct device *device;  /*创建设备*/
    int major;  /*定义主设备号*/
    int minor;  /*定义次设备号*/
};


static void LED_switch(u8 sta){
    u32 val = 0;
    if(sta == LEDON){
        val = readl(GPIO1_DR);
        val &= ~(1 << 3);
        writel(val, GPIO1_DR);
    }
    else if(sta == LEDOFF){
        val = readl(GPIO1_DR);
        val |= (1 << 3);
        writel(val, GPIO1_DR);
    }
}
static int newchrled_open(struct inode *inode, struct file *filp){
        filp->private_data = &newchrled;
        return 0;
}

static ssize_t newchrled_write(struct file *filp, const char __user *buf,
			 size_t count, loff_t *ppos){
    int retvalue;
    unsigned char databuf[1];

    retvalue = copy_from_user(databuf, buf, count);
    if(retvalue < 0){
        printk("kernel write error \r\n");
        return -EFAULT;
    }

    /*开灯还是关灯*/
    LED_switch(databuf[0]);
    return 0;
}
    
static int newchrled_release(struct inode *inode, struct file *filp){
    return 0;
}


static const struct file_operations newchrled_fops = {
    .owner = THIS_MODULE,
    .write = newchrled_write,
    .open = newchrled_open,
    .release = newchrled_release,
};



static int led_probe(struct platform_device *dev){
    int ret = 0;
    unsigned int val = 0;
    int i;
    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;
    }


    /*led初始化*/
     /*2、地址映射*/
    IMX6ULL_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]));

    /*3、初始化*/
    /*开启时钟*/
    val = readl(IMX6ULL_CCM_CCGR1);
    val &= ~(3 << 26);  //清除先前的26,27位
    val |= (3 << 26);   //26,27位置1
    writel(val, IMX6ULL_CCM_CCGR1);

    /*配置GPIO1_IO03*/
    writel(0x5, SW_MUX_GPIO1_IO03); //设置复用
    writel(0x10B0, SW_PAD_GPIO1_IO03);  //设置电气属性

    val = readl(GPIO1_GDIR);
    val |= (1 << 3);
    writel(val, GPIO1_GDIR);
    /*注册字符设备*/
    if(newchrled.major){    /*给定主设备号*/
        newchrled.devid = MKDEV(newchrled.major, 0);    /*获取设备号*/
        ret = register_chrdev_region(newchrled.devid, PLATFORMLED_COUNT, PLATFORMLED_NAME);
    }
    else{   /*s否则自动申请设备号*/
        ret = alloc_chrdev_region(&newchrled.devid, 0, PLATFORMLED_COUNT, PLATFORMLED_NAME);
        newchrled.major = MAJOR(newchrled.devid);
        newchrled.minor = MINOR(newchrled.devid);
    }
    if(ret < 0){
        printk("newchrled register error!\r\n");
        goto fail_devid;
    }
    printk("newchrled major = %d, minor = %d \r\n", newchrled.major, newchrled.minor);//打印主次设备号

    newchrled.cdev.owner = THIS_MODULE;
    cdev_init(&newchrled.cdev, &newchrled_fops);
    ret = cdev_add(&newchrled.cdev, newchrled.devid, PLATFORMLED_COUNT);
     if(ret < 0){
        goto fail_cdev;
    }
   
    /*自动创建设备节点*/
    newchrled.class = class_create(THIS_MODULE, PLATFORMLED_NAME);
    if(IS_ERR(newchrled.class)){
        ret = PTR_ERR(newchrled.class);
        goto fail_class;
    }

    newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, PLATFORMLED_NAME);
     if(IS_ERR(newchrled.device)){
         ret = PTR_ERR(newchrled.device);
        goto fail_device;
    }
    return 0;

fail_device:
    class_destroy(newchrled.class);
fail_class:
    cdev_del(&newchrled.cdev);
fail_cdev:
    unregister_chrdev_region(newchrled.devid, PLATFORMLED_COUNT);
fail_devid:
    return ret;
}

static int led_remove(struct platform_device *dev){
    unsigned int val = 0;
    /*关闭LED*/
   
    val = readl(GPIO1_DR);
    val |= (1 << 3);
    writel(val, GPIO1_DR);

    /*取消地址映射*/
    iounmap(IMX6ULL_CCM_CCGR1);
    iounmap(SW_MUX_GPIO1_IO03);
    iounmap(SW_PAD_GPIO1_IO03);
    iounmap(GPIO1_DR);
    iounmap(GPIO1_GDIR);

    /*删除字符设备*/
    cdev_del(&newchrled.cdev);
    /*注销设备号*/
    unregister_chrdev_region(newchrled.devid, PLATFORMLED_COUNT);

    /*销毁设备*/
    device_destroy(newchrled.class, newchrled.devid);
    /*销毁类*/
    class_destroy(newchrled.class);

    return 0;
}




/*platform结构体*/
static struct platform_driver led_driver = {
    .driver = {
        .name = "imx6ul-led",
    },
    .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驱动*/
    platform_driver_unregister(&led_driver);
}


/*驱动入口和出口*/
module_init(leddriver_init);
module_exit(leddriver_exit);

/*协议*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZYC");

platform_device驱动代码: 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


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



void leddevice_release(struct device *dev){
    printk("leddevice release\r\n");
}


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


static struct platform_device leddevice = {
  .name = "imx6ul-led",
  .id = -1,
  .dev = {
      .release = &leddevice_release,
  },
  .num_resources = ARRAY_SIZE(led_resources),
  .resource = led_resources,
};

static int __init leddevice_init(void){

    /*注册platform设备*/
    return platform_device_register(&leddevice);
}

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


/*驱动入口和出口*/
module_init(leddevice_init);
module_exit(leddevice_exit);

/*协议*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZYC");

        代码流程:①注册platform_driver②注册platform_device(①和②顺序可以互换)
③.name是否匹配?
执行.probe函数进行初始化:选择其他匹配方法;
④卸载驱动执行.remove函数。

三、设备树匹配方法

        在使用设备树的时候,只需要配置platform_driver驱动,不用配置platform_device。platform 驱动会通过of_match_table来保存兼容性值,也就是表明此驱动兼容哪些设备。

实验代码如下:

        

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


#define GPIOLED_COUNT 1
#define GPIOLED_NAME "dtsplatformled"

#define LEDOFF 0
#define LEDON 1

/*gpio设备结构体*/
struct gpioled_dev{
    dev_t devid; /*设备号*/
    int major; /*主设备号*/
    int minor; /*次设备号*/
    struct cdev cdev;
    struct class *class;    /*创建类*/
    struct device *device;  /*创建设备*/
    struct device_node *node; /*设备节点*/
    int led_gpio;
};

struct gpioled_dev gpioled;

static int gpioled_open(struct inode *inode, struct file *filp){
    filp->private_data = &gpioled; /* 设置私有数据 */
    return 0;
}

static ssize_t gpioled_write(struct file *filp, const char __user *buf,size_t cnt, loff_t *offt)
{
    int ret = 0;
    unsigned char databuf[1];
    
    ret = copy_from_user(databuf, buf, cnt);
    if(ret < 0){
        return -EINVAL;
    }
    if(databuf[0] == LEDON){
        gpio_set_value(gpioled.led_gpio, 0);
    }
    else if(databuf[0] == LEDOFF){
        gpio_set_value(gpioled.led_gpio, 1);
    }
    return 0;
}

static int gpioled_release(struct inode *inode, struct file *filp){
    

    return 0;
}

/*定义字符操作集*/
static const struct file_operations gpioled_fops = {
    .owner = THIS_MODULE,
    .write = gpioled_write,
    .open = gpioled_open,
    .release = gpioled_release,
};


static int led_probe(struct platform_device *dev){
     int ret = 0;

    printk("led_probe! \r\n");

    gpioled.major = 0;  /*linux内核自动申请设备号*/
    /*注册字符设备驱动*/
    if(gpioled.major){  /*给定主设备号*/
        gpioled.devid = MKDEV(gpioled.major, 0);
        ret = register_chrdev_region(gpioled.devid, GPIOLED_COUNT, GPIOLED_NAME);
        }
        else{   /*没给定设备号*/
        ret = alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_COUNT, GPIOLED_NAME);
        gpioled.major = MAJOR(gpioled.devid);   /*保存主设备号*/
        gpioled.minor = MINOR(gpioled.devid);   /*保存次设备号*/
    }
    if(ret < 0){
        goto failed_devid;
    }
        printk("gpioled major = %d ,minor = %d \r\n",gpioled.major,gpioled.minor);

        /*初始化cdev*/
        gpioled.cdev.owner = THIS_MODULE;
        cdev_init(&gpioled.cdev, &gpioled_fops);
        
        /*添加cdev*/
        ret = cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_COUNT);
        if(ret < 0){
            goto failed_cdev;
        }

        
        /*自动创建设备节点*/
        /*创建类*/
        gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
        if(IS_ERR(gpioled.class)){   /*判断是否创建类成功*/
            ret = PTR_ERR(gpioled.class);
            goto failed_class;
        }
        /*创建设备*/
        gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
        if(IS_ERR(gpioled.device)){   /*判断是否创建类成功*/
            ret = PTR_ERR(gpioled.device);
            goto failed_device;
        }

         /*获取设备节点*/
        gpioled.node = of_find_node_by_path("/gpioled");
        if(gpioled.node == NULL){    /*寻找节点失败*/
            ret = -EINVAL;
            goto failed_findnode;
        }

        /*获取led所对应的gpio*/
        gpioled.led_gpio = of_get_named_gpio(gpioled.node, "led-gpios", 0);
        if(gpioled.led_gpio < 0){
            printk("can't find led gpio \r\n");
            ret = -EINVAL;
            goto failed_findnode;
        }

        printk("led gpio num = %d \r\n",gpioled.led_gpio);

        /*申请gpio*/
        ret = gpio_request(gpioled.led_gpio, "led-gpios");
        if(ret){
            printk("Failed to request gpio \r\n");
            ret = -EINVAL;
            goto failed_findnode;
        }

        /*使用IO,申请为输出*/
        ret = gpio_direction_output(gpioled.led_gpio, 1); /*设置为输出,高电平不点亮*/
        if(ret < 0){
            goto failed_setoutput;
        }

        /*输出低电平,点亮gpio*/
        gpio_set_value(gpioled.led_gpio, 0);


        return 0;

failed_setoutput:
        gpio_free(gpioled.led_gpio);
failed_findnode:
        device_destroy(gpioled.class, gpioled.devid);
failed_device:
        class_destroy(gpioled.class);
failed_class:
        cdev_del(&gpioled.cdev);
failed_cdev:
        unregister_chrdev_region(gpioled.devid, GPIOLED_COUNT);
failed_devid:
        return ret;
    
    return 0;
}

static int led_remove(struct platform_device *dev){
    printk("led_removes! \r\n");

     /*关灯*/
    /*输出高电平,关闭gpio*/
        gpio_set_value(gpioled.led_gpio, 1);
    /*注销字符设备驱动*/
    cdev_del(&gpioled.cdev);
    unregister_chrdev_region(gpioled.devid, GPIOLED_COUNT);

    device_destroy(gpioled.class, gpioled.devid);
    class_destroy(gpioled.class);

    /*释放IO*/
    gpio_free(gpioled.led_gpio);

    return 0;
}


struct of_device_id led_of_match[] = {
    {.compatible = "alientek,gpioled"},
    {/* Sentinel */},

};

struct platform_driver led_driver = {
    .driver ={
        .name = "imx6ul-led",   /*无设备树的时候,会和设备里面platform_device->driver->name匹配*/
        .of_match_table = led_of_match, /*设备树匹配表*/
    },
    .probe = led_probe,
    .remove = led_remove,
};


/*驱动加载函数*/
static int __init leddriver_init(void){

    /*注册platform驱动*/
    platform_driver_register(&led_driver);
    return 0;
}

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


/*驱动入口和出口*/
module_init(leddriver_init);
module_exit(leddriver_exit);

/*协议*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZYC");

  代码流程:①注册platform_driver        ②.compatible是否匹配?执行.probe函数进行初始化:选择其他匹配方法;③卸载驱动执行.remove函数。

总结:

        platform平台设备驱动是基于设备总线驱动模型的,它只不过是将 device 进一步封装成为 platform_device,将 device_driver 进一步封装成为 platform_device_driver,将我们之前学过的驱动代码套了一层壳。



 

你可能感兴趣的:(linux驱动,#,IMX6ULL,linux,嵌入式,c语言)