IMX6ULL驅動學習--Platform驅動開發

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 <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

/*  寄存器物理地址  */
#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 <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

/*  寄存器物理地址  */
#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 <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/of_address.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#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 <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/of_address.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#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");
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章