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_table
的compatible
比較,相同就匹配成功。
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;
};
start
和end
分別表示資源起始和終止信息,通常指內存起始和終止地址,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" },
{ }
};
設備樹中compatible
爲gpio-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");