IMX6ULL驱动学习--Platform驱动开发

Platform平台驱动

  • Platform设备驱动
    • 业务逻辑
    • 驱动和设备匹配
      • BUS定义
      • 匹配过程
    • Platform驱动
      • 驱动结构体定义
      • 驱动实现过程
      • 驱动模板
    • Platform设备
      • platform_device结构体
        • platform_device 结构体定义
        • platform_device设备信息框架
      • 设备树
        • 设备信息编辑
  • Platform测试程序
    • 设备信息结构体版
      • platform_device
      • platform_driver
    • 设备树版
      • 设备树信息
      • platform_driver

Platform设备驱动

Platform 设备驱动,又称平台设备驱动。linux系统在考虑到驱动的可重用性时,提出了驱动的分离与分层思想,将驱动与设备硬件信息分开。当需要更改硬件接口时,不需要改变驱动,只需要修改设备信息即可。

业务逻辑

Platform设备驱动分为三部分:设备信息,总线和设备驱动。当注册驱动时,总线在设备列表中查询与之匹配的设备,并将二者联系起来。同样的注册设备,也会反向匹配驱动。

驱动和设备匹配

BUS定义

在include/linux/device.h中有 bus_type 类型定义如下:

struct bus_type {
	const char		*name;
	const char		*dev_name;
	struct device		*dev_root;
	struct device_attribute	*dev_attrs;	/* use dev_groups instead */
	const struct attribute_group **bus_groups;
	const struct attribute_group **dev_groups;
	const struct attribute_group **drv_groups;

	int (*match)(struct device *dev, struct device_driver *drv);
	int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
	int (*probe)(struct device *dev);
	int (*remove)(struct device *dev);
	void (*shutdown)(struct device *dev);

	int (*online)(struct device *dev);
	int (*offline)(struct device *dev);

	int (*suspend)(struct device *dev, pm_message_t state);
	int (*resume)(struct device *dev);

	const struct dev_pm_ops *pm;

	const struct iommu_ops *iommu_ops;

	struct subsys_private *p;
	struct lock_class_key lock_key;
};

其中int (*match)(struct device *dev, struct device_driver *drv);函数很重要,用于设备和驱动的匹配。在drivers/base/platform.c中定义了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平台总线,其中platform_match就是匹配函数。

匹配过程

  • Platform平台总线匹配函数
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);
}

函数中使用了四种匹配方法:

  • OF类型匹配
    设备树采用的匹配方式,其中of_driver_match_device函数在include/linux/of_device.h中定义如下:
static inline int of_driver_match_device(struct device *dev,
					 const struct device_driver *drv)
{
	return of_match_device(drv->of_match_table, dev) != NULL;
}

将驱动中的drv->of_match_table表与设备节点的compatible属性匹配,如果相同则匹配成功,随后probe函数就会执行。

  • ACPI匹配
  • id_table匹配
    每个platform_driver结构体中有一个id_table成员变量,保存了很多id信息,存放着这个platform驱动所支持的驱动类型,将id_table与设备匹配。
  • name字段匹配
    直接比较驱动和设备name字段。如果相同就匹配成功。

Platform驱动

驱动结构体定义

platform_driver结构体表示platform驱动,定义在include/linux/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;
};
  • probe函数
    设备与驱动匹配成功后执行,需用户实现。
  • driver成员
    device_driver结构体变量,Linux 内核里面大量使用到了面向对象的思维, device_driver 相当于基类,提供了最基础的驱动框架。plaform_driver 继承了这个基类,然后在此基础上又添加了一些特有的成员变量。 device_driver定义在include/linux/device,h中,定义如下:
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;
};

of_match_table就是采用设备树的时候驱动使用的匹配表,同样是数组,每个匹配项都为 of_device_id 结构体类型,此结构体定义在文件 include/linux/mod_devicetable.h 中,内容如下:

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

其中compatible很重要,设备树节点的compatible会与of_match_tablecompatible比较,相同就匹配成功。

  • id_table表,用于总线匹配驱动和设备,是一个数组,每个元素为platform_device_id,结果如下:
struct platform_device_id {
	char name[PLATFORM_NAME_SIZE];
	kernel_ulong_t driver_data;
};

驱动实现过程

  • 定义platform_driver结构体变量,实现其成员变量,重点是probe函数和匹配方法,匹配成功后probe函数执行,具体的驱动程序在probe中编写。
  • 在驱动入口函数中调用platform_driver_register函数向内核注册platform驱动,其函数原型如下:
int platform_driver_register (struct platform_driver *driver)
@driver:	要注册的驱动
@return:	负值 失败,0 成功。
  • 在驱动卸载函数中调用platform_driver_unregister卸载驱动,其函数原型如下:
void platform_driver_unregister(struct platform_driver *drv)
@driver:	要卸载的驱动
@return:	无。

驱动模板

......
//设备结构体
struct  xxx_dev
{
    struct cdev cdev;           //cdev
	 /*		设备结构体其他内容	*/
};


struct xxx_dev xxxdev;//设备结构体变量

static int xxx_open(struct inode *inode, struct file *filp)
{
	......
	return 0;
}


static ssize_t xxx_write(struct file *filp,const char __user *buf,size_t cnt, loff_t *offt)
{
	......
    return 0;
}


/*   设备操作函数集合   */
static struct file_operations xxx_fops = {
    .owner = THIS_MODULE,
    .open = xxx_open,
    .write = xxx_write,
};


static int xxx_probe(struct platform_device *dev)
{
	......
    cdev_init(&xxx.cdev,&xxx_fops);
    return 0;
}



static int xxx_remove(struct platform_device *dev)
{
	......
    cdev_del(&leddev.cdev); //删除cdev
    return 0;
}



/*
@description      :   platform 匹配列表。
*/
static const struct of_device_id xxx_of_match[] = 
{
    {   .compatible = "gpio-xxx"  },
    {       }
};



/*
@description      :   platform 驱动结构体。
*/
static struct platform_driver led_driver = {
    .driver = {
        .name = "xxx",   //驱动名字,用于和设备匹配
        .of_match_table = xxx_of_match,
    },
    .probe = xxx_probe,
    .remove = xxx_remove,
};



/*
@description      :   驱动模块加载函数。
@param            :   无   
@return           :   无
*/
static void __init xxx_init(void)
{
    return platform_driver_register(&xxx_driver);
}



/*
@description      :   驱动模块卸载函数。
@param            :   无   
@return           :   无
*/
static void __exit xxx_exit(void)
{
    platform_driver_unregister(&xxx_driver);
}


module_init(xxx_init);
module_exit(xxx_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Turing");

Platform设备

Platform 设备信息有两种方式:

  • platform_device 结构体
  • 设备树

platform_device结构体

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;
};
  • name 表示设备名字,要与platform驱动的name字段相同,否则设备无法匹配驱动。
  • num_resources表示资源数量,一般为resources资源大小
  • resources表示资源,也就是设备信息,比如外设寄存器。定义如下:
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表示设备信息后,需要将设备注册信息注册到内核中,使用函数如下:

int platform_device_register(struct platform_device *pdev)
@pdev	:	要注册的设备信息
@return	:	负数 失败,0 成功。

不再使用platform时需要注销设备,注销函数如下:

void platform_device_unregister(struct platform_device *pdev)
@pdev	:	要注销的设备信息
@return	:	无。

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_IO04_BASE      (0x020E006C)
#define SW_PAD_GPIO1_IO04_BASE      (0x020E02F8)
#define GPIO1_DR_BASE               (0x0209C000)
#define GPIO1_GDIR_BASE             (0x0209C004)
#define REGISTER_LENGTH             4
/*
* @description      : 释放 platform 设备模块的时候会执行
* @param - dev      : 要释放的设备
* @return           : 无
*/
static void led_release(struct device *dev)
{
    printk("led device released!\r\n");
}

/*
 *  设备资源信息,也就是led使用到的资源信息
 */
static struct resource led_resource[] = {
    [0] = {
        .start = CCM_CCGR1_BASE,
        .end = (CCM_CCGR1_BASE + REGISTER_LENGTH - 1),
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = SW_MUX_GPIO1_IO04_BASE,
        .end = (SW_MUX_GPIO1_IO04_BASE + REGISTER_LENGTH - 1),
        .flags = IORESOURCE_MEM,
    },
    [2] = {
        .start = SW_PAD_GPIO1_IO04_BASE,
        .end = (SW_PAD_GPIO1_IO04_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,
    },
};

/*
 *  platform 设备结构体
 */
static struct platform_device leddevice = {
    .name = "imx6ul-led",
    .id = -1,
    .dev =  {
        .release = &led_release,
    },
    .num_resources = ARRAY_SIZE(led_resource),
    .resource = led_resource,
};

/*
* @description      : 设备模块加载
* @param - dev      : 无
* @return           : 无
*/
static int __init leddevice_init(void)
{
    return platform_device_register(&leddevice);
}

/*
* @description      : 设备模块注销
* @param - dev      : 无
* @return           : 无
*/
static void __exit leddevice_exit(void)
{
    return platform_device_register(&leddevice);
}


module_init(leddevice_init);
module_exit(leddevice_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Turing");

设备树

设备的描述信息放在了设备树中,驱动直接读取设备树中的信息即可。

设备信息编辑

  • 创建设备节点
    重点是要设置好compatible属性,用于匹配驱动。例如:
	light {
		#address-cells = <1>;
		#size-cells = <1>;
		compatible = "gpio-light";
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_light>;
		light-gpio = <&gpio1 2 GPIO_ACTIVE_LOW>;
		status = "okay";
		
	};
static const struct of_device_id led_of_match[] = 
{
    {   .compatible = "gpio-light"  },
    {       }
};

设备树中compatiblegpio-light,驱动中的of_match表中compatible也为gpio-light,二者匹配成功。

Platform测试程序

设备信息结构体版

demo gitee链接:https://gitee.com/cnfu/IMX6ULL_drivers_study/tree/master/12.platform

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_IO04_BASE      (0x020E006C)
#define SW_PAD_GPIO1_IO04_BASE      (0x020E02F8)
#define GPIO1_DR_BASE               (0x0209C000)
#define GPIO1_GDIR_BASE             (0x0209C004)
#define REGISTER_LENGTH             4
/*
* @description      : 释放 platform 设备模块的时候会执行
* @param - dev      : 要释放的设备
* @return           : 无
*/
static void led_release(struct device *dev)
{
    printk("led device released!\r\n");
}

/*
 *  设备资源信息,也就是led使用到的资源信息
 */
static struct resource led_resource[] = {
    [0] = {
        .start = CCM_CCGR1_BASE,
        .end = (CCM_CCGR1_BASE + REGISTER_LENGTH - 1),
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = SW_MUX_GPIO1_IO04_BASE,
        .end = (SW_MUX_GPIO1_IO04_BASE + REGISTER_LENGTH - 1),
        .flags = IORESOURCE_MEM,
    },
    [2] = {
        .start = SW_PAD_GPIO1_IO04_BASE,
        .end = (SW_PAD_GPIO1_IO04_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,
    },
};

/*
 *  platform 设备结构体
 */
static struct platform_device leddevice = {
    .name = "imx6ul-led",
    .id = -1,
    .dev =  {
        .release = &led_release,
    },
    .num_resources = ARRAY_SIZE(led_resource),
    .resource = led_resource,
};

/*
* @description      : 设备模块加载
* @param - dev      : 无
* @return           : 无
*/
static int __init leddevice_init(void)
{
    return platform_device_register(&leddevice);
}

/*
* @description      : 设备模块注销
* @param - dev      : 无
* @return           : 无
*/
static void __exit leddevice_exit(void)
{
    return platform_device_register(&leddevice);
}


module_init(leddevice_init);
module_exit(leddevice_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Turing");

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 
#include 

#define LEDDEV_CNT           1
#define LEDDEV_NAME          "platled"

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

static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO04;
static void __iomem *SW_PAD_GPIO1_IO04;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;


//gpioled设备结构体
struct  leddev_dev
{
    /* data */
    dev_t devid;                //设备号
    struct cdev cdev;           //cdev
    struct class *class;        //类
    struct device *device;      //设备
    int major;                  //主设备号
    
};

struct leddev_dev leddev;//led设备
/*
@description        :   led打开或关闭
@param - sta        :   LEDON    
@return             :   无
*/
void led_switch(u8 sta)
{
    u32 val = 0;
    if(sta == LEDON){
        val = readl(GPIO1_DR);
        val &= ~(1 << 4);
        writel(val, GPIO1_DR);
    }
    else if (sta == LEDOFF)
    {
        val = readl(GPIO1_DR);
        val |= (1 << 4);
        writel(val, GPIO1_DR);        
    }
}




/*
@description      :   打开设备
@param - inode    :   传递给驱动的inode   
@param - filp     :   设备文件 
@return           :   0 成功; 其他 失败
*/
static int led_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &leddev;        //设置私有数据
    return 0;
}

/*
@description      :   向设备写数据
@param - filp     :   设备文件,表示打开的文件描述符   
@param - buf      :   写入的数据
@param - cnt      :   数据长度 
@param - offt     :   相对于文件首地址的偏移
@return           :   写入的字节数,如果<0 写入失败
*/
static ssize_t led_write(struct file *filp,const char __user *buf,size_t cnt, loff_t *offt)
{
    int retvalue;
    unsigned char databuf[1];
    unsigned char ledstat;
    retvalue = copy_from_user(databuf,buf,cnt);
    if(retvalue < 0)
        return -EFAULT;
    
    ledstat = databuf[0];
    if(ledstat == LEDON)
    {
        led_switch(LEDON);
    }
    else if (ledstat == LEDOFF)
    {
        led_switch(LEDOFF);
    }

    return 0;
}

/*   设备操作函数集合   */
static struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .write = led_write,
};
/*
@description      :   platform 驱动的 probe函数,驱动与设备匹配之后会执行此函数。
@param - dev      :   platform 设备   
@return           :   0,成功,其他负值,失败
*/
static int led_probe(struct platform_device *dev)
{
    int i = 0;
    int ressize[5];
    u32 val = 0;
    struct resource *ledsource[5];

    printk("led driver and device has matched!\r\n");
    /* 1.获取资源 */
    for(i = 0; i < 5; i++ )
    {
        ledsource[i] = platform_get_resource(dev,IORESOURCE_MEM,i);
        if(!ledsource[i])
        {
            dev_err(&dev->dev,"No MEM Resource for always on\r\n");
            return -ENXIO;
        }
        ressize[i] = resource_size(ledsource[i]);

    }
    /*   2.初始化LED   */
    /*  寄存器映射 */
    IMX6U_CCM_CCGR1 = ioremap(ledsource[0]->start,ressize[0]);
    SW_MUX_GPIO1_IO04 = ioremap(ledsource[1]->start,ressize[1]);
    SW_PAD_GPIO1_IO04 = ioremap(ledsource[2]->start,ressize[2]);
    GPIO1_DR = ioremap(ledsource[3]->start,ressize[3]);
    GPIO1_GDIR = ioremap(ledsource[4]->start,ressize[4]);

    val = readl(IMX6U_CCM_CCGR1);
    val  &= ~(3 << 26);
    val  |= (3 << 26);
    writel(val,IMX6U_CCM_CCGR1);

    writel(5,SW_MUX_GPIO1_IO04);
    writel(0x10B0,SW_PAD_GPIO1_IO04);
        
    //设置GPIO1_IO04为输出功能
    val = readl(GPIO1_GDIR);
    val &= ~(1<<4);
    val |= (1<<4);
    writel(val,GPIO1_GDIR);

    //默认关闭LED
    val = readl(GPIO1_DR);
    val |= (1<<4);
    writel(val,GPIO1_DR);


    //注册字符设备驱动

    //1.创建设备号
    if(leddev.major)
    {
        leddev.devid = MKDEV(leddev.major,0);
        register_chrdev_region(leddev.devid,LEDDEV_CNT,LEDDEV_NAME);
    }
    else
    {
        alloc_chrdev_region(&leddev.devid,0,LEDDEV_CNT,LEDDEV_NAME);//申请设备号
        leddev.major = MAJOR(leddev.devid);//获取主设备号

    }

    printk("leddev major=%d\r\n",leddev.major);
    
    //2.初始化cdev
    leddev.cdev.owner = THIS_MODULE;
    cdev_init(&leddev.cdev,&led_fops);

    //3.添加cdev
    cdev_add(&leddev.cdev,leddev.devid,LEDDEV_CNT);

    //4.创建类
    leddev.class = class_create(THIS_MODULE,LEDDEV_NAME);
    if(IS_ERR(leddev.class))
    {
        return PTR_ERR(leddev.class);
    }
    //5.创建设备
    leddev.device = device_create(leddev.class,NULL,leddev.devid,NULL,LEDDEV_NAME);
    if(IS_ERR(leddev.device))
    {
        return PTR_ERR(leddev.device);
    }
    return 0;
}

/*
@description      :   移除platform 驱动时会执行此函数。
@param - dev      :   platform 设备   
@return           :   0,成功,其他负值,失败
*/
static int led_remove(struct platform_device *dev)
{
    iounmap(IMX6U_CCM_CCGR1);
    iounmap(SW_MUX_GPIO1_IO04);
    iounmap(SW_PAD_GPIO1_IO04);
    iounmap(GPIO1_DR);
    iounmap(GPIO1_GDIR);

    cdev_del(&leddev.cdev); //删除cdev
    unregister_chrdev_region(leddev.devid,LEDDEV_CNT);
    device_destroy(leddev.class,leddev.devid);
    class_destroy(leddev.class);

    return 0;

}
/*
@description      :   platform 驱动结构体。
*/
static struct platform_driver led_driver = {
    .driver = {
        .name = "imx6ul-led",   //驱动名字,用于和设备匹配
    },
    .probe = led_probe,
    .remove = led_remove,
};
/*
@description      :   驱动模块加载函数。
@param            :   无   
@return           :   无
*/
static void __init leddriver_init(void)
{
    return platform_driver_register(&led_driver);
}

/*
@description      :   驱动模块卸载函数。
@param            :   无   
@return           :   无
*/
static void __exit leddriver_exit(void)
{
    platform_driver_unregister(&led_driver);
}

module_init(leddriver_init);
module_exit(leddriver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Turing");

设备树版

demo gitee链接:https://gitee.com/cnfu/IMX6ULL_drivers_study/tree/master/13.dtsplatform

设备树信息

	light {
		#address-cells = <1>;
		#size-cells = <1>;
		compatible = "gpio-light";
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_light>;
		light-gpio = <&gpio1 2 GPIO_ACTIVE_LOW>;
		status = "okay";
		
	};

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 
#include 

#define LEDDEV_CNT           1
#define LEDDEV_NAME          "dtsplatled"

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


//gpioled设备结构体
struct  leddev_dev
{
    /* data */
    dev_t devid;                //设备号
    struct cdev cdev;           //cdev
    struct class *class;        //类
    struct device *device;      //设备
    int major;                  //主设备号
    struct device_node *node;   //LED设备节点
    int led0;                   //LED灯GPIO编号    
};

struct leddev_dev leddev;//led设备
/*
@description        :   led打开或关闭
@param - sta        :   LEDON    
@return             :   无
*/
void led_switch(u8 sta)
{
    u32 val = 0;
    if(sta == LEDON){
        gpio_set_value(leddev.led0,0);
    }
    else if (sta == LEDOFF)
    {
        gpio_set_value(leddev.led0,1);
    }
}




/*
@description      :   打开设备
@param - inode    :   传递给驱动的inode   
@param - filp     :   设备文件 
@return           :   0 成功; 其他 失败
*/
static int led_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &leddev;        //设置私有数据
    return 0;
}

/*
@description      :   向设备写数据
@param - filp     :   设备文件,表示打开的文件描述符   
@param - buf      :   写入的数据
@param - cnt      :   数据长度 
@param - offt     :   相对于文件首地址的偏移
@return           :   写入的字节数,如果<0 写入失败
*/
static ssize_t led_write(struct file *filp,const char __user *buf,size_t cnt, loff_t *offt)
{
    int retvalue;
    unsigned char databuf[2];
    unsigned char ledstat;
    retvalue = copy_from_user(databuf,buf,cnt);
    if(retvalue < 0)
        return -EFAULT;
    
    ledstat = databuf[0];
    if(ledstat == LEDON)
    {
        led_switch(LEDON);
    }
    else if (ledstat == LEDOFF)
    {
        led_switch(LEDOFF);
    }

    return 0;
}

/*   设备操作函数集合   */
static struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .write = led_write,
};
/*
@description      :   platform 驱动的 probe函数,驱动与设备匹配之后会执行此函数。
@param - dev      :   platform 设备   
@return           :   0,成功,其他负值,失败
*/
static int led_probe(struct platform_device *dev)
{
    // int i = 0;
    // int ressize[5];
    // u32 val = 0;
    // struct resource *ledsource[5];

    printk("led driver and device has matched!\r\n");


    //注册字符设备驱动

    //1.创建设备号
    if(leddev.major)
    {
        leddev.devid = MKDEV(leddev.major,0);
        register_chrdev_region(leddev.devid,LEDDEV_CNT,LEDDEV_NAME);
    }
    else
    {
        alloc_chrdev_region(&leddev.devid,0,LEDDEV_CNT,LEDDEV_NAME);//申请设备号
        leddev.major = MAJOR(leddev.devid);//获取主设备号

    }

    printk("leddev major=%d\r\n",leddev.major);
    
    //2.初始化cdev
    leddev.cdev.owner = THIS_MODULE;
    cdev_init(&leddev.cdev,&led_fops);

    //3.添加cdev
    cdev_add(&leddev.cdev,leddev.devid,LEDDEV_CNT);

    //4.创建类
    leddev.class = class_create(THIS_MODULE,LEDDEV_NAME);
    if(IS_ERR(leddev.class))
    {
        return PTR_ERR(leddev.class);
    }
    //5.创建设备
    leddev.device = device_create(leddev.class,NULL,leddev.devid,NULL,LEDDEV_NAME);
    if(IS_ERR(leddev.device))
    {
        return PTR_ERR(leddev.device);
    }

    /*  6.初始化IO  */
    leddev.node = of_find_node_by_path("/light");
    if(leddev.node == NULL)
    {
        printk("gpioled node not find!\r\n");
        return -EINVAL;
    }

    leddev.led0 = of_get_named_gpio(leddev.node,"light-gpio",0);
    if(leddev.led0 < 0)
    {
        printk("can not get light-gpio!\r\n");
        return -EINVAL;
    }

    gpio_request(leddev.led0,"led0");
    gpio_direction_output(leddev.led0,1);

    return 0;
}

/*
@description      :   移除platform 驱动时会执行此函数。
@param - dev      :   platform 设备   
@return           :   0,成功,其他负值,失败
*/
static int led_remove(struct platform_device *dev)
{

    gpio_set_value(leddev.led0,1);//恢复默认值

    cdev_del(&leddev.cdev); //删除cdev
    unregister_chrdev_region(leddev.devid,LEDDEV_CNT);
    device_destroy(leddev.class,leddev.devid);
    class_destroy(leddev.class);

    return 0;

}

/*
@description      :   platform 匹配列表。
*/
static const struct of_device_id led_of_match[] = 
{
    {   .compatible = "gpio-light"  },
    {       }
};

/*
@description      :   platform 驱动结构体。
*/
static struct platform_driver led_driver = {
    .driver = {
        .name = "gpio-light",   //驱动名字,用于和设备匹配
        .of_match_table = led_of_match,
    },
    .probe = led_probe,
    .remove = led_remove,
};
/*
@description      :   驱动模块加载函数。
@param            :   无   
@return           :   无
*/
static void __init leddriver_init(void)
{
    return platform_driver_register(&led_driver);
}

/*
@description      :   驱动模块卸载函数。
@param            :   无   
@return           :   无
*/
static void __exit leddriver_exit(void)
{
    platform_driver_unregister(&led_driver);
}

module_init(leddriver_init);
module_exit(leddriver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Turing");

你可能感兴趣的:(linux)